(function (context, $) { "use strict"; context.JK = context.JK || {}; context.JK.ConfigureTracksHelper = function (app) { var logger = context.JK.logger; var ASSIGNMENT = context.JK.ASSIGNMENT; var VOICE_CHAT = context.JK.VOICE_CHAT; var MAX_TRACKS = context.JK.MAX_TRACKS; var MAX_OUTPUTS = context.JK.MAX_OUTPUTS; var gearUtils = context.JK.GearUtils; var $parent = null; var $templateAssignablePort = null; var $templateTrackTarget = null; var $templateOutputTarget = null; var $unassignedInputsHolder = null; var $unassignedOutputsHolder = null; var $tracksHolder = null; var $outputChannelHolder = null; var $instrumentsHolder = null; var isDragging = false; function removeHoverer($hoverChannel) { var $channel = $hoverChannel.data('original') $channel.data('cloned', null); $hoverChannel.remove(); } function hoverIn($channel) { if(isDragging) return; var $container = $channel.closest('.target'); var inTarget = $container.length > 0; if(!inTarget) { $container = $channel.closest('.channels-holder') } var $inputs = $container.find('.ftue-input'); var index = $inputs.index($channel); // $channel.css('padding', '0 5px'); if(inTarget) { $channel.data('container', $container) $channel.addClass('hovering'); $channel.css('color', 'white') $channel.css('background-color', '#333'); $channel.css('border', '#333'); $channel.css('border-radius', '2px'); $channel.css('min-width', '49%'); $channel.css('width', 'auto'); $channel.css('position', 'absolute'); $container.css('overflow', 'visible'); } else { var $offsetParent = $channel.offsetParent(); var parentOffset = $offsetParent.offset(); var hoverChannel = $(context._.template($templateAssignablePort.html(), {id: 'bogus', name: $channel.text()}, { variable: 'data' })); hoverChannel .css('position', 'absolute') .css('color', 'white') .css('left', $channel.position().left) .css('top', $channel.position().top) .css('background-color', '#333') .css('min-width', $channel.width()) .css('min-height', $channel.height()) .css('z-index', 10000) .data('original', $channel) $channel.data('cloned', hoverChannel); hoverChannel .hover(function(e) {}, function() { removeHoverer($(this)); }) .mousedown(function(e) { // because we have obscured the element the user wants to drag, // we proxy a mousedown on the hover-element to the covered .ftue-input ($channel). // this causes jquery.drag to get going even though the user clicked a different element $channel.trigger(e) }) hoverChannel.prependTo($offsetParent); } $channel.css('z-index', 10000) if(inTarget && $inputs.length == 2) { if(index == 0) { $channel.css('right', '50%') } else { $channel.css('left', '51%') } } } function hoverOut($channel) { var $cloned = $channel.data('cloned'); if($cloned) { return; // let the cloned handle the rest of hover out logic when it's hovered-out } $channel .removeClass('hovering') .css('color', '') .css('font-weight', '') .css('position', '') .css('width', '') .css('background-color', '') .css('padding', '') .css('border', '') .css('border-radius', '') .css('right', '') .css('left', '') .css('min-width', '') .css('z-index', '') .css('margin-left', '') .css('max-width', 'auto'); //var $container = $channel.closest('.target'); var $container = $channel.data('container'); if($container) { $container.css('overflow', '') } } function fixClone($clone) { $clone .css('color', '') .css('font-weight', '') .css('width', 'auto') .css('background-color', '') .css('padding', '') .css('border', '') .css('border-radius', '') .css('right', '') .css('min-width', '') } function loadChannels(forceInputsToUnassign) { var musicPorts = jamClient.FTUEGetChannels(); $unassignedInputsHolder.empty(); $unassignedOutputsHolder.empty(); // reset all counts $tracksHolder.find('.track-target').attr('track-count', '0'); $outputChannelHolder.find('.output-target').attr('output-count', '0'); $tracksHolder.find('.ftue-input').remove(); $outputChannelHolder.find('.ftue-input').remove(); var inputChannels = musicPorts.inputs; var outputChannels = musicPorts.outputs; // uncomment to add a bunch of bogus channels //inputChannels = inputChannels.concat(inputChannels.concat(inputChannels.concat(inputChannels))) context._.each(inputChannels, function (inputChannel) { var $channel = $(context._.template($templateAssignablePort.html(), inputChannel, { variable: 'data' })); $channel.hover( function() { hoverIn ($(this)) }, function() { hoverOut($(this)) } ); if(forceInputsToUnassign || inputChannel.assignment == ASSIGNMENT.UNASSIGNED) { unassignInputChannel($channel); } else if(inputChannel.assignment == ASSIGNMENT.CHAT) { // well, we can't show it as unused... if there were a place to show chat inputs, we would put it there. // but we don't have it, so just skip logger.debug("skipping channel ", inputChannel) return; } else { // find the track this belongs in var trackNumber = inputChannel.assignment - 1; var $track = $tracksHolder.find('.track[data-num="' + trackNumber + '"]') if($track.length == 0) { context.JK.alertSupportedNeeded('Unable to find a track for channel with assignment ' + inputChannel.assignment); return false; } addChannelToTrack($channel, $track.find('.track-target')); } $channel.draggable({ helper: 'clone', start: function(e, ui) { isDragging = true; var $channel = $(this); fixClone(ui.helper); var $track = $channel.closest('.track-target'); var isUnassigned = $track.length == 0; if(isUnassigned) { $tracksHolder.find('.track-target').addClass('possible-target'); } else { $tracksHolder.find('.track-target').addClass('possible-target'); $unassignedInputsHolder.addClass('possible-target'); } }, stop: function() { isDragging = false; $tracksHolder.find('.track-target').removeClass('possible-target'); $unassignedInputsHolder.removeClass('possible-target') } }); }) var outputAssignment = 0; context._.each(outputChannels, function (outputChannel, index) { var $channel = $(context._.template($templateAssignablePort.html(), outputChannel, { variable: 'data' })); $channel.hover( function() { hoverIn ($(this)) }, function() { hoverOut($(this)) } ); if(outputChannel.assignment == ASSIGNMENT.UNASSIGNED) { unassignOutputChannel($channel); } else { var $output = $outputChannelHolder.find('.output[data-num="' + index + '"]') if($output.length == 0) { context.JK.alertSupportedNeeded('Unable to find an output for channel with assignment ' + outputChannel.assignment); return false; } addChannelToOutput($channel, $output.find('.output-target')); } $channel.draggable({ helper: 'clone', start: function(e,ui) { isDragging = true; var $channel = $(this); fixClone(ui.helper); var $output = $channel.closest('.output-target'); var isUnassigned = $output.length == 0; if(isUnassigned) { $outputChannelHolder.find('.output-target').addClass('possible-target'); } else { $outputChannelHolder.find('.output-target').addClass('possible-target'); $unassignedOutputsHolder.addClass('possible-target'); } }, stop: function() { isDragging = false; $outputChannelHolder.find('.output-target').removeClass('possible-target'); $unassignedOutputsHolder.removeClass('possible-target') } }); }); } // iterates through the dom and returns a pure data structure for track associations and output channels function getCurrentState() { var state = {}; state.tracks = []; state.unassignedChannels = []; state.outputs = []; var $unassignedInputChannels = $unassignedInputsHolder.find('.ftue-input'); var $unassignedOutputChannels = $unassignedOutputsHolder.find('.ftue-input'); var $tracks = $tracksHolder.find('.track-target'); var $outputs = $outputChannelHolder.find('.output-target'); context._.each($unassignedInputChannels, function($unassignedInput) { $unassignedInput = $($unassignedInput); var channelId = $unassignedInput.attr('data-input-id'); state.unassignedChannels.push(channelId); }) context._.each($unassignedOutputChannels, function($unassignedOutput) { $unassignedOutput = $($unassignedOutput); var channelId = $unassignedOutput.attr('data-input-id'); state.unassignedChannels.push(channelId); }) context._.each($tracks, function($track, index) { $track = $($track); var $assignedChannels = $track.find('.ftue-input'); var track = {index: index, channels:[]}; context._.each($assignedChannels, function($assignedChannel) { $assignedChannel = $($assignedChannel); track.channels.push($assignedChannel.attr('data-input-id')) }); // sparse array if(track.channels.length > 0) { state.tracks.push(track); } var $instrument = $instrumentsHolder.find('[data-num="' + index + '"]').find('.icon-instrument-select'); track.instrument_id = $instrument.data('instrument_id'); }) context._.each($outputs, function($output, index) { $output = $($output); var $assignedChannel = $output.find('.ftue-input'); // this is overkill since there should only be 1 or 0 .ftue-inputs in a given .output var outputSlot = {index: index, channels:[]}; context._.each($assignedChannel, function($assignedChannel) { $assignedChannel = $($assignedChannel); outputSlot.channels.push($assignedChannel.attr('data-input-id')) }); // sparse array if(outputSlot.channels.length > 0) { state.outputs.push(outputSlot); } }) return state; } function validate(tracks) { // there must be at least one assigned channel if(tracks.tracks.length == 0) { logger.debug("ConfigureTracks validation error: must have assigned at least one input port to a track."); context.JK.Banner.showAlert('Must have assigned at least one input port to a track.'); return false; } // there must be some instruments context._.each(tracks.tracks, function(track) { if(!track.instrument_id) { logger.debug("ConfigureTracks validation error: all tracks with ports assigned must specify an instrument."); context.JK.Banner.showAlert('All tracks with ports assigned must specify an instrument.'); return false; } }); // there must be exactly 2 output channels assigned if(tracks.outputs.length != 2 || (tracks.outputs[0].channels.length != 1 && track.outputs[1].channels.length != 1)) { logger.debug("ConfigureTracks validation error: must have assigned exactly two output ports"); context.JK.Banner.showAlert('Must have assigned exactly 2 output ports.'); return false; } return true; } function save(state) { context._.each(state.unassignedChannels, function(unassignedChannelId) { context.jamClient.TrackSetAssignment(unassignedChannelId, true, ASSIGNMENT.UNASSIGNED); }); // save input/tracks context._.each(state.tracks, function(track, index) { var trackNumber = index + 1; context._.each(track.channels, function(channelId) { context.jamClient.TrackSetAssignment(channelId, true, trackNumber); }); logger.debug("context.jamClient.TrackSetInstrument(trackNumber, track.instrument_id)", trackNumber, track.instrument_id); context.jamClient.TrackSetInstrument(trackNumber, context.JK.instrument_id_to_instrument[track.instrument_id].client_id); }); // save outputs context._.each(state.outputs, function(output, index) { context._.each(output.channels, function(channelId) { context.jamClient.TrackSetAssignment(channelId, true, ASSIGNMENT.OUTPUT); }); }); var result = context.jamClient.TrackSaveAssignments(); if(!result || result.length == 0) { // success return true; } else { context.JK.Banner.showAlert('Unable to save assignments. ' + result); return false; } } function loadTrackInstruments(forceInputsToUnassign) { var $trackInstruments = $instrumentsHolder.find('.track-instrument'); context._.each($trackInstruments, function(trackInstrument) { var $trackInstrument = $(trackInstrument); var trackIndex = parseInt($trackInstrument.attr('data-num')) + 1; var clientInstrument = context.jamClient.TrackGetInstrument(trackIndex); var instrument = context.JK.client_to_server_instrument_map[clientInstrument]; $trackInstrument.instrumentSelectorSet(instrument ? instrument.server_id : instrument); }); } function trySave() { var state = getCurrentState(); if(!validate(state)) { return false; } var saved = save(state); if(saved) { context.JK.GA.trackConfigureTracksCompletion(context.JK.detectOS()); } return saved; } function reset(forceInputsToUnassign) { loadChannels(forceInputsToUnassign); loadTrackInstruments(forceInputsToUnassign); } function unassignOutputChannel($channel) { var $originallyAssignedTrack = $channel.closest('.output-target'); $unassignedOutputsHolder.append($channel); $originallyAssignedTrack.attr('output-count', $originallyAssignedTrack.find('.ftue-input:not(.ui-draggable-dragging)').length); } function unassignInputChannel($channel) { var $originallyAssignedTrack = $channel.closest('.track-target'); $unassignedInputsHolder.append($channel); $originallyAssignedTrack.attr('track-count', $originallyAssignedTrack.find('.ftue-input:not(.ui-draggable-dragging)').length); } function addChannelToTrack($channel, $track) { var $originallyAssignedTrack = $channel.closest('.track-target'); $track.append($channel); $track.attr('track-count', $track.find('.ftue-input:not(.ui-draggable-dragging)').length); $originallyAssignedTrack.attr('track-count', $originallyAssignedTrack.find('.ftue-input:not(.ui-draggable-dragging)').length) } function addChannelToOutput($channel, $slot) { var $originallyAssignedTrack = $channel.closest('.output-target'); $slot.append($channel); $slot.attr('output-count', $slot.find('.ftue-input:not(.ui-draggable-dragging)').length); $originallyAssignedTrack.attr('output-count', $originallyAssignedTrack.find('.ftue-input:not(.ui-draggable-dragging)').length) } function initializeUnassignedOutputDroppable() { $unassignedOutputsHolder.droppable( { activeClass: 'drag-in-progress', hoverClass: 'drag-hovering', drop: function( event, ui ) { var $channel = ui.draggable; //$channel.css('left', '0').css('top', '0'); unassignOutputChannel($channel); } }); } function initializeUnassignedInputDroppable() { $unassignedInputsHolder.droppable( { activeClass: 'drag-in-progress', hoverClass: 'drag-hovering', drop: function( event, ui ) { var $channel = ui.draggable; //$channel.css('left', '0').css('top', '0'); unassignInputChannel($channel); } }); } function initializeOutputDroppables() { var i; for(i = 0; i < MAX_OUTPUTS; i++) { var $target = $(context._.template($templateOutputTarget.html(), {num: i }, { variable: 'data' })); $outputChannelHolder.append($target); $target.find('.output-target').droppable( { activeClass: 'drag-in-progress', hoverClass: 'drag-hovering', drop: function( event, ui ) { var $slot = $(this); if($slot.attr('output-count') == 1) { return false; // max of 1 output per slot } var $channel = ui.draggable; //$channel.css('left', '0').css('top', '0'); addChannelToOutput($channel, $slot); } }); } } function initializeTrackDroppables() { var i; for(i = 0; i < MAX_TRACKS; i++) { var $target = $(context._.template($templateTrackTarget.html(), {num: i }, { variable: 'data' })); $tracksHolder.append($target); $target.find('.track-target').droppable( { activeClass: 'drag-in-progress', hoverClass: 'drag-hovering', drop: function( event, ui ) { var $track = $(this); if($track.attr('track-count') == 2) { return false; // max of 2 inputs per track } var $channel = ui.draggable; //$channel.css('left', '0').css('top', '0'); addChannelToTrack($channel, $track); } }); } } function initializeInstrumentDropdown() { var i; for(i = 0; i < MAX_TRACKS; i++) { var $root = $('
'); $root.instrumentSelector().attr('data-num', i); $instrumentsHolder.append($root); } } function initialize(_$parent) { $parent = _$parent; $templateAssignablePort = $('#template-assignable-port'); $templateTrackTarget = $('#template-track-target'); $templateOutputTarget = $('#template-output-target'); $unassignedInputsHolder = $parent.find('.unassigned-input-channels') $unassignedOutputsHolder = $parent.find('.unassigned-output-channels'); $tracksHolder = $parent.find('.tracks'); $instrumentsHolder = $parent.find('.instruments'); $outputChannelHolder = $parent.find('.output-channels'); initializeUnassignedInputDroppable(); initializeTrackDroppables(); initializeInstrumentDropdown(); initializeUnassignedOutputDroppable(); initializeOutputDroppables(); } this.initialize = initialize; this.trySave = trySave; this.reset = reset; return this; }; })(window, jQuery);