3 new commits in galaxy-central: https://bitbucket.org/galaxy/galaxy-central/commits/769e8dca49f0/ Changeset: 769e8dca49f0 Branch: next-stable User: natefoo Date: 2014-10-06 16:58:28+00:00 Summary: Close next-stable branch for release_2014.10.06 Affected #: 0 files https://bitbucket.org/galaxy/galaxy-central/commits/2092948937ac/ Changeset: 2092948937ac Branch: stable User: natefoo Date: 2014-10-06 16:58:53+00:00 Summary: Merge next-stable to stable for release_2014.10.06 Affected #: 951 files diff -r 007f6a80629a74650b72d67821a9505932d284f3 -r 2092948937ac30ef82f71463a235c66d34987088 README.txt --- a/README.txt +++ b/README.txt @@ -1,31 +1,34 @@ -GALAXY -====== -http://galaxyproject.org/ - -The latest information about Galaxy is always available via the Galaxy -website above. - -HOW TO START -============ -Galaxy requires Python 2.6 or 2.7. To check your python version, run: - -% python -V -Python 2.7.3 - -Start Galaxy: - -% sh run.sh - -Once Galaxy completes startup, you should be able to view Galaxy in your -browser at: - -http://localhost:8080 - -You may wish to make changes from the default configuration. This can be done -in the universe_wsgi.ini file. Tools are configured in tool_conf.xml. Details -on adding tools can be found on the Galaxy website (linked above). - -Not all dependencies are included for the tools provided in the sample -tool_conf.xml. A full list of external dependencies is available at: - -https://wiki.galaxyproject.org/Admin/Tools/ToolDependencies +GALAXY +====== +http://galaxyproject.org/ + +The latest information about Galaxy is always available via the Galaxy +website above. + +HOW TO START +============ +Galaxy requires Python 2.6 or 2.7. To check your python version, run: + +% python -V +Python 2.7.3 + +Start Galaxy: + +% sh run.sh + +Once Galaxy completes startup, you should be able to view Galaxy in your +browser at: + +http://localhost:8080 + +You may wish to make changes from the default configuration. This can be done +in the config/galaxy.ini file. Tools can be either installed from the Tool Shed +or added manually. For details please see the Galaxy wiki: + +https://wiki.galaxyproject.org/Admin/Tools/AddToolFromToolShedTutorial + + +Not all dependencies are included for the tools provided in the sample +tool_conf.xml. A full list of external dependencies is available at: + +https://wiki.galaxyproject.org/Admin/Tools/ToolDependencies diff -r 007f6a80629a74650b72d67821a9505932d284f3 -r 2092948937ac30ef82f71463a235c66d34987088 buildbot_setup.sh --- a/buildbot_setup.sh +++ b/buildbot_setup.sh @@ -42,25 +42,6 @@ /galaxy/software/tool-data/gatk " -SAMPLES=" -tool_conf.xml.sample -datatypes_conf.xml.sample -universe_wsgi.ini.sample -tool_data_table_conf.xml.sample -tool_sheds_conf.xml.sample -shed_tool_data_table_conf.xml.sample -migrated_tools_conf.xml.sample -data_manager_conf.xml.sample -shed_data_manager_conf.xml.sample -tool-data/shared/ensembl/builds.txt.sample -tool-data/shared/igv/igv_build_sites.txt.sample -tool-data/shared/ncbi/builds.txt.sample -tool-data/shared/rviewer/rviewer_build_sites.txt.sample -tool-data/shared/ucsc/builds.txt.sample -tool-data/shared/ucsc/publicbuilds.txt.sample -tool-data/shared/ucsc/ucsc_build_sites.txt.sample -" - DIRS=" database database/files @@ -108,14 +89,11 @@ ;; esac -for sample in $SAMPLES; do - file=${sample%.sample} - echo "Copying $sample to $file" - cp $sample $file -done +# set up configs from samples. +./scripts/common_startup.sh echo "Copying job_conf.xml.sample_basic to job_conf.xml" -cp job_conf.xml.sample_basic job_conf.xml +cp config/job_conf.xml.sample_basic config/job_conf.xml for dir in $DIRS; do if [ ! -d $dir ]; then diff -r 007f6a80629a74650b72d67821a9505932d284f3 -r 2092948937ac30ef82f71463a235c66d34987088 client/GruntFile.js --- /dev/null +++ b/client/GruntFile.js @@ -0,0 +1,88 @@ +module.exports = function(grunt) { + + // Project configuration. + grunt.initConfig({ + pkg: grunt.file.readJSON( 'package.json' ), + + // default task + // use 'grunt copy' to copy the entire <galaxy>/client/galaxy/scripts dir into <galaxy>/static/scripts + copy: { + main: { + files: [ + { + expand : true, + cwd : 'galaxy/scripts/', + src : '**', + dest : '../static/scripts' + } + ] + } + }, + + // use 'grunt pack' to call pack_scripts.py to pack all or selected files in static/scripts + exec: { + packScripts: { + cwd: '../static/scripts', + target: [], + cmd: function(){ + var targets = grunt.config( 'exec.packScripts.target' ); + // if nothing was passed in pack all scripts + if( !targets.length ){ + return './pack_scripts.py'; + } + + grunt.log.write( 'packing: ' + targets + '\n' ); + return targets.map( function( target ){ + return './pack_scripts.py ' + target; + }).join( '; ' ); + } + } + }, + + // use 'grunt watch' (from a new tab in your terminal) to have grunt re-copy changed files automatically + watch: { + // watch for changes in the src dir + files: [ 'galaxy/scripts/**' ], + tasks: [ 'copy', 'pack' ], + options: { + spawn: false + } + } + }); + + grunt.loadNpmTasks( 'grunt-contrib-watch' ); + grunt.loadNpmTasks( 'grunt-contrib-copy'); + grunt.loadNpmTasks('grunt-exec'); + + grunt.registerTask( 'pack', [ 'exec' ] ); + grunt.registerTask( 'default', [ 'copy', 'pack' ] ); + + // -------------------------------------------------------------------------- copy,pack only those changed + // adapted from grunt-contrib-watch jslint example + //TODO: a bit hacky and there's prob. a better way + //NOTE: copy will fail silently if a file isn't found + + // outer scope variable for the event handler and onChange fn - begin with empty hash + var changedFiles = Object.create(null); + + // when files are changed, set the copy src and packScripts target to the filenames of the updated files + var onChange = grunt.util._.debounce(function() { + grunt.config( 'copy.main.files', [{ + expand: true, + cwd: 'galaxy/scripts', + src: Object.keys( changedFiles ), + dest: '../static/scripts/' + }]); + grunt.config( 'exec.packScripts.target', Object.keys( changedFiles ) ); + changedFiles = Object.create(null); + }, 200); + + grunt.event.on('watch', function(action, filepath) { + // store each filepath in a Files obj, the debounced fn above will use it as an aggregate list for copying + // we need to take galaxy/scripts out of the filepath or it will be copied to the wrong loc + filepath = filepath.replace( /galaxy\/scripts\//, '' ); + changedFiles[filepath] = action; + onChange(); + }); + +}; diff -r 007f6a80629a74650b72d67821a9505932d284f3 -r 2092948937ac30ef82f71463a235c66d34987088 client/README.txt --- /dev/null +++ b/client/README.txt @@ -0,0 +1,42 @@ +Client Build System +=================== + +Builds and moves the client-side scripts necessary for running the Galaxy webapps. There's no need to use this system +unless you are modifying or developing client-side scripts. + +You'll need Node and the Node Package Manager (npm): nodejs.org. + +Once npm is installed, install the grunt task manager and it's command line into your global scope: + + npm install -g grunt grunt-cli + +Next, from within this directory, install the local build dependencies: + + cd client + npm install + +You're now ready to re-build the client scripts after modifying them. + + +Rebuilding +========== + +There are two methods for rebuilding: a complete rebuild and automatic, partial rebuilds while you develop. + +A complete rebuild can be done with the following (from the `client` directory): + + grunt + +This will copy any files in `client/galaxy/scripts` to `static/scripts` and run `static/scripts/pack_scripts.py` on all. + +Grunt can also do an automatic, partial rebuild of any files you change *as you develop* by: + + 1. opening a new terminal session + 2. `cd client` + 3. `grunt watch` + +This starts a new grunt watch process that will monitor the files in `client/galaxy/scripts` for changes and copy and +pack them when they change. + +You can stop the `grunt watch` task by pressing `Ctrl+C`. Note: you should also be able to background that task if you +prefer. diff -r 007f6a80629a74650b72d67821a9505932d284f3 -r 2092948937ac30ef82f71463a235c66d34987088 client/galaxy/scripts/base.js --- /dev/null +++ b/client/galaxy/scripts/base.js @@ -0,0 +1,15 @@ +define( ["libs/backbone/backbone"], function( Backbone ) { + + var Base = function() { + if( this.initialize ) { + this.initialize.apply(this, arguments); + } + }; + Base.extend = Backbone.Model.extend; + + return { + Base: Base, + Backbone: Backbone + }; + +}); \ No newline at end of file diff -r 007f6a80629a74650b72d67821a9505932d284f3 -r 2092948937ac30ef82f71463a235c66d34987088 client/galaxy/scripts/galaxy-app-base.js --- /dev/null +++ b/client/galaxy/scripts/galaxy-app-base.js @@ -0,0 +1,160 @@ +define([ + 'mvc/user/user-model', + 'utils/metrics-logger', + 'utils/add-logging', + 'utils/localization', + 'bootstrapped-data' +], function( userModel, metricsLogger, addLogging, localize, bootstrapped ){ +// ============================================================================ +/** Base galaxy client-side application. + * Iniitializes: + * logger : the logger/metrics-logger + * localize : the string localizer + * config : the current configuration (any k/v in + * galaxy.ini available from the configuration API) + * user : the current user (as a mvc/user/user-model) + */ +function GalaxyApp( options ){ + var self = this; + return self._init( options || {} ); +} + +// add logging shortcuts for this object +addLogging( GalaxyApp, 'GalaxyApp' ); + +/** default options */ +GalaxyApp.prototype.defaultOptions = { + /** monkey patch attributes from existing window.Galaxy object? */ + patchExisting : true, + /** root url of this app */ + // move to self.root? + root : '/' +}; + +/** initalize options and sub-components */ +GalaxyApp.prototype._init = function init( options ){ + var self = this; + _.extend( self, Backbone.Events ); + + self._processOptions( options ); + self.debug( 'GalaxyApp.options: ', self.options ); + + self._patchGalaxy( window.Galaxy ); + + self._initLogger( options.loggerOptions || {} ); + self.debug( 'GalaxyApp.logger: ', self.logger ); + + self._initLocale(); + self.debug( 'GalaxyApp.localize: ', self.localize ); + + self.config = options.config || bootstrapped.config || {}; + self.debug( 'GalaxyApp.config: ', self.config ); + + self._initUser( options.user || bootstrapped.user || {} ); + self.debug( 'GalaxyApp.user: ', self.user ); + + //TODO: temp + self.trigger( 'ready', self ); + //if( typeof options.onload === 'function' ){ + // options.onload(); + //} + + self._setUpListeners(); + + return self; +}; + +/** add an option from options if the key matches an option in defaultOptions */ +GalaxyApp.prototype._processOptions = function _processOptions( options ){ + var self = this, + defaults = self.defaultOptions; + self.debug( '_processOptions: ', options ); + + self.options = {}; + for( var k in defaults ){ + if( defaults.hasOwnProperty( k ) ){ + self.options[ k ] = ( options.hasOwnProperty( k ) )?( options[ k ] ):( defaults[ k ] ); + } + } + return self; +}; + +/** add an option from options if the key matches an option in defaultOptions */ +GalaxyApp.prototype._patchGalaxy = function _processOptions( patchWith ){ + var self = this; + // in case req or plain script tag order has created a prev. version of the Galaxy obj... + if( self.options.patchExisting && patchWith ){ + self.debug( 'found existing Galaxy object:', patchWith ); + // ...(for now) monkey patch any added attributes that the previous Galaxy may have had + //TODO: move those attributes to more formal assignment in GalaxyApp + for( var k in patchWith ){ + if( patchWith.hasOwnProperty( k ) ){ + self.debug( '\t patching in ' + k + ' to Galaxy' ); + self[ k ] = patchWith[ k ]; + } + } + } +}; + +/** set up the metrics logger (utils/metrics-logger) and pass loggerOptions */ +GalaxyApp.prototype._initLogger = function _initLogger( loggerOptions ){ + var self = this; + self.debug( '_initLogger:', loggerOptions ); + self.logger = new metricsLogger.MetricsLogger( loggerOptions ); + return self; +}; + +/** add the localize fn to this object and the window namespace (as '_l') */ +GalaxyApp.prototype._initLocale = function _initLocale( options ){ + var self = this; + self.debug( '_initLocale:', options ); + self.localize = localize; + // add to window as global shortened alias + window._l = self.localize; + return self; +}; + +/** set up the current user as a Backbone model (mvc/user/user-model) */ +GalaxyApp.prototype._initUser = function _initUser( userJSON ){ + var self = this; + self.debug( '_initUser:', userJSON ); + self.user = new userModel.User( userJSON ); + //TODO: temp - old alias + self.currUser = self.user; + return self; +}; + +/** Set up DOM/jQuery/Backbone event listeners enabled for all pages */ +GalaxyApp.prototype._setUpListeners = function _setUpListeners(){ + var self = this; + + // hook to jq beforeSend to record the most recent ajax call and cache some data about it + /** cached info about the last ajax call made through jQuery */ + self.lastAjax = {}; + $( document ).bind( 'ajaxSend', function( ev, xhr, options ){ + var data = options.data; + try { + data = JSON.parse( data ); + } catch( err ){} + + self.lastAjax = { + url : location.href.slice( 0, -1 ) + options.url, + data : data + }; + //TODO:?? we might somehow manage to *retry* ajax using either this hook or Backbone.sync + }); + +}; + +/** string rep */ +GalaxyApp.prototype.toString = function toString(){ + var userEmail = this.user.get( 'email' ) || '(anonymous)'; + return 'GalaxyApp(' + userEmail + ')'; +}; + + +// ============================================================================ + return { + GalaxyApp : GalaxyApp + }; +}); diff -r 007f6a80629a74650b72d67821a9505932d284f3 -r 2092948937ac30ef82f71463a235c66d34987088 client/galaxy/scripts/galaxy.autocom_tagging.js --- /dev/null +++ b/client/galaxy/scripts/galaxy.autocom_tagging.js @@ -0,0 +1,368 @@ +/** +* JQuery extension for tagging with autocomplete. +* @author: Jeremy Goecks +* @require: jquery.autocomplete plugin +*/ +// +// Initialize "tag click functions" for tags. +// +function init_tag_click_function(tag_elt, click_func) { + $(tag_elt).find('.tag-name').each( function() { + $(this).click( function() { + var tag_str = $(this).text(); + var tag_name_and_value = tag_str.split(":"); + click_func(tag_name_and_value[0], tag_name_and_value[1]); + return true; + }); + }); +} + +jQuery.fn.autocomplete_tagging = function(options) { + + var defaults = { + get_toggle_link_text_fn: function(tags) { + var text = ""; + var num_tags = obj_length(tags); + if (num_tags > 0) { + text = num_tags + (num_tags > 1 ? " Tags" : " Tag"); + } else { + text = "Add tags"; + } + return text; + }, + tag_click_fn : function (name, value) {}, + editable: true, + input_size: 20, + in_form: false, + tags : {}, + use_toggle_link: true, + item_id: "", + add_tag_img: "", + add_tag_img_rollover: "", + delete_tag_img: "", + ajax_autocomplete_tag_url: "", + ajax_retag_url: "", + ajax_delete_tag_url: "", + ajax_add_tag_url: "" + }; + + var settings = jQuery.extend(defaults, options); + + // + // Initalize object's elements. + // + + // Get elements for this object. For this_obj, assume the last element with the id is the "this"; this is somewhat of a hack to address the problem + // that there may be two tagging elements for a single item if there are both community and individual tags for an element. + var this_obj = $(this); + var tag_area = this_obj.find('.tag-area'); + var toggle_link = this_obj.find('.toggle-link'); + var tag_input_field = this_obj.find('.tag-input'); + var add_tag_button = this_obj.find('.add-tag-button'); + + // Initialize toggle link. + toggle_link.click( function() { + // Take special actions depending on whether toggle is showing or hiding link. + var after_toggle_fn; + if (tag_area.is(":hidden")) { + after_toggle_fn = function() { + // If there are no tags, go right to editing mode by generating a click on the area. + var num_tags = $(this).find('.tag-button').length; + if (num_tags === 0) { + tag_area.click(); + } + }; + } else { + after_toggle_fn = function() { + tag_area.blur(); + }; + } + tag_area.slideToggle("fast", after_toggle_fn); + return $(this); + }); + + // Initialize tag input field. + if (settings.editable) { + tag_input_field.hide(); + } + tag_input_field.keyup( function(e) { + if ( e.keyCode === 27 ) { + // Escape key + $(this).trigger( "blur" ); + } else if ( + ( e.keyCode === 13 ) || // Return Key + ( e.keyCode === 188 ) || // Comma + ( e.keyCode === 32 ) // Space + ) { + // + // Check input. + // + + var new_value = this.value; + + // Suppress space after a ":" + if ( new_value.indexOf(": ", new_value.length - 2) !== -1) { + this.value = new_value.substring(0, new_value.length-1); + return false; + } + + // Remove trigger keys from input. + if ( (e.keyCode === 188) || (e.keyCode === 32) ) { + new_value = new_value.substring( 0 , new_value.length - 1 ); + } + + // Trim whitespace. + new_value = $.trim(new_value); + + // Too short? + if (new_value.length < 2) { + return false; + } + + // + // New tag OK - apply it. + // + + this.value = ""; // Reset text field now that tag is being added + + // Add button for tag after all other tag buttons. + var new_tag_button = build_tag_button(new_value); + var tag_buttons = tag_area.children(".tag-button"); + if (tag_buttons.length !== 0) { + var last_tag_button = tag_buttons.slice(tag_buttons.length-1); + last_tag_button.after(new_tag_button); + } else { + tag_area.prepend(new_tag_button); + } + + // Add tag to internal list. + var tag_name_and_value = new_value.split(":"); + settings.tags[tag_name_and_value[0]] = tag_name_and_value[1]; + + // Update toggle link text. + var new_text = settings.get_toggle_link_text_fn(settings.tags); + toggle_link.text(new_text); + + // Commit tag to server. + var zz = $(this); + $.ajax({ + url: settings.ajax_add_tag_url, + data: { new_tag: new_value }, + error: function() { + // Failed. Roll back changes and show alert. + new_tag_button.remove(); + delete settings.tags[tag_name_and_value[0]]; + var new_text = settings.get_toggle_link_text_fn(settings.tags); + toggle_link.text(new_text); + alert( "Add tag failed" ); + }, + success: function() { + // Flush autocomplete cache because it's not out of date. + // TODO: in the future, we could remove the particular item + // that was chosen from the cache rather than flush it. + zz.data('autocompleter').cacheFlush(); + } + }); + + return false; + } + }); + + // Add autocomplete to input. + var format_item_func = function(key, row_position, num_rows, value, search_term) { + var tag_name_and_value = value.split(":"); + return (tag_name_and_value.length === 1 ? tag_name_and_value[0] : tag_name_and_value[1]); + }; + var autocomplete_options = { selectFirst: false, formatItem: format_item_func, + autoFill: false, highlight: false }; + tag_input_field.autocomplete(settings.ajax_autocomplete_tag_url, autocomplete_options); + + + // Initialize delete tag images for current tags. + this_obj.find('.delete-tag-img').each(function() { + init_delete_tag_image( $(this) ); + }); + + + // Initialize tag click function. + init_tag_click_function($(this), settings.tag_click_fn); + + // Initialize "add tag" button. + add_tag_button.click( function() { + $(this).hide(); + + // Clicking on button is the same as clicking on the tag area. + tag_area.click(); + return false; + }); + + // + // Set up tag area interactions; these are needed only if tags are editable. + // + if (settings.editable) { + // When the tag area blurs, go to "view tag" mode. + tag_area.bind("blur", function(e) { + if (obj_length(settings.tags) > 0) { + add_tag_button.show(); + tag_input_field.hide(); + tag_area.removeClass("active-tag-area"); + // tag_area.addClass("tooltip"); + } else { + // No tags, so do nothing to ensure that input is still visible. + } + }); + + // On click, enable user to add tags. + tag_area.click( function(e) { + var is_active = $(this).hasClass("active-tag-area"); + + // If a "delete image" object was pressed and area is inactive, do nothing. + if ($(e.target).hasClass("delete-tag-img") && !is_active) { + return false; + } + + // If a "tag name" object was pressed and area is inactive, do nothing. + if ($(e.target).hasClass("tag-name") && !is_active) { + return false; + } + + // Remove tooltip. + // $(this).removeClass("tooltip"); + + // Hide add tag button, show tag_input field. Change background to show + // area is active. + $(this).addClass("active-tag-area"); + add_tag_button.hide(); + tag_input_field.show(); + tag_input_field.focus(); + + // Add handler to document that will call blur when the tag area is blurred; + // a tag area is blurred when a user clicks on an element outside the area. + var handle_document_click = function(e) { + var check_click = function(tag_area, target) { + var tag_area_id = tag_area.attr("id"); + // Blur the tag area if the element clicked on is not in the tag area. + if (target !== tag_area) { + tag_area.blur(); + $(window).unbind("click.tagging_blur"); + $(this).addClass("tooltip"); + } + }; + check_click(tag_area, $(e.target)); + }; + // TODO: we should attach the click handler to all frames in order to capture + // clicks outside the frame that this element is in. + //window.parent.document.onclick = handle_document_click; + //var temp = $(window.parent.document.body).contents().find("iframe").html(); + //alert(temp); + //$(document).parent().click(handle_document_click); + $(window).bind("click.tagging_blur", handle_document_click); + + return false; + }); + } + + // If using toggle link, hide the tag area. Otherwise, show the tag area. + if (settings.use_toggle_link) { + tag_area.hide(); + } + + // + // Helper functions. + // + + // + // Collapse tag name + value into a single string. + // + function build_tag_str(tag_name, tag_value) { + return tag_name + ( tag_value ? ":" + tag_value : ""); + } + + + // Initialize a "delete tag image": when click, delete tag from UI and send delete request to server. + function init_delete_tag_image(delete_img) { + $(delete_img).mouseenter( function () { + $(this).attr("src", settings.delete_tag_img_rollover); + }); + $(delete_img).mouseleave( function () { + $(this).attr("src", settings.delete_tag_img); + }); + $(delete_img).click( function () { + // Tag button is image's parent. + var tag_button = $(this).parent(); + + // Get tag name, value. + var tag_name_elt = tag_button.find(".tag-name").eq(0); + var tag_str = tag_name_elt.text(); + var tag_name_and_value = tag_str.split(":"); + var tag_name = tag_name_and_value[0]; + var tag_value = tag_name_and_value[1]; + + var prev_button = tag_button.prev(); + tag_button.remove(); + + // Remove tag from local list for consistency. + delete settings.tags[tag_name]; + + // Update toggle link text. + var new_text = settings.get_toggle_link_text_fn(settings.tags); + toggle_link.text(new_text); + + // Delete tag. + $.ajax({ + url: settings.ajax_delete_tag_url, + data: { tag_name: tag_name }, + error: function() { + // Failed. Roll back changes and show alert. + settings.tags[tag_name] = tag_value; + if (prev_button.hasClass("tag-button")) { + prev_button.after(tag_button); + } else { + tag_area.prepend(tag_button); + } + alert( "Remove tag failed" ); + + toggle_link.text(settings.get_toggle_link_text_fn(settings.tags)); + + // TODO: no idea why it's necessary to set this up again. + delete_img.mouseenter( function () { + $(this).attr("src", settings.delete_tag_img_rollover); + }); + delete_img.mouseleave( function () { + $(this).attr("src", settings.delete_tag_img); + }); + }, + success: function() {} + }); + + return true; + }); + } + + // + // Function that builds a tag button. + // + function build_tag_button(tag_str) { + // Build "delete tag" image. + var delete_img = $("<img/>").attr("src", settings.delete_tag_img).addClass("delete-tag-img"); + init_delete_tag_image(delete_img); + + // Build tag button. + var tag_name_elt = $("<span>").text(tag_str).addClass("tag-name"); + tag_name_elt.click( function() { + var tag_name_and_value = tag_str.split(":"); + settings.tag_click_fn(tag_name_and_value[0], tag_name_and_value[1]); + return true; + }); + + var tag_button = $("<span></span>").addClass("tag-button"); + tag_button.append(tag_name_elt); + // Allow delete only if element is editable. + if (settings.editable) { + tag_button.append(delete_img); + } + + return tag_button; + } + +}; diff -r 007f6a80629a74650b72d67821a9505932d284f3 -r 2092948937ac30ef82f71463a235c66d34987088 client/galaxy/scripts/galaxy.base.js --- /dev/null +++ b/client/galaxy/scripts/galaxy.base.js @@ -0,0 +1,658 @@ +// requestAnimationFrame polyfill +(function() { + var lastTime = 0; + var vendors = ['ms', 'moz', 'webkit', 'o']; + for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) { + window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame']; + window.cancelRequestAnimationFrame = window[vendors[x]+ + 'CancelRequestAnimationFrame']; + } + + if (!window.requestAnimationFrame) + window.requestAnimationFrame = function(callback, element) { + var currTime = new Date().getTime(); + var timeToCall = Math.max(0, 16 - (currTime - lastTime)); + var id = window.setTimeout(function() { callback(currTime + timeToCall); }, + timeToCall); + lastTime = currTime + timeToCall; + return id; + }; + + if (!window.cancelAnimationFrame) + window.cancelAnimationFrame = function(id) { + clearTimeout(id); + }; +}()); + +// IE doesn't implement Array.indexOf +if (!Array.indexOf) { + Array.prototype.indexOf = function(obj) { + for (var i = 0, len = this.length; i < len; i++) { + if (this[i] == obj) { + return i; + } + } + return -1; + }; +} + +// Returns the number of keys (elements) in an array/dictionary. +function obj_length(obj) { + if (obj.length !== undefined) { + return obj.length; + } + + var count = 0; + for (var element in obj) { + count++; + } + return count; +} + +$.fn.makeAbsolute = function(rebase) { + return this.each(function() { + var el = $(this); + var pos = el.position(); + el.css({ + position: "absolute", + marginLeft: 0, marginTop: 0, + top: pos.top, left: pos.left, + right: $(window).width() - ( pos.left + el.width() ) + }); + if (rebase) { + el.remove().appendTo("body"); + } + }); +}; + +/** + * Sets up popupmenu rendering and binds options functions to the appropriate links. + * initial_options is a dict with text describing the option pointing to either (a) a + * function to perform; or (b) another dict with two required keys, 'url' and 'action' (the + * function to perform. (b) is useful for exposing the underlying URL of the option. + */ +function make_popupmenu(button_element, initial_options) { + /* Use the $.data feature to store options with the link element. + This allows options to be changed at a later time + */ + var element_menu_exists = (button_element.data("menu_options")); + button_element.data("menu_options", initial_options); + + // If element already has menu, nothing else to do since HTML and actions are already set. + if (element_menu_exists) { return; } + + button_element.bind("click.show_popup", function(e) { + // Close existing visible menus + $(".popmenu-wrapper").remove(); + + // Need setTimeouts so clicks don't interfere with each other + setTimeout( function() { + // Dynamically generate the wrapper holding all the selectable options of the menu. + var menu_element = $( "<ul class='dropdown-menu' id='" + button_element.attr('id') + "-menu'></ul>" ); + var options = button_element.data("menu_options"); + if (obj_length(options) <= 0) { + $("<li>No Options.</li>").appendTo(menu_element); + } + $.each( options, function( k, v ) { + if (v) { + // Action can be either an anonymous function and a mapped dict. + var action = v.action || v; + menu_element.append( $("<li></li>").append( $("<a>").attr("href", v.url).html(k).click(action) ) ); + } else { + menu_element.append( $("<li></li>").addClass( "head" ).append( $("<a href='#'></a>").html(k) ) ); + } + }); + var wrapper = $( "<div class='popmenu-wrapper' style='position: absolute;left: 0; top: -1000;'></div>" ) + .append( menu_element ).appendTo( "body" ); + + var x = e.pageX - wrapper.width() / 2 ; + x = Math.min( x, $(document).scrollLeft() + $(window).width() - $(wrapper).width() - 5 ); + x = Math.max( x, $(document).scrollLeft() + 5 ); + + wrapper.css({ + top: e.pageY, + left: x + }); + }, 10); + + setTimeout( function() { + // Bind click event to current window and all frames to remove any visible menus + // Bind to document object instead of window object for IE compat + var close_popup = function(el) { + $(el).bind("click.close_popup", function() { + $(".popmenu-wrapper").remove(); + el.unbind("click.close_popup"); + }); + }; + close_popup( $(window.document) ); // Current frame + close_popup( $(window.top.document) ); // Parent frame + for (var frame_id = window.top.frames.length; frame_id--;) { // Sibling frames + var frame = $(window.top.frames[frame_id].document); + close_popup(frame); + } + }, 50); + + return false; + }); + +} + +/** + * Convert two seperate (often adjacent) divs into galaxy popupmenu + * - div 1 contains a number of anchors which become the menu options + * - div 1 should have a 'popupmenu' attribute + * - this popupmenu attribute contains the id of div 2 + * - div 2 becomes the 'face' of the popupmenu + * + * NOTE: make_popup_menus finds and operates on all divs with a popupmenu attr (no need to point it at something) + * but (since that selector searches the dom on the page), you can send a parent in + * NOTE: make_popup_menus, and make_popupmenu are horrible names + */ +function make_popup_menus( parent ) { + // find all popupmenu menu divs (divs that contains anchors to be converted to menu options) + // either in the parent or the document if no parent passed + parent = parent || document; + $( parent ).find( "div[popupmenu]" ).each( function() { + var options = {}; + var menu = $(this); + + // find each anchor in the menu, convert them into an options map: { a.text : click_function } + menu.find( "a" ).each( function() { + var link = $(this), + link_dom = link.get(0), + confirmtext = link_dom.getAttribute( "confirm" ), + href = link_dom.getAttribute( "href" ), + target = link_dom.getAttribute( "target" ); + + // no href - no function (gen. a label) + if (!href) { + options[ link.text() ] = null; + + } else { + options[ link.text() ] = { + url: href, + action: function() { + + // if theres confirm text, send the dialog + if ( !confirmtext || confirm( confirmtext ) ) { + // link.click() doesn't use target for some reason, + // so manually do it here. + if (target) { + window.open(href, target); + return false; + } + // For all other links, do the default action. + else { + link.click(); + } + } + } + }; + } + }); + // locate the element with the id corresponding to the menu's popupmenu attr + var box = $( parent ).find( "#" + menu.attr( 'popupmenu' ) ); + + // For menus with clickable link text, make clicking on the link go through instead + // of activating the popup menu + box.find("a").bind("click", function(e) { + e.stopPropagation(); // Stop bubbling so clicking on the link goes through + return true; + }); + + // attach the click events and menu box building to the box element + make_popupmenu(box, options); + box.addClass("popup"); + menu.remove(); + }); +} + +// Alphanumeric/natural sort fn +function naturalSort(a, b) { + // setup temp-scope variables for comparison evauluation + var re = /(-?[0-9\.]+)/g, + x = a.toString().toLowerCase() || '', + y = b.toString().toLowerCase() || '', + nC = String.fromCharCode(0), + xN = x.replace( re, nC + '$1' + nC ).split(nC), + yN = y.replace( re, nC + '$1' + nC ).split(nC), + xD = (new Date(x)).getTime(), + yD = xD ? (new Date(y)).getTime() : null; + // natural sorting of dates + if ( yD ) { + if ( xD < yD ) { return -1; } + else if ( xD > yD ) { return 1; } + } + // natural sorting through split numeric strings and default strings + var oFxNcL, oFyNcL; + for ( var cLoc = 0, numS = Math.max(xN.length, yN.length); cLoc < numS; cLoc++ ) { + oFxNcL = parseFloat(xN[cLoc]) || xN[cLoc]; + oFyNcL = parseFloat(yN[cLoc]) || yN[cLoc]; + if (oFxNcL < oFyNcL) { return -1; } + else if (oFxNcL > oFyNcL) { return 1; } + } + return 0; +} + +$.fn.refresh_select2 = function() { + var select_elt = $(this); + var options = { placeholder:'Click to select', + closeOnSelect: !select_elt.is("[MULTIPLE]"), + dropdownAutoWidth : true, + containerCssClass: 'select2-minwidth' + }; + return select_elt.select2( options ); +} + +// Replace select box with a text input box + autocomplete. +function replace_big_select_inputs(min_length, max_length, select_elts) { + // To do replace, the select2 plugin must be loaded. + + if (!jQuery.fn.select2) { + return; + } + + // Set default for min_length and max_length + if (min_length === undefined) { + min_length = 20; + } + if (max_length === undefined) { + max_length = 3000; + } + + select_elts = select_elts || $('select'); + + select_elts.each( function() { + var select_elt = $(this).not('[multiple]'); + // Make sure that options is within range. + var num_options = select_elt.find('option').length; + if ( (num_options < min_length) || (num_options > max_length) ) { + return; + } + + if (select_elt.hasClass("no-autocomplete")) { + return; + } + + /* Replaced jQuery.autocomplete with select2, notes: + * - multiple selects are supported + * - the original element is updated with the value, convert_to_values should not be needed + * - events are fired when updating the original element, so refresh_on_change should just work + * + * - should we still sort dbkey fields here? + */ + select_elt.refresh_select2(); + }); +} + +/** + * Make an element with text editable: (a) when user clicks on text, a textbox/area + * is provided for editing; (b) when enter key pressed, element's text is set and on_finish + * is called. + */ +// TODO: use this function to implement async_save_text (implemented below). +$.fn.make_text_editable = function(config_dict) { + // Get config options. + var num_cols = ("num_cols" in config_dict ? config_dict.num_cols : 30), + num_rows = ("num_rows" in config_dict ? config_dict.num_rows : 4), + use_textarea = ("use_textarea" in config_dict ? config_dict.use_textarea : false), + on_finish = ("on_finish" in config_dict ? config_dict.on_finish : null), + help_text = ("help_text" in config_dict ? config_dict.help_text : null); + + // Add element behavior. + var container = $(this); + container.addClass("editable-text").click(function(e) { + // If there's already an input element, editing is active, so do nothing. + if ($(this).children(":input").length > 0) { + return; + } + + container.removeClass("editable-text"); + + // Handler for setting element text. + var set_text = function(new_text) { + container.find(":input").remove(); + + if (new_text !== "") { + container.text(new_text); + } + else { + // No text; need a line so that there is a click target. + container.html("<br>"); + } + container.addClass("editable-text"); + + if (on_finish) { + on_finish(new_text); + } + }; + + // Create input element(s) for editing. + var cur_text = ("cur_text" in config_dict ? config_dict.cur_text : container.text() ), + input_elt, button_elt; + + if (use_textarea) { + input_elt = $("<textarea/>") + .attr({ rows: num_rows, cols: num_cols }).text($.trim(cur_text)) + .keyup(function(e) { + if (e.keyCode === 27) { + // Escape key. + set_text(cur_text); + } + }); + button_elt = $("<button/>").text("Done").click(function() { + set_text(input_elt.val()); + // Return false so that click does not propogate to container. + return false; + }); + } + else { + input_elt = $("<input type='text'/>").attr({ value: $.trim(cur_text), size: num_cols }) + .blur(function() { + set_text(cur_text); + }).keyup(function(e) { + if (e.keyCode === 27) { + // Escape key. + $(this).trigger("blur"); + } else if (e.keyCode === 13) { + // Enter key. + set_text($(this).val()); + } + + // Do not propogate event to avoid unwanted side effects. + e.stopPropagation(); + }); + } + + // Replace text with input object(s) and focus & select. + container.text(""); + container.append(input_elt); + if (button_elt) { + container.append(button_elt); + } + input_elt.focus(); + input_elt.select(); + + // Do not propogate to elements below b/c that blurs input and prevents it from being used. + e.stopPropagation(); + }); + + // Add help text if there some. + if (help_text) { + container.attr("title", help_text).tooltip(); + } + + return container; +}; + +/** + * Edit and save text asynchronously. + */ +function async_save_text( click_to_edit_elt, text_elt_id, save_url, + text_parm_name, num_cols, use_textarea, num_rows, on_start, on_finish ) { + // Set defaults if necessary. + if (num_cols === undefined) { + num_cols = 30; + } + if (num_rows === undefined) { + num_rows = 4; + } + + // Set up input element. + $("#" + click_to_edit_elt).click(function() { + // Check if this is already active + if ( $("#renaming-active").length > 0) { + return; + } + var text_elt = $("#" + text_elt_id), + old_text = text_elt.text(), + t; + + if (use_textarea) { + t = $("<textarea></textarea>").attr({ rows: num_rows, cols: num_cols }).text( $.trim(old_text) ); + } else { + t = $("<input type='text'></input>").attr({ value: $.trim(old_text), size: num_cols }); + } + t.attr("id", "renaming-active"); + t.blur( function() { + $(this).remove(); + text_elt.show(); + if (on_finish) { + on_finish(t); + } + }); + t.keyup( function( e ) { + if ( e.keyCode === 27 ) { + // Escape key + $(this).trigger( "blur" ); + } else if ( e.keyCode === 13 ) { + // Enter key submits + var ajax_data = {}; + ajax_data[text_parm_name] = $(this).val(); + $(this).trigger( "blur" ); + $.ajax({ + url: save_url, + data: ajax_data, + error: function() { + alert( "Text editing for elt " + text_elt_id + " failed" ); + // TODO: call finish or no? For now, let's not because error occurred. + }, + success: function(processed_text) { + // Set new text and call finish method. + if (processed_text !== "") { + text_elt.text(processed_text); + } else { + text_elt.html("<em>None</em>"); + } + if (on_finish) { + on_finish(t); + } + } + }); + } + }); + + if (on_start) { + on_start(t); + } + // Replace text with input object and focus & select. + text_elt.hide(); + t.insertAfter(text_elt); + t.focus(); + t.select(); + + return; + }); +} + +function commatize( number ) { + number += ''; // Convert to string + var rgx = /(\d+)(\d{3})/; + while (rgx.test(number)) { + number = number.replace(rgx, '$1' + ',' + '$2'); + } + return number; +} + +// Reset tool search to start state. +function reset_tool_search( initValue ) { + // Function may be called in top frame or in tool_menu_frame; + // in either case, get the tool menu frame. + var tool_menu_frame = $("#galaxy_tools").contents(); + if (tool_menu_frame.length === 0) { + tool_menu_frame = $(document); + } + + // Remove classes that indicate searching is active. + $(this).removeClass("search_active"); + tool_menu_frame.find(".toolTitle").removeClass("search_match"); + + // Reset visibility of tools and labels. + tool_menu_frame.find(".toolSectionBody").hide(); + tool_menu_frame.find(".toolTitle").show(); + tool_menu_frame.find(".toolPanelLabel").show(); + tool_menu_frame.find(".toolSectionWrapper").each( function() { + if ($(this).attr('id') !== 'recently_used_wrapper') { + // Default action. + $(this).show(); + } else if ($(this).hasClass("user_pref_visible")) { + $(this).show(); + } + }); + tool_menu_frame.find("#search-no-results").hide(); + + // Reset search input. + tool_menu_frame.find("#search-spinner").hide(); + if (initValue) { + var search_input = tool_menu_frame.find("#tool-search-query"); + search_input.val("search tools"); + } +} + +// Create GalaxyAsync object. +var GalaxyAsync = function(log_action) { + this.url_dict = {}; + this.log_action = (log_action === undefined ? false : log_action); +}; + +GalaxyAsync.prototype.set_func_url = function( func_name, url ) { + this.url_dict[func_name] = url; +}; + +// Set user preference asynchronously. +GalaxyAsync.prototype.set_user_pref = function( pref_name, pref_value ) { + // Get URL. + var url = this.url_dict[arguments.callee]; + if (url === undefined) { return false; } + $.ajax({ + url: url, + data: { "pref_name" : pref_name, "pref_value" : pref_value }, + error: function() { return false; }, + success: function() { return true; } + }); +}; + +// Log user action asynchronously. +GalaxyAsync.prototype.log_user_action = function( action, context, params ) { + if (!this.log_action) { return; } + + // Get URL. + var url = this.url_dict[arguments.callee]; + if (url === undefined) { return false; } + $.ajax({ + url: url, + data: { "action" : action, "context" : context, "params" : params }, + error: function() { return false; }, + success: function() { return true; } + }); +}; + +// Initialize refresh events. +function init_refresh_on_change () { + $("select[refresh_on_change='true']") + .off('change') + .change(function() { + var select_field = $(this), + select_val = select_field.val(), + refresh = false, + ref_on_change_vals = select_field.attr("refresh_on_change_values"); + if (ref_on_change_vals) { + ref_on_change_vals = ref_on_change_vals.split(','); + var last_selected_value = select_field.attr("last_selected_value"); + if ($.inArray(select_val, ref_on_change_vals) === -1 && $.inArray(last_selected_value, ref_on_change_vals) === -1) { + return; + } + } + $(window).trigger("refresh_on_change"); + $(document).trigger("convert_to_values"); // Convert autocomplete text to values + select_field.get(0).form.submit(); + }); + + // checkboxes refresh on change + $(":checkbox[refresh_on_change='true']") + .off('click') + .click( function() { + var select_field = $(this), + select_val = select_field.val(), + refresh = false, + ref_on_change_vals = select_field.attr("refresh_on_change_values"); + if (ref_on_change_vals) { + ref_on_change_vals = ref_on_change_vals.split(','); + var last_selected_value = select_field.attr("last_selected_value"); + if ($.inArray(select_val, ref_on_change_vals) === -1 && $.inArray(last_selected_value, ref_on_change_vals) === -1) { + return; + } + } + $(window).trigger("refresh_on_change"); + select_field.get(0).form.submit(); + }); + + // Links with confirmation + $( "a[confirm]" ) + .off('click') + .click( function() { + return confirm( $(this).attr("confirm") ); + }); +}; + + +// jQuery plugin to prevent double submission of forms +// Ref: http://stackoverflow.com/questions/2830542/prevent-double-submission-of-form... +jQuery.fn.preventDoubleSubmission = function() { + $(this).on('submit',function(e){ + var $form = $(this); + + if ($form.data('submitted') === true) { + // Previously submitted - don't submit again + e.preventDefault(); + } else { + // Mark it so that the next submit can be ignored + $form.data('submitted', true); + } + }); + + // Keep chainability + return this; +}; + +$(document).ready( function() { + + // Refresh events for form fields. + init_refresh_on_change(); + + // Tooltips + if ( $.fn.tooltip ) { + // Put tooltips below items in panel header so that they do not overlap masthead. + $(".unified-panel-header [title]").tooltip( { placement: 'bottom' } ); + + // default tooltip initialization, it will follow the data-placement tag for tooltip location + // and fallback to 'top' if not present + $("[title]").tooltip(); + } + // Make popup menus. + make_popup_menus(); + + // Replace big selects. + replace_big_select_inputs(20, 1500); + + // If galaxy_main frame does not exist and link targets galaxy_main, + // add use_panels=True and set target to self. + $("a").click( function() { + var anchor = $(this); + var galaxy_main_exists = (parent.frames && parent.frames.galaxy_main); + if ( ( anchor.attr( "target" ) == "galaxy_main" ) && ( !galaxy_main_exists ) ) { + var href = anchor.attr("href"); + if (href.indexOf("?") == -1) { + href += "?"; + } + else { + href += "&"; + } + href += "use_panels=True"; + anchor.attr("href", href); + anchor.attr("target", "_self"); + } + return anchor; + }); + +}); diff -r 007f6a80629a74650b72d67821a9505932d284f3 -r 2092948937ac30ef82f71463a235c66d34987088 client/galaxy/scripts/galaxy.frame.js --- /dev/null +++ b/client/galaxy/scripts/galaxy.frame.js @@ -0,0 +1,224 @@ +// dependencies +define(["galaxy.masthead", "mvc/ui/ui-frames"], function(mod_masthead, Frames) { + +// frame manager +var GalaxyFrame = Backbone.View.extend( +{ + // base element + el_main: 'body', + + // frame active/disabled + active: false, + + // button active + button_active: null, + + // button load + button_load : null, + + // initialize + initialize : function(options) + { + // add to masthead menu + var self = this; + + // create frames + this.frames = new Frames.View({ + visible: false, + }); + + // add activate icon + this.button_active = new mod_masthead.GalaxyMastheadIcon ( + { + icon : 'fa-th', + tooltip : 'Enable/Disable Scratchbook', + onclick : function() { self._activate(); }, + onunload : function() { + if (self.frames.length() > 0) { + return "You opened " + self.frames.length() + " frame(s) which will be lost."; + } + } + }); + + // add to masthead + Galaxy.masthead.append(this.button_active); + + // add load icon + this.button_load = new mod_masthead.GalaxyMastheadIcon ( + { + icon : 'fa-eye', + tooltip : 'Show/Hide Scratchbook', + onclick : function(e) { + if (self.frames.visible) { + self.frames.hide(); + } else { + self.frames.show(); + } + }, + with_number : true + }); + + // add to masthead + Galaxy.masthead.append(this.button_load); + + // create + this.setElement(this.frames.$el); + + // append to main + $(this.el_main).append(this.$el); + + // refresh menu + this.frames.setOnChange(function() { + self._refresh(); + }); + this._refresh(); + }, + + /** + * Add a dataset to the frames. + */ + add_dataset: function(dataset_id) { + var self = this; + require(['mvc/data'], function(DATA) { + var dataset = new DATA.Dataset({ id: dataset_id }); + $.when( dataset.fetch() ).then( function() { + // Construct frame config based on dataset's type. + var frame_config = { + title: dataset.get('name') + }, + // HACK: For now, assume 'tabular' and 'interval' are the only + // modules that contain tabular files. This needs to be replaced + // will a is_datatype() function. + is_tabular = _.find(['tabular', 'interval'], function(data_type) { + return dataset.get('data_type').indexOf(data_type) !== -1; + }); + + // Use tabular chunked display if dataset is tabular; otherwise load via URL. + if (is_tabular) { + var tabular_dataset = new DATA.TabularDataset(dataset.toJSON()); + _.extend(frame_config, { + type: 'other', + content: function( parent_elt ) { + DATA.createTabularDatasetChunkedView({ + model: tabular_dataset, + parent_elt: parent_elt, + embedded: true, + height: '100%' + }); + } + }); + } + else { + _.extend(frame_config, { + type: 'url', + content: galaxy_config.root + 'datasets/' + + dataset.id + '/display/?preview=True' + }); + } + + self.add(frame_config); + + }); + }); + + }, + + /** + * Add and display a new frame/window based on options. + */ + add: function(options) + { + // open new tab + if (options.target == '_blank') + { + window.open(options.content); + return; + } + + // reload entire window + if (options.target == '_top' || options.target == '_parent' || options.target == '_self') + { + window.location = options.content; + return; + } + + // validate + if (!this.active) + { + // fix url if main frame is unavailable + var $galaxy_main = $(window.parent.document).find('#galaxy_main'); + if (options.target == 'galaxy_main' || options.target == 'center') + { + if ($galaxy_main.length === 0) + { + var href = options.content; + if (href.indexOf('?') == -1) + href += '?'; + else + href += '&'; + href += 'use_panels=True'; + window.location = href; + } else { + $galaxy_main.attr('src', options.content); + } + } else + window.location = options.content; + + // stop + return; + } + + // add to frames view + this.frames.add(options); + }, + + // activate/disable panel + _activate: function () + { + // check + if (this.active) + { + // disable + this.active = false; + + // toggle + this.button_active.untoggle(); + + // hide panel + this.frames.hide(); + } else { + // activate + this.active = true; + + // untoggle + this.button_active.toggle(); + } + }, + + // update frame counter + _refresh: function() + { + // update on screen counter + this.button_load.number(this.frames.length()); + + // check + if(this.frames.length() === 0) + this.button_load.hide(); + else + this.button_load.show(); + + // check + if (this.frames.visible) { + this.button_load.toggle(); + } else { + this.button_load.untoggle(); + } + } +}); + +// return +return { + GalaxyFrame: GalaxyFrame +}; + +}); diff -r 007f6a80629a74650b72d67821a9505932d284f3 -r 2092948937ac30ef82f71463a235c66d34987088 client/galaxy/scripts/galaxy.library.js --- /dev/null +++ b/client/galaxy/scripts/galaxy.library.js @@ -0,0 +1,169 @@ +// MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM +// === MAIN GALAXY LIBRARY MODULE ==== +// MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM + +define([ + "galaxy.masthead", + "utils/utils", + "libs/toastr", + "mvc/base-mvc", + "mvc/library/library-model", + "mvc/library/library-folderlist-view", + "mvc/library/library-librarylist-view", + "mvc/library/library-librarytoolbar-view", + "mvc/library/library-foldertoolbar-view", + "mvc/library/library-dataset-view", + "mvc/library/library-library-view", + "mvc/library/library-folder-view" + ], +function(mod_masthead, + mod_utils, + mod_toastr, + mod_baseMVC, + mod_library_model, + mod_folderlist_view, + mod_librarylist_view, + mod_librarytoolbar_view, + mod_foldertoolbar_view, + mod_library_dataset_view, + mod_library_library_view, + mod_library_folder_view + ) { + +// ============================================================================ +// ROUTER +var LibraryRouter = Backbone.Router.extend({ + initialize: function() { + this.routesHit = 0; + //keep count of number of routes handled by the application + Backbone.history.on('route', function() { this.routesHit++; }, this); + }, + + routes: { + "" : "libraries", + "library/:library_id/permissions" : "library_permissions", + "folders/:folder_id/permissions" : "folder_permissions", + "folders/:id" : "folder_content", + "folders/:folder_id/datasets/:dataset_id" : "dataset_detail", + "folders/:folder_id/datasets/:dataset_id/permissions" : "dataset_permissions", + "folders/:folder_id/datasets/:dataset_id/versions/:ldda_id" : "dataset_version", + "folders/:folder_id/download/:format" : "download", + "folders/:folder_id/import/:source" : "import_datasets" + }, + + back: function() { + if(this.routesHit > 1) { + //more than one route hit -> user did not land to current page directly + window.history.back(); + } else { + //otherwise go to the home page. Use replaceState if available so + //the navigation doesn't create an extra history entry + this.navigate('#', {trigger:true, replace:true}); + } + } +}); + +// ============================================================================ +/** session storage for library preferences */ +var LibraryPrefs = mod_baseMVC.SessionStorageModel.extend({ + defaults : { + with_deleted : false, + sort_order : 'asc', + sort_by : 'name' + } +}); + +// ============================================================================ +// Main controller of Galaxy Library +var GalaxyLibrary = Backbone.View.extend({ + + libraryToolbarView: null, + libraryListView: null, + library_router: null, + libraryView: null, + folderToolbarView: null, + folderListView: null, + datasetView: null, + + initialize : function(){ + Galaxy.libraries = this; + + this.preferences = new LibraryPrefs( {id: 'global-lib-prefs'} ); + + this.library_router = new LibraryRouter(); + + this.library_router.on('route:libraries', function() { + Galaxy.libraries.libraryToolbarView = new mod_librarytoolbar_view.LibraryToolbarView(); + Galaxy.libraries.libraryListView = new mod_librarylist_view.LibraryListView(); + }); + + this.library_router.on('route:folder_content', function(id) { + if (Galaxy.libraries.folderToolbarView){ + Galaxy.libraries.folderToolbarView.$el.unbind('click'); + } + Galaxy.libraries.folderToolbarView = new mod_foldertoolbar_view.FolderToolbarView({id: id}); + Galaxy.libraries.folderListView = new mod_folderlist_view.FolderListView({id: id}); + }); + + this.library_router.on('route:download', function(folder_id, format) { + if ($('#folder_list_body').find(':checked').length === 0) { + mod_toastr.info( 'You must select at least one dataset to download' ); + Galaxy.libraries.library_router.navigate('folders/' + folder_id, {trigger: true, replace: true}); + } else { + Galaxy.libraries.folderToolbarView.download(folder_id, format); + Galaxy.libraries.library_router.navigate('folders/' + folder_id, {trigger: false, replace: true}); + } + }); + + this.library_router.on('route:dataset_detail', function(folder_id, dataset_id){ + if (Galaxy.libraries.datasetView){ + Galaxy.libraries.datasetView.$el.unbind('click'); + } + Galaxy.libraries.datasetView = new mod_library_dataset_view.LibraryDatasetView({id: dataset_id}); + }); + this.library_router.on('route:dataset_version', function(folder_id, dataset_id, ldda_id){ + if (Galaxy.libraries.datasetView){ + Galaxy.libraries.datasetView.$el.unbind('click'); + } + Galaxy.libraries.datasetView = new mod_library_dataset_view.LibraryDatasetView({id: dataset_id, ldda_id: ldda_id, show_version: true}); + }); + + this.library_router.on('route:dataset_permissions', function(folder_id, dataset_id){ + if (Galaxy.libraries.datasetView){ + Galaxy.libraries.datasetView.$el.unbind('click'); + } + Galaxy.libraries.datasetView = new mod_library_dataset_view.LibraryDatasetView({id: dataset_id, show_permissions: true}); + }); + + this.library_router.on('route:library_permissions', function(library_id){ + if (Galaxy.libraries.libraryView){ + Galaxy.libraries.libraryView.$el.unbind('click'); + } + Galaxy.libraries.libraryView = new mod_library_library_view.LibraryView({id: library_id, show_permissions: true}); + }); + + this.library_router.on('route:folder_permissions', function(folder_id){ + if (Galaxy.libraries.folderView){ + Galaxy.libraries.folderView.$el.unbind('click'); + } + Galaxy.libraries.folderView = new mod_library_folder_view.FolderView({id: folder_id, show_permissions: true}); + }); + this.library_router.on('route:import_datasets', function(folder_id, source){ + if (Galaxy.libraries.folderToolbarView && Galaxy.libraries.folderListView){ + Galaxy.libraries.folderToolbarView.showImportModal({source:source}); + } else { + Galaxy.libraries.folderToolbarView = new mod_foldertoolbar_view.FolderToolbarView({id: folder_id}); + Galaxy.libraries.folderListView = new mod_folderlist_view.FolderListView({id: folder_id}); + Galaxy.libraries.folderToolbarView.showImportModal({source: source}); + } + }); + + Backbone.history.start({pushState: false}); + } +}); + +return { + GalaxyApp: GalaxyLibrary +}; + +}); This diff is so big that we needed to truncate the remainder. https://bitbucket.org/galaxy/galaxy-central/commits/3b3cd242b4b7/ Changeset: 3b3cd242b4b7 Branch: stable User: natefoo Date: 2014-10-06 16:59:17+00:00 Summary: Added tag release_2014.10.06 for changeset 2092948937ac Affected #: 1 file diff -r 2092948937ac30ef82f71463a235c66d34987088 -r 3b3cd242b4b7cd20b9c868c393c455524b31b87c .hgtags --- a/.hgtags +++ b/.hgtags @@ -19,3 +19,4 @@ 2a756ca2cb1826db7796018e77d12e2dd7b67603 latest_2014.02.10 ca45b78adb4152fc6e7395514d46eba6b7d0b838 release_2014.08.11 548ab24667d6206780237bd807f7d857a484c461 latest_2014.08.11 +2092948937ac30ef82f71463a235c66d34987088 release_2014.10.06 Repository URL: https://bitbucket.org/galaxy/galaxy-central/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email.