From d4452060de8a05af2e4d7e7e9ba47314e3141903 Mon Sep 17 00:00:00 2001 From: Manuel Trezza <5673677+mtrezza@users.noreply.github.com> Date: Wed, 26 Nov 2025 23:06:41 +0100 Subject: [PATCH 1/5] fix uneven scroll up --- src/dashboard/Data/Browser/DataBrowser.react.js | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/dashboard/Data/Browser/DataBrowser.react.js b/src/dashboard/Data/Browser/DataBrowser.react.js index aeeddc3c2..ea0c9828a 100644 --- a/src/dashboard/Data/Browser/DataBrowser.react.js +++ b/src/dashboard/Data/Browser/DataBrowser.react.js @@ -168,6 +168,7 @@ export default class DataBrowser extends React.Component { this.aggregationPanelRef = React.createRef(); this.panelColumnRefs = []; this.multiPanelWrapperRef = React.createRef(); + this.isSyncingPanelScroll = false; } componentWillReceiveProps(props) { @@ -908,10 +909,11 @@ export default class DataBrowser extends React.Component { } handlePanelScroll(event, index) { - if (!this.state.syncPanelScroll || this.state.panelCount <= 1) { + if (!this.state.syncPanelScroll || this.state.panelCount <= 1 || this.isSyncingPanelScroll) { return; } + this.isSyncingPanelScroll = true; // Sync scroll position to all other panel columns const scrollTop = event.target.scrollTop; this.panelColumnRefs.forEach((ref, i) => { @@ -919,6 +921,9 @@ export default class DataBrowser extends React.Component { ref.current.scrollTop = scrollTop; } }); + requestAnimationFrame(() => { + this.isSyncingPanelScroll = false; + }); } handleWrapperWheel(event) { @@ -929,6 +934,7 @@ export default class DataBrowser extends React.Component { // Prevent default scrolling event.preventDefault(); + this.isSyncingPanelScroll = true; // Apply scroll to all columns const delta = event.deltaY; this.panelColumnRefs.forEach((ref) => { @@ -936,6 +942,9 @@ export default class DataBrowser extends React.Component { ref.current.scrollTop += delta; } }); + requestAnimationFrame(() => { + this.isSyncingPanelScroll = false; + }); } fetchDataForMultiPanel(objectId) { From ca4eab0ff3c7b8a6bfcca8bfc68b5fea05228252 Mon Sep 17 00:00:00 2001 From: Manuel Trezza <5673677+mtrezza@users.noreply.github.com> Date: Wed, 26 Nov 2025 23:15:36 +0100 Subject: [PATCH 2/5] fix --- .../Data/Browser/DataBrowser.react.js | 93 +++++++++++++++---- 1 file changed, 77 insertions(+), 16 deletions(-) diff --git a/src/dashboard/Data/Browser/DataBrowser.react.js b/src/dashboard/Data/Browser/DataBrowser.react.js index ea0c9828a..e7e010d85 100644 --- a/src/dashboard/Data/Browser/DataBrowser.react.js +++ b/src/dashboard/Data/Browser/DataBrowser.react.js @@ -168,6 +168,7 @@ export default class DataBrowser extends React.Component { this.aggregationPanelRef = React.createRef(); this.panelColumnRefs = []; this.multiPanelWrapperRef = React.createRef(); + this.panelScrollPositions = []; this.isSyncingPanelScroll = false; } @@ -908,24 +909,92 @@ export default class DataBrowser extends React.Component { }); } - handlePanelScroll(event, index) { - if (!this.state.syncPanelScroll || this.state.panelCount <= 1 || this.isSyncingPanelScroll) { + applySyncedScroll(delta, sourceIndex, sourcePreviousScrollTop) { + if ( + !this.state.syncPanelScroll || + this.state.panelCount <= 1 || + this.isSyncingPanelScroll || + delta === 0 + ) { + return; + } + + const panels = this.panelColumnRefs; + if (panels.length === 0) { return; } + const maxScrollTops = panels.map(ref => { + const node = ref?.current; + return node ? Math.max(0, node.scrollHeight - node.clientHeight) : 0; + }); + const minMaxScrollTop = maxScrollTops.length ? Math.min(...maxScrollTops) : 0; + + const previousPositions = panels.map((ref, i) => { + if (i === sourceIndex && sourcePreviousScrollTop !== undefined) { + return sourcePreviousScrollTop; + } + const stored = this.panelScrollPositions[i]; + if (stored !== undefined) { + return stored; + } + const node = ref?.current; + return node ? node.scrollTop : 0; + }); + + const hasPanelBeyondShortest = previousPositions.some(pos => pos > minMaxScrollTop); + this.isSyncingPanelScroll = true; - // Sync scroll position to all other panel columns - const scrollTop = event.target.scrollTop; - this.panelColumnRefs.forEach((ref, i) => { - if (i !== index && ref && ref.current) { - ref.current.scrollTop = scrollTop; + + panels.forEach((ref, i) => { + const node = ref?.current; + if (!node) { + return; } + + const maxScroll = maxScrollTops[i]; + const baseScrollTop = previousPositions[i] ?? 0; + let nextScrollTop = baseScrollTop + delta; + + if (delta < 0) { + if (hasPanelBeyondShortest) { + if (baseScrollTop > minMaxScrollTop) { + nextScrollTop = Math.max(nextScrollTop, minMaxScrollTop); + } else { + // Keep shortest (or already-caught-up) panels pinned until others reach the same position. + nextScrollTop = minMaxScrollTop; + } + } else { + nextScrollTop = Math.max(nextScrollTop, 0); + } + } else { + nextScrollTop = Math.min(nextScrollTop, maxScroll); + } + + nextScrollTop = Math.max(0, Math.min(nextScrollTop, maxScroll)); + node.scrollTop = nextScrollTop; + this.panelScrollPositions[i] = nextScrollTop; }); + requestAnimationFrame(() => { this.isSyncingPanelScroll = false; }); } + handlePanelScroll(event, index) { + const currentScrollTop = event.target.scrollTop; + const previousScrollTop = + this.panelScrollPositions[index] !== undefined ? this.panelScrollPositions[index] : currentScrollTop; + this.panelScrollPositions[index] = currentScrollTop; + + if (!this.state.syncPanelScroll || this.state.panelCount <= 1 || this.isSyncingPanelScroll) { + return; + } + + const delta = currentScrollTop - previousScrollTop; + this.applySyncedScroll(delta, index, previousScrollTop); + } + handleWrapperWheel(event) { if (!this.state.syncPanelScroll || this.state.panelCount <= 1) { return; @@ -934,17 +1003,9 @@ export default class DataBrowser extends React.Component { // Prevent default scrolling event.preventDefault(); - this.isSyncingPanelScroll = true; // Apply scroll to all columns const delta = event.deltaY; - this.panelColumnRefs.forEach((ref) => { - if (ref && ref.current) { - ref.current.scrollTop += delta; - } - }); - requestAnimationFrame(() => { - this.isSyncingPanelScroll = false; - }); + this.applySyncedScroll(delta); } fetchDataForMultiPanel(objectId) { From 5a1019f7eea891349dd42a6d4abef0c83ed103ea Mon Sep 17 00:00:00 2001 From: Manuel Trezza <5673677+mtrezza@users.noreply.github.com> Date: Wed, 26 Nov 2025 23:15:44 +0100 Subject: [PATCH 3/5] Revert "fix" This reverts commit ca4eab0ff3c7b8a6bfcca8bfc68b5fea05228252. --- .../Data/Browser/DataBrowser.react.js | 93 ++++--------------- 1 file changed, 16 insertions(+), 77 deletions(-) diff --git a/src/dashboard/Data/Browser/DataBrowser.react.js b/src/dashboard/Data/Browser/DataBrowser.react.js index e7e010d85..ea0c9828a 100644 --- a/src/dashboard/Data/Browser/DataBrowser.react.js +++ b/src/dashboard/Data/Browser/DataBrowser.react.js @@ -168,7 +168,6 @@ export default class DataBrowser extends React.Component { this.aggregationPanelRef = React.createRef(); this.panelColumnRefs = []; this.multiPanelWrapperRef = React.createRef(); - this.panelScrollPositions = []; this.isSyncingPanelScroll = false; } @@ -909,92 +908,24 @@ export default class DataBrowser extends React.Component { }); } - applySyncedScroll(delta, sourceIndex, sourcePreviousScrollTop) { - if ( - !this.state.syncPanelScroll || - this.state.panelCount <= 1 || - this.isSyncingPanelScroll || - delta === 0 - ) { - return; - } - - const panels = this.panelColumnRefs; - if (panels.length === 0) { + handlePanelScroll(event, index) { + if (!this.state.syncPanelScroll || this.state.panelCount <= 1 || this.isSyncingPanelScroll) { return; } - const maxScrollTops = panels.map(ref => { - const node = ref?.current; - return node ? Math.max(0, node.scrollHeight - node.clientHeight) : 0; - }); - const minMaxScrollTop = maxScrollTops.length ? Math.min(...maxScrollTops) : 0; - - const previousPositions = panels.map((ref, i) => { - if (i === sourceIndex && sourcePreviousScrollTop !== undefined) { - return sourcePreviousScrollTop; - } - const stored = this.panelScrollPositions[i]; - if (stored !== undefined) { - return stored; - } - const node = ref?.current; - return node ? node.scrollTop : 0; - }); - - const hasPanelBeyondShortest = previousPositions.some(pos => pos > minMaxScrollTop); - this.isSyncingPanelScroll = true; - - panels.forEach((ref, i) => { - const node = ref?.current; - if (!node) { - return; + // Sync scroll position to all other panel columns + const scrollTop = event.target.scrollTop; + this.panelColumnRefs.forEach((ref, i) => { + if (i !== index && ref && ref.current) { + ref.current.scrollTop = scrollTop; } - - const maxScroll = maxScrollTops[i]; - const baseScrollTop = previousPositions[i] ?? 0; - let nextScrollTop = baseScrollTop + delta; - - if (delta < 0) { - if (hasPanelBeyondShortest) { - if (baseScrollTop > minMaxScrollTop) { - nextScrollTop = Math.max(nextScrollTop, minMaxScrollTop); - } else { - // Keep shortest (or already-caught-up) panels pinned until others reach the same position. - nextScrollTop = minMaxScrollTop; - } - } else { - nextScrollTop = Math.max(nextScrollTop, 0); - } - } else { - nextScrollTop = Math.min(nextScrollTop, maxScroll); - } - - nextScrollTop = Math.max(0, Math.min(nextScrollTop, maxScroll)); - node.scrollTop = nextScrollTop; - this.panelScrollPositions[i] = nextScrollTop; }); - requestAnimationFrame(() => { this.isSyncingPanelScroll = false; }); } - handlePanelScroll(event, index) { - const currentScrollTop = event.target.scrollTop; - const previousScrollTop = - this.panelScrollPositions[index] !== undefined ? this.panelScrollPositions[index] : currentScrollTop; - this.panelScrollPositions[index] = currentScrollTop; - - if (!this.state.syncPanelScroll || this.state.panelCount <= 1 || this.isSyncingPanelScroll) { - return; - } - - const delta = currentScrollTop - previousScrollTop; - this.applySyncedScroll(delta, index, previousScrollTop); - } - handleWrapperWheel(event) { if (!this.state.syncPanelScroll || this.state.panelCount <= 1) { return; @@ -1003,9 +934,17 @@ export default class DataBrowser extends React.Component { // Prevent default scrolling event.preventDefault(); + this.isSyncingPanelScroll = true; // Apply scroll to all columns const delta = event.deltaY; - this.applySyncedScroll(delta); + this.panelColumnRefs.forEach((ref) => { + if (ref && ref.current) { + ref.current.scrollTop += delta; + } + }); + requestAnimationFrame(() => { + this.isSyncingPanelScroll = false; + }); } fetchDataForMultiPanel(objectId) { From e5664fe8a8c517fdd29453ee81237735594ff470 Mon Sep 17 00:00:00 2001 From: Manuel Trezza <5673677+mtrezza@users.noreply.github.com> Date: Wed, 26 Nov 2025 23:15:47 +0100 Subject: [PATCH 4/5] Revert "fix uneven scroll up" This reverts commit d4452060de8a05af2e4d7e7e9ba47314e3141903. --- src/dashboard/Data/Browser/DataBrowser.react.js | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/src/dashboard/Data/Browser/DataBrowser.react.js b/src/dashboard/Data/Browser/DataBrowser.react.js index ea0c9828a..aeeddc3c2 100644 --- a/src/dashboard/Data/Browser/DataBrowser.react.js +++ b/src/dashboard/Data/Browser/DataBrowser.react.js @@ -168,7 +168,6 @@ export default class DataBrowser extends React.Component { this.aggregationPanelRef = React.createRef(); this.panelColumnRefs = []; this.multiPanelWrapperRef = React.createRef(); - this.isSyncingPanelScroll = false; } componentWillReceiveProps(props) { @@ -909,11 +908,10 @@ export default class DataBrowser extends React.Component { } handlePanelScroll(event, index) { - if (!this.state.syncPanelScroll || this.state.panelCount <= 1 || this.isSyncingPanelScroll) { + if (!this.state.syncPanelScroll || this.state.panelCount <= 1) { return; } - this.isSyncingPanelScroll = true; // Sync scroll position to all other panel columns const scrollTop = event.target.scrollTop; this.panelColumnRefs.forEach((ref, i) => { @@ -921,9 +919,6 @@ export default class DataBrowser extends React.Component { ref.current.scrollTop = scrollTop; } }); - requestAnimationFrame(() => { - this.isSyncingPanelScroll = false; - }); } handleWrapperWheel(event) { @@ -934,7 +929,6 @@ export default class DataBrowser extends React.Component { // Prevent default scrolling event.preventDefault(); - this.isSyncingPanelScroll = true; // Apply scroll to all columns const delta = event.deltaY; this.panelColumnRefs.forEach((ref) => { @@ -942,9 +936,6 @@ export default class DataBrowser extends React.Component { ref.current.scrollTop += delta; } }); - requestAnimationFrame(() => { - this.isSyncingPanelScroll = false; - }); } fetchDataForMultiPanel(objectId) { From 8f011bae5a58d5f1fa597e7c6ff7d8c80b1ed913 Mon Sep 17 00:00:00 2001 From: Manuel Trezza <5673677+mtrezza@users.noreply.github.com> Date: Wed, 26 Nov 2025 23:28:11 +0100 Subject: [PATCH 5/5] Update DataBrowser.react.js --- .../Data/Browser/DataBrowser.react.js | 40 ++++++++++++++++++- 1 file changed, 38 insertions(+), 2 deletions(-) diff --git a/src/dashboard/Data/Browser/DataBrowser.react.js b/src/dashboard/Data/Browser/DataBrowser.react.js index aeeddc3c2..5e168a796 100644 --- a/src/dashboard/Data/Browser/DataBrowser.react.js +++ b/src/dashboard/Data/Browser/DataBrowser.react.js @@ -167,6 +167,8 @@ export default class DataBrowser extends React.Component { this.saveOrderTimeout = null; this.aggregationPanelRef = React.createRef(); this.panelColumnRefs = []; + this.activePanelIndex = -1; + this.isWheelScrolling = false; this.multiPanelWrapperRef = React.createRef(); } @@ -912,6 +914,18 @@ export default class DataBrowser extends React.Component { return; } + if (this.isWheelScrolling) { + return; + } + + if ( + this.activePanelIndex !== -1 && + this.activePanelIndex !== undefined && + this.activePanelIndex !== index + ) { + return; + } + // Sync scroll position to all other panel columns const scrollTop = event.target.scrollTop; this.panelColumnRefs.forEach((ref, i) => { @@ -926,14 +940,33 @@ export default class DataBrowser extends React.Component { return; } + // Set wheel scrolling flag + this.isWheelScrolling = true; + if (this.wheelTimeout) { + clearTimeout(this.wheelTimeout); + } + this.wheelTimeout = setTimeout(() => { + this.isWheelScrolling = false; + }, 100); + // Prevent default scrolling event.preventDefault(); - // Apply scroll to all columns + // Find the maximum scrollTop among all panels to use as the base + let maxScrollTop = 0; + this.panelColumnRefs.forEach((ref) => { + if (ref && ref.current && ref.current.scrollTop > maxScrollTop) { + maxScrollTop = ref.current.scrollTop; + } + }); + + // Apply delta to the max scrollTop and set it to all panels const delta = event.deltaY; + const newScrollTop = maxScrollTop + delta; + this.panelColumnRefs.forEach((ref) => { if (ref && ref.current) { - ref.current.scrollTop += delta; + ref.current.scrollTop = newScrollTop; } }); } @@ -1430,6 +1463,9 @@ export default class DataBrowser extends React.Component {
(this.activePanelIndex = index)} + onTouchStart={() => (this.activePanelIndex = index)} + onFocus={() => (this.activePanelIndex = index)} onScroll={(e) => this.handlePanelScroll(e, index)} > {this.state.showPanelCheckbox && (