From 16a60b954c5d24b36f607fc29a48cd99e44dc94e Mon Sep 17 00:00:00 2001 From: David Bejar Caceres Date: Sat, 8 Oct 2022 00:54:17 +0200 Subject: [PATCH 1/4] New demo move item close to left/right border, moves the timeline bounds --- .../app/demo-scroll-close-to-borders/index.js | 178 ++++++++++++++++++ demo/app/index.js | 1 + src/lib/items/Item.js | 22 ++- 3 files changed, 195 insertions(+), 6 deletions(-) create mode 100644 demo/app/demo-scroll-close-to-borders/index.js 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..f3d761a58 --- /dev/null +++ b/demo/app/demo-scroll-close-to-borders/index.js @@ -0,0 +1,178 @@ +/* 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) // right time from timeline, 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 percentCloseToBorder = 2; + + // 5 % 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 * percentCloseToBorder) / 100)); + const duration = item.end - item.start; + const rightBorderTime = time + duration; + + // Moves to right + if (rightBorderTime > stateTo - timeBorderArea) { + const newFrom = stateFrom + (timeBorderArea / percentCloseToBorder); // Moves 20 percent + const newTo = stateTo + (timeBorderArea / percentCloseToBorder); // Moves 20 percent + + this.timelineComponent.current.updateScrollCanvas(newFrom, newTo); + return time + (dragStart.offset); + } + + // Moves to left + if (time < stateFrom + timeBorderArea) { + const newFrom = stateFrom - (timeBorderArea / percentCloseToBorder); // Moves 20 percent + const newTo = stateTo - (timeBorderArea / percentCloseToBorder); // Moves 20 percent + + this.timelineComponent.current.updateScrollCanvas(newFrom, newTo); + return time + (dragStart.offset); + } + } + + return timeDONOTUSE; + } + + render() { + const { groups, items, defaultTimeStart, defaultTimeEnd } = this.state + + return ( + Above The Left} + canMove + canResize="both" + canSelect + itemsSorted + itemTouchSendsClick={false} + stackItems + itemHeightRatio={0.75} + defaultTimeStart={defaultTimeStart} + defaultTimeEnd={defaultTimeEnd} + onItemMove={this.handleItemMove} + onItemResize={this.handleItemResize} + onTimeChange={this.handleTimeChange} + onZoom={this.handleZoom} + moveResizeValidator={this.moveResizeValidator} + buffer={3} + > + + + + + ) + } +} 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/src/lib/items/Item.js b/src/lib/items/Item.js index 2099a1a1a..6a9349a72 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,9 @@ export default class Item extends Component { dragTime = this.props.moveResizeValidator( 'move', this.props.item, - dragTime + dragTime, + null, + e ) } @@ -339,7 +347,8 @@ export default class Item extends Component { 'resize', this.props.item, resizeTime, - resizeEdge + resizeEdge, + e ) } @@ -362,7 +371,8 @@ export default class Item extends Component { 'resize', this.props.item, resizeTime, - resizeEdge + resizeEdge, + e ) } @@ -425,7 +435,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 From b1d4af8cbabd641906b1ca5daec2f2e0a7c5f886 Mon Sep 17 00:00:00 2001 From: David Bejar Caceres Date: Sat, 8 Oct 2022 01:06:47 +0200 Subject: [PATCH 2/4] Add move timeline when resizing item left/right close to borders --- .../app/demo-scroll-close-to-borders/index.js | 38 ++++++++++++++++--- 1 file changed, 33 insertions(+), 5 deletions(-) diff --git a/demo/app/demo-scroll-close-to-borders/index.js b/demo/app/demo-scroll-close-to-borders/index.js index f3d761a58..6a811973e 100644 --- a/demo/app/demo-scroll-close-to-borders/index.js +++ b/demo/app/demo-scroll-close-to-borders/index.js @@ -115,15 +115,15 @@ export default class App extends Component { const zoomMillis = Math.round((stateTo - stateFrom)); const percentCloseToBorder = 2; - // 5 % Percent of the window area will be used for activanting the move Time window, will change base on zoom level + // 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 * percentCloseToBorder) / 100)); const duration = item.end - item.start; const rightBorderTime = time + duration; // Moves to right if (rightBorderTime > stateTo - timeBorderArea) { - const newFrom = stateFrom + (timeBorderArea / percentCloseToBorder); // Moves 20 percent - const newTo = stateTo + (timeBorderArea / percentCloseToBorder); // Moves 20 percent + const newFrom = stateFrom + (timeBorderArea / percentCloseToBorder); + const newTo = stateTo + (timeBorderArea / percentCloseToBorder); this.timelineComponent.current.updateScrollCanvas(newFrom, newTo); return time + (dragStart.offset); @@ -131,14 +131,42 @@ export default class App extends Component { // Moves to left if (time < stateFrom + timeBorderArea) { - const newFrom = stateFrom - (timeBorderArea / percentCloseToBorder); // Moves 20 percent - const newTo = stateTo - (timeBorderArea / percentCloseToBorder); // Moves 20 percent + const newFrom = stateFrom - (timeBorderArea / percentCloseToBorder); + const newTo = stateTo - (timeBorderArea / percentCloseToBorder); 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) // right time from timeline, 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 percentCloseToBorder = 2; + + // 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 * percentCloseToBorder) / 100)); + + // Moves to right + if (resizeEdge === 'right' && time > stateTo - timeBorderArea) { + const newFrom = stateFrom + (timeBorderArea / percentCloseToBorder); + const newTo = stateTo + (timeBorderArea / percentCloseToBorder); + + this.timelineComponent.current.updateScrollCanvas(newFrom, newTo); + return time + (timeBorderArea / 2); + } else if (time < stateFrom + timeBorderArea) { // Moves to left + const newFrom = stateFrom - (timeBorderArea / percentCloseToBorder); + const newTo = stateTo - (timeBorderArea / percentCloseToBorder); + + this.timelineComponent.current.updateScrollCanvas(newFrom, newTo); + return time - (timeBorderArea / 2); + } + } + return timeDONOTUSE; } From 17f37ad0921a521596dfb245bbe64944d64e0665 Mon Sep 17 00:00:00 2001 From: David Bejar Caceres Date: Sat, 8 Oct 2022 01:29:49 +0200 Subject: [PATCH 3/4] Clean code, comment example code --- .../app/demo-scroll-close-to-borders/index.js | 37 +++++++++---------- src/lib/items/Item.js | 9 +++-- 2 files changed, 24 insertions(+), 22 deletions(-) diff --git a/demo/app/demo-scroll-close-to-borders/index.js b/demo/app/demo-scroll-close-to-borders/index.js index 6a811973e..574390e86 100644 --- a/demo/app/demo-scroll-close-to-borders/index.js +++ b/demo/app/demo-scroll-close-to-borders/index.js @@ -108,31 +108,31 @@ export default class App extends Component { 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) // right time from timeline, DO NOT USE time "param" + 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 percentCloseToBorder = 2; + 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 * percentCloseToBorder) / 100)); + const timeBorderArea = Math.round(((zoomMillis * closeToBorderTolerance) / 100)); const duration = item.end - item.start; const rightBorderTime = time + duration; - // Moves to right + // Moves timeline to right, when item close to right border if (rightBorderTime > stateTo - timeBorderArea) { - const newFrom = stateFrom + (timeBorderArea / percentCloseToBorder); - const newTo = stateTo + (timeBorderArea / percentCloseToBorder); + const newFrom = stateFrom + (timeBorderArea / closeToBorderTolerance); + const newTo = stateTo + (timeBorderArea / closeToBorderTolerance); this.timelineComponent.current.updateScrollCanvas(newFrom, newTo); return time + (dragStart.offset); } - // Moves to left + // Moves canvas to left, when item close to left border if (time < stateFrom + timeBorderArea) { - const newFrom = stateFrom - (timeBorderArea / percentCloseToBorder); - const newTo = stateTo - (timeBorderArea / percentCloseToBorder); + const newFrom = stateFrom - (timeBorderArea / closeToBorderTolerance); + const newTo = stateTo - (timeBorderArea / closeToBorderTolerance); this.timelineComponent.current.updateScrollCanvas(newFrom, newTo); return time + (dragStart.offset); @@ -141,26 +141,26 @@ export default class App extends Component { if (action === 'resize' && this.timelineComponent && this.timelineComponent.current && this.timelineComponent.current.state) { - const time = this.timelineComponent.current.timeFromItemEvent(e) // right time from timeline, DO NOT USE time "param" + 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 percentCloseToBorder = 2; + 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 * percentCloseToBorder) / 100)); + const timeBorderArea = Math.round(((zoomMillis * closeToBorderTolerance) / 100)); - // Moves to right + // Moves timeline to right, when item close to right border if (resizeEdge === 'right' && time > stateTo - timeBorderArea) { - const newFrom = stateFrom + (timeBorderArea / percentCloseToBorder); - const newTo = stateTo + (timeBorderArea / percentCloseToBorder); + 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 to left - const newFrom = stateFrom - (timeBorderArea / percentCloseToBorder); - const newTo = stateTo - (timeBorderArea / percentCloseToBorder); + } 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); @@ -180,7 +180,6 @@ export default class App extends Component { items={items} keys={keys} sidebarWidth={150} - sidebarContent={
Above The Left
} canMove canResize="both" canSelect diff --git a/src/lib/items/Item.js b/src/lib/items/Item.js index 6a9349a72..df75b86c1 100644 --- a/src/lib/items/Item.js +++ b/src/lib/items/Item.js @@ -300,7 +300,8 @@ export default class Item extends Component { this.props.item, dragTime, null, - e + e, + this.state.dragStart ) } @@ -348,7 +349,8 @@ export default class Item extends Component { this.props.item, resizeTime, resizeEdge, - e + e, + this.state.dragStart ) } @@ -372,7 +374,8 @@ export default class Item extends Component { this.props.item, resizeTime, resizeEdge, - e + e, + this.state.dragStart ) } From c10fe9b19ba5d4dac38dc7524797b5ea8bd8ce86 Mon Sep 17 00:00:00 2001 From: David Bejar Caceres Date: Sat, 8 Oct 2022 02:03:13 +0200 Subject: [PATCH 4/4] Added CodeSanbox example --- examples/README.md | 6 ++++++ 1 file changed, 6 insertions(+) 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) +