mirror of
https://github.com/galaxyproject/galaxy.git
synced 2026-09-21 13:50:20 +08:00
(alt)history: split files up, rework names, add global Galaxy namespace
This commit is contained in:
@@ -1,479 +1,10 @@
|
||||
//define([
|
||||
// "../mvc/base-mvc"
|
||||
//
|
||||
//], function(){
|
||||
/* =============================================================================
|
||||
Backbone.js implementation of history panel
|
||||
|
||||
TODO:
|
||||
bug:
|
||||
anon, mako:
|
||||
tooltips not rendered
|
||||
anno, tags rendered
|
||||
title editable
|
||||
bug:
|
||||
when over quota history is re-rendered, over quota msg is not displayed
|
||||
bc the quota:over event isn't fired
|
||||
bc the user state hasn't changed
|
||||
|
||||
anon user, mako template init:
|
||||
bug: rename url seems to be wrong url
|
||||
|
||||
currently, adding a dataset (via tool execute, etc.) creates a new dataset and refreshes the page
|
||||
logged in, mako template:
|
||||
BUG: am able to start upload even if over quota - 'runs' forever
|
||||
|
||||
BUG: from above sit, delete uploading hda - now in state 'discarded'! ...new state to handle
|
||||
bug: quotaMeter bar rendering square in chrome
|
||||
BUG: quotaMsg not showing when 100% (on load)
|
||||
BUG: upload, history size, doesn't change
|
||||
TODO: on hdas state:ready, update ONLY the size...from what? histories.py? in js?
|
||||
BUG: imported, shared history with unaccessible dataset errs in historycontents when getting history
|
||||
(entire history is inaccessible)
|
||||
??: still happening?
|
||||
|
||||
from loadFromApi:
|
||||
BUG: not showing previous annotations
|
||||
|
||||
fixed:
|
||||
BUG: historyItem, error'd ds show display, download?
|
||||
FIXED: removed
|
||||
bug: loading hdas (alt_hist)
|
||||
FIXED: added anon user api request ( trans.user == None and trans.history.id == requested id )
|
||||
bug: quota meter not updating on upload/tool run
|
||||
FIXED: quotaMeter now listens for 'state:ready' from glx_history in alternate_history.mako
|
||||
bug: use of new HDACollection with event listener in init doesn't die...keeps reporting
|
||||
FIXED: change getVisible to return an array
|
||||
BUG: history, broken intial hist state (running, updater, etc.)
|
||||
??: doesn't seem to happen anymore
|
||||
BUG: collapse all should remove all expanded from storage
|
||||
FIXED: hideAllItemBodies now resets storage.expandedItems
|
||||
BUG: historyItem, shouldn't allow tag, annotate, peek on purged datasets
|
||||
FIXED: ok state now shows only: info, rerun
|
||||
BUG: history?, some ids aren't returning encoded...
|
||||
FIXED:???
|
||||
BUG: history, showing deleted ds
|
||||
FIXED
|
||||
UGH: historyItems have to be decorated with history_ids (api/histories/:history_id/contents/:id)
|
||||
FIXED by adding history_id to history_contents.show
|
||||
BUG: history, if hist has err'd ds, hist has perm state 'error', updater on following ds's doesn't run
|
||||
FIXED by reordering history state from ds' states here and histories
|
||||
BUG: history, broken annotation on reload (can't get thru api (sets fine, tho))
|
||||
FIXED: get thru api for now
|
||||
|
||||
to relational model?
|
||||
HDACollection, meta_files, display_apps, etc.
|
||||
|
||||
|
||||
quota mgr
|
||||
show_deleted/hidden:
|
||||
use storage
|
||||
on/off ui
|
||||
move histview fadein/out in render to app?
|
||||
don't draw body until it's first expand event
|
||||
localize all
|
||||
break this file up
|
||||
?: render url templates on init or render?
|
||||
?: history, annotation won't accept unicode
|
||||
|
||||
hierarchy:
|
||||
dataset -> hda
|
||||
history -> historyForEditing, historyForViewing
|
||||
display_structured?
|
||||
|
||||
btw: get an error'd ds by running fastqc on fastq (when you don't have it installed)
|
||||
|
||||
meta:
|
||||
css/html class/id 'item' -> hda
|
||||
add classes, ids on empty divs
|
||||
events (local/ui and otherwise)
|
||||
list in docs as well
|
||||
require.js
|
||||
convert function comments to jsDoc style, complete comments
|
||||
move inline styles into base.less
|
||||
watch the magic strings
|
||||
watch your globals
|
||||
|
||||
features:
|
||||
lineage
|
||||
hide button
|
||||
show permissions in info
|
||||
show shared/sharing status on ds, history
|
||||
maintain scroll position on refresh (storage?)
|
||||
selection, multi-select (and actions common to selected (ugh))
|
||||
searching
|
||||
sorting, re-shuffling
|
||||
|
||||
============================================================================= */
|
||||
var HistoryDatasetAssociation = BaseModel.extend( LoggableMixin ).extend({
|
||||
// a single HDA model
|
||||
|
||||
// uncomment this out see log messages
|
||||
//logger : console,
|
||||
|
||||
defaults : {
|
||||
// ---these are part of an HDA proper:
|
||||
|
||||
// parent (containing) history
|
||||
history_id : null,
|
||||
// often used with tagging
|
||||
model_class : 'HistoryDatasetAssociation',
|
||||
// index within history (??)
|
||||
hid : 0,
|
||||
|
||||
// ---whereas these are Dataset related/inherited
|
||||
|
||||
id : null,
|
||||
name : '',
|
||||
// one of HistoryDatasetAssociation.STATES
|
||||
state : '',
|
||||
// sniffed datatype (sam, tabular, bed, etc.)
|
||||
data_type : null,
|
||||
// size in bytes
|
||||
file_size : 0,
|
||||
|
||||
// array of associated file types (eg. [ 'bam_index', ... ])
|
||||
meta_files : [],
|
||||
|
||||
misc_blurb : '',
|
||||
misc_info : '',
|
||||
|
||||
deleted : false,
|
||||
purged : false,
|
||||
// aka. !hidden
|
||||
visible : false,
|
||||
// based on trans.user (is_admin or security_agent.can_access_dataset( <user_roles>, hda.dataset ))
|
||||
accessible : false,
|
||||
|
||||
//TODO: this needs to be removed (it is a function of the view type (e.g. HDAForEditingView))
|
||||
for_editing : true
|
||||
},
|
||||
|
||||
// fetch location of this history in the api
|
||||
url : function(){
|
||||
//TODO: get this via url router
|
||||
return 'api/histories/' + this.get( 'history_id' ) + '/contents/' + this.get( 'id' );
|
||||
},
|
||||
|
||||
// (curr) only handles changing state of non-accessible hdas to STATES.NOT_VIEWABLE
|
||||
//TODO:? use initialize (or validate) to check purged AND deleted -> purged XOR deleted
|
||||
initialize : function(){
|
||||
this.log( this + '.initialize', this.attributes );
|
||||
this.log( '\tparent history_id: ' + this.get( 'history_id' ) );
|
||||
|
||||
//!! this state is not in trans.app.model.Dataset.states - set it here -
|
||||
//TODO: change to server side.
|
||||
if( !this.get( 'accessible' ) ){
|
||||
this.set( 'state', HistoryDatasetAssociation.STATES.NOT_VIEWABLE );
|
||||
}
|
||||
|
||||
// if the state has changed and the new state is a ready state, fire an event
|
||||
this.on( 'change:state', function( currModel, newState ){
|
||||
this.log( this + ' has changed state:', currModel, newState );
|
||||
if( this.inReadyState() ){
|
||||
this.trigger( 'state:ready', this.get( 'id' ), newState, this.previous( 'state' ), currModel );
|
||||
}
|
||||
});
|
||||
|
||||
// debug on change events
|
||||
//this.on( 'change', function( currModel, changedList ){
|
||||
// this.log( this + ' has changed:', currModel, changedList );
|
||||
//});
|
||||
//this.bind( 'all', function( event ){
|
||||
// this.log( this + '', arguments );
|
||||
//});
|
||||
},
|
||||
|
||||
isDeletedOrPurged : function(){
|
||||
return ( this.get( 'deleted' ) || this.get( 'purged' ) );
|
||||
},
|
||||
|
||||
// based on show_deleted, show_hidden (gen. from the container control), would this ds show in the list of ds's?
|
||||
//TODO: too many visibles
|
||||
isVisible : function( show_deleted, show_hidden ){
|
||||
var isVisible = true;
|
||||
if( ( !show_deleted )
|
||||
&& ( this.get( 'deleted' ) || this.get( 'purged' ) ) ){
|
||||
isVisible = false;
|
||||
}
|
||||
if( ( !show_hidden )
|
||||
&& ( !this.get( 'visible' ) ) ){
|
||||
isVisible = false;
|
||||
}
|
||||
return isVisible;
|
||||
},
|
||||
|
||||
// 'ready' states are states where no processing (for the ds) is left to do on the server
|
||||
inReadyState : function(){
|
||||
var state = this.get( 'state' );
|
||||
return (
|
||||
( state === HistoryDatasetAssociation.STATES.NEW )
|
||||
|| ( state === HistoryDatasetAssociation.STATES.OK )
|
||||
|| ( state === HistoryDatasetAssociation.STATES.EMPTY )
|
||||
|| ( state === HistoryDatasetAssociation.STATES.FAILED_METADATA )
|
||||
|| ( state === HistoryDatasetAssociation.STATES.NOT_VIEWABLE )
|
||||
|| ( state === HistoryDatasetAssociation.STATES.DISCARDED )
|
||||
|| ( state === HistoryDatasetAssociation.STATES.ERROR )
|
||||
);
|
||||
},
|
||||
|
||||
// convenience fn to match hda.has_data
|
||||
hasData : function(){
|
||||
//TODO:?? is this equivalent to all possible hda.has_data calls?
|
||||
return ( this.get( 'file_size' ) > 0 );
|
||||
},
|
||||
|
||||
toString : function(){
|
||||
var nameAndId = this.get( 'id' ) || '';
|
||||
if( this.get( 'name' ) ){
|
||||
nameAndId += ':"' + this.get( 'name' ) + '"';
|
||||
}
|
||||
return 'HistoryDatasetAssociation(' + nameAndId + ')';
|
||||
}
|
||||
});
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
HistoryDatasetAssociation.STATES = {
|
||||
UPLOAD : 'upload',
|
||||
QUEUED : 'queued',
|
||||
RUNNING : 'running',
|
||||
SETTING_METADATA : 'setting_metadata',
|
||||
|
||||
NEW : 'new',
|
||||
OK : 'ok',
|
||||
EMPTY : 'empty',
|
||||
|
||||
FAILED_METADATA : 'failed_metadata',
|
||||
NOT_VIEWABLE : 'noPermission', // not in trans.app.model.Dataset.states
|
||||
DISCARDED : 'discarded',
|
||||
ERROR : 'error'
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
var HDACollection = Backbone.Collection.extend( LoggableMixin ).extend({
|
||||
model : HistoryDatasetAssociation,
|
||||
|
||||
//logger : console,
|
||||
|
||||
initialize : function(){
|
||||
//this.bind( 'all', function( event ){
|
||||
// this.log( this + '', arguments );
|
||||
//});
|
||||
},
|
||||
|
||||
// return the ids of every hda in this collection
|
||||
ids : function(){
|
||||
return this.map( function( item ){ return item.id; });
|
||||
},
|
||||
|
||||
// return an HDA collection containing every 'shown' hda based on show_deleted/hidden
|
||||
getVisible : function( show_deleted, show_hidden ){
|
||||
return this.filter( function( item ){ return item.isVisible( show_deleted, show_hidden ); });
|
||||
},
|
||||
|
||||
// get a map where <possible hda state> : [ <list of hda ids in that state> ]
|
||||
getStateLists : function(){
|
||||
var stateLists = {};
|
||||
_.each( _.values( HistoryDatasetAssociation.STATES ), function( state ){
|
||||
stateLists[ state ] = [];
|
||||
});
|
||||
//NOTE: will err on unknown state
|
||||
this.each( function( item ){
|
||||
stateLists[ item.get( 'state' ) ].push( item.get( 'id' ) );
|
||||
});
|
||||
return stateLists;
|
||||
},
|
||||
|
||||
// returns the id of every hda still running (not in a ready state)
|
||||
running : function(){
|
||||
var idList = [];
|
||||
this.each( function( item ){
|
||||
if( !item.inReadyState() ){
|
||||
idList.push( item.get( 'id' ) );
|
||||
}
|
||||
});
|
||||
return idList;
|
||||
},
|
||||
|
||||
// update (fetch -> render) the hdas with the ids given
|
||||
update : function( ids ){
|
||||
this.log( this + 'update:', ids );
|
||||
|
||||
if( !( ids && ids.length ) ){ return; }
|
||||
|
||||
var collection = this;
|
||||
_.each( ids, function( id, index ){
|
||||
var historyItem = collection.get( id );
|
||||
historyItem.fetch();
|
||||
});
|
||||
},
|
||||
|
||||
toString : function(){
|
||||
return ( 'HDACollection(' + this.ids().join(',') + ')' );
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
//==============================================================================
|
||||
var History = BaseModel.extend( LoggableMixin ).extend({
|
||||
//TODO: bind change events from items and collection to this (itemLengths, states)
|
||||
|
||||
// uncomment this out see log messages
|
||||
//logger : console,
|
||||
|
||||
// values from api (may need more)
|
||||
defaults : {
|
||||
id : '',
|
||||
name : '',
|
||||
state : '',
|
||||
|
||||
//TODO: wire these to items (or this)
|
||||
show_deleted : false,
|
||||
show_hidden : false,
|
||||
|
||||
diskSize : 0,
|
||||
deleted : false,
|
||||
|
||||
tags : [],
|
||||
annotation : null,
|
||||
|
||||
//TODO: quota msg and message? how to get those over the api?
|
||||
message : null,
|
||||
quotaMsg : false
|
||||
},
|
||||
|
||||
url : function(){
|
||||
// api location of history resource
|
||||
//TODO: hardcoded
|
||||
return 'api/histories/' + this.get( 'id' );
|
||||
},
|
||||
|
||||
initialize : function( initialSettings, initialHdas ){
|
||||
this.log( this + ".initialize:", initialSettings, initialHdas );
|
||||
|
||||
this.hdas = new HDACollection();
|
||||
|
||||
// if we've got hdas passed in the constructor, load them and set up updates if needed
|
||||
if( initialHdas && initialHdas.length ){
|
||||
this.hdas.reset( initialHdas );
|
||||
this.checkForUpdates();
|
||||
}
|
||||
|
||||
//this.on( 'change', function( currModel, changedList ){
|
||||
// this.log( this + ' has changed:', currModel, changedList );
|
||||
//});
|
||||
//this.bind( 'all', function( event ){
|
||||
// this.log( this + '', arguments );
|
||||
//});
|
||||
},
|
||||
|
||||
// get data via the api (alternative to sending options,hdas to initialize)
|
||||
loadFromApi : function( historyId, callback ){
|
||||
var history = this;
|
||||
|
||||
// fetch the history AND the user (mainly to see if they're logged in at this point)
|
||||
history.attributes.id = historyId;
|
||||
//TODO:?? really? fetch user here?
|
||||
jQuery.when( jQuery.ajax( 'api/users/current' ), history.fetch()
|
||||
|
||||
).then( function( userResponse, historyResponse ){
|
||||
//console.warn( 'fetched user, history: ', userResponse, historyResponse );
|
||||
history.attributes.user = userResponse[0]; //? meh.
|
||||
history.log( history );
|
||||
|
||||
}).then( function(){
|
||||
// ...then the hdas (using contents?ids=...)
|
||||
jQuery.ajax( history.url() + '/contents?' + jQuery.param({
|
||||
ids : history.itemIdsFromStateIds().join( ',' )
|
||||
|
||||
// reset the collection to the hdas returned
|
||||
})).success( function( hdas ){
|
||||
//console.warn( 'fetched hdas' );
|
||||
history.hdas.reset( hdas );
|
||||
history.checkForUpdates();
|
||||
callback();
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
// reduce the state_ids map of hda id lists -> a single list of ids
|
||||
//...ugh - seems roundabout; necessary because the history doesn't have a straightforward list of ids
|
||||
// (and history_contents/index curr returns a summary only)
|
||||
hdaIdsFromStateIds : function(){
|
||||
return _.reduce( _.values( this.get( 'state_ids' ) ), function( reduction, currIdList ){
|
||||
return reduction.concat( currIdList );
|
||||
});
|
||||
},
|
||||
|
||||
// get the history's state from it's cummulative ds states, delay + update if needed
|
||||
checkForUpdates : function( datasets ){
|
||||
// get overall History state from collection, run updater if History has running/queued hdas
|
||||
// boiling it down on the client to running/not
|
||||
if( this.hdas.running().length ){
|
||||
this.stateUpdater();
|
||||
}
|
||||
return this;
|
||||
},
|
||||
|
||||
// update this history, find any hda's running/queued, update ONLY those that have changed states,
|
||||
// set up to run this again in some interval of time
|
||||
stateUpdater : function(){
|
||||
var history = this,
|
||||
oldState = this.get( 'state' ),
|
||||
// state ids is a map of every possible hda state, each containing a list of ids for hdas in that state
|
||||
oldStateIds = this.get( 'state_ids' );
|
||||
|
||||
// pull from the history api
|
||||
//TODO: fetch?
|
||||
jQuery.ajax( 'api/histories/' + this.get( 'id' )
|
||||
|
||||
).success( function( response ){
|
||||
//this.log( 'historyApiRequest, response:', response );
|
||||
history.set( response );
|
||||
history.log( 'current history state:', history.get( 'state' ),
|
||||
'(was)', oldState,
|
||||
'new size:', history.get( 'nice_size' ) );
|
||||
|
||||
//TODO: revisit this - seems too elaborate, need something straightforward
|
||||
// for each state, check for the difference between old dataset states and new
|
||||
// the goal here is to check ONLY those datasets that have changed states (not all datasets)
|
||||
var changedIds = [];
|
||||
_.each( _.keys( response.state_ids ), function( state ){
|
||||
var diffIds = _.difference( response.state_ids[ state ], oldStateIds[ state ] );
|
||||
// aggregate those changed ids
|
||||
changedIds = changedIds.concat( diffIds );
|
||||
});
|
||||
|
||||
// send the changed ids (if any) to dataset collection to have them fetch their own model changes
|
||||
if( changedIds.length ){
|
||||
history.hdas.update( changedIds );
|
||||
}
|
||||
|
||||
// set up to keep pulling if this history in run/queue state
|
||||
//TODO: magic number here
|
||||
if( ( history.get( 'state' ) === HistoryDatasetAssociation.STATES.RUNNING )
|
||||
|| ( history.get( 'state' ) === HistoryDatasetAssociation.STATES.QUEUED ) ){
|
||||
setTimeout( function(){
|
||||
history.stateUpdater();
|
||||
}, 4000 );
|
||||
}
|
||||
|
||||
}).error( function( xhr, status, error ){
|
||||
if( console && console.warn ){
|
||||
console.warn( 'Error getting history updates from the server:', xhr, status, error );
|
||||
}
|
||||
alert( 'Error getting history updates from the server.\n' + error );
|
||||
});
|
||||
},
|
||||
|
||||
toString : function(){
|
||||
var nameString = ( this.get( 'name' ) )?
|
||||
( ',' + this.get( 'name' ) ) : ( '' );
|
||||
return 'History(' + this.get( 'id' ) + nameString + ')';
|
||||
}
|
||||
});
|
||||
|
||||
//==============================================================================
|
||||
/** View for editing (working with - as opposed to viewing/read-only) an hda
|
||||
*
|
||||
*/
|
||||
var HDAView = BaseView.extend( LoggableMixin ).extend({
|
||||
//??TODO: add alias in initialize this.hda = this.model?
|
||||
// view for HistoryDatasetAssociation model above
|
||||
@@ -1325,287 +856,7 @@ function create_trackster_action_fn(vis_url, dataset_params, dbkey) {
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
//==============================================================================
|
||||
// view for the HDACollection (as per current right hand panel)
|
||||
var HistoryView = BaseView.extend( LoggableMixin ).extend({
|
||||
|
||||
// uncomment this out see log messages
|
||||
//logger : console,
|
||||
|
||||
// direct attachment to existing element
|
||||
el : 'body.historyPage',
|
||||
|
||||
// init with the model, urlTemplates, set up storage, bind HDACollection events
|
||||
//NOTE: this will create or load PersistantStorage keyed under 'HistoryView.<id>'
|
||||
//pre: you'll need to pass in the urlTemplates (urlTemplates : { history : {...}, hda : {...} })
|
||||
initialize : function( attributes ){
|
||||
this.log( this + '.initialize:', attributes );
|
||||
|
||||
// set up url templates
|
||||
//TODO: prob. better to put this in class scope (as the handlebars templates), but...
|
||||
// they're added to GalaxyPaths on page load (after this file is loaded)
|
||||
if( !attributes.urlTemplates ){ throw( 'HDAView needs urlTemplates on initialize' ); }
|
||||
if( !attributes.urlTemplates.history ){ throw( 'HDAView needs urlTemplates.history on initialize' ); }
|
||||
if( !attributes.urlTemplates.hda ){ throw( 'HDAView needs urlTemplates.hda on initialize' ); }
|
||||
this.urlTemplates = attributes.urlTemplates.history;
|
||||
this.hdaUrlTemplates = attributes.urlTemplates.hda;
|
||||
|
||||
// data that needs to be persistant over page refreshes
|
||||
// (note the key function which uses the history id as well)
|
||||
this.storage = new PersistantStorage(
|
||||
'HistoryView.' + this.model.get( 'id' ),
|
||||
{ expandedHdas : {} }
|
||||
);
|
||||
|
||||
// bind events from the model's hda collection
|
||||
//this.model.bind( 'change', this.render, this );
|
||||
this.model.bind( 'change:nice_size', this.updateHistoryDiskSize, this );
|
||||
|
||||
this.model.hdas.bind( 'add', this.add, this );
|
||||
this.model.hdas.bind( 'reset', this.addAll, this );
|
||||
this.model.hdas.bind( 'all', this.all, this );
|
||||
|
||||
//this.bind( 'all', function(){
|
||||
// this.log( arguments );
|
||||
//}, this );
|
||||
|
||||
// set up instance vars
|
||||
this.hdaViews = {};
|
||||
this.urls = {};
|
||||
},
|
||||
|
||||
add : function( hda ){
|
||||
//console.debug( 'add.' + this, hda );
|
||||
//TODO
|
||||
},
|
||||
|
||||
addAll : function(){
|
||||
//console.debug( 'addAll.' + this );
|
||||
// re render when all hdas are reset
|
||||
this.render();
|
||||
},
|
||||
|
||||
all : function( event ){
|
||||
//console.debug( 'allItemEvents.' + this, event );
|
||||
//...for which to do the debuggings
|
||||
},
|
||||
|
||||
// render the urls for this view using urlTemplates and the model data
|
||||
renderUrls : function( modelJson ){
|
||||
var historyView = this;
|
||||
|
||||
historyView.urls = {};
|
||||
_.each( this.urlTemplates, function( urlTemplate, urlKey ){
|
||||
historyView.urls[ urlKey ] = _.template( urlTemplate, modelJson );
|
||||
});
|
||||
return historyView.urls;
|
||||
},
|
||||
|
||||
// render urls, historyView body, and hdas (if any are shown), fade out, swap, fade in, set up behaviours
|
||||
render : function(){
|
||||
var historyView = this,
|
||||
setUpQueueName = historyView.toString() + '.set-up',
|
||||
newRender = $( '<div/>' ),
|
||||
modelJson = this.model.toJSON(),
|
||||
initialRender = ( this.$el.children().size() === 0 );
|
||||
|
||||
//console.debug( this + '.render, initialRender:', initialRender );
|
||||
|
||||
// render the urls and add them to the model json
|
||||
modelJson.urls = this.renderUrls( modelJson );
|
||||
|
||||
// render the main template, tooltips
|
||||
//NOTE: this is done before the items, since item views should handle theirs themselves
|
||||
newRender.append( HistoryView.templates.historyPanel( modelJson ) );
|
||||
newRender.find( '.tooltip' ).tooltip();
|
||||
|
||||
// render hda views (if any and any shown (show_deleted/hidden)
|
||||
if( !this.model.hdas.length
|
||||
|| !this.renderItems( newRender.find( '#' + this.model.get( 'id' ) + '-datasets' ) ) ){
|
||||
// if history is empty or no hdas would be rendered, show the empty message
|
||||
newRender.find( '#emptyHistoryMessage' ).show();
|
||||
}
|
||||
|
||||
// fade out existing, swap with the new, fade in, set up behaviours
|
||||
$( historyView ).queue( setUpQueueName, function( next ){
|
||||
historyView.$el.fadeOut( 'fast', function(){ next(); });
|
||||
});
|
||||
$( historyView ).queue( setUpQueueName, function( next ){
|
||||
// swap over from temp div newRender
|
||||
historyView.$el.html( '' );
|
||||
historyView.$el.append( newRender.children() );
|
||||
|
||||
historyView.$el.fadeIn( 'fast', function(){ next(); });
|
||||
});
|
||||
$( historyView ).queue( setUpQueueName, function( next ){
|
||||
this.log( historyView + ' rendered:', historyView.$el );
|
||||
|
||||
//TODO: ideally, these would be set up before the fade in (can't because of async save text)
|
||||
historyView.setUpBehaviours();
|
||||
|
||||
if( initialRender ){
|
||||
historyView.trigger( 'rendered:initial' );
|
||||
|
||||
} else {
|
||||
historyView.trigger( 'rendered' );
|
||||
}
|
||||
next();
|
||||
});
|
||||
$( historyView ).dequeue( setUpQueueName );
|
||||
return this;
|
||||
},
|
||||
|
||||
// set up a view for each item to be shown, init with model and listeners, cache to map ( model.id : view )
|
||||
renderItems : function( $whereTo ){
|
||||
this.hdaViews = {};
|
||||
var historyView = this,
|
||||
show_deleted = this.model.get( 'show_deleted' ),
|
||||
show_hidden = this.model.get( 'show_hidden' ),
|
||||
visibleHdas = this.model.hdas.getVisible( show_deleted, show_hidden );
|
||||
|
||||
// only render the shown hdas
|
||||
_.each( visibleHdas, function( hda ){
|
||||
var hdaId = hda.get( 'id' ),
|
||||
expanded = historyView.storage.get( 'expandedHdas' ).get( hdaId );
|
||||
historyView.hdaViews[ hdaId ] = new HDAView({
|
||||
model : hda,
|
||||
expanded : expanded,
|
||||
urlTemplates : historyView.hdaUrlTemplates
|
||||
});
|
||||
historyView.setUpHdaListeners( historyView.hdaViews[ hdaId ] );
|
||||
// render it (NOTE: reverse order, newest on top (prepend))
|
||||
//TODO: by default send a reverse order list (although this may be more efficient - it's more confusing)
|
||||
$whereTo.prepend( historyView.hdaViews[ hdaId ].render().$el );
|
||||
});
|
||||
return visibleHdas.length;
|
||||
},
|
||||
|
||||
// set up HistoryView->HDAView listeners
|
||||
setUpHdaListeners : function( hdaView ){
|
||||
var historyView = this;
|
||||
|
||||
// use storage to maintain a list of hdas whose bodies are expanded
|
||||
hdaView.bind( 'toggleBodyVisibility', function( id, visible ){
|
||||
if( visible ){
|
||||
historyView.storage.get( 'expandedHdas' ).set( id, true );
|
||||
} else {
|
||||
historyView.storage.get( 'expandedHdas' ).deleteKey( id );
|
||||
}
|
||||
});
|
||||
|
||||
// rendering listeners
|
||||
hdaView.bind( 'rendered:ready', function(){ historyView.trigger( 'hda:rendered:ready' ); });
|
||||
},
|
||||
|
||||
// set up js/widget behaviours: tooltips,
|
||||
//TODO: these should be either sub-MVs, or handled by events
|
||||
setUpBehaviours : function(){
|
||||
// anon users shouldn't have access to any of these
|
||||
if( !( this.model.get( 'user' ) && this.model.get( 'user' ).email ) ){ return; }
|
||||
|
||||
// annotation slide down
|
||||
var historyAnnotationArea = this.$( '#history-annotation-area' );
|
||||
this.$( '#history-annotate' ).click( function() {
|
||||
if ( historyAnnotationArea.is( ":hidden" ) ) {
|
||||
historyAnnotationArea.slideDown( "fast" );
|
||||
} else {
|
||||
historyAnnotationArea.slideUp( "fast" );
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
// title and annotation editable text
|
||||
//NOTE: these use page scoped selectors - so these need to be in the page DOM before they're applicable
|
||||
async_save_text( "history-name-container", "history-name",
|
||||
this.urls.rename, "new_name", 18 );
|
||||
|
||||
async_save_text( "history-annotation-container", "history-annotation",
|
||||
this.urls.annotate, "new_annotation", 18, true, 4 );
|
||||
},
|
||||
|
||||
// update the history size display (curr. upper right of panel)
|
||||
updateHistoryDiskSize : function(){
|
||||
this.$el.find( '#history-size' ).text( this.model.get( 'nice_size' ) );
|
||||
},
|
||||
|
||||
//TODO: this seems more like a per user message than a history message; IOW, this doesn't belong here
|
||||
showQuotaMessage : function( userData ){
|
||||
var msg = this.$el.find( '#quota-message-container' );
|
||||
//this.log( this + ' showing quota message:', msg, userData );
|
||||
if( msg.is( ':hidden' ) ){ msg.slideDown( 'fast' ); }
|
||||
},
|
||||
|
||||
//TODO: this seems more like a per user message than a history message
|
||||
hideQuotaMessage : function( userData ){
|
||||
var msg = this.$el.find( '#quota-message-container' );
|
||||
//this.log( this + ' hiding quota message:', msg, userData );
|
||||
if( !msg.is( ':hidden' ) ){ msg.slideUp( 'fast' ); }
|
||||
},
|
||||
|
||||
events : {
|
||||
'click #history-collapse-all' : 'hideAllHdaBodies',
|
||||
'click #history-tag' : 'loadAndDisplayTags'
|
||||
},
|
||||
|
||||
// collapse all hda bodies
|
||||
hideAllHdaBodies : function(){
|
||||
_.each( this.hdaViews, function( item ){
|
||||
item.toggleBodyVisibility( null, false );
|
||||
});
|
||||
this.storage.set( 'expandedHdas', {} );
|
||||
},
|
||||
|
||||
// find the tag area and, if initial: (via ajax) load the html for displaying them; otherwise, unhide/hide
|
||||
//TODO: into sub-MV
|
||||
loadAndDisplayTags : function( event ){
|
||||
this.log( this + '.loadAndDisplayTags', event );
|
||||
var tagArea = this.$el.find( '#history-tag-area' ),
|
||||
tagElt = tagArea.find( '.tag-elt' );
|
||||
this.log( '\t tagArea', tagArea, ' tagElt', tagElt );
|
||||
|
||||
// Show or hide tag area; if showing tag area and it's empty, fill it
|
||||
if( tagArea.is( ":hidden" ) ){
|
||||
if( !jQuery.trim( tagElt.html() ) ){
|
||||
var view = this;
|
||||
// Need to fill tag element.
|
||||
$.ajax({
|
||||
//TODO: the html from this breaks a couple of times
|
||||
url: view.urls.tag,
|
||||
error: function() { alert( "Tagging failed" ); },
|
||||
success: function(tag_elt_html) {
|
||||
//view.log( view + ' tag elt html (ajax)', tag_elt_html );
|
||||
tagElt.html(tag_elt_html);
|
||||
tagElt.find(".tooltip").tooltip();
|
||||
tagArea.slideDown("fast");
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// Tag element already filled: show
|
||||
tagArea.slideDown("fast");
|
||||
}
|
||||
|
||||
} else {
|
||||
// Currently shown: Hide
|
||||
tagArea.slideUp("fast");
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
toString : function(){
|
||||
var nameString = this.model.get( 'name' ) || '';
|
||||
return 'HistoryView(' + nameString + ')';
|
||||
}
|
||||
});
|
||||
HistoryView.templates = {
|
||||
historyPanel : Handlebars.templates[ 'template-history-historyPanel' ]
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
//return {
|
||||
// HistoryItem : HistoryItem,
|
||||
// HDAView : HDAView,
|
||||
// HistoryCollection : HistoryCollection,
|
||||
// History : History,
|
||||
// HistoryView : HistoryView
|
||||
//};});
|
||||
@@ -0,0 +1,223 @@
|
||||
//define([
|
||||
// "../mvc/base-mvc"
|
||||
//], function(){
|
||||
//==============================================================================
|
||||
/**
|
||||
*
|
||||
*/
|
||||
var HistoryDatasetAssociation = BaseModel.extend( LoggableMixin ).extend({
|
||||
// a single HDA model
|
||||
|
||||
// uncomment this out see log messages
|
||||
//logger : console,
|
||||
|
||||
defaults : {
|
||||
// ---these are part of an HDA proper:
|
||||
|
||||
// parent (containing) history
|
||||
history_id : null,
|
||||
// often used with tagging
|
||||
model_class : 'HistoryDatasetAssociation',
|
||||
// index within history (??)
|
||||
hid : 0,
|
||||
|
||||
// ---whereas these are Dataset related/inherited
|
||||
|
||||
id : null,
|
||||
name : '',
|
||||
// one of HistoryDatasetAssociation.STATES
|
||||
state : '',
|
||||
// sniffed datatype (sam, tabular, bed, etc.)
|
||||
data_type : null,
|
||||
// size in bytes
|
||||
file_size : 0,
|
||||
|
||||
// array of associated file types (eg. [ 'bam_index', ... ])
|
||||
meta_files : [],
|
||||
|
||||
misc_blurb : '',
|
||||
misc_info : '',
|
||||
|
||||
deleted : false,
|
||||
purged : false,
|
||||
// aka. !hidden
|
||||
visible : false,
|
||||
// based on trans.user (is_admin or security_agent.can_access_dataset( <user_roles>, hda.dataset ))
|
||||
accessible : false,
|
||||
|
||||
//TODO: this needs to be removed (it is a function of the view type (e.g. HDAForEditingView))
|
||||
for_editing : true
|
||||
},
|
||||
|
||||
// fetch location of this history in the api
|
||||
url : function(){
|
||||
//TODO: get this via url router
|
||||
return 'api/histories/' + this.get( 'history_id' ) + '/contents/' + this.get( 'id' );
|
||||
},
|
||||
|
||||
// (curr) only handles changing state of non-accessible hdas to STATES.NOT_VIEWABLE
|
||||
//TODO:? use initialize (or validate) to check purged AND deleted -> purged XOR deleted
|
||||
initialize : function(){
|
||||
this.log( this + '.initialize', this.attributes );
|
||||
this.log( '\tparent history_id: ' + this.get( 'history_id' ) );
|
||||
|
||||
//!! this state is not in trans.app.model.Dataset.states - set it here -
|
||||
//TODO: change to server side.
|
||||
if( !this.get( 'accessible' ) ){
|
||||
this.set( 'state', HistoryDatasetAssociation.STATES.NOT_VIEWABLE );
|
||||
}
|
||||
|
||||
// if the state has changed and the new state is a ready state, fire an event
|
||||
this.on( 'change:state', function( currModel, newState ){
|
||||
this.log( this + ' has changed state:', currModel, newState );
|
||||
if( this.inReadyState() ){
|
||||
this.trigger( 'state:ready', this.get( 'id' ), newState, this.previous( 'state' ), currModel );
|
||||
}
|
||||
});
|
||||
|
||||
// debug on change events
|
||||
//this.on( 'change', function( currModel, changedList ){
|
||||
// this.log( this + ' has changed:', currModel, changedList );
|
||||
//});
|
||||
//this.bind( 'all', function( event ){
|
||||
// this.log( this + '', arguments );
|
||||
//});
|
||||
},
|
||||
|
||||
isDeletedOrPurged : function(){
|
||||
return ( this.get( 'deleted' ) || this.get( 'purged' ) );
|
||||
},
|
||||
|
||||
// based on show_deleted, show_hidden (gen. from the container control), would this ds show in the list of ds's?
|
||||
//TODO: too many visibles
|
||||
isVisible : function( show_deleted, show_hidden ){
|
||||
var isVisible = true;
|
||||
if( ( !show_deleted )
|
||||
&& ( this.get( 'deleted' ) || this.get( 'purged' ) ) ){
|
||||
isVisible = false;
|
||||
}
|
||||
if( ( !show_hidden )
|
||||
&& ( !this.get( 'visible' ) ) ){
|
||||
isVisible = false;
|
||||
}
|
||||
return isVisible;
|
||||
},
|
||||
|
||||
// 'ready' states are states where no processing (for the ds) is left to do on the server
|
||||
inReadyState : function(){
|
||||
var state = this.get( 'state' );
|
||||
return (
|
||||
( state === HistoryDatasetAssociation.STATES.NEW )
|
||||
|| ( state === HistoryDatasetAssociation.STATES.OK )
|
||||
|| ( state === HistoryDatasetAssociation.STATES.EMPTY )
|
||||
|| ( state === HistoryDatasetAssociation.STATES.FAILED_METADATA )
|
||||
|| ( state === HistoryDatasetAssociation.STATES.NOT_VIEWABLE )
|
||||
|| ( state === HistoryDatasetAssociation.STATES.DISCARDED )
|
||||
|| ( state === HistoryDatasetAssociation.STATES.ERROR )
|
||||
);
|
||||
},
|
||||
|
||||
// convenience fn to match hda.has_data
|
||||
hasData : function(){
|
||||
//TODO:?? is this equivalent to all possible hda.has_data calls?
|
||||
return ( this.get( 'file_size' ) > 0 );
|
||||
},
|
||||
|
||||
toString : function(){
|
||||
var nameAndId = this.get( 'id' ) || '';
|
||||
if( this.get( 'name' ) ){
|
||||
nameAndId += ':"' + this.get( 'name' ) + '"';
|
||||
}
|
||||
return 'HistoryDatasetAssociation(' + nameAndId + ')';
|
||||
}
|
||||
});
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
HistoryDatasetAssociation.STATES = {
|
||||
UPLOAD : 'upload',
|
||||
QUEUED : 'queued',
|
||||
RUNNING : 'running',
|
||||
SETTING_METADATA : 'setting_metadata',
|
||||
|
||||
NEW : 'new',
|
||||
OK : 'ok',
|
||||
EMPTY : 'empty',
|
||||
|
||||
FAILED_METADATA : 'failed_metadata',
|
||||
NOT_VIEWABLE : 'noPermission', // not in trans.app.model.Dataset.states
|
||||
DISCARDED : 'discarded',
|
||||
ERROR : 'error'
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
*
|
||||
*/
|
||||
var HDACollection = Backbone.Collection.extend( LoggableMixin ).extend({
|
||||
model : HistoryDatasetAssociation,
|
||||
|
||||
//logger : console,
|
||||
|
||||
initialize : function(){
|
||||
//this.bind( 'all', function( event ){
|
||||
// this.log( this + '', arguments );
|
||||
//});
|
||||
},
|
||||
|
||||
// return the ids of every hda in this collection
|
||||
ids : function(){
|
||||
return this.map( function( item ){ return item.id; });
|
||||
},
|
||||
|
||||
// return an HDA collection containing every 'shown' hda based on show_deleted/hidden
|
||||
getVisible : function( show_deleted, show_hidden ){
|
||||
return this.filter( function( item ){ return item.isVisible( show_deleted, show_hidden ); });
|
||||
},
|
||||
|
||||
// get a map where <possible hda state> : [ <list of hda ids in that state> ]
|
||||
getStateLists : function(){
|
||||
var stateLists = {};
|
||||
_.each( _.values( HistoryDatasetAssociation.STATES ), function( state ){
|
||||
stateLists[ state ] = [];
|
||||
});
|
||||
//NOTE: will err on unknown state
|
||||
this.each( function( item ){
|
||||
stateLists[ item.get( 'state' ) ].push( item.get( 'id' ) );
|
||||
});
|
||||
return stateLists;
|
||||
},
|
||||
|
||||
// returns the id of every hda still running (not in a ready state)
|
||||
running : function(){
|
||||
var idList = [];
|
||||
this.each( function( item ){
|
||||
if( !item.inReadyState() ){
|
||||
idList.push( item.get( 'id' ) );
|
||||
}
|
||||
});
|
||||
return idList;
|
||||
},
|
||||
|
||||
// update (fetch -> render) the hdas with the ids given
|
||||
update : function( ids ){
|
||||
this.log( this + 'update:', ids );
|
||||
|
||||
if( !( ids && ids.length ) ){ return; }
|
||||
|
||||
var collection = this;
|
||||
_.each( ids, function( id, index ){
|
||||
var historyItem = collection.get( id );
|
||||
historyItem.fetch();
|
||||
});
|
||||
},
|
||||
|
||||
toString : function(){
|
||||
return ( 'HDACollection(' + this.ids().join(',') + ')' );
|
||||
}
|
||||
});
|
||||
|
||||
//==============================================================================
|
||||
//return {
|
||||
// HistoryDatasetAssociation : HistoryDatasetAssociation,
|
||||
// HDACollection : HDACollection,
|
||||
//};});
|
||||
@@ -0,0 +1,180 @@
|
||||
//define([
|
||||
// "../mvc/base-mvc"
|
||||
//], function(){
|
||||
//==============================================================================
|
||||
/**
|
||||
*
|
||||
*/
|
||||
var History = BaseModel.extend( LoggableMixin ).extend({
|
||||
//TODO: bind change events from items and collection to this (itemLengths, states)
|
||||
|
||||
// uncomment this out see log messages
|
||||
//logger : console,
|
||||
|
||||
// values from api (may need more)
|
||||
defaults : {
|
||||
id : '',
|
||||
name : '',
|
||||
state : '',
|
||||
|
||||
//TODO: wire these to items (or this)
|
||||
show_deleted : false,
|
||||
show_hidden : false,
|
||||
|
||||
diskSize : 0,
|
||||
deleted : false,
|
||||
|
||||
tags : [],
|
||||
annotation : null,
|
||||
|
||||
//TODO: quota msg and message? how to get those over the api?
|
||||
message : null,
|
||||
quotaMsg : false
|
||||
},
|
||||
|
||||
url : function(){
|
||||
// api location of history resource
|
||||
//TODO: hardcoded
|
||||
return 'api/histories/' + this.get( 'id' );
|
||||
},
|
||||
|
||||
initialize : function( initialSettings, initialHdas ){
|
||||
this.log( this + ".initialize:", initialSettings, initialHdas );
|
||||
|
||||
this.hdas = new HDACollection();
|
||||
|
||||
// if we've got hdas passed in the constructor, load them and set up updates if needed
|
||||
if( initialHdas && initialHdas.length ){
|
||||
this.hdas.reset( initialHdas );
|
||||
this.checkForUpdates();
|
||||
}
|
||||
|
||||
//this.on( 'change', function( currModel, changedList ){
|
||||
// this.log( this + ' has changed:', currModel, changedList );
|
||||
//});
|
||||
//this.bind( 'all', function( event ){
|
||||
// this.log( this + '', arguments );
|
||||
//});
|
||||
},
|
||||
|
||||
// get data via the api (alternative to sending options,hdas to initialize)
|
||||
loadFromApi : function( historyId, callback ){
|
||||
var history = this;
|
||||
|
||||
// fetch the history AND the user (mainly to see if they're logged in at this point)
|
||||
history.attributes.id = historyId;
|
||||
//TODO:?? really? fetch user here?
|
||||
jQuery.when( jQuery.ajax( 'api/users/current' ), history.fetch()
|
||||
|
||||
).then( function( userResponse, historyResponse ){
|
||||
//console.warn( 'fetched user, history: ', userResponse, historyResponse );
|
||||
history.attributes.user = userResponse[0]; //? meh.
|
||||
history.log( history );
|
||||
|
||||
}).then( function(){
|
||||
// ...then the hdas (using contents?ids=...)
|
||||
jQuery.ajax( history.url() + '/contents?' + jQuery.param({
|
||||
ids : history.itemIdsFromStateIds().join( ',' )
|
||||
|
||||
// reset the collection to the hdas returned
|
||||
})).success( function( hdas ){
|
||||
//console.warn( 'fetched hdas' );
|
||||
history.hdas.reset( hdas );
|
||||
history.checkForUpdates();
|
||||
callback();
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
// reduce the state_ids map of hda id lists -> a single list of ids
|
||||
//...ugh - seems roundabout; necessary because the history doesn't have a straightforward list of ids
|
||||
// (and history_contents/index curr returns a summary only)
|
||||
hdaIdsFromStateIds : function(){
|
||||
return _.reduce( _.values( this.get( 'state_ids' ) ), function( reduction, currIdList ){
|
||||
return reduction.concat( currIdList );
|
||||
});
|
||||
},
|
||||
|
||||
// get the history's state from it's cummulative ds states, delay + update if needed
|
||||
checkForUpdates : function( datasets ){
|
||||
// get overall History state from collection, run updater if History has running/queued hdas
|
||||
// boiling it down on the client to running/not
|
||||
if( this.hdas.running().length ){
|
||||
this.stateUpdater();
|
||||
}
|
||||
return this;
|
||||
},
|
||||
|
||||
// update this history, find any hda's running/queued, update ONLY those that have changed states,
|
||||
// set up to run this again in some interval of time
|
||||
stateUpdater : function(){
|
||||
var history = this,
|
||||
oldState = this.get( 'state' ),
|
||||
// state ids is a map of every possible hda state, each containing a list of ids for hdas in that state
|
||||
oldStateIds = this.get( 'state_ids' );
|
||||
|
||||
// pull from the history api
|
||||
//TODO: fetch?
|
||||
jQuery.ajax( 'api/histories/' + this.get( 'id' )
|
||||
|
||||
).success( function( response ){
|
||||
//this.log( 'historyApiRequest, response:', response );
|
||||
history.set( response );
|
||||
history.log( 'current history state:', history.get( 'state' ),
|
||||
'(was)', oldState,
|
||||
'new size:', history.get( 'nice_size' ) );
|
||||
|
||||
//TODO: revisit this - seems too elaborate, need something straightforward
|
||||
// for each state, check for the difference between old dataset states and new
|
||||
// the goal here is to check ONLY those datasets that have changed states (not all datasets)
|
||||
var changedIds = [];
|
||||
_.each( _.keys( response.state_ids ), function( state ){
|
||||
var diffIds = _.difference( response.state_ids[ state ], oldStateIds[ state ] );
|
||||
// aggregate those changed ids
|
||||
changedIds = changedIds.concat( diffIds );
|
||||
});
|
||||
|
||||
// send the changed ids (if any) to dataset collection to have them fetch their own model changes
|
||||
if( changedIds.length ){
|
||||
history.hdas.update( changedIds );
|
||||
}
|
||||
|
||||
// set up to keep pulling if this history in run/queue state
|
||||
//TODO: magic number here
|
||||
if( ( history.get( 'state' ) === HistoryDatasetAssociation.STATES.RUNNING )
|
||||
|| ( history.get( 'state' ) === HistoryDatasetAssociation.STATES.QUEUED ) ){
|
||||
setTimeout( function(){
|
||||
history.stateUpdater();
|
||||
}, 4000 );
|
||||
}
|
||||
|
||||
}).error( function( xhr, status, error ){
|
||||
if( console && console.warn ){
|
||||
console.warn( 'Error getting history updates from the server:', xhr, status, error );
|
||||
}
|
||||
alert( 'Error getting history updates from the server.\n' + error );
|
||||
});
|
||||
},
|
||||
|
||||
toString : function(){
|
||||
var nameString = ( this.get( 'name' ) )?
|
||||
( ',' + this.get( 'name' ) ) : ( '' );
|
||||
return 'History(' + this.get( 'id' ) + nameString + ')';
|
||||
}
|
||||
});
|
||||
|
||||
//==============================================================================
|
||||
/** A collection of histories (per user or admin)
|
||||
* (stub) currently unused
|
||||
*/
|
||||
var HistoryCollection = Backbone.Collection.extend( LoggableMixin ).extend({
|
||||
model : History,
|
||||
urlRoot : 'api/histories',
|
||||
logger : console
|
||||
});
|
||||
|
||||
//==============================================================================
|
||||
//return {
|
||||
// History : History,
|
||||
// HistoryCollection : HistoryCollection,
|
||||
//};});
|
||||
@@ -0,0 +1,380 @@
|
||||
//define([
|
||||
// "../mvc/base-mvc"
|
||||
//], function(){
|
||||
/* =============================================================================
|
||||
Backbone.js implementation of history panel
|
||||
|
||||
TODO:
|
||||
anon user, mako template init:
|
||||
bug: rename url seems to be wrong url
|
||||
|
||||
logged in, mako template:
|
||||
BUG: meter is not updating RELIABLY on change:nice_size
|
||||
BUG: am able to start upload even if over quota - 'runs' forever
|
||||
bug: quotaMeter bar rendering square in chrome
|
||||
BUG: quotaMsg not showing when 100% (on load)
|
||||
BUG: imported, shared history with unaccessible dataset errs in historycontents when getting history
|
||||
(entire history is inaccessible)
|
||||
??: still happening?
|
||||
|
||||
from loadFromApi:
|
||||
BUG: not showing previous annotations
|
||||
|
||||
fixed:
|
||||
BUG: upload, history size, doesn't change
|
||||
FIXED: using change:nice_size to trigger re-render of history size
|
||||
BUG: delete uploading hda - now in state 'discarded'! ...new state to handle
|
||||
FIXED: handled state
|
||||
BUG: historyItem, error'd ds show display, download?
|
||||
FIXED: removed
|
||||
bug: loading hdas (alt_hist)
|
||||
FIXED: added anon user api request ( trans.user == None and trans.history.id == requested id )
|
||||
bug: quota meter not updating on upload/tool run
|
||||
FIXED: quotaMeter now listens for 'state:ready' from glx_history in alternate_history.mako
|
||||
bug: use of new HDACollection with event listener in init doesn't die...keeps reporting
|
||||
FIXED: change getVisible to return an array
|
||||
BUG: history, broken intial hist state (running, updater, etc.)
|
||||
??: doesn't seem to happen anymore
|
||||
BUG: collapse all should remove all expanded from storage
|
||||
FIXED: hideAllItemBodies now resets storage.expandedItems
|
||||
BUG: historyItem, shouldn't allow tag, annotate, peek on purged datasets
|
||||
FIXED: ok state now shows only: info, rerun
|
||||
BUG: history?, some ids aren't returning encoded...
|
||||
FIXED:???
|
||||
BUG: history, showing deleted ds
|
||||
FIXED
|
||||
UGH: historyItems have to be decorated with history_ids (api/histories/:history_id/contents/:id)
|
||||
FIXED by adding history_id to history_contents.show
|
||||
BUG: history, if hist has err'd ds, hist has perm state 'error', updater on following ds's doesn't run
|
||||
FIXED by reordering history state from ds' states here and histories
|
||||
BUG: history, broken annotation on reload (can't get thru api (sets fine, tho))
|
||||
FIXED: get thru api for now
|
||||
|
||||
replication:
|
||||
show_deleted/hidden:
|
||||
use storage
|
||||
on/off ui
|
||||
move histview fadein/out in render to app?
|
||||
don't draw body until it's first expand event
|
||||
localize all
|
||||
?: render url templates on init or render?
|
||||
?: history, annotation won't accept unicode
|
||||
|
||||
RESTful:
|
||||
move over webui functions available in api
|
||||
delete, undelete
|
||||
update?
|
||||
currently, adding a dataset (via tool execute, etc.) creates a new dataset and refreshes the page
|
||||
provide a means to update the panel via js
|
||||
|
||||
hierarchy:
|
||||
to relational model?
|
||||
HDACollection, meta_files, display_apps, etc.
|
||||
dataset -> hda
|
||||
history -> historyForEditing, historyForViewing
|
||||
display_structured?
|
||||
|
||||
meta:
|
||||
css/html class/id 'item' -> hda
|
||||
add classes, ids on empty divs
|
||||
events (local/ui and otherwise)
|
||||
list in docs as well
|
||||
require.js
|
||||
convert function comments to jsDoc style, complete comments
|
||||
move inline styles into base.less
|
||||
watch the magic strings
|
||||
watch your globals
|
||||
|
||||
feature creep:
|
||||
lineage
|
||||
hide button
|
||||
show permissions in info
|
||||
show shared/sharing status on ds, history
|
||||
maintain scroll position on refresh (storage?)
|
||||
selection, multi-select (and actions common to selected (ugh))
|
||||
searching
|
||||
sorting, re-shuffling
|
||||
|
||||
============================================================================= */
|
||||
/** view for the HDACollection (as per current right hand panel)
|
||||
*
|
||||
*/
|
||||
var HistoryPanel = BaseView.extend( LoggableMixin ).extend({
|
||||
|
||||
// uncomment this out see log messages
|
||||
//logger : console,
|
||||
|
||||
// direct attachment to existing element
|
||||
el : 'body.historyPage',
|
||||
|
||||
// init with the model, urlTemplates, set up storage, bind HDACollection events
|
||||
//NOTE: this will create or load PersistantStorage keyed under 'HistoryView.<id>'
|
||||
//pre: you'll need to pass in the urlTemplates (urlTemplates : { history : {...}, hda : {...} })
|
||||
initialize : function( attributes ){
|
||||
this.log( this + '.initialize:', attributes );
|
||||
|
||||
// set up url templates
|
||||
//TODO: prob. better to put this in class scope (as the handlebars templates), but...
|
||||
// they're added to GalaxyPaths on page load (after this file is loaded)
|
||||
if( !attributes.urlTemplates ){ throw( 'HDAView needs urlTemplates on initialize' ); }
|
||||
if( !attributes.urlTemplates.history ){ throw( 'HDAView needs urlTemplates.history on initialize' ); }
|
||||
if( !attributes.urlTemplates.hda ){ throw( 'HDAView needs urlTemplates.hda on initialize' ); }
|
||||
this.urlTemplates = attributes.urlTemplates.history;
|
||||
this.hdaUrlTemplates = attributes.urlTemplates.hda;
|
||||
|
||||
// data that needs to be persistant over page refreshes
|
||||
// (note the key function which uses the history id as well)
|
||||
this.storage = new PersistantStorage(
|
||||
'HistoryView.' + this.model.get( 'id' ),
|
||||
{ expandedHdas : {} }
|
||||
);
|
||||
|
||||
// bind events from the model's hda collection
|
||||
//this.model.bind( 'change', this.render, this );
|
||||
this.model.bind( 'change:nice_size', this.updateHistoryDiskSize, this );
|
||||
|
||||
this.model.hdas.bind( 'add', this.add, this );
|
||||
this.model.hdas.bind( 'reset', this.addAll, this );
|
||||
this.model.hdas.bind( 'all', this.all, this );
|
||||
|
||||
//this.bind( 'all', function(){
|
||||
// this.log( arguments );
|
||||
//}, this );
|
||||
|
||||
// set up instance vars
|
||||
this.hdaViews = {};
|
||||
this.urls = {};
|
||||
},
|
||||
|
||||
add : function( hda ){
|
||||
//console.debug( 'add.' + this, hda );
|
||||
//TODO
|
||||
},
|
||||
|
||||
addAll : function(){
|
||||
//console.debug( 'addAll.' + this );
|
||||
// re render when all hdas are reset
|
||||
this.render();
|
||||
},
|
||||
|
||||
all : function( event ){
|
||||
//console.debug( 'allItemEvents.' + this, event );
|
||||
//...for which to do the debuggings
|
||||
},
|
||||
|
||||
// render the urls for this view using urlTemplates and the model data
|
||||
renderUrls : function( modelJson ){
|
||||
var historyView = this;
|
||||
|
||||
historyView.urls = {};
|
||||
_.each( this.urlTemplates, function( urlTemplate, urlKey ){
|
||||
historyView.urls[ urlKey ] = _.template( urlTemplate, modelJson );
|
||||
});
|
||||
return historyView.urls;
|
||||
},
|
||||
|
||||
// render urls, historyView body, and hdas (if any are shown), fade out, swap, fade in, set up behaviours
|
||||
render : function(){
|
||||
var historyView = this,
|
||||
setUpQueueName = historyView.toString() + '.set-up',
|
||||
newRender = $( '<div/>' ),
|
||||
modelJson = this.model.toJSON(),
|
||||
initialRender = ( this.$el.children().size() === 0 );
|
||||
|
||||
//console.debug( this + '.render, initialRender:', initialRender );
|
||||
|
||||
// render the urls and add them to the model json
|
||||
modelJson.urls = this.renderUrls( modelJson );
|
||||
|
||||
// render the main template, tooltips
|
||||
//NOTE: this is done before the items, since item views should handle theirs themselves
|
||||
newRender.append( HistoryPanel.templates.historyPanel( modelJson ) );
|
||||
newRender.find( '.tooltip' ).tooltip();
|
||||
|
||||
// render hda views (if any and any shown (show_deleted/hidden)
|
||||
if( !this.model.hdas.length
|
||||
|| !this.renderItems( newRender.find( '#' + this.model.get( 'id' ) + '-datasets' ) ) ){
|
||||
// if history is empty or no hdas would be rendered, show the empty message
|
||||
newRender.find( '#emptyHistoryMessage' ).show();
|
||||
}
|
||||
|
||||
// fade out existing, swap with the new, fade in, set up behaviours
|
||||
$( historyView ).queue( setUpQueueName, function( next ){
|
||||
historyView.$el.fadeOut( 'fast', function(){ next(); });
|
||||
});
|
||||
$( historyView ).queue( setUpQueueName, function( next ){
|
||||
// swap over from temp div newRender
|
||||
historyView.$el.html( '' );
|
||||
historyView.$el.append( newRender.children() );
|
||||
|
||||
historyView.$el.fadeIn( 'fast', function(){ next(); });
|
||||
});
|
||||
$( historyView ).queue( setUpQueueName, function( next ){
|
||||
this.log( historyView + ' rendered:', historyView.$el );
|
||||
|
||||
//TODO: ideally, these would be set up before the fade in (can't because of async save text)
|
||||
historyView.setUpBehaviours();
|
||||
|
||||
if( initialRender ){
|
||||
historyView.trigger( 'rendered:initial' );
|
||||
|
||||
} else {
|
||||
historyView.trigger( 'rendered' );
|
||||
}
|
||||
next();
|
||||
});
|
||||
$( historyView ).dequeue( setUpQueueName );
|
||||
return this;
|
||||
},
|
||||
|
||||
// set up a view for each item to be shown, init with model and listeners, cache to map ( model.id : view )
|
||||
renderItems : function( $whereTo ){
|
||||
this.hdaViews = {};
|
||||
var historyView = this,
|
||||
show_deleted = this.model.get( 'show_deleted' ),
|
||||
show_hidden = this.model.get( 'show_hidden' ),
|
||||
visibleHdas = this.model.hdas.getVisible( show_deleted, show_hidden );
|
||||
|
||||
// only render the shown hdas
|
||||
_.each( visibleHdas, function( hda ){
|
||||
var hdaId = hda.get( 'id' ),
|
||||
expanded = historyView.storage.get( 'expandedHdas' ).get( hdaId );
|
||||
historyView.hdaViews[ hdaId ] = new HDAView({
|
||||
model : hda,
|
||||
expanded : expanded,
|
||||
urlTemplates : historyView.hdaUrlTemplates
|
||||
});
|
||||
historyView.setUpHdaListeners( historyView.hdaViews[ hdaId ] );
|
||||
// render it (NOTE: reverse order, newest on top (prepend))
|
||||
//TODO: by default send a reverse order list (although this may be more efficient - it's more confusing)
|
||||
$whereTo.prepend( historyView.hdaViews[ hdaId ].render().$el );
|
||||
});
|
||||
return visibleHdas.length;
|
||||
},
|
||||
|
||||
// set up HistoryView->HDAView listeners
|
||||
setUpHdaListeners : function( hdaView ){
|
||||
var historyView = this;
|
||||
|
||||
// use storage to maintain a list of hdas whose bodies are expanded
|
||||
hdaView.bind( 'toggleBodyVisibility', function( id, visible ){
|
||||
if( visible ){
|
||||
historyView.storage.get( 'expandedHdas' ).set( id, true );
|
||||
} else {
|
||||
historyView.storage.get( 'expandedHdas' ).deleteKey( id );
|
||||
}
|
||||
});
|
||||
|
||||
// rendering listeners
|
||||
hdaView.bind( 'rendered:ready', function(){ historyView.trigger( 'hda:rendered:ready' ); });
|
||||
},
|
||||
|
||||
// set up js/widget behaviours: tooltips,
|
||||
//TODO: these should be either sub-MVs, or handled by events
|
||||
setUpBehaviours : function(){
|
||||
// anon users shouldn't have access to any of these
|
||||
if( !( this.model.get( 'user' ) && this.model.get( 'user' ).email ) ){ return; }
|
||||
|
||||
// annotation slide down
|
||||
var historyAnnotationArea = this.$( '#history-annotation-area' );
|
||||
this.$( '#history-annotate' ).click( function() {
|
||||
if ( historyAnnotationArea.is( ":hidden" ) ) {
|
||||
historyAnnotationArea.slideDown( "fast" );
|
||||
} else {
|
||||
historyAnnotationArea.slideUp( "fast" );
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
// title and annotation editable text
|
||||
//NOTE: these use page scoped selectors - so these need to be in the page DOM before they're applicable
|
||||
async_save_text( "history-name-container", "history-name",
|
||||
this.urls.rename, "new_name", 18 );
|
||||
|
||||
async_save_text( "history-annotation-container", "history-annotation",
|
||||
this.urls.annotate, "new_annotation", 18, true, 4 );
|
||||
},
|
||||
|
||||
// update the history size display (curr. upper right of panel)
|
||||
updateHistoryDiskSize : function(){
|
||||
this.$el.find( '#history-size' ).text( this.model.get( 'nice_size' ) );
|
||||
},
|
||||
|
||||
//TODO: this seems more like a per user message than a history message; IOW, this doesn't belong here
|
||||
showQuotaMessage : function( userData ){
|
||||
var msg = this.$el.find( '#quota-message-container' );
|
||||
//this.log( this + ' showing quota message:', msg, userData );
|
||||
if( msg.is( ':hidden' ) ){ msg.slideDown( 'fast' ); }
|
||||
},
|
||||
|
||||
//TODO: this seems more like a per user message than a history message
|
||||
hideQuotaMessage : function( userData ){
|
||||
var msg = this.$el.find( '#quota-message-container' );
|
||||
//this.log( this + ' hiding quota message:', msg, userData );
|
||||
if( !msg.is( ':hidden' ) ){ msg.slideUp( 'fast' ); }
|
||||
},
|
||||
|
||||
events : {
|
||||
'click #history-collapse-all' : 'hideAllHdaBodies',
|
||||
'click #history-tag' : 'loadAndDisplayTags'
|
||||
},
|
||||
|
||||
// collapse all hda bodies
|
||||
hideAllHdaBodies : function(){
|
||||
_.each( this.hdaViews, function( item ){
|
||||
item.toggleBodyVisibility( null, false );
|
||||
});
|
||||
this.storage.set( 'expandedHdas', {} );
|
||||
},
|
||||
|
||||
// find the tag area and, if initial: (via ajax) load the html for displaying them; otherwise, unhide/hide
|
||||
//TODO: into sub-MV
|
||||
loadAndDisplayTags : function( event ){
|
||||
this.log( this + '.loadAndDisplayTags', event );
|
||||
var tagArea = this.$el.find( '#history-tag-area' ),
|
||||
tagElt = tagArea.find( '.tag-elt' );
|
||||
this.log( '\t tagArea', tagArea, ' tagElt', tagElt );
|
||||
|
||||
// Show or hide tag area; if showing tag area and it's empty, fill it
|
||||
if( tagArea.is( ":hidden" ) ){
|
||||
if( !jQuery.trim( tagElt.html() ) ){
|
||||
var view = this;
|
||||
// Need to fill tag element.
|
||||
$.ajax({
|
||||
//TODO: the html from this breaks a couple of times
|
||||
url: view.urls.tag,
|
||||
error: function() { alert( "Tagging failed" ); },
|
||||
success: function(tag_elt_html) {
|
||||
//view.log( view + ' tag elt html (ajax)', tag_elt_html );
|
||||
tagElt.html(tag_elt_html);
|
||||
tagElt.find(".tooltip").tooltip();
|
||||
tagArea.slideDown("fast");
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// Tag element already filled: show
|
||||
tagArea.slideDown("fast");
|
||||
}
|
||||
|
||||
} else {
|
||||
// Currently shown: Hide
|
||||
tagArea.slideUp("fast");
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
toString : function(){
|
||||
var nameString = this.model.get( 'name' ) || '';
|
||||
return 'HistoryView(' + nameString + ')';
|
||||
}
|
||||
});
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
HistoryPanel.templates = {
|
||||
historyPanel : Handlebars.templates[ 'template-history-historyPanel' ]
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
//return {
|
||||
// HistoryPanel : HistoryPanel
|
||||
//};});
|
||||
@@ -222,97 +222,127 @@ ${h.templates(
|
||||
"template-user-quotaMeter-usage"
|
||||
)}
|
||||
|
||||
##TODO: fix: curr hasta be _after_ h.templates - move somehow
|
||||
##TODO: fix: curr hasta be _after_ h.templates bc these use those templates - move somehow
|
||||
${h.js(
|
||||
"mvc/history",
|
||||
"mvc/dataset/hda-model", "mvc/dataset/hda-edit",
|
||||
"mvc/history/history-model", "mvc/history/history-panel",
|
||||
|
||||
##"mvc/tags", "mvc/annotations",
|
||||
"mvc/user/user-model", "mvc/user/user-quotameter"
|
||||
)}
|
||||
|
||||
<script type="text/javascript">
|
||||
function galaxyPageSetUp(){
|
||||
// moving global functions, objects into Galaxy namespace
|
||||
top.Galaxy = top.Galaxy || {};
|
||||
|
||||
// bad idea from memleak standpoint?
|
||||
top.Galaxy.mainWindow = top.Galaxy.mainWindow || top.frames.galaxy_main;
|
||||
top.Galaxy.toolWindow = top.Galaxy.toolWindow || top.frames.galaxy_tools;
|
||||
top.Galaxy.historyWindow = top.Galaxy.historyWindow || top.frames.galaxy_history;
|
||||
|
||||
top.Galaxy.$masthead = top.Galaxy.$masthead || $( top.document ).find( 'div#masthead' );
|
||||
top.Galaxy.$messagebox = top.Galaxy.$messagebox || $( top.document ).find( 'div#messagebox' );
|
||||
top.Galaxy.$leftPanel = top.Galaxy.$leftPanel || $( top.document ).find( 'div#left' );
|
||||
top.Galaxy.$centerPanel = top.Galaxy.$centerPanel || $( top.document ).find( 'div#center' );
|
||||
top.Galaxy.$rightPanel = top.Galaxy.$rightPanel || $( top.document ).find( 'div#right' );
|
||||
|
||||
//modals
|
||||
// other base functions
|
||||
|
||||
// global backbone models
|
||||
top.Galaxy.currUser = top.Galaxy.currUser;
|
||||
top.Galaxy.currHistoryPanel = top.Galaxy.currHistoryPanel;
|
||||
top.Galaxy.historyPanels = top.Galaxy.historyPanels || [];
|
||||
|
||||
top.Galaxy.paths = galaxy_paths;
|
||||
|
||||
window.Galaxy = top.Galaxy;
|
||||
}
|
||||
|
||||
// set js localizable strings
|
||||
GalaxyLocalization.setLocalizedString( ${ create_localization_json( get_page_localized_strings() ) } );
|
||||
|
||||
// add needed controller urls to GalaxyPaths
|
||||
galaxy_paths.set( 'hda', ${get_hda_url_templates()} );
|
||||
galaxy_paths.set( 'history', ${get_history_url_templates()} );
|
||||
//console.debug( 'galaxy_paths:', galaxy_paths );
|
||||
|
||||
$(function(){
|
||||
galaxyPageSetUp();
|
||||
Galaxy.historyFrame = window;
|
||||
|
||||
// ostensibly, this is the App
|
||||
if( console && console.debug ){
|
||||
//if( console.clear ){ console.clear(); }
|
||||
console.debug( 'using backbone.js in history panel' );
|
||||
console.pretty = function( o ){ $( '<pre/>' ).text( JSON.stringify( o, null, ' ' ) ).appendTo( 'body' ); }
|
||||
top.storage = jQuery.jStorage
|
||||
}
|
||||
|
||||
|
||||
// load initial data in this page - since we're already sending it...
|
||||
// LOAD INITIAL DATA IN THIS PAGE - since we're already sending it...
|
||||
// ...use mako to 'bootstrap' the models
|
||||
var user = ${ get_current_user() },
|
||||
history = ${ get_history( history.id ) },
|
||||
hdas = ${ get_hdas( history.id, datasets ) };
|
||||
//console.debug( 'user:', user );
|
||||
//console.debug( 'history:', history );
|
||||
//console.debug( 'hdas:', hdas );
|
||||
var currUser = new User( user );
|
||||
if( !Galaxy.currUser ){ Galaxy.currUser = currUser; }
|
||||
|
||||
// i don't like this relationship, but user authentication changes views/behaviour
|
||||
// add user data to history
|
||||
// i don't like this history+user relationship, but user authentication changes views/behaviour
|
||||
history.user = user;
|
||||
// is page sending in show settings? if so override history's
|
||||
//TODO: move into historyPanel
|
||||
history.show_deleted = ${ 'true' if show_deleted else 'false' };
|
||||
history.show_hidden = ${ 'true' if show_hidden else 'false' };
|
||||
|
||||
//console.debug( 'galaxy_paths:', galaxy_paths );
|
||||
var glx_history = new History( history, hdas );
|
||||
glx_history.logger = console;
|
||||
|
||||
var glx_history_view = new HistoryView({
|
||||
model: glx_history,
|
||||
var historyPanel = new HistoryPanel({
|
||||
model : new History( history, hdas ),
|
||||
urlTemplates: galaxy_paths.attributes,
|
||||
logger: console
|
||||
logger : console
|
||||
});
|
||||
glx_history_view.render();
|
||||
|
||||
|
||||
// ...OR load from the api
|
||||
//var glx_history = new History().setPaths( galaxy_paths ),
|
||||
// glx_history_view = new HistoryView({ model: glx_history });
|
||||
//console.warn( 'fetching' );
|
||||
//glx_history.loadFromApi( pageData.history.id );
|
||||
historyPanel.render();
|
||||
if( !Galaxy.currHistoryPanel ){ Galaxy.currHistoryPanel = historyPanel; }
|
||||
if( !( historyPanel in Galaxy.historyPanels ) ){ Galaxy.historyPanels.unshift( historyPanel ); }
|
||||
|
||||
|
||||
// quota meter is a cross-frame ui element (meter in masthead, over quota message in history)
|
||||
// ...or LOAD FROM THE API
|
||||
//historyPanel = new HistoryView({ model: new History().setPaths( galaxy_paths ) });
|
||||
//historyPanel.loadFromApi( pageData.history.id );
|
||||
|
||||
|
||||
// QUOTA METER is a cross-frame ui element (meter in masthead, over quota message in history)
|
||||
// create it and join them here for now (via events)
|
||||
window.currUser = new User( user );
|
||||
//TODO: this really belongs in the masthead
|
||||
|
||||
//window.currUser.logger = console;
|
||||
window.quotaMeter = new UserQuotaMeter({ model: currUser, el: $( top.document ).find( '.quota-meter-container' ) });
|
||||
window.quotaMeter.render();
|
||||
//window.quotaMeter.logger = console;
|
||||
var quotaMeter = new UserQuotaMeter({
|
||||
model : currUser,
|
||||
el : $( top.document ).find( '.quota-meter-container' )
|
||||
});
|
||||
//quotaMeter.logger = console; window.quotaMeter = quotaMeter
|
||||
quotaMeter.render();
|
||||
|
||||
// show/hide the 'over quota message' in the history when the meter tells it to
|
||||
quotaMeter.bind( 'quota:over', glx_history_view.showQuotaMessage, glx_history_view );
|
||||
quotaMeter.bind( 'quota:under', glx_history_view.hideQuotaMessage, glx_history_view );
|
||||
quotaMeter.bind( 'quota:over', historyPanel.showQuotaMessage, historyPanel );
|
||||
quotaMeter.bind( 'quota:under', historyPanel.hideQuotaMessage, historyPanel );
|
||||
// having to add this to handle re-render of hview while overquota (the above do not fire)
|
||||
glx_history_view.on( 'rendered', function(){
|
||||
if( window.quotaMeter.isOverQuota() ){
|
||||
glx_history_view.showQuotaMessage();
|
||||
historyPanel.on( 'rendered', function(){
|
||||
if( quotaMeter.isOverQuota() ){
|
||||
historyPanel.showQuotaMessage();
|
||||
}
|
||||
});
|
||||
//TODO: this _is_ sent to the page (over_quota)...
|
||||
|
||||
// update the quota meter when current history changes size
|
||||
glx_history.bind( 'change:nice_size', function(){
|
||||
window.quotaMeter.update()
|
||||
}, window.quotaMeter );
|
||||
historyPanel.model.bind( 'change:nice_size', function(){
|
||||
quotaMeter.update()
|
||||
}, quotaMeter );
|
||||
|
||||
|
||||
if( console && console.debug ){
|
||||
window.user = top.user = user;
|
||||
window._history = top._history = history;
|
||||
window.hdas = top.hdas = hdas;
|
||||
window.glx_history = top.glx_history = glx_history;
|
||||
window.glx_history_view = top.glx_history_view = glx_history_view;
|
||||
top.storage = jQuery.jStorage
|
||||
}
|
||||
|
||||
return;
|
||||
});
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user