
1 new commit in galaxy-central: https://bitbucket.org/galaxy/galaxy-central/commits/ff5d57faf17d/ Changeset: ff5d57faf17d User: carlfeberhard Date: 2014-09-29 14:54:01+00:00 Summary: History multi-view: documentation and some cleanup Affected #: 3 files diff -r 633b4212806d850135a28966d9759da3157b22e8 -r ff5d57faf17d38d345105300097fc7d196f3527e client/galaxy/scripts/mvc/history/multi-panel.js --- a/client/galaxy/scripts/mvc/history/multi-panel.js +++ b/client/galaxy/scripts/mvc/history/multi-panel.js @@ -30,6 +30,7 @@ } return name; } + function copyHistory( name ){ var $copyIndicator = $( '<p><span class="fa fa-spinner fa-spin"></span> Copying history...</p>' ) .css( 'margin-top', '8px' ); @@ -69,21 +70,16 @@ /* ============================================================================== TODO: copy places old current in wrong location - size not updating on dnd - sort by size is broken + sort by size is confusing (but correct) - nice_size shows actual disk usage, !== size handle delete current currently manual by user - delete then create new - make one step render move all columns over as one (agg. then html()) loading indicator on startup - loading indicators on histories (while queueHdaLoading) - __panelPatchRenderEmptyMsg performance - page load takes a while - renders where blocking each other - used _.delay( 0 ) - sort takes a while rendering columns could be better - lazy load history list + lazy load history list (for large numbers) + req. API limit/offset move in-view from pubsub handle errors @@ -103,10 +99,11 @@ privatize non-interface fns search for and drag and drop - should reset after dataset is loaded (or alt. clear search) - better ajax error handling ?columns should be a panel, not have a panel ============================================================================== */ +/** @class A container for a history panel that renders controls for that history (delete, copy, etc.) + */ var HistoryPanelColumn = Backbone.View.extend( baseMVC.LoggableMixin ).extend({ //TODO: extend from panel? (instead of aggregating) @@ -120,7 +117,7 @@ }, // ------------------------------------------------------------------------ set up - /** */ + /** set up passed-in panel (if any) and listeners */ initialize : function initialize( options ){ options = options || {}; @@ -132,7 +129,7 @@ this.setUpListeners(); }, - /** */ + /** create a history panel for this column */ createPanel : function createPanel( panelOptions ){ panelOptions = _.extend({ model : this.model, @@ -148,7 +145,8 @@ }, //TODO: needs work - /** */ +//TODO: move to stub-class to avoid monkey-patching + /** the function monkey-patched into panels to show contents loading state */ __patch_renderEmptyMessage : function( $whereTo ){ var panel = this, hdaCount = _.chain( this.model.get( 'state_ids' ) ).values().flatten().value().length, @@ -170,7 +168,7 @@ return $emptyMsg; }, - /** */ + /** set up reflexive listeners */ setUpListeners : function setUpListeners(){ var column = this; //this.log( 'setUpListeners', this ); @@ -180,7 +178,7 @@ this.setUpPanelListeners(); }, - /** */ + /** set listeners needed for panel */ setUpPanelListeners : function setUpPanelListeners(){ var column = this; this.listenTo( this.panel, { @@ -193,7 +191,7 @@ }, this ); }, - /** */ + /** do the dimensions of this column overlap the given (horizontal) browser coords? */ inView : function( viewLeft, viewRight ){ //TODO: offset is expensive var columnLeft = this.$el.offset().left, @@ -203,13 +201,13 @@ return true; }, - /** */ + /** shortcut to the panel */ $panel : function $panel(){ return this.$( '.history-panel' ); }, // ------------------------------------------------------------------------ render - /** */ + /** render ths column, its panel, and set up plugins */ render : function render( speed ){ speed = ( speed !== undefined )?( speed ):( 'fast' ); //this.log( this + '.render', this.$el, this.el ); @@ -225,14 +223,14 @@ return this; }, - /** */ + /** set up plugins */ setUpBehaviors : function setUpBehaviors(){ //this.log( 'setUpBehaviors:', this ); //var column = this; // on panel size change, ... }, - /** */ + /** column body template with inner div for panel based on data (model json) */ template : function template( data ){ data = data || {}; var html = [ @@ -253,6 +251,7 @@ return $( html ); }, + /** controls template displaying controls above the panel based on this.currentHistory */ controlsLeftTemplate : function(){ return ( this.currentHistory )? [ @@ -267,7 +266,7 @@ ].join( '' ); }, - /** */ + /** render the panel contained in the column using speed for fx speed */ renderPanel : function renderPanel( speed ){ speed = ( speed !== undefined )?( speed ):( 'fast' ); this.panel.setElement( this.$panel() ).render( speed ); @@ -275,9 +274,11 @@ }, // ------------------------------------------------------------------------ behaviors and events - /** */ + /** event map */ events : { + // will make this the current history 'click .switch-to.btn' : function(){ this.model.setAsCurrent(); }, + // toggles deleted here and on the server and re-renders 'click .delete-history.btn' : function(){ var column = this, xhr; @@ -294,17 +295,18 @@ column.render(); }); }, + // will copy this history and make the copy the current history 'click .copy-history.btn' : 'copy' }, // ------------------------------------------------------------------------ non-current controls - /** */ + /** Open a modal to get a new history name, copy it (if not canceled), and makes the copy current */ copy : function copy(){ historyCopyDialog( this.model ); }, // ------------------------------------------------------------------------ misc - /** */ + /** String rep */ toString : function(){ return 'HistoryPanelColumn(' + ( this.panel? this.panel : '' ) + ')'; } @@ -312,13 +314,14 @@ //============================================================================== -// a multi-history view that displays histories in full +/** @class A view of a HistoryCollection and displays histories similarly to the current history panel. + */ var MultiPanelColumns = Backbone.View.extend( baseMVC.LoggableMixin ).extend({ //logger : console, // ------------------------------------------------------------------------ set up - /** */ + /** Set up internals, history collection, and columns to display the history */ initialize : function initialize( options ){ options = options || {}; this.log( this + '.init', options ); @@ -352,8 +355,6 @@ this.collection = null; this.setCollection( options.histories || [] ); - ///** the column views of histories */ - //this.columns = []; /** model id to column map */ this.columnMap = {}; //TODO: why create here? @@ -362,14 +363,16 @@ this.setUpListeners(); }, - /** */ + /** Set up reflexive listeners */ setUpListeners : function setUpListeners(){ //var multipanel = this; //multipanel.log( 'setUpListeners', multipanel ); }, // ------------------------------------------------------------------------ collection - /** */ + /** Set up a (new) history collection, sorting and adding listeners + * @fires 'new-collection' when set with this view as the arg + */ setCollection : function setCollection( models ){ var multipanel = this; multipanel.stopListening( multipanel.collection ); @@ -383,6 +386,7 @@ return multipanel; }, + /** Set up listeners for the collection - handling: added histories, change of current, deletion, and sorting */ setUpCollectionListeners : function(){ var multipanel = this, collection = multipanel.collection; @@ -394,15 +398,16 @@ // handle deleting a history (depends on whether panels is including deleted or not) 'change:deleted': multipanel.handleDeletedHistory, - 'sort' : function(){ multipanel.renderColumns( 0 ); }, + 'sort' : function(){ multipanel.renderColumns( 0 ); } + // debugging //'all' : function(){ // console.info( 'collection:', arguments ); //} }); }, - /** */ + /** Re-render and set currentHistoryId to reflect a new current history */ setCurrentHistory : function setCurrentHistory( history ){ var oldCurrentColumn = this.columnMap[ this.currentHistoryId ]; if( oldCurrentColumn ){ @@ -425,7 +430,9 @@ return newCurrentColumn; }, - /** */ + /** Either remove a deleted history or re-render it to show the deleted message + * based on collection.includeDeleted + */ handleDeletedHistory : function handleDeletedHistory( history ){ if( history.get( 'deleted' ) ){ this.log( 'handleDeletedHistory', this.collection.includeDeleted, history ); @@ -446,7 +453,9 @@ } }, - /** */ + /** Sort this collection based on order (defaulting to this.order) a string key + * (sorting the collection will re-render the panel) + */ sortCollection : function( order, options ){ order = order || this.order; var currentHistoryId = this.currentHistoryId; @@ -477,7 +486,7 @@ return this.collection; }, - /** */ + /** Set the sort order and re-sort */ setOrder : function( order ){ if( [ 'update', 'name', 'size' ].indexOf( order ) === -1 ){ order = 'update'; @@ -487,12 +496,12 @@ return this; }, - /** */ + /** create a new history and set it to current */ create : function( ev ){ return this.collection.create({ current: true }); }, - ///** */ + ///** delete the current history */ //deleteCurrent : function deleteCurrent(){ // var multipanel = this, // currentColumn = multipanel.columnMap[ multipanel.currentHistoryId ]; @@ -515,7 +524,7 @@ }); }, - /** */ + /** create a column and its panel and set up any listeners to them */ createColumn : function createColumn( history, options ){ // options passed can be re-used, so extend them before adding the model to prevent pollution for the next options = _.extend( {}, options, { model: history }); @@ -525,10 +534,6 @@ return column; }, - columnMapLength : function(){ - return Object.keys( this.columnMap ).length; - }, - sortedFilteredColumns : function( filters ){ filters = filters || this.filters; if( !filters || !filters.length ){ @@ -598,7 +603,7 @@ }); }, - /** */ + /** set up listeners for a column and it's panel - handling: hda lazy-loading, drag and drop */ setUpColumnListeners : function setUpColumnListeners( column ){ var multipanel = this; multipanel.listenTo( column, { @@ -641,8 +646,13 @@ }, + /** conv. fn to count the columns in columnMap */ + columnMapLength : function(){ + return Object.keys( this.columnMap ).length; + }, + // ------------------------------------------------------------------------ render - /** */ + /** Render this view, columns, and set up view plugins */ render : function render( speed ){ speed = speed !== undefined? speed: this.fxSpeed; var multipanel = this; @@ -660,14 +670,16 @@ return multipanel; }, - /** */ + /** Template - overall structure relies on flex-boxes and is 3 components: header, middle, footer */ template : function template( options ){ options = options || {}; var html = []; if( this.options.headerHeight ){ html = html.concat([ + // a loading overlay '<div class="loading-overlay flex-row"><div class="loading-overlay-message">loading...</div></div>', '<div class="header flex-column-container">', + // page & history controls '<div class="header-control header-control-left flex-column">', '<button class="done btn btn-default">', _l( 'Done' ), '</button>', '<button class="include-deleted btn btn-default"></button>', @@ -689,10 +701,12 @@ '</div>', '<div id="search-histories" class="header-search"></div>', '</div>', + // feedback '<div class="header-control header-control-center flex-column">', '<div class="header-info">', '</div>', '</div>', + // dataset controls '<div class="header-control header-control-right flex-column">', '<div id="search-datasets" class="header-search"></div>', '<button id="toggle-deleted" class="btn btn-default">', @@ -707,15 +721,17 @@ } html = html.concat([ + // middle - where the columns go '<div class="outer-middle flex-row flex-row-container">', '<div class="middle flex-column-container flex-row"></div>', '</div>', + // footer '<div class="footer flex-column-container">','</div>' ]); return $( html.join( '' ) ); }, - /** */ + /** Render the columns and panels */ renderColumns : function renderColumns( speed ){ speed = speed !== undefined? speed: this.fxSpeed; //this.log( 'renderColumns:', speed ); @@ -780,14 +796,16 @@ if( this.searchFor && sortedAndFiltered.length <= 1 ){ } else { + // check for in-view, hda lazy-loading if so multipanel.checkColumnsInView(); + // the first, current column has position: fixed and flex css will not apply - adjust height manually this._recalcFirstColumnHeight(); } return sortedAndFiltered; }, - /** */ + /** Render a single column using the non-blocking setTimeout( 0 ) pattern */ renderColumn : function( column, speed ){ speed = speed !== undefined? speed: this.fxSpeed; return _.delay( function(){ return column.render( speed ); }, 0 ); @@ -796,7 +814,9 @@ //TODO: combine the following two more sensibly //TODO: could have HistoryContents.haveDetails return false // if column.model.contents.length === 0 && !column.model.get( 'empty' ) then just check that - /** */ + /** Get the *summary* contents of a column's history (and details on any expanded contents), + * queueing the ajax call and using a named queue to prevent the call being sent twice + */ queueHdaFetch : function queueHdaFetch( column ){ //this.log( 'queueHdaFetch:', column ); // if the history model says it has hdas but none are present, queue an ajax req for them @@ -822,7 +842,7 @@ } }, - /** */ + /** Get the *detailed* json for *all* of a column's history's contents - req'd for searching */ queueHdaFetchDetails : function( column ){ if( ( column.model.contents.length === 0 && !column.model.get( 'empty' ) ) || ( !column.model.contents.haveDetails() ) ){ @@ -841,11 +861,7 @@ } }, - /** */ - allColumns : function allColumns(){ - return [ this.currentColumn ].concat( this.columns ); - }, - + /** put a text msg in the header */ renderInfo : function( msg ){ this.$( '.header .header-info' ).text( msg ); }, @@ -853,9 +869,13 @@ // ------------------------------------------------------------------------ events/behaviors /** */ events : { + // will move to the server root (gen. Analyze data) 'click .done.btn' : function(){ window.location = '/'; }, + //TODO:?? could just go back - but that's not always correct/desired behav. //'click .done.btn' : function(){ window.history.back(); }, + // creates a new empty history and makes it current 'click .create-new.btn' : 'create', + // these change the collection and column sort order 'click .order .order-update' : function( e ){ this.setOrder( 'update' ); }, 'click .order .order-name' : function( e ){ this.setOrder( 'name' ); }, 'click .order .order-size' : function( e ){ this.setOrder( 'size' ); } @@ -863,22 +883,25 @@ //'dragstart .list-item .title-bar' : function( e ){ console.debug( 'ok' ); } }, + /** Include deleted histories in the collection */ includeDeletedHistories : function(){ //TODO: better through API/limit+offset window.location += ( /\?/.test( window.location.toString() ) )?( '&' ):( '?' ) + 'include_deleted_histories=True'; }, + /** Show only non-deleted histories */ excludeDeletedHistories : function(){ //TODO: better through API/limit+offset window.location = window.location.toString().replace( /[&\?]include_deleted_histories=True/g, '' ); }, - /** */ + /** Set up any view plugins */ setUpBehaviors : function(){ var multipanel = this; //TODO: currently doesn't need to be a mode button + // toggle button for include deleted multipanel.$( '.include-deleted' ).modeButton({ initialMode : this.collection.includeDeleted? 'exclude' : 'include', switchModesOnClick : false, @@ -949,6 +972,7 @@ }); //TODO: each panel stores the hidden/deleted state - and that isn't reflected in the buttons + // toggle button for showing deleted history contents multipanel.$( '#toggle-deleted' ).modeButton({ initialMode : 'include', modes: [ @@ -964,6 +988,7 @@ }); }); + // toggle button for showing hidden history contents multipanel.$( '#toggle-hidden' ).modeButton({ initialMode : 'include', modes: [ @@ -990,33 +1015,26 @@ this.$( '.middle' ).parent().scroll( debouncedInView ); }, + ///** Put the in-view columns then the other columns in a queue, rendering each one at a time */ //panelRenderQueue : function( columns, fn, args, renderEventName ){ //}, + /** Adjust the height of the first, current column since flex-boxes won't work with fixed postiion elements */ _recalcFirstColumnHeight : function(){ - //var currentColumn = this.columnMap[ this.currentHistoryId ]; - //if( !currentColumn ){ return; } var $firstColumn = this.$( '.history-column' ).first(), middleHeight = this.$( '.middle' ).height(), controlHeight = $firstColumn.find( '.panel-controls' ).height(); $firstColumn.height( middleHeight ) .find( '.inner' ).height( middleHeight - controlHeight ); - -//console.debug( '$firstColumn:', $firstColumn ); - //var currentColumn = this.columnMap[ this.currentHistoryId ]; - //if( !currentColumn ){ return; } - //var middleHeight = this.$( '.middle' ).height(), - // controlHeight = currentColumn.$( '.panel-controls' ).height(); - //currentColumn.$el.height( middleHeight ) - // .find( '.inner' ).height( middleHeight - controlHeight ); }, + /** Get the left and right pixel coords of the middle element */ _viewport : function(){ var viewLeft = this.$( '.middle' ).parent().offset().left; return { left: viewLeft, right: viewLeft + this.$( '.middle' ).parent().width() }; }, - /** */ + /** returns the columns currently in the viewport */ columnsInView : function(){ //TODO: uses offset which is render intensive //TODO: 2N - could use arg filter (sortedFilteredColumns( filter )) instead @@ -1027,15 +1045,16 @@ }, //TODO: sortByInView - return cols in view, then others - /** */ + /** trigger in-view from columns in-view */ checkColumnsInView : function(){ +//TODO: assbackward //console.debug( 'checking columns in view', this.columnsInView() ); this.columnsInView().forEach( function( column ){ column.trigger( 'in-view', column ); }); }, - /** */ + /** Show and enable the current columns drop target */ currentColumnDropTargetOn : function(){ var currentColumn = this.columnMap[ this.currentHistoryId ]; if( !currentColumn ){ return; } @@ -1044,7 +1063,7 @@ currentColumn.panel.dropTargetOn(); }, - /** */ + /** Hide and disable the current columns drop target */ currentColumnDropTargetOff : function(){ var currentColumn = this.columnMap[ this.currentHistoryId ]; if( !currentColumn ){ return; } @@ -1053,7 +1072,7 @@ }, // ------------------------------------------------------------------------ misc - /** */ + /** String rep */ toString : function(){ return 'MultiPanelColumns(' + ( this.columns? this.columns.length : 0 ) + ')'; } diff -r 633b4212806d850135a28966d9759da3157b22e8 -r ff5d57faf17d38d345105300097fc7d196f3527e static/scripts/mvc/history/multi-panel.js --- a/static/scripts/mvc/history/multi-panel.js +++ b/static/scripts/mvc/history/multi-panel.js @@ -30,6 +30,7 @@ } return name; } + function copyHistory( name ){ var $copyIndicator = $( '<p><span class="fa fa-spinner fa-spin"></span> Copying history...</p>' ) .css( 'margin-top', '8px' ); @@ -69,21 +70,16 @@ /* ============================================================================== TODO: copy places old current in wrong location - size not updating on dnd - sort by size is broken + sort by size is confusing (but correct) - nice_size shows actual disk usage, !== size handle delete current currently manual by user - delete then create new - make one step render move all columns over as one (agg. then html()) loading indicator on startup - loading indicators on histories (while queueHdaLoading) - __panelPatchRenderEmptyMsg performance - page load takes a while - renders where blocking each other - used _.delay( 0 ) - sort takes a while rendering columns could be better - lazy load history list + lazy load history list (for large numbers) + req. API limit/offset move in-view from pubsub handle errors @@ -103,10 +99,11 @@ privatize non-interface fns search for and drag and drop - should reset after dataset is loaded (or alt. clear search) - better ajax error handling ?columns should be a panel, not have a panel ============================================================================== */ +/** @class A container for a history panel that renders controls for that history (delete, copy, etc.) + */ var HistoryPanelColumn = Backbone.View.extend( baseMVC.LoggableMixin ).extend({ //TODO: extend from panel? (instead of aggregating) @@ -120,7 +117,7 @@ }, // ------------------------------------------------------------------------ set up - /** */ + /** set up passed-in panel (if any) and listeners */ initialize : function initialize( options ){ options = options || {}; @@ -132,7 +129,7 @@ this.setUpListeners(); }, - /** */ + /** create a history panel for this column */ createPanel : function createPanel( panelOptions ){ panelOptions = _.extend({ model : this.model, @@ -148,7 +145,8 @@ }, //TODO: needs work - /** */ +//TODO: move to stub-class to avoid monkey-patching + /** the function monkey-patched into panels to show contents loading state */ __patch_renderEmptyMessage : function( $whereTo ){ var panel = this, hdaCount = _.chain( this.model.get( 'state_ids' ) ).values().flatten().value().length, @@ -170,7 +168,7 @@ return $emptyMsg; }, - /** */ + /** set up reflexive listeners */ setUpListeners : function setUpListeners(){ var column = this; //this.log( 'setUpListeners', this ); @@ -180,7 +178,7 @@ this.setUpPanelListeners(); }, - /** */ + /** set listeners needed for panel */ setUpPanelListeners : function setUpPanelListeners(){ var column = this; this.listenTo( this.panel, { @@ -193,7 +191,7 @@ }, this ); }, - /** */ + /** do the dimensions of this column overlap the given (horizontal) browser coords? */ inView : function( viewLeft, viewRight ){ //TODO: offset is expensive var columnLeft = this.$el.offset().left, @@ -203,13 +201,13 @@ return true; }, - /** */ + /** shortcut to the panel */ $panel : function $panel(){ return this.$( '.history-panel' ); }, // ------------------------------------------------------------------------ render - /** */ + /** render ths column, its panel, and set up plugins */ render : function render( speed ){ speed = ( speed !== undefined )?( speed ):( 'fast' ); //this.log( this + '.render', this.$el, this.el ); @@ -225,14 +223,14 @@ return this; }, - /** */ + /** set up plugins */ setUpBehaviors : function setUpBehaviors(){ //this.log( 'setUpBehaviors:', this ); //var column = this; // on panel size change, ... }, - /** */ + /** column body template with inner div for panel based on data (model json) */ template : function template( data ){ data = data || {}; var html = [ @@ -253,6 +251,7 @@ return $( html ); }, + /** controls template displaying controls above the panel based on this.currentHistory */ controlsLeftTemplate : function(){ return ( this.currentHistory )? [ @@ -267,7 +266,7 @@ ].join( '' ); }, - /** */ + /** render the panel contained in the column using speed for fx speed */ renderPanel : function renderPanel( speed ){ speed = ( speed !== undefined )?( speed ):( 'fast' ); this.panel.setElement( this.$panel() ).render( speed ); @@ -275,9 +274,11 @@ }, // ------------------------------------------------------------------------ behaviors and events - /** */ + /** event map */ events : { + // will make this the current history 'click .switch-to.btn' : function(){ this.model.setAsCurrent(); }, + // toggles deleted here and on the server and re-renders 'click .delete-history.btn' : function(){ var column = this, xhr; @@ -294,17 +295,18 @@ column.render(); }); }, + // will copy this history and make the copy the current history 'click .copy-history.btn' : 'copy' }, // ------------------------------------------------------------------------ non-current controls - /** */ + /** Open a modal to get a new history name, copy it (if not canceled), and makes the copy current */ copy : function copy(){ historyCopyDialog( this.model ); }, // ------------------------------------------------------------------------ misc - /** */ + /** String rep */ toString : function(){ return 'HistoryPanelColumn(' + ( this.panel? this.panel : '' ) + ')'; } @@ -312,13 +314,14 @@ //============================================================================== -// a multi-history view that displays histories in full +/** @class A view of a HistoryCollection and displays histories similarly to the current history panel. + */ var MultiPanelColumns = Backbone.View.extend( baseMVC.LoggableMixin ).extend({ //logger : console, // ------------------------------------------------------------------------ set up - /** */ + /** Set up internals, history collection, and columns to display the history */ initialize : function initialize( options ){ options = options || {}; this.log( this + '.init', options ); @@ -352,8 +355,6 @@ this.collection = null; this.setCollection( options.histories || [] ); - ///** the column views of histories */ - //this.columns = []; /** model id to column map */ this.columnMap = {}; //TODO: why create here? @@ -362,14 +363,16 @@ this.setUpListeners(); }, - /** */ + /** Set up reflexive listeners */ setUpListeners : function setUpListeners(){ //var multipanel = this; //multipanel.log( 'setUpListeners', multipanel ); }, // ------------------------------------------------------------------------ collection - /** */ + /** Set up a (new) history collection, sorting and adding listeners + * @fires 'new-collection' when set with this view as the arg + */ setCollection : function setCollection( models ){ var multipanel = this; multipanel.stopListening( multipanel.collection ); @@ -383,6 +386,7 @@ return multipanel; }, + /** Set up listeners for the collection - handling: added histories, change of current, deletion, and sorting */ setUpCollectionListeners : function(){ var multipanel = this, collection = multipanel.collection; @@ -394,15 +398,16 @@ // handle deleting a history (depends on whether panels is including deleted or not) 'change:deleted': multipanel.handleDeletedHistory, - 'sort' : function(){ multipanel.renderColumns( 0 ); }, + 'sort' : function(){ multipanel.renderColumns( 0 ); } + // debugging //'all' : function(){ // console.info( 'collection:', arguments ); //} }); }, - /** */ + /** Re-render and set currentHistoryId to reflect a new current history */ setCurrentHistory : function setCurrentHistory( history ){ var oldCurrentColumn = this.columnMap[ this.currentHistoryId ]; if( oldCurrentColumn ){ @@ -425,7 +430,9 @@ return newCurrentColumn; }, - /** */ + /** Either remove a deleted history or re-render it to show the deleted message + * based on collection.includeDeleted + */ handleDeletedHistory : function handleDeletedHistory( history ){ if( history.get( 'deleted' ) ){ this.log( 'handleDeletedHistory', this.collection.includeDeleted, history ); @@ -446,7 +453,9 @@ } }, - /** */ + /** Sort this collection based on order (defaulting to this.order) a string key + * (sorting the collection will re-render the panel) + */ sortCollection : function( order, options ){ order = order || this.order; var currentHistoryId = this.currentHistoryId; @@ -477,7 +486,7 @@ return this.collection; }, - /** */ + /** Set the sort order and re-sort */ setOrder : function( order ){ if( [ 'update', 'name', 'size' ].indexOf( order ) === -1 ){ order = 'update'; @@ -487,12 +496,12 @@ return this; }, - /** */ + /** create a new history and set it to current */ create : function( ev ){ return this.collection.create({ current: true }); }, - ///** */ + ///** delete the current history */ //deleteCurrent : function deleteCurrent(){ // var multipanel = this, // currentColumn = multipanel.columnMap[ multipanel.currentHistoryId ]; @@ -515,7 +524,7 @@ }); }, - /** */ + /** create a column and its panel and set up any listeners to them */ createColumn : function createColumn( history, options ){ // options passed can be re-used, so extend them before adding the model to prevent pollution for the next options = _.extend( {}, options, { model: history }); @@ -525,10 +534,6 @@ return column; }, - columnMapLength : function(){ - return Object.keys( this.columnMap ).length; - }, - sortedFilteredColumns : function( filters ){ filters = filters || this.filters; if( !filters || !filters.length ){ @@ -598,7 +603,7 @@ }); }, - /** */ + /** set up listeners for a column and it's panel - handling: hda lazy-loading, drag and drop */ setUpColumnListeners : function setUpColumnListeners( column ){ var multipanel = this; multipanel.listenTo( column, { @@ -641,8 +646,13 @@ }, + /** conv. fn to count the columns in columnMap */ + columnMapLength : function(){ + return Object.keys( this.columnMap ).length; + }, + // ------------------------------------------------------------------------ render - /** */ + /** Render this view, columns, and set up view plugins */ render : function render( speed ){ speed = speed !== undefined? speed: this.fxSpeed; var multipanel = this; @@ -660,14 +670,16 @@ return multipanel; }, - /** */ + /** Template - overall structure relies on flex-boxes and is 3 components: header, middle, footer */ template : function template( options ){ options = options || {}; var html = []; if( this.options.headerHeight ){ html = html.concat([ + // a loading overlay '<div class="loading-overlay flex-row"><div class="loading-overlay-message">loading...</div></div>', '<div class="header flex-column-container">', + // page & history controls '<div class="header-control header-control-left flex-column">', '<button class="done btn btn-default">', _l( 'Done' ), '</button>', '<button class="include-deleted btn btn-default"></button>', @@ -689,10 +701,12 @@ '</div>', '<div id="search-histories" class="header-search"></div>', '</div>', + // feedback '<div class="header-control header-control-center flex-column">', '<div class="header-info">', '</div>', '</div>', + // dataset controls '<div class="header-control header-control-right flex-column">', '<div id="search-datasets" class="header-search"></div>', '<button id="toggle-deleted" class="btn btn-default">', @@ -707,15 +721,17 @@ } html = html.concat([ + // middle - where the columns go '<div class="outer-middle flex-row flex-row-container">', '<div class="middle flex-column-container flex-row"></div>', '</div>', + // footer '<div class="footer flex-column-container">','</div>' ]); return $( html.join( '' ) ); }, - /** */ + /** Render the columns and panels */ renderColumns : function renderColumns( speed ){ speed = speed !== undefined? speed: this.fxSpeed; //this.log( 'renderColumns:', speed ); @@ -780,14 +796,16 @@ if( this.searchFor && sortedAndFiltered.length <= 1 ){ } else { + // check for in-view, hda lazy-loading if so multipanel.checkColumnsInView(); + // the first, current column has position: fixed and flex css will not apply - adjust height manually this._recalcFirstColumnHeight(); } return sortedAndFiltered; }, - /** */ + /** Render a single column using the non-blocking setTimeout( 0 ) pattern */ renderColumn : function( column, speed ){ speed = speed !== undefined? speed: this.fxSpeed; return _.delay( function(){ return column.render( speed ); }, 0 ); @@ -796,7 +814,9 @@ //TODO: combine the following two more sensibly //TODO: could have HistoryContents.haveDetails return false // if column.model.contents.length === 0 && !column.model.get( 'empty' ) then just check that - /** */ + /** Get the *summary* contents of a column's history (and details on any expanded contents), + * queueing the ajax call and using a named queue to prevent the call being sent twice + */ queueHdaFetch : function queueHdaFetch( column ){ //this.log( 'queueHdaFetch:', column ); // if the history model says it has hdas but none are present, queue an ajax req for them @@ -822,7 +842,7 @@ } }, - /** */ + /** Get the *detailed* json for *all* of a column's history's contents - req'd for searching */ queueHdaFetchDetails : function( column ){ if( ( column.model.contents.length === 0 && !column.model.get( 'empty' ) ) || ( !column.model.contents.haveDetails() ) ){ @@ -841,11 +861,7 @@ } }, - /** */ - allColumns : function allColumns(){ - return [ this.currentColumn ].concat( this.columns ); - }, - + /** put a text msg in the header */ renderInfo : function( msg ){ this.$( '.header .header-info' ).text( msg ); }, @@ -853,9 +869,13 @@ // ------------------------------------------------------------------------ events/behaviors /** */ events : { + // will move to the server root (gen. Analyze data) 'click .done.btn' : function(){ window.location = '/'; }, + //TODO:?? could just go back - but that's not always correct/desired behav. //'click .done.btn' : function(){ window.history.back(); }, + // creates a new empty history and makes it current 'click .create-new.btn' : 'create', + // these change the collection and column sort order 'click .order .order-update' : function( e ){ this.setOrder( 'update' ); }, 'click .order .order-name' : function( e ){ this.setOrder( 'name' ); }, 'click .order .order-size' : function( e ){ this.setOrder( 'size' ); } @@ -863,22 +883,25 @@ //'dragstart .list-item .title-bar' : function( e ){ console.debug( 'ok' ); } }, + /** Include deleted histories in the collection */ includeDeletedHistories : function(){ //TODO: better through API/limit+offset window.location += ( /\?/.test( window.location.toString() ) )?( '&' ):( '?' ) + 'include_deleted_histories=True'; }, + /** Show only non-deleted histories */ excludeDeletedHistories : function(){ //TODO: better through API/limit+offset window.location = window.location.toString().replace( /[&\?]include_deleted_histories=True/g, '' ); }, - /** */ + /** Set up any view plugins */ setUpBehaviors : function(){ var multipanel = this; //TODO: currently doesn't need to be a mode button + // toggle button for include deleted multipanel.$( '.include-deleted' ).modeButton({ initialMode : this.collection.includeDeleted? 'exclude' : 'include', switchModesOnClick : false, @@ -949,6 +972,7 @@ }); //TODO: each panel stores the hidden/deleted state - and that isn't reflected in the buttons + // toggle button for showing deleted history contents multipanel.$( '#toggle-deleted' ).modeButton({ initialMode : 'include', modes: [ @@ -964,6 +988,7 @@ }); }); + // toggle button for showing hidden history contents multipanel.$( '#toggle-hidden' ).modeButton({ initialMode : 'include', modes: [ @@ -990,33 +1015,26 @@ this.$( '.middle' ).parent().scroll( debouncedInView ); }, + ///** Put the in-view columns then the other columns in a queue, rendering each one at a time */ //panelRenderQueue : function( columns, fn, args, renderEventName ){ //}, + /** Adjust the height of the first, current column since flex-boxes won't work with fixed postiion elements */ _recalcFirstColumnHeight : function(){ - //var currentColumn = this.columnMap[ this.currentHistoryId ]; - //if( !currentColumn ){ return; } var $firstColumn = this.$( '.history-column' ).first(), middleHeight = this.$( '.middle' ).height(), controlHeight = $firstColumn.find( '.panel-controls' ).height(); $firstColumn.height( middleHeight ) .find( '.inner' ).height( middleHeight - controlHeight ); - -//console.debug( '$firstColumn:', $firstColumn ); - //var currentColumn = this.columnMap[ this.currentHistoryId ]; - //if( !currentColumn ){ return; } - //var middleHeight = this.$( '.middle' ).height(), - // controlHeight = currentColumn.$( '.panel-controls' ).height(); - //currentColumn.$el.height( middleHeight ) - // .find( '.inner' ).height( middleHeight - controlHeight ); }, + /** Get the left and right pixel coords of the middle element */ _viewport : function(){ var viewLeft = this.$( '.middle' ).parent().offset().left; return { left: viewLeft, right: viewLeft + this.$( '.middle' ).parent().width() }; }, - /** */ + /** returns the columns currently in the viewport */ columnsInView : function(){ //TODO: uses offset which is render intensive //TODO: 2N - could use arg filter (sortedFilteredColumns( filter )) instead @@ -1027,15 +1045,16 @@ }, //TODO: sortByInView - return cols in view, then others - /** */ + /** trigger in-view from columns in-view */ checkColumnsInView : function(){ +//TODO: assbackward //console.debug( 'checking columns in view', this.columnsInView() ); this.columnsInView().forEach( function( column ){ column.trigger( 'in-view', column ); }); }, - /** */ + /** Show and enable the current columns drop target */ currentColumnDropTargetOn : function(){ var currentColumn = this.columnMap[ this.currentHistoryId ]; if( !currentColumn ){ return; } @@ -1044,7 +1063,7 @@ currentColumn.panel.dropTargetOn(); }, - /** */ + /** Hide and disable the current columns drop target */ currentColumnDropTargetOff : function(){ var currentColumn = this.columnMap[ this.currentHistoryId ]; if( !currentColumn ){ return; } @@ -1053,7 +1072,7 @@ }, // ------------------------------------------------------------------------ misc - /** */ + /** String rep */ toString : function(){ return 'MultiPanelColumns(' + ( this.columns? this.columns.length : 0 ) + ')'; } diff -r 633b4212806d850135a28966d9759da3157b22e8 -r ff5d57faf17d38d345105300097fc7d196f3527e static/scripts/packed/mvc/history/multi-panel.js --- a/static/scripts/packed/mvc/history/multi-panel.js +++ b/static/scripts/packed/mvc/history/multi-panel.js @@ -1,1 +1,1 @@ -define(["mvc/history/history-model","mvc/history/history-panel-edit","mvc/base-mvc","utils/ajax-queue"],function(d,m,A,a){window.HISTORY_MODEL=d;function g(I,F){F=F||{};if(!(Galaxy&&Galaxy.modal)){return I.copy()}var G=I.get("name"),D="Copy of '"+G+"'";function E(K){if(!K){if(!Galaxy.modal.$("#invalid-title").size()){var J=$("<p/>").attr("id","invalid-title").css({color:"red","margin-top":"8px"}).addClass("bg-danger").text(_l("Please enter a valid history title"));Galaxy.modal.$(".modal-body").append(J)}return false}return K}function H(J){var K=$('<p><span class="fa fa-spinner fa-spin"></span> Copying history...</p>').css("margin-top","8px");Galaxy.modal.$(".modal-body").append(K);I.copy(true,J).fail(function(){alert(_l("History could not be copied. Please contact a Galaxy administrator"))}).always(function(){Galaxy.modal.hide()})}Galaxy.modal.show(_.extend({title:_l("Copying history")+' "'+G+'"',body:$(['<label for="copy-modal-title">',_l("Enter a title for the copied history"),":","</label><br />",'<input id="copy-modal-title" class="form-control" style="width: 100%" value="',D,'" />'].join("")),buttons:{Cancel:function(){Galaxy.modal.hide()},Copy:function(){var J=Galaxy.modal.$("#copy-modal-title").val();if(!E(J)){return}H(J)}}},F));$("#copy-modal-title").focus().select()}var C=Backbone.View.extend(A.LoggableMixin).extend({tagName:"div",className:"history-column flex-column flex-row-container",id:function r(){if(!this.model){return""}return"history-column-"+this.model.get("id")},initialize:function c(D){D=D||{};this.panel=D.panel||this.createPanel(D);this.setUpListeners()},createPanel:function v(E){E=_.extend({model:this.model,dragItems:true},E);var D=new m.HistoryPanelEdit(E);D._renderEmptyMessage=this.__patch_renderEmptyMessage;return D},__patch_renderEmptyMessage:function(F){var E=this,G=_.chain(this.model.get("state_ids")).values().flatten().value().length,D=E.$emptyMessage(F);if(!_.isEmpty(E.hdaViews)){D.hide()}else{if(G&&!this.model.contents.length){D.empty().append($('<span class="fa fa-spinner fa-spin"></span><i>loading datasets...</i>')).show()}else{if(E.searchFor){D.text(E.noneFoundMsg).show()}else{D.text(E.emptyMsg).show()}}}return D},setUpListeners:function f(){var D=this;this.once("rendered",function(){D.trigger("rendered:initial",D)});this.setUpPanelListeners()},setUpPanelListeners:function l(){var D=this;this.listenTo(this.panel,{rendered:function(){D.trigger("rendered",D)}},this)},inView:function(D,E){var G=this.$el.offset().left,F=G+this.$el.width();if(F<D){return false}if(G>E){return false}return true},$panel:function e(){return this.$(".history-panel")},render:function B(E){E=(E!==undefined)?(E):("fast");var D=this.model?this.model.toJSON():{};this.$el.html(this.template(D));this.renderPanel(E);this.setUpBehaviors();return this},setUpBehaviors:function w(){},template:function x(E){E=E||{};var D=['<div class="panel-controls clear flex-row">',this.controlsLeftTemplate(),'<div class="pull-right">','<button class="delete-history btn btn-default">',E.deleted?_l("Undelete"):_l("Delete"),"</button>",'<button class="copy-history btn btn-default">',_l("Copy"),"</button>","</div>","</div>",'<div class="inner flex-row flex-column-container">','<div id="history-',E.id,'" class="history-column history-panel flex-column"></div>',"</div>"].join("");return $(D)},controlsLeftTemplate:function(){return(this.currentHistory)?['<div class="pull-left">','<button class="create-new btn btn-default">',_l("Create new"),"</button> ","</div>"].join(""):['<div class="pull-left">','<button class="switch-to btn btn-default">',_l("Switch to"),"</button>","</div>"].join("")},renderPanel:function i(D){D=(D!==undefined)?(D):("fast");this.panel.setElement(this.$panel()).render(D);return this},events:{"click .switch-to.btn":function(){this.model.setAsCurrent()},"click .delete-history.btn":function(){var D=this,E;if(this.model.get("deleted")){E=this.model.undelete()}else{E=this.model._delete()}E.fail(function(H,F,G){alert(_l("Could not delete the history")+":\n"+G)}).done(function(F){D.render()})},"click .copy-history.btn":"copy"},copy:function t(){g(this.model)},toString:function(){return"HistoryPanelColumn("+(this.panel?this.panel:"")+")"}});var n=Backbone.View.extend(A.LoggableMixin).extend({initialize:function c(D){D=D||{};this.log(this+".init",D);if(!D.currentHistoryId){throw new Error(this+" requires a currentHistoryId in the options")}this.currentHistoryId=D.currentHistoryId;this.options={columnWidth:312,borderWidth:1,columnGap:8,headerHeight:29,footerHeight:0,controlsHeight:20};this.order=D.order||"update";this.hdaQueue=new a.NamedAjaxQueue([],false);this.collection=null;this.setCollection(D.histories||[]);this.columnMap={};this.createColumns(D.columnOptions);this.setUpListeners()},setUpListeners:function f(){},setCollection:function z(E){var D=this;D.stopListening(D.collection);D.collection=E;D.sortCollection(D.order,{silent:true});D.setUpCollectionListeners();D.trigger("new-collection",D);return D},setUpCollectionListeners:function(){var D=this,E=D.collection;D.listenTo(E,{add:D.addAsCurrentColumn,"set-as-current":D.setCurrentHistory,"change:deleted":D.handleDeletedHistory,sort:function(){D.renderColumns(0)},})},setCurrentHistory:function q(E){var D=this.columnMap[this.currentHistoryId];if(D){D.currentHistory=false;D.$el.height("")}this.currentHistoryId=E.id;var F=this.columnMap[this.currentHistoryId];F.currentHistory=true;this.sortCollection();multipanel._recalcFirstColumnHeight();return F},handleDeletedHistory:function b(E){if(E.get("deleted")){this.log("handleDeletedHistory",this.collection.includeDeleted,E);var D=this;column=D.columnMap[E.id];if(!column){return}if(column.model.id===this.currentHistoryId){}else{if(!D.collection.includeDeleted){D.removeColumn(column)}}}},sortCollection:function(D,E){D=D||this.order;var F=this.currentHistoryId;switch(D){case"name":this.collection.comparator=function(G){return[G.id!==F,G.get("name").toLowerCase()]};break;case"size":this.collection.comparator=function(G){return[G.id!==F,G.get("size")]};break;default:this.collection.comparator=function(G){return[G.id!==F,Date(G.get("update_time"))]}}this.collection.sort(E);return this.collection},setOrder:function(D){if(["update","name","size"].indexOf(D)===-1){D="update"}this.order=D;this.sortCollection();return this},create:function(D){return this.collection.create({current:true})},createColumns:function s(E){E=E||{};var D=this;this.columnMap={};D.collection.each(function(F,G){var H=D.createColumn(F,E);D.columnMap[F.id]=H})},createColumn:function u(F,D){D=_.extend({},D,{model:F});var E=new C(D);if(F.id===this.currentHistoryId){E.currentHistory=true}this.setUpColumnListeners(E);return E},columnMapLength:function(){return Object.keys(this.columnMap).length},sortedFilteredColumns:function(D){D=D||this.filters;if(!D||!D.length){return this.sortedColumns()}var E=this;return E.sortedColumns().filter(function(H,G){var F=H.currentHistory||_.every(D.map(function(I){return I.call(H)}));return F})},sortedColumns:function(){var E=this;var D=this.collection.map(function(G,F){return E.columnMap[G.id]});return D},addColumn:function p(F,D){D=D!==undefined?D:true;var E=this.createColumn(F);this.columnMap[F.id]=E;if(D){this.renderColumns()}return E},addAsCurrentColumn:function p(F){var E=this,D=this.addColumn(F,false);this.setCurrentHistory(F);D.once("rendered",function(){E.queueHdaFetch(D)});return D},removeColumn:function y(F,E){E=E!==undefined?E:true;this.log("removeColumn",F);if(!F){return}var G=this,D=this.options.columnWidth+this.options.columnGap;F.$el.fadeOut("fast",function(){if(E){$(this).remove();G.$(".middle").width(G.$(".middle").width()-D);G.checkColumnsInView();G._recalcFirstColumnHeight()}G.stopListening(F.panel);G.stopListening(F);delete G.columnMap[F.model.id];F.remove()})},setUpColumnListeners:function o(D){var E=this;E.listenTo(D,{"in-view":E.queueHdaFetch});E.listenTo(D.panel,{"view:draggable:dragstart":function(I,G,F,H){E._dropData=JSON.parse(I.dataTransfer.getData("text"));E.currentColumnDropTargetOn()},"view:draggable:dragend":function(I,G,F,H){E._dropData=null;E.currentColumnDropTargetOff()},"droptarget:drop":function(H,I,G){var J=E._dropData.filter(function(K){return(_.isObject(K)&&K.id&&K.model_class==="HistoryDatasetAssociation")});E._dropData=null;var F=new a.NamedAjaxQueue();J.forEach(function(K){F.add({name:"copy-"+K.id,fn:function(){return G.model.contents.copy(K.id)}})});F.start();F.done(function(K){G.model.fetch()})}})},render:function B(E){E=E!==undefined?E:this.fxSpeed;var D=this;D.log(D+".render");D.$el.html(D.template(D.options));D.renderColumns(E);D.setUpBehaviors();D.trigger("rendered",D);return D},template:function x(D){D=D||{};var E=[];if(this.options.headerHeight){E=E.concat(['<div class="loading-overlay flex-row"><div class="loading-overlay-message">loading...</div></div>','<div class="header flex-column-container">','<div class="header-control header-control-left flex-column">','<button class="done btn btn-default">',_l("Done"),"</button>",'<button class="include-deleted btn btn-default"></button>','<div class="order btn-group">','<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown">',_l("Order histories by")+'... <span class="caret"></span>',"</button>",'<ul class="dropdown-menu" role="menu">','<li><a href="javascript:void(0);" class="order-update">',_l("Time of last update"),"</a></li>",'<li><a href="javascript:void(0);" class="order-name">',_l("Name"),"</a></li>",'<li><a href="javascript:void(0);" class="order-size">',_l("Size"),"</a></li>","</ul>","</div>",'<div id="search-histories" class="header-search"></div>',"</div>",'<div class="header-control header-control-center flex-column">','<div class="header-info">',"</div>","</div>",'<div class="header-control header-control-right flex-column">','<div id="search-datasets" class="header-search"></div>','<button id="toggle-deleted" class="btn btn-default">',_l("Include deleted datasets"),"</button>",'<button id="toggle-hidden" class="btn btn-default">',_l("Include hidden datasets"),"</button>","</div>","</div>"])}E=E.concat(['<div class="outer-middle flex-row flex-row-container">','<div class="middle flex-column-container flex-row"></div>',"</div>",'<div class="footer flex-column-container">',"</div>"]);return $(E.join(""))},renderColumns:function k(G){G=G!==undefined?G:this.fxSpeed;var F=this,D=F.sortedFilteredColumns();F.$(".middle").width(D.length*(this.options.columnWidth+this.options.columnGap)+this.options.columnGap+16);var E=F.$(".middle");E.empty();D.forEach(function(I,H){I.$el.appendTo(E);I.delegateEvents();F.renderColumn(I,G)});if(this.searchFor&&D.length<=1){}else{F.checkColumnsInView();this._recalcFirstColumnHeight()}return D},renderColumn:function(D,E){E=E!==undefined?E:this.fxSpeed;return _.delay(function(){return D.render(E)},0)},queueHdaFetch:function j(F){if(F.model.contents.length===0&&!F.model.get("empty")){var D={},E=_.values(F.panel.storage.get("expandedIds")).join();if(E){D.dataset_details=E}this.hdaQueue.add({name:F.model.id,fn:function(){var G=F.model.contents.fetch({data:D,silent:true});return G.done(function(H){F.panel.renderItems()})}});if(!this.hdaQueue.running){this.hdaQueue.start()}}},queueHdaFetchDetails:function(D){if((D.model.contents.length===0&&!D.model.get("empty"))||(!D.model.contents.haveDetails())){this.hdaQueue.add({name:D.model.id,fn:function(){var E=D.model.contents.fetch({data:{details:"all"},silent:true});return E.done(function(F){D.panel.renderItems()})}});if(!this.hdaQueue.running){this.hdaQueue.start()}}},allColumns:function h(){return[this.currentColumn].concat(this.columns)},renderInfo:function(D){this.$(".header .header-info").text(D)},events:{"click .done.btn":function(){window.location="/"},"click .create-new.btn":"create","click .order .order-update":function(D){this.setOrder("update")},"click .order .order-name":function(D){this.setOrder("name")},"click .order .order-size":function(D){this.setOrder("size")}},includeDeletedHistories:function(){window.location+=(/\?/.test(window.location.toString()))?("&"):("?")+"include_deleted_histories=True"},excludeDeletedHistories:function(){window.location=window.location.toString().replace(/[&\?]include_deleted_histories=True/g,"")},setUpBehaviors:function(){var E=this;E.$(".include-deleted").modeButton({initialMode:this.collection.includeDeleted?"exclude":"include",switchModesOnClick:false,modes:[{mode:"include",html:_l("Include deleted histories"),onclick:_.bind(E.includeDeletedHistories,E)},{mode:"exclude",html:_l("Exclude deleted histories"),onclick:_.bind(E.excludeDeletedHistories,E)}]});E.$("#search-histories").searchInput({name:"search-histories",placeholder:_l("search histories"),onsearch:function(F){E.searchFor=F;E.filters=[function(){return this.model.get("name").indexOf(E.searchFor)!==-1}];E.renderColumns(0)},onclear:function(F){E.searchFor=null;E.filters=[];E.renderColumns(0)}});E.$("#search-datasets").searchInput({name:"search-datasets",placeholder:_l("search all datasets"),onfirstsearch:function(F){E.hdaQueue.clear();E.$("#search-datasets").searchInput("toggle-loading");E.searchFor=F;E.sortedFilteredColumns().forEach(function(G){G.panel.searchItems(F);E.queueHdaFetchDetails(G)});E.hdaQueue.progress(function(G){E.renderInfo([_l("loading"),(G.curr+1),_l("of"),G.total].join(" "))});E.hdaQueue.deferred.done(function(){E.renderInfo("");E.$("#search-datasets").searchInput("toggle-loading")})},onsearch:function(F){E.searchFor=F;E.sortedFilteredColumns().forEach(function(G){G.panel.searchItems(F)})},onclear:function(F){E.searchFor=null;E.sortedFilteredColumns().forEach(function(G){G.panel.clearSearch()})}});E.$("#toggle-deleted").modeButton({initialMode:"include",modes:[{mode:"exclude",html:_l("Exclude deleted datasets")},{mode:"include",html:_l("Include deleted datasets")}]}).click(function(){var F=$(this).modeButton("getMode").mode==="exclude";E.sortedFilteredColumns().forEach(function(H,G){_.delay(function(){H.panel.toggleShowDeleted(F,false)},G*200)})});E.$("#toggle-hidden").modeButton({initialMode:"include",modes:[{mode:"exclude",html:_l("Exclude hidden datasets")},{mode:"include",html:_l("Include hidden datasets")}]}).click(function(){var F=$(this).modeButton("getMode").mode==="exclude";E.sortedFilteredColumns().forEach(function(H,G){_.delay(function(){H.panel.toggleShowHidden(F,false)},G*200)})});$(window).resize(function(){E._recalcFirstColumnHeight()});var D=_.debounce(_.bind(this.checkColumnsInView,this),100);this.$(".middle").parent().scroll(D)},_recalcFirstColumnHeight:function(){var D=this.$(".history-column").first(),F=this.$(".middle").height(),E=D.find(".panel-controls").height();D.height(F).find(".inner").height(F-E)},_viewport:function(){var D=this.$(".middle").parent().offset().left;return{left:D,right:D+this.$(".middle").parent().width()}},columnsInView:function(){var D=this._viewport();return this.sortedFilteredColumns().filter(function(E){return E.currentHistory||E.inView(D.left,D.right)})},checkColumnsInView:function(){this.columnsInView().forEach(function(D){D.trigger("in-view",D)})},currentColumnDropTargetOn:function(){var D=this.columnMap[this.currentHistoryId];if(!D){return}D.panel.dataDropped=function(E){};D.panel.dropTargetOn()},currentColumnDropTargetOff:function(){var D=this.columnMap[this.currentHistoryId];if(!D){return}D.panel.dataDropped=m.HistoryPanelEdit.prototype.dataDrop;D.panel.dropTargetOff()},toString:function(){return"MultiPanelColumns("+(this.columns?this.columns.length:0)+")"}});return{MultiPanelColumns:n}}); \ No newline at end of file +define(["mvc/history/history-model","mvc/history/history-panel-edit","mvc/base-mvc","utils/ajax-queue"],function(d,l,z,a){window.HISTORY_MODEL=d;function g(H,E){E=E||{};if(!(Galaxy&&Galaxy.modal)){return H.copy()}var F=H.get("name"),C="Copy of '"+F+"'";function D(J){if(!J){if(!Galaxy.modal.$("#invalid-title").size()){var I=$("<p/>").attr("id","invalid-title").css({color:"red","margin-top":"8px"}).addClass("bg-danger").text(_l("Please enter a valid history title"));Galaxy.modal.$(".modal-body").append(I)}return false}return J}function G(I){var J=$('<p><span class="fa fa-spinner fa-spin"></span> Copying history...</p>').css("margin-top","8px");Galaxy.modal.$(".modal-body").append(J);H.copy(true,I).fail(function(){alert(_l("History could not be copied. Please contact a Galaxy administrator"))}).always(function(){Galaxy.modal.hide()})}Galaxy.modal.show(_.extend({title:_l("Copying history")+' "'+F+'"',body:$(['<label for="copy-modal-title">',_l("Enter a title for the copied history"),":","</label><br />",'<input id="copy-modal-title" class="form-control" style="width: 100%" value="',C,'" />'].join("")),buttons:{Cancel:function(){Galaxy.modal.hide()},Copy:function(){var I=Galaxy.modal.$("#copy-modal-title").val();if(!D(I)){return}G(I)}}},E));$("#copy-modal-title").focus().select()}var B=Backbone.View.extend(z.LoggableMixin).extend({tagName:"div",className:"history-column flex-column flex-row-container",id:function q(){if(!this.model){return""}return"history-column-"+this.model.get("id")},initialize:function c(C){C=C||{};this.panel=C.panel||this.createPanel(C);this.setUpListeners()},createPanel:function u(D){D=_.extend({model:this.model,dragItems:true},D);var C=new l.HistoryPanelEdit(D);C._renderEmptyMessage=this.__patch_renderEmptyMessage;return C},__patch_renderEmptyMessage:function(E){var D=this,F=_.chain(this.model.get("state_ids")).values().flatten().value().length,C=D.$emptyMessage(E);if(!_.isEmpty(D.hdaViews)){C.hide()}else{if(F&&!this.model.contents.length){C.empty().append($('<span class="fa fa-spinner fa-spin"></span><i>loading datasets...</i>')).show()}else{if(D.searchFor){C.text(D.noneFoundMsg).show()}else{C.text(D.emptyMsg).show()}}}return C},setUpListeners:function f(){var C=this;this.once("rendered",function(){C.trigger("rendered:initial",C)});this.setUpPanelListeners()},setUpPanelListeners:function k(){var C=this;this.listenTo(this.panel,{rendered:function(){C.trigger("rendered",C)}},this)},inView:function(C,D){var F=this.$el.offset().left,E=F+this.$el.width();if(E<C){return false}if(F>D){return false}return true},$panel:function e(){return this.$(".history-panel")},render:function A(D){D=(D!==undefined)?(D):("fast");var C=this.model?this.model.toJSON():{};this.$el.html(this.template(C));this.renderPanel(D);this.setUpBehaviors();return this},setUpBehaviors:function v(){},template:function w(D){D=D||{};var C=['<div class="panel-controls clear flex-row">',this.controlsLeftTemplate(),'<div class="pull-right">','<button class="delete-history btn btn-default">',D.deleted?_l("Undelete"):_l("Delete"),"</button>",'<button class="copy-history btn btn-default">',_l("Copy"),"</button>","</div>","</div>",'<div class="inner flex-row flex-column-container">','<div id="history-',D.id,'" class="history-column history-panel flex-column"></div>',"</div>"].join("");return $(C)},controlsLeftTemplate:function(){return(this.currentHistory)?['<div class="pull-left">','<button class="create-new btn btn-default">',_l("Create new"),"</button> ","</div>"].join(""):['<div class="pull-left">','<button class="switch-to btn btn-default">',_l("Switch to"),"</button>","</div>"].join("")},renderPanel:function h(C){C=(C!==undefined)?(C):("fast");this.panel.setElement(this.$panel()).render(C);return this},events:{"click .switch-to.btn":function(){this.model.setAsCurrent()},"click .delete-history.btn":function(){var C=this,D;if(this.model.get("deleted")){D=this.model.undelete()}else{D=this.model._delete()}D.fail(function(G,E,F){alert(_l("Could not delete the history")+":\n"+F)}).done(function(E){C.render()})},"click .copy-history.btn":"copy"},copy:function s(){g(this.model)},toString:function(){return"HistoryPanelColumn("+(this.panel?this.panel:"")+")"}});var m=Backbone.View.extend(z.LoggableMixin).extend({initialize:function c(C){C=C||{};this.log(this+".init",C);if(!C.currentHistoryId){throw new Error(this+" requires a currentHistoryId in the options")}this.currentHistoryId=C.currentHistoryId;this.options={columnWidth:312,borderWidth:1,columnGap:8,headerHeight:29,footerHeight:0,controlsHeight:20};this.order=C.order||"update";this.hdaQueue=new a.NamedAjaxQueue([],false);this.collection=null;this.setCollection(C.histories||[]);this.columnMap={};this.createColumns(C.columnOptions);this.setUpListeners()},setUpListeners:function f(){},setCollection:function y(D){var C=this;C.stopListening(C.collection);C.collection=D;C.sortCollection(C.order,{silent:true});C.setUpCollectionListeners();C.trigger("new-collection",C);return C},setUpCollectionListeners:function(){var C=this,D=C.collection;C.listenTo(D,{add:C.addAsCurrentColumn,"set-as-current":C.setCurrentHistory,"change:deleted":C.handleDeletedHistory,sort:function(){C.renderColumns(0)}})},setCurrentHistory:function p(D){var C=this.columnMap[this.currentHistoryId];if(C){C.currentHistory=false;C.$el.height("")}this.currentHistoryId=D.id;var E=this.columnMap[this.currentHistoryId];E.currentHistory=true;this.sortCollection();multipanel._recalcFirstColumnHeight();return E},handleDeletedHistory:function b(D){if(D.get("deleted")){this.log("handleDeletedHistory",this.collection.includeDeleted,D);var C=this;column=C.columnMap[D.id];if(!column){return}if(column.model.id===this.currentHistoryId){}else{if(!C.collection.includeDeleted){C.removeColumn(column)}}}},sortCollection:function(C,D){C=C||this.order;var E=this.currentHistoryId;switch(C){case"name":this.collection.comparator=function(F){return[F.id!==E,F.get("name").toLowerCase()]};break;case"size":this.collection.comparator=function(F){return[F.id!==E,F.get("size")]};break;default:this.collection.comparator=function(F){return[F.id!==E,Date(F.get("update_time"))]}}this.collection.sort(D);return this.collection},setOrder:function(C){if(["update","name","size"].indexOf(C)===-1){C="update"}this.order=C;this.sortCollection();return this},create:function(C){return this.collection.create({current:true})},createColumns:function r(D){D=D||{};var C=this;this.columnMap={};C.collection.each(function(E,F){var G=C.createColumn(E,D);C.columnMap[E.id]=G})},createColumn:function t(E,C){C=_.extend({},C,{model:E});var D=new B(C);if(E.id===this.currentHistoryId){D.currentHistory=true}this.setUpColumnListeners(D);return D},sortedFilteredColumns:function(C){C=C||this.filters;if(!C||!C.length){return this.sortedColumns()}var D=this;return D.sortedColumns().filter(function(G,F){var E=G.currentHistory||_.every(C.map(function(H){return H.call(G)}));return E})},sortedColumns:function(){var D=this;var C=this.collection.map(function(F,E){return D.columnMap[F.id]});return C},addColumn:function o(E,C){C=C!==undefined?C:true;var D=this.createColumn(E);this.columnMap[E.id]=D;if(C){this.renderColumns()}return D},addAsCurrentColumn:function o(E){var D=this,C=this.addColumn(E,false);this.setCurrentHistory(E);C.once("rendered",function(){D.queueHdaFetch(C)});return C},removeColumn:function x(E,D){D=D!==undefined?D:true;this.log("removeColumn",E);if(!E){return}var F=this,C=this.options.columnWidth+this.options.columnGap;E.$el.fadeOut("fast",function(){if(D){$(this).remove();F.$(".middle").width(F.$(".middle").width()-C);F.checkColumnsInView();F._recalcFirstColumnHeight()}F.stopListening(E.panel);F.stopListening(E);delete F.columnMap[E.model.id];E.remove()})},setUpColumnListeners:function n(C){var D=this;D.listenTo(C,{"in-view":D.queueHdaFetch});D.listenTo(C.panel,{"view:draggable:dragstart":function(H,F,E,G){D._dropData=JSON.parse(H.dataTransfer.getData("text"));D.currentColumnDropTargetOn()},"view:draggable:dragend":function(H,F,E,G){D._dropData=null;D.currentColumnDropTargetOff()},"droptarget:drop":function(G,H,F){var I=D._dropData.filter(function(J){return(_.isObject(J)&&J.id&&J.model_class==="HistoryDatasetAssociation")});D._dropData=null;var E=new a.NamedAjaxQueue();I.forEach(function(J){E.add({name:"copy-"+J.id,fn:function(){return F.model.contents.copy(J.id)}})});E.start();E.done(function(J){F.model.fetch()})}})},columnMapLength:function(){return Object.keys(this.columnMap).length},render:function A(D){D=D!==undefined?D:this.fxSpeed;var C=this;C.log(C+".render");C.$el.html(C.template(C.options));C.renderColumns(D);C.setUpBehaviors();C.trigger("rendered",C);return C},template:function w(C){C=C||{};var D=[];if(this.options.headerHeight){D=D.concat(['<div class="loading-overlay flex-row"><div class="loading-overlay-message">loading...</div></div>','<div class="header flex-column-container">','<div class="header-control header-control-left flex-column">','<button class="done btn btn-default">',_l("Done"),"</button>",'<button class="include-deleted btn btn-default"></button>','<div class="order btn-group">','<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown">',_l("Order histories by")+'... <span class="caret"></span>',"</button>",'<ul class="dropdown-menu" role="menu">','<li><a href="javascript:void(0);" class="order-update">',_l("Time of last update"),"</a></li>",'<li><a href="javascript:void(0);" class="order-name">',_l("Name"),"</a></li>",'<li><a href="javascript:void(0);" class="order-size">',_l("Size"),"</a></li>","</ul>","</div>",'<div id="search-histories" class="header-search"></div>',"</div>",'<div class="header-control header-control-center flex-column">','<div class="header-info">',"</div>","</div>",'<div class="header-control header-control-right flex-column">','<div id="search-datasets" class="header-search"></div>','<button id="toggle-deleted" class="btn btn-default">',_l("Include deleted datasets"),"</button>",'<button id="toggle-hidden" class="btn btn-default">',_l("Include hidden datasets"),"</button>","</div>","</div>"])}D=D.concat(['<div class="outer-middle flex-row flex-row-container">','<div class="middle flex-column-container flex-row"></div>',"</div>",'<div class="footer flex-column-container">',"</div>"]);return $(D.join(""))},renderColumns:function j(F){F=F!==undefined?F:this.fxSpeed;var E=this,C=E.sortedFilteredColumns();E.$(".middle").width(C.length*(this.options.columnWidth+this.options.columnGap)+this.options.columnGap+16);var D=E.$(".middle");D.empty();C.forEach(function(H,G){H.$el.appendTo(D);H.delegateEvents();E.renderColumn(H,F)});if(this.searchFor&&C.length<=1){}else{E.checkColumnsInView();this._recalcFirstColumnHeight()}return C},renderColumn:function(C,D){D=D!==undefined?D:this.fxSpeed;return _.delay(function(){return C.render(D)},0)},queueHdaFetch:function i(E){if(E.model.contents.length===0&&!E.model.get("empty")){var C={},D=_.values(E.panel.storage.get("expandedIds")).join();if(D){C.dataset_details=D}this.hdaQueue.add({name:E.model.id,fn:function(){var F=E.model.contents.fetch({data:C,silent:true});return F.done(function(G){E.panel.renderItems()})}});if(!this.hdaQueue.running){this.hdaQueue.start()}}},queueHdaFetchDetails:function(C){if((C.model.contents.length===0&&!C.model.get("empty"))||(!C.model.contents.haveDetails())){this.hdaQueue.add({name:C.model.id,fn:function(){var D=C.model.contents.fetch({data:{details:"all"},silent:true});return D.done(function(E){C.panel.renderItems()})}});if(!this.hdaQueue.running){this.hdaQueue.start()}}},renderInfo:function(C){this.$(".header .header-info").text(C)},events:{"click .done.btn":function(){window.location="/"},"click .create-new.btn":"create","click .order .order-update":function(C){this.setOrder("update")},"click .order .order-name":function(C){this.setOrder("name")},"click .order .order-size":function(C){this.setOrder("size")}},includeDeletedHistories:function(){window.location+=(/\?/.test(window.location.toString()))?("&"):("?")+"include_deleted_histories=True"},excludeDeletedHistories:function(){window.location=window.location.toString().replace(/[&\?]include_deleted_histories=True/g,"")},setUpBehaviors:function(){var D=this;D.$(".include-deleted").modeButton({initialMode:this.collection.includeDeleted?"exclude":"include",switchModesOnClick:false,modes:[{mode:"include",html:_l("Include deleted histories"),onclick:_.bind(D.includeDeletedHistories,D)},{mode:"exclude",html:_l("Exclude deleted histories"),onclick:_.bind(D.excludeDeletedHistories,D)}]});D.$("#search-histories").searchInput({name:"search-histories",placeholder:_l("search histories"),onsearch:function(E){D.searchFor=E;D.filters=[function(){return this.model.get("name").indexOf(D.searchFor)!==-1}];D.renderColumns(0)},onclear:function(E){D.searchFor=null;D.filters=[];D.renderColumns(0)}});D.$("#search-datasets").searchInput({name:"search-datasets",placeholder:_l("search all datasets"),onfirstsearch:function(E){D.hdaQueue.clear();D.$("#search-datasets").searchInput("toggle-loading");D.searchFor=E;D.sortedFilteredColumns().forEach(function(F){F.panel.searchItems(E);D.queueHdaFetchDetails(F)});D.hdaQueue.progress(function(F){D.renderInfo([_l("loading"),(F.curr+1),_l("of"),F.total].join(" "))});D.hdaQueue.deferred.done(function(){D.renderInfo("");D.$("#search-datasets").searchInput("toggle-loading")})},onsearch:function(E){D.searchFor=E;D.sortedFilteredColumns().forEach(function(F){F.panel.searchItems(E)})},onclear:function(E){D.searchFor=null;D.sortedFilteredColumns().forEach(function(F){F.panel.clearSearch()})}});D.$("#toggle-deleted").modeButton({initialMode:"include",modes:[{mode:"exclude",html:_l("Exclude deleted datasets")},{mode:"include",html:_l("Include deleted datasets")}]}).click(function(){var E=$(this).modeButton("getMode").mode==="exclude";D.sortedFilteredColumns().forEach(function(G,F){_.delay(function(){G.panel.toggleShowDeleted(E,false)},F*200)})});D.$("#toggle-hidden").modeButton({initialMode:"include",modes:[{mode:"exclude",html:_l("Exclude hidden datasets")},{mode:"include",html:_l("Include hidden datasets")}]}).click(function(){var E=$(this).modeButton("getMode").mode==="exclude";D.sortedFilteredColumns().forEach(function(G,F){_.delay(function(){G.panel.toggleShowHidden(E,false)},F*200)})});$(window).resize(function(){D._recalcFirstColumnHeight()});var C=_.debounce(_.bind(this.checkColumnsInView,this),100);this.$(".middle").parent().scroll(C)},_recalcFirstColumnHeight:function(){var C=this.$(".history-column").first(),E=this.$(".middle").height(),D=C.find(".panel-controls").height();C.height(E).find(".inner").height(E-D)},_viewport:function(){var C=this.$(".middle").parent().offset().left;return{left:C,right:C+this.$(".middle").parent().width()}},columnsInView:function(){var C=this._viewport();return this.sortedFilteredColumns().filter(function(D){return D.currentHistory||D.inView(C.left,C.right)})},checkColumnsInView:function(){this.columnsInView().forEach(function(C){C.trigger("in-view",C)})},currentColumnDropTargetOn:function(){var C=this.columnMap[this.currentHistoryId];if(!C){return}C.panel.dataDropped=function(D){};C.panel.dropTargetOn()},currentColumnDropTargetOff:function(){var C=this.columnMap[this.currentHistoryId];if(!C){return}C.panel.dataDropped=l.HistoryPanelEdit.prototype.dataDrop;C.panel.dropTargetOff()},toString:function(){return"MultiPanelColumns("+(this.columns?this.columns.length:0)+")"}});return{MultiPanelColumns:m}}); \ No newline at end of file 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.