skedify-calendar
Version:
Skedify Calendar React component
584 lines (469 loc) • 23.7 kB
JavaScript
;
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;