jam-cloud/web/app/assets/javascripts/configureTracksHelper.js

586 lines
20 KiB
JavaScript
Raw Normal View History

(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)
.css('background-position', '4px 6px')
.css('padding-left', '14px')
.data('original', $channel);
$channel.data('cloned', hoverChannel);
hoverChannel
.hover(function(e) {
var hoverCheckTimeout = hoverChannel.data('hoverCheckTimeout');
if(hoverCheckTimeout) {
clearTimeout(hoverCheckTimeout);
hoverChannel.data('hoverCheckTimeout', null);
}
}, function() { removeHoverer($(this)); })
.mousedown(function(e) {
2014-06-13 17:51:03 +00:00
// 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.data('hoverCheckTimeout', setTimeout(function() {
// check if element has already been left
hoverChannel.data('hoverCheckTimeout', null);
removeHoverer(hoverChannel);
}, 500));
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('padding-left', '')
.css('background-position', '')
.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', '')
}
// inputChannelFilter is an optional argument that is used by the Gear Wizard.
// basically, if an input channel isn't in there, it's not going to be displayed
function loadChannels(forceInputsToUnassign, inputChannelFilter) {
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) {
if(inputChannelFilter && !(inputChannelFilter.indexOf(inputChannel.id) > -1)) {
// skipping input channel because it's not in the filter
return;
}
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 {
2014-08-14 21:54:36 +00:00
var $output = $outputChannelHolder.find('.output[data-num="' + outputAssignment + '"]');
outputAssignment++;
if($output.length == 0) {
2014-08-14 21:54:36 +00:00
context.JK.alertSupportedNeeded('Unable to find an output holder for channel: ' + outputChannel.id + ' 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, inputChannelFilter) {
loadChannels(forceInputsToUnassign, inputChannelFilter);
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 = $('<div class="track-instrument"></div>');
$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);