Use popper.js instead of bootstrap tours

This commit is contained in:
guerler
2022-04-22 11:15:17 -04:00
parent cf46c5c1fa
commit 23f1509176
21 changed files with 400 additions and 413 deletions
-1
View File
@@ -40,7 +40,6 @@ const PATHS = {
// automatically. Eventually, this dictionary and staging step will
// not be necessary.
backbone: ["backbone.js", "backbone.js"],
"@galaxyproject/bootstrap-tour": ["build/js/bootstrap-tour.js", "bootstrap-tour.js"],
jquery: ["dist/jquery.js", "jquery/jquery.js"],
"jquery-migrate": ["dist/jquery-migrate.js", "jquery/jquery.migrate.js"],
"jquery-mousewheel": ["jquery.mousewheel.js", "jquery/jquery.mousewheel.js"],
+1 -1
View File
@@ -20,10 +20,10 @@
"@fortawesome/free-regular-svg-icons": "^5.15.4",
"@fortawesome/free-solid-svg-icons": "^5.15.4",
"@fortawesome/vue-fontawesome": "^2.0.6",
"@galaxyproject/bootstrap-tour": "0.12.2",
"@handsontable/vue": "^2.0.0-beta1",
"@hirez_io/observer-spy": "^2.1.2",
"@johmun/vue-tags-input": "^2.1.0",
"@popperjs/core": "^2.11.5",
"@sentry/browser": "^6.17.4",
"assert": "^2.0.0",
"axios": "^0.25.0",
+1 -1
View File
@@ -31,7 +31,7 @@ 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 { openGlobalUploadModal } from "components/Upload";
export { runTour } from "components/Tour/runTour";
export { Toast } from "ui/toast"; // TODO: remove when external consumers are updated/gone (IES right now)
export function trackster(options) {
+103
View File
@@ -0,0 +1,103 @@
<template>
<TourStep
v-if="currentStep"
:step="currentStep"
:key="currentIndex"
:is-playing="isPlaying"
:is-last="isLast"
@next="next"
@end="end"
@play="play" />
</template>
<script>
import TourStep from "./TourStep";
// popup display duration when auto-playing the tour
const playDelay = 3000;
export default {
components: {
TourStep,
},
props: {
steps: {
type: Array,
required: true,
},
},
data() {
return {
currentIndex: -1,
isPlaying: false,
};
},
beforeDestroy() {
window.removeEventListener("keyup", this.handleKeyup);
},
computed: {
currentStep() {
return this.steps[this.currentIndex];
},
numberOfSteps() {
return this.steps.length;
},
isLast() {
return this.currentIndex === this.steps.length - 1;
},
},
mounted() {
this.start();
},
methods: {
start() {
window.addEventListener("keyup", this.handleKeyup);
this.currentIndex = 0;
},
play(isPlaying) {
this.isPlaying = isPlaying;
if (this.isPlaying) {
this.next();
}
},
async next() {
// do post-actions
if (this.currentStep && this.currentStep.onNext) {
await this.currentStep.onNext();
}
// do pre-actions
const nextIndex = this.currentIndex + 1;
if (nextIndex < this.numberOfSteps && this.currentIndex !== -1) {
const nextStep = this.steps[nextIndex];
if (nextStep.onBefore) {
await nextStep.onBefore();
// automatically continues to next step if enabled
if (this.isPlaying) {
setTimeout(() => {
if (this.isPlaying) {
this.next();
}
}, playDelay);
}
}
}
// go to next step
this.currentIndex = nextIndex;
},
end() {
this.currentIndex = -1;
this.isPlaying = false;
},
handleKeyup(e) {
switch (e.keyCode) {
case 39:
this.next();
break;
case 27:
this.end();
break;
}
},
},
};
</script>
+75
View File
@@ -0,0 +1,75 @@
<template>
<div
class="tour-element"
:class="{ 'tour-element-sticky': !targetElement, 'tour-has-title': !!step.title }"
:id="'tour-element-' + hash"
:ref="'tour-element-' + hash">
<div v-if="step.title" class="tour-header">
<div class="tour-title" v-html="step.title"></div>
</div>
<div v-if="step.content" class="tour-content" v-html="step.content" />
<div class="tour-buttons">
<div v-if="isLast">
<button class="tour-button tour-end" @click.prevent="$emit('end')">Close</button>
</div>
<div v-else-if="isPlaying">
<button class="tour-button tour-stop" @click.prevent="$emit('play', false)">Stop</button>
</div>
<div v-else>
<button class="tour-button tour-end" @click.prevent="$emit('end')">Cancel</button>
<button class="tour-button tour-play" @click.prevent="$emit('play', true)">Play</button>
<button class="tour-button tour-next" @click.prevent="$emit('next')">Continue</button>
</div>
</div>
<div v-if="targetElement" class="tour-arrow" data-popper-arrow />
</div>
</template>
<script>
import { createPopper } from "@popperjs/core";
import sum from "hash-sum";
export default {
props: {
step: {
type: Object,
},
isPlaying: {
type: Boolean,
},
isLast: {
type: Boolean,
},
},
data() {
return {
hash: sum(this.step.target),
targetElement: document.querySelector(this.step.target),
};
},
methods: {
createStep() {
if (this.targetElement) {
this.targetElement.scrollIntoView({ behavior: "smooth" });
createPopper(this.targetElement, this.$refs["tour-element-" + this.hash], {
modifiers: [
{
name: "preventOverflow",
options: {
altAxis: true,
tether: false,
padding: 20,
},
},
],
strategy: "absolute",
placement: "auto",
});
}
},
},
mounted() {
this.createStep();
},
};
</script>
+104
View File
@@ -0,0 +1,104 @@
import axios from "axios";
import { getAppRoot } from "onload";
import { mountVueComponent } from "utils/mountVueComponent";
import Tour from "./Tour";
// delays and maximum number of attempts to wait for element
const attempts = 100;
const delay = 200;
// return tour data
async function getTourData(tourId) {
const url = `${getAppRoot()}api/tours/${tourId}`;
const { data } = await axios.get(url);
return data;
}
// returns the selected element
function getElement(selector) {
return document.querySelector(selector);
}
// wait for element
function waitForElement(selector, resolve, reject, tries) {
const el = getElement(selector);
if (el) {
resolve();
} else if (tries > 0) {
setTimeout(() => {
waitForElement(selector, resolve, reject, tries - 1);
}, delay);
} else {
console.error("Tours - Element not found.", selector);
reject();
}
}
// performs a click event on the selected element
function doClick(targets) {
if (targets) {
targets.forEach((selector) => {
const el = getElement(selector);
if (el) {
el.click();
} else {
console.error("Tours - Click target not found.", selector);
}
});
}
}
// insert text into the selected element
function doInsert(selector, value) {
if (value !== null) {
const el = getElement(selector);
if (el) {
el.value = value;
const event = new Event("input");
el.dispatchEvent(event);
} else {
console.error("Tours - Insert target not found.", selector);
}
}
}
// mount tours to body
function mountTour(props) {
const container = document.createElement("div");
const body = document.querySelector("body");
body.append(container);
const mountFn = mountVueComponent(Tour);
return mountFn(props, container);
}
// executes tour
export async function runTour(tourId, tourData = null) {
if (!tourData) {
tourData = await getTourData(tourId);
}
const steps = [];
Object.values(tourData.steps).forEach((step) => {
steps.push({
target: step.element,
title: step.title,
content: step.content,
onBefore: () => {
return new Promise((resolve, reject) => {
if (step.element) {
// wait for element before continuing tour
waitForElement(step.element, resolve, reject, attempts);
} else {
resolve();
}
}).then(() => {
doClick(step.preclick);
doInsert(step.element, step.textinsert);
});
},
onNext: () => {
doClick(step.postclick);
},
});
});
return mountTour({ steps });
}
+4 -5
View File
@@ -22,7 +22,7 @@ import UserPreferences from "components/User/UserPreferences";
import DatasetList from "components/Dataset/DatasetList";
import { getUserPreferencesModel } from "components/User/UserPreferencesModel";
import CustomBuilds from "components/User/CustomBuilds";
import Tours from "mvc/tours";
import { runTour } from "components/Tour/runTour";
import GridView from "mvc/grid/grid-view";
import GridShared from "mvc/grid/grid-shared";
import JobDetails from "components/JobInformation/JobDetails";
@@ -142,10 +142,9 @@ export const getAnalysisRouter = (Galaxy) => {
},
show_tours: function (tour_id) {
this.home();
if (tour_id) {
Tours.giveTourById(tour_id);
} else {
this.page.display(new Tours.ToursView());
runTour(tour_id);
}
},
@@ -429,7 +428,7 @@ export const getAnalysisRouter = (Galaxy) => {
},
/** */
home: function (params) {
home: function (params = {}) {
// TODO: to router, remove Globals
// load a tool by id (tool_id) or rerun a previous tool execution (job_id)
if (params.tool_id || params.job_id) {
-255
View File
@@ -1,255 +0,0 @@
/**
* This is the primary galaxy tours definition, currently only used for
* rendering a tour menu.
*/
import _l from "utils/localization";
// The following must remain staged out of libs and not sourced from
// node_modules, until TDTs are bundled.
import _ from "underscore";
import $ from "jquery";
import Backbone from "backbone";
import { getAppRoot } from "onload/loadConfig";
import "@galaxyproject/bootstrap-tour";
// bootstrap-tour configures a window.Tour object; keep a local ref.
const Tour = window.Tour;
// For buttons to show, we need to add them to bootstrap/popper's sanitizing filters.
$.fn.tooltip.Constructor.Default.whiteList.button = ["data-role"];
var gxy_root = getAppRoot();
const TOURPAGE_TEMPLATE = `
<h2>Galaxy Tours</h2>
<p>This page presents a list of interactive tours available on this Galaxy server.
Select any tour to get started (and remember, you can click 'End Tour' at any time).</p>
<div class="row mb-3">
<div class="col-12 btn-group" role="group" aria-label="Tag selector">
<% _.each(tourtagorder, function(tag) { %>
<button class="btn btn-primary tag-selector-button" tag-selector-button="<%- tag.key %>">
<%- tag.name %>
</button>
<% }); %>
</div>
</div>
<div class="row mb-3">
<div class="col-12">
<h4>Tours</h4>
<ul class="list-group">
<% _.each(tours, function(tour) { %>
<li class="list-group-item" tags="<%- tour.attributes.tags_lc %>">
<a href="/tours/<%- tour.id %>" class="tourItem" data-tour.id=<%- tour.id %>>
<%- tour.attributes.name || tour.id %>
</a>
- <%- tour.attributes.description || "No description given." %>
<% _.each(tour.attributes.tags, function(tag) { %>
<span class="badge badge-primary">
<%- tag.charAt(0).toUpperCase() + tag.slice(1) %>
</span>
<% }); %>
</li>
<% }); %>
</ul>
</div>
</div>`;
var tour_opts = {
storage: window.sessionStorage,
onEnd: function () {
window.sessionStorage.removeItem("activeGalaxyTour");
},
delay: 150, // Attempts to make it look natural
orphan: true,
};
var hooked_tour_from_data = (data) => {
_.each(data.steps, (step) => {
if (step.preclick) {
step.onShow = () => {
_.each(step.preclick, (preclick) => {
// TODO: click delay between clicks
$(preclick).click();
});
};
}
if (step.postclick) {
step.onHide = () => {
_.each(step.postclick, (postclick) => {
// TODO: click delay between clicks
$(postclick).click();
});
};
}
if (step.textinsert) {
// Have to manually trigger a change here, for some
// elements which have additional logic, like the
// upload input box
step.onShown = () => {
$(step.element).val(step.textinsert).trigger("change");
};
}
if (step.path) {
// Galaxy does *not* support automagic path navigation right now in
// Tours -- too many ways to get your client 'stuck' in automatic
// navigation loops. We can probably re-enable this as our client
// routing matures.
console.warn(
"This Galaxy Tour is attempting to use path navigation. This is known to be unstable and can possibly get the Galaxy client 'stuck' in a tour, and at this time is not allowed."
);
delete step.path;
}
});
return data;
};
var TourItem = Backbone.Model.extend({
urlRoot: `${gxy_root}api/tours`,
});
var Tours = Backbone.Collection.extend({
url: `${gxy_root}api/tours`,
model: TourItem,
});
export var ToursView = Backbone.View.extend({
title: _l("Tours"),
initialize: function () {
var self = this;
this.setElement("<div/>");
this.model = new Tours();
this.model.fetch({
success: function () {
self.render();
},
error: function () {
// Do something.
console.error("Failed to fetch tours.");
},
});
},
render: function () {
var tpl = _.template(TOURPAGE_TEMPLATE);
var tourtags = {};
_.each(this.model.models, (tour) => {
tour.attributes.tags_lc = [];
if (tour.attributes.tags === null) {
if (tourtags.Untagged === undefined) {
tourtags.Untagged = { name: "Untagged", tours: [] };
}
tourtags.Untagged.tours.push(tour);
} else {
_.each(tour.attributes.tags, (otag) => {
var tag = otag.charAt(0).toUpperCase() + otag.slice(1);
if (tourtags[tag] === undefined) {
tourtags[tag] = { name: tag, tours: [] };
}
tour.attributes.tags_lc.push(otag.toLowerCase());
tourtags[tag].tours.push(tour);
});
}
});
//var tourtagorder = Object.keys(tourtags).sort();
var tourtagorder = [];
Object.keys(tourtags).forEach(function (tag, index) {
tourtagorder.push({ name: tag, key: tag.toLowerCase() });
});
this.$el
.html(
tpl({
tours: this.model.models,
tourtags: tourtags,
tourtagorder: tourtagorder,
})
)
.on("click", ".tourItem", function (e) {
e.preventDefault();
giveTourById($(this).data("tour.id"));
})
.on("click", ".tag-selector-button", (e) => {
var elem = $(e.target);
var active_tags = [];
// Switch classes for the buttons
elem.toggleClass("btn-primary");
elem.toggleClass("btn-secondary");
// Get all non-disabled tags
$(`.tag-selector-button.btn-primary`).each(function () {
active_tags.push($(this).attr("tag-selector-button"));
});
// Loop over all list items, subsequently determine these are
// only the tours (tags should be unique). Then use the non-disabled tags to
// determien whether or not to display this specific tour.
$(`li.list-group-item`).each(function () {
if ($(this).attr("tags")) {
var tour_tags = [];
var tour_tags_html = $(this).attr("tags");
tour_tags = tour_tags_html.split(",");
var fil_tour_tags = tour_tags.filter(function (tag) {
return active_tags.indexOf(tag.toLowerCase()) > -1;
});
$(this).css("display", fil_tour_tags.length > 0 ? "block" : "none");
}
});
});
},
});
export function giveTourWithData(data) {
const hookedTourData = hooked_tour_from_data(data);
window.sessionStorage.setItem("activeGalaxyTour", JSON.stringify(data));
// Store tour steps in sessionStorage to easily persist w/o hackery.
const tour = new Tour(_.extend({ steps: hookedTourData.steps }, tour_opts));
// Always clean restart, since this is a new, explicit execution.
tour.init();
tour.goTo(0);
tour.restart();
return tour;
}
export function giveTourById(tour_id) {
var url = `${gxy_root}api/tours/${tour_id}`;
$.getJSON(url, (data) => {
giveTourWithData(data);
});
}
export function activeGalaxyTourRunner() {
var et = JSON.parse(window.sessionStorage.getItem("activeGalaxyTour"));
if (et) {
et = hooked_tour_from_data(et);
if (et && et.steps) {
if (window && window.self === window.top) {
// Only kick off a new tour if this is the toplevel window (non-iframe). This
// functionality actually *could* be useful, but we'd need to handle it better and
// come up with some design guidelines for tours jumping between windows.
// Disabling for now.
var tour = new Tour(
_.extend(
{
steps: et.steps,
},
tour_opts
)
);
tour.init();
tour.restart();
}
}
}
}
export default {
ToursView: ToursView,
giveTourWithData: giveTourWithData,
giveTourById: giveTourById,
activeGalaxyTourRunner: activeGalaxyTourRunner,
};
-2
View File
@@ -18,7 +18,6 @@ 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() {
@@ -32,7 +31,6 @@ export function globalInits() {
init_refresh_on_change,
() => replace_big_select_inputs(20, 1500),
make_popup_menus,
initTours,
onloadWebhooks
);
}
@@ -1,7 +0,0 @@
import Tours from "mvc/tours";
export function initTours(Galaxy) {
console.log("initTours");
Tours.activeGalaxyTourRunner();
Galaxy.giveTourWithData = Tours.giveTourWithData;
}
-1
View File
@@ -1,7 +1,6 @@
// Bootstrap overwrites .tooltip() method, load it after jquery-ui
// (which is loaded everywhere via libs/jquery.custom.js)
import "bootstrap";
import "@galaxyproject/bootstrap-tour";
// Galaxy core styles
import "scss/base.scss";
+82 -3
View File
@@ -1,4 +1,83 @@
.tour-tour > .popover-title {
background-color: $brand-dark;
color: $white;
.tour-element {
background: $white;
border-radius: $border-radius-base;
box-shadow: 0 $border-radius-base $border-radius-extralarge $brand-dark;
max-width: 400px;
outline: none;
width: 100%;
z-index: 9999;
&-sticky {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
.tour-header {
@extend .p-1;
border-top-left-radius: $border-radius-base;
border-top-right-radius: $border-radius-base;
background: $brand-info;
.tour-title {
@extend .h5;
@extend .m-0;
@extend .p-1;
color: $white;
}
}
.tour-content {
@extend .p-2;
border-radius: $border-radius-base;
color: $text-color;
}
.tour-buttons {
@extend .float-right;
@extend .p-2;
.tour-button {
@extend .ml-1;
@extend .btn;
@extend .btn-sm;
@extend .btn-info;
}
}
}
.tour-element,
.tour-element *,
.tour-element :after,
.tour-element :before {
box-sizing: border-box;
}
.tour-arrow,
.tour-arrow:before {
height: 16px;
width: 16px;
position: absolute;
z-index: -1;
}
.tour-arrow:before {
background: $white;
content: "";
transform: rotate(45deg);
}
.tour-element[data-popper-placement^="top"] > .tour-arrow {
bottom: -8px;
}
.tour-element[data-popper-placement^="bottom"] > .tour-arrow {
top: -8px;
}
.tour-element[data-popper-placement^="left"] > .tour-arrow {
right: -8px;
}
.tour-element[data-popper-placement^="right"] > .tour-arrow {
left: -8px;
}
.tour-element.tour-has-title[data-popper-placement^="bottom"] > .tour-arrow:before {
background-color: $brand-info;
}
-1
View File
@@ -3,7 +3,6 @@
// jQuery.fn.tooltip lives on one of these 2 libs
import "bootstrap";
import "@galaxyproject/bootstrap-tour";
import sinon from "sinon";
import Backbone from "backbone";
+7 -16
View File
@@ -1167,15 +1167,6 @@
resolved "https://registry.yarnpkg.com/@fortawesome/vue-fontawesome/-/vue-fontawesome-2.0.6.tgz#87e691ed87f28f4667238573a29743f543a087f6"
integrity sha512-V3vT3flY15AKbUS31aZOP12awQI3aAzkr2B1KnqcHLmwrmy51DW3pwyBczKdypV8QxBZ8U68Hl2XxK2nudTxpg==
"@galaxyproject/bootstrap-tour@0.12.2":
version "0.12.2"
resolved "https://registry.yarnpkg.com/@galaxyproject/bootstrap-tour/-/bootstrap-tour-0.12.2.tgz#9277e2dd976b8c778927c70d8180bb7a7090f009"
integrity sha512-iGmRhRhoq5vuhgVNig5/Hiu+86emgBDMtB12vo0ApAh9K+1lSnb8NLvfxDOOD7XlO9n4UMFJ5xnjSzeKCnOhqQ==
dependencies:
bootstrap ">=4.0.0-beta"
jquery "^3.0.0"
popper.js "^1.12.3"
"@handsontable/vue@^2.0.0-beta1":
version "2.0.0"
resolved "https://registry.yarnpkg.com/@handsontable/vue/-/vue-2.0.0.tgz#28ae7d247a89738088abc32584562925dde18db0"
@@ -1440,6 +1431,11 @@
consola "^2.15.0"
node-fetch "^2.6.1"
"@popperjs/core@^2.11.5":
version "2.11.5"
resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.5.tgz#db5a11bf66bdab39569719555b0f76e138d7bd64"
integrity sha512-9X2obfABZuDVLCgPK9aX0a/x4jaOEweTTWE2+9sr0Qqqevj2Uv5XorvusThmc9XGYpS9yI+fhh8RTafBtGposw==
"@sentry/browser@^6.17.4":
version "6.17.4"
resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-6.17.4.tgz#c2711a12e89dab4abecd9a04716c07c86712fa5a"
@@ -2664,11 +2660,6 @@ bootstrap@4.6, "bootstrap@>=4.5.3 <5.0.0":
resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-4.6.1.tgz#bc25380c2c14192374e8dec07cf01b2742d222a2"
integrity sha512-0dj+VgI9Ecom+rvvpNZ4MUZJz8dcX7WCX+eTID9+/8HgOkv3dsRzi8BGeZJCQU6flWQVYxwTQnEZFrmJSEO7og==
bootstrap@>=4.0.0-beta:
version "5.1.3"
resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-5.1.3.tgz#ba081b0c130f810fa70900acbc1c6d3c28fa8f34"
integrity sha512-fcQztozJ8jToQWXxVuEyXWW+dSo8AiXWKwiSSrKWsRB/Qt+Ewwza+JWoLKiTuQLaEPhdNAJ7+Dosc9DOIqNy7Q==
brace-expansion@^1.1.7:
version "1.1.11"
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd"
@@ -6159,7 +6150,7 @@ jquery@2:
resolved "https://registry.yarnpkg.com/jquery/-/jquery-2.2.4.tgz#2c89d6889b5eac522a7eea32c14521559c6cbf02"
integrity sha1-LInWiJterFIqfuoywUUhVZxsvwI=
jquery@>=1.12.0, "jquery@>=1.8.0 <4.0.0", jquery@^3.0.0:
jquery@>=1.12.0, "jquery@>=1.8.0 <4.0.0":
version "3.6.0"
resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.6.0.tgz#c72a09f15c1bdce142f49dbf1170bdf8adac2470"
integrity sha512-JVzAR/AjBvVt2BmYhxRCSYysDsPcssdmTFnzyLEts9qNwmjmu4JTAMYubEfwVOSwpQ1I1sKKFcxhZCI2buerfw==
@@ -7496,7 +7487,7 @@ pkg-dir@^4.1.0, pkg-dir@^4.2.0:
dependencies:
find-up "^4.0.0"
popper.js@^1.12.3, popper.js@^1.16.1:
popper.js@^1.16.1:
version "1.16.1"
resolved "https://registry.yarnpkg.com/popper.js/-/popper.js-1.16.1.tgz#2a223cb3dc7b6213d740e40372be40de43e65b1b"
integrity sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==
+6 -39
View File
@@ -11,8 +11,7 @@ steps:
# If you don't specify any title, a default title is used, defined above.
- title: "Welcome to Galaxy"
# 'content' is the actual text that is shown to the user
content: "This short tour will guide you through Galaxy's user interface.<br>
You can navigate with your arrow keys and leave the tour at any time point with 'Escape' or the 'End tour' button."
content: "This short tour will guide you through Galaxy's user interface. You can leave the tour at any time by pressing escape."
# backdrop is just one of many properties you can attach to one step-container,
# a full reference can be found at http://bootstraptour.com/api/
backdrop: true
@@ -21,10 +20,7 @@ steps:
# In this case we want to highlight the Upload button with the `.upload-button` selector
- title: "Upload your data"
element: ".upload-button"
intro: "Galaxy supports many ways to get in your data.<br>
Use this button to upload your data."
# position of the text box relative to the selected element
position: "right"
intro: "Galaxy supports many ways to get in your data. Use this button to upload your data."
# You can trigger click() events on arbitrary elements before (preclick) or after (postclick) the element is shown
postclick:
- ".upload-button"
@@ -32,19 +28,16 @@ steps:
- title: "Upload your data"
element: "#btn-local"
intro: "You can upload data from your computer."
position: "top"
postclick:
- "#btn-new"
- title: "Upload your data"
element: "#btn-new"
intro: "Copy and paste data directly into Galaxy or include URLs that lead to your data"
position: "top"
- title: "Insert URLs"
element: ".upload-text-content:first"
element: ".upload-text-content"
intro: "URLs separated by a line break are automatically downloaded by Galaxy."
position: "top"
textinsert: |
https://raw.githubusercontent.com/bgruening/galaxytools/adf077b912ddebd97b07b947b855cdd2862ed8ef/tools/augustus/test-data/human_augustus.fa
https://raw.githubusercontent.com/bgruening/galaxytools/adf077b912ddebd97b07b947b855cdd2862ed8ef/tools/sailfish/test-data/reads_2.fastq
@@ -53,61 +46,51 @@ steps:
- title: "Start the upload"
element: "#btn-start"
intro: "Upload the data into your Galaxy history."
position: "top"
postclick:
- "#btn-start"
- title: "Close upload/download manager"
element: "#btn-close"
intro: "Close the upload manager with this button or with a click outside of the manager window."
position: "top"
postclick:
- "#btn-close"
- title: "Tools"
element: "#left"
intro: "This is your ToolBox. All tools available in your Galaxy instance you can find here."
position: "right"
- title: "Tool categories"
element: '.toolSectionTitle'
intro: "Tools are grouped into categories to make them easier to find."
position: "right"
- title: "Tool search bar"
element: '.search-query'
intro: "You can search for tools by keywords."
position: "right"
textinsert: 'filter'
- title: "Select a tool"
element: 'a[href$="tool_runner?tool_id=Filter1"]'
intro: "Select and load a tool for your analysis by clicking the underlined link."
position: "right"
postclick:
- 'a[href$="tool_runner?tool_id=Filter1"]'
- title: "Filter Tool"
element: '.center-panel'
intro: "Your tool is loaded into the main Galaxy page and ready for use."
position: "right"
- title: "Tool parameters"
element: '.center-panel .portlet-content'
intro: "Here you can choose your tool parameters. Select your input dataset from your history and specify parameters for your analysis."
position: "right"
- title: "Tool help"
element: 'div.form-help'
intro: "Every Galaxy tool has a help section with detailed information about the tool and its parameters.
Have a look and study your tool in depth by reading it."
position: "top"
- title: "Run your tool"
element: '#execute'
intro: "Click on 'Execute' to run your tool and send it to the compute cluster.
Don't be afraid to test different settings in parallel. Galaxy can handle multiple runs of the same tool."
position: "right"
postclick:
- '#execute'
@@ -116,88 +99,72 @@ steps:
intro: "This is your Galaxy history! All analysis steps with results of the tools and their parameters will be recorded here and can be redone at any time.<br>
A running tool is shown in yellow and if your calculation is done, it turns green. If your dataset is
grey this means you are queued and need to wait until your tool can be started. If your dataset turns into red, an error has occurred. Please report the bug to the Galaxy team with the bug report button."
position: "left"
- title: "View dataset"
element: "#current-history-panel div.content-item button[title='Display']"
intro: "View your dataset by clicking the eye button."
position: "left"
- title: "Rename dataset"
element: "#current-history-panel div.content-item button[title='Edit attributes']"
intro: "Rename your dataset by clicking the pencil button."
position: "left"
- title: "Dataset information"
element: "div.content-item .content-title"
intro: "This is your dataset. You can get more informations and options like different visualizations by clicking on it."
position: "left"
postclick:
- "div.content-item .content-title"
- title: "Remove dataset"
element: "#current-history-panel div.content-item button[title='Delete']"
intro: "Delete your dataset by clicking the trash-button."
position: "left"
- title: "Dataset information"
element: "#current-history-panel div.content-item button[title='Dataset Details']"
intro: "Clicking on your dataset provides you with more information regarding your dataset (e.g. filetype or size)."
position: "left"
preclick:
- "#current-history-panel div.content-item button[title='Dataset Details']"
- title: "Re-run tool"
element: "#current-history-panel div.content-item button[title='Run Job Again']"
intro: "By clicking the reload button, you can re-run your tool again (e.g. with different parameters or on another dataset)."
position: "left"
- title: "Panel collapse"
element: "#left > div.unified-panel-footer > div.panel-collapse.left"
intro: "To extend the view for your main Galaxy page in the middle, you can collapse the tool panel on the left hand side. Clicking the panel arrow on the right hand side, collapses the history."
position: "top"
postclick:
- "#left > div.unified-panel-footer > div.panel-collapse.left"
- title: 'Top panel'
element: "#masthead"
intro: "The top panel will give you access to a lot of useful things."
position: "bottom"
- title: 'Analyze data'
element: "#analysis"
intro: "This is the current view. Start your <b>analysis</b> from here."
position: "bottom"
intro: "This is the current view. Start your <b>Analysis</b> from here."
- title: 'Workflow'
element: "#workflow"
intro: "Create, manage, import, export and share your <b>Workflows</b>."
position: "bottom"
- title: 'Shared data'
element: "#shared"
intro: "Get access to all <b>Workflows</b>, <b>Histories</b>, <b>Pages</b>, <b>Visualizations</b> and your <b>Data Library</b> that are shared with you."
position: "bottom"
- title: 'Visualization'
element: "#visualization"
intro: "Do you want to start to visualize your data in Trackster? Start here!<br>Galaxy offers a lot more visualizations!"
position: "bottom"
intro: "Galaxy offers a lot of visualizations to review your data!"
- title: 'Help'
element: "#help"
intro: "Here you get more help from the Galaxy <b>Community</b> with Videos, a QA board and more tours."
position: "bottom"
- title: 'User Preferences'
element: "#user"
intro: "Login and start your analysis!"
position: "bottom"
- title: 'Scratchbook'
element: "#enable-scratchbook"
intro: "Scratchbook offers you multiple visualizations next to each other. Have a look at our Scratchbook tour to learn more about it."
position: "bottom"
- title: "Enjoy Galaxy"
intro: "Thanks for taking this tour! Happy research with Galaxy!"
+1 -18
View File
@@ -13,26 +13,22 @@ steps:
intro: |
At first we upload some data into your Galaxy history.<br>
Use this button to upload your data.
position: "right"
postclick:
- ".upload-button"
- title: "Upload your data"
element: "#btn-local"
intro: "You can upload data from your computer."
position: "top"
postclick:
- "#btn-new"
- title: "Upload your data"
element: "#btn-new"
intro: "Copy and paste data directly into Galaxy or include URLs that lead to your data."
position: "top"
- title: "Insert URLs"
element: ".upload-text-content"
intro: "URLs separated by a line break are automatically downloaded by Galaxy."
position: "bottom"
textinsert: |
https://raw.githubusercontent.com/galaxyproject/galaxy/dev/test-data/1.fasta
https://raw.githubusercontent.com/bgruening/galaxytools/adf077b912ddebd97b07b947b855cdd2862ed8ef/tools/statistics/test-data/anderson.tabular
@@ -40,7 +36,6 @@ steps:
- title: "Start the upload"
element: "#btn-start"
intro: "Upload the data into your Galaxy <b>History</b>."
position: "top"
postclick:
- "#btn-start"
- "#btn-close"
@@ -48,50 +43,42 @@ steps:
- title: "History"
element: "#right"
intro: "This is your history! All analysis steps will be recorded and can be redone at any time."
position: "left"
- title: "Dataset information"
element: "div.content-item .content-title"
intro: "This is one of your uploaded datasets. You can get more informations and options like different visualizations by clicking on it."
position: "left"
postclick:
- "div.content-item .content-title"
- title: "Metadata"
element: "#current-history-panel div.content-item span.datatype"
intro: "Galaxy has assigned a datatype to your dataset during upload, which you can see here."
position: "left"
- element: "#current-history-panel div.content-item button[title='Download']"
title: "Download your dataset"
intro: "You can download every dataset by using the floppy disc symbol."
position: "left"
- element: "#current-history-panel div.content-item button[title='Dataset Details']"
title: "Even more information"
intro: "Get an overview of all metadata associated with your dataset by using the Information symbol."
position: "left"
preclick:
- "#current-history-panel div.content-item button[title='Dataset Details']"
- element: "#current-history-panel div.content-item button[title='Display']"
title: "Inspect your data"
intro: "The eye symbol can be used to look at your data."
position: "left"
preclick:
- "#current-history-panel div.content-item button[title='Display']"
- element: "#current-history-panel div.content-item button[title='Edit attributes']"
title: "Edit metadata"
intro: "With the pencil button you can edit metadata attributes of your dataset, like the associated filetype or the dataset name."
position: "left"
preclick:
- "#current-history-panel div.content-item button[title='Edit attributes']"
- element: "#current-history-panel div.content-item button[title='Delete']"
title: "Remove datasets"
intro: "You can remove a dataset from the history with the cross symbol."
position: "left"
postclick:
- "#current-history-panel div.content-item button[title='Delete']"
@@ -99,27 +86,23 @@ steps:
title: "Filter your datasets"
intro: "By default your history will hide all deleted datasets from you. You can visualize them by filtering."
textinsert: "deleted=true"
position: "bottom"
- element: "#current-history-panel div.content-item button[title='Undelete']"
title: "Undeleting a dataset"
intro: |
Galaxy datasets are only marked as deleted and can be recovered by clicking this link.
Please note that datasets marked as deleted can be purged by your administrator at any time.
position: "bottom"
postclick:
- "#current-history-panel div.content-item button[title='Undelete']"
- element: "#current-history-panel [data-description='filter text input']"
title: "Search your History"
intro: "You can filter your history by typing your search term in here. Galaxy supports more advanced filters that can be seen here."
position: "left"
textinsert: "WWFSMD"
textinsert: ""
- element: "#current-history-panel [title='Show history options']"
title: "History Options"
intro: "In the History menu you will find a lot more useful History options."
position: "left"
postclick:
- "#current-history-panel [title='Show history options']"
+1 -11
View File
@@ -11,19 +11,16 @@ steps:
- element: "#tool-panel-upload-button"
intro: "Before using the Scratchbook, we will upload some tabular data."
position: "right"
postclick:
- "#tool-panel-upload-button"
- element: "#btn-new"
intro: "We will be using the paste feature to create a new dataset."
position: "top"
postclick:
- "#btn-new"
- element: ".upload-text-content"
intro: "...and paste content into the text area field."
position: "top"
textinsert: |
1 0.039 0.000
2 0.455 0.000
@@ -36,47 +33,40 @@ steps:
- element: ".upload-settings"
intro: "Now, we may further configure the upload content."
position: "right"
postclick:
- ".upload-settings"
- element: ".upload-space_to_tab"
intro: "...by specifying that all spaces in our table should be converted into tabs."
position: "left"
postclick:
- ".upload-space_to_tab"
- ".popover-close"
- element: "#btn-start"
intro: "Upload the data into your Galaxy history."
position: "top"
postclick:
- "#btn-start"
- element: "#btn-close"
intro: "Hit the close button to close the upload dialog."
position: "top"
postclick:
- "#btn-close"
- element: "#enable-scratchbook > .nav-link"
intro: "Clicking this button will enable Galaxy's Scratchbook mode."
position: "right"
postclick:
- "#enable-scratchbook > .nav-link"
- element: "#right"
intro: "This is your history. It contains all datasets you are currently working with including our uploaded table."
position: "left"
- element: "#current-history-panel div.content-item button[title='Display']"
intro: "Clicking the eye-icon usually displays a dataset in the center panel."
position: "left"
postclick:
- "#current-history-panel div.content-item button[title='Display']"
- element: "#frame-0"
intro: "However while in Scratchbook mode, the dataset will be shown as resizable window."
position: "right"
- title: "Done."
intro: "You have created a Scratchbook view. Click on the background and select more datasets."
@@ -12,13 +12,11 @@ class TourGenerator:
def __init__(self, trans, tool_id, tool_version):
self._trans = trans
self._tool = self._trans.app.toolbox.get_tool(tool_id, tool_version)
self._use_datasets = True
self._data_inputs = {}
self._tour = {}
self._hids = {}
self._errors = []
self._upload_test_data()
self._generate_tour()
@@ -29,26 +27,21 @@ class TourGenerator:
"""
if not self._tool.tests:
raise ValueError("Tests are not defined.")
self._test = self._tool.tests[0]
# All inputs with the type 'data'
self._data_inputs = {x.name: x for x in self._tool.input_params if x.type == "data"}
# Datasets from the <test></test> section
test_datasets = {
input_name: self._test.inputs[input_name][0]
for input_name in self._data_inputs.keys()
if input_name in self._test.inputs.keys()
}
# Conditional datasets
for name in self._test.inputs.keys():
if "|" in name:
input_name = name.split("|")[1]
if input_name in self._data_inputs.keys():
test_datasets.update({input_name: self._test.inputs[name][0]})
if not test_datasets.keys():
not_supported_input_types = [
k for k, v in self._tool.inputs.items() if v.type == "repeat" or v.type == "data_collection"
@@ -60,7 +53,6 @@ class TourGenerator:
# so we can generate a tour without them
self._use_datasets = False
return
# Upload all test datasets
for input_name, input in self._data_inputs.items():
if input_name in test_datasets.keys():
@@ -68,10 +60,8 @@ class TourGenerator:
input_path = self._tool.test_data_path(filename)
if not input_path:
raise ValueError('Test dataset "%s" doesn\'t exist.' % input_name)
upload_tool = self._trans.app.toolbox.get_tool("upload1")
filename = os.path.basename(input_path)
with open(input_path, "rb") as f:
content = f.read()
headers = {
@@ -79,11 +69,9 @@ class TourGenerator:
"files_0|file_data", filename
),
}
input_file = cgi_FieldStorage(headers=headers)
input_file.file = input_file.make_file()
input_file.file.write(content)
inputs = {
"dbkey": "?", # is it always a question mark?
"file_type": input.extensions[0],
@@ -92,11 +80,9 @@ class TourGenerator:
"files_0|to_posix_lines": "Yes",
"files_0|file_data": input_file,
}
params = Params(inputs, sanitize=False)
incoming = params.__dict__
output = upload_tool.handle_input(self._trans, incoming, history=None)
job_errors = output.get("job_errors", [])
if job_errors:
# self._errors.extend(job_errors)
@@ -108,7 +94,6 @@ class TourGenerator:
"""Generate a tour."""
tour_name = self._tool.name + " Tour"
test_inputs = self._test.inputs.keys()
steps = [
{
"title": tour_name,
@@ -116,36 +101,34 @@ class TourGenerator:
"orphan": True,
}
]
for name, input in self._tool.inputs.items():
cond_case_steps = []
if input.type == "repeat":
continue
step = {"title": input.label, "element": "[tour_id=%s]" % name, "placement": "right", "content": ""}
step = {
"title": input.label,
"element": "[id='form-element-%s']" % name,
"placement": "right",
"content": "",
}
if input.type == "text":
if name in test_inputs:
param = self._test.inputs[name]
step["content"] = "Enter value(s): <b>%s</b>" % param
else:
step["content"] = "Enter a value"
elif input.type == "integer" or input.type == "float":
if name in test_inputs:
num_param = self._test.inputs[name][0]
step["content"] = "Enter number: <b>%s</b>" % num_param
else:
step["content"] = "Enter a number"
elif input.type == "boolean":
if name in test_inputs:
choice = "Yes" if self._test.inputs[name][0] is True else "No"
step["content"] = "Choose <b>%s</b>" % choice
else:
step["content"] = "Choose Yes/No"
elif input.type == "select":
params = []
if name in test_inputs:
@@ -158,7 +141,6 @@ class TourGenerator:
step["content"] = "Select parameter(s): <b>%s</b>" % select_param
else:
step["content"] = "Select a parameter"
elif input.type == "data":
if name in test_inputs:
hid = self._hids[name]
@@ -166,26 +148,22 @@ class TourGenerator:
step["content"] = f"Select dataset: <b>{hid}: {dataset}</b>"
else:
step["content"] = "Select a dataset"
elif input.type == "conditional":
param_id = f"{input.name}|{input.test_param.name}"
step["title"] = input.test_param.label
step["element"] = '[tour_id="%s"]' % param_id
step["element"] = "[id='form-element-%s']" % param_id
params = []
if param_id in self._test.inputs.keys():
for option in input.test_param.static_options:
for test_option in self._test.inputs[param_id]:
if test_option == option[1]:
params.append(option[0])
# Conditional param cases
cases = {}
for case in input.cases:
for key, value in case.inputs.items():
if key not in cases.keys():
cases[key] = value.label
for case_id, case_title in cases.items():
tour_id = f"{input.name}|{case_id}"
if tour_id in self._test.inputs.keys():
@@ -199,32 +177,27 @@ class TourGenerator:
cond_case_steps.append(
{
"title": case_title,
"element": '[tour_id="%s"]' % tour_id,
"element": "[id='form-element-%s']" % tour_id,
"placement": "right",
"content": step_msg,
}
)
if params:
cond_param = ", ".join(params)
step["content"] = "Select parameter(s): <b>%s</b>" % cond_param
else:
step["content"] = "Select a parameter"
elif input.type == "data_column":
if name in test_inputs:
column_param = self._test.inputs[name][0]
step["content"] = "Select <b>Column: %s</b>" % column_param
else:
step["content"] = "Select a column"
else:
step["content"] = "Select a parameter"
steps.append(step)
if cond_case_steps:
steps.extend(cond_case_steps) # add conditional input steps
# Add the last step
steps.append(
{
@@ -232,10 +205,8 @@ class TourGenerator:
"content": "Click <b>Execute</b> button to run the tool.",
"element": "#execute",
"placement": "bottom",
# 'postclick': ['#execute']
}
)
self._tour = {
"title_default": tour_name,
"name": tour_name,
@@ -254,21 +225,16 @@ class TourGenerator:
def main(trans, webhook, params):
error = ""
data = {}
try:
if not params or "tool_id" not in params.keys():
raise KeyError("Tool id is missing.")
if not params or "tool_version" not in params.keys():
raise KeyError("Tool version is missing.")
tool_id = params["tool_id"]
tool_version = params["tool_version"]
tour_generator = TourGenerator(trans, tool_id, tool_version)
data = tour_generator.get_data()
except Exception as e:
error = str(e)
log.exception(e)
return {"success": not error, "error": error, "data": data}
File diff suppressed because one or more lines are too long
@@ -50,8 +50,7 @@ window.TourGenerator = Backbone.View.extend({
}
},
_generateTour: function (data) {
const Galaxy = window.bundleEntries.getGalaxyInstance();
const tour = Galaxy.giveTourWithData(data);
const tour = window.bundleEntries.runTour("auto.generated", data);
// Force ending the tour when pressing the Execute button
$("#execute").on("mousedown", () => {
if (tour) {
+5 -7
View File
@@ -658,13 +658,11 @@ invocations:
tour:
popover:
selectors:
_: '.popover'
title: '${_} .popover-header'
content: '${_} .popover-body'
next: '${_} button[data-role="next"]'
previous: '${_} button[data-role="prev"]'
end: '${_} button[data-role="end"]'
_: '.tour-element'
title: '${_} .tour-title'
content: '${_} .tour-content'
next: '${_} .tour-next'
end: '${_} .tour-end'
admin: