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 (