UNPKG

skedify-calendar

Version:
584 lines (469 loc) 23.7 kB
'use strict'; exports.__esModule = true; exports.SATURDAY = exports.FRIDAY = exports.THURSDAY = exports.WEDNESDAY = exports.TUESDAY = exports.MONDAY = exports.SUNDAY = undefined; var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; var _DAY_MAP, _class, _temp; var _react = require('react'); var _react2 = _interopRequireDefault(_react); var _uuid = require('uuid'); var _dateFns = require('date-fns'); var _ComponentMeasurer = require('./ComponentMeasurer'); var _DragHandle = require('./DragHandle'); var _Stack = require('./Stack'); var _isFunction = require('../../utils/isFunction'); var _range = require('../../utils/range'); var _constrain = require('../../utils/constrain'); var _time = require('../../utils/time'); var _Slot = require('./Slot'); var _defaultRenderers = require('./defaultRenderers'); var _Gutter = require('./Gutter'); var _Event = require('./Event'); var _LocaleContext = require('./LocaleContext'); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _objectWithoutProperties(obj, keys) { var target = {}; for (var i in obj) { if (keys.indexOf(i) >= 0) continue; if (!Object.prototype.hasOwnProperty.call(obj, i)) continue; target[i] = obj[i]; } return target; } function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } var SUNDAY = exports.SUNDAY = (0, _uuid.v4)(); var MONDAY = exports.MONDAY = (0, _uuid.v4)(); var TUESDAY = exports.TUESDAY = (0, _uuid.v4)(); var WEDNESDAY = exports.WEDNESDAY = (0, _uuid.v4)(); var THURSDAY = exports.THURSDAY = (0, _uuid.v4)(); var FRIDAY = exports.FRIDAY = (0, _uuid.v4)(); var SATURDAY = exports.SATURDAY = (0, _uuid.v4)(); var PLACEHOLDER_EVENT = (0, _uuid.v4)(); var DAY_MAP = (_DAY_MAP = {}, _DAY_MAP[SUNDAY] = 0, _DAY_MAP[MONDAY] = 1, _DAY_MAP[TUESDAY] = 2, _DAY_MAP[WEDNESDAY] = 3, _DAY_MAP[THURSDAY] = 4, _DAY_MAP[FRIDAY] = 5, _DAY_MAP[SATURDAY] = 6, _DAY_MAP); var DEFAULT_SLOT_HEIGHT = 20; var ONE_WEEK = 7; function hasTimeChanged(start1, end1, start2, end2) { return '' + start1 !== '' + start2 || '' + end1 !== '' + end2; } var Calendar = (_temp = _class = function (_Component) { _inherits(Calendar, _Component); function Calendar() { _classCallCheck(this, Calendar); for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) { args[_key] = arguments[_key]; } var _this = _possibleConstructorReturn(this, _Component.call.apply(_Component, [this].concat(args))); _this.mousePositionToClosestSlot = function (event) { var _this$state = _this.state, gutterWidth = _this$state.gutterWidth, mainElement = _this$state.mainElement; var _this$props = _this.props, step = _this$props.step, minTime = _this$props.minTime, daysToRender = _this$props.daysToRender; var cursor = { top: event.clientY, left: event.clientX }; var _mainElement$rect = mainElement.rect(), mainElementLeft = _mainElement$rect.left, mainElementTop = _mainElement$rect.top; // If the cursor is in the scrollbar, don't create a pseudo event if (cursor.left - mainElementLeft > mainElement.element.scrollWidth) { return undefined; } var position = { left: Math.max(0, cursor.left - mainElementLeft + mainElement.element.scrollLeft - gutterWidth), top: Math.max(0, cursor.top - mainElementTop + mainElement.element.scrollTop) }; var _this$getVisibleRange = _this.getVisibleRange(), start = _this$getVisibleRange.start; var startTime = (0, _time.timeStringToDate)(minTime, start); var sizeOfOneSlot = _this.getSizeOfOneSlot(); return (0, _dateFns.addDays)((0, _dateFns.addMinutes)(startTime, Math.floor(position.top / sizeOfOneSlot) * step), Math.floor(position.left / ((mainElement.width - gutterWidth) / daysToRender))); }; _this.createPseudoEvent = function (event, handle) { return function (DOMEvent) { DOMEvent.preventDefault(); DOMEvent.stopPropagation(); var step = _this.props.step; var closestSlot = _this.mousePositionToClosestSlot(DOMEvent); var isPlaceholderEvent = event === PLACEHOLDER_EVENT; _this.setState({ handle: handle, pseudoEvent: _extends({}, isPlaceholderEvent ? { start: closestSlot, end: (0, _dateFns.addMinutes)(closestSlot, step) } : event, { id: (0, _uuid.v4)(), isPlaceholderEvent: isPlaceholderEvent, mouseStartPositionDiffInSeconds: (0, _dateFns.differenceInSeconds)(closestSlot, event.start), isDirty: false, original: isPlaceholderEvent ? { start: closestSlot, end: closestSlot } : event }) }); }; }; _this.startInteraction = function (event) { var _handlers; var pseudoEvent = _this.state.pseudoEvent; // There is no pseudo event yet // So interaction are a bit useless if (!pseudoEvent) { return; } // There is no pressure meaning // That the mouse is not pressed down if (event.pressure <= 0) { return; } var closestSlot = _this.mousePositionToClosestSlot(event); var _ref = [(0, _dateFns.getHours)(closestSlot), (0, _dateFns.getMinutes)(closestSlot)], hours = _ref[0], minutes = _ref[1]; var handlers = (_handlers = {}, _handlers[_DragHandle.DragHandle.PLACEMENT_BOTTOM] = function (state) { var nextEnd = (0, _dateFns.setHours)((0, _dateFns.setMinutes)(state.pseudoEvent.end, minutes), hours); var shouldSwapStartAndEnd = (0, _dateFns.differenceInSeconds)(nextEnd, state.pseudoEvent.original.start) >= 0; return { start: shouldSwapStartAndEnd ? state.pseudoEvent.original.start : nextEnd, end: shouldSwapStartAndEnd ? nextEnd : state.pseudoEvent.original.start }; }, _handlers[_DragHandle.DragHandle.PLACEMENT_TOP] = function (state) { var nextStart = (0, _dateFns.setHours)((0, _dateFns.setMinutes)(state.pseudoEvent.start, minutes), hours); var shouldSwapStartAndEnd = (0, _dateFns.differenceInSeconds)(nextStart, state.pseudoEvent.original.end) >= 0; return { start: shouldSwapStartAndEnd ? state.pseudoEvent.original.end : nextStart, end: shouldSwapStartAndEnd ? nextStart : state.pseudoEvent.original.end }; }, _handlers[_DragHandle.DragHandle.MOVING_INDICATOR] = function (state) { var nextStart = (0, _dateFns.subSeconds)(closestSlot, state.pseudoEvent.mouseStartPositionDiffInSeconds); return { start: nextStart, end: (0, _dateFns.addSeconds)(nextStart, (0, _dateFns.differenceInSeconds)(state.pseudoEvent.end, state.pseudoEvent.start)) }; }, _handlers); _this.setState(function (state) { return { pseudoEvent: _extends({}, state.pseudoEvent, { isDirty: true }, handlers[state.handle] ? handlers[state.handle](state) : {}) }; }); }; _this.finishInteraction = function () { var pseudoEvent = _this.state.pseudoEvent; var _this$props2 = _this.props, onEventUpdate = _this$props2.onEventUpdate, onEventCreate = _this$props2.onEventCreate, onEventClick = _this$props2.onEventClick, canCreateEvent = _this$props2.canCreateEvent, canUpdateEvent = _this$props2.canUpdateEvent; if (!pseudoEvent) { return; } var event = pseudoEvent.original, start = pseudoEvent.start, end = pseudoEvent.end, isPlaceholderEvent = pseudoEvent.isPlaceholderEvent, isDirty = pseudoEvent.isDirty; if (isPlaceholderEvent) { // In this case, we don't have an original event // Thus, we are working with a new event var newEvent = { start: start, end: end }; if (canCreateEvent(newEvent)) { onEventCreate({ start: start, end: end }); } } else if ((0, _isFunction.isFunction)(onEventClick) && !isDirty) { // It is the same start & end, so we probably wanted a click... onEventClick(event); } else if (hasTimeChanged(event.start, event.end, start, end) && canUpdateEvent({ event: event, start: start, end: end })) { // We do have an existing event // Let's update it onEventUpdate({ event: event, start: start, end: end }); } // Let's clean our pseudo event _this.setState({ handle: null, pseudoEvent: null }); }; _this.cancelInteraction = function () { // Let's clean our pseudo event _this.setState({ handle: null, pseudoEvent: null }); }; _this.getAmountOfSlotsToRender = function () { var _this$props3 = _this.props, minTime = _this$props3.minTime, maxTime = _this$props3.maxTime, step = _this$props3.step; var _this$getVisibleRange2 = _this.getVisibleRange(), start = _this$getVisibleRange2.start; var startTime = (0, _time.timeStringToDate)(minTime, start); var endTime = (0, _time.timeStringToDate)(maxTime, start); return Math.round((Math.abs((0, _dateFns.differenceInHours)(endTime, startTime)) + step / 60) * (60 / step)); }; _this.getSizeOfOneSlot = function () { var mainElement = _this.state.mainElement; if (!mainElement) { return DEFAULT_SLOT_HEIGHT; } return Math.max(Math.max(mainElement.element.scrollHeight - mainElement.element.offsetTop, mainElement.height) / _this.getAmountOfSlotsToRender(), DEFAULT_SLOT_HEIGHT); // 1 slot is equal to the step. Steps are in minutes }; _this.getVisibleRange = function () { var _this$props4 = _this.props, date = _this$props4.date, daysToRender = _this$props4.daysToRender, weekStartsOn = _this$props4.weekStartsOn; var start = daysToRender === ONE_WEEK ? (0, _dateFns.startOfWeek)(date, { weekStartsOn: DAY_MAP[weekStartsOn] }) : (0, _dateFns.startOfDay)(date); var end = (0, _dateFns.addDays)(start, daysToRender - 1); return { start: start, end: end }; }; _this.state = { handle: null, pseudoEvent: null, mainElement: null, gutterWidth: 0, scrollBarWidth: 0 }; return _this; } Calendar.prototype.render = function render() { var _this2 = this; var _props = this.props, step = _props.step, events = _props.events, onEventUpdate = _props.onEventUpdate, onEventCreate = _props.onEventCreate, renderHeader = _props.renderHeader, renderDayHeader = _props.renderDayHeader, renderGutterItem = _props.renderGutterItem, renderEvent = _props.renderEvent, renderPlaceholder = _props.renderPlaceholder, daysToRender = _props.daysToRender, minTime = _props.minTime, locale = _props.locale, canCreateEvent = _props.canCreateEvent, canUpdateEvent = _props.canUpdateEvent; var _state = this.state, pseudoEvent = _state.pseudoEvent, gutterWidth = _state.gutterWidth, scrollBarWidth = _state.scrollBarWidth; var _getVisibleRange = this.getVisibleRange(), start = _getVisibleRange.start, end = _getVisibleRange.end; var isUpdatingExistingEventsAllowed = (0, _isFunction.isFunction)(onEventUpdate); var isCreatingNewEventsAllowed = (0, _isFunction.isFunction)(onEventCreate); var allEvents = pseudoEvent ? [].concat(events, [pseudoEvent]) : events; var dayHeaderContents = (0, _range.range)(daysToRender).map(function (index) { return renderDayHeader((0, _dateFns.addDays)(start, index)); }); var startTime = (0, _time.timeStringToDate)(minTime, start); var dayHeaders = dayHeaderContents.every(function (value) { return value === null; }) ? null : _react2.default.createElement( _Stack.Stack, { align: _Stack.Stack.align.NORMAL, className: 'SkedifyCalendar__WeekHeader' }, _react2.default.createElement('div', { key: 'gutter_spot', className: 'SkedifyCalendar__WeekHeader__Gutter', style: { width: gutterWidth } }), _react2.default.createElement( _Stack.Stack, { distribution: _Stack.Stack.distribution.BETWEEN, className: 'SkedifyCalendar__WeekHeader__Days' }, dayHeaderContents.map(function (contents, index) { return _react2.default.createElement( _react2.default.Fragment, { key: index }, contents ); }) ), _react2.default.createElement('div', { key: 'scrollbar_spot', className: 'SkedifyCalendar__WeekHeader__Scrollbar', style: { width: scrollBarWidth } }) ); return _react2.default.createElement( _LocaleContext.LocaleContext.Provider, { value: (0, _LocaleContext.loadLocale)(locale) }, _react2.default.createElement( 'div', { className: 'SkedifyCalendar' }, renderHeader({ start: start, end: end }), dayHeaders, _react2.default.createElement(_ComponentMeasurer.ComponentMeasurer, { onUpdate: function onUpdate(_ref2) { var ref = _ref2.ref, info = _objectWithoutProperties(_ref2, ['ref']); _this2.setState({ mainElement: info, scrollBarWidth: info.element.offsetWidth - info.element.clientWidth }, function () { var _props2 = _this2.props, scrollToTime = _props2.scrollToTime, maxTime = _props2.maxTime; if (!scrollToTime) { return; } var constrainedScrollToTime = (0, _constrain.constrain)((0, _time.timeStringToSeconds)((0, _dateFns.format)(scrollToTime, 'HH:mm:ss')), (0, _time.timeStringToSeconds)(minTime), (0, _time.timeStringToSeconds)(maxTime)); var startTimeInSeconds = (0, _time.timeStringToSeconds)((0, _dateFns.format)(startTime, 'HH:mm:ss')); var sizeOfOneSlot = _this2.getSizeOfOneSlot(); ref.current.scrollTop = sizeOfOneSlot * ((constrainedScrollToTime - startTimeInSeconds) / 60 / step); }); }, render: function render(_ref3) { var ref = _ref3.ref, element = _ref3.element, _ref3$height = _ref3.height, height = _ref3$height === undefined ? 0 : _ref3$height; var sizeOfOneSlot = _this2.getSizeOfOneSlot(); var slotsToRender = _this2.getAmountOfSlotsToRender(); var _ref4 = element || {}, _ref4$scrollHeight = _ref4.scrollHeight, scrollHeight = _ref4$scrollHeight === undefined ? 0 : _ref4$scrollHeight, _ref4$offsetTop = _ref4.offsetTop, offsetTop = _ref4$offsetTop === undefined ? 0 : _ref4$offsetTop; var MINIMUM_POSITION = 0; var MAXIMUM_POSITION = Math.max(scrollHeight - offsetTop, height); return _react2.default.createElement( _react2.default.Fragment, null, _react2.default.createElement( _Stack.Stack, { ref: ref, distribution: _Stack.Stack.distribution.AROUND, className: 'SkedifyCalendar__Main', style: { height: 'calc(100% - ' + offsetTop + 'px)' }, onPointerDown: function onPointerDown(DOMEvent) { if (isCreatingNewEventsAllowed) { _this2.createPseudoEvent(PLACEHOLDER_EVENT, _DragHandle.DragHandle.PLACEMENT_BOTTOM)(DOMEvent); } }, onPointerMove: _this2.startInteraction, onPointerUp: _this2.finishInteraction, onPointerLeave: _this2.cancelInteraction }, _react2.default.createElement(_ComponentMeasurer.ComponentMeasurer, { onUpdate: function onUpdate(_ref5) { var width = _ref5.width; _this2.setState({ gutterWidth: width }); }, render: function render(_ref6) { var gutterRef = _ref6.ref; return _react2.default.createElement(_Gutter.Gutter, { ref: gutterRef, slotsToRender: slotsToRender, sizeOfOneSlot: sizeOfOneSlot, startTime: (0, _dateFns.format)(startTime, 'yyyy-MM-dd\'T\'HH:mm:ss.SSSxxx'), step: step, render: renderGutterItem }); } }), (0, _range.range)(daysToRender).map(function (index) { var dateToRender = (0, _dateFns.addDays)(start, index); var startOfThisDay = (0, _time.timeStringToDate)(minTime, dateToRender); var eventsOnThisDay = allEvents.filter(function (event) { return (0, _dateFns.isSameDay)(event.start, dateToRender); }); return _react2.default.createElement( _Stack.Stack, { key: index, direction: _Stack.Stack.direction.VERTICAL, distribution: _Stack.Stack.distribution.NORMAL, className: 'SkedifyCalendar__Column' }, (0, _range.range)(slotsToRender).map(function (slotIndex) { return _react2.default.createElement(_Slot.Slot, { key: slotIndex, height: sizeOfOneSlot }); }), eventsOnThisDay.map(function (event, eventIndex) { var normalPosition = sizeOfOneSlot * ((0, _dateFns.differenceInMinutes)(event.start, startOfThisDay) / step); var positionTop = (0, _constrain.constrain)(normalPosition, MINIMUM_POSITION, MAXIMUM_POSITION); var eventHeight = (0, _constrain.constrain)(Math.round(sizeOfOneSlot * (0, _dateFns.differenceInMinutes)(event.end, event.start) / step), sizeOfOneSlot, MAXIMUM_POSITION - positionTop); return _react2.default.createElement(_Event.Event, { key: event.id || eventIndex // assuming an id exist on the event, if not, use the index , event: event, interactionInfo: event === pseudoEvent && event.isPlaceholderEvent ? { get isCreatingAllowed() { return event === pseudoEvent && event.isPlaceholderEvent ? canCreateEvent({ start: event.start, end: event.end }) : false; } } : { // Can we interact with the current event get isInteractable() { return isUpdatingExistingEventsAllowed; }, // Are we interacting with the current event get isInteracting() { return pseudoEvent ? pseudoEvent.original === event : false; }, // Is there another event that is currently being interacted with get isOtherInteracting() { return pseudoEvent !== null && pseudoEvent.original !== event; }, // Is current event the pseudo event get isPseudo() { return pseudoEvent === event; }, get isUpdatingAllowed() { return event === pseudoEvent ? canUpdateEvent({ event: event.original, start: event.start, end: event.end }) : true; } }, onActivate: isUpdatingExistingEventsAllowed ? _this2.createPseudoEvent : undefined, positionTop: positionTop, height: normalPosition < 0 ? eventHeight + normalPosition // We have to subtract the negative part from the height. : eventHeight, render: event === pseudoEvent && event.isPlaceholderEvent ? renderPlaceholder : renderEvent }); }) ); }) ) ); } }) ) ); }; return Calendar; }(_react.Component), _class.MONDAY = MONDAY, _class.TUESDAY = TUESDAY, _class.WEDNESDAY = WEDNESDAY, _class.THURSDAY = THURSDAY, _class.FRIDAY = FRIDAY, _class.SATURDAY = SATURDAY, _class.SUNDAY = SUNDAY, _class.DEFAULT_RENDER_HEADER = _defaultRenderers.DEFAULT_RENDER_HEADER, _class.DEFAULT_RENDER_DAY_HEADER = _defaultRenderers.DEFAULT_RENDER_DAY_HEADER, _class.DEFAULT_RENDER_GUTTER_ITEM = _defaultRenderers.DEFAULT_RENDER_GUTTER_ITEM, _class.DEFAULT_RENDER_EVENT = _defaultRenderers.DEFAULT_RENDER_EVENT, _class.DEFAULT_RENDER_PLACEHOLDER = _defaultRenderers.DEFAULT_RENDER_PLACEHOLDER, _class.defaultProps = { date: new Date(), // The date to be used to render the calendar events: [], // A list of events to render locale: 'en', weekStartsOn: SUNDAY, daysToRender: 7, step: 30, // Time in minutes for 1 slot minTime: '00:00:00', maxTime: '24:00:00', renderHeader: _defaultRenderers.DEFAULT_RENDER_HEADER, renderDayHeader: _defaultRenderers.DEFAULT_RENDER_DAY_HEADER, renderGutterItem: _defaultRenderers.DEFAULT_RENDER_GUTTER_ITEM, renderEvent: _defaultRenderers.DEFAULT_RENDER_EVENT, renderPlaceholder: _defaultRenderers.DEFAULT_RENDER_PLACEHOLDER, canUpdateEvent: function canUpdateEvent() { return true; }, canCreateEvent: function canCreateEvent() { return true; }, onEventClick: function onEventClick() { return undefined; } }, _temp); exports.default = Calendar;