Merge branch 'dev' into remove_openid_login_mako

This commit is contained in:
guerler
2019-01-09 16:16:42 -05:00
390 changed files with 22327 additions and 3884 deletions
+1 -1
View File
@@ -1,3 +1,3 @@
#!/bin/bash
./run_tests.sh --dockerize --python3 --db postgres --clean_pyc --integration "$@"
DOCKER_RUN_EXTRA_ARGS="--privileged" ./run_tests.sh --dockerize --python3 --db postgres --clean_pyc --integration "$@"
+1 -1
View File
@@ -1,3 +1,3 @@
#!/bin/bash
./run_tests.sh --dockerize --db postgres --clean_pyc --integration "$@"
DOCKER_RUN_EXTRA_ARGS="--privileged" ./run_tests.sh --dockerize --db postgres --clean_pyc --integration "$@"
+24 -15
View File
@@ -4,11 +4,18 @@ Client Build System
Installs, stages, and builds the client-side scripts necessary for running the
Galaxy webapp. There's no need to use this system unless you are modifying or
developing client-side scripts, or are running the development branch of
Galaxy.
Galaxy. When started through `run.sh` or any other method that utilizes
`common_startup.sh`, Galaxy will also (since 18.09) *automatically* build
the client as a part of server startup, when it detects changes, unless that
functionality is explicitly disabled.
The base dependencies you will need are Node.js and Yarn. On OSX the easiest
way to get set up is using homebrew and the command `brew install nodejs yarn`.
More information including instructions for other platforms is available at
The base dependencies used are Node.js and Yarn. Galaxy now includes these in
the virtual environment, and they can be accessed by activating that with
`source .venv/bin/activate` from the Galaxy root directory.
If you'd like to install your own dependencies, on OSX the easiest way to get
set up is using homebrew and the command `brew install nodejs yarn`. More
information including instructions for other platforms is available at
nodejs.org and yarnpkg.com.
The Galaxy client build has necessarily grown more complex in the past several
@@ -22,11 +29,11 @@ https://gitter.im/galaxyproject/Lobby.
Complete Client Build
================================================
There are many moving parts to the client build system, but the entrypoint for
There are many moving parts to the client build system, but the entry point for
most people is the 'client' rule in the Makefile at the root of the Galaxy
repository. Execute the following to perform a complete build suitable for
local development, including dependency staging, style building, script
processing and bundling. This is a development-specific build which includes
processing, and bundling. This is a development-specific build which includes
extra debugging features, and excludes several production optimizations made
during the build process.
@@ -57,19 +64,21 @@ This will first stage any dependencies (yarn-installed packages like jquery,
etc), and then will watch for changes in any of the galaxy client source files.
When a file is changed, the client will automatically rebuild, after which you
can usually force refresh your browser to see changes. Note that it is still
recommended to run 'make client' after you are finished actively developing
using 'make client-watch'.
recommended to run `make client` after you are finished actively developing
using `make client-watch`.
Changing Styles/CSS
===================
The CSS and styling used by Galaxy is also controlled from this directory.
Galaxy uses Sass for its styling, a superset of CSS that compiles to CSS. Sass
files are kept in client/galaxy/style/scss. Compiled CSS is in
static/style/blue.
Galaxy uses Sass for its styling, which is a superset of CSS that compiles down
to regular CSS. Most Galaxy styling source (.scss) files are kept in
`client/galaxy/style/scss. There are additionally style blocks alongside some
Vue components -- styles that are particular to that individual component and
do not apply site-wide.
As mentioned above, 'make client' will also rebuild styles. If you *only* want
to run the style task, use the following command from the `client` directory:
On build, the compiled css bundle is served at `/static/style/base.css`.
yarn run style
As mentioned above, `make client` will rebuild styles, as a part of the webpack
build. For iterative development, "Watch Mode" rebuilds as described above do
include style changes.
+1 -1
View File
@@ -1,2 +1,2 @@
export { setGalaxyInstance, getGalaxyInstance } from "./singleton";
export { default as monitor } from "./monitor";
export { default as galaxyFacade } from "./monitor";
+18 -14
View File
@@ -1,26 +1,30 @@
import { installMonitor } from "utils/installMonitor";
// Initializes "training wheels" causing legacy references to window.Galaxy to
// pass through the singleton accessors. All future code should access galaxy
// through getGalaxyInstance, and rarely with setGalaxyInstance
import { getGalaxyInstance, setGalaxyInstance } from "app";
import { getAppRoot } from "onload/loadConfig";
let proxy;
import { serverPath } from "utils/serverPath";
const galaxyStub = {
root: getAppRoot(),
config: {}
};
if (!proxy) {
// force references to window.galaxy to pass through the set/get instance functions
// The monitor is going to store Galaxy at window._monitorStorage["Galaxy"]
Object.defineProperty(window._monitorStorage, "Galaxy", {
if (!window.Galaxy) {
Object.defineProperty(window, "Galaxy", {
enumerable: true,
configurable: false,
get: getGalaxyInstance,
set: newValue => setGalaxyInstance(() => newValue)
get: function() {
console.warn("accessing (get) window.Galaxy", serverPath());
return getGalaxyInstance() || galaxyStub;
},
set: function(newValue) {
console.warn("accessing (set) window.Galaxy", serverPath());
setGalaxyInstance(newValue);
}
});
let existingGalaxy = getGalaxyInstance();
proxy = installMonitor("Galaxy", existingGalaxy || galaxyStub);
} else {
console.error("Detected redefinition of window.Galaxy -- skipping, but this should be investigated.", serverPath());
}
export default proxy;
export default window.Galaxy;
+12 -34
View File
@@ -4,12 +4,12 @@
import addLogging from "utils/add-logging";
import { GalaxyApp } from "./galaxy";
// import { serverPath } from "utils/serverPath";
import { serverPath } from "utils/serverPath";
export function setGalaxyInstance(factory, atTop = true) {
// console.warn("setGalaxyInstance", serverPath());
export function setGalaxyInstance(factory) {
console.warn("setGalaxyInstance", serverPath());
let storage = getStorage(atTop);
let storage = getStorage();
let newInstance = factory(GalaxyApp);
if (!(newInstance instanceof GalaxyApp)) {
newInstance = new GalaxyApp(newInstance);
@@ -18,45 +18,23 @@ export function setGalaxyInstance(factory, atTop = true) {
addLogging(newInstance, "GalaxyApp");
}
// Debugging frame property
// storage._galaxyInstance = new Proxy(newInstance, {
// get(galaxy, prop) {
// if (prop == "frame") {
// console.groupCollapsed("Frame Get", serverPath());
// console.trace();
// console.groupEnd();
// }
// return galaxy[prop];
// },
// set(galaxy, prop, val) {
// galaxy[prop] = val;
// if (prop == "frame") {
// console.groupCollapsed("Frame Set", serverPath());
// console.log(val);
// console.trace();
// console.groupEnd();
// }
// return true;
// }
// });
storage._galaxyInstance = newInstance;
return storage._galaxyInstance;
}
export function getGalaxyInstance(atTop = true) {
let storage = getStorage(atTop);
export function getGalaxyInstance() {
let storage = getStorage();
return storage._galaxyInstance;
}
export function galaxyIsInitialized(atTop = true) {
let instance = getGalaxyInstance(atTop);
export function galaxyIsInitialized() {
let instance = getGalaxyInstance();
return instance !== null;
}
export function getStorage(atTop = true) {
let storage = window !== window.top && atTop ? window.top : window;
return storage;
// Having a CORS issue in the toolshed iframe, store separate versions
// of galaxy in each window for the short-term
export function getStorage() {
return window;
}
-19
View File
@@ -1,19 +0,0 @@
/**
* These props are being exported with extended.js now since
* extended is exposing its props on window via the expose-loader
*/
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";
export function chart(options) {
return new Client(options);
}
export let chartUtilities = {
Datasets: Datasets,
Jobs: Jobs,
Series: Series
};
@@ -1,7 +1,14 @@
/**
* The big list of horrible globals we expose on window.bundleEntries.
*
* Everything that is exposed on this global variable is something that the python templates
* require for their hardcoded initializations. These objects are going to have to continue
* to exist until such time as we replace the overall application with a Vue component which
* will handle initializations for components individually.
*/
import $ from "jquery";
import "bootstrap";
// export { GalaxyApp } from "app";
import { getGalaxyInstance } from "app";
export { getGalaxyInstance, setGalaxyInstance } from "app";
import WorkflowView from "mvc/workflow/workflow-view";
import { TracksterUIView } from "viz/trackster";
@@ -11,9 +18,6 @@ export { PhylovizView as phyloviz } from "viz/phyloviz";
export { SweepsterVisualization, SweepsterVisualizationView } from "viz/sweepster";
import GalaxyLibrary from "galaxy.library";
import AdminToolshed from "admin.toolshed";
import Masthead from "layout/masthead";
import user from "mvc/user/user-model";
import Modal from "mvc/ui/ui-modal";
export { default as pages } from "galaxy.pages";
export { createTabularDatasetChunkedView } from "mvc/dataset/data";
import { HistoryCollection } from "mvc/history/history-model";
@@ -28,19 +32,6 @@ export { default as HDAModel } from "mvc/history/hda-model";
export { default as LegacyGridView } from "legacy/grid/grid-view";
export { create_chart, create_histogram } from "reports/run_stats";
export { default as ToolshedGroups } from "toolshed/toolshed.groups";
export { chart, chartUtilities } from "./chart";
export function masthead(options) {
let Galaxy = getGalaxyInstance();
if (!Galaxy.user) {
Galaxy.user = new user.User(options.user_json);
}
if (!Galaxy.masthead) {
Galaxy.masthead = new Masthead.View(options);
Galaxy.modal = new Modal.View();
$("#masthead").replaceWith(Galaxy.masthead.render().$el);
}
}
export function adminToolshed(options) {
new AdminToolshed.GalaxyApp(options);
@@ -80,3 +71,32 @@ export function multiHistory(options) {
multipanel.render(0);
});
}
// Previously wandering around as window.thing = thing in the onload script
export { default as panels } from "layout/panel";
export { show_in_overlay, hide_modal, show_message, show_modal, Modal } from "layout/modal";
export { make_popupmenu, make_popup_menus } from "ui/popupmenu";
export { default as async_save_text } from "utils/async-save-text";
// Previously "chart"
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";
export function chart(options) {
return new Client(options);
}
export let chartUtilities = {
Datasets: Datasets,
Jobs: Jobs,
Series: Series
};
export { initMasthead } from "components/Masthead/initMasthead";
export { panelManagement } from "onload/globalInits/panelManagement";
export { init_tag_click_function } from "ui/autocom_tagging";
// Used in common.mako
export { default as store } from "store";
@@ -0,0 +1,162 @@
<template>
<div class="galaxy-loader"
:class="[`galaxy-loader_${style}`, {'galaxy-loader_center': center}]"
:style="{transform: `scale(${size/100})`}">
<div class="galaxy-loader_strip-1"></div>
<div class="galaxy-loader_strip-2"></div>
<div class="galaxy-loader_strip-3"></div>
</div>
</template>
<script>
export default {
props: {
style: {
type: String,
default: "light"
},
size: {
type: Number,
default: 100
},
center: {
type: Boolean,
default: false
}
}
};
</script>
<style>
.galaxy-loader.galaxy-loader_center {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
margin: auto;
}
.galaxy-loader {
display: inline-block;
width: 100px;
height: 100px;
border-radius: 50px;
position: relative;
overflow: hidden;
}
.galaxy-loader_light {
background-color: transparent;
border: 2px solid #58585a;
box-sizing: border-box;
}
.galaxy-loader_dark {
background-color: #2c3143;
border: 2px solid #2c3143;
box-sizing: border-box;
}
.galaxy-loader_eu {
background-color: transparent;
border: 2px solid #063591;
box-sizing: border-box;
}
.galaxy-loader > div {
background-color: rgba(255, 0, 0, 0.3);
display: block;
height: 18px;
position: absolute;
border-radius: 2px;
-webkit-animation: galaxy-loader_animation 1.2s infinite ease-in-out;
animation: galaxy-loader_animation 1.2s infinite ease-in-out;
}
.galaxy-loader > .galaxy-loader_strip-1 {
top: 16px;
left: 13px;
width: 59px;
-webkit-animation-delay: -1.2s;
animation-delay: -1.2s;
}
.galaxy-loader > .galaxy-loader_strip-2 {
top: 39px;
left: 13px;
width: 44px;
-webkit-animation-delay: -1.1s;
animation-delay: -1.1s;
}
.galaxy-loader > .galaxy-loader_strip-3 {
top: 61px;
left: 25px;
width: 59px;
-webkit-animation-delay: -1s;
animation-delay: -1s;
}
.galaxy-loader_light > .galaxy-loader_strip-1,
.galaxy-loader_light > .galaxy-loader_strip-2 {
background-color: #58585a;
}
.galaxy-loader_light > .galaxy-loader_strip-3 {
background: linear-gradient(to right, rgba(207, 188, 59, 1), rgba(207, 188, 59, 1) 20%, rgba(207, 188, 59, 0) 100%);
}
.galaxy-loader_dark > .galaxy-loader_strip-1,
.galaxy-loader_dark > .galaxy-loader_strip-2 {
background-color: white;
}
.galaxy-loader_dark > .galaxy-loader_strip-3 {
background: linear-gradient(to right, rgba(207, 188, 59, 1), rgba(207, 188, 59, 1) 20%, rgba(207, 188, 59, 0) 100%);
}
.galaxy-loader_eu > .galaxy-loader_strip-1,
.galaxy-loader_eu > .galaxy-loader_strip-2 {
background-color: #063591;
}
.galaxy-loader_eu > .galaxy-loader_strip-3 {
background: linear-gradient(to right, rgba(207, 188, 59, 1), rgba(207, 188, 59, 1) 20%, rgba(207, 188, 59, 0) 100%);
}
@-webkit-keyframes galaxy-loader_animation {
0%,
60%,
100% {
-webkit-transform: scaleX(1);
}
20% {
-webkit-transform: scaleX(0.8);
}
40% {
-webkit-transform: scaleX(1.2);
}
}
@keyframes galaxy-loader_animation {
0%,
60%,
100% {
transform: scaleX(1);
-webkit-transform: scaleX(1);
}
20% {
transform: scaleX(0.8);
-webkit-transform: scaleX(0.8);
}
40% {
transform: scaleX(1.2);
-webkit-transform: scaleX(1.2);
}
}
</style>
@@ -0,0 +1,58 @@
<template>
<div id="masthead"></div>
</template>
<script>
import { getGalaxyInstance } from "app";
import Masthead from "layout/masthead";
export default {
mounted() {
this.backboneRender();
},
updated() {
this.backboneRender();
},
methods: {
backboneRender() {
// dont bother unless we're in top window
if (window.top !== window) {
return;
}
let galaxy = getGalaxyInstance();
// old backbone view
if (!galaxy.masthead) {
galaxy.masthead = new Masthead.View(this.$props);
}
// bolt the rendered DOM onto the vue component's element
let backboneDom = galaxy.masthead.render();
this.$el.appendChild(backboneDom.$el[0]);
}
}
};
</script>
<style lang="scss">
#masthead:empty {
display: none;
}
.framed {
#messagebox,
#masthead {
display: none;
}
#center,
#right,
#left {
top: 0;
}
}
</style>
@@ -0,0 +1,16 @@
/**
* Temporary function used to mount the masthead inside the current application.
* This function is exposed with the rest of the page-globals in bundledEntries.
*/
import Vue from "vue";
import Masthead from "components/Masthead";
export function initMasthead(config, container) {
console.log("initMasthead");
let Component = Vue.extend(Masthead);
return new Component({
props: Object.keys(config),
propsData: config,
el: container
});
}
@@ -413,6 +413,7 @@
</template>
<script>
import { getAppRoot } from "onload/loadConfig";
import { getGalaxyInstance } from "app";
import AjaxQueue from "utils/ajax-queue";
import axios from "axios";
import _l from "utils/localization";
@@ -1376,6 +1377,7 @@ export default {
this.mapping.splice(index, 1);
},
refreshAndWait(response) {
let Galaxy = getGalaxyInstance();
if (Galaxy && Galaxy.currHistoryPanel) {
Galaxy.currHistoryPanel.refreshContents();
}
@@ -1394,8 +1396,8 @@ export default {
"Unknown error encountered while running your upload job, this could be a server issue or a problem with the upload definition.";
this.doFullJobCheck(jobId);
} else {
const history =
parent.Galaxy && parent.Galaxy.currHistoryPanel && parent.Galaxy.currHistoryPanel.model;
let Galaxy = getGalaxyInstance();
const history = Galaxy && Galaxy.currHistoryPanel && Galaxy.currHistoryPanel.model;
history.refresh();
this.oncreate();
}
@@ -1480,6 +1482,7 @@ export default {
this.oncreate();
}
} else {
let Galaxy = getGalaxyInstance();
const historyId = Galaxy.currHistoryPanel.model.id;
let elements, targets;
if (collectionType) {
@@ -0,0 +1,17 @@
import { getAppRoot } from "onload/loadConfig";
import axios from "axios";
export function getErrorStack() {
let url = `${getAppRoot()}api/tools/error_stack`;
return axios.get(url);
}
export function getDisplayApplications() {
let url = `${getAppRoot()}api/display_applications`;
return axios.get(url);
}
export function reloadDisplayApplications(ids) {
let url = `${getAppRoot()}api/display_applications/reload`;
return axios.post(url, { ids: ids });
}
@@ -0,0 +1,124 @@
<template>
<div>
<b-alert :show="messageVisible" :variant="messageVariant">
{{ messageText }}
</b-alert>
<b-alert :show="warningVisible" variant="warning">
No display applications available.
</b-alert>
<div v-if="applicationsVisible" class="card-header">
There are currently {{ applicationsLength }}
<b-button size="sm" @click.prevent="reloadAll()" title="Reload all display applications" data-placement="bottom">
<span class="fa fa-refresh"/>
</b-button>
display applications loaded.
</div>
<b-table id="display-applications-grid" v-if="applicationsVisible" striped :fields="applicationsAttributes" :items="applications">
<template slot="reload" slot-scope="data">
<b-button size="sm" title="Reload display application" data-placement="bottom" @click.prevent="reload(data.item.id, data.index)">
<span class="fa fa-refresh"/>
</b-button>
</template>
<template slot="links" slot-scope="data">
<li v-for="link in data.item.links">{{ link.name }}</li>
</template>
</b-table>
</div>
</template>
<script>
import { getDisplayApplications, reloadDisplayApplications } from "./AdminServices.js";
export default {
data() {
return {
applications: [],
applicationsLoaded: false,
applicationsAttributes: [
{ key: "reload" },
{ key: "name", sortable: true },
{ key: "id", sortable: true },
{ key: "version" },
{ key: "links" }
],
messageText: null,
messageVariant: null
};
},
computed: {
applicationsIndex: function() {
let result = {};
for (let app of this.applications) {
result[app.id] = app;
}
return result;
},
applicationsAll: function() {
let result = [];
for (let app of this.applications) {
result.push(app.id);
}
return result;
},
applicationsVisible: function() {
return this.applications.length > 0;
},
applicationsLength: function() {
return this.applications.length;
},
messageVisible: function() {
return this.messageText != null;
},
warningVisible: function() {
return !this.applicationsVisible && this.applicationsLoaded;
}
},
created() {
getDisplayApplications()
.then(response => {
this.applications = response.data;
this.applicationsLoaded = true;
})
.catch(e => {
this._errorMessage(e);
});
},
methods: {
reload: function(id) {
this._reload([id]);
},
reloadAll: function() {
let ids = [];
for (let app of this.applications) {
ids.push(app.id);
}
this._reload(ids);
},
_reload: function(ids) {
this._highlightRows(this.applicationsAll, "default");
reloadDisplayApplications(ids)
.then(response => {
this.messageVariant = "info";
this.messageText = response.data.message;
this._highlightRows(response.data.failed, "danger");
this._highlightRows(response.data.reloaded, "success");
})
.catch(e => {
this._errorMessage(e);
});
},
_highlightRows: function(appList, status) {
for (let appIndex of appList) {
let app = this.applicationsIndex[appIndex];
if (app) {
app._rowVariant = status;
}
}
},
_errorMessage: function(e) {
let message = e && e.response && e.response.data && e.response.data.err_msg;
this.messageText = message || "Request failed for an unknown reason.";
this.messageVariant = "danger";
}
}
};
</script>
@@ -0,0 +1,54 @@
<template>
<div>
<b-alert :show="messageVisible" variant="danger">
{{ messageText }}
</b-alert>
<b-alert :show="infoVisible" variant="info">
No errors available.
</b-alert>
<b-table striped v-if="errorStackVisible"
:fields="errorStackAttributes"
:items="errorStack"/>
</div>
</template>
<script>
import { getErrorStack } from "./AdminServices.js";
export default {
data() {
return {
errorStack: [],
errorStackLoaded: false,
errorStackAttributes: [
{ key: "time", sortable: true },
{ key: "phase", sortable: true },
{ key: "file", sortable: true },
{ key: "error" }
],
messageText: null
};
},
computed: {
errorStackVisible: function() {
return this.errorStack.length > 0;
},
messageVisible: function() {
return this.messageText != null;
},
infoVisible: function() {
return !this.errorStackVisible && this.errorStackLoaded;
}
},
created() {
getErrorStack()
.then(response => {
this.errorStack = response.data;
this.errorStackLoaded = true;
})
.catch(e => {
let message = e && e.response && e.response.data && e.response.data.err_msg;
this.messageText = message || "Request failed for an unknown reason.";
});
}
};
</script>
@@ -26,9 +26,12 @@
import axios from "axios";
import Vue from "vue";
import BootstrapVue from "bootstrap-vue";
import { getAppRoot } from "onload";
Vue.use(BootstrapVue);
let rootUrl = getAppRoot();
export default {
props: {
show_welcome_with_login: {
@@ -59,17 +62,17 @@ export default {
submit: function(method) {
let data = {login: this.login, password: this.password};
axios
.post(`${Galaxy.root}user/login`, data)
.post(`${rootUrl}user/login`, data)
.then(response => {
if (response.data.message && response.data.status) {
alert(response.data.message);
}
if (response.data.expired_user) {
window.location = `${Galaxy.root}root/login?expired_user=${response.data.expired_user}`;
window.location = `${rootUrl}root/login?expired_user=${response.data.expired_user}`;
} else if (response.data.redirect) {
window.location = response.data.redirect;
window.location = encodeURI(response.data.redirect);
} else {
window.location = `${Galaxy.root}`;
window.location = `${rootUrl}`;
}
})
.catch(error => {
@@ -81,7 +84,7 @@ export default {
reset: function(ev) {
ev.preventDefault();
axios
.post(`${Galaxy.root}user/reset_password`, {email: this.login})
.post(`${rootUrl}user/reset_password`, {email: this.login})
.then(response => {
this.messageVariant = "info";
this.messageText = response.data.message;
@@ -1,38 +1,29 @@
import $ from "jquery";
import _ from "underscore";
import { getGalaxyInstance } from "app";
import { getAppRoot } from "onload";
import _l from "utils/localization";
import { setGalaxyInstance } from "app";
import { getAppRoot } from "onload/loadConfig";
import AdminPanel from "./panels/admin-panel";
import FormWrapper from "mvc/form/form-wrapper";
import GridView from "mvc/grid/grid-view";
import QueryStringParsing from "utils/query-string-parsing";
import Router from "layout/router";
import Utils from "utils/utils";
import Page from "layout/page";
import DataTables from "components/admin/DataTables.vue";
import DataTypes from "components/admin/DataTypes.vue";
import DataManagerView from "components/admin/DataManager/DataManagerView.vue";
import DataManagerRouter from "components/admin/DataManager/DataManagerRouter.vue";
import ErrorStack from "components/admin/ErrorStack.vue";
import DisplayApplications from "components/admin/DisplayApplications.vue";
import Vue from "vue";
import { serverPath } from "utils/serverPath";
window.app = function app(options, bootstrapped) {
console.warn("Admin init", serverPath());
export const getAdminRouter = (Galaxy, options) => {
let galaxyRoot = getAppRoot();
let Galaxy = setGalaxyInstance(GalaxyApp => {
let galaxy = new GalaxyApp(options, bootstrapped);
galaxy.debug("admin app");
return galaxy;
});
/** Routes */
var AdminRouter = Router.extend({
return Router.extend({
routes: {
"(/)admin(/)": "home",
"(/)admin(/)users": "show_users",
"(/)admin(/)roles": "show_roles",
"(/)admin(/)groups": "show_groups",
"(/)admin(/)error_stack": "show_error_stack",
"(/)admin(/)display_applications": "show_display_applications",
"(/)admin(/)tool_versions": "show_tool_versions",
"(/)admin(/)quotas": "show_quotas",
"(/)admin(/)repositories": "show_repositories",
@@ -45,102 +36,91 @@ window.app = function app(options, bootstrapped) {
},
authenticate: function() {
let Galaxy = getGalaxyInstance();
return Galaxy.user && Galaxy.user.id && Galaxy.user.get("is_admin");
},
not_found: function() {
window.location.href = window.location.href;
window.location.reload(); // = window.location.href;
},
home: function() {
this.page
.$("#galaxy_main")
.prop("src", `${getAppRoot()}admin/center?message=${options.message}&status=${options.status}`);
.prop("src", `${galaxyRoot}admin/center?message=${options.message}&status=${options.status}`);
},
show_users: function() {
this.page.display(
new GridView({
url_base: `${getAppRoot()}admin/users_list`,
url_data: Galaxy.params
})
);
this._show_grid_view("admin/users_list");
},
show_roles: function() {
this.page.display(
new GridView({
url_base: `${getAppRoot()}admin/roles_list`,
url_data: Galaxy.params
})
);
this._show_grid_view("admin/roles_list");
},
show_groups: function() {
this.page.display(
new GridView({
url_base: `${getAppRoot()}admin/groups_list`,
url_data: Galaxy.params
})
);
this._show_grid_view("admin/groups_list");
},
show_repositories: function() {
this.page.display(
new GridView({
url_base: `${getAppRoot()}admin_toolshed/browse_repositories`,
url_data: Galaxy.params
})
);
this._show_grid_view("admin_toolshed/browse_repositories");
},
show_tool_versions: function() {
this._show_grid_view("admin/tool_versions_list");
},
show_quotas: function() {
this._show_grid_view("admin/quotas_list");
},
_show_grid_view: function(urlSuffix) {
let Galaxy = getGalaxyInstance();
this.page.display(
new GridView({
url_base: `${getAppRoot()}admin/tool_versions_list`,
url_base: `${galaxyRoot}${urlSuffix}`,
url_data: Galaxy.params
})
);
},
show_quotas: function() {
this.page.display(
new GridView({
url_base: `${getAppRoot()}admin/quotas_list`,
url_data: Galaxy.params
})
);
},
_display_vue_helper: function(component, props) {
let instance = Vue.extend(component);
let vm = document.createElement("div");
this.page.display(vm);
new instance(props).$mount(vm);
},
show_data_tables: function() {
this._display_vue_helper(DataTables);
},
show_data_types: function() {
this._display_vue_helper(DataTypes);
},
show_error_stack: function() {
this._display_vue_helper(ErrorStack);
},
show_display_applications: function() {
this._display_vue_helper(DisplayApplications);
},
show_data_manager: function(path) {
let Galaxy = getGalaxyInstance();
console.log("show_data_manager");
let vueMount = document.createElement("div");
this.page.display(vueMount);
// always set the route back to the base, i.e.
// `${getAppRoot()}admin/data_manager`
// `${galaxyRoot}admin/data_manager`
Galaxy.debug("show_data_manager: path='" + path + "'");
DataManagerRouter.replace(path || "/");
new Vue({ router: DataManagerRouter, render: h => h(DataManagerView) }).$mount(vueMount);
},
show_forms: function() {
this.page.display(
new GridView({
url_base: `${getAppRoot()}forms/forms_list`,
url_data: Galaxy.params
})
);
this._show_grid_view("forms/forms_list");
},
show_form: function(form_id) {
@@ -214,15 +194,4 @@ window.app = function app(options, bootstrapped) {
this.page.display(new FormWrapper.View(form_defs[form_id]));
}
});
$(() => {
_.extend(options.config, { active_view: "admin" });
Utils.setWindowTitle("Administration");
Galaxy.page = new Page.View(
_.extend(options, {
Left: AdminPanel,
Router: AdminRouter
})
);
});
};
@@ -0,0 +1,24 @@
import _ from "underscore";
import { standardInit, addInitialization } from "onload";
import { getAdminRouter } from "./AdminRouter";
import AdminPanel from "../panels/admin-panel";
import Utils from "utils/utils";
import Page from "layout/page";
addInitialization((Galaxy, { options }) => {
console.log("Admin custom page setup");
Object.assign(options.config, {
active_view: "admin"
});
let pageOptions = Object.assign({}, options, {
Left: AdminPanel,
Router: getAdminRouter(Galaxy, options)
});
Utils.setWindowTitle("Administration");
Galaxy.page = new Page.View(pageOptions);
});
window.addEventListener("load", () => standardInit("admin"));
@@ -1,12 +1,20 @@
import $ from "jquery";
/** 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
* * a right history menu that shows the user's current data
* * a center panel
* Both panels (generally) persist while the center panel shows any
* UI needed for the current step of an analysis, like:
* * tool forms to set tool parameters,
* * tables showing the contents of datasets
* * etc.
*/
import _ from "underscore";
import { setGalaxyInstance } from "app";
import { getAppRoot } from "onload/loadConfig";
import { getGalaxyInstance } from "app";
import { getAppRoot } from "onload";
import decodeUriComponent from "decode-uri-component";
import Router from "layout/router";
import ToolPanel from "./panels/tool-panel";
import HistoryPanel from "./panels/history-panel";
import Page from "layout/page";
import ToolForm from "mvc/tool/tool-form";
import FormWrapper from "mvc/form/form-wrapper";
import Sharing from "components/Sharing.vue";
@@ -31,28 +39,11 @@ import Citations from "components/Citations.vue";
import DisplayStructure from "components/DisplayStructured.vue";
import Vue from "vue";
/** 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
* * a right history menu that shows the user's current data
* * a center panel
* Both panels (generally) persist while the center panel shows any
* UI needed for the current step of an analysis, like:
* * tool forms to set tool parameters,
* * tables showing the contents of datasets
* * etc.
*/
window.app = function app(options, bootstrapped) {
let Galaxy = setGalaxyInstance(GalaxyApp => {
let galaxy = new GalaxyApp(options, bootstrapped);
galaxy.debug("analysis app");
return galaxy;
});
/** Routes */
var AnalysisRouter = Router.extend({
/** Routes */
export const getAnalysisRouter = Galaxy =>
Router.extend({
routes: {
"(/)": "home",
"(/)(#)(_=_)": "home",
"(/)root*": "home",
"(/)tours(/)(:tour_id)": "show_tours",
"(/)user(/)": "show_user",
@@ -87,6 +78,7 @@ window.app = function app(options, bootstrapped) {
require_login: ["show_user", "show_user_form", "show_workflows"],
authenticate: function(args, name) {
let Galaxy = getGalaxyInstance();
return (Galaxy.user && Galaxy.user.id) || this.require_login.indexOf(name) == -1;
},
@@ -110,6 +102,7 @@ window.app = function app(options, bootstrapped) {
},
show_user_form: function(form_id) {
let Galaxy = getGalaxyInstance();
var model = new UserPreferences.Model({
user_id: Galaxy.params.id
});
@@ -393,19 +386,3 @@ 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,
Right: HistoryPanel,
Router: AnalysisRouter
})
);
});
};
@@ -0,0 +1,23 @@
import { standardInit, addInitialization } from "onload";
import { getAnalysisRouter } from "./AnalysisRouter";
import ToolPanel from "entry/panels/tool-panel";
import HistoryPanel from "entry/panels/history-panel";
import Page from "layout/page";
addInitialization((Galaxy, { options = {} }) => {
console.log("Analysis custom page setup");
let pageOptions = Object.assign({}, options, {
config: Object.assign({}, options.config, {
hide_panels: Galaxy.params.hide_panels,
hide_masthead: Galaxy.params.hide_masthead
}),
Left: ToolPanel,
Right: HistoryPanel,
Router: getAnalysisRouter(Galaxy)
});
Galaxy.page = new Page.View(pageOptions);
});
window.addEventListener("load", () => standardInit("analysis"));
+3
View File
@@ -0,0 +1,3 @@
import { standardInit } from "onload";
window.addEventListener("load", () => standardInit("generic"));
@@ -0,0 +1,42 @@
import $ from "jquery";
import { getAppRoot } from "onload";
import { standardInit, addInitialization } from "onload";
import Page from "layout/page";
import Login from "components/login/Login.vue";
import Password from "components/login/Password.vue";
import Vue from "vue";
export function showOrRedirect(Galaxy, { options }) {
console.log("showOrRedirect");
if (!options.show_welcome_with_login) {
let appRoot = getAppRoot();
let destination = options.redirect ? options.redirect : appRoot;
let params = $.param({
use_panels: "True",
redirect: encodeURI(destination)
});
window.location.href = `${appRoot}user/login?${params}`;
}
}
export function initLoginView(Galaxy, { options }) {
console.log("initLoginView");
/*let pageConfig = Object.assign({}, options, {
Right: getLoginPage(options)
});
Galaxy.page = new Page.View(pageConfig);*/
Galaxy.page = new Page.View(options);
var vm = document.createElement("div");
Galaxy.display(vm);
var component = Galaxy.params.token || Galaxy.params.expired_user ? Password : Login;
var loginInstance = Vue.extend(component);
new loginInstance({propsData: options}).$mount(vm);
}
addInitialization(showOrRedirect, initLoginView);
window.addEventListener("load", () => standardInit("login"));
@@ -1,19 +1,18 @@
import _ from "underscore";
import $ from "jquery";
import Backbone from "backbone";
import _ from "underscore";
import _l from "utils/localization";
import { getGalaxyInstance } from "app";
var AdminPanel = Backbone.View.extend({
initialize: function(page, options) {
let Galaxy = getGalaxyInstance();
var self = this;
this.page = page;
this.root = options.root;
this.config = options.config;
this.settings = options.settings;
this.model = new Backbone.Model({
title: `Galaxy version ${Galaxy.config.version_major}`
title: `Galaxy version ${getGalaxyInstance().config.version_major}`
});
this.categories = new Backbone.Collection([
{
@@ -34,6 +33,7 @@ var AdminPanel = Backbone.View.extend({
{
title: _l("Display applications"),
url: "admin/display_applications",
target: "__use_router__",
id: "admin-link-display-applications"
},
{
@@ -132,7 +132,8 @@ var AdminPanel = Backbone.View.extend({
},
{
title: _l("View error logs"),
url: "admin/tool_errors"
url: "admin/error_stack",
target: "__use_router__"
}
]
}
@@ -1,15 +1,15 @@
import Backbone from "backbone";
import _l from "utils/localization";
import { getGalaxyInstance } from "app";
import Ui from "mvc/ui/ui-misc";
import historyOptionsMenu from "mvc/history/options-menu";
import CurrentHistoryView from "mvc/history/history-view-edit-current";
import _l from "utils/localization";
import { getGalaxyInstance } from "app";
/** the right hand panel in the analysis page that shows the current history */
var HistoryPanel = Backbone.View.extend({
initialize: function(page, options) {
var Galaxy = getGalaxyInstance();
var self = this;
let Galaxy = getGalaxyInstance();
let self = this;
this.userIsAnonymous = Galaxy.user.isAnonymous();
this.allow_user_dataset_purge = options.config.allow_user_dataset_purge;
@@ -1,13 +1,17 @@
import _ from "underscore";
import Backbone from "backbone";
import { getAppRoot } from "onload/loadConfig";
import { getGalaxyInstance } from "app";
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";
import { getGalaxyInstance } from "app";
import { getAppRoot } from "onload";
var ToolPanel = Backbone.View.extend({
initialize: function(page, options) {
let Galaxy = getGalaxyInstance();
let appRoot = getAppRoot();
// access configuration options
var config = options.config;
this.root = options.root;
@@ -31,7 +35,7 @@ var ToolPanel = Backbone.View.extend({
// add upload modal
this.upload_button = new Upload({
upload_path: config.nginx_upload_path || `${getAppRoot()}api/tools`,
upload_path: config.nginx_upload_path || `${appRoot}api/tools`,
chunk_upload_size: config.chunk_upload_size,
ftp_upload_site: config.ftp_upload_site,
default_genome: config.default_genome,
@@ -39,7 +43,6 @@ var ToolPanel = Backbone.View.extend({
});
// add uploader button to Galaxy object
let Galaxy = getGalaxyInstance();
Galaxy.upload = this.upload_button;
// components for panel definition
@@ -79,8 +82,9 @@ var ToolPanel = Backbone.View.extend({
/** build a link to one tool */
_templateTool: function(tool) {
let appRoot = getAppRoot();
return `<div class="toolTitle">
<a href="${getAppRoot()}${tool.href}" target="galaxy_main">
<a href="${appRoot}${tool.href}" target="galaxy_main">
${tool.title}
</a>
</div>`;
@@ -88,8 +92,9 @@ var ToolPanel = Backbone.View.extend({
/** build a link to 'All Workflows' */
_templateAllWorkflow: function(tool) {
let appRoot = getAppRoot();
return `<div class="toolTitle">
<a href="${getAppRoot()}${tool.href}">
<a href="${appRoot}${tool.href}">
${tool.title}
</a>
</div>`;
@@ -97,8 +102,9 @@ var ToolPanel = Backbone.View.extend({
/** build links to workflows in toolpanel */
_templateWorkflowLink: function(wf) {
let appRoot = getAppRoot();
return `<div class="toolTitle">
<a class="${wf.cls}" href="${getAppRoot()}${wf.href}">
<a class="${wf.cls}" href="${appRoot}${wf.href}">
${_.escape(wf.title)}
</a>
</div>`;
+34 -34
View File
@@ -1,8 +1,22 @@
import $ from "jquery";
import _l from "utils/localization";
import "libs/jquery/jquery.form";
import "libs/jquery/jquery.wymeditor";
import "libs/jquery/jquery.autocomplete";
import GridView from "mvc/grid/grid-view";
import { show_modal, hide_modal } from "layout/modal";
import { make_popupmenu } from "ui/popupmenu";
import { getGalaxyInstance } from "app";
import { buildConfig } from "utils/genericConfig";
// Built a generic config container for the properties that
// are passed in from python then used (formerly) globally
const configs = buildConfig();
// Turn these window variables into config props list_objects_url,
// set_accessible_url, get_name_and_link_url, editor_base_path,
// iframe_base_path, save_url, page_id, page_list_url
function setPageConfigs(incoming = {}) {
return Object.assign(configs, incoming);
}
var WYMeditor = window.WYMeditor;
@@ -29,22 +43,6 @@ var CONTROLS = {
DIALOG_EMBED_VISUALIZATION: "embed_visualization"
};
// Initialize Galaxy elements.
function init_galaxy_elts(wym) {
// Set up events to make annotation easy.
$(".annotation", wym._doc.body).each(function() {
$(this).click(function() {
// Works in Safari, not in Firefox.
var range = wym._doc.createRange();
range.selectNodeContents(this);
var selection = window.getSelection();
selection.removeAllRanges();
selection.addRange(range);
var t = "";
});
});
}
// Based on the dialog type, return a dictionary of information about an item
function get_item_info(dialog_type) {
var item_singular;
@@ -86,7 +84,7 @@ function get_item_info(dialog_type) {
// Build ajax URL that lists items for selection.
var item_list_action = `list_${item_plural.toLowerCase()}_for_selection`;
var ajax_url = list_objects_url.replace("LIST_ACTION", item_list_action);
var ajax_url = configs.list_objects_url.replace("LIST_ACTION", item_list_action);
// Set up and return dict.
return {
@@ -100,7 +98,7 @@ function get_item_info(dialog_type) {
// Make an item importable.
function make_item_importable(item_controller, item_id, item_type) {
var ajax_url = set_accessible_url.replace("ITEM_CONTROLLER", item_controller);
var ajax_url = configs.set_accessible_url.replace("ITEM_CONTROLLER", item_controller);
$.ajax({
type: "POST",
url: ajax_url,
@@ -265,7 +263,6 @@ WYMeditor.editor.prototype.dialog = function(dialogType, dialogFeatures, bodyHtm
if (iRows > 0 && iCols > 0) {
var table = wym._doc.createElement(WYMeditor.TABLE);
var newRow = null;
var newCol = null;
var sCaption = $(wym._options.captionSelector).val();
@@ -330,6 +327,7 @@ WYMeditor.editor.prototype.dialog = function(dialogType, dialogFeatures, bodyHtm
url_base: item_info.list_ajax_url,
embedded: true
});
let Galaxy = getGalaxyInstance();
Galaxy.modal.show({
title: `Insert Link to ${item_info.singular}`,
body: $("<div/>")
@@ -349,7 +347,6 @@ WYMeditor.editor.prototype.dialog = function(dialogType, dialogFeatures, bodyHtm
if ($("#make-importable:checked").val() != null) make_importable = true;
// Insert links to history for each checked item.
var item_ids = new Array();
grid.$("input[name=id]:checked").each(function() {
var item_id = $(this).val();
@@ -357,7 +354,7 @@ WYMeditor.editor.prototype.dialog = function(dialogType, dialogFeatures, bodyHtm
if (make_importable) make_item_importable(item_info.controller, item_id, item_info.singular);
// Insert link(s) to item(s). This is done by getting item info and then manipulating wym.
var url_template = get_name_and_link_url + item_id;
var url_template = configs.get_name_and_link_url + item_id;
var ajax_url = url_template.replace("ITEM_CONTROLLER", item_info.controller);
$.getJSON(ajax_url, returned_item_info => {
// Get link text.
@@ -400,7 +397,7 @@ WYMeditor.editor.prototype.dialog = function(dialogType, dialogFeatures, bodyHtm
dialogType == CONTROLS.DIALOG_EMBED_VISUALIZATION
) {
// Based on item type, set useful vars.
var item_info;
let item_info;
switch (dialogType) {
case CONTROLS.DIALOG_EMBED_HISTORY:
item_info = get_item_info(CONTROLS.ITEM_HISTORY);
@@ -419,10 +416,11 @@ WYMeditor.editor.prototype.dialog = function(dialogType, dialogFeatures, bodyHtm
break;
}
var grid = new GridView({
let grid = new GridView({
url_base: item_info.list_ajax_url,
embedded: true
});
let Galaxy = getGalaxyInstance();
Galaxy.modal.show({
title: `Insert Link to ${item_info.singular}`,
body: $("<div/>")
@@ -492,7 +490,9 @@ WYMeditor.editor.prototype.dialog = function(dialogType, dialogFeatures, bodyHtm
}
};
export default function pagesEditorOnload() {
export default function pagesEditorOnload(renderedConfigs = {}) {
setPageConfigs(renderedConfigs);
// Generic error handling
$(document).ajaxError((e, x) => {
// console.log( e, x );
@@ -503,8 +503,8 @@ export default function pagesEditorOnload() {
// Create editor
$("[name=page_content]").wymeditor({
skin: "galaxy",
basePath: editor_base_path,
iframeBasePath: iframe_base_path,
basePath: configs.editor_base_path,
iframeBasePath: configs.iframe_base_path,
// + WYMeditor.HTML
boxHtml: `<table class='wym_box' width='100%' height='100%'><tr><td><div class='wym_area_top'>${
WYMeditor.TOOLS
@@ -552,12 +552,12 @@ export default function pagesEditorOnload() {
// Do save.
$.ajax({
url: save_url,
url: configs.save_url,
type: "POST",
data: {
id: page_id,
id: configs.page_id,
content: editor.xhtml(),
annotations: JSON.stringify(new Object()),
annotations: JSON.stringify({}),
// annotations: JSON.stringify(annotations),
_: "true"
},
@@ -580,7 +580,7 @@ export default function pagesEditorOnload() {
if (changed) {
var do_close = () => {
window.onbeforeunload = undefined;
window.document.location = page_list_url;
window.document.location = configs.page_list_url;
};
show_modal(
"Close editor",
@@ -596,7 +596,7 @@ export default function pagesEditorOnload() {
}
);
} else {
window.document.location = page_list_url;
window.document.location = configs.page_list_url;
}
});
+7
View File
@@ -222,6 +222,13 @@ var Collection = Backbone.Collection.extend({
target: "_blank"
});
}
if (options.helpsite_url) {
helpTab.menu.unshift({
title: _l("Galaxy Help"),
url: options.helpsite_url,
target: "_blank"
});
}
this.add(helpTab);
//
+7 -15
View File
@@ -1,7 +1,7 @@
/* global $ */
import $ from "jquery";
// Modal dialog boxes
var Modal = function(options) {
export const Modal = function(options) {
this.$overlay = options.overlay;
this.$dialog = options.dialog;
this.$header = this.$dialog.find(".modal-header");
@@ -96,6 +96,7 @@ $.extend(Modal.prototype, {
var modal;
// TODO: move into init chain
$(() => {
modal = new Modal({
overlay: $("#top-modal"),
@@ -105,11 +106,11 @@ $(() => {
});
// Backward compatibility
function hide_modal() {
export function hide_modal() {
modal.hide();
}
function show_modal(title, body, buttons, extra_buttons, init_fn) {
export function show_modal(title, body, buttons, extra_buttons, init_fn) {
modal.setContent({
title: title,
body: body,
@@ -119,7 +120,7 @@ function show_modal(title, body, buttons, extra_buttons, init_fn) {
modal.show({ backdrop: true }, init_fn);
}
function show_message(title, body, buttons, extra_buttons, init_fn) {
export function show_message(title, body, buttons, extra_buttons, init_fn) {
modal.setContent({
title: title,
body: body,
@@ -129,7 +130,7 @@ function show_message(title, body, buttons, extra_buttons, init_fn) {
modal.show({ backdrop: false }, init_fn);
}
function show_in_overlay(options) {
export function show_in_overlay(options) {
var width = options.width || "600";
var height = options.height || "400";
var scroll = options.scroll || "auto";
@@ -148,12 +149,3 @@ function show_in_overlay(options) {
});
modal.show({ backdrop: true });
}
// ============================================================================
export default {
Modal: Modal,
hide_modal: hide_modal,
show_modal: show_modal,
show_message: show_message,
show_in_overlay: show_in_overlay
};
+17 -11
View File
@@ -203,23 +203,29 @@ var CenterPanel = Backbone.View.extend({
this.setElement($(this.template()));
this.$frame = this.$(".center-frame");
this.$panel = this.$(".center-panel");
this.$frame.on("load", _.bind(this._iframeChangeHandler, this));
this.$frame.on("load", this._iframeChangeHandler.bind(this));
},
/** Display iframe if its target url changes, hide center panel */
_iframeChangeHandler: function(ev) {
var iframe = ev.currentTarget;
var location = iframe.contentWindow && iframe.contentWindow.location;
if (location && location.host) {
$(iframe).show();
this.$panel.empty().hide();
var Galaxy = getGalaxyInstance();
Galaxy.trigger("center-frame:load", {
fullpath: location.pathname + location.search + location.hash,
pathname: location.pathname,
search: location.search,
hash: location.hash
});
var Galaxy = getGalaxyInstance();
// Adding try/catch to manage a CORS error in toolshed. Accessing
// location.host is a CORS no-no
try {
if (location && location.host) {
$(iframe).show();
this.$panel.empty().hide();
Galaxy.trigger("center-frame:load", {
fullpath: location.pathname + location.search + location.hash,
pathname: location.pathname,
search: location.search,
hash: location.hash
});
}
} catch (err) {
console.warn("_iframeChangeHandler error", ev, location, Galaxy);
}
},
+14 -3
View File
@@ -1,3 +1,7 @@
import _ from "underscore";
import jQuery from "jquery";
import Backbone from "backbone";
// This is necessary so that, when nested arrays are used in ajax/post/get methods, square brackets ('[]') are
// not appended to the identifier of a nested array.
jQuery.ajaxSettings.traditional = true;
@@ -7,6 +11,10 @@ import Utils from "utils/utils";
import GridModel from "legacy/grid/grid-model";
import Templates from "legacy/grid/grid-template";
import PopupMenu from "mvc/ui/popup-menu";
import { init_refresh_on_change } from "onload/globalInits/init_refresh_on_change";
var $ = jQuery;
// grid view
export default Backbone.View.extend({
// model
@@ -19,6 +27,11 @@ export default Backbone.View.extend({
this.title = grid_config.title;
this.title_id = grid_config.title_id;
var self = this;
// why is this a global?
// Answer: autocom_tagging passes this function from python and
// it must be on window scope or it won't work.
// TODO: refactor this out by turning the tagging functionality into a component
window.add_tag_to_grid_filter = (tag_name, tag_value) => {
// Put tag name and value together.
var tag = tag_name + (tag_value !== undefined && tag_value !== "" ? `:${tag_value}` : "");
@@ -117,9 +130,7 @@ export default Backbone.View.extend({
// attach global event handler
// TODO: redundant (the onload/standard page handlers do this) - but needed because these are constructed after page ready
if (window.init_refresh_on_change) {
window.init_refresh_on_change();
}
init_refresh_on_change();
},
// Initialize grid controls
@@ -17,7 +17,10 @@ require("imports-loader?jQuery=jqueryVendor!libs/jquery/jquery-ui");
require("imports-loader?jQuery=jqueryVendor!libs/farbtastic");
require("imports-loader?jQuery=jqueryVendor,$=jqueryVendor,define=>false!jquery.cookie");
require("imports-loader?jQuery=jqueryVendor!libs/jquery/jquery.dynatree");
require("imports-loader?jQuery=jqueryVendor!libs/jquery/jquery.wymeditor");
require("imports-loader?jQuery=jqueryVendor!jquery.complexify");
require("imports-loader?jQuery=jqueryVendor!jquery-migrate");
// require("imports-loader?jQuery=jqueryVendor!../ui/autocom_tagging");
module.exports = jQuery;
@@ -1,12 +1,10 @@
import LIST_ITEM from "mvc/list/list-item";
import DATASET_LI from "mvc/dataset/dataset-li";
import _ from "underscore";
import $ from "jquery";
import { ListItemView, FoldoutListItemView } from "mvc/list/list-item";
import { DatasetListItemView } from "mvc/dataset/dataset-li";
import BASE_MVC from "mvc/base-mvc";
import _l from "utils/localization";
//==============================================================================
var FoldoutListItemView = LIST_ITEM.FoldoutListItemView;
var ListItemView = LIST_ITEM.ListItemView;
/** @class Read only view for DatasetCollection.
*/
var DCListItemView = FoldoutListItemView.extend(
@@ -189,9 +187,9 @@ DCEListItemView.prototype.templates = (() => {
/** @class Read only view for a DatasetCollectionElement that is also an DatasetAssociation
* (a dataset contained in a dataset collection).
*/
var DatasetDCEListItemView = DATASET_LI.DatasetListItemView.extend(
var DatasetDCEListItemView = DatasetListItemView.extend(
/** @lends DatasetDCEListItemView.prototype */ {
className: `${DATASET_LI.DatasetListItemView.prototype.className} dataset-collection-element`,
className: `${DatasetListItemView.prototype.className} dataset-collection-element`,
/** set up */
initialize: function(attributes) {
@@ -199,7 +197,7 @@ var DatasetDCEListItemView = DATASET_LI.DatasetListItemView.extend(
this.logger = this.model.logger = attributes.logger;
}
this.log("DatasetDCEListItemView.initialize:", attributes);
DATASET_LI.DatasetListItemView.prototype.initialize.call(this, attributes);
DatasetListItemView.prototype.initialize.call(this, attributes);
},
/** In this override, only get details if in the ready state.
@@ -210,7 +208,7 @@ var DatasetDCEListItemView = DATASET_LI.DatasetListItemView.extend(
if (view.model.inReadyState() && !view.model.hasDetails()) {
return view.model.fetch({ silent: true });
}
return jQuery.when();
return $.when();
},
// ......................................................................... misc
@@ -239,7 +237,7 @@ DatasetDCEListItemView.prototype.templates = (() => {
"element"
);
return _.extend({}, DATASET_LI.DatasetListItemView.prototype.templates, {
return _.extend({}, DatasetListItemView.prototype.templates, {
titleBar: titleBarTemplate
});
})();
@@ -1,9 +1,7 @@
import _ from "underscore";
import { getAppRoot } from "onload/loadConfig";
import LIST_VIEW from "mvc/list/list-view";
import DC_MODEL from "mvc/collection/collection-model";
import DC_LI from "mvc/collection/collection-li";
import BASE_MVC from "mvc/base-mvc";
import _l from "utils/localization";
var logNamespace = "collections";
@@ -56,7 +54,6 @@ var CollectionView = _super.extend(
var panel = this;
this.handleWarning($newRender);
panel.log("_queueNewRender:", $newRender, speed);
// TODO: jquery@1.12 doesn't change display when the elem has display: flex
// this causes display: block for those elems after the use of show/hide animations
// animations are removed from this view for now until fixed
@@ -1,3 +1,6 @@
import _ from "underscore";
import jQuery from "jquery";
import Backbone from "backbone";
import HDCA from "mvc/history/hdca-model";
import STATES from "mvc/dataset/states";
import BASE_MVC from "mvc/base-mvc";
@@ -7,10 +10,12 @@ import naturalSort from "utils/natural-sort";
import _l from "utils/localization";
import RuleCollectionBuilder from "components/RuleCollectionBuilder.vue";
import Vue from "vue";
import { getGalaxyInstance } from "app";
import "ui/hoverhighlight";
var logNamespace = "collections";
var $ = jQuery;
/*==============================================================================
TODO:
@@ -1016,6 +1021,7 @@ var ListCollectionCreator = Backbone.View.extend(BASE_MVC.LoggableMixin)
const collectionCreatorModalSetup = function _collectionCreatorModalSetup(options) {
const deferred = jQuery.Deferred();
let Galaxy = getGalaxyInstance();
const modal = Galaxy.modal || new UI_MODAL.View();
const creatorOptions = _.defaults(options || {}, {
@@ -179,6 +179,14 @@ var View = Backbone.View.extend({
onclick: function() {
self._submit("datatype", form);
}
}),
submit_datatype_detect: new Ui.Button({
tooltip: _l("Detect the datatype and change it."),
title: _l("Detect datatype"),
icon: "fa-undo",
onclick: function() {
self._submit("datatype_detect", form);
}
})
}
});
@@ -83,7 +83,7 @@ var DatasetListItemEdit = _super.extend(
}
// disable if still uploading or new
} else if (_.contains([STATES.UPLOAD, STATES.NEW], this.model.get("state"))) {
} else if ([STATES.UPLOAD, STATES.NEW].includes(this.model.get("state"))) {
editBtnData.disabled = true;
editBtnData.title = _l("This dataset is not yet editable");
}
@@ -119,10 +119,13 @@ var DatasetListItemEdit = _super.extend(
var state = this.model.get("state");
if (!this.model.isDeletedOrPurged() && _.contains([STATES.OK, STATES.FAILED_METADATA], state)) {
if (!this.model.isDeletedOrPurged()) {
//Enable tagging+annotation regardless of job state (see issue #6330)
this._renderTags($details);
this._renderAnnotation($details);
this._makeDbkeyEditLink($details);
if ([STATES.OK, STATES.FAILED_METADATA].includes(state)) {
this._makeDbkeyEditLink($details);
}
}
this._setUpBehaviors($details);
@@ -20,7 +20,7 @@ var _super = LIST_ITEM.ListItemView;
/** @class Read only list view for either LDDAs, HDAs, or HDADCEs.
* Roughly, any DatasetInstance (and not a raw Dataset).
*/
var DatasetListItemView = _super.extend(
export var DatasetListItemView = _super.extend(
/** @lends DatasetListItemView.prototype */ {
_logNamespace: logNamespace,
+8 -2
View File
@@ -1,3 +1,8 @@
import _ from "underscore";
import $ from "jquery";
import Backbone from "backbone";
import { getGalaxyInstance } from "app";
/* This class maps the form dom to an api compatible javascript dictionary. */
export var Manager = Backbone.Model.extend({
initialize: function(app) {
@@ -12,7 +17,7 @@ export var Manager = Backbone.Model.extend({
var id = $(this).attr("id");
var field = self.app.field_list[id];
if (field) {
sum += `${id}:${JSON.stringify(field.value && field.value())}:${field.collapsed};`;
sum += `${id}:${JSON.stringify(field.value && field.value())}:${field.collapsed}:${field.connected};`;
}
});
return sum;
@@ -84,7 +89,7 @@ export var Manager = Backbone.Model.extend({
if (field && field.value) {
value = field.value();
if (input.ignore === undefined || input.ignore != value) {
if (field.collapsed && field.connected) {
if (field.connected) {
value = { __class__: "ConnectedValue" };
} else if (field.collapsed && input.collapsible_value) {
value = input.collapsible_value;
@@ -209,6 +214,7 @@ export var visitInputs = (inputs, callback, prefix, context) => {
}
});
for (var key in inputs) {
let Galaxy = getGalaxyInstance();
var node = inputs[key];
node.name = node.name || key;
var name = prefix ? `${prefix}|${node.name}` : node.name;
+14 -17
View File
@@ -12,6 +12,8 @@ export default Backbone.View.extend({
new Backbone.Model({
text_enable: this.app_options.text_enable || "Enable",
text_disable: this.app_options.text_disable || "Disable",
text_connected_enable: this.app_options.text_connected_enable || "Add connection to module",
text_connected_disable: this.app_options.text_connected_disable || "Remove connection from module",
cls_enable: this.app_options.cls_enable || "fa fa-caret-square-o-down",
cls_disable: this.app_options.cls_disable || "fa fa-caret-square-o-up",
always_refresh: this.app_options.always_refresh
@@ -38,8 +40,7 @@ export default Backbone.View.extend({
// decide wether to expand or collapse fields
var collapsible_value = this.model.get("collapsible_value");
var value = JSON.stringify(this.model.get("value"));
var connected = value == JSON.stringify({ __class__: "ConnectedValue" });
this.field.connected = connected;
this.field.connected = value == JSON.stringify({ __class__: "ConnectedValue" });
this.field.collapsed =
this.field.connected ||
(collapsible_value !== undefined &&
@@ -50,22 +51,14 @@ export default Backbone.View.extend({
// add click handler
var self = this;
this.$collapsible_icon.on("click", () => {
if (self.field.connected) {
return;
}
self.field.collapsed = !self.field.collapsed;
if (self.field.collapsed) {
self.field.connected = false;
}
self.field.connected = false;
app.trigger && app.trigger("change");
self.render();
});
this.$connected_icon.on("click", () => {
self.field.connected = !self.field.connected;
self.field.collapsed = self.field.connected;
if (!self.field.connected) {
this.model.set("value", null);
}
app.trigger && app.trigger("change");
self.render();
});
@@ -130,11 +123,10 @@ export default Backbone.View.extend({
// render collapsible options
const connected = this.field.connected;
const collapsible =
!connected &&
!this.field.collapsible_disabled &&
!this.model.get("disabled") &&
this.model.get("collapsible_value") !== undefined;
const connectable = this.model.get("connectable");
const connectable = collapsible && this.model.get("connectable");
if (collapsible || connectable) {
var collapsible_state = this.field.collapsed ? "enable" : "disable";
this.$title_text.hide();
@@ -145,6 +137,7 @@ export default Backbone.View.extend({
.removeClass()
.addClass("icon ui-form-collapsible-icon")
.addClass(this.model.get(`cls_${collapsible_state}`))
.tooltip("dispose")
.attr("data-original-title", this.model.get(`text_${collapsible_state}`))
.tooltip({ placement: "bottom" })
.show();
@@ -152,12 +145,16 @@ export default Backbone.View.extend({
this.$collapsible_icon.hide();
}
if (connectable) {
const connectedIconStyle = this.field.connected ? "fa fa-times" : "fa fa-arrows-h";
const connected_icon_style = this.field.connected ? "fa fa-times" : "fa fa-arrows-h";
const connected_icon_margin = !connected && collapsible ? "ml-1" : "";
const connected_collapsible_state = this.field.connected ? "disable" : "enable";
this.$connected_icon
.removeClass()
.addClass("icon ui-form-connected-icon")
.addClass(connectedIconStyle)
.attr("data-original-title", this.model.get(`text_${collapsible_state}`))
.addClass(connected_icon_style)
.addClass(connected_icon_margin)
.tooltip("dispose")
.attr("data-original-title", this.model.get(`text_connected_${connected_collapsible_state}`))
.tooltip({ placement: "bottom" })
.show();
} else {
@@ -186,7 +183,7 @@ export default Backbone.View.extend({
.addClass("ui-form-collapsible")
.append($("<i/>").addClass("ui-form-collapsible-icon"))
.append($("<i/>").addClass("ui-form-connected-icon"))
.append($("<span/>").addClass("ui-form-collapsible-text"))
.append($("<span/>").addClass("ui-form-collapsible-text ml-1"))
)
.append($("<span/>").addClass("ui-form-title-text"))
)
@@ -1,5 +1,9 @@
import _l from "utils/localization";
/** This class creates a ui component which enables the dynamic creation of portlets */
import _l from "utils/localization";
import _ from "underscore";
import $ from "jquery";
import Backbone from "backbone";
import { getGalaxyInstance } from "app";
import Utils from "utils/utils";
import Portlet from "mvc/ui/ui-portlet";
import Ui from "mvc/ui/ui-misc";
@@ -38,6 +42,7 @@ export var View = Backbone.View.extend({
/** Add new repeat block */
add: function(options) {
let Galaxy = getGalaxyInstance();
if (!options.id || this.list[options.id]) {
Galaxy.emit.debug("form-repeat::add()", "Duplicate or invalid repeat block id.");
return;
@@ -70,6 +75,7 @@ export var View = Backbone.View.extend({
/** Delete repeat block */
del: function(id) {
let Galaxy = getGalaxyInstance();
if (!this.list[id]) {
Galaxy.emit.debug("form-repeat::del()", "Invalid repeat block id.");
return;
+5 -4
View File
@@ -1,13 +1,14 @@
import Backbone from "backbone";
import _ from "underscore";
import $ from "jquery";
import { getGalaxyInstance } from "app";
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";
import { init_refresh_on_change } from "onload/globalInits/init_refresh_on_change";
/* global $ */
// This is necessary so that, when nested arrays are used in ajax/post/get methods, square brackets ('[]') are
// not appended to the identifier of a nested array.
$.ajaxSettings.traditional = true;
@@ -23,6 +24,8 @@ export default Backbone.View.extend({
this.title = grid_config.title;
this.active_tab = grid_config.active_tab;
var self = this;
// Why is this a global?
window.add_tag_to_grid_filter = (tag_name, tag_value) => {
// Put tag name and value together.
var tag = tag_name + (tag_value !== undefined && tag_value !== "" ? `:${tag_value}` : "");
@@ -118,9 +121,7 @@ export default Backbone.View.extend({
// attach global event handler
// TODO: redundant (the onload/standard page handlers do this) - but needed because these are constructed after page ready
if (window.init_refresh_on_change) {
window.init_refresh_on_change();
}
init_refresh_on_change();
},
// Initialize grid controls
@@ -1,7 +1,11 @@
import { getGalaxyInstance } from "app";
import MODAL from "mvc/ui/ui-modal";
import ERROR_MODAL from "mvc/ui/error-modal";
import _l from "utils/localization";
import _ from "underscore";
import jQuery from "jquery";
import { getGalaxyInstance } from "app";
var $ = jQuery;
//==============================================================================
/**
+3 -4
View File
@@ -1,7 +1,7 @@
import _ from "underscore";
import STATES from "mvc/dataset/states";
import DC_LI from "mvc/collection/collection-li";
import DC_VIEW from "mvc/collection/collection-view";
import BASE_MVC from "mvc/base-mvc";
import HISTORY_ITEM_LI from "mvc/history/history-item-li";
import _l from "utils/localization";
@@ -17,7 +17,8 @@ var HDCAListItemView = _super.extend(
_setUpListeners: function() {
_super.prototype._setUpListeners.call(this);
var renderListen = (model, options) => {
this.render();
// We want this to swap immediately without extra animations.
this.render(0);
};
if (this.model.jobStatesSummary) {
this.listenTo(this.model.jobStatesSummary, "change", renderListen);
@@ -64,9 +65,7 @@ var HDCAListItemView = _super.extend(
stateDescription: function() {
var collection = this.model;
var elementCount = collection.get("element_count");
var jobStateSource = collection.get("job_source_type");
var collectionType = this.model.get("collection_type");
var collectionTypeDescription = DC_VIEW.collectionTypeDescription(collection);
var simpleDescription = DC_VIEW.collectionDescription(collection);
var jobStatesSummary = collection.jobStatesSummary;
@@ -1,5 +1,9 @@
import Utils from "utils/utils";
function _templateNametag(tag) {
return `<span class="badge badge-primary badge-tags">${_.escape(tag.slice(5))}</span>`;
return `<span style="${Utils.generateTagStyle(tag.slice(5))}" class="badge badge-primary badge-tags">${_.escape(
tag.slice(5)
)}</span>`;
}
function nametagTemplate(historyItem) {
@@ -1,5 +1,5 @@
import $ from "jquery";
import _ from "underscore";
import jQuery from "jquery";
import { getGalaxyInstance } from "app";
import HISTORY_VIEW from "mvc/history/history-view";
import HDA_MODEL from "mvc/history/hda-model";
@@ -15,6 +15,8 @@ import BASE_MVC from "mvc/base-mvc";
import _l from "utils/localization";
import "ui/editable-text";
var $ = jQuery;
/* =============================================================================
TODO:
@@ -98,7 +98,25 @@ var menu = [
}
}
},
{
html: _l("Make Data Private"),
anon: true,
func: function() {
if (
Galaxy &&
Galaxy.currHistoryPanel &&
confirm(
_l(
"This will make all the data in this history private (excluding library datasets), and will set permissions such that all new data is created as private. Any datasets within that are currently shared will need to be re-shared or published. Are you sure you want to do this?"
)
)
) {
$.post(`${Galaxy.root}history/make_private`, { history_id: Galaxy.currHistoryPanel.model.id }, () => {
Galaxy.currHistoryPanel.loadCurrentHistory();
});
}
}
},
{
html: _l("Dataset Actions"),
header: true,
@@ -1,6 +1,7 @@
import _ from "underscore";
import $ from "jquery";
import Backbone from "backbone";
import { getGalaxyInstance } from "app";
import { getAppRoot } from "onload/loadConfig";
import mod_toastr from "libs/toastr";
import mod_library_model from "mvc/library/library-model";
@@ -25,6 +26,7 @@ var FolderView = Backbone.View.extend({
},
fetchFolder: function(options) {
let Galaxy = getGalaxyInstance();
this.options = _.extend(this.options, options);
this.model = new mod_library_model.FolderAsModel({
id: this.options.id
@@ -55,6 +57,7 @@ var FolderView = Backbone.View.extend({
},
showPermissions: function(options) {
let Galaxy = getGalaxyInstance();
this.options = _.extend(this.options, options);
$(".tooltip").remove();
@@ -1,6 +1,10 @@
import $ from "jquery";
import _ from "underscore";
import Backbone from "backbone";
import mod_toastr from "libs/toastr";
import mod_library_model from "mvc/library/library-model";
import mod_library_folderrow_view from "mvc/library/library-folderrow-view";
import { getGalaxyInstance } from "app";
var FolderListView = Backbone.View.extend({
el: "#folder_items_element",
@@ -55,8 +59,7 @@ var FolderListView = Backbone.View.extend({
this.fetchFolder();
},
fetchFolder: function(options) {
var options = options || {};
fetchFolder: function(options = {}) {
this.options.include_deleted = options.include_deleted;
var self = this;
@@ -74,6 +77,7 @@ var FolderListView = Backbone.View.extend({
self.render();
},
error: function(model, response) {
let Galaxy = getGalaxyInstance();
if (typeof response.responseJSON !== "undefined") {
mod_toastr.error(`${response.responseJSON.err_msg} Click this to go back.`, "", {
onclick: function() {
@@ -137,6 +141,7 @@ var FolderListView = Backbone.View.extend({
},
paginate: function(options) {
let Galaxy = getGalaxyInstance();
this.options = _.extend(this.options, options);
if (this.options.show_page === null || this.options.show_page < 1) {
@@ -176,6 +181,7 @@ var FolderListView = Backbone.View.extend({
* be added to the view's collection.
*/
addAll: function(models) {
let Galaxy = getGalaxyInstance();
_.each(models, model => {
Galaxy.libraries.folderListView.collection.add(model, {
current_sort_order: false
@@ -192,6 +198,7 @@ var FolderListView = Backbone.View.extend({
* and that event will be bound on all subviews.
*/
postRender: function() {
let Galaxy = getGalaxyInstance();
var fetched_metadata = this.folderContainer.attributes.metadata;
fetched_metadata.contains_file_or_folder =
typeof this.collection.findWhere({ type: "file" }) !== "undefined" ||
@@ -84,7 +84,8 @@ var FolderToolbarView = Backbone.View.extend({
id: this.options.id,
is_admin: false,
is_anonym: true,
multiple_add_dataset_options: false
multiple_add_dataset_options: false,
Galaxy: Galaxy
};
if (Galaxy.user) {
template_defaults.is_admin = Galaxy.user.isAdmin();
@@ -1,5 +1,9 @@
// dependencies
import _ from "underscore";
import $ from "jquery";
import Backbone from "backbone";
import mod_toastr from "libs/toastr";
import { getGalaxyInstance } from "app";
// galaxy library row view
var LibraryRowView = Backbone.View.extend({
events: {
@@ -28,6 +32,7 @@ var LibraryRowView = Backbone.View.extend({
render: function(library) {
if (typeof library === "undefined") {
let Galaxy = getGalaxyInstance();
library = Galaxy.libraries.libraryListView.collection.get(this.$el.data("id"));
console.log(library);
}
@@ -117,6 +122,7 @@ var LibraryRowView = Backbone.View.extend({
},
save_library_modification: function() {
let Galaxy = getGalaxyInstance();
var library = Galaxy.libraries.libraryListView.collection.get(this.$el.data("id"));
var is_changed = false;
@@ -168,6 +174,7 @@ var LibraryRowView = Backbone.View.extend({
},
delete_library: function() {
let Galaxy = getGalaxyInstance();
var library = Galaxy.libraries.libraryListView.collection.get(this.$el.data("id"));
var row_view = this;
// mark the library deleted
@@ -197,6 +204,7 @@ var LibraryRowView = Backbone.View.extend({
},
undelete_library: function() {
let Galaxy = getGalaxyInstance();
var library = Galaxy.libraries.libraryListView.collection.get(this.$el.data("id"));
var row_view = this;
@@ -1,4 +1,5 @@
import _ from "underscore";
import $ from "jquery";
import Backbone from "backbone";
import _l from "utils/localization";
import mod_toastr from "libs/toastr";
+6 -4
View File
@@ -1,5 +1,7 @@
import $ from "jquery";
import Backbone from "backbone";
import _ from "underscore";
import BASE_MVC from "mvc/base-mvc";
import _l from "utils/localization";
var logNamespace = "list";
//==============================================================================
@@ -142,7 +144,7 @@ var ExpandableView = Backbone.View.extend(BASE_MVC.LoggableMixin).extend({
if (!this.model.hasDetails()) {
return this.model.fetch();
}
return jQuery.when();
return $.when();
},
/** Inner fn called when expand (public) has fetched the details */
@@ -182,7 +184,7 @@ var ExpandableView = Backbone.View.extend(BASE_MVC.LoggableMixin).extend({
*
* Designed as a base class for history panel contents - but usable elsewhere (I hope).
*/
var ListItemView = ExpandableView.extend(
export var ListItemView = ExpandableView.extend(
BASE_MVC.mixin(BASE_MVC.SelectableViewMixin, BASE_MVC.DraggableViewMixin, {
tagName: "div",
className: "list-item",
@@ -380,7 +382,7 @@ ListItemView.prototype.templates = (() => {
*
* _renderDetails does the work of creating this.details: a sub-view that shows the nested list
*/
var FoldoutListItemView = ListItemView.extend({
export var FoldoutListItemView = ListItemView.extend({
/** If 'foldout': show the sub-panel inside the expanded item
* If 'drilldown': only fire events and handle by pub-sub
* (allow the panel containing this item to attach it, hide itself, etc.)
@@ -1,3 +1,6 @@
import $ from "jquery";
import Backbone from "backbone";
import _ from "underscore";
import LIST_ITEM from "mvc/list/list-item";
import LoadingIndicator from "ui/loading-indicator";
import BASE_MVC from "mvc/base-mvc";
@@ -183,14 +183,12 @@ var View = Backbone.View.extend({
input.is_workflow =
(data_ref.step_linked && !self._isDataStep(data_ref.step_linked)) || input.wp_linked;
}
if (
is_data_input ||
(input.value && input.value.__class__ == "RuntimeValue" && !input.step_linked)
) {
if (is_data_input ||
(input.value && input.value.__class__ == "RuntimeValue" && !input.step_linked)) {
step.collapsed = false;
}
if (is_runtime_value) {
input.value = input.default_value;
input.value = null;
}
input.flavor = "workflow";
if (!is_runtime_value && !is_data_input && input.type !== "hidden" && !input.wp_linked) {
+6 -5
View File
@@ -143,13 +143,14 @@ var View = Backbone.View.extend({
// remap feature
if (options.job_id && options.job_remap) {
var label, help;
if (options.job_remap === "job_produced_collection_elements") {
var label = "Replace elements in collection ?";
var help =
label = "Replace elements in collection ?";
help =
"The previous run of this tool failed. Use this option to replace the failed element(s) in the dataset collection that were produced during the previous tool run.";
} else {
var label = "Resume dependencies from this job ?";
var help =
label = "Resume dependencies from this job ?";
help =
"The previous run of this tool failed and other tools were waiting for it to finish successfully. Use this option to resume those tools using the new output(s) of this tool run.";
}
options.inputs.push({
@@ -353,7 +354,7 @@ var View = Backbone.View.extend({
if (list.length > 0) {
blurb += `<p>${title}:</p>`;
for (let item of list) {
let rowString = max > 0 ? `${item.hid}: ${item.name}` : "...";
let rowString = max > 0 ? `${item.hid}: ${_.escape(item.name)}` : "...";
blurb += `<p class="messagerow">
<b>${rowString}</b>
</p>`;
@@ -1,8 +1,10 @@
// Provides support for interacting with the GenomeSpace File Browser popup dialogue
import { getGalaxyInstance } from "app";
// tool form templates
export default {
openFileBrowser: function(options) {
let Galaxy = getGalaxyInstance();
var GS_UI_URL = Galaxy.config.genomespace_ui_url;
var GS_UPLOAD_URL = `${GS_UI_URL}upload/loadUrlToGenomespace.html?getLocation=true`;
+1 -1
View File
@@ -40,7 +40,7 @@ var FrameView = Backbone.View.extend({
render: function() {
var self = this;
var options = this.model.attributes;
this.$title.html(options.title || "");
this.$title.html(_.escape(options.title) || "");
this.$header.find(".f-icon-left").remove();
_.each(options.menu, option => {
var $option = $("<div/>")
@@ -1,7 +1,12 @@
import _ from "underscore";
import $ from "jquery";
import Backbone from "backbone";
import _l from "utils/localization";
import Utils from "utils/utils";
import Ui from "mvc/ui/ui-misc";
import Select from "mvc/ui/ui-select-default";
import { getGalaxyInstance } from "app";
/** Batch mode variations */
var Batch = { DISABLED: "disabled", ENABLED: "enabled", LINKED: "linked" };
@@ -169,20 +174,20 @@ var View = Backbone.View.extend({
};
// add drag-drop event handlers
this.$el
.on("dragenter", function(e) {
this.lastenter = e.target;
self.$el.addClass("ui-dragover");
})
.on("dragover", e => {
e.preventDefault();
})
.on("dragleave", function(e) {
this.lastenter === e.target && self.$el.removeClass("ui-dragover");
})
.on("drop", e => {
self._handleDrop(e);
});
let element = this.$el.get(0);
element.addEventListener("dragenter", e => {
this.lastenter = e.target;
self.$el.addClass("ui-dragover");
});
element.addEventListener("dragover", e => {
e.preventDefault();
});
element.addEventListener("dragleave", e => {
this.lastenter === e.target && self.$el.removeClass("ui-dragover");
});
element.addEventListener("drop", e => {
self._handleDrop(e);
});
// track current history elements
this.history = {};
@@ -229,6 +234,7 @@ var View = Backbone.View.extend({
/** Return the currently selected dataset values */
value: function(new_value) {
let Galaxy = getGalaxyInstance();
new_value !== undefined && this.model.set("value", new_value);
var current = this.model.get("current");
if (this.config[current]) {
@@ -277,6 +283,7 @@ var View = Backbone.View.extend({
/** Change of type */
_changeType: function() {
let Galaxy = getGalaxyInstance();
var self = this;
// identify selector type identifier i.e. [ flavor ]_[ type ]_[ multiple ]
@@ -412,7 +419,7 @@ var View = Backbone.View.extend({
var current = this.model.get("current");
var config = this.config[current];
var field = this.fields[current];
var drop_data = JSON.parse(ev.originalEvent.dataTransfer.getData("text"))[0];
var drop_data = JSON.parse(ev.dataTransfer.getData("text"))[0];
var new_id = drop_data.id;
var new_src = drop_data.history_content_type == "dataset_collection" ? "hdca" : "hda";
var new_value = { id: new_id, src: new_src };
@@ -208,9 +208,9 @@ var View = Backbone.View.extend({
${_.reduce(
filteredTags.slice(0, 5),
(memo, tag) => {
return `${memo}&nbsp;<div class="badge badge-primary badge-tags">${_.escape(
tag
)}</div>`;
return `${memo}&nbsp;<div style="${Utils.generateTagStyle(
tag.slice(5)
)}" class="badge badge-primary badge-tags">${_.escape(tag)}</div>`;
},
""
)}
@@ -1,5 +1,10 @@
import _l from "utils/localization";
/** Renders contents of the collection uploader */
import _l from "utils/localization";
import _ from "underscore";
import $ from "jquery";
import Backbone from "backbone";
import { getGalaxyInstance } from "app";
import Utils from "utils/utils";
import UploadModel from "mvc/upload/upload-model";
import UploadRow from "mvc/upload/collection/collection-row";
@@ -10,6 +15,7 @@ import Select from "mvc/ui/ui-select";
import Ui from "mvc/ui/ui-misc";
import LIST_COLLECTION_CREATOR from "mvc/collection/list-collection-creator";
import "utils/uploadbox";
export default Backbone.View.extend({
// current upload size in bytes
upload_size: 0,
@@ -246,6 +252,7 @@ export default Backbone.View.extend({
this.counter.announce--;
this.counter.success++;
this._updateScreen();
let Galaxy = getGalaxyInstance();
Galaxy.currHistoryPanel.refreshContents();
},
@@ -273,6 +280,7 @@ export default Backbone.View.extend({
},
_eventBuild: function() {
let Galaxy = getGalaxyInstance();
var allHids = [];
_.forEach(this.collection.models, upload => {
allHids.push.apply(allHids, upload.get("hids"));
@@ -38,10 +38,10 @@ export default Backbone.View.extend({
// build upload functions
this.uploadinput = this.$el.uploadinput({
ondragover: function() {
self.model.get("enabled") && self.$el.addClass("warning");
self.model.get("enabled") && self.$el.addClass("alert-success");
},
ondragleave: function() {
self.$el.removeClass("warning");
self.$el.removeClass("alert-success");
},
onchange: function(files) {
if (self.model.get("status") != "running" && files && files.length > 0) {
@@ -1,11 +1,16 @@
import _l from "utils/localization";
/** Renders contents of the composite uploader */
import Utils from "utils/utils";
import _l from "utils/localization";
import _ from "underscore";
import $ from "jquery";
import Backbone from "backbone";
import { getGalaxyInstance } from "app";
// import Utils from "utils/utils";
import UploadModel from "mvc/upload/upload-model";
import UploadRow from "mvc/upload/composite/composite-row";
import UploadExtension from "mvc/upload/upload-extension";
import Select from "mvc/ui/ui-select";
import Ui from "mvc/ui/ui-misc";
export default Backbone.View.extend({
collection: new UploadModel.Collection(),
initialize: function(app) {
@@ -155,6 +160,7 @@ export default Backbone.View.extend({
/** Refresh success state */
_eventSuccess: function(message) {
let Galaxy = getGalaxyInstance();
this.collection.each(it => {
it.set("status", "success");
});
@@ -1,5 +1,7 @@
import _l from "utils/localization";
/** Upload app contains the upload progress button and upload modal, compiles model data for API request **/
import _l from "utils/localization";
import Backbone from "backbone";
import { getGalaxyInstance } from "app";
import Utils from "utils/utils";
import Modal from "mvc/ui/ui-modal";
import Tabs from "mvc/ui/ui-tabs";
@@ -9,7 +11,6 @@ import UploadViewDefault from "mvc/upload/default/default-view";
import UploadViewComposite from "mvc/upload/composite/composite-view";
import UploadViewCollection from "mvc/upload/collection/collection-view";
import UploadViewRuleBased from "mvc/upload/collection/rules-input-view";
import { getGalaxyInstance } from "app";
export default Backbone.View.extend({
options: {
@@ -1,4 +1,3 @@
import _ from "underscore";
import Backbone from "backbone";
import { getAppRoot } from "onload/loadConfig";
import baseMVC from "mvc/base-mvc";
@@ -48,6 +48,40 @@ var Model = Backbone.Model.extend({
submit_title: "Save permissions",
redirect: "user"
},
make_data_private: {
title: _l("Make all data private"),
description: _l("Click here to make all data private."),
icon: "fa-lock",
onclick: function() {
if (
confirm(
_l(
"WARNING: This will make all datasets (excluding library datasets) for which you have " +
"'management' permissions, in all of your histories " +
"private, and will set permissions such that all " +
"of your new data in these histories is created as private. Any " +
"datasets within that are currently shared will need " +
"to be re-shared or published. Are you sure you " +
"want to do this?"
)
)
) {
$.post(`${Galaxy.root}history/make_private`, { all_histories: true }, () => {
Galaxy.modal.show({
title: _l("Datasets are now private"),
body: `All of your histories and datsets have been made private. If you'd like to make all *future* histories private please use the <a href="${
Galaxy.root
}user/permissions">User Permissions</a> interface.`,
buttons: {
Close: function() {
Galaxy.modal.hide();
}
}
});
});
}
}
},
api_key: {
title: _l("Manage API key"),
description: _l("Access your current API key or create a new one."),
@@ -130,6 +164,7 @@ var View = Backbone.View.extend({
}
self._addLink("custom_builds");
self._addLink("permissions");
self._addLink("make_data_private");
self._addLink("api_key");
if (config.has_user_tool_filters) {
self._addLink("toolbox_filters");
@@ -14,11 +14,8 @@ import Ui from "mvc/ui/ui-misc";
import async_save_text from "utils/async-save-text";
import "ui/editable-text";
// TODO: make show_message and the other utility functions importable/used
// everywhere, instead of this global model
/* global show_message */
/* global hide_modal */
/* global make_popupmenu */
import { hide_modal, show_message, show_modal } from "layout/modal";
import { make_popupmenu } from "ui/popupmenu";
// TODO; tie into Galaxy state?
window.workflow_globals = window.workflow_globals || {};
@@ -78,7 +75,7 @@ export default Backbone.View.extend({
window.onbeforeunload = undefined;
window.document.location = self.urls.workflow_index;
};
window.show_modal(
show_modal(
"Close workflow editor",
"There are unsaved changes to your workflow which will be lost.",
{
@@ -132,7 +129,7 @@ export default Backbone.View.extend({
self.showWorkflowParameters();
self.build_version_select();
if (data.errors) {
window.show_modal("Saving workflow", body, {
show_modal("Saving workflow", body, {
Ok: hide_modal
});
} else {
@@ -143,7 +140,7 @@ export default Backbone.View.extend({
}
},
error: function(response) {
window.show_modal("Saving workflow failed.", response.err_msg, { Ok: hide_modal });
show_modal("Saving workflow failed.", response.err_msg, { Ok: hide_modal });
}
});
};
@@ -335,7 +332,6 @@ export default Backbone.View.extend({
self.workflow.fit_canvas_to_nodes();
self.scroll_to_nodes();
self.canvas_manager.draw_overview();
// make_popupmenu($("#workflow-versions-switch"), self.get_workflow_versions());
self.build_version_select();
// Determine if any parameters were 'upgraded' and provide message
@@ -355,7 +351,7 @@ export default Backbone.View.extend({
}
});
if (upgrade_message) {
window.show_modal(
show_modal(
"Issues loading this workflow",
`Please review the following issues, possibly resulting from tool upgrades or changes.<p><ul>${upgrade_message}</ul></p>`,
{ Continue: hide_modal }
@@ -366,7 +362,7 @@ export default Backbone.View.extend({
self.showWorkflowParameters();
},
error: function(response) {
window.show_modal("Loading workflow failed.", response.err_msg, {
show_modal("Loading workflow failed.", response.err_msg, {
Ok: function(response) {
window.onbeforeunload = undefined;
window.document.location = workflow_index;
@@ -381,7 +377,7 @@ export default Backbone.View.extend({
// Load workflow definition
this.load_workflow(self.options.id, self.options.version);
if (window.make_popupmenu) {
if (make_popupmenu) {
make_popupmenu($("#workflow-options-button"), {
Save: save_current_workflow,
"Save As": workflow_save_as,
@@ -406,7 +402,7 @@ export default Backbone.View.extend({
'<form><label style="display:inline-block; width: 100%;">Save as name: </label><input type="text" id="workflow_rename" style="width: 80%;" autofocus/>' +
'<br><label style="display:inline-block; width: 100%;">Annotation: </label><input type="text" id="wf_annotation" style="width: 80%;" /></form>'
);
window.show_modal("Save As a New Workflow", body, {
show_modal("Save As a New Workflow", body, {
OK: function() {
var rename_name =
$("#workflow_rename").val().length > 0
@@ -573,7 +569,7 @@ export default Backbone.View.extend({
copy_into_workflow: function(workflowId) {
// Load workflow definition
var self = this;
this._workflowLoadAjax(workflowId, None, {
this._workflowLoadAjax(workflowId, null, {
success: function(data) {
self.workflow.from_simple(data, false);
// Determine if any parameters were 'upgraded' and provide message
@@ -586,7 +582,7 @@ export default Backbone.View.extend({
upgrade_message += "</ul></li>";
});
if (upgrade_message) {
window.show_modal(
show_modal(
"Subworkflow embedded with changes",
`Problems were encountered loading this workflow (possibly a result of tool upgrades). Please review the following parameters and then save.<ul>${upgrade_message}</ul>`,
{ Continue: hide_modal }
@@ -0,0 +1,17 @@
import { setGalaxyInstance } from "app";
/**
* Most of the time, all we do is launch a GalaxyApp with the passed configs
*
* @param {string} label
* @returns {function} Returns a function that accepts the application
* configuration and returns a galaxy instance.
*/
export const defaultAppFactory = (config, label = "Galaxy") => {
return setGalaxyInstance(GalaxyApp => {
let { options, bootstrapped } = config;
let app = new GalaxyApp(options, bootstrapped);
app.debug(`${label} app`);
return app;
});
};
@@ -0,0 +1,10 @@
import { getRootFromIndexLink } from "./getRootFromIndexLink";
export const defaultConfigs = {
options: {
root: getRootFromIndexLink()
},
bootstrapped: {},
form_input_auto_focus: false,
sentry: {}
};
@@ -0,0 +1,11 @@
// Finds <link rel="index"> in head element and pulls root url fragment from
// there That should probably be a <base> tag instead since that's how
// they're using <link rel="index" />
import { serverPath } from "utils/serverPath";
export function getRootFromIndexLink(defaultRoot = "/") {
let links = document.getElementsByTagName("link");
let indexLink = Array.from(links).find(link => link.rel == "index");
return indexLink && indexLink.href ? serverPath(indexLink.href) : defaultRoot;
}
@@ -0,0 +1,37 @@
import $ from "jquery";
// If galaxy_main frame does not exist and link targets galaxy_main,
// add use_panels=True and set target to self.
export function adjustIframeLinks() {
console.log("adjustIframeLinks");
$("a").click(function() {
var anchor = $(this);
var galaxy_main_exists = window.parent.frames && window.parent.frames.galaxy_main;
if (anchor.attr("target") == "galaxy_main" && !galaxy_main_exists) {
var href = anchor.attr("href");
if (href.indexOf("?") == -1) {
href += "?";
} else {
href += "&";
}
href += "use_panels=True";
anchor.attr("href", href);
anchor.attr("target", "_self");
}
return anchor;
});
}
/**
* Adds an identifying class at the document root so that we can tell when we're
* inside an iframe from inside the css, removing the need for all those
* pointless conditional checks to apply iframe-only styles.
*/
export function addIframeClass() {
console.log("addIframeClass");
if (window.self !== window.top) {
console.log(">>> We are in an iframe");
document.body.classList.add("framed");
}
}
@@ -0,0 +1,38 @@
/**
* This is a collection of miscellaneous javascripts that that were previously
* printed to the page by python templates or just floating around the onload.js
* file.
*
* One day soon, these will all disappear because any functionality they provide
* will more properly be run inside a lifecycle handler inside of a component.
*/
import { prependInitialization } from "../initQueue";
// specific initialization functions
import { monitorInit } from "utils/installMonitor";
import { initSentry } from "./initSentry";
import { initTooltips } from "./initTooltips";
import { adjustIframeLinks, addIframeClass } from "./iframesAreTerrible";
import { init_refresh_on_change } from "./init_refresh_on_change";
import { onloadWebhooks } from "./onloadWebhooks";
import { replace_big_select_inputs } from "./replace_big_select_inputs";
import { make_popup_menus } from "ui/popupmenu";
import { initTours } from "./initTours";
import { initModals } from "./initModals";
export function globalInits() {
prependInitialization(
monitorInit,
initSentry,
addIframeClass,
adjustIframeLinks,
initModals,
initTooltips,
init_refresh_on_change,
() => replace_big_select_inputs(20, 1500),
make_popup_menus,
initTours,
onloadWebhooks
);
}
@@ -0,0 +1,11 @@
// Previously ran in masthead, moved into standard init
import Modal from "mvc/ui/ui-modal";
export function initModals(galaxy) {
console.log("initModals");
if (!galaxy.modal) {
galaxy.modal = new Modal.View();
}
}
@@ -0,0 +1,19 @@
import Raven from "libs/raven";
/**
* Initializes raven/sentry, one of the few functions that should actually be
* in a global init. Transplanted here from js-app.mako
*
* @param {object} config Galaxy configuration object
*/
export const initSentry = (galaxy, config) => {
console.log("initSentry");
if (config.sentry) {
let { sentry_dsn_public, email } = config.sentry;
Raven.config(sentry_dsn_public).install();
if (email) {
Raven.setUser({ email });
}
}
};
@@ -0,0 +1,16 @@
// Initialize refresh events.
import $ from "jquery";
export function initTooltips() {
console.log("initTooltips");
// Tooltips
if ($.fn.tooltip) {
// Put tooltips below items in panel header so that they do not overlap masthead.
$(".unified-panel-header [title]").tooltip({ placement: "bottom" });
// default tooltip initialization, it will follow the data-placement tag for tooltip location
// and fallback to 'top' if not present
$("[title]").tooltip();
}
}
@@ -0,0 +1,7 @@
import Tours from "mvc/tours";
export function initTours(Galaxy) {
console.log("initTours");
Tours.activeGalaxyTourRunner();
Galaxy.giveTourWithData = Tours.giveTourWithData;
}
@@ -0,0 +1,54 @@
import $ from "jquery";
export function init_refresh_on_change() {
console.log("init_refresh_on_change");
$("select[refresh_on_change='true']")
.off("change")
.change(function() {
var select_field = $(this);
var select_val = select_field.val();
var ref_on_change_vals = select_field.attr("refresh_on_change_values");
if (ref_on_change_vals) {
ref_on_change_vals = ref_on_change_vals.split(",");
var last_selected_value = select_field.attr("last_selected_value");
if (
$.inArray(select_val, ref_on_change_vals) === -1 &&
$.inArray(last_selected_value, ref_on_change_vals) === -1
) {
return;
}
}
$(window).trigger("refresh_on_change");
$(document).trigger("convert_to_values"); // Convert autocomplete text to values
select_field.get(0).form.submit();
});
// checkboxes refresh on change
$(":checkbox[refresh_on_change='true']")
.off("click")
.click(function() {
var select_field = $(this);
var select_val = select_field.val();
var ref_on_change_vals = select_field.attr("refresh_on_change_values");
if (ref_on_change_vals) {
ref_on_change_vals = ref_on_change_vals.split(",");
var last_selected_value = select_field.attr("last_selected_value");
if (
$.inArray(select_val, ref_on_change_vals) === -1 &&
$.inArray(last_selected_value, ref_on_change_vals) === -1
) {
return;
}
}
$(window).trigger("refresh_on_change");
select_field.get(0).form.submit();
});
// Links with confirmation
$("a[confirm]")
.off("click")
.click(function() {
return confirm($(this).attr("confirm"));
});
}
@@ -0,0 +1,20 @@
import Webhooks from "mvc/webhooks";
import Utils from "utils/utils";
export function onloadWebhooks(Galaxy) {
console.log("onloadWebhooks");
if (Galaxy.config.enable_webhooks) {
Webhooks.load({
type: "onload",
callback: function(webhooks) {
webhooks.each(model => {
var webhook = model.toJSON();
if (webhook.activate && webhook.script) {
Utils.appendScriptStyle(webhook);
}
});
}
});
}
}
@@ -0,0 +1,36 @@
/**
* Panel management code transplanted from base.mako
* TODO: figure out when these functions fire
*
* @param {} panelConfig
*/
export function panelManagement(panelConfig) {
console.log("panelManagement");
console.log("(looks like it's assinging some global handlers to window to manage the left and right panel views?)");
let { left_panel, right_panel, rightPanelSelector, leftPanelSelector } = panelConfig;
if (left_panel) {
var lp = new window.bundleEntries.panels.LeftPanel({
el: leftPanelSelector
});
window.force_left_panel = function(x) {
console.log("window.force_left_panel fired");
lp.force_panel(x);
};
}
if (right_panel) {
var rp = new window.bundleEntries.panels.RightPanel({
el: rightPanelSelector
});
window.handle_minwidth_hint = function(x) {
console.log("window.handle_minwidth_hint", x);
rp.handle_minwidth_hint(x);
};
window.force_right_panel = function(x) {
console.log("window.force_right_panel", x);
rp.force_panel(x);
};
}
}
@@ -0,0 +1,57 @@
// Transplanted from "onload.js"
import $ from "jquery";
function refresh_select2(element) {
var select_elt = $(element);
var options = {
placeholder: "Click to select",
closeOnSelect: !select_elt.is("[MULTIPLE]"),
dropdownAutoWidth: true,
containerCssClass: "select2-minwidth"
};
return element.select2(options);
}
// Replace select box with a text input box + autocomplete.
export function replace_big_select_inputs(min_length, max_length, select_elts) {
console.log("replace_big_select_inputs");
// To do replace, the select2 plugin must be loaded.
if (!$.fn.select2) {
console.warn("No select 2");
return;
}
// Set default for min_length and max_length
if (min_length === undefined) {
min_length = 20;
}
if (max_length === undefined) {
max_length = 3000;
}
select_elts = select_elts || $("select");
select_elts.each(function() {
var select_elt = $(this).not("[multiple]");
// Make sure that options is within range.
var num_options = select_elt.find("option").length;
if (num_options < min_length || num_options > max_length) {
return;
}
if (select_elt.hasClass("no-autocomplete")) {
return;
}
/* Replaced jQuery.autocomplete with select2, notes:
* - multiple selects are supported
* - the original element is updated with the value, convert_to_values should not be needed
* - events are fired when updating the original element, so refresh_on_change should just work
*
* - should we still sort dbkey fields here?
*/
refresh_select2(select_elt);
});
}
+7 -219
View File
@@ -1,225 +1,13 @@
import "polyfills";
import _ from "underscore";
import $ from "jquery";
import { getAppRoot } from "onload/loadConfig";
import { getGalaxyInstance } from "app";
// Bootstrap overwrites .tooltip() method, so load it after jquery-ui
// Bootstrap overwrites .tooltip() method, load it after jquery-ui
// (which is loaded everywhere via libs/jquery.custom.js)
import "bootstrap";
import "bootstrap-tour";
// Galaxy core styles
import "scss/base.scss";
// window.bundleEntries
import "apps/extended";
// these are galaxy globals not defined in the provider (although they could be - but why encourage that?)
import Panel from "layout/panel";
window.panels = Panel;
// using extend bc there are multiple fns/objs to decorate the window with
import layout_modal from "layout/modal";
_.extend(window, layout_modal);
import async_save_text from "utils/async-save-text";
window.async_save_text = async_save_text;
import Popupmenu from "ui/popupmenu";
window.make_popupmenu = Popupmenu.make_popupmenu;
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";
import { installMonitor } from "utils/installMonitor";
// ============================================================================
// jquery on document ready
// ============================================================================
// Replace select box with a text input box + autocomplete.
function replace_big_select_inputs(min_length, max_length, select_elts) {
function refresh_select2(element) {
var select_elt = $(element);
var options = {
placeholder: "Click to select",
closeOnSelect: !select_elt.is("[MULTIPLE]"),
dropdownAutoWidth: true,
containerCssClass: "select2-minwidth"
};
return element.select2(options);
}
// To do replace, the select2 plugin must be loaded.
if (!$.fn.select2) {
console.warn("No select 2");
return;
}
// Set default for min_length and max_length
if (min_length === undefined) {
min_length = 20;
}
if (max_length === undefined) {
max_length = 3000;
}
select_elts = select_elts || $("select");
select_elts.each(function() {
var select_elt = $(this).not("[multiple]");
// Make sure that options is within range.
var num_options = select_elt.find("option").length;
if (num_options < min_length || num_options > max_length) {
return;
}
if (select_elt.hasClass("no-autocomplete")) {
return;
}
/* Replaced jQuery.autocomplete with select2, notes:
* - multiple selects are supported
* - the original element is updated with the value, convert_to_values should not be needed
* - events are fired when updating the original element, so refresh_on_change should just work
*
* - should we still sort dbkey fields here?
*/
refresh_select2(select_elt);
});
}
// Initialize refresh events.
function init_refresh_on_change() {
$("select[refresh_on_change='true']")
.off("change")
.change(function() {
var select_field = $(this);
var select_val = select_field.val();
var ref_on_change_vals = select_field.attr("refresh_on_change_values");
if (ref_on_change_vals) {
ref_on_change_vals = ref_on_change_vals.split(",");
var last_selected_value = select_field.attr("last_selected_value");
if (
$.inArray(select_val, ref_on_change_vals) === -1 &&
$.inArray(last_selected_value, ref_on_change_vals) === -1
) {
return;
}
}
$(window).trigger("refresh_on_change");
$(document).trigger("convert_to_values"); // Convert autocomplete text to values
select_field.get(0).form.submit();
});
// checkboxes refresh on change
$(":checkbox[refresh_on_change='true']")
.off("click")
.click(function() {
var select_field = $(this);
var select_val = select_field.val();
var ref_on_change_vals = select_field.attr("refresh_on_change_values");
if (ref_on_change_vals) {
ref_on_change_vals = ref_on_change_vals.split(",");
var last_selected_value = select_field.attr("last_selected_value");
if (
$.inArray(select_val, ref_on_change_vals) === -1 &&
$.inArray(last_selected_value, ref_on_change_vals) === -1
) {
return;
}
}
$(window).trigger("refresh_on_change");
select_field.get(0).form.submit();
});
// Links with confirmation
$("a[confirm]")
.off("click")
.click(function() {
return confirm($(this).attr("confirm"));
});
}
// used globally in grid-view
window.init_refresh_on_change = init_refresh_on_change;
$(document).ready(() => {
// Refresh events for form fields.
init_refresh_on_change();
// Tooltips
if ($.fn.tooltip) {
// Put tooltips below items in panel header so that they do not overlap masthead.
$(".unified-panel-header [title]").tooltip({ placement: "bottom" });
// default tooltip initialization, it will follow the data-placement tag for tooltip location
// and fallback to 'top' if not present
$("[title]").tooltip();
}
// Make popup menus.
Popupmenu.make_popup_menus();
// Replace big selects.
replace_big_select_inputs(20, 1500);
// If galaxy_main frame does not exist and link targets galaxy_main,
// add use_panels=True and set target to self.
$("a").click(function() {
var anchor = $(this);
var galaxy_main_exists = window.parent.frames && window.parent.frames.galaxy_main;
if (anchor.attr("target") == "galaxy_main" && !galaxy_main_exists) {
var href = anchor.attr("href");
if (href.indexOf("?") == -1) {
href += "?";
} else {
href += "&";
}
href += "use_panels=True";
anchor.attr("href", href);
anchor.attr("target", "_self");
}
return anchor;
});
Tours.activeGalaxyTourRunner();
let Galaxy = getGalaxyInstance();
Galaxy.giveTourWithData = Tours.giveTourWithData;
function onloadWebhooks() {
if (getAppRoot() !== undefined) {
if (Galaxy.config.enable_webhooks) {
// Load all webhooks with the type 'onload'
Webhooks.load({
type: "onload",
callback: function(webhooks) {
webhooks.each(model => {
var webhook = model.toJSON();
if (webhook.activate && webhook.script) {
Utils.appendScriptStyle(webhook);
}
});
}
});
}
} else {
setTimeout(onloadWebhooks, 100);
}
}
onloadWebhooks();
});
// add monitors for all the horrible little global variables
let watchme = [
...Object.keys(layout_modal),
"panels",
"async_save_text",
"make_popupmenu",
"make_popup_menus",
"init_tag_click_function"
];
watchme.forEach(prop => installMonitor(prop));
// Module exports appear as objects on window.config in the browser
export { standardInit } from "./standardInit";
export { initializations$, addInitialization, prependInitialization, clearInitQueue } from "./initQueue";
export { config$, set as setConfig, get as getConfig, getAppRoot } from "./loadConfig";
export { getRootFromIndexLink } from "./getRootFromIndexLink";
+35
View File
@@ -0,0 +1,35 @@
/**
* There's a bunch of little init scripts running around. They will become part
* of the mounting logic for components one day, but for now I want them out of
* the python templates.
*
* You can add more init functions using addInitialization either from a python
* template, or from anywhere else in the code if you import the loadConfig
* file.
*
* In a python template (the config object is attached to window in the browser):
* config.addInitialiation(function (galaxyInstance, config) { // your init } )
* In any webpack script:
* import { addInitialization } from "onload";
* addInitialization((galaxyInstance, config) => { // do things });
*/
import { BehaviorSubject } from "rxjs";
import { filter } from "rxjs/operators";
const queue = (window.top._initQueue = new BehaviorSubject([]));
// don't emit unless there are things to initialize
export const initializations$ = queue.pipe(filter(list => list.length > 0));
export const addInitialization = (...fns) => {
let nextInits = [...queue.getValue(), ...fns];
queue.next(nextInits);
};
export const prependInitialization = (...fns) => {
let nextInits = [...fns, ...queue.getValue()];
queue.next(nextInits);
};
export const clearInitQueue = () => queue.next([]);
+74 -15
View File
@@ -1,13 +1,82 @@
// import deepAssign from "deep-assign";
import { serverPath } from "utils/serverPath";
/**
* Exposes the global configuration variable via a config.set function which is
* exposed to the python templates. Or, you can just import the functions and
* use them inside webpack-generated code. The idea is that we provide an
* observable that debounces changes and avoids instantiating a Galaxy app until
* values stop getting merged into the config.
*
* To set custom configuration variables from python just use something like:
* config.set({
* abc: {
* thing: "doodah"
* }
* });
*
* inside a python template to append new properties to the global config before
* it is passed to the GalaxyApp constructor
*
* This alleviates some of the timing headaches caused by python printing out
* script tags in the HTML body, or loading new config information inside of
* iframes, etc.
*/
import { BehaviorSubject, Subject } from "rxjs";
import { debounceTime, scan, filter } from "rxjs/operators";
import { getRootFromIndexLink } from "./getRootFromIndexLink";
import { defaultConfigs } from "./defaultConfigs";
// exporting this addInitialization to window.config variable
export { addInitialization } from "./initQueue";
// Config observable
// Process incoming config.set valls
const input = new Subject();
const mergedConfigs = input.pipe(
filter(fragment => fragment instanceof Object),
scan((config, fragment) => {
return Object.assign({}, config, fragment);
}, defaultConfigs)
);
// keep running config value
const currentConfig$ = new BehaviorSubject(defaultConfigs);
mergedConfigs.subscribe(currentConfig$);
// debounce, filter out initial null and other invalid stuff
export const config$ = currentConfig$.asObservable().pipe(
filter(Boolean),
debounceTime(100)
);
/**
* Adds config objects into the global configuration.
* @param {...objects} fragments Config objects to be merged into the global config
*/
export function set(...fragments) {
fragments.forEach(fragment => input.next(fragment));
}
/**
* Retrieves current value of the configuration, used for legacy code.
*/
export function get() {
return currentConfig$.getValue();
}
/**
* A lot of backbone objects are accessing window.Galaxy purely to get this
* value which is problematic since they execute as soon as the scripts hit the
* page.
*
* @param {*} defaultRoot
*/
export function getAppRoot(defaultRoot = "/") {
// try configs
let root = defaultRoot;
try {
root = window.galaxyConfig.options.root;
// try actual config
root = get().options.root;
} catch (err) {
// console.warn("galaxyConfig not defined");
try {
root = getRootFromIndexLink(defaultRoot);
} catch (err) {
@@ -16,13 +85,3 @@ export function getAppRoot(defaultRoot = "/") {
}
return root;
}
// finds <link> in head element and pulls root url fragment from there
function getRootFromIndexLink(defaultRoot = "/") {
let links = document.getElementsByTagName("link");
let indexLink = [...links].find(link => link.rel == "index");
if (indexLink && indexLink.href) {
return serverPath(indexLink.href);
}
return defaultRoot;
}
@@ -0,0 +1,53 @@
/**
* Entry point view initialization This is a collection of functions that were
* jammed together in the old onload.js script. I've added a queue so we can
* extend the initializations externally without creating a new entry point (at
* least until these are all components)
*
* This code should be considered transitional. Soon we will have a real Vue app
* and it will control the order of initialization based on individual component
* needs. For now, we need to organize the random scripts that are being printed
* to the python templates.
*/
import { combineLatest } from "rxjs";
import { map } from "rxjs/operators";
import { serverPath } from "utils/serverPath";
import { defaultAppFactory } from "./defaultAppFactory";
import { globalInits } from "./globalInits";
// observable configs and init queue
import { config$ } from "./loadConfig";
import { initializations$, clearInitQueue } from "./initQueue";
/**
* This is the standard endpoint initialization chain. Configs are loaded, the
* app is instantiated, then a bunch of initialization scripts are run and
* passed the new app instance and the configuration variables.
*
* @param {string} label Logging identifier
* @param {function} appFactory Override this parameter with a function matching the
* signature of defaultAppFactory in the event that you require a custom Galaxy
* application instance.
*/
export function standardInit(label = "Galaxy", appFactory = defaultAppFactory) {
// register assortment of random javascript inits
// which were transplanted from python templates
globalInits();
// waits for configs to stop changing then instantiates Galaxy
let galaxy$ = config$.pipe(map(cfg => appFactory(cfg, label)));
// once config, app and initialization observables have a value then run all
// the initialization functions, this will keep running new initialization
// functions even if they are registered super-late because combineLatest
// will not remake a the existing Galaxy or config objects, it'll just run
// the new batch of freshly registered init functions
combineLatest(config$, galaxy$, initializations$).subscribe(([config, galaxy, inits]) => {
console.groupCollapsed(`runInitializations`, label, serverPath());
inits.forEach(fn => fn(galaxy, config));
clearInitQueue();
console.groupEnd();
});
}
+2 -3
View File
@@ -1,8 +1,6 @@
/** Creates a generic/global Galaxy environment, loads shared libraries and a
* fake server */
import $ from "jquery";
// jQuery.fn.tooltip lives on one of these 2 libs
import "bootstrap";
import "bootstrap-tour";
@@ -10,6 +8,7 @@ import "bootstrap-tour";
import sinon from "sinon";
import Backbone from "backbone";
import { setGalaxyInstance } from "app";
import { getAppRoot } from "onload";
import galaxyOptions from "qunit/test-data/bootstrapped";
import serverdata from "qunit/test-data/fakeserver";
import "./assets/base.css";
@@ -33,7 +32,7 @@ export default {
window.WAIT_FADE = 300;
window.fakeserver = sinon.fakeServer.create();
for (var route in serverdata) {
window.fakeserver.respondWith("GET", window.Galaxy.root + route, [
window.fakeserver.respondWith("GET", getAppRoot() + route, [
200,
{ "Content-Type": "application/json" },
serverdata[route].data
+16 -16
View File
@@ -2,22 +2,22 @@
//var testsContext = require.context(".", true, /_tests$/);
//testsContext.keys().forEach(testsContext);
import "qunit/tests/galaxy_app_base_tests";
import "qunit/tests/jquery_plugin_tests";
import "qunit/tests/metrics_logger_tests";
import "./tests/galaxy_app_base_tests";
import "./tests/jquery_plugin_tests";
import "./tests/metrics_logger_tests";
// form_tests seems to need to come before something - maybe multiple things
import "qunit/tests/form_tests";
import "./tests/form_tests";
import "qunit/tests/list_of_pairs_collection_creator_tests";
import "qunit/tests/upload_dialog_tests";
import "qunit/tests/workflow_editor_tests";
import "qunit/tests/masthead_tests";
import "qunit/tests/graph_tests";
import "qunit/tests/job_dag_tests";
import "qunit/tests/history_contents_model_tests";
import "qunit/tests/hda_base_tests";
import "qunit/tests/modal_tests";
import "qunit/tests/page_tests";
import "qunit/tests/utils_tests";
import "qunit/tests/ui_tests";
import "./tests/list_of_pairs_collection_creator_tests";
import "./tests/upload_dialog_tests";
import "./tests/workflow_editor_tests";
import "./tests/masthead_tests";
import "./tests/graph_tests";
import "./tests/job_dag_tests";
import "./tests/history_contents_model_tests";
import "./tests/hda_base_tests";
import "./tests/modal_tests";
import "./tests/page_tests";
import "./tests/utils_tests";
import "./tests/ui_tests";
@@ -24,10 +24,6 @@ QUnit.module("Form test", {
});
QUnit.test("tool-form", function(assert) {
// // Huh? The following seems to be needed by tool-form.js - once the global usage
// // is cleaned up in that module this can be deleted I assume.
// window.parent.Galaxy = window.Galaxy;
var toolform = new ToolForm.View({ id: "test" });
$("body").prepend(toolform.$el);
window.fakeserver.respond();
@@ -2,6 +2,8 @@
import $ from "jquery";
import testApp from "qunit/test-app";
import Masthead from "layout/masthead";
import { getAppRoot } from "onload";
import { getGalaxyInstance } from "app";
QUnit.module("Masthead test", {
beforeEach: function() {
@@ -54,8 +56,7 @@ QUnit.test("tabs", function(assert) {
assert.ok($toggle.css("visibility") == "hidden", "Tab hidden");
tab.set("visible", true);
assert.ok($toggle.css("visibility") == "visible", "Tab visible, again");
// TODO: cleanup global usage so window.Galaxy isn't needed here.
assert.ok($toggle.attr("href") == window.Galaxy.root, "Correct initial url");
assert.ok($toggle.attr("href") == getAppRoot(), "Correct initial url");
tab.set("url", "_url");
assert.ok($toggle.attr("href") == "/_url", "Correct test url");
tab.set("url", "http://_url");
@@ -121,6 +122,6 @@ QUnit.test("tabs", function(assert) {
assert.ok(!$toggle.hasClass("toggle"), "Untoggled before click");
$toggle.trigger("click");
assert.ok($toggle.hasClass("toggle"), "Toggled after click");
// TODO: cleanup global usage so window.Galaxy isn't needed here.
assert.ok(window.Galaxy.frame.active, "Scratchbook is active");
let galaxy = getGalaxyInstance();
assert.ok(galaxy.frame.active, "Scratchbook is active");
});
@@ -11,8 +11,8 @@ import TerminalsView from "mvc/workflow/workflow-view-terminals";
import Connector from "mvc/workflow/workflow-connector";
import { getAppRoot } from "onload/loadConfig";
window.show_modal = function(a, b, c) {};
window.hide_modal = function() {};
// window.show_modal = function(a, b, c) {};
// window.hide_modal = function() {};
// create body and app
var create_app = function() {
+2 -8
View File
@@ -1,7 +1,4 @@
import jQuery from "jquery";
("use_strict");
var $ = jQuery;
/* global jQuery, $ */
// ============================================================================
/**
@@ -12,7 +9,7 @@ var $ = jQuery;
//
// Initialize "tag click functions" for tags.
//
function init_tag_click_function(tag_elt, click_func) {
export function init_tag_click_function(tag_elt, click_func) {
$(tag_elt)
.find(".tag-name")
.each(function() {
@@ -377,6 +374,3 @@ jQuery.fn.autocomplete_tagging = function(options) {
return tag_button;
}
};
// ============================================================================
export default init_tag_click_function;
+4 -10
View File
@@ -1,6 +1,4 @@
import jQuery from "jquery";
("use_strict");
var $ = jQuery;
// ============================================================================
@@ -11,7 +9,7 @@ var $ = jQuery;
* function to perform; or (b) another dict with two required keys, 'url' and 'action' (the
* function to perform. (b) is useful for exposing the underlying URL of the option.
*/
function make_popupmenu(button_element, initial_options) {
export function make_popupmenu(button_element, initial_options) {
/* Use the $.data feature to store options with the link element.
This allows options to be changed at a later time
*/
@@ -102,7 +100,9 @@ function make_popupmenu(button_element, initial_options) {
* but (since that selector searches the dom on the page), you can send a parent in
* NOTE: make_popup_menus, and make_popupmenu are horrible names
*/
function make_popup_menus(parent) {
export function make_popup_menus(parent) {
console.log("make_popup_menus");
// find all popupmenu menu divs (divs that contains anchors to be converted to menu options)
// either in the parent or the document if no parent passed
parent = parent || document;
@@ -161,9 +161,3 @@ function make_popup_menus(parent) {
menu.remove();
});
}
// ============================================================================
export default {
make_popupmenu: make_popupmenu,
make_popup_menus: make_popup_menus
};
@@ -0,0 +1,17 @@
/**
* Generic config storage object
*/
export function buildConfig(defaultConfigs = {}) {
let storage = Object.assign({}, defaultConfigs);
return new Proxy(storage, {
get(target, propName) {
if (propName in target) {
return target[propName];
} else {
console.warn("Requested missing config prop", propName);
}
}
});
}
+89 -90
View File
@@ -8,109 +8,81 @@
*/
import { mock } from "utils/mock";
const fakeLogger = mock(console);
// stores values that are returned when somebody asks for window.Something
window._monitorStorage = window._monitorStorage || {};
export function installMonitor(globalProp, fallbackValue) {
export function installMonitor(globalProp) {
// Need to handle the special case of window.Galaxy because
// we already install an Object.defineProperty on window.Galaxy
// to make the singleton available to legacy scripts
if (globalProp == "Galaxy") {
// handled in singleton
return;
}
// watch window.globalProp as a whole with a Object.defineProperty
installObjectWatcher(globalProp);
}
export function installObjectWatcher(globalProp, getter = null, setter = null) {
let label = `window.${globalProp}`;
let debug = isPropMonitored(globalProp);
let logger = debug ? console : fakeLogger;
// initialize storage with existing value
let existingValue = window[globalProp] || fallbackValue;
window._monitorStorage[globalProp] = existingValue;
logger.groupCollapsed(`${label} populating monitor storage with initial value`);
logger.log(existingValue);
logger.groupEnd();
let logger = debug ? console : mock(console);
// Replaces window.Thing with an object definition that forwards
// gets and set values to window._monitorStorage
try {
Object.defineProperty(window, globalProp, {
enumerable: true,
configurable: false,
get() {
let val = undefined;
logger.groupCollapsed(`${label} read`);
logger.trace();
try {
Object.defineProperty(window, globalProp, {
enumerable: true,
configurable: false,
get() {
let val = undefined;
logger.groupCollapsed(`${label} read`);
logger.trace();
try {
if (getter) {
val = getter();
} else {
val = window._monitorStorage[globalProp];
logger.log(val);
} catch (err) {
logger.warn("Unable to retrieve");
logger.warn(err);
}
logger.groupEnd();
return val;
},
set(newValue) {
logger.groupCollapsed(`${label} write...`, String(newValue));
logger.trace();
try {
logger.log(val);
} catch (err) {
logger.warn("Unable to retrieve");
logger.warn(err);
}
logger.groupEnd();
return val;
},
set(newValue) {
logger.groupCollapsed(`${label} write...`, String(newValue));
logger.trace();
try {
if (setter) {
setter(newValue);
} else {
window._monitorStorage[globalProp] = newValue;
} catch (err) {
logger.log("Unable to set value on window facade");
logger.warn(err);
}
logger.groupEnd();
}
});
} catch (err) {
logger.warn("Unable to install facade", err);
}
// Proxies the above object definition so we can watch changes to
// that object's properties (i.e. window.Thing.prop = "abc")
return new Proxy(
{},
{
get(o, prop) {
let val = undefined;
logger.groupCollapsed(`${label}.${prop} read`);
logger.trace();
try {
let target = window[globalProp];
val = target[prop];
} catch (err) {
logger.warn("Unable to retrieve property from proxy", prop);
logger.warn(err);
}
logger.groupEnd();
return val;
},
set(o, prop, val) {
let didWrite = true;
logger.groupCollapsed(`${label}.${prop} write`);
logger.trace();
try {
let target = window[globalProp];
logger.log("new value", String(val));
target[prop] = val;
} catch (err) {
logger.warn("Unable to write", globalProp, val);
didWrite = false;
}
logger.groupEnd();
return didWrite;
} catch (err) {
logger.log("Unable to set value on window facade");
logger.warn(err);
}
logger.groupEnd();
}
);
}
// Shows which properties are actually being monitored
export function showMonitoredProperties() {
return Object.keys(window._monitorStorage);
});
}
// sets a flag in sessionStorage to install a monitor for the
// indicated prop on the next page refresh
export function toggleGlobalMonitor(prop, bShow = false) {
export function toggleGlobalMonitor(prop, bShow = true) {
let toggleList = getToggles();
toggleList[prop] = Boolean(bShow);
if (bShow) {
toggleList[prop] = true;
} else {
if (prop in toggleList) {
delete toggleList[prop];
}
}
setToggles(toggleList);
return toggleList;
}
@@ -121,22 +93,49 @@ export function isPropMonitored(prop) {
return toggleList[prop] == true;
}
// show list of monitor toggles
export function showMonitorToggles() {
let toggleList = getToggles();
console.log(toggleList);
}
// retrieve toggle list from the session storage
function getToggles() {
let json = sessionStorage.getItem("global_monitors");
let existinglist = json ? JSON.parse(json) : {};
return existinglist;
}
// put the toggle list back in session storage
function setToggles(toggleList) {
sessionStorage.setItem("global_monitors", JSON.stringify(toggleList));
}
// Console utilities
// Display properties available for monitoring at time of page reload
// (you can add more during a page-session with installMonitor)
export function monitorInit() {
let monitoredProps = Object.keys(getToggles());
// Type showMonitoredProperties in developer console to list which
// global variables have actually been proxied
window.showMonitoredProperties = showMonitoredProperties;
console.groupCollapsed("monitor init");
console.log(
"The following global properties are monitored because they were setup in the javascript source manually:",
monitoredProps
);
console.log("To toggle messages on/off, type toggleGlobalMonitor('Galaxy', true|false) into the console");
console.log("You can set up any other variable by typing installMonitor('a window variable name')");
console.groupEnd();
// Type toggleGlobalMonitor("variableName", true|false) in
// the developer console to enable/disable
window.toggleGlobalMonitor = toggleGlobalMonitor;
// installs monitors from stored list
monitoredProps.forEach(installMonitor);
// makes monitor installation available at console
window.installMonitor = installMonitor;
// Type showMonitoredProperties in developer console to list which
// global variables have actually been proxied
window.showMonitorToggles = showMonitorToggles;
// Type toggleGlobalMonitor("variableName", true|false) in
// the developer console to enable/disable
window.toggleGlobalMonitor = toggleGlobalMonitor;
}
+4 -3
View File
@@ -256,18 +256,19 @@ import { getAppRoot } from "onload/loadConfig";
);
// drag/drop events
el.on("drop", e => {
let element = el.get(0);
element.addEventListener("drop", e => {
opts.ondragleave(e);
if (e.dataTransfer) {
opts.onchange(e.dataTransfer.files);
e.preventDefault();
}
});
el.on("dragover", e => {
element.addEventListener("dragover", e => {
e.preventDefault();
opts.ondragover(e);
});
el.on("dragleave", e => {
element.addEventListener("dragleave", e => {
e.stopPropagation();
opts.ondragleave(e);
});
+88 -1
View File
@@ -349,6 +349,92 @@ export function setWindowTitle(title) {
}
}
/**
* Calculate a 32 bit FNV-1a hash
* Found here: https://gist.github.com/vaiorabbit/5657561
* Ref.: http://isthe.com/chongo/tech/comp/fnv/
*
* @param {string} str the input value
* @returns {integer}
*/
function hashFnv32a(str) {
var i,
l,
hval = 0x811c9dc5;
for (i = 0, l = str.length; i < l; i++) {
hval ^= str.charCodeAt(i);
hval += (hval << 1) + (hval << 4) + (hval << 7) + (hval << 8) + (hval << 24);
}
return hval >>> 0;
}
/**
* Implement W3C contrasting color algorithm
* http://www.w3.org/TR/AERT#color-contrast
*
* @param {number} r Red
* @param {number} g Green
* @param {number} b Blue
* @return {string} Either 'white' or 'black'
*
* Assumes r, g, b are in the set [0, 1]
*/
function contrastingColor(r, g, b) {
var o = (r * 255 * 299 + g * 255 * 587 + b * 255 * 114) / 1000;
return o > 125 ? "black" : "white";
}
/**
* Converts an HSL color value to RGB. Conversion formula
* adapted from http://en.wikipedia.org/wiki/HSL_color_space.
* Assumes h, s, and l are contained in the set [0, 1] and
* returns r, g, and b in the set [0, 1].
*
* @param {number} h The hue
* @param {number} s The saturation
* @param {number} l The lightness
* @return {Array} The RGB representation
*/
function hslToRgb(h, s, l) {
var r, g, b;
if (s == 0) {
r = g = b = l; // achromatic
} else {
var hue2rgb = function hue2rgb(p, q, t) {
if (t < 0) t += 1;
if (t > 1) t -= 1;
if (t < 1 / 6) return p + (q - p) * 6 * t;
if (t < 1 / 2) return q;
if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
return p;
};
var q = l < 0.5 ? l * (1 + s) : l + s - l * s;
var p = 2 * l - q;
r = hue2rgb(p, q, h + 1 / 3);
g = hue2rgb(p, q, h);
b = hue2rgb(p, q, h - 1 / 3);
}
return [r, g, b];
}
export function generateTagStyle(tag) {
var hash = hashFnv32a(tag);
var hue = Math.abs((hash >> 4) % 360);
var lightnessOffset = 75;
var lightness = lightnessOffset + (hash & 0xf);
var bgColor = `hsl(${hue}, 100%, ${lightness}%)`;
var brColor = `hsl(${hue}, 100%, ${lightness - 40}%)`;
var [r, g, b] = hslToRgb(hue, 1.0, lightness / 100);
var fgColor = contrastingColor(r, g, b);
return `background-color: ${bgColor}; color: ${fgColor}; border: 1px solid ${brColor}`;
}
export default {
cssLoadFile: cssLoadFile,
cssGetAttribute: cssGetAttribute,
@@ -368,5 +454,6 @@ export default {
linkify: linkify,
appendScriptStyle: appendScriptStyle,
getQueryString: getQueryString,
setWindowTitle: setWindowTitle
setWindowTitle: setWindowTitle,
generateTagStyle: generateTagStyle
};
+4 -3
View File
@@ -1,4 +1,7 @@
import * as _ from "libs/underscore";
import $ from "jquery";
import _ from "underscore";
import Backbone from "backbone";
import { make_popupmenu } from "ui/popupmenu";
/**
* View for track/group header.
*/
@@ -93,8 +96,6 @@ var TrackHeaderView = Backbone.View.extend({
this.action_icons.mode_icon.attr("title", `Set display mode (now: ${this.mode})`);
// Setup popup menu for changing modes.
var self = this;
var track = this.model;
var mode_mapping = {};
for (var i = 0, len = track.display_modes.length; i < len; i++) {
+33
View File
@@ -36,6 +36,7 @@ $fa-font-path: "../../../node_modules/font-awesome/fonts/";
@import "tour.scss";
@import "flex.scss";
@import "charts.scss";
@import "message.scss";
// Mixins
@mixin user-select($select) {
@@ -184,6 +185,13 @@ body {
height: 100%;
}
}
// Replaces unnecessary if/then logic in base_panels.mako
&:first-child {
left: 0;
}
&:last-child {
right: 0;
}
}
#right {
@@ -2080,3 +2088,28 @@ div.toolTitleNoSection {
[v-cloak] {
display: none;
}
/* Toolshed, reports custom styles from mako templates */
body.toolshed {
margin: 0;
padding: 0;
overflow: hidden;
#left {
background: #c1c9e5 url(../../images/menu_bg.png) top repeat-x;
}
.unified-panel-body {
overflow: auto;
}
.toolMenu {
margin-left: 10px;
}
}
body.reports {
margin: 0;
padding: 0;
overflow: hidden;
#left {
background: #c1c9e5 url(../../images/menu_bg.png) top repeat-x;
}
}
+7
View File
@@ -0,0 +1,7 @@
// message box
#center:only-child {
// padding: 10px; (currently disabled as it interferes with the display of many single-page views.
.message {
margin: 10px;
}
}
+1 -2
View File
@@ -131,10 +131,9 @@ $ui-margin-horizontal-large: $margin-v * 2;
word-wrap: break-word;
font-weight: bold;
.ui-form-collapsible {
cursor: pointer;
.icon {
cursor: pointer;
font-size: 1.2em;
width: 20px;
position: relative;
top: 1px;
}
+3
View File
@@ -279,6 +279,9 @@
td {
padding: 0.5rem;
}
.alert-success {
background: $brand-success !important;
}
}
.upload-settings {
+1 -1
View File
@@ -11,7 +11,7 @@ var paths = {
scripts: [
"galaxy/scripts/**/*.js",
"!galaxy/scripts/qunit/**/*",
"!galaxy/scripts/apps/**/*",
"!galaxy/scripts/entry/**/*",
"!galaxy/scripts/libs/**/*"
],
plugin_dirs: ["../config/plugins/**/static/**/*", "!../config/plugins/**/node_modules{,/**}"],

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