diff --git a/client/galaxy/scripts/apps/analysis.js b/client/galaxy/scripts/apps/analysis.js index bc929786f05..9ba05169265 100644 --- a/client/galaxy/scripts/apps/analysis.js +++ b/client/galaxy/scripts/apps/analysis.js @@ -1,9 +1,88 @@ -var App = require( '../mvc/app/app-view' ); +var jQuery = require( 'jquery' ), + $ = jQuery, + PANEL = require( 'layout/panel' ), + ToolPanel = require( './tool-panel' ), + HistoryPanel = require( './history-panel' ), + PAGE = require( 'layout/page' ), + ToolsForm = require( 'mvc/tools/tools-form' ); -//TODO: this doesn't address multiple apps per page window.app = function app( options, bootstrapped ){ - $( function() { - var app = new App( options.config ); + Galaxy.debug( 'building app:', options, bootstrapped ); + + // .................................................... panels and page + var toolPanel = new ToolPanel({ + el : '#left', + userIsAnonymous : Galaxy.user.isAnonymous(), + require_login : options.config.require_login, + spinner_url : options.config.spinner_url, + search_url : options.config.search_url, + toolbox : options.config.toolbox, + toolbox_in_panel: options.config.toolbox_in_panel, + stored_workflow_menu_entries : options.config.stored_workflow_menu_entries, + }), + centerPanel = new PANEL.CenterPanel({ + el : '#center' + }), + historyPanel = new HistoryPanel({ + el : '#right', + galaxyRoot : Galaxy.root, + userIsAnonymous : Galaxy.user.isAnonymous(), + allow_user_dataset_purge: Galaxy.config.allow_user_dataset_purge, + }), + analysisPage = new PAGE.PageLayoutView( _.extend( options, { + el : 'body', + left : toolPanel, + center : centerPanel, + right : historyPanel, + })); + + // .................................................... decorate the galaxy object + // TODO: most of this is becoming unnecessary as we move to apps + Galaxy.page = analysisPage; + + // add tool panel to Galaxy object + Galaxy.toolPanel = toolPanel.tool_panel; + Galaxy.upload = toolPanel.uploadButton; + + Galaxy.currHistoryPanel = historyPanel.historyView; + Galaxy.currHistoryPanel.connectToQuotaMeter( Galaxy.quotaMeter ); + Galaxy.currHistoryPanel.listenToGalaxy( Galaxy ); + + //HACK: move to router/App + Galaxy.app = { + display : function( view, target ){ + // TODO: Remove this line after select2 update + $( '.select2-hidden-accessible' ).remove(); + centerPanel.display( view ); + }, + }; + + // .................................................... start up + $(function(){ + analysisPage.render(); + Galaxy.currHistoryPanel.loadCurrentHistory(); + + // TODO: to router, remove Globals + var params = $.extend( {}, options.config.params ); + if( params.tool_id && params.tool_id !== 'upload1' ){ + params.id = params.tool_id; + centerPanel.display( new ToolsForm.View( params ) ); + + } else if( params.job_id ){ + params.id = params.tool_id; + centerPanel.display( new ToolsForm.View( params ) ); + + } else { + var iframeUrl = Galaxy.root; + if( params.workflow_id ){ + iframeUrl += 'workflow/run?id=' + params.workflow_id; + } else if( params.m_c ){ + iframeUrl += params.m_c + '/' + params.m_a; + } else { + iframeUrl += 'root/welcome'; + } + centerPanel.$( '#galaxy_main' ).prop( 'src', iframeUrl ); + } }); }; diff --git a/client/galaxy/scripts/apps/galaxy.js b/client/galaxy/scripts/apps/galaxy.js index 3cbb69a7545..b1899f89723 100644 --- a/client/galaxy/scripts/apps/galaxy.js +++ b/client/galaxy/scripts/apps/galaxy.js @@ -1,4 +1,4 @@ +//TODO: remove this var GalaxyApp = require( '../galaxy-app-base' ).GalaxyApp; window.GalaxyApp = GalaxyApp; -console.debug( 'galaxy.js, GalaxyApp is now in window' ); exports = GalaxyApp; diff --git a/client/galaxy/scripts/apps/history-panel.js b/client/galaxy/scripts/apps/history-panel.js new file mode 100644 index 00000000000..fc0f4feed73 --- /dev/null +++ b/client/galaxy/scripts/apps/history-panel.js @@ -0,0 +1,83 @@ +var RightPanel = require( 'layout/panel' ).RightPanel, + Ui = require( 'mvc/ui/ui-misc' ), + historyOptionsMenu = require( 'mvc/history/options-menu' ); + CurrentHistoryView = require( 'mvc/history/history-panel-edit-current' ).CurrentHistoryPanel; + +//TODO: localize +// rename other {history,list}-panels to {history,list}-views +var HistoryPanel = RightPanel.extend({ + + title : 'History', + + initialize : function( options ){ + RightPanel.prototype.initialize.call( this, options ); + var self = this; + + // this button re-fetches the history and contents and re-renders the history panel + this.refreshButton = new Ui.ButtonLink({ + id : 'history-refresh-button', + title : 'Refresh history', + cls : 'panel-header-button', + icon : 'fa fa-refresh', + onclick : function() { + self.historyView.loadCurrentHistory(); + } + }); + // opens a drop down menu with history related functions (like view all, delete, share, etc.) + this.optionsButton = new Ui.ButtonLink({ + id : 'history-options-button', + title : 'History options', + cls : 'panel-header-button', + icon : 'fa fa-cog', + }); + // goes to a page showing all the users histories in panel form (for logged in users) + this.viewMultiButton = null; + if( !options.userIsAnonymous ){ + this.viewMultiButton = new Ui.ButtonLink({ + id : 'history-view-multi-button', + title : 'View all histories', + cls : 'panel-header-button', + icon : 'fa fa-columns', + href : options.galaxyRoot + 'history/view_multiple' + }); + } + + // build history options menu + this.optionsMenu = historyOptionsMenu( this.optionsButton.$el, { + anonymous : options.userIsAnonymous, + purgeAllowed : options.allow_user_dataset_purge, + root : options.galaxyRoot + }); + + // view of the current history + this.historyView = new CurrentHistoryView({ + purgeAllowed : options.allow_user_dataset_purge, + linkTarget : 'galaxy_main', + $scrollContainer: function(){ return this.$el.parent(); } + }); + }, + + render : function(){ + RightPanel.prototype.render.call( this ); + this.debug( 'rendering:' ); + this.$( '.panel-header-buttons' ).append([ + this.refreshButton.$el, + this.optionsButton.$el, + this.viewMultiButton? this.viewMultiButton.$el : null, + ]); + this.historyView.setElement( this.$( '.history-panel' ) ); + }, + + /** panel dom template. id is 'right' or 'left' */ + _templateBody : function( data ){ + return [ + '
', + '
', + '
' + ].join(''); + }, + + toString : function(){ return 'HistoryPanel'; } +}); + +module.exports = HistoryPanel; \ No newline at end of file diff --git a/client/galaxy/scripts/apps/tool-panel.js b/client/galaxy/scripts/apps/tool-panel.js new file mode 100644 index 00000000000..c268da65163 --- /dev/null +++ b/client/galaxy/scripts/apps/tool-panel.js @@ -0,0 +1,108 @@ +var LeftPanel = require( 'layout/panel' ).LeftPanel, + Tools = require( 'mvc/tools' ), + Upload = require( 'mvc/upload/upload-view' ); +//TODO: localize + +/* Builds the tool panel on the left */ +var ToolPanel = LeftPanel.extend({ + + title : 'Tools', + + initialize: function( options ){ + LeftPanel.prototype.initialize.call( this, options ); + this.log( this + '.initialize:', options ); + this.stored_workflow_menu_entries = options.stored_workflow_menu_entries || []; + + // TODO: this switch doesn't belong here? + if( options.userIsAnonymous || !options.require_login ){ + // create tool search, tool panel, and tool panel view. + var tool_search = new Tools.ToolSearch({ + spinner_url : options.spinner_url, + search_url : options.search_url, + hidden : false + }); + var tools = new Tools.ToolCollection( options.toolbox ); + this.tool_panel = new Tools.ToolPanel({ + tool_search : tool_search, + tools : tools, + layout : options.toolbox_in_panel + }); + this.tool_panel_view = new Tools.ToolPanelView({ model: this.tool_panel }); + // add upload plugin + this.uploadButton = new Upload( options ); + } + }, + + render : function(){ + var self = this; + LeftPanel.prototype.render.call( self ); + self.$( '.panel-header-buttons' ).append( self.uploadButton.$el ); + + // if there are tools, render panel and display everything + if (self.tool_panel.get( 'layout' ).size() > 0) { + self.tool_panel_view.render(); + //TODO: why the hide/show? + self.$( '.toolMenu' ).show(); + } + self.$( '.toolMenuContainer' ).prepend( self.tool_panel_view.$el ); + + self._renderWorkflowMenu(); + + // if a tool link has the minsizehint attribute, handle it here (gen. by hiding the tool panel) + self.$( 'a[minsizehint]' ).click( function() { + if ( parent.handle_minwidth_hint ) { + parent.handle_minwidth_hint( $( self ).attr( 'minsizehint' ) ); + } + }); + }, + + _renderWorkflowMenu : function(){ + var self = this; + // add internal workflow list + self.$( '#internal-workflows' ).append( self._templateTool({ + title : 'All workflows', + href : 'workflow/list_for_run' + })); + _.each( self.stored_workflow_menu_entries, function( menu_entry ){ + self.$( '#internal-workflows' ).append( self._templateTool({ + title : menu_entry.stored_workflow.name, + href : 'workflow/run?id=' + menu_entry.encoded_stored_workflow_id + })); + }); + }, + + _templateTool: function( tool ) { + return [ + '
', + // global + '', tool.title, '', + '
' + ].join(''); + }, + + _templateBody : function(){ + return [ + '
', + '
', + '', + '
', + '
', + '
', + 'Workflows', + '
', + '
', + '
', + '
', + '
', + '
' + ].join(''); + }, + + toString : function(){ return 'ToolPanel'; } +}); + +module.exports = ToolPanel; \ No newline at end of file diff --git a/client/galaxy/scripts/galaxy-app-base.js b/client/galaxy/scripts/galaxy-app-base.js index c005b37ef12..5825ce743db 100644 --- a/client/galaxy/scripts/galaxy-app-base.js +++ b/client/galaxy/scripts/galaxy-app-base.js @@ -19,7 +19,6 @@ define([ * user : the current user (as a mvc/user/user-model) */ function GalaxyApp( options, bootstrapped ){ - console.debug( 'GalaxyApp:', options, bootstrapped ); var self = this; return self._init( options || {}, bootstrapped || {} ); } @@ -43,19 +42,20 @@ GalaxyApp.prototype._init = function __init( options, bootstrapped ){ _.extend( self, Backbone.Events ); if( localDebugging ){ self.logger = console; + console.debug( 'debugging galaxy:', 'options:', options, 'bootstrapped:', bootstrapped ); } self._processOptions( options ); - self.debug( 'GalaxyApp.options: ', self.options ); // special case for root self.root = options.root || '/'; self._initConfig( options.config || bootstrapped.config || {} ); - self.debug( 'GalaxyApp.config: ', self.config ); - self._patchGalaxy( window.Galaxy ); self._initLogger( self.options.loggerOptions || {} ); + // at this point, either logging or not and namespaces are enabled - chat it up + self.debug( 'GalaxyApp.options: ', self.options ); + self.debug( 'GalaxyApp.config: ', self.config ); self.debug( 'GalaxyApp.logger: ', self.logger ); self._initLocale(); @@ -86,7 +86,6 @@ GalaxyApp.prototype.defaultOptions = { GalaxyApp.prototype._processOptions = function _processOptions( options ){ var self = this, defaults = self.defaultOptions; - self.debug( '_processOptions: ', options ); self.options = {}; for( var k in defaults ){ @@ -100,7 +99,6 @@ GalaxyApp.prototype._processOptions = function _processOptions( options ){ /** parse the config and any extra info derived from it */ GalaxyApp.prototype._initConfig = function _initConfig( config ){ var self = this; - self.debug( '_initConfig: ', config ); self.config = config; // give precendence to localdebugging for this setting @@ -114,12 +112,12 @@ GalaxyApp.prototype._patchGalaxy = function _patchGalaxy( 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 ); + // 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.debug( '\t patching in ' + k + ' to Galaxy:', self[ k ] ); self[ k ] = patchWith[ k ]; } } @@ -138,7 +136,6 @@ GalaxyApp.prototype._initLogger = function _initLogger( loggerOptions ){ loggerOptions.consoleNamespaceWhitelist = localStorage.getItem( NAMESPACE_KEY ).split( ',' ); } catch( storageErr ){} } - self.debug( '_initLogger:', loggerOptions ); self.logger = new metricsLogger.MetricsLogger( loggerOptions ); if( self.config.debug ){ diff --git a/client/galaxy/scripts/layout/page.js b/client/galaxy/scripts/layout/page.js new file mode 100644 index 00000000000..23a6a08f12b --- /dev/null +++ b/client/galaxy/scripts/layout/page.js @@ -0,0 +1,149 @@ +define([ + 'layout/masthead', + 'layout/panel', + 'mvc/ui/ui-modal', + 'mvc/base-mvc' +], function( MASTHEAD, PANEL, MODAL, BASE_MVC ) { + +// ============================================================================ +var PageLayoutView = Backbone.View.extend( BASE_MVC.LoggableMixin ).extend({ + _logNamespace : 'layout', + + el : 'body', + className : 'full-content', + + _panelIds : [ + 'left', 'center', 'right' + ], + + defaultOptions : { + message_box_visible : false, + message_box_content : '', + message_box_class : 'info', + + show_inactivity_warning : false, + inactivity_box_content : '', + }, + + initialize : function( options ){ + this.log( this + '.initialize:', options ); + _.extend( this, _.pick( options, this._panelIds ) ); + this.options = _.defaults( _.omit( options, this._panelIds ), this.defaultOptions ); + }, + + /** */ + $everything : function(){ + return this.$( '#everything' ); + }, + + render : function(){ + this.log( this + '.render:' ); + this.$el.attr( 'scroll', 'no' ); + this.$el.html( this.template( this.options ) ); + + this.modal = new MODAL.View(); + //TODO: no render on masthead, needs init each time + this.masthead = new MASTHEAD.GalaxyMasthead( this.options.config ); + + // TODO: remove globals + Galaxy.modal = this.modal; + Galaxy.masthead = this.masthead; + + if( this.options.message_box_visible ){ + this.messageBox( this.options.message_box_content, this.options.message_box_class ); + } + if( this.options.show_inactivity_warning ){ + this.inactivityWarning( this.options.inactivity_box_content ); + } + + this.renderPanels(); + return this; + }, + + /** */ + messageBox : function( content, level ){ + content = content || ''; + level = level || 'info'; + this.$el.addClass( 'has-message-box' ); + this.$( '#messagebox' ) + .attr( 'class', 'panel-' + level + '-message' ) + .html( content ) + .toggle( !!content ); + return this; + }, + + /** */ + inactivityWarning : function( content ){ + var verificationLink = 'Resend verification.'; + this.$el.addClass( 'has-inactivity-box' ); + this.$( '#inactivebox' ) + .html( content ) + .append( ' ' + verificationLink ) + .toggle( !!content ); + return this; + }, + + /** */ + renderPanels : function(){ + var page = this; + // TODO: Remove this line after select2 update + $( '.select2-hidden-accessible' ).remove(); + this._panelIds.forEach( function( panelId ){ + if( _.has( page, panelId ) ){ + var panelView = page[ panelId ]; + panelView.setElement( '#' + panelId ); + panelView.render(); + } + }); + return this; + }, + + /** body template */ + //TODO: to underscore + template: function( options ){ + //TODO: remove inline styling + return [ + '
', + '
', + '', + '', + // content here + // TODO: wrapping div + this.left? '
' : '', + this.center? '
' : '', + this.right? '', + // a dropdown overlay for capturing clicks/drags + '', + // display message when js is disabled + '' + ].join(''); + }, + + hideSidePanels : function(){ + if( this.left ){ + this.left.hide(); + } + if( this.right ){ + this.right.hide(); + } + }, + + toString : function(){ return 'PageLayoutView'; } +}); + +// ============================================================================ + return { + PageLayoutView: PageLayoutView + }; +}); diff --git a/client/galaxy/scripts/layout/panel.js b/client/galaxy/scripts/layout/panel.js index c2b3a39383e..4facedbc31f 100644 --- a/client/galaxy/scripts/layout/panel.js +++ b/client/galaxy/scripts/layout/panel.js @@ -1,137 +1,286 @@ define([ 'jquery', -], function( jQuery ){ + 'libs/underscore', + 'libs/backbone', + 'mvc/base-mvc', +], function( jQuery, _, Backbone, BASE_MVC ){ "use strict"; // ============================================================================ var $ = jQuery; -// Panels var MIN_PANEL_WIDTH = 160, MAX_PANEL_WIDTH = 800; -/** */ -var Panel = function( options ) { - this.$panel = options.panel; - this.$center = options.center; - this.$drag = options.drag; - this.$toggle = options.toggle; - this.left = !options.right; - this.hidden = false; - this.hidden_by_tool = false; - this.saved_size = null; - this.init(); -}; +// ---------------------------------------------------------------------------- +/** + * + */ +var SidePanel = Backbone.View.extend( BASE_MVC.LoggableMixin ).extend({ + _logNamespace : 'layout', -$.extend( Panel.prototype, { + initialize: function( attributes ){ + this.log( this + '.initialize:', attributes ); + this.title = attributes.title || this.title || ''; - /** */ - resize: function( x ) { - this.$panel.css( "width", x ); - if ( this.left ) { - this.$center.css( "left", x ); - } else { - this.$center.css( "right", x ); - } - // ie7-recalc.js - if ( document.recalc ) { document.recalc(); } + this.hidden = false; + this.savedSize = null; + this.hiddenByTool = false; }, - /** */ - do_toggle: function() { - var self = this; - if ( this.hidden ) { - this.$toggle.removeClass( "hidden" ); - if ( this.left ) { - this.$panel.css( "left", - this.saved_size ).show().animate( { "left": 0 }, "fast", function () { - self.resize( self.saved_size ); - }); - } else { - this.$panel.css( "right", - this.saved_size ).show().animate( { "right": 0 }, "fast", function () { - self.resize( self.saved_size ); - }); - } - self.hidden = false; - } else { - self.saved_size = this.$panel.width(); - if ( document.recalc ) { document.recalc(); } - // Hide border - if ( this.left ) { - this.$panel.animate( { left: - this.saved_size }, "fast" ); - } else { - this.$panel.animate( { right: - this.saved_size }, "fast" ); - } - // self.resize(0); - if ( this.left ) { - this.$center.css( "left", 0 ); - } else { - this.$center.css( "right", 0 ); - } - - self.hidden = true; - self.$toggle.addClass( "hidden" ); - } - this.hidden_by_tool = false; + $center : function(){ + return this.$el.siblings( '#center' ); }, - /** */ - handle_minwidth_hint: function( x ) { - var space = this.$center.width() - ( this.hidden ? this.saved_size : 0 ); - if ( space < x ) - { - if ( ! this.hidden ) { - this.do_toggle(); - this.hidden_by_tool = true; - } - } else { - if ( this.hidden_by_tool ) { - this.do_toggle(); - this.hidden_by_tool = false; - } - } + $toggleButton : function(){ + return this.$( '.unified-panel-footer > .panel-collapse' ); }, - /** */ - force_panel: function( op ) { - if ( ( this.hidden && op == 'show' ) || ( ! this.hidden && op == 'hide' ) ) { - this.do_toggle(); - } + render: function(){ + this.log( this + '.render:' ); + this.$el.html( this.template( this.id ) ); }, - /** */ - init: function() { + /** panel dom template. id is 'right' or 'left' */ + template: function(){ + return [ + this._templateHeader(), + this._templateBody(), + this._templateFooter(), + ].join(''); + }, + + /** panel dom template. id is 'right' or 'left' */ + _templateHeader: function( data ){ + return [ + '
', + '
', + '
', + '
', _.escape( this.title ), '
', + '
', + '
', + ].join(''); + }, + + /** panel dom template. id is 'right' or 'left' */ + _templateBody: function( data ){ + return '
'; + }, + + /** panel dom template. id is 'right' or 'left' */ + _templateFooter: function( data ){ + return [ + '