diff --git a/src/org/labkey/targetedms/view/qcSummary.jsp b/src/org/labkey/targetedms/view/qcSummary.jsp
index d73f459bc..d4cb41907 100644
--- a/src/org/labkey/targetedms/view/qcSummary.jsp
+++ b/src/org/labkey/targetedms/view/qcSummary.jsp
@@ -24,8 +24,7 @@
{
dependencies.add("Ext4");
dependencies.add("vis/vis");
- dependencies.add("hopscotch/css/hopscotch.min.css");
- dependencies.add("hopscotch/js/hopscotch.min.js");
+ dependencies.add("internal/tippy");
dependencies.add("targetedms/js/BaseQCPlotPanel.js");
dependencies.add("targetedms/css/QCSummary.css");
dependencies.add("targetedms/js/QCSummaryPanel.js");
diff --git a/src/org/labkey/targetedms/view/qcTrendPlotReport.jsp b/src/org/labkey/targetedms/view/qcTrendPlotReport.jsp
index 63172b584..a4d5fc5ec 100644
--- a/src/org/labkey/targetedms/view/qcTrendPlotReport.jsp
+++ b/src/org/labkey/targetedms/view/qcTrendPlotReport.jsp
@@ -30,8 +30,7 @@
dependencies.add("Ext4");
dependencies.add("Ext4ClientApi");
dependencies.add("vis/vis");
- dependencies.add("hopscotch/css/hopscotch.min.css");
- dependencies.add("hopscotch/js/hopscotch.min.js");
+ dependencies.add("internal/tippy");
dependencies.add("targetedms/css/SVGExportIcon.css");
dependencies.add("targetedms/css/qcTrendPlotReport.css");
dependencies.add("targetedms/js/QCPlotHelperBase.js");
@@ -99,24 +98,53 @@
});
}
+ let plotTypeTooltipInstance = null;
+
function createPlotTypeTooltip(tgt, plotType) {
- let calloutMgr = hopscotch.getCalloutManager();
- calloutMgr.removeAllCallouts();
- calloutMgr.createCallout({
- id: Ext4.id(),
- target: tgt,
+ destroyPlotTypeTooltip();
+
+ const title = plotType.trim() + ' Plot Type';
+ const content = getPlotTypeHelpTooltip(plotType.trim());
+
+ plotTypeTooltipInstance = tippy(tgt, {
+ content: '
' +
+ '
' +
+ LABKEY.Utils.encodeHtml(title) + '
' +
+ content + '
',
+ allowHTML: true,
placement: 'top',
- width: 300,
- xOffset: -250,
- arrowOffset: 270,
- showCloseButton: false,
- title: plotType.trim() + ' Plot Type',
- content: this.getPlotTypeHelpTooltip(plotType.trim())
- }, this);
+ maxWidth: 350,
+ showOnCreate: true,
+ trigger: 'manual',
+ hideOnClick: false,
+ offset: [-250, 10],
+ onShow(instance) {
+ const tippyBox = instance.popper.querySelector('.tippy-box');
+ if (tippyBox) {
+ tippyBox.style.border = '5px solid #5d5c5c';
+ tippyBox.style.backgroundColor = 'white';
+ tippyBox.style.borderRadius = '4px';
+ }
+
+ const arrow = instance.popper.querySelector('.tippy-arrow');
+ if (arrow) {
+ arrow.style.color = '#5d5c5c';
+ arrow.style.width = '20px';
+ arrow.style.height = '20px';
+ const arrowBorder = arrow.querySelector('svg');
+ if (arrowBorder) {
+ arrowBorder.style.fill = '#5d5c5c';
+ }
+ }
+ }
+ });
}
function destroyPlotTypeTooltip() {
- hopscotch.getCalloutManager().removeAllCallouts();
+ if (plotTypeTooltipInstance) {
+ plotTypeTooltipInstance.destroy();
+ plotTypeTooltipInstance = null;
+ }
}
function getPlotTypeHelpTooltip(plotTypeName) {
diff --git a/test/src/org/labkey/test/components/targetedms/QCPlotsWebPart.java b/test/src/org/labkey/test/components/targetedms/QCPlotsWebPart.java
index 20b4c2434..2c62d912d 100644
--- a/test/src/org/labkey/test/components/targetedms/QCPlotsWebPart.java
+++ b/test/src/org/labkey/test/components/targetedms/QCPlotsWebPart.java
@@ -85,7 +85,6 @@ private void doAndWaitForUpdate(Runnable action)
{
WebElement plot = waitForPlotPanel();
- closeBubble();
action.run();
getWrapper().shortWait().until(ExpectedConditions.stalenessOf(plot));
@@ -159,7 +158,6 @@ private void setMetricType(MetricType metricType, MetricType currentMetricType,
{
doAndWaitForUpdate(() ->
{
- // scroll to prevent inadvertent hover over QC Summary webpart items that show hopscotch tooltips
getWrapper().scrollIntoView(metricTypeCombo, true);
getWrapper()._ext4Helper.selectComboBoxItem(metricTypeCombo, metricType.toString());
});
@@ -530,10 +528,11 @@ public WebElement openExclusionBubble(String acquiredDate)
WebElement point = getPointByAcquiredDate(acquiredDate);
ScrollUtils.scrollIntoView(point, center, center);
getWrapper().mouseOverWithoutScrolling(point);
- return getWrapper().isElementPresent(Locator.tagWithClass("div", "x4-form-display-field")
- .containing(acquiredDate.substring(0, 16))); // drop seconds part (e.g. "2013-08-12 04:54") for trailing mean/CV
+ return getWrapper().isElementPresent(Locator.tagWithClass("div", "qc-plot-hover-panel")
+ .withDescendant(Locator.tagWithClass("div", "qc-hover-field")
+ .containing(acquiredDate.substring(0, 16)))); // drop seconds part (e.g. "2013-08-12 04:54") for trailing mean/CV
});
- return elementCache().hopscotchBubble.findElement(getDriver());
+ return elementCache().tippyBubble.findElement(getDriver());
}
@LogMethod
@@ -676,21 +675,6 @@ public void checkPlotType(QCPlotType plotType)
}
}
- private void dismissTooltip()
- {
- int halfWidth = elementCache().webPartTitle.getSize().getWidth() / 2;
- int xOffset = elementCache().webPartTitle.getLocation().getX() + halfWidth; // distance to edge of window from center of element
- getWrapper().scrollIntoView(elementCache().webPartTitle);
- new Actions(getDriver())
- .moveToElement(elementCache().webPartTitle) // Start at the center of the title
- .moveByOffset(-xOffset, 0) // Move all the way to the left edge of the window
- .perform(); // Should dismiss hover tooltips
- WebElement closeHopscotch = Locator.byClass("hopscotch-close").findElementOrNull(getDriver());
- if (closeHopscotch != null && closeHopscotch.isDisplayed())
- closeHopscotch.click();
- getWrapper().shortWait().until(ExpectedConditions.invisibilityOfElementLocated(Locator.byClass("hopscotch-callout")));
- }
-
public boolean isPlotTypeSelected(QCPlotType plotType)
{
return getCurrentQCPlotTypes().contains(plotType);
@@ -708,37 +692,24 @@ public void checkAllPlotTypes(boolean selected)
}
}
- public void closeBubble()
- {
- Optional optCloseButton = elementCache().hopscotchBubbleClose.findOptionalElement(getDriver());
- optCloseButton.ifPresent(closeButton -> {
- WebDriverWait wait = new WebDriverWait(getDriver(), Duration.ofSeconds(2));
- wait.until(ExpectedConditions.elementToBeClickable(closeButton)).click();
- wait.until(ExpectedConditions.stalenessOf(closeButton));
- });
- }
-
public void goToPreviousPage()
{
- closeBubble();
getWrapper().doAndWaitForPageToLoad(() -> elementCache().paginationPrevBtn.findElement(this).click());
}
public void goToNextPage()
{
- closeBubble();
getWrapper().doAndWaitForPageToLoad(() -> elementCache().paginationNextBtn.findElement(this).click());
}
public Locator.XPathLocator getBubble()
{
- return Locator.byClass("hopscotch-bubble-container");
+ return Locator.tagWithClass("div", "qc-plot-hover-panel");
}
public Locator.XPathLocator getBubbleContent()
{
- Locator.XPathLocator hopscotchBubble = Locator.byClass("hopscotch-bubble-container");
- return hopscotchBubble.append(Locator.byClass("hopscotch-bubble-content").append(Locator.byClass("hopscotch-content").withText()));
+ return Locator.tagWithClass("div", "qc-plot-hover-panel");
}
public ConfigureMetricsUIPage clickConfigureQCMetrics()
@@ -970,8 +941,7 @@ public class Elements extends BodyWebPart>.ElementCache
Locator.CssLocator paginationPrevBtn = Locator.css(".qc-paging-prev");
Locator.CssLocator paginationNextBtn = Locator.css(".qc-paging-next");
Locator.CssLocator svgBackgrounds = Locator.css("svg g.brush rect.background");
- Locator.XPathLocator hopscotchBubble = Locator.byClass("hopscotch-bubble-container");
- Locator.XPathLocator hopscotchBubbleClose = Locator.byClass("hopscotch-bubble-close");
+ Locator.XPathLocator tippyBubble = Locator.tagWithClass("div", "qc-plot-hover-panel");
List findSeriesPanels()
{
diff --git a/test/src/org/labkey/test/components/targetedms/QCSummaryWebPart.java b/test/src/org/labkey/test/components/targetedms/QCSummaryWebPart.java
index 091625aa0..38130d9ef 100644
--- a/test/src/org/labkey/test/components/targetedms/QCSummaryWebPart.java
+++ b/test/src/org/labkey/test/components/targetedms/QCSummaryWebPart.java
@@ -65,22 +65,17 @@ public UtilizationCalendarWebPart gotoUtilizationCalendar()
public Locator.XPathLocator getBubble()
{
- return Locators.hopscotchBubble;
- }
-
- public void closeBubble()
- {
- getWrapper().click(Locators.hopscotchBubbleClose);
+ return Locators.tippyBubble;
}
public Locator.XPathLocator getBubbleContent()
{
- return Locators.hopscotchBubbleContent;
+ return Locators.tippyBubbleContent;
}
public String getBubbleText()
{
- return Locators.hopscotchBubbleContent.withText().waitForElement(getDriver(), 1000).getText();
+ return Locators.tippyBubbleContent.withText().waitForElement(getDriver(), 1000).getText();
}
public List getQcSummaryTiles()
@@ -130,9 +125,8 @@ public List summaryTiles()
private static abstract class Locators
{
- static final Locator.XPathLocator hopscotchBubble = Locator.byClass("hopscotch-bubble-container");
- static final Locator.XPathLocator hopscotchBubbleContent = hopscotchBubble.append(Locator.byClass("hopscotch-bubble-content").append(Locator.byClass("hopscotch-content")));
- static final Locator.XPathLocator hopscotchBubbleClose = Locator.byClass("hopscotch-bubble-close");
+ static final Locator.XPathLocator tippyBubble = Locator.byClass("tippy-box");
+ static final Locator.XPathLocator tippyBubbleContent = Locator.byClass("tippy-content");
static final Locator summaryTile = Locator.tagWithClass("div", "summary-tile");
static final Locator recentSampleFilesLoading = Locator.tagWithClass("div", "sample-file-details-loading");
static final Locator recentSampleFile = Locator.css("div.sample-file-item");
diff --git a/test/src/org/labkey/test/tests/panoramapremium/TargetedMSiRTMetricsTest.java b/test/src/org/labkey/test/tests/panoramapremium/TargetedMSiRTMetricsTest.java
index d8c80b852..8ac2e2403 100644
--- a/test/src/org/labkey/test/tests/panoramapremium/TargetedMSiRTMetricsTest.java
+++ b/test/src/org/labkey/test/tests/panoramapremium/TargetedMSiRTMetricsTest.java
@@ -94,7 +94,6 @@ public void testFileWithIRTMetricValue()
qcPlotsWebPart.openExclusionBubble(acquiredDate);
String ticAreaHoverText = waitForElement(qcPlotsWebPart.getBubbleContent()).getText();
checker().withScreenshot("IRTCorrelation").verifyTrue("Incorrect iRT Correlation value calculated", ticAreaHoverText.contains("0.999"));
- qcPlotsWebPart.closeBubble();
log("Verifying the iRT Intercept plot values");
acquiredDate = "2014-03-16 05:21:58";
@@ -103,7 +102,6 @@ public void testFileWithIRTMetricValue()
qcPlotsWebPart.openExclusionBubble(acquiredDate);
ticAreaHoverText = waitForElement(qcPlotsWebPart.getBubbleContent()).getText();
checker().withScreenshot("IRTIntercept").verifyTrue("Incorrect iRT Intercept value calculated", ticAreaHoverText.contains("34.2"));
- qcPlotsWebPart.closeBubble();
log("Verifying the iRT Slope plot values");
acquiredDate = "2014-03-17 06:46:14";
@@ -113,7 +111,6 @@ public void testFileWithIRTMetricValue()
waitForElement(qcPlotsWebPart.getBubble());
ticAreaHoverText = waitForElement(qcPlotsWebPart.getBubbleContent()).getText();
checker().withScreenshot("IRTSlope").verifyTrue("Incorrect iRT Slope value calculated", ticAreaHoverText.contains("0.646"));
- qcPlotsWebPart.closeBubble();
mouseOut();
log("Verifying the tooltip area of QC summary web part");
diff --git a/test/src/org/labkey/test/tests/targetedms/TargetedMSQCSummaryTest.java b/test/src/org/labkey/test/tests/targetedms/TargetedMSQCSummaryTest.java
index 3b908238e..f3f87ee71 100644
--- a/test/src/org/labkey/test/tests/targetedms/TargetedMSQCSummaryTest.java
+++ b/test/src/org/labkey/test/tests/targetedms/TargetedMSQCSummaryTest.java
@@ -369,9 +369,8 @@ private void validateSampleFile(int fileDetailIndex, Map fileDet
fail("The bubble text for the file detail not as expected. Bubble text: '" + actualText + "' Missing: '" + perBubbleTexts.stream().filter(s -> !actualText.contains(s)).collect(Collectors.joining(",")) + "'");
}
}
- qcSummaryWebPart.closeBubble();
-
- log("Move the mouse to avoid another hopscotch bubble.");
+
+ log("Move the mouse to avoid another tippy bubble.");
mouseOver(Locator.css(".labkey-page-nav"));
waitForElementToDisappear(qcSummaryWebPart.getBubble());
diff --git a/test/src/org/labkey/test/tests/targetedms/TargetedMSQCTest.java b/test/src/org/labkey/test/tests/targetedms/TargetedMSQCTest.java
index f9016ad3d..10a15d581 100644
--- a/test/src/org/labkey/test/tests/targetedms/TargetedMSQCTest.java
+++ b/test/src/org/labkey/test/tests/targetedms/TargetedMSQCTest.java
@@ -812,8 +812,6 @@ public void testSmallMoleculeQC()
//Check for clickable PDF and PNG export icons for Combined plot
verifyDownloadablePlotIcons(1);
- checkAndCloseTooltip();
-
//deselect "Show All Peptides in Single Plot"
qcPlotsWebPart.setShowAllPeptidesInSinglePlot(false, currentPagePlotCount);
@@ -821,13 +819,6 @@ public void testSmallMoleculeQC()
verifyDownloadablePlotIcons(currentPagePlotCount);
}
- private void checkAndCloseTooltip()
- {
- Locator bubbleClose = Locator.byClass("hopscotch-bubble-close");
- if (isElementPresent(bubbleClose) && isElementVisible(bubbleClose))
- click(bubbleClose);
- }
-
@Test
public void testQCPlotExclusions()
{
@@ -1001,23 +992,26 @@ private void verifyExclusionButtonSelection(String acquiredDate, QCPlotsWebPart.
{
QCPlotsWebPart qcPlotsWebPart = new PanoramaDashboard(this).getQcPlotsWebPart();
WebElement bubble = qcPlotsWebPart.openExclusionBubble(acquiredDate);
- RadioButton radioButton = RadioButton.RadioButton().withLabel(state.getLabel()).find(bubble);
- assertTrue("QC data point exclusion selection not as expected:" + state.getLabel(), radioButton.isChecked());
- qcPlotsWebPart.closeBubble();
+ WebElement radioButton = Locator.tag("input").withAttribute("type", "radio")
+ .withAttribute("name", "exclusion-status")
+ .followingSibling("label").withText(state.getLabel())
+ .precedingSibling("input").findElement(bubble);
+ assertTrue("QC data point exclusion selection not as expected:" + state.getLabel(), radioButton.isSelected());
}
private void changePointExclusionState(String acquiredDate, QCPlotsWebPart.QCPlotExclusionState state, int waitForPlotCount)
{
QCPlotsWebPart qcPlotsWebPart = new PanoramaDashboard(this).getQcPlotsWebPart();
WebElement bubble = qcPlotsWebPart.openExclusionBubble(acquiredDate);
- RadioButton radioButton = RadioButton.RadioButton().withLabel(state.getLabel()).find(bubble);
- if (!radioButton.isChecked())
+ WebElement radioButton = Locator.tag("input").withAttribute("type", "radio")
+ .withAttribute("name", "exclusion-status")
+ .followingSibling("label").withText(state.getLabel())
+ .precedingSibling("input").findElement(bubble);
+ if (!radioButton.isSelected())
{
- radioButton.check();
- clickAndWait(Ext4Helper.Locators.ext4Button("Save").findElement(bubble));
+ radioButton.click();
+ clickAndWait(Locator.tagWithClass("button", "labkey-button").withText("Save").findElement(bubble));
}
- else
- qcPlotsWebPart.closeBubble();
qcPlotsWebPart.waitForPlots(waitForPlotCount);
}
diff --git a/test/src/org/labkey/test/tests/targetedms/TargetedMSTrailingMeanAndCVTest.java b/test/src/org/labkey/test/tests/targetedms/TargetedMSTrailingMeanAndCVTest.java
index d03e4691e..b92e0348d 100644
--- a/test/src/org/labkey/test/tests/targetedms/TargetedMSTrailingMeanAndCVTest.java
+++ b/test/src/org/labkey/test/tests/targetedms/TargetedMSTrailingMeanAndCVTest.java
@@ -167,7 +167,6 @@ public void testTrailingCVPlotType()
3 runs average
Acquired:
2013-08-09 11:39 - 2013-08-12 04:54""", toolTipText);
- qcPlotsWebPart.closeBubble();
log("Verifying the count of points on the plot");
Assert.assertEquals("Invalid point count for all replicates", REPLICATE_COUNT * RUN_COUNT,
qcPlotsWebPart.getPointElements("d", SvgShapes.CIRCLE.getPathPrefix(), true).size());
@@ -191,7 +190,6 @@ public void testTrailingCVPlotType()
3 runs average
Acquired:
2013-08-21 04:46 - 2013-08-21 09:07""", toolTipText);
- qcPlotsWebPart.closeBubble();
log("Verifying the count of points on the plot with guide set");
Assert.assertEquals("Invalid number of point count for all replicates - plots with guide set", REPLICATE_COUNT * RUN_COUNT ,
diff --git a/webapp/TargetedMS/css/QCSummary.css b/webapp/TargetedMS/css/QCSummary.css
index c338204db..d8c4eb528 100644
--- a/webapp/TargetedMS/css/QCSummary.css
+++ b/webapp/TargetedMS/css/QCSummary.css
@@ -81,16 +81,6 @@
margin: 0;
}
-div.hopscotch-bubble .hopscotch-actions,
-div.hopscotch-bubble .hopscotch-content {
- margin: 0;
- padding: 0;
-}
-
-div.hopscotch-bubble .hopscotch-title {
- padding-bottom: 5px;
-}
-
td.outlier-column-header, td.outlier-metric-label {
white-space: nowrap;
}
\ No newline at end of file
diff --git a/webapp/TargetedMS/css/qcTrendPlotReport.css b/webapp/TargetedMS/css/qcTrendPlotReport.css
index 0f2461254..80aab9dfd 100644
--- a/webapp/TargetedMS/css/qcTrendPlotReport.css
+++ b/webapp/TargetedMS/css/qcTrendPlotReport.css
@@ -82,10 +82,6 @@
cursor: pointer;
}
-.hopscotch-bubble-container .qc-hover-value-break {
- word-break: break-all;
-}
-
.qc-trend-plot-panel .x4-panel-header,
.qc-trend-plot-panel .x4-toolbar-footer {
background: #EEEEEE;
diff --git a/webapp/TargetedMS/js/QCPlotHoverPanel.js b/webapp/TargetedMS/js/QCPlotHoverPanel.js
index 660f2fc57..fb07bff5b 100644
--- a/webapp/TargetedMS/js/QCPlotHoverPanel.js
+++ b/webapp/TargetedMS/js/QCPlotHoverPanel.js
@@ -4,59 +4,65 @@
* Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0
*/
-Ext4.define('LABKEY.targetedms.QCPlotHoverPanel', {
- extend: 'Ext.panel.Panel',
- border: false,
-
- pointData: null,
- valueName: null,
- metricProps: null,
- originalStatus: 0,
- existingExclusions: null,
- canEdit: false,
- trailingRuns: null,
- trailingStartDate: null,
- trailingEndDate: null,
-
- STATE: {
+LABKEY = LABKEY || {};
+LABKEY.targetedms = LABKEY.targetedms || {};
+
+LABKEY.targetedms.QCPlotHoverPanel = function (config) {
+ this.pointData = config.pointData || {};
+ this.valueName = config.valueName || null;
+ this.metricProps = config.metricProps || {};
+ this.originalStatus = 0;
+ this.existingExclusions = null;
+ this.canEdit = config.canEdit || false;
+ this.trailingRuns = config.trailingRuns || null;
+ this.trailingStartDate = config.trailingStartDate || null;
+ this.trailingEndDate = config.trailingEndDate || null;
+ this.renderTo = config.renderTo;
+ this.onClose = config.onClose || function () {
+ };
+
+ this.STATE = {
INCLUDE: 0,
EXCLUDE_METRIC: 1,
EXCLUDE_ALL: 2
- },
+ };
- initComponent : function() {
- if (this.pointData == null) {
- this.pointData = {};
- }
- if (this.metricProps == null) {
- this.metricProps = {};
- }
+ this.containerEl = null;
+ this.exclusionPanel = null;
+ this.exclusionsSaveBtn = null;
+ this.exclusionRadioGroup = null;
+ this.viewDocumentURL = null;
- this.items = [];
- this.callParent();
+ this.init();
+};
- if(!this.metricProps.precursorScoped)
+LABKEY.targetedms.QCPlotHoverPanel.prototype = {
+
+ init: function () {
+ if (!this.metricProps.precursorScoped) {
this.getRunId();
- else
+ } else {
this.getExistingReplicateExclusions();
+ }
},
- getExistingReplicateExclusions : function() {
- if (Ext4.isNumber(this.pointData['ReplicateId'])) {
+ getExistingReplicateExclusions: function () {
+ if (typeof this.pointData['ReplicateId'] === 'number') {
LABKEY.Query.selectRows({
schemaName: 'targetedms',
queryName: 'QCMetricExclusion',
filterArray: [LABKEY.Filter.create('ReplicateId', this.pointData['ReplicateId'])],
scope: this,
- success: function(data) {
+ success: function (data) {
this.existingExclusions = data.rows;
// set the initial status for this point based on the existing exclusions
- var metricIds = Ext4.Array.pluck(this.existingExclusions, 'MetricId');
+ var metricIds = this.existingExclusions.map(function (item) {
+ return item.MetricId;
+ });
if (metricIds.indexOf(null) > -1) {
this.originalStatus = this.STATE.EXCLUDE_ALL;
- }
- else if (metricIds.indexOf(this.metricProps.id) > -1) {
+ } else if (metricIds.indexOf(this.metricProps.id) > -1) {
this.originalStatus = this.STATE.EXCLUDE_METRIC;
}
@@ -66,7 +72,14 @@ Ext4.define('LABKEY.targetedms.QCPlotHoverPanel', {
}
},
- initializePanel : function() {
+ initializePanel: function () {
+ this.containerEl = document.createElement('div');
+ this.containerEl.className = 'qc-plot-hover-panel';
+ this.containerEl.style.backgroundColor = 'white';
+ this.containerEl.style.border = '5px solid rgba(0, 0, 0, 0.5)';
+ this.containerEl.style.padding = '10px';
+ this.containerEl.style.minWidth = '600px';
+ this.containerEl.style.color = 'black';
let hideExclusionAndPointClickLinks = false;
if (this.valueName === "TrailingMean") {
@@ -77,251 +90,350 @@ Ext4.define('LABKEY.targetedms.QCPlotHoverPanel', {
}
if (this.metricProps.name !== undefined) {
- this.add(this.getPlotPointDetailField('Metric', this.metricProps.name));
+ this.addElement(this.getPlotPointDetailField('Metric', this.metricProps.name));
}
- this.add(this.getPlotPointDetailField(this.pointData['dataType'], this.pointData['fragment'], 'qc-hover-value-break'));
+ var fragmentValue = this.pointData['fragment'];
+ if (typeof fragmentValue === 'object' && fragmentValue !== null) {
+ fragmentValue = fragmentValue.name || fragmentValue.toString();
+ }
+ this.addElement(this.getPlotPointDetailField(this.pointData['dataType'], fragmentValue, 'qc-hover-value-break'));
if (this.valueName.indexOf('CUSUM') > -1) {
- this.add(this.getPlotPointDetailField('Group', 'CUSUMmN' === this.valueName || 'CUSUMvN' === this.valueName ? 'CUSUM-' : 'CUSUM+'));
+ this.addElement(this.getPlotPointDetailField('Group', 'CUSUMmN' === this.valueName || 'CUSUMvN' === this.valueName ? 'CUSUM-' : 'CUSUM+'));
}
if (this.pointData.conversion && this.pointData.rawValue !== undefined && this.valueName.indexOf("CUSUM") === -1) {
if (this.pointData.conversion === 'percentDeviation') {
- this.add(this.getPlotPointDetailField('Value', LABKEY.targetedms.PlotSettingsUtil.formatNumeric(this.pointData.rawValue)));
- this.add(this.getPlotPointDetailField('% of Mean', (this.valueName ? this.pointData[this.valueName] : this.pointData['value']) + '%'))
- }
- else if (this.pointData.conversion === 'standardDeviation') {
- this.add(this.getPlotPointDetailField('Value', LABKEY.targetedms.PlotSettingsUtil.formatNumeric(this.pointData.rawValue)));
- this.add(this.getPlotPointDetailField('Std Devs', this.valueName ? this.pointData[this.valueName] : this.pointData['value']))
- }
- else if (this.pointData.conversion === 'deltaFromMean') {
- this.add(this.getPlotPointDetailField('Value', LABKEY.targetedms.PlotSettingsUtil.formatNumeric(this.pointData.rawValue)));
- this.add(this.getPlotPointDetailField('Delta From Mean', LABKEY.targetedms.PlotSettingsUtil.formatNumeric(this.valueName ? this.pointData[this.valueName] : this.pointData['value'])))
+ this.addElement(this.getPlotPointDetailField('Value', LABKEY.targetedms.PlotSettingsUtil.formatNumeric(this.pointData.rawValue)));
+ this.addElement(this.getPlotPointDetailField('% of Mean', (this.valueName ? this.pointData[this.valueName] : this.pointData['value']) + '%'))
+ } else if (this.pointData.conversion === 'standardDeviation') {
+ this.addElement(this.getPlotPointDetailField('Value', LABKEY.targetedms.PlotSettingsUtil.formatNumeric(this.pointData.rawValue)));
+ this.addElement(this.getPlotPointDetailField('Std Devs', this.valueName ? this.pointData[this.valueName] : this.pointData['value']))
+ } else if (this.pointData.conversion === 'deltaFromMean') {
+ this.addElement(this.getPlotPointDetailField('Value', LABKEY.targetedms.PlotSettingsUtil.formatNumeric(this.pointData.rawValue)));
+ this.addElement(this.getPlotPointDetailField('Delta From Mean', LABKEY.targetedms.PlotSettingsUtil.formatNumeric(this.valueName ? this.pointData[this.valueName] : this.pointData['value'])))
+ } else {
+ this.addElement(this.getPlotPointDetailField('Value', LABKEY.targetedms.PlotSettingsUtil.formatNumeric(this.valueName ? this.pointData[this.valueName] : this.pointData['value'])));
}
- else {
- this.add(this.getPlotPointDetailField('Value', LABKEY.targetedms.PlotSettingsUtil.formatNumeric(this.valueName ? this.pointData[this.valueName] : this.pointData['value'])));
- }
- }
- else {
- this.add(this.getPlotPointDetailField('Value', LABKEY.targetedms.PlotSettingsUtil.formatNumeric(this.valueName ? this.pointData[this.valueName] : this.pointData['value'])));
+ } else {
+ this.addElement(this.getPlotPointDetailField('Value', LABKEY.targetedms.PlotSettingsUtil.formatNumeric(this.valueName ? this.pointData[this.valueName] : this.pointData['value'])));
}
if (hideExclusionAndPointClickLinks) {
let numOfRunsAverage = 0;
// check if guide set is present
- if (this.pointData['inGuideSetTrainingRange'] !== undefined) {
- numOfRunsAverage = this.trailingRuns > this.pointData['TrainingSeqIdx'] ? this.pointData['TrainingSeqIdx'] : this.trailingRuns
- }
- else {
- numOfRunsAverage = this.trailingRuns > this.pointData['seqValue'] + 1 ? this.pointData['seqValue'] + 1 : this.trailingRuns;
- }
- this.add(this.getPlotPointDetailField('Replicate', numOfRunsAverage + " runs average"));
- this.add(this.getPlotPointDetailField('Acquired', this.trailingStartDate + " - " + this.trailingEndDate));
- }
- else {
- this.add(this.getPlotPointDetailField('Replicate', this.pointData['ReplicateName']));
- this.add(this.getPlotPointDetailField('Acquired', this.pointData['fullDate']));
+ if (this.pointData['inGuideSetTrainingRange'] !== undefined) {
+ numOfRunsAverage = this.trailingRuns > this.pointData['TrainingSeqIdx'] ? this.pointData['TrainingSeqIdx'] : this.trailingRuns
+ } else {
+ numOfRunsAverage = this.trailingRuns > this.pointData['seqValue'] + 1 ? this.pointData['seqValue'] + 1 : this.trailingRuns;
+ }
+ this.addElement(this.getPlotPointDetailField('Replicate', numOfRunsAverage + " runs average"));
+ this.addElement(this.getPlotPointDetailField('Acquired', this.trailingStartDate + " - " + this.trailingEndDate));
+ } else {
+ this.addElement(this.getPlotPointDetailField('Replicate', this.pointData['ReplicateName']));
+ this.addElement(this.getPlotPointDetailField('Acquired', this.pointData['fullDate']));
}
if (!hideExclusionAndPointClickLinks) {
- this.add(this.getPlotPointDetailField('File Path', this.pointData['FilePath'].replace(/\\/g, '\\').replace(/\//g, '\/').replace(/_/g, '_')));
+ this.addElement(this.getPlotPointDetailField('File Path', this.pointData['FilePath'].replace(/\\/g, '\\').replace(/\//g, '\/').replace(/_/g, '_')));
if (this.canEdit) {
- this.add(this.getPlotPointExclusionPanel());
+ this.addElement(this.getPlotPointExclusionPanel());
+ } else {
+ this.addElement(this.getPlotPointDetailField('Status', this.pointData['IgnoreInQC'] ? 'Not included in QC' : 'Included in QC'));
}
- else {
- this.add(this.getPlotPointDetailField('Status', this.pointData['IgnoreInQC'] ? 'Not included in QC' : 'Included in QC'));
+
+ var linksDiv = document.createElement('div');
+ linksDiv.innerHTML = this.getPlotPointClickLinks();
+ this.addElement(linksDiv);
+ }
+
+ if (this.renderTo) {
+ var targetEl = typeof this.renderTo === 'string' ? document.getElementById(this.renderTo) : this.renderTo;
+ if (targetEl) {
+ targetEl.appendChild(this.containerEl);
}
+ }
+ },
- this.add(Ext4.create('Ext.Component', {html: this.getPlotPointClickLinks()}));
+ addElement: function (el) {
+ if (el && this.containerEl) {
+ this.containerEl.appendChild(el);
}
},
- getPlotPointDetailField : function(label, value, includeCls) {
- return Ext4.create('Ext.form.field.Display', {
- cls: 'qc-hover-field' + (Ext4.isString(includeCls) ? ' ' + includeCls : ''),
- fieldLabel: label,
- labelWidth: 75,
- width: 450,
- value: value
- });
+ getPlotPointDetailField: function (label, value, includeCls) {
+ var fieldDiv = document.createElement('div');
+ fieldDiv.className = 'qc-hover-field' + (typeof includeCls === 'string' ? ' ' + includeCls : '');
+ fieldDiv.style.width = '100%';
+ fieldDiv.style.display = 'flex';
+ fieldDiv.style.marginBottom = '5px';
+
+ var labelSpan = document.createElement('span');
+ labelSpan.className = 'qc-hover-field-label';
+ labelSpan.style.display = 'inline-block';
+ labelSpan.style.width = '120px';
+ labelSpan.style.fontWeight = 'bold';
+ labelSpan.style.flexShrink = '0';
+ labelSpan.textContent = label + ':';
+
+ var valueSpan = document.createElement('span');
+ valueSpan.className = 'qc-hover-field-value';
+ valueSpan.style.flex = '1';
+ valueSpan.style.wordBreak = 'break-all';
+ valueSpan.innerHTML = value;
+
+ fieldDiv.appendChild(labelSpan);
+ fieldDiv.appendChild(valueSpan);
+
+ return fieldDiv;
},
- getPlotPointExclusionPanel : function() {
+ getPlotPointExclusionPanel: function () {
if (!this.exclusionPanel) {
- this.exclusionPanel = Ext4.create('Ext.form.Panel', {
- border: false,
- margin: '10px 0',
- style: 'border-top: solid #eeeeee 1px; border-bottom: solid #eeeeee 1px;',
- items: [this.getPlotPointExclusionRadioGroup()],
- dockedItems: [{
- xtype: 'toolbar',
- dock: 'right',
- ui: 'footer',
- padding: '0 0 10px 0',
- items: ['->', this.getPlotPointExclusionSaveBtn()]
- }]
- });
+ this.exclusionPanel = document.createElement('div');
+ this.exclusionPanel.className = 'qc-hover-exclusion-panel';
+ this.exclusionPanel.style.margin = '10px 0';
+ this.exclusionPanel.style.borderTop = 'solid #eeeeee 1px';
+ this.exclusionPanel.style.borderBottom = 'solid #eeeeee 1px';
+ this.exclusionPanel.style.padding = '10px 0';
+
+ this.exclusionPanel.appendChild(this.getPlotPointExclusionRadioGroup());
+ this.exclusionPanel.appendChild(this.getPlotPointExclusionSaveBtn());
}
return this.exclusionPanel;
},
- getPlotPointExclusionSaveBtn : function() {
+ getPlotPointExclusionSaveBtn: function () {
if (!this.exclusionsSaveBtn) {
- this.exclusionsSaveBtn = Ext4.create('Ext.button.Button', {
- text: 'Save',
- disabled: true,
- scope: this,
- handler: function() {
- var newStatus = this.getPlotPointExclusionRadioGroup().getValue().status;
- if (newStatus !== this.originalStatus) {
- this.getEl().mask();
-
- // Scenarios:
- // 1 - from include to exclude metric - insert new row with MetricId
- // 2 - from include to exclude all - delete all for replicate and then insert new row without MetricId
- // 3 - from exclude metric to include - delete row for MetricId
- // 4 - from exclude metric to exclude all - delete all for replicate and then insert new row without MetricId
- // 5 - from exclude all to include - delete all for replicate
- // 6 - from exclude all to exclude metric - delete all for replicate and then insert new row with MetricId
- var s1 = this.originalStatus === this.STATE.INCLUDE && newStatus === this.STATE.EXCLUDE_METRIC;
- var s2 = this.originalStatus === this.STATE.INCLUDE && newStatus === this.STATE.EXCLUDE_ALL;
- var s3 = this.originalStatus === this.STATE.EXCLUDE_METRIC && newStatus === this.STATE.INCLUDE;
- var s4 = this.originalStatus === this.STATE.EXCLUDE_METRIC && newStatus === this.STATE.EXCLUDE_ALL;
- var s5 = this.originalStatus === this.STATE.EXCLUDE_ALL && newStatus === this.STATE.INCLUDE;
- var s6 = this.originalStatus === this.STATE.EXCLUDE_ALL && newStatus === this.STATE.EXCLUDE_METRIC;
-
- var commands = [];
-
- if (this.existingExclusions.length > 0) {
- // for scenarios s2, s4, s5, and s6 - delete all existing exclusions for this replicate
- if (s2 || s4 || s5 || s6) {
- commands.push({
- schemaName: 'targetedms',
- queryName: 'QCMetricExclusion',
- command: 'delete',
- rows: this.existingExclusions
- });
- }
-
- // for scenario s3 - delete the existing exclusion for this replicate/metric
- if (s3) {
- commands.push({
- schemaName: 'targetedms',
- queryName: 'QCMetricExclusion',
- command: 'delete',
- rows: [this.existingExclusions[Ext4.Array.pluck(this.existingExclusions, 'MetricId').indexOf(this.metricProps.id)]]
- });
- }
- }
-
- // for scenarios s1 and s6 - insert a new exclusion for this replicate/metric
- if (s1 || s6) {
+ var btnContainer = document.createElement('div');
+ btnContainer.style.marginLeft = '120px';
+ btnContainer.style.marginTop = '10px';
+
+ this.exclusionsSaveBtn = document.createElement('button');
+ this.exclusionsSaveBtn.textContent = 'Save';
+ this.exclusionsSaveBtn.disabled = true;
+ this.exclusionsSaveBtn.className = 'labkey-button';
+
+ btnContainer.appendChild(this.exclusionsSaveBtn);
+
+ var self = this;
+ this.exclusionsSaveBtn.addEventListener('click', function () {
+ var newStatus = self.getRadioGroupValue();
+ if (newStatus !== self.originalStatus) {
+ self.showMask();
+
+ // Scenarios:
+ // 1 - from include to exclude metric - insert new row with MetricId
+ // 2 - from include to exclude all - delete all for replicate and then insert new row without MetricId
+ // 3 - from exclude metric to include - delete row for MetricId
+ // 4 - from exclude metric to exclude all - delete all for replicate and then insert new row without MetricId
+ // 5 - from exclude all to include - delete all for replicate
+ // 6 - from exclude all to exclude metric - delete all for replicate and then insert new row with MetricId
+ var s1 = self.originalStatus === self.STATE.INCLUDE && newStatus === self.STATE.EXCLUDE_METRIC;
+ var s2 = self.originalStatus === self.STATE.INCLUDE && newStatus === self.STATE.EXCLUDE_ALL;
+ var s3 = self.originalStatus === self.STATE.EXCLUDE_METRIC && newStatus === self.STATE.INCLUDE;
+ var s4 = self.originalStatus === self.STATE.EXCLUDE_METRIC && newStatus === self.STATE.EXCLUDE_ALL;
+ var s5 = self.originalStatus === self.STATE.EXCLUDE_ALL && newStatus === self.STATE.INCLUDE;
+ var s6 = self.originalStatus === self.STATE.EXCLUDE_ALL && newStatus === self.STATE.EXCLUDE_METRIC;
+
+ var commands = [];
+
+ if (self.existingExclusions.length > 0) {
+ // for scenarios s2, s4, s5, and s6 - delete all existing exclusions for this replicate
+ if (s2 || s4 || s5 || s6) {
commands.push({
schemaName: 'targetedms',
queryName: 'QCMetricExclusion',
- command: 'insert',
- rows: [{ ReplicateId: this.pointData['ReplicateId'], MetricId: this.metricProps.id }]
+ command: 'delete',
+ rows: self.existingExclusions
});
}
- // for scenarios s2 and s4 - insert a new exclusion for this replicate without a metric value
- if (s2 || s4) {
+ // for scenario s3 - delete the existing exclusion for this replicate/metric
+ if (s3) {
+ var metricIds = self.existingExclusions.map(function (item) {
+ return item.MetricId;
+ });
commands.push({
schemaName: 'targetedms',
queryName: 'QCMetricExclusion',
- command: 'insert',
- rows: [{ ReplicateId: this.pointData['ReplicateId'] }]
+ command: 'delete',
+ rows: [self.existingExclusions[metricIds.indexOf(self.metricProps.id)]]
});
}
+ }
- LABKEY.Query.saveRows({
- commands: commands,
- scope: this,
- success: function(data) {
- // Issue 30343: need to reload the full page because the QC Summary webpart might be
- // present and need to be updated according to the updated exclusion state.
- window.location.reload();
- }
+ // for scenarios s1 and s6 - insert a new exclusion for this replicate/metric
+ if (s1 || s6) {
+ commands.push({
+ schemaName: 'targetedms',
+ queryName: 'QCMetricExclusion',
+ command: 'insert',
+ rows: [{ ReplicateId: self.pointData['ReplicateId'], MetricId: self.metricProps.id }]
});
}
- else {
- this.fireEvent('close');
+
+ // for scenarios s2 and s4 - insert a new exclusion for this replicate without a metric value
+ if (s2 || s4) {
+ commands.push({
+ schemaName: 'targetedms',
+ queryName: 'QCMetricExclusion',
+ command: 'insert',
+ rows: [{ ReplicateId: self.pointData['ReplicateId'] }]
+ });
}
+
+ LABKEY.Query.saveRows({
+ commands: commands,
+ scope: self,
+ success: function (data) {
+ // Issue 30343: need to reload the full page because the QC Summary webpart might be
+ // present and need to be updated according to the updated exclusion state.
+ window.location.reload();
+ }
+ });
+ } else {
+ self.onClose();
}
});
+
+ return btnContainer;
}
- return this.exclusionsSaveBtn;
+ return this.exclusionsSaveBtn.parentNode;
},
- getPlotPointExclusionRadioGroup : function() {
+ getPlotPointExclusionRadioGroup: function () {
if (!this.exclusionRadioGroup) {
- this.exclusionRadioGroup = Ext4.create('Ext.form.RadioGroup', {
- cls: 'qc-hover-field',
- fieldLabel: 'Status',
- labelWidth: 75,
- padding: '10px 0 0 0',
- width: 450,
- columns: 1,
- items: [{
- boxLabel: 'Include', name: 'status',
- inputValue: this.STATE.INCLUDE,
- checked: this.originalStatus === this.STATE.INCLUDE
- },{
- boxLabel: 'Exclude replicate for this metric', name: 'status',
- inputValue: this.STATE.EXCLUDE_METRIC,
+ var radioGroupDiv = document.createElement('div');
+ radioGroupDiv.className = 'qc-hover-field';
+ radioGroupDiv.style.padding = '10px 0 0 0';
+ radioGroupDiv.style.width = '100%';
+ radioGroupDiv.style.display = 'flex';
+
+ var labelDiv = document.createElement('div');
+ labelDiv.style.display = 'inline-block';
+ labelDiv.style.width = '120px';
+ labelDiv.style.fontWeight = 'bold';
+ labelDiv.style.flexShrink = '0';
+ labelDiv.style.verticalAlign = 'top';
+ labelDiv.textContent = 'Status:';
+ radioGroupDiv.appendChild(labelDiv);
+
+ var optionsDiv = document.createElement('div');
+ optionsDiv.style.flex = '1';
+
+ var self = this;
+ var options = [
+ { label: 'Include', value: this.STATE.INCLUDE, checked: this.originalStatus === this.STATE.INCLUDE },
+ {
+ label: 'Exclude replicate for this metric',
+ value: this.STATE.EXCLUDE_METRIC,
checked: this.originalStatus === this.STATE.EXCLUDE_METRIC
- },{
- boxLabel: 'Exclude replicate for all metrics', name: 'status',
- inputValue: this.STATE.EXCLUDE_ALL,
+ },
+ {
+ label: 'Exclude replicate for all metrics',
+ value: this.STATE.EXCLUDE_ALL,
checked: this.originalStatus === this.STATE.EXCLUDE_ALL
- }],
- listeners: {
- scope: this,
- change: function(cmp, newVal, oldVal) {
- this.getPlotPointExclusionSaveBtn().setDisabled(newVal.status === this.originalStatus);
- }
}
+ ];
+
+ options.forEach(function (option) {
+ var optionDiv = document.createElement('div');
+ optionDiv.style.marginBottom = '5px';
+
+ var radio = document.createElement('input');
+ radio.type = 'radio';
+ radio.name = 'exclusion-status';
+ radio.value = option.value;
+ radio.checked = option.checked;
+ radio.addEventListener('change', function () {
+ var currentValue = self.getRadioGroupValue();
+ self.exclusionsSaveBtn.disabled = (currentValue === self.originalStatus);
+ });
+
+ var label = document.createElement('label');
+ label.style.marginLeft = '5px';
+ label.textContent = option.label;
+
+ optionDiv.appendChild(radio);
+ optionDiv.appendChild(label);
+ optionsDiv.appendChild(optionDiv);
});
+
+ radioGroupDiv.appendChild(optionsDiv);
+ this.exclusionRadioGroup = radioGroupDiv;
}
return this.exclusionRadioGroup;
},
- getPlotPointClickLinks : function() {
+ getRadioGroupValue: function () {
+ if (this.exclusionRadioGroup) {
+ var radios = this.exclusionRadioGroup.querySelectorAll('input[name="exclusion-status"]');
+ for (var i = 0; i < radios.length; i++) {
+ if (radios[i].checked) {
+ return parseInt(radios[i].value);
+ }
+ }
+ }
+ return this.STATE.INCLUDE;
+ },
+
+ showMask: function () {
+ if (this.containerEl) {
+ var mask = document.createElement('div');
+ mask.style.position = 'absolute';
+ mask.style.top = '0';
+ mask.style.left = '0';
+ mask.style.width = '100%';
+ mask.style.height = '100%';
+ mask.style.backgroundColor = 'rgba(255, 255, 255, 0.7)';
+ mask.style.zIndex = '1000';
+ this.containerEl.style.position = 'relative';
+ this.containerEl.appendChild(mask);
+ }
+ },
+
+ getPlotPointClickLinks: function () {
//Choose action target based on precursor type
var action = this.pointData['dataType'] === 'Peptide' ? "precursorAllChromatogramsChart" : "moleculePrecursorAllChromatogramsChart",
- url = LABKEY.ActionURL.buildURL('targetedms', action, LABKEY.ActionURL.getContainer(), {
- id: this.pointData['PrecursorId'],
- chromInfoId: this.pointData['PrecursorChromInfoId']
- });
+ url = LABKEY.ActionURL.buildURL('targetedms', action, LABKEY.ActionURL.getContainer(), {
+ id: this.pointData['PrecursorId'],
+ chromInfoId: this.pointData['PrecursorChromInfoId']
+ });
return LABKEY.Utils.textLink({
- text: this.metricProps.precursorScoped ? 'View Chromatogram': 'View Document',
- href: this.metricProps.precursorScoped ? url + '#ChromInfo' + this.pointData['PrecursorChromInfoId'] : this.viewDocumentURL
- }) + ' ' +
- LABKEY.Utils.textLink({
- text: 'View Replicate',
- href: LABKEY.ActionURL.buildURL('targetedms', 'showSampleFile', LABKEY.ActionURL.getContainer(), {id: this.pointData['SampleFileId']})
- });
+ text: this.metricProps.precursorScoped ? 'View Chromatogram' : 'View Document',
+ href: this.metricProps.precursorScoped ? url + '#ChromInfo' + this.pointData['PrecursorChromInfoId'] : this.viewDocumentURL
+ }) + ' ' +
+ LABKEY.Utils.textLink({
+ text: 'View Replicate',
+ href: LABKEY.ActionURL.buildURL('targetedms', 'showSampleFile', LABKEY.ActionURL.getContainer(), { id: this.pointData['SampleFileId'] })
+ });
},
getRunId: function () {
-
LABKEY.Query.executeSql({
schemaName: 'targetedms',
sql: 'SELECT SampleFileId.ReplicateId.RunId.Id as runId from PrecursorChromInfo',
scope: this,
success: function (results) {
var runId;
- if(results && results.rows)
+ if (results && results.rows)
runId = results.rows[0]["runId"];
- this.viewDocumentURL = LABKEY.ActionURL.buildURL('targetedms', 'showPrecursorList', null, {id: runId});
+ this.viewDocumentURL = LABKEY.ActionURL.buildURL('targetedms', 'showPrecursorList', null, { id: runId });
this.getExistingReplicateExclusions();
}
});
+ },
+
+ destroy: function () {
+ if (this.containerEl && this.containerEl.parentNode) {
+ this.containerEl.parentNode.removeChild(this.containerEl);
+ }
}
-});
\ No newline at end of file
+};
\ No newline at end of file
diff --git a/webapp/TargetedMS/js/QCSummaryPanel.js b/webapp/TargetedMS/js/QCSummaryPanel.js
index 99bf9d736..2b5c8ad90 100644
--- a/webapp/TargetedMS/js/QCSummaryPanel.js
+++ b/webapp/TargetedMS/js/QCSummaryPanel.js
@@ -227,8 +227,10 @@ Ext4.define('LABKEY.targetedms.QCSummary', {
},
showAutoQCMessage : function(divId, autoQC, hasChildren) {
+
var divEl = Ext4.get(divId),
- content = '';
+ content = '',
+ me = this;
if (!divEl)
return;
@@ -248,26 +250,58 @@ Ext4.define('LABKEY.targetedms.QCSummary', {
content = '' + content + '
' + this.getAutoQCSetupInfo();
- // add mouse listeners to the div element for when to show the AutoQC message
- divEl.on('mouseover', function() {
- var calloutMgr = hopscotch.getCalloutManager();
- calloutMgr.removeAllCallouts();
- calloutMgr.createCallout({
- id: Ext4.id(),
- target: divEl.dom,
- placement: 'left',
- yOffset: -22,
- arrowOffset: 7,
- width: 300,
- showCloseButton: false,
- content: content
- });
- }, this);
+ tippy(divEl.dom, {
+ content: content,
+ allowHTML: true,
+ placement: 'right',
+ arrow: true,
+ maxWidth: 300,
+ trigger: 'mouseenter',
+ hideOnClick: false,
+ appendTo: document.body,
+ followCursor: 'initial',
+ onShow(instance) {
+ // Hide any previously open tooltip
+ if (me.currentTippy && me.currentTippy !== instance) {
+ me.currentTippy.hide();
+ }
+ me.currentTippy = instance;
- // close the hover details on mouseout of the autoQC element
- divEl.on('mouseout', function() {
- hopscotch.getCalloutManager().removeAllCallouts();
- }, this);
+ const tippyBox = instance.popper.querySelector('.tippy-box');
+ const tippyContent = instance.popper.querySelector('.tippy-content');
+ const tippyArrow = instance.popper.querySelector('.tippy-arrow');
+
+ if (tippyBox) {
+ tippyBox.style.backgroundColor = 'white';
+ tippyBox.style.color = 'black';
+ tippyBox.style.border = '2px solid #808080';
+ tippyBox.style.boxShadow = '0 0 10px rgba(0,0,0,0.2)';
+ }
+ if (tippyContent) {
+ tippyContent.style.padding = '10px';
+ tippyContent.style.maxHeight = 'none';
+ tippyContent.style.overflow = 'visible';
+ tippyContent.style.height = 'auto';
+ tippyContent.style.width = 'auto';
+ }
+ if (tippyArrow) {
+ tippyArrow.style.color = 'white';
+ // Create border using multiple 1px drop shadows
+ tippyArrow.style.filter = 'drop-shadow(0 0 0 #808080) drop-shadow(0 1px 0 #808080) drop-shadow(0 -1px 0 #808080) drop-shadow(1px 0 0 #808080) drop-shadow(-1px 0 0 #808080)';
+ // Adjust positioning to account for the border
+ const placement = instance.props.placement;
+ if (placement.startsWith('bottom')) {
+ tippyArrow.style.top = '-1px';
+ } else if (placement.startsWith('top')) {
+ tippyArrow.style.bottom = '-1px';
+ } else if (placement.startsWith('left')) {
+ tippyArrow.style.right = '-1px';
+ } else if (placement.startsWith('right')) {
+ tippyArrow.style.left = '-1px';
+ }
+ }
+ }
+ });
},
queryContainerSampleFileStats: function (container) {
@@ -407,7 +441,8 @@ Ext4.define('LABKEY.targetedms.QCSummary', {
showSampleFileStatsDetails : function(divId, sampleFile) {
var task = new Ext4.util.DelayedTask(),
divEl = Ext4.get(divId),
- content = '';
+ content = '',
+ me = this;
var sampleHREF = LABKEY.ActionURL.buildURL('targetedms', 'showSampleFile', LABKEY.ActionURL.getContainer(), {id: sampleFile.SampleId});
@@ -480,42 +515,68 @@ Ext4.define('LABKEY.targetedms.QCSummary', {
content += '';
}
- // add mouse listeners to the div element for when to show the hover details for this sample file
- divEl.on('mouseover', function() {
- task.delay(1000, function(el){
- var calloutMgr = hopscotch.getCalloutManager();
- calloutMgr.removeAllCallouts();
- calloutMgr.createCallout({
- id: Ext4.id(),
- target: el.dom,
- placement: 'bottom',
- width: sampleFile.Metrics.length > 0 ? 800 : 300,
- content: content,
- onShow: this.attachHopscotchMouseClose
- });
- }, this, [divEl]);
- }, this);
-
- // cancel the hover details show event if the user was just passing over the div without stopping for X amount of time
- divEl.on('mouseout', function() {
- task.cancel();
- }, this);
- },
-
- attachHopscotchMouseClose: function() {
- var closeTask = new Ext4.util.DelayedTask();
- var h = Ext4.select('.hopscotch-bubble-container');
-
- // on mouseout call the delayed task to close the callout
- h.on('mouseout', function() {
- closeTask.delay(1000, function() {
- hopscotch.getCalloutManager().removeAllCallouts();
- });
- });
+ tippy(divEl.dom, {
+ content: content,
+ allowHTML: true,
+ placement: 'bottom',
+ arrow: true,
+ maxWidth: sampleFile.Metrics.length > 0 ? 800 : 300,
+ trigger: 'mouseenter',
+ delay: [500, 0],
+ interactive: true,
+ hideOnClick: false,
+ appendTo: document.body,
+ followCursor: 'initial',
+ onShow(instance) {
+ // Hide any previously open tooltip
+ if (me.currentTippy && me.currentTippy !== instance) {
+ me.currentTippy.hide();
+ }
+ me.currentTippy = instance;
+
+ // Apply light background styling
+ const tippyBox = instance.popper.querySelector('.tippy-box');
+ const tippyContent = instance.popper.querySelector('.tippy-content');
+ const tippyArrow = instance.popper.querySelector('.tippy-arrow');
+
+ if (tippyBox) {
+ tippyBox.style.backgroundColor = 'white';
+ tippyBox.style.color = 'black';
+ tippyBox.style.border = '2px solid #808080';
+ tippyBox.style.boxShadow = '0 0 10px rgba(0,0,0,0.2)';
+ }
+ if (tippyContent) {
+ tippyContent.style.padding = '10px';
+ tippyContent.style.maxHeight = 'none';
+ tippyContent.style.overflow = 'visible';
+ tippyContent.style.height = 'auto';
+ tippyContent.style.width = 'auto';
+ }
+ if (tippyArrow) {
+ tippyArrow.style.color = 'white';
+ // Create border using multiple 1px drop shadows
+ tippyArrow.style.filter = 'drop-shadow(0 0 0 #808080) drop-shadow(0 1px 0 #808080) drop-shadow(0 -1px 0 #808080) drop-shadow(1px 0 0 #808080) drop-shadow(-1px 0 0 #808080)';
+ // Adjust positioning to account for the border
+ const placement = instance.props.placement;
+ if (placement.startsWith('bottom')) {
+ tippyArrow.style.top = '-1px';
+ } else if (placement.startsWith('top')) {
+ tippyArrow.style.bottom = '-1px';
+ } else if (placement.startsWith('left')) {
+ tippyArrow.style.right = '-1px';
+ } else if (placement.startsWith('right')) {
+ tippyArrow.style.left = '-1px';
+ }
+ }
- // if the mouseover happens again for this element before the delay, cancel it to keep callout open
- h.on('mouseover', function() {
- closeTask.cancel();
+ // Add a delay before hiding when mouse leaves
+ instance.popper.addEventListener('mouseleave', function() {
+ setTimeout(function() {
+ if (!instance.state.isVisible) return;
+ instance.hide();
+ }, 1000);
+ });
+ }
});
},
diff --git a/webapp/TargetedMS/js/QCTrendPlotPanel.js b/webapp/TargetedMS/js/QCTrendPlotPanel.js
index 01d7ad580..251006872 100644
--- a/webapp/TargetedMS/js/QCTrendPlotPanel.js
+++ b/webapp/TargetedMS/js/QCTrendPlotPanel.js
@@ -1202,7 +1202,6 @@ Ext4.define('LABKEY.targetedms.QCTrendPlotPanel', {
},
displayTrendPlot: function() {
- hopscotch.getCalloutManager().removeAllCallouts();
this.setBrushingEnabled(false);
this.updateSelectedAnnotations();
@@ -1400,8 +1399,7 @@ Ext4.define('LABKEY.targetedms.QCTrendPlotPanel', {
},
plotPointMouseOver : function(event, row, layerSel, point, valueName, plotConfig) {
- var showHoverTask = new Ext4.util.DelayedTask(),
- metricProps = this.getMetricPropsById(row.MetricId),
+ let metricProps = this.getMetricPropsById(row.MetricId),
me = this;
let panelY = me.canUserEdit() ? -375 : -270;
@@ -1413,52 +1411,57 @@ Ext4.define('LABKEY.targetedms.QCTrendPlotPanel', {
let trailingStartDate = Ext4.util.Format.date(row['TrailingStartDate'], 'Y-m-d H:i');
let trailingEndDate = Ext4.util.Format.date(row['fullDate'], 'Y-m-d H:i');
- showHoverTask.delay(500, function() {
- var calloutMgr = hopscotch.getCalloutManager(),
- hopscotchId = Ext4.id(),
- contentDivId = Ext4.id(),
- shiftLeft = (event.clientX || event.x) > (Ext4.getBody().getWidth() / 2),
- config = {
- id: hopscotchId,
- showCloseButton: true,
- bubbleWidth: 450,
- placement: 'top',
- xOffset: shiftLeft ? -428 : -53,
- arrowOffset: shiftLeft ? 410 : 35,
- yOffset: panelY,
- target: point,
- content: '',
- onShow: function() {
- me.attachHopscotchMouseClose();
-
- Ext4.create('LABKEY.targetedms.QCPlotHoverPanel', {
- renderTo: contentDivId,
- pointData: row,
- valueName: valueName,
- trailingRuns: trailingRuns,
- trailingStartDate: trailingStartDate,
- trailingEndDate: trailingEndDate,
- metricProps: metricProps,
- canEdit: me.canUserEdit(),
- listeners: {
- scope: me,
- close: function() {
- calloutMgr.removeAllCallouts();
- }
- }
- });
- }
- };
+ // Hide any previously open tooltip
+ if (me.currentTippy && me.currentTippy !== point._tippy) {
+ me.currentTippy.hide();
+ }
+
+ if (!point._tippy) {
+ const container = document.createElement('div');
+ container.id = Ext4.id();
+ document.body.appendChild(container);
+
+ new LABKEY.targetedms.QCPlotHoverPanel({
+ pointData: row,
+ valueName: valueName,
+ trailingRuns: trailingRuns,
+ trailingStartDate: trailingStartDate,
+ trailingEndDate: trailingEndDate,
+ metricProps: metricProps,
+ canEdit: me.canUserEdit(),
+ renderTo: container
+ });
- calloutMgr.removeAllCallouts();
- calloutMgr.createCallout(config);
+ tippy(point, {
+ allowHTML: true,
+ interactive: true,
+ theme: 'light',
+ content: container,
+ hideOnClick: 'toggle',
+ arrow: true,
+ maxWidth: 500,
+ appendTo: document.body,
+ onShow(instance) {
+ const tippyBox = instance.popper.querySelector('.tippy-box');
+ const tippyContent = instance.popper.querySelector('.tippy-content');
+ if (tippyBox) {
+ tippyBox.style.backgroundColor = 'transparent';
+ tippyBox.style.border = 'none';
+ tippyBox.style.boxShadow = 'none';
+ }
+ if (tippyContent) {
+ tippyContent.style.padding = '5px';
+ tippyContent.style.wordWrap = 'break-word';
+ tippyContent.style.overflowWrap = 'break-word';
+ }
+ }
});
+ } else {
+ point._tippy.show();
+ }
- // cancel the hover details show event if the user was just
- // passing over the point without stopping for X amount of time
- Ext4.get(point).on('mouseout', function() {
- showHoverTask.cancel();
- }, this);
+ // Track the currently open tooltip
+ me.currentTippy = point._tippy;
// for the combined / single plot case, we want to have point hover highlight the given series
// by using opacity to "push" the other points and lines to the background
@@ -1491,23 +1494,6 @@ Ext4.define('LABKEY.targetedms.QCTrendPlotPanel', {
legendItems.attr('fill-opacity', legendOpacityAcc).attr('stroke-opacity', legendOpacityAcc);
},
- attachHopscotchMouseClose: function() {
- var closeTask = new Ext4.util.DelayedTask();
- var h = Ext4.select('.hopscotch-bubble-container');
-
- // on mouseout call the delayed task to close the callout
- h.on('mouseout', function() {
- closeTask.delay(1000, function() {
- hopscotch.getCalloutManager().removeAllCallouts();
- });
- });
-
- // if the mouseover happens again for this element before the delay, cancel it to keep callout open
- h.on('mouseover', function() {
- closeTask.cancel();
- });
- },
-
plotBrushStartEvent : function(plot) {
this.clearPlotBrush(plot);
},