(merge dev)

This commit is contained in:
carlfeberhard
2015-10-23 12:17:32 -04:00
1449 changed files with 52508 additions and 42480 deletions
+17
View File
@@ -0,0 +1,17 @@
.venv/
client/
database/
doc/patch.py
doc/source/conf.py
lib/galaxy/util/jstree.py
lib/pkg_resources.py
scripts/api/
scripts/data_libraries/
scripts/loc_files/
scripts/microbes/
scripts/others/
scripts/scramble/
scripts/tool_shed/
scripts/tools/
scripts/transfer.py
tools/
+2 -6
View File
@@ -2,11 +2,7 @@
set -e
# Run broad range of checks over specific white-listed files, this uses
# the default checks defined in setup.cfg.
while read p; do
flake8 $(eval echo "$p")
done <.ci/pep8_sources.txt
flake8 --exclude `paste -sd, .ci/flake8_blacklist.txt` .
# Look for obviously broken stuff lots more places.
flake8 --select=E901,E902,F821,F822,F823,F831 --exclude lib/galaxy/util/pastescript/serve.py,lib/pkg_resources.py lib/ test/{api,unit}
flake8 --select=E901,E902,F821,F822,F823,F831 --exclude lib/pkg_resources.py contrib/ lib/
-49
View File
@@ -1,49 +0,0 @@
cron/parse_builds.py
lib/galaxy/auth
lib/galaxy/config.py
lib/galaxy/datatypes/{data,proteomics}.py
lib/galaxy/datatypes/converters/{bedgraph_to_array_tree_converter,wiggle_to_array_tree_converter}.py
lib/galaxy/exceptions/error_codes.py
lib/galaxy/jobs/{__init__,error_level,manager,stock_rules}.py
lib/galaxy/main.py
lib/galaxy/managers/{api_keys,citations,collections,collections_util,datasets,folders,pages,tags,workflows}.py
lib/galaxy/model/orm/{engine_factory,now}.py
lib/galaxy/model/{base,custom_types,search,util}.py
lib/galaxy/model/tool_shed_install/__init__.py
lib/galaxy/objectstore/{__init__,pulsar,rods,s3}.py
lib/galaxy/openid/__init__.py
lib/galaxy/queues.py
lib/galaxy/queue_worker.py
lib/galaxy/tags
lib/galaxy/tools
lib/galaxy/util/{__init__,json,permutations,plugin_config,properties,simplegraph,sockets,sleeper,streamball,submodules,xml_macros}.py
lib/galaxy/visualization/data_providers/{__init__,genome,registry}.py
lib/galaxy/web/{proxy,security}
lib/galaxy/web/base/{__init__,interactive_environments}.py
lib/galaxy/web/formatting.py
lib/galaxy/webapps/__init__.py
lib/galaxy/webapps/galaxy/__init__.py
lib/galaxy/webapps/galaxy/api/{__init__,authenticate,dataset_collections,datatypes,folder_contents,folders,lda_datasets,job_files,libraries,library_contents,provenance,remote_files,search,users}.py
lib/galaxy/webapps/galaxy/controllers/{__init__,admin,cloudlaunch,workflow}.py
lib/galaxy/webapps/reports/app.py
lib/galaxy/webapps/reports/controllers/{__init__,jobs,query,workflows}.py
lib/galaxy/webapps/tool_shed/app.py
lib/galaxy/webapps/tool_shed/controllers/__init__.py
lib/galaxy/webapps/tool_shed/framework/__init__.py
lib/galaxy/webapps/tool_shed/framework/middleware/__init__.py
lib/galaxy/webapps/tool_shed/util/{__init__,shed_statistics}.py
lib/galaxy/work
lib/galaxy/workflow
lib/galaxy/version.py
lib/pulsar
lib/tool_shed/__init__.py
lib/tool_shed/galaxy_install/{install_manager,repair_repository_manager}.py
lib/tool_shed/galaxy_install/repository_dependencies
lib/tool_shed/galaxy_install/tools/{__init__,tool_panel_manager}.py
lib/tool_shed/repository_types/{__init__,registry,unrestricted}.py
lib/tool_shed/util/{__init__,repository_content_util,tool_util,web_util,workflow_util}.py
test/functional/tools
test/unit/jobs/test_job_configuration.py
test/unit/tools
tools/data_source/upload.py
tools/evolution/codingSnps_filter.py
+1
View File
@@ -76,6 +76,7 @@ tool-data/shared/igv/igv_build_sites.txt
tool-data/shared/rviewer/rviewer_build_sites.txt
tool-data/shared/ncbi/builds.txt
tool-data/shared/ucsc/builds.txt
tool-data/shared/ucsc/manual_builds.txt
tool-data/shared/ensembl/builds.txt
tool-data/shared/ucsc/publicbuilds.txt
tool-data/shared/ucsc/ucsc_build_sites.txt
+99
View File
@@ -0,0 +1,99 @@
Galaxy Project Code of Conduct
==============================
This code of conduct outlines our expectations for participants within the
Galaxy community, as well as steps to reporting unacceptable behavior. We are
committed to providing a welcoming and inspiring community for all and expect
our code of conduct to be honored. Anyone who violates this code of conduct may
be banned from the community.
Our open source community strives to:
* **Be friendly and patient.**
* **Be welcoming**: We strive to be a community that welcomes and
supports people of all backgrounds and identities. This includes, but is not
limited to members of any race, ethnicity, culture, national origin, colour,
immigration status, social and economic class, educational level, sex, sexual
orientation, gender identity and expression, age, size, family status,
political belief, religion, and mental and physical ability.
* **Be considerate**: Your work will be used by other people, and you in turn
will depend on the work of others. Any decision you take will affect users
and colleagues, and you should take those consequences into account when
making decisions. Remember that we're a world-wide community, so you might
not be communicating in someone else's primary language.
* **Be respectful**: Not all of us will agree all the time, but disagreement is
no excuse for poor behavior and poor manners. We might all experience some
frustration now and then, but we cannot allow that frustration to turn into a
personal attack. It’s important to remember that a community where people
feel uncomfortable or threatened is not a productive one.
* **Be careful in the words that we choose**: We are a community of
professionals, and we conduct ourselves professionally. Be kind to others. Do
not insult or put down other participants. Harassment and other exclusionary
behavior aren't acceptable. This includes, but is not limited to: Violent
threats or language directed against another person, Discriminatory jokes and
language, Posting sexually explicit or violent material, Posting (or
threatening to post) other people’s personally identifying information
(“doxing”), Personal insults, especially those using racist or sexist terms,
Unwelcome sexual attention, Advocating for, or encouraging, any of the above
behavior, Repeated harassment of others. In general, if someone asks you to
stop, then stop.
* **Try to understand why we disagree**: Disagreements, both social and
technical, happen all the time. It is important that we resolve disagreements
and differing views constructively. Remember that we’re different. Diversity
contributes to the strength of our community, which is composed of people
from a wide range of backgrounds. Different people have different
perspectives on issues. Being unable to understand why someone holds a
viewpoint doesn’t mean that they’re wrong. Don’t forget that it is human to
err and blaming each other doesn’t get us anywhere. Instead, focus on helping
to resolve issues and learning from mistakes.
### Diversity Statement
We encourage everyone to participate and are committed to building a community
for all. Although we will fail at times, we seek to treat everyone both as
fairly and equally as possible. Whenever a participant has made a mistake, we
expect them to take responsibility for it. If someone has been harmed or
offended, it is our responsibility to listen carefully and respectfully, and do
our best to right the wrong.
Although this list cannot be exhaustive, we explicitly honor diversity in age,
gender, gender identity or expression, culture, ethnicity, language, national
origin, political beliefs, profession, race, religion, sexual orientation,
socioeconomic status, and technical ability. We will not tolerate
discrimination based on any of the protected characteristics above, including
participants with disabilities.
### Reporting Issues
If you experience or witness unacceptable behavior, or have any other concerns,
please report it by contacting Dave Clements (clementsgalaxy@gmail.com). To
report an issue involving Dave Clements please email James Taylor
(james@taylorlab.org). All reports will be handled with discretion. In your
report please include:
- Your contact information.
- Names (real, nicknames, or pseudonyms) of any individuals involved. If there
are additional witnesses, please include them as well. Your account of what
occurred, and if you believe the incident is ongoing. If there is a publicly
available record (e.g. a mailing list archive or a public IRC logger), please
include a link.
- Any additional information that may be helpful.
After filing a report, a representative will contact you personally, review the
incident, follow up with any additional questions, and make a decision as to
how to respond. If the person who is harassing you is part of the response
team, they will recuse themselves from handling your incident. If the complaint
originates from a member of the response team, it will be handled by a
different member of the response team. We will respect confidentiality requests
for the purpose of protecting victims of abuse.
### Attribution & Acknowledgements
This code of conduct is based on the Open Code of Conduct from the TODOGroup.
+40 -94
View File
@@ -1,5 +1,6 @@
# Contributing
Galaxy welcomes new development!
This document briefly describes how to contribute to the [core
galaxy project](https://github.com/galaxyproject/galaxy) -
also checkout our 2013 Galaxy Community
@@ -13,37 +14,53 @@ deeper discussion of some of these points - please see the
## Before you Begin
If you have an idea for a feature to add or an approach for a bugfix -
If you have an idea for a feature to add or an approach for a bugfix,
it is best to communicate with Galaxy developers early. The most
common venue for this is the [Galaxy Trello
board](https://wiki.galaxyproject.org/Issues). Browse through existing
cards [here](http://bit.ly/gxytrello) and if one seems related comment
on it. If no existing cards seem appropriate, a new issue can be
opened using [this form](http://galaxyproject.org/trello). Galaxy
developers are also generally available via
common venues for this are
[GitHub issues](https://github.com/galaxyproject/galaxy/issues) and the
[Galaxy and Tool Shed Trello boards](https://wiki.galaxyproject.org/Issues).
Browse through existing GitHub issues and Trello cards and if one seems related,
comment on it. We also maintain a [card](https://trello.com/c/eFdPIdIB) with
links to smaller issues we believe would make the best entry points for new
developers.
Galaxy developers are generally available via
[IRC](https://wiki.galaxyproject.org/GetInvolved#IRC_Channel) and on
the [development mailing list](http://dev.list.galaxyproject.org/).
## Reporting a new issue
If no existing Galaxy issue/Trello card seems appropriate, a new issue can be
opened using [this form](https://github.com/galaxyproject/galaxy/issues/new).
## How to Contribute
* All changes to the [core galaxy project](https://github.com/galaxyproject/galaxy)
should be made through pull requests to this repository (with just two
exceptions outlined below).
* If you are new to Git - please check out the very useful tutorials on the [GitHub training hub](https://training.github.com/kit/)
* If you are new to Git, the [Try Git](http://try.github.com/) tutorial is a good places to start.
More learning resources are listed at https://help.github.com/articles/good-resources-for-learning-git-and-github/ .
* Make sure you have a free [GitHub account](https://github.com/)
* Make sure you have a free [GitHub](https://github.com/) account.
* Fork the galaxy repository on
[GitHub](https://github.com/galaxyproject/galaxy) to make your changes.
* Fork the [galaxy repository](https://github.com/galaxyproject/galaxy) on
GitHub to make your changes.
(While many Galaxy instances track
[galaxy-dist](https://bitbucket.org/galaxy/galaxy-dist), active development
happens in the galaxy GitHub repository and this is where pull requests
should be made).
To keep your copy up to date with respect to the main repository, you need to
frequently [sync your fork](https://help.github.com/articles/syncing-a-fork/):
```
$ git remote add upstream https://github.com/galaxyproject/galaxy
$ git fetch upstream
$ git checkout dev
$ git merge upstream/dev
```
* Choose the correct branch to develop your changes against.
* Additions to the code base should be pushed to the `dev` branch (`git
* Additions of new features to the code base should be pushed to the `dev` branch (`git
checkout dev`).
* Most bug fixes to previously release components (things in galaxy-dist)
@@ -51,7 +68,7 @@ the [development mailing list](http://dev.list.galaxyproject.org/).
* Serious security problems should not be fixed via pull request - please
responsibly disclose these by e-mailing them (with or without patches) to
galaxy-lab@lists.galaxyproject.org. The Galaxy core development team will
galaxy-committers@lists.galaxyproject.org . The Galaxy core development team will
issue patches to public servers before announcing the issue to ensure there
is time to patch and highlight these fixes widely. We will provide you
credit for the discovery when publicly disclosing the issue.
@@ -68,7 +85,8 @@ the [development mailing list](http://dev.list.galaxyproject.org/).
and run any that seem relevant. Developers reviewing your pull
request will be happy to help guide you to running the most relevant
tests as part of the pull request review process and may request the
output of these tests.
output of these tests. You can run the continuous integration tests locally
using `tox`, example: `tox -e py27-lint,py27-unit`.
* Commit and push your changes to your
[fork](https://help.github.com/articles/pushing-to-a-remote/).
@@ -77,23 +95,22 @@ the [development mailing list](http://dev.list.galaxyproject.org/).
request](https://help.github.com/articles/creating-a-pull-request/)
with these changes. You pull request message ideally should include:
* A description of why the change should be made.
* A description of why the changes should be made.
* A description of implementation of the change.
* A description of the implementation of the changes.
* A description of how to test the change.
* A description of how to test the changes.
## Ideas
* The pull request should pass all the continuous integration tests which are
automatically run by GitHub using e.g. Travis CI.
Galaxy's [Trello board](http://bit.ly/gxytrello) is filled with bugs and ideas
for enhancements, but we maintain a [card](https://trello.com/c/eFdPIdIB) with
links to smaller issues we believe would make the best entry points for new
developers.
* Your pull request will be handled according to
[some rules](doc/source/project/organization.rst#handling-pull-requests).
## A Quick Note about Tools
For the most part, Galaxy tools should be published to the
[ToolShed](https://wiki.galaxyproject.org/ToolShed) and not in this
[Tool Shed](https://wiki.galaxyproject.org/ToolShed) and not in this
repository directly. If you are looking to supply fixes for migrated
core tools that used to exist in this repository - please checkout
the [tools-devteam](https://github.com/galaxyproject/tools-devteam)
@@ -101,74 +118,3 @@ developers.
More information about tool development can be found [on the
wiki](https://wiki.galaxyproject.org/Develop).
## Handling Pull Requests
Everyone is encouraged to express opinions and issue non-binding votes on pull
requests, but only members of the *committers* group may issue binding votes
on pull requests. Information on the *committers* group can be found in the
[organization document](https://github.com/galaxyproject/galaxy/blob/dev/doc/source/project/organization.rst)
describing governance of the core Galaxy code base.
Votes on pull requests should take the form of
[+1, 0, -1, and fractions](http://www.apache.org/foundation/voting.html)
as outlined by the Apache Foundation.
Pull requests modifying pre-existing releases should be restricted to bug fixes
and require at least 2 *+1* binding votes from someone other than the author of
the pull request with no *-1* binding votes.
Pull requests changing or clarifying the procedures governing this repository:
- Must be made to the ``dev`` branch of this repository.
- Must remain open for at least 192 hours (unless every qualified committer has
voted).
- Require binding *+1* votes from at least 25% of qualified *committers* with no
*-1* binding votes.
- Should be titled with the prefix *[PROCEDURES]* and tagged with
the *procedures* tag in Github.
- Should not be modified once open. If changes are needed, the pull request
should be closed, re-opened with modifications, and votes reset.
- Should be restricted to just modifying the procedures and generally should not
contain code modifications.
- If the pull request adds or removes committers, there must be a separate
pull request for each person added or removed.
Any other pull request requires at least 1 *+1* binding vote from someone other
than the author of the pull request.
### Timelines
Except in the case of pull requests modifying governance procedures, there are
generally no objective guidelines defining how long pull requests must remain
open for comment. Subjectively speaking though - larger and more potentially
controversial pull requests containing enhancements should remain open for a at
least a few days to give everyone the opportunity to weigh in.
### Vetos
A note on vetos (*-1* votes) taken verbatim from the
[Apache Foundation](http://www.apache.org/foundation/voting.html):
>"A code-modification proposal may be stopped dead in its tracks by a -1 vote
by a qualified voter. This constitutes a veto, and it cannot be overruled nor
overridden by anyone. Vetos stand until and unless withdrawn by their casters.
>
>To prevent vetos from being used capriciously, they must be accompanied by a
technical justification showing why the change is bad (opens a security
exposure, negatively affects performance, etc. ). A veto without a
justification is invalid and has no weight."
For votes regarding non-coding issues such as procedure changes, the requirement
that a veto is accompanied by a *technical* justification is relaxed somewhat,
though a well reasoned justification must still be included.
### Direct Commit Access
The Galaxy *committers* group may only commit directly to Galaxy (i.e. outside
of a pull request and not following the procedures described here) the
following two categories of patches:
* Patches for serious security vulnerabilities.
* Cherry-picking and/or merging of existing approved commits to other
branches.
+8 -42
View File
@@ -1,20 +1,23 @@
.. figure:: https://wiki.galaxyproject.org/Images/GalaxyLogo?action=AttachFile&do=get&target=galaxy_project_logo.jpg
:alt: Galaxy Logo
The latest information about Galaxy is available via `http://galaxyproject.org/ <http://galaxyproject.org/>`__
The latest information about Galaxy is available via `https://galaxyproject.org/ <https://galaxyproject.org/>`__
.. image:: https://img.shields.io/badge/questions-galaxy%20biostar-blue.svg
:target: https://biostar.usegalaxy.org
:alt: Ask a question
.. image:: https://img.shields.io/badge/chat-irc.freenode.net%23galaxyproject-blue.svg
:target: https://webchat.freenode.net/?channels=galaxyproject
:alt: Chat with us
.. image:: https://readthedocs.org/projects/galaxy/badge/?version=master
:target: http://galaxy.readthedocs.org/en/master/
:target: https://galaxy.readthedocs.org/en/master/
:alt: Documentation Status
.. image:: https://travis-ci.org/galaxyproject/galaxy.svg?branch=dev
:target: https://travis-ci.org/galaxyproject/galaxy
:alt: Inspect the test results
Galaxy Quickstart
=================
@@ -49,44 +52,7 @@ Not all dependencies are included for the tools provided in the sample
https://wiki.galaxyproject.org/Admin/Tools/ToolDependencies
Issues
======
Issues can be submitted to trello via the `galaxyproject
website <http://galaxyproject.org/trello/>`__ and viewed on the `Galaxy
Trello Board <https://trello.com/b/75c1kASa/galaxy-development>`__.
Galaxy Development
==================
Galaxy welcomes new development! There is extensive documentation on developing
with Galaxy on the `wiki <https://wiki.galaxyproject.org/Develop>`__.
Source Repository
-----------------
Galaxy development has moved to `Github
<https://github.com/galaxyproject/galaxy>`__.
**Developers** should develop against the ``dev`` branch in the Github
repository.
**Deployers** can continue to pull Galaxy changes from Bitbucket without
interruption. Alternatively, deployers can track the ``master`` branch in
Github.
Syncing a Fork
--------------
If you fork Galaxy to work on it, you may be interested in keeping your copy
up to date with respect to the main repository. Github has `good documentation
<https://help.github.com/articles/syncing-a-fork/>`__ on this.
.. code:: console
$ git remote add upstream https://github.com/galaxyproject/galaxy
$ git fetch upstream
$ git checkout dev
$ git merge upstream/dev
Issues and Galaxy Development
=============================
Please see `CONTRIBUTING.md <CONTRIBUTING.md>`_ .
+2 -1
View File
@@ -22,5 +22,6 @@ module.exports = function(grunt) {
// see the sub directory grunt-tasks/ for individual task definitions
grunt.loadTasks( 'grunt-tasks' );
grunt.registerTask( 'default', [ 'uglify' ] );
// note: 'handlebars' *not* 'templates' since handlebars doesn't call uglify
grunt.registerTask( 'default', [ 'handlebars', 'uglify' ] );
};
+13
View File
@@ -33,6 +33,19 @@ This will:
2. generate source maps and place them in static/maps
Templates
=========
You can change and recompile the templates by using:
grunt templates
This will:
1. recompile the templates in client/galaxy/scripts/templates to client/galaxy/scripts/templates/compiled
2. minify and generate source maps for the compiled templates
Changing Styles/CSS
===================
+3 -3
View File
@@ -9,7 +9,7 @@
"homepage": "usegalaxy.org",
"dependencies": {
"jquery": "~1.11.1",
"traceKit": "*",
"tracekit": "*",
"ravenjs": "~1.1.16",
"underscore": "~1.7.0",
"backbone": "~1.1.2",
@@ -18,7 +18,7 @@
"farbtastic": "~2.0.0-alpha.1",
"toastr": "~2.1.0",
"jQTouch": "git://github.com/senchalabs/jQTouch#~1.0.0",
"bib2json": "git://github.com/mayanklahiri/bib2json",
"bib2json": "git://github.com/galaxyproject/bib2json#galaxy",
"jquery-form": "~3.46.0",
"jquery-autocomplete": "git://github.com/dyve/jquery-autocomplete",
"select2": "~3.5.2",
@@ -30,7 +30,7 @@
"jstree": "~3.0.9",
"jquery-ui": "git://github.com/jquery/jquery-ui.git#~1.11.2",
"threedubmedia.jquery.event": "*",
"handlebars": "~2.0.0",
"handlebars": "~3.0.0",
"jquery-migrate": "~1.2.1",
"requirejs": "~2.1.17"
},
-31
View File
@@ -138,40 +138,9 @@ GalaxyApp.prototype._setUpListeners = function _setUpListeners(){
};
//TODO:?? we might somehow manage to *retry* ajax using either this hook or Backbone.sync
});
self._listenToGalaxyMain();
return self;
};
/** trigger an event when the main frame loads a new page */
GalaxyApp.prototype._listenToGalaxyMain = function _listenToGalaxyMain(){
var self = this,
selector = 'iframe#galaxy_main',
galaxy_main = $( selector );
galaxy_main = galaxy_main.size()? galaxy_main.get( 0 ).contentWindow : undefined ;
if( galaxy_main ){
// TODO: this would be better external to this function so it can be removed
var handler = function __galaxyMainOnLoadHandler( ev ){
try {
var _galaxy_main = $( selector ).get( 0 ).contentWindow,
full = _galaxy_main.location.pathname + _galaxy_main.location.search + _galaxy_main.location.hash;
self.trigger( 'galaxy_main:load', {
fullpath: full,
pathname: _galaxy_main.location.pathname,
search : _galaxy_main.location.search,
hash : _galaxy_main.location.hash
});
} catch( err ){
self.debug( 'Error handling main frame load:', err );
}
};
$( selector ).on( 'load', handler );
} else {
self.debug( 'No galaxy_main found' );
}
};
/** string rep */
GalaxyApp.prototype.toString = function toString(){
var userEmail = this.user.get( 'email' ) || '(anonymous)';
+5 -2
View File
@@ -97,6 +97,9 @@ var GalaxyLibrary = Backbone.View.extend({
this.library_router = new LibraryRouter();
this.library_router.on( 'route:libraries', function() {
if ( Galaxy.libraries.libraryToolbarView){
Galaxy.libraries.libraryToolbarView.$el.unbind('click');
}
Galaxy.libraries.libraryToolbarView = new mod_librarytoolbar_view.LibraryToolbarView();
Galaxy.libraries.libraryListView = new mod_librarylist_view.LibraryListView();
});
@@ -106,7 +109,7 @@ var GalaxyLibrary = Backbone.View.extend({
Galaxy.libraries.libraryToolbarView = new mod_librarytoolbar_view.LibraryToolbarView();
Galaxy.libraries.libraryListView = new mod_librarylist_view.LibraryListView( { show_page: show_page } );
} else {
Galaxy.libraries.libraryListView.render( { show_page: show_page } )
Galaxy.libraries.libraryListView.render( { show_page: show_page } );
}
});
@@ -123,7 +126,7 @@ var GalaxyLibrary = Backbone.View.extend({
Galaxy.libraries.folderToolbarView = new mod_foldertoolbar_view.FolderToolbarView( {id: id} );
Galaxy.libraries.folderListView = new mod_folderlist_view.FolderListView( { id: id, show_page: show_page } );
} else {
Galaxy.libraries.folderListView.render( { id: id, show_page: parseInt( show_page ) } )
Galaxy.libraries.folderListView.render( { id: id, show_page: parseInt( show_page ) } );
}
});
+135 -156
View File
@@ -1,91 +1,84 @@
// dependencies
define(["galaxy.masthead"], function(mod_masthead) {
define(['galaxy.masthead'], function( Masthead ) {
/** GalaxyMenu uses the GalaxyMasthead class in order to add menu items and icons to the Masthead **/
var GalaxyMenu = Backbone.Model.extend({
// options
options: null,
// link masthead class
masthead: null,
// initialize
initialize: function(options) {
initialize: function( options ) {
this.options = options.config;
this.masthead = options.masthead;
this.create();
},
// default menu
create: function(){
create: function() {
//
// Analyze data tab.
//
var tab_analysis = new mod_masthead.GalaxyMastheadTab({
id : "analysis",
title : "Analyze Data",
content : "",
var tab_analysis = new Masthead.GalaxyMastheadTab({
id : 'analysis',
title : 'Analyze Data',
content : '',
title_attribute : 'Analysis home view'
});
this.masthead.append(tab_analysis);
this.masthead.append( tab_analysis );
//
// Workflow tab.
//
var workflow_options = {
id : "workflow",
title : "Workflow",
content : "workflow",
id : 'workflow',
title : 'Workflow',
content : 'workflow',
title_attribute : 'Chain tools into workflows'
}
if (!this.options.user.valid){
if ( !Galaxy.user.id ) {
workflow_options.disabled = true; // disable workflows for anonymous users
}
var tab_workflow = new mod_masthead.GalaxyMastheadTab(workflow_options);
this.masthead.append(tab_workflow);
var tab_workflow = new Masthead.GalaxyMastheadTab( workflow_options );
this.masthead.append( tab_workflow );
//
// 'Shared Items' or Libraries tab.
//
var tab_shared = new mod_masthead.GalaxyMastheadTab({
id : "shared",
title : "Shared Data",
content : "library/index",
var tab_shared = new Masthead.GalaxyMastheadTab({
id : 'shared',
title : 'Shared Data',
content : 'library/index',
title_attribute : 'Access published resources'
});
tab_shared.add({
title : "Data Libraries",
content : "library/index"
title : 'Data Libraries',
content : 'library/index'
});
tab_shared.add({
title : "Data Libraries Beta",
content : "library/list",
title : 'Data Libraries Beta',
content : 'library/list',
divider : true
});
tab_shared.add({
title : "Published Histories",
content : "history/list_published"
title : 'Published Histories',
content : 'history/list_published'
});
tab_shared.add({
title : "Published Workflows",
content : "workflow/list_published"
title : 'Published Workflows',
content : 'workflow/list_published'
});
tab_shared.add({
title : "Published Visualizations",
content : "visualization/list_published"
title : 'Published Visualizations',
content : 'visualization/list_published'
});
tab_shared.add({
title : "Published Pages",
content : "page/list_published"
title : 'Published Pages',
content : 'page/list_published'
});
this.masthead.append(tab_shared);
@@ -93,25 +86,24 @@ var GalaxyMenu = Backbone.Model.extend({
//
// Lab menu.
//
if (this.options.user.requests)
{
var tab_lab = new mod_masthead.GalaxyMastheadTab({
id : "lab",
title : "Lab"
if ( this.options.user_requests ) {
var tab_lab = new Masthead.GalaxyMastheadTab({
id : 'lab',
title : 'Lab'
});
tab_lab.add({
title : "Sequencing Requests",
content : "requests/index"
title : 'Sequencing Requests',
content : 'requests/index'
});
tab_lab.add({
title : "Find Samples",
content : "requests/find_samples_index"
title : 'Find Samples',
content : 'requests/find_samples_index'
});
tab_lab.add({
title : "Help",
title : 'Help',
content : this.options.lims_doc_url
});
this.masthead.append(tab_lab);
this.masthead.append( tab_lab );
}
//
@@ -119,219 +111,206 @@ var GalaxyMenu = Backbone.Model.extend({
//
var visualization_options = {
id : "visualization",
title : "Visualization",
content : "visualization/list",
id : 'visualization',
title : 'Visualization',
content : 'visualization/list',
title_attribute : 'Visualize datasets'
}
if (!this.options.user.valid){
visualization_options.disabled = true; // disable visualizations for anonymous users
// disable visualizations for anonymous users
if ( !Galaxy.user.id ) {
visualization_options.disabled = true;
}
var tab_visualization = new Masthead.GalaxyMastheadTab( visualization_options );
var tab_visualization = new mod_masthead.GalaxyMastheadTab(visualization_options);
if (this.options.user.valid){ //add submenu only when user is logged in
// add submenu only when user is logged in
if ( Galaxy.user.id ) {
tab_visualization.add({
title : "New Track Browser",
content : "visualization/trackster",
target : "_frame"
title : 'New Track Browser',
content : 'visualization/trackster',
target : '_frame'
});
tab_visualization.add({
title : "Saved Visualizations",
content : "visualization/list",
target : "_frame"
title : 'Saved Visualizations',
content : 'visualization/list',
target : '_frame'
});
}
this.masthead.append(tab_visualization);
//
// Cloud menu.
//
if (this.options.enable_cloud_launch){
var tab_cloud = new mod_masthead.GalaxyMastheadTab({
id : "cloud",
title : "Cloud",
content : "cloudlaunch/index"
});
tab_cloud.add({
title : "New Cloud Cluster",
content : "cloudlaunch/index"
});
this.masthead.append(tab_cloud);
}
this.masthead.append( tab_visualization );
//
// Admin.
//
if (this.options.is_admin_user) {
var tab_admin = new mod_masthead.GalaxyMastheadTab({
id : "admin",
title : "Admin",
content : "admin",
extra_class : "admin-only",
if ( Galaxy.user.get( 'is_admin' ) ) {
var tab_admin = new Masthead.GalaxyMastheadTab({
id : 'admin',
title : 'Admin',
content : 'admin',
extra_class : 'admin-only',
title_attribute : 'Administer this Galaxy'
});
this.masthead.append(tab_admin);
this.masthead.append( tab_admin );
}
//
// Help tab.
//
var tab_help = new mod_masthead.GalaxyMastheadTab({
id : "help",
title : "Help",
var tab_help = new Masthead.GalaxyMastheadTab({
id : 'help',
title : 'Help',
title_attribute : 'Support, contact, and community hubs'
});
if (this.options.biostar_url){
if ( this.options.biostar_url ){
tab_help.add({
title : "Galaxy Biostar",
title : 'Galaxy Biostar',
content : this.options.biostar_url_redirect,
target : "_blank"
target : '_blank'
});
tab_help.add({
title : "Ask a question",
content : "biostar/biostar_question_redirect",
target : "_blank"
title : 'Ask a question',
content : 'biostar/biostar_question_redirect',
target : '_blank'
});
}
tab_help.add({
title : "Support",
title : 'Support',
content : this.options.support_url,
target : "_blank"
target : '_blank'
});
tab_help.add({
title : "Search",
title : 'Search',
content : this.options.search_url,
target : "_blank"
target : '_blank'
});
tab_help.add({
title : "Mailing Lists",
title : 'Mailing Lists',
content : this.options.mailing_lists,
target : "_blank"
target : '_blank'
});
tab_help.add({
title : "Videos",
title : 'Videos',
content : this.options.screencasts_url,
target : "_blank"
target : '_blank'
});
tab_help.add({
title : "Wiki",
title : 'Wiki',
content : this.options.wiki_url,
target : "_blank"
target : '_blank'
});
tab_help.add({
title : "How to Cite Galaxy",
title : 'How to Cite Galaxy',
content : this.options.citation_url,
target : "_blank"
target : '_blank'
});
if (this.options.terms_url){
tab_help.add({
title : "Terms and Conditions",
title : 'Terms and Conditions',
content : this.options.terms_url,
target : "_blank"
target : '_blank'
});
}
this.masthead.append(tab_help);
this.masthead.append( tab_help );
//
// User tab.
//
if (!this.options.user.valid){
var tab_user = new mod_masthead.GalaxyMastheadTab({
id : "user",
title : "User",
extra_class : "loggedout-only",
if ( !Galaxy.user.id ){
var tab_user = new Masthead.GalaxyMastheadTab({
id : 'user',
title : 'User',
extra_class : 'loggedout-only',
title_attribute : 'Account registration or login'
});
// login
tab_user.add({
title : "Login",
content : "user/login",
target : "galaxy_main"
title : 'Login',
content : 'user/login',
target : 'galaxy_main'
});
// register
if (this.options.allow_user_creation){
if ( this.options.allow_user_creation ){
tab_user.add({
title : "Register",
content : "user/create",
target : "galaxy_main"
title : 'Register',
content : 'user/create',
target : 'galaxy_main'
});
}
// add to masthead
this.masthead.append(tab_user);
this.masthead.append( tab_user );
} else {
var tab_user = new mod_masthead.GalaxyMastheadTab({
id : "user",
title : "User",
extra_class : "loggedin-only",
var tab_user = new Masthead.GalaxyMastheadTab({
id : 'user',
title : 'User',
extra_class : 'loggedin-only',
title_attribute : 'Account preferences and saved data'
});
// show user logged in info
tab_user.add({
title : "Logged in as " + this.options.user.email
title : 'Logged in as ' + Galaxy.user.get( 'email' )
});
tab_user.add({
title : "Preferences",
content : "user?cntrller=user",
target : "galaxy_main"
title : 'Preferences',
content : 'user?cntrller=user',
target : 'galaxy_main'
});
tab_user.add({
title : "Custom Builds",
content : "user/dbkeys",
target : "galaxy_main"
title : 'Custom Builds',
content : 'user/dbkeys',
target : 'galaxy_main'
});
tab_user.add({
title : "Logout",
content : "user/logout",
target : "_top",
title : 'Logout',
content : 'user/logout',
target : '_top',
divider : true
});
// default tabs
tab_user.add({
title : "Saved Histories",
content : "history/list",
target : "galaxy_main"
title : 'Saved Histories',
content : 'history/list',
target : 'galaxy_main'
});
tab_user.add({
title : "Saved Datasets",
content : "dataset/list",
target : "galaxy_main"
title : 'Saved Datasets',
content : 'dataset/list',
target : 'galaxy_main'
});
tab_user.add({
title : "Saved Pages",
content : "page/list",
target : "_top"
title : 'Saved Pages',
content : 'page/list',
target : '_top'
});
tab_user.add({
title : "API Keys",
content : "user/api_keys?cntrller=user",
target : "galaxy_main"
title : 'API Keys',
content : 'user/api_keys?cntrller=user',
target : 'galaxy_main'
});
if (this.options.use_remote_user){
if ( this.options.use_remote_user ){
tab_user.add({
title : "Public Name",
content : "user/edit_username?cntrller=user",
target : "galaxy_main"
title : 'Public Name',
content : 'user/edit_username?cntrller=user',
target : 'galaxy_main'
});
}
// add to masthead
this.masthead.append(tab_user);
this.masthead.append( tab_user );
}
// identify active tab
if (this.options.active_view)
this.masthead.highlight(this.options.active_view);
if ( this.options.active_view ) {
this.masthead.highlight( this.options.active_view );
}
}
});
+3 -2
View File
@@ -10,7 +10,6 @@ var ensure_dd_helper = function () {
}
// Panels
var MIN_PANEL_WIDTH = 160,
MAX_PANEL_WIDTH = 800;
@@ -118,9 +117,11 @@ $.extend( Panel.prototype, {
$( this ).hide().off( 'mousemove', move );
});
});
window.force_left_panel = function( x ) { self.force_panel( x ) };
window.handle_minwidth_hint = function( x ) { self.handle_minwidth_hint( x ) };
}
});
// Modal dialog boxes
var Modal = function( options ) {
this.$overlay = options.overlay;
+8 -8
View File
@@ -1,6 +1,6 @@
!function() {
var d3 = {
version: "3.5.5"
version: "3.5.6"
};
var d3_arraySlice = [].slice, d3_array = function(list) {
return d3_arraySlice.call(list);
@@ -1463,8 +1463,7 @@
function zoomended(dispatch) {
if (!--zooming) dispatch({
type: "zoomend"
});
center0 = null;
}), center0 = null;
}
function mousedowned() {
var that = this, target = d3.event.target, dispatch = event.of(that, arguments), dragged = 0, subject = d3.select(d3_window(that)).on(mousemove, moved).on(mouseup, ended), location0 = location(d3.mouse(that)), dragRestore = d3_event_dragSuppress(that);
@@ -1553,8 +1552,8 @@
}
function mousewheeled() {
var dispatch = event.of(this, arguments);
if (mousewheelTimer) clearTimeout(mousewheelTimer); else translate0 = location(center0 = center || d3.mouse(this)),
d3_selection_interrupt.call(this), zoomstarted(dispatch);
if (mousewheelTimer) clearTimeout(mousewheelTimer); else d3_selection_interrupt.call(this),
translate0 = location(center0 = center || d3.mouse(this)), zoomstarted(dispatch);
mousewheelTimer = setTimeout(function() {
mousewheelTimer = null;
zoomended(dispatch);
@@ -1699,8 +1698,9 @@
return v < 16 ? "0" + Math.max(0, v).toString(16) : Math.min(255, v).toString(16);
}
function d3_rgb_parse(format, rgb, hsl) {
format = format.toLowerCase();
var r = 0, g = 0, b = 0, m1, m2, color;
m1 = /([a-z]+)\((.*)\)/i.exec(format);
m1 = /([a-z]+)\((.*)\)/.exec(format);
if (m1) {
m2 = m1[2].split(",");
switch (m1[1]) {
@@ -1715,7 +1715,7 @@
}
}
}
if (color = d3_rgb_names.get(format.toLowerCase())) {
if (color = d3_rgb_names.get(format)) {
return rgb(color.r, color.g, color.b);
}
if (format != null && format.charAt(0) === "#" && !isNaN(color = parseInt(format.slice(1), 16))) {
@@ -5785,7 +5785,7 @@
}
d3.interpolators = [ function(a, b) {
var t = typeof b;
return (t === "string" ? d3_rgb_names.has(b) || /^(#|rgb\(|hsl\()/.test(b) ? d3_interpolateRgb : d3_interpolateString : b instanceof d3_color ? d3_interpolateRgb : Array.isArray(b) ? d3_interpolateArray : t === "object" && isNaN(b) ? d3_interpolateObject : d3_interpolateNumber)(a, b);
return (t === "string" ? d3_rgb_names.has(b.toLowerCase()) || /^(#|rgb\(|hsl\()/i.test(b) ? d3_interpolateRgb : d3_interpolateString : b instanceof d3_color ? d3_interpolateRgb : Array.isArray(b) ? d3_interpolateArray : t === "object" && isNaN(b) ? d3_interpolateObject : d3_interpolateNumber)(a, b);
} ];
d3.interpolateArray = d3_interpolateArray;
function d3_interpolateArray(a, b) {
+20 -22
View File
@@ -1,6 +1,6 @@
// Farbtastic 2.0 alpha
// Farbtastic 2.0.0-alpha.1
(function ($) {
var __debug = false;
$.fn.farbtastic = function (options) {
@@ -15,7 +15,7 @@ $.farbtastic = function (container, options) {
$._farbtastic = function (container, options) {
var fb = this;
/////////////////////////////////////////////////////
/**
@@ -98,7 +98,7 @@ $._farbtastic = function (container, options) {
.find('div>*').css('position', 'absolute');
// IE Fix: Recreate canvas elements with doc.createElement and excanvas.
navigator.userAgent.match(/msie/i) && $('canvas', container).each(function () {
$.browser.msie || false && $('canvas', container).each(function () {
// Fetch info.
var attr = { 'class': $(this).attr('class'), style: this.getAttribute('style') },
e = document.createElement('canvas');
@@ -130,7 +130,7 @@ $._farbtastic = function (container, options) {
fb.ctxOverlay = fb.cnvOverlay[0].getContext('2d');
fb.ctxMask.translate(fb.mid, fb.mid);
fb.ctxOverlay.translate(fb.mid, fb.mid);
// Draw widget base layers.
fb.drawCircle();
fb.drawMask();
@@ -166,7 +166,7 @@ $._farbtastic = function (container, options) {
// New color
color2 = fb.pack(fb.HSLToRGB([d2, 1, 0.5]));
if (i > 0) {
if (navigator.userAgent.match(/msie/i)) {
if ($.browser.msie || false) {
// IE's gradient calculations mess up the colors. Correct along the diagonals.
var corr = (1 + Math.min(Math.abs(Math.tan(angle1)), Math.abs(Math.tan(Math.PI / 2 - angle1)))) / n;
color1 = fb.pack(fb.HSLToRGB([d1 - 0.15 * corr, 1, 0.5]));
@@ -204,7 +204,7 @@ $._farbtastic = function (container, options) {
m.restore();
__debug && $('body').append('<div>drawCircle '+ (+(new Date()) - tm) +'ms');
};
/**
* Draw the saturation/luminance mask.
*/
@@ -224,9 +224,9 @@ $._farbtastic = function (container, options) {
var c = (a > 0) ? ((2 * l - 1 + a) * .5 / a) : 0;
outputPixel(x, y, c, a);
}
}
}
}
// Method #1: direct pixel access (new Canvas).
if (fb.ctxMask.getImageData) {
// Create half-resolution buffer.
@@ -246,7 +246,7 @@ $._farbtastic = function (container, options) {
fb.ctxMask.drawImage(buffer, 0, 0, sz + 1, sz + 1, -sq, -sq, sq * 2, sq * 2);
}
// Method #2: drawing commands (old Canvas).
else if (!navigator.userAgent.match(/msie/i)) {
else if (!($.browser.msie || false)) {
// Render directly at half-resolution
var sz = Math.floor(size / 2);
calculateMask(sz, sz, function (x, y, c, a) {
@@ -287,7 +287,7 @@ $._farbtastic = function (container, options) {
}
cache.push([c, a]);
});
}
}
__debug && $('body').append('<div>drawMask '+ (+(new Date()) - tm) +'ms');
}
@@ -313,7 +313,7 @@ $._farbtastic = function (container, options) {
// Update the overlay canvas.
fb.ctxOverlay.clearRect(-fb.mid, -fb.mid, sz, sz);
for (i in circles) {
for (var i = 0; i < circles.length; i++) {
var c = circles[i];
fb.ctxOverlay.lineWidth = c.lw;
fb.ctxOverlay.strokeStyle = c.c;
@@ -335,7 +335,7 @@ $._farbtastic = function (container, options) {
// Draw markers
fb.drawMarkers();
// Linked elements or callback
if (typeof fb.callback == 'object') {
// Set background/foreground color
@@ -349,21 +349,21 @@ $._farbtastic = function (container, options) {
if ((typeof this.value == 'string') && this.value != fb.color) {
this.value = fb.color;
}
});
}).change();
}
else if (typeof fb.callback == 'function') {
fb.callback.call(fb, fb.color);
}
}
/**
* Helper for returning coordinates relative to the center.
*/
fb.widgetCoords = function (event) {
return {
x: event.pageX - fb.offset.left - fb.mid,
x: event.pageX - fb.offset.left - fb.mid,
y: event.pageY - fb.offset.top - fb.mid
};
};
}
/**
@@ -426,7 +426,7 @@ $._farbtastic = function (container, options) {
fb.packDX = function (c, a) {
return '#' + fb.dec2hex(a) + fb.dec2hex(c) + fb.dec2hex(c) + fb.dec2hex(c);
};
fb.pack = function (rgb) {
var r = Math.round(rgb[0] * 255);
var g = Math.round(rgb[1] * 255);
@@ -496,8 +496,7 @@ $._farbtastic = function (container, options) {
options = $.extend({
width: 300,
wheelWidth: (options.width || 300) / 10,
callback: null,
color: "#808080"
callback: null
}, options);
// Initialize.
@@ -511,8 +510,7 @@ $._farbtastic = function (container, options) {
fb.linkTo(options.callback);
}
// Set to gray.
fb.setColor( "#808080" );
fb.setColor( options.color );
if (!fb.color) fb.setColor('#808080');
}
})(jQuery);
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+3457 -2034
View File
File diff suppressed because it is too large Load Diff
+45 -7
View File
@@ -1,4 +1,4 @@
/*! Raven.js 1.1.18 (8ad15bc) | github.com/getsentry/raven-js */
/*! Raven.js 1.1.19 (b51bc89) | github.com/getsentry/raven-js */
/*
* Includes TraceKit
@@ -1093,7 +1093,7 @@ var _Raven = window.Raven,
* @this {Raven}
*/
var Raven = {
VERSION: '1.1.18',
VERSION: '1.1.19',
debug: true,
@@ -1385,6 +1385,32 @@ var Raven = {
return Raven;
},
/*
* Set the dataCallback option
*
* @param {function} callback The callback to run which allows the
* data blob to be mutated before sending
* @return {Raven}
*/
setDataCallback: function(callback) {
globalOptions.dataCallback = callback;
return Raven;
},
/*
* Set the shouldSendCallback option
*
* @param {function} callback The callback to run which allows
* introspecting the blob before sending
* @return {Raven}
*/
setShouldSendCallback: function(callback) {
globalOptions.shouldSendCallback = callback;
return Raven;
},
/*
* Get the latest raw exception that was captured by Raven.
*
@@ -1475,7 +1501,7 @@ function parseDSN(str) {
}
function isUndefined(what) {
return typeof what === 'undefined';
return what === void 0;
}
function isFunction(what) {
@@ -1483,7 +1509,7 @@ function isFunction(what) {
}
function isString(what) {
return typeof what === 'string';
return objectPrototype.toString.call(what) === '[object String]';
}
function isObject(what) {
@@ -1758,7 +1784,12 @@ function send(data) {
if (globalOptions.release) data.release = globalOptions.release;
if (isFunction(globalOptions.dataCallback)) {
data = globalOptions.dataCallback(data);
data = globalOptions.dataCallback(data) || data;
}
// Why??????????
if (!data || isEmptyObject(data)) {
return;
}
// Check if the request should be filtered or not
@@ -1776,7 +1807,7 @@ function send(data) {
function makeRequest(data) {
var img = new Image(),
var img = newImage(),
src = globalServer + authQueryString + '&sentry_data=' + encodeURIComponent(JSON.stringify(data));
img.crossOrigin = 'anonymous';
@@ -1795,6 +1826,13 @@ function makeRequest(data) {
img.src = src;
}
// Note: this is shitty, but I can't figure out how to get
// sinon to stub document.createElement without breaking everything
// so this wrapper is just so I can stub it for tests.
function newImage() {
return document.createElement('img');
}
function isSetup() {
if (!hasJSON) return false; // needs JSON support
if (!globalServer) {
@@ -1868,4 +1906,4 @@ if (typeof define === 'function' && define.amd) {
window.Raven = Raven;
}
})(window);
})(typeof window !== 'undefined' ? window : this);
+37 -17
View File
@@ -1,5 +1,5 @@
/** vim: et:ts=4:sw=4:sts=4
* @license RequireJS 2.1.17 Copyright (c) 2010-2015, The Dojo Foundation All Rights Reserved.
* @license RequireJS 2.1.20 Copyright (c) 2010-2015, The Dojo Foundation All Rights Reserved.
* Available via the MIT or new BSD license.
* see: http://github.com/jrburke/requirejs for details
*/
@@ -12,7 +12,7 @@ var requirejs, require, define;
(function (global) {
var req, s, head, baseElement, dataMain, src,
interactiveScript, currentlyAddingScript, mainScript, subPath,
version = '2.1.17',
version = '2.1.20',
commentRegExp = /(\/\*([\s\S]*?)\*\/|([^:]|^)\/\/(.*)$)/mg,
cjsRequireRegExp = /[^.]\s*require\s*\(\s*["']([^'"\s]+)["']\s*\)/g,
jsSuffixRegExp = /\.js$/,
@@ -21,7 +21,6 @@ var requirejs, require, define;
ostring = op.toString,
hasOwn = op.hasOwnProperty,
ap = Array.prototype,
apsp = ap.splice,
isBrowser = !!(typeof window !== 'undefined' && typeof navigator !== 'undefined' && window.document),
isWebWorker = !isBrowser && typeof importScripts !== 'undefined',
//PS3 indicates loaded and complete, but need to wait for complete
@@ -554,11 +553,13 @@ var requirejs, require, define;
function takeGlobalQueue() {
//Push all the globalDefQueue items into the context's defQueue
if (globalDefQueue.length) {
//Array splice in the values since the context code has a
//local var ref to defQueue, so cannot just reassign the one
//on context.
apsp.apply(defQueue,
[defQueue.length, 0].concat(globalDefQueue));
each(globalDefQueue, function(queueItem) {
var id = queueItem[0];
if (typeof id === 'string') {
context.defQueueMap[id] = true;
}
defQueue.push(queueItem);
});
globalDefQueue = [];
}
}
@@ -589,7 +590,7 @@ var requirejs, require, define;
id: mod.map.id,
uri: mod.map.url,
config: function () {
return getOwn(config.config, mod.map.id) || {};
return getOwn(config.config, mod.map.id) || {};
},
exports: mod.exports || (mod.exports = {})
});
@@ -845,7 +846,10 @@ var requirejs, require, define;
factory = this.factory;
if (!this.inited) {
this.fetch();
// Only fetch if not already in the defQueue.
if (!hasProp(context.defQueueMap, id)) {
this.fetch();
}
} else if (this.error) {
this.emit('error', this.error);
} else if (!this.defining) {
@@ -1117,6 +1121,9 @@ var requirejs, require, define;
this.depCount += 1;
on(depMap, 'defined', bind(this, function (depExports) {
if (this.undefed) {
return;
}
this.defineDep(i, depExports);
this.check();
}));
@@ -1233,13 +1240,15 @@ var requirejs, require, define;
while (defQueue.length) {
args = defQueue.shift();
if (args[0] === null) {
return onError(makeError('mismatch', 'Mismatched anonymous define() module: ' + args[args.length - 1]));
return onError(makeError('mismatch', 'Mismatched anonymous define() module: ' +
args[args.length - 1]));
} else {
//args are id, deps, factory. Should be normalized by the
//define() function.
callGetModule(args);
}
}
context.defQueueMap = {};
}
context = {
@@ -1249,6 +1258,7 @@ var requirejs, require, define;
defined: defined,
urlFetched: urlFetched,
defQueue: defQueue,
defQueueMap: {},
Module: Module,
makeModuleMap: makeModuleMap,
nextTick: req.nextTick,
@@ -1320,7 +1330,7 @@ var requirejs, require, define;
each(cfg.packages, function (pkgObj) {
var location, name;
pkgObj = typeof pkgObj === 'string' ? { name: pkgObj } : pkgObj;
pkgObj = typeof pkgObj === 'string' ? {name: pkgObj} : pkgObj;
name = pkgObj.name;
location = pkgObj.location;
@@ -1347,7 +1357,7 @@ var requirejs, require, define;
//late to modify them, and ignore unnormalized ones
//since they are transient.
if (!mod.inited && !mod.map.unnormalized) {
mod.map = makeModuleMap(id);
mod.map = makeModuleMap(id, null, true);
}
});
@@ -1483,6 +1493,7 @@ var requirejs, require, define;
var map = makeModuleMap(id, relMap, true),
mod = getOwn(registry, id);
mod.undefed = true;
removeScript(id);
delete defined[id];
@@ -1493,10 +1504,11 @@ var requirejs, require, define;
//in array so that the splices do not
//mess up the iteration.
eachReverse(defQueue, function(args, i) {
if(args[0] === id) {
if (args[0] === id) {
defQueue.splice(i, 1);
}
});
delete context.defQueueMap[id];
if (mod) {
//Hold on to listeners in case the
@@ -1558,6 +1570,7 @@ var requirejs, require, define;
callGetModule(args);
}
context.defQueueMap = {};
//Do this after the cycle of callGetModule in case the result
//of those calls/init calls changes the registry.
@@ -1852,6 +1865,9 @@ var requirejs, require, define;
if (isBrowser) {
//In the browser so use a script tag
node = req.createNode(config, moduleName, url);
if (config.onNodeCreated) {
config.onNodeCreated(node, config, moduleName, url);
}
node.setAttribute('data-requirecontext', context.contextName);
node.setAttribute('data-requiremodule', moduleName);
@@ -1980,7 +1996,7 @@ var requirejs, require, define;
//like a module name.
mainScript = mainScript.replace(jsSuffixRegExp, '');
//If mainScript is still a path, fall back to dataMain
//If mainScript is still a path, fall back to dataMain
if (req.jsExtRegExp.test(mainScript)) {
mainScript = dataMain;
}
@@ -2059,14 +2075,18 @@ var requirejs, require, define;
//where the module name is not known until the script onload event
//occurs. If no context, use the global queue, and get it processed
//in the onscript load callback.
(context ? context.defQueue : globalDefQueue).push([name, deps, callback]);
if (context) {
context.defQueue.push([name, deps, callback]);
context.defQueueMap[name] = true;
} else {
globalDefQueue.push([name, deps, callback]);
}
};
define.amd = {
jQuery: true
};
/**
* Executes the text. Normally just uses eval, but can be modified
* to use a better, environment-specific call. Only used for transpiling
+76 -37
View File
@@ -1,10 +1,12 @@
/*
TraceKit - Cross brower stack traces - github.com/occ/TraceKit
TraceKit - Cross brower stack traces - github.com/csnover/TraceKit
MIT license
*/
(function(window, undefined) {
if (!window) {
return;
}
var TraceKit = {};
var _oldTraceKit = window.TraceKit;
@@ -18,7 +20,7 @@ var UNKNOWN_FUNCTION = '?';
* _has, a better form of hasOwnProperty
* Example: _has(MainHostObject, property) === true/false
*
* @param {Object} host object to check property
* @param {Object} object to check property
* @param {string} key to check
*/
function _has(object, key) {
@@ -126,9 +128,9 @@ TraceKit.report = (function reportModuleWrapper() {
* Dispatch stack information to all handlers.
* @param {Object.<string, *>} stack
*/
function notifyHandlers(stack, windowError) {
function notifyHandlers(stack, isWindowError) {
var exception = null;
if (windowError && !TraceKit.collectWindowErrors) {
if (isWindowError && !TraceKit.collectWindowErrors) {
return;
}
for (var i in handlers) {
@@ -155,29 +157,39 @@ TraceKit.report = (function reportModuleWrapper() {
* @param {string} url URL of script that generated the exception.
* @param {(number|string)} lineNo The line number at which the error
* occurred.
* @param {?(number|string)} columnNo The column number at which the error
* occurred.
* @param {?Error} errorObj The actual Error object.
*/
function traceKitWindowOnError(message, url, lineNo) {
function traceKitWindowOnError(message, url, lineNo, columnNo, errorObj) {
var stack = null;
if (lastExceptionStack) {
TraceKit.computeStackTrace.augmentStackTraceWithInitialElement(lastExceptionStack, url, lineNo, message);
stack = lastExceptionStack;
lastExceptionStack = null;
lastException = null;
} else {
var location = {
'url': url,
'line': lineNo
};
location.func = TraceKit.computeStackTrace.guessFunctionName(location.url, location.line);
location.context = TraceKit.computeStackTrace.gatherContext(location.url, location.line);
stack = {
'mode': 'onerror',
'message': message,
'url': document.location.href,
'stack': [location],
'useragent': navigator.userAgent
};
if (errorObj) {
stack = TraceKit.computeStackTrace(errorObj);
}
else
{
if (lastExceptionStack) {
TraceKit.computeStackTrace.augmentStackTraceWithInitialElement(lastExceptionStack, url, lineNo, message);
stack = lastExceptionStack;
lastExceptionStack = null;
lastException = null;
} else {
var location = {
'url': url,
'line': lineNo,
'column': columnNo
};
location.func = TraceKit.computeStackTrace.guessFunctionName(location.url, location.line);
location.context = TraceKit.computeStackTrace.gatherContext(location.url, location.line);
stack = {
'mode': 'onerror',
'message': message,
'url': document.location.href,
'stack': [location],
'useragent': navigator.userAgent
};
}
}
notifyHandlers(stack, 'from window.onerror');
@@ -346,11 +358,17 @@ TraceKit.computeStackTrace = (function computeStackTraceWrapper() {
* @return {Array.<string>} Source contents.
*/
function getSource(url) {
if (typeof url !== 'string') {
return [];
}
if (!_has(sourceCache, url)) {
// URL needs to be able to fetched within the acceptable domain. Otherwise,
// cross-domain errors will be triggered.
var source = '';
if (url.indexOf(document.domain) !== -1) {
url = url || '';
if (url.indexOf && url.indexOf(document.domain) !== -1) {
source = loadSource(url);
}
sourceCache[url] = source ? source.split('\n') : [];
@@ -587,6 +605,7 @@ TraceKit.computeStackTrace = (function computeStackTraceWrapper() {
// ex.message = qq is not defined
// ex.fileName = http://...
// ex.lineNumber = 59
// ex.columnNumber = 69
// ex.stack = ...stack trace... (see the example below)
// ex.name = ReferenceError
//
@@ -618,8 +637,9 @@ TraceKit.computeStackTrace = (function computeStackTraceWrapper() {
return null;
}
var chrome = /^\s*at (?:((?:\[object object\])?\S+(?: \[as \S+\])?) )?\(?((?:file|http|https):.*?):(\d+)(?::(\d+))?\)?\s*$/i,
gecko = /^\s*(\S*)(?:\((.*?)\))?@((?:file|http|https).*?):(\d+)(?::(\d+))?\s*$/i,
var chrome = /^\s*at (.*?) ?\(?((?:file|https?|chrome-extension):.*?):(\d+)(?::(\d+))?\)?\s*$/i,
gecko = /^\s*(.*?)(?:\((.*?)\))?@?((?:file|https?|chrome):.*?):(\d+)(?::(\d+))?\s*$/i,
winjs = /^\s*at (?:((?:\[object object\])?.+) )?\(?((?:ms-appx|http|https):.*?):(\d+)(?::(\d+))?\)?\s*$/i,
lines = ex.stack.split('\n'),
stack = [],
parts,
@@ -642,6 +662,13 @@ TraceKit.computeStackTrace = (function computeStackTraceWrapper() {
'line': +parts[3],
'column': parts[4] ? +parts[4] : null
};
} else if ((parts = winjs.exec(lines[i]))) {
element = {
'url': parts[2],
'func': parts[1] || UNKNOWN_FUNCTION,
'line': +parts[3],
'column': parts[4] ? +parts[4] : null
};
} else {
continue;
}
@@ -657,14 +684,19 @@ TraceKit.computeStackTrace = (function computeStackTraceWrapper() {
stack.push(element);
}
if (stack[0] && stack[0].line && !stack[0].column && reference) {
stack[0].column = findSourceInLine(reference[1], stack[0].url, stack[0].line);
}
if (!stack.length) {
return null;
}
if (stack[0] && stack[0].line && !stack[0].column && reference) {
stack[0].column = findSourceInLine(reference[1], stack[0].url, stack[0].line);
} else if (!stack[0].column && !_isUndefined(ex.columnNumber)) {
// FireFox uses this awesome columnNumber property for its top frame
// Also note, Firefox's column number is 0-based and everything else expects 1-based,
// so adding 1
stack[0].column = ex.columnNumber + 1;
}
return {
'mode': 'stack',
'name': ex.name,
@@ -762,8 +794,8 @@ TraceKit.computeStackTrace = (function computeStackTraceWrapper() {
return null;
}
var lineRE1 = /^\s*Line (\d+) of linked script ((?:file|http|https)\S+)(?:: in function (\S+))?\s*$/i,
lineRE2 = /^\s*Line (\d+) of inline#(\d+) script in ((?:file|http|https)\S+)(?:: in function (\S+))?\s*$/i,
var lineRE1 = /^\s*Line (\d+) of linked script ((?:file|https?)\S+)(?:: in function (\S+))?\s*$/i,
lineRE2 = /^\s*Line (\d+) of inline#(\d+) script in ((?:file|https?)\S+)(?:: in function (\S+))?\s*$/i,
lineRE3 = /^\s*Line (\d+) of function script\s*$/i,
stack = [],
scripts = document.getElementsByTagName('script'),
@@ -939,6 +971,12 @@ TraceKit.computeStackTrace = (function computeStackTraceWrapper() {
item.func = parts[1];
}
if (typeof item.func === 'undefined') {
try {
item.func = parts.input.substring(0, parts.input.indexOf('{'));
} catch (e) { }
}
if ((source = findSourceByFunctionBody(curr))) {
item.url = source.url;
item.line = source.line;
@@ -1059,6 +1097,7 @@ TraceKit.computeStackTrace = (function computeStackTraceWrapper() {
computeStackTrace.guessFunctionName = guessFunctionName;
computeStackTrace.gatherContext = gatherContext;
computeStackTrace.ofCaller = computeStackTraceOfCaller;
computeStackTrace.getSource = getSource;
return computeStackTrace;
}());
@@ -1067,7 +1106,7 @@ TraceKit.computeStackTrace = (function computeStackTraceWrapper() {
* Extends support for global error handling for asynchronous browser
* functions. Adopted from Closure Library's errorhandler.js
*/
(function extendToAsynchronousCallbacks() {
TraceKit.extendToAsynchronousCallbacks = function () {
var _helper = function _helper(fnName) {
var originalFn = window[fnName];
window[fnName] = function traceKitAsyncExtension() {
@@ -1090,7 +1129,7 @@ TraceKit.computeStackTrace = (function computeStackTraceWrapper() {
_helper('setTimeout');
_helper('setInterval');
}());
};
//Default options:
if (!TraceKit.remoteFetching) {
@@ -1109,4 +1148,4 @@ if (!TraceKit.linesOfContext || TraceKit.linesOfContext < 1) {
// Export to global object
window.TraceKit = TraceKit;
}(window));
}(typeof window !== 'undefined' ? window : global));
@@ -0,0 +1,223 @@
define(['utils/utils', 'mvc/tools', 'mvc/upload/upload-view', 'mvc/ui/ui-misc',
'mvc/history/options-menu', 'mvc/history/history-panel-edit-current', 'mvc/tools/tools-form'],
function( Utils, Tools, Upload, Ui, optionsMenu, HistoryPanel, ToolsForm ) {
/* Builds the center panel */
var CenterPanel = Backbone.View.extend({
initialize: function( options ) {
this.options = Utils.merge( options, {} );
this.setElement( this._template() );
var self = this;
this.$( '#galaxy_main' ).on( 'load', function() {
var location = this.contentWindow && this.contentWindow.location;
if ( location && location.host ) {
$( this ).show();
self.$( '#center-panel' ).empty().hide();
Galaxy.trigger( 'galaxy_main:load', {
fullpath: location.pathname + location.search + location.hash,
pathname: location.pathname,
search : location.search,
hash : location.hash
});
}
});
var params = $.extend( {}, Galaxy.params );
if ( params.tool_id !== 'upload1' && ( params.tool_id || params.job_id ) ) {
params.tool_id && ( params.id = params.tool_id );
this.display( ( new ToolsForm.View( params ) ).$el );
} else {
this.$( '#galaxy_main' ).prop( 'src', Galaxy.root + (
( params.workflow_id && ( 'workflow/run?id=' + params.workflow_id ) ) ||
( params.m_c && ( params.m_c + '/' + params.m_a ) ) ||
( Galaxy.config.require_login && !Galaxy.user.id && 'user/login') ||
'root/welcome'
));
}
},
display: function( $el ) {
this.$( '#galaxy_main' ).hide();
this.$( '#center-panel' ).empty().scrollTop( 0 ).append( $el ).show();
},
_template: function() {
return '<div style="position: absolute; width: 100%; height: 100%">' +
'<iframe name="galaxy_main" id="galaxy_main" frameborder="0" style="position: absolute; width: 100%; height: 100%;"/>' +
'<div id="center-panel" style="position: absolute; width: 100%; height: 100%; padding: 10px; overflow: auto;"/>' +
'</div>';
}
});
/* Builds the tool panel on the left */
var LeftPanel = Backbone.View.extend({
initialize: function( options ) {
this.options = Utils.merge( options, {} );
this.setElement( this._template() );
// create tool search, tool panel, and tool panel view.
if ( Galaxy.user.id || !Galaxy.config.require_login ) {
var tool_search = new Tools.ToolSearch({
spinner_url : options.spinner_url,
search_url : options.search_url,
hidden : false
});
var tools = new Tools.ToolCollection( options.toolbox );
var tool_panel = new Tools.ToolPanel({
tool_search : tool_search,
tools : tools,
layout : options.toolbox_in_panel
});
tool_panel_view = new Tools.ToolPanelView({ model: tool_panel });
// add tool panel to Galaxy object
Galaxy.toolPanel = tool_panel;
// if there are tools, render panel and display everything
if (tool_panel.get( 'layout' ).size() > 0) {
tool_panel_view.render();
this.$( '.toolMenu' ).show();
}
this.$el.prepend( tool_panel_view.$el );
// add internal workflow list
this.$( '#internal-workflows' ).append( this._templateTool({
title : 'All workflows',
href : 'workflow/list_for_run'
}) )
for ( var i in options.stored_workflow_menu_entries ) {
var m = options.stored_workflow_menu_entries[ i ];
this.$( '#internal-workflows' ).append( this._templateTool({
title : m.stored_workflow.name,
href : 'workflow/run?id=' + m.encoded_stored_workflow_id
}) );
}
// minsize init hint
this.$( 'a[minsizehint]' ).click( function() {
if ( parent.handle_minwidth_hint ) {
parent.handle_minwidth_hint( $( this ).attr( 'minsizehint' ) );
}
});
// add upload plugin
Galaxy.upload = new Upload( options );
// define components (is used in app-view.js)
this.components = {
header : {
title : 'Tools',
buttons : [ Galaxy.upload ]
}
}
}
},
_templateTool: function( options ) {
return '<div class="toolTitle">' +
'<a href="' + Galaxy.root + options.href + '" target="galaxy_main">' + options.title + '</a>' +
'</div>';
},
_template: function() {
return '<div class="toolMenuContainer">' +
'<div class="toolMenu" style="display: none">' +
'<div id="search-no-results" style="display: none; padding-top: 5px">' +
'<em><strong>Search did not match any tools.</strong></em>' +
'</div>' +
'</div>' +
'<div class="toolSectionPad"/>' +
'<div class="toolSectionPad"/>' +
'<div class="toolSectionTitle" id="title_XXinternalXXworkflow">' +
'<span>Workflows</span>' +
'</div>' +
'<div id="internal-workflows" class="toolSectionBody">' +
'<div class="toolSectionBg"/>' +
'</div>' +
'</div>';
}
});
/* Builds the history panel on the right */
var RightPanel = Backbone.View.extend({
initialize: function(options) {
this.options = Utils.merge( options, {} );
this.setElement( this._template() );
var headerButtons = [];
// this button re-fetches the history and contents and re-renders the history panel
var buttonRefresh = new Ui.ButtonLink({
id : 'history-refresh-button',
title : 'Refresh history',
cls : 'panel-header-button',
icon : 'fa fa-refresh',
onclick : function() {
if( top.Galaxy && top.Galaxy.currHistoryPanel ) {
top.Galaxy.currHistoryPanel.loadCurrentHistory();
}
}
});
headerButtons.push( buttonRefresh );
// opens a drop down menu with history related functions (like view all, delete, share, etc.)
var buttonOptions = new Ui.ButtonLink({
id : 'history-options-button',
title : 'History options',
cls : 'panel-header-button',
target : 'galaxy_main',
icon : 'fa fa-cog',
href : Galaxy.root + 'root/history_options'
});
headerButtons.push( buttonOptions );
// goes to a page showing all the users histories in panel form (for logged in users)
if( !Galaxy.user.isAnonymous() ){
var buttonViewMulti = new Ui.ButtonLink({
id : 'history-view-multi-button',
title : 'View all histories',
cls : 'panel-header-button',
icon : 'fa fa-columns',
href : Galaxy.root + 'history/view_multiple'
});
headerButtons.push( buttonViewMulti );
}
// define components (is used in app-view.js)
this.components = {
header : {
title : 'History',
cls : 'history-panel-header',
buttons : headerButtons
},
body : {
cls : 'unified-panel-body-background',
}
};
// build history options menu
Galaxy.historyOptionsMenu = optionsMenu( buttonOptions.$el, {
anonymous : Galaxy.user.isAnonymous(),
purgeAllowed : Galaxy.config.allow_user_dataset_purge,
root : Galaxy.root
});
// load current history
Galaxy.currHistoryPanel = new HistoryPanel.CurrentHistoryPanel({
el : this.$el,
purgeAllowed : Galaxy.config.allow_user_dataset_purge,
linkTarget : 'galaxy_main',
$scrollContainer: function(){ return this.$el.parent(); }
});
Galaxy.currHistoryPanel.connectToQuotaMeter( Galaxy.quotaMeter );
Galaxy.currHistoryPanel.listenToGalaxy( Galaxy );
Galaxy.currHistoryPanel.loadCurrentHistory();
},
// body template
_template: function() {
return '<div id="current-history-panel" class="history-panel"/>';
}
});
return {
left : LeftPanel,
center : CenterPanel,
right : RightPanel
};
});
+139
View File
@@ -0,0 +1,139 @@
/**
This is the entrance point for the Galaxy UI.
*/
define(['utils/utils', 'galaxy.masthead', 'galaxy.menu', 'galaxy.frame',
'mvc/ui/ui-portlet', 'mvc/ui/ui-misc', 'mvc/ui/ui-modal',
'mvc/user/user-quotameter', 'mvc/app/app-analysis'],
function( Utils, Masthead, Menu, Frame, Portlet, Ui, Modal, QuotaMeter, Analysis ) {
return Backbone.View.extend({
initialize: function( options ) {
this.options = Utils.merge( options, {} );
this.setElement( this._template( options ) );
// register this view
Galaxy.app = this;
// url request parameters
Galaxy.params = this.options.params;
// shared backbone router
Galaxy.router = new Backbone.Router();
// configure body
$( 'body' ).append( this.$el );
ensure_dd_helper();
// adjust parent container
var $container = $( this.$el.parent() ).attr( 'scroll', 'no' ).addClass( 'full-content' );
if ( this.options.message_box_visible ) {
$container.addClass( 'has-message-box' );
this.$( '#messagebox' ).show();
}
if ( this.options.show_inactivity_warning ) {
$container.addClass( 'has-inactivity-box' );
this.$( '#inactivebox' ).show();
}
// load global galaxy objects
if ( !Galaxy.masthead ) {
Galaxy.masthead = new Masthead.GalaxyMasthead( options );
Galaxy.modal = new Modal.View();
Galaxy.frame = new Frame.GalaxyFrame();
// construct default menu options
Galaxy.menu = new Menu.GalaxyMenu({
masthead : Galaxy.masthead,
config : options
});
// set up the quota meter (And fetch the current user data from trans)
// add quota meter to masthead
Galaxy.quotaMeter = new QuotaMeter.UserQuotaMeter({
model : Galaxy.user,
el : Galaxy.masthead.$( '.quota-meter-container' )
}).render();
}
// build page
this._buildPanels( Analysis, options );
},
/** Display content */
display: function ( $el, target ) {
// TODO: Remove this line after select2 update
$( '.select2-hidden-accessible' ).remove();
this.panels && this.panels[ target || 'center' ].display( $el );
},
/** Build all panels **/
_buildPanels: function( Views, options ) {
this.panels = [];
var panel_ids = [ 'left', 'center', 'right' ];
for ( var i in panel_ids ) {
var id = panel_ids[ i ];
var view = this.panels[ id ] = new Views[ id ]( options );
if ( id == 'center' ) {
this.$('#center').append( view.$el );
} else {
var components = Utils.merge( view.components, {
header : {
title : '',
cls : '',
buttons : []
},
body : {
cls : ''
}
});
var $panel = $( this._templatePanel( id ) );
$panel.find('.panel-header-text').html( components.header.title );
$panel.find('.unified-panel-header-inner').addClass( components.header.cls );
for ( var i in components.header.buttons ) {
$panel.find('.panel-header-buttons').append( components.header.buttons[ i ].$el );
}
$panel.find('.unified-panel-body').addClass( components.body.cls ).append( view.$el );
var panel = new Panel( {
center : this.$( '#center' ),
panel : $panel,
drag : $panel.find('.unified-panel-footer > .drag' ),
toggle : $panel.find('.unified-panel-footer > .panel-collapse' ),
right : id == 'right'
} );
this.$el.append( $panel );
}
}
},
/** Template for left/right panel */
_templatePanel: function( id ) {
return '<div id="' + id + '">' +
'<div class="unified-panel-header" unselectable="on">' +
'<div class="unified-panel-header-inner">' +
'<div class="panel-header-buttons" style="float: right"/>' +
'<div class="panel-header-text"/>' +
'</div>' +
'</div>' +
'<div class="unified-panel-body"/>' +
'<div class="unified-panel-footer">' +
'<div class="panel-collapse ' + id + '"/>' +
'<div class="drag"/>' +
'</div>' +
'</div>';
},
/** Main template **/
_template: function() {
return '<div id="everything" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%;">' +
'<div id="background"/>' +
'<div id="messagebox" class="panel-' + Galaxy.config.message_box_class + '-message" style="display: none;">' +
Galaxy.config.message_box_content +
'</div>' +
'<div id="inactivebox" class="panel-warning-message" style="display: none;">' +
Galaxy.config.inactivity_box_content +
' <a href="' + Galaxy.root + 'user/resend_verification">Resend verification.</a>' +
'</div>' +
'<div id="center" class="inbound"/>' +
'</div>';
}
});
});
+2 -3
View File
@@ -2,7 +2,6 @@ define([
'utils/add-logging',
'utils/localization'
], function( addLogging, _l ){
//ASSUMES: backbone
//==============================================================================
/** @class Mixin to add logging capabilities to an object.
* Designed to allow switching an objects log output off/on at one central
@@ -33,7 +32,7 @@ var LoggableMixin = /** @lends LoggableMixin# */{
// replace null with console (if available) to see all logs
logger : null,
_logNamespace : '?',
/** Output log messages/arguments to logger.
* @param {Arguments} ... (this function is variadic)
* @returns undefined if not this.logger
@@ -446,7 +445,7 @@ var SelectableViewMixin = {
/** is the view currently selected? */
this.selected = attributes.selected || false;
},
/** $el sub-element where the selector is rendered and what can be clicked to select. */
$selector : function(){
return this.$( '.selector' );
@@ -56,6 +56,8 @@ var DCEListItemEdit = DCEListItemView.extend(
//==============================================================================
// NOTE: this does not inherit from DatasetDCEListItemView as you would expect
//TODO: but should - if we can find something simpler than using diamond
/** @class Editable view for a DatasetCollectionElement that is also an DatasetAssociation
* (a dataset contained in a dataset collection).
*/
@@ -70,6 +72,24 @@ var DatasetDCEListItemEdit = DATASET_LI_EDIT.DatasetListItemEdit.extend(
DATASET_LI_EDIT.DatasetListItemEdit.prototype.initialize.call( this, attributes );
},
// NOTE: this does not inherit from DatasetDCEListItemView - so we duplicate this here
//TODO: fix
/** In this override, only get details if in the ready state.
* Note: fetch with no 'change' event triggering to prevent automatic rendering.
*/
_fetchModelDetails : function(){
var view = this;
if( view.model.inReadyState() && !view.model.hasDetails() ){
return view.model.fetch({ silent: true });
}
return jQuery.when();
},
/** Override to remove delete button */
_renderDeleteButton : function(){
return null;
},
// ......................................................................... misc
/** String representation */
toString : function(){
@@ -195,6 +195,17 @@ var DatasetDCEListItemView = DATASET_LI.DatasetListItemView.extend(
DATASET_LI.DatasetListItemView.prototype.initialize.call( this, attributes );
},
/** In this override, only get details if in the ready state.
* Note: fetch with no 'change' event triggering to prevent automatic rendering.
*/
_fetchModelDetails : function(){
var view = this;
if( view.model.inReadyState() && !view.model.hasDetails() ){
return view.model.fetch({ silent: true });
}
return jQuery.when();
},
// ......................................................................... misc
/** String representation */
toString : function(){
@@ -126,6 +126,18 @@ var DatasetDCE = DATASET_MODEL.DatasetAssociation.extend( BASE_MVC.mixin( Datase
/** logger used to record this.log messages, commonly set to console */
//logger : console,
/** url fn */
url : function(){
var galaxyRoot = (( window.galaxy_config && galaxy_config.root )?( galaxy_config.root ):( '/' ));
// won't always be an hda
if( !this.has( 'history_id' ) ){
console.warn( 'no endpoint for non-hdas within a collection yet' );
// (a little silly since this api endpoint *also* points at hdas)
return galaxyRoot + 'api/datasets';
}
return galaxyRoot + 'api/histories/' + this.get( 'history_id' ) + '/contents/' + this.get( 'id' );
},
defaults : _.extend( {},
DATASET_MODEL.DatasetAssociation.prototype.defaults,
DatasetCollectionElementMixin.defaults
@@ -133,7 +145,7 @@ var DatasetDCE = DATASET_MODEL.DatasetAssociation.extend( BASE_MVC.mixin( Datase
// because all objects have constructors (as this hashmap would even if this next line wasn't present)
// the constructor in hcontentMixin won't be attached by BASE_MVC.mixin to this model
// - re-apply manually it now
// - re-apply manually for now
/** call the mixin constructor */
constructor : function( attributes, options ){
this.debug( '\t DatasetDCE.constructor:', attributes, options );
@@ -148,6 +160,12 @@ var DatasetDCE = DATASET_MODEL.DatasetAssociation.extend( BASE_MVC.mixin( Datase
DATASET_MODEL.DatasetAssociation.prototype.initialize.call( this, attributes, options );
},
/** Does this model already contain detailed data (as opposed to just summary level data)? */
hasDetails : function(){
// dataset collection api does return genome_build but doesn't return annotation
return _.has( this.attributes, 'annotation' );
},
/** String representation. */
toString : function(){
var objStr = this.get( 'element_identifier' );
@@ -241,10 +259,12 @@ var DatasetCollection = Backbone.Model
return json;
},
/** is the collection done with updates and ready to be used? (finished running, etc.) */
/** Is this collection in a 'ready' state no processing (for the collection) is left
* to do on the server.
*/
inReadyState : function(){
//TODO: state currenly unimplemented for collections
return true;
var populated = this.get( 'populated' );
return ( this.isDeletedOrPurged() || populated );
},
//TODO:?? the following are the same interface as DatasetAssociation - can we combine?
@@ -568,7 +568,8 @@ var PairedCollectionCreator = Backbone.View.extend( baseMVC.LoggableMixin ).exte
*/
createList : function( name ){
var creator = this,
url = '/api/histories/' + this.historyId + '/contents/dataset_collections';
root = ( window.Galaxy && Galaxy.options.root )? Galaxy.options.root : '/',
url = root + 'api/histories/' + this.historyId + '/contents/dataset_collections';
//TODO: use ListPairedCollection.create()
var ajaxData = {
@@ -5,8 +5,9 @@ define([
"mvc/annotations",
"ui/fa-icon-button",
"mvc/base-mvc",
"mvc/tools/tools-form",
"utils/localization"
], function( STATES, DATASET_LI, TAGS, ANNOTATIONS, faIconButton, BASE_MVC, _l ){
], function( STATES, DATASET_LI, TAGS, ANNOTATIONS, faIconButton, BASE_MVC, ToolsForm, _l ){
//==============================================================================
var _super = DATASET_LI.DatasetListItemView;
/** @class Editing view for DatasetAssociation.
@@ -157,13 +158,23 @@ var DatasetListItemEdit = _super.extend(
/** Render icon-button to re-run the job that created this dataset. */
_renderRerunButton : function(){
return faIconButton({
title : _l( 'Run this job again' ),
href : this.model.urls.rerun,
classes : 'rerun-btn',
target : this.linkTarget,
faIcon : 'fa-refresh'
});
var creating_job = this.model.get('creating_job');
if (this.model.get('rerunnable')){
return faIconButton({
title : _l( 'Run this job again' ),
href : this.model.urls.rerun,
classes : 'rerun-btn',
target : this.linkTarget,
faIcon : 'fa-refresh',
onclick : function(ev) {
ev.preventDefault();
var form = new ToolsForm.View({'job_id' : creating_job});
form.deferred.execute(function(){
Galaxy.app.display(form.$el);
});
}
});
}
},
/** Render an icon-button or popupmenu of links based on the applicable visualizations */
@@ -37,8 +37,6 @@ var DatasetListItemView = _super.extend(
/** where should pages from links be displayed? (default to new tab/window) */
this.linkTarget = attributes.linkTarget || '_blank';
this._setUpListeners();
},
/** event listeners */
@@ -60,7 +58,7 @@ var DatasetListItemView = _super.extend(
},
// ......................................................................... expandable
/** In this override, only get details if in the ready state.
/** In this override, only get details if in the ready state, get rerunnable if in other states.
* Note: fetch with no 'change' event triggering to prevent automatic rendering.
*/
_fetchModelDetails : function(){
+1 -12
View File
@@ -100,14 +100,11 @@ define([], function() {
/** Main Template
*/
_template: function(options) {
// create table element
var tmp = '<div class="ui-table-form-element">' +
var tmp = '<div class="ui-table-form-element input-name-' + options.name + '">' +
'<div class="ui-table-form-error ui-error">' +
'<span class="fa fa-arrow-down"/><span class="ui-table-form-error-text"/>' +
'</div>' +
'<div class="ui-table-form-title">';
// is optional
if (options.collapsible) {
tmp += '<div class="ui-table-form-optional">' +
'<i class="icon"/>' + options.label +
@@ -115,12 +112,8 @@ define([], function() {
} else {
tmp += options.label;
}
// finalize title
tmp += '</div>' +
'<div class="ui-table-form-field">';
// add help
tmp += '<div class="ui-table-form-info">';
if (options.help) {
tmp += options.help;
@@ -129,12 +122,8 @@ define([], function() {
}
}
tmp += '</div>';
// finalize
tmp += '</div>' +
'</div>';
// return input element
return tmp;
}
});
@@ -29,7 +29,7 @@ define(['utils/utils',
// create table
this.table = new Table.View(options);
// create parameter handler
this.parameters = new Parameters(app, options);
@@ -179,7 +179,7 @@ define(['utils/utils',
onnew : function() {
// create
create(input_def.inputs);
// trigger refresh
self.app.trigger('change');
}
@@ -202,7 +202,7 @@ define(['utils/utils',
ondel : function() {
// delete repeat block
repeat.del(sub_section_id);
// trigger refresh
self.app.trigger('change');
}
@@ -245,7 +245,7 @@ define(['utils/utils',
_addSection: function(input_def) {
// link this
var self = this;
// create sub section
var sub_section = new View(self.app, {
inputs : input_def.inputs
@@ -316,6 +316,7 @@ define(['utils/utils',
// create input field wrapper
var input_element = new InputElement(this.app, {
name : input_def.name,
label : input_def.label || input_def.name,
value : input_def.value,
default_value : input_def.default_value,
@@ -348,4 +349,4 @@ define(['utils/utils',
return {
View: View
};
});
});
@@ -2,9 +2,6 @@
define(['utils/utils', 'mvc/ui/ui-misc', 'mvc/ui/ui-tabs', 'mvc/tools/tools-template'],
function(Utils, Ui, Tabs, ToolTemplate) {
// track current history elements
history = {};
// hda/hdca content selector ui element
var View = Backbone.View.extend({
// initialize
@@ -13,6 +10,10 @@ var View = Backbone.View.extend({
this.app = app;
this.options = options;
// track current history elements
this.history = {};
// link this
var self = this;
@@ -197,7 +198,7 @@ var View = Backbone.View.extend({
value: item.id
});
// backup to local history
history[item.id + '_' + item.src] = item;
self.history[item.id + '_' + item.src] = item;
}
// update field
field.update(select_options);
@@ -272,7 +273,7 @@ var View = Backbone.View.extend({
// append to dataset ids
for (var i in id_list) {
var details = history[id_list[i] + '_' + this.list[this.current].type];
var details = this.history[id_list[i] + '_' + this.list[this.current].type];
if (details) {
result.values.push(details);
} else {
@@ -315,7 +316,7 @@ var View = Backbone.View.extend({
/** Assists in identifying the batch mode */
_batch: function() {
if (this.current == 'collection') {
var hdca = history[this._select().value() + '_hdca'];
var hdca = this.history[this._select().value() + '_hdca'];
if (hdca && hdca.map_over_type) {
return true;
}
+11 -17
View File
@@ -91,13 +91,6 @@ define(['utils/utils', 'mvc/ui/ui-portlet', 'mvc/ui/ui-misc',
}
},
/** Shows the final message (usually upon successful job submission)
*/
reciept: function($el) {
this.$el.empty();
this.$el.append($el);
},
/** Highlight and scroll to input element (currently only used for error notifications)
*/
highlight: function (input_id, message, silent) {
@@ -114,9 +107,14 @@ define(['utils/utils', 'mvc/ui/ui-portlet', 'mvc/ui/ui-misc',
// scroll to first input element
if (!silent) {
$('html, body').animate({
scrollTop: input_element.$el.offset().top - 20
}, 500);
if (self==top) {
var $panel = this.$el.parents().filter(function() {
return $(this).css('overflow') == 'auto';
}).first();
$panel.animate({ scrollTop : $panel.scrollTop() + input_element.$el.offset().top - 50 }, 500);
} else {
$('html, body').animate({ scrollTop : input_element.$el.offset().top - 20 }, 500);
}
}
}
},
@@ -201,12 +199,8 @@ define(['utils/utils', 'mvc/ui/ui-portlet', 'mvc/ui/ui-misc',
inputs : this.options.inputs
});
// switch to classic tool form mako if the form definition is incompatible
if (this.incompatible) {
this.$el.hide();
$('#tool-form-classic').show();
return;
}
// remove tooltips
$( '.tooltip' ).remove();
// create portlet
this.portlet = new Portlet.View({
@@ -240,4 +234,4 @@ define(['utils/utils', 'mvc/ui/ui-portlet', 'mvc/ui/ui-misc',
console.debug('tools-form-base::initialize() - Completed.');
}
});
});
});
+1 -1
View File
@@ -118,4 +118,4 @@ return Backbone.Model.extend({
});
});
});
+77 -71
View File
@@ -14,13 +14,13 @@ return Backbone.View.extend({
// model
grid: null,
// Initialize
initialize: function(grid_config)
{
// set element
this.setElement('#grid-container');
// fix padding
if (grid_config.use_panels) {
$('#center').css ({
@@ -28,11 +28,11 @@ return Backbone.View.extend({
overflow : 'auto'
});
}
// initialize controls
this.init_grid(grid_config);
},
// refresh frames
handle_refresh: function (refresh_frames) {
if (refresh_frames) {
@@ -49,26 +49,26 @@ return Backbone.View.extend({
{
// link grid model
this.grid = new GridModel(grid_config);
// get options
var options = this.grid.attributes;
// handle refresh requests
this.handle_refresh(options.refresh_frames);
// strip protocol and domain
var url = this.grid.get('url_base');
url = url.replace(/^.*\/\/[^\/]+/, '');
this.grid.set('url_base', url);
// append main template
this.$el.html(Templates.grid(options));
// update div contents
this.$el.find('#grid-table-header').html(Templates.header(options));
this.$el.find('#grid-table-body').html(Templates.body(options));
this.$el.find('#grid-table-footer').html(Templates.footer(options));
// update message
if (options.message) {
this.$el.find('#grid-message').html(Templates.message(options));
@@ -77,37 +77,37 @@ return Backbone.View.extend({
setTimeout( function() { self.$el.find('#grid-message').html(''); }, 5000);
}
}
// configure elements
this.init_grid_elements();
this.init_grid_controls();
// attach global event handler
init_refresh_on_change();
},
// Initialize grid controls
init_grid_controls: function() {
// link
var self = this;
// Initialize grid operation button.
this.$el.find('.operation-button').each(function() {
$(this).off();
$(this).click(function() {
self.submit_operation(this, operation.confirm);
self.submit_operation(this);
return false;
});
});
// Initialize text filters to select text on click and use normal font when user is typing.
this.$el.find('input[type=text]').each(function() {
$(this).off();
$(this).click(function() { $(this).select(); } )
.keyup(function () { $(this).css('font-style', 'normal'); });
});
// Initialize sort links.
this.$el.find('.sort-link').each( function() {
$(this).off();
@@ -116,7 +116,7 @@ return Backbone.View.extend({
return false;
});
});
// Initialize text filters.
this.$el.find('.text-filter-form').each( function() {
$(this).off();
@@ -129,14 +129,14 @@ return Backbone.View.extend({
return false;
});
});
// Initialize categorical filters.
this.$el.find('.text-filter-val > a').each( function() {
$(this).off();
$(this).click( function() {
// Remove visible element.
$(this).parent().remove();
// Remove filter condition.
self.remove_filter_condition ($(this).attr('filter_key'), $(this).attr('filter_val'));
@@ -144,7 +144,7 @@ return Backbone.View.extend({
return false;
});
});
// Initialize categorical filters.
this.$el.find('.categorical-filter > a').each( function() {
$(this).off();
@@ -153,7 +153,7 @@ return Backbone.View.extend({
return false;
});
});
// Initialize autocomplete for text inputs in search UI.
var t1 = this.$el.find('#input-tags-filter');
if (t1.length) {
@@ -165,7 +165,7 @@ return Backbone.View.extend({
t2.autocomplete(this.grid.history_name_autocomplete_url,
{ selectFirst: false, autoFill: false, highlight: false, mustMatch: false });
}
// Initialize standard, advanced search toggles.
this.$el.find('.advanced-search-toggle').each( function() {
$(this).off();
@@ -175,7 +175,7 @@ return Backbone.View.extend({
return false;
});
});
// Add event to check all box
this.$el.find('#check_all').off();
this.$el.find('#check_all').on('click', function() {
@@ -192,13 +192,13 @@ return Backbone.View.extend({
var update_checked = function() {
check_count.text( $(checkboxes).filter(":checked").length );
};
$(checkboxes).each( function() {
$(this).change(update_checked);
});
update_checked();
});
// Initialize ratings.
if (this.$el.find('.community_rating_star').length !== 0)
this.$el.find('.community_rating_star').rating({});
@@ -206,7 +206,7 @@ return Backbone.View.extend({
// get options
var options = this.grid.attributes;
var self = this;
//
// add page click events
//
@@ -216,7 +216,7 @@ return Backbone.View.extend({
return false;
});
});
//
// add inbound/outbound events
//
@@ -227,10 +227,10 @@ return Backbone.View.extend({
inbound : true
});
return false;
});
});
this.$el.find('.use-outbound').each( function() {
$(this).click( function(e) {
self.execute({
@@ -239,13 +239,13 @@ return Backbone.View.extend({
return false;
});
});
// empty grid?
var items_length = options.items.length;
if (items_length == 0) {
return;
}
//
// add operation popup menus
//
@@ -253,12 +253,12 @@ return Backbone.View.extend({
{
// get items
var item = options.items[i];
// get identifiers
var button = this.$el.find('#grid-' + i + '-popup');
button.off();
var popup = new PopupMenu(button);
// load details
for (var j in options['operations'])
{
@@ -267,7 +267,7 @@ return Backbone.View.extend({
var operation_id = operation['label'];
var operation_settings = item['operation_config'][operation_id];
var encode_id = item['encode_id'];
// check
if (operation_settings['allowed'] && operation['allow_popup'])
{
@@ -280,7 +280,7 @@ return Backbone.View.extend({
confirmation_text : operation['confirm'],
inbound : operation['inbound']
};
// add popup function
popupConfig.func = function(e)
{
@@ -289,7 +289,7 @@ return Backbone.View.extend({
var options = this.findItemByHtml(label);
self.execute(options);
};
// add item
popup.addItem(popupConfig);
}
@@ -303,10 +303,10 @@ return Backbone.View.extend({
if (value === "") {
return false;
}
// Add condition to grid.
this.grid.add_filter(name, value, true);
// Add button that displays filter and provides a button to delete it.
var t = $(Templates.filter_element(name, value));
var self = this;
@@ -321,7 +321,7 @@ return Backbone.View.extend({
// append to container
var container = this.$el.find('#' + name + '-filtering-criteria');
container.append(t);
// execute
this.go_page_one();
this.execute();
@@ -331,12 +331,12 @@ return Backbone.View.extend({
remove_filter_condition: function (name, value) {
// Remove filter condition.
this.grid.remove_filter(name, value);
// Execute
this.go_page_one();
this.execute();
},
// Set sort condition for grid.
set_sort_condition: function (col_key) {
// Set new sort condition. New sort is col_key if sorting new column; if reversing sort on
@@ -349,17 +349,17 @@ return Backbone.View.extend({
new_sort = '-' + col_key;
}
}
// Remove sort arrows elements.
this.$el.find('.sort-arrow').remove();
// Add sort arrow element to new sort column.
var sort_arrow = (new_sort.substring(0,1) == '-') ? '&uarr;' : '&darr;';
var t = $('<span>' + sort_arrow + '</span>').addClass('sort-arrow');
// Add to header
this.$el.find('#' + col_key + '-header').append(t);
// Update grid.
this.grid.set('sort_key', new_sort);
this.go_page_one();
@@ -394,7 +394,7 @@ return Backbone.View.extend({
$(this).append(t);
}
});
// Update grid.
this.grid.add_filter(name, new_value);
this.go_page_one();
@@ -417,7 +417,7 @@ return Backbone.View.extend({
$(this).empty();
$(this).addClass('inactive-link');
$(this).text(text);
}
}
else if (page_num === cur_page) {
// Add hyperlink to this page since grid will no longer be on this page. It is assumed that
// this element has a single child, a hyperlink/anchor.
@@ -445,26 +445,32 @@ return Backbone.View.extend({
{
// identify operation
var operation_name = $(operation_button).val();
// verify in any item is selected
var number_of_checked_ids = this.$el.find('input[name="id"]:checked').length;
if (!number_of_checked_ids > 0) {
return false;
}
// Check to see if there's grid confirmation text for this operation
var operation = _.findWhere(this.grid.attributes.operations, {label: operation_name});
if (operation && !confirmation_text){
confirmation_text = operation.confirm || '';
}
// collect ids
var item_ids = [];
this.$el.find('input[name=id]:checked').each(function() {
item_ids.push( $(this).val() );
});
// execute operation
this.execute({
operation: operation_name,
id: item_ids,
confirmation_text: confirmation_text
});
// return
return true;
},
@@ -492,14 +498,14 @@ return Backbone.View.extend({
}
this.init_grid_elements();
},
// Go back to page one; this is useful when a filter is applied.
go_page_one: function () {
// Need to go back to page 1 if not showing all.
var cur_page = this.grid.get('cur_page');
if (cur_page !== null && cur_page !== undefined && cur_page !== 'all') {
this.grid.set('cur_page', 1);
}
}
},
//
@@ -532,7 +538,7 @@ return Backbone.View.extend({
var href_parms = href_parms_str.split("&");
for (var index = 0; index < href_parms.length; index++) {
if (href_parms[index].indexOf('operation') != -1) {
// Found operation parm; get operation value.
// Found operation parm; get operation value.
operation = href_parms[index].split('=')[1];
operation = operation.replace (/\+/g, ' ');
} else if (href_parms[index].indexOf('id') != -1) {
@@ -543,7 +549,7 @@ return Backbone.View.extend({
}
}
}
// check for operation details
if (operation && id) {
// show confirmation box
@@ -566,11 +572,11 @@ return Backbone.View.extend({
} else {
this.go_to(inbound, href);
}
// done
return false;
}
// refresh grid
if (href) {
this.go_to(inbound, href);
@@ -593,23 +599,23 @@ return Backbone.View.extend({
// get aysnc status
var async = this.grid.get('async');
this.grid.set('async', false);
// get slide status
advanced_search = this.$el.find('#advanced-search').is(':visible');
this.grid.set('advanced_search', advanced_search);
// get default url
if(!href) {
href = this.grid.get('url_base') + '?' + $.param(this.grid.get_url_data());
}
// clear grid of transient request attributes.
this.grid.set({
operation: undefined,
item_ids: undefined,
async: async
});
if (inbound) {
// this currently assumes that there is only a single grid shown at a time
var $div = $('.grid-header').closest('.inbound');
@@ -618,7 +624,7 @@ return Backbone.View.extend({
return;
}
}
window.location = href;
},
@@ -626,7 +632,7 @@ return Backbone.View.extend({
update_grid: function () {
// If there's an operation, do POST; otherwise, do GET.
var method = (this.grid.get('operation') ? 'POST' : 'GET' );
// Show overlay to indicate loading and prevent user actions.
this.$el.find('.loading-elt-overlay').show();
var self = this;
@@ -636,21 +642,21 @@ return Backbone.View.extend({
data: self.grid.get_url_data(),
error: function(response) { alert( 'Grid refresh failed' );},
success: function(response_text) {
// backup
var embedded = self.grid.get('embedded');
var insert = self.grid.get('insert');
// request new configuration
var json = $.parseJSON(response_text);
// update
json.embedded = embedded;
json.insert = insert;
// Initialize new grid config
self.init_grid(json);
// Hide loading overlay.
self.$el.find('.loading-elt-overlay').hide();
},
@@ -661,7 +667,7 @@ return Backbone.View.extend({
item_ids: undefined
});
}
});
});
}
});
@@ -0,0 +1,100 @@
define([
"utils/localization"
], function( _l ){
/* =============================================================================
Wrapper function around the global Galaxy.modal for use when copying histories.
==============================================================================*/
function _renderBody( vars ){
return [
'<form action="">',
'<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="', vars.defaultCopyName, '" />',
'<br />',
'<p>', _l( 'You can make a copy of the history that includes all datasets in the original history' ),
_l( ' or just the active (not deleted) datasets.' ), '</p>',
// copy non-deleted is the default
'<input name="copy-what" type="radio" id="copy-non-deleted" value="copy-non-deleted" checked />',
'<label for="copy-non-deleted">', _l( 'Copy only active (not deleted) datasets' ), '</label><br />',
'<input name="copy-what" type="radio" id="copy-all" value="copy-all" />',
'<label for="copy-all">', _l( 'Copy all datasets, including deleted ones' ), '</label><br />',
'</form>'
].join('');
}
function _validateName( name ){
if( !name ){
if( !Galaxy.modal.$( '#invalid-title' ).size() ){
var $invalidTitle = $( '<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( $invalidTitle );
}
return false;
}
return name;
}
function _renderCopyIndicator(){
return $([
'<p>', '<span class="fa fa-spinner fa-spin"></span> ', _l( 'Copying history' ), '...', '</p>'
].join( '' ))
//TODO: move out of inline
.css({ 'margin-top': '8px' });
}
/** show the dialog and handle validation, ajax, and callbacks */
function historyCopyDialog( history, options ){
options = options || {};
// fall back to un-notifying copy
if( !( Galaxy && Galaxy.modal ) ){
return history.copy();
}
// maybe better as multiselect dialog?
var historyName = history.get( 'name' ),
defaultCopyName = "Copy of '" + historyName + "'";
function copyHistory( name ){
var copyAllDatasets = Galaxy.modal.$( 'input[name="copy-what"]:checked' ).val() === 'copy-all',
$copyIndicator = _renderCopyIndicator();
Galaxy.modal.$( '.modal-body' ).children().replaceWith( $copyIndicator );
Galaxy.modal.$( 'button' ).prop( 'disabled', true );
history.copy( true, name, copyAllDatasets )
//TODO: make this unneccessary with pub-sub error or handling via Galaxy
.fail( function(){
alert( _l( 'History could not be copied. Please contact a Galaxy administrator' ) );
})
.always( function(){
Galaxy.modal.hide();
});
}
function checkNameAndCopy(){
var name = Galaxy.modal.$( '#copy-modal-title' ).val();
if( !_validateName( name ) ){ return; }
copyHistory( name );
}
Galaxy.modal.show( _.extend({
title : _l( 'Copying history' ) + ' "' + historyName + '"',
body : $( _renderBody({ defaultCopyName: defaultCopyName }) ),
buttons : {
'Cancel' : function(){ Galaxy.modal.hide(); },
'Copy' : checkNameAndCopy
},
closing_events : true
}, options ));
$( '#copy-modal-title' ).focus().select();
$( '#copy-modal-title' ).on( 'keydown', function( ev ){
if( ev.keyCode === 13 ){
checkNameAndCopy();
}
});
// TODO: return a promise completed on copy, close, or error
}
//==============================================================================
return historyCopyDialog;
});
@@ -16,13 +16,30 @@ var HDAListItemEdit = _super.extend(
className : _super.prototype.className + " history-content",
// ......................................................................... events
/** In this override, only get details if in the ready state, get rerunnable if in other states.
* Note: fetch with no 'change' event triggering to prevent automatic rendering.
*/
_fetchModelDetails : function(){
var view = this;
if( view.model.inReadyState() && !view.model.hasDetails() ){
return view.model.fetch({ silent: true });
// special case the need for the rerunnable and creating_job attributes
// needed for rendering re-run button on queued, running datasets
} else if( !view.model.has( 'rerunnable' ) ){
return view.model.fetch({ silent: true, data: {
// only fetch rerunnable and creating_job to keep overhead down
keys: [ 'rerunnable', 'creating_job' ].join(',')
}});
}
return jQuery.when();
},
/** event map */
events : _.extend( _.clone( _super.prototype.events ), {
'click .unhide-link' : function( ev ){ this.model.unhide(); return false; }
}),
// ......................................................................... misc
/** string rep */
toString : function(){
var modelString = ( this.model )?( this.model + '' ):( '(no model)' );
+4 -6
View File
@@ -21,11 +21,9 @@ var HDCAListItemView = _super.extend(
/** event listeners */
_setUpListeners : function(){
_super.prototype._setUpListeners.call( this );
// re-rendering on deletion
this.model.on( 'change', function( model, options ){
if( _.isEqual( _.keys( model.changed ), [ 'visible' ] ) ){
this.render();
}
this.model.on({
'change:populated change:visible' : function( model, options ){ this.render(); },
}, this );
},
@@ -46,7 +44,7 @@ var HDCAListItemView = _super.extend(
_swapNewRender : function( $newRender ){
_super.prototype._swapNewRender.call( this, $newRender );
//TODO: model currently has no state
var state = this.model.get( 'state' ) || STATES.OK;
var state = !this.model.get( 'populated' ) ? STATES.RUNNING : STATES.OK;
//if( this.model.has( 'state' ) ){
this.$el.addClass( 'state-' + state );
//}
@@ -25,7 +25,6 @@ var HistoryContents = Backbone.Collection.extend( BASE_MVC.LoggableMixin ).exten
/** since history content is a mix, override model fn into a factory, creating based on history_content_type */
model : function( attrs, options ) {
//TODO: can we move the type_id stuff here?
//attrs.type_id = typeIdStr( attrs );
@@ -134,13 +133,25 @@ var HistoryContents = Backbone.Collection.extend( BASE_MVC.LoggableMixin ).exten
return item.isVisible( show_deleted, show_hidden );
}));
_.each( filters, function( filter_fn ){
if( !_.isFunction( filter_fn ) ){ return; }
filteredHdas = new HistoryContents( filteredHdas.filter( filter_fn ) );
_.each( filters, function( filterFn ){
if( !_.isFunction( filterFn ) ){ return; }
filteredHdas = new HistoryContents( filteredHdas.filter( filterFn ) );
});
return filteredHdas;
},
/** return a new contents collection of only hidden items */
hidden : function(){
function filterFn( c ){ return c.hidden(); }
return new HistoryContents( this.filter( filterFn ) );
},
/** return a new contents collection of only hidden items */
deleted : function(){
function filterFn( c ){ return c.get( 'deleted' ); }
return new HistoryContents( this.filter( filterFn ) );
},
/** return true if any contents don't have details */
haveDetails : function(){
return this.all( function( content ){ return content.hasDetails(); });
@@ -244,6 +255,7 @@ var HistoryContents = Backbone.Collection.extend( BASE_MVC.LoggableMixin ).exten
// ........................................................................ misc
/** override to ensure type id is set */
set : function( models, options ){
models = _.isArray( models )? models : [ models ];
_.each( models, function( model ){
if( !model.type_id || !model.get || !model.get( 'type_id' ) ){
model.type_id = HISTORY_CONTENT.typeIdStr( model.history_content_type, model.id );
@@ -1,8 +1,10 @@
define([
"mvc/history/history-contents",
"utils/utils",
"mvc/base-mvc",
"utils/localization"
], function( HISTORY_CONTENTS, BASE_MVC, _l ){
], function( HISTORY_CONTENTS, UTILS, BASE_MVC, _l ){
//==============================================================================
/** @class Model for a Galaxy history resource - both a record of user
* tool use and a collection of the datasets those tools produced.
@@ -96,6 +98,26 @@ var History = Backbone.Model.extend( BASE_MVC.LoggableMixin ).extend(
this.clearUpdateTimeout();
},
/** convert size in bytes to a more human readable version */
nice_size : function(){
return UTILS.bytesToString( this.get( 'size' ), true, 2 );
},
/** override to add nice_size */
toJSON : function(){
return _.extend( Backbone.Model.prototype.toJSON.call( this ), {
nice_size : this.nice_size()
});
},
/** override to allow getting nice_size */
get : function( key ){
if( key === 'nice_size' ){
return this.nice_size();
}
return Backbone.Model.prototype.get.apply( this, arguments );
},
// ........................................................................ common queries
/** T/F is this history owned by the current user (Galaxy.currUser)
* Note: that this will return false for an anon user even if the history is theirs.
@@ -222,7 +244,7 @@ var History = Backbone.Model.extend( BASE_MVC.LoggableMixin ).extend(
* @fires copied passed this history and the response JSON from the copy
* @returns {xhr}
*/
copy : function( current, name ){
copy : function( current, name, allDatasets ){
current = ( current !== undefined )?( current ):( true );
if( !this.id ){
throw new Error( 'You must set the history ID before copying it.' );
@@ -235,8 +257,9 @@ var History = Backbone.Model.extend( BASE_MVC.LoggableMixin ).extend(
if( name ){
postData.name = name;
}
//TODO:?? all datasets?
if( !allDatasets ){
postData.all_datasets = false;
}
var history = this,
copy = jQuery.post( this.urlRoot, postData );
@@ -354,60 +377,265 @@ History.getHistoryData = function getHistoryData( historyId, options ){
};
//==============================================================================
var ControlledFetchMixin = {
/** Override to convert certain options keys into API index parameters */
fetch : function( options ){
options = options || {};
options.data = options.data || this._buildFetchData( options );
// use repeated params for arrays, e.g. q=1&qv=1&q=2&qv=2
options.traditional = true;
return Backbone.Collection.prototype.fetch.call( this, options );
},
/** These attribute keys are valid params to fetch/API-index */
_fetchOptions : [
/** model dependent string to control the order of models returned */
'order',
/** limit the number of models returned from a fetch */
'limit',
/** skip this number of models when fetching */
'offset',
/** what series of attributes to return (model dependent) */
'view',
/** individual keys to return for the models (see api/histories.index) */
'keys'
],
/** Build the data dictionary to send to fetch's XHR as data */
_buildFetchData : function( options ){
var data = {},
fetchDefaults = this._fetchDefaults();
options = _.defaults( options || {}, fetchDefaults );
data = _.pick( options, this._fetchOptions );
var filters = _.has( options, 'filters' )? options.filters : ( fetchDefaults.filters || {} );
if( !_.isEmpty( filters ) ){
_.extend( data, this._buildFetchFilters( filters ) );
}
return data;
},
/** Override to have defaults for fetch options and filters */
_fetchDefaults : function(){
// to be overridden
return {};
},
/** Convert dictionary filters to qqv style arrays */
_buildFetchFilters : function( filters ){
var filterMap = {
q : [],
qv : []
};
_.each( filters, function( v, k ){
if( v === true ){ v = 'True'; }
if( v === false ){ v = 'False'; }
filterMap.q.push( k );
filterMap.qv.push( v );
});
return filterMap;
},
};
//==============================================================================
/** @class A collection of histories (per user).
* (stub) currently unused.
*/
var HistoryCollection = Backbone.Collection.extend( BASE_MVC.LoggableMixin ).extend(
var HistoryCollection = Backbone.Collection.extend( BASE_MVC.LoggableMixin ).extend( ControlledFetchMixin ).extend(
/** @lends HistoryCollection.prototype */{
model : History,
/** logger used to record this.log messages, commonly set to console */
//logger : console,
urlRoot : ( window.galaxy_config? galaxy_config.root : '/' ) + 'api/histories',
//url : function(){ return this.urlRoot; },
/** @type {String} the default sortOrders key for sorting */
DEFAULT_ORDER : 'update_time',
/** @type {Object} map of collection sorting orders generally containing a getter to return the attribute
* sorted by and asc T/F if it is an ascending sort.
*/
sortOrders : {
'update_time' : {
getter : function( h ){ return new Date( h.get( 'update_time' ) ); },
asc : false
},
'update_time-asc' : {
getter : function( h ){ return new Date( h.get( 'update_time' ) ); },
asc : true
},
'name' : {
getter : function( h ){ return h.get( 'name' ); },
asc : true
},
'name-dsc' : {
getter : function( h ){ return h.get( 'name' ); },
asc : false
},
'size' : {
getter : function( h ){ return h.get( 'size' ); },
asc : false
},
'size-asc' : {
getter : function( h ){ return h.get( 'size' ); },
asc : true
}
},
initialize : function( models, options ){
options = options || {};
this.log( 'HistoryCollection.initialize', arguments );
// instance vars
/** @type {boolean} should deleted histories be included */
this.includeDeleted = options.includeDeleted || false;
// set the sort order
this.setOrder( options.order || this.DEFAULT_ORDER );
/** @type {String} encoded id of the history that's current */
this.currentHistoryId = options.currentHistoryId;
/** @type {boolean} have all histories been fetched and in the collection? */
this.allFetched = options.allFetched || false;
//this.on( 'all', function(){
// this.on( 'all', function(){
// console.info( 'event:', arguments );
//});
// });
this.setUpListeners();
},
setUpListeners : function setUpListeners(){
var collection = this;
urlRoot : ( window.galaxy_config? galaxy_config.root : '/' ) + 'api/histories',
url : function(){ return this.urlRoot; },
// when a history is deleted, remove it from the collection (if optionally set to do so)
this.on( 'change:deleted', function( history ){
this.debug( 'change:deleted', collection.includeDeleted, history.get( 'deleted' ) );
if( !collection.includeDeleted && history.get( 'deleted' ) ){
collection.remove( history );
}
});
// listen for a history copy, adding it to the beginning of the collection
this.on( 'copied', function( original, newData ){
this.unshift( new History( newData, [] ) );
});
/** returns map of default filters and settings for fetching from the API */
_fetchDefaults : function(){
// to be overridden
var defaults = {
order : this.order,
view : 'detailed'
};
if( !this.includeDeleted ){
defaults.filters = {
deleted : false,
purged : false,
};
}
return defaults;
},
/** set up reflexive event handlers */
setUpListeners : function setUpListeners(){
this.on({
// when a history is deleted, remove it from the collection (if optionally set to do so)
'change:deleted' : function( history ){
// TODO: this becomes complicated when more filters are used
this.debug( 'change:deleted', this.includeDeleted, history.get( 'deleted' ) );
if( !this.includeDeleted && history.get( 'deleted' ) ){
this.remove( history );
}
},
// listen for a history copy, setting it to current
'copied' : function( original, newData ){
this.setCurrent( new History( newData, [] ) );
},
// when a history is made current, track the id in the collection
'set-as-current' : function( history ){
var oldCurrentId = this.currentHistoryId;
this.trigger( 'no-longer-current', oldCurrentId );
this.currentHistoryId = history.id;
}
}, this );
},
/** override to allow passing options.order and setting the sort order to one of sortOrders */
sort : function( options ){
options = options || {};
this.setOrder( options.order );
return Backbone.Collection.prototype.sort.call( this, options );
},
/** build the comparator used to sort this collection using the sortOrder map and the given order key
* @event 'changed-order' passed the new order and the collection
*/
setOrder : function( order ){
var collection = this,
sortOrder = this.sortOrders[ order ];
if( _.isUndefined( sortOrder ) ){ return; }
collection.order = order;
collection.comparator = function comparator( a, b ){
var currentHistoryId = collection.currentHistoryId;
// current always first
if( a.id === currentHistoryId ){ return -1; }
if( b.id === currentHistoryId ){ return 1; }
// then compare by an attribute
a = sortOrder.getter( a );
b = sortOrder.getter( b );
return sortOrder.asc?
( ( a === b )?( 0 ):( a > b ? 1 : -1 ) ):
( ( a === b )?( 0 ):( a > b ? -1 : 1 ) );
};
collection.trigger( 'changed-order', collection.order, collection );
return collection;
},
/** override to provide order and offsets based on instance vars, set limit if passed,
* and set allFetched/fire 'all-fetched' when xhr returns
*/
fetch : function( options ){
options = options || {};
if( this.allFetched ){ return jQuery.when({}); }
var collection = this,
fetchOptions = _.defaults( options, {
remove : false,
offset : collection.length >= 1? ( collection.length - 1 ) : 0,
order : collection.order
}),
limit = options.limit;
if( !_.isUndefined( limit ) ){
fetchOptions.limit = limit;
}
return ControlledFetchMixin.fetch.call( this, fetchOptions )
.done( function _postFetchMore( fetchData ){
var numFetched = _.isArray( fetchData )? fetchData.length : 0;
// anything less than a full page means we got all there is to get
if( !limit || numFetched < limit ){
collection.allFetched = true;
collection.trigger( 'all-fetched', collection );
}
}
);
},
/** create a new history and by default set it to be the current history */
create : function create( data, hdas, historyOptions, xhrOptions ){
//TODO: .create is actually a collection function that's overridden here
var collection = this,
xhr = jQuery.getJSON( galaxy_config.root + 'history/create_new_current' );
return xhr.done( function( newData ){
var history = new History( newData, [], historyOptions || {} );
// new histories go in the front
//TODO: (implicit ordering by update time...)
collection.unshift( history );
collection.trigger( 'new-current' );
collection.setCurrent( new History( newData, [], historyOptions || {} ) );
});
//TODO: move back to using history.save (via Deferred.then w/ set_as_current)
},
/** set the current history to the given history, placing it first in the collection.
* Pass standard bbone options for use in unshift.
* @triggers new-current passed history and this collection
*/
setCurrent : function( history, options ){
options = options || {};
// new histories go in the front
this.unshift( history, options );
this.currentHistoryId = history.get( 'id' );
if( !options.silent ){
this.trigger( 'new-current', history, this );
}
return this;
},
/** override to reset allFetched flag to false */
reset : function( models, options ){
this.allFetched = false;
return Backbone.Collection.prototype.reset.call( this, models, options );
},
toString: function toString(){
@@ -87,14 +87,6 @@ var CurrentHistoryPanel = _super.extend(
_super.prototype._setUpListeners.call( this );
var panel = this;
// cache the scroll position (only if visible)
this.$scrollContainer().on( 'scroll', function( ev ){
if( panel.$el.is( ':visible' ) ){
panel.preferences.set( 'scrollPosition', $( this ).scrollTop() );
}
});
//TODO: doesn't work
// reset scroll position when there's a new history
this.on( 'new-model', function(){
panel.preferences.set( 'scrollPosition', 0 );
@@ -191,6 +183,30 @@ var CurrentHistoryPanel = _super.extend(
},
// ------------------------------------------------------------------------ panel rendering
/** override to add a handler to capture the scroll position when the parent scrolls */
_setUpBehaviors : function( $where ){
$where = $where || this.$el;
// we need to call this in _setUpBehaviors which is called after render since the $el
// may not be attached to $el.parent and $scrollContainer() may not work
var panel = this;
_super.prototype._setUpBehaviors.call( panel, $where );
// cache the handler to remove and re-add so we don't pile up the handlers
if( !this._debouncedScrollCaptureHandler ){
this._debouncedScrollCaptureHandler = _.debounce( function scrollCapture(){
// cache the scroll position (only if visible)
if( panel.$el.is( ':visible' ) ){
panel.preferences.set( 'scrollPosition', $( this ).scrollTop() );
}
}, 40 );
}
panel.$scrollContainer()
.off( 'scroll', this._debouncedScrollCaptureHandler )
.on( 'scroll', this._debouncedScrollCaptureHandler );
return panel;
},
/** In this override, handle null models and move the search input to the top */
_buildNewRender : function(){
if( !this.model ){ return $(); }
@@ -281,7 +297,7 @@ var CurrentHistoryPanel = _super.extend(
_.delay( function(){
var pos = panel.preferences.get( 'scrollPosition' );
if( pos ){
panel.scrollTo( pos );
panel.scrollTo( pos, 0 );
}
}, 10 );
//TODO: is this enough of a delay on larger histories?
@@ -109,7 +109,7 @@ var HistoryPanelEdit = _super.extend(
/** listening for history and HDA events */
_setUpModelListeners : function(){
_super.prototype._setUpModelListeners.call( this );
this.model.on( 'change:nice_size', this.updateHistoryDiskSize, this );
this.model.on( 'change:size', this.updateHistoryDiskSize, this );
return this;
},
@@ -143,17 +143,21 @@ var HistoryPanel = _super.extend(
*/
_setUpListeners : function(){
_super.prototype._setUpListeners.call( this );
this.on( 'error', function( model, xhr, options, msg, details ){
this.errorHandler( model, xhr, options, msg, details );
});
this.on( 'loading-done', function(){
//TODO:?? if( this.collection.length ){
if( !this.views.length ){
this.trigger( 'empty-history', this );
this.on({
error : function( model, xhr, options, msg, details ){
this.errorHandler( model, xhr, options, msg, details );
},
'loading-done' : function(){
//TODO:?? if( this.collection.length ){
if( !this.views.length ){
this.trigger( 'empty-history', this );
}
},
'views:ready view:attached view:removed' : function( view ){
this._renderSelectButton();
}
});
// this.on( 'all', function(){ console.debug( arguments ); });
},
// ------------------------------------------------------------------------ loading history/hda models
@@ -304,20 +308,34 @@ var HistoryPanel = _super.extend(
/** In this override, add a btn to toggle the selectors */
_buildNewRender : function(){
var $newRender = _super.prototype._buildNewRender.call( this );
//if( this.views.length && this.multiselectActions().length ){
if( this.multiselectActions().length ){
$newRender.find( '.controls .actions' ).prepend( this._renderSelectButton() );
}
this._renderSelectButton( $newRender );
return $newRender;
},
/** button for starting select mode */
_renderSelectButton : function( $where ){
$where = $where || this.$el;
// do not render selector option if no actions
if( !this.multiselectActions().length ){
return null;
}
// do not render (and remove even) if nothing to select
if( !this.views.length ){
this.hideSelectors();
$where.find( '.controls .actions .show-selectors-btn' ).remove();
return null;
}
// don't bother rendering if there's one already
var $existing = $where.find( '.controls .actions .show-selectors-btn' );
if( $existing.size() ){
return $existing;
}
return faIconButton({
title : _l( 'Operations on multiple datasets' ),
classes : 'show-selectors-btn',
faIcon : 'fa-check-square-o'
});
}).prependTo( $where.find( '.controls .actions' ) );
},
// ------------------------------------------------------------------------ sub-views
@@ -398,9 +416,9 @@ var HistoryPanel = _super.extend(
if( store ){
this.storage.set( 'show_deleted', show );
}
this.trigger( 'show-hidden', show );
//TODO:?? to events on storage('change:show_deleted')
this.renderItems();
this.trigger( 'show-deleted', show );
return this.showDeleted;
},
@@ -416,9 +434,9 @@ var HistoryPanel = _super.extend(
if( store ){
this.storage.set( 'show_hidden', show );
}
this.trigger( 'show-hidden', show );
//TODO:?? to events on storage('change:show_deleted')
this.renderItems();
this.trigger( 'show-hidden', show );
return this.showHidden;
},
@@ -439,7 +457,7 @@ var HistoryPanel = _super.extend(
panel.$el.find( inputSelector ).searchInput( 'toggle-loading' );
})
.done( function(){
panel.searchItems( searchFor );
panel.searchItems( panel.searchFor );
});
},
+265 -264
View File
@@ -1,102 +1,29 @@
define([
"mvc/history/history-model",
"mvc/history/history-panel-edit",
"mvc/history/copy-dialog",
"mvc/base-mvc",
"utils/ajax-queue",
"ui/mode-button",
"ui/search-input"
], function( HISTORY_MODEL, HPANEL_EDIT, baseMVC, ajaxQueue ){
//==============================================================================
/** */
function historyCopyDialog( history, options ){
options = options || {};
// fall back to un-notifying copy
if( !( Galaxy && Galaxy.modal ) ){
return history.copy();
}
// maybe better as multiselect dialog?
var historyName = history.get( 'name' ),
defaultCopyName = "Copy of '" + historyName + "'";
function validateName( name ){
if( !name ){
if( !Galaxy.modal.$( '#invalid-title' ).size() ){
var $invalidTitle = $( '<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( $invalidTitle );
}
return false;
}
return name;
}
function copyHistory( name ){
var $copyIndicator = $( '<p><span class="fa fa-spinner fa-spin"></span> Copying history...</p>' )
.css( 'margin-top', '8px' );
Galaxy.modal.$( '.modal-body' ).append( $copyIndicator );
history.copy( true, name )
//TODO: make this unneccessary with pub-sub error
.fail( function(){
alert( _l( 'History could not be copied. Please contact a Galaxy administrator' ) );
})
.always( function(){
Galaxy.modal.hide();
});
}
function checkNameAndCopy(){
var name = Galaxy.modal.$( '#copy-modal-title' ).val();
if( !validateName( name ) ){ return; }
copyHistory( name );
}
Galaxy.modal.show( _.extend({
title : _l( 'Copying history' ) + ' "' + historyName + '"',
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="',
defaultCopyName, '" />'
].join('')),
buttons : {
'Cancel' : function(){ Galaxy.modal.hide(); },
'Copy' : checkNameAndCopy
}
}, options ));
$( '#copy-modal-title' ).focus().select();
$( '#copy-modal-title' ).on( 'keydown', function( ev ){
if( ev.keyCode === 13 ){
checkNameAndCopy();
}
});
}
], function( HISTORY_MODEL, HPANEL_EDIT, historyCopyDialog, baseMVC, ajaxQueue ){
/* ==============================================================================
TODO:
rendering/delayed rendering is a mess
may not need to render columns in renderColumns (just moving them/attaching them may be enough)
copy places old current in wrong location
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())
performance
rendering columns could be better
lazy load history list (for large numbers)
req. API limit/offset
move in-view from pubsub
handle errors
handle no histories
handle no histories found
handle anon
no button
error message on loading url direct
include hidden/deleted
allow toggling w/o setting in storage
reloading with expanded collections doesn't get details of that collection
@@ -150,28 +77,38 @@ var HistoryPanelColumn = Backbone.View.extend( baseMVC.LoggableMixin ).extend({
return panel;
},
//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,
// a more useful attribute, but does not account for collections (for some reason)
// hdaCount = _.chain( this.model.get( 'state_ids' ) ).values().flatten().value().length,
empty = panel.model.get( 'empty' ),
newMsg = panel.emptyMsg,
$emptyMsg = panel.$emptyMessage( $whereTo );
if( !_.isEmpty( panel.hdaViews ) ){
$emptyMsg.hide();
} else if( hdaCount && !this.model.contents.length ){
$emptyMsg.empty()
.append( $( '<span class="fa fa-spinner fa-spin"></span> <i>loading datasets...</i>' ) ).show();
} else if( panel.searchFor ){
$emptyMsg.text( panel.noneFoundMsg ).show();
} else {
$emptyMsg.text( panel.emptyMsg ).show();
return $emptyMsg.hide();
}
return $emptyMsg;
if( !empty ){
if( !this.model.contents.length ){
newMsg = '<span class="fa fa-spinner fa-spin"></span> <i>' + _l( 'loading datasets' ) + '...</i>';
} if( panel.searchFor ){
// this is a hack until HDCAs implement searching and haveDetails entirely
var mixed = !!panel.model.contents.find( function( c ){
return c.get( 'model_class' ) !== 'HistoryDatasetAssociation';
});
if( !mixed && !panel.model.contents.haveDetails() ){
newMsg = '<span class="fa fa-spinner fa-spin"></span> <i>' + _l( 'searching' ) + '...</i>';
} else {
newMsg = panel.noneFoundMsg;
}
}
}
return $emptyMsg.empty().append( newMsg ).show();
},
/** set up reflexive listeners */
@@ -384,26 +321,18 @@ var MultiPanelColumns = Backbone.View.extend( baseMVC.LoggableMixin ).extend({
this.$el.addClass( this.className );
// --- instance vars
if( !options.currentHistoryId ){
throw new Error( this + ' requires a currentHistoryId in the options' );
}
this.currentHistoryId = options.currentHistoryId;
//TODO: move these to some defaults
this.options = {
columnWidth : 312,
borderWidth : 1,
columnGap : 8,
headerHeight : 29,
footerHeight : 0,
controlsHeight : 20
};
/** the order that the collection is rendered in */
this.order = options.order || 'update';
this._initSortOrders();
/** how many histories to get when fetching a new batch/page */
this.perPage = options.perPage || 10;
/** named ajax queue for loading hdas */
this.hdaQueue = new ajaxQueue.NamedAjaxQueue( [], false );
@@ -411,107 +340,107 @@ var MultiPanelColumns = Backbone.View.extend( baseMVC.LoggableMixin ).extend({
// --- set up models, sub-views, and listeners
/** the original unfiltered and unordered collection of histories */
this.collection = null;
this.setCollection( options.histories || [] );
/** model id to column map */
this.columnMap = {};
//TODO: why create here?
this.createColumns( options.columnOptions );
/** model id to column map */
this.columnOptions = options.columnOptions || {};
/** what to search for within all histories */
this.historySearch = null;
/** what to search for within all datasets */
this.datasetSearch = null;
this.setCollection( options.histories );
this.setUpListeners();
},
_initSortOrders : function(){
function _comparator( getter, options ){
return function __comparator( a, b, currentHistoryId ){
// current always first
if( a.id === currentHistoryId ){ return -1; }
if( b.id === currentHistoryId ){ return 1; }
// then compare by an attribute
a = getter( a );
b = getter( b );
return options.asc?
( ( a === b )?( 0 ):( a > b ? 1 : -1 ) ):
( ( a === b )?( 0 ):( a > b ? -1 : 1 ) );
};
}
this.sortOrders = {
// default
update : {
text: _l( 'most recent first' ),
fn: _comparator( function( h ){ return new Date( h.get( 'update_time' ) ); }, { asc : false })
},
'name' : {
text: _l( 'name, a to z' ),
fn: _comparator( function( h ){ return h.get( 'name' ); }, { asc : true })
},
'name-dsc' : {
text: _l( 'name, z to a' ),
fn: _comparator( function( h ){ return h.get( 'name' ); }, { asc : false })
},
'size' : {
text: _l( 'size, large to small' ),
fn: _comparator( function( h ){ return h.get( 'size' ); }, { asc : false })
},
'size-dsc' : {
text: _l( 'size, small to large' ),
fn: _comparator( function( h ){ return h.get( 'size' ); }, { asc : true })
}
};
return this.sortOrders;
},
/** Set up reflexive listeners */
setUpListeners : function setUpListeners(){
//var multipanel = this;
var multipanel = this;
//multipanel.log( 'setUpListeners', multipanel );
this.on( 'end-of-scroll', function(){
multipanel.collection.fetch({ limit : multipanel.perPage });
});
},
// ------------------------------------------------------------------------ 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 ){
setCollection : function setCollection( collection ){
this.stopListening( this.collection );
this.collection = collection || new HISTORY_MODEL.HistoryCollection();
this.setUpCollectionListeners();
this.collection.sort({ silent : true });
this.createColumns();
this.hdaQueue.clear();
// multipanel.addModels( collection, { silent: true });
this.trigger( 'new-collection', this );
return this;
},
/** Set up a (new) history collection, sorting and adding listeners
* @fires 'new-collection' when set with this view as the arg
*/
addModels : function addModels( models, collection, options ){
options = options || {};
var multipanel = this;
multipanel.stopListening( multipanel.collection );
multipanel.collection = models;
multipanel.sortCollection( multipanel.order, { silent: true });
multipanel.setUpCollectionListeners();
multipanel.trigger( 'new-collection', multipanel );
return multipanel;
models = _.isArray( models )? models : [ models ];
models.forEach( function( model ){
multipanel.addColumn( model, false );
});
return this;
},
/** Set up listeners for the collection - handling: added histories, change of current, deletion, and sorting */
setUpCollectionListeners : function(){
var multipanel = this,
collection = multipanel.collection;
multipanel.listenTo( collection, {
var multipanel = this;
multipanel.listenTo( multipanel.collection, {
// handle ajax errors from the collection
'error' : multipanel._ajaxErrorHandler,
// add multiple models
'add' : multipanel.addModels,
// when all the histories a user has have been fetched
'all-fetched' : multipanel._postFetchAll,
// handle addition of histories, triggered by column copy and create new
'add': multipanel.addAsCurrentColumn,
'new-current' : multipanel.addAsCurrentColumn,
// handle setting a history as current, triggered by history.setAsCurrent
'set-as-current': multipanel.setCurrentHistory,
'set-as-current' : multipanel.setCurrentHistory,
// handle deleting a history (depends on whether panels is including deleted or not)
'change:deleted change:purged': multipanel.handleDeletedHistory,
'sort' : function(){ multipanel.renderColumns( 0 ); }
'change:deleted change:purged' : multipanel.handleDeletedHistory,
// re-render columns after a sort
'sort' : function(){
multipanel.renderColumns( 0 );
},
});
},
_postFetchAll : function( fetchData ){
this.$( '.histories-loading-indicator' ).remove();
// when new histories is fetched and the indicator is not required,
// the panel will jump slightly left - totally aesthetic but jarring
// TODO: this probably would be best handled elsewhere during a refinement cycle (if any)
if( !this.historySearch ){
var $scrollContainer = this.$( '.outer-middle' );
$scrollContainer.scrollLeft( $scrollContainer.scrollLeft() + 24 );
}
},
/** Re-render and set currentHistoryId to reflect a new current history */
setCurrentHistory : function setCurrentHistory( history ){
var oldCurrentColumn = this.columnMap[ this.currentHistoryId ];
var oldCurrentColumn = _.findWhere( this.columnMap, { currentHistory: true });
if( oldCurrentColumn ){
oldCurrentColumn.currentHistory = false;
oldCurrentColumn.$el.height( '' );
}
this.currentHistoryId = history.id;
var newCurrentColumn = this.columnMap[ this.currentHistoryId ];
var newCurrentColumn = this.columnMap[ this.collection.currentHistoryId ];
newCurrentColumn.currentHistory = true;
this.sortCollection();
multipanel._recalcFirstColumnHeight();
this.collection.sort();
this._recalcFirstColumnHeight();
return newCurrentColumn;
},
@@ -526,7 +455,7 @@ var MultiPanelColumns = Backbone.View.extend( baseMVC.LoggableMixin ).extend({
if( !column ){ return; }
// if it's the current column, create a new, empty history as the new current
if( column.model.id === this.currentHistoryId ){
if( column.model.id === this.collection.currentHistoryId ){
//TODO: figuring out the order of async here is tricky
// - for now let the user handle the two step process
//multipanel.collection.create().done( function(){
@@ -538,30 +467,15 @@ var MultiPanelColumns = Backbone.View.extend( baseMVC.LoggableMixin ).extend({
}
},
/** 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 = !_.isUndefined( order )? order : this.order;
if( !( order in this.sortOrders ) ){
order = 'update';
/** If Galaxy object is available handle error there, otherwise, locally (and crudely) */
_ajaxErrorHandler : function(){
var errArgs = _.toArray( arguments );
if( window.Galaxy && Galaxy.handleErrors ){
Galaxy.handleErrors.apply( Galaxy, errArgs );
} else {
alert( _l( 'There was a problem getting data from the server' ) );
console.error( errArgs[0].responseText );
}
this.order = order;
var currentHistoryId = this.currentHistoryId,
sortOrder = this.sortOrders[ order ];
this.collection.comparator = function __comparator( a, b ){
return sortOrder.fn( a, b, currentHistoryId );
};
// set the sort order text
this.$( '.current-order' ).text( sortOrder.text );
if( this.$( '.more-options' ).is( ':visible' ) ){
this.$( '.open-more-options.btn' ).popover( 'show' );
}
//NOTE: auto fires 'sort' from collection
this.collection.sort( options );
return this.collection;
},
/** create a new history and set it to current */
@@ -571,11 +485,12 @@ var MultiPanelColumns = Backbone.View.extend( baseMVC.LoggableMixin ).extend({
// ------------------------------------------------------------------------ columns
/** create columns from collection */
createColumns : function createColumns( columnOptions ){
columnOptions = columnOptions || {};
createColumns : function createColumns( models, columnOptions ){
columnOptions = columnOptions || this.options.columnOptions;
var multipanel = this;
// clear column map
this.columnMap = {};
// TODO: make cummulative
multipanel.columnMap = {};
multipanel.collection.each( function( model, i ){
var column = multipanel.createColumn( model, columnOptions );
multipanel.columnMap[ model.id ] = column;
@@ -590,41 +505,19 @@ var MultiPanelColumns = Backbone.View.extend( baseMVC.LoggableMixin ).extend({
purgeAllowed: Galaxy.config.allow_user_dataset_purge
});
var column = new HistoryPanelColumn( options );
if( history.id === this.currentHistoryId ){ column.currentHistory = true; }
if( history.id === this.collection.currentHistoryId ){ column.currentHistory = true; }
this.setUpColumnListeners( column );
return column;
},
/** return array of Columns filtered by filters and sorted to match the collection
* @param: filters Function[] array of filter fns
*/
sortedFilteredColumns : function( filters ){
filters = filters || this.filters;
if( !filters || !filters.length ){
return this.sortedColumns();
if( this.datasetSearch ){
column.panel.searchItems( this.datasetSearch );
this.queueHdaFetchDetails( column );
}
var multipanel = this;
return multipanel.sortedColumns().filter( function( column, index ){
var filtered = column.currentHistory || _.every( filters.map( function( filter ){
return filter.call( column );
}));
return filtered;
});
},
/** return array of Columns sorted to match the collection */
sortedColumns : function(){
var multipanel = this;
var sorted = this.collection.map( function( history, index ){
return multipanel.columnMap[ history.id ];
});
return sorted;
return column;
},
/** add a new column for history and render all columns if render is true */
addColumn : function add( history, render ){
//this.debug( 'adding column for:', history );
render = render !== undefined? render: true;
render = render !== undefined? render : true;
var newColumn = this.createColumn( history );
this.columnMap[ history.id ] = newColumn;
if( render ){
@@ -634,7 +527,7 @@ var MultiPanelColumns = Backbone.View.extend( baseMVC.LoggableMixin ).extend({
},
/** add a new column for history and make it the current history/column */
addAsCurrentColumn : function add( history ){
addAsCurrentColumn : function add( history, collection, options ){
//this.log( 'adding current column for:', history );
var multipanel = this,
newColumn = this.addColumn( history, false );
@@ -674,7 +567,7 @@ var MultiPanelColumns = Backbone.View.extend( baseMVC.LoggableMixin ).extend({
multipanel.listenTo( column, {
//'all': function(){ console.info( 'column ' + column + ':', arguments ) },
'in-view': multipanel.queueHdaFetch
});
});
multipanel.listenTo( column.panel, {
//'all': function(){ console.info( 'panel ' + column.panel + ':', arguments ) },
@@ -695,7 +588,9 @@ var MultiPanelColumns = Backbone.View.extend( baseMVC.LoggableMixin ).extend({
multipanel._dropData = null;
var queue = new ajaxQueue.NamedAjaxQueue();
toCopy.forEach( function( content ){
// need to reverse to better match expected order
// TODO: reconsider order in list-panel._setUpItemViewListeners, dragstart (instead of here)
toCopy.reverse().forEach( function( content ){
queue.add({
name : 'copy-' + content.id,
fn : function(){
@@ -716,6 +611,32 @@ var MultiPanelColumns = Backbone.View.extend( baseMVC.LoggableMixin ).extend({
return Object.keys( this.columnMap ).length;
},
/** return array of Columns filtered by filters and sorted to match the collection
* @param: filters Function[] array of filter fns
*/
sortedFilteredColumns : function( filters ){
filters = filters || this.filters;
if( !filters || !filters.length ){
return this.sortedColumns();
}
var multipanel = this;
return multipanel.sortedColumns().filter( function( column, index ){
var filtered = column.currentHistory || _.every( filters.map( function( filter ){
return filter.call( column );
}));
return filtered;
});
},
/** return array of Columns sorted to match the collection */
sortedColumns : function(){
var multipanel = this;
var sorted = this.collection.map( function( history, index ){
return multipanel.columnMap[ history.id ];
});
return sorted;
},
// ------------------------------------------------------------------------ render
/** Render this view, columns, and set up view plugins */
render : function render( speed ){
@@ -744,30 +665,41 @@ var MultiPanelColumns = Backbone.View.extend( baseMVC.LoggableMixin ).extend({
//this.log( '\t sortedAndFiltered:', sortedAndFiltered );
// set up width based on collection size
multipanel.$( '.middle' ).width( sortedAndFiltered.length
//TODO: magic number 16 === the amount that safely prevents stacking of columns when adding a new one
* ( this.options.columnWidth + this.options.columnGap ) + this.options.columnGap + 16 );
var $middle = multipanel.$( '.middle' ).empty()
.width( multipanel._calcMiddleWidth( sortedAndFiltered.length ) );
var $middle = multipanel.$( '.middle' );
$middle.empty();
sortedAndFiltered.forEach( function( column, i ){
column.$el.appendTo( $middle );
column.delegateEvents();
multipanel.renderColumn( column, speed );
});
//TODO: event columns-rendered
if( !multipanel.collection.allFetched ){
$middle.append( multipanel.loadingIndicatorTemplate( multipanel ) );
}
//TODO: sorta - at least their fx queue has started the re-rendering
multipanel.trigger( 'columns-rendered', sortedAndFiltered, multipanel );
if( this.searchFor && sortedAndFiltered.length <= 1 ){
if( multipanel.datasetSearch && 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();
multipanel._recalcFirstColumnHeight();
}
return sortedAndFiltered;
},
_calcMiddleWidth : function( numColumns ){
var preventStackWidthAdj = 16;
return (
numColumns * ( this.options.columnWidth + this.options.columnGap ) +
// last column gap
this.options.columnGap +
// the amount that safely prevents stacking of columns when adding a new one
preventStackWidthAdj
);
},
/** Render a single column using the non-blocking setTimeout( 0 ) pattern */
renderColumn : function( column, speed ){
speed = speed !== undefined? speed: this.fxSpeed;
@@ -833,15 +765,14 @@ var MultiPanelColumns = Backbone.View.extend( baseMVC.LoggableMixin ).extend({
/** */
events : {
// will move to the server root (gen. Analyze data)
'click .done.btn' : 'close',
'click .done.btn' : 'close',
// creates a new empty history and makes it current
'click .create-new.btn' : 'create',
'click #include-deleted' : '_clickToggleDeletedHistories',
'click .create-new.btn' : 'create',
'click #include-deleted' : '_clickToggleDeletedHistories',
// these change the collection and column sort order
'click .order .set-order' : '_chooseOrder',
'click #toggle-deleted' : '_clickToggleDeletedDatasets',
'click #toggle-hidden' : '_clickToggleHiddenDatasets'
'click .order .set-order' : '_chooseOrder',
'click #toggle-deleted' : '_clickToggleDeletedDatasets',
'click #toggle-hidden' : '_clickToggleHiddenDatasets',
//'dragstart .list-item .title-bar' : function( e ){ console.debug( 'ok' ); }
},
@@ -857,7 +788,8 @@ var MultiPanelColumns = Backbone.View.extend( baseMVC.LoggableMixin ).extend({
},
_clickToggleDeletedHistories : function( ev ){
return this.toggleDeletedHistories( $( ev.currentTarget ).is( ':checked' ) );
this.toggleDeletedHistories( $( ev.currentTarget ).is( ':checked' ) );
this.toggleOptionsPopover();
},
/** Include deleted histories in the collection */
toggleDeletedHistories : function( show ){
@@ -869,7 +801,8 @@ var MultiPanelColumns = Backbone.View.extend( baseMVC.LoggableMixin ).extend({
},
_clickToggleDeletedDatasets : function( ev ){
return this.toggleDeletedDatasets( $( ev.currentTarget ).is( ':checked' ) );
this.toggleDeletedDatasets( $( ev.currentTarget ).is( ':checked' ) );
this.toggleOptionsPopover();
},
toggleDeletedDatasets : function( show ){
show = show !== undefined? show : false;
@@ -882,7 +815,8 @@ var MultiPanelColumns = Backbone.View.extend( baseMVC.LoggableMixin ).extend({
},
_clickToggleHiddenDatasets : function( ev ){
return this.toggleHiddenDatasets( $( ev.currentTarget ).is( ':checked' ) );
this.toggleHiddenDatasets( $( ev.currentTarget ).is( ':checked' ) );
this.toggleOptionsPopover();
},
toggleHiddenDatasets : function( show ){
show = show !== undefined? show : false;
@@ -894,9 +828,27 @@ var MultiPanelColumns = Backbone.View.extend( baseMVC.LoggableMixin ).extend({
});
},
/** change the collection order and re-fetch when the drop down in the options menu is changed */
_chooseOrder : function( ev ){
var orderKey = $( ev.currentTarget ).data( 'order' );
this.sortCollection( orderKey );
var multipanel = this,
collection = multipanel.collection,
orderKey = $( ev.currentTarget ).data( 'order' );
// set the sort order text also
multipanel.$( '.current-order' ).text( multipanel.orderDescriptions[ orderKey ] );
multipanel.toggleOptionsPopover();
// set the order and re-fetch using the new order
collection.setOrder( orderKey );
collection.reset( collection.slice( 0, 1 ), { silent : true });
multipanel.setCollection( collection );
collection.fetch({ limit : multipanel.perPage });
multipanel.once( 'columns-rendered', multipanel._scrollLeft, this );
//TODO: check allFetched and do not reset if so - just sort instead
},
/** scroll the column container right or left */
_scrollLeft : function( val ){
this.$( '.outer-middle' ).scrollLeft( val || 0 );
},
/** Set up any view plugins */
@@ -908,15 +860,25 @@ var MultiPanelColumns = Backbone.View.extend( baseMVC.LoggableMixin ).extend({
multipanel.$( '#search-histories' ).searchInput({
name : 'search-histories',
placeholder : _l( 'search histories' ),
onfirstsearch : function( searchFor ){
multipanel.$( '#search-histories' ).searchInput( 'toggle-loading' );
multipanel.renderInfo( _l( 'loading all histories for search' ) );
multipanel.collection.fetch()
.done( function(){
multipanel.$( '#search-histories' ).searchInput( 'toggle-loading' );
multipanel.renderInfo( '' );
});
},
onsearch : function( searchFor ){
multipanel.searchFor = searchFor;
multipanel.historySearch = searchFor;
multipanel.filters = [ function(){
return this.model.matchesAll( multipanel.searchFor );
return this.model.matchesAll( multipanel.historySearch );
}];
multipanel.renderColumns( 0 );
},
onclear : function( searchFor ){
multipanel.searchFor = null;
multipanel.historySearch = null;
//TODO: remove specifically not just reset
multipanel.filters = [];
multipanel.renderColumns( 0 );
@@ -931,7 +893,7 @@ var MultiPanelColumns = Backbone.View.extend( baseMVC.LoggableMixin ).extend({
onfirstsearch : function( searchFor ){
multipanel.hdaQueue.clear();
multipanel.$( '#search-datasets' ).searchInput( 'toggle-loading' );
multipanel.searchFor = searchFor;
multipanel.datasetSearch = searchFor;
multipanel.sortedFilteredColumns().forEach( function( column ){
column.panel.searchItems( searchFor );
// load details for them that need
@@ -948,13 +910,13 @@ var MultiPanelColumns = Backbone.View.extend( baseMVC.LoggableMixin ).extend({
});
},
onsearch : function( searchFor ){
multipanel.searchFor = searchFor;
multipanel.datasetSearch = searchFor;
multipanel.sortedFilteredColumns().forEach( function( column ){
column.panel.searchItems( searchFor );
});
},
onclear : function( searchFor ){
multipanel.searchFor = null;
multipanel.datasetSearch = null;
multipanel.sortedFilteredColumns().forEach( function( column ){
column.panel.clearSearch();
});
@@ -969,11 +931,14 @@ var MultiPanelColumns = Backbone.View.extend( baseMVC.LoggableMixin ).extend({
// when scrolling - check for histories now in view: they will fire 'in-view' and queueHdaLoading if necc.
//TODO:?? might be able to simplify and not use pub-sub
var debouncedInView = _.debounce( function _debouncedInner(){
multipanel.checkColumnsInView();
var viewport = multipanel._viewport();
multipanel.checkColumnsInView( viewport );
multipanel.checkForEndOfScroll( viewport );
}, 100 );
this.$( '.middle' ).parent().scroll( debouncedInView );
},
/** create the options popover */
_moreOptionsPopover : function(){
return this.$( '.open-more-options.btn' ).popover({
container : '.header',
@@ -983,6 +948,12 @@ var MultiPanelColumns = Backbone.View.extend( baseMVC.LoggableMixin ).extend({
});
},
/** change the collection order and re-fetch when the drop down in the options menu is changed */
toggleOptionsPopover : function( ev ){
// hide seems broken in our version
this.$( '.open-more-options.btn' ).popover( 'toggle' );
},
/** Adjust the height of the first, current column since flex-boxes won't work with fixed postiion elements */
_recalcFirstColumnHeight : function(){
var $firstColumn = this.$( '.history-column' ).first(),
@@ -994,15 +965,20 @@ var MultiPanelColumns = Backbone.View.extend( baseMVC.LoggableMixin ).extend({
/** 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() };
var $outerMiddle = this.$( '.middle' ).parent(),
viewLeft = $outerMiddle.offset().left,
width = $outerMiddle.width();
return {
left : viewLeft,
right : viewLeft + width
};
},
/** returns the columns currently in the viewport */
columnsInView : function(){
columnsInView : function( viewport ){
//TODO: uses offset which is render intensive
//TODO: 2N - could use arg filter (sortedFilteredColumns( filter )) instead
var vp = this._viewport();
var vp = viewport || this._viewport();
return this.sortedFilteredColumns().filter( function( column ){
return column.currentHistory || column.inView( vp.left, vp.right );
});
@@ -1017,9 +993,20 @@ var MultiPanelColumns = Backbone.View.extend( baseMVC.LoggableMixin ).extend({
});
},
/** */
checkForEndOfScroll : function( viewport ){
viewport = viewport || this._viewport();
var END_PADDING = 16,
$middle = this.$( '.middle' ),
scrollRight = $middle.parent().scrollLeft() + viewport.right;
if( scrollRight >= ( $middle.width() - END_PADDING ) ){
this.trigger( 'end-of-scroll' );
}
},
/** Show and enable the current columns drop target */
currentColumnDropTargetOn : function(){
var currentColumn = this.columnMap[ this.currentHistoryId ];
var currentColumn = this.columnMap[ this.collection.currentHistoryId ];
if( !currentColumn ){ return; }
//TODO: fix this - shouldn't need monkeypatch
currentColumn.panel.dataDropped = function( data ){};
@@ -1028,7 +1015,7 @@ var MultiPanelColumns = Backbone.View.extend( baseMVC.LoggableMixin ).extend({
/** Hide and disable the current columns drop target */
currentColumnDropTargetOff : function(){
var currentColumn = this.columnMap[ this.currentHistoryId ];
var currentColumn = this.columnMap[ this.collection.currentHistoryId ];
if( !currentColumn ){ return; }
currentColumn.panel.dataDropped = HPANEL_EDIT.HistoryPanelEdit.prototype.dataDrop;
// slight override of dropTargetOff to not erase drop-target-help
@@ -1066,22 +1053,36 @@ var MultiPanelColumns = Backbone.View.extend( baseMVC.LoggableMixin ).extend({
'<div class="middle flex-column-container flex-row"></div>',
'</div>',
// footer
'<div class="footer flex-column-container">',
'<div class="footer flex-column-container"></div>'
].join(''), { variable: 'view' }),
loadingIndicatorTemplate : _.template([
'<div class="histories-loading-indicator">',
'<span class="fa fa-spin fa-spinner"></span>', _l( 'Loading histories' ), '...',
'</div>'
].join(''), { variable: 'view' }),
orderDescriptions : {
'update_time' : _l( 'most recent first' ),
'update_time-asc' : _l( 'least recent first' ),
'name' : _l( 'name, a to z' ),
'name-dsc' : _l( 'name, z to a' ),
'size' : _l( 'size, large to small' ),
'size-asc' : _l( 'size, small to large' )
},
optionsPopoverTemplate : _.template([
'<div class="more-options">',
'<div class="order btn-group">',
'<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown">',
_l( 'Order histories by' ) + ' ',
'<span class="current-order"><%= view.sortOrders[ view.order ].text %></span> ',
'<span class="current-order"><%= view.orderDescriptions[ view.collection.order ] %></span> ',
'<span class="caret"></span>',
'</button>',
'<ul class="dropdown-menu" role="menu">',
'<% _.each( view.sortOrders, function( order, key ){ %>',
'<li><a href="javascript:void(0);" class="set-order" data-order="<%= key %>">',
'<%= order.text %>',
'<% _.each( view.orderDescriptions, function( text, order ){ %>',
'<li><a href="javascript:void(0);" class="set-order" data-order="<%= order %>">',
'<%= text %>',
'</a></li>',
'<% }); %>',
'</ul>',
@@ -66,15 +66,40 @@ var menu = [
},
{
html : _l( 'Unhide Hidden Datasets' ),
confirm : _l( 'Really unhide all hidden datasets?' ),
href : 'history/unhide_datasets?current=True',
anon : true
anon : true,
func : function() {
if( Galaxy && Galaxy.currHistoryPanel && confirm( _l( 'Really unhide all hidden datasets?' ) ) ){
var filtered = Galaxy.currHistoryPanel.model.contents.hidden();
//TODO: batch
filtered.ajaxQueue( Backbone.Model.prototype.save, { visible : true })
.done( function(){
Galaxy.currHistoryPanel.renderItems();
})
.fail( function(){
alert( 'There was an error unhiding the datasets' );
console.error( arguments );
});
}
}
},
{
html : _l( 'Delete Hidden Datasets' ),
confirm : _l( 'Really delete all hidden datasets?' ),
href : 'history/delete_hidden_datasets',
anon : true
anon : true,
func : function() {
if( Galaxy && Galaxy.currHistoryPanel && confirm( _l( 'Really delete all hidden datasets?' ) ) ){
var filtered = Galaxy.currHistoryPanel.model.contents.hidden();
//TODO: batch
// both delete *and* unhide them
filtered.ajaxQueue( Backbone.Model.prototype.save, { deleted : true, visible: true })
.done( function(){
Galaxy.currHistoryPanel.renderItems();
})
.fail( function(){
alert( 'There was an error deleting the datasets' );
console.error( arguments );
});
}
}
},
{
html : _l( 'Purge Deleted Datasets' ),
@@ -28,6 +28,7 @@ var FolderListView = Backbone.View.extend({
events: {
'click #select-all-checkboxes' : 'selectAll',
'click .dataset_row' : 'selectClickedRow',
'click .folder_row' : 'selectClickedRow',
'click .sort-folder-link' : 'sortColumnClicked'
},
@@ -175,7 +176,7 @@ var FolderListView = Backbone.View.extend({
*/
postRender: function(){
var fetched_metadata = this.folderContainer.attributes.metadata;
fetched_metadata.contains_file = typeof this.collection.findWhere({type: 'file'}) !== 'undefined';
fetched_metadata.contains_file_or_folder = typeof this.collection.findWhere({type: 'file'}) !== 'undefined' || typeof this.collection.findWhere({type: 'folder'}) !== 'undefined';
Galaxy.libraries.folderToolbarView.configureElements(fetched_metadata);
$('.library-row').hover(function() {
$(this).find('.show_on_hover').show();
@@ -201,10 +202,10 @@ var FolderListView = Backbone.View.extend({
* @param {Item or FolderAsModel} model of the view that will be rendered
*/
renderOne: function(model){
if (model.get('type') !== 'folder'){
this.options.contains_file = true;
this.options.contains_file_or_folder = true;
//if (model.get('type') !== 'folder'){
// model.set('readable_size', this.size_to_string(model.get('file_size')));
}
//}
model.set('folder_id', this.id);
var rowView = new mod_library_folderrow_view.FolderRowView(model);
@@ -262,6 +263,11 @@ var FolderListView = Backbone.View.extend({
* Currently supports only sorting by name.
*/
sortFolder: function(sort_by, order){
console.log('sorting');
// default to asc sort by name
if (sort_by === 'undefined' && order === 'undefined'){
return this.collection.sortByNameAsc();
}
if (sort_by === 'name'){
if (order === 'asc'){
return this.collection.sortByNameAsc();
@@ -329,12 +335,14 @@ var FolderListView = Backbone.View.extend({
$row.removeClass('light').addClass('dark');
$row.find('a').removeClass('light').addClass('dark');
$row.find('.fa-file-o').removeClass('fa-file-o').addClass('fa-file');
$row.find('.fa-folder-o').removeClass('fa-folder-o').addClass('fa-folder');
},
makeWhiteRow: function($row){
$row.removeClass('dark').addClass('light');
$row.find('a').removeClass('dark').addClass('light');
$row.find('.fa-file').removeClass('fa-file').addClass('fa-file-o');
$row.find('.fa-folder').removeClass('fa-folder').addClass('fa-folder-o');
},
renderSortIcon: function(){
@@ -15,7 +15,8 @@ var FolderRowView = Backbone.View.extend({
lastSelectedHistory: '',
events: {
'click .undelete_dataset_btn' : 'undelete_dataset'
'click .undelete_dataset_btn' : 'undeleteDataset',
'click .undelete_folder_btn' : 'undeleteFolder'
},
options: {
@@ -29,16 +30,23 @@ var FolderRowView = Backbone.View.extend({
render: function(folder_item){
var template = null;
if (folder_item.get('type') === 'folder'){
if (folder_item.get('type') === 'folder' || folder_item.get('model_class') === 'LibraryFolder'){
this.options.type = 'folder';
template = this.templateRowFolder();
} else {
if (folder_item.get('deleted')){
template = this.templateRowDeletedFolder();
} else{
template = this.templateRowFolder();
}
} else if (folder_item.get('type') === 'file' || folder_item.get('model_class') === 'LibraryDataset'){
this.options.type = 'file';
if (folder_item.get('deleted')){
template = this.templateRowDeletedFile();
} else {
template = this.templateRowFile();
}
} else {
console.error('Unknown library item type found.');
console.error(folder_item.get('type') || folder_item.get('model_class'));
}
this.setElement(template({content_item:folder_item}));
this.$el.show();
@@ -52,7 +60,7 @@ var FolderRowView = Backbone.View.extend({
/**
* Undeletes the dataset on server and renders the row again.
*/
undelete_dataset : function(event){
undeleteDataset : function(event){
$(".tooltip").hide();
var that = this;
var dataset_id = $(event.target).closest('tr')[0].id;
@@ -63,6 +71,7 @@ var FolderRowView = Backbone.View.extend({
Galaxy.libraries.folderListView.collection.remove(dataset_id);
var updated_dataset = new mod_library_model.Item(response);
Galaxy.libraries.folderListView.collection.add(updated_dataset);
Galaxy.libraries.folderListView.collection.sortByNameAsc();
mod_toastr.success('Dataset undeleted. Click this to see it.', '', {onclick: function() {
var folder_id = that.model.get('folder_id');
window.location='#folders/' + folder_id + '/datasets/' + that.id;
@@ -78,6 +87,33 @@ var FolderRowView = Backbone.View.extend({
});
},
/**
* Undeletes the folder on server and renders the row again.
*/
undeleteFolder : function(event){
$(".tooltip").hide();
var that = this;
var folder_id = $(event.target).closest('tr')[0].id;
var folder = Galaxy.libraries.folderListView.collection.get(folder_id);
folder.url = folder.urlRoot + folder.id + '?undelete=true';
folder.destroy({
success : function(model, response){
Galaxy.libraries.folderListView.collection.remove(folder_id);
var updated_folder = new mod_library_model.FolderAsModel(response);
Galaxy.libraries.folderListView.collection.add(updated_folder);
Galaxy.libraries.folderListView.collection.sortByNameAsc();
mod_toastr.success('Folder undeleted.');
},
error : function(model, response){
if (typeof response.responseJSON !== "undefined"){
mod_toastr.error('Folder was not undeleted. ' + response.responseJSON.err_msg);
} else {
mod_toastr.error('An error occured! Folder was not undeleted. Please try again.');
}
}
});
},
templateRowFolder: function() {
tmpl_array = [];
@@ -85,7 +121,7 @@ var FolderRowView = Backbone.View.extend({
tmpl_array.push(' <td>');
tmpl_array.push(' <span title="Folder" class="fa fa-folder-o"></span>');
tmpl_array.push(' </td>');
tmpl_array.push(' <td></td>');
tmpl_array.push(' <td style="text-align: center; "><input style="margin: 0;" type="checkbox"></td>');
tmpl_array.push(' <td>');
tmpl_array.push(' <a href="#folders/<%- content_item.id %>"><%- content_item.get("name") %></a>');
tmpl_array.push(' </td>');
@@ -138,6 +174,26 @@ var FolderRowView = Backbone.View.extend({
tmpl_array.push(' <td><span data-toggle="tooltip" data-placement="top" title="Marked deleted" style="color:grey;" class="fa fa-ban fa-lg"> </span><button data-toggle="tooltip" data-placement="top" title="Undelete <%- content_item.get("name") %>" class="primary-button btn-xs undelete_dataset_btn show_on_hover" type="button" style="display:none; margin-left:1em;"><span class="fa fa-unlock"> Undelete</span></button></td>');
tmpl_array.push('</tr>');
return _.template(tmpl_array.join(''));
},
templateRowDeletedFolder: function(){
tmpl_array = [];
tmpl_array.push('<tr class="active folder_row light library-row" id="<%- content_item.id %>">');
tmpl_array.push(' <td>');
tmpl_array.push(' <span title="Folder" class="fa fa-folder-o"></span>');
tmpl_array.push(' </td>');
tmpl_array.push(' <td></td>');
tmpl_array.push(' <td style="color:grey;">');
tmpl_array.push(' <%- content_item.get("name") %>');
tmpl_array.push(' </td>');
tmpl_array.push(' <td>folder</td>');
tmpl_array.push(' <td></td>');
tmpl_array.push(' <td><%= _.escape(content_item.get("update_time")) %></td>'); // time updated
tmpl_array.push(' <td><span data-toggle="tooltip" data-placement="top" title="Marked deleted" style="color:grey;" class="fa fa-ban fa-lg"> </span><button data-toggle="tooltip" data-placement="top" title="Undelete <%- content_item.get("name") %>" class="primary-button btn-xs undelete_folder_btn show_on_hover" type="button" style="display:none; margin-left:1em;"><span class="fa fa-unlock"> Undelete</span></button></td>');
tmpl_array.push('</tr>');
return _.template(tmpl_array.join(''));
}
@@ -26,9 +26,9 @@ var FolderToolbarView = Backbone.View.extend({
},
defaults: {
'can_add_library_item' : false,
'contains_file' : false,
'chain_call_control' : {
'can_add_library_item' : false,
'contains_file_or_folder' : false,
'chain_call_control' : {
'total_number' : 0,
'failed_number' : 0
},
@@ -116,7 +116,7 @@ var FolderToolbarView = Backbone.View.extend({
} else{
$('.add-library-items').hide();
}
if (this.options.contains_file === true){
if (this.options.contains_file_or_folder === true){
if (Galaxy.currUser){
if (!Galaxy.currUser.isAnonymous()){
$('.logged-dataset-manipulation').show();
@@ -250,9 +250,12 @@ var FolderToolbarView = Backbone.View.extend({
// we can save last used history to pre-select it next time
this.options.last_used_history_id = history_id;
var dataset_ids = [];
var folder_ids = [];
$('#folder_table').find(':checked').each(function(){
if (this.parentElement.parentElement.id !== '') {
if (this.parentElement.parentElement.id !== '' && this.parentElement.parentElement.classList.contains('dataset_row') ) {
dataset_ids.push(this.parentElement.parentElement.id);
} else if (this.parentElement.parentElement.id !== '' && this.parentElement.parentElement.classList.contains('folder_row') ) {
folder_ids.push(this.parentElement.parentElement.id);
}
});
// prepare the dataset objects to be imported
@@ -265,6 +268,18 @@ var FolderToolbarView = Backbone.View.extend({
historyItem.source = 'library';
datasets_to_import.push(historyItem);
}
// prepare the folder objects to be imported
var folders_to_import = [];
for (var i = folder_ids.length - 1; i >= 0; i--) {
var library_folder_id = folder_ids[i];
var historyItem = new mod_library_model.HistoryItem();
historyItem.url = historyItem.urlRoot + history_id + '/contents';
historyItem.content = library_folder_id;
historyItem.source = 'library_folder';
datasets_to_import.push(historyItem);
}
this.initChainCallControl( { length: datasets_to_import.length, action: 'to_history', history_name: history_name } );
// set the used history as current so user will see the last one
// that he imported into in the history panel on the 'analysis' page
@@ -289,13 +304,16 @@ var FolderToolbarView = Backbone.View.extend({
*/
download : function( folder_id, format ){
var dataset_ids = [];
var folder_ids = [];
$( '#folder_table' ).find( ':checked' ).each( function(){
if ( this.parentElement.parentElement.id !== '' ) {
if ( this.parentElement.parentElement.id !== '' && this.parentElement.parentElement.classList.contains('dataset_row') ) {
dataset_ids.push( this.parentElement.parentElement.id );
} else if ( this.parentElement.parentElement.id !== '' && this.parentElement.parentElement.classList.contains('folder_row') ) {
folder_ids.push( this.parentElement.parentElement.id );
}
} );
var url = ( window.galaxy_config ? galaxy_config.root : '/' ) + 'api/libraries/datasets/download/' + format;
var data = { 'ld_ids' : dataset_ids };
var data = { 'ld_ids' : dataset_ids, 'folder_ids' : folder_ids };
this.processDownload( url, data, 'get' );
},
@@ -845,37 +863,45 @@ var FolderToolbarView = Backbone.View.extend({
* call them in chain. Update progress bar in between each.
* @param {array} lddas_set array of lddas to delete
*/
chainCallDeletingHdas: function( lddas_set ){
chainCallDeletingItems: function( items_to_delete ){
var self = this;
this.deleted_lddas = new mod_library_model.Folder();
var popped_item = lddas_set.pop();
this.deleted_items = new mod_library_model.Folder();
var popped_item = items_to_delete.pop();
if ( typeof popped_item === "undefined" ) {
if ( this.options.chain_call_control.failed_number === 0 ){
mod_toastr.success( 'Selected datasets were deleted.' );
mod_toastr.success( 'Selected items were deleted.' );
} else if ( this.options.chain_call_control.failed_number === this.options.chain_call_control.total_number ){
mod_toastr.error( 'There was an error and no datasets were deleted. Please make sure you have sufficient permissions.' );
mod_toastr.error( 'There was an error and no items were deleted. Please make sure you have sufficient permissions.' );
} else if ( this.options.chain_call_control.failed_number < this.options.chain_call_control.total_number ){
mod_toastr.warning( 'Some of the datasets could not be deleted. Please make sure you have sufficient permissions.' );
mod_toastr.warning( 'Some of the items could not be deleted. Please make sure you have sufficient permissions.' );
}
Galaxy.modal.hide();
return this.deleted_lddas;
return this.deleted_items;
}
var promise = $.when( popped_item.destroy() );
promise.done( function( dataset ){
promise.done( function( item ){
Galaxy.libraries.folderListView.collection.remove( popped_item.id );
self.updateProgress();
// add the deleted dataset to collection, triggers rendering
// add the deleted item to collection, triggers rendering
if ( Galaxy.libraries.folderListView.options.include_deleted ){
var updated_dataset = new mod_library_model.Item( dataset );
Galaxy.libraries.folderListView.collection.add( updated_dataset );
var updated_item = null;
if (item.type === 'folder' || item.model_class === 'LibraryFolder'){
updated_item = new mod_library_model.FolderAsModel( item );
} else if (item.type === 'file' || item.model_class === 'LibraryDataset'){
updated_item = new mod_library_model.Item( item );
} else {
console.error('Unknown library item type found.');
console.error(item.type || item.model_class);
}
Galaxy.libraries.folderListView.collection.add( updated_item );
}
self.chainCallDeletingHdas( lddas_set );
self.chainCallDeletingItems( items_to_delete );
})
.fail( function(){
self.options.chain_call_control.failed_number += 1;
self.updateProgress();
self.chainCallDeletingHdas( lddas_set );
self.chainCallDeletingItems( items_to_delete );
});
},
@@ -913,25 +939,35 @@ var FolderToolbarView = Backbone.View.extend({
this.options.chain_call_control.failed_number = 0;
var dataset_ids = [];
var folder_ids = [];
checkedValues.each(function(){
if (this.parentElement.parentElement.id !== '') {
dataset_ids.push(this.parentElement.parentElement.id);
if (this.parentElement.parentElement.id.substring(0,1) == 'F'){
folder_ids.push(this.parentElement.parentElement.id);
} else {
dataset_ids.push(this.parentElement.parentElement.id);
}
}
});
// init the progress bar
this.progressStep = 100 / dataset_ids.length;
var items_total = dataset_ids.length + folder_ids.length
this.progressStep = 100 / items_total;
this.progress = 0;
// prepare the dataset items to be added
var lddas_to_delete = [];
var items_to_delete = [];
for (var i = dataset_ids.length - 1; i >= 0; i--) {
var dataset = new mod_library_model.Item({id:dataset_ids[i]});
lddas_to_delete.push(dataset);
items_to_delete.push(dataset);
}
for (var i = folder_ids.length - 1; i >= 0; i--) {
var folder = new mod_library_model.FolderAsModel({id:folder_ids[i]});
items_to_delete.push(folder);
}
this.options.chain_call_control.total_number = dataset_ids.length;
this.options.chain_call_control.total_number = items_total.length;
// call the recursive function to call ajax one after each other (request FIFO queue)
this.chainCallDeletingHdas(lddas_to_delete);
this.chainCallDeletingItems(items_to_delete);
}
},
@@ -994,6 +1030,9 @@ var FolderToolbarView = Backbone.View.extend({
}
},
/**
* Show user the prompt to change the number of items shown on page.
*/
showPageSizePrompt: function(){
var folder_page_size = prompt( 'How many items per page do you want to see?', Galaxy.libraries.preferences.get( 'folder_page_size' ) );
if ( ( folder_page_size != null ) && ( folder_page_size == parseInt( folder_page_size ) ) ) {
@@ -1009,8 +1048,16 @@ var FolderToolbarView = Backbone.View.extend({
tmpl_array.push('<div class="library_style_container">');
// TOOLBAR START
tmpl_array.push(' <div id="library_toolbar">');
tmpl_array.push('<form class="form-inline" role="form">');
tmpl_array.push(' <span><strong>DATA LIBRARIES</strong></span>');
tmpl_array.push(' <span data-toggle="tooltip" data-placement="top" class="logged-dataset-manipulation" title="Include deleted datasets" style="display:none;"> | <input id="include_deleted_datasets_chk" style="margin: 0;" type="checkbox"> include deleted | </input></span>');
tmpl_array.push(' <span id="folder_paginator" class="library-paginator">');
// paginator will append here
tmpl_array.push(' </span>');
tmpl_array.push('<div class="checkbox toolbar-item logged-dataset-manipulation" style="height: 20px; display:none;">');
tmpl_array.push('<label>');
tmpl_array.push('<input id="include_deleted_datasets_chk" type="checkbox"> include deleted </input>');
tmpl_array.push('</label>');
tmpl_array.push('</div>');
tmpl_array.push(' <button style="display:none;" data-toggle="tooltip" data-placement="top" title="Create New Folder" id="toolbtn_create_folder" class="btn btn-default primary-button add-library-items" type="button"><span class="fa fa-plus"></span> <span class="fa fa-folder"></span></button>');
tmpl_array.push('<% if(mutiple_add_dataset_options) { %>');
@@ -1056,12 +1103,9 @@ var FolderToolbarView = Backbone.View.extend({
tmpl_array.push(' <button data-toggle="tooltip" data-placement="top" title="Mark selected datasets deleted" id="toolbtn_bulk_delete" class="primary-button logged-dataset-manipulation" style="margin-left: 0.5em; display:none; " type="button"><span class="fa fa-times"></span> Delete</button>');
tmpl_array.push(' <button data-id="<%- id %>" data-toggle="tooltip" data-placement="top" title="Show library information" id="toolbtn_show_libinfo" class="primary-button" style="margin-left: 0.5em;" type="button"><span class="fa fa-info-circle"></span> Library Info</button>');
tmpl_array.push(' <span class="help-button" data-toggle="tooltip" data-placement="top" title="Visit Libraries Wiki"><a href="https://wiki.galaxyproject.org/DataLibraries/screen/FolderContents" target="_blank"><button class="primary-button" type="button"><span class="fa fa-question-circle"></span> Help</button></a></span>');
tmpl_array.push(' <span id="folder_paginator" class="library-paginator">');
// paginator will append here
tmpl_array.push(' </span>');
tmpl_array.push(' </div>');
tmpl_array.push('</form>');
// TOOLBAR END
tmpl_array.push(' <div id="folder_items_element">');
tmpl_array.push(' </div>');
@@ -1081,6 +1125,7 @@ var FolderToolbarView = Backbone.View.extend({
tmpl_array.push('<p><%- library.get("description") %></p>');
tmpl_array.push('<h3>Library synopsis:</h3>');
tmpl_array.push('<p><%- library.get("synopsis") %></p>');
tmpl_array.push('<p data-toggle="tooltip" data-placement="top" title="<%- library.get("create_time") %>">created <%- library.get("create_time_pretty") %></p>');
tmpl_array.push('</div>');
@@ -4,14 +4,16 @@ define([
"utils/utils",
"libs/toastr",
"mvc/library/library-model",
"mvc/library/library-libraryrow-view"
"mvc/library/library-libraryrow-view",
"libs/underscore"
], function(
mod_masthead,
mod_baseMVC,
mod_utils,
mod_toastr,
mod_library_model,
mod_library_libraryrow_view
mod_library_libraryrow_view,
_
){
var LibraryListView = Backbone.View.extend({
@@ -32,11 +34,9 @@ var LibraryListView = Backbone.View.extend({
* @param {object} options an object with options
*/
initialize : function( options ){
this.options = _.defaults( this.options || {}, this.defaults, options );
this.options = _.defaults( this.options || {}, options, this.defaults );
var that = this;
this.modal = null;
// map of library model ids to library views = cache
this.rowViews = {};
// collection of {Item}s
this.collection = new mod_library_model.Libraries();
this.collection.fetch({
@@ -60,6 +60,7 @@ var LibraryListView = Backbone.View.extend({
*/
render: function ( options ) {
this.options = _.extend( this.options, options );
this.setElement('#libraries_element');
var template = this.templateLibraryList();
var libraries_to_render = null;
var models = null;
@@ -75,10 +76,16 @@ var LibraryListView = Backbone.View.extend({
libraries_to_render = this.collection.where( { deleted: false } );
}
} else if ( models !== null ){
libraries_to_render = models;
if ( Galaxy.libraries.preferences.get( 'with_deleted' ) ){
libraries_to_render = models;
} else {
var is_deleted = function(model){ return model.get('deleted') === false; }
libraries_to_render = _.filter(models, is_deleted );
}
} else {
libraries_to_render = [];
}
// pagination
if ( this.options.show_page === null || this.options.show_page < 1 ){
this.options.show_page = 1;
@@ -95,14 +102,16 @@ var LibraryListView = Backbone.View.extend({
}
this.$el.html( template({
length: 1,
order: Galaxy.libraries.preferences.get( 'sort_order' )
order: Galaxy.libraries.preferences.get( 'sort_order' ),
search_term: Galaxy.libraries.libraryToolbarView.options.search_term
}));
Galaxy.libraries.libraryToolbarView.renderPaginator( this.options );
this.renderRows( libraries_to_render );
} else {
this.$el.html( template({
length: 0,
order: Galaxy.libraries.preferences.get( 'sort_order' )
order: Galaxy.libraries.preferences.get( 'sort_order' ),
search_term: Galaxy.libraries.libraryToolbarView.options.search_term
}));
Galaxy.libraries.libraryToolbarView.renderPaginator( this.options );
}
@@ -117,14 +126,7 @@ var LibraryListView = Backbone.View.extend({
renderRows: function( libraries_to_render ){
for ( var i = 0; i < libraries_to_render.length; i++ ) {
var library = libraries_to_render[i];
// search whether we have the item cached
var cachedView = _.findWhere( this.rowViews, { id: library.get( 'id' ) } );
if ( cachedView !== undefined && this instanceof Backbone.View ){
cachedView.delegateEvents();
this.$el.find( '#library_list_body' ).append( cachedView.el );
} else {
this.renderOne( { library: library } )
}
this.renderOne( { library: library } );
}
},
@@ -136,8 +138,6 @@ var LibraryListView = Backbone.View.extend({
var library = options.library;
var rowView = new mod_library_libraryrow_view.LibraryRowView( library );
this.$el.find( '#library_list_body' ).append( rowView.el );
// save new rowView to cache
this.rowViews[ library.get( 'id' ) ] = rowView;
},
/**
@@ -174,6 +174,24 @@ var LibraryListView = Backbone.View.extend({
window.location = '/user/login';
},
/**
* In case the search_term is not empty perform the search and render
* the result. Render all visible libraries otherwise.
* @param {string} search_term string to search for
*/
searchLibraries: function(search_term){
var trimmed_term = $.trim(search_term);
if (trimmed_term !== ''){
var results = null
results = this.collection.search( search_term );
this.options.searching = true;
this.render({'models': results});
} else {
this.options.searching = false;
this.render();
}
},
// MMMMMMMMMMMMMMMMMM
// === TEMPLATES ====
// MMMMMMMMMMMMMMMMMM
@@ -183,7 +201,11 @@ var LibraryListView = Backbone.View.extend({
tmpl_array.push('<div class="library_container table-responsive">');
tmpl_array.push('<% if(length === 0) { %>');
tmpl_array.push('<% if(search_term.length > 0) { %>');
tmpl_array.push('<div>There are no libraries matching your search. Try different keyword.</div>');
tmpl_array.push('<% } else{ %>');
tmpl_array.push('<div>There are no libraries visible to you here. If you expected some to show up please consult the <a href="https://wiki.galaxyproject.org/Admin/DataLibraries/LibrarySecurity" target="_blank">library security wikipage</a> or visit the <a href="https://biostar.usegalaxy.org/" target="_blank">Galaxy support site</a>.</div>');
tmpl_array.push('<% }%>');
tmpl_array.push('<% } else{ %>');
tmpl_array.push('<table class="grid table table-condensed">');
tmpl_array.push(' <thead>');
@@ -5,19 +5,24 @@ function(mod_toastr,
mod_library_model) {
/**
* This view represents the top part of the library page.
* It contains the tool bar with buttons.
* It contains the tool bar with controls.
*/
var LibraryToolbarView = Backbone.View.extend({
el: '#center',
defaults: {
search_term: ''
},
events: {
'click #create_new_library_btn' : 'showLibraryModal',
'click #include_deleted_chk' : 'includeDeletedChecked',
'click #page_size_prompt' : 'showPageSizePrompt'
'click #lib_page_size_prompt' : 'showPageSizePrompt',
'keyup .library-search-input' : 'searchLibraries'
},
initialize: function( options ){
this.options = _.defaults( this.options || {}, options );
this.options = _.defaults( this.options || {}, options, this.defaults );
this.render();
},
@@ -36,93 +41,109 @@ var LibraryToolbarView = Backbone.View.extend({
},
/**
* Called from LibraryListView when needed.
* @param {object} options common options
* Renders the element that shows pages into its div within the toolbar.
*/
renderPaginator: function( options ){
this.options = _.extend( this.options, options );
var paginator_template = this.templatePaginator();
this.$el.find( '#library_paginator' ).html( paginator_template({
show_page: parseInt( this.options.show_page ),
page_count: parseInt( this.options.page_count ),
total_libraries_count: this.options.total_libraries_count,
libraries_shown: this.options.libraries_shown
show_page: parseInt( this.options.show_page ),
page_count: parseInt( this.options.page_count ),
total_libraries_count: this.options.total_libraries_count,
libraries_shown: this.options.libraries_shown
}));
},
/**
* User clicked on 'New library' button. Show modal to
* satisfy the wish.
*/
showLibraryModal : function (event){
event.preventDefault();
event.stopPropagation();
var self = this;
this.modal = Galaxy.modal;
this.modal.show({
closing_events : true,
title : 'Create New Library',
body : this.templateNewLibraryInModal(),
buttons : {
'Create' : function() { self.createNewLibrary(); },
'Close' : function() { self.modal.hide(); }
}
});
},
createNewLibrary: function(){
var libraryDetails = this.serializeNewLibrary();
if (this.valdiateNewLibrary(libraryDetails)){
var library = new mod_library_model.Library();
var self = this;
library.save(libraryDetails, {
success: function (library) {
Galaxy.libraries.libraryListView.collection.add(library);
self.modal.hide();
self.clearLibraryModal();
Galaxy.libraries.libraryListView.render();
mod_toastr.success('Library created.');
},
error: function(model, response){
if (typeof response.responseJSON !== "undefined"){
mod_toastr.error(response.responseJSON.err_msg);
} else {
mod_toastr.error('An error occured.');
}
}
});
} else {
mod_toastr.error('Library\'s name is missing.');
event.preventDefault();
event.stopPropagation();
var self = this;
this.modal = Galaxy.modal;
this.modal.show({
closing_events : true,
title : 'Create New Library',
body : this.templateNewLibraryInModal(),
buttons : {
'Create' : function() { self.createNewLibrary(); },
'Close' : function() { self.modal.hide(); }
}
return false;
});
},
/**
* Create the new library using the API asynchronously.
*/
createNewLibrary: function(){
var libraryDetails = this.serializeNewLibrary();
if (this.validateNewLibrary(libraryDetails)){
var library = new mod_library_model.Library();
var self = this;
library.save(libraryDetails, {
success: function (library) {
Galaxy.libraries.libraryListView.collection.add(library);
self.modal.hide();
self.clearLibraryModal();
Galaxy.libraries.libraryListView.render();
mod_toastr.success('Library created.');
},
error: function(model, response){
if (typeof response.responseJSON !== "undefined"){
mod_toastr.error(response.responseJSON.err_msg);
} else {
mod_toastr.error('An error occured.');
}
}
});
} else {
mod_toastr.error('Library\'s name is missing.');
}
return false;
},
/**
* Show user the propmpt to change the number of libs shown on page.
*/
showPageSizePrompt: function(){
var library_page_size = prompt( 'How many libraries per page do you want to see?', Galaxy.libraries.preferences.get( 'library_page_size' ) );
if ( ( library_page_size != null ) && ( library_page_size == parseInt( library_page_size ) ) ) {
Galaxy.libraries.preferences.set( { 'library_page_size': parseInt( library_page_size ) } );
Galaxy.libraries.libraryListView.render( { show_page: 1 } );
Galaxy.libraries.preferences.set( { 'library_page_size': parseInt( library_page_size ) } );
Galaxy.libraries.libraryListView.render( { show_page: 1 } );
}
},
// clear the library modal once saved
/**
* Clear the library modal once it is saved.
*/
clearLibraryModal : function(){
$("input[name='Name']").val('');
$("input[name='Description']").val('');
$("input[name='Synopsis']").val('');
$("input[name='Name']").val('');
$("input[name='Description']").val('');
$("input[name='Synopsis']").val('');
},
/**
* Prepare new library variables to be submitted to API.
*/
serializeNewLibrary : function(){
return {
name: $("input[name='Name']").val(),
description: $("input[name='Description']").val(),
synopsis: $("input[name='Synopsis']").val()
};
return {
name: $("input[name='Name']").val(),
description: $("input[name='Description']").val(),
synopsis: $("input[name='Synopsis']").val()
};
},
valdiateNewLibrary: function( libraryDetails ){
/**
* Check whether entered values are valid.
*/
validateNewLibrary: function( libraryDetails ){
return libraryDetails.name !== '';
},
/**
* Include or exclude deleted libraries in the view.
* @param {object} event common event
*/
includeDeletedChecked: function( event ){
if (event.target.checked){
@@ -134,89 +155,102 @@ var LibraryToolbarView = Backbone.View.extend({
}
},
/**
* Take the contents of the search field and send it to the list view
* to query the collection of libraries.
*/
searchLibraries: function(event){
var search_term = $(".library-search-input").val();
this.options.search_term = search_term;
Galaxy.libraries.libraryListView.searchLibraries(search_term);
},
templateToolBar: function(){
tmpl_array = [];
tmpl_array.push('<div class="library_style_container">');
tmpl_array.push(' <div id="toolbar_form">');
tmpl_array.push(' <div id="library_toolbar">');
tmpl_array.push(' <form class="form-inline" role="form">');
tmpl_array.push(' <span><strong><a href="#" title="Go to first page">DATA LIBRARIES</a></strong></span>');
tmpl_array.push(' <% if(admin_user === true) { %>');
tmpl_array.push(' <div class="checkbox" style="height: 20px;">');
tmpl_array.push(' <label>');
tmpl_array.push(' <input id="include_deleted_chk" type="checkbox"> include deleted </input>');
tmpl_array.push(' </label>');
tmpl_array.push(' </div>');
tmpl_array.push(' <span data-toggle="tooltip" data-placement="top" title="Create New Library"><button id="create_new_library_btn" class="primary-button btn-xs" type="button"><span class="fa fa-plus"></span> New Library</button></span>');
tmpl_array.push(' <% } %>');
tmpl_array.push(' <span class="help-button" data-toggle="tooltip" data-placement="top" title="Visit Libraries Wiki"><a href="https://wiki.galaxyproject.org/DataLibraries/screen/ListOfLibraries" target="_blank"><button class="primary-button" type="button"><span class="fa fa-question-circle"></span> Help</button></a></span>');
tmpl_array.push(' <span id="library_paginator" class="library-paginator">');
// paginator will append here
tmpl_array.push(' </span>');
tmpl_array.push(' </form>');
tmpl_array.push(' </div>');
tmpl_array.push(' </div>');
tmpl_array.push(' <div id="libraries_element">');
// table with libraries will append here
tmpl_array.push(' </div>');
tmpl_array.push('</div>');
return _.template(tmpl_array.join(''));
return _.template([
'<div class="library_style_container">',
'<div id="toolbar_form">',
'<div id="library_toolbar">',
'<form class="form-inline" role="form">',
'<span><strong><a href="#" title="Go to first page">DATA LIBRARIES</a></strong></span>',
'<span id="library_paginator" class="library-paginator">',
// paginator will append here
'</span>',
'<div class="form-group toolbar-item">',
'<input type="text" class="form-control library-search-input" placeholder="Search" size="30">',
'</div>',
// only admins see the following
'<% if(admin_user === true) { %>',
'<div class="checkbox toolbar-item" style="height: 20px;">',
'<label>',
'<input id="include_deleted_chk" type="checkbox"> include deleted </input>',
'</label>',
'</div>',
'<span class="toolbar-item" data-toggle="tooltip" data-placement="top" title="Create New Library">',
'<button id="create_new_library_btn" class="primary-button btn-xs" type="button"><span class="fa fa-plus"></span> New Library</button>',
'</span>',
'<% } %>',
'<span class="help-button" data-toggle="tooltip" data-placement="top" title="Visit Libraries Wiki">',
'<a href="https://wiki.galaxyproject.org/DataLibraries/screen/ListOfLibraries" target="_blank">',
'<button class="primary-button" type="button"><span class="fa fa-question-circle"></span> Help</button>',
'</a>',
'</span>',
'</form>',
'</div>',
'</div>',
'<div id="libraries_element">',
// table with libraries will append here
'</div>',
'</div>'
].join(''));
},
templatePaginator: function(){
tmpl_array = [];
tmpl_array.push(' <ul class="pagination pagination-sm">');
tmpl_array.push(' <% if ( ( show_page - 1 ) > 0 ) { %>');
tmpl_array.push(' <% if ( ( show_page - 1 ) > page_count ) { %>'); // we are on higher page than total page count
tmpl_array.push(' <li><a href="#page/1"><span class="fa fa-angle-double-left"></span></a></li>');
tmpl_array.push(' <li class="disabled"><a href="#page/<% print( show_page ) %>"><% print( show_page - 1 ) %></a></li>');
tmpl_array.push(' <% } else { %>');
tmpl_array.push(' <li><a href="#page/1"><span class="fa fa-angle-double-left"></span></a></li>');
tmpl_array.push(' <li><a href="#page/<% print( show_page - 1 ) %>"><% print( show_page - 1 ) %></a></li>');
tmpl_array.push(' <% } %>');
tmpl_array.push(' <% } else { %>'); // we are on the first page
tmpl_array.push(' <li class="disabled"><a href="#page/1"><span class="fa fa-angle-double-left"></span></a></li>');
tmpl_array.push(' <li class="disabled"><a href="#page/<% print( show_page ) %>"><% print( show_page - 1 ) %></a></li>');
tmpl_array.push(' <% } %>');
tmpl_array.push(' <li class="active">');
tmpl_array.push(' <a href="#page/<% print( show_page ) %>"><% print( show_page ) %></a>');
tmpl_array.push(' </li>');
tmpl_array.push(' <% if ( ( show_page ) < page_count ) { %>');
tmpl_array.push(' <li><a href="#page/<% print( show_page + 1 ) %>"><% print( show_page + 1 ) %></a></li>');
tmpl_array.push(' <li><a href="#page/<% print( page_count ) %>"><span class="fa fa-angle-double-right"></span></a></li>');
tmpl_array.push(' <% } else { %>');
tmpl_array.push(' <li class="disabled"><a href="#page/<% print( show_page ) %>"><% print( show_page + 1 ) %></a></li>');
tmpl_array.push(' <li class="disabled"><a href="#page/<% print( page_count ) %>"><span class="fa fa-angle-double-right"></span></a></li>');
tmpl_array.push(' <% } %>');
tmpl_array.push(' </ul>');
tmpl_array.push(' <span>');
tmpl_array.push(' showing <a data-toggle="tooltip" data-placement="top" title="Click to change the number of libraries on page" id="page_size_prompt"><%- libraries_shown %></a> of <%- total_libraries_count %> libraries');
tmpl_array.push(' </span>');
return _.template(tmpl_array.join(''));
return _.template([
'<ul class="pagination pagination-sm">',
'<% if ( ( show_page - 1 ) > 0 ) { %>',
'<% if ( ( show_page - 1 ) > page_count ) { %>', // we are on higher page than total page count
'<li><a href="#page/1"><span class="fa fa-angle-double-left"></span></a></li>',
'<li class="disabled"><a href="#page/<% print( show_page ) %>"><% print( show_page - 1 ) %></a></li>',
'<% } else { %>',
'<li><a href="#page/1"><span class="fa fa-angle-double-left"></span></a></li>',
'<li><a href="#page/<% print( show_page - 1 ) %>"><% print( show_page - 1 ) %></a></li>',
'<% } %>',
'<% } else { %>', // we are on the first page
'<li class="disabled"><a href="#page/1"><span class="fa fa-angle-double-left"></span></a></li>',
'<li class="disabled"><a href="#page/<% print( show_page ) %>"><% print( show_page - 1 ) %></a></li>',
'<% } %>',
'<li class="active">',
'<a href="#page/<% print( show_page ) %>"><% print( show_page ) %></a>',
'</li>',
'<% if ( ( show_page ) < page_count ) { %>',
'<li><a href="#page/<% print( show_page + 1 ) %>"><% print( show_page + 1 ) %></a></li>',
'<li><a href="#page/<% print( page_count ) %>"><span class="fa fa-angle-double-right"></span></a></li>',
'<% } else { %>',
'<li class="disabled"><a href="#page/<% print( show_page ) %>"><% print( show_page + 1 ) %></a></li>',
'<li class="disabled"><a href="#page/<% print( page_count ) %>"><span class="fa fa-angle-double-right"></span></a></li>',
'<% } %>',
'</ul>',
'<span id="lib_page_size_prompt">',
' showing <a data-toggle="tooltip" data-placement="top" title="Click to change the number of libraries on page"><%- libraries_shown %></a> of <%- total_libraries_count %> libraries',
'</span>'
].join(''));
},
templateNewLibraryInModal: function(){
tmpl_array = [];
tmpl_array.push('<div id="new_library_modal">');
tmpl_array.push(' <form>');
tmpl_array.push(' <input type="text" name="Name" value="" placeholder="Name">');
tmpl_array.push(' <input type="text" name="Description" value="" placeholder="Description">');
tmpl_array.push(' <input type="text" name="Synopsis" value="" placeholder="Synopsis">');
tmpl_array.push(' </form>');
tmpl_array.push('</div>');
return tmpl_array.join('');
return _.template([
'<div id="new_library_modal">',
'<form>',
'<input type="text" name="Name" value="" placeholder="Name">',
'<input type="text" name="Description" value="" placeholder="Description">',
'<input type="text" name="Synopsis" value="" placeholder="Synopsis">',
'</form>',
'</div>'
].join(''));
}
});
return {
LibraryToolbarView: LibraryToolbarView
LibraryToolbarView: LibraryToolbarView
};
});
@@ -31,6 +31,21 @@ define([], function() {
options = options || {};
},
search : function(search_term){
/**
* Search the collection and return only the models that have
* the search term in their names.
* [the term to search]
* @type {string}
*/
if (search_term == "") return this;
var lowercase_term = search_term.toLowerCase();
return this.filter(function(data) {
lowercase_name = data.get("name").toLowerCase();
return lowercase_name.indexOf(lowercase_term) !== -1;
});
},
/** Get every 'shown' library in this collection based on deleted filter
* @param {Boolean} show_deleted are we including deleted libraries?
* @returns array of library models
@@ -83,20 +98,19 @@ define([], function() {
// ============================================================================
// FOLDER RELATED MODELS
var Item = Backbone.Model.extend({
var LibraryItem = Backbone.Model.extend({
});
var Ldda = LibraryItem.extend({
urlRoot : ( window.galaxy_config ? galaxy_config.root : '/' ) + 'api/libraries/datasets/'
});
var Ldda = Backbone.Model.extend({
urlRoot : ( window.galaxy_config ? galaxy_config.root : '/' ) + 'api/libraries/datasets/'
});
var FolderAsModel = Backbone.Model.extend({
urlRoot: ( window.galaxy_config ? galaxy_config.root : '/' ) + 'api/folders'
var FolderAsModel = LibraryItem.extend({
urlRoot: ( window.galaxy_config ? galaxy_config.root : '/' ) + 'api/folders/'
});
var Folder = Backbone.Collection.extend({
model: Item,
model: LibraryItem,
/** Sort collection by item name (ascending) and return the sorted
* collection. Folders go before datasets.
@@ -150,16 +164,28 @@ define([], function() {
});
var FolderContainer = Backbone.Model.extend({
defaults : {
folder : new Folder(),
urlRoot : ( window.galaxy_config ? galaxy_config.root : '/' ) + 'api/folders/',
id : "unknown"
},
parse : function(obj) {
// response is not a simple array, it contains metadata
// this will update the inner collection
this.get("folder").reset(obj.folder_contents);
return obj;
defaults : {
folder : new Folder(),
urlRoot : ( window.galaxy_config ? galaxy_config.root : '/' ) + 'api/folders/',
id : "unknown"
},
parse : function(obj) {
// empty the collection
this.get("folder").reset();
// response is not a simple array, it contains metadata
// this will update the inner collection
for (var i = 0; i < obj.folder_contents.length; i++) {
if (obj.folder_contents[i].type === 'folder'){
var folder_item = new FolderAsModel(obj.folder_contents[i])
this.get("folder").add(folder_item);
} else if(obj.folder_contents[i].type === 'file'){
var file_item = new Ldda(obj.folder_contents[i])
this.get("folder").add(file_item);
} else{
console.error('Unknown folder item type encountered while parsing response.');
}
};
return obj;
}
});
@@ -204,10 +230,10 @@ define([], function() {
return {
Library: Library,
FolderAsModel : FolderAsModel,
Libraries : Libraries,
Item : Item,
Item : Ldda,
Ldda : Ldda,
FolderAsModel : FolderAsModel,
Folder : Folder,
FolderContainer : FolderContainer,
HistoryItem : HistoryItem,
+2 -1
View File
@@ -371,6 +371,7 @@ var ListPanel = Backbone.View.extend( BASE_MVC.LoggableMixin ).extend(
} else {
panel._renderEmptyMessage( $whereTo ).show();
}
panel.trigger( 'views:ready', panel.views );
return panel.views;
},
@@ -635,8 +636,8 @@ var ListPanel = Backbone.View.extend( BASE_MVC.LoggableMixin ).extend(
//this.log( 'onSearchClear', this );
this.searchFor = '';
this.trigger( 'search:clear', this );
this.renderItems();
this.$( '> .controls .search-query' ).val( '' );
this.renderItems();
return this;
},
+15 -3
View File
@@ -2,8 +2,8 @@
* Model, view, and controller objects for Galaxy tools and tool panel.
*/
define( ["libs/underscore", "viz/trackster/util", "mvc/data" ],
function(_, util, data) {
define( ["libs/underscore", "viz/trackster/util", "mvc/data", "mvc/tools/tools-form" ],
function(_, util, data, ToolsForm) {
/**
* Mixin for tracking model visibility.
@@ -262,7 +262,7 @@ var Tool = Backbone.Model.extend({
// Run job and resolve run_deferred to tool outputs.
$.when(ss_deferred.go()).then(function(result) {
run_deferred.resolve(new data.DatasetCollection().reset(result));
run_deferred.resolve(new data.DatasetCollection(result));
});
return run_deferred;
}
@@ -524,6 +524,18 @@ var ToolLinkView = BaseView.extend({
});
}
// regular tools
if ( this.model.id !== 'upload1' && this.model.get( 'model_class' ) === 'Tool' ) {
var self = this;
$link.find('a').on('click', function(e) {
e.preventDefault();
var form = new ToolsForm.View( { id : self.model.id, version : self.model.get('version') } );
form.deferred.execute(function() {
Galaxy.app.display( form.$el );
});
});
}
// add element
this.$el.append($link);
return this;
@@ -9,23 +9,19 @@ define(['utils/utils', 'utils/deferred', 'mvc/ui/ui-misc', 'mvc/form/form-view',
return Backbone.View.extend({
// initialize
initialize: function(options) {
// check if the user is an admin
var galaxy = parent.Galaxy;
if (galaxy && galaxy.currUser) {
this.is_admin = galaxy.currUser.get('is_admin');
} else {
this.is_admin = false;
}
this.options = Utils.merge(options, {});
this.setElement('<div/>');
// create deferred processing queue handler
// this handler reduces the number of requests to the api by filtering redundant requests
this.deferred = new Deferred();
// set element
this.setElement('<div/>');
// create form
this._buildForm(options);
if (options.inputs) {
this._buildForm(options);
} else {
this._buildModel(options, true);
}
},
/** Build form */
@@ -48,6 +44,9 @@ define(['utils/utils', 'utils/deferred', 'mvc/ui/ui-misc', 'mvc/form/form-view',
}
}, this.options);
// allow option customization
this.options.customize && this.options.customize( this.options );
// create form
this.form = new Form(this.options);
@@ -61,28 +60,21 @@ define(['utils/utils', 'utils/deferred', 'mvc/ui/ui-misc', 'mvc/form/form-view',
/** Builds a new model through api call and recreates the entire form
*/
_buildModel: function(options) {
// link this
_buildModel: function(options, hide_message) {
var self = this;
// update current version
this.options.id = options.id;
this.options.version = options.version;
// construct url
var build_url = galaxy_config.root + 'api/tools/' + options.id + '/build?';
if (options.job_id) {
build_url += 'job_id=' + options.job_id;
// build request url
var build_url = '';
var build_data = {};
if ( options.job_id ) {
build_url = Galaxy.root + 'api/jobs/' + options.job_id + '/build_for_rerun';
} else {
if (options.dataset_id) {
build_url += 'dataset_id=' + options.dataset_id;
} else {
build_url += 'tool_version=' + options.version + '&';
var loc = top.location.href;
var pos = loc.indexOf('?');
if (loc.indexOf('tool_id=') != -1 && pos !== -1) {
build_url += loc.slice(pos + 1);
}
build_url = Galaxy.root + 'api/tools/' + options.id + '/build';
if ( Galaxy.params && Galaxy.params.tool_id == options.id ) {
build_data = $.extend( {}, Galaxy.params );
options.version && ( build_data[ 'tool_version' ] = options.version );
}
}
@@ -93,12 +85,13 @@ define(['utils/utils', 'utils/deferred', 'mvc/ui/ui-misc', 'mvc/form/form-view',
Utils.request({
type : 'GET',
url : build_url,
data : build_data,
success : function(new_model) {
// rebuild form
self._buildForm(new_model['tool_model'] || new_model);
// show version message
self.form.message.update({
!hide_message && self.form.message.update({
status : 'success',
message : 'Now you are using \'' + self.options.name + '\' version ' + self.options.version + '.',
persistent : false
@@ -120,16 +113,25 @@ define(['utils/utils', 'utils/deferred', 'mvc/ui/ui-misc', 'mvc/form/form-view',
console.debug(response);
// show error
var error_message = response.error || 'Uncaught error.';
self.form.modal.show({
title : 'Tool cannot be executed',
body : error_message,
buttons : {
'Close' : function() {
self.form.modal.hide();
var error_message = ( response && response.err_msg ) || 'Uncaught error.';
if ( self.$el.is(':empty') ) {
self.$el.prepend((new Ui.Message({
message : error_message,
status : 'danger',
persistent : true,
large : true
})).$el);
} else {
Galaxy.modal.show({
title : 'Tool request failed',
body : error_message,
buttons : {
'Close' : function() {
Galaxy.modal.hide();
}
}
}
});
});
}
}
});
},
@@ -138,7 +140,7 @@ define(['utils/utils', 'utils/deferred', 'mvc/ui/ui-misc', 'mvc/form/form-view',
*/
_updateModel: function() {
// model url for request
var model_url = this.options.update_url || galaxy_config.root + 'api/tools/' + this.options.id + '/build';
var model_url = this.options.update_url || Galaxy.root + 'api/tools/' + this.options.id + '/build';
// link this
var self = this;
@@ -269,19 +271,19 @@ define(['utils/utils', 'utils/deferred', 'mvc/ui/ui-misc', 'mvc/form/form-view',
title : 'Share',
tooltip : 'Share this tool',
onclick : function() {
prompt('Copy to clipboard: Ctrl+C, Enter', window.location.origin + galaxy_config.root + 'root?tool_id=' + options.id);
prompt('Copy to clipboard: Ctrl+C, Enter', window.location.origin + Galaxy.root + 'root?tool_id=' + options.id);
}
});
// add admin operations
if (this.is_admin) {
if (Galaxy.user && Galaxy.user.get('is_admin')) {
// create download button
menu_button.addMenu({
icon : 'fa-download',
title : 'Download',
tooltip : 'Download this tool',
onclick : function() {
window.location.href = galaxy_config.root + 'api/tools/' + options.id + '/download';
window.location.href = Galaxy.root + 'api/tools/' + options.id + '/download';
}
});
}
@@ -351,4 +353,4 @@ define(['utils/utils', 'utils/deferred', 'mvc/ui/ui-misc', 'mvc/form/form-view',
}
}
});
});
});
@@ -121,6 +121,8 @@ define(['utils/utils', 'mvc/tools/tools-form-base'],
_makeSection: function(output_id, datatypes){
// format datatypes
var extensions = [];
var input_terminal_names = [];
for (key in datatypes) {
extensions.push({
0 : datatypes[key],
@@ -128,6 +130,10 @@ define(['utils/utils', 'mvc/tools/tools-form-base'],
});
}
for (key in this.node.input_terminals){
input_terminal_names.push(this.node.input_terminals[key].name);
}
// sort extensions
extensions.sort(function(a, b) {
return a.label > b.label ? 1 : a.label < b.label ? -1 : 0;
@@ -154,15 +160,15 @@ define(['utils/utils', 'mvc/tools/tools-form-base'],
flat : true,
inputs : [{
action : 'RenameDatasetAction',
argument : 'newname',
pja_arg : 'newname',
label : 'Rename dataset',
type : 'text',
value : '',
ignore : '',
help : 'This action will rename the result dataset. Click <a href="https://wiki.galaxyproject.org/Learn/AdvancedWorkflow/Variables">here</a> for more information.'
help : 'This action will rename the output dataset. Click <a href="https://wiki.galaxyproject.org/Learn/AdvancedWorkflow/Variables">here</a> for more information. Valid inputs are: <strong>' + input_terminal_names.join(", ") + '</strong>.'
},{
action : 'ChangeDatatypeAction',
argument : 'newtype',
pja_arg : 'newtype',
label : 'Change datatype',
type : 'select',
ignore : '__empty__',
@@ -171,7 +177,7 @@ define(['utils/utils', 'mvc/tools/tools-form-base'],
help : 'This action will change the datatype of the output to the indicated value.'
},{
action : 'TagDatasetAction',
argument : 'tags',
pja_arg : 'tags',
label : 'Tags',
type : 'text',
value : '',
@@ -183,35 +189,35 @@ define(['utils/utils', 'mvc/tools/tools-form-base'],
flat : true,
inputs : [{
action : 'ColumnSetAction',
argument : 'chromCol',
pja_arg : 'chromCol',
label : 'Chrom column',
type : 'integer',
value : '',
ignore : ''
},{
action : 'ColumnSetAction',
argument : 'startCol',
pja_arg : 'startCol',
label : 'Start column',
type : 'integer',
value : '',
ignore : ''
},{
action : 'ColumnSetAction',
argument : 'endCol',
pja_arg : 'endCol',
label : 'End column',
type : 'integer',
value : '',
ignore : ''
},{
action : 'ColumnSetAction',
argument : 'strandCol',
pja_arg : 'strandCol',
label : 'Strand column',
type : 'integer',
value : '',
ignore : ''
},{
action : 'ColumnSetAction',
argument : 'nameCol',
pja_arg : 'nameCol',
label : 'Name column',
type : 'integer',
value : '',
@@ -231,8 +237,8 @@ define(['utils/utils', 'mvc/tools/tools-form-base'],
if (input.action) {
// construct identifier as expected by backend
input.name = 'pja__' + output_id + '__' + input.action;
if (input.argument) {
input.name += '__' + input.argument;
if (input.pja_arg) {
input.name += '__' + input.pja_arg;
}
// modify names of payload arguments
@@ -253,8 +259,8 @@ define(['utils/utils', 'mvc/tools/tools-form-base'],
}
// update input field value
if (input.argument) {
input.value = d.action_arguments && d.action_arguments[input.argument] || input.value;
if (input.pja_arg) {
input.value = d.action_arguments && d.action_arguments[input.pja_arg] || input.value;
} else {
input.value = 'true';
}
+37 -40
View File
@@ -2,49 +2,46 @@
This is the regular tool form.
*/
define(['utils/utils', 'mvc/ui/ui-misc', 'mvc/tools/tools-form-base', 'mvc/tools/tools-jobs'],
function(Utils, Ui, ToolFormBase, ToolJobs) {
// create form view
function( Utils, Ui, ToolFormBase, ToolJobs ) {
var View = ToolFormBase.extend({
// initialize
initialize: function(options) {
// execute button
initialize: function( options ) {
var self = this;
var execute = new Ui.Button({
icon : 'fa-check',
tooltip : 'Execute: ' + options.name,
title : 'Execute',
cls : 'btn btn-primary',
floating : 'clear',
onclick : function() {
execute.wait();
self.form.portlet.disable();
ToolJobs.submit(self.form, options, function() {
execute.unwait();
self.form.portlet.enable();
});
ToolFormBase.prototype.initialize.call( this, Utils.merge({
customize : function( options ) {
// build execute button
options.buttons = {
execute : execute_btn = new Ui.Button({
icon : 'fa-check',
tooltip : 'Execute: ' + options.name + ' (' + options.version + ')',
title : 'Execute',
cls : 'btn btn-primary',
floating : 'clear',
onclick : function() {
execute_btn.wait();
self.form.portlet.disable();
ToolJobs.submit( self.form, options, function() {
execute_btn.unwait();
self.form.portlet.enable();
} );
}
})
};
// remap feature
if ( options.job_id && options.job_remap ) {
options.inputs[ 'rerun_remap_job_id' ] = {
label : 'Resume dependencies from this job',
name : 'rerun_remap_job_id',
type : 'select',
display : 'radio',
ignore : '__ignore__',
value : '__ignore__',
options : [ [ 'Yes', options.job_id ], [ 'No', '__ignore__' ] ],
help : 'The previous run of this tool failed and other tools were waiting for it to finish successfully. Use this option to resume those tools using the new output(s) of this tool run.'
}
}
}
});
// add remap button
if (options.job_id && options.job_remap) {
options.inputs['rerun_remap_job_id'] = {
label : 'Resume dependencies from this job',
name : 'rerun_remap_job_id',
type : 'select',
display : 'radio',
ignore : '__ignore__',
value : '__ignore__',
options : [['Yes', options.job_id], ['No', '__ignore__']],
help : 'The previous run of this tool failed and other tools were waiting for it to finish successfully. Use this option to resume those tools using the new output(s) of this tool run.'
}
}
// create options dictionary
options = Utils.merge(options, {
buttons : {
execute : execute
}
});
ToolFormBase.prototype.initialize.call(this, options);
}, options ) );
}
});
+11 -11
View File
@@ -34,23 +34,23 @@ return {
data : job_def,
success : function(response) {
callback && callback();
form.reciept(ToolTemplate.success(response));
form.$el.replaceWith( ToolTemplate.success( response ) );
// begin checking the history for updates
self._refreshHdas();
},
error : function(response, response_full) {
error : function(response) {
callback && callback();
if (response && response.message && response.message.data) {
var error_messages = form.data.matchResponse(response.message.data);
console.debug(response);
if (response && response.err_data) {
var error_messages = form.data.matchResponse(response.err_data);
for (var input_id in error_messages) {
form.highlight(input_id, error_messages[input_id]);
break;
}
} else {
// show error message with details
console.debug(response);
response && form.modal.show({
form.modal.show({
title : 'Job submission failed',
body : ToolTemplate.error(job_def),
body : ( response && response.err_msg ) || ToolTemplate.error( job_def ),
buttons : {
'Close' : function() {
form.modal.hide();
@@ -135,11 +135,11 @@ return {
/** Refreshes the history panel
*/
_refreshHdas: function() {
_refreshHdas: function(detailIds, options) {
if (parent.Galaxy && parent.Galaxy.currHistoryPanel) {
parent.Galaxy.currHistoryPanel.refreshContents();
parent.Galaxy.currHistoryPanel.refreshContents(detailIds, options);
}
}
};
});
});
@@ -1,74 +0,0 @@
// dependencies
define(['utils/utils'], function(Utils) {
// plugin
return Backbone.View.extend({
// default options
optionsDefault: {
icons : ['fa fa-square-o', 'fa fa-minus-square-o', 'fa fa-check-square-o'],
value : 0,
title : 'Select/Unselect all'
},
// initialize
initialize: function(options) {
// configure options
this.options = Utils.merge(options, this.optionsDefault);
// create new element
this.setElement(this._template());
this.$title = this.$('.title');
this.$icon = this.$('.icon');
// set initial value
this.value(this.options.value);
// set title
this.$title.html(this.options.title);
// add event handler
var self = this;
this.$el.on('click', function() {
self.current = (self.current === 0 && 2) || 0;
self.value(self.current);
self.options.onclick && self.options.onclick();
});
},
/* Sets a new value and/or returns the current value.
* @param{Integer} new_val - Set a new value 0=unchecked, 1=partial and 2=checked.
* OR:
* @param{Integer} new_val - Number of selected options.
* @param{Integer} total - Total number of available options.
*/
value: function (new_val, total) {
if (new_val !== undefined) {
if (total) {
if (new_val !== 0) {
if (new_val !== total) {
new_val = 1;
} else {
new_val = 2;
}
}
}
this.current = new_val;
this.$icon.removeClass()
.addClass('icon')
.addClass(this.options.icons[new_val]);
this.options.onchange && this.options.onchange(new_val);
}
return this.current;
},
/** Template containing the check button and the title
*/
_template: function() {
return '<div class="ui-button-check" >' +
'<span class="icon"/>' +
'<span class="title"/>' +
'</div>';
}
});
});
@@ -1,172 +0,0 @@
// dependencies
define(['utils/utils'], function(Utils) {
/**
* This class creates a button with dropdown menu. It extends the functionality of the Ui.ButtonIcon class.
*/
return Backbone.View.extend({
// main options
optionsDefault: {
// same as Ui.ButtonIcon
id : '',
title : '',
floating : 'right',
icon : null,
onclick : null,
cls : 'ui-button-icon ui-button-menu',
tooltip : '',
// additional options
target : '',
href : '',
onunload : null,
visible : true,
tag : ''
},
// optional sub menu
$menu: null,
// initialize
initialize: function (options) {
// get options
this.options = Utils.merge(options, this.optionsDefault);
// add template for tab
this.setElement($(this._template(this.options)));
// find root
var $root = $(this.el).find('.root');
// link head
var self = this;
$root.on('click', function(e) {
// hide all tooltips
$('.tooltip').hide();
// prevent default
e.preventDefault();
// add click event
if(self.options.onclick) {
self.options.onclick();
}
});
// visiblity
if (!this.options.visible)
this.hide();
// add tooltip
$root.tooltip({title: options.tooltip, placement: 'bottom'});
},
// show
show: function() {
$(this.el).show();
},
// hide
hide: function() {
$(this.el).hide();
},
// add menu item
addMenu: function (options) {
// menu option defaults
var menuOptions = {
title : '',
target : '',
href : '',
onclick : null,
divider : false,
icon : null,
cls : 'button-menu btn-group'
}
// get options
menuOptions = Utils.merge(options, menuOptions);
// check if submenu element is available
if (!this.$menu) {
// insert submenu element into root
$(this.el).append(this._templateMenu());
// update element link
this.$menu = $(this.el).find('.menu');
}
// create
var $item = $(this._templateMenuItem(menuOptions));
// add events
$item.on('click', function(e) {
// prevent default
e.preventDefault();
// add click event
if(menuOptions.onclick) {
menuOptions.onclick();
}
});
// append menu
this.$menu.append($item);
// append divider
if (menuOptions.divider)
this.$menu.append($(this._templateDivider()));
},
// fill template header
_templateMenuItem: function (options) {
var tmpl = '<li>' +
'<a class="dropdown-item" href="' + options.href + '" target="' + options.target + '">';
if (options.icon)
tmpl += '<i class="fa ' + options.icon + '"></i>';
tmpl += ' ' + options.title +
'</a>' +
'</li>';
return tmpl;
},
// fill template header
_templateMenu: function () {
return '<ul class="menu dropdown-menu pull-right" role="menu"></ul>';
},
_templateDivider: function() {
return '<li class="divider"></li>';
},
// element
_template: function(options) {
// TODO: width/margin should be set in css
var width = '';
var margin = '';
if (options.title) {
width = 'width: auto;';
} else {
margin = 'margin: 0px;';
}
// create base string
var str = '<div id="' + options.id + '" style="float: ' + options.floating + '; ' + width + '" class="dropdown ' + options.cls + '">' +
'<div class="root button dropdown-toggle" data-toggle="dropdown" style="' + margin + '">' +
'<i class="icon fa ' + options.icon + '"/>';
// title
if (options.title) {
str += '&nbsp;<span class="title">' + options.title + '</span>';
}
// finalize
str += '</div>' +
'</div>';
return str;
}
});
});
+387
View File
@@ -0,0 +1,387 @@
/** This class contains all button views.
*/
define(['utils/utils'], function( Utils ) {
/** This renders the default button which is used e.g. at the bottom of the upload modal.
*/
var ButtonBase = Backbone.View.extend({
initialize: function( options ) {
this.options = Utils.merge( options, {
id : Utils.uid(),
title : '',
floating : 'right',
icon : '',
cls : 'ui-button btn btn-default',
cls_wait : 'btn btn-info'
} );
this.setElement( this._template( this.options ) );
var self = this;
$( this.el ).on( 'click' , function() {
$( '.tooltip' ).hide();
if ( options.onclick && !self.disabled ) {
options.onclick();
}
} );
$( this.el ).tooltip( { title: options.tooltip, placement: 'bottom' } );
},
// disable
disable: function() {
this.$el.addClass( 'disabled' );
this.disabled = true;
},
// enable
enable: function() {
this.$el.removeClass( 'disabled' );
this.disabled = false;
},
// show spinner
wait: function() {
this.$el.removeClass( this.options.cls ).addClass( this.options.cls_wait ).prop( 'disabled', true );
this.$( '.icon' ).removeClass( this.options.icon ).addClass( 'fa-spinner fa-spin' );
this.$( '.title' ).html( 'Sending...' );
},
// hide spinner
unwait: function() {
this.$el.removeClass( this.options.cls_wait ).addClass( this.options.cls ).prop( 'disabled', false );
this.$( '.icon' ).removeClass( 'fa-spinner fa-spin' ).addClass( this.options.icon );
this.$( '.title' ).html( this.options.title );
},
// template
_template: function( options ) {
var str = '<button id="' + options.id + '" type="submit" style="float: ' + options.floating + ';" type="button" class="' + options.cls + '">';
if (options.icon) {
str += '<i class="icon fa ' + options.icon + '"/>&nbsp;';
}
str += '<span class="title">' + options.title + '</span>' +
'</button>';
return str;
}
});
/** This button allows the right-click/open-in-new-tab feature, its used e.g. for panel buttons.
*/
var ButtonLink = ButtonBase.extend({
initialize: function( options ) {
ButtonBase.prototype.initialize.call( this, options );
},
_template: function( options ) {
return '<a id="' + options.id + '" class="' + options.cls + '" href="' + ( options.href || 'javascript:void(0)' ) + '"' +
' title="' + options.title + '" target="' + ( options.target || '_top' ) + '">' + '<span class="' + options.icon + '"/>' +
'</a>';
}
});
/** The check button is used in the tool form and allows to distinguish between multiple states e.g. all, partially and nothing selected.
*/
var ButtonCheck = Backbone.View.extend({
initialize: function( options ) {
// configure options
this.options = Utils.merge(options, {
title : 'Select/Unselect all',
icons : ['fa fa-square-o', 'fa fa-minus-square-o', 'fa fa-check-square-o'],
value : 0
});
// create new element
this.setElement( this._template() );
this.$title = this.$( '.title' );
this.$icon = this.$( '.icon' );
// set initial value
this.value( this.options.value );
// set title
this.$title.html( this.options.title );
// add event handler
var self = this;
this.$el.on('click', function() {
self.current = ( self.current === 0 && 2 ) || 0;
self.value( self.current );
self.options.onclick && self.options.onclick();
});
},
/* Sets a new value and/or returns the current value.
* @param{Integer} new_val - Set a new value 0=unchecked, 1=partial and 2=checked.
* OR:
* @param{Integer} new_val - Number of selected options.
* @param{Integer} total - Total number of available options.
*/
value: function ( new_val, total ) {
if ( new_val !== undefined ) {
if ( total ) {
if ( new_val !== 0 ) {
new_val = ( new_val !== total ) && 1 || 2;
}
}
this.current = new_val;
this.$icon.removeClass()
.addClass( 'icon' )
.addClass( this.options.icons[ new_val ] );
this.options.onchange && this.options.onchange( new_val );
}
return this.current;
},
/** Template containing the check button and the title
*/
_template: function() {
return '<div class="ui-button-check" >' +
'<span class="icon"/>' +
'<span class="title"/>' +
'</div>';
}
});
/** This renders a differently styled, more compact button version.
TODO: Consolidate with icon-button.js and/or button-default.js.
*/
var ButtonIcon = Backbone.View.extend({
initialize : function( options ) {
// get options
this.options = Utils.merge( options, {
id : Utils.uid(),
title : '',
floating : 'right',
cls : 'ui-button-icon',
icon : '',
tooltip : '',
onclick : null
});
// create new element
this.setElement( this._template( this.options ) );
// link button element
this.$button = this.$el.find( '.button' );
// add event
var self = this;
$(this.el).on('click', function() {
// hide all tooltips
$( '.tooltip' ).hide();
// execute onclick callback
if ( options.onclick && !self.disabled ) {
options.onclick();
}
});
// add tooltip
this.$button.tooltip( { title: options.tooltip, placement: 'bottom' } );
},
// disable
disable: function() {
this.$button.addClass( 'disabled' );
this.disabled = true;
},
// enable
enable: function() {
this.$button.removeClass( 'disabled' );
this.disabled = false;
},
// change icon
setIcon: function(icon_cls) {
this.$('i').removeClass( this.options.icon ).addClass( icon_cls );
this.options.icon = icon_cls;
},
// template
_template: function( options ) {
// width
var width = '';
if ( options.title ) {
width = 'width: auto;';
}
// string
var str = '<div id="' + options.id + '" style="float: ' + options.floating + '; ' + width + '" class="' + options.cls + '">' +
'<div class="button">';
if (options.title) {
str += '<i class="icon fa ' + options.icon + '"/>&nbsp;' +
'<span class="title">' + options.title + '</span>';
} else {
str += '<i class="icon fa ' + options.icon + '"/>';
}
str += '</div>' +
'</div>';
return str;
}
});
/** This class creates a button with dropdown menu. It extends the functionality of the Ui.ButtonIcon class.
TODO: Consolidate class, use common base class
*/
var ButtonMenu = Backbone.View.extend({
// optional sub menu
$menu: null,
// initialize
initialize: function ( options ) {
// get options
this.options = Utils.merge( options, {
// same as Ui.ButtonIcon
id : '',
title : '',
floating : 'right',
pull : 'right',
icon : null,
onclick : null,
cls : 'ui-button-icon ui-button-menu',
tooltip : '',
// additional options
target : '',
href : '',
onunload : null,
visible : true,
tag : ''
} );
// add template for tab
this.setElement( $( this._template( this.options ) ) );
// find root
var $root = $( this.el ).find( '.root' );
// link head
var self = this;
$root.on( 'click', function( e ) {
// hide all tooltips
$( '.tooltip' ).hide();
// prevent default
e.preventDefault();
// add click event
if( self.options.onclick ) {
self.options.onclick();
}
});
// visiblity
if ( !this.options.visible )
this.hide();
// add tooltip
$root.tooltip( { title: options.tooltip, placement: 'bottom' } );
},
// show
show: function() {
$( this.el ).show();
},
// hide
hide: function() {
$( this.el ).hide();
},
// add menu item
addMenu: function ( options ) {
// menu option defaults
var menuOptions = {
title : '',
target : '',
href : '',
onclick : null,
divider : false,
icon : null,
cls : 'button-menu btn-group'
}
// get options
menuOptions = Utils.merge( options, menuOptions );
// check if submenu element is available
if ( !this.$menu ) {
// insert submenu element into root
$( this.el ).append( this._templateMenu() );
// update element link
this.$menu = $( this.el ).find( '.menu' );
}
// create
var $item = $( this._templateMenuItem( menuOptions ) );
// add events
$item.on( 'click', function( e ) {
// prevent default
e.preventDefault();
// add click event
if( menuOptions.onclick ) {
menuOptions.onclick ();
}
});
// append menu
this.$menu.append( $item );
// append divider
if ( menuOptions.divider ) {
this.$menu.append( $( this._templateDivider() ) );
}
},
// fill template header
_templateMenuItem: function ( options ) {
var tmpl = '<li>' +
'<a class="dropdown-item" href="' + options.href + '" target="' + options.target + '">';
if ( options.icon ) {
tmpl += '<i class="fa ' + options.icon + '"/>';
}
tmpl += ' ' + options.title +
'</a>' +
'</li>';
return tmpl;
},
// fill template header
_templateMenu: function () {
return '<ul class="menu dropdown-menu pull-' + this.options.pull + '" role="menu"/>';
},
_templateDivider: function() {
return '<li class="divider"/>';
},
// element
_template: function(options) {
// TODO: width/margin should be set in css
var width = '';
var margin = '';
if ( options.title ) {
width = 'width: auto;';
} else {
margin = 'margin: 0px;';
}
var str = '<div id="' + options.id + '" style="float: ' + options.floating + '; ' + width + '" class="dropdown ' + options.cls + '">' +
'<div class="root button dropdown-toggle" data-toggle="dropdown" style="' + margin + '">' +
'<i class="icon fa ' + options.icon + '"/>';
if ( options.title ) {
str += '&nbsp;<span class="title">' + options.title + '</span>';
}
str += '</div>' +
'</div>';
return str;
}
});
return {
ButtonDefault : ButtonBase,
ButtonLink : ButtonLink,
ButtonIcon : ButtonIcon,
ButtonCheck : ButtonCheck,
ButtonMenu : ButtonMenu
}
});
+165 -177
View File
@@ -1,199 +1,187 @@
// dependencies
define(['utils/utils', 'mvc/ui/ui-misc'], function(Utils, Ui) {
/** Renders the color picker used e.g. in the tool form **/
define(['utils/utils'], function( Utils ) {
return Backbone.View.extend({
colors: {
standard: ['c00000','ff0000','ffc000','ffff00','92d050','00b050','00b0f0','0070c0','002060','7030a0'],
base : ['ffffff','000000','eeece1','1f497d','4f81bd','c0504d','9bbb59','8064a2','4bacc6','f79646'],
theme :[['f2f2f2','7f7f7f','ddd9c3','c6d9f0','dbe5f1','f2dcdb','ebf1dd','e5e0ec','dbeef3','fdeada'],
['d8d8d8','595959','c4bd97','8db3e2','b8cce4','e5b9b7','d7e3bc','ccc1d9','b7dde8','fbd5b5'],
['bfbfbf','3f3f3f','938953','548dd4','95b3d7','d99694','c3d69b','b2a2c7','92cddc','fac08f'],
['a5a5a5','262626','494429','17365d','366092','953734','76923c','5f497a','31859b','e36c09'],
['7f7f7e','0c0c0c','1d1b10','0f243e','244061','632423','4f6128','3f3151','205867','974806']]
},
initialize : function( options ) {
this.options = Utils.merge( options, {} );
this.setElement( this._template() );
// plugin
return Backbone.View.extend({
colors: {
standard: ['c00000','ff0000','ffc000','ffff00','92d050','00b050','00b0f0','0070c0','002060','7030a0'],
base : ['ffffff','000000','eeece1','1f497d','4f81bd','c0504d','9bbb59','8064a2','4bacc6','f79646'],
theme :[['f2f2f2','7f7f7f','ddd9c3','c6d9f0','dbe5f1','f2dcdb','ebf1dd','e5e0ec','dbeef3','fdeada'],
['d8d8d8','595959','c4bd97','8db3e2','b8cce4','e5b9b7','d7e3bc','ccc1d9','b7dde8','fbd5b5'],
['bfbfbf','3f3f3f','938953','548dd4','95b3d7','d99694','c3d69b','b2a2c7','92cddc','fac08f'],
['a5a5a5','262626','494429','17365d','366092','953734','76923c','5f497a','31859b','e36c09'],
['7f7f7e','0c0c0c','1d1b10','0f243e','244061','632423','4f6128','3f3151','205867','974806']]
},
// link components
this.$panel = this.$( '.ui-color-picker-panel' );
this.$view = this.$( '.ui-color-picker-view' );
this.$value = this.$( '.ui-color-picker-value' );
this.$header = this.$( '.ui-color-picker-header' );
// default options
optionsDefault: {
},
// build panel
this._build();
// initialize
initialize : function(options) {
// configure options
this.options = Utils.merge(options, this.optionsDefault);
// hide panel on start up
this.visible = false;
// create new element
this.setElement(this._template());
this.$panel = this.$('.ui-color-picker-panel');
this.$view = this.$('.ui-color-picker-view');
this.$value = this.$('.ui-color-picker-value');
this.$header = this.$('.ui-color-picker-header');
// set initial value
this.value( this.options.value );
// build panel
this._build();
// link boxes
this.$boxes = this.$( '.ui-color-picker-box' );
// hide panel on start up
this.visible = false;
// add event handler
var self = this;
this.$boxes.on( 'click', function() {
self.value( $( this ).css( 'background-color' ) );
self.$header.trigger( 'click' );
} );
this.$header.on( 'click', function() {
self.visible = !self.visible;
if ( self.visible ) {
self.$view.fadeIn( 'fast' );
} else {
self.$view.fadeOut( 'fast' );
}
} );
},
// set initial value
this.value(this.options.value);
// link boxes
this.$boxes = this.$('.ui-color-picker-box');
// add event handler
var self = this;
this.$boxes.on('click', function() {
self.value($(this).css('background-color'));
self.$header.trigger('click');
});
this.$header.on('click', function() {
self.visible = !self.visible;
if (self.visible) {
self.$view.fadeIn('fast');
} else {
self.$view.fadeOut('fast');
}
});
},
// value
value : function (new_val) {
if (new_val !== undefined) {
// set default value
if (new_val !== null) {
// value
value : function ( new_val ) {
if ( new_val !== undefined && new_val !== null ) {
// update color value
this.$value.css('background-color', new_val);
this.$value.css( 'background-color', new_val );
// check selected color in panel
this.$('.ui-color-picker-box').empty();
this.$(this._getValue()).html(this._templateCheck());
this.$( '.ui-color-picker-box' ).empty();
this.$( this._getValue() ).html( this._templateCheck() );
// trigger custom event
this.options.onchange && this.options.onchange(new_val);
this.options.onchange && this.options.onchange( new_val );
}
}
// return current value
return this._getValue();
},
// return current value
return this._getValue();
},
// get value from dom
_getValue: function() {
var rgb = this.$value.css('background-color');
rgb = rgb.match(/^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/);
if (rgb) {
function hex(x) {
return ('0' + parseInt(x).toString(16)).slice(-2);
}
return '#' + hex(rgb[1]) + hex(rgb[2]) + hex(rgb[3]);
} else {
return null;
}
},
// build panel
_build: function() {
var $content = this._content({
label : 'Theme Colors',
colors : this.colors.base,
padding : 10
});
for (var i in this.colors.theme) {
var line_def = {};
if (i == 0) {
line_def['bottom'] = true;
} else {
if (i != this.colors.theme.length - 1) {
line_def['top'] = true;
line_def['bottom'] = true;
} else {
line_def['top'] = true;
line_def['padding'] = 5;
// get value from dom
_getValue: function() {
var rgb = this.$value.css( 'background-color' );
rgb = rgb.match(/^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/);
if ( rgb ) {
function hex( x ) {
return ( '0' + parseInt( x ).toString( 16 ) ).slice( -2 );
}
return '#' + hex( rgb[ 1] ) + hex( rgb[ 2 ] ) + hex( rgb[ 3 ] );
} else {
return null;
}
line_def['colors'] = this.colors.theme[i];
this._content(line_def);
}
this._content({
label : 'Standard Colors',
colors : this.colors.standard,
padding : 5
});
},
},
// create content
_content: function(options) {
// get parameters
var label = options.label;
var colors = options.colors;
var padding = options.padding;
var top = options.top;
var bottom = options.bottom;
// create lines
var $content = $(this._templateContent());
// set label
var $label = $content.find('.label');
if (options.label) {
$label.html(options.label);
} else {
$label.hide();
}
// build line
var $line = $content.find('.line');
this.$panel.append($content);
for (var i in colors) {
var $box = $(this._templateBox(colors[i]));
if (top) {
$box.css('border-top', 'none');
$box.css('border-top-left-radius', '0px');
$box.css('border-top-right-radius', '0px');
// build panel
_build: function() {
var $content = this._content({
label : 'Theme Colors',
colors : this.colors.base,
padding : 10
});
for ( var i in this.colors.theme ) {
var line_def = {};
if ( i == 0 ) {
line_def[ 'bottom' ] = true;
} else {
if ( i != this.colors.theme.length - 1 ) {
line_def[ 'top' ] = true;
line_def[ 'bottom' ] = true;
} else {
line_def[ 'top' ] = true;
line_def[ 'padding' ] = 5;
}
}
line_def[ 'colors' ] = this.colors.theme[ i ];
this._content( line_def );
}
if (bottom) {
$box.css('border-bottom', 'none');
$box.css('border-bottom-left-radius', '0px');
$box.css('border-bottom-right-radius', '0px');
this._content({
label : 'Standard Colors',
colors : this.colors.standard,
padding : 5
});
},
// create content
_content: function( options ) {
// get parameters
var label = options.label;
var colors = options.colors;
var padding = options.padding;
var top = options.top;
var bottom = options.bottom;
// create lines
var $content = $( this._templateContent() );
// set label
var $label = $content.find( '.label' );
if ( options.label ) {
$label.html( options.label );
} else {
$label.hide();
}
$line.append($box);
// build line
var $line = $content.find( '.line' );
this.$panel.append( $content );
for ( var i in colors ) {
var $box = $( this._templateBox( colors[ i ] ) );
if ( top ) {
$box.css( 'border-top', 'none' );
$box.css( 'border-top-left-radius', '0px' );
$box.css( 'border-top-right-radius', '0px' );
}
if ( bottom ) {
$box.css( 'border-bottom', 'none' );
$box.css( 'border-bottom-left-radius', '0px' );
$box.css( 'border-bottom-right-radius', '0px' );
}
$line.append( $box );
}
if (padding) {
$line.css( 'padding-bottom', padding );
}
return $content;
},
// check icon
_templateCheck: function() {
return '<div class="ui-color-picker-check fa fa-check"/>';
},
// content template
_templateContent: function() {
return '<div class="ui-color-picker-content">' +
'<div class="label"/>' +
'<div class="line"/>' +
'</div>';
},
// box template
_templateBox: function( color ) {
return '<div id="' + color + '" class="ui-color-picker-box" style="background-color: #' + color + ';"/>';
},
// template
_template: function() {
return '<div class="ui-color-picker">' +
'<div class="ui-color-picker-header">' +
'<div class="ui-color-picker-value"/>' +
'<div class="ui-color-picker-label">Select a color</div>' +
'</div>' +
'<div class="ui-color-picker-view ui-input">' +
'<div class="ui-color-picker-panel"/>' +
'</div>'
'</div>';
}
if (padding) {
$line.css('padding-bottom', padding);
}
return $content;
},
// check icon
_templateCheck: function() {
return '<div class="ui-color-picker-check fa fa-check"/>';
},
// content template
_templateContent: function() {
return '<div class="ui-color-picker-content">' +
'<div class="label"/>' +
'<div class="line"/>' +
'</div>';
},
// box template
_templateBox: function(color) {
return '<div id="' + color + '" class="ui-color-picker-box" style="background-color: #' + color + ';"/>';
},
// template
_template: function() {
return '<div class="ui-color-picker">' +
'<div class="ui-color-picker-header">' +
'<div class="ui-color-picker-value"/>' +
'<div class="ui-color-picker-label">Select a color</div>' +
'</div>' +
'<div class="ui-color-picker-view ui-input">' +
'<div class="ui-color-picker-panel"/>' +
'</div>'
'</div>';
}
});
});
});
+261 -478
View File
@@ -1,493 +1,276 @@
// dependencies
/**
* This class contains backbone wrappers for basic ui elements such as Images, Labels, Buttons, Input fields etc.
*/
define(['utils/utils',
'mvc/ui/ui-select-default',
'mvc/ui/ui-slider',
'mvc/ui/ui-options',
'mvc/ui/ui-drilldown',
'mvc/ui/ui-button-menu',
'mvc/ui/ui-button-check',
'mvc/ui/ui-buttons',
'mvc/ui/ui-modal'],
function(Utils, Select, Slider, Options, Drilldown, ButtonMenu, ButtonCheck, Modal) {
function( Utils, Select, Slider, Options, Drilldown, Buttons, Modal ) {
/**
* This class contains backbone wrappers for basic ui elements such as Images, Labels, Buttons, Input fields etc.
*/
// plugin
var Image = Backbone.View.extend({
// options
optionsDefault: {
url : '',
cls : ''
},
// initialize
initialize : function(options) {
// get options
this.options = Utils.merge(options, this.optionsDefault);
// create new element
this.setElement(this._template(this.options));
},
// template
_template: function(options) {
return '<img class="ui-image ' + options.cls + '" src="' + options.url + '"/>';
}
});
// plugin
var Label = Backbone.View.extend({
// options
optionsDefault: {
title : '',
cls : ''
},
// initialize
initialize : function(options) {
// get options
this.options = Utils.merge(options, this.optionsDefault);
// create new element
this.setElement(this._template(this.options));
},
// title
title: function(new_title) {
this.$el.html(new_title);
},
// template
_template: function(options) {
return '<label class="ui-label ' + options.cls + '">' + options.title + '</label>';
},
// value
value: function() {
return options.title;
}
});
// plugin
var Icon = Backbone.View.extend({
// options
optionsDefault: {
floating : 'right',
icon : '',
tooltip : '',
placement : 'bottom',
title : '',
cls : ''
},
// initialize
initialize : function(options) {
// get options
this.options = Utils.merge(options, this.optionsDefault);
// create new element
this.setElement(this._template(this.options));
// add tooltip
$(this.el).tooltip({title: options.tooltip, placement: 'bottom'});
},
// element
_template: function(options) {
return '<div>' +
'<span class="fa ' + options.icon + '" class="ui-icon"/>&nbsp;' +
options.title +
'</div>';
}
});
// plugin
var Button = Backbone.View.extend({
// options
optionsDefault: {
id : Utils.uid(),
title : '',
floating : 'right',
cls : 'ui-button btn btn-default',
icon : ''
},
// initialize
initialize : function(options) {
// get options
this.options = Utils.merge(options, this.optionsDefault);
// create new element
this.setElement(this._template(this.options));
// add event
$(this.el).on('click', function() {
// hide all tooltips
$('.tooltip').hide();
// execute onclick callback
if (options.onclick) {
options.onclick();
}
});
// add tooltip
$(this.el).tooltip({title: options.tooltip, placement: 'bottom'});
},
// show spinner
wait: function() {
this.$el.removeClass(this.options.cls).addClass('btn btn-info').prop('disabled', true);
this.$('.icon').removeClass(this.options.icon).addClass('fa-spinner fa-spin');
this.$('.title').html('Sending...');
},
// hide spinner
unwait: function() {
this.$el.removeClass('btn btn-info').addClass(this.options.cls).prop('disabled', false);
this.$('.icon').removeClass('fa-spinner fa-spin').addClass(this.options.icon);
this.$('.title').html(this.options.title);
},
// element
_template: function(options) {
var str = '<button id="' + options.id + '" type="submit" style="float: ' + options.floating + ';" type="button" class="' + options.cls + '">';
if (options.icon) {
str += '<i class="icon fa ' + options.icon + '"></i>&nbsp;';
}
str += '<span class="title">' + options.title + '</span>' +
'</button>';
return str;
}
});
// plugin
var ButtonIcon = Backbone.View.extend({
// options
optionsDefault: {
id : Utils.uid(),
title : '',
floating : 'right',
cls : 'ui-button-icon',
icon : '',
tooltip : '',
onclick : null
},
// initialize
initialize : function(options) {
// get options
this.options = Utils.merge(options, this.optionsDefault);
// create new element
this.setElement(this._template(this.options));
// link button element
this.$button = this.$el.find('.button');
// add event
var self = this;
$(this.el).on('click', function() {
// hide all tooltips
$('.tooltip').hide();
// execute onclick callback
if (options.onclick && !self.disabled) {
options.onclick();
}
});
// add tooltip
this.$button.tooltip({title: options.tooltip, placement: 'bottom'});
},
// disable
disable: function() {
this.$button.addClass('disabled');
this.disabled = true;
},
// enable
enable: function() {
this.$button.removeClass('disabled');
this.disabled = false;
},
// change icon
setIcon: function(icon_cls) {
this.$('i').removeClass(this.options.icon).addClass(icon_cls);
this.options.icon = icon_cls;
},
// template
_template: function(options) {
// width
var width = '';
if (options.title) {
width = 'width: auto;';
}
// string
var str = '<div id="' + options.id + '" style="float: ' + options.floating + '; ' + width + '" class="' + options.cls + '">' +
'<div class="button">';
if (options.title) {
str += '<i class="icon fa ' + options.icon + '"/>&nbsp;' +
'<span class="title">' + options.title + '</span>';
} else {
str += '<i class="icon fa ' + options.icon + '"/>';
}
str += '</div>' +
'</div>';
return str;
}
});
// plugin
var Anchor = Backbone.View.extend({
// options
optionsDefault: {
title : '',
cls : ''
},
// initialize
initialize : function(options) {
// get options
this.options = Utils.merge(options, this.optionsDefault);
// create new element
this.setElement(this._template(this.options));
// add event
$(this.el).on('click', options.onclick);
},
// element
_template: function(options) {
return '<div><a href="javascript:void(0)" class="ui-anchor ' + options.cls + '">' + options.title + '</a></div>';
}
});
// plugin
var Message = Backbone.View.extend({
// options
optionsDefault: {
message : null,
status : 'info',
cls : '',
persistent : false
},
// initialize
initialize : function(options) {
// configure options
this.options = Utils.merge(options, this.optionsDefault);
// create new element
this.setElement('<div class="' + this.options.cls + '"/>');
// show initial message
if (this.options.message) {
this.update(this.options);
}
},
// update
update : function(options) {
// get options
this.options = Utils.merge(options, this.optionsDefault);
// show message
if (options.message != '') {
this.$el.html(this._template(this.options));
this.$el.find('.alert').append(options.message);
this.$el.fadeIn();
// clear previous timeouts
if (this.timeout) {
window.clearTimeout(this.timeout);
}
// set timeout if message is not persistent
if (!options.persistent) {
var self = this;
this.timeout = window.setTimeout(function() {
if (self.$el.is(':visible')) {
self.$el.fadeOut();
} else {
self.$el.hide();
}
}, 3000);
}
} else {
this.$el.fadeOut();
}
},
// element
_template: function(options) {
return '<div class="ui-message alert alert-' + options.status + '"/>';
}
});
// plugin
var Searchbox = Backbone.View.extend({
// options
optionsDefault: {
onclick : null,
searchword : ''
},
// initialize
initialize : function(options) {
// configure options
this.options = Utils.merge(options, this.optionsDefault);
// create new element
this.setElement(this._template(this.options));
// add click event
var self = this;
if (this.options.onclick) {
this.$el.on('submit', function(e) {
var search_field = self.$el.find('#search');
self.options.onclick(search_field.val());
/** Image wrapper */
var Image = Backbone.View.extend({
initialize : function(options) {
this.options = Utils.merge(options, {
url : '',
cls : ''
});
this.setElement(this._template(this.options));
},
_template: function(options) {
return '<img class="ui-image ' + options.cls + '" src="' + options.url + '"/>';
}
},
});
// element
_template: function(options) {
return '<div class="ui-search">' +
'<form onsubmit="return false;">' +
'<input id="search" class="form-control input-sm" type="text" name="search" placeholder="Search..." value="' + options.searchword + '">' +
'<button type="submit" class="btn search-btn">' +
'<i class="fa fa-search"></i>' +
'</button>' +
'</form>' +
'</div>';
}
});
// plugin
var Input = Backbone.View.extend({
// options
optionsDefault: {
type : 'text',
placeholder : '',
disabled : false,
visible : true,
cls : '',
area : false
},
// initialize
initialize : function(options) {
// configure options
this.options = Utils.merge(options, this.optionsDefault);
// create new element
this.setElement(this._template(this.options));
// set initial value
if (this.options.value !== undefined) {
this.value(this.options.value);
/** Label wrapper */
var Label = Backbone.View.extend({
initialize : function(options) {
this.options = Utils.merge(options, {
title : '',
cls : ''
});
this.setElement(this._template(this.options));
},
title: function(new_title) {
this.$el.html(new_title);
},
value: function() {
return options.title;
},
_template: function(options) {
return '<label class="ui-label ' + options.cls + '">' + options.title + '</label>';
}
});
// disable input field
if (this.options.disabled) {
this.$el.prop('disabled', true);
}
// hide input field
if (!this.options.visible) {
this.$el.hide();
}
// onchange event handler. fires on user activity.
var self = this;
this.$el.on('input', function() {
if (self.options.onchange) {
self.options.onchange(self.$el.val());
}
});
},
// value
value : function (new_val) {
if (new_val !== undefined) {
this.$el.val(new_val);
}
return this.$el.val();
},
// element
_template: function(options) {
if (options.area) {
return '<textarea id="' + options.id + '" class="ui-textarea ' + options.cls + '"></textarea>';
} else {
return '<input id="' + options.id + '" type="' + options.type + '" value="' + options.value + '" placeholder="' + options.placeholder + '" class="ui-input ' + options.cls + '">';
}
}
});
// plugin
var Hidden = Backbone.View.extend({
// initialize
initialize : function(options) {
// configure options
this.options = options;
// create new element
this.setElement(this._template(this.options));
// set initial value
if (this.options.value !== undefined) {
this.value(this.options.value);
}
},
// value
value : function (new_val) {
if (new_val !== undefined) {
this.$('hidden').val(new_val);
}
return this.$('hidden').val();
},
// element
_template: function(options) {
var tmpl = '<div id="' + options.id + '" >';
if (options.info) {
tmpl += '<div>' + options.info + '</div>';
}
tmpl += '<hidden value="' + options.value + '"/>' +
/** Displays an icon with title */
var Icon = Backbone.View.extend({
initialize : function(options) {
this.options = Utils.merge(options, {
floating : 'right',
icon : '',
tooltip : '',
placement : 'bottom',
title : '',
cls : ''
});
this.setElement(this._template(this.options));
$(this.el).tooltip({title: options.tooltip, placement: 'bottom'});
},
_template: function(options) {
return '<div>' +
'<span class="fa ' + options.icon + '" class="ui-icon"/>&nbsp;' +
options.title +
'</div>';
return tmpl;
}
});
/** Renders an anchor element */
var Anchor = Backbone.View.extend({
initialize : function(options) {
this.options = Utils.merge(options, {
title : '',
cls : ''
});
this.setElement(this._template(this.options));
$(this.el).on('click', options.onclick);
},
_template: function(options) {
return '<div><a href="javascript:void(0)" class="ui-anchor ' + options.cls + '">' + options.title + '</a></div>';
}
});
/** Displays messages used e.g. in the tool form */
var Message = Backbone.View.extend({
initialize : function(options) {
this.options = Utils.merge(options, {
message : null,
status : 'info',
cls : '',
persistent : false
});
this.setElement('<div class="' + this.options.cls + '"/>');
this.options.message && this.update(this.options);
},
// update
update: function(options) {
// get options
this.options = Utils.merge(options, this.options);
// show message
if (options.message != '') {
this.$el.html(this._template(this.options));
this.$el.fadeIn();
// clear previous timeouts
if (this.timeout) {
window.clearTimeout(this.timeout);
}
// set timeout if message is not persistent
if (!options.persistent) {
var self = this;
this.timeout = window.setTimeout(function() {
if (self.$el.is(':visible')) {
self.$el.fadeOut();
} else {
self.$el.hide();
}
}, 3000);
}
} else {
this.$el.fadeOut();
}
},
// template
_template: function(options) {
var cls_status = 'ui-message alert alert-' + options.status;
if (options.large) {
cls_status = ( ( options.status == 'success' && 'done' ) ||
( options.status == 'danger' && 'error' ) ||
options.status ) + 'messagelarge';
}
return '<div class="' + cls_status + '" >' +
options.message +
'</div>';
}
});
/** Render a search box */
var Searchbox = Backbone.View.extend({
initialize : function(options) {
this.options = Utils.merge(options, {
onclick : null,
searchword : ''
});
this.setElement(this._template(this.options));
var self = this;
if (this.options.onclick) {
this.$el.on('submit', function(e) {
var search_field = self.$el.find('#search');
self.options.onclick(search_field.val());
});
}
},
_template: function(options) {
return '<div class="ui-search">' +
'<form onsubmit="return false;">' +
'<input id="search" class="form-control input-sm" type="text" name="search" placeholder="Search..." value="' + options.searchword + '">' +
'<button type="submit" class="btn search-btn">' +
'<i class="fa fa-search"></i>' +
'</button>' +
'</form>' +
'</div>';
}
});
/** Renders an input element used e.g. in the tool form */
var Input = Backbone.View.extend({
initialize : function(options) {
// configure options
this.options = Utils.merge(options, {
type : 'text',
placeholder : '',
disabled : false,
visible : true,
cls : '',
area : false
});
// create new element
this.setElement(this._template(this.options));
// set initial value
if (this.options.value !== undefined) {
this.value(this.options.value);
}
// disable input field
if (this.options.disabled) {
this.$el.prop('disabled', true);
}
// hide input field
if (!this.options.visible) {
this.$el.hide();
}
// onchange event handler. fires on user activity.
var self = this;
this.$el.on('input', function() {
if (self.options.onchange) {
self.options.onchange(self.$el.val());
}
});
},
// value
value : function (new_val) {
if (new_val !== undefined) {
this.$el.val(new_val);
}
return this.$el.val();
},
// template
_template: function(options) {
if (options.area) {
return '<textarea id="' + options.id + '" class="ui-textarea ' + options.cls + '"></textarea>';
} else {
return '<input id="' + options.id + '" type="' + options.type + '" value="' + options.value + '" placeholder="' + options.placeholder + '" class="ui-input ' + options.cls + '">';
}
}
});
/** Creates a hidden element input field used e.g. in the tool form */
var Hidden = Backbone.View.extend({
initialize : function(options) {
this.options = options;
this.setElement(this._template(this.options));
if (this.options.value !== undefined) {
this.value(this.options.value);
}
},
value : function (new_val) {
if (new_val !== undefined) {
this.$('hidden').val(new_val);
}
return this.$('hidden').val();
},
_template: function(options) {
var tmpl = '<div id="' + options.id + '" >';
if (options.info) {
tmpl += '<div>' + options.info + '</div>';
}
tmpl += '<hidden value="' + options.value + '"/>' +
'</div>';
return tmpl;
}
});
return {
Anchor : Anchor,
Button : Buttons.ButtonDefault,
ButtonIcon : Buttons.ButtonIcon,
ButtonCheck : Buttons.ButtonCheck,
ButtonMenu : Buttons.ButtonMenu,
ButtonLink : Buttons.ButtonLink,
Icon : Icon,
Image : Image,
Input : Input,
Label : Label,
Message : Message,
Modal : Modal,
RadioButton : Options.RadioButton,
Checkbox : Options.Checkbox,
Radio : Options.Radio,
Searchbox : Searchbox,
Select : Select,
Hidden : Hidden,
Slider : Slider,
Drilldown : Drilldown
}
});
// return
return {
Anchor : Anchor,
Button : Button,
ButtonIcon : ButtonIcon,
ButtonCheck : ButtonCheck,
ButtonMenu : ButtonMenu,
Icon : Icon,
Image : Image,
Input : Input,
Label : Label,
Message : Message,
Modal : Modal,
RadioButton : Options.RadioButton,
Checkbox : Options.Checkbox,
Radio : Options.Radio,
Searchbox : Searchbox,
Select : Select,
Hidden : Hidden,
Slider : Slider,
Drilldown : Drilldown
}
});
+10 -4
View File
@@ -13,7 +13,8 @@ var View = Backbone.View.extend({
height : null,
width : null,
closing_events : false,
closing_callback : null
closing_callback : null,
title_separator : true
},
// button list
@@ -35,12 +36,12 @@ var View = Backbone.View.extend({
if (this.options.height){
this.$body.css('height', this.options.height);
this.$body.css('overflow', 'hidden');
} else{
} else {
this.$body.css('max-height', $(window).height() / 2);
}
// fix width
if (this.options.width){
if (this.options.width) {
this.$dialog.css('width', this.options.width);
}
@@ -169,6 +170,11 @@ var View = Backbone.View.extend({
// hide modal if background is clicked
this.$el.find('.modal-backdrop').on('click', function() { self.hide(); });
}
// removes the default separator line
if (!this.options.title_separator) {
this.$('.modal-header').css({ 'border': 'none', 'padding-bottom': '0px' });
}
},
// fill regular modal template
@@ -180,7 +186,7 @@ var View = Backbone.View.extend({
'<div class="modal-header">' +
'<button type="button" class="close" style="display: none;">&times;</button>' +
'<h4 class="title">' + title + '</h4>' +
'</div>' +
'</div>' +
'<div class="modal-body" style="position: static;"></div>' +
'<div class="modal-footer">' +
'<div class="buttons" style="float: right;"></div>' +
+6 -9
View File
@@ -1,5 +1,5 @@
// dependencies
define(['utils/utils', 'mvc/ui/ui-button-check'], function(Utils, ButtonCheck) {
define(['utils/utils', 'mvc/ui/ui-buttons'], function(Utils, Buttons) {
/** Base class for options based ui elements **/
var Base = Backbone.View.extend({
@@ -8,18 +8,15 @@ var Base = Backbone.View.extend({
// link this
var self = this;
// options
this.optionsDefault = {
// configure options
this.options = Utils.merge(options, {
visible : true,
data : [],
id : Utils.uid(),
error_text : 'No data available.',
error_text : 'No options available.',
wait_text : 'Please wait...',
multiple : false
};
// configure options
this.options = Utils.merge(options, this.optionsDefault);
});
// create new element
this.setElement('<div class="ui-options"/>');
@@ -36,7 +33,7 @@ var Base = Backbone.View.extend({
// add select/unselect all button
if (this.options.multiple) {
this.all_button = new ButtonCheck({
this.all_button = new Buttons.ButtonCheck({
onclick: function() {
self.$('input').prop('checked', self.all_button.value() !== 0);
self.trigger('change');
+75 -75
View File
@@ -2,7 +2,7 @@
define(['utils/utils'], function(Utils) {
var View = Backbone.View.extend({
// default options
optionsDefault: {
with_close : true,
@@ -10,45 +10,44 @@ var View = Backbone.View.extend({
title : null,
placement : 'top'
},
// visibility flag
visible: false,
// initialize
initialize: function (options) {
// link this
var self = this;
// update options
this.options = _.defaults(options, this.optionsDefault);
// set element
this.setElement(this._template(this.options));
// attach popover to parent
this.options.container.parent().append(this.$el);
// attach close
if (this.options.with_close) {
this.$el.find('#close').on('click', function() { self.hide(); });
}
// generate unique id
this.uid = Utils.uid();
// add event to hide if click is outside of popup
var self = this;
$('body').on('mousedown.' + this.uid, function(e) { self._hide(e) });
},
// title
title: function(val) {
if (val !== undefined) {
this.$el.find('.popover-title-label').html(val);
}
},
// show
show: function () {
// show popover
@@ -57,107 +56,108 @@ var View = Backbone.View.extend({
// calculate position
var position = this._get_placement(this.options.placement);
// set position
this.$el.css(position);
},
// calculate position and error
_get_placement: function(placement) {
// get popover dimensions
var width = this._get_width(this.$el);
var height = this.$el.height();
// get container details
var $container = this.options.container;
var container_width = this._get_width($container);
var container_height = this._get_height($container);
var container_position = $container.position();
// initialize position
var top = 0;
var left = 0;
// calculate position
if (placement == 'top' || placement == 'bottom') {
left = container_position.left - width + (container_width + width) / 2;
if (placement == 'top') {
top = container_position.top - height - 5;
} else {
top = container_position.top + container_height + 5;
}
}
// return
return {top: top, left: left};
},
// width
_get_width: function($el) {
return $el.width() + parseInt($el.css('padding-left')) + parseInt($el.css('padding-right'))
},
// height
_get_height: function($el) {
return $el.height() + parseInt($el.css('padding-top')) + parseInt($el.css('padding-bottom'))
},
// hide
hide: function () {
this.$el.hide();
this.visible = false;
},
// append
// append new doms to popover content
append: function($el) {
this.$el.find('.popover-content').append($el);
},
// empty
// empty popover content
empty: function($el) {
this.$el.find('.popover-content').empty();
},
// remove
// remove event handler and dom
remove: function() {
// remove event handler
$('body').off('mousedown.' + this.uid);
// remove element from dom
this.$el.remove();
},
// calculate position and error
_get_placement: function(placement) {
// get popover dimensions
var width = this._get_width(this.$el);
var height = this.$el.height();
// get container details
var $container = this.options.container;
var container_width = this._get_width($container);
var container_height = this._get_height($container);
var container_position = $container.position();
// initialize position
var top = 0;
var left = 0;
// calculate position
if (['top', 'bottom'].indexOf(placement) != -1) {
left = container_position.left - width + (container_width + width) / 2;
switch (placement) {
case 'top':
top = container_position.top - height - 5;
break;
case 'bottom':
top = container_position.top + container_height + 5;
break;
}
} else {
top = container_position.top - height + (container_height + height) / 2;
switch (placement) {
case 'right':
left = container_position.left + container_width;
break;
}
}
// return
return {top: top, left: left};
},
// width
_get_width: function($el) {
return $el.width() + parseInt($el.css('padding-left')) + parseInt($el.css('padding-right'))
},
// height
_get_height: function($el) {
return $el.height() + parseInt($el.css('padding-top')) + parseInt($el.css('padding-bottom'))
},
// remove
_hide : function(e) {
//the 'is' for buttons that trigger popups
//the 'has' for icons within a button that triggers a popup
// the 'is' for buttons that trigger popups
// the 'has' for icons within a button that triggers a popup
if (!$(this.options.container).is(e.target) &&
!$(this.el).is(e.target) &&
$(this.el).has(e.target).length === 0) {
this.hide();
}
},
// template
_template: function(options) {
var tmpl = '<div class="ui-popover popover fade ' + options.placement + ' in">' +
'<div class="arrow"></div>' +
'<div class="arrow"/>' +
'<div class="popover-title">' +
'<div class="popover-title-label">' +
options.title +
'</div>';
// add close icon
if (options.with_close) {
tmpl += '<div id="close" class="popover-close fa fa-times-circle"></div>';
tmpl += '<div id="close" class="popover-close fa fa-times-circle"/>';
}
// finalize
tmpl += '</div>' +
'<div class="popover-content"></div>' +
'<div class="popover-content"/>' +
'</div>';
// return
return tmpl;
}
});
@@ -1,16 +1,14 @@
// dependencies
define(['utils/utils', 'mvc/ui/ui-button-check'], function(Utils, ButtonCheck) {
/**
* This class creates/wraps a default html select field as backbone class.
*/
define(['utils/utils', 'mvc/ui/ui-buttons'], function(Utils, Buttons) {
var View = Backbone.View.extend({
// options
optionsDefault: {
id : Utils.uid(),
cls : 'ui-select',
error_text : 'No data available',
empty_text : 'No selection',
error_text : 'No options available',
empty_text : 'Nothing selected',
visible : true,
wait : false,
multiple : false,
@@ -38,7 +36,7 @@ var View = Backbone.View.extend({
if (this.options.multiple) {
// create select all button
if (this.options.searchable) {
this.all_button = new ButtonCheck({
this.all_button = new Buttons.ButtonCheck({
onclick: function() {
var new_value = [];
if (self.all_button.value() !== 0) {
@@ -4,7 +4,7 @@ define(['utils/utils', 'mvc/ui/ui-misc', 'mvc/ui/ui-table', 'mvc/ui/ui-list'],
// collection of libraries
var Libraries = Backbone.Collection.extend({
url: galaxy_config.root + 'api/libraries'
url: galaxy_config.root + 'api/libraries?deleted=false'
});
// collection of dataset
+3 -3
View File
@@ -68,9 +68,9 @@ var View = Backbone.View.extend({
this.$text.on('keydown', function (e) {
var v = e.which;
pressed[v] = true;
if (!(v == 8 || v == 9 || v == 13 || v == 37 || v == 39 || (v >= 48 && v <= 57)
|| (v == 190 && $(this).val().indexOf('.') == -1 && self.options.precise)
|| (v == 189 && $(this).val().indexOf('-') == -1)
if (!(v == 8 || v == 9 || v == 13 || v == 37 || v == 39 || (v >= 48 && v <= 57) || (v >= 96 && v <= 105)
|| ((v == 190 || v == 110) && $(this).val().indexOf('.') == -1 && self.options.precise)
|| ((v == 189 || v == 109) && $(this).val().indexOf('-') == -1)
|| pressed[91] || pressed[17])) {
event.preventDefault();
}
@@ -0,0 +1,354 @@
// dependencies
define(['utils/utils',
'mvc/upload/upload-settings',
'mvc/upload/upload-ftp',
'mvc/ui/ui-popover',
'mvc/ui/ui-misc',
'mvc/ui/ui-select',
'utils/uploadbox'],
function( Utils,
UploadSettings,
UploadFtp,
Popover,
Ui,
Select
) {
// renders the composite upload row view
return Backbone.View.extend({
// states
status_classes : {
init : 'upload-mode fa fa-exclamation text-primary',
ready : 'upload-mode fa fa-check text-success',
running : 'upload-mode fa fa-spinner fa-spin',
success : 'upload-mode fa fa-check',
error : 'upload-mode fa fa-exclamation-triangle'
},
// initialize
initialize: function(app, options) {
// link app
this.app = app;
// link this
var self = this;
// create model
this.model = options.model;
// add upload row
this.setElement(this._template(options.model));
// build upload functions
this.uploadinput = this.$el.uploadinput({
ondragover: function() {
if (self.model.get('enabled')) {
self.$el.addClass('warning');
}
},
ondragleave: function() {
self.$el.removeClass('warning');
},
onchange: function(files) {
if (self.model.get('status') != 'running' && files && files.length > 0) {
self.model.reset({
'file_data': files[0],
'file_name': files[0].name,
'file_size': files[0].size,
'file_mode': files[0].mode || 'local'
});
self._refreshReady();
}
}
});
// source selection popup
this.button_menu = new Ui.ButtonMenu({
icon : 'fa-caret-down',
title : 'Select',
pull : 'left'
});
this.$('#source').append(this.button_menu.$el);
this.button_menu.addMenu({
icon : 'fa-laptop',
title : 'Choose local file',
onclick : function() {
self.uploadinput.dialog();
}
});
if (this.app.ftp_upload_site) {
this.button_menu.addMenu({
icon : 'fa-folder-open-o',
title : 'Choose FTP file',
onclick : function() {
self._showFtp();
}
});
}
this.button_menu.addMenu({
icon : 'fa-edit',
title : 'Paste/Fetch data',
onclick : function() {
self.model.reset({
'file_mode': 'new',
'file_name': 'New File'
});
}
});
// add ftp file viewer
this.ftp = new Popover.View({
title : 'Choose FTP file:',
container : this.$('#source').find('.ui-button-menu'),
placement : 'right'
});
// append popup to settings icon
this.settings = new Popover.View({
title : 'Upload configuration',
container : this.$('#settings'),
placement : 'bottom'
});
//
// ui events
//
// handle text editing event
this.$('#text-content').on('change input', function(e) {
self.model.set('url_paste', $(e.target).val());
self.model.set('file_size', $(e.target).val().length);
self._refreshReady();
});
// handle settings popover
this.$('#settings').on('click' , function(e) { self._showSettings(); })
.on('mousedown', function(e) { e.preventDefault(); });
//
// model events
//
this.model.on('change:percentage', function() {
self._refreshPercentage();
});
this.model.on('change:status', function() {
self._refreshStatus();
});
this.model.on('change:info', function() {
self._refreshInfo();
});
this.model.on('change:file_name', function() {
self._refreshFileName();
});
this.model.on('change:file_mode', function() {
self._refreshMode();
});
this.model.on('change:file_size', function() {
self._refreshFileSize();
});
this.model.on('remove', function() {
self.remove();
});
this.app.collection.on('reset', function() {
self.remove();
});
},
// render
render: function() {
this.$('#file_name').html(this.model.get('file_name') || '-');
this.$('#file_desc').html(this.model.get('file_desc') || 'Unavailable');
this.$('#file_size').html(Utils.bytesToString (this.model.get('file_size')));
this.$('#status').removeClass().addClass(this.status_classes.init);
},
// remove
remove: function() {
// call the base class remove method
Backbone.View.prototype.remove.apply(this);
},
//
// handle model events
//
// refresh ready or not states
_refreshReady: function() {
this.app.collection.each(function(model) {
model.set('status', (model.get('file_size') > 0) && 'ready' || 'init');
});
},
// refresh mode and e.g. show/hide textarea field
_refreshMode: function() {
var file_mode = this.model.get('file_mode');
if (file_mode == 'new') {
this.height = this.$el.height();
this.$('#text').css({
'width' : this.$el.width() - 16 + 'px',
'top' : this.$el.height() - 8 + 'px'
}).show();
this.$el.height(this.$el.height() - 8 + this.$('#text').height() + 16);
this.$('#text-content').val('').trigger('keyup');
} else {
this.$el.height(this.height);
this.$('#text').hide();
}
},
// information
_refreshInfo: function() {
var info = this.model.get('info');
if (info) {
this.$('#info-text').html('<strong>Failed: </strong>' + info).show();
} else {
this.$('#info-text').hide();
}
},
// percentage
_refreshPercentage : function() {
var percentage = parseInt(this.model.get('percentage'));
if (percentage != 0) {
this.$('.progress-bar').css({ width : percentage + '%' });
} else {
this.$('.progress-bar').addClass('no-transition');
this.$('.progress-bar').css({ width : '0%' });
this.$('.progress-bar')[0].offsetHeight;
this.$('.progress-bar').removeClass('no-transition');
}
if (percentage != 100) {
this.$('#percentage').html(percentage + '%');
} else {
this.$('#percentage').html('Adding to history...');
}
},
// status
_refreshStatus : function() {
// identify new status
var status = this.model.get('status');
// identify symbol and reset classes
this.$('#status').removeClass().addClass(this.status_classes[status]);
// enable/disable model flag
this.model.set('enabled', status != 'running');
// enable/disable row fields
this.$('#text-content').attr('disabled', !this.model.get('enabled'));
// remove status classes
this.$el.removeClass('success danger warning');
// set status classes
if (status == 'running' || status == 'ready') {
this.model.set('percentage', 0);
}
if (status == 'running') {
this.$('#source').find('.button').addClass('disabled');
} else {
this.$('#source').find('.button').removeClass('disabled');
}
if (status == 'success') {
this.$el.addClass('success');
this.model.set('percentage', 100);
this.$('#percentage').html('100%');
}
if (status == 'error') {
this.$el.addClass('danger');
this.model.set('percentage', 0);
this.$('#info-progress').hide();
this.$('#info-text').show();
} else {
this.$('#info-progress').show();
this.$('#info-text').hide();
}
},
// file name
_refreshFileName: function() {
this.$('#file_name').html(this.model.get('file_name') || '-');
},
// file size
_refreshFileSize: function() {
this.$('#file_size').html(Utils.bytesToString (this.model.get('file_size')));
},
// show/hide ftp popup
_showFtp: function() {
if (!this.ftp.visible) {
this.ftp.empty();
var self = this;
this.ftp.append((new UploadFtp({
ftp_upload_site: this.app.ftp_upload_site,
onchange: function(ftp_file) {
self.ftp.hide();
if (self.model.get('status') != 'running' && ftp_file) {
self.model.reset({
'file_mode': 'ftp',
'file_name': ftp_file.path,
'file_size': ftp_file.size,
'file_path': ftp_file.path
});
self._refreshReady();
}
}
})).$el);
this.ftp.show();
} else {
this.ftp.hide();
}
},
// show/hide settings popup
_showSettings : function() {
if (!this.settings.visible) {
this.settings.empty();
this.settings.append((new UploadSettings(this)).$el);
this.settings.show();
} else {
this.settings.hide();
}
},
// template
_template: function(options) {
return '<tr id="upload-row-' + options.id + '" class="upload-row">' +
'<td>' +
'<div id="source"/>' +
'<div class="upload-text-column">' +
'<div id="text" class="text">' +
'<div class="text-info">You can tell Galaxy to download data from web by entering URL in this box (one per line). You can also directly paste the contents of a file.</div>' +
'<textarea id="text-content" class="text-content form-control"/>' +
'</div>' +
'</div>' +
'</td>' +
'<td>' +
'<div id="status"/>' +
'</td>' +
'<td>' +
'<div id="file_desc" class="upload-title"/>' +
'</td>' +
'<td>' +
'<div id="file_name" class="upload-title"/>' +
'</td>' +
'<td>' +
'<div id="file_size" class="upload-size"/>' +
'</td>' +
'<td><div id="settings" class="upload-icon-button fa fa-gear"/></td>' +
'<td>' +
'<div id="info" class="upload-info">' +
'<div id="info-text"/>' +
'<div id="info-progress" class="progress">' +
'<div class="progress-bar progress-bar-success"/>' +
'<div id="percentage" class="percentage">0%</div>' +
'</div>' +
'</div>' +
'</td>' +
'</tr>';
}
});
});
@@ -0,0 +1,257 @@
/** Renders contents of the composite uploader */
define(['utils/utils',
'mvc/upload/upload-model',
'mvc/upload/composite/composite-row',
'mvc/ui/ui-popover',
'mvc/ui/ui-select',
'mvc/ui/ui-misc'],
function( Utils,
UploadModel,
UploadRow,
Popover,
Select,
Ui
) {
return Backbone.View.extend({
// extension selector
select_extension: null,
// genome selector
select_genome: null,
// collection
collection: new UploadModel.Collection(),
// initialize
initialize: function(app) {
// link app
this.app = app;
this.options = app.options;
this.list_extensions = app.list_extensions;
this.list_genomes = app.list_genomes;
this.ftp_upload_site = app.currentFtp();
// link this
var self = this;
// set element
this.setElement(this._template());
// create button section
this.btnStart = new Ui.Button({ title: 'Start', onclick: function() { self._eventStart(); } });
this.btnClose = new Ui.Button({ title: 'Close', onclick: function() { self.app.modal.hide(); } });
// append buttons to dom
var buttons = [ this.btnStart, this.btnClose ];
for (var i in buttons) {
this.$('#upload-buttons').prepend(buttons[i].$el);
}
// select extension
this.select_extension = new Select.View({
css : 'footer-selection',
container : this.$('#footer-extension'),
data : _.filter(this.list_extensions, function(ext) { return ext.composite_files }),
onchange : function(extension) {
self.collection.reset();
var details = _.findWhere(self.list_extensions, { id : extension });
if (details && details.composite_files) {
for (var i in details.composite_files) {
var item = details.composite_files[i];
self.collection.add({
id : self.collection.size(),
file_desc : item['description'] || item['name']
});
}
}
}
});
// handle extension info popover
this.$('#footer-extension-info').on('click', function(e) {
self._showExtensionInfo({
$el : $(e.target),
title : self.select_extension.text(),
extension : self.select_extension.value(),
placement : 'top'
});
}).on('mousedown', function(e) { e.preventDefault(); });
// genome extension
this.select_genome = new Select.View({
css : 'footer-selection',
container : this.$('#footer-genome'),
data : this.list_genomes,
value : this.options.default_genome
});
// listener for collection triggers on change in composite datatype
this.collection.on('add', function (model) {
self._eventAnnounce(model);
});
this.collection.on('change add', function() {
self._updateScreen();
}).trigger('change');
// trigger initial onchange event
this.select_extension.options.onchange(this.select_extension.value());
},
//
// upload events / process pipeline
//
// builds the basic ui with placeholder rows for each composite data type file
_eventAnnounce: function(model) {
// create view/model
var upload_row = new UploadRow(this, { model : model });
// add upload row element to table
this.$('#upload-table > tbody:first').append(upload_row.$el);
// render
upload_row.render();
// table visibility
if (this.collection.length > 0) {
this.$('#upload-table').show();
} else {
this.$('#upload-table').hide();
}
},
// start upload process
_eventStart: function() {
var self = this;
this.collection.each(function(model) {
model.set('genome', self.select_genome.value());
model.set('extension', self.select_extension.value());
});
$.uploadpost({
url : this.app.options.nginx_upload_path,
data : this.app.toData(this.collection.filter()),
success : function(message) { self._eventSuccess(message); },
error : function(message) { self._eventError(message); },
progress : function(percentage) { self._eventProgress(percentage); }
});
},
// progress
_eventProgress: function(percentage) {
this.collection.each(function(it) { it.set('percentage', percentage); });
},
// success
_eventSuccess: function(message) {
this.collection.each(function(it) {
it.set('status', 'success');
});
Galaxy.currHistoryPanel.refreshContents();
},
// error
_eventError: function(message) {
this.collection.each(function(it) {
it.set('status', 'error');
it.set('info', message);
});
},
// display extension info popup
_showExtensionInfo: function(options) {
// initialize
var self = this;
var $el = options.$el;
var extension = options.extension;
var title = options.title;
var description = _.findWhere(this.list_extensions, { id : extension });
// create popup
this.extension_popup && this.extension_popup.remove();
this.extension_popup = new Popover.View({
placement: options.placement || 'bottom',
container: $el,
destroy: true
});
// add content and show popup
this.extension_popup.title(title);
this.extension_popup.empty();
this.extension_popup.append(this._templateDescription(description));
this.extension_popup.show();
},
// set screen
_updateScreen: function () {
// show start button if components have been selected
var model = this.collection.first();
if (model && model.get('status') == 'running') {
this.select_genome.disable();
this.select_extension.disable();
} else {
this.select_genome.enable();
this.select_extension.enable();
}
if (this.collection.where({ status : 'ready' }).length == this.collection.length && this.collection.length > 0) {
this.btnStart.enable();
this.btnStart.$el.addClass('btn-primary');
} else {
this.btnStart.disable();
this.btnStart.$el.removeClass('btn-primary');
}
// table visibility
if (this.collection.length > 0) {
this.$('#upload-table').show();
} else {
this.$('#upload-table').hide();
}
},
// template for extensions description
_templateDescription: function(options) {
if (options.description) {
var tmpl = options.description;
if (options.description_url) {
tmpl += '&nbsp;(<a href="' + options.description_url + '" target="_blank">read more</a>)';
}
return tmpl;
} else {
return 'There is no description available for this file extension.';
}
},
// load html template
_template: function() {
return '<div class="upload-view-composite">' +
'<div id="upload-footer" class="upload-footer">' +
'<span class="footer-title">Composite Type:</span>' +
'<span id="footer-extension"/>' +
'<span id="footer-extension-info" class="upload-icon-button fa fa-search"/> ' +
'<span class="footer-title">Genome/Build:</span>' +
'<span id="footer-genome"/>' +
'</div>' +
'<div id="upload-box" class="upload-box">' +
'<table id="upload-table" class="ui-table-striped" style="display: none;">' +
'<thead>' +
'<tr>' +
'<th/>' +
'<th/>' +
'<th>Description</th>' +
'<th>Name</th>' +
'<th>Size</th>' +
'<th>Settings</th>' +
'<th>Status</th>' +
'</tr>' +
'</thead>' +
'<tbody></tbody>' +
'</table>' +
'</div>' +
'<div id="upload-buttons" class="upload-buttons"/>' +
'</div>';
}
});
});
@@ -12,13 +12,8 @@ define(['utils/utils',
Select
) {
// item view
// row view
return Backbone.View.extend({
// options
options: {
padding : 8
},
// states
status_classes : {
init : 'upload-icon-button fa fa-trash-o',
@@ -46,18 +41,15 @@ return Backbone.View.extend({
var self = this;
// create model
this.model = new UploadModel.Model(options);
this.model = options.model;
// add upload item
this.setElement(this._template(options));
// link item
var it = this.$el;
// add upload row
this.setElement(this._template(options.model));
// append popup to settings icon
this.settings = new Popover.View({
title : 'Upload configuration',
container : it.find('#settings'),
container : this.$('#settings'),
placement : 'bottom'
});
@@ -66,13 +58,13 @@ return Backbone.View.extend({
// select genomes
this.select_genome = new Select.View({
css: 'genome',
css: 'upload-genome',
onchange : function(genome) {
self.model.set('genome', genome);
self.app.updateGenome(genome, true);
},
data: self.app.list_genomes,
container: it.find('#genome'),
container: this.$('#genome'),
value: default_genome
});
@@ -84,13 +76,13 @@ return Backbone.View.extend({
// select extension
this.select_extension = new Select.View({
css: 'extension',
css: 'upload-extension',
onchange : function(extension) {
self.model.set('extension', extension);
self.app.updateExtension(extension, true);
},
data: self.app.list_extensions,
container: it.find('#extension'),
container: this.$('#extension'),
value: default_extension
});
@@ -102,10 +94,10 @@ return Backbone.View.extend({
//
// handle click event
it.find('#symbol').on('click', function() { self._removeRow(); });
this.$('#symbol').on('click', function() { self._removeRow(); });
// handle extension info popover
it.find('#extension-info').on('click' , function(e) {
this.$('#extension-info').on('click' , function(e) {
self.app.showExtensionInfo({
$el : $(e.target),
title : self.select_extension.text(),
@@ -114,20 +106,15 @@ return Backbone.View.extend({
}).on('mousedown', function(e) { e.preventDefault(); });
// handle settings popover
it.find('#settings').on('click' , function(e) { self._showSettings(); })
this.$('#settings').on('click' , function(e) { self._showSettings(); })
.on('mousedown', function(e) { e.preventDefault(); });
// handle text editing event
it.find('#text-content').on('keyup', function(e) {
this.$('#text-content').on('change input', function(e) {
self.model.set('url_paste', $(e.target).val());
self.model.set('file_size', $(e.target).val().length);
});
// handle space to tabs button
it.find('#space_to_tabs').on('change', function(e) {
self.model.set('space_to_tabs', $(e.target).prop('checked'));
});
//
// model events
//
@@ -164,54 +151,36 @@ return Backbone.View.extend({
var file_size = this.model.get('file_size');
var file_mode = this.model.get('file_mode');
// link item
var it = this.$el;
// update title
it.find('#title').html(file_name);
this.$('#title').html(file_name);
// update info
it.find('#size').html(Utils.bytesToString (file_size));
this.$('#size').html(Utils.bytesToString (file_size));
// remove mode class
it.find('#mode')
this.$('#mode')
.removeClass()
.addClass('mode');
.addClass('upload-mode')
.addClass('text-primary');
// activate text field if file is new
if (file_mode == 'new') {
// get text component
var text = it.find('#text');
// get padding
var padding = this.options.padding;
// get dimensions
var width = it.width() - 2 * padding;
var height = it.height() - padding;
// set dimensions
text.css('width', width + 'px');
text.css('top', height + 'px');
it.height(height + text.height() + 2 * padding);
// show text field
text.show();
// update icon
it.find('#mode').addClass('fa fa-pencil');
this.$('#text').css({
'width' : this.$el.width() - 16 + 'px',
'top' : this.$el.height() - 8 + 'px'
}).show();
this.$el.height(this.$el.height() - 8 + this.$('#text').height() + 16);
this.$('#mode').addClass('fa fa-edit');
}
// file from local disk
if (file_mode == 'local') {
// update icon
it.find('#mode').addClass('fa fa-laptop');
this.$('#mode').addClass('fa fa-laptop');
}
// file from ftp
if (file_mode == 'ftp') {
// update icon
it.find('#mode').addClass('fa fa-code-fork');
this.$('#mode').addClass('fa fa-folder-open-o');
}
},
@@ -244,74 +213,61 @@ return Backbone.View.extend({
// write error message
var info = this.model.get('info');
if (info) {
this.$el.find('#info').html('<strong>Failed: </strong>' + info).show();
this.$('#info').html('<strong>Failed: </strong>' + info).show();
} else {
this.$el.find('#info').hide();
this.$('#info').hide();
}
},
// progress
_refreshPercentage : function() {
var percentage = parseInt(this.model.get('percentage'));
this.$el.find('.progress-bar').css({ width : percentage + '%' });
this.$('.progress-bar').css({ width : percentage + '%' });
if (percentage != 100)
this.$el.find('#percentage').html(percentage + '%');
this.$('#percentage').html(percentage + '%');
else
this.$el.find('#percentage').html('Adding to history...');
this.$('#percentage').html('Adding to history...');
},
// status
_refreshStatus : function() {
// get element
var it = this.$el;
// identify new status
var status = this.model.get('status');
var status_class = this.status_classes[status];
// identify symbol and reset classes
var sy = this.$el.find('#symbol');
sy.removeClass();
this.$('#symbol').removeClass().addClass(this.status_classes[status]);
// set new status class
sy.addClass(status_class);
// enable/disable model flag
this.model.set('enabled', status == 'init');
// enable form fields
if (status == 'init') {
// select fields
// enable/disable row fields
var enabled = this.model.get('enabled');
this.$('#text-content').attr('disabled', !enabled);
if (enabled) {
this.select_genome.enable();
this.select_extension.enable();
// default fields
it.find('#text-content').attr('disabled', false);
it.find('#space_to_tabs').attr('disabled', false);
} else {
// select fields
this.select_genome.disable();
this.select_extension.disable();
// default fields
it.find('#text-content').attr('disabled', true);
it.find('#space_to_tabs').attr('disabled', true);
}
// success
if (status == 'success') {
it.addClass('success');
it.find('#percentage').html('100%');
this.$el.addClass('success');
this.$('#percentage').html('100%');
}
// error
if (status == 'error') {
it.addClass('danger');
it.find('.progress').remove();
this.$el.addClass('danger');
this.$('.progress').remove();
}
},
// refresh size
_refreshFileSize: function() {
var count = this.model.get('file_size');
this.$el.find('#size').html(Utils.bytesToString (count));
this.$('#size').html(Utils.bytesToString (count));
},
//
@@ -345,38 +301,38 @@ return Backbone.View.extend({
// template
_template: function(options) {
return '<tr id="upload-item-' + options.id + '" class="upload-item">' +
return '<tr id="upload-row-' + options.id + '" class="upload-row">' +
'<td>' +
'<div class="name-column">' +
'<div id="mode"></div>' +
'<div id="title" class="title"></div>' +
'<div class="upload-text-column">' +
'<div id="mode"/>' +
'<div id="title" class="upload-title"/>' +
'<div id="text" class="text">' +
'<div class="text-info">You can tell Galaxy to download data from web by entering URL in this box (one per line). You can also directly paste the contents of a file.</div>' +
'<textarea id="text-content" class="text-content form-control"></textarea>' +
'<textarea id="text-content" class="text-content form-control"/>' +
'</div>' +
'</div>' +
'</td>' +
'<td>' +
'<div id="size" class="size"></div>' +
'<div id="size" class="upload-size"/>' +
'</td>' +
'<td>' +
'<div id="extension" class="extension" style="float: left;"/>&nbsp;&nbsp' +
'<div id="extension" class="upload-extension" style="float: left;"/>&nbsp;&nbsp' +
'<div id="extension-info" class="upload-icon-button fa fa-search"/>' +
'</td>' +
'<td>' +
'<div id="genome" class="genome" />' +
'<div id="genome" class="upload-genome"/>' +
'</td>' +
'<td><div id="settings" class="upload-icon-button fa fa-gear"></div>' +
'<td><div id="settings" class="upload-icon-button fa fa-gear"/></td>' +
'<td>' +
'<div id="info" class="info">' +
'<div id="info" class="upload-info">' +
'<div class="progress">' +
'<div class="progress-bar progress-bar-success"></div>' +
'<div class="progress-bar progress-bar-success"/>' +
'<div id="percentage" class="percentage">0%</div>' +
'</div>' +
'</div>' +
'</td>' +
'<td>' +
'<div id="symbol" class="' + this.status_classes.init + '"></div>' +
'<div id="symbol" class="' + this.status_classes.init + '"/>' +
'</td>' +
'</tr>';
}
@@ -0,0 +1,568 @@
/** Renders contents of the default uploader */
define(['utils/utils',
'mvc/upload/upload-model',
'mvc/upload/default/default-row',
'mvc/upload/upload-ftp',
'mvc/ui/ui-popover',
'mvc/ui/ui-select',
'mvc/ui/ui-misc',
'utils/uploadbox'],
function( Utils,
UploadModel,
UploadRow,
UploadFtp,
Popover,
Select,
Ui
) {
return Backbone.View.extend({
// extension selector
select_extension : null,
// genome selector
select_genome: null,
// jquery uploadbox plugin
uploadbox: null,
// current upload size in bytes
upload_size: 0,
// contains upload row models
collection : new UploadModel.Collection(),
// ftp file viewer
ftp : null,
// keeps track of the current uploader state
counter : {
announce : 0,
success : 0,
error : 0,
running : 0,
reset : function() {
this.announce = this.success = this.error = this.running = 0;
}
},
// initialize
initialize : function(app) {
// link app
this.app = app;
this.options = app.options;
this.list_extensions = app.list_extensions;
this.list_genomes = app.list_genomes;
this.ui_button = app.ui_button;
this.ftp_upload_site = app.currentFtp();
// link this
var self = this;
// set element
this.setElement(this._template());
// create button section
this.btnLocal = new Ui.Button({ id: 'btn-local', title: 'Choose local file', onclick: function() { self.uploadbox.select(); }, icon: 'fa fa-laptop' });
this.btnFtp = new Ui.Button({ id: 'btn-ftp', title: 'Choose FTP file', onclick: function() { self._eventFtp(); }, icon: 'fa fa-folder-open-o' });
this.btnCreate = new Ui.Button({ id: 'btn-new', title: 'Paste/Fetch data', onclick: function() { self._eventCreate(); }, icon: 'fa fa-edit' });
this.btnStart = new Ui.Button({ id: 'btn-start', title: 'Start', onclick: function() { self._eventStart(); } });
this.btnStop = new Ui.Button({ id: 'btn-stop', title: 'Pause', onclick: function() { self._eventStop(); } });
this.btnReset = new Ui.Button({ id: 'btn-reset', title: 'Reset', onclick: function() { self._eventReset(); } });
this.btnClose = new Ui.Button({ id: 'btn-close', title: 'Close', onclick: function() { self.app.modal.hide(); } });
// append buttons to dom
var buttons = [ this.btnLocal, this.btnFtp, this.btnCreate, this.btnStop, this.btnReset, this.btnStart, this.btnClose ];
for (var i in buttons) {
this.$('#upload-buttons').prepend(buttons[i].$el);
}
// file upload
var self = this;
this.uploadbox = this.$('#upload-box').uploadbox({
url : this.app.options.nginx_upload_path,
announce : function(index, file) { self._eventAnnounce(index, file) },
initialize : function(index) { return self.app.toData([ self.collection.get(index) ], self.history_id) },
progress : function(index, percentage) { self._eventProgress(index, percentage) },
success : function(index, message) { self._eventSuccess(index, message) },
error : function(index, message) { self._eventError(index, message) },
complete : function() { self._eventComplete() },
ondragover : function() { self.$('.upload-box').addClass('highlight'); },
ondragleave : function() { self.$('.upload-box').removeClass('highlight'); }
});
// add ftp file viewer
this.ftp = new Popover.View({
title : 'FTP files',
container : this.btnFtp.$el
});
// select extension
this.select_extension = new Select.View({
css : 'footer-selection',
container : this.$('#footer-extension'),
data : _.filter(this.list_extensions, function(ext) { return !ext.composite_files }),
value : this.options.default_extension,
onchange : function(extension) {
self.updateExtension(extension);
}
});
// handle extension info popover
self.$('#footer-extension-info').on('click', function(e) {
self.showExtensionInfo({
$el : $(e.target),
title : self.select_extension.text(),
extension : self.select_extension.value(),
placement : 'top'
});
}).on('mousedown', function(e) { e.preventDefault(); });
// genome extension
this.select_genome = new Select.View({
css : 'footer-selection',
container : this.$('#footer-genome'),
data : this.list_genomes,
value : this.options.default_genome,
onchange : function(genome) {
self.updateGenome(genome);
}
});
// events
this.collection.on('remove', function(model) {
self._eventRemove(model);
});
// setup info
this._updateScreen();
},
//
// events triggered by the upload box plugin
//
// a new file has been dropped/selected through the uploadbox plugin
_eventAnnounce: function(index, file) {
// update counter
this.counter.announce++;
// create if model has not been created yet
var new_model = new UploadModel.Model({
id : index,
file_name : file.name,
file_size : file.size,
file_mode : file.mode || 'local',
file_path : file.path,
file_data : file
});
// add model to collection
this.collection.add(new_model);
// create view/model
var upload_row = new UploadRow(this, { model: new_model });
// add upload row element to table
this.$('#upload-table > tbody:first').append(upload_row.$el);
// show on screen info
this._updateScreen();
// render
upload_row.render();
},
// progress
_eventProgress: function(index, percentage) {
// set progress for row
var it = this.collection.get(index);
it.set('percentage', percentage);
// update ui button
this.ui_button.set('percentage', this._uploadPercentage(percentage, it.get('file_size')));
},
// success
_eventSuccess: function(index, message) {
// update status
var it = this.collection.get(index);
it.set('percentage', 100);
it.set('status', 'success');
// update ui button
this.ui_button.set('percentage', this._uploadPercentage(100, it.get('file_size')));
// update completed
this.upload_completed += it.get('file_size') * 100;
// update counter
this.counter.announce--;
this.counter.success++;
// update on screen info
this._updateScreen();
// update galaxy history
Galaxy.currHistoryPanel.refreshContents();
},
// error
_eventError: function(index, message) {
// get element
var it = this.collection.get(index);
// update status
it.set('percentage', 100);
it.set('status', 'error');
it.set('info', message);
// update ui button
this.ui_button.set('percentage', this._uploadPercentage(100, it.get('file_size')));
this.ui_button.set('status', 'danger');
// update completed
this.upload_completed += it.get('file_size') * 100;
// update counter
this.counter.announce--;
this.counter.error++;
// update on screen info
this._updateScreen();
},
// queue is done
_eventComplete: function() {
// reset queued upload to initial status
this.collection.each(function(model) {
if(model.get('status') == 'queued') {
model.set('status', 'init');
}
});
// update running
this.counter.running = 0;
// update on screen info
this._updateScreen();
},
//
// events triggered by collection
//
// remove model from upload list
_eventRemove: function(model) {
// update status
var status = model.get('status');
// reduce counter
if (status == 'success') {
this.counter.success--;
} else if (status == 'error') {
this.counter.error--;
} else {
this.counter.announce--;
}
// remove from queue
this.uploadbox.remove(model.id);
// show on screen info
this._updateScreen();
},
//
// events triggered by this view
//
// [public] display extension info popup
showExtensionInfo: function(options) {
// initialize
var self = this;
var $el = options.$el;
var extension = options.extension;
var title = options.title;
var description = _.findWhere(self.list_extensions, {'id': extension});
// create popup
this.extension_popup && this.extension_popup.remove();
this.extension_popup = new Popover.View({
placement: options.placement || 'bottom',
container: $el,
destroy: true
});
// add content and show popup
this.extension_popup.title(title);
this.extension_popup.empty();
this.extension_popup.append(this._templateDescription(description));
this.extension_popup.show();
},
// show/hide ftp popup
_eventFtp: function() {
if (!this.ftp.visible) {
this.ftp.empty();
var self = this;
this.ftp.append((new UploadFtp({
collection : this.collection,
ftp_upload_site : this.ftp_upload_site,
onadd: function(ftp_file) {
self.uploadbox.add([{
mode: 'ftp',
name: ftp_file.path,
size: ftp_file.size,
path: ftp_file.path
}]);
},
onremove: function(model_index) {
self.collection.remove(model_index);
}
})).$el);
this.ftp.show();
} else {
this.ftp.hide();
}
},
// create a new file
_eventCreate: function (){
this.uploadbox.add([{
name : 'New File',
size : 0,
mode : 'new'
}]);
},
// start upload process
_eventStart: function() {
// check
if (this.counter.announce == 0 || this.counter.running > 0) {
return;
}
// reset current size
var self = this;
this.upload_size = 0;
this.upload_completed = 0;
// switch icons for new uploads
this.collection.each(function(model) {
if(model.get('status') == 'init') {
model.set('status', 'queued');
self.upload_size += model.get('file_size');
}
});
// reset progress
this.ui_button.set('percentage', 0);
this.ui_button.set('status', 'success');
// update running
this.counter.running = this.counter.announce;
// set current history id
this.history_id = this.app.currentHistory();
// initiate upload procedure in plugin
this.uploadbox.start();
// update on screen info
this._updateScreen();
},
// pause upload process
_eventStop: function() {
// check
if (this.counter.running > 0) {
// show upload has paused
this.ui_button.set('status', 'info');
// set html content
$('#upload-info').html('Queue will pause after completing the current file...');
// request pause
this.uploadbox.stop();
}
},
// remove all
_eventReset: function() {
// make sure queue is not running
if (this.counter.running == 0){
// reset collection
this.collection.reset();
// reset counter
this.counter.reset();
// remove from queue
this.uploadbox.reset();
// reset value for universal type drop-down
this.select_extension.value(this.options.default_extension);
this.select_genome.value(this.options.default_genome);
// reset button
this.ui_button.set('percentage', 0);
// show on screen info
this._updateScreen();
}
},
// update extension for all models
updateExtension: function(extension, defaults_only) {
var self = this;
this.collection.each(function(model) {
if (model.get('status') == 'init' && (model.get('extension') == self.options.default_extension || !defaults_only)) {
model.set('extension', extension);
}
});
},
// update genome for all models
updateGenome: function(genome, defaults_only) {
var self = this;
this.collection.each(function(model) {
if (model.get('status') == 'init' && (model.get('genome') == self.options.default_genome || !defaults_only)) {
model.set('genome', genome);
}
});
},
// set screen
_updateScreen: function () {
/*
update on screen info
*/
// check default message
if(this.counter.announce == 0) {
if (this.uploadbox.compatible()) {
message = '&nbsp;';
} else {
message = 'Browser does not support Drag & Drop. Try Firefox 4+, Chrome 7+, IE 10+, Opera 12+ or Safari 6+.';
}
} else {
if (this.counter.running == 0) {
message = 'You added ' + this.counter.announce + ' file(s) to the queue. Add more files or click \'Start\' to proceed.';
} else {
message = 'Please wait...' + this.counter.announce + ' out of ' + this.counter.running + ' remaining.';
}
}
// set html content
this.$('#upload-info').html(message);
/*
update button status
*/
// update reset button
if (this.counter.running == 0 && this.counter.announce + this.counter.success + this.counter.error > 0) {
this.btnReset.enable();
} else {
this.btnReset.disable();
}
// update upload button
if (this.counter.running == 0 && this.counter.announce > 0) {
this.btnStart.enable();
this.btnStart.$el.addClass('btn-primary');
} else {
this.btnStart.disable();
this.btnStart.$el.removeClass('btn-primary');
}
// pause upload button
if (this.counter.running > 0) {
this.btnStop.enable();
} else {
this.btnStop.disable();
}
// select upload button
if (this.counter.running == 0) {
this.btnLocal.enable();
this.btnFtp.enable();
this.btnCreate.enable();
} else {
this.btnLocal.disable();
this.btnFtp.disable();
this.btnCreate.disable();
}
// ftp button
if (this.ftp_upload_site) {
this.btnFtp.$el.show();
} else {
this.btnFtp.$el.hide();
}
// table visibility
if (this.counter.announce + this.counter.success + this.counter.error > 0) {
this.$('#upload-table').show();
this.$('.upload-helper').hide();
} else {
this.$('#upload-table').hide();
this.$('.upload-helper').show();
}
},
// calculate percentage of all queued uploads
_uploadPercentage: function(percentage, size) {
return (this.upload_completed + (percentage * size)) / this.upload_size;
},
// template for extensions description
_templateDescription: function(options) {
if (options.description) {
var tmpl = options.description;
if (options.description_url) {
tmpl += '&nbsp;(<a href="' + options.description_url + '" target="_blank">read more</a>)';
}
return tmpl;
} else {
return 'There is no description available for this file extension.';
}
},
// load html template
_template: function() {
return '<div class="upload-view-default">' +
'<div class="upload-top">' +
'<h6 id="upload-info" class="upload-info"/>' +
'</div>' +
'<div id="upload-box" class="upload-box">' +
'<div class="upload-helper"><i class="fa fa-files-o"/>Drop files here</div>' +
'<table id="upload-table" class="ui-table-striped" style="display: none;">' +
'<thead>' +
'<tr>' +
'<th>Name</th>' +
'<th>Size</th>' +
'<th>Type</th>' +
'<th>Genome</th>' +
'<th>Settings</th>' +
'<th>Status</th>' +
'<th/>' +
'</tr>' +
'</thead>' +
'<tbody/>' +
'</table>' +
'</div>' +
'<div id="upload-footer" class="upload-footer">' +
'<span class="footer-title">Type (set all):</span>' +
'<span id="footer-extension"/>' +
'<span id="footer-extension-info" class="upload-icon-button fa fa-search"/> ' +
'<span class="footer-title">Genome (set all):</span>' +
'<span id="footer-genome"/>' +
'</div>' +
'<div id="upload-buttons" class="upload-buttons"/>' +
'</div>';
}
});
});
@@ -1,101 +1,95 @@
// dependencies
define([], function() {
// create model
// model for upload/progress bar button
var Model = Backbone.Model.extend({
defaults: {
icon : 'fa-upload',
tooltip : 'Download from URL or upload files from disk',
label : 'Load Data',
percentage : 0,
icon : 'fa-circle',
label : '',
status : ''
}
});
// progress bar button on ui
// view for upload/progress bar button
var View = Backbone.View.extend({
// model
model : null,
// initialize
initialize : function(model) {
initialize : function( options ) {
// link this
var self = this;
// create model
this.model = model;
// get options
this.options = this.model.attributes;
var model = options.model;
// create new element
this.setElement(this._template(this.options));
this.setElement( this._template() );
// add event
$(this.el).on('click', this.options.onclick);
this.$el.on( 'click', function( e ) { options.onclick( e ); });
// add tooltip
if (this.options.tooltip) {
$(this.el).tooltip({title: this.options.tooltip, placement: 'bottom'});
}
this.$el.tooltip( { title: model.get('tooltip'), placement: 'bottom' } );
// events
this.model.on('change:percentage', function() {
self._percentage(self.model.get('percentage'));
model.on( 'change:percentage', function() {
self._percentage( model.get( 'percentage' ) );
});
this.model.on('change:status', function() {
self._status(self.model.get('status'));
model.on( 'change:status', function() {
self._status( model.get( 'status' ) );
});
// unload event
var self = this;
$(window).on('beforeunload', function() {
$( window ).on( 'beforeunload', function() {
var text = "";
if (self.options.onunload) {
text = self.options.onunload();
if ( options.onunload ) {
text = options.onunload();
}
if (text != "") {
if ( text != "" ) {
return text;
}
});
},
// set status
_status: function(value) {
var $el = this.$el.find('.progress-bar');
_status: function( value ) {
var $el = this.$el.find( '.progress-bar' );
$el.removeClass();
$el.addClass('progress-bar');
$el.addClass('progress-bar-notransition');
if (value != '') {
$el.addClass('progress-bar-' + value);
$el.addClass( 'progress-bar' );
$el.addClass( 'progress-bar-notransition' );
if ( value != '' ) {
$el.addClass( 'progress-bar-' + value );
}
},
// set percentage
_percentage: function(value) {
var $el = this.$el.find('.progress-bar');
$el.css({ width : value + '%' });
_percentage: function( value ) {
var $el = this.$el.find( '.progress-bar' );
$el.css( { width : value + '%' } );
},
// template
_template: function(options) {
return '<div style="float: right">' +
'<div class="upload-button">' +
'<div class="progress">' +
'<div class="progress-bar"></div>' +
'</div>' +
'<div id="label" class="label">' +
'<a class="panel-header-button" href="javascript:void(0)">' +
'<span class="fa fa-upload"></span>' +
'</a>' +
'</div>' +
_template: function() {
return '<div class="upload-button">' +
'<div class="progress">' +
'<div class="progress-bar"></div>' +
'</div>' +
'<div id="label" class="label">' +
'<a class="panel-header-button" href="javascript:void(0)">' +
'<span class="fa fa-upload"></span>' +
'</a>' +
'</div>' +
'</div>';
}
});
return {
Model: Model,
View: View
Model : Model,
View : View
};
});
+75 -90
View File
@@ -1,23 +1,25 @@
// dependencies
/** This renders the content of the ftp popup **/
define(['utils/utils'], function(Utils) {
// item view
return Backbone.View.extend({
// options
options: {
class_add : 'upload-icon-button fa fa-square-o',
class_remove : 'upload-icon-button fa fa-check-square-o',
class_partial : 'upload-icon-button fa fa-minus-square-o'
},
// render
initialize: function(app) {
// link app
this.app = app;
initialize: function(options) {
// link options
this.options = Utils.merge(options, {
class_add : 'upload-icon-button fa fa-square-o',
class_remove : 'upload-icon-button fa fa-check-square-o',
class_partial : 'upload-icon-button fa fa-minus-square-o',
collection : null,
onchange : function() {},
onadd : function() {},
onremove : function() {}
});
// link this
var self = this;
// link app
this.collection = this.options.collection;
// set template
this.setElement(this._template());
@@ -34,7 +36,7 @@ return Backbone.View.extend({
// events
events: {
'mousedown' : function(e) { e.preventDefault(); }
'mousedown': function(e) { e.preventDefault(); }
},
// fill table
@@ -45,9 +47,9 @@ return Backbone.View.extend({
// add files to table
var size = 0;
for (key in ftp_files) {
this.rows.push(this._add(ftp_files[key]));
size += ftp_files[key].size;
for (index in ftp_files) {
this.rows.push(this._add(ftp_files[index]));
size += ftp_files[index].size;
}
// update stats
@@ -55,28 +57,26 @@ return Backbone.View.extend({
this.$el.find('#upload-ftp-disk').html(Utils.bytesToString (size, true));
// add event handler to select/unselect all
this.$select_all = $('#upload-selectall');
this.$select_all.addClass(this.options.class_add);
var self = this;
this.$select_all.on('click', function() {
var add = self.$select_all.hasClass(self.options.class_add);
for (key in ftp_files) {
var ftp_file = ftp_files[key];
var model_index = self._find(ftp_file);
if(!model_index && add || model_index && !add) {
self.rows[key].trigger('click');
if (this.collection) {
var self = this;
this.$('._has_collection').show();
this.$select_all = $('#upload-selectall');
this.$select_all.addClass(this.options.class_add);
this.$select_all.on('click', function() {
var add = self.$select_all.hasClass(self.options.class_add);
for (index in ftp_files) {
var ftp_file = ftp_files[index];
var model_index = self._find(ftp_file);
if(!model_index && add || model_index && !add) {
self.rows[index].trigger('click');
}
}
}
});
// refresh
self._refresh();
});
this._refresh();
}
} else {
// add info
this.$el.find('#upload-ftp-content').html($(this._templateInfo()));
}
// hide spinner
this.$el.find('#upload-ftp-wait').hide();
},
@@ -94,48 +94,36 @@ return Backbone.View.extend({
// append to table
$(this.el).find('tbody').append($it);
// find model and set initial 'add' icon class
var icon_class = '';
if (this._find(ftp_file)) {
icon_class = this.options.class_remove;
} else {
icon_class = this.options.class_add;
}
// add icon class
$icon.addClass(icon_class);
// click to add ftp files
$it.on('click', function() {
// find model
var model_index = self._find(ftp_file);
// update icon
$icon.removeClass();
// add model
if (!model_index) {
// add to uploadbox
self.app.uploadbox.add([{
mode : 'ftp',
name : ftp_file.path,
size : ftp_file.size,
path : ftp_file.path
}]);
// add new icon class
$icon.addClass(self.options.class_remove);
// collection mode with add/remove triggers
if (this.collection) {
// find model and set initial 'add' icon class
var icon_class = '';
if (this._find(ftp_file)) {
icon_class = this.options.class_remove;
} else {
// remove
self.app.collection.remove(model_index);
// add new icon class
$icon.addClass(self.options.class_add);
icon_class = this.options.class_add;
}
$icon.addClass(icon_class);
// update select all checkbox
self._refresh();
});
// click triggers add/remove events
$it.on('click', function() {
var model_index = self._find(ftp_file);
$icon.removeClass();
if (!model_index) {
self.options.onadd(ftp_file);
$icon.addClass(self.options.class_remove);
} else {
self.options.onremove(model_index);
$icon.addClass(self.options.class_add);
}
self._refresh();
});
} else {
// click triggers only change
$it.on('click', function() {
self.options.onchange(ftp_file);
});
}
// return dom handler
return $it;
@@ -143,7 +131,7 @@ return Backbone.View.extend({
// refresh
_refresh: function() {
var filtered = this.app.collection.where({file_mode : 'ftp'});
var filtered = this.collection.where({file_mode: 'ftp', enabled: true});
this.$select_all.removeClass();
if (filtered.length == 0) {
this.$select_all.addClass(this.options.class_add);
@@ -158,24 +146,21 @@ return Backbone.View.extend({
// get model index
_find: function(ftp_file) {
var item = this.app.collection.findWhere({
var item = this.collection.findWhere({
file_path : ftp_file.path,
status : 'init',
file_mode : 'ftp'
file_mode : 'ftp',
enabled : true
});
if (item) {
return item.get('id');
}
return null;
return item && item.get('id');
},
// template row
_templateRow: function(options) {
return '<tr class="upload-ftp-row">' +
'<td><div class="icon"/></td>' +
'<td class="label"><p>' + options.path + '</p></td>' +
'<td class="nonlabel">' + Utils.bytesToString(options.size) + '</td>' +
'<td class="nonlabel">' + options.ctime + '</td>' +
'<td class="_has_collection" style="display: none;"><div class="icon"/></td>' +
'<td class="ftp-name">' + options.path + '</td>' +
'<td class="ftp-size">' + Utils.bytesToString(options.size) + '</td>' +
'<td class="ftp-time">' + options.ctime + '</td>' +
'</tr>';
},
@@ -191,13 +176,13 @@ return Backbone.View.extend({
'<table class="grid" style="float: left;">' +
'<thead>' +
'<tr>' +
'<th><div id="upload-selectall"></th>' +
'<th class="_has_collection" style="display: none;"><div id="upload-selectall"></th>' +
'<th>Name</th>' +
'<th>Size</th>' +
'<th>Created</th>' +
'</tr>' +
'</thead>' +
'<tbody></tbody>' +
'<tbody/>' +
'</table>';
},
@@ -212,8 +197,8 @@ return Backbone.View.extend({
_template: function() {
return '<div class="upload-ftp">' +
'<div id="upload-ftp-wait" class="upload-ftp-wait fa fa-spinner fa-spin"/>' +
'<div class="upload-ftp-help">This Galaxy server allows you to upload files via FTP. To upload some files, log in to the FTP server at <strong>' + this.app.options.ftp_upload_site + '</strong> using your Galaxy credentials (email address and password).</div>' +
'<div id="upload-ftp-content"></div>' +
'<div class="upload-ftp-help">This Galaxy server allows you to upload files via FTP. To upload some files, log in to the FTP server at <strong>' + this.options.ftp_upload_site + '</strong> using your Galaxy credentials (email address and password).</div>' +
'<div id="upload-ftp-content"/>' +
'<div>';
}
});
@@ -9,13 +9,19 @@ var Model = Backbone.Model.extend({
url_paste : '',
status : 'init',
info : null,
file_mode : 'local',
file_name : '',
file_mode : '',
file_size : 0,
file_type : null,
file_path : '',
file_data : null,
percentage : 0,
space_to_tabs : false,
to_posix_lines : true
space_to_tab : false,
to_posix_lines : true,
enabled : true
},
reset: function(attr) {
this.clear().set(this.defaults).set(attr);
}
});
@@ -1,7 +1,5 @@
// dependencies
/** This renders the content of the settings popup, allowing users to specify flags i.e. for space-to-tab conversion **/
define(['utils/utils'], function(Utils) {
// item view
return Backbone.View.extend({
// options
options: {
@@ -9,7 +7,7 @@ return Backbone.View.extend({
class_uncheck : 'upload-icon-button fa fa-square-o'
},
// render
// initialize
initialize: function(app) {
// link app
this.app = app;
@@ -23,13 +21,13 @@ return Backbone.View.extend({
// link model
this.model = this.app.model;
// ui event: space to tabs
this.$el.find('#upload-space-to-tabs').on('click', function() {
self._switchState('#upload-space-to-tabs', 'space_to_tabs');
// ui event: space-to-tab
this.$('#upload-space-to-tab').on('click', function() {
self._switchState('#upload-space-to-tab', 'space_to_tab');
});
// ui event: to posix
this.$el.find('#upload-to-posix-lines').on('click', function() {
// ui event: to-posix-lines
this.$('#upload-to-posix-lines').on('click', function() {
self._switchState('#upload-to-posix-lines', 'to_posix_lines');
});
@@ -45,12 +43,12 @@ return Backbone.View.extend({
// render
render: function() {
// render states
this._renderState('#upload-space-to-tabs', this.model.get('space_to_tabs'));
this._renderState('#upload-space-to-tab', this.model.get('space_to_tab'));
this._renderState('#upload-to-posix-lines', this.model.get('to_posix_lines'));
// disable options
var $cover = this.$el.find('#upload-settings-cover');
if (this.model.get('status') != 'init') {
var $cover = this.$('#upload-settings-cover');
if (!this.model.get('enabled')) {
$cover.show();
} else {
$cover.hide();
@@ -59,7 +57,7 @@ return Backbone.View.extend({
// switch state
_switchState: function (element_id, parameter_id) {
if (this.model.get('status') == 'init') {
if (this.model.get('enabled')) {
var checked = !this.model.get(parameter_id);
this.model.set(parameter_id, checked);
this._renderState(element_id, checked);
@@ -68,8 +66,7 @@ return Backbone.View.extend({
// render state
_renderState: function (element_id, checked) {
// swith icon class
var $it = this.$el.find(element_id);
var $it = this.$(element_id);
$it.removeClass();
if (checked) {
$it.addClass(this.options.class_check);
@@ -82,10 +79,10 @@ return Backbone.View.extend({
_template: function() {
return '<div class="upload-settings" style="position: relative;">' +
'<div id="upload-settings-cover" class="upload-settings-cover"/>' +
'<table class="table table-striped">' +
'<table class="ui-table-striped">' +
'<tbody>' +
'<tr>' +
'<td><div id="upload-space-to-tabs"/></td>' +
'<td><div id="upload-space-to-tab"/></td>' +
'<td>Convert spaces to tabs</td>' +
'</tr>' +
'<tr>' +
+155 -662
View File
@@ -1,173 +1,118 @@
// dependencies
define(["utils/utils",
"mvc/upload/upload-button",
"mvc/upload/upload-model",
"mvc/upload/upload-row",
"mvc/upload/upload-ftp",
"mvc/ui/ui-popover",
"mvc/ui/ui-modal",
"mvc/ui/ui-select",
"utils/uploadbox"],
/** Upload app contains the upload progress button and upload modal, compiles model data for API request **/
define(['utils/utils',
'mvc/ui/ui-modal',
'mvc/ui/ui-tabs',
'mvc/upload/upload-button',
'mvc/upload/default/default-view',
'mvc/upload/composite/composite-view'],
function( Utils,
UploadButton,
UploadModel,
UploadItem,
UploadFtp,
Popover,
Modal,
Select
) {
// galaxy upload
Tabs,
UploadButton,
UploadViewDefault,
UploadViewComposite ) {
return Backbone.View.extend({
// options
// default options
options : {
nginx_upload_path : ''
},
// default for selection fields
default_extension : 'auto',
default_genome : '?',
// own modal
modal : null,
// button
ui_button : null,
// jquery uploadbox plugin
uploadbox: null,
// current history
current_history: null,
// extension selector
select_extension : null,
// genome selector
select_genome: null,
// current upload size
upload_size: 0,
// extension types
list_extensions :[],
// genomes
list_genomes : [],
// datatype placeholder for auto-detection
auto: {
id : 'auto',
text : 'Auto-detect',
description : 'This system will try to detect the file type automatically. If your file is not detected properly as one of the known formats, it most likely means that it has some format problems (e.g., different number of columns on different rows). You can still coerce the system to set your data to the format you think it should be. You can also upload compressed files, which will automatically be decompressed.'
},
// collection
collection : new UploadModel.Collection(),
// ftp file viewer
ftp : null,
// counter
counter : {
// stats
announce : 0,
success : 0,
error : 0,
running : 0,
// reset stats
reset : function() {
this.announce = this.success = this.error = this.running = 0;
nginx_upload_path : '',
ftp_upload_site : 'n/a',
default_genome : '?',
default_extension : 'auto',
height : 500,
width : 900,
auto : {
id : 'auto',
text : 'Auto-detect',
description : 'This system will try to detect the file type automatically. If your file is not detected properly as one of the known formats, it most likely means that it has some format problems (e.g., different number of columns on different rows). You can still coerce the system to set your data to the format you think it should be. You can also upload compressed files, which will automatically be decompressed.'
}
},
// upload modal container
modal: null,
// progress button in panel
ui_button: null,
// current history identifier
current_history: null,
// contains all available dataset extensions/types
list_extensions: [],
// contains all available genomes
list_genomes: [],
// initialize
initialize : function(options) {
initialize: function( options ) {
// link this
var self = this;
// read in options
if (options) {
this.options = _.defaults(options, this.options);
}
// merge parsed options
this.options = Utils.merge( options, this.options );
// create model
this.ui_button = new UploadButton.Model({
icon : 'fa-upload',
tooltip : 'Download from URL or upload files from disk',
label : 'Load Data',
// create model for upload/progress button
this.ui_button = new UploadButton.Model();
// create view for upload/progress button
this.ui_button_view = new UploadButton.View({
model : this.ui_button,
onclick : function(e) {
if (e) {
// prevent default
e.preventDefault();
// show
self.show()
}
e.preventDefault();
self.show()
},
onunload : function() {
if (self.counter.running > 0) {
return "Several uploads are still processing.";
var percentage = self.ui_button.get('percentage', 0);
if (percentage > 0 && percentage < 100) {
return 'Several uploads are queued.';
}
}
});
// define location
$('.with-upload-button').append((new UploadButton.View(this.ui_button)).$el);
// set element to button view
this.setElement( this.ui_button_view.$el );
// load extension
// load extensions
var self = this;
Utils.get({
url : galaxy_config.root + "api/datatypes?extension_only=False",
success : function(datatypes) {
for (key in datatypes) {
url : galaxy_config.root + 'api/datatypes?extension_only=False',
success : function( datatypes ) {
for ( key in datatypes ) {
self.list_extensions.push({
id : datatypes[key].extension,
text : datatypes[key].extension,
description : datatypes[key].description,
description_url : datatypes[key].description_url
id : datatypes[ key ].extension,
text : datatypes[ key ].extension,
description : datatypes[ key ].description,
description_url : datatypes[ key ].description_url,
composite_files : datatypes[ key ].composite_files
});
}
// sort
self.list_extensions.sort(function(a, b) {
return a.text > b.text ? 1 : a.text < b.text ? -1 : 0;
self.list_extensions.sort( function( a, b ) {
var a_text = a.text && a.text.toLowerCase();
var b_text = b.text && b.text.toLowerCase();
return a_text > b_text ? 1 : a_text < b_text ? -1 : 0;
});
// add auto field
if (!self.options.datatypes_disable_auto) {
self.list_extensions.unshift(self.auto);
if ( !self.options.datatypes_disable_auto ) {
self.list_extensions.unshift( self.options.auto );
}
}
});
// load genomes
Utils.get({
url : galaxy_config.root + "api/genomes",
success : function(genomes) {
for (key in genomes) {
url : galaxy_config.root + 'api/genomes',
success : function( genomes ) {
for ( key in genomes ) {
self.list_genomes.push({
id : genomes[key][1],
text : genomes[key][0]
id : genomes[ key ][ 1 ],
text : genomes[ key ][ 0 ]
});
}
// sort
self.list_genomes.sort(function(a, b) {
if (a.id == self.default_genome) { return -1; }
if (b.id == self.default_genome) { return 1; }
self.list_genomes.sort( function( a, b ) {
if ( a.id == self.options.default_genome ) { return -1; }
if ( b.id == self.options.default_genome ) { return 1; }
return a.text > b.text ? 1 : a.text < b.text ? -1 : 0;
});
}
});
// events
this.collection.on('remove', function(item) {
self._eventRemove(item);
});
},
//
@@ -178,558 +123,106 @@ return Backbone.View.extend({
show: function () {
// wait for galaxy history panel
var self = this;
if (!Galaxy.currHistoryPanel || !Galaxy.currHistoryPanel.model) {
if ( !Galaxy.currHistoryPanel || !Galaxy.currHistoryPanel.model ) {
window.setTimeout(function() { self.show() }, 500)
return;
}
// set current user
this.current_user = Galaxy.user.id;
// create modal
if (!this.modal) {
if ( !this.modal ) {
// build tabs
this.tabs = new Tabs.View();
// add tabs
this.default_view = new UploadViewDefault( this );
this.tabs.add({
id : 'regular',
title : 'Regular',
$el : this.default_view.$el
});
this.composite_view = new UploadViewComposite( this );
this.tabs.add({
id : 'composite',
title : 'Composite',
$el : this.composite_view.$el
});
// make modal
var self = this;
this.modal = new Modal.View({
title : 'Download data directly from web or upload files from your disk',
body : this._template('upload-box', 'upload-info'),
buttons : {
'Choose local file' : function() {self.uploadbox.select()},
'Choose FTP file' : function() {self._eventFtp()},
'Paste/Fetch data' : function() {self._eventCreate()},
'Start' : function() {self._eventStart()},
'Pause' : function() {self._eventStop()},
'Reset' : function() {self._eventReset()},
'Close' : function() {self.modal.hide()},
},
height : '400',
width : '900',
closing_events : true
});
// set element
this.setElement('#upload-box');
// file upload
var self = this;
this.uploadbox = this.$el.uploadbox({
announce : function(index, file, message) { self._eventAnnounce(index, file, message) },
initialize : function(index, file, message) { return self._eventInitialize(index, file, message) },
progress : function(index, file, message) { self._eventProgress(index, file, message) },
success : function(index, file, message) { self._eventSuccess(index, file, message) },
error : function(index, file, message) { self._eventError(index, file, message) },
complete : function() { self._eventComplete() }
});
// add ftp file viewer
var button = this.modal.getButton('Choose FTP file');
this.ftp = new Popover.View({
title : 'FTP files',
container : button
});
// select extension
this.select_extension = new Select.View({
css : 'header-selection',
data : this.list_extensions,
container : this.$el.parent().find('#header-extension'),
value : this.default_extension,
onchange : function(extension) {
self.updateExtension(extension);
}
});
// handle extension info popover
self.$el.parent().find('#header-extension-info').on('click' , function(e) {
self.showExtensionInfo({
$el : $(e.target),
title : self.select_extension.text(),
extension : self.select_extension.value(),
placement : 'top'
});
}).on('mousedown', function(e) { e.preventDefault(); });
// genome extension
this.select_genome = new Select.View({
css : 'header-selection',
data : this.list_genomes,
container : this.$el.parent().find('#header-genome'),
value : this.default_genome,
onchange : function(genome) {
self.updateGenome(genome);
}
title : 'Download from web or upload from disk',
body : this.tabs.$el,
height : this.options.height,
width : this.options.width,
closing_events : true,
title_separator : false
});
}
// show modal
this.modal.show();
// refresh
this._updateUser();
// setup info
this._updateScreen();
},
showExtensionInfo : function(options) {
// initialize
var self = this;
var $el = options.$el;
var extension = options.extension;
var title = options.title;
var description = _.findWhere(self.list_extensions, {'id': extension});
// create popup
this.extension_popup && this.extension_popup.remove();
this.extension_popup = new Popover.View({
placement: options.placement || 'bottom',
container: $el,
destroy: true
});
// add content and show popup
this.extension_popup.title(title);
this.extension_popup.empty();
this.extension_popup.append(this._templateDescription(description));
this.extension_popup.show();
// refresh user and current history
currentHistory: function() {
return this.current_user && Galaxy.currHistoryPanel.model.get( 'id' );
},
//
// events triggered by collection
//
// remove item from upload list
_eventRemove : function(item) {
// update status
var status = item.get('status');
// reduce counter
if (status == 'success') {
this.counter.success--;
} else if (status == 'error') {
this.counter.error--;
} else {
this.counter.announce--;
}
// show on screen info
this._updateScreen();
// remove from queue
this.uploadbox.remove(item.id);
// get ftp configuration
currentFtp: function() {
return this.current_user && this.options.ftp_upload_site;
},
//
// events triggered by the upload box plugin
//
// a new file has been dropped/selected through the uploadbox plugin
_eventAnnounce : function(index, file, message) {
// update counter
this.counter.announce++;
// update screen
this._updateScreen();
// create view/model
var upload_item = new UploadItem(this, {
id : index,
file_name : file.name,
file_size : file.size,
file_mode : file.mode,
file_path : file.path
});
// add to collection
this.collection.add(upload_item.model);
// add upload item element to table
$(this.el).find('tbody:first').append(upload_item.$el);
// render
upload_item.render();
},
// the uploadbox plugin is initializing the upload for this file
_eventInitialize : function(index, file, message) {
// get element
var it = this.collection.get(index);
// update status
it.set('status', 'running');
// get configuration
var file_name = it.get('file_name');
var file_path = it.get('file_path');
var file_mode = it.get('file_mode');
var extension = it.get('extension');
var genome = it.get('genome');
var url_paste = it.get('url_paste');
var space_to_tabs = it.get('space_to_tabs');
var to_posix_lines = it.get('to_posix_lines');
// validate
if (!url_paste && !(file.size > 0))
return null;
// configure uploadbox
this.uploadbox.configure({url : this.options.nginx_upload_path});
// local files
if (file_mode == 'local') {
this.uploadbox.configure({paramname : 'files_0|file_data'});
} else {
this.uploadbox.configure({paramname : null});
/**
* Package API data from array of models
* @param{Array} items - Upload items/rows filtered from a collection
*/
toData: function( items, history_id ) {
// create dictionary for data submission
var data = {
payload: {
'tool_id' : 'upload1',
'history_id' : history_id || this.currentHistory(),
'inputs' : {}
},
files: [],
error_message: null
}
// configure tool
tool_input = {};
// new files
if (file_mode == 'new') {
tool_input['files_0|url_paste'] = url_paste;
// add upload tools input data
if ( items && items.length > 0 ) {
var inputs = {};
inputs[ 'dbkey' ] = items[0].get( 'genome', null );
inputs[ 'file_type' ] = items[0].get( 'extension', null );
for ( var index in items ) {
var it = items[ index ];
it.set( 'status', 'running' );
if ( it.get( 'file_size' ) > 0 ) {
var prefix = 'files_' + index + '|';
inputs[ prefix + 'type' ] = 'upload_dataset';
inputs[ prefix + 'space_to_tab' ] = it.get( 'space_to_tab' ) && 'Yes' || null;
inputs[ prefix + 'to_posix_lines' ] = it.get( 'to_posix_lines' ) && 'Yes' || null;
switch ( it.get( 'file_mode' ) ) {
case 'new':
inputs[ prefix + 'url_paste' ] = it.get( 'url_paste' );
break;
case 'ftp':
inputs[ prefix + 'ftp_files' ] = it.get( 'file_path' );
break;
case 'local':
data.files.push( { name: prefix + 'file_data', file: it.get( 'file_data' ) } );
}
} else {
data.error_message = 'Upload content incomplete.';
it.set( 'status', 'error' );
it.set( 'info', data.error_message );
break;
}
}
data.payload.inputs = JSON.stringify( inputs );
}
// files from ftp
if (file_mode == 'ftp') {
tool_input['files_0|ftp_files'] = file_path;
}
// add common configuration
tool_input['dbkey'] = genome;
tool_input['file_type'] = extension;
tool_input['files_0|type'] = 'upload_dataset';
tool_input['files_0|space_to_tab'] = space_to_tabs && 'Yes' || null;
tool_input['files_0|to_posix_lines'] = to_posix_lines && 'Yes' || null;
// setup data
data = {};
data['history_id'] = this.current_history;
data['tool_id'] = 'upload1';
data['inputs'] = JSON.stringify(tool_input);
// return additional data to be send with file
return data;
},
// progress
_eventProgress : function(index, file, percentage) {
// set progress for row
var it = this.collection.get(index);
it.set('percentage', percentage);
// update ui button
this.ui_button.set('percentage', this._uploadPercentage(percentage, file.size));
},
// success
_eventSuccess : function(index, file, message) {
// update status
var it = this.collection.get(index);
it.set('percentage', 100);
it.set('status', 'success');
// file size
var file_size = it.get('file_size');
// update ui button
this.ui_button.set('percentage', this._uploadPercentage(100, file_size));
// update completed
this.upload_completed += file_size * 100;
// update counter
this.counter.announce--;
this.counter.success++;
// update on screen info
this._updateScreen();
// update galaxy history
Galaxy.currHistoryPanel.refreshContents();
},
// error
_eventError : function(index, file, message) {
// get element
var it = this.collection.get(index);
// update status
it.set('percentage', 100);
it.set('status', 'error');
it.set('info', message);
// update ui button
this.ui_button.set('percentage', this._uploadPercentage(100, file.size));
this.ui_button.set('status', 'danger');
// update completed
this.upload_completed += file.size * 100;
// update counter
this.counter.announce--;
this.counter.error++;
// update on screen info
this._updateScreen();
},
// queue is done
_eventComplete: function() {
// reset queued upload to initial status
this.collection.each(function(item) {
if(item.get('status') == 'queued') {
item.set('status', 'init');
}
});
// update running
this.counter.running = 0;
this._updateScreen();
},
//
// events triggered by this view
//
_eventFtp: function() {
// check if popover is visible
if (!this.ftp.visible) {
// show popover
this.ftp.empty();
this.ftp.append((new UploadFtp(this)).$el);
this.ftp.show();
} else {
// hide popover
this.ftp.hide();
}
},
// create a new file
_eventCreate : function (){
this.uploadbox.add([{
name : 'New File',
size : 0,
mode : 'new'
}]);
},
// start upload process
_eventStart : function() {
// check
if (this.counter.announce == 0 || this.counter.running > 0) {
return;
}
// reset current size
var self = this;
this.upload_size = 0;
this.upload_completed = 0;
// switch icons for new uploads
this.collection.each(function(item) {
if(item.get('status') == 'init') {
item.set('status', 'queued');
self.upload_size += item.get('file_size');
}
});
// reset progress
this.ui_button.set('percentage', 0);
this.ui_button.set('status', 'success');
// update running
this.counter.running = this.counter.announce;
this._updateScreen();
// initiate upload procedure in plugin
this.uploadbox.start();
},
// pause upload process
_eventStop : function() {
// check
if (this.counter.running == 0) {
return;
}
// show upload has paused
this.ui_button.set('status', 'info');
// request pause
this.uploadbox.stop();
// set html content
$('#upload-info').html('Queue will pause after completing the current file...');
},
// remove all
_eventReset : function() {
// make sure queue is not running
if (this.counter.running == 0){
// reset collection
this.collection.reset();
// reset counter
this.counter.reset();
// show on screen info
this._updateScreen();
// remove from queue
this.uploadbox.reset();
// reset value for universal type drop-down
this.select_extension.value(this.default_extension);
this.select_genome.value(this.default_genome);
// reset button
this.ui_button.set('percentage', 0);
}
},
// update extension for all models
updateExtension: function(extension, defaults_only) {
var self = this;
this.collection.each(function(item) {
if (item.get('status') == 'init' && (item.get('extension') == self.default_extension || !defaults_only)) {
item.set('extension', extension);
}
});
},
// update genome for all models
updateGenome: function(genome, defaults_only) {
var self = this;
this.collection.each(function(item) {
if (item.get('status') == 'init' && (item.get('genome') == self.default_genome || !defaults_only)) {
item.set('genome', genome);
}
});
},
// update uset
_updateUser: function() {
// backup current history
this.current_user = Galaxy.currUser.get('id');
this.current_history = null;
if (this.current_user) {
this.current_history = Galaxy.currHistoryPanel.model.get('id');
}
},
// set screen
_updateScreen: function () {
/*
update on screen info
*/
// check default message
if(this.counter.announce == 0){
if (this.uploadbox.compatible())
message = 'You can Drag & Drop files into this box.';
else
message = 'Unfortunately, your browser does not support multiple file uploads or drag&drop.<br>Some supported browsers are: Firefox 4+, Chrome 7+, IE 10+, Opera 12+ or Safari 6+.'
} else {
if (this.counter.running == 0)
message = 'You added ' + this.counter.announce + ' file(s) to the queue. Add more files or click \'Start\' to proceed.';
else
message = 'Please wait...' + this.counter.announce + ' out of ' + this.counter.running + ' remaining.';
}
// set html content
$('#upload-info').html(message);
/*
update button status
*/
// update reset button
if (this.counter.running == 0 && this.counter.announce + this.counter.success + this.counter.error > 0)
this.modal.enableButton('Reset');
else
this.modal.disableButton('Reset');
// update upload button
if (this.counter.running == 0 && this.counter.announce > 0)
this.modal.enableButton('Start');
else
this.modal.disableButton('Start');
// pause upload button
if (this.counter.running > 0)
this.modal.enableButton('Pause');
else
this.modal.disableButton('Pause');
// select upload button
if (this.counter.running == 0){
this.modal.enableButton('Choose local file');
this.modal.enableButton('Choose FTP file');
this.modal.enableButton('Paste/Fetch data');
} else {
this.modal.disableButton('Choose local file');
this.modal.disableButton('Choose FTP file');
this.modal.disableButton('Paste/Fetch data');
}
// ftp button
if (this.current_user && this.options.ftp_upload_dir && this.options.ftp_upload_site) {
this.modal.showButton('Choose FTP file');
} else {
this.modal.hideButton('Choose FTP file');
}
// table visibility
if (this.counter.announce + this.counter.success + this.counter.error > 0) {
$(this.el).find('#upload-table').show();
} else {
$(this.el).find('#upload-table').hide();
}
},
// calculate percentage of all queued uploads
_uploadPercentage: function(percentage, size) {
return (this.upload_completed + (percentage * size)) / this.upload_size;
},
// template for extensions description
_templateDescription: function(options) {
if (options.description) {
var tmpl = options.description;
if (options.description_url) {
tmpl += '&nbsp;(<a href="' + options.description_url + '" target="_blank">read more</a>)';
}
return tmpl;
} else {
return 'There is no description available for this file extension.';
}
},
// load html template
_template: function(id, idInfo) {
return '<div class="upload-top">' +
'<h6 id="' + idInfo + '" class="upload-info"></h6>' +
'</div>' +
'<div id="' + id + '" class="upload-box">' +
'<table id="upload-table" class="table table-striped" style="display: none;">' +
'<thead>' +
'<tr>' +
'<th>Name</th>' +
'<th>Size</th>' +
'<th>Type</th>' +
'<th>Genome</th>' +
'<th>Settings</th>' +
'<th>Status</th>' +
'<th></th>' +
'</tr>' +
'</thead>' +
'<tbody></tbody>' +
'</table>' +
'</div>' +
'<div id="upload-header" class="upload-header">' +
'<span class="header-title">Type (set all):</span>' +
'<span id="header-extension"/>' +
'<span id="header-extension-info" class="upload-icon-button fa fa-search"/> ' +
'<span class="header-title">Genome (set all):</span>' +
'<span id="header-genome"/>' +
'</div>';
}
});
@@ -454,7 +454,7 @@ define(['mvc/workflow/workflow-globals'], function( Globals ) {
}
});
var OutputCollectionTerminal = OutputTerminal.extend( {
var OutputCollectionTerminal = Terminal.extend( {
initialize: function( attr ) {
Terminal.prototype.initialize.call( this, attr );
this.datatypes = attr.datatypes;
@@ -0,0 +1,652 @@
function days_in_month(month, year) {
return new Date(year, month, 0).getDate();
}
function date_by_subtracting_days(date, days) {
return new Date(
date.getFullYear(),
date.getMonth(),
date.getDate() - days,
date.getHours(),
date.getMinutes(),
date.getSeconds(),
date.getMilliseconds()
);
}
function date_by_subtracting_hours(date, hours) {
return new Date(
date.getFullYear(),
date.getMonth(),
date.getDate(),
date.getHours() - hours,
date.getMinutes(),
date.getSeconds(),
date.getMilliseconds()
);
}
// Gets the utc time without minutes and seconds
function get_utc_time_hours() {
var date = new Date();
return new Date(
date.getUTCFullYear(),
date.getUTCMonth(),
date.getUTCDate(),
date.getUTCHours(),
0, 0
);
}
// Refreshes the page for more up to date information
function refresh() {
window.location.reload(true);
}
setTimeout(refresh, 60000); //1 minute = 60000 ms
function create_chart( inp_data, name, time, title ) {
// Initialize starting variables
var data = inp_data;
var hours_array = []
var now = get_utc_time_hours()
for(var i = 0; i < 24; i++) {
hours_array.push(date_by_subtracting_hours(now, i));
}
var days_array = []
var now = get_utc_time_hours()
for(var i = 0; i < 30; i++) {
days_array.push(date_by_subtracting_days(now, i));
}
var margin = {top: 60, right: 30, bottom: 50, left: 60};
var width = 300;
var barWidth = 0;
if(time == "hours") {
barWidth = width / 24;
} else if(time == "days") {
barWidth = width / 30;
}
var chart_width = width + margin.left + margin.right;
var chart_zoom = 1.75;
var height = 150;
if(d3.max(data) != 0) {
var zoom = height / d3.max(data);
} else {
var zoom = 1.0;
}
var chart_height = height + margin.top + margin.bottom;
// Function for zooming in and out of charts
function click() {
var classes = d3.select(this).attr("class");
classes = classes.split(" ");
d3.selectAll("." + classes[0]).filter("." + classes[1])
.style("cursor", "zoom-in")
.transition()
.duration(750)
.attr("height", chart_height)
.attr("width", chart_width);
d3.select(this)
.style("cursor", "default")
.transition()
.duration(750)
.attr("height", chart_height*chart_zoom)
.attr("width", chart_width*chart_zoom);
}
// Initialize all chart containers to have the correct height
$(".charts").css("height", chart_height * (chart_zoom));
// Create the chart object
var chart = d3.select("#" + name)
.attr("width", chart_width)
.attr("height", chart_height)
.attr("preserveAspectRatio", "xMidYMin")
.attr("viewBox", "0 0 " + chart_width + " " + chart_height)
.on("click", click);
// Create bars on the chart and assosciate data with it
var bar = chart.selectAll("g")
.data(data)
.enter().append("g")
.attr("transform", function(d, i) {
// Place the bar in the correct place
curr_margin = +margin.left;
curr_margin += +(i * barWidth);
return "translate(" + curr_margin + "," + margin.top + ")";
})
.on("mouseenter", function(d) {
// Show tool tip
var i = 1;
var size = d;
while( size >= 10) {
size = size / 10;
i++;
}
var wdth = (i * 4) + 10;
d3.select(d3.event.path[1]).select(".tool_tip")
.select("text")
.attr("transform", "translate( " + (margin.left - 5) + ", " + ((height - (d * zoom)) + +margin.top + 10) + " )" )
.attr("visibility", "visible")
.text(d);
d3.select(d3.event.path[1]).select(".tool_tip")
.attr("width", wdth + "px")
.attr("height", "15px")
.select("rect")
.attr("transform", "translate( " + ((+margin.left) - wdth) + ", " + ((height - (d * zoom)) + +margin.top) + " )" )
.attr("width", wdth + "px")
.attr("height", "15px")
.attr("fill","#ebd9b2");
})
.on("mouseleave", function(d) {
// Remove tool tip
d3.select(d3.event.path[1]).select(".tool_tip")
.select("text")
.attr("visibility", "hidden");
d3.select(d3.event.path[1]).select(".tool_tip")
.select("rect")
.attr("width", "0")
.attr("height", "0")
.attr("fill","")
.text(d);
});
// Add a title to the chart
chart.append("g")
.append("text")
.attr("class", "title")
.attr("text-anchor", "end")
.attr("transform", function(e) {
return "translate( " + width + ",15 )";
})
.text(title);
// Add an x axis line to the chart
chart.append("g")
.attr("class", "axis")
.append("path")
.attr("class", "x")
.attr("d", function(d) {
var m_x = margin.left;
var m_y = +margin.top + height;
var l_x = m_x + width;
var l_y = m_y;
return "M" + m_x + " " + m_y + " L " + l_x + " " + l_y;
});
// Declare how high the y axis goes
var y = d3.scale.linear()
.range([height, 0]);
// Create a yAxis object
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.tickFormat( function(d) { return d3.round( d*d3.max(data), 0 ) });
// Put the y axis on the chart
chart.append("g")
.attr("class", "y axis")
.attr("id", ("y_" + name))
.attr("text-anchor", "end")
.attr("transform", "translate( " + margin.left + "," + margin.top + ")")
.call(yAxis)
.select(".domain");
// Put a title for y axis on chart
chart.append("g")
.append("text")
.attr("class", "ax_title")
.attr("transform", function(e) {
var axis = d3.select("#y_" + name).node()
var left_pad = +margin.left - axis.getBoundingClientRect().width - 5;
var top_pad = +margin.top + (axis.getBoundingClientRect().height/2) - 30
var trans = "translate(" + left_pad + "," + top_pad + ")rotate(-90)";
return trans;
})
.text("Number of Jobs");
// Add color to the chart's bars
bar.append("rect")
.attr("y", function(d) { return height - (d * zoom); })
.attr("height", function(d) { return (d * zoom); })
.attr("width", barWidth - 1);
// Append x axis
if(time == "hours") {
// Append hour lines
bar.append("line")
.attr("x1", 0)
.attr("y1", 0)
.attr("x2", 0)
.attr("y2", 3)
.attr("stroke", "black")
.attr("stroke-width", 1)
.attr("pointer-events", "none")
.attr("transform", function(d, i) {
return "translate( " + (barWidth/2) + ", " + height + ")"
});
// Append hour numbers
bar.append("text")
.attr("fill", "rgb(0,0,0)")
.attr("transform", "translate( 10, " + (height + 10) + " )")
.text(function(d, i) {
var time = "0000"
if( hours_array[i].getHours() < 10 ) {
time = "0" + String(hours_array[i].getHours());
} else {
time = hours_array[i].getHours();
}
return time;
});
// Append day lines
var curr_day = "";
var first = false;
bar.append("line")
.attr("x1", 0)
.attr("y1", 0)
.attr("x2", 0)
.attr("y2", function(d, i) {
var _y2 = 0;
if(hours_array[i].getDate() != curr_day) {
if(!first) {
_y2 = 27;
first = true;
} else {
_y2 = 20;
}
curr_day = hours_array[i].getDate();
}
return _y2;
})
.attr("stroke", "black")
.attr("stroke-width", 1)
.attr("pointer-events", "none")
.attr("transform", function(d, i) {
return "translate( 0, " + height + ")";
});
// Append day numbers
curr_day = "";
curr_day_text = "";
first = false;
bar.append("text")
.attr("fill", "rgb(0,0,0)")
.attr("pointer-events", "none")
.text(function(d, i) {
var time = "";
var locale = "en-us";
if(hours_array[i].getDate() != curr_day_text) {
time = String(hours_array[i].toLocaleString(locale, { month: "long" }));
time += " " + String(hours_array[i].getDate())
curr_day_text = hours_array[i].getDate();
}
return time;
})
.attr("transform", function(d, i) {
var text_height = height;
var this_width = d3.select(this).node().getBBox().width;
if(hours_array[i].getDate() != curr_day) {
if(!first) {
text_height += 26;
first = true;
} else {
text_height += 18;
}
curr_day = hours_array[i].getDate();
}
return "translate( " + (this_width + 2) + ", " + (text_height) + " )"
});
} else if(time == "days") {
// Append day lines
bar.append("line")
.attr("x1", 0)
.attr("y1", 0)
.attr("x2", 0)
.attr("y2", 3)
.attr("stroke", "black")
.attr("stroke-width", 1)
.attr("pointer-events", "none")
.attr("transform", function(d, i) {
return "translate( " + (barWidth/2) + ", " + height + ")"
});
// Append day numbers
bar.append("text")
.attr("fill", "rgb(0,0,0)")
.attr("transform", "translate( 9, " + (height + 10) + " )")
.text(function(d, i) {
var time = "0000"
if( days_array[i].getDate() < 10 ) {
time = "0" + String(days_array[i].getDate());
} else {
time = days_array[i].getDate();
}
return time;
});
// Append month lines
var curr_month = "";
var first = false;
bar.append("line")
.attr("x1", 0)
.attr("y1", 0)
.attr("x2", 0)
.attr("y2", function(d, i) {
var _y2 = 0;
if(days_array[i].getMonth() != curr_month) {
if(!first) {
_y2 = 27;
first = true;
} else {
_y2 = 20;
}
curr_month = days_array[i].getMonth();
}
return _y2;
})
.attr("stroke", "black")
.attr("stroke-width", 1)
.attr("pointer-events", "none")
.attr("transform", function(d, i) {
return "translate( 0, " + height + ")";
});
// Append month numbers
curr_month = "";
curr_month_text = "";
first = false;
bar.append("text")
.attr("fill", "rgb(0,100,0)")
.attr("pointer-events", "none")
.text(function(d, i) {
var time = "";
var locale = "en-us";
if(days_array[i].getMonth() != curr_month_text) {
time = String(days_array[i].toLocaleString(locale, { month: "long" }));
time += " " + String(days_array[i].getFullYear())
curr_month_text = days_array[i].getMonth();
}
return time;
})
.attr("transform", function(d, i) {
var text_height = height;
var this_width = d3.select(this).node().getBBox().width;
if(days_array[i].getMonth() != curr_month) {
if(!first) {
text_height += 26;
first = true;
} else {
text_height += 18;
}
curr_month = days_array[i].getMonth();
}
return "translate( " + (this_width + 2) + ", " + (text_height) + " )"
});
}
// Put an invisible tool tip on the chart
chart.append("g")
.attr("class", "tool_tip")
.append("rect");
chart.select(".tool_tip")
.append("text");
// Initialize initial zoomed charts
if(name == "jc_dy_chart" || name == "jc_hr_chart") {
d3.select("#" + name)
.attr("height", chart_height * chart_zoom)
.attr("width", chart_width * chart_zoom)
.style("cursor", "default");
}
}
//============================================================================================================
function create_histogram( inp_data, name, title ) {
// Initialize initial variables
// inp_data is an array of numbers that are the amount of minutes per run
var data = inp_data;
var chart_zoom = 1.75;
var margin = {top: 60, right: 30, bottom: 50, left: 60};
var height = 150;
var chart_height = height + margin.top + margin.bottom;
var width = 300;
var chart_width = width + margin.left + margin.right;
// Cereate x axis metadata
// Used for x axis, histogram creation, and bar initialization
var x = d3.scale.linear()
.domain([0, d3.max(data)])
.range([0, width]);
// Generate a histogram using twenty uniformly-spaced bins.
var data = d3.layout.histogram()
.bins(x.ticks(20))(data);
// Create an array of the sizes of the bars
var lengths = [];
for(var i = 0; i < data.length; i ++) {
lengths.push(data[i].length)
}
// Find the amount needed to magnify the bars
if(d3.max(data) != 0) {
var zoom = height / d3.max(lengths);
} else {
var zoom = 1.0;
}
// Create y axis metadata
// Used for y axis and bar initialization
var y = d3.scale.linear()
.domain([0, d3.max(data, function(d) { return d.y; })])
.range([height, 0]);
// Function for zooming in and out of charts
function click() {
var classes = d3.select(this).attr("class");
classes = classes.split(" ");
d3.selectAll("." + classes[0]).filter("." + classes[1])
.style("cursor", "zoom-in")
.transition()
.duration(750)
.attr("height", chart_height)
.attr("width", chart_width);
d3.select(this)
.style("cursor", "default")
.transition()
.duration(750)
.attr("height", chart_height*chart_zoom)
.attr("width", chart_width*chart_zoom);
}
// Formatter for x axis times (converting minutes to HH:MM).
var formatMinutes = function(d) {
hours = Math.floor( d / 60 )
minutes = Math.floor(d - (hours * 60))
if(hours < 10) {
hours = "0" + hours
}
if(minutes < 10) {
minutes = "0" + minutes
}
return hours + ":" + minutes;
};
// Create a chart object
var chart = d3.select("#" + name)
.attr("viewBox", "0 0 " + chart_width + " " + chart_height)
.attr("width", chart_width)
.attr("height", chart_height)
.attr("preserveAspectRatio", "xMidYMin")
.on("click", click);
// Put title on chart
chart.append("g")
.append("text")
.attr("class", "title")
.attr("transform", function(e) {
return "translate( " + width + ",15 )";
})
.text(title);
// Put bars on chart
var bar = chart.selectAll(".bar")
.data(data)
.enter().append("g")
.attr("class", "bar")
.attr("transform", function(d) {
return "translate(" + (+x(d.x) + +margin.left) + "," + (+y(d.y) + +margin.top) + ")"; })
.on("mouseenter", function(d) {
// Show tool tip
i = 0;
size = d.length;
while( size >= 1) {
size = size / 10;
i++;
}
var wdth = (i * 4) + 10;
d3.select(d3.event.path[1]).select(".tool_tip")
.select("text")
.attr("transform", "translate( " + (margin.left - 5) + ", " + (height - (d.length * zoom) + +margin.top + 10) + " )" )
.attr("visibility", "visible")
.text(d.length);
d3.select(d3.event.path[1]).select(".tool_tip")
.attr("width", wdth + "px")
.attr("height", "15px")
.select("rect")
.attr("transform", "translate( " + ((+margin.left) - wdth) + ", " + (height - (d.length * zoom) + +margin.top) + " )")
.attr("width", wdth + "px")
.attr("height", "15px")
.attr("fill","#ebd9b2");
})
.on("mouseleave", function(d) {
// Remove tool tip
d3.select(d3.event.path[1]).select(".tool_tip")
.select("text")
.attr("visibility", "hidden");
d3.select(d3.event.path[1]).select(".tool_tip")
.select("rect")
.attr("width", "0")
.attr("height", "0")
.attr("fill","")
});
// Create bar width
var bar_x;
if(data[0] == undefined) {
bar_x = 1;
} else {
bar_x = x(data[0].dx);
}
// Add color to bar
bar.append("rect")
.attr("x", 1)
.attr("width", bar_x - 1)
.attr("height", function(d) { return height - y(d.y); });
// Create x axis
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom")
.tickFormat(formatMinutes);
// Add x axis to chart
chart.append("g")
.attr("class", "x axis")
.attr("id", "x_" + name)
.attr("transform", "translate( " + margin.left + "," + (+height + +margin.top) + ")")
.call(xAxis);
// Add a title to the x axis
chart.append("g")
.append("text")
.attr("class", "ax_title")
.attr("transform", function(e) {
var axis = d3.select("#x_" + name).node()
var left_pad = +margin.left + (axis.getBoundingClientRect().width/2) + 30;
var top_pad = +margin.top + height + axis.getBoundingClientRect().height + 10
var trans = "translate(" + left_pad + "," + top_pad + ")";
return trans;
})
.text("ETA - hrs:mins");
// Create y axis
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
// Add y axis to chart
chart.append("g")
.attr("class", "y axis")
.attr("id", ("y_" + name))
.attr("transform", "translate( " + margin.left + "," + margin.top + ")")
.call(yAxis);
// Add a title to the y axis
chart.append("g")
.append("text")
.attr("class", "ax_title")
.attr("transform", function(e) {
var axis = d3.select("#y_" + name).node()
var left_pad = +margin.left - axis.getBoundingClientRect().width - 5;
var top_pad = +margin.top + (axis.getBoundingClientRect().height/2) - 30
var trans = "translate(" + left_pad + "," + top_pad + ")rotate(-90)";
return trans;
})
.text("Number of Jobs");
// Put an invisible tool tip on the chart
chart.append("g")
.attr("class", "tool_tip")
.append("rect");
chart.select(".tool_tip")
.append("text");
}
@@ -1,237 +0,0 @@
#!/usr/bin/env python
# Script requires handlebars compiler be installed; use node package manager
# to install handlebars.
"""%prog [options] [ specific .handlebars or .html template file to compile ]
Compiles handlebars templates into javascript for better load times and performance.
Handlebars compilation errors will print to stderr.
NOTE!: you will need node.js, npm, and handlebars (installed from npm) in order to compile templates
See http://handlebarsjs.com/precompilation.html for more info.
Should be called from the $GLX_ROOT/static/scripts/templates directory.
Compiled templates will be written to the compiled/ directory.
If the --multi-ext option is passed a file extension string, the script will:
* search for *.<extension> in the current directory,
* extract each '<script type="text/template"> from the file and write them into a new handlebars file
named using the id of the script tag (eg. id="undeleteMsg" -> undeleteMsg.handlebars)
* these new handlebars scripts will be compiled along with any other handlebars templates in the dir
additionally for each of those files, the script will:
* extract each '<script type="text/javascript"> from the file and write them ALL into a single js file
named using the prefix 'helpers-' and the basename of the enclosing file
(eg. common-templates.html -> helpers-common-templates.js )
* these new handlebars scripts will be compiled along with any other handlebars templates in the dir
These files can be included as if they were templates (eg. h.templates( 'helpers-common-templates' ) )
This hopefully will allow both multiple templates and their associated Handlebars helper functions to be
included in one file.
Currently, this works (best?) if:
* the multiple templates are within one html file
* each template is enclosed in a <script> tag with type="text/template" and some id attribute
* each helper fn is enclosed in a <script> tag with type="text/javascript" and some id attribute
e.g. <script type="text/template" id="history-deletedMsg" class="template">Deleted {{ msg }}</script>
"""
# ------------------------------------------------------------------------------
TODO = """
remove intermediate, handlebars step of multi->handlebars->compiled/.js (if handlebars does stdin)
"""
import sys
from glob import glob
from subprocess import call
import os
from optparse import OptionParser
import re
import logging as log
log.basicConfig(
#level = log.DEBUG,
name = __name__
)
COMPILED_DIR = 'compiled'
COMPILED_EXT = '.js'
COMPILE_CMD_STR = "handlebars %s -f %s"
COMPILE_MINIMIZE_SWITCH = ' -m'
TEMPLATE_TAG = 'script'
TEMPLATE_TYPE = 'text/template'
HELPER_TYPE = 'text/javascript'
# both of these are off by default for backward compat
DEFAULT_MINIMIZATION = False
DEFAULT_MULTI_EXT = None #'.html'
# ------------------------------------------------------------------------------
def parse_html_tag_attrs( string ):
attrs = {}
for match in re.finditer( r'(?P<key>\w+?)=[\'|\"](?P<val>.*?)[\'|\"]', string, re.DOTALL | re.MULTILINE ):
match = match.groupdict()
key = match[ 'key' ]
val = match[ 'val' ]
attrs[ key ] = val
return attrs
def split_on_html_tag( string, tag ):
tag_pattern = r'<%s\s*(?P<attrs>.*?)>(?P<body>.*?)</%s>' % ( tag, tag )
log.debug( tag_pattern )
tag_pattern = re.compile( tag_pattern, re.MULTILINE | re.DOTALL )
found_list = re.findall( tag_pattern, string )
for attrs, body in found_list:
yield ( parse_html_tag_attrs( attrs ), body )
def filter_on_tag_type( generator, type_attr_to_match ):
for attrs, body in generator:
log.debug( 'attrs: %s', str( attrs ) )
if( ( 'type' in attrs )
and ( attrs[ 'type' ] == type_attr_to_match ) ):
yield attrs, body
# ------------------------------------------------------------------------------
def break_multi_template( multi_template_filename ):
"""parse the multi template, writing each template into a new handlebars tmpl and returning their names"""
template_filenames = []
# parse the multi template
print "\nBreaking multi-template file %s into individual templates and helpers:" % ( multi_template_filename )
with open( multi_template_filename, 'r' ) as multi_template_file:
multi_template_file_text = multi_template_file.read()
# write a template file for each template (name based on id in tag)
tag_generator = split_on_html_tag( multi_template_file_text, TEMPLATE_TAG )
for attrs, template_text in filter_on_tag_type( tag_generator, TEMPLATE_TYPE ):
if( 'id' not in attrs ):
log.warning( 'Template has no "id". attrs: %s' %( str( attrs ) ) )
continue
template_id = attrs[ 'id' ]
template_text = template_text.strip()
handlebar_template_filename = template_id + '.handlebars'
with open( handlebar_template_filename, 'w' ) as handlebar_template_file:
handlebar_template_file.write( template_text )
log.debug( "%s\n%s\n", template_id, template_text )
template_filenames.append( handlebar_template_filename )
## write all helpers to a single 'helper-' prefixed js file in the compilation dir
helper_fns = []
# same tag, different type
tag_generator = split_on_html_tag( multi_template_file_text, TEMPLATE_TAG )
for attrs, helper_text in filter_on_tag_type( tag_generator, HELPER_TYPE ):
helper_text = helper_text.strip()
print '(helper):', ( attrs[ 'id' ] if 'id' in attrs else '(No id)' )
helper_fns.append( helper_text )
if helper_fns:
# prefix original filename (in compiled dir) and write all helper funcs to that file
helper_filename = 'helpers-' + os.path.splitext( multi_template_filename )[0] + '.js'
helper_filename = os.path.join( COMPILED_DIR, helper_filename )
with open( helper_filename, 'w' ) as helper_file:
helper_file.write( '\n'.join( helper_fns ) )
print '(helper functions written to %s)' % helper_filename
print '\n'.join( template_filenames )
return template_filenames
# ------------------------------------------------------------------------------
def compile_template( template_filename, minimize=False ):
"""compile the given template file (optionally minimizing the js) using subprocess.
Use the basename of the template file for the outputed js.
"""
template_basename = os.path.splitext( os.path.split( template_filename )[1] )[0]
compiled_filename = os.path.join( COMPILED_DIR, template_basename + COMPILED_EXT )
command_string = COMPILE_CMD_STR % ( template_filename, compiled_filename )
if minimize:
command_string += COMPILE_MINIMIZE_SWITCH
print command_string
return call( command_string, shell=True )
# ------------------------------------------------------------------------------
def main( options, args ):
"""Break multi template files into single templates, compile all single templates.
If args, compile that as a list of specific handlebars and/or multi template files.
"""
print "(Call this script with the '-h' option for more help)"
handlebars_templates = []
# If specific scripts specified on command line, just compile them,
if len( args ) >= 1:
handlebars_templates = filter( lambda( x ): x.endswith( '.handlebars' ), args )
# otherwise compile all in the current dir
else:
handlebars_templates = glob( '*.handlebars' )
# if desired, break up any passed-in or found multi template files
# adding the names of the new single templates to those needing compilation
multi_template_template_filenames = []
if options.multi_ext:
multi_templates = []
if len( args ) >= 1:
multi_templates = filter( lambda( x ): x.endswith( options.multi_ext ), args )
else:
multi_templates = glob( '*' + options.multi_ext )
for multi_template_filename in multi_templates:
multi_template_template_filenames.extend( break_multi_template( multi_template_filename ) )
# unique filenames only (Q&D)
handlebars_templates = list( set( handlebars_templates + multi_template_template_filenames ) )
# compile the templates
print "\nCompiling templates:"
filenames_w_possible_errors = []
for handlebars_template in handlebars_templates:
shell_ret = compile_template( handlebars_template, options.minimize )
if shell_ret:
filenames_w_possible_errors.append( handlebars_template )
# report any possible errors
if filenames_w_possible_errors:
print "\nThere may have been errors on the following files:"
print ',\n'.join( filenames_w_possible_errors )
print "\nCall this script with the '-h' for more help"
# delete multi template intermediate files
if options.remove_intermediate:
print "\nCleaning up intermediate multi-template template files:"
for filename in multi_template_template_filenames:
try:
print 'removing', filename
os.remove( filename )
except Exception, exc:
print exc
# ------------------------------------------------------------------------------
if __name__ == '__main__':
optparser = OptionParser( usage=__doc__ )
optparser.add_option( '-m', '--minimize', dest='minimize', action='store_true', default=DEFAULT_MINIMIZATION,
help=( 'minimize compiled template scripts via handlebars '
+ '(defaults to %s)' % DEFAULT_MINIMIZATION ) )
optparser.add_option( '--multi-ext', dest='multi_ext', metavar="FILE_EXTENSION", default=DEFAULT_MULTI_EXT,
help=( 'indicates that files ending with the given string contain multiple '
+ 'templates and the script should break those into individual '
+ 'handlebars templates (defaults to "%s")' ) % DEFAULT_MULTI_EXT )
optparser.add_option( '--no-remove', action='store_false', dest='remove_intermediate', default=True,
help=( 'remove intermediate *.handlebars files when using multiple template'
+ 'files (defaults to "True")' ) )
( options, args ) = optparser.parse_args()
sys.exit( main( options, args ) )
@@ -1,6 +1,7 @@
(function() {
var template = Handlebars.template, templates = Handlebars.templates = Handlebars.templates || {};
templates['panel_section'] = template({"compiler":[6,">= 2.0.0-beta.1"],"main":function(depth0,helpers,partials,data) {
this["Handlebars"] = this["Handlebars"] || {};
this["Handlebars"]["templates"] = this["Handlebars"]["templates"] || {};
this["Handlebars"]["templates"]["panel_section"] = Handlebars.template({"compiler":[6,">= 2.0.0-beta.1"],"main":function(depth0,helpers,partials,data) {
var helper, alias1=helpers.helperMissing, alias2="function", alias3=this.escapeExpression;
return "<div class=\"toolSectionTitle\" id=\"title_"
@@ -10,5 +11,4 @@ templates['panel_section'] = template({"compiler":[6,">= 2.0.0-beta.1"],"main":f
+ "</span></a>\n</div>\n<div id=\""
+ alias3(((helper = (helper = helpers.id || (depth0 != null ? depth0.id : depth0)) != null ? helper : alias1),(typeof helper === alias2 ? helper.call(depth0,{"name":"id","hash":{},"data":data}) : helper)))
+ "\" class=\"toolSectionBody\" style=\"display: none; \">\n <div class=\"toolSectionBg\"></div>\n<div>";
},"useData":true});
})();
},"useData":true});
@@ -1,6 +1,7 @@
(function() {
var template = Handlebars.template, templates = Handlebars.templates = Handlebars.templates || {};
templates['tool_form'] = template({"1":function(depth0,helpers,partials,data) {
this["Handlebars"] = this["Handlebars"] || {};
this["Handlebars"]["templates"] = this["Handlebars"]["templates"] || {};
this["Handlebars"]["templates"]["tool_form"] = Handlebars.template({"1":function(depth0,helpers,partials,data) {
var stack1, helper, alias1=helpers.helperMissing, alias2="function", alias3=this.escapeExpression;
return " <div class=\"form-row\">\n <label for=\""
@@ -24,5 +25,4 @@ templates['tool_form'] = template({"1":function(depth0,helpers,partials,data) {
+ " </div>\n <div class=\"form-row form-actions\">\n <input type=\"submit\" class=\"btn btn-primary\" name=\"runtool_btn\" value=\"Execute\">\n</div>\n<div class=\"toolHelp\">\n <div class=\"toolHelpBody\">"
+ alias3(((helper = (helper = helpers.help || (depth0 != null ? depth0.help : depth0)) != null ? helper : alias1),(typeof helper === alias2 ? helper.call(depth0,{"name":"help","hash":{},"data":data}) : helper)))
+ "</div>\n</div>";
},"useData":true});
})();
},"useData":true});
@@ -1,6 +1,7 @@
(function() {
var template = Handlebars.template, templates = Handlebars.templates = Handlebars.templates || {};
templates['tool_link'] = template({"compiler":[6,">= 2.0.0-beta.1"],"main":function(depth0,helpers,partials,data) {
this["Handlebars"] = this["Handlebars"] || {};
this["Handlebars"]["templates"] = this["Handlebars"]["templates"] || {};
this["Handlebars"]["templates"]["tool_link"] = Handlebars.template({"compiler":[6,">= 2.0.0-beta.1"],"main":function(depth0,helpers,partials,data) {
var helper, alias1=helpers.helperMissing, alias2="function", alias3=this.escapeExpression;
return "<a class=\""
@@ -15,5 +16,4 @@ templates['tool_link'] = template({"compiler":[6,">= 2.0.0-beta.1"],"main":funct
+ alias3(((helper = (helper = helpers.name || (depth0 != null ? depth0.name : depth0)) != null ? helper : alias1),(typeof helper === alias2 ? helper.call(depth0,{"name":"name","hash":{},"data":data}) : helper)))
+ "</a> "
+ alias3(((helper = (helper = helpers.description || (depth0 != null ? depth0.description : depth0)) != null ? helper : alias1),(typeof helper === alias2 ? helper.call(depth0,{"name":"description","hash":{},"data":data}) : helper)));
},"useData":true});
})();
},"useData":true});
@@ -1,6 +1,7 @@
(function() {
var template = Handlebars.template, templates = Handlebars.templates = Handlebars.templates || {};
templates['tool_search'] = template({"compiler":[6,">= 2.0.0-beta.1"],"main":function(depth0,helpers,partials,data) {
this["Handlebars"] = this["Handlebars"] || {};
this["Handlebars"]["templates"] = this["Handlebars"]["templates"] || {};
this["Handlebars"]["templates"]["tool_search"] = Handlebars.template({"compiler":[6,">= 2.0.0-beta.1"],"main":function(depth0,helpers,partials,data) {
var helper, alias1=helpers.helperMissing, alias2="function", alias3=this.escapeExpression;
return "<input type=\"text\" name=\"query\" placeholder=\""
@@ -8,5 +9,4 @@ templates['tool_search'] = template({"compiler":[6,">= 2.0.0-beta.1"],"main":fun
+ "\" id=\"tool-search-query\" autocomplete=\"off\" class=\"search-query parent-width\" />\n<a id=\"search-clear-btn\" title=\"clear search (esc)\"> </a>\n<img src=\""
+ alias3(((helper = (helper = helpers.spinner_url || (depth0 != null ? depth0.spinner_url : depth0)) != null ? helper : alias1),(typeof helper === alias2 ? helper.call(depth0,{"name":"spinner_url","hash":{},"data":data}) : helper)))
+ "\" id=\"search-spinner\" class=\"search-spinner\"/>\n";
},"useData":true});
})();
},"useData":true});
+24 -24
View File
@@ -4,7 +4,7 @@ define(['libs/underscore', 'viz/trackster/util', 'utils/config'], function(_, ut
* A configuration setting. Currently key is used as id.
*/
var ConfigSetting = Backbone.Model.extend({
initialize: function(options) {
// Use key as id for now.
var key = this.get('key');
@@ -16,8 +16,8 @@ var ConfigSetting = Backbone.Model.extend({
this.set(_.extend({}, defaults, options));
}
if (this.get('value') === undefined) {
// Use default to set value.
if (this.get('value') === undefined && this.get('default_value') !== undefined) {
// Use default to set value (if present).
this.set_value(this.get('default_value'));
// If no default value for color config, set random color.
@@ -134,7 +134,7 @@ var ConfigSettingCollection = Backbone.Collection.extend({
return _.extend({}, m, { value: saved_values[m.key] });
});
}
return new ConfigSettingCollection(models);
}
});
@@ -150,7 +150,7 @@ var ConfigSettingCollectionView = Backbone.View.extend({
*/
render: function() {
var container = this.$el;
this.collection.each(function(param, index) {
// Hidden params have no representation in the form
if (param.get('hidden')) { return; }
@@ -165,7 +165,7 @@ var ConfigSettingCollectionView = Backbone.View.extend({
if ( type === 'bool' ) {
row.append( $('<input type="checkbox" />').attr("id", id ).attr("name", id ).attr( 'checked', value ) );
}
// Draw parameter as textbox
// Draw parameter as textbox
else if ( type === 'text' ) {
row.append( $('<input type="text"/>').attr("id", id ).val(value).click( function() { $(this).select(); }));
}
@@ -177,39 +177,39 @@ var ConfigSettingCollectionView = Backbone.View.extend({
});
select.val( value );
row.append( select );
}
// Draw parameter as color picker
// Draw parameter as color picker
else if ( type === 'color' ) {
var
var
container_div = $("<div/>").appendTo(row),
input = $('<input />').attr("id", id ).attr("name", id ).val( value ).css("float", "left")
input = $('<input />').attr("id", id ).attr("name", id ).val( value ).css("float", "left")
.appendTo(container_div).click(function(e) {
// Hide other pickers.
$(".tooltip").removeClass( "in" );
// Show input's color picker.
var tip = $(this).siblings(".tooltip").addClass( "in" );
tip.css( {
tip.css( {
// left: $(this).position().left + ( $(input).width() / 2 ) - 60,
// top: $(this).position().top + $(this.height)
// top: $(this).position().top + $(this.height)
left: $(this).position().left + $(this).width() + 5,
top: $(this).position().top - ( $(tip).height() / 2 ) + ( $(this).height() / 2 )
} ).show();
// Click management:
// Click management:
// Keep showing tip if clicking in tip.
tip.click(function(e) {
e.stopPropagation();
});
// Hide tip if clicking outside of tip.
$(document).bind( "click.color-picker", function() {
tip.hide();
$(document).unbind( "click.color-picker" );
});
// No propagation to avoid triggering document click (and tip hiding) above.
e.stopPropagation();
}),
@@ -222,20 +222,20 @@ var ConfigSettingCollectionView = Backbone.View.extend({
tip_inner = $("<div class='tooltip-inner' style='text-align: inherit'></div>").appendTo(tip),
tip_arrow = $("<div class='tooltip-arrow'></div>").appendTo(tip),
farb_obj = $.farbtastic(tip_inner, { width: 100, height: 100, callback: input, color: value });
// Clear floating.
container_div.append( $("<div/>").css("clear", "both"));
// Use function to fix farb_obj value.
(function(fixed_farb_obj) {
new_color_icon.click(function() {
fixed_farb_obj.setColor(util_mod.get_random_color());
});
});
})(farb_obj);
}
}
else {
row.append( $('<input />').attr("id", id ).attr("name", id ).val( value ) );
row.append( $('<input />').attr("id", id ).attr("name", id ).val( value ) );
}
// Help text
if ( param.help ) {
@@ -0,0 +1,21 @@
define([], function(){
// ============================================================================
function get( key, queryString ){
queryString = queryString || window.location.search.substr( 1 );
var keyRegex = new RegExp( key + '=([^&#$]+)' ),
matches = queryString.match( keyRegex );
if( !matches || !matches.length ){
return undefined;
}
matches = matches.splice( 1 );
if( matches.length === 1 ){
return matches[0];
}
return matches;
}
// ============================================================================
return {
get : get
};
});
+295 -364
View File
@@ -1,364 +1,295 @@
/*
galaxy upload lib - uses FileReader, FormData and XMLHttpRequest
*/
;(function($)
{
// add event properties
jQuery.event.props.push("dataTransfer");
// default options
var default_opts =
{
url : '',
paramname : 'content',
maxfilesize : 2048,
dragover : function() {},
dragleave : function() {},
announce : function() {},
initialize : function() {},
progress : function() {},
success : function() {},
error : function(index, file, message) { alert(message); },
complete : function() {},
error_filesize : "File exceeds 2GB. Please use an FTP client.",
error_default : "Please make sure the file is available.",
error_server : "Upload request failed.",
error_login : "Uploads require you to log in.",
error_missing : "No upload content available."
}
// options
var opts = {};
// file queue
var queue = {};
// queue index counter
var queue_index = 0;
// queue length
var queue_length = 0;
// indicates if queue is currently running
var queue_running = false;
var queue_stop = false;
// element
var el = null;
// xml request element
var xhr = null;
// attach to element
$.fn.uploadbox = function(options)
{
// parse options
opts = $.extend({}, default_opts, options);
// element
el = this;
// append upload button
el.append('<input id="uploadbox_input" type="file" style="display: none" multiple>');
// attach events
el.on('drop', drop);
el.on('dragover', dragover);
el.on('dragleave', dragleave);
// attach change event
$('#uploadbox_input').change(function(e)
{
// add files to queue
add(e.target.files);
// reset
$(this).val('');
});
// drop event
function drop(e)
{
// check if it's a file transfer
if(!e.dataTransfer)
return;
// add files to queue
add(e.dataTransfer.files);
// prevent default
e.preventDefault();
// return
return false;
}
// drag over
function dragover(e)
{
e.preventDefault();
opts.dragover.call(e);
}
// drag leave
function dragleave(e)
{
e.stopPropagation();
opts.dragleave.call(e);
}
// progress
function progress(e)
{
// get percentage
if (e.lengthComputable)
opts.progress(this.index, this.file, Math.round((e.loaded * 100) / e.total));
}
// respond to an upload request
function add(files)
{
// only allow adding file if current batch is complete
if (queue_running)
return;
// backup queue index
var current_index = queue_index;
// add files to queue
for (var i = 0; i < files.length; i++)
{
// new identifier
var index = String(queue_index++);
// add to queue
queue[index] = files[i];
// announce
opts.announce(index, queue[index], '');
// increase counter
queue_length++;
}
// return
return current_index;
}
// remove entry from queue
function remove(index)
{
if (queue[index])
{
// remove from queue
delete queue[index];
// update counter
queue_length--;
}
}
// process an upload, recursive
function process()
{
// validate
if (queue_length == 0 || queue_stop)
{
queue_stop = false;
queue_running = false;
opts.complete();
return;
} else
queue_running = true;
// get an identifier from the queue
var index = -1;
for (var key in queue)
{
index = key;
break;
}
// get current file from queue
var file = queue[index];
// remove from queue
remove(index)
// collect file details
var filesize = file.size;
var filemode = file.mode;
// identify maximum file size
var maxfilesize = 1048576 * opts.maxfilesize;
// check file size, unless it's an ftp file
if (filesize < maxfilesize || file.mode == 'ftp')
{
// get parameters
var data = opts.initialize(index, file);
// validate
if (data)
send(index, file, data);
else
error(index, file, opts.error_missing);
} else {
// skip file
error(index, file, opts.error_filesize);
}
}
// send file
function send (index, file, data)
{
// construct form data
var formData = new FormData();
for (var key in data) {
formData.append(key, data[key]);
}
// check file size
if (file.size > 0 && opts.paramname) {
formData.append(opts.paramname, file, file.name);
}
// prepare request
xhr = new XMLHttpRequest();
xhr.open('POST', opts.url, true);
xhr.setRequestHeader('Accept', 'application/json');
xhr.setRequestHeader('Cache-Control', 'no-cache');
xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
// captures state changes
xhr.onreadystatechange = function()
{
// check for request completed, server connection closed
if (xhr.readyState != xhr.DONE)
return;
// retrieve response
var response = null;
if (xhr.responseText)
{
try
{
response = jQuery.parseJSON(xhr.responseText);
} catch (e) {
response = xhr.responseText;
}
}
// pass any error to the error option
if (xhr.status < 200 || xhr.status > 299)
{
// format status
var text = xhr.statusText;
if (xhr.status == 403)
text = opts.error_login;
else if (xhr.status == 0)
text = opts.error_server;
else if (!text)
text = opts.error_default;
// request error
error(index, file, text + " (" + xhr.status + ")");
} else
// parse response
success(index, file, response);
}
// prepare upload progress
xhr.upload.index = index;
xhr.upload.file = file;
xhr.upload.addEventListener('progress', progress, false);
// send request
xhr.send(formData);
}
// success
function success (index, file, msg)
{
// parse message
opts.success(index, file, msg);
// restart process after success
process();
}
// error
function error (index, file, err)
{
// parse error
opts.error(index, file, err);
// restart process after error
process();
}
/*
public interface
*/
// open file browser for selection
function select()
{
$('#uploadbox_input').trigger('click');
}
// remove all entries from queue
function reset(index)
{
for (index in queue)
remove(index);
}
// initiate upload process
function start()
{
if (!queue_running)
{
queue_running = true;
process();
}
}
// stop upload process
function stop()
{
// request stop
queue_stop = true;
}
// set options
function configure(options)
{
// update current configuration
opts = $.extend({}, opts, options);
// return new configuration
return opts;
}
// verify browser compatibility
function compatible()
{
return window.File && window.FormData && window.XMLHttpRequest && window.FileList;
}
// export functions
return {
'select' : select,
'add' : add,
'remove' : remove,
'start' : start,
'stop' : stop,
'reset' : reset,
'configure' : configure,
'compatible' : compatible
};
}
})(jQuery);
/*
galaxy upload plugins - requires FormData and XMLHttpRequest
*/
;(function($){
// add event properties
jQuery.event.props.push("dataTransfer");
/**
Posts file data to the API
*/
$.uploadpost = function (config) {
// parse options
var cnf = $.extend({}, {
data : {},
success : function() {},
error : function() {},
progress : function() {},
url : null,
maxfilesize : 2048,
error_filesize : 'File exceeds 2GB. Please use an FTP client.',
error_default : 'Please make sure the file is available.',
error_server : 'Upload request failed.',
error_login : 'Uploads require you to log in.'
}, config);
// link data
var data = cnf.data;
// check errors
if (data.error_message) {
cnf.error(data.error_message);
return;
}
// construct form data
var form = new FormData();
for (var key in data.payload) {
form.append(key, data.payload[key]);
}
// add files to submission
var sizes = 0;
for (var key in data.files) {
var d = data.files[key];
form.append(d.name, d.file, d.file.name);
sizes += d.file.size;
}
// check file size, unless it's an ftp file
if (sizes > 1048576 * cnf.maxfilesize) {
cnf.error(cnf.error_filesize);
return;
}
// prepare request
xhr = new XMLHttpRequest();
xhr.open('POST', cnf.url, true);
xhr.setRequestHeader('Accept', 'application/json');
xhr.setRequestHeader('Cache-Control', 'no-cache');
xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
// captures state changes
xhr.onreadystatechange = function() {
// check for request completed, server connection closed
if (xhr.readyState == xhr.DONE) {
// parse response
var response = null;
if (xhr.responseText) {
try {
response = jQuery.parseJSON(xhr.responseText);
} catch (e) {
response = xhr.responseText;
}
}
// pass any error to the error option
if (xhr.status < 200 || xhr.status > 299) {
var text = xhr.statusText;
if (xhr.status == 403) {
text = cnf.error_login;
} else if (xhr.status == 0) {
text = cnf.error_server;
} else if (!text) {
text = cnf.error_default;
}
cnf.error(text + ' (' + xhr.status + ')');
} else {
cnf.success(response);
}
}
}
// prepare upload progress
xhr.upload.addEventListener('progress', function(e) {
if (e.lengthComputable) {
cnf.progress(Math.round((e.loaded * 100) / e.total));
}
}, false);
// send request
console.debug('uploadbox::uploadpost() - Posting following data:');
console.debug(cnf);
xhr.send(form);
}
/**
Handles the upload events drag/drop etc.
*/
$.fn.uploadinput = function(options) {
// initialize
var el = this;
var opts = $.extend({}, {
ondragover : function() {},
ondragleave : function() {},
onchange : function() {},
multiple : false
}, options);
// append hidden upload field
var $input = $('<input type="file" style="display: none" ' + (opts.multiple && 'multiple' || '') + '/>');
el.append($input).change(function (e) {
opts.onchange(e.target.files);
$(this).val('');
});
// drag/drop events
el.on('drop', function (e) {
opts.ondragleave(e);
if(e.dataTransfer) {
opts.onchange(e.dataTransfer.files);
e.preventDefault();
}
});
el.on('dragover', function (e) {
e.preventDefault();
opts.ondragover(e);
});
el.on('dragleave', function (e) {
e.stopPropagation();
opts.ondragleave(e);
});
// exports
return {
dialog: function () {
$input.trigger('click');
}
}
}
/**
Handles the upload queue and events such as drag/drop etc.
*/
$.fn.uploadbox = function(options) {
// parse options
var opts = $.extend({}, {
dragover : function() {},
dragleave : function() {},
announce : function(d) {},
initialize : function(d) {},
progress : function(d, m) {},
success : function(d, m) {},
error : function(d, m) { alert(m); },
complete : function() {}
}, options);
// file queue
var queue = {};
// queue index/length counter
var queue_index = 0;
var queue_length = 0;
// indicates if queue is currently running
var queue_running = false;
var queue_stop = false;
// element
var uploadinput = $(this).uploadinput({
multiple : true,
onchange : function(files) { add(files); },
ondragover : options.ondragover,
ondragleave : options.ondragleave
});
// add new files to upload queue
function add(files) {
if (files && files.length && !queue_running) {
var current_index = queue_index;
for (var i = 0; i < files.length; i++) {
var index = String(queue_index++);
queue[index] = files[i];
opts.announce(index, queue[index]);
queue_length++;
}
return current_index;
}
}
// remove file from queue
function remove(index) {
if (queue[index]) {
delete queue[index];
queue_length--;
}
}
// process an upload, recursive
function process() {
// validate
if (queue_length == 0 || queue_stop) {
queue_stop = false;
queue_running = false;
opts.complete();
return;
} else {
queue_running = true;
}
// get an identifier from the queue
var index = -1;
for (var key in queue) {
index = key;
break;
}
// get current file from queue
var file = queue[index];
// remove from queue
remove(index)
// create and submit data
$.uploadpost({
url : opts.url,
data : opts.initialize(index),
success : function(message) { opts.success(index, message); process();},
error : function(message) { opts.error(index, message); process();},
progress : function(percentage) { opts.progress(index, percentage); }
});
}
/*
public interface
*/
// open file browser for selection
function select() {
uploadinput.dialog();
}
// remove all entries from queue
function reset(index) {
for (index in queue) {
remove(index);
}
}
// initiate upload process
function start() {
if (!queue_running) {
queue_running = true;
process();
}
}
// stop upload process
function stop() {
queue_stop = true;
}
// set options
function configure(options) {
opts = $.extend({}, opts, options);
return opts;
}
// verify browser compatibility
function compatible() {
return window.File && window.FormData && window.XMLHttpRequest && window.FileList;
}
// export functions
return {
'select' : select,
'add' : add,
'remove' : remove,
'start' : start,
'stop' : stop,
'reset' : reset,
'configure' : configure,
'compatible' : compatible
};
}
})(jQuery);
+42 -37
View File
@@ -4,7 +4,7 @@
*/
// dependencies
define(["libs/underscore"], function(_) {
define([], function() {
/** Traverse through json
*/
@@ -116,7 +116,7 @@ function request (options) {
data : options.data || {},
url : options.url
}
// encode data into url
if (ajaxConfig.type == 'GET' || ajaxConfig.type == 'DELETE') {
if (ajaxConfig.url.indexOf('?') == -1) {
@@ -156,36 +156,27 @@ function request (options) {
});
};
/**
/**
* Read a property value from CSS
* @param{String} classname - CSS class
* @param{String} name - CSS property
*/
function cssGetAttribute (classname, name) {
// place dummy element
var el = $('<div class="' + classname + '"></div>');
// required append
el.appendTo(':eq(0)');
// get value
var value = el.css(name);
// remove element
el.remove();
// return css value
return value;
};
/**
* Load a CSS file
* @param{String} url - Url of CSS file
*/
function cssLoadFile (url) {
// check if css is already available
if (!$('link[href^="' + url + '"]').length)
if (!$('link[href^="' + url + '"]').length) {
$('<link href="' + galaxy_config.root + url + '" rel="stylesheet">').appendTo('head');
}
};
/**
@@ -194,29 +185,49 @@ function cssLoadFile (url) {
* @param{Object} optionsDefault - Source dictionary
*/
function merge (options, optionsDefault) {
if (options)
if (options) {
return _.defaults(options, optionsDefault);
else
} else {
return optionsDefault;
}
};
/**
* Round floaing point 'number' to 'numPlaces' number of decimal places.
* @param{Object} number a floaing point number
* @param{Object} numPlaces number of decimal places
*/
function roundToDecimalPlaces( number, numPlaces ){
var placesMultiplier = 1;
for( var i=0; i<numPlaces; i++ ){
placesMultiplier *= 10;
}
return Math.round( number * placesMultiplier ) / placesMultiplier;
}
// calculate on import
var kb = 1024,
mb = kb * kb,
gb = mb * kb,
tb = gb * kb;
/**
* Format byte size to string with units
* @param{Integer} size - Size in bytes
* @param{Boolean} normal_font - Switches font between normal and bold
*/
function bytesToString (size, normal_font) {
function bytesToString (size, normal_font, numberPlaces) {
numberPlaces = numberPlaces !== undefined? numberPlaces: 1;
// identify unit
var unit = "";
if (size >= 100000000000) { size = size / 100000000000; unit = 'TB'; } else
if (size >= 100000000) { size = size / 100000000; unit = 'GB'; } else
if (size >= 100000) { size = size / 100000; unit = 'MB'; } else
if (size >= 100) { size = size / 100; unit = 'KB'; } else
if (size > 0) { size = size * 10; unit = 'b'; } else
return '<strong>-</strong>';
if (size >= tb){ size = size / tb; unit = 'TB'; } else
if (size >= gb){ size = size / gb; unit = 'GB'; } else
if (size >= mb){ size = size / mb; unit = 'MB'; } else
if (size >= kb){ size = size / kb; unit = 'KB'; } else
if (size > 0){ unit = 'b'; }
else { return normal_font? '0 b': '<strong>-</strong>'; }
// return formatted string
var rounded = (Math.round(size) / 10);
var rounded = unit == 'b'? size: roundToDecimalPlaces( size, numberPlaces );
if (normal_font) {
return rounded + ' ' + unit;
} else {
@@ -236,27 +247,21 @@ function uid(){
* Create a time stamp
*/
function time() {
// get date object
var d = new Date();
// format items
var hours = (d.getHours() < 10 ? "0" : "") + d.getHours();
var minutes = (d.getMinutes() < 10 ? "0" : "") + d.getMinutes()
// format final stamp
var datetime = d.getDate() + "/"
return datetime = d.getDate() + "/"
+ (d.getMonth() + 1) + "/"
+ d.getFullYear() + ", "
+ hours + ":"
+ minutes;
return datetime;
};
return {
cssLoadFile : cssLoadFile,
cssGetAttribute : cssGetAttribute,
get : get,
merge : merge,
cssLoadFile: cssLoadFile,
cssGetAttribute: cssGetAttribute,
get: get,
merge: merge,
bytesToString: bytesToString,
uid: uid,
time: time,
+48 -48
View File
@@ -1,5 +1,5 @@
/**
* Visualization and components for Sweepster, a visualization for exploring a tool's parameter space via
* Visualization and components for Sweepster, a visualization for exploring a tool's parameter space via
* genomic visualization.
*/
@@ -7,7 +7,7 @@ define(["libs/underscore", "libs/d3", "viz/trackster/util", "viz/visualization",
function(_, d3, util, visualization, tracks, tools, data, config, mod_icon_btn) {
/**
* A collection of tool input settings. Object is useful for keeping a list of settings
* A collection of tool input settings. Object is useful for keeping a list of settings
* for future use without changing the input's value and for preserving inputs order.
*/
var ToolInputsSettings = Backbone.Model.extend({
@@ -16,7 +16,7 @@ var ToolInputsSettings = Backbone.Model.extend({
values: null
}
});
/**
* Tree for a tool's parameters.
*/
@@ -25,7 +25,7 @@ var ToolParameterTree = Backbone.Model.extend({
tool: null,
tree_data: null
},
initialize: function(options) {
// Set up tool parameters to work with tree.
var self = this;
@@ -109,7 +109,7 @@ var ToolParameterTree = Backbone.Model.extend({
};
});
}
// Recurse to handle other parameters.
return _.map(settings, function(setting) {
return {
@@ -141,7 +141,7 @@ var ToolParameterTree = Backbone.Model.extend({
get_num_leaves: function() {
return this.get_tree_params().reduce(function(memo, param) { return memo * param.get_samples().length; }, 1);
},
/**
* Returns array of ToolInputsSettings objects based on a node and its subtree.
*/
@@ -159,7 +159,7 @@ var ToolParameterTree = Backbone.Model.extend({
cur_node = cur_node.parent;
}
}
// Walk subtree starting at clicked node to get full list of settings.
var self = this,
get_settings = function(node, settings) {
@@ -168,7 +168,7 @@ var ToolParameterTree = Backbone.Model.extend({
if (node.param) {
settings[node.param.get('name')] = node.value;
}
if (!node.children) {
// At leaf node, so return settings.
return new ToolInputsSettings({
@@ -182,10 +182,10 @@ var ToolParameterTree = Backbone.Model.extend({
}
},
all_settings = get_settings(target_node, fixed_settings);
// If user clicked on leaf, settings is a single dict. Convert to array for simplicity.
if (!_.isArray(all_settings)) { all_settings = [ all_settings ]; }
return all_settings;
},
@@ -235,7 +235,7 @@ var ToolParameterTree = Backbone.Model.extend({
* Returns a list of parameters used in tree.
*/
toJSON: function() {
// FIXME: returning and jsonifying complete param causes trouble on the server side,
// FIXME: returning and jsonifying complete param causes trouble on the server side,
// so just use essential attributes for now.
return this.get_tree_params().map(function(param) {
return {
@@ -255,7 +255,7 @@ var SweepsterTrack = Backbone.Model.extend({
settings: null,
regions: null
},
initialize: function(options) {
this.set('regions', options.regions);
if (options.track) {
@@ -272,7 +272,7 @@ var SweepsterTrack = Backbone.Model.extend({
var this_settings = this.get('settings'),
other_settings = a_track.get('settings');
for (var prop in this_settings) {
if (!other_settings[prop] ||
if (!other_settings[prop] ||
this_settings[prop] !== other_settings[prop]) {
return false;
}
@@ -316,8 +316,8 @@ var SweepsterVisualization = visualization.Visualization.extend({
this.set('tool_with_samplable_inputs', tool_with_samplable_inputs);
// Remove complex parameters for now.
tool_with_samplable_inputs.remove_inputs( [ 'data', 'hidden_data', 'conditional', 'text' ] );
this.set('parameter_tree', new ToolParameterTree({
this.set('parameter_tree', new ToolParameterTree({
tool: tool_with_samplable_inputs,
config: options.tree_config
}));
@@ -445,9 +445,9 @@ var SweepsterTrackView = Backbone.View.extend({
var ToolInputValOrSweepView = Backbone.View.extend({
// Template for rendering sweep inputs:
number_input_template: '<div class="form-row-input sweep">' +
number_input_template: '<div class="form-row-input sweep">' +
'<input class="min" type="text" size="6" value="<%= min %>"> - ' +
'<input class="max" type="text" size="6" value="<%= max %>">' +
'<input class="max" type="text" size="6" value="<%= max %>">' +
' samples: <input class="num_samples" type="text" size="1" value="<%= num_samples %>">' +
'</div>',
@@ -471,16 +471,16 @@ var ToolInputValOrSweepView = Backbone.View.extend({
// Add row for parameter sweep inputs.
if (input instanceof tools.IntegerToolParameter) {
sweep_inputs_row = $(_.template(this.number_input_template, this.model.toJSON()));
sweep_inputs_row = $( _.template(this.number_input_template)(this.model.toJSON()) );
}
else if (input instanceof tools.SelectToolParameter) {
var options = _.map(this.$el.find('select option'), function(option) {
return $(option).val();
}),
options_text = options.join(', ');
sweep_inputs_row = $(_.template(this.select_input_template, {
sweep_inputs_row = $( _.template(this.select_input_template)({
options: options_text
}));
}) );
}
sweep_inputs_row.insertAfter(single_input_row);
@@ -497,7 +497,7 @@ var ToolInputValOrSweepView = Backbone.View.extend({
$(this).hide();
self.$el.find('.icon-button.toggle').show();
}
},
{
title: 'Remove parameter from tree',
@@ -511,9 +511,9 @@ var ToolInputValOrSweepView = Backbone.View.extend({
self.$el.find('.icon-button.plus-button').show();
}
}
],
],
{
});
this.$el.prepend(menu.$el);
@@ -568,12 +568,12 @@ var ToolParameterTreeDesignView = Backbone.View.extend({
*/
var ToolParameterTreeView = Backbone.View.extend({
className: 'tool-parameter-tree',
initialize: function(options) {
// When tree data changes, re-render.
this.model.on('change:tree_data', this.render, this);
},
render: function() {
// Start fresh.
this.$el.children().remove();
@@ -640,7 +640,7 @@ var ToolParameterTreeView = Backbone.View.extend({
.on('mouseout', function() {
node.style('fill', '#000');
});
node.append("circle")
.attr("r", 9);
@@ -658,13 +658,13 @@ var ToolParameterTreeView = Backbone.View.extend({
var SweepsterVisualizationView = Backbone.View.extend({
className: 'Sweepster',
helpText:
helpText:
'<div><h4>Getting Started</h4>' +
'<ol><li>Create a parameter tree by using the icons next to the tool\'s parameter names to add or remove parameters.' +
'<li>Adjust the tree by using parameter inputs to select min, max, and number of samples' +
'<li>Run the tool with different settings by clicking on tree nodes' +
'</ol></div>',
initialize: function(options) {
this.canvas_manager = new visualization.CanvasManager(this.$el.parents('body'));
this.tool_param_tree_view = new ToolParameterTreeView({ model: this.model.get('parameter_tree') });
@@ -680,7 +680,7 @@ var SweepsterVisualizationView = Backbone.View.extend({
});
// Set block, reverse strand block colors; these colors will be used for all tracks.
this.config = config.ConfigSettingCollection.from_models_and_saved_values(
this.config = config.ConfigSettingCollection.from_models_and_saved_values(
[
{ key: 'name', label: 'Name', type: 'text', default_value: '' },
{ key: 'a_color', label: 'A Color', type: 'color', default_value: "#FF0000" },
@@ -693,7 +693,7 @@ var SweepsterVisualizationView = Backbone.View.extend({
], {}
);
},
render: function() {
// Render tree design view in left panel.
var tree_design_view = new ToolParameterTreeDesignView({
@@ -749,7 +749,7 @@ var SweepsterVisualizationView = Backbone.View.extend({
[
// Save.
/*
{ icon_class: 'disk--arrow', title: 'Save', on_click: function() {
{ icon_class: 'disk--arrow', title: 'Save', on_click: function() {
// Show saving dialog box
show_modal("Saving...", "progress");
@@ -760,8 +760,8 @@ var SweepsterVisualizationView = Backbone.View.extend({
'has_changes': false
});
})
.error(function() {
show_modal( "Could Not Save", "Could not save visualization. Please try again later.",
.error(function() {
show_modal( "Could Not Save", "Could not save visualization. Please try again later.",
{ "Close" : hide_modal } );
});
} },
@@ -772,14 +772,14 @@ var SweepsterVisualizationView = Backbone.View.extend({
title: 'Set display mode'
},
// Close viz.
{
icon_class: 'cross-circle',
title: 'Close',
on_click: function() {
{
icon_class: 'cross-circle',
title: 'Close',
on_click: function() {
window.location = "${h.url_for( controller='visualization', action='list' )}";
}
}
}
],
],
{
tooltip_config: {placement: 'bottom'}
});
@@ -797,13 +797,13 @@ var SweepsterVisualizationView = Backbone.View.extend({
});
make_popupmenu(menu.$el.find('.chevron-expand'), mode_mapping);
menu.$el.attr("style", "float: right");
$("#right .unified-panel-header-inner").append(menu.$el);
},
get_base_color: function(base) {
return this.config.get_value(base.toLowerCase() + '_color') ||
return this.config.get_value(base.toLowerCase() + '_color') ||
this.config.get_value('n_color');
},
@@ -857,7 +857,7 @@ var SweepsterVisualizationView = Backbone.View.extend({
},
/**
* Sets up handling when tree nodes are clicked. When a node is clicked, the tool is run for each of
* Sets up handling when tree nodes are clicked. When a node is clicked, the tool is run for each of
* the settings defined by the node's subtree and tracks are added for each run.
*/
handle_node_clicks: function() {
@@ -875,11 +875,11 @@ var SweepsterVisualizationView = Backbone.View.extend({
// Do not allow 10+ jobs to be run.
if (all_settings.length >= 10) {
show_modal("Whoa there cowboy!",
show_modal("Whoa there cowboy!",
"You clicked on a node to try " + self.model.get('tool').get('name') +
" with " + all_settings.length +
" different combinations of settings. You can only run 10 jobs at a time.",
{
{
"Ok": function() { hide_modal(); run_jobs_deferred.resolve(false); }
});
}
@@ -901,7 +901,7 @@ var SweepsterVisualizationView = Backbone.View.extend({
self.add_track(pm_track);
return pm_track;
});
// For each track, run tool using track's settings and update track.
_.each(new_tracks, function(pm_track, index) {
setTimeout(function() {
@@ -915,7 +915,7 @@ var SweepsterVisualizationView = Backbone.View.extend({
track_config = dataset.get('track_config');
// Set dataset to be the tool's output.
track_config.dataset = dataset;
// Set tool to null so that it is not unpacked; unpacking it messes with
// Set tool to null so that it is not unpacked; unpacking it messes with
// the tool parameters and parameter tree.
track_config.tool = null;
@@ -924,11 +924,11 @@ var SweepsterVisualizationView = Backbone.View.extend({
// Create and add track for output dataset.
var track_obj = tracks.object_from_template(track_config, self, null);
track_obj.init_for_tool_data();
pm_track.set('track', track_obj);
});
}, index * 10000);
});
});
});
});
}
+79 -5
View File
@@ -31,8 +31,8 @@ require(
});
// trackster viewer
define( ["libs/underscore", "base", "viz/trackster/tracks", "viz/visualization", "mvc/ui/icon-button" ],
function(_, base, tracks, visualization, mod_icon_btn) {
define( ["libs/underscore", "base", "viz/trackster/tracks", "viz/visualization", "mvc/ui/icon-button", "utils/query-string-parsing" ],
function(_, base, tracks, visualization, mod_icon_btn, query_string ) {
/**
* User interface controls for trackster
*/
@@ -401,11 +401,84 @@ var TracksterView = Backbone.View.extend(
// hide right panel
force_right_panel("hide");
// check if id is available
if (galaxy_config.app.id)
if (galaxy_config.app.id){
this.view_existing();
else
} else if( query_string.get( 'dataset_id' ) ){
this.choose_existing_or_new();
} else {
this.view_new();
}
},
choose_existing_or_new : function () {
var self = this;
var dbkey = query_string.get( 'dbkey' ),
listTracksParams = {},
dataset_params = {
dbkey : dbkey,
dataset_id : query_string.get( 'dataset_id' ),
hda_ldda : query_string.get( 'hda_ldda' )
};
if (dbkey){
listTracksParams[ 'f-dbkey' ] = dbkey;
}
$.ajax({
url: galaxy_config.root + 'visualization/list_tracks?' + $.param( listTracksParams ),
dataType: "html",
error: function() { alert( "Could not add this dataset to browser." ); },
success: function(table_html) {
console.debug( 'success' );
function view_in_saved(){
// Show new modal with saved visualizations.
Galaxy.modal.show({
title : "Add Data to Saved Visualization",
body : table_html,
buttons : {
"Cancel": function() {
// go back to the first choice
show_choice();
},
"Add to visualization": function() {
$(parent.document).find('input[name=id]:checked').each(function() {
var vis_id = $(this).val();
dataset_params.id = vis_id;
window.location = galaxy_config.root + "visualization/trackster?" + $.param(dataset_params);
});
}
}
});
}
function show_choice(){
Galaxy.modal.show({
title : "View Data in a New or Saved Visualization?",
// either have text in here or have to remove body and the header/footer margins
body : (
// TODO: yay. inline styles. thanks, galaxy
'<p><ul style="list-style: disc inside none">You can add this dataset as:' +
"<li>a new track to one of your existing, saved Trackster " +
"sessions if they share the genome build: <b>" + dbkey + "</b></li>" +
"<li>or create a new session with this dataset as the only track</li>" +
"</ul></p>"
),
buttons : {
"Cancel": function() {
window.location = galaxy_config.root + "visualization/list";
},
"View in saved visualization": function() {
view_in_saved();
},
"View in new visualization": function() {
self.view_new();
}
}
});
}
show_choice();
}
});
},
// view
@@ -459,7 +532,8 @@ var TracksterView = Backbone.View.extend(
});
// select default
if (galaxy_config.app.default_dbkey) {
var dbkeys_in_genomes = response.map( function( r ){ return r[1]; });
if (galaxy_config.app.default_dbkey && _.contains(dbkeys_in_genomes,galaxy_config.app.default_dbkey)) {
$("#new-dbkey").val( galaxy_config.app.default_dbkey );
}
+88 -92
View File
@@ -8,7 +8,7 @@ define( ["libs/underscore"], function( _ ) {
*/
var BEFORE = 1001, CONTAINS = 1002, OVERLAP_START = 1003, OVERLAP_END = 1004, CONTAINED_BY = 1005, AFTER = 1006;
var compute_overlap = function(first_region, second_region) {
var
var
first_start = first_region[0], first_end = first_region[1],
second_start = second_region[0], second_end = second_region[1],
overlap;
@@ -34,7 +34,7 @@ var compute_overlap = function(first_region, second_region) {
overlap = OVERLAP_END;
}
}
return overlap;
};
@@ -60,7 +60,7 @@ var dashedLine = function(ctx, x1, y1, x2, y2, dashLen) {
var dashX = dX / dashes;
var dashY = dY / dashes;
var q;
for (q = 0; q < dashes; q++, x1 += dashX, y1 += dashY) {
if (q % 2 !== 0) {
continue;
@@ -74,11 +74,11 @@ var dashedLine = function(ctx, x1, y1, x2, y2, dashLen) {
*/
var drawDownwardEquilateralTriangle = function(ctx, down_vertex_x, down_vertex_y, side_len) {
// Compute other two points of triangle.
var
var
x1 = down_vertex_x - side_len/2,
x2 = down_vertex_x + side_len/2,
y = down_vertex_y - Math.sqrt( side_len*3/2 );
// Draw and fill.
ctx.beginPath();
ctx.moveTo(x1, y);
@@ -198,16 +198,16 @@ LinePainter.prototype.draw = function(ctx, width, height, w_scale) {
ctx.fillStyle = "#aaa";
ctx.fillRect( 0, y_zero, width, 1 );
}
ctx.beginPath();
var x_scaled, y, delta_x_px;
if (data.length > 1) {
delta_x_px = Math.ceil((data[1][0] - data[0][0]) * w_scale);
}
}
else {
delta_x_px = 10;
}
// Painter color can be in either block_color (FeatureTrack) or color pref (LineTrack).
var painter_color = this.prefs.block_color || this.prefs.color,
// Extract RGB from preference color.
@@ -215,10 +215,10 @@ LinePainter.prototype.draw = function(ctx, width, height, w_scale) {
pref_r = (pref_color & 0xff0000) >> 16,
pref_g = (pref_color & 0x00ff00) >> 8,
pref_b = pref_color & 0x0000ff,
top_overflow = false,
top_overflow = false,
bot_overflow = false;
// Paint track.
for (var i = 0, len = data.length; i < len; i++) {
// Reset attributes for next point.
@@ -241,20 +241,20 @@ LinePainter.prototype.draw = function(ctx, width, height, w_scale) {
if (y < min_value) {
bot_overflow = true;
y = min_value;
}
}
else if (y > max_value) {
top_overflow = true;
y = max_value;
}
// Draw point.
if (mode === "Histogram") {
// y becomes the bar height in pixels, which is the negated for canvas coords
y = Math.round( y / vertical_range * height_px );
ctx.fillRect(x_scaled, y_zero, delta_x_px, - y );
}
}
else if (mode === "Intensity") {
var
var
saturation = (y - min_value) / vertical_range,
// Range is [pref_color, 255] where saturation = 0 --> 255 and saturation = 1 --> pref color
new_r = Math.round( pref_r + (255 - pref_r) * (1 - saturation) ),
@@ -262,23 +262,23 @@ LinePainter.prototype.draw = function(ctx, width, height, w_scale) {
new_b = Math.round( pref_b + (255 - pref_b) * (1 - saturation) );
ctx.fillStyle = "rgb(" + new_r + "," + new_g + "," + new_b + ")";
ctx.fillRect(x_scaled, 0, delta_x_px, height_px);
}
}
else { // mode is Coverage/Line or Filled.
// Scale Y value.
y = Math.round( height_px - (y - min_value) / vertical_range * height_px );
if (in_path) {
ctx.lineTo(x_scaled, y);
}
}
else {
in_path = true;
if (mode === "Filled") {
ctx.moveTo(x_scaled, height_px);
ctx.lineTo(x_scaled, y);
}
}
else {
ctx.moveTo(x_scaled, y);
// Use this approach (note: same as for filled) to draw line from 0 to
// Use this approach (note: same as for filled) to draw line from 0 to
// first data point.
//ctx.moveTo(x_scaled, height_px);
//ctx.lineTo(x_scaled, y);
@@ -292,7 +292,7 @@ LinePainter.prototype.draw = function(ctx, width, height, w_scale) {
var overflow_x;
if (mode === "Histogram" || mode === "Intensity") {
overflow_x = delta_x_px;
}
}
else { // Line and Filled, which are points
x_scaled -= 2; // Move it over to the left so it's centered on the point
overflow_x = 4;
@@ -312,11 +312,11 @@ LinePainter.prototype.draw = function(ctx, width, height, w_scale) {
ctx.lineTo( 0, y_zero );
}
ctx.fill();
}
}
else {
ctx.stroke();
}
ctx.restore();
};
@@ -356,7 +356,7 @@ FeaturePositionMapper.prototype.get_feature_data = function(x, y) {
if (!this.feature_positions[slot]) {
return null;
}
// Find feature using X.
x += this.translation;
for (var i = 0; i < this.feature_positions[slot].length; i++) {
@@ -404,8 +404,8 @@ _.extend(FeaturePainter.prototype, {
* a FeaturePositionMapper object with information about where features were drawn.
*/
draw: function(ctx, width, height, w_scale, slots) {
var data = this.data,
view_start = this.view_start,
var data = this.data,
view_start = this.view_start,
view_end = this.view_end;
ctx.save();
@@ -423,11 +423,11 @@ _.extend(FeaturePainter.prototype, {
feature_uid = feature[0],
feature_start = feature[1],
feature_end = feature[2],
// Slot valid only if features are slotted and this feature is slotted;
// Slot valid only if features are slotted and this feature is slotted;
// feature may not be due to lack of space.
slot = (slots && slots[feature_uid] !== undefined ? slots[feature_uid].slot : null);
// Draw feature if (a) mode is dense or feature is slotted (as it must be for all non-dense modes) and
// Draw feature if (a) mode is dense or feature is slotted (as it must be for all non-dense modes) and
// (b) there's overlap between the feature and drawing region.
if ( (this.mode === "Dense" || slot !== null) && ( feature_start < view_end && feature_end > view_start ) ) {
x_draw_coords = this.draw_element(ctx, this.mode, feature, slot, view_start, view_end, w_scale, y_scale, width);
@@ -449,7 +449,7 @@ _.extend(FeaturePainter.prototype, {
});
},
/**
/**
* Abstract function for drawing an individual feature.
*/
draw_element: function(ctx, mode, feature, slot, tile_low, tile_high, w_scale, y_scale, width ) {
@@ -486,7 +486,7 @@ _.extend(LinkedFeaturePainter.prototype, FeaturePainter.prototype, {
get_row_height: function() {
var mode = this.mode, height;
if (mode === "Dense") {
height = DENSE_TRACK_HEIGHT;
height = DENSE_TRACK_HEIGHT;
}
else if (mode === "no_detail") {
height = NO_DETAIL_TRACK_HEIGHT;
@@ -520,20 +520,20 @@ _.extend(LinkedFeaturePainter.prototype, FeaturePainter.prototype, {
// For now, assume '.' === '+'
block_color = (!feature_strand || feature_strand === "+" || feature_strand === "." ? this.prefs.block_color : this.prefs.reverse_strand_color);
label_color = this.prefs.label_color;
// Set global alpha.
ctx.globalAlpha = this.alpha_scaler.gen_val(feature);
// In dense mode, put all data in top slot.
if (mode === "Dense") {
slot = 1;
}
if (mode === "no_detail") {
// No details for feature, so only one way to display.
ctx.fillStyle = block_color;
ctx.fillRect(f_start, y_start + 5, f_end - f_start, NO_DETAIL_FEATURE_HEIGHT);
}
}
else { // Mode is either Squish or Pack:
// Feature details.
var feature_ts = feature[5],
@@ -541,12 +541,12 @@ _.extend(LinkedFeaturePainter.prototype, FeaturePainter.prototype, {
feature_blocks = feature[7],
// Whether we are drawing full height or squished features
full_height = true;
if (feature_ts && feature_te) {
thick_start = Math.floor( Math.max(0, (feature_ts - tile_low) * w_scale) );
thick_end = Math.ceil( Math.min(width, Math.max(0, (feature_te - tile_low) * w_scale)) );
}
// Set vars that depend on mode.
var thin_height, thick_height;
if (mode === "Squish" ) {
@@ -560,7 +560,7 @@ _.extend(LinkedFeaturePainter.prototype, FeaturePainter.prototype, {
thin_height = 5;
thick_height = PACK_FEATURE_HEIGHT;
}
// Draw feature/feature blocks + connectors.
if (!feature_blocks) {
// If there are no blocks, treat the feature as one big exon.
@@ -575,15 +575,15 @@ _.extend(LinkedFeaturePainter.prototype, FeaturePainter.prototype, {
}
ctx.fillRect(f_start, y_start + 1, f_end - f_start, thick_height);
}
} else {
} else {
//
// There are feature blocks and mode is either Squish or Pack.
//
// Approach: (a) draw whole feature as connector/intron and (b) draw blocks as
// Approach: (a) draw whole feature as connector/intron and (b) draw blocks as
// needed. This ensures that whole feature, regardless of whether it starts with
// a block, is visible.
//
// Compute y axis start position and height
var cur_y_start, cur_height;
if (mode === "Squish" || mode === "Dense") {
@@ -620,7 +620,7 @@ _.extend(LinkedFeaturePainter.prototype, FeaturePainter.prototype, {
}
ctx.fillRect(f_start, cur_y_start, f_end - f_start, cur_height);
}
// Draw blocks.
var start_and_height;
for (var k = 0, k_len = feature_blocks.length; k < k_len; k++) {
@@ -630,7 +630,7 @@ _.extend(LinkedFeaturePainter.prototype, FeaturePainter.prototype, {
block_end = Math.ceil( Math.min(width, Math.max((block[1] - tile_low - 0.5) * w_scale)) ),
last_block_start, last_block_end;
// Skip drawing if block not on tile.
// Skip drawing if block not on tile.
if (block_start > block_end) { continue; }
// Draw thin block.
@@ -666,15 +666,15 @@ _.extend(LinkedFeaturePainter.prototype, FeaturePainter.prototype, {
last_block_start = block_start;
last_block_end = block_end;
}
// FIXME: Height scaling only works in Pack mode right now.
if (mode === "Pack") {
// Reset alpha so height scaling is not impacted by alpha scaling.
ctx.globalAlpha = 1;
// Height scaling: draw white lines to reduce height according to height scale factor.
ctx.fillStyle = "white"; // TODO: set this to background color.
var
var
hscale_factor = this.height_scaler.gen_val(feature),
// Ceil ensures that min height is >= 1.
new_height = Math.ceil(thick_height * hscale_factor),
@@ -682,13 +682,13 @@ _.extend(LinkedFeaturePainter.prototype, FeaturePainter.prototype, {
if (hscale_factor !== 1) {
ctx.fillRect(f_start, cur_y_start + 1, f_end - f_start, ws_height);
ctx.fillRect(f_start, cur_y_start + thick_height - ws_height + 1, f_end - f_start, ws_height);
}
}
}
}
}
// Reset alpha so that label is not transparent.
ctx.globalAlpha = 1;
// Draw label for Pack mode.
if (feature_name && mode === "Pack" && feature_start > tile_low) {
ctx.fillStyle = label_color;
@@ -705,10 +705,10 @@ _.extend(LinkedFeaturePainter.prototype, FeaturePainter.prototype, {
//ctx.fillStyle = block_color;
}
}
// Reset global alpha.
ctx.globalAlpha = 1;
return [draw_start, draw_end];
}
});
@@ -726,7 +726,7 @@ _.extend(ReadPainter.prototype, FeaturePainter.prototype, {
get_row_height: function() {
var height, mode = this.mode;
if (mode === "Dense") {
height = DENSE_TRACK_HEIGHT;
height = DENSE_TRACK_HEIGHT;
}
else if (mode === "Squish") {
height = SQUISH_TRACK_HEIGHT;
@@ -821,7 +821,7 @@ _.extend(ReadPainter.prototype, FeaturePainter.prototype, {
drawing_blocks = [],
s_start,
s_end;
// Keep list of items that need to be drawn on top of initial drawing layer.
var draw_last = [];
@@ -837,7 +837,7 @@ _.extend(ReadPainter.prototype, FeaturePainter.prototype, {
if (is_overlap([feature_start + block[0], feature_start + block[1]], tile_region)) {
s_start = get_draw_coord(feature_start + block[0]);
s_end = get_draw_coord(feature_start + block[1]);
// Make sure that block is drawn even if it too small to be rendered officially; in this case,
// read is drawn at 1px.
// TODO: need to ensure that s_start, s_end are calculated the same for both slotting
@@ -861,7 +861,7 @@ _.extend(ReadPainter.prototype, FeaturePainter.prototype, {
var seq_start = feature_start + base_offset;
s_start = get_draw_coord(seq_start);
s_end = get_draw_coord(seq_start + cig_len);
// Skip feature if it's not in tile.
if (!is_overlap([seq_start, seq_start + cig_len], tile_region)) {
// Update offsets.
@@ -869,7 +869,7 @@ _.extend(ReadPainter.prototype, FeaturePainter.prototype, {
seq_offset = update_seq_offset(seq_offset, cig_op, cig_len);
continue;
}
// Make sure that read is drawn even if it too small to be rendered officially; in this case,
// read is drawn at 1px.
// TODO: need to ensure that s_start, s_end are calculated the same for both slotting
@@ -877,8 +877,8 @@ _.extend(ReadPainter.prototype, FeaturePainter.prototype, {
if (s_start === s_end) {
s_end += 1;
}
// Draw read feature.
// Draw read feature.
switch (cig_op) {
case "H": // Hard clipping.
case "S": // Soft clipping.
@@ -932,7 +932,7 @@ _.extend(ReadPainter.prototype, FeaturePainter.prototype, {
// Move forward in sequence only if sequence used to get mismatches.
if (cig_op === 'X') { seq_offset += cig_len; }
base_offset += cig_len;
break;
case "N": // Skipped bases.
ctx.fillStyle = CONNECTOR_COLOR;
@@ -948,7 +948,7 @@ _.extend(ReadPainter.prototype, FeaturePainter.prototype, {
// Check to see if sequence should be drawn at all by looking at the overlap between
// the sequence region and the tile region.
var insert_x_coord = s_start - gap;
if (is_overlap([seq_start, seq_start + cig_len], tile_region)) {
var seq = read_seq.slice(seq_offset, seq_offset + cig_len);
// Insertion point is between the sequence start and the previous base: (-gap) moves
@@ -992,7 +992,7 @@ _.extend(ReadPainter.prototype, FeaturePainter.prototype, {
// Draw block.
ctx.fillStyle = "yellow";
// TODO: This is a pretty hack-ish way to fill rectangle based on mode.
ctx.fillRect(x_center, y_start + (this.mode !== "Dense" ? 2 : 5),
ctx.fillRect(x_center, y_start + (this.mode !== "Dense" ? 2 : 5),
s_end - s_start, (mode !== "Dense" ? SQUISH_FEATURE_HEIGHT : DENSE_FEATURE_HEIGHT));
}
}
@@ -1011,7 +1011,7 @@ _.extend(ReadPainter.prototype, FeaturePainter.prototype, {
break;
}
}
//
// Draw last items.
//
@@ -1032,7 +1032,7 @@ _.extend(ReadPainter.prototype, FeaturePainter.prototype, {
}
}
},
/**
* Draw a complete read pair
*/
@@ -1048,7 +1048,7 @@ _.extend(ReadPainter.prototype, FeaturePainter.prototype, {
y_start = (mode === "Dense" ? 0 : (0 + slot)) * y_scale,
draw_height = (mode === 'Pack' ? PACK_FEATURE_HEIGHT : SQUISH_FEATURE_HEIGHT),
label_color = this.prefs.label_color;
// Draw read.
if (feature[5] instanceof Array) {
// Read is paired.
@@ -1097,7 +1097,7 @@ _.extend(ReadPainter.prototype, FeaturePainter.prototype, {
ctx.fillText(feature_name, f_start - LABEL_SPACING, y_start + 9, this.max_label_length);
}
}
// FIXME: provide actual coordinates for drawn read.
return [0,0];
}
@@ -1122,7 +1122,7 @@ _.extend(ArcLinkedFeaturePainter.prototype, FeaturePainter.prototype, LinkedFeat
return longest_feature_length;
},
get_top_padding: function( width ) {
get_top_padding: function( width ) {
var view_range = this.view_end - this.view_start,
w_scale = width / view_range;
return Math.min( 128, Math.ceil( ( this.longest_feature_length / 2 ) * w_scale ) );
@@ -1131,7 +1131,7 @@ _.extend(ArcLinkedFeaturePainter.prototype, FeaturePainter.prototype, LinkedFeat
draw_connector: function( ctx, block1_start, block1_end, block2_start, block2_end, y_start ) {
// Arc drawing -- from closest endpoints
var x_center = ( block1_end + block2_start ) / 2,
radius = block2_start - x_center;
radius = block2_start - x_center;
// For full half circles
var angle1 = Math.PI, angle2 = 0;
if ( radius > 0 ) {
@@ -1290,32 +1290,33 @@ SplitRamp.prototype.map_value = function( value ) {
var DiagonalHeatmapPainter = function(data, view_start, view_end, prefs, mode) {
Painter.call( this, data, view_start, view_end, prefs, mode );
var i, len;
if ( this.prefs.min_value === undefined ) {
var min_value = Infinity;
for (i = 0, len = this.data.length; i < len; i++) {
min_value = Math.min( min_value, this.data[i][5] );
min_value = Math.min( min_value, this.data[i][6] );
}
this.prefs.min_value = min_value;
}
if ( this.prefs.max_value === undefined ) {
var max_value = -Infinity;
for (i = 0, len = this.data.length; i < len; i++) {
max_value = Math.max( max_value, this.data[i][5] );
max_value = Math.max( max_value, this.data[i][6] );
}
this.prefs.max_value = max_value;
}
};
DiagonalHeatmapPainter.prototype.default_prefs = {
min_value: undefined,
max_value: undefined,
mode: "Heatmap",
DiagonalHeatmapPainter.prototype.default_prefs = {
min_value: undefined,
max_value: undefined,
mode: "Heatmap",
pos_color: "#FF8C00",
neg_color: "#4169E1"
neg_color: "#4169E1"
};
DiagonalHeatmapPainter.prototype.draw = function(ctx, width, height, w_scale) {
var
var
min_value = this.prefs.min_value,
max_value = this.prefs.max_value,
value_range = max_value - min_value,
@@ -1336,15 +1337,11 @@ DiagonalHeatmapPainter.prototype.draw = function(ctx, width, height, w_scale) {
// Draw into triangle, then rotate and scale
ctx.rotate(-45 * Math.PI / 180);
ctx.scale( invsqrt2, invsqrt2 );
// Paint track.
for (var i = 0, len = data.length; i < len; i++) {
d = data[i];
// Ensure the cell is visible
// if ( )
s1 = scale( d[1] );
e1 = scale( d[2] );
s2 = scale( d[4] );
@@ -1352,10 +1349,9 @@ DiagonalHeatmapPainter.prototype.draw = function(ctx, width, height, w_scale) {
value = d[6];
ctx.fillStyle = ( ramp.map_value( value ) );
ctx.fillRect( s1, s2, ( e1 - s1 ), ( e2 - s2 ) );
}
ctx.restore();
};
@@ -1372,7 +1368,7 @@ var ReadPainterUtils = function(ctx, row_height, px_per_base, mode) {
_.extend(ReadPainterUtils.prototype, {
/**
* Draw deletion of base(s).
* Draw deletion of base(s).
* @param draw_detail if true, drawing in detail and deletion is drawn more subtly
*/
draw_deletion: function(x, y, len) {
@@ -1399,7 +1395,7 @@ _.extend(VariantPainter.prototype, Painter.prototype, {
get_row_height: function() {
var mode = this.mode, height;
if (mode === "Dense") {
height = DENSE_TRACK_HEIGHT;
height = DENSE_TRACK_HEIGHT;
}
else if (mode === "Squish") {
height = SQUISH_TRACK_HEIGHT;
@@ -1431,8 +1427,8 @@ _.extend(VariantPainter.prototype, Painter.prototype, {
draw: function(ctx, width, height, w_scale) {
ctx.save();
var
/**
var
/**
* Returns dictionary of information about an indel; returns empty if there no indel. Assumes indel is left-aligned.
* Dict attributes:
* -type: 'insertion' or 'deletion'
@@ -1485,7 +1481,7 @@ _.extend(VariantPainter.prototype, Painter.prototype, {
row_height = (this.mode === 'Squish' ? SQUISH_TRACK_HEIGHT : PACK_TRACK_HEIGHT),
// If zoomed out, fill the whole row with feature to make it easier to read;
// when zoomed in, use feature height so that there are gaps in sample rows.
feature_height = (w_scale < 0.1 ?
feature_height = (w_scale < 0.1 ?
row_height :
(this.mode === 'Squish' ? SQUISH_FEATURE_HEIGHT : PACK_FEATURE_HEIGHT)
),
@@ -1495,7 +1491,7 @@ _.extend(VariantPainter.prototype, Painter.prototype, {
// If there's a single sample, update drawing variables.
if (num_samples === 1) {
row_height = feature_height =
row_height = feature_height =
(w_scale < ctx.canvas.manager.char_width_px ? this.prefs.summary_height : row_height);
paint_utils.row_height = row_height;
// No summary when there's a single sample.
@@ -1519,7 +1515,7 @@ _.extend(VariantPainter.prototype, Painter.prototype, {
alt = [ locus_data[4].split(',') ];
sample_gts = locus_data[7].split(',');
allele_counts = locus_data.slice(8);
// Process alterate values to derive information about each alt.
alt = _.map(_.flatten(alt), function(a) {
var alt_info = {
@@ -1528,7 +1524,7 @@ _.extend(VariantPainter.prototype, Painter.prototype, {
start: 0
},
indel_info = get_indel_info(ref, a);
return _.extend(alt_info, indel_info);
});
@@ -1541,7 +1537,7 @@ _.extend(VariantPainter.prototype, Painter.prototype, {
if (draw_summary) {
ctx.fillStyle = '#999999';
ctx.globalAlpha = 1;
for (j = 0; j < alt.length; j++) {
for (j = 0; j < alt.length; j++) {
// Draw background for summary.
draw_x_start = this.get_start_draw_pos(pos + alt[j].start, w_scale);
ctx.fillRect(draw_x_start, 0, base_px, this.prefs.summary_height);
@@ -1564,7 +1560,7 @@ _.extend(VariantPainter.prototype, Painter.prototype, {
draw_y_start = (draw_summary ? this.prefs.summary_height + this.divider_height : 0);
for (j = 0; j < sample_gts.length; j++, draw_y_start += row_height) {
genotype = (sample_gts[j] ? sample_gts[j].split(/\/|\|/) : ['0', '0']);
// Get variant to draw and set drawing properties.
variant = null;
if (genotype[0] === genotype[1]) {
File diff suppressed because it is too large Load Diff
+16 -19
View File
@@ -4,6 +4,9 @@
// Galaxy specific variables
@import "galaxy_variables.less";
// Galaxy Report Webapp
@import "reports";
@white: #fff;
@black: #000;
@@ -134,14 +137,6 @@ body {
#left-border {
left: 250px;
}
.reports-panel-container {
position: absolute;
top: @panel_header_height;
bottom: 10px;
overflow: scroll;
width: 100%;
padding: 10px;
}
#center {
left:250 + @border-default-color_padding;
right: 250 + @border-default-color_padding;
@@ -295,10 +290,15 @@ div.unified-panel-body {
top: @panel_header_height;
bottom: 0;
width: 100%;
padding: 0px;
}
div.unified-panel-body-background {
background: none repeat scroll 0 0 #DFE5F9;
}
#left > div.unified-panel-body, #right > div.unified-panel-body {
bottom: @panel_footer_height;
overflow: auto;
}
.panel-header-button {
@@ -762,7 +762,7 @@ input[type="submit"], button {
// Messages
.errormessagelarge, .warningmessagelarge, .donemessagelarge, .infomessagelarge {
.errormessagelarge, .warningmessagelarge, .donemessagelarge, .infomessagelarge, .toolHelpBody .error, .toolHelpBody .warning, .toolHelpBody .note {
.alert();
min-height: 36px;
padding-left: 52px;
@@ -800,7 +800,7 @@ input[type="submit"], button {
background-image: none;
}
.errormessage, .warningmessage, .donemessage, .infomessage, .errormessagesmall, .warningmessagesmall, .donemessagesmall, .infomessagesmall {
.errormessage, .warningmessage, .donemessage, .infomessage, .errormessagesmall, .warningmessagesmall, .donemessagesmall, .infomessagesmall, toolHelpBody .error, .toolHelpBody .warning, .toolHelpBody .note {
.alert();
padding: 5px;
padding-left: 25px;
@@ -812,11 +812,11 @@ input[type="submit"], button {
background-position: 5px 5px;
}
.errormessage, .errormessagesmall {
.errormessage, .errormessagesmall, .toolHelpBody .error {
.alert-danger();
}
.warningmessage, .warningmessagesmall {
.warningmessage, .warningmessagesmall, .toolHelpBody .warning {
.alert-warning();
background-image: url(warn_small.png);
}
@@ -826,7 +826,7 @@ input[type="submit"], button {
background-image: url(ok_small.png);
}
.infomessage, .infomessagesmall {
.infomessage, .infomessagesmall, .toolHelpBody .note {
.alert-info();
background-image: url(info_small.png);
}
@@ -1447,9 +1447,6 @@ div.permissionContainer {
}
// ============================================================================ History
@import "sprite-history-states.less";
@import "sprite-history-buttons.less";
@import "ui/icon-btn.less";
@import "list-item.less";
@import "history.less";
@@ -1628,7 +1625,7 @@ div.toolTitleNoSection
.sprite(@tags);
}
.icon-button.tag--plus {
.sprite(@tag--plus);
.sprite(@tag-plus);
}
.icon-button.toggle-expand {
.sprite(@toggle-expand);
@@ -1676,10 +1673,10 @@ div.toolTitleNoSection
.sprite(@chart);
}
.icon-button.disk--arrow {
.sprite(@disk--arrow-bw);
.sprite(@disk-arrow-bw);
}
.icon-button.disk--arrow:hover {
.sprite(@disk--arrow);
.sprite(@disk-arrow);
}
.icon-button.cross-circle {
.sprite(@cross-circle-bw);
@@ -15,7 +15,7 @@
// Default border bolor, this is between gray-light and gray-lighter -- classic Galaxy border color
@border-default-color: lighten(black, 75%);
// When making borders for components with other colors, how much to darken
// When making borders for components with other colors, how much to darken
@border-darken-percent: 40%;
// For making state colors, how much to darken and lighten
@@ -29,16 +29,16 @@
// Bootstrap default colors for states, will be adjusted based on @base-color
@bs-primary: #428bca;
@bs-info: #5bc0de;
@bs-primary: #428bca;
@bs-success: #5cb85c;
@bs-warning: #f0ad4e;
@bs-danger: #d9534f;
@bs-info: #5bc0de;
// Derived variables used below
@sat: saturation(@base-color);
@luma: luma(@base-color);
@luma: luminance(@base-color);
@light: lightness(@base-color);
@tone: desaturate(@base-color,100%);
@@ -64,13 +64,32 @@
// Brand colors
// -------------------------
@brand-primary: @base-color;
// @brand-primary: hsl(hue(@bs-primary),(saturation(@bs-info) + @sat + 20)/2,@luma - 10);
@brand-primary: hsl(
hue(@bs-primary),
(saturation(@bs-info) + @sat + 20)/2,
@luma - 10
);
@brand-info: hsl(hue(@bs-info),(saturation(@bs-info) + @sat + 20)/2,@luma);
@brand-success: hsl(hue(@bs-success),(saturation(@bs-success) + @sat + 20)/2,(luma(@bs-success) + @luma - 20)/2);
@brand-warning: hsl(hue(@bs-warning),(saturation(@bs-warning) + @sat + 20)/2,(luma(@bs-warning) + @luma - 20)/2);
@brand-danger: hsl(hue(@bs-danger),(saturation(@bs-danger) + @sat + 20)/2,(luma(@bs-danger) + @luma + 10)/2);
@brand-info: hsl(
hue(@bs-info),
(saturation(@bs-info) + @sat + 20)/2,
@luma
);
@brand-success: hsl(
hue(@bs-success),
(saturation(@bs-success) + @sat + 20)/2,
(luminance(@bs-success) + @luma - 20)/2
);
@brand-warning: hsl(
hue(@bs-warning),
(saturation(@bs-warning) + @sat + 20)/2,
(luminance(@bs-warning) + @luma - 20)/2
);
@brand-danger: hsl(
hue(@bs-danger),
(saturation(@bs-danger) + @sat + 20)/2,
(luminance(@bs-danger) + @luma + 10)/2
);
// Scaffolding
// -------------------------
@@ -430,7 +449,7 @@
@state-danger-border: darken(@state-danger-bg, @border-darken-percent);
@state-success-text: darken(@brand-success, @state-text-darken-percent);
@state-success-bg: saturate(lighten(@brand-success, @state-bg-lighten-percent),@state-bg-saturate-percent);
@state-success-bg: saturate(lighten(@brand-success, @state-bg-lighten-percent, relative),@state-bg-saturate-percent);
@state-success-border: darken(@state-success-bg, @border-darken-percent);
@state-info-text: darken(@brand-info, @state-text-darken-percent);
+22 -173
View File
@@ -1,3 +1,6 @@
@import "sprite-history-states.less";
@import "sprite-history-buttons.less";
// ---------------------------------------------------------------------------- panel header buttons
.history-panel-header {
.panel-header-button {
@@ -637,12 +640,16 @@
// popover containing sorting and filters
.more-options {
.btn {
width: 100%;
height: 26px;
}
input[type=checkbox] {
margin-top: 1px;
}
}
.popover {
min-width: 290px;
}
}
.footer {
min-height: @mph-footer_height;
@@ -800,183 +807,25 @@
overflow: auto;
}
}
// loading indicator on far right of multipanel view
.histories-loading-indicator {
// if we don't bring the width down here, the space is way too large for vertical text
width: 8px;
transform: rotate(90deg);
transform-origin: left top 0;
margin-left: 16px;
white-space: nowrap;
color: grey;
span {
// space btwn icon and text
margin-right: 8px;
}
}
}
// ---------------------------------------------------------------------------- older styles
//TODO: these may still apply in mobile display, workflow stuff
body.historyPage {
background: @side-panel-bg;
color: @base-text-color;
margin: 5px;
border: 0;
padding: 0;
}
// Control links at top of history
div.historyLinks {
margin: 5px 5px;
}
// Default history item appearance
div.historyItem {
margin: 0px -5px;
padding: 8px 10px;
border-top: solid @border-default-color 1px;
// border-left: solid @state-queued-border 5px;
border-right: none;
word-wrap: break-word;
background: @state-default-bg;
.state-icon {
display: inline-block;
vertical-align: middle;
width: 16px;
height: 16px;
background-position: 0 1px;
background-repeat: no-repeat;
}
.historyItemTitle {
font-weight: bold;
line-height: 16px;
}
}
.historyItemContainer:last-child div.historyItem {
border-bottom: solid @border-default-color 1px;
}
// Nested history items
div.historyItem div.historyItem {
margin-right: -11px;
}
// Change background/border color depending on state
div.historyItem-ok,
div.historyItem-failed_metadata {
background: @state-success-bg;
.state-icon {
display: none;
}
}
div.historyItem-error {
background: @state-danger-bg;
.state-icon {
.sprite(@data_error);
}
}
div.historyItem-empty {
background: @state-danger-bg;
.state-icon {
.sprite(@data_empty);
}
}
div.historyItem-running {
background: @state-running-bg;
.state-icon {
background-image: url(data_running.gif);
}
}
div.historyItem-setting_metadata {
background: @state-running-bg;
.state-icon {
background-image: url(data_running.gif);
}
}
div.historyItem-upload {
background: @state-info-bg;
.state-icon {
background-image: url(data_upload.gif);
}
}
div.historyItem-queued {
background: @state-default-bg;
.state-icon {
.sprite(@data_queued);
}
}
div.historyItem-noPermission {
filter: alpha(opacity=60);
-moz-opacity: .60;
opacity: .60;
.state-icon {
line-height: 16px;
.fa-icon;
&:before {
content: "\f05e";
}
}
}
div.historyItem-paused {
background: @state-paused-bg;
.state-icon {
line-height: 16px;
.fa-icon;
&:before {
content: "\f04c";
}
}
}
div.historyItem-new {
.state-icon {
line-height: 16px;
.fa-icon;
&:before {
content: "\f071";
}
}
}
// Special case for showing the spinner but not changing the background
div.historyItemTitleBar.spinner .state-icon {
background: url(data_running.gif) 0 1px no-repeat !important;
}
// Buttons
div.historyItemButtons {
float: right;
.icon-button.display {
.sprite(@eye_icon);
}
.icon-button.display:hover {
.sprite(@eye_icon_dark);
}
.icon-button.display_disabled {
.sprite(@eye_icon_grey);
}
.icon-button.delete {
.sprite(@delete_icon);
}
.icon-button.delete:hover {
.sprite(@delete_icon_dark);
}
.icon-button.delete_disabled {
.sprite(@delete_icon_grey);
}
.icon-button.edit {
.sprite(@pencil_icon);
}
.icon-button.edit:hover {
.sprite(@pencil_icon_dark);
}
.icon-button.edit_disabled {
.sprite(@pencil_icon_grey);
}
}
div.historyItemBody div {
padding-top: 2px;
}
pre.peek {
background: white;
color: black;
+4 -1
View File
@@ -119,10 +119,13 @@ th.button_heading{
}
#library_toolbar {
margin-bottom: 1em;
margin-bottom: 0.5em;
span {
margin-right: 0.2em;
}
.toolbar-item{
margin-left: 1em;
}
}
img.expanderIcon {
+2
View File
@@ -55,6 +55,8 @@
color: @btn-default-color;
cursor: pointer;
vertical-align: middle;
// prevent text highlight when holding shift to select a range
.user-select(none);
}
.selector:hover {
color: maroon;

Some files were not shown because too many files have changed in this diff Show More