diff --git a/demo/app/demo-scroll-close-to-borders/index.js b/demo/app/demo-scroll-close-to-borders/index.js new file mode 100644 index 000000000..574390e86 --- /dev/null +++ b/demo/app/demo-scroll-close-to-borders/index.js @@ -0,0 +1,205 @@ +/* eslint-disable no-console */ +import React, { Component } from 'react' +import moment from 'moment' + +import Timeline, { + TimelineMarkers, + TodayMarker, +} 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) + this.timelineComponent = React.createRef(); + + 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 + } + } + + 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, timeDONOTUSE, resizeEdge, e, dragStart) => { + if (action === 'move' && this.timelineComponent && this.timelineComponent.current && this.timelineComponent.current.state) { + const time = this.timelineComponent.current.timeFromItemEvent(e) // time from drag/resize event, DO NOT USE "time" param + const stateFrom = this.timelineComponent.current.state.visibleTimeStart; + const stateTo = this.timelineComponent.current.state.visibleTimeEnd; + + const zoomMillis = Math.round((stateTo - stateFrom)); + const closeToBorderTolerance = 3; // How close item to border enables the auto-scroll canvas, 2-5 are good values. + + // Percent of the window area will be used for activanting the move Time window, will change base on zoom level + const timeBorderArea = Math.round(((zoomMillis * closeToBorderTolerance) / 100)); + const duration = item.end - item.start; + const rightBorderTime = time + duration; + + // Moves timeline to right, when item close to right border + if (rightBorderTime > stateTo - timeBorderArea) { + const newFrom = stateFrom + (timeBorderArea / closeToBorderTolerance); + const newTo = stateTo + (timeBorderArea / closeToBorderTolerance); + + this.timelineComponent.current.updateScrollCanvas(newFrom, newTo); + return time + (dragStart.offset); + } + + // Moves canvas to left, when item close to left border + if (time < stateFrom + timeBorderArea) { + const newFrom = stateFrom - (timeBorderArea / closeToBorderTolerance); + const newTo = stateTo - (timeBorderArea / closeToBorderTolerance); + + this.timelineComponent.current.updateScrollCanvas(newFrom, newTo); + return time + (dragStart.offset); + } + } + + + if (action === 'resize' && this.timelineComponent && this.timelineComponent.current && this.timelineComponent.current.state) { + const time = this.timelineComponent.current.timeFromItemEvent(e) // time from drag/resize event, DO NOT USE "time" param + const stateFrom = this.timelineComponent.current.state.visibleTimeStart; + const stateTo = this.timelineComponent.current.state.visibleTimeEnd; + + const zoomMillis = Math.round((stateTo - stateFrom)); + const closeToBorderTolerance = 2; // How close item to border enables the auto-scroll canvas, 2-5 are good values. + + // Percent of the window area will be used for activanting the move Time window, will change base on zoom level + const timeBorderArea = Math.round(((zoomMillis * closeToBorderTolerance) / 100)); + + // Moves timeline to right, when item close to right border + if (resizeEdge === 'right' && time > stateTo - timeBorderArea) { + const newFrom = stateFrom + (timeBorderArea / closeToBorderTolerance); + const newTo = stateTo + (timeBorderArea / closeToBorderTolerance); + + this.timelineComponent.current.updateScrollCanvas(newFrom, newTo); + return time + (timeBorderArea / 2); + } else if (time < stateFrom + timeBorderArea) { // Moves canvas to left, when item close to left border + const newFrom = stateFrom - (timeBorderArea / closeToBorderTolerance); + const newTo = stateTo - (timeBorderArea / closeToBorderTolerance); + + this.timelineComponent.current.updateScrollCanvas(newFrom, newTo); + return time - (timeBorderArea / 2); + } + } + + return timeDONOTUSE; + } + + render() { + const { groups, items, defaultTimeStart, defaultTimeEnd } = this.state + + return ( + + + + + + ) + } +} diff --git a/demo/app/index.js b/demo/app/index.js index 30c7cc437..49264478a 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, + scrollCloseToBorders: require('./demo-scroll-close-to-borders').default, } // A simple component that shows the pathname of the current location diff --git a/examples/README.md b/examples/README.md index 6fcf88eb7..c47a56eac 100644 --- a/examples/README.md +++ b/examples/README.md @@ -84,3 +84,9 @@ Native info label was removed with 0.26.0 and now the responsibility to render t [Example Codesandbox](https://codesandbox.io/s/timeline-demo-info-label-neec9) +## Move/Resize item close to left/right border, auto scroll timeline + +This demo shows how to use the `moveResizeValidator()` function to move/resize an item close to the left/right border of the timeline, and auto scroll the timeline bounds to the direction of the move/resize item. By doing this you can keep moving/resizing the item without to stop, move timeline and move/resize again and item. Below you can check the example "scrollCloseToBorders" + +[Example Codesandbox](https://codesandbox.io/p/github/davidbejarcaceres/react-calendar-timeline/demo/MoveResize-items-moves-timeline-when-item-close-to-left-right-border) + diff --git a/src/lib/items/Item.js b/src/lib/items/Item.js index 2099a1a1a..df75b86c1 100644 --- a/src/lib/items/Item.js +++ b/src/lib/items/Item.js @@ -248,7 +248,10 @@ export default class Item extends Component { dragStart: { x: e.pageX, y: e.pageY, - offset: this.itemTimeStart - clickTime }, + clientX: e.clientX, + offset: this.itemTimeStart - clickTime, + itemTimeStart: this.itemTimeStart + }, preDragPosition: { x: e.target.offsetLeft, y: e.target.offsetTop }, dragTime: this.itemTimeStart, dragGroupDelta: 0 @@ -265,7 +268,10 @@ export default class Item extends Component { dragTime = this.props.moveResizeValidator( 'move', this.props.item, - dragTime + dragTime, + null, + e, + this.state.dragStart ) } @@ -292,7 +298,10 @@ export default class Item extends Component { dragTime = this.props.moveResizeValidator( 'move', this.props.item, - dragTime + dragTime, + null, + e, + this.state.dragStart ) } @@ -339,7 +348,9 @@ export default class Item extends Component { 'resize', this.props.item, resizeTime, - resizeEdge + resizeEdge, + e, + this.state.dragStart ) } @@ -362,7 +373,9 @@ export default class Item extends Component { 'resize', this.props.item, resizeTime, - resizeEdge + resizeEdge, + e, + this.state.dragStart ) } @@ -425,7 +438,7 @@ export default class Item extends Component { const willBeAbleToResizeRight = this.props.selected && this.canResizeRight(this.props) - if(!!this.item){ + if(this.item){ if (this.props.selected && !interactMounted) { this.mountInteract() interactMounted = true