Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
## Unreleased

* [Fix blank page when resizing gantt to minimum width](https://github.com/flower-platform/react-timeline-10000/pull/116)

* [ContextMenu: scrollable and always visible](https://github.com/flower-platform/react-timeline-10000/pull/114/changes)

## v2.15.0
Expand Down
2 changes: 1 addition & 1 deletion src/components/BackgroundLayer.js
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,7 @@ export class BackgroundLayer extends React.Component {
*/
calculateHorizontalPosition(start, end) {
const intervalMillis = this.props.endDateTimeline.diff(this.props.startDateTimeline, 'milliseconds');
const pixelsPerMillis = (this.props.width - this.props.leftOffset) / intervalMillis;
const pixelsPerMillis = intervalMillis === 0 ? 0 : (this.props.width - this.props.leftOffset) / intervalMillis;
const startAsMoment = convertDateToMoment(start);
const endAsMoment = convertDateToMoment(end);
if (endAsMoment.isBefore(this.props.startDateTimeline) || startAsMoment.isAfter(this.props.endDateTimeline)) {
Expand Down
4 changes: 2 additions & 2 deletions src/components/Scrollbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -168,9 +168,9 @@ export class Scrollbar extends React.Component<ScrollbarProperties, { scrollbarS
}

onScroll() {
const unit_per_px = this.props.pageSize / this.state.scrollbarSize;
const unit_per_px = this.state.scrollbarSize === 0 ? 0 : this.props.pageSize / this.state.scrollbarSize;
const scrollPositionInPixels = this.props.direction == Direction.HORIZONTAL ? this._outterDiv.scrollLeft : this._outterDiv.scrollTop;
this.props.onScroll(this.props.minScrollPosition + unit_per_px * scrollPositionInPixels);
this.props.onScroll?.(this.props.minScrollPosition + unit_per_px * scrollPositionInPixels);
}

getInnerDivSize(): number {
Expand Down
30 changes: 20 additions & 10 deletions src/components/timebar.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,19 @@ export default class Timebar extends React.Component {
this.setState({topBarComponent, bottomBarComponent});
}

setResolutionIfNeeded(resolution) {
if (this.state.resolution.top !== resolution.top || this.state.resolution.bottom !== resolution.bottom) {
this.setState({resolution});
}
}
Comment on lines +38 to +42

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

To ensure defensive programming, it is safer to guard against this.state.resolution being undefined or null before accessing its properties. Using a fallback object prevents potential runtime TypeErrors.

Suggested change
setResolutionIfNeeded(resolution) {
if (this.state.resolution.top !== resolution.top || this.state.resolution.bottom !== resolution.bottom) {
this.setState({resolution});
}
}
setResolutionIfNeeded(resolution) {
const currentResolution = this.state.resolution || {};
if (currentResolution.top !== resolution.top || currentResolution.bottom !== resolution.bottom) {
this.setState({resolution});
}
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nu e nevoie


/**
* On new props we check if a resolution is given, and if not we guess one
* @param {Object} nextProps Props coming in
*/
componentWillReceiveProps(nextProps) {
if (nextProps.top_resolution && nextProps.bottom_resolution) {
this.setState({resolution: {top: nextProps.top_resolution, bottom: nextProps.bottom_resolution}});
this.setResolutionIfNeeded({top: nextProps.top_resolution, bottom: nextProps.bottom_resolution});
} else {
this.guessResolution(nextProps.start, nextProps.end);
}
Expand All @@ -59,7 +65,12 @@ export default class Timebar extends React.Component {
) {
const bottomBarComponent = this.getBottomBar();
const topBarComponent = this.getTopBar();
Comment on lines 66 to 67

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Calling this.getBottomBar() inside componentDidUpdate triggers this.props.setVerticalGridLines(bottomBar) on every update, even if bottomBarComponent hasn't changed. Since getBottomBar has this side effect, it is recommended to separate the side effect (updating vertical grid lines) from the getter method, or only call it when the bottom bar component actually changes.

@valeriu-vi valeriu-vi Jul 29, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are conditie inainte de a fi apelat

this.setState({topBarComponent, bottomBarComponent});
if (
!_.isEqual(this.state.bottomBarComponent, bottomBarComponent) ||
!_.isEqual(this.state.topBarComponent, topBarComponent)
) {
this.setState({topBarComponent, bottomBarComponent});
}
}
}

Expand All @@ -76,20 +87,19 @@ export default class Timebar extends React.Component {
}
const durationMilliSecs = end.diff(start);
/// 1ms -> 1s
if (durationMilliSecs <= 1000) this.setState({resolution: {top: 'second', bottom: 'millisecond'}});
if (durationMilliSecs <= 1000) this.setResolutionIfNeeded({top: 'second', bottom: 'millisecond'});
// 1s -> 2m
else if (durationMilliSecs <= 60 * 2 * 1000) this.setState({resolution: {top: 'minute', bottom: 'second'}});
else if (durationMilliSecs <= 60 * 2 * 1000) this.setResolutionIfNeeded({top: 'minute', bottom: 'second'});
// 2m -> 2h
else if (durationMilliSecs <= 60 * 60 * 2 * 1000) this.setState({resolution: {top: 'hour', bottom: 'minute'}});
else if (durationMilliSecs <= 60 * 60 * 2 * 1000) this.setResolutionIfNeeded({top: 'hour', bottom: 'minute'});
// 2h -> 3d
else if (durationMilliSecs <= 24 * 60 * 60 * 3 * 1000) this.setState({resolution: {top: 'day', bottom: 'hour'}});
else if (durationMilliSecs <= 24 * 60 * 60 * 3 * 1000) this.setResolutionIfNeeded({top: 'day', bottom: 'hour'});
// 1d -> 30d
else if (durationMilliSecs <= 30 * 24 * 60 * 60 * 1000) this.setState({resolution: {top: 'month', bottom: 'day'}});
else if (durationMilliSecs <= 30 * 24 * 60 * 60 * 1000) this.setResolutionIfNeeded({top: 'month', bottom: 'day'});
//30d -> 1y
else if (durationMilliSecs <= 365 * 24 * 60 * 60 * 1000)
this.setState({resolution: {top: 'year', bottom: 'month'}});
else if (durationMilliSecs <= 365 * 24 * 60 * 60 * 1000) this.setResolutionIfNeeded({top: 'year', bottom: 'month'});
// 1y ->
else this.setState({resolution: {top: 'year', bottom: 'year'}});
else this.setResolutionIfNeeded({top: 'year', bottom: 'year'});
}

/**
Expand Down
4 changes: 3 additions & 1 deletion src/timeline.js
Original file line number Diff line number Diff line change
Expand Up @@ -2274,7 +2274,9 @@ export default class Timeline extends React.Component {
* @param { object } verticalGridLines
*/
setVerticalGridLines(verticalGridLines) {
this.setState({verticalGridLines});
if (!_.isEqual(this.state.verticalGridLines, verticalGridLines)) {
this.setState({verticalGridLines});
}
}

handleScrollTable = scrollPos => {
Expand Down
7 changes: 4 additions & 3 deletions src/utils/timeUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export function timeSnap(time, snapMilliseconds) {
*/
export function pixelsPerMillisecond(vis_start, vis_end, total_width) {
const start_end_ms = vis_end.diff(vis_start, 'milliseconds');
return total_width / start_end_ms;
return start_end_ms === 0 ? 0 : total_width / start_end_ms;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

While returning 0 prevents division by zero inside pixelsPerMillisecond, it introduces a division by zero in getTimeAtPixel (line 56), where pixel_location is divided by the result of pixelsPerMillisecond. If pixelsPerMillisecond returns 0, getTimeAtPixel will calculate min_offset as Infinity or NaN, leading to an invalid Moment object and potential runtime errors or blank pages when the mouse is moved.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

}

/**
Expand All @@ -53,7 +53,8 @@ export function getSnapPixelFromDelta(delta, vis_start, vis_end, total_width, sn
* @returns {moment} Moment object
*/
export function getTimeAtPixel(pixel_location, vis_start, vis_end, total_width, snapMilliseconds = 0) {
let min_offset = pixel_location / pixelsPerMillisecond(vis_start, vis_end, total_width);
let pixels_per_ms = pixelsPerMillisecond(vis_start, vis_end, total_width);
let min_offset = pixels_per_ms == 0 ? 0 : pixel_location / pixels_per_ms;
let timeAtPix = vis_start.clone().add(min_offset, 'milliseconds');
if (snapMilliseconds !== 0) timeAtPix = timeSnap(timeAtPix, snapMilliseconds);
return timeAtPix;
Expand Down Expand Up @@ -84,7 +85,7 @@ export function getDurationFromPixels(pixels, vis_start, vis_end, total_width) {
const start_end_ms = vis_end.diff(vis_start, 'milliseconds');
if (start_end_ms === 0) return moment.duration(0, 'milliseconds');
const pixels_per_ms = total_width / start_end_ms;
let millis = pixels / pixels_per_ms;
let millis = pixels_per_ms === 0 ? 0 : pixels / pixels_per_ms;
return moment.duration(millis, 'milliseconds');
}

Expand Down
Loading