Merge remote-tracking branch 'upstream/dev' into slow_query_logging_tweaks

This commit is contained in:
Dannon Baker
2018-03-20 13:11:11 -04:00
972 changed files with 23674 additions and 40303 deletions
+10 -9
View File
@@ -7,15 +7,16 @@ else
fi
export ACK_OPTIONS=" --type python \
--ignore-dir=.git/ \
--ignore-dir=.tox/ \
--ignore-dir=.venv/ \
--ignore-dir=client/node_modules/ \
--ignore-dir=database/ \
--ignore-dir=doc/build/ \
--ignore-dir=eggs/ \
--ignore-dir=static/maps/ \
--ignore-dir=static/scripts/"
--ignore-dir=.git \
--ignore-dir=.tox \
--ignore-dir=.venv \
--ignore-dir=.venv3 \
--ignore-dir=client/node_modules \
--ignore-dir=database \
--ignore-dir=doc/build \
--ignore-dir=eggs \
--ignore-dir=static/maps \
--ignore-dir=static/scripts"
PYTHON2_ONLY_MODULES="__builtin__ _winreg BaseHTTPServer CGIHTTPServer \
ConfigParser Cookie cookielib copy_reg cPickle cStringIO Dialog dummy_thread \
+10 -6
View File
@@ -1,8 +1,12 @@
.venv/
.tox/
client/node_modules/
database/
.git
.tox
.venv
.venv3
client/node_modules
database
doc/build
doc/source/conf.py
eggs/
eggs
lib/galaxy/util/jstree.py
lib/galaxy/web/proxy/js/node_modules/
static/maps
static/scripts
+1 -1
View File
@@ -2,7 +2,7 @@
set -e
flake8 --exclude `paste -sd, .ci/flake8_blacklist.txt` .
flake8 --exclude $(paste -sd, .ci/flake8_blacklist.txt) .
# Apply stricter rules for the directories shared with Pulsar
flake8 --ignore=D --max-line-length=150 lib/galaxy/jobs/runners/util/
+2 -2
View File
@@ -15,7 +15,7 @@ args="--ignore=D --select=D100,D201,D202,D206,D207,D208,D209,D211,D3,D403"
if [ "$1" = "--include" ];
then
flake8 $args `paste .ci/flake8_docstrings_include_list.txt`
flake8 $args $(paste -sd' ' .ci/flake8_docstrings_include_list.txt)
else
flake8 $args --exclude `paste -sd, .ci/flake8_blacklist.txt` .
flake8 $args --exclude $(paste -sd, .ci/flake8_blacklist.txt) .
fi
+2 -2
View File
@@ -7,6 +7,7 @@ scripts/scramble/archives
# Python virtualenv
.venv
.venv3
# Database stuff
database/beaker_sessions
@@ -123,9 +124,8 @@ tool-data/shared/jars/
sprite-*.less
# JS, Local node_modules, and bower_components directories
static/scripts/bundled/[0-9]*
static/scripts
static/maps
static/scripts/bundled/*.map
node_modules
bower_components
client/galaxy/scripts/libs/*
+13 -6
View File
@@ -8,13 +8,13 @@ env:
- TOX_ENV=py27-lint
- TOX_ENV=py27-unit
- TOX_ENV=qunit
- TOX_ENV=first_startup
- TOX_ENV=py27-lint-docstring-include-list
- TOX_ENV=check-python-dependencies
- TOX_ENV=py27-first_startup
- TOX_ENV=py27-lint_docstring_include_list
- TOX_ENV=check_python_dependencies
matrix:
include:
- env: TOX_ENV=validate-test-tools
- env: TOX_ENV=validate_test_tools
addons:
apt:
packages:
@@ -25,12 +25,19 @@ matrix:
packages:
- ack-grep
allow_failures:
- env: TOX_ENV=check-python-dependencies
- env: TOX_ENV=check_python_dependencies
install:
- set -e
- pip install tox
- if [ "$TOX_ENV" == "first_startup" ]; then bash -c "bash scripts/common_startup.sh && wget -q https://github.com/jmchilton/galaxy-downloads/raw/master/db_gx_rev_0127.sqlite && mv db_gx_rev_0127.sqlite database/universe.sqlite && bash manage_db.sh -c ./config/galaxy.yml.sample upgrade"; fi
- |
if [ "$TOX_ENV" == "py27-first_startup" ]; then
sh scripts/common_startup.sh
wget -q https://github.com/jmchilton/galaxy-downloads/raw/master/db_gx_rev_0127.sqlite
mv db_gx_rev_0127.sqlite database/universe.sqlite
sh manage_db.sh -c ./config/galaxy.yml.sample upgrade
fi
script: tox -e $TOX_ENV
+5 -16
View File
@@ -17,7 +17,6 @@ DOC_SOURCE_DIR=$(DOCS_DIR)/source
SLIDESHOW_DIR=$(DOC_SOURCE_DIR)/slideshow
OPEN_RESOURCE=bash -c 'open $$0 || xdg-open $$0'
SLIDESHOW_TO_PDF?=bash -c 'docker run --rm -v `pwd`:/cwd astefanutti/decktape /cwd/$$0 /cwd/`dirname $$0`/`basename -s .html $$0`.pdf'
CLIENT_COMMIT_WARNING="Please remember to 'make client-production' when finished developing, before a commit!"
YARN := $(shell command -v yarn 2> /dev/null)
all: help
@@ -72,9 +71,6 @@ tool-shed-config-convert-dry-run: ## convert old style tool shed ini to yaml (dr
tool-shed-config-convert: ## convert old style tool shed ini to yaml
$(CONFIG_MANAGE) convert tool_shed
tool-shed-config-rebuild-sample: ## Rebuild sample tool shed yaml file from schema
$(CONFIG_MANAGE) build_sample_yaml tool_shed --add-comments
reports-config-validate: ## validate reports YAML configuration file
$(CONFIG_MANAGE) validate reports
@@ -84,15 +80,9 @@ reports-config-convert-dry-run: ## convert old style reports ini to yaml (dry ru
reports-config-convert: ## convert old style reports ini to yaml
$(CONFIG_MANAGE) convert reports
reports-config-rebuild-sample: ## Rebuild sample reports yaml file from schema
$(CONFIG_MANAGE) build_sample_yaml reports --add-comments
reports-config-lint: ## lint reports YAML configuration file
$(CONFIG_MANAGE) lint reports
reports-config-rebuild-rst: ## Rebuild sample reports RST docs
$(CONFIG_MANAGE) build_rst reports > doc/source/admin/reports_options.rst
config-validate: ## validate galaxy YAML configuration file
$(CONFIG_MANAGE) validate galaxy
@@ -102,15 +92,16 @@ config-convert-dry-run: ## convert old style galaxy ini to yaml (dry run)
config-convert: ## convert old style galaxy ini to yaml
$(CONFIG_MANAGE) convert galaxy
config-rebuild-sample: ## Rebuild sample galaxy yaml file from schema
config-rebuild: ## Rebuild all sample YAML and RST files from config schema
$(CONFIG_MANAGE) build_sample_yaml galaxy --add-comments
$(CONFIG_MANAGE) build_rst galaxy > doc/source/admin/galaxy_options.rst
$(CONFIG_MANAGE) build_sample_yaml reports --add-comments
$(CONFIG_MANAGE) build_rst reports > doc/source/admin/reports_options.rst
$(CONFIG_MANAGE) build_sample_yaml tool_shed --add-comments
config-lint: ## lint galaxy YAML configuration file
$(CONFIG_MANAGE) lint galaxy
config-rebuild-rst: ## Rebuild sample galaxy RST docs
$(CONFIG_MANAGE) build_rst galaxy > doc/source/admin/galaxy_options.rst
release-ensure-upstream: ## Ensure upstream branch for release commands setup
ifeq (shell git remote -v | grep $(RELEASE_UPSTREAM), )
git remote add $(RELEASE_UPSTREAM) git@github.com:galaxyproject/galaxy.git
@@ -156,7 +147,6 @@ endif
client: node-deps ## Rebuild client-side artifacts for local development.
cd client && yarn run build
@echo $(CLIENT_COMMIT_WARNING)
client-production: node-deps ## Rebuild client-side artifacts for a production deployment (or committing to the repository).
cd client && yarn run build-production
@@ -169,7 +159,6 @@ client-format: node-deps ## Reformat client code
client-watch: node-deps ## A useful target for parallel development building.
cd client && yarn run watch
@echo $(CLIENT_COMMIT_WARNING)
client-test: client ## Run qunit tests via Karma
cd client && yarn run test
+42 -3
View File
@@ -1,5 +1,6 @@
import jQuery from "jquery";
var $ = jQuery;
import * as _ from "underscore";
import GalaxyApp from "galaxy";
import Router from "layout/router";
import ToolPanel from "./panels/tool-panel";
@@ -14,6 +15,7 @@ import GridView from "mvc/grid/grid-view";
import GridShared from "mvc/grid/grid-shared";
import Workflows from "mvc/workflow/workflow";
import HistoryImport from "components/HistoryImport.vue";
import HistoryView from "components/HistoryView.vue";
import HistoryList from "mvc/history/history-list";
import ToolFormComposite from "mvc/tool/tool-form-composite";
import QueryStringParsing from "utils/query-string-parsing";
@@ -21,9 +23,13 @@ import Utils from "utils/utils";
import Ui from "mvc/ui/ui-misc";
import DatasetError from "mvc/dataset/dataset-error";
import DatasetEditAttributes from "mvc/dataset/dataset-edit-attributes";
import Visualization from "mvc/visualization/visualization-view";
import Citations from "components/Citations.vue";
import DisplayStructure from "components/DisplayStructured.vue";
import Vue from "vue";
/* global Galaxy */
/** define the 'Analyze Data'/analysis/main/home page for Galaxy
* * has a masthead
* * a left tool menu to allow the user to load tools in the center panel
@@ -52,6 +58,8 @@ window.app = function app(options, bootstrapped) {
"(/)pages(/)edit(/)": "show_pages_edit",
"(/)pages(/)(:action_id)": "show_pages",
"(/)visualizations(/)edit(/)": "show_visualizations_edit",
"(/)visualizations/show/(:visualization_id)": "show_visualizations_client",
"(/)visualizations/dataset_id=(:dataset_id)": "show_visualizations_selector",
"(/)visualizations/(:action_id)": "show_visualizations",
"(/)workflows/import_workflow": "show_import_workflow",
"(/)workflows/run(/)": "show_run",
@@ -62,6 +70,8 @@ window.app = function app(options, bootstrapped) {
"(/)histories(/)rename(/)": "show_histories_rename",
"(/)histories(/)import(/)": "show_histories_import",
"(/)histories(/)permissions(/)": "show_histories_permissions",
"(/)histories/view": "show_history_view",
"(/)histories/show_structure": "show_history_structure",
"(/)histories(/)(:action_id)": "show_histories",
"(/)datasets(/)list(/)": "show_datasets",
"(/)custom_builds": "show_custom_builds",
@@ -113,14 +123,37 @@ window.app = function app(options, bootstrapped) {
);
},
show_visualizations_selector: function(dataset_id) {
this.page.display(
new Visualization.View({
dataset_id: dataset_id
})
);
},
show_workflows_published: function() {
this.page.display(
new GridView({
url_base: `${Galaxy.root}workflow/list_published`
url_base: `${Galaxy.root}workflow/list_published`,
active_tab: "shared"
})
);
},
show_history_view: function() {
var historyInstance = Vue.extend(HistoryView);
var vm = document.createElement("div");
this.page.display(vm);
new historyInstance({ propsData: { id: QueryStringParsing.get("id") } }).$mount(vm);
},
show_history_structure: function() {
let displayStructureInstance = Vue.extend(DisplayStructure);
let vm = document.createElement("div");
this.page.display(vm);
new displayStructureInstance({ propsData: { id: QueryStringParsing.get(" id: ") } }).$mount(vm);
},
show_histories: function(action_id) {
this.page.display(new HistoryList.View({ action_id: action_id }));
},
@@ -160,7 +193,8 @@ window.app = function app(options, bootstrapped) {
show_openids: function() {
this.page.display(
new GridView({
url_base: `${Galaxy.root}user/openids_list`
url_base: `${Galaxy.root}user/openids_list`,
active_tab: "user"
})
);
},
@@ -168,7 +202,8 @@ window.app = function app(options, bootstrapped) {
show_datasets: function() {
this.page.display(
new GridView({
url_base: `${Galaxy.root}dataset/list`
url_base: `${Galaxy.root}dataset/list`,
active_tab: "user"
})
);
},
@@ -302,6 +337,10 @@ window.app = function app(options, bootstrapped) {
// render and start the router
$(() => {
options.config = _.extend(options.config, {
hide_panels: Galaxy.params.hide_panels,
hide_masthead: Galaxy.params.hide_masthead
});
Galaxy.page = new Page.View(
_.extend(options, {
Left: ToolPanel,
+20
View File
@@ -0,0 +1,20 @@
import * as _ from "libs/underscore";
import Client from "mvc/visualization/chart/chart-client";
import Datasets from "mvc/visualization/chart/utilities/datasets";
import Series from "mvc/visualization/chart/utilities/series";
import Jobs from "mvc/visualization/chart/utilities/jobs";
window.bundleEntries = window.bundleEntries || {};
export const bundleEntries = {
chart: function(options) {
return new Client(options);
},
chartUtilities: {
Datasets: Datasets,
Jobs: Jobs,
Series: Series
}
};
_.extend(window.bundleEntries, bundleEntries);
+1 -86
View File
@@ -1,4 +1,3 @@
import _l from "utils/localization";
import GalaxyApp from "galaxy";
import WorkflowView from "mvc/workflow/workflow-view";
import Trackster from "viz/trackster";
@@ -17,7 +16,6 @@ import StructureView from "mvc/history/history-structure-view";
import HistoryContents from "mvc/history/history-contents";
import MultiPanel from "mvc/history/multi-panel";
import HistoryView from "mvc/history/history-view";
import HistoryViewEdit from "mvc/history/history-view-edit";
import HistoryViewAnnotated from "mvc/history/history-view-annotated";
import HistoryCopyDialog from "mvc/history/copy-dialog";
import HDAListItemEdit from "mvc/history/hda-li-edit";
@@ -67,89 +65,6 @@ export function libraryEntry(options) {
new GalaxyLibrary.GalaxyApp(options);
}
export function historyEntry(options) {
$("#toggle-deleted").modeButton({
initialMode: options.initialModeDeleted,
modes: [
{ mode: "showing_deleted", html: _l("Exclude deleted") },
{ mode: "not_showing_deleted", html: _l("Include deleted") }
]
});
$("#toggle-hidden").modeButton({
initialMode: options.initialModeHidden,
modes: [
{ mode: "showing_hidden", html: _l("Exclude hidden") },
{ mode: "not_showing_hidden", html: _l("Include hidden") }
]
});
$("#switch").click(function() {
//##HACK:ity hack hack
//##TODO: remove when out of iframe
var hview =
Galaxy.currHistoryPanel || (window.top.Galaxy && window.top.Galaxy.currHistoryPanel)
? window.top.Galaxy.currHistoryPanel
: null;
if (hview) {
hview.switchToHistory("${ history[ 'id' ] }");
} else {
window.location = "${ switch_to_url }";
}
});
// use_panels effects where the the center_panel() is rendered:
// w/o it renders to the body, w/ it renders to #center - we need to adjust a few things for scrolling to work
if (options.hasMasthead) {
$("#center").addClass("flex-vertical-container");
}
let viewClass = options.userIsOwner ? HistoryViewEdit.HistoryViewEdit : HistoryView.HistoryView;
let historyModel = new History.History(options.historyJSON);
// attach the copy dialog to the import button now that we have a history
$("#import").click(function() {
HistoryCopyDialog(historyModel, {
useImport: true,
// use default datasets option to match the toggle-deleted button
allDatasets: $("#toggle-deleted").modeButton("getMode").mode === "showing_deleted"
}).done(function() {
if (window === window.parent) {
window.location = Galaxy.root;
} else if (Galaxy.currHistoryPanel) {
Galaxy.currHistoryPanel.loadCurrentHistory();
}
});
});
let historyView = new viewClass({
el: $("#history-" + options.historyJSON.id),
className: viewClass.prototype.className + " wide",
$scrollContainer: options.hasMasthead
? function() {
return this.$el.parent();
}
: undefined,
model: historyModel,
show_deleted: options.showDeletedJson,
show_hidden: options.showHiddenJson,
purgeAllowed: options.allow_user_dataset_purge
});
historyView.trigger("loading");
historyModel
.fetchContents({ silent: true })
.fail(function() {
alert("Galaxy history failed to load");
})
.done(function() {
historyView.trigger("loading-done");
historyView.render();
});
$("#toggle-deleted").on("click", function() {
historyView.toggleShowDeleted();
});
$("#toggle-hidden").on("click", function() {
historyView.toggleShowHidden();
});
}
export function multiHistoryEntry(options) {
let histories = new History.HistoryCollection([], {
includeDeleted: options.includingDeleted,
@@ -180,7 +95,7 @@ export const bundleEntries = {
phyloviz: Phyloviz.PhylovizView,
createTabularDatasetChunkedView: Data.createTabularDatasetChunkedView,
multiHistory: multiHistoryEntry,
history: historyEntry,
history: HistoryView.historyEntry,
History: History.History,
HistoryContents: HistoryContents.HistoryContents,
SweepsterVisualization: Sweepster.SweepsterVisualization,
+39 -53
View File
@@ -2,6 +2,7 @@ import Tools from "mvc/tool/tools";
import Upload from "mvc/upload/upload-view";
import _l from "utils/localization";
import ToolForm from "mvc/tool/tool-form-composite";
import _ from "libs/underscore";
var ToolPanel = Backbone.View.extend({
initialize: function(page, options) {
@@ -28,7 +29,8 @@ var ToolPanel = Backbone.View.extend({
// add upload modal
this.upload_button = new Upload({
nginx_upload_path: config.nginx_upload_path,
upload_path: config.nginx_upload_path || `${Galaxy.root}api/tools`,
chunk_upload_size: config.chunk_upload_size,
ftp_upload_site: config.ftp_upload_site,
default_genome: config.default_genome,
default_extension: config.default_extension
@@ -74,70 +76,54 @@ var ToolPanel = Backbone.View.extend({
/** build a link to one tool */
_templateTool: function(tool) {
return [
'<div class="toolTitle">',
'<a href="',
this.root,
tool.href,
'" target="galaxy_main">',
tool.title,
"</a>",
"</div>"
].join("");
return `<div class="toolTitle">
<a href="${Galaxy.root}${tool.href}" target="galaxy_main">
${tool.title}
</a>
</div>`;
},
/** build a link to 'All Workflows' */
_templateAllWorkflow: function(tool) {
return [
'<div class="toolTitle">',
// global
'<a href="',
Galaxy.root,
tool.href,
'">',
tool.title,
"</a>",
"</div>"
].join("");
return `<div class="toolTitle">
<a href="${Galaxy.root}${tool.href}">
${tool.title}
</a>
</div>`;
},
/** build links to workflows in toolpanel */
_templateWorkflowLink: function(wf) {
return [
'<div class="toolTitle">',
`<a class="${wf.cls} " href="`,
Galaxy.root,
wf.href,
'">',
wf.title,
"</a>",
"</div>"
].join("");
return `<div class="toolTitle">
<a class="${wf.cls}" href="${Galaxy.root}${wf.href}">
${_.escape(wf.title)}
</a>
</div>`;
},
/** override to include inital menu dom and workflow section */
_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>",
_l("Search did not match any tools."),
"</strong></em>",
"</div>",
"</div>",
'<div class="toolSectionPad"/>',
'<div class="toolSectionPad"/>',
'<div class="toolSectionTitle" id="title_XXinternalXXworkflow">',
"<span>",
_l("Workflows"),
"</span>",
"</div>",
'<div id="internal-workflows" class="toolSectionBody">',
'<div class="toolSectionBg"/>',
"</div>",
"</div>"
].join("");
return `<div class="toolMenuContainer">
<div class="toolMenu" style="display: none">
<div id="search-no-results" style="display: none; padding-top: 5px">
<em>
<strong>
${_l("Search did not match any tools.")}
</strong>
</em>
</div>
</div>
<div class="toolSectionPad"/>
<div class="toolSectionPad"/>
<div class="toolSectionTitle" id="title_XXinternalXXworkflow">
<span>
${_l("Workflows")}
</span>
</div>
<div id="internal-workflows" class="toolSectionBody">
<div class="toolSectionBg"/>
</div>
</div>`;
},
toString: function() {
@@ -0,0 +1,205 @@
<template>
<div>
<div v-for="error in errorMessages">
<div class="alert alert-danger" role="alert">
{{ error }}
</div>
</div>
<div v-html="historyTemplate">
</div>
</div>
</template>
<script>
import axios from "axios";
import HDAModel from "mvc/history/hda-model";
import HDAListItemEdit from "mvc/history/hda-li-edit";
export default {
props: {
id: {
type: String,
required: false
}
},
data() {
return {
historyTemplate: "",
historyJSON: {},
// TODO: Error message standardization -- use bootstrap-vue component or the like.
errorMessages: []
};
},
created: function() {
let historyId = this.id,
url = "";
if (historyId !== undefined && historyId !== "" && historyId !== null) {
url = Galaxy.root + "history/display_structured?id=" + historyId;
} else {
url = Galaxy.root + "history/display_structured";
}
this.ajaxCall(url);
},
methods: {
ajaxCall: function(url) {
axios
.get(url)
.then(response => {
this._updateHistoryData(response);
})
.catch(e => {
console.error(e);
this.errorMessages.push(
"Error fetching history data -- reload the page to retry this request. Please contact an administrator if the problem persists."
);
});
},
_updateHistoryData: function(response) {
let historyItems = response.data;
this.historyTemplate = historyItems.template;
this.historyJSON = historyItems.history_json;
},
makeHistoryView: function(historyDict) {
window.hdas = historyDict.map(hda => {
return new HDAListItemEdit.HDAListItemEdit({
model: new HDAModel.HistoryDatasetAssociation(hda),
el: $("#hda-" + hda.id),
linkTarget: "galaxy_main",
purgeAllowed: Galaxy.config.allow_user_dataset_purge,
logger: Galaxy.logger
}).render(0);
});
// toggle the body section of each item in the structure
$(function() {
$(".workflow, .tool").each((index, element) => {
let body = $(element).children(".body");
$(element)
.children(".header")
.click(e => {
body.toggle();
})
.addClass("clickable");
});
});
}
},
updated: function() {
this.makeHistoryView(this.historyJSON);
}
};
</script>
<style>
.bold {
font-weight: bold;
}
.light {
font-weight: lighter;
color: grey;
}
.right-aligned {
text-align: right;
}
.clickable {
cursor: pointer;
}
.workflow {
border: solid gray 1px;
margin-bottom: 1%;
}
.workflow > .header {
background: lightgray;
padding: 5px 10px;
}
.workflow > .light {
color: gray;
}
.workflow > .body {
border-top: solid gray 1px;
}
.workflow > .body > .toolForm {
border: 0px;
margin: 0;
}
div.toolForm {
border-width: 1px;
border-radius: 0px;
margin-bottom: 1%;
}
.toolForm > .header {
background-color: #ebd9b2;
padding: 5px 10px;
}
.workflow div.toolForm:not(:first-child) .header {
border-top: 1px solid #d6b161;
}
div.toolFormTitle {
padding: 0px 0px 4px 0px;
margin: 0px 0px 4px 0px;
border: 0px;
background-color: transparent;
border-bottom: 1px solid #d6b161;
}
/* down from EBD9B2 --> 90743A */
.toolFormTitle > .light {
color: #90743a;
}
.toolForm em {
color: #90743a;
}
.job-inputs {
margin: 0px 6px 0px 6px;
text-align: left;
}
.job-inputs td:nth-child(1) {
text-align: right;
font-weight: lighter;
color: #90743a;
}
.job-inputs td:nth-child(1):after {
content: ":";
}
.job-inputs td:nth-child(2) {
padding-left: 4px;
}
.job-inputs em {
}
.job-inputs-show {
float: right;
}
.copied-from {
border: 1px solid lightgrey;
border-width: 1px 1px 0px 1px;
margin-bottom: 1%;
}
.copied-from .header {
border-bottom: 1px solid lightgrey;
padding: 5px;
}
.copied-from .header .bold,
.copied-from .header a {
color: #888;
}
.dataset.hda {
min-height: 37px;
border-width: 0px 0px 1px 0px;
}
.toolFormBody > .dataset.hda:last-child {
border-bottom-width: 0px;
}
.dataset.hda:first-child {
border-top: 1px solid #d6b161;
}
.dataset.hda .dataset-title-bar {
padding-top: 8px;
padding-left: 10px;
}
</style>
@@ -0,0 +1,133 @@
<template>
<div id="structured-history-view">
<div id="history-view-controls" class="clear">
<div class="pull-left">
<span v-if="historyHistory['purged'] == false" >
<span v-if="historyData.user_is_owner == false" >
<button id="import" class="btn btn-default">Import and start using history</button>
</span>
<span v-if="historyData.history_is_current == false">
<button id="switch-history" class="btn btn-default" v-on:click="switchHistory">Switch to this history</button>
</span>
<button id="show-structure" class="btn btn-default" v-on:click="showStructure">Show structure</button>
</span>
</div>
<div class="pull-right">
<button id="toggle-deleted" class="btn btn-default">Include deleted</button>
<button id="toggle-hidden" class="btn btn-default">Include hidden</button>
</div>
</div>
<div v-for="error in errorMessages">
<div class="alert alert-danger" role="alert">
{{ error }}
</div>
</div>
<div :id="'history-' + historyHistory['id']" class="history-panel unified-panel-body" style="overflow: auto;"></div>
</div>
</template>
<script>
import axios from "axios";
import Vue from "vue";
import DisplayStructure from "components/DisplayStructured.vue";
import QueryStringParsing from "utils/query-string-parsing";
import HistoryView from "mvc/history/history-view";
export default {
props: {
id: {
type: String,
required: true
}
},
data() {
return {
historyData: {},
historyHistory: {},
// TODO: Pick a standard messaging convention and use a child component for it here.
errorMessages: []
};
},
created: function() {
let url = Galaxy.root + "history/view/" + this.id;
this.ajaxCall(url, this.updateHistoryView);
},
methods: {
ajaxCall: function(url, callBack) {
axios
.get(url)
.then(response => {
callBack(response);
})
.catch(e => {
this.showError(
"Error fetching histories -- reload the page to retry this request. Please contact an administrator if the problem persists.",
e
);
});
},
updateHistoryView: function(response) {
this.historyData = response.data;
this.historyHistory = response.data.history;
},
showError: function(errorMsg, verbose) {
console.error(verbose);
this.errorMessages.push(errorMsg);
},
makeHistoryView: function(history) {
$(function() {
let options = {
hasMasthead: history.use_panels ? "true" : "false",
userIsOwner: history.user_is_owner ? "true" : "false",
isCurrent: history.history_is_current ? "true" : "false",
historyJSON: history.history,
showDeletedJson: history.show_deleted,
showHiddenJson: history.show_hidden,
initialModeDeleted: history.show_deleted ? "showing_deleted" : "not_showing_deleted",
initialModeHidden: history.show_hidden ? "showing_hidden" : "not_showing_hidden",
allowUserDatasetPurge: history.allow_user_dataset_purge ? "true" : "false"
};
options.viewToUse = options.userIsOwner
? { location: "mvc/history/history-view-edit", className: "HistoryViewEdit" }
: { location: "mvc/history/history-view", className: "HistoryView" };
HistoryView.historyEntry(options);
});
},
showStructure: function() {
let displayStructureInstance = Vue.extend(DisplayStructure),
mountView = document.createElement("div");
Galaxy.page.center.display(mountView);
new displayStructureInstance({ propsData: { id: QueryStringParsing.get("id") } }).$mount(mountView);
},
switchHistory: function() {
let url = Galaxy.root + "history/switch_to_history?hist_id=" + this.historyHistory["id"];
this.ajaxCall(url, this.reloadPage);
},
reloadPage: function() {
window.location.reload();
}
},
updated: function() {
this.makeHistoryView(this.historyData);
}
};
</script>
<style>
#history-view-controls {
flex: 0 0 44px;
background-color: white;
border-bottom: 1px solid #ddd;
width: 100%;
padding: 8px;
}
.history-panel > .controls .title {
font-size: 120%;
}
.history-panel > .controls .title input {
font-size: 100%;
}
a.btn {
text-decoration: none;
}
</style>
+37
View File
@@ -5,6 +5,9 @@ import userModel from "mvc/user/user-model";
import metricsLogger from "utils/metrics-logger";
import addLogging from "utils/add-logging";
import localize from "utils/localization";
/* global $ */
// TODO: move into a singleton pattern and have dependents import Galaxy
// ============================================================================
/** Base galaxy client-side application.
@@ -70,6 +73,9 @@ GalaxyApp.prototype._init = function __init(options, bootstrapped) {
self._initUser(options.user || {});
self.debug("GalaxyApp.user: ", self.user);
self._initUserLocale();
self.debug("currentLocale: ", sessionStorage.getItem("currentLocale"));
self._setUpListeners();
self.trigger("ready", self);
@@ -171,6 +177,37 @@ GalaxyApp.prototype._initLocale = function _initLocale(options) {
return self;
};
/** add the localize fn to this object and the window namespace (as '_l') */
GalaxyApp.prototype._initUserLocale = function _initUserLocale(options) {
var self = this;
// Choose best locale
var global_locale = self.config.default_locale ? self.config.default_locale.toLowerCase() : false;
var extra_user_preferences = {};
if (self.user && self.user.attributes.preferences && "extra_user_preferences" in self.user.attributes.preferences) {
extra_user_preferences = JSON.parse(self.user.attributes.preferences.extra_user_preferences);
}
var user_locale =
"localization|locale" in extra_user_preferences
? extra_user_preferences["localization|locale"].toLowerCase()
: false;
if (user_locale == "auto") {
user_locale = false;
}
var nav_locale =
typeof navigator === "undefined"
? "__root"
: (navigator.language || navigator.userLanguage || "__root").toLowerCase();
let locale = user_locale || global_locale || nav_locale;
sessionStorage.setItem("currentLocale", locale);
};
/** set up the current user as a Backbone model (mvc/user/user-model) */
GalaxyApp.prototype._initUser = function _initUser(userJSON) {
var self = this;
+4 -2
View File
@@ -1,7 +1,7 @@
import Utils from "utils/utils";
import Menu from "layout/menu";
import Scratchbook from "layout/scratchbook";
import QuotaMeter from "mvc/user/user-quotameter";
/** Masthead **/
var View = Backbone.View.extend({
initialize: function(options) {
@@ -62,7 +62,9 @@ var View = Backbone.View.extend({
var text = "";
self.collection.each(model => {
var q = model.get("onbeforeunload") && model.get("onbeforeunload")();
q && (text += `${q} `);
if (q) {
text += `${q} `;
}
});
if (text !== "") {
return text;
+4 -4
View File
@@ -105,15 +105,15 @@ var Collection = Backbone.Collection.extend({
//
// Webhooks
//
Webhooks.add({
url: "api/webhooks/masthead/all",
Webhooks.load({
type: "masthead",
callback: function(webhooks) {
$(document).ready(() => {
$.each(webhooks.models, (index, model) => {
webhooks.each(model => {
var webhook = model.toJSON();
if (webhook.activate) {
var obj = {
id: webhook.name,
id: webhook.id,
icon: webhook.config.icon,
url: webhook.config.url,
tooltip: webhook.config.tooltip,
+57 -33
View File
@@ -2,6 +2,7 @@ import Masthead from "layout/masthead";
import Panel from "layout/panel";
import Modal from "mvc/ui/ui-modal";
import Utils from "utils/utils";
var View = Backbone.View.extend({
el: "body",
className: "full-content",
@@ -14,7 +15,9 @@ var View = Backbone.View.extend({
message_box_content: "",
message_box_class: "info",
show_inactivity_warning: false,
inactivity_box_content: ""
inactivity_box_content: "",
hide_panels: false,
hide_masthead: false
});
// attach global objects, build mastheads
@@ -27,6 +30,9 @@ var View = Backbone.View.extend({
Utils.setWindowTitle();
view.allow_title_display = true;
}
if (view.active_tab) {
Utils.setActiveTab(view.active_tab);
}
self.center.display(view);
};
Galaxy.router = this.router = options.Router && new options.Router(self, options);
@@ -36,44 +42,62 @@ var View = Backbone.View.extend({
// build page template
this.$el.attr("scroll", "no");
this.$el.html(this._template());
this.$("#masthead").replaceWith(this.masthead.$el);
this.$("#center").append(this.center.$el);
this.$el.append(this.masthead.frame.$el);
this.$el.append(this.modal.$el);
this.$masthead = this.$("#masthead");
this.$center = this.$("#center");
this.$messagebox = this.$("#messagebox");
this.$inactivebox = this.$("#inactivebox");
// build components
if (this.config.hide_masthead) {
this.$masthead.remove();
this.$center.css("top", 0);
} else {
this.$masthead.replaceWith(this.masthead.$el);
}
this.$center.append(this.center.$el);
this.$el.append(this.masthead.frame.$el);
this.$el.append(this.modal.$el);
// build panels
this.panels = {};
_.each(this._panelids, panel_id => {
var panel_class_name = panel_id.charAt(0).toUpperCase() + panel_id.slice(1);
var panel_class = options[panel_class_name];
if (panel_class) {
var panel_instance = new panel_class(self, options);
self[panel_instance.toString()] = panel_instance;
self.panels[panel_id] = new Panel.SidePanel({
id: panel_id,
el: self.$(`#${panel_id}`),
view: panel_instance
});
}
});
if (!this.config.hide_panels) {
_.each(this._panelids, panel_id => {
var panel_class_name = panel_id.charAt(0).toUpperCase() + panel_id.slice(1);
var panel_class = options[panel_class_name];
if (panel_class) {
var panel_instance = new panel_class(self, options);
var panel_el = self.$(`#${panel_id}`);
self[panel_instance.toString()] = panel_instance;
self.panels[panel_id] = new Panel.SidePanel({
id: panel_id,
el: panel_el,
view: panel_instance
});
if (this.config.hide_masthead) {
panel_el.css("top", 0);
}
}
});
}
this.render();
// start the router
this.router &&
if (this.router) {
Backbone.history.start({
root: Galaxy.root,
pushState: true
});
}
},
render: function() {
// TODO: Remove this line after select2 update
$(".select2-hidden-accessible").remove();
this.masthead.render();
this.renderMessageBox();
this.renderInactivityBox();
if (!this.config.hide_masthead) {
this.masthead.render();
this.renderMessageBox();
this.renderInactivityBox();
}
this.renderPanels();
this._checkCommunicationServerOnline();
return this;
@@ -125,7 +149,7 @@ var View = Backbone.View.extend({
if (panel) {
panel.render();
} else {
self.$("#center").css(panel_id, 0);
self.$center.css(panel_id, 0);
self.$(`#${panel_id}`).hide();
}
});
@@ -135,16 +159,16 @@ var View = Backbone.View.extend({
/** body template */
_template: function() {
return [
'<div id="everything">',
'<div id="background"/>',
'<div id="masthead"/>',
'<div id="messagebox"/>',
'<div id="inactivebox" class="panel-warning-message" />',
'<div id="left" />',
'<div id="center" />',
'<div id="right" />',
"</div>",
'<div id="dd-helper" />'
`<div id="everything">
<div id="background"/>
<div id="masthead"/>
<div id="messagebox"/>
<div id="inactivebox" class="panel-warning-message" />
<div id="left" />
<div id="center" />
<div id="right" />
</div>
<div id="dd-helper" />`
].join("");
},
+11 -1
View File
@@ -218,13 +218,23 @@ export default Backbone.View.extend({
var $galaxy_main = $(window.parent.document).find("#galaxy_main");
if (options.target == "galaxy_main" || options.target == "center") {
if ($galaxy_main.length === 0) {
window.location = `${options.url + (options.url.indexOf("?") == -1 ? "?" : "&")}use_panels=True`;
window.location = this._build_url(options.url, { use_panels: true });
} else {
$galaxy_main.attr("src", options.url);
}
} else window.location = options.url;
} else {
options.url = this._build_url(options.url, { hide_panels: true, hide_masthead: true });
this.frames.add(options);
}
},
/** Url helper */
_build_url: function(url, options) {
if (url) {
url += url.indexOf("?") == -1 ? "?" : "&";
url += $.param(options, true);
return url;
}
}
});
@@ -1,5 +1,6 @@
import DATASET_MODEL from "mvc/dataset/dataset-model";
import BASE_MVC from "mvc/base-mvc";
import Utils from "utils/utils";
import _l from "utils/localization";
//==============================================================================
@@ -59,10 +60,17 @@ var DatasetCollectionElementMixin = {
/** merge the attributes of the sub-object 'object' into this model */
_mergeObject: function(attributes) {
// if we don't preserve and correct ids here, the element id becomes the object id
// and collision in backbone's _byId will occur and only
// Don't let the dataset ID replace the DCE's ID so record it as the
// element_id and when fetching dataset details below use the element_id
// instead of this.id.
const object = attributes.object;
let elementId = this.elementId;
if(object) {
elementId = attributes.object.id;
delete attributes.object.id;
}
_.extend(attributes, attributes.object, {
element_id: attributes.id
element_id: elementId
});
delete attributes.object;
return attributes;
@@ -122,7 +130,16 @@ var DatasetDCE = DATASET_MODEL.DatasetAssociation.extend(
// (a little silly since this api endpoint *also* points at hdas)
return `${Galaxy.root}api/datasets`;
}
return `${Galaxy.root}api/histories/${this.get("history_id")}/contents/${this.get("id")}`;
const datasetId = this._getDatasetId();
const url = `${Galaxy.root}api/histories/${this.get("history_id")}/contents/${datasetId}`;
return url;
},
_getDatasetId: function() {
// I'm a DCE acting as dataset, this URL needs to be the dataset URL so
// use element_id instead of id. See note above in _mergeObject and
// discussion on #3782 for more context.
return this.get("element_id");
},
defaults: _.extend(
@@ -280,16 +297,19 @@ var DatasetCollection = Backbone.Model.extend(BASE_MVC.LoggableMixin)
return parsed;
},
/** save this dataset, _Mark_ing it as deleted (just a flag) */
delete: function(options) {
/** save this collection, _Mark_ing it as deleted (just a flag) */
delete: function(recursive, purge, options) {
recursive = recursive || false;
purge = purge || false;
if (this.get("deleted")) {
return jQuery.when();
}
return this.save({ deleted: true }, options);
options = Utils.merge(options, { method: "delete" });
return this.save({ deleted: true, recursive: recursive, purge: purge }, options);
},
/** save this dataset, _Mark_ing it as undeleted */
/** save this collection, _Mark_ing it as undeleted */
undelete: function(options) {
if (!this.get("deleted") || this.get("purged")) {
if (!this.get("deleted")) {
return jQuery.when();
}
return this.save({ deleted: false }, options);
@@ -32,7 +32,7 @@ var CollectionView = _super.extend(
initialize: function(attributes) {
_super.prototype.initialize.call(this, attributes);
this.linkTarget = attributes.linkTarget || "_blank";
this.dragItems = true;
this.hasUser = attributes.hasUser;
/** A stack of panels that currently cover or hide this panel */
this.panelStack = [];
@@ -165,7 +165,6 @@ var DatasetListItemEdit = _super.extend(
href: "#",
faIcon: "fa-question",
onclick: function() {
var divString = `thdiv-${datasetID}`;
if (self.$el.find(".toolhelp").length > 0) {
self.$el.find(".toolhelp").toggle();
} else {
@@ -261,18 +260,22 @@ var DatasetListItemEdit = _super.extend(
return null;
}
var $visualizations = $(this.templates.visualizations(visualizations, this));
//HACK: need to re-write those directed at galaxy_main with linkTarget
$visualizations.find('[target="galaxy_main"]').attr("target", this.linkTarget);
// use addBack here to include the root $visualizations elem (for the case of 1 visualization)
this._addScratchBookFn($visualizations.find(".visualization-link").addBack(".visualization-link"));
return $visualizations;
if (visualizations.length >= 1) {
var url = Galaxy.root + "visualizations/dataset_id=" + this.model.get("id");
return $("<a/>")
.addClass("visualization-link icon-btn")
.attr("href", url)
.append($("<span/>").addClass("fa fa-bar-chart-o"))
.on("click", function(e) {
Galaxy.frame.add({ url: url, title: "Visualization" });
e.preventDefault();
});
}
},
/** add scratchbook functionality to visualization links */
_addScratchBookFn: function($links) {
var li = this;
$links.click(function(ev) {
$links.click(ev => {
if (Galaxy.frame && Galaxy.frame.active) {
Galaxy.frame.add({
title: _l("Visualization"),
@@ -302,7 +302,7 @@ var DatasetListItemView = _super.extend(
)}">
<span class="fa fa-floppy-o"></span>
</a>
<ul class="dropdown-menu" role="menu" aria-labelledby="dLabel">
<ul class="dropdown-menu" role="menu">
<li>
<a href="${urls.download}">
${_l("Download dataset")}
@@ -54,9 +54,13 @@ var DatasetAssociation = Backbone.Model.extend(BASE_MVC.LoggableMixin).extend(
this._setUpListeners();
},
_getDatasetId: function() {
return this.get("id");
},
/** returns misc. web urls for rendering things like re-run, display, etc. */
_generateUrls: function() {
var id = this.get("id");
const id = this._getDatasetId();
if (!id) {
return {};
}
+1 -1
View File
@@ -200,7 +200,7 @@ export var matchCase = (input, value) => {
* @param{dict} callback - Called with the mapped dictionary object and corresponding model node
*/
export var visitInputs = (inputs, callback, prefix, context) => {
context = $.extend(true, {}, context);
context = $.extend({}, context);
_.each(inputs, input => {
if (input && input.type && input.name) {
context[input.name] = input;
@@ -224,9 +224,7 @@ export default Backbone.Model.extend({
var self = this;
return new SelectGenomeSpace.View({
id: `field-${input_def.id}`,
onchange: function() {
self.app.trigger("change");
}
onchange: input_def.onchange
});
},
@@ -26,9 +26,8 @@ var View = Backbone.View.extend({
},
/** Add a new input element */
add: function(input) {
var input_def = jQuery.extend(true, {}, input);
input_def.id = input.id = Utils.uid();
add: function(input_def) {
input_def.id = Utils.uid();
this.app.input_list[input_def.id] = input_def;
switch (input_def.type) {
case "conditional":
+2 -5
View File
@@ -5,8 +5,7 @@ import Portlet from "mvc/ui/ui-portlet";
import Ui from "mvc/ui/ui-misc";
import FormSection from "mvc/form/form-section";
import FormData from "mvc/form/form-data";
export var View = Backbone.View.extend({
export default Backbone.View.extend({
initialize: function(options) {
this.model = new Backbone.Model({
initial_errors: false,
@@ -81,7 +80,7 @@ export var View = Backbone.View.extend({
.first();
$panel.animate(
{
scrollTop: $panel.scrollTop() + input_element.$el.offset().top - 120
scrollTop: $panel.scrollTop() + input_element.$el.offset().top - $panel.position().top - 120
},
500
);
@@ -174,5 +173,3 @@ export var View = Backbone.View.extend({
Galaxy.emit.debug("form-view::initialize()", "Completed");
}
});
export default View;
@@ -1,9 +1,11 @@
/** This class renders the grid list with shared section. */
import GridView from "mvc/grid/grid-view";
import LoadingIndicator from "ui/loading-indicator";
var View = Backbone.View.extend({
initialize: function(options) {
var self = this;
this.setElement($("<div/>"));
LoadingIndicator.markViewAsLoading(this);
this.model = new Backbone.Model(options);
this.item = this.model.get("item");
this.title = this.model.get("plural");
@@ -92,8 +92,10 @@ export default {
for (let column of options.columns) {
if (column.visible) {
tmpl += `<th id="${column.key}-header">`;
if (column.href) {
tmpl += `<a href="${column.href}" class="sort-link" sort_key="${column.key}">${column.label}</a>`;
if (column.sortable) {
tmpl += `<a href="javascript:void(0)" class="sort-link" sort_key="${column.key}">${
column.label
}</a>`;
} else {
tmpl += column.label;
}
+6 -2
View File
@@ -7,6 +7,8 @@ import Utils from "utils/utils";
import GridModel from "mvc/grid/grid-model";
import Templates from "mvc/grid/grid-template";
import PopupMenu from "mvc/ui/popup-menu";
import LoadingIndicator from "ui/loading-indicator";
// grid view
export default Backbone.View.extend({
// model
@@ -16,6 +18,7 @@ export default Backbone.View.extend({
initialize: function(grid_config) {
this.grid = new GridModel();
this.title = grid_config.title;
this.active_tab = grid_config.active_tab;
var self = this;
window.add_tag_to_grid_filter = (tag_name, tag_value) => {
// Put tag name and value together.
@@ -28,9 +31,8 @@ export default Backbone.View.extend({
self.add_filter_condition("tags", tag);
};
// set element
this.setElement("<div/>");
if (grid_config.url_base && !grid_config.items) {
LoadingIndicator.markViewAsLoading(this);
var url_data = grid_config.url_data || {};
_.each(grid_config.filters, (v, k) => {
url_data[`f-${k}`] = v;
@@ -44,6 +46,8 @@ export default Backbone.View.extend({
}
});
} else {
// set element
this.setElement("<div>");
this.init_grid(grid_config);
}
@@ -11,6 +11,13 @@ var HDCAListItemEdit = _super.extend(
/** @lends HDCAListItemEdit.prototype */ {
/** logger used to record this.log messages, commonly set to console */
//logger : console,
/** set up: options */
initialize: function(attributes) {
_super.prototype.initialize.call(this, attributes);
/** allow user purge of dataset files? */
this.purgeAllowed = attributes.purgeAllowed || false;
},
/** Override to return editable versions of the collection panels */
_getFoldoutPanelClass: function() {
@@ -25,22 +32,45 @@ var HDCAListItemEdit = _super.extend(
return _super.prototype._renderPrimaryActions.call(this).concat([this._renderDeleteButton()]);
},
/** Render icon-button to delete this collection. */
_renderDeleteButton: function() {
var deleted = this.model.get("deleted");
return faIconButton({
title: deleted ? _l("Dataset collection is already deleted") : _l("Delete"),
classes: "delete-btn",
faIcon: "fa-times",
disabled: deleted,
onclick: () => {
// ...bler... tooltips being left behind in DOM (hover out never called on deletion)
this.$el.find(".icon-btn.delete-btn").trigger("mouseout");
this.model["delete"]();
}
});
return $(`
<div class="dropdown">
<a class="delete-btn icon-btn" title="${_l("Delete")}" data-toggle="dropdown">
<span class="fa fa-times"></span>
</a>
<ul class="dropdown-menu pull-right" role="menu">
<li>
<a href="#" class="delete-collection">
${_l("Collection Only")}
</a>
</li>
<li>
<a href="#" class="delete-collection-and-datasets">
${_l("Delete Datasets")}
</a>
</li>
<li style="display: ${this.purgeAllowed ? "inherit" : "none"}">
<a href="#" class="delete-collection-and-purge-datasets">
${_l("Permanently Delete Datasets")}
</a>
</li>
</ul>
</div>`);
},
// ......................................................................... misc
events: _.extend(_.clone(_super.prototype.events), {
"click .delete-collection": function(ev) {
this.model["delete"]();
},
"click .delete-collection-and-datasets": function(ev) {
this.model["delete"](true);
},
"click .delete-collection-and-purge-datasets": function(ev) {
this.model["delete"](true, true);
}
}),
// ......................................................................... misc
/** string rep */
toString: function() {
@@ -5,11 +5,13 @@ import HISTORY_PREFS from "mvc/history/history-preferences";
import JOB_STATES_MODEL from "mvc/history/job-states-model";
import BASE_MVC from "mvc/base-mvc";
import AJAX_QUEUE from "utils/ajax-queue";
import * as _ from "libs/underscore";
import * as Backbone from "libs/backbone";
var limitPerPageDefault = 500;
try {
limitPerPageDefault = localStorage.getItem("historyContentsLimitPerPageDefault") || limitPerPageDefault;
} catch (err) {}
/* global Galaxy */
/* global jQuery */
const limitPerPageDefault = window.localStorage.getItem("historyContentsLimitPerPageDefault") || 500;
//==============================================================================
var _super = CONTROLLED_FETCH_COLLECTION.PaginatedCollection;
@@ -72,7 +74,7 @@ var HistoryContents = _super.extend(BASE_MVC.LoggableMixin).extend({
if (historyContent.attributes.history_content_type === "dataset_collection") {
var jobSourceType = historyContent.attributes.job_source_type;
var jobSourceId = historyContent.attributes.job_source_id;
if (jobSourceType) {
if (jobSourceType && this.jobStateSummariesCollection) {
this.jobStateSummariesCollection.add({
id: jobSourceId,
model: jobSourceType,
@@ -314,15 +316,14 @@ var HistoryContents = _super.extend(BASE_MVC.LoggableMixin).extend({
/** fetch all the visible==false contents of this collection */
fetchHidden: function(options) {
options = options || {};
var self = this;
options.filters = _.extend(options.filters, {
visible: false
});
options.remove = false;
self.trigger("fetching-hidden", self);
return self.fetch(options).always(() => {
self.trigger("fetching-hidden-done", self);
this.trigger("fetching-hidden", this);
return this.fetch(options).always(() => {
this.trigger("fetching-hidden-done", this);
});
},
@@ -338,17 +339,16 @@ var HistoryContents = _super.extend(BASE_MVC.LoggableMixin).extend({
// TODO: to batch
/** helper that fetches using filterParams then calls save on each fetched using updateWhat as the save params */
_filterAndUpdate: function(filterParams, updateWhat) {
var self = this;
var idAttribute = self.model.prototype.idAttribute;
var idAttribute = this.model.prototype.idAttribute;
var updateArgs = [updateWhat];
return self.fetch({ filters: filterParams, remove: false }).then(fetched => {
return this.fetch({ filters: filterParams, remove: false }).then(fetched => {
// convert filtered json array to model array
fetched = fetched.reduce((modelArray, currJson, i) => {
var model = self.get(currJson[idAttribute]);
var model = this.get(currJson[idAttribute]);
return model ? modelArray.concat(model) : modelArray;
}, []);
return self.ajaxQueue("save", updateArgs, fetched);
return this.ajaxQueue("save", updateArgs, fetched);
});
},
@@ -366,43 +366,44 @@ var HistoryContents = _super.extend(BASE_MVC.LoggableMixin).extend({
).deferred;
},
_recursivelyFetch: function(options, detailKeys, deferred, limit, offset) {
offset = offset || 0;
var _options = _.extend(_.clone(options), {
view: "summary",
keys: detailKeys,
limit: limit,
offset: offset,
reset: offset === 0,
remove: false
});
_.defer(() => {
this.fetch
.call(this, _options)
.fail(deferred.reject)
.done(response => {
deferred.notify(response, limit, offset);
if (response.length !== limit) {
this.allFetched = true;
deferred.resolve(response, limit, offset);
} else {
this._recursivelyFetch(options, detailKeys, deferred, limit, offset + limit);
}
});
});
},
/** fetch contents' details in batches of limitPerCall - note: only get searchable details here */
progressivelyFetchDetails: function(options) {
options = options || {};
var deferred = jQuery.Deferred();
var self = this;
var limit = options.limitPerCall || self.limitPerProgressiveFetch;
// TODO: only fetch tags and annotations if specifically requested
var searchAttributes = HDA_MODEL.HistoryDatasetAssociation.prototype.searchAttributes;
var detailKeys = searchAttributes.join(",");
function _recursivelyFetch(offset) {
offset = offset || 0;
var _options = _.extend(_.clone(options), {
view: "summary",
keys: detailKeys,
limit: limit,
offset: offset,
reset: offset === 0,
remove: false
});
_.defer(() => {
self.fetch
.call(self, _options)
.fail(deferred.reject)
.done(response => {
deferred.notify(response, limit, offset);
if (response.length !== limit) {
self.allFetched = true;
deferred.resolve(response, limit, offset);
} else {
_recursivelyFetch(offset + limit);
}
});
});
}
_recursivelyFetch();
options = options || {};
let deferred = jQuery.Deferred();
this._recursivelyFetch(
options,
HDA_MODEL.HistoryDatasetAssociation.prototype.searchAttributes.join(","),
deferred,
options.limitPerCall || this.limitPerProgressiveFetch
);
return deferred;
},
@@ -1,11 +1,61 @@
import _l from "utils/localization";
/** This class renders the grid list. */
import AjaxQueue from "utils/ajax-queue";
import Utils from "utils/utils";
/** This class renders the grid list. */
import GridView from "mvc/grid/grid-view";
import HistoryModel from "mvc/history/history-model";
import historyCopyDialog from "mvc/history/copy-dialog";
import LoadingIndicator from "ui/loading-indicator";
var HistoryGridView = GridView.extend({
initialize: function(grid_config) {
this.ajaxQueue = new AjaxQueue.AjaxQueue();
GridView.prototype.initialize.call(this, grid_config);
},
init_grid_elements: function() {
const ajaxQueue = this.ajaxQueue;
ajaxQueue.stop();
GridView.prototype.init_grid_elements.call(this);
const fetchDetails = $.makeArray(
this.$el.find(".delayed-value-datasets_by_state").map((i, el) => {
return () => {
const historyId = $(el).data("history-id");
const url = `${
Galaxy.root
}api/histories/${historyId}?keys=nice_size,contents_active,contents_states`;
const options = {};
options.url = url;
options.type = "GET";
options.success = req => {
const contentsStates = req["contents_states"];
let stateHtml = "";
for (let state of ["ok", "running", "queued", "new", "error"]) {
const stateCount = contentsStates[state];
if (stateCount) {
stateHtml += `<div class="count-box state-color-${state}" title="Datasets in ${state} state">${stateCount}</div> `;
}
}
const contentsActive = req["contents_active"];
const deleted = contentsActive["deleted"];
if (deleted) {
stateHtml += `<div class="count-box state-color-deleted" title="Deleted datasets">${deleted}</div> `;
}
const hidden = contentsActive["hidden"];
if (hidden) {
stateHtml += `<div class="count-box state-color-hidden" title="Hidden datasets">${hidden}</div> `;
}
$(`.delayed-value-datasets_by_state[data-history-id='${historyId}']`).html(stateHtml);
$(`.delayed-value-disk_size[data-history-id='${historyId}']`).html(req["nice_size"]);
};
var xhr = jQuery.ajax(options);
return xhr;
};
})
);
fetchDetails.forEach(fn => ajaxQueue.add(fn));
ajaxQueue.start();
},
_showCopyDialog: function(id) {
var history = new HistoryModel.History({ id: id });
history
@@ -55,7 +105,8 @@ var View = Backbone.View.extend({
title: _l("Histories"),
initialize: function(options) {
var self = this;
this.setElement($("<div/>"));
LoadingIndicator.markViewAsLoading(this);
this.model = new Backbone.Model();
Utils.get({
url: `${Galaxy.root}history/${options.action_id}?${$.param(Galaxy.params)}`,
@@ -275,7 +275,11 @@ var HistoryViewEdit = _super.extend(
func: function() {
if (confirm(_l("This will permanently remove the data in your datasets. Are you sure?"))) {
var action = HDA_MODEL.HistoryDatasetAssociation.prototype.purge;
panel.getSelectedModels().ajaxQueue(action);
const historyContents = panel.getSelectedModels();
const selectedDatasets = historyContents.filter(
c => c.get("history_content_type") == "dataset"
);
historyContents.ajaxQueue(action, {}, selectedDatasets);
}
}
});
@@ -1,15 +1,21 @@
import * as _ from "underscore";
import _l from "utils/localization";
import LIST_VIEW from "mvc/list/list-view";
import HISTORY_MODEL from "mvc/history/history-model";
import HISTORY_CONTENTS from "mvc/history/history-contents";
import HISTORY_PREFS from "mvc/history/history-preferences";
import History from "mvc/history/history-model";
import HDA_LI from "mvc/history/hda-li";
import HDCA_LI from "mvc/history/hdca-li";
import USER from "mvc/user/user-model";
import ERROR_MODAL from "mvc/ui/error-modal";
import faIconButton from "ui/fa-icon-button";
import BASE_MVC from "mvc/base-mvc";
import _l from "utils/localization";
import HistoryViewEdit from "mvc/history/history-view-edit";
import HistoryCopyDialog from "mvc/history/copy-dialog";
import "ui/search-input";
import "ui/mode-button";
/* global $ */
/* global Galaxy */
/* =============================================================================
TODO:
@@ -413,7 +419,7 @@ var HistoryView = _super.extend(
this.$(inputSelector).searchInput("toggle-loading");
// set this now so that only results will show during progress
this.searchFor = searchFor;
var xhr = this.model.contents
this.model.contents
.progressivelyFetchDetails({ silent: true })
.progress((response, limit, offset) => {
this.renderItems();
@@ -611,7 +617,91 @@ HistoryView.prototype.templates = (() => {
});
})();
export function historyEntry(options) {
$("#toggle-deleted").modeButton({
initialMode: options.initialModeDeleted,
modes: [
{ mode: "showing_deleted", html: _l("Exclude deleted") },
{ mode: "not_showing_deleted", html: _l("Include deleted") }
]
});
$("#toggle-hidden").modeButton({
initialMode: options.initialModeHidden,
modes: [
{ mode: "showing_hidden", html: _l("Exclude hidden") },
{ mode: "not_showing_hidden", html: _l("Include hidden") }
]
});
$("#switch").click(function() {
//##HACK:ity hack hack
//##TODO: remove when out of iframe
var hview =
Galaxy.currHistoryPanel || (window.top.Galaxy && window.top.Galaxy.currHistoryPanel)
? window.top.Galaxy.currHistoryPanel
: null;
if (hview) {
hview.switchToHistory("${ history[ 'id' ] }");
} else {
window.location = "${ switch_to_url }";
}
});
// use_panels effects where the the center_panel() is rendered:
// w/o it renders to the body, w/ it renders to #center - we need to adjust a few things for scrolling to work
if (options.hasMasthead) {
$("#center").addClass("flex-vertical-container");
}
let viewClass = options.userIsOwner ? HistoryViewEdit.HistoryViewEdit : HistoryView.HistoryView;
let historyModel = new History.History(options.historyJSON);
// attach the copy dialog to the import button now that we have a history
$("#import").click(function() {
HistoryCopyDialog(historyModel, {
useImport: true,
// use default datasets option to match the toggle-deleted button
allDatasets: $("#toggle-deleted").modeButton("getMode").mode === "showing_deleted"
}).done(function() {
if (window === window.parent) {
window.location = Galaxy.root;
} else if (Galaxy.currHistoryPanel) {
Galaxy.currHistoryPanel.loadCurrentHistory();
}
});
});
let historyView = new viewClass({
el: $("#history-" + options.historyJSON.id),
className: viewClass.prototype.className + " wide",
$scrollContainer: options.hasMasthead
? function() {
return this.$el.parent();
}
: undefined,
model: historyModel,
show_deleted: options.showDeletedJson,
show_hidden: options.showHiddenJson,
purgeAllowed: options.allow_user_dataset_purge
});
historyView.trigger("loading");
historyModel
.fetchContents({ silent: true })
.fail(function() {
alert("Galaxy history failed to load");
})
.done(function() {
historyView.trigger("loading-done");
historyView.render();
});
$("#toggle-deleted").on("click", function() {
historyView.toggleShowDeleted();
});
$("#toggle-hidden").on("click", function() {
historyView.toggleShowHidden();
});
}
//==============================================================================
export default {
HistoryView: HistoryView
HistoryView: HistoryView,
historyEntry: historyEntry
};
@@ -8,7 +8,6 @@ import historyCopyDialog from "mvc/history/copy-dialog";
import ERROR_MODAL from "mvc/ui/error-modal";
import baseMVC from "mvc/base-mvc";
import ajaxQueue from "utils/ajax-queue";
import "ui/mode-button";
import "ui/search-input";
/* global $ */
@@ -1,9 +1,12 @@
import * as _ from "underscore";
import _l from "utils/localization";
import PopupMenu from "mvc/ui/popup-menu";
import historyCopyDialog from "mvc/history/copy-dialog";
import BASE_MVC from "mvc/base-mvc";
import _l from "utils/localization";
import Webhooks from "mvc/webhooks";
/* global $ */
/* global Galaxy */
// ============================================================================
var menu = [
{
@@ -47,8 +50,12 @@ var menu = [
},
{
html: _l("Show Structure"),
href: "history/display_structured",
anon: true
anon: true,
func: function() {
if (Galaxy && Galaxy.currHistoryPanel && Galaxy.router) {
Galaxy.router.push(`/histories/show_structure`);
}
}
},
{
html: _l("Extract Workflow"),
@@ -175,13 +182,13 @@ var menu = [
];
// Webhooks
Webhooks.add({
url: "api/webhooks/history-menu/all",
Webhooks.load({
type: "history-menu",
async: false, // (hypothetically) slows down the performance
callback: function(webhooks) {
var webhooks_menu = [];
$.each(webhooks.models, (index, model) => {
webhooks.each(model => {
var webhook = model.toJSON();
if (webhook.activate) {
webhooks_menu.push({
@@ -220,6 +227,7 @@ function buildMenu(isAnon, purgeAllowed, urlRoot) {
if (menuOption.confirm) {
menuOption.func = () => {
if (confirm(menuOption.confirm)) {
/* galaxy_main is a global here: TODO: Fix it! */
galaxy_main.location = menuOption.href;
}
};
+1 -1
View File
@@ -791,7 +791,7 @@ var ListPanel = Backbone.View.extend(BASE_MVC.LoggableMixin).extend(
this.debug("_showLoadingIndicator", this.indicator, msg, speed, callback);
speed = speed !== undefined ? speed : this.fxSpeed;
if (!this.indicator) {
this.indicator = new LoadingIndicator(this.$el);
this.indicator = new LoadingIndicator.LoadingIndicator(this.$el);
this.debug("\t created", this.indicator);
}
if (!this.$el.is(":visible")) {
@@ -6,6 +6,7 @@ import Utils from "utils/utils";
import Deferred from "utils/deferred";
import Ui from "mvc/ui/ui-misc";
import FormBase from "mvc/form/form-view";
import Webhooks from "mvc/webhooks";
import Citations from "components/Citations.vue";
import Vue from "vue";
export default FormBase.extend({
@@ -200,19 +201,23 @@ export default FormBase.extend({
}
// add tool menu webhooks
$.getJSON("/api/webhooks/tool-menu/all", webhooks => {
_.each(webhooks, webhook => {
if (webhook.activate && webhook.config.function) {
menu_button.addMenu({
icon: webhook.config.icon,
title: webhook.config.title,
onclick: function() {
var func = new Function("options", webhook.config.function);
func(options);
}
});
}
});
Webhooks.load({
type: "tool-menu",
callback: function(webhooks) {
webhooks.each(model => {
var webhook = model.toJSON();
if (webhook.activate && webhook.config.function) {
menu_button.addMenu({
icon: webhook.config.icon,
title: webhook.config.title,
onclick: function() {
var func = new Function("options", webhook.config.function);
func(options);
}
});
}
});
}
});
return {
@@ -25,18 +25,6 @@ var View = Backbone.View.extend({
$("body").append(this.$el);
this._configure();
this.render();
$(window).resize(() => {
self._refresh();
});
},
/** Refresh height of scrollable div below header, handle scrolling by lazy loading steps */
_refresh: function(step_index) {
var margin =
_.reduce(this.$el.children(), (memo, child) => memo + $(child).outerHeight(), 0) -
this.$steps.height() +
90;
this.$steps.css("height", $(window).height() - margin);
},
/** Configures form/step options for each workflow step */
@@ -432,7 +420,6 @@ var View = Backbone.View.extend({
}
self.forms[step.index] = form;
self._append(self.$steps, form.$el);
self._refresh();
step.needs_refresh && self._refreshStep(step);
form.portlet[!self.show_progress ? "enable" : "disable"]();
self.show_progress &&
@@ -598,7 +585,7 @@ var View = Backbone.View.extend({
if ($.isArray(response) && response.length > 0) {
self.$el.append($("<div/>", { id: "webhook-view" }));
var WebhookApp = new Webhooks.WebhookView({
urlRoot: `${Galaxy.root}api/webhooks/workflow`,
type: "workflow",
toolId: job_def.tool_id,
toolVersion: job_def.tool_version
});
+1 -1
View File
@@ -227,7 +227,7 @@ var View = Backbone.View.extend({
if (response.jobs && response.jobs.length > 0) {
self.$el.append($("<div/>", { id: "webhook-view" }));
var WebhookApp = new Webhooks.WebhookView({
urlRoot: `${Galaxy.root}api/webhooks/tool`,
type: "tool",
toolId: job_def.tool_id
});
}
@@ -8,11 +8,12 @@ export default {
var newWin = window.open(GS_UPLOAD_URL, "GenomeSpace File Browser", "height=360px,width=600px");
successCalBack = options["successCallback"];
window.addEventListener(
"message",
e => {
successCalBack(e.data);
if (options.successCallback && e.data.destination) {
options.successCallback(e.data);
}
},
false
);
@@ -414,9 +414,20 @@ var View = Backbone.View.extend({
var field = this.fields[current];
var drop_data = JSON.parse(ev.originalEvent.dataTransfer.getData("text"))[0];
var new_id = drop_data.id;
var new_src = drop_data.history_content_type == "dataset" ? "hda" : "hdca";
var new_src = drop_data.history_content_type == "dataset_collection" ? "hdca" : "hda";
var new_value = { id: new_id, src: new_src };
if (data && _.findWhere(data[new_src], new_value)) {
if (data && drop_data.history_id) {
if (!_.findWhere(data[new_src], new_value)) {
data[new_src].push({
id: new_id,
src: new_src,
hid: drop_data.hid || "Dropped",
name: drop_data.hid ? drop_data.name : new_id,
keep: true,
tags: []
});
this._changeData();
}
if (config.src == new_src) {
var current_value = field.value();
if (current_value && config.multiple) {
@@ -11,6 +11,7 @@ var View = Backbone.View.extend({
initialize: function(options) {
// link this
var self = this;
this.options = options;
// create insert new list element button
this.browse_button = new Ui.ButtonIcon({
@@ -18,23 +19,17 @@ var View = Backbone.View.extend({
icon: "fa fa-sign-in",
tooltip: _l("Browse GenomeSpace"),
onclick: function() {
self.browseGenomeSpace();
self.browseGenomeSpace(options);
}
});
// create genomespace filepath textbox
this.filename_textbox = new Ui.Input();
// create genomespace token textbox
this.token_textbox = new Ui.Input({
type: "password"
});
// create elements
this.setElement(this._template(options));
this.$(".ui-gs-browse-button").append(this.browse_button.$el);
this.$(".ui-gs-filename-textbox").append(this.filename_textbox.$el);
this.$(".ui-gs-token-textbox").append(this.token_textbox.$el);
},
/** Browse GenomeSpace */
@@ -42,7 +37,7 @@ var View = Backbone.View.extend({
var self = this;
GenomespaceBrowser.openFileBrowser({
successCallback: function(data) {
self.value(`${data.destination}^${data.token}`);
self.value(data.destination);
}
});
},
@@ -55,15 +50,11 @@ var View = Backbone.View.extend({
'<span class="ui-gs-browse-button" />' +
'<span class="ui-gs-filename-textbox" />' +
"</div>" +
'<div class="ui-gs-token-field">' +
'<span class=ui-gs-label"><div class="ui-gs-token-label">Token</div></span>' +
'<span class="ui-gs-token-textbox" />' +
"</div>" +
"</div>"
);
},
/** Return/Set currently selected genomespace filename/token */
/** Return/Set currently selected genomespace filename */
value: function(new_value) {
// check if new_value is defined
if (new_value !== undefined) {
@@ -75,16 +66,15 @@ var View = Backbone.View.extend({
// get value
_getValue: function() {
return `${this.filename_textbox.value()}^${this.token_textbox.value()}`;
return this.filename_textbox.value();
},
// set value
_setValue: function(new_value) {
if (new_value) {
values = new_value.split("^");
this.filename_textbox.value(values[0]);
this.token_textbox.value(values[1]);
this.filename_textbox.value(new_value);
}
this.options.onchange && this.options.onchange(new_value);
}
});
+21 -16
View File
@@ -1,8 +1,7 @@
/** This class renders the selection grid. */
import Utils from "utils/utils";
import Tabs from "mvc/ui/ui-tabs";
export var View = Backbone.View.extend({
var View = Backbone.View.extend({
events: {
"click .ui-thumbnails-item": "_onclick",
"dblclick .ui-thumbnails-item": "_ondblclick"
@@ -21,15 +20,15 @@ export var View = Backbone.View.extend({
render: function() {
this.first = null;
this.tabs.delAll();
this._renderDefault();
this._renderRegular();
this._renderList();
},
_renderDefault: function() {
_renderRegular: function() {
var title_length = 20;
var $el = $("<div/>").addClass("ui-thumbnails-grid");
this.collection.each(model => {
if (model.get("keywords").indexOf("default") !== -1) {
if (model.get("regular")) {
var title = model.get("title");
$el.append(
$(
@@ -94,6 +93,9 @@ export var View = Backbone.View.extend({
.closest(".ui-thumbnails-item")
.attr("value")
);
if (this.model.get("onclick")) {
this.model.get("onclick")(this.value());
}
},
/** Add double click handler */
@@ -103,27 +105,30 @@ export var View = Backbone.View.extend({
}
},
/** Image/Icon template */
_templateImage: function(options) {
if (options.image_src) {
return `<img class="ui-thumbnails-image" src="${options.image_src}"/>`;
}
return `<div class="ui-thumbnails-icon fa fa-eye"/>`;
},
/* Thumbnail template with image */
_templateThumbnailItem: function(options) {
return `<div class="ui-thumbnails-item ui-thumbnails-item-float" value="${
options.id
}"><img class="ui-thumbnails-image" src="${
options.image_src
}"><div class="ui-thumbnails-title ui-form-info"><span class="fa ${options.title_icon}"/>${
return `<div class="ui-thumbnails-item ui-thumbnails-item-float" value="${options.id}">${this._templateImage(
options
)}<div class="ui-thumbnails-title ui-form-info"><span class="fa ${options.title_icon}"/>${
options.title
}</div><div>`;
},
/* Thumbnail template with image and description */
_templateRegularItem: function(options) {
return `<div class="ui-thumbnails-item" value="${
options.id
}"><table><tr><td><img class="ui-thumbnails-image" src="${
options.image_src
}"></td><td><div class="ui-thumbnails-description-title ui-form-info">${
return `<div class="ui-thumbnails-item" value="${options.id}"><table><tr><td>${this._templateImage(
options
)}</td><td><div class="ui-thumbnails-description-title ui-form-info">${
options.title
}</div><div class="ui-thumbnails-description-text ui-form-info">${options.description}</div></td></tr><div>`;
}
});
export default { View: View };
@@ -116,7 +116,7 @@ export default Backbone.View.extend({
// file upload
this.uploadbox = this.$(".upload-box").uploadbox({
url: this.app.options.nginx_upload_path,
url: this.app.options.upload_path,
announce: function(index, file) {
self._eventAnnounce(index, file);
},
@@ -133,7 +133,7 @@ export default Backbone.View.extend({
});
});
$.uploadpost({
url: this.app.options.nginx_upload_path,
url: this.app.options.upload_path,
data: this.app.toData(this.collection.filter()),
success: function(message) {
self._eventSuccess(message);
@@ -11,6 +11,7 @@ export default Backbone.View.extend({
init: "upload-icon-button fa fa-trash-o",
queued: "upload-icon fa fa-spinner fa-spin",
running: "upload-icon fa fa-spinner fa-spin",
warning: "upload-icon fa fa-spinner fa-spin",
success: "upload-icon-button fa fa-check",
error: "upload-icon-button fa fa-exclamation-triangle"
},
@@ -18,6 +19,7 @@ export default Backbone.View.extend({
initialize: function(app, options) {
var self = this;
this.app = app;
this.list_extensions = app.list_extensions;
this.model = options.model;
this.setElement(this._template(options.model));
this.$mode = this.$(".upload-mode");
@@ -57,7 +59,7 @@ export default Backbone.View.extend({
// create select extension
this.select_extension = new Select.View({
css: "upload-extension",
data: self.app.list_extensions,
data: _.filter(this.list_extensions, ext => !ext.composite_files),
container: this.$(".upload-extension"),
value: default_extension,
onchange: function(extension) {
@@ -183,7 +185,7 @@ export default Backbone.View.extend({
_refreshInfo: function() {
var info = this.model.get("info");
if (info) {
this.$info_text.html(`<strong>Failed: </strong>${info}`).show();
this.$info_text.html(`<strong>Warning: </strong>${info}`).show();
} else {
this.$info_text.hide();
}
@@ -213,13 +215,17 @@ export default Backbone.View.extend({
this.select_genome.disable();
this.select_extension.disable();
}
this.$info_progress.show();
this.$el.removeClass().addClass("upload-row");
if (status == "success") {
this.$el.addClass("success");
this.$percentage.html("100%");
}
if (status == "error") {
} else if (status == "error") {
this.$el.addClass("danger");
this.$info_progress.hide();
} else if (status == "warning") {
this.$el.addClass("warning");
this.$info_progress.hide();
}
},
@@ -104,7 +104,7 @@ export default Backbone.View.extend({
// file upload
this.uploadbox = this.$uploadbox.uploadbox({
url: this.app.options.nginx_upload_path,
url: this.app.options.upload_path,
announce: function(index, file) {
self._eventAnnounce(index, file);
},
@@ -120,6 +120,9 @@ export default Backbone.View.extend({
error: function(index, message) {
self._eventError(index, message);
},
warning: function(index, message) {
self._eventWarning(index, message);
},
complete: function() {
self._eventComplete();
},
@@ -247,7 +250,7 @@ export default Backbone.View.extend({
/** Progress */
_eventProgress: function(index, percentage) {
var it = this.collection.get(index);
it.set("percentage", percentage);
it.set({ percentage: percentage, status: "running", info: "" });
this.ui_button.model.set("percentage", this._uploadPercentage(percentage, it.get("file_size")));
},
@@ -263,6 +266,12 @@ export default Backbone.View.extend({
Galaxy.currHistoryPanel.refreshContents();
},
/** Warning */
_eventWarning: function(index, message) {
var it = this.collection.get(index);
it.set({ status: "warning", info: message });
},
/** Error */
_eventError: function(index, message) {
var it = this.collection.get(index);
@@ -363,7 +372,10 @@ export default Backbone.View.extend({
this._uploadFtp();
// queue remaining files
this.uploadbox.start();
this.uploadbox.start({
id: Galaxy.user.id,
chunk_upload_size: this.app.options.chunk_upload_size
});
this.render();
}
},
@@ -430,7 +442,7 @@ export default Backbone.View.extend({
if (list.length > 0) {
$.uploadpost({
data: this.app.toData(list),
url: this.app.options.nginx_upload_path,
url: this.app.options.upload_path,
success: function(message) {
_.each(list, model => {
self._eventSuccess(model.id);
@@ -452,37 +464,37 @@ export default Backbone.View.extend({
/** Template */
_template: function() {
return (
'<div class="upload-view-default">' +
'<div class="upload-top">' +
'<h6 class="upload-top-info"/>' +
"</div>" +
'<div class="upload-box">' +
'<div class="upload-helper"><i class="fa fa-files-o"/>Drop files here</div>' +
'<table class="upload-table 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 class="upload-footer">' +
'<span class="upload-footer-title">Type (set all):</span>' +
'<span class="upload-footer-extension"/>' +
'<span class="upload-footer-extension-info upload-icon-button fa fa-search"/> ' +
'<span class="upload-footer-title">Genome (set all):</span>' +
'<span class="upload-footer-genome"/>' +
"</div>" +
'<div class="upload-buttons"/>' +
"</div>"
);
return `<div class="upload-view-default">
<div class="upload-top">
<h6 class="upload-top-info"/>
</div>
<div class="upload-box">
<div class="upload-helper">
<i class="fa fa-files-o"/>Drop files here
</div>
<table class="upload-table 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 class="upload-footer">
<span class="upload-footer-title">Type (set all):</span>
<span class="upload-footer-extension"/>
<span class="upload-footer-extension-info upload-icon-button fa fa-search"/>
<span class="upload-footer-title">Genome (set all):</span>
<span class="upload-footer-genome"/>
</div>
<div class="upload-buttons"/>
</div>`;
}
});
@@ -9,7 +9,6 @@ import UploadViewComposite from "mvc/upload/composite/composite-view";
import UploadViewCollection from "mvc/upload/collection/collection-view";
export default Backbone.View.extend({
options: {
nginx_upload_path: "",
ftp_upload_site: "n/a",
default_genome: "?",
default_extension: "auto",
@@ -0,0 +1,51 @@
import Deferred from "utils/deferred";
import Modal from "mvc/ui/ui-modal";
import Ui from "mvc/ui/ui-misc";
import Chart from "mvc/visualization/chart/components/model";
import Editor from "mvc/visualization/chart/views/editor";
import Viewer from "mvc/visualization/chart/views/viewer";
import Menu from "mvc/visualization/chart/views/menu";
import * as Backbone from "backbone";
/* global $ */
/* global Galaxy */
export default Backbone.View.extend({
initialize: function(options) {
this.modal = (window.parent.Galaxy && window.parent.Galaxy.modal) || new Modal.View();
this.setElement(
$("<div/>")
.addClass("charts-client")
.append($("<div/>").addClass("charts-buttons"))
.append($("<div/>").addClass("charts-center"))
.append($("<div/>").addClass("charts-right"))
);
this.$center = this.$(".charts-center");
this.$right = this.$(".charts-right");
this.$buttons = this.$(".charts-buttons");
this.chart = new Chart({}, options);
this.chart.plugin = options.visualization_plugin;
this.chart.plugin.specs = this.chart.plugin.specs || {};
this.chart_load = options.chart_load;
this.message = new Ui.Message();
this.deferred = new Deferred();
this.viewer = new Viewer(this);
this.editor = new Editor(this);
this.menu = new Menu(this);
this.$center.append(this.viewer.$el);
this.$right.append(this.message.$el).append(this.editor.$el);
this.$buttons.append(this.menu.$el);
$.ajax({
url: `${Galaxy.root}api/datasets/${options.dataset_id}`
})
.done(dataset => {
this.dataset = dataset;
this.chart.load();
this.chart.trigger("redraw");
})
.fail(response => {
let message = response.responseJSON && response.responseJSON.err_msg;
this.errormessage = message || "Import failed for unkown reason.";
});
}
});
@@ -0,0 +1,117 @@
import * as Backbone from "backbone";
import Utils from "utils/utils";
import { Visualization } from "mvc/visualization/visualization-model";
export default Backbone.Model.extend({
defaults: {
title: "",
type: "",
date: null,
state: "",
state_info: "",
modified: false,
dataset_id: "",
dataset_id_job: ""
},
initialize: function(options, viz_options) {
this.groups = new Backbone.Collection();
this.settings = new Backbone.Model();
this.visualization_id = viz_options.visualization_id;
this.visualization_name = viz_options.visualization_name;
this.dataset_id = viz_options.dataset_id;
this.chart_dict = viz_options.chart_dict;
console.debug("model::initialize() - Initialized with configuration:");
console.debug(viz_options);
},
reset: function() {
this.clear().set({
title: "New Chart",
dataset_id: this.dataset_id
});
this.settings.clear();
this.groups.reset();
this.groups.add({ id: Utils.uid() });
},
state: function(value, info) {
this.set({ state: value, state_info: info });
this.trigger("set:state");
console.debug("model::state() - " + info + " (" + value + ")");
},
/** Create chart dictionary */
serialize: function() {
var d = {
attributes: this.attributes,
settings: this.settings.attributes,
groups: []
};
this.groups.each(function(group) {
d.groups.push(group.attributes);
});
return d;
},
/** Pack and save nested chart model */
save: function(options) {
var self = this;
options = options || {};
this.chart_dict = this.serialize();
var viz = new Visualization({
id: this.visualization_id || undefined,
type: this.visualization_name,
title: this.get("title") || "",
config: {
dataset_id: this.dataset_id,
chart_dict: this.chart_dict
}
});
viz
.save()
.then(function(response) {
if (response && response.id) {
self.visualization_id = response.id;
if (options.success) {
options.success();
}
console.debug("model::save() - Received visualization id: " + response.id);
} else {
if (options.error) {
options.error();
}
console.debug("model::save() - Unrecognized response. Saving may have failed.");
}
})
.fail(function(response) {
if (options.error) {
options.error();
}
console.debug("model::save() - Saving failed.");
});
console.debug("model::save() - Saved with configuration:");
console.debug(this.chart_last);
},
/** Load nested models/collections from packed dictionary */
load: function(chart_parsed) {
var d = chart_parsed || this.chart_dict;
console.debug("model::load() - Attempting to load with configuration:");
console.debug(d);
this.reset();
if (d && d.attributes) {
this.set(d.attributes);
this.state("ok", "Loading saved visualization...");
this.settings.set(d.settings);
this.groups.reset();
this.groups.add(d.groups);
this.set("modified", false);
this.trigger("load");
console.debug("model::load() - Loading chart model " + d.attributes.type + ".");
return true;
}
this.trigger("load");
console.debug("model::load() - Visualization attributes unavailable.");
return false;
}
});
@@ -0,0 +1,186 @@
/** This class enables users to export/download a chart as PNG, SVG or PDF. */
/** PNG export */
function createPNG(options) {
if (options.$el.find("svg").length > 0) {
_svg2png(options);
} else {
_canvas2png(options.$el.find(".charts-viewer-canvas"));
}
}
/** Convert canvas to png */
function _canvas2png($canvas) {
try {
if ($canvas.width() !== 0 && $canvas.height() !== 0) {
var newCanvas = document.createElement("canvas");
newCanvas.width = $canvas.outerWidth(true);
newCanvas.height = $canvas.outerHeight(true);
var newContext = newCanvas.getContext("2d");
newContext.save();
newContext.fillStyle = "rgb(255,255,255)";
newContext.fillRect(0, 0, newCanvas.width, newCanvas.height);
newContext.restore();
newContext.translate(0, 0);
newContext.textAlign = "left";
newContext.textBaseline = "top";
function _toImage($el, x_offset, y_offset) {
var tagname = $el.prop("tagName").toLowerCase();
var p = $el.position();
var left =
x_offset +
p.left +
parseInt($el.css("marginLeft"), 10) +
parseInt($el.css("borderLeftWidth"), 10) +
parseInt($el.css("paddingLeft"), 10);
var top =
y_offset +
p.top +
parseInt($el.css("marginTop"), 10) +
parseInt($el.css("borderTopWidth"), 10) +
parseInt($el.css("paddingTop"), 10);
var w = newCanvas.width;
if (tagname == "div" || tagname == "span") {
$el.children().each(function() {
_toImage($(this), left, top);
});
} else if (tagname == "canvas") {
newContext.drawImage($el[0], left, top);
}
}
$canvas.children().each(function() {
_toImage($(this), 0, 0);
});
var imgData = newCanvas.toDataURL("image/png");
if (imgData) {
window.location.href = imgData.replace("image/png", "image/octet-stream");
}
}
} catch (err) {
console.debug("FAILED - screenshot::_canvas2png() - " + err);
if (options.error) {
options.error("Please reduce your visualization to a single panel and try again.");
}
}
}
/** Convert svg to png */
function _svg2png(options) {
var scale = 5;
var xml = toXML(options);
var canvas = document.createElement("canvas");
var context = canvas.getContext("2d");
var source = new Image();
var $container = $('<div style="display:none;"/>').append($(canvas));
$("body").append($container);
canvas.width = xml.width * scale;
canvas.height = xml.height * scale;
source.src = "data:image/svg+xml;base64," + btoa(xml.string);
source.onload = function() {
context.drawImage(source, 0, 0, canvas.width, canvas.height);
window.location.href = canvas.toDataURL("image/png").replace("image/png", "image/octet-stream");
$container.remove();
};
}
/** SVG export */
function createSVG(options) {
window.location.href = "data:none/none;base64," + btoa(toXML(options).string);
}
/** PDF export */
function createPDF(options) {
var xml = toXML(options);
var data = {
filename: "visualization",
type: "application/pdf",
height: xml.height,
width: xml.width,
scale: 2,
svg: xml.string
};
var $el = $("body");
var form = $el.find("#viewer-form");
if (form.length === 0) {
form = $("<form>", {
id: "viewer-form",
method: "post",
action: "http://export.highcharts.com/",
display: "none"
});
$el.append(form);
}
form.empty();
for (var name in data) {
var input = $("<input/>", {
type: "hidden",
name: name,
value: data[name]
});
form.append(input);
}
try {
form.submit();
} catch (err) {
console.log(err);
}
}
/** XML export */
function toXML(options) {
var $svg = options.$el.find("svg");
if ($svg.length == 0) {
if (options.error) {
options.error("No SVG found. This visualization type does not support SVG/PDF export.");
return;
}
}
var $el = options.$el;
var nsvgs = $svg.length;
var height = parseInt($svg.first().css("height"));
var width = parseInt($svg.first().css("width"));
var serializer = new XMLSerializer();
var $composite = $("<svg/>").attr({
version: "1.1",
xmlns: "http://www.w3.org/2000/svg",
width: width * nsvgs,
height: height
});
var xmlString = "";
var offsetX = 0;
$svg.each(function() {
var $svg = $(this).clone();
_inline($svg);
var $g = $('<g transform="translate(' + offsetX + ', 0)">').attr("xmlns", "http://www.w3.org/2000/svg");
$g.append($svg.find("g").first());
$composite.append($g);
offsetX += width;
});
return {
string: serializer.serializeToString($composite[0]),
height: height,
width: width
};
}
/** inlines CSS code */
function _inline($target) {
for (var sheet_id in document.styleSheets) {
var sheet = document.styleSheets[sheet_id];
var rules = sheet.cssRules;
if (rules) {
for (var idx = 0, len = rules.length; idx < len; idx++) {
try {
$target.find(rules[idx].selectorText).each(function(i, elem) {
elem.style.cssText += rules[idx].style.cssText;
});
} catch (err) {}
}
}
}
}
export default {
createPNG: createPNG,
createSVG: createSVG,
createPDF: createPDF
};
@@ -0,0 +1,174 @@
/** This class handles, formats and caches datasets. */
import Utils from "utils/utils";
import * as _ from "underscore";
/* global Galaxy */
/** Assists in assigning the viewport panels */
var requestPanels = function(options) {
var process = options.process;
var chart = options.chart;
var render = options.render;
var targets = options.targets;
var dataset_id = options.dataset_id || options.chart.get("dataset_id");
var dataset_groups = options.dataset_groups || options.chart.groups;
request({
chart: chart,
dataset_id: dataset_id,
dataset_groups: dataset_groups,
success: function(result) {
try {
if (targets.length == result.length) {
var valid = true;
for (var group_index in result) {
var group = result[group_index];
if (!render(targets[group_index], [group])) {
valid = false;
break;
}
}
if (valid) {
chart.state("ok", "Multi-panel chart drawn.");
}
} else if (targets.length == 1) {
if (render(targets[0], result)) {
chart.state("ok", "Chart drawn.");
}
} else {
chart.state("failed", "Invalid panel count.");
}
process.resolve();
} catch (err) {
console.debug("FAILED: tabular-utilities::panelHelper() - " + err);
chart.state("failed", err);
process.reject();
}
}
});
};
/** Fills request dictionary with data from cache/response */
var _cache = {};
var request = function(options) {
var groups = options.dataset_groups;
var dataset_id = options.dataset_id;
// identify columns needed to fulfill request
var column_list = [];
groups.each(function(group) {
_.each(group.get("__data_columns"), function(column_def, column_name) {
var column = group.get(column_name);
var block_id = _block_id(dataset_id, column);
if (
column_list.indexOf(column) === -1 &&
!_cache[block_id] &&
column != "auto" &&
column != "zero" &&
column !== undefined
) {
column_list.push(column);
}
});
});
if (column_list.length === 0) {
_fillFromCache(options);
return;
}
// Fetch data columns into dataset object
Utils.get({
url: Galaxy.root + "api/datasets/" + dataset_id,
data: {
data_type: "raw_data",
provider: "dataset-column",
indeces: column_list.toString()
},
success: function(response) {
var column_length = column_list.length;
var results = new Array(column_length);
for (let i = 0; i < results.length; i++) {
results[i] = [];
}
for (let i in response.data) {
var row = response.data[i];
for (let j in row) {
var v = row[j];
if (v !== undefined && v != 2147483647 && j < column_length) {
results[j].push(v);
}
}
}
console.debug("tabular-datasets::_fetch() - Fetching complete.");
for (let i in results) {
var column = column_list[i];
var block_id = _block_id(dataset_id, column);
_cache[block_id] = results[i];
}
_fillFromCache(options);
}
});
};
/** Fill data from cache */
var _fillFromCache = function(options) {
var groups = options.dataset_groups;
var dataset_id = options.dataset_id;
console.debug("tabular-datasets::_fillFromCache() - Filling request from cache.");
var limit = 0;
groups.each(function(group) {
_.each(group.get("__data_columns"), function(column_def, column_name) {
var column = group.get(column_name);
var block_id = _block_id(dataset_id, column);
var column_data = _cache[block_id];
if (column_data) {
limit = Math.max(limit, column_data.length);
}
});
});
if (limit === 0) {
console.debug("tabular-datasets::_fillFromCache() - No data available.");
}
var results = [];
groups.each(function(group, group_index) {
var dict = Utils.merge({ key: group_index + ":" + group.get("key"), values: [] }, group.attributes);
for (let j = 0; j < limit; j++) {
dict.values[j] = { x: parseInt(j) };
}
results.push(dict);
});
groups.each(function(group, group_index) {
var values = results[group_index].values;
_.each(group.get("__data_columns"), function(column_def, column_name) {
var column = group.get(column_name);
switch (column) {
case "auto":
for (let j = 0; j < limit; j++) {
values[j][column_name] = parseInt(j);
}
break;
case "zero":
for (let j = 0; j < limit; j++) {
values[j][column_name] = 0;
}
break;
default:
var block_id = _block_id(dataset_id, column);
var column_data = _cache[block_id];
for (let j = 0; j < limit; j++) {
var value = values[j];
var v = column_data[j];
if (isNaN(v) && !column_def.is_label) {
v = 0;
}
value[column_name] = v;
}
}
});
});
options.success(results);
};
/** Get block id */
var _block_id = function(dataset_id, column) {
return dataset_id + "_" + "_" + column;
};
export default { request: request, requestPanels: requestPanels };
@@ -0,0 +1,159 @@
/** This class handles job submissions to the Galaxy API. */
import * as _ from "underscore";
import Utils from "utils/utils";
/* global Galaxy */
/** Time to wait before refreshing to check if job has completed */
const WAITTIME = 1000;
/** build job dictionary */
var requestCharts = function(chart, module) {
var settings_string = "";
var columns_string = "";
var group_index = 0;
for (var key in chart.settings.attributes) {
var settings_value = chart.settings.get(key);
_.each([[" ", "&#32;"], [",", "&#44;"], [":", "&#58;"]], function(pair) {
settings_value = settings_value.replace(new RegExp(pair[0], "g"), pair[1]);
});
settings_string += key + ":" + settings_value + ", ";
}
settings_string = settings_string.substring(0, settings_string.length - 2);
chart.groups.each(function(group) {
group_index++;
_.each(group.get("__data_columns"), function(data_columns, name) {
columns_string += name + "_" + group_index + ":" + (parseInt(group.get(name)) + 1) + ", ";
});
});
columns_string = columns_string.substring(0, columns_string.length - 2);
return {
tool_id: "charts",
inputs: {
input: {
id: chart.get("dataset_id"),
src: "hda"
},
module: module,
columns: columns_string,
settings: settings_string
}
};
};
/** Submit job request to charts tool */
var request = function(chart, parameters, success, error) {
chart.state("wait", "Requesting job results...");
if (chart.get("modified")) {
cleanup(chart);
chart.set("modified", false);
}
var dataset_id_job = chart.get("dataset_id_job");
if (dataset_id_job !== "") {
wait(chart, success, error);
} else {
chart.state("wait", "Sending job request...");
Utils.request({
type: "POST",
url: Galaxy.root + "api/tools",
data: parameters,
success: function(response) {
if (!response.outputs || response.outputs.length === 0) {
chart.state("failed", "Job submission failed. No response.");
if (error) {
error();
}
} else {
refreshHdas();
var job = response.outputs[0];
chart.state(
"wait",
"Your job has been queued. You may close the browser window. The job will run in the background."
);
chart.set("dataset_id_job", job.id);
chart.save();
wait(chart, success, error);
}
},
error: function(response) {
var message = "";
if (response && response.message && response.message.data && response.message.data.input) {
message = response.message.data.input + ".";
}
chart.state(
"failed",
"This visualization requires the '" +
parameters.tool_id +
"' tool. Please make sure it is installed. " +
message
);
if (error) {
error();
}
}
});
}
};
/* Remove previous data when re-running jobs */
var cleanup = function(chart) {
var previous = chart.get("dataset_id_job");
if (previous !== "") {
Utils.request({
type: "PUT",
url: Galaxy.root + "api/histories/none/contents/" + previous,
data: { deleted: true },
success: function() {
refreshHdas();
}
});
chart.set("dataset_id_job", "");
}
};
/** Request job details */
var wait = function(chart, success, error) {
Utils.request({
type: "GET",
url: Galaxy.root + "api/datasets/" + chart.get("dataset_id_job"),
data: {},
success: function(dataset) {
var ready = false;
switch (dataset.state) {
case "ok":
chart.state("wait", "Job completed successfully...");
if (success) {
success(dataset);
}
ready = true;
break;
case "error":
chart.state("failed", "Job has failed. Please check the history for details.");
if (error) {
error(dataset);
}
ready = true;
break;
case "running":
chart.state(
"wait",
"Your job is running. You may close the browser window. The job will continue in the background."
);
}
if (!ready) {
window.setTimeout(function() {
wait(chart, success, error);
}, WAITTIME);
}
}
});
};
/** Refresh history panel */
var refreshHdas = function() {
if (Galaxy && Galaxy.currHistoryPanel) {
Galaxy.currHistoryPanel.refreshContents();
}
};
export default { request: request, requestCharts: requestCharts };
@@ -0,0 +1,233 @@
import * as _ from "underscore";
// TODO: This d3 must(?) just be global because various charts use different
// versions, and we want to use whichever one that is. I think. I hope.
/* global d3 */
/** Get domain boundaries value */
function getDomains(groups, keys) {
function _apply(operator, key) {
let value;
for (let group_index in groups) {
let value_sub = d3[operator](groups[group_index].values, function(d) {
return d[key];
});
value = value === undefined ? value_sub : Math[operator](value, value_sub);
}
return value;
}
var result = {};
for (var index in keys) {
var key = keys[index];
result[key] = {
min: _apply("min", key),
max: _apply("max", key)
};
}
return result;
}
/** Default series maker */
function makeSeries(groups, keys) {
var plot_data = [];
for (var group_index in groups) {
var group = groups[group_index];
var data = [];
for (var value_index in group.values) {
var point = [];
if (keys) {
for (var key_index in keys) {
let column_index = keys[key_index];
point.push(group.values[value_index][column_index]);
}
} else {
for (let column_index in group.values[value_index]) {
point.push(group.values[value_index][column_index]);
}
}
data.push(point);
}
plot_data.push(data);
}
return plot_data;
}
/** Default category maker */
function makeCategories(groups, column_keys) {
var array = {};
var data_columns = groups[0].__data_columns;
_.each(column_keys, function(key) {
if (data_columns[key].is_label) {
array[key] = [];
}
});
if (groups && groups[0]) {
_.each(groups[0].values, function(value_dict) {
for (var key in array) {
array[key].push(String(value_dict[key]));
}
});
}
mapCategories(array, groups);
return { array: array };
}
/** Apply default mapping index all values contained in label columns (for all groups) */
function mapCategories(array, groups) {
_.each(groups, function(group) {
_.each(group.values, function(value_dict, i) {
for (var key in array) {
value_dict[key] = parseInt(i);
}
});
});
}
/** Category make for unique category labels */
function makeUniqueCategories(groups, with_index) {
var categories = {};
var array = {};
var counter = {};
var data_columns = groups[0].__data_columns;
_.each(data_columns, function(column_def, key) {
if (column_def.is_label) {
categories[key] = {};
array[key] = [];
counter[key] = 0;
}
});
// index all values contained in label columns (for all groups)
for (var i in groups) {
var group = groups[i];
for (var j in group.values) {
var value_dict = group.values[j];
for (var key in categories) {
var value = String(value_dict[key]);
if (categories[key][value] === undefined) {
categories[key][value] = counter[key];
array[key].push(with_index ? [counter[key], value] : value);
counter[key]++;
}
}
}
}
// convert group values into category indeces
for (let i in groups) {
let group = groups[i];
for (let j in group.values) {
let value_dict = group.values[j];
for (let key in categories) {
let value = String(value_dict[key]);
value_dict[key] = categories[key][value];
}
}
}
return {
categories: categories,
array: array,
counter: counter
};
}
/** Make axis */
function makeTickFormat(options) {
var type = options.type;
var precision = options.precision;
var categories = options.categories;
var formatter = options.formatter;
if (type == "hide") {
formatter(function() {
return "";
});
} else if (type == "auto") {
if (categories) {
formatter(function(value) {
return categories[value] || "";
});
}
} else {
var d3format = function(d) {
switch (type) {
case "s":
var prefix = d3.formatPrefix(d);
return prefix.scale(d).toFixed() + prefix.symbol;
default:
return d3.format("." + precision + type)(d);
}
};
if (categories) {
formatter(function(value) {
var label = categories[value];
if (label) {
if (isNaN(label)) {
return label;
} else {
try {
return d3format(label);
} catch (err) {
return label;
}
}
} else {
return "";
}
});
} else {
formatter(function(value) {
return d3format(value);
});
}
}
}
/** Add zoom handler */
function addZoom(options) {
var scaleExtent = 100;
var yAxis = options.yAxis;
var xAxis = options.xAxis;
var xDomain = options.xDomain || xAxis.scale().domain;
var yDomain = options.yDomain || yAxis.scale().domain;
var redraw = options.redraw;
var svg = options.svg;
var xScale = xAxis.scale();
var yScale = yAxis.scale();
var x_boundary = xScale.domain().slice();
var y_boundary = yScale.domain().slice();
var d3zoom = d3.behavior.zoom();
xScale.nice();
yScale.nice();
function fixDomain(domain, boundary) {
domain[0] = Math.min(Math.max(domain[0], boundary[0]), boundary[1] - boundary[1] / scaleExtent);
domain[1] = Math.max(boundary[0] + boundary[1] / scaleExtent, Math.min(domain[1], boundary[1]));
return domain;
}
function zoomed() {
yDomain(fixDomain(yScale.domain(), y_boundary));
xDomain(fixDomain(xScale.domain(), x_boundary));
redraw();
}
function unzoomed() {
xDomain(x_boundary);
yDomain(y_boundary);
redraw();
d3zoom.scale(1);
d3zoom.translate([0, 0]);
}
d3zoom
.x(xScale)
.y(yScale)
.scaleExtent([1, scaleExtent])
.on("zoom", zoomed);
svg.call(d3zoom).on("dblclick.zoom", unzoomed);
return d3zoom;
}
export default {
makeCategories: makeCategories,
makeUniqueCategories: makeUniqueCategories,
makeSeries: makeSeries,
getDomains: getDomains,
mapCategories: mapCategories,
makeTickFormat: makeTickFormat,
addZoom: addZoom
};
@@ -0,0 +1,37 @@
/** Renders the visualization header with title, logo and description. */
import Utils from "utils/utils";
export default Backbone.View.extend({
initialize: function(app) {
this.plugin = app.chart.plugin;
this.setElement(this._template());
this.$title = this.$(".charts-description-title");
this.$image = this.$(".charts-description-image");
this.$text = this.$(".charts-description-text");
this.render();
},
render: function() {
if (this.plugin.logo) {
this.$image.attr("src", Galaxy.root + this.plugin.logo);
this.$title.html(this.plugin.html || "Unavailable");
this.$text.html(Utils.linkify(this.plugin.description || ""));
this.$el.show();
} else {
this.$el.hide();
}
},
_template: function() {
return `<div class="charts-description">
<table>
<tr>
<td class="charts-description-image-td">
<img class="charts-description-image"/>
</td>
<td>
<div class="charts-description-title ui-form-info"/>
</td>
</tr>
</table>
<div class="charts-description-text ui-form-info"/>
</div>`;
}
});
@@ -0,0 +1,52 @@
/**
* The charts editor holds the tabs for selecting chart types, chart configuration
* and data group selections.
*/
import Ui from "mvc/ui/ui-misc";
import Utils from "utils/utils";
import Tabs from "mvc/ui/ui-tabs";
import Groups from "mvc/visualization/chart/views/groups";
import Settings from "mvc/visualization/chart/views/settings";
import Description from "mvc/visualization/chart/views/description";
export default Backbone.View.extend({
initialize: function(app, options) {
this.app = app;
this.chart = this.app.chart;
this.description = new Description(this.app);
this.title = new Ui.Input({
onchange: () => {
this.chart.set("title", this.title.value());
}
});
this.tabs = new Tabs.View({});
this.tabs.add({
id: "settings",
icon: "fa fa-gear",
tooltip: "Change settings.",
$el: $("<div/>")
.append(new Ui.Label({ title: "Provide a title:" }).$el)
.append(this.title.$el)
.append(
$("<div/>")
.addClass("ui-form-info ui-margin-bottom")
.html("This title will appear in the list of 'Saved Visualizations'.")
)
.append(new Settings(this.app).$el)
});
if (this.chart.plugin.groups) {
this.tabs.add({
id: "groups",
icon: "fa-database",
tooltip: "Select data.",
$el: new Groups(this.app).$el
});
}
this.setElement("<div class='charts-editor'/>");
this.$el.append(this.description.$el);
this.$el.append(this.tabs.$el);
this.listenTo(this.chart, "load", () => {
this.title.value(this.chart.get("title"));
});
}
});
@@ -0,0 +1,122 @@
/** This class renders the chart data selection form with repeats. */
import Utils from "utils/utils";
import Ui from "mvc/ui/ui-misc";
import Form from "mvc/form/form-view";
import Repeat from "mvc/form/form-repeat";
import FormData from "mvc/form/form-data";
var GroupView = Backbone.View.extend({
initialize: function(app, options) {
var self = this;
this.deferred = app.deferred;
this.chart = app.chart;
this.group = options.group;
this.setElement($("<div/>"));
this.listenTo(this.chart, "change:dataset_id", function() {
self.render();
});
this.render();
},
render: function() {
var self = this;
var inputs = Utils.clone(this.chart.plugin.groups) || [];
var dataset_id = this.chart.get("dataset_id");
if (dataset_id) {
this.chart.state("wait", "Loading metadata...");
this.deferred.execute(function(process) {
Utils.get({
url: Galaxy.root + "api/datasets/" + dataset_id,
cache: true,
success: function(dataset) {
var data_columns = {};
FormData.visitInputs(inputs, function(input, prefixed) {
if (input.type == "data_column") {
data_columns[prefixed] = Utils.clone(input);
var columns = [];
if (input.is_auto) {
columns.push({ label: "Column: Row Number", value: "auto" });
}
if (input.is_zero) {
columns.push({ label: "Column: None", value: "zero" });
}
var meta = dataset.metadata_column_types;
for (var key in meta) {
if (
(["int", "float"].indexOf(meta[key]) != -1 && input.is_numeric) ||
input.is_label
) {
columns.push({ label: "Column: " + (parseInt(key) + 1), value: key });
}
}
input.data = columns;
}
var model_value = self.group.get(prefixed);
if (model_value !== undefined && !input.hidden) {
input.value = model_value;
}
});
inputs.push({
name: "__data_columns",
type: "hidden",
hidden: true,
value: data_columns
});
self.chart.state("ok", "Metadata initialized...");
self.form = new Form({
inputs: inputs,
cls: "ui-portlet-plain",
onchange: function() {
self.group.set(self.form.data.create());
self.chart.trigger("redraw");
}
});
self.group.set(self.form.data.create());
self.$el.empty().append(self.form.$el);
process.resolve();
self.chart.trigger("redraw");
}
});
});
}
}
});
export default Backbone.View.extend({
initialize: function(app) {
var self = this;
this.app = app;
this.chart = app.chart;
this.repeat = new Repeat.View({
title: "Data series",
title_new: "Data series",
min: 1,
onnew: function() {
self.chart.groups.add({ id: Utils.uid() });
}
});
this.setElement($("<div/>").append(this.repeat.$el));
this.listenTo(this.chart.groups, "remove", function(group) {
self.repeat.del(group.id);
self.chart.trigger("redraw");
});
this.listenTo(this.chart.groups, "reset", function() {
self.repeat.delAll();
});
this.listenTo(this.chart.groups, "add", function(group) {
self.repeat.add({
id: group.id,
cls: "ui-portlet-panel",
$el: new GroupView(self.app, { group: group }).$el,
ondel: function() {
self.chart.groups.remove(group);
}
});
});
},
render: function() {
if (_.size(this.chart.plugin.groups) > 0) {
this.repeat.$el.show();
} else {
this.repeat.$el.hide();
}
}
});
@@ -0,0 +1,149 @@
/** This class renders the chart menu options. */
import Ui from "mvc/ui/ui-misc";
import Screenshot from "mvc/visualization/chart/components/screenshot";
import Utils from "utils/utils";
export default Backbone.View.extend({
initialize: function(app) {
this.app = app;
this.model = new Backbone.Model({ visible: true });
this.export_button = new Ui.ButtonMenu({
icon: "fa-camera",
tooltip: "Export"
});
this.export_button.addMenu({
key: "png",
title: "Save as PNG",
icon: "fa-file",
onclick: () => {
this._wait(app.chart, () => {
Screenshot.createPNG({
$el: app.viewer.$el,
title: app.chart.get("title"),
error: err => {
app.message.update({ message: err, status: "danger" });
}
});
});
}
});
this.export_button.addMenu({
key: "svg",
title: "Save as SVG",
icon: "fa-file-text-o",
onclick: () => {
this._wait(app.chart, () => {
Screenshot.createSVG({
$el: app.viewer.$el,
title: app.chart.get("title"),
error: err => {
app.message.update({ message: err, status: "danger" });
}
});
});
}
});
this.export_button.addMenu({
key: "pdf",
title: "Save as PDF",
icon: "fa-file-o",
onclick: () => {
app.modal.show({
title: "Send visualization data for PDF creation",
body:
"Galaxy does not provide integrated PDF export scripts. You may click 'Continue' to create the PDF by using a 3rd party service (https://export.highcharts.com).",
buttons: {
Cancel: () => {
app.modal.hide();
},
Continue: () => {
app.modal.hide();
this._wait(app.chart, () => {
Screenshot.createPDF({
$el: app.viewer.$el,
title: app.chart.get("title"),
error: err => {
app.message.update({ message: err, status: "danger" });
}
});
});
}
}
});
}
});
this.buttons = [
new Ui.ButtonIcon({
icon: "fa-angle-double-left",
tooltip: "Show",
cls: "ui-button-icon charts-fullscreen-button",
onclick: () => {
this.model.set("visible", true);
window.dispatchEvent(new Event("resize"));
}
}),
new Ui.ButtonIcon({
icon: "fa-angle-double-right",
tooltip: "Hide",
onclick: () => {
this.model.set("visible", false);
window.dispatchEvent(new Event("resize"));
}
}),
this.export_button,
new Ui.ButtonIcon({
icon: "fa-save",
tooltip: "Save",
onclick: () => {
if (app.chart.get("title")) {
app.message.update({
message: `Saving '${app.chart.get(
"title"
)}'. It will appear in the list of 'Saved Visualizations'.`,
status: "success"
});
app.chart.save({
error: () => {
app.message.update({
message: "Could not save visualization.",
status: "danger"
});
}
});
} else {
app.message.update({
message: "Please provide a name.",
status: "danger"
});
}
}
})
];
this.setElement("<div/>");
for (let b of this.buttons) {
this.$el.append(b.$el);
}
this.listenTo(this.model, "change", () => this.render());
this.render();
},
render: function() {
var visible = this.model.get("visible");
this.app.$el[visible ? "removeClass" : "addClass"]("charts-fullscreen");
this.export_button.model.set("visible", visible);
var exports = this.app.chart.plugin.specs.exports || [];
this.export_button.collection.each(model => {
model.set("visible", exports.indexOf(model.get("key")) !== -1);
});
},
_wait: function(chart, callback) {
if (this.app.deferred.ready()) {
callback();
} else {
this.app.message.update({
message: "Your visualization is currently being processed. Please wait and try again."
});
}
}
});
@@ -0,0 +1,49 @@
/** This class renders the chart configuration form. */
import Utils from "utils/utils";
import Form from "mvc/form/form-view";
import FormData from "mvc/form/form-data";
export default Backbone.View.extend({
initialize: function(app, options) {
var self = this;
this.chart = app.chart;
this.setElement("<div/>");
this.listenTo(this.chart, "load", function() {
self.render();
});
},
render: function() {
var self = this;
var inputs = Utils.clone(this.chart.plugin.settings) || [];
var panel_option = this.chart.plugin.specs.use_panels;
if (panel_option == "optional") {
inputs.push({
name: "__use_panels",
type: "boolean",
label: "Use multi-panels",
help: "Would you like to separate your data into individual panels?"
});
} else {
this.chart.settings.set("__use_panels", panel_option == "yes" ? "true" : "false");
}
this.$el.empty().addClass("ui-margin-bottom");
if (_.size(inputs) > 0) {
FormData.visitInputs(inputs, function(input, name) {
var model_value = self.chart.settings.get(name);
if (model_value !== undefined && !input.hidden) {
input.value = model_value;
}
});
this.form = new Form({
inputs: inputs,
cls: "ui-portlet-plain",
onchange: function() {
self.chart.settings.set(self.form.data.create());
self.chart.trigger("redraw");
}
});
this.chart.settings.set(this.form.data.create());
this.$el.append(this.form.$el);
}
}
});
@@ -0,0 +1,92 @@
/**
* The viewer creates and manages the dom elements used by the visualization plugins to draw the chart.
* This is the last class of the charts core classes before handing control over to the visualization plugins.
*/
import Utils from "utils/utils";
export default Backbone.View.extend({
initialize: function(app, options) {
var self = this;
this.app = app;
this.chart = this.app.chart;
this.options = options;
this.setElement(
$("<div/>")
.addClass("charts-viewer")
.append(
$("<div/>")
.addClass("info")
.append($("<span/>").addClass("icon"))
.append($("<span/>").addClass("text"))
)
);
this.$info = this.$(".info");
this.$icon = this.$(".icon");
this.$text = this.$(".text");
this._fullscreen(this.$el, 20);
this._createContainer("div");
this.chart.on("redraw", function() {
self.app.deferred.execute(function(process) {
console.debug("viewer:redraw() - Redrawing...");
self._draw(process, self.chart);
});
});
this.chart.on("set:state", function() {
var $container = self.$(".charts-viewer-container");
var $info = self.$info;
var $icon = self.$icon;
var $text = self.$text;
$icon.removeClass();
$info.show();
$text.html(self.chart.get("state_info"));
var state = self.chart.get("state");
switch (state) {
case "ok":
$info.hide();
$container.show();
break;
case "failed":
$icon.addClass("icon fa fa-warning");
$container.hide();
break;
default:
$icon.addClass("icon fa fa-spinner fa-spin");
$container.show();
}
});
},
/** Force resize to fullscreen */
_fullscreen: function($el, margin) {
$el.css("height", $(window).height() - margin);
$(window).resize(function() {
$el.css("height", $(window).height() - margin);
});
},
/** A chart may contain multiple sub charts/containers which are created here */
_createContainer: function(tag, n) {
tag = tag || "div";
n = n || 1;
this.$(".charts-viewer-container").remove();
this.targets = [];
for (var i = 0; i < n; i++) {
var container_id = Utils.uid();
var container_el = $("<div/>")
.addClass("charts-viewer-container")
.width(parseInt(100 / n) + "%")
.append($("<" + tag + " class='charts-viewer-canvas' />").attr("id", container_id));
this.$el.append(container_el);
this.targets.push(container_id);
}
},
/** Draws a new chart by loading and executing the corresponding chart wrapper */
_draw: function(process, chart) {
var n_panels = chart.settings.get("__use_panels") == "true" ? chart.groups.length : 1;
this._createContainer(chart.plugin.specs.tag, n_panels);
chart.set("date", Utils.time());
chart.state("wait", "Please wait...");
this.app.chart_load({ process: process, chart: chart, dataset: this.app.dataset, targets: this.targets });
}
});
@@ -0,0 +1,66 @@
/**
* Visualization selection grid
*/
import Thumbnails from "mvc/ui/ui-thumbnails";
var View = Backbone.View.extend({
initialize: function(options) {
var self = this;
this.setElement($("<div/>"));
this.model = new Backbone.Model();
$.ajax({
url: `${Galaxy.root}api/datasets/${options.dataset_id}`
}).done(dataset => {
if (dataset.visualizations.length === 0) {
self.$el.append(
$("<div/>")
.addClass("errormessagelarge")
.append(
$("<p/>").text(
"Unfortunately we could not identify a suitable plugin. Feel free to contact us if you are aware of visualizations for this datatype."
)
)
);
} else {
self.model.set({ dataset: dataset });
self.render();
}
});
},
render: function() {
var self = this;
var dataset = this.model.get("dataset");
this.vis_index = {};
this.vis_array = [];
_.each(
dataset.visualizations.sort(function(a, b) {
return a.html < b.html ? -1 : 1;
}),
function(v, id) {
var dict = {
id: id,
name: v.name,
keywords: v.keywords || [],
title: v.html,
image_src: v.logo ? Galaxy.root + v.logo : null,
description: v.description || "No description available.",
regular: v.regular,
visualization: v
};
self.vis_index[dict.id] = dict;
self.vis_array.push(dict);
}
);
this.types = new Thumbnails.View({
title_default: "Suggested plugins",
title_list: "List of available plugins",
collection: this.vis_array,
onclick: function(id) {
var vis_type = self.vis_index[id];
$("#galaxy_main").attr("src", vis_type.visualization.href);
}
});
this.$el.empty().append(this.types.$el);
}
});
export default { View: View };
+60 -40
View File
@@ -1,66 +1,86 @@
/**
Webhooks
**/
import Utils from "utils/utils";
var WebhookModel = Backbone.Model.extend({
defaults: {
activate: false
const Webhooks = Backbone.Collection.extend({
url: function() {
return `${Galaxy.root}api/webhooks`;
}
});
var Webhooks = Backbone.Collection.extend({
model: WebhookModel
});
var WebhookView = Backbone.View.extend({
const WebhookView = Backbone.View.extend({
el: "#webhook-view",
initialize: function(options) {
var me = this;
var toolId = options.toolId || "";
var toolVersion = options.toolVersion || "";
const toolId = options.toolId || "";
const toolVersion = options.toolVersion || "";
this.$el.attr("tool_id", toolId);
this.$el.attr("tool_version", toolVersion);
this.model = new WebhookModel();
this.model.urlRoot = options.urlRoot;
this.model.fetch({
success: function() {
me.render();
const webhooks = new Webhooks();
webhooks.fetch({
success: data => {
if (options.type) {
data.reset(filterType(data, options.type));
}
if (data.length > 0) {
this.render(weightedRandomPick(data));
}
}
});
},
render: function() {
var webhook = this.model.toJSON();
this.$el.html(`<div id="${webhook.name}"></div>`);
if (webhook.styles)
$("<style/>", { type: "text/css" })
.text(webhook.styles)
.appendTo("head");
if (webhook.script)
$("<script/>", { type: "text/javascript" })
.text(webhook.script)
.appendTo("head");
render: function(model) {
const webhook = model.toJSON();
this.$el.html(`<div id="${webhook.id}"></div>`);
Utils.appendScriptStyle(webhook);
return this;
}
});
var add = options => {
var webhooks = new Webhooks();
webhooks.url = Galaxy.root + options.url;
const load = options => {
const webhooks = new Webhooks();
webhooks.fetch({
async: options.async ? options.async : true,
success: options.callback
async: options.async !== undefined ? options.async : true,
success: data => {
if (options.type) {
data.reset(filterType(data, options.type));
}
options.callback(data);
}
});
};
function filterType(data, type) {
return data.models.filter(item => {
let itype = item.get("type");
if (itype) {
return itype.indexOf(type) !== -1;
} else {
return false;
}
});
}
function weightedRandomPick(data) {
const weights = data.pluck("weight");
const sum = weights.reduce((a, b) => a + b);
const normalizedWeightsMap = new Map();
weights.forEach((weight, index) => {
normalizedWeightsMap.set(index, parseFloat((weight / sum).toFixed(2)));
});
const table = [];
for (const [index, weight] of normalizedWeightsMap) {
for (let i = 0; i < weight * 100; i++) {
table.push(index);
}
}
return data.at(table[Math.floor(Math.random() * table.length)]);
}
export default {
Webhooks: Webhooks,
WebhookView: WebhookView,
add: add
load: load
};
@@ -180,7 +180,7 @@ var Node = Backbone.Model.extend({
$(element).removeClass("toolForm-active");
},
set_tool_version: function() {
if (this.config_form) {
if (this.type === "tool" && this.config_form) {
this.tool_version = this.config_form.version;
this.content_id = this.config_form.id;
}
@@ -4,6 +4,8 @@ import TAGS from "mvc/tag";
import WORKFLOWS from "mvc/workflow/workflow-model";
import QueryStringParsing from "utils/query-string-parsing";
import _l from "utils/localization";
import LoadingIndicator from "ui/loading-indicator";
/** View of the individual workflows */
const WorkflowItemView = Backbone.View.extend({
tagName: "tr", // name of (orphan) root tag in this.el
@@ -156,8 +158,9 @@ const WorkflowItemView = Backbone.View.extend({
/** View of the main workflow list page */
const WorkflowListView = Backbone.View.extend({
title: _l("Workflows"),
active_tab: "workflow",
initialize: function() {
this.setElement("<div/>");
LoadingIndicator.markViewAsLoading(this);
_.bindAll(this, "adjustActiondropdown");
this.collection = new WORKFLOWS.WorkflowCollection();
this.collection.fetch().done(this.render());
+13 -13
View File
@@ -25,6 +25,8 @@ window.make_popup_menus = Popupmenu.make_popup_menus;
import init_tag_click_function from "ui/autocom_tagging";
window.init_tag_click_function = init_tag_click_function;
import Tours from "mvc/tours";
import Webhooks from "mvc/webhooks";
import Utils from "utils/utils";
// console.debug( 'galaxy globals loaded' );
// ============================================================================
@@ -176,21 +178,19 @@ $(document).ready(() => {
Tours.activeGalaxyTourRunner();
function onloadWebhooks() {
// Wait until Galaxy.config is loaded.
if (Galaxy.config) {
if (Galaxy.root !== undefined) {
if (Galaxy.config.enable_webhooks) {
// Load all webhooks with the type 'onload'
$.getJSON(`${Galaxy.root}api/webhooks/onload/all`, webhooks => {
_.each(webhooks, webhook => {
if (webhook.activate && webhook.script) {
$("<script/>", { type: "text/javascript" })
.text(webhook.script)
.appendTo("head");
$("<style/>", { type: "text/css" })
.text(webhook.styles)
.appendTo("head");
}
});
Webhooks.load({
type: "onload",
callback: function(webhooks) {
webhooks.each(model => {
var webhook = model.toJSON();
if (webhook.activate && webhook.script) {
Utils.appendScriptStyle(webhook);
}
});
}
});
}
} else {
+2
View File
@@ -1,4 +1,6 @@
/** Creates a generic/global Galaxy environment, loads shared libraries and a fake server */
/* global define */
define(
[
"jquery",
@@ -1,8 +1,7 @@
/* global define */
/* global QUnit */
import testApp from "qunit/test-app";
import Page from "layout/page";
import Panel from "layout/panel";
QUnit.module("Page test", {
beforeEach: function() {
@@ -68,15 +68,12 @@ QUnit.test("thumbnails", function(assert) {
assert.ok(thumb.$(".tab-pane").length == options.ntabs, "Two tabs found.");
assert.ok(thumb.$(".ui-thumbnails-item").length == options.nitems, "Thumbnail item.");
assert.ok(
$(thumb.$(".ui-thumbnails-image")[options.index || 0]).attr("src") == options.image_src,
$(thumb.$(".ui-thumbnails-image")[options.index]).attr("src") == options.image_src,
"Correct image source"
);
assert.ok($(thumb.$(".ui-thumbnails-title")[options.index]).html() == options.title, "Correct title with icon");
assert.ok(
$(thumb.$(".ui-thumbnails-title")[options.index || 0]).html() == options.title,
"Correct title with icon"
);
assert.ok(
$(thumb.$(".ui-thumbnails-description-text")[options.index || 0]).html() == options.description,
$(thumb.$(".ui-thumbnails-description-text")[options.index]).html() == options.description,
"Correct description"
);
};
@@ -86,7 +83,7 @@ QUnit.test("thumbnails", function(assert) {
collection: [
{
id: "id",
keywords: "default",
regular: true,
title: "title",
title_icon: "title_icon",
image_src: "image_src",
@@ -97,6 +94,7 @@ QUnit.test("thumbnails", function(assert) {
var model = thumb.model;
$("body").prepend(thumb.$el);
_test({
index: 0,
ntabs: 2,
nitems: 2,
image_src: "image_src",
@@ -105,7 +103,7 @@ QUnit.test("thumbnails", function(assert) {
});
thumb.collection.add({
id: "id_a",
keywords: "default_a",
regular: true,
title: "title_a",
title_icon: "title_icon_a",
image_src: "image_src_a",
@@ -99,5 +99,13 @@ function LoadingIndicator($where, options) {
return self;
}
const markViewAsLoading = function(view) {
view.setElement($('<div><div class="loading"></div></div>'));
new LoadingIndicator(view.$(".loading")).show();
};
//============================================================================
export default LoadingIndicator;
export default {
LoadingIndicator: LoadingIndicator,
markViewAsLoading: markViewAsLoading
};
+12 -9
View File
@@ -1,6 +1,10 @@
import * as _ from "libs/underscore";
import * as Backbone from "libs/backbone";
import util_mod from "viz/trackster/util";
import config_mod from "utils/config";
/* global $ */
/* global Galaxy */
/**
* A configuration setting. Currently key is used as id.
*/
@@ -307,23 +311,22 @@ var ConfigSettingCollectionView = Backbone.View.extend({
// No propagation to avoid triggering document click (and tip hiding) above.
e.stopPropagation();
});
var // Icon for setting a new random color; behavior set below.
new_color_icon = $("<a href='javascript:void(0)'/>")
// Icon for setting a new random color; behavior set below.
var new_color_icon = $("<a href='javascript:void(0)'/>")
.addClass("icon-button arrow-circle")
.appendTo(container_div)
.attr("title", "Set new random color")
.tooltip();
var // Color picker in tool tip style.
tip = $("<div class='tooltip right' style='position: absolute;' />")
// Color picker in tool tip style.
var tip = $("<div class='tooltip right' style='position: absolute;' />")
.appendTo(container_div)
.hide();
var // Inner div for padding purposes
tip_inner = $("<div class='tooltip-inner' style='text-align: inherit'></div>").appendTo(tip);
// Inner div for padding purposes
var tip_inner = $("<div class='tooltip-inner' style='text-align: inherit'></div>").appendTo(tip);
var tip_arrow = $("<div class='tooltip-arrow'></div>").appendTo(tip);
$("<div class='tooltip-arrow'></div>").appendTo(tip);
var farb_obj = $.farbtastic(tip_inner, {
width: 100,
+7 -8
View File
@@ -2,16 +2,15 @@ define(["i18n!nls/locale"], function(localeStrings) {
// =============================================================================
/** Simple string replacement localization. Language data from galaxy/scripts/nls */
// support both requirejs and webpack from the same file
// if loaded via webpack, it will be a different format than via requirejs - convert
if (localeStrings.hasOwnProperty("__root")) {
//console.debug( 'amdi18n+webpack localization for ' + locale + ' loaded' );
var locale =
typeof navigator === "undefined"
? "__root"
: (navigator.language || navigator.userLanguage || "__root").toLowerCase();
localeStrings =
localeStrings["__" + locale] || localeStrings["__" + locale.split("-")[0]] || localeStrings.__root;
locale = sessionStorage.getItem("currentLocale");
if (locale) {
localeStrings =
localeStrings["__" + locale] || localeStrings["__" + locale.split("-")[0]] || localeStrings.__root;
}
// } else {
// console.debug( 'i18n+requirejs localization for ' + locale + ' loaded' );
+227 -93
View File
@@ -6,81 +6,29 @@
jQuery.event.props.push("dataTransfer");
/**
Posts file data to the API
xhr request helper
*/
$.uploadpost = config => {
// parse options
var _uploadrequest = config => {
var cnf = $.extend(
{},
{
data: {},
success: function() {},
error: function() {},
progress: function() {},
url: null,
maxfilesize: 2048,
error_filesize: "File exceeds 2GB. Please use a 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_login: "Uploads require you to log in.",
error_retry: "Waiting for server to resume..."
},
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
console.debug(cnf);
var 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.setRequestHeader("Accept", "application/json");
xhr.onreadystatechange = () => {
// check for request completed, server connection closed
if (xhr.readyState == xhr.DONE) {
// parse response
var response = null;
var extra_info = "";
if (xhr.responseText) {
try {
response = jQuery.parseJSON(xhr.responseText);
extra_info = response.err_msg;
} catch (e) {
response = xhr.responseText;
extra_info = response;
}
}
// pass any error to the error option
if (xhr.status < 200 || xhr.status > 299) {
if ([502, 0].indexOf(xhr.status) !== -1 && cnf.warning) {
cnf.warning(cnf.error_retry);
} else if (xhr.status < 200 || xhr.status > 299) {
var text = xhr.statusText;
if (xhr.status == 403) {
text = cnf.error_login;
@@ -89,27 +37,191 @@
} else if (!text) {
text = cnf.error_default;
}
cnf.error(`${text} (${xhr.status}). ${extra_info}`);
cnf.error(`${text} (${xhr.status})`);
} else {
var response = null;
if (xhr.responseText) {
try {
response = jQuery.parseJSON(xhr.responseText);
} catch (e) {
response = xhr.responseText;
}
}
cnf.success(response);
}
}
};
xhr.upload.addEventListener("progress", cnf.progress, false);
xhr.send(cnf.data);
};
// prepare upload progress
xhr.upload.addEventListener(
"progress",
e => {
/**
Posts chunked files to the API.
*/
$.uploadchunk = function(config) {
// parse options
var cnf = $.extend(
{},
{
data: {},
success: () => {},
error: () => {},
warning: () => {},
progress: () => {},
attempts: 70000,
timeout: 5000,
url: null,
error_file: "File not provided.",
error_attempt: "Maximum number of attempts reached.",
error_tool: "Tool submission failed."
},
config
);
// initial validation
var data = cnf.data;
if (data.error_message) {
cnf.error(data.error_message);
return;
}
var file_data = data.files && data.files[0];
if (!file_data) {
cnf.error(cnf.error_file);
return;
}
var file = file_data.file;
var attempts = cnf.attempts;
var session_id = `${cnf.session.id}-${new Date().valueOf()}-${file.size}`;
var chunk_size = cnf.session.chunk_upload_size;
console.debug(`Starting chunked uploads [size=${chunk_size}].`);
// chunk processing helper
function process(start) {
start = start || 0;
var slicer = file.mozSlice || file.webkitSlice || file.slice;
if (!slicer) {
cnf.error("Browser does not support chunked uploads.");
return;
}
var end = Math.min(start + chunk_size, file.size);
var size = file.size;
console.debug(`Submitting chunk at ${start} bytes...`);
var form = new FormData();
form.append("session_id", session_id);
form.append("session_start", start);
form.append("session_chunk", slicer.bind(file)(start, end));
_uploadrequest({
url: `${Galaxy.root}api/uploads`,
data: form,
success: upload_response => {
var new_start = start + chunk_size;
if (new_start < size) {
attempts = cnf.attempts;
process(new_start);
} else {
console.debug("Upload completed.");
data.payload.inputs = JSON.parse(data.payload.inputs);
data.payload.inputs["files_0|file_data"] = {
session_id: session_id,
name: file.name
};
data.payload.inputs = JSON.stringify(data.payload.inputs);
$.ajax({
url: `${Galaxy.root}api/tools`,
method: "POST",
data: data.payload,
success: tool_response => {
cnf.success(tool_response);
},
error: tool_response => {
var err_msg =
tool_response && tool_response.responseJSON && tool_response.responseJSON.err_msg;
cnf.error(err_msg || cnf.error_tool);
}
});
}
},
warning: upload_response => {
if (--attempts > 0) {
console.debug("Retrying last chunk...");
cnf.warning(upload_response);
setTimeout(() => process(start), cnf.timeout);
} else {
console.debug(cnf.error_attempt);
cnf.error(cnf.error_attempt);
}
},
error: upload_response => {
console.debug(upload_response);
cnf.error(upload_response);
},
progress: e => {
if (e.lengthComputable) {
cnf.progress(Math.min(Math.round((start + e.loaded) * 100 / file.size), 100));
}
}
});
}
// initiate processing queue for chunks
process();
};
/**
Posts multiple files without chunking to the API.
*/
$.uploadpost = function(config) {
var cnf = $.extend(
{},
{
data: {},
success: () => {},
error: () => {},
progress: () => {},
url: null,
maxfilesize: 1048576 * 2048,
error_filesize: "File exceeds 2GB. Please use a FTP client."
},
config
);
var data = cnf.data;
if (data.error_message) {
cnf.error(data.error_message);
return;
}
// construct form data
var form = new FormData();
for (let key in data.payload) {
form.append(key, data.payload[key]);
}
// add files to submission
var sizes = 0;
for (let 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 > cnf.maxfilesize) {
cnf.error(cnf.error_filesize);
return;
}
// submit request
_uploadrequest({
url: cnf.url,
data: form,
success: cnf.success,
error: cnf.error,
progress: e => {
if (e.lengthComputable) {
cnf.progress(Math.round(e.loaded * 100 / e.total));
}
},
false
);
// send request
Galaxy.emit.debug("uploadbox::uploadpost()", "Posting following data.", cnf);
xhr.send(form);
}
});
};
/**
@@ -121,9 +233,9 @@
var opts = $.extend(
{},
{
ondragover: function() {},
ondragleave: function() {},
onchange: function() {},
ondragover: () => {},
ondragleave: () => {},
onchange: () => {},
multiple: false
},
options
@@ -132,7 +244,7 @@
// append hidden upload field
var $input = $(`<input type="file" style="display: none" ${(opts.multiple && "multiple") || ""}/>`);
el.append(
$input.change(function(e) {
$input.change(e => {
opts.onchange(e.target.files);
$(this).val("");
})
@@ -157,7 +269,7 @@
// exports
return {
dialog: function() {
dialog: () => {
$input.trigger("click");
}
};
@@ -171,16 +283,17 @@
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) {
dragover: () => {},
dragleave: () => {},
announce: d => {},
initialize: d => {},
progress: (d, m) => {},
success: (d, m) => {},
warning: (d, m) => {},
error: (d, m) => {
alert(m);
},
complete: function() {}
complete: () => {}
},
options
);
@@ -188,6 +301,9 @@
// file queue
var queue = {};
// session options
var session = null;
// queue index/length counter
var queue_index = 0;
var queue_length = 0;
@@ -199,7 +315,10 @@
// element
var uploadinput = $(this).uploadinput({
multiple: true,
onchange: function(files) {
onchange: files => {
_.each(files, file => {
file.chunk_mode = true;
});
add(files);
},
ondragover: options.ondragover,
@@ -252,7 +371,7 @@
// get an identifier from the queue
var index = -1;
for (var key in queue) {
for (let key in queue) {
index = key;
break;
}
@@ -264,18 +383,32 @@
remove(index);
// create and submit data
$.uploadpost({
var submitter = $.uploadpost;
if (
file.chunk_mode &&
session &&
session.id &&
session.chunk_upload_size &&
session.chunk_upload_size > 0
) {
submitter = $.uploadchunk;
}
submitter({
url: opts.url,
data: opts.initialize(index),
success: function(message) {
session: session,
success: message => {
opts.success(index, message);
process();
},
error: function(message) {
warning: message => {
opts.warning(index, message);
},
error: message => {
opts.error(index, message);
process();
},
progress: function(percentage) {
progress: percentage => {
opts.progress(index, percentage);
}
});
@@ -298,7 +431,8 @@
}
// initiate upload process
function start() {
function start(_session) {
session = _session;
if (!queue_running) {
queue_running = true;
process();
+35 -10
View File
@@ -3,6 +3,11 @@
* do not require their own classes/files
*/
import _l from "utils/localization";
import * as _ from "underscore";
/* global $ */
/* global Galaxy */
/** Builds a basic iframe */
export function iframe(src) {
return `<iframe src="${src}" frameborder="0" style="width: 100%; height: 100%;"/>`;
@@ -14,7 +19,9 @@ export function deepeach(dict, callback) {
var d = dict[i];
if (_.isObject(d)) {
var new_dict = callback(d);
new_dict && (dict[i] = new_dict);
if (new_dict) {
dict[i] = new_dict;
}
deepeach(d, callback);
}
}
@@ -117,7 +124,9 @@ export function get(options) {
top.__utils__get__ = top.__utils__get__ || {};
var cache_key = JSON.stringify(options);
if (options.cache && top.__utils__get__[cache_key]) {
options.success && options.success(top.__utils__get__[cache_key]);
if (options.success) {
options.success(top.__utils__get__[cache_key]);
}
window.console.debug(`utils.js::get() - Fetching from cache [${options.url}].`);
} else {
request({
@@ -125,10 +134,14 @@ export function get(options) {
data: options.data,
success: function(response) {
top.__utils__get__[cache_key] = response;
options.success && options.success(response);
if (options.success) {
options.success(response);
}
},
error: function(response, status) {
options.error && options.error(response, status);
if (options.error) {
options.error(response, status);
}
}
});
}
@@ -169,24 +182,30 @@ export function request(options) {
if (typeof response === "string") {
try {
response = response.replace("Infinity,", '"Infinity",');
response = jQuery.parseJSON(response);
response = $.parseJSON(response);
} catch (e) {
console.debug(e);
}
}
options.success && options.success(response);
if (options.success) {
options.success(response);
}
})
.fail(response => {
var response_text = null;
try {
response_text = jQuery.parseJSON(response.responseText);
response_text = $.parseJSON(response.responseText);
} catch (e) {
response_text = response.responseText;
}
options.error && options.error(response_text, response.status);
if (options.error) {
options.error(response_text, response.status);
}
})
.always(() => {
options.complete && options.complete();
if (options.complete) {
options.complete();
}
});
}
@@ -330,6 +349,11 @@ export function setWindowTitle(title) {
}
}
export function setActiveTab(active_tab) {
$("#masthead .dropdown.active").removeClass("active");
$("#" + active_tab + ">.dropdown").addClass("active");
}
export default {
cssLoadFile: cssLoadFile,
cssGetAttribute: cssGetAttribute,
@@ -349,5 +373,6 @@ export default {
linkify: linkify,
appendScriptStyle: appendScriptStyle,
getQueryString: getQueryString,
setWindowTitle: setWindowTitle
setWindowTitle: setWindowTitle,
setActiveTab: setActiveTab
};
+170 -183
View File
@@ -1,15 +1,15 @@
import _l from "utils/localization";
/**
* Top-level trackster code, used for creating/loading visualizations and user interface elements.
*/
//import * as $ from 'jquery';
import _l from "utils/localization";
import * as _ from "libs/underscore";
import * as Backbone from "libs/backbone";
import tracks from "viz/trackster/tracks";
import visualization from "viz/visualization";
import mod_icon_btn from "mvc/ui/icon-button";
import IconButton from "mvc/ui/icon-button";
import query_string from "utils/query-string-parsing";
import GridView from "mvc/grid/grid-view";
import mod_utils from "utils/utils";
import Utils from "utils/utils";
import "libs/jquery/jquery.event.drag";
import "libs/jquery/jquery.event.hover";
import "libs/jquery/jquery.mousewheel";
@@ -20,44 +20,36 @@ import "libs/jquery/jquery.form";
import "libs/jquery/jquery.rating";
import "ui/editable-text";
// trackster global variables
var ui = null;
var view = null;
var browser_router = null;
/**
* Base Object/Model for inhertiance.
*/
var Base = function() {
if (this.initialize) {
this.initialize.apply(this, arguments);
}
};
Base.extend = Backbone.Model.extend;
/* global Galaxy */
/* global $ */
/**
* User interface controls for trackster
*/
var TracksterUI = Base.extend({
initialize: function(baseURL) {
mod_utils.cssLoadFile("static/style/jquery.rating.css");
mod_utils.cssLoadFile("static/style/autocomplete_tagging.css");
mod_utils.cssLoadFile("static/style/jquery-ui/smoothness/jquery-ui.css");
mod_utils.cssLoadFile("static/style/library.css");
mod_utils.cssLoadFile("static/style/trackster.css");
class TracksterUI extends Backbone.Model {
constructor(options) {
super(options);
}
initialize(baseURL) {
this.baseURL = baseURL;
},
Utils.cssLoadFile("static/style/jquery.rating.css");
Utils.cssLoadFile("static/style/autocomplete_tagging.css");
Utils.cssLoadFile("static/style/jquery-ui/smoothness/jquery-ui.css");
Utils.cssLoadFile("static/style/library.css");
Utils.cssLoadFile("static/style/trackster.css");
}
/**
* Save visualization, returning a Deferred object for the remote call to save.
*/
save_viz: function() {
save_viz() {
// show dialog
Galaxy.modal.show({ title: "Saving...", body: "progress" });
// Save bookmarks.
var bookmarks = [];
$(".bookmark").each(function() {
$(".bookmark").each(() => {
bookmarks.push({
position: $(this)
.children(".position")
@@ -69,14 +61,16 @@ var TracksterUI = Base.extend({
});
// FIXME: give unique IDs to Drawables and save overview as ID.
var overview_track_name = view.overview_drawable ? view.overview_drawable.config.get_value("name") : null;
var overview_track_name = this.view.overview_drawable
? this.view.overview_drawable.config.get_value("name")
: null;
var viz_config = {
view: view.to_dict(),
view: this.view.to_dict(),
viewport: {
chrom: view.chrom,
start: view.low,
end: view.high,
chrom: this.view.chrom,
start: this.view.low,
end: this.view.high,
overview: overview_track_name
},
bookmarks: bookmarks
@@ -88,17 +82,17 @@ var TracksterUI = Base.extend({
type: "POST",
dataType: "json",
data: {
id: view.vis_id,
title: view.config.get_value("name"),
dbkey: view.dbkey,
id: this.view.vis_id,
title: this.view.config.get_value("name"),
dbkey: this.view.dbkey,
type: "trackster",
vis_json: JSON.stringify(viz_config)
}
})
.success(vis_info => {
Galaxy.modal.hide();
view.vis_id = vis_info.vis_id;
view.has_changes = false;
this.view.vis_id = vis_info.vis_id;
this.view.has_changes = false;
// Needed to set URL when first saving a visualization.
window.history.pushState({}, "", vis_info.url + window.location.hash);
@@ -109,29 +103,27 @@ var TracksterUI = Base.extend({
title: _l("Could Not Save"),
body: "Could not save visualization. Please try again later.",
buttons: {
Cancel: function() {
Cancel: () => {
Galaxy.modal.hide();
}
}
});
});
},
}
/**
* Create button menu
*/
createButtonMenu: function() {
var self = this;
var menu = mod_icon_btn.create_icon_buttons_menu(
createButtonMenu() {
var menu = IconButton.create_icon_buttons_menu(
[
{
icon_class: "plus-button",
title: _l("Add tracks"),
on_click: function() {
visualization.select_datasets({ dbkey: view.dbkey }, new_tracks => {
on_click: () => {
visualization.select_datasets({ dbkey: this.view.dbkey }, new_tracks => {
_.each(new_tracks, track => {
view.add_drawable(tracks.object_from_template(track, view, view));
this.view.add_drawable(tracks.object_from_template(track, this.view, this.view));
});
});
}
@@ -139,9 +131,9 @@ var TracksterUI = Base.extend({
{
icon_class: "block--plus",
title: _l("Add group"),
on_click: function() {
view.add_drawable(
new tracks.DrawableGroup(view, view, {
on_click: () => {
this.view.add_drawable(
new tracks.DrawableGroup(this.view, this.view, {
name: "New Group"
})
);
@@ -150,7 +142,7 @@ var TracksterUI = Base.extend({
{
icon_class: "bookmarks",
title: _l("Bookmarks"),
on_click: function() {
on_click: () => {
// HACK -- use style to determine if panel is hidden and hide/show accordingly.
window.force_right_panel($("div#right").css("right") == "0px" ? "hide" : "show");
}
@@ -158,22 +150,22 @@ var TracksterUI = Base.extend({
{
icon_class: "globe",
title: _l("Circster"),
on_click: function() {
window.location = `${self.baseURL}visualization/circster?id=${view.vis_id}`;
on_click: () => {
window.location = `${this.baseURL}visualization/circster?id=${this.view.vis_id}`;
}
},
{
icon_class: "disk--arrow",
title: _l("Save"),
on_click: function() {
self.save_viz();
on_click: () => {
this.save_viz();
}
},
{
icon_class: "cross-circle",
title: _l("Close"),
on_click: function() {
self.handle_unsaved_changes(view);
on_click: () => {
this.handle_unsaved_changes(this.view);
}
}
],
@@ -184,12 +176,12 @@ var TracksterUI = Base.extend({
this.buttonMenu = menu;
return menu;
},
}
/**
* Add bookmark.
*/
add_bookmark: function(position, annotation, editable) {
add_bookmark(position, annotation, editable) {
// Create HTML.
var bookmarks_container = $("#right .unified-panel-body");
@@ -201,11 +193,12 @@ var TracksterUI = Base.extend({
.addClass("position")
.appendTo(new_bookmark);
var position_link = $("<a href=''/>")
//position_link
$("<a href=''/>")
.text(position)
.appendTo(position_div)
.click(() => {
view.go_to(position);
this.view.go_to(position);
return false;
});
@@ -222,11 +215,12 @@ var TracksterUI = Base.extend({
// Remove bookmark.
new_bookmark.slideUp("fast");
new_bookmark.remove();
view.has_changes = true;
this.view.has_changes = true;
return false;
});
var delete_icon = $("<a href=''/>")
// delete_icon
$("<a href=''/>")
.addClass("icon-button delete")
.appendTo(delete_icon_container);
@@ -239,56 +233,50 @@ var TracksterUI = Base.extend({
.addClass("annotation");
}
view.has_changes = true;
this.view.has_changes = true;
return new_bookmark;
},
}
/**
* Create a complete Trackster visualization. Returns view.
*/
create_visualization: function(view_config, viewport_config, drawables_config, bookmarks_config, editable) {
create_visualization(view_config, viewport_config, drawables_config, bookmarks_config, editable) {
// Create view.
var self = this;
this.view = new tracks.TracksterView(_.extend(view_config, { header: false }));
this.view.editor = true;
view = new tracks.TracksterView(_.extend(view_config, { header: false }));
view.editor = true;
$.when(view.load_chroms_deferred).then(chrom_info => {
$.when(this.view.load_chroms_deferred).then(chrom_info => {
var overview_drawable_name = null;
// Viewport config.
if (viewport_config) {
var chrom = viewport_config.chrom;
var start = viewport_config.start;
var end = viewport_config.end;
var overview_drawable_name = viewport_config.overview;
overview_drawable_name = viewport_config.overview;
if (chrom && start !== undefined && end) {
view.change_chrom(chrom, start, end);
this.view.change_chrom(chrom, start, end);
} else {
// No valid viewport, so use first chromosome.
view.change_chrom(chrom_info[0].chrom);
this.view.change_chrom(chrom_info[0].chrom);
}
} else {
// No viewport, so use first chromosome.
view.change_chrom(chrom_info[0].chrom);
this.view.change_chrom(chrom_info[0].chrom);
}
// Add drawables to view.
if (drawables_config) {
// FIXME: can from_dict() be used to create view and add drawables?
var drawable_config;
var drawable_type;
var drawable;
for (var i = 0; i < drawables_config.length; i++) {
view.add_drawable(tracks.object_from_template(drawables_config[i], view, view));
for (let i = 0; i < drawables_config.length; i++) {
this.view.add_drawable(tracks.object_from_template(drawables_config[i], this.view, this.view));
}
}
// Set overview.
var overview_drawable;
for (var i = 0; i < view.drawables.length; i++) {
if (view.drawables[i].config.get_value("name") === overview_drawable_name) {
view.set_overview(view.drawables[i]);
for (let i = 0; i < this.view.drawables.length; i++) {
if (this.view.drawables[i].config.get_value("name") === overview_drawable_name) {
this.view.set_overview(this.view.drawables[i]);
break;
}
}
@@ -296,34 +284,35 @@ var TracksterUI = Base.extend({
// Load bookmarks.
if (bookmarks_config) {
var bookmark;
for (var i = 0; i < bookmarks_config.length; i++) {
for (let i = 0; i < bookmarks_config.length; i++) {
bookmark = bookmarks_config[i];
self.add_bookmark(bookmark["position"], bookmark["annotation"], editable);
this.add_bookmark(bookmark.position, bookmark.annotation, editable);
}
}
// View has no changes as of yet.
view.has_changes = false;
this.view.has_changes = false;
});
// Final initialization.
this.set_up_router({ view: view });
this.set_up_router({ view: this.view });
return view;
},
// TODO: This is hopefully not necessary anymore, since we're using the instance view. Do it for compatibility for now.
return this.view;
}
/**
* Set up location router to use hashes as track browser locations.
*/
set_up_router: function(options) {
set_up_router(options) {
new visualization.TrackBrowserRouter(options);
Backbone.history.start();
},
}
/**
* Set up keyboard navigation for a visualization.
*/
init_keyboard_nav: function(view) {
init_keyboard_nav(view) {
// Keyboard navigation. Scroll ~7% of height when scrolling up/down.
$(document).keyup(e => {
// Do not navigate if arrow keys used in input element.
@@ -337,39 +326,38 @@ var TracksterUI = Base.extend({
view.move_fraction(0.25);
break;
case 38:
var change = Math.round(view.viewport_container.height() / 15.0);
// var change = Math.round(view.viewport_container.height() / 15.0);
view.viewport_container.scrollTop(view.viewport_container.scrollTop() - 20);
break;
case 39:
view.move_fraction(-0.25);
break;
case 40:
var change = Math.round(view.viewport_container.height() / 15.0);
// var change = Math.round(view.viewport_container.height() / 15.0);
view.viewport_container.scrollTop(view.viewport_container.scrollTop() + 20);
break;
}
});
},
}
/**
* Handle unsaved changes in visualization.
*/
handle_unsaved_changes: function(view) {
handle_unsaved_changes(view) {
if (view.has_changes) {
var self = this;
Galaxy.modal.show({
title: _l("Close visualization"),
body: "There are unsaved changes to your visualization which will be lost if you do not save them.",
buttons: {
Cancel: function() {
Cancel: () => {
Galaxy.modal.hide();
},
"Leave without Saving": function() {
"Leave without Saving": () => {
$(window).off("beforeunload");
window.location = `${Galaxy.root}visualization`;
},
Save: function() {
$.when(self.save_viz()).then(() => {
Save: () => {
$.when(this.save_viz()).then(() => {
window.location = `${Galaxy.root}visualization`;
});
}
@@ -379,22 +367,25 @@ var TracksterUI = Base.extend({
window.location = `${Galaxy.root}visualization`;
}
}
});
}
var TracksterView = Backbone.View.extend({
class TracksterUIView extends Backbone.View {
constructor(options) {
super(options);
}
// initalize trackster
initialize: function() {
initialize() {
// load ui
ui = new TracksterUI(Galaxy.root);
this.ui = new TracksterUI(Galaxy.root);
// create button menu
ui.createButtonMenu();
this.ui.createButtonMenu();
// attach the button menu to the panel header and float it left
ui.buttonMenu.$el.attr("style", "float: right");
this.ui.buttonMenu.$el.attr("style", "float: right");
// add to center panel
$("#center .unified-panel-header-inner").append(ui.buttonMenu.$el);
$("#center .unified-panel-header-inner").append(this.ui.buttonMenu.$el);
// configure right panel
$("#right .unified-panel-title").append("Bookmarks");
@@ -404,7 +395,7 @@ var TracksterView = Backbone.View.extend({
// resize view when showing/hiding right panel (bookmarks for now).
$("#right-border").click(() => {
view.resize_window();
this.ui.view.resize_window();
});
// hide right panel
@@ -418,10 +409,9 @@ var TracksterView = Backbone.View.extend({
} else {
this.view_new();
}
},
}
choose_existing_or_new: function() {
var self = this;
choose_existing_or_new() {
var dbkey = query_string.get("dbkey");
var listTracksParams = {};
@@ -442,21 +432,21 @@ var TracksterView = Backbone.View.extend({
body: `<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 ||
"Not available."}</b></li><li>or create a new session with this dataset as the only track</li></ul></p>`,
buttons: {
Cancel: function() {
Cancel: () => {
window.location = `${Galaxy.root}visualizations/list`;
},
"View in saved visualization": function() {
self.view_in_saved(dataset_params);
"View in saved visualization": () => {
this.view_in_saved(dataset_params);
},
"View in new visualization": function() {
self.view_new();
"View in new visualization": () => {
this.view_new();
}
}
});
},
}
// view
view_in_saved: function(dataset_params) {
view_in_saved(dataset_params) {
var tracks_grid = new GridView({
url_base: `${Galaxy.root}visualization/list_tracks`,
embedded: true
@@ -465,28 +455,28 @@ var TracksterView = Backbone.View.extend({
title: _l("Add Data to Saved Visualization"),
body: tracks_grid.$el,
buttons: {
Cancel: function() {
Cancel: () => {
window.location = `${Galaxy.root}visualizations/list`;
},
"Add to visualization": function() {
$(parent.document)
"Add to visualization": () => {
$(window.parent.document)
.find("input[name=id]:checked")
.each(function() {
.each(() => {
dataset_params.id = $(this).val();
window.location = `${Galaxy.root}visualization/trackster?${$.param(dataset_params)}`;
});
}
}
});
},
}
// view
view_existing: function() {
view_existing() {
// get config
var viz_config = window.galaxy_config.app.viz_config;
// view
view = ui.create_visualization(
this.ui.create_visualization(
{
container: $("#center .unified-panel-body"),
name: viz_config.title,
@@ -501,31 +491,28 @@ var TracksterView = Backbone.View.extend({
// initialize editor
this.init_editor();
},
}
// view
view_new: function() {
// reference this
var self = this;
view_new() {
// ajax
$.ajax({
url: `${Galaxy.root}api/genomes?chrom_info=True`,
data: {},
error: function() {
error: () => {
alert("Couldn't create new browser.");
},
success: function(response) {
success: response => {
// show dialog
Galaxy.modal.show({
title: _l("New Visualization"),
body: self.template_view_new(response),
body: this.template_view_new(response),
buttons: {
Cancel: function() {
Cancel: () => {
window.location = `${Galaxy.root}visualizations/list`;
},
Create: function() {
self.create_browser($("#new-title").val(), $("#new-dbkey").val());
Create: () => {
this.create_browser($("#new-title").val(), $("#new-dbkey").val());
Galaxy.modal.hide();
}
}
@@ -548,10 +535,10 @@ var TracksterView = Backbone.View.extend({
$("#overlay").css("overflow", "auto");
}
});
},
}
// new browser form
template_view_new: function(response) {
template_view_new(response) {
// start template
var html =
'<form id="new-browser-form" action="javascript:void(0);" method="post" onsubmit="return false;">' +
@@ -568,7 +555,7 @@ var TracksterView = Backbone.View.extend({
'<select name="dbkey" id="new-dbkey">';
// add dbkeys
for (var i = 0; i < response.length; i++) {
for (let i = 0; i < response.length; i++) {
html += `<option value="${response[i][1]}">${response[i][0]}</option>`;
}
@@ -579,13 +566,48 @@ var TracksterView = Backbone.View.extend({
// return
return html;
},
}
// initialization for editor-specific functions.
init_editor() {
// set title
$("#center .unified-panel-title").text(`${this.ui.view.config.get_value("name")} (${this.ui.view.dbkey})`);
// add dataset
if (window.galaxy_config.app.add_dataset)
$.ajax({
url: `${Galaxy.root}api/datasets/${window.galaxy_config.app.add_dataset}`,
data: { hda_ldda: "hda", data_type: "track_config" },
dataType: "json",
success: track_data => {
this.ui.view.add_drawable(tracks.object_from_template(track_data, this.ui.view, this.ui.view));
}
});
// initialize icons
$("#add-bookmark-button").click(() => {
// add new bookmark.
var position = `${this.ui.view.chrom}:${this.ui.view.low}-${this.ui.view.high}`;
var annotation = "Bookmark description";
return this.ui.add_bookmark(position, annotation, true);
});
// initialize keyboard
this.ui.init_keyboard_nav(this.ui.view);
$(window).on("beforeunload", () => {
if (this.ui.view.has_changes) {
return "There are unsaved changes to your visualization that will be lost if you leave this page.";
}
});
}
// create
create_browser: function(name, dbkey) {
create_browser(name, dbkey) {
$(document).trigger("convert_to_values");
view = ui.create_visualization(
this.ui.create_visualization(
{
container: $("#center .unified-panel-body"),
name: name,
@@ -598,46 +620,11 @@ var TracksterView = Backbone.View.extend({
this.init_editor();
// modify view setting
view.editor = true;
},
// initialization for editor-specific functions.
init_editor: function() {
// set title
$("#center .unified-panel-title").text(`${view.config.get_value("name")} (${view.dbkey})`);
// add dataset
if (window.galaxy_config.app.add_dataset)
$.ajax({
url: `${Galaxy.root}api/datasets/${window.galaxy_config.app.add_dataset}`,
data: { hda_ldda: "hda", data_type: "track_config" },
dataType: "json",
success: function(track_data) {
view.add_drawable(tracks.object_from_template(track_data, view, view));
}
});
// initialize icons
$("#add-bookmark-button").click(() => {
// add new bookmark.
var position = `${view.chrom}:${view.low}-${view.high}`;
var annotation = "Bookmark description";
return ui.add_bookmark(position, annotation, true);
});
// initialize keyboard
ui.init_keyboard_nav(view);
$(window).on("beforeunload", () => {
if (view.has_changes) {
return "There are unsaved changes to your visualization that will be lost if you leave this page.";
}
});
this.ui.view.editor = true;
}
});
}
export default {
TracksterUI: TracksterUI,
GalaxyApp: TracksterView
GalaxyApp: TracksterUIView
};
@@ -151,7 +151,7 @@ var NumberFilter = function(obj_dict) {
.addClass("elt-label")
.appendTo(filter.parent_div);
var name_span = $("<span/>")
$("<span/>")
.addClass("slider-name")
.text(`${filter.name} `)
.appendTo(filter_label);
@@ -341,9 +341,6 @@ extend(NumberFilter.prototype, {
return true;
}
// Keep value function.
var filter = this;
// Do filtering.
var to_filter = element[this.index];
if (to_filter instanceof Array) {
@@ -482,7 +479,7 @@ var FiltersManager = function(track, obj_dict) {
filter.height_icon.addClass("active").show();
}
} else {
console.log("ERROR: unsupported filter: ", name, type);
console.log("ERROR: unsupported filter: ", filters_dict[i]);
}
}
@@ -656,6 +653,7 @@ extend(FiltersManager.prototype, {
// Remove current filter.
filters = filters.slice(1);
// DBTODO: This will never work, run_tool_url doesn't exist?
$.getJSON(run_tool_url, url_params, response => {
if (response.error) {
// General error.
File diff suppressed because it is too large Load Diff
@@ -45,7 +45,7 @@ extend(FeatureSlotter.prototype, {
var draw_end = Math.ceil(feature[2] * this.w_scale);
var f_name = feature[3];
var text_align;
//var text_align;
// Update start, end drawing locations to include feature name.
// Try to put the name on the left, if not, put on right.
@@ -55,10 +55,10 @@ extend(FeatureSlotter.prototype, {
var text_len = this.measureText(f_name).width + (LABEL_SPACING + PACK_SPACING);
if (draw_start - text_len >= 0) {
draw_start -= text_len;
text_align = "left";
//text_align = "left";
} else {
draw_end += text_len;
text_align = "right";
//text_align = "right";
}
}
@@ -130,7 +130,7 @@ extend(FeatureSlotter.prototype, {
// Loop through features to (a) find those that are not yet slotted and (b) update
// those that are slotted if new information is availabe. For (a), features already
// slotted (based on slotting from other tiles) will retain their current slot.
for (var i = 0, len = features.length; i < len; i++) {
for (let i = 0, len = features.length; i < len; i++) {
feature = features[i];
feature_uid = feature[0];
var slotted_info = this.slots[feature_uid];
@@ -163,7 +163,7 @@ extend(FeatureSlotter.prototype, {
// Slot unslotted features.
// Do slotting.
for (var i = 0, len = undone.length; i < len; i++) {
for (let i = 0, len = undone.length; i < len; i++) {
feature = features[undone[i]];
feature_uid = feature[0];
var draw_coords = this._get_draw_coords(feature);
+167 -173
View File
@@ -1,5 +1,6 @@
import _l from "utils/localization";
import * as _ from "libs/underscore";
import * as Backbone from "libs/backbone";
import visualization from "viz/visualization";
import viz_views from "viz/viz_views";
import util from "viz/trackster/util";
@@ -13,6 +14,9 @@ import bbi from "viz/bbi-data-manager";
import "ui/editable-text";
var extend = _.extend;
/* global $ */
/* global Galaxy */
// ---- Web UI specific utilities ----
/**
@@ -24,9 +28,9 @@ var html_elt_js_obj_dict = {};
/**
* Designates an HTML as a container.
*/
var is_container = (element, obj) => {
function is_container(element, obj) {
html_elt_js_obj_dict[element.attr("id")] = obj;
};
}
/**
* Make `element` moveable within parent and sibling elements by dragging `handle` (a selector).
@@ -37,7 +41,7 @@ var is_container = (element, obj) => {
* @param container_selector selector used to identify possible containers for this element
* @param element_js_obj JavaScript object associated with element; used
*/
var moveable = (element, handle_class, container_selector, element_js_obj) => {
function moveable(element, handle_class, container_selector, element_js_obj) {
// HACK: set default value for container selector.
container_selector = ".group";
@@ -48,11 +52,9 @@ var moveable = (element, handle_class, container_selector, element_js_obj) => {
element
.bind("drag", { handle: `.${handle_class}`, relative: true }, function(e, d) {
var element = $(this);
var parent = $(this).parent();
var // Only sorting amongst tracks and groups.
children = parent.children(".track,.group");
var parent = element.parent();
// Only sorting amongst tracks and groups.
var children = parent.children(".track,.group");
var this_obj = html_elt_js_obj_dict[$(this).attr("id")];
var child;
var container;
@@ -144,46 +146,47 @@ var moveable = (element, handle_class, container_selector, element_js_obj) => {
.bind("dragend", function() {
$(this).removeClass("dragging");
});
};
}
/**
* Init constants & functions used throughout trackster.
*/
var // Padding at the top of tracks for error messages
ERROR_PADDING = 20;
var // Maximum number of rows un a slotted track
MAX_FEATURE_DEPTH = 100;
// Padding at the top of tracks for error messages
const ERROR_PADDING = 20;
var // Minimum width for window for squish to be used.
MIN_SQUISH_VIEW_WIDTH = 12000;
// Maximum number of rows un a slotted track
const MAX_FEATURE_DEPTH = 100;
var // Number of pixels per tile, not including left offset.
TILE_SIZE = 400;
// Minimum width for window for squish to be used.
const MIN_SQUISH_VIEW_WIDTH = 12000;
var DEFAULT_DATA_QUERY_WAIT = 5000;
// Number of pixels per tile, not including left offset.
const TILE_SIZE = 400;
var // Maximum number of chromosomes that are selectable at any one time.
MAX_CHROMS_SELECTABLE = 100;
const DEFAULT_DATA_QUERY_WAIT = 5000;
var DATA_ERROR = "Cannot display dataset due to an error. ";
// Maximum number of chromosomes that are selectable at any one time.
const MAX_CHROMS_SELECTABLE = 100;
var DATA_NOCONVERTER = "A converter for this dataset is not installed. Please check your datatypes_conf.xml file.";
const DATA_ERROR = "Cannot display dataset due to an error. ";
var DATA_NONE = "No data for this chrom/contig.";
const DATA_NOCONVERTER = "A converter for this dataset is not installed. Please check your datatypes_conf.xml file.";
var DATA_PENDING =
const DATA_NONE = "No data for this chrom/contig.";
const DATA_PENDING =
"Preparing data. This can take a while for a large dataset. " +
"If the visualization is saved and closed, preparation will continue in the background.";
var DATA_CANNOT_RUN_TOOL = "Tool cannot be rerun: ";
var DATA_LOADING = "Loading data...";
var DATA_OK = "Ready for display";
var TILE_CACHE_SIZE = 10;
var DATA_CACHE_SIZE = 20;
var // Numerical/continuous data display modes.
CONTINUOUS_DATA_MODES = ["Histogram", "Line", "Filled", "Intensity"];
const DATA_CANNOT_RUN_TOOL = "Tool cannot be rerun: ";
//var DATA_LOADING = "Loading data...";
const DATA_OK = "Ready for display";
const TILE_CACHE_SIZE = 10;
//var DATA_CACHE_SIZE = 20;
//
// Numerical/continuous data display modes.
const CONTINUOUS_DATA_MODES = ["Histogram", "Line", "Filled", "Intensity"];
/**
* Round a number to a given number of decimal places.
@@ -248,7 +251,10 @@ var Drawable = function(view, container, obj_dict) {
this.action_icons = {};
// -- Set up drawable configuration. --
this.config = config_mod.ConfigSettingCollection.from_models_and_saved_values(this.config_params, obj_dict.prefs);
this.config = config_mod.ConfigSettingCollection.from_models_and_saved_values(
this.build_config_params(),
obj_dict.prefs
);
// If there's no saved name, use object name.
if (!this.config.get_value("name")) {
@@ -342,6 +348,10 @@ extend(Drawable.prototype, {
}
],
build_config_params: function() {
return this.config_params;
},
config_onchange: function() {},
init: function() {},
@@ -723,7 +733,6 @@ extend(DrawableGroup.prototype, Drawable.prototype, DrawableCollection.prototype
for (i = 0; i < num_drawables; i++) {
drawable = this.drawables[i];
if (drawable.get_type() !== a_type) {
can_composite = false;
break;
}
if (drawable instanceof FeatureTrack) {
@@ -772,16 +781,12 @@ extend(DrawableGroup.prototype, Drawable.prototype, DrawableCollection.prototype
// manager.
//
this.filters_manager.remove_all();
var filters;
var new_filter;
var min;
var max;
for (var filter_name in shared_filters) {
filters = shared_filters[filter_name];
let filters = shared_filters[filter_name];
if (filters.length === num_feature_tracks) {
// Add new filter.
// FIXME: can filter.copy() be used?
new_filter = new filters_mod.NumberFilter({
let new_filter = new filters_mod.NumberFilter({
name: filters[0].name,
index: filters[0].index
});
@@ -834,7 +839,7 @@ extend(DrawableGroup.prototype, Drawable.prototype, DrawableCollection.prototype
name: this.config.get_value("name"),
drawables: this.drawables
});
var index = this.container.replace_drawable(this, composite_track, true);
this.container.replace_drawable(this, composite_track, true);
composite_track.request_draw();
},
@@ -981,7 +986,8 @@ var TracksterView = Backbone.View.extend({
this.intro_div = $("<div/>")
.addClass("intro")
.appendTo(this.viewport_container);
var add_tracks_button = $("<div/>")
// Add tracks button
$("<div/>")
.text("Add Datasets to Visualization")
.addClass("action-button")
.appendTo(this.intro_div)
@@ -1270,11 +1276,11 @@ var TracksterView = Backbone.View.extend({
$(window).bind("resize", function() {
// Stop previous timer.
if (this.resize_timer) {
clearTimeout(this.resize_timer);
window.clearTimeout(this.resize_timer);
}
// When function activated, resize window and redraw.
this.resize_timer = setTimeout(() => {
this.resize_timer = window.setTimeout(() => {
view.resize_window();
}, 500);
});
@@ -1309,18 +1315,17 @@ extend(TracksterView.prototype, DrawableCollection.prototype, {
trigger_navigate: function(new_chrom, new_low, new_high, delay) {
// Stop previous timer.
if (this.timer) {
clearTimeout(this.timer);
window.clearTimeout(this.timer);
}
if (delay) {
// To aggregate calls, use timer and only navigate once
// location has stabilized.
var self = this;
this.timer = setTimeout(() => {
self.trigger("navigate", `${new_chrom}:${new_low}-${new_high}`);
this.timer = window.setTimeout(() => {
this.trigger("navigate", `${new_chrom}:${new_low}-${new_high}`);
}, 500);
} else {
view.trigger("navigate", `${new_chrom}:${new_low}-${new_high}`);
this.trigger("navigate", `${new_chrom}:${new_low}-${new_high}`);
}
},
@@ -1343,13 +1348,12 @@ extend(TracksterView.prototype, DrawableCollection.prototype, {
load_chroms: function(url_parms) {
url_parms.num = MAX_CHROMS_SELECTABLE;
var view = this;
var chrom_data = $.Deferred();
$.ajax({
url: `${Galaxy.root}api/genomes/${this.dbkey}`,
data: url_parms,
dataType: "json",
success: function(result) {
success: result => {
// Do nothing if could not load chroms.
if (result.chrom_info.length === 0) {
return;
@@ -1357,34 +1361,34 @@ extend(TracksterView.prototype, DrawableCollection.prototype, {
// Load chroms.
if (result.reference) {
var ref_track = new ReferenceTrack(view);
view.add_label_track(ref_track);
view.reference_track = ref_track;
var ref_track = new ReferenceTrack(this);
this.add_label_track(ref_track);
this.reference_track = ref_track;
}
view.chrom_data = result.chrom_info;
this.chrom_data = result.chrom_info;
view.chrom_select.html("");
view.chrom_select.append($('<option value="">Select Chrom/Contig</option>'));
this.chrom_select.html("");
this.chrom_select.append($('<option value="">Select Chrom/Contig</option>'));
for (var i = 0, len = view.chrom_data.length; i < len; i++) {
var chrom = view.chrom_data[i].chrom;
for (var i = 0, len = this.chrom_data.length; i < len; i++) {
var chrom = this.chrom_data[i].chrom;
var chrom_option = $("<option>");
chrom_option.text(chrom);
chrom_option.val(chrom);
view.chrom_select.append(chrom_option);
this.chrom_select.append(chrom_option);
}
if (result.prev_chroms) {
view.chrom_select.append($(`<option value="previous">Previous ${MAX_CHROMS_SELECTABLE}</option>`));
this.chrom_select.append($(`<option value="previous">Previous ${MAX_CHROMS_SELECTABLE}</option>`));
}
if (result.next_chroms) {
view.chrom_select.append($(`<option value="next">Next ${MAX_CHROMS_SELECTABLE}</option>`));
this.chrom_select.append($(`<option value="next">Next ${MAX_CHROMS_SELECTABLE}</option>`));
}
view.chrom_start_index = result.start_index;
this.chrom_start_index = result.start_index;
chrom_data.resolve(result.chrom_info);
},
error: function() {
alert(`Could not load chroms for this dbkey: ${view.dbkey}`);
alert(`Could not load chroms for this dbkey: ${this.dbkey}`);
}
});
return chrom_data;
@@ -1491,7 +1495,7 @@ extend(TracksterView.prototype, DrawableCollection.prototype, {
str = str.replace(/,/g, "");
// Replace colons and hyphens with space for easy parsing.
str = str.replace(/:|\-/g, " ");
str = str.replace(/:|-/g, " ");
// Parse new location.
var chrom_pos = str.split(/\s+/);
@@ -1537,10 +1541,10 @@ extend(TracksterView.prototype, DrawableCollection.prototype, {
// Set up timeout to redraw with more data when moving stops.
if (this.redraw_on_move_fn) {
clearTimeout(this.redraw_on_move_fn);
window.clearTimeout(this.redraw_on_move_fn);
}
this.redraw_on_move_fn = setTimeout(() => {
this.redraw_on_move_fn = window.setTimeout(() => {
view.request_redraw();
}, 200);
@@ -1617,7 +1621,7 @@ extend(TracksterView.prototype, DrawableCollection.prototype, {
// Set up redraw if it has not been requested since last redraw.
if (!this.requested_redraw) {
requestAnimationFrame(() => {
window.requestAnimationFrame(() => {
view._redraw();
});
this.requested_redraw = true;
@@ -1779,8 +1783,8 @@ extend(TracksterView.prototype, DrawableCollection.prototype, {
this.overview_box.height(this.default_overview_height);
this.overview_close.hide();
this.overview_highlight.hide();
view.resize_window();
view.overview_drawable = null;
this.resize_window();
this.overview_drawable = null;
}
});
@@ -1828,7 +1832,7 @@ var ToolParameterView = Backbone.View.extend({
var param = this.model;
// Param label.
var label_div = $("<div>")
$("<div>")
.addClass("param-label")
.text(param.get("label"))
.appendTo(param_div);
@@ -1881,7 +1885,7 @@ var TracksterToolView = Backbone.View.extend({
});
// Add name, inputs.
var name_div = $("<div class='tool-name'>")
$("<div class='tool-name'>")
.appendTo(parent_div)
.text(tool.get("name"));
tool.get("inputs").each(param => {
@@ -1989,44 +1993,42 @@ var TracksterToolView = Backbone.View.extend({
regions: [region.toJSON()]
};
var current_track = track;
var // Set name of track to include tool name, parameters, and region used.
track_name = tool.get("name") + current_track.tool_region_and_parameters_str(region);
// Set name of track to include tool name, parameters, and region used.
var track_name = tool.get("name") + track.tool_region_and_parameters_str(region);
var container;
// If track not in a group, create a group for it and add new track to group. If track
// already in group, add track to group.
if (current_track.container === view) {
if (track.container === track.view) {
// Create new group.
var group = new DrawableGroup(view, view, {
var group = new DrawableGroup(track.view, track.view, {
name: track.config.get_value("name")
});
// Replace track with group.
var index = current_track.container.replace_drawable(current_track, group, false);
var index = track.container.replace_drawable(track, group, false);
// Update HTML.
// FIXME: this is ugly way to replace a track with a group -- make this easier via
// a Drawable or DrawableCollection function.
group.container_div.insertBefore(current_track.view.content_div.children()[index]);
group.add_drawable(current_track);
current_track.container_div.appendTo(group.content_div);
group.container_div.insertBefore(track.view.content_div.children()[index]);
group.add_drawable(track);
track.container_div.appendTo(group.content_div);
container = group;
} else {
// Use current group.
container = current_track.container;
container = track.container;
}
// Create and init new track.
var new_track = new current_track.constructor(view, container, {
var new_track = new track.constructor(track.view, container, {
name: track_name,
hda_ldda: "hda"
});
new_track.init_for_tool_data();
new_track.change_mode(current_track.mode);
new_track.set_filters_manager(current_track.filters_manager.copy(new_track));
new_track.change_mode(track.mode);
new_track.set_filters_manager(track.filters_manager.copy(new_track));
new_track.update_icons();
container.add_drawable(new_track);
new_track.tiles_div.text("Starting job.");
@@ -2441,12 +2443,17 @@ extend(Track.prototype, Drawable.prototype, {
name: "param_space_viz_icon",
title: _l("Tool parameter space visualization"),
css_class: "arrow-split",
on_click_fn: function(track) {
var html = `<strong>Tool</strong>:${track.tool.get(
"name"
)}<br/><strong>Dataset</strong>:${track.config.get_value(
"name"
)}<br/><strong>Region(s)</strong>: <select name="regions"><option value="cur">current viewing area</option><option value="bookmarks">bookmarks</option><option value="both">current viewing area and bookmarks</option></select>`;
on_click_fn: track => {
var html = `
<strong>Tool</strong>:${track.tool.get("name")}<br/>
<strong>Dataset</strong>:${track.config.get_value("name")}<br/>
<strong>Region(s)</strong>:
<select name="regions">
<option value="cur">current viewing area</option>
<option value="bookmarks">bookmarks</option>
<option value="both">current viewing area and bookmarks</option>
</select>
`;
var cancel_fn = () => {
Galaxy.modal.hide();
@@ -2454,22 +2461,22 @@ extend(Track.prototype, Drawable.prototype, {
};
var ok_fn = () => {
var regions_to_use = $('select[name="regions"] option:selected').val(),
regions,
view_region = new visualization.GenomeRegion({
chrom: view.chrom,
start: view.low,
end: view.high
}),
bookmarked_regions = _.map(
$(".bookmark"),
elt =>
new visualization.GenomeRegion({
from_str: $(elt)
.children(".position")
.text()
})
);
var regions_to_use = $('select[name="regions"] option:selected').val();
var regions;
var view_region = new visualization.GenomeRegion({
chrom: this.view.chrom,
start: this.view.low,
end: this.view.high
});
var bookmarked_regions = _.map(
$(".bookmark"),
elt =>
new visualization.GenomeRegion({
from_str: $(elt)
.children(".position")
.text()
})
);
// Get regions for visualization.
if (regions_to_use === "cur") {
@@ -2493,6 +2500,8 @@ extend(Track.prototype, Drawable.prototype, {
})}`;
};
/*
* TODO: Re-enable this when functional.
var check_enter_esc = e => {
if ((e.keyCode || e.which) === 27) {
// Escape key
@@ -2502,6 +2511,7 @@ extend(Track.prototype, Drawable.prototype, {
ok_fn();
}
};
*/
// show dialog
Galaxy.modal.show({
@@ -2732,7 +2742,7 @@ extend(Track.prototype, Drawable.prototype, {
track.container_div.addClass("pending");
track.show_message(DATA_PENDING);
//$("<img/>").attr("src", image_path + "/yui/rel_interstitial_loading.gif").appendTo(track.tiles_div);
setTimeout(() => {
window.setTimeout(() => {
track.init();
}, track.data_query_wait);
} else if (result === "data" || result.status === "data") {
@@ -3040,7 +3050,7 @@ extend(TiledTrack.prototype, Drawable.prototype, Track.prototype, {
var clear_after = options && options.clear_after;
var low = this.view.low;
var high = this.view.high;
var range = high - low;
//var range = high - low;
var width = this.view.container.width();
var w_scale = this.view.resolution_px_b;
var resolution = 1 / w_scale;
@@ -3049,7 +3059,7 @@ extend(TiledTrack.prototype, Drawable.prototype, Track.prototype, {
if (this.is_overview) {
low = this.view.max_low;
high = this.view.max_high;
w_scale = width / (view.max_high - view.max_low);
w_scale = width / (this.view.max_high - this.view.max_low);
resolution = 1 / w_scale;
}
@@ -3105,17 +3115,16 @@ extend(TiledTrack.prototype, Drawable.prototype, Track.prototype, {
}
// When all tiles are drawn, call post-draw actions.
var track = this;
$.when.apply($, tile_promises).then(() => {
// Step (c) for (re)moving tiles when clear_after is true:
track.tiles_div.children(".remove").remove();
this.tiles_div.children(".remove").remove();
// Only do postdraw actions for tiles; instances where tiles may not be drawn include:
// (a) ReferenceTrack without sufficient resolution;
// (b) data_fetch = false.
tiles = _.filter(tiles, t => t !== null);
if (tiles.length !== 0) {
track.postdraw_actions(tiles, width, w_scale, clear_after);
this.postdraw_actions(tiles, width, w_scale, clear_after);
}
});
},
@@ -3124,18 +3133,17 @@ extend(TiledTrack.prototype, Drawable.prototype, Track.prototype, {
* Add a maximum/minimum label to track.
*/
_add_yaxis_label: function(type, on_change) {
var track = this;
var css_class = type === "max" ? "top" : "bottom";
var text = type === "max" ? "max" : "min";
var pref_name = type === "max" ? "max_value" : "min_value";
var label = this.container_div.find(`.yaxislabel.${css_class}`);
var value = round(track.config.get_value(pref_name), 1);
var value = round(this.config.get_value(pref_name), 1);
// Default action for on_change is to redraw track.
on_change =
on_change ||
(() => {
track.request_draw({ clear_tile_cache: true });
this.request_draw({ clear_tile_cache: true });
});
if (label.length !== 0) {
@@ -3149,7 +3157,7 @@ extend(TiledTrack.prototype, Drawable.prototype, Track.prototype, {
num_cols: 12,
on_finish: function(new_val) {
$(".tooltip").remove();
track.config.set_value(pref_name, round(new_val, 1));
this.config.set_value(pref_name, round(new_val, 1));
on_change();
},
help_text: `Set ${text} value`
@@ -3176,18 +3184,17 @@ extend(TiledTrack.prototype, Drawable.prototype, Track.prototype, {
// Clear because this is set when drawing.
this.max_height_px = 0;
var track = this;
_.each(tiles, tile => {
if (!(tile instanceof LineTrackTile)) {
tile.html_elt.remove();
track.draw_helper(tile.region, w_scale, {
this.draw_helper(tile.region, w_scale, {
force: true,
mode: "Coverage"
});
}
});
track._add_yaxis_label("max");
this._add_yaxis_label("max");
} else {
// -- Drawing in non-Coverage mode. --
@@ -3247,19 +3254,16 @@ extend(TiledTrack.prototype, Drawable.prototype, Track.prototype, {
var mode = options.mode || this.mode;
var resolution = 1 / w_scale;
var // Useful vars.
track = this;
var drawables = this._get_drawables();
var key = this._gen_tile_cache_key(w_scale, region);
var is_tile = o => o && "track" in o;
// Check tile cache, if found show existing tile in correct position
var tile = force ? undefined : track.tile_cache.get_elt(key);
var tile = force ? undefined : this.tile_cache.get_elt(key);
if (tile) {
if (is_tile(tile)) {
track.show_tile(tile, w_scale);
this.show_tile(tile, w_scale);
}
return tile;
}
@@ -3277,16 +3281,16 @@ extend(TiledTrack.prototype, Drawable.prototype, Track.prototype, {
// Map drawable object to data needed for drawing.
var tile_data = _.map(drawables, (
d // Get the track data/promise.
) => d.data_manager.get_data(region, data_mode, resolution, track.data_url_extra_params));
) => d.data_manager.get_data(region, data_mode, resolution, this.data_url_extra_params));
// Get reference data/promise.
if (view.reference_track) {
if (this.view.reference_track) {
tile_data.push(
view.reference_track.data_manager.get_data(
this.view.reference_track.data_manager.get_data(
region,
mode,
resolution,
view.reference_track.data_url_extra_params
this.view.reference_track.data_url_extra_params
)
);
}
@@ -3298,7 +3302,7 @@ extend(TiledTrack.prototype, Drawable.prototype, Track.prototype, {
// When data is available, draw tile.
//
var tile_drawn = $.Deferred();
track.tile_cache.set_elt(key, tile_drawn);
this.tile_cache.set_elt(key, tile_drawn);
$.when.apply($, get_tile_data()).then(() => {
var tile_data = get_tile_data();
var tracks_data = tile_data;
@@ -3309,41 +3313,36 @@ extend(TiledTrack.prototype, Drawable.prototype, Track.prototype, {
// Deferred, try again from the top. NOTE: this condition could (should?) be handled by the
// GenomeDataManager in visualization module.
if (_.find(tile_data, d => util.is_deferred(d))) {
track.tile_cache.set_elt(key, undefined);
$.when(track.draw_helper(region, w_scale, options)).then(tile => {
this.tile_cache.set_elt(key, undefined);
$.when(this.draw_helper(region, w_scale, options)).then(tile => {
tile_drawn.resolve(tile);
});
return;
}
// If sequence data is available, subset to get only data in region.
if (view.reference_track) {
seq_data = view.reference_track.data_manager.subset_entry(tile_data.pop(), region);
if (this.view.reference_track) {
seq_data = this.view.reference_track.data_manager.subset_entry(tile_data.pop(), region);
}
// Get drawing modes, heights for all tracks.
var drawing_modes = [];
var drawing_heights = [];
_.each(drawables, (d, i) => {
var mode = d.mode;
var data = tracks_data[i];
if (mode === "Auto") {
mode = d.get_mode(data);
d.update_auto_mode(mode);
if (d.mode === "Auto") {
d.mode = d.get_mode(data);
d.update_auto_mode(d.mode);
}
drawing_modes.push(mode);
drawing_heights.push(d.get_canvas_height(data, mode, w_scale, width));
drawing_modes.push(d.mode);
drawing_heights.push(d.get_canvas_height(data, d.mode, w_scale, width));
});
var canvas = track.view.canvas_manager.new_canvas();
var canvas = this.view.canvas_manager.new_canvas();
var tile_low = region.get("start");
var tile_high = region.get("end");
var all_data_index = 0;
var width = Math.ceil((tile_high - tile_low) * w_scale) + track.left_offset;
var width = Math.ceil((tile_high - tile_low) * w_scale) + this.left_offset;
var height = _.max(drawing_heights);
var tile;
@@ -3354,7 +3353,7 @@ extend(TiledTrack.prototype, Drawable.prototype, Track.prototype, {
// Height is specified in options or is the height found above.
canvas.height = options.height || height;
var ctx = canvas.getContext("2d");
ctx.translate(track.left_offset, 0);
ctx.translate(this.left_offset, 0);
if (drawables.length > 1) {
ctx.globalAlpha = 0.5;
ctx.globalCompositeOperation = "source-over";
@@ -3365,8 +3364,8 @@ extend(TiledTrack.prototype, Drawable.prototype, Track.prototype, {
// Don't cache, show if no tile.
if (tile !== undefined) {
track.tile_cache.set_elt(key, tile);
track.show_tile(tile, w_scale);
this.tile_cache.set_elt(key, tile);
this.show_tile(tile, w_scale);
}
tile_drawn.resolve(tile);
@@ -3428,7 +3427,6 @@ extend(TiledTrack.prototype, Drawable.prototype, Track.prototype, {
* an existing tile rather than reshowing it.
*/
show_tile: function(tile, w_scale) {
var track = this;
var tile_element = tile.html_elt;
// -- Show/move tile element. --
@@ -3785,7 +3783,7 @@ extend(CompositeTrack.prototype, TiledTrack.prototype, {
}
// Replace track with group.
var index = this.container.replace_drawable(this, group, true);
this.container.replace_drawable(this, group, true);
group.request_draw({ clear_tile_cache: true });
},
@@ -3819,7 +3817,6 @@ extend(CompositeTrack.prototype, TiledTrack.prototype, {
* Update minimum, maximum for component tracks.
*/
update_all_min_max: function() {
var track = this;
var min_value = this.config.get_value("min_value");
var max_value = this.config.get_value("max_value");
_.each(this.drawables, d => {
@@ -4127,7 +4124,8 @@ extend(DiagonalHeatmapTrack.prototype, Drawable.prototype, TiledTrack.prototype,
hda_ldda: track.dataset.get("hda_ldda")
},
result => {
var data = result.data;
// What does this do? Is it meant to be attached to some higher scope state object?
// var data = result.data;
}
);
},
@@ -4163,10 +4161,9 @@ var FeatureTrack = function(view, container, obj_dict) {
this.slotters = {};
this.start_end_dct = {};
this.left_offset = 200;
// this.painter = painters.LinkedFeaturePainter;
this.set_painter_from_config();
};
extend(FeatureTrack.prototype, Drawable.prototype, TiledTrack.prototype, {
display_modes: ["Auto", "Coverage", "Dense", "Squish", "Pack"],
@@ -4258,9 +4255,6 @@ extend(FeatureTrack.prototype, Drawable.prototype, TiledTrack.prototype, {
postdraw_actions: function(tiles, width, w_scale, clear_after) {
TiledTrack.prototype.postdraw_actions.call(this, tiles, width, w_scale, clear_after);
var track = this;
var i;
var line_track_tiles = _.filter(tiles, t => t instanceof LineTrackTile);
//
@@ -4277,7 +4271,6 @@ extend(FeatureTrack.prototype, Drawable.prototype, TiledTrack.prototype, {
});
// Draw incomplete features on each tile.
var self = this;
_.each(tiles, tile => {
// Remove features already drawn on tile originally.
var tile_incomplete_features = _.omit(
@@ -4296,16 +4289,16 @@ extend(FeatureTrack.prototype, Drawable.prototype, TiledTrack.prototype, {
data: _.values(tile_incomplete_features)
};
var new_canvas = self.view.canvas_manager.new_canvas();
var new_canvas = this.view.canvas_manager.new_canvas();
var new_canvas_ctx = new_canvas.getContext("2d");
new_canvas.height = Math.max(
tile.canvas.height,
self.get_canvas_height(features, tile.mode, tile.w_scale, 100)
this.get_canvas_height(features, tile.mode, tile.w_scale, 100)
);
new_canvas.width = tile.canvas.width;
new_canvas_ctx.drawImage(tile.canvas, 0, 0);
new_canvas_ctx.translate(track.left_offset, 0);
var new_tile = self.draw_tile(
new_canvas_ctx.translate(this.left_offset, 0);
var new_tile = this.draw_tile(
features,
new_canvas_ctx,
tile.mode,
@@ -4348,8 +4341,8 @@ extend(FeatureTrack.prototype, Drawable.prototype, TiledTrack.prototype, {
//
// Update filtering UI.
if (track.filters_manager) {
var filters = track.filters_manager.filters;
if (this.filters_manager) {
var filters = this.filters_manager.filters;
var f;
for (f = 0; f < filters.length; f++) {
filters[f].update_ui_elt();
@@ -4361,7 +4354,7 @@ extend(FeatureTrack.prototype, Drawable.prototype, TiledTrack.prototype, {
var example_feature;
var filter;
for (i = 0; i < tiles.length; i++) {
for (let i = 0; i < tiles.length; i++) {
if (tiles[i].data.length) {
example_feature = tiles[i].data[0];
for (f = 0; f < filters.length; f++) {
@@ -4375,12 +4368,12 @@ extend(FeatureTrack.prototype, Drawable.prototype, TiledTrack.prototype, {
}
// If filter availability changed, hide filter div if necessary and update menu.
if (track.filters_available !== filters_available) {
track.filters_available = filters_available;
if (!track.filters_available) {
track.filters_manager.hide();
if (this.filters_available !== filters_available) {
this.filters_available = filters_available;
if (!this.filters_available) {
this.filters_manager.hide();
}
track.update_icons();
this.update_icons();
}
}
@@ -4389,7 +4382,7 @@ extend(FeatureTrack.prototype, Drawable.prototype, TiledTrack.prototype, {
//
if (tiles[0] instanceof FeatureTrackTile) {
var all_slotted = true;
for (i = 0; i < tiles.length; i++) {
for (let i = 0; i < tiles.length; i++) {
if (!tiles[i].all_slotted) {
all_slotted = false;
break;
@@ -4557,6 +4550,7 @@ extend(FeatureTrack.prototype, Drawable.prototype, TiledTrack.prototype, {
);
var feature_mapper = null;
var incomplete_features = null;
ctx.fillStyle = this.config.get_value("block_color");
ctx.font = ctx.canvas.manager.default_font;
+3 -5
View File
@@ -5,7 +5,6 @@ import util_mod from "viz/trackster/util";
import config_mod from "utils/config";
import GridView from "mvc/grid/grid-view";
import Tabs from "mvc/ui/ui-tabs";
import Ui from "mvc/ui/ui-misc";
/**
* Mixin for returning custom JSON representation from toJSON. Class attribute to_json_keys defines a set of attributes
* to include in the representation; to_json_mappers defines mappers for returned objects.
@@ -1109,12 +1108,11 @@ var TrackBrowserRouter = Backbone.Router.extend({
// Can't put regular expression in routes dictionary.
// NOTE: parentheses are used to denote parameters returned to callback.
this.route(/([\w]+)$/, "change_location");
this.route(/([\w\+]+\:[\d,]+-[\d,]+)$/, "change_location");
this.route(/([\w+]+:[\d,]+-[\d,]+)$/, "change_location");
// Handle navigate events from view.
var self = this;
self.view.on("navigate", new_loc => {
self.navigate(new_loc);
this.view.on("navigate", new_loc => {
this.navigate(new_loc);
});
},
+12 -4
View File
@@ -31,6 +31,7 @@
@import "jstree.less";
@import "tour.less";
@import "flex.less";
@import "charts.less";
// Mixins
.unselectable {
@@ -1177,8 +1178,15 @@ ul.manage-table-actions li {
}
.state-color-deleted {
border-color: @state-deleted-border;
background: @state-deleted-bg;
border-color: darken(@state-default-border, 30%);
border-style: dotted;
background: darken(@state-default-bg, 30%);
}
.state-color-hidden {
border-color: @state-default-border;
border-style: dotted;
background: @state-default-bg;
}
.state-fg-new {
@@ -1797,7 +1805,7 @@ div.toolTitleNoSection
display: none;
}
// communication channel bootstrap modal
// communication channel bootstrap modal
.chat-modal {
overflow: hidden;
}
@@ -1812,7 +1820,7 @@ div.toolTitleNoSection
}
.close-modal {
float: right;
float: right;
cursor: pointer;
}
+128
View File
@@ -0,0 +1,128 @@
.charts-client {
.charts-buttons {
float: right;
margin: 5px 0px;
z-index: 1000;
.ui-button-icon {
display: block;
margin-right: 5px;
}
.ui-button-icon {
background: white;
color: #666;
border-color: white;
.icon {
font-size: 1.2em;
line-height: 1.2em;
}
}
.ui-button-icon.charts-fullscreen-button {
display: none;
}
}
.charts-center {
float: left;
width: 70%;
overflow: hidden;
padding: 10px 5px 10px 10px;
.charts-viewer {
height: inherit;
min-height: 50px;
.info {
position: absolute;
margin: 40px 20px 50px 10px;
width: 50%;
}
.text {
position: relative;
margin-left: 5px;
top: -1px;
font-size: 1.0em;
display: inline;
}
.icon {
font-size: 1.2em;
display: inline-block;
}
.charts-viewer-container {
float: left;
display: block;
height: 100%;
}
.charts-viewer-canvas {
display: block;
width: 100%;
min-height: 100px;
height: inherit;
}
}
}
.charts-right {
float: left;
width: 30%;
height: 100vh;
padding-bottom: 32px;
overflow: hidden;
display: flex;
flex-direction: column;
.ui-message {
margin: 5px;
width: auto;
}
.charts-editor {
display: block;
flex: 1;
overflow: auto;
padding: 5px 10px 10px 5px;
.charts-description {
margin-bottom: 20px;
.charts-description-image-td {
vertical-align: top;
}
.charts-description-image {
width: 50px;
height: 43px;
margin-right: 10px;
}
.charts-description-title {
font-weight: bold;
}
.charts-description-text {
margin-top: 5px;
word-break: break-word;
}
}
.tab-icon {
margin-right: 0px;
color: #666;
font-size: 1.2em;
}
.portlet-content {
padding: 0px;
}
.ui-table-section, .ui-portlet-panel .portlet-content {
padding-left: 5px;
}
}
}
}
.charts-client.charts-fullscreen {
.charts-buttons {
position: absolute;
right: 0px;
.ui-button-icon,
.ui-button-menu {
display: none;
}
.ui-button-icon.charts-fullscreen-button {
display: block;
}
}
.charts-center {
width: 100%;
}
.charts-right {
display: none;
}
}
@@ -1,5 +1,5 @@
// Additional variables that are not used by bootstrap but are used
// in Galaxy stylesheets
// in Galaxy stylesheets
@base-bg: @white;
@base-text-color: @black;
@@ -29,8 +29,8 @@
@state-running-border: #AAAA66;
@state-running-bg: #FFFFCC;
@state-deleted-border: #330066;
@state-deleted-bg: #3399FF;
// @state-deleted-border: #330066;
// @state-deleted-bg: #3399FF;
// Theme, expects tmp-site-config.less written by grunt
@import "tmp-site-config.less";
+25 -14
View File
@@ -294,6 +294,13 @@
width: 100px;
height: 86px;
}
.ui-thumbnails-icon {
width: 100px;
height: 86px;
font-size: 4em;
padding: 20px 26px;
color: lighten(#25537b, 10%);
}
.ui-thumbnails-description-title {
font-weight: bold;
}
@@ -301,7 +308,18 @@
word-wrap: break-word;
}
}
.ui-thumbnail-current {
.ui-thumbnails-item:hover {
.ui-thumbnails-title {
text-decoration: underline;
}
.ui-thumbnails-description-title {
text-decoration: underline;
}
.ui-thumbnails-description-text {
text-decoration: underline;
}
}
.ui-thumbnail-current-highlight {
border-color:#66afe9;
background-color:#EEEEFF;
outline:0;
@@ -468,6 +486,9 @@
.ui-form-composite {
max-width: 900px;
height: 100%;
flex-direction: column;
display: flex;
.ui-form-header {
&:extend(h3);
margin-top: 0px;
@@ -888,10 +909,6 @@
float: right;
width: ~'calc(100% - 76px)';
}
.ui-gs-token-textbox {
float: right;
width: ~'calc(100% - 76px)';
}
.ui-gs-browse-button {
float: left;
.ui-button-icon {
@@ -899,15 +916,9 @@
margin-right: 5px;
}
}
.ui-gs-token-label {
margin-top: 5px;
margin-left: 32px;
margin-right: 5px;
float: left;
}
.ui-gs-token-field {
clear: both;
padding-top: 5px;
.ui-gs-browse-field {
height: @input-height-base;
line-height: @line-height-base;
}
}
+1 -1
View File
@@ -40,7 +40,7 @@
.icon-btn-group {
display: inline-block;
.icon-btn:not(:last-child) {
.icon-btn:not(:last-of-type) {
margin: 0px;
border-radius: 0px;
border-right: none;
+1 -1
View File
@@ -36,7 +36,7 @@
"build-production": "NODE_ENV=production gulp stage-libs && concurrently \"yarn run style\" \"yarn run webpack-production\" \"yarn run gulp clean && yarn run gulp-production\" && yarn run save-build-hash",
"build-production-maps": "NODE_ENV=production gulp stage-libs && concurrently \"yarn run style\" \"yarn run webpack-production-maps\" \"yarn run gulp clean && yarn run gulp-production-maps\" && yarn run save-build-hash",
"webpack": "webpack -d",
"save-build-hash": "git rev-parse HEAD > ../static/client_build_hash.txt",
"save-build-hash": "(git rev-parse HEAD 2>/dev/null || echo '') >../static/client_build_hash.txt",
"webpack-watch": "webpack -d --watch",
"webpack-production": "webpack -p",
"webpack-production-maps": "GXY_BUILD_SOURCEMAPS=1 webpack -p",
+1
View File
@@ -43,6 +43,7 @@ let buildconfig = {
login: "./galaxy/scripts/apps/login.js",
analysis: "./galaxy/scripts/apps/analysis.js",
admin: "./galaxy/scripts/apps/admin.js",
chart: "./galaxy/scripts/apps/chart.js",
extended: "./galaxy/scripts/apps/extended.js"
},
output: {
+24 -7
View File
@@ -10,16 +10,23 @@
<datatype extension="fli" type="galaxy.datatypes.tabular:FeatureLocationIndex" display_in_upload="false"/>
<datatype extension="bam" type="galaxy.datatypes.binary:Bam" mimetype="application/octet-stream" display_in_upload="true" description="A binary file compressed in the BGZF format with a '.bam' file extension." description_url="https://wiki.galaxyproject.org/Learn/Datatypes#BAM">
<converter file="bam_to_bigwig_converter.xml" target_datatype="bigwig"/>
<converter file="to_qname_sorted_bam.xml" target_datatype="qname_sorted.bam"/>
<display file="ucsc/bam.xml" />
<display file="ensembl/ensembl_bam.xml" />
<display file="igv/bam.xml" />
<display file="igb/bam.xml" />
<display file="iobio/bam.xml" />
</datatype>
<datatype extension="bam_native" type="galaxy.datatypes.binary:BamNative" mimetype="application/octet-stream" display_in_upload="true" description="A binary file compressed in the BGZF format with a '.bam' file extension." description_url="https://wiki.galaxyproject.org/Learn/Datatypes#BAM">
<converter file="bam_to_bigwig_converter.xml" target_datatype="bigwig"/>
<converter file="bam_native_to_bam_converter.xml" target_datatype="bam"/>
<datatype extension="qname_input_sorted.bam" type="galaxy.datatypes.binary:BamInputSorted" mimetype="application/octet-stream" display_in_upload="false" description="A binary file compressed in the BGZF format with a '.bam' file extension and sorted based on the aligner output." description_url="https://wiki.galaxyproject.org/Learn/Datatypes#BAM">
</datatype>
<datatype extension="qname_sorted.bam" type="galaxy.datatypes.binary:BamQuerynameSorted" mimetype="application/octet-stream" display_in_upload="true" description="A binary file compressed in the BGZF format with a '.bam' file extension and sorted by queryname." description_url="https://wiki.galaxyproject.org/Learn/Datatypes#BAM">
</datatype>
<datatype extension="unsorted.bam" type="galaxy.datatypes.binary:BamNative" mimetype="application/octet-stream" display_in_upload="true" description="A binary file compressed in the BGZF format with a '.bam' file extension." description_url="https://wiki.galaxyproject.org/Learn/Datatypes#BAM">
<converter file="bam_to_bigwig_converter.xml" target_datatype="bigwig"/>
<converter file="to_coordinate_sorted_bam.xml" target_datatype="bam"/>
<converter file="to_qname_sorted_bam.xml" target_datatype="qname_sorted.bam"/>
</datatype>
<datatype extension="probam" type="galaxy.datatypes.binary:ProBam" mimetype="application/octet-stream" display_in_upload="true"/>
<datatype extension="cram" type="galaxy.datatypes.binary:CRAM" mimetype="application/octet-stream" display_in_upload="true" description="CRAM is a file format for highly efficient and tunable reference-based compression of alignment data." description_url="http://www.ebi.ac.uk/ena/software/cram-usage">
<converter file="cram_to_bam_converter.xml" target_datatype="bam"/>
</datatype>
@@ -40,6 +47,7 @@
<datatype extension="bed6" type="galaxy.datatypes.interval:Bed6" display_in_upload="true">
</datatype>
<datatype extension="bed12" type="galaxy.datatypes.interval:Bed12" display_in_upload="true"/>
<datatype extension="probed" type="galaxy.datatypes.interval:ProBed" display_in_upload="true"/>
<datatype extension="len" type="galaxy.datatypes.chrominfo:ChromInfo" display_in_upload="true">
<converter file="len_to_linecount.xml" target_datatype="linecount" />
</datatype>
@@ -66,6 +74,7 @@
<datatype extension="bowtie_base_index" type="galaxy.datatypes.ngsindex:BowtieBaseIndex" mimetype="text/html" display_in_upload="false"/>
<datatype extension="csfasta" type="galaxy.datatypes.sequence:csFasta" display_in_upload="true"/>
<datatype extension="data" type="galaxy.datatypes.data:Data" mimetype="application/octet-stream" max_optional_metadata_filesize="1048576" />
<datatype extension="binary" type="galaxy.datatypes.binary:Binary" mimetype="application/octet-stream" max_optional_metadata_filesize="1048576" />
<datatype extension="d3_hierarchy" type="galaxy.datatypes.text:Json" mimetype="application/json" subclass="true" display_in_upload="true"/>
<datatype extension="data_manager_json" type="galaxy.datatypes.text:Json" mimetype="application/json" subclass="true" display_in_upload="false"/>
<datatype extension="dbn" type="galaxy.datatypes.sequence:DotBracket" display_in_upload="true" description="Dot-Bracket format is a text-based format for storing both an RNA sequence and its corresponding 2D structure." description_url="https://wiki.galaxyproject.org/Learn/Datatypes#Dbn"/>
@@ -79,6 +88,9 @@
<converter file="fasta_to_fai.xml" target_datatype="fai"/>
<display file="igv/genome_fasta.xml" inherit="true"/>
</datatype>
<datatype extension="fasta.gz" type="galaxy.datatypes.sequence:FastaGz" display_in_upload="true">
<converter file="fastagz_to_fasta.xml" target_datatype="fasta"/>
</datatype>
<datatype extension="fastq" type="galaxy.datatypes.sequence:Fastq" display_in_upload="true" description="FASTQ format is a text-based format for storing both a biological sequence (usually nucleotide sequence) and its corresponding quality scores." description_url="https://wiki.galaxyproject.org/Learn/Datatypes#Fastq">
<converter file="fastq_to_fqtoc.xml" target_datatype="fqtoc"/>
</datatype>
@@ -288,8 +300,9 @@
<datatype extension="qual454" type="galaxy.datatypes.qualityscore:QualityScore454" display_in_upload="true"/>
<datatype extension="roadmaps" type="galaxy.datatypes.assembly:Roadmaps" display_in_upload="false"/>
<datatype extension="sam" type="galaxy.datatypes.tabular:Sam" display_in_upload="true">
<converter file="sam_to_bam_native.xml" target_datatype="bam_native"/>
<converter file="sam_to_bam.xml" target_datatype="bam"/>
<converter file="sam_to_unsorted_bam.xml" target_datatype="unsorted.bam"/>
<converter file="to_coordinate_sorted_bam.xml" target_datatype="bam"/>
<converter file="to_qname_sorted_bam.xml" target_datatype="qname_sorted.bam"/>
<converter file="sam_to_bigwig_converter.xml" target_datatype="bigwig"/>
</datatype>
<datatype extension="scf" type="galaxy.datatypes.binary:Scf" mimetype="application/octet-stream" display_in_upload="true" description="A binary sequence file in 'scf' format with a '.scf' file extension. You must manually select this 'File Format' when uploading the file." description_url="https://wiki.galaxyproject.org/Learn/Datatypes#Scf"/>
@@ -331,7 +344,7 @@
<datatype extension="bcf_uncompressed" type="galaxy.datatypes.binary:BcfUncompressed" mimetype="application/octet-stream">
<converter file="bcf_uncompressed_to_bcf_converter.xml" target_datatype="bcf"/>
</datatype>
<datatype extension="velvet" type="galaxy.datatypes.assembly:Velvet" display_in_upload="false"/>
<datatype extension="velvet" type="galaxy.datatypes.assembly:Velvet" display_in_upload="true"/>
<datatype extension="wig" type="galaxy.datatypes.interval:Wiggle" display_in_upload="true" description="The wiggle format is line-oriented. Wiggle data is preceded by a track definition line, which adds a number of options for controlling the default display of this track." description_url="https://wiki.galaxyproject.org/Learn/Datatypes#Wig">
<converter file="wig_to_bigwig_converter.xml" target_datatype="bigwig"/>
<converter file="wiggle_to_simple_converter.xml" target_datatype="interval"/>
@@ -485,7 +498,7 @@
<datatype extension="ncbi" type="galaxy.datatypes.data:Text" subclass="true"/>
<datatype extension="nexus" type="galaxy.datatypes.data:Text" subclass="true"/>
<datatype extension="nexusnon" type="galaxy.datatypes.data:Text" subclass="true"/>
<datatype extension="phylip" type="galaxy.datatypes.phylip:Phylip" subclass="true" display_in_upload="true"/>
<datatype extension="phylip" type="galaxy.datatypes.phylip:Phylip" display_in_upload="true"/>
<datatype extension="phylipnon" type="galaxy.datatypes.data:Text" subclass="true"/>
<datatype extension="pir" type="galaxy.datatypes.data:Text" subclass="true"/>
<datatype extension="staden" type="galaxy.datatypes.data:Text" subclass="true"/>
@@ -675,6 +688,9 @@
<datatype extension="allegro_ihaplo" type="galaxy.datatypes.tabular:Tabular" />
<datatype extension="allegro_descent" type="galaxy.datatypes.tabular:Tabular" />
<datatype extension="allegro_fparam" type="galaxy.datatypes.genetics:AllegroLOD" />
<!-- IDEAS datatypes -->
<datatype extension="ideaspre" type="galaxy.datatypes.genetics:IdeasPre" display_in_upload="true"/>
<!-- End IDEAS datatypes -->
</registration>
<sniffers>
<!--
@@ -763,6 +779,7 @@
<!-- TODO: see molecules.py <sniffer type="galaxy.datatypes.molecules:SMILES"/>-->
<sniffer type="galaxy.datatypes.phylip:Phylip"/>
<sniffer type="galaxy.datatypes.sequence:Fasta"/>
<sniffer type="galaxy.datatypes.sequence:FastaGz"/>
<sniffer type="galaxy.datatypes.sequence:FastqSanger"/>
<sniffer type="galaxy.datatypes.sequence:FastqSangerGz"/>
<sniffer type="galaxy.datatypes.sequence:FastqSangerBz2"/>
+36 -1
View File
@@ -133,6 +133,12 @@ galaxy:
# than 5 milliseconds.
#slow_query_log_threshold: 0
# Enable's a per request sql debugging option. If this is set to true,
# append ?sql_debug=1 to web request URLs to enable detailed logging
# on the backend of SQL queries generated during that request. This is
# useful for debugging slow endpoints during development.
#enable_per_request_sql_debugging: false
# By default, Galaxy will use the same database to track user data and
# tool shed install data. There are many situations in which it is
# valuable to separate these - for instance bootstrapping fresh Galaxy
@@ -219,7 +225,7 @@ galaxy:
# conda channels to enable by default (http://conda.pydata.org/docs
# /custom-channels.html)
#conda_ensure_channels: 'iuc,bioconda,conda-forge,defaults,r'
#conda_ensure_channels: 'iuc,bioconda,conda-forge,defaults'
# Set to True to instruct Galaxy to look for and install missing tool
# dependencies before each job runs.
@@ -618,6 +624,14 @@ galaxy:
# standard).
#pretty_datetime_format: $locale (UTC)
# Default localization for Galaxy UI. Allowed values are listed at the
# end of client/galaxy/scripts/nls/locale.js. With the default value
# (auto), the locale will be automatically adjusted to the user's
# navigator language. Users can override this settings in their user
# preferences if the localization settings are enabled in
# user_preferences_extra_conf.yml
#default_locale: auto
# URL (with schema http/https) of the Galaxy instance as accessible
# within your local network - if specified used as a default by pulsar
# file staging and Jupyter Docker container for communicating back
@@ -798,6 +812,11 @@ galaxy:
# documentation for the corresponding nginx configuration.
#nginx_upload_job_files_path: false
# Galaxy can upload user files in chunks without using nginx. Enable
# the chunk uploader by specifying a chunk size larger than 0. The
# chunk size is specified in bytes (default: 100MB).
#chunk_upload_size: 104857600
# Have Galaxy manage dynamic proxy component for routing requests to
# other services based on Galaxy's session cookie. It will attempt to
# do this by default though you do need to install node+npm and do an
@@ -990,6 +1009,13 @@ galaxy:
# <project_name> -> Settings -> API Keys.
#sentry_dsn: null
# Sentry slow request logging. Requests slower than the threshold
# indicated below will be sent as events to the configured Sentry
# server (above, sentry_dsn). A value of '0' is disabled. For
# example, you would set this to .005 to log all queries taking longer
# than 5 milliseconds.
#sentry_sloreq_threshold: 0
# Log to statsd Statsd is an external statistics aggregator
# (https://github.com/etsy/statsd) Enabling the following options will
# cause galaxy to log request timing and other statistics to the
@@ -1146,6 +1172,15 @@ galaxy:
# Set maximum size of ngrams
#tool_ngram_maxsize: 4
# Set tool test data directory. The test framework sets this value to
# 'test-data,https://github.com/galaxyproject/galaxy-test-data.git'
# which will cause Galaxy to clone down extra test data on the fly for
# certain tools distributed with Galaxy but this is likely not
# appropriate for production systems. Instead one can simply clone
# that repository directly and specify a path here instead of a Git
# HTTP repository.
#tool_test_data_directories: test-data
# Galaxy encodes various internal values when these values will be
# output in some format (for example, in a URL or cookie). You should
# set a key to be used by the algorithm that encodes and decodes these
+17 -12
View File
@@ -15,7 +15,7 @@
<!-- Core plugin captures Galaxy slots, start and end of job (in seconds
since epoch) and computes runtime in seconds. -->
<core />
<!-- Uncomment to dump processor count for each job - linux only. -->
<!-- <cpuinfo /> -->
<!-- Uncomment to dump information about all processors for for each
@@ -30,13 +30,18 @@
only. -->
<!-- <uname /> -->
<!-- Uncomment following to enable plugin dumping complete environment
<!-- Uncomment following to enable plugin dumping complete environment
for each job, potentially useful for debuging -->
<!-- <env /> -->
<!-- env plugin can also record more targetted, obviously useful variables
as well. -->
<!-- <env variables="HOSTNAME,SLURM_CPUS_ON_NODE,SLURM_JOBID" /> -->
<!-- If galaxy jobs are run in cgroups, like slurm does if memory limits
are enforced, we can try to grep some information from this. -->
<!-- <cgroup /> -->
<!-- <cgroup verbose="true" /> -->
<!-- <collectl /> -->
<!-- Collectl (http://collectl.sourceforge.net/) is a powerful monitoring
utility capable of gathering numerous system and process level
@@ -46,7 +51,7 @@
is highly customiziable - both using the attributes documented below
or simply hacking up the code in lib/galaxy/jobs/metrics.
Warning: In order to use this plugin collectl must be available on the
Warning: In order to use this plugin collectl must be available on the
compute server the job runs on and on the local Galaxy server as well
(unless in this latter case summarize_process_data is set to False).
@@ -64,12 +69,12 @@
full time-series data corresponding to a job run.
'subsystems': Comma separated list of collectl subystems to collect
data for. Plugin doesn't currently expose all of them or offer
summary data for any of them except 'process' but extensions
would be welcome. May seem pointless to include subsystems
data for. Plugin doesn't currently expose all of them or offer
summary data for any of them except 'process' but extensions
would be welcome. May seem pointless to include subsystems
beside process since they won't be processed online by Galaxy -
but if 'saved_logs_path' these files can be played back at anytime.
Available subsystems - 'process', 'cpu', 'memory', 'network',
'disk', 'network'. (Default 'process').
@@ -108,13 +113,13 @@
'flush': Interval (in seconds I think) between when collectl will
flush its buffer to disk. Galaxy overrides this to disable
flushing by default if not set.
flushing by default if not set.
'local_collectl_path', 'remote_collectl_path', 'collectl_path':
By default, jobs will just assume collectl is on the PATH, but
it can be overridden with 'local_collectl_path' and
'remote_collectl_path' (or simply 'collectl_path' if it is not
on the path but installed in the same location both locally and
By default, jobs will just assume collectl is on the PATH, but
it can be overridden with 'local_collectl_path' and
'remote_collectl_path' (or simply 'collectl_path' if it is not
on the path but installed in the same location both locally and
remotely).
There are more and more increasingly obsecure options including -
@@ -1,41 +0,0 @@
{
"name": "galaxy-charts-plugins",
"version": "0.1.0",
"description": "Charts visualization plugin.",
"keywords": [
"galaxy",
"visualization",
"d3"
],
"repository": {
"type": "git",
"url": "https://github.com/guerler/galaxy-charts"
},
"license": "AFL-3.0",
"dependencies": {
"amdi18n-loader": "^0.2.0",
"grunt": "^0.4.5",
"grunt-bower-install-simple": "^1.1.0",
"grunt-check-modules": "^1.0.0",
"grunt-cli": "^0.1.13",
"grunt-contrib-clean": "^0.6.0",
"grunt-contrib-copy": "^0.5.0",
"grunt-contrib-less": "^1.1.0",
"grunt-contrib-uglify": "^0.8.0",
"grunt-contrib-watch": "^0.6.1",
"grunt-exec": "^0.4.6",
"grunt-spritesmith": "^4.7.1",
"i18n-webpack-plugin": "^0.2.7",
"webpack": "^1.10.1",
"webpack-dev-server": "^1.7.0",
"backbone": "^1.3.3",
"bootstrap": "^3.3.7",
"cytoscape": "^2.7.10",
"jquery": "^3.1.1",
"cytoscape-edgehandles": "^2.6.0"
},
"devDependencies": {
"css-loader": "^0.24.0",
"style-loader": "^0.13.1"
}
}
@@ -1,83 +0,0 @@
.charts-viewer {
overflow: auto;
}
.charts-viewer .ui-message {
position: absolute;
width: calc(100% - 20px);
z-index: 100;
}
.charts-description {
margin-bottom: 20px;
}
.charts-description-image-td {
vertical-align: top;
}
.charts-description-image {
width: 50px;
height: 43px;
margin-right: 10px;
}
.charts-description-title {
font-weight: bold;
}
.charts-description-text {
word-wrap: break-word;
}
.charts-tooltip {
position: absolute;
text-align: center;
padding: 3px 5px 3px 5px;
font: 12px sans-serif;
background: white;
border: 1px solid gray;
border-radius: 4px;
pointer-events: none;
}
.charts-tooltip-first {
font-weight: bold;
text-align: left;
padding-right: 5px;
}
.charts-viewport {
height: inherit;
min-height: 50px;
}
.charts-viewport .info {
position: absolute;
margin: 40px 20px 50px 10px;
}
.charts-viewport .text {
position: relative;
margin-left: 5px;
top: -1px;
font-size: 1.0em;
display: inline;
}
.charts-viewport .icon {
font-size: 1.2em;
display: inline-block;
}
.charts-viewport .charts-viewport-container {
float: left;
display: block;
height: 100%;
}
.charts-viewport .charts-viewport-canvas {
display: block;
width: 100%;
min-height: 100px;
height: inherit;
}
@@ -1,93 +0,0 @@
/**
* Main application class.
*/
define(
[
"mvc/ui/ui-modal",
"mvc/ui/ui-portlet",
"mvc/ui/ui-misc",
"utils/utils",
"plugin/components/model",
"utils/deferred",
"plugin/views/viewer",
"plugin/views/editor"
],
function(Modal, Portlet, Ui, Utils, Chart, Deferred, Viewer, Editor) {
return Backbone.View.extend({
initialize: function(options) {
var self = this;
require(["repository/build/registry"], function(Registry) {
console.debug("app::initialize() - Loaded Registry:");
console.debug(Registry);
Utils.get({
url: Galaxy.root + "api/datasets/" + options.dataset_id,
success: function(dataset) {
self.dataset = dataset;
self.types = {};
_.each(Registry, function(type, type_id) {
if (!type.datatypes || type.datatypes.indexOf(dataset.file_ext) != -1) {
self.types[type_id] = type;
}
});
if (_.size(self.types) === 0) {
self.$el.append(
$("<div/>")
.addClass("errormessagelarge")
.append(
$("<p/>").text(
"Unfortunately we could not identify a suitable plugin. Feel free to contact us if you are aware of visualizations for this datatype."
)
)
);
} else {
self._build(options);
}
}
});
}, function(err) {
self.$el.append(
$("<div/>")
.addClass("errormessagelarge")
.append($("<p/>").text("Unable to access the plugin repository:"))
.append($("<pre/>").text("charts_repository_url = " + repository_root))
.append(
$("<p/>").html(
"Please verify that your internet connection works properly and that the above url is correct. Contact your admin if this error persists."
)
)
);
});
},
_build: function(options) {
this.options = options;
this.modal = (parent.Galaxy && parent.Galaxy.modal) || new Modal.View();
this.chart = new Chart({}, options);
this.deferred = new Deferred.default();
this.viewer = new Viewer(this);
this.editor = new Editor(this);
this.$el.append(this.viewer.$el);
this.$el.append(this.editor.$el);
this.go(this.chart.load() ? "viewer" : "editor");
},
/** Loads a view and makes sure that all others are hidden */
go: function(view_id) {
$(".tooltip").hide();
this.viewer.hide();
this.editor.hide();
this[view_id].show();
},
/** Split chart type into path components */
split: function(chart_type) {
var path = chart_type.split(/_(.+)/);
if (path.length >= 2) {
return path[0] + "/" + path[1];
} else {
return chart_type;
}
}
});
}
);
@@ -1,101 +0,0 @@
define(["utils/utils", "mvc/visualization/visualization-model"], function(Utils) {
return Backbone.Model.extend({
defaults: {
title: "",
type: "",
date: null,
state: "",
state_info: "",
modified: false,
dataset_id: "",
dataset_id_job: ""
},
initialize: function(options, viz_options) {
this.groups = new Backbone.Collection();
this.settings = new Backbone.Model();
this.definition = {};
this.viz_options = viz_options;
console.debug("model::initialize() - Initialized with configuration:");
console.debug(viz_options);
},
reset: function() {
this.clear().set({
title: "New Chart",
type: "__first",
dataset_id: this.viz_options.dataset_id
});
this.settings.clear();
this.groups.reset();
this.groups.add({ id: Utils.uid() });
},
state: function(value, info) {
this.set({ state: value, state_info: info });
this.trigger("set:state");
console.debug("model::state() - " + info + " (" + value + ")");
},
/** Pack and save nested chart model */
save: function(options) {
var self = this;
options = options || {};
var chart_dict = {
attributes: this.attributes,
settings: this.settings.attributes,
groups: []
};
this.groups.each(function(group) {
chart_dict.groups.push(group.attributes);
});
var viz = new Visualization({
id: this.viz_options.visualization_id || undefined,
type: "charts",
title: this.get("title") || "",
config: {
dataset_id: this.viz_options.dataset_id,
chart_dict: (this.viz_options.chart_dict = chart_dict)
}
});
viz
.save()
.then(function(response) {
if (response && response.id) {
self.viz_options.visualization_id = response.id;
options.success && options.success();
console.debug("model::save() - Received visualization id: " + response.id);
} else {
options.error && options.error();
console.debug("model::save() - Unrecognized response. Saving may have failed.");
}
})
.fail(function(response) {
options.error && options.error();
console.debug("model::save() - Saving failed.");
});
console.debug("model::save() - Saved with configuration:");
console.debug(this.viz_options);
},
/** Load nested models/collections from packed dictionary */
load: function() {
console.debug("model::load() - Attempting to load with configuration:");
console.debug(this.viz_options);
var chart_dict = this.viz_options.chart_dict;
if (chart_dict && chart_dict.attributes) {
this.set(chart_dict.attributes);
this.state("ok", "Loading saved visualization...");
this.settings.set(chart_dict.settings);
this.groups.reset();
this.groups.add(chart_dict.groups);
this.set("modified", false);
console.debug("model::load() - Loading chart model " + chart_dict.attributes.type + ".");
this.trigger("redraw");
return true;
}
console.debug("model::load() - Visualization attributes unavailable.");
return false;
}
});
});
@@ -1,188 +0,0 @@
/** This class enables users to export/download a chart as PNG, SVG or PDF. */
define(["libs/underscore"], function(_) {
/** PNG export */
function createPNG(options) {
if (options.$el.find("svg").length > 0) {
_svg2png(options);
} else {
_canvas2png(options.$el.find(".charts-viewport-canvas"));
}
}
/** Convert canvas to png */
function _canvas2png($canvas) {
try {
if ($canvas.width() !== 0 && $canvas.height() !== 0) {
var newCanvas = document.createElement("canvas");
newCanvas.width = $canvas.outerWidth(true);
newCanvas.height = $canvas.outerHeight(true);
var newContext = newCanvas.getContext("2d");
newContext.save();
newContext.fillStyle = "rgb(255,255,255)";
newContext.fillRect(0, 0, newCanvas.width, newCanvas.height);
newContext.restore();
newContext.translate(0, 0);
newContext.textAlign = "left";
newContext.textBaseline = "top";
function _toImage($el, x_offset, y_offset) {
var tagname = $el.prop("tagName").toLowerCase();
var p = $el.position();
var left =
x_offset +
p.left +
parseInt($el.css("marginLeft"), 10) +
parseInt($el.css("borderLeftWidth"), 10) +
parseInt($el.css("paddingLeft"), 10);
var top =
y_offset +
p.top +
parseInt($el.css("marginTop"), 10) +
parseInt($el.css("borderTopWidth"), 10) +
parseInt($el.css("paddingTop"), 10);
var w = newCanvas.width;
if (tagname == "div" || tagname == "span") {
$el.children().each(function() {
_toImage($(this), left, top);
});
} else if (tagname == "canvas") {
newContext.drawImage($el[0], left, top);
}
}
$canvas.children().each(function() {
_toImage($(this), 0, 0);
});
var imgData = newCanvas.toDataURL("image/png");
if (imgData) {
window.location.href = imgData.replace("image/png", "image/octet-stream");
}
}
} catch (err) {
console.debug("FAILED - screenshot::_canvas2png() - " + err);
if (options.error) {
options.error("Please reduce your visualization to a single panel and try again.");
}
}
}
/** Convert svg to png */
function _svg2png(options) {
var scale = 5;
var xml = toXML(options);
var canvas = document.createElement("canvas");
var context = canvas.getContext("2d");
var source = new Image();
var $container = $('<div style="display:none;"/>').append($(canvas));
$("body").append($container);
canvas.width = xml.width * scale;
canvas.height = xml.height * scale;
source.src = "data:image/svg+xml;base64," + btoa(xml.string);
source.onload = function() {
context.drawImage(source, 0, 0, canvas.width, canvas.height);
window.location.href = canvas.toDataURL("image/png").replace("image/png", "image/octet-stream");
$container.remove();
};
}
/** SVG export */
function createSVG(options) {
window.location.href = "data:none/none;base64," + btoa(toXML(options).string);
}
/** PDF export */
function createPDF(options) {
var xml = toXML(options);
var data = {
filename: "visualization",
type: "application/pdf",
height: xml.height,
width: xml.width,
scale: 2,
svg: xml.string
};
var $el = $("body");
var form = $el.find("#viewport-form");
if (form.length === 0) {
form = $("<form>", {
id: "viewport-form",
method: "post",
action: "http://export.highcharts.com/",
display: "none"
});
$el.append(form);
}
form.empty();
for (var name in data) {
var input = $("<input/>", {
type: "hidden",
name: name,
value: data[name]
});
form.append(input);
}
try {
form.submit();
} catch (err) {
console.log(err);
}
}
/** XML export */
function toXML(options) {
var $svg = options.$el.find("svg");
if ($svg.length == 0) {
if (options.error) {
options.error("No SVG found. This visualization type does not support SVG/PDF export.");
return;
}
}
var $el = options.$el;
var nsvgs = $svg.length;
var height = parseInt($svg.first().css("height"));
var width = parseInt($svg.first().css("width"));
var serializer = new XMLSerializer();
var $composite = $("<svg/>").attr({
version: "1.1",
xmlns: "http://www.w3.org/2000/svg",
width: width * nsvgs,
height: height
});
var xmlString = "";
var offsetX = 0;
$svg.each(function() {
var $svg = $(this).clone();
_inline($svg);
var $g = $('<g transform="translate(' + offsetX + ', 0)">').attr("xmlns", "http://www.w3.org/2000/svg");
$g.append($svg.find("g").first());
$composite.append($g);
offsetX += width;
});
return {
string: serializer.serializeToString($composite[0]),
height: height,
width: width
};
}
/** inlines CSS code */
function _inline($target) {
for (var sheet_id in document.styleSheets) {
var sheet = document.styleSheets[sheet_id];
var rules = sheet.cssRules;
if (rules) {
for (var idx = 0, len = rules.length; idx < len; idx++) {
try {
$target.find(rules[idx].selectorText).each(function(i, elem) {
elem.style.cssText += rules[idx].style.cssText;
});
} catch (err) {}
}
}
}
}
return {
createPNG: createPNG,
createSVG: createSVG,
createPDF: createPDF
};
});
@@ -1,48 +0,0 @@
/** This class renders the chart configuration form. */
define(["utils/utils"], function(Utils) {
return Backbone.View.extend({
initialize: function(app, options) {
var self = this;
this.chart = app.chart;
this.app = app;
this.setElement(this._template());
this.$title = this.$(".charts-description-title");
this.$image = this.$(".charts-description-image");
this.$text = this.$(".charts-description-text");
this.listenTo(this.chart, "change", function() {
self.render();
});
this.render();
},
render: function() {
if (this.chart.get("type")) {
this.$image.attr(
"src",
repository_root + "/visualizations/" + this.app.split(this.chart.get("type")) + "/logo.png"
);
this.$title.html(this.chart.definition.title + " (" + this.chart.definition.library + ")");
this.$text.html(Utils.linkify(this.chart.definition.description || ""));
this.$el.show();
} else {
this.$el.hide();
}
},
_template: function() {
return (
'<div class="charts-description">' +
"<table>" +
"<tr>" +
'<td class="charts-description-image-td">' +
'<img class="charts-description-image"/>' +
"</td>" +
"<td>" +
'<div class="charts-description-title ui-form-info"/>' +
'<div class="charts-description-text ui-form-info"/>' +
"</td>" +
"</tr>" +
"</table>" +
"</div>"
);
}
});
});
@@ -1,208 +0,0 @@
/**
* The charts editor holds the tabs for selecting chart types, chart configuration
* and data group selections.
*/
define(
[
"mvc/ui/ui-tabs",
"mvc/ui/ui-misc",
"mvc/ui/ui-portlet",
"mvc/ui/ui-thumbnails",
"utils/utils",
"plugin/views/settings",
"plugin/views/groups"
],
function(Tabs, Ui, Portlet, Thumbnails, Utils, SettingsView, GroupsView) {
return Backbone.View.extend({
initialize: function(app, options) {
var self = this;
this.app = app;
this.chart = this.app.chart;
this.message = new Ui.Message({ cls: "ui-margin-bottom" });
this.portlet = new Portlet.View({
icon: "fa-bar-chart-o",
title: "Editor",
operations: {
draw: new Ui.ButtonIcon({
icon: "fa-line-chart",
tooltip: "Render Visualization",
title: "Visualize",
onclick: function() {
self._drawChart();
}
}),
back: new Ui.ButtonIcon({
icon: "fa-caret-left",
tooltip: "Return to Viewer",
title: "Cancel",
onclick: function() {
self.app.go("viewer");
self.chart.load();
}
})
}
});
// grid with chart types
this.types = new Thumbnails.View({
title_default: "Suggested visualizations",
title_list: "List of available visualizations",
collection: _.map(this.app.types, function(type, type_id) {
return {
id: type_id,
keywords: type.keywords,
title: type.title + " (" + type.library + ")",
title_icon: type.zoomable && "fa-search-plus",
image_src: repository_root + "/visualizations/" + self.app.split(type_id) + "/logo.png",
description: type.description
};
}),
ondblclick: function(chart_type) {
self._drawChart();
},
onchange: function(chart_type) {
var chart_definition = self.app.types[chart_type];
if (!chart_definition) {
self.tabs.hideTab("settings");
self.tabs.hideTab("groups");
self.portlet.hideOperation("draw");
console.debug("editor::onchange() - Chart type not found.");
self.message.update({
message:
"The requested visualization type could not be found. Please select a new type from below or contact us.",
status: "danger",
persistent: true
});
} else {
self.tabs.showTab("settings");
self.tabs.showTab("groups");
self.portlet.showOperation("draw");
self.chart.definition = chart_definition;
self.chart.set({ type: chart_type, modified: true });
self.message.model.set("message", "");
console.debug("editor::onchange() - Switched visualization type.");
}
}
});
// input field for chart title
this.title = new Ui.Input({
placeholder: "Chart title",
onchange: function() {
self.chart.set("title", self.title.value());
}
});
// create tabs
this.tabs = new Tabs.View({});
this.tabs.add({
id: "main",
title: "Start",
icon: "fa fa-bars",
tooltip: "Start by selecting a visualization.",
$el: $("<div/>")
.append(new Ui.Label({ title: "Provide a title:" }).$el)
.append(this.title.$el)
.append(
$("<div/>")
.addClass("ui-form-info ui-margin-bottom")
.html("This title will appear in the list of 'Saved Visualizations'.")
)
.append(new Ui.Label({ title: "Select a visualization:" }).$el.addClass("ui-margin-top"))
.append(this.types.$el)
});
this.tabs.add({
id: "settings",
title: "Customize",
icon: "fa-gear",
tooltip: "Customize the visualization.",
$el: new SettingsView(this.app).$el
});
this.tabs.add({
id: "groups",
title: "Select data",
icon: "fa-database",
tooltip: "Specify data options.",
$el: new GroupsView(this.app).$el
});
// set elements
this.portlet.append(this.message.$el);
this.portlet.append(this.tabs.$el.addClass("ui-margin-top-large"));
this.portlet.hideOperation("back");
this.setElement(this.portlet.$el);
// chart events
this.listenTo(this.chart, "change:title", function(chart) {
self._refreshTitle();
});
this.listenTo(this.chart, "change:type", function(chart) {
self.types.value(chart.get("type"));
});
this.listenTo(this.chart, "redraw", function(chart) {
self.portlet.showOperation("back");
});
this.chart.reset();
},
/** Show editor */
show: function() {
this.$el.show();
},
/** Hide editor */
hide: function() {
this.$el.hide();
},
/** Refresh title handler */
_refreshTitle: function() {
var title = this.chart.get("title");
this.portlet.title(title);
this.title.value(title);
},
/** Draw chart data */
_drawChart: function() {
var self = this;
this.chart.set({
type: this.types.value(),
title: this.title.value(),
date: Utils.time()
});
if (this.chart.groups.length == 0) {
this.message.update({
message: "Please specify data options before rendering the visualization.",
persistent: false
});
this.tabs.show("groups");
return;
}
var valid = true;
var chart_def = this.chart.definition;
this.chart.groups.each(function(group) {
if (valid) {
_.each(group.get("__data_columns"), function(data_columns, name) {
if (group.attributes[name] === null) {
self.message.update({
status: "danger",
message:
"This visualization type requires column types not found in your tabular file.",
persistent: false
});
self.tabs.show("groups");
valid = false;
}
});
}
});
if (valid) {
this.app.go("viewer");
this.app.deferred.execute(function() {
self.chart.trigger("redraw");
});
}
}
});
}
);
@@ -1,143 +0,0 @@
/** This class renders the chart data selection form with repeats. */
define(
[
"utils/utils",
"mvc/ui/ui-misc",
"mvc/form/form-view",
"mvc/form/form-repeat",
"mvc/form/form-data",
"plugin/views/description"
],
function(Utils, Ui, Form, Repeat, FormData, Description) {
var GroupView = Backbone.View.extend({
initialize: function(app, options) {
var self = this;
this.app = app;
this.chart = app.chart;
this.group = options.group;
this.setElement($("<div/>"));
this.listenTo(this.chart, "change:dataset_id change:type", function() {
self.render();
});
this.render();
},
render: function() {
var self = this;
var inputs = this.chart.definition.groups ? Utils.clone(this.chart.definition.groups) : {};
var dataset_id = this.chart.get("dataset_id");
var chart_type = this.chart.get("type");
var chart_definition = this.chart.definition;
if (dataset_id && chart_type) {
this.chart.state("wait", "Loading metadata...");
this.app.deferred.execute(function(process) {
Utils.get({
url: Galaxy.root + "api/datasets/" + dataset_id,
cache: true,
success: function(dataset) {
var data_columns = {};
FormData.visitInputs(inputs, function(input, prefixed) {
if (input.type == "data_column") {
data_columns[prefixed] = Utils.clone(input);
var columns = [];
input.is_auto && columns.push({ label: "Column: Row Number", value: "auto" });
input.is_zero && columns.push({ label: "Column: None", value: "zero" });
var meta = dataset.metadata_column_types;
for (var key in meta) {
var valid =
(["int", "float"].indexOf(meta[key]) != -1 && input.is_numeric) ||
input.is_label;
valid &&
columns.push({ label: "Column: " + (parseInt(key) + 1), value: key });
}
input.data = columns;
}
var model_value = self.group.get(prefixed);
model_value !== undefined && !input.hidden && (input.value = model_value);
});
inputs["__data_columns"] = {
name: "__data_columns",
type: "hidden",
hidden: true,
value: data_columns
};
self.chart.state("ok", "Metadata initialized...");
self.form = new Form.View({
inputs: inputs,
cls: "ui-portlet-plain",
onchange: function() {
self.group.set(self.form.data.create());
self.chart.set("modified", true);
}
});
self.group.set(self.form.data.create());
self.$el.empty().append(self.form.$el);
process.resolve();
}
});
});
}
}
});
return Backbone.View.extend({
initialize: function(app) {
var self = this;
this.app = app;
this.chart = app.chart;
this.repeat = new Repeat.View({
title: "Data series",
title_new: "Data series",
min: 1,
onnew: function() {
self.chart.groups.add({ id: Utils.uid() });
}
});
this.description = new Description(this.app);
this.message = new Ui.Message({
message: "There are no options for this visualization type.",
persistent: true,
status: "info"
});
this.setElement(
$("<div/>")
.append(this.description.$el)
.append(this.repeat.$el.addClass("ui-margin-bottom"))
.append(this.message.$el.addClass("ui-margin-bottom"))
);
this.listenTo(this.chart, "change", function() {
self.render();
});
this.listenTo(this.chart.groups, "add remove reset", function() {
self.chart.set("modified", true);
});
this.listenTo(this.chart.groups, "remove", function(group) {
self.repeat.del(group.id);
});
this.listenTo(this.chart.groups, "reset", function() {
self.repeat.delAll();
});
this.listenTo(this.chart.groups, "add", function(group) {
self.repeat.add({
id: group.id,
cls: "ui-portlet-panel",
$el: new GroupView(self.app, { group: group }).$el,
ondel: function() {
self.chart.groups.remove(group);
}
});
});
},
render: function() {
if (_.size(this.chart.definition.groups) > 0) {
this.repeat.$el.show();
this.message.$el.hide();
} else {
this.repeat.$el.hide();
this.message.$el.show();
}
}
});
}
);
@@ -1,61 +0,0 @@
/** This class renders the chart configuration form. */
define(
["utils/utils", "mvc/ui/ui-misc", "mvc/form/form-view", "mvc/form/form-data", "plugin/views/description"],
function(Utils, Ui, Form, FormData, Description) {
return Backbone.View.extend({
initialize: function(app, options) {
var self = this;
this.app = app;
this.chart = app.chart;
this.description = new Description(this.app);
this.message = new Ui.Message({
message: "There are no options for this visualization type.",
persistent: true,
status: "info"
});
this.setElement(
$("<div/>")
.append(this.description.$el)
.append(this.message.$el.addClass("ui-margin-bottom"))
.append((this.$form = $("<div/>").addClass("ui-margin-bottom")))
);
this.listenTo(this.chart, "change", function() {
self.render();
});
},
render: function() {
var self = this;
var inputs = Utils.clone(this.chart.definition.settings) || {};
var panel_option = this.chart.definition.use_panels;
if (panel_option == "both") {
inputs["__use_panels"] = {
type: "boolean",
label: "Use multi-panels",
help: "Would you like to separate your data into individual panels?"
};
} else {
this.chart.settings.set("__use_panels", panel_option == "yes" ? "true" : "false");
}
if (_.size(inputs) > 0) {
FormData.visitInputs(inputs, function(input, name) {
var model_value = self.chart.settings.get(name);
model_value !== undefined && !input.hidden && (input.value = model_value);
});
this.form = new Form.View({
inputs: inputs,
cls: "ui-portlet-plain",
onchange: function() {
self.chart.settings.set(self.form.data.create());
}
});
this.chart.settings.set(this.form.data.create());
this.$form.empty().append(this.form.$el);
this.message.$el.hide();
} else {
this.$form.empty();
this.message.$el.show();
}
}
});
}
);

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