From 24854052215bf04782694cac1f6d712280c1458e Mon Sep 17 00:00:00 2001 From: GabrielCrackPro Date: Sat, 4 Feb 2023 16:52:30 +0100 Subject: [PATCH 1/8] Added demo collision --- demo/app/demo-colision/index.js | 211 ++++++++++++++++++++++++++++++++ demo/app/index.js | 1 + 2 files changed, 212 insertions(+) create mode 100644 demo/app/demo-colision/index.js diff --git a/demo/app/demo-colision/index.js b/demo/app/demo-colision/index.js new file mode 100644 index 000000000..025e966bd --- /dev/null +++ b/demo/app/demo-colision/index.js @@ -0,0 +1,211 @@ +/* eslint-disable no-console */ +import React, { Component } from 'react' +import moment from 'moment' + +import Timeline, { + TimelineMarkers, + TodayMarker, + CustomMarker, + CursorMarker, +} from 'react-calendar-timeline' + +import generateFakeData from '../generate-fake-data' + +var minTime = moment() + .add(-6, 'months') + .valueOf() +var maxTime = moment() + .add(6, 'months') + .valueOf() + +var keys = { + groupIdKey: 'id', + groupTitleKey: 'title', + groupRightTitleKey: 'rightTitle', + itemIdKey: 'id', + itemTitleKey: 'title', + itemDivTitleKey: 'title', + itemGroupKey: 'group', + itemTimeStartKey: 'start', + itemTimeEndKey: 'end' +} + +export default class App extends Component { + constructor(props) { + super(props) + + const { groups, items } = generateFakeData() + const defaultTimeStart = moment() + .startOf('day') + .toDate() + const defaultTimeEnd = moment() + .startOf('day') + .add(1, 'day') + .toDate() + + this.state = { + groups, + items, + defaultTimeStart, + defaultTimeEnd + } + } + + handleCanvasClick = (groupId, time) => { + console.log('Canvas clicked', groupId, moment(time).format()) + } + + handleCanvasDoubleClick = (groupId, time) => { + console.log('Canvas double clicked', groupId, moment(time).format()) + } + + handleCanvasContextMenu = (group, time) => { + console.log('Canvas context menu', group, moment(time).format()) + } + + handleItemClick = (itemId, _, time) => { + console.log('Clicked: ' + itemId, moment(time).format()) + } + + handleItemSelect = (itemId, _, time) => { + console.log('Selected: ' + itemId, moment(time).format()) + } + + handleItemDoubleClick = (itemId, _, time) => { + console.log('Double Click: ' + itemId, moment(time).format()) + } + + handleItemContextMenu = (itemId, _, time) => { + console.log('Context Menu: ' + itemId, moment(time).format()) + } + + handleItemColision = (currentItem, item, time) => { + console.log('Item ' + currentItem + 'collided with ' + item, moment(time).format()) + } + + handleItemMove = (itemId, dragTime, newGroupOrder) => { + const { items, groups } = this.state + + const group = groups[newGroupOrder] + + this.setState({ + items: items.map( + item => + item.id === itemId + ? Object.assign({}, item, { + start: dragTime, + end: dragTime + (item.end - item.start), + group: group.id + }) + : item + ) + }) + + console.log('Moved', itemId, dragTime, newGroupOrder) + } + + handleItemResize = (itemId, time, edge) => { + const { items } = this.state + + this.setState({ + items: items.map( + item => + item.id === itemId + ? Object.assign({}, item, { + start: edge === 'left' ? time : item.start, + end: edge === 'left' ? item.end : time + }) + : item + ) + }) + + console.log('Resized', itemId, time, edge) + } + + // this limits the timeline to -6 months ... +6 months + handleTimeChange = (visibleTimeStart, visibleTimeEnd, updateScrollCanvas) => { + if (visibleTimeStart < minTime && visibleTimeEnd > maxTime) { + updateScrollCanvas(minTime, maxTime) + } else if (visibleTimeStart < minTime) { + updateScrollCanvas(minTime, minTime + (visibleTimeEnd - visibleTimeStart)) + } else if (visibleTimeEnd > maxTime) { + updateScrollCanvas(maxTime - (visibleTimeEnd - visibleTimeStart), maxTime) + } else { + updateScrollCanvas(visibleTimeStart, visibleTimeEnd) + } + } + + handleZoom = (timelineContext, unit) => { + console.log('Zoomed', timelineContext, unit) + } + + + moveResizeValidator = (action, item, time) => { + if (time < new Date().getTime()) { + var newTime = + Math.ceil(new Date().getTime() / (15 * 60 * 1000)) * (15 * 60 * 1000) + return newTime + } + + return time + } + + render() { + const { groups, items, defaultTimeStart, defaultTimeEnd } = this.state + + return ( + Above The Left} + canMove + canResize="right" + canSelect + itemsSorted + itemTouchSendsClick={false} + stackItems + itemHeightRatio={0.75} + defaultTimeStart={defaultTimeStart} + defaultTimeEnd={defaultTimeEnd} + onCanvasClick={this.handleCanvasClick} + onCanvasDoubleClick={this.handleCanvasDoubleClick} + onCanvasContextMenu={this.handleCanvasContextMenu} + onItemClick={this.handleItemClick} + onItemSelect={this.handleItemSelect} + onItemContextMenu={this.handleItemContextMenu} + onItemMove={this.handleItemMove} + onItemResize={this.handleItemResize} + onItemDoubleClick={this.handleItemDoubleClick} + onTimeChange={this.handleTimeChange} + onZoom={this.handleZoom} + moveResizeValidator={this.moveResizeValidator} + buffer={3} + > + + + + + {({ styles }) => { + const newStyles = { ...styles, backgroundColor: 'blue' } + return
+ }} + + + + + ) + } +} diff --git a/demo/app/index.js b/demo/app/index.js index 30c7cc437..0fea24d91 100644 --- a/demo/app/index.js +++ b/demo/app/index.js @@ -18,6 +18,7 @@ const demos = { customInfoLabel: require('./demo-custom-info-label').default, controledSelect: require('./demo-controlled-select').default, controlledScrolling: require('./demo-controlled-scrolling').default, + colision: require('./demo-colision/').default, } // A simple component that shows the pathname of the current location From f404f51a3bc815cef115871ab70b597de624d323 Mon Sep 17 00:00:00 2001 From: GabrielCrackPro Date: Sat, 4 Feb 2023 17:07:54 +0100 Subject: [PATCH 2/8] Fixed item collision name --- demo/app/demo-colision/index.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/demo/app/demo-colision/index.js b/demo/app/demo-colision/index.js index 025e966bd..8a5a4ccdb 100644 --- a/demo/app/demo-colision/index.js +++ b/demo/app/demo-colision/index.js @@ -79,7 +79,7 @@ export default class App extends Component { console.log('Context Menu: ' + itemId, moment(time).format()) } - handleItemColision = (currentItem, item, time) => { + handleItemCollision = (currentItem, item, time) => { console.log('Item ' + currentItem + 'collided with ' + item, moment(time).format()) } @@ -180,6 +180,7 @@ export default class App extends Component { onItemDoubleClick={this.handleItemDoubleClick} onTimeChange={this.handleTimeChange} onZoom={this.handleZoom} + onColision={this.handleItemColision} moveResizeValidator={this.moveResizeValidator} buffer={3} > From dec83d21dc753e997efec2e7534b24a0b8e823fe Mon Sep 17 00:00:00 2001 From: GabrielCrackPro Date: Sun, 5 Feb 2023 09:40:02 +0100 Subject: [PATCH 3/8] Typo fixed --- demo/app/demo-colision/index.js | 2 +- demo/app/index.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/demo/app/demo-colision/index.js b/demo/app/demo-colision/index.js index 8a5a4ccdb..6332d0781 100644 --- a/demo/app/demo-colision/index.js +++ b/demo/app/demo-colision/index.js @@ -180,7 +180,7 @@ export default class App extends Component { onItemDoubleClick={this.handleItemDoubleClick} onTimeChange={this.handleTimeChange} onZoom={this.handleZoom} - onColision={this.handleItemColision} + onCollision={this.handleItemColision} moveResizeValidator={this.moveResizeValidator} buffer={3} > diff --git a/demo/app/index.js b/demo/app/index.js index 0fea24d91..7b8b0e12e 100644 --- a/demo/app/index.js +++ b/demo/app/index.js @@ -18,7 +18,7 @@ const demos = { customInfoLabel: require('./demo-custom-info-label').default, controledSelect: require('./demo-controlled-select').default, controlledScrolling: require('./demo-controlled-scrolling').default, - colision: require('./demo-colision/').default, + collision: require('./demo-colision/').default, } // A simple component that shows the pathname of the current location From 4cdf2139a1f3d6fd97b437f44b283cdea8a80e0e Mon Sep 17 00:00:00 2001 From: GabrielCrackPro Date: Sun, 5 Feb 2023 09:45:34 +0100 Subject: [PATCH 4/8] Added id to handleItemCollision event --- demo/app/demo-colision/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/demo/app/demo-colision/index.js b/demo/app/demo-colision/index.js index 6332d0781..1f7c835c7 100644 --- a/demo/app/demo-colision/index.js +++ b/demo/app/demo-colision/index.js @@ -79,8 +79,8 @@ export default class App extends Component { console.log('Context Menu: ' + itemId, moment(time).format()) } - handleItemCollision = (currentItem, item, time) => { - console.log('Item ' + currentItem + 'collided with ' + item, moment(time).format()) + handleItemCollision = (currentItem, item) => { + console.log('Item ' + currentItem.id + 'collided with ' + item.id) } handleItemMove = (itemId, dragTime, newGroupOrder) => { From 94d1fe0596da72e2ec85c0994a6a69a2efedab9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20P=C3=A9rez=20Terol?= Date: Sun, 5 Feb 2023 10:06:52 +0100 Subject: [PATCH 5/8] Add item collision event feature --- src/lib/Timeline.js | 14 +++++++++++++- src/lib/utility/generic.js | 14 ++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/src/lib/Timeline.js b/src/lib/Timeline.js index c77ba2f35..00fc6502d 100644 --- a/src/lib/Timeline.js +++ b/src/lib/Timeline.js @@ -17,7 +17,7 @@ import { getCanvasWidth, stackTimelineItems } from './utility/calendar' -import { _get, _length } from './utility/generic' +import { _get, _length, isCollision } from './utility/generic' import { defaultKeys, defaultTimeSteps, @@ -69,6 +69,7 @@ export default class ReactCalendarTimeline extends Component { onItemContextMenu: PropTypes.func, onCanvasDoubleClick: PropTypes.func, onCanvasContextMenu: PropTypes.func, + onCollision: PropTypes.func, onZoom: PropTypes.func, onItemDrag: PropTypes.func, @@ -159,6 +160,7 @@ export default class ReactCalendarTimeline extends Component { onCanvasClick: null, onItemDoubleClick: null, onItemContextMenu: null, + onCollision: null, onZoom: null, verticalLineClassNamesForTime: null, @@ -656,6 +658,12 @@ export default class ReactCalendarTimeline extends Component { this.setState({ draggingItem: null, dragTime: null, dragGroupTitle: null }) if (this.props.onItemMove) { this.props.onItemMove(item, dragTime, newGroupOrder) + + this.items() + } + + if(this.props.onCollision) { + isCollision(this.props.items, item, this.props.onCollision); } } @@ -679,6 +687,10 @@ export default class ReactCalendarTimeline extends Component { if (this.props.onItemResize && timeDelta !== 0) { this.props.onItemResize(item, resizeTime, edge) } + + if(this.props.onCollision) { + isCollision(this.props.items, item, this.props.onCollision); + } } updatingItem = ({ eventType, itemId, time, edge, newGroupOrder }) => { diff --git a/src/lib/utility/generic.js b/src/lib/utility/generic.js index 3e4ae1527..89fd66f87 100644 --- a/src/lib/utility/generic.js +++ b/src/lib/utility/generic.js @@ -33,4 +33,18 @@ export function keyBy(value, key) { return obj } +export function isCollision(items, pickedItem, callback) { + const currentItem = items[pickedItem]; + const itemsGroup = items.filter(item => item.group === currentItem.group && item.id !== currentItem.id); + + itemsGroup.forEach(item => { + if((currentItem.start <= item.start && currentItem.end >= item.end) || + (currentItem.start >= item.start && currentItem.end <= item.end) || + (currentItem.start <= item.start && currentItem.end >= item.start) || + (currentItem.start <= item.end && currentItem.end >= item.end)) { + return callback(currentItem, item); + } + }); +} + export function noop() {} From 6a860cb652de799e4ca67cd1ce4884f33b3cc8f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20P=C3=A9rez=20Terol?= Date: Sun, 5 Feb 2023 10:07:27 +0100 Subject: [PATCH 6/8] Fix typo --- demo/app/demo-colision/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/demo/app/demo-colision/index.js b/demo/app/demo-colision/index.js index 1f7c835c7..23337d353 100644 --- a/demo/app/demo-colision/index.js +++ b/demo/app/demo-colision/index.js @@ -180,7 +180,7 @@ export default class App extends Component { onItemDoubleClick={this.handleItemDoubleClick} onTimeChange={this.handleTimeChange} onZoom={this.handleZoom} - onCollision={this.handleItemColision} + onCollision={this.handleItemCollision} moveResizeValidator={this.moveResizeValidator} buffer={3} > From 0defc1716747a9f6a409fa812e3602d5e9d43500 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20P=C3=A9rez=20Terol?= Date: Sun, 5 Feb 2023 12:26:20 +0100 Subject: [PATCH 7/8] Add tests --- __tests__/utils/calendar/item-collision.js | 97 ++++++++++++++++++++++ 1 file changed, 97 insertions(+) create mode 100644 __tests__/utils/calendar/item-collision.js diff --git a/__tests__/utils/calendar/item-collision.js b/__tests__/utils/calendar/item-collision.js new file mode 100644 index 000000000..c9da52473 --- /dev/null +++ b/__tests__/utils/calendar/item-collision.js @@ -0,0 +1,97 @@ +import { isCollision } from "../../../src/lib/utility/generic"; + + +describe('item collision', ()=>{ + + const items = [ + { + id: '1', + group: '1', + start_time: 100, + end_time: 120, + canMove: false, + canResize: false, + className: '' + }, + { + id: '2', + group: '1', + start_time: 110, + end_time: 120, + canMove: false, + canResize: false, + className: '' + }, + { + id: '3', + group: '1', + start_time: 200, + end_time: 250, + canMove: false, + canResize: false, + className: '' + }, + { + id: '4', + group: '1', + start_time: 210, + end_time: 240, + canMove: false, + canResize: false, + className: '' + }, + { + id: '5', + group: '1', + start_time: 210, + end_time: 240, + canMove: false, + canResize: false, + className: '' + }, + { + id: '6', + group: '1', + start_time: 115, + end_time: 130, + canMove: false, + canResize: false, + className: '' + }, + ]; + + describe('From sides', () => { + + it('From left', () => { + isCollision(items, 1, (currentItem, item) => { + expect(currentItem.id).toBe(items[1].id); + expect(item.id).toBe(items[0].id); + }); + + isCollision(items, 2, () => { + throw new Error('Don\'t call me'); + }); + }); + + it('From right', () => { + isCollision(items, 0, (currentItem, item) => { + expect(currentItem.id).toBe(items[1].id); + expect(item.id).toBe(items[0].id); + }); + + isCollision(items, 2, () => { + throw new Error('Don\'t call me'); + }); + }); + }); + + describe('From nested', () => { + it('From inside', () => { + isCollision(items, 3, (currentItem, item) => { + expect(currentItem.id).toBe(items[3].id); + expect(item.id).toBe(items[2].id); + }); + }); + }); + +}); From 669fa2e1b0e51d4b5b7507595b777de838aec00f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20P=C3=A9rez=20Terol?= Date: Sun, 5 Feb 2023 12:30:31 +0100 Subject: [PATCH 8/8] Collision CHANGELOG --- CHANGELOG.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 03a23704b..e7723ec23 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,9 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres (more or less) to [Semantic Versioning](http://semver.org/). ## Unreleased - +* Added onCollision event between items. +* Added item collision tests +* Added item collision demo ## 0.28.0