From 1317df50a7cfe1fabf24089950892a48bdac7fbf Mon Sep 17 00:00:00 2001
From: Benedikt Seeger <benedikt.seeger@ptb.de>
Date: Tue, 11 Mar 2025 09:52:37 +0100
Subject: [PATCH] imporved tool tips

---
 package-lock.json                            | 41 ++++++++++--
 package.json                                 |  5 +-
 src/renderers/InfluenceConditionsRenderer.js | 30 +++++++--
 src/renderers/MeasurementRenderer.js         | 69 +++++++++++++++++---
 src/renderers/MeasuringEquipmentRenderer.js  | 57 +++++++++++++++-
 src/styles.css                               |  6 ++
 6 files changed, 186 insertions(+), 22 deletions(-)

diff --git a/package-lock.json b/package-lock.json
index 6419b97..8ad1778 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,19 +1,19 @@
 {
   "name": "dccviewer-js",
-  "version": "0.1.0",
+  "version": "0.1.1",
   "lockfileVersion": 3,
   "requires": true,
   "packages": {
     "": {
       "name": "dccviewer-js",
-      "version": "0.1.0",
+      "version": "0.1.1",
       "dependencies": {
         "dsiunits-js": "^0.9.1",
         "events": "^3.3.0",
         "jsoneditor": "^9.5.6",
-        "mime": "^4.0.6",
-        "mime-types": "^2.1.35",
         "plotly.js-dist": "^2.18.2",
+        "prismjs": "^1.29.0",
+        "tippy.js": "^6.2.5",
         "xml2js": "^0.4.23"
       },
       "devDependencies": {
@@ -23,6 +23,8 @@
         "identity-obj-proxy": "^3.0.0",
         "jest": "^29.0.0",
         "jest-environment-jsdom": "^29.7.0",
+        "mime": "^4.0.6",
+        "mime-types": "^2.1.35",
         "vite": "^4.5.9"
       }
     },
@@ -2543,6 +2545,16 @@
         "@jridgewell/sourcemap-codec": "^1.4.14"
       }
     },
+    "node_modules/@popperjs/core": {
+      "version": "2.11.8",
+      "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz",
+      "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==",
+      "license": "MIT",
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/popperjs"
+      }
+    },
     "node_modules/@sinclair/typebox": {
       "version": "0.27.8",
       "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz",
@@ -5243,6 +5255,7 @@
       "version": "4.0.6",
       "resolved": "https://registry.npmjs.org/mime/-/mime-4.0.6.tgz",
       "integrity": "sha512-4rGt7rvQHBbaSOF9POGkk1ocRP16Md1x36Xma8sz8h8/vfCUI2OtEIeCqe4Ofes853x4xDoPiFLIT47J5fI/7A==",
+      "dev": true,
       "funding": [
         "https://github.com/sponsors/broofa"
       ],
@@ -5258,6 +5271,7 @@
       "version": "1.52.0",
       "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
       "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
+      "dev": true,
       "license": "MIT",
       "engines": {
         "node": ">= 0.6"
@@ -5267,6 +5281,7 @@
       "version": "2.1.35",
       "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
       "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
+      "dev": true,
       "license": "MIT",
       "dependencies": {
         "mime-db": "1.52.0"
@@ -5643,6 +5658,15 @@
         "url": "https://github.com/chalk/ansi-styles?sponsor=1"
       }
     },
+    "node_modules/prismjs": {
+      "version": "1.29.0",
+      "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.29.0.tgz",
+      "integrity": "sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=6"
+      }
+    },
     "node_modules/prompts": {
       "version": "2.4.2",
       "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz",
@@ -6143,6 +6167,15 @@
         "node": ">=8"
       }
     },
+    "node_modules/tippy.js": {
+      "version": "6.2.5",
+      "resolved": "https://registry.npmjs.org/tippy.js/-/tippy.js-6.2.5.tgz",
+      "integrity": "sha512-UIf8G99PMXGmdWPrr36s/DjQBdfxMPwzvPUXsxs3tDFDTZ1SgvKG+Jvt6RJ+aBqYL0oe/STxh3MNkCV3IWAKmw==",
+      "license": "MIT",
+      "dependencies": {
+        "@popperjs/core": "^2.4.4"
+      }
+    },
     "node_modules/tmpl": {
       "version": "1.0.5",
       "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz",
diff --git a/package.json b/package.json
index 5f12e85..da02f4d 100644
--- a/package.json
+++ b/package.json
@@ -25,6 +25,7 @@
     "events": "^3.3.0",
     "jsoneditor": "^9.5.6",
     "plotly.js-dist": "^2.18.2",
+    "tippy.js": "^6.2.5",
     "xml2js": "^0.4.23"
   },
   "devDependencies": {
@@ -34,9 +35,9 @@
     "identity-obj-proxy": "^3.0.0",
     "jest": "^29.0.0",
     "jest-environment-jsdom": "^29.7.0",
-    "vite": "^4.5.9",
     "mime": "^4.0.6",
-    "mime-types": "^2.1.35"
+    "mime-types": "^2.1.35",
+    "vite": "^4.5.9"
   },
   "babel": {
     "presets": [
diff --git a/src/renderers/InfluenceConditionsRenderer.js b/src/renderers/InfluenceConditionsRenderer.js
index 3ed8474..39ab26c 100644
--- a/src/renderers/InfluenceConditionsRenderer.js
+++ b/src/renderers/InfluenceConditionsRenderer.js
@@ -1,5 +1,7 @@
 // src/renderers/InfluenceConditionsRenderer.js
 import { DSIUnit } from "dsiunits-js";
+import tippy from 'tippy.js';
+import 'tippy.js/dist/tippy.css';
 
 export class InfluenceConditionsRenderer {
     constructor(influenceConditionsData, language) {
@@ -95,9 +97,12 @@ export class InfluenceConditionsRenderer {
                     if (Object.keys(additional).length > 0) {
                         const infoSpan = document.createElement('span');
                         infoSpan.textContent = ' ⓘ';
-                        infoSpan.style.cursor = 'pointer';
-                        infoSpan.style.color = '#888';
-                        infoSpan.title = JSON.stringify(additional, null, 2);
+                        tippy(infoSpan, {
+                            content: formatAdditionalInfoHTML(additional),
+                            allowHTML: true,
+                            theme: 'light'
+                        })
+
                         valueCell.appendChild(infoSpan);
                     }
                     row.appendChild(valueCell);
@@ -230,7 +235,7 @@ export class InfluenceConditionsRenderer {
 
     // Helper: extract additional data (all keys except the common ones) for tooltip purposes
     _getAdditionalData(quantity) {
-        const exclude = ['dcc:name', 'dcc:description', 'si:real', 'si:realListXMLList', 'dcc:noQuantity'];
+        const exclude = ['dcc:name', 'dcc:description', 'si:real', 'si:realListXMLList'];
         const additional = {};
         Object.keys(quantity).forEach(key => {
             if (!exclude.includes(key)) {
@@ -240,3 +245,20 @@ export class InfluenceConditionsRenderer {
         return additional;
     }
 }
+
+function formatAdditionalInfoHTML(additional) {
+    if (typeof additional !== 'object' || additional === null) {
+        return String(additional);
+    }
+    let parts = [];
+    for (const [key, value] of Object.entries(additional)) {
+        const valStr = (typeof value === 'object' && value !== null)
+            ? JSON.stringify(value)
+            : String(value);
+        parts.push(
+            `<span class="tooltip-key" style="color:#007acc;">${key}</span>: ` +
+            `<span class="tooltip-value" style="color:#cc7000;">${valStr}</span>`
+        );
+    }
+    return parts.join(', ');
+}
\ No newline at end of file
diff --git a/src/renderers/MeasurementRenderer.js b/src/renderers/MeasurementRenderer.js
index 52757c0..568e27a 100644
--- a/src/renderers/MeasurementRenderer.js
+++ b/src/renderers/MeasurementRenderer.js
@@ -333,10 +333,44 @@ export function renderSingleMeasurementResult(resultObj, language, tabPanel) {
       uncertaintiesArray.push(info.uncertainty || []);
       conformityArray.push(info.conformity ? info.conformity.getConformityValues() : []);
     });
-    // Remove the Comments column entirely
-    // headers.push('Comments');
+    let commentsArray = [];
+    if (listData['dcc:measurementMetaData'] && listData['dcc:measurementMetaData']['dcc:metaData']) {
+      let metaData = listData['dcc:measurementMetaData']['dcc:metaData'];
+      if (!Array.isArray(metaData)) { metaData = [metaData]; }
+      metaData.forEach(md => {
+        if (md.$ && md.$.refType && md.$.refType.includes('basic_tableRowComment')) {
+          let commentName = '';
+          if (md['dcc:name'] && md['dcc:name']['dcc:content']) {
+            let content = md['dcc:name']['dcc:content'];
+            commentName = Array.isArray(content)
+                ? (content.find(item => item.$ && item.$.lang === language) || content[0])._ || ''
+                : content._ || content;
+          }
+          let commentDescription = '';
+          if (md['dcc:description'] && md['dcc:description']['dcc:content']) {
+            let desc = md['dcc:description']['dcc:content'];
+            commentDescription = Array.isArray(desc)
+                ? (desc.find(item => item.$ && item.$.lang === language) || desc[0])._ || ''
+                : desc._ || desc;
+          }
+          let validListStr = '';
+          if (md['dcc:validXMLList']) {
+            validListStr = md['dcc:validXMLList'].trim();
+          }
+          let validArray = validListStr.split(/\s+/);
+          commentsArray.push({
+            name: commentName,
+            description: commentDescription,
+            valid: validArray
+          });
+        }
+      });
+    }
+    headers.push('Comments');
+
 
     const tableData = [headers];
+// Then, when building each data row:
     for (let i = 0; i < xValues.length; i++) {
       const row = [];
       row.push(xValues[i]);
@@ -350,6 +384,22 @@ export function renderSingleMeasurementResult(resultObj, language, tabPanel) {
           row.push(conformityArray[idx][i] || '');
         }
       });
+      // NEW: Build comment cell by checking each comment's valid array for this row
+            let tooltipText = '';
+            let headerText = '';
+            commentsArray.forEach(comment => {
+                  if (comment.valid[i] && comment.valid[i].toLowerCase() === 'true') {
+                        tooltipText += `${comment.description}\n`;
+                       if (!headerText) {
+                              headerText = comment.name;
+                          }
+                    }
+              });
+      if (tooltipText) {
+        row.push(`<strong>${headerText}</strong> <details style="display:inline;"><summary style="cursor:pointer; display:inline; list-style:none;">&#9660;</summary><div>${tooltipText.trim()}</div></details>`);
+      } else {
+        row.push('');
+      }
       tableData.push(row);
     }
     renderTable(tableData, computeConformityMapping());
@@ -528,7 +578,12 @@ export function renderSingleMeasurementResult(resultObj, language, tabPanel) {
             cell.style.backgroundColor = conformityMapping[cellIndex];
           }
         } else {
-          cell.textContent = cellData;
+          // For non-header rows, if this is the last column (Comments), use innerHTML.
+          if (cellIndex === rowData.length - 1) {
+            cell.innerHTML = cellData;
+          } else {
+            cell.textContent = cellData;
+          }
           if (
               tableData[0][cellIndex] &&
               tableData[0][cellIndex].toLowerCase().includes('conformity') &&
@@ -544,12 +599,8 @@ export function renderSingleMeasurementResult(resultObj, language, tabPanel) {
         cell.style.border = '1px solid #ccc';
         tr.appendChild(cell);
       });
-      tr.addEventListener('mouseover', () => {
-        tr.style.backgroundColor = '#eef';
-      });
-      tr.addEventListener('mouseout', () => {
-        tr.style.backgroundColor = '';
-      });
+      tr.addEventListener('mouseover', () => { tr.style.backgroundColor = '#eef'; });
+      tr.addEventListener('mouseout', () => { tr.style.backgroundColor = ''; });
       table.appendChild(tr);
     });
     tableContainer.appendChild(table);
diff --git a/src/renderers/MeasuringEquipmentRenderer.js b/src/renderers/MeasuringEquipmentRenderer.js
index defba94..e6125b6 100644
--- a/src/renderers/MeasuringEquipmentRenderer.js
+++ b/src/renderers/MeasuringEquipmentRenderer.js
@@ -1,5 +1,7 @@
 // src/renderers/MeasuringEquipmentsRenderer.js
-import { DCCRealListQuantity } from '../dccQuantity.js';
+import {DCCQuantity, DCCRealListQuantity, DCCRealQuantity} from '../dccQuantity.js';
+import tippy from 'tippy.js';
+import 'tippy.js/dist/tippy.css';
 
 export class MeasuringEquipmentsRenderer {
     constructor(measuringEquipmentsData, language) {
@@ -92,7 +94,7 @@ export class MeasuringEquipmentsRenderer {
 
                 // Table header
                 const headerRow = document.createElement('tr');
-                ['Quantity', 'Value'].forEach(text => {
+                ['Quantity', 'Value','Description'].forEach(text => {
                     const th = document.createElement('th');
                     th.textContent = text;
                     th.style.border = '1px solid #ccc';
@@ -112,10 +114,30 @@ export class MeasuringEquipmentsRenderer {
                     const valueCell = document.createElement('td');
                     valueCell.style.border = '1px solid #ccc';
                     valueCell.style.padding = '4px';
-                    const qtyObj = new DCCRealListQuantity(q);
+                    const qtyObj = new DCCRealQuantity(q);
                     // Use the DSIUnit library to convert the unit string into HTML
                     valueCell.innerHTML = qtyObj.getValues().join(' ') + ' ' + qtyObj.getUnit({ oneLine: true });
+                    // Attach tooltip for additional data if present
+                    const additional = this._getAdditionalData(q);
+                    if (Object.keys(additional).length > 0) {
+                        const infoSpan = document.createElement('span');
+                        infoSpan.textContent = ' ⓘ';
+                        infoSpan.style.cursor = 'pointer';
+                        infoSpan.style.color = '#888';
+                        valueCell.appendChild(infoSpan);
+                        tippy(infoSpan, {
+                            content: formatAdditionalInfoHTML(additional),
+                            allowHTML: true,
+                            theme: 'light'
+                        });
+                    }
                     row.appendChild(valueCell);
+                    // Column 3: Description for the quantity
+                    const descCell = document.createElement('td');
+                    descCell.style.border = '1px solid #ccc';
+                    descCell.style.padding = '4px';
+                    descCell.textContent = this._getText(q['dcc:description']);
+                    row.appendChild(descCell);
                     table.appendChild(row);
                 });
                 contentDiv.appendChild(table);
@@ -138,4 +160,33 @@ export class MeasuringEquipmentsRenderer {
         }
         return content._ || content;
     }
+
+    // Helper: extract additional data (all keys except the common ones) for tooltip purposes
+    _getAdditionalData(quantity) {
+        const exclude = ['dcc:name', 'dcc:description', 'si:real', 'si:realListXMLList'];
+        const additional = {};
+        Object.keys(quantity).forEach(key => {
+            if (!exclude.includes(key)) {
+                additional[key] = quantity[key];
+            }
+        });
+        return additional;
+    }
 }
+
+function formatAdditionalInfoHTML(additional) {
+    if (typeof additional !== 'object' || additional === null) {
+        return String(additional);
+    }
+    let parts = [];
+    for (const [key, value] of Object.entries(additional)) {
+        const valStr = (typeof value === 'object' && value !== null)
+            ? JSON.stringify(value)
+            : String(value);
+        parts.push(
+            `<span class="tooltip-key" style="color:#007acc;">${key}</span>: ` +
+            `<span class="tooltip-value" style="color:#cc7000;">${valStr}</span>`
+        );
+    }
+    return parts.join(', ');
+}
\ No newline at end of file
diff --git a/src/styles.css b/src/styles.css
index a5dc683..2525970 100644
--- a/src/styles.css
+++ b/src/styles.css
@@ -32,3 +32,9 @@ th, td {
   padding: 10px;
   border: 1px dashed #999;
 }
+
+/* Override Tippy's light theme padding */
+.tippy-box[data-theme~="light"] {
+  padding: 10px !important;  /* Increase padding */
+  font-size: 14px;  /* Adjust font size if needed */
+}
-- 
GitLab