diff --git a/src/sites/iva/iva-app.js b/src/sites/iva/iva-app.js index d6f222e7f9..a4c941b495 100644 --- a/src/sites/iva/iva-app.js +++ b/src/sites/iva/iva-app.js @@ -1268,6 +1268,7 @@ class IvaApp extends LitElement { content = html` @@ -1291,6 +1292,7 @@ class IvaApp extends LitElement { content = html` `; @@ -1299,6 +1301,7 @@ class IvaApp extends LitElement { content = html` @@ -1308,6 +1311,7 @@ class IvaApp extends LitElement { content = html` `; @@ -1344,7 +1348,8 @@ class IvaApp extends LitElement { case "analysis-tools": content = html` + .opencgaSession="${this.opencgaSession}" + .tool="${this.queries[this.tool]?.tool}"> `; break; diff --git a/src/webcomponents/commons/analysis/analysis-tools.js b/src/webcomponents/commons/analysis/analysis-tools.js index c8b69c9832..ef1a1ba0b0 100644 --- a/src/webcomponents/commons/analysis/analysis-tools.js +++ b/src/webcomponents/commons/analysis/analysis-tools.js @@ -1,4 +1,5 @@ -import {LitElement, html, nothing, render} from "lit"; +import {LitElement, html} from "lit"; +import WebUtils from "../utils/web-utils.js"; import "../tool-header.js"; import "../view/vertical-menu.js"; import "../../clinical/analysis/mutational-signature-analysis.js"; @@ -42,6 +43,9 @@ export default class AnalysisTools extends LitElement { opencgaSession: { type: Object, }, + tool: { + type: String, + }, }; } @@ -49,12 +53,21 @@ export default class AnalysisTools extends LitElement { this._config = this.getDefaultConfig(); } + onChangeActiveItem(event) { + const [app, tool] = WebUtils.getApplicationAndToolFromHash(); + WebUtils.redirectTo(this.opencgaSession, app, tool, { + tool: event.detail.value, + }); + } + render() { return html` + .activeItem="${this.tool}" + .config="${this._config || {}}" + @changeActiveItem="${event => this.onChangeActiveItem(event)}"> `; } diff --git a/src/webcomponents/commons/filters-toolbar.js b/src/webcomponents/commons/filters-toolbar.js index a3ad6cfcb8..a9af2c2ebc 100644 --- a/src/webcomponents/commons/filters-toolbar.js +++ b/src/webcomponents/commons/filters-toolbar.js @@ -439,7 +439,8 @@ export default class FiltersToolbar extends LitElement { onCopyLink() { // 1. Generate the url to the tool with the current query - const link = WebUtils.getIVALink(this.opencgaSession, this.toolId, this.preparedQuery); + const [app, tool] = WebUtils.getApplicationAndToolFromHash(); + const link = WebUtils.getIVALink(this.opencgaSession, app, tool, this.preparedQuery); // 2. Copy this link to the user clipboard UtilsNew.copyToClipboard(link); diff --git a/src/webcomponents/commons/opencga-active-filters.js b/src/webcomponents/commons/opencga-active-filters.js index 14c02caf9d..9f004ddf48 100644 --- a/src/webcomponents/commons/opencga-active-filters.js +++ b/src/webcomponents/commons/opencga-active-filters.js @@ -719,7 +719,8 @@ export default class OpencgaActiveFilters extends LitElement { onCopyLink() { // 1. Generate the url to the tool with the current query - const link = WebUtils.getIVALink(this.opencgaSession, this.toolId, this.query); + const [app, tool] = WebUtils.getApplicationAndToolFromHash(); + const link = WebUtils.getIVALink(this.opencgaSession, app, tool, this.query); // 2. Copy this link to the user clipboard UtilsNew.copyToClipboard(link); // 3. Notify user that the link has been copied to the clipboard diff --git a/src/webcomponents/commons/utils/web-utils.js b/src/webcomponents/commons/utils/web-utils.js index 8f2be9b30b..b241346d70 100644 --- a/src/webcomponents/commons/utils/web-utils.js +++ b/src/webcomponents/commons/utils/web-utils.js @@ -60,26 +60,27 @@ export default class WebUtils { return (resource && mapResourcePermissionId[resource] && mode) ? `${mode.toUpperCase()}_${mapResourcePermissionId[resource]}` : ""; } - static getIVALink(opencgaSession, tool, query = {}) { - const baseUrl = (new URL(window.location.pathname, window.location.origin)); - let queryStr = ""; - // Check if query object has been provided - if (query) { - queryStr = "?" + (new URLSearchParams(query)).toString(); - } - return `${baseUrl}#${tool}/${opencgaSession.project.id}/${opencgaSession.study.id}${queryStr}`; - } - - static getInterpreterLink(opencgaSession, caseId = "") { + static getLink(opencgaSession, app = "", tool = "", query = null) { const hashItems = [ - // ...window.location.hash.replace("#", "").split("/").slice(0, -3), // '#clinical/portal/project/study' --> ['clinical'] - "clinical", - "interpreter", + app, + tool, opencgaSession?.project?.id || "", opencgaSession?.study?.id || "", ]; - return `#${hashItems.filter(Boolean).join("/")}${!!caseId ? "?id=" + caseId : ""}`; + // build the querystring fragment of the URL if a query object is provided + const queryStr = (query && Object.keys(query || {}).length > 0) ? "?" + (new URLSearchParams(query)).toString() : ""; + + // build and return the internal link + return `#${hashItems.filter(Boolean).join("/")}${queryStr}`; + } + + static getIVALink(opencgaSession, app, tool, query = null) { + return (new URL(window.location.pathname, window.location.origin)) + WebUtils.getLink(opencgaSession, app, tool, query); + } + + static getInterpreterLink(opencgaSession, query = null) { + return WebUtils.getLink(opencgaSession, "clinical", "interpreter", query); } static getClinicalAnalysisPriorityColour(rank) { @@ -100,4 +101,13 @@ export default class WebUtils { }); } + static getApplicationAndToolFromHash(hash = "") { + // '#clinical/portal/project/study' --> ['clinical', 'portal] + // '#portal/project/study' --> ['portal'] + return (hash || window.location.hash).replace("#", "").split("/").slice(0, -2); + } + + static redirectTo(opencgaSession, app = "", tool = "", query = {}) { + window.location.hash = WebUtils.getLink(opencgaSession, app, tool, query); + } } diff --git a/src/webcomponents/commons/view/vertical-menu.js b/src/webcomponents/commons/view/vertical-menu.js index ac8ba76732..70930d2aac 100644 --- a/src/webcomponents/commons/view/vertical-menu.js +++ b/src/webcomponents/commons/view/vertical-menu.js @@ -1,5 +1,6 @@ import {LitElement, html, nothing} from "lit"; import UtilsNew from "../../../core/utils-new.js"; +import LitUtils from "../utils/lit-utils.js"; export default class VerticalMenu extends LitElement { @@ -17,6 +18,9 @@ export default class VerticalMenu extends LitElement { opencgaSession: { type: Object, }, + activeItem: { + type: String, + }, config: { type: Object, }, @@ -35,8 +39,15 @@ export default class VerticalMenu extends LitElement { ...this.getDefaultConfig(), ...this.config, }; - // initialize the active item - if (!this._activeItem) { + } + + if (changedProperties.has("config") || changedProperties.has("activeItem")) { + // check if we have to change the active item + if (this.activeItem && this.activeItem !== this._activeItem) { + this._activeItem = this.activeItem; + } + // initialize the active item if not set + if (!this._activeItem && !this.activeItem) { this._activeItem = this._config.menu[0].submenu[0].id; } } @@ -46,6 +57,7 @@ export default class VerticalMenu extends LitElement { onChangeActiveItem(newActiveItem) { this._activeItem = newActiveItem; + LitUtils.dispatchCustomEvent(this, "changeActiveItem", this._activeItem); this.requestUpdate(); } diff --git a/src/webcomponents/organization/admin/organization-admin.js b/src/webcomponents/organization/admin/organization-admin.js index cb19e10d88..8db5fcd3cf 100644 --- a/src/webcomponents/organization/admin/organization-admin.js +++ b/src/webcomponents/organization/admin/organization-admin.js @@ -14,6 +14,7 @@ * limitations under the License. */ import {LitElement, html} from "lit"; +import WebUtils from "../../commons/utils/web-utils.js"; import OpencgaCatalogUtils from "../../../core/clients/opencga/opencga-catalog-utils.js"; import "./group-admin-browser.js"; import "./user-admin-browser.js"; @@ -38,7 +39,10 @@ export default class OrganizationAdmin extends LitElement { static get properties() { return { opencgaSession: { - type: Object + type: Object, + }, + tool: { + type: String, }, }; } @@ -47,6 +51,13 @@ export default class OrganizationAdmin extends LitElement { this._config = this.getDefaultConfig(); } + onChangeActiveItem(event) { + const [app, tool] = WebUtils.getApplicationAndToolFromHash(); + WebUtils.redirectTo(this.opencgaSession, app, tool, { + tool: event.detail.value, + }); + } + render() { if (!this.opencgaSession?.organization || !OpencgaCatalogUtils.isOrganizationAdmin(this.opencgaSession?.organization, this.opencgaSession?.user?.id)) { return html` @@ -60,7 +71,9 @@ export default class OrganizationAdmin extends LitElement { + .activeItem="${this.tool}" + .config="${this._config || {}}" + @changeActiveItem="${event => this.onChangeActiveItem(event)}"> `; } diff --git a/src/webcomponents/study/admin/study-admin-iva.js b/src/webcomponents/study/admin/study-admin-iva.js index 620b1ed7a3..3d761b42bc 100644 --- a/src/webcomponents/study/admin/study-admin-iva.js +++ b/src/webcomponents/study/admin/study-admin-iva.js @@ -15,9 +15,7 @@ */ import {LitElement, html} from "lit"; -import LitUtils from "../../commons/utils/lit-utils"; -import NotificationUtils from "../../commons/utils/notification-utils"; -import UtilsNew from "../../../core/utils-new"; +import WebUtils from "../../commons/utils/web-utils.js"; import OpencgaCatalogUtils from "../../../core/clients/opencga/opencga-catalog-utils"; import "../../commons/view/vertical-menu.js"; import "../../commons/pages/restricted-access-page.js"; @@ -41,6 +39,9 @@ export default class StudyAdminIva extends LitElement { opencgaSession: { type: Object, }, + tool: { + type: String, + }, settings: { type: Object, }, @@ -78,6 +79,13 @@ export default class StudyAdminIva extends LitElement { return (title === "Clinical Analysis Browser") ? "Clinical Analysis Portal" : title; } + onChangeActiveItem(event) { + const [app, tool] = WebUtils.getApplicationAndToolFromHash(); + WebUtils.redirectTo(this.opencgaSession, app, tool, { + tool: event.detail.value, + }); + } + render() { const isOrganizationAdmin = OpencgaCatalogUtils.isOrganizationAdmin(this.opencgaSession?.organization, this.opencgaSession?.user?.id); const isAdmin = OpencgaCatalogUtils.isAdmin(this.opencgaSession?.study, this.opencgaSession?.user?.id); @@ -94,7 +102,9 @@ export default class StudyAdminIva extends LitElement { + .activeItem="${this.tool}" + .config="${this._config || {}}" + @changeActiveItem="${event => this.onChangeActiveItem(event)}"> `; } diff --git a/src/webcomponents/study/admin/study-admin.js b/src/webcomponents/study/admin/study-admin.js index 51f034a2e7..a59add1e4b 100644 --- a/src/webcomponents/study/admin/study-admin.js +++ b/src/webcomponents/study/admin/study-admin.js @@ -15,6 +15,7 @@ */ import {LitElement, html} from "lit"; +import WebUtils from "../../commons/utils/web-utils.js"; import OpencgaCatalogUtils from "../../../core/clients/opencga/opencga-catalog-utils.js"; import "./study-admin-users.js"; import "./study-admin-permissions.js"; @@ -44,6 +45,9 @@ export default class StudyAdmin extends LitElement { opencgaSession: { type: Object, }, + tool: { + type: String, + }, }; } @@ -51,6 +55,13 @@ export default class StudyAdmin extends LitElement { this._config = this.getDefaultConfig(); } + onChangeActiveItem(event) { + const [app, tool] = WebUtils.getApplicationAndToolFromHash(); + WebUtils.redirectTo(this.opencgaSession, app, tool, { + tool: event.detail.value, + }); + } + render() { const isOrganizationAdmin = OpencgaCatalogUtils.isOrganizationAdmin(this.opencgaSession?.organization, this.opencgaSession?.user?.id); const isAdmin = OpencgaCatalogUtils.isAdmin(this.opencgaSession?.study, this.opencgaSession?.user?.id); @@ -67,7 +78,9 @@ export default class StudyAdmin extends LitElement { + .activeItem="${this.tool}" + .config="${this._config || {}}" + @changeActiveItem="${event => this.onChangeActiveItem(event)}"> `; } diff --git a/src/webcomponents/study/admin/variant/operations-admin.js b/src/webcomponents/study/admin/variant/operations-admin.js index ea8742c0f3..d2d0a08994 100644 --- a/src/webcomponents/study/admin/variant/operations-admin.js +++ b/src/webcomponents/study/admin/variant/operations-admin.js @@ -15,6 +15,7 @@ */ import {LitElement, html} from "lit"; +import WebUtils from "../../../commons/utils/web-utils.js"; import OpencgaCatalogUtils from "../../../../core/clients/opencga/opencga-catalog-utils"; import "../../../variant/operation/variant-index-operation.js"; import "../../../variant/operation/variant-stats-index-operation.js"; @@ -40,6 +41,9 @@ export default class OperationsAdmin extends LitElement { opencgaSession: { type: Object }, + tool: { + type: String, + }, }; } @@ -48,6 +52,13 @@ export default class OperationsAdmin extends LitElement { this._config = this.getDefaultConfig(); } + onChangeActiveItem(event) { + const [app, tool] = WebUtils.getApplicationAndToolFromHash(); + WebUtils.redirectTo(this.opencgaSession, app, tool, { + tool: event.detail.value, + }); + } + render() { const isOrganizationAdmin = OpencgaCatalogUtils.isOrganizationAdmin(this.opencgaSession?.organization, this.opencgaSession?.user?.id); const isAdmin = OpencgaCatalogUtils.isAdmin(this.opencgaSession?.study, this.opencgaSession?.user?.id); @@ -64,7 +75,9 @@ export default class OperationsAdmin extends LitElement { + .activeItem="${this.tool}" + .config="${this._config || {}}" + @changeActiveItem="${event => this.onChangeActiveItem(event)}"> `; } diff --git a/src/webcomponents/variant/variant-browser-grid.js b/src/webcomponents/variant/variant-browser-grid.js index 6b43d5f23c..68c9dc3e33 100644 --- a/src/webcomponents/variant/variant-browser-grid.js +++ b/src/webcomponents/variant/variant-browser-grid.js @@ -903,7 +903,8 @@ export default class VariantBrowserGrid extends LitElement { break; case "copy-link": // 1. Generate the URL to this variant - const link = WebUtils.getIVALink(this.opencgaSession, this.toolId, {id: variant.id}); + const [app, tool] = WebUtils.getApplicationAndToolFromHash(); + const link = WebUtils.getIVALink(this.opencgaSession, app, tool, {id: variant.id}); // 2. Copy this link to the clipboard UtilsNew.copyToClipboard(link); // 3. Notify user that link has been copied to the clipboard