diff --git a/ruby/lib/jam_ruby/constants/validation_messages.rb b/ruby/lib/jam_ruby/constants/validation_messages.rb index 724eb5680..ea914a4c9 100644 --- a/ruby/lib/jam_ruby/constants/validation_messages.rb +++ b/ruby/lib/jam_ruby/constants/validation_messages.rb @@ -79,6 +79,10 @@ module ValidationMessages # notification DIFFERENT_SOURCE_TARGET = 'can\'t be same as the sender' + # mods + MODS_NO_SHOW_MUST_BE_HASH = 'no_show must be a hash' + MODS_UNKNOWN_KEY = 'unknown mod' + # takes either a string/string hash, or a string/array-of-strings|symbols hash, # and creates a ActiveRecord.errors style object def createValidationStyleObject(validation_errors) diff --git a/ruby/lib/jam_ruby/models/user.rb b/ruby/lib/jam_ruby/models/user.rb index a9c278566..af931a0ac 100644 --- a/ruby/lib/jam_ruby/models/user.rb +++ b/ruby/lib/jam_ruby/models/user.rb @@ -155,6 +155,7 @@ module JamRuby validate :validate_avatar_info validate :email_case_insensitive_uniqueness validate :update_email_case_insensitive_uniqueness, :if => :updating_email + validate :validate_mods scope :musicians, where(:musician => true) scope :fans, where(:musician => false) @@ -185,6 +186,17 @@ module JamRuby errors.add(:musician_instruments, ValidationMessages::INSTRUMENT_LIMIT_EXCEEDED) if !administratively_created && musician && musician_instruments.length > 5 end + # let's work to stop junk from getting into the mods array; this is essentially the schema + def validate_mods + mods_json.each do |key, value| + if key == "no_show" + errors.add(:mods, ValidationMessages::MODS_NO_SHOW_MUST_BE_HASH) unless value.is_a?(Hash) + else + errors.add(:mods, ValidationMessages::MODS_UNKNOWN_KEY) + end + end + end + def validate_current_password # checks if the user put in their current password (used when changing your email, for instance) errors.add(:current_password, ValidationMessages::NOT_YOUR_PASSWORD) if should_confirm_existing_password? && !valid_password?(self.current_password) @@ -355,7 +367,20 @@ module JamRuby # mods comes back as text; so give ourselves a parsed version def mods_json - @mods_json ||= mods ? JSON.parse(mods, symbolize_names: true) : {} + @mods_json ||= mods ? JSON.parse(mods) : {} + end + + # new_modes should be a regular hash with non-symbolized keys (vs symbolized keys) + def mod_merge(new_mods) + self.mods = (mods_json.merge(new_mods) do |key, old_val, new_val| + if key == "no_show" + # we take the values from previous no_shows, and merge it with the new no_shows + old_val.merge(new_val) + else + raise "unknown in mode_merge key: #{key}" + end + end).to_json + @mods_json = nil # invalidate this since we've updated self.mods end def heartbeat_interval_client diff --git a/ruby/spec/jam_ruby/models/user_spec.rb b/ruby/spec/jam_ruby/models/user_spec.rb index 29a39fa48..36425e495 100644 --- a/ruby/spec/jam_ruby/models/user_spec.rb +++ b/ruby/spec/jam_ruby/models/user_spec.rb @@ -77,14 +77,9 @@ describe User do it { should be_valid } end - describe "when mods is empty" do - before { @user.mods = 'nil' } - it { should_not be_valid } - end - describe "when mods is json object" do - before { @user.mods = '{"key":"value"}' } + before { @user.mods = '{"no_show":{"value": true}}' } it { should be_valid } end @@ -453,25 +448,9 @@ describe User do describe "mods" do it "should allow update of JSON" do - @user.mods = {some_field: 5}.to_json + @user.mods = {no_show: {something:1}}.to_json @user.save! end - - it "should return heartbeart interval" do - @user.heartbeat_interval_client.should be_nil - @user.mods = {heartbeat_interval_client: 5}.to_json - @user.save! - @user = User.find(@user.id) # necessary because mods_json is cached in the model - @user.heartbeat_interval_client.should == 5 - end - - it "should return connection_expire_time" do - @user.connection_expire_time_client.should be_nil - @user.mods = {connection_expire_time_client: 5}.to_json - @user.save! - @user = User.find(@user.id) # necessary because mods_json is cached in the model - @user.connection_expire_time_client.should == 5 - end end describe "audio latency" do @@ -587,6 +566,52 @@ describe User do end end + describe "mods_merge" do + let(:user) {FactoryGirl.create(:user)} + + it "allow empty merge" do + user.mod_merge({}) + user.valid?.should be_true + user.mods.should == {}.to_json + end + + it "allow no_show set" do + user.mod_merge({"no_show" => {"some_screen" => true}}) + user.valid?.should be_true + user.mods.should == {no_show:{some_screen:true}}.to_json + end + + it "allow no_show aggregation" do + user.mod_merge({"no_show" => {"some_screen1" => true}}) + user.save! + user.reload + user.mod_merge({"no_show" => {"some_screen2" => true}}) + user.valid?.should be_true + user.mods.should == {"no_show" => {"some_screen1" => true, "some_screen2" => true}}.to_json + end + + it "allow no_show override" do + user.mod_merge({"no_show" => {"some_screen1" => true}}) + user.save! + user.reload + user.mod_merge({"no_show" => {"some_screen1" => false}}) + user.valid?.should be_true + user.mods.should == {no_show:{some_screen1:false}}.to_json + end + + it "does not allow random root keys" do + user.mod_merge({random_root_key:true}) + user.valid?.should be_false + user.errors[:mods].should == [ValidationMessages::MODS_UNKNOWN_KEY] + end + + it "does not allow non-hash no_show" do + user.mod_merge({no_show:true}) + user.valid?.should be_false + user.errors[:mods].should == [ValidationMessages::MODS_NO_SHOW_MUST_BE_HASH] + end + end + =begin describe "update avatar" do diff --git a/web/app/assets/javascripts/dialog/banner.js b/web/app/assets/javascripts/dialog/banner.js index b34134129..991d14d67 100644 --- a/web/app/assets/javascripts/dialog/banner.js +++ b/web/app/assets/javascripts/dialog/banner.js @@ -6,12 +6,16 @@ context.JK = context.JK || {}; context.JK.Banner = (function () { + var modUtils = context.JK.ModUtils; var self = this; var logger = context.JK.logger; var $banner = null; var $closeBtn = null; var $yesBtn = null; var $noBtn = null; + var $noShow = null; + var $noShowCheckbox = null; + var $buttons = null; // you can also do // * showAlert('title', 'text') @@ -77,7 +81,7 @@ - if((options.type == "alert" && !options.buttons) || options.close) { + if((options.type == "alert" && !options.buttons) || options.close || options.no_show) { var closeButtonText = 'CLOSE'; if(options.close !== null && typeof options.close == 'object') { @@ -87,13 +91,22 @@ } } + if(options.no_show) { + $buttons.addClass('center') + $noShowCheckbox.data('no_show', options.no_show) + $noShow.show() + } $closeBtn.show().text(closeButtonText).unbind('click').click(function() { + if ($noShowCheckbox.is(':visible') && $noShowCheckbox.is(':checked')) { + modUtils.updateNoShow($noShowCheckbox.data('no_show')) + } hide(); return false; }); } else { $closeBtn.hide(); + $noShow.hide(); } if(options.type == "yes_no") { @@ -118,7 +131,6 @@ } if(options.buttons) { - var $buttons = $banner.find('.buttons') context._.each(options.buttons, function(button, i) { if(!button.name) throw "button.name must be specified"; if(!button.click) throw "button.click must be specified"; @@ -148,6 +160,9 @@ $banner.find('.user-btn').remove(); $('#banner_overlay .dialog-inner').html(""); $('#banner_overlay').hide(); + $buttons.removeClass('center') + $noShowCheckbox.data('no_show', null).iCheck('uncheck').attr('checked', false) + $buttons.children().hide(); } function initialize() { @@ -157,6 +172,11 @@ $closeBtn = $banner.find('.close-btn'); $yesBtn = $banner.find('.yes-btn'); $noBtn = $banner.find('.no-btn'); + $noShowCheckbox = $banner.find('.no-more-show-checkbox') + $noShow = $banner.find('.no-more-show') + $buttons = $banner.find('.buttons') + + context.JK.checkbox($noShowCheckbox); return self; } diff --git a/web/app/assets/javascripts/fakeJamClient.js b/web/app/assets/javascripts/fakeJamClient.js index 294580721..899ca4274 100644 --- a/web/app/assets/javascripts/fakeJamClient.js +++ b/web/app/assets/javascripts/fakeJamClient.js @@ -427,7 +427,8 @@ stereo: true, volume_left: -40, volume_right:-40, - instrument_id:50 // see globals.js + instrument_id:50, // see globals.js + mode: false }); } return response; @@ -489,6 +490,7 @@ function SessionGetDeviceLatency() { return 10.0; } function SessionPageEnter() {logger.debug("FakeJamClient: SessionPageEnter"); return {}} function SessionPageLeave() {logger.debug("FakeJamClient: SessionPageLeave")} + function SetMixerMode(mode) {} function SessionGetMasterLocalMix() { logger.debug('SessionGetMasterLocalMix. Returning: ' + _mix); return _mix; @@ -929,6 +931,7 @@ this.SessionGetTracksPlayDurationMs = SessionGetTracksPlayDurationMs; this.SessionPageEnter = SessionPageEnter; this.SessionPageLeave = SessionPageLeave; + this.SetMixerMode = SetMixerMode; this.SetVURefreshRate = SetVURefreshRate; this.SessionGetMasterLocalMix = SessionGetMasterLocalMix; diff --git a/web/app/assets/javascripts/globals.js b/web/app/assets/javascripts/globals.js index eb6fd0b27..0e6e05e99 100644 --- a/web/app/assets/javascripts/globals.js +++ b/web/app/assets/javascripts/globals.js @@ -39,7 +39,8 @@ FILE_MANAGER_CMD_START : 'file_manager_cmd_start', FILE_MANAGER_CMD_STOP : 'file_manager_cmd_stop', FILE_MANAGER_CMD_PROGRESS : 'file_manager_cmd_progress', - FILE_MANAGER_CMD_ASAP_UPDATE : 'file_manager_cmd_asap_update' + FILE_MANAGER_CMD_ASAP_UPDATE : 'file_manager_cmd_asap_update', + MIXER_MODE_CHANGED : 'mixer_mode_changed' }; context.JK.ALERT_NAMES = { @@ -284,4 +285,14 @@ showASIO: false } } + + context.JK.MIX_MODES = { + MASTER: true, + PERSONAL: false + } + + /** NAMED_MESSAGES means messages that we show to the user (dialogs/banners/whatever), that we have formally named */ + context.JK.NAMED_MESSAGES = { + MASTER_VS_PERSONAL_MIX : 'master_vs_personal_mix' + } })(window,jQuery); \ No newline at end of file diff --git a/web/app/assets/javascripts/mods_utils.js.coffee b/web/app/assets/javascripts/mods_utils.js.coffee new file mode 100644 index 000000000..b10250c32 --- /dev/null +++ b/web/app/assets/javascripts/mods_utils.js.coffee @@ -0,0 +1,35 @@ +# +# Common utility functions. +# + +$ = jQuery +context = window +context.JK ||= {}; + +class ModUtils + constructor: () -> + @logger = context.JK.logger + + init: () => + + # creates a new show structure suitable for applying to a user update + noShow: (noShowName) => + noShowValue = {} + noShowValue[noShowName] = true + {no_show: noShowValue} + + updateNoShow: (noShowName) => + context.JK.app.updateUserModel({mods: this.noShow(noShowName)}) + + # returns a deferred, so use .done + shouldShow: (noShowName) => + deferred = new $.Deferred(); + context.JK.app.user() + .done((user) => ( + noShows = user.mods?.no_show + shouldShowForName = if noShows? then !noShows[noShowName] else true + deferred.resolve(shouldShowForName) + )) + return deferred; +# global instance +context.JK.ModUtils = new ModUtils() \ No newline at end of file diff --git a/web/app/assets/javascripts/session.js b/web/app/assets/javascripts/session.js index 9a8e1dd6a..8e3a2a4a6 100644 --- a/web/app/assets/javascripts/session.js +++ b/web/app/assets/javascripts/session.js @@ -5,8 +5,11 @@ context.JK = context.JK || {}; context.JK.SessionScreen = function(app) { var EVENTS = context.JK.EVENTS; + var MIX_MODES = context.JK.MIX_MODES; + var NAMED_MESSAGES = context.JK.NAMED_MESSAGES; var gearUtils = context.JK.GearUtils; var sessionUtils = context.JK.SessionUtils; + var modUtils = context.JK.ModUtils; var logger = context.JK.logger; var self = this; var sessionModel = null; @@ -38,8 +41,10 @@ var sessionPageDone = null; var $recordingManagerViewer = null; var $screen = null; - var rest = context.JK.Rest(); + var $mixModeDropdown = null; + var $templateMixerModeChange = null; + var rest = context.JK.Rest(); var RENDER_SESSION_DELAY = 750; // When I need to render a session, I have to wait a bit for the mixers to be there. var defaultParticipant = { @@ -490,14 +495,35 @@ var mixerIds = context.jamClient.SessionGetIDs(); var holder = $.extend(true, {}, {mixers: context.jamClient.SessionGetControlState(mixerIds)}); mixers = holder.mixers; + + console.log("mixers", mixers) + // grab the first mixer, and check the mode + + var newMixerMode;; + if(mixers.length > 0) { + newMixerMode = mixers[0]["mode"] + if(newMixerMode === undefined) { + logger.error("mixer mode not present. defaulting to personal") + newMixerMode = MIX_MODES.PERSONAL; + } + } + else { + logger.error("no mixers present. defaulting mixer mode to personal") + newMixerMode = MIX_MODES.PERSONAL; + } + + sessionModel.setMixerMode(newMixerMode) + // Always add a hard-coded simplified 'mixer' for the L2M mix + + /** var l2m_mixer = { - id: '__L2M__', - range_low: -80, - range_high: 20, - volume_left: context.jamClient.SessionGetMasterLocalMix() - }; - mixers.push(l2m_mixer); + id: '__L2M__', + range_low: -80, + range_high: 20, + volume_left: context.jamClient.SessionGetMasterLocalMix() + }; + mixers.push(l2m_mixer);*/ } function _mixersForGroupId(groupId) { @@ -558,13 +584,15 @@ var gainPercent = 0; var mixerIds = []; $.each(mixers, function(index, mixer) { - if (mixer.group_id === ChannelGroupIds.MasterGroup) { + if (sessionModel.isMasterMixMode() && mixer.group_id === ChannelGroupIds.MasterGroup) { mixerIds.push(mixer.id); gainPercent = percentFromMixerValue( mixer.range_low, mixer.range_high, mixer.volume_left); } - if (mixer.group_id === ChannelGroupIds.MonitorGroup) { + else if (!sessionModel.isMasterMixMode() && mixer.group_id === ChannelGroupIds.MonitorGroup) { mixerIds.push(mixer.id); + gainPercent = percentFromMixerValue( + mixer.range_low, mixer.range_high, mixer.volume_left); } }); var faderId = mixerIds.join(','); @@ -808,13 +836,28 @@ // With multiple tracks, there will be more than one mixer for a // particular client, in a particular group, and I'll need to further // identify by track id or something similar. - var mixer = _mixerForClientId( + + var mixer = null; + if(sessionModel.isMasterMixMode()) { + mixer = _mixerForClientId( participant.client_id, [ - ChannelGroupIds.AudioInputMusicGroup, - ChannelGroupIds.PeerAudioInputMusicGroup + ChannelGroupIds.AudioInputMusicGroup, + ChannelGroupIds.PeerAudioInputMusicGroup ], usedMixers); + } + else { + // don't pass in used mixers; we need to associate multiple tracks with the same mixer + mixer = _mixerForClientId( + participant.client_id, + [ + ChannelGroupIds.AudioInputMusicGroup, + ChannelGroupIds.UserMusicInputGroup + ], + {}); + } + if (mixer) { usedMixers[mixer.id] = true; myTrack = (mixer.group_id === ChannelGroupIds.AudioInputMusicGroup); @@ -829,6 +872,7 @@ trackData.muteClass = muteClass; trackData.mixerId = mixer.id; trackData.noaudio = false; + trackData.group_id = mixer.group_id; context.jamClient.SessionSetUserName(participant.client_id,name); } else { // No mixer to match, yet @@ -856,20 +900,20 @@ addNewGearDialog = new context.JK.AddNewGearDialog(app, self); } - function connectTrackToMixer(trackSelector, clientId, mixerId, gainPercent) { + function connectTrackToMixer(trackSelector, clientId, mixerId, gainPercent, groupId) { var vuOpts = $.extend({}, trackVuOpts); var faderOpts = $.extend({}, trackFaderOpts); faderOpts.faderId = mixerId; var vuLeftSelector = trackSelector + " .track-vu-left"; var vuRightSelector = trackSelector + " .track-vu-right"; var faderSelector = trackSelector + " .track-gain"; - var $fader = $(faderSelector).attr('mixer-id', mixerId); + var $fader = $(faderSelector).attr('mixer-id', mixerId).data('groupId', groupId) var $track = $(trackSelector); // Set mixer-id attributes and render VU/Fader context.JK.VuHelpers.renderVU(vuLeftSelector, vuOpts); - $track.find('.track-vu-left').attr('mixer-id', mixerId + '_vul'); + $track.find('.track-vu-left').attr('mixer-id', mixerId + '_vul').data('groupId', groupId) context.JK.VuHelpers.renderVU(vuRightSelector, vuOpts); - $track.find('.track-vu-right').attr('mixer-id', mixerId + '_vur'); + $track.find('.track-vu-right').attr('mixer-id', mixerId + '_vur').data('groupId', groupId) context.JK.FaderHelpers.renderFader($fader, faderOpts); // Set gain position context.JK.FaderHelpers.setFaderValue(mixerId, gainPercent); @@ -887,13 +931,26 @@ var keysToDelete = []; for (var key in lookingForMixers) { var clientId = lookingForMixers[key]; - var mixer = _mixerForClientId( + var mixer = null; + if(sessionModel.isMasterMixMode()) { + mixer = _mixerForClientId( clientId, [ - ChannelGroupIds.AudioInputMusicGroup, - ChannelGroupIds.PeerAudioInputMusicGroup + ChannelGroupIds.AudioInputMusicGroup, + ChannelGroupIds.PeerAudioInputMusicGroup ], usedMixers); + } + else { + // don't pass in used mixers; we need to associate multiple tracks with the same mixer + mixer = _mixerForClientId( + clientId, + [ + ChannelGroupIds.AudioInputMusicGroup, + ChannelGroupIds.UserMusicInputGroup + ], + {}); + } if (mixer) { var participant = (sessionModel.getParticipant(clientId) || {name:'unknown'}).name; logger.debug("found mixer=" + mixer.id + ", participant=" + participant) @@ -903,7 +960,7 @@ mixer.range_low, mixer.range_high, mixer.volume_left); var trackSelector = 'div.track[track-id="' + key + '"]'; - connectTrackToMixer(trackSelector, key, mixer.id, gainPercent); + connectTrackToMixer(trackSelector, key, mixer.id, gainPercent, mixer.group_id); var $track = $('div.track[client-id="' + clientId + '"]'); $track.find('.track-icon-mute').attr('mixer-id', mixer.id); // hide overlay for all tracks associated with this client id (if one mixer is present, then all tracks are valid) @@ -983,7 +1040,7 @@ // Render VU meters and gain fader var trackSelector = parentSelector + ' .session-track[track-id="' + trackData.trackId + '"]'; var gainPercent = trackData.gainPercent || 0; - connectTrackToMixer(trackSelector, trackData.clientId, trackData.mixerId, gainPercent); + connectTrackToMixer(trackSelector, trackData.clientId, trackData.mixerId, gainPercent, trackData.group_id); var $closeButton = $('#div-track-close', 'div[track-id="' + trackData.trackId + '"]'); if (!allowDelete) { @@ -1015,7 +1072,7 @@ // Render VU meters and gain fader var trackSelector = parentSelector + ' .session-track[track-id="' + trackData.trackId + '"]'; var gainPercent = trackData.gainPercent || 0; - connectTrackToMixer(trackSelector, trackData.clientId, trackData.mixerId, gainPercent); + connectTrackToMixer(trackSelector, trackData.clientId, trackData.mixerId, gainPercent, null); tracks[trackData.trackId] = new context.JK.SessionTrack(trackData.clientId); } @@ -1028,11 +1085,17 @@ function faderChanged(e, data) { var $target = $(this); var faderId = $target.attr('mixer-id'); + var groupId = $target.data('groupId'); var mixerIds = faderId.split(','); $.each(mixerIds, function(i,v) { var broadcast = !(data.dragging); // If fader is still dragging, don't broadcast fillTrackVolumeObject(v, broadcast); setMixerVolume(v, data.percentage); + + if(groupId == ChannelGroupIds.UserMusicInputGroup) { + // there may be other mixers with this same ID in the case of a Peer Music Stream, so update them as well + context.JK.FaderHelpers.setFaderValue(v, data.percentage); + } }); } @@ -1437,6 +1500,28 @@ sessionId); inviteMusiciansUtil.loadFriends(); $(friendInput).show(); + } + + function onMixerModeChanged(e, data) + { + $mixModeDropdown.easyDropDown('select', data.mode, true); + + setTimeout(renderSession, 1); + } + + function onUserChangeMixMode(e) { + var mode = $mixModeDropdown.val() == "master" ? MIX_MODES.MASTER : MIX_MODES.PERSONAL; + + context.jamClient.SetMixerMode(mode) + + modUtils.shouldShow(NAMED_MESSAGES.MASTER_VS_PERSONAL_MIX).done(function(shouldShow) { + if(shouldShow) { + var modeChangeHtml = $($templateMixerModeChange.html()); + context.JK.Banner.show({title: 'Master vs. Personal Mix', text: modeChangeHtml, no_show: NAMED_MESSAGES.MASTER_VS_PERSONAL_MIX}); + } + }) + + return true; } function events() { @@ -1460,6 +1545,8 @@ .on('play', onPlay) .on('change-position', onChangePlayPosition); $(friendInput).focus(function() { $(this).val(''); }) + $(document).on(EVENTS.MIXER_MODE_CHANGED, onMixerModeChanged) + $mixModeDropdown.change(onUserChangeMixMode) } this.initialize = function(localRecordingsDialogInstance, recordingFinishedDialogInstance, friendSelectorDialog) { @@ -1470,7 +1557,6 @@ context.jamClient.SetVURefreshRate(150); context.jamClient.RegisterVolChangeCallBack("JK.HandleVolumeChangeCallback"); playbackControls = new context.JK.PlaybackControls($('.session-recordings .recording-controls')); - events(); var screenBindings = { 'beforeShow': beforeShow, @@ -1483,7 +1569,12 @@ $recordingManagerViewer = $('#recording-manager-viewer'); $screen = $('#session-screen'); - // make sure no previous plays are still going on by accident + $mixModeDropdown = $screen.find('select.monitor-mode') + $templateMixerModeChange = $('#template-mixer-mode-change'); + events(); + + + // make sure no previous plays are still going on by accident context.jamClient.SessionStopPlay(); if(context.jamClient.SessionRemoveAllPlayTracks) { // upgrade guard diff --git a/web/app/assets/javascripts/sessionModel.js b/web/app/assets/javascripts/sessionModel.js index 60d8862b8..719fc650f 100644 --- a/web/app/assets/javascripts/sessionModel.js +++ b/web/app/assets/javascripts/sessionModel.js @@ -11,6 +11,7 @@ context.JK.SessionModel = function(app, server, client, sessionScreen) { var ALERT_TYPES = context.JK.ALERT_TYPES; var EVENTS = context.JK.EVENTS; + var MIX_MODES = context.JK.MIX_MODES; var userTracks = null; // comes from the backend var clientId = client.clientID; @@ -33,6 +34,8 @@ var startTime = null; var joinDeferred = null; + var mixerMode = MIX_MODES.GLOBAL; + server.registerOnSocketClosed(onWebsocketDisconnected); function id() { @@ -93,6 +96,13 @@ return inSession; } + function onMixerModeChanged(newMixerMode) + { + mixerMode = newMixerMode; + var mode = newMixerMode == MIX_MODES.MASTER ? "master" : "personal"; + logger.debug("onMixerModeChanged:" + mode); + $(document).triggerHandler(EVENTS.MIXER_MODE_CHANGED, {mode:mode}); + } function waitForSessionPageEnterDone() { sessionPageEnterDeferred = $.Deferred(); @@ -277,6 +287,7 @@ userTracks = null; startTime = null; joinDeferred = null; + mixerMode = MIX_MODES.PERSONAL; if(fullyJoined) { $(document).trigger(EVENTS.SESSION_ENDED, {session: {id: currentSessionId}}); } @@ -485,6 +496,16 @@ }); } + function setMixerMode(newMixerMode) { + if(mixerMode != newMixerMode) { + onMixerModeChanged(newMixerMode); + } + } + + function isMasterMixMode() { + return mixerMode == MIX_MODES.MASTER; + } + function onWebsocketDisconnected(in_error) { // kill the streaming of the session immediately if(currentSessionId) { @@ -616,6 +637,12 @@ else if(inSession() && (text == 'RebuildMediaControl' || text == 'RebuildRemoteUserControl')) { refreshCurrentSession(true); } + else if(inSession() && (text == 'Global Peer Input Mixer Mode')) { + setMixerMode(MIX_MODES.MASTER); + } + else if(inSession() && (text == 'Local Peer Stream Mixer Mode')) { + setMixerMode(MIX_MODES.PERSONAL); + } } // Public interface @@ -635,6 +662,8 @@ this.recordingModel = recordingModel; this.findUserBy = findUserBy; this.inSession = inSession; + this.setMixerMode = setMixerMode; + this.isMasterMixMode = isMasterMixMode; // ALERT HANDLERS this.onBackendMixerChanged = onBackendMixerChanged; diff --git a/web/app/assets/stylesheets/client/session.css.scss b/web/app/assets/stylesheets/client/session.css.scss index 0a7e00e86..2fb6c24b2 100644 --- a/web/app/assets/stylesheets/client/session.css.scss +++ b/web/app/assets/stylesheets/client/session.css.scss @@ -12,7 +12,6 @@ padding:11px 0px 11px 0px; background-color:#4c4c4c; min-height:20px; - overflow-x:hidden; position:relative; } @@ -69,6 +68,11 @@ .playback-mode-buttons { display:none; } + + .monitor-mode-holder .easydropdown-wrapper{ + top:-7px; + left:5px; + } } diff --git a/web/app/assets/stylesheets/dialogs/banner.css.scss b/web/app/assets/stylesheets/dialogs/banner.css.scss index fbad88349..7a84be027 100644 --- a/web/app/assets/stylesheets/dialogs/banner.css.scss +++ b/web/app/assets/stylesheets/dialogs/banner.css.scss @@ -8,6 +8,11 @@ height:240px; } + .dialog-inner { + padding-bottom:0; + margin-bottom:25px; + } + h2 { font-weight:bold; font-size:x-large; @@ -21,12 +26,40 @@ .buttons { margin:0 20px 20px 0; + position:relative; + + &.center { + text-align:center; + float:none; + } } .close-btn { display:none; } + + .no-more-show { + display:none; + position:absolute; + margin-left:50px; + left:50%; + top:-4px; + color:rgb(170, 170, 170); + + span { + font-size:15px; + } + + .icheckbox_minimal { + display:inline-block; + position:relative; + top:3px; + margin-right:3px; + + } + } + ul { list-style:disc; margin-left:20px; @@ -40,6 +73,10 @@ margin: 15px 12px 15px 36px; } + .definition { + font-weight:bold; + } + .end-content { height: 0; line-height: 0; diff --git a/web/app/controllers/api_users_controller.rb b/web/app/controllers/api_users_controller.rb index 06d9d6b03..d9b473307 100644 --- a/web/app/controllers/api_users_controller.rb +++ b/web/app/controllers/api_users_controller.rb @@ -46,6 +46,7 @@ class ApiUsersController < ApiController @user.show_whats_next = params[:show_whats_next] if params.has_key?(:show_whats_next) @user.subscribe_email = params[:subscribe_email] if params.has_key?(:subscribe_email) @user.biography = params[:biography] if params.has_key?(:biography) + @user.mod_merge(params[:mods]) if params[:mods] # allow keyword of 'LATEST' to mean set the notification_seen_at to the most recent notification for this user if params.has_key?(:notification_seen_at) diff --git a/web/app/views/api_users/show.rabl b/web/app/views/api_users/show.rabl index fd8b9b87e..59d72f304 100644 --- a/web/app/views/api_users/show.rabl +++ b/web/app/views/api_users/show.rabl @@ -17,6 +17,10 @@ if @user == current_user geoiplocation.info if geoiplocation end + node :mods do |user| + user.mods_json + end + elsif current_user node :is_friend do |uu| current_user.friends?(@user) diff --git a/web/app/views/clients/_session.html.erb b/web/app/views/clients/_session.html.erb index 6865d987f..dc0f6a81e 100644 --- a/web/app/views/clients/_session.html.erb +++ b/web/app/views/clients/_session.html.erb @@ -30,12 +30,22 @@ + +