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
73 changes: 38 additions & 35 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,49 +38,52 @@
},
"homepage": "https://github.com/razbensimon/react-vis-timeline#readme",
"devDependencies": {
"@babel/cli": "^7.8.4",
"@babel/core": "^7.8.7",
"@babel/plugin-proposal-class-properties": "^7.8.3",
"@babel/plugin-transform-runtime": "^7.8.3",
"@babel/preset-env": "^7.8.7",
"@babel/preset-react": "^7.8.3",
"@babel/preset-typescript": "^7.8.3",
"@types/react": "^16.9.35",
"@typescript-eslint/eslint-plugin": "2.x",
"@typescript-eslint/parser": "2.x",
"babel-eslint": "10.x",
"babel-plugin-import": "^1.13.0",
"@babel/cli": "^7.19.3",
"@babel/core": "^7.20.2",
"@babel/eslint-parser": "^7.19.1",
"@babel/plugin-proposal-class-properties": "^7.18.6",
"@babel/plugin-transform-runtime": "^7.19.6",
"@babel/preset-env": "^7.20.2",
"@babel/preset-react": "^7.18.6",
"@babel/preset-typescript": "^7.18.6",
"@types/lodash": "^4.14.189",
"@types/react": "^18.0.25",
"@typescript-eslint/eslint-plugin": "^5.44.0",
"@typescript-eslint/parser": "^5.44.0",
"babel-plugin-import": "^1.13.5",
"babel-plugin-transform-rename-import": "^2.3.0",
"eslint": "6.x",
"eslint-config-react-app": "^5.2.1",
"eslint-plugin-flowtype": "4.x",
"eslint-plugin-import": "2.x",
"eslint-plugin-jsx-a11y": "6.x",
"eslint-plugin-react": "7.x",
"eslint-plugin-react-hooks": "2.x",
"husky": "^4.2.5",
"lint-staged": "^10.2.2",
"lodash": "^4.17.15",
"moment": "^2.25.3",
"prettier": "2.0.5",
"react": "^0.14 || ^15.0 || ^16.0 || ^17.0",
"react-dom": "^0.14 || ^15.0 || ^16.0 || ^17.0",
"typescript": "^3.9.2"
"eslint": "^8.28.0",
"eslint-config-react-app": "^7.0.1",
"eslint-plugin-flowtype": "^8.0.3",
"eslint-plugin-import": "^2.26.0",
"eslint-plugin-jsx-a11y": "^6.6.1",
"eslint-plugin-react": "^7.31.11",
"eslint-plugin-react-hooks": "^4.6.0",
"husky": "^8.0.2",
"lint-staged": "^13.0.3",
"lodash": "^4.17.21",
"moment": "^2.29.4",
"prettier": "^2.7.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"tslib": "^2.4.1",
"typescript": "^4.9.3"
},
"dependencies": {
"@egjs/hammerjs": "^2.0.17",
"component-emitter": "^1.3.0",
"keycharm": "^0.3.1",
"propagating-hammerjs": "^1.4.7",
"uuid": "^7.0.0",
"vis-data": "^7.1.0",
"vis-timeline": "^7.4.2",
"vis-util": "^4.3.4"
"keycharm": "^0.4.0",
"propagating-hammerjs": "^2.0.1",
"uuid": "^8.3.2",
"vis-data": "^7.1.4",
"vis-timeline": "^7.7.0",
"vis-util": "^5.0.3",
"xss": "^1.0.14"
},
"peerDependencies": {
"lodash": "^4.17.15",
"moment": "^2.25",
"react": "^0.14 || ^15.0 || ^16.0",
"react-dom": "^0.14 || ^15.0 || ^16.0"
"react": "^0.14 || ^15.0 || ^16.0 || ^17.0 || ^18.0",
"react-dom": "^0.14 || ^15.0 || ^16.0 || ^17.0 || ^18.0"
}
}
218 changes: 68 additions & 150 deletions src/timeline.tsx
Original file line number Diff line number Diff line change
@@ -1,26 +1,8 @@
import React, { Component } from 'react';
import { DataSet } from 'vis-data/esnext';
import { Timeline as VisTimelineCtor } from 'vis-timeline/esnext';
import type {
DateType,
IdType,
Timeline as VisTimeline,
TimelineAnimationOptions,
TimelineGroup,
TimelineItem,
TimelineOptions
} from 'vis-timeline/types';
import 'vis-timeline/styles/vis-timeline-graph2d.min.css';

import _difference from 'lodash/difference';
import _intersection from 'lodash/intersection';
import _each from 'lodash/each';
import _assign from 'lodash/assign';
import _omit from 'lodash/omit';
import _keys from 'lodash/keys';
import { difference, intersection, each, omit, keys } from 'lodash';
import type { CustomTime, SelectionOptions, TimelineEventsHandlers, TimelineEventsWithMissing } from './models';

const noop = function () {};
import { DateType, IdType, Timeline as VisTimeline, TimelineGroup, TimelineItem, TimelineOptions } from 'vis-timeline';
import 'vis-timeline/styles/vis-timeline-graph2d.min.css';

const events: TimelineEventsWithMissing[] = [
'currentTimeTick',
Expand All @@ -46,11 +28,6 @@ const events: TimelineEventsWithMissing[] = [
'markerchanged'
];

const eventDefaultProps: TimelineEventsHandlers = {};
_each(events, event => {
eventDefaultProps[`${event}Handler`] = noop;
});

type Props = {
initialItems?: TimelineItem[];
initialGroups?: TimelineGroup[];
Expand All @@ -63,165 +40,106 @@ type Props = {
} & TimelineEventsHandlers;

export class Timeline extends Component<Props, {}> {
static defaultProps = _assign(
{
initialItems: [],
groups: [],
options: {},
selection: [],
customTimes: []
},
eventDefaultProps
);

public timeline: Readonly<VisTimeline>;
public readonly items: DataSet<TimelineItem>;
public readonly groups: DataSet<TimelineGroup>;
public timeline: VisTimeline | null = null;

#ref = React.createRef<HTMLDivElement>();

constructor(props: Props) {
super(props);

Object.defineProperty(this, 'items', {
value: new DataSet<TimelineItem>(),
writable: false
});

Object.defineProperty(this, 'groups', {
value: new DataSet<TimelineGroup>(),
writable: false
});
}

componentWillUnmount() {
this.timeline.destroy();
override componentWillUnmount() {
this.timeline?.destroy();
}

componentDidMount() {
Object.defineProperty(this, 'timeline', {
value: new VisTimelineCtor(this.#ref.current, this.items, this.groups, this.props.options),
writable: false
});

override componentDidMount() {
this.timeline = new VisTimeline(
this.#ref.current,
this.props.initialItems,
this.props.initialGroups,
this.props.options
);
for (const event of events) {
const eventHandler = this.props[`${event}Handler`];
if (eventHandler !== noop) {
this.timeline.on(event, eventHandler);
if (typeof eventHandler === 'function') {
this.timeline.on(event, eventHandler as (properties: any) => void);
}
}

this.init();
}

shouldComponentUpdate(nextProps: Props) {
const { initialItems, initialGroups, options, selection, customTimes, currentTime } = this.props;
const { options, selection, selectionOptions = {}, customTimes, animate = true, currentTime } = this.props;

const itemsChange = initialItems !== nextProps.initialItems;
const groupsChange = initialGroups !== nextProps.initialGroups;
const optionsChange = options !== nextProps.options;
const customTimesChange = customTimes !== nextProps.customTimes;
const selectionChange = selection !== nextProps.selection;
const currentTimeChange = currentTime !== nextProps.currentTime;
let timelineOptions = options;

if (groupsChange) {
console.warn(
"react-vis-timeline: you are trying to change 'initialGroups' prop. Please use the public api exposed with in ref"
);
}
if (animate) {
// If animate option is set, we should animate the timeline to any new
// start/end values instead of jumping straight to them
timelineOptions = omit(options, 'start', 'end');

if (itemsChange) {
console.warn(
"react-vis-timeline: you are trying to change 'initialItems' prop. Please use the public api exposed with in ref"
);
this.timeline.setWindow(options.start, options.end, {
animation: animate
});
}

if (optionsChange) {
this.timeline.setOptions(nextProps.options);
}
this.timeline.setOptions(timelineOptions);
this.updateSelection(selection, selectionOptions);

if (customTimesChange) {
this.updateCustomTimes(customTimes, nextProps.customTimes);
if (currentTime) {
this.timeline.setCurrentTime(currentTime);
}

if (selectionChange) {
this.updateSelection(nextProps.selection, nextProps.selectionOptions);
}
this.updateCustomTimes([], customTimes);
}

if (currentTimeChange) {
this.timeline.setCurrentTime(nextProps.currentTime);
override shouldComponentUpdate(nextProps: Props) {
if (this.timeline) {
const { initialItems, initialGroups, options, selection, customTimes, currentTime } = this.props;
const itemsChange = initialItems !== nextProps.initialItems;
const groupsChange = initialGroups !== nextProps.initialGroups;
const optionsChange = options !== nextProps.options;
const customTimesChange = customTimes !== nextProps.customTimes;
const selectionChange = selection !== nextProps.selection;
const currentTimeChange = currentTime !== nextProps.currentTime;
if (groupsChange) {
this.timeline.setGroups(nextProps.initialGroups);
}
if (itemsChange) {
this.timeline.setItems(nextProps.initialItems);
}
if (optionsChange) {
this.timeline.setOptions(nextProps.options);
}
if (customTimesChange) {
this.updateCustomTimes(customTimes, nextProps.customTimes);
}
if (selectionChange) {
this.updateSelection(nextProps.selection, nextProps.selectionOptions);
}
if (currentTimeChange) {
this.timeline.setCurrentTime(nextProps.currentTime);
}
}

return false;
}

updateCustomTimes(prevCustomTimes: CustomTime[], customTimes: CustomTime[]) {
private updateCustomTimes(prevCustomTimes: CustomTime[], customTimes: CustomTime[]) {
// diff the custom times to decipher new, removing, updating
const customTimeKeysPrev = _keys(prevCustomTimes);
const customTimeKeysNew = _keys(customTimes);
const customTimeKeysToAdd = _difference(customTimeKeysNew, customTimeKeysPrev);
const customTimeKeysToRemove = _difference(customTimeKeysPrev, customTimeKeysNew);
const customTimeKeysToUpdate = _intersection(customTimeKeysPrev, customTimeKeysNew);

_each(customTimeKeysToRemove, id => this.timeline.removeCustomTime(id));
_each(customTimeKeysToAdd, id => {
const customTimeKeysPrev = keys(prevCustomTimes);
const customTimeKeysNew = keys(customTimes);
const customTimeKeysToAdd = difference(customTimeKeysNew, customTimeKeysPrev);
const customTimeKeysToRemove = difference(customTimeKeysPrev, customTimeKeysNew);
const customTimeKeysToUpdate = intersection(customTimeKeysPrev, customTimeKeysNew);
each(customTimeKeysToRemove, id => this.timeline.removeCustomTime(id));
each(customTimeKeysToAdd, id => {
const datetime = customTimes[id].datetime;
this.timeline.addCustomTime(datetime, id);
});
_each(customTimeKeysToUpdate, id => {
each(customTimeKeysToUpdate, id => {
const datetime = customTimes[id].datetime;
this.timeline.setCustomTime(datetime, id);
});
}

updateSelection(selection: IdType | IdType[], selectionOptions: SelectionOptions): void {
private updateSelection(selection: IdType | IdType[], selectionOptions: SelectionOptions): void {
this.timeline.setSelection(selection, selectionOptions as Required<SelectionOptions>);
}

init() {
const {
initialItems,
initialGroups,
options,
selection,
selectionOptions = {},
customTimes,
animate = true,
currentTime
} = this.props;

let timelineOptions = options;

if (animate) {
// If animate option is set, we should animate the timeline to any new
// start/end values instead of jumping straight to them
timelineOptions = _omit(options, 'start', 'end');

this.timeline.setWindow(options.start, options.end, {
animation: animate
} as TimelineAnimationOptions);
}

this.timeline.setOptions(timelineOptions);

if (initialGroups?.length > 0) {
this.groups.add(initialGroups);
}

if (initialItems?.length > 0) {
this.items.add(initialItems);
}

this.updateSelection(selection, selectionOptions);

if (currentTime) {
this.timeline.setCurrentTime(currentTime);
}

this.updateCustomTimes([], customTimes);
}

render() {
override render() {
return <div ref={this.#ref} />;
}
}
Loading