diff --git a/client/package.json b/client/package.json index 206ba9df25b..42e72995779 100644 --- a/client/package.json +++ b/client/package.json @@ -92,6 +92,7 @@ "vuex": "^3.6.2", "vuex-cache": "^3.4.0", "vuex-persist": "2.3", + "winbox": "^0.2.1", "xml-beautifier": "^0.5.0" }, "scripts": { diff --git a/client/src/components/History/Content/ContentItem.vue b/client/src/components/History/Content/ContentItem.vue index dd18db8e7b5..76704a5401a 100644 --- a/client/src/components/History/Content/ContentItem.vue +++ b/client/src/components/History/Content/ContentItem.vue @@ -65,6 +65,7 @@ diff --git a/client/src/components/Masthead/MastheadItem.vue b/client/src/components/Masthead/MastheadItem.vue index 848fd9a0e90..ec93a4b5218 100644 --- a/client/src/components/Masthead/MastheadItem.vue +++ b/client/src/components/Masthead/MastheadItem.vue @@ -148,9 +148,6 @@ export default { propogateClick(tab, event) { event.preventDefault(); tab.onclick(); - if (tab.id === "enable-scratchbook") { - this.$emit("updateScratchbookTab", tab); - } }, formatUrl(url) { if (typeof url === "string" && url.indexOf("//") === -1 && url.charAt(0) != "/") { diff --git a/client/src/components/plugins/legacyNavigation.js b/client/src/components/plugins/legacyNavigation.js index 0efac6066b1..516d20d438b 100644 --- a/client/src/components/plugins/legacyNavigation.js +++ b/client/src/components/plugins/legacyNavigation.js @@ -17,13 +17,13 @@ export function backboneRoute(path, ...args) { } } -// wrapper for scratchbook/multiview feature +// wrapper for window manager export function iframeAdd({ path, title = "Galaxy", tryIframe = true }) { const Galaxy = getGalaxyInstance(); if (Galaxy.frame && Galaxy.frame.active) { Galaxy.frame.add({ - url: prependPath(path), title: _l(title), + url: prependPath(path), }); return true; } else if (tryIframe) { diff --git a/client/src/layout/masthead.js b/client/src/layout/masthead.js index ec5bfffaca9..af36d9de6b0 100644 --- a/client/src/layout/masthead.js +++ b/client/src/layout/masthead.js @@ -1,5 +1,5 @@ import $ from "jquery"; -import Scratchbook from "layout/scratchbook"; +import { WindowManager } from "layout/window-manager"; import QuotaMeter from "mvc/user/user-quotameter"; import { getGalaxyInstance } from "app"; import { getAppRoot } from "onload/loadConfig"; @@ -13,7 +13,7 @@ export class MastheadState { constructor(Galaxy = null) { Galaxy = Galaxy || getGalaxyInstance(); - Galaxy.frame = this.frame = new Scratchbook(); + Galaxy.frame = this.windowManager = new WindowManager(); // set up the quota meter (And fetch the current user data from trans) // add quota meter to masthead @@ -35,7 +35,7 @@ export class MastheadState { } }) .on("beforeunload", () => { - const text = this.frame.beforeUnload(); + const text = this.windowManager.beforeUnload(); if (text) { return text; } diff --git a/client/src/layout/menu.js b/client/src/layout/menu.js index 0ed3a76019c..c186a6b8eb8 100644 --- a/client/src/layout/menu.js +++ b/client/src/layout/menu.js @@ -239,7 +239,6 @@ export function fetchMenu(options = {}) { cls: "loggedout-only", tooltip: _l("Login"), url: "login", - noscratchbook: true, }; } } else { diff --git a/client/src/layout/page.js b/client/src/layout/page.js index 4c9f72ccfb5..5897bcd3763 100644 --- a/client/src/layout/page.js +++ b/client/src/layout/page.js @@ -62,7 +62,6 @@ const View = Backbone.View.extend({ this.masthead = mountMasthead(this.$masthead[0], this.config, mastheadState); } this.$center.append(this.center.$el); - this.$el.append(mastheadState.frame.$el); this.$el.append(this.modal.$el); // build panels diff --git a/client/src/layout/scratchbook.js b/client/src/layout/scratchbook.js deleted file mode 100644 index bdbc9534b6b..00000000000 --- a/client/src/layout/scratchbook.js +++ /dev/null @@ -1,247 +0,0 @@ -/** Frame manager uses the ui-frames to create the scratch book masthead icon and functionality **/ -import _ from "underscore"; -import $ from "jquery"; -import Backbone from "backbone"; -import { getAppRoot } from "onload/loadConfig"; -import { getGalaxyInstance } from "app"; -import Frames from "mvc/ui/ui-frames"; -import { Dataset, createTabularDatasetChunkedView, TabularDataset } from "mvc/dataset/data"; -import visualization from "viz/visualization"; -import { TracksterUI } from "viz/trackster"; -import _l from "utils/localization"; - -export default Backbone.View.extend({ - initialize: function (options) { - options = options || {}; - this.frames = new Frames.View({ visible: false }); - this.setElement(this.frames.$el); - this.active = false; - this.buttonActive = { - id: "enable-scratchbook", - icon: "fa-th", - tooltip: _l("Enable/Disable Scratchbook"), - toggle: false, - onclick: () => { - this.active = !this.active; - this.buttonActive.toggle = this.active; - this.buttonActive.show_note = this.active; - this.buttonActive.note_cls = this.active && "fa fa-check"; - if (!this.active) { - this.frames.hide(); - } - }, - }; - this.buttonLoad = { - id: "show-scratchbook", - icon: "fa-eye", - tooltip: _l("Show/Hide Scratchbook"), - show_note: true, - visible: false, - note: "", - onclick: (e) => { - if (this.frames.visible) { - this.frames.hide(); - } else { - this.frames.show(); - } - }, - }; - this.history_cache = {}; - }, - - getFrames() { - // needed for Vue.js integration - return this.frames; - }, - - beforeUnload() { - let confirmText = ""; - if (this.frames.length() > 0) { - confirmText = `You opened ${this.frames.length()} frame(s) which will be lost.`; - } - return confirmText; - }, - - /** Add a dataset to the frames */ - addDataset: function (dataset_id) { - const self = this; - let current_dataset = null; - const Galaxy = getGalaxyInstance(); - if (Galaxy && Galaxy.currHistoryPanel) { - const history_id = Galaxy.currHistoryPanel.model.id; - this.history_cache[history_id] = { - name: Galaxy.currHistoryPanel.model.get("name"), - dataset_ids: [], - }; - Galaxy.currHistoryPanel.collection.each((model) => { - if (!model.get("deleted") && model.get("visible") && model.get("history_content_type") == "dataset") { - self.history_cache[history_id].dataset_ids.push(model.get("id")); - } - }); - } - const _findDataset = (dataset, offset) => { - if (dataset) { - const history_details = self.history_cache[dataset.get("history_id")]; - if (history_details && history_details.dataset_ids) { - const dataset_list = history_details.dataset_ids; - const pos = dataset_list.indexOf(dataset.get("id")); - if (pos !== -1 && pos + offset >= 0 && pos + offset < dataset_list.length) { - return dataset_list[pos + offset]; - } - } - } - }; - const _loadOffset = (dataset, offset, frame) => { - const new_dataset_id = _findDataset(dataset, offset); - if (new_dataset_id) { - self._loadDataset(new_dataset_id, (new_dataset, config) => { - current_dataset = new_dataset; - frame.model.set(config); - }); - } - }; - this._loadDataset(dataset_id, (dataset, config) => { - current_dataset = dataset; - self.add( - _.extend( - { - menu: [ - { - icon: "fa fa-chevron-circle-left", - tooltip: _l("Previous in History"), - onclick: function (frame) { - _loadOffset(current_dataset, -1, frame); - }, - disabled: function () { - return !_findDataset(current_dataset, -1); - }, - }, - { - icon: "fa fa-chevron-circle-right", - tooltip: _l("Next in History"), - onclick: function (frame) { - _loadOffset(current_dataset, 1, frame); - }, - disabled: function () { - return !_findDataset(current_dataset, 1); - }, - }, - ], - }, - config - ) - ); - }); - }, - - _loadDataset: function (dataset_id, callback) { - const self = this; - const dataset = new Dataset({ id: dataset_id }); - $.when(dataset.fetch()).then(() => { - const is_tabular = _.find( - ["tabular", "interval"], - (data_type) => dataset.get("data_type").indexOf(data_type) !== -1 - ); - let title = dataset.get("name"); - const history_details = self.history_cache[dataset.get("history_id")]; - if (history_details) { - title = `${history_details.name}: ${title}`; - } - callback( - dataset, - is_tabular - ? { - title: title, - url: null, - content: createTabularDatasetChunkedView({ - model: new TabularDataset(dataset.toJSON()), - embedded: true, - height: "100%", - }).$el, - } - : { - title: title, - url: `${getAppRoot()}datasets/${dataset_id}/display/?preview=True`, - content: null, - } - ); - }); - }, - - /** Add a trackster visualization to the frames. */ - addTrackster: function (viz_id) { - const self = this; - const viz = new visualization.Visualization({ id: viz_id }); - $.when(viz.fetch()).then(() => { - const ui = new TracksterUI(getAppRoot()); - - // Construct frame config based on dataset's type. - const frame_config = { - title: viz.get("name"), - type: "other", - content: function (parent_elt) { - // Create view config. - const view_config = { - container: parent_elt, - name: viz.get("title"), - id: viz.id, - // FIXME: this will not work with custom builds b/c the dbkey needed to be encoded. - dbkey: viz.get("dbkey"), - stand_alone: false, - }; - - const latest_revision = viz.get("latest_revision"); - const drawables = latest_revision.config.view.drawables; - - // Set up datasets in drawables. - _.each(drawables, (d) => { - d.dataset = { - hda_ldda: d.hda_ldda, - id: d.dataset_id, - }; - }); - ui.create_visualization( - view_config, - latest_revision.config.viewport, - latest_revision.config.view.drawables, - latest_revision.config.bookmarks, - false - ); - }, - }; - self.add(frame_config); - }); - }, - - /** Add and display a new frame/window based on options. */ - add: function (options) { - if (options.target == "_blank") { - window.open(options.url); - } else if (options.target == "_top" || options.target == "_parent" || options.target == "_self") { - window.location = options.url; - } else if (!this.active || options.noscratchbook) { - const $galaxy_main = $(window.parent.document).find("#galaxy_main"); - if (options.target == "galaxy_main" || options.target == "center") { - if ($galaxy_main.length === 0) { - window.location = this._build_url(options.url, { use_panels: true }); - } else { - $galaxy_main.attr("src", options.url); - } - } else { - window.location = options.url; - } - } else { - options.url = this._build_url(options.url, { hide_panels: true, hide_masthead: true }); - this.frames.add(options); - } - }, - - /** Url helper */ - _build_url: function (url, options) { - if (url) { - url += url.indexOf("?") == -1 ? "?" : "&"; - url += $.param(options, true); - return url; - } - }, -}); diff --git a/client/src/layout/window-manager.js b/client/src/layout/window-manager.js new file mode 100644 index 00000000000..99befc0a057 --- /dev/null +++ b/client/src/layout/window-manager.js @@ -0,0 +1,74 @@ +/** Adds window manager masthead icon and functionality **/ +import _l from "utils/localization"; +import WinBox from "winbox/src/js/winbox.js"; +import "winbox/dist/css/winbox.min.css"; + +export class WindowManager { + constructor(options) { + options = options || {}; + this.counter = 0; + this.active = false; + this.buttonActive = { + id: "enable-window-manager", + icon: "fa-th", + tooltip: _l("Enable/Disable Window Manager"), + toggle: false, + onclick: () => { + this.active = !this.active; + this.buttonActive.toggle = this.active; + this.buttonActive.show_note = this.active; + this.buttonActive.note_cls = this.active && "fa fa-check"; + }, + }; + } + + /** Add and display a new window based on options. */ + add(options) { + if (options.target == "_blank") { + window.open(options.url); + } else if (options.target == "_top" || options.target == "_parent" || options.target == "_self") { + window.location = options.url; + } else if (!this.active) { + const $galaxy_main = window.parent.document.getElementById("galaxy_main"); + if (options.target == "galaxy_main" || options.target == "center") { + if ($galaxy_main.length === 0) { + window.location = this._build_url(options.url, { use_panels: true }); + } else { + $galaxy_main.attr("src", options.url); + } + } else { + window.location = options.url; + } + } else { + this.counter++; + const url = this._build_url(options.url, { hide_panels: true, hide_masthead: true }); + WinBox.new({ + title: options.title || "Window", + url: url, + onclose: () => { + this.counter--; + }, + }); + } + } + + /** Called before closing all windows. */ + beforeUnload() { + let confirmText = ""; + if (this.counter > 0) { + confirmText = `You opened ${this.counter} window(s) which will be lost.`; + } + return confirmText; + } + + /** Url helper */ + _build_url(url, options) { + if (url) { + url += url.indexOf("?") == -1 ? "?" : "&"; + Object.entries(options).forEach(([key, value]) => { + url += `${key}=${value}&`; + }); + return url; + } + } +} diff --git a/client/src/mvc/dataset/dataset-li.js b/client/src/mvc/dataset/dataset-li.js index b2c0afcaf1a..7f51996127e 100644 --- a/client/src/mvc/dataset/dataset-li.js +++ b/client/src/mvc/dataset/dataset-li.js @@ -9,6 +9,7 @@ import BASE_MVC from "mvc/base-mvc"; import _l from "utils/localization"; import { mountNametags } from "components/Nametags"; import { Toast } from "ui/toast"; +import { getAppRoot } from "onload/loadConfig"; var logNamespace = "dataset"; /*============================================================================== @@ -187,7 +188,8 @@ export var DatasetListItemView = _super.extend( if (Galaxy.frame && Galaxy.frame.active) { // Add dataset to frames. const identifier = self.model.get("element_id") || self.model.get("id"); - Galaxy.frame.addDataset(identifier); + const url = `${getAppRoot()}datasets/${identifier}/display/?preview=True`; + Galaxy.frame.add({ url }); ev.preventDefault(); } }; diff --git a/client/src/mvc/ui/ui-frames.js b/client/src/mvc/ui/ui-frames.js deleted file mode 100644 index 79ee255792c..00000000000 --- a/client/src/mvc/ui/ui-frames.js +++ /dev/null @@ -1,648 +0,0 @@ -/** Frame view */ -import _ from "underscore"; -import $ from "jquery"; -import Backbone from "backbone"; -import _l from "utils/localization"; -import { getGalaxyInstance } from "app"; - -var FrameView = Backbone.View.extend({ - initialize: function (options) { - this.model = (options && options.model) || new Backbone.Model(options); - this.setElement($("
").addClass("corner frame")); - this.$el - .append( - $("
") - .addClass("f-header corner") - .append($("
").addClass("f-title")) - .append( - $("
") - .addClass("f-icon f-close fa fa-close") - .tooltip({ - title: _l("Close"), - placement: "bottom", - }) - ) - ) - .append($("
").addClass("f-content")) - .append($("
").addClass("f-resize f-icon corner fa fa-expand").tooltip({ title: "Resize" })) - .append($("
").addClass("f-cover")); - this.$header = this.$(".f-header"); - this.$title = this.$(".f-title"); - this.$content = this.$(".f-content"); - this.render(); - this.listenTo(this.model, "change", this.render, this); - }, - - render: function () { - var self = this; - var options = this.model.attributes; - this.$title.html(_.escape(options.title) || ""); - this.$header.find(".f-icon-left").remove(); - _.each(options.menu, (option) => { - var $option = $("
").addClass("f-icon-left").addClass(option.icon); - if (_.isFunction(option.disabled) && option.disabled()) { - $option.attr("disabled", true); - } else { - $option - .on("click", () => { - option.onclick(self); - }) - .tooltip({ - title: option.tooltip || "", - placement: "bottom", - }); - } - self.$header.append($option); - }); - if (options.url) { - this.$content.html( - $("