Enable horizontal scrolling with snap-to-default on mobile topic tree (#1034)
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: thomasnordquist <7721625+thomasnordquist@users.noreply.github.com>
This commit is contained in:
@@ -13,6 +13,9 @@ const MovingAverage = require('moving-average')
|
|||||||
const averagingTimeInterval = 10 * 1000
|
const averagingTimeInterval = 10 * 1000
|
||||||
const average = MovingAverage(averagingTimeInterval)
|
const average = MovingAverage(averagingTimeInterval)
|
||||||
|
|
||||||
|
// Mobile viewport breakpoint - matches CSS media queries in ContentView
|
||||||
|
const MOBILE_BREAKPOINT = 768
|
||||||
|
|
||||||
declare var window: any
|
declare var window: any
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@@ -26,6 +29,7 @@ interface Props {
|
|||||||
|
|
||||||
interface State {
|
interface State {
|
||||||
lastUpdate: number
|
lastUpdate: number
|
||||||
|
isMobile: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
function useArrowKeyEventHandler(actions: typeof treeActions) {
|
function useArrowKeyEventHandler(actions: typeof treeActions) {
|
||||||
@@ -53,12 +57,16 @@ function useArrowKeyEventHandler(actions: typeof treeActions) {
|
|||||||
|
|
||||||
class TreeComponent extends React.PureComponent<Props, State> {
|
class TreeComponent extends React.PureComponent<Props, State> {
|
||||||
private updateTimer?: any
|
private updateTimer?: any
|
||||||
|
private resizeTimer?: any
|
||||||
private perf: number = 0
|
private perf: number = 0
|
||||||
private renderTime = 0
|
private renderTime = 0
|
||||||
|
|
||||||
constructor(props: any) {
|
constructor(props: any) {
|
||||||
super(props)
|
super(props)
|
||||||
this.state = { lastUpdate: 0 }
|
this.state = {
|
||||||
|
lastUpdate: 0,
|
||||||
|
isMobile: typeof window !== 'undefined' && window.innerWidth <= MOBILE_BREAKPOINT,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private keyEventHandler = useArrowKeyEventHandler(this.props.actions)
|
private keyEventHandler = useArrowKeyEventHandler(this.props.actions)
|
||||||
@@ -66,6 +74,27 @@ class TreeComponent extends React.PureComponent<Props, State> {
|
|||||||
average.push(Date.now(), ms)
|
average.push(Date.now(), ms)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private handleResize = () => {
|
||||||
|
// Debounce resize events - only update after user stops resizing
|
||||||
|
if (this.resizeTimer) {
|
||||||
|
clearTimeout(this.resizeTimer)
|
||||||
|
}
|
||||||
|
|
||||||
|
this.resizeTimer = setTimeout(() => {
|
||||||
|
const isMobile = typeof window !== 'undefined' && window.innerWidth <= MOBILE_BREAKPOINT
|
||||||
|
if (this.state.isMobile !== isMobile) {
|
||||||
|
this.setState({ isMobile })
|
||||||
|
}
|
||||||
|
this.resizeTimer = undefined
|
||||||
|
}, 150) // Wait 150ms after last resize event
|
||||||
|
}
|
||||||
|
|
||||||
|
public componentDidMount() {
|
||||||
|
if (typeof window !== 'undefined') {
|
||||||
|
window.addEventListener('resize', this.handleResize)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public componentWillReceiveProps(nextProps: Props) {
|
public componentWillReceiveProps(nextProps: Props) {
|
||||||
if (this.props.tree !== nextProps.tree) {
|
if (this.props.tree !== nextProps.tree) {
|
||||||
if (this.props.tree) {
|
if (this.props.tree) {
|
||||||
@@ -80,6 +109,18 @@ class TreeComponent extends React.PureComponent<Props, State> {
|
|||||||
|
|
||||||
public componentWillUnmount() {
|
public componentWillUnmount() {
|
||||||
this.props.tree && this.props.tree.didUpdate.unsubscribe(this.throttledTreeUpdate)
|
this.props.tree && this.props.tree.didUpdate.unsubscribe(this.throttledTreeUpdate)
|
||||||
|
if (typeof window !== 'undefined') {
|
||||||
|
window.removeEventListener('resize', this.handleResize)
|
||||||
|
}
|
||||||
|
// Clean up any pending timers to prevent memory leaks
|
||||||
|
if (this.resizeTimer) {
|
||||||
|
clearTimeout(this.resizeTimer)
|
||||||
|
this.resizeTimer = undefined
|
||||||
|
}
|
||||||
|
if (this.updateTimer) {
|
||||||
|
clearTimeout(this.updateTimer)
|
||||||
|
this.updateTimer = undefined
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public throttledTreeUpdate = () => {
|
public throttledTreeUpdate = () => {
|
||||||
@@ -127,30 +168,47 @@ class TreeComponent extends React.PureComponent<Props, State> {
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { isMobile } = this.state
|
||||||
|
|
||||||
const style: React.CSSProperties = {
|
const style: React.CSSProperties = {
|
||||||
lineHeight: '1.1',
|
lineHeight: '1.1',
|
||||||
cursor: 'default',
|
cursor: 'default',
|
||||||
overflowY: 'scroll',
|
overflowY: 'scroll',
|
||||||
overflowX: 'hidden',
|
overflowX: isMobile ? 'auto' : 'hidden', // Enable horizontal scrolling on mobile
|
||||||
height: '100%',
|
height: '100%',
|
||||||
width: '100%',
|
width: '100%',
|
||||||
outline: '24px black !important',
|
outline: '24px black !important',
|
||||||
paddingBottom: '16px', // avoid conflict with chart panel Resizer
|
paddingBottom: '16px', // avoid conflict with chart panel Resizer
|
||||||
|
// Scroll snap to default position on mobile
|
||||||
|
...(isMobile && {
|
||||||
|
scrollSnapType: 'x mandatory',
|
||||||
|
WebkitOverflowScrolling: 'touch', // Smooth scrolling on iOS
|
||||||
|
}),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const treeNode = (
|
||||||
|
<TreeNode
|
||||||
|
key={tree.hash()}
|
||||||
|
isRoot={true}
|
||||||
|
treeNode={tree}
|
||||||
|
name={this.props.host}
|
||||||
|
collapsed={false}
|
||||||
|
settings={this.props.settings}
|
||||||
|
lastUpdate={tree.lastUpdate}
|
||||||
|
actions={this.props.actions}
|
||||||
|
selectTopicAction={this.props.actions.selectTopic}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={style} tabIndex={0} onKeyDown={this.keyEventHandler}>
|
<div style={style} tabIndex={0} onKeyDown={this.keyEventHandler}>
|
||||||
<TreeNode
|
{isMobile ? (
|
||||||
key={tree.hash()}
|
<div style={{ scrollSnapAlign: 'start', minWidth: '100%' }}>
|
||||||
isRoot={true}
|
{treeNode}
|
||||||
treeNode={tree}
|
</div>
|
||||||
name={this.props.host}
|
) : (
|
||||||
collapsed={false}
|
treeNode
|
||||||
settings={this.props.settings}
|
)}
|
||||||
lastUpdate={tree.lastUpdate}
|
|
||||||
actions={this.props.actions}
|
|
||||||
selectTopicAction={this.props.actions.selectTopic}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user