From 6e861841749b7b69663ab59044650f4d895073ef Mon Sep 17 00:00:00 2001 From: Manuel Trezza <5673677+mtrezza@users.noreply.github.com> Date: Tue, 9 Sep 2025 16:21:43 +0200 Subject: [PATCH 1/7] feat --- src/dashboard/Data/Views/Views.react.js | 89 +++++++++++++++++++++++-- src/dashboard/Data/Views/Views.scss | 21 ++++++ 2 files changed, 104 insertions(+), 6 deletions(-) diff --git a/src/dashboard/Data/Views/Views.react.js b/src/dashboard/Data/Views/Views.react.js index 88f0b98a24..5841e22ab8 100644 --- a/src/dashboard/Data/Views/Views.react.js +++ b/src/dashboard/Data/Views/Views.react.js @@ -550,12 +550,36 @@ class Views extends TableView { } renderHeaders() { - return this.state.order.map(({ name, width }, i) => ( -
- {name} - this.handleResize(i, delta)} /> -
- )); + return this.state.order.map(({ name, width }, i) => { + const columnType = this.state.columns[name]?.type; + const isPointerColumn = columnType === 'Pointer'; + + return ( +
+ + {name} + {isPointerColumn && ( + { + e.stopPropagation(); + e.preventDefault(); + this.handleOpenAllPointers(name); + }} + title="Open all pointers in new tabs" + > + + + )} + + this.handleResize(i, delta)} /> +
+ ); + }); } renderEmpty() { @@ -831,6 +855,59 @@ class Views extends TableView { this.setState({ viewValue: value }); } + handleOpenAllPointers(columnName) { + const data = this.tableData(); + const pointers = data + .map(row => row[columnName]) + .filter(value => value && value.__type === 'Pointer' && value.className && value.objectId); + + // Open each unique pointer in a new tab + const uniquePointers = new Map(); + pointers.forEach(pointer => { + const key = `${pointer.className}-${pointer.objectId}`; + if (!uniquePointers.has(key)) { + uniquePointers.set(key, pointer); + } + }); + + if (uniquePointers.size === 0) { + this.showNote('No pointers found in this column', true); + return; + } + + const pointersArray = Array.from(uniquePointers.values()); + + // Open all tabs immediately to maintain user activation context + let successCount = 0; + + pointersArray.forEach((pointer) => { + try { + const filters = JSON.stringify([{ field: 'objectId', constraint: 'eq', compareTo: pointer.objectId }]); + const url = generatePath( + this.context, + `browser/${pointer.className}?filters=${encodeURIComponent(filters)}`, + true + ); + const newWindow = window.open(url, '_blank'); + + if (newWindow) { + successCount++; + } + } catch { + // Error handled by final notification logic + } + }); + + // Show result notification + if (successCount === pointersArray.length) { + this.showNote(`Opened ${successCount} pointer${successCount > 1 ? 's' : ''} in new tab${successCount > 1 ? 's' : ''}`, false); + } else if (successCount > 0) { + this.showNote(`Opened ${successCount} of ${pointersArray.length} tabs. ${pointersArray.length - successCount} blocked by popup blocker.`, true); + } else { + this.showNote('Unable to open tabs. Please allow popups for this site and try again.', true); + } + } + showNote(message, isError) { if (!message) { return; diff --git a/src/dashboard/Data/Views/Views.scss b/src/dashboard/Data/Views/Views.scss index 03c6794437..a65e7e18ff 100644 --- a/src/dashboard/Data/Views/Views.scss +++ b/src/dashboard/Data/Views/Views.scss @@ -18,6 +18,27 @@ text-overflow: ellipsis; } +.headerText { + display: flex; + align-items: center; + gap: 6px; +} + +.pointerIcon { + cursor: pointer; + opacity: 0.7; + transition: opacity 0.2s ease; + z-index: 10; + pointer-events: auto; + display: inline-flex; + align-items: center; + justify-content: center; + + &:hover { + opacity: 1; + } +} + .handle { position: absolute; top: 0; From 029a4ee442e6c3f2da8ce7585d5b2d7caaec8073 Mon Sep 17 00:00:00 2001 From: Manuel Trezza <5673677+mtrezza@users.noreply.github.com> Date: Tue, 9 Sep 2025 16:30:13 +0200 Subject: [PATCH 2/7] icon style --- src/dashboard/Data/Views/Views.react.js | 5 +++-- src/dashboard/Data/Views/Views.scss | 14 +++++++++++++- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/src/dashboard/Data/Views/Views.react.js b/src/dashboard/Data/Views/Views.react.js index 5841e22ab8..fdc07d6ef2 100644 --- a/src/dashboard/Data/Views/Views.react.js +++ b/src/dashboard/Data/Views/Views.react.js @@ -570,8 +570,9 @@ class Views extends TableView { > )} diff --git a/src/dashboard/Data/Views/Views.scss b/src/dashboard/Data/Views/Views.scss index a65e7e18ff..d48b2c72b0 100644 --- a/src/dashboard/Data/Views/Views.scss +++ b/src/dashboard/Data/Views/Views.scss @@ -21,7 +21,8 @@ .headerText { display: flex; align-items: center; - gap: 6px; + justify-content: space-between; + width: 100%; } .pointerIcon { @@ -33,9 +34,20 @@ display: inline-flex; align-items: center; justify-content: center; + height: 20px; + width: 20px; + background: rgba(255, 255, 255, 0.2); + border-radius: 50%; + margin-left: 5px; + flex-shrink: 0; + + & svg { + transform: rotate(316deg); + } &:hover { opacity: 1; + background: rgba(255, 255, 255, 0.3); } } From 6455852bd146ab4c3e3cc999ea8ad28298e87860 Mon Sep 17 00:00:00 2001 From: Manuel Trezza <5673677+mtrezza@users.noreply.github.com> Date: Tue, 9 Sep 2025 16:34:01 +0200 Subject: [PATCH 3/7] partial pointer clumn --- src/dashboard/Data/Views/Views.react.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/dashboard/Data/Views/Views.react.js b/src/dashboard/Data/Views/Views.react.js index fdc07d6ef2..e43896eeba 100644 --- a/src/dashboard/Data/Views/Views.react.js +++ b/src/dashboard/Data/Views/Views.react.js @@ -277,6 +277,9 @@ class Views extends TableView { } if (!columns[key]) { columns[key] = { type, width: Math.min(computeWidth(key), 200) }; + } else if (type === 'Pointer' && columns[key].type !== 'Pointer') { + // If we find a pointer value, upgrade the column type to Pointer + columns[key].type = 'Pointer'; } const width = computeWidth(val); if (width > columns[key].width && columns[key].width < 200) { From 9b2aaacf83299d9b30f9eb3397471822c9bed514 Mon Sep 17 00:00:00 2001 From: Manuel Trezza <5673677+mtrezza@users.noreply.github.com> Date: Tue, 9 Sep 2025 16:37:59 +0200 Subject: [PATCH 4/7] error --- src/dashboard/Data/Views/Views.react.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/dashboard/Data/Views/Views.react.js b/src/dashboard/Data/Views/Views.react.js index e43896eeba..0590edf3ba 100644 --- a/src/dashboard/Data/Views/Views.react.js +++ b/src/dashboard/Data/Views/Views.react.js @@ -897,8 +897,8 @@ class Views extends TableView { if (newWindow) { successCount++; } - } catch { - // Error handled by final notification logic + } catch (error) { + console.error('Failed to open tab for pointer:', pointer, error); } }); From c8f2019078bbedf30f046e77a4ac51bd02da6ad5 Mon Sep 17 00:00:00 2001 From: Manuel Trezza <5673677+mtrezza@users.noreply.github.com> Date: Tue, 9 Sep 2025 18:17:28 +0200 Subject: [PATCH 5/7] fixes --- src/dashboard/Data/Views/Views.react.js | 41 ++++++++++++++++--------- src/dashboard/Data/Views/Views.scss | 25 ++++++++++++++- 2 files changed, 50 insertions(+), 16 deletions(-) diff --git a/src/dashboard/Data/Views/Views.react.js b/src/dashboard/Data/Views/Views.react.js index 0590edf3ba..3fe204cf02 100644 --- a/src/dashboard/Data/Views/Views.react.js +++ b/src/dashboard/Data/Views/Views.react.js @@ -560,15 +560,17 @@ class Views extends TableView { return (
- {name} + {name} {isPointerColumn && ( - { e.stopPropagation(); e.preventDefault(); this.handleOpenAllPointers(name); }} + aria-label={`Open all pointers in ${name} column in new tabs`} title="Open all pointers in new tabs" > - + )} this.handleResize(i, delta)} /> @@ -851,7 +853,8 @@ class Views extends TableView { `browser/${className}?filters=${encodeURIComponent(filters)}`, true ), - '_blank' + '_blank', + 'noopener,noreferrer' ); } @@ -868,7 +871,8 @@ class Views extends TableView { // Open each unique pointer in a new tab const uniquePointers = new Map(); pointers.forEach(pointer => { - const key = `${pointer.className}-${pointer.objectId}`; + // Use a more collision-proof key format with explicit separators + const key = `className:${pointer.className}|objectId:${pointer.objectId}`; if (!uniquePointers.has(key)) { uniquePointers.set(key, pointer); } @@ -881,8 +885,16 @@ class Views extends TableView { const pointersArray = Array.from(uniquePointers.values()); + // Confirm for large numbers of tabs to prevent overwhelming the user + if (pointersArray.length > 10) { + const confirmMessage = `This will open ${pointersArray.length} new tabs. This might overwhelm your browser. Continue?`; + if (!confirm(confirmMessage)) { + return; + } + } + // Open all tabs immediately to maintain user activation context - let successCount = 0; + let errorCount = 0; pointersArray.forEach((pointer) => { try { @@ -892,21 +904,20 @@ class Views extends TableView { `browser/${pointer.className}?filters=${encodeURIComponent(filters)}`, true ); - const newWindow = window.open(url, '_blank'); - - if (newWindow) { - successCount++; - } + window.open(url, '_blank', 'noopener,noreferrer'); + // Note: window.open with security attributes may return null even when successful, + // so we assume success unless an exception is thrown } catch (error) { console.error('Failed to open tab for pointer:', pointer, error); + errorCount++; } }); // Show result notification - if (successCount === pointersArray.length) { - this.showNote(`Opened ${successCount} pointer${successCount > 1 ? 's' : ''} in new tab${successCount > 1 ? 's' : ''}`, false); - } else if (successCount > 0) { - this.showNote(`Opened ${successCount} of ${pointersArray.length} tabs. ${pointersArray.length - successCount} blocked by popup blocker.`, true); + if (errorCount === 0) { + this.showNote(`Opened ${pointersArray.length} pointer${pointersArray.length > 1 ? 's' : ''} in new tab${pointersArray.length > 1 ? 's' : ''}`, false); + } else if (errorCount < pointersArray.length) { + this.showNote(`Opened ${pointersArray.length - errorCount} of ${pointersArray.length} tabs. ${errorCount} failed to open.`, true); } else { this.showNote('Unable to open tabs. Please allow popups for this site and try again.', true); } diff --git a/src/dashboard/Data/Views/Views.scss b/src/dashboard/Data/Views/Views.scss index d48b2c72b0..83ec89cde1 100644 --- a/src/dashboard/Data/Views/Views.scss +++ b/src/dashboard/Data/Views/Views.scss @@ -23,9 +23,26 @@ align-items: center; justify-content: space-between; width: 100%; + min-width: 0; // Enable text truncation in flex containers +} + +.headerLabel { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + flex: 1; + min-width: 0; // Enable text truncation } .pointerIcon { + // Reset button styles + border: none; + padding: 0; + font: inherit; + color: inherit; + background: rgba(255, 255, 255, 0.2); + + // Custom styles cursor: pointer; opacity: 0.7; transition: opacity 0.2s ease; @@ -36,7 +53,6 @@ justify-content: center; height: 20px; width: 20px; - background: rgba(255, 255, 255, 0.2); border-radius: 50%; margin-left: 5px; flex-shrink: 0; @@ -49,6 +65,13 @@ opacity: 1; background: rgba(255, 255, 255, 0.3); } + + &:focus { + opacity: 1; + background: rgba(255, 255, 255, 0.4); + outline: 2px solid rgba(255, 255, 255, 0.8); + outline-offset: 1px; + } } .handle { From 8b5b56793c57d8829b8a94996741acc06ddd1cbc Mon Sep 17 00:00:00 2001 From: Manuel Trezza <5673677+mtrezza@users.noreply.github.com> Date: Tue, 9 Sep 2025 18:18:26 +0200 Subject: [PATCH 6/7] button --- src/dashboard/Data/Views/Views.react.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/dashboard/Data/Views/Views.react.js b/src/dashboard/Data/Views/Views.react.js index 3fe204cf02..9ee1663b01 100644 --- a/src/dashboard/Data/Views/Views.react.js +++ b/src/dashboard/Data/Views/Views.react.js @@ -569,6 +569,8 @@ class Views extends TableView { e.stopPropagation(); e.preventDefault(); this.handleOpenAllPointers(name); + // Remove focus after action to follow UX best practices + e.currentTarget.blur(); }} aria-label={`Open all pointers in ${name} column in new tabs`} title="Open all pointers in new tabs" From ebad50249127068642a12311c65e4cdb2dfb5409 Mon Sep 17 00:00:00 2001 From: Manuel Trezza <5673677+mtrezza@users.noreply.github.com> Date: Tue, 9 Sep 2025 18:20:24 +0200 Subject: [PATCH 7/7] lint --- src/dashboard/Data/Views/Views.react.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dashboard/Data/Views/Views.react.js b/src/dashboard/Data/Views/Views.react.js index 9ee1663b01..c2b63df30b 100644 --- a/src/dashboard/Data/Views/Views.react.js +++ b/src/dashboard/Data/Views/Views.react.js @@ -556,7 +556,7 @@ class Views extends TableView { return this.state.order.map(({ name, width }, i) => { const columnType = this.state.columns[name]?.type; const isPointerColumn = columnType === 'Pointer'; - + return (