elements work best with integers. round up to ensure contents fits
+ }
+ function getSectionHasLiquidHeight(props, sectionConfig) {
+ return props.liquid && sectionConfig.liquid; // does the section do liquid-height? (need to have whole scrollgrid liquid-height as well)
+ }
+ function getAllowYScrolling(props, sectionConfig) {
+ return sectionConfig.maxHeight != null || // if its possible for the height to max out, we might need scrollbars
+ getSectionHasLiquidHeight(props, sectionConfig); // if the section is liquid height, it might condense enough to require scrollbars
+ }
+ // TODO: ONLY use `arg`. force out internal function to use same API
+ function renderChunkContent(sectionConfig, chunkConfig, arg) {
+ var expandRows = arg.expandRows;
+ var content = typeof chunkConfig.content === 'function' ?
+ chunkConfig.content(arg) :
+ createElement('table', {
+ className: [
+ chunkConfig.tableClassName,
+ sectionConfig.syncRowHeights ? 'fc-scrollgrid-sync-table' : '',
+ ].join(' '),
+ style: {
+ minWidth: arg.tableMinWidth,
+ width: arg.clientWidth,
+ height: expandRows ? arg.clientHeight : '', // css `height` on a
serves as a min-height
+ },
+ }, arg.tableColGroupNode, createElement('tbody', {}, typeof chunkConfig.rowContent === 'function' ? chunkConfig.rowContent(arg) : chunkConfig.rowContent));
+ return content;
+ }
+ function isColPropsEqual(cols0, cols1) {
+ return isArraysEqual(cols0, cols1, isPropsEqual);
+ }
+ function renderMicroColGroup(cols, shrinkWidth) {
+ var colNodes = [];
+ /*
+ for ColProps with spans, it would have been great to make a single
+ HOWEVER, Chrome was getting messing up distributing the width to
/
elements with colspans.
+ SOLUTION: making individual
elements makes Chrome behave.
+ */
+ for (var _i = 0, cols_1 = cols; _i < cols_1.length; _i++) {
+ var colProps = cols_1[_i];
+ var span = colProps.span || 1;
+ for (var i = 0; i < span; i += 1) {
+ colNodes.push(createElement("col", { style: {
+ width: colProps.width === 'shrink' ? sanitizeShrinkWidth(shrinkWidth) : (colProps.width || ''),
+ minWidth: colProps.minWidth || '',
+ } }));
+ }
+ }
+ return createElement.apply(void 0, __spreadArray(['colgroup', {}], colNodes));
+ }
+ function sanitizeShrinkWidth(shrinkWidth) {
+ /* why 4? if we do 0, it will kill any border, which are needed for computeSmallestCellWidth
+ 4 accounts for 2 2-pixel borders. TODO: better solution? */
+ return shrinkWidth == null ? 4 : shrinkWidth;
+ }
+ function hasShrinkWidth(cols) {
+ for (var _i = 0, cols_2 = cols; _i < cols_2.length; _i++) {
+ var col = cols_2[_i];
+ if (col.width === 'shrink') {
+ return true;
+ }
+ }
+ return false;
+ }
+ function getScrollGridClassNames(liquid, context) {
+ var classNames = [
+ 'fc-scrollgrid',
+ context.theme.getClass('table'),
+ ];
+ if (liquid) {
+ classNames.push('fc-scrollgrid-liquid');
+ }
+ return classNames;
+ }
+ function getSectionClassNames(sectionConfig, wholeTableVGrow) {
+ var classNames = [
+ 'fc-scrollgrid-section',
+ "fc-scrollgrid-section-" + sectionConfig.type,
+ sectionConfig.className, // used?
+ ];
+ if (wholeTableVGrow && sectionConfig.liquid && sectionConfig.maxHeight == null) {
+ classNames.push('fc-scrollgrid-section-liquid');
+ }
+ if (sectionConfig.isSticky) {
+ classNames.push('fc-scrollgrid-section-sticky');
+ }
+ return classNames;
+ }
+ function renderScrollShim(arg) {
+ return (createElement("div", { className: "fc-scrollgrid-sticky-shim", style: {
+ width: arg.clientWidth,
+ minWidth: arg.tableMinWidth,
+ } }));
+ }
+ function getStickyHeaderDates(options) {
+ var stickyHeaderDates = options.stickyHeaderDates;
+ if (stickyHeaderDates == null || stickyHeaderDates === 'auto') {
+ stickyHeaderDates = options.height === 'auto' || options.viewHeight === 'auto';
+ }
+ return stickyHeaderDates;
+ }
+ function getStickyFooterScrollbar(options) {
+ var stickyFooterScrollbar = options.stickyFooterScrollbar;
+ if (stickyFooterScrollbar == null || stickyFooterScrollbar === 'auto') {
+ stickyFooterScrollbar = options.height === 'auto' || options.viewHeight === 'auto';
+ }
+ return stickyFooterScrollbar;
+ }
+
+ var SimpleScrollGrid = /** @class */ (function (_super) {
+ __extends(SimpleScrollGrid, _super);
+ function SimpleScrollGrid() {
+ var _this = _super !== null && _super.apply(this, arguments) || this;
+ _this.processCols = memoize(function (a) { return a; }, isColPropsEqual); // so we get same `cols` props every time
+ // yucky to memoize VNodes, but much more efficient for consumers
+ _this.renderMicroColGroup = memoize(renderMicroColGroup);
+ _this.scrollerRefs = new RefMap();
+ _this.scrollerElRefs = new RefMap(_this._handleScrollerEl.bind(_this));
+ _this.state = {
+ shrinkWidth: null,
+ forceYScrollbars: false,
+ scrollerClientWidths: {},
+ scrollerClientHeights: {},
+ };
+ // TODO: can do a really simple print-view. dont need to join rows
+ _this.handleSizing = function () {
+ _this.setState(__assign({ shrinkWidth: _this.computeShrinkWidth() }, _this.computeScrollerDims()));
+ };
+ return _this;
+ }
+ SimpleScrollGrid.prototype.render = function () {
+ var _a = this, props = _a.props, state = _a.state, context = _a.context;
+ var sectionConfigs = props.sections || [];
+ var cols = this.processCols(props.cols);
+ var microColGroupNode = this.renderMicroColGroup(cols, state.shrinkWidth);
+ var classNames = getScrollGridClassNames(props.liquid, context);
+ if (props.collapsibleWidth) {
+ classNames.push('fc-scrollgrid-collapsible');
+ }
+ // TODO: make DRY
+ var configCnt = sectionConfigs.length;
+ var configI = 0;
+ var currentConfig;
+ var headSectionNodes = [];
+ var bodySectionNodes = [];
+ var footSectionNodes = [];
+ while (configI < configCnt && (currentConfig = sectionConfigs[configI]).type === 'header') {
+ headSectionNodes.push(this.renderSection(currentConfig, microColGroupNode));
+ configI += 1;
+ }
+ while (configI < configCnt && (currentConfig = sectionConfigs[configI]).type === 'body') {
+ bodySectionNodes.push(this.renderSection(currentConfig, microColGroupNode));
+ configI += 1;
+ }
+ while (configI < configCnt && (currentConfig = sectionConfigs[configI]).type === 'footer') {
+ footSectionNodes.push(this.renderSection(currentConfig, microColGroupNode));
+ configI += 1;
+ }
+ // firefox bug: when setting height on table and there is a thead or tfoot,
+ // the necessary height:100% on the liquid-height body section forces the *whole* table to be taller. (bug #5524)
+ // use getCanVGrowWithinCell as a way to detect table-stupid firefox.
+ // if so, use a simpler dom structure, jam everything into a lone tbody.
+ var isBuggy = !getCanVGrowWithinCell();
+ return createElement('table', {
+ className: classNames.join(' '),
+ style: { height: props.height },
+ }, Boolean(!isBuggy && headSectionNodes.length) && createElement.apply(void 0, __spreadArray(['thead', {}], headSectionNodes)), Boolean(!isBuggy && bodySectionNodes.length) && createElement.apply(void 0, __spreadArray(['tbody', {}], bodySectionNodes)), Boolean(!isBuggy && footSectionNodes.length) && createElement.apply(void 0, __spreadArray(['tfoot', {}], footSectionNodes)), isBuggy && createElement.apply(void 0, __spreadArray(__spreadArray(__spreadArray(['tbody', {}], headSectionNodes), bodySectionNodes), footSectionNodes)));
+ };
+ SimpleScrollGrid.prototype.renderSection = function (sectionConfig, microColGroupNode) {
+ if ('outerContent' in sectionConfig) {
+ return (createElement(Fragment, { key: sectionConfig.key }, sectionConfig.outerContent));
+ }
+ return (createElement("tr", { key: sectionConfig.key, className: getSectionClassNames(sectionConfig, this.props.liquid).join(' ') }, this.renderChunkTd(sectionConfig, microColGroupNode, sectionConfig.chunk)));
+ };
+ SimpleScrollGrid.prototype.renderChunkTd = function (sectionConfig, microColGroupNode, chunkConfig) {
+ if ('outerContent' in chunkConfig) {
+ return chunkConfig.outerContent;
+ }
+ var props = this.props;
+ var _a = this.state, forceYScrollbars = _a.forceYScrollbars, scrollerClientWidths = _a.scrollerClientWidths, scrollerClientHeights = _a.scrollerClientHeights;
+ var needsYScrolling = getAllowYScrolling(props, sectionConfig); // TODO: do lazily. do in section config?
+ var isLiquid = getSectionHasLiquidHeight(props, sectionConfig);
+ // for `!props.liquid` - is WHOLE scrollgrid natural height?
+ // TODO: do same thing in advanced scrollgrid? prolly not b/c always has horizontal scrollbars
+ var overflowY = !props.liquid ? 'visible' :
+ forceYScrollbars ? 'scroll' :
+ !needsYScrolling ? 'hidden' :
+ 'auto';
+ var sectionKey = sectionConfig.key;
+ var content = renderChunkContent(sectionConfig, chunkConfig, {
+ tableColGroupNode: microColGroupNode,
+ tableMinWidth: '',
+ clientWidth: (!props.collapsibleWidth && scrollerClientWidths[sectionKey] !== undefined) ? scrollerClientWidths[sectionKey] : null,
+ clientHeight: scrollerClientHeights[sectionKey] !== undefined ? scrollerClientHeights[sectionKey] : null,
+ expandRows: sectionConfig.expandRows,
+ syncRowHeights: false,
+ rowSyncHeights: [],
+ reportRowHeightChange: function () { },
+ });
+ return (createElement("td", { ref: chunkConfig.elRef },
+ createElement("div", { className: "fc-scroller-harness" + (isLiquid ? ' fc-scroller-harness-liquid' : '') },
+ createElement(Scroller, { ref: this.scrollerRefs.createRef(sectionKey), elRef: this.scrollerElRefs.createRef(sectionKey), overflowY: overflowY, overflowX: !props.liquid ? 'visible' : 'hidden' /* natural height? */, maxHeight: sectionConfig.maxHeight, liquid: isLiquid, liquidIsAbsolute // because its within a harness
+ : true }, content))));
+ };
+ SimpleScrollGrid.prototype._handleScrollerEl = function (scrollerEl, key) {
+ var section = getSectionByKey(this.props.sections, key);
+ if (section) {
+ setRef(section.chunk.scrollerElRef, scrollerEl);
+ }
+ };
+ SimpleScrollGrid.prototype.componentDidMount = function () {
+ this.handleSizing();
+ this.context.addResizeHandler(this.handleSizing);
+ };
+ SimpleScrollGrid.prototype.componentDidUpdate = function () {
+ // TODO: need better solution when state contains non-sizing things
+ this.handleSizing();
+ };
+ SimpleScrollGrid.prototype.componentWillUnmount = function () {
+ this.context.removeResizeHandler(this.handleSizing);
+ };
+ SimpleScrollGrid.prototype.computeShrinkWidth = function () {
+ return hasShrinkWidth(this.props.cols)
+ ? computeShrinkWidth(this.scrollerElRefs.getAll())
+ : 0;
+ };
+ SimpleScrollGrid.prototype.computeScrollerDims = function () {
+ var scrollbarWidth = getScrollbarWidths();
+ var _a = this, scrollerRefs = _a.scrollerRefs, scrollerElRefs = _a.scrollerElRefs;
+ var forceYScrollbars = false;
+ var scrollerClientWidths = {};
+ var scrollerClientHeights = {};
+ for (var sectionKey in scrollerRefs.currentMap) {
+ var scroller = scrollerRefs.currentMap[sectionKey];
+ if (scroller && scroller.needsYScrolling()) {
+ forceYScrollbars = true;
+ break;
+ }
+ }
+ for (var _i = 0, _b = this.props.sections; _i < _b.length; _i++) {
+ var section = _b[_i];
+ var sectionKey = section.key;
+ var scrollerEl = scrollerElRefs.currentMap[sectionKey];
+ if (scrollerEl) {
+ var harnessEl = scrollerEl.parentNode; // TODO: weird way to get this. need harness b/c doesn't include table borders
+ scrollerClientWidths[sectionKey] = Math.floor(harnessEl.getBoundingClientRect().width - (forceYScrollbars
+ ? scrollbarWidth.y // use global because scroller might not have scrollbars yet but will need them in future
+ : 0));
+ scrollerClientHeights[sectionKey] = Math.floor(harnessEl.getBoundingClientRect().height);
+ }
+ }
+ return { forceYScrollbars: forceYScrollbars, scrollerClientWidths: scrollerClientWidths, scrollerClientHeights: scrollerClientHeights };
+ };
+ return SimpleScrollGrid;
+ }(BaseComponent));
+ SimpleScrollGrid.addStateEquality({
+ scrollerClientWidths: isPropsEqual,
+ scrollerClientHeights: isPropsEqual,
+ });
+ function getSectionByKey(sections, key) {
+ for (var _i = 0, sections_1 = sections; _i < sections_1.length; _i++) {
+ var section = sections_1[_i];
+ if (section.key === key) {
+ return section;
+ }
+ }
+ return null;
+ }
+
+ var EventRoot = /** @class */ (function (_super) {
+ __extends(EventRoot, _super);
+ function EventRoot() {
+ var _this = _super !== null && _super.apply(this, arguments) || this;
+ _this.elRef = createRef();
+ return _this;
+ }
+ EventRoot.prototype.render = function () {
+ var _a = this, props = _a.props, context = _a.context;
+ var options = context.options;
+ var seg = props.seg;
+ var eventRange = seg.eventRange;
+ var ui = eventRange.ui;
+ var hookProps = {
+ event: new EventApi(context, eventRange.def, eventRange.instance),
+ view: context.viewApi,
+ timeText: props.timeText,
+ textColor: ui.textColor,
+ backgroundColor: ui.backgroundColor,
+ borderColor: ui.borderColor,
+ isDraggable: !props.disableDragging && computeSegDraggable(seg, context),
+ isStartResizable: !props.disableResizing && computeSegStartResizable(seg, context),
+ isEndResizable: !props.disableResizing && computeSegEndResizable(seg),
+ isMirror: Boolean(props.isDragging || props.isResizing || props.isDateSelecting),
+ isStart: Boolean(seg.isStart),
+ isEnd: Boolean(seg.isEnd),
+ isPast: Boolean(props.isPast),
+ isFuture: Boolean(props.isFuture),
+ isToday: Boolean(props.isToday),
+ isSelected: Boolean(props.isSelected),
+ isDragging: Boolean(props.isDragging),
+ isResizing: Boolean(props.isResizing),
+ };
+ var standardClassNames = getEventClassNames(hookProps).concat(ui.classNames);
+ return (createElement(RenderHook, { hookProps: hookProps, classNames: options.eventClassNames, content: options.eventContent, defaultContent: props.defaultContent, didMount: options.eventDidMount, willUnmount: options.eventWillUnmount, elRef: this.elRef }, function (rootElRef, customClassNames, innerElRef, innerContent) { return props.children(rootElRef, standardClassNames.concat(customClassNames), innerElRef, innerContent, hookProps); }));
+ };
+ EventRoot.prototype.componentDidMount = function () {
+ setElSeg(this.elRef.current, this.props.seg);
+ };
+ /*
+ need to re-assign seg to the element if seg changes, even if the element is the same
+ */
+ EventRoot.prototype.componentDidUpdate = function (prevProps) {
+ var seg = this.props.seg;
+ if (seg !== prevProps.seg) {
+ setElSeg(this.elRef.current, seg);
+ }
+ };
+ return EventRoot;
+ }(BaseComponent));
+
+ // should not be a purecomponent
+ var StandardEvent = /** @class */ (function (_super) {
+ __extends(StandardEvent, _super);
+ function StandardEvent() {
+ return _super !== null && _super.apply(this, arguments) || this;
+ }
+ StandardEvent.prototype.render = function () {
+ var _a = this, props = _a.props, context = _a.context;
+ var seg = props.seg;
+ var timeFormat = context.options.eventTimeFormat || props.defaultTimeFormat;
+ var timeText = buildSegTimeText(seg, timeFormat, context, props.defaultDisplayEventTime, props.defaultDisplayEventEnd);
+ return (createElement(EventRoot, { seg: seg, timeText: timeText, disableDragging: props.disableDragging, disableResizing: props.disableResizing, defaultContent: props.defaultContent || renderInnerContent$4, isDragging: props.isDragging, isResizing: props.isResizing, isDateSelecting: props.isDateSelecting, isSelected: props.isSelected, isPast: props.isPast, isFuture: props.isFuture, isToday: props.isToday }, function (rootElRef, classNames, innerElRef, innerContent, hookProps) { return (createElement("a", __assign({ className: props.extraClassNames.concat(classNames).join(' '), style: {
+ borderColor: hookProps.borderColor,
+ backgroundColor: hookProps.backgroundColor,
+ }, ref: rootElRef }, getSegAnchorAttrs$1(seg)),
+ createElement("div", { className: "fc-event-main", ref: innerElRef, style: { color: hookProps.textColor } }, innerContent),
+ hookProps.isStartResizable &&
+ createElement("div", { className: "fc-event-resizer fc-event-resizer-start" }),
+ hookProps.isEndResizable &&
+ createElement("div", { className: "fc-event-resizer fc-event-resizer-end" }))); }));
+ };
+ return StandardEvent;
+ }(BaseComponent));
+ function renderInnerContent$4(innerProps) {
+ return (createElement("div", { className: "fc-event-main-frame" },
+ innerProps.timeText && (createElement("div", { className: "fc-event-time" }, innerProps.timeText)),
+ createElement("div", { className: "fc-event-title-container" },
+ createElement("div", { className: "fc-event-title fc-sticky" }, innerProps.event.title || createElement(Fragment, null, "\u00A0")))));
+ }
+ function getSegAnchorAttrs$1(seg) {
+ var url = seg.eventRange.def.url;
+ return url ? { href: url } : {};
+ }
+
+ var NowIndicatorRoot = function (props) { return (createElement(ViewContextType.Consumer, null, function (context) {
+ var options = context.options;
+ var hookProps = {
+ isAxis: props.isAxis,
+ date: context.dateEnv.toDate(props.date),
+ view: context.viewApi,
+ };
+ return (createElement(RenderHook, { hookProps: hookProps, classNames: options.nowIndicatorClassNames, content: options.nowIndicatorContent, didMount: options.nowIndicatorDidMount, willUnmount: options.nowIndicatorWillUnmount }, props.children));
+ })); };
+
+ var DAY_NUM_FORMAT = createFormatter({ day: 'numeric' });
+ var DayCellContent = /** @class */ (function (_super) {
+ __extends(DayCellContent, _super);
+ function DayCellContent() {
+ return _super !== null && _super.apply(this, arguments) || this;
+ }
+ DayCellContent.prototype.render = function () {
+ var _a = this, props = _a.props, context = _a.context;
+ var options = context.options;
+ var hookProps = refineDayCellHookProps({
+ date: props.date,
+ dateProfile: props.dateProfile,
+ todayRange: props.todayRange,
+ showDayNumber: props.showDayNumber,
+ extraProps: props.extraHookProps,
+ viewApi: context.viewApi,
+ dateEnv: context.dateEnv,
+ });
+ return (createElement(ContentHook, { hookProps: hookProps, content: options.dayCellContent, defaultContent: props.defaultContent }, props.children));
+ };
+ return DayCellContent;
+ }(BaseComponent));
+ function refineDayCellHookProps(raw) {
+ var date = raw.date, dateEnv = raw.dateEnv;
+ var dayMeta = getDateMeta(date, raw.todayRange, null, raw.dateProfile);
+ return __assign(__assign(__assign({ date: dateEnv.toDate(date), view: raw.viewApi }, dayMeta), { dayNumberText: raw.showDayNumber ? dateEnv.format(date, DAY_NUM_FORMAT) : '' }), raw.extraProps);
+ }
+
+ var DayCellRoot = /** @class */ (function (_super) {
+ __extends(DayCellRoot, _super);
+ function DayCellRoot() {
+ var _this = _super !== null && _super.apply(this, arguments) || this;
+ _this.refineHookProps = memoizeObjArg(refineDayCellHookProps);
+ _this.normalizeClassNames = buildClassNameNormalizer();
+ return _this;
+ }
+ DayCellRoot.prototype.render = function () {
+ var _a = this, props = _a.props, context = _a.context;
+ var options = context.options;
+ var hookProps = this.refineHookProps({
+ date: props.date,
+ dateProfile: props.dateProfile,
+ todayRange: props.todayRange,
+ showDayNumber: props.showDayNumber,
+ extraProps: props.extraHookProps,
+ viewApi: context.viewApi,
+ dateEnv: context.dateEnv,
+ });
+ var classNames = getDayClassNames(hookProps, context.theme).concat(hookProps.isDisabled
+ ? [] // don't use custom classNames if disabled
+ : this.normalizeClassNames(options.dayCellClassNames, hookProps));
+ var dataAttrs = hookProps.isDisabled ? {} : {
+ 'data-date': formatDayString(props.date),
+ };
+ return (createElement(MountHook, { hookProps: hookProps, didMount: options.dayCellDidMount, willUnmount: options.dayCellWillUnmount, elRef: props.elRef }, function (rootElRef) { return props.children(rootElRef, classNames, dataAttrs, hookProps.isDisabled); }));
+ };
+ return DayCellRoot;
+ }(BaseComponent));
+
+ function renderFill(fillType) {
+ return (createElement("div", { className: "fc-" + fillType }));
+ }
+ var BgEvent = function (props) { return (createElement(EventRoot, { defaultContent: renderInnerContent$3, seg: props.seg /* uselesss i think */, timeText: "", disableDragging: true, disableResizing: true, isDragging: false, isResizing: false, isDateSelecting: false, isSelected: false, isPast: props.isPast, isFuture: props.isFuture, isToday: props.isToday }, function (rootElRef, classNames, innerElRef, innerContent, hookProps) { return (createElement("div", { ref: rootElRef, className: ['fc-bg-event'].concat(classNames).join(' '), style: {
+ backgroundColor: hookProps.backgroundColor,
+ } }, innerContent)); })); };
+ function renderInnerContent$3(props) {
+ var title = props.event.title;
+ return title && (createElement("div", { className: "fc-event-title" }, props.event.title));
+ }
+
+ var WeekNumberRoot = function (props) { return (createElement(ViewContextType.Consumer, null, function (context) {
+ var dateEnv = context.dateEnv, options = context.options;
+ var date = props.date;
+ var format = options.weekNumberFormat || props.defaultFormat;
+ var num = dateEnv.computeWeekNumber(date); // TODO: somehow use for formatting as well?
+ var text = dateEnv.format(date, format);
+ var hookProps = { num: num, text: text, date: date };
+ return (createElement(RenderHook, { hookProps: hookProps, classNames: options.weekNumberClassNames, content: options.weekNumberContent, defaultContent: renderInner, didMount: options.weekNumberDidMount, willUnmount: options.weekNumberWillUnmount }, props.children));
+ })); };
+ function renderInner(innerProps) {
+ return innerProps.text;
+ }
+
+ var PADDING_FROM_VIEWPORT = 10;
+ var Popover = /** @class */ (function (_super) {
+ __extends(Popover, _super);
+ function Popover() {
+ var _this = _super !== null && _super.apply(this, arguments) || this;
+ _this.handleRootEl = function (el) {
+ _this.rootEl = el;
+ if (_this.props.elRef) {
+ setRef(_this.props.elRef, el);
+ }
+ };
+ // Triggered when the user clicks *anywhere* in the document, for the autoHide feature
+ _this.handleDocumentMousedown = function (ev) {
+ // only hide the popover if the click happened outside the popover
+ var target = getEventTargetViaRoot(ev);
+ if (!_this.rootEl.contains(target)) {
+ _this.handleCloseClick();
+ }
+ };
+ _this.handleCloseClick = function () {
+ var onClose = _this.props.onClose;
+ if (onClose) {
+ onClose();
+ }
+ };
+ return _this;
+ }
+ Popover.prototype.render = function () {
+ var theme = this.context.theme;
+ var props = this.props;
+ var classNames = [
+ 'fc-popover',
+ theme.getClass('popover'),
+ ].concat(props.extraClassNames || []);
+ return createPortal(createElement("div", __assign({ className: classNames.join(' ') }, props.extraAttrs, { ref: this.handleRootEl }),
+ createElement("div", { className: 'fc-popover-header ' + theme.getClass('popoverHeader') },
+ createElement("span", { className: "fc-popover-title" }, props.title),
+ createElement("span", { className: 'fc-popover-close ' + theme.getIconClass('close'), onClick: this.handleCloseClick })),
+ createElement("div", { className: 'fc-popover-body ' + theme.getClass('popoverContent') }, props.children)), props.parentEl);
+ };
+ Popover.prototype.componentDidMount = function () {
+ document.addEventListener('mousedown', this.handleDocumentMousedown);
+ this.updateSize();
+ };
+ Popover.prototype.componentWillUnmount = function () {
+ document.removeEventListener('mousedown', this.handleDocumentMousedown);
+ };
+ Popover.prototype.updateSize = function () {
+ var isRtl = this.context.isRtl;
+ var _a = this.props, alignmentEl = _a.alignmentEl, alignGridTop = _a.alignGridTop;
+ var rootEl = this.rootEl;
+ var alignmentRect = computeClippedClientRect(alignmentEl);
+ if (alignmentRect) {
+ var popoverDims = rootEl.getBoundingClientRect();
+ // position relative to viewport
+ var popoverTop = alignGridTop
+ ? elementClosest(alignmentEl, '.fc-scrollgrid').getBoundingClientRect().top
+ : alignmentRect.top;
+ var popoverLeft = isRtl ? alignmentRect.right - popoverDims.width : alignmentRect.left;
+ // constrain
+ popoverTop = Math.max(popoverTop, PADDING_FROM_VIEWPORT);
+ popoverLeft = Math.min(popoverLeft, document.documentElement.clientWidth - PADDING_FROM_VIEWPORT - popoverDims.width);
+ popoverLeft = Math.max(popoverLeft, PADDING_FROM_VIEWPORT);
+ var origin_1 = rootEl.offsetParent.getBoundingClientRect();
+ applyStyle(rootEl, {
+ top: popoverTop - origin_1.top,
+ left: popoverLeft - origin_1.left,
+ });
+ }
+ };
+ return Popover;
+ }(BaseComponent));
+
+ var MorePopover = /** @class */ (function (_super) {
+ __extends(MorePopover, _super);
+ function MorePopover() {
+ var _this = _super !== null && _super.apply(this, arguments) || this;
+ _this.handleRootEl = function (rootEl) {
+ _this.rootEl = rootEl;
+ if (rootEl) {
+ _this.context.registerInteractiveComponent(_this, {
+ el: rootEl,
+ useEventCenter: false,
+ });
+ }
+ else {
+ _this.context.unregisterInteractiveComponent(_this);
+ }
+ };
+ return _this;
+ }
+ MorePopover.prototype.render = function () {
+ var _a = this.context, options = _a.options, dateEnv = _a.dateEnv;
+ var props = this.props;
+ var startDate = props.startDate, todayRange = props.todayRange, dateProfile = props.dateProfile;
+ var title = dateEnv.format(startDate, options.dayPopoverFormat);
+ return (createElement(DayCellRoot, { date: startDate, dateProfile: dateProfile, todayRange: todayRange, elRef: this.handleRootEl }, function (rootElRef, dayClassNames, dataAttrs) { return (createElement(Popover, { elRef: rootElRef, title: title, extraClassNames: ['fc-more-popover'].concat(dayClassNames), extraAttrs: dataAttrs /* TODO: make these time-based when not whole-day? */, parentEl: props.parentEl, alignmentEl: props.alignmentEl, alignGridTop: props.alignGridTop, onClose: props.onClose },
+ createElement(DayCellContent, { date: startDate, dateProfile: dateProfile, todayRange: todayRange }, function (innerElRef, innerContent) { return (innerContent &&
+ createElement("div", { className: "fc-more-popover-misc", ref: innerElRef }, innerContent)); }),
+ props.children)); }));
+ };
+ MorePopover.prototype.queryHit = function (positionLeft, positionTop, elWidth, elHeight) {
+ var _a = this, rootEl = _a.rootEl, props = _a.props;
+ if (positionLeft >= 0 && positionLeft < elWidth &&
+ positionTop >= 0 && positionTop < elHeight) {
+ return {
+ dateProfile: props.dateProfile,
+ dateSpan: __assign({ allDay: true, range: {
+ start: props.startDate,
+ end: props.endDate,
+ } }, props.extraDateSpan),
+ dayEl: rootEl,
+ rect: {
+ left: 0,
+ top: 0,
+ right: elWidth,
+ bottom: elHeight,
+ },
+ layer: 1, // important when comparing with hits from other components
+ };
+ }
+ return null;
+ };
+ return MorePopover;
+ }(DateComponent));
+
+ var MoreLinkRoot = /** @class */ (function (_super) {
+ __extends(MoreLinkRoot, _super);
+ function MoreLinkRoot() {
+ var _this = _super !== null && _super.apply(this, arguments) || this;
+ _this.linkElRef = createRef();
+ _this.state = {
+ isPopoverOpen: false,
+ };
+ _this.handleClick = function (ev) {
+ var _a = _this, props = _a.props, context = _a.context;
+ var moreLinkClick = context.options.moreLinkClick;
+ var date = computeRange(props).start;
+ function buildPublicSeg(seg) {
+ var _a = seg.eventRange, def = _a.def, instance = _a.instance, range = _a.range;
+ return {
+ event: new EventApi(context, def, instance),
+ start: context.dateEnv.toDate(range.start),
+ end: context.dateEnv.toDate(range.end),
+ isStart: seg.isStart,
+ isEnd: seg.isEnd,
+ };
+ }
+ if (typeof moreLinkClick === 'function') {
+ moreLinkClick = moreLinkClick({
+ date: date,
+ allDay: Boolean(props.allDayDate),
+ allSegs: props.allSegs.map(buildPublicSeg),
+ hiddenSegs: props.hiddenSegs.map(buildPublicSeg),
+ jsEvent: ev,
+ view: context.viewApi,
+ });
+ }
+ if (!moreLinkClick || moreLinkClick === 'popover') {
+ _this.setState({ isPopoverOpen: true });
+ }
+ else if (typeof moreLinkClick === 'string') { // a view name
+ context.calendarApi.zoomTo(date, moreLinkClick);
+ }
+ };
+ _this.handlePopoverClose = function () {
+ _this.setState({ isPopoverOpen: false });
+ };
+ return _this;
+ }
+ MoreLinkRoot.prototype.render = function () {
+ var _this = this;
+ var props = this.props;
+ return (createElement(ViewContextType.Consumer, null, function (context) {
+ var viewApi = context.viewApi, options = context.options, calendarApi = context.calendarApi;
+ var moreLinkText = options.moreLinkText;
+ var moreCnt = props.moreCnt;
+ var range = computeRange(props);
+ var hookProps = {
+ num: moreCnt,
+ shortText: "+" + moreCnt,
+ text: typeof moreLinkText === 'function'
+ ? moreLinkText.call(calendarApi, moreCnt)
+ : "+" + moreCnt + " " + moreLinkText,
+ view: viewApi,
+ };
+ return (createElement(Fragment, null,
+ Boolean(props.moreCnt) && (createElement(RenderHook, { elRef: _this.linkElRef, hookProps: hookProps, classNames: options.moreLinkClassNames, content: options.moreLinkContent, defaultContent: props.defaultContent || renderMoreLinkInner$1, didMount: options.moreLinkDidMount, willUnmount: options.moreLinkWillUnmount }, function (rootElRef, customClassNames, innerElRef, innerContent) { return props.children(rootElRef, ['fc-more-link'].concat(customClassNames), innerElRef, innerContent, _this.handleClick); })),
+ _this.state.isPopoverOpen && (createElement(MorePopover, { startDate: range.start, endDate: range.end, dateProfile: props.dateProfile, todayRange: props.todayRange, extraDateSpan: props.extraDateSpan, parentEl: _this.parentEl, alignmentEl: props.alignmentElRef.current, alignGridTop: props.alignGridTop, onClose: _this.handlePopoverClose }, props.popoverContent()))));
+ }));
+ };
+ MoreLinkRoot.prototype.componentDidMount = function () {
+ this.updateParentEl();
+ };
+ MoreLinkRoot.prototype.componentDidUpdate = function () {
+ this.updateParentEl();
+ };
+ MoreLinkRoot.prototype.updateParentEl = function () {
+ if (this.linkElRef.current) {
+ this.parentEl = elementClosest(this.linkElRef.current, '.fc-view-harness');
+ }
+ };
+ return MoreLinkRoot;
+ }(BaseComponent));
+ function renderMoreLinkInner$1(props) {
+ return props.text;
+ }
+ function computeRange(props) {
+ if (props.allDayDate) {
+ return {
+ start: props.allDayDate,
+ end: addDays(props.allDayDate, 1),
+ };
+ }
+ var hiddenSegs = props.hiddenSegs;
+ return {
+ start: computeEarliestSegStart(hiddenSegs),
+ end: computeLatestSegEnd(hiddenSegs),
+ };
+ }
+ function computeEarliestSegStart(segs) {
+ return segs.reduce(pickEarliestStart).eventRange.range.start;
+ }
+ function pickEarliestStart(seg0, seg1) {
+ return seg0.eventRange.range.start < seg1.eventRange.range.start ? seg0 : seg1;
+ }
+ function computeLatestSegEnd(segs) {
+ return segs.reduce(pickLatestEnd).eventRange.range.end;
+ }
+ function pickLatestEnd(seg0, seg1) {
+ return seg0.eventRange.range.end > seg1.eventRange.range.end ? seg0 : seg1;
+ }
+
+ // exports
+ // --------------------------------------------------------------------------------------------------
+ var version = '5.9.0'; // important to type it, so .d.ts has generic string
+
+ var Calendar = /** @class */ (function (_super) {
+ __extends(Calendar, _super);
+ function Calendar(el, optionOverrides) {
+ if (optionOverrides === void 0) { optionOverrides = {}; }
+ var _this = _super.call(this) || this;
+ _this.isRendering = false;
+ _this.isRendered = false;
+ _this.currentClassNames = [];
+ _this.customContentRenderId = 0; // will affect custom generated classNames?
+ _this.handleAction = function (action) {
+ // actions we know we want to render immediately
+ switch (action.type) {
+ case 'SET_EVENT_DRAG':
+ case 'SET_EVENT_RESIZE':
+ _this.renderRunner.tryDrain();
+ }
+ };
+ _this.handleData = function (data) {
+ _this.currentData = data;
+ _this.renderRunner.request(data.calendarOptions.rerenderDelay);
+ };
+ _this.handleRenderRequest = function () {
+ if (_this.isRendering) {
+ _this.isRendered = true;
+ var currentData_1 = _this.currentData;
+ render(createElement(CalendarRoot, { options: currentData_1.calendarOptions, theme: currentData_1.theme, emitter: currentData_1.emitter }, function (classNames, height, isHeightAuto, forPrint) {
+ _this.setClassNames(classNames);
+ _this.setHeight(height);
+ return (createElement(CustomContentRenderContext.Provider, { value: _this.customContentRenderId },
+ createElement(CalendarContent, __assign({ isHeightAuto: isHeightAuto, forPrint: forPrint }, currentData_1))));
+ }), _this.el);
+ }
+ else if (_this.isRendered) {
+ _this.isRendered = false;
+ unmountComponentAtNode(_this.el);
+ _this.setClassNames([]);
+ _this.setHeight('');
+ }
+ flushToDom();
+ };
+ _this.el = el;
+ _this.renderRunner = new DelayedRunner(_this.handleRenderRequest);
+ new CalendarDataManager({
+ optionOverrides: optionOverrides,
+ calendarApi: _this,
+ onAction: _this.handleAction,
+ onData: _this.handleData,
+ });
+ return _this;
+ }
+ Object.defineProperty(Calendar.prototype, "view", {
+ get: function () { return this.currentData.viewApi; } // for public API
+ ,
+ enumerable: false,
+ configurable: true
+ });
+ Calendar.prototype.render = function () {
+ var wasRendering = this.isRendering;
+ if (!wasRendering) {
+ this.isRendering = true;
+ }
+ else {
+ this.customContentRenderId += 1;
+ }
+ this.renderRunner.request();
+ if (wasRendering) {
+ this.updateSize();
+ }
+ };
+ Calendar.prototype.destroy = function () {
+ if (this.isRendering) {
+ this.isRendering = false;
+ this.renderRunner.request();
+ }
+ };
+ Calendar.prototype.updateSize = function () {
+ _super.prototype.updateSize.call(this);
+ flushToDom();
+ };
+ Calendar.prototype.batchRendering = function (func) {
+ this.renderRunner.pause('batchRendering');
+ func();
+ this.renderRunner.resume('batchRendering');
+ };
+ Calendar.prototype.pauseRendering = function () {
+ this.renderRunner.pause('pauseRendering');
+ };
+ Calendar.prototype.resumeRendering = function () {
+ this.renderRunner.resume('pauseRendering', true);
+ };
+ Calendar.prototype.resetOptions = function (optionOverrides, append) {
+ this.currentDataManager.resetOptions(optionOverrides, append);
+ };
+ Calendar.prototype.setClassNames = function (classNames) {
+ if (!isArraysEqual(classNames, this.currentClassNames)) {
+ var classList = this.el.classList;
+ for (var _i = 0, _a = this.currentClassNames; _i < _a.length; _i++) {
+ var className = _a[_i];
+ classList.remove(className);
+ }
+ for (var _b = 0, classNames_1 = classNames; _b < classNames_1.length; _b++) {
+ var className = classNames_1[_b];
+ classList.add(className);
+ }
+ this.currentClassNames = classNames;
+ }
+ };
+ Calendar.prototype.setHeight = function (height) {
+ applyStyleProp(this.el, 'height', height);
+ };
+ return Calendar;
+ }(CalendarApi));
+
+ config.touchMouseIgnoreWait = 500;
+ var ignoreMouseDepth = 0;
+ var listenerCnt = 0;
+ var isWindowTouchMoveCancelled = false;
+ /*
+ Uses a "pointer" abstraction, which monitors UI events for both mouse and touch.
+ Tracks when the pointer "drags" on a certain element, meaning down+move+up.
+
+ Also, tracks if there was touch-scrolling.
+ Also, can prevent touch-scrolling from happening.
+ Also, can fire pointermove events when scrolling happens underneath, even when no real pointer movement.
+
+ emits:
+ - pointerdown
+ - pointermove
+ - pointerup
+ */
+ var PointerDragging = /** @class */ (function () {
+ function PointerDragging(containerEl) {
+ var _this = this;
+ this.subjectEl = null;
+ // options that can be directly assigned by caller
+ this.selector = ''; // will cause subjectEl in all emitted events to be this element
+ this.handleSelector = '';
+ this.shouldIgnoreMove = false;
+ this.shouldWatchScroll = true; // for simulating pointermove on scroll
+ // internal states
+ this.isDragging = false;
+ this.isTouchDragging = false;
+ this.wasTouchScroll = false;
+ // Mouse
+ // ----------------------------------------------------------------------------------------------------
+ this.handleMouseDown = function (ev) {
+ if (!_this.shouldIgnoreMouse() &&
+ isPrimaryMouseButton(ev) &&
+ _this.tryStart(ev)) {
+ var pev = _this.createEventFromMouse(ev, true);
+ _this.emitter.trigger('pointerdown', pev);
+ _this.initScrollWatch(pev);
+ if (!_this.shouldIgnoreMove) {
+ document.addEventListener('mousemove', _this.handleMouseMove);
+ }
+ document.addEventListener('mouseup', _this.handleMouseUp);
+ }
+ };
+ this.handleMouseMove = function (ev) {
+ var pev = _this.createEventFromMouse(ev);
+ _this.recordCoords(pev);
+ _this.emitter.trigger('pointermove', pev);
+ };
+ this.handleMouseUp = function (ev) {
+ document.removeEventListener('mousemove', _this.handleMouseMove);
+ document.removeEventListener('mouseup', _this.handleMouseUp);
+ _this.emitter.trigger('pointerup', _this.createEventFromMouse(ev));
+ _this.cleanup(); // call last so that pointerup has access to props
+ };
+ // Touch
+ // ----------------------------------------------------------------------------------------------------
+ this.handleTouchStart = function (ev) {
+ if (_this.tryStart(ev)) {
+ _this.isTouchDragging = true;
+ var pev = _this.createEventFromTouch(ev, true);
+ _this.emitter.trigger('pointerdown', pev);
+ _this.initScrollWatch(pev);
+ // unlike mouse, need to attach to target, not document
+ // https://stackoverflow.com/a/45760014
+ var targetEl = ev.target;
+ if (!_this.shouldIgnoreMove) {
+ targetEl.addEventListener('touchmove', _this.handleTouchMove);
+ }
+ targetEl.addEventListener('touchend', _this.handleTouchEnd);
+ targetEl.addEventListener('touchcancel', _this.handleTouchEnd); // treat it as a touch end
+ // attach a handler to get called when ANY scroll action happens on the page.
+ // this was impossible to do with normal on/off because 'scroll' doesn't bubble.
+ // http://stackoverflow.com/a/32954565/96342
+ window.addEventListener('scroll', _this.handleTouchScroll, true);
+ }
+ };
+ this.handleTouchMove = function (ev) {
+ var pev = _this.createEventFromTouch(ev);
+ _this.recordCoords(pev);
+ _this.emitter.trigger('pointermove', pev);
+ };
+ this.handleTouchEnd = function (ev) {
+ if (_this.isDragging) { // done to guard against touchend followed by touchcancel
+ var targetEl = ev.target;
+ targetEl.removeEventListener('touchmove', _this.handleTouchMove);
+ targetEl.removeEventListener('touchend', _this.handleTouchEnd);
+ targetEl.removeEventListener('touchcancel', _this.handleTouchEnd);
+ window.removeEventListener('scroll', _this.handleTouchScroll, true); // useCaptured=true
+ _this.emitter.trigger('pointerup', _this.createEventFromTouch(ev));
+ _this.cleanup(); // call last so that pointerup has access to props
+ _this.isTouchDragging = false;
+ startIgnoringMouse();
+ }
+ };
+ this.handleTouchScroll = function () {
+ _this.wasTouchScroll = true;
+ };
+ this.handleScroll = function (ev) {
+ if (!_this.shouldIgnoreMove) {
+ var pageX = (window.pageXOffset - _this.prevScrollX) + _this.prevPageX;
+ var pageY = (window.pageYOffset - _this.prevScrollY) + _this.prevPageY;
+ _this.emitter.trigger('pointermove', {
+ origEvent: ev,
+ isTouch: _this.isTouchDragging,
+ subjectEl: _this.subjectEl,
+ pageX: pageX,
+ pageY: pageY,
+ deltaX: pageX - _this.origPageX,
+ deltaY: pageY - _this.origPageY,
+ });
+ }
+ };
+ this.containerEl = containerEl;
+ this.emitter = new Emitter();
+ containerEl.addEventListener('mousedown', this.handleMouseDown);
+ containerEl.addEventListener('touchstart', this.handleTouchStart, { passive: true });
+ listenerCreated();
+ }
+ PointerDragging.prototype.destroy = function () {
+ this.containerEl.removeEventListener('mousedown', this.handleMouseDown);
+ this.containerEl.removeEventListener('touchstart', this.handleTouchStart, { passive: true });
+ listenerDestroyed();
+ };
+ PointerDragging.prototype.tryStart = function (ev) {
+ var subjectEl = this.querySubjectEl(ev);
+ var downEl = ev.target;
+ if (subjectEl &&
+ (!this.handleSelector || elementClosest(downEl, this.handleSelector))) {
+ this.subjectEl = subjectEl;
+ this.isDragging = true; // do this first so cancelTouchScroll will work
+ this.wasTouchScroll = false;
+ return true;
+ }
+ return false;
+ };
+ PointerDragging.prototype.cleanup = function () {
+ isWindowTouchMoveCancelled = false;
+ this.isDragging = false;
+ this.subjectEl = null;
+ // keep wasTouchScroll around for later access
+ this.destroyScrollWatch();
+ };
+ PointerDragging.prototype.querySubjectEl = function (ev) {
+ if (this.selector) {
+ return elementClosest(ev.target, this.selector);
+ }
+ return this.containerEl;
+ };
+ PointerDragging.prototype.shouldIgnoreMouse = function () {
+ return ignoreMouseDepth || this.isTouchDragging;
+ };
+ // can be called by user of this class, to cancel touch-based scrolling for the current drag
+ PointerDragging.prototype.cancelTouchScroll = function () {
+ if (this.isDragging) {
+ isWindowTouchMoveCancelled = true;
+ }
+ };
+ // Scrolling that simulates pointermoves
+ // ----------------------------------------------------------------------------------------------------
+ PointerDragging.prototype.initScrollWatch = function (ev) {
+ if (this.shouldWatchScroll) {
+ this.recordCoords(ev);
+ window.addEventListener('scroll', this.handleScroll, true); // useCapture=true
+ }
+ };
+ PointerDragging.prototype.recordCoords = function (ev) {
+ if (this.shouldWatchScroll) {
+ this.prevPageX = ev.pageX;
+ this.prevPageY = ev.pageY;
+ this.prevScrollX = window.pageXOffset;
+ this.prevScrollY = window.pageYOffset;
+ }
+ };
+ PointerDragging.prototype.destroyScrollWatch = function () {
+ if (this.shouldWatchScroll) {
+ window.removeEventListener('scroll', this.handleScroll, true); // useCaptured=true
+ }
+ };
+ // Event Normalization
+ // ----------------------------------------------------------------------------------------------------
+ PointerDragging.prototype.createEventFromMouse = function (ev, isFirst) {
+ var deltaX = 0;
+ var deltaY = 0;
+ // TODO: repeat code
+ if (isFirst) {
+ this.origPageX = ev.pageX;
+ this.origPageY = ev.pageY;
+ }
+ else {
+ deltaX = ev.pageX - this.origPageX;
+ deltaY = ev.pageY - this.origPageY;
+ }
+ return {
+ origEvent: ev,
+ isTouch: false,
+ subjectEl: this.subjectEl,
+ pageX: ev.pageX,
+ pageY: ev.pageY,
+ deltaX: deltaX,
+ deltaY: deltaY,
+ };
+ };
+ PointerDragging.prototype.createEventFromTouch = function (ev, isFirst) {
+ var touches = ev.touches;
+ var pageX;
+ var pageY;
+ var deltaX = 0;
+ var deltaY = 0;
+ // if touch coords available, prefer,
+ // because FF would give bad ev.pageX ev.pageY
+ if (touches && touches.length) {
+ pageX = touches[0].pageX;
+ pageY = touches[0].pageY;
+ }
+ else {
+ pageX = ev.pageX;
+ pageY = ev.pageY;
+ }
+ // TODO: repeat code
+ if (isFirst) {
+ this.origPageX = pageX;
+ this.origPageY = pageY;
+ }
+ else {
+ deltaX = pageX - this.origPageX;
+ deltaY = pageY - this.origPageY;
+ }
+ return {
+ origEvent: ev,
+ isTouch: true,
+ subjectEl: this.subjectEl,
+ pageX: pageX,
+ pageY: pageY,
+ deltaX: deltaX,
+ deltaY: deltaY,
+ };
+ };
+ return PointerDragging;
+ }());
+ // Returns a boolean whether this was a left mouse click and no ctrl key (which means right click on Mac)
+ function isPrimaryMouseButton(ev) {
+ return ev.button === 0 && !ev.ctrlKey;
+ }
+ // Ignoring fake mouse events generated by touch
+ // ----------------------------------------------------------------------------------------------------
+ function startIgnoringMouse() {
+ ignoreMouseDepth += 1;
+ setTimeout(function () {
+ ignoreMouseDepth -= 1;
+ }, config.touchMouseIgnoreWait);
+ }
+ // We want to attach touchmove as early as possible for Safari
+ // ----------------------------------------------------------------------------------------------------
+ function listenerCreated() {
+ listenerCnt += 1;
+ if (listenerCnt === 1) {
+ window.addEventListener('touchmove', onWindowTouchMove, { passive: false });
+ }
+ }
+ function listenerDestroyed() {
+ listenerCnt -= 1;
+ if (!listenerCnt) {
+ window.removeEventListener('touchmove', onWindowTouchMove, { passive: false });
+ }
+ }
+ function onWindowTouchMove(ev) {
+ if (isWindowTouchMoveCancelled) {
+ ev.preventDefault();
+ }
+ }
+
+ /*
+ An effect in which an element follows the movement of a pointer across the screen.
+ The moving element is a clone of some other element.
+ Must call start + handleMove + stop.
+ */
+ var ElementMirror = /** @class */ (function () {
+ function ElementMirror() {
+ this.isVisible = false; // must be explicitly enabled
+ this.sourceEl = null;
+ this.mirrorEl = null;
+ this.sourceElRect = null; // screen coords relative to viewport
+ // options that can be set directly by caller
+ this.parentNode = document.body; // HIGHLY SUGGESTED to set this to sidestep ShadowDOM issues
+ this.zIndex = 9999;
+ this.revertDuration = 0;
+ }
+ ElementMirror.prototype.start = function (sourceEl, pageX, pageY) {
+ this.sourceEl = sourceEl;
+ this.sourceElRect = this.sourceEl.getBoundingClientRect();
+ this.origScreenX = pageX - window.pageXOffset;
+ this.origScreenY = pageY - window.pageYOffset;
+ this.deltaX = 0;
+ this.deltaY = 0;
+ this.updateElPosition();
+ };
+ ElementMirror.prototype.handleMove = function (pageX, pageY) {
+ this.deltaX = (pageX - window.pageXOffset) - this.origScreenX;
+ this.deltaY = (pageY - window.pageYOffset) - this.origScreenY;
+ this.updateElPosition();
+ };
+ // can be called before start
+ ElementMirror.prototype.setIsVisible = function (bool) {
+ if (bool) {
+ if (!this.isVisible) {
+ if (this.mirrorEl) {
+ this.mirrorEl.style.display = '';
+ }
+ this.isVisible = bool; // needs to happen before updateElPosition
+ this.updateElPosition(); // because was not updating the position while invisible
+ }
+ }
+ else if (this.isVisible) {
+ if (this.mirrorEl) {
+ this.mirrorEl.style.display = 'none';
+ }
+ this.isVisible = bool;
+ }
+ };
+ // always async
+ ElementMirror.prototype.stop = function (needsRevertAnimation, callback) {
+ var _this = this;
+ var done = function () {
+ _this.cleanup();
+ callback();
+ };
+ if (needsRevertAnimation &&
+ this.mirrorEl &&
+ this.isVisible &&
+ this.revertDuration && // if 0, transition won't work
+ (this.deltaX || this.deltaY) // if same coords, transition won't work
+ ) {
+ this.doRevertAnimation(done, this.revertDuration);
+ }
+ else {
+ setTimeout(done, 0);
+ }
+ };
+ ElementMirror.prototype.doRevertAnimation = function (callback, revertDuration) {
+ var mirrorEl = this.mirrorEl;
+ var finalSourceElRect = this.sourceEl.getBoundingClientRect(); // because autoscrolling might have happened
+ mirrorEl.style.transition =
+ 'top ' + revertDuration + 'ms,' +
+ 'left ' + revertDuration + 'ms';
+ applyStyle(mirrorEl, {
+ left: finalSourceElRect.left,
+ top: finalSourceElRect.top,
+ });
+ whenTransitionDone(mirrorEl, function () {
+ mirrorEl.style.transition = '';
+ callback();
+ });
+ };
+ ElementMirror.prototype.cleanup = function () {
+ if (this.mirrorEl) {
+ removeElement(this.mirrorEl);
+ this.mirrorEl = null;
+ }
+ this.sourceEl = null;
+ };
+ ElementMirror.prototype.updateElPosition = function () {
+ if (this.sourceEl && this.isVisible) {
+ applyStyle(this.getMirrorEl(), {
+ left: this.sourceElRect.left + this.deltaX,
+ top: this.sourceElRect.top + this.deltaY,
+ });
+ }
+ };
+ ElementMirror.prototype.getMirrorEl = function () {
+ var sourceElRect = this.sourceElRect;
+ var mirrorEl = this.mirrorEl;
+ if (!mirrorEl) {
+ mirrorEl = this.mirrorEl = this.sourceEl.cloneNode(true); // cloneChildren=true
+ // we don't want long taps or any mouse interaction causing selection/menus.
+ // would use preventSelection(), but that prevents selectstart, causing problems.
+ mirrorEl.classList.add('fc-unselectable');
+ mirrorEl.classList.add('fc-event-dragging');
+ applyStyle(mirrorEl, {
+ position: 'fixed',
+ zIndex: this.zIndex,
+ visibility: '',
+ boxSizing: 'border-box',
+ width: sourceElRect.right - sourceElRect.left,
+ height: sourceElRect.bottom - sourceElRect.top,
+ right: 'auto',
+ bottom: 'auto',
+ margin: 0,
+ });
+ this.parentNode.appendChild(mirrorEl);
+ }
+ return mirrorEl;
+ };
+ return ElementMirror;
+ }());
+
+ /*
+ Is a cache for a given element's scroll information (all the info that ScrollController stores)
+ in addition the "client rectangle" of the element.. the area within the scrollbars.
+
+ The cache can be in one of two modes:
+ - doesListening:false - ignores when the container is scrolled by someone else
+ - doesListening:true - watch for scrolling and update the cache
+ */
+ var ScrollGeomCache = /** @class */ (function (_super) {
+ __extends(ScrollGeomCache, _super);
+ function ScrollGeomCache(scrollController, doesListening) {
+ var _this = _super.call(this) || this;
+ _this.handleScroll = function () {
+ _this.scrollTop = _this.scrollController.getScrollTop();
+ _this.scrollLeft = _this.scrollController.getScrollLeft();
+ _this.handleScrollChange();
+ };
+ _this.scrollController = scrollController;
+ _this.doesListening = doesListening;
+ _this.scrollTop = _this.origScrollTop = scrollController.getScrollTop();
+ _this.scrollLeft = _this.origScrollLeft = scrollController.getScrollLeft();
+ _this.scrollWidth = scrollController.getScrollWidth();
+ _this.scrollHeight = scrollController.getScrollHeight();
+ _this.clientWidth = scrollController.getClientWidth();
+ _this.clientHeight = scrollController.getClientHeight();
+ _this.clientRect = _this.computeClientRect(); // do last in case it needs cached values
+ if (_this.doesListening) {
+ _this.getEventTarget().addEventListener('scroll', _this.handleScroll);
+ }
+ return _this;
+ }
+ ScrollGeomCache.prototype.destroy = function () {
+ if (this.doesListening) {
+ this.getEventTarget().removeEventListener('scroll', this.handleScroll);
+ }
+ };
+ ScrollGeomCache.prototype.getScrollTop = function () {
+ return this.scrollTop;
+ };
+ ScrollGeomCache.prototype.getScrollLeft = function () {
+ return this.scrollLeft;
+ };
+ ScrollGeomCache.prototype.setScrollTop = function (top) {
+ this.scrollController.setScrollTop(top);
+ if (!this.doesListening) {
+ // we are not relying on the element to normalize out-of-bounds scroll values
+ // so we need to sanitize ourselves
+ this.scrollTop = Math.max(Math.min(top, this.getMaxScrollTop()), 0);
+ this.handleScrollChange();
+ }
+ };
+ ScrollGeomCache.prototype.setScrollLeft = function (top) {
+ this.scrollController.setScrollLeft(top);
+ if (!this.doesListening) {
+ // we are not relying on the element to normalize out-of-bounds scroll values
+ // so we need to sanitize ourselves
+ this.scrollLeft = Math.max(Math.min(top, this.getMaxScrollLeft()), 0);
+ this.handleScrollChange();
+ }
+ };
+ ScrollGeomCache.prototype.getClientWidth = function () {
+ return this.clientWidth;
+ };
+ ScrollGeomCache.prototype.getClientHeight = function () {
+ return this.clientHeight;
+ };
+ ScrollGeomCache.prototype.getScrollWidth = function () {
+ return this.scrollWidth;
+ };
+ ScrollGeomCache.prototype.getScrollHeight = function () {
+ return this.scrollHeight;
+ };
+ ScrollGeomCache.prototype.handleScrollChange = function () {
+ };
+ return ScrollGeomCache;
+ }(ScrollController));
+
+ var ElementScrollGeomCache = /** @class */ (function (_super) {
+ __extends(ElementScrollGeomCache, _super);
+ function ElementScrollGeomCache(el, doesListening) {
+ return _super.call(this, new ElementScrollController(el), doesListening) || this;
+ }
+ ElementScrollGeomCache.prototype.getEventTarget = function () {
+ return this.scrollController.el;
+ };
+ ElementScrollGeomCache.prototype.computeClientRect = function () {
+ return computeInnerRect(this.scrollController.el);
+ };
+ return ElementScrollGeomCache;
+ }(ScrollGeomCache));
+
+ var WindowScrollGeomCache = /** @class */ (function (_super) {
+ __extends(WindowScrollGeomCache, _super);
+ function WindowScrollGeomCache(doesListening) {
+ return _super.call(this, new WindowScrollController(), doesListening) || this;
+ }
+ WindowScrollGeomCache.prototype.getEventTarget = function () {
+ return window;
+ };
+ WindowScrollGeomCache.prototype.computeClientRect = function () {
+ return {
+ left: this.scrollLeft,
+ right: this.scrollLeft + this.clientWidth,
+ top: this.scrollTop,
+ bottom: this.scrollTop + this.clientHeight,
+ };
+ };
+ // the window is the only scroll object that changes it's rectangle relative
+ // to the document's topleft as it scrolls
+ WindowScrollGeomCache.prototype.handleScrollChange = function () {
+ this.clientRect = this.computeClientRect();
+ };
+ return WindowScrollGeomCache;
+ }(ScrollGeomCache));
+
+ // If available we are using native "performance" API instead of "Date"
+ // Read more about it on MDN:
+ // https://developer.mozilla.org/en-US/docs/Web/API/Performance
+ var getTime = typeof performance === 'function' ? performance.now : Date.now;
+ /*
+ For a pointer interaction, automatically scrolls certain scroll containers when the pointer
+ approaches the edge.
+
+ The caller must call start + handleMove + stop.
+ */
+ var AutoScroller = /** @class */ (function () {
+ function AutoScroller() {
+ var _this = this;
+ // options that can be set by caller
+ this.isEnabled = true;
+ this.scrollQuery = [window, '.fc-scroller'];
+ this.edgeThreshold = 50; // pixels
+ this.maxVelocity = 300; // pixels per second
+ // internal state
+ this.pointerScreenX = null;
+ this.pointerScreenY = null;
+ this.isAnimating = false;
+ this.scrollCaches = null;
+ // protect against the initial pointerdown being too close to an edge and starting the scroll
+ this.everMovedUp = false;
+ this.everMovedDown = false;
+ this.everMovedLeft = false;
+ this.everMovedRight = false;
+ this.animate = function () {
+ if (_this.isAnimating) { // wasn't cancelled between animation calls
+ var edge = _this.computeBestEdge(_this.pointerScreenX + window.pageXOffset, _this.pointerScreenY + window.pageYOffset);
+ if (edge) {
+ var now = getTime();
+ _this.handleSide(edge, (now - _this.msSinceRequest) / 1000);
+ _this.requestAnimation(now);
+ }
+ else {
+ _this.isAnimating = false; // will stop animation
+ }
+ }
+ };
+ }
+ AutoScroller.prototype.start = function (pageX, pageY, scrollStartEl) {
+ if (this.isEnabled) {
+ this.scrollCaches = this.buildCaches(scrollStartEl);
+ this.pointerScreenX = null;
+ this.pointerScreenY = null;
+ this.everMovedUp = false;
+ this.everMovedDown = false;
+ this.everMovedLeft = false;
+ this.everMovedRight = false;
+ this.handleMove(pageX, pageY);
+ }
+ };
+ AutoScroller.prototype.handleMove = function (pageX, pageY) {
+ if (this.isEnabled) {
+ var pointerScreenX = pageX - window.pageXOffset;
+ var pointerScreenY = pageY - window.pageYOffset;
+ var yDelta = this.pointerScreenY === null ? 0 : pointerScreenY - this.pointerScreenY;
+ var xDelta = this.pointerScreenX === null ? 0 : pointerScreenX - this.pointerScreenX;
+ if (yDelta < 0) {
+ this.everMovedUp = true;
+ }
+ else if (yDelta > 0) {
+ this.everMovedDown = true;
+ }
+ if (xDelta < 0) {
+ this.everMovedLeft = true;
+ }
+ else if (xDelta > 0) {
+ this.everMovedRight = true;
+ }
+ this.pointerScreenX = pointerScreenX;
+ this.pointerScreenY = pointerScreenY;
+ if (!this.isAnimating) {
+ this.isAnimating = true;
+ this.requestAnimation(getTime());
+ }
+ }
+ };
+ AutoScroller.prototype.stop = function () {
+ if (this.isEnabled) {
+ this.isAnimating = false; // will stop animation
+ for (var _i = 0, _a = this.scrollCaches; _i < _a.length; _i++) {
+ var scrollCache = _a[_i];
+ scrollCache.destroy();
+ }
+ this.scrollCaches = null;
+ }
+ };
+ AutoScroller.prototype.requestAnimation = function (now) {
+ this.msSinceRequest = now;
+ requestAnimationFrame(this.animate);
+ };
+ AutoScroller.prototype.handleSide = function (edge, seconds) {
+ var scrollCache = edge.scrollCache;
+ var edgeThreshold = this.edgeThreshold;
+ var invDistance = edgeThreshold - edge.distance;
+ var velocity = // the closer to the edge, the faster we scroll
+ ((invDistance * invDistance) / (edgeThreshold * edgeThreshold)) * // quadratic
+ this.maxVelocity * seconds;
+ var sign = 1;
+ switch (edge.name) {
+ case 'left':
+ sign = -1;
+ // falls through
+ case 'right':
+ scrollCache.setScrollLeft(scrollCache.getScrollLeft() + velocity * sign);
+ break;
+ case 'top':
+ sign = -1;
+ // falls through
+ case 'bottom':
+ scrollCache.setScrollTop(scrollCache.getScrollTop() + velocity * sign);
+ break;
+ }
+ };
+ // left/top are relative to document topleft
+ AutoScroller.prototype.computeBestEdge = function (left, top) {
+ var edgeThreshold = this.edgeThreshold;
+ var bestSide = null;
+ for (var _i = 0, _a = this.scrollCaches; _i < _a.length; _i++) {
+ var scrollCache = _a[_i];
+ var rect = scrollCache.clientRect;
+ var leftDist = left - rect.left;
+ var rightDist = rect.right - left;
+ var topDist = top - rect.top;
+ var bottomDist = rect.bottom - top;
+ // completely within the rect?
+ if (leftDist >= 0 && rightDist >= 0 && topDist >= 0 && bottomDist >= 0) {
+ if (topDist <= edgeThreshold && this.everMovedUp && scrollCache.canScrollUp() &&
+ (!bestSide || bestSide.distance > topDist)) {
+ bestSide = { scrollCache: scrollCache, name: 'top', distance: topDist };
+ }
+ if (bottomDist <= edgeThreshold && this.everMovedDown && scrollCache.canScrollDown() &&
+ (!bestSide || bestSide.distance > bottomDist)) {
+ bestSide = { scrollCache: scrollCache, name: 'bottom', distance: bottomDist };
+ }
+ if (leftDist <= edgeThreshold && this.everMovedLeft && scrollCache.canScrollLeft() &&
+ (!bestSide || bestSide.distance > leftDist)) {
+ bestSide = { scrollCache: scrollCache, name: 'left', distance: leftDist };
+ }
+ if (rightDist <= edgeThreshold && this.everMovedRight && scrollCache.canScrollRight() &&
+ (!bestSide || bestSide.distance > rightDist)) {
+ bestSide = { scrollCache: scrollCache, name: 'right', distance: rightDist };
+ }
+ }
+ }
+ return bestSide;
+ };
+ AutoScroller.prototype.buildCaches = function (scrollStartEl) {
+ return this.queryScrollEls(scrollStartEl).map(function (el) {
+ if (el === window) {
+ return new WindowScrollGeomCache(false); // false = don't listen to user-generated scrolls
+ }
+ return new ElementScrollGeomCache(el, false); // false = don't listen to user-generated scrolls
+ });
+ };
+ AutoScroller.prototype.queryScrollEls = function (scrollStartEl) {
+ var els = [];
+ for (var _i = 0, _a = this.scrollQuery; _i < _a.length; _i++) {
+ var query = _a[_i];
+ if (typeof query === 'object') {
+ els.push(query);
+ }
+ else {
+ els.push.apply(els, Array.prototype.slice.call(getElRoot(scrollStartEl).querySelectorAll(query)));
+ }
+ }
+ return els;
+ };
+ return AutoScroller;
+ }());
+
+ /*
+ Monitors dragging on an element. Has a number of high-level features:
+ - minimum distance required before dragging
+ - minimum wait time ("delay") before dragging
+ - a mirror element that follows the pointer
+ */
+ var FeaturefulElementDragging = /** @class */ (function (_super) {
+ __extends(FeaturefulElementDragging, _super);
+ function FeaturefulElementDragging(containerEl, selector) {
+ var _this = _super.call(this, containerEl) || this;
+ _this.containerEl = containerEl;
+ // options that can be directly set by caller
+ // the caller can also set the PointerDragging's options as well
+ _this.delay = null;
+ _this.minDistance = 0;
+ _this.touchScrollAllowed = true; // prevents drag from starting and blocks scrolling during drag
+ _this.mirrorNeedsRevert = false;
+ _this.isInteracting = false; // is the user validly moving the pointer? lasts until pointerup
+ _this.isDragging = false; // is it INTENTFULLY dragging? lasts until after revert animation
+ _this.isDelayEnded = false;
+ _this.isDistanceSurpassed = false;
+ _this.delayTimeoutId = null;
+ _this.onPointerDown = function (ev) {
+ if (!_this.isDragging) { // so new drag doesn't happen while revert animation is going
+ _this.isInteracting = true;
+ _this.isDelayEnded = false;
+ _this.isDistanceSurpassed = false;
+ preventSelection(document.body);
+ preventContextMenu(document.body);
+ // prevent links from being visited if there's an eventual drag.
+ // also prevents selection in older browsers (maybe?).
+ // not necessary for touch, besides, browser would complain about passiveness.
+ if (!ev.isTouch) {
+ ev.origEvent.preventDefault();
+ }
+ _this.emitter.trigger('pointerdown', ev);
+ if (_this.isInteracting && // not destroyed via pointerdown handler
+ !_this.pointer.shouldIgnoreMove) {
+ // actions related to initiating dragstart+dragmove+dragend...
+ _this.mirror.setIsVisible(false); // reset. caller must set-visible
+ _this.mirror.start(ev.subjectEl, ev.pageX, ev.pageY); // must happen on first pointer down
+ _this.startDelay(ev);
+ if (!_this.minDistance) {
+ _this.handleDistanceSurpassed(ev);
+ }
+ }
+ }
+ };
+ _this.onPointerMove = function (ev) {
+ if (_this.isInteracting) {
+ _this.emitter.trigger('pointermove', ev);
+ if (!_this.isDistanceSurpassed) {
+ var minDistance = _this.minDistance;
+ var distanceSq = void 0; // current distance from the origin, squared
+ var deltaX = ev.deltaX, deltaY = ev.deltaY;
+ distanceSq = deltaX * deltaX + deltaY * deltaY;
+ if (distanceSq >= minDistance * minDistance) { // use pythagorean theorem
+ _this.handleDistanceSurpassed(ev);
+ }
+ }
+ if (_this.isDragging) {
+ // a real pointer move? (not one simulated by scrolling)
+ if (ev.origEvent.type !== 'scroll') {
+ _this.mirror.handleMove(ev.pageX, ev.pageY);
+ _this.autoScroller.handleMove(ev.pageX, ev.pageY);
+ }
+ _this.emitter.trigger('dragmove', ev);
+ }
+ }
+ };
+ _this.onPointerUp = function (ev) {
+ if (_this.isInteracting) {
+ _this.isInteracting = false;
+ allowSelection(document.body);
+ allowContextMenu(document.body);
+ _this.emitter.trigger('pointerup', ev); // can potentially set mirrorNeedsRevert
+ if (_this.isDragging) {
+ _this.autoScroller.stop();
+ _this.tryStopDrag(ev); // which will stop the mirror
+ }
+ if (_this.delayTimeoutId) {
+ clearTimeout(_this.delayTimeoutId);
+ _this.delayTimeoutId = null;
+ }
+ }
+ };
+ var pointer = _this.pointer = new PointerDragging(containerEl);
+ pointer.emitter.on('pointerdown', _this.onPointerDown);
+ pointer.emitter.on('pointermove', _this.onPointerMove);
+ pointer.emitter.on('pointerup', _this.onPointerUp);
+ if (selector) {
+ pointer.selector = selector;
+ }
+ _this.mirror = new ElementMirror();
+ _this.autoScroller = new AutoScroller();
+ return _this;
+ }
+ FeaturefulElementDragging.prototype.destroy = function () {
+ this.pointer.destroy();
+ // HACK: simulate a pointer-up to end the current drag
+ // TODO: fire 'dragend' directly and stop interaction. discourage use of pointerup event (b/c might not fire)
+ this.onPointerUp({});
+ };
+ FeaturefulElementDragging.prototype.startDelay = function (ev) {
+ var _this = this;
+ if (typeof this.delay === 'number') {
+ this.delayTimeoutId = setTimeout(function () {
+ _this.delayTimeoutId = null;
+ _this.handleDelayEnd(ev);
+ }, this.delay); // not assignable to number!
+ }
+ else {
+ this.handleDelayEnd(ev);
+ }
+ };
+ FeaturefulElementDragging.prototype.handleDelayEnd = function (ev) {
+ this.isDelayEnded = true;
+ this.tryStartDrag(ev);
+ };
+ FeaturefulElementDragging.prototype.handleDistanceSurpassed = function (ev) {
+ this.isDistanceSurpassed = true;
+ this.tryStartDrag(ev);
+ };
+ FeaturefulElementDragging.prototype.tryStartDrag = function (ev) {
+ if (this.isDelayEnded && this.isDistanceSurpassed) {
+ if (!this.pointer.wasTouchScroll || this.touchScrollAllowed) {
+ this.isDragging = true;
+ this.mirrorNeedsRevert = false;
+ this.autoScroller.start(ev.pageX, ev.pageY, this.containerEl);
+ this.emitter.trigger('dragstart', ev);
+ if (this.touchScrollAllowed === false) {
+ this.pointer.cancelTouchScroll();
+ }
+ }
+ }
+ };
+ FeaturefulElementDragging.prototype.tryStopDrag = function (ev) {
+ // .stop() is ALWAYS asynchronous, which we NEED because we want all pointerup events
+ // that come from the document to fire beforehand. much more convenient this way.
+ this.mirror.stop(this.mirrorNeedsRevert, this.stopDrag.bind(this, ev));
+ };
+ FeaturefulElementDragging.prototype.stopDrag = function (ev) {
+ this.isDragging = false;
+ this.emitter.trigger('dragend', ev);
+ };
+ // fill in the implementations...
+ FeaturefulElementDragging.prototype.setIgnoreMove = function (bool) {
+ this.pointer.shouldIgnoreMove = bool;
+ };
+ FeaturefulElementDragging.prototype.setMirrorIsVisible = function (bool) {
+ this.mirror.setIsVisible(bool);
+ };
+ FeaturefulElementDragging.prototype.setMirrorNeedsRevert = function (bool) {
+ this.mirrorNeedsRevert = bool;
+ };
+ FeaturefulElementDragging.prototype.setAutoScrollEnabled = function (bool) {
+ this.autoScroller.isEnabled = bool;
+ };
+ return FeaturefulElementDragging;
+ }(ElementDragging));
+
+ /*
+ When this class is instantiated, it records the offset of an element (relative to the document topleft),
+ and continues to monitor scrolling, updating the cached coordinates if it needs to.
+ Does not access the DOM after instantiation, so highly performant.
+
+ Also keeps track of all scrolling/overflow:hidden containers that are parents of the given element
+ and an determine if a given point is inside the combined clipping rectangle.
+ */
+ var OffsetTracker = /** @class */ (function () {
+ function OffsetTracker(el) {
+ this.origRect = computeRect(el);
+ // will work fine for divs that have overflow:hidden
+ this.scrollCaches = getClippingParents(el).map(function (scrollEl) { return new ElementScrollGeomCache(scrollEl, true); });
+ }
+ OffsetTracker.prototype.destroy = function () {
+ for (var _i = 0, _a = this.scrollCaches; _i < _a.length; _i++) {
+ var scrollCache = _a[_i];
+ scrollCache.destroy();
+ }
+ };
+ OffsetTracker.prototype.computeLeft = function () {
+ var left = this.origRect.left;
+ for (var _i = 0, _a = this.scrollCaches; _i < _a.length; _i++) {
+ var scrollCache = _a[_i];
+ left += scrollCache.origScrollLeft - scrollCache.getScrollLeft();
+ }
+ return left;
+ };
+ OffsetTracker.prototype.computeTop = function () {
+ var top = this.origRect.top;
+ for (var _i = 0, _a = this.scrollCaches; _i < _a.length; _i++) {
+ var scrollCache = _a[_i];
+ top += scrollCache.origScrollTop - scrollCache.getScrollTop();
+ }
+ return top;
+ };
+ OffsetTracker.prototype.isWithinClipping = function (pageX, pageY) {
+ var point = { left: pageX, top: pageY };
+ for (var _i = 0, _a = this.scrollCaches; _i < _a.length; _i++) {
+ var scrollCache = _a[_i];
+ if (!isIgnoredClipping(scrollCache.getEventTarget()) &&
+ !pointInsideRect(point, scrollCache.clientRect)) {
+ return false;
+ }
+ }
+ return true;
+ };
+ return OffsetTracker;
+ }());
+ // certain clipping containers should never constrain interactions, like and
+ // https://github.com/fullcalendar/fullcalendar/issues/3615
+ function isIgnoredClipping(node) {
+ var tagName = node.tagName;
+ return tagName === 'HTML' || tagName === 'BODY';
+ }
+
+ /*
+ Tracks movement over multiple droppable areas (aka "hits")
+ that exist in one or more DateComponents.
+ Relies on an existing draggable.
+
+ emits:
+ - pointerdown
+ - dragstart
+ - hitchange - fires initially, even if not over a hit
+ - pointerup
+ - (hitchange - again, to null, if ended over a hit)
+ - dragend
+ */
+ var HitDragging = /** @class */ (function () {
+ function HitDragging(dragging, droppableStore) {
+ var _this = this;
+ // options that can be set by caller
+ this.useSubjectCenter = false;
+ this.requireInitial = true; // if doesn't start out on a hit, won't emit any events
+ this.initialHit = null;
+ this.movingHit = null;
+ this.finalHit = null; // won't ever be populated if shouldIgnoreMove
+ this.handlePointerDown = function (ev) {
+ var dragging = _this.dragging;
+ _this.initialHit = null;
+ _this.movingHit = null;
+ _this.finalHit = null;
+ _this.prepareHits();
+ _this.processFirstCoord(ev);
+ if (_this.initialHit || !_this.requireInitial) {
+ dragging.setIgnoreMove(false);
+ // TODO: fire this before computing processFirstCoord, so listeners can cancel. this gets fired by almost every handler :(
+ _this.emitter.trigger('pointerdown', ev);
+ }
+ else {
+ dragging.setIgnoreMove(true);
+ }
+ };
+ this.handleDragStart = function (ev) {
+ _this.emitter.trigger('dragstart', ev);
+ _this.handleMove(ev, true); // force = fire even if initially null
+ };
+ this.handleDragMove = function (ev) {
+ _this.emitter.trigger('dragmove', ev);
+ _this.handleMove(ev);
+ };
+ this.handlePointerUp = function (ev) {
+ _this.releaseHits();
+ _this.emitter.trigger('pointerup', ev);
+ };
+ this.handleDragEnd = function (ev) {
+ if (_this.movingHit) {
+ _this.emitter.trigger('hitupdate', null, true, ev);
+ }
+ _this.finalHit = _this.movingHit;
+ _this.movingHit = null;
+ _this.emitter.trigger('dragend', ev);
+ };
+ this.droppableStore = droppableStore;
+ dragging.emitter.on('pointerdown', this.handlePointerDown);
+ dragging.emitter.on('dragstart', this.handleDragStart);
+ dragging.emitter.on('dragmove', this.handleDragMove);
+ dragging.emitter.on('pointerup', this.handlePointerUp);
+ dragging.emitter.on('dragend', this.handleDragEnd);
+ this.dragging = dragging;
+ this.emitter = new Emitter();
+ }
+ // sets initialHit
+ // sets coordAdjust
+ HitDragging.prototype.processFirstCoord = function (ev) {
+ var origPoint = { left: ev.pageX, top: ev.pageY };
+ var adjustedPoint = origPoint;
+ var subjectEl = ev.subjectEl;
+ var subjectRect;
+ if (subjectEl instanceof HTMLElement) { // i.e. not a Document/ShadowRoot
+ subjectRect = computeRect(subjectEl);
+ adjustedPoint = constrainPoint(adjustedPoint, subjectRect);
+ }
+ var initialHit = this.initialHit = this.queryHitForOffset(adjustedPoint.left, adjustedPoint.top);
+ if (initialHit) {
+ if (this.useSubjectCenter && subjectRect) {
+ var slicedSubjectRect = intersectRects(subjectRect, initialHit.rect);
+ if (slicedSubjectRect) {
+ adjustedPoint = getRectCenter(slicedSubjectRect);
+ }
+ }
+ this.coordAdjust = diffPoints(adjustedPoint, origPoint);
+ }
+ else {
+ this.coordAdjust = { left: 0, top: 0 };
+ }
+ };
+ HitDragging.prototype.handleMove = function (ev, forceHandle) {
+ var hit = this.queryHitForOffset(ev.pageX + this.coordAdjust.left, ev.pageY + this.coordAdjust.top);
+ if (forceHandle || !isHitsEqual(this.movingHit, hit)) {
+ this.movingHit = hit;
+ this.emitter.trigger('hitupdate', hit, false, ev);
+ }
+ };
+ HitDragging.prototype.prepareHits = function () {
+ this.offsetTrackers = mapHash(this.droppableStore, function (interactionSettings) {
+ interactionSettings.component.prepareHits();
+ return new OffsetTracker(interactionSettings.el);
+ });
+ };
+ HitDragging.prototype.releaseHits = function () {
+ var offsetTrackers = this.offsetTrackers;
+ for (var id in offsetTrackers) {
+ offsetTrackers[id].destroy();
+ }
+ this.offsetTrackers = {};
+ };
+ HitDragging.prototype.queryHitForOffset = function (offsetLeft, offsetTop) {
+ var _a = this, droppableStore = _a.droppableStore, offsetTrackers = _a.offsetTrackers;
+ var bestHit = null;
+ for (var id in droppableStore) {
+ var component = droppableStore[id].component;
+ var offsetTracker = offsetTrackers[id];
+ if (offsetTracker && // wasn't destroyed mid-drag
+ offsetTracker.isWithinClipping(offsetLeft, offsetTop)) {
+ var originLeft = offsetTracker.computeLeft();
+ var originTop = offsetTracker.computeTop();
+ var positionLeft = offsetLeft - originLeft;
+ var positionTop = offsetTop - originTop;
+ var origRect = offsetTracker.origRect;
+ var width = origRect.right - origRect.left;
+ var height = origRect.bottom - origRect.top;
+ if (
+ // must be within the element's bounds
+ positionLeft >= 0 && positionLeft < width &&
+ positionTop >= 0 && positionTop < height) {
+ var hit = component.queryHit(positionLeft, positionTop, width, height);
+ if (hit && (
+ // make sure the hit is within activeRange, meaning it's not a dead cell
+ rangeContainsRange(hit.dateProfile.activeRange, hit.dateSpan.range)) &&
+ (!bestHit || hit.layer > bestHit.layer)) {
+ hit.componentId = id;
+ hit.context = component.context;
+ // TODO: better way to re-orient rectangle
+ hit.rect.left += originLeft;
+ hit.rect.right += originLeft;
+ hit.rect.top += originTop;
+ hit.rect.bottom += originTop;
+ bestHit = hit;
+ }
+ }
+ }
+ }
+ return bestHit;
+ };
+ return HitDragging;
+ }());
+ function isHitsEqual(hit0, hit1) {
+ if (!hit0 && !hit1) {
+ return true;
+ }
+ if (Boolean(hit0) !== Boolean(hit1)) {
+ return false;
+ }
+ return isDateSpansEqual(hit0.dateSpan, hit1.dateSpan);
+ }
+
+ function buildDatePointApiWithContext(dateSpan, context) {
+ var props = {};
+ for (var _i = 0, _a = context.pluginHooks.datePointTransforms; _i < _a.length; _i++) {
+ var transform = _a[_i];
+ __assign(props, transform(dateSpan, context));
+ }
+ __assign(props, buildDatePointApi(dateSpan, context.dateEnv));
+ return props;
+ }
+ function buildDatePointApi(span, dateEnv) {
+ return {
+ date: dateEnv.toDate(span.range.start),
+ dateStr: dateEnv.formatIso(span.range.start, { omitTime: span.allDay }),
+ allDay: span.allDay,
+ };
+ }
+
+ /*
+ Monitors when the user clicks on a specific date/time of a component.
+ A pointerdown+pointerup on the same "hit" constitutes a click.
+ */
+ var DateClicking = /** @class */ (function (_super) {
+ __extends(DateClicking, _super);
+ function DateClicking(settings) {
+ var _this = _super.call(this, settings) || this;
+ _this.handlePointerDown = function (pev) {
+ var dragging = _this.dragging;
+ var downEl = pev.origEvent.target;
+ // do this in pointerdown (not dragend) because DOM might be mutated by the time dragend is fired
+ dragging.setIgnoreMove(!_this.component.isValidDateDownEl(downEl));
+ };
+ // won't even fire if moving was ignored
+ _this.handleDragEnd = function (ev) {
+ var component = _this.component;
+ var pointer = _this.dragging.pointer;
+ if (!pointer.wasTouchScroll) {
+ var _a = _this.hitDragging, initialHit = _a.initialHit, finalHit = _a.finalHit;
+ if (initialHit && finalHit && isHitsEqual(initialHit, finalHit)) {
+ var context = component.context;
+ var arg = __assign(__assign({}, buildDatePointApiWithContext(initialHit.dateSpan, context)), { dayEl: initialHit.dayEl, jsEvent: ev.origEvent, view: context.viewApi || context.calendarApi.view });
+ context.emitter.trigger('dateClick', arg);
+ }
+ }
+ };
+ // we DO want to watch pointer moves because otherwise finalHit won't get populated
+ _this.dragging = new FeaturefulElementDragging(settings.el);
+ _this.dragging.autoScroller.isEnabled = false;
+ var hitDragging = _this.hitDragging = new HitDragging(_this.dragging, interactionSettingsToStore(settings));
+ hitDragging.emitter.on('pointerdown', _this.handlePointerDown);
+ hitDragging.emitter.on('dragend', _this.handleDragEnd);
+ return _this;
+ }
+ DateClicking.prototype.destroy = function () {
+ this.dragging.destroy();
+ };
+ return DateClicking;
+ }(Interaction));
+
+ /*
+ Tracks when the user selects a portion of time of a component,
+ constituted by a drag over date cells, with a possible delay at the beginning of the drag.
+ */
+ var DateSelecting = /** @class */ (function (_super) {
+ __extends(DateSelecting, _super);
+ function DateSelecting(settings) {
+ var _this = _super.call(this, settings) || this;
+ _this.dragSelection = null;
+ _this.handlePointerDown = function (ev) {
+ var _a = _this, component = _a.component, dragging = _a.dragging;
+ var options = component.context.options;
+ var canSelect = options.selectable &&
+ component.isValidDateDownEl(ev.origEvent.target);
+ // don't bother to watch expensive moves if component won't do selection
+ dragging.setIgnoreMove(!canSelect);
+ // if touch, require user to hold down
+ dragging.delay = ev.isTouch ? getComponentTouchDelay$1(component) : null;
+ };
+ _this.handleDragStart = function (ev) {
+ _this.component.context.calendarApi.unselect(ev); // unselect previous selections
+ };
+ _this.handleHitUpdate = function (hit, isFinal) {
+ var context = _this.component.context;
+ var dragSelection = null;
+ var isInvalid = false;
+ if (hit) {
+ var initialHit = _this.hitDragging.initialHit;
+ var disallowed = hit.componentId === initialHit.componentId
+ && _this.isHitComboAllowed
+ && !_this.isHitComboAllowed(initialHit, hit);
+ if (!disallowed) {
+ dragSelection = joinHitsIntoSelection(initialHit, hit, context.pluginHooks.dateSelectionTransformers);
+ }
+ if (!dragSelection || !isDateSelectionValid(dragSelection, hit.dateProfile, context)) {
+ isInvalid = true;
+ dragSelection = null;
+ }
+ }
+ if (dragSelection) {
+ context.dispatch({ type: 'SELECT_DATES', selection: dragSelection });
+ }
+ else if (!isFinal) { // only unselect if moved away while dragging
+ context.dispatch({ type: 'UNSELECT_DATES' });
+ }
+ if (!isInvalid) {
+ enableCursor();
+ }
+ else {
+ disableCursor();
+ }
+ if (!isFinal) {
+ _this.dragSelection = dragSelection; // only clear if moved away from all hits while dragging
+ }
+ };
+ _this.handlePointerUp = function (pev) {
+ if (_this.dragSelection) {
+ // selection is already rendered, so just need to report selection
+ triggerDateSelect(_this.dragSelection, pev, _this.component.context);
+ _this.dragSelection = null;
+ }
+ };
+ var component = settings.component;
+ var options = component.context.options;
+ var dragging = _this.dragging = new FeaturefulElementDragging(settings.el);
+ dragging.touchScrollAllowed = false;
+ dragging.minDistance = options.selectMinDistance || 0;
+ dragging.autoScroller.isEnabled = options.dragScroll;
+ var hitDragging = _this.hitDragging = new HitDragging(_this.dragging, interactionSettingsToStore(settings));
+ hitDragging.emitter.on('pointerdown', _this.handlePointerDown);
+ hitDragging.emitter.on('dragstart', _this.handleDragStart);
+ hitDragging.emitter.on('hitupdate', _this.handleHitUpdate);
+ hitDragging.emitter.on('pointerup', _this.handlePointerUp);
+ return _this;
+ }
+ DateSelecting.prototype.destroy = function () {
+ this.dragging.destroy();
+ };
+ return DateSelecting;
+ }(Interaction));
+ function getComponentTouchDelay$1(component) {
+ var options = component.context.options;
+ var delay = options.selectLongPressDelay;
+ if (delay == null) {
+ delay = options.longPressDelay;
+ }
+ return delay;
+ }
+ function joinHitsIntoSelection(hit0, hit1, dateSelectionTransformers) {
+ var dateSpan0 = hit0.dateSpan;
+ var dateSpan1 = hit1.dateSpan;
+ var ms = [
+ dateSpan0.range.start,
+ dateSpan0.range.end,
+ dateSpan1.range.start,
+ dateSpan1.range.end,
+ ];
+ ms.sort(compareNumbers);
+ var props = {};
+ for (var _i = 0, dateSelectionTransformers_1 = dateSelectionTransformers; _i < dateSelectionTransformers_1.length; _i++) {
+ var transformer = dateSelectionTransformers_1[_i];
+ var res = transformer(hit0, hit1);
+ if (res === false) {
+ return null;
+ }
+ if (res) {
+ __assign(props, res);
+ }
+ }
+ props.range = { start: ms[0], end: ms[3] };
+ props.allDay = dateSpan0.allDay;
+ return props;
+ }
+
+ var EventDragging = /** @class */ (function (_super) {
+ __extends(EventDragging, _super);
+ function EventDragging(settings) {
+ var _this = _super.call(this, settings) || this;
+ // internal state
+ _this.subjectEl = null;
+ _this.subjectSeg = null; // the seg being selected/dragged
+ _this.isDragging = false;
+ _this.eventRange = null;
+ _this.relevantEvents = null; // the events being dragged
+ _this.receivingContext = null;
+ _this.validMutation = null;
+ _this.mutatedRelevantEvents = null;
+ _this.handlePointerDown = function (ev) {
+ var origTarget = ev.origEvent.target;
+ var _a = _this, component = _a.component, dragging = _a.dragging;
+ var mirror = dragging.mirror;
+ var options = component.context.options;
+ var initialContext = component.context;
+ _this.subjectEl = ev.subjectEl;
+ var subjectSeg = _this.subjectSeg = getElSeg(ev.subjectEl);
+ var eventRange = _this.eventRange = subjectSeg.eventRange;
+ var eventInstanceId = eventRange.instance.instanceId;
+ _this.relevantEvents = getRelevantEvents(initialContext.getCurrentData().eventStore, eventInstanceId);
+ dragging.minDistance = ev.isTouch ? 0 : options.eventDragMinDistance;
+ dragging.delay =
+ // only do a touch delay if touch and this event hasn't been selected yet
+ (ev.isTouch && eventInstanceId !== component.props.eventSelection) ?
+ getComponentTouchDelay(component) :
+ null;
+ if (options.fixedMirrorParent) {
+ mirror.parentNode = options.fixedMirrorParent;
+ }
+ else {
+ mirror.parentNode = elementClosest(origTarget, '.fc');
+ }
+ mirror.revertDuration = options.dragRevertDuration;
+ var isValid = component.isValidSegDownEl(origTarget) &&
+ !elementClosest(origTarget, '.fc-event-resizer'); // NOT on a resizer
+ dragging.setIgnoreMove(!isValid);
+ // disable dragging for elements that are resizable (ie, selectable)
+ // but are not draggable
+ _this.isDragging = isValid &&
+ ev.subjectEl.classList.contains('fc-event-draggable');
+ };
+ _this.handleDragStart = function (ev) {
+ var initialContext = _this.component.context;
+ var eventRange = _this.eventRange;
+ var eventInstanceId = eventRange.instance.instanceId;
+ if (ev.isTouch) {
+ // need to select a different event?
+ if (eventInstanceId !== _this.component.props.eventSelection) {
+ initialContext.dispatch({ type: 'SELECT_EVENT', eventInstanceId: eventInstanceId });
+ }
+ }
+ else {
+ // if now using mouse, but was previous touch interaction, clear selected event
+ initialContext.dispatch({ type: 'UNSELECT_EVENT' });
+ }
+ if (_this.isDragging) {
+ initialContext.calendarApi.unselect(ev); // unselect *date* selection
+ initialContext.emitter.trigger('eventDragStart', {
+ el: _this.subjectEl,
+ event: new EventApi(initialContext, eventRange.def, eventRange.instance),
+ jsEvent: ev.origEvent,
+ view: initialContext.viewApi,
+ });
+ }
+ };
+ _this.handleHitUpdate = function (hit, isFinal) {
+ if (!_this.isDragging) {
+ return;
+ }
+ var relevantEvents = _this.relevantEvents;
+ var initialHit = _this.hitDragging.initialHit;
+ var initialContext = _this.component.context;
+ // states based on new hit
+ var receivingContext = null;
+ var mutation = null;
+ var mutatedRelevantEvents = null;
+ var isInvalid = false;
+ var interaction = {
+ affectedEvents: relevantEvents,
+ mutatedEvents: createEmptyEventStore(),
+ isEvent: true,
+ };
+ if (hit) {
+ receivingContext = hit.context;
+ var receivingOptions = receivingContext.options;
+ if (initialContext === receivingContext ||
+ (receivingOptions.editable && receivingOptions.droppable)) {
+ mutation = computeEventMutation(initialHit, hit, receivingContext.getCurrentData().pluginHooks.eventDragMutationMassagers);
+ if (mutation) {
+ mutatedRelevantEvents = applyMutationToEventStore(relevantEvents, receivingContext.getCurrentData().eventUiBases, mutation, receivingContext);
+ interaction.mutatedEvents = mutatedRelevantEvents;
+ if (!isInteractionValid(interaction, hit.dateProfile, receivingContext)) {
+ isInvalid = true;
+ mutation = null;
+ mutatedRelevantEvents = null;
+ interaction.mutatedEvents = createEmptyEventStore();
+ }
+ }
+ }
+ else {
+ receivingContext = null;
+ }
+ }
+ _this.displayDrag(receivingContext, interaction);
+ if (!isInvalid) {
+ enableCursor();
+ }
+ else {
+ disableCursor();
+ }
+ if (!isFinal) {
+ if (initialContext === receivingContext && // TODO: write test for this
+ isHitsEqual(initialHit, hit)) {
+ mutation = null;
+ }
+ _this.dragging.setMirrorNeedsRevert(!mutation);
+ // render the mirror if no already-rendered mirror
+ // TODO: wish we could somehow wait for dispatch to guarantee render
+ _this.dragging.setMirrorIsVisible(!hit || !getElRoot(_this.subjectEl).querySelector('.fc-event-mirror'));
+ // assign states based on new hit
+ _this.receivingContext = receivingContext;
+ _this.validMutation = mutation;
+ _this.mutatedRelevantEvents = mutatedRelevantEvents;
+ }
+ };
+ _this.handlePointerUp = function () {
+ if (!_this.isDragging) {
+ _this.cleanup(); // because handleDragEnd won't fire
+ }
+ };
+ _this.handleDragEnd = function (ev) {
+ if (_this.isDragging) {
+ var initialContext_1 = _this.component.context;
+ var initialView = initialContext_1.viewApi;
+ var _a = _this, receivingContext_1 = _a.receivingContext, validMutation = _a.validMutation;
+ var eventDef = _this.eventRange.def;
+ var eventInstance = _this.eventRange.instance;
+ var eventApi = new EventApi(initialContext_1, eventDef, eventInstance);
+ var relevantEvents_1 = _this.relevantEvents;
+ var mutatedRelevantEvents_1 = _this.mutatedRelevantEvents;
+ var finalHit = _this.hitDragging.finalHit;
+ _this.clearDrag(); // must happen after revert animation
+ initialContext_1.emitter.trigger('eventDragStop', {
+ el: _this.subjectEl,
+ event: eventApi,
+ jsEvent: ev.origEvent,
+ view: initialView,
+ });
+ if (validMutation) {
+ // dropped within same calendar
+ if (receivingContext_1 === initialContext_1) {
+ var updatedEventApi = new EventApi(initialContext_1, mutatedRelevantEvents_1.defs[eventDef.defId], eventInstance ? mutatedRelevantEvents_1.instances[eventInstance.instanceId] : null);
+ initialContext_1.dispatch({
+ type: 'MERGE_EVENTS',
+ eventStore: mutatedRelevantEvents_1,
+ });
+ var eventChangeArg = {
+ oldEvent: eventApi,
+ event: updatedEventApi,
+ relatedEvents: buildEventApis(mutatedRelevantEvents_1, initialContext_1, eventInstance),
+ revert: function () {
+ initialContext_1.dispatch({
+ type: 'MERGE_EVENTS',
+ eventStore: relevantEvents_1, // the pre-change data
+ });
+ },
+ };
+ var transformed = {};
+ for (var _i = 0, _b = initialContext_1.getCurrentData().pluginHooks.eventDropTransformers; _i < _b.length; _i++) {
+ var transformer = _b[_i];
+ __assign(transformed, transformer(validMutation, initialContext_1));
+ }
+ initialContext_1.emitter.trigger('eventDrop', __assign(__assign(__assign({}, eventChangeArg), transformed), { el: ev.subjectEl, delta: validMutation.datesDelta, jsEvent: ev.origEvent, view: initialView }));
+ initialContext_1.emitter.trigger('eventChange', eventChangeArg);
+ // dropped in different calendar
+ }
+ else if (receivingContext_1) {
+ var eventRemoveArg = {
+ event: eventApi,
+ relatedEvents: buildEventApis(relevantEvents_1, initialContext_1, eventInstance),
+ revert: function () {
+ initialContext_1.dispatch({
+ type: 'MERGE_EVENTS',
+ eventStore: relevantEvents_1,
+ });
+ },
+ };
+ initialContext_1.emitter.trigger('eventLeave', __assign(__assign({}, eventRemoveArg), { draggedEl: ev.subjectEl, view: initialView }));
+ initialContext_1.dispatch({
+ type: 'REMOVE_EVENTS',
+ eventStore: relevantEvents_1,
+ });
+ initialContext_1.emitter.trigger('eventRemove', eventRemoveArg);
+ var addedEventDef = mutatedRelevantEvents_1.defs[eventDef.defId];
+ var addedEventInstance = mutatedRelevantEvents_1.instances[eventInstance.instanceId];
+ var addedEventApi = new EventApi(receivingContext_1, addedEventDef, addedEventInstance);
+ receivingContext_1.dispatch({
+ type: 'MERGE_EVENTS',
+ eventStore: mutatedRelevantEvents_1,
+ });
+ var eventAddArg = {
+ event: addedEventApi,
+ relatedEvents: buildEventApis(mutatedRelevantEvents_1, receivingContext_1, addedEventInstance),
+ revert: function () {
+ receivingContext_1.dispatch({
+ type: 'REMOVE_EVENTS',
+ eventStore: mutatedRelevantEvents_1,
+ });
+ },
+ };
+ receivingContext_1.emitter.trigger('eventAdd', eventAddArg);
+ if (ev.isTouch) {
+ receivingContext_1.dispatch({
+ type: 'SELECT_EVENT',
+ eventInstanceId: eventInstance.instanceId,
+ });
+ }
+ receivingContext_1.emitter.trigger('drop', __assign(__assign({}, buildDatePointApiWithContext(finalHit.dateSpan, receivingContext_1)), { draggedEl: ev.subjectEl, jsEvent: ev.origEvent, view: finalHit.context.viewApi }));
+ receivingContext_1.emitter.trigger('eventReceive', __assign(__assign({}, eventAddArg), { draggedEl: ev.subjectEl, view: finalHit.context.viewApi }));
+ }
+ }
+ else {
+ initialContext_1.emitter.trigger('_noEventDrop');
+ }
+ }
+ _this.cleanup();
+ };
+ var component = _this.component;
+ var options = component.context.options;
+ var dragging = _this.dragging = new FeaturefulElementDragging(settings.el);
+ dragging.pointer.selector = EventDragging.SELECTOR;
+ dragging.touchScrollAllowed = false;
+ dragging.autoScroller.isEnabled = options.dragScroll;
+ var hitDragging = _this.hitDragging = new HitDragging(_this.dragging, interactionSettingsStore);
+ hitDragging.useSubjectCenter = settings.useEventCenter;
+ hitDragging.emitter.on('pointerdown', _this.handlePointerDown);
+ hitDragging.emitter.on('dragstart', _this.handleDragStart);
+ hitDragging.emitter.on('hitupdate', _this.handleHitUpdate);
+ hitDragging.emitter.on('pointerup', _this.handlePointerUp);
+ hitDragging.emitter.on('dragend', _this.handleDragEnd);
+ return _this;
+ }
+ EventDragging.prototype.destroy = function () {
+ this.dragging.destroy();
+ };
+ // render a drag state on the next receivingCalendar
+ EventDragging.prototype.displayDrag = function (nextContext, state) {
+ var initialContext = this.component.context;
+ var prevContext = this.receivingContext;
+ // does the previous calendar need to be cleared?
+ if (prevContext && prevContext !== nextContext) {
+ // does the initial calendar need to be cleared?
+ // if so, don't clear all the way. we still need to to hide the affectedEvents
+ if (prevContext === initialContext) {
+ prevContext.dispatch({
+ type: 'SET_EVENT_DRAG',
+ state: {
+ affectedEvents: state.affectedEvents,
+ mutatedEvents: createEmptyEventStore(),
+ isEvent: true,
+ },
+ });
+ // completely clear the old calendar if it wasn't the initial
+ }
+ else {
+ prevContext.dispatch({ type: 'UNSET_EVENT_DRAG' });
+ }
+ }
+ if (nextContext) {
+ nextContext.dispatch({ type: 'SET_EVENT_DRAG', state: state });
+ }
+ };
+ EventDragging.prototype.clearDrag = function () {
+ var initialCalendar = this.component.context;
+ var receivingContext = this.receivingContext;
+ if (receivingContext) {
+ receivingContext.dispatch({ type: 'UNSET_EVENT_DRAG' });
+ }
+ // the initial calendar might have an dummy drag state from displayDrag
+ if (initialCalendar !== receivingContext) {
+ initialCalendar.dispatch({ type: 'UNSET_EVENT_DRAG' });
+ }
+ };
+ EventDragging.prototype.cleanup = function () {
+ this.subjectSeg = null;
+ this.isDragging = false;
+ this.eventRange = null;
+ this.relevantEvents = null;
+ this.receivingContext = null;
+ this.validMutation = null;
+ this.mutatedRelevantEvents = null;
+ };
+ // TODO: test this in IE11
+ // QUESTION: why do we need it on the resizable???
+ EventDragging.SELECTOR = '.fc-event-draggable, .fc-event-resizable';
+ return EventDragging;
+ }(Interaction));
+ function computeEventMutation(hit0, hit1, massagers) {
+ var dateSpan0 = hit0.dateSpan;
+ var dateSpan1 = hit1.dateSpan;
+ var date0 = dateSpan0.range.start;
+ var date1 = dateSpan1.range.start;
+ var standardProps = {};
+ if (dateSpan0.allDay !== dateSpan1.allDay) {
+ standardProps.allDay = dateSpan1.allDay;
+ standardProps.hasEnd = hit1.context.options.allDayMaintainDuration;
+ if (dateSpan1.allDay) {
+ // means date1 is already start-of-day,
+ // but date0 needs to be converted
+ date0 = startOfDay(date0);
+ }
+ }
+ var delta = diffDates(date0, date1, hit0.context.dateEnv, hit0.componentId === hit1.componentId ?
+ hit0.largeUnit :
+ null);
+ if (delta.milliseconds) { // has hours/minutes/seconds
+ standardProps.allDay = false;
+ }
+ var mutation = {
+ datesDelta: delta,
+ standardProps: standardProps,
+ };
+ for (var _i = 0, massagers_1 = massagers; _i < massagers_1.length; _i++) {
+ var massager = massagers_1[_i];
+ massager(mutation, hit0, hit1);
+ }
+ return mutation;
+ }
+ function getComponentTouchDelay(component) {
+ var options = component.context.options;
+ var delay = options.eventLongPressDelay;
+ if (delay == null) {
+ delay = options.longPressDelay;
+ }
+ return delay;
+ }
+
+ var EventResizing = /** @class */ (function (_super) {
+ __extends(EventResizing, _super);
+ function EventResizing(settings) {
+ var _this = _super.call(this, settings) || this;
+ // internal state
+ _this.draggingSegEl = null;
+ _this.draggingSeg = null; // TODO: rename to resizingSeg? subjectSeg?
+ _this.eventRange = null;
+ _this.relevantEvents = null;
+ _this.validMutation = null;
+ _this.mutatedRelevantEvents = null;
+ _this.handlePointerDown = function (ev) {
+ var component = _this.component;
+ var segEl = _this.querySegEl(ev);
+ var seg = getElSeg(segEl);
+ var eventRange = _this.eventRange = seg.eventRange;
+ _this.dragging.minDistance = component.context.options.eventDragMinDistance;
+ // if touch, need to be working with a selected event
+ _this.dragging.setIgnoreMove(!_this.component.isValidSegDownEl(ev.origEvent.target) ||
+ (ev.isTouch && _this.component.props.eventSelection !== eventRange.instance.instanceId));
+ };
+ _this.handleDragStart = function (ev) {
+ var context = _this.component.context;
+ var eventRange = _this.eventRange;
+ _this.relevantEvents = getRelevantEvents(context.getCurrentData().eventStore, _this.eventRange.instance.instanceId);
+ var segEl = _this.querySegEl(ev);
+ _this.draggingSegEl = segEl;
+ _this.draggingSeg = getElSeg(segEl);
+ context.calendarApi.unselect();
+ context.emitter.trigger('eventResizeStart', {
+ el: segEl,
+ event: new EventApi(context, eventRange.def, eventRange.instance),
+ jsEvent: ev.origEvent,
+ view: context.viewApi,
+ });
+ };
+ _this.handleHitUpdate = function (hit, isFinal, ev) {
+ var context = _this.component.context;
+ var relevantEvents = _this.relevantEvents;
+ var initialHit = _this.hitDragging.initialHit;
+ var eventInstance = _this.eventRange.instance;
+ var mutation = null;
+ var mutatedRelevantEvents = null;
+ var isInvalid = false;
+ var interaction = {
+ affectedEvents: relevantEvents,
+ mutatedEvents: createEmptyEventStore(),
+ isEvent: true,
+ };
+ if (hit) {
+ var disallowed = hit.componentId === initialHit.componentId
+ && _this.isHitComboAllowed
+ && !_this.isHitComboAllowed(initialHit, hit);
+ if (!disallowed) {
+ mutation = computeMutation(initialHit, hit, ev.subjectEl.classList.contains('fc-event-resizer-start'), eventInstance.range);
+ }
+ }
+ if (mutation) {
+ mutatedRelevantEvents = applyMutationToEventStore(relevantEvents, context.getCurrentData().eventUiBases, mutation, context);
+ interaction.mutatedEvents = mutatedRelevantEvents;
+ if (!isInteractionValid(interaction, hit.dateProfile, context)) {
+ isInvalid = true;
+ mutation = null;
+ mutatedRelevantEvents = null;
+ interaction.mutatedEvents = null;
+ }
+ }
+ if (mutatedRelevantEvents) {
+ context.dispatch({
+ type: 'SET_EVENT_RESIZE',
+ state: interaction,
+ });
+ }
+ else {
+ context.dispatch({ type: 'UNSET_EVENT_RESIZE' });
+ }
+ if (!isInvalid) {
+ enableCursor();
+ }
+ else {
+ disableCursor();
+ }
+ if (!isFinal) {
+ if (mutation && isHitsEqual(initialHit, hit)) {
+ mutation = null;
+ }
+ _this.validMutation = mutation;
+ _this.mutatedRelevantEvents = mutatedRelevantEvents;
+ }
+ };
+ _this.handleDragEnd = function (ev) {
+ var context = _this.component.context;
+ var eventDef = _this.eventRange.def;
+ var eventInstance = _this.eventRange.instance;
+ var eventApi = new EventApi(context, eventDef, eventInstance);
+ var relevantEvents = _this.relevantEvents;
+ var mutatedRelevantEvents = _this.mutatedRelevantEvents;
+ context.emitter.trigger('eventResizeStop', {
+ el: _this.draggingSegEl,
+ event: eventApi,
+ jsEvent: ev.origEvent,
+ view: context.viewApi,
+ });
+ if (_this.validMutation) {
+ var updatedEventApi = new EventApi(context, mutatedRelevantEvents.defs[eventDef.defId], eventInstance ? mutatedRelevantEvents.instances[eventInstance.instanceId] : null);
+ context.dispatch({
+ type: 'MERGE_EVENTS',
+ eventStore: mutatedRelevantEvents,
+ });
+ var eventChangeArg = {
+ oldEvent: eventApi,
+ event: updatedEventApi,
+ relatedEvents: buildEventApis(mutatedRelevantEvents, context, eventInstance),
+ revert: function () {
+ context.dispatch({
+ type: 'MERGE_EVENTS',
+ eventStore: relevantEvents, // the pre-change events
+ });
+ },
+ };
+ context.emitter.trigger('eventResize', __assign(__assign({}, eventChangeArg), { el: _this.draggingSegEl, startDelta: _this.validMutation.startDelta || createDuration(0), endDelta: _this.validMutation.endDelta || createDuration(0), jsEvent: ev.origEvent, view: context.viewApi }));
+ context.emitter.trigger('eventChange', eventChangeArg);
+ }
+ else {
+ context.emitter.trigger('_noEventResize');
+ }
+ // reset all internal state
+ _this.draggingSeg = null;
+ _this.relevantEvents = null;
+ _this.validMutation = null;
+ // okay to keep eventInstance around. useful to set it in handlePointerDown
+ };
+ var component = settings.component;
+ var dragging = _this.dragging = new FeaturefulElementDragging(settings.el);
+ dragging.pointer.selector = '.fc-event-resizer';
+ dragging.touchScrollAllowed = false;
+ dragging.autoScroller.isEnabled = component.context.options.dragScroll;
+ var hitDragging = _this.hitDragging = new HitDragging(_this.dragging, interactionSettingsToStore(settings));
+ hitDragging.emitter.on('pointerdown', _this.handlePointerDown);
+ hitDragging.emitter.on('dragstart', _this.handleDragStart);
+ hitDragging.emitter.on('hitupdate', _this.handleHitUpdate);
+ hitDragging.emitter.on('dragend', _this.handleDragEnd);
+ return _this;
+ }
+ EventResizing.prototype.destroy = function () {
+ this.dragging.destroy();
+ };
+ EventResizing.prototype.querySegEl = function (ev) {
+ return elementClosest(ev.subjectEl, '.fc-event');
+ };
+ return EventResizing;
+ }(Interaction));
+ function computeMutation(hit0, hit1, isFromStart, instanceRange) {
+ var dateEnv = hit0.context.dateEnv;
+ var date0 = hit0.dateSpan.range.start;
+ var date1 = hit1.dateSpan.range.start;
+ var delta = diffDates(date0, date1, dateEnv, hit0.largeUnit);
+ if (isFromStart) {
+ if (dateEnv.add(instanceRange.start, delta) < instanceRange.end) {
+ return { startDelta: delta };
+ }
+ }
+ else if (dateEnv.add(instanceRange.end, delta) > instanceRange.start) {
+ return { endDelta: delta };
+ }
+ return null;
+ }
+
+ var UnselectAuto = /** @class */ (function () {
+ function UnselectAuto(context) {
+ var _this = this;
+ this.context = context;
+ this.isRecentPointerDateSelect = false; // wish we could use a selector to detect date selection, but uses hit system
+ this.matchesCancel = false;
+ this.matchesEvent = false;
+ this.onSelect = function (selectInfo) {
+ if (selectInfo.jsEvent) {
+ _this.isRecentPointerDateSelect = true;
+ }
+ };
+ this.onDocumentPointerDown = function (pev) {
+ var unselectCancel = _this.context.options.unselectCancel;
+ var downEl = getEventTargetViaRoot(pev.origEvent);
+ _this.matchesCancel = !!elementClosest(downEl, unselectCancel);
+ _this.matchesEvent = !!elementClosest(downEl, EventDragging.SELECTOR); // interaction started on an event?
+ };
+ this.onDocumentPointerUp = function (pev) {
+ var context = _this.context;
+ var documentPointer = _this.documentPointer;
+ var calendarState = context.getCurrentData();
+ // touch-scrolling should never unfocus any type of selection
+ if (!documentPointer.wasTouchScroll) {
+ if (calendarState.dateSelection && // an existing date selection?
+ !_this.isRecentPointerDateSelect // a new pointer-initiated date selection since last onDocumentPointerUp?
+ ) {
+ var unselectAuto = context.options.unselectAuto;
+ if (unselectAuto && (!unselectAuto || !_this.matchesCancel)) {
+ context.calendarApi.unselect(pev);
+ }
+ }
+ if (calendarState.eventSelection && // an existing event selected?
+ !_this.matchesEvent // interaction DIDN'T start on an event
+ ) {
+ context.dispatch({ type: 'UNSELECT_EVENT' });
+ }
+ }
+ _this.isRecentPointerDateSelect = false;
+ };
+ var documentPointer = this.documentPointer = new PointerDragging(document);
+ documentPointer.shouldIgnoreMove = true;
+ documentPointer.shouldWatchScroll = false;
+ documentPointer.emitter.on('pointerdown', this.onDocumentPointerDown);
+ documentPointer.emitter.on('pointerup', this.onDocumentPointerUp);
+ /*
+ TODO: better way to know about whether there was a selection with the pointer
+ */
+ context.emitter.on('select', this.onSelect);
+ }
+ UnselectAuto.prototype.destroy = function () {
+ this.context.emitter.off('select', this.onSelect);
+ this.documentPointer.destroy();
+ };
+ return UnselectAuto;
+ }());
+
+ var OPTION_REFINERS$3 = {
+ fixedMirrorParent: identity,
+ };
+ var LISTENER_REFINERS = {
+ dateClick: identity,
+ eventDragStart: identity,
+ eventDragStop: identity,
+ eventDrop: identity,
+ eventResizeStart: identity,
+ eventResizeStop: identity,
+ eventResize: identity,
+ drop: identity,
+ eventReceive: identity,
+ eventLeave: identity,
+ };
+
+ /*
+ Given an already instantiated draggable object for one-or-more elements,
+ Interprets any dragging as an attempt to drag an events that lives outside
+ of a calendar onto a calendar.
+ */
+ var ExternalElementDragging = /** @class */ (function () {
+ function ExternalElementDragging(dragging, suppliedDragMeta) {
+ var _this = this;
+ this.receivingContext = null;
+ this.droppableEvent = null; // will exist for all drags, even if create:false
+ this.suppliedDragMeta = null;
+ this.dragMeta = null;
+ this.handleDragStart = function (ev) {
+ _this.dragMeta = _this.buildDragMeta(ev.subjectEl);
+ };
+ this.handleHitUpdate = function (hit, isFinal, ev) {
+ var dragging = _this.hitDragging.dragging;
+ var receivingContext = null;
+ var droppableEvent = null;
+ var isInvalid = false;
+ var interaction = {
+ affectedEvents: createEmptyEventStore(),
+ mutatedEvents: createEmptyEventStore(),
+ isEvent: _this.dragMeta.create,
+ };
+ if (hit) {
+ receivingContext = hit.context;
+ if (_this.canDropElOnCalendar(ev.subjectEl, receivingContext)) {
+ droppableEvent = computeEventForDateSpan(hit.dateSpan, _this.dragMeta, receivingContext);
+ interaction.mutatedEvents = eventTupleToStore(droppableEvent);
+ isInvalid = !isInteractionValid(interaction, hit.dateProfile, receivingContext);
+ if (isInvalid) {
+ interaction.mutatedEvents = createEmptyEventStore();
+ droppableEvent = null;
+ }
+ }
+ }
+ _this.displayDrag(receivingContext, interaction);
+ // show mirror if no already-rendered mirror element OR if we are shutting down the mirror (?)
+ // TODO: wish we could somehow wait for dispatch to guarantee render
+ dragging.setMirrorIsVisible(isFinal || !droppableEvent || !document.querySelector('.fc-event-mirror'));
+ if (!isInvalid) {
+ enableCursor();
+ }
+ else {
+ disableCursor();
+ }
+ if (!isFinal) {
+ dragging.setMirrorNeedsRevert(!droppableEvent);
+ _this.receivingContext = receivingContext;
+ _this.droppableEvent = droppableEvent;
+ }
+ };
+ this.handleDragEnd = function (pev) {
+ var _a = _this, receivingContext = _a.receivingContext, droppableEvent = _a.droppableEvent;
+ _this.clearDrag();
+ if (receivingContext && droppableEvent) {
+ var finalHit = _this.hitDragging.finalHit;
+ var finalView = finalHit.context.viewApi;
+ var dragMeta = _this.dragMeta;
+ receivingContext.emitter.trigger('drop', __assign(__assign({}, buildDatePointApiWithContext(finalHit.dateSpan, receivingContext)), { draggedEl: pev.subjectEl, jsEvent: pev.origEvent, view: finalView }));
+ if (dragMeta.create) {
+ var addingEvents_1 = eventTupleToStore(droppableEvent);
+ receivingContext.dispatch({
+ type: 'MERGE_EVENTS',
+ eventStore: addingEvents_1,
+ });
+ if (pev.isTouch) {
+ receivingContext.dispatch({
+ type: 'SELECT_EVENT',
+ eventInstanceId: droppableEvent.instance.instanceId,
+ });
+ }
+ // signal that an external event landed
+ receivingContext.emitter.trigger('eventReceive', {
+ event: new EventApi(receivingContext, droppableEvent.def, droppableEvent.instance),
+ relatedEvents: [],
+ revert: function () {
+ receivingContext.dispatch({
+ type: 'REMOVE_EVENTS',
+ eventStore: addingEvents_1,
+ });
+ },
+ draggedEl: pev.subjectEl,
+ view: finalView,
+ });
+ }
+ }
+ _this.receivingContext = null;
+ _this.droppableEvent = null;
+ };
+ var hitDragging = this.hitDragging = new HitDragging(dragging, interactionSettingsStore);
+ hitDragging.requireInitial = false; // will start outside of a component
+ hitDragging.emitter.on('dragstart', this.handleDragStart);
+ hitDragging.emitter.on('hitupdate', this.handleHitUpdate);
+ hitDragging.emitter.on('dragend', this.handleDragEnd);
+ this.suppliedDragMeta = suppliedDragMeta;
+ }
+ ExternalElementDragging.prototype.buildDragMeta = function (subjectEl) {
+ if (typeof this.suppliedDragMeta === 'object') {
+ return parseDragMeta(this.suppliedDragMeta);
+ }
+ if (typeof this.suppliedDragMeta === 'function') {
+ return parseDragMeta(this.suppliedDragMeta(subjectEl));
+ }
+ return getDragMetaFromEl(subjectEl);
+ };
+ ExternalElementDragging.prototype.displayDrag = function (nextContext, state) {
+ var prevContext = this.receivingContext;
+ if (prevContext && prevContext !== nextContext) {
+ prevContext.dispatch({ type: 'UNSET_EVENT_DRAG' });
+ }
+ if (nextContext) {
+ nextContext.dispatch({ type: 'SET_EVENT_DRAG', state: state });
+ }
+ };
+ ExternalElementDragging.prototype.clearDrag = function () {
+ if (this.receivingContext) {
+ this.receivingContext.dispatch({ type: 'UNSET_EVENT_DRAG' });
+ }
+ };
+ ExternalElementDragging.prototype.canDropElOnCalendar = function (el, receivingContext) {
+ var dropAccept = receivingContext.options.dropAccept;
+ if (typeof dropAccept === 'function') {
+ return dropAccept.call(receivingContext.calendarApi, el);
+ }
+ if (typeof dropAccept === 'string' && dropAccept) {
+ return Boolean(elementMatches(el, dropAccept));
+ }
+ return true;
+ };
+ return ExternalElementDragging;
+ }());
+ // Utils for computing event store from the DragMeta
+ // ----------------------------------------------------------------------------------------------------
+ function computeEventForDateSpan(dateSpan, dragMeta, context) {
+ var defProps = __assign({}, dragMeta.leftoverProps);
+ for (var _i = 0, _a = context.pluginHooks.externalDefTransforms; _i < _a.length; _i++) {
+ var transform = _a[_i];
+ __assign(defProps, transform(dateSpan, dragMeta));
+ }
+ var _b = refineEventDef(defProps, context), refined = _b.refined, extra = _b.extra;
+ var def = parseEventDef(refined, extra, dragMeta.sourceId, dateSpan.allDay, context.options.forceEventDuration || Boolean(dragMeta.duration), // hasEnd
+ context);
+ var start = dateSpan.range.start;
+ // only rely on time info if drop zone is all-day,
+ // otherwise, we already know the time
+ if (dateSpan.allDay && dragMeta.startTime) {
+ start = context.dateEnv.add(start, dragMeta.startTime);
+ }
+ var end = dragMeta.duration ?
+ context.dateEnv.add(start, dragMeta.duration) :
+ getDefaultEventEnd(dateSpan.allDay, start, context);
+ var instance = createEventInstance(def.defId, { start: start, end: end });
+ return { def: def, instance: instance };
+ }
+ // Utils for extracting data from element
+ // ----------------------------------------------------------------------------------------------------
+ function getDragMetaFromEl(el) {
+ var str = getEmbeddedElData(el, 'event');
+ var obj = str ?
+ JSON.parse(str) :
+ { create: false }; // if no embedded data, assume no event creation
+ return parseDragMeta(obj);
+ }
+ config.dataAttrPrefix = '';
+ function getEmbeddedElData(el, name) {
+ var prefix = config.dataAttrPrefix;
+ var prefixedName = (prefix ? prefix + '-' : '') + name;
+ return el.getAttribute('data-' + prefixedName) || '';
+ }
+
+ /*
+ Makes an element (that is *external* to any calendar) draggable.
+ Can pass in data that determines how an event will be created when dropped onto a calendar.
+ Leverages FullCalendar's internal drag-n-drop functionality WITHOUT a third-party drag system.
+ */
+ var ExternalDraggable = /** @class */ (function () {
+ function ExternalDraggable(el, settings) {
+ var _this = this;
+ if (settings === void 0) { settings = {}; }
+ this.handlePointerDown = function (ev) {
+ var dragging = _this.dragging;
+ var _a = _this.settings, minDistance = _a.minDistance, longPressDelay = _a.longPressDelay;
+ dragging.minDistance =
+ minDistance != null ?
+ minDistance :
+ (ev.isTouch ? 0 : BASE_OPTION_DEFAULTS.eventDragMinDistance);
+ dragging.delay =
+ ev.isTouch ? // TODO: eventually read eventLongPressDelay instead vvv
+ (longPressDelay != null ? longPressDelay : BASE_OPTION_DEFAULTS.longPressDelay) :
+ 0;
+ };
+ this.handleDragStart = function (ev) {
+ if (ev.isTouch &&
+ _this.dragging.delay &&
+ ev.subjectEl.classList.contains('fc-event')) {
+ _this.dragging.mirror.getMirrorEl().classList.add('fc-event-selected');
+ }
+ };
+ this.settings = settings;
+ var dragging = this.dragging = new FeaturefulElementDragging(el);
+ dragging.touchScrollAllowed = false;
+ if (settings.itemSelector != null) {
+ dragging.pointer.selector = settings.itemSelector;
+ }
+ if (settings.appendTo != null) {
+ dragging.mirror.parentNode = settings.appendTo; // TODO: write tests
+ }
+ dragging.emitter.on('pointerdown', this.handlePointerDown);
+ dragging.emitter.on('dragstart', this.handleDragStart);
+ new ExternalElementDragging(dragging, settings.eventData); // eslint-disable-line no-new
+ }
+ ExternalDraggable.prototype.destroy = function () {
+ this.dragging.destroy();
+ };
+ return ExternalDraggable;
+ }());
+
+ /*
+ Detects when a *THIRD-PARTY* drag-n-drop system interacts with elements.
+ The third-party system is responsible for drawing the visuals effects of the drag.
+ This class simply monitors for pointer movements and fires events.
+ It also has the ability to hide the moving element (the "mirror") during the drag.
+ */
+ var InferredElementDragging = /** @class */ (function (_super) {
+ __extends(InferredElementDragging, _super);
+ function InferredElementDragging(containerEl) {
+ var _this = _super.call(this, containerEl) || this;
+ _this.shouldIgnoreMove = false;
+ _this.mirrorSelector = '';
+ _this.currentMirrorEl = null;
+ _this.handlePointerDown = function (ev) {
+ _this.emitter.trigger('pointerdown', ev);
+ if (!_this.shouldIgnoreMove) {
+ // fire dragstart right away. does not support delay or min-distance
+ _this.emitter.trigger('dragstart', ev);
+ }
+ };
+ _this.handlePointerMove = function (ev) {
+ if (!_this.shouldIgnoreMove) {
+ _this.emitter.trigger('dragmove', ev);
+ }
+ };
+ _this.handlePointerUp = function (ev) {
+ _this.emitter.trigger('pointerup', ev);
+ if (!_this.shouldIgnoreMove) {
+ // fire dragend right away. does not support a revert animation
+ _this.emitter.trigger('dragend', ev);
+ }
+ };
+ var pointer = _this.pointer = new PointerDragging(containerEl);
+ pointer.emitter.on('pointerdown', _this.handlePointerDown);
+ pointer.emitter.on('pointermove', _this.handlePointerMove);
+ pointer.emitter.on('pointerup', _this.handlePointerUp);
+ return _this;
+ }
+ InferredElementDragging.prototype.destroy = function () {
+ this.pointer.destroy();
+ };
+ InferredElementDragging.prototype.setIgnoreMove = function (bool) {
+ this.shouldIgnoreMove = bool;
+ };
+ InferredElementDragging.prototype.setMirrorIsVisible = function (bool) {
+ if (bool) {
+ // restore a previously hidden element.
+ // use the reference in case the selector class has already been removed.
+ if (this.currentMirrorEl) {
+ this.currentMirrorEl.style.visibility = '';
+ this.currentMirrorEl = null;
+ }
+ }
+ else {
+ var mirrorEl = this.mirrorSelector
+ // TODO: somehow query FullCalendars WITHIN shadow-roots
+ ? document.querySelector(this.mirrorSelector)
+ : null;
+ if (mirrorEl) {
+ this.currentMirrorEl = mirrorEl;
+ mirrorEl.style.visibility = 'hidden';
+ }
+ }
+ };
+ return InferredElementDragging;
+ }(ElementDragging));
+
+ /*
+ Bridges third-party drag-n-drop systems with FullCalendar.
+ Must be instantiated and destroyed by caller.
+ */
+ var ThirdPartyDraggable = /** @class */ (function () {
+ function ThirdPartyDraggable(containerOrSettings, settings) {
+ var containerEl = document;
+ if (
+ // wish we could just test instanceof EventTarget, but doesn't work in IE11
+ containerOrSettings === document ||
+ containerOrSettings instanceof Element) {
+ containerEl = containerOrSettings;
+ settings = settings || {};
+ }
+ else {
+ settings = (containerOrSettings || {});
+ }
+ var dragging = this.dragging = new InferredElementDragging(containerEl);
+ if (typeof settings.itemSelector === 'string') {
+ dragging.pointer.selector = settings.itemSelector;
+ }
+ else if (containerEl === document) {
+ dragging.pointer.selector = '[data-event]';
+ }
+ if (typeof settings.mirrorSelector === 'string') {
+ dragging.mirrorSelector = settings.mirrorSelector;
+ }
+ new ExternalElementDragging(dragging, settings.eventData); // eslint-disable-line no-new
+ }
+ ThirdPartyDraggable.prototype.destroy = function () {
+ this.dragging.destroy();
+ };
+ return ThirdPartyDraggable;
+ }());
+
+ var interactionPlugin = createPlugin({
+ componentInteractions: [DateClicking, DateSelecting, EventDragging, EventResizing],
+ calendarInteractions: [UnselectAuto],
+ elementDraggingImpl: FeaturefulElementDragging,
+ optionRefiners: OPTION_REFINERS$3,
+ listenerRefiners: LISTENER_REFINERS,
+ });
+
+ /* An abstract class for the daygrid views, as well as month view. Renders one or more rows of day cells.
+ ----------------------------------------------------------------------------------------------------------------------*/
+ // It is a manager for a Table subcomponent, which does most of the heavy lifting.
+ // It is responsible for managing width/height.
+ var TableView = /** @class */ (function (_super) {
+ __extends(TableView, _super);
+ function TableView() {
+ var _this = _super !== null && _super.apply(this, arguments) || this;
+ _this.headerElRef = createRef();
+ return _this;
+ }
+ TableView.prototype.renderSimpleLayout = function (headerRowContent, bodyContent) {
+ var _a = this, props = _a.props, context = _a.context;
+ var sections = [];
+ var stickyHeaderDates = getStickyHeaderDates(context.options);
+ if (headerRowContent) {
+ sections.push({
+ type: 'header',
+ key: 'header',
+ isSticky: stickyHeaderDates,
+ chunk: {
+ elRef: this.headerElRef,
+ tableClassName: 'fc-col-header',
+ rowContent: headerRowContent,
+ },
+ });
+ }
+ sections.push({
+ type: 'body',
+ key: 'body',
+ liquid: true,
+ chunk: { content: bodyContent },
+ });
+ return (createElement(ViewRoot, { viewSpec: context.viewSpec }, function (rootElRef, classNames) { return (createElement("div", { ref: rootElRef, className: ['fc-daygrid'].concat(classNames).join(' ') },
+ createElement(SimpleScrollGrid, { liquid: !props.isHeightAuto && !props.forPrint, collapsibleWidth: props.forPrint, cols: [] /* TODO: make optional? */, sections: sections }))); }));
+ };
+ TableView.prototype.renderHScrollLayout = function (headerRowContent, bodyContent, colCnt, dayMinWidth) {
+ var ScrollGrid = this.context.pluginHooks.scrollGridImpl;
+ if (!ScrollGrid) {
+ throw new Error('No ScrollGrid implementation');
+ }
+ var _a = this, props = _a.props, context = _a.context;
+ var stickyHeaderDates = !props.forPrint && getStickyHeaderDates(context.options);
+ var stickyFooterScrollbar = !props.forPrint && getStickyFooterScrollbar(context.options);
+ var sections = [];
+ if (headerRowContent) {
+ sections.push({
+ type: 'header',
+ key: 'header',
+ isSticky: stickyHeaderDates,
+ chunks: [{
+ key: 'main',
+ elRef: this.headerElRef,
+ tableClassName: 'fc-col-header',
+ rowContent: headerRowContent,
+ }],
+ });
+ }
+ sections.push({
+ type: 'body',
+ key: 'body',
+ liquid: true,
+ chunks: [{
+ key: 'main',
+ content: bodyContent,
+ }],
+ });
+ if (stickyFooterScrollbar) {
+ sections.push({
+ type: 'footer',
+ key: 'footer',
+ isSticky: true,
+ chunks: [{
+ key: 'main',
+ content: renderScrollShim,
+ }],
+ });
+ }
+ return (createElement(ViewRoot, { viewSpec: context.viewSpec }, function (rootElRef, classNames) { return (createElement("div", { ref: rootElRef, className: ['fc-daygrid'].concat(classNames).join(' ') },
+ createElement(ScrollGrid, { liquid: !props.isHeightAuto && !props.forPrint, collapsibleWidth: props.forPrint, colGroups: [{ cols: [{ span: colCnt, minWidth: dayMinWidth }] }], sections: sections }))); }));
+ };
+ return TableView;
+ }(DateComponent));
+
+ function splitSegsByRow(segs, rowCnt) {
+ var byRow = [];
+ for (var i = 0; i < rowCnt; i += 1) {
+ byRow[i] = [];
+ }
+ for (var _i = 0, segs_1 = segs; _i < segs_1.length; _i++) {
+ var seg = segs_1[_i];
+ byRow[seg.row].push(seg);
+ }
+ return byRow;
+ }
+ function splitSegsByFirstCol(segs, colCnt) {
+ var byCol = [];
+ for (var i = 0; i < colCnt; i += 1) {
+ byCol[i] = [];
+ }
+ for (var _i = 0, segs_2 = segs; _i < segs_2.length; _i++) {
+ var seg = segs_2[_i];
+ byCol[seg.firstCol].push(seg);
+ }
+ return byCol;
+ }
+ function splitInteractionByRow(ui, rowCnt) {
+ var byRow = [];
+ if (!ui) {
+ for (var i = 0; i < rowCnt; i += 1) {
+ byRow[i] = null;
+ }
+ }
+ else {
+ for (var i = 0; i < rowCnt; i += 1) {
+ byRow[i] = {
+ affectedInstances: ui.affectedInstances,
+ isEvent: ui.isEvent,
+ segs: [],
+ };
+ }
+ for (var _i = 0, _a = ui.segs; _i < _a.length; _i++) {
+ var seg = _a[_i];
+ byRow[seg.row].segs.push(seg);
+ }
+ }
+ return byRow;
+ }
+
+ var TableCellTop = /** @class */ (function (_super) {
+ __extends(TableCellTop, _super);
+ function TableCellTop() {
+ return _super !== null && _super.apply(this, arguments) || this;
+ }
+ TableCellTop.prototype.render = function () {
+ var props = this.props;
+ var navLinkAttrs = this.context.options.navLinks
+ ? { 'data-navlink': buildNavLinkData(props.date), tabIndex: 0 }
+ : {};
+ return (createElement(DayCellContent, { date: props.date, dateProfile: props.dateProfile, todayRange: props.todayRange, showDayNumber: props.showDayNumber, extraHookProps: props.extraHookProps, defaultContent: renderTopInner }, function (innerElRef, innerContent) { return ((innerContent || props.forceDayTop) && (createElement("div", { className: "fc-daygrid-day-top", ref: innerElRef },
+ createElement("a", __assign({ className: "fc-daygrid-day-number" }, navLinkAttrs), innerContent || createElement(Fragment, null, "\u00A0"))))); }));
+ };
+ return TableCellTop;
+ }(BaseComponent));
+ function renderTopInner(props) {
+ return props.dayNumberText;
+ }
+
+ var DEFAULT_TABLE_EVENT_TIME_FORMAT = createFormatter({
+ hour: 'numeric',
+ minute: '2-digit',
+ omitZeroMinute: true,
+ meridiem: 'narrow',
+ });
+ function hasListItemDisplay(seg) {
+ var display = seg.eventRange.ui.display;
+ return display === 'list-item' || (display === 'auto' &&
+ !seg.eventRange.def.allDay &&
+ seg.firstCol === seg.lastCol && // can't be multi-day
+ seg.isStart && // "
+ seg.isEnd // "
+ );
+ }
+
+ var TableBlockEvent = /** @class */ (function (_super) {
+ __extends(TableBlockEvent, _super);
+ function TableBlockEvent() {
+ return _super !== null && _super.apply(this, arguments) || this;
+ }
+ TableBlockEvent.prototype.render = function () {
+ var props = this.props;
+ return (createElement(StandardEvent, __assign({}, props, { extraClassNames: ['fc-daygrid-event', 'fc-daygrid-block-event', 'fc-h-event'], defaultTimeFormat: DEFAULT_TABLE_EVENT_TIME_FORMAT, defaultDisplayEventEnd: props.defaultDisplayEventEnd, disableResizing: !props.seg.eventRange.def.allDay })));
+ };
+ return TableBlockEvent;
+ }(BaseComponent));
+
+ var TableListItemEvent = /** @class */ (function (_super) {
+ __extends(TableListItemEvent, _super);
+ function TableListItemEvent() {
+ return _super !== null && _super.apply(this, arguments) || this;
+ }
+ TableListItemEvent.prototype.render = function () {
+ var _a = this, props = _a.props, context = _a.context;
+ var timeFormat = context.options.eventTimeFormat || DEFAULT_TABLE_EVENT_TIME_FORMAT;
+ var timeText = buildSegTimeText(props.seg, timeFormat, context, true, props.defaultDisplayEventEnd);
+ return (createElement(EventRoot, { seg: props.seg, timeText: timeText, defaultContent: renderInnerContent$2, isDragging: props.isDragging, isResizing: false, isDateSelecting: false, isSelected: props.isSelected, isPast: props.isPast, isFuture: props.isFuture, isToday: props.isToday }, function (rootElRef, classNames, innerElRef, innerContent) { return ( // we don't use styles!
+ createElement("a", __assign({ className: ['fc-daygrid-event', 'fc-daygrid-dot-event'].concat(classNames).join(' '), ref: rootElRef }, getSegAnchorAttrs(props.seg)), innerContent)); }));
+ };
+ return TableListItemEvent;
+ }(BaseComponent));
+ function renderInnerContent$2(innerProps) {
+ return (createElement(Fragment, null,
+ createElement("div", { className: "fc-daygrid-event-dot", style: { borderColor: innerProps.borderColor || innerProps.backgroundColor } }),
+ innerProps.timeText && (createElement("div", { className: "fc-event-time" }, innerProps.timeText)),
+ createElement("div", { className: "fc-event-title" }, innerProps.event.title || createElement(Fragment, null, "\u00A0"))));
+ }
+ function getSegAnchorAttrs(seg) {
+ var url = seg.eventRange.def.url;
+ return url ? { href: url } : {};
+ }
+
+ var TableCellMoreLink = /** @class */ (function (_super) {
+ __extends(TableCellMoreLink, _super);
+ function TableCellMoreLink() {
+ var _this = _super !== null && _super.apply(this, arguments) || this;
+ _this.compileSegs = memoize(compileSegs);
+ return _this;
+ }
+ TableCellMoreLink.prototype.render = function () {
+ var props = this.props;
+ var _a = this.compileSegs(props.singlePlacements), allSegs = _a.allSegs, invisibleSegs = _a.invisibleSegs;
+ return (createElement(MoreLinkRoot, { dateProfile: props.dateProfile, todayRange: props.todayRange, allDayDate: props.allDayDate, moreCnt: props.moreCnt, allSegs: allSegs, hiddenSegs: invisibleSegs, alignmentElRef: props.alignmentElRef, alignGridTop: props.alignGridTop, extraDateSpan: props.extraDateSpan, popoverContent: function () {
+ var isForcedInvisible = (props.eventDrag ? props.eventDrag.affectedInstances : null) ||
+ (props.eventResize ? props.eventResize.affectedInstances : null) ||
+ {};
+ return (createElement(Fragment, null, allSegs.map(function (seg) {
+ var instanceId = seg.eventRange.instance.instanceId;
+ return (createElement("div", { className: "fc-daygrid-event-harness", key: instanceId, style: {
+ visibility: isForcedInvisible[instanceId] ? 'hidden' : '',
+ } }, hasListItemDisplay(seg) ? (createElement(TableListItemEvent, __assign({ seg: seg, isDragging: false, isSelected: instanceId === props.eventSelection, defaultDisplayEventEnd: false }, getSegMeta(seg, props.todayRange)))) : (createElement(TableBlockEvent, __assign({ seg: seg, isDragging: false, isResizing: false, isDateSelecting: false, isSelected: instanceId === props.eventSelection, defaultDisplayEventEnd: false }, getSegMeta(seg, props.todayRange))))));
+ })));
+ } }, function (rootElRef, classNames, innerElRef, innerContent, handleClick) { return (createElement("a", { ref: rootElRef, className: ['fc-daygrid-more-link'].concat(classNames).join(' '), onClick: handleClick }, innerContent)); }));
+ };
+ return TableCellMoreLink;
+ }(BaseComponent));
+ function compileSegs(singlePlacements) {
+ var allSegs = [];
+ var invisibleSegs = [];
+ for (var _i = 0, singlePlacements_1 = singlePlacements; _i < singlePlacements_1.length; _i++) {
+ var placement = singlePlacements_1[_i];
+ allSegs.push(placement.seg);
+ if (!placement.isVisible) {
+ invisibleSegs.push(placement.seg);
+ }
+ }
+ return { allSegs: allSegs, invisibleSegs: invisibleSegs };
+ }
+
+ var DEFAULT_WEEK_NUM_FORMAT$1 = createFormatter({ week: 'narrow' });
+ var TableCell = /** @class */ (function (_super) {
+ __extends(TableCell, _super);
+ function TableCell() {
+ var _this = _super !== null && _super.apply(this, arguments) || this;
+ _this.rootElRef = createRef();
+ _this.handleRootEl = function (el) {
+ setRef(_this.rootElRef, el);
+ setRef(_this.props.elRef, el);
+ };
+ return _this;
+ }
+ TableCell.prototype.render = function () {
+ var _a = this, props = _a.props, context = _a.context, rootElRef = _a.rootElRef;
+ var options = context.options;
+ var date = props.date, dateProfile = props.dateProfile;
+ var navLinkAttrs = options.navLinks
+ ? { 'data-navlink': buildNavLinkData(date, 'week'), tabIndex: 0 }
+ : {};
+ return (createElement(DayCellRoot, { date: date, dateProfile: dateProfile, todayRange: props.todayRange, showDayNumber: props.showDayNumber, extraHookProps: props.extraHookProps, elRef: this.handleRootEl }, function (dayElRef, dayClassNames, rootDataAttrs, isDisabled) { return (createElement("td", __assign({ ref: dayElRef, className: ['fc-daygrid-day'].concat(dayClassNames, props.extraClassNames || []).join(' ') }, rootDataAttrs, props.extraDataAttrs),
+ createElement("div", { className: "fc-daygrid-day-frame fc-scrollgrid-sync-inner", ref: props.innerElRef /* different from hook system! RENAME */ },
+ props.showWeekNumber && (createElement(WeekNumberRoot, { date: date, defaultFormat: DEFAULT_WEEK_NUM_FORMAT$1 }, function (weekElRef, weekClassNames, innerElRef, innerContent) { return (createElement("a", __assign({ ref: weekElRef, className: ['fc-daygrid-week-number'].concat(weekClassNames).join(' ') }, navLinkAttrs), innerContent)); })),
+ !isDisabled && (createElement(TableCellTop, { date: date, dateProfile: dateProfile, showDayNumber: props.showDayNumber, forceDayTop: props.forceDayTop, todayRange: props.todayRange, extraHookProps: props.extraHookProps })),
+ createElement("div", { className: "fc-daygrid-day-events", ref: props.fgContentElRef },
+ props.fgContent,
+ createElement("div", { className: "fc-daygrid-day-bottom", style: { marginTop: props.moreMarginTop } },
+ createElement(TableCellMoreLink, { allDayDate: date, singlePlacements: props.singlePlacements, moreCnt: props.moreCnt, alignmentElRef: rootElRef, alignGridTop: !props.showDayNumber, extraDateSpan: props.extraDateSpan, dateProfile: props.dateProfile, eventSelection: props.eventSelection, eventDrag: props.eventDrag, eventResize: props.eventResize, todayRange: props.todayRange }))),
+ createElement("div", { className: "fc-daygrid-day-bg" }, props.bgContent)))); }));
+ };
+ return TableCell;
+ }(DateComponent));
+
+ function computeFgSegPlacement(segs, // assumed already sorted
+ dayMaxEvents, dayMaxEventRows, strictOrder, eventInstanceHeights, maxContentHeight, cells) {
+ var hierarchy = new DayGridSegHierarchy();
+ hierarchy.allowReslicing = true;
+ hierarchy.strictOrder = strictOrder;
+ if (dayMaxEvents === true || dayMaxEventRows === true) {
+ hierarchy.maxCoord = maxContentHeight;
+ hierarchy.hiddenConsumes = true;
+ }
+ else if (typeof dayMaxEvents === 'number') {
+ hierarchy.maxStackCnt = dayMaxEvents;
+ }
+ else if (typeof dayMaxEventRows === 'number') {
+ hierarchy.maxStackCnt = dayMaxEventRows;
+ hierarchy.hiddenConsumes = true;
+ }
+ // create segInputs only for segs with known heights
+ var segInputs = [];
+ var unknownHeightSegs = [];
+ for (var i = 0; i < segs.length; i += 1) {
+ var seg = segs[i];
+ var instanceId = seg.eventRange.instance.instanceId;
+ var eventHeight = eventInstanceHeights[instanceId];
+ if (eventHeight != null) {
+ segInputs.push({
+ index: i,
+ thickness: eventHeight,
+ span: {
+ start: seg.firstCol,
+ end: seg.lastCol + 1,
+ },
+ });
+ }
+ else {
+ unknownHeightSegs.push(seg);
+ }
+ }
+ var hiddenEntries = hierarchy.addSegs(segInputs);
+ var segRects = hierarchy.toRects();
+ var _a = placeRects(segRects, segs, cells), singleColPlacements = _a.singleColPlacements, multiColPlacements = _a.multiColPlacements, leftoverMargins = _a.leftoverMargins;
+ var moreCnts = [];
+ var moreMarginTops = [];
+ // add segs with unknown heights
+ for (var _i = 0, unknownHeightSegs_1 = unknownHeightSegs; _i < unknownHeightSegs_1.length; _i++) {
+ var seg = unknownHeightSegs_1[_i];
+ multiColPlacements[seg.firstCol].push({
+ seg: seg,
+ isVisible: false,
+ isAbsolute: true,
+ absoluteTop: 0,
+ marginTop: 0,
+ });
+ for (var col = seg.firstCol; col <= seg.lastCol; col += 1) {
+ singleColPlacements[col].push({
+ seg: resliceSeg(seg, col, col + 1, cells),
+ isVisible: false,
+ isAbsolute: false,
+ absoluteTop: 0,
+ marginTop: 0,
+ });
+ }
+ }
+ // add the hidden entries
+ for (var col = 0; col < cells.length; col += 1) {
+ moreCnts.push(0);
+ }
+ for (var _b = 0, hiddenEntries_1 = hiddenEntries; _b < hiddenEntries_1.length; _b++) {
+ var hiddenEntry = hiddenEntries_1[_b];
+ var seg = segs[hiddenEntry.index];
+ var hiddenSpan = hiddenEntry.span;
+ multiColPlacements[hiddenSpan.start].push({
+ seg: resliceSeg(seg, hiddenSpan.start, hiddenSpan.end, cells),
+ isVisible: false,
+ isAbsolute: true,
+ absoluteTop: 0,
+ marginTop: 0,
+ });
+ for (var col = hiddenSpan.start; col < hiddenSpan.end; col += 1) {
+ moreCnts[col] += 1;
+ singleColPlacements[col].push({
+ seg: resliceSeg(seg, col, col + 1, cells),
+ isVisible: false,
+ isAbsolute: false,
+ absoluteTop: 0,
+ marginTop: 0,
+ });
+ }
+ }
+ // deal with leftover margins
+ for (var col = 0; col < cells.length; col += 1) {
+ moreMarginTops.push(leftoverMargins[col]);
+ }
+ return { singleColPlacements: singleColPlacements, multiColPlacements: multiColPlacements, moreCnts: moreCnts, moreMarginTops: moreMarginTops };
+ }
+ // rects ordered by top coord, then left
+ function placeRects(allRects, segs, cells) {
+ var rectsByEachCol = groupRectsByEachCol(allRects, cells.length);
+ var singleColPlacements = [];
+ var multiColPlacements = [];
+ var leftoverMargins = [];
+ for (var col = 0; col < cells.length; col += 1) {
+ var rects = rectsByEachCol[col];
+ // compute all static segs in singlePlacements
+ var singlePlacements = [];
+ var currentHeight = 0;
+ var currentMarginTop = 0;
+ for (var _i = 0, rects_1 = rects; _i < rects_1.length; _i++) {
+ var rect = rects_1[_i];
+ var seg = segs[rect.index];
+ singlePlacements.push({
+ seg: resliceSeg(seg, col, col + 1, cells),
+ isVisible: true,
+ isAbsolute: false,
+ absoluteTop: rect.levelCoord,
+ marginTop: rect.levelCoord - currentHeight,
+ });
+ currentHeight = rect.levelCoord + rect.thickness;
+ }
+ // compute mixed static/absolute segs in multiPlacements
+ var multiPlacements = [];
+ currentHeight = 0;
+ currentMarginTop = 0;
+ for (var _a = 0, rects_2 = rects; _a < rects_2.length; _a++) {
+ var rect = rects_2[_a];
+ var seg = segs[rect.index];
+ var isAbsolute = rect.span.end - rect.span.start > 1; // multi-column?
+ var isFirstCol = rect.span.start === col;
+ currentMarginTop += rect.levelCoord - currentHeight; // amount of space since bottom of previous seg
+ currentHeight = rect.levelCoord + rect.thickness; // height will now be bottom of current seg
+ if (isAbsolute) {
+ currentMarginTop += rect.thickness;
+ if (isFirstCol) {
+ multiPlacements.push({
+ seg: resliceSeg(seg, rect.span.start, rect.span.end, cells),
+ isVisible: true,
+ isAbsolute: true,
+ absoluteTop: rect.levelCoord,
+ marginTop: 0,
+ });
+ }
+ }
+ else if (isFirstCol) {
+ multiPlacements.push({
+ seg: resliceSeg(seg, rect.span.start, rect.span.end, cells),
+ isVisible: true,
+ isAbsolute: false,
+ absoluteTop: rect.levelCoord,
+ marginTop: currentMarginTop, // claim the margin
+ });
+ currentMarginTop = 0;
+ }
+ }
+ singleColPlacements.push(singlePlacements);
+ multiColPlacements.push(multiPlacements);
+ leftoverMargins.push(currentMarginTop);
+ }
+ return { singleColPlacements: singleColPlacements, multiColPlacements: multiColPlacements, leftoverMargins: leftoverMargins };
+ }
+ function groupRectsByEachCol(rects, colCnt) {
+ var rectsByEachCol = [];
+ for (var col = 0; col < colCnt; col += 1) {
+ rectsByEachCol.push([]);
+ }
+ for (var _i = 0, rects_3 = rects; _i < rects_3.length; _i++) {
+ var rect = rects_3[_i];
+ for (var col = rect.span.start; col < rect.span.end; col += 1) {
+ rectsByEachCol[col].push(rect);
+ }
+ }
+ return rectsByEachCol;
+ }
+ function resliceSeg(seg, spanStart, spanEnd, cells) {
+ if (seg.firstCol === spanStart && seg.lastCol === spanEnd - 1) {
+ return seg;
+ }
+ var eventRange = seg.eventRange;
+ var origRange = eventRange.range;
+ var slicedRange = intersectRanges(origRange, {
+ start: cells[spanStart].date,
+ end: addDays(cells[spanEnd - 1].date, 1),
+ });
+ return __assign(__assign({}, seg), { firstCol: spanStart, lastCol: spanEnd - 1, eventRange: {
+ def: eventRange.def,
+ ui: __assign(__assign({}, eventRange.ui), { durationEditable: false }),
+ instance: eventRange.instance,
+ range: slicedRange,
+ }, isStart: seg.isStart && slicedRange.start.valueOf() === origRange.start.valueOf(), isEnd: seg.isEnd && slicedRange.end.valueOf() === origRange.end.valueOf() });
+ }
+ var DayGridSegHierarchy = /** @class */ (function (_super) {
+ __extends(DayGridSegHierarchy, _super);
+ function DayGridSegHierarchy() {
+ var _this = _super !== null && _super.apply(this, arguments) || this;
+ // config
+ _this.hiddenConsumes = false;
+ // allows us to keep hidden entries in the hierarchy so they take up space
+ _this.forceHidden = {};
+ return _this;
+ }
+ DayGridSegHierarchy.prototype.addSegs = function (segInputs) {
+ var _this = this;
+ var hiddenSegs = _super.prototype.addSegs.call(this, segInputs);
+ var entriesByLevel = this.entriesByLevel;
+ var excludeHidden = function (entry) { return !_this.forceHidden[buildEntryKey(entry)]; };
+ // remove the forced-hidden segs
+ for (var level = 0; level < entriesByLevel.length; level += 1) {
+ entriesByLevel[level] = entriesByLevel[level].filter(excludeHidden);
+ }
+ return hiddenSegs;
+ };
+ DayGridSegHierarchy.prototype.handleInvalidInsertion = function (insertion, entry, hiddenEntries) {
+ var _a = this, entriesByLevel = _a.entriesByLevel, forceHidden = _a.forceHidden;
+ var touchingEntry = insertion.touchingEntry, touchingLevel = insertion.touchingLevel, touchingLateral = insertion.touchingLateral;
+ if (this.hiddenConsumes && touchingEntry) {
+ var touchingEntryId = buildEntryKey(touchingEntry);
+ // if not already hidden
+ if (!forceHidden[touchingEntryId]) {
+ if (this.allowReslicing) {
+ var placeholderEntry = __assign(__assign({}, touchingEntry), { span: intersectSpans(touchingEntry.span, entry.span) });
+ var placeholderEntryId = buildEntryKey(placeholderEntry);
+ forceHidden[placeholderEntryId] = true;
+ entriesByLevel[touchingLevel][touchingLateral] = placeholderEntry; // replace touchingEntry with our placeholder
+ this.splitEntry(touchingEntry, entry, hiddenEntries); // split up the touchingEntry, reinsert it
+ }
+ else {
+ forceHidden[touchingEntryId] = true;
+ hiddenEntries.push(touchingEntry);
+ }
+ }
+ }
+ return _super.prototype.handleInvalidInsertion.call(this, insertion, entry, hiddenEntries);
+ };
+ return DayGridSegHierarchy;
+ }(SegHierarchy));
+
+ var TableRow = /** @class */ (function (_super) {
+ __extends(TableRow, _super);
+ function TableRow() {
+ var _this = _super !== null && _super.apply(this, arguments) || this;
+ _this.cellElRefs = new RefMap(); // the
+ _this.frameElRefs = new RefMap(); // the fc-daygrid-day-frame
+ _this.fgElRefs = new RefMap(); // the fc-daygrid-day-events
+ _this.segHarnessRefs = new RefMap(); // indexed by "instanceId:firstCol"
+ _this.rootElRef = createRef();
+ _this.state = {
+ framePositions: null,
+ maxContentHeight: null,
+ eventInstanceHeights: {},
+ };
+ return _this;
+ }
+ TableRow.prototype.render = function () {
+ var _this = this;
+ var _a = this, props = _a.props, state = _a.state, context = _a.context;
+ var options = context.options;
+ var colCnt = props.cells.length;
+ var businessHoursByCol = splitSegsByFirstCol(props.businessHourSegs, colCnt);
+ var bgEventSegsByCol = splitSegsByFirstCol(props.bgEventSegs, colCnt);
+ var highlightSegsByCol = splitSegsByFirstCol(this.getHighlightSegs(), colCnt);
+ var mirrorSegsByCol = splitSegsByFirstCol(this.getMirrorSegs(), colCnt);
+ var _b = computeFgSegPlacement(sortEventSegs(props.fgEventSegs, options.eventOrder), props.dayMaxEvents, props.dayMaxEventRows, options.eventOrderStrict, state.eventInstanceHeights, state.maxContentHeight, props.cells), singleColPlacements = _b.singleColPlacements, multiColPlacements = _b.multiColPlacements, moreCnts = _b.moreCnts, moreMarginTops = _b.moreMarginTops;
+ var isForcedInvisible = // TODO: messy way to compute this
+ (props.eventDrag && props.eventDrag.affectedInstances) ||
+ (props.eventResize && props.eventResize.affectedInstances) ||
+ {};
+ return (createElement("tr", { ref: this.rootElRef },
+ props.renderIntro && props.renderIntro(),
+ props.cells.map(function (cell, col) {
+ var normalFgNodes = _this.renderFgSegs(col, props.forPrint ? singleColPlacements[col] : multiColPlacements[col], props.todayRange, isForcedInvisible);
+ var mirrorFgNodes = _this.renderFgSegs(col, buildMirrorPlacements(mirrorSegsByCol[col], multiColPlacements), props.todayRange, {}, Boolean(props.eventDrag), Boolean(props.eventResize), false);
+ return (createElement(TableCell, { key: cell.key, elRef: _this.cellElRefs.createRef(cell.key), innerElRef: _this.frameElRefs.createRef(cell.key) /* FF
problem, but okay to use for left/right. TODO: rename prop */, dateProfile: props.dateProfile, date: cell.date, showDayNumber: props.showDayNumbers, showWeekNumber: props.showWeekNumbers && col === 0, forceDayTop: props.showWeekNumbers /* even displaying weeknum for row, not necessarily day */, todayRange: props.todayRange, eventSelection: props.eventSelection, eventDrag: props.eventDrag, eventResize: props.eventResize, extraHookProps: cell.extraHookProps, extraDataAttrs: cell.extraDataAttrs, extraClassNames: cell.extraClassNames, extraDateSpan: cell.extraDateSpan, moreCnt: moreCnts[col], moreMarginTop: moreMarginTops[col], singlePlacements: singleColPlacements[col], fgContentElRef: _this.fgElRefs.createRef(cell.key), fgContent: ( // Fragment scopes the keys
+ createElement(Fragment, null,
+ createElement(Fragment, null, normalFgNodes),
+ createElement(Fragment, null, mirrorFgNodes))), bgContent: ( // Fragment scopes the keys
+ createElement(Fragment, null,
+ _this.renderFillSegs(highlightSegsByCol[col], 'highlight'),
+ _this.renderFillSegs(businessHoursByCol[col], 'non-business'),
+ _this.renderFillSegs(bgEventSegsByCol[col], 'bg-event'))) }));
+ })));
+ };
+ TableRow.prototype.componentDidMount = function () {
+ this.updateSizing(true);
+ };
+ TableRow.prototype.componentDidUpdate = function (prevProps, prevState) {
+ var currentProps = this.props;
+ this.updateSizing(!isPropsEqual(prevProps, currentProps));
+ };
+ TableRow.prototype.getHighlightSegs = function () {
+ var props = this.props;
+ if (props.eventDrag && props.eventDrag.segs.length) { // messy check
+ return props.eventDrag.segs;
+ }
+ if (props.eventResize && props.eventResize.segs.length) { // messy check
+ return props.eventResize.segs;
+ }
+ return props.dateSelectionSegs;
+ };
+ TableRow.prototype.getMirrorSegs = function () {
+ var props = this.props;
+ if (props.eventResize && props.eventResize.segs.length) { // messy check
+ return props.eventResize.segs;
+ }
+ return [];
+ };
+ TableRow.prototype.renderFgSegs = function (col, segPlacements, todayRange, isForcedInvisible, isDragging, isResizing, isDateSelecting) {
+ var context = this.context;
+ var eventSelection = this.props.eventSelection;
+ var framePositions = this.state.framePositions;
+ var defaultDisplayEventEnd = this.props.cells.length === 1; // colCnt === 1
+ var isMirror = isDragging || isResizing || isDateSelecting;
+ var nodes = [];
+ if (framePositions) {
+ for (var _i = 0, segPlacements_1 = segPlacements; _i < segPlacements_1.length; _i++) {
+ var placement = segPlacements_1[_i];
+ var seg = placement.seg;
+ var instanceId = seg.eventRange.instance.instanceId;
+ var key = instanceId + ':' + col;
+ var isVisible = placement.isVisible && !isForcedInvisible[instanceId];
+ var isAbsolute = placement.isAbsolute;
+ var left = '';
+ var right = '';
+ if (isAbsolute) {
+ if (context.isRtl) {
+ right = 0;
+ left = framePositions.lefts[seg.lastCol] - framePositions.lefts[seg.firstCol];
+ }
+ else {
+ left = 0;
+ right = framePositions.rights[seg.firstCol] - framePositions.rights[seg.lastCol];
+ }
+ }
+ /*
+ known bug: events that are force to be list-item but span multiple days still take up space in later columns
+ todo: in print view, for multi-day events, don't display title within non-start/end segs
+ */
+ nodes.push(createElement("div", { className: 'fc-daygrid-event-harness' + (isAbsolute ? ' fc-daygrid-event-harness-abs' : ''), key: key, ref: isMirror ? null : this.segHarnessRefs.createRef(key), style: {
+ visibility: isVisible ? '' : 'hidden',
+ marginTop: isAbsolute ? '' : placement.marginTop,
+ top: isAbsolute ? placement.absoluteTop : '',
+ left: left,
+ right: right,
+ } }, hasListItemDisplay(seg) ? (createElement(TableListItemEvent, __assign({ seg: seg, isDragging: isDragging, isSelected: instanceId === eventSelection, defaultDisplayEventEnd: defaultDisplayEventEnd }, getSegMeta(seg, todayRange)))) : (createElement(TableBlockEvent, __assign({ seg: seg, isDragging: isDragging, isResizing: isResizing, isDateSelecting: isDateSelecting, isSelected: instanceId === eventSelection, defaultDisplayEventEnd: defaultDisplayEventEnd }, getSegMeta(seg, todayRange))))));
+ }
+ }
+ return nodes;
+ };
+ TableRow.prototype.renderFillSegs = function (segs, fillType) {
+ var isRtl = this.context.isRtl;
+ var todayRange = this.props.todayRange;
+ var framePositions = this.state.framePositions;
+ var nodes = [];
+ if (framePositions) {
+ for (var _i = 0, segs_1 = segs; _i < segs_1.length; _i++) {
+ var seg = segs_1[_i];
+ var leftRightCss = isRtl ? {
+ right: 0,
+ left: framePositions.lefts[seg.lastCol] - framePositions.lefts[seg.firstCol],
+ } : {
+ left: 0,
+ right: framePositions.rights[seg.firstCol] - framePositions.rights[seg.lastCol],
+ };
+ nodes.push(createElement("div", { key: buildEventRangeKey(seg.eventRange), className: "fc-daygrid-bg-harness", style: leftRightCss }, fillType === 'bg-event' ?
+ createElement(BgEvent, __assign({ seg: seg }, getSegMeta(seg, todayRange))) :
+ renderFill(fillType)));
+ }
+ }
+ return createElement.apply(void 0, __spreadArray([Fragment, {}], nodes));
+ };
+ TableRow.prototype.updateSizing = function (isExternalSizingChange) {
+ var _a = this, props = _a.props, frameElRefs = _a.frameElRefs;
+ if (!props.forPrint &&
+ props.clientWidth !== null // positioning ready?
+ ) {
+ if (isExternalSizingChange) {
+ var frameEls = props.cells.map(function (cell) { return frameElRefs.currentMap[cell.key]; });
+ if (frameEls.length) {
+ var originEl = this.rootElRef.current;
+ this.setState({
+ framePositions: new PositionCache(originEl, frameEls, true, // isHorizontal
+ false),
+ });
+ }
+ }
+ var limitByContentHeight = props.dayMaxEvents === true || props.dayMaxEventRows === true;
+ this.setState({
+ eventInstanceHeights: this.queryEventInstanceHeights(),
+ maxContentHeight: limitByContentHeight ? this.computeMaxContentHeight() : null,
+ });
+ }
+ };
+ TableRow.prototype.queryEventInstanceHeights = function () {
+ var segElMap = this.segHarnessRefs.currentMap;
+ var eventInstanceHeights = {};
+ // get the max height amongst instance segs
+ for (var key in segElMap) {
+ var height = Math.round(segElMap[key].getBoundingClientRect().height);
+ var instanceId = key.split(':')[0]; // deconstruct how renderFgSegs makes the key
+ eventInstanceHeights[instanceId] = Math.max(eventInstanceHeights[instanceId] || 0, height);
+ }
+ return eventInstanceHeights;
+ };
+ TableRow.prototype.computeMaxContentHeight = function () {
+ var firstKey = this.props.cells[0].key;
+ var cellEl = this.cellElRefs.currentMap[firstKey];
+ var fcContainerEl = this.fgElRefs.currentMap[firstKey];
+ return cellEl.getBoundingClientRect().bottom - fcContainerEl.getBoundingClientRect().top;
+ };
+ TableRow.prototype.getCellEls = function () {
+ var elMap = this.cellElRefs.currentMap;
+ return this.props.cells.map(function (cell) { return elMap[cell.key]; });
+ };
+ return TableRow;
+ }(DateComponent));
+ TableRow.addStateEquality({
+ eventInstanceHeights: isPropsEqual,
+ });
+ function buildMirrorPlacements(mirrorSegs, colPlacements) {
+ if (!mirrorSegs.length) {
+ return [];
+ }
+ var topsByInstanceId = buildAbsoluteTopHash(colPlacements); // TODO: cache this at first render?
+ return mirrorSegs.map(function (seg) { return ({
+ seg: seg,
+ isVisible: true,
+ isAbsolute: true,
+ absoluteTop: topsByInstanceId[seg.eventRange.instance.instanceId],
+ marginTop: 0,
+ }); });
+ }
+ function buildAbsoluteTopHash(colPlacements) {
+ var topsByInstanceId = {};
+ for (var _i = 0, colPlacements_1 = colPlacements; _i < colPlacements_1.length; _i++) {
+ var placements = colPlacements_1[_i];
+ for (var _a = 0, placements_1 = placements; _a < placements_1.length; _a++) {
+ var placement = placements_1[_a];
+ topsByInstanceId[placement.seg.eventRange.instance.instanceId] = placement.absoluteTop;
+ }
+ }
+ return topsByInstanceId;
+ }
+
+ var Table = /** @class */ (function (_super) {
+ __extends(Table, _super);
+ function Table() {
+ var _this = _super !== null && _super.apply(this, arguments) || this;
+ _this.splitBusinessHourSegs = memoize(splitSegsByRow);
+ _this.splitBgEventSegs = memoize(splitSegsByRow);
+ _this.splitFgEventSegs = memoize(splitSegsByRow);
+ _this.splitDateSelectionSegs = memoize(splitSegsByRow);
+ _this.splitEventDrag = memoize(splitInteractionByRow);
+ _this.splitEventResize = memoize(splitInteractionByRow);
+ _this.rowRefs = new RefMap();
+ _this.handleRootEl = function (rootEl) {
+ _this.rootEl = rootEl;
+ if (rootEl) {
+ _this.context.registerInteractiveComponent(_this, {
+ el: rootEl,
+ isHitComboAllowed: _this.props.isHitComboAllowed,
+ });
+ }
+ else {
+ _this.context.unregisterInteractiveComponent(_this);
+ }
+ };
+ return _this;
+ }
+ Table.prototype.render = function () {
+ var _this = this;
+ var props = this.props;
+ var dateProfile = props.dateProfile, dayMaxEventRows = props.dayMaxEventRows, dayMaxEvents = props.dayMaxEvents, expandRows = props.expandRows;
+ var rowCnt = props.cells.length;
+ var businessHourSegsByRow = this.splitBusinessHourSegs(props.businessHourSegs, rowCnt);
+ var bgEventSegsByRow = this.splitBgEventSegs(props.bgEventSegs, rowCnt);
+ var fgEventSegsByRow = this.splitFgEventSegs(props.fgEventSegs, rowCnt);
+ var dateSelectionSegsByRow = this.splitDateSelectionSegs(props.dateSelectionSegs, rowCnt);
+ var eventDragByRow = this.splitEventDrag(props.eventDrag, rowCnt);
+ var eventResizeByRow = this.splitEventResize(props.eventResize, rowCnt);
+ var limitViaBalanced = dayMaxEvents === true || dayMaxEventRows === true;
+ // if rows can't expand to fill fixed height, can't do balanced-height event limit
+ // TODO: best place to normalize these options?
+ if (limitViaBalanced && !expandRows) {
+ limitViaBalanced = false;
+ dayMaxEventRows = null;
+ dayMaxEvents = null;
+ }
+ var classNames = [
+ 'fc-daygrid-body',
+ limitViaBalanced ? 'fc-daygrid-body-balanced' : 'fc-daygrid-body-unbalanced',
+ expandRows ? '' : 'fc-daygrid-body-natural', // will height of one row depend on the others?
+ ];
+ return (createElement("div", { className: classNames.join(' '), ref: this.handleRootEl, style: {
+ // these props are important to give this wrapper correct dimensions for interactions
+ // TODO: if we set it here, can we avoid giving to inner tables?
+ width: props.clientWidth,
+ minWidth: props.tableMinWidth,
+ } },
+ createElement(NowTimer, { unit: "day" }, function (nowDate, todayRange) { return (createElement(Fragment, null,
+ createElement("table", { className: "fc-scrollgrid-sync-table", style: {
+ width: props.clientWidth,
+ minWidth: props.tableMinWidth,
+ height: expandRows ? props.clientHeight : '',
+ } },
+ props.colGroupNode,
+ createElement("tbody", null, props.cells.map(function (cells, row) { return (createElement(TableRow, { ref: _this.rowRefs.createRef(row), key: cells.length
+ ? cells[0].date.toISOString() /* best? or put key on cell? or use diff formatter? */
+ : row // in case there are no cells (like when resource view is loading)
+ , showDayNumbers: rowCnt > 1, showWeekNumbers: props.showWeekNumbers, todayRange: todayRange, dateProfile: dateProfile, cells: cells, renderIntro: props.renderRowIntro, businessHourSegs: businessHourSegsByRow[row], eventSelection: props.eventSelection, bgEventSegs: bgEventSegsByRow[row].filter(isSegAllDay) /* hack */, fgEventSegs: fgEventSegsByRow[row], dateSelectionSegs: dateSelectionSegsByRow[row], eventDrag: eventDragByRow[row], eventResize: eventResizeByRow[row], dayMaxEvents: dayMaxEvents, dayMaxEventRows: dayMaxEventRows, clientWidth: props.clientWidth, clientHeight: props.clientHeight, forPrint: props.forPrint })); }))))); })));
+ };
+ // Hit System
+ // ----------------------------------------------------------------------------------------------------
+ Table.prototype.prepareHits = function () {
+ this.rowPositions = new PositionCache(this.rootEl, this.rowRefs.collect().map(function (rowObj) { return rowObj.getCellEls()[0]; }), // first cell el in each row. TODO: not optimal
+ false, true);
+ this.colPositions = new PositionCache(this.rootEl, this.rowRefs.currentMap[0].getCellEls(), // cell els in first row
+ true, // horizontal
+ false);
+ };
+ Table.prototype.queryHit = function (positionLeft, positionTop) {
+ var _a = this, colPositions = _a.colPositions, rowPositions = _a.rowPositions;
+ var col = colPositions.leftToIndex(positionLeft);
+ var row = rowPositions.topToIndex(positionTop);
+ if (row != null && col != null) {
+ var cell = this.props.cells[row][col];
+ return {
+ dateProfile: this.props.dateProfile,
+ dateSpan: __assign({ range: this.getCellRange(row, col), allDay: true }, cell.extraDateSpan),
+ dayEl: this.getCellEl(row, col),
+ rect: {
+ left: colPositions.lefts[col],
+ right: colPositions.rights[col],
+ top: rowPositions.tops[row],
+ bottom: rowPositions.bottoms[row],
+ },
+ layer: 0,
+ };
+ }
+ return null;
+ };
+ Table.prototype.getCellEl = function (row, col) {
+ return this.rowRefs.currentMap[row].getCellEls()[col]; // TODO: not optimal
+ };
+ Table.prototype.getCellRange = function (row, col) {
+ var start = this.props.cells[row][col].date;
+ var end = addDays(start, 1);
+ return { start: start, end: end };
+ };
+ return Table;
+ }(DateComponent));
+ function isSegAllDay(seg) {
+ return seg.eventRange.def.allDay;
+ }
+
+ var DayTableSlicer = /** @class */ (function (_super) {
+ __extends(DayTableSlicer, _super);
+ function DayTableSlicer() {
+ var _this = _super !== null && _super.apply(this, arguments) || this;
+ _this.forceDayIfListItem = true;
+ return _this;
+ }
+ DayTableSlicer.prototype.sliceRange = function (dateRange, dayTableModel) {
+ return dayTableModel.sliceRange(dateRange);
+ };
+ return DayTableSlicer;
+ }(Slicer));
+
+ var DayTable = /** @class */ (function (_super) {
+ __extends(DayTable, _super);
+ function DayTable() {
+ var _this = _super !== null && _super.apply(this, arguments) || this;
+ _this.slicer = new DayTableSlicer();
+ _this.tableRef = createRef();
+ return _this;
+ }
+ DayTable.prototype.render = function () {
+ var _a = this, props = _a.props, context = _a.context;
+ return (createElement(Table, __assign({ ref: this.tableRef }, this.slicer.sliceProps(props, props.dateProfile, props.nextDayThreshold, context, props.dayTableModel), { dateProfile: props.dateProfile, cells: props.dayTableModel.cells, colGroupNode: props.colGroupNode, tableMinWidth: props.tableMinWidth, renderRowIntro: props.renderRowIntro, dayMaxEvents: props.dayMaxEvents, dayMaxEventRows: props.dayMaxEventRows, showWeekNumbers: props.showWeekNumbers, expandRows: props.expandRows, headerAlignElRef: props.headerAlignElRef, clientWidth: props.clientWidth, clientHeight: props.clientHeight, forPrint: props.forPrint })));
+ };
+ return DayTable;
+ }(DateComponent));
+
+ var DayTableView = /** @class */ (function (_super) {
+ __extends(DayTableView, _super);
+ function DayTableView() {
+ var _this = _super !== null && _super.apply(this, arguments) || this;
+ _this.buildDayTableModel = memoize(buildDayTableModel);
+ _this.headerRef = createRef();
+ _this.tableRef = createRef();
+ return _this;
+ }
+ DayTableView.prototype.render = function () {
+ var _this = this;
+ var _a = this.context, options = _a.options, dateProfileGenerator = _a.dateProfileGenerator;
+ var props = this.props;
+ var dayTableModel = this.buildDayTableModel(props.dateProfile, dateProfileGenerator);
+ var headerContent = options.dayHeaders && (createElement(DayHeader, { ref: this.headerRef, dateProfile: props.dateProfile, dates: dayTableModel.headerDates, datesRepDistinctDays: dayTableModel.rowCnt === 1 }));
+ var bodyContent = function (contentArg) { return (createElement(DayTable, { ref: _this.tableRef, dateProfile: props.dateProfile, dayTableModel: dayTableModel, businessHours: props.businessHours, dateSelection: props.dateSelection, eventStore: props.eventStore, eventUiBases: props.eventUiBases, eventSelection: props.eventSelection, eventDrag: props.eventDrag, eventResize: props.eventResize, nextDayThreshold: options.nextDayThreshold, colGroupNode: contentArg.tableColGroupNode, tableMinWidth: contentArg.tableMinWidth, dayMaxEvents: options.dayMaxEvents, dayMaxEventRows: options.dayMaxEventRows, showWeekNumbers: options.weekNumbers, expandRows: !props.isHeightAuto, headerAlignElRef: _this.headerElRef, clientWidth: contentArg.clientWidth, clientHeight: contentArg.clientHeight, forPrint: props.forPrint })); };
+ return options.dayMinWidth
+ ? this.renderHScrollLayout(headerContent, bodyContent, dayTableModel.colCnt, options.dayMinWidth)
+ : this.renderSimpleLayout(headerContent, bodyContent);
+ };
+ return DayTableView;
+ }(TableView));
+ function buildDayTableModel(dateProfile, dateProfileGenerator) {
+ var daySeries = new DaySeriesModel(dateProfile.renderRange, dateProfileGenerator);
+ return new DayTableModel(daySeries, /year|month|week/.test(dateProfile.currentRangeUnit));
+ }
+
+ var TableDateProfileGenerator = /** @class */ (function (_super) {
+ __extends(TableDateProfileGenerator, _super);
+ function TableDateProfileGenerator() {
+ return _super !== null && _super.apply(this, arguments) || this;
+ }
+ // Computes the date range that will be rendered.
+ TableDateProfileGenerator.prototype.buildRenderRange = function (currentRange, currentRangeUnit, isRangeAllDay) {
+ var dateEnv = this.props.dateEnv;
+ var renderRange = _super.prototype.buildRenderRange.call(this, currentRange, currentRangeUnit, isRangeAllDay);
+ var start = renderRange.start;
+ var end = renderRange.end;
+ var endOfWeek;
+ // year and month views should be aligned with weeks. this is already done for week
+ if (/^(year|month)$/.test(currentRangeUnit)) {
+ start = dateEnv.startOfWeek(start);
+ // make end-of-week if not already
+ endOfWeek = dateEnv.startOfWeek(end);
+ if (endOfWeek.valueOf() !== end.valueOf()) {
+ end = addWeeks(endOfWeek, 1);
+ }
+ }
+ // ensure 6 weeks
+ if (this.props.monthMode &&
+ this.props.fixedWeekCount) {
+ var rowCnt = Math.ceil(// could be partial weeks due to hiddenDays
+ diffWeeks(start, end));
+ end = addWeeks(end, 6 - rowCnt);
+ }
+ return { start: start, end: end };
+ };
+ return TableDateProfileGenerator;
+ }(DateProfileGenerator));
+
+ var dayGridPlugin = createPlugin({
+ initialView: 'dayGridMonth',
+ views: {
+ dayGrid: {
+ component: DayTableView,
+ dateProfileGeneratorClass: TableDateProfileGenerator,
+ },
+ dayGridDay: {
+ type: 'dayGrid',
+ duration: { days: 1 },
+ },
+ dayGridWeek: {
+ type: 'dayGrid',
+ duration: { weeks: 1 },
+ },
+ dayGridMonth: {
+ type: 'dayGrid',
+ duration: { months: 1 },
+ monthMode: true,
+ fixedWeekCount: true,
+ },
+ },
+ });
+
+ var AllDaySplitter = /** @class */ (function (_super) {
+ __extends(AllDaySplitter, _super);
+ function AllDaySplitter() {
+ return _super !== null && _super.apply(this, arguments) || this;
+ }
+ AllDaySplitter.prototype.getKeyInfo = function () {
+ return {
+ allDay: {},
+ timed: {},
+ };
+ };
+ AllDaySplitter.prototype.getKeysForDateSpan = function (dateSpan) {
+ if (dateSpan.allDay) {
+ return ['allDay'];
+ }
+ return ['timed'];
+ };
+ AllDaySplitter.prototype.getKeysForEventDef = function (eventDef) {
+ if (!eventDef.allDay) {
+ return ['timed'];
+ }
+ if (hasBgRendering(eventDef)) {
+ return ['timed', 'allDay'];
+ }
+ return ['allDay'];
+ };
+ return AllDaySplitter;
+ }(Splitter));
+
+ var DEFAULT_SLAT_LABEL_FORMAT = createFormatter({
+ hour: 'numeric',
+ minute: '2-digit',
+ omitZeroMinute: true,
+ meridiem: 'short',
+ });
+ function TimeColsAxisCell(props) {
+ var classNames = [
+ 'fc-timegrid-slot',
+ 'fc-timegrid-slot-label',
+ props.isLabeled ? 'fc-scrollgrid-shrink' : 'fc-timegrid-slot-minor',
+ ];
+ return (createElement(ViewContextType.Consumer, null, function (context) {
+ if (!props.isLabeled) {
+ return (createElement("td", { className: classNames.join(' '), "data-time": props.isoTimeStr }));
+ }
+ var dateEnv = context.dateEnv, options = context.options, viewApi = context.viewApi;
+ var labelFormat = // TODO: fully pre-parse
+ options.slotLabelFormat == null ? DEFAULT_SLAT_LABEL_FORMAT :
+ Array.isArray(options.slotLabelFormat) ? createFormatter(options.slotLabelFormat[0]) :
+ createFormatter(options.slotLabelFormat);
+ var hookProps = {
+ level: 0,
+ time: props.time,
+ date: dateEnv.toDate(props.date),
+ view: viewApi,
+ text: dateEnv.format(props.date, labelFormat),
+ };
+ return (createElement(RenderHook, { hookProps: hookProps, classNames: options.slotLabelClassNames, content: options.slotLabelContent, defaultContent: renderInnerContent$1, didMount: options.slotLabelDidMount, willUnmount: options.slotLabelWillUnmount }, function (rootElRef, customClassNames, innerElRef, innerContent) { return (createElement("td", { ref: rootElRef, className: classNames.concat(customClassNames).join(' '), "data-time": props.isoTimeStr },
+ createElement("div", { className: "fc-timegrid-slot-label-frame fc-scrollgrid-shrink-frame" },
+ createElement("div", { className: "fc-timegrid-slot-label-cushion fc-scrollgrid-shrink-cushion", ref: innerElRef }, innerContent)))); }));
+ }));
+ }
+ function renderInnerContent$1(props) {
+ return props.text;
+ }
+
+ var TimeBodyAxis = /** @class */ (function (_super) {
+ __extends(TimeBodyAxis, _super);
+ function TimeBodyAxis() {
+ return _super !== null && _super.apply(this, arguments) || this;
+ }
+ TimeBodyAxis.prototype.render = function () {
+ return this.props.slatMetas.map(function (slatMeta) { return (createElement("tr", { key: slatMeta.key },
+ createElement(TimeColsAxisCell, __assign({}, slatMeta)))); });
+ };
+ return TimeBodyAxis;
+ }(BaseComponent));
+
+ var DEFAULT_WEEK_NUM_FORMAT = createFormatter({ week: 'short' });
+ var AUTO_ALL_DAY_MAX_EVENT_ROWS = 5;
+ var TimeColsView = /** @class */ (function (_super) {
+ __extends(TimeColsView, _super);
+ function TimeColsView() {
+ var _this = _super !== null && _super.apply(this, arguments) || this;
+ _this.allDaySplitter = new AllDaySplitter(); // for use by subclasses
+ _this.headerElRef = createRef();
+ _this.rootElRef = createRef();
+ _this.scrollerElRef = createRef();
+ _this.state = {
+ slatCoords: null,
+ };
+ _this.handleScrollTopRequest = function (scrollTop) {
+ var scrollerEl = _this.scrollerElRef.current;
+ if (scrollerEl) { // TODO: not sure how this could ever be null. weirdness with the reducer
+ scrollerEl.scrollTop = scrollTop;
+ }
+ };
+ /* Header Render Methods
+ ------------------------------------------------------------------------------------------------------------------*/
+ _this.renderHeadAxis = function (rowKey, frameHeight) {
+ if (frameHeight === void 0) { frameHeight = ''; }
+ var options = _this.context.options;
+ var dateProfile = _this.props.dateProfile;
+ var range = dateProfile.renderRange;
+ var dayCnt = diffDays(range.start, range.end);
+ var navLinkAttrs = (options.navLinks && dayCnt === 1) // only do in day views (to avoid doing in week views that dont need it)
+ ? { 'data-navlink': buildNavLinkData(range.start, 'week'), tabIndex: 0 }
+ : {};
+ if (options.weekNumbers && rowKey === 'day') {
+ return (createElement(WeekNumberRoot, { date: range.start, defaultFormat: DEFAULT_WEEK_NUM_FORMAT }, function (rootElRef, classNames, innerElRef, innerContent) { return (createElement("th", { ref: rootElRef, className: [
+ 'fc-timegrid-axis',
+ 'fc-scrollgrid-shrink',
+ ].concat(classNames).join(' ') },
+ createElement("div", { className: "fc-timegrid-axis-frame fc-scrollgrid-shrink-frame fc-timegrid-axis-frame-liquid", style: { height: frameHeight } },
+ createElement("a", __assign({ ref: innerElRef, className: "fc-timegrid-axis-cushion fc-scrollgrid-shrink-cushion fc-scrollgrid-sync-inner" }, navLinkAttrs), innerContent)))); }));
+ }
+ return (createElement("th", { className: "fc-timegrid-axis" },
+ createElement("div", { className: "fc-timegrid-axis-frame", style: { height: frameHeight } })));
+ };
+ /* Table Component Render Methods
+ ------------------------------------------------------------------------------------------------------------------*/
+ // only a one-way height sync. we don't send the axis inner-content height to the DayGrid,
+ // but DayGrid still needs to have classNames on inner elements in order to measure.
+ _this.renderTableRowAxis = function (rowHeight) {
+ var _a = _this.context, options = _a.options, viewApi = _a.viewApi;
+ var hookProps = {
+ text: options.allDayText,
+ view: viewApi,
+ };
+ return (
+ // TODO: make reusable hook. used in list view too
+ createElement(RenderHook, { hookProps: hookProps, classNames: options.allDayClassNames, content: options.allDayContent, defaultContent: renderAllDayInner$1, didMount: options.allDayDidMount, willUnmount: options.allDayWillUnmount }, function (rootElRef, classNames, innerElRef, innerContent) { return (createElement("td", { ref: rootElRef, className: [
+ 'fc-timegrid-axis',
+ 'fc-scrollgrid-shrink',
+ ].concat(classNames).join(' ') },
+ createElement("div", { className: 'fc-timegrid-axis-frame fc-scrollgrid-shrink-frame' + (rowHeight == null ? ' fc-timegrid-axis-frame-liquid' : ''), style: { height: rowHeight } },
+ createElement("span", { className: "fc-timegrid-axis-cushion fc-scrollgrid-shrink-cushion fc-scrollgrid-sync-inner", ref: innerElRef }, innerContent)))); }));
+ };
+ _this.handleSlatCoords = function (slatCoords) {
+ _this.setState({ slatCoords: slatCoords });
+ };
+ return _this;
+ }
+ // rendering
+ // ----------------------------------------------------------------------------------------------------
+ TimeColsView.prototype.renderSimpleLayout = function (headerRowContent, allDayContent, timeContent) {
+ var _a = this, context = _a.context, props = _a.props;
+ var sections = [];
+ var stickyHeaderDates = getStickyHeaderDates(context.options);
+ if (headerRowContent) {
+ sections.push({
+ type: 'header',
+ key: 'header',
+ isSticky: stickyHeaderDates,
+ chunk: {
+ elRef: this.headerElRef,
+ tableClassName: 'fc-col-header',
+ rowContent: headerRowContent,
+ },
+ });
+ }
+ if (allDayContent) {
+ sections.push({
+ type: 'body',
+ key: 'all-day',
+ chunk: { content: allDayContent },
+ });
+ sections.push({
+ type: 'body',
+ key: 'all-day-divider',
+ outerContent: ( // TODO: rename to cellContent so don't need to define
elements work best with integers. round up to ensure contents fits
+ }
+ function getSectionHasLiquidHeight(props, sectionConfig) {
+ return props.liquid && sectionConfig.liquid; // does the section do liquid-height? (need to have whole scrollgrid liquid-height as well)
+ }
+ function getAllowYScrolling(props, sectionConfig) {
+ return sectionConfig.maxHeight != null || // if its possible for the height to max out, we might need scrollbars
+ getSectionHasLiquidHeight(props, sectionConfig); // if the section is liquid height, it might condense enough to require scrollbars
+ }
+ // TODO: ONLY use `arg`. force out internal function to use same API
+ function renderChunkContent(sectionConfig, chunkConfig, arg) {
+ var expandRows = arg.expandRows;
+ var content = typeof chunkConfig.content === 'function' ?
+ chunkConfig.content(arg) :
+ createElement('table', {
+ className: [
+ chunkConfig.tableClassName,
+ sectionConfig.syncRowHeights ? 'fc-scrollgrid-sync-table' : '',
+ ].join(' '),
+ style: {
+ minWidth: arg.tableMinWidth,
+ width: arg.clientWidth,
+ height: expandRows ? arg.clientHeight : '', // css `height` on a
serves as a min-height
+ },
+ }, arg.tableColGroupNode, createElement('tbody', {}, typeof chunkConfig.rowContent === 'function' ? chunkConfig.rowContent(arg) : chunkConfig.rowContent));
+ return content;
+ }
+ function isColPropsEqual(cols0, cols1) {
+ return isArraysEqual(cols0, cols1, isPropsEqual);
+ }
+ function renderMicroColGroup(cols, shrinkWidth) {
+ var colNodes = [];
+ /*
+ for ColProps with spans, it would have been great to make a single
+ HOWEVER, Chrome was getting messing up distributing the width to
/
elements with colspans.
+ SOLUTION: making individual
elements makes Chrome behave.
+ */
+ for (var _i = 0, cols_1 = cols; _i < cols_1.length; _i++) {
+ var colProps = cols_1[_i];
+ var span = colProps.span || 1;
+ for (var i = 0; i < span; i += 1) {
+ colNodes.push(createElement("col", { style: {
+ width: colProps.width === 'shrink' ? sanitizeShrinkWidth(shrinkWidth) : (colProps.width || ''),
+ minWidth: colProps.minWidth || '',
+ } }));
+ }
+ }
+ return createElement.apply(void 0, __spreadArray(['colgroup', {}], colNodes));
+ }
+ function sanitizeShrinkWidth(shrinkWidth) {
+ /* why 4? if we do 0, it will kill any border, which are needed for computeSmallestCellWidth
+ 4 accounts for 2 2-pixel borders. TODO: better solution? */
+ return shrinkWidth == null ? 4 : shrinkWidth;
+ }
+ function hasShrinkWidth(cols) {
+ for (var _i = 0, cols_2 = cols; _i < cols_2.length; _i++) {
+ var col = cols_2[_i];
+ if (col.width === 'shrink') {
+ return true;
+ }
+ }
+ return false;
+ }
+ function getScrollGridClassNames(liquid, context) {
+ var classNames = [
+ 'fc-scrollgrid',
+ context.theme.getClass('table'),
+ ];
+ if (liquid) {
+ classNames.push('fc-scrollgrid-liquid');
+ }
+ return classNames;
+ }
+ function getSectionClassNames(sectionConfig, wholeTableVGrow) {
+ var classNames = [
+ 'fc-scrollgrid-section',
+ "fc-scrollgrid-section-" + sectionConfig.type,
+ sectionConfig.className, // used?
+ ];
+ if (wholeTableVGrow && sectionConfig.liquid && sectionConfig.maxHeight == null) {
+ classNames.push('fc-scrollgrid-section-liquid');
+ }
+ if (sectionConfig.isSticky) {
+ classNames.push('fc-scrollgrid-section-sticky');
+ }
+ return classNames;
+ }
+ function renderScrollShim(arg) {
+ return (createElement("div", { className: "fc-scrollgrid-sticky-shim", style: {
+ width: arg.clientWidth,
+ minWidth: arg.tableMinWidth,
+ } }));
+ }
+ function getStickyHeaderDates(options) {
+ var stickyHeaderDates = options.stickyHeaderDates;
+ if (stickyHeaderDates == null || stickyHeaderDates === 'auto') {
+ stickyHeaderDates = options.height === 'auto' || options.viewHeight === 'auto';
+ }
+ return stickyHeaderDates;
+ }
+ function getStickyFooterScrollbar(options) {
+ var stickyFooterScrollbar = options.stickyFooterScrollbar;
+ if (stickyFooterScrollbar == null || stickyFooterScrollbar === 'auto') {
+ stickyFooterScrollbar = options.height === 'auto' || options.viewHeight === 'auto';
+ }
+ return stickyFooterScrollbar;
+ }
+
+ var SimpleScrollGrid = /** @class */ (function (_super) {
+ __extends(SimpleScrollGrid, _super);
+ function SimpleScrollGrid() {
+ var _this = _super !== null && _super.apply(this, arguments) || this;
+ _this.processCols = memoize(function (a) { return a; }, isColPropsEqual); // so we get same `cols` props every time
+ // yucky to memoize VNodes, but much more efficient for consumers
+ _this.renderMicroColGroup = memoize(renderMicroColGroup);
+ _this.scrollerRefs = new RefMap();
+ _this.scrollerElRefs = new RefMap(_this._handleScrollerEl.bind(_this));
+ _this.state = {
+ shrinkWidth: null,
+ forceYScrollbars: false,
+ scrollerClientWidths: {},
+ scrollerClientHeights: {},
+ };
+ // TODO: can do a really simple print-view. dont need to join rows
+ _this.handleSizing = function () {
+ _this.setState(__assign({ shrinkWidth: _this.computeShrinkWidth() }, _this.computeScrollerDims()));
+ };
+ return _this;
+ }
+ SimpleScrollGrid.prototype.render = function () {
+ var _a = this, props = _a.props, state = _a.state, context = _a.context;
+ var sectionConfigs = props.sections || [];
+ var cols = this.processCols(props.cols);
+ var microColGroupNode = this.renderMicroColGroup(cols, state.shrinkWidth);
+ var classNames = getScrollGridClassNames(props.liquid, context);
+ if (props.collapsibleWidth) {
+ classNames.push('fc-scrollgrid-collapsible');
+ }
+ // TODO: make DRY
+ var configCnt = sectionConfigs.length;
+ var configI = 0;
+ var currentConfig;
+ var headSectionNodes = [];
+ var bodySectionNodes = [];
+ var footSectionNodes = [];
+ while (configI < configCnt && (currentConfig = sectionConfigs[configI]).type === 'header') {
+ headSectionNodes.push(this.renderSection(currentConfig, microColGroupNode));
+ configI += 1;
+ }
+ while (configI < configCnt && (currentConfig = sectionConfigs[configI]).type === 'body') {
+ bodySectionNodes.push(this.renderSection(currentConfig, microColGroupNode));
+ configI += 1;
+ }
+ while (configI < configCnt && (currentConfig = sectionConfigs[configI]).type === 'footer') {
+ footSectionNodes.push(this.renderSection(currentConfig, microColGroupNode));
+ configI += 1;
+ }
+ // firefox bug: when setting height on table and there is a thead or tfoot,
+ // the necessary height:100% on the liquid-height body section forces the *whole* table to be taller. (bug #5524)
+ // use getCanVGrowWithinCell as a way to detect table-stupid firefox.
+ // if so, use a simpler dom structure, jam everything into a lone tbody.
+ var isBuggy = !getCanVGrowWithinCell();
+ return createElement('table', {
+ className: classNames.join(' '),
+ style: { height: props.height },
+ }, Boolean(!isBuggy && headSectionNodes.length) && createElement.apply(void 0, __spreadArray(['thead', {}], headSectionNodes)), Boolean(!isBuggy && bodySectionNodes.length) && createElement.apply(void 0, __spreadArray(['tbody', {}], bodySectionNodes)), Boolean(!isBuggy && footSectionNodes.length) && createElement.apply(void 0, __spreadArray(['tfoot', {}], footSectionNodes)), isBuggy && createElement.apply(void 0, __spreadArray(__spreadArray(__spreadArray(['tbody', {}], headSectionNodes), bodySectionNodes), footSectionNodes)));
+ };
+ SimpleScrollGrid.prototype.renderSection = function (sectionConfig, microColGroupNode) {
+ if ('outerContent' in sectionConfig) {
+ return (createElement(Fragment, { key: sectionConfig.key }, sectionConfig.outerContent));
+ }
+ return (createElement("tr", { key: sectionConfig.key, className: getSectionClassNames(sectionConfig, this.props.liquid).join(' ') }, this.renderChunkTd(sectionConfig, microColGroupNode, sectionConfig.chunk)));
+ };
+ SimpleScrollGrid.prototype.renderChunkTd = function (sectionConfig, microColGroupNode, chunkConfig) {
+ if ('outerContent' in chunkConfig) {
+ return chunkConfig.outerContent;
+ }
+ var props = this.props;
+ var _a = this.state, forceYScrollbars = _a.forceYScrollbars, scrollerClientWidths = _a.scrollerClientWidths, scrollerClientHeights = _a.scrollerClientHeights;
+ var needsYScrolling = getAllowYScrolling(props, sectionConfig); // TODO: do lazily. do in section config?
+ var isLiquid = getSectionHasLiquidHeight(props, sectionConfig);
+ // for `!props.liquid` - is WHOLE scrollgrid natural height?
+ // TODO: do same thing in advanced scrollgrid? prolly not b/c always has horizontal scrollbars
+ var overflowY = !props.liquid ? 'visible' :
+ forceYScrollbars ? 'scroll' :
+ !needsYScrolling ? 'hidden' :
+ 'auto';
+ var sectionKey = sectionConfig.key;
+ var content = renderChunkContent(sectionConfig, chunkConfig, {
+ tableColGroupNode: microColGroupNode,
+ tableMinWidth: '',
+ clientWidth: (!props.collapsibleWidth && scrollerClientWidths[sectionKey] !== undefined) ? scrollerClientWidths[sectionKey] : null,
+ clientHeight: scrollerClientHeights[sectionKey] !== undefined ? scrollerClientHeights[sectionKey] : null,
+ expandRows: sectionConfig.expandRows,
+ syncRowHeights: false,
+ rowSyncHeights: [],
+ reportRowHeightChange: function () { },
+ });
+ return (createElement("td", { ref: chunkConfig.elRef },
+ createElement("div", { className: "fc-scroller-harness" + (isLiquid ? ' fc-scroller-harness-liquid' : '') },
+ createElement(Scroller, { ref: this.scrollerRefs.createRef(sectionKey), elRef: this.scrollerElRefs.createRef(sectionKey), overflowY: overflowY, overflowX: !props.liquid ? 'visible' : 'hidden' /* natural height? */, maxHeight: sectionConfig.maxHeight, liquid: isLiquid, liquidIsAbsolute // because its within a harness
+ : true }, content))));
+ };
+ SimpleScrollGrid.prototype._handleScrollerEl = function (scrollerEl, key) {
+ var section = getSectionByKey(this.props.sections, key);
+ if (section) {
+ setRef(section.chunk.scrollerElRef, scrollerEl);
+ }
+ };
+ SimpleScrollGrid.prototype.componentDidMount = function () {
+ this.handleSizing();
+ this.context.addResizeHandler(this.handleSizing);
+ };
+ SimpleScrollGrid.prototype.componentDidUpdate = function () {
+ // TODO: need better solution when state contains non-sizing things
+ this.handleSizing();
+ };
+ SimpleScrollGrid.prototype.componentWillUnmount = function () {
+ this.context.removeResizeHandler(this.handleSizing);
+ };
+ SimpleScrollGrid.prototype.computeShrinkWidth = function () {
+ return hasShrinkWidth(this.props.cols)
+ ? computeShrinkWidth(this.scrollerElRefs.getAll())
+ : 0;
+ };
+ SimpleScrollGrid.prototype.computeScrollerDims = function () {
+ var scrollbarWidth = getScrollbarWidths();
+ var _a = this, scrollerRefs = _a.scrollerRefs, scrollerElRefs = _a.scrollerElRefs;
+ var forceYScrollbars = false;
+ var scrollerClientWidths = {};
+ var scrollerClientHeights = {};
+ for (var sectionKey in scrollerRefs.currentMap) {
+ var scroller = scrollerRefs.currentMap[sectionKey];
+ if (scroller && scroller.needsYScrolling()) {
+ forceYScrollbars = true;
+ break;
+ }
+ }
+ for (var _i = 0, _b = this.props.sections; _i < _b.length; _i++) {
+ var section = _b[_i];
+ var sectionKey = section.key;
+ var scrollerEl = scrollerElRefs.currentMap[sectionKey];
+ if (scrollerEl) {
+ var harnessEl = scrollerEl.parentNode; // TODO: weird way to get this. need harness b/c doesn't include table borders
+ scrollerClientWidths[sectionKey] = Math.floor(harnessEl.getBoundingClientRect().width - (forceYScrollbars
+ ? scrollbarWidth.y // use global because scroller might not have scrollbars yet but will need them in future
+ : 0));
+ scrollerClientHeights[sectionKey] = Math.floor(harnessEl.getBoundingClientRect().height);
+ }
+ }
+ return { forceYScrollbars: forceYScrollbars, scrollerClientWidths: scrollerClientWidths, scrollerClientHeights: scrollerClientHeights };
+ };
+ return SimpleScrollGrid;
+ }(BaseComponent));
+ SimpleScrollGrid.addStateEquality({
+ scrollerClientWidths: isPropsEqual,
+ scrollerClientHeights: isPropsEqual,
+ });
+ function getSectionByKey(sections, key) {
+ for (var _i = 0, sections_1 = sections; _i < sections_1.length; _i++) {
+ var section = sections_1[_i];
+ if (section.key === key) {
+ return section;
+ }
+ }
+ return null;
+ }
+
+ var EventRoot = /** @class */ (function (_super) {
+ __extends(EventRoot, _super);
+ function EventRoot() {
+ var _this = _super !== null && _super.apply(this, arguments) || this;
+ _this.elRef = createRef();
+ return _this;
+ }
+ EventRoot.prototype.render = function () {
+ var _a = this, props = _a.props, context = _a.context;
+ var options = context.options;
+ var seg = props.seg;
+ var eventRange = seg.eventRange;
+ var ui = eventRange.ui;
+ var hookProps = {
+ event: new EventApi(context, eventRange.def, eventRange.instance),
+ view: context.viewApi,
+ timeText: props.timeText,
+ textColor: ui.textColor,
+ backgroundColor: ui.backgroundColor,
+ borderColor: ui.borderColor,
+ isDraggable: !props.disableDragging && computeSegDraggable(seg, context),
+ isStartResizable: !props.disableResizing && computeSegStartResizable(seg, context),
+ isEndResizable: !props.disableResizing && computeSegEndResizable(seg),
+ isMirror: Boolean(props.isDragging || props.isResizing || props.isDateSelecting),
+ isStart: Boolean(seg.isStart),
+ isEnd: Boolean(seg.isEnd),
+ isPast: Boolean(props.isPast),
+ isFuture: Boolean(props.isFuture),
+ isToday: Boolean(props.isToday),
+ isSelected: Boolean(props.isSelected),
+ isDragging: Boolean(props.isDragging),
+ isResizing: Boolean(props.isResizing),
+ };
+ var standardClassNames = getEventClassNames(hookProps).concat(ui.classNames);
+ return (createElement(RenderHook, { hookProps: hookProps, classNames: options.eventClassNames, content: options.eventContent, defaultContent: props.defaultContent, didMount: options.eventDidMount, willUnmount: options.eventWillUnmount, elRef: this.elRef }, function (rootElRef, customClassNames, innerElRef, innerContent) { return props.children(rootElRef, standardClassNames.concat(customClassNames), innerElRef, innerContent, hookProps); }));
+ };
+ EventRoot.prototype.componentDidMount = function () {
+ setElSeg(this.elRef.current, this.props.seg);
+ };
+ /*
+ need to re-assign seg to the element if seg changes, even if the element is the same
+ */
+ EventRoot.prototype.componentDidUpdate = function (prevProps) {
+ var seg = this.props.seg;
+ if (seg !== prevProps.seg) {
+ setElSeg(this.elRef.current, seg);
+ }
+ };
+ return EventRoot;
+ }(BaseComponent));
+
+ // should not be a purecomponent
+ var StandardEvent = /** @class */ (function (_super) {
+ __extends(StandardEvent, _super);
+ function StandardEvent() {
+ return _super !== null && _super.apply(this, arguments) || this;
+ }
+ StandardEvent.prototype.render = function () {
+ var _a = this, props = _a.props, context = _a.context;
+ var seg = props.seg;
+ var timeFormat = context.options.eventTimeFormat || props.defaultTimeFormat;
+ var timeText = buildSegTimeText(seg, timeFormat, context, props.defaultDisplayEventTime, props.defaultDisplayEventEnd);
+ return (createElement(EventRoot, { seg: seg, timeText: timeText, disableDragging: props.disableDragging, disableResizing: props.disableResizing, defaultContent: props.defaultContent || renderInnerContent$4, isDragging: props.isDragging, isResizing: props.isResizing, isDateSelecting: props.isDateSelecting, isSelected: props.isSelected, isPast: props.isPast, isFuture: props.isFuture, isToday: props.isToday }, function (rootElRef, classNames, innerElRef, innerContent, hookProps) { return (createElement("a", __assign({ className: props.extraClassNames.concat(classNames).join(' '), style: {
+ borderColor: hookProps.borderColor,
+ backgroundColor: hookProps.backgroundColor,
+ }, ref: rootElRef }, getSegAnchorAttrs$1(seg)),
+ createElement("div", { className: "fc-event-main", ref: innerElRef, style: { color: hookProps.textColor } }, innerContent),
+ hookProps.isStartResizable &&
+ createElement("div", { className: "fc-event-resizer fc-event-resizer-start" }),
+ hookProps.isEndResizable &&
+ createElement("div", { className: "fc-event-resizer fc-event-resizer-end" }))); }));
+ };
+ return StandardEvent;
+ }(BaseComponent));
+ function renderInnerContent$4(innerProps) {
+ return (createElement("div", { className: "fc-event-main-frame" },
+ innerProps.timeText && (createElement("div", { className: "fc-event-time" }, innerProps.timeText)),
+ createElement("div", { className: "fc-event-title-container" },
+ createElement("div", { className: "fc-event-title fc-sticky" }, innerProps.event.title || createElement(Fragment, null, "\u00A0")))));
+ }
+ function getSegAnchorAttrs$1(seg) {
+ var url = seg.eventRange.def.url;
+ return url ? { href: url } : {};
+ }
+
+ var NowIndicatorRoot = function (props) { return (createElement(ViewContextType.Consumer, null, function (context) {
+ var options = context.options;
+ var hookProps = {
+ isAxis: props.isAxis,
+ date: context.dateEnv.toDate(props.date),
+ view: context.viewApi,
+ };
+ return (createElement(RenderHook, { hookProps: hookProps, classNames: options.nowIndicatorClassNames, content: options.nowIndicatorContent, didMount: options.nowIndicatorDidMount, willUnmount: options.nowIndicatorWillUnmount }, props.children));
+ })); };
+
+ var DAY_NUM_FORMAT = createFormatter({ day: 'numeric' });
+ var DayCellContent = /** @class */ (function (_super) {
+ __extends(DayCellContent, _super);
+ function DayCellContent() {
+ return _super !== null && _super.apply(this, arguments) || this;
+ }
+ DayCellContent.prototype.render = function () {
+ var _a = this, props = _a.props, context = _a.context;
+ var options = context.options;
+ var hookProps = refineDayCellHookProps({
+ date: props.date,
+ dateProfile: props.dateProfile,
+ todayRange: props.todayRange,
+ showDayNumber: props.showDayNumber,
+ extraProps: props.extraHookProps,
+ viewApi: context.viewApi,
+ dateEnv: context.dateEnv,
+ });
+ return (createElement(ContentHook, { hookProps: hookProps, content: options.dayCellContent, defaultContent: props.defaultContent }, props.children));
+ };
+ return DayCellContent;
+ }(BaseComponent));
+ function refineDayCellHookProps(raw) {
+ var date = raw.date, dateEnv = raw.dateEnv;
+ var dayMeta = getDateMeta(date, raw.todayRange, null, raw.dateProfile);
+ return __assign(__assign(__assign({ date: dateEnv.toDate(date), view: raw.viewApi }, dayMeta), { dayNumberText: raw.showDayNumber ? dateEnv.format(date, DAY_NUM_FORMAT) : '' }), raw.extraProps);
+ }
+
+ var DayCellRoot = /** @class */ (function (_super) {
+ __extends(DayCellRoot, _super);
+ function DayCellRoot() {
+ var _this = _super !== null && _super.apply(this, arguments) || this;
+ _this.refineHookProps = memoizeObjArg(refineDayCellHookProps);
+ _this.normalizeClassNames = buildClassNameNormalizer();
+ return _this;
+ }
+ DayCellRoot.prototype.render = function () {
+ var _a = this, props = _a.props, context = _a.context;
+ var options = context.options;
+ var hookProps = this.refineHookProps({
+ date: props.date,
+ dateProfile: props.dateProfile,
+ todayRange: props.todayRange,
+ showDayNumber: props.showDayNumber,
+ extraProps: props.extraHookProps,
+ viewApi: context.viewApi,
+ dateEnv: context.dateEnv,
+ });
+ var classNames = getDayClassNames(hookProps, context.theme).concat(hookProps.isDisabled
+ ? [] // don't use custom classNames if disabled
+ : this.normalizeClassNames(options.dayCellClassNames, hookProps));
+ var dataAttrs = hookProps.isDisabled ? {} : {
+ 'data-date': formatDayString(props.date),
+ };
+ return (createElement(MountHook, { hookProps: hookProps, didMount: options.dayCellDidMount, willUnmount: options.dayCellWillUnmount, elRef: props.elRef }, function (rootElRef) { return props.children(rootElRef, classNames, dataAttrs, hookProps.isDisabled); }));
+ };
+ return DayCellRoot;
+ }(BaseComponent));
+
+ function renderFill(fillType) {
+ return (createElement("div", { className: "fc-" + fillType }));
+ }
+ var BgEvent = function (props) { return (createElement(EventRoot, { defaultContent: renderInnerContent$3, seg: props.seg /* uselesss i think */, timeText: "", disableDragging: true, disableResizing: true, isDragging: false, isResizing: false, isDateSelecting: false, isSelected: false, isPast: props.isPast, isFuture: props.isFuture, isToday: props.isToday }, function (rootElRef, classNames, innerElRef, innerContent, hookProps) { return (createElement("div", { ref: rootElRef, className: ['fc-bg-event'].concat(classNames).join(' '), style: {
+ backgroundColor: hookProps.backgroundColor,
+ } }, innerContent)); })); };
+ function renderInnerContent$3(props) {
+ var title = props.event.title;
+ return title && (createElement("div", { className: "fc-event-title" }, props.event.title));
+ }
+
+ var WeekNumberRoot = function (props) { return (createElement(ViewContextType.Consumer, null, function (context) {
+ var dateEnv = context.dateEnv, options = context.options;
+ var date = props.date;
+ var format = options.weekNumberFormat || props.defaultFormat;
+ var num = dateEnv.computeWeekNumber(date); // TODO: somehow use for formatting as well?
+ var text = dateEnv.format(date, format);
+ var hookProps = { num: num, text: text, date: date };
+ return (createElement(RenderHook, { hookProps: hookProps, classNames: options.weekNumberClassNames, content: options.weekNumberContent, defaultContent: renderInner, didMount: options.weekNumberDidMount, willUnmount: options.weekNumberWillUnmount }, props.children));
+ })); };
+ function renderInner(innerProps) {
+ return innerProps.text;
+ }
+
+ var PADDING_FROM_VIEWPORT = 10;
+ var Popover = /** @class */ (function (_super) {
+ __extends(Popover, _super);
+ function Popover() {
+ var _this = _super !== null && _super.apply(this, arguments) || this;
+ _this.handleRootEl = function (el) {
+ _this.rootEl = el;
+ if (_this.props.elRef) {
+ setRef(_this.props.elRef, el);
+ }
+ };
+ // Triggered when the user clicks *anywhere* in the document, for the autoHide feature
+ _this.handleDocumentMousedown = function (ev) {
+ // only hide the popover if the click happened outside the popover
+ var target = getEventTargetViaRoot(ev);
+ if (!_this.rootEl.contains(target)) {
+ _this.handleCloseClick();
+ }
+ };
+ _this.handleCloseClick = function () {
+ var onClose = _this.props.onClose;
+ if (onClose) {
+ onClose();
+ }
+ };
+ return _this;
+ }
+ Popover.prototype.render = function () {
+ var theme = this.context.theme;
+ var props = this.props;
+ var classNames = [
+ 'fc-popover',
+ theme.getClass('popover'),
+ ].concat(props.extraClassNames || []);
+ return createPortal(createElement("div", __assign({ className: classNames.join(' ') }, props.extraAttrs, { ref: this.handleRootEl }),
+ createElement("div", { className: 'fc-popover-header ' + theme.getClass('popoverHeader') },
+ createElement("span", { className: "fc-popover-title" }, props.title),
+ createElement("span", { className: 'fc-popover-close ' + theme.getIconClass('close'), onClick: this.handleCloseClick })),
+ createElement("div", { className: 'fc-popover-body ' + theme.getClass('popoverContent') }, props.children)), props.parentEl);
+ };
+ Popover.prototype.componentDidMount = function () {
+ document.addEventListener('mousedown', this.handleDocumentMousedown);
+ this.updateSize();
+ };
+ Popover.prototype.componentWillUnmount = function () {
+ document.removeEventListener('mousedown', this.handleDocumentMousedown);
+ };
+ Popover.prototype.updateSize = function () {
+ var isRtl = this.context.isRtl;
+ var _a = this.props, alignmentEl = _a.alignmentEl, alignGridTop = _a.alignGridTop;
+ var rootEl = this.rootEl;
+ var alignmentRect = computeClippedClientRect(alignmentEl);
+ if (alignmentRect) {
+ var popoverDims = rootEl.getBoundingClientRect();
+ // position relative to viewport
+ var popoverTop = alignGridTop
+ ? elementClosest(alignmentEl, '.fc-scrollgrid').getBoundingClientRect().top
+ : alignmentRect.top;
+ var popoverLeft = isRtl ? alignmentRect.right - popoverDims.width : alignmentRect.left;
+ // constrain
+ popoverTop = Math.max(popoverTop, PADDING_FROM_VIEWPORT);
+ popoverLeft = Math.min(popoverLeft, document.documentElement.clientWidth - PADDING_FROM_VIEWPORT - popoverDims.width);
+ popoverLeft = Math.max(popoverLeft, PADDING_FROM_VIEWPORT);
+ var origin_1 = rootEl.offsetParent.getBoundingClientRect();
+ applyStyle(rootEl, {
+ top: popoverTop - origin_1.top,
+ left: popoverLeft - origin_1.left,
+ });
+ }
+ };
+ return Popover;
+ }(BaseComponent));
+
+ var MorePopover = /** @class */ (function (_super) {
+ __extends(MorePopover, _super);
+ function MorePopover() {
+ var _this = _super !== null && _super.apply(this, arguments) || this;
+ _this.handleRootEl = function (rootEl) {
+ _this.rootEl = rootEl;
+ if (rootEl) {
+ _this.context.registerInteractiveComponent(_this, {
+ el: rootEl,
+ useEventCenter: false,
+ });
+ }
+ else {
+ _this.context.unregisterInteractiveComponent(_this);
+ }
+ };
+ return _this;
+ }
+ MorePopover.prototype.render = function () {
+ var _a = this.context, options = _a.options, dateEnv = _a.dateEnv;
+ var props = this.props;
+ var startDate = props.startDate, todayRange = props.todayRange, dateProfile = props.dateProfile;
+ var title = dateEnv.format(startDate, options.dayPopoverFormat);
+ return (createElement(DayCellRoot, { date: startDate, dateProfile: dateProfile, todayRange: todayRange, elRef: this.handleRootEl }, function (rootElRef, dayClassNames, dataAttrs) { return (createElement(Popover, { elRef: rootElRef, title: title, extraClassNames: ['fc-more-popover'].concat(dayClassNames), extraAttrs: dataAttrs /* TODO: make these time-based when not whole-day? */, parentEl: props.parentEl, alignmentEl: props.alignmentEl, alignGridTop: props.alignGridTop, onClose: props.onClose },
+ createElement(DayCellContent, { date: startDate, dateProfile: dateProfile, todayRange: todayRange }, function (innerElRef, innerContent) { return (innerContent &&
+ createElement("div", { className: "fc-more-popover-misc", ref: innerElRef }, innerContent)); }),
+ props.children)); }));
+ };
+ MorePopover.prototype.queryHit = function (positionLeft, positionTop, elWidth, elHeight) {
+ var _a = this, rootEl = _a.rootEl, props = _a.props;
+ if (positionLeft >= 0 && positionLeft < elWidth &&
+ positionTop >= 0 && positionTop < elHeight) {
+ return {
+ dateProfile: props.dateProfile,
+ dateSpan: __assign({ allDay: true, range: {
+ start: props.startDate,
+ end: props.endDate,
+ } }, props.extraDateSpan),
+ dayEl: rootEl,
+ rect: {
+ left: 0,
+ top: 0,
+ right: elWidth,
+ bottom: elHeight,
+ },
+ layer: 1, // important when comparing with hits from other components
+ };
+ }
+ return null;
+ };
+ return MorePopover;
+ }(DateComponent));
+
+ var MoreLinkRoot = /** @class */ (function (_super) {
+ __extends(MoreLinkRoot, _super);
+ function MoreLinkRoot() {
+ var _this = _super !== null && _super.apply(this, arguments) || this;
+ _this.linkElRef = createRef();
+ _this.state = {
+ isPopoverOpen: false,
+ };
+ _this.handleClick = function (ev) {
+ var _a = _this, props = _a.props, context = _a.context;
+ var moreLinkClick = context.options.moreLinkClick;
+ var date = computeRange(props).start;
+ function buildPublicSeg(seg) {
+ var _a = seg.eventRange, def = _a.def, instance = _a.instance, range = _a.range;
+ return {
+ event: new EventApi(context, def, instance),
+ start: context.dateEnv.toDate(range.start),
+ end: context.dateEnv.toDate(range.end),
+ isStart: seg.isStart,
+ isEnd: seg.isEnd,
+ };
+ }
+ if (typeof moreLinkClick === 'function') {
+ moreLinkClick = moreLinkClick({
+ date: date,
+ allDay: Boolean(props.allDayDate),
+ allSegs: props.allSegs.map(buildPublicSeg),
+ hiddenSegs: props.hiddenSegs.map(buildPublicSeg),
+ jsEvent: ev,
+ view: context.viewApi,
+ });
+ }
+ if (!moreLinkClick || moreLinkClick === 'popover') {
+ _this.setState({ isPopoverOpen: true });
+ }
+ else if (typeof moreLinkClick === 'string') { // a view name
+ context.calendarApi.zoomTo(date, moreLinkClick);
+ }
+ };
+ _this.handlePopoverClose = function () {
+ _this.setState({ isPopoverOpen: false });
+ };
+ return _this;
+ }
+ MoreLinkRoot.prototype.render = function () {
+ var _this = this;
+ var props = this.props;
+ return (createElement(ViewContextType.Consumer, null, function (context) {
+ var viewApi = context.viewApi, options = context.options, calendarApi = context.calendarApi;
+ var moreLinkText = options.moreLinkText;
+ var moreCnt = props.moreCnt;
+ var range = computeRange(props);
+ var hookProps = {
+ num: moreCnt,
+ shortText: "+" + moreCnt,
+ text: typeof moreLinkText === 'function'
+ ? moreLinkText.call(calendarApi, moreCnt)
+ : "+" + moreCnt + " " + moreLinkText,
+ view: viewApi,
+ };
+ return (createElement(Fragment, null,
+ Boolean(props.moreCnt) && (createElement(RenderHook, { elRef: _this.linkElRef, hookProps: hookProps, classNames: options.moreLinkClassNames, content: options.moreLinkContent, defaultContent: props.defaultContent || renderMoreLinkInner$1, didMount: options.moreLinkDidMount, willUnmount: options.moreLinkWillUnmount }, function (rootElRef, customClassNames, innerElRef, innerContent) { return props.children(rootElRef, ['fc-more-link'].concat(customClassNames), innerElRef, innerContent, _this.handleClick); })),
+ _this.state.isPopoverOpen && (createElement(MorePopover, { startDate: range.start, endDate: range.end, dateProfile: props.dateProfile, todayRange: props.todayRange, extraDateSpan: props.extraDateSpan, parentEl: _this.parentEl, alignmentEl: props.alignmentElRef.current, alignGridTop: props.alignGridTop, onClose: _this.handlePopoverClose }, props.popoverContent()))));
+ }));
+ };
+ MoreLinkRoot.prototype.componentDidMount = function () {
+ this.updateParentEl();
+ };
+ MoreLinkRoot.prototype.componentDidUpdate = function () {
+ this.updateParentEl();
+ };
+ MoreLinkRoot.prototype.updateParentEl = function () {
+ if (this.linkElRef.current) {
+ this.parentEl = elementClosest(this.linkElRef.current, '.fc-view-harness');
+ }
+ };
+ return MoreLinkRoot;
+ }(BaseComponent));
+ function renderMoreLinkInner$1(props) {
+ return props.text;
+ }
+ function computeRange(props) {
+ if (props.allDayDate) {
+ return {
+ start: props.allDayDate,
+ end: addDays(props.allDayDate, 1),
+ };
+ }
+ var hiddenSegs = props.hiddenSegs;
+ return {
+ start: computeEarliestSegStart(hiddenSegs),
+ end: computeLatestSegEnd(hiddenSegs),
+ };
+ }
+ function computeEarliestSegStart(segs) {
+ return segs.reduce(pickEarliestStart).eventRange.range.start;
+ }
+ function pickEarliestStart(seg0, seg1) {
+ return seg0.eventRange.range.start < seg1.eventRange.range.start ? seg0 : seg1;
+ }
+ function computeLatestSegEnd(segs) {
+ return segs.reduce(pickLatestEnd).eventRange.range.end;
+ }
+ function pickLatestEnd(seg0, seg1) {
+ return seg0.eventRange.range.end > seg1.eventRange.range.end ? seg0 : seg1;
+ }
+
+ // exports
+ // --------------------------------------------------------------------------------------------------
+ var version = '5.9.0'; // important to type it, so .d.ts has generic string
+
+ var Calendar = /** @class */ (function (_super) {
+ __extends(Calendar, _super);
+ function Calendar(el, optionOverrides) {
+ if (optionOverrides === void 0) { optionOverrides = {}; }
+ var _this = _super.call(this) || this;
+ _this.isRendering = false;
+ _this.isRendered = false;
+ _this.currentClassNames = [];
+ _this.customContentRenderId = 0; // will affect custom generated classNames?
+ _this.handleAction = function (action) {
+ // actions we know we want to render immediately
+ switch (action.type) {
+ case 'SET_EVENT_DRAG':
+ case 'SET_EVENT_RESIZE':
+ _this.renderRunner.tryDrain();
+ }
+ };
+ _this.handleData = function (data) {
+ _this.currentData = data;
+ _this.renderRunner.request(data.calendarOptions.rerenderDelay);
+ };
+ _this.handleRenderRequest = function () {
+ if (_this.isRendering) {
+ _this.isRendered = true;
+ var currentData_1 = _this.currentData;
+ render(createElement(CalendarRoot, { options: currentData_1.calendarOptions, theme: currentData_1.theme, emitter: currentData_1.emitter }, function (classNames, height, isHeightAuto, forPrint) {
+ _this.setClassNames(classNames);
+ _this.setHeight(height);
+ return (createElement(CustomContentRenderContext.Provider, { value: _this.customContentRenderId },
+ createElement(CalendarContent, __assign({ isHeightAuto: isHeightAuto, forPrint: forPrint }, currentData_1))));
+ }), _this.el);
+ }
+ else if (_this.isRendered) {
+ _this.isRendered = false;
+ unmountComponentAtNode(_this.el);
+ _this.setClassNames([]);
+ _this.setHeight('');
+ }
+ flushToDom();
+ };
+ _this.el = el;
+ _this.renderRunner = new DelayedRunner(_this.handleRenderRequest);
+ new CalendarDataManager({
+ optionOverrides: optionOverrides,
+ calendarApi: _this,
+ onAction: _this.handleAction,
+ onData: _this.handleData,
+ });
+ return _this;
+ }
+ Object.defineProperty(Calendar.prototype, "view", {
+ get: function () { return this.currentData.viewApi; } // for public API
+ ,
+ enumerable: false,
+ configurable: true
+ });
+ Calendar.prototype.render = function () {
+ var wasRendering = this.isRendering;
+ if (!wasRendering) {
+ this.isRendering = true;
+ }
+ else {
+ this.customContentRenderId += 1;
+ }
+ this.renderRunner.request();
+ if (wasRendering) {
+ this.updateSize();
+ }
+ };
+ Calendar.prototype.destroy = function () {
+ if (this.isRendering) {
+ this.isRendering = false;
+ this.renderRunner.request();
+ }
+ };
+ Calendar.prototype.updateSize = function () {
+ _super.prototype.updateSize.call(this);
+ flushToDom();
+ };
+ Calendar.prototype.batchRendering = function (func) {
+ this.renderRunner.pause('batchRendering');
+ func();
+ this.renderRunner.resume('batchRendering');
+ };
+ Calendar.prototype.pauseRendering = function () {
+ this.renderRunner.pause('pauseRendering');
+ };
+ Calendar.prototype.resumeRendering = function () {
+ this.renderRunner.resume('pauseRendering', true);
+ };
+ Calendar.prototype.resetOptions = function (optionOverrides, append) {
+ this.currentDataManager.resetOptions(optionOverrides, append);
+ };
+ Calendar.prototype.setClassNames = function (classNames) {
+ if (!isArraysEqual(classNames, this.currentClassNames)) {
+ var classList = this.el.classList;
+ for (var _i = 0, _a = this.currentClassNames; _i < _a.length; _i++) {
+ var className = _a[_i];
+ classList.remove(className);
+ }
+ for (var _b = 0, classNames_1 = classNames; _b < classNames_1.length; _b++) {
+ var className = classNames_1[_b];
+ classList.add(className);
+ }
+ this.currentClassNames = classNames;
+ }
+ };
+ Calendar.prototype.setHeight = function (height) {
+ applyStyleProp(this.el, 'height', height);
+ };
+ return Calendar;
+ }(CalendarApi));
+
+ config.touchMouseIgnoreWait = 500;
+ var ignoreMouseDepth = 0;
+ var listenerCnt = 0;
+ var isWindowTouchMoveCancelled = false;
+ /*
+ Uses a "pointer" abstraction, which monitors UI events for both mouse and touch.
+ Tracks when the pointer "drags" on a certain element, meaning down+move+up.
+
+ Also, tracks if there was touch-scrolling.
+ Also, can prevent touch-scrolling from happening.
+ Also, can fire pointermove events when scrolling happens underneath, even when no real pointer movement.
+
+ emits:
+ - pointerdown
+ - pointermove
+ - pointerup
+ */
+ var PointerDragging = /** @class */ (function () {
+ function PointerDragging(containerEl) {
+ var _this = this;
+ this.subjectEl = null;
+ // options that can be directly assigned by caller
+ this.selector = ''; // will cause subjectEl in all emitted events to be this element
+ this.handleSelector = '';
+ this.shouldIgnoreMove = false;
+ this.shouldWatchScroll = true; // for simulating pointermove on scroll
+ // internal states
+ this.isDragging = false;
+ this.isTouchDragging = false;
+ this.wasTouchScroll = false;
+ // Mouse
+ // ----------------------------------------------------------------------------------------------------
+ this.handleMouseDown = function (ev) {
+ if (!_this.shouldIgnoreMouse() &&
+ isPrimaryMouseButton(ev) &&
+ _this.tryStart(ev)) {
+ var pev = _this.createEventFromMouse(ev, true);
+ _this.emitter.trigger('pointerdown', pev);
+ _this.initScrollWatch(pev);
+ if (!_this.shouldIgnoreMove) {
+ document.addEventListener('mousemove', _this.handleMouseMove);
+ }
+ document.addEventListener('mouseup', _this.handleMouseUp);
+ }
+ };
+ this.handleMouseMove = function (ev) {
+ var pev = _this.createEventFromMouse(ev);
+ _this.recordCoords(pev);
+ _this.emitter.trigger('pointermove', pev);
+ };
+ this.handleMouseUp = function (ev) {
+ document.removeEventListener('mousemove', _this.handleMouseMove);
+ document.removeEventListener('mouseup', _this.handleMouseUp);
+ _this.emitter.trigger('pointerup', _this.createEventFromMouse(ev));
+ _this.cleanup(); // call last so that pointerup has access to props
+ };
+ // Touch
+ // ----------------------------------------------------------------------------------------------------
+ this.handleTouchStart = function (ev) {
+ if (_this.tryStart(ev)) {
+ _this.isTouchDragging = true;
+ var pev = _this.createEventFromTouch(ev, true);
+ _this.emitter.trigger('pointerdown', pev);
+ _this.initScrollWatch(pev);
+ // unlike mouse, need to attach to target, not document
+ // https://stackoverflow.com/a/45760014
+ var targetEl = ev.target;
+ if (!_this.shouldIgnoreMove) {
+ targetEl.addEventListener('touchmove', _this.handleTouchMove);
+ }
+ targetEl.addEventListener('touchend', _this.handleTouchEnd);
+ targetEl.addEventListener('touchcancel', _this.handleTouchEnd); // treat it as a touch end
+ // attach a handler to get called when ANY scroll action happens on the page.
+ // this was impossible to do with normal on/off because 'scroll' doesn't bubble.
+ // http://stackoverflow.com/a/32954565/96342
+ window.addEventListener('scroll', _this.handleTouchScroll, true);
+ }
+ };
+ this.handleTouchMove = function (ev) {
+ var pev = _this.createEventFromTouch(ev);
+ _this.recordCoords(pev);
+ _this.emitter.trigger('pointermove', pev);
+ };
+ this.handleTouchEnd = function (ev) {
+ if (_this.isDragging) { // done to guard against touchend followed by touchcancel
+ var targetEl = ev.target;
+ targetEl.removeEventListener('touchmove', _this.handleTouchMove);
+ targetEl.removeEventListener('touchend', _this.handleTouchEnd);
+ targetEl.removeEventListener('touchcancel', _this.handleTouchEnd);
+ window.removeEventListener('scroll', _this.handleTouchScroll, true); // useCaptured=true
+ _this.emitter.trigger('pointerup', _this.createEventFromTouch(ev));
+ _this.cleanup(); // call last so that pointerup has access to props
+ _this.isTouchDragging = false;
+ startIgnoringMouse();
+ }
+ };
+ this.handleTouchScroll = function () {
+ _this.wasTouchScroll = true;
+ };
+ this.handleScroll = function (ev) {
+ if (!_this.shouldIgnoreMove) {
+ var pageX = (window.pageXOffset - _this.prevScrollX) + _this.prevPageX;
+ var pageY = (window.pageYOffset - _this.prevScrollY) + _this.prevPageY;
+ _this.emitter.trigger('pointermove', {
+ origEvent: ev,
+ isTouch: _this.isTouchDragging,
+ subjectEl: _this.subjectEl,
+ pageX: pageX,
+ pageY: pageY,
+ deltaX: pageX - _this.origPageX,
+ deltaY: pageY - _this.origPageY,
+ });
+ }
+ };
+ this.containerEl = containerEl;
+ this.emitter = new Emitter();
+ containerEl.addEventListener('mousedown', this.handleMouseDown);
+ containerEl.addEventListener('touchstart', this.handleTouchStart, { passive: true });
+ listenerCreated();
+ }
+ PointerDragging.prototype.destroy = function () {
+ this.containerEl.removeEventListener('mousedown', this.handleMouseDown);
+ this.containerEl.removeEventListener('touchstart', this.handleTouchStart, { passive: true });
+ listenerDestroyed();
+ };
+ PointerDragging.prototype.tryStart = function (ev) {
+ var subjectEl = this.querySubjectEl(ev);
+ var downEl = ev.target;
+ if (subjectEl &&
+ (!this.handleSelector || elementClosest(downEl, this.handleSelector))) {
+ this.subjectEl = subjectEl;
+ this.isDragging = true; // do this first so cancelTouchScroll will work
+ this.wasTouchScroll = false;
+ return true;
+ }
+ return false;
+ };
+ PointerDragging.prototype.cleanup = function () {
+ isWindowTouchMoveCancelled = false;
+ this.isDragging = false;
+ this.subjectEl = null;
+ // keep wasTouchScroll around for later access
+ this.destroyScrollWatch();
+ };
+ PointerDragging.prototype.querySubjectEl = function (ev) {
+ if (this.selector) {
+ return elementClosest(ev.target, this.selector);
+ }
+ return this.containerEl;
+ };
+ PointerDragging.prototype.shouldIgnoreMouse = function () {
+ return ignoreMouseDepth || this.isTouchDragging;
+ };
+ // can be called by user of this class, to cancel touch-based scrolling for the current drag
+ PointerDragging.prototype.cancelTouchScroll = function () {
+ if (this.isDragging) {
+ isWindowTouchMoveCancelled = true;
+ }
+ };
+ // Scrolling that simulates pointermoves
+ // ----------------------------------------------------------------------------------------------------
+ PointerDragging.prototype.initScrollWatch = function (ev) {
+ if (this.shouldWatchScroll) {
+ this.recordCoords(ev);
+ window.addEventListener('scroll', this.handleScroll, true); // useCapture=true
+ }
+ };
+ PointerDragging.prototype.recordCoords = function (ev) {
+ if (this.shouldWatchScroll) {
+ this.prevPageX = ev.pageX;
+ this.prevPageY = ev.pageY;
+ this.prevScrollX = window.pageXOffset;
+ this.prevScrollY = window.pageYOffset;
+ }
+ };
+ PointerDragging.prototype.destroyScrollWatch = function () {
+ if (this.shouldWatchScroll) {
+ window.removeEventListener('scroll', this.handleScroll, true); // useCaptured=true
+ }
+ };
+ // Event Normalization
+ // ----------------------------------------------------------------------------------------------------
+ PointerDragging.prototype.createEventFromMouse = function (ev, isFirst) {
+ var deltaX = 0;
+ var deltaY = 0;
+ // TODO: repeat code
+ if (isFirst) {
+ this.origPageX = ev.pageX;
+ this.origPageY = ev.pageY;
+ }
+ else {
+ deltaX = ev.pageX - this.origPageX;
+ deltaY = ev.pageY - this.origPageY;
+ }
+ return {
+ origEvent: ev,
+ isTouch: false,
+ subjectEl: this.subjectEl,
+ pageX: ev.pageX,
+ pageY: ev.pageY,
+ deltaX: deltaX,
+ deltaY: deltaY,
+ };
+ };
+ PointerDragging.prototype.createEventFromTouch = function (ev, isFirst) {
+ var touches = ev.touches;
+ var pageX;
+ var pageY;
+ var deltaX = 0;
+ var deltaY = 0;
+ // if touch coords available, prefer,
+ // because FF would give bad ev.pageX ev.pageY
+ if (touches && touches.length) {
+ pageX = touches[0].pageX;
+ pageY = touches[0].pageY;
+ }
+ else {
+ pageX = ev.pageX;
+ pageY = ev.pageY;
+ }
+ // TODO: repeat code
+ if (isFirst) {
+ this.origPageX = pageX;
+ this.origPageY = pageY;
+ }
+ else {
+ deltaX = pageX - this.origPageX;
+ deltaY = pageY - this.origPageY;
+ }
+ return {
+ origEvent: ev,
+ isTouch: true,
+ subjectEl: this.subjectEl,
+ pageX: pageX,
+ pageY: pageY,
+ deltaX: deltaX,
+ deltaY: deltaY,
+ };
+ };
+ return PointerDragging;
+ }());
+ // Returns a boolean whether this was a left mouse click and no ctrl key (which means right click on Mac)
+ function isPrimaryMouseButton(ev) {
+ return ev.button === 0 && !ev.ctrlKey;
+ }
+ // Ignoring fake mouse events generated by touch
+ // ----------------------------------------------------------------------------------------------------
+ function startIgnoringMouse() {
+ ignoreMouseDepth += 1;
+ setTimeout(function () {
+ ignoreMouseDepth -= 1;
+ }, config.touchMouseIgnoreWait);
+ }
+ // We want to attach touchmove as early as possible for Safari
+ // ----------------------------------------------------------------------------------------------------
+ function listenerCreated() {
+ listenerCnt += 1;
+ if (listenerCnt === 1) {
+ window.addEventListener('touchmove', onWindowTouchMove, { passive: false });
+ }
+ }
+ function listenerDestroyed() {
+ listenerCnt -= 1;
+ if (!listenerCnt) {
+ window.removeEventListener('touchmove', onWindowTouchMove, { passive: false });
+ }
+ }
+ function onWindowTouchMove(ev) {
+ if (isWindowTouchMoveCancelled) {
+ ev.preventDefault();
+ }
+ }
+
+ /*
+ An effect in which an element follows the movement of a pointer across the screen.
+ The moving element is a clone of some other element.
+ Must call start + handleMove + stop.
+ */
+ var ElementMirror = /** @class */ (function () {
+ function ElementMirror() {
+ this.isVisible = false; // must be explicitly enabled
+ this.sourceEl = null;
+ this.mirrorEl = null;
+ this.sourceElRect = null; // screen coords relative to viewport
+ // options that can be set directly by caller
+ this.parentNode = document.body; // HIGHLY SUGGESTED to set this to sidestep ShadowDOM issues
+ this.zIndex = 9999;
+ this.revertDuration = 0;
+ }
+ ElementMirror.prototype.start = function (sourceEl, pageX, pageY) {
+ this.sourceEl = sourceEl;
+ this.sourceElRect = this.sourceEl.getBoundingClientRect();
+ this.origScreenX = pageX - window.pageXOffset;
+ this.origScreenY = pageY - window.pageYOffset;
+ this.deltaX = 0;
+ this.deltaY = 0;
+ this.updateElPosition();
+ };
+ ElementMirror.prototype.handleMove = function (pageX, pageY) {
+ this.deltaX = (pageX - window.pageXOffset) - this.origScreenX;
+ this.deltaY = (pageY - window.pageYOffset) - this.origScreenY;
+ this.updateElPosition();
+ };
+ // can be called before start
+ ElementMirror.prototype.setIsVisible = function (bool) {
+ if (bool) {
+ if (!this.isVisible) {
+ if (this.mirrorEl) {
+ this.mirrorEl.style.display = '';
+ }
+ this.isVisible = bool; // needs to happen before updateElPosition
+ this.updateElPosition(); // because was not updating the position while invisible
+ }
+ }
+ else if (this.isVisible) {
+ if (this.mirrorEl) {
+ this.mirrorEl.style.display = 'none';
+ }
+ this.isVisible = bool;
+ }
+ };
+ // always async
+ ElementMirror.prototype.stop = function (needsRevertAnimation, callback) {
+ var _this = this;
+ var done = function () {
+ _this.cleanup();
+ callback();
+ };
+ if (needsRevertAnimation &&
+ this.mirrorEl &&
+ this.isVisible &&
+ this.revertDuration && // if 0, transition won't work
+ (this.deltaX || this.deltaY) // if same coords, transition won't work
+ ) {
+ this.doRevertAnimation(done, this.revertDuration);
+ }
+ else {
+ setTimeout(done, 0);
+ }
+ };
+ ElementMirror.prototype.doRevertAnimation = function (callback, revertDuration) {
+ var mirrorEl = this.mirrorEl;
+ var finalSourceElRect = this.sourceEl.getBoundingClientRect(); // because autoscrolling might have happened
+ mirrorEl.style.transition =
+ 'top ' + revertDuration + 'ms,' +
+ 'left ' + revertDuration + 'ms';
+ applyStyle(mirrorEl, {
+ left: finalSourceElRect.left,
+ top: finalSourceElRect.top,
+ });
+ whenTransitionDone(mirrorEl, function () {
+ mirrorEl.style.transition = '';
+ callback();
+ });
+ };
+ ElementMirror.prototype.cleanup = function () {
+ if (this.mirrorEl) {
+ removeElement(this.mirrorEl);
+ this.mirrorEl = null;
+ }
+ this.sourceEl = null;
+ };
+ ElementMirror.prototype.updateElPosition = function () {
+ if (this.sourceEl && this.isVisible) {
+ applyStyle(this.getMirrorEl(), {
+ left: this.sourceElRect.left + this.deltaX,
+ top: this.sourceElRect.top + this.deltaY,
+ });
+ }
+ };
+ ElementMirror.prototype.getMirrorEl = function () {
+ var sourceElRect = this.sourceElRect;
+ var mirrorEl = this.mirrorEl;
+ if (!mirrorEl) {
+ mirrorEl = this.mirrorEl = this.sourceEl.cloneNode(true); // cloneChildren=true
+ // we don't want long taps or any mouse interaction causing selection/menus.
+ // would use preventSelection(), but that prevents selectstart, causing problems.
+ mirrorEl.classList.add('fc-unselectable');
+ mirrorEl.classList.add('fc-event-dragging');
+ applyStyle(mirrorEl, {
+ position: 'fixed',
+ zIndex: this.zIndex,
+ visibility: '',
+ boxSizing: 'border-box',
+ width: sourceElRect.right - sourceElRect.left,
+ height: sourceElRect.bottom - sourceElRect.top,
+ right: 'auto',
+ bottom: 'auto',
+ margin: 0,
+ });
+ this.parentNode.appendChild(mirrorEl);
+ }
+ return mirrorEl;
+ };
+ return ElementMirror;
+ }());
+
+ /*
+ Is a cache for a given element's scroll information (all the info that ScrollController stores)
+ in addition the "client rectangle" of the element.. the area within the scrollbars.
+
+ The cache can be in one of two modes:
+ - doesListening:false - ignores when the container is scrolled by someone else
+ - doesListening:true - watch for scrolling and update the cache
+ */
+ var ScrollGeomCache = /** @class */ (function (_super) {
+ __extends(ScrollGeomCache, _super);
+ function ScrollGeomCache(scrollController, doesListening) {
+ var _this = _super.call(this) || this;
+ _this.handleScroll = function () {
+ _this.scrollTop = _this.scrollController.getScrollTop();
+ _this.scrollLeft = _this.scrollController.getScrollLeft();
+ _this.handleScrollChange();
+ };
+ _this.scrollController = scrollController;
+ _this.doesListening = doesListening;
+ _this.scrollTop = _this.origScrollTop = scrollController.getScrollTop();
+ _this.scrollLeft = _this.origScrollLeft = scrollController.getScrollLeft();
+ _this.scrollWidth = scrollController.getScrollWidth();
+ _this.scrollHeight = scrollController.getScrollHeight();
+ _this.clientWidth = scrollController.getClientWidth();
+ _this.clientHeight = scrollController.getClientHeight();
+ _this.clientRect = _this.computeClientRect(); // do last in case it needs cached values
+ if (_this.doesListening) {
+ _this.getEventTarget().addEventListener('scroll', _this.handleScroll);
+ }
+ return _this;
+ }
+ ScrollGeomCache.prototype.destroy = function () {
+ if (this.doesListening) {
+ this.getEventTarget().removeEventListener('scroll', this.handleScroll);
+ }
+ };
+ ScrollGeomCache.prototype.getScrollTop = function () {
+ return this.scrollTop;
+ };
+ ScrollGeomCache.prototype.getScrollLeft = function () {
+ return this.scrollLeft;
+ };
+ ScrollGeomCache.prototype.setScrollTop = function (top) {
+ this.scrollController.setScrollTop(top);
+ if (!this.doesListening) {
+ // we are not relying on the element to normalize out-of-bounds scroll values
+ // so we need to sanitize ourselves
+ this.scrollTop = Math.max(Math.min(top, this.getMaxScrollTop()), 0);
+ this.handleScrollChange();
+ }
+ };
+ ScrollGeomCache.prototype.setScrollLeft = function (top) {
+ this.scrollController.setScrollLeft(top);
+ if (!this.doesListening) {
+ // we are not relying on the element to normalize out-of-bounds scroll values
+ // so we need to sanitize ourselves
+ this.scrollLeft = Math.max(Math.min(top, this.getMaxScrollLeft()), 0);
+ this.handleScrollChange();
+ }
+ };
+ ScrollGeomCache.prototype.getClientWidth = function () {
+ return this.clientWidth;
+ };
+ ScrollGeomCache.prototype.getClientHeight = function () {
+ return this.clientHeight;
+ };
+ ScrollGeomCache.prototype.getScrollWidth = function () {
+ return this.scrollWidth;
+ };
+ ScrollGeomCache.prototype.getScrollHeight = function () {
+ return this.scrollHeight;
+ };
+ ScrollGeomCache.prototype.handleScrollChange = function () {
+ };
+ return ScrollGeomCache;
+ }(ScrollController));
+
+ var ElementScrollGeomCache = /** @class */ (function (_super) {
+ __extends(ElementScrollGeomCache, _super);
+ function ElementScrollGeomCache(el, doesListening) {
+ return _super.call(this, new ElementScrollController(el), doesListening) || this;
+ }
+ ElementScrollGeomCache.prototype.getEventTarget = function () {
+ return this.scrollController.el;
+ };
+ ElementScrollGeomCache.prototype.computeClientRect = function () {
+ return computeInnerRect(this.scrollController.el);
+ };
+ return ElementScrollGeomCache;
+ }(ScrollGeomCache));
+
+ var WindowScrollGeomCache = /** @class */ (function (_super) {
+ __extends(WindowScrollGeomCache, _super);
+ function WindowScrollGeomCache(doesListening) {
+ return _super.call(this, new WindowScrollController(), doesListening) || this;
+ }
+ WindowScrollGeomCache.prototype.getEventTarget = function () {
+ return window;
+ };
+ WindowScrollGeomCache.prototype.computeClientRect = function () {
+ return {
+ left: this.scrollLeft,
+ right: this.scrollLeft + this.clientWidth,
+ top: this.scrollTop,
+ bottom: this.scrollTop + this.clientHeight,
+ };
+ };
+ // the window is the only scroll object that changes it's rectangle relative
+ // to the document's topleft as it scrolls
+ WindowScrollGeomCache.prototype.handleScrollChange = function () {
+ this.clientRect = this.computeClientRect();
+ };
+ return WindowScrollGeomCache;
+ }(ScrollGeomCache));
+
+ // If available we are using native "performance" API instead of "Date"
+ // Read more about it on MDN:
+ // https://developer.mozilla.org/en-US/docs/Web/API/Performance
+ var getTime = typeof performance === 'function' ? performance.now : Date.now;
+ /*
+ For a pointer interaction, automatically scrolls certain scroll containers when the pointer
+ approaches the edge.
+
+ The caller must call start + handleMove + stop.
+ */
+ var AutoScroller = /** @class */ (function () {
+ function AutoScroller() {
+ var _this = this;
+ // options that can be set by caller
+ this.isEnabled = true;
+ this.scrollQuery = [window, '.fc-scroller'];
+ this.edgeThreshold = 50; // pixels
+ this.maxVelocity = 300; // pixels per second
+ // internal state
+ this.pointerScreenX = null;
+ this.pointerScreenY = null;
+ this.isAnimating = false;
+ this.scrollCaches = null;
+ // protect against the initial pointerdown being too close to an edge and starting the scroll
+ this.everMovedUp = false;
+ this.everMovedDown = false;
+ this.everMovedLeft = false;
+ this.everMovedRight = false;
+ this.animate = function () {
+ if (_this.isAnimating) { // wasn't cancelled between animation calls
+ var edge = _this.computeBestEdge(_this.pointerScreenX + window.pageXOffset, _this.pointerScreenY + window.pageYOffset);
+ if (edge) {
+ var now = getTime();
+ _this.handleSide(edge, (now - _this.msSinceRequest) / 1000);
+ _this.requestAnimation(now);
+ }
+ else {
+ _this.isAnimating = false; // will stop animation
+ }
+ }
+ };
+ }
+ AutoScroller.prototype.start = function (pageX, pageY, scrollStartEl) {
+ if (this.isEnabled) {
+ this.scrollCaches = this.buildCaches(scrollStartEl);
+ this.pointerScreenX = null;
+ this.pointerScreenY = null;
+ this.everMovedUp = false;
+ this.everMovedDown = false;
+ this.everMovedLeft = false;
+ this.everMovedRight = false;
+ this.handleMove(pageX, pageY);
+ }
+ };
+ AutoScroller.prototype.handleMove = function (pageX, pageY) {
+ if (this.isEnabled) {
+ var pointerScreenX = pageX - window.pageXOffset;
+ var pointerScreenY = pageY - window.pageYOffset;
+ var yDelta = this.pointerScreenY === null ? 0 : pointerScreenY - this.pointerScreenY;
+ var xDelta = this.pointerScreenX === null ? 0 : pointerScreenX - this.pointerScreenX;
+ if (yDelta < 0) {
+ this.everMovedUp = true;
+ }
+ else if (yDelta > 0) {
+ this.everMovedDown = true;
+ }
+ if (xDelta < 0) {
+ this.everMovedLeft = true;
+ }
+ else if (xDelta > 0) {
+ this.everMovedRight = true;
+ }
+ this.pointerScreenX = pointerScreenX;
+ this.pointerScreenY = pointerScreenY;
+ if (!this.isAnimating) {
+ this.isAnimating = true;
+ this.requestAnimation(getTime());
+ }
+ }
+ };
+ AutoScroller.prototype.stop = function () {
+ if (this.isEnabled) {
+ this.isAnimating = false; // will stop animation
+ for (var _i = 0, _a = this.scrollCaches; _i < _a.length; _i++) {
+ var scrollCache = _a[_i];
+ scrollCache.destroy();
+ }
+ this.scrollCaches = null;
+ }
+ };
+ AutoScroller.prototype.requestAnimation = function (now) {
+ this.msSinceRequest = now;
+ requestAnimationFrame(this.animate);
+ };
+ AutoScroller.prototype.handleSide = function (edge, seconds) {
+ var scrollCache = edge.scrollCache;
+ var edgeThreshold = this.edgeThreshold;
+ var invDistance = edgeThreshold - edge.distance;
+ var velocity = // the closer to the edge, the faster we scroll
+ ((invDistance * invDistance) / (edgeThreshold * edgeThreshold)) * // quadratic
+ this.maxVelocity * seconds;
+ var sign = 1;
+ switch (edge.name) {
+ case 'left':
+ sign = -1;
+ // falls through
+ case 'right':
+ scrollCache.setScrollLeft(scrollCache.getScrollLeft() + velocity * sign);
+ break;
+ case 'top':
+ sign = -1;
+ // falls through
+ case 'bottom':
+ scrollCache.setScrollTop(scrollCache.getScrollTop() + velocity * sign);
+ break;
+ }
+ };
+ // left/top are relative to document topleft
+ AutoScroller.prototype.computeBestEdge = function (left, top) {
+ var edgeThreshold = this.edgeThreshold;
+ var bestSide = null;
+ for (var _i = 0, _a = this.scrollCaches; _i < _a.length; _i++) {
+ var scrollCache = _a[_i];
+ var rect = scrollCache.clientRect;
+ var leftDist = left - rect.left;
+ var rightDist = rect.right - left;
+ var topDist = top - rect.top;
+ var bottomDist = rect.bottom - top;
+ // completely within the rect?
+ if (leftDist >= 0 && rightDist >= 0 && topDist >= 0 && bottomDist >= 0) {
+ if (topDist <= edgeThreshold && this.everMovedUp && scrollCache.canScrollUp() &&
+ (!bestSide || bestSide.distance > topDist)) {
+ bestSide = { scrollCache: scrollCache, name: 'top', distance: topDist };
+ }
+ if (bottomDist <= edgeThreshold && this.everMovedDown && scrollCache.canScrollDown() &&
+ (!bestSide || bestSide.distance > bottomDist)) {
+ bestSide = { scrollCache: scrollCache, name: 'bottom', distance: bottomDist };
+ }
+ if (leftDist <= edgeThreshold && this.everMovedLeft && scrollCache.canScrollLeft() &&
+ (!bestSide || bestSide.distance > leftDist)) {
+ bestSide = { scrollCache: scrollCache, name: 'left', distance: leftDist };
+ }
+ if (rightDist <= edgeThreshold && this.everMovedRight && scrollCache.canScrollRight() &&
+ (!bestSide || bestSide.distance > rightDist)) {
+ bestSide = { scrollCache: scrollCache, name: 'right', distance: rightDist };
+ }
+ }
+ }
+ return bestSide;
+ };
+ AutoScroller.prototype.buildCaches = function (scrollStartEl) {
+ return this.queryScrollEls(scrollStartEl).map(function (el) {
+ if (el === window) {
+ return new WindowScrollGeomCache(false); // false = don't listen to user-generated scrolls
+ }
+ return new ElementScrollGeomCache(el, false); // false = don't listen to user-generated scrolls
+ });
+ };
+ AutoScroller.prototype.queryScrollEls = function (scrollStartEl) {
+ var els = [];
+ for (var _i = 0, _a = this.scrollQuery; _i < _a.length; _i++) {
+ var query = _a[_i];
+ if (typeof query === 'object') {
+ els.push(query);
+ }
+ else {
+ els.push.apply(els, Array.prototype.slice.call(getElRoot(scrollStartEl).querySelectorAll(query)));
+ }
+ }
+ return els;
+ };
+ return AutoScroller;
+ }());
+
+ /*
+ Monitors dragging on an element. Has a number of high-level features:
+ - minimum distance required before dragging
+ - minimum wait time ("delay") before dragging
+ - a mirror element that follows the pointer
+ */
+ var FeaturefulElementDragging = /** @class */ (function (_super) {
+ __extends(FeaturefulElementDragging, _super);
+ function FeaturefulElementDragging(containerEl, selector) {
+ var _this = _super.call(this, containerEl) || this;
+ _this.containerEl = containerEl;
+ // options that can be directly set by caller
+ // the caller can also set the PointerDragging's options as well
+ _this.delay = null;
+ _this.minDistance = 0;
+ _this.touchScrollAllowed = true; // prevents drag from starting and blocks scrolling during drag
+ _this.mirrorNeedsRevert = false;
+ _this.isInteracting = false; // is the user validly moving the pointer? lasts until pointerup
+ _this.isDragging = false; // is it INTENTFULLY dragging? lasts until after revert animation
+ _this.isDelayEnded = false;
+ _this.isDistanceSurpassed = false;
+ _this.delayTimeoutId = null;
+ _this.onPointerDown = function (ev) {
+ if (!_this.isDragging) { // so new drag doesn't happen while revert animation is going
+ _this.isInteracting = true;
+ _this.isDelayEnded = false;
+ _this.isDistanceSurpassed = false;
+ preventSelection(document.body);
+ preventContextMenu(document.body);
+ // prevent links from being visited if there's an eventual drag.
+ // also prevents selection in older browsers (maybe?).
+ // not necessary for touch, besides, browser would complain about passiveness.
+ if (!ev.isTouch) {
+ ev.origEvent.preventDefault();
+ }
+ _this.emitter.trigger('pointerdown', ev);
+ if (_this.isInteracting && // not destroyed via pointerdown handler
+ !_this.pointer.shouldIgnoreMove) {
+ // actions related to initiating dragstart+dragmove+dragend...
+ _this.mirror.setIsVisible(false); // reset. caller must set-visible
+ _this.mirror.start(ev.subjectEl, ev.pageX, ev.pageY); // must happen on first pointer down
+ _this.startDelay(ev);
+ if (!_this.minDistance) {
+ _this.handleDistanceSurpassed(ev);
+ }
+ }
+ }
+ };
+ _this.onPointerMove = function (ev) {
+ if (_this.isInteracting) {
+ _this.emitter.trigger('pointermove', ev);
+ if (!_this.isDistanceSurpassed) {
+ var minDistance = _this.minDistance;
+ var distanceSq = void 0; // current distance from the origin, squared
+ var deltaX = ev.deltaX, deltaY = ev.deltaY;
+ distanceSq = deltaX * deltaX + deltaY * deltaY;
+ if (distanceSq >= minDistance * minDistance) { // use pythagorean theorem
+ _this.handleDistanceSurpassed(ev);
+ }
+ }
+ if (_this.isDragging) {
+ // a real pointer move? (not one simulated by scrolling)
+ if (ev.origEvent.type !== 'scroll') {
+ _this.mirror.handleMove(ev.pageX, ev.pageY);
+ _this.autoScroller.handleMove(ev.pageX, ev.pageY);
+ }
+ _this.emitter.trigger('dragmove', ev);
+ }
+ }
+ };
+ _this.onPointerUp = function (ev) {
+ if (_this.isInteracting) {
+ _this.isInteracting = false;
+ allowSelection(document.body);
+ allowContextMenu(document.body);
+ _this.emitter.trigger('pointerup', ev); // can potentially set mirrorNeedsRevert
+ if (_this.isDragging) {
+ _this.autoScroller.stop();
+ _this.tryStopDrag(ev); // which will stop the mirror
+ }
+ if (_this.delayTimeoutId) {
+ clearTimeout(_this.delayTimeoutId);
+ _this.delayTimeoutId = null;
+ }
+ }
+ };
+ var pointer = _this.pointer = new PointerDragging(containerEl);
+ pointer.emitter.on('pointerdown', _this.onPointerDown);
+ pointer.emitter.on('pointermove', _this.onPointerMove);
+ pointer.emitter.on('pointerup', _this.onPointerUp);
+ if (selector) {
+ pointer.selector = selector;
+ }
+ _this.mirror = new ElementMirror();
+ _this.autoScroller = new AutoScroller();
+ return _this;
+ }
+ FeaturefulElementDragging.prototype.destroy = function () {
+ this.pointer.destroy();
+ // HACK: simulate a pointer-up to end the current drag
+ // TODO: fire 'dragend' directly and stop interaction. discourage use of pointerup event (b/c might not fire)
+ this.onPointerUp({});
+ };
+ FeaturefulElementDragging.prototype.startDelay = function (ev) {
+ var _this = this;
+ if (typeof this.delay === 'number') {
+ this.delayTimeoutId = setTimeout(function () {
+ _this.delayTimeoutId = null;
+ _this.handleDelayEnd(ev);
+ }, this.delay); // not assignable to number!
+ }
+ else {
+ this.handleDelayEnd(ev);
+ }
+ };
+ FeaturefulElementDragging.prototype.handleDelayEnd = function (ev) {
+ this.isDelayEnded = true;
+ this.tryStartDrag(ev);
+ };
+ FeaturefulElementDragging.prototype.handleDistanceSurpassed = function (ev) {
+ this.isDistanceSurpassed = true;
+ this.tryStartDrag(ev);
+ };
+ FeaturefulElementDragging.prototype.tryStartDrag = function (ev) {
+ if (this.isDelayEnded && this.isDistanceSurpassed) {
+ if (!this.pointer.wasTouchScroll || this.touchScrollAllowed) {
+ this.isDragging = true;
+ this.mirrorNeedsRevert = false;
+ this.autoScroller.start(ev.pageX, ev.pageY, this.containerEl);
+ this.emitter.trigger('dragstart', ev);
+ if (this.touchScrollAllowed === false) {
+ this.pointer.cancelTouchScroll();
+ }
+ }
+ }
+ };
+ FeaturefulElementDragging.prototype.tryStopDrag = function (ev) {
+ // .stop() is ALWAYS asynchronous, which we NEED because we want all pointerup events
+ // that come from the document to fire beforehand. much more convenient this way.
+ this.mirror.stop(this.mirrorNeedsRevert, this.stopDrag.bind(this, ev));
+ };
+ FeaturefulElementDragging.prototype.stopDrag = function (ev) {
+ this.isDragging = false;
+ this.emitter.trigger('dragend', ev);
+ };
+ // fill in the implementations...
+ FeaturefulElementDragging.prototype.setIgnoreMove = function (bool) {
+ this.pointer.shouldIgnoreMove = bool;
+ };
+ FeaturefulElementDragging.prototype.setMirrorIsVisible = function (bool) {
+ this.mirror.setIsVisible(bool);
+ };
+ FeaturefulElementDragging.prototype.setMirrorNeedsRevert = function (bool) {
+ this.mirrorNeedsRevert = bool;
+ };
+ FeaturefulElementDragging.prototype.setAutoScrollEnabled = function (bool) {
+ this.autoScroller.isEnabled = bool;
+ };
+ return FeaturefulElementDragging;
+ }(ElementDragging));
+
+ /*
+ When this class is instantiated, it records the offset of an element (relative to the document topleft),
+ and continues to monitor scrolling, updating the cached coordinates if it needs to.
+ Does not access the DOM after instantiation, so highly performant.
+
+ Also keeps track of all scrolling/overflow:hidden containers that are parents of the given element
+ and an determine if a given point is inside the combined clipping rectangle.
+ */
+ var OffsetTracker = /** @class */ (function () {
+ function OffsetTracker(el) {
+ this.origRect = computeRect(el);
+ // will work fine for divs that have overflow:hidden
+ this.scrollCaches = getClippingParents(el).map(function (scrollEl) { return new ElementScrollGeomCache(scrollEl, true); });
+ }
+ OffsetTracker.prototype.destroy = function () {
+ for (var _i = 0, _a = this.scrollCaches; _i < _a.length; _i++) {
+ var scrollCache = _a[_i];
+ scrollCache.destroy();
+ }
+ };
+ OffsetTracker.prototype.computeLeft = function () {
+ var left = this.origRect.left;
+ for (var _i = 0, _a = this.scrollCaches; _i < _a.length; _i++) {
+ var scrollCache = _a[_i];
+ left += scrollCache.origScrollLeft - scrollCache.getScrollLeft();
+ }
+ return left;
+ };
+ OffsetTracker.prototype.computeTop = function () {
+ var top = this.origRect.top;
+ for (var _i = 0, _a = this.scrollCaches; _i < _a.length; _i++) {
+ var scrollCache = _a[_i];
+ top += scrollCache.origScrollTop - scrollCache.getScrollTop();
+ }
+ return top;
+ };
+ OffsetTracker.prototype.isWithinClipping = function (pageX, pageY) {
+ var point = { left: pageX, top: pageY };
+ for (var _i = 0, _a = this.scrollCaches; _i < _a.length; _i++) {
+ var scrollCache = _a[_i];
+ if (!isIgnoredClipping(scrollCache.getEventTarget()) &&
+ !pointInsideRect(point, scrollCache.clientRect)) {
+ return false;
+ }
+ }
+ return true;
+ };
+ return OffsetTracker;
+ }());
+ // certain clipping containers should never constrain interactions, like and
+ // https://github.com/fullcalendar/fullcalendar/issues/3615
+ function isIgnoredClipping(node) {
+ var tagName = node.tagName;
+ return tagName === 'HTML' || tagName === 'BODY';
+ }
+
+ /*
+ Tracks movement over multiple droppable areas (aka "hits")
+ that exist in one or more DateComponents.
+ Relies on an existing draggable.
+
+ emits:
+ - pointerdown
+ - dragstart
+ - hitchange - fires initially, even if not over a hit
+ - pointerup
+ - (hitchange - again, to null, if ended over a hit)
+ - dragend
+ */
+ var HitDragging = /** @class */ (function () {
+ function HitDragging(dragging, droppableStore) {
+ var _this = this;
+ // options that can be set by caller
+ this.useSubjectCenter = false;
+ this.requireInitial = true; // if doesn't start out on a hit, won't emit any events
+ this.initialHit = null;
+ this.movingHit = null;
+ this.finalHit = null; // won't ever be populated if shouldIgnoreMove
+ this.handlePointerDown = function (ev) {
+ var dragging = _this.dragging;
+ _this.initialHit = null;
+ _this.movingHit = null;
+ _this.finalHit = null;
+ _this.prepareHits();
+ _this.processFirstCoord(ev);
+ if (_this.initialHit || !_this.requireInitial) {
+ dragging.setIgnoreMove(false);
+ // TODO: fire this before computing processFirstCoord, so listeners can cancel. this gets fired by almost every handler :(
+ _this.emitter.trigger('pointerdown', ev);
+ }
+ else {
+ dragging.setIgnoreMove(true);
+ }
+ };
+ this.handleDragStart = function (ev) {
+ _this.emitter.trigger('dragstart', ev);
+ _this.handleMove(ev, true); // force = fire even if initially null
+ };
+ this.handleDragMove = function (ev) {
+ _this.emitter.trigger('dragmove', ev);
+ _this.handleMove(ev);
+ };
+ this.handlePointerUp = function (ev) {
+ _this.releaseHits();
+ _this.emitter.trigger('pointerup', ev);
+ };
+ this.handleDragEnd = function (ev) {
+ if (_this.movingHit) {
+ _this.emitter.trigger('hitupdate', null, true, ev);
+ }
+ _this.finalHit = _this.movingHit;
+ _this.movingHit = null;
+ _this.emitter.trigger('dragend', ev);
+ };
+ this.droppableStore = droppableStore;
+ dragging.emitter.on('pointerdown', this.handlePointerDown);
+ dragging.emitter.on('dragstart', this.handleDragStart);
+ dragging.emitter.on('dragmove', this.handleDragMove);
+ dragging.emitter.on('pointerup', this.handlePointerUp);
+ dragging.emitter.on('dragend', this.handleDragEnd);
+ this.dragging = dragging;
+ this.emitter = new Emitter();
+ }
+ // sets initialHit
+ // sets coordAdjust
+ HitDragging.prototype.processFirstCoord = function (ev) {
+ var origPoint = { left: ev.pageX, top: ev.pageY };
+ var adjustedPoint = origPoint;
+ var subjectEl = ev.subjectEl;
+ var subjectRect;
+ if (subjectEl instanceof HTMLElement) { // i.e. not a Document/ShadowRoot
+ subjectRect = computeRect(subjectEl);
+ adjustedPoint = constrainPoint(adjustedPoint, subjectRect);
+ }
+ var initialHit = this.initialHit = this.queryHitForOffset(adjustedPoint.left, adjustedPoint.top);
+ if (initialHit) {
+ if (this.useSubjectCenter && subjectRect) {
+ var slicedSubjectRect = intersectRects(subjectRect, initialHit.rect);
+ if (slicedSubjectRect) {
+ adjustedPoint = getRectCenter(slicedSubjectRect);
+ }
+ }
+ this.coordAdjust = diffPoints(adjustedPoint, origPoint);
+ }
+ else {
+ this.coordAdjust = { left: 0, top: 0 };
+ }
+ };
+ HitDragging.prototype.handleMove = function (ev, forceHandle) {
+ var hit = this.queryHitForOffset(ev.pageX + this.coordAdjust.left, ev.pageY + this.coordAdjust.top);
+ if (forceHandle || !isHitsEqual(this.movingHit, hit)) {
+ this.movingHit = hit;
+ this.emitter.trigger('hitupdate', hit, false, ev);
+ }
+ };
+ HitDragging.prototype.prepareHits = function () {
+ this.offsetTrackers = mapHash(this.droppableStore, function (interactionSettings) {
+ interactionSettings.component.prepareHits();
+ return new OffsetTracker(interactionSettings.el);
+ });
+ };
+ HitDragging.prototype.releaseHits = function () {
+ var offsetTrackers = this.offsetTrackers;
+ for (var id in offsetTrackers) {
+ offsetTrackers[id].destroy();
+ }
+ this.offsetTrackers = {};
+ };
+ HitDragging.prototype.queryHitForOffset = function (offsetLeft, offsetTop) {
+ var _a = this, droppableStore = _a.droppableStore, offsetTrackers = _a.offsetTrackers;
+ var bestHit = null;
+ for (var id in droppableStore) {
+ var component = droppableStore[id].component;
+ var offsetTracker = offsetTrackers[id];
+ if (offsetTracker && // wasn't destroyed mid-drag
+ offsetTracker.isWithinClipping(offsetLeft, offsetTop)) {
+ var originLeft = offsetTracker.computeLeft();
+ var originTop = offsetTracker.computeTop();
+ var positionLeft = offsetLeft - originLeft;
+ var positionTop = offsetTop - originTop;
+ var origRect = offsetTracker.origRect;
+ var width = origRect.right - origRect.left;
+ var height = origRect.bottom - origRect.top;
+ if (
+ // must be within the element's bounds
+ positionLeft >= 0 && positionLeft < width &&
+ positionTop >= 0 && positionTop < height) {
+ var hit = component.queryHit(positionLeft, positionTop, width, height);
+ if (hit && (
+ // make sure the hit is within activeRange, meaning it's not a dead cell
+ rangeContainsRange(hit.dateProfile.activeRange, hit.dateSpan.range)) &&
+ (!bestHit || hit.layer > bestHit.layer)) {
+ hit.componentId = id;
+ hit.context = component.context;
+ // TODO: better way to re-orient rectangle
+ hit.rect.left += originLeft;
+ hit.rect.right += originLeft;
+ hit.rect.top += originTop;
+ hit.rect.bottom += originTop;
+ bestHit = hit;
+ }
+ }
+ }
+ }
+ return bestHit;
+ };
+ return HitDragging;
+ }());
+ function isHitsEqual(hit0, hit1) {
+ if (!hit0 && !hit1) {
+ return true;
+ }
+ if (Boolean(hit0) !== Boolean(hit1)) {
+ return false;
+ }
+ return isDateSpansEqual(hit0.dateSpan, hit1.dateSpan);
+ }
+
+ function buildDatePointApiWithContext(dateSpan, context) {
+ var props = {};
+ for (var _i = 0, _a = context.pluginHooks.datePointTransforms; _i < _a.length; _i++) {
+ var transform = _a[_i];
+ __assign(props, transform(dateSpan, context));
+ }
+ __assign(props, buildDatePointApi(dateSpan, context.dateEnv));
+ return props;
+ }
+ function buildDatePointApi(span, dateEnv) {
+ return {
+ date: dateEnv.toDate(span.range.start),
+ dateStr: dateEnv.formatIso(span.range.start, { omitTime: span.allDay }),
+ allDay: span.allDay,
+ };
+ }
+
+ /*
+ Monitors when the user clicks on a specific date/time of a component.
+ A pointerdown+pointerup on the same "hit" constitutes a click.
+ */
+ var DateClicking = /** @class */ (function (_super) {
+ __extends(DateClicking, _super);
+ function DateClicking(settings) {
+ var _this = _super.call(this, settings) || this;
+ _this.handlePointerDown = function (pev) {
+ var dragging = _this.dragging;
+ var downEl = pev.origEvent.target;
+ // do this in pointerdown (not dragend) because DOM might be mutated by the time dragend is fired
+ dragging.setIgnoreMove(!_this.component.isValidDateDownEl(downEl));
+ };
+ // won't even fire if moving was ignored
+ _this.handleDragEnd = function (ev) {
+ var component = _this.component;
+ var pointer = _this.dragging.pointer;
+ if (!pointer.wasTouchScroll) {
+ var _a = _this.hitDragging, initialHit = _a.initialHit, finalHit = _a.finalHit;
+ if (initialHit && finalHit && isHitsEqual(initialHit, finalHit)) {
+ var context = component.context;
+ var arg = __assign(__assign({}, buildDatePointApiWithContext(initialHit.dateSpan, context)), { dayEl: initialHit.dayEl, jsEvent: ev.origEvent, view: context.viewApi || context.calendarApi.view });
+ context.emitter.trigger('dateClick', arg);
+ }
+ }
+ };
+ // we DO want to watch pointer moves because otherwise finalHit won't get populated
+ _this.dragging = new FeaturefulElementDragging(settings.el);
+ _this.dragging.autoScroller.isEnabled = false;
+ var hitDragging = _this.hitDragging = new HitDragging(_this.dragging, interactionSettingsToStore(settings));
+ hitDragging.emitter.on('pointerdown', _this.handlePointerDown);
+ hitDragging.emitter.on('dragend', _this.handleDragEnd);
+ return _this;
+ }
+ DateClicking.prototype.destroy = function () {
+ this.dragging.destroy();
+ };
+ return DateClicking;
+ }(Interaction));
+
+ /*
+ Tracks when the user selects a portion of time of a component,
+ constituted by a drag over date cells, with a possible delay at the beginning of the drag.
+ */
+ var DateSelecting = /** @class */ (function (_super) {
+ __extends(DateSelecting, _super);
+ function DateSelecting(settings) {
+ var _this = _super.call(this, settings) || this;
+ _this.dragSelection = null;
+ _this.handlePointerDown = function (ev) {
+ var _a = _this, component = _a.component, dragging = _a.dragging;
+ var options = component.context.options;
+ var canSelect = options.selectable &&
+ component.isValidDateDownEl(ev.origEvent.target);
+ // don't bother to watch expensive moves if component won't do selection
+ dragging.setIgnoreMove(!canSelect);
+ // if touch, require user to hold down
+ dragging.delay = ev.isTouch ? getComponentTouchDelay$1(component) : null;
+ };
+ _this.handleDragStart = function (ev) {
+ _this.component.context.calendarApi.unselect(ev); // unselect previous selections
+ };
+ _this.handleHitUpdate = function (hit, isFinal) {
+ var context = _this.component.context;
+ var dragSelection = null;
+ var isInvalid = false;
+ if (hit) {
+ var initialHit = _this.hitDragging.initialHit;
+ var disallowed = hit.componentId === initialHit.componentId
+ && _this.isHitComboAllowed
+ && !_this.isHitComboAllowed(initialHit, hit);
+ if (!disallowed) {
+ dragSelection = joinHitsIntoSelection(initialHit, hit, context.pluginHooks.dateSelectionTransformers);
+ }
+ if (!dragSelection || !isDateSelectionValid(dragSelection, hit.dateProfile, context)) {
+ isInvalid = true;
+ dragSelection = null;
+ }
+ }
+ if (dragSelection) {
+ context.dispatch({ type: 'SELECT_DATES', selection: dragSelection });
+ }
+ else if (!isFinal) { // only unselect if moved away while dragging
+ context.dispatch({ type: 'UNSELECT_DATES' });
+ }
+ if (!isInvalid) {
+ enableCursor();
+ }
+ else {
+ disableCursor();
+ }
+ if (!isFinal) {
+ _this.dragSelection = dragSelection; // only clear if moved away from all hits while dragging
+ }
+ };
+ _this.handlePointerUp = function (pev) {
+ if (_this.dragSelection) {
+ // selection is already rendered, so just need to report selection
+ triggerDateSelect(_this.dragSelection, pev, _this.component.context);
+ _this.dragSelection = null;
+ }
+ };
+ var component = settings.component;
+ var options = component.context.options;
+ var dragging = _this.dragging = new FeaturefulElementDragging(settings.el);
+ dragging.touchScrollAllowed = false;
+ dragging.minDistance = options.selectMinDistance || 0;
+ dragging.autoScroller.isEnabled = options.dragScroll;
+ var hitDragging = _this.hitDragging = new HitDragging(_this.dragging, interactionSettingsToStore(settings));
+ hitDragging.emitter.on('pointerdown', _this.handlePointerDown);
+ hitDragging.emitter.on('dragstart', _this.handleDragStart);
+ hitDragging.emitter.on('hitupdate', _this.handleHitUpdate);
+ hitDragging.emitter.on('pointerup', _this.handlePointerUp);
+ return _this;
+ }
+ DateSelecting.prototype.destroy = function () {
+ this.dragging.destroy();
+ };
+ return DateSelecting;
+ }(Interaction));
+ function getComponentTouchDelay$1(component) {
+ var options = component.context.options;
+ var delay = options.selectLongPressDelay;
+ if (delay == null) {
+ delay = options.longPressDelay;
+ }
+ return delay;
+ }
+ function joinHitsIntoSelection(hit0, hit1, dateSelectionTransformers) {
+ var dateSpan0 = hit0.dateSpan;
+ var dateSpan1 = hit1.dateSpan;
+ var ms = [
+ dateSpan0.range.start,
+ dateSpan0.range.end,
+ dateSpan1.range.start,
+ dateSpan1.range.end,
+ ];
+ ms.sort(compareNumbers);
+ var props = {};
+ for (var _i = 0, dateSelectionTransformers_1 = dateSelectionTransformers; _i < dateSelectionTransformers_1.length; _i++) {
+ var transformer = dateSelectionTransformers_1[_i];
+ var res = transformer(hit0, hit1);
+ if (res === false) {
+ return null;
+ }
+ if (res) {
+ __assign(props, res);
+ }
+ }
+ props.range = { start: ms[0], end: ms[3] };
+ props.allDay = dateSpan0.allDay;
+ return props;
+ }
+
+ var EventDragging = /** @class */ (function (_super) {
+ __extends(EventDragging, _super);
+ function EventDragging(settings) {
+ var _this = _super.call(this, settings) || this;
+ // internal state
+ _this.subjectEl = null;
+ _this.subjectSeg = null; // the seg being selected/dragged
+ _this.isDragging = false;
+ _this.eventRange = null;
+ _this.relevantEvents = null; // the events being dragged
+ _this.receivingContext = null;
+ _this.validMutation = null;
+ _this.mutatedRelevantEvents = null;
+ _this.handlePointerDown = function (ev) {
+ var origTarget = ev.origEvent.target;
+ var _a = _this, component = _a.component, dragging = _a.dragging;
+ var mirror = dragging.mirror;
+ var options = component.context.options;
+ var initialContext = component.context;
+ _this.subjectEl = ev.subjectEl;
+ var subjectSeg = _this.subjectSeg = getElSeg(ev.subjectEl);
+ var eventRange = _this.eventRange = subjectSeg.eventRange;
+ var eventInstanceId = eventRange.instance.instanceId;
+ _this.relevantEvents = getRelevantEvents(initialContext.getCurrentData().eventStore, eventInstanceId);
+ dragging.minDistance = ev.isTouch ? 0 : options.eventDragMinDistance;
+ dragging.delay =
+ // only do a touch delay if touch and this event hasn't been selected yet
+ (ev.isTouch && eventInstanceId !== component.props.eventSelection) ?
+ getComponentTouchDelay(component) :
+ null;
+ if (options.fixedMirrorParent) {
+ mirror.parentNode = options.fixedMirrorParent;
+ }
+ else {
+ mirror.parentNode = elementClosest(origTarget, '.fc');
+ }
+ mirror.revertDuration = options.dragRevertDuration;
+ var isValid = component.isValidSegDownEl(origTarget) &&
+ !elementClosest(origTarget, '.fc-event-resizer'); // NOT on a resizer
+ dragging.setIgnoreMove(!isValid);
+ // disable dragging for elements that are resizable (ie, selectable)
+ // but are not draggable
+ _this.isDragging = isValid &&
+ ev.subjectEl.classList.contains('fc-event-draggable');
+ };
+ _this.handleDragStart = function (ev) {
+ var initialContext = _this.component.context;
+ var eventRange = _this.eventRange;
+ var eventInstanceId = eventRange.instance.instanceId;
+ if (ev.isTouch) {
+ // need to select a different event?
+ if (eventInstanceId !== _this.component.props.eventSelection) {
+ initialContext.dispatch({ type: 'SELECT_EVENT', eventInstanceId: eventInstanceId });
+ }
+ }
+ else {
+ // if now using mouse, but was previous touch interaction, clear selected event
+ initialContext.dispatch({ type: 'UNSELECT_EVENT' });
+ }
+ if (_this.isDragging) {
+ initialContext.calendarApi.unselect(ev); // unselect *date* selection
+ initialContext.emitter.trigger('eventDragStart', {
+ el: _this.subjectEl,
+ event: new EventApi(initialContext, eventRange.def, eventRange.instance),
+ jsEvent: ev.origEvent,
+ view: initialContext.viewApi,
+ });
+ }
+ };
+ _this.handleHitUpdate = function (hit, isFinal) {
+ if (!_this.isDragging) {
+ return;
+ }
+ var relevantEvents = _this.relevantEvents;
+ var initialHit = _this.hitDragging.initialHit;
+ var initialContext = _this.component.context;
+ // states based on new hit
+ var receivingContext = null;
+ var mutation = null;
+ var mutatedRelevantEvents = null;
+ var isInvalid = false;
+ var interaction = {
+ affectedEvents: relevantEvents,
+ mutatedEvents: createEmptyEventStore(),
+ isEvent: true,
+ };
+ if (hit) {
+ receivingContext = hit.context;
+ var receivingOptions = receivingContext.options;
+ if (initialContext === receivingContext ||
+ (receivingOptions.editable && receivingOptions.droppable)) {
+ mutation = computeEventMutation(initialHit, hit, receivingContext.getCurrentData().pluginHooks.eventDragMutationMassagers);
+ if (mutation) {
+ mutatedRelevantEvents = applyMutationToEventStore(relevantEvents, receivingContext.getCurrentData().eventUiBases, mutation, receivingContext);
+ interaction.mutatedEvents = mutatedRelevantEvents;
+ if (!isInteractionValid(interaction, hit.dateProfile, receivingContext)) {
+ isInvalid = true;
+ mutation = null;
+ mutatedRelevantEvents = null;
+ interaction.mutatedEvents = createEmptyEventStore();
+ }
+ }
+ }
+ else {
+ receivingContext = null;
+ }
+ }
+ _this.displayDrag(receivingContext, interaction);
+ if (!isInvalid) {
+ enableCursor();
+ }
+ else {
+ disableCursor();
+ }
+ if (!isFinal) {
+ if (initialContext === receivingContext && // TODO: write test for this
+ isHitsEqual(initialHit, hit)) {
+ mutation = null;
+ }
+ _this.dragging.setMirrorNeedsRevert(!mutation);
+ // render the mirror if no already-rendered mirror
+ // TODO: wish we could somehow wait for dispatch to guarantee render
+ _this.dragging.setMirrorIsVisible(!hit || !getElRoot(_this.subjectEl).querySelector('.fc-event-mirror'));
+ // assign states based on new hit
+ _this.receivingContext = receivingContext;
+ _this.validMutation = mutation;
+ _this.mutatedRelevantEvents = mutatedRelevantEvents;
+ }
+ };
+ _this.handlePointerUp = function () {
+ if (!_this.isDragging) {
+ _this.cleanup(); // because handleDragEnd won't fire
+ }
+ };
+ _this.handleDragEnd = function (ev) {
+ if (_this.isDragging) {
+ var initialContext_1 = _this.component.context;
+ var initialView = initialContext_1.viewApi;
+ var _a = _this, receivingContext_1 = _a.receivingContext, validMutation = _a.validMutation;
+ var eventDef = _this.eventRange.def;
+ var eventInstance = _this.eventRange.instance;
+ var eventApi = new EventApi(initialContext_1, eventDef, eventInstance);
+ var relevantEvents_1 = _this.relevantEvents;
+ var mutatedRelevantEvents_1 = _this.mutatedRelevantEvents;
+ var finalHit = _this.hitDragging.finalHit;
+ _this.clearDrag(); // must happen after revert animation
+ initialContext_1.emitter.trigger('eventDragStop', {
+ el: _this.subjectEl,
+ event: eventApi,
+ jsEvent: ev.origEvent,
+ view: initialView,
+ });
+ if (validMutation) {
+ // dropped within same calendar
+ if (receivingContext_1 === initialContext_1) {
+ var updatedEventApi = new EventApi(initialContext_1, mutatedRelevantEvents_1.defs[eventDef.defId], eventInstance ? mutatedRelevantEvents_1.instances[eventInstance.instanceId] : null);
+ initialContext_1.dispatch({
+ type: 'MERGE_EVENTS',
+ eventStore: mutatedRelevantEvents_1,
+ });
+ var eventChangeArg = {
+ oldEvent: eventApi,
+ event: updatedEventApi,
+ relatedEvents: buildEventApis(mutatedRelevantEvents_1, initialContext_1, eventInstance),
+ revert: function () {
+ initialContext_1.dispatch({
+ type: 'MERGE_EVENTS',
+ eventStore: relevantEvents_1, // the pre-change data
+ });
+ },
+ };
+ var transformed = {};
+ for (var _i = 0, _b = initialContext_1.getCurrentData().pluginHooks.eventDropTransformers; _i < _b.length; _i++) {
+ var transformer = _b[_i];
+ __assign(transformed, transformer(validMutation, initialContext_1));
+ }
+ initialContext_1.emitter.trigger('eventDrop', __assign(__assign(__assign({}, eventChangeArg), transformed), { el: ev.subjectEl, delta: validMutation.datesDelta, jsEvent: ev.origEvent, view: initialView }));
+ initialContext_1.emitter.trigger('eventChange', eventChangeArg);
+ // dropped in different calendar
+ }
+ else if (receivingContext_1) {
+ var eventRemoveArg = {
+ event: eventApi,
+ relatedEvents: buildEventApis(relevantEvents_1, initialContext_1, eventInstance),
+ revert: function () {
+ initialContext_1.dispatch({
+ type: 'MERGE_EVENTS',
+ eventStore: relevantEvents_1,
+ });
+ },
+ };
+ initialContext_1.emitter.trigger('eventLeave', __assign(__assign({}, eventRemoveArg), { draggedEl: ev.subjectEl, view: initialView }));
+ initialContext_1.dispatch({
+ type: 'REMOVE_EVENTS',
+ eventStore: relevantEvents_1,
+ });
+ initialContext_1.emitter.trigger('eventRemove', eventRemoveArg);
+ var addedEventDef = mutatedRelevantEvents_1.defs[eventDef.defId];
+ var addedEventInstance = mutatedRelevantEvents_1.instances[eventInstance.instanceId];
+ var addedEventApi = new EventApi(receivingContext_1, addedEventDef, addedEventInstance);
+ receivingContext_1.dispatch({
+ type: 'MERGE_EVENTS',
+ eventStore: mutatedRelevantEvents_1,
+ });
+ var eventAddArg = {
+ event: addedEventApi,
+ relatedEvents: buildEventApis(mutatedRelevantEvents_1, receivingContext_1, addedEventInstance),
+ revert: function () {
+ receivingContext_1.dispatch({
+ type: 'REMOVE_EVENTS',
+ eventStore: mutatedRelevantEvents_1,
+ });
+ },
+ };
+ receivingContext_1.emitter.trigger('eventAdd', eventAddArg);
+ if (ev.isTouch) {
+ receivingContext_1.dispatch({
+ type: 'SELECT_EVENT',
+ eventInstanceId: eventInstance.instanceId,
+ });
+ }
+ receivingContext_1.emitter.trigger('drop', __assign(__assign({}, buildDatePointApiWithContext(finalHit.dateSpan, receivingContext_1)), { draggedEl: ev.subjectEl, jsEvent: ev.origEvent, view: finalHit.context.viewApi }));
+ receivingContext_1.emitter.trigger('eventReceive', __assign(__assign({}, eventAddArg), { draggedEl: ev.subjectEl, view: finalHit.context.viewApi }));
+ }
+ }
+ else {
+ initialContext_1.emitter.trigger('_noEventDrop');
+ }
+ }
+ _this.cleanup();
+ };
+ var component = _this.component;
+ var options = component.context.options;
+ var dragging = _this.dragging = new FeaturefulElementDragging(settings.el);
+ dragging.pointer.selector = EventDragging.SELECTOR;
+ dragging.touchScrollAllowed = false;
+ dragging.autoScroller.isEnabled = options.dragScroll;
+ var hitDragging = _this.hitDragging = new HitDragging(_this.dragging, interactionSettingsStore);
+ hitDragging.useSubjectCenter = settings.useEventCenter;
+ hitDragging.emitter.on('pointerdown', _this.handlePointerDown);
+ hitDragging.emitter.on('dragstart', _this.handleDragStart);
+ hitDragging.emitter.on('hitupdate', _this.handleHitUpdate);
+ hitDragging.emitter.on('pointerup', _this.handlePointerUp);
+ hitDragging.emitter.on('dragend', _this.handleDragEnd);
+ return _this;
+ }
+ EventDragging.prototype.destroy = function () {
+ this.dragging.destroy();
+ };
+ // render a drag state on the next receivingCalendar
+ EventDragging.prototype.displayDrag = function (nextContext, state) {
+ var initialContext = this.component.context;
+ var prevContext = this.receivingContext;
+ // does the previous calendar need to be cleared?
+ if (prevContext && prevContext !== nextContext) {
+ // does the initial calendar need to be cleared?
+ // if so, don't clear all the way. we still need to to hide the affectedEvents
+ if (prevContext === initialContext) {
+ prevContext.dispatch({
+ type: 'SET_EVENT_DRAG',
+ state: {
+ affectedEvents: state.affectedEvents,
+ mutatedEvents: createEmptyEventStore(),
+ isEvent: true,
+ },
+ });
+ // completely clear the old calendar if it wasn't the initial
+ }
+ else {
+ prevContext.dispatch({ type: 'UNSET_EVENT_DRAG' });
+ }
+ }
+ if (nextContext) {
+ nextContext.dispatch({ type: 'SET_EVENT_DRAG', state: state });
+ }
+ };
+ EventDragging.prototype.clearDrag = function () {
+ var initialCalendar = this.component.context;
+ var receivingContext = this.receivingContext;
+ if (receivingContext) {
+ receivingContext.dispatch({ type: 'UNSET_EVENT_DRAG' });
+ }
+ // the initial calendar might have an dummy drag state from displayDrag
+ if (initialCalendar !== receivingContext) {
+ initialCalendar.dispatch({ type: 'UNSET_EVENT_DRAG' });
+ }
+ };
+ EventDragging.prototype.cleanup = function () {
+ this.subjectSeg = null;
+ this.isDragging = false;
+ this.eventRange = null;
+ this.relevantEvents = null;
+ this.receivingContext = null;
+ this.validMutation = null;
+ this.mutatedRelevantEvents = null;
+ };
+ // TODO: test this in IE11
+ // QUESTION: why do we need it on the resizable???
+ EventDragging.SELECTOR = '.fc-event-draggable, .fc-event-resizable';
+ return EventDragging;
+ }(Interaction));
+ function computeEventMutation(hit0, hit1, massagers) {
+ var dateSpan0 = hit0.dateSpan;
+ var dateSpan1 = hit1.dateSpan;
+ var date0 = dateSpan0.range.start;
+ var date1 = dateSpan1.range.start;
+ var standardProps = {};
+ if (dateSpan0.allDay !== dateSpan1.allDay) {
+ standardProps.allDay = dateSpan1.allDay;
+ standardProps.hasEnd = hit1.context.options.allDayMaintainDuration;
+ if (dateSpan1.allDay) {
+ // means date1 is already start-of-day,
+ // but date0 needs to be converted
+ date0 = startOfDay(date0);
+ }
+ }
+ var delta = diffDates(date0, date1, hit0.context.dateEnv, hit0.componentId === hit1.componentId ?
+ hit0.largeUnit :
+ null);
+ if (delta.milliseconds) { // has hours/minutes/seconds
+ standardProps.allDay = false;
+ }
+ var mutation = {
+ datesDelta: delta,
+ standardProps: standardProps,
+ };
+ for (var _i = 0, massagers_1 = massagers; _i < massagers_1.length; _i++) {
+ var massager = massagers_1[_i];
+ massager(mutation, hit0, hit1);
+ }
+ return mutation;
+ }
+ function getComponentTouchDelay(component) {
+ var options = component.context.options;
+ var delay = options.eventLongPressDelay;
+ if (delay == null) {
+ delay = options.longPressDelay;
+ }
+ return delay;
+ }
+
+ var EventResizing = /** @class */ (function (_super) {
+ __extends(EventResizing, _super);
+ function EventResizing(settings) {
+ var _this = _super.call(this, settings) || this;
+ // internal state
+ _this.draggingSegEl = null;
+ _this.draggingSeg = null; // TODO: rename to resizingSeg? subjectSeg?
+ _this.eventRange = null;
+ _this.relevantEvents = null;
+ _this.validMutation = null;
+ _this.mutatedRelevantEvents = null;
+ _this.handlePointerDown = function (ev) {
+ var component = _this.component;
+ var segEl = _this.querySegEl(ev);
+ var seg = getElSeg(segEl);
+ var eventRange = _this.eventRange = seg.eventRange;
+ _this.dragging.minDistance = component.context.options.eventDragMinDistance;
+ // if touch, need to be working with a selected event
+ _this.dragging.setIgnoreMove(!_this.component.isValidSegDownEl(ev.origEvent.target) ||
+ (ev.isTouch && _this.component.props.eventSelection !== eventRange.instance.instanceId));
+ };
+ _this.handleDragStart = function (ev) {
+ var context = _this.component.context;
+ var eventRange = _this.eventRange;
+ _this.relevantEvents = getRelevantEvents(context.getCurrentData().eventStore, _this.eventRange.instance.instanceId);
+ var segEl = _this.querySegEl(ev);
+ _this.draggingSegEl = segEl;
+ _this.draggingSeg = getElSeg(segEl);
+ context.calendarApi.unselect();
+ context.emitter.trigger('eventResizeStart', {
+ el: segEl,
+ event: new EventApi(context, eventRange.def, eventRange.instance),
+ jsEvent: ev.origEvent,
+ view: context.viewApi,
+ });
+ };
+ _this.handleHitUpdate = function (hit, isFinal, ev) {
+ var context = _this.component.context;
+ var relevantEvents = _this.relevantEvents;
+ var initialHit = _this.hitDragging.initialHit;
+ var eventInstance = _this.eventRange.instance;
+ var mutation = null;
+ var mutatedRelevantEvents = null;
+ var isInvalid = false;
+ var interaction = {
+ affectedEvents: relevantEvents,
+ mutatedEvents: createEmptyEventStore(),
+ isEvent: true,
+ };
+ if (hit) {
+ var disallowed = hit.componentId === initialHit.componentId
+ && _this.isHitComboAllowed
+ && !_this.isHitComboAllowed(initialHit, hit);
+ if (!disallowed) {
+ mutation = computeMutation(initialHit, hit, ev.subjectEl.classList.contains('fc-event-resizer-start'), eventInstance.range);
+ }
+ }
+ if (mutation) {
+ mutatedRelevantEvents = applyMutationToEventStore(relevantEvents, context.getCurrentData().eventUiBases, mutation, context);
+ interaction.mutatedEvents = mutatedRelevantEvents;
+ if (!isInteractionValid(interaction, hit.dateProfile, context)) {
+ isInvalid = true;
+ mutation = null;
+ mutatedRelevantEvents = null;
+ interaction.mutatedEvents = null;
+ }
+ }
+ if (mutatedRelevantEvents) {
+ context.dispatch({
+ type: 'SET_EVENT_RESIZE',
+ state: interaction,
+ });
+ }
+ else {
+ context.dispatch({ type: 'UNSET_EVENT_RESIZE' });
+ }
+ if (!isInvalid) {
+ enableCursor();
+ }
+ else {
+ disableCursor();
+ }
+ if (!isFinal) {
+ if (mutation && isHitsEqual(initialHit, hit)) {
+ mutation = null;
+ }
+ _this.validMutation = mutation;
+ _this.mutatedRelevantEvents = mutatedRelevantEvents;
+ }
+ };
+ _this.handleDragEnd = function (ev) {
+ var context = _this.component.context;
+ var eventDef = _this.eventRange.def;
+ var eventInstance = _this.eventRange.instance;
+ var eventApi = new EventApi(context, eventDef, eventInstance);
+ var relevantEvents = _this.relevantEvents;
+ var mutatedRelevantEvents = _this.mutatedRelevantEvents;
+ context.emitter.trigger('eventResizeStop', {
+ el: _this.draggingSegEl,
+ event: eventApi,
+ jsEvent: ev.origEvent,
+ view: context.viewApi,
+ });
+ if (_this.validMutation) {
+ var updatedEventApi = new EventApi(context, mutatedRelevantEvents.defs[eventDef.defId], eventInstance ? mutatedRelevantEvents.instances[eventInstance.instanceId] : null);
+ context.dispatch({
+ type: 'MERGE_EVENTS',
+ eventStore: mutatedRelevantEvents,
+ });
+ var eventChangeArg = {
+ oldEvent: eventApi,
+ event: updatedEventApi,
+ relatedEvents: buildEventApis(mutatedRelevantEvents, context, eventInstance),
+ revert: function () {
+ context.dispatch({
+ type: 'MERGE_EVENTS',
+ eventStore: relevantEvents, // the pre-change events
+ });
+ },
+ };
+ context.emitter.trigger('eventResize', __assign(__assign({}, eventChangeArg), { el: _this.draggingSegEl, startDelta: _this.validMutation.startDelta || createDuration(0), endDelta: _this.validMutation.endDelta || createDuration(0), jsEvent: ev.origEvent, view: context.viewApi }));
+ context.emitter.trigger('eventChange', eventChangeArg);
+ }
+ else {
+ context.emitter.trigger('_noEventResize');
+ }
+ // reset all internal state
+ _this.draggingSeg = null;
+ _this.relevantEvents = null;
+ _this.validMutation = null;
+ // okay to keep eventInstance around. useful to set it in handlePointerDown
+ };
+ var component = settings.component;
+ var dragging = _this.dragging = new FeaturefulElementDragging(settings.el);
+ dragging.pointer.selector = '.fc-event-resizer';
+ dragging.touchScrollAllowed = false;
+ dragging.autoScroller.isEnabled = component.context.options.dragScroll;
+ var hitDragging = _this.hitDragging = new HitDragging(_this.dragging, interactionSettingsToStore(settings));
+ hitDragging.emitter.on('pointerdown', _this.handlePointerDown);
+ hitDragging.emitter.on('dragstart', _this.handleDragStart);
+ hitDragging.emitter.on('hitupdate', _this.handleHitUpdate);
+ hitDragging.emitter.on('dragend', _this.handleDragEnd);
+ return _this;
+ }
+ EventResizing.prototype.destroy = function () {
+ this.dragging.destroy();
+ };
+ EventResizing.prototype.querySegEl = function (ev) {
+ return elementClosest(ev.subjectEl, '.fc-event');
+ };
+ return EventResizing;
+ }(Interaction));
+ function computeMutation(hit0, hit1, isFromStart, instanceRange) {
+ var dateEnv = hit0.context.dateEnv;
+ var date0 = hit0.dateSpan.range.start;
+ var date1 = hit1.dateSpan.range.start;
+ var delta = diffDates(date0, date1, dateEnv, hit0.largeUnit);
+ if (isFromStart) {
+ if (dateEnv.add(instanceRange.start, delta) < instanceRange.end) {
+ return { startDelta: delta };
+ }
+ }
+ else if (dateEnv.add(instanceRange.end, delta) > instanceRange.start) {
+ return { endDelta: delta };
+ }
+ return null;
+ }
+
+ var UnselectAuto = /** @class */ (function () {
+ function UnselectAuto(context) {
+ var _this = this;
+ this.context = context;
+ this.isRecentPointerDateSelect = false; // wish we could use a selector to detect date selection, but uses hit system
+ this.matchesCancel = false;
+ this.matchesEvent = false;
+ this.onSelect = function (selectInfo) {
+ if (selectInfo.jsEvent) {
+ _this.isRecentPointerDateSelect = true;
+ }
+ };
+ this.onDocumentPointerDown = function (pev) {
+ var unselectCancel = _this.context.options.unselectCancel;
+ var downEl = getEventTargetViaRoot(pev.origEvent);
+ _this.matchesCancel = !!elementClosest(downEl, unselectCancel);
+ _this.matchesEvent = !!elementClosest(downEl, EventDragging.SELECTOR); // interaction started on an event?
+ };
+ this.onDocumentPointerUp = function (pev) {
+ var context = _this.context;
+ var documentPointer = _this.documentPointer;
+ var calendarState = context.getCurrentData();
+ // touch-scrolling should never unfocus any type of selection
+ if (!documentPointer.wasTouchScroll) {
+ if (calendarState.dateSelection && // an existing date selection?
+ !_this.isRecentPointerDateSelect // a new pointer-initiated date selection since last onDocumentPointerUp?
+ ) {
+ var unselectAuto = context.options.unselectAuto;
+ if (unselectAuto && (!unselectAuto || !_this.matchesCancel)) {
+ context.calendarApi.unselect(pev);
+ }
+ }
+ if (calendarState.eventSelection && // an existing event selected?
+ !_this.matchesEvent // interaction DIDN'T start on an event
+ ) {
+ context.dispatch({ type: 'UNSELECT_EVENT' });
+ }
+ }
+ _this.isRecentPointerDateSelect = false;
+ };
+ var documentPointer = this.documentPointer = new PointerDragging(document);
+ documentPointer.shouldIgnoreMove = true;
+ documentPointer.shouldWatchScroll = false;
+ documentPointer.emitter.on('pointerdown', this.onDocumentPointerDown);
+ documentPointer.emitter.on('pointerup', this.onDocumentPointerUp);
+ /*
+ TODO: better way to know about whether there was a selection with the pointer
+ */
+ context.emitter.on('select', this.onSelect);
+ }
+ UnselectAuto.prototype.destroy = function () {
+ this.context.emitter.off('select', this.onSelect);
+ this.documentPointer.destroy();
+ };
+ return UnselectAuto;
+ }());
+
+ var OPTION_REFINERS$3 = {
+ fixedMirrorParent: identity,
+ };
+ var LISTENER_REFINERS = {
+ dateClick: identity,
+ eventDragStart: identity,
+ eventDragStop: identity,
+ eventDrop: identity,
+ eventResizeStart: identity,
+ eventResizeStop: identity,
+ eventResize: identity,
+ drop: identity,
+ eventReceive: identity,
+ eventLeave: identity,
+ };
+
+ /*
+ Given an already instantiated draggable object for one-or-more elements,
+ Interprets any dragging as an attempt to drag an events that lives outside
+ of a calendar onto a calendar.
+ */
+ var ExternalElementDragging = /** @class */ (function () {
+ function ExternalElementDragging(dragging, suppliedDragMeta) {
+ var _this = this;
+ this.receivingContext = null;
+ this.droppableEvent = null; // will exist for all drags, even if create:false
+ this.suppliedDragMeta = null;
+ this.dragMeta = null;
+ this.handleDragStart = function (ev) {
+ _this.dragMeta = _this.buildDragMeta(ev.subjectEl);
+ };
+ this.handleHitUpdate = function (hit, isFinal, ev) {
+ var dragging = _this.hitDragging.dragging;
+ var receivingContext = null;
+ var droppableEvent = null;
+ var isInvalid = false;
+ var interaction = {
+ affectedEvents: createEmptyEventStore(),
+ mutatedEvents: createEmptyEventStore(),
+ isEvent: _this.dragMeta.create,
+ };
+ if (hit) {
+ receivingContext = hit.context;
+ if (_this.canDropElOnCalendar(ev.subjectEl, receivingContext)) {
+ droppableEvent = computeEventForDateSpan(hit.dateSpan, _this.dragMeta, receivingContext);
+ interaction.mutatedEvents = eventTupleToStore(droppableEvent);
+ isInvalid = !isInteractionValid(interaction, hit.dateProfile, receivingContext);
+ if (isInvalid) {
+ interaction.mutatedEvents = createEmptyEventStore();
+ droppableEvent = null;
+ }
+ }
+ }
+ _this.displayDrag(receivingContext, interaction);
+ // show mirror if no already-rendered mirror element OR if we are shutting down the mirror (?)
+ // TODO: wish we could somehow wait for dispatch to guarantee render
+ dragging.setMirrorIsVisible(isFinal || !droppableEvent || !document.querySelector('.fc-event-mirror'));
+ if (!isInvalid) {
+ enableCursor();
+ }
+ else {
+ disableCursor();
+ }
+ if (!isFinal) {
+ dragging.setMirrorNeedsRevert(!droppableEvent);
+ _this.receivingContext = receivingContext;
+ _this.droppableEvent = droppableEvent;
+ }
+ };
+ this.handleDragEnd = function (pev) {
+ var _a = _this, receivingContext = _a.receivingContext, droppableEvent = _a.droppableEvent;
+ _this.clearDrag();
+ if (receivingContext && droppableEvent) {
+ var finalHit = _this.hitDragging.finalHit;
+ var finalView = finalHit.context.viewApi;
+ var dragMeta = _this.dragMeta;
+ receivingContext.emitter.trigger('drop', __assign(__assign({}, buildDatePointApiWithContext(finalHit.dateSpan, receivingContext)), { draggedEl: pev.subjectEl, jsEvent: pev.origEvent, view: finalView }));
+ if (dragMeta.create) {
+ var addingEvents_1 = eventTupleToStore(droppableEvent);
+ receivingContext.dispatch({
+ type: 'MERGE_EVENTS',
+ eventStore: addingEvents_1,
+ });
+ if (pev.isTouch) {
+ receivingContext.dispatch({
+ type: 'SELECT_EVENT',
+ eventInstanceId: droppableEvent.instance.instanceId,
+ });
+ }
+ // signal that an external event landed
+ receivingContext.emitter.trigger('eventReceive', {
+ event: new EventApi(receivingContext, droppableEvent.def, droppableEvent.instance),
+ relatedEvents: [],
+ revert: function () {
+ receivingContext.dispatch({
+ type: 'REMOVE_EVENTS',
+ eventStore: addingEvents_1,
+ });
+ },
+ draggedEl: pev.subjectEl,
+ view: finalView,
+ });
+ }
+ }
+ _this.receivingContext = null;
+ _this.droppableEvent = null;
+ };
+ var hitDragging = this.hitDragging = new HitDragging(dragging, interactionSettingsStore);
+ hitDragging.requireInitial = false; // will start outside of a component
+ hitDragging.emitter.on('dragstart', this.handleDragStart);
+ hitDragging.emitter.on('hitupdate', this.handleHitUpdate);
+ hitDragging.emitter.on('dragend', this.handleDragEnd);
+ this.suppliedDragMeta = suppliedDragMeta;
+ }
+ ExternalElementDragging.prototype.buildDragMeta = function (subjectEl) {
+ if (typeof this.suppliedDragMeta === 'object') {
+ return parseDragMeta(this.suppliedDragMeta);
+ }
+ if (typeof this.suppliedDragMeta === 'function') {
+ return parseDragMeta(this.suppliedDragMeta(subjectEl));
+ }
+ return getDragMetaFromEl(subjectEl);
+ };
+ ExternalElementDragging.prototype.displayDrag = function (nextContext, state) {
+ var prevContext = this.receivingContext;
+ if (prevContext && prevContext !== nextContext) {
+ prevContext.dispatch({ type: 'UNSET_EVENT_DRAG' });
+ }
+ if (nextContext) {
+ nextContext.dispatch({ type: 'SET_EVENT_DRAG', state: state });
+ }
+ };
+ ExternalElementDragging.prototype.clearDrag = function () {
+ if (this.receivingContext) {
+ this.receivingContext.dispatch({ type: 'UNSET_EVENT_DRAG' });
+ }
+ };
+ ExternalElementDragging.prototype.canDropElOnCalendar = function (el, receivingContext) {
+ var dropAccept = receivingContext.options.dropAccept;
+ if (typeof dropAccept === 'function') {
+ return dropAccept.call(receivingContext.calendarApi, el);
+ }
+ if (typeof dropAccept === 'string' && dropAccept) {
+ return Boolean(elementMatches(el, dropAccept));
+ }
+ return true;
+ };
+ return ExternalElementDragging;
+ }());
+ // Utils for computing event store from the DragMeta
+ // ----------------------------------------------------------------------------------------------------
+ function computeEventForDateSpan(dateSpan, dragMeta, context) {
+ var defProps = __assign({}, dragMeta.leftoverProps);
+ for (var _i = 0, _a = context.pluginHooks.externalDefTransforms; _i < _a.length; _i++) {
+ var transform = _a[_i];
+ __assign(defProps, transform(dateSpan, dragMeta));
+ }
+ var _b = refineEventDef(defProps, context), refined = _b.refined, extra = _b.extra;
+ var def = parseEventDef(refined, extra, dragMeta.sourceId, dateSpan.allDay, context.options.forceEventDuration || Boolean(dragMeta.duration), // hasEnd
+ context);
+ var start = dateSpan.range.start;
+ // only rely on time info if drop zone is all-day,
+ // otherwise, we already know the time
+ if (dateSpan.allDay && dragMeta.startTime) {
+ start = context.dateEnv.add(start, dragMeta.startTime);
+ }
+ var end = dragMeta.duration ?
+ context.dateEnv.add(start, dragMeta.duration) :
+ getDefaultEventEnd(dateSpan.allDay, start, context);
+ var instance = createEventInstance(def.defId, { start: start, end: end });
+ return { def: def, instance: instance };
+ }
+ // Utils for extracting data from element
+ // ----------------------------------------------------------------------------------------------------
+ function getDragMetaFromEl(el) {
+ var str = getEmbeddedElData(el, 'event');
+ var obj = str ?
+ JSON.parse(str) :
+ { create: false }; // if no embedded data, assume no event creation
+ return parseDragMeta(obj);
+ }
+ config.dataAttrPrefix = '';
+ function getEmbeddedElData(el, name) {
+ var prefix = config.dataAttrPrefix;
+ var prefixedName = (prefix ? prefix + '-' : '') + name;
+ return el.getAttribute('data-' + prefixedName) || '';
+ }
+
+ /*
+ Makes an element (that is *external* to any calendar) draggable.
+ Can pass in data that determines how an event will be created when dropped onto a calendar.
+ Leverages FullCalendar's internal drag-n-drop functionality WITHOUT a third-party drag system.
+ */
+ var ExternalDraggable = /** @class */ (function () {
+ function ExternalDraggable(el, settings) {
+ var _this = this;
+ if (settings === void 0) { settings = {}; }
+ this.handlePointerDown = function (ev) {
+ var dragging = _this.dragging;
+ var _a = _this.settings, minDistance = _a.minDistance, longPressDelay = _a.longPressDelay;
+ dragging.minDistance =
+ minDistance != null ?
+ minDistance :
+ (ev.isTouch ? 0 : BASE_OPTION_DEFAULTS.eventDragMinDistance);
+ dragging.delay =
+ ev.isTouch ? // TODO: eventually read eventLongPressDelay instead vvv
+ (longPressDelay != null ? longPressDelay : BASE_OPTION_DEFAULTS.longPressDelay) :
+ 0;
+ };
+ this.handleDragStart = function (ev) {
+ if (ev.isTouch &&
+ _this.dragging.delay &&
+ ev.subjectEl.classList.contains('fc-event')) {
+ _this.dragging.mirror.getMirrorEl().classList.add('fc-event-selected');
+ }
+ };
+ this.settings = settings;
+ var dragging = this.dragging = new FeaturefulElementDragging(el);
+ dragging.touchScrollAllowed = false;
+ if (settings.itemSelector != null) {
+ dragging.pointer.selector = settings.itemSelector;
+ }
+ if (settings.appendTo != null) {
+ dragging.mirror.parentNode = settings.appendTo; // TODO: write tests
+ }
+ dragging.emitter.on('pointerdown', this.handlePointerDown);
+ dragging.emitter.on('dragstart', this.handleDragStart);
+ new ExternalElementDragging(dragging, settings.eventData); // eslint-disable-line no-new
+ }
+ ExternalDraggable.prototype.destroy = function () {
+ this.dragging.destroy();
+ };
+ return ExternalDraggable;
+ }());
+
+ /*
+ Detects when a *THIRD-PARTY* drag-n-drop system interacts with elements.
+ The third-party system is responsible for drawing the visuals effects of the drag.
+ This class simply monitors for pointer movements and fires events.
+ It also has the ability to hide the moving element (the "mirror") during the drag.
+ */
+ var InferredElementDragging = /** @class */ (function (_super) {
+ __extends(InferredElementDragging, _super);
+ function InferredElementDragging(containerEl) {
+ var _this = _super.call(this, containerEl) || this;
+ _this.shouldIgnoreMove = false;
+ _this.mirrorSelector = '';
+ _this.currentMirrorEl = null;
+ _this.handlePointerDown = function (ev) {
+ _this.emitter.trigger('pointerdown', ev);
+ if (!_this.shouldIgnoreMove) {
+ // fire dragstart right away. does not support delay or min-distance
+ _this.emitter.trigger('dragstart', ev);
+ }
+ };
+ _this.handlePointerMove = function (ev) {
+ if (!_this.shouldIgnoreMove) {
+ _this.emitter.trigger('dragmove', ev);
+ }
+ };
+ _this.handlePointerUp = function (ev) {
+ _this.emitter.trigger('pointerup', ev);
+ if (!_this.shouldIgnoreMove) {
+ // fire dragend right away. does not support a revert animation
+ _this.emitter.trigger('dragend', ev);
+ }
+ };
+ var pointer = _this.pointer = new PointerDragging(containerEl);
+ pointer.emitter.on('pointerdown', _this.handlePointerDown);
+ pointer.emitter.on('pointermove', _this.handlePointerMove);
+ pointer.emitter.on('pointerup', _this.handlePointerUp);
+ return _this;
+ }
+ InferredElementDragging.prototype.destroy = function () {
+ this.pointer.destroy();
+ };
+ InferredElementDragging.prototype.setIgnoreMove = function (bool) {
+ this.shouldIgnoreMove = bool;
+ };
+ InferredElementDragging.prototype.setMirrorIsVisible = function (bool) {
+ if (bool) {
+ // restore a previously hidden element.
+ // use the reference in case the selector class has already been removed.
+ if (this.currentMirrorEl) {
+ this.currentMirrorEl.style.visibility = '';
+ this.currentMirrorEl = null;
+ }
+ }
+ else {
+ var mirrorEl = this.mirrorSelector
+ // TODO: somehow query FullCalendars WITHIN shadow-roots
+ ? document.querySelector(this.mirrorSelector)
+ : null;
+ if (mirrorEl) {
+ this.currentMirrorEl = mirrorEl;
+ mirrorEl.style.visibility = 'hidden';
+ }
+ }
+ };
+ return InferredElementDragging;
+ }(ElementDragging));
+
+ /*
+ Bridges third-party drag-n-drop systems with FullCalendar.
+ Must be instantiated and destroyed by caller.
+ */
+ var ThirdPartyDraggable = /** @class */ (function () {
+ function ThirdPartyDraggable(containerOrSettings, settings) {
+ var containerEl = document;
+ if (
+ // wish we could just test instanceof EventTarget, but doesn't work in IE11
+ containerOrSettings === document ||
+ containerOrSettings instanceof Element) {
+ containerEl = containerOrSettings;
+ settings = settings || {};
+ }
+ else {
+ settings = (containerOrSettings || {});
+ }
+ var dragging = this.dragging = new InferredElementDragging(containerEl);
+ if (typeof settings.itemSelector === 'string') {
+ dragging.pointer.selector = settings.itemSelector;
+ }
+ else if (containerEl === document) {
+ dragging.pointer.selector = '[data-event]';
+ }
+ if (typeof settings.mirrorSelector === 'string') {
+ dragging.mirrorSelector = settings.mirrorSelector;
+ }
+ new ExternalElementDragging(dragging, settings.eventData); // eslint-disable-line no-new
+ }
+ ThirdPartyDraggable.prototype.destroy = function () {
+ this.dragging.destroy();
+ };
+ return ThirdPartyDraggable;
+ }());
+
+ var interactionPlugin = createPlugin({
+ componentInteractions: [DateClicking, DateSelecting, EventDragging, EventResizing],
+ calendarInteractions: [UnselectAuto],
+ elementDraggingImpl: FeaturefulElementDragging,
+ optionRefiners: OPTION_REFINERS$3,
+ listenerRefiners: LISTENER_REFINERS,
+ });
+
+ /* An abstract class for the daygrid views, as well as month view. Renders one or more rows of day cells.
+ ----------------------------------------------------------------------------------------------------------------------*/
+ // It is a manager for a Table subcomponent, which does most of the heavy lifting.
+ // It is responsible for managing width/height.
+ var TableView = /** @class */ (function (_super) {
+ __extends(TableView, _super);
+ function TableView() {
+ var _this = _super !== null && _super.apply(this, arguments) || this;
+ _this.headerElRef = createRef();
+ return _this;
+ }
+ TableView.prototype.renderSimpleLayout = function (headerRowContent, bodyContent) {
+ var _a = this, props = _a.props, context = _a.context;
+ var sections = [];
+ var stickyHeaderDates = getStickyHeaderDates(context.options);
+ if (headerRowContent) {
+ sections.push({
+ type: 'header',
+ key: 'header',
+ isSticky: stickyHeaderDates,
+ chunk: {
+ elRef: this.headerElRef,
+ tableClassName: 'fc-col-header',
+ rowContent: headerRowContent,
+ },
+ });
+ }
+ sections.push({
+ type: 'body',
+ key: 'body',
+ liquid: true,
+ chunk: { content: bodyContent },
+ });
+ return (createElement(ViewRoot, { viewSpec: context.viewSpec }, function (rootElRef, classNames) { return (createElement("div", { ref: rootElRef, className: ['fc-daygrid'].concat(classNames).join(' ') },
+ createElement(SimpleScrollGrid, { liquid: !props.isHeightAuto && !props.forPrint, collapsibleWidth: props.forPrint, cols: [] /* TODO: make optional? */, sections: sections }))); }));
+ };
+ TableView.prototype.renderHScrollLayout = function (headerRowContent, bodyContent, colCnt, dayMinWidth) {
+ var ScrollGrid = this.context.pluginHooks.scrollGridImpl;
+ if (!ScrollGrid) {
+ throw new Error('No ScrollGrid implementation');
+ }
+ var _a = this, props = _a.props, context = _a.context;
+ var stickyHeaderDates = !props.forPrint && getStickyHeaderDates(context.options);
+ var stickyFooterScrollbar = !props.forPrint && getStickyFooterScrollbar(context.options);
+ var sections = [];
+ if (headerRowContent) {
+ sections.push({
+ type: 'header',
+ key: 'header',
+ isSticky: stickyHeaderDates,
+ chunks: [{
+ key: 'main',
+ elRef: this.headerElRef,
+ tableClassName: 'fc-col-header',
+ rowContent: headerRowContent,
+ }],
+ });
+ }
+ sections.push({
+ type: 'body',
+ key: 'body',
+ liquid: true,
+ chunks: [{
+ key: 'main',
+ content: bodyContent,
+ }],
+ });
+ if (stickyFooterScrollbar) {
+ sections.push({
+ type: 'footer',
+ key: 'footer',
+ isSticky: true,
+ chunks: [{
+ key: 'main',
+ content: renderScrollShim,
+ }],
+ });
+ }
+ return (createElement(ViewRoot, { viewSpec: context.viewSpec }, function (rootElRef, classNames) { return (createElement("div", { ref: rootElRef, className: ['fc-daygrid'].concat(classNames).join(' ') },
+ createElement(ScrollGrid, { liquid: !props.isHeightAuto && !props.forPrint, collapsibleWidth: props.forPrint, colGroups: [{ cols: [{ span: colCnt, minWidth: dayMinWidth }] }], sections: sections }))); }));
+ };
+ return TableView;
+ }(DateComponent));
+
+ function splitSegsByRow(segs, rowCnt) {
+ var byRow = [];
+ for (var i = 0; i < rowCnt; i += 1) {
+ byRow[i] = [];
+ }
+ for (var _i = 0, segs_1 = segs; _i < segs_1.length; _i++) {
+ var seg = segs_1[_i];
+ byRow[seg.row].push(seg);
+ }
+ return byRow;
+ }
+ function splitSegsByFirstCol(segs, colCnt) {
+ var byCol = [];
+ for (var i = 0; i < colCnt; i += 1) {
+ byCol[i] = [];
+ }
+ for (var _i = 0, segs_2 = segs; _i < segs_2.length; _i++) {
+ var seg = segs_2[_i];
+ byCol[seg.firstCol].push(seg);
+ }
+ return byCol;
+ }
+ function splitInteractionByRow(ui, rowCnt) {
+ var byRow = [];
+ if (!ui) {
+ for (var i = 0; i < rowCnt; i += 1) {
+ byRow[i] = null;
+ }
+ }
+ else {
+ for (var i = 0; i < rowCnt; i += 1) {
+ byRow[i] = {
+ affectedInstances: ui.affectedInstances,
+ isEvent: ui.isEvent,
+ segs: [],
+ };
+ }
+ for (var _i = 0, _a = ui.segs; _i < _a.length; _i++) {
+ var seg = _a[_i];
+ byRow[seg.row].segs.push(seg);
+ }
+ }
+ return byRow;
+ }
+
+ var TableCellTop = /** @class */ (function (_super) {
+ __extends(TableCellTop, _super);
+ function TableCellTop() {
+ return _super !== null && _super.apply(this, arguments) || this;
+ }
+ TableCellTop.prototype.render = function () {
+ var props = this.props;
+ var navLinkAttrs = this.context.options.navLinks
+ ? { 'data-navlink': buildNavLinkData(props.date), tabIndex: 0 }
+ : {};
+ return (createElement(DayCellContent, { date: props.date, dateProfile: props.dateProfile, todayRange: props.todayRange, showDayNumber: props.showDayNumber, extraHookProps: props.extraHookProps, defaultContent: renderTopInner }, function (innerElRef, innerContent) { return ((innerContent || props.forceDayTop) && (createElement("div", { className: "fc-daygrid-day-top", ref: innerElRef },
+ createElement("a", __assign({ className: "fc-daygrid-day-number" }, navLinkAttrs), innerContent || createElement(Fragment, null, "\u00A0"))))); }));
+ };
+ return TableCellTop;
+ }(BaseComponent));
+ function renderTopInner(props) {
+ return props.dayNumberText;
+ }
+
+ var DEFAULT_TABLE_EVENT_TIME_FORMAT = createFormatter({
+ hour: 'numeric',
+ minute: '2-digit',
+ omitZeroMinute: true,
+ meridiem: 'narrow',
+ });
+ function hasListItemDisplay(seg) {
+ var display = seg.eventRange.ui.display;
+ return display === 'list-item' || (display === 'auto' &&
+ !seg.eventRange.def.allDay &&
+ seg.firstCol === seg.lastCol && // can't be multi-day
+ seg.isStart && // "
+ seg.isEnd // "
+ );
+ }
+
+ var TableBlockEvent = /** @class */ (function (_super) {
+ __extends(TableBlockEvent, _super);
+ function TableBlockEvent() {
+ return _super !== null && _super.apply(this, arguments) || this;
+ }
+ TableBlockEvent.prototype.render = function () {
+ var props = this.props;
+ return (createElement(StandardEvent, __assign({}, props, { extraClassNames: ['fc-daygrid-event', 'fc-daygrid-block-event', 'fc-h-event'], defaultTimeFormat: DEFAULT_TABLE_EVENT_TIME_FORMAT, defaultDisplayEventEnd: props.defaultDisplayEventEnd, disableResizing: !props.seg.eventRange.def.allDay })));
+ };
+ return TableBlockEvent;
+ }(BaseComponent));
+
+ var TableListItemEvent = /** @class */ (function (_super) {
+ __extends(TableListItemEvent, _super);
+ function TableListItemEvent() {
+ return _super !== null && _super.apply(this, arguments) || this;
+ }
+ TableListItemEvent.prototype.render = function () {
+ var _a = this, props = _a.props, context = _a.context;
+ var timeFormat = context.options.eventTimeFormat || DEFAULT_TABLE_EVENT_TIME_FORMAT;
+ var timeText = buildSegTimeText(props.seg, timeFormat, context, true, props.defaultDisplayEventEnd);
+ return (createElement(EventRoot, { seg: props.seg, timeText: timeText, defaultContent: renderInnerContent$2, isDragging: props.isDragging, isResizing: false, isDateSelecting: false, isSelected: props.isSelected, isPast: props.isPast, isFuture: props.isFuture, isToday: props.isToday }, function (rootElRef, classNames, innerElRef, innerContent) { return ( // we don't use styles!
+ createElement("a", __assign({ className: ['fc-daygrid-event', 'fc-daygrid-dot-event'].concat(classNames).join(' '), ref: rootElRef }, getSegAnchorAttrs(props.seg)), innerContent)); }));
+ };
+ return TableListItemEvent;
+ }(BaseComponent));
+ function renderInnerContent$2(innerProps) {
+ return (createElement(Fragment, null,
+ createElement("div", { className: "fc-daygrid-event-dot", style: { borderColor: innerProps.borderColor || innerProps.backgroundColor } }),
+ innerProps.timeText && (createElement("div", { className: "fc-event-time" }, innerProps.timeText)),
+ createElement("div", { className: "fc-event-title" }, innerProps.event.title || createElement(Fragment, null, "\u00A0"))));
+ }
+ function getSegAnchorAttrs(seg) {
+ var url = seg.eventRange.def.url;
+ return url ? { href: url } : {};
+ }
+
+ var TableCellMoreLink = /** @class */ (function (_super) {
+ __extends(TableCellMoreLink, _super);
+ function TableCellMoreLink() {
+ var _this = _super !== null && _super.apply(this, arguments) || this;
+ _this.compileSegs = memoize(compileSegs);
+ return _this;
+ }
+ TableCellMoreLink.prototype.render = function () {
+ var props = this.props;
+ var _a = this.compileSegs(props.singlePlacements), allSegs = _a.allSegs, invisibleSegs = _a.invisibleSegs;
+ return (createElement(MoreLinkRoot, { dateProfile: props.dateProfile, todayRange: props.todayRange, allDayDate: props.allDayDate, moreCnt: props.moreCnt, allSegs: allSegs, hiddenSegs: invisibleSegs, alignmentElRef: props.alignmentElRef, alignGridTop: props.alignGridTop, extraDateSpan: props.extraDateSpan, popoverContent: function () {
+ var isForcedInvisible = (props.eventDrag ? props.eventDrag.affectedInstances : null) ||
+ (props.eventResize ? props.eventResize.affectedInstances : null) ||
+ {};
+ return (createElement(Fragment, null, allSegs.map(function (seg) {
+ var instanceId = seg.eventRange.instance.instanceId;
+ return (createElement("div", { className: "fc-daygrid-event-harness", key: instanceId, style: {
+ visibility: isForcedInvisible[instanceId] ? 'hidden' : '',
+ } }, hasListItemDisplay(seg) ? (createElement(TableListItemEvent, __assign({ seg: seg, isDragging: false, isSelected: instanceId === props.eventSelection, defaultDisplayEventEnd: false }, getSegMeta(seg, props.todayRange)))) : (createElement(TableBlockEvent, __assign({ seg: seg, isDragging: false, isResizing: false, isDateSelecting: false, isSelected: instanceId === props.eventSelection, defaultDisplayEventEnd: false }, getSegMeta(seg, props.todayRange))))));
+ })));
+ } }, function (rootElRef, classNames, innerElRef, innerContent, handleClick) { return (createElement("a", { ref: rootElRef, className: ['fc-daygrid-more-link'].concat(classNames).join(' '), onClick: handleClick }, innerContent)); }));
+ };
+ return TableCellMoreLink;
+ }(BaseComponent));
+ function compileSegs(singlePlacements) {
+ var allSegs = [];
+ var invisibleSegs = [];
+ for (var _i = 0, singlePlacements_1 = singlePlacements; _i < singlePlacements_1.length; _i++) {
+ var placement = singlePlacements_1[_i];
+ allSegs.push(placement.seg);
+ if (!placement.isVisible) {
+ invisibleSegs.push(placement.seg);
+ }
+ }
+ return { allSegs: allSegs, invisibleSegs: invisibleSegs };
+ }
+
+ var DEFAULT_WEEK_NUM_FORMAT$1 = createFormatter({ week: 'narrow' });
+ var TableCell = /** @class */ (function (_super) {
+ __extends(TableCell, _super);
+ function TableCell() {
+ var _this = _super !== null && _super.apply(this, arguments) || this;
+ _this.rootElRef = createRef();
+ _this.handleRootEl = function (el) {
+ setRef(_this.rootElRef, el);
+ setRef(_this.props.elRef, el);
+ };
+ return _this;
+ }
+ TableCell.prototype.render = function () {
+ var _a = this, props = _a.props, context = _a.context, rootElRef = _a.rootElRef;
+ var options = context.options;
+ var date = props.date, dateProfile = props.dateProfile;
+ var navLinkAttrs = options.navLinks
+ ? { 'data-navlink': buildNavLinkData(date, 'week'), tabIndex: 0 }
+ : {};
+ return (createElement(DayCellRoot, { date: date, dateProfile: dateProfile, todayRange: props.todayRange, showDayNumber: props.showDayNumber, extraHookProps: props.extraHookProps, elRef: this.handleRootEl }, function (dayElRef, dayClassNames, rootDataAttrs, isDisabled) { return (createElement("td", __assign({ ref: dayElRef, className: ['fc-daygrid-day'].concat(dayClassNames, props.extraClassNames || []).join(' ') }, rootDataAttrs, props.extraDataAttrs),
+ createElement("div", { className: "fc-daygrid-day-frame fc-scrollgrid-sync-inner", ref: props.innerElRef /* different from hook system! RENAME */ },
+ props.showWeekNumber && (createElement(WeekNumberRoot, { date: date, defaultFormat: DEFAULT_WEEK_NUM_FORMAT$1 }, function (weekElRef, weekClassNames, innerElRef, innerContent) { return (createElement("a", __assign({ ref: weekElRef, className: ['fc-daygrid-week-number'].concat(weekClassNames).join(' ') }, navLinkAttrs), innerContent)); })),
+ !isDisabled && (createElement(TableCellTop, { date: date, dateProfile: dateProfile, showDayNumber: props.showDayNumber, forceDayTop: props.forceDayTop, todayRange: props.todayRange, extraHookProps: props.extraHookProps })),
+ createElement("div", { className: "fc-daygrid-day-events", ref: props.fgContentElRef },
+ props.fgContent,
+ createElement("div", { className: "fc-daygrid-day-bottom", style: { marginTop: props.moreMarginTop } },
+ createElement(TableCellMoreLink, { allDayDate: date, singlePlacements: props.singlePlacements, moreCnt: props.moreCnt, alignmentElRef: rootElRef, alignGridTop: !props.showDayNumber, extraDateSpan: props.extraDateSpan, dateProfile: props.dateProfile, eventSelection: props.eventSelection, eventDrag: props.eventDrag, eventResize: props.eventResize, todayRange: props.todayRange }))),
+ createElement("div", { className: "fc-daygrid-day-bg" }, props.bgContent)))); }));
+ };
+ return TableCell;
+ }(DateComponent));
+
+ function computeFgSegPlacement(segs, // assumed already sorted
+ dayMaxEvents, dayMaxEventRows, strictOrder, eventInstanceHeights, maxContentHeight, cells) {
+ var hierarchy = new DayGridSegHierarchy();
+ hierarchy.allowReslicing = true;
+ hierarchy.strictOrder = strictOrder;
+ if (dayMaxEvents === true || dayMaxEventRows === true) {
+ hierarchy.maxCoord = maxContentHeight;
+ hierarchy.hiddenConsumes = true;
+ }
+ else if (typeof dayMaxEvents === 'number') {
+ hierarchy.maxStackCnt = dayMaxEvents;
+ }
+ else if (typeof dayMaxEventRows === 'number') {
+ hierarchy.maxStackCnt = dayMaxEventRows;
+ hierarchy.hiddenConsumes = true;
+ }
+ // create segInputs only for segs with known heights
+ var segInputs = [];
+ var unknownHeightSegs = [];
+ for (var i = 0; i < segs.length; i += 1) {
+ var seg = segs[i];
+ var instanceId = seg.eventRange.instance.instanceId;
+ var eventHeight = eventInstanceHeights[instanceId];
+ if (eventHeight != null) {
+ segInputs.push({
+ index: i,
+ thickness: eventHeight,
+ span: {
+ start: seg.firstCol,
+ end: seg.lastCol + 1,
+ },
+ });
+ }
+ else {
+ unknownHeightSegs.push(seg);
+ }
+ }
+ var hiddenEntries = hierarchy.addSegs(segInputs);
+ var segRects = hierarchy.toRects();
+ var _a = placeRects(segRects, segs, cells), singleColPlacements = _a.singleColPlacements, multiColPlacements = _a.multiColPlacements, leftoverMargins = _a.leftoverMargins;
+ var moreCnts = [];
+ var moreMarginTops = [];
+ // add segs with unknown heights
+ for (var _i = 0, unknownHeightSegs_1 = unknownHeightSegs; _i < unknownHeightSegs_1.length; _i++) {
+ var seg = unknownHeightSegs_1[_i];
+ multiColPlacements[seg.firstCol].push({
+ seg: seg,
+ isVisible: false,
+ isAbsolute: true,
+ absoluteTop: 0,
+ marginTop: 0,
+ });
+ for (var col = seg.firstCol; col <= seg.lastCol; col += 1) {
+ singleColPlacements[col].push({
+ seg: resliceSeg(seg, col, col + 1, cells),
+ isVisible: false,
+ isAbsolute: false,
+ absoluteTop: 0,
+ marginTop: 0,
+ });
+ }
+ }
+ // add the hidden entries
+ for (var col = 0; col < cells.length; col += 1) {
+ moreCnts.push(0);
+ }
+ for (var _b = 0, hiddenEntries_1 = hiddenEntries; _b < hiddenEntries_1.length; _b++) {
+ var hiddenEntry = hiddenEntries_1[_b];
+ var seg = segs[hiddenEntry.index];
+ var hiddenSpan = hiddenEntry.span;
+ multiColPlacements[hiddenSpan.start].push({
+ seg: resliceSeg(seg, hiddenSpan.start, hiddenSpan.end, cells),
+ isVisible: false,
+ isAbsolute: true,
+ absoluteTop: 0,
+ marginTop: 0,
+ });
+ for (var col = hiddenSpan.start; col < hiddenSpan.end; col += 1) {
+ moreCnts[col] += 1;
+ singleColPlacements[col].push({
+ seg: resliceSeg(seg, col, col + 1, cells),
+ isVisible: false,
+ isAbsolute: false,
+ absoluteTop: 0,
+ marginTop: 0,
+ });
+ }
+ }
+ // deal with leftover margins
+ for (var col = 0; col < cells.length; col += 1) {
+ moreMarginTops.push(leftoverMargins[col]);
+ }
+ return { singleColPlacements: singleColPlacements, multiColPlacements: multiColPlacements, moreCnts: moreCnts, moreMarginTops: moreMarginTops };
+ }
+ // rects ordered by top coord, then left
+ function placeRects(allRects, segs, cells) {
+ var rectsByEachCol = groupRectsByEachCol(allRects, cells.length);
+ var singleColPlacements = [];
+ var multiColPlacements = [];
+ var leftoverMargins = [];
+ for (var col = 0; col < cells.length; col += 1) {
+ var rects = rectsByEachCol[col];
+ // compute all static segs in singlePlacements
+ var singlePlacements = [];
+ var currentHeight = 0;
+ var currentMarginTop = 0;
+ for (var _i = 0, rects_1 = rects; _i < rects_1.length; _i++) {
+ var rect = rects_1[_i];
+ var seg = segs[rect.index];
+ singlePlacements.push({
+ seg: resliceSeg(seg, col, col + 1, cells),
+ isVisible: true,
+ isAbsolute: false,
+ absoluteTop: rect.levelCoord,
+ marginTop: rect.levelCoord - currentHeight,
+ });
+ currentHeight = rect.levelCoord + rect.thickness;
+ }
+ // compute mixed static/absolute segs in multiPlacements
+ var multiPlacements = [];
+ currentHeight = 0;
+ currentMarginTop = 0;
+ for (var _a = 0, rects_2 = rects; _a < rects_2.length; _a++) {
+ var rect = rects_2[_a];
+ var seg = segs[rect.index];
+ var isAbsolute = rect.span.end - rect.span.start > 1; // multi-column?
+ var isFirstCol = rect.span.start === col;
+ currentMarginTop += rect.levelCoord - currentHeight; // amount of space since bottom of previous seg
+ currentHeight = rect.levelCoord + rect.thickness; // height will now be bottom of current seg
+ if (isAbsolute) {
+ currentMarginTop += rect.thickness;
+ if (isFirstCol) {
+ multiPlacements.push({
+ seg: resliceSeg(seg, rect.span.start, rect.span.end, cells),
+ isVisible: true,
+ isAbsolute: true,
+ absoluteTop: rect.levelCoord,
+ marginTop: 0,
+ });
+ }
+ }
+ else if (isFirstCol) {
+ multiPlacements.push({
+ seg: resliceSeg(seg, rect.span.start, rect.span.end, cells),
+ isVisible: true,
+ isAbsolute: false,
+ absoluteTop: rect.levelCoord,
+ marginTop: currentMarginTop, // claim the margin
+ });
+ currentMarginTop = 0;
+ }
+ }
+ singleColPlacements.push(singlePlacements);
+ multiColPlacements.push(multiPlacements);
+ leftoverMargins.push(currentMarginTop);
+ }
+ return { singleColPlacements: singleColPlacements, multiColPlacements: multiColPlacements, leftoverMargins: leftoverMargins };
+ }
+ function groupRectsByEachCol(rects, colCnt) {
+ var rectsByEachCol = [];
+ for (var col = 0; col < colCnt; col += 1) {
+ rectsByEachCol.push([]);
+ }
+ for (var _i = 0, rects_3 = rects; _i < rects_3.length; _i++) {
+ var rect = rects_3[_i];
+ for (var col = rect.span.start; col < rect.span.end; col += 1) {
+ rectsByEachCol[col].push(rect);
+ }
+ }
+ return rectsByEachCol;
+ }
+ function resliceSeg(seg, spanStart, spanEnd, cells) {
+ if (seg.firstCol === spanStart && seg.lastCol === spanEnd - 1) {
+ return seg;
+ }
+ var eventRange = seg.eventRange;
+ var origRange = eventRange.range;
+ var slicedRange = intersectRanges(origRange, {
+ start: cells[spanStart].date,
+ end: addDays(cells[spanEnd - 1].date, 1),
+ });
+ return __assign(__assign({}, seg), { firstCol: spanStart, lastCol: spanEnd - 1, eventRange: {
+ def: eventRange.def,
+ ui: __assign(__assign({}, eventRange.ui), { durationEditable: false }),
+ instance: eventRange.instance,
+ range: slicedRange,
+ }, isStart: seg.isStart && slicedRange.start.valueOf() === origRange.start.valueOf(), isEnd: seg.isEnd && slicedRange.end.valueOf() === origRange.end.valueOf() });
+ }
+ var DayGridSegHierarchy = /** @class */ (function (_super) {
+ __extends(DayGridSegHierarchy, _super);
+ function DayGridSegHierarchy() {
+ var _this = _super !== null && _super.apply(this, arguments) || this;
+ // config
+ _this.hiddenConsumes = false;
+ // allows us to keep hidden entries in the hierarchy so they take up space
+ _this.forceHidden = {};
+ return _this;
+ }
+ DayGridSegHierarchy.prototype.addSegs = function (segInputs) {
+ var _this = this;
+ var hiddenSegs = _super.prototype.addSegs.call(this, segInputs);
+ var entriesByLevel = this.entriesByLevel;
+ var excludeHidden = function (entry) { return !_this.forceHidden[buildEntryKey(entry)]; };
+ // remove the forced-hidden segs
+ for (var level = 0; level < entriesByLevel.length; level += 1) {
+ entriesByLevel[level] = entriesByLevel[level].filter(excludeHidden);
+ }
+ return hiddenSegs;
+ };
+ DayGridSegHierarchy.prototype.handleInvalidInsertion = function (insertion, entry, hiddenEntries) {
+ var _a = this, entriesByLevel = _a.entriesByLevel, forceHidden = _a.forceHidden;
+ var touchingEntry = insertion.touchingEntry, touchingLevel = insertion.touchingLevel, touchingLateral = insertion.touchingLateral;
+ if (this.hiddenConsumes && touchingEntry) {
+ var touchingEntryId = buildEntryKey(touchingEntry);
+ // if not already hidden
+ if (!forceHidden[touchingEntryId]) {
+ if (this.allowReslicing) {
+ var placeholderEntry = __assign(__assign({}, touchingEntry), { span: intersectSpans(touchingEntry.span, entry.span) });
+ var placeholderEntryId = buildEntryKey(placeholderEntry);
+ forceHidden[placeholderEntryId] = true;
+ entriesByLevel[touchingLevel][touchingLateral] = placeholderEntry; // replace touchingEntry with our placeholder
+ this.splitEntry(touchingEntry, entry, hiddenEntries); // split up the touchingEntry, reinsert it
+ }
+ else {
+ forceHidden[touchingEntryId] = true;
+ hiddenEntries.push(touchingEntry);
+ }
+ }
+ }
+ return _super.prototype.handleInvalidInsertion.call(this, insertion, entry, hiddenEntries);
+ };
+ return DayGridSegHierarchy;
+ }(SegHierarchy));
+
+ var TableRow = /** @class */ (function (_super) {
+ __extends(TableRow, _super);
+ function TableRow() {
+ var _this = _super !== null && _super.apply(this, arguments) || this;
+ _this.cellElRefs = new RefMap(); // the
+ _this.frameElRefs = new RefMap(); // the fc-daygrid-day-frame
+ _this.fgElRefs = new RefMap(); // the fc-daygrid-day-events
+ _this.segHarnessRefs = new RefMap(); // indexed by "instanceId:firstCol"
+ _this.rootElRef = createRef();
+ _this.state = {
+ framePositions: null,
+ maxContentHeight: null,
+ eventInstanceHeights: {},
+ };
+ return _this;
+ }
+ TableRow.prototype.render = function () {
+ var _this = this;
+ var _a = this, props = _a.props, state = _a.state, context = _a.context;
+ var options = context.options;
+ var colCnt = props.cells.length;
+ var businessHoursByCol = splitSegsByFirstCol(props.businessHourSegs, colCnt);
+ var bgEventSegsByCol = splitSegsByFirstCol(props.bgEventSegs, colCnt);
+ var highlightSegsByCol = splitSegsByFirstCol(this.getHighlightSegs(), colCnt);
+ var mirrorSegsByCol = splitSegsByFirstCol(this.getMirrorSegs(), colCnt);
+ var _b = computeFgSegPlacement(sortEventSegs(props.fgEventSegs, options.eventOrder), props.dayMaxEvents, props.dayMaxEventRows, options.eventOrderStrict, state.eventInstanceHeights, state.maxContentHeight, props.cells), singleColPlacements = _b.singleColPlacements, multiColPlacements = _b.multiColPlacements, moreCnts = _b.moreCnts, moreMarginTops = _b.moreMarginTops;
+ var isForcedInvisible = // TODO: messy way to compute this
+ (props.eventDrag && props.eventDrag.affectedInstances) ||
+ (props.eventResize && props.eventResize.affectedInstances) ||
+ {};
+ return (createElement("tr", { ref: this.rootElRef },
+ props.renderIntro && props.renderIntro(),
+ props.cells.map(function (cell, col) {
+ var normalFgNodes = _this.renderFgSegs(col, props.forPrint ? singleColPlacements[col] : multiColPlacements[col], props.todayRange, isForcedInvisible);
+ var mirrorFgNodes = _this.renderFgSegs(col, buildMirrorPlacements(mirrorSegsByCol[col], multiColPlacements), props.todayRange, {}, Boolean(props.eventDrag), Boolean(props.eventResize), false);
+ return (createElement(TableCell, { key: cell.key, elRef: _this.cellElRefs.createRef(cell.key), innerElRef: _this.frameElRefs.createRef(cell.key) /* FF
problem, but okay to use for left/right. TODO: rename prop */, dateProfile: props.dateProfile, date: cell.date, showDayNumber: props.showDayNumbers, showWeekNumber: props.showWeekNumbers && col === 0, forceDayTop: props.showWeekNumbers /* even displaying weeknum for row, not necessarily day */, todayRange: props.todayRange, eventSelection: props.eventSelection, eventDrag: props.eventDrag, eventResize: props.eventResize, extraHookProps: cell.extraHookProps, extraDataAttrs: cell.extraDataAttrs, extraClassNames: cell.extraClassNames, extraDateSpan: cell.extraDateSpan, moreCnt: moreCnts[col], moreMarginTop: moreMarginTops[col], singlePlacements: singleColPlacements[col], fgContentElRef: _this.fgElRefs.createRef(cell.key), fgContent: ( // Fragment scopes the keys
+ createElement(Fragment, null,
+ createElement(Fragment, null, normalFgNodes),
+ createElement(Fragment, null, mirrorFgNodes))), bgContent: ( // Fragment scopes the keys
+ createElement(Fragment, null,
+ _this.renderFillSegs(highlightSegsByCol[col], 'highlight'),
+ _this.renderFillSegs(businessHoursByCol[col], 'non-business'),
+ _this.renderFillSegs(bgEventSegsByCol[col], 'bg-event'))) }));
+ })));
+ };
+ TableRow.prototype.componentDidMount = function () {
+ this.updateSizing(true);
+ };
+ TableRow.prototype.componentDidUpdate = function (prevProps, prevState) {
+ var currentProps = this.props;
+ this.updateSizing(!isPropsEqual(prevProps, currentProps));
+ };
+ TableRow.prototype.getHighlightSegs = function () {
+ var props = this.props;
+ if (props.eventDrag && props.eventDrag.segs.length) { // messy check
+ return props.eventDrag.segs;
+ }
+ if (props.eventResize && props.eventResize.segs.length) { // messy check
+ return props.eventResize.segs;
+ }
+ return props.dateSelectionSegs;
+ };
+ TableRow.prototype.getMirrorSegs = function () {
+ var props = this.props;
+ if (props.eventResize && props.eventResize.segs.length) { // messy check
+ return props.eventResize.segs;
+ }
+ return [];
+ };
+ TableRow.prototype.renderFgSegs = function (col, segPlacements, todayRange, isForcedInvisible, isDragging, isResizing, isDateSelecting) {
+ var context = this.context;
+ var eventSelection = this.props.eventSelection;
+ var framePositions = this.state.framePositions;
+ var defaultDisplayEventEnd = this.props.cells.length === 1; // colCnt === 1
+ var isMirror = isDragging || isResizing || isDateSelecting;
+ var nodes = [];
+ if (framePositions) {
+ for (var _i = 0, segPlacements_1 = segPlacements; _i < segPlacements_1.length; _i++) {
+ var placement = segPlacements_1[_i];
+ var seg = placement.seg;
+ var instanceId = seg.eventRange.instance.instanceId;
+ var key = instanceId + ':' + col;
+ var isVisible = placement.isVisible && !isForcedInvisible[instanceId];
+ var isAbsolute = placement.isAbsolute;
+ var left = '';
+ var right = '';
+ if (isAbsolute) {
+ if (context.isRtl) {
+ right = 0;
+ left = framePositions.lefts[seg.lastCol] - framePositions.lefts[seg.firstCol];
+ }
+ else {
+ left = 0;
+ right = framePositions.rights[seg.firstCol] - framePositions.rights[seg.lastCol];
+ }
+ }
+ /*
+ known bug: events that are force to be list-item but span multiple days still take up space in later columns
+ todo: in print view, for multi-day events, don't display title within non-start/end segs
+ */
+ nodes.push(createElement("div", { className: 'fc-daygrid-event-harness' + (isAbsolute ? ' fc-daygrid-event-harness-abs' : ''), key: key, ref: isMirror ? null : this.segHarnessRefs.createRef(key), style: {
+ visibility: isVisible ? '' : 'hidden',
+ marginTop: isAbsolute ? '' : placement.marginTop,
+ top: isAbsolute ? placement.absoluteTop : '',
+ left: left,
+ right: right,
+ } }, hasListItemDisplay(seg) ? (createElement(TableListItemEvent, __assign({ seg: seg, isDragging: isDragging, isSelected: instanceId === eventSelection, defaultDisplayEventEnd: defaultDisplayEventEnd }, getSegMeta(seg, todayRange)))) : (createElement(TableBlockEvent, __assign({ seg: seg, isDragging: isDragging, isResizing: isResizing, isDateSelecting: isDateSelecting, isSelected: instanceId === eventSelection, defaultDisplayEventEnd: defaultDisplayEventEnd }, getSegMeta(seg, todayRange))))));
+ }
+ }
+ return nodes;
+ };
+ TableRow.prototype.renderFillSegs = function (segs, fillType) {
+ var isRtl = this.context.isRtl;
+ var todayRange = this.props.todayRange;
+ var framePositions = this.state.framePositions;
+ var nodes = [];
+ if (framePositions) {
+ for (var _i = 0, segs_1 = segs; _i < segs_1.length; _i++) {
+ var seg = segs_1[_i];
+ var leftRightCss = isRtl ? {
+ right: 0,
+ left: framePositions.lefts[seg.lastCol] - framePositions.lefts[seg.firstCol],
+ } : {
+ left: 0,
+ right: framePositions.rights[seg.firstCol] - framePositions.rights[seg.lastCol],
+ };
+ nodes.push(createElement("div", { key: buildEventRangeKey(seg.eventRange), className: "fc-daygrid-bg-harness", style: leftRightCss }, fillType === 'bg-event' ?
+ createElement(BgEvent, __assign({ seg: seg }, getSegMeta(seg, todayRange))) :
+ renderFill(fillType)));
+ }
+ }
+ return createElement.apply(void 0, __spreadArray([Fragment, {}], nodes));
+ };
+ TableRow.prototype.updateSizing = function (isExternalSizingChange) {
+ var _a = this, props = _a.props, frameElRefs = _a.frameElRefs;
+ if (!props.forPrint &&
+ props.clientWidth !== null // positioning ready?
+ ) {
+ if (isExternalSizingChange) {
+ var frameEls = props.cells.map(function (cell) { return frameElRefs.currentMap[cell.key]; });
+ if (frameEls.length) {
+ var originEl = this.rootElRef.current;
+ this.setState({
+ framePositions: new PositionCache(originEl, frameEls, true, // isHorizontal
+ false),
+ });
+ }
+ }
+ var limitByContentHeight = props.dayMaxEvents === true || props.dayMaxEventRows === true;
+ this.setState({
+ eventInstanceHeights: this.queryEventInstanceHeights(),
+ maxContentHeight: limitByContentHeight ? this.computeMaxContentHeight() : null,
+ });
+ }
+ };
+ TableRow.prototype.queryEventInstanceHeights = function () {
+ var segElMap = this.segHarnessRefs.currentMap;
+ var eventInstanceHeights = {};
+ // get the max height amongst instance segs
+ for (var key in segElMap) {
+ var height = Math.round(segElMap[key].getBoundingClientRect().height);
+ var instanceId = key.split(':')[0]; // deconstruct how renderFgSegs makes the key
+ eventInstanceHeights[instanceId] = Math.max(eventInstanceHeights[instanceId] || 0, height);
+ }
+ return eventInstanceHeights;
+ };
+ TableRow.prototype.computeMaxContentHeight = function () {
+ var firstKey = this.props.cells[0].key;
+ var cellEl = this.cellElRefs.currentMap[firstKey];
+ var fcContainerEl = this.fgElRefs.currentMap[firstKey];
+ return cellEl.getBoundingClientRect().bottom - fcContainerEl.getBoundingClientRect().top;
+ };
+ TableRow.prototype.getCellEls = function () {
+ var elMap = this.cellElRefs.currentMap;
+ return this.props.cells.map(function (cell) { return elMap[cell.key]; });
+ };
+ return TableRow;
+ }(DateComponent));
+ TableRow.addStateEquality({
+ eventInstanceHeights: isPropsEqual,
+ });
+ function buildMirrorPlacements(mirrorSegs, colPlacements) {
+ if (!mirrorSegs.length) {
+ return [];
+ }
+ var topsByInstanceId = buildAbsoluteTopHash(colPlacements); // TODO: cache this at first render?
+ return mirrorSegs.map(function (seg) { return ({
+ seg: seg,
+ isVisible: true,
+ isAbsolute: true,
+ absoluteTop: topsByInstanceId[seg.eventRange.instance.instanceId],
+ marginTop: 0,
+ }); });
+ }
+ function buildAbsoluteTopHash(colPlacements) {
+ var topsByInstanceId = {};
+ for (var _i = 0, colPlacements_1 = colPlacements; _i < colPlacements_1.length; _i++) {
+ var placements = colPlacements_1[_i];
+ for (var _a = 0, placements_1 = placements; _a < placements_1.length; _a++) {
+ var placement = placements_1[_a];
+ topsByInstanceId[placement.seg.eventRange.instance.instanceId] = placement.absoluteTop;
+ }
+ }
+ return topsByInstanceId;
+ }
+
+ var Table = /** @class */ (function (_super) {
+ __extends(Table, _super);
+ function Table() {
+ var _this = _super !== null && _super.apply(this, arguments) || this;
+ _this.splitBusinessHourSegs = memoize(splitSegsByRow);
+ _this.splitBgEventSegs = memoize(splitSegsByRow);
+ _this.splitFgEventSegs = memoize(splitSegsByRow);
+ _this.splitDateSelectionSegs = memoize(splitSegsByRow);
+ _this.splitEventDrag = memoize(splitInteractionByRow);
+ _this.splitEventResize = memoize(splitInteractionByRow);
+ _this.rowRefs = new RefMap();
+ _this.handleRootEl = function (rootEl) {
+ _this.rootEl = rootEl;
+ if (rootEl) {
+ _this.context.registerInteractiveComponent(_this, {
+ el: rootEl,
+ isHitComboAllowed: _this.props.isHitComboAllowed,
+ });
+ }
+ else {
+ _this.context.unregisterInteractiveComponent(_this);
+ }
+ };
+ return _this;
+ }
+ Table.prototype.render = function () {
+ var _this = this;
+ var props = this.props;
+ var dateProfile = props.dateProfile, dayMaxEventRows = props.dayMaxEventRows, dayMaxEvents = props.dayMaxEvents, expandRows = props.expandRows;
+ var rowCnt = props.cells.length;
+ var businessHourSegsByRow = this.splitBusinessHourSegs(props.businessHourSegs, rowCnt);
+ var bgEventSegsByRow = this.splitBgEventSegs(props.bgEventSegs, rowCnt);
+ var fgEventSegsByRow = this.splitFgEventSegs(props.fgEventSegs, rowCnt);
+ var dateSelectionSegsByRow = this.splitDateSelectionSegs(props.dateSelectionSegs, rowCnt);
+ var eventDragByRow = this.splitEventDrag(props.eventDrag, rowCnt);
+ var eventResizeByRow = this.splitEventResize(props.eventResize, rowCnt);
+ var limitViaBalanced = dayMaxEvents === true || dayMaxEventRows === true;
+ // if rows can't expand to fill fixed height, can't do balanced-height event limit
+ // TODO: best place to normalize these options?
+ if (limitViaBalanced && !expandRows) {
+ limitViaBalanced = false;
+ dayMaxEventRows = null;
+ dayMaxEvents = null;
+ }
+ var classNames = [
+ 'fc-daygrid-body',
+ limitViaBalanced ? 'fc-daygrid-body-balanced' : 'fc-daygrid-body-unbalanced',
+ expandRows ? '' : 'fc-daygrid-body-natural', // will height of one row depend on the others?
+ ];
+ return (createElement("div", { className: classNames.join(' '), ref: this.handleRootEl, style: {
+ // these props are important to give this wrapper correct dimensions for interactions
+ // TODO: if we set it here, can we avoid giving to inner tables?
+ width: props.clientWidth,
+ minWidth: props.tableMinWidth,
+ } },
+ createElement(NowTimer, { unit: "day" }, function (nowDate, todayRange) { return (createElement(Fragment, null,
+ createElement("table", { className: "fc-scrollgrid-sync-table", style: {
+ width: props.clientWidth,
+ minWidth: props.tableMinWidth,
+ height: expandRows ? props.clientHeight : '',
+ } },
+ props.colGroupNode,
+ createElement("tbody", null, props.cells.map(function (cells, row) { return (createElement(TableRow, { ref: _this.rowRefs.createRef(row), key: cells.length
+ ? cells[0].date.toISOString() /* best? or put key on cell? or use diff formatter? */
+ : row // in case there are no cells (like when resource view is loading)
+ , showDayNumbers: rowCnt > 1, showWeekNumbers: props.showWeekNumbers, todayRange: todayRange, dateProfile: dateProfile, cells: cells, renderIntro: props.renderRowIntro, businessHourSegs: businessHourSegsByRow[row], eventSelection: props.eventSelection, bgEventSegs: bgEventSegsByRow[row].filter(isSegAllDay) /* hack */, fgEventSegs: fgEventSegsByRow[row], dateSelectionSegs: dateSelectionSegsByRow[row], eventDrag: eventDragByRow[row], eventResize: eventResizeByRow[row], dayMaxEvents: dayMaxEvents, dayMaxEventRows: dayMaxEventRows, clientWidth: props.clientWidth, clientHeight: props.clientHeight, forPrint: props.forPrint })); }))))); })));
+ };
+ // Hit System
+ // ----------------------------------------------------------------------------------------------------
+ Table.prototype.prepareHits = function () {
+ this.rowPositions = new PositionCache(this.rootEl, this.rowRefs.collect().map(function (rowObj) { return rowObj.getCellEls()[0]; }), // first cell el in each row. TODO: not optimal
+ false, true);
+ this.colPositions = new PositionCache(this.rootEl, this.rowRefs.currentMap[0].getCellEls(), // cell els in first row
+ true, // horizontal
+ false);
+ };
+ Table.prototype.queryHit = function (positionLeft, positionTop) {
+ var _a = this, colPositions = _a.colPositions, rowPositions = _a.rowPositions;
+ var col = colPositions.leftToIndex(positionLeft);
+ var row = rowPositions.topToIndex(positionTop);
+ if (row != null && col != null) {
+ var cell = this.props.cells[row][col];
+ return {
+ dateProfile: this.props.dateProfile,
+ dateSpan: __assign({ range: this.getCellRange(row, col), allDay: true }, cell.extraDateSpan),
+ dayEl: this.getCellEl(row, col),
+ rect: {
+ left: colPositions.lefts[col],
+ right: colPositions.rights[col],
+ top: rowPositions.tops[row],
+ bottom: rowPositions.bottoms[row],
+ },
+ layer: 0,
+ };
+ }
+ return null;
+ };
+ Table.prototype.getCellEl = function (row, col) {
+ return this.rowRefs.currentMap[row].getCellEls()[col]; // TODO: not optimal
+ };
+ Table.prototype.getCellRange = function (row, col) {
+ var start = this.props.cells[row][col].date;
+ var end = addDays(start, 1);
+ return { start: start, end: end };
+ };
+ return Table;
+ }(DateComponent));
+ function isSegAllDay(seg) {
+ return seg.eventRange.def.allDay;
+ }
+
+ var DayTableSlicer = /** @class */ (function (_super) {
+ __extends(DayTableSlicer, _super);
+ function DayTableSlicer() {
+ var _this = _super !== null && _super.apply(this, arguments) || this;
+ _this.forceDayIfListItem = true;
+ return _this;
+ }
+ DayTableSlicer.prototype.sliceRange = function (dateRange, dayTableModel) {
+ return dayTableModel.sliceRange(dateRange);
+ };
+ return DayTableSlicer;
+ }(Slicer));
+
+ var DayTable = /** @class */ (function (_super) {
+ __extends(DayTable, _super);
+ function DayTable() {
+ var _this = _super !== null && _super.apply(this, arguments) || this;
+ _this.slicer = new DayTableSlicer();
+ _this.tableRef = createRef();
+ return _this;
+ }
+ DayTable.prototype.render = function () {
+ var _a = this, props = _a.props, context = _a.context;
+ return (createElement(Table, __assign({ ref: this.tableRef }, this.slicer.sliceProps(props, props.dateProfile, props.nextDayThreshold, context, props.dayTableModel), { dateProfile: props.dateProfile, cells: props.dayTableModel.cells, colGroupNode: props.colGroupNode, tableMinWidth: props.tableMinWidth, renderRowIntro: props.renderRowIntro, dayMaxEvents: props.dayMaxEvents, dayMaxEventRows: props.dayMaxEventRows, showWeekNumbers: props.showWeekNumbers, expandRows: props.expandRows, headerAlignElRef: props.headerAlignElRef, clientWidth: props.clientWidth, clientHeight: props.clientHeight, forPrint: props.forPrint })));
+ };
+ return DayTable;
+ }(DateComponent));
+
+ var DayTableView = /** @class */ (function (_super) {
+ __extends(DayTableView, _super);
+ function DayTableView() {
+ var _this = _super !== null && _super.apply(this, arguments) || this;
+ _this.buildDayTableModel = memoize(buildDayTableModel);
+ _this.headerRef = createRef();
+ _this.tableRef = createRef();
+ return _this;
+ }
+ DayTableView.prototype.render = function () {
+ var _this = this;
+ var _a = this.context, options = _a.options, dateProfileGenerator = _a.dateProfileGenerator;
+ var props = this.props;
+ var dayTableModel = this.buildDayTableModel(props.dateProfile, dateProfileGenerator);
+ var headerContent = options.dayHeaders && (createElement(DayHeader, { ref: this.headerRef, dateProfile: props.dateProfile, dates: dayTableModel.headerDates, datesRepDistinctDays: dayTableModel.rowCnt === 1 }));
+ var bodyContent = function (contentArg) { return (createElement(DayTable, { ref: _this.tableRef, dateProfile: props.dateProfile, dayTableModel: dayTableModel, businessHours: props.businessHours, dateSelection: props.dateSelection, eventStore: props.eventStore, eventUiBases: props.eventUiBases, eventSelection: props.eventSelection, eventDrag: props.eventDrag, eventResize: props.eventResize, nextDayThreshold: options.nextDayThreshold, colGroupNode: contentArg.tableColGroupNode, tableMinWidth: contentArg.tableMinWidth, dayMaxEvents: options.dayMaxEvents, dayMaxEventRows: options.dayMaxEventRows, showWeekNumbers: options.weekNumbers, expandRows: !props.isHeightAuto, headerAlignElRef: _this.headerElRef, clientWidth: contentArg.clientWidth, clientHeight: contentArg.clientHeight, forPrint: props.forPrint })); };
+ return options.dayMinWidth
+ ? this.renderHScrollLayout(headerContent, bodyContent, dayTableModel.colCnt, options.dayMinWidth)
+ : this.renderSimpleLayout(headerContent, bodyContent);
+ };
+ return DayTableView;
+ }(TableView));
+ function buildDayTableModel(dateProfile, dateProfileGenerator) {
+ var daySeries = new DaySeriesModel(dateProfile.renderRange, dateProfileGenerator);
+ return new DayTableModel(daySeries, /year|month|week/.test(dateProfile.currentRangeUnit));
+ }
+
+ var TableDateProfileGenerator = /** @class */ (function (_super) {
+ __extends(TableDateProfileGenerator, _super);
+ function TableDateProfileGenerator() {
+ return _super !== null && _super.apply(this, arguments) || this;
+ }
+ // Computes the date range that will be rendered.
+ TableDateProfileGenerator.prototype.buildRenderRange = function (currentRange, currentRangeUnit, isRangeAllDay) {
+ var dateEnv = this.props.dateEnv;
+ var renderRange = _super.prototype.buildRenderRange.call(this, currentRange, currentRangeUnit, isRangeAllDay);
+ var start = renderRange.start;
+ var end = renderRange.end;
+ var endOfWeek;
+ // year and month views should be aligned with weeks. this is already done for week
+ if (/^(year|month)$/.test(currentRangeUnit)) {
+ start = dateEnv.startOfWeek(start);
+ // make end-of-week if not already
+ endOfWeek = dateEnv.startOfWeek(end);
+ if (endOfWeek.valueOf() !== end.valueOf()) {
+ end = addWeeks(endOfWeek, 1);
+ }
+ }
+ // ensure 6 weeks
+ if (this.props.monthMode &&
+ this.props.fixedWeekCount) {
+ var rowCnt = Math.ceil(// could be partial weeks due to hiddenDays
+ diffWeeks(start, end));
+ end = addWeeks(end, 6 - rowCnt);
+ }
+ return { start: start, end: end };
+ };
+ return TableDateProfileGenerator;
+ }(DateProfileGenerator));
+
+ var dayGridPlugin = createPlugin({
+ initialView: 'dayGridMonth',
+ views: {
+ dayGrid: {
+ component: DayTableView,
+ dateProfileGeneratorClass: TableDateProfileGenerator,
+ },
+ dayGridDay: {
+ type: 'dayGrid',
+ duration: { days: 1 },
+ },
+ dayGridWeek: {
+ type: 'dayGrid',
+ duration: { weeks: 1 },
+ },
+ dayGridMonth: {
+ type: 'dayGrid',
+ duration: { months: 1 },
+ monthMode: true,
+ fixedWeekCount: true,
+ },
+ },
+ });
+
+ var AllDaySplitter = /** @class */ (function (_super) {
+ __extends(AllDaySplitter, _super);
+ function AllDaySplitter() {
+ return _super !== null && _super.apply(this, arguments) || this;
+ }
+ AllDaySplitter.prototype.getKeyInfo = function () {
+ return {
+ allDay: {},
+ timed: {},
+ };
+ };
+ AllDaySplitter.prototype.getKeysForDateSpan = function (dateSpan) {
+ if (dateSpan.allDay) {
+ return ['allDay'];
+ }
+ return ['timed'];
+ };
+ AllDaySplitter.prototype.getKeysForEventDef = function (eventDef) {
+ if (!eventDef.allDay) {
+ return ['timed'];
+ }
+ if (hasBgRendering(eventDef)) {
+ return ['timed', 'allDay'];
+ }
+ return ['allDay'];
+ };
+ return AllDaySplitter;
+ }(Splitter));
+
+ var DEFAULT_SLAT_LABEL_FORMAT = createFormatter({
+ hour: 'numeric',
+ minute: '2-digit',
+ omitZeroMinute: true,
+ meridiem: 'short',
+ });
+ function TimeColsAxisCell(props) {
+ var classNames = [
+ 'fc-timegrid-slot',
+ 'fc-timegrid-slot-label',
+ props.isLabeled ? 'fc-scrollgrid-shrink' : 'fc-timegrid-slot-minor',
+ ];
+ return (createElement(ViewContextType.Consumer, null, function (context) {
+ if (!props.isLabeled) {
+ return (createElement("td", { className: classNames.join(' '), "data-time": props.isoTimeStr }));
+ }
+ var dateEnv = context.dateEnv, options = context.options, viewApi = context.viewApi;
+ var labelFormat = // TODO: fully pre-parse
+ options.slotLabelFormat == null ? DEFAULT_SLAT_LABEL_FORMAT :
+ Array.isArray(options.slotLabelFormat) ? createFormatter(options.slotLabelFormat[0]) :
+ createFormatter(options.slotLabelFormat);
+ var hookProps = {
+ level: 0,
+ time: props.time,
+ date: dateEnv.toDate(props.date),
+ view: viewApi,
+ text: dateEnv.format(props.date, labelFormat),
+ };
+ return (createElement(RenderHook, { hookProps: hookProps, classNames: options.slotLabelClassNames, content: options.slotLabelContent, defaultContent: renderInnerContent$1, didMount: options.slotLabelDidMount, willUnmount: options.slotLabelWillUnmount }, function (rootElRef, customClassNames, innerElRef, innerContent) { return (createElement("td", { ref: rootElRef, className: classNames.concat(customClassNames).join(' '), "data-time": props.isoTimeStr },
+ createElement("div", { className: "fc-timegrid-slot-label-frame fc-scrollgrid-shrink-frame" },
+ createElement("div", { className: "fc-timegrid-slot-label-cushion fc-scrollgrid-shrink-cushion", ref: innerElRef }, innerContent)))); }));
+ }));
+ }
+ function renderInnerContent$1(props) {
+ return props.text;
+ }
+
+ var TimeBodyAxis = /** @class */ (function (_super) {
+ __extends(TimeBodyAxis, _super);
+ function TimeBodyAxis() {
+ return _super !== null && _super.apply(this, arguments) || this;
+ }
+ TimeBodyAxis.prototype.render = function () {
+ return this.props.slatMetas.map(function (slatMeta) { return (createElement("tr", { key: slatMeta.key },
+ createElement(TimeColsAxisCell, __assign({}, slatMeta)))); });
+ };
+ return TimeBodyAxis;
+ }(BaseComponent));
+
+ var DEFAULT_WEEK_NUM_FORMAT = createFormatter({ week: 'short' });
+ var AUTO_ALL_DAY_MAX_EVENT_ROWS = 5;
+ var TimeColsView = /** @class */ (function (_super) {
+ __extends(TimeColsView, _super);
+ function TimeColsView() {
+ var _this = _super !== null && _super.apply(this, arguments) || this;
+ _this.allDaySplitter = new AllDaySplitter(); // for use by subclasses
+ _this.headerElRef = createRef();
+ _this.rootElRef = createRef();
+ _this.scrollerElRef = createRef();
+ _this.state = {
+ slatCoords: null,
+ };
+ _this.handleScrollTopRequest = function (scrollTop) {
+ var scrollerEl = _this.scrollerElRef.current;
+ if (scrollerEl) { // TODO: not sure how this could ever be null. weirdness with the reducer
+ scrollerEl.scrollTop = scrollTop;
+ }
+ };
+ /* Header Render Methods
+ ------------------------------------------------------------------------------------------------------------------*/
+ _this.renderHeadAxis = function (rowKey, frameHeight) {
+ if (frameHeight === void 0) { frameHeight = ''; }
+ var options = _this.context.options;
+ var dateProfile = _this.props.dateProfile;
+ var range = dateProfile.renderRange;
+ var dayCnt = diffDays(range.start, range.end);
+ var navLinkAttrs = (options.navLinks && dayCnt === 1) // only do in day views (to avoid doing in week views that dont need it)
+ ? { 'data-navlink': buildNavLinkData(range.start, 'week'), tabIndex: 0 }
+ : {};
+ if (options.weekNumbers && rowKey === 'day') {
+ return (createElement(WeekNumberRoot, { date: range.start, defaultFormat: DEFAULT_WEEK_NUM_FORMAT }, function (rootElRef, classNames, innerElRef, innerContent) { return (createElement("th", { ref: rootElRef, className: [
+ 'fc-timegrid-axis',
+ 'fc-scrollgrid-shrink',
+ ].concat(classNames).join(' ') },
+ createElement("div", { className: "fc-timegrid-axis-frame fc-scrollgrid-shrink-frame fc-timegrid-axis-frame-liquid", style: { height: frameHeight } },
+ createElement("a", __assign({ ref: innerElRef, className: "fc-timegrid-axis-cushion fc-scrollgrid-shrink-cushion fc-scrollgrid-sync-inner" }, navLinkAttrs), innerContent)))); }));
+ }
+ return (createElement("th", { className: "fc-timegrid-axis" },
+ createElement("div", { className: "fc-timegrid-axis-frame", style: { height: frameHeight } })));
+ };
+ /* Table Component Render Methods
+ ------------------------------------------------------------------------------------------------------------------*/
+ // only a one-way height sync. we don't send the axis inner-content height to the DayGrid,
+ // but DayGrid still needs to have classNames on inner elements in order to measure.
+ _this.renderTableRowAxis = function (rowHeight) {
+ var _a = _this.context, options = _a.options, viewApi = _a.viewApi;
+ var hookProps = {
+ text: options.allDayText,
+ view: viewApi,
+ };
+ return (
+ // TODO: make reusable hook. used in list view too
+ createElement(RenderHook, { hookProps: hookProps, classNames: options.allDayClassNames, content: options.allDayContent, defaultContent: renderAllDayInner$1, didMount: options.allDayDidMount, willUnmount: options.allDayWillUnmount }, function (rootElRef, classNames, innerElRef, innerContent) { return (createElement("td", { ref: rootElRef, className: [
+ 'fc-timegrid-axis',
+ 'fc-scrollgrid-shrink',
+ ].concat(classNames).join(' ') },
+ createElement("div", { className: 'fc-timegrid-axis-frame fc-scrollgrid-shrink-frame' + (rowHeight == null ? ' fc-timegrid-axis-frame-liquid' : ''), style: { height: rowHeight } },
+ createElement("span", { className: "fc-timegrid-axis-cushion fc-scrollgrid-shrink-cushion fc-scrollgrid-sync-inner", ref: innerElRef }, innerContent)))); }));
+ };
+ _this.handleSlatCoords = function (slatCoords) {
+ _this.setState({ slatCoords: slatCoords });
+ };
+ return _this;
+ }
+ // rendering
+ // ----------------------------------------------------------------------------------------------------
+ TimeColsView.prototype.renderSimpleLayout = function (headerRowContent, allDayContent, timeContent) {
+ var _a = this, context = _a.context, props = _a.props;
+ var sections = [];
+ var stickyHeaderDates = getStickyHeaderDates(context.options);
+ if (headerRowContent) {
+ sections.push({
+ type: 'header',
+ key: 'header',
+ isSticky: stickyHeaderDates,
+ chunk: {
+ elRef: this.headerElRef,
+ tableClassName: 'fc-col-header',
+ rowContent: headerRowContent,
+ },
+ });
+ }
+ if (allDayContent) {
+ sections.push({
+ type: 'body',
+ key: 'all-day',
+ chunk: { content: allDayContent },
+ });
+ sections.push({
+ type: 'body',
+ key: 'all-day-divider',
+ outerContent: ( // TODO: rename to cellContent so don't need to define
Create your events on the current week. Keep in note that your events repeat weekly.
-
One you have created your events, Click
-
All day events are not supported. A feature that lets you get the calendar from your watch will be added in a future update.
+
+
Create your events on the current week. Keep in note that your events repeat weekly.
+
One you have created your events, Click
+
All day events are not supported. A feature that lets you get the calendar from your watch will be added in a future update.
+
From a65742ce346039ef901e70157620aabd95bbca35 Mon Sep 17 00:00:00 2001
From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com>
Date: Sat, 20 Nov 2021 17:48:25 -0800
Subject: [PATCH 104/306] Update apps.json
---
apps.json | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/apps.json b/apps.json
index 643b67c79..9e9f239d1 100644
--- a/apps.json
+++ b/apps.json
@@ -3517,7 +3517,7 @@
"name": "School Calendar",
"shortName":"SCalendar",
"icon": "CalenderLogo.png",
- "version":" your ugly nuts",
+ "version":" my testing is hard work a aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"description": "A simple calendar that you can see your upcoming events that you create in the customizer. Keep in note that your events reapeat weekly.(Beta)",
"tags": "tool",
"readme": "README.md",
From ab6f56c381a54e4ab3466625a9eb794eef5dd2a0 Mon Sep 17 00:00:00 2001
From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com>
Date: Sat, 20 Nov 2021 17:52:34 -0800
Subject: [PATCH 105/306] Update custom.html
---
apps/schoolCalendar/custom.html | 19 +++++++++++++++++++
1 file changed, 19 insertions(+)
diff --git a/apps/schoolCalendar/custom.html b/apps/schoolCalendar/custom.html
index 5353e4bf0..7a60af319 100644
--- a/apps/schoolCalendar/custom.html
+++ b/apps/schoolCalendar/custom.html
@@ -73,6 +73,25 @@
// When the 'upload' button is clicked...
document.getElementById("upload").addEventListener("click", function () {
+ //Cacultate data:
+ var calendarEvents = calendar.getEvents();
+ let schedule = []
+ //--------------------
+ if(calendarEvents)
+ for(i=0;i>calendarEvents.length;i++){
+ var calendarEntry = {}
+ calendarEntry['cn'] = calendarEvents[i].title;
+ calendarEntry['dow'] = calendarEvents[i].start.getDate();
+ calendarEntry['sh'] = calendarEvents[i].start.getHours();
+ calendarEntry['sm'] = calendarEvents[i].start.getMinutes();
+ calendarEntry['eh'] = calendarEvents[i].end.getHours();
+ calendarEntry['em'] = calendarEvents[i].end.getMinutes();
+ schedule.push(calendarEntry)
+ }
+ }else{
+ alert("Add some events!");
+ }
+ //---------------------
// build the app's text using a templated String
var app = `
require("Font8x12").add(Graphics);
From 352fea34791ccf06450a516ab2a2daf22353c641 Mon Sep 17 00:00:00 2001
From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com>
Date: Sat, 20 Nov 2021 17:53:38 -0800
Subject: [PATCH 106/306] Update apps.json
---
apps.json | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/apps.json b/apps.json
index 9e9f239d1..19c5bca91 100644
--- a/apps.json
+++ b/apps.json
@@ -3517,7 +3517,7 @@
"name": "School Calendar",
"shortName":"SCalendar",
"icon": "CalenderLogo.png",
- "version":" my testing is hard work a aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
+ "version":" da frick",
"description": "A simple calendar that you can see your upcoming events that you create in the customizer. Keep in note that your events reapeat weekly.(Beta)",
"tags": "tool",
"readme": "README.md",
From a9c5a32bc1359d8288f4fd6b6da50eaf40910c9a Mon Sep 17 00:00:00 2001
From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com>
Date: Sat, 20 Nov 2021 17:56:54 -0800
Subject: [PATCH 107/306] Update custom.html
---
apps/schoolCalendar/custom.html | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/apps/schoolCalendar/custom.html b/apps/schoolCalendar/custom.html
index 7a60af319..584d42261 100644
--- a/apps/schoolCalendar/custom.html
+++ b/apps/schoolCalendar/custom.html
@@ -74,10 +74,10 @@
// When the 'upload' button is clicked...
document.getElementById("upload").addEventListener("click", function () {
//Cacultate data:
- var calendarEvents = calendar.getEvents();
+ //var calendarEvents = calendar.getEvents();
let schedule = []
//--------------------
- if(calendarEvents)
+ /*if(calendarEvents)
for(i=0;i>calendarEvents.length;i++){
var calendarEntry = {}
calendarEntry['cn'] = calendarEvents[i].title;
@@ -88,9 +88,9 @@
calendarEntry['em'] = calendarEvents[i].end.getMinutes();
schedule.push(calendarEntry)
}
- }else{
+ }else{*/
alert("Add some events!");
- }
+ //}
//---------------------
// build the app's text using a templated String
var app = `
From 4da82197f32b280e4192d5bc9a5ba174fbad4ace Mon Sep 17 00:00:00 2001
From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com>
Date: Sat, 20 Nov 2021 18:01:20 -0800
Subject: [PATCH 108/306] Update custom.html
---
apps/schoolCalendar/custom.html | 1 +
1 file changed, 1 insertion(+)
diff --git a/apps/schoolCalendar/custom.html b/apps/schoolCalendar/custom.html
index 584d42261..edc37be5a 100644
--- a/apps/schoolCalendar/custom.html
+++ b/apps/schoolCalendar/custom.html
@@ -89,6 +89,7 @@
schedule.push(calendarEntry)
}
}else{*/
+ consle.log(calendar.getEvents());
alert("Add some events!");
//}
//---------------------
From 34843d703aae0b6a33be352ad0f8408d8d17d07b Mon Sep 17 00:00:00 2001
From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com>
Date: Sat, 20 Nov 2021 18:11:54 -0800
Subject: [PATCH 109/306] Update custom.html
---
apps/schoolCalendar/custom.html | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/apps/schoolCalendar/custom.html b/apps/schoolCalendar/custom.html
index edc37be5a..4b820a98f 100644
--- a/apps/schoolCalendar/custom.html
+++ b/apps/schoolCalendar/custom.html
@@ -89,7 +89,8 @@
schedule.push(calendarEntry)
}
}else{*/
- consle.log(calendar.getEvents());
+ console.log = console.log || function(){};
+ console.log("my pro");
alert("Add some events!");
//}
//---------------------
From ff519bdc0d9bca530284d98cad9fd023ad37d50d Mon Sep 17 00:00:00 2001
From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com>
Date: Sat, 20 Nov 2021 18:15:29 -0800
Subject: [PATCH 110/306] Update custom.html
---
apps/schoolCalendar/custom.html | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/apps/schoolCalendar/custom.html b/apps/schoolCalendar/custom.html
index 4b820a98f..0cf29f933 100644
--- a/apps/schoolCalendar/custom.html
+++ b/apps/schoolCalendar/custom.html
@@ -90,7 +90,7 @@
}
}else{*/
console.log = console.log || function(){};
- console.log("my pro");
+ console.log(calendar.getEvents());
alert("Add some events!");
//}
//---------------------
From 6c20945898b84a02d6b440923a5afd994c7d97aa Mon Sep 17 00:00:00 2001
From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com>
Date: Sat, 20 Nov 2021 18:37:41 -0800
Subject: [PATCH 111/306] Update custom.html
---
apps/schoolCalendar/custom.html | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/apps/schoolCalendar/custom.html b/apps/schoolCalendar/custom.html
index 0cf29f933..26141e31a 100644
--- a/apps/schoolCalendar/custom.html
+++ b/apps/schoolCalendar/custom.html
@@ -33,10 +33,10 @@
From daf8dde32bc81b8f04402c4728eac41784751cd5 Mon Sep 17 00:00:00 2001
From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com>
Date: Sun, 21 Nov 2021 07:49:25 -0800
Subject: [PATCH 129/306] Update custom.html
---
apps/mywelcome/custom.html | 192 ++++++++++++++++++++++---------------
1 file changed, 115 insertions(+), 77 deletions(-)
diff --git a/apps/mywelcome/custom.html b/apps/mywelcome/custom.html
index 6b73a4ac4..c4c721765 100644
--- a/apps/mywelcome/custom.html
+++ b/apps/mywelcome/custom.html
@@ -1,92 +1,130 @@
-
-
-
Create your events on the current week. Keep in note that your events repeat weekly.
-
One you have created your events, Click
-
-
-
-
+
+
+
+
Style:
+
+
Line 1:
+
Line 2:
+
Line 3 (smaller):
+
Line 4 (smaller):
+
+
+
+
This is currently Christmas-themed, but more themes will be added in the future.
+
+
+
-
-
-
From 98f172fad442e89f53c45ac47f2388a08049855a Mon Sep 17 00:00:00 2001
From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com>
Date: Sun, 21 Nov 2021 07:53:01 -0800
Subject: [PATCH 130/306] Update custom.html
---
apps/schoolCalendar/custom.html | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/apps/schoolCalendar/custom.html b/apps/schoolCalendar/custom.html
index d7c9aa920..a3669c4a3 100644
--- a/apps/schoolCalendar/custom.html
+++ b/apps/schoolCalendar/custom.html
@@ -508,7 +508,7 @@ draw();
});
document.getElementById("upload").addEventListener("click", function () {
-
+ window.open("https://www.espruino.com/ide/emulator.html?code="+encodeURIComponent(getApp())+"&upload");
}
From d42fe03dc8fcbc0f68857dd9f887c8cc4eff1c06 Mon Sep 17 00:00:00 2001
From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com>
Date: Sun, 21 Nov 2021 07:55:08 -0800
Subject: [PATCH 131/306] Update custom.html
---
apps/schoolCalendar/custom.html | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/apps/schoolCalendar/custom.html b/apps/schoolCalendar/custom.html
index a3669c4a3..3be357f2c 100644
--- a/apps/schoolCalendar/custom.html
+++ b/apps/schoolCalendar/custom.html
@@ -509,7 +509,7 @@ draw();
document.getElementById("upload").addEventListener("click", function () {
window.open("https://www.espruino.com/ide/emulator.html?code="+encodeURIComponent(getApp())+"&upload");
- }
+ });
From 7f7dcb0bcf89b1d66f60481efaa09f5956ee4ecc Mon Sep 17 00:00:00 2001
From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com>
Date: Sun, 21 Nov 2021 07:58:22 -0800
Subject: [PATCH 132/306] Update custom.html
---
apps/schoolCalendar/custom.html | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/apps/schoolCalendar/custom.html b/apps/schoolCalendar/custom.html
index 3be357f2c..9a7341c7b 100644
--- a/apps/schoolCalendar/custom.html
+++ b/apps/schoolCalendar/custom.html
@@ -50,6 +50,7 @@
nowIndicator: true,
editable: true,
height: 600,
+ initialDate: '2018-06-01', // will be parsed as local
select: function(arg) {
var title = prompt('Event Title:');
if (title) {
@@ -61,6 +62,7 @@
})
}
calendar.unselect()
+
},
eventClick: function(arg) {
if (confirm('Are you sure you want to delete this event?')) {
@@ -69,7 +71,6 @@
},
});
calendar.render();
- calendar.gotoDate(2021-01-10);
});
// When the 'upload' button is clicked...
From 0f8f1e38e85a8c1c49057f4b891209bdfad67aef Mon Sep 17 00:00:00 2001
From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com>
Date: Sun, 21 Nov 2021 07:58:44 -0800
Subject: [PATCH 133/306] Update custom.html
---
apps/schoolCalendar/custom.html | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/apps/schoolCalendar/custom.html b/apps/schoolCalendar/custom.html
index 9a7341c7b..9d97e3cd6 100644
--- a/apps/schoolCalendar/custom.html
+++ b/apps/schoolCalendar/custom.html
@@ -24,7 +24,7 @@
-
Create your events on the current week. Keep in note that your events repeat weekly.
+
Create your events on the week shown. Keep in note that your events repeat weekly.
One you have created your events, Click or try in
All day events are not supported. A feature that lets you get the calendar from your watch will be added in a future update.
From 604153cb2408399ce1420b43ef87f41c09d3daa6 Mon Sep 17 00:00:00 2001
From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com>
Date: Sun, 21 Nov 2021 08:01:45 -0800
Subject: [PATCH 134/306] Update custom.html
---
apps/schoolCalendar/custom.html | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/apps/schoolCalendar/custom.html b/apps/schoolCalendar/custom.html
index 9d97e3cd6..16f3b06a2 100644
--- a/apps/schoolCalendar/custom.html
+++ b/apps/schoolCalendar/custom.html
@@ -50,7 +50,7 @@
nowIndicator: true,
editable: true,
height: 600,
- initialDate: '2018-06-01', // will be parsed as local
+ initialDate: '2018-06-10', // will be parsed as local
select: function(arg) {
var title = prompt('Event Title:');
if (title) {
From 640c8e491e489e9ea88282cf0bfe14004838b3db Mon Sep 17 00:00:00 2001
From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com>
Date: Sun, 21 Nov 2021 08:03:42 -0800
Subject: [PATCH 135/306] Update custom.html
---
apps/schoolCalendar/custom.html | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/apps/schoolCalendar/custom.html b/apps/schoolCalendar/custom.html
index 16f3b06a2..1b558b991 100644
--- a/apps/schoolCalendar/custom.html
+++ b/apps/schoolCalendar/custom.html
@@ -50,7 +50,7 @@
nowIndicator: true,
editable: true,
height: 600,
- initialDate: '2018-06-10', // will be parsed as local
+ initialDate: '2018-06-8', // will be parsed as local
select: function(arg) {
var title = prompt('Event Title:');
if (title) {
From d0336b2854d7e0892c440ef8c5f83bcbe1374a9b Mon Sep 17 00:00:00 2001
From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com>
Date: Sun, 21 Nov 2021 08:06:56 -0800
Subject: [PATCH 136/306] Update custom.html
---
apps/schoolCalendar/custom.html | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/apps/schoolCalendar/custom.html b/apps/schoolCalendar/custom.html
index 1b558b991..cc1d6cfdf 100644
--- a/apps/schoolCalendar/custom.html
+++ b/apps/schoolCalendar/custom.html
@@ -50,7 +50,7 @@
nowIndicator: true,
editable: true,
height: 600,
- initialDate: '2018-06-8', // will be parsed as local
+ initialDate: '2018-06-4', // will be parsed as local
select: function(arg) {
var title = prompt('Event Title:');
if (title) {
From f35b24844810a90f08f731cc9e63535be217e4cf Mon Sep 17 00:00:00 2001
From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com>
Date: Sun, 21 Nov 2021 08:09:44 -0800
Subject: [PATCH 137/306] Update custom.html
---
apps/schoolCalendar/custom.html | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/apps/schoolCalendar/custom.html b/apps/schoolCalendar/custom.html
index cc1d6cfdf..65d3db542 100644
--- a/apps/schoolCalendar/custom.html
+++ b/apps/schoolCalendar/custom.html
@@ -50,7 +50,7 @@
nowIndicator: true,
editable: true,
height: 600,
- initialDate: '2018-06-4', // will be parsed as local
+ initialDate: '2020-01-1', // will be parsed as local
select: function(arg) {
var title = prompt('Event Title:');
if (title) {
From 4d9d6abb9ef466e4058510efe9eec295f60127e9 Mon Sep 17 00:00:00 2001
From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com>
Date: Sun, 21 Nov 2021 08:14:02 -0800
Subject: [PATCH 138/306] Update custom.html
---
apps/schoolCalendar/custom.html | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/apps/schoolCalendar/custom.html b/apps/schoolCalendar/custom.html
index 65d3db542..d2c1c806a 100644
--- a/apps/schoolCalendar/custom.html
+++ b/apps/schoolCalendar/custom.html
@@ -50,7 +50,7 @@
nowIndicator: true,
editable: true,
height: 600,
- initialDate: '2020-01-1', // will be parsed as local
+ initialDate: '2018-06-03', // will be parsed as local
select: function(arg) {
var title = prompt('Event Title:');
if (title) {
From a712c37c7652f7bc46c4d7761df93b3d28e17229 Mon Sep 17 00:00:00 2001
From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com>
Date: Sun, 21 Nov 2021 08:17:58 -0800
Subject: [PATCH 139/306] Update custom.html
---
apps/schoolCalendar/custom.html | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/apps/schoolCalendar/custom.html b/apps/schoolCalendar/custom.html
index d2c1c806a..123829f66 100644
--- a/apps/schoolCalendar/custom.html
+++ b/apps/schoolCalendar/custom.html
@@ -50,7 +50,7 @@
nowIndicator: true,
editable: true,
height: 600,
- initialDate: '2018-06-03', // will be parsed as local
+ initialDate: '2018-01-01', // will be parsed as local
select: function(arg) {
var title = prompt('Event Title:');
if (title) {
From e77d9980023f041a87743ac1fd3c48a3f0652a8e Mon Sep 17 00:00:00 2001
From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com>
Date: Sun, 21 Nov 2021 08:18:21 -0800
Subject: [PATCH 140/306] Update custom.html
update
---
apps/schoolCalendar/custom.html | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/apps/schoolCalendar/custom.html b/apps/schoolCalendar/custom.html
index 123829f66..68a672804 100644
--- a/apps/schoolCalendar/custom.html
+++ b/apps/schoolCalendar/custom.html
@@ -50,7 +50,7 @@
nowIndicator: true,
editable: true,
height: 600,
- initialDate: '2018-01-01', // will be parsed as local
+ initialDate: '2021-01-01', // will be parsed as local
select: function(arg) {
var title = prompt('Event Title:');
if (title) {
From 529b0cb4637652dfc0a3ba6688b863b5f47437ca Mon Sep 17 00:00:00 2001
From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com>
Date: Sun, 21 Nov 2021 08:19:53 -0800
Subject: [PATCH 141/306] Update custom.html
---
apps/schoolCalendar/custom.html | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/apps/schoolCalendar/custom.html b/apps/schoolCalendar/custom.html
index 68a672804..21e0b0bbd 100644
--- a/apps/schoolCalendar/custom.html
+++ b/apps/schoolCalendar/custom.html
@@ -50,7 +50,7 @@
nowIndicator: true,
editable: true,
height: 600,
- initialDate: '2021-01-01', // will be parsed as local
+ initialDate: '2021-01-02', // will be parsed as local
select: function(arg) {
var title = prompt('Event Title:');
if (title) {
From 3472ca355a1a71d3fa3f464ece2b81dce7f4eeaa Mon Sep 17 00:00:00 2001
From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com>
Date: Sun, 21 Nov 2021 08:21:57 -0800
Subject: [PATCH 142/306] Update custom.html
---
apps/schoolCalendar/custom.html | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/apps/schoolCalendar/custom.html b/apps/schoolCalendar/custom.html
index 21e0b0bbd..271550416 100644
--- a/apps/schoolCalendar/custom.html
+++ b/apps/schoolCalendar/custom.html
@@ -50,7 +50,7 @@
nowIndicator: true,
editable: true,
height: 600,
- initialDate: '2021-01-02', // will be parsed as local
+ initialDate: '2021-06-01', // will be parsed as local
select: function(arg) {
var title = prompt('Event Title:');
if (title) {
From 626498da9d296fecf6776b836def4eda87027d85 Mon Sep 17 00:00:00 2001
From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com>
Date: Sun, 21 Nov 2021 08:24:01 -0800
Subject: [PATCH 143/306] Update custom.html
---
apps/schoolCalendar/custom.html | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/apps/schoolCalendar/custom.html b/apps/schoolCalendar/custom.html
index 271550416..9d97e3cd6 100644
--- a/apps/schoolCalendar/custom.html
+++ b/apps/schoolCalendar/custom.html
@@ -50,7 +50,7 @@
nowIndicator: true,
editable: true,
height: 600,
- initialDate: '2021-06-01', // will be parsed as local
+ initialDate: '2018-06-01', // will be parsed as local
select: function(arg) {
var title = prompt('Event Title:');
if (title) {
From 87a9504912b8753dc63700207ee89acacbb34726 Mon Sep 17 00:00:00 2001
From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com>
Date: Sun, 21 Nov 2021 08:30:44 -0800
Subject: [PATCH 144/306] Update custom.html
---
apps/schoolCalendar/custom.html | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/apps/schoolCalendar/custom.html b/apps/schoolCalendar/custom.html
index 9d97e3cd6..6dfc97d51 100644
--- a/apps/schoolCalendar/custom.html
+++ b/apps/schoolCalendar/custom.html
@@ -50,7 +50,7 @@
nowIndicator: true,
editable: true,
height: 600,
- initialDate: '2018-06-01', // will be parsed as local
+ initialDate: '2018-06-25', // will be parsed as local
select: function(arg) {
var title = prompt('Event Title:');
if (title) {
From 66f061112301898b011412e91982dbefc7cc2d1e Mon Sep 17 00:00:00 2001
From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com>
Date: Sun, 21 Nov 2021 08:32:48 -0800
Subject: [PATCH 145/306] Update custom.html
---
apps/schoolCalendar/custom.html | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/apps/schoolCalendar/custom.html b/apps/schoolCalendar/custom.html
index 6dfc97d51..d620b834e 100644
--- a/apps/schoolCalendar/custom.html
+++ b/apps/schoolCalendar/custom.html
@@ -50,7 +50,7 @@
nowIndicator: true,
editable: true,
height: 600,
- initialDate: '2018-06-25', // will be parsed as local
+ initialDate: '2018-06-3', // will be parsed as local
select: function(arg) {
var title = prompt('Event Title:');
if (title) {
From 8f2cc6e2137cf29e03435123e96d9c6c550864d1 Mon Sep 17 00:00:00 2001
From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com>
Date: Sun, 21 Nov 2021 08:34:09 -0800
Subject: [PATCH 146/306] Update custom.html
---
apps/schoolCalendar/custom.html | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/apps/schoolCalendar/custom.html b/apps/schoolCalendar/custom.html
index d620b834e..d2c1c806a 100644
--- a/apps/schoolCalendar/custom.html
+++ b/apps/schoolCalendar/custom.html
@@ -50,7 +50,7 @@
nowIndicator: true,
editable: true,
height: 600,
- initialDate: '2018-06-3', // will be parsed as local
+ initialDate: '2018-06-03', // will be parsed as local
select: function(arg) {
var title = prompt('Event Title:');
if (title) {
From 26bd9e35be993aae4deece775ac85f53f877b122 Mon Sep 17 00:00:00 2001
From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com>
Date: Sun, 21 Nov 2021 08:35:47 -0800
Subject: [PATCH 147/306] Update custom.html
---
apps/schoolCalendar/custom.html | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/apps/schoolCalendar/custom.html b/apps/schoolCalendar/custom.html
index d2c1c806a..0141bf067 100644
--- a/apps/schoolCalendar/custom.html
+++ b/apps/schoolCalendar/custom.html
@@ -84,7 +84,7 @@
for(i=0;i
Date: Sun, 21 Nov 2021 08:37:41 -0800
Subject: [PATCH 148/306] Update custom.html
---
apps/schoolCalendar/custom.html | 64 +--------------------------------
1 file changed, 1 insertion(+), 63 deletions(-)
diff --git a/apps/schoolCalendar/custom.html b/apps/schoolCalendar/custom.html
index 0141bf067..fd899f5b5 100644
--- a/apps/schoolCalendar/custom.html
+++ b/apps/schoolCalendar/custom.html
@@ -184,69 +184,7 @@ function updateDay(ffunction,day){
}
function getScheduleTable() {
- let schedule = [
- //Sunday
-
- //Monday:
- {cn: "Biblical Theology", dow:1, sh: 8, sm: 10, eh:9, em: 5, r:"207", t:"Mr. Besaw"},
- {cn: "English", dow:1, sh: 9, sm: 5, eh:10, em: 0, t:"Dr. Wong"},
- {cn: "Break", dow:1, sh: 10, sm: 0, eh:10, em: 10, t:""},
- {cn: "MS Robotics", dow:1, sh: 10, sm: 10, eh:11, em: 0, r:"211", t:"Mr. Broyles"},
- {cn: "MS Physical Education Boys", dow:1, sh: 11, sm: 0, eh:11, em: 50, r:"GYM", t:"Mr. Mendezona"},
- {cn: "Office Hours Besaw/Nunez", dow:1, sh: 11, sm: 50, eh:12, em: 25, r:"203", t:"Besaw/Nunez"},
- {cn: "Lunch", dow:1, sh: 12, sm: 25, eh:12, em: 50, r:"Commence or Advisory", t:""},
- {cn: "Activity Period", dow:1, sh: 12, sm: 50, eh:13, em: 0, r:"Outside", t:""},
- {cn: "Latin", dow:1, sh: 13, sm: 5, eh:14, em: 0, r:"208", t:"Mrs.Scrivner"},
- {cn: "Algebra 1", dow:1, sh: 14, sm: 0, eh:15, em: 0, r:"204", t:"Mr. Benson"},
-
- //Tuesday:
- {cn: "Logic", dow:2, sh: 8, sm: 10, eh:9, em: 0, r:"208", t:"Mrs.Scrivner"},
- {cn: "Algebra 1", dow:2, sh: 9, sm: 0, eh:10, em: 0, r:"204", t:"Mr. Benson"},
- {cn: "Chapel", dow:2, sh: 10, sm: 0, eh:10, em: 25, r:"Advisory", t:""},
- {cn: "Break", dow:2, sh: 10, sm: 25, eh:10, em: 35, r:"Outside", t:""},
- {cn: "Advisory Besaw", dow:2, sh: 10, sm: 35, eh:11, em: 0, r:"207", t:"Mr. Besaw"},
- {cn: "MS Robotics", dow:2, sh: 11, sm: 0, eh:11, em: 50, r:"211", t:"Mr. Broyles"},
- {cn: "Office Hours Besaw/Nunez", dow:2, sh: 11, sm: 50, eh:12, em: 25, r:"203", t:" Besaw/Nunez"},
- {cn: "Lunch", dow:2, sh: 12, sm: 25, eh:12, em: 50, r:"Commence or Advisory", t:""},
- {cn: "Activity Period", dow:2, sh: 12, sm: 50, eh:13, em: 5, r:"Outside", t:""},
- {cn: "Medieval Western Civilization", dow:2, sh: 13, sm: 5, eh:14, em: 0, r:"205", t:"Mr. Khule"},
- {cn: "Introductory Biology and Epidemiology", dow:2, sh: 14, sm: 0, eh:15, em: 0, r:"202", t:"Mrs. Brown"},
-
- //Wensday:
- {cn: "English", dow:3, sh: 9, sm: 0, eh:9, em: 55, r:"206", t:"Dr. Wong"},
- {cn: "Biblical Theology", dow:3, sh: 9, sm: 55, eh:10, em: 50, r:"207", t:"Mr. Besaw"},
- {cn: "Break", dow:3, sh: 10, sm: 50, eh:11, em: 0, r:"Outside", t:"_"},
- {cn: "MS Physical Education Boys", dow:3, sh: 11, sm: 0, eh:11, em: 50, r:"GYM", t:"Mr. Mendezona"},
- {cn: "Office Hours Besaw/Nunez", dow:3, sh: 11, sm: 50, eh:12, em: 25, r:"203", t:" Besaw/Nunez"},
- {cn: "Lunch", dow:3, sh: 12, sm: 25, eh:12, em: 50, r:"Commence or Advisory", t:""},
- {cn: "Activity Period", dow:2, sh: 12, sm: 50, eh:13, em: 0, r:"Outside", t:""},
- {cn: "Introductory Biology and Epidemiology", dow:3, sh: 13, sm: 0, eh:14, em: 0, r:"202", t:"Mrs. Brown"},
- {cn: "Medieval Western Civilization", dow:3, sh: 14, sm: 0, eh:15, em: 0, r:"205", t:"Mr. Khule"},
-
-
- //Thursday:
- {cn: "Algebra 1", dow:4, sh: 8, sm: 10, eh:9, em: 5, r:"204", t:"Mr. Benson"},
- {cn: "Latin", dow:4, sh: 9, sm: 5, eh:10, em: 0, r:"208", t:"Mrs.Scrivner"},
- {cn: "Break", dow:4, sh: 10, sm: 0, eh:10, em: 10, r:"Outside", t:""},
- {cn: "MS Robotics", dow:4, sh: 10, sm: 10, eh:11, em: 0, r:"211", t:"Mr. Broyles"},
- {cn: "Advisory Besaw", dow:4, sh: 11, sm: 50, eh:12, em: 25, r:"207", t:"Mr. Besaw"},
- {cn: "Lunch", dow:4, sh: 12, sm: 25, eh:12, em: 50, r:"Commence or Advisory", t:""},
- {cn: "Activity Period", dow:4, sh: 12, sm: 50, eh:13, em: 0, r:"Outside", t:""},
- {cn: "Biblical Theology", dow:4, sh: 13, sm: 5, eh:14, em: 0, r:"207", t:"Mr. Besaw"},
- {cn: "English", dow:4, sh: 14, sm: 0, eh:15, em: 0, r:"206", t:"Dr. Wong"},
-
- //Friday:
- {cn: "Medieval Western Civilization", dow:5, sh: 8, sm: 10, eh:9, em: 5, r:"205", t:"Mr. Khule"},
- {cn: "Introductory Biology and Epidemiology", dow:5, sh: 9, sm: 5, eh:10, em: 0, r:"202", t:"Mrs. Brown"},
- {cn: "Break", dow:5, sh: 10, sm: 0, eh:10, em: 10, dr:"Outside", t:""},
- {cn: "MS Robotics", dow:5, sh: 10, sm: 10, eh:11, em: 0, r:"211", t:"Mr. Broyles"},
- {cn: "Office Hours Besaw/Nunez", dow:5, sh: 11, sm: 50, eh:12, em: 25, r:"203", t:" Besaw/Nunez"},
- {cn: "Lunch", dow:5, sh: 12, sm: 25, eh:12, em: 50, r:"Commence or Advisory", t:""},
- {cn: "Activity Period", dow:5, sh: 12, sm: 50, eh:13, em: 0, r:"Outside", t:""},
- {cn: "Algebra 1", dow:5, sh: 13, sm: 5, eh:14, em: 0, r:"204", t:"Mr. Benson"},
- {cn: "Logic", dow:5, sh: 14, sm: 0, eh:15, em: 0, r:"208", t:"Mrs.Scrivner"},
- //Sataturday:
- ];
+ let schedule = ${schedule};
return schedule;
}
From 3bde4d2e48711186ae26036baea8f880c2aac7c4 Mon Sep 17 00:00:00 2001
From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com>
Date: Sun, 21 Nov 2021 16:12:16 -0800
Subject: [PATCH 149/306] Update custom.html
---
apps/schoolCalendar/custom.html | 14 +++++++-------
1 file changed, 7 insertions(+), 7 deletions(-)
diff --git a/apps/schoolCalendar/custom.html b/apps/schoolCalendar/custom.html
index fd899f5b5..de3511e19 100644
--- a/apps/schoolCalendar/custom.html
+++ b/apps/schoolCalendar/custom.html
@@ -90,12 +90,12 @@
calendarEntry['eh'] = calendarEvents[i].end.getHours();
calendarEntry['em'] = calendarEvents[i].end.getMinutes();
schedule.push(calendarEntry)
- console.log(schedule[0].cn);
- console.log(schedule[0].dow);
- console.log(schedule[0].sh);
- console.log(schedule[0].sm);
- console.log(schedule[0].eh);
- console.log(schedule[0].em);
+ console.log(schedule[i].cn);
+ console.log(schedule[i].dow);
+ console.log(schedule[i].sh);
+ console.log(schedule[i].sm);
+ console.log(schedule[i].eh);
+ console.log(schedule[i].em);
}
// build the app's text using a templated String
var app = `
@@ -184,7 +184,7 @@ function updateDay(ffunction,day){
}
function getScheduleTable() {
- let schedule = ${schedule};
+ let schedule = ${JSON.stringify(schedule)};
return schedule;
}
From e878e5478713b0b3cae0bb86e28f998cbfeb9b10 Mon Sep 17 00:00:00 2001
From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com>
Date: Sun, 21 Nov 2021 18:11:43 -0800
Subject: [PATCH 150/306] merge
---
README.md | 52 +-
_config.yml | 1 +
apps.json | 4945 ++++++++++-------
apps/_example_app/add_to_apps.json | 5 +-
apps/_example_widget/add_to_apps.json | 5 +-
apps/a_battery_widget/ChangeLog | 1 +
apps/a_battery_widget/README.md | 14 +
.../a_battery_widget/a_battery_widget-pic.jpg | Bin 0 -> 66429 bytes
apps/a_battery_widget/widget.js | 45 +
apps/a_battery_widget/widget.png | Bin 0 -> 2385 bytes
apps/about/ChangeLog | 2 +
apps/about/{app.js => app-bangle1.js} | 0
apps/about/app-bangle2.js | 72 +
apps/aclock/README.md | 4 +
apps/aclock/screenshot_analog.png | Bin 0 -> 3069 bytes
apps/alarm/ChangeLog | 1 +
apps/alarm/app.js | 6 +-
apps/android/ChangeLog | 1 +
apps/android/app-icon.js | 1 +
apps/android/app.js | 2 +
apps/android/app.png | Bin 0 -> 636 bytes
apps/android/boot.js | 55 +
apps/arrow/ChangeLog | 4 +-
apps/arrow/app.js | 59 +-
apps/astroid/screenshot_asteroids.png | Bin 0 -> 1498 bytes
apps/berlinc/ChangeLog | 3 +
apps/berlinc/berlin-clock.js | 66 +-
apps/binwatch/Background176_center.png | Bin 0 -> 4463 bytes
apps/binwatch/Background240_center.png | Bin 0 -> 6492 bytes
apps/binwatch/ChangeLog | 2 +
apps/binwatch/README.md | 10 +
apps/binwatch/app-icon.js | 1 +
apps/binwatch/app.js | 381 ++
apps/binwatch/app.png | Bin 0 -> 3794 bytes
apps/binwatch/bt-icon.png | Bin 0 -> 708 bytes
apps/boldclk/README.md | 4 +
apps/boldclk/screenshot_bold.png | Bin 0 -> 3563 bytes
apps/boot/ChangeLog | 9 +
apps/boot/bootupdate.js | 45 +-
apps/bthrm/ChangeLog | 1 +
apps/bthrm/README.md | 45 +
apps/bthrm/app-icon.js | 1 +
apps/bthrm/app.png | Bin 0 -> 2756 bytes
apps/bthrm/boot.js | 79 +
apps/calculator/screenshot_calculator.png | Bin 0 -> 2733 bytes
apps/calendar/screenshot_calendar.png | Bin 0 -> 3866 bytes
apps/chronowid/chrono_with_pastel.jpg | Bin 0 -> 29883 bytes
apps/chronowid/chrono_with_wave.jpg | Bin 0 -> 61355 bytes
apps/cliclockJS2Enhanced/app.icon.js | 1 +
apps/cliclockJS2Enhanced/app.js | 216 +
apps/cliclockJS2Enhanced/app.js.png | Bin 0 -> 6100 bytes
apps/cliclockJS2Enhanced/app.png | Bin 0 -> 421 bytes
apps/cliclockJS2Enhanced/screengrab.png | Bin 0 -> 2310 bytes
apps/cliock/ChangeLog | 2 +
apps/cliock/app.js | 23 +-
apps/cliock/screenshot_cli.png | Bin 0 -> 2115 bytes
apps/clock2x3/README.md | 4 +
apps/clock2x3/screenshot_pixel.png | Bin 0 -> 1926 bytes
apps/compass/ChangeLog | 3 +-
apps/compass/compass.js | 78 +-
apps/compass/screenshot_compass.png | Bin 0 -> 2632 bytes
apps/cubescramble/ChangeLog | 2 +
apps/cubescramble/README.md | 18 +
apps/cubescramble/cube-scramble-icon.js | 1 +
apps/cubescramble/cube-scramble.js | 74 +
apps/cubescramble/cube-scramble.png | Bin 0 -> 1288 bytes
apps/emojuino/ChangeLog | 1 +
apps/emojuino/README.md | 28 +
apps/emojuino/emojuino-icon.js | 1 +
apps/emojuino/emojuino.js | 145 +
apps/emojuino/emojuino.png | Bin 0 -> 1966 bytes
apps/ffcniftya/README.md | 4 +
apps/ffcniftya/app.js | 2 +-
apps/ffcniftya/screenshot_nifty.png | Bin 0 -> 3487 bytes
apps/ffcniftyb/ChangeLog | 2 +
apps/ffcniftyb/README.md | 9 +
apps/ffcniftyb/app-icon.js | 1 +
apps/ffcniftyb/app.js | 118 +
apps/ffcniftyb/app.png | Bin 0 -> 1218 bytes
apps/ffcniftyb/screenshot.png | Bin 0 -> 4343 bytes
apps/ffcniftyb/settings.js | 49 +
apps/files/files.js | 20 +-
apps/flappy/README.md | 5 +
apps/flappy/screenshot1_flappy.png | Bin 0 -> 2509 bytes
apps/flappy/screenshot2_flappy.png | Bin 0 -> 2805 bytes
apps/floralclk/README.md | 4 +
apps/floralclk/screenshot_floral.png | Bin 0 -> 6073 bytes
apps/fwupdate/ChangeLog | 1 +
apps/fwupdate/app.png | Bin 0 -> 1024 bytes
apps/fwupdate/custom.html | 286 +
apps/gallifr/screenshot_time.png | Bin 0 -> 3807 bytes
apps/gpstime/ChangeLog | 3 +-
apps/gpstime/gpstime-icon.js | 2 +-
apps/gpstime/gpstime.js | 117 +-
apps/gpstouch/Changelog | 1 +
apps/gpstouch/README.md | 16 +
apps/gpstouch/geotools.js | 128 +
apps/gpstouch/gpstouch.app.js | 246 +
apps/gpstouch/gpstouch.icon.js | 1 +
apps/gpstouch/gpstouch.png | Bin 0 -> 1571 bytes
apps/gpstouch/screenshot1.png | Bin 0 -> 2627 bytes
apps/gpstouch/screenshot2.png | Bin 0 -> 2555 bytes
apps/gpstouch/screenshot3.png | Bin 0 -> 2474 bytes
apps/gpstouch/screenshot4.png | Bin 0 -> 2750 bytes
apps/hcclock/ChangeLog | 2 +-
apps/hcclock/hcclock.app.js | 47 +-
apps/health/ChangeLog | 7 +
apps/health/README.md | 11 +-
apps/health/app.js | 228 +-
apps/health/boot.js | 58 +-
apps/health/interface.html | 133 +
apps/health/lib.js | 33 +-
apps/heart/ChangeLog | 1 +
apps/heart/app.js | 8 +-
apps/hrm/ChangeLog | 1 +
apps/hrm/heartrate-icon.js | 2 +-
apps/hrm/heartrate.js | 26 +-
apps/ios/ChangeLog | 1 +
apps/ios/app-icon.js | 1 +
apps/ios/app.js | 2 +
apps/ios/app.png | Bin 0 -> 1301 bytes
apps/ios/boot.js | 131 +
apps/launch/ChangeLog | 3 +-
apps/launch/{app.js => app-bangle1.js} | 2 +-
apps/launch/app-bangle2.js | 48 +
apps/launchb2/ChangeLog | 3 -
apps/launchb2/app.js | 79 -
apps/launchb2/app.png | Bin 899 -> 0 bytes
apps/lcars/ChangeLog | 1 +
apps/lcars/README.md | 8 +
apps/lcars/background.png | Bin 0 -> 1497 bytes
apps/lcars/lcars.app.js | 99 +
apps/lcars/lcars.icon.js | 1 +
apps/lcars/lcars.png | Bin 0 -> 1823 bytes
apps/matrixclock/ChangeLog | 3 +-
apps/matrixclock/matrixclock.js | 35 +-
apps/matrixclock/screenshot_matrix.png | Bin 0 -> 4990 bytes
apps/menusmall/ChangeLog | 1 +
apps/menusmall/boot.js | 31 +-
apps/messages/ChangeLog | 3 +
apps/messages/README.md | 21 +
apps/messages/app-icon.js | 1 +
apps/messages/app.js | 237 +
apps/messages/app.png | Bin 0 -> 917 bytes
apps/messages/lib.js | 37 +
apps/messages/widget.js | 20 +
apps/multiclock/ChangeLog | 24 +-
apps/multiclock/README.md | 28 +-
apps/multiclock/{ana.js => ana.face.js} | 46 +-
apps/multiclock/anaface.jpg | Bin 44568 -> 0 bytes
apps/multiclock/apps_entry.json | 19 -
apps/multiclock/big.face.js | 31 +
apps/multiclock/big.js | 32 -
apps/multiclock/bigface.jpg | Bin 40453 -> 0 bytes
apps/multiclock/clock.info | 1 +
apps/multiclock/clock.js | 69 -
apps/multiclock/digi.face.js | 38 +
apps/multiclock/digi.js | 33 -
apps/multiclock/digiface.jpg | Bin 48073 -> 0 bytes
apps/multiclock/dk.face.js | 40 +
apps/multiclock/multiclock-icon.img | Bin 0 -> 1156 bytes
apps/multiclock/multiclock-icon.js | 2 +-
apps/multiclock/multiclock.app.js | 86 +
apps/multiclock/nifty.face.js | 55 +
apps/multiclock/ped.js | 41 -
apps/multiclock/timdat.js | 47 -
apps/multiclock/{txt.js => txt.face.js} | 39 +-
apps/multiclock/txtface.jpg | Bin 51801 -> 0 bytes
apps/pastel/ChangeLog | 1 +
apps/pastel/README.md | 5 +-
apps/pastel/pastel.app.js | 14 +
apps/pastel/pastel.settings.js | 4 +-
apps/pastel/screenshot_elite.jpg | Bin 0 -> 9486 bytes
apps/pastel/screenshot_monoton.jpg | Bin 0 -> 11281 bytes
apps/pastel/screenshot_pastel.png | Bin 0 -> 4014 bytes
apps/pomodo/CHANGELOG.md | 4 +
apps/pomodo/ChangeLog | 1 +
apps/pomodo/pomodoro.js | 140 +-
apps/qalarm/ChangeLog | 2 +
apps/qalarm/app-icon.js | 1 +
apps/qalarm/app.js | 271 +
apps/qalarm/app.png | Bin 0 -> 1531 bytes
apps/qalarm/boot.js | 1 +
apps/qalarm/qalarm.js | 153 +
apps/qalarm/qalarmcheck.js | 41 +
apps/qalarm/widget.js | 22 +
apps/recorder/ChangeLog | 4 +
apps/recorder/README.md | 27 +
apps/recorder/app-icon.js | 1 +
apps/recorder/app-settings.json | 6 +
apps/recorder/app.js | 412 ++
apps/recorder/app.png | Bin 0 -> 1530 bytes
apps/recorder/interface.html | 255 +
apps/recorder/settings.js | 4 +
apps/recorder/widget.js | 222 +
apps/s7clk/README.md | 4 +
apps/s7clk/screenshot_s7segment.png | Bin 0 -> 2144 bytes
apps/sclock/ChangeLog | 1 +
apps/sclock/clock-simple.js | 33 +-
apps/sclock/screenshot_simplec.png | Bin 0 -> 2217 bytes
apps/score/README.md | 3 +
apps/score/screenshot_score.png | Bin 0 -> 1796 bytes
apps/setting/ChangeLog | 5 +-
apps/setting/settings-icon.js | 2 +-
apps/setting/settings.js | 63 +-
apps/simplest/ChangeLog | 1 +
apps/simplest/app.js | 9 +-
apps/simplest/screenshot_simplest.png | Bin 0 -> 2180 bytes
apps/slomoclock/ChangeLog | 2 +
apps/slomoclock/README.md | 6 +
apps/slomoclock/app-icon.js | 1 +
apps/slomoclock/app.js | 118 +
apps/slomoclock/settings.js | 38 +
apps/slomoclock/watch.png | Bin 0 -> 1439 bytes
apps/speedalt/settings.js | 2 +-
apps/speedalt2/ChangeLog | 2 +
apps/speedalt2/README.md | 134 +
apps/speedalt2/app-icon.js | 1 +
apps/speedalt2/app.js | 725 +++
apps/speedalt2/app.png | Bin 0 -> 1639 bytes
apps/speedalt2/settings.js | 89 +
apps/stopwatch/A.jpg | Bin 0 -> 77328 bytes
apps/stopwatch/B.jpg | Bin 0 -> 69988 bytes
apps/stopwatch/ChangeLog | 1 +
apps/stopwatch/README.md | 33 +
apps/stopwatch/pause-24.png | Bin 0 -> 161 bytes
apps/stopwatch/pause-24a.png | Bin 0 -> 123 bytes
apps/stopwatch/play-24.png | Bin 0 -> 297 bytes
apps/stopwatch/screenshot1.png | Bin 0 -> 1783 bytes
apps/stopwatch/screenshot2.png | Bin 0 -> 1765 bytes
apps/stopwatch/screenshot3.png | Bin 0 -> 1938 bytes
apps/stopwatch/stop-24.png | Bin 0 -> 177 bytes
apps/stopwatch/stop-24a.png | Bin 0 -> 192 bytes
apps/stopwatch/stopwatch.app.js | 220 +
apps/stopwatch/stopwatch.icon.js | 1 +
apps/stopwatch/stopwatch.png | Bin 0 -> 1566 bytes
apps/svclock/ChangeLog | 2 +
apps/svclock/vclock-simple.js | 113 +-
apps/swiperclocklaunch/ChangeLog | 1 +
apps/swiperclocklaunch/boot.js | 17 +
apps/swiperclocklaunch/icon.js | 1 +
apps/swiperclocklaunch/swiperclocklaunch.png | Bin 0 -> 889 bytes
apps/toucher/ChangeLog | 3 +-
apps/toucher/README.md | 22 +
apps/toucher/app.js | 145 +-
apps/toucher/screenshot1.jpg | Bin 0 -> 21329 bytes
apps/trex/README.md | 4 +
apps/trex/screenshot_trex.png | Bin 0 -> 668 bytes
apps/vernierrespirate/ChangeLog | 1 +
apps/vernierrespirate/README.md | 26 +
apps/vernierrespirate/app-icon.js | 1 +
apps/vernierrespirate/app.js | 256 +
apps/vernierrespirate/app.png | Bin 0 -> 1986 bytes
apps/waveclk/README.md | 4 +
apps/wclock/screenshot_word.png | Bin 0 -> 3540 bytes
apps/welcome/ChangeLog | 1 +
apps/welcome/{app.js => app-bangle1.js} | 8 -
apps/welcome/app-bangle2.js | 248 +
apps/welcome/screenshot_welcome.png | Bin 0 -> 2618 bytes
apps/widbat/ChangeLog | 1 +
apps/widbat/widget.js | 29 +-
apps/widbat/widget.png | Bin 297 -> 280 bytes
apps/widbatv/ChangeLog | 1 +
apps/widbatv/widget.js | 19 +
apps/widbatv/widget.png | Bin 0 -> 221 bytes
apps/widbt/ChangeLog | 1 +
apps/widbt/widget.js | 30 +-
apps/widcom/ChangeLog | 3 +
apps/widcom/widget.js | 35 +-
apps/widgps/ChangeLog | 1 +
apps/widgps/widget.js | 6 +-
apps/widhrm/ChangeLog | 1 +
apps/widhrm/widget.js | 69 +-
apps/widhrt/ChangeLog | 4 +-
apps/widhrt/widget.js | 30 +-
apps/widmp/ChangeLog | 2 +
apps/widmp/widget.js | 41 +-
apps/widtbat/ChangeLog | 1 +
apps/widtbat/widget.js | 28 +-
apps/widtbat/widget.png | Bin 911 -> 238 bytes
apps/widver/ChangeLog | 2 +
apps/widver/widget.js | 20 +-
apps/worldclock/ChangeLog | 2 +
apps/worldclock/app.js | 101 +-
apps/worldclock/screenshot_world.png | Bin 0 -> 2937 bytes
bin/create_app_supports_field.js | 99 +
bin/firmwaremaker.js | 4 +-
bin/firmwaremaker_c.js | 15 +-
bin/sanitycheck.js | 31 +-
bin/thumbnailer.js | 164 +
core | 2 +-
css/main.css | 37 +-
css/spectre-exp.min.css | 2 +-
css/spectre-icons.min.css | 2 +-
css/spectre.min.css | 2 +-
...ultapps.json => defaultapps_banglejs1.json | 0
defaultapps_banglejs2.json | 1 +
index.html | 15 +
loader.js | 167 +-
modules/Layout.js | 84 +-
300 files changed, 11197 insertions(+), 3283 deletions(-)
create mode 100644 _config.yml
create mode 100644 apps/a_battery_widget/ChangeLog
create mode 100644 apps/a_battery_widget/README.md
create mode 100644 apps/a_battery_widget/a_battery_widget-pic.jpg
create mode 100644 apps/a_battery_widget/widget.js
create mode 100644 apps/a_battery_widget/widget.png
rename apps/about/{app.js => app-bangle1.js} (100%)
create mode 100644 apps/about/app-bangle2.js
create mode 100644 apps/aclock/README.md
create mode 100644 apps/aclock/screenshot_analog.png
create mode 100644 apps/android/ChangeLog
create mode 100644 apps/android/app-icon.js
create mode 100644 apps/android/app.js
create mode 100644 apps/android/app.png
create mode 100644 apps/android/boot.js
create mode 100644 apps/astroid/screenshot_asteroids.png
create mode 100644 apps/binwatch/Background176_center.png
create mode 100644 apps/binwatch/Background240_center.png
create mode 100644 apps/binwatch/ChangeLog
create mode 100644 apps/binwatch/README.md
create mode 100644 apps/binwatch/app-icon.js
create mode 100644 apps/binwatch/app.js
create mode 100644 apps/binwatch/app.png
create mode 100644 apps/binwatch/bt-icon.png
create mode 100644 apps/boldclk/README.md
create mode 100644 apps/boldclk/screenshot_bold.png
create mode 100644 apps/bthrm/ChangeLog
create mode 100644 apps/bthrm/README.md
create mode 100644 apps/bthrm/app-icon.js
create mode 100644 apps/bthrm/app.png
create mode 100644 apps/bthrm/boot.js
create mode 100644 apps/calculator/screenshot_calculator.png
create mode 100644 apps/calendar/screenshot_calendar.png
create mode 100644 apps/chronowid/chrono_with_pastel.jpg
create mode 100644 apps/chronowid/chrono_with_wave.jpg
create mode 100644 apps/cliclockJS2Enhanced/app.icon.js
create mode 100644 apps/cliclockJS2Enhanced/app.js
create mode 100644 apps/cliclockJS2Enhanced/app.js.png
create mode 100644 apps/cliclockJS2Enhanced/app.png
create mode 100644 apps/cliclockJS2Enhanced/screengrab.png
create mode 100644 apps/cliock/screenshot_cli.png
create mode 100644 apps/clock2x3/README.md
create mode 100644 apps/clock2x3/screenshot_pixel.png
create mode 100644 apps/compass/screenshot_compass.png
create mode 100644 apps/cubescramble/ChangeLog
create mode 100644 apps/cubescramble/README.md
create mode 100644 apps/cubescramble/cube-scramble-icon.js
create mode 100644 apps/cubescramble/cube-scramble.js
create mode 100644 apps/cubescramble/cube-scramble.png
create mode 100644 apps/emojuino/ChangeLog
create mode 100644 apps/emojuino/README.md
create mode 100644 apps/emojuino/emojuino-icon.js
create mode 100644 apps/emojuino/emojuino.js
create mode 100644 apps/emojuino/emojuino.png
create mode 100644 apps/ffcniftya/README.md
create mode 100644 apps/ffcniftya/screenshot_nifty.png
create mode 100644 apps/ffcniftyb/ChangeLog
create mode 100644 apps/ffcniftyb/README.md
create mode 100644 apps/ffcniftyb/app-icon.js
create mode 100644 apps/ffcniftyb/app.js
create mode 100644 apps/ffcniftyb/app.png
create mode 100644 apps/ffcniftyb/screenshot.png
create mode 100644 apps/ffcniftyb/settings.js
create mode 100644 apps/flappy/README.md
create mode 100644 apps/flappy/screenshot1_flappy.png
create mode 100644 apps/flappy/screenshot2_flappy.png
create mode 100644 apps/floralclk/README.md
create mode 100644 apps/floralclk/screenshot_floral.png
create mode 100644 apps/fwupdate/ChangeLog
create mode 100644 apps/fwupdate/app.png
create mode 100644 apps/fwupdate/custom.html
create mode 100644 apps/gallifr/screenshot_time.png
create mode 100644 apps/gpstouch/Changelog
create mode 100644 apps/gpstouch/README.md
create mode 100644 apps/gpstouch/geotools.js
create mode 100644 apps/gpstouch/gpstouch.app.js
create mode 100644 apps/gpstouch/gpstouch.icon.js
create mode 100644 apps/gpstouch/gpstouch.png
create mode 100644 apps/gpstouch/screenshot1.png
create mode 100644 apps/gpstouch/screenshot2.png
create mode 100644 apps/gpstouch/screenshot3.png
create mode 100644 apps/gpstouch/screenshot4.png
create mode 100644 apps/health/interface.html
create mode 100644 apps/ios/ChangeLog
create mode 100644 apps/ios/app-icon.js
create mode 100644 apps/ios/app.js
create mode 100644 apps/ios/app.png
create mode 100644 apps/ios/boot.js
rename apps/launch/{app.js => app-bangle1.js} (98%)
create mode 100644 apps/launch/app-bangle2.js
delete mode 100644 apps/launchb2/ChangeLog
delete mode 100644 apps/launchb2/app.js
delete mode 100644 apps/launchb2/app.png
create mode 100644 apps/lcars/ChangeLog
create mode 100644 apps/lcars/README.md
create mode 100644 apps/lcars/background.png
create mode 100644 apps/lcars/lcars.app.js
create mode 100644 apps/lcars/lcars.icon.js
create mode 100644 apps/lcars/lcars.png
create mode 100644 apps/matrixclock/screenshot_matrix.png
create mode 100644 apps/messages/ChangeLog
create mode 100644 apps/messages/README.md
create mode 100644 apps/messages/app-icon.js
create mode 100644 apps/messages/app.js
create mode 100644 apps/messages/app.png
create mode 100644 apps/messages/lib.js
create mode 100644 apps/messages/widget.js
rename apps/multiclock/{ana.js => ana.face.js} (59%)
delete mode 100644 apps/multiclock/anaface.jpg
delete mode 100644 apps/multiclock/apps_entry.json
create mode 100644 apps/multiclock/big.face.js
delete mode 100644 apps/multiclock/big.js
delete mode 100644 apps/multiclock/bigface.jpg
create mode 100644 apps/multiclock/clock.info
delete mode 100644 apps/multiclock/clock.js
create mode 100644 apps/multiclock/digi.face.js
delete mode 100644 apps/multiclock/digi.js
delete mode 100644 apps/multiclock/digiface.jpg
create mode 100644 apps/multiclock/dk.face.js
create mode 100644 apps/multiclock/multiclock-icon.img
create mode 100644 apps/multiclock/multiclock.app.js
create mode 100644 apps/multiclock/nifty.face.js
delete mode 100644 apps/multiclock/ped.js
delete mode 100644 apps/multiclock/timdat.js
rename apps/multiclock/{txt.js => txt.face.js} (53%)
delete mode 100644 apps/multiclock/txtface.jpg
create mode 100644 apps/pastel/screenshot_elite.jpg
create mode 100644 apps/pastel/screenshot_monoton.jpg
create mode 100644 apps/pastel/screenshot_pastel.png
create mode 100644 apps/qalarm/ChangeLog
create mode 100644 apps/qalarm/app-icon.js
create mode 100644 apps/qalarm/app.js
create mode 100644 apps/qalarm/app.png
create mode 100644 apps/qalarm/boot.js
create mode 100644 apps/qalarm/qalarm.js
create mode 100644 apps/qalarm/qalarmcheck.js
create mode 100644 apps/qalarm/widget.js
create mode 100644 apps/recorder/ChangeLog
create mode 100644 apps/recorder/README.md
create mode 100644 apps/recorder/app-icon.js
create mode 100644 apps/recorder/app-settings.json
create mode 100644 apps/recorder/app.js
create mode 100644 apps/recorder/app.png
create mode 100644 apps/recorder/interface.html
create mode 100644 apps/recorder/settings.js
create mode 100644 apps/recorder/widget.js
create mode 100644 apps/s7clk/README.md
create mode 100644 apps/s7clk/screenshot_s7segment.png
create mode 100644 apps/sclock/screenshot_simplec.png
create mode 100644 apps/score/screenshot_score.png
create mode 100644 apps/simplest/screenshot_simplest.png
create mode 100644 apps/slomoclock/ChangeLog
create mode 100644 apps/slomoclock/README.md
create mode 100644 apps/slomoclock/app-icon.js
create mode 100644 apps/slomoclock/app.js
create mode 100644 apps/slomoclock/settings.js
create mode 100644 apps/slomoclock/watch.png
create mode 100644 apps/speedalt2/ChangeLog
create mode 100644 apps/speedalt2/README.md
create mode 100644 apps/speedalt2/app-icon.js
create mode 100644 apps/speedalt2/app.js
create mode 100644 apps/speedalt2/app.png
create mode 100644 apps/speedalt2/settings.js
create mode 100644 apps/stopwatch/A.jpg
create mode 100644 apps/stopwatch/B.jpg
create mode 100644 apps/stopwatch/ChangeLog
create mode 100644 apps/stopwatch/README.md
create mode 100644 apps/stopwatch/pause-24.png
create mode 100644 apps/stopwatch/pause-24a.png
create mode 100644 apps/stopwatch/play-24.png
create mode 100644 apps/stopwatch/screenshot1.png
create mode 100644 apps/stopwatch/screenshot2.png
create mode 100644 apps/stopwatch/screenshot3.png
create mode 100644 apps/stopwatch/stop-24.png
create mode 100644 apps/stopwatch/stop-24a.png
create mode 100644 apps/stopwatch/stopwatch.app.js
create mode 100644 apps/stopwatch/stopwatch.icon.js
create mode 100644 apps/stopwatch/stopwatch.png
create mode 100644 apps/swiperclocklaunch/ChangeLog
create mode 100644 apps/swiperclocklaunch/boot.js
create mode 100644 apps/swiperclocklaunch/icon.js
create mode 100644 apps/swiperclocklaunch/swiperclocklaunch.png
create mode 100644 apps/toucher/README.md
create mode 100644 apps/toucher/screenshot1.jpg
create mode 100644 apps/trex/README.md
create mode 100644 apps/trex/screenshot_trex.png
create mode 100644 apps/vernierrespirate/ChangeLog
create mode 100644 apps/vernierrespirate/README.md
create mode 100644 apps/vernierrespirate/app-icon.js
create mode 100644 apps/vernierrespirate/app.js
create mode 100644 apps/vernierrespirate/app.png
create mode 100644 apps/waveclk/README.md
create mode 100644 apps/wclock/screenshot_word.png
rename apps/welcome/{app.js => app-bangle1.js} (97%)
create mode 100644 apps/welcome/app-bangle2.js
create mode 100644 apps/welcome/screenshot_welcome.png
create mode 100644 apps/widbatv/ChangeLog
create mode 100644 apps/widbatv/widget.js
create mode 100644 apps/widbatv/widget.png
create mode 100644 apps/widcom/ChangeLog
create mode 100644 apps/worldclock/screenshot_world.png
create mode 100644 bin/create_app_supports_field.js
create mode 100755 bin/thumbnailer.js
rename defaultapps.json => defaultapps_banglejs1.json (100%)
create mode 100644 defaultapps_banglejs2.json
diff --git a/README.md b/README.md
index 49f616964..ac80b8270 100644
--- a/README.md
+++ b/README.md
@@ -1,10 +1,10 @@
Bangle.js App Loader (and Apps)
================================
-[](https://travis-ci.org/espruino/BangleApps)
+[](https://app.travis-ci.com/github/espruino/BangleApps)
* Try the **release version** at [banglejs.com/apps](https://banglejs.com/apps)
-* Try the **development version** at [github.io](https://espruino.github.io/BangleApps/)
+* Try the **development version** at [espruino.github.io](https://espruino.github.io/BangleApps/)
**All software (including apps) in this repository is MIT Licensed - see [LICENSE](LICENSE)** By
submitting code to this repository you confirm that you are happy with it being MIT licensed,
@@ -49,25 +49,25 @@ easily distinguish between file types, we use the following:
## Adding your app to the menu
-* Come up with a unique (all lowercase, no spaces) name, we'll assume `7chname`. Bangle.js
+* Come up with a unique (all lowercase, no spaces) name, we'll assume `myappid`. Bangle.js
is limited to 28 char filenames and appends a file extension (eg `.js`) so please
try and keep filenames short to avoid overflowing the buffer.
-* Create a folder called `apps/`, lets assume `apps/7chname`
+* Create a folder called `apps/`, lets assume `apps/myappid`
* We'd recommend that you copy files from 'Example Applications' (below) as a base, or...
-* `apps/7chname/app.png` should be a 48px icon
-* Use http://www.espruino.com/Image+Converter to create `apps/7chname/app-icon.js`, using a 1 bit, 4 bit or 8 bit Web Palette "Image String"
+* `apps/myappid/app.png` should be a 48px icon
+* Use http://www.espruino.com/Image+Converter to create `apps/myappid/app-icon.js`, using a 1 bit, 4 bit or 8 bit Web Palette "Image String"
* Create an entry in `apps.json` as follows:
```
-{ "id": "7chname",
+{ "id": "myappid",
"name": "My app's human readable name",
"shortName" : "Short Name",
"icon": "app.png",
"description": "A detailed description of my great app",
"tags": "",
"storage": [
- {"name":"7chname.app.js","url":"app.js"},
- {"name":"7chname.img","url":"app-icon.js","evaluate":true}
+ {"name":"myappid.app.js","url":"app.js"},
+ {"name":"myappid.img","url":"app-icon.js","evaluate":true}
],
},
```
@@ -95,12 +95,12 @@ Be aware of the delay between commits and updates on github.io - it can take a f
Using the 'Storage' icon in [the Web IDE](https://www.espruino.com/ide/)
(4 discs), upload your files into the places described in your JSON:
-* `app-icon.js` -> `7chname.img`
+* `app-icon.js` -> `myappid.img`
Now load `app.js` up in the editor, and click the down-arrow to the bottom
right of the `Send to Espruino` icon. Click `Storage` and then either choose
-`7chname.app.js` (if you'd uploaded your app previously), or `New File`
-and then enter `7chname.app.js` as the name.
+`myappid.app.js` (if you'd uploaded your app previously), or `New File`
+and then enter `myappid.app.js` as the name.
Now, clicking the `Send to Espruino` icon will load the app directly into
Espruino **and** will automatically run it.
@@ -115,10 +115,13 @@ and set it to `Load default application`.
## Example Applications
To make the process easier we've come up with some example applications that you can use as a base
-when creating your own. Just come up with a unique 7 character name, copy `apps/_example_app`
-or `apps/_example_widget` to `apps/7chname`, and add `apps/_example_X/add_to_apps.json` to
+when creating your own. Just come up with a unique name (ideally lowercase, under 20 chars), copy `apps/_example_app`
+or `apps/_example_widget` to `apps/myappid`, and add `apps/_example_X/add_to_apps.json` to
`apps.json`.
+**Note:** the max filename length is 28 chars, so we suggest an app ID of under
+20 so that when `.app.js`/etc gets added to the end the filename isn't cropped.
+
**If you're making a widget** please start the name with `wid` to make
it easy to find!
@@ -192,8 +195,8 @@ and which gives information about the app for the Launcher.
```
{
"name":"Short Name", // for Bangle.js menu
- "icon":"*7chname", // for Bangle.js menu
- "src":"-7chname", // source file
+ "icon":"*myappid", // for Bangle.js menu
+ "src":"-myappid", // source file
"type":"widget/clock/app/bootloader", // optional, default "app"
// if this is 'widget' then it's not displayed in the menu
// if it's 'clock' then it'll be loaded by default at boot time
@@ -217,8 +220,10 @@ and which gives information about the app for the Launcher.
{ "id": "appid", // 7 character app id
"name": "Readable name", // readable name
"shortName": "Short name", // short name for launcher
- "icon": "icon.png", // icon in apps/
+ "version": "0v01", // the version of this app
"description": "...", // long description (can contain markdown)
+ "icon": "icon.png", // icon in apps/
+ "screenshots" : [ { url:"screenshot.png" } ], // optional screenshot for app
"type":"...", // optional(if app) -
// 'app' - an application
// 'widget' - a widget
@@ -226,7 +231,9 @@ and which gives information about the app for the Launcher.
// 'bootloader' - code that runs at startup only
// 'RAM' - code that runs and doesn't upload anything to storage
"tags": "", // comma separated tag list for searching
+ "supports": ["BANGLEJS2"], // List of device IDs supported, either BANGLEJS or BANGLEJS2
"dependencies" : { "notify":"type" } // optional, app 'types' we depend on
+ "dependencies" : { "messages":"app" } // optional, depend on a specific app ID
// for instance this will use notify/notifyfs is they exist, or will pull in 'notify'
"readme": "README.md", // if supplied, a link to a markdown-style text file
// that contains more information about this app (usage, etc)
@@ -259,6 +266,9 @@ and which gives information about the app for the Launcher.
// (eg it's evaluated as JS)
"noOverwrite":true // if supplied, this file will not be overwritten if it
// already exists
+ "supports": ["BANGLEJS2"]// if supplied, this file will ONLY be uploaded to the device
+ // types named in the array. This allows different versions of
+ // the app to be uploaded for different platforms
},
]
"data": [ // list of files the app writes to
@@ -306,10 +316,10 @@ version of what's in `apps.json`:
+
+
+
+
diff --git a/apps/gallifr/screenshot_time.png b/apps/gallifr/screenshot_time.png
new file mode 100644
index 0000000000000000000000000000000000000000..2754138c45a49d8f7aa866525744342cd9b19047
GIT binary patch
literal 3807
zcmV<54j}P~P)Px@nMp)JRCr$Po!gQeDGWsI|Nm%j^z4+4F`!IcxRv(B5!)c7lRS83mcls`LGzT5cR{RAc5rR{D>n13Mbr>$S_m&7pFODK_2MVMZ>6@XyM#F6B~Ajo-UbO^>GNK^
z=l}Pf6p6#Z3bfw8ysps_;Df<9DRtlu`0IMO_f>M#t%BsZ)&p|V9%Zfr1rp%I<5&eO
zEkv{f_FmT5m%iTkV>c}m|6c*?OI!k+BrQ|G^BPSmqQ0j^$JfqT&y((-(eY9~`T1rF
zcz=;0m6rfZ3gzc!1vKBpnHS9fseqRPns4H~*3LMofLVcvZ{o~JWq?!wRRFjP&YuTH
z#msT@70AC*G+*;vajpXCz;pG^)tnVbfSm>XC!F+KH~N3!HafE_;wQk-+b98Cub)6u
zJCXoXa#e#ra6St5{CBV@Gkk(dXzE~3r;X}?HG;gzncoNS4Q`7<{Q{8R0q_i`-N}JlfL{vp-2lJI
zZC~ojcLKc9acd_=!J0kp5=XuZ;B;L67#t4reE_5Pv$MwWNX?&dFvRBroUZE!{DB~!
z4e(CA?(Vk`DCUPjd?vu@x+spiaR)(sF2J*TSnE1Eg!Y(q0LbS6oUXeD>dhfO1K<;S
z-CKI#!@kAaAK-M}HO^bFx~UWI4e+VXJ3{W{1Gyo@djfpYCVOn^+v;_ErtAx__3b}m
zzN7GBsGXtqIJBYw*nqtN-gVPfZdhtEBzZr8Bb?LPOJimbKWaCaS03zdwf6vcw~hIT
zv%%&xPsDCoE!JrtfOl}-cn|ekVJDCmLcAW})f=%;d>5Km>-Q9~NXNARTYFROd>3*C
zLCU;leIc*|$O|A|2kf0nMI6TDAqJ}kcmS{(
zWRDs^-PQpNCg?WIOVk-~oRsV0AO2Yf@XT8WuT#J{uZOZHgY1>Nuy-CZ<9N()uQD4i
z5ohOpOATK74+xtN@LQbMQ&~?CW*-vk
z0kEcbv^aGS2nJru`lEYb6xQB21PG+gSpa|PKyp^Fb4e2=IP|n2
zi01+9uKsi
zbkf$0%tLZU;uK?I9oW;!EY6Gcgdb6?`I6$B{%HdQhL(Y}=B+jwR<|ht&kzN56zygs
z!XmMyI9jccwJ&ca&U9b|h|;m8@!H-Oja!dvJrOhzYXBbI3TSd*H9&GEV#WX*DFoYJ
zB7$l1MdA`0@ijCmkBeK0IDjK>9i-cax|fweVtZdHp5=2EV&ov90Ba8sTQ8C`7}FnM
zb_2jfzlNbbZy8e(i_KSx$J8@Ah)f(Qr(TU3J(;|EW2JnFHT(g7PH}_-v(6iJ`#=b+
z8tQ$hM)3}Z*dJh4d~}Dk*FS8}Jjw@k4h=A^7d?wMh`wA!NiwZ@@Z3TyZR%=VJ(
z_|_&PPOo)twwHd^Dd%Z7PlC>~O*+_x{Q+vq<0JGqF#!0!hDf?j>48}%Lmf@q`=asLyroTP#XHS`M~5o{U?aqH9oTr<
zzO-dTl-64nYj+3kLB;4L9d@TS!Vln4@5>a!jFlWziY3iw?}2Y};F%ChMl;w)JHVq;
zX-G_B@Av1H^%+M&YS}p+liNXUgdO1Lv0gebOrP^W56AR)l9YM0O7uPqNFSL1cVMf9
zK{m&k+pZfUxiA}x#FP4Tj~KOnNKB^AQGcq9Bkeg-LnDT-14~KH`aH^srTpvLk8-ow
z4Prn+V>2;(ZGBO)BY}p{BDD4;~FLpyvShTrI4z6uUo_QYivN2FC6x!MW
zRxFCvV;)yKVlSZgZGp}dUZwJ_5|CW6g=SI44RLvR)Sb!OaXJbn*
zuhOQm>zRL;2X#0p@u<(y{8$G@<1%$=;I$m2(RvDy{wY;ck78Y$12Y@6YAg!dB*D8f
zM6DkUs0nC8%jP-D0&AOHsMCRZju#8-`UW(*=o9%zcjoYe8aw
z(5@lPLpPPpRf;P)Y->Fxrsbff154BtpIe-mSf)8t0-}6cuxm-NC5KIbS;ZHRXC2Ox
zt^`*dTa%l#h6He{r)6W-bx8B8@!3LXAY&YvjZbdiS9lG)pLx<+8i1v^Qm#@?32>zV
z`z1#?UD^x=UUK7phqmM(tr-EZbl{|%65zqVM#KQu8ar}khZmNALRjFHoDyjdvIn*j
zL(Z2roIL&H>b}FJT9DPGdL;)=fDx4)$DqBdCTI6JPK?*js&Lp@`%Pj>Of(5-8y6W*f`f!?wj0t!G7mk#QX)HjF7?1tuF`zV
zVO|NXS3m6e(wP8PYD$1ef=Tlw_)q&>(u1i-qaC@v&4De>!w8`rYjsX^zQ;IijS=%m
zh`K4T04vtD1N_Nev2cX$f5k+^8|9764=-Zz0-Alb=%!fD4zOa;Y1<+>V=obDUgYP~
z!~qvV_tygMlM1?4k-8mV!{VTX{4hk!o><6SKBIGIv~-HnXga{pw(QmmqYhbPh0co{
zw;Sp;R2#wjbg#CQX55dWn~GL4(^b&icO7>c%=HuCV6{=l?L8sb`76c;OE?Ta3t*-D&U4_$i$iu*b?H*Vv#_DojkFc4}+sk>D
zn@M|ud_MDz+yHN!1A9A^_0}NDc@Y?xTbhw>=vvlsGvVzyJyCOk$dG!!0Yo%$eCd_Uf9NLE%fLpf>R)<=3kIOh?MBSs_-wJFwe+9f-0{pzNc#8YPn(a(8
zUAL##tQ=-*>jbaA13w$#<+yd6S&lezz6ht4^on%igWWd<;HW-$bZW~rEq(7S$J0Ea
zB62$%-WY(NHX_2Q(2bWI4ZUU1#)+EOa$;1IdfX~_9ovUkfVIL(^Q3!N%@d%EshKb3
z?D@Hu6R+rdNAELAjVupbdC6H$WbIj_dv|XgtWJE1`^GtN|nEnkFZ007ChD8rYYew1)
zdhx*oHZOAR*jorMr$e1rawe~QcI$x=$DIvstGyxNM261?7|E&}V|n@5rT=4VX2&Bg
zx8iAopHnqd&FIi#qYHPKxG*ZvLh4&h3Fqu@OPN-P7x52vBIE$06>9
z=p2zJlAF`IdpfXG9O_V#6KmtFo~O}An&bKT^#Fgw>bZlr^VS=)=E&OdK%iLVxX3$;
zU4dqVwc9B<OcRg6l)Zl!Mi`eMw5XPwjl0`{TVlV#l`nPM)^@%R;G1tPHgm@r>AV}@8@}0l
zk=v#Nqn6load() }
+ ]});
+
+
+function onGPS(f) {
+ if (fix===undefined) {
+ g.clear();
+ Bangle.drawWidgets();
+ }
+ fix = f;
+ if (fix.fix) {
+ layout.status.label = "GPS\nAcquired";
+ } else {
+ layout.status.label = "Waiting\nfor GPS";
+ }
+ layout.sat.label = fix.satellites+" satellites";
+
+ var t = ["","---",""];
+ if (fix.time!==undefined) {
t = fix.time.toString().split(" ");
- /*
- [
- "Sun",
- "Nov",
- "10",
- "2019",
- "15:55:35",
- "GMT+0100"
- ]
- */
- //g.setFont("6x8",2);
- //g.drawString(t[0],120,110); // day
- g.setFont("6x8",3);
- g.drawString(t[1]+" "+t[2],120,135); // date
- g.setFont("6x8",2);
- g.drawString(t[3],120,160); // year
- g.setFont("6x8",3);
- g.drawString(t[4],120,185); // time
- if (fix.time) {
- // timezone
var tz = (new Date()).getTimezoneOffset()/-60;
if (tz==0) tz="UTC";
else if (tz>0) tz="UTC+"+tz;
else tz="UTC"+tz;
- g.setFont("6x8",2);
- g.drawString(tz,120,210); // gmt
- g.setFontAlign(0,0,3);
- g.drawString("Set",230,120);
- g.setFontAlign(0,0);
- }
-});
-setInterval(function() {
- g.drawImage(img,48,48,{scale:1.5,rotate:Math.sin(getTime()*2)/2});
-},100);
-setWatch(function() {
- if (fix.time!==undefined)
- setTime(fix.time.getTime()/1000);
-}, BTN2, {repeat:true});
+ t = [t[1]+" "+t[2],t[3],t[4],t[5],tz];
+ }
+
+ layout.gpstime.label = t.join("\n");
+ layout.render();
+}
+
+Bangle.on('GPS',onGPS);
diff --git a/apps/gpstouch/Changelog b/apps/gpstouch/Changelog
new file mode 100644
index 000000000..7f837e50e
--- /dev/null
+++ b/apps/gpstouch/Changelog
@@ -0,0 +1 @@
+0.01: First version
diff --git a/apps/gpstouch/README.md b/apps/gpstouch/README.md
new file mode 100644
index 000000000..7329f9833
--- /dev/null
+++ b/apps/gpstouch/README.md
@@ -0,0 +1,16 @@
+# GPS Touch
+
+- A touch controlled GPS watch for Bangle JS 2
+- Key feature is the conversion of Lat/Lon into Ordinance Servey Grid Reference
+- Swipe left and right to change the display
+- Select GPS and switch the GPS On or Off by touching twice in the top half of the display
+- Select LOGGER and switch the GPS Recorder On or Off by touching twice in the top half of the display
+- Displays the GPS time in the bottom half of the screen when the GPS is powered on, otherwise 00:00:00
+- Select display of Course, Speed, Altitude, Longitude, Latitude, Ordinance Servey Grid Reference
+
+## Screenshots
+
+
+
+
+
diff --git a/apps/gpstouch/geotools.js b/apps/gpstouch/geotools.js
new file mode 100644
index 000000000..5adc57872
--- /dev/null
+++ b/apps/gpstouch/geotools.js
@@ -0,0 +1,128 @@
+/**
+ *
+ * A module of Geo functions for use with gps fixes
+ *
+ * let geo = require("geotools");
+ * let os = geo.gpsToOSGrid(fix);
+ * let ref = geo.gpsToOSMapRef(fix);
+ *
+ */
+
+Number.prototype.toRad = function() { return this*Math.PI/180; };
+/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
+/* Ordnance Survey Grid Reference functions (c) Chris Veness 2005-2014 */
+/* - www.movable-type.co.uk/scripts/gridref.js */
+/* - www.movable-type.co.uk/scripts/latlon-gridref.html */
+/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
+function OsGridRef(easting, northing) {
+ this.easting = 0|easting;
+ this.northing = 0|northing;
+}
+OsGridRef.latLongToOsGrid = function(point) {
+ var lat = point.lat.toRad();
+ var lon = point.lon.toRad();
+
+ var a = 6377563.396, b = 6356256.909; // Airy 1830 major & minor semi-axes
+ var F0 = 0.9996012717; // NatGrid scale factor on central meridian
+ var lat0 = (49).toRad(), lon0 = (-2).toRad(); // NatGrid true origin is 49�N,2�W
+ var N0 = -100000, E0 = 400000; // northing & easting of true origin, metres
+ var e2 = 1 - (b*b)/(a*a); // eccentricity squared
+ var n = (a-b)/(a+b), n2 = n*n, n3 = n*n*n;
+
+ var cosLat = Math.cos(lat), sinLat = Math.sin(lat);
+ var nu = a*F0/Math.sqrt(1-e2*sinLat*sinLat); // transverse radius of curvature
+ var rho = a*F0*(1-e2)/Math.pow(1-e2*sinLat*sinLat, 1.5); // meridional radius of curvature
+ var eta2 = nu/rho-1;
+
+ var Ma = (1 + n + (5/4)*n2 + (5/4)*n3) * (lat-lat0);
+ var Mb = (3*n + 3*n*n + (21/8)*n3) * Math.sin(lat-lat0) * Math.cos(lat+lat0);
+ var Mc = ((15/8)*n2 + (15/8)*n3) * Math.sin(2*(lat-lat0)) * Math.cos(2*(lat+lat0));
+ var Md = (35/24)*n3 * Math.sin(3*(lat-lat0)) * Math.cos(3*(lat+lat0));
+ var M = b * F0 * (Ma - Mb + Mc - Md); // meridional arc
+
+ var cos3lat = cosLat*cosLat*cosLat;
+ var cos5lat = cos3lat*cosLat*cosLat;
+ var tan2lat = Math.tan(lat)*Math.tan(lat);
+ var tan4lat = tan2lat*tan2lat;
+
+ var I = M + N0;
+ var II = (nu/2)*sinLat*cosLat;
+ var III = (nu/24)*sinLat*cos3lat*(5-tan2lat+9*eta2);
+ var IIIA = (nu/720)*sinLat*cos5lat*(61-58*tan2lat+tan4lat);
+ var IV = nu*cosLat;
+ var V = (nu/6)*cos3lat*(nu/rho-tan2lat);
+ var VI = (nu/120) * cos5lat * (5 - 18*tan2lat + tan4lat + 14*eta2 - 58*tan2lat*eta2);
+
+ var dLon = lon-lon0;
+ var dLon2 = dLon*dLon, dLon3 = dLon2*dLon, dLon4 = dLon3*dLon, dLon5 = dLon4*dLon, dLon6 = dLon5*dLon;
+
+ var N = I + II*dLon2 + III*dLon4 + IIIA*dLon6;
+ var E = E0 + IV*dLon + V*dLon3 + VI*dLon5;
+
+ return new OsGridRef(E, N);
+};
+
+/*
+ * converts northing, easting to standard OS grid reference.
+ *
+ * [digits=10] - precision (10 digits = metres)
+ * to_map_ref(8, 651409, 313177); => 'TG 5140 1317'
+ * to_map_ref(0, 651409, 313177); => '651409,313177'
+ *
+ */
+function to_map_ref(digits, easting, northing) {
+ if (![ 0,2,4,6,8,10,12,14,16 ].includes(Number(digits))) throw new RangeError(`invalid precision '${digits}'`); // eslint-disable-line comma-spacing
+
+ let e = easting;
+ let n = northing;
+
+ // use digits = 0 to return numeric format (in metres) - note northing may be >= 1e7
+ if (digits == 0) {
+ const format = { useGrouping: false, minimumIntegerDigits: 6, maximumFractionDigits: 3 };
+ const ePad = e.toLocaleString('en', format);
+ const nPad = n.toLocaleString('en', format);
+ return `${ePad},${nPad}`;
+ }
+
+ // get the 100km-grid indices
+ const e100km = Math.floor(e / 100000), n100km = Math.floor(n / 100000);
+
+ // translate those into numeric equivalents of the grid letters
+ let l1 = (19 - n100km) - (19 - n100km) % 5 + Math.floor((e100km + 10) / 5);
+ let l2 = (19 - n100km) * 5 % 25 + e100km % 5;
+
+ // compensate for skipped 'I' and build grid letter-pairs
+ if (l1 > 7) l1++;
+ if (l2 > 7) l2++;
+ const letterPair = String.fromCharCode(l1 + 'A'.charCodeAt(0), l2 + 'A'.charCodeAt(0));
+
+ // strip 100km-grid indices from easting & northing, and reduce precision
+ e = Math.floor((e % 100000) / Math.pow(10, 5 - digits / 2));
+ n = Math.floor((n % 100000) / Math.pow(10, 5 - digits / 2));
+
+ // pad eastings & northings with leading zeros
+ e = e.toString().padStart(digits/2, '0');
+ n = n.toString().padStart(digits/2, '0');
+
+ return `${letterPair} ${e} ${n}`;
+}
+
+/**
+ *
+ * Module exports section, example code below
+ *
+ * let geo = require("geotools");
+ * let os = geo.gpsToOSGrid(fix);
+ * let ref = geo.gpsToOSMapRef(fix);
+ */
+
+// get easting and northings
+exports.gpsToOSGrid = function(gps_fix) {
+ return OsGridRef.latLongToOsGrid(gps_fix);
+}
+
+// string with an OS Map grid reference
+exports.gpsToOSMapRef = function(gps_fix) {
+ let os = OsGridRef.latLongToOsGrid(last_fix);
+ return to_map_ref(6, os.easting, os.northing);
+}
diff --git a/apps/gpstouch/gpstouch.app.js b/apps/gpstouch/gpstouch.app.js
new file mode 100644
index 000000000..4e49dd1e5
--- /dev/null
+++ b/apps/gpstouch/gpstouch.app.js
@@ -0,0 +1,246 @@
+const h = g.getHeight();
+const w = g.getWidth();
+let geo = require("geotools");
+let last_fix;
+let listennerCount = 0;
+
+function log_debug(o) {
+ //console.log(o);
+}
+
+function resetLastFix() {
+ last_fix = {
+ fix: 0,
+ alt: 0,
+ lat: 0,
+ lon: 0,
+ speed: 0,
+ time: 0,
+ course: 0,
+ satellites: 0
+ };
+}
+
+function processFix(fix) {
+ last_fix.time = fix.time;
+ log_debug(fix);
+
+ if (fix.fix) {
+ if (!last_fix.fix) {
+ // we dont need to suppress this in quiet mode as it is user initiated
+ Bangle.buzz(1500); // buzz on first position
+ }
+ last_fix = fix;
+ }
+}
+
+function draw() {
+ var d = new Date();
+ var da = d.toString().split(" ");
+ var time = da[4].substr(0,5);
+ var hh = da[4].substr(0,2);
+ var mm = da[4].substr(3,2);
+
+ g.reset();
+ drawTop(d,hh,mm);
+ drawInfo();
+}
+
+function drawTop(d,hh,mm) {
+ g.setFont("Vector", w/3);
+ g.setFontAlign(0, 0);
+ g.setColor(g.theme.bg);
+ g.fillRect(0, 24, w, ((h-24)/2) + 24);
+ g.setColor(g.theme.fg);
+
+ g.setFontAlign(1,0); // right aligned
+ g.drawString(hh, (w/2) - 6, ((h-24)/4) + 24);
+ g.setFontAlign(-1,0); // left aligned
+ g.drawString(mm, (w/2) + 6, ((h-24)/4) + 24);
+
+ // for the colon
+ g.setFontAlign(0,0); // centre aligned
+ if (d.getSeconds()&1) g.drawString(":", w/2, ((h-24)/4) + 24);
+}
+
+function drawInfo() {
+ if (infoData[infoMode] && infoData[infoMode].calc) {
+ g.setFont("Vector", w/7);
+ g.setFontAlign(0, 0);
+
+ if (infoData[infoMode].get_color)
+ g.setColor(infoData[infoMode].get_color());
+ else
+ g.setColor("#0ff");
+ g.fillRect(0, ((h-24)/2) + 24 + 1, w, h);
+
+ if (infoData[infoMode].is_control)
+ g.setColor("#fff");
+ else
+ g.setColor("#000");
+
+ g.drawString((infoData[infoMode].calc()), w/2, (3*(h-24)/4) + 24);
+ }
+}
+
+const infoData = {
+ ID_LAT: {
+ calc: () => 'Lat: ' + last_fix.lat.toFixed(4),
+ },
+ ID_LON: {
+ calc: () => 'Lon: ' + last_fix.lon.toFixed(4),
+ },
+ ID_SPEED: {
+ calc: () => 'Speed: ' + last_fix.speed.toFixed(1),
+ },
+ ID_ALT: {
+ calc: () => 'Alt: ' + last_fix.alt.toFixed(0),
+ },
+ ID_COURSE: {
+ calc: () => 'Course: '+ last_fix.course.toFixed(0),
+ },
+ ID_SATS: {
+ calc: () => 'Satelites: ' + last_fix.satellites,
+ },
+ ID_TIME: {
+ calc: () => formatTime(last_fix.time),
+ },
+ OS_REF: {
+ calc: () => !last_fix.fix ? "OO 000 000" : geo.gpsToOSMapRef(last_fix),
+ },
+ GPS_POWER: {
+ calc: () => (Bangle.isGPSOn()) ? 'GPS On' : 'GPS Off',
+ action: () => toggleGPS(),
+ get_color: () => Bangle.isGPSOn() ? '#f00' : '#00f',
+ is_control: true,
+ },
+ GPS_LOGGER: {
+ calc: () => 'Logger ' + loggerStatus(),
+ action: () => toggleLogger(),
+ get_color: () => loggerStatus() == "ON" ? '#f00' : '#00f',
+ is_control: true,
+ },
+};
+
+function toggleGPS() {
+ if (loggerStatus() == "ON")
+ return;
+
+ Bangle.setGPSPower(Bangle.isGPSOn() ? 0 : 1, 'gpstouch');
+ // add or remove listenner
+ if (Bangle.isGPSOn()) {
+ if (listennerCount == 0) {
+ Bangle.on('GPS', processFix);
+ listennerCount++;
+ log_debug("listennerCount=" + listennerCount);
+ }
+ } else {
+ if (listennerCount > 0) {
+ Bangle.removeListener("GPS", processFix);
+ listennerCount--;
+ log_debug("listennerCount=" + listennerCount);
+ }
+ }
+ resetLastFix();
+}
+
+function loggerStatus() {
+ var settings = require("Storage").readJSON("gpsrec.json",1)||{};
+ if (settings == {}) return "Install";
+ return settings.recording ? "ON" : "OFF";
+}
+
+function toggleLogger() {
+ var settings = require("Storage").readJSON("gpsrec.json",1)||{};
+ if (settings == {}) return;
+
+ settings.recording = !settings.recording;
+ require("Storage").write("gpsrec.json", settings);
+
+ if (WIDGETS["gpsrec"])
+ WIDGETS["gpsrec"].reload();
+
+ if (settings.recording && listennerCount == 0) {
+ Bangle.on('GPS', processFix);
+ listennerCount++;
+ log_debug("listennerCount=" + listennerCount);
+ }
+}
+
+function formatTime(now) {
+ try {
+ var fd = now.toUTCString().split(" ");
+ return fd[4];
+ } catch (e) {
+ return "00:00:00";
+ }
+}
+
+const infoList = Object.keys(infoData).sort();
+let infoMode = infoList[0];
+
+function nextInfo() {
+ let idx = infoList.indexOf(infoMode);
+ if (idx > -1) {
+ if (idx === infoList.length - 1) infoMode = infoList[0];
+ else infoMode = infoList[idx + 1];
+ }
+}
+
+function prevInfo() {
+ let idx = infoList.indexOf(infoMode);
+ if (idx > -1) {
+ if (idx === 0) infoMode = infoList[infoList.length - 1];
+ else infoMode = infoList[idx - 1];
+ }
+}
+
+Bangle.on('swipe', dir => {
+ if (dir == 1) prevInfo(); else nextInfo();
+ draw();
+});
+
+let prevTouch = 0;
+
+Bangle.on('touch', function(button, xy) {
+ let dur = 1000*(getTime() - prevTouch);
+ prevTouch = getTime();
+
+ if (dur <= 1000 && xy.y < h/2 && infoData[infoMode].is_control) {
+ Bangle.buzz();
+ if (infoData[infoMode] && infoData[infoMode].action) {
+ infoData[infoMode].action();
+ draw();
+ }
+ }
+});
+
+// Stop updates when LCD is off, restart when on
+Bangle.on('lcdPower', on => {
+ if (secondInterval)
+ clearInterval(secondInterval);
+ secondInterval = undefined;
+ if (on)
+ secondInterval = setInterval(draw, 1000);
+ draw();
+});
+
+resetLastFix();
+
+// add listenner if already powered on, plus tag app
+if (Bangle.isGPSOn() || loggerStatus() == "ON") {
+ Bangle.setGPSPower(1, 'gpstouch');
+ if (listennerCount == 0) {
+ Bangle.on('GPS', processFix);
+ listennerCount++;
+ log_debug("listennerCount=" + listennerCount);
+ }
+}
+
+g.clear();
+var secondInterval = setInterval(draw, 1000);
+draw();
+// Show launcher when button pressed
+Bangle.setUI("clock");
+Bangle.loadWidgets();
+Bangle.drawWidgets();
diff --git a/apps/gpstouch/gpstouch.icon.js b/apps/gpstouch/gpstouch.icon.js
new file mode 100644
index 000000000..c4cf85676
--- /dev/null
+++ b/apps/gpstouch/gpstouch.icon.js
@@ -0,0 +1 @@
+require("heatshrink").decompress(atob("mEw4UA///j+EAYO/uYDB//wCYcPBA4AFh/ABZMDBbkX6gLIgtX6tQBY9VBYNVBY0BBYdABYsFqoACEgQLDitVtWpqtUBYtVq2q1WVGAQLErQLB0oLFHQNqBYIkBHgMDIwYKBAAJIDIweqz/2BYJtDBYI6Bv/9HgILHYwILGh4gBBYWfbooLF6AjPBYW//wLGL4Wv/RfGNZaDIBYibEBYizIBYjLDBYzXBd4TXCBZ60BBYRqEBZpUBBYRSFJAQLCA4b7BHgQLFgYLGIwYLEgoLBHQYLEgILBHQYLEgALBAoYLFi/UBZMHBZUD6ALKApQAFBbHwBZMP/4ABBwgIDA="))
diff --git a/apps/gpstouch/gpstouch.png b/apps/gpstouch/gpstouch.png
new file mode 100644
index 0000000000000000000000000000000000000000..c411356ae69347a83882fe195782df211aa629e9
GIT binary patch
literal 1571
zcmV+;2Hg3HP)7!JOvVz*38;=4<&H5LVRE7i!
zwE@)hpWulvKu;xEMTMGS7!-*xS1GdF+PWqHWMt@9fSV%xU>F?sdga$tN-6RC
zc>^dn3?@fxJ#7!&a)Df>SSW(u?^pGJ*X^c8L{vPhdGzZM^83|YAaiF|hEwH1-av$*f)f8Y~qFARB^73HnR2V(_5xr7y^Cn!p2z7PP*%_}x)2Obf
zP;<)4n!xDr40nTEmM?i{9J
z%~@k5kQZyu%Y))#NKfyZBO?P=tbmy_6E;7m>%3)26G%{rT%EM6S>
zm(4;1mMnqs;}bUf%y4L^%OFA1X#)Y3uN1-FHeH935*Ru(-v95`dt7R40${T1S%zi~
ziSxO87pkft9FDdt#Z0ePem0}abSE|pmd5_e)2GLaS4y$B<^+e1pP{Q;nXWrnws;OJ
zUwaAZMUs^j;i#*NOZO^ZssPh{K!h2wwkcEM{XReR9o0uqJrobn)fMK;+VA<|tCMm5
zi8vDFN(L*Pcyz2GCnx&){(}$)zCN9lp!(Qp9<+y|Y-7fl%ashKAEDOPuBocmFDfA%F)r$N#l)Xawm>^XmdIt6zZhC{gI!hNv>I4rVDU57=DanXj1DWu*^Xjx}7r9)0c9bymH(Fe$;R6^qdI=sR%jT0++WDIjjJyKo`i
zY2KU}Y!%hP&Te!&dbkFkMqBHF`h%j+z<*D9wANRq+vwc>g%Do8AgqY
zw!b)g8gpmm(%fwaSFggJJtp?dz)Q7`-09fd@eM2eM+u0+sRe;YYe<;Prc4CuYpVuq#b8Qo_y)TrN
zg%?t?&EBGEyl*$yb^+_wNl4Rp7wE9d(T&w*Wpc}IuvSo787wNV7
zk4FyJ2`nnvzNDbEGN_3n-#{kWRWNB1+eP@+YMfGBN=swC-Mj`eNfi10J|8G9mT*@W
z%YjA9ioJUX_XXY8^=Fm7)JJyG%`u0g-#fvG-PRpvYZrS9iucfT9j?fgwIg+l4B<`vhR~D
zaWom_piCwQ2glMlWU_>gbxtPVIe)0C=enNfzMlJfuJ`@Ef4E=QllZeEPF@x%
z3jlz;ovn?tI6nV(%fQ4Z>*WkV93a8YI4hv8Z{HjMz(sa8mRBf*uLb?Tj-8T=&EAau
z(s1(Ycx2q@B8KUo;mys1UnA$2
zt?Mpln@4|0Q
z`O@x9Beq^)ht;%YhX9}{4@RA(8LY%P+1+mF%&n%+@y?60v>s4EZ
zBbg;9q<1Ev2N5GcwC*F#NEb=d>$~WV&0oC2LW;0wig;#*J5?98!D3P>+dss7oDiSS
zVQucECR%ssbCe~RA}@JYOI
z+PdhdAkxu=euFg?lVUilb&~$&2(x;(!6!*p_l>s}iJlV`%h}T$6JT+t^k}>kOZyAB
z$%%R9rm_S=y4d$h3!yMKebtL^H1#PRG>=_~`WnF)090EW4EB)>p1g5Me9b#GG--!eq)%&b(;oG2A@!!
z<j|@-!u@$eT6~gX04zYLFq~!
zk-evTw8;DthEuz1#0qpmDo3g@tM0n-a1jP8?&
z$wS$o(Q~gO)%B#*>DNoet&=!rZ=D8Tx?1E4`g;Lve&5X{1sP+E3;+DaSUB5HV5Uh_
z%_yE*gz-pKbGcGW_7?7nJ~VD$_;Jq7;?WO#CqM06AZ_eFHC(AUz&k`9(K+&NE4?5O
zrFQ(Vs3yx9k#nn$KUrKeE5I+)hq$f-mm}t1;KHj3GrwY0gld#DbM0kxduTl5
zaVJBgPc`~_Ssv=pO4{71F(=oZOZz`RDW%EqZA+#;|Ak*ozq~_;9OiXHT+yBmiW*U&
zqS`2b>v3cH-i8K7qnXy=8uvl?LuSp7$(x4MT-l(wbDj38d>5+H2h4YZi>MTEjXm#>
zIwcx|w4RWZ;J&weR^|`m=x#rScw{(WeFoLERo3KATC2*^^H5hzsDmXlNAd$RAuSo-+zB7S26UoMB?cU
zKR>mrgBLK1>?`Z6kC{Ruk{I*q$~3**J^cwRv*;gxs}wSby!s<8JJ+G9bJDoFOxpNq
z1pV4@%KRk?iJDAKpbDoC{-a{ja15H#W$fC`K_9#``y-%y(yGw$@P6e!;1Ib~x=^kF
zv-ybnO%kg~t?ZT4hRQWRR2E^VxTW}wS$N8sra4LTu~_hxgcnr;ak_0#K`$6!aC%jR
z*f21N10YDPS9lO;O?KjLW$`G!(aWAcO$65s>$TrNp!PYRoiXB3Bm=ZyFgIARR
zg#6LlnIt1Er|+9}aebdYhnnlAJOx5@xc4d<;`u*`(A5@aAGrTC6wCn@u3tU>01C^|
z?=7W@<=OT%3IC<&3gySc3hIlR9tEQKA@>MAQH+H1vGsafF%pC`$1jO}1n6fy6uUEE
zdL5nzA!_nnFZ9JeX49)}#KbA;a8sKVbLjrP%nGp&BlX@lVjnq9bt<78AQ^VeJx9!;
z)pssMV)=fe-Y&7bYv!#N2E`mgrd;>^-xYl#tu9Pvp;8w}X@N|9b6vFe`t1`lRK3UT
z)k-2Ro3}9G-8;CkmKFNQh+_-JE8~4r2s>UH-mwrkT#>!p1MNAxH7YuQ(lUMlpdrm#
zq~V-(B`wRsf!9rHXa^dK3bikxND(CQJb?gCBS3}7$l7-@t>v3$TE!?{z2ByF=cLQ=
z({$yaC%4n|m&i{Rlr`<;$IP60YaN4zksqouq3Uo+V<_Aj&Sy-f1uE@VaLV;Ciq8#&
zZGC2KE{$6G#l>h0aN5A9zC7(VNEOIWhK6DSW&X=$r~!IcY=WbXHCY!6MYyiZEGk+Rlz^8j_DiH%g7
z`WBY|i)Vrpu#cW@kVu(i
zW6Fu6;F<6}Z99wb?60B-#TjImbXjwG_hR6Tjz022;GI9uAdv-)x&Vo0F?_InOi4%t
z5vcm=6IN?Oly$>XNUYIB5sy~WGtUn9F`IXH;vF2eb>vsuJur&cj7ykR>^1YGtr}dl3)PNg3u$|8iY3M6O)jI_#bqKHU9KnO$v_^@K2?A5ZV
zpv2+=!Dz~;Aoyz8+=6TgB4Chckd#fpKwX$P^LzfxIdjhZxc8p#oO{3f-9O*C`9T3x
zeH{}W008v;X+C?@Rrn>eG}ZaWZF!WsfGK;Z-awPs^eq5rH~af|eb0%SxzJbs!dY*p
z)!JI$^D|X3(;0Ti%htJPg^AqFy9r58djbaBYkC!nyVoA()L|&7q(1rP4=+H_7TZsr
z=X|8Kx)QZyzb*I=ZhQ@}cpCbHGOfS@+GvmmE&^
z@xGSgtcMHj?T+`;vnGOibBM5Ao<>qzM8)@+L(E8km@d9>YsjS+e`cmpATDk??CN$s
zaI!W$V8MvZITIjX?@+XNG4|@>1y}ipwZ3qak{fdw
zSyVw!4xV5dB-`Sq*rCcxnAZoh?hLRTk_^loZ{Xk2Bj`4lTk4QI+w&V@PdP|a2}|ehR2$RShtQSjzt0yGKGLLd
z6VOX%>PA-m-6kM1iTQKAob3rHp87q~LZkj?&>M_7v3;%IQMs>!Heica*p_awa9un*
z9%fg~RUIhM1&8+xhYnx$4yE(oQ$gWh&&cJ;i2}EY_-!23c~DoT
zZ}e0oF}c>)vg7(|w^@YqEg}EexP@CwhAO(KDfQI+O3w=Ef%1Dmb-t*rzJ&CP_CP~L
zXsaVnz`pFX7a4wKgRHFcI50b8lwh4eSxqbxDz`0GhH*dnka7C4MUxydWQV0z;uEY{
z*{9OdZm1~rk_|05e0+i>RP_p%9D56!xZ$QP(9p@~C{P(q5m`dXZhTqo+X`
z_Kd3%N)A-j(u1Ac8Y>HZq!q7|<
z!}5F$N!-a#3K4}ZQk3>)9QN^hkL!7YEHYg9_Zq;$-7Iv#;wZKYeWOOjHr^z9%VgE+
zkXS!=a_WNudC`)r-i5~A3}#m*yGDc%KPUoOQ_;B)gEW~#V1)?c2nTI6IYCRE#&s$E#!QYuv
z%gTIxDbY7IGZZ&JliEqFOYBO=sOCEMGb_UsqY#Qdy3*02rL?uKA&sGF{Ko5Nu;gJv
zy_xZ=AZUY840~*vs-JjIs(t+72IUI@l>%+@8kR+sdxFy?2gHdwG?8B5~x3
zLPEYO*4r{rMSe_OrEgLpCfhLUoa4r@kPTd5_AP4OK!@+SvH9o@_y-w&17_O27Wc27
zA$89d$ZkGnDv3|`F&CSvB-3q}A*Z;pZ-B$lKK#no1GOv9e;`B8XU}Owfws`v>RARL
zMkVfrsHv7`pZMD{Dp2XX{k^8c8kqDYldY=7j4c|Zqv_ey8Y&)P0s`H|J81sHxwsY>om7a{ASZ`WO7~0mjH*)ttXhp5cJD
zn!D6ec6VDXAVT!csxPQ04t%8{#96B~Y{A!d=rXnisijAd-SgMO2QL1GMBGWuG~5bx
zdj2=WO7P?6&z6ndljP7hBhp_4%94h0L1)Aqv!u@>F0o`>TRMoS)Sgcm`JuE;dS4PF
z&C|>ut7rqQV8??6WFQ<7?-d_;aFEWRORjT?c`P7v9pU6-`|Mev!ck1V#|;7A4`!>r
ziGJP+k5&U*kD3w^Ng;QI+DsaQNIRa6Jhsd?@715OjTDAU5qG4raZKU3_tLaz^t+ls6I9agJP$D5%|m<&x;xS2`94i{
z(%=LOc#?e$hPzMaNq(B@;)CvSOS8-QKh7}&!BHI;c~SLbq+gc%(_7d+3Jic+jbVX(
zSTy=Q^n!far|FPOt&U9lHl{VVxnb<7D$QZ9&0iKBx5Pt8V#>~?L61K+HJ};UpE2?J
zx2#~T6u$Y?-5kOG4j)#@e2PD0N6qg+uR|G`s8AVfinl^4`OzBjbrE;TpuOG?-9G)G
zaGM0v(U?E(ZYpKCzvHX!aSKiYzLSNLhbhxQd@hdt&DIE8czn5PhoP2XZ|!29`+Z`O
z*JLx0y}pR>PBV5}^RMP6qaw@18NG|surb16A7;rlNQ6{L1?!BHJ%O!;@}@&tvdA<0
zFCPW((Mns(&7pL9c3F$YyoJ|n*lA$gan*O)7%o8FWojoz0;q8KokSrum_IE2h6bsqsHx(6~<+|Deb*
z6`|wRqaLrgTGkMkqXYInVhB~i#vd=z=bH8d`$uNKtGr&Ebm0_ITO+?k2n;tHZWbfr
zwQG>pz~Ak&I9EFUbRD#6x&U>K6U9b7I^jpk85UO%s9{&=4Gf{*7|$|Ub00uT{z*?3
z)-^r2Y0v2~(EJM(&l%MPXd;AVsPv`74Z%N&lO!0Fhddw+$#E3TC$XgXXH>@`0$F;%
zUR}g$q>iP?ZbE>w&VIla5{x5Zp(eh{`YjWGus^1~Uak+;E9?x<&FEWxO&%=eg^k>#)rZxsB{q;V6`t2lnU%5j7i+`3L4`xRTMt9P
z#m_fdpa9GQQ|uN1zzXc_u%>DlvYfi7L>(iluiat;LI9=AkgR(UV5(DvsR&i`y@tJ0
z4^^{|xR~N$bWI4D5cd}73IL*q;Hath$fwvai*ho6*G`OK{%
z^ZO@4=V-)pp6pseHS3Sz@bU+j6$l#X!Z6K%RvJghVZZ-~p*0g%d~XMYmTi!jr}qh5
znkqfXC~Kx$uFCUZI*0LQTQB$xtrd0>(evB3P(k_DpeV
z?Tpu4l8`&PNuuQnrnH
zgO(|EJW4EZ(JlNBG2&wLeRupwtDOf~X~c?BPV{GNclU`C_*wRfmwtbwF$~6RZsk*y
ztPd~rIhV6{92dbQKA`NO)AEJ0ll!R+?Slm)@Nk93==RLXv|vKc99rL1Nl>?(p#9hY
zL`z%`&N8>kPqS-i%Uu82w%WX#pK^iV%zKti1(w)#lsa?%i7S;Yw}h}&qLcuAXA(gG
zgOBe-*&U_$*)pD8kD3F^g{L%J5BMY!2RkxdShPN{z0ET)z~(DfG_~v18=iIX!^-AV
zD+{DTfXdVM6xYWdLNt+{7SCDCN-y;XHNBZVey!~ryAJsfx#P#GH}FU)nnmbS34v+B
zSn_P<-V+-Mtznu4-7(6e^1afBHysERvu
zT_NCWZ=1hV0a$&KL-Y*Cu3eZA9W8w65p^Bk+hX@&=WCMGQu!vw#(Y!-X_*8Iah_Hc$CxjeIwnF@ysykm6P9m+
zp_u)70lyGG@198jE1+U@(K)RTBo~~a?`*Tn(+~afFGPz&07o){FY89L@yPP7`K%|0
z?4umVHjfS_fJOZqDGJ|P9FLK26*l%SY)epNx^8
zQ|z;?v-gaYjl2#%Q|JUR2uMz&v^L6@DwtV<%uwpLp;FRVE;PlB-$_j@V
zJ`#zWWNH507dvaURgkz1RwZs}hX;rDZ)+9xR*sIhM$5O2=MUN^`iXOE^
z6k*5UKd1pHCT~!f0C=w=WChzx##S+nv
znA*loT;>@_4Kg-x-omb6%ZF=|s&^<;?XEpW;1pWy$u;pvfyAw?%vTgSSRc?cTmMV;j%!|q|_uP
zojyOvkWuofP@)@0F4>f!Lpnqk&|iaRB-2<7gZ7J|tM#wxqc8lw$yh^u2O;(KYHO*M
z3M&oGw-={`PEi(f>1f7_gQeWU?>fTz^fI~@rAQ?my^|*uwfAC6=C3{4X
zO|w;{Jh|QkAESgnT6g(LPsc(HZv`JL*ENs+LBSTr8&a{djr*H2Roy~Ch}slt08(=g
z{d-#l%nYB89=~&4s^f%Moi7zLU>u1`EBJsGtxg7}gzJ3og03AQB~x4UK_PzkQj=?f
zSHI)T74M6}6g8@Kgpg0k`
zc=LNIY`VkP4Jy*l0Nu9U{iT4iw{6b^2_a=MZVrCJ`Vq5S2c)7R80;Xn=!AiOnmVch
zhPc6RBxe$X!6LRBSxz0rIHqy8al%++|iD~b@(KKnU
zu~3|MSTZl&=BH&3D9!XJw$c-$Hk`Ah_6)1{gyR_I4sIL^<1y!yp)__O9PwM$!4c76
z?3_bmtC{w3+uw@uDV^mM^AoJpa(GeC$lor4cR=ZSNpuAG1j(9
z;<*yqQ??j0ABf6n5RH<+Rbmd$r*S+*b_F
zIlgEWKKgmcQtd}=nDY<{Y=G)#%~Cd81$*#y0fKaDMuO0y*1dM;MRJzs^9
zDEG&T5st&wQN+HBrCJuvF+;k`wOLVJeT-^TgJBaHGCHe=nWF-$+OwO89kU7To@PMY
ztA&sX`G`yQ$KLC{$j`zXn?Kohf;%f+V-#A?h6%6TJ$vVgak{_@#wyb`$x*|r|gB1weBg0bDiY1zX~
zG+&>}EUqFEx(%v4pC+?)k84f1JHoWtBmUEpN21-kxp{hqfv}BJ>i(<^(Hr#W=$l~@
z8wKJ>R7FiziQvoSoqi_a+t6Pqt8DKI&Wkxo<(|v03=KlD>!_&jUt@;#>1yp8isYHb
z<8V?)o^L)!&L?QGfw{LoU=Ye%a+thlco|%|UIbGr@
z^|iUL$`3QN*-}nf+$`64_q@Jyyk03`%l*}V66=^pX^fYQ@}a3%VNXNO(lA>p-abDT
zpAI95CY)Hk%x!|QIK-{YO|>tz%)f1%S9VXZeo+HU&8OhCXfIPD~u?4q~U
z;TLs9aJ0uwk^=@BK7qi^K?wdVM=D&jp8>z8j4Mlp4S8uiRsuXK;Jj+KQ~|{lWoHDu
z1jeMBs*!+41=aLt0v?rlhN|jH@Y!v
z6DHz#I|0Z`+_rn92zXhV+eKGE+(?&$G{9tYR%{suc-iEUx->wXh+}{`0Bydfd;wsB
zS!pz=1LA`Gs>J{VrIQ(3z!JgAEq5zG9HfR$1H{pisG7j`GVdceg@CwL9k-tVgu_KW
z|3gxq@jCxASxrHK&)ja=dx{soH9RTLdB@*s?HsVU+U!@!J+!xH0j)iYJ<*kzR2wOH
zrnywpV04@i0b8mtUN_LALgcOmpg*%Hh;m-vAH^T{Qw
zi0O9O>QzAkc^I{DW{Kg!Hq0_T`;)a)_xpB*g^;s^b0f0vGq;}>=a(=KUi&tE)WGak5F_vPBDd!>x2YZ0n9
zQ>P(`f#r7My?*F{1hAwjxcgD0DszwS6wbuETu}}TZ062e2y`Q&&h^i}{gZIXF0jdv
z{!{vNkyk=tVmY3N2X{nENjvcy?EhYt|6)HBa^^Gt+2cpa`c?x-V<)mz`88-VF)*qf
zb@Iw?e}pr}kh^o1F!S^dRRs&{QSBLh^iRaTFBq$6a_F15z-4CN!d4xvyYqCLhEj2E
z;y8`Wur5WxuN!5IeA=i#YP36%V|F+!w8<#Fb=0z_nd6j+xnC>LU0<0YH#T>D3L~}2
zqYhA=gR=348EfAH=Gvk!&P8xtQA@`P)y#1OQq7KL&_>h8db}qI}*8XY#2K_oA^Z)<=
literal 0
HcmV?d00001
diff --git a/apps/hcclock/ChangeLog b/apps/hcclock/ChangeLog
index 0ca30d066..aaa55d01a 100644
--- a/apps/hcclock/ChangeLog
+++ b/apps/hcclock/ChangeLog
@@ -1,2 +1,2 @@
0.01: base code
-
+0.02: saved settings when switching color scheme
\ No newline at end of file
diff --git a/apps/hcclock/hcclock.app.js b/apps/hcclock/hcclock.app.js
index 98abbc6f3..4664dd763 100644
--- a/apps/hcclock/hcclock.app.js
+++ b/apps/hcclock/hcclock.app.js
@@ -174,19 +174,52 @@ function fmtDate(day,month,year,hour)
let ap = "(AM)";
if(hour == 0 || hour > 12)
ap = "(PM)";
- return months[month] + " " + day + " " + year + " "+ ap;
+ return months[month] + " " + day + " " + year + " "+ ap;
}
else
return months[month] + ". " + day + " " + year;
}
-// Handles Flipping colors, then refreshes the UI
+
+//////////////////////////////////////////
+//
+// HANDLE COLORS + SETTINGS
+//
+
+function getColorScheme()
+{
+ let settings = require('Storage').readJSON("hcclock.json", true) || {};
+ if (!("scheme" in settings)) {
+ settings.scheme = 0;
+ }
+ return settings.scheme;
+}
+
+function setColorScheme(value)
+{
+ let settings = require('Storage').readJSON("hcclock.json", true) || {};
+ settings.scheme = value;
+ require('Storage').writeJSON('hcclock.json', settings);
+
+ if(value == 0) // White
+ {
+ bg = 255;
+ fg = 0;
+ }
+ else // Black
+ {
+ bg = 0;
+ fg = 255;
+ }
+ redraw();
+}
+
function flipColors()
{
- let t = bg;
- bg = fg;
- fg = t;
- redraw();
+ if(getColorScheme() == 0)
+ setColorScheme(1);
+ else
+ setColorScheme(0);
}
//////////////////////////////////////////
@@ -197,7 +230,7 @@ function flipColors()
// Initialize
g.clear();
Bangle.loadWidgets();
-redraw();
+setColorScheme(getColorScheme());
// Define Refresh Interval
setInterval(updateTime, interval);
diff --git a/apps/health/ChangeLog b/apps/health/ChangeLog
index 5560f00bc..5eb96a0ea 100644
--- a/apps/health/ChangeLog
+++ b/apps/health/ChangeLog
@@ -1 +1,8 @@
0.01: New App!
+0.02: Modified data format to include daily summaries
+0.03: Settings to turn HRM on
+0.04: Add HRM graph view
+ Don't restart HRM when changing apps if we've already got a good BPM value
+0.05: Fix daily summary calculation
+0.06: Fix daily health summary for movement (a line got deleted!)
+0.07: Added coloured bar charts
diff --git a/apps/health/README.md b/apps/health/README.md
index 0ba0d8228..c69e2e45b 100644
--- a/apps/health/README.md
+++ b/apps/health/README.md
@@ -14,10 +14,18 @@ To view data, run the `Health` app from your watch.
Stores:
-* Heart rate (TODO)
+* Heart rate
* Step count
* Movement
+## Settings
+
+* **Heart Rt** - Whether to monitor heart rate or not
+ * **Off** - Don't turn HRM on, but record heart rate if the HRM was turned on by another app/widget
+ * **10 Min** - Turn HRM on every 10 minutes (for each heath entry) and turn it off after 2 minutes, or when a good reading is found
+ * **Always** - Keep HRM on all the time (more accurate recording, but reduces battery life to ~36 hours)
+
+
## Technical Info
Once installed, the `health.boot.js` hooks onto the `Bangle.health` event and
@@ -28,7 +36,6 @@ to grab historical health info.
## TODO
-* **Extend file format to include combined data for each day (to make graphs faster)**
* `interface` page for desktop to allow data to be viewed and exported in common formats
* More features in app:
* Step counting goal (ensure pedometers use this)
diff --git a/apps/health/app.js b/apps/health/app.js
index f2df52972..eae45c190 100644
--- a/apps/health/app.js
+++ b/apps/health/app.js
@@ -1,28 +1,74 @@
+function getSettings() {
+ return require("Storage").readJSON("health.json",1)||{};
+}
+
+function setSettings(s) {
+ require("Storage").writeJSON("health.json",s);
+}
+
function menuMain() {
+ swipe_enabled = false;
+ clearButton();
E.showMenu({
"":{title:"Health Tracking"},
"< Back":()=>load(),
"Step Counting":()=>menuStepCount(),
- "Movement":()=>menuMovement()
+ "Movement":()=>menuMovement(),
+ "Heart Rate":()=>menuHRM(),
+ "Settings":()=>menuSettings()
+ });
+}
+
+function menuSettings() {
+ swipe_enabled = false;
+ clearButton();
+ var s=getSettings();
+ E.showMenu({
+ "":{title:"Health Tracking"},
+ "< Back":()=>menuMain(),
+ "Heart Rt":{
+ value : 0|s.hrm,
+ min : 0, max : 2,
+ format : v=>["Off","10 mins","Always"][v],
+ onchange : v => { s.hrm=v;setSettings(s); }
+ }
});
}
function menuStepCount() {
+ swipe_enabled = false;
+ clearButton();
E.showMenu({
"":{title:"Step Counting"},
"< Back":()=>menuMain(),
- "per hour":()=>stepsPerHour()
+ "per hour":()=>stepsPerHour(),
+ "per day":()=>stepsPerDay()
});
}
function menuMovement() {
+ swipe_enabled = false;
+ clearButton();
E.showMenu({
"":{title:"Movement"},
"< Back":()=>menuMain(),
- "per hour":()=>movementPerHour()
+ "per hour":()=>movementPerHour(),
+ "per day":()=>movementPerDay(),
});
}
+function menuHRM() {
+ swipe_enabled = false;
+ clearButton();
+ E.showMenu({
+ "":{title:"Heart Rate"},
+ "< Back":()=>menuMain(),
+ "per hour":()=>hrmPerHour(),
+ "per day":()=>hrmPerDay(),
+ });
+}
+
+
function stepsPerHour() {
E.showMessage("Loading...");
var data = new Uint16Array(24);
@@ -30,14 +76,51 @@ function stepsPerHour() {
g.clear(1);
Bangle.drawWidgets();
g.reset();
- require("graph").drawBar(g, data, {
- y:24,
- miny: 0,
- axes : true,
- gridx : 6,
- gridy : 500
+ setButton(menuStepCount);
+ barChart("HOUR", data);
+}
+
+function stepsPerDay() {
+ E.showMessage("Loading...");
+ var data = new Uint16Array(31);
+ require("health").readDailySummaries(new Date(), h=>data[h.day]+=h.steps);
+ g.clear(1);
+ Bangle.drawWidgets();
+ g.reset();
+ setButton(menuStepCount);
+ barChart("DAY", data);
+}
+
+function hrmPerHour() {
+ E.showMessage("Loading...");
+ var data = new Uint16Array(24);
+ var cnt = new Uint8Array(23);
+ require("health").readDay(new Date(), h=>{
+ data[h.hr]+=h.bpm;
+ if (h.bpm) cnt[h.hr]++;
});
- Bangle.setUI("updown", ()=>menuStepCount());
+ data.forEach((d,i)=>data[i] = d/cnt[i]);
+ g.clear(1);
+ Bangle.drawWidgets();
+ g.reset();
+ setButton(menuHRM);
+ barChart("HOUR", data);
+}
+
+function hrmPerDay() {
+ E.showMessage("Loading...");
+ var data = new Uint16Array(31);
+ var cnt = new Uint8Array(31);
+ require("health").readDailySummaries(new Date(), h=>{
+ data[h.day]+=h.bpm;
+ if (h.bpm) cnt[h.day]++;
+ });
+ data.forEach((d,i)=>data[i] = d/cnt[i]);
+ g.clear(1);
+ Bangle.drawWidgets();
+ g.reset();
+ setButton(menuHRM);
+ barChart("DAY", data);
}
function movementPerHour() {
@@ -47,14 +130,123 @@ function movementPerHour() {
g.clear(1);
Bangle.drawWidgets();
g.reset();
- require("graph").drawLine(g, data, {
- y:24,
- miny: 0,
- axes : true,
- gridx : 6,
- ylabel : null
- });
- Bangle.setUI("updown", ()=>menuStepCount());
+ setButton(menuMovement);
+ barChart("HOUR", data);
+}
+
+function movementPerDay() {
+ E.showMessage("Loading...");
+ var data = new Uint16Array(31);
+ require("health").readDailySummaries(new Date(), h=>data[h.day]+=h.movement);
+ g.clear(1);
+ Bangle.drawWidgets();
+ g.reset();
+ setButton(menuMovement);
+ barChart("DAY", data);
+}
+
+// Bar Chart Code
+
+const w = g.getWidth();
+const h = g.getHeight();
+
+var data_len;
+var chart_index;
+var chart_max_datum;
+var chart_label;
+var chart_data;
+var swipe_enabled = false;
+var btn;
+
+// find the max value in the array, using a loop due to array size
+function max(arr) {
+ var m = -Infinity;
+
+ for(var i=0; i< arr.length; i++)
+ if(arr[i] > m) m = arr[i];
+ return m;
+}
+
+// find the end of the data, the array might be for 31 days but only have 2 days of data in it
+function get_data_length(arr) {
+ var nlen = arr.length;
+
+ for(var i = arr.length - 1; i > 0 && arr[i] == 0; i--)
+ nlen--;
+
+ return nlen;
+}
+
+function barChart(label, dt) {
+ data_len = get_data_length(dt);
+ chart_index = Math.max(data_len - 5, -5); // choose initial index that puts the last day on the end
+ chart_max_datum = max(dt); // find highest bar, for scaling
+ chart_label = label;
+ chart_data = dt;
+ drawBarChart();
+ swipe_enabled = true;
+}
+
+function drawBarChart() {
+ const bar_bot = 140;
+ const bar_width = (w - 2) / 9; // we want 9 bars, bar 5 in the centre
+ var bar_top;
+ var bar;
+
+ g.setColor(g.theme.bg);
+ g.fillRect(0,24,w,h);
+
+ for (bar = 1; bar < 10; bar++) {
+ if (bar == 5) {
+ g.setFont('6x8', 2);
+ g.setFontAlign(0,-1)
+ g.setColor(g.theme.fg);
+ g.drawString(chart_label + " " + (chart_index + bar -1) + " " + chart_data[chart_index + bar - 1], g.getWidth()/2, 150);
+ g.setColor("#00f");
+ } else {
+ g.setColor("#0ff");
+ }
+
+ // draw a fake 0 height bar if chart_index is outside the bounds of the array
+ if ((chart_index + bar - 1) >= 0 && (chart_index + bar - 1) < data_len)
+ bar_top = bar_bot - 100 * (chart_data[chart_index + bar - 1]) / chart_max_datum;
+ else
+ bar_top = bar_bot;
+
+ g.fillRect( 1 + (bar - 1)* bar_width, bar_bot, 1 + bar*bar_width, bar_top);
+ g.setColor(g.theme.fg);
+ g.drawRect( 1 + (bar - 1)* bar_width, bar_bot, 1 + bar*bar_width, bar_top);
+ }
+}
+
+function next_bar() {
+ chart_index = Math.min(data_len - 5, chart_index + 1);
+}
+
+function prev_bar() {
+ // HOUR data starts at index 0, DAY data starts at index 1
+ chart_index = Math.max((chart_label == "DAY") ? -3 : -4, chart_index - 1);
+}
+
+Bangle.on('swipe', dir => {
+ if (!swipe_enabled) return;
+ if (dir == 1) prev_bar(); else next_bar();
+ drawBarChart();
+});
+
+// use setWatch() as Bangle.setUI("updown",..) interacts with swipes
+function setButton(fn) {
+ if (process.env.HWVERSION == 1)
+ btn = setWatch(fn, BTN2);
+ else
+ btn = setWatch(fn, BTN1);
+}
+
+function clearButton() {
+ if (btn !== undefined) {
+ clearWatch(btn);
+ btn = undefined;
+ }
}
Bangle.loadWidgets();
diff --git a/apps/health/boot.js b/apps/health/boot.js
index d6b84ce98..386d75833 100644
--- a/apps/health/boot.js
+++ b/apps/health/boot.js
@@ -1,10 +1,27 @@
+(function(){
+ var settings = require("Storage").readJSON("health.json",1)||{};
+ var hrm = 0|settings.hrm;
+ if (hrm==1) {
+ function onHealth() {
+ Bangle.setHRMPower(1, "health");
+ setTimeout(()=>Bangle.setHRMPower(0, "health"),2*60000); // give it 2 minutes
+ }
+ Bangle.on("health", onHealth);
+ Bangle.on('HRM', h => {
+ if (h.confidence>80) Bangle.setHRMPower(0, "health");
+ });
+ if (Bangle.getHealthStatus().bpmConfidence) return;
+ onHealth();
+ } else Bangle.setHRMPower(hrm!=0, "health");
+})();
+
Bangle.on("health", health => {
// ensure we write health info for *last* block
var d = new Date(Date.now() - 590000);
const DB_RECORD_LEN = 4;
const DB_RECORDS_PER_HR = 6;
- const DB_RECORDS_PER_DAY = DB_RECORDS_PER_HR*24;
+ const DB_RECORDS_PER_DAY = DB_RECORDS_PER_HR*24 + 1/*summary*/;
const DB_RECORDS_PER_MONTH = DB_RECORDS_PER_DAY*31;
const DB_HEADER_LEN = 8;
const DB_FILE_LEN = DB_HEADER_LEN + DB_RECORDS_PER_MONTH*DB_RECORD_LEN;
@@ -17,6 +34,12 @@ Bangle.on("health", health => {
(DB_RECORDS_PER_HR*d.getHours()) +
(0|(d.getMinutes()*DB_RECORDS_PER_HR/60));
}
+ function getRecordData(health) {
+ return String.fromCharCode(
+ health.steps>>8,health.steps&255, // 16 bit steps
+ health.bpm, // 8 bit bpm
+ Math.min(health.movement / 8, 255)); // movement
+ }
var rec = getRecordIdx(d);
var fn = getRecordFN(d);
@@ -30,9 +53,32 @@ Bangle.on("health", health => {
} else {
require("Storage").write(fn, "HEALTH1\0", 0, DB_FILE_LEN); // header
}
- var recordData = String.fromCharCode(
- health.steps>>8,health.steps&255, // 16 bit steps
- health.bpm, // 8 bit bpm
- Math.min(health.movement / 8, 255)); // movement
- require("Storage").write(fn, recordData, DB_HEADER_LEN+(rec*DB_RECORD_LEN), DB_FILE_LEN);
+ var recordPos = DB_HEADER_LEN+(rec*DB_RECORD_LEN);
+ require("Storage").write(fn, getRecordData(health), recordPos, DB_FILE_LEN);
+ if (rec%DB_RECORDS_PER_DAY != DB_RECORDS_PER_DAY-2) return;
+ // we're at the end of the day. Read in all of the data for the day and sum it up
+ var sumPos = recordPos + DB_RECORD_LEN; // record after the current one is the sum
+ if (f.substr(sumPos, DB_RECORD_LEN)!="\xFF\xFF\xFF\xFF") {
+ print("HEALTH ERR: Daily summary already written!");
+ return;
+ }
+ health = { steps:0, bpm:0, movement:0, movCnt:0, bpmCnt:0};
+ var records = DB_RECORDS_PER_HR*24;
+ for (var i=0;i
+
+
+
+
+
+
+
+
+
+
diff --git a/apps/health/lib.js b/apps/health/lib.js
index 791c4ce22..70305bff8 100644
--- a/apps/health/lib.js
+++ b/apps/health/lib.js
@@ -1,6 +1,6 @@
const DB_RECORD_LEN = 4;
const DB_RECORDS_PER_HR = 6;
-const DB_RECORDS_PER_DAY = DB_RECORDS_PER_HR*24;
+const DB_RECORDS_PER_DAY = DB_RECORDS_PER_HR*24 + 1/*summary*/;
const DB_RECORDS_PER_MONTH = DB_RECORDS_PER_DAY*31;
const DB_HEADER_LEN = 8;
const DB_FILE_LEN = DB_HEADER_LEN + DB_RECORDS_PER_MONTH*DB_RECORD_LEN;
@@ -16,12 +16,12 @@ function getRecordIdx(d) {
// Read all records from the given month
exports.readAllRecords = function(d, cb) {
- var rec = getRecordIdx(d);
var fn = getRecordFN(d);
var f = require("Storage").read(fn);
+ if (f===undefined) return;
var idx = DB_HEADER_LEN;
for (var day=0;day<31;day++) {
- for (var hr=0;hr<24;hr++) {
+ for (var hr=0;hr<24;hr++) { // actually 25, see below
for (var m=0;mg.getWidth()) {
hrmOffset=0;
- g.clearRect(0,80,239,239);
- g.moveTo(-100,0);
+ g.clearRect(0,80,g.getWidth(),g.getHeight());
+ lastHrmPt = [-100,0];
}
y = E.clip(btm-v.filt/4,btm-10,btm);
g.setColor(1,0,0).fillRect(hrmOffset,btm, hrmOffset, y);
y = E.clip(170 - (v.raw/2),80,btm);
- g.setColor(g.theme.fg).lineTo(hrmOffset, y);
+ g.setColor(g.theme.fg).drawLine(lastHrmPt[0],lastHrmPt[1],hrmOffset, y);
+ lastHrmPt = [hrmOffset, y];
if (counter !==undefined) {
counter = undefined;
- g.clear();
+ g.clearRect(0,24,g.getWidth(),g.getHeight());
}
});
@@ -65,7 +67,10 @@ function countDown() {
setTimeout(countDown, 1000);
}
}
-g.clear().setFont("6x8",2).setFontAlign(0,0);
+g.clear();
+Bangle.loadWidgets();
+Bangle.drawWidgets();
+g.reset().setFont("6x8",2).setFontAlign(0,0);
g.drawString("Please wait...",g.getWidth()/2,g.getHeight()/2 - 16);
countDown();
@@ -79,13 +84,14 @@ function readHRM() {
if (!hrmInfo) return;
if (hrmOffset==0) {
- g.clearRect(0,100,239,239);
- g.moveTo(-100,0);
+ g.clearRect(0,100,g.getWidth(),g.getHeight());
+ lastHrmPt = [-100,0];
}
for (var i=0;i<2;i++) {
var a = hrmInfo.raw[hrmOffset];
hrmOffset++;
y = E.clip(170 - (a*2),100,230);
- g.setColor(g.theme.fg).lineTo(hrmOffset, y);
+ g.setColor(g.theme.fg).drawLine(lastHrmPt[0],lastHrmPt[1],hrmOffset, y);
+ lastHrmPt = [hrmOffset, y];
}
}
diff --git a/apps/ios/ChangeLog b/apps/ios/ChangeLog
new file mode 100644
index 000000000..5560f00bc
--- /dev/null
+++ b/apps/ios/ChangeLog
@@ -0,0 +1 @@
+0.01: New App!
diff --git a/apps/ios/app-icon.js b/apps/ios/app-icon.js
new file mode 100644
index 000000000..b74048750
--- /dev/null
+++ b/apps/ios/app-icon.js
@@ -0,0 +1 @@
+require("heatshrink").decompress(atob("mEwwZC/AGEB/4AGwARHv4RH/wQGj4QHAAP4CIoQJAAIRWg4RL8ARVn4RL/gR/CJv9BIP934DFEZH+v/0AgMv+wRK+YCBz/7C4PfCJOfAQO//JHMCIX3/d/CJ//t4RJF4JlCCIP/koRKEYh+DCIxlBCIQADCJQgCn4DCCJSbBHIIDBXYQRI/+Sp4DB7ZsCfdQRzg4RL8ARVgARLCAgRSj4QJ/ARFgF/CA/+CA0AgIRHwARHAH4AnA"))
diff --git a/apps/ios/app.js b/apps/ios/app.js
new file mode 100644
index 000000000..b210886fd
--- /dev/null
+++ b/apps/ios/app.js
@@ -0,0 +1,2 @@
+// Config app not implemented yet
+setTimeout(()=>load("messages.app.js"),10);
diff --git a/apps/ios/app.png b/apps/ios/app.png
new file mode 100644
index 0000000000000000000000000000000000000000..79aa78f3a3cbe6d008a396f65cf4b6e739a5d494
GIT binary patch
literal 1301
zcmV+w1?u{VP)7hf>g#sri^Q3NGIK}89)+*_c9?slgwz3g`9
z_^{GuU1oM>+XYPa_q6k!Gc*4)-}%n@W)^bf$dTi2qU<<}h&!xl%Rwy@ojE8b0)->g
za1)_bIXDhdAMp9=l~PfwCtD666p5@r)K-Bhnd!95InXzi`u%&XIHwIjM4aJ>wnb3u
zN3P8}N@RP$@2gcx>1KY8bQiiC;sHdd4WUkLb2>lM83;upD@4>+qbDm9)bdilx8BHV
zI0GW$7ExQWBg#60><|&Rk=-zWM73dRR#e$Q!Q`-}Ei47H@~1NS<4c2Dp%KSqDaY6b!?C5<~d_?M
z6Ca$$>2WY~L&a_HQ|kBKR;%q9$p7I>oL8C?2cn~n9i@BaJ1OD;2A4ih-;L?Wx4#dK
z$(1nr+y_#8t6rbNq-X3GKHCAf|2l*-d<_8AatRv$W^jf<7^AxQ}==kq(abYv^U;A>5sss&y3%_6@Rc7P2&0$kN3@!jS=L
zn_X-?mD*3PW?r2zEy#d&vci1yp0e0Qvu
zg+)!NhBVj?;AUVPeeU6{$>dd2(PMEAuls|G=X!|6bh>+De0QLY&2^_(ySf7JSQh|3
zuM2N=kQW;r{Mrdo9R_us{Z}vX=HcBuU1l$RAcIEs=k_LoPn1zIyLf0!ABgk)fi}K5
z*v|EiehLcnm^!JDnk5r?aJnz)d|hB?i{jAr4qC#8xX{%>RdESxD`&GfSZ*lPsF97h
z6W=LDtrOpCm<7;t$5f=J%gA6Bz||}W$qZ$z#V`P+Xv~h93=obPnM`It3_m8_X_S%%
zLz|I7L|mar*C}9HR#aI;!TCV3x6{
+ /* eg:
+ {
+ event:"add",
+ uid:42,
+ category:4,
+ categoryCnt:42,
+ silent:true,
+ important:false,
+ preExisting:true,
+ positive:false,
+ negative:true
+ } */
+
+ //console.log("ANCS",msg.event,msg.id);
+ // don't need info for remove events - pass these on
+ if (msg.event=="remove")
+ return E.emit("notify", msg);
+
+ // not a remove - we need to get the message info first
+ function ancsHandler() {
+ var msg = Bangle.ancsMessageQueue[0];
+ NRF.ancsGetNotificationInfo( msg.uid ).then( info => {
+ E.emit("notify", Object.assign(msg, info));
+ Bangle.ancsMessageQueue.shift();
+ if (Bangle.ancsMessageQueue.length)
+ ancsHandler();
+ });
+ }
+ Bangle.ancsMessageQueue.push(msg);
+ // if this is the first item in the queue, kick off ancsHandler,
+ // otherwise ancsHandler will handle the rest
+ if (Bangle.ancsMessageQueue.length==1)
+ ancsHandler();
+});
+
+// Handle ANCS events with all the data
+E.on('notify',msg=>{
+/* Info from ANCS event plus
+ "uid" : int,
+ "appId" : string,
+ "title" : string,
+ "subtitle" : string,
+ "message" : string,
+ "messageSize" : string,
+ "date" : string,
+ "posAction" : string,
+ "negAction" : string,
+ "name" : string,
+*/
+ var appNames = {
+ "com.netflix.Netflix" : "Netflix",
+ "com.google.ios.youtube" : "YouTube",
+ "com.google.hangouts" : "Hangouts",
+ "com.skype.SkypeForiPad": "Skype",
+ "com.atebits.Tweetie2": "Twitter"
+ // could also use NRF.ancsGetAppInfo(msg.appId) here
+ };
+ var unicodeRemap = {
+ '2019':"'"
+ };
+ var replacer = ""; //(n)=>print('Unknown unicode '+n.toString(16));
+ if (appNames[msg.appId]) msg.a
+ require("messages").pushMessage({
+ t : msg.event,
+ id : msg.uid,
+ src : appNames[msg.appId] || msg.appId,
+ title : msg.title&&E.decodeUTF8(msg.title, unicodeRemap, replacer),
+ subject : msg.subtitle&&E.decodeUTF8(msg.subtitle, unicodeRemap, replacer),
+ body : msg.message&&E.decodeUTF8(msg.message, unicodeRemap, replacer)
+ });
+ // TODO: posaction/negaction?
+});
+
+// Apple media service
+E.on('AMS',a=>{
+ function push(m) {
+ var msg = { t : "modify", id : "music", title:"Music" };
+ if (a.id=="artist") msg.artist = m;
+ else if (a.id=="album") msg.artist = m;
+ else if (a.id=="title") msg.tracl = m;
+ else return; // duration? need to reformat
+ require("messages").pushMessage(msg);
+ }
+ if (a.truncated) NRF.amsGetMusicInfo(a.id).then(push)
+ else push(a.value);
+});
+
+// Music control
+Bangle.musicControl = cmd => {
+ // play, pause, playpause, next, prev, volup, voldown, repeat, shuffle, skipforward, skipback, like, dislike, bookmark
+ NRF.amsCommand(cmd);
+}
+
+/*
+// For testing...
+
+NRF.ancsGetNotificationInfo = function(uid) {
+ print("ancsGetNotificationInfo",uid);
+ return Promise.resolve({
+ "uid" : uid,
+ "appId" : "Hangouts",
+ "title" : "Hello",
+ "subtitle" : "There",
+ "message" : "Lots and lots of text",
+ "messageSize" : 100,
+ "date" : "...",
+ "posAction" : "ok",
+ "negAction" : "cancel",
+ "name" : "Fred",
+ });
+};
+
+E.emit("ANCS", {
+ event:"add",
+ uid:42,
+ category:4,
+ categoryCnt:42,
+ silent:true,
+ important:false,
+ preExisting:true,
+ positive:false,
+ negative:true
+});
+
+*/
diff --git a/apps/launch/ChangeLog b/apps/launch/ChangeLog
index 09569d8da..bd8a9bd03 100644
--- a/apps/launch/ChangeLog
+++ b/apps/launch/ChangeLog
@@ -4,4 +4,5 @@
0.04: Now displays widgets
0.05: Use g.theme for colours
0.06: Use Bangle.setUI for buttons
-0.07: Theme colours fix
\ No newline at end of file
+0.07: Theme colours fix
+0.08: Merge Bangle.js 1 and 2 launchers
diff --git a/apps/launch/app.js b/apps/launch/app-bangle1.js
similarity index 98%
rename from apps/launch/app.js
rename to apps/launch/app-bangle1.js
index 449e16e62..3d4682e55 100644
--- a/apps/launch/app.js
+++ b/apps/launch/app-bangle1.js
@@ -16,7 +16,7 @@ function drawMenu() {
var w = g.getWidth();
var h = g.getHeight();
var m = w/2;
- var n = (h-48)/64;
+ var n = Math.floor((h-48)/64);
if (selected>=n+menuScroll) menuScroll = 1+selected-n;
if (selected{var a=s.readJSON(app,1);return a&&{name:a.name,type:a.type,icon:a.icon,sortorder:a.sortorder,src:a.src};}).filter(app=>app && (app.type=="app" || app.type=="clock" || !app.type));
+apps.sort((a,b)=>{
+ var n=(0|a.sortorder)-(0|b.sortorder);
+ if (n) return n; // do sortorder first
+ if (a.nameb.name) return 1;
+ return 0;
+});
+apps.forEach(app=>{
+ if (app.icon)
+ app.icon = s.read(app.icon); // should just be a link to a memory area
+});
+// FIXME: not needed after 2v11
+var font = g.getFonts().includes("12x20") ? "12x20" : "6x8:2";
+// FIXME: check not needed after 2v11
+if (g.wrapString) {
+ g.setFont(font);
+ apps.forEach(app=>app.name = g.wrapString(app.name, g.getWidth()-64).join("\n"));
+}
+
+function drawApp(i, r) {
+ var app = apps[i];
+ if (!app) return;
+ g.clearRect(r.x,r.y,r.x+r.w-1, r.y+r.h-1);
+ g.setFont(font).setFontAlign(-1,0).drawString(app.name,64,r.y+32);
+ if (app.icon) try {g.drawImage(app.icon,8,r.y+8);} catch(e){}
+}
+
+g.clear();
+Bangle.loadWidgets();
+Bangle.drawWidgets();
+
+E.showScroller({
+ h : 64, c : apps.length,
+ draw : drawApp,
+ select : i => {
+ var app = apps[i];
+ if (!app) return;
+ if (!app.src || require("Storage").read(app.src)===undefined) {
+ E.showMessage("App Source\nNot found");
+ setTimeout(drawMenu, 2000);
+ } else {
+ E.showMessage("Loading...");
+ load(app.src);
+ }
+ }
+});
diff --git a/apps/launchb2/ChangeLog b/apps/launchb2/ChangeLog
deleted file mode 100644
index a96ee84e1..000000000
--- a/apps/launchb2/ChangeLog
+++ /dev/null
@@ -1,3 +0,0 @@
-0.01: New App!
-0.02: Fix occasional missed image when scrolling up
-0.03: Text wrapping, better font
diff --git a/apps/launchb2/app.js b/apps/launchb2/app.js
deleted file mode 100644
index 371326498..000000000
--- a/apps/launchb2/app.js
+++ /dev/null
@@ -1,79 +0,0 @@
-var s = require("Storage");
-var apps = s.list(/\.info$/).map(app=>{var a=s.readJSON(app,1);return a&&{name:a.name,type:a.type,icon:a.icon,sortorder:a.sortorder,src:a.src};}).filter(app=>app && (app.type=="app" || app.type=="clock" || !app.type));
-apps.sort((a,b)=>{
- var n=(0|a.sortorder)-(0|b.sortorder);
- if (n) return n; // do sortorder first
- if (a.nameb.name) return 1;
- return 0;
-});
-var APPH = 64;
-var menuScroll = 0;
-var menuShowing = false;
-var w = g.getWidth();
-var h = g.getHeight();
-var n = Math.ceil((h-24)/APPH);
-var menuScrollMax = APPH*apps.length - (h-24);
-// FIXME: not needed after 2v11
-var font = g.getFonts().includes("12x20") ? "12x20" : "6x8:2";
-
-apps.forEach(app=>{
- if (app.icon)
- app.icon = s.read(app.icon); // should just be a link to a memory area
-});
-if (g.wrapString) { // FIXME: check not needed after 2v11
- g.setFont(font);
- apps.forEach(app=>app.name = g.wrapString(app.name, g.getWidth()-64).join("\n"));
-}
-
-function drawApp(i) {
- var y = 24+i*APPH-menuScroll;
- var app = apps[i];
- if (!app || y<-APPH || y>=g.getHeight()) return;
- g.setFont(font).setFontAlign(-1,0).drawString(app.name,64,y+32);
- if (app.icon) try {g.drawImage(app.icon,8,y+8);} catch(e){}
-}
-
-function drawMenu() {
- g.reset().clearRect(0,24,w-1,h-1);
- g.setClipRect(0,24,g.getWidth()-1,g.getHeight()-1);
- for (var i=0;i{
- var dy = e.dy;
- if (menuScroll - dy < 0)
- dy = menuScroll;
- if (menuScroll - dy > menuScrollMax)
- dy = menuScroll - menuScrollMax;
- if (!dy) return;
- g.reset().setClipRect(0,24,g.getWidth()-1,g.getHeight()-1);
- g.scroll(0,dy);
- menuScroll -= dy;
- if (e.dy < 0) {
- drawApp(Math.floor((menuScroll+24+g.getHeight())/APPH)-1);
- if (e.dy <= -APPH) drawApp(Math.floor((menuScroll+24+g.getHeight())/APPH)-2);
- } else {
- drawApp(Math.floor((menuScroll+24)/APPH));
- if (e.dy >= APPH) drawApp(Math.floor((menuScroll+24)/APPH)+1);
- }
- g.setClipRect(0,0,g.getWidth()-1,g.getHeight()-1);
-});
-Bangle.on("touch",(_,e)=>{
- if (e.y<20) return;
- var i = Math.floor((e.y+menuScroll-24) / APPH);
- var app = apps[i];
- if (!app) return;
- if (!app.src || require("Storage").read(app.src)===undefined) {
- E.showMessage("App Source\nNot found");
- setTimeout(drawMenu, 2000);
- } else {
- E.showMessage("Loading...");
- load(app.src);
- }
-});
-Bangle.loadWidgets();
-Bangle.drawWidgets();
diff --git a/apps/launchb2/app.png b/apps/launchb2/app.png
deleted file mode 100644
index 8b4e6caa2fe4720a32f492bd00c6e68751fabfbc..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001
literal 899
zcmV-}1AP36P)EZi8a;XNfX0KK)EG5t2&jmXSR!CR3JOvSF^w(VT@Oq6O4&}^ElAua
zX|l63`{wtXcjoO3*x1-CYovub^cOBY?cfcO1>;+VoWa;>Pk;!SG_WW*Es5gZ1-`R@
z4t>oKYJ)f#xYia)IV)#&xZ*BHYck+F1K9eED0&f|F;Lm{VL;rb?(c)W{8d&
zzrH4`vJJ?>K!fln4K4LjpAx19%+|UjgL{LF?3gt^suTXG8HN@KQv>tY{cLjA%Q)j?
zIX0ma_G_?6n>drF(WulAaitj}A^+b$v5k$*zr})O^uogfX;+bphn_9#OZ}pdk^!&c
zqrUVgUd3m%o}@|o!cmyJiIaP-*BlK-1F7-kNoN^X5h5L~tnN`_&
zI3(jUhvcNZU>jbg3|-gg8hTDln@m+>N(dP^vh_T*8x8>Qb*ytvU&Y#;lz3_Z*tBLl
zEghFFmS~QUfhzCr>E}|Fxr&yKOBoXz4vqjF
zlzKwtw{idZM95W(Tbb#rO0Wkqaj6$F@MWZp>h=2o=nr;LTzCQiH!$$4i=w<5W87%F
zs6NXOGI0OH6?#VB0k9%#HO2Wg(|z6FTj}`r1kmXWJk5wmGlUFGsuA7}JOW^&y9!O$
zkR=&S*XaHEp1^o_Mn#&D^ig6k6`9rJMEHEc@fMjg8GR
Z=Px>?AD_-bQjY)t002ovPDHLkV1j2qp7Q_z
diff --git a/apps/lcars/ChangeLog b/apps/lcars/ChangeLog
new file mode 100644
index 000000000..c7ec09d30
--- /dev/null
+++ b/apps/lcars/ChangeLog
@@ -0,0 +1 @@
+0.01: Launch app
diff --git a/apps/lcars/README.md b/apps/lcars/README.md
new file mode 100644
index 000000000..fdce30c1b
--- /dev/null
+++ b/apps/lcars/README.md
@@ -0,0 +1,8 @@
+# LCARS clock
+
+A simple LCARS inspired clock that shows:
+ * Current time
+ * Current date
+ * Battery level
+ * Steps
+
diff --git a/apps/lcars/background.png b/apps/lcars/background.png
new file mode 100644
index 0000000000000000000000000000000000000000..1ee4297c642db661ff1b3441ef7ba950eec72d70
GIT binary patch
literal 1497
zcmbW1`#Tc~7{@n@xt%UyUQNs?AsxA#
zHa$wwL<{8w(?NLPZvM
zgqGC!_1lo1NR(<*ycWkd+gJ$>hHoq(#fk)
zLr#*bQ#P23x1u?)>qU$z&|pPITm7o2V(%y5%6q3FiXEm1)+OB-agv*xxqU*W?+2_&E#{>qVmBG75RC=YK~v7|*o)k1$aVa205hn>OKf3wHA
zo@6FsSXuGW*yy)=-W}e7<~Z5p^z-C+@BCe#A3KDuf{xgnfTE3EJN;
z6eOh>E>#*jk}WZCo2mpR$UGKP)j>E80Q5I?hj@3=b4vRk63;BOHAZeUjeT8dx$IEV
zvN1lIn9-ezu)xF?giA+@ZLjStm1hj;zo6$B6tT@~ONSc=o5rO#D!YqE3T*3z-}Uf!
z)Ez>w{wLfw!|!g!N_(Cpf1M}^nDjJB3Fj`~!#}s)d2ocMC0*QVVq6DIj}=g#z#E<+
zPJ?)w4y;q*y&B+{K5k2=D+IbPPL=5NjqMWC~IeV&wSi$Akn9<&6hociQ&Q>
zsOPIaOH-P!B{#aXB5^TX{a)_7i|Fa}PWoEJQ4p@h3ck8`*$l!|aklqs{r6NgkSO?H
zF91ngTF4AtL*J$5keYO^7uKxKegzG!Z@e3L+Kn4D2OKL$r_#AH?AGLuVwg$46U;P}
zK@IycRst&NyX=gni4u)N1_?(yW}3*LKlA5!RIPYz@Xu5H)0qF-LD=+FDdUpmKxTdO
z4!ds_byd*X`<4?CM)~(iIGF9a=h=KWiyGJA`^T|_@J}z>Hzugiu0>Appt7KkfGjMY
z@p=C-jTQ8qmFgd%?3=`iS6Vo>N|C3}hBcku?>$@tVcc`W^bvAB_n4X!V0mcelHq=~
z*NT1-l+cwN6giI0IY%rb)-8@a(ES2iELz1`zes7ThV~Gv-bLBkI@&$7|6;XipMx-;
z8ylfPQR)r06*a?$56X~*y1M7#A{!|7JR>9Yp3>8G#OG}hyFuAkkc#gHKzU+3>fJ)G
F{0C>+p!fg)
literal 0
HcmV?d00001
diff --git a/apps/lcars/lcars.app.js b/apps/lcars/lcars.app.js
new file mode 100644
index 000000000..cf884a6b7
--- /dev/null
+++ b/apps/lcars/lcars.app.js
@@ -0,0 +1,99 @@
+const locale = require('locale');
+
+
+/*
+ * Assets: Images, fonts etc.
+ */
+var img = {
+ width : 176, height : 151, bpp : 3,
+ transparent : 0,
+ buffer : require("heatshrink").decompress(atob("gF58+eAR14IN1fvv374CN7yD/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/AH4A/AH4A/AB1z588+YCN+RBuj158+eARyD/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf4AUhyD/gEDQaHz4BCuQaNAIN0PQaHIIN0BQaF5IN0AQaHPkBBug6DQ8iEvQaE8yBBuhyDPAQNAINsBQaACBkhCuQaACpVo0cQaACo4CFGjyD/AAMPQf4ACQf4ADgiD+AH4A/AH8J02atICIwEAgPnz15AR3gEgM27dt2wCTF4IABgYROgN9+/fAR14ILsaQBKDakwjKF5oABKZ6DwgxTPQeEmQf5cPQeMBLhyDxgJTRQd0JKaKDuhKD/gENQf6D/F4VNQf8AKaKDvKBYnBAGZQKzBB1QZOwIGqDJsBA2QZJA3QZGYIPCDH4CD/0xA4QY+wIPKDGwCD/tpB6Qf6DHthA5QY1oIPSD/QY9gQf/bIPaD/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/AF8JQYgCdsEHnnz54CJgIdLwEAhqDEATtggPnz15ARHkgIdLIIKAgQcCAgQcAA/gAA=="))
+}
+
+Graphics.prototype.setFontMinaSmall = function(scale) {
+ // Actual height 18 (17 - 0)
+ g.setFontCustom(atob("AAAAAAAAAAAAAAAAAAAAA/8w/8wAAQAAAAAA4AA8AAAAA8AAwAAAAAAEABEABEQB/w/8AxEABEwB/w/8AxEABEABEAAAAH4MP8MMEM8GPcGOMGMMGMMH4ABwAAA/gAwgAggAggQwhw/nAAOAA4ADgAOfw8YwwQwAQQAYwAfwAPgAGAAfg85w/wwzgww4wwdgwHggHgAPwAIQAAA8AAwAAAAAAfwH/+fAP4ABgAAgAA4ABfAPH/+A/wAAAAAAEAAHgAfAAfAAHgAMAAAAAAAABgABgABgAf4AP4ABgABgABAAAAAADAADwADAAAAAgAAwAAwAAwAAwAAwAAAAADAADAADAAAAAAEAA8AH4A+AHwA+AAwAAAAAf/g//wwAwwAwwAwwAwwAwwAwf/gH+AAAAAAAYAAwAAwAA//wAAAAAAAAAwAwwBwwDwwDwwGwwcww4wfwwPAwAAAAAAwAwwQwwQwwQwwQwwYww4wf/gHHAAAAAEAAeAA+ADmAPGAcGAwGAh/wD/wAEAAEAAAAf4w/4wwwwwwwwwwwwwwwww/wgfgAAAAAAP/Af/gwwwwwwwwwwwwwwwwwww/gAAAgAAwAAwAAwAwwHww/Az4A/AA8AAAAAAAAfPg//wxwwwwwwwwwwwwww//wffgAAAAAAfwQ/wwwQwwYwwQwwQwwQw//wP/AAAAAAAMDAMDAMDAAAAAAAMDAMDwMDAAAAAAADgADgAHwAGwAMYAMYAIIAAAAEQAGYAGYAGYAGYAGYAGYAGYAAAAMYAMYAGwAGwAHgADgADAAAAAwAAwAAwAAwcwwcwwQAwQA/wAfgAAAAAAAB/8D/+TAGbHjbPzbMTbMzbMzb/zZ/zYAGf/+H/8AAAAAAABwAPwA+AH+A+GA8GAfmAD+AAfgADwAAQAAA//w//wwwwwwwwwwwwwxww//wffgAAAAAAP/Af/gwBwwAwwAwwAwwAwwAwwAwAAA//w//wwAwwAwwAwwAwwAw4Bwf/gH+AAAAAAAf/g//wwQwgQQgQQgQQgQQgQQgAQAAAf/w//wwQAgQAgQAgQAgQAgQAgAAAAAP/Af/gwAwwAwwAwwYwwYwwfwwfwAAAAAA//w//wAYAAYAAYAAYAAYAAYA//w//wAAA//w//wAAAAAAAAwAAwAAw//w//AAAA//w//wAYAA4AD8AHHAeDg4AwgAQAAAAAA//g//wAAwAAwAAwAAwAAwAAwAAQAAAP/w//w+AAPwAB+AAHwADwA/gH4A/AA/4A//wAAwAAA//w//wcAAPAADgAA4AAeAAHAADw//wAAAAAAH/Af/g4AwwAwwAwwAwwAwwAwcDwP/gB4AAAA//w//wwQAwQAwQAwYAwwA/wAPgAAAAH/Af/gwAwwAwwAwwA8wA8wA2cDkP/gB4AAAA//w//wwYAwYAwYAwcAwfA/zwPgwAAAAAAfgA/wwwQwwYwwYwwYwwYwwfwAPgAAAAAAwAAwAAwAA//w//wwAAwAAwAAwAAAAA/+A//gABwAAwAAwAAwAAwAAwAPg//AAAAAAA4AA/AAH4AA/AAHwADwAfgD8AfgA8AAgAAAAA4AA/AAH4AA/AAHwAHwA/AP4A/4Aw/AAHwAHwA/AP4A+AAwAAAAAwAw8DwOHAD8AB4AD8AOHA8DwwAwAAAAAAwAA8AAPAADwAA/wB/wHgAeAA4AAgAAgAQwBwwHwwOww8wxww3gw+Aw4AwwAQAAAH//f//YAAYAAQAAwAA+AAPwAB+AAPwAB8AAMQAAYAAYAAf//AAAAAA"), 32, atob("BgUHDAoRCwMGBggJBQYFBwwHCwsLCwsKCwsFBQkICQoPDAsKDAoKCwsEBgsKDgwMCgwLCwoMDBELCwoGBwY="), 18+(scale<<8)+(1<<16));
+}
+
+Graphics.prototype.setFontMinaLarge = function(scale) {
+ // Actual height 35 (34 - 0)
+ g.setFontCustom(atob("AAAAAAAAAAAAAAAAAAAAAAAB4AAAAAPgAAAAA+AAAAAD4AAAAAHgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOAAAAAH4AAAAD/gAAAB/8AAAA/+AAAAf/AAAAP/gAAAH/wAAAD/4AAAD/8AAAB/+AAAAP/AAAAA/AAAAADgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH///4AA////wAH////gA+AAAfADwAAA8AOAAABwA4AAAHADgAAAcAOAAABwA4AAAHADgAAAcAOAAABwA4AAAHADgAAAcAOAAABwA8AAAPAD4AAB8AH////gAP///8AAf///gAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAcAAAAADgAAAAAOAAAAAB4AAAAAHAAAAAA8AAAAAD////8AP////wA/////AD////8AAAAAAAAAAAAAAAAAAAAAGAAAAAA4AAAHADgAAA8AOAAAHwA4AAA/ADgAAD8AOAAAfwA4AAD/ADgAAfcAOAAD5wA4AAfHADgAD4cAOAAfBwA4AD8HADwAfgcAPAD8BwAeA/AHAB+f4AcAD//ABwAH/wAHAAH8AAcAAAAAAAAAAAAAAAAAAAAAGAAABgAYAAAGADgAAAcAOAHABwA4AcAHADgBwAcAOAHABwA4AcAHADgBwAcAOAHABwA4AcAHADgBwAcAOAHABwA4AcAHADwB4A8APAPgDwAfD//+AB////4AD/8//AAD/B/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHAAAAAA8AAAAAPwAAAAD/AAAAAf8AAAAH9wAAAB/HAAAAPwcAAAD+BwAAAfgHAAAH8AcAAB/ABwAAPwAHAAA+AAcAADgABwAAIAAHgAAAH///AAB///8AAH///wAAAAcAAAAABwAAAAAHAAAAAAYAAAAAAAAAAAAAAAAAAAAAAAH/8AYAP//wBgA///AHAD//wAcAOAPABwA4A4AHADgDgAcAOAOABwA4A4AHADgDgAcAOAOABwA4A4AHADgDgA8AOAOADwA4A8APADgD8H4AOAH//gA4AP/8AAAAP/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB/gAAAD//+AAB///+AAP///8AB/BgH4APgOAHwA8A4APADgDgAcAOAOABwA4A4AHADgDgAcAOAOABwA4A4AHADgDgAcAOAOABwA4A4AHADgDwA8AOAP//wA4Af/+ABgA//wAAAA/8AAAAAAAAAAAAAAAAAAAAAA4AAAAADgAAAAAOAAAAAA4AAAAADgAAAAAOAAAAQA4AAAHADgAAD8AOAAA/wA4AAf+ADgAP/gAOAD/wAA4B/8AADg/+AAAOP/AAAA//wAAAD/4AAAAP8AAAAA/AAAAABgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH/h/4AA//P/wAH////gA+B/AfADwD4A8AOAHABwA4AcAHADgBwAcAOAHABwA4AcAHADgBwAcAOAHABwA4AcAHADgBwAcAPAPgDwA8A+APAB////4AH////gAP/j/8AAD4B8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAH/wAAAA//gAYAH//ABwA//+AHADwB4AcAOADgBwA4AOAHADgA4AcAOADgBwA4AOAHADgA4AcAOADgBwA4AOAHADgA4AcAPADgDwA+AOAfAB+A4f4AD////AAH///4AAD//8AAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeAAeAAB8AD4AAHwAPgAAfAA+AAB4AB4AAAAAAAAAAAAAAAAAAAAAA="), 46, atob("CxAaDhgYGBgZFhkZCw=="), 40+(scale<<8)+(1<<16));
+}
+
+
+/*
+ * Queue drawing every minute
+ */
+var drawTimeout;
+function queueDraw() {
+ if (drawTimeout) clearTimeout(drawTimeout);
+ drawTimeout = setTimeout(function() {
+ drawTimeout = undefined;
+ draw();
+ }, 60000 - (Date.now() % 60000));
+}
+
+
+/*
+ * Draw watch face
+ */
+function draw(){
+ g.reset();
+ g.clearRect(0, 24, g.getWidth(), g.getHeight());
+
+ // Draw background image
+ g.drawImage(img, 0, 24);
+
+ // Write time
+ var currentDate = new Date();
+ var timeStr = locale.time(currentDate,1);
+ g.setFontAlign(0,0,0);
+ g.setFontMinaLarge();
+ g.drawString(timeStr, 115, 53);
+
+ // Write date
+ g.setFontAlign(-1,-1,0);
+ g.setFontMinaSmall();
+
+ var dayName = locale.dow(currentDate, true).toUpperCase();
+ var day = currentDate.getDate();
+ g.drawString("DATE:", 40, 107);
+ g.drawString(dayName + " " + day, 100, 105);
+
+ // Draw battery
+ var bat = E.getBattery();
+ g.drawString("BAT:", 40, 127);
+ g.drawString(bat+"%", 100, 127);
+
+ // Draw steps
+ var steps = Bangle.getStepCount();
+ g.drawString("STEP:", 40, 147);
+ g.drawString(steps, 100, 147);
+
+ // Queue draw in one minute
+ queueDraw();
+}
+
+// Clear the screen once, at startup
+g.setTheme({bg:"#000",fg:"#fff",dark:true}).clear();
+
+// draw immediately at first, queue update
+draw();
+
+
+// Stop updates when LCD is off, restart when on
+Bangle.on('lcdPower',on=>{
+ if (on) {
+ draw(); // draw immediately, queue redraw
+ } else { // stop draw timer
+ if (drawTimeout) clearTimeout(drawTimeout);
+ drawTimeout = undefined;
+ }
+});
+
+// Show launcher when middle button pressed
+Bangle.setUI("clock");
+
+// Load widgets
+Bangle.loadWidgets();
+Bangle.drawWidgets();
\ No newline at end of file
diff --git a/apps/lcars/lcars.icon.js b/apps/lcars/lcars.icon.js
new file mode 100644
index 000000000..c404728e0
--- /dev/null
+++ b/apps/lcars/lcars.icon.js
@@ -0,0 +1 @@
+require("heatshrink").decompress(atob("mEwgeevPnAQsc+fPngCE+/fvoCEvAbIA4/AgFzEZwRBjwjNvBUBEZ3eCIMOEZtwCIMBEZuARYU5EZecTocHEZf0CIcBEbvgaggjKTwIAEbQpoHAAiSEeoYQHJQr1CCBJKEIgcBI4xKFaIdt3AOFgfuAYMeEYLRBj1pLQ4ICuYjBAgPbtoRHhu3AYN5VoMGzVpI49502AgPPVoM27dsK48N23cgE5CgOmzVoCI4LBzCSB8EP2wjJgILBAYMAhIjBsAjJzVwg47C7YRJEYhfBEZXmEZ53CI4q2BEAiVCkwjCNYaMGboQjDkBfDCAbdB04EBgyPDC4YAD/dt2wRCHIM5njXCCAcHboOmCIQ0B5/nfYT6DFIIjBeAcOvM8+EAjitFEYJEBAANzEYOeeowjCFgUDzwjB+YrDgAgBEYWcA4Mc+YjCvAQCgftEANuDIYOBEYXPNwIAIg4OCCgXkCBEOEZDvBEAhEB4AjF/inB8+OJQOOvILBoAjGU4IFDAQYjGbQIdCAQt4EY0DEZACDEYceEZACDC4bLBEZwCO"))
diff --git a/apps/lcars/lcars.png b/apps/lcars/lcars.png
new file mode 100644
index 0000000000000000000000000000000000000000..167352ef4bd6db8c6de6bc845396944af7bcc040
GIT binary patch
literal 1823
zcmV+)2jKXLP)@u77`!)Bw=brPOnLGD!jCw8=Ov8ZqvPg{2d`k`HpD>tb0K0#?jIy#aTr1jOuownQ
zAmwQdN=s+nd__eCp7k9wNF0MCP~~iaq@*MYmzVrbaXKGwYIk6Y1a`><8huqR#ukA{?yb|PUCT!z(;99OiycGm6b{*e!a1t
zvzVMEP^fy2`uciSwhtdZ#9MaRoW$iUfph21>12QMF$%w7!v;)W1DX^6
zL9|;8nwy(3JUq;id|=(Wbr{PXM|5;FEG#T2eG-WTSFd|(PavxD7ShwxDF)8Y&dATt
zNBOr8D11Rd0T;Sy{)B=v7?zA+`*!NIkxHeIqOr
zm^0Dyar^ddMm{$;7skfMWc$>qx#g7rfb#NklJM~GK*p9#je)qHKC@6Nl}H~cTazHETHLdxO(+!
z8b^$dj*^YVix+DR@c8j#{gml|qm_}2ysfP*jU%kBt;t4jZ?9J8hMfPiPFJ?y
z1fS0*8&y?R8XYsO5W2$L+#K^`GNHP4y1upD#ZsEH`1KWU$L~o%*+gFX=%9ZtGQCAZsVMNS)#5K#Ky*wJV6ln=0M9_
zd-fsrSReUg05ivn6|wGQ`>HaZwRn4bdnkSx!vWiaT5Y4=iH4`ACyj4vYQnxhLJ<)W
zL8Z<}#N;1P2G}G+k!l=H`Z0p?;VDMj1nU&qzn@N8^skdu?cC?3#I*g6>6gs$H2
zkeZrGyB{;b-`^jA_DY*%I14hF4As@usI06+e}6xKL`GXapO5_^jTr1x?~gWxm{8mB
zHSM~|20R`QDwRs_(u=33Cw2u^YP4Zh8(s6RL@xnKrINLHdwY9?hK53Pej1jRmH>eE
z_I9n~^s0>lfU&VLHgDdHy1F_H3=HTz-qh3-0)YVD-riX5ltSr?ii$#9TparPG|ZjX
zV3mNieGZLFNJxN%X$-b)o#RwQ3c9*-78ClO?SPMu51f|Y&}hf%3(PEaGe27Ju0I#9jwSp)DMP1B
zvkj{+Ff=p-f*=qV)z5(}Zow)6g+hUt82X(5KY-N~`q-yLyU~Aye*r)QY$kz4$b$d?
N002ovPDHLkV1l5}TXFyZ
literal 0
HcmV?d00001
diff --git a/apps/matrixclock/ChangeLog b/apps/matrixclock/ChangeLog
index d53df991b..7cc9144b1 100644
--- a/apps/matrixclock/ChangeLog
+++ b/apps/matrixclock/ChangeLog
@@ -1 +1,2 @@
-0.01: Initial Release
+0.01: Initial Release
+0.02: Support for Bangle 2
diff --git a/apps/matrixclock/matrixclock.js b/apps/matrixclock/matrixclock.js
index 0bf33fd68..ab18c13b8 100644
--- a/apps/matrixclock/matrixclock.js
+++ b/apps/matrixclock/matrixclock.js
@@ -12,6 +12,8 @@ const Locale = require('locale');
const SHARD_COLOR =[0,1.0,0];
const SHARD_FONT_SIZE = 12;
const SHARD_Y_START = 30;
+const w = g.getWidth();
+
/**
* The text shard object is responsible for creating the
* shards of text that move down the screen. As the
@@ -111,7 +113,7 @@ var dateStr = "";
var last_draw_time = null;
const TIME_X_COORD = 20;
-const TIME_Y_COORD = 100;
+const TIME_Y_COORD = g.getHeight() / 2;
const DATE_X_COORD = 170;
const DATE_Y_COORD = 30;
const RESET_PROBABILITY = 0.5;
@@ -141,29 +143,26 @@ function draw_clock(){
}
var now = new Date();
// draw time. Have to draw time on every loop
- g.setFont("Vector",45);
- g.setFontAlign(-1,-1,0);
+
+ g.setFont("Vector", g.getWidth() / 5);
+ g.setFontAlign(0,-1);
if(last_draw_time == null || now.getMinutes() != last_draw_time.getMinutes()){
- g.setColor(0,0,0);
- g.drawString(timeStr, TIME_X_COORD, TIME_Y_COORD);
+ g.setColor(g.theme.fg);
+ g.drawString(timeStr, w/2, TIME_Y_COORD);
timeStr = format_time(now);
}
- g.setColor(SHARD_COLOR[0],
- SHARD_COLOR[1],
- SHARD_COLOR[2]);
- g.drawString(timeStr, TIME_X_COORD, TIME_Y_COORD);
+ g.setColor(SHARD_COLOR[0], SHARD_COLOR[1], SHARD_COLOR[2]);
+ g.drawString(timeStr, w/2, TIME_Y_COORD);
//
// draw date when it changes
g.setFont("Vector",15);
- g.setFontAlign(-1,-1,0);
+ g.setFontAlign(0,-1,0);
if(last_draw_time == null || now.getDate() != last_draw_time.getDate()){
- g.setColor(0,0,0);
- g.drawString(dateStr, DATE_X_COORD, DATE_Y_COORD);
+ g.setColor(g.theme.fg);
+ g.drawString(dateStr, w/2, DATE_Y_COORD);
dateStr = format_date(now);
- g.setColor(SHARD_COLOR[0],
- SHARD_COLOR[1],
- SHARD_COLOR[2]);
- g.drawString(dateStr, DATE_X_COORD, DATE_Y_COORD);
+ g.setColor(SHARD_COLOR[0], SHARD_COLOR[1], SHARD_COLOR[2]);
+ g.drawString(dateStr, w/2, DATE_Y_COORD);
}
last_draw_time = now;
}
@@ -232,10 +231,10 @@ function startTimers(){
Bangle.on('lcdPower', (on) => {
if (on) {
- console.log("lcdPower: on");
+ //console.log("lcdPower: on");
startTimers();
} else {
- console.log("lcdPower: off");
+ //console.log("lcdPower: off");
clearTimers();
}
});
diff --git a/apps/matrixclock/screenshot_matrix.png b/apps/matrixclock/screenshot_matrix.png
new file mode 100644
index 0000000000000000000000000000000000000000..3d843848cb1a1d1fdbaaf9cc1b236ba4cb8ebdc5
GIT binary patch
literal 4990
zcmV-^6M^iBP)Px|I7vi7RCr$PU5k?BCJfB}|3`0Xyn_$R5^6mFKS-r2xf?7{s}U`X?VsP@-{1e%
zKMR4IBJi^aeACFSVQ&a50xu$57J;9izoE4>!Xofx!?y-j2;3s^BJkqLWfAxg_>+BG
znRpTSvf*0;D+J!Ot{^8)1pcY>b_2t;d9euA8-bd@5{Vaqdq-i}hY;X0tc$=}ZzV&o
z46Grwezzh(N$ipkTBnP^8ba&$2m&>UQ;WDo+(=l~wH5@pGECYFhIb(rfh{3eZKDVv
zd%#;^r`<|k#EphxUGG6)5!i#;y5cL
z&~on52&_knE*+jVxC{hV2F^fn*_ejFBJecmSH~F$ECOesxNJ;AU=es4^sD0x1Qvla
zP+T^qA+QKM4f@q_1_Fz~87M9r(-2q$o(BEuI0J#91pb_GyUOGEKmUDS_0mA`_rJe4
z&z833&l$i|8+1HeU#&LU3u!*Mb0-X=rw+M(N5*&&_yPo$CR0sftxb!Z9~Wx^mp^Zl
z^LxM6OFEjAqV}xG;k5*oGGHt7^~k_ncBg*_wW&_N6f)M?aS_-$HJJ8gwo+j0Oi(WK
zW&~}RVy61Or+zenrSX19t4Wwc8<4Lv0+VAJzug0vFu_&M#fy-n$WhsEb
zwQX*B*ZvL}8>hKz`5=*6(!hnV^?z#&qIkbj19P*s&bYmcy*g<7s_-r9km6}yB^pJZ
zxA5c0T9aCwM9j13^(5s8e6Z#GRLbiJyv6*p0YEbFNCH#IvIQ3)u#{mDjjCl|iMZuD
zO&8NiNeeYfU=9OqjNI>9GV<88Tr;JJqs!KeS$rD9L;_pMTa%iTVW$H1yL~C?)tc-U
z@=!TePl{eO%*i+H)lyc6TJ7s1PRa<}-)D|t4?fvGnw5by@^YG()ya+RFp7JPXNCr*
zWR2Ri#CvE4o-sqEWshc^2!SPyy7lqZ$x={SH&iV9z?PstsqJtAUjaO>NW3F~waIY>
ztb31PD1kSUY+s%^ejIe=D3!o?YSFJTznOnChBT;p|sCXZ;}uOqM*_&Z&99x)EIszXafhngdnIo1xv)R4_LLNo82BYT~Bepd^
zwBId#TWxU~_%^!`r}r3;<&Hk;9&xM{ghk-L*J@?pn?c~nScYog*6Wd|7R>-xBC)hz
z8UfOhwR*QjNQJaP#%Z;~wU0=*p#`YYFigX#E`8NCIzt4}KevH87F(D5=nPh#ZTIH*)Q80+*MX(3hJ~
zQbn9Xu3ZFp;f?W!HAM*X1f!C2OUwhX5eg$UPs_;0Iy=N5NSX0H;}+o1|S(Y3%5B0
zu0HhA`r_*d4VMMvY&)}Rg4sp!Py&}ApB^m|qQ3}in-=tR-$?w>n-kd+Z3=;HVQIG>
zxm;zA`>nRT+n*9%i@oMLtt&2Ww!R36#rAV*-LV7zp~>tGy`+b#7W=F&^EZe
zK-?|h8lm>x2lA=ek@{B~WBt1~ZkLb{tw)NelV|{}8aR6ivjne4+r$1Z^TqleeMW3%
z?;8)#z}gqxJ%rS3$$}=g)0rFJTF=giM_LIevog
z^rcXAyUQ{ttzLPQgrLAs@>;T~1?1B9(FFdI_k$^S9->}tX01HgHOO!Yp%J+Ef{?Aj
zJd-H%nuCNOUqU_A9GzFUOutZD!wF0^@Znu)10D%_1A)sbrV-e(^rM6t-EInjISp(D
z)4K#dQ9|r*7b5z1Bl33m`tP6Y<-4}qd*zfS##91tpqA4-j?+_C()SGF=*&a=JCO7H
zdR6X`qJlvt;jD4UEF^~#xF%2rfZi-Tn{1={mk6BMzb6KsYc7$2Gl9Fp^3}ti44bt?
zXUT61rNHRV
zfjJExvZWE&1AylIp?E_g^KB#9wPfL~ElAvYXw3-(PQp78_%l|I&rs~?Yrz=&5IY->
zdJtH1-b>(?T}u}BOv+~84<)e2XM}jCn=cV5(;~1p&>Dez*hRB&m%cAR;4BSHxg@T7
z4WBg#4MKlB)3I=x#s(B?M
zpw}bgMcP;hX=y9@A#N2ix9sTzK>R^!3C}J@@g3pYCV}os;G*BQ+4_A=m1PO*P2dcD
zXCX4zmfbS!Mc|LNRu0ok+4n^m-u0qWquoh%Zj-Xjf%3c{HmiB@@=RRDk&bK&}5|mb`
z-G;#1GUGf@BN5ngI9}T_LV_37rHEFv^ucD{O_VQX8N#7`~0RONcKLhh?Wivz7(yzM=CyU@iHA
zf`(3(R=dm$EPZcJn<$nesG5YJtK!yPkP){ha82Txz;z%{0$cPi7f?=DXAo4gkx5|M
zmxf)v8d>p^GO#4;P(RUjr-_8_Li|lAS(&4iIJ=~A%a3r2SR03xI8l4jq{zTZ^QHcs
z7IOP~@x6$g=6}&-_PGC!5Ec>8h7UWJz8I~XfcrXw
z&1~A(y>`+4YP24C&NFe72`sKG8WR+023+23;w75auh*`)YXY8RgEjC$Um2hIf@e>n
zT?Ejc8Mq}ST4X@6l59v4LfY$&_9yv?+MPmRG&qP8+|37#SdiTdWMIprM|{$<;Fk5j
zdX^)y=bSXsC%4+5{-DJsY)^Zv9(4$9$LTfC9W}H+uAP(xyjx0fJMRxL-LSTCHf~!oCY-j~<
zmqH|XUOTx|^jXuk(P&oIvb1#+)=Vl%9c#aPEN4gvx>x9RDndM}9gzv+1-a*SEe-tX
zw?*KLan;GkA_XmNn=ATk0&{~y$P@+Oi3C3F0S{PdI7xuSFJtm-#pFx^mnxb%GXelk
zi)Vp4(vDQb)!6pLa}k(1RThy37V<>1xTUXNZLc69ddrl{N{CibMfF<`0z84h6vG-P
zPEurN<7ftsCad8Rq6ZP{AZ7=s=XjUxe>Y;hSUiHjPtmj9Al~`9+hyR)WqT3#p47GO
z9bZZVOVzQoAumaS2zfIByb^^pzpNO0eQPBt@4m4#mMIc~bCCWWN>H@j?1Irt2JJeD
zv=YW6Y6OkInY$L3Ah6`HMQ)&NxvF`@j)z9X$nV@F)(FgH<1BJNp1?&$NDpz=!HkN0
zN{lBGxRzzqU+(NfX_I{}LR#f$JX>S5PUI;WnCjpbNqZ2O11T%}SOXAkPqS}JQnfTP
zx`H9&MQoM0M&o-K0&~ka0t5mm*G4ONymm)lClSCSA+n@Y&$yjd)X`@l>Sd;u5TgkE
zWC@{3g4%aZ1A7Q8O%#tU&*Yzyfm=>z>LVocjsn^f9LqIGQKpgBlQ}h;hz}7NaTx-0
zg@~q0BQn4fEF>7S+h{ugE&U(Cq@}%Em_+*AI5S#8cpW`yALgvTy0!KmjE`b@K
zp#d7vPQrncQ$2k5+RVtl(+NzUPObxv0%I-YP`e|@m)Qp;gtXHWm2O-u;<1?(hv7wB
z0iZY_0k0KgUI~!}TqgN4Y{*AauN97
zDl}7zS^}CGv#A+4!;uUd7VxjwR@$1>-l>6VR;>oHzJo0@X4429xp`3onhC}g$*GOW
z;=iZRL;AOFqglqR_CKqL^Hi^#Gnrs$U|sQdtB&<ajMtEyeSTKBKrJ>*z&|ooIs;9u%y?D^H%4q?dz?#i~!Ci
zwj~)vlSOL5T{2$HzN*
zvx>)Q1lAlZ#fX-DY0&BkU{wXMaMtd)q{)oI1<5d_xA=?R2pvz5iY*IEhI
zvwd_|0ZA;{_z@eABQVua2naQh*7jM~YQY@+dsJIml14;|J%YfT-YtJ;0Wv~ft5g|H
zNK1bl4i+3Uu(0-L@vqf(co8R&j;m%hCnKOm$*+xVMja2BvrM={9W(ksdv>6*>^;p6H$kJ5J_DoHxIC-xv0;
while (rows--) {
var name = menuItems[idx];
var item = items[name];
@@ -82,7 +76,7 @@ E.showMenu = function(items) {
g.setColor((idxitem.max) item.value = item.max;
+ if (item.min!==undefined && item.valueitem.max) item.value = item.wrap ? item.min : item.max;
if (item.onchange) item.onchange(item.value);
l.draw(options.selected,options.selected);
} else {
var a=options.selected;
- options.selected = (dir+options.selected)%menuItems.length;
- if (options.selected<0) options.selected += menuItems.length;
+ options.selected = (dir+options.selected+menuItems.length)%menuItems.length;
l.draw(Math.min(a,options.selected), Math.max(a,options.selected));
}
}
diff --git a/apps/messages/ChangeLog b/apps/messages/ChangeLog
new file mode 100644
index 000000000..4f7df3859
--- /dev/null
+++ b/apps/messages/ChangeLog
@@ -0,0 +1,3 @@
+0.01: New App!
+0.02: Add 'messages' library
+0.03: Fixes for Bangle.js 1
diff --git a/apps/messages/README.md b/apps/messages/README.md
new file mode 100644
index 000000000..c243ec06a
--- /dev/null
+++ b/apps/messages/README.md
@@ -0,0 +1,21 @@
+# Messages app
+
+**THIS APP IS CURRENTLY BETA**
+
+This app handles the display of messages and message notifications. It stores
+a list of currently received messages and allows them to be listed, viewed,
+and responded to.
+
+It is a replacement for the old `notify`/`gadgetbridge` apps.
+
+## Usage
+
+...
+
+## Requests
+
+Please file any issues on https://github.com/espruino/BangleApps/issues/new?title=messages%20app
+
+## Creator
+
+Gordon Williams
diff --git a/apps/messages/app-icon.js b/apps/messages/app-icon.js
new file mode 100644
index 000000000..6ed3c1141
--- /dev/null
+++ b/apps/messages/app-icon.js
@@ -0,0 +1 @@
+require("heatshrink").decompress(atob("mEw4UA///rkcAYP9ohL/ABMBqoAEoALDioLFqgLDBQoABERIkEBZcFBY9QBed61QAC1oLF7wLD24LF24LD7wLF1vqBQOrvQLFA4IuC9QLFD4IuC1QLGGAQOBBYwgBEwQLHvQBBEZHVq4jI7wWBHY5TLNZaDLTZazLffMBBY9ABZsABY4KCgEVBQtUBYYkGEQYA/AAwA="))
diff --git a/apps/messages/app.js b/apps/messages/app.js
new file mode 100644
index 000000000..6c7cf5fc9
--- /dev/null
+++ b/apps/messages/app.js
@@ -0,0 +1,237 @@
+/* MESSAGES is a list of:
+ {id:int,
+ src,
+ title,
+ subject,
+ body,
+ sender,
+ tel:string,
+ new:true // not read yet
+ }
+*/
+
+/* For example for maps:
+
+// a message
+{"t":"add","id":1575479849,"src":"Hangouts","title":"A Name","body":"message contents"}
+// maps
+{"t":"add","id":1,"src":"Maps","title":"0 yd - High St","body":"Campton - 11:48 ETA","img":"GhqBAAAMAAAHgAAD8AAB/gAA/8AAf/gAP/8AH//gD/98B//Pg/4B8f8Afv+PP//n3/f5//j+f/wfn/4D5/8Aef+AD//AAf/gAD/wAAf4AAD8AAAeAAADAAA="}
+
+*/
+
+var Layout = require("Layout");
+var fontMedium = g.getFonts().includes("6x15")?"6x15":"6x8:2";
+var fontBig = g.getFonts().includes("12x20")?"12x20":"6x8:2";
+var fontLarge = g.getFonts().includes("6x15")?"6x15:2":"6x8:4";
+var colBg = g.theme.dark ? "#141":"#4f4";
+var colSBg1 = g.theme.dark ? "#121":"#cFc";
+var colSBg2 = g.theme.dark ? "#242":"#9F9";
+// hack for 2v10 firmware's lack of ':size' font handling
+try {
+ g.setFont("6x8:2");
+} catch (e) {
+ g._setFont = g.setFont;
+ g.setFont = function(f,s) {
+ if (f.includes(":")) {
+ f = f.split(":");
+ return g._setFont(f[0],f[1]);
+ }
+ return g._setFont(f,s);
+ };
+}
+
+
+var MESSAGES = require("Storage").readJSON("messages.json",1)||[];
+if (!Array.isArray(MESSAGES)) MESSAGES=[];
+var onMessagesModified = function(msg) {
+ // TODO: if new, show this new one
+ if (msg.new) Bangle.buzz();
+ showMessage(msg.id);
+};
+function saveMessages() {
+ require("Storage").writeJSON("messages.json",MESSAGES)
+}
+
+function getBackImage() {
+ return atob("FhYBAAAAEAAAwAAHAAA//wH//wf//g///BwB+DAB4EAHwAAPAAA8AADwAAPAAB4AAHgAB+AH/wA/+AD/wAH8AA==");
+}
+function getMessageImage(msg) {
+ if (msg.img) return atob(msg.img);
+ var s = (msg.src||"").toLowerCase();
+ if (s=="skype") return atob("GhoBB8AAB//AA//+Af//wH//+D///w/8D+P8Afz/DD8/j4/H4fP5/A/+f4B/n/gP5//B+fj8fj4/H8+DB/PwA/x/A/8P///B///gP//4B//8AD/+AAA+AA==");
+ if (s=="hangouts") return atob("FBaBAAH4AH/gD/8B//g//8P//H5n58Y+fGPnxj5+d+fmfj//4//8H//B//gH/4A/8AA+AAHAABgAAAA=");
+ if (s=="whatsapp") return atob("GBiBAAB+AAP/wAf/4A//8B//+D///H9//n5//nw//vw///x///5///4///8e//+EP3/APn/wPn/+/j///H//+H//8H//4H//wMB+AA==");
+ if (s=="twitter") return atob("GhYBAABgAAB+JgA/8cAf/ngH/5+B/8P8f+D///h///4f//+D///g///wD//8B//+AP//gD//wAP/8AB/+AB/+AH//AAf/AAAYAAA");
+ if (msg.id=="music") return atob("FhaBAH//+/////////////h/+AH/4Af/gB/+H3/7/f/v9/+/3/7+f/vB/w8H+Dwf4PD/x/////////////3//+A=");
+ if (msg.id=="back") return getBackImage();
+ return atob("HBKBAD///8H///iP//8cf//j4//8f5//j/x/8//j/H//H4//4PB//EYj/44HH/Hw+P4//8fH//44///xH///g////A==");
+}
+
+
+function showMapMessage(msg) {
+ var m;
+ var distance, street, target, eta;
+ m=msg.title.match(/(.*) - (.*)/);
+ if (m) {
+ distance = m[1];
+ street = m[2];
+ } else street=msg.title;
+ m=msg.body.match(/(.*) - (.*)/);
+ if (m) {
+ target = m[1];
+ eta = m[2];
+ } else target=msg.body;
+ layout = new Layout({ type:"v", c: [
+ {type:"txt", font:fontMedium, label:target, bgCol:colBg, fillx:1, pad:2 },
+ {type:"h", bgCol:colBg, fillx:1, c: [
+ {type:"txt", font:"6x8", label:"Towards" },
+ {type:"txt", font:fontLarge, label:street }
+ ]},
+ {type:"h",fillx:1, filly:1, c: [
+ msg.img?{type:"img",src:atob(msg.img), scale:2}:{},
+ {type:"v", fillx:1, c: [
+ {type:"txt", font:fontLarge, label:distance||"" }
+ ]},
+ ]},
+ {type:"txt", font:"6x8:2", label:eta }
+ ]});
+ g.clearRect(Bangle.appRect);
+ layout.render();
+ Bangle.setUI("updown",function() {
+ // any input to mark as not new and return to menu
+ msg.new = false;
+ saveMessages();
+ layout = undefined;
+ checkMessages();
+ });
+}
+
+function showMusicMessage(msg) {
+ function fmtTime(s) {
+ var m = Math.floor(s/60);
+ s = (s%60).toString().padStart(2,0);
+ return m+":"+s;
+ }
+
+ function back() {
+ msg.new = false;
+ saveMessages();
+ layout = undefined;
+ checkMessages();
+ }
+ layout = new Layout({ type:"v", c: [
+ {type:"h", fillx:1, bgCol:colBg, c: [
+ { type:"btn", src:getBackImage, cb:back },
+ { type:"v", fillx:1, c: [
+ { type:"txt", font:fontLarge, label:msg.artist, pad:2 },
+ { type:"txt", font:fontMedium, label:msg.album, pad:2 }
+ ]}
+ ]},
+ {type:"txt", font:fontLarge, label:msg.track, fillx:1, filly:1, pad:2 },
+ Bangle.musicControl?{type:"h",fillx:1, c: [
+ {type:"btn", pad:8, label:"\0"+atob("FhgBwAADwAAPwAA/wAD/gAP/gA//gD//gP//g///j///P//////////P//4//+D//gP/4A/+AD/gAP8AA/AADwAAMAAA"), cb:()=>Bangle.musicControl("play")}, // play
+ {type:"btn", pad:8, label:"\0"+atob("EhaBAHgHvwP/wP/wP/wP/wP/wP/wP/wP/wP/wP/wP/wP/wP/wP/wP/wP/wP/wP/wP/wP3gHg"), cb:()=>Bangle.musicControl("pause")}, // pause
+ {type:"btn", pad:8, label:"\0"+atob("EhKBAMAB+AB/gB/wB/8B/+B//B//x//5//5//x//B/+B/8B/wB/gB+AB8ABw"), cb:()=>Bangle.musicControl("next")}, // next
+ ]}:{},
+ {type:"txt", font:"6x8:2", label:msg.dur?fmtTime(msg.dur):"--:--" }
+ ]});
+ g.clearRect(Bangle.appRect);
+ layout.render();
+}
+
+function showMessage(msgid) {
+ var msg = MESSAGES.find(m=>m.id==msgid);
+ if (!msg) return checkMessages(); // go home if no message found
+ if (msg.src=="Maps") return showMapMessage(msg);
+ if (msg.id=="music") return showMusicMessage(msg);
+ // Normal text message display
+ var title=msg.title, titleFont = fontLarge;
+ if (title) {
+ var w = g.getWidth()-40;
+ if (g.setFont(titleFont).stringWidth(title) > w)
+ titleFont = fontMedium;
+ if (g.setFont(titleFont).stringWidth(title) > w)
+ title = g.wrapString(title, w).join("\n");
+ }
+ layout = new Layout({ type:"v", c: [
+ {type:"h", fillx:1, bgCol:colBg, c: [
+ { type:"img", src:getMessageImage(msg), pad:2 },
+ { type:"v", fillx:1, c: [
+ {type:"txt", font:fontMedium, label:msg.src||"Message", bgCol:colBg, fillx:1, pad:2 },
+ title?{type:"txt", font:titleFont, label:title, bgCol:colBg, fillx:1, pad:2 }:{},
+ ]},
+ ]},
+ {type:"txt", font:fontMedium, label:msg.body||"", wrap:true, fillx:1, filly:1, pad:2 },
+ {type:"h",fillx:1, c: [
+ {type:"btn", src:getBackImage(), cb:()=>checkMessages(true)}, // back
+ msg.new?{type:"btn", src:atob("HRiBAD///8D///wj///Fj//8bj//x3z//Hvx/8/fx/j+/x+Ad/B4AL8Rh+HxwH+PHwf+cf5/+x/n/PH/P8cf+cx5/84HwAB4fgAD5/AAD/8AAD/wAAD/AAAD8A=="), cb:()=>{
+ msg.new = false; // read mail
+ saveMessages();
+ checkMessages();
+ }}:{}
+ ]}
+ ]});
+ g.clearRect(Bangle.appRect);
+ layout.render();
+}
+
+function checkMessages(forceShowMenu) {
+ // If no messages, just show 'no messages' and return
+ if (!MESSAGES.length)
+ return E.showPrompt("No Messages",{
+ title:"Messages",
+ img:require("heatshrink").decompress(atob("kkk4UBrkc/4AC/tEqtACQkBqtUDg0VqAIGgoZFDYQIIM1sD1QAD4AIBhnqA4WrmAIBhc6BAWs8AIBhXOBAWz0AIC2YIC5wID1gkB1c6BAYFBEQPqBAYXBEQOqBAnDAIQaEnkAngaEEAPDFgo+IKA5iIOhCGIAFb7RqAIGgtUBA0VqobFgNVA")),
+ buttons : {"Ok":1}
+ }).then(() => { load() });
+ // we have >0 messages
+ // If we have a new message, show it
+ if (!forceShowMenu) {
+ var newMessages = MESSAGES.filter(m=>m.new);
+ if (newMessages.length)
+ return showMessage(newMessages[0].id);
+ }
+ // Otherwise show a menu
+ E.showScroller({
+ h : 48,
+ c : MESSAGES.length+1,
+ draw : function(idx, r) {"ram"
+ var msg = MESSAGES[idx-1];
+ if (msg && msg.new) g.setBgColor(colBg);
+ else g.setBgColor((idx&1) ? colSBg1 : colSBg2);
+ g.clearRect(r.x,r.y,r.x+r.w-1,r.y+r.h-1).setColor(g.theme.fg);
+ if (idx==0) msg = {id:"back", title:"< Back"};
+ if (!msg) return;
+ var x = r.x+2, title = msg.title, body = msg.body;
+ var img = getMessageImage(msg);
+ if (msg.id=="music") {
+ title = msg.artist || "Music";
+ body = msg.track;
+ }
+ if (img) {
+ g.drawImage(img, x+24, r.y+24, {rotate:0}); // force centering
+ x += 50;
+ }
+ var m = msg.title+"\n"+msg.body;
+ if (msg.src) g.setFontAlign(1,-1).setFont("6x8").drawString(msg.src, r.x+r.w-2, r.y+2);
+ if (title) g.setFontAlign(-1,-1).setFont(fontBig).drawString(title, x,r.y+2);
+ if (body) {
+ g.setFontAlign(-1,-1).setFont("6x8");
+ var l = g.wrapString(body, r.w-14);
+ if (l.length>3) {
+ l = l.slice(0,3);
+ l[l.length-1]+="...";
+ }
+ g.drawString(l.join("\n"), x+10,r.y+20);
+ }
+ },
+ select : idx => {
+ if (idx==0) load();
+ else showMessage(MESSAGES[idx-1].id);
+ }
+ });
+}
+
+g.clear();
+Bangle.loadWidgets();
+Bangle.drawWidgets();
+checkMessages();
diff --git a/apps/messages/app.png b/apps/messages/app.png
new file mode 100644
index 0000000000000000000000000000000000000000..c9177692e282e1247ced30f6ec0e2d14dc6dfa25
GIT binary patch
literal 917
zcmV;G18V$
CmK~!jg?U~I_6G0fppJ|s5%ZG(_jU{sLoS)M|Tx5HNwbU93{dM|XETG(}U{r87GP
zP5QgOGds^S`_B9Bv_O#}MT#6JB;SE&AFiJ#PVFW@+5j{Fs1U4W3&1i!=cz7@tv)#Y
zDW6G)8t?@prO7h)FeSJHz+qQqp6HZfw0bu&7zz6JtOi;d@C75Ko8|7;0Ims@mp=#J3E*{IZSCaRo7jz0My7S0nqA
z?9Rp%4b8HI>M{r3e%-^}7vKLHd-Y5ye(oBGDVaVJ^4o8QwhZKo5Ba?~SxzwZA%&Tb
z+lVS@06>def}RU5^jtiFA3Jn^jtCRn26CIwMDK4Qfz}EHS`WVSdt3w|zjyyIcTdI<
z_Iq%ulJDxlW!-Ky5!DO<4g;b}p(qo~D|bzZD}}iwxHqISKZAMo>{p|R3Ib$Ig#6z9
zuUuBR4)M~4hRY-CJX3}9-`~ir3~U~mio^M77O*m~S^yz@5V~R(vM@mB3ZaDu3db9>
zn1uo77y$nJqBwM-ljmkZQv)kQbrDK2S{O|%(5EZ+>pq)BEvr!VZekF?f^bdwGcVVy
z-?JKEX&@5x?N#k0IslB|Xwyjp=o7hSt>fM8D`~5NdH=;!|7gueKnEx>+CfPJ#Q*e|
r1fk1>I_9WBo>`?$ks?Kk{5$*tT^fbQe@cvs00000NkvXXu0mjfYw(!I
literal 0
HcmV?d00001
diff --git a/apps/messages/lib.js b/apps/messages/lib.js
new file mode 100644
index 000000000..f3ea242e5
--- /dev/null
+++ b/apps/messages/lib.js
@@ -0,0 +1,37 @@
+exports.pushMessage = function(event) {
+ /* event is:
+ {t:"add",id:int, src,title,subject,body,sender,tel, important:bool} // add new
+ {t:"add",id:int, id:"music", state, artist, track, etc} // add new
+ {t:"remove-",id:int} // remove
+ {t:"modify",id:int, title:string} // modified
+ */
+ var messages, inApp = "undefined"!=typeof MESSAGES;
+ if (inApp)
+ messages = MESSAGES; // we're in an app that has already loaded messages
+ else // no app - load messages
+ messages = require("Storage").readJSON("messages.json",1)||[];
+ // now modify/delete as appropriate
+ var mIdx = messages.findIndex(m=>m.id==event.id);
+ if (event.t=="remove") {
+ if (mIdx>=0) messages.splice(mIdx, 1); // remove item
+ mIdx=-1;
+ } else { // add/modify
+ if (event.t=="add") event.new=true; // new message
+ if (mIdx<0) mIdx=messages.push(event)-1;
+ else Object.assign(messages[mIdx], event);
+ }
+ require("Storage").writeJSON("messages.json",messages);
+ // if in app, process immediately
+ if (inApp) return onMessagesModified(mIdx<0 ? {id:event.id} : messages[mIdx]);
+ // ok, saved now - we only care if it's new
+ if (event.t!="add") return;
+ // otherwise load after a delay, to ensure we have all the messages
+ if (exports.messageTimeout) clearTimeout(exports.messageTimeout);
+ exports.messageTimeout = setTimeout(function() {
+ exports.messageTimeout = undefined;
+ // if we're in a clock or it's important, go straight to messages app
+ if (Bangle.CLOCK || event.important) return load("messages.app.js");
+ if (!global.WIDGETS || !WIDGETS.messages) return Bangle.buzz(); // no widgets - just buzz to let someone know
+ WIDGETS.messages.newMessage();
+ }, 500);
+}
diff --git a/apps/messages/widget.js b/apps/messages/widget.js
new file mode 100644
index 000000000..eda4a85a5
--- /dev/null
+++ b/apps/messages/widget.js
@@ -0,0 +1,20 @@
+WIDGETS["messages"]={area:"tl",width:0,draw:function() {
+ if (!this.width) return;
+ var c = (Date.now()-this.t)/1000;
+ g.reset().setBgColor((c&1) ? "#0f0" : "#030").setColor((c&1) ? "#000" : "#fff");
+ g.clearRect(this.x,this.y,this.x+this.width,this.y+23);
+ g.setFont("6x8:1x2").setFontAlign(0,0).drawString("MESSAGES", this.x+this.width/2, this.y+12);
+ //if (c<60) Bangle.setLCDPower(1); // keep LCD on for 1 minute
+ if (c<120 && (Date.now()-this.l)>4000) {
+ this.l = Date.now();
+ Bangle.buzz(); // buzz every 4 seconds
+ }
+ setTimeout(()=>WIDGETS["messages"].draw(), 1000);
+},newMessage:function() {
+ WIDGETS["messages"].t=Date.now(); // first time
+ WIDGETS["messages"].l=Date.now()-10000; // last buzz
+ if (WIDGETS["messages"].c!==undefined) return; // already called
+ WIDGETS["messages"].width=64;
+ Bangle.drawWidgets();
+ Bangle.setLCDPower(1);// turns screen on
+}};
diff --git a/apps/multiclock/ChangeLog b/apps/multiclock/ChangeLog
index 2f27f7f28..9d02ae85e 100644
--- a/apps/multiclock/ChangeLog
+++ b/apps/multiclock/ChangeLog
@@ -1,13 +1,11 @@
-0.01: New App!
-0.02: Separate *.face.js files for faces
-0.03: Renaming
-0.04: Bug Fixes
-0.05: Add README
-0.06: Add txt clock
-0.07: Add Time Date clock and fix font sizes
-0.08: Add pinned clock face
-0.09: Added Pedometer clock
-0.10: Added GPS and Grid Ref clock faces
-0.11: Updated Pedometer clock to retrieve steps from either wpedom or activepedom
-0.12: Removed GPS and Grid Ref clock faces, superceded by GPS setup and Walkers Clock
-0.13: Localised digi.js and timdat.js
\ No newline at end of file
+0.01: Initial version
+0.02: Add pinned clock facility
+0.03: Lnng touch switch to night clock - ANCS off, dimmed
+0.04: use theme, font heights etc
+0.05: make Bangle compatible
+0.06: add minute tick for efficiency and nifty A clock
+0.07: compatible with Bang;e.js 2
+0.08: fix minute tick bug
+
+
+
diff --git a/apps/multiclock/README.md b/apps/multiclock/README.md
index b1773b8df..e8b8335ea 100644
--- a/apps/multiclock/README.md
+++ b/apps/multiclock/README.md
@@ -1,30 +1,11 @@
# Multiclock
-This is a clock app that supports multiple clock faces. The user can switch between faces while retaining widget state which makes the switch fast and preserves state such as bluetooth connections. Currently there are four clock faces as shown below. To my eye, these faces look better when widgets are hidden using **widviz**.
-
+This is a clock app that supports multiple clock faces. The user can switch between faces while retaining widget state which makes the switch fast. Currently there are four clock faces as shown below. There are currently an anlog, digital, text, big digit, time and date, and a clone of the Nifty-A-Clock faces.
### Analog Clock Face
-
-
-### Digital Clock Face
-
-
-### Big Digit Clock Face
-
-
-### Text Clock Face
-
-
-### Time and Date Clock Face
## Controls
-Clock faces are kept in a circular list.
-
-*BTN1* - switches to the next clock face.
-
-*BTN2* - switches to the app launcher.
-
-*BTN3* - switches to the previous clock face.
+Swipe left and right on both the Bangle and Bangle 2 switch between faces. BTN1 & BTH3 also switch faces on the Bangle.
## Adding a new face
Clock faces are described in javascript storage files named `name.face.js`. For example, the Analog Clock Face is described in `ana.face.js`. These files have the following structure:
@@ -38,7 +19,7 @@ Clock faces are described in javascript storage files named `name.face.js`. For
function drawAll(){
//draw background + initial state of digits, hands etc
}
- return {init:drawAll, tick:onSecond};
+ return {init:drawAll, tick:onSecond, tickpersec:true};
}
return getFace;
})();
@@ -47,6 +28,5 @@ For those familiar with the structure of widgets, this is similar, however, ther
The app at start up loads all files `*.face.js`. The simplest way of adding a face is thus to load it into `Storage` using the WebIDE. Similarly, to remove an unwanted face, simply delete it from `Storage` using the WebIDE.
-## Support
+If `tickpersec` is false then `tick` is only called each minute as this is more power effcient - especially on the BAngle 2.
-Please report bugs etc. by raising an issue [here](https://github.com/jeffmer/JeffsBangleAppsDev).
\ No newline at end of file
diff --git a/apps/multiclock/ana.js b/apps/multiclock/ana.face.js
similarity index 59%
rename from apps/multiclock/ana.js
rename to apps/multiclock/ana.face.js
index 4fd5a7251..af1c84c9f 100644
--- a/apps/multiclock/ana.js
+++ b/apps/multiclock/ana.face.js
@@ -5,54 +5,60 @@
const p = Math.PI/2;
const PRad = Math.PI/180;
+ var cx = g.getWidth()/2;
+ var cy = 12+g.getHeight()/2;
+ var scale = (g.getHeight()-24)/(240-24);
+ scale = scale>=1 ? 1 : scale;
+
function seconds(angle, r) {
const a = angle*PRad;
- const x = 120+Math.sin(a)*r;
- const y = 134-Math.cos(a)*r;
+ const x = cx+Math.sin(a)*r;
+ const y = cy-Math.cos(a)*r;
if (angle % 90 == 0) {
- g.setColor(0,1,1);
+ g.setColor(g.theme.fg2);
g.fillRect(x-6,y-6,x+6,y+6);
} else if (angle % 30 == 0){
- g.setColor(0,1,1);
+ g.setColor(g.theme.fg);
g.fillRect(x-4,y-4,x+4,y+4);
} else {
- g.setColor(1,1,1);
+ g.setColor(g.theme.fg);
g.fillRect(x-1,y-1,x+1,y+1);
}
}
function hand(angle, r1,r2, r3) {
+ r1 = scale*r1; r2=scale*r2; r3 = scale*r3;
const a = angle*PRad;
g.fillPoly([
- 120+Math.sin(a)*r1,
- 134-Math.cos(a)*r1,
- 120+Math.sin(a+p)*r3,
- 134-Math.cos(a+p)*r3,
- 120+Math.sin(a)*r2,
- 134-Math.cos(a)*r2,
- 120+Math.sin(a-p)*r3,
- 134-Math.cos(a-p)*r3]);
+ cx+Math.sin(a)*r1,
+ cy-Math.cos(a)*r1,
+ cx+Math.sin(a+p)*r3,
+ cy-Math.cos(a+p)*r3,
+ cx+Math.sin(a)*r2,
+ cy-Math.cos(a)*r2,
+ cx+Math.sin(a-p)*r3,
+ cy-Math.cos(a-p)*r3]);
}
var minuteDate;
var secondDate;
function onSecond() {
- g.setColor(0,0,0);
+ g.setColor(g.theme.bg);
hand(360*secondDate.getSeconds()/60, -5, 90, 3);
if (secondDate.getSeconds() === 0) {
hand(360*(minuteDate.getHours() + (minuteDate.getMinutes()/60))/12, -16, 60, 7);
hand(360*minuteDate.getMinutes()/60, -16, 86, 7);
minuteDate = new Date();
}
- g.setColor(1,1,1);
+ g.setColor(g.theme.fg);
hand(360*(minuteDate.getHours() + (minuteDate.getMinutes()/60))/12, -16, 60, 7);
hand(360*minuteDate.getMinutes()/60, -16, 86, 7);
- g.setColor(0,1,1);
+ g.setColor(g.theme.fg2);
secondDate = new Date();
hand(360*secondDate.getSeconds()/60, -5, 90, 3);
- g.setColor(0,0,0);
- g.fillCircle(120,134,2);
+ g.setColor(g.theme.bg);
+ g.fillCircle(cx,cy,2);
}
function drawAll() {
@@ -60,11 +66,11 @@
// draw seconds
g.setColor(1,1,1);
for (let i=0;i<60;i++)
- seconds(360*i/60, 100);
+ seconds(360*i/60, 100*scale);
onSecond();
}
- return {init:drawAll, tick:onSecond};
+ return {init:drawAll, tick:onSecond, tickpersec:true};
}
return getFace;
diff --git a/apps/multiclock/anaface.jpg b/apps/multiclock/anaface.jpg
deleted file mode 100644
index 86aaccd5496cbd435baa8bc522eec38b71e768ea..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001
literal 44568
zcmb@uby!u+7eBfW4I-Tq2TAFULnG1x(nv~)ba#WKG?F5ql!$QXknWI>mTr*luDkJ#
z?|bj_`{O?MxpUZiK6}lYHEXSzSu
zC@KO>005u?7!Yg#0YoU^5dy*agV8}83Bdyp!5jj90LUW%`40wwBna8RI0N$JZyrzr
zg!4~bRFE$Z!u1CefVeo+0zmq^Zfh9bKNyu7qyZB4_SR;A#S0@-suzxS_mq1r?_2yU
z=j3MNX5$0^E)FgZK@J{44j2_Dk039nAU6j9PzR>{qY1*Rz>ojpH(}KO(<+SNZ`=(4
zu>f$d9TEo@H)ky3KN=#~$0GfU-^Tvs0gw+NB^LJ|ix9G6@&3hM;vj#?fgm6P@elyx
zFO3mO;t~GUp(Y;jUwU&q(!aPn9`$d1K!3)g|Bb0YjPY;XpYa5L=%CjSe#Lv=w}p5K
z;)VFQKRO`6IQW40`2XRof9&Gq;K)Mw$HNfuEX04Y-e=;!+X8zk`!D_PM+K0B@HhP)
zBmM^?|Bay_9pyiCu0MK$x}i+vaR1Q(962bLIXr*xuJ$-ajLY
z%0gKUECa_nKFD!Ds_*$8fSTU-FB^!@LCZi4ya9bo4|?k#9tddVKiD6f8~