From d50ba356d68ebeffd028a924450d4a6ad4fddd74 Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Thu, 16 Sep 2021 18:36:59 -0700 Subject: [PATCH 001/155] Update apps.json --- apps.json | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/apps.json b/apps.json index aad5a07be..004226c86 100644 --- a/apps.json +++ b/apps.json @@ -3501,5 +3501,19 @@ "storage": [ {"name":"widclkbttm.wid.js","url":"widclkbttm.wid.js"} ] - } + }, + { "id": "schoolCalender", + "name": "School Calendar", + "shortName":"SCalender", + "icon": "CalenderLogo.png", + "version":"0.01", + "description": "A simple calendar that you can see your upcoming events. Keep in note that your events reapeat weekly.", + "tags": "tool", + "readme": "README.md", + "custom":"interface.html", + "data": [ + {"name":"app.json"} + ] +} + ] From 135165c983beca48190d091e355436100beee41d Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Thu, 16 Sep 2021 18:37:52 -0700 Subject: [PATCH 002/155] Add files via upload --- apps/schoolCalender/CalenderLogo.png | Bin 0 -> 1162 bytes apps/schoolCalender/ChangeLog.md | 2 + apps/schoolCalender/README.md | 9 + apps/schoolCalender/app-icon.js | 1 + apps/schoolCalender/comments.js | 25 + .../fullcalendar/interaction/LICENSE.txt | 22 + .../fullcalendar/interaction/README.md | 8 + .../fullcalendar/interaction/package.json | 32 + .../interaction/src/ElementScrollGeomCache.ts | 16 + .../interaction/src/OffsetTracker.ts | 76 + .../interaction/src/ScrollGeomCache.ts | 107 + .../interaction/src/WindowScrollGeomCache.ts | 27 + .../interaction/src/api-type-deps.ts | 6 + .../interaction/src/dnd/AutoScroller.ts | 217 + .../interaction/src/dnd/ElementMirror.ts | 146 + .../src/dnd/FeaturefulElementDragging.ts | 213 + .../interaction/src/dnd/PointerDragging.ts | 344 + .../ExternalDraggable.ts | 70 + .../ExternalElementDragging.ts | 268 + .../InferredElementDragging.ts | 77 + .../ThirdPartyDraggable.ts | 52 + .../src/interactions/DateClicking.ts | 71 + .../src/interactions/DateSelecting.ts | 152 + .../src/interactions/EventDragging.ts | 482 + .../src/interactions/EventResizing.ts | 263 + .../src/interactions/HitDragging.ts | 220 + .../src/interactions/UnselectAuto.ts | 77 + .../interaction/src/main.global.ts | 7 + .../fullcalendar/interaction/src/main.ts | 23 + .../interaction/src/options-declare.ts | 9 + .../fullcalendar/interaction/src/options.ts | 26 + .../fullcalendar/interaction/src/utils.ts | 38 + .../fullcalendar/interaction/tsconfig.json | 13 + .../fullcalendar/locales-all.js | 1622 ++ apps/schoolCalender/fullcalendar/main.css | 1446 ++ apps/schoolCalender/fullcalendar/main.js | 14738 ++++++++++++++++ apps/schoolCalender/interface.html | 60 + apps/schoolCalender/schoolCalender.js | 229 + 38 files changed, 21194 insertions(+) create mode 100644 apps/schoolCalender/CalenderLogo.png create mode 100644 apps/schoolCalender/ChangeLog.md create mode 100644 apps/schoolCalender/README.md create mode 100644 apps/schoolCalender/app-icon.js create mode 100644 apps/schoolCalender/comments.js create mode 100644 apps/schoolCalender/fullcalendar/interaction/LICENSE.txt create mode 100644 apps/schoolCalender/fullcalendar/interaction/README.md create mode 100644 apps/schoolCalender/fullcalendar/interaction/package.json create mode 100644 apps/schoolCalender/fullcalendar/interaction/src/ElementScrollGeomCache.ts create mode 100644 apps/schoolCalender/fullcalendar/interaction/src/OffsetTracker.ts create mode 100644 apps/schoolCalender/fullcalendar/interaction/src/ScrollGeomCache.ts create mode 100644 apps/schoolCalender/fullcalendar/interaction/src/WindowScrollGeomCache.ts create mode 100644 apps/schoolCalender/fullcalendar/interaction/src/api-type-deps.ts create mode 100644 apps/schoolCalender/fullcalendar/interaction/src/dnd/AutoScroller.ts create mode 100644 apps/schoolCalender/fullcalendar/interaction/src/dnd/ElementMirror.ts create mode 100644 apps/schoolCalender/fullcalendar/interaction/src/dnd/FeaturefulElementDragging.ts create mode 100644 apps/schoolCalender/fullcalendar/interaction/src/dnd/PointerDragging.ts create mode 100644 apps/schoolCalender/fullcalendar/interaction/src/interactions-external/ExternalDraggable.ts create mode 100644 apps/schoolCalender/fullcalendar/interaction/src/interactions-external/ExternalElementDragging.ts create mode 100644 apps/schoolCalender/fullcalendar/interaction/src/interactions-external/InferredElementDragging.ts create mode 100644 apps/schoolCalender/fullcalendar/interaction/src/interactions-external/ThirdPartyDraggable.ts create mode 100644 apps/schoolCalender/fullcalendar/interaction/src/interactions/DateClicking.ts create mode 100644 apps/schoolCalender/fullcalendar/interaction/src/interactions/DateSelecting.ts create mode 100644 apps/schoolCalender/fullcalendar/interaction/src/interactions/EventDragging.ts create mode 100644 apps/schoolCalender/fullcalendar/interaction/src/interactions/EventResizing.ts create mode 100644 apps/schoolCalender/fullcalendar/interaction/src/interactions/HitDragging.ts create mode 100644 apps/schoolCalender/fullcalendar/interaction/src/interactions/UnselectAuto.ts create mode 100644 apps/schoolCalender/fullcalendar/interaction/src/main.global.ts create mode 100644 apps/schoolCalender/fullcalendar/interaction/src/main.ts create mode 100644 apps/schoolCalender/fullcalendar/interaction/src/options-declare.ts create mode 100644 apps/schoolCalender/fullcalendar/interaction/src/options.ts create mode 100644 apps/schoolCalender/fullcalendar/interaction/src/utils.ts create mode 100644 apps/schoolCalender/fullcalendar/interaction/tsconfig.json create mode 100644 apps/schoolCalender/fullcalendar/locales-all.js create mode 100644 apps/schoolCalender/fullcalendar/main.css create mode 100644 apps/schoolCalender/fullcalendar/main.js create mode 100644 apps/schoolCalender/interface.html create mode 100644 apps/schoolCalender/schoolCalender.js diff --git a/apps/schoolCalender/CalenderLogo.png b/apps/schoolCalender/CalenderLogo.png new file mode 100644 index 0000000000000000000000000000000000000000..49d198a15b0feb1c4d175282c6e1c1112df8b06c GIT binary patch literal 1162 zcmV;51anZw{&TSgFQ0s2e#fgMU}6Rl(1Cxt0iNJP?%_Xw&WO?Wwfph4b@stgo+Qa&i(UCnxF= z=^f0^&*Sj$5W~a6ihNb)l-ma_oI-sNn#AfHKVDn)1KFU|8k1(FzFl78?CeY#U}0f_ zt*$IhHpGmKH2oliK)jN`FZi~|&(6*wpU>k`D$U`+_o|PNE}>0tfFZyFtu-8(t`Mk4 zRVadHfw%A8(ZHa13UYil?-IKa`-5p2n{%CWPgF;s%vHCvW7)h_h`u0X2E*gnkAhjL7?#g!$$ zm&@qu>r+YlRVJ;lbwApL8doz}bu2NPJ9T+Lj;K2XsklI%Bg*<07Z*^WVtNPHd5(J{ zi)?T27ixenz{XvLjL)@vy)h_U=P@O?KINEKJM=B{v^Vc zQg7yIFotKK#7GlDQ8PnhosU!%@z7{B``hhynA5}plQ?{oFs0Ou4@N-|w= schedule[i].startingTimeHour && currentMinute >= schedule[i].startingTimeMinute){ + if(currentHour <= schedule[i].endingTimeHour){ + console.log("Time of Day "+schedule[i].description); + g.drawString(i + ": "+schedule[i].description, 10, 10*i-100); + } + + //console.log("DayOfWeek:"+currentDayOfWeek+", Hour:"+ currentHour + ", Minute:" + currentMinute); + //console.log("DayOfWeek:"+schedule[i].dayOfWeek+", StartHour:"+ schedule[i].startingTimeHour +", EndHour:" + schedule[i].endingTimeHour + ", StartMinute:" + schedule[i].startingTimeMinute + ", EndMinute:" + schedule[i].endingTimeMinute); + + + + + + + + + + + + + + + + */ diff --git a/apps/schoolCalender/fullcalendar/interaction/LICENSE.txt b/apps/schoolCalender/fullcalendar/interaction/LICENSE.txt new file mode 100644 index 000000000..84924a980 --- /dev/null +++ b/apps/schoolCalender/fullcalendar/interaction/LICENSE.txt @@ -0,0 +1,22 @@ +MIT License + +Copyright (c) 2021 Adam Shaw + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/apps/schoolCalender/fullcalendar/interaction/README.md b/apps/schoolCalender/fullcalendar/interaction/README.md new file mode 100644 index 000000000..6d5821d71 --- /dev/null +++ b/apps/schoolCalender/fullcalendar/interaction/README.md @@ -0,0 +1,8 @@ + +# FullCalendar Interaction Plugin + +Provides functionality for event drag-n-drop, resizing, dateClick, and selectable actions + +[View the docs »](https://fullcalendar.io/docs/editable) + +This package was created from the [FullCalendar monorepo »](https://github.com/fullcalendar/fullcalendar) diff --git a/apps/schoolCalender/fullcalendar/interaction/package.json b/apps/schoolCalender/fullcalendar/interaction/package.json new file mode 100644 index 000000000..d382635a8 --- /dev/null +++ b/apps/schoolCalender/fullcalendar/interaction/package.json @@ -0,0 +1,32 @@ +{ + "name": "@fullcalendar/interaction", + "version": "5.9.0", + "title": "FullCalendar Interaction Plugin", + "description": "Provides functionality for event drag-n-drop, resizing, dateClick, and selectable actions", + "docs": "https://fullcalendar.io/docs/editable", + "dependencies": { + "@fullcalendar/common": "workspace:~5.9.0", + "tslib": "^2.1.0" + }, + "main": "main.cjs.js", + "module": "main.js", + "types": "main.d.ts", + "jsdelivr": "main.global.min.js", + "browserGlobal": "FullCalendarInteraction", + "homepage": "https://fullcalendar.io/", + "bugs": "https://fullcalendar.io/reporting-bugs", + "repository": { + "type": "git", + "url": "https://github.com/fullcalendar/fullcalendar.git", + "homepage": "https://github.com/fullcalendar/fullcalendar" + }, + "license": "MIT", + "author": { + "name": "Adam Shaw", + "email": "arshaw@arshaw.com", + "url": "http://arshaw.com/" + }, + "devDependencies": { + "@fullcalendar/core-preact": "workspace:*" + } +} diff --git a/apps/schoolCalender/fullcalendar/interaction/src/ElementScrollGeomCache.ts b/apps/schoolCalender/fullcalendar/interaction/src/ElementScrollGeomCache.ts new file mode 100644 index 000000000..83be540cd --- /dev/null +++ b/apps/schoolCalender/fullcalendar/interaction/src/ElementScrollGeomCache.ts @@ -0,0 +1,16 @@ +import { computeInnerRect, ElementScrollController } from '@fullcalendar/common' +import { ScrollGeomCache } from './ScrollGeomCache' + +export class ElementScrollGeomCache extends ScrollGeomCache { + constructor(el: HTMLElement, doesListening: boolean) { + super(new ElementScrollController(el), doesListening) + } + + getEventTarget(): EventTarget { + return (this.scrollController as ElementScrollController).el + } + + computeClientRect() { + return computeInnerRect((this.scrollController as ElementScrollController).el) + } +} diff --git a/apps/schoolCalender/fullcalendar/interaction/src/OffsetTracker.ts b/apps/schoolCalender/fullcalendar/interaction/src/OffsetTracker.ts new file mode 100644 index 000000000..b1fac23e6 --- /dev/null +++ b/apps/schoolCalender/fullcalendar/interaction/src/OffsetTracker.ts @@ -0,0 +1,76 @@ +import { + getClippingParents, computeRect, + pointInsideRect, Rect, +} from '@fullcalendar/common' +import { ElementScrollGeomCache } from './ElementScrollGeomCache' + +/* +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. +*/ +export class OffsetTracker { // ElementOffsetTracker + scrollCaches: ElementScrollGeomCache[] + origRect: Rect + + constructor(el: HTMLElement) { + this.origRect = computeRect(el) + + // will work fine for divs that have overflow:hidden + this.scrollCaches = getClippingParents(el).map( + (scrollEl) => new ElementScrollGeomCache(scrollEl, true), // listen=true + ) + } + + destroy() { + for (let scrollCache of this.scrollCaches) { + scrollCache.destroy() + } + } + + computeLeft() { + let left = this.origRect.left + + for (let scrollCache of this.scrollCaches) { + left += scrollCache.origScrollLeft - scrollCache.getScrollLeft() + } + + return left + } + + computeTop() { + let top = this.origRect.top + + for (let scrollCache of this.scrollCaches) { + top += scrollCache.origScrollTop - scrollCache.getScrollTop() + } + + return top + } + + isWithinClipping(pageX: number, pageY: number): boolean { + let point = { left: pageX, top: pageY } + + for (let scrollCache of this.scrollCaches) { + if ( + !isIgnoredClipping(scrollCache.getEventTarget()) && + !pointInsideRect(point, scrollCache.clientRect) + ) { + return false + } + } + + return true + } +} + +// certain clipping containers should never constrain interactions, like and +// https://github.com/fullcalendar/fullcalendar/issues/3615 +function isIgnoredClipping(node: EventTarget) { + let tagName = (node as HTMLElement).tagName + + return tagName === 'HTML' || tagName === 'BODY' +} diff --git a/apps/schoolCalender/fullcalendar/interaction/src/ScrollGeomCache.ts b/apps/schoolCalender/fullcalendar/interaction/src/ScrollGeomCache.ts new file mode 100644 index 000000000..63ec6a5e1 --- /dev/null +++ b/apps/schoolCalender/fullcalendar/interaction/src/ScrollGeomCache.ts @@ -0,0 +1,107 @@ +import { Rect, ScrollController } from '@fullcalendar/common' + +/* +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 +*/ +export abstract class ScrollGeomCache extends ScrollController { + clientRect: Rect + origScrollTop: number + origScrollLeft: number + + protected scrollController: ScrollController + protected doesListening: boolean + protected scrollTop: number + protected scrollLeft: number + protected scrollWidth: number + protected scrollHeight: number + protected clientWidth: number + protected clientHeight: number + + constructor(scrollController: ScrollController, doesListening: boolean) { + super() + 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) + } + } + + abstract getEventTarget(): EventTarget + abstract computeClientRect(): Rect + + destroy() { + if (this.doesListening) { + this.getEventTarget().removeEventListener('scroll', this.handleScroll) + } + } + + handleScroll = () => { + this.scrollTop = this.scrollController.getScrollTop() + this.scrollLeft = this.scrollController.getScrollLeft() + this.handleScrollChange() + } + + getScrollTop() { + return this.scrollTop + } + + getScrollLeft() { + return this.scrollLeft + } + + setScrollTop(top: number) { + 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() + } + } + + setScrollLeft(top: number) { + 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() + } + } + + getClientWidth() { + return this.clientWidth + } + + getClientHeight() { + return this.clientHeight + } + + getScrollWidth() { + return this.scrollWidth + } + + getScrollHeight() { + return this.scrollHeight + } + + handleScrollChange() { + } +} diff --git a/apps/schoolCalender/fullcalendar/interaction/src/WindowScrollGeomCache.ts b/apps/schoolCalender/fullcalendar/interaction/src/WindowScrollGeomCache.ts new file mode 100644 index 000000000..ca65dee6e --- /dev/null +++ b/apps/schoolCalender/fullcalendar/interaction/src/WindowScrollGeomCache.ts @@ -0,0 +1,27 @@ +import { Rect, WindowScrollController } from '@fullcalendar/common' +import { ScrollGeomCache } from './ScrollGeomCache' + +export class WindowScrollGeomCache extends ScrollGeomCache { + constructor(doesListening: boolean) { + super(new WindowScrollController(), doesListening) + } + + getEventTarget(): EventTarget { + return window + } + + computeClientRect(): Rect { + 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 + handleScrollChange() { + this.clientRect = this.computeClientRect() + } +} diff --git a/apps/schoolCalender/fullcalendar/interaction/src/api-type-deps.ts b/apps/schoolCalender/fullcalendar/interaction/src/api-type-deps.ts new file mode 100644 index 000000000..2b1b51b06 --- /dev/null +++ b/apps/schoolCalender/fullcalendar/interaction/src/api-type-deps.ts @@ -0,0 +1,6 @@ +// TODO: rename file to public-types.ts + +export { DateClickArg } from './interactions/DateClicking' +export { EventDragStartArg, EventDragStopArg } from './interactions/EventDragging' +export { EventResizeStartArg, EventResizeStopArg, EventResizeDoneArg } from './interactions/EventResizing' +export { DropArg, EventReceiveArg, EventLeaveArg } from './utils' diff --git a/apps/schoolCalender/fullcalendar/interaction/src/dnd/AutoScroller.ts b/apps/schoolCalender/fullcalendar/interaction/src/dnd/AutoScroller.ts new file mode 100644 index 000000000..8d4e8f015 --- /dev/null +++ b/apps/schoolCalender/fullcalendar/interaction/src/dnd/AutoScroller.ts @@ -0,0 +1,217 @@ +import { getElRoot } from '@fullcalendar/common' +import { ScrollGeomCache } from '../ScrollGeomCache' +import { ElementScrollGeomCache } from '../ElementScrollGeomCache' +import { WindowScrollGeomCache } from '../WindowScrollGeomCache' + +interface Edge { + scrollCache: ScrollGeomCache + name: 'top' | 'left' | 'right' | 'bottom' + distance: number // how many pixels the current pointer is from the edge +} + +// 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 +const getTime = typeof performance === 'function' ? (performance as any).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. +*/ +export class AutoScroller { + // options that can be set by caller + isEnabled: boolean = true + scrollQuery: (Window | string)[] = [window, '.fc-scroller'] + edgeThreshold: number = 50 // pixels + maxVelocity: number = 300 // pixels per second + + // internal state + pointerScreenX: number | null = null + pointerScreenY: number | null = null + isAnimating: boolean = false + scrollCaches: ScrollGeomCache[] | null = null + msSinceRequest?: number + + // protect against the initial pointerdown being too close to an edge and starting the scroll + everMovedUp: boolean = false + everMovedDown: boolean = false + everMovedLeft: boolean = false + everMovedRight: boolean = false + + start(pageX: number, pageY: number, scrollStartEl: HTMLElement) { + 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) + } + } + + handleMove(pageX: number, pageY: number) { + if (this.isEnabled) { + let pointerScreenX = pageX - window.pageXOffset + let pointerScreenY = pageY - window.pageYOffset + + let yDelta = this.pointerScreenY === null ? 0 : pointerScreenY - this.pointerScreenY + let 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()) + } + } + } + + stop() { + if (this.isEnabled) { + this.isAnimating = false // will stop animation + + for (let scrollCache of this.scrollCaches!) { + scrollCache.destroy() + } + + this.scrollCaches = null + } + } + + requestAnimation(now: number) { + this.msSinceRequest = now + requestAnimationFrame(this.animate) + } + + private animate = () => { + if (this.isAnimating) { // wasn't cancelled between animation calls + let edge = this.computeBestEdge( + this.pointerScreenX! + window.pageXOffset, + this.pointerScreenY! + window.pageYOffset, + ) + + if (edge) { + let now = getTime() + this.handleSide(edge, (now - this.msSinceRequest!) / 1000) + this.requestAnimation(now) + } else { + this.isAnimating = false // will stop animation + } + } + } + + private handleSide(edge: Edge, seconds: number) { + let { scrollCache } = edge + let { edgeThreshold } = this + let invDistance = edgeThreshold - edge.distance + let velocity = // the closer to the edge, the faster we scroll + ((invDistance * invDistance) / (edgeThreshold * edgeThreshold)) * // quadratic + this.maxVelocity * seconds + let 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 + private computeBestEdge(left: number, top: number): Edge | null { + let { edgeThreshold } = this + let bestSide: Edge | null = null + + for (let scrollCache of this.scrollCaches!) { + let rect = scrollCache.clientRect + let leftDist = left - rect.left + let rightDist = rect.right - left + let topDist = top - rect.top + let 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, name: 'top', distance: topDist } + } + + if ( + bottomDist <= edgeThreshold && this.everMovedDown && scrollCache.canScrollDown() && + (!bestSide || bestSide.distance > bottomDist) + ) { + bestSide = { scrollCache, name: 'bottom', distance: bottomDist } + } + + if ( + leftDist <= edgeThreshold && this.everMovedLeft && scrollCache.canScrollLeft() && + (!bestSide || bestSide.distance > leftDist) + ) { + bestSide = { scrollCache, name: 'left', distance: leftDist } + } + + if ( + rightDist <= edgeThreshold && this.everMovedRight && scrollCache.canScrollRight() && + (!bestSide || bestSide.distance > rightDist) + ) { + bestSide = { scrollCache, name: 'right', distance: rightDist } + } + } + } + + return bestSide + } + + private buildCaches(scrollStartEl: HTMLElement) { + return this.queryScrollEls(scrollStartEl).map((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 + }) + } + + private queryScrollEls(scrollStartEl: HTMLElement) { + let els = [] + + for (let query of this.scrollQuery) { + if (typeof query === 'object') { + els.push(query) + } else { + els.push(...Array.prototype.slice.call( + getElRoot(scrollStartEl).querySelectorAll(query), + )) + } + } + + return els + } +} diff --git a/apps/schoolCalender/fullcalendar/interaction/src/dnd/ElementMirror.ts b/apps/schoolCalender/fullcalendar/interaction/src/dnd/ElementMirror.ts new file mode 100644 index 000000000..6c1e9f4a1 --- /dev/null +++ b/apps/schoolCalender/fullcalendar/interaction/src/dnd/ElementMirror.ts @@ -0,0 +1,146 @@ +import { removeElement, applyStyle, whenTransitionDone, Rect } from '@fullcalendar/common' + +/* +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. +*/ +export class ElementMirror { + isVisible: boolean = false // must be explicitly enabled + origScreenX?: number + origScreenY?: number + deltaX?: number + deltaY?: number + sourceEl: HTMLElement | null = null + mirrorEl: HTMLElement | null = null + sourceElRect: Rect | null = null // screen coords relative to viewport + + // options that can be set directly by caller + parentNode: HTMLElement = document.body // HIGHLY SUGGESTED to set this to sidestep ShadowDOM issues + zIndex: number = 9999 + revertDuration: number = 0 + + start(sourceEl: HTMLElement, pageX: number, pageY: number) { + 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() + } + + handleMove(pageX: number, pageY: number) { + this.deltaX = (pageX - window.pageXOffset) - this.origScreenX! + this.deltaY = (pageY - window.pageYOffset) - this.origScreenY! + this.updateElPosition() + } + + // can be called before start + setIsVisible(bool: boolean) { + 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 + stop(needsRevertAnimation: boolean, callback: () => void) { + let done = () => { + 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) + } + } + + doRevertAnimation(callback: () => void, revertDuration: number) { + let mirrorEl = this.mirrorEl! + let 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, () => { + mirrorEl.style.transition = '' + callback() + }) + } + + cleanup() { + if (this.mirrorEl) { + removeElement(this.mirrorEl) + this.mirrorEl = null + } + + this.sourceEl = null + } + + updateElPosition() { + if (this.sourceEl && this.isVisible) { + applyStyle(this.getMirrorEl(), { + left: this.sourceElRect!.left + this.deltaX!, + top: this.sourceElRect!.top + this.deltaY!, + }) + } + } + + getMirrorEl(): HTMLElement { + let sourceElRect = this.sourceElRect! + let mirrorEl = this.mirrorEl + + if (!mirrorEl) { + mirrorEl = this.mirrorEl = this.sourceEl!.cloneNode(true) as HTMLElement // 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: '', // in case original element was hidden by the drag effect + boxSizing: 'border-box', // for easy width/height + width: sourceElRect.right - sourceElRect.left, // explicit height in case there was a 'right' value + height: sourceElRect.bottom - sourceElRect.top, // explicit width in case there was a 'bottom' value + right: 'auto', // erase and set width instead + bottom: 'auto', // erase and set height instead + margin: 0, + }) + + this.parentNode.appendChild(mirrorEl) + } + + return mirrorEl + } +} diff --git a/apps/schoolCalender/fullcalendar/interaction/src/dnd/FeaturefulElementDragging.ts b/apps/schoolCalender/fullcalendar/interaction/src/dnd/FeaturefulElementDragging.ts new file mode 100644 index 000000000..3f1c7826b --- /dev/null +++ b/apps/schoolCalender/fullcalendar/interaction/src/dnd/FeaturefulElementDragging.ts @@ -0,0 +1,213 @@ +import { + PointerDragEvent, + preventSelection, + allowSelection, + preventContextMenu, + allowContextMenu, + ElementDragging, +} from '@fullcalendar/common' +import { PointerDragging } from './PointerDragging' +import { ElementMirror } from './ElementMirror' +import { AutoScroller } from './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 +*/ +export class FeaturefulElementDragging extends ElementDragging { + pointer: PointerDragging + mirror: ElementMirror + autoScroller: AutoScroller + + // options that can be directly set by caller + // the caller can also set the PointerDragging's options as well + delay: number | null = null + minDistance: number = 0 + touchScrollAllowed: boolean = true // prevents drag from starting and blocks scrolling during drag + + mirrorNeedsRevert: boolean = false + isInteracting: boolean = false // is the user validly moving the pointer? lasts until pointerup + isDragging: boolean = false // is it INTENTFULLY dragging? lasts until after revert animation + isDelayEnded: boolean = false + isDistanceSurpassed: boolean = false + delayTimeoutId: number | null = null + + constructor(private containerEl: HTMLElement, selector?: string) { + super(containerEl) + + let 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() + } + + destroy() { + 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({} as any) + } + + onPointerDown = (ev: PointerDragEvent) => { + 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 as HTMLElement, ev.pageX, ev.pageY) // must happen on first pointer down + + this.startDelay(ev) + + if (!this.minDistance) { + this.handleDistanceSurpassed(ev) + } + } + } + } + + onPointerMove = (ev: PointerDragEvent) => { + if (this.isInteracting) { + this.emitter.trigger('pointermove', ev) + + if (!this.isDistanceSurpassed) { + let minDistance = this.minDistance + let distanceSq // current distance from the origin, squared + let { deltaX, deltaY } = ev + + 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) + } + } + } + + onPointerUp = (ev: PointerDragEvent) => { + 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 + } + } + } + + startDelay(ev: PointerDragEvent) { + if (typeof this.delay === 'number') { + this.delayTimeoutId = setTimeout(() => { + this.delayTimeoutId = null + this.handleDelayEnd(ev) + }, this.delay) as any // not assignable to number! + } else { + this.handleDelayEnd(ev) + } + } + + handleDelayEnd(ev: PointerDragEvent) { + this.isDelayEnded = true + this.tryStartDrag(ev) + } + + handleDistanceSurpassed(ev: PointerDragEvent) { + this.isDistanceSurpassed = true + this.tryStartDrag(ev) + } + + tryStartDrag(ev: PointerDragEvent) { + 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() + } + } + } + } + + tryStopDrag(ev: PointerDragEvent) { + // .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), // bound with args + ) + } + + stopDrag(ev: PointerDragEvent) { + this.isDragging = false + this.emitter.trigger('dragend', ev) + } + + // fill in the implementations... + + setIgnoreMove(bool: boolean) { + this.pointer.shouldIgnoreMove = bool + } + + setMirrorIsVisible(bool: boolean) { + this.mirror.setIsVisible(bool) + } + + setMirrorNeedsRevert(bool: boolean) { + this.mirrorNeedsRevert = bool + } + + setAutoScrollEnabled(bool: boolean) { + this.autoScroller.isEnabled = bool + } +} diff --git a/apps/schoolCalender/fullcalendar/interaction/src/dnd/PointerDragging.ts b/apps/schoolCalender/fullcalendar/interaction/src/dnd/PointerDragging.ts new file mode 100644 index 000000000..dbe7d8abb --- /dev/null +++ b/apps/schoolCalender/fullcalendar/interaction/src/dnd/PointerDragging.ts @@ -0,0 +1,344 @@ +import { config, elementClosest, Emitter, PointerDragEvent } from '@fullcalendar/common' + +config.touchMouseIgnoreWait = 500 + +let ignoreMouseDepth = 0 +let listenerCnt = 0 +let 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 +*/ +export class PointerDragging { + containerEl: EventTarget + subjectEl: HTMLElement | null = null + emitter: Emitter + + // options that can be directly assigned by caller + selector: string = '' // will cause subjectEl in all emitted events to be this element + handleSelector: string = '' + shouldIgnoreMove: boolean = false + shouldWatchScroll: boolean = true // for simulating pointermove on scroll + + // internal states + isDragging: boolean = false + isTouchDragging: boolean = false + wasTouchScroll: boolean = false + origPageX: number + origPageY: number + prevPageX: number + prevPageY: number + prevScrollX: number // at time of last pointer pageX/pageY capture + prevScrollY: number // " + + constructor(containerEl: EventTarget) { + this.containerEl = containerEl + this.emitter = new Emitter() + containerEl.addEventListener('mousedown', this.handleMouseDown as EventListener) + containerEl.addEventListener('touchstart', this.handleTouchStart as EventListener, { passive: true }) + listenerCreated() + } + + destroy() { + this.containerEl.removeEventListener('mousedown', this.handleMouseDown as EventListener) + this.containerEl.removeEventListener('touchstart', this.handleTouchStart as EventListener, { passive: true } as AddEventListenerOptions) + listenerDestroyed() + } + + tryStart(ev: UIEvent): boolean { + let subjectEl = this.querySubjectEl(ev) + let downEl = ev.target as HTMLElement + + 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 + } + + cleanup() { + isWindowTouchMoveCancelled = false + this.isDragging = false + this.subjectEl = null + // keep wasTouchScroll around for later access + this.destroyScrollWatch() + } + + querySubjectEl(ev: UIEvent): HTMLElement { + if (this.selector) { + return elementClosest(ev.target as HTMLElement, this.selector) + } + return this.containerEl as HTMLElement + } + + // Mouse + // ---------------------------------------------------------------------------------------------------- + + handleMouseDown = (ev: MouseEvent) => { + if ( + !this.shouldIgnoreMouse() && + isPrimaryMouseButton(ev) && + this.tryStart(ev) + ) { + let 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) + } + } + + handleMouseMove = (ev: MouseEvent) => { + let pev = this.createEventFromMouse(ev) + this.recordCoords(pev) + this.emitter.trigger('pointermove', pev) + } + + handleMouseUp = (ev: MouseEvent) => { + 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 + } + + shouldIgnoreMouse() { + return ignoreMouseDepth || this.isTouchDragging + } + + // Touch + // ---------------------------------------------------------------------------------------------------- + + handleTouchStart = (ev: TouchEvent) => { + if (this.tryStart(ev)) { + this.isTouchDragging = true + + let 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 + let targetEl = ev.target as HTMLElement + + 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, // useCapture + ) + } + } + + handleTouchMove = (ev: TouchEvent) => { + let pev = this.createEventFromTouch(ev) + this.recordCoords(pev) + this.emitter.trigger('pointermove', pev) + } + + handleTouchEnd = (ev: TouchEvent) => { + if (this.isDragging) { // done to guard against touchend followed by touchcancel + let targetEl = ev.target as HTMLElement + + 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() + } + } + + handleTouchScroll = () => { + this.wasTouchScroll = true + } + + // can be called by user of this class, to cancel touch-based scrolling for the current drag + cancelTouchScroll() { + if (this.isDragging) { + isWindowTouchMoveCancelled = true + } + } + + // Scrolling that simulates pointermoves + // ---------------------------------------------------------------------------------------------------- + + initScrollWatch(ev: PointerDragEvent) { + if (this.shouldWatchScroll) { + this.recordCoords(ev) + window.addEventListener('scroll', this.handleScroll, true) // useCapture=true + } + } + + recordCoords(ev: PointerDragEvent) { + if (this.shouldWatchScroll) { + this.prevPageX = (ev as any).pageX + this.prevPageY = (ev as any).pageY + this.prevScrollX = window.pageXOffset + this.prevScrollY = window.pageYOffset + } + } + + handleScroll = (ev: UIEvent) => { + if (!this.shouldIgnoreMove) { + let pageX = (window.pageXOffset - this.prevScrollX) + this.prevPageX + let pageY = (window.pageYOffset - this.prevScrollY) + this.prevPageY + + this.emitter.trigger('pointermove', { + origEvent: ev, + isTouch: this.isTouchDragging, + subjectEl: this.subjectEl, + pageX, + pageY, + deltaX: pageX - this.origPageX, + deltaY: pageY - this.origPageY, + } as PointerDragEvent) + } + } + + destroyScrollWatch() { + if (this.shouldWatchScroll) { + window.removeEventListener('scroll', this.handleScroll, true) // useCaptured=true + } + } + + // Event Normalization + // ---------------------------------------------------------------------------------------------------- + + createEventFromMouse(ev: MouseEvent, isFirst?: boolean): PointerDragEvent { + let deltaX = 0 + let 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, + deltaY, + } + } + + createEventFromTouch(ev: TouchEvent, isFirst?: boolean): PointerDragEvent { + let touches = ev.touches + let pageX + let pageY + let deltaX = 0 + let 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 as any).pageX + pageY = (ev as any).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, + pageY, + deltaX, + deltaY, + } + } +} + +// Returns a boolean whether this was a left mouse click and no ctrl key (which means right click on Mac) +function isPrimaryMouseButton(ev: MouseEvent) { + return ev.button === 0 && !ev.ctrlKey +} + +// Ignoring fake mouse events generated by touch +// ---------------------------------------------------------------------------------------------------- + +function startIgnoringMouse() { // can be made non-class function + ignoreMouseDepth += 1 + + setTimeout(() => { + 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 } as AddEventListenerOptions) + } +} + +function onWindowTouchMove(ev: UIEvent) { + if (isWindowTouchMoveCancelled) { + ev.preventDefault() + } +} diff --git a/apps/schoolCalender/fullcalendar/interaction/src/interactions-external/ExternalDraggable.ts b/apps/schoolCalender/fullcalendar/interaction/src/interactions-external/ExternalDraggable.ts new file mode 100644 index 000000000..22aba108d --- /dev/null +++ b/apps/schoolCalender/fullcalendar/interaction/src/interactions-external/ExternalDraggable.ts @@ -0,0 +1,70 @@ +import { BASE_OPTION_DEFAULTS, PointerDragEvent } from '@fullcalendar/common' +import { FeaturefulElementDragging } from '../dnd/FeaturefulElementDragging' +import { ExternalElementDragging, DragMetaGenerator } from './ExternalElementDragging' + +export interface ExternalDraggableSettings { + eventData?: DragMetaGenerator + itemSelector?: string + minDistance?: number + longPressDelay?: number + appendTo?: HTMLElement +} + +/* +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. +*/ +export class ExternalDraggable { + dragging: FeaturefulElementDragging + settings: ExternalDraggableSettings + + constructor(el: HTMLElement, settings: ExternalDraggableSettings = {}) { + this.settings = settings + + let 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 + } + + handlePointerDown = (ev: PointerDragEvent) => { + let { dragging } = this + let { minDistance, longPressDelay } = this.settings + + 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 + } + + handleDragStart = (ev: PointerDragEvent) => { + if ( + ev.isTouch && + this.dragging.delay && + (ev.subjectEl as HTMLElement).classList.contains('fc-event') + ) { + this.dragging.mirror.getMirrorEl().classList.add('fc-event-selected') + } + } + + destroy() { + this.dragging.destroy() + } +} diff --git a/apps/schoolCalender/fullcalendar/interaction/src/interactions-external/ExternalElementDragging.ts b/apps/schoolCalender/fullcalendar/interaction/src/interactions-external/ExternalElementDragging.ts new file mode 100644 index 000000000..95ac7e0c2 --- /dev/null +++ b/apps/schoolCalender/fullcalendar/interaction/src/interactions-external/ExternalElementDragging.ts @@ -0,0 +1,268 @@ +import { + Hit, + interactionSettingsStore, + PointerDragEvent, + parseEventDef, createEventInstance, EventTuple, + createEmptyEventStore, eventTupleToStore, + config, + DateSpan, DatePointApi, + EventInteractionState, + DragMetaInput, DragMeta, parseDragMeta, + EventApi, + elementMatches, + enableCursor, disableCursor, + isInteractionValid, + ElementDragging, + ViewApi, + CalendarContext, + getDefaultEventEnd, + refineEventDef, +} from '@fullcalendar/common' +import { __assign } from 'tslib' +import { HitDragging } from '../interactions/HitDragging' +import { buildDatePointApiWithContext } from '../utils' + +export type DragMetaGenerator = DragMetaInput | ((el: HTMLElement) => DragMetaInput) + +export interface ExternalDropApi extends DatePointApi { + draggedEl: HTMLElement + jsEvent: UIEvent + view: ViewApi +} + +/* +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. +*/ +export class ExternalElementDragging { + hitDragging: HitDragging + receivingContext: CalendarContext | null = null + droppableEvent: EventTuple | null = null // will exist for all drags, even if create:false + suppliedDragMeta: DragMetaGenerator | null = null + dragMeta: DragMeta | null = null + + constructor(dragging: ElementDragging, suppliedDragMeta?: DragMetaGenerator) { + let 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 + } + + handleDragStart = (ev: PointerDragEvent) => { + this.dragMeta = this.buildDragMeta(ev.subjectEl as HTMLElement) + } + + buildDragMeta(subjectEl: HTMLElement) { + if (typeof this.suppliedDragMeta === 'object') { + return parseDragMeta(this.suppliedDragMeta) + } + if (typeof this.suppliedDragMeta === 'function') { + return parseDragMeta(this.suppliedDragMeta(subjectEl)) + } + return getDragMetaFromEl(subjectEl) + } + + handleHitUpdate = (hit: Hit | null, isFinal: boolean, ev: PointerDragEvent) => { + let { dragging } = this.hitDragging + let receivingContext: CalendarContext | null = null + let droppableEvent: EventTuple | null = null + let isInvalid = false + let interaction: EventInteractionState = { + affectedEvents: createEmptyEventStore(), + mutatedEvents: createEmptyEventStore(), + isEvent: this.dragMeta!.create, + } + + if (hit) { + receivingContext = hit.context + + if (this.canDropElOnCalendar(ev.subjectEl as HTMLElement, 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'), // TODO: turn className into constant + // TODO: somehow query FullCalendars WITHIN shadow-roots for existing event-mirror els + ) + + if (!isInvalid) { + enableCursor() + } else { + disableCursor() + } + + if (!isFinal) { + dragging.setMirrorNeedsRevert(!droppableEvent) + + this.receivingContext = receivingContext + this.droppableEvent = droppableEvent + } + } + + handleDragEnd = (pev: PointerDragEvent) => { + let { receivingContext, droppableEvent } = this + + this.clearDrag() + + if (receivingContext && droppableEvent) { + let finalHit = this.hitDragging.finalHit! + let finalView = finalHit.context.viewApi + let dragMeta = this.dragMeta! + + receivingContext.emitter.trigger('drop', { + ...buildDatePointApiWithContext(finalHit.dateSpan, receivingContext), + draggedEl: pev.subjectEl as HTMLElement, + jsEvent: pev.origEvent as MouseEvent, // Is this always a mouse event? See #4655 + view: finalView, + }) + + if (dragMeta.create) { + let addingEvents = eventTupleToStore(droppableEvent) + + receivingContext.dispatch({ + type: 'MERGE_EVENTS', + eventStore: addingEvents, + }) + + 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() { + receivingContext.dispatch({ + type: 'REMOVE_EVENTS', + eventStore: addingEvents, + }) + }, + draggedEl: pev.subjectEl as HTMLElement, + view: finalView, + }) + } + } + + this.receivingContext = null + this.droppableEvent = null + } + + displayDrag(nextContext: CalendarContext | null, state: EventInteractionState) { + let prevContext = this.receivingContext + + if (prevContext && prevContext !== nextContext) { + prevContext.dispatch({ type: 'UNSET_EVENT_DRAG' }) + } + + if (nextContext) { + nextContext.dispatch({ type: 'SET_EVENT_DRAG', state }) + } + } + + clearDrag() { + if (this.receivingContext) { + this.receivingContext.dispatch({ type: 'UNSET_EVENT_DRAG' }) + } + } + + canDropElOnCalendar(el: HTMLElement, receivingContext: CalendarContext): boolean { + let 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 + } +} + +// Utils for computing event store from the DragMeta +// ---------------------------------------------------------------------------------------------------- + +function computeEventForDateSpan(dateSpan: DateSpan, dragMeta: DragMeta, context: CalendarContext): EventTuple { + let defProps = { ...dragMeta.leftoverProps } + + for (let transform of context.pluginHooks.externalDefTransforms) { + __assign(defProps, transform(dateSpan, dragMeta)) + } + + let { refined, extra } = refineEventDef(defProps, context) + let def = parseEventDef( + refined, + extra, + dragMeta.sourceId, + dateSpan.allDay, + context.options.forceEventDuration || Boolean(dragMeta.duration), // hasEnd + context, + ) + + let 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) + } + + let end = dragMeta.duration ? + context.dateEnv.add(start, dragMeta.duration) : + getDefaultEventEnd(dateSpan.allDay, start, context) + + let instance = createEventInstance(def.defId, { start, end }) + + return { def, instance } +} + +// Utils for extracting data from element +// ---------------------------------------------------------------------------------------------------- + +function getDragMetaFromEl(el: HTMLElement): DragMeta { + let str = getEmbeddedElData(el, 'event') + let obj = str ? + JSON.parse(str) : + { create: false } // if no embedded data, assume no event creation + + return parseDragMeta(obj) +} + +config.dataAttrPrefix = '' + +function getEmbeddedElData(el: HTMLElement, name: string): string { + let prefix = config.dataAttrPrefix + let prefixedName = (prefix ? prefix + '-' : '') + name + + return el.getAttribute('data-' + prefixedName) || '' +} diff --git a/apps/schoolCalender/fullcalendar/interaction/src/interactions-external/InferredElementDragging.ts b/apps/schoolCalender/fullcalendar/interaction/src/interactions-external/InferredElementDragging.ts new file mode 100644 index 000000000..ab03cec00 --- /dev/null +++ b/apps/schoolCalender/fullcalendar/interaction/src/interactions-external/InferredElementDragging.ts @@ -0,0 +1,77 @@ +import { PointerDragEvent, ElementDragging } from '@fullcalendar/common' +import { PointerDragging } from '../dnd/PointerDragging' + +/* +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. +*/ +export class InferredElementDragging extends ElementDragging { + pointer: PointerDragging + shouldIgnoreMove: boolean = false + mirrorSelector: string = '' + currentMirrorEl: HTMLElement | null = null + + constructor(containerEl: HTMLElement) { + super(containerEl) + + let pointer = this.pointer = new PointerDragging(containerEl) + pointer.emitter.on('pointerdown', this.handlePointerDown) + pointer.emitter.on('pointermove', this.handlePointerMove) + pointer.emitter.on('pointerup', this.handlePointerUp) + } + + destroy() { + this.pointer.destroy() + } + + handlePointerDown = (ev: PointerDragEvent) => { + this.emitter.trigger('pointerdown', ev) + + if (!this.shouldIgnoreMove) { + // fire dragstart right away. does not support delay or min-distance + this.emitter.trigger('dragstart', ev) + } + } + + handlePointerMove = (ev: PointerDragEvent) => { + if (!this.shouldIgnoreMove) { + this.emitter.trigger('dragmove', ev) + } + } + + handlePointerUp = (ev: PointerDragEvent) => { + this.emitter.trigger('pointerup', ev) + + if (!this.shouldIgnoreMove) { + // fire dragend right away. does not support a revert animation + this.emitter.trigger('dragend', ev) + } + } + + setIgnoreMove(bool: boolean) { + this.shouldIgnoreMove = bool + } + + setMirrorIsVisible(bool: boolean) { + 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 { + let mirrorEl = this.mirrorSelector + // TODO: somehow query FullCalendars WITHIN shadow-roots + ? document.querySelector(this.mirrorSelector) as HTMLElement + : null + + if (mirrorEl) { + this.currentMirrorEl = mirrorEl + mirrorEl.style.visibility = 'hidden' + } + } + } +} diff --git a/apps/schoolCalender/fullcalendar/interaction/src/interactions-external/ThirdPartyDraggable.ts b/apps/schoolCalender/fullcalendar/interaction/src/interactions-external/ThirdPartyDraggable.ts new file mode 100644 index 000000000..324b22255 --- /dev/null +++ b/apps/schoolCalender/fullcalendar/interaction/src/interactions-external/ThirdPartyDraggable.ts @@ -0,0 +1,52 @@ +import { ExternalElementDragging, DragMetaGenerator } from './ExternalElementDragging' +import { InferredElementDragging } from './InferredElementDragging' + +export interface ThirdPartyDraggableSettings { + eventData?: DragMetaGenerator + itemSelector?: string + mirrorSelector?: string +} + +/* +Bridges third-party drag-n-drop systems with FullCalendar. +Must be instantiated and destroyed by caller. +*/ +export class ThirdPartyDraggable { + dragging: InferredElementDragging + + constructor( + containerOrSettings?: EventTarget | ThirdPartyDraggableSettings, + settings?: ThirdPartyDraggableSettings, + ) { + let containerEl: EventTarget = document + + if ( + // wish we could just test instanceof EventTarget, but doesn't work in IE11 + containerOrSettings === document || + containerOrSettings instanceof Element + ) { + containerEl = containerOrSettings as EventTarget + settings = settings || {} + } else { + settings = (containerOrSettings || {}) as ThirdPartyDraggableSettings + } + + let dragging = this.dragging = new InferredElementDragging(containerEl as HTMLElement) + + 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 + } + + destroy() { + this.dragging.destroy() + } +} diff --git a/apps/schoolCalender/fullcalendar/interaction/src/interactions/DateClicking.ts b/apps/schoolCalender/fullcalendar/interaction/src/interactions/DateClicking.ts new file mode 100644 index 000000000..06b352de9 --- /dev/null +++ b/apps/schoolCalender/fullcalendar/interaction/src/interactions/DateClicking.ts @@ -0,0 +1,71 @@ +import { + PointerDragEvent, Interaction, InteractionSettings, interactionSettingsToStore, + DatePointApi, + ViewApi, +} from '@fullcalendar/common' +import { FeaturefulElementDragging } from '../dnd/FeaturefulElementDragging' +import { HitDragging, isHitsEqual } from './HitDragging' +import { buildDatePointApiWithContext } from '../utils' + +export interface DateClickArg extends DatePointApi { + dayEl: HTMLElement + jsEvent: MouseEvent + view: ViewApi +} + +/* +Monitors when the user clicks on a specific date/time of a component. +A pointerdown+pointerup on the same "hit" constitutes a click. +*/ +export class DateClicking extends Interaction { + dragging: FeaturefulElementDragging + hitDragging: HitDragging + + constructor(settings: InteractionSettings) { + super(settings) + + // 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 + + let hitDragging = this.hitDragging = new HitDragging(this.dragging, interactionSettingsToStore(settings)) + hitDragging.emitter.on('pointerdown', this.handlePointerDown) + hitDragging.emitter.on('dragend', this.handleDragEnd) + } + + destroy() { + this.dragging.destroy() + } + + handlePointerDown = (pev: PointerDragEvent) => { + let { dragging } = this + let downEl = pev.origEvent.target as HTMLElement + + // 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 + handleDragEnd = (ev: PointerDragEvent) => { + let { component } = this + let { pointer } = this.dragging + + if (!pointer.wasTouchScroll) { + let { initialHit, finalHit } = this.hitDragging + + if (initialHit && finalHit && isHitsEqual(initialHit, finalHit)) { + let { context } = component + let arg: DateClickArg = { + ...buildDatePointApiWithContext(initialHit.dateSpan, context), + dayEl: initialHit.dayEl, + jsEvent: ev.origEvent as MouseEvent, + view: context.viewApi || context.calendarApi.view, + } + + context.emitter.trigger('dateClick', arg) + } + } + } +} diff --git a/apps/schoolCalender/fullcalendar/interaction/src/interactions/DateSelecting.ts b/apps/schoolCalender/fullcalendar/interaction/src/interactions/DateSelecting.ts new file mode 100644 index 000000000..fa6b3ff09 --- /dev/null +++ b/apps/schoolCalender/fullcalendar/interaction/src/interactions/DateSelecting.ts @@ -0,0 +1,152 @@ +import { + compareNumbers, enableCursor, disableCursor, DateComponent, Hit, + DateSpan, PointerDragEvent, dateSelectionJoinTransformer, + Interaction, InteractionSettings, interactionSettingsToStore, + triggerDateSelect, isDateSelectionValid, +} from '@fullcalendar/common' +import { __assign } from 'tslib' +import { HitDragging } from './HitDragging' +import { FeaturefulElementDragging } from '../dnd/FeaturefulElementDragging' + +/* +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. +*/ +export class DateSelecting extends Interaction { + dragging: FeaturefulElementDragging + hitDragging: HitDragging + dragSelection: DateSpan | null = null + + constructor(settings: InteractionSettings) { + super(settings) + let { component } = settings + let { options } = component.context + + let dragging = this.dragging = new FeaturefulElementDragging(settings.el) + dragging.touchScrollAllowed = false + dragging.minDistance = options.selectMinDistance || 0 + dragging.autoScroller.isEnabled = options.dragScroll + + let 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) + } + + destroy() { + this.dragging.destroy() + } + + handlePointerDown = (ev: PointerDragEvent) => { + let { component, dragging } = this + let { options } = component.context + + let canSelect = options.selectable && + component.isValidDateDownEl(ev.origEvent.target as HTMLElement) + + // 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(component) : null + } + + handleDragStart = (ev: PointerDragEvent) => { + this.component.context.calendarApi.unselect(ev) // unselect previous selections + } + + handleHitUpdate = (hit: Hit | null, isFinal: boolean) => { + let { context } = this.component + let dragSelection: DateSpan | null = null + let isInvalid = false + + if (hit) { + let initialHit = this.hitDragging.initialHit! + let 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 + } + } + + handlePointerUp = (pev: PointerDragEvent) => { + if (this.dragSelection) { + // selection is already rendered, so just need to report selection + triggerDateSelect(this.dragSelection, pev, this.component.context) + + this.dragSelection = null + } + } +} + +function getComponentTouchDelay(component: DateComponent): number { + let { options } = component.context + let delay = options.selectLongPressDelay + + if (delay == null) { + delay = options.longPressDelay + } + + return delay +} + +function joinHitsIntoSelection(hit0: Hit, hit1: Hit, dateSelectionTransformers: dateSelectionJoinTransformer[]): DateSpan { + let dateSpan0 = hit0.dateSpan + let dateSpan1 = hit1.dateSpan + let ms = [ + dateSpan0.range.start, + dateSpan0.range.end, + dateSpan1.range.start, + dateSpan1.range.end, + ] + + ms.sort(compareNumbers) + + let props = {} as DateSpan + + for (let transformer of dateSelectionTransformers) { + let 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 +} diff --git a/apps/schoolCalender/fullcalendar/interaction/src/interactions/EventDragging.ts b/apps/schoolCalender/fullcalendar/interaction/src/interactions/EventDragging.ts new file mode 100644 index 000000000..9a9ecfc49 --- /dev/null +++ b/apps/schoolCalender/fullcalendar/interaction/src/interactions/EventDragging.ts @@ -0,0 +1,482 @@ +import { + DateComponent, Seg, + PointerDragEvent, Hit, + EventMutation, applyMutationToEventStore, + startOfDay, + elementClosest, + EventStore, getRelevantEvents, createEmptyEventStore, + EventInteractionState, + diffDates, enableCursor, disableCursor, + EventRenderRange, getElSeg, + EventApi, + eventDragMutationMassager, + Interaction, InteractionSettings, interactionSettingsStore, + EventDropTransformers, + CalendarContext, + ViewApi, + EventChangeArg, + buildEventApis, + EventAddArg, + EventRemoveArg, + isInteractionValid, + getElRoot, +} from '@fullcalendar/common' +import { __assign } from 'tslib' +import { HitDragging, isHitsEqual } from './HitDragging' +import { FeaturefulElementDragging } from '../dnd/FeaturefulElementDragging' +import { buildDatePointApiWithContext } from '../utils' + +export type EventDragStopArg = EventDragArg +export type EventDragStartArg = EventDragArg + +export interface EventDragArg { + el: HTMLElement + event: EventApi + jsEvent: MouseEvent + view: ViewApi +} + +export class EventDragging extends Interaction { // TODO: rename to EventSelectingAndDragging + // TODO: test this in IE11 + // QUESTION: why do we need it on the resizable??? + static SELECTOR = '.fc-event-draggable, .fc-event-resizable' + + dragging: FeaturefulElementDragging + hitDragging: HitDragging + + // internal state + subjectEl: HTMLElement | null = null + subjectSeg: Seg | null = null // the seg being selected/dragged + isDragging: boolean = false + eventRange: EventRenderRange | null = null + relevantEvents: EventStore | null = null // the events being dragged + receivingContext: CalendarContext | null = null + validMutation: EventMutation | null = null + mutatedRelevantEvents: EventStore | null = null + + constructor(settings: InteractionSettings) { + super(settings) + let { component } = this + let { options } = component.context + + let dragging = this.dragging = new FeaturefulElementDragging(settings.el) + dragging.pointer.selector = EventDragging.SELECTOR + dragging.touchScrollAllowed = false + dragging.autoScroller.isEnabled = options.dragScroll + + let 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) + } + + destroy() { + this.dragging.destroy() + } + + handlePointerDown = (ev: PointerDragEvent) => { + let origTarget = ev.origEvent.target as HTMLElement + let { component, dragging } = this + let { mirror } = dragging + let { options } = component.context + let initialContext = component.context + this.subjectEl = ev.subjectEl as HTMLElement + let subjectSeg = this.subjectSeg = getElSeg(ev.subjectEl as HTMLElement)! + let eventRange = this.eventRange = subjectSeg.eventRange! + let 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 + + let 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 as HTMLElement).classList.contains('fc-event-draggable') + } + + handleDragStart = (ev: PointerDragEvent) => { + let initialContext = this.component.context + let eventRange = this.eventRange! + let 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 }) + } + } 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 as MouseEvent, // Is this always a mouse event? See #4655 + view: initialContext.viewApi, + } as EventDragStartArg) + } + } + + handleHitUpdate = (hit: Hit | null, isFinal: boolean) => { + if (!this.isDragging) { + return + } + + let relevantEvents = this.relevantEvents! + let initialHit = this.hitDragging.initialHit! + let initialContext = this.component.context + + // states based on new hit + let receivingContext: CalendarContext | null = null + let mutation: EventMutation | null = null + let mutatedRelevantEvents: EventStore | null = null + let isInvalid = false + let interaction: EventInteractionState = { + affectedEvents: relevantEvents, + mutatedEvents: createEmptyEventStore(), + isEvent: true, + } + + if (hit) { + receivingContext = hit.context + let 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'), // TODO: turn className into constant + ) + + // assign states based on new hit + this.receivingContext = receivingContext + this.validMutation = mutation + this.mutatedRelevantEvents = mutatedRelevantEvents + } + } + + handlePointerUp = () => { + if (!this.isDragging) { + this.cleanup() // because handleDragEnd won't fire + } + } + + handleDragEnd = (ev: PointerDragEvent) => { + if (this.isDragging) { + let initialContext = this.component.context + let initialView = initialContext.viewApi + let { receivingContext, validMutation } = this + let eventDef = this.eventRange!.def + let eventInstance = this.eventRange!.instance + let eventApi = new EventApi(initialContext, eventDef, eventInstance) + let relevantEvents = this.relevantEvents! + let mutatedRelevantEvents = this.mutatedRelevantEvents! + let { finalHit } = this.hitDragging + + this.clearDrag() // must happen after revert animation + + initialContext.emitter.trigger('eventDragStop', { + el: this.subjectEl, + event: eventApi, + jsEvent: ev.origEvent as MouseEvent, // Is this always a mouse event? See #4655 + view: initialView, + } as EventDragStopArg) + + if (validMutation) { + // dropped within same calendar + if (receivingContext === initialContext) { + let updatedEventApi = new EventApi( + initialContext, + mutatedRelevantEvents.defs[eventDef.defId], + eventInstance ? mutatedRelevantEvents.instances[eventInstance.instanceId] : null, + ) + + initialContext.dispatch({ + type: 'MERGE_EVENTS', + eventStore: mutatedRelevantEvents, + }) + + let eventChangeArg: EventChangeArg = { + oldEvent: eventApi, + event: updatedEventApi, + relatedEvents: buildEventApis(mutatedRelevantEvents, initialContext, eventInstance), + revert() { + initialContext.dispatch({ + type: 'MERGE_EVENTS', + eventStore: relevantEvents, // the pre-change data + }) + }, + } + + let transformed: ReturnType = {} + for (let transformer of initialContext.getCurrentData().pluginHooks.eventDropTransformers) { + __assign(transformed, transformer(validMutation, initialContext)) + } + + initialContext.emitter.trigger('eventDrop', { + ...eventChangeArg, + ...transformed, + el: ev.subjectEl as HTMLElement, + delta: validMutation.datesDelta!, + jsEvent: ev.origEvent as MouseEvent, // bad + view: initialView, + }) + + initialContext.emitter.trigger('eventChange', eventChangeArg) + + // dropped in different calendar + } else if (receivingContext) { + let eventRemoveArg: EventRemoveArg = { + event: eventApi, + relatedEvents: buildEventApis(relevantEvents, initialContext, eventInstance), + revert() { + initialContext.dispatch({ + type: 'MERGE_EVENTS', + eventStore: relevantEvents, + }) + }, + } + + initialContext.emitter.trigger('eventLeave', { + ...eventRemoveArg, + draggedEl: ev.subjectEl as HTMLElement, + view: initialView, + }) + + initialContext.dispatch({ + type: 'REMOVE_EVENTS', + eventStore: relevantEvents, + }) + + initialContext.emitter.trigger('eventRemove', eventRemoveArg) + + let addedEventDef = mutatedRelevantEvents.defs[eventDef.defId] + let addedEventInstance = mutatedRelevantEvents.instances[eventInstance.instanceId] + let addedEventApi = new EventApi(receivingContext, addedEventDef, addedEventInstance) + + receivingContext.dispatch({ + type: 'MERGE_EVENTS', + eventStore: mutatedRelevantEvents, + }) + + let eventAddArg: EventAddArg = { + event: addedEventApi, + relatedEvents: buildEventApis(mutatedRelevantEvents, receivingContext, addedEventInstance), + revert() { + receivingContext.dispatch({ + type: 'REMOVE_EVENTS', + eventStore: mutatedRelevantEvents, + }) + }, + } + + receivingContext.emitter.trigger('eventAdd', eventAddArg) + + if (ev.isTouch) { + receivingContext.dispatch({ + type: 'SELECT_EVENT', + eventInstanceId: eventInstance.instanceId, + }) + } + + receivingContext.emitter.trigger('drop', { + ...buildDatePointApiWithContext(finalHit.dateSpan, receivingContext), + draggedEl: ev.subjectEl as HTMLElement, + jsEvent: ev.origEvent as MouseEvent, // Is this always a mouse event? See #4655 + view: finalHit.context.viewApi, + }) + + receivingContext.emitter.trigger('eventReceive', { + ...eventAddArg, + draggedEl: ev.subjectEl as HTMLElement, + view: finalHit.context.viewApi, + }) + } + } else { + initialContext.emitter.trigger('_noEventDrop') + } + } + + this.cleanup() + } + + // render a drag state on the next receivingCalendar + displayDrag(nextContext: CalendarContext | null, state: EventInteractionState) { + let initialContext = this.component.context + let 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 }) + } + } + + clearDrag() { + let initialCalendar = this.component.context + let { receivingContext } = this + + 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' }) + } + } + + cleanup() { // reset all internal state + this.subjectSeg = null + this.isDragging = false + this.eventRange = null + this.relevantEvents = null + this.receivingContext = null + this.validMutation = null + this.mutatedRelevantEvents = null + } +} + +function computeEventMutation(hit0: Hit, hit1: Hit, massagers: eventDragMutationMassager[]): EventMutation { + let dateSpan0 = hit0.dateSpan + let dateSpan1 = hit1.dateSpan + let date0 = dateSpan0.range.start + let date1 = dateSpan1.range.start + let standardProps = {} as any + + 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) + } + } + + let delta = diffDates( + date0, date1, + hit0.context.dateEnv, + hit0.componentId === hit1.componentId ? + hit0.largeUnit : + null, + ) + + if (delta.milliseconds) { // has hours/minutes/seconds + standardProps.allDay = false + } + + let mutation: EventMutation = { + datesDelta: delta, + standardProps, + } + + for (let massager of massagers) { + massager(mutation, hit0, hit1) + } + + return mutation +} + +function getComponentTouchDelay(component: DateComponent): number | null { + let { options } = component.context + let delay = options.eventLongPressDelay + + if (delay == null) { + delay = options.longPressDelay + } + + return delay +} diff --git a/apps/schoolCalender/fullcalendar/interaction/src/interactions/EventResizing.ts b/apps/schoolCalender/fullcalendar/interaction/src/interactions/EventResizing.ts new file mode 100644 index 000000000..013b399ae --- /dev/null +++ b/apps/schoolCalender/fullcalendar/interaction/src/interactions/EventResizing.ts @@ -0,0 +1,263 @@ +import { + Seg, Hit, + EventMutation, applyMutationToEventStore, + elementClosest, + PointerDragEvent, + EventStore, getRelevantEvents, createEmptyEventStore, + diffDates, enableCursor, disableCursor, + DateRange, + EventApi, + EventRenderRange, getElSeg, + createDuration, + EventInteractionState, + Interaction, InteractionSettings, interactionSettingsToStore, ViewApi, Duration, EventChangeArg, buildEventApis, isInteractionValid, +} from '@fullcalendar/common' +import { __assign } from 'tslib' +import { HitDragging, isHitsEqual } from './HitDragging' +import { FeaturefulElementDragging } from '../dnd/FeaturefulElementDragging' + +export type EventResizeStartArg = EventResizeStartStopArg +export type EventResizeStopArg = EventResizeStartStopArg + +export interface EventResizeStartStopArg { + el: HTMLElement + event: EventApi + jsEvent: MouseEvent + view: ViewApi +} + +export interface EventResizeDoneArg extends EventChangeArg { + el: HTMLElement + startDelta: Duration + endDelta: Duration + jsEvent: MouseEvent + view: ViewApi +} + +export class EventResizing extends Interaction { + dragging: FeaturefulElementDragging + hitDragging: HitDragging + + // internal state + draggingSegEl: HTMLElement | null = null + draggingSeg: Seg | null = null // TODO: rename to resizingSeg? subjectSeg? + eventRange: EventRenderRange | null = null + relevantEvents: EventStore | null = null + validMutation: EventMutation | null = null + mutatedRelevantEvents: EventStore | null = null + + constructor(settings: InteractionSettings) { + super(settings) + let { component } = settings + + let dragging = this.dragging = new FeaturefulElementDragging(settings.el) + dragging.pointer.selector = '.fc-event-resizer' + dragging.touchScrollAllowed = false + dragging.autoScroller.isEnabled = component.context.options.dragScroll + + let 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) + } + + destroy() { + this.dragging.destroy() + } + + handlePointerDown = (ev: PointerDragEvent) => { + let { component } = this + let segEl = this.querySegEl(ev) + let seg = getElSeg(segEl) + let 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 as HTMLElement) || + (ev.isTouch && this.component.props.eventSelection !== eventRange.instance!.instanceId), + ) + } + + handleDragStart = (ev: PointerDragEvent) => { + let { context } = this.component + let eventRange = this.eventRange! + + this.relevantEvents = getRelevantEvents( + context.getCurrentData().eventStore, + this.eventRange.instance!.instanceId, + ) + + let 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 as MouseEvent, // Is this always a mouse event? See #4655 + view: context.viewApi, + } as EventResizeStartArg) + } + + handleHitUpdate = (hit: Hit | null, isFinal: boolean, ev: PointerDragEvent) => { + let { context } = this.component + let relevantEvents = this.relevantEvents! + let initialHit = this.hitDragging.initialHit! + let eventInstance = this.eventRange.instance! + let mutation: EventMutation | null = null + let mutatedRelevantEvents: EventStore | null = null + let isInvalid = false + let interaction: EventInteractionState = { + affectedEvents: relevantEvents, + mutatedEvents: createEmptyEventStore(), + isEvent: true, + } + + if (hit) { + let disallowed = hit.componentId === initialHit.componentId + && this.isHitComboAllowed + && !this.isHitComboAllowed(initialHit, hit) + + if (!disallowed) { + mutation = computeMutation( + initialHit, + hit, + (ev.subjectEl as HTMLElement).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 + } + } + + handleDragEnd = (ev: PointerDragEvent) => { + let { context } = this.component + let eventDef = this.eventRange!.def + let eventInstance = this.eventRange!.instance + let eventApi = new EventApi(context, eventDef, eventInstance) + let relevantEvents = this.relevantEvents! + let mutatedRelevantEvents = this.mutatedRelevantEvents! + + context.emitter.trigger('eventResizeStop', { + el: this.draggingSegEl, + event: eventApi, + jsEvent: ev.origEvent as MouseEvent, // Is this always a mouse event? See #4655 + view: context.viewApi, + } as EventResizeStopArg) + + if (this.validMutation) { + let updatedEventApi = new EventApi( + context, + mutatedRelevantEvents.defs[eventDef.defId], + eventInstance ? mutatedRelevantEvents.instances[eventInstance.instanceId] : null, + ) + + context.dispatch({ + type: 'MERGE_EVENTS', + eventStore: mutatedRelevantEvents, + }) + + let eventChangeArg: EventChangeArg = { + oldEvent: eventApi, + event: updatedEventApi, + relatedEvents: buildEventApis(mutatedRelevantEvents, context, eventInstance), + revert() { + context.dispatch({ + type: 'MERGE_EVENTS', + eventStore: relevantEvents, // the pre-change events + }) + }, + } + + context.emitter.trigger('eventResize', { + ...eventChangeArg, + el: this.draggingSegEl, + startDelta: this.validMutation.startDelta || createDuration(0), + endDelta: this.validMutation.endDelta || createDuration(0), + jsEvent: ev.origEvent as MouseEvent, + 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 + } + + querySegEl(ev: PointerDragEvent) { + return elementClosest(ev.subjectEl as HTMLElement, '.fc-event') + } +} + +function computeMutation( + hit0: Hit, + hit1: Hit, + isFromStart: boolean, + instanceRange: DateRange, +): EventMutation | null { + let dateEnv = hit0.context.dateEnv + let date0 = hit0.dateSpan.range.start + let date1 = hit1.dateSpan.range.start + + let 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 +} diff --git a/apps/schoolCalender/fullcalendar/interaction/src/interactions/HitDragging.ts b/apps/schoolCalender/fullcalendar/interaction/src/interactions/HitDragging.ts new file mode 100644 index 000000000..d0a329e9c --- /dev/null +++ b/apps/schoolCalender/fullcalendar/interaction/src/interactions/HitDragging.ts @@ -0,0 +1,220 @@ +import { + Emitter, PointerDragEvent, + isDateSpansEqual, + computeRect, + constrainPoint, intersectRects, getRectCenter, diffPoints, Point, + rangeContainsRange, + Hit, + InteractionSettingsStore, + mapHash, + ElementDragging, +} from '@fullcalendar/common' +import { OffsetTracker } from '../OffsetTracker' + +/* +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 +*/ +export class HitDragging { + droppableStore: InteractionSettingsStore + dragging: ElementDragging + emitter: Emitter + + // options that can be set by caller + useSubjectCenter: boolean = false + requireInitial: boolean = true // if doesn't start out on a hit, won't emit any events + + // internal state + offsetTrackers: { [componentUid: string]: OffsetTracker } + initialHit: Hit | null = null + movingHit: Hit | null = null + finalHit: Hit | null = null // won't ever be populated if shouldIgnoreMove + coordAdjust?: Point + + constructor(dragging: ElementDragging, droppableStore: InteractionSettingsStore) { + 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() + } + + handlePointerDown = (ev: PointerDragEvent) => { + let { dragging } = this + + 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) + } + } + + // sets initialHit + // sets coordAdjust + processFirstCoord(ev: PointerDragEvent) { + let origPoint = { left: ev.pageX, top: ev.pageY } + let adjustedPoint = origPoint + let subjectEl = ev.subjectEl + let subjectRect + + if (subjectEl instanceof HTMLElement) { // i.e. not a Document/ShadowRoot + subjectRect = computeRect(subjectEl) + adjustedPoint = constrainPoint(adjustedPoint, subjectRect) + } + + let initialHit = this.initialHit = this.queryHitForOffset(adjustedPoint.left, adjustedPoint.top) + if (initialHit) { + if (this.useSubjectCenter && subjectRect) { + let slicedSubjectRect = intersectRects(subjectRect, initialHit.rect) + if (slicedSubjectRect) { + adjustedPoint = getRectCenter(slicedSubjectRect) + } + } + + this.coordAdjust = diffPoints(adjustedPoint, origPoint) + } else { + this.coordAdjust = { left: 0, top: 0 } + } + } + + handleDragStart = (ev: PointerDragEvent) => { + this.emitter.trigger('dragstart', ev) + this.handleMove(ev, true) // force = fire even if initially null + } + + handleDragMove = (ev: PointerDragEvent) => { + this.emitter.trigger('dragmove', ev) + this.handleMove(ev) + } + + handlePointerUp = (ev: PointerDragEvent) => { + this.releaseHits() + this.emitter.trigger('pointerup', ev) + } + + handleDragEnd = (ev: PointerDragEvent) => { + if (this.movingHit) { + this.emitter.trigger('hitupdate', null, true, ev) + } + + this.finalHit = this.movingHit + this.movingHit = null + this.emitter.trigger('dragend', ev) + } + + handleMove(ev: PointerDragEvent, forceHandle?: boolean) { + let 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) + } + } + + prepareHits() { + this.offsetTrackers = mapHash(this.droppableStore, (interactionSettings) => { + interactionSettings.component.prepareHits() + return new OffsetTracker(interactionSettings.el) + }) + } + + releaseHits() { + let { offsetTrackers } = this + + for (let id in offsetTrackers) { + offsetTrackers[id].destroy() + } + + this.offsetTrackers = {} + } + + queryHitForOffset(offsetLeft: number, offsetTop: number): Hit | null { + let { droppableStore, offsetTrackers } = this + let bestHit: Hit | null = null + + for (let id in droppableStore) { + let component = droppableStore[id].component + let offsetTracker = offsetTrackers[id] + + if ( + offsetTracker && // wasn't destroyed mid-drag + offsetTracker.isWithinClipping(offsetLeft, offsetTop) + ) { + let originLeft = offsetTracker.computeLeft() + let originTop = offsetTracker.computeTop() + let positionLeft = offsetLeft - originLeft + let positionTop = offsetTop - originTop + let { origRect } = offsetTracker + let width = origRect.right - origRect.left + let height = origRect.bottom - origRect.top + + if ( + // must be within the element's bounds + positionLeft >= 0 && positionLeft < width && + positionTop >= 0 && positionTop < height + ) { + let 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 + } +} + +export function isHitsEqual(hit0: Hit | null, hit1: Hit | null): boolean { + if (!hit0 && !hit1) { + return true + } + + if (Boolean(hit0) !== Boolean(hit1)) { + return false + } + + return isDateSpansEqual(hit0!.dateSpan, hit1!.dateSpan) +} diff --git a/apps/schoolCalender/fullcalendar/interaction/src/interactions/UnselectAuto.ts b/apps/schoolCalender/fullcalendar/interaction/src/interactions/UnselectAuto.ts new file mode 100644 index 000000000..23e5b47cb --- /dev/null +++ b/apps/schoolCalender/fullcalendar/interaction/src/interactions/UnselectAuto.ts @@ -0,0 +1,77 @@ +import { + DateSelectionApi, + PointerDragEvent, + elementClosest, + CalendarContext, + getEventTargetViaRoot, +} from '@fullcalendar/common' +import { PointerDragging } from '../dnd/PointerDragging' +import { EventDragging } from './EventDragging' + +export class UnselectAuto { + documentPointer: PointerDragging // for unfocusing + isRecentPointerDateSelect = false // wish we could use a selector to detect date selection, but uses hit system + matchesCancel = false + matchesEvent = false + + constructor(private context: CalendarContext) { + let 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) + } + + destroy() { + this.context.emitter.off('select', this.onSelect) + this.documentPointer.destroy() + } + + onSelect = (selectInfo: DateSelectionApi) => { + if (selectInfo.jsEvent) { + this.isRecentPointerDateSelect = true + } + } + + onDocumentPointerDown = (pev: PointerDragEvent) => { + let unselectCancel = this.context.options.unselectCancel + let downEl = getEventTargetViaRoot(pev.origEvent) as HTMLElement + + this.matchesCancel = !!elementClosest(downEl, unselectCancel) + this.matchesEvent = !!elementClosest(downEl, EventDragging.SELECTOR) // interaction started on an event? + } + + onDocumentPointerUp = (pev: PointerDragEvent) => { + let { context } = this + let { documentPointer } = this + let 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? + ) { + let 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 + } +} diff --git a/apps/schoolCalender/fullcalendar/interaction/src/main.global.ts b/apps/schoolCalender/fullcalendar/interaction/src/main.global.ts new file mode 100644 index 000000000..0af579774 --- /dev/null +++ b/apps/schoolCalender/fullcalendar/interaction/src/main.global.ts @@ -0,0 +1,7 @@ +import { globalPlugins } from '@fullcalendar/common' +import plugin from './main' + +globalPlugins.push(plugin) + +export default plugin +export * from './main' diff --git a/apps/schoolCalender/fullcalendar/interaction/src/main.ts b/apps/schoolCalender/fullcalendar/interaction/src/main.ts new file mode 100644 index 000000000..f57049ca3 --- /dev/null +++ b/apps/schoolCalender/fullcalendar/interaction/src/main.ts @@ -0,0 +1,23 @@ +import { createPlugin } from '@fullcalendar/common' +import { DateClicking } from './interactions/DateClicking' +import { DateSelecting } from './interactions/DateSelecting' +import { EventDragging } from './interactions/EventDragging' +import { EventResizing } from './interactions/EventResizing' +import { UnselectAuto } from './interactions/UnselectAuto' +import { FeaturefulElementDragging } from './dnd/FeaturefulElementDragging' +import { OPTION_REFINERS, LISTENER_REFINERS } from './options' +import './options-declare' + +export default createPlugin({ + componentInteractions: [DateClicking, DateSelecting, EventDragging, EventResizing], + calendarInteractions: [UnselectAuto], + elementDraggingImpl: FeaturefulElementDragging, + optionRefiners: OPTION_REFINERS, + listenerRefiners: LISTENER_REFINERS, +}) + +export * from './api-type-deps' +export { FeaturefulElementDragging } +export { PointerDragging } from './dnd/PointerDragging' +export { ExternalDraggable as Draggable } from './interactions-external/ExternalDraggable' +export { ThirdPartyDraggable } from './interactions-external/ThirdPartyDraggable' diff --git a/apps/schoolCalender/fullcalendar/interaction/src/options-declare.ts b/apps/schoolCalender/fullcalendar/interaction/src/options-declare.ts new file mode 100644 index 000000000..9cbc5a4a8 --- /dev/null +++ b/apps/schoolCalender/fullcalendar/interaction/src/options-declare.ts @@ -0,0 +1,9 @@ +import { OPTION_REFINERS, LISTENER_REFINERS } from './options' + +type ExtraOptionRefiners = typeof OPTION_REFINERS +type ExtraListenerRefiners = typeof LISTENER_REFINERS + +declare module '@fullcalendar/common' { + interface BaseOptionRefiners extends ExtraOptionRefiners {} + interface CalendarListenerRefiners extends ExtraListenerRefiners {} +} diff --git a/apps/schoolCalender/fullcalendar/interaction/src/options.ts b/apps/schoolCalender/fullcalendar/interaction/src/options.ts new file mode 100644 index 000000000..a11ce5399 --- /dev/null +++ b/apps/schoolCalender/fullcalendar/interaction/src/options.ts @@ -0,0 +1,26 @@ +import { identity, Identity, EventDropArg } from '@fullcalendar/common' + +// public +import { + DateClickArg, + EventDragStartArg, EventDragStopArg, + EventResizeStartArg, EventResizeStopArg, EventResizeDoneArg, + DropArg, EventReceiveArg, EventLeaveArg, +} from './api-type-deps' + +export const OPTION_REFINERS = { + fixedMirrorParent: identity as Identity, +} + +export const LISTENER_REFINERS = { + dateClick: identity as Identity<(arg: DateClickArg) => void>, + eventDragStart: identity as Identity<(arg: EventDragStartArg) => void>, + eventDragStop: identity as Identity<(arg: EventDragStopArg) => void>, + eventDrop: identity as Identity<(arg: EventDropArg) => void>, + eventResizeStart: identity as Identity<(arg: EventResizeStartArg) => void>, + eventResizeStop: identity as Identity<(arg: EventResizeStopArg) => void>, + eventResize: identity as Identity<(arg: EventResizeDoneArg) => void>, + drop: identity as Identity<(arg: DropArg) => void>, + eventReceive: identity as Identity<(arg: EventReceiveArg) => void>, + eventLeave: identity as Identity<(arg: EventLeaveArg) => void>, +} diff --git a/apps/schoolCalender/fullcalendar/interaction/src/utils.ts b/apps/schoolCalender/fullcalendar/interaction/src/utils.ts new file mode 100644 index 000000000..056a0040d --- /dev/null +++ b/apps/schoolCalender/fullcalendar/interaction/src/utils.ts @@ -0,0 +1,38 @@ +import { DateSpan, CalendarContext, DatePointApi, DateEnv, ViewApi, EventApi } from '@fullcalendar/common' +import { __assign } from 'tslib' + +export interface DropArg extends DatePointApi { + draggedEl: HTMLElement + jsEvent: MouseEvent + view: ViewApi +} + +export type EventReceiveArg = EventReceiveLeaveArg +export type EventLeaveArg = EventReceiveLeaveArg +export interface EventReceiveLeaveArg { // will this become public? + draggedEl: HTMLElement + event: EventApi + relatedEvents: EventApi[] + revert: () => void + view: ViewApi +} + +export function buildDatePointApiWithContext(dateSpan: DateSpan, context: CalendarContext) { + let props = {} as DatePointApi + + for (let transform of context.pluginHooks.datePointTransforms) { + __assign(props, transform(dateSpan, context)) + } + + __assign(props, buildDatePointApi(dateSpan, context.dateEnv)) + + return props +} + +export function buildDatePointApi(span: DateSpan, dateEnv: DateEnv): DatePointApi { + return { + date: dateEnv.toDate(span.range.start), + dateStr: dateEnv.formatIso(span.range.start, { omitTime: span.allDay }), + allDay: span.allDay, + } +} diff --git a/apps/schoolCalender/fullcalendar/interaction/tsconfig.json b/apps/schoolCalender/fullcalendar/interaction/tsconfig.json new file mode 100644 index 000000000..6172b1a45 --- /dev/null +++ b/apps/schoolCalender/fullcalendar/interaction/tsconfig.json @@ -0,0 +1,13 @@ +{ + "extends": "../../tsconfig.base", + "compilerOptions": { + "rootDir": "src", + "outDir": "tsc" + }, + "include": [ + "src/**/*" + ], + "references": [ + { "path": "../common" } + ] +} diff --git a/apps/schoolCalender/fullcalendar/locales-all.js b/apps/schoolCalender/fullcalendar/locales-all.js new file mode 100644 index 000000000..f8be47ef2 --- /dev/null +++ b/apps/schoolCalender/fullcalendar/locales-all.js @@ -0,0 +1,1622 @@ +[].push.apply(FullCalendar.globalLocales, function () { + 'use strict'; + + var l0 = { + code: 'af', + week: { + dow: 1, // Maandag is die eerste dag van die week. + doy: 4, // Die week wat die 4de Januarie bevat is die eerste week van die jaar. + }, + buttonText: { + prev: 'Vorige', + next: 'Volgende', + today: 'Vandag', + year: 'Jaar', + month: 'Maand', + week: 'Week', + day: 'Dag', + list: 'Agenda', + }, + allDayText: 'Heeldag', + moreLinkText: 'Addisionele', + noEventsText: 'Daar is geen gebeurtenisse nie', + }; + + var l1 = { + code: 'ar-dz', + week: { + dow: 0, // Sunday is the first day of the week. + doy: 4, // The week that contains Jan 1st is the first week of the year. + }, + direction: 'rtl', + buttonText: { + prev: 'السابق', + next: 'التالي', + today: 'اليوم', + month: 'شهر', + week: 'أسبوع', + day: 'يوم', + list: 'أجندة', + }, + weekText: 'أسبوع', + allDayText: 'اليوم كله', + moreLinkText: 'أخرى', + noEventsText: 'أي أحداث لعرض', + }; + + var l2 = { + code: 'ar-kw', + week: { + dow: 0, // Sunday is the first day of the week. + doy: 12, // The week that contains Jan 1st is the first week of the year. + }, + direction: 'rtl', + buttonText: { + prev: 'السابق', + next: 'التالي', + today: 'اليوم', + month: 'شهر', + week: 'أسبوع', + day: 'يوم', + list: 'أجندة', + }, + weekText: 'أسبوع', + allDayText: 'اليوم كله', + moreLinkText: 'أخرى', + noEventsText: 'أي أحداث لعرض', + }; + + var l3 = { + code: 'ar-ly', + week: { + dow: 6, // Saturday is the first day of the week. + doy: 12, // The week that contains Jan 1st is the first week of the year. + }, + direction: 'rtl', + buttonText: { + prev: 'السابق', + next: 'التالي', + today: 'اليوم', + month: 'شهر', + week: 'أسبوع', + day: 'يوم', + list: 'أجندة', + }, + weekText: 'أسبوع', + allDayText: 'اليوم كله', + moreLinkText: 'أخرى', + noEventsText: 'أي أحداث لعرض', + }; + + var l4 = { + code: 'ar-ma', + week: { + dow: 6, // Saturday is the first day of the week. + doy: 12, // The week that contains Jan 1st is the first week of the year. + }, + direction: 'rtl', + buttonText: { + prev: 'السابق', + next: 'التالي', + today: 'اليوم', + month: 'شهر', + week: 'أسبوع', + day: 'يوم', + list: 'أجندة', + }, + weekText: 'أسبوع', + allDayText: 'اليوم كله', + moreLinkText: 'أخرى', + noEventsText: 'أي أحداث لعرض', + }; + + var l5 = { + code: 'ar-sa', + week: { + dow: 0, // Sunday is the first day of the week. + doy: 6, // The week that contains Jan 1st is the first week of the year. + }, + direction: 'rtl', + buttonText: { + prev: 'السابق', + next: 'التالي', + today: 'اليوم', + month: 'شهر', + week: 'أسبوع', + day: 'يوم', + list: 'أجندة', + }, + weekText: 'أسبوع', + allDayText: 'اليوم كله', + moreLinkText: 'أخرى', + noEventsText: 'أي أحداث لعرض', + }; + + var l6 = { + code: 'ar-tn', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + direction: 'rtl', + buttonText: { + prev: 'السابق', + next: 'التالي', + today: 'اليوم', + month: 'شهر', + week: 'أسبوع', + day: 'يوم', + list: 'أجندة', + }, + weekText: 'أسبوع', + allDayText: 'اليوم كله', + moreLinkText: 'أخرى', + noEventsText: 'أي أحداث لعرض', + }; + + var l7 = { + code: 'ar', + week: { + dow: 6, // Saturday is the first day of the week. + doy: 12, // The week that contains Jan 1st is the first week of the year. + }, + direction: 'rtl', + buttonText: { + prev: 'السابق', + next: 'التالي', + today: 'اليوم', + month: 'شهر', + week: 'أسبوع', + day: 'يوم', + list: 'أجندة', + }, + weekText: 'أسبوع', + allDayText: 'اليوم كله', + moreLinkText: 'أخرى', + noEventsText: 'أي أحداث لعرض', + }; + + var l8 = { + code: 'az', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + buttonText: { + prev: 'Əvvəl', + next: 'Sonra', + today: 'Bu Gün', + month: 'Ay', + week: 'Həftə', + day: 'Gün', + list: 'Gündəm', + }, + weekText: 'Həftə', + allDayText: 'Bütün Gün', + moreLinkText: function(n) { + return '+ daha çox ' + n + }, + noEventsText: 'Göstərmək üçün hadisə yoxdur', + }; + + var l9 = { + code: 'bg', + week: { + dow: 1, // Monday is the first day of the week. + doy: 7, // The week that contains Jan 1st is the first week of the year. + }, + buttonText: { + prev: 'назад', + next: 'напред', + today: 'днес', + month: 'Месец', + week: 'Седмица', + day: 'Ден', + list: 'График', + }, + allDayText: 'Цял ден', + moreLinkText: function(n) { + return '+още ' + n + }, + noEventsText: 'Няма събития за показване', + }; + + var l10 = { + code: 'bn', + week: { + dow: 0, // Sunday is the first day of the week. + doy: 6, // The week that contains Jan 1st is the first week of the year. + }, + buttonText: { + prev: 'পেছনে', + next: 'সামনে', + today: 'আজ', + month: 'মাস', + week: 'সপ্তাহ', + day: 'দিন', + list: 'তালিকা', + }, + weekText: 'সপ্তাহ', + allDayText: 'সারাদিন', + moreLinkText: function(n) { + return '+অন্যান্য ' + n + }, + noEventsText: 'কোনো ইভেন্ট নেই', + }; + + var l11 = { + code: 'bs', + week: { + dow: 1, // Monday is the first day of the week. + doy: 7, // The week that contains Jan 1st is the first week of the year. + }, + buttonText: { + prev: 'Prošli', + next: 'Sljedeći', + today: 'Danas', + month: 'Mjesec', + week: 'Sedmica', + day: 'Dan', + list: 'Raspored', + }, + weekText: 'Sed', + allDayText: 'Cijeli dan', + moreLinkText: function(n) { + return '+ još ' + n + }, + noEventsText: 'Nema događaja za prikazivanje', + }; + + var l12 = { + code: 'ca', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + buttonText: { + prev: 'Anterior', + next: 'Següent', + today: 'Avui', + month: 'Mes', + week: 'Setmana', + day: 'Dia', + list: 'Agenda', + }, + weekText: 'Set', + allDayText: 'Tot el dia', + moreLinkText: 'més', + noEventsText: 'No hi ha esdeveniments per mostrar', + }; + + var l13 = { + code: 'cs', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + buttonText: { + prev: 'Dříve', + next: 'Později', + today: 'Nyní', + month: 'Měsíc', + week: 'Týden', + day: 'Den', + list: 'Agenda', + }, + weekText: 'Týd', + allDayText: 'Celý den', + moreLinkText: function(n) { + return '+další: ' + n + }, + noEventsText: 'Žádné akce k zobrazení', + }; + + var l14 = { + code: 'cy', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + buttonText: { + prev: 'Blaenorol', + next: 'Nesaf', + today: 'Heddiw', + year: 'Blwyddyn', + month: 'Mis', + week: 'Wythnos', + day: 'Dydd', + list: 'Rhestr', + }, + weekText: 'Wythnos', + allDayText: 'Trwy\'r dydd', + moreLinkText: 'Mwy', + noEventsText: 'Dim digwyddiadau', + }; + + var l15 = { + code: 'da', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + buttonText: { + prev: 'Forrige', + next: 'Næste', + today: 'I dag', + month: 'Måned', + week: 'Uge', + day: 'Dag', + list: 'Agenda', + }, + weekText: 'Uge', + allDayText: 'Hele dagen', + moreLinkText: 'flere', + noEventsText: 'Ingen arrangementer at vise', + }; + + var l16 = { + code: 'de-at', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + buttonText: { + prev: 'Zurück', + next: 'Vor', + today: 'Heute', + year: 'Jahr', + month: 'Monat', + week: 'Woche', + day: 'Tag', + list: 'Terminübersicht', + }, + weekText: 'KW', + allDayText: 'Ganztägig', + moreLinkText: function(n) { + return '+ weitere ' + n + }, + noEventsText: 'Keine Ereignisse anzuzeigen', + }; + + var l17 = { + code: 'de', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + buttonText: { + prev: 'Zurück', + next: 'Vor', + today: 'Heute', + year: 'Jahr', + month: 'Monat', + week: 'Woche', + day: 'Tag', + list: 'Terminübersicht', + }, + weekText: 'KW', + allDayText: 'Ganztägig', + moreLinkText: function(n) { + return '+ weitere ' + n + }, + noEventsText: 'Keine Ereignisse anzuzeigen', + }; + + var l18 = { + code: 'el', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4st is the first week of the year. + }, + buttonText: { + prev: 'Προηγούμενος', + next: 'Επόμενος', + today: 'Σήμερα', + month: 'Μήνας', + week: 'Εβδομάδα', + day: 'Ημέρα', + list: 'Ατζέντα', + }, + weekText: 'Εβδ', + allDayText: 'Ολοήμερο', + moreLinkText: 'περισσότερα', + noEventsText: 'Δεν υπάρχουν γεγονότα προς εμφάνιση', + }; + + var l19 = { + code: 'en-au', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + }; + + var l20 = { + code: 'en-gb', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + }; + + var l21 = { + code: 'en-nz', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + }; + + var l22 = { + code: 'eo', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + buttonText: { + prev: 'Antaŭa', + next: 'Sekva', + today: 'Hodiaŭ', + month: 'Monato', + week: 'Semajno', + day: 'Tago', + list: 'Tagordo', + }, + weekText: 'Sm', + allDayText: 'Tuta tago', + moreLinkText: 'pli', + noEventsText: 'Neniuj eventoj por montri', + }; + + var l23 = { + code: 'es', + week: { + dow: 0, // Sunday is the first day of the week. + doy: 6, // The week that contains Jan 1st is the first week of the year. + }, + buttonText: { + prev: 'Ant', + next: 'Sig', + today: 'Hoy', + month: 'Mes', + week: 'Semana', + day: 'Día', + list: 'Agenda', + }, + weekText: 'Sm', + allDayText: 'Todo el día', + moreLinkText: 'más', + noEventsText: 'No hay eventos para mostrar', + }; + + var l24 = { + code: 'es', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + buttonText: { + prev: 'Ant', + next: 'Sig', + today: 'Hoy', + month: 'Mes', + week: 'Semana', + day: 'Día', + list: 'Agenda', + }, + weekText: 'Sm', + allDayText: 'Todo el día', + moreLinkText: 'más', + noEventsText: 'No hay eventos para mostrar', + }; + + var l25 = { + code: 'et', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + buttonText: { + prev: 'Eelnev', + next: 'Järgnev', + today: 'Täna', + month: 'Kuu', + week: 'Nädal', + day: 'Päev', + list: 'Päevakord', + }, + weekText: 'näd', + allDayText: 'Kogu päev', + moreLinkText: function(n) { + return '+ veel ' + n + }, + noEventsText: 'Kuvamiseks puuduvad sündmused', + }; + + var l26 = { + code: 'eu', + week: { + dow: 1, // Monday is the first day of the week. + doy: 7, // The week that contains Jan 1st is the first week of the year. + }, + buttonText: { + prev: 'Aur', + next: 'Hur', + today: 'Gaur', + month: 'Hilabetea', + week: 'Astea', + day: 'Eguna', + list: 'Agenda', + }, + weekText: 'As', + allDayText: 'Egun osoa', + moreLinkText: 'gehiago', + noEventsText: 'Ez dago ekitaldirik erakusteko', + }; + + var l27 = { + code: 'fa', + week: { + dow: 6, // Saturday is the first day of the week. + doy: 12, // The week that contains Jan 1st is the first week of the year. + }, + direction: 'rtl', + buttonText: { + prev: 'قبلی', + next: 'بعدی', + today: 'امروز', + month: 'ماه', + week: 'هفته', + day: 'روز', + list: 'برنامه', + }, + weekText: 'هف', + allDayText: 'تمام روز', + moreLinkText: function(n) { + return 'بیش از ' + n + }, + noEventsText: 'هیچ رویدادی به نمایش', + }; + + var l28 = { + code: 'fi', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + buttonText: { + prev: 'Edellinen', + next: 'Seuraava', + today: 'Tänään', + month: 'Kuukausi', + week: 'Viikko', + day: 'Päivä', + list: 'Tapahtumat', + }, + weekText: 'Vk', + allDayText: 'Koko päivä', + moreLinkText: 'lisää', + noEventsText: 'Ei näytettäviä tapahtumia', + }; + + var l29 = { + code: 'fr', + buttonText: { + prev: 'Précédent', + next: 'Suivant', + today: "Aujourd'hui", + year: 'Année', + month: 'Mois', + week: 'Semaine', + day: 'Jour', + list: 'Mon planning', + }, + weekText: 'Sem.', + allDayText: 'Toute la journée', + moreLinkText: 'en plus', + noEventsText: 'Aucun événement à afficher', + }; + + var l30 = { + code: 'fr-ch', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + buttonText: { + prev: 'Précédent', + next: 'Suivant', + today: 'Courant', + year: 'Année', + month: 'Mois', + week: 'Semaine', + day: 'Jour', + list: 'Mon planning', + }, + weekText: 'Sm', + allDayText: 'Toute la journée', + moreLinkText: 'en plus', + noEventsText: 'Aucun événement à afficher', + }; + + var l31 = { + code: 'fr', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + buttonText: { + prev: 'Précédent', + next: 'Suivant', + today: "Aujourd'hui", + year: 'Année', + month: 'Mois', + week: 'Semaine', + day: 'Jour', + list: 'Planning', + }, + weekText: 'Sem.', + allDayText: 'Toute la journée', + moreLinkText: 'en plus', + noEventsText: 'Aucun événement à afficher', + }; + + var l32 = { + code: 'gl', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + buttonText: { + prev: 'Ant', + next: 'Seg', + today: 'Hoxe', + month: 'Mes', + week: 'Semana', + day: 'Día', + list: 'Axenda', + }, + weekText: 'Sm', + allDayText: 'Todo o día', + moreLinkText: 'máis', + noEventsText: 'Non hai eventos para amosar', + }; + + var l33 = { + code: 'he', + direction: 'rtl', + buttonText: { + prev: 'הקודם', + next: 'הבא', + today: 'היום', + month: 'חודש', + week: 'שבוע', + day: 'יום', + list: 'סדר יום', + }, + allDayText: 'כל היום', + moreLinkText: 'אחר', + noEventsText: 'אין אירועים להצגה', + weekText: 'שבוע', + }; + + var l34 = { + code: 'hi', + week: { + dow: 0, // Sunday is the first day of the week. + doy: 6, // The week that contains Jan 1st is the first week of the year. + }, + buttonText: { + prev: 'पिछला', + next: 'अगला', + today: 'आज', + month: 'महीना', + week: 'सप्ताह', + day: 'दिन', + list: 'कार्यसूची', + }, + weekText: 'हफ्ता', + allDayText: 'सभी दिन', + moreLinkText: function(n) { + return '+अधिक ' + n + }, + noEventsText: 'कोई घटनाओं को प्रदर्शित करने के लिए', + }; + + var l35 = { + code: 'hr', + week: { + dow: 1, // Monday is the first day of the week. + doy: 7, // The week that contains Jan 1st is the first week of the year. + }, + buttonText: { + prev: 'Prijašnji', + next: 'Sljedeći', + today: 'Danas', + month: 'Mjesec', + week: 'Tjedan', + day: 'Dan', + list: 'Raspored', + }, + weekText: 'Tje', + allDayText: 'Cijeli dan', + moreLinkText: function(n) { + return '+ još ' + n + }, + noEventsText: 'Nema događaja za prikaz', + }; + + var l36 = { + code: 'hu', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + buttonText: { + prev: 'vissza', + next: 'előre', + today: 'ma', + month: 'Hónap', + week: 'Hét', + day: 'Nap', + list: 'Lista', + }, + weekText: 'Hét', + allDayText: 'Egész nap', + moreLinkText: 'további', + noEventsText: 'Nincs megjeleníthető esemény', + }; + + var l37 = { + code: 'hy-am', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + buttonText: { + prev: 'Նախորդ', + next: 'Հաջորդ', + today: 'Այսօր', + month: 'Ամիս', + week: 'Շաբաթ', + day: 'Օր', + list: 'Օրվա ցուցակ', + }, + weekText: 'Շաբ', + allDayText: 'Ամբողջ օր', + moreLinkText: function(n) { + return '+ ևս ' + n + }, + noEventsText: 'Բացակայում է իրադարձությունը ցուցադրելու', + }; + + var l38 = { + code: 'id', + week: { + dow: 1, // Monday is the first day of the week. + doy: 7, // The week that contains Jan 1st is the first week of the year. + }, + buttonText: { + prev: 'mundur', + next: 'maju', + today: 'hari ini', + month: 'Bulan', + week: 'Minggu', + day: 'Hari', + list: 'Agenda', + }, + weekText: 'Mg', + allDayText: 'Sehari penuh', + moreLinkText: 'lebih', + noEventsText: 'Tidak ada acara untuk ditampilkan', + }; + + var l39 = { + code: 'is', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + buttonText: { + prev: 'Fyrri', + next: 'Næsti', + today: 'Í dag', + month: 'Mánuður', + week: 'Vika', + day: 'Dagur', + list: 'Dagskrá', + }, + weekText: 'Vika', + allDayText: 'Allan daginn', + moreLinkText: 'meira', + noEventsText: 'Engir viðburðir til að sýna', + }; + + var l40 = { + code: 'it', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + buttonText: { + prev: 'Prec', + next: 'Succ', + today: 'Oggi', + month: 'Mese', + week: 'Settimana', + day: 'Giorno', + list: 'Agenda', + }, + weekText: 'Sm', + allDayText: 'Tutto il giorno', + moreLinkText: function(n) { + return '+altri ' + n + }, + noEventsText: 'Non ci sono eventi da visualizzare', + }; + + var l41 = { + code: 'ja', + buttonText: { + prev: '前', + next: '次', + today: '今日', + month: '月', + week: '週', + day: '日', + list: '予定リスト', + }, + weekText: '週', + allDayText: '終日', + moreLinkText: function(n) { + return '他 ' + n + ' 件' + }, + noEventsText: '表示する予定はありません', + }; + + var l42 = { + code: 'ka', + week: { + dow: 1, + doy: 7, + }, + buttonText: { + prev: 'წინა', + next: 'შემდეგი', + today: 'დღეს', + month: 'თვე', + week: 'კვირა', + day: 'დღე', + list: 'დღის წესრიგი', + }, + weekText: 'კვ', + allDayText: 'მთელი დღე', + moreLinkText: function(n) { + return '+ კიდევ ' + n + }, + noEventsText: 'ღონისძიებები არ არის', + }; + + var l43 = { + code: 'kk', + week: { + dow: 1, // Monday is the first day of the week. + doy: 7, // The week that contains Jan 1st is the first week of the year. + }, + buttonText: { + prev: 'Алдыңғы', + next: 'Келесі', + today: 'Бүгін', + month: 'Ай', + week: 'Апта', + day: 'Күн', + list: 'Күн тәртібі', + }, + weekText: 'Не', + allDayText: 'Күні бойы', + moreLinkText: function(n) { + return '+ тағы ' + n + }, + noEventsText: 'Көрсету үшін оқиғалар жоқ', + }; + + var l44 = { + code: 'km', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + buttonText: { + prev: 'មុន', + next: 'បន្ទាប់', + today: 'ថ្ងៃនេះ', + year: 'ឆ្នាំ', + month: 'ខែ', + week: 'សប្តាហ៍', + day: 'ថ្ងៃ', + list: 'បញ្ជី', + }, + weekText: 'សប្តាហ៍', + allDayText: 'ពេញមួយថ្ងៃ', + moreLinkText: 'ច្រើនទៀត', + noEventsText: 'គ្មានព្រឹត្តិការណ៍ត្រូវបង្ហាញ', + }; + + var l45 = { + code: 'ko', + buttonText: { + prev: '이전달', + next: '다음달', + today: '오늘', + month: '월', + week: '주', + day: '일', + list: '일정목록', + }, + weekText: '주', + allDayText: '종일', + moreLinkText: '개', + noEventsText: '일정이 없습니다', + }; + + var l46 = { + code: 'ku', + week: { + dow: 6, // Saturday is the first day of the week. + doy: 12, // The week that contains Jan 1st is the first week of the year. + }, + direction: 'rtl', + buttonText: { + prev: 'پێشتر', + next: 'دواتر', + today: 'ئەمڕو', + month: 'مانگ', + week: 'هەفتە', + day: 'ڕۆژ', + list: 'بەرنامە', + }, + weekText: 'هەفتە', + allDayText: 'هەموو ڕۆژەکە', + moreLinkText: 'زیاتر', + noEventsText: 'هیچ ڕووداوێك نیە', + }; + + var l47 = { + code: 'lb', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + buttonText: { + prev: 'Zréck', + next: 'Weider', + today: 'Haut', + month: 'Mount', + week: 'Woch', + day: 'Dag', + list: 'Terminiwwersiicht', + }, + weekText: 'W', + allDayText: 'Ganzen Dag', + moreLinkText: 'méi', + noEventsText: 'Nee Evenementer ze affichéieren', + }; + + var l48 = { + code: 'lt', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + buttonText: { + prev: 'Atgal', + next: 'Pirmyn', + today: 'Šiandien', + month: 'Mėnuo', + week: 'Savaitė', + day: 'Diena', + list: 'Darbotvarkė', + }, + weekText: 'SAV', + allDayText: 'Visą dieną', + moreLinkText: 'daugiau', + noEventsText: 'Nėra įvykių rodyti', + }; + + var l49 = { + code: 'lv', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + buttonText: { + prev: 'Iepr.', + next: 'Nāk.', + today: 'Šodien', + month: 'Mēnesis', + week: 'Nedēļa', + day: 'Diena', + list: 'Dienas kārtība', + }, + weekText: 'Ned.', + allDayText: 'Visu dienu', + moreLinkText: function(n) { + return '+vēl ' + n + }, + noEventsText: 'Nav notikumu', + }; + + var l50 = { + code: 'mk', + buttonText: { + prev: 'претходно', + next: 'следно', + today: 'Денес', + month: 'Месец', + week: 'Недела', + day: 'Ден', + list: 'График', + }, + weekText: 'Сед', + allDayText: 'Цел ден', + moreLinkText: function(n) { + return '+повеќе ' + n + }, + noEventsText: 'Нема настани за прикажување', + }; + + var l51 = { + code: 'ms', + week: { + dow: 1, // Monday is the first day of the week. + doy: 7, // The week that contains Jan 1st is the first week of the year. + }, + buttonText: { + prev: 'Sebelum', + next: 'Selepas', + today: 'hari ini', + month: 'Bulan', + week: 'Minggu', + day: 'Hari', + list: 'Agenda', + }, + weekText: 'Mg', + allDayText: 'Sepanjang hari', + moreLinkText: function(n) { + return 'masih ada ' + n + ' acara' + }, + noEventsText: 'Tiada peristiwa untuk dipaparkan', + }; + + var l52 = { + code: 'nb', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + buttonText: { + prev: 'Forrige', + next: 'Neste', + today: 'I dag', + month: 'Måned', + week: 'Uke', + day: 'Dag', + list: 'Agenda', + }, + weekText: 'Uke', + allDayText: 'Hele dagen', + moreLinkText: 'til', + noEventsText: 'Ingen hendelser å vise', + }; + + var l53 = { + code: 'ne', // code for nepal + week: { + dow: 7, // Sunday is the first day of the week. + doy: 1, // The week that contains Jan 1st is the first week of the year. + }, + buttonText: { + prev: 'अघिल्लो', + next: 'अर्को', + today: 'आज', + month: 'महिना', + week: 'हप्ता', + day: 'दिन', + list: 'सूची', + }, + weekText: 'हप्ता', + allDayText: 'दिनभरि', + moreLinkText: 'थप लिंक', + noEventsText: 'देखाउनको लागि कुनै घटनाहरू छैनन्', + }; + + var l54 = { + code: 'nl', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + buttonText: { + prev: 'Vorige', + next: 'Volgende', + today: 'Vandaag', + year: 'Jaar', + month: 'Maand', + week: 'Week', + day: 'Dag', + list: 'Agenda', + }, + allDayText: 'Hele dag', + moreLinkText: 'extra', + noEventsText: 'Geen evenementen om te laten zien', + }; + + var l55 = { + code: 'nn', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + buttonText: { + prev: 'Førre', + next: 'Neste', + today: 'I dag', + month: 'Månad', + week: 'Veke', + day: 'Dag', + list: 'Agenda', + }, + weekText: 'Veke', + allDayText: 'Heile dagen', + moreLinkText: 'til', + noEventsText: 'Ingen hendelser å vise', + }; + + var l56 = { + code: 'pl', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + buttonText: { + prev: 'Poprzedni', + next: 'Następny', + today: 'Dziś', + month: 'Miesiąc', + week: 'Tydzień', + day: 'Dzień', + list: 'Plan dnia', + }, + weekText: 'Tydz', + allDayText: 'Cały dzień', + moreLinkText: 'więcej', + noEventsText: 'Brak wydarzeń do wyświetlenia', + }; + + var l57 = { + code: 'pt-br', + buttonText: { + prev: 'Anterior', + next: 'Próximo', + today: 'Hoje', + month: 'Mês', + week: 'Semana', + day: 'Dia', + list: 'Lista', + }, + weekText: 'Sm', + allDayText: 'dia inteiro', + moreLinkText: function(n) { + return 'mais +' + n + }, + noEventsText: 'Não há eventos para mostrar', + }; + + var l58 = { + code: 'pt', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + buttonText: { + prev: 'Anterior', + next: 'Seguinte', + today: 'Hoje', + month: 'Mês', + week: 'Semana', + day: 'Dia', + list: 'Agenda', + }, + weekText: 'Sem', + allDayText: 'Todo o dia', + moreLinkText: 'mais', + noEventsText: 'Não há eventos para mostrar', + }; + + var l59 = { + code: 'ro', + week: { + dow: 1, // Monday is the first day of the week. + doy: 7, // The week that contains Jan 1st is the first week of the year. + }, + buttonText: { + prev: 'precedentă', + next: 'următoare', + today: 'Azi', + month: 'Lună', + week: 'Săptămână', + day: 'Zi', + list: 'Agendă', + }, + weekText: 'Săpt', + allDayText: 'Toată ziua', + moreLinkText: function(n) { + return '+alte ' + n + }, + noEventsText: 'Nu există evenimente de afișat', + }; + + var l60 = { + code: 'ru', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + buttonText: { + prev: 'Пред', + next: 'След', + today: 'Сегодня', + month: 'Месяц', + week: 'Неделя', + day: 'День', + list: 'Повестка дня', + }, + weekText: 'Нед', + allDayText: 'Весь день', + moreLinkText: function(n) { + return '+ ещё ' + n + }, + noEventsText: 'Нет событий для отображения', + }; + + var l61 = { + code: 'sk', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + buttonText: { + prev: 'Predchádzajúci', + next: 'Nasledujúci', + today: 'Dnes', + month: 'Mesiac', + week: 'Týždeň', + day: 'Deň', + list: 'Rozvrh', + }, + weekText: 'Ty', + allDayText: 'Celý deň', + moreLinkText: function(n) { + return '+ďalšie: ' + n + }, + noEventsText: 'Žiadne akcie na zobrazenie', + }; + + var l62 = { + code: 'sl', + week: { + dow: 1, // Monday is the first day of the week. + doy: 7, // The week that contains Jan 1st is the first week of the year. + }, + buttonText: { + prev: 'Prejšnji', + next: 'Naslednji', + today: 'Trenutni', + month: 'Mesec', + week: 'Teden', + day: 'Dan', + list: 'Dnevni red', + }, + weekText: 'Teden', + allDayText: 'Ves dan', + moreLinkText: 'več', + noEventsText: 'Ni dogodkov za prikaz', + }; + + var l63 = { + code: 'sm', + buttonText: { + prev: 'Talu ai', + next: 'Mulimuli atu', + today: 'Aso nei', + month: 'Masina', + week: 'Vaiaso', + day: 'Aso', + list: 'Faasologa', + }, + weekText: 'Vaiaso', + allDayText: 'Aso atoa', + moreLinkText: 'sili atu', + noEventsText: 'Leai ni mea na tutupu', + }; + + var l64 = { + code: 'sq', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + buttonText: { + prev: 'mbrapa', + next: 'Përpara', + today: 'sot', + month: 'Muaj', + week: 'Javë', + day: 'Ditë', + list: 'Listë', + }, + weekText: 'Ja', + allDayText: 'Gjithë ditën', + moreLinkText: function(n) { + return '+më tepër ' + n + }, + noEventsText: 'Nuk ka evente për të shfaqur', + }; + + var l65 = { + code: 'sr-cyrl', + week: { + dow: 1, // Monday is the first day of the week. + doy: 7, // The week that contains Jan 1st is the first week of the year. + }, + buttonText: { + prev: 'Претходна', + next: 'следећи', + today: 'Данас', + month: 'Месец', + week: 'Недеља', + day: 'Дан', + list: 'Планер', + }, + weekText: 'Сед', + allDayText: 'Цео дан', + moreLinkText: function(n) { + return '+ још ' + n + }, + noEventsText: 'Нема догађаја за приказ', + }; + + var l66 = { + code: 'sr', + week: { + dow: 1, // Monday is the first day of the week. + doy: 7, // The week that contains Jan 1st is the first week of the year. + }, + buttonText: { + prev: 'Prethodna', + next: 'Sledeći', + today: 'Danas', + month: 'Mеsеc', + week: 'Nеdеlja', + day: 'Dan', + list: 'Planеr', + }, + weekText: 'Sed', + allDayText: 'Cеo dan', + moreLinkText: function(n) { + return '+ još ' + n + }, + noEventsText: 'Nеma događaja za prikaz', + }; + + var l67 = { + code: 'sv', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + buttonText: { + prev: 'Förra', + next: 'Nästa', + today: 'Idag', + month: 'Månad', + week: 'Vecka', + day: 'Dag', + list: 'Program', + }, + weekText: 'v.', + allDayText: 'Heldag', + moreLinkText: 'till', + noEventsText: 'Inga händelser att visa', + }; + + var l68 = { + code: 'ta-in', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + buttonText: { + prev: 'முந்தைய', + next: 'அடுத்தது', + today: 'இன்று', + month: 'மாதம்', + week: 'வாரம்', + day: 'நாள்', + list: 'தினசரி அட்டவணை', + }, + weekText: 'வாரம்', + allDayText: 'நாள் முழுவதும்', + moreLinkText: function(n) { + return '+ மேலும் ' + n + }, + noEventsText: 'காண்பிக்க நிகழ்வுகள் இல்லை', + }; + + var l69 = { + code: 'th', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + buttonText: { + prev: 'ก่อนหน้า', + next: 'ถัดไป', + prevYear: 'ปีก่อนหน้า', + nextYear: 'ปีถัดไป', + year: 'ปี', + today: 'วันนี้', + month: 'เดือน', + week: 'สัปดาห์', + day: 'วัน', + list: 'กำหนดการ', + }, + weekText: 'สัปดาห์', + allDayText: 'ตลอดวัน', + moreLinkText: 'เพิ่มเติม', + noEventsText: 'ไม่มีกิจกรรมที่จะแสดง', + }; + + var l70 = { + code: 'tr', + week: { + dow: 1, // Monday is the first day of the week. + doy: 7, // The week that contains Jan 1st is the first week of the year. + }, + buttonText: { + prev: 'geri', + next: 'ileri', + today: 'bugün', + month: 'Ay', + week: 'Hafta', + day: 'Gün', + list: 'Ajanda', + }, + weekText: 'Hf', + allDayText: 'Tüm gün', + moreLinkText: 'daha fazla', + noEventsText: 'Gösterilecek etkinlik yok', + }; + + var l71 = { + code: 'ug', + buttonText: { + month: 'ئاي', + week: 'ھەپتە', + day: 'كۈن', + list: 'كۈنتەرتىپ', + }, + allDayText: 'پۈتۈن كۈن', + }; + + var l72 = { + code: 'uk', + week: { + dow: 1, // Monday is the first day of the week. + doy: 7, // The week that contains Jan 1st is the first week of the year. + }, + buttonText: { + prev: 'Попередній', + next: 'далі', + today: 'Сьогодні', + month: 'Місяць', + week: 'Тиждень', + day: 'День', + list: 'Порядок денний', + }, + weekText: 'Тиж', + allDayText: 'Увесь день', + moreLinkText: function(n) { + return '+ще ' + n + '...' + }, + noEventsText: 'Немає подій для відображення', + }; + + var l73 = { + code: 'uz', + buttonText: { + month: 'Oy', + week: 'Xafta', + day: 'Kun', + list: 'Kun tartibi', + }, + allDayText: "Kun bo'yi", + moreLinkText: function(n) { + return '+ yana ' + n + }, + noEventsText: "Ko'rsatish uchun voqealar yo'q", + }; + + var l74 = { + code: 'vi', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + buttonText: { + prev: 'Trước', + next: 'Tiếp', + today: 'Hôm nay', + month: 'Tháng', + week: 'Tuần', + day: 'Ngày', + list: 'Lịch biểu', + }, + weekText: 'Tu', + allDayText: 'Cả ngày', + moreLinkText: function(n) { + return '+ thêm ' + n + }, + noEventsText: 'Không có sự kiện để hiển thị', + }; + + var l75 = { + code: 'zh-cn', + week: { + // GB/T 7408-1994《数据元和交换格式·信息交换·日期和时间表示法》与ISO 8601:1988等效 + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + buttonText: { + prev: '上月', + next: '下月', + today: '今天', + month: '月', + week: '周', + day: '日', + list: '日程', + }, + weekText: '周', + allDayText: '全天', + moreLinkText: function(n) { + return '另外 ' + n + ' 个' + }, + noEventsText: '没有事件显示', + }; + + var l76 = { + code: 'zh-tw', + buttonText: { + prev: '上月', + next: '下月', + today: '今天', + month: '月', + week: '週', + day: '天', + list: '活動列表', + }, + weekText: '周', + allDayText: '整天', + moreLinkText: '顯示更多', + noEventsText: '没有任何活動', + }; + + /* eslint max-len: off */ + + var localesAll = [ + l0, l1, l2, l3, l4, l5, l6, l7, l8, l9, l10, l11, l12, l13, l14, l15, l16, l17, l18, l19, l20, l21, l22, l23, l24, l25, l26, l27, l28, l29, l30, l31, l32, l33, l34, l35, l36, l37, l38, l39, l40, l41, l42, l43, l44, l45, l46, l47, l48, l49, l50, l51, l52, l53, l54, l55, l56, l57, l58, l59, l60, l61, l62, l63, l64, l65, l66, l67, l68, l69, l70, l71, l72, l73, l74, l75, l76, + ]; + + return localesAll; + +}()); diff --git a/apps/schoolCalender/fullcalendar/main.css b/apps/schoolCalender/fullcalendar/main.css new file mode 100644 index 000000000..957887b56 --- /dev/null +++ b/apps/schoolCalender/fullcalendar/main.css @@ -0,0 +1,1446 @@ + +/* classes attached to */ +/* TODO: make fc-event selector work when calender in shadow DOM */ +.fc-not-allowed, +.fc-not-allowed .fc-event { /* override events' custom cursors */ + cursor: not-allowed; +} + +/* TODO: not attached to body. attached to specific els. move */ +.fc-unselectable { + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + -webkit-touch-callout: none; + -webkit-tap-highlight-color: rgba(0, 0, 0, 0); +} +.fc { + /* layout of immediate children */ + display: flex; + flex-direction: column; + + font-size: 1em +} +.fc, + .fc *, + .fc *:before, + .fc *:after { + box-sizing: border-box; + } +.fc table { + border-collapse: collapse; + border-spacing: 0; + font-size: 1em; /* normalize cross-browser */ + } +.fc th { + text-align: center; + } +.fc th, + .fc td { + vertical-align: top; + padding: 0; + } +.fc a[data-navlink] { + cursor: pointer; + } +.fc a[data-navlink]:hover { + text-decoration: underline; + } +.fc-direction-ltr { + direction: ltr; + text-align: left; +} +.fc-direction-rtl { + direction: rtl; + text-align: right; +} +.fc-theme-standard td, + .fc-theme-standard th { + border: 1px solid #ddd; + border: 1px solid var(--fc-border-color, #ddd); + } +/* for FF, which doesn't expand a 100% div within a table cell. use absolute positioning */ +/* inner-wrappers are responsible for being absolute */ +/* TODO: best place for this? */ +.fc-liquid-hack td, + .fc-liquid-hack th { + position: relative; + } + +@font-face { + font-family: 'fcicons'; + src: url("data:application/x-font-ttf;charset=utf-8;base64,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") format('truetype'); + font-weight: normal; + font-style: normal; +} + +.fc-icon { + /* added for fc */ + display: inline-block; + width: 1em; + height: 1em; + text-align: center; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + + /* use !important to prevent issues with browser extensions that change fonts */ + font-family: 'fcicons' !important; + speak: none; + font-style: normal; + font-weight: normal; + font-variant: normal; + text-transform: none; + line-height: 1; + + /* Better Font Rendering =========== */ + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +.fc-icon-chevron-left:before { + content: "\e900"; +} + +.fc-icon-chevron-right:before { + content: "\e901"; +} + +.fc-icon-chevrons-left:before { + content: "\e902"; +} + +.fc-icon-chevrons-right:before { + content: "\e903"; +} + +.fc-icon-minus-square:before { + content: "\e904"; +} + +.fc-icon-plus-square:before { + content: "\e905"; +} + +.fc-icon-x:before { + content: "\e906"; +} +/* +Lots taken from Flatly (MIT): https://bootswatch.com/4/flatly/bootstrap.css + +These styles only apply when the standard-theme is activated. +When it's NOT activated, the fc-button classes won't even be in the DOM. +*/ +.fc { + + /* reset */ + +} +.fc .fc-button { + border-radius: 0; + overflow: visible; + text-transform: none; + margin: 0; + font-family: inherit; + font-size: inherit; + line-height: inherit; + } +.fc .fc-button:focus { + outline: 1px dotted; + outline: 5px auto -webkit-focus-ring-color; + } +.fc .fc-button { + -webkit-appearance: button; + } +.fc .fc-button:not(:disabled) { + cursor: pointer; + } +.fc .fc-button::-moz-focus-inner { + padding: 0; + border-style: none; + } +.fc { + + /* theme */ + +} +.fc .fc-button { + display: inline-block; + font-weight: 400; + text-align: center; + vertical-align: middle; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + background-color: transparent; + border: 1px solid transparent; + padding: 0.4em 0.65em; + font-size: 1em; + line-height: 1.5; + border-radius: 0.25em; + } +.fc .fc-button:hover { + text-decoration: none; + } +.fc .fc-button:focus { + outline: 0; + box-shadow: 0 0 0 0.2rem rgba(44, 62, 80, 0.25); + } +.fc .fc-button:disabled { + opacity: 0.65; + } +.fc { + + /* "primary" coloring */ + +} +.fc .fc-button-primary { + color: #fff; + color: var(--fc-button-text-color, #fff); + background-color: #2C3E50; + background-color: var(--fc-button-bg-color, #2C3E50); + border-color: #2C3E50; + border-color: var(--fc-button-border-color, #2C3E50); + } +.fc .fc-button-primary:hover { + color: #fff; + color: var(--fc-button-text-color, #fff); + background-color: #1e2b37; + background-color: var(--fc-button-hover-bg-color, #1e2b37); + border-color: #1a252f; + border-color: var(--fc-button-hover-border-color, #1a252f); + } +.fc .fc-button-primary:disabled { /* not DRY */ + color: #fff; + color: var(--fc-button-text-color, #fff); + background-color: #2C3E50; + background-color: var(--fc-button-bg-color, #2C3E50); + border-color: #2C3E50; + border-color: var(--fc-button-border-color, #2C3E50); /* overrides :hover */ + } +.fc .fc-button-primary:focus { + box-shadow: 0 0 0 0.2rem rgba(76, 91, 106, 0.5); + } +.fc .fc-button-primary:not(:disabled):active, + .fc .fc-button-primary:not(:disabled).fc-button-active { + color: #fff; + color: var(--fc-button-text-color, #fff); + background-color: #1a252f; + background-color: var(--fc-button-active-bg-color, #1a252f); + border-color: #151e27; + border-color: var(--fc-button-active-border-color, #151e27); + } +.fc .fc-button-primary:not(:disabled):active:focus, + .fc .fc-button-primary:not(:disabled).fc-button-active:focus { + box-shadow: 0 0 0 0.2rem rgba(76, 91, 106, 0.5); + } +.fc { + + /* icons within buttons */ + +} +.fc .fc-button .fc-icon { + vertical-align: middle; + font-size: 1.5em; /* bump up the size (but don't make it bigger than line-height of button, which is 1.5em also) */ + } +.fc .fc-button-group { + position: relative; + display: inline-flex; + vertical-align: middle; + } +.fc .fc-button-group > .fc-button { + position: relative; + flex: 1 1 auto; + } +.fc .fc-button-group > .fc-button:hover { + z-index: 1; + } +.fc .fc-button-group > .fc-button:focus, + .fc .fc-button-group > .fc-button:active, + .fc .fc-button-group > .fc-button.fc-button-active { + z-index: 1; + } +.fc-direction-ltr .fc-button-group > .fc-button:not(:first-child) { + margin-left: -1px; + border-top-left-radius: 0; + border-bottom-left-radius: 0; + } +.fc-direction-ltr .fc-button-group > .fc-button:not(:last-child) { + border-top-right-radius: 0; + border-bottom-right-radius: 0; + } +.fc-direction-rtl .fc-button-group > .fc-button:not(:first-child) { + margin-right: -1px; + border-top-right-radius: 0; + border-bottom-right-radius: 0; + } +.fc-direction-rtl .fc-button-group > .fc-button:not(:last-child) { + border-top-left-radius: 0; + border-bottom-left-radius: 0; + } +.fc .fc-toolbar { + display: flex; + justify-content: space-between; + align-items: center; + } +.fc .fc-toolbar.fc-header-toolbar { + margin-bottom: 1.5em; + } +.fc .fc-toolbar.fc-footer-toolbar { + margin-top: 1.5em; + } +.fc .fc-toolbar-title { + font-size: 1.75em; + margin: 0; + } +.fc-direction-ltr .fc-toolbar > * > :not(:first-child) { + margin-left: .75em; /* space between */ + } +.fc-direction-rtl .fc-toolbar > * > :not(:first-child) { + margin-right: .75em; /* space between */ + } +.fc-direction-rtl .fc-toolbar-ltr { /* when the toolbar-chunk positioning system is explicitly left-to-right */ + flex-direction: row-reverse; + } +.fc .fc-scroller { + -webkit-overflow-scrolling: touch; + position: relative; /* for abs-positioned elements within */ + } +.fc .fc-scroller-liquid { + height: 100%; + } +.fc .fc-scroller-liquid-absolute { + position: absolute; + top: 0; + right: 0; + left: 0; + bottom: 0; + } +.fc .fc-scroller-harness { + position: relative; + overflow: hidden; + direction: ltr; + /* hack for chrome computing the scroller's right/left wrong for rtl. undone below... */ + /* TODO: demonstrate in codepen */ + } +.fc .fc-scroller-harness-liquid { + height: 100%; + } +.fc-direction-rtl .fc-scroller-harness > .fc-scroller { /* undo above hack */ + direction: rtl; + } +.fc-theme-standard .fc-scrollgrid { + border: 1px solid #ddd; + border: 1px solid var(--fc-border-color, #ddd); /* bootstrap does this. match */ + } +.fc .fc-scrollgrid, + .fc .fc-scrollgrid table { /* all tables (self included) */ + width: 100%; /* because tables don't normally do this */ + table-layout: fixed; + } +.fc .fc-scrollgrid table { /* inner tables */ + border-top-style: hidden; + border-left-style: hidden; + border-right-style: hidden; + } +.fc .fc-scrollgrid { + + border-collapse: separate; + border-right-width: 0; + border-bottom-width: 0; + + } +.fc .fc-scrollgrid-liquid { + height: 100%; + } +.fc .fc-scrollgrid-section { /* a */ + height: 1px /* better than 0, for firefox */ + + } +.fc .fc-scrollgrid-section > td { + height: 1px; /* needs a height so inner div within grow. better than 0, for firefox */ + } +.fc .fc-scrollgrid-section table { + height: 1px; + /* for most browsers, if a height isn't set on the table, can't do liquid-height within cells */ + /* serves as a min-height. harmless */ + } +.fc .fc-scrollgrid-section-liquid > td { + height: 100%; /* better than `auto`, for firefox */ + } +.fc .fc-scrollgrid-section > * { + border-top-width: 0; + border-left-width: 0; + } +.fc .fc-scrollgrid-section-header > *, + .fc .fc-scrollgrid-section-footer > * { + border-bottom-width: 0; + } +.fc .fc-scrollgrid-section-body table, + .fc .fc-scrollgrid-section-footer table { + border-bottom-style: hidden; /* head keeps its bottom border tho */ + } +.fc { + + /* stickiness */ + +} +.fc .fc-scrollgrid-section-sticky > * { + background: #fff; + background: var(--fc-page-bg-color, #fff); + position: sticky; + z-index: 3; /* TODO: var */ + /* TODO: box-shadow when sticking */ + } +.fc .fc-scrollgrid-section-header.fc-scrollgrid-section-sticky > * { + top: 0; /* because border-sharing causes a gap at the top */ + /* TODO: give safari -1. has bug */ + } +.fc .fc-scrollgrid-section-footer.fc-scrollgrid-section-sticky > * { + bottom: 0; /* known bug: bottom-stickiness doesn't work in safari */ + } +.fc .fc-scrollgrid-sticky-shim { /* for horizontal scrollbar */ + height: 1px; /* needs height to create scrollbars */ + margin-bottom: -1px; + } +.fc-sticky { /* no .fc wrap because used as child of body */ + position: sticky; +} +.fc .fc-view-harness { + flex-grow: 1; /* because this harness is WITHIN the .fc's flexbox */ + position: relative; + } +.fc { + + /* when the harness controls the height, make the view liquid */ + +} +.fc .fc-view-harness-active > .fc-view { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + } +.fc .fc-col-header-cell-cushion { + display: inline-block; /* x-browser for when sticky (when multi-tier header) */ + padding: 2px 4px; + } +.fc .fc-bg-event, + .fc .fc-non-business, + .fc .fc-highlight { + /* will always have a harness with position:relative/absolute, so absolutely expand */ + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + } +.fc .fc-non-business { + background: rgba(215, 215, 215, 0.3); + background: var(--fc-non-business-color, rgba(215, 215, 215, 0.3)); + } +.fc .fc-bg-event { + background: rgb(143, 223, 130); + background: var(--fc-bg-event-color, rgb(143, 223, 130)); + opacity: 0.3; + opacity: var(--fc-bg-event-opacity, 0.3) + } +.fc .fc-bg-event .fc-event-title { + margin: .5em; + font-size: .85em; + font-size: var(--fc-small-font-size, .85em); + font-style: italic; + } +.fc .fc-highlight { + background: rgba(188, 232, 241, 0.3); + background: var(--fc-highlight-color, rgba(188, 232, 241, 0.3)); + } +.fc .fc-cell-shaded, + .fc .fc-day-disabled { + background: rgba(208, 208, 208, 0.3); + background: var(--fc-neutral-bg-color, rgba(208, 208, 208, 0.3)); + } +/* link resets */ +/* ---------------------------------------------------------------------------------------------------- */ +a.fc-event, +a.fc-event:hover { + text-decoration: none; +} +/* cursor */ +.fc-event[href], +.fc-event.fc-event-draggable { + cursor: pointer; +} +/* event text content */ +/* ---------------------------------------------------------------------------------------------------- */ +.fc-event .fc-event-main { + position: relative; + z-index: 2; + } +/* dragging */ +/* ---------------------------------------------------------------------------------------------------- */ +.fc-event-dragging:not(.fc-event-selected) { /* MOUSE */ + opacity: 0.75; + } +.fc-event-dragging.fc-event-selected { /* TOUCH */ + box-shadow: 0 2px 7px rgba(0, 0, 0, 0.3); + } +/* resizing */ +/* ---------------------------------------------------------------------------------------------------- */ +/* (subclasses should hone positioning for touch and non-touch) */ +.fc-event .fc-event-resizer { + display: none; + position: absolute; + z-index: 4; + } +.fc-event:hover, /* MOUSE */ +.fc-event-selected { /* TOUCH */ + +} +.fc-event:hover .fc-event-resizer, .fc-event-selected .fc-event-resizer { + display: block; + } +.fc-event-selected .fc-event-resizer { + border-radius: 4px; + border-radius: calc(var(--fc-event-resizer-dot-total-width, 8px) / 2); + border-width: 1px; + border-width: var(--fc-event-resizer-dot-border-width, 1px); + width: 8px; + width: var(--fc-event-resizer-dot-total-width, 8px); + height: 8px; + height: var(--fc-event-resizer-dot-total-width, 8px); + border-style: solid; + border-color: inherit; + background: #fff; + background: var(--fc-page-bg-color, #fff) + + /* expand hit area */ + + } +.fc-event-selected .fc-event-resizer:before { + content: ''; + position: absolute; + top: -20px; + left: -20px; + right: -20px; + bottom: -20px; + } +/* selecting (always TOUCH) */ +/* ---------------------------------------------------------------------------------------------------- */ +.fc-event-selected { + box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2) + + /* expand hit area (subclasses should expand) */ + +} +.fc-event-selected:before { + content: ""; + position: absolute; + z-index: 3; + top: 0; + left: 0; + right: 0; + bottom: 0; + } +.fc-event-selected { + + /* dimmer effect */ + +} +.fc-event-selected:after { + content: ""; + background: rgba(0, 0, 0, 0.25); + background: var(--fc-event-selected-overlay-color, rgba(0, 0, 0, 0.25)); + position: absolute; + z-index: 1; + + /* assume there's a border on all sides. overcome it. */ + /* sometimes there's NOT a border, in which case the dimmer will go over */ + /* an adjacent border, which looks fine. */ + top: -1px; + left: -1px; + right: -1px; + bottom: -1px; + } +/* +A HORIZONTAL event +*/ +.fc-h-event { /* allowed to be top-level */ + display: block; + border: 1px solid #3788d8; + border: 1px solid var(--fc-event-border-color, #3788d8); + background-color: #3788d8; + background-color: var(--fc-event-bg-color, #3788d8) + +} +.fc-h-event .fc-event-main { + color: #fff; + color: var(--fc-event-text-color, #fff); + } +.fc-h-event .fc-event-main-frame { + display: flex; /* for make fc-event-title-container expand */ + } +.fc-h-event .fc-event-time { + max-width: 100%; /* clip overflow on this element */ + overflow: hidden; + } +.fc-h-event .fc-event-title-container { /* serves as a container for the sticky cushion */ + flex-grow: 1; + flex-shrink: 1; + min-width: 0; /* important for allowing to shrink all the way */ + } +.fc-h-event .fc-event-title { + display: inline-block; /* need this to be sticky cross-browser */ + vertical-align: top; /* for not messing up line-height */ + left: 0; /* for sticky */ + right: 0; /* for sticky */ + max-width: 100%; /* clip overflow on this element */ + overflow: hidden; + } +.fc-h-event.fc-event-selected:before { + /* expand hit area */ + top: -10px; + bottom: -10px; + } +/* adjust border and border-radius (if there is any) for non-start/end */ +.fc-direction-ltr .fc-daygrid-block-event:not(.fc-event-start), +.fc-direction-rtl .fc-daygrid-block-event:not(.fc-event-end) { + border-top-left-radius: 0; + border-bottom-left-radius: 0; + border-left-width: 0; +} +.fc-direction-ltr .fc-daygrid-block-event:not(.fc-event-end), +.fc-direction-rtl .fc-daygrid-block-event:not(.fc-event-start) { + border-top-right-radius: 0; + border-bottom-right-radius: 0; + border-right-width: 0; +} +/* resizers */ +.fc-h-event:not(.fc-event-selected) .fc-event-resizer { + top: 0; + bottom: 0; + width: 8px; + width: var(--fc-event-resizer-thickness, 8px); +} +.fc-direction-ltr .fc-h-event:not(.fc-event-selected) .fc-event-resizer-start, +.fc-direction-rtl .fc-h-event:not(.fc-event-selected) .fc-event-resizer-end { + cursor: w-resize; + left: -4px; + left: calc(var(--fc-event-resizer-thickness, 8px) / -2); +} +.fc-direction-ltr .fc-h-event:not(.fc-event-selected) .fc-event-resizer-end, +.fc-direction-rtl .fc-h-event:not(.fc-event-selected) .fc-event-resizer-start { + cursor: e-resize; + right: -4px; + right: calc(var(--fc-event-resizer-thickness, 8px) / -2); +} +/* resizers for TOUCH */ +.fc-h-event.fc-event-selected .fc-event-resizer { + top: 50%; + margin-top: -4px; + margin-top: calc(var(--fc-event-resizer-dot-total-width, 8px) / -2); +} +.fc-direction-ltr .fc-h-event.fc-event-selected .fc-event-resizer-start, +.fc-direction-rtl .fc-h-event.fc-event-selected .fc-event-resizer-end { + left: -4px; + left: calc(var(--fc-event-resizer-dot-total-width, 8px) / -2); +} +.fc-direction-ltr .fc-h-event.fc-event-selected .fc-event-resizer-end, +.fc-direction-rtl .fc-h-event.fc-event-selected .fc-event-resizer-start { + right: -4px; + right: calc(var(--fc-event-resizer-dot-total-width, 8px) / -2); +} +.fc .fc-popover { + position: absolute; + z-index: 9999; + box-shadow: 0 2px 6px rgba(0,0,0,.15); + } +.fc .fc-popover-header { + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; + padding: 3px 4px; + } +.fc .fc-popover-title { + margin: 0 2px; + } +.fc .fc-popover-close { + cursor: pointer; + opacity: 0.65; + font-size: 1.1em; + } +.fc-theme-standard .fc-popover { + border: 1px solid #ddd; + border: 1px solid var(--fc-border-color, #ddd); + background: #fff; + background: var(--fc-page-bg-color, #fff); + } +.fc-theme-standard .fc-popover-header { + background: rgba(208, 208, 208, 0.3); + background: var(--fc-neutral-bg-color, rgba(208, 208, 208, 0.3)); + } + + +:root { + --fc-daygrid-event-dot-width: 8px; +} +/* help things clear margins of inner content */ +.fc-daygrid-day-frame, +.fc-daygrid-day-events, +.fc-daygrid-event-harness { /* for event top/bottom margins */ +} +.fc-daygrid-day-frame:before, .fc-daygrid-day-events:before, .fc-daygrid-event-harness:before { + content: ""; + clear: both; + display: table; } +.fc-daygrid-day-frame:after, .fc-daygrid-day-events:after, .fc-daygrid-event-harness:after { + content: ""; + clear: both; + display: table; } +.fc .fc-daygrid-body { /* a
that wraps the table */ + position: relative; + z-index: 1; /* container inner z-index's because s can't do it */ + } +.fc .fc-daygrid-day.fc-day-today { + background-color: rgba(255, 220, 40, 0.15); + background-color: var(--fc-today-bg-color, rgba(255, 220, 40, 0.15)); + } +.fc .fc-daygrid-day-frame { + position: relative; + min-height: 100%; /* seems to work better than `height` because sets height after rows/cells naturally do it */ + } +.fc { + + /* cell top */ + +} +.fc .fc-daygrid-day-top { + display: flex; + flex-direction: row-reverse; + } +.fc .fc-day-other .fc-daygrid-day-top { + opacity: 0.3; + } +.fc { + + /* day number (within cell top) */ + +} +.fc .fc-daygrid-day-number { + position: relative; + z-index: 4; + padding: 4px; + } +.fc { + + /* event container */ + +} +.fc .fc-daygrid-day-events { + margin-top: 1px; /* needs to be margin, not padding, so that available cell height can be computed */ + } +.fc { + + /* positioning for balanced vs natural */ + +} +.fc .fc-daygrid-body-balanced .fc-daygrid-day-events { + position: absolute; + left: 0; + right: 0; + } +.fc .fc-daygrid-body-unbalanced .fc-daygrid-day-events { + position: relative; /* for containing abs positioned event harnesses */ + min-height: 2em; /* in addition to being a min-height during natural height, equalizes the heights a little bit */ + } +.fc .fc-daygrid-body-natural { /* can coexist with -unbalanced */ + } +.fc .fc-daygrid-body-natural .fc-daygrid-day-events { + margin-bottom: 1em; + } +.fc { + + /* event harness */ + +} +.fc .fc-daygrid-event-harness { + position: relative; + } +.fc .fc-daygrid-event-harness-abs { + position: absolute; + top: 0; /* fallback coords for when cannot yet be computed */ + left: 0; /* */ + right: 0; /* */ + } +.fc .fc-daygrid-bg-harness { + position: absolute; + top: 0; + bottom: 0; + } +.fc { + + /* bg content */ + +} +.fc .fc-daygrid-day-bg .fc-non-business { z-index: 1 } +.fc .fc-daygrid-day-bg .fc-bg-event { z-index: 2 } +.fc .fc-daygrid-day-bg .fc-highlight { z-index: 3 } +.fc { + + /* events */ + +} +.fc .fc-daygrid-event { + z-index: 6; + margin-top: 1px; + } +.fc .fc-daygrid-event.fc-event-mirror { + z-index: 7; + } +.fc { + + /* cell bottom (within day-events) */ + +} +.fc .fc-daygrid-day-bottom { + font-size: .85em; + padding: 2px 3px 0 + } +.fc .fc-daygrid-day-bottom:before { + content: ""; + clear: both; + display: table; } +.fc .fc-daygrid-more-link { + position: relative; + z-index: 4; + cursor: pointer; + } +.fc { + + /* week number (within frame) */ + +} +.fc .fc-daygrid-week-number { + position: absolute; + z-index: 5; + top: 0; + padding: 2px; + min-width: 1.5em; + text-align: center; + background-color: rgba(208, 208, 208, 0.3); + background-color: var(--fc-neutral-bg-color, rgba(208, 208, 208, 0.3)); + color: #808080; + color: var(--fc-neutral-text-color, #808080); + } +.fc { + + /* popover */ + +} +.fc .fc-more-popover .fc-popover-body { + min-width: 220px; + padding: 10px; + } +.fc-direction-ltr .fc-daygrid-event.fc-event-start, +.fc-direction-rtl .fc-daygrid-event.fc-event-end { + margin-left: 2px; +} +.fc-direction-ltr .fc-daygrid-event.fc-event-end, +.fc-direction-rtl .fc-daygrid-event.fc-event-start { + margin-right: 2px; +} +.fc-direction-ltr .fc-daygrid-week-number { + left: 0; + border-radius: 0 0 3px 0; + } +.fc-direction-rtl .fc-daygrid-week-number { + right: 0; + border-radius: 0 0 0 3px; + } +.fc-liquid-hack .fc-daygrid-day-frame { + position: static; /* will cause inner absolute stuff to expand to */ + } +.fc-daygrid-event { /* make root-level, because will be dragged-and-dropped outside of a component root */ + position: relative; /* for z-indexes assigned later */ + white-space: nowrap; + border-radius: 3px; /* dot event needs this to when selected */ + font-size: .85em; + font-size: var(--fc-small-font-size, .85em); +} +/* --- the rectangle ("block") style of event --- */ +.fc-daygrid-block-event .fc-event-time { + font-weight: bold; + } +.fc-daygrid-block-event .fc-event-time, + .fc-daygrid-block-event .fc-event-title { + padding: 1px; + } +/* --- the dot style of event --- */ +.fc-daygrid-dot-event { + display: flex; + align-items: center; + padding: 2px 0 + +} +.fc-daygrid-dot-event .fc-event-title { + flex-grow: 1; + flex-shrink: 1; + min-width: 0; /* important for allowing to shrink all the way */ + overflow: hidden; + font-weight: bold; + } +.fc-daygrid-dot-event:hover, + .fc-daygrid-dot-event.fc-event-mirror { + background: rgba(0, 0, 0, 0.1); + } +.fc-daygrid-dot-event.fc-event-selected:before { + /* expand hit area */ + top: -10px; + bottom: -10px; + } +.fc-daygrid-event-dot { /* the actual dot */ + margin: 0 4px; + box-sizing: content-box; + width: 0; + height: 0; + border: 4px solid #3788d8; + border: calc(var(--fc-daygrid-event-dot-width, 8px) / 2) solid var(--fc-event-border-color, #3788d8); + border-radius: 4px; + border-radius: calc(var(--fc-daygrid-event-dot-width, 8px) / 2); +} +/* --- spacing between time and title --- */ +.fc-direction-ltr .fc-daygrid-event .fc-event-time { + margin-right: 3px; + } +.fc-direction-rtl .fc-daygrid-event .fc-event-time { + margin-left: 3px; + } + + +/* +A VERTICAL event +*/ + +.fc-v-event { /* allowed to be top-level */ + display: block; + border: 1px solid #3788d8; + border: 1px solid var(--fc-event-border-color, #3788d8); + background-color: #3788d8; + background-color: var(--fc-event-bg-color, #3788d8) + +} + +.fc-v-event .fc-event-main { + color: #fff; + color: var(--fc-event-text-color, #fff); + height: 100%; + } + +.fc-v-event .fc-event-main-frame { + height: 100%; + display: flex; + flex-direction: column; + } + +.fc-v-event .fc-event-time { + flex-grow: 0; + flex-shrink: 0; + max-height: 100%; + overflow: hidden; + } + +.fc-v-event .fc-event-title-container { /* a container for the sticky cushion */ + flex-grow: 1; + flex-shrink: 1; + min-height: 0; /* important for allowing to shrink all the way */ + } + +.fc-v-event .fc-event-title { /* will have fc-sticky on it */ + top: 0; + bottom: 0; + max-height: 100%; /* clip overflow */ + overflow: hidden; + } + +.fc-v-event:not(.fc-event-start) { + border-top-width: 0; + border-top-left-radius: 0; + border-top-right-radius: 0; + } + +.fc-v-event:not(.fc-event-end) { + border-bottom-width: 0; + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; + } + +.fc-v-event.fc-event-selected:before { + /* expand hit area */ + left: -10px; + right: -10px; + } + +.fc-v-event { + + /* resizer (mouse AND touch) */ + +} + +.fc-v-event .fc-event-resizer-start { + cursor: n-resize; + } + +.fc-v-event .fc-event-resizer-end { + cursor: s-resize; + } + +.fc-v-event { + + /* resizer for MOUSE */ + +} + +.fc-v-event:not(.fc-event-selected) .fc-event-resizer { + height: 8px; + height: var(--fc-event-resizer-thickness, 8px); + left: 0; + right: 0; + } + +.fc-v-event:not(.fc-event-selected) .fc-event-resizer-start { + top: -4px; + top: calc(var(--fc-event-resizer-thickness, 8px) / -2); + } + +.fc-v-event:not(.fc-event-selected) .fc-event-resizer-end { + bottom: -4px; + bottom: calc(var(--fc-event-resizer-thickness, 8px) / -2); + } + +.fc-v-event { + + /* resizer for TOUCH (when event is "selected") */ + +} + +.fc-v-event.fc-event-selected .fc-event-resizer { + left: 50%; + margin-left: -4px; + margin-left: calc(var(--fc-event-resizer-dot-total-width, 8px) / -2); + } + +.fc-v-event.fc-event-selected .fc-event-resizer-start { + top: -4px; + top: calc(var(--fc-event-resizer-dot-total-width, 8px) / -2); + } + +.fc-v-event.fc-event-selected .fc-event-resizer-end { + bottom: -4px; + bottom: calc(var(--fc-event-resizer-dot-total-width, 8px) / -2); + } +.fc .fc-timegrid .fc-daygrid-body { /* the all-day daygrid within the timegrid view */ + z-index: 2; /* put above the timegrid-body so that more-popover is above everything. TODO: better solution */ + } +.fc .fc-timegrid-divider { + padding: 0 0 2px; /* browsers get confused when you set height. use padding instead */ + } +.fc .fc-timegrid-body { + position: relative; + z-index: 1; /* scope the z-indexes of slots and cols */ + min-height: 100%; /* fill height always, even when slat table doesn't grow */ + } +.fc .fc-timegrid-axis-chunk { /* for advanced ScrollGrid */ + position: relative /* offset parent for now-indicator-container */ + + } +.fc .fc-timegrid-axis-chunk > table { + position: relative; + z-index: 1; /* above the now-indicator-container */ + } +.fc .fc-timegrid-slots { + position: relative; + z-index: 1; + } +.fc .fc-timegrid-slot { /* a */ + height: 1.5em; + border-bottom: 0 /* each cell owns its top border */ + } +.fc .fc-timegrid-slot:empty:before { + content: '\00a0'; /* make sure there's at least an empty space to create height for height syncing */ + } +.fc .fc-timegrid-slot-minor { + border-top-style: dotted; + } +.fc .fc-timegrid-slot-label-cushion { + display: inline-block; + white-space: nowrap; + } +.fc .fc-timegrid-slot-label { + vertical-align: middle; /* vertical align the slots */ + } +.fc { + + + /* slots AND axis cells (top-left corner of view including the "all-day" text) */ + +} +.fc .fc-timegrid-axis-cushion, + .fc .fc-timegrid-slot-label-cushion { + padding: 0 4px; + } +.fc { + + + /* axis cells (top-left corner of view including the "all-day" text) */ + /* vertical align is more complicated, uses flexbox */ + +} +.fc .fc-timegrid-axis-frame-liquid { + height: 100%; /* will need liquid-hack in FF */ + } +.fc .fc-timegrid-axis-frame { + overflow: hidden; + display: flex; + align-items: center; /* vertical align */ + justify-content: flex-end; /* horizontal align. matches text-align below */ + } +.fc .fc-timegrid-axis-cushion { + max-width: 60px; /* limits the width of the "all-day" text */ + flex-shrink: 0; /* allows text to expand how it normally would, regardless of constrained width */ + } +.fc-direction-ltr .fc-timegrid-slot-label-frame { + text-align: right; + } +.fc-direction-rtl .fc-timegrid-slot-label-frame { + text-align: left; + } +.fc-liquid-hack .fc-timegrid-axis-frame-liquid { + height: auto; + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + } +.fc .fc-timegrid-col.fc-day-today { + background-color: rgba(255, 220, 40, 0.15); + background-color: var(--fc-today-bg-color, rgba(255, 220, 40, 0.15)); + } +.fc .fc-timegrid-col-frame { + min-height: 100%; /* liquid-hack is below */ + position: relative; + } +.fc-media-screen.fc-liquid-hack .fc-timegrid-col-frame { + height: auto; + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + } +.fc-media-screen .fc-timegrid-cols { + position: absolute; /* no z-index. children will decide and go above slots */ + top: 0; + left: 0; + right: 0; + bottom: 0 + } +.fc-media-screen .fc-timegrid-cols > table { + height: 100%; + } +.fc-media-screen .fc-timegrid-col-bg, + .fc-media-screen .fc-timegrid-col-events, + .fc-media-screen .fc-timegrid-now-indicator-container { + position: absolute; + top: 0; + left: 0; + right: 0; + } +.fc { + + /* bg */ + +} +.fc .fc-timegrid-col-bg { + z-index: 2; /* TODO: kill */ + } +.fc .fc-timegrid-col-bg .fc-non-business { z-index: 1 } +.fc .fc-timegrid-col-bg .fc-bg-event { z-index: 2 } +.fc .fc-timegrid-col-bg .fc-highlight { z-index: 3 } +.fc .fc-timegrid-bg-harness { + position: absolute; /* top/bottom will be set by JS */ + left: 0; + right: 0; + } +.fc { + + /* fg events */ + /* (the mirror segs are put into a separate container with same classname, */ + /* and they must be after the normal seg container to appear at a higher z-index) */ + +} +.fc .fc-timegrid-col-events { + z-index: 3; + /* child event segs have z-indexes that are scoped within this div */ + } +.fc { + + /* now indicator */ + +} +.fc .fc-timegrid-now-indicator-container { + bottom: 0; + overflow: hidden; /* don't let overflow of lines/arrows cause unnecessary scrolling */ + /* z-index is set on the individual elements */ + } +.fc-direction-ltr .fc-timegrid-col-events { + margin: 0 2.5% 0 2px; + } +.fc-direction-rtl .fc-timegrid-col-events { + margin: 0 2px 0 2.5%; + } +.fc-timegrid-event-harness { + position: absolute /* top/left/right/bottom will all be set by JS */ +} +.fc-timegrid-event-harness > .fc-timegrid-event { + position: absolute; /* absolute WITHIN the harness */ + top: 0; /* for when not yet positioned */ + bottom: 0; /* " */ + left: 0; + right: 0; + } +.fc-timegrid-event-harness-inset .fc-timegrid-event, +.fc-timegrid-event.fc-event-mirror, +.fc-timegrid-more-link { + box-shadow: 0px 0px 0px 1px #fff; + box-shadow: 0px 0px 0px 1px var(--fc-page-bg-color, #fff); +} +.fc-timegrid-event, +.fc-timegrid-more-link { /* events need to be root */ + font-size: .85em; + font-size: var(--fc-small-font-size, .85em); + border-radius: 3px; +} +.fc-timegrid-event { /* events need to be root */ + margin-bottom: 1px /* give some space from bottom */ +} +.fc-timegrid-event .fc-event-main { + padding: 1px 1px 0; + } +.fc-timegrid-event .fc-event-time { + white-space: nowrap; + font-size: .85em; + font-size: var(--fc-small-font-size, .85em); + margin-bottom: 1px; + } +.fc-timegrid-event-short .fc-event-main-frame { + flex-direction: row; + overflow: hidden; + } +.fc-timegrid-event-short .fc-event-time:after { + content: '\00a0-\00a0'; /* dash surrounded by non-breaking spaces */ + } +.fc-timegrid-event-short .fc-event-title { + font-size: .85em; + font-size: var(--fc-small-font-size, .85em) + } +.fc-timegrid-more-link { /* does NOT inherit from fc-timegrid-event */ + position: absolute; + z-index: 9999; /* hack */ + color: inherit; + color: var(--fc-more-link-text-color, inherit); + background: #d0d0d0; + background: var(--fc-more-link-bg-color, #d0d0d0); + cursor: pointer; + margin-bottom: 1px; /* match space below fc-timegrid-event */ +} +.fc-timegrid-more-link-inner { /* has fc-sticky */ + padding: 3px 2px; + top: 0; +} +.fc-direction-ltr .fc-timegrid-more-link { + right: 0; + } +.fc-direction-rtl .fc-timegrid-more-link { + left: 0; + } +.fc { + + /* line */ + +} +.fc .fc-timegrid-now-indicator-line { + position: absolute; + z-index: 4; + left: 0; + right: 0; + border-style: solid; + border-color: red; + border-color: var(--fc-now-indicator-color, red); + border-width: 1px 0 0; + } +.fc { + + /* arrow */ + +} +.fc .fc-timegrid-now-indicator-arrow { + position: absolute; + z-index: 4; + margin-top: -5px; /* vertically center on top coordinate */ + border-style: solid; + border-color: red; + border-color: var(--fc-now-indicator-color, red); + } +.fc-direction-ltr .fc-timegrid-now-indicator-arrow { + left: 0; + + /* triangle pointing right. TODO: mixin */ + border-width: 5px 0 5px 6px; + border-top-color: transparent; + border-bottom-color: transparent; + } +.fc-direction-rtl .fc-timegrid-now-indicator-arrow { + right: 0; + + /* triangle pointing left. TODO: mixin */ + border-width: 5px 6px 5px 0; + border-top-color: transparent; + border-bottom-color: transparent; + } + + +:root { + --fc-list-event-dot-width: 10px; + --fc-list-event-hover-bg-color: #f5f5f5; +} +.fc-theme-standard .fc-list { + border: 1px solid #ddd; + border: 1px solid var(--fc-border-color, #ddd); + } +.fc { + + /* message when no events */ + +} +.fc .fc-list-empty { + background-color: rgba(208, 208, 208, 0.3); + background-color: var(--fc-neutral-bg-color, rgba(208, 208, 208, 0.3)); + height: 100%; + display: flex; + justify-content: center; + align-items: center; /* vertically aligns fc-list-empty-inner */ + } +.fc .fc-list-empty-cushion { + margin: 5em 0; + } +.fc { + + /* table within the scroller */ + /* ---------------------------------------------------------------------------------------------------- */ + +} +.fc .fc-list-table { + width: 100%; + border-style: hidden; /* kill outer border on theme */ + } +.fc .fc-list-table tr > * { + border-left: 0; + border-right: 0; + } +.fc .fc-list-sticky .fc-list-day > * { /* the cells */ + position: sticky; + top: 0; + background: #fff; + background: var(--fc-page-bg-color, #fff); /* for when headers are styled to be transparent and sticky */ + } +.fc .fc-list-table th { + padding: 0; /* uses an inner-wrapper instead... */ + } +.fc .fc-list-table td, + .fc .fc-list-day-cushion { + padding: 8px 14px; + } +.fc { + + + /* date heading rows */ + /* ---------------------------------------------------------------------------------------------------- */ + +} +.fc .fc-list-day-cushion:after { + content: ""; + clear: both; + display: table; /* clear floating */ + } +.fc-theme-standard .fc-list-day-cushion { + background-color: rgba(208, 208, 208, 0.3); + background-color: var(--fc-neutral-bg-color, rgba(208, 208, 208, 0.3)); + } +.fc-direction-ltr .fc-list-day-text, +.fc-direction-rtl .fc-list-day-side-text { + float: left; +} +.fc-direction-ltr .fc-list-day-side-text, +.fc-direction-rtl .fc-list-day-text { + float: right; +} +/* make the dot closer to the event title */ +.fc-direction-ltr .fc-list-table .fc-list-event-graphic { padding-right: 0 } +.fc-direction-rtl .fc-list-table .fc-list-event-graphic { padding-left: 0 } +.fc .fc-list-event.fc-event-forced-url { + cursor: pointer; /* whole row will seem clickable */ + } +.fc .fc-list-event:hover td { + background-color: #f5f5f5; + background-color: var(--fc-list-event-hover-bg-color, #f5f5f5); + } +.fc { + + /* shrink certain cols */ + +} +.fc .fc-list-event-graphic, + .fc .fc-list-event-time { + white-space: nowrap; + width: 1px; + } +.fc .fc-list-event-dot { + display: inline-block; + box-sizing: content-box; + width: 0; + height: 0; + border: 5px solid #3788d8; + border: calc(var(--fc-list-event-dot-width, 10px) / 2) solid var(--fc-event-border-color, #3788d8); + border-radius: 5px; + border-radius: calc(var(--fc-list-event-dot-width, 10px) / 2); + } +.fc { + + /* reset styling */ + +} +.fc .fc-list-event-title a { + color: inherit; + text-decoration: none; + } +.fc { + + /* underline link when hovering over any part of row */ + +} +.fc .fc-list-event.fc-event-forced-url:hover a { + text-decoration: underline; + } + + + + .fc-theme-bootstrap a:not([href]) { + color: inherit; /* natural color for navlinks */ + } + diff --git a/apps/schoolCalender/fullcalendar/main.js b/apps/schoolCalender/fullcalendar/main.js new file mode 100644 index 000000000..54bf45d3f --- /dev/null +++ b/apps/schoolCalender/fullcalendar/main.js @@ -0,0 +1,14738 @@ +/*! +FullCalendar v5.9.0 +Docs & License: https://fullcalendar.io/ +(c) 2021 Adam Shaw +*/ +var FullCalendar = (function (exports) { + 'use strict'; + + /*! ***************************************************************************** + Copyright (c) Microsoft Corporation. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH + REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, + INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR + OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + PERFORMANCE OF THIS SOFTWARE. + ***************************************************************************** */ + /* global Reflect, Promise */ + + var extendStatics = function(d, b) { + extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; }; + return extendStatics(d, b); + }; + + function __extends(d, b) { + if (typeof b !== "function" && b !== null) + throw new TypeError("Class extends value " + String(b) + " is not a constructor or null"); + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + } + + var __assign = function() { + __assign = Object.assign || function __assign(t) { + for (var s, i = 1, n = arguments.length; i < n; i++) { + s = arguments[i]; + for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; + } + return t; + }; + return __assign.apply(this, arguments); + }; + + function __spreadArray(to, from, pack) { + if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) { + if (ar || !(i in from)) { + if (!ar) ar = Array.prototype.slice.call(from, 0, i); + ar[i] = from[i]; + } + } + return to.concat(ar || from); + } + + var n,u,i$1,t,o,r$1={},f$1=[],e$1=/acit|ex(?:s|g|n|p|$)|rph|grid|ows|mnc|ntw|ine[ch]|zoo|^ord|itera/i;function c$1(n,l){for(var u in l)n[u]=l[u];return n}function s(n){var l=n.parentNode;l&&l.removeChild(n);}function a$1(n,l,u){var i,t,o,r=arguments,f={};for(o in l)"key"==o?i=l[o]:"ref"==o?t=l[o]:f[o]=l[o];if(arguments.length>3)for(u=[u],o=3;o0?v$1(k.type,k.props,k.key,null,k.__v):k)){if(k.__=u,k.__b=u.__b+1,null===(_=A[h])||_&&k.key==_.key&&k.type===_.type)A[h]=void 0;else for(p=0;p3;)e.pop()();if(e[1]>>1,1),t.i.removeChild(n);}}),N(a$1(T,{context:t.context},n.__v),t.l)):t.l&&t.componentWillUnmount();}function I(n,t){return a$1(j,{__v:n,i:t})}(F.prototype=new p).__e=function(n){var t=this,e=U(t.__v),r=t.o.get(n);return r[0]++,function(u){var o=function(){t.props.revealOrder?(r.push(u),M(t,n,r)):u();};e?e(o):o();}},F.prototype.render=function(n){this.u=null,this.o=new Map;var t=w$1(n.children);n.revealOrder&&"b"===n.revealOrder[0]&&t.reverse();for(var e=t.length;e--;)this.o.set(t[e],this.u=[1,0,this.u]);return n.children},F.prototype.componentDidUpdate=F.prototype.componentDidMount=function(){var n=this;this.o.forEach(function(t,e){M(n,e,t);});};var W="undefined"!=typeof Symbol&&Symbol.for&&Symbol.for("react.element")||60103,P=/^(?:accent|alignment|arabic|baseline|cap|clip(?!PathU)|color|fill|flood|font|glyph(?!R)|horiz|marker(?!H|W|U)|overline|paint|stop|strikethrough|stroke|text(?!L)|underline|unicode|units|v|vector|vert|word|writing|x(?!C))[A-Z]/,V=function(n){return ("undefined"!=typeof Symbol&&"symbol"==typeof Symbol()?/fil|che|rad/i:/fil|che|ra/i).test(n)};p.prototype.isReactComponent={},["componentWillMount","componentWillReceiveProps","componentWillUpdate"].forEach(function(n){Object.defineProperty(p.prototype,n,{configurable:!0,get:function(){return this["UNSAFE_"+n]},set:function(t){Object.defineProperty(this,n,{configurable:!0,writable:!0,value:t});}});});var H=n.event;function Z(){}function Y(){return this.cancelBubble}function $(){return this.defaultPrevented}n.event=function(n){return H&&(n=H(n)),n.persist=Z,n.isPropagationStopped=Y,n.isDefaultPrevented=$,n.nativeEvent=n};var G={configurable:!0,get:function(){return this.class}},J=n.vnode;n.vnode=function(n){var t=n.type,e=n.props,r=e;if("string"==typeof t){for(var u in r={},e){var o=e[u];"value"===u&&"defaultValue"in e&&null==o||("defaultValue"===u&&"value"in e&&null==e.value?u="value":"download"===u&&!0===o?o="":/ondoubleclick/i.test(u)?u="ondblclick":/^onchange(textarea|input)/i.test(u+t)&&!V(e.type)?u="oninput":/^on(Ani|Tra|Tou|BeforeInp)/.test(u)?u=u.toLowerCase():P.test(u)?u=u.replace(/[A-Z0-9]/,"-$&").toLowerCase():null===o&&(o=void 0),r[u]=o);}"select"==t&&r.multiple&&Array.isArray(r.value)&&(r.value=w$1(e.children).forEach(function(n){n.props.selected=-1!=r.value.indexOf(n.props.value);})),"select"==t&&null!=r.defaultValue&&(r.value=w$1(e.children).forEach(function(n){n.props.selected=r.multiple?-1!=r.defaultValue.indexOf(n.props.value):r.defaultValue==n.props.value;})),n.props=r;}t&&e.class!=e.className&&(G.enumerable="className"in e,null!=e.className&&(r.class=e.className),Object.defineProperty(r,"className",G)),n.$$typeof=W,J&&J(n);};var K=n.__r;n.__r=function(n){K&&K(n);};"object"==typeof performance&&"function"==typeof performance.now?performance.now.bind(performance):function(){return Date.now()}; + + var globalObj = typeof globalThis !== 'undefined' ? globalThis : window; // // TODO: streamline when killing IE11 support + if (globalObj.FullCalendarVDom) { + console.warn('FullCalendar VDOM already loaded'); + } + else { + globalObj.FullCalendarVDom = { + Component: p, + createElement: a$1, + render: N, + createRef: h, + Fragment: y, + createContext: createContext$1, + createPortal: I, + flushToDom: flushToDom$1, + unmountComponentAtNode: unmountComponentAtNode$1, + }; + } + // HACKS... + // TODO: lock version + // TODO: link gh issues + function flushToDom$1() { + var oldDebounceRendering = n.debounceRendering; // orig + var callbackQ = []; + function execCallbackSync(callback) { + callbackQ.push(callback); + } + n.debounceRendering = execCallbackSync; + N(a$1(FakeComponent, {}), document.createElement('div')); + while (callbackQ.length) { + callbackQ.shift()(); + } + n.debounceRendering = oldDebounceRendering; + } + var FakeComponent = /** @class */ (function (_super) { + __extends(FakeComponent, _super); + function FakeComponent() { + return _super !== null && _super.apply(this, arguments) || this; + } + FakeComponent.prototype.render = function () { return a$1('div', {}); }; + FakeComponent.prototype.componentDidMount = function () { this.setState({}); }; + return FakeComponent; + }(p)); + function createContext$1(defaultValue) { + var ContextType = q(defaultValue); + var origProvider = ContextType.Provider; + ContextType.Provider = function () { + var _this = this; + var isNew = !this.getChildContext; + var children = origProvider.apply(this, arguments); // eslint-disable-line prefer-rest-params + if (isNew) { + var subs_1 = []; + this.shouldComponentUpdate = function (_props) { + if (_this.props.value !== _props.value) { + subs_1.forEach(function (c) { + c.context = _props.value; + c.forceUpdate(); + }); + } + }; + this.sub = function (c) { + subs_1.push(c); + var old = c.componentWillUnmount; + c.componentWillUnmount = function () { + subs_1.splice(subs_1.indexOf(c), 1); + old && old.call(c); + }; + }; + } + return children; + }; + return ContextType; + } + function unmountComponentAtNode$1(node) { + N(null, node); + } + + // no public types yet. when there are, export from: + // import {} from './api-type-deps' + var EventSourceApi = /** @class */ (function () { + function EventSourceApi(context, internalEventSource) { + this.context = context; + this.internalEventSource = internalEventSource; + } + EventSourceApi.prototype.remove = function () { + this.context.dispatch({ + type: 'REMOVE_EVENT_SOURCE', + sourceId: this.internalEventSource.sourceId, + }); + }; + EventSourceApi.prototype.refetch = function () { + this.context.dispatch({ + type: 'FETCH_EVENT_SOURCES', + sourceIds: [this.internalEventSource.sourceId], + isRefetch: true, + }); + }; + Object.defineProperty(EventSourceApi.prototype, "id", { + get: function () { + return this.internalEventSource.publicId; + }, + enumerable: false, + configurable: true + }); + Object.defineProperty(EventSourceApi.prototype, "url", { + get: function () { + return this.internalEventSource.meta.url; + }, + enumerable: false, + configurable: true + }); + Object.defineProperty(EventSourceApi.prototype, "format", { + get: function () { + return this.internalEventSource.meta.format; // TODO: bad. not guaranteed + }, + enumerable: false, + configurable: true + }); + return EventSourceApi; + }()); + + function removeElement(el) { + if (el.parentNode) { + el.parentNode.removeChild(el); + } + } + // Querying + // ---------------------------------------------------------------------------------------------------------------- + function elementClosest(el, selector) { + if (el.closest) { + return el.closest(selector); + // really bad fallback for IE + // from https://developer.mozilla.org/en-US/docs/Web/API/Element/closest + } + if (!document.documentElement.contains(el)) { + return null; + } + do { + if (elementMatches(el, selector)) { + return el; + } + el = (el.parentElement || el.parentNode); + } while (el !== null && el.nodeType === 1); + return null; + } + function elementMatches(el, selector) { + var method = el.matches || el.matchesSelector || el.msMatchesSelector; + return method.call(el, selector); + } + // accepts multiple subject els + // returns a real array. good for methods like forEach + // TODO: accept the document + function findElements(container, selector) { + var containers = container instanceof HTMLElement ? [container] : container; + var allMatches = []; + for (var i = 0; i < containers.length; i += 1) { + var matches = containers[i].querySelectorAll(selector); + for (var j = 0; j < matches.length; j += 1) { + allMatches.push(matches[j]); + } + } + return allMatches; + } + // accepts multiple subject els + // only queries direct child elements // TODO: rename to findDirectChildren! + function findDirectChildren(parent, selector) { + var parents = parent instanceof HTMLElement ? [parent] : parent; + var allMatches = []; + for (var i = 0; i < parents.length; i += 1) { + var childNodes = parents[i].children; // only ever elements + for (var j = 0; j < childNodes.length; j += 1) { + var childNode = childNodes[j]; + if (!selector || elementMatches(childNode, selector)) { + allMatches.push(childNode); + } + } + } + return allMatches; + } + // Style + // ---------------------------------------------------------------------------------------------------------------- + var PIXEL_PROP_RE = /(top|left|right|bottom|width|height)$/i; + function applyStyle(el, props) { + for (var propName in props) { + applyStyleProp(el, propName, props[propName]); + } + } + function applyStyleProp(el, name, val) { + if (val == null) { + el.style[name] = ''; + } + else if (typeof val === 'number' && PIXEL_PROP_RE.test(name)) { + el.style[name] = val + "px"; + } + else { + el.style[name] = val; + } + } + // Event Handling + // ---------------------------------------------------------------------------------------------------------------- + // if intercepting bubbled events at the document/window/body level, + // and want to see originating element (the 'target'), use this util instead + // of `ev.target` because it goes within web-component boundaries. + function getEventTargetViaRoot(ev) { + var _a, _b; + return (_b = (_a = ev.composedPath) === null || _a === void 0 ? void 0 : _a.call(ev)[0]) !== null && _b !== void 0 ? _b : ev.target; + } + // Shadow DOM consuderations + // ---------------------------------------------------------------------------------------------------------------- + function getElRoot(el) { + return el.getRootNode ? el.getRootNode() : document; + } + + // Stops a mouse/touch event from doing it's native browser action + function preventDefault(ev) { + ev.preventDefault(); + } + // Event Delegation + // ---------------------------------------------------------------------------------------------------------------- + function buildDelegationHandler(selector, handler) { + return function (ev) { + var matchedChild = elementClosest(ev.target, selector); + if (matchedChild) { + handler.call(matchedChild, ev, matchedChild); + } + }; + } + function listenBySelector(container, eventType, selector, handler) { + var attachedHandler = buildDelegationHandler(selector, handler); + container.addEventListener(eventType, attachedHandler); + return function () { + container.removeEventListener(eventType, attachedHandler); + }; + } + function listenToHoverBySelector(container, selector, onMouseEnter, onMouseLeave) { + var currentMatchedChild; + return listenBySelector(container, 'mouseover', selector, function (mouseOverEv, matchedChild) { + if (matchedChild !== currentMatchedChild) { + currentMatchedChild = matchedChild; + onMouseEnter(mouseOverEv, matchedChild); + var realOnMouseLeave_1 = function (mouseLeaveEv) { + currentMatchedChild = null; + onMouseLeave(mouseLeaveEv, matchedChild); + matchedChild.removeEventListener('mouseleave', realOnMouseLeave_1); + }; + // listen to the next mouseleave, and then unattach + matchedChild.addEventListener('mouseleave', realOnMouseLeave_1); + } + }); + } + // Animation + // ---------------------------------------------------------------------------------------------------------------- + var transitionEventNames = [ + 'webkitTransitionEnd', + 'otransitionend', + 'oTransitionEnd', + 'msTransitionEnd', + 'transitionend', + ]; + // triggered only when the next single subsequent transition finishes + function whenTransitionDone(el, callback) { + var realCallback = function (ev) { + callback(ev); + transitionEventNames.forEach(function (eventName) { + el.removeEventListener(eventName, realCallback); + }); + }; + transitionEventNames.forEach(function (eventName) { + el.addEventListener(eventName, realCallback); // cross-browser way to determine when the transition finishes + }); + } + + var guidNumber = 0; + function guid() { + guidNumber += 1; + return String(guidNumber); + } + /* FullCalendar-specific DOM Utilities + ----------------------------------------------------------------------------------------------------------------------*/ + // Make the mouse cursor express that an event is not allowed in the current area + function disableCursor() { + document.body.classList.add('fc-not-allowed'); + } + // Returns the mouse cursor to its original look + function enableCursor() { + document.body.classList.remove('fc-not-allowed'); + } + /* Selection + ----------------------------------------------------------------------------------------------------------------------*/ + function preventSelection(el) { + el.classList.add('fc-unselectable'); + el.addEventListener('selectstart', preventDefault); + } + function allowSelection(el) { + el.classList.remove('fc-unselectable'); + el.removeEventListener('selectstart', preventDefault); + } + /* Context Menu + ----------------------------------------------------------------------------------------------------------------------*/ + function preventContextMenu(el) { + el.addEventListener('contextmenu', preventDefault); + } + function allowContextMenu(el) { + el.removeEventListener('contextmenu', preventDefault); + } + function parseFieldSpecs(input) { + var specs = []; + var tokens = []; + var i; + var token; + if (typeof input === 'string') { + tokens = input.split(/\s*,\s*/); + } + else if (typeof input === 'function') { + tokens = [input]; + } + else if (Array.isArray(input)) { + tokens = input; + } + for (i = 0; i < tokens.length; i += 1) { + token = tokens[i]; + if (typeof token === 'string') { + specs.push(token.charAt(0) === '-' ? + { field: token.substring(1), order: -1 } : + { field: token, order: 1 }); + } + else if (typeof token === 'function') { + specs.push({ func: token }); + } + } + return specs; + } + function compareByFieldSpecs(obj0, obj1, fieldSpecs) { + var i; + var cmp; + for (i = 0; i < fieldSpecs.length; i += 1) { + cmp = compareByFieldSpec(obj0, obj1, fieldSpecs[i]); + if (cmp) { + return cmp; + } + } + return 0; + } + function compareByFieldSpec(obj0, obj1, fieldSpec) { + if (fieldSpec.func) { + return fieldSpec.func(obj0, obj1); + } + return flexibleCompare(obj0[fieldSpec.field], obj1[fieldSpec.field]) + * (fieldSpec.order || 1); + } + function flexibleCompare(a, b) { + if (!a && !b) { + return 0; + } + if (b == null) { + return -1; + } + if (a == null) { + return 1; + } + if (typeof a === 'string' || typeof b === 'string') { + return String(a).localeCompare(String(b)); + } + return a - b; + } + /* String Utilities + ----------------------------------------------------------------------------------------------------------------------*/ + function padStart(val, len) { + var s = String(val); + return '000'.substr(0, len - s.length) + s; + } + /* Number Utilities + ----------------------------------------------------------------------------------------------------------------------*/ + function compareNumbers(a, b) { + return a - b; + } + function isInt(n) { + return n % 1 === 0; + } + /* FC-specific DOM dimension stuff + ----------------------------------------------------------------------------------------------------------------------*/ + function computeSmallestCellWidth(cellEl) { + var allWidthEl = cellEl.querySelector('.fc-scrollgrid-shrink-frame'); + var contentWidthEl = cellEl.querySelector('.fc-scrollgrid-shrink-cushion'); + if (!allWidthEl) { + throw new Error('needs fc-scrollgrid-shrink-frame className'); // TODO: use const + } + if (!contentWidthEl) { + throw new Error('needs fc-scrollgrid-shrink-cushion className'); + } + return cellEl.getBoundingClientRect().width - allWidthEl.getBoundingClientRect().width + // the cell padding+border + contentWidthEl.getBoundingClientRect().width; + } + + var DAY_IDS = ['sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat']; + // Adding + function addWeeks(m, n) { + var a = dateToUtcArray(m); + a[2] += n * 7; + return arrayToUtcDate(a); + } + function addDays(m, n) { + var a = dateToUtcArray(m); + a[2] += n; + return arrayToUtcDate(a); + } + function addMs(m, n) { + var a = dateToUtcArray(m); + a[6] += n; + return arrayToUtcDate(a); + } + // Diffing (all return floats) + // TODO: why not use ranges? + function diffWeeks(m0, m1) { + return diffDays(m0, m1) / 7; + } + function diffDays(m0, m1) { + return (m1.valueOf() - m0.valueOf()) / (1000 * 60 * 60 * 24); + } + function diffHours(m0, m1) { + return (m1.valueOf() - m0.valueOf()) / (1000 * 60 * 60); + } + function diffMinutes(m0, m1) { + return (m1.valueOf() - m0.valueOf()) / (1000 * 60); + } + function diffSeconds(m0, m1) { + return (m1.valueOf() - m0.valueOf()) / 1000; + } + function diffDayAndTime(m0, m1) { + var m0day = startOfDay(m0); + var m1day = startOfDay(m1); + return { + years: 0, + months: 0, + days: Math.round(diffDays(m0day, m1day)), + milliseconds: (m1.valueOf() - m1day.valueOf()) - (m0.valueOf() - m0day.valueOf()), + }; + } + // Diffing Whole Units + function diffWholeWeeks(m0, m1) { + var d = diffWholeDays(m0, m1); + if (d !== null && d % 7 === 0) { + return d / 7; + } + return null; + } + function diffWholeDays(m0, m1) { + if (timeAsMs(m0) === timeAsMs(m1)) { + return Math.round(diffDays(m0, m1)); + } + return null; + } + // Start-Of + function startOfDay(m) { + return arrayToUtcDate([ + m.getUTCFullYear(), + m.getUTCMonth(), + m.getUTCDate(), + ]); + } + function startOfHour(m) { + return arrayToUtcDate([ + m.getUTCFullYear(), + m.getUTCMonth(), + m.getUTCDate(), + m.getUTCHours(), + ]); + } + function startOfMinute(m) { + return arrayToUtcDate([ + m.getUTCFullYear(), + m.getUTCMonth(), + m.getUTCDate(), + m.getUTCHours(), + m.getUTCMinutes(), + ]); + } + function startOfSecond(m) { + return arrayToUtcDate([ + m.getUTCFullYear(), + m.getUTCMonth(), + m.getUTCDate(), + m.getUTCHours(), + m.getUTCMinutes(), + m.getUTCSeconds(), + ]); + } + // Week Computation + function weekOfYear(marker, dow, doy) { + var y = marker.getUTCFullYear(); + var w = weekOfGivenYear(marker, y, dow, doy); + if (w < 1) { + return weekOfGivenYear(marker, y - 1, dow, doy); + } + var nextW = weekOfGivenYear(marker, y + 1, dow, doy); + if (nextW >= 1) { + return Math.min(w, nextW); + } + return w; + } + function weekOfGivenYear(marker, year, dow, doy) { + var firstWeekStart = arrayToUtcDate([year, 0, 1 + firstWeekOffset(year, dow, doy)]); + var dayStart = startOfDay(marker); + var days = Math.round(diffDays(firstWeekStart, dayStart)); + return Math.floor(days / 7) + 1; // zero-indexed + } + // start-of-first-week - start-of-year + function firstWeekOffset(year, dow, doy) { + // first-week day -- which january is always in the first week (4 for iso, 1 for other) + var fwd = 7 + dow - doy; + // first-week day local weekday -- which local weekday is fwd + var fwdlw = (7 + arrayToUtcDate([year, 0, fwd]).getUTCDay() - dow) % 7; + return -fwdlw + fwd - 1; + } + // Array Conversion + function dateToLocalArray(date) { + return [ + date.getFullYear(), + date.getMonth(), + date.getDate(), + date.getHours(), + date.getMinutes(), + date.getSeconds(), + date.getMilliseconds(), + ]; + } + function arrayToLocalDate(a) { + return new Date(a[0], a[1] || 0, a[2] == null ? 1 : a[2], // day of month + a[3] || 0, a[4] || 0, a[5] || 0); + } + function dateToUtcArray(date) { + return [ + date.getUTCFullYear(), + date.getUTCMonth(), + date.getUTCDate(), + date.getUTCHours(), + date.getUTCMinutes(), + date.getUTCSeconds(), + date.getUTCMilliseconds(), + ]; + } + function arrayToUtcDate(a) { + // according to web standards (and Safari), a month index is required. + // massage if only given a year. + if (a.length === 1) { + a = a.concat([0]); + } + return new Date(Date.UTC.apply(Date, a)); + } + // Other Utils + function isValidDate(m) { + return !isNaN(m.valueOf()); + } + function timeAsMs(m) { + return m.getUTCHours() * 1000 * 60 * 60 + + m.getUTCMinutes() * 1000 * 60 + + m.getUTCSeconds() * 1000 + + m.getUTCMilliseconds(); + } + + function createEventInstance(defId, range, forcedStartTzo, forcedEndTzo) { + return { + instanceId: guid(), + defId: defId, + range: range, + forcedStartTzo: forcedStartTzo == null ? null : forcedStartTzo, + forcedEndTzo: forcedEndTzo == null ? null : forcedEndTzo, + }; + } + + var hasOwnProperty = Object.prototype.hasOwnProperty; + // Merges an array of objects into a single object. + // The second argument allows for an array of property names who's object values will be merged together. + function mergeProps(propObjs, complexPropsMap) { + var dest = {}; + if (complexPropsMap) { + for (var name_1 in complexPropsMap) { + var complexObjs = []; + // collect the trailing object values, stopping when a non-object is discovered + for (var i = propObjs.length - 1; i >= 0; i -= 1) { + var val = propObjs[i][name_1]; + if (typeof val === 'object' && val) { // non-null object + complexObjs.unshift(val); + } + else if (val !== undefined) { + dest[name_1] = val; // if there were no objects, this value will be used + break; + } + } + // if the trailing values were objects, use the merged value + if (complexObjs.length) { + dest[name_1] = mergeProps(complexObjs); + } + } + } + // copy values into the destination, going from last to first + for (var i = propObjs.length - 1; i >= 0; i -= 1) { + var props = propObjs[i]; + for (var name_2 in props) { + if (!(name_2 in dest)) { // if already assigned by previous props or complex props, don't reassign + dest[name_2] = props[name_2]; + } + } + } + return dest; + } + function filterHash(hash, func) { + var filtered = {}; + for (var key in hash) { + if (func(hash[key], key)) { + filtered[key] = hash[key]; + } + } + return filtered; + } + function mapHash(hash, func) { + var newHash = {}; + for (var key in hash) { + newHash[key] = func(hash[key], key); + } + return newHash; + } + function arrayToHash(a) { + var hash = {}; + for (var _i = 0, a_1 = a; _i < a_1.length; _i++) { + var item = a_1[_i]; + hash[item] = true; + } + return hash; + } + function buildHashFromArray(a, func) { + var hash = {}; + for (var i = 0; i < a.length; i += 1) { + var tuple = func(a[i], i); + hash[tuple[0]] = tuple[1]; + } + return hash; + } + function hashValuesToArray(obj) { + var a = []; + for (var key in obj) { + a.push(obj[key]); + } + return a; + } + function isPropsEqual(obj0, obj1) { + if (obj0 === obj1) { + return true; + } + for (var key in obj0) { + if (hasOwnProperty.call(obj0, key)) { + if (!(key in obj1)) { + return false; + } + } + } + for (var key in obj1) { + if (hasOwnProperty.call(obj1, key)) { + if (obj0[key] !== obj1[key]) { + return false; + } + } + } + return true; + } + function getUnequalProps(obj0, obj1) { + var keys = []; + for (var key in obj0) { + if (hasOwnProperty.call(obj0, key)) { + if (!(key in obj1)) { + keys.push(key); + } + } + } + for (var key in obj1) { + if (hasOwnProperty.call(obj1, key)) { + if (obj0[key] !== obj1[key]) { + keys.push(key); + } + } + } + return keys; + } + function compareObjs(oldProps, newProps, equalityFuncs) { + if (equalityFuncs === void 0) { equalityFuncs = {}; } + if (oldProps === newProps) { + return true; + } + for (var key in newProps) { + if (key in oldProps && isObjValsEqual(oldProps[key], newProps[key], equalityFuncs[key])) ; + else { + return false; + } + } + // check for props that were omitted in the new + for (var key in oldProps) { + if (!(key in newProps)) { + return false; + } + } + return true; + } + /* + assumed "true" equality for handler names like "onReceiveSomething" + */ + function isObjValsEqual(val0, val1, comparator) { + if (val0 === val1 || comparator === true) { + return true; + } + if (comparator) { + return comparator(val0, val1); + } + return false; + } + function collectFromHash(hash, startIndex, endIndex, step) { + if (startIndex === void 0) { startIndex = 0; } + if (step === void 0) { step = 1; } + var res = []; + if (endIndex == null) { + endIndex = Object.keys(hash).length; + } + for (var i = startIndex; i < endIndex; i += step) { + var val = hash[i]; + if (val !== undefined) { // will disregard undefined for sparse arrays + res.push(val); + } + } + return res; + } + + function parseRecurring(refined, defaultAllDay, dateEnv, recurringTypes) { + for (var i = 0; i < recurringTypes.length; i += 1) { + var parsed = recurringTypes[i].parse(refined, dateEnv); + if (parsed) { + var allDay = refined.allDay; + if (allDay == null) { + allDay = defaultAllDay; + if (allDay == null) { + allDay = parsed.allDayGuess; + if (allDay == null) { + allDay = false; + } + } + } + return { + allDay: allDay, + duration: parsed.duration, + typeData: parsed.typeData, + typeId: i, + }; + } + } + return null; + } + function expandRecurring(eventStore, framingRange, context) { + var dateEnv = context.dateEnv, pluginHooks = context.pluginHooks, options = context.options; + var defs = eventStore.defs, instances = eventStore.instances; + // remove existing recurring instances + // TODO: bad. always expand events as a second step + instances = filterHash(instances, function (instance) { return !defs[instance.defId].recurringDef; }); + for (var defId in defs) { + var def = defs[defId]; + if (def.recurringDef) { + var duration = def.recurringDef.duration; + if (!duration) { + duration = def.allDay ? + options.defaultAllDayEventDuration : + options.defaultTimedEventDuration; + } + var starts = expandRecurringRanges(def, duration, framingRange, dateEnv, pluginHooks.recurringTypes); + for (var _i = 0, starts_1 = starts; _i < starts_1.length; _i++) { + var start = starts_1[_i]; + var instance = createEventInstance(defId, { + start: start, + end: dateEnv.add(start, duration), + }); + instances[instance.instanceId] = instance; + } + } + } + return { defs: defs, instances: instances }; + } + /* + Event MUST have a recurringDef + */ + function expandRecurringRanges(eventDef, duration, framingRange, dateEnv, recurringTypes) { + var typeDef = recurringTypes[eventDef.recurringDef.typeId]; + var markers = typeDef.expand(eventDef.recurringDef.typeData, { + start: dateEnv.subtract(framingRange.start, duration), + end: framingRange.end, + }, dateEnv); + // the recurrence plugins don't guarantee that all-day events are start-of-day, so we have to + if (eventDef.allDay) { + markers = markers.map(startOfDay); + } + return markers; + } + + var INTERNAL_UNITS = ['years', 'months', 'days', 'milliseconds']; + var PARSE_RE = /^(-?)(?:(\d+)\.)?(\d+):(\d\d)(?::(\d\d)(?:\.(\d\d\d))?)?/; + // Parsing and Creation + function createDuration(input, unit) { + var _a; + if (typeof input === 'string') { + return parseString(input); + } + if (typeof input === 'object' && input) { // non-null object + return parseObject(input); + } + if (typeof input === 'number') { + return parseObject((_a = {}, _a[unit || 'milliseconds'] = input, _a)); + } + return null; + } + function parseString(s) { + var m = PARSE_RE.exec(s); + if (m) { + var sign = m[1] ? -1 : 1; + return { + years: 0, + months: 0, + days: sign * (m[2] ? parseInt(m[2], 10) : 0), + milliseconds: sign * ((m[3] ? parseInt(m[3], 10) : 0) * 60 * 60 * 1000 + // hours + (m[4] ? parseInt(m[4], 10) : 0) * 60 * 1000 + // minutes + (m[5] ? parseInt(m[5], 10) : 0) * 1000 + // seconds + (m[6] ? parseInt(m[6], 10) : 0) // ms + ), + }; + } + return null; + } + function parseObject(obj) { + var duration = { + years: obj.years || obj.year || 0, + months: obj.months || obj.month || 0, + days: obj.days || obj.day || 0, + milliseconds: (obj.hours || obj.hour || 0) * 60 * 60 * 1000 + // hours + (obj.minutes || obj.minute || 0) * 60 * 1000 + // minutes + (obj.seconds || obj.second || 0) * 1000 + // seconds + (obj.milliseconds || obj.millisecond || obj.ms || 0), // ms + }; + var weeks = obj.weeks || obj.week; + if (weeks) { + duration.days += weeks * 7; + duration.specifiedWeeks = true; + } + return duration; + } + // Equality + function durationsEqual(d0, d1) { + return d0.years === d1.years && + d0.months === d1.months && + d0.days === d1.days && + d0.milliseconds === d1.milliseconds; + } + function asCleanDays(dur) { + if (!dur.years && !dur.months && !dur.milliseconds) { + return dur.days; + } + return 0; + } + // Simple Math + function addDurations(d0, d1) { + return { + years: d0.years + d1.years, + months: d0.months + d1.months, + days: d0.days + d1.days, + milliseconds: d0.milliseconds + d1.milliseconds, + }; + } + function subtractDurations(d1, d0) { + return { + years: d1.years - d0.years, + months: d1.months - d0.months, + days: d1.days - d0.days, + milliseconds: d1.milliseconds - d0.milliseconds, + }; + } + function multiplyDuration(d, n) { + return { + years: d.years * n, + months: d.months * n, + days: d.days * n, + milliseconds: d.milliseconds * n, + }; + } + // Conversions + // "Rough" because they are based on average-case Gregorian months/years + function asRoughYears(dur) { + return asRoughDays(dur) / 365; + } + function asRoughMonths(dur) { + return asRoughDays(dur) / 30; + } + function asRoughDays(dur) { + return asRoughMs(dur) / 864e5; + } + function asRoughMinutes(dur) { + return asRoughMs(dur) / (1000 * 60); + } + function asRoughSeconds(dur) { + return asRoughMs(dur) / 1000; + } + function asRoughMs(dur) { + return dur.years * (365 * 864e5) + + dur.months * (30 * 864e5) + + dur.days * 864e5 + + dur.milliseconds; + } + // Advanced Math + function wholeDivideDurations(numerator, denominator) { + var res = null; + for (var i = 0; i < INTERNAL_UNITS.length; i += 1) { + var unit = INTERNAL_UNITS[i]; + if (denominator[unit]) { + var localRes = numerator[unit] / denominator[unit]; + if (!isInt(localRes) || (res !== null && res !== localRes)) { + return null; + } + res = localRes; + } + else if (numerator[unit]) { + // needs to divide by something but can't! + return null; + } + } + return res; + } + function greatestDurationDenominator(dur) { + var ms = dur.milliseconds; + if (ms) { + if (ms % 1000 !== 0) { + return { unit: 'millisecond', value: ms }; + } + if (ms % (1000 * 60) !== 0) { + return { unit: 'second', value: ms / 1000 }; + } + if (ms % (1000 * 60 * 60) !== 0) { + return { unit: 'minute', value: ms / (1000 * 60) }; + } + if (ms) { + return { unit: 'hour', value: ms / (1000 * 60 * 60) }; + } + } + if (dur.days) { + if (dur.specifiedWeeks && dur.days % 7 === 0) { + return { unit: 'week', value: dur.days / 7 }; + } + return { unit: 'day', value: dur.days }; + } + if (dur.months) { + return { unit: 'month', value: dur.months }; + } + if (dur.years) { + return { unit: 'year', value: dur.years }; + } + return { unit: 'millisecond', value: 0 }; + } + + // timeZoneOffset is in minutes + function buildIsoString(marker, timeZoneOffset, stripZeroTime) { + if (stripZeroTime === void 0) { stripZeroTime = false; } + var s = marker.toISOString(); + s = s.replace('.000', ''); + if (stripZeroTime) { + s = s.replace('T00:00:00Z', ''); + } + if (s.length > 10) { // time part wasn't stripped, can add timezone info + if (timeZoneOffset == null) { + s = s.replace('Z', ''); + } + else if (timeZoneOffset !== 0) { + s = s.replace('Z', formatTimeZoneOffset(timeZoneOffset, true)); + } + // otherwise, its UTC-0 and we want to keep the Z + } + return s; + } + // formats the date, but with no time part + // TODO: somehow merge with buildIsoString and stripZeroTime + // TODO: rename. omit "string" + function formatDayString(marker) { + return marker.toISOString().replace(/T.*$/, ''); + } + // TODO: use Date::toISOString and use everything after the T? + function formatIsoTimeString(marker) { + return padStart(marker.getUTCHours(), 2) + ':' + + padStart(marker.getUTCMinutes(), 2) + ':' + + padStart(marker.getUTCSeconds(), 2); + } + function formatTimeZoneOffset(minutes, doIso) { + if (doIso === void 0) { doIso = false; } + var sign = minutes < 0 ? '-' : '+'; + var abs = Math.abs(minutes); + var hours = Math.floor(abs / 60); + var mins = Math.round(abs % 60); + if (doIso) { + return sign + padStart(hours, 2) + ":" + padStart(mins, 2); + } + return "GMT" + sign + hours + (mins ? ":" + padStart(mins, 2) : ''); + } + + // TODO: new util arrayify? + function removeExact(array, exactVal) { + var removeCnt = 0; + var i = 0; + while (i < array.length) { + if (array[i] === exactVal) { + array.splice(i, 1); + removeCnt += 1; + } + else { + i += 1; + } + } + return removeCnt; + } + function isArraysEqual(a0, a1, equalityFunc) { + if (a0 === a1) { + return true; + } + var len = a0.length; + var i; + if (len !== a1.length) { // not array? or not same length? + return false; + } + for (i = 0; i < len; i += 1) { + if (!(equalityFunc ? equalityFunc(a0[i], a1[i]) : a0[i] === a1[i])) { + return false; + } + } + return true; + } + + function memoize(workerFunc, resEquality, teardownFunc) { + var currentArgs; + var currentRes; + return function () { + var newArgs = []; + for (var _i = 0; _i < arguments.length; _i++) { + newArgs[_i] = arguments[_i]; + } + if (!currentArgs) { + currentRes = workerFunc.apply(this, newArgs); + } + else if (!isArraysEqual(currentArgs, newArgs)) { + if (teardownFunc) { + teardownFunc(currentRes); + } + var res = workerFunc.apply(this, newArgs); + if (!resEquality || !resEquality(res, currentRes)) { + currentRes = res; + } + } + currentArgs = newArgs; + return currentRes; + }; + } + function memoizeObjArg(workerFunc, resEquality, teardownFunc) { + var _this = this; + var currentArg; + var currentRes; + return function (newArg) { + if (!currentArg) { + currentRes = workerFunc.call(_this, newArg); + } + else if (!isPropsEqual(currentArg, newArg)) { + if (teardownFunc) { + teardownFunc(currentRes); + } + var res = workerFunc.call(_this, newArg); + if (!resEquality || !resEquality(res, currentRes)) { + currentRes = res; + } + } + currentArg = newArg; + return currentRes; + }; + } + function memoizeArraylike(// used at all? + workerFunc, resEquality, teardownFunc) { + var _this = this; + var currentArgSets = []; + var currentResults = []; + return function (newArgSets) { + var currentLen = currentArgSets.length; + var newLen = newArgSets.length; + var i = 0; + for (; i < currentLen; i += 1) { + if (!newArgSets[i]) { // one of the old sets no longer exists + if (teardownFunc) { + teardownFunc(currentResults[i]); + } + } + else if (!isArraysEqual(currentArgSets[i], newArgSets[i])) { + if (teardownFunc) { + teardownFunc(currentResults[i]); + } + var res = workerFunc.apply(_this, newArgSets[i]); + if (!resEquality || !resEquality(res, currentResults[i])) { + currentResults[i] = res; + } + } + } + for (; i < newLen; i += 1) { + currentResults[i] = workerFunc.apply(_this, newArgSets[i]); + } + currentArgSets = newArgSets; + currentResults.splice(newLen); // remove excess + return currentResults; + }; + } + function memoizeHashlike(// used? + workerFunc, resEquality, teardownFunc) { + var _this = this; + var currentArgHash = {}; + var currentResHash = {}; + return function (newArgHash) { + var newResHash = {}; + for (var key in newArgHash) { + if (!currentResHash[key]) { + newResHash[key] = workerFunc.apply(_this, newArgHash[key]); + } + else if (!isArraysEqual(currentArgHash[key], newArgHash[key])) { + if (teardownFunc) { + teardownFunc(currentResHash[key]); + } + var res = workerFunc.apply(_this, newArgHash[key]); + newResHash[key] = (resEquality && resEquality(res, currentResHash[key])) + ? currentResHash[key] + : res; + } + else { + newResHash[key] = currentResHash[key]; + } + } + currentArgHash = newArgHash; + currentResHash = newResHash; + return newResHash; + }; + } + + var EXTENDED_SETTINGS_AND_SEVERITIES = { + week: 3, + separator: 0, + omitZeroMinute: 0, + meridiem: 0, + omitCommas: 0, + }; + var STANDARD_DATE_PROP_SEVERITIES = { + timeZoneName: 7, + era: 6, + year: 5, + month: 4, + day: 2, + weekday: 2, + hour: 1, + minute: 1, + second: 1, + }; + var MERIDIEM_RE = /\s*([ap])\.?m\.?/i; // eats up leading spaces too + var COMMA_RE = /,/g; // we need re for globalness + var MULTI_SPACE_RE = /\s+/g; + var LTR_RE = /\u200e/g; // control character + var UTC_RE = /UTC|GMT/; + var NativeFormatter = /** @class */ (function () { + function NativeFormatter(formatSettings) { + var standardDateProps = {}; + var extendedSettings = {}; + var severity = 0; + for (var name_1 in formatSettings) { + if (name_1 in EXTENDED_SETTINGS_AND_SEVERITIES) { + extendedSettings[name_1] = formatSettings[name_1]; + severity = Math.max(EXTENDED_SETTINGS_AND_SEVERITIES[name_1], severity); + } + else { + standardDateProps[name_1] = formatSettings[name_1]; + if (name_1 in STANDARD_DATE_PROP_SEVERITIES) { // TODO: what about hour12? no severity + severity = Math.max(STANDARD_DATE_PROP_SEVERITIES[name_1], severity); + } + } + } + this.standardDateProps = standardDateProps; + this.extendedSettings = extendedSettings; + this.severity = severity; + this.buildFormattingFunc = memoize(buildFormattingFunc); + } + NativeFormatter.prototype.format = function (date, context) { + return this.buildFormattingFunc(this.standardDateProps, this.extendedSettings, context)(date); + }; + NativeFormatter.prototype.formatRange = function (start, end, context, betterDefaultSeparator) { + var _a = this, standardDateProps = _a.standardDateProps, extendedSettings = _a.extendedSettings; + var diffSeverity = computeMarkerDiffSeverity(start.marker, end.marker, context.calendarSystem); + if (!diffSeverity) { + return this.format(start, context); + } + var biggestUnitForPartial = diffSeverity; + if (biggestUnitForPartial > 1 && // the two dates are different in a way that's larger scale than time + (standardDateProps.year === 'numeric' || standardDateProps.year === '2-digit') && + (standardDateProps.month === 'numeric' || standardDateProps.month === '2-digit') && + (standardDateProps.day === 'numeric' || standardDateProps.day === '2-digit')) { + biggestUnitForPartial = 1; // make it look like the dates are only different in terms of time + } + var full0 = this.format(start, context); + var full1 = this.format(end, context); + if (full0 === full1) { + return full0; + } + var partialDateProps = computePartialFormattingOptions(standardDateProps, biggestUnitForPartial); + var partialFormattingFunc = buildFormattingFunc(partialDateProps, extendedSettings, context); + var partial0 = partialFormattingFunc(start); + var partial1 = partialFormattingFunc(end); + var insertion = findCommonInsertion(full0, partial0, full1, partial1); + var separator = extendedSettings.separator || betterDefaultSeparator || context.defaultSeparator || ''; + if (insertion) { + return insertion.before + partial0 + separator + partial1 + insertion.after; + } + return full0 + separator + full1; + }; + NativeFormatter.prototype.getLargestUnit = function () { + switch (this.severity) { + case 7: + case 6: + case 5: + return 'year'; + case 4: + return 'month'; + case 3: + return 'week'; + case 2: + return 'day'; + default: + return 'time'; // really? + } + }; + return NativeFormatter; + }()); + function buildFormattingFunc(standardDateProps, extendedSettings, context) { + var standardDatePropCnt = Object.keys(standardDateProps).length; + if (standardDatePropCnt === 1 && standardDateProps.timeZoneName === 'short') { + return function (date) { return (formatTimeZoneOffset(date.timeZoneOffset)); }; + } + if (standardDatePropCnt === 0 && extendedSettings.week) { + return function (date) { return (formatWeekNumber(context.computeWeekNumber(date.marker), context.weekText, context.locale, extendedSettings.week)); }; + } + return buildNativeFormattingFunc(standardDateProps, extendedSettings, context); + } + function buildNativeFormattingFunc(standardDateProps, extendedSettings, context) { + standardDateProps = __assign({}, standardDateProps); // copy + extendedSettings = __assign({}, extendedSettings); // copy + sanitizeSettings(standardDateProps, extendedSettings); + standardDateProps.timeZone = 'UTC'; // we leverage the only guaranteed timeZone for our UTC markers + var normalFormat = new Intl.DateTimeFormat(context.locale.codes, standardDateProps); + var zeroFormat; // needed? + if (extendedSettings.omitZeroMinute) { + var zeroProps = __assign({}, standardDateProps); + delete zeroProps.minute; // seconds and ms were already considered in sanitizeSettings + zeroFormat = new Intl.DateTimeFormat(context.locale.codes, zeroProps); + } + return function (date) { + var marker = date.marker; + var format; + if (zeroFormat && !marker.getUTCMinutes()) { + format = zeroFormat; + } + else { + format = normalFormat; + } + var s = format.format(marker); + return postProcess(s, date, standardDateProps, extendedSettings, context); + }; + } + function sanitizeSettings(standardDateProps, extendedSettings) { + // deal with a browser inconsistency where formatting the timezone + // requires that the hour/minute be present. + if (standardDateProps.timeZoneName) { + if (!standardDateProps.hour) { + standardDateProps.hour = '2-digit'; + } + if (!standardDateProps.minute) { + standardDateProps.minute = '2-digit'; + } + } + // only support short timezone names + if (standardDateProps.timeZoneName === 'long') { + standardDateProps.timeZoneName = 'short'; + } + // if requesting to display seconds, MUST display minutes + if (extendedSettings.omitZeroMinute && (standardDateProps.second || standardDateProps.millisecond)) { + delete extendedSettings.omitZeroMinute; + } + } + function postProcess(s, date, standardDateProps, extendedSettings, context) { + s = s.replace(LTR_RE, ''); // remove left-to-right control chars. do first. good for other regexes + if (standardDateProps.timeZoneName === 'short') { + s = injectTzoStr(s, (context.timeZone === 'UTC' || date.timeZoneOffset == null) ? + 'UTC' : // important to normalize for IE, which does "GMT" + formatTimeZoneOffset(date.timeZoneOffset)); + } + if (extendedSettings.omitCommas) { + s = s.replace(COMMA_RE, '').trim(); + } + if (extendedSettings.omitZeroMinute) { + s = s.replace(':00', ''); // zeroFormat doesn't always achieve this + } + // ^ do anything that might create adjacent spaces before this point, + // because MERIDIEM_RE likes to eat up loading spaces + if (extendedSettings.meridiem === false) { + s = s.replace(MERIDIEM_RE, '').trim(); + } + else if (extendedSettings.meridiem === 'narrow') { // a/p + s = s.replace(MERIDIEM_RE, function (m0, m1) { return m1.toLocaleLowerCase(); }); + } + else if (extendedSettings.meridiem === 'short') { // am/pm + s = s.replace(MERIDIEM_RE, function (m0, m1) { return m1.toLocaleLowerCase() + "m"; }); + } + else if (extendedSettings.meridiem === 'lowercase') { // other meridiem transformers already converted to lowercase + s = s.replace(MERIDIEM_RE, function (m0) { return m0.toLocaleLowerCase(); }); + } + s = s.replace(MULTI_SPACE_RE, ' '); + s = s.trim(); + return s; + } + function injectTzoStr(s, tzoStr) { + var replaced = false; + s = s.replace(UTC_RE, function () { + replaced = true; + return tzoStr; + }); + // IE11 doesn't include UTC/GMT in the original string, so append to end + if (!replaced) { + s += " " + tzoStr; + } + return s; + } + function formatWeekNumber(num, weekText, locale, display) { + var parts = []; + if (display === 'narrow') { + parts.push(weekText); + } + else if (display === 'short') { + parts.push(weekText, ' '); + } + // otherwise, considered 'numeric' + parts.push(locale.simpleNumberFormat.format(num)); + if (locale.options.direction === 'rtl') { // TODO: use control characters instead? + parts.reverse(); + } + return parts.join(''); + } + // Range Formatting Utils + // 0 = exactly the same + // 1 = different by time + // and bigger + function computeMarkerDiffSeverity(d0, d1, ca) { + if (ca.getMarkerYear(d0) !== ca.getMarkerYear(d1)) { + return 5; + } + if (ca.getMarkerMonth(d0) !== ca.getMarkerMonth(d1)) { + return 4; + } + if (ca.getMarkerDay(d0) !== ca.getMarkerDay(d1)) { + return 2; + } + if (timeAsMs(d0) !== timeAsMs(d1)) { + return 1; + } + return 0; + } + function computePartialFormattingOptions(options, biggestUnit) { + var partialOptions = {}; + for (var name_2 in options) { + if (!(name_2 in STANDARD_DATE_PROP_SEVERITIES) || // not a date part prop (like timeZone) + STANDARD_DATE_PROP_SEVERITIES[name_2] <= biggestUnit) { + partialOptions[name_2] = options[name_2]; + } + } + return partialOptions; + } + function findCommonInsertion(full0, partial0, full1, partial1) { + var i0 = 0; + while (i0 < full0.length) { + var found0 = full0.indexOf(partial0, i0); + if (found0 === -1) { + break; + } + var before0 = full0.substr(0, found0); + i0 = found0 + partial0.length; + var after0 = full0.substr(i0); + var i1 = 0; + while (i1 < full1.length) { + var found1 = full1.indexOf(partial1, i1); + if (found1 === -1) { + break; + } + var before1 = full1.substr(0, found1); + i1 = found1 + partial1.length; + var after1 = full1.substr(i1); + if (before0 === before1 && after0 === after1) { + return { + before: before0, + after: after0, + }; + } + } + } + return null; + } + + function expandZonedMarker(dateInfo, calendarSystem) { + var a = calendarSystem.markerToArray(dateInfo.marker); + return { + marker: dateInfo.marker, + timeZoneOffset: dateInfo.timeZoneOffset, + array: a, + year: a[0], + month: a[1], + day: a[2], + hour: a[3], + minute: a[4], + second: a[5], + millisecond: a[6], + }; + } + + function createVerboseFormattingArg(start, end, context, betterDefaultSeparator) { + var startInfo = expandZonedMarker(start, context.calendarSystem); + var endInfo = end ? expandZonedMarker(end, context.calendarSystem) : null; + return { + date: startInfo, + start: startInfo, + end: endInfo, + timeZone: context.timeZone, + localeCodes: context.locale.codes, + defaultSeparator: betterDefaultSeparator || context.defaultSeparator, + }; + } + + /* + TODO: fix the terminology of "formatter" vs "formatting func" + */ + /* + At the time of instantiation, this object does not know which cmd-formatting system it will use. + It receives this at the time of formatting, as a setting. + */ + var CmdFormatter = /** @class */ (function () { + function CmdFormatter(cmdStr) { + this.cmdStr = cmdStr; + } + CmdFormatter.prototype.format = function (date, context, betterDefaultSeparator) { + return context.cmdFormatter(this.cmdStr, createVerboseFormattingArg(date, null, context, betterDefaultSeparator)); + }; + CmdFormatter.prototype.formatRange = function (start, end, context, betterDefaultSeparator) { + return context.cmdFormatter(this.cmdStr, createVerboseFormattingArg(start, end, context, betterDefaultSeparator)); + }; + return CmdFormatter; + }()); + + var FuncFormatter = /** @class */ (function () { + function FuncFormatter(func) { + this.func = func; + } + FuncFormatter.prototype.format = function (date, context, betterDefaultSeparator) { + return this.func(createVerboseFormattingArg(date, null, context, betterDefaultSeparator)); + }; + FuncFormatter.prototype.formatRange = function (start, end, context, betterDefaultSeparator) { + return this.func(createVerboseFormattingArg(start, end, context, betterDefaultSeparator)); + }; + return FuncFormatter; + }()); + + function createFormatter(input) { + if (typeof input === 'object' && input) { // non-null object + return new NativeFormatter(input); + } + if (typeof input === 'string') { + return new CmdFormatter(input); + } + if (typeof input === 'function') { + return new FuncFormatter(input); + } + return null; + } + + // base options + // ------------ + var BASE_OPTION_REFINERS = { + navLinkDayClick: identity, + navLinkWeekClick: identity, + duration: createDuration, + bootstrapFontAwesome: identity, + buttonIcons: identity, + customButtons: identity, + defaultAllDayEventDuration: createDuration, + defaultTimedEventDuration: createDuration, + nextDayThreshold: createDuration, + scrollTime: createDuration, + scrollTimeReset: Boolean, + slotMinTime: createDuration, + slotMaxTime: createDuration, + dayPopoverFormat: createFormatter, + slotDuration: createDuration, + snapDuration: createDuration, + headerToolbar: identity, + footerToolbar: identity, + defaultRangeSeparator: String, + titleRangeSeparator: String, + forceEventDuration: Boolean, + dayHeaders: Boolean, + dayHeaderFormat: createFormatter, + dayHeaderClassNames: identity, + dayHeaderContent: identity, + dayHeaderDidMount: identity, + dayHeaderWillUnmount: identity, + dayCellClassNames: identity, + dayCellContent: identity, + dayCellDidMount: identity, + dayCellWillUnmount: identity, + initialView: String, + aspectRatio: Number, + weekends: Boolean, + weekNumberCalculation: identity, + weekNumbers: Boolean, + weekNumberClassNames: identity, + weekNumberContent: identity, + weekNumberDidMount: identity, + weekNumberWillUnmount: identity, + editable: Boolean, + viewClassNames: identity, + viewDidMount: identity, + viewWillUnmount: identity, + nowIndicator: Boolean, + nowIndicatorClassNames: identity, + nowIndicatorContent: identity, + nowIndicatorDidMount: identity, + nowIndicatorWillUnmount: identity, + showNonCurrentDates: Boolean, + lazyFetching: Boolean, + startParam: String, + endParam: String, + timeZoneParam: String, + timeZone: String, + locales: identity, + locale: identity, + themeSystem: String, + dragRevertDuration: Number, + dragScroll: Boolean, + allDayMaintainDuration: Boolean, + unselectAuto: Boolean, + dropAccept: identity, + eventOrder: parseFieldSpecs, + eventOrderStrict: Boolean, + handleWindowResize: Boolean, + windowResizeDelay: Number, + longPressDelay: Number, + eventDragMinDistance: Number, + expandRows: Boolean, + height: identity, + contentHeight: identity, + direction: String, + weekNumberFormat: createFormatter, + eventResizableFromStart: Boolean, + displayEventTime: Boolean, + displayEventEnd: Boolean, + weekText: String, + progressiveEventRendering: Boolean, + businessHours: identity, + initialDate: identity, + now: identity, + eventDataTransform: identity, + stickyHeaderDates: identity, + stickyFooterScrollbar: identity, + viewHeight: identity, + defaultAllDay: Boolean, + eventSourceFailure: identity, + eventSourceSuccess: identity, + eventDisplay: String, + eventStartEditable: Boolean, + eventDurationEditable: Boolean, + eventOverlap: identity, + eventConstraint: identity, + eventAllow: identity, + eventBackgroundColor: String, + eventBorderColor: String, + eventTextColor: String, + eventColor: String, + eventClassNames: identity, + eventContent: identity, + eventDidMount: identity, + eventWillUnmount: identity, + selectConstraint: identity, + selectOverlap: identity, + selectAllow: identity, + droppable: Boolean, + unselectCancel: String, + slotLabelFormat: identity, + slotLaneClassNames: identity, + slotLaneContent: identity, + slotLaneDidMount: identity, + slotLaneWillUnmount: identity, + slotLabelClassNames: identity, + slotLabelContent: identity, + slotLabelDidMount: identity, + slotLabelWillUnmount: identity, + dayMaxEvents: identity, + dayMaxEventRows: identity, + dayMinWidth: Number, + slotLabelInterval: createDuration, + allDayText: String, + allDayClassNames: identity, + allDayContent: identity, + allDayDidMount: identity, + allDayWillUnmount: identity, + slotMinWidth: Number, + navLinks: Boolean, + eventTimeFormat: createFormatter, + rerenderDelay: Number, + moreLinkText: identity, + selectMinDistance: Number, + selectable: Boolean, + selectLongPressDelay: Number, + eventLongPressDelay: Number, + selectMirror: Boolean, + eventMaxStack: Number, + eventMinHeight: Number, + eventMinWidth: Number, + eventShortHeight: Number, + slotEventOverlap: Boolean, + plugins: identity, + firstDay: Number, + dayCount: Number, + dateAlignment: String, + dateIncrement: createDuration, + hiddenDays: identity, + monthMode: Boolean, + fixedWeekCount: Boolean, + validRange: identity, + visibleRange: identity, + titleFormat: identity, + // only used by list-view, but languages define the value, so we need it in base options + noEventsText: String, + moreLinkClick: identity, + moreLinkClassNames: identity, + moreLinkContent: identity, + moreLinkDidMount: identity, + moreLinkWillUnmount: identity, + }; + // do NOT give a type here. need `typeof BASE_OPTION_DEFAULTS` to give real results. + // raw values. + var BASE_OPTION_DEFAULTS = { + eventDisplay: 'auto', + defaultRangeSeparator: ' - ', + titleRangeSeparator: ' \u2013 ', + defaultTimedEventDuration: '01:00:00', + defaultAllDayEventDuration: { day: 1 }, + forceEventDuration: false, + nextDayThreshold: '00:00:00', + dayHeaders: true, + initialView: '', + aspectRatio: 1.35, + headerToolbar: { + start: 'title', + center: '', + end: 'today prev,next', + }, + weekends: true, + weekNumbers: false, + weekNumberCalculation: 'local', + editable: false, + nowIndicator: false, + scrollTime: '06:00:00', + scrollTimeReset: true, + slotMinTime: '00:00:00', + slotMaxTime: '24:00:00', + showNonCurrentDates: true, + lazyFetching: true, + startParam: 'start', + endParam: 'end', + timeZoneParam: 'timeZone', + timeZone: 'local', + locales: [], + locale: '', + themeSystem: 'standard', + dragRevertDuration: 500, + dragScroll: true, + allDayMaintainDuration: false, + unselectAuto: true, + dropAccept: '*', + eventOrder: 'start,-duration,allDay,title', + dayPopoverFormat: { month: 'long', day: 'numeric', year: 'numeric' }, + handleWindowResize: true, + windowResizeDelay: 100, + longPressDelay: 1000, + eventDragMinDistance: 5, + expandRows: false, + navLinks: false, + selectable: false, + eventMinHeight: 15, + eventMinWidth: 30, + eventShortHeight: 30, + }; + // calendar listeners + // ------------------ + var CALENDAR_LISTENER_REFINERS = { + datesSet: identity, + eventsSet: identity, + eventAdd: identity, + eventChange: identity, + eventRemove: identity, + windowResize: identity, + eventClick: identity, + eventMouseEnter: identity, + eventMouseLeave: identity, + select: identity, + unselect: identity, + loading: identity, + // internal + _unmount: identity, + _beforeprint: identity, + _afterprint: identity, + _noEventDrop: identity, + _noEventResize: identity, + _resize: identity, + _scrollRequest: identity, + }; + // calendar-specific options + // ------------------------- + var CALENDAR_OPTION_REFINERS = { + buttonText: identity, + views: identity, + plugins: identity, + initialEvents: identity, + events: identity, + eventSources: identity, + }; + var COMPLEX_OPTION_COMPARATORS = { + headerToolbar: isBoolComplexEqual, + footerToolbar: isBoolComplexEqual, + buttonText: isBoolComplexEqual, + buttonIcons: isBoolComplexEqual, + }; + function isBoolComplexEqual(a, b) { + if (typeof a === 'object' && typeof b === 'object' && a && b) { // both non-null objects + return isPropsEqual(a, b); + } + return a === b; + } + // view-specific options + // --------------------- + var VIEW_OPTION_REFINERS = { + type: String, + component: identity, + buttonText: String, + buttonTextKey: String, + dateProfileGeneratorClass: identity, + usesMinMaxTime: Boolean, + classNames: identity, + content: identity, + didMount: identity, + willUnmount: identity, + }; + // util funcs + // ---------------------------------------------------------------------------------------------------- + function mergeRawOptions(optionSets) { + return mergeProps(optionSets, COMPLEX_OPTION_COMPARATORS); + } + function refineProps(input, refiners) { + var refined = {}; + var extra = {}; + for (var propName in refiners) { + if (propName in input) { + refined[propName] = refiners[propName](input[propName]); + } + } + for (var propName in input) { + if (!(propName in refiners)) { + extra[propName] = input[propName]; + } + } + return { refined: refined, extra: extra }; + } + function identity(raw) { + return raw; + } + + function parseEvents(rawEvents, eventSource, context, allowOpenRange) { + var eventStore = createEmptyEventStore(); + var eventRefiners = buildEventRefiners(context); + for (var _i = 0, rawEvents_1 = rawEvents; _i < rawEvents_1.length; _i++) { + var rawEvent = rawEvents_1[_i]; + var tuple = parseEvent(rawEvent, eventSource, context, allowOpenRange, eventRefiners); + if (tuple) { + eventTupleToStore(tuple, eventStore); + } + } + return eventStore; + } + function eventTupleToStore(tuple, eventStore) { + if (eventStore === void 0) { eventStore = createEmptyEventStore(); } + eventStore.defs[tuple.def.defId] = tuple.def; + if (tuple.instance) { + eventStore.instances[tuple.instance.instanceId] = tuple.instance; + } + return eventStore; + } + // retrieves events that have the same groupId as the instance specified by `instanceId` + // or they are the same as the instance. + // why might instanceId not be in the store? an event from another calendar? + function getRelevantEvents(eventStore, instanceId) { + var instance = eventStore.instances[instanceId]; + if (instance) { + var def_1 = eventStore.defs[instance.defId]; + // get events/instances with same group + var newStore = filterEventStoreDefs(eventStore, function (lookDef) { return isEventDefsGrouped(def_1, lookDef); }); + // add the original + // TODO: wish we could use eventTupleToStore or something like it + newStore.defs[def_1.defId] = def_1; + newStore.instances[instance.instanceId] = instance; + return newStore; + } + return createEmptyEventStore(); + } + function isEventDefsGrouped(def0, def1) { + return Boolean(def0.groupId && def0.groupId === def1.groupId); + } + function createEmptyEventStore() { + return { defs: {}, instances: {} }; + } + function mergeEventStores(store0, store1) { + return { + defs: __assign(__assign({}, store0.defs), store1.defs), + instances: __assign(__assign({}, store0.instances), store1.instances), + }; + } + function filterEventStoreDefs(eventStore, filterFunc) { + var defs = filterHash(eventStore.defs, filterFunc); + var instances = filterHash(eventStore.instances, function (instance) { return (defs[instance.defId] // still exists? + ); }); + return { defs: defs, instances: instances }; + } + function excludeSubEventStore(master, sub) { + var defs = master.defs, instances = master.instances; + var filteredDefs = {}; + var filteredInstances = {}; + for (var defId in defs) { + if (!sub.defs[defId]) { // not explicitly excluded + filteredDefs[defId] = defs[defId]; + } + } + for (var instanceId in instances) { + if (!sub.instances[instanceId] && // not explicitly excluded + filteredDefs[instances[instanceId].defId] // def wasn't filtered away + ) { + filteredInstances[instanceId] = instances[instanceId]; + } + } + return { + defs: filteredDefs, + instances: filteredInstances, + }; + } + + function normalizeConstraint(input, context) { + if (Array.isArray(input)) { + return parseEvents(input, null, context, true); // allowOpenRange=true + } + if (typeof input === 'object' && input) { // non-null object + return parseEvents([input], null, context, true); // allowOpenRange=true + } + if (input != null) { + return String(input); + } + return null; + } + + function parseClassNames(raw) { + if (Array.isArray(raw)) { + return raw; + } + if (typeof raw === 'string') { + return raw.split(/\s+/); + } + return []; + } + + // TODO: better called "EventSettings" or "EventConfig" + // TODO: move this file into structs + // TODO: separate constraint/overlap/allow, because selection uses only that, not other props + var EVENT_UI_REFINERS = { + display: String, + editable: Boolean, + startEditable: Boolean, + durationEditable: Boolean, + constraint: identity, + overlap: identity, + allow: identity, + className: parseClassNames, + classNames: parseClassNames, + color: String, + backgroundColor: String, + borderColor: String, + textColor: String, + }; + var EMPTY_EVENT_UI = { + display: null, + startEditable: null, + durationEditable: null, + constraints: [], + overlap: null, + allows: [], + backgroundColor: '', + borderColor: '', + textColor: '', + classNames: [], + }; + function createEventUi(refined, context) { + var constraint = normalizeConstraint(refined.constraint, context); + return { + display: refined.display || null, + startEditable: refined.startEditable != null ? refined.startEditable : refined.editable, + durationEditable: refined.durationEditable != null ? refined.durationEditable : refined.editable, + constraints: constraint != null ? [constraint] : [], + overlap: refined.overlap != null ? refined.overlap : null, + allows: refined.allow != null ? [refined.allow] : [], + backgroundColor: refined.backgroundColor || refined.color || '', + borderColor: refined.borderColor || refined.color || '', + textColor: refined.textColor || '', + classNames: (refined.className || []).concat(refined.classNames || []), // join singular and plural + }; + } + // TODO: prevent against problems with <2 args! + function combineEventUis(uis) { + return uis.reduce(combineTwoEventUis, EMPTY_EVENT_UI); + } + function combineTwoEventUis(item0, item1) { + return { + display: item1.display != null ? item1.display : item0.display, + startEditable: item1.startEditable != null ? item1.startEditable : item0.startEditable, + durationEditable: item1.durationEditable != null ? item1.durationEditable : item0.durationEditable, + constraints: item0.constraints.concat(item1.constraints), + overlap: typeof item1.overlap === 'boolean' ? item1.overlap : item0.overlap, + allows: item0.allows.concat(item1.allows), + backgroundColor: item1.backgroundColor || item0.backgroundColor, + borderColor: item1.borderColor || item0.borderColor, + textColor: item1.textColor || item0.textColor, + classNames: item0.classNames.concat(item1.classNames), + }; + } + + var EVENT_NON_DATE_REFINERS = { + id: String, + groupId: String, + title: String, + url: String, + }; + var EVENT_DATE_REFINERS = { + start: identity, + end: identity, + date: identity, + allDay: Boolean, + }; + var EVENT_REFINERS = __assign(__assign(__assign({}, EVENT_NON_DATE_REFINERS), EVENT_DATE_REFINERS), { extendedProps: identity }); + function parseEvent(raw, eventSource, context, allowOpenRange, refiners) { + if (refiners === void 0) { refiners = buildEventRefiners(context); } + var _a = refineEventDef(raw, context, refiners), refined = _a.refined, extra = _a.extra; + var defaultAllDay = computeIsDefaultAllDay(eventSource, context); + var recurringRes = parseRecurring(refined, defaultAllDay, context.dateEnv, context.pluginHooks.recurringTypes); + if (recurringRes) { + var def = parseEventDef(refined, extra, eventSource ? eventSource.sourceId : '', recurringRes.allDay, Boolean(recurringRes.duration), context); + def.recurringDef = { + typeId: recurringRes.typeId, + typeData: recurringRes.typeData, + duration: recurringRes.duration, + }; + return { def: def, instance: null }; + } + var singleRes = parseSingle(refined, defaultAllDay, context, allowOpenRange); + if (singleRes) { + var def = parseEventDef(refined, extra, eventSource ? eventSource.sourceId : '', singleRes.allDay, singleRes.hasEnd, context); + var instance = createEventInstance(def.defId, singleRes.range, singleRes.forcedStartTzo, singleRes.forcedEndTzo); + return { def: def, instance: instance }; + } + return null; + } + function refineEventDef(raw, context, refiners) { + if (refiners === void 0) { refiners = buildEventRefiners(context); } + return refineProps(raw, refiners); + } + function buildEventRefiners(context) { + return __assign(__assign(__assign({}, EVENT_UI_REFINERS), EVENT_REFINERS), context.pluginHooks.eventRefiners); + } + /* + Will NOT populate extendedProps with the leftover properties. + Will NOT populate date-related props. + */ + function parseEventDef(refined, extra, sourceId, allDay, hasEnd, context) { + var def = { + title: refined.title || '', + groupId: refined.groupId || '', + publicId: refined.id || '', + url: refined.url || '', + recurringDef: null, + defId: guid(), + sourceId: sourceId, + allDay: allDay, + hasEnd: hasEnd, + ui: createEventUi(refined, context), + extendedProps: __assign(__assign({}, (refined.extendedProps || {})), extra), + }; + for (var _i = 0, _a = context.pluginHooks.eventDefMemberAdders; _i < _a.length; _i++) { + var memberAdder = _a[_i]; + __assign(def, memberAdder(refined)); + } + // help out EventApi from having user modify props + Object.freeze(def.ui.classNames); + Object.freeze(def.extendedProps); + return def; + } + function parseSingle(refined, defaultAllDay, context, allowOpenRange) { + var allDay = refined.allDay; + var startMeta; + var startMarker = null; + var hasEnd = false; + var endMeta; + var endMarker = null; + var startInput = refined.start != null ? refined.start : refined.date; + startMeta = context.dateEnv.createMarkerMeta(startInput); + if (startMeta) { + startMarker = startMeta.marker; + } + else if (!allowOpenRange) { + return null; + } + if (refined.end != null) { + endMeta = context.dateEnv.createMarkerMeta(refined.end); + } + if (allDay == null) { + if (defaultAllDay != null) { + allDay = defaultAllDay; + } + else { + // fall back to the date props LAST + allDay = (!startMeta || startMeta.isTimeUnspecified) && + (!endMeta || endMeta.isTimeUnspecified); + } + } + if (allDay && startMarker) { + startMarker = startOfDay(startMarker); + } + if (endMeta) { + endMarker = endMeta.marker; + if (allDay) { + endMarker = startOfDay(endMarker); + } + if (startMarker && endMarker <= startMarker) { + endMarker = null; + } + } + if (endMarker) { + hasEnd = true; + } + else if (!allowOpenRange) { + hasEnd = context.options.forceEventDuration || false; + endMarker = context.dateEnv.add(startMarker, allDay ? + context.options.defaultAllDayEventDuration : + context.options.defaultTimedEventDuration); + } + return { + allDay: allDay, + hasEnd: hasEnd, + range: { start: startMarker, end: endMarker }, + forcedStartTzo: startMeta ? startMeta.forcedTzo : null, + forcedEndTzo: endMeta ? endMeta.forcedTzo : null, + }; + } + function computeIsDefaultAllDay(eventSource, context) { + var res = null; + if (eventSource) { + res = eventSource.defaultAllDay; + } + if (res == null) { + res = context.options.defaultAllDay; + } + return res; + } + + /* Date stuff that doesn't belong in datelib core + ----------------------------------------------------------------------------------------------------------------------*/ + // given a timed range, computes an all-day range that has the same exact duration, + // but whose start time is aligned with the start of the day. + function computeAlignedDayRange(timedRange) { + var dayCnt = Math.floor(diffDays(timedRange.start, timedRange.end)) || 1; + var start = startOfDay(timedRange.start); + var end = addDays(start, dayCnt); + return { start: start, end: end }; + } + // given a timed range, computes an all-day range based on how for the end date bleeds into the next day + // TODO: give nextDayThreshold a default arg + function computeVisibleDayRange(timedRange, nextDayThreshold) { + if (nextDayThreshold === void 0) { nextDayThreshold = createDuration(0); } + var startDay = null; + var endDay = null; + if (timedRange.end) { + endDay = startOfDay(timedRange.end); + var endTimeMS = timedRange.end.valueOf() - endDay.valueOf(); // # of milliseconds into `endDay` + // If the end time is actually inclusively part of the next day and is equal to or + // beyond the next day threshold, adjust the end to be the exclusive end of `endDay`. + // Otherwise, leaving it as inclusive will cause it to exclude `endDay`. + if (endTimeMS && endTimeMS >= asRoughMs(nextDayThreshold)) { + endDay = addDays(endDay, 1); + } + } + if (timedRange.start) { + startDay = startOfDay(timedRange.start); // the beginning of the day the range starts + // If end is within `startDay` but not past nextDayThreshold, assign the default duration of one day. + if (endDay && endDay <= startDay) { + endDay = addDays(startDay, 1); + } + } + return { start: startDay, end: endDay }; + } + // spans from one day into another? + function isMultiDayRange(range) { + var visibleRange = computeVisibleDayRange(range); + return diffDays(visibleRange.start, visibleRange.end) > 1; + } + function diffDates(date0, date1, dateEnv, largeUnit) { + if (largeUnit === 'year') { + return createDuration(dateEnv.diffWholeYears(date0, date1), 'year'); + } + if (largeUnit === 'month') { + return createDuration(dateEnv.diffWholeMonths(date0, date1), 'month'); + } + return diffDayAndTime(date0, date1); // returns a duration + } + + function parseRange(input, dateEnv) { + var start = null; + var end = null; + if (input.start) { + start = dateEnv.createMarker(input.start); + } + if (input.end) { + end = dateEnv.createMarker(input.end); + } + if (!start && !end) { + return null; + } + if (start && end && end < start) { + return null; + } + return { start: start, end: end }; + } + // SIDE-EFFECT: will mutate ranges. + // Will return a new array result. + function invertRanges(ranges, constraintRange) { + var invertedRanges = []; + var start = constraintRange.start; // the end of the previous range. the start of the new range + var i; + var dateRange; + // ranges need to be in order. required for our date-walking algorithm + ranges.sort(compareRanges); + for (i = 0; i < ranges.length; i += 1) { + dateRange = ranges[i]; + // add the span of time before the event (if there is any) + if (dateRange.start > start) { // compare millisecond time (skip any ambig logic) + invertedRanges.push({ start: start, end: dateRange.start }); + } + if (dateRange.end > start) { + start = dateRange.end; + } + } + // add the span of time after the last event (if there is any) + if (start < constraintRange.end) { // compare millisecond time (skip any ambig logic) + invertedRanges.push({ start: start, end: constraintRange.end }); + } + return invertedRanges; + } + function compareRanges(range0, range1) { + return range0.start.valueOf() - range1.start.valueOf(); // earlier ranges go first + } + function intersectRanges(range0, range1) { + var start = range0.start, end = range0.end; + var newRange = null; + if (range1.start !== null) { + if (start === null) { + start = range1.start; + } + else { + start = new Date(Math.max(start.valueOf(), range1.start.valueOf())); + } + } + if (range1.end != null) { + if (end === null) { + end = range1.end; + } + else { + end = new Date(Math.min(end.valueOf(), range1.end.valueOf())); + } + } + if (start === null || end === null || start < end) { + newRange = { start: start, end: end }; + } + return newRange; + } + function rangesEqual(range0, range1) { + return (range0.start === null ? null : range0.start.valueOf()) === (range1.start === null ? null : range1.start.valueOf()) && + (range0.end === null ? null : range0.end.valueOf()) === (range1.end === null ? null : range1.end.valueOf()); + } + function rangesIntersect(range0, range1) { + return (range0.end === null || range1.start === null || range0.end > range1.start) && + (range0.start === null || range1.end === null || range0.start < range1.end); + } + function rangeContainsRange(outerRange, innerRange) { + return (outerRange.start === null || (innerRange.start !== null && innerRange.start >= outerRange.start)) && + (outerRange.end === null || (innerRange.end !== null && innerRange.end <= outerRange.end)); + } + function rangeContainsMarker(range, date) { + return (range.start === null || date >= range.start) && + (range.end === null || date < range.end); + } + // If the given date is not within the given range, move it inside. + // (If it's past the end, make it one millisecond before the end). + function constrainMarkerToRange(date, range) { + if (range.start != null && date < range.start) { + return range.start; + } + if (range.end != null && date >= range.end) { + return new Date(range.end.valueOf() - 1); + } + return date; + } + + /* + Specifying nextDayThreshold signals that all-day ranges should be sliced. + */ + function sliceEventStore(eventStore, eventUiBases, framingRange, nextDayThreshold) { + var inverseBgByGroupId = {}; + var inverseBgByDefId = {}; + var defByGroupId = {}; + var bgRanges = []; + var fgRanges = []; + var eventUis = compileEventUis(eventStore.defs, eventUiBases); + for (var defId in eventStore.defs) { + var def = eventStore.defs[defId]; + var ui = eventUis[def.defId]; + if (ui.display === 'inverse-background') { + if (def.groupId) { + inverseBgByGroupId[def.groupId] = []; + if (!defByGroupId[def.groupId]) { + defByGroupId[def.groupId] = def; + } + } + else { + inverseBgByDefId[defId] = []; + } + } + } + for (var instanceId in eventStore.instances) { + var instance = eventStore.instances[instanceId]; + var def = eventStore.defs[instance.defId]; + var ui = eventUis[def.defId]; + var origRange = instance.range; + var normalRange = (!def.allDay && nextDayThreshold) ? + computeVisibleDayRange(origRange, nextDayThreshold) : + origRange; + var slicedRange = intersectRanges(normalRange, framingRange); + if (slicedRange) { + if (ui.display === 'inverse-background') { + if (def.groupId) { + inverseBgByGroupId[def.groupId].push(slicedRange); + } + else { + inverseBgByDefId[instance.defId].push(slicedRange); + } + } + else if (ui.display !== 'none') { + (ui.display === 'background' ? bgRanges : fgRanges).push({ + def: def, + ui: ui, + instance: instance, + range: slicedRange, + isStart: normalRange.start && normalRange.start.valueOf() === slicedRange.start.valueOf(), + isEnd: normalRange.end && normalRange.end.valueOf() === slicedRange.end.valueOf(), + }); + } + } + } + for (var groupId in inverseBgByGroupId) { // BY GROUP + var ranges = inverseBgByGroupId[groupId]; + var invertedRanges = invertRanges(ranges, framingRange); + for (var _i = 0, invertedRanges_1 = invertedRanges; _i < invertedRanges_1.length; _i++) { + var invertedRange = invertedRanges_1[_i]; + var def = defByGroupId[groupId]; + var ui = eventUis[def.defId]; + bgRanges.push({ + def: def, + ui: ui, + instance: null, + range: invertedRange, + isStart: false, + isEnd: false, + }); + } + } + for (var defId in inverseBgByDefId) { + var ranges = inverseBgByDefId[defId]; + var invertedRanges = invertRanges(ranges, framingRange); + for (var _a = 0, invertedRanges_2 = invertedRanges; _a < invertedRanges_2.length; _a++) { + var invertedRange = invertedRanges_2[_a]; + bgRanges.push({ + def: eventStore.defs[defId], + ui: eventUis[defId], + instance: null, + range: invertedRange, + isStart: false, + isEnd: false, + }); + } + } + return { bg: bgRanges, fg: fgRanges }; + } + function hasBgRendering(def) { + return def.ui.display === 'background' || def.ui.display === 'inverse-background'; + } + function setElSeg(el, seg) { + el.fcSeg = seg; + } + function getElSeg(el) { + return el.fcSeg || + el.parentNode.fcSeg || // for the harness + null; + } + // event ui computation + function compileEventUis(eventDefs, eventUiBases) { + return mapHash(eventDefs, function (eventDef) { return compileEventUi(eventDef, eventUiBases); }); + } + function compileEventUi(eventDef, eventUiBases) { + var uis = []; + if (eventUiBases['']) { + uis.push(eventUiBases['']); + } + if (eventUiBases[eventDef.defId]) { + uis.push(eventUiBases[eventDef.defId]); + } + uis.push(eventDef.ui); + return combineEventUis(uis); + } + function sortEventSegs(segs, eventOrderSpecs) { + var objs = segs.map(buildSegCompareObj); + objs.sort(function (obj0, obj1) { return compareByFieldSpecs(obj0, obj1, eventOrderSpecs); }); + return objs.map(function (c) { return c._seg; }); + } + // returns a object with all primitive props that can be compared + function buildSegCompareObj(seg) { + var eventRange = seg.eventRange; + var eventDef = eventRange.def; + var range = eventRange.instance ? eventRange.instance.range : eventRange.range; + var start = range.start ? range.start.valueOf() : 0; // TODO: better support for open-range events + var end = range.end ? range.end.valueOf() : 0; // " + return __assign(__assign(__assign({}, eventDef.extendedProps), eventDef), { id: eventDef.publicId, start: start, + end: end, duration: end - start, allDay: Number(eventDef.allDay), _seg: seg }); + } + function computeSegDraggable(seg, context) { + var pluginHooks = context.pluginHooks; + var transformers = pluginHooks.isDraggableTransformers; + var _a = seg.eventRange, def = _a.def, ui = _a.ui; + var val = ui.startEditable; + for (var _i = 0, transformers_1 = transformers; _i < transformers_1.length; _i++) { + var transformer = transformers_1[_i]; + val = transformer(val, def, ui, context); + } + return val; + } + function computeSegStartResizable(seg, context) { + return seg.isStart && seg.eventRange.ui.durationEditable && context.options.eventResizableFromStart; + } + function computeSegEndResizable(seg, context) { + return seg.isEnd && seg.eventRange.ui.durationEditable; + } + function buildSegTimeText(seg, timeFormat, context, defaultDisplayEventTime, // defaults to true + defaultDisplayEventEnd, // defaults to true + startOverride, endOverride) { + var dateEnv = context.dateEnv, options = context.options; + var displayEventTime = options.displayEventTime, displayEventEnd = options.displayEventEnd; + var eventDef = seg.eventRange.def; + var eventInstance = seg.eventRange.instance; + if (displayEventTime == null) { + displayEventTime = defaultDisplayEventTime !== false; + } + if (displayEventEnd == null) { + displayEventEnd = defaultDisplayEventEnd !== false; + } + var wholeEventStart = eventInstance.range.start; + var wholeEventEnd = eventInstance.range.end; + var segStart = startOverride || seg.start || seg.eventRange.range.start; + var segEnd = endOverride || seg.end || seg.eventRange.range.end; + var isStartDay = startOfDay(wholeEventStart).valueOf() === startOfDay(segStart).valueOf(); + var isEndDay = startOfDay(addMs(wholeEventEnd, -1)).valueOf() === startOfDay(addMs(segEnd, -1)).valueOf(); + if (displayEventTime && !eventDef.allDay && (isStartDay || isEndDay)) { + segStart = isStartDay ? wholeEventStart : segStart; + segEnd = isEndDay ? wholeEventEnd : segEnd; + if (displayEventEnd && eventDef.hasEnd) { + return dateEnv.formatRange(segStart, segEnd, timeFormat, { + forcedStartTzo: startOverride ? null : eventInstance.forcedStartTzo, + forcedEndTzo: endOverride ? null : eventInstance.forcedEndTzo, + }); + } + return dateEnv.format(segStart, timeFormat, { + forcedTzo: startOverride ? null : eventInstance.forcedStartTzo, // nooooo, same + }); + } + return ''; + } + function getSegMeta(seg, todayRange, nowDate) { + var segRange = seg.eventRange.range; + return { + isPast: segRange.end < (nowDate || todayRange.start), + isFuture: segRange.start >= (nowDate || todayRange.end), + isToday: todayRange && rangeContainsMarker(todayRange, segRange.start), + }; + } + function getEventClassNames(props) { + var classNames = ['fc-event']; + if (props.isMirror) { + classNames.push('fc-event-mirror'); + } + if (props.isDraggable) { + classNames.push('fc-event-draggable'); + } + if (props.isStartResizable || props.isEndResizable) { + classNames.push('fc-event-resizable'); + } + if (props.isDragging) { + classNames.push('fc-event-dragging'); + } + if (props.isResizing) { + classNames.push('fc-event-resizing'); + } + if (props.isSelected) { + classNames.push('fc-event-selected'); + } + if (props.isStart) { + classNames.push('fc-event-start'); + } + if (props.isEnd) { + classNames.push('fc-event-end'); + } + if (props.isPast) { + classNames.push('fc-event-past'); + } + if (props.isToday) { + classNames.push('fc-event-today'); + } + if (props.isFuture) { + classNames.push('fc-event-future'); + } + return classNames; + } + function buildEventRangeKey(eventRange) { + return eventRange.instance + ? eventRange.instance.instanceId + : eventRange.def.defId + ":" + eventRange.range.start.toISOString(); + // inverse-background events don't have specific instances. TODO: better solution + } + + var STANDARD_PROPS = { + start: identity, + end: identity, + allDay: Boolean, + }; + function parseDateSpan(raw, dateEnv, defaultDuration) { + var span = parseOpenDateSpan(raw, dateEnv); + var range = span.range; + if (!range.start) { + return null; + } + if (!range.end) { + if (defaultDuration == null) { + return null; + } + range.end = dateEnv.add(range.start, defaultDuration); + } + return span; + } + /* + TODO: somehow combine with parseRange? + Will return null if the start/end props were present but parsed invalidly. + */ + function parseOpenDateSpan(raw, dateEnv) { + var _a = refineProps(raw, STANDARD_PROPS), standardProps = _a.refined, extra = _a.extra; + var startMeta = standardProps.start ? dateEnv.createMarkerMeta(standardProps.start) : null; + var endMeta = standardProps.end ? dateEnv.createMarkerMeta(standardProps.end) : null; + var allDay = standardProps.allDay; + if (allDay == null) { + allDay = (startMeta && startMeta.isTimeUnspecified) && + (!endMeta || endMeta.isTimeUnspecified); + } + return __assign({ range: { + start: startMeta ? startMeta.marker : null, + end: endMeta ? endMeta.marker : null, + }, allDay: allDay }, extra); + } + function isDateSpansEqual(span0, span1) { + return rangesEqual(span0.range, span1.range) && + span0.allDay === span1.allDay && + isSpanPropsEqual(span0, span1); + } + // the NON-DATE-RELATED props + function isSpanPropsEqual(span0, span1) { + for (var propName in span1) { + if (propName !== 'range' && propName !== 'allDay') { + if (span0[propName] !== span1[propName]) { + return false; + } + } + } + // are there any props that span0 has that span1 DOESN'T have? + // both have range/allDay, so no need to special-case. + for (var propName in span0) { + if (!(propName in span1)) { + return false; + } + } + return true; + } + function buildDateSpanApi(span, dateEnv) { + return __assign(__assign({}, buildRangeApi(span.range, dateEnv, span.allDay)), { allDay: span.allDay }); + } + function buildRangeApiWithTimeZone(range, dateEnv, omitTime) { + return __assign(__assign({}, buildRangeApi(range, dateEnv, omitTime)), { timeZone: dateEnv.timeZone }); + } + function buildRangeApi(range, dateEnv, omitTime) { + return { + start: dateEnv.toDate(range.start), + end: dateEnv.toDate(range.end), + startStr: dateEnv.formatIso(range.start, { omitTime: omitTime }), + endStr: dateEnv.formatIso(range.end, { omitTime: omitTime }), + }; + } + function fabricateEventRange(dateSpan, eventUiBases, context) { + var res = refineEventDef({ editable: false }, context); + var def = parseEventDef(res.refined, res.extra, '', // sourceId + dateSpan.allDay, true, // hasEnd + context); + return { + def: def, + ui: compileEventUi(def, eventUiBases), + instance: createEventInstance(def.defId, dateSpan.range), + range: dateSpan.range, + isStart: true, + isEnd: true, + }; + } + + function triggerDateSelect(selection, pev, context) { + context.emitter.trigger('select', __assign(__assign({}, buildDateSpanApiWithContext(selection, context)), { jsEvent: pev ? pev.origEvent : null, view: context.viewApi || context.calendarApi.view })); + } + function triggerDateUnselect(pev, context) { + context.emitter.trigger('unselect', { + jsEvent: pev ? pev.origEvent : null, + view: context.viewApi || context.calendarApi.view, + }); + } + function buildDateSpanApiWithContext(dateSpan, context) { + var props = {}; + for (var _i = 0, _a = context.pluginHooks.dateSpanTransforms; _i < _a.length; _i++) { + var transform = _a[_i]; + __assign(props, transform(dateSpan, context)); + } + __assign(props, buildDateSpanApi(dateSpan, context.dateEnv)); + return props; + } + // Given an event's allDay status and start date, return what its fallback end date should be. + // TODO: rename to computeDefaultEventEnd + function getDefaultEventEnd(allDay, marker, context) { + var dateEnv = context.dateEnv, options = context.options; + var end = marker; + if (allDay) { + end = startOfDay(end); + end = dateEnv.add(end, options.defaultAllDayEventDuration); + } + else { + end = dateEnv.add(end, options.defaultTimedEventDuration); + } + return end; + } + + // applies the mutation to ALL defs/instances within the event store + function applyMutationToEventStore(eventStore, eventConfigBase, mutation, context) { + var eventConfigs = compileEventUis(eventStore.defs, eventConfigBase); + var dest = createEmptyEventStore(); + for (var defId in eventStore.defs) { + var def = eventStore.defs[defId]; + dest.defs[defId] = applyMutationToEventDef(def, eventConfigs[defId], mutation, context); + } + for (var instanceId in eventStore.instances) { + var instance = eventStore.instances[instanceId]; + var def = dest.defs[instance.defId]; // important to grab the newly modified def + dest.instances[instanceId] = applyMutationToEventInstance(instance, def, eventConfigs[instance.defId], mutation, context); + } + return dest; + } + function applyMutationToEventDef(eventDef, eventConfig, mutation, context) { + var standardProps = mutation.standardProps || {}; + // if hasEnd has not been specified, guess a good value based on deltas. + // if duration will change, there's no way the default duration will persist, + // and thus, we need to mark the event as having a real end + if (standardProps.hasEnd == null && + eventConfig.durationEditable && + (mutation.startDelta || mutation.endDelta)) { + standardProps.hasEnd = true; // TODO: is this mutation okay? + } + var copy = __assign(__assign(__assign({}, eventDef), standardProps), { ui: __assign(__assign({}, eventDef.ui), standardProps.ui) }); + if (mutation.extendedProps) { + copy.extendedProps = __assign(__assign({}, copy.extendedProps), mutation.extendedProps); + } + for (var _i = 0, _a = context.pluginHooks.eventDefMutationAppliers; _i < _a.length; _i++) { + var applier = _a[_i]; + applier(copy, mutation, context); + } + if (!copy.hasEnd && context.options.forceEventDuration) { + copy.hasEnd = true; + } + return copy; + } + function applyMutationToEventInstance(eventInstance, eventDef, // must first be modified by applyMutationToEventDef + eventConfig, mutation, context) { + var dateEnv = context.dateEnv; + var forceAllDay = mutation.standardProps && mutation.standardProps.allDay === true; + var clearEnd = mutation.standardProps && mutation.standardProps.hasEnd === false; + var copy = __assign({}, eventInstance); + if (forceAllDay) { + copy.range = computeAlignedDayRange(copy.range); + } + if (mutation.datesDelta && eventConfig.startEditable) { + copy.range = { + start: dateEnv.add(copy.range.start, mutation.datesDelta), + end: dateEnv.add(copy.range.end, mutation.datesDelta), + }; + } + if (mutation.startDelta && eventConfig.durationEditable) { + copy.range = { + start: dateEnv.add(copy.range.start, mutation.startDelta), + end: copy.range.end, + }; + } + if (mutation.endDelta && eventConfig.durationEditable) { + copy.range = { + start: copy.range.start, + end: dateEnv.add(copy.range.end, mutation.endDelta), + }; + } + if (clearEnd) { + copy.range = { + start: copy.range.start, + end: getDefaultEventEnd(eventDef.allDay, copy.range.start, context), + }; + } + // in case event was all-day but the supplied deltas were not + // better util for this? + if (eventDef.allDay) { + copy.range = { + start: startOfDay(copy.range.start), + end: startOfDay(copy.range.end), + }; + } + // handle invalid durations + if (copy.range.end < copy.range.start) { + copy.range.end = getDefaultEventEnd(eventDef.allDay, copy.range.start, context); + } + return copy; + } + + // no public types yet. when there are, export from: + // import {} from './api-type-deps' + var ViewApi = /** @class */ (function () { + function ViewApi(type, getCurrentData, dateEnv) { + this.type = type; + this.getCurrentData = getCurrentData; + this.dateEnv = dateEnv; + } + Object.defineProperty(ViewApi.prototype, "calendar", { + get: function () { + return this.getCurrentData().calendarApi; + }, + enumerable: false, + configurable: true + }); + Object.defineProperty(ViewApi.prototype, "title", { + get: function () { + return this.getCurrentData().viewTitle; + }, + enumerable: false, + configurable: true + }); + Object.defineProperty(ViewApi.prototype, "activeStart", { + get: function () { + return this.dateEnv.toDate(this.getCurrentData().dateProfile.activeRange.start); + }, + enumerable: false, + configurable: true + }); + Object.defineProperty(ViewApi.prototype, "activeEnd", { + get: function () { + return this.dateEnv.toDate(this.getCurrentData().dateProfile.activeRange.end); + }, + enumerable: false, + configurable: true + }); + Object.defineProperty(ViewApi.prototype, "currentStart", { + get: function () { + return this.dateEnv.toDate(this.getCurrentData().dateProfile.currentRange.start); + }, + enumerable: false, + configurable: true + }); + Object.defineProperty(ViewApi.prototype, "currentEnd", { + get: function () { + return this.dateEnv.toDate(this.getCurrentData().dateProfile.currentRange.end); + }, + enumerable: false, + configurable: true + }); + ViewApi.prototype.getOption = function (name) { + return this.getCurrentData().options[name]; // are the view-specific options + }; + return ViewApi; + }()); + + var EVENT_SOURCE_REFINERS$1 = { + id: String, + defaultAllDay: Boolean, + url: String, + format: String, + events: identity, + eventDataTransform: identity, + // for any network-related sources + success: identity, + failure: identity, + }; + function parseEventSource(raw, context, refiners) { + if (refiners === void 0) { refiners = buildEventSourceRefiners(context); } + var rawObj; + if (typeof raw === 'string') { + rawObj = { url: raw }; + } + else if (typeof raw === 'function' || Array.isArray(raw)) { + rawObj = { events: raw }; + } + else if (typeof raw === 'object' && raw) { // not null + rawObj = raw; + } + if (rawObj) { + var _a = refineProps(rawObj, refiners), refined = _a.refined, extra = _a.extra; + var metaRes = buildEventSourceMeta(refined, context); + if (metaRes) { + return { + _raw: raw, + isFetching: false, + latestFetchId: '', + fetchRange: null, + defaultAllDay: refined.defaultAllDay, + eventDataTransform: refined.eventDataTransform, + success: refined.success, + failure: refined.failure, + publicId: refined.id || '', + sourceId: guid(), + sourceDefId: metaRes.sourceDefId, + meta: metaRes.meta, + ui: createEventUi(refined, context), + extendedProps: extra, + }; + } + } + return null; + } + function buildEventSourceRefiners(context) { + return __assign(__assign(__assign({}, EVENT_UI_REFINERS), EVENT_SOURCE_REFINERS$1), context.pluginHooks.eventSourceRefiners); + } + function buildEventSourceMeta(raw, context) { + var defs = context.pluginHooks.eventSourceDefs; + for (var i = defs.length - 1; i >= 0; i -= 1) { // later-added plugins take precedence + var def = defs[i]; + var meta = def.parseMeta(raw); + if (meta) { + return { sourceDefId: i, meta: meta }; + } + } + return null; + } + + function reduceCurrentDate(currentDate, action) { + switch (action.type) { + case 'CHANGE_DATE': + return action.dateMarker; + default: + return currentDate; + } + } + function getInitialDate(options, dateEnv) { + var initialDateInput = options.initialDate; + // compute the initial ambig-timezone date + if (initialDateInput != null) { + return dateEnv.createMarker(initialDateInput); + } + return getNow(options.now, dateEnv); // getNow already returns unzoned + } + function getNow(nowInput, dateEnv) { + if (typeof nowInput === 'function') { + nowInput = nowInput(); + } + if (nowInput == null) { + return dateEnv.createNowMarker(); + } + return dateEnv.createMarker(nowInput); + } + + var CalendarApi = /** @class */ (function () { + function CalendarApi() { + } + CalendarApi.prototype.getCurrentData = function () { + return this.currentDataManager.getCurrentData(); + }; + CalendarApi.prototype.dispatch = function (action) { + return this.currentDataManager.dispatch(action); + }; + Object.defineProperty(CalendarApi.prototype, "view", { + get: function () { return this.getCurrentData().viewApi; } // for public API + , + enumerable: false, + configurable: true + }); + CalendarApi.prototype.batchRendering = function (callback) { + callback(); + }; + CalendarApi.prototype.updateSize = function () { + this.trigger('_resize', true); + }; + // Options + // ----------------------------------------------------------------------------------------------------------------- + CalendarApi.prototype.setOption = function (name, val) { + this.dispatch({ + type: 'SET_OPTION', + optionName: name, + rawOptionValue: val, + }); + }; + CalendarApi.prototype.getOption = function (name) { + return this.currentDataManager.currentCalendarOptionsInput[name]; + }; + CalendarApi.prototype.getAvailableLocaleCodes = function () { + return Object.keys(this.getCurrentData().availableRawLocales); + }; + // Trigger + // ----------------------------------------------------------------------------------------------------------------- + CalendarApi.prototype.on = function (handlerName, handler) { + var currentDataManager = this.currentDataManager; + if (currentDataManager.currentCalendarOptionsRefiners[handlerName]) { + currentDataManager.emitter.on(handlerName, handler); + } + else { + console.warn("Unknown listener name '" + handlerName + "'"); + } + }; + CalendarApi.prototype.off = function (handlerName, handler) { + this.currentDataManager.emitter.off(handlerName, handler); + }; + // not meant for public use + CalendarApi.prototype.trigger = function (handlerName) { + var _a; + var args = []; + for (var _i = 1; _i < arguments.length; _i++) { + args[_i - 1] = arguments[_i]; + } + (_a = this.currentDataManager.emitter).trigger.apply(_a, __spreadArray([handlerName], args)); + }; + // View + // ----------------------------------------------------------------------------------------------------------------- + CalendarApi.prototype.changeView = function (viewType, dateOrRange) { + var _this = this; + this.batchRendering(function () { + _this.unselect(); + if (dateOrRange) { + if (dateOrRange.start && dateOrRange.end) { // a range + _this.dispatch({ + type: 'CHANGE_VIEW_TYPE', + viewType: viewType, + }); + _this.dispatch({ + type: 'SET_OPTION', + optionName: 'visibleRange', + rawOptionValue: dateOrRange, + }); + } + else { + var dateEnv = _this.getCurrentData().dateEnv; + _this.dispatch({ + type: 'CHANGE_VIEW_TYPE', + viewType: viewType, + dateMarker: dateEnv.createMarker(dateOrRange), + }); + } + } + else { + _this.dispatch({ + type: 'CHANGE_VIEW_TYPE', + viewType: viewType, + }); + } + }); + }; + // Forces navigation to a view for the given date. + // `viewType` can be a specific view name or a generic one like "week" or "day". + // needs to change + CalendarApi.prototype.zoomTo = function (dateMarker, viewType) { + var state = this.getCurrentData(); + var spec; + viewType = viewType || 'day'; // day is default zoom + spec = state.viewSpecs[viewType] || this.getUnitViewSpec(viewType); + this.unselect(); + if (spec) { + this.dispatch({ + type: 'CHANGE_VIEW_TYPE', + viewType: spec.type, + dateMarker: dateMarker, + }); + } + else { + this.dispatch({ + type: 'CHANGE_DATE', + dateMarker: dateMarker, + }); + } + }; + // Given a duration singular unit, like "week" or "day", finds a matching view spec. + // Preference is given to views that have corresponding buttons. + CalendarApi.prototype.getUnitViewSpec = function (unit) { + var _a = this.getCurrentData(), viewSpecs = _a.viewSpecs, toolbarConfig = _a.toolbarConfig; + var viewTypes = [].concat(toolbarConfig.viewsWithButtons); + var i; + var spec; + for (var viewType in viewSpecs) { + viewTypes.push(viewType); + } + for (i = 0; i < viewTypes.length; i += 1) { + spec = viewSpecs[viewTypes[i]]; + if (spec) { + if (spec.singleUnit === unit) { + return spec; + } + } + } + return null; + }; + // Current Date + // ----------------------------------------------------------------------------------------------------------------- + CalendarApi.prototype.prev = function () { + this.unselect(); + this.dispatch({ type: 'PREV' }); + }; + CalendarApi.prototype.next = function () { + this.unselect(); + this.dispatch({ type: 'NEXT' }); + }; + CalendarApi.prototype.prevYear = function () { + var state = this.getCurrentData(); + this.unselect(); + this.dispatch({ + type: 'CHANGE_DATE', + dateMarker: state.dateEnv.addYears(state.currentDate, -1), + }); + }; + CalendarApi.prototype.nextYear = function () { + var state = this.getCurrentData(); + this.unselect(); + this.dispatch({ + type: 'CHANGE_DATE', + dateMarker: state.dateEnv.addYears(state.currentDate, 1), + }); + }; + CalendarApi.prototype.today = function () { + var state = this.getCurrentData(); + this.unselect(); + this.dispatch({ + type: 'CHANGE_DATE', + dateMarker: getNow(state.calendarOptions.now, state.dateEnv), + }); + }; + CalendarApi.prototype.gotoDate = function (zonedDateInput) { + var state = this.getCurrentData(); + this.unselect(); + this.dispatch({ + type: 'CHANGE_DATE', + dateMarker: state.dateEnv.createMarker(zonedDateInput), + }); + }; + CalendarApi.prototype.incrementDate = function (deltaInput) { + var state = this.getCurrentData(); + var delta = createDuration(deltaInput); + if (delta) { // else, warn about invalid input? + this.unselect(); + this.dispatch({ + type: 'CHANGE_DATE', + dateMarker: state.dateEnv.add(state.currentDate, delta), + }); + } + }; + // for external API + CalendarApi.prototype.getDate = function () { + var state = this.getCurrentData(); + return state.dateEnv.toDate(state.currentDate); + }; + // Date Formatting Utils + // ----------------------------------------------------------------------------------------------------------------- + CalendarApi.prototype.formatDate = function (d, formatter) { + var dateEnv = this.getCurrentData().dateEnv; + return dateEnv.format(dateEnv.createMarker(d), createFormatter(formatter)); + }; + // `settings` is for formatter AND isEndExclusive + CalendarApi.prototype.formatRange = function (d0, d1, settings) { + var dateEnv = this.getCurrentData().dateEnv; + return dateEnv.formatRange(dateEnv.createMarker(d0), dateEnv.createMarker(d1), createFormatter(settings), settings); + }; + CalendarApi.prototype.formatIso = function (d, omitTime) { + var dateEnv = this.getCurrentData().dateEnv; + return dateEnv.formatIso(dateEnv.createMarker(d), { omitTime: omitTime }); + }; + // Date Selection / Event Selection / DayClick + // ----------------------------------------------------------------------------------------------------------------- + // this public method receives start/end dates in any format, with any timezone + // NOTE: args were changed from v3 + CalendarApi.prototype.select = function (dateOrObj, endDate) { + var selectionInput; + if (endDate == null) { + if (dateOrObj.start != null) { + selectionInput = dateOrObj; + } + else { + selectionInput = { + start: dateOrObj, + end: null, + }; + } + } + else { + selectionInput = { + start: dateOrObj, + end: endDate, + }; + } + var state = this.getCurrentData(); + var selection = parseDateSpan(selectionInput, state.dateEnv, createDuration({ days: 1 })); + if (selection) { // throw parse error otherwise? + this.dispatch({ type: 'SELECT_DATES', selection: selection }); + triggerDateSelect(selection, null, state); + } + }; + // public method + CalendarApi.prototype.unselect = function (pev) { + var state = this.getCurrentData(); + if (state.dateSelection) { + this.dispatch({ type: 'UNSELECT_DATES' }); + triggerDateUnselect(pev, state); + } + }; + // Public Events API + // ----------------------------------------------------------------------------------------------------------------- + CalendarApi.prototype.addEvent = function (eventInput, sourceInput) { + if (eventInput instanceof EventApi) { + var def = eventInput._def; + var instance = eventInput._instance; + var currentData = this.getCurrentData(); + // not already present? don't want to add an old snapshot + if (!currentData.eventStore.defs[def.defId]) { + this.dispatch({ + type: 'ADD_EVENTS', + eventStore: eventTupleToStore({ def: def, instance: instance }), // TODO: better util for two args? + }); + this.triggerEventAdd(eventInput); + } + return eventInput; + } + var state = this.getCurrentData(); + var eventSource; + if (sourceInput instanceof EventSourceApi) { + eventSource = sourceInput.internalEventSource; + } + else if (typeof sourceInput === 'boolean') { + if (sourceInput) { // true. part of the first event source + eventSource = hashValuesToArray(state.eventSources)[0]; + } + } + else if (sourceInput != null) { // an ID. accepts a number too + var sourceApi = this.getEventSourceById(sourceInput); // TODO: use an internal function + if (!sourceApi) { + console.warn("Could not find an event source with ID \"" + sourceInput + "\""); // TODO: test + return null; + } + eventSource = sourceApi.internalEventSource; + } + var tuple = parseEvent(eventInput, eventSource, state, false); + if (tuple) { + var newEventApi = new EventApi(state, tuple.def, tuple.def.recurringDef ? null : tuple.instance); + this.dispatch({ + type: 'ADD_EVENTS', + eventStore: eventTupleToStore(tuple), + }); + this.triggerEventAdd(newEventApi); + return newEventApi; + } + return null; + }; + CalendarApi.prototype.triggerEventAdd = function (eventApi) { + var _this = this; + var emitter = this.getCurrentData().emitter; + emitter.trigger('eventAdd', { + event: eventApi, + relatedEvents: [], + revert: function () { + _this.dispatch({ + type: 'REMOVE_EVENTS', + eventStore: eventApiToStore(eventApi), + }); + }, + }); + }; + // TODO: optimize + CalendarApi.prototype.getEventById = function (id) { + var state = this.getCurrentData(); + var _a = state.eventStore, defs = _a.defs, instances = _a.instances; + id = String(id); + for (var defId in defs) { + var def = defs[defId]; + if (def.publicId === id) { + if (def.recurringDef) { + return new EventApi(state, def, null); + } + for (var instanceId in instances) { + var instance = instances[instanceId]; + if (instance.defId === def.defId) { + return new EventApi(state, def, instance); + } + } + } + } + return null; + }; + CalendarApi.prototype.getEvents = function () { + var currentData = this.getCurrentData(); + return buildEventApis(currentData.eventStore, currentData); + }; + CalendarApi.prototype.removeAllEvents = function () { + this.dispatch({ type: 'REMOVE_ALL_EVENTS' }); + }; + // Public Event Sources API + // ----------------------------------------------------------------------------------------------------------------- + CalendarApi.prototype.getEventSources = function () { + var state = this.getCurrentData(); + var sourceHash = state.eventSources; + var sourceApis = []; + for (var internalId in sourceHash) { + sourceApis.push(new EventSourceApi(state, sourceHash[internalId])); + } + return sourceApis; + }; + CalendarApi.prototype.getEventSourceById = function (id) { + var state = this.getCurrentData(); + var sourceHash = state.eventSources; + id = String(id); + for (var sourceId in sourceHash) { + if (sourceHash[sourceId].publicId === id) { + return new EventSourceApi(state, sourceHash[sourceId]); + } + } + return null; + }; + CalendarApi.prototype.addEventSource = function (sourceInput) { + var state = this.getCurrentData(); + if (sourceInput instanceof EventSourceApi) { + // not already present? don't want to add an old snapshot + if (!state.eventSources[sourceInput.internalEventSource.sourceId]) { + this.dispatch({ + type: 'ADD_EVENT_SOURCES', + sources: [sourceInput.internalEventSource], + }); + } + return sourceInput; + } + var eventSource = parseEventSource(sourceInput, state); + if (eventSource) { // TODO: error otherwise? + this.dispatch({ type: 'ADD_EVENT_SOURCES', sources: [eventSource] }); + return new EventSourceApi(state, eventSource); + } + return null; + }; + CalendarApi.prototype.removeAllEventSources = function () { + this.dispatch({ type: 'REMOVE_ALL_EVENT_SOURCES' }); + }; + CalendarApi.prototype.refetchEvents = function () { + this.dispatch({ type: 'FETCH_EVENT_SOURCES', isRefetch: true }); + }; + // Scroll + // ----------------------------------------------------------------------------------------------------------------- + CalendarApi.prototype.scrollToTime = function (timeInput) { + var time = createDuration(timeInput); + if (time) { + this.trigger('_scrollRequest', { time: time }); + } + }; + return CalendarApi; + }()); + + var EventApi = /** @class */ (function () { + // instance will be null if expressing a recurring event that has no current instances, + // OR if trying to validate an incoming external event that has no dates assigned + function EventApi(context, def, instance) { + this._context = context; + this._def = def; + this._instance = instance || null; + } + /* + TODO: make event struct more responsible for this + */ + EventApi.prototype.setProp = function (name, val) { + var _a, _b; + if (name in EVENT_DATE_REFINERS) { + console.warn('Could not set date-related prop \'name\'. Use one of the date-related methods instead.'); + // TODO: make proper aliasing system? + } + else if (name === 'id') { + val = EVENT_NON_DATE_REFINERS[name](val); + this.mutate({ + standardProps: { publicId: val }, // hardcoded internal name + }); + } + else if (name in EVENT_NON_DATE_REFINERS) { + val = EVENT_NON_DATE_REFINERS[name](val); + this.mutate({ + standardProps: (_a = {}, _a[name] = val, _a), + }); + } + else if (name in EVENT_UI_REFINERS) { + var ui = EVENT_UI_REFINERS[name](val); + if (name === 'color') { + ui = { backgroundColor: val, borderColor: val }; + } + else if (name === 'editable') { + ui = { startEditable: val, durationEditable: val }; + } + else { + ui = (_b = {}, _b[name] = val, _b); + } + this.mutate({ + standardProps: { ui: ui }, + }); + } + else { + console.warn("Could not set prop '" + name + "'. Use setExtendedProp instead."); + } + }; + EventApi.prototype.setExtendedProp = function (name, val) { + var _a; + this.mutate({ + extendedProps: (_a = {}, _a[name] = val, _a), + }); + }; + EventApi.prototype.setStart = function (startInput, options) { + if (options === void 0) { options = {}; } + var dateEnv = this._context.dateEnv; + var start = dateEnv.createMarker(startInput); + if (start && this._instance) { // TODO: warning if parsed bad + var instanceRange = this._instance.range; + var startDelta = diffDates(instanceRange.start, start, dateEnv, options.granularity); // what if parsed bad!? + if (options.maintainDuration) { + this.mutate({ datesDelta: startDelta }); + } + else { + this.mutate({ startDelta: startDelta }); + } + } + }; + EventApi.prototype.setEnd = function (endInput, options) { + if (options === void 0) { options = {}; } + var dateEnv = this._context.dateEnv; + var end; + if (endInput != null) { + end = dateEnv.createMarker(endInput); + if (!end) { + return; // TODO: warning if parsed bad + } + } + if (this._instance) { + if (end) { + var endDelta = diffDates(this._instance.range.end, end, dateEnv, options.granularity); + this.mutate({ endDelta: endDelta }); + } + else { + this.mutate({ standardProps: { hasEnd: false } }); + } + } + }; + EventApi.prototype.setDates = function (startInput, endInput, options) { + if (options === void 0) { options = {}; } + var dateEnv = this._context.dateEnv; + var standardProps = { allDay: options.allDay }; + var start = dateEnv.createMarker(startInput); + var end; + if (!start) { + return; // TODO: warning if parsed bad + } + if (endInput != null) { + end = dateEnv.createMarker(endInput); + if (!end) { // TODO: warning if parsed bad + return; + } + } + if (this._instance) { + var instanceRange = this._instance.range; + // when computing the diff for an event being converted to all-day, + // compute diff off of the all-day values the way event-mutation does. + if (options.allDay === true) { + instanceRange = computeAlignedDayRange(instanceRange); + } + var startDelta = diffDates(instanceRange.start, start, dateEnv, options.granularity); + if (end) { + var endDelta = diffDates(instanceRange.end, end, dateEnv, options.granularity); + if (durationsEqual(startDelta, endDelta)) { + this.mutate({ datesDelta: startDelta, standardProps: standardProps }); + } + else { + this.mutate({ startDelta: startDelta, endDelta: endDelta, standardProps: standardProps }); + } + } + else { // means "clear the end" + standardProps.hasEnd = false; + this.mutate({ datesDelta: startDelta, standardProps: standardProps }); + } + } + }; + EventApi.prototype.moveStart = function (deltaInput) { + var delta = createDuration(deltaInput); + if (delta) { // TODO: warning if parsed bad + this.mutate({ startDelta: delta }); + } + }; + EventApi.prototype.moveEnd = function (deltaInput) { + var delta = createDuration(deltaInput); + if (delta) { // TODO: warning if parsed bad + this.mutate({ endDelta: delta }); + } + }; + EventApi.prototype.moveDates = function (deltaInput) { + var delta = createDuration(deltaInput); + if (delta) { // TODO: warning if parsed bad + this.mutate({ datesDelta: delta }); + } + }; + EventApi.prototype.setAllDay = function (allDay, options) { + if (options === void 0) { options = {}; } + var standardProps = { allDay: allDay }; + var maintainDuration = options.maintainDuration; + if (maintainDuration == null) { + maintainDuration = this._context.options.allDayMaintainDuration; + } + if (this._def.allDay !== allDay) { + standardProps.hasEnd = maintainDuration; + } + this.mutate({ standardProps: standardProps }); + }; + EventApi.prototype.formatRange = function (formatInput) { + var dateEnv = this._context.dateEnv; + var instance = this._instance; + var formatter = createFormatter(formatInput); + if (this._def.hasEnd) { + return dateEnv.formatRange(instance.range.start, instance.range.end, formatter, { + forcedStartTzo: instance.forcedStartTzo, + forcedEndTzo: instance.forcedEndTzo, + }); + } + return dateEnv.format(instance.range.start, formatter, { + forcedTzo: instance.forcedStartTzo, + }); + }; + EventApi.prototype.mutate = function (mutation) { + var instance = this._instance; + if (instance) { + var def = this._def; + var context_1 = this._context; + var eventStore_1 = context_1.getCurrentData().eventStore; + var relevantEvents = getRelevantEvents(eventStore_1, instance.instanceId); + var eventConfigBase = { + '': { + display: '', + startEditable: true, + durationEditable: true, + constraints: [], + overlap: null, + allows: [], + backgroundColor: '', + borderColor: '', + textColor: '', + classNames: [], + }, + }; + relevantEvents = applyMutationToEventStore(relevantEvents, eventConfigBase, mutation, context_1); + var oldEvent = new EventApi(context_1, def, instance); // snapshot + this._def = relevantEvents.defs[def.defId]; + this._instance = relevantEvents.instances[instance.instanceId]; + context_1.dispatch({ + type: 'MERGE_EVENTS', + eventStore: relevantEvents, + }); + context_1.emitter.trigger('eventChange', { + oldEvent: oldEvent, + event: this, + relatedEvents: buildEventApis(relevantEvents, context_1, instance), + revert: function () { + context_1.dispatch({ + type: 'RESET_EVENTS', + eventStore: eventStore_1, + }); + }, + }); + } + }; + EventApi.prototype.remove = function () { + var context = this._context; + var asStore = eventApiToStore(this); + context.dispatch({ + type: 'REMOVE_EVENTS', + eventStore: asStore, + }); + context.emitter.trigger('eventRemove', { + event: this, + relatedEvents: [], + revert: function () { + context.dispatch({ + type: 'MERGE_EVENTS', + eventStore: asStore, + }); + }, + }); + }; + Object.defineProperty(EventApi.prototype, "source", { + get: function () { + var sourceId = this._def.sourceId; + if (sourceId) { + return new EventSourceApi(this._context, this._context.getCurrentData().eventSources[sourceId]); + } + return null; + }, + enumerable: false, + configurable: true + }); + Object.defineProperty(EventApi.prototype, "start", { + get: function () { + return this._instance ? + this._context.dateEnv.toDate(this._instance.range.start) : + null; + }, + enumerable: false, + configurable: true + }); + Object.defineProperty(EventApi.prototype, "end", { + get: function () { + return (this._instance && this._def.hasEnd) ? + this._context.dateEnv.toDate(this._instance.range.end) : + null; + }, + enumerable: false, + configurable: true + }); + Object.defineProperty(EventApi.prototype, "startStr", { + get: function () { + var instance = this._instance; + if (instance) { + return this._context.dateEnv.formatIso(instance.range.start, { + omitTime: this._def.allDay, + forcedTzo: instance.forcedStartTzo, + }); + } + return ''; + }, + enumerable: false, + configurable: true + }); + Object.defineProperty(EventApi.prototype, "endStr", { + get: function () { + var instance = this._instance; + if (instance && this._def.hasEnd) { + return this._context.dateEnv.formatIso(instance.range.end, { + omitTime: this._def.allDay, + forcedTzo: instance.forcedEndTzo, + }); + } + return ''; + }, + enumerable: false, + configurable: true + }); + Object.defineProperty(EventApi.prototype, "id", { + // computable props that all access the def + // TODO: find a TypeScript-compatible way to do this at scale + get: function () { return this._def.publicId; }, + enumerable: false, + configurable: true + }); + Object.defineProperty(EventApi.prototype, "groupId", { + get: function () { return this._def.groupId; }, + enumerable: false, + configurable: true + }); + Object.defineProperty(EventApi.prototype, "allDay", { + get: function () { return this._def.allDay; }, + enumerable: false, + configurable: true + }); + Object.defineProperty(EventApi.prototype, "title", { + get: function () { return this._def.title; }, + enumerable: false, + configurable: true + }); + Object.defineProperty(EventApi.prototype, "url", { + get: function () { return this._def.url; }, + enumerable: false, + configurable: true + }); + Object.defineProperty(EventApi.prototype, "display", { + get: function () { return this._def.ui.display || 'auto'; } // bad. just normalize the type earlier + , + enumerable: false, + configurable: true + }); + Object.defineProperty(EventApi.prototype, "startEditable", { + get: function () { return this._def.ui.startEditable; }, + enumerable: false, + configurable: true + }); + Object.defineProperty(EventApi.prototype, "durationEditable", { + get: function () { return this._def.ui.durationEditable; }, + enumerable: false, + configurable: true + }); + Object.defineProperty(EventApi.prototype, "constraint", { + get: function () { return this._def.ui.constraints[0] || null; }, + enumerable: false, + configurable: true + }); + Object.defineProperty(EventApi.prototype, "overlap", { + get: function () { return this._def.ui.overlap; }, + enumerable: false, + configurable: true + }); + Object.defineProperty(EventApi.prototype, "allow", { + get: function () { return this._def.ui.allows[0] || null; }, + enumerable: false, + configurable: true + }); + Object.defineProperty(EventApi.prototype, "backgroundColor", { + get: function () { return this._def.ui.backgroundColor; }, + enumerable: false, + configurable: true + }); + Object.defineProperty(EventApi.prototype, "borderColor", { + get: function () { return this._def.ui.borderColor; }, + enumerable: false, + configurable: true + }); + Object.defineProperty(EventApi.prototype, "textColor", { + get: function () { return this._def.ui.textColor; }, + enumerable: false, + configurable: true + }); + Object.defineProperty(EventApi.prototype, "classNames", { + // NOTE: user can't modify these because Object.freeze was called in event-def parsing + get: function () { return this._def.ui.classNames; }, + enumerable: false, + configurable: true + }); + Object.defineProperty(EventApi.prototype, "extendedProps", { + get: function () { return this._def.extendedProps; }, + enumerable: false, + configurable: true + }); + EventApi.prototype.toPlainObject = function (settings) { + if (settings === void 0) { settings = {}; } + var def = this._def; + var ui = def.ui; + var _a = this, startStr = _a.startStr, endStr = _a.endStr; + var res = {}; + if (def.title) { + res.title = def.title; + } + if (startStr) { + res.start = startStr; + } + if (endStr) { + res.end = endStr; + } + if (def.publicId) { + res.id = def.publicId; + } + if (def.groupId) { + res.groupId = def.groupId; + } + if (def.url) { + res.url = def.url; + } + if (ui.display && ui.display !== 'auto') { + res.display = ui.display; + } + // TODO: what about recurring-event properties??? + // TODO: include startEditable/durationEditable/constraint/overlap/allow + if (settings.collapseColor && ui.backgroundColor && ui.backgroundColor === ui.borderColor) { + res.color = ui.backgroundColor; + } + else { + if (ui.backgroundColor) { + res.backgroundColor = ui.backgroundColor; + } + if (ui.borderColor) { + res.borderColor = ui.borderColor; + } + } + if (ui.textColor) { + res.textColor = ui.textColor; + } + if (ui.classNames.length) { + res.classNames = ui.classNames; + } + if (Object.keys(def.extendedProps).length) { + if (settings.collapseExtendedProps) { + __assign(res, def.extendedProps); + } + else { + res.extendedProps = def.extendedProps; + } + } + return res; + }; + EventApi.prototype.toJSON = function () { + return this.toPlainObject(); + }; + return EventApi; + }()); + function eventApiToStore(eventApi) { + var _a, _b; + var def = eventApi._def; + var instance = eventApi._instance; + return { + defs: (_a = {}, _a[def.defId] = def, _a), + instances: instance + ? (_b = {}, _b[instance.instanceId] = instance, _b) : {}, + }; + } + function buildEventApis(eventStore, context, excludeInstance) { + var defs = eventStore.defs, instances = eventStore.instances; + var eventApis = []; + var excludeInstanceId = excludeInstance ? excludeInstance.instanceId : ''; + for (var id in instances) { + var instance = instances[id]; + var def = defs[instance.defId]; + if (instance.instanceId !== excludeInstanceId) { + eventApis.push(new EventApi(context, def, instance)); + } + } + return eventApis; + } + + var calendarSystemClassMap = {}; + function registerCalendarSystem(name, theClass) { + calendarSystemClassMap[name] = theClass; + } + function createCalendarSystem(name) { + return new calendarSystemClassMap[name](); + } + var GregorianCalendarSystem = /** @class */ (function () { + function GregorianCalendarSystem() { + } + GregorianCalendarSystem.prototype.getMarkerYear = function (d) { + return d.getUTCFullYear(); + }; + GregorianCalendarSystem.prototype.getMarkerMonth = function (d) { + return d.getUTCMonth(); + }; + GregorianCalendarSystem.prototype.getMarkerDay = function (d) { + return d.getUTCDate(); + }; + GregorianCalendarSystem.prototype.arrayToMarker = function (arr) { + return arrayToUtcDate(arr); + }; + GregorianCalendarSystem.prototype.markerToArray = function (marker) { + return dateToUtcArray(marker); + }; + return GregorianCalendarSystem; + }()); + registerCalendarSystem('gregory', GregorianCalendarSystem); + + var ISO_RE = /^\s*(\d{4})(-?(\d{2})(-?(\d{2})([T ](\d{2}):?(\d{2})(:?(\d{2})(\.(\d+))?)?(Z|(([-+])(\d{2})(:?(\d{2}))?))?)?)?)?$/; + function parse(str) { + var m = ISO_RE.exec(str); + if (m) { + var marker = new Date(Date.UTC(Number(m[1]), m[3] ? Number(m[3]) - 1 : 0, Number(m[5] || 1), Number(m[7] || 0), Number(m[8] || 0), Number(m[10] || 0), m[12] ? Number("0." + m[12]) * 1000 : 0)); + if (isValidDate(marker)) { + var timeZoneOffset = null; + if (m[13]) { + timeZoneOffset = (m[15] === '-' ? -1 : 1) * (Number(m[16] || 0) * 60 + + Number(m[18] || 0)); + } + return { + marker: marker, + isTimeUnspecified: !m[6], + timeZoneOffset: timeZoneOffset, + }; + } + } + return null; + } + + var DateEnv = /** @class */ (function () { + function DateEnv(settings) { + var timeZone = this.timeZone = settings.timeZone; + var isNamedTimeZone = timeZone !== 'local' && timeZone !== 'UTC'; + if (settings.namedTimeZoneImpl && isNamedTimeZone) { + this.namedTimeZoneImpl = new settings.namedTimeZoneImpl(timeZone); + } + this.canComputeOffset = Boolean(!isNamedTimeZone || this.namedTimeZoneImpl); + this.calendarSystem = createCalendarSystem(settings.calendarSystem); + this.locale = settings.locale; + this.weekDow = settings.locale.week.dow; + this.weekDoy = settings.locale.week.doy; + if (settings.weekNumberCalculation === 'ISO') { + this.weekDow = 1; + this.weekDoy = 4; + } + if (typeof settings.firstDay === 'number') { + this.weekDow = settings.firstDay; + } + if (typeof settings.weekNumberCalculation === 'function') { + this.weekNumberFunc = settings.weekNumberCalculation; + } + this.weekText = settings.weekText != null ? settings.weekText : settings.locale.options.weekText; + this.cmdFormatter = settings.cmdFormatter; + this.defaultSeparator = settings.defaultSeparator; + } + // Creating / Parsing + DateEnv.prototype.createMarker = function (input) { + var meta = this.createMarkerMeta(input); + if (meta === null) { + return null; + } + return meta.marker; + }; + DateEnv.prototype.createNowMarker = function () { + if (this.canComputeOffset) { + return this.timestampToMarker(new Date().valueOf()); + } + // if we can't compute the current date val for a timezone, + // better to give the current local date vals than UTC + return arrayToUtcDate(dateToLocalArray(new Date())); + }; + DateEnv.prototype.createMarkerMeta = function (input) { + if (typeof input === 'string') { + return this.parse(input); + } + var marker = null; + if (typeof input === 'number') { + marker = this.timestampToMarker(input); + } + else if (input instanceof Date) { + input = input.valueOf(); + if (!isNaN(input)) { + marker = this.timestampToMarker(input); + } + } + else if (Array.isArray(input)) { + marker = arrayToUtcDate(input); + } + if (marker === null || !isValidDate(marker)) { + return null; + } + return { marker: marker, isTimeUnspecified: false, forcedTzo: null }; + }; + DateEnv.prototype.parse = function (s) { + var parts = parse(s); + if (parts === null) { + return null; + } + var marker = parts.marker; + var forcedTzo = null; + if (parts.timeZoneOffset !== null) { + if (this.canComputeOffset) { + marker = this.timestampToMarker(marker.valueOf() - parts.timeZoneOffset * 60 * 1000); + } + else { + forcedTzo = parts.timeZoneOffset; + } + } + return { marker: marker, isTimeUnspecified: parts.isTimeUnspecified, forcedTzo: forcedTzo }; + }; + // Accessors + DateEnv.prototype.getYear = function (marker) { + return this.calendarSystem.getMarkerYear(marker); + }; + DateEnv.prototype.getMonth = function (marker) { + return this.calendarSystem.getMarkerMonth(marker); + }; + // Adding / Subtracting + DateEnv.prototype.add = function (marker, dur) { + var a = this.calendarSystem.markerToArray(marker); + a[0] += dur.years; + a[1] += dur.months; + a[2] += dur.days; + a[6] += dur.milliseconds; + return this.calendarSystem.arrayToMarker(a); + }; + DateEnv.prototype.subtract = function (marker, dur) { + var a = this.calendarSystem.markerToArray(marker); + a[0] -= dur.years; + a[1] -= dur.months; + a[2] -= dur.days; + a[6] -= dur.milliseconds; + return this.calendarSystem.arrayToMarker(a); + }; + DateEnv.prototype.addYears = function (marker, n) { + var a = this.calendarSystem.markerToArray(marker); + a[0] += n; + return this.calendarSystem.arrayToMarker(a); + }; + DateEnv.prototype.addMonths = function (marker, n) { + var a = this.calendarSystem.markerToArray(marker); + a[1] += n; + return this.calendarSystem.arrayToMarker(a); + }; + // Diffing Whole Units + DateEnv.prototype.diffWholeYears = function (m0, m1) { + var calendarSystem = this.calendarSystem; + if (timeAsMs(m0) === timeAsMs(m1) && + calendarSystem.getMarkerDay(m0) === calendarSystem.getMarkerDay(m1) && + calendarSystem.getMarkerMonth(m0) === calendarSystem.getMarkerMonth(m1)) { + return calendarSystem.getMarkerYear(m1) - calendarSystem.getMarkerYear(m0); + } + return null; + }; + DateEnv.prototype.diffWholeMonths = function (m0, m1) { + var calendarSystem = this.calendarSystem; + if (timeAsMs(m0) === timeAsMs(m1) && + calendarSystem.getMarkerDay(m0) === calendarSystem.getMarkerDay(m1)) { + return (calendarSystem.getMarkerMonth(m1) - calendarSystem.getMarkerMonth(m0)) + + (calendarSystem.getMarkerYear(m1) - calendarSystem.getMarkerYear(m0)) * 12; + } + return null; + }; + // Range / Duration + DateEnv.prototype.greatestWholeUnit = function (m0, m1) { + var n = this.diffWholeYears(m0, m1); + if (n !== null) { + return { unit: 'year', value: n }; + } + n = this.diffWholeMonths(m0, m1); + if (n !== null) { + return { unit: 'month', value: n }; + } + n = diffWholeWeeks(m0, m1); + if (n !== null) { + return { unit: 'week', value: n }; + } + n = diffWholeDays(m0, m1); + if (n !== null) { + return { unit: 'day', value: n }; + } + n = diffHours(m0, m1); + if (isInt(n)) { + return { unit: 'hour', value: n }; + } + n = diffMinutes(m0, m1); + if (isInt(n)) { + return { unit: 'minute', value: n }; + } + n = diffSeconds(m0, m1); + if (isInt(n)) { + return { unit: 'second', value: n }; + } + return { unit: 'millisecond', value: m1.valueOf() - m0.valueOf() }; + }; + DateEnv.prototype.countDurationsBetween = function (m0, m1, d) { + // TODO: can use greatestWholeUnit + var diff; + if (d.years) { + diff = this.diffWholeYears(m0, m1); + if (diff !== null) { + return diff / asRoughYears(d); + } + } + if (d.months) { + diff = this.diffWholeMonths(m0, m1); + if (diff !== null) { + return diff / asRoughMonths(d); + } + } + if (d.days) { + diff = diffWholeDays(m0, m1); + if (diff !== null) { + return diff / asRoughDays(d); + } + } + return (m1.valueOf() - m0.valueOf()) / asRoughMs(d); + }; + // Start-Of + // these DON'T return zoned-dates. only UTC start-of dates + DateEnv.prototype.startOf = function (m, unit) { + if (unit === 'year') { + return this.startOfYear(m); + } + if (unit === 'month') { + return this.startOfMonth(m); + } + if (unit === 'week') { + return this.startOfWeek(m); + } + if (unit === 'day') { + return startOfDay(m); + } + if (unit === 'hour') { + return startOfHour(m); + } + if (unit === 'minute') { + return startOfMinute(m); + } + if (unit === 'second') { + return startOfSecond(m); + } + return null; + }; + DateEnv.prototype.startOfYear = function (m) { + return this.calendarSystem.arrayToMarker([ + this.calendarSystem.getMarkerYear(m), + ]); + }; + DateEnv.prototype.startOfMonth = function (m) { + return this.calendarSystem.arrayToMarker([ + this.calendarSystem.getMarkerYear(m), + this.calendarSystem.getMarkerMonth(m), + ]); + }; + DateEnv.prototype.startOfWeek = function (m) { + return this.calendarSystem.arrayToMarker([ + this.calendarSystem.getMarkerYear(m), + this.calendarSystem.getMarkerMonth(m), + m.getUTCDate() - ((m.getUTCDay() - this.weekDow + 7) % 7), + ]); + }; + // Week Number + DateEnv.prototype.computeWeekNumber = function (marker) { + if (this.weekNumberFunc) { + return this.weekNumberFunc(this.toDate(marker)); + } + return weekOfYear(marker, this.weekDow, this.weekDoy); + }; + // TODO: choke on timeZoneName: long + DateEnv.prototype.format = function (marker, formatter, dateOptions) { + if (dateOptions === void 0) { dateOptions = {}; } + return formatter.format({ + marker: marker, + timeZoneOffset: dateOptions.forcedTzo != null ? + dateOptions.forcedTzo : + this.offsetForMarker(marker), + }, this); + }; + DateEnv.prototype.formatRange = function (start, end, formatter, dateOptions) { + if (dateOptions === void 0) { dateOptions = {}; } + if (dateOptions.isEndExclusive) { + end = addMs(end, -1); + } + return formatter.formatRange({ + marker: start, + timeZoneOffset: dateOptions.forcedStartTzo != null ? + dateOptions.forcedStartTzo : + this.offsetForMarker(start), + }, { + marker: end, + timeZoneOffset: dateOptions.forcedEndTzo != null ? + dateOptions.forcedEndTzo : + this.offsetForMarker(end), + }, this, dateOptions.defaultSeparator); + }; + /* + DUMB: the omitTime arg is dumb. if we omit the time, we want to omit the timezone offset. and if we do that, + might as well use buildIsoString or some other util directly + */ + DateEnv.prototype.formatIso = function (marker, extraOptions) { + if (extraOptions === void 0) { extraOptions = {}; } + var timeZoneOffset = null; + if (!extraOptions.omitTimeZoneOffset) { + if (extraOptions.forcedTzo != null) { + timeZoneOffset = extraOptions.forcedTzo; + } + else { + timeZoneOffset = this.offsetForMarker(marker); + } + } + return buildIsoString(marker, timeZoneOffset, extraOptions.omitTime); + }; + // TimeZone + DateEnv.prototype.timestampToMarker = function (ms) { + if (this.timeZone === 'local') { + return arrayToUtcDate(dateToLocalArray(new Date(ms))); + } + if (this.timeZone === 'UTC' || !this.namedTimeZoneImpl) { + return new Date(ms); + } + return arrayToUtcDate(this.namedTimeZoneImpl.timestampToArray(ms)); + }; + DateEnv.prototype.offsetForMarker = function (m) { + if (this.timeZone === 'local') { + return -arrayToLocalDate(dateToUtcArray(m)).getTimezoneOffset(); // convert "inverse" offset to "normal" offset + } + if (this.timeZone === 'UTC') { + return 0; + } + if (this.namedTimeZoneImpl) { + return this.namedTimeZoneImpl.offsetForArray(dateToUtcArray(m)); + } + return null; + }; + // Conversion + DateEnv.prototype.toDate = function (m, forcedTzo) { + if (this.timeZone === 'local') { + return arrayToLocalDate(dateToUtcArray(m)); + } + if (this.timeZone === 'UTC') { + return new Date(m.valueOf()); // make sure it's a copy + } + if (!this.namedTimeZoneImpl) { + return new Date(m.valueOf() - (forcedTzo || 0)); + } + return new Date(m.valueOf() - + this.namedTimeZoneImpl.offsetForArray(dateToUtcArray(m)) * 1000 * 60); + }; + return DateEnv; + }()); + + var globalLocales = []; + + var RAW_EN_LOCALE = { + code: 'en', + week: { + dow: 0, + doy: 4, // 4 days need to be within the year to be considered the first week + }, + direction: 'ltr', + buttonText: { + prev: 'prev', + next: 'next', + prevYear: 'prev year', + nextYear: 'next year', + year: 'year', + today: 'today', + month: 'month', + week: 'week', + day: 'day', + list: 'list', + }, + weekText: 'W', + allDayText: 'all-day', + moreLinkText: 'more', + noEventsText: 'No events to display', + }; + function organizeRawLocales(explicitRawLocales) { + var defaultCode = explicitRawLocales.length > 0 ? explicitRawLocales[0].code : 'en'; + var allRawLocales = globalLocales.concat(explicitRawLocales); + var rawLocaleMap = { + en: RAW_EN_LOCALE, // necessary? + }; + for (var _i = 0, allRawLocales_1 = allRawLocales; _i < allRawLocales_1.length; _i++) { + var rawLocale = allRawLocales_1[_i]; + rawLocaleMap[rawLocale.code] = rawLocale; + } + return { + map: rawLocaleMap, + defaultCode: defaultCode, + }; + } + function buildLocale(inputSingular, available) { + if (typeof inputSingular === 'object' && !Array.isArray(inputSingular)) { + return parseLocale(inputSingular.code, [inputSingular.code], inputSingular); + } + return queryLocale(inputSingular, available); + } + function queryLocale(codeArg, available) { + var codes = [].concat(codeArg || []); // will convert to array + var raw = queryRawLocale(codes, available) || RAW_EN_LOCALE; + return parseLocale(codeArg, codes, raw); + } + function queryRawLocale(codes, available) { + for (var i = 0; i < codes.length; i += 1) { + var parts = codes[i].toLocaleLowerCase().split('-'); + for (var j = parts.length; j > 0; j -= 1) { + var simpleId = parts.slice(0, j).join('-'); + if (available[simpleId]) { + return available[simpleId]; + } + } + } + return null; + } + function parseLocale(codeArg, codes, raw) { + var merged = mergeProps([RAW_EN_LOCALE, raw], ['buttonText']); + delete merged.code; // don't want this part of the options + var week = merged.week; + delete merged.week; + return { + codeArg: codeArg, + codes: codes, + week: week, + simpleNumberFormat: new Intl.NumberFormat(codeArg), + options: merged, + }; + } + + function formatDate(dateInput, options) { + if (options === void 0) { options = {}; } + var dateEnv = buildDateEnv$1(options); + var formatter = createFormatter(options); + var dateMeta = dateEnv.createMarkerMeta(dateInput); + if (!dateMeta) { // TODO: warning? + return ''; + } + return dateEnv.format(dateMeta.marker, formatter, { + forcedTzo: dateMeta.forcedTzo, + }); + } + function formatRange(startInput, endInput, options) { + var dateEnv = buildDateEnv$1(typeof options === 'object' && options ? options : {}); // pass in if non-null object + var formatter = createFormatter(options); + var startMeta = dateEnv.createMarkerMeta(startInput); + var endMeta = dateEnv.createMarkerMeta(endInput); + if (!startMeta || !endMeta) { // TODO: warning? + return ''; + } + return dateEnv.formatRange(startMeta.marker, endMeta.marker, formatter, { + forcedStartTzo: startMeta.forcedTzo, + forcedEndTzo: endMeta.forcedTzo, + isEndExclusive: options.isEndExclusive, + defaultSeparator: BASE_OPTION_DEFAULTS.defaultRangeSeparator, + }); + } + // TODO: more DRY and optimized + function buildDateEnv$1(settings) { + var locale = buildLocale(settings.locale || 'en', organizeRawLocales([]).map); // TODO: don't hardcode 'en' everywhere + return new DateEnv(__assign(__assign({ timeZone: BASE_OPTION_DEFAULTS.timeZone, calendarSystem: 'gregory' }, settings), { locale: locale })); + } + + var DEF_DEFAULTS = { + startTime: '09:00', + endTime: '17:00', + daysOfWeek: [1, 2, 3, 4, 5], + display: 'inverse-background', + classNames: 'fc-non-business', + groupId: '_businessHours', // so multiple defs get grouped + }; + /* + TODO: pass around as EventDefHash!!! + */ + function parseBusinessHours(input, context) { + return parseEvents(refineInputs(input), null, context); + } + function refineInputs(input) { + var rawDefs; + if (input === true) { + rawDefs = [{}]; // will get DEF_DEFAULTS verbatim + } + else if (Array.isArray(input)) { + // if specifying an array, every sub-definition NEEDS a day-of-week + rawDefs = input.filter(function (rawDef) { return rawDef.daysOfWeek; }); + } + else if (typeof input === 'object' && input) { // non-null object + rawDefs = [input]; + } + else { // is probably false + rawDefs = []; + } + rawDefs = rawDefs.map(function (rawDef) { return (__assign(__assign({}, DEF_DEFAULTS), rawDef)); }); + return rawDefs; + } + + function pointInsideRect(point, rect) { + return point.left >= rect.left && + point.left < rect.right && + point.top >= rect.top && + point.top < rect.bottom; + } + // Returns a new rectangle that is the intersection of the two rectangles. If they don't intersect, returns false + function intersectRects(rect1, rect2) { + var res = { + left: Math.max(rect1.left, rect2.left), + right: Math.min(rect1.right, rect2.right), + top: Math.max(rect1.top, rect2.top), + bottom: Math.min(rect1.bottom, rect2.bottom), + }; + if (res.left < res.right && res.top < res.bottom) { + return res; + } + return false; + } + function translateRect(rect, deltaX, deltaY) { + return { + left: rect.left + deltaX, + right: rect.right + deltaX, + top: rect.top + deltaY, + bottom: rect.bottom + deltaY, + }; + } + // Returns a new point that will have been moved to reside within the given rectangle + function constrainPoint(point, rect) { + return { + left: Math.min(Math.max(point.left, rect.left), rect.right), + top: Math.min(Math.max(point.top, rect.top), rect.bottom), + }; + } + // Returns a point that is the center of the given rectangle + function getRectCenter(rect) { + return { + left: (rect.left + rect.right) / 2, + top: (rect.top + rect.bottom) / 2, + }; + } + // Subtracts point2's coordinates from point1's coordinates, returning a delta + function diffPoints(point1, point2) { + return { + left: point1.left - point2.left, + top: point1.top - point2.top, + }; + } + + var canVGrowWithinCell; + function getCanVGrowWithinCell() { + if (canVGrowWithinCell == null) { + canVGrowWithinCell = computeCanVGrowWithinCell(); + } + return canVGrowWithinCell; + } + function computeCanVGrowWithinCell() { + // for SSR, because this function is call immediately at top-level + // TODO: just make this logic execute top-level, immediately, instead of doing lazily + if (typeof document === 'undefined') { + return true; + } + var el = document.createElement('div'); + el.style.position = 'absolute'; + el.style.top = '0px'; + el.style.left = '0px'; + el.innerHTML = '
'; + el.querySelector('table').style.height = '100px'; + el.querySelector('div').style.height = '100%'; + document.body.appendChild(el); + var div = el.querySelector('div'); + var possible = div.offsetHeight > 0; + document.body.removeChild(el); + return possible; + } + + var EMPTY_EVENT_STORE = createEmptyEventStore(); // for purecomponents. TODO: keep elsewhere + var Splitter = /** @class */ (function () { + function Splitter() { + this.getKeysForEventDefs = memoize(this._getKeysForEventDefs); + this.splitDateSelection = memoize(this._splitDateSpan); + this.splitEventStore = memoize(this._splitEventStore); + this.splitIndividualUi = memoize(this._splitIndividualUi); + this.splitEventDrag = memoize(this._splitInteraction); + this.splitEventResize = memoize(this._splitInteraction); + this.eventUiBuilders = {}; // TODO: typescript protection + } + Splitter.prototype.splitProps = function (props) { + var _this = this; + var keyInfos = this.getKeyInfo(props); + var defKeys = this.getKeysForEventDefs(props.eventStore); + var dateSelections = this.splitDateSelection(props.dateSelection); + var individualUi = this.splitIndividualUi(props.eventUiBases, defKeys); // the individual *bases* + var eventStores = this.splitEventStore(props.eventStore, defKeys); + var eventDrags = this.splitEventDrag(props.eventDrag); + var eventResizes = this.splitEventResize(props.eventResize); + var splitProps = {}; + this.eventUiBuilders = mapHash(keyInfos, function (info, key) { return _this.eventUiBuilders[key] || memoize(buildEventUiForKey); }); + for (var key in keyInfos) { + var keyInfo = keyInfos[key]; + var eventStore = eventStores[key] || EMPTY_EVENT_STORE; + var buildEventUi = this.eventUiBuilders[key]; + splitProps[key] = { + businessHours: keyInfo.businessHours || props.businessHours, + dateSelection: dateSelections[key] || null, + eventStore: eventStore, + eventUiBases: buildEventUi(props.eventUiBases[''], keyInfo.ui, individualUi[key]), + eventSelection: eventStore.instances[props.eventSelection] ? props.eventSelection : '', + eventDrag: eventDrags[key] || null, + eventResize: eventResizes[key] || null, + }; + } + return splitProps; + }; + Splitter.prototype._splitDateSpan = function (dateSpan) { + var dateSpans = {}; + if (dateSpan) { + var keys = this.getKeysForDateSpan(dateSpan); + for (var _i = 0, keys_1 = keys; _i < keys_1.length; _i++) { + var key = keys_1[_i]; + dateSpans[key] = dateSpan; + } + } + return dateSpans; + }; + Splitter.prototype._getKeysForEventDefs = function (eventStore) { + var _this = this; + return mapHash(eventStore.defs, function (eventDef) { return _this.getKeysForEventDef(eventDef); }); + }; + Splitter.prototype._splitEventStore = function (eventStore, defKeys) { + var defs = eventStore.defs, instances = eventStore.instances; + var splitStores = {}; + for (var defId in defs) { + for (var _i = 0, _a = defKeys[defId]; _i < _a.length; _i++) { + var key = _a[_i]; + if (!splitStores[key]) { + splitStores[key] = createEmptyEventStore(); + } + splitStores[key].defs[defId] = defs[defId]; + } + } + for (var instanceId in instances) { + var instance = instances[instanceId]; + for (var _b = 0, _c = defKeys[instance.defId]; _b < _c.length; _b++) { + var key = _c[_b]; + if (splitStores[key]) { // must have already been created + splitStores[key].instances[instanceId] = instance; + } + } + } + return splitStores; + }; + Splitter.prototype._splitIndividualUi = function (eventUiBases, defKeys) { + var splitHashes = {}; + for (var defId in eventUiBases) { + if (defId) { // not the '' key + for (var _i = 0, _a = defKeys[defId]; _i < _a.length; _i++) { + var key = _a[_i]; + if (!splitHashes[key]) { + splitHashes[key] = {}; + } + splitHashes[key][defId] = eventUiBases[defId]; + } + } + } + return splitHashes; + }; + Splitter.prototype._splitInteraction = function (interaction) { + var splitStates = {}; + if (interaction) { + var affectedStores_1 = this._splitEventStore(interaction.affectedEvents, this._getKeysForEventDefs(interaction.affectedEvents)); + // can't rely on defKeys because event data is mutated + var mutatedKeysByDefId = this._getKeysForEventDefs(interaction.mutatedEvents); + var mutatedStores_1 = this._splitEventStore(interaction.mutatedEvents, mutatedKeysByDefId); + var populate = function (key) { + if (!splitStates[key]) { + splitStates[key] = { + affectedEvents: affectedStores_1[key] || EMPTY_EVENT_STORE, + mutatedEvents: mutatedStores_1[key] || EMPTY_EVENT_STORE, + isEvent: interaction.isEvent, + }; + } + }; + for (var key in affectedStores_1) { + populate(key); + } + for (var key in mutatedStores_1) { + populate(key); + } + } + return splitStates; + }; + return Splitter; + }()); + function buildEventUiForKey(allUi, eventUiForKey, individualUi) { + var baseParts = []; + if (allUi) { + baseParts.push(allUi); + } + if (eventUiForKey) { + baseParts.push(eventUiForKey); + } + var stuff = { + '': combineEventUis(baseParts), + }; + if (individualUi) { + __assign(stuff, individualUi); + } + return stuff; + } + + function getDateMeta(date, todayRange, nowDate, dateProfile) { + return { + dow: date.getUTCDay(), + isDisabled: Boolean(dateProfile && !rangeContainsMarker(dateProfile.activeRange, date)), + isOther: Boolean(dateProfile && !rangeContainsMarker(dateProfile.currentRange, date)), + isToday: Boolean(todayRange && rangeContainsMarker(todayRange, date)), + isPast: Boolean(nowDate ? (date < nowDate) : todayRange ? (date < todayRange.start) : false), + isFuture: Boolean(nowDate ? (date > nowDate) : todayRange ? (date >= todayRange.end) : false), + }; + } + function getDayClassNames(meta, theme) { + var classNames = [ + 'fc-day', + "fc-day-" + DAY_IDS[meta.dow], + ]; + if (meta.isDisabled) { + classNames.push('fc-day-disabled'); + } + else { + if (meta.isToday) { + classNames.push('fc-day-today'); + classNames.push(theme.getClass('today')); + } + if (meta.isPast) { + classNames.push('fc-day-past'); + } + if (meta.isFuture) { + classNames.push('fc-day-future'); + } + if (meta.isOther) { + classNames.push('fc-day-other'); + } + } + return classNames; + } + function getSlotClassNames(meta, theme) { + var classNames = [ + 'fc-slot', + "fc-slot-" + DAY_IDS[meta.dow], + ]; + if (meta.isDisabled) { + classNames.push('fc-slot-disabled'); + } + else { + if (meta.isToday) { + classNames.push('fc-slot-today'); + classNames.push(theme.getClass('today')); + } + if (meta.isPast) { + classNames.push('fc-slot-past'); + } + if (meta.isFuture) { + classNames.push('fc-slot-future'); + } + } + return classNames; + } + + function buildNavLinkData(date, type) { + if (type === void 0) { type = 'day'; } + return JSON.stringify({ + date: formatDayString(date), + type: type, + }); + } + + var _isRtlScrollbarOnLeft = null; + function getIsRtlScrollbarOnLeft() { + if (_isRtlScrollbarOnLeft === null) { + _isRtlScrollbarOnLeft = computeIsRtlScrollbarOnLeft(); + } + return _isRtlScrollbarOnLeft; + } + function computeIsRtlScrollbarOnLeft() { + var outerEl = document.createElement('div'); + applyStyle(outerEl, { + position: 'absolute', + top: -1000, + left: 0, + border: 0, + padding: 0, + overflow: 'scroll', + direction: 'rtl', + }); + outerEl.innerHTML = '
'; + document.body.appendChild(outerEl); + var innerEl = outerEl.firstChild; + var res = innerEl.getBoundingClientRect().left > outerEl.getBoundingClientRect().left; + removeElement(outerEl); + return res; + } + + var _scrollbarWidths; + function getScrollbarWidths() { + if (!_scrollbarWidths) { + _scrollbarWidths = computeScrollbarWidths(); + } + return _scrollbarWidths; + } + function computeScrollbarWidths() { + var el = document.createElement('div'); + el.style.overflow = 'scroll'; + el.style.position = 'absolute'; + el.style.top = '-9999px'; + el.style.left = '-9999px'; + document.body.appendChild(el); + var res = computeScrollbarWidthsForEl(el); + document.body.removeChild(el); + return res; + } + // WARNING: will include border + function computeScrollbarWidthsForEl(el) { + return { + x: el.offsetHeight - el.clientHeight, + y: el.offsetWidth - el.clientWidth, + }; + } + + function computeEdges(el, getPadding) { + if (getPadding === void 0) { getPadding = false; } + var computedStyle = window.getComputedStyle(el); + var borderLeft = parseInt(computedStyle.borderLeftWidth, 10) || 0; + var borderRight = parseInt(computedStyle.borderRightWidth, 10) || 0; + var borderTop = parseInt(computedStyle.borderTopWidth, 10) || 0; + var borderBottom = parseInt(computedStyle.borderBottomWidth, 10) || 0; + var badScrollbarWidths = computeScrollbarWidthsForEl(el); // includes border! + var scrollbarLeftRight = badScrollbarWidths.y - borderLeft - borderRight; + var scrollbarBottom = badScrollbarWidths.x - borderTop - borderBottom; + var res = { + borderLeft: borderLeft, + borderRight: borderRight, + borderTop: borderTop, + borderBottom: borderBottom, + scrollbarBottom: scrollbarBottom, + scrollbarLeft: 0, + scrollbarRight: 0, + }; + if (getIsRtlScrollbarOnLeft() && computedStyle.direction === 'rtl') { // is the scrollbar on the left side? + res.scrollbarLeft = scrollbarLeftRight; + } + else { + res.scrollbarRight = scrollbarLeftRight; + } + if (getPadding) { + res.paddingLeft = parseInt(computedStyle.paddingLeft, 10) || 0; + res.paddingRight = parseInt(computedStyle.paddingRight, 10) || 0; + res.paddingTop = parseInt(computedStyle.paddingTop, 10) || 0; + res.paddingBottom = parseInt(computedStyle.paddingBottom, 10) || 0; + } + return res; + } + function computeInnerRect(el, goWithinPadding, doFromWindowViewport) { + if (goWithinPadding === void 0) { goWithinPadding = false; } + var outerRect = doFromWindowViewport ? el.getBoundingClientRect() : computeRect(el); + var edges = computeEdges(el, goWithinPadding); + var res = { + left: outerRect.left + edges.borderLeft + edges.scrollbarLeft, + right: outerRect.right - edges.borderRight - edges.scrollbarRight, + top: outerRect.top + edges.borderTop, + bottom: outerRect.bottom - edges.borderBottom - edges.scrollbarBottom, + }; + if (goWithinPadding) { + res.left += edges.paddingLeft; + res.right -= edges.paddingRight; + res.top += edges.paddingTop; + res.bottom -= edges.paddingBottom; + } + return res; + } + function computeRect(el) { + var rect = el.getBoundingClientRect(); + return { + left: rect.left + window.pageXOffset, + top: rect.top + window.pageYOffset, + right: rect.right + window.pageXOffset, + bottom: rect.bottom + window.pageYOffset, + }; + } + function computeClippedClientRect(el) { + var clippingParents = getClippingParents(el); + var rect = el.getBoundingClientRect(); + for (var _i = 0, clippingParents_1 = clippingParents; _i < clippingParents_1.length; _i++) { + var clippingParent = clippingParents_1[_i]; + var intersection = intersectRects(rect, clippingParent.getBoundingClientRect()); + if (intersection) { + rect = intersection; + } + else { + return null; + } + } + return rect; + } + function computeHeightAndMargins(el) { + return el.getBoundingClientRect().height + computeVMargins(el); + } + function computeVMargins(el) { + var computed = window.getComputedStyle(el); + return parseInt(computed.marginTop, 10) + + parseInt(computed.marginBottom, 10); + } + // does not return window + function getClippingParents(el) { + var parents = []; + while (el instanceof HTMLElement) { // will stop when gets to document or null + var computedStyle = window.getComputedStyle(el); + if (computedStyle.position === 'fixed') { + break; + } + if ((/(auto|scroll)/).test(computedStyle.overflow + computedStyle.overflowY + computedStyle.overflowX)) { + parents.push(el); + } + el = el.parentNode; + } + return parents; + } + + // given a function that resolves a result asynchronously. + // the function can either call passed-in success and failure callbacks, + // or it can return a promise. + // if you need to pass additional params to func, bind them first. + function unpromisify(func, success, failure) { + // guard against success/failure callbacks being called more than once + // and guard against a promise AND callback being used together. + var isResolved = false; + var wrappedSuccess = function () { + if (!isResolved) { + isResolved = true; + success.apply(this, arguments); // eslint-disable-line prefer-rest-params + } + }; + var wrappedFailure = function () { + if (!isResolved) { + isResolved = true; + if (failure) { + failure.apply(this, arguments); // eslint-disable-line prefer-rest-params + } + } + }; + var res = func(wrappedSuccess, wrappedFailure); + if (res && typeof res.then === 'function') { + res.then(wrappedSuccess, wrappedFailure); + } + } + + var Emitter = /** @class */ (function () { + function Emitter() { + this.handlers = {}; + this.thisContext = null; + } + Emitter.prototype.setThisContext = function (thisContext) { + this.thisContext = thisContext; + }; + Emitter.prototype.setOptions = function (options) { + this.options = options; + }; + Emitter.prototype.on = function (type, handler) { + addToHash(this.handlers, type, handler); + }; + Emitter.prototype.off = function (type, handler) { + removeFromHash(this.handlers, type, handler); + }; + Emitter.prototype.trigger = function (type) { + var args = []; + for (var _i = 1; _i < arguments.length; _i++) { + args[_i - 1] = arguments[_i]; + } + var attachedHandlers = this.handlers[type] || []; + var optionHandler = this.options && this.options[type]; + var handlers = [].concat(optionHandler || [], attachedHandlers); + for (var _a = 0, handlers_1 = handlers; _a < handlers_1.length; _a++) { + var handler = handlers_1[_a]; + handler.apply(this.thisContext, args); + } + }; + Emitter.prototype.hasHandlers = function (type) { + return (this.handlers[type] && this.handlers[type].length) || + (this.options && this.options[type]); + }; + return Emitter; + }()); + function addToHash(hash, type, handler) { + (hash[type] || (hash[type] = [])) + .push(handler); + } + function removeFromHash(hash, type, handler) { + if (handler) { + if (hash[type]) { + hash[type] = hash[type].filter(function (func) { return func !== handler; }); + } + } + else { + delete hash[type]; // remove all handler funcs for this type + } + } + + /* + Records offset information for a set of elements, relative to an origin element. + Can record the left/right OR the top/bottom OR both. + Provides methods for querying the cache by position. + */ + var PositionCache = /** @class */ (function () { + function PositionCache(originEl, els, isHorizontal, isVertical) { + this.els = els; + var originClientRect = this.originClientRect = originEl.getBoundingClientRect(); // relative to viewport top-left + if (isHorizontal) { + this.buildElHorizontals(originClientRect.left); + } + if (isVertical) { + this.buildElVerticals(originClientRect.top); + } + } + // Populates the left/right internal coordinate arrays + PositionCache.prototype.buildElHorizontals = function (originClientLeft) { + var lefts = []; + var rights = []; + for (var _i = 0, _a = this.els; _i < _a.length; _i++) { + var el = _a[_i]; + var rect = el.getBoundingClientRect(); + lefts.push(rect.left - originClientLeft); + rights.push(rect.right - originClientLeft); + } + this.lefts = lefts; + this.rights = rights; + }; + // Populates the top/bottom internal coordinate arrays + PositionCache.prototype.buildElVerticals = function (originClientTop) { + var tops = []; + var bottoms = []; + for (var _i = 0, _a = this.els; _i < _a.length; _i++) { + var el = _a[_i]; + var rect = el.getBoundingClientRect(); + tops.push(rect.top - originClientTop); + bottoms.push(rect.bottom - originClientTop); + } + this.tops = tops; + this.bottoms = bottoms; + }; + // Given a left offset (from document left), returns the index of the el that it horizontally intersects. + // If no intersection is made, returns undefined. + PositionCache.prototype.leftToIndex = function (leftPosition) { + var _a = this, lefts = _a.lefts, rights = _a.rights; + var len = lefts.length; + var i; + for (i = 0; i < len; i += 1) { + if (leftPosition >= lefts[i] && leftPosition < rights[i]) { + return i; + } + } + return undefined; // TODO: better + }; + // Given a top offset (from document top), returns the index of the el that it vertically intersects. + // If no intersection is made, returns undefined. + PositionCache.prototype.topToIndex = function (topPosition) { + var _a = this, tops = _a.tops, bottoms = _a.bottoms; + var len = tops.length; + var i; + for (i = 0; i < len; i += 1) { + if (topPosition >= tops[i] && topPosition < bottoms[i]) { + return i; + } + } + return undefined; // TODO: better + }; + // Gets the width of the element at the given index + PositionCache.prototype.getWidth = function (leftIndex) { + return this.rights[leftIndex] - this.lefts[leftIndex]; + }; + // Gets the height of the element at the given index + PositionCache.prototype.getHeight = function (topIndex) { + return this.bottoms[topIndex] - this.tops[topIndex]; + }; + return PositionCache; + }()); + + /* eslint max-classes-per-file: "off" */ + /* + An object for getting/setting scroll-related information for an element. + Internally, this is done very differently for window versus DOM element, + so this object serves as a common interface. + */ + var ScrollController = /** @class */ (function () { + function ScrollController() { + } + ScrollController.prototype.getMaxScrollTop = function () { + return this.getScrollHeight() - this.getClientHeight(); + }; + ScrollController.prototype.getMaxScrollLeft = function () { + return this.getScrollWidth() - this.getClientWidth(); + }; + ScrollController.prototype.canScrollVertically = function () { + return this.getMaxScrollTop() > 0; + }; + ScrollController.prototype.canScrollHorizontally = function () { + return this.getMaxScrollLeft() > 0; + }; + ScrollController.prototype.canScrollUp = function () { + return this.getScrollTop() > 0; + }; + ScrollController.prototype.canScrollDown = function () { + return this.getScrollTop() < this.getMaxScrollTop(); + }; + ScrollController.prototype.canScrollLeft = function () { + return this.getScrollLeft() > 0; + }; + ScrollController.prototype.canScrollRight = function () { + return this.getScrollLeft() < this.getMaxScrollLeft(); + }; + return ScrollController; + }()); + var ElementScrollController = /** @class */ (function (_super) { + __extends(ElementScrollController, _super); + function ElementScrollController(el) { + var _this = _super.call(this) || this; + _this.el = el; + return _this; + } + ElementScrollController.prototype.getScrollTop = function () { + return this.el.scrollTop; + }; + ElementScrollController.prototype.getScrollLeft = function () { + return this.el.scrollLeft; + }; + ElementScrollController.prototype.setScrollTop = function (top) { + this.el.scrollTop = top; + }; + ElementScrollController.prototype.setScrollLeft = function (left) { + this.el.scrollLeft = left; + }; + ElementScrollController.prototype.getScrollWidth = function () { + return this.el.scrollWidth; + }; + ElementScrollController.prototype.getScrollHeight = function () { + return this.el.scrollHeight; + }; + ElementScrollController.prototype.getClientHeight = function () { + return this.el.clientHeight; + }; + ElementScrollController.prototype.getClientWidth = function () { + return this.el.clientWidth; + }; + return ElementScrollController; + }(ScrollController)); + var WindowScrollController = /** @class */ (function (_super) { + __extends(WindowScrollController, _super); + function WindowScrollController() { + return _super !== null && _super.apply(this, arguments) || this; + } + WindowScrollController.prototype.getScrollTop = function () { + return window.pageYOffset; + }; + WindowScrollController.prototype.getScrollLeft = function () { + return window.pageXOffset; + }; + WindowScrollController.prototype.setScrollTop = function (n) { + window.scroll(window.pageXOffset, n); + }; + WindowScrollController.prototype.setScrollLeft = function (n) { + window.scroll(n, window.pageYOffset); + }; + WindowScrollController.prototype.getScrollWidth = function () { + return document.documentElement.scrollWidth; + }; + WindowScrollController.prototype.getScrollHeight = function () { + return document.documentElement.scrollHeight; + }; + WindowScrollController.prototype.getClientHeight = function () { + return document.documentElement.clientHeight; + }; + WindowScrollController.prototype.getClientWidth = function () { + return document.documentElement.clientWidth; + }; + return WindowScrollController; + }(ScrollController)); + + var Theme = /** @class */ (function () { + function Theme(calendarOptions) { + if (this.iconOverrideOption) { + this.setIconOverride(calendarOptions[this.iconOverrideOption]); + } + } + Theme.prototype.setIconOverride = function (iconOverrideHash) { + var iconClassesCopy; + var buttonName; + if (typeof iconOverrideHash === 'object' && iconOverrideHash) { // non-null object + iconClassesCopy = __assign({}, this.iconClasses); + for (buttonName in iconOverrideHash) { + iconClassesCopy[buttonName] = this.applyIconOverridePrefix(iconOverrideHash[buttonName]); + } + this.iconClasses = iconClassesCopy; + } + else if (iconOverrideHash === false) { + this.iconClasses = {}; + } + }; + Theme.prototype.applyIconOverridePrefix = function (className) { + var prefix = this.iconOverridePrefix; + if (prefix && className.indexOf(prefix) !== 0) { // if not already present + className = prefix + className; + } + return className; + }; + Theme.prototype.getClass = function (key) { + return this.classes[key] || ''; + }; + Theme.prototype.getIconClass = function (buttonName, isRtl) { + var className; + if (isRtl && this.rtlIconClasses) { + className = this.rtlIconClasses[buttonName] || this.iconClasses[buttonName]; + } + else { + className = this.iconClasses[buttonName]; + } + if (className) { + return this.baseIconClass + " " + className; + } + return ''; + }; + Theme.prototype.getCustomButtonIconClass = function (customButtonProps) { + var className; + if (this.iconOverrideCustomButtonOption) { + className = customButtonProps[this.iconOverrideCustomButtonOption]; + if (className) { + return this.baseIconClass + " " + this.applyIconOverridePrefix(className); + } + } + return ''; + }; + return Theme; + }()); + Theme.prototype.classes = {}; + Theme.prototype.iconClasses = {}; + Theme.prototype.baseIconClass = ''; + Theme.prototype.iconOverridePrefix = ''; + + /// + if (typeof FullCalendarVDom === 'undefined') { + throw new Error('Please import the top-level fullcalendar lib before attempting to import a plugin.'); + } + var Component = FullCalendarVDom.Component; + var createElement = FullCalendarVDom.createElement; + var render = FullCalendarVDom.render; + var createRef = FullCalendarVDom.createRef; + var Fragment = FullCalendarVDom.Fragment; + var createContext = FullCalendarVDom.createContext; + var createPortal = FullCalendarVDom.createPortal; + var flushToDom = FullCalendarVDom.flushToDom; + var unmountComponentAtNode = FullCalendarVDom.unmountComponentAtNode; + /* eslint-enable */ + + var ScrollResponder = /** @class */ (function () { + function ScrollResponder(execFunc, emitter, scrollTime, scrollTimeReset) { + var _this = this; + this.execFunc = execFunc; + this.emitter = emitter; + this.scrollTime = scrollTime; + this.scrollTimeReset = scrollTimeReset; + this.handleScrollRequest = function (request) { + _this.queuedRequest = __assign({}, _this.queuedRequest || {}, request); + _this.drain(); + }; + emitter.on('_scrollRequest', this.handleScrollRequest); + this.fireInitialScroll(); + } + ScrollResponder.prototype.detach = function () { + this.emitter.off('_scrollRequest', this.handleScrollRequest); + }; + ScrollResponder.prototype.update = function (isDatesNew) { + if (isDatesNew && this.scrollTimeReset) { + this.fireInitialScroll(); // will drain + } + else { + this.drain(); + } + }; + ScrollResponder.prototype.fireInitialScroll = function () { + this.handleScrollRequest({ + time: this.scrollTime, + }); + }; + ScrollResponder.prototype.drain = function () { + if (this.queuedRequest && this.execFunc(this.queuedRequest)) { + this.queuedRequest = null; + } + }; + return ScrollResponder; + }()); + + var ViewContextType = createContext({}); // for Components + function buildViewContext(viewSpec, viewApi, viewOptions, dateProfileGenerator, dateEnv, theme, pluginHooks, dispatch, getCurrentData, emitter, calendarApi, registerInteractiveComponent, unregisterInteractiveComponent) { + return { + dateEnv: dateEnv, + options: viewOptions, + pluginHooks: pluginHooks, + emitter: emitter, + dispatch: dispatch, + getCurrentData: getCurrentData, + calendarApi: calendarApi, + viewSpec: viewSpec, + viewApi: viewApi, + dateProfileGenerator: dateProfileGenerator, + theme: theme, + isRtl: viewOptions.direction === 'rtl', + addResizeHandler: function (handler) { + emitter.on('_resize', handler); + }, + removeResizeHandler: function (handler) { + emitter.off('_resize', handler); + }, + createScrollResponder: function (execFunc) { + return new ScrollResponder(execFunc, emitter, createDuration(viewOptions.scrollTime), viewOptions.scrollTimeReset); + }, + registerInteractiveComponent: registerInteractiveComponent, + unregisterInteractiveComponent: unregisterInteractiveComponent, + }; + } + + /* eslint max-classes-per-file: off */ + var PureComponent = /** @class */ (function (_super) { + __extends(PureComponent, _super); + function PureComponent() { + return _super !== null && _super.apply(this, arguments) || this; + } + PureComponent.prototype.shouldComponentUpdate = function (nextProps, nextState) { + if (this.debug) { + // eslint-disable-next-line no-console + console.log(getUnequalProps(nextProps, this.props), getUnequalProps(nextState, this.state)); + } + return !compareObjs(this.props, nextProps, this.propEquality) || + !compareObjs(this.state, nextState, this.stateEquality); + }; + PureComponent.addPropsEquality = addPropsEquality; + PureComponent.addStateEquality = addStateEquality; + PureComponent.contextType = ViewContextType; + return PureComponent; + }(Component)); + PureComponent.prototype.propEquality = {}; + PureComponent.prototype.stateEquality = {}; + var BaseComponent = /** @class */ (function (_super) { + __extends(BaseComponent, _super); + function BaseComponent() { + return _super !== null && _super.apply(this, arguments) || this; + } + BaseComponent.contextType = ViewContextType; + return BaseComponent; + }(PureComponent)); + function addPropsEquality(propEquality) { + var hash = Object.create(this.prototype.propEquality); + __assign(hash, propEquality); + this.prototype.propEquality = hash; + } + function addStateEquality(stateEquality) { + var hash = Object.create(this.prototype.stateEquality); + __assign(hash, stateEquality); + this.prototype.stateEquality = hash; + } + // use other one + function setRef(ref, current) { + if (typeof ref === 'function') { + ref(current); + } + else if (ref) { + // see https://github.com/facebook/react/issues/13029 + ref.current = current; + } + } + + /* + an INTERACTABLE date component + + PURPOSES: + - hook up to fg, fill, and mirror renderers + - interface for dragging and hits + */ + var DateComponent = /** @class */ (function (_super) { + __extends(DateComponent, _super); + function DateComponent() { + var _this = _super !== null && _super.apply(this, arguments) || this; + _this.uid = guid(); + return _this; + } + // Hit System + // ----------------------------------------------------------------------------------------------------------------- + DateComponent.prototype.prepareHits = function () { + }; + DateComponent.prototype.queryHit = function (positionLeft, positionTop, elWidth, elHeight) { + return null; // this should be abstract + }; + // Pointer Interaction Utils + // ----------------------------------------------------------------------------------------------------------------- + DateComponent.prototype.isValidSegDownEl = function (el) { + return !this.props.eventDrag && // HACK + !this.props.eventResize && // HACK + !elementClosest(el, '.fc-event-mirror'); + }; + DateComponent.prototype.isValidDateDownEl = function (el) { + return !elementClosest(el, '.fc-event:not(.fc-bg-event)') && + !elementClosest(el, '.fc-more-link') && // a "more.." link + !elementClosest(el, 'a[data-navlink]') && // a clickable nav link + !elementClosest(el, '.fc-popover'); // hack + }; + return DateComponent; + }(BaseComponent)); + + // TODO: easier way to add new hooks? need to update a million things + function createPlugin(input) { + return { + id: guid(), + deps: input.deps || [], + reducers: input.reducers || [], + isLoadingFuncs: input.isLoadingFuncs || [], + contextInit: [].concat(input.contextInit || []), + eventRefiners: input.eventRefiners || {}, + eventDefMemberAdders: input.eventDefMemberAdders || [], + eventSourceRefiners: input.eventSourceRefiners || {}, + isDraggableTransformers: input.isDraggableTransformers || [], + eventDragMutationMassagers: input.eventDragMutationMassagers || [], + eventDefMutationAppliers: input.eventDefMutationAppliers || [], + dateSelectionTransformers: input.dateSelectionTransformers || [], + datePointTransforms: input.datePointTransforms || [], + dateSpanTransforms: input.dateSpanTransforms || [], + views: input.views || {}, + viewPropsTransformers: input.viewPropsTransformers || [], + isPropsValid: input.isPropsValid || null, + externalDefTransforms: input.externalDefTransforms || [], + viewContainerAppends: input.viewContainerAppends || [], + eventDropTransformers: input.eventDropTransformers || [], + componentInteractions: input.componentInteractions || [], + calendarInteractions: input.calendarInteractions || [], + themeClasses: input.themeClasses || {}, + eventSourceDefs: input.eventSourceDefs || [], + cmdFormatter: input.cmdFormatter, + recurringTypes: input.recurringTypes || [], + namedTimeZonedImpl: input.namedTimeZonedImpl, + initialView: input.initialView || '', + elementDraggingImpl: input.elementDraggingImpl, + optionChangeHandlers: input.optionChangeHandlers || {}, + scrollGridImpl: input.scrollGridImpl || null, + contentTypeHandlers: input.contentTypeHandlers || {}, + listenerRefiners: input.listenerRefiners || {}, + optionRefiners: input.optionRefiners || {}, + propSetHandlers: input.propSetHandlers || {}, + }; + } + function buildPluginHooks(pluginDefs, globalDefs) { + var isAdded = {}; + var hooks = { + reducers: [], + isLoadingFuncs: [], + contextInit: [], + eventRefiners: {}, + eventDefMemberAdders: [], + eventSourceRefiners: {}, + isDraggableTransformers: [], + eventDragMutationMassagers: [], + eventDefMutationAppliers: [], + dateSelectionTransformers: [], + datePointTransforms: [], + dateSpanTransforms: [], + views: {}, + viewPropsTransformers: [], + isPropsValid: null, + externalDefTransforms: [], + viewContainerAppends: [], + eventDropTransformers: [], + componentInteractions: [], + calendarInteractions: [], + themeClasses: {}, + eventSourceDefs: [], + cmdFormatter: null, + recurringTypes: [], + namedTimeZonedImpl: null, + initialView: '', + elementDraggingImpl: null, + optionChangeHandlers: {}, + scrollGridImpl: null, + contentTypeHandlers: {}, + listenerRefiners: {}, + optionRefiners: {}, + propSetHandlers: {}, + }; + function addDefs(defs) { + for (var _i = 0, defs_1 = defs; _i < defs_1.length; _i++) { + var def = defs_1[_i]; + if (!isAdded[def.id]) { + isAdded[def.id] = true; + addDefs(def.deps); + hooks = combineHooks(hooks, def); + } + } + } + if (pluginDefs) { + addDefs(pluginDefs); + } + addDefs(globalDefs); + return hooks; + } + function buildBuildPluginHooks() { + var currentOverrideDefs = []; + var currentGlobalDefs = []; + var currentHooks; + return function (overrideDefs, globalDefs) { + if (!currentHooks || !isArraysEqual(overrideDefs, currentOverrideDefs) || !isArraysEqual(globalDefs, currentGlobalDefs)) { + currentHooks = buildPluginHooks(overrideDefs, globalDefs); + } + currentOverrideDefs = overrideDefs; + currentGlobalDefs = globalDefs; + return currentHooks; + }; + } + function combineHooks(hooks0, hooks1) { + return { + reducers: hooks0.reducers.concat(hooks1.reducers), + isLoadingFuncs: hooks0.isLoadingFuncs.concat(hooks1.isLoadingFuncs), + contextInit: hooks0.contextInit.concat(hooks1.contextInit), + eventRefiners: __assign(__assign({}, hooks0.eventRefiners), hooks1.eventRefiners), + eventDefMemberAdders: hooks0.eventDefMemberAdders.concat(hooks1.eventDefMemberAdders), + eventSourceRefiners: __assign(__assign({}, hooks0.eventSourceRefiners), hooks1.eventSourceRefiners), + isDraggableTransformers: hooks0.isDraggableTransformers.concat(hooks1.isDraggableTransformers), + eventDragMutationMassagers: hooks0.eventDragMutationMassagers.concat(hooks1.eventDragMutationMassagers), + eventDefMutationAppliers: hooks0.eventDefMutationAppliers.concat(hooks1.eventDefMutationAppliers), + dateSelectionTransformers: hooks0.dateSelectionTransformers.concat(hooks1.dateSelectionTransformers), + datePointTransforms: hooks0.datePointTransforms.concat(hooks1.datePointTransforms), + dateSpanTransforms: hooks0.dateSpanTransforms.concat(hooks1.dateSpanTransforms), + views: __assign(__assign({}, hooks0.views), hooks1.views), + viewPropsTransformers: hooks0.viewPropsTransformers.concat(hooks1.viewPropsTransformers), + isPropsValid: hooks1.isPropsValid || hooks0.isPropsValid, + externalDefTransforms: hooks0.externalDefTransforms.concat(hooks1.externalDefTransforms), + viewContainerAppends: hooks0.viewContainerAppends.concat(hooks1.viewContainerAppends), + eventDropTransformers: hooks0.eventDropTransformers.concat(hooks1.eventDropTransformers), + calendarInteractions: hooks0.calendarInteractions.concat(hooks1.calendarInteractions), + componentInteractions: hooks0.componentInteractions.concat(hooks1.componentInteractions), + themeClasses: __assign(__assign({}, hooks0.themeClasses), hooks1.themeClasses), + eventSourceDefs: hooks0.eventSourceDefs.concat(hooks1.eventSourceDefs), + cmdFormatter: hooks1.cmdFormatter || hooks0.cmdFormatter, + recurringTypes: hooks0.recurringTypes.concat(hooks1.recurringTypes), + namedTimeZonedImpl: hooks1.namedTimeZonedImpl || hooks0.namedTimeZonedImpl, + initialView: hooks0.initialView || hooks1.initialView, + elementDraggingImpl: hooks0.elementDraggingImpl || hooks1.elementDraggingImpl, + optionChangeHandlers: __assign(__assign({}, hooks0.optionChangeHandlers), hooks1.optionChangeHandlers), + scrollGridImpl: hooks1.scrollGridImpl || hooks0.scrollGridImpl, + contentTypeHandlers: __assign(__assign({}, hooks0.contentTypeHandlers), hooks1.contentTypeHandlers), + listenerRefiners: __assign(__assign({}, hooks0.listenerRefiners), hooks1.listenerRefiners), + optionRefiners: __assign(__assign({}, hooks0.optionRefiners), hooks1.optionRefiners), + propSetHandlers: __assign(__assign({}, hooks0.propSetHandlers), hooks1.propSetHandlers), + }; + } + + var StandardTheme = /** @class */ (function (_super) { + __extends(StandardTheme, _super); + function StandardTheme() { + return _super !== null && _super.apply(this, arguments) || this; + } + return StandardTheme; + }(Theme)); + StandardTheme.prototype.classes = { + root: 'fc-theme-standard', + tableCellShaded: 'fc-cell-shaded', + buttonGroup: 'fc-button-group', + button: 'fc-button fc-button-primary', + buttonActive: 'fc-button-active', + }; + StandardTheme.prototype.baseIconClass = 'fc-icon'; + StandardTheme.prototype.iconClasses = { + close: 'fc-icon-x', + prev: 'fc-icon-chevron-left', + next: 'fc-icon-chevron-right', + prevYear: 'fc-icon-chevrons-left', + nextYear: 'fc-icon-chevrons-right', + }; + StandardTheme.prototype.rtlIconClasses = { + prev: 'fc-icon-chevron-right', + next: 'fc-icon-chevron-left', + prevYear: 'fc-icon-chevrons-right', + nextYear: 'fc-icon-chevrons-left', + }; + StandardTheme.prototype.iconOverrideOption = 'buttonIcons'; // TODO: make TS-friendly + StandardTheme.prototype.iconOverrideCustomButtonOption = 'icon'; + StandardTheme.prototype.iconOverridePrefix = 'fc-icon-'; + + function compileViewDefs(defaultConfigs, overrideConfigs) { + var hash = {}; + var viewType; + for (viewType in defaultConfigs) { + ensureViewDef(viewType, hash, defaultConfigs, overrideConfigs); + } + for (viewType in overrideConfigs) { + ensureViewDef(viewType, hash, defaultConfigs, overrideConfigs); + } + return hash; + } + function ensureViewDef(viewType, hash, defaultConfigs, overrideConfigs) { + if (hash[viewType]) { + return hash[viewType]; + } + var viewDef = buildViewDef(viewType, hash, defaultConfigs, overrideConfigs); + if (viewDef) { + hash[viewType] = viewDef; + } + return viewDef; + } + function buildViewDef(viewType, hash, defaultConfigs, overrideConfigs) { + var defaultConfig = defaultConfigs[viewType]; + var overrideConfig = overrideConfigs[viewType]; + var queryProp = function (name) { return ((defaultConfig && defaultConfig[name] !== null) ? defaultConfig[name] : + ((overrideConfig && overrideConfig[name] !== null) ? overrideConfig[name] : null)); }; + var theComponent = queryProp('component'); + var superType = queryProp('superType'); + var superDef = null; + if (superType) { + if (superType === viewType) { + throw new Error('Can\'t have a custom view type that references itself'); + } + superDef = ensureViewDef(superType, hash, defaultConfigs, overrideConfigs); + } + if (!theComponent && superDef) { + theComponent = superDef.component; + } + if (!theComponent) { + return null; // don't throw a warning, might be settings for a single-unit view + } + return { + type: viewType, + component: theComponent, + defaults: __assign(__assign({}, (superDef ? superDef.defaults : {})), (defaultConfig ? defaultConfig.rawOptions : {})), + overrides: __assign(__assign({}, (superDef ? superDef.overrides : {})), (overrideConfig ? overrideConfig.rawOptions : {})), + }; + } + + /* eslint max-classes-per-file: off */ + // NOTE: in JSX, you should always use this class with arg. otherwise, will default to any??? + var RenderHook = /** @class */ (function (_super) { + __extends(RenderHook, _super); + function RenderHook() { + var _this = _super !== null && _super.apply(this, arguments) || this; + _this.rootElRef = createRef(); + _this.handleRootEl = function (el) { + setRef(_this.rootElRef, el); + if (_this.props.elRef) { + setRef(_this.props.elRef, el); + } + }; + return _this; + } + RenderHook.prototype.render = function () { + var _this = this; + var props = this.props; + var hookProps = props.hookProps; + return (createElement(MountHook, { hookProps: hookProps, didMount: props.didMount, willUnmount: props.willUnmount, elRef: this.handleRootEl }, function (rootElRef) { return (createElement(ContentHook, { hookProps: hookProps, content: props.content, defaultContent: props.defaultContent, backupElRef: _this.rootElRef }, function (innerElRef, innerContent) { return props.children(rootElRef, normalizeClassNames(props.classNames, hookProps), innerElRef, innerContent); })); })); + }; + return RenderHook; + }(BaseComponent)); + // TODO: rename to be about function, not default. use in above type + // for forcing rerender of components that use the ContentHook + var CustomContentRenderContext = createContext(0); + function ContentHook(props) { + return (createElement(CustomContentRenderContext.Consumer, null, function (renderId) { return (createElement(ContentHookInner, __assign({ renderId: renderId }, props))); })); + } + var ContentHookInner = /** @class */ (function (_super) { + __extends(ContentHookInner, _super); + function ContentHookInner() { + var _this = _super !== null && _super.apply(this, arguments) || this; + _this.innerElRef = createRef(); + return _this; + } + ContentHookInner.prototype.render = function () { + return this.props.children(this.innerElRef, this.renderInnerContent()); + }; + ContentHookInner.prototype.componentDidMount = function () { + this.updateCustomContent(); + }; + ContentHookInner.prototype.componentDidUpdate = function () { + this.updateCustomContent(); + }; + ContentHookInner.prototype.componentWillUnmount = function () { + if (this.customContentInfo && this.customContentInfo.destroy) { + this.customContentInfo.destroy(); + } + }; + ContentHookInner.prototype.renderInnerContent = function () { + var customContentInfo = this.customContentInfo; // only populated if using non-[p]react node(s) + var innerContent = this.getInnerContent(); + var meta = this.getContentMeta(innerContent); + // initial run, or content-type changing? (from vue -> react for example) + if (!customContentInfo || customContentInfo.contentKey !== meta.contentKey) { + // clearing old value + if (customContentInfo) { + if (customContentInfo.destroy) { + customContentInfo.destroy(); + } + customContentInfo = this.customContentInfo = null; + } + // assigning new value + if (meta.contentKey) { + customContentInfo = this.customContentInfo = __assign({ contentKey: meta.contentKey, contentVal: innerContent[meta.contentKey] }, meta.buildLifecycleFuncs()); + } + // updating + } + else if (customContentInfo) { + customContentInfo.contentVal = innerContent[meta.contentKey]; + } + return customContentInfo + ? [] // signal that something was specified + : innerContent; // assume a [p]react vdom node. use it + }; + ContentHookInner.prototype.getInnerContent = function () { + var props = this.props; + var innerContent = normalizeContent(props.content, props.hookProps); + if (innerContent === undefined) { // use the default + innerContent = normalizeContent(props.defaultContent, props.hookProps); + } + return innerContent == null ? null : innerContent; // convert undefined to null (better for React) + }; + ContentHookInner.prototype.getContentMeta = function (innerContent) { + var contentTypeHandlers = this.context.pluginHooks.contentTypeHandlers; + var contentKey = ''; + var buildLifecycleFuncs = null; + if (innerContent) { // allowed to be null, for convenience to caller + for (var searchKey in contentTypeHandlers) { + if (innerContent[searchKey] !== undefined) { + contentKey = searchKey; + buildLifecycleFuncs = contentTypeHandlers[searchKey]; + break; + } + } + } + return { contentKey: contentKey, buildLifecycleFuncs: buildLifecycleFuncs }; + }; + ContentHookInner.prototype.updateCustomContent = function () { + if (this.customContentInfo) { // for non-[p]react + this.customContentInfo.render(this.innerElRef.current || this.props.backupElRef.current, // the element to render into + this.customContentInfo.contentVal); + } + }; + return ContentHookInner; + }(BaseComponent)); + var MountHook = /** @class */ (function (_super) { + __extends(MountHook, _super); + function MountHook() { + var _this = _super !== null && _super.apply(this, arguments) || this; + _this.handleRootEl = function (rootEl) { + _this.rootEl = rootEl; + if (_this.props.elRef) { + setRef(_this.props.elRef, rootEl); + } + }; + return _this; + } + MountHook.prototype.render = function () { + return this.props.children(this.handleRootEl); + }; + MountHook.prototype.componentDidMount = function () { + var callback = this.props.didMount; + if (callback) { + callback(__assign(__assign({}, this.props.hookProps), { el: this.rootEl })); + } + }; + MountHook.prototype.componentWillUnmount = function () { + var callback = this.props.willUnmount; + if (callback) { + callback(__assign(__assign({}, this.props.hookProps), { el: this.rootEl })); + } + }; + return MountHook; + }(BaseComponent)); + function buildClassNameNormalizer() { + var currentGenerator; + var currentHookProps; + var currentClassNames = []; + return function (generator, hookProps) { + if (!currentHookProps || !isPropsEqual(currentHookProps, hookProps) || generator !== currentGenerator) { + currentGenerator = generator; + currentHookProps = hookProps; + currentClassNames = normalizeClassNames(generator, hookProps); + } + return currentClassNames; + }; + } + function normalizeClassNames(classNames, hookProps) { + if (typeof classNames === 'function') { + classNames = classNames(hookProps); + } + return parseClassNames(classNames); + } + function normalizeContent(input, hookProps) { + if (typeof input === 'function') { + return input(hookProps, createElement); // give the function the vdom-creation func + } + return input; + } + + var ViewRoot = /** @class */ (function (_super) { + __extends(ViewRoot, _super); + function ViewRoot() { + var _this = _super !== null && _super.apply(this, arguments) || this; + _this.normalizeClassNames = buildClassNameNormalizer(); + return _this; + } + ViewRoot.prototype.render = function () { + var _a = this, props = _a.props, context = _a.context; + var options = context.options; + var hookProps = { view: context.viewApi }; + var customClassNames = this.normalizeClassNames(options.viewClassNames, hookProps); + return (createElement(MountHook, { hookProps: hookProps, didMount: options.viewDidMount, willUnmount: options.viewWillUnmount, elRef: props.elRef }, function (rootElRef) { return props.children(rootElRef, ["fc-" + props.viewSpec.type + "-view", 'fc-view'].concat(customClassNames)); })); + }; + return ViewRoot; + }(BaseComponent)); + + function parseViewConfigs(inputs) { + return mapHash(inputs, parseViewConfig); + } + function parseViewConfig(input) { + var rawOptions = typeof input === 'function' ? + { component: input } : + input; + var component = rawOptions.component; + if (rawOptions.content) { + component = createViewHookComponent(rawOptions); + // TODO: remove content/classNames/didMount/etc from options? + } + return { + superType: rawOptions.type, + component: component, + rawOptions: rawOptions, + }; + } + function createViewHookComponent(options) { + return function (viewProps) { return (createElement(ViewContextType.Consumer, null, function (context) { return (createElement(ViewRoot, { viewSpec: context.viewSpec }, function (viewElRef, viewClassNames) { + var hookProps = __assign(__assign({}, viewProps), { nextDayThreshold: context.options.nextDayThreshold }); + return (createElement(RenderHook, { hookProps: hookProps, classNames: options.classNames, content: options.content, didMount: options.didMount, willUnmount: options.willUnmount, elRef: viewElRef }, function (rootElRef, customClassNames, innerElRef, innerContent) { return (createElement("div", { className: viewClassNames.concat(customClassNames).join(' '), ref: rootElRef }, innerContent)); })); + })); })); }; + } + + function buildViewSpecs(defaultInputs, optionOverrides, dynamicOptionOverrides, localeDefaults) { + var defaultConfigs = parseViewConfigs(defaultInputs); + var overrideConfigs = parseViewConfigs(optionOverrides.views); + var viewDefs = compileViewDefs(defaultConfigs, overrideConfigs); + return mapHash(viewDefs, function (viewDef) { return buildViewSpec(viewDef, overrideConfigs, optionOverrides, dynamicOptionOverrides, localeDefaults); }); + } + function buildViewSpec(viewDef, overrideConfigs, optionOverrides, dynamicOptionOverrides, localeDefaults) { + var durationInput = viewDef.overrides.duration || + viewDef.defaults.duration || + dynamicOptionOverrides.duration || + optionOverrides.duration; + var duration = null; + var durationUnit = ''; + var singleUnit = ''; + var singleUnitOverrides = {}; + if (durationInput) { + duration = createDurationCached(durationInput); + if (duration) { // valid? + var denom = greatestDurationDenominator(duration); + durationUnit = denom.unit; + if (denom.value === 1) { + singleUnit = durationUnit; + singleUnitOverrides = overrideConfigs[durationUnit] ? overrideConfigs[durationUnit].rawOptions : {}; + } + } + } + var queryButtonText = function (optionsSubset) { + var buttonTextMap = optionsSubset.buttonText || {}; + var buttonTextKey = viewDef.defaults.buttonTextKey; + if (buttonTextKey != null && buttonTextMap[buttonTextKey] != null) { + return buttonTextMap[buttonTextKey]; + } + if (buttonTextMap[viewDef.type] != null) { + return buttonTextMap[viewDef.type]; + } + if (buttonTextMap[singleUnit] != null) { + return buttonTextMap[singleUnit]; + } + return null; + }; + return { + type: viewDef.type, + component: viewDef.component, + duration: duration, + durationUnit: durationUnit, + singleUnit: singleUnit, + optionDefaults: viewDef.defaults, + optionOverrides: __assign(__assign({}, singleUnitOverrides), viewDef.overrides), + buttonTextOverride: queryButtonText(dynamicOptionOverrides) || + queryButtonText(optionOverrides) || // constructor-specified buttonText lookup hash takes precedence + viewDef.overrides.buttonText, + buttonTextDefault: queryButtonText(localeDefaults) || + viewDef.defaults.buttonText || + queryButtonText(BASE_OPTION_DEFAULTS) || + viewDef.type, // fall back to given view name + }; + } + // hack to get memoization working + var durationInputMap = {}; + function createDurationCached(durationInput) { + var json = JSON.stringify(durationInput); + var res = durationInputMap[json]; + if (res === undefined) { + res = createDuration(durationInput); + durationInputMap[json] = res; + } + return res; + } + + var DateProfileGenerator = /** @class */ (function () { + function DateProfileGenerator(props) { + this.props = props; + this.nowDate = getNow(props.nowInput, props.dateEnv); + this.initHiddenDays(); + } + /* Date Range Computation + ------------------------------------------------------------------------------------------------------------------*/ + // Builds a structure with info about what the dates/ranges will be for the "prev" view. + DateProfileGenerator.prototype.buildPrev = function (currentDateProfile, currentDate, forceToValid) { + var dateEnv = this.props.dateEnv; + var prevDate = dateEnv.subtract(dateEnv.startOf(currentDate, currentDateProfile.currentRangeUnit), // important for start-of-month + currentDateProfile.dateIncrement); + return this.build(prevDate, -1, forceToValid); + }; + // Builds a structure with info about what the dates/ranges will be for the "next" view. + DateProfileGenerator.prototype.buildNext = function (currentDateProfile, currentDate, forceToValid) { + var dateEnv = this.props.dateEnv; + var nextDate = dateEnv.add(dateEnv.startOf(currentDate, currentDateProfile.currentRangeUnit), // important for start-of-month + currentDateProfile.dateIncrement); + return this.build(nextDate, 1, forceToValid); + }; + // Builds a structure holding dates/ranges for rendering around the given date. + // Optional direction param indicates whether the date is being incremented/decremented + // from its previous value. decremented = -1, incremented = 1 (default). + DateProfileGenerator.prototype.build = function (currentDate, direction, forceToValid) { + if (forceToValid === void 0) { forceToValid = true; } + var props = this.props; + var validRange; + var currentInfo; + var isRangeAllDay; + var renderRange; + var activeRange; + var isValid; + validRange = this.buildValidRange(); + validRange = this.trimHiddenDays(validRange); + if (forceToValid) { + currentDate = constrainMarkerToRange(currentDate, validRange); + } + currentInfo = this.buildCurrentRangeInfo(currentDate, direction); + isRangeAllDay = /^(year|month|week|day)$/.test(currentInfo.unit); + renderRange = this.buildRenderRange(this.trimHiddenDays(currentInfo.range), currentInfo.unit, isRangeAllDay); + renderRange = this.trimHiddenDays(renderRange); + activeRange = renderRange; + if (!props.showNonCurrentDates) { + activeRange = intersectRanges(activeRange, currentInfo.range); + } + activeRange = this.adjustActiveRange(activeRange); + activeRange = intersectRanges(activeRange, validRange); // might return null + // it's invalid if the originally requested date is not contained, + // or if the range is completely outside of the valid range. + isValid = rangesIntersect(currentInfo.range, validRange); + return { + // constraint for where prev/next operations can go and where events can be dragged/resized to. + // an object with optional start and end properties. + validRange: validRange, + // range the view is formally responsible for. + // for example, a month view might have 1st-31st, excluding padded dates + currentRange: currentInfo.range, + // name of largest unit being displayed, like "month" or "week" + currentRangeUnit: currentInfo.unit, + isRangeAllDay: isRangeAllDay, + // dates that display events and accept drag-n-drop + // will be `null` if no dates accept events + activeRange: activeRange, + // date range with a rendered skeleton + // includes not-active days that need some sort of DOM + renderRange: renderRange, + // Duration object that denotes the first visible time of any given day + slotMinTime: props.slotMinTime, + // Duration object that denotes the exclusive visible end time of any given day + slotMaxTime: props.slotMaxTime, + isValid: isValid, + // how far the current date will move for a prev/next operation + dateIncrement: this.buildDateIncrement(currentInfo.duration), + // pass a fallback (might be null) ^ + }; + }; + // Builds an object with optional start/end properties. + // Indicates the minimum/maximum dates to display. + // not responsible for trimming hidden days. + DateProfileGenerator.prototype.buildValidRange = function () { + var input = this.props.validRangeInput; + var simpleInput = typeof input === 'function' + ? input.call(this.props.calendarApi, this.nowDate) + : input; + return this.refineRange(simpleInput) || + { start: null, end: null }; // completely open-ended + }; + // Builds a structure with info about the "current" range, the range that is + // highlighted as being the current month for example. + // See build() for a description of `direction`. + // Guaranteed to have `range` and `unit` properties. `duration` is optional. + DateProfileGenerator.prototype.buildCurrentRangeInfo = function (date, direction) { + var props = this.props; + var duration = null; + var unit = null; + var range = null; + var dayCount; + if (props.duration) { + duration = props.duration; + unit = props.durationUnit; + range = this.buildRangeFromDuration(date, direction, duration, unit); + } + else if ((dayCount = this.props.dayCount)) { + unit = 'day'; + range = this.buildRangeFromDayCount(date, direction, dayCount); + } + else if ((range = this.buildCustomVisibleRange(date))) { + unit = props.dateEnv.greatestWholeUnit(range.start, range.end).unit; + } + else { + duration = this.getFallbackDuration(); + unit = greatestDurationDenominator(duration).unit; + range = this.buildRangeFromDuration(date, direction, duration, unit); + } + return { duration: duration, unit: unit, range: range }; + }; + DateProfileGenerator.prototype.getFallbackDuration = function () { + return createDuration({ day: 1 }); + }; + // Returns a new activeRange to have time values (un-ambiguate) + // slotMinTime or slotMaxTime causes the range to expand. + DateProfileGenerator.prototype.adjustActiveRange = function (range) { + var _a = this.props, dateEnv = _a.dateEnv, usesMinMaxTime = _a.usesMinMaxTime, slotMinTime = _a.slotMinTime, slotMaxTime = _a.slotMaxTime; + var start = range.start, end = range.end; + if (usesMinMaxTime) { + // expand active range if slotMinTime is negative (why not when positive?) + if (asRoughDays(slotMinTime) < 0) { + start = startOfDay(start); // necessary? + start = dateEnv.add(start, slotMinTime); + } + // expand active range if slotMaxTime is beyond one day (why not when negative?) + if (asRoughDays(slotMaxTime) > 1) { + end = startOfDay(end); // necessary? + end = addDays(end, -1); + end = dateEnv.add(end, slotMaxTime); + } + } + return { start: start, end: end }; + }; + // Builds the "current" range when it is specified as an explicit duration. + // `unit` is the already-computed greatestDurationDenominator unit of duration. + DateProfileGenerator.prototype.buildRangeFromDuration = function (date, direction, duration, unit) { + var _a = this.props, dateEnv = _a.dateEnv, dateAlignment = _a.dateAlignment; + var start; + var end; + var res; + // compute what the alignment should be + if (!dateAlignment) { + var dateIncrement = this.props.dateIncrement; + if (dateIncrement) { + // use the smaller of the two units + if (asRoughMs(dateIncrement) < asRoughMs(duration)) { + dateAlignment = greatestDurationDenominator(dateIncrement).unit; + } + else { + dateAlignment = unit; + } + } + else { + dateAlignment = unit; + } + } + // if the view displays a single day or smaller + if (asRoughDays(duration) <= 1) { + if (this.isHiddenDay(start)) { + start = this.skipHiddenDays(start, direction); + start = startOfDay(start); + } + } + function computeRes() { + start = dateEnv.startOf(date, dateAlignment); + end = dateEnv.add(start, duration); + res = { start: start, end: end }; + } + computeRes(); + // if range is completely enveloped by hidden days, go past the hidden days + if (!this.trimHiddenDays(res)) { + date = this.skipHiddenDays(date, direction); + computeRes(); + } + return res; + }; + // Builds the "current" range when a dayCount is specified. + DateProfileGenerator.prototype.buildRangeFromDayCount = function (date, direction, dayCount) { + var _a = this.props, dateEnv = _a.dateEnv, dateAlignment = _a.dateAlignment; + var runningCount = 0; + var start = date; + var end; + if (dateAlignment) { + start = dateEnv.startOf(start, dateAlignment); + } + start = startOfDay(start); + start = this.skipHiddenDays(start, direction); + end = start; + do { + end = addDays(end, 1); + if (!this.isHiddenDay(end)) { + runningCount += 1; + } + } while (runningCount < dayCount); + return { start: start, end: end }; + }; + // Builds a normalized range object for the "visible" range, + // which is a way to define the currentRange and activeRange at the same time. + DateProfileGenerator.prototype.buildCustomVisibleRange = function (date) { + var props = this.props; + var input = props.visibleRangeInput; + var simpleInput = typeof input === 'function' + ? input.call(props.calendarApi, props.dateEnv.toDate(date)) + : input; + var range = this.refineRange(simpleInput); + if (range && (range.start == null || range.end == null)) { + return null; + } + return range; + }; + // Computes the range that will represent the element/cells for *rendering*, + // but which may have voided days/times. + // not responsible for trimming hidden days. + DateProfileGenerator.prototype.buildRenderRange = function (currentRange, currentRangeUnit, isRangeAllDay) { + return currentRange; + }; + // Compute the duration value that should be added/substracted to the current date + // when a prev/next operation happens. + DateProfileGenerator.prototype.buildDateIncrement = function (fallback) { + var dateIncrement = this.props.dateIncrement; + var customAlignment; + if (dateIncrement) { + return dateIncrement; + } + if ((customAlignment = this.props.dateAlignment)) { + return createDuration(1, customAlignment); + } + if (fallback) { + return fallback; + } + return createDuration({ days: 1 }); + }; + DateProfileGenerator.prototype.refineRange = function (rangeInput) { + if (rangeInput) { + var range = parseRange(rangeInput, this.props.dateEnv); + if (range) { + range = computeVisibleDayRange(range); + } + return range; + } + return null; + }; + /* Hidden Days + ------------------------------------------------------------------------------------------------------------------*/ + // Initializes internal variables related to calculating hidden days-of-week + DateProfileGenerator.prototype.initHiddenDays = function () { + var hiddenDays = this.props.hiddenDays || []; // array of day-of-week indices that are hidden + var isHiddenDayHash = []; // is the day-of-week hidden? (hash with day-of-week-index -> bool) + var dayCnt = 0; + var i; + if (this.props.weekends === false) { + hiddenDays.push(0, 6); // 0=sunday, 6=saturday + } + for (i = 0; i < 7; i += 1) { + if (!(isHiddenDayHash[i] = hiddenDays.indexOf(i) !== -1)) { + dayCnt += 1; + } + } + if (!dayCnt) { + throw new Error('invalid hiddenDays'); // all days were hidden? bad. + } + this.isHiddenDayHash = isHiddenDayHash; + }; + // Remove days from the beginning and end of the range that are computed as hidden. + // If the whole range is trimmed off, returns null + DateProfileGenerator.prototype.trimHiddenDays = function (range) { + var start = range.start, end = range.end; + if (start) { + start = this.skipHiddenDays(start); + } + if (end) { + end = this.skipHiddenDays(end, -1, true); + } + if (start == null || end == null || start < end) { + return { start: start, end: end }; + } + return null; + }; + // Is the current day hidden? + // `day` is a day-of-week index (0-6), or a Date (used for UTC) + DateProfileGenerator.prototype.isHiddenDay = function (day) { + if (day instanceof Date) { + day = day.getUTCDay(); + } + return this.isHiddenDayHash[day]; + }; + // Incrementing the current day until it is no longer a hidden day, returning a copy. + // DOES NOT CONSIDER validRange! + // If the initial value of `date` is not a hidden day, don't do anything. + // Pass `isExclusive` as `true` if you are dealing with an end date. + // `inc` defaults to `1` (increment one day forward each time) + DateProfileGenerator.prototype.skipHiddenDays = function (date, inc, isExclusive) { + if (inc === void 0) { inc = 1; } + if (isExclusive === void 0) { isExclusive = false; } + while (this.isHiddenDayHash[(date.getUTCDay() + (isExclusive ? inc : 0) + 7) % 7]) { + date = addDays(date, inc); + } + return date; + }; + return DateProfileGenerator; + }()); + + function reduceViewType(viewType, action) { + switch (action.type) { + case 'CHANGE_VIEW_TYPE': + viewType = action.viewType; + } + return viewType; + } + + function reduceDynamicOptionOverrides(dynamicOptionOverrides, action) { + var _a; + switch (action.type) { + case 'SET_OPTION': + return __assign(__assign({}, dynamicOptionOverrides), (_a = {}, _a[action.optionName] = action.rawOptionValue, _a)); + default: + return dynamicOptionOverrides; + } + } + + function reduceDateProfile(currentDateProfile, action, currentDate, dateProfileGenerator) { + var dp; + switch (action.type) { + case 'CHANGE_VIEW_TYPE': + return dateProfileGenerator.build(action.dateMarker || currentDate); + case 'CHANGE_DATE': + return dateProfileGenerator.build(action.dateMarker); + case 'PREV': + dp = dateProfileGenerator.buildPrev(currentDateProfile, currentDate); + if (dp.isValid) { + return dp; + } + break; + case 'NEXT': + dp = dateProfileGenerator.buildNext(currentDateProfile, currentDate); + if (dp.isValid) { + return dp; + } + break; + } + return currentDateProfile; + } + + function initEventSources(calendarOptions, dateProfile, context) { + var activeRange = dateProfile ? dateProfile.activeRange : null; + return addSources({}, parseInitialSources(calendarOptions, context), activeRange, context); + } + function reduceEventSources(eventSources, action, dateProfile, context) { + var activeRange = dateProfile ? dateProfile.activeRange : null; // need this check? + switch (action.type) { + case 'ADD_EVENT_SOURCES': // already parsed + return addSources(eventSources, action.sources, activeRange, context); + case 'REMOVE_EVENT_SOURCE': + return removeSource(eventSources, action.sourceId); + case 'PREV': // TODO: how do we track all actions that affect dateProfile :( + case 'NEXT': + case 'CHANGE_DATE': + case 'CHANGE_VIEW_TYPE': + if (dateProfile) { + return fetchDirtySources(eventSources, activeRange, context); + } + return eventSources; + case 'FETCH_EVENT_SOURCES': + return fetchSourcesByIds(eventSources, action.sourceIds ? // why no type? + arrayToHash(action.sourceIds) : + excludeStaticSources(eventSources, context), activeRange, action.isRefetch || false, context); + case 'RECEIVE_EVENTS': + case 'RECEIVE_EVENT_ERROR': + return receiveResponse(eventSources, action.sourceId, action.fetchId, action.fetchRange); + case 'REMOVE_ALL_EVENT_SOURCES': + return {}; + default: + return eventSources; + } + } + function reduceEventSourcesNewTimeZone(eventSources, dateProfile, context) { + var activeRange = dateProfile ? dateProfile.activeRange : null; // need this check? + return fetchSourcesByIds(eventSources, excludeStaticSources(eventSources, context), activeRange, true, context); + } + function computeEventSourcesLoading(eventSources) { + for (var sourceId in eventSources) { + if (eventSources[sourceId].isFetching) { + return true; + } + } + return false; + } + function addSources(eventSourceHash, sources, fetchRange, context) { + var hash = {}; + for (var _i = 0, sources_1 = sources; _i < sources_1.length; _i++) { + var source = sources_1[_i]; + hash[source.sourceId] = source; + } + if (fetchRange) { + hash = fetchDirtySources(hash, fetchRange, context); + } + return __assign(__assign({}, eventSourceHash), hash); + } + function removeSource(eventSourceHash, sourceId) { + return filterHash(eventSourceHash, function (eventSource) { return eventSource.sourceId !== sourceId; }); + } + function fetchDirtySources(sourceHash, fetchRange, context) { + return fetchSourcesByIds(sourceHash, filterHash(sourceHash, function (eventSource) { return isSourceDirty(eventSource, fetchRange, context); }), fetchRange, false, context); + } + function isSourceDirty(eventSource, fetchRange, context) { + if (!doesSourceNeedRange(eventSource, context)) { + return !eventSource.latestFetchId; + } + return !context.options.lazyFetching || + !eventSource.fetchRange || + eventSource.isFetching || // always cancel outdated in-progress fetches + fetchRange.start < eventSource.fetchRange.start || + fetchRange.end > eventSource.fetchRange.end; + } + function fetchSourcesByIds(prevSources, sourceIdHash, fetchRange, isRefetch, context) { + var nextSources = {}; + for (var sourceId in prevSources) { + var source = prevSources[sourceId]; + if (sourceIdHash[sourceId]) { + nextSources[sourceId] = fetchSource(source, fetchRange, isRefetch, context); + } + else { + nextSources[sourceId] = source; + } + } + return nextSources; + } + function fetchSource(eventSource, fetchRange, isRefetch, context) { + var options = context.options, calendarApi = context.calendarApi; + var sourceDef = context.pluginHooks.eventSourceDefs[eventSource.sourceDefId]; + var fetchId = guid(); + sourceDef.fetch({ + eventSource: eventSource, + range: fetchRange, + isRefetch: isRefetch, + context: context, + }, function (res) { + var rawEvents = res.rawEvents; + if (options.eventSourceSuccess) { + rawEvents = options.eventSourceSuccess.call(calendarApi, rawEvents, res.xhr) || rawEvents; + } + if (eventSource.success) { + rawEvents = eventSource.success.call(calendarApi, rawEvents, res.xhr) || rawEvents; + } + context.dispatch({ + type: 'RECEIVE_EVENTS', + sourceId: eventSource.sourceId, + fetchId: fetchId, + fetchRange: fetchRange, + rawEvents: rawEvents, + }); + }, function (error) { + console.warn(error.message, error); + if (options.eventSourceFailure) { + options.eventSourceFailure.call(calendarApi, error); + } + if (eventSource.failure) { + eventSource.failure(error); + } + context.dispatch({ + type: 'RECEIVE_EVENT_ERROR', + sourceId: eventSource.sourceId, + fetchId: fetchId, + fetchRange: fetchRange, + error: error, + }); + }); + return __assign(__assign({}, eventSource), { isFetching: true, latestFetchId: fetchId }); + } + function receiveResponse(sourceHash, sourceId, fetchId, fetchRange) { + var _a; + var eventSource = sourceHash[sourceId]; + if (eventSource && // not already removed + fetchId === eventSource.latestFetchId) { + return __assign(__assign({}, sourceHash), (_a = {}, _a[sourceId] = __assign(__assign({}, eventSource), { isFetching: false, fetchRange: fetchRange }), _a)); + } + return sourceHash; + } + function excludeStaticSources(eventSources, context) { + return filterHash(eventSources, function (eventSource) { return doesSourceNeedRange(eventSource, context); }); + } + function parseInitialSources(rawOptions, context) { + var refiners = buildEventSourceRefiners(context); + var rawSources = [].concat(rawOptions.eventSources || []); + var sources = []; // parsed + if (rawOptions.initialEvents) { + rawSources.unshift(rawOptions.initialEvents); + } + if (rawOptions.events) { + rawSources.unshift(rawOptions.events); + } + for (var _i = 0, rawSources_1 = rawSources; _i < rawSources_1.length; _i++) { + var rawSource = rawSources_1[_i]; + var source = parseEventSource(rawSource, context, refiners); + if (source) { + sources.push(source); + } + } + return sources; + } + function doesSourceNeedRange(eventSource, context) { + var defs = context.pluginHooks.eventSourceDefs; + return !defs[eventSource.sourceDefId].ignoreRange; + } + + function reduceEventStore(eventStore, action, eventSources, dateProfile, context) { + switch (action.type) { + case 'RECEIVE_EVENTS': // raw + return receiveRawEvents(eventStore, eventSources[action.sourceId], action.fetchId, action.fetchRange, action.rawEvents, context); + case 'ADD_EVENTS': // already parsed, but not expanded + return addEvent(eventStore, action.eventStore, // new ones + dateProfile ? dateProfile.activeRange : null, context); + case 'RESET_EVENTS': + return action.eventStore; + case 'MERGE_EVENTS': // already parsed and expanded + return mergeEventStores(eventStore, action.eventStore); + case 'PREV': // TODO: how do we track all actions that affect dateProfile :( + case 'NEXT': + case 'CHANGE_DATE': + case 'CHANGE_VIEW_TYPE': + if (dateProfile) { + return expandRecurring(eventStore, dateProfile.activeRange, context); + } + return eventStore; + case 'REMOVE_EVENTS': + return excludeSubEventStore(eventStore, action.eventStore); + case 'REMOVE_EVENT_SOURCE': + return excludeEventsBySourceId(eventStore, action.sourceId); + case 'REMOVE_ALL_EVENT_SOURCES': + return filterEventStoreDefs(eventStore, function (eventDef) { return (!eventDef.sourceId // only keep events with no source id + ); }); + case 'REMOVE_ALL_EVENTS': + return createEmptyEventStore(); + default: + return eventStore; + } + } + function receiveRawEvents(eventStore, eventSource, fetchId, fetchRange, rawEvents, context) { + if (eventSource && // not already removed + fetchId === eventSource.latestFetchId // TODO: wish this logic was always in event-sources + ) { + var subset = parseEvents(transformRawEvents(rawEvents, eventSource, context), eventSource, context); + if (fetchRange) { + subset = expandRecurring(subset, fetchRange, context); + } + return mergeEventStores(excludeEventsBySourceId(eventStore, eventSource.sourceId), subset); + } + return eventStore; + } + function transformRawEvents(rawEvents, eventSource, context) { + var calEachTransform = context.options.eventDataTransform; + var sourceEachTransform = eventSource ? eventSource.eventDataTransform : null; + if (sourceEachTransform) { + rawEvents = transformEachRawEvent(rawEvents, sourceEachTransform); + } + if (calEachTransform) { + rawEvents = transformEachRawEvent(rawEvents, calEachTransform); + } + return rawEvents; + } + function transformEachRawEvent(rawEvents, func) { + var refinedEvents; + if (!func) { + refinedEvents = rawEvents; + } + else { + refinedEvents = []; + for (var _i = 0, rawEvents_1 = rawEvents; _i < rawEvents_1.length; _i++) { + var rawEvent = rawEvents_1[_i]; + var refinedEvent = func(rawEvent); + if (refinedEvent) { + refinedEvents.push(refinedEvent); + } + else if (refinedEvent == null) { + refinedEvents.push(rawEvent); + } // if a different falsy value, do nothing + } + } + return refinedEvents; + } + function addEvent(eventStore, subset, expandRange, context) { + if (expandRange) { + subset = expandRecurring(subset, expandRange, context); + } + return mergeEventStores(eventStore, subset); + } + function rezoneEventStoreDates(eventStore, oldDateEnv, newDateEnv) { + var defs = eventStore.defs; + var instances = mapHash(eventStore.instances, function (instance) { + var def = defs[instance.defId]; + if (def.allDay || def.recurringDef) { + return instance; // isn't dependent on timezone + } + return __assign(__assign({}, instance), { range: { + start: newDateEnv.createMarker(oldDateEnv.toDate(instance.range.start, instance.forcedStartTzo)), + end: newDateEnv.createMarker(oldDateEnv.toDate(instance.range.end, instance.forcedEndTzo)), + }, forcedStartTzo: newDateEnv.canComputeOffset ? null : instance.forcedStartTzo, forcedEndTzo: newDateEnv.canComputeOffset ? null : instance.forcedEndTzo }); + }); + return { defs: defs, instances: instances }; + } + function excludeEventsBySourceId(eventStore, sourceId) { + return filterEventStoreDefs(eventStore, function (eventDef) { return eventDef.sourceId !== sourceId; }); + } + // QUESTION: why not just return instances? do a general object-property-exclusion util + function excludeInstances(eventStore, removals) { + return { + defs: eventStore.defs, + instances: filterHash(eventStore.instances, function (instance) { return !removals[instance.instanceId]; }), + }; + } + + function reduceDateSelection(currentSelection, action) { + switch (action.type) { + case 'UNSELECT_DATES': + return null; + case 'SELECT_DATES': + return action.selection; + default: + return currentSelection; + } + } + + function reduceSelectedEvent(currentInstanceId, action) { + switch (action.type) { + case 'UNSELECT_EVENT': + return ''; + case 'SELECT_EVENT': + return action.eventInstanceId; + default: + return currentInstanceId; + } + } + + function reduceEventDrag(currentDrag, action) { + var newDrag; + switch (action.type) { + case 'UNSET_EVENT_DRAG': + return null; + case 'SET_EVENT_DRAG': + newDrag = action.state; + return { + affectedEvents: newDrag.affectedEvents, + mutatedEvents: newDrag.mutatedEvents, + isEvent: newDrag.isEvent, + }; + default: + return currentDrag; + } + } + + function reduceEventResize(currentResize, action) { + var newResize; + switch (action.type) { + case 'UNSET_EVENT_RESIZE': + return null; + case 'SET_EVENT_RESIZE': + newResize = action.state; + return { + affectedEvents: newResize.affectedEvents, + mutatedEvents: newResize.mutatedEvents, + isEvent: newResize.isEvent, + }; + default: + return currentResize; + } + } + + function parseToolbars(calendarOptions, calendarOptionOverrides, theme, viewSpecs, calendarApi) { + var viewsWithButtons = []; + var headerToolbar = calendarOptions.headerToolbar ? parseToolbar(calendarOptions.headerToolbar, calendarOptions, calendarOptionOverrides, theme, viewSpecs, calendarApi, viewsWithButtons) : null; + var footerToolbar = calendarOptions.footerToolbar ? parseToolbar(calendarOptions.footerToolbar, calendarOptions, calendarOptionOverrides, theme, viewSpecs, calendarApi, viewsWithButtons) : null; + return { headerToolbar: headerToolbar, footerToolbar: footerToolbar, viewsWithButtons: viewsWithButtons }; + } + function parseToolbar(sectionStrHash, calendarOptions, calendarOptionOverrides, theme, viewSpecs, calendarApi, viewsWithButtons) { + return mapHash(sectionStrHash, function (sectionStr) { return parseSection(sectionStr, calendarOptions, calendarOptionOverrides, theme, viewSpecs, calendarApi, viewsWithButtons); }); + } + /* + BAD: querying icons and text here. should be done at render time + */ + function parseSection(sectionStr, calendarOptions, calendarOptionOverrides, theme, viewSpecs, calendarApi, viewsWithButtons) { + var isRtl = calendarOptions.direction === 'rtl'; + var calendarCustomButtons = calendarOptions.customButtons || {}; + var calendarButtonTextOverrides = calendarOptionOverrides.buttonText || {}; + var calendarButtonText = calendarOptions.buttonText || {}; + var sectionSubstrs = sectionStr ? sectionStr.split(' ') : []; + return sectionSubstrs.map(function (buttonGroupStr) { return (buttonGroupStr.split(',').map(function (buttonName) { + if (buttonName === 'title') { + return { buttonName: buttonName }; + } + var customButtonProps; + var viewSpec; + var buttonClick; + var buttonIcon; // only one of these will be set + var buttonText; // " + if ((customButtonProps = calendarCustomButtons[buttonName])) { + buttonClick = function (ev) { + if (customButtonProps.click) { + customButtonProps.click.call(ev.target, ev, ev.target); // TODO: use Calendar this context? + } + }; + (buttonIcon = theme.getCustomButtonIconClass(customButtonProps)) || + (buttonIcon = theme.getIconClass(buttonName, isRtl)) || + (buttonText = customButtonProps.text); + } + else if ((viewSpec = viewSpecs[buttonName])) { + viewsWithButtons.push(buttonName); + buttonClick = function () { + calendarApi.changeView(buttonName); + }; + (buttonText = viewSpec.buttonTextOverride) || + (buttonIcon = theme.getIconClass(buttonName, isRtl)) || + (buttonText = viewSpec.buttonTextDefault); + } + else if (calendarApi[buttonName]) { // a calendarApi method + buttonClick = function () { + calendarApi[buttonName](); + }; + (buttonText = calendarButtonTextOverrides[buttonName]) || + (buttonIcon = theme.getIconClass(buttonName, isRtl)) || + (buttonText = calendarButtonText[buttonName]); + // ^ everything else is considered default + } + return { buttonName: buttonName, buttonClick: buttonClick, buttonIcon: buttonIcon, buttonText: buttonText }; + })); }); + } + + var eventSourceDef$3 = { + ignoreRange: true, + parseMeta: function (refined) { + if (Array.isArray(refined.events)) { + return refined.events; + } + return null; + }, + fetch: function (arg, success) { + success({ + rawEvents: arg.eventSource.meta, + }); + }, + }; + var arrayEventSourcePlugin = createPlugin({ + eventSourceDefs: [eventSourceDef$3], + }); + + var eventSourceDef$2 = { + parseMeta: function (refined) { + if (typeof refined.events === 'function') { + return refined.events; + } + return null; + }, + fetch: function (arg, success, failure) { + var dateEnv = arg.context.dateEnv; + var func = arg.eventSource.meta; + unpromisify(func.bind(null, buildRangeApiWithTimeZone(arg.range, dateEnv)), function (rawEvents) { + success({ rawEvents: rawEvents }); // needs an object response + }, failure); + }, + }; + var funcEventSourcePlugin = createPlugin({ + eventSourceDefs: [eventSourceDef$2], + }); + + function requestJson(method, url, params, successCallback, failureCallback) { + method = method.toUpperCase(); + var body = null; + if (method === 'GET') { + url = injectQueryStringParams(url, params); + } + else { + body = encodeParams(params); + } + var xhr = new XMLHttpRequest(); + xhr.open(method, url, true); + if (method !== 'GET') { + xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); + } + xhr.onload = function () { + if (xhr.status >= 200 && xhr.status < 400) { + var parsed = false; + var res = void 0; + try { + res = JSON.parse(xhr.responseText); + parsed = true; + } + catch (err) { + // will handle parsed=false + } + if (parsed) { + successCallback(res, xhr); + } + else { + failureCallback('Failure parsing JSON', xhr); + } + } + else { + failureCallback('Request failed', xhr); + } + }; + xhr.onerror = function () { + failureCallback('Request failed', xhr); + }; + xhr.send(body); + } + function injectQueryStringParams(url, params) { + return url + + (url.indexOf('?') === -1 ? '?' : '&') + + encodeParams(params); + } + function encodeParams(params) { + var parts = []; + for (var key in params) { + parts.push(encodeURIComponent(key) + "=" + encodeURIComponent(params[key])); + } + return parts.join('&'); + } + + var JSON_FEED_EVENT_SOURCE_REFINERS = { + method: String, + extraParams: identity, + startParam: String, + endParam: String, + timeZoneParam: String, + }; + + var eventSourceDef$1 = { + parseMeta: function (refined) { + if (refined.url && (refined.format === 'json' || !refined.format)) { + return { + url: refined.url, + format: 'json', + method: (refined.method || 'GET').toUpperCase(), + extraParams: refined.extraParams, + startParam: refined.startParam, + endParam: refined.endParam, + timeZoneParam: refined.timeZoneParam, + }; + } + return null; + }, + fetch: function (arg, success, failure) { + var meta = arg.eventSource.meta; + var requestParams = buildRequestParams$1(meta, arg.range, arg.context); + requestJson(meta.method, meta.url, requestParams, function (rawEvents, xhr) { + success({ rawEvents: rawEvents, xhr: xhr }); + }, function (errorMessage, xhr) { + failure({ message: errorMessage, xhr: xhr }); + }); + }, + }; + var jsonFeedEventSourcePlugin = createPlugin({ + eventSourceRefiners: JSON_FEED_EVENT_SOURCE_REFINERS, + eventSourceDefs: [eventSourceDef$1], + }); + function buildRequestParams$1(meta, range, context) { + var dateEnv = context.dateEnv, options = context.options; + var startParam; + var endParam; + var timeZoneParam; + var customRequestParams; + var params = {}; + startParam = meta.startParam; + if (startParam == null) { + startParam = options.startParam; + } + endParam = meta.endParam; + if (endParam == null) { + endParam = options.endParam; + } + timeZoneParam = meta.timeZoneParam; + if (timeZoneParam == null) { + timeZoneParam = options.timeZoneParam; + } + // retrieve any outbound GET/POST data from the options + if (typeof meta.extraParams === 'function') { + // supplied as a function that returns a key/value object + customRequestParams = meta.extraParams(); + } + else { + // probably supplied as a straight key/value object + customRequestParams = meta.extraParams || {}; + } + __assign(params, customRequestParams); + params[startParam] = dateEnv.formatIso(range.start); + params[endParam] = dateEnv.formatIso(range.end); + if (dateEnv.timeZone !== 'local') { + params[timeZoneParam] = dateEnv.timeZone; + } + return params; + } + + var SIMPLE_RECURRING_REFINERS = { + daysOfWeek: identity, + startTime: createDuration, + endTime: createDuration, + duration: createDuration, + startRecur: identity, + endRecur: identity, + }; + + var recurring = { + parse: function (refined, dateEnv) { + if (refined.daysOfWeek || refined.startTime || refined.endTime || refined.startRecur || refined.endRecur) { + var recurringData = { + daysOfWeek: refined.daysOfWeek || null, + startTime: refined.startTime || null, + endTime: refined.endTime || null, + startRecur: refined.startRecur ? dateEnv.createMarker(refined.startRecur) : null, + endRecur: refined.endRecur ? dateEnv.createMarker(refined.endRecur) : null, + }; + var duration = void 0; + if (refined.duration) { + duration = refined.duration; + } + if (!duration && refined.startTime && refined.endTime) { + duration = subtractDurations(refined.endTime, refined.startTime); + } + return { + allDayGuess: Boolean(!refined.startTime && !refined.endTime), + duration: duration, + typeData: recurringData, // doesn't need endTime anymore but oh well + }; + } + return null; + }, + expand: function (typeData, framingRange, dateEnv) { + var clippedFramingRange = intersectRanges(framingRange, { start: typeData.startRecur, end: typeData.endRecur }); + if (clippedFramingRange) { + return expandRanges(typeData.daysOfWeek, typeData.startTime, clippedFramingRange, dateEnv); + } + return []; + }, + }; + var simpleRecurringEventsPlugin = createPlugin({ + recurringTypes: [recurring], + eventRefiners: SIMPLE_RECURRING_REFINERS, + }); + function expandRanges(daysOfWeek, startTime, framingRange, dateEnv) { + var dowHash = daysOfWeek ? arrayToHash(daysOfWeek) : null; + var dayMarker = startOfDay(framingRange.start); + var endMarker = framingRange.end; + var instanceStarts = []; + while (dayMarker < endMarker) { + var instanceStart + // if everyday, or this particular day-of-week + = void 0; + // if everyday, or this particular day-of-week + if (!dowHash || dowHash[dayMarker.getUTCDay()]) { + if (startTime) { + instanceStart = dateEnv.add(dayMarker, startTime); + } + else { + instanceStart = dayMarker; + } + instanceStarts.push(instanceStart); + } + dayMarker = addDays(dayMarker, 1); + } + return instanceStarts; + } + + var changeHandlerPlugin = createPlugin({ + optionChangeHandlers: { + events: function (events, context) { + handleEventSources([events], context); + }, + eventSources: handleEventSources, + }, + }); + /* + BUG: if `event` was supplied, all previously-given `eventSources` will be wiped out + */ + function handleEventSources(inputs, context) { + var unfoundSources = hashValuesToArray(context.getCurrentData().eventSources); + var newInputs = []; + for (var _i = 0, inputs_1 = inputs; _i < inputs_1.length; _i++) { + var input = inputs_1[_i]; + var inputFound = false; + for (var i = 0; i < unfoundSources.length; i += 1) { + if (unfoundSources[i]._raw === input) { + unfoundSources.splice(i, 1); // delete + inputFound = true; + break; + } + } + if (!inputFound) { + newInputs.push(input); + } + } + for (var _a = 0, unfoundSources_1 = unfoundSources; _a < unfoundSources_1.length; _a++) { + var unfoundSource = unfoundSources_1[_a]; + context.dispatch({ + type: 'REMOVE_EVENT_SOURCE', + sourceId: unfoundSource.sourceId, + }); + } + for (var _b = 0, newInputs_1 = newInputs; _b < newInputs_1.length; _b++) { + var newInput = newInputs_1[_b]; + context.calendarApi.addEventSource(newInput); + } + } + + function handleDateProfile(dateProfile, context) { + context.emitter.trigger('datesSet', __assign(__assign({}, buildRangeApiWithTimeZone(dateProfile.activeRange, context.dateEnv)), { view: context.viewApi })); + } + + function handleEventStore(eventStore, context) { + var emitter = context.emitter; + if (emitter.hasHandlers('eventsSet')) { + emitter.trigger('eventsSet', buildEventApis(eventStore, context)); + } + } + + /* + this array is exposed on the root namespace so that UMD plugins can add to it. + see the rollup-bundles script. + */ + var globalPlugins = [ + arrayEventSourcePlugin, + funcEventSourcePlugin, + jsonFeedEventSourcePlugin, + simpleRecurringEventsPlugin, + changeHandlerPlugin, + createPlugin({ + isLoadingFuncs: [ + function (state) { return computeEventSourcesLoading(state.eventSources); }, + ], + contentTypeHandlers: { + html: function () { return ({ render: injectHtml }); }, + domNodes: function () { return ({ render: injectDomNodes }); }, + }, + propSetHandlers: { + dateProfile: handleDateProfile, + eventStore: handleEventStore, + }, + }), + ]; + function injectHtml(el, html) { + el.innerHTML = html; + } + function injectDomNodes(el, domNodes) { + var oldNodes = Array.prototype.slice.call(el.childNodes); // TODO: use array util + var newNodes = Array.prototype.slice.call(domNodes); // TODO: use array util + if (!isArraysEqual(oldNodes, newNodes)) { + for (var _i = 0, newNodes_1 = newNodes; _i < newNodes_1.length; _i++) { + var newNode = newNodes_1[_i]; + el.appendChild(newNode); + } + oldNodes.forEach(removeElement); + } + } + + var DelayedRunner = /** @class */ (function () { + function DelayedRunner(drainedOption) { + this.drainedOption = drainedOption; + this.isRunning = false; + this.isDirty = false; + this.pauseDepths = {}; + this.timeoutId = 0; + } + DelayedRunner.prototype.request = function (delay) { + this.isDirty = true; + if (!this.isPaused()) { + this.clearTimeout(); + if (delay == null) { + this.tryDrain(); + } + else { + this.timeoutId = setTimeout(// NOT OPTIMAL! TODO: look at debounce + this.tryDrain.bind(this), delay); + } + } + }; + DelayedRunner.prototype.pause = function (scope) { + if (scope === void 0) { scope = ''; } + var pauseDepths = this.pauseDepths; + pauseDepths[scope] = (pauseDepths[scope] || 0) + 1; + this.clearTimeout(); + }; + DelayedRunner.prototype.resume = function (scope, force) { + if (scope === void 0) { scope = ''; } + var pauseDepths = this.pauseDepths; + if (scope in pauseDepths) { + if (force) { + delete pauseDepths[scope]; + } + else { + pauseDepths[scope] -= 1; + var depth = pauseDepths[scope]; + if (depth <= 0) { + delete pauseDepths[scope]; + } + } + this.tryDrain(); + } + }; + DelayedRunner.prototype.isPaused = function () { + return Object.keys(this.pauseDepths).length; + }; + DelayedRunner.prototype.tryDrain = function () { + if (!this.isRunning && !this.isPaused()) { + this.isRunning = true; + while (this.isDirty) { + this.isDirty = false; + this.drained(); // might set isDirty to true again + } + this.isRunning = false; + } + }; + DelayedRunner.prototype.clear = function () { + this.clearTimeout(); + this.isDirty = false; + this.pauseDepths = {}; + }; + DelayedRunner.prototype.clearTimeout = function () { + if (this.timeoutId) { + clearTimeout(this.timeoutId); + this.timeoutId = 0; + } + }; + DelayedRunner.prototype.drained = function () { + if (this.drainedOption) { + this.drainedOption(); + } + }; + return DelayedRunner; + }()); + + var TaskRunner = /** @class */ (function () { + function TaskRunner(runTaskOption, drainedOption) { + this.runTaskOption = runTaskOption; + this.drainedOption = drainedOption; + this.queue = []; + this.delayedRunner = new DelayedRunner(this.drain.bind(this)); + } + TaskRunner.prototype.request = function (task, delay) { + this.queue.push(task); + this.delayedRunner.request(delay); + }; + TaskRunner.prototype.pause = function (scope) { + this.delayedRunner.pause(scope); + }; + TaskRunner.prototype.resume = function (scope, force) { + this.delayedRunner.resume(scope, force); + }; + TaskRunner.prototype.drain = function () { + var queue = this.queue; + while (queue.length) { + var completedTasks = []; + var task = void 0; + while ((task = queue.shift())) { + this.runTask(task); + completedTasks.push(task); + } + this.drained(completedTasks); + } // keep going, in case new tasks were added in the drained handler + }; + TaskRunner.prototype.runTask = function (task) { + if (this.runTaskOption) { + this.runTaskOption(task); + } + }; + TaskRunner.prototype.drained = function (completedTasks) { + if (this.drainedOption) { + this.drainedOption(completedTasks); + } + }; + return TaskRunner; + }()); + + // Computes what the title at the top of the calendarApi should be for this view + function buildTitle(dateProfile, viewOptions, dateEnv) { + var range; + // for views that span a large unit of time, show the proper interval, ignoring stray days before and after + if (/^(year|month)$/.test(dateProfile.currentRangeUnit)) { + range = dateProfile.currentRange; + } + else { // for day units or smaller, use the actual day range + range = dateProfile.activeRange; + } + return dateEnv.formatRange(range.start, range.end, createFormatter(viewOptions.titleFormat || buildTitleFormat(dateProfile)), { + isEndExclusive: dateProfile.isRangeAllDay, + defaultSeparator: viewOptions.titleRangeSeparator, + }); + } + // Generates the format string that should be used to generate the title for the current date range. + // Attempts to compute the most appropriate format if not explicitly specified with `titleFormat`. + function buildTitleFormat(dateProfile) { + var currentRangeUnit = dateProfile.currentRangeUnit; + if (currentRangeUnit === 'year') { + return { year: 'numeric' }; + } + if (currentRangeUnit === 'month') { + return { year: 'numeric', month: 'long' }; // like "September 2014" + } + var days = diffWholeDays(dateProfile.currentRange.start, dateProfile.currentRange.end); + if (days !== null && days > 1) { + // multi-day range. shorter, like "Sep 9 - 10 2014" + return { year: 'numeric', month: 'short', day: 'numeric' }; + } + // one day. longer, like "September 9 2014" + return { year: 'numeric', month: 'long', day: 'numeric' }; + } + + // in future refactor, do the redux-style function(state=initial) for initial-state + // also, whatever is happening in constructor, have it happen in action queue too + var CalendarDataManager = /** @class */ (function () { + function CalendarDataManager(props) { + var _this = this; + this.computeOptionsData = memoize(this._computeOptionsData); + this.computeCurrentViewData = memoize(this._computeCurrentViewData); + this.organizeRawLocales = memoize(organizeRawLocales); + this.buildLocale = memoize(buildLocale); + this.buildPluginHooks = buildBuildPluginHooks(); + this.buildDateEnv = memoize(buildDateEnv); + this.buildTheme = memoize(buildTheme); + this.parseToolbars = memoize(parseToolbars); + this.buildViewSpecs = memoize(buildViewSpecs); + this.buildDateProfileGenerator = memoizeObjArg(buildDateProfileGenerator); + this.buildViewApi = memoize(buildViewApi); + this.buildViewUiProps = memoizeObjArg(buildViewUiProps); + this.buildEventUiBySource = memoize(buildEventUiBySource, isPropsEqual); + this.buildEventUiBases = memoize(buildEventUiBases); + this.parseContextBusinessHours = memoizeObjArg(parseContextBusinessHours); + this.buildTitle = memoize(buildTitle); + this.emitter = new Emitter(); + this.actionRunner = new TaskRunner(this._handleAction.bind(this), this.updateData.bind(this)); + this.currentCalendarOptionsInput = {}; + this.currentCalendarOptionsRefined = {}; + this.currentViewOptionsInput = {}; + this.currentViewOptionsRefined = {}; + this.currentCalendarOptionsRefiners = {}; + this.getCurrentData = function () { return _this.data; }; + this.dispatch = function (action) { + _this.actionRunner.request(action); // protects against recursive calls to _handleAction + }; + this.props = props; + this.actionRunner.pause(); + var dynamicOptionOverrides = {}; + var optionsData = this.computeOptionsData(props.optionOverrides, dynamicOptionOverrides, props.calendarApi); + var currentViewType = optionsData.calendarOptions.initialView || optionsData.pluginHooks.initialView; + var currentViewData = this.computeCurrentViewData(currentViewType, optionsData, props.optionOverrides, dynamicOptionOverrides); + // wire things up + // TODO: not DRY + props.calendarApi.currentDataManager = this; + this.emitter.setThisContext(props.calendarApi); + this.emitter.setOptions(currentViewData.options); + var currentDate = getInitialDate(optionsData.calendarOptions, optionsData.dateEnv); + var dateProfile = currentViewData.dateProfileGenerator.build(currentDate); + if (!rangeContainsMarker(dateProfile.activeRange, currentDate)) { + currentDate = dateProfile.currentRange.start; + } + var calendarContext = { + dateEnv: optionsData.dateEnv, + options: optionsData.calendarOptions, + pluginHooks: optionsData.pluginHooks, + calendarApi: props.calendarApi, + dispatch: this.dispatch, + emitter: this.emitter, + getCurrentData: this.getCurrentData, + }; + // needs to be after setThisContext + for (var _i = 0, _a = optionsData.pluginHooks.contextInit; _i < _a.length; _i++) { + var callback = _a[_i]; + callback(calendarContext); + } + // NOT DRY + var eventSources = initEventSources(optionsData.calendarOptions, dateProfile, calendarContext); + var initialState = { + dynamicOptionOverrides: dynamicOptionOverrides, + currentViewType: currentViewType, + currentDate: currentDate, + dateProfile: dateProfile, + businessHours: this.parseContextBusinessHours(calendarContext), + eventSources: eventSources, + eventUiBases: {}, + eventStore: createEmptyEventStore(), + renderableEventStore: createEmptyEventStore(), + dateSelection: null, + eventSelection: '', + eventDrag: null, + eventResize: null, + selectionConfig: this.buildViewUiProps(calendarContext).selectionConfig, + }; + var contextAndState = __assign(__assign({}, calendarContext), initialState); + for (var _b = 0, _c = optionsData.pluginHooks.reducers; _b < _c.length; _b++) { + var reducer = _c[_b]; + __assign(initialState, reducer(null, null, contextAndState)); + } + if (computeIsLoading(initialState, calendarContext)) { + this.emitter.trigger('loading', true); // NOT DRY + } + this.state = initialState; + this.updateData(); + this.actionRunner.resume(); + } + CalendarDataManager.prototype.resetOptions = function (optionOverrides, append) { + var props = this.props; + props.optionOverrides = append + ? __assign(__assign({}, props.optionOverrides), optionOverrides) : optionOverrides; + this.actionRunner.request({ + type: 'NOTHING', + }); + }; + CalendarDataManager.prototype._handleAction = function (action) { + var _a = this, props = _a.props, state = _a.state, emitter = _a.emitter; + var dynamicOptionOverrides = reduceDynamicOptionOverrides(state.dynamicOptionOverrides, action); + var optionsData = this.computeOptionsData(props.optionOverrides, dynamicOptionOverrides, props.calendarApi); + var currentViewType = reduceViewType(state.currentViewType, action); + var currentViewData = this.computeCurrentViewData(currentViewType, optionsData, props.optionOverrides, dynamicOptionOverrides); + // wire things up + // TODO: not DRY + props.calendarApi.currentDataManager = this; + emitter.setThisContext(props.calendarApi); + emitter.setOptions(currentViewData.options); + var calendarContext = { + dateEnv: optionsData.dateEnv, + options: optionsData.calendarOptions, + pluginHooks: optionsData.pluginHooks, + calendarApi: props.calendarApi, + dispatch: this.dispatch, + emitter: emitter, + getCurrentData: this.getCurrentData, + }; + var currentDate = state.currentDate, dateProfile = state.dateProfile; + if (this.data && this.data.dateProfileGenerator !== currentViewData.dateProfileGenerator) { // hack + dateProfile = currentViewData.dateProfileGenerator.build(currentDate); + } + currentDate = reduceCurrentDate(currentDate, action); + dateProfile = reduceDateProfile(dateProfile, action, currentDate, currentViewData.dateProfileGenerator); + if (action.type === 'PREV' || // TODO: move this logic into DateProfileGenerator + action.type === 'NEXT' || // " + !rangeContainsMarker(dateProfile.currentRange, currentDate)) { + currentDate = dateProfile.currentRange.start; + } + var eventSources = reduceEventSources(state.eventSources, action, dateProfile, calendarContext); + var eventStore = reduceEventStore(state.eventStore, action, eventSources, dateProfile, calendarContext); + var isEventsLoading = computeEventSourcesLoading(eventSources); // BAD. also called in this func in computeIsLoading + var renderableEventStore = (isEventsLoading && !currentViewData.options.progressiveEventRendering) ? + (state.renderableEventStore || eventStore) : // try from previous state + eventStore; + var _b = this.buildViewUiProps(calendarContext), eventUiSingleBase = _b.eventUiSingleBase, selectionConfig = _b.selectionConfig; // will memoize obj + var eventUiBySource = this.buildEventUiBySource(eventSources); + var eventUiBases = this.buildEventUiBases(renderableEventStore.defs, eventUiSingleBase, eventUiBySource); + var newState = { + dynamicOptionOverrides: dynamicOptionOverrides, + currentViewType: currentViewType, + currentDate: currentDate, + dateProfile: dateProfile, + eventSources: eventSources, + eventStore: eventStore, + renderableEventStore: renderableEventStore, + selectionConfig: selectionConfig, + eventUiBases: eventUiBases, + businessHours: this.parseContextBusinessHours(calendarContext), + dateSelection: reduceDateSelection(state.dateSelection, action), + eventSelection: reduceSelectedEvent(state.eventSelection, action), + eventDrag: reduceEventDrag(state.eventDrag, action), + eventResize: reduceEventResize(state.eventResize, action), + }; + var contextAndState = __assign(__assign({}, calendarContext), newState); + for (var _i = 0, _c = optionsData.pluginHooks.reducers; _i < _c.length; _i++) { + var reducer = _c[_i]; + __assign(newState, reducer(state, action, contextAndState)); // give the OLD state, for old value + } + var wasLoading = computeIsLoading(state, calendarContext); + var isLoading = computeIsLoading(newState, calendarContext); + // TODO: use propSetHandlers in plugin system + if (!wasLoading && isLoading) { + emitter.trigger('loading', true); + } + else if (wasLoading && !isLoading) { + emitter.trigger('loading', false); + } + this.state = newState; + if (props.onAction) { + props.onAction(action); + } + }; + CalendarDataManager.prototype.updateData = function () { + var _a = this, props = _a.props, state = _a.state; + var oldData = this.data; + var optionsData = this.computeOptionsData(props.optionOverrides, state.dynamicOptionOverrides, props.calendarApi); + var currentViewData = this.computeCurrentViewData(state.currentViewType, optionsData, props.optionOverrides, state.dynamicOptionOverrides); + var data = this.data = __assign(__assign(__assign({ viewTitle: this.buildTitle(state.dateProfile, currentViewData.options, optionsData.dateEnv), calendarApi: props.calendarApi, dispatch: this.dispatch, emitter: this.emitter, getCurrentData: this.getCurrentData }, optionsData), currentViewData), state); + var changeHandlers = optionsData.pluginHooks.optionChangeHandlers; + var oldCalendarOptions = oldData && oldData.calendarOptions; + var newCalendarOptions = optionsData.calendarOptions; + if (oldCalendarOptions && oldCalendarOptions !== newCalendarOptions) { + if (oldCalendarOptions.timeZone !== newCalendarOptions.timeZone) { + // hack + state.eventSources = data.eventSources = reduceEventSourcesNewTimeZone(data.eventSources, state.dateProfile, data); + state.eventStore = data.eventStore = rezoneEventStoreDates(data.eventStore, oldData.dateEnv, data.dateEnv); + } + for (var optionName in changeHandlers) { + if (oldCalendarOptions[optionName] !== newCalendarOptions[optionName]) { + changeHandlers[optionName](newCalendarOptions[optionName], data); + } + } + } + if (props.onData) { + props.onData(data); + } + }; + CalendarDataManager.prototype._computeOptionsData = function (optionOverrides, dynamicOptionOverrides, calendarApi) { + // TODO: blacklist options that are handled by optionChangeHandlers + var _a = this.processRawCalendarOptions(optionOverrides, dynamicOptionOverrides), refinedOptions = _a.refinedOptions, pluginHooks = _a.pluginHooks, localeDefaults = _a.localeDefaults, availableLocaleData = _a.availableLocaleData, extra = _a.extra; + warnUnknownOptions(extra); + var dateEnv = this.buildDateEnv(refinedOptions.timeZone, refinedOptions.locale, refinedOptions.weekNumberCalculation, refinedOptions.firstDay, refinedOptions.weekText, pluginHooks, availableLocaleData, refinedOptions.defaultRangeSeparator); + var viewSpecs = this.buildViewSpecs(pluginHooks.views, optionOverrides, dynamicOptionOverrides, localeDefaults); + var theme = this.buildTheme(refinedOptions, pluginHooks); + var toolbarConfig = this.parseToolbars(refinedOptions, optionOverrides, theme, viewSpecs, calendarApi); + return { + calendarOptions: refinedOptions, + pluginHooks: pluginHooks, + dateEnv: dateEnv, + viewSpecs: viewSpecs, + theme: theme, + toolbarConfig: toolbarConfig, + localeDefaults: localeDefaults, + availableRawLocales: availableLocaleData.map, + }; + }; + // always called from behind a memoizer + CalendarDataManager.prototype.processRawCalendarOptions = function (optionOverrides, dynamicOptionOverrides) { + var _a = mergeRawOptions([ + BASE_OPTION_DEFAULTS, + optionOverrides, + dynamicOptionOverrides, + ]), locales = _a.locales, locale = _a.locale; + var availableLocaleData = this.organizeRawLocales(locales); + var availableRawLocales = availableLocaleData.map; + var localeDefaults = this.buildLocale(locale || availableLocaleData.defaultCode, availableRawLocales).options; + var pluginHooks = this.buildPluginHooks(optionOverrides.plugins || [], globalPlugins); + var refiners = this.currentCalendarOptionsRefiners = __assign(__assign(__assign(__assign(__assign({}, BASE_OPTION_REFINERS), CALENDAR_LISTENER_REFINERS), CALENDAR_OPTION_REFINERS), pluginHooks.listenerRefiners), pluginHooks.optionRefiners); + var extra = {}; + var raw = mergeRawOptions([ + BASE_OPTION_DEFAULTS, + localeDefaults, + optionOverrides, + dynamicOptionOverrides, + ]); + var refined = {}; + var currentRaw = this.currentCalendarOptionsInput; + var currentRefined = this.currentCalendarOptionsRefined; + var anyChanges = false; + for (var optionName in raw) { + if (optionName !== 'plugins') { // because plugins is special-cased + if (raw[optionName] === currentRaw[optionName] || + (COMPLEX_OPTION_COMPARATORS[optionName] && + (optionName in currentRaw) && + COMPLEX_OPTION_COMPARATORS[optionName](currentRaw[optionName], raw[optionName]))) { + refined[optionName] = currentRefined[optionName]; + } + else if (refiners[optionName]) { + refined[optionName] = refiners[optionName](raw[optionName]); + anyChanges = true; + } + else { + extra[optionName] = currentRaw[optionName]; + } + } + } + if (anyChanges) { + this.currentCalendarOptionsInput = raw; + this.currentCalendarOptionsRefined = refined; + } + return { + rawOptions: this.currentCalendarOptionsInput, + refinedOptions: this.currentCalendarOptionsRefined, + pluginHooks: pluginHooks, + availableLocaleData: availableLocaleData, + localeDefaults: localeDefaults, + extra: extra, + }; + }; + CalendarDataManager.prototype._computeCurrentViewData = function (viewType, optionsData, optionOverrides, dynamicOptionOverrides) { + var viewSpec = optionsData.viewSpecs[viewType]; + if (!viewSpec) { + throw new Error("viewType \"" + viewType + "\" is not available. Please make sure you've loaded all neccessary plugins"); + } + var _a = this.processRawViewOptions(viewSpec, optionsData.pluginHooks, optionsData.localeDefaults, optionOverrides, dynamicOptionOverrides), refinedOptions = _a.refinedOptions, extra = _a.extra; + warnUnknownOptions(extra); + var dateProfileGenerator = this.buildDateProfileGenerator({ + dateProfileGeneratorClass: viewSpec.optionDefaults.dateProfileGeneratorClass, + duration: viewSpec.duration, + durationUnit: viewSpec.durationUnit, + usesMinMaxTime: viewSpec.optionDefaults.usesMinMaxTime, + dateEnv: optionsData.dateEnv, + calendarApi: this.props.calendarApi, + slotMinTime: refinedOptions.slotMinTime, + slotMaxTime: refinedOptions.slotMaxTime, + showNonCurrentDates: refinedOptions.showNonCurrentDates, + dayCount: refinedOptions.dayCount, + dateAlignment: refinedOptions.dateAlignment, + dateIncrement: refinedOptions.dateIncrement, + hiddenDays: refinedOptions.hiddenDays, + weekends: refinedOptions.weekends, + nowInput: refinedOptions.now, + validRangeInput: refinedOptions.validRange, + visibleRangeInput: refinedOptions.visibleRange, + monthMode: refinedOptions.monthMode, + fixedWeekCount: refinedOptions.fixedWeekCount, + }); + var viewApi = this.buildViewApi(viewType, this.getCurrentData, optionsData.dateEnv); + return { viewSpec: viewSpec, options: refinedOptions, dateProfileGenerator: dateProfileGenerator, viewApi: viewApi }; + }; + CalendarDataManager.prototype.processRawViewOptions = function (viewSpec, pluginHooks, localeDefaults, optionOverrides, dynamicOptionOverrides) { + var raw = mergeRawOptions([ + BASE_OPTION_DEFAULTS, + viewSpec.optionDefaults, + localeDefaults, + optionOverrides, + viewSpec.optionOverrides, + dynamicOptionOverrides, + ]); + var refiners = __assign(__assign(__assign(__assign(__assign(__assign({}, BASE_OPTION_REFINERS), CALENDAR_LISTENER_REFINERS), CALENDAR_OPTION_REFINERS), VIEW_OPTION_REFINERS), pluginHooks.listenerRefiners), pluginHooks.optionRefiners); + var refined = {}; + var currentRaw = this.currentViewOptionsInput; + var currentRefined = this.currentViewOptionsRefined; + var anyChanges = false; + var extra = {}; + for (var optionName in raw) { + if (raw[optionName] === currentRaw[optionName]) { + refined[optionName] = currentRefined[optionName]; + } + else { + if (raw[optionName] === this.currentCalendarOptionsInput[optionName]) { + if (optionName in this.currentCalendarOptionsRefined) { // might be an "extra" prop + refined[optionName] = this.currentCalendarOptionsRefined[optionName]; + } + } + else if (refiners[optionName]) { + refined[optionName] = refiners[optionName](raw[optionName]); + } + else { + extra[optionName] = raw[optionName]; + } + anyChanges = true; + } + } + if (anyChanges) { + this.currentViewOptionsInput = raw; + this.currentViewOptionsRefined = refined; + } + return { + rawOptions: this.currentViewOptionsInput, + refinedOptions: this.currentViewOptionsRefined, + extra: extra, + }; + }; + return CalendarDataManager; + }()); + function buildDateEnv(timeZone, explicitLocale, weekNumberCalculation, firstDay, weekText, pluginHooks, availableLocaleData, defaultSeparator) { + var locale = buildLocale(explicitLocale || availableLocaleData.defaultCode, availableLocaleData.map); + return new DateEnv({ + calendarSystem: 'gregory', + timeZone: timeZone, + namedTimeZoneImpl: pluginHooks.namedTimeZonedImpl, + locale: locale, + weekNumberCalculation: weekNumberCalculation, + firstDay: firstDay, + weekText: weekText, + cmdFormatter: pluginHooks.cmdFormatter, + defaultSeparator: defaultSeparator, + }); + } + function buildTheme(options, pluginHooks) { + var ThemeClass = pluginHooks.themeClasses[options.themeSystem] || StandardTheme; + return new ThemeClass(options); + } + function buildDateProfileGenerator(props) { + var DateProfileGeneratorClass = props.dateProfileGeneratorClass || DateProfileGenerator; + return new DateProfileGeneratorClass(props); + } + function buildViewApi(type, getCurrentData, dateEnv) { + return new ViewApi(type, getCurrentData, dateEnv); + } + function buildEventUiBySource(eventSources) { + return mapHash(eventSources, function (eventSource) { return eventSource.ui; }); + } + function buildEventUiBases(eventDefs, eventUiSingleBase, eventUiBySource) { + var eventUiBases = { '': eventUiSingleBase }; + for (var defId in eventDefs) { + var def = eventDefs[defId]; + if (def.sourceId && eventUiBySource[def.sourceId]) { + eventUiBases[defId] = eventUiBySource[def.sourceId]; + } + } + return eventUiBases; + } + function buildViewUiProps(calendarContext) { + var options = calendarContext.options; + return { + eventUiSingleBase: createEventUi({ + display: options.eventDisplay, + editable: options.editable, + startEditable: options.eventStartEditable, + durationEditable: options.eventDurationEditable, + constraint: options.eventConstraint, + overlap: typeof options.eventOverlap === 'boolean' ? options.eventOverlap : undefined, + allow: options.eventAllow, + backgroundColor: options.eventBackgroundColor, + borderColor: options.eventBorderColor, + textColor: options.eventTextColor, + color: options.eventColor, + // classNames: options.eventClassNames // render hook will handle this + }, calendarContext), + selectionConfig: createEventUi({ + constraint: options.selectConstraint, + overlap: typeof options.selectOverlap === 'boolean' ? options.selectOverlap : undefined, + allow: options.selectAllow, + }, calendarContext), + }; + } + function computeIsLoading(state, context) { + for (var _i = 0, _a = context.pluginHooks.isLoadingFuncs; _i < _a.length; _i++) { + var isLoadingFunc = _a[_i]; + if (isLoadingFunc(state)) { + return true; + } + } + return false; + } + function parseContextBusinessHours(calendarContext) { + return parseBusinessHours(calendarContext.options.businessHours, calendarContext); + } + function warnUnknownOptions(options, viewName) { + for (var optionName in options) { + console.warn("Unknown option '" + optionName + "'" + + (viewName ? " for view '" + viewName + "'" : '')); + } + } + + // TODO: move this to react plugin? + var CalendarDataProvider = /** @class */ (function (_super) { + __extends(CalendarDataProvider, _super); + function CalendarDataProvider(props) { + var _this = _super.call(this, props) || this; + _this.handleData = function (data) { + if (!_this.dataManager) { // still within initial run, before assignment in constructor + // eslint-disable-next-line react/no-direct-mutation-state + _this.state = data; // can't use setState yet + } + else { + _this.setState(data); + } + }; + _this.dataManager = new CalendarDataManager({ + optionOverrides: props.optionOverrides, + calendarApi: props.calendarApi, + onData: _this.handleData, + }); + return _this; + } + CalendarDataProvider.prototype.render = function () { + return this.props.children(this.state); + }; + CalendarDataProvider.prototype.componentDidUpdate = function (prevProps) { + var newOptionOverrides = this.props.optionOverrides; + if (newOptionOverrides !== prevProps.optionOverrides) { // prevent recursive handleData + this.dataManager.resetOptions(newOptionOverrides); + } + }; + return CalendarDataProvider; + }(Component)); + + // HELPERS + /* + if nextDayThreshold is specified, slicing is done in an all-day fashion. + you can get nextDayThreshold from context.nextDayThreshold + */ + function sliceEvents(props, allDay) { + return sliceEventStore(props.eventStore, props.eventUiBases, props.dateProfile.activeRange, allDay ? props.nextDayThreshold : null).fg; + } + + var NamedTimeZoneImpl = /** @class */ (function () { + function NamedTimeZoneImpl(timeZoneName) { + this.timeZoneName = timeZoneName; + } + return NamedTimeZoneImpl; + }()); + + var SegHierarchy = /** @class */ (function () { + function SegHierarchy() { + // settings + this.strictOrder = false; + this.allowReslicing = false; + this.maxCoord = -1; // -1 means no max + this.maxStackCnt = -1; // -1 means no max + this.levelCoords = []; // ordered + this.entriesByLevel = []; // parallel with levelCoords + this.stackCnts = {}; // TODO: use better technique!? + } + SegHierarchy.prototype.addSegs = function (inputs) { + var hiddenEntries = []; + for (var _i = 0, inputs_1 = inputs; _i < inputs_1.length; _i++) { + var input = inputs_1[_i]; + this.insertEntry(input, hiddenEntries); + } + return hiddenEntries; + }; + SegHierarchy.prototype.insertEntry = function (entry, hiddenEntries) { + var insertion = this.findInsertion(entry); + if (this.isInsertionValid(insertion, entry)) { + this.insertEntryAt(entry, insertion); + return 1; + } + return this.handleInvalidInsertion(insertion, entry, hiddenEntries); + }; + SegHierarchy.prototype.isInsertionValid = function (insertion, entry) { + return (this.maxCoord === -1 || insertion.levelCoord + entry.thickness <= this.maxCoord) && + (this.maxStackCnt === -1 || insertion.stackCnt < this.maxStackCnt); + }; + // returns number of new entries inserted + SegHierarchy.prototype.handleInvalidInsertion = function (insertion, entry, hiddenEntries) { + if (this.allowReslicing && insertion.touchingEntry) { + return this.splitEntry(entry, insertion.touchingEntry, hiddenEntries); + } + hiddenEntries.push(entry); + return 0; + }; + SegHierarchy.prototype.splitEntry = function (entry, barrier, hiddenEntries) { + var partCnt = 0; + var splitHiddenEntries = []; + var entrySpan = entry.span; + var barrierSpan = barrier.span; + if (entrySpan.start < barrierSpan.start) { + partCnt += this.insertEntry({ + index: entry.index, + thickness: entry.thickness, + span: { start: entrySpan.start, end: barrierSpan.start }, + }, splitHiddenEntries); + } + if (entrySpan.end > barrierSpan.end) { + partCnt += this.insertEntry({ + index: entry.index, + thickness: entry.thickness, + span: { start: barrierSpan.end, end: entrySpan.end }, + }, splitHiddenEntries); + } + if (partCnt) { + hiddenEntries.push.apply(hiddenEntries, __spreadArray([{ + index: entry.index, + thickness: entry.thickness, + span: intersectSpans(barrierSpan, entrySpan), // guaranteed to intersect + }], splitHiddenEntries)); + return partCnt; + } + hiddenEntries.push(entry); + return 0; + }; + SegHierarchy.prototype.insertEntryAt = function (entry, insertion) { + var _a = this, entriesByLevel = _a.entriesByLevel, levelCoords = _a.levelCoords; + if (insertion.lateral === -1) { + // create a new level + insertAt(levelCoords, insertion.level, insertion.levelCoord); + insertAt(entriesByLevel, insertion.level, [entry]); + } + else { + // insert into existing level + insertAt(entriesByLevel[insertion.level], insertion.lateral, entry); + } + this.stackCnts[buildEntryKey(entry)] = insertion.stackCnt; + }; + SegHierarchy.prototype.findInsertion = function (newEntry) { + var _a = this, levelCoords = _a.levelCoords, entriesByLevel = _a.entriesByLevel, strictOrder = _a.strictOrder, stackCnts = _a.stackCnts; + var levelCnt = levelCoords.length; + var candidateCoord = 0; + var touchingLevel = -1; + var touchingLateral = -1; + var touchingEntry = null; + var stackCnt = 0; + for (var trackingLevel = 0; trackingLevel < levelCnt; trackingLevel += 1) { + var trackingCoord = levelCoords[trackingLevel]; + // if the current level is past the placed entry, we have found a good empty space and can stop. + // if strictOrder, keep finding more lateral intersections. + if (!strictOrder && trackingCoord >= candidateCoord + newEntry.thickness) { + break; + } + var trackingEntries = entriesByLevel[trackingLevel]; + var trackingEntry = void 0; + var searchRes = binarySearch(trackingEntries, newEntry.span.start, getEntrySpanEnd); // find first entry after newEntry's end + var lateralIndex = searchRes[0] + searchRes[1]; // if exact match (which doesn't collide), go to next one + while ( // loop through entries that horizontally intersect + (trackingEntry = trackingEntries[lateralIndex]) && // but not past the whole entry list + trackingEntry.span.start < newEntry.span.end // and not entirely past newEntry + ) { + var trackingEntryBottom = trackingCoord + trackingEntry.thickness; + // intersects into the top of the candidate? + if (trackingEntryBottom > candidateCoord) { + candidateCoord = trackingEntryBottom; + touchingEntry = trackingEntry; + touchingLevel = trackingLevel; + touchingLateral = lateralIndex; + } + // butts up against top of candidate? (will happen if just intersected as well) + if (trackingEntryBottom === candidateCoord) { + // accumulate the highest possible stackCnt of the trackingEntries that butt up + stackCnt = Math.max(stackCnt, stackCnts[buildEntryKey(trackingEntry)] + 1); + } + lateralIndex += 1; + } + } + // the destination level will be after touchingEntry's level. find it + var destLevel = 0; + if (touchingEntry) { + destLevel = touchingLevel + 1; + while (destLevel < levelCnt && levelCoords[destLevel] < candidateCoord) { + destLevel += 1; + } + } + // if adding to an existing level, find where to insert + var destLateral = -1; + if (destLevel < levelCnt && levelCoords[destLevel] === candidateCoord) { + destLateral = binarySearch(entriesByLevel[destLevel], newEntry.span.end, getEntrySpanEnd)[0]; + } + return { + touchingLevel: touchingLevel, + touchingLateral: touchingLateral, + touchingEntry: touchingEntry, + stackCnt: stackCnt, + levelCoord: candidateCoord, + level: destLevel, + lateral: destLateral, + }; + }; + // sorted by levelCoord (lowest to highest) + SegHierarchy.prototype.toRects = function () { + var _a = this, entriesByLevel = _a.entriesByLevel, levelCoords = _a.levelCoords; + var levelCnt = entriesByLevel.length; + var rects = []; + for (var level = 0; level < levelCnt; level += 1) { + var entries = entriesByLevel[level]; + var levelCoord = levelCoords[level]; + for (var _i = 0, entries_1 = entries; _i < entries_1.length; _i++) { + var entry = entries_1[_i]; + rects.push(__assign(__assign({}, entry), { levelCoord: levelCoord })); + } + } + return rects; + }; + return SegHierarchy; + }()); + function getEntrySpanEnd(entry) { + return entry.span.end; + } + function buildEntryKey(entry) { + return entry.index + ':' + entry.span.start; + } + // returns groups with entries sorted by input order + function groupIntersectingEntries(entries) { + var merges = []; + for (var _i = 0, entries_2 = entries; _i < entries_2.length; _i++) { + var entry = entries_2[_i]; + var filteredMerges = []; + var hungryMerge = { + span: entry.span, + entries: [entry], + }; + for (var _a = 0, merges_1 = merges; _a < merges_1.length; _a++) { + var merge = merges_1[_a]; + if (intersectSpans(merge.span, hungryMerge.span)) { + hungryMerge = { + entries: merge.entries.concat(hungryMerge.entries), + span: joinSpans(merge.span, hungryMerge.span), + }; + } + else { + filteredMerges.push(merge); + } + } + filteredMerges.push(hungryMerge); + merges = filteredMerges; + } + return merges; + } + function joinSpans(span0, span1) { + return { + start: Math.min(span0.start, span1.start), + end: Math.max(span0.end, span1.end), + }; + } + function intersectSpans(span0, span1) { + var start = Math.max(span0.start, span1.start); + var end = Math.min(span0.end, span1.end); + if (start < end) { + return { start: start, end: end }; + } + return null; + } + // general util + // --------------------------------------------------------------------------------------------------------------------- + function insertAt(arr, index, item) { + arr.splice(index, 0, item); + } + function binarySearch(a, searchVal, getItemVal) { + var startIndex = 0; + var endIndex = a.length; // exclusive + if (!endIndex || searchVal < getItemVal(a[startIndex])) { // no items OR before first item + return [0, 0]; + } + if (searchVal > getItemVal(a[endIndex - 1])) { // after last item + return [endIndex, 0]; + } + while (startIndex < endIndex) { + var middleIndex = Math.floor(startIndex + (endIndex - startIndex) / 2); + var middleVal = getItemVal(a[middleIndex]); + if (searchVal < middleVal) { + endIndex = middleIndex; + } + else if (searchVal > middleVal) { + startIndex = middleIndex + 1; + } + else { // equal! + return [middleIndex, 1]; + } + } + return [startIndex, 0]; + } + + var Interaction = /** @class */ (function () { + function Interaction(settings) { + this.component = settings.component; + this.isHitComboAllowed = settings.isHitComboAllowed || null; + } + Interaction.prototype.destroy = function () { + }; + return Interaction; + }()); + function parseInteractionSettings(component, input) { + return { + component: component, + el: input.el, + useEventCenter: input.useEventCenter != null ? input.useEventCenter : true, + isHitComboAllowed: input.isHitComboAllowed || null, + }; + } + function interactionSettingsToStore(settings) { + var _a; + return _a = {}, + _a[settings.component.uid] = settings, + _a; + } + // global state + var interactionSettingsStore = {}; + + /* + An abstraction for a dragging interaction originating on an event. + Does higher-level things than PointerDragger, such as possibly: + - a "mirror" that moves with the pointer + - a minimum number of pixels or other criteria for a true drag to begin + + subclasses must emit: + - pointerdown + - dragstart + - dragmove + - pointerup + - dragend + */ + var ElementDragging = /** @class */ (function () { + function ElementDragging(el, selector) { + this.emitter = new Emitter(); + } + ElementDragging.prototype.destroy = function () { + }; + ElementDragging.prototype.setMirrorIsVisible = function (bool) { + // optional if subclass doesn't want to support a mirror + }; + ElementDragging.prototype.setMirrorNeedsRevert = function (bool) { + // optional if subclass doesn't want to support a mirror + }; + ElementDragging.prototype.setAutoScrollEnabled = function (bool) { + // optional + }; + return ElementDragging; + }()); + + // TODO: get rid of this in favor of options system, + // tho it's really easy to access this globally rather than pass thru options. + var config = {}; + + /* + Information about what will happen when an external element is dragged-and-dropped + onto a calendar. Contains information for creating an event. + */ + var DRAG_META_REFINERS = { + startTime: createDuration, + duration: createDuration, + create: Boolean, + sourceId: String, + }; + function parseDragMeta(raw) { + var _a = refineProps(raw, DRAG_META_REFINERS), refined = _a.refined, extra = _a.extra; + return { + startTime: refined.startTime || null, + duration: refined.duration || null, + create: refined.create != null ? refined.create : true, + sourceId: refined.sourceId, + leftoverProps: extra, + }; + } + + var ToolbarSection = /** @class */ (function (_super) { + __extends(ToolbarSection, _super); + function ToolbarSection() { + return _super !== null && _super.apply(this, arguments) || this; + } + ToolbarSection.prototype.render = function () { + var _this = this; + var children = this.props.widgetGroups.map(function (widgetGroup) { return _this.renderWidgetGroup(widgetGroup); }); + return createElement.apply(void 0, __spreadArray(['div', { className: 'fc-toolbar-chunk' }], children)); + }; + ToolbarSection.prototype.renderWidgetGroup = function (widgetGroup) { + var props = this.props; + var theme = this.context.theme; + var children = []; + var isOnlyButtons = true; + for (var _i = 0, widgetGroup_1 = widgetGroup; _i < widgetGroup_1.length; _i++) { + var widget = widgetGroup_1[_i]; + var buttonName = widget.buttonName, buttonClick = widget.buttonClick, buttonText = widget.buttonText, buttonIcon = widget.buttonIcon; + if (buttonName === 'title') { + isOnlyButtons = false; + children.push(createElement("h2", { className: "fc-toolbar-title" }, props.title)); + } + else { + var ariaAttrs = buttonIcon ? { 'aria-label': buttonName } : {}; + var buttonClasses = ["fc-" + buttonName + "-button", theme.getClass('button')]; + if (buttonName === props.activeButton) { + buttonClasses.push(theme.getClass('buttonActive')); + } + var isDisabled = (!props.isTodayEnabled && buttonName === 'today') || + (!props.isPrevEnabled && buttonName === 'prev') || + (!props.isNextEnabled && buttonName === 'next'); + children.push(createElement("button", __assign({ disabled: isDisabled, className: buttonClasses.join(' '), onClick: buttonClick, type: "button" }, ariaAttrs), buttonText || (buttonIcon ? createElement("span", { className: buttonIcon }) : ''))); + } + } + if (children.length > 1) { + var groupClassName = (isOnlyButtons && theme.getClass('buttonGroup')) || ''; + return createElement.apply(void 0, __spreadArray(['div', { className: groupClassName }], children)); + } + return children[0]; + }; + return ToolbarSection; + }(BaseComponent)); + + var Toolbar = /** @class */ (function (_super) { + __extends(Toolbar, _super); + function Toolbar() { + return _super !== null && _super.apply(this, arguments) || this; + } + Toolbar.prototype.render = function () { + var _a = this.props, model = _a.model, extraClassName = _a.extraClassName; + var forceLtr = false; + var startContent; + var endContent; + var centerContent = model.center; + if (model.left) { + forceLtr = true; + startContent = model.left; + } + else { + startContent = model.start; + } + if (model.right) { + forceLtr = true; + endContent = model.right; + } + else { + endContent = model.end; + } + var classNames = [ + extraClassName || '', + 'fc-toolbar', + forceLtr ? 'fc-toolbar-ltr' : '', + ]; + return (createElement("div", { className: classNames.join(' ') }, + this.renderSection('start', startContent || []), + this.renderSection('center', centerContent || []), + this.renderSection('end', endContent || []))); + }; + Toolbar.prototype.renderSection = function (key, widgetGroups) { + var props = this.props; + return (createElement(ToolbarSection, { key: key, widgetGroups: widgetGroups, title: props.title, activeButton: props.activeButton, isTodayEnabled: props.isTodayEnabled, isPrevEnabled: props.isPrevEnabled, isNextEnabled: props.isNextEnabled })); + }; + return Toolbar; + }(BaseComponent)); + + // TODO: do function component? + var ViewContainer = /** @class */ (function (_super) { + __extends(ViewContainer, _super); + function ViewContainer() { + var _this = _super !== null && _super.apply(this, arguments) || this; + _this.state = { + availableWidth: null, + }; + _this.handleEl = function (el) { + _this.el = el; + setRef(_this.props.elRef, el); + _this.updateAvailableWidth(); + }; + _this.handleResize = function () { + _this.updateAvailableWidth(); + }; + return _this; + } + ViewContainer.prototype.render = function () { + var _a = this, props = _a.props, state = _a.state; + var aspectRatio = props.aspectRatio; + var classNames = [ + 'fc-view-harness', + (aspectRatio || props.liquid || props.height) + ? 'fc-view-harness-active' // harness controls the height + : 'fc-view-harness-passive', // let the view do the height + ]; + var height = ''; + var paddingBottom = ''; + if (aspectRatio) { + if (state.availableWidth !== null) { + height = state.availableWidth / aspectRatio; + } + else { + // while waiting to know availableWidth, we can't set height to *zero* + // because will cause lots of unnecessary scrollbars within scrollgrid. + // BETTER: don't start rendering ANYTHING yet until we know container width + // NOTE: why not always use paddingBottom? Causes height oscillation (issue 5606) + paddingBottom = (1 / aspectRatio) * 100 + "%"; + } + } + else { + height = props.height || ''; + } + return (createElement("div", { ref: this.handleEl, onClick: props.onClick, className: classNames.join(' '), style: { height: height, paddingBottom: paddingBottom } }, props.children)); + }; + ViewContainer.prototype.componentDidMount = function () { + this.context.addResizeHandler(this.handleResize); + }; + ViewContainer.prototype.componentWillUnmount = function () { + this.context.removeResizeHandler(this.handleResize); + }; + ViewContainer.prototype.updateAvailableWidth = function () { + if (this.el && // needed. but why? + this.props.aspectRatio // aspectRatio is the only height setting that needs availableWidth + ) { + this.setState({ availableWidth: this.el.offsetWidth }); + } + }; + return ViewContainer; + }(BaseComponent)); + + /* + Detects when the user clicks on an event within a DateComponent + */ + var EventClicking = /** @class */ (function (_super) { + __extends(EventClicking, _super); + function EventClicking(settings) { + var _this = _super.call(this, settings) || this; + _this.handleSegClick = function (ev, segEl) { + var component = _this.component; + var context = component.context; + var seg = getElSeg(segEl); + if (seg && // might be the
surrounding the more link + component.isValidSegDownEl(ev.target)) { + // our way to simulate a link click for elements that can't be tags + // grab before trigger fired in case trigger trashes DOM thru rerendering + var hasUrlContainer = elementClosest(ev.target, '.fc-event-forced-url'); + var url = hasUrlContainer ? hasUrlContainer.querySelector('a[href]').href : ''; + context.emitter.trigger('eventClick', { + el: segEl, + event: new EventApi(component.context, seg.eventRange.def, seg.eventRange.instance), + jsEvent: ev, + view: context.viewApi, + }); + if (url && !ev.defaultPrevented) { + window.location.href = url; + } + } + }; + _this.destroy = listenBySelector(settings.el, 'click', '.fc-event', // on both fg and bg events + _this.handleSegClick); + return _this; + } + return EventClicking; + }(Interaction)); + + /* + Triggers events and adds/removes core classNames when the user's pointer + enters/leaves event-elements of a component. + */ + var EventHovering = /** @class */ (function (_super) { + __extends(EventHovering, _super); + function EventHovering(settings) { + var _this = _super.call(this, settings) || this; + // for simulating an eventMouseLeave when the event el is destroyed while mouse is over it + _this.handleEventElRemove = function (el) { + if (el === _this.currentSegEl) { + _this.handleSegLeave(null, _this.currentSegEl); + } + }; + _this.handleSegEnter = function (ev, segEl) { + if (getElSeg(segEl)) { // TODO: better way to make sure not hovering over more+ link or its wrapper + _this.currentSegEl = segEl; + _this.triggerEvent('eventMouseEnter', ev, segEl); + } + }; + _this.handleSegLeave = function (ev, segEl) { + if (_this.currentSegEl) { + _this.currentSegEl = null; + _this.triggerEvent('eventMouseLeave', ev, segEl); + } + }; + _this.removeHoverListeners = listenToHoverBySelector(settings.el, '.fc-event', // on both fg and bg events + _this.handleSegEnter, _this.handleSegLeave); + return _this; + } + EventHovering.prototype.destroy = function () { + this.removeHoverListeners(); + }; + EventHovering.prototype.triggerEvent = function (publicEvName, ev, segEl) { + var component = this.component; + var context = component.context; + var seg = getElSeg(segEl); + if (!ev || component.isValidSegDownEl(ev.target)) { + context.emitter.trigger(publicEvName, { + el: segEl, + event: new EventApi(context, seg.eventRange.def, seg.eventRange.instance), + jsEvent: ev, + view: context.viewApi, + }); + } + }; + return EventHovering; + }(Interaction)); + + var CalendarContent = /** @class */ (function (_super) { + __extends(CalendarContent, _super); + function CalendarContent() { + var _this = _super !== null && _super.apply(this, arguments) || this; + _this.buildViewContext = memoize(buildViewContext); + _this.buildViewPropTransformers = memoize(buildViewPropTransformers); + _this.buildToolbarProps = memoize(buildToolbarProps); + _this.handleNavLinkClick = buildDelegationHandler('a[data-navlink]', _this._handleNavLinkClick.bind(_this)); + _this.headerRef = createRef(); + _this.footerRef = createRef(); + _this.interactionsStore = {}; + // Component Registration + // ----------------------------------------------------------------------------------------------------------------- + _this.registerInteractiveComponent = function (component, settingsInput) { + var settings = parseInteractionSettings(component, settingsInput); + var DEFAULT_INTERACTIONS = [ + EventClicking, + EventHovering, + ]; + var interactionClasses = DEFAULT_INTERACTIONS.concat(_this.props.pluginHooks.componentInteractions); + var interactions = interactionClasses.map(function (TheInteractionClass) { return new TheInteractionClass(settings); }); + _this.interactionsStore[component.uid] = interactions; + interactionSettingsStore[component.uid] = settings; + }; + _this.unregisterInteractiveComponent = function (component) { + for (var _i = 0, _a = _this.interactionsStore[component.uid]; _i < _a.length; _i++) { + var listener = _a[_i]; + listener.destroy(); + } + delete _this.interactionsStore[component.uid]; + delete interactionSettingsStore[component.uid]; + }; + // Resizing + // ----------------------------------------------------------------------------------------------------------------- + _this.resizeRunner = new DelayedRunner(function () { + _this.props.emitter.trigger('_resize', true); // should window resizes be considered "forced" ? + _this.props.emitter.trigger('windowResize', { view: _this.props.viewApi }); + }); + _this.handleWindowResize = function (ev) { + var options = _this.props.options; + if (options.handleWindowResize && + ev.target === window // avoid jqui events + ) { + _this.resizeRunner.request(options.windowResizeDelay); + } + }; + return _this; + } + /* + renders INSIDE of an outer div + */ + CalendarContent.prototype.render = function () { + var props = this.props; + var toolbarConfig = props.toolbarConfig, options = props.options; + var toolbarProps = this.buildToolbarProps(props.viewSpec, props.dateProfile, props.dateProfileGenerator, props.currentDate, getNow(props.options.now, props.dateEnv), // TODO: use NowTimer???? + props.viewTitle); + var viewVGrow = false; + var viewHeight = ''; + var viewAspectRatio; + if (props.isHeightAuto || props.forPrint) { + viewHeight = ''; + } + else if (options.height != null) { + viewVGrow = true; + } + else if (options.contentHeight != null) { + viewHeight = options.contentHeight; + } + else { + viewAspectRatio = Math.max(options.aspectRatio, 0.5); // prevent from getting too tall + } + var viewContext = this.buildViewContext(props.viewSpec, props.viewApi, props.options, props.dateProfileGenerator, props.dateEnv, props.theme, props.pluginHooks, props.dispatch, props.getCurrentData, props.emitter, props.calendarApi, this.registerInteractiveComponent, this.unregisterInteractiveComponent); + return (createElement(ViewContextType.Provider, { value: viewContext }, + toolbarConfig.headerToolbar && (createElement(Toolbar, __assign({ ref: this.headerRef, extraClassName: "fc-header-toolbar", model: toolbarConfig.headerToolbar }, toolbarProps))), + createElement(ViewContainer, { liquid: viewVGrow, height: viewHeight, aspectRatio: viewAspectRatio, onClick: this.handleNavLinkClick }, + this.renderView(props), + this.buildAppendContent()), + toolbarConfig.footerToolbar && (createElement(Toolbar, __assign({ ref: this.footerRef, extraClassName: "fc-footer-toolbar", model: toolbarConfig.footerToolbar }, toolbarProps))))); + }; + CalendarContent.prototype.componentDidMount = function () { + var props = this.props; + this.calendarInteractions = props.pluginHooks.calendarInteractions + .map(function (CalendarInteractionClass) { return new CalendarInteractionClass(props); }); + window.addEventListener('resize', this.handleWindowResize); + var propSetHandlers = props.pluginHooks.propSetHandlers; + for (var propName in propSetHandlers) { + propSetHandlers[propName](props[propName], props); + } + }; + CalendarContent.prototype.componentDidUpdate = function (prevProps) { + var props = this.props; + var propSetHandlers = props.pluginHooks.propSetHandlers; + for (var propName in propSetHandlers) { + if (props[propName] !== prevProps[propName]) { + propSetHandlers[propName](props[propName], props); + } + } + }; + CalendarContent.prototype.componentWillUnmount = function () { + window.removeEventListener('resize', this.handleWindowResize); + this.resizeRunner.clear(); + for (var _i = 0, _a = this.calendarInteractions; _i < _a.length; _i++) { + var interaction = _a[_i]; + interaction.destroy(); + } + this.props.emitter.trigger('_unmount'); + }; + CalendarContent.prototype._handleNavLinkClick = function (ev, anchorEl) { + var _a = this.props, dateEnv = _a.dateEnv, options = _a.options, calendarApi = _a.calendarApi; + var navLinkOptions = anchorEl.getAttribute('data-navlink'); + navLinkOptions = navLinkOptions ? JSON.parse(navLinkOptions) : {}; + var dateMarker = dateEnv.createMarker(navLinkOptions.date); + var viewType = navLinkOptions.type; + var customAction = viewType === 'day' ? options.navLinkDayClick : + viewType === 'week' ? options.navLinkWeekClick : null; + if (typeof customAction === 'function') { + customAction.call(calendarApi, dateEnv.toDate(dateMarker), ev); + } + else { + if (typeof customAction === 'string') { + viewType = customAction; + } + calendarApi.zoomTo(dateMarker, viewType); + } + }; + CalendarContent.prototype.buildAppendContent = function () { + var props = this.props; + var children = props.pluginHooks.viewContainerAppends.map(function (buildAppendContent) { return buildAppendContent(props); }); + return createElement.apply(void 0, __spreadArray([Fragment, {}], children)); + }; + CalendarContent.prototype.renderView = function (props) { + var pluginHooks = props.pluginHooks; + var viewSpec = props.viewSpec; + var viewProps = { + dateProfile: props.dateProfile, + businessHours: props.businessHours, + eventStore: props.renderableEventStore, + eventUiBases: props.eventUiBases, + dateSelection: props.dateSelection, + eventSelection: props.eventSelection, + eventDrag: props.eventDrag, + eventResize: props.eventResize, + isHeightAuto: props.isHeightAuto, + forPrint: props.forPrint, + }; + var transformers = this.buildViewPropTransformers(pluginHooks.viewPropsTransformers); + for (var _i = 0, transformers_1 = transformers; _i < transformers_1.length; _i++) { + var transformer = transformers_1[_i]; + __assign(viewProps, transformer.transform(viewProps, props)); + } + var ViewComponent = viewSpec.component; + return (createElement(ViewComponent, __assign({}, viewProps))); + }; + return CalendarContent; + }(PureComponent)); + function buildToolbarProps(viewSpec, dateProfile, dateProfileGenerator, currentDate, now, title) { + // don't force any date-profiles to valid date profiles (the `false`) so that we can tell if it's invalid + var todayInfo = dateProfileGenerator.build(now, undefined, false); // TODO: need `undefined` or else INFINITE LOOP for some reason + var prevInfo = dateProfileGenerator.buildPrev(dateProfile, currentDate, false); + var nextInfo = dateProfileGenerator.buildNext(dateProfile, currentDate, false); + return { + title: title, + activeButton: viewSpec.type, + isTodayEnabled: todayInfo.isValid && !rangeContainsMarker(dateProfile.currentRange, now), + isPrevEnabled: prevInfo.isValid, + isNextEnabled: nextInfo.isValid, + }; + } + // Plugin + // ----------------------------------------------------------------------------------------------------------------- + function buildViewPropTransformers(theClasses) { + return theClasses.map(function (TheClass) { return new TheClass(); }); + } + + var CalendarRoot = /** @class */ (function (_super) { + __extends(CalendarRoot, _super); + function CalendarRoot() { + var _this = _super !== null && _super.apply(this, arguments) || this; + _this.state = { + forPrint: false, + }; + _this.handleBeforePrint = function () { + _this.setState({ forPrint: true }); + }; + _this.handleAfterPrint = function () { + _this.setState({ forPrint: false }); + }; + return _this; + } + CalendarRoot.prototype.render = function () { + var props = this.props; + var options = props.options; + var forPrint = this.state.forPrint; + var isHeightAuto = forPrint || options.height === 'auto' || options.contentHeight === 'auto'; + var height = (!isHeightAuto && options.height != null) ? options.height : ''; + var classNames = [ + 'fc', + forPrint ? 'fc-media-print' : 'fc-media-screen', + "fc-direction-" + options.direction, + props.theme.getClass('root'), + ]; + if (!getCanVGrowWithinCell()) { + classNames.push('fc-liquid-hack'); + } + return props.children(classNames, height, isHeightAuto, forPrint); + }; + CalendarRoot.prototype.componentDidMount = function () { + var emitter = this.props.emitter; + emitter.on('_beforeprint', this.handleBeforePrint); + emitter.on('_afterprint', this.handleAfterPrint); + }; + CalendarRoot.prototype.componentWillUnmount = function () { + var emitter = this.props.emitter; + emitter.off('_beforeprint', this.handleBeforePrint); + emitter.off('_afterprint', this.handleAfterPrint); + }; + return CalendarRoot; + }(BaseComponent)); + + // Computes a default column header formatting string if `colFormat` is not explicitly defined + function computeFallbackHeaderFormat(datesRepDistinctDays, dayCnt) { + // if more than one week row, or if there are a lot of columns with not much space, + // put just the day numbers will be in each cell + if (!datesRepDistinctDays || dayCnt > 10) { + return createFormatter({ weekday: 'short' }); // "Sat" + } + if (dayCnt > 1) { + return createFormatter({ weekday: 'short', month: 'numeric', day: 'numeric', omitCommas: true }); // "Sat 11/12" + } + return createFormatter({ weekday: 'long' }); // "Saturday" + } + + var CLASS_NAME = 'fc-col-header-cell'; // do the cushion too? no + function renderInner$1(hookProps) { + return hookProps.text; + } + + var TableDateCell = /** @class */ (function (_super) { + __extends(TableDateCell, _super); + function TableDateCell() { + return _super !== null && _super.apply(this, arguments) || this; + } + TableDateCell.prototype.render = function () { + var _a = this.context, dateEnv = _a.dateEnv, options = _a.options, theme = _a.theme, viewApi = _a.viewApi; + var props = this.props; + var date = props.date, dateProfile = props.dateProfile; + var dayMeta = getDateMeta(date, props.todayRange, null, dateProfile); + var classNames = [CLASS_NAME].concat(getDayClassNames(dayMeta, theme)); + var text = dateEnv.format(date, props.dayHeaderFormat); + // if colCnt is 1, we are already in a day-view and don't need a navlink + var navLinkAttrs = (options.navLinks && !dayMeta.isDisabled && props.colCnt > 1) + ? { 'data-navlink': buildNavLinkData(date), tabIndex: 0 } + : {}; + var hookProps = __assign(__assign(__assign({ date: dateEnv.toDate(date), view: viewApi }, props.extraHookProps), { text: text }), dayMeta); + return (createElement(RenderHook, { hookProps: hookProps, classNames: options.dayHeaderClassNames, content: options.dayHeaderContent, defaultContent: renderInner$1, didMount: options.dayHeaderDidMount, willUnmount: options.dayHeaderWillUnmount }, function (rootElRef, customClassNames, innerElRef, innerContent) { return (createElement("th", __assign({ ref: rootElRef, className: classNames.concat(customClassNames).join(' '), "data-date": !dayMeta.isDisabled ? formatDayString(date) : undefined, colSpan: props.colSpan }, props.extraDataAttrs), + createElement("div", { className: "fc-scrollgrid-sync-inner" }, !dayMeta.isDisabled && (createElement("a", __assign({ ref: innerElRef, className: [ + 'fc-col-header-cell-cushion', + props.isSticky ? 'fc-sticky' : '', + ].join(' ') }, navLinkAttrs), innerContent))))); })); + }; + return TableDateCell; + }(BaseComponent)); + + var TableDowCell = /** @class */ (function (_super) { + __extends(TableDowCell, _super); + function TableDowCell() { + return _super !== null && _super.apply(this, arguments) || this; + } + TableDowCell.prototype.render = function () { + var props = this.props; + var _a = this.context, dateEnv = _a.dateEnv, theme = _a.theme, viewApi = _a.viewApi, options = _a.options; + var date = addDays(new Date(259200000), props.dow); // start with Sun, 04 Jan 1970 00:00:00 GMT + var dateMeta = { + dow: props.dow, + isDisabled: false, + isFuture: false, + isPast: false, + isToday: false, + isOther: false, + }; + var classNames = [CLASS_NAME].concat(getDayClassNames(dateMeta, theme), props.extraClassNames || []); + var text = dateEnv.format(date, props.dayHeaderFormat); + var hookProps = __assign(__assign(__assign(__assign({ // TODO: make this public? + date: date }, dateMeta), { view: viewApi }), props.extraHookProps), { text: text }); + return (createElement(RenderHook, { hookProps: hookProps, classNames: options.dayHeaderClassNames, content: options.dayHeaderContent, defaultContent: renderInner$1, didMount: options.dayHeaderDidMount, willUnmount: options.dayHeaderWillUnmount }, function (rootElRef, customClassNames, innerElRef, innerContent) { return (createElement("th", __assign({ ref: rootElRef, className: classNames.concat(customClassNames).join(' '), colSpan: props.colSpan }, props.extraDataAttrs), + createElement("div", { className: "fc-scrollgrid-sync-inner" }, + createElement("a", { className: [ + 'fc-col-header-cell-cushion', + props.isSticky ? 'fc-sticky' : '', + ].join(' '), ref: innerElRef }, innerContent)))); })); + }; + return TableDowCell; + }(BaseComponent)); + + var NowTimer = /** @class */ (function (_super) { + __extends(NowTimer, _super); + function NowTimer(props, context) { + var _this = _super.call(this, props, context) || this; + _this.initialNowDate = getNow(context.options.now, context.dateEnv); + _this.initialNowQueriedMs = new Date().valueOf(); + _this.state = _this.computeTiming().currentState; + return _this; + } + NowTimer.prototype.render = function () { + var _a = this, props = _a.props, state = _a.state; + return props.children(state.nowDate, state.todayRange); + }; + NowTimer.prototype.componentDidMount = function () { + this.setTimeout(); + }; + NowTimer.prototype.componentDidUpdate = function (prevProps) { + if (prevProps.unit !== this.props.unit) { + this.clearTimeout(); + this.setTimeout(); + } + }; + NowTimer.prototype.componentWillUnmount = function () { + this.clearTimeout(); + }; + NowTimer.prototype.computeTiming = function () { + var _a = this, props = _a.props, context = _a.context; + var unroundedNow = addMs(this.initialNowDate, new Date().valueOf() - this.initialNowQueriedMs); + var currentUnitStart = context.dateEnv.startOf(unroundedNow, props.unit); + var nextUnitStart = context.dateEnv.add(currentUnitStart, createDuration(1, props.unit)); + var waitMs = nextUnitStart.valueOf() - unroundedNow.valueOf(); + // there is a max setTimeout ms value (https://stackoverflow.com/a/3468650/96342) + // ensure no longer than a day + waitMs = Math.min(1000 * 60 * 60 * 24, waitMs); + return { + currentState: { nowDate: currentUnitStart, todayRange: buildDayRange(currentUnitStart) }, + nextState: { nowDate: nextUnitStart, todayRange: buildDayRange(nextUnitStart) }, + waitMs: waitMs, + }; + }; + NowTimer.prototype.setTimeout = function () { + var _this = this; + var _a = this.computeTiming(), nextState = _a.nextState, waitMs = _a.waitMs; + this.timeoutId = setTimeout(function () { + _this.setState(nextState, function () { + _this.setTimeout(); + }); + }, waitMs); + }; + NowTimer.prototype.clearTimeout = function () { + if (this.timeoutId) { + clearTimeout(this.timeoutId); + } + }; + NowTimer.contextType = ViewContextType; + return NowTimer; + }(Component)); + function buildDayRange(date) { + var start = startOfDay(date); + var end = addDays(start, 1); + return { start: start, end: end }; + } + + var DayHeader = /** @class */ (function (_super) { + __extends(DayHeader, _super); + function DayHeader() { + var _this = _super !== null && _super.apply(this, arguments) || this; + _this.createDayHeaderFormatter = memoize(createDayHeaderFormatter); + return _this; + } + DayHeader.prototype.render = function () { + var context = this.context; + var _a = this.props, dates = _a.dates, dateProfile = _a.dateProfile, datesRepDistinctDays = _a.datesRepDistinctDays, renderIntro = _a.renderIntro; + var dayHeaderFormat = this.createDayHeaderFormatter(context.options.dayHeaderFormat, datesRepDistinctDays, dates.length); + return (createElement(NowTimer, { unit: "day" }, function (nowDate, todayRange) { return (createElement("tr", null, + renderIntro && renderIntro('day'), + dates.map(function (date) { return (datesRepDistinctDays ? (createElement(TableDateCell, { key: date.toISOString(), date: date, dateProfile: dateProfile, todayRange: todayRange, colCnt: dates.length, dayHeaderFormat: dayHeaderFormat })) : (createElement(TableDowCell, { key: date.getUTCDay(), dow: date.getUTCDay(), dayHeaderFormat: dayHeaderFormat }))); }))); })); + }; + return DayHeader; + }(BaseComponent)); + function createDayHeaderFormatter(explicitFormat, datesRepDistinctDays, dateCnt) { + return explicitFormat || computeFallbackHeaderFormat(datesRepDistinctDays, dateCnt); + } + + var DaySeriesModel = /** @class */ (function () { + function DaySeriesModel(range, dateProfileGenerator) { + var date = range.start; + var end = range.end; + var indices = []; + var dates = []; + var dayIndex = -1; + while (date < end) { // loop each day from start to end + if (dateProfileGenerator.isHiddenDay(date)) { + indices.push(dayIndex + 0.5); // mark that it's between indices + } + else { + dayIndex += 1; + indices.push(dayIndex); + dates.push(date); + } + date = addDays(date, 1); + } + this.dates = dates; + this.indices = indices; + this.cnt = dates.length; + } + DaySeriesModel.prototype.sliceRange = function (range) { + var firstIndex = this.getDateDayIndex(range.start); // inclusive first index + var lastIndex = this.getDateDayIndex(addDays(range.end, -1)); // inclusive last index + var clippedFirstIndex = Math.max(0, firstIndex); + var clippedLastIndex = Math.min(this.cnt - 1, lastIndex); + // deal with in-between indices + clippedFirstIndex = Math.ceil(clippedFirstIndex); // in-between starts round to next cell + clippedLastIndex = Math.floor(clippedLastIndex); // in-between ends round to prev cell + if (clippedFirstIndex <= clippedLastIndex) { + return { + firstIndex: clippedFirstIndex, + lastIndex: clippedLastIndex, + isStart: firstIndex === clippedFirstIndex, + isEnd: lastIndex === clippedLastIndex, + }; + } + return null; + }; + // Given a date, returns its chronolocial cell-index from the first cell of the grid. + // If the date lies between cells (because of hiddenDays), returns a floating-point value between offsets. + // If before the first offset, returns a negative number. + // If after the last offset, returns an offset past the last cell offset. + // Only works for *start* dates of cells. Will not work for exclusive end dates for cells. + DaySeriesModel.prototype.getDateDayIndex = function (date) { + var indices = this.indices; + var dayOffset = Math.floor(diffDays(this.dates[0], date)); + if (dayOffset < 0) { + return indices[0] - 1; + } + if (dayOffset >= indices.length) { + return indices[indices.length - 1] + 1; + } + return indices[dayOffset]; + }; + return DaySeriesModel; + }()); + + var DayTableModel = /** @class */ (function () { + function DayTableModel(daySeries, breakOnWeeks) { + var dates = daySeries.dates; + var daysPerRow; + var firstDay; + var rowCnt; + if (breakOnWeeks) { + // count columns until the day-of-week repeats + firstDay = dates[0].getUTCDay(); + for (daysPerRow = 1; daysPerRow < dates.length; daysPerRow += 1) { + if (dates[daysPerRow].getUTCDay() === firstDay) { + break; + } + } + rowCnt = Math.ceil(dates.length / daysPerRow); + } + else { + rowCnt = 1; + daysPerRow = dates.length; + } + this.rowCnt = rowCnt; + this.colCnt = daysPerRow; + this.daySeries = daySeries; + this.cells = this.buildCells(); + this.headerDates = this.buildHeaderDates(); + } + DayTableModel.prototype.buildCells = function () { + var rows = []; + for (var row = 0; row < this.rowCnt; row += 1) { + var cells = []; + for (var col = 0; col < this.colCnt; col += 1) { + cells.push(this.buildCell(row, col)); + } + rows.push(cells); + } + return rows; + }; + DayTableModel.prototype.buildCell = function (row, col) { + var date = this.daySeries.dates[row * this.colCnt + col]; + return { + key: date.toISOString(), + date: date, + }; + }; + DayTableModel.prototype.buildHeaderDates = function () { + var dates = []; + for (var col = 0; col < this.colCnt; col += 1) { + dates.push(this.cells[0][col].date); + } + return dates; + }; + DayTableModel.prototype.sliceRange = function (range) { + var colCnt = this.colCnt; + var seriesSeg = this.daySeries.sliceRange(range); + var segs = []; + if (seriesSeg) { + var firstIndex = seriesSeg.firstIndex, lastIndex = seriesSeg.lastIndex; + var index = firstIndex; + while (index <= lastIndex) { + var row = Math.floor(index / colCnt); + var nextIndex = Math.min((row + 1) * colCnt, lastIndex + 1); + segs.push({ + row: row, + firstCol: index % colCnt, + lastCol: (nextIndex - 1) % colCnt, + isStart: seriesSeg.isStart && index === firstIndex, + isEnd: seriesSeg.isEnd && (nextIndex - 1) === lastIndex, + }); + index = nextIndex; + } + } + return segs; + }; + return DayTableModel; + }()); + + var Slicer = /** @class */ (function () { + function Slicer() { + this.sliceBusinessHours = memoize(this._sliceBusinessHours); + this.sliceDateSelection = memoize(this._sliceDateSpan); + this.sliceEventStore = memoize(this._sliceEventStore); + this.sliceEventDrag = memoize(this._sliceInteraction); + this.sliceEventResize = memoize(this._sliceInteraction); + this.forceDayIfListItem = false; // hack + } + Slicer.prototype.sliceProps = function (props, dateProfile, nextDayThreshold, context) { + var extraArgs = []; + for (var _i = 4; _i < arguments.length; _i++) { + extraArgs[_i - 4] = arguments[_i]; + } + var eventUiBases = props.eventUiBases; + var eventSegs = this.sliceEventStore.apply(this, __spreadArray([props.eventStore, eventUiBases, dateProfile, nextDayThreshold], extraArgs)); + return { + dateSelectionSegs: this.sliceDateSelection.apply(this, __spreadArray([props.dateSelection, eventUiBases, context], extraArgs)), + businessHourSegs: this.sliceBusinessHours.apply(this, __spreadArray([props.businessHours, dateProfile, nextDayThreshold, context], extraArgs)), + fgEventSegs: eventSegs.fg, + bgEventSegs: eventSegs.bg, + eventDrag: this.sliceEventDrag.apply(this, __spreadArray([props.eventDrag, eventUiBases, dateProfile, nextDayThreshold], extraArgs)), + eventResize: this.sliceEventResize.apply(this, __spreadArray([props.eventResize, eventUiBases, dateProfile, nextDayThreshold], extraArgs)), + eventSelection: props.eventSelection, + }; // TODO: give interactionSegs? + }; + Slicer.prototype.sliceNowDate = function (// does not memoize + date, context) { + var extraArgs = []; + for (var _i = 2; _i < arguments.length; _i++) { + extraArgs[_i - 2] = arguments[_i]; + } + return this._sliceDateSpan.apply(this, __spreadArray([{ range: { start: date, end: addMs(date, 1) }, allDay: false }, + {}, + context], extraArgs)); + }; + Slicer.prototype._sliceBusinessHours = function (businessHours, dateProfile, nextDayThreshold, context) { + var extraArgs = []; + for (var _i = 4; _i < arguments.length; _i++) { + extraArgs[_i - 4] = arguments[_i]; + } + if (!businessHours) { + return []; + } + return this._sliceEventStore.apply(this, __spreadArray([expandRecurring(businessHours, computeActiveRange(dateProfile, Boolean(nextDayThreshold)), context), + {}, + dateProfile, + nextDayThreshold], extraArgs)).bg; + }; + Slicer.prototype._sliceEventStore = function (eventStore, eventUiBases, dateProfile, nextDayThreshold) { + var extraArgs = []; + for (var _i = 4; _i < arguments.length; _i++) { + extraArgs[_i - 4] = arguments[_i]; + } + if (eventStore) { + var rangeRes = sliceEventStore(eventStore, eventUiBases, computeActiveRange(dateProfile, Boolean(nextDayThreshold)), nextDayThreshold); + return { + bg: this.sliceEventRanges(rangeRes.bg, extraArgs), + fg: this.sliceEventRanges(rangeRes.fg, extraArgs), + }; + } + return { bg: [], fg: [] }; + }; + Slicer.prototype._sliceInteraction = function (interaction, eventUiBases, dateProfile, nextDayThreshold) { + var extraArgs = []; + for (var _i = 4; _i < arguments.length; _i++) { + extraArgs[_i - 4] = arguments[_i]; + } + if (!interaction) { + return null; + } + var rangeRes = sliceEventStore(interaction.mutatedEvents, eventUiBases, computeActiveRange(dateProfile, Boolean(nextDayThreshold)), nextDayThreshold); + return { + segs: this.sliceEventRanges(rangeRes.fg, extraArgs), + affectedInstances: interaction.affectedEvents.instances, + isEvent: interaction.isEvent, + }; + }; + Slicer.prototype._sliceDateSpan = function (dateSpan, eventUiBases, context) { + var extraArgs = []; + for (var _i = 3; _i < arguments.length; _i++) { + extraArgs[_i - 3] = arguments[_i]; + } + if (!dateSpan) { + return []; + } + var eventRange = fabricateEventRange(dateSpan, eventUiBases, context); + var segs = this.sliceRange.apply(this, __spreadArray([dateSpan.range], extraArgs)); + for (var _a = 0, segs_1 = segs; _a < segs_1.length; _a++) { + var seg = segs_1[_a]; + seg.eventRange = eventRange; + } + return segs; + }; + /* + "complete" seg means it has component and eventRange + */ + Slicer.prototype.sliceEventRanges = function (eventRanges, extraArgs) { + var segs = []; + for (var _i = 0, eventRanges_1 = eventRanges; _i < eventRanges_1.length; _i++) { + var eventRange = eventRanges_1[_i]; + segs.push.apply(segs, this.sliceEventRange(eventRange, extraArgs)); + } + return segs; + }; + /* + "complete" seg means it has component and eventRange + */ + Slicer.prototype.sliceEventRange = function (eventRange, extraArgs) { + var dateRange = eventRange.range; + // hack to make multi-day events that are being force-displayed as list-items to take up only one day + if (this.forceDayIfListItem && eventRange.ui.display === 'list-item') { + dateRange = { + start: dateRange.start, + end: addDays(dateRange.start, 1), + }; + } + var segs = this.sliceRange.apply(this, __spreadArray([dateRange], extraArgs)); + for (var _i = 0, segs_2 = segs; _i < segs_2.length; _i++) { + var seg = segs_2[_i]; + seg.eventRange = eventRange; + seg.isStart = eventRange.isStart && seg.isStart; + seg.isEnd = eventRange.isEnd && seg.isEnd; + } + return segs; + }; + return Slicer; + }()); + /* + for incorporating slotMinTime/slotMaxTime if appropriate + TODO: should be part of DateProfile! + TimelineDateProfile already does this btw + */ + function computeActiveRange(dateProfile, isComponentAllDay) { + var range = dateProfile.activeRange; + if (isComponentAllDay) { + return range; + } + return { + start: addMs(range.start, dateProfile.slotMinTime.milliseconds), + end: addMs(range.end, dateProfile.slotMaxTime.milliseconds - 864e5), // 864e5 = ms in a day + }; + } + + // high-level segmenting-aware tester functions + // ------------------------------------------------------------------------------------------------------------------------ + function isInteractionValid(interaction, dateProfile, context) { + var instances = interaction.mutatedEvents.instances; + for (var instanceId in instances) { + if (!rangeContainsRange(dateProfile.validRange, instances[instanceId].range)) { + return false; + } + } + return isNewPropsValid({ eventDrag: interaction }, context); // HACK: the eventDrag props is used for ALL interactions + } + function isDateSelectionValid(dateSelection, dateProfile, context) { + if (!rangeContainsRange(dateProfile.validRange, dateSelection.range)) { + return false; + } + return isNewPropsValid({ dateSelection: dateSelection }, context); + } + function isNewPropsValid(newProps, context) { + var calendarState = context.getCurrentData(); + var props = __assign({ businessHours: calendarState.businessHours, dateSelection: '', eventStore: calendarState.eventStore, eventUiBases: calendarState.eventUiBases, eventSelection: '', eventDrag: null, eventResize: null }, newProps); + return (context.pluginHooks.isPropsValid || isPropsValid)(props, context); + } + function isPropsValid(state, context, dateSpanMeta, filterConfig) { + if (dateSpanMeta === void 0) { dateSpanMeta = {}; } + if (state.eventDrag && !isInteractionPropsValid(state, context, dateSpanMeta, filterConfig)) { + return false; + } + if (state.dateSelection && !isDateSelectionPropsValid(state, context, dateSpanMeta, filterConfig)) { + return false; + } + return true; + } + // Moving Event Validation + // ------------------------------------------------------------------------------------------------------------------------ + function isInteractionPropsValid(state, context, dateSpanMeta, filterConfig) { + var currentState = context.getCurrentData(); + var interaction = state.eventDrag; // HACK: the eventDrag props is used for ALL interactions + var subjectEventStore = interaction.mutatedEvents; + var subjectDefs = subjectEventStore.defs; + var subjectInstances = subjectEventStore.instances; + var subjectConfigs = compileEventUis(subjectDefs, interaction.isEvent ? + state.eventUiBases : + { '': currentState.selectionConfig }); + if (filterConfig) { + subjectConfigs = mapHash(subjectConfigs, filterConfig); + } + // exclude the subject events. TODO: exclude defs too? + var otherEventStore = excludeInstances(state.eventStore, interaction.affectedEvents.instances); + var otherDefs = otherEventStore.defs; + var otherInstances = otherEventStore.instances; + var otherConfigs = compileEventUis(otherDefs, state.eventUiBases); + for (var subjectInstanceId in subjectInstances) { + var subjectInstance = subjectInstances[subjectInstanceId]; + var subjectRange = subjectInstance.range; + var subjectConfig = subjectConfigs[subjectInstance.defId]; + var subjectDef = subjectDefs[subjectInstance.defId]; + // constraint + if (!allConstraintsPass(subjectConfig.constraints, subjectRange, otherEventStore, state.businessHours, context)) { + return false; + } + // overlap + var eventOverlap = context.options.eventOverlap; + var eventOverlapFunc = typeof eventOverlap === 'function' ? eventOverlap : null; + for (var otherInstanceId in otherInstances) { + var otherInstance = otherInstances[otherInstanceId]; + // intersect! evaluate + if (rangesIntersect(subjectRange, otherInstance.range)) { + var otherOverlap = otherConfigs[otherInstance.defId].overlap; + // consider the other event's overlap. only do this if the subject event is a "real" event + if (otherOverlap === false && interaction.isEvent) { + return false; + } + if (subjectConfig.overlap === false) { + return false; + } + if (eventOverlapFunc && !eventOverlapFunc(new EventApi(context, otherDefs[otherInstance.defId], otherInstance), // still event + new EventApi(context, subjectDef, subjectInstance))) { + return false; + } + } + } + // allow (a function) + var calendarEventStore = currentState.eventStore; // need global-to-calendar, not local to component (splittable)state + for (var _i = 0, _a = subjectConfig.allows; _i < _a.length; _i++) { + var subjectAllow = _a[_i]; + var subjectDateSpan = __assign(__assign({}, dateSpanMeta), { range: subjectInstance.range, allDay: subjectDef.allDay }); + var origDef = calendarEventStore.defs[subjectDef.defId]; + var origInstance = calendarEventStore.instances[subjectInstanceId]; + var eventApi = void 0; + if (origDef) { // was previously in the calendar + eventApi = new EventApi(context, origDef, origInstance); + } + else { // was an external event + eventApi = new EventApi(context, subjectDef); // no instance, because had no dates + } + if (!subjectAllow(buildDateSpanApiWithContext(subjectDateSpan, context), eventApi)) { + return false; + } + } + } + return true; + } + // Date Selection Validation + // ------------------------------------------------------------------------------------------------------------------------ + function isDateSelectionPropsValid(state, context, dateSpanMeta, filterConfig) { + var relevantEventStore = state.eventStore; + var relevantDefs = relevantEventStore.defs; + var relevantInstances = relevantEventStore.instances; + var selection = state.dateSelection; + var selectionRange = selection.range; + var selectionConfig = context.getCurrentData().selectionConfig; + if (filterConfig) { + selectionConfig = filterConfig(selectionConfig); + } + // constraint + if (!allConstraintsPass(selectionConfig.constraints, selectionRange, relevantEventStore, state.businessHours, context)) { + return false; + } + // overlap + var selectOverlap = context.options.selectOverlap; + var selectOverlapFunc = typeof selectOverlap === 'function' ? selectOverlap : null; + for (var relevantInstanceId in relevantInstances) { + var relevantInstance = relevantInstances[relevantInstanceId]; + // intersect! evaluate + if (rangesIntersect(selectionRange, relevantInstance.range)) { + if (selectionConfig.overlap === false) { + return false; + } + if (selectOverlapFunc && !selectOverlapFunc(new EventApi(context, relevantDefs[relevantInstance.defId], relevantInstance), null)) { + return false; + } + } + } + // allow (a function) + for (var _i = 0, _a = selectionConfig.allows; _i < _a.length; _i++) { + var selectionAllow = _a[_i]; + var fullDateSpan = __assign(__assign({}, dateSpanMeta), selection); + if (!selectionAllow(buildDateSpanApiWithContext(fullDateSpan, context), null)) { + return false; + } + } + return true; + } + // Constraint Utils + // ------------------------------------------------------------------------------------------------------------------------ + function allConstraintsPass(constraints, subjectRange, otherEventStore, businessHoursUnexpanded, context) { + for (var _i = 0, constraints_1 = constraints; _i < constraints_1.length; _i++) { + var constraint = constraints_1[_i]; + if (!anyRangesContainRange(constraintToRanges(constraint, subjectRange, otherEventStore, businessHoursUnexpanded, context), subjectRange)) { + return false; + } + } + return true; + } + function constraintToRanges(constraint, subjectRange, // for expanding a recurring constraint, or expanding business hours + otherEventStore, // for if constraint is an even group ID + businessHoursUnexpanded, // for if constraint is 'businessHours' + context) { + if (constraint === 'businessHours') { + return eventStoreToRanges(expandRecurring(businessHoursUnexpanded, subjectRange, context)); + } + if (typeof constraint === 'string') { // an group ID + return eventStoreToRanges(filterEventStoreDefs(otherEventStore, function (eventDef) { return eventDef.groupId === constraint; })); + } + if (typeof constraint === 'object' && constraint) { // non-null object + return eventStoreToRanges(expandRecurring(constraint, subjectRange, context)); + } + return []; // if it's false + } + // TODO: move to event-store file? + function eventStoreToRanges(eventStore) { + var instances = eventStore.instances; + var ranges = []; + for (var instanceId in instances) { + ranges.push(instances[instanceId].range); + } + return ranges; + } + // TODO: move to geom file? + function anyRangesContainRange(outerRanges, innerRange) { + for (var _i = 0, outerRanges_1 = outerRanges; _i < outerRanges_1.length; _i++) { + var outerRange = outerRanges_1[_i]; + if (rangeContainsRange(outerRange, innerRange)) { + return true; + } + } + return false; + } + + var VISIBLE_HIDDEN_RE = /^(visible|hidden)$/; + var Scroller = /** @class */ (function (_super) { + __extends(Scroller, _super); + function Scroller() { + var _this = _super !== null && _super.apply(this, arguments) || this; + _this.handleEl = function (el) { + _this.el = el; + setRef(_this.props.elRef, el); + }; + return _this; + } + Scroller.prototype.render = function () { + var props = this.props; + var liquid = props.liquid, liquidIsAbsolute = props.liquidIsAbsolute; + var isAbsolute = liquid && liquidIsAbsolute; + var className = ['fc-scroller']; + if (liquid) { + if (liquidIsAbsolute) { + className.push('fc-scroller-liquid-absolute'); + } + else { + className.push('fc-scroller-liquid'); + } + } + return (createElement("div", { ref: this.handleEl, className: className.join(' '), style: { + overflowX: props.overflowX, + overflowY: props.overflowY, + left: (isAbsolute && -(props.overcomeLeft || 0)) || '', + right: (isAbsolute && -(props.overcomeRight || 0)) || '', + bottom: (isAbsolute && -(props.overcomeBottom || 0)) || '', + marginLeft: (!isAbsolute && -(props.overcomeLeft || 0)) || '', + marginRight: (!isAbsolute && -(props.overcomeRight || 0)) || '', + marginBottom: (!isAbsolute && -(props.overcomeBottom || 0)) || '', + maxHeight: props.maxHeight || '', + } }, props.children)); + }; + Scroller.prototype.needsXScrolling = function () { + if (VISIBLE_HIDDEN_RE.test(this.props.overflowX)) { + return false; + } + // testing scrollWidth>clientWidth is unreliable cross-browser when pixel heights aren't integers. + // much more reliable to see if children are taller than the scroller, even tho doesn't account for + // inner-child margins and absolute positioning + var el = this.el; + var realClientWidth = this.el.getBoundingClientRect().width - this.getYScrollbarWidth(); + var children = el.children; + for (var i = 0; i < children.length; i += 1) { + var childEl = children[i]; + if (childEl.getBoundingClientRect().width > realClientWidth) { + return true; + } + } + return false; + }; + Scroller.prototype.needsYScrolling = function () { + if (VISIBLE_HIDDEN_RE.test(this.props.overflowY)) { + return false; + } + // testing scrollHeight>clientHeight is unreliable cross-browser when pixel heights aren't integers. + // much more reliable to see if children are taller than the scroller, even tho doesn't account for + // inner-child margins and absolute positioning + var el = this.el; + var realClientHeight = this.el.getBoundingClientRect().height - this.getXScrollbarWidth(); + var children = el.children; + for (var i = 0; i < children.length; i += 1) { + var childEl = children[i]; + if (childEl.getBoundingClientRect().height > realClientHeight) { + return true; + } + } + return false; + }; + Scroller.prototype.getXScrollbarWidth = function () { + if (VISIBLE_HIDDEN_RE.test(this.props.overflowX)) { + return 0; + } + return this.el.offsetHeight - this.el.clientHeight; // only works because we guarantee no borders. TODO: add to CSS with important? + }; + Scroller.prototype.getYScrollbarWidth = function () { + if (VISIBLE_HIDDEN_RE.test(this.props.overflowY)) { + return 0; + } + return this.el.offsetWidth - this.el.clientWidth; // only works because we guarantee no borders. TODO: add to CSS with important? + }; + return Scroller; + }(BaseComponent)); + + /* + TODO: somehow infer OtherArgs from masterCallback? + TODO: infer RefType from masterCallback if provided + */ + var RefMap = /** @class */ (function () { + function RefMap(masterCallback) { + var _this = this; + this.masterCallback = masterCallback; + this.currentMap = {}; + this.depths = {}; + this.callbackMap = {}; + this.handleValue = function (val, key) { + var _a = _this, depths = _a.depths, currentMap = _a.currentMap; + var removed = false; + var added = false; + if (val !== null) { + // for bug... ACTUALLY: can probably do away with this now that callers don't share numeric indices anymore + removed = (key in currentMap); + currentMap[key] = val; + depths[key] = (depths[key] || 0) + 1; + added = true; + } + else { + depths[key] -= 1; + if (!depths[key]) { + delete currentMap[key]; + delete _this.callbackMap[key]; + removed = true; + } + } + if (_this.masterCallback) { + if (removed) { + _this.masterCallback(null, String(key)); + } + if (added) { + _this.masterCallback(val, String(key)); + } + } + }; + } + RefMap.prototype.createRef = function (key) { + var _this = this; + var refCallback = this.callbackMap[key]; + if (!refCallback) { + refCallback = this.callbackMap[key] = function (val) { + _this.handleValue(val, String(key)); + }; + } + return refCallback; + }; + // TODO: check callers that don't care about order. should use getAll instead + // NOTE: this method has become less valuable now that we are encouraged to map order by some other index + // TODO: provide ONE array-export function, buildArray, which fails on non-numeric indexes. caller can manipulate and "collect" + RefMap.prototype.collect = function (startIndex, endIndex, step) { + return collectFromHash(this.currentMap, startIndex, endIndex, step); + }; + RefMap.prototype.getAll = function () { + return hashValuesToArray(this.currentMap); + }; + return RefMap; + }()); + + function computeShrinkWidth(chunkEls) { + var shrinkCells = findElements(chunkEls, '.fc-scrollgrid-shrink'); + var largestWidth = 0; + for (var _i = 0, shrinkCells_1 = shrinkCells; _i < shrinkCells_1.length; _i++) { + var shrinkCell = shrinkCells_1[_i]; + largestWidth = Math.max(largestWidth, computeSmallestCellWidth(shrinkCell)); + } + return Math.ceil(largestWidth); // 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 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 ? + createElement("tr", { className: "fc-scrollgrid-section" }, + createElement("td", { className: 'fc-timegrid-divider ' + context.theme.getClass('tableCellShaded') }))), + }); + } + sections.push({ + type: 'body', + key: 'body', + liquid: true, + expandRows: Boolean(context.options.expandRows), + chunk: { + scrollerElRef: this.scrollerElRef, + content: timeContent, + }, + }); + return (createElement(ViewRoot, { viewSpec: context.viewSpec, elRef: this.rootElRef }, function (rootElRef, classNames) { return (createElement("div", { className: ['fc-timegrid'].concat(classNames).join(' '), ref: rootElRef }, + createElement(SimpleScrollGrid, { liquid: !props.isHeightAuto && !props.forPrint, collapsibleWidth: props.forPrint, cols: [{ width: 'shrink' }], sections: sections }))); })); + }; + TimeColsView.prototype.renderHScrollLayout = function (headerRowContent, allDayContent, timeContent, colCnt, dayMinWidth, slatMetas, slatCoords) { + var _this = this; + var ScrollGrid = this.context.pluginHooks.scrollGridImpl; + if (!ScrollGrid) { + throw new Error('No ScrollGrid implementation'); + } + var _a = this, context = _a.context, props = _a.props; + 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, + syncRowHeights: true, + chunks: [ + { + key: 'axis', + rowContent: function (arg) { return (createElement("tr", null, _this.renderHeadAxis('day', arg.rowSyncHeights[0]))); }, + }, + { + key: 'cols', + elRef: this.headerElRef, + tableClassName: 'fc-col-header', + rowContent: headerRowContent, + }, + ], + }); + } + if (allDayContent) { + sections.push({ + type: 'body', + key: 'all-day', + syncRowHeights: true, + chunks: [ + { + key: 'axis', + rowContent: function (contentArg) { return (createElement("tr", null, _this.renderTableRowAxis(contentArg.rowSyncHeights[0]))); }, + }, + { + key: 'cols', + content: allDayContent, + }, + ], + }); + sections.push({ + key: 'all-day-divider', + type: 'body', + outerContent: ( // TODO: rename to cellContent so don't need to define ? + createElement("tr", { className: "fc-scrollgrid-section" }, + createElement("td", { colSpan: 2, className: 'fc-timegrid-divider ' + context.theme.getClass('tableCellShaded') }))), + }); + } + var isNowIndicator = context.options.nowIndicator; + sections.push({ + type: 'body', + key: 'body', + liquid: true, + expandRows: Boolean(context.options.expandRows), + chunks: [ + { + key: 'axis', + content: function (arg) { return ( + // TODO: make this now-indicator arrow more DRY with TimeColsContent + createElement("div", { className: "fc-timegrid-axis-chunk" }, + createElement("table", { style: { height: arg.expandRows ? arg.clientHeight : '' } }, + arg.tableColGroupNode, + createElement("tbody", null, + createElement(TimeBodyAxis, { slatMetas: slatMetas }))), + createElement("div", { className: "fc-timegrid-now-indicator-container" }, + createElement(NowTimer, { unit: isNowIndicator ? 'minute' : 'day' /* hacky */ }, function (nowDate) { + var nowIndicatorTop = isNowIndicator && + slatCoords && + slatCoords.safeComputeTop(nowDate); // might return void + if (typeof nowIndicatorTop === 'number') { + return (createElement(NowIndicatorRoot, { isAxis: true, date: nowDate }, function (rootElRef, classNames, innerElRef, innerContent) { return (createElement("div", { ref: rootElRef, className: ['fc-timegrid-now-indicator-arrow'].concat(classNames).join(' '), style: { top: nowIndicatorTop } }, innerContent)); })); + } + return null; + })))); }, + }, + { + key: 'cols', + scrollerElRef: this.scrollerElRef, + content: timeContent, + }, + ], + }); + if (stickyFooterScrollbar) { + sections.push({ + key: 'footer', + type: 'footer', + isSticky: true, + chunks: [ + { + key: 'axis', + content: renderScrollShim, + }, + { + key: 'cols', + content: renderScrollShim, + }, + ], + }); + } + return (createElement(ViewRoot, { viewSpec: context.viewSpec, elRef: this.rootElRef }, function (rootElRef, classNames) { return (createElement("div", { className: ['fc-timegrid'].concat(classNames).join(' '), ref: rootElRef }, + createElement(ScrollGrid, { liquid: !props.isHeightAuto && !props.forPrint, collapsibleWidth: false, colGroups: [ + { width: 'shrink', cols: [{ width: 'shrink' }] }, + { cols: [{ span: colCnt, minWidth: dayMinWidth }] }, + ], sections: sections }))); })); + }; + /* Dimensions + ------------------------------------------------------------------------------------------------------------------*/ + TimeColsView.prototype.getAllDayMaxEventProps = function () { + var _a = this.context.options, dayMaxEvents = _a.dayMaxEvents, dayMaxEventRows = _a.dayMaxEventRows; + if (dayMaxEvents === true || dayMaxEventRows === true) { // is auto? + dayMaxEvents = undefined; + dayMaxEventRows = AUTO_ALL_DAY_MAX_EVENT_ROWS; // make sure "auto" goes to a real number + } + return { dayMaxEvents: dayMaxEvents, dayMaxEventRows: dayMaxEventRows }; + }; + return TimeColsView; + }(DateComponent)); + function renderAllDayInner$1(hookProps) { + return hookProps.text; + } + + var TimeColsSlatsCoords = /** @class */ (function () { + function TimeColsSlatsCoords(positions, dateProfile, slotDuration) { + this.positions = positions; + this.dateProfile = dateProfile; + this.slotDuration = slotDuration; + } + TimeColsSlatsCoords.prototype.safeComputeTop = function (date) { + var dateProfile = this.dateProfile; + if (rangeContainsMarker(dateProfile.currentRange, date)) { + var startOfDayDate = startOfDay(date); + var timeMs = date.valueOf() - startOfDayDate.valueOf(); + if (timeMs >= asRoughMs(dateProfile.slotMinTime) && + timeMs < asRoughMs(dateProfile.slotMaxTime)) { + return this.computeTimeTop(createDuration(timeMs)); + } + } + return null; + }; + // Computes the top coordinate, relative to the bounds of the grid, of the given date. + // A `startOfDayDate` must be given for avoiding ambiguity over how to treat midnight. + TimeColsSlatsCoords.prototype.computeDateTop = function (when, startOfDayDate) { + if (!startOfDayDate) { + startOfDayDate = startOfDay(when); + } + return this.computeTimeTop(createDuration(when.valueOf() - startOfDayDate.valueOf())); + }; + // Computes the top coordinate, relative to the bounds of the grid, of the given time (a Duration). + // This is a makeshify way to compute the time-top. Assumes all slatMetas dates are uniform. + // Eventually allow computation with arbirary slat dates. + TimeColsSlatsCoords.prototype.computeTimeTop = function (duration) { + var _a = this, positions = _a.positions, dateProfile = _a.dateProfile; + var len = positions.els.length; + // floating-point value of # of slots covered + var slatCoverage = (duration.milliseconds - asRoughMs(dateProfile.slotMinTime)) / asRoughMs(this.slotDuration); + var slatIndex; + var slatRemainder; + // compute a floating-point number for how many slats should be progressed through. + // from 0 to number of slats (inclusive) + // constrained because slotMinTime/slotMaxTime might be customized. + slatCoverage = Math.max(0, slatCoverage); + slatCoverage = Math.min(len, slatCoverage); + // an integer index of the furthest whole slat + // from 0 to number slats (*exclusive*, so len-1) + slatIndex = Math.floor(slatCoverage); + slatIndex = Math.min(slatIndex, len - 1); + // how much further through the slatIndex slat (from 0.0-1.0) must be covered in addition. + // could be 1.0 if slatCoverage is covering *all* the slots + slatRemainder = slatCoverage - slatIndex; + return positions.tops[slatIndex] + + positions.getHeight(slatIndex) * slatRemainder; + }; + return TimeColsSlatsCoords; + }()); + + var TimeColsSlatsBody = /** @class */ (function (_super) { + __extends(TimeColsSlatsBody, _super); + function TimeColsSlatsBody() { + return _super !== null && _super.apply(this, arguments) || this; + } + TimeColsSlatsBody.prototype.render = function () { + var _a = this, props = _a.props, context = _a.context; + var options = context.options; + var slatElRefs = props.slatElRefs; + return (createElement("tbody", null, props.slatMetas.map(function (slatMeta, i) { + var hookProps = { + time: slatMeta.time, + date: context.dateEnv.toDate(slatMeta.date), + view: context.viewApi, + }; + var classNames = [ + 'fc-timegrid-slot', + 'fc-timegrid-slot-lane', + slatMeta.isLabeled ? '' : 'fc-timegrid-slot-minor', + ]; + return (createElement("tr", { key: slatMeta.key, ref: slatElRefs.createRef(slatMeta.key) }, + props.axis && (createElement(TimeColsAxisCell, __assign({}, slatMeta))), + createElement(RenderHook, { hookProps: hookProps, classNames: options.slotLaneClassNames, content: options.slotLaneContent, didMount: options.slotLaneDidMount, willUnmount: options.slotLaneWillUnmount }, function (rootElRef, customClassNames, innerElRef, innerContent) { return (createElement("td", { ref: rootElRef, className: classNames.concat(customClassNames).join(' '), "data-time": slatMeta.isoTimeStr }, innerContent)); }))); + }))); + }; + return TimeColsSlatsBody; + }(BaseComponent)); + + /* + for the horizontal "slats" that run width-wise. Has a time axis on a side. Depends on RTL. + */ + var TimeColsSlats = /** @class */ (function (_super) { + __extends(TimeColsSlats, _super); + function TimeColsSlats() { + var _this = _super !== null && _super.apply(this, arguments) || this; + _this.rootElRef = createRef(); + _this.slatElRefs = new RefMap(); + return _this; + } + TimeColsSlats.prototype.render = function () { + var _a = this, props = _a.props, context = _a.context; + return (createElement("div", { className: "fc-timegrid-slots", ref: this.rootElRef }, + createElement("table", { className: context.theme.getClass('table'), style: { + minWidth: props.tableMinWidth, + width: props.clientWidth, + height: props.minHeight, + } }, + props.tableColGroupNode /* relies on there only being a single for the axis */, + createElement(TimeColsSlatsBody, { slatElRefs: this.slatElRefs, axis: props.axis, slatMetas: props.slatMetas })))); + }; + TimeColsSlats.prototype.componentDidMount = function () { + this.updateSizing(); + }; + TimeColsSlats.prototype.componentDidUpdate = function () { + this.updateSizing(); + }; + TimeColsSlats.prototype.componentWillUnmount = function () { + if (this.props.onCoords) { + this.props.onCoords(null); + } + }; + TimeColsSlats.prototype.updateSizing = function () { + var _a = this, context = _a.context, props = _a.props; + if (props.onCoords && + props.clientWidth !== null // means sizing has stabilized + ) { + var rootEl = this.rootElRef.current; + if (rootEl.offsetHeight) { // not hidden by css + props.onCoords(new TimeColsSlatsCoords(new PositionCache(this.rootElRef.current, collectSlatEls(this.slatElRefs.currentMap, props.slatMetas), false, true), this.props.dateProfile, context.options.slotDuration)); + } + } + }; + return TimeColsSlats; + }(BaseComponent)); + function collectSlatEls(elMap, slatMetas) { + return slatMetas.map(function (slatMeta) { return elMap[slatMeta.key]; }); + } + + function splitSegsByCol(segs, colCnt) { + var segsByCol = []; + var i; + for (i = 0; i < colCnt; i += 1) { + segsByCol.push([]); + } + if (segs) { + for (i = 0; i < segs.length; i += 1) { + segsByCol[segs[i].col].push(segs[i]); + } + } + return segsByCol; + } + function splitInteractionByCol(ui, colCnt) { + var byRow = []; + if (!ui) { + for (var i = 0; i < colCnt; i += 1) { + byRow[i] = null; + } + } + else { + for (var i = 0; i < colCnt; 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.col].segs.push(seg); + } + } + return byRow; + } + + var TimeColMoreLink = /** @class */ (function (_super) { + __extends(TimeColMoreLink, _super); + function TimeColMoreLink() { + var _this = _super !== null && _super.apply(this, arguments) || this; + _this.rootElRef = createRef(); + return _this; + } + TimeColMoreLink.prototype.render = function () { + var _this = this; + var props = this.props; + return (createElement(MoreLinkRoot, { allDayDate: null, moreCnt: props.hiddenSegs.length, allSegs: props.hiddenSegs, hiddenSegs: props.hiddenSegs, alignmentElRef: this.rootElRef, defaultContent: renderMoreLinkInner, extraDateSpan: props.extraDateSpan, dateProfile: props.dateProfile, todayRange: props.todayRange, popoverContent: function () { return renderPlainFgSegs(props.hiddenSegs, props); } }, function (rootElRef, classNames, innerElRef, innerContent, handleClick) { return (createElement("a", { ref: function (el) { + setRef(rootElRef, el); + setRef(_this.rootElRef, el); + }, className: ['fc-timegrid-more-link'].concat(classNames).join(' '), style: { top: props.top, bottom: props.bottom }, onClick: handleClick }, + createElement("div", { ref: innerElRef, className: "fc-timegrid-more-link-inner fc-sticky" }, innerContent))); })); + }; + return TimeColMoreLink; + }(BaseComponent)); + function renderMoreLinkInner(props) { + return props.shortText; + } + + // segInputs assumed sorted + function buildPositioning(segInputs, strictOrder, maxStackCnt) { + var hierarchy = new SegHierarchy(); + if (strictOrder != null) { + hierarchy.strictOrder = strictOrder; + } + if (maxStackCnt != null) { + hierarchy.maxStackCnt = maxStackCnt; + } + var hiddenEntries = hierarchy.addSegs(segInputs); + var hiddenGroups = groupIntersectingEntries(hiddenEntries); + var web = buildWeb(hierarchy); + web = stretchWeb(web, 1); // all levelCoords/thickness will have 0.0-1.0 + var segRects = webToRects(web); + return { segRects: segRects, hiddenGroups: hiddenGroups }; + } + function buildWeb(hierarchy) { + var entriesByLevel = hierarchy.entriesByLevel; + var buildNode = cacheable(function (level, lateral) { return level + ':' + lateral; }, function (level, lateral) { + var siblingRange = findNextLevelSegs(hierarchy, level, lateral); + var nextLevelRes = buildNodes(siblingRange, buildNode); + var entry = entriesByLevel[level][lateral]; + return [ + __assign(__assign({}, entry), { nextLevelNodes: nextLevelRes[0] }), + entry.thickness + nextLevelRes[1], // the pressure builds + ]; + }); + return buildNodes(entriesByLevel.length + ? { level: 0, lateralStart: 0, lateralEnd: entriesByLevel[0].length } + : null, buildNode)[0]; + } + function buildNodes(siblingRange, buildNode) { + if (!siblingRange) { + return [[], 0]; + } + var level = siblingRange.level, lateralStart = siblingRange.lateralStart, lateralEnd = siblingRange.lateralEnd; + var lateral = lateralStart; + var pairs = []; + while (lateral < lateralEnd) { + pairs.push(buildNode(level, lateral)); + lateral += 1; + } + pairs.sort(cmpDescPressures); + return [ + pairs.map(extractNode), + pairs[0][1], // first item's pressure + ]; + } + function cmpDescPressures(a, b) { + return b[1] - a[1]; + } + function extractNode(a) { + return a[0]; + } + function findNextLevelSegs(hierarchy, subjectLevel, subjectLateral) { + var levelCoords = hierarchy.levelCoords, entriesByLevel = hierarchy.entriesByLevel; + var subjectEntry = entriesByLevel[subjectLevel][subjectLateral]; + var afterSubject = levelCoords[subjectLevel] + subjectEntry.thickness; + var levelCnt = levelCoords.length; + var level = subjectLevel; + // skip past levels that are too high up + for (; level < levelCnt && levelCoords[level] < afterSubject; level += 1) + ; // do nothing + for (; level < levelCnt; level += 1) { + var entries = entriesByLevel[level]; + var entry = void 0; + var searchIndex = binarySearch(entries, subjectEntry.span.start, getEntrySpanEnd); + var lateralStart = searchIndex[0] + searchIndex[1]; // if exact match (which doesn't collide), go to next one + var lateralEnd = lateralStart; + while ( // loop through entries that horizontally intersect + (entry = entries[lateralEnd]) && // but not past the whole seg list + entry.span.start < subjectEntry.span.end) { + lateralEnd += 1; + } + if (lateralStart < lateralEnd) { + return { level: level, lateralStart: lateralStart, lateralEnd: lateralEnd }; + } + } + return null; + } + function stretchWeb(topLevelNodes, totalThickness) { + var stretchNode = cacheable(function (node, startCoord, prevThickness) { return buildEntryKey(node); }, function (node, startCoord, prevThickness) { + var nextLevelNodes = node.nextLevelNodes, thickness = node.thickness; + var allThickness = thickness + prevThickness; + var thicknessFraction = thickness / allThickness; + var endCoord; + var newChildren = []; + if (!nextLevelNodes.length) { + endCoord = totalThickness; + } + else { + for (var _i = 0, nextLevelNodes_1 = nextLevelNodes; _i < nextLevelNodes_1.length; _i++) { + var childNode = nextLevelNodes_1[_i]; + if (endCoord === undefined) { + var res = stretchNode(childNode, startCoord, allThickness); + endCoord = res[0]; + newChildren.push(res[1]); + } + else { + var res = stretchNode(childNode, endCoord, 0); + newChildren.push(res[1]); + } + } + } + var newThickness = (endCoord - startCoord) * thicknessFraction; + return [endCoord - newThickness, __assign(__assign({}, node), { thickness: newThickness, nextLevelNodes: newChildren })]; + }); + return topLevelNodes.map(function (node) { return stretchNode(node, 0, 0)[1]; }); + } + // not sorted in any particular order + function webToRects(topLevelNodes) { + var rects = []; + var processNode = cacheable(function (node, levelCoord, stackDepth) { return buildEntryKey(node); }, function (node, levelCoord, stackDepth) { + var rect = __assign(__assign({}, node), { levelCoord: levelCoord, + stackDepth: stackDepth, stackForward: 0 }); + rects.push(rect); + return (rect.stackForward = processNodes(node.nextLevelNodes, levelCoord + node.thickness, stackDepth + 1) + 1); + }); + function processNodes(nodes, levelCoord, stackDepth) { + var stackForward = 0; + for (var _i = 0, nodes_1 = nodes; _i < nodes_1.length; _i++) { + var node = nodes_1[_i]; + stackForward = Math.max(processNode(node, levelCoord, stackDepth), stackForward); + } + return stackForward; + } + processNodes(topLevelNodes, 0, 0); + return rects; // TODO: sort rects by levelCoord to be consistent with toRects? + } + // TODO: move to general util + function cacheable(keyFunc, workFunc) { + var cache = {}; + return function () { + var args = []; + for (var _i = 0; _i < arguments.length; _i++) { + args[_i] = arguments[_i]; + } + var key = keyFunc.apply(void 0, args); + return (key in cache) + ? cache[key] + : (cache[key] = workFunc.apply(void 0, args)); + }; + } + + function computeSegVCoords(segs, colDate, slatCoords, eventMinHeight) { + if (slatCoords === void 0) { slatCoords = null; } + if (eventMinHeight === void 0) { eventMinHeight = 0; } + var vcoords = []; + if (slatCoords) { + for (var i = 0; i < segs.length; i += 1) { + var seg = segs[i]; + var spanStart = slatCoords.computeDateTop(seg.start, colDate); + var spanEnd = Math.max(spanStart + (eventMinHeight || 0), // :( + slatCoords.computeDateTop(seg.end, colDate)); + vcoords.push({ + start: Math.round(spanStart), + end: Math.round(spanEnd), // + }); + } + } + return vcoords; + } + function computeFgSegPlacements(segs, segVCoords, // might not have for every seg + eventOrderStrict, eventMaxStack) { + var segInputs = []; + var dumbSegs = []; // segs without coords + for (var i = 0; i < segs.length; i += 1) { + var vcoords = segVCoords[i]; + if (vcoords) { + segInputs.push({ + index: i, + thickness: 1, + span: vcoords, + }); + } + else { + dumbSegs.push(segs[i]); + } + } + var _a = buildPositioning(segInputs, eventOrderStrict, eventMaxStack), segRects = _a.segRects, hiddenGroups = _a.hiddenGroups; + var segPlacements = []; + for (var _i = 0, segRects_1 = segRects; _i < segRects_1.length; _i++) { + var segRect = segRects_1[_i]; + segPlacements.push({ + seg: segs[segRect.index], + rect: segRect, + }); + } + for (var _b = 0, dumbSegs_1 = dumbSegs; _b < dumbSegs_1.length; _b++) { + var dumbSeg = dumbSegs_1[_b]; + segPlacements.push({ seg: dumbSeg, rect: null }); + } + return { segPlacements: segPlacements, hiddenGroups: hiddenGroups }; + } + + var DEFAULT_TIME_FORMAT$1 = createFormatter({ + hour: 'numeric', + minute: '2-digit', + meridiem: false, + }); + var TimeColEvent = /** @class */ (function (_super) { + __extends(TimeColEvent, _super); + function TimeColEvent() { + return _super !== null && _super.apply(this, arguments) || this; + } + TimeColEvent.prototype.render = function () { + var classNames = [ + 'fc-timegrid-event', + 'fc-v-event', + ]; + if (this.props.isShort) { + classNames.push('fc-timegrid-event-short'); + } + return (createElement(StandardEvent, __assign({}, this.props, { defaultTimeFormat: DEFAULT_TIME_FORMAT$1, extraClassNames: classNames }))); + }; + return TimeColEvent; + }(BaseComponent)); + + var TimeColMisc = /** @class */ (function (_super) { + __extends(TimeColMisc, _super); + function TimeColMisc() { + return _super !== null && _super.apply(this, arguments) || this; + } + TimeColMisc.prototype.render = function () { + var props = this.props; + return (createElement(DayCellContent, { date: props.date, dateProfile: props.dateProfile, todayRange: props.todayRange, extraHookProps: props.extraHookProps }, function (innerElRef, innerContent) { return (innerContent && + createElement("div", { className: "fc-timegrid-col-misc", ref: innerElRef }, innerContent)); })); + }; + return TimeColMisc; + }(BaseComponent)); + + var TimeCol = /** @class */ (function (_super) { + __extends(TimeCol, _super); + function TimeCol() { + var _this = _super !== null && _super.apply(this, arguments) || this; + _this.sortEventSegs = memoize(sortEventSegs); + return _this; + } + // TODO: memoize event-placement? + TimeCol.prototype.render = function () { + var _this = this; + var _a = this, props = _a.props, context = _a.context; + var isSelectMirror = context.options.selectMirror; + var mirrorSegs = (props.eventDrag && props.eventDrag.segs) || + (props.eventResize && props.eventResize.segs) || + (isSelectMirror && props.dateSelectionSegs) || + []; + var interactionAffectedInstances = // TODO: messy way to compute this + (props.eventDrag && props.eventDrag.affectedInstances) || + (props.eventResize && props.eventResize.affectedInstances) || + {}; + var sortedFgSegs = this.sortEventSegs(props.fgEventSegs, context.options.eventOrder); + return (createElement(DayCellRoot, { elRef: props.elRef, date: props.date, dateProfile: props.dateProfile, todayRange: props.todayRange, extraHookProps: props.extraHookProps }, function (rootElRef, classNames, dataAttrs) { return (createElement("td", __assign({ ref: rootElRef, className: ['fc-timegrid-col'].concat(classNames, props.extraClassNames || []).join(' ') }, dataAttrs, props.extraDataAttrs), + createElement("div", { className: "fc-timegrid-col-frame" }, + createElement("div", { className: "fc-timegrid-col-bg" }, + _this.renderFillSegs(props.businessHourSegs, 'non-business'), + _this.renderFillSegs(props.bgEventSegs, 'bg-event'), + _this.renderFillSegs(props.dateSelectionSegs, 'highlight')), + createElement("div", { className: "fc-timegrid-col-events" }, _this.renderFgSegs(sortedFgSegs, interactionAffectedInstances, false, false, false)), + createElement("div", { className: "fc-timegrid-col-events" }, _this.renderFgSegs(mirrorSegs, {}, Boolean(props.eventDrag), Boolean(props.eventResize), Boolean(isSelectMirror))), + createElement("div", { className: "fc-timegrid-now-indicator-container" }, _this.renderNowIndicator(props.nowIndicatorSegs)), + createElement(TimeColMisc, { date: props.date, dateProfile: props.dateProfile, todayRange: props.todayRange, extraHookProps: props.extraHookProps })))); })); + }; + TimeCol.prototype.renderFgSegs = function (sortedFgSegs, segIsInvisible, isDragging, isResizing, isDateSelecting) { + var props = this.props; + if (props.forPrint) { + return renderPlainFgSegs(sortedFgSegs, props); + } + return this.renderPositionedFgSegs(sortedFgSegs, segIsInvisible, isDragging, isResizing, isDateSelecting); + }; + TimeCol.prototype.renderPositionedFgSegs = function (segs, // if not mirror, needs to be sorted + segIsInvisible, isDragging, isResizing, isDateSelecting) { + var _this = this; + var _a = this.context.options, eventMaxStack = _a.eventMaxStack, eventShortHeight = _a.eventShortHeight, eventOrderStrict = _a.eventOrderStrict, eventMinHeight = _a.eventMinHeight; + var _b = this.props, date = _b.date, slatCoords = _b.slatCoords, eventSelection = _b.eventSelection, todayRange = _b.todayRange, nowDate = _b.nowDate; + var isMirror = isDragging || isResizing || isDateSelecting; + var segVCoords = computeSegVCoords(segs, date, slatCoords, eventMinHeight); + var _c = computeFgSegPlacements(segs, segVCoords, eventOrderStrict, eventMaxStack), segPlacements = _c.segPlacements, hiddenGroups = _c.hiddenGroups; + return (createElement(Fragment, null, + this.renderHiddenGroups(hiddenGroups, segs), + segPlacements.map(function (segPlacement) { + var seg = segPlacement.seg, rect = segPlacement.rect; + var instanceId = seg.eventRange.instance.instanceId; + var isVisible = isMirror || Boolean(!segIsInvisible[instanceId] && rect); + var vStyle = computeSegVStyle(rect && rect.span); + var hStyle = (!isMirror && rect) ? _this.computeSegHStyle(rect) : { left: 0, right: 0 }; + var isInset = Boolean(rect) && rect.stackForward > 0; + var isShort = Boolean(rect) && (rect.span.end - rect.span.start) < eventShortHeight; // look at other places for this problem + return (createElement("div", { className: 'fc-timegrid-event-harness' + + (isInset ? ' fc-timegrid-event-harness-inset' : ''), key: instanceId, style: __assign(__assign({ visibility: isVisible ? '' : 'hidden' }, vStyle), hStyle) }, + createElement(TimeColEvent, __assign({ seg: seg, isDragging: isDragging, isResizing: isResizing, isDateSelecting: isDateSelecting, isSelected: instanceId === eventSelection, isShort: isShort }, getSegMeta(seg, todayRange, nowDate))))); + }))); + }; + // will already have eventMinHeight applied because segInputs already had it + TimeCol.prototype.renderHiddenGroups = function (hiddenGroups, segs) { + var _a = this.props, extraDateSpan = _a.extraDateSpan, dateProfile = _a.dateProfile, todayRange = _a.todayRange, nowDate = _a.nowDate, eventSelection = _a.eventSelection, eventDrag = _a.eventDrag, eventResize = _a.eventResize; + return (createElement(Fragment, null, hiddenGroups.map(function (hiddenGroup) { + var positionCss = computeSegVStyle(hiddenGroup.span); + var hiddenSegs = compileSegsFromEntries(hiddenGroup.entries, segs); + return (createElement(TimeColMoreLink, { key: buildIsoString(computeEarliestSegStart(hiddenSegs)), hiddenSegs: hiddenSegs, top: positionCss.top, bottom: positionCss.bottom, extraDateSpan: extraDateSpan, dateProfile: dateProfile, todayRange: todayRange, nowDate: nowDate, eventSelection: eventSelection, eventDrag: eventDrag, eventResize: eventResize })); + }))); + }; + TimeCol.prototype.renderFillSegs = function (segs, fillType) { + var _a = this, props = _a.props, context = _a.context; + var segVCoords = computeSegVCoords(segs, props.date, props.slatCoords, context.options.eventMinHeight); // don't assume all populated + var children = segVCoords.map(function (vcoords, i) { + var seg = segs[i]; + return (createElement("div", { key: buildEventRangeKey(seg.eventRange), className: "fc-timegrid-bg-harness", style: computeSegVStyle(vcoords) }, fillType === 'bg-event' ? + createElement(BgEvent, __assign({ seg: seg }, getSegMeta(seg, props.todayRange, props.nowDate))) : + renderFill(fillType))); + }); + return createElement(Fragment, null, children); + }; + TimeCol.prototype.renderNowIndicator = function (segs) { + var _a = this.props, slatCoords = _a.slatCoords, date = _a.date; + if (!slatCoords) { + return null; + } + return segs.map(function (seg, i) { return (createElement(NowIndicatorRoot, { isAxis: false, date: date, + // key doesn't matter. will only ever be one + key: i }, function (rootElRef, classNames, innerElRef, innerContent) { return (createElement("div", { ref: rootElRef, className: ['fc-timegrid-now-indicator-line'].concat(classNames).join(' '), style: { top: slatCoords.computeDateTop(seg.start, date) } }, innerContent)); })); }); + }; + TimeCol.prototype.computeSegHStyle = function (segHCoords) { + var _a = this.context, isRtl = _a.isRtl, options = _a.options; + var shouldOverlap = options.slotEventOverlap; + var nearCoord = segHCoords.levelCoord; // the left side if LTR. the right side if RTL. floating-point + var farCoord = segHCoords.levelCoord + segHCoords.thickness; // the right side if LTR. the left side if RTL. floating-point + var left; // amount of space from left edge, a fraction of the total width + var right; // amount of space from right edge, a fraction of the total width + if (shouldOverlap) { + // double the width, but don't go beyond the maximum forward coordinate (1.0) + farCoord = Math.min(1, nearCoord + (farCoord - nearCoord) * 2); + } + if (isRtl) { + left = 1 - farCoord; + right = nearCoord; + } + else { + left = nearCoord; + right = 1 - farCoord; + } + var props = { + zIndex: segHCoords.stackDepth + 1, + left: left * 100 + '%', + right: right * 100 + '%', + }; + if (shouldOverlap && !segHCoords.stackForward) { + // add padding to the edge so that forward stacked events don't cover the resizer's icon + props[isRtl ? 'marginLeft' : 'marginRight'] = 10 * 2; // 10 is a guesstimate of the icon's width + } + return props; + }; + return TimeCol; + }(BaseComponent)); + function renderPlainFgSegs(sortedFgSegs, _a) { + var todayRange = _a.todayRange, nowDate = _a.nowDate, eventSelection = _a.eventSelection, eventDrag = _a.eventDrag, eventResize = _a.eventResize; + var hiddenInstances = (eventDrag ? eventDrag.affectedInstances : null) || + (eventResize ? eventResize.affectedInstances : null) || + {}; + return (createElement(Fragment, null, sortedFgSegs.map(function (seg) { + var instanceId = seg.eventRange.instance.instanceId; + return (createElement("div", { key: instanceId, style: { visibility: hiddenInstances[instanceId] ? 'hidden' : '' } }, + createElement(TimeColEvent, __assign({ seg: seg, isDragging: false, isResizing: false, isDateSelecting: false, isSelected: instanceId === eventSelection, isShort: false }, getSegMeta(seg, todayRange, nowDate))))); + }))); + } + function computeSegVStyle(segVCoords) { + if (!segVCoords) { + return { top: '', bottom: '' }; + } + return { + top: segVCoords.start, + bottom: -segVCoords.end, + }; + } + function compileSegsFromEntries(segEntries, allSegs) { + return segEntries.map(function (segEntry) { return allSegs[segEntry.index]; }); + } + + var TimeColsContent = /** @class */ (function (_super) { + __extends(TimeColsContent, _super); + function TimeColsContent() { + var _this = _super !== null && _super.apply(this, arguments) || this; + _this.splitFgEventSegs = memoize(splitSegsByCol); + _this.splitBgEventSegs = memoize(splitSegsByCol); + _this.splitBusinessHourSegs = memoize(splitSegsByCol); + _this.splitNowIndicatorSegs = memoize(splitSegsByCol); + _this.splitDateSelectionSegs = memoize(splitSegsByCol); + _this.splitEventDrag = memoize(splitInteractionByCol); + _this.splitEventResize = memoize(splitInteractionByCol); + _this.rootElRef = createRef(); + _this.cellElRefs = new RefMap(); + return _this; + } + TimeColsContent.prototype.render = function () { + var _this = this; + var _a = this, props = _a.props, context = _a.context; + var nowIndicatorTop = context.options.nowIndicator && + props.slatCoords && + props.slatCoords.safeComputeTop(props.nowDate); // might return void + var colCnt = props.cells.length; + var fgEventSegsByRow = this.splitFgEventSegs(props.fgEventSegs, colCnt); + var bgEventSegsByRow = this.splitBgEventSegs(props.bgEventSegs, colCnt); + var businessHourSegsByRow = this.splitBusinessHourSegs(props.businessHourSegs, colCnt); + var nowIndicatorSegsByRow = this.splitNowIndicatorSegs(props.nowIndicatorSegs, colCnt); + var dateSelectionSegsByRow = this.splitDateSelectionSegs(props.dateSelectionSegs, colCnt); + var eventDragByRow = this.splitEventDrag(props.eventDrag, colCnt); + var eventResizeByRow = this.splitEventResize(props.eventResize, colCnt); + return (createElement("div", { className: "fc-timegrid-cols", ref: this.rootElRef }, + createElement("table", { style: { + minWidth: props.tableMinWidth, + width: props.clientWidth, + } }, + props.tableColGroupNode, + createElement("tbody", null, + createElement("tr", null, + props.axis && (createElement("td", { className: "fc-timegrid-col fc-timegrid-axis" }, + createElement("div", { className: "fc-timegrid-col-frame" }, + createElement("div", { className: "fc-timegrid-now-indicator-container" }, typeof nowIndicatorTop === 'number' && (createElement(NowIndicatorRoot, { isAxis: true, date: props.nowDate }, function (rootElRef, classNames, innerElRef, innerContent) { return (createElement("div", { ref: rootElRef, className: ['fc-timegrid-now-indicator-arrow'].concat(classNames).join(' '), style: { top: nowIndicatorTop } }, innerContent)); })))))), + props.cells.map(function (cell, i) { return (createElement(TimeCol, { key: cell.key, elRef: _this.cellElRefs.createRef(cell.key), dateProfile: props.dateProfile, date: cell.date, nowDate: props.nowDate, todayRange: props.todayRange, extraHookProps: cell.extraHookProps, extraDataAttrs: cell.extraDataAttrs, extraClassNames: cell.extraClassNames, extraDateSpan: cell.extraDateSpan, fgEventSegs: fgEventSegsByRow[i], bgEventSegs: bgEventSegsByRow[i], businessHourSegs: businessHourSegsByRow[i], nowIndicatorSegs: nowIndicatorSegsByRow[i], dateSelectionSegs: dateSelectionSegsByRow[i], eventDrag: eventDragByRow[i], eventResize: eventResizeByRow[i], slatCoords: props.slatCoords, eventSelection: props.eventSelection, forPrint: props.forPrint })); })))))); + }; + TimeColsContent.prototype.componentDidMount = function () { + this.updateCoords(); + }; + TimeColsContent.prototype.componentDidUpdate = function () { + this.updateCoords(); + }; + TimeColsContent.prototype.updateCoords = function () { + var props = this.props; + if (props.onColCoords && + props.clientWidth !== null // means sizing has stabilized + ) { + props.onColCoords(new PositionCache(this.rootElRef.current, collectCellEls(this.cellElRefs.currentMap, props.cells), true, // horizontal + false)); + } + }; + return TimeColsContent; + }(BaseComponent)); + function collectCellEls(elMap, cells) { + return cells.map(function (cell) { return elMap[cell.key]; }); + } + + /* A component that renders one or more columns of vertical time slots + ----------------------------------------------------------------------------------------------------------------------*/ + var TimeCols = /** @class */ (function (_super) { + __extends(TimeCols, _super); + function TimeCols() { + var _this = _super !== null && _super.apply(this, arguments) || this; + _this.processSlotOptions = memoize(processSlotOptions); + _this.state = { + slatCoords: null, + }; + _this.handleRootEl = function (el) { + if (el) { + _this.context.registerInteractiveComponent(_this, { + el: el, + isHitComboAllowed: _this.props.isHitComboAllowed, + }); + } + else { + _this.context.unregisterInteractiveComponent(_this); + } + }; + _this.handleScrollRequest = function (request) { + var onScrollTopRequest = _this.props.onScrollTopRequest; + var slatCoords = _this.state.slatCoords; + if (onScrollTopRequest && slatCoords) { + if (request.time) { + var top_1 = slatCoords.computeTimeTop(request.time); + top_1 = Math.ceil(top_1); // zoom can give weird floating-point values. rather scroll a little bit further + if (top_1) { + top_1 += 1; // to overcome top border that slots beyond the first have. looks better + } + onScrollTopRequest(top_1); + } + return true; + } + return false; + }; + _this.handleColCoords = function (colCoords) { + _this.colCoords = colCoords; + }; + _this.handleSlatCoords = function (slatCoords) { + _this.setState({ slatCoords: slatCoords }); + if (_this.props.onSlatCoords) { + _this.props.onSlatCoords(slatCoords); + } + }; + return _this; + } + TimeCols.prototype.render = function () { + var _a = this, props = _a.props, state = _a.state; + return (createElement("div", { className: "fc-timegrid-body", 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(TimeColsSlats, { axis: props.axis, dateProfile: props.dateProfile, slatMetas: props.slatMetas, clientWidth: props.clientWidth, minHeight: props.expandRows ? props.clientHeight : '', tableMinWidth: props.tableMinWidth, tableColGroupNode: props.axis ? props.tableColGroupNode : null /* axis depends on the colgroup's shrinking */, onCoords: this.handleSlatCoords }), + createElement(TimeColsContent, { cells: props.cells, axis: props.axis, dateProfile: props.dateProfile, businessHourSegs: props.businessHourSegs, bgEventSegs: props.bgEventSegs, fgEventSegs: props.fgEventSegs, dateSelectionSegs: props.dateSelectionSegs, eventSelection: props.eventSelection, eventDrag: props.eventDrag, eventResize: props.eventResize, todayRange: props.todayRange, nowDate: props.nowDate, nowIndicatorSegs: props.nowIndicatorSegs, clientWidth: props.clientWidth, tableMinWidth: props.tableMinWidth, tableColGroupNode: props.tableColGroupNode, slatCoords: state.slatCoords, onColCoords: this.handleColCoords, forPrint: props.forPrint }))); + }; + TimeCols.prototype.componentDidMount = function () { + this.scrollResponder = this.context.createScrollResponder(this.handleScrollRequest); + }; + TimeCols.prototype.componentDidUpdate = function (prevProps) { + this.scrollResponder.update(prevProps.dateProfile !== this.props.dateProfile); + }; + TimeCols.prototype.componentWillUnmount = function () { + this.scrollResponder.detach(); + }; + TimeCols.prototype.queryHit = function (positionLeft, positionTop) { + var _a = this.context, dateEnv = _a.dateEnv, options = _a.options; + var colCoords = this.colCoords; + var dateProfile = this.props.dateProfile; + var slatCoords = this.state.slatCoords; + var _b = this.processSlotOptions(this.props.slotDuration, options.snapDuration), snapDuration = _b.snapDuration, snapsPerSlot = _b.snapsPerSlot; + var colIndex = colCoords.leftToIndex(positionLeft); + var slatIndex = slatCoords.positions.topToIndex(positionTop); + if (colIndex != null && slatIndex != null) { + var cell = this.props.cells[colIndex]; + var slatTop = slatCoords.positions.tops[slatIndex]; + var slatHeight = slatCoords.positions.getHeight(slatIndex); + var partial = (positionTop - slatTop) / slatHeight; // floating point number between 0 and 1 + var localSnapIndex = Math.floor(partial * snapsPerSlot); // the snap # relative to start of slat + var snapIndex = slatIndex * snapsPerSlot + localSnapIndex; + var dayDate = this.props.cells[colIndex].date; + var time = addDurations(dateProfile.slotMinTime, multiplyDuration(snapDuration, snapIndex)); + var start = dateEnv.add(dayDate, time); + var end = dateEnv.add(start, snapDuration); + return { + dateProfile: dateProfile, + dateSpan: __assign({ range: { start: start, end: end }, allDay: false }, cell.extraDateSpan), + dayEl: colCoords.els[colIndex], + rect: { + left: colCoords.lefts[colIndex], + right: colCoords.rights[colIndex], + top: slatTop, + bottom: slatTop + slatHeight, + }, + layer: 0, + }; + } + return null; + }; + return TimeCols; + }(DateComponent)); + function processSlotOptions(slotDuration, snapDurationOverride) { + var snapDuration = snapDurationOverride || slotDuration; + var snapsPerSlot = wholeDivideDurations(slotDuration, snapDuration); + if (snapsPerSlot === null) { + snapDuration = slotDuration; + snapsPerSlot = 1; + // TODO: say warning? + } + return { snapDuration: snapDuration, snapsPerSlot: snapsPerSlot }; + } + + var DayTimeColsSlicer = /** @class */ (function (_super) { + __extends(DayTimeColsSlicer, _super); + function DayTimeColsSlicer() { + return _super !== null && _super.apply(this, arguments) || this; + } + DayTimeColsSlicer.prototype.sliceRange = function (range, dayRanges) { + var segs = []; + for (var col = 0; col < dayRanges.length; col += 1) { + var segRange = intersectRanges(range, dayRanges[col]); + if (segRange) { + segs.push({ + start: segRange.start, + end: segRange.end, + isStart: segRange.start.valueOf() === range.start.valueOf(), + isEnd: segRange.end.valueOf() === range.end.valueOf(), + col: col, + }); + } + } + return segs; + }; + return DayTimeColsSlicer; + }(Slicer)); + + var DayTimeCols = /** @class */ (function (_super) { + __extends(DayTimeCols, _super); + function DayTimeCols() { + var _this = _super !== null && _super.apply(this, arguments) || this; + _this.buildDayRanges = memoize(buildDayRanges); + _this.slicer = new DayTimeColsSlicer(); + _this.timeColsRef = createRef(); + return _this; + } + DayTimeCols.prototype.render = function () { + var _this = this; + var _a = this, props = _a.props, context = _a.context; + var dateProfile = props.dateProfile, dayTableModel = props.dayTableModel; + var isNowIndicator = context.options.nowIndicator; + var dayRanges = this.buildDayRanges(dayTableModel, dateProfile, context.dateEnv); + // give it the first row of cells + // TODO: would move this further down hierarchy, but sliceNowDate needs it + return (createElement(NowTimer, { unit: isNowIndicator ? 'minute' : 'day' }, function (nowDate, todayRange) { return (createElement(TimeCols, __assign({ ref: _this.timeColsRef }, _this.slicer.sliceProps(props, dateProfile, null, context, dayRanges), { forPrint: props.forPrint, axis: props.axis, dateProfile: dateProfile, slatMetas: props.slatMetas, slotDuration: props.slotDuration, cells: dayTableModel.cells[0], tableColGroupNode: props.tableColGroupNode, tableMinWidth: props.tableMinWidth, clientWidth: props.clientWidth, clientHeight: props.clientHeight, expandRows: props.expandRows, nowDate: nowDate, nowIndicatorSegs: isNowIndicator && _this.slicer.sliceNowDate(nowDate, context, dayRanges), todayRange: todayRange, onScrollTopRequest: props.onScrollTopRequest, onSlatCoords: props.onSlatCoords }))); })); + }; + return DayTimeCols; + }(DateComponent)); + function buildDayRanges(dayTableModel, dateProfile, dateEnv) { + var ranges = []; + for (var _i = 0, _a = dayTableModel.headerDates; _i < _a.length; _i++) { + var date = _a[_i]; + ranges.push({ + start: dateEnv.add(date, dateProfile.slotMinTime), + end: dateEnv.add(date, dateProfile.slotMaxTime), + }); + } + return ranges; + } + + // potential nice values for the slot-duration and interval-duration + // from largest to smallest + var STOCK_SUB_DURATIONS = [ + { hours: 1 }, + { minutes: 30 }, + { minutes: 15 }, + { seconds: 30 }, + { seconds: 15 }, + ]; + function buildSlatMetas(slotMinTime, slotMaxTime, explicitLabelInterval, slotDuration, dateEnv) { + var dayStart = new Date(0); + var slatTime = slotMinTime; + var slatIterator = createDuration(0); + var labelInterval = explicitLabelInterval || computeLabelInterval(slotDuration); + var metas = []; + while (asRoughMs(slatTime) < asRoughMs(slotMaxTime)) { + var date = dateEnv.add(dayStart, slatTime); + var isLabeled = wholeDivideDurations(slatIterator, labelInterval) !== null; + metas.push({ + date: date, + time: slatTime, + key: date.toISOString(), + isoTimeStr: formatIsoTimeString(date), + isLabeled: isLabeled, + }); + slatTime = addDurations(slatTime, slotDuration); + slatIterator = addDurations(slatIterator, slotDuration); + } + return metas; + } + // Computes an automatic value for slotLabelInterval + function computeLabelInterval(slotDuration) { + var i; + var labelInterval; + var slotsPerLabel; + // find the smallest stock label interval that results in more than one slots-per-label + for (i = STOCK_SUB_DURATIONS.length - 1; i >= 0; i -= 1) { + labelInterval = createDuration(STOCK_SUB_DURATIONS[i]); + slotsPerLabel = wholeDivideDurations(labelInterval, slotDuration); + if (slotsPerLabel !== null && slotsPerLabel > 1) { + return labelInterval; + } + } + return slotDuration; // fall back + } + + var DayTimeColsView = /** @class */ (function (_super) { + __extends(DayTimeColsView, _super); + function DayTimeColsView() { + var _this = _super !== null && _super.apply(this, arguments) || this; + _this.buildTimeColsModel = memoize(buildTimeColsModel); + _this.buildSlatMetas = memoize(buildSlatMetas); + return _this; + } + DayTimeColsView.prototype.render = function () { + var _this = this; + var _a = this.context, options = _a.options, dateEnv = _a.dateEnv, dateProfileGenerator = _a.dateProfileGenerator; + var props = this.props; + var dateProfile = props.dateProfile; + var dayTableModel = this.buildTimeColsModel(dateProfile, dateProfileGenerator); + var splitProps = this.allDaySplitter.splitProps(props); + var slatMetas = this.buildSlatMetas(dateProfile.slotMinTime, dateProfile.slotMaxTime, options.slotLabelInterval, options.slotDuration, dateEnv); + var dayMinWidth = options.dayMinWidth; + var hasAttachedAxis = !dayMinWidth; + var hasDetachedAxis = dayMinWidth; + var headerContent = options.dayHeaders && (createElement(DayHeader, { dates: dayTableModel.headerDates, dateProfile: dateProfile, datesRepDistinctDays: true, renderIntro: hasAttachedAxis ? this.renderHeadAxis : null })); + var allDayContent = (options.allDaySlot !== false) && (function (contentArg) { return (createElement(DayTable, __assign({}, splitProps.allDay, { dateProfile: dateProfile, dayTableModel: dayTableModel, nextDayThreshold: options.nextDayThreshold, tableMinWidth: contentArg.tableMinWidth, colGroupNode: contentArg.tableColGroupNode, renderRowIntro: hasAttachedAxis ? _this.renderTableRowAxis : null, showWeekNumbers: false, expandRows: false, headerAlignElRef: _this.headerElRef, clientWidth: contentArg.clientWidth, clientHeight: contentArg.clientHeight, forPrint: props.forPrint }, _this.getAllDayMaxEventProps()))); }); + var timeGridContent = function (contentArg) { return (createElement(DayTimeCols, __assign({}, splitProps.timed, { dayTableModel: dayTableModel, dateProfile: dateProfile, axis: hasAttachedAxis, slotDuration: options.slotDuration, slatMetas: slatMetas, forPrint: props.forPrint, tableColGroupNode: contentArg.tableColGroupNode, tableMinWidth: contentArg.tableMinWidth, clientWidth: contentArg.clientWidth, clientHeight: contentArg.clientHeight, onSlatCoords: _this.handleSlatCoords, expandRows: contentArg.expandRows, onScrollTopRequest: _this.handleScrollTopRequest }))); }; + return hasDetachedAxis + ? this.renderHScrollLayout(headerContent, allDayContent, timeGridContent, dayTableModel.colCnt, dayMinWidth, slatMetas, this.state.slatCoords) + : this.renderSimpleLayout(headerContent, allDayContent, timeGridContent); + }; + return DayTimeColsView; + }(TimeColsView)); + function buildTimeColsModel(dateProfile, dateProfileGenerator) { + var daySeries = new DaySeriesModel(dateProfile.renderRange, dateProfileGenerator); + return new DayTableModel(daySeries, false); + } + + var OPTION_REFINERS$2 = { + allDaySlot: Boolean, + }; + + var timeGridPlugin = createPlugin({ + initialView: 'timeGridWeek', + optionRefiners: OPTION_REFINERS$2, + views: { + timeGrid: { + component: DayTimeColsView, + usesMinMaxTime: true, + allDaySlot: true, + slotDuration: '00:30:00', + slotEventOverlap: true, // a bad name. confused with overlap/constraint system + }, + timeGridDay: { + type: 'timeGrid', + duration: { days: 1 }, + }, + timeGridWeek: { + type: 'timeGrid', + duration: { weeks: 1 }, + }, + }, + }); + + var ListViewHeaderRow = /** @class */ (function (_super) { + __extends(ListViewHeaderRow, _super); + function ListViewHeaderRow() { + return _super !== null && _super.apply(this, arguments) || this; + } + ListViewHeaderRow.prototype.render = function () { + var _a = this.props, dayDate = _a.dayDate, todayRange = _a.todayRange; + var _b = this.context, theme = _b.theme, dateEnv = _b.dateEnv, options = _b.options, viewApi = _b.viewApi; + var dayMeta = getDateMeta(dayDate, todayRange); + // will ever be falsy? + var text = options.listDayFormat ? dateEnv.format(dayDate, options.listDayFormat) : ''; + // will ever be falsy? also, BAD NAME "alt" + var sideText = options.listDaySideFormat ? dateEnv.format(dayDate, options.listDaySideFormat) : ''; + var navLinkData = options.navLinks + ? buildNavLinkData(dayDate) + : null; + var hookProps = __assign({ date: dateEnv.toDate(dayDate), view: viewApi, text: text, + sideText: sideText, + navLinkData: navLinkData }, dayMeta); + var classNames = ['fc-list-day'].concat(getDayClassNames(dayMeta, theme)); + // TODO: make a reusable HOC for dayHeader (used in daygrid/timegrid too) + return (createElement(RenderHook, { hookProps: hookProps, classNames: options.dayHeaderClassNames, content: options.dayHeaderContent, defaultContent: renderInnerContent, didMount: options.dayHeaderDidMount, willUnmount: options.dayHeaderWillUnmount }, function (rootElRef, customClassNames, innerElRef, innerContent) { return (createElement("tr", { ref: rootElRef, className: classNames.concat(customClassNames).join(' '), "data-date": formatDayString(dayDate) }, + createElement("th", { colSpan: 3 }, + createElement("div", { className: 'fc-list-day-cushion ' + theme.getClass('tableCellShaded'), ref: innerElRef }, innerContent)))); })); + }; + return ListViewHeaderRow; + }(BaseComponent)); + function renderInnerContent(props) { + var navLinkAttrs = props.navLinkData // is there a type for this? + ? { 'data-navlink': props.navLinkData, tabIndex: 0 } + : {}; + return (createElement(Fragment, null, + props.text && (createElement("a", __assign({ className: "fc-list-day-text" }, navLinkAttrs), props.text)), + props.sideText && (createElement("a", __assign({ className: "fc-list-day-side-text" }, navLinkAttrs), props.sideText)))); + } + + var DEFAULT_TIME_FORMAT = createFormatter({ + hour: 'numeric', + minute: '2-digit', + meridiem: 'short', + }); + var ListViewEventRow = /** @class */ (function (_super) { + __extends(ListViewEventRow, _super); + function ListViewEventRow() { + return _super !== null && _super.apply(this, arguments) || this; + } + ListViewEventRow.prototype.render = function () { + var _a = this, props = _a.props, context = _a.context; + var seg = props.seg; + var timeFormat = context.options.eventTimeFormat || DEFAULT_TIME_FORMAT; + return (createElement(EventRoot, { seg: seg, timeText: "" // BAD. because of all-day content + , disableDragging: true, disableResizing: true, defaultContent: renderEventInnerContent, isPast: props.isPast, isFuture: props.isFuture, isToday: props.isToday, isSelected: props.isSelected, isDragging: props.isDragging, isResizing: props.isResizing, isDateSelecting: props.isDateSelecting }, function (rootElRef, classNames, innerElRef, innerContent, hookProps) { return (createElement("tr", { className: ['fc-list-event', hookProps.event.url ? 'fc-event-forced-url' : ''].concat(classNames).join(' '), ref: rootElRef }, + buildTimeContent(seg, timeFormat, context), + createElement("td", { className: "fc-list-event-graphic" }, + createElement("span", { className: "fc-list-event-dot", style: { borderColor: hookProps.borderColor || hookProps.backgroundColor } })), + createElement("td", { className: "fc-list-event-title", ref: innerElRef }, innerContent))); })); + }; + return ListViewEventRow; + }(BaseComponent)); + function renderEventInnerContent(props) { + var event = props.event; + var url = event.url; + var anchorAttrs = url ? { href: url } : {}; + return (createElement("a", __assign({}, anchorAttrs), event.title)); + } + function buildTimeContent(seg, timeFormat, context) { + var options = context.options; + if (options.displayEventTime !== false) { + var eventDef = seg.eventRange.def; + var eventInstance = seg.eventRange.instance; + var doAllDay = false; + var timeText = void 0; + if (eventDef.allDay) { + doAllDay = true; + } + else if (isMultiDayRange(seg.eventRange.range)) { // TODO: use (!isStart || !isEnd) instead? + if (seg.isStart) { + timeText = buildSegTimeText(seg, timeFormat, context, null, null, eventInstance.range.start, seg.end); + } + else if (seg.isEnd) { + timeText = buildSegTimeText(seg, timeFormat, context, null, null, seg.start, eventInstance.range.end); + } + else { + doAllDay = true; + } + } + else { + timeText = buildSegTimeText(seg, timeFormat, context); + } + if (doAllDay) { + var hookProps = { + text: context.options.allDayText, + view: context.viewApi, + }; + return (createElement(RenderHook, { hookProps: hookProps, classNames: options.allDayClassNames, content: options.allDayContent, defaultContent: renderAllDayInner, didMount: options.allDayDidMount, willUnmount: options.allDayWillUnmount }, function (rootElRef, classNames, innerElRef, innerContent) { return (createElement("td", { className: ['fc-list-event-time'].concat(classNames).join(' '), ref: rootElRef }, innerContent)); })); + } + return (createElement("td", { className: "fc-list-event-time" }, timeText)); + } + return null; + } + function renderAllDayInner(hookProps) { + return hookProps.text; + } + + /* + Responsible for the scroller, and forwarding event-related actions into the "grid". + */ + var ListView = /** @class */ (function (_super) { + __extends(ListView, _super); + function ListView() { + var _this = _super !== null && _super.apply(this, arguments) || this; + _this.computeDateVars = memoize(computeDateVars); + _this.eventStoreToSegs = memoize(_this._eventStoreToSegs); + _this.setRootEl = function (rootEl) { + if (rootEl) { + _this.context.registerInteractiveComponent(_this, { + el: rootEl, + }); + } + else { + _this.context.unregisterInteractiveComponent(_this); + } + }; + return _this; + } + ListView.prototype.render = function () { + var _this = this; + var _a = this, props = _a.props, context = _a.context; + var extraClassNames = [ + 'fc-list', + context.theme.getClass('table'), + context.options.stickyHeaderDates !== false ? 'fc-list-sticky' : '', + ]; + var _b = this.computeDateVars(props.dateProfile), dayDates = _b.dayDates, dayRanges = _b.dayRanges; + var eventSegs = this.eventStoreToSegs(props.eventStore, props.eventUiBases, dayRanges); + return (createElement(ViewRoot, { viewSpec: context.viewSpec, elRef: this.setRootEl }, function (rootElRef, classNames) { return (createElement("div", { ref: rootElRef, className: extraClassNames.concat(classNames).join(' ') }, + createElement(Scroller, { liquid: !props.isHeightAuto, overflowX: props.isHeightAuto ? 'visible' : 'hidden', overflowY: props.isHeightAuto ? 'visible' : 'auto' }, eventSegs.length > 0 ? + _this.renderSegList(eventSegs, dayDates) : + _this.renderEmptyMessage()))); })); + }; + ListView.prototype.renderEmptyMessage = function () { + var _a = this.context, options = _a.options, viewApi = _a.viewApi; + var hookProps = { + text: options.noEventsText, + view: viewApi, + }; + return (createElement(RenderHook, { hookProps: hookProps, classNames: options.noEventsClassNames, content: options.noEventsContent, defaultContent: renderNoEventsInner, didMount: options.noEventsDidMount, willUnmount: options.noEventsWillUnmount }, function (rootElRef, classNames, innerElRef, innerContent) { return (createElement("div", { className: ['fc-list-empty'].concat(classNames).join(' '), ref: rootElRef }, + createElement("div", { className: "fc-list-empty-cushion", ref: innerElRef }, innerContent))); })); + }; + ListView.prototype.renderSegList = function (allSegs, dayDates) { + var _a = this.context, theme = _a.theme, options = _a.options; + var segsByDay = groupSegsByDay(allSegs); // sparse array + return (createElement(NowTimer, { unit: "day" }, function (nowDate, todayRange) { + var innerNodes = []; + for (var dayIndex = 0; dayIndex < segsByDay.length; dayIndex += 1) { + var daySegs = segsByDay[dayIndex]; + if (daySegs) { // sparse array, so might be undefined + var dayStr = dayDates[dayIndex].toISOString(); + // append a day header + innerNodes.push(createElement(ListViewHeaderRow, { key: dayStr, dayDate: dayDates[dayIndex], todayRange: todayRange })); + daySegs = sortEventSegs(daySegs, options.eventOrder); + for (var _i = 0, daySegs_1 = daySegs; _i < daySegs_1.length; _i++) { + var seg = daySegs_1[_i]; + innerNodes.push(createElement(ListViewEventRow, __assign({ key: dayStr + ':' + seg.eventRange.instance.instanceId /* are multiple segs for an instanceId */, seg: seg, isDragging: false, isResizing: false, isDateSelecting: false, isSelected: false }, getSegMeta(seg, todayRange, nowDate)))); + } + } + } + return (createElement("table", { className: 'fc-list-table ' + theme.getClass('table') }, + createElement("tbody", null, innerNodes))); + })); + }; + ListView.prototype._eventStoreToSegs = function (eventStore, eventUiBases, dayRanges) { + return this.eventRangesToSegs(sliceEventStore(eventStore, eventUiBases, this.props.dateProfile.activeRange, this.context.options.nextDayThreshold).fg, dayRanges); + }; + ListView.prototype.eventRangesToSegs = function (eventRanges, dayRanges) { + var segs = []; + for (var _i = 0, eventRanges_1 = eventRanges; _i < eventRanges_1.length; _i++) { + var eventRange = eventRanges_1[_i]; + segs.push.apply(segs, this.eventRangeToSegs(eventRange, dayRanges)); + } + return segs; + }; + ListView.prototype.eventRangeToSegs = function (eventRange, dayRanges) { + var dateEnv = this.context.dateEnv; + var nextDayThreshold = this.context.options.nextDayThreshold; + var range = eventRange.range; + var allDay = eventRange.def.allDay; + var dayIndex; + var segRange; + var seg; + var segs = []; + for (dayIndex = 0; dayIndex < dayRanges.length; dayIndex += 1) { + segRange = intersectRanges(range, dayRanges[dayIndex]); + if (segRange) { + seg = { + component: this, + eventRange: eventRange, + start: segRange.start, + end: segRange.end, + isStart: eventRange.isStart && segRange.start.valueOf() === range.start.valueOf(), + isEnd: eventRange.isEnd && segRange.end.valueOf() === range.end.valueOf(), + dayIndex: dayIndex, + }; + segs.push(seg); + // detect when range won't go fully into the next day, + // and mutate the latest seg to the be the end. + if (!seg.isEnd && !allDay && + dayIndex + 1 < dayRanges.length && + range.end < + dateEnv.add(dayRanges[dayIndex + 1].start, nextDayThreshold)) { + seg.end = range.end; + seg.isEnd = true; + break; + } + } + } + return segs; + }; + return ListView; + }(DateComponent)); + function renderNoEventsInner(hookProps) { + return hookProps.text; + } + function computeDateVars(dateProfile) { + var dayStart = startOfDay(dateProfile.renderRange.start); + var viewEnd = dateProfile.renderRange.end; + var dayDates = []; + var dayRanges = []; + while (dayStart < viewEnd) { + dayDates.push(dayStart); + dayRanges.push({ + start: dayStart, + end: addDays(dayStart, 1), + }); + dayStart = addDays(dayStart, 1); + } + return { dayDates: dayDates, dayRanges: dayRanges }; + } + // Returns a sparse array of arrays, segs grouped by their dayIndex + function groupSegsByDay(segs) { + var segsByDay = []; // sparse array + var i; + var seg; + for (i = 0; i < segs.length; i += 1) { + seg = segs[i]; + (segsByDay[seg.dayIndex] || (segsByDay[seg.dayIndex] = [])) + .push(seg); + } + return segsByDay; + } + + var OPTION_REFINERS$1 = { + listDayFormat: createFalsableFormatter, + listDaySideFormat: createFalsableFormatter, + noEventsClassNames: identity, + noEventsContent: identity, + noEventsDidMount: identity, + noEventsWillUnmount: identity, + // noEventsText is defined in base options + }; + function createFalsableFormatter(input) { + return input === false ? null : createFormatter(input); + } + + var listPlugin = createPlugin({ + optionRefiners: OPTION_REFINERS$1, + views: { + list: { + component: ListView, + buttonTextKey: 'list', + listDayFormat: { month: 'long', day: 'numeric', year: 'numeric' }, // like "January 1, 2016" + }, + listDay: { + type: 'list', + duration: { days: 1 }, + listDayFormat: { weekday: 'long' }, // day-of-week is all we need. full date is probably in headerToolbar + }, + listWeek: { + type: 'list', + duration: { weeks: 1 }, + listDayFormat: { weekday: 'long' }, + listDaySideFormat: { month: 'long', day: 'numeric', year: 'numeric' }, + }, + listMonth: { + type: 'list', + duration: { month: 1 }, + listDaySideFormat: { weekday: 'long' }, // day-of-week is nice-to-have + }, + listYear: { + type: 'list', + duration: { year: 1 }, + listDaySideFormat: { weekday: 'long' }, // day-of-week is nice-to-have + }, + }, + }); + + var BootstrapTheme = /** @class */ (function (_super) { + __extends(BootstrapTheme, _super); + function BootstrapTheme() { + return _super !== null && _super.apply(this, arguments) || this; + } + return BootstrapTheme; + }(Theme)); + BootstrapTheme.prototype.classes = { + root: 'fc-theme-bootstrap', + table: 'table-bordered', + tableCellShaded: 'table-active', + buttonGroup: 'btn-group', + button: 'btn btn-primary', + buttonActive: 'active', + popover: 'popover', + popoverHeader: 'popover-header', + popoverContent: 'popover-body', + }; + BootstrapTheme.prototype.baseIconClass = 'fa'; + BootstrapTheme.prototype.iconClasses = { + close: 'fa-times', + prev: 'fa-chevron-left', + next: 'fa-chevron-right', + prevYear: 'fa-angle-double-left', + nextYear: 'fa-angle-double-right', + }; + BootstrapTheme.prototype.rtlIconClasses = { + prev: 'fa-chevron-right', + next: 'fa-chevron-left', + prevYear: 'fa-angle-double-right', + nextYear: 'fa-angle-double-left', + }; + BootstrapTheme.prototype.iconOverrideOption = 'bootstrapFontAwesome'; // TODO: make TS-friendly. move the option-processing into this plugin + BootstrapTheme.prototype.iconOverrideCustomButtonOption = 'bootstrapFontAwesome'; + BootstrapTheme.prototype.iconOverridePrefix = 'fa-'; + var plugin = createPlugin({ + themeClasses: { + bootstrap: BootstrapTheme, + }, + }); + + // rename this file to options.ts like other packages? + var OPTION_REFINERS = { + googleCalendarApiKey: String, + }; + + var EVENT_SOURCE_REFINERS = { + googleCalendarApiKey: String, + googleCalendarId: String, + googleCalendarApiBase: String, + extraParams: identity, + }; + + // TODO: expose somehow + var API_BASE = 'https://www.googleapis.com/calendar/v3/calendars'; + var eventSourceDef = { + parseMeta: function (refined) { + var googleCalendarId = refined.googleCalendarId; + if (!googleCalendarId && refined.url) { + googleCalendarId = parseGoogleCalendarId(refined.url); + } + if (googleCalendarId) { + return { + googleCalendarId: googleCalendarId, + googleCalendarApiKey: refined.googleCalendarApiKey, + googleCalendarApiBase: refined.googleCalendarApiBase, + extraParams: refined.extraParams, + }; + } + return null; + }, + fetch: function (arg, onSuccess, onFailure) { + var _a = arg.context, dateEnv = _a.dateEnv, options = _a.options; + var meta = arg.eventSource.meta; + var apiKey = meta.googleCalendarApiKey || options.googleCalendarApiKey; + if (!apiKey) { + onFailure({ + message: 'Specify a googleCalendarApiKey. See http://fullcalendar.io/docs/google_calendar/', + }); + } + else { + var url = buildUrl(meta); + // TODO: make DRY with json-feed-event-source + var extraParams = meta.extraParams; + var extraParamsObj = typeof extraParams === 'function' ? extraParams() : extraParams; + var requestParams_1 = buildRequestParams(arg.range, apiKey, extraParamsObj, dateEnv); + requestJson('GET', url, requestParams_1, function (body, xhr) { + if (body.error) { + onFailure({ + message: 'Google Calendar API: ' + body.error.message, + errors: body.error.errors, + xhr: xhr, + }); + } + else { + onSuccess({ + rawEvents: gcalItemsToRawEventDefs(body.items, requestParams_1.timeZone), + xhr: xhr, + }); + } + }, function (message, xhr) { + onFailure({ message: message, xhr: xhr }); + }); + } + }, + }; + function parseGoogleCalendarId(url) { + var match; + // detect if the ID was specified as a single string. + // will match calendars like "asdf1234@calendar.google.com" in addition to person email calendars. + if (/^[^/]+@([^/.]+\.)*(google|googlemail|gmail)\.com$/.test(url)) { + return url; + } + if ((match = /^https:\/\/www.googleapis.com\/calendar\/v3\/calendars\/([^/]*)/.exec(url)) || + (match = /^https?:\/\/www.google.com\/calendar\/feeds\/([^/]*)/.exec(url))) { + return decodeURIComponent(match[1]); + } + return null; + } + function buildUrl(meta) { + var apiBase = meta.googleCalendarApiBase; + if (!apiBase) { + apiBase = API_BASE; + } + return apiBase + '/' + encodeURIComponent(meta.googleCalendarId) + '/events'; + } + function buildRequestParams(range, apiKey, extraParams, dateEnv) { + var params; + var startStr; + var endStr; + if (dateEnv.canComputeOffset) { + // strings will naturally have offsets, which GCal needs + startStr = dateEnv.formatIso(range.start); + endStr = dateEnv.formatIso(range.end); + } + else { + // when timezone isn't known, we don't know what the UTC offset should be, so ask for +/- 1 day + // from the UTC day-start to guarantee we're getting all the events + // (start/end will be UTC-coerced dates, so toISOString is okay) + startStr = addDays(range.start, -1).toISOString(); + endStr = addDays(range.end, 1).toISOString(); + } + params = __assign(__assign({}, (extraParams || {})), { key: apiKey, timeMin: startStr, timeMax: endStr, singleEvents: true, maxResults: 9999 }); + if (dateEnv.timeZone !== 'local') { + params.timeZone = dateEnv.timeZone; + } + return params; + } + function gcalItemsToRawEventDefs(items, gcalTimezone) { + return items.map(function (item) { return gcalItemToRawEventDef(item, gcalTimezone); }); + } + function gcalItemToRawEventDef(item, gcalTimezone) { + var url = item.htmlLink || null; + // make the URLs for each event show times in the correct timezone + if (url && gcalTimezone) { + url = injectQsComponent(url, 'ctz=' + gcalTimezone); + } + return { + id: item.id, + title: item.summary, + start: item.start.dateTime || item.start.date, + end: item.end.dateTime || item.end.date, + url: url, + location: item.location, + description: item.description, + attachments: item.attachments || [], + extendedProps: (item.extendedProperties || {}).shared || {}, + }; + } + // Injects a string like "arg=value" into the querystring of a URL + // TODO: move to a general util file? + function injectQsComponent(url, component) { + // inject it after the querystring but before the fragment + return url.replace(/(\?.*?)?(#|$)/, function (whole, qs, hash) { return (qs ? qs + '&' : '?') + component + hash; }); + } + var googleCalendarPlugin = createPlugin({ + eventSourceDefs: [eventSourceDef], + optionRefiners: OPTION_REFINERS, + eventSourceRefiners: EVENT_SOURCE_REFINERS, + }); + + globalPlugins.push(interactionPlugin, dayGridPlugin, timeGridPlugin, listPlugin, plugin, googleCalendarPlugin); + + exports.BASE_OPTION_DEFAULTS = BASE_OPTION_DEFAULTS; + exports.BASE_OPTION_REFINERS = BASE_OPTION_REFINERS; + exports.BaseComponent = BaseComponent; + exports.BgEvent = BgEvent; + exports.BootstrapTheme = BootstrapTheme; + exports.Calendar = Calendar; + exports.CalendarApi = CalendarApi; + exports.CalendarContent = CalendarContent; + exports.CalendarDataManager = CalendarDataManager; + exports.CalendarDataProvider = CalendarDataProvider; + exports.CalendarRoot = CalendarRoot; + exports.Component = Component; + exports.ContentHook = ContentHook; + exports.CustomContentRenderContext = CustomContentRenderContext; + exports.DateComponent = DateComponent; + exports.DateEnv = DateEnv; + exports.DateProfileGenerator = DateProfileGenerator; + exports.DayCellContent = DayCellContent; + exports.DayCellRoot = DayCellRoot; + exports.DayGridView = DayTableView; + exports.DayHeader = DayHeader; + exports.DaySeriesModel = DaySeriesModel; + exports.DayTable = DayTable; + exports.DayTableModel = DayTableModel; + exports.DayTableSlicer = DayTableSlicer; + exports.DayTimeCols = DayTimeCols; + exports.DayTimeColsSlicer = DayTimeColsSlicer; + exports.DayTimeColsView = DayTimeColsView; + exports.DelayedRunner = DelayedRunner; + exports.Draggable = ExternalDraggable; + exports.ElementDragging = ElementDragging; + exports.ElementScrollController = ElementScrollController; + exports.Emitter = Emitter; + exports.EventApi = EventApi; + exports.EventRoot = EventRoot; + exports.EventSourceApi = EventSourceApi; + exports.FeaturefulElementDragging = FeaturefulElementDragging; + exports.Fragment = Fragment; + exports.Interaction = Interaction; + exports.ListView = ListView; + exports.MoreLinkRoot = MoreLinkRoot; + exports.MountHook = MountHook; + exports.NamedTimeZoneImpl = NamedTimeZoneImpl; + exports.NowIndicatorRoot = NowIndicatorRoot; + exports.NowTimer = NowTimer; + exports.PointerDragging = PointerDragging; + exports.PositionCache = PositionCache; + exports.RefMap = RefMap; + exports.RenderHook = RenderHook; + exports.ScrollController = ScrollController; + exports.ScrollResponder = ScrollResponder; + exports.Scroller = Scroller; + exports.SegHierarchy = SegHierarchy; + exports.SimpleScrollGrid = SimpleScrollGrid; + exports.Slicer = Slicer; + exports.Splitter = Splitter; + exports.StandardEvent = StandardEvent; + exports.Table = Table; + exports.TableDateCell = TableDateCell; + exports.TableDowCell = TableDowCell; + exports.TableView = TableView; + exports.Theme = Theme; + exports.ThirdPartyDraggable = ThirdPartyDraggable; + exports.TimeCols = TimeCols; + exports.TimeColsSlatsCoords = TimeColsSlatsCoords; + exports.TimeColsView = TimeColsView; + exports.ViewApi = ViewApi; + exports.ViewContextType = ViewContextType; + exports.ViewRoot = ViewRoot; + exports.WeekNumberRoot = WeekNumberRoot; + exports.WindowScrollController = WindowScrollController; + exports.addDays = addDays; + exports.addDurations = addDurations; + exports.addMs = addMs; + exports.addWeeks = addWeeks; + exports.allowContextMenu = allowContextMenu; + exports.allowSelection = allowSelection; + exports.applyMutationToEventStore = applyMutationToEventStore; + exports.applyStyle = applyStyle; + exports.applyStyleProp = applyStyleProp; + exports.asCleanDays = asCleanDays; + exports.asRoughMinutes = asRoughMinutes; + exports.asRoughMs = asRoughMs; + exports.asRoughSeconds = asRoughSeconds; + exports.binarySearch = binarySearch; + exports.buildClassNameNormalizer = buildClassNameNormalizer; + exports.buildDayRanges = buildDayRanges; + exports.buildDayTableModel = buildDayTableModel; + exports.buildEntryKey = buildEntryKey; + exports.buildEventApis = buildEventApis; + exports.buildEventRangeKey = buildEventRangeKey; + exports.buildHashFromArray = buildHashFromArray; + exports.buildIsoString = buildIsoString; + exports.buildNavLinkData = buildNavLinkData; + exports.buildSegCompareObj = buildSegCompareObj; + exports.buildSegTimeText = buildSegTimeText; + exports.buildSlatMetas = buildSlatMetas; + exports.buildTimeColsModel = buildTimeColsModel; + exports.collectFromHash = collectFromHash; + exports.combineEventUis = combineEventUis; + exports.compareByFieldSpec = compareByFieldSpec; + exports.compareByFieldSpecs = compareByFieldSpecs; + exports.compareNumbers = compareNumbers; + exports.compareObjs = compareObjs; + exports.computeEarliestSegStart = computeEarliestSegStart; + exports.computeEdges = computeEdges; + exports.computeFallbackHeaderFormat = computeFallbackHeaderFormat; + exports.computeHeightAndMargins = computeHeightAndMargins; + exports.computeInnerRect = computeInnerRect; + exports.computeRect = computeRect; + exports.computeSegDraggable = computeSegDraggable; + exports.computeSegEndResizable = computeSegEndResizable; + exports.computeSegStartResizable = computeSegStartResizable; + exports.computeShrinkWidth = computeShrinkWidth; + exports.computeSmallestCellWidth = computeSmallestCellWidth; + exports.computeVisibleDayRange = computeVisibleDayRange; + exports.config = config; + exports.constrainPoint = constrainPoint; + exports.createContext = createContext; + exports.createDuration = createDuration; + exports.createElement = createElement; + exports.createEmptyEventStore = createEmptyEventStore; + exports.createEventInstance = createEventInstance; + exports.createEventUi = createEventUi; + exports.createFormatter = createFormatter; + exports.createPlugin = createPlugin; + exports.createPortal = createPortal; + exports.createRef = createRef; + exports.diffDates = diffDates; + exports.diffDayAndTime = diffDayAndTime; + exports.diffDays = diffDays; + exports.diffPoints = diffPoints; + exports.diffWeeks = diffWeeks; + exports.diffWholeDays = diffWholeDays; + exports.diffWholeWeeks = diffWholeWeeks; + exports.disableCursor = disableCursor; + exports.elementClosest = elementClosest; + exports.elementMatches = elementMatches; + exports.enableCursor = enableCursor; + exports.eventTupleToStore = eventTupleToStore; + exports.filterEventStoreDefs = filterEventStoreDefs; + exports.filterHash = filterHash; + exports.findDirectChildren = findDirectChildren; + exports.findElements = findElements; + exports.flexibleCompare = flexibleCompare; + exports.flushToDom = flushToDom; + exports.formatDate = formatDate; + exports.formatDayString = formatDayString; + exports.formatIsoTimeString = formatIsoTimeString; + exports.formatRange = formatRange; + exports.getAllowYScrolling = getAllowYScrolling; + exports.getCanVGrowWithinCell = getCanVGrowWithinCell; + exports.getClippingParents = getClippingParents; + exports.getDateMeta = getDateMeta; + exports.getDayClassNames = getDayClassNames; + exports.getDefaultEventEnd = getDefaultEventEnd; + exports.getElRoot = getElRoot; + exports.getElSeg = getElSeg; + exports.getEntrySpanEnd = getEntrySpanEnd; + exports.getEventClassNames = getEventClassNames; + exports.getEventTargetViaRoot = getEventTargetViaRoot; + exports.getIsRtlScrollbarOnLeft = getIsRtlScrollbarOnLeft; + exports.getRectCenter = getRectCenter; + exports.getRelevantEvents = getRelevantEvents; + exports.getScrollGridClassNames = getScrollGridClassNames; + exports.getScrollbarWidths = getScrollbarWidths; + exports.getSectionClassNames = getSectionClassNames; + exports.getSectionHasLiquidHeight = getSectionHasLiquidHeight; + exports.getSegMeta = getSegMeta; + exports.getSlotClassNames = getSlotClassNames; + exports.getStickyFooterScrollbar = getStickyFooterScrollbar; + exports.getStickyHeaderDates = getStickyHeaderDates; + exports.getUnequalProps = getUnequalProps; + exports.globalLocales = globalLocales; + exports.globalPlugins = globalPlugins; + exports.greatestDurationDenominator = greatestDurationDenominator; + exports.groupIntersectingEntries = groupIntersectingEntries; + exports.guid = guid; + exports.hasBgRendering = hasBgRendering; + exports.hasShrinkWidth = hasShrinkWidth; + exports.identity = identity; + exports.interactionSettingsStore = interactionSettingsStore; + exports.interactionSettingsToStore = interactionSettingsToStore; + exports.intersectRanges = intersectRanges; + exports.intersectRects = intersectRects; + exports.intersectSpans = intersectSpans; + exports.isArraysEqual = isArraysEqual; + exports.isColPropsEqual = isColPropsEqual; + exports.isDateSelectionValid = isDateSelectionValid; + exports.isDateSpansEqual = isDateSpansEqual; + exports.isInt = isInt; + exports.isInteractionValid = isInteractionValid; + exports.isMultiDayRange = isMultiDayRange; + exports.isPropsEqual = isPropsEqual; + exports.isPropsValid = isPropsValid; + exports.isValidDate = isValidDate; + exports.joinSpans = joinSpans; + exports.listenBySelector = listenBySelector; + exports.mapHash = mapHash; + exports.memoize = memoize; + exports.memoizeArraylike = memoizeArraylike; + exports.memoizeHashlike = memoizeHashlike; + exports.memoizeObjArg = memoizeObjArg; + exports.mergeEventStores = mergeEventStores; + exports.multiplyDuration = multiplyDuration; + exports.padStart = padStart; + exports.parseBusinessHours = parseBusinessHours; + exports.parseClassNames = parseClassNames; + exports.parseDragMeta = parseDragMeta; + exports.parseEventDef = parseEventDef; + exports.parseFieldSpecs = parseFieldSpecs; + exports.parseMarker = parse; + exports.pointInsideRect = pointInsideRect; + exports.preventContextMenu = preventContextMenu; + exports.preventDefault = preventDefault; + exports.preventSelection = preventSelection; + exports.rangeContainsMarker = rangeContainsMarker; + exports.rangeContainsRange = rangeContainsRange; + exports.rangesEqual = rangesEqual; + exports.rangesIntersect = rangesIntersect; + exports.refineEventDef = refineEventDef; + exports.refineProps = refineProps; + exports.removeElement = removeElement; + exports.removeExact = removeExact; + exports.render = render; + exports.renderChunkContent = renderChunkContent; + exports.renderFill = renderFill; + exports.renderMicroColGroup = renderMicroColGroup; + exports.renderScrollShim = renderScrollShim; + exports.requestJson = requestJson; + exports.sanitizeShrinkWidth = sanitizeShrinkWidth; + exports.setElSeg = setElSeg; + exports.setRef = setRef; + exports.sliceEventStore = sliceEventStore; + exports.sliceEvents = sliceEvents; + exports.sortEventSegs = sortEventSegs; + exports.startOfDay = startOfDay; + exports.translateRect = translateRect; + exports.triggerDateSelect = triggerDateSelect; + exports.unmountComponentAtNode = unmountComponentAtNode; + exports.unpromisify = unpromisify; + exports.version = version; + exports.whenTransitionDone = whenTransitionDone; + exports.wholeDivideDurations = wholeDivideDurations; + + Object.defineProperty(exports, '__esModule', { value: true }); + + return exports; + +}({})); diff --git a/apps/schoolCalender/interface.html b/apps/schoolCalender/interface.html new file mode 100644 index 000000000..3b626c5e9 --- /dev/null +++ b/apps/schoolCalender/interface.html @@ -0,0 +1,60 @@ + + + + + + + + + + + +
+ + diff --git a/apps/schoolCalender/schoolCalender.js b/apps/schoolCalender/schoolCalender.js new file mode 100644 index 000000000..be4c45d45 --- /dev/null +++ b/apps/schoolCalender/schoolCalender.js @@ -0,0 +1,229 @@ +require("FontTeletext5x9Mode7").add(Graphics); +Bangle.setLCDMode(); + +function getBackgroundImage() { + return require("heatshrink").decompress(atob("gMwyEgBAsAgQBCgcAggBCgsAgwBCg8AhABChMAhQBChcAhgBChsAhwBCh8AiEAiIBCiUAiYBCikAioBCi0Ai4BCjEAjIBCjUAjYBCjkAjoBCj0Aj4BBA")); +} + +Graphics.prototype.setFontAudiowide = function() { + // Actual height 33 (36 - 4) + var widths = atob("BxYfDBkYGhkZFRkZCA=="); + var font = atob("AAAAAAAAA8AAAAHgAAAB8AAAAHgAAAA4AAAAAAAAAAEAAAABgAAAA8AAAAPgAAAH8AAAB/gAAA/4AAAf+AAAH/AAAD/wAAA/4AAAf8AAAP/AAAD/gAAB/4AAAf8AAAD+AAAAfgAAADwAAAAcAAAAAAAAAAAAAAAAAAAAAAP/AAAH//AAB//8AAf//wAH///AA///4APwD/gB8A/8APgP/gB8D98AfAfvgD4H58AfB/PgD4Px8AfD8PgD4/h8AfH4PgD5+B8AP/wPgB/8B8AP/APgB/4D8AH8B/AA///4AD//+AAP//gAA//4AAB/8AAAAAAAAAAAAAAAAAAD4AAAAfAAAAD4AAAAfAAAAD///8Af///gD///8Af///gD///8AAAAAAAAAAAAAAAAAAAA/8AAAf/gD4H/8AfA//gD4P/8AfB+PgD4Ph8AfB8PgD4Ph8AfB8PgD4Ph8AfB8PgD4Ph8AfB8PgD4Ph8AfB8PgB8Ph8AP/8PgB//B8AP/4PgA/+B8AD/gPgABgA8AAAAAAAAAAAAPA4HgD4Ph8AfB8PgD4Ph8AfB8PgD4Ph8AfB8PgD4Ph8AfB8PgD4Ph8AfB8PgD4Ph8AfB8PgD4Ph8AfB8PgB8Ph8AP///gB///8AH///AA///wAB//8AAAAAAAAAAAAB/8AAAf/4AAD//gAAf/8AAD//gAAf/8AAAAPgAAAB8AAAAPgAAAB8AAAAPgAAAB8AAAAPgAAAB8AAAAPgAAAB8AAAAPgAAP///gD///8Af///gD///8Af///gD///8AAAAAAAAAAAAAAAAAAAAAAAAAAAAD/8B8Af/wPgD//B8Af/4PgD//h8AfB8PgD4Ph8AfB8PgD4Ph8AfB8PgD4Ph8AfB8PgD4Ph8AfB8PgD4Ph8AfB8PgD4Ph8AfB//gD4H/8AfA//AAAD/4AAAP8AAAAAAAAAAAAAH//AAB//8AAf//wAH///AB///8AP58/gB8Ph8APh8PgD4Ph8AfB8PgD4Ph8AfB8PgD4Ph8AfB8PgD4Ph8AfB8PgD4Ph8AfB//gD4H/8AAA//AAAD/4AAAP8AAAAAAAAAAAAD4AAAAfAAAAD4AAAAfAABgD4AA8AfAAPgD4AH8AfAD/gD4A/8AfAf/AD4P/gAfH/wAD5/8AAf/+AAD//AAAf/gAAD/4AAAf8AAAB+AAAAPAAAAAAAAAAAAAAAAAB/gAAAf+AAP//4AH///gA///8AP/+PgB//h8APh8PgD4Ph8AfB8PgD4Ph8AfB8PgD4Ph8AfB8PgB8Ph8AP/8PgB//h8AP///gA///8AD///AADz/4AAAP8AAAAMAAAAAAAAAAAAAB/gAAA/+AAAH/4AAB//B8AP/8PgB8Ph8AfB8PgD4Ph8AfB8PgD4Ph8AfB8PgD4Ph8AfB8PgD4Ph8APh8PgB8Ph8APx8fgB///8AH///AAf//wAD//8AAH//AAAAAAAAAAAAAAAAAAAAAAAAAOAHgAD4A8AAfAPgAD4A8AAOAHAAAAAAA=="); + var scale = 1; // size multiplier for this font + g.setFontCustom(font, 46, widths, 33+(scale<<8)+(1<<16)); +}; + +function logDebug(message){ + //console.log(message); +} + +var NEXTCLASS = 4; +var CURRRENTCLASS = 3; +var NEXTNEXTCLASS = 5; +var BEHINDCLASS = 2; +var BEHINDBEHINDCLASS = 1; +var NEXTNEXTNEXTCLASS = 6; +var stage = 3; + +function drawInfo(){ + var currentDate = new Date(); + var currentDayOfWeek = currentDate.getDay(); + var currentHour = currentDate.getHours(); + var currentMinute = currentDate.getMinutes(); + var currentMinuteUpdated; + var currentHourUpdated; + if (currentMinute<10){ + currentMinuteUpdated = "0"+currentMinute; + }else{ + currentMinuteUpdated = currentMinute; + }if(currentHour >= 13){ + currentHourUpdated = currentHour-12; + }else{ + currentHourUpdated = currentHour; + } + for(var i = 0;i<=240;i++){ + g.drawImage(getBackgroundImage(),i,120,{scale:5,rotate:0}); + } + g.setColor(255,255,255); + g.setFont("Audiowide"); + g.drawString(currentHourUpdated+":"+currentMinuteUpdated, 145, 16); + g.setFont("Teletext5x9Mode7", 2); + foundClass = processDay(); + if (foundClass.startingTimeMinute<10){ + classMinuteUpdated = "0"+foundClass.startingTimeMinute; + }else{ + classMinuteUpdated = foundClass.startingTimeMinute; + } + if (foundClass.endingTimeMinute<10){ + classEndingMinuteUpdated = "0"+foundClass.endingTimeMinute; + }else{ + classEndingMinuteUpdated = foundClass.endingTimeMinute; + }if(foundClass.startingTimeHour >= 13){ + classHourUpdated = foundClass.startingTimeHour-12; + }else{ + classHourUpdated = foundClass.startingTimeHour; + }if(foundClass.endingTimeHour >= 13){ + classEndingHourUpdated = foundClass.endingTimeHour-12; + }else{ + classEndingHourUpdated = foundClass.endingTimeHour; + } + switch (foundClass.dayOfWeek) { + case 0: + updatedDay = "Sun"; + break; + case 1: + updatedDay = "Mon"; + break; + case 2: + updatedDay = "Tue"; + break; + case 3: + updatedDay = "Wed"; + break; + case 4: + updatedDay = "Thur"; + break; + case 5: + updatedDay = "Fri"; + break; + case 6: + updatedDay = "Sat"; +} + if (foundClass != null) { + g.drawString(classHourUpdated+":"+classMinuteUpdated+" - "+classEndingHourUpdated+":"+classEndingMinuteUpdated+" "+updatedDay, 25, 50); + g.drawString(foundClass.className, 25, 80); + g.drawString(foundClass.teacher, 25, 110); + g.drawString(foundClass.roomNumber, 25, 140); + } +} +setInterval(drawInfo, 60000); + +function processDay(){ + let schedule = [ + //Sunday + + //Monday: + {className: "Biblical Theology", dayOfWeek:1, startingTimeHour: 8, startingTimeMinute: 10, endingTimeHour:9, endingTimeMinute: 5, description:"Biblical Theology 7B 3B Mr. Besaw Block 3B M207", roomNumber:"207", teacher:"Mr. Besaw"}, + {className: "English", dayOfWeek:1, startingTimeHour: 9, startingTimeMinute: 5, endingTimeHour:10, endingTimeMinute: 0, description:"English 7B 4B Dr. Wong Block 4B M206", teacher:"Dr. Wong"}, + {className: "Break", dayOfWeek:1, startingTimeHour: 10, startingTimeMinute: 0, endingTimeHour:10, endingTimeMinute: 10, description:"Break MF MS", teacher:""}, + {className: "MS Robotics", dayOfWeek:1, startingTimeHour: 10, startingTimeMinute: 10, endingTimeHour:11, endingTimeMinute: 0, description:"MS Robotics S1A Mr. Broyles MS MF Elective Block A M211", roomNumber:"211", teacher:"Mr. Broyles"}, + {className: "MS Physical Education Boys", dayOfWeek:1, startingTimeHour: 11, startingTimeMinute: 0, endingTimeHour:11, endingTimeMinute: 50, description:"MS Physical Education Boys S1B Mr. Mendezona MS MF Elective Block B Gym", roomNumber:"GYM", teacher:"Mr. Mendezona"}, + {className: "Office Hours Besaw/Nunez", dayOfWeek:1, startingTimeHour: 11, startingTimeMinute: 50, endingTimeHour:12, endingTimeMinute: 25, description:"Office Hours Besaw/Nunez Mr. Besaw, Dr. Nunez, Mrs.McDonough, Mr. Pettit Office Hours MF MS M203", roomNumber:"203", teacher:"Besaw/Nunez"}, + {className: "Lunch", dayOfWeek:1, startingTimeHour: 12, startingTimeMinute: 25, endingTimeHour:12, endingTimeMinute: 50, description:"Lunch MF MS", roomNumber:"Commence or Advisory", teacher:""}, + {className: "Activity Period", dayOfWeek:1, startingTimeHour: 12, startingTimeMinute: 50, endingTimeHour:13, endingTimeMinute: 0, description:"Activity Period MF MS", roomNumber:"Outside", teacher:""}, + {className: "Latin", dayOfWeek:1, startingTimeHour: 13, startingTimeMinute: 5, endingTimeHour:14, endingTimeMinute: 0, description:"Latin 7B 5B Mrs. Scrivner Block 5B M208", roomNumber:"208", teacher:"Mrs.Scrivner"}, + {className: "Algebra 1", dayOfWeek:1, startingTimeHour: 14, startingTimeMinute: 0, endingTimeHour:15, endingTimeMinute: 0, description:"Algebra 1 7B 6B Mr. Benson Block 6B M204", roomNumber:"204", teacher:"Mr. Benson"}, + + //Tuesday: + {className: "Logic", dayOfWeek:2, startingTimeHour: 8, startingTimeMinute: 10, endingTimeHour:9, endingTimeMinute: 0, description:"Logic 7B 5B Mrs. Scrivner Block 5B M208", roomNumber:"208", teacher:"Mrs.Scrivner"}, + {className: "Algebra 1", dayOfWeek:2, startingTimeHour: 9, startingTimeMinute: 0, endingTimeHour:10, endingTimeMinute: 0, description:"Algebra 1 7B 6B Mr. Benson Block 6B M204", roomNumber:"204", teacher:"Mr. Benson"}, + {className: "Chapel", dayOfWeek:2, startingTimeHour: 10, startingTimeMinute: 0, endingTimeHour:10, endingTimeMinute: 25, description:"Chapel MF MS", roomNumber:"Advisory", teacher:""}, + {className: "Break", dayOfWeek:2, startingTimeHour: 10, startingTimeMinute: 25, endingTimeHour:10, endingTimeMinute: 35, description:"Break MF MS", roomNumber:"Outside", teacher:""}, + {className: "Advisory Besaw", dayOfWeek:2, startingTimeHour: 10, startingTimeMinute: 35, endingTimeHour:11, endingTimeMinute: 0, description:"Advisory Besaw Mr. Besaw Advisory MF MS M207", roomNumber:"207", teacher:"Mr. Besaw"}, + {className: "MS Robotics", dayOfWeek:2, startingTimeHour: 11, startingTimeMinute: 0, endingTimeHour:11, endingTimeMinute: 50, description:"MS Robotics S1A Mr. Broyles MS MF Elective Block A M211", roomNumber:"211", teacher:"Mr. Broyles"}, + {className: "Office Hours Besaw/Nunez", dayOfWeek:2, startingTimeHour: 11, startingTimeMinute: 50, endingTimeHour:12, endingTimeMinute: 25, description:"Office Hours Besaw/Nunez Mr. Besaw, Dr. Nunez, Mrs.McDonough, Mr. Pettit Office Hours MF MS M203", roomNumber:"203", teacher:" Besaw/Nunez"}, + {className: "Lunch", dayOfWeek:2, startingTimeHour: 12, startingTimeMinute: 25, endingTimeHour:12, endingTimeMinute: 50, description:"Lunch MF MS", roomNumber:"Commence or Advisory", teacher:""}, + {className: "Activity Period", dayOfWeek:2, startingTimeHour: 12, startingTimeMinute: 50, endingTimeHour:13, endingTimeMinute: 5, description:"Activity Period MF MS", roomNumber:"Outside", teacher:""}, + {className: "Medieval Western Civilization", dayOfWeek:2, startingTimeHour: 13, startingTimeMinute: 5, endingTimeHour:14, endingTimeMinute: 0, description:"Medieval Western Civilization 7B 1B Mr. Kuhle Block 1BM205", roomNumber:"205", teacher:"Mr. Khule"}, + {className: "Introductory Biology and Epidemiology", dayOfWeek:2, startingTimeHour: 14, startingTimeMinute: 0, endingTimeHour:15, endingTimeMinute: 0, description:"Introductory Biology and Epidemiology 7B 2B Mrs. Brown Block 2B M202", roomNumber:"202", teacher:"Mrs. Brown"}, + + //Wensday: + {className: "English", dayOfWeek:3, startingTimeHour: 9, startingTimeMinute: 0, endingTimeHour:9, endingTimeMinute: 55, description:"English 7B 4B Dr. Wong Block 4B M206", roomNumber:"206", teacher:"Dr. Wong"}, + {className: "Biblical Theology", dayOfWeek:3, startingTimeHour: 9, startingTimeMinute: 55, endingTimeHour:10, endingTimeMinute: 50, description:"Biblical Theology 7B 3B Mr. Besaw Block 3B M207", roomNumber:"207", teacher:"Mr. Besaw"}, + {className: "Break", dayOfWeek:3, startingTimeHour: 10, startingTimeMinute: 50, endingTimeHour:11, endingTimeMinute: 0, description:"Break MF MS", roomNumber:"Outside", teacher:""}, + {className: "MS Physical Education Boys", dayOfWeek:3, startingTimeHour: 11, startingTimeMinute: 0, endingTimeHour:11, endingTimeMinute: 50, description:"MS Physical Education Boys S1B Mr. Mendezona MS MF Elective Block B Gym", roomNumber:"GYM", teacher:"Mr. Mendezona"}, + {className: "Office Hours Besaw/Nunez", dayOfWeek:3, startingTimeHour: 11, startingTimeMinute: 50, endingTimeHour:12, endingTimeMinute: 25, description:"Office Hours Besaw/Nunez Mr. Besaw, Dr. Nunez, Mrs.McDonough, Mr. Pettit Office Hours MF MS M203", roomNumber:"203", teacher:" Besaw/Nunez"}, + {className: "Lunch", dayOfWeek:3, startingTimeHour: 12, startingTimeMinute: 25, endingTimeHour:12, endingTimeMinute: 50, description:"Lunch MF MS", roomNumber:"Commence or Advisory", teacher:""}, + {className: "Activity Period", dayOfWeek:2, startingTimeHour: 12, startingTimeMinute: 50, endingTimeHour:13, endingTimeMinute: 0, description:"Activity Period MF MS", roomNumber:"Outside", teacher:""}, + {className: "Introductory Biology and Epidemiology", dayOfWeek:3, startingTimeHour: 13, startingTimeMinute: 0, endingTimeHour:14, endingTimeMinute: 0, description:"Introductory Biology and Epidemiology 7B 2B Mrs. Brown Block 2B M202", roomNumber:"202", teacher:"Mrs. Brown"}, + {className: "Medieval Western Civilization", dayOfWeek:3, startingTimeHour: 14, startingTimeMinute: 0, endingTimeHour:15, endingTimeMinute: 0, description:"Medieval Western Civilization 7B 1B Mr. Kuhle Block 1B M205", roomNumber:"205", teacher:"Mr. Khule"}, + + //Thursday: + {className: "Algebra 1", dayOfWeek:4, startingTimeHour: 8, startingTimeMinute: 10, endingTimeHour:9, endingTimeMinute: 5, description:"Algebra 1 7B 6B Mr. Benson Block 6B M204", roomNumber:"204", teacher:"Mr. Benson"}, + {className: "Latin", dayOfWeek:4, startingTimeHour: 9, startingTimeMinute: 5, endingTimeHour:10, endingTimeMinute: 0, description:"Latin 7B 5B Mrs. Scrivner Block 5B M208", roomNumber:"208", teacher:"Mrs.Scrivner"}, + {className: "Break", dayOfWeek:4, startingTimeHour: 10, startingTimeMinute: 0, endingTimeHour:10, endingTimeMinute: 10, description:"Break MF MS", roomNumber:"Outside", teacher:""}, + {className: "MS Robotics", dayOfWeek:4, startingTimeHour: 10, startingTimeMinute: 10, endingTimeHour:11, endingTimeMinute: 0, description:"MS Robotics S1A Mr. Broyles MS MF Elective Block A M211", roomNumber:"211", teacher:"Mr. Broyles"}, + {className: "Advisory Besaw", dayOfWeek:4, startingTimeHour: 11, startingTimeMinute: 50, endingTimeHour:12, endingTimeMinute: 25, description:"Advisory Besaw Mr. Besaw Advisory MF MS M207", roomNumber:"207", teacher:"Mr. Besaw"}, + {className: "Lunch", dayOfWeek:4, startingTimeHour: 12, startingTimeMinute: 25, endingTimeHour:12, endingTimeMinute: 50, description:"Lunch MF MS", roomNumber:"Commence or Advisory", teacher:""}, + {className: "Activity Period", dayOfWeek:4, startingTimeHour: 12, startingTimeMinute: 50, endingTimeHour:13, endingTimeMinute: 0, description:"Activity Period MF MS", roomNumber:"Outside", teacher:""}, + {className: "Biblical Theology", dayOfWeek:4, startingTimeHour: 13, startingTimeMinute: 5, endingTimeHour:14, endingTimeMinute: 0, description:"Biblical Theology 7B 3B Mr. Besaw Block 3B M207", roomNumber:"207", teacher:"Mr. Besaw"}, + {className: "English", dayOfWeek:4, startingTimeHour: 14, startingTimeMinute: 0, endingTimeHour:15, endingTimeMinute: 0, description:"English 7B 4B Dr. Wong Block 4B M206", roomNumber:"206", teacher:"Dr. Wong"}, + + //Friday: + {className: "Medieval Western Civilization", dayOfWeek:5, startingTimeHour: 8, startingTimeMinute: 10, endingTimeHour:9, endingTimeMinute: 5, description:"Medieval Western Civilization 7B 1B Mr. Kuhle Block 1B M205", roomNumber:"205", teacher:"Mr. Khule"}, + {className: "Introductory Biology and Epidemiology", dayOfWeek:5, startingTimeHour: 9, startingTimeMinute: 5, endingTimeHour:10, endingTimeMinute: 0, description:"Introductory Biology and Epidemiology 7B 2B Mrs. Brown Block 2B M202", roomNumber:"202", teacher:"Mrs. Brown"}, + {className: "Break", dayOfWeek:5, startingTimeHour: 10, startingTimeMinute: 0, endingTimeHour:10, endingTimeMinute: 10, description:"Break MF MS", roomNumber:"Outside", teacher:""}, + {className: "MS Robotics", dayOfWeek:5, startingTimeHour: 10, startingTimeMinute: 10, endingTimeHour:11, endingTimeMinute: 0, description:"MS Robotics S1A Mr. Broyles MS MF Elective Block A M211", roomNumber:"211", teacher:"Mr. Broyles"}, + {className: "Office Hours Besaw/Nunez", dayOfWeek:5, startingTimeHour: 11, startingTimeMinute: 50, endingTimeHour:12, endingTimeMinute: 25, description:"Office Hours Besaw/Nunez Mr. Besaw, Dr. Nunez, Mrs.McDonough, Mr. Pettit Office Hours MF MS M203", roomNumber:"203", teacher:" Besaw/Nunez"}, + {className: "Lunch", dayOfWeek:5, startingTimeHour: 12, startingTimeMinute: 25, endingTimeHour:12, endingTimeMinute: 50, description:"Lunch MF MS", roomNumber:"Commence or Advisory", teacher:""}, + {className: "Activity Period", dayOfWeek:5, startingTimeHour: 12, startingTimeMinute: 50, endingTimeHour:13, endingTimeMinute: 0, description:"Activity Period MF MS", roomNumber:"Outside", teacher:""}, + {className: "Algebra 1", dayOfWeek:5, startingTimeHour: 13, startingTimeMinute: 5, endingTimeHour:14, endingTimeMinute: 0, description:"Algebra 1 7B 6B Mr. Benson Block 6B M204", roomNumber:"204", teacher:"Mr. Benson"}, + {className: "Logic", dayOfWeek:5, startingTimeHour: 14, startingTimeMinute: 0, endingTimeHour:15, endingTimeMinute: 0, description:"Logic 7B 5B Mrs. Scrivner Block 5B M208", roomNumber:"208", teacher:"Mrs.Scrivner"}, + + //Sataturday: + ]; + + var currentDate = new Date(); + var currentDayOfWeek = currentDate.getDay(); + var currentHour = currentDate.getHours(); + var currentMinute = currentDate.getMinutes(); + var minofDay = (currentHour*60)+currentMinute; + var i; + var currentPositon; + for(i = 0;i= (schedule[i].startingTimeHour*60+schedule[i].startingTimeMinute) && minofDay < (schedule[i].endingTimeHour*60+schedule[i].endingTimeMinute) ){ + console.log("Match:" + schedule[i].className); + console.log("stage:" + stage); + if(stage == 3){ + return schedule[i]; + }else if(stage == 4 && ++currentPositon <= schedule.length){ + return schedule[currentPositon]; + }else if(stage == 5 && (currentPositon+=2) <= schedule.length){ + return schedule[currentPositon]; + }else if(stage == 6 && (currentPositon+=3) <= schedule.length){ + return schedule[currentPositon]; + }else if(stage == 2 && (currentPositon-=1) <= schedule.length){ + return schedule[currentPositon]; + }else if(stage == 1 && (currentPositon-=2) <= schedule.length){ + return schedule[currentPositon]; + } + } + } + } + return null; +} + + +setWatch(() => { + if(stage<=1){ + }else{ + stage -= 1; + drawInfo(); + } +}, BTN1, {repeat:true}); + +setWatch(() => { +}, BTN2, {repeat:true}); + +setWatch(() => { + if(stage>=6){ + }else{ + stage += 1; + drawInfo(); + } +}, BTN3, {repeat:true}); + +setWatch(() => { + +}, BTN4, {repeat:true}); + +setWatch(() => { + +}, BTN5, {repeat:true}); + +drawInfo(); From cfde9e14a87466a8dc7cb92934c3af373f6a43d0 Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Thu, 16 Sep 2021 18:41:40 -0700 Subject: [PATCH 003/155] Update interface.html --- apps/schoolCalender/interface.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/schoolCalender/interface.html b/apps/schoolCalender/interface.html index 3b626c5e9..1761da640 100644 --- a/apps/schoolCalender/interface.html +++ b/apps/schoolCalender/interface.html @@ -11,7 +11,7 @@ headerToolbar: { left: 'prev,next today', center: 'title', - right: 'dayGridMonth,timeGridWeek,timeGridDay,listWeek' + right: 'timeGridWeek,listWeek' }, navLinks: true, // can click day/week names to navigate views editable: true, From bc71f317311509e8f3fd5054e6c3efaa05bc0416 Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Thu, 16 Sep 2021 18:47:13 -0700 Subject: [PATCH 004/155] Update interface.html --- apps/schoolCalender/interface.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/schoolCalender/interface.html b/apps/schoolCalender/interface.html index 1761da640..26f97c2f7 100644 --- a/apps/schoolCalender/interface.html +++ b/apps/schoolCalender/interface.html @@ -9,7 +9,7 @@ var calendar = new FullCalendar.Calendar(calendarEl, { initialView: 'timeGridWeek', headerToolbar: { - left: 'prev,next today', + left: '', center: 'title', right: 'timeGridWeek,listWeek' }, From 751fbdda43ea542db4887bed7b4d4e2ad7628a23 Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Thu, 16 Sep 2021 19:00:47 -0700 Subject: [PATCH 005/155] Update interface.html --- apps/schoolCalender/interface.html | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/apps/schoolCalender/interface.html b/apps/schoolCalender/interface.html index 26f97c2f7..201aa9a91 100644 --- a/apps/schoolCalender/interface.html +++ b/apps/schoolCalender/interface.html @@ -52,6 +52,10 @@ width: 100%; } + From 34baf995e7f5288266b5da08bcac6d76202a92fc Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Thu, 16 Sep 2021 19:04:33 -0700 Subject: [PATCH 006/155] Update interface.html --- apps/schoolCalender/interface.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/schoolCalender/interface.html b/apps/schoolCalender/interface.html index 201aa9a91..bbcb3ffb2 100644 --- a/apps/schoolCalender/interface.html +++ b/apps/schoolCalender/interface.html @@ -52,9 +52,9 @@ width: 100%; } - +

+

+

This is currently Christmas-themed, but more themes will be added in the future.

+ + + - - -
From ec453c0e100f7fd140305fe8ed4a62d3987e68f1 Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Mon, 20 Sep 2021 19:45:50 -0700 Subject: [PATCH 023/155] Update README.md --- apps/schoolCalender/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/schoolCalender/README.md b/apps/schoolCalender/README.md index e053cfdb5..c91f59759 100644 --- a/apps/schoolCalender/README.md +++ b/apps/schoolCalender/README.md @@ -1,6 +1,6 @@ # Bangle.js Calendar -School Calender is a calender that you can see your upcoming classes or schedule. +School Calender is a calender that you can see your upcoming classes or schedule. Keep in note that the events repeat weekly. ## Versions: From ceab5d46a23f913faf96be90e644458ac5546157 Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Tue, 21 Sep 2021 20:58:01 -0700 Subject: [PATCH 024/155] Rename schoolCalender.js to app.js --- apps/schoolCalender/{schoolCalender.js => app.js} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename apps/schoolCalender/{schoolCalender.js => app.js} (100%) diff --git a/apps/schoolCalender/schoolCalender.js b/apps/schoolCalender/app.js similarity index 100% rename from apps/schoolCalender/schoolCalender.js rename to apps/schoolCalender/app.js From d8c149073fb6bca8f5f78ba4a031004adeab1a6b Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Tue, 21 Sep 2021 21:01:31 -0700 Subject: [PATCH 025/155] Update apps.json --- apps.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/apps.json b/apps.json index 052feff77..c8b8a1ad0 100644 --- a/apps.json +++ b/apps.json @@ -3512,6 +3512,10 @@ "tags": "tool", "readme": "README.md", "custom":"interface.html", + "storage": [ + {"name":"schoolCalender.app.js","url":"app.js"}, + {"name":"schoolCalendar.img","url":"app-icon.js","evaluate":true} + ], "data": [ {"name":"app.json"} ] From 2ca58d2cc436b72beb87d347b8d8c217b461e805 Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Tue, 21 Sep 2021 21:02:14 -0700 Subject: [PATCH 026/155] Update custom.html --- apps/mywelcome/custom.html | 203 +++++++++++++++---------------------- 1 file changed, 79 insertions(+), 124 deletions(-) diff --git a/apps/mywelcome/custom.html b/apps/mywelcome/custom.html index b021b7b1a..6b73a4ac4 100644 --- a/apps/mywelcome/custom.html +++ b/apps/mywelcome/custom.html @@ -1,137 +1,92 @@ - - - -
-

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 3d8ebe82c289156581799134b3c56fd02ade1fad Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Tue, 21 Sep 2021 21:07:20 -0700 Subject: [PATCH 027/155] Update interface.html --- apps/schoolCalender/interface.html | 196 ++++++++++++----------------- 1 file changed, 79 insertions(+), 117 deletions(-) diff --git a/apps/schoolCalender/interface.html b/apps/schoolCalender/interface.html index 10e7b2717..9f49c5886 100644 --- a/apps/schoolCalender/interface.html +++ b/apps/schoolCalender/interface.html @@ -1,130 +1,92 @@ - - - -
-

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 1f130c53b46546328260a6b94ad182be5d55ff18 Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Wed, 22 Sep 2021 07:25:41 -0700 Subject: [PATCH 028/155] Update interface.html --- apps/schoolCalender/interface.html | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/schoolCalender/interface.html b/apps/schoolCalender/interface.html index 9f49c5886..a03aa1660 100644 --- a/apps/schoolCalender/interface.html +++ b/apps/schoolCalender/interface.html @@ -27,6 +27,7 @@ + - - - - -
- - - From 941bc852c1ab4472dc74a7f84e12e92ab891432e Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Mon, 4 Oct 2021 15:57:53 -0700 Subject: [PATCH 031/155] Add files via upload --- apps/schoolCalendar/CalenderLogo.png | Bin 0 -> 1162 bytes apps/schoolCalendar/ChangeLog.md | 2 + apps/schoolCalendar/README.md | 9 + apps/schoolCalendar/app-icon.js | 1 + .../fullcalendar/interaction/LICENSE.txt | 22 + .../fullcalendar/interaction/README.md | 8 + .../fullcalendar/interaction/package.json | 32 + .../interaction/src/ElementScrollGeomCache.ts | 16 + .../interaction/src/OffsetTracker.ts | 76 + .../interaction/src/ScrollGeomCache.ts | 107 + .../interaction/src/WindowScrollGeomCache.ts | 27 + .../interaction/src/api-type-deps.ts | 6 + .../interaction/src/dnd/AutoScroller.ts | 217 + .../interaction/src/dnd/ElementMirror.ts | 146 + .../src/dnd/FeaturefulElementDragging.ts | 213 + .../interaction/src/dnd/PointerDragging.ts | 344 + .../ExternalDraggable.ts | 70 + .../ExternalElementDragging.ts | 268 + .../InferredElementDragging.ts | 77 + .../ThirdPartyDraggable.ts | 52 + .../src/interactions/DateClicking.ts | 71 + .../src/interactions/DateSelecting.ts | 152 + .../src/interactions/EventDragging.ts | 482 + .../src/interactions/EventResizing.ts | 263 + .../src/interactions/HitDragging.ts | 220 + .../src/interactions/UnselectAuto.ts | 77 + .../interaction/src/main.global.ts | 7 + .../fullcalendar/interaction/src/main.ts | 23 + .../interaction/src/options-declare.ts | 9 + .../fullcalendar/interaction/src/options.ts | 26 + .../fullcalendar/interaction/src/utils.ts | 38 + .../fullcalendar/interaction/tsconfig.json | 13 + .../fullcalendar/locales-all.js | 1622 ++ apps/schoolCalendar/fullcalendar/main.css | 1446 ++ apps/schoolCalendar/fullcalendar/main.js | 14738 ++++++++++++++++ apps/schoolCalendar/interface.html | 82 + apps/schoolCalendar/schoolCalendar.js | 229 + 37 files changed, 21191 insertions(+) create mode 100644 apps/schoolCalendar/CalenderLogo.png create mode 100644 apps/schoolCalendar/ChangeLog.md create mode 100644 apps/schoolCalendar/README.md create mode 100644 apps/schoolCalendar/app-icon.js create mode 100644 apps/schoolCalendar/fullcalendar/interaction/LICENSE.txt create mode 100644 apps/schoolCalendar/fullcalendar/interaction/README.md create mode 100644 apps/schoolCalendar/fullcalendar/interaction/package.json create mode 100644 apps/schoolCalendar/fullcalendar/interaction/src/ElementScrollGeomCache.ts create mode 100644 apps/schoolCalendar/fullcalendar/interaction/src/OffsetTracker.ts create mode 100644 apps/schoolCalendar/fullcalendar/interaction/src/ScrollGeomCache.ts create mode 100644 apps/schoolCalendar/fullcalendar/interaction/src/WindowScrollGeomCache.ts create mode 100644 apps/schoolCalendar/fullcalendar/interaction/src/api-type-deps.ts create mode 100644 apps/schoolCalendar/fullcalendar/interaction/src/dnd/AutoScroller.ts create mode 100644 apps/schoolCalendar/fullcalendar/interaction/src/dnd/ElementMirror.ts create mode 100644 apps/schoolCalendar/fullcalendar/interaction/src/dnd/FeaturefulElementDragging.ts create mode 100644 apps/schoolCalendar/fullcalendar/interaction/src/dnd/PointerDragging.ts create mode 100644 apps/schoolCalendar/fullcalendar/interaction/src/interactions-external/ExternalDraggable.ts create mode 100644 apps/schoolCalendar/fullcalendar/interaction/src/interactions-external/ExternalElementDragging.ts create mode 100644 apps/schoolCalendar/fullcalendar/interaction/src/interactions-external/InferredElementDragging.ts create mode 100644 apps/schoolCalendar/fullcalendar/interaction/src/interactions-external/ThirdPartyDraggable.ts create mode 100644 apps/schoolCalendar/fullcalendar/interaction/src/interactions/DateClicking.ts create mode 100644 apps/schoolCalendar/fullcalendar/interaction/src/interactions/DateSelecting.ts create mode 100644 apps/schoolCalendar/fullcalendar/interaction/src/interactions/EventDragging.ts create mode 100644 apps/schoolCalendar/fullcalendar/interaction/src/interactions/EventResizing.ts create mode 100644 apps/schoolCalendar/fullcalendar/interaction/src/interactions/HitDragging.ts create mode 100644 apps/schoolCalendar/fullcalendar/interaction/src/interactions/UnselectAuto.ts create mode 100644 apps/schoolCalendar/fullcalendar/interaction/src/main.global.ts create mode 100644 apps/schoolCalendar/fullcalendar/interaction/src/main.ts create mode 100644 apps/schoolCalendar/fullcalendar/interaction/src/options-declare.ts create mode 100644 apps/schoolCalendar/fullcalendar/interaction/src/options.ts create mode 100644 apps/schoolCalendar/fullcalendar/interaction/src/utils.ts create mode 100644 apps/schoolCalendar/fullcalendar/interaction/tsconfig.json create mode 100644 apps/schoolCalendar/fullcalendar/locales-all.js create mode 100644 apps/schoolCalendar/fullcalendar/main.css create mode 100644 apps/schoolCalendar/fullcalendar/main.js create mode 100644 apps/schoolCalendar/interface.html create mode 100644 apps/schoolCalendar/schoolCalendar.js diff --git a/apps/schoolCalendar/CalenderLogo.png b/apps/schoolCalendar/CalenderLogo.png new file mode 100644 index 0000000000000000000000000000000000000000..49d198a15b0feb1c4d175282c6e1c1112df8b06c GIT binary patch literal 1162 zcmV;51anZw{&TSgFQ0s2e#fgMU}6Rl(1Cxt0iNJP?%_Xw&WO?Wwfph4b@stgo+Qa&i(UCnxF= z=^f0^&*Sj$5W~a6ihNb)l-ma_oI-sNn#AfHKVDn)1KFU|8k1(FzFl78?CeY#U}0f_ zt*$IhHpGmKH2oliK)jN`FZi~|&(6*wpU>k`D$U`+_o|PNE}>0tfFZyFtu-8(t`Mk4 zRVadHfw%A8(ZHa13UYil?-IKa`-5p2n{%CWPgF;s%vHCvW7)h_h`u0X2E*gnkAhjL7?#g!$$ zm&@qu>r+YlRVJ;lbwApL8doz}bu2NPJ9T+Lj;K2XsklI%Bg*<07Z*^WVtNPHd5(J{ zi)?T27ixenz{XvLjL)@vy)h_U=P@O?KINEKJM=B{v^Vc zQg7yIFotKK#7GlDQ8PnhosU!%@z7{B``hhynA5}plQ?{oFs0Ou4@N-|w new ElementScrollGeomCache(scrollEl, true), // listen=true + ) + } + + destroy() { + for (let scrollCache of this.scrollCaches) { + scrollCache.destroy() + } + } + + computeLeft() { + let left = this.origRect.left + + for (let scrollCache of this.scrollCaches) { + left += scrollCache.origScrollLeft - scrollCache.getScrollLeft() + } + + return left + } + + computeTop() { + let top = this.origRect.top + + for (let scrollCache of this.scrollCaches) { + top += scrollCache.origScrollTop - scrollCache.getScrollTop() + } + + return top + } + + isWithinClipping(pageX: number, pageY: number): boolean { + let point = { left: pageX, top: pageY } + + for (let scrollCache of this.scrollCaches) { + if ( + !isIgnoredClipping(scrollCache.getEventTarget()) && + !pointInsideRect(point, scrollCache.clientRect) + ) { + return false + } + } + + return true + } +} + +// certain clipping containers should never constrain interactions, like and +// https://github.com/fullcalendar/fullcalendar/issues/3615 +function isIgnoredClipping(node: EventTarget) { + let tagName = (node as HTMLElement).tagName + + return tagName === 'HTML' || tagName === 'BODY' +} diff --git a/apps/schoolCalendar/fullcalendar/interaction/src/ScrollGeomCache.ts b/apps/schoolCalendar/fullcalendar/interaction/src/ScrollGeomCache.ts new file mode 100644 index 000000000..63ec6a5e1 --- /dev/null +++ b/apps/schoolCalendar/fullcalendar/interaction/src/ScrollGeomCache.ts @@ -0,0 +1,107 @@ +import { Rect, ScrollController } from '@fullcalendar/common' + +/* +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 +*/ +export abstract class ScrollGeomCache extends ScrollController { + clientRect: Rect + origScrollTop: number + origScrollLeft: number + + protected scrollController: ScrollController + protected doesListening: boolean + protected scrollTop: number + protected scrollLeft: number + protected scrollWidth: number + protected scrollHeight: number + protected clientWidth: number + protected clientHeight: number + + constructor(scrollController: ScrollController, doesListening: boolean) { + super() + 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) + } + } + + abstract getEventTarget(): EventTarget + abstract computeClientRect(): Rect + + destroy() { + if (this.doesListening) { + this.getEventTarget().removeEventListener('scroll', this.handleScroll) + } + } + + handleScroll = () => { + this.scrollTop = this.scrollController.getScrollTop() + this.scrollLeft = this.scrollController.getScrollLeft() + this.handleScrollChange() + } + + getScrollTop() { + return this.scrollTop + } + + getScrollLeft() { + return this.scrollLeft + } + + setScrollTop(top: number) { + 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() + } + } + + setScrollLeft(top: number) { + 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() + } + } + + getClientWidth() { + return this.clientWidth + } + + getClientHeight() { + return this.clientHeight + } + + getScrollWidth() { + return this.scrollWidth + } + + getScrollHeight() { + return this.scrollHeight + } + + handleScrollChange() { + } +} diff --git a/apps/schoolCalendar/fullcalendar/interaction/src/WindowScrollGeomCache.ts b/apps/schoolCalendar/fullcalendar/interaction/src/WindowScrollGeomCache.ts new file mode 100644 index 000000000..ca65dee6e --- /dev/null +++ b/apps/schoolCalendar/fullcalendar/interaction/src/WindowScrollGeomCache.ts @@ -0,0 +1,27 @@ +import { Rect, WindowScrollController } from '@fullcalendar/common' +import { ScrollGeomCache } from './ScrollGeomCache' + +export class WindowScrollGeomCache extends ScrollGeomCache { + constructor(doesListening: boolean) { + super(new WindowScrollController(), doesListening) + } + + getEventTarget(): EventTarget { + return window + } + + computeClientRect(): Rect { + 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 + handleScrollChange() { + this.clientRect = this.computeClientRect() + } +} diff --git a/apps/schoolCalendar/fullcalendar/interaction/src/api-type-deps.ts b/apps/schoolCalendar/fullcalendar/interaction/src/api-type-deps.ts new file mode 100644 index 000000000..2b1b51b06 --- /dev/null +++ b/apps/schoolCalendar/fullcalendar/interaction/src/api-type-deps.ts @@ -0,0 +1,6 @@ +// TODO: rename file to public-types.ts + +export { DateClickArg } from './interactions/DateClicking' +export { EventDragStartArg, EventDragStopArg } from './interactions/EventDragging' +export { EventResizeStartArg, EventResizeStopArg, EventResizeDoneArg } from './interactions/EventResizing' +export { DropArg, EventReceiveArg, EventLeaveArg } from './utils' diff --git a/apps/schoolCalendar/fullcalendar/interaction/src/dnd/AutoScroller.ts b/apps/schoolCalendar/fullcalendar/interaction/src/dnd/AutoScroller.ts new file mode 100644 index 000000000..8d4e8f015 --- /dev/null +++ b/apps/schoolCalendar/fullcalendar/interaction/src/dnd/AutoScroller.ts @@ -0,0 +1,217 @@ +import { getElRoot } from '@fullcalendar/common' +import { ScrollGeomCache } from '../ScrollGeomCache' +import { ElementScrollGeomCache } from '../ElementScrollGeomCache' +import { WindowScrollGeomCache } from '../WindowScrollGeomCache' + +interface Edge { + scrollCache: ScrollGeomCache + name: 'top' | 'left' | 'right' | 'bottom' + distance: number // how many pixels the current pointer is from the edge +} + +// 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 +const getTime = typeof performance === 'function' ? (performance as any).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. +*/ +export class AutoScroller { + // options that can be set by caller + isEnabled: boolean = true + scrollQuery: (Window | string)[] = [window, '.fc-scroller'] + edgeThreshold: number = 50 // pixels + maxVelocity: number = 300 // pixels per second + + // internal state + pointerScreenX: number | null = null + pointerScreenY: number | null = null + isAnimating: boolean = false + scrollCaches: ScrollGeomCache[] | null = null + msSinceRequest?: number + + // protect against the initial pointerdown being too close to an edge and starting the scroll + everMovedUp: boolean = false + everMovedDown: boolean = false + everMovedLeft: boolean = false + everMovedRight: boolean = false + + start(pageX: number, pageY: number, scrollStartEl: HTMLElement) { + 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) + } + } + + handleMove(pageX: number, pageY: number) { + if (this.isEnabled) { + let pointerScreenX = pageX - window.pageXOffset + let pointerScreenY = pageY - window.pageYOffset + + let yDelta = this.pointerScreenY === null ? 0 : pointerScreenY - this.pointerScreenY + let 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()) + } + } + } + + stop() { + if (this.isEnabled) { + this.isAnimating = false // will stop animation + + for (let scrollCache of this.scrollCaches!) { + scrollCache.destroy() + } + + this.scrollCaches = null + } + } + + requestAnimation(now: number) { + this.msSinceRequest = now + requestAnimationFrame(this.animate) + } + + private animate = () => { + if (this.isAnimating) { // wasn't cancelled between animation calls + let edge = this.computeBestEdge( + this.pointerScreenX! + window.pageXOffset, + this.pointerScreenY! + window.pageYOffset, + ) + + if (edge) { + let now = getTime() + this.handleSide(edge, (now - this.msSinceRequest!) / 1000) + this.requestAnimation(now) + } else { + this.isAnimating = false // will stop animation + } + } + } + + private handleSide(edge: Edge, seconds: number) { + let { scrollCache } = edge + let { edgeThreshold } = this + let invDistance = edgeThreshold - edge.distance + let velocity = // the closer to the edge, the faster we scroll + ((invDistance * invDistance) / (edgeThreshold * edgeThreshold)) * // quadratic + this.maxVelocity * seconds + let 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 + private computeBestEdge(left: number, top: number): Edge | null { + let { edgeThreshold } = this + let bestSide: Edge | null = null + + for (let scrollCache of this.scrollCaches!) { + let rect = scrollCache.clientRect + let leftDist = left - rect.left + let rightDist = rect.right - left + let topDist = top - rect.top + let 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, name: 'top', distance: topDist } + } + + if ( + bottomDist <= edgeThreshold && this.everMovedDown && scrollCache.canScrollDown() && + (!bestSide || bestSide.distance > bottomDist) + ) { + bestSide = { scrollCache, name: 'bottom', distance: bottomDist } + } + + if ( + leftDist <= edgeThreshold && this.everMovedLeft && scrollCache.canScrollLeft() && + (!bestSide || bestSide.distance > leftDist) + ) { + bestSide = { scrollCache, name: 'left', distance: leftDist } + } + + if ( + rightDist <= edgeThreshold && this.everMovedRight && scrollCache.canScrollRight() && + (!bestSide || bestSide.distance > rightDist) + ) { + bestSide = { scrollCache, name: 'right', distance: rightDist } + } + } + } + + return bestSide + } + + private buildCaches(scrollStartEl: HTMLElement) { + return this.queryScrollEls(scrollStartEl).map((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 + }) + } + + private queryScrollEls(scrollStartEl: HTMLElement) { + let els = [] + + for (let query of this.scrollQuery) { + if (typeof query === 'object') { + els.push(query) + } else { + els.push(...Array.prototype.slice.call( + getElRoot(scrollStartEl).querySelectorAll(query), + )) + } + } + + return els + } +} diff --git a/apps/schoolCalendar/fullcalendar/interaction/src/dnd/ElementMirror.ts b/apps/schoolCalendar/fullcalendar/interaction/src/dnd/ElementMirror.ts new file mode 100644 index 000000000..6c1e9f4a1 --- /dev/null +++ b/apps/schoolCalendar/fullcalendar/interaction/src/dnd/ElementMirror.ts @@ -0,0 +1,146 @@ +import { removeElement, applyStyle, whenTransitionDone, Rect } from '@fullcalendar/common' + +/* +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. +*/ +export class ElementMirror { + isVisible: boolean = false // must be explicitly enabled + origScreenX?: number + origScreenY?: number + deltaX?: number + deltaY?: number + sourceEl: HTMLElement | null = null + mirrorEl: HTMLElement | null = null + sourceElRect: Rect | null = null // screen coords relative to viewport + + // options that can be set directly by caller + parentNode: HTMLElement = document.body // HIGHLY SUGGESTED to set this to sidestep ShadowDOM issues + zIndex: number = 9999 + revertDuration: number = 0 + + start(sourceEl: HTMLElement, pageX: number, pageY: number) { + 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() + } + + handleMove(pageX: number, pageY: number) { + this.deltaX = (pageX - window.pageXOffset) - this.origScreenX! + this.deltaY = (pageY - window.pageYOffset) - this.origScreenY! + this.updateElPosition() + } + + // can be called before start + setIsVisible(bool: boolean) { + 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 + stop(needsRevertAnimation: boolean, callback: () => void) { + let done = () => { + 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) + } + } + + doRevertAnimation(callback: () => void, revertDuration: number) { + let mirrorEl = this.mirrorEl! + let 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, () => { + mirrorEl.style.transition = '' + callback() + }) + } + + cleanup() { + if (this.mirrorEl) { + removeElement(this.mirrorEl) + this.mirrorEl = null + } + + this.sourceEl = null + } + + updateElPosition() { + if (this.sourceEl && this.isVisible) { + applyStyle(this.getMirrorEl(), { + left: this.sourceElRect!.left + this.deltaX!, + top: this.sourceElRect!.top + this.deltaY!, + }) + } + } + + getMirrorEl(): HTMLElement { + let sourceElRect = this.sourceElRect! + let mirrorEl = this.mirrorEl + + if (!mirrorEl) { + mirrorEl = this.mirrorEl = this.sourceEl!.cloneNode(true) as HTMLElement // 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: '', // in case original element was hidden by the drag effect + boxSizing: 'border-box', // for easy width/height + width: sourceElRect.right - sourceElRect.left, // explicit height in case there was a 'right' value + height: sourceElRect.bottom - sourceElRect.top, // explicit width in case there was a 'bottom' value + right: 'auto', // erase and set width instead + bottom: 'auto', // erase and set height instead + margin: 0, + }) + + this.parentNode.appendChild(mirrorEl) + } + + return mirrorEl + } +} diff --git a/apps/schoolCalendar/fullcalendar/interaction/src/dnd/FeaturefulElementDragging.ts b/apps/schoolCalendar/fullcalendar/interaction/src/dnd/FeaturefulElementDragging.ts new file mode 100644 index 000000000..3f1c7826b --- /dev/null +++ b/apps/schoolCalendar/fullcalendar/interaction/src/dnd/FeaturefulElementDragging.ts @@ -0,0 +1,213 @@ +import { + PointerDragEvent, + preventSelection, + allowSelection, + preventContextMenu, + allowContextMenu, + ElementDragging, +} from '@fullcalendar/common' +import { PointerDragging } from './PointerDragging' +import { ElementMirror } from './ElementMirror' +import { AutoScroller } from './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 +*/ +export class FeaturefulElementDragging extends ElementDragging { + pointer: PointerDragging + mirror: ElementMirror + autoScroller: AutoScroller + + // options that can be directly set by caller + // the caller can also set the PointerDragging's options as well + delay: number | null = null + minDistance: number = 0 + touchScrollAllowed: boolean = true // prevents drag from starting and blocks scrolling during drag + + mirrorNeedsRevert: boolean = false + isInteracting: boolean = false // is the user validly moving the pointer? lasts until pointerup + isDragging: boolean = false // is it INTENTFULLY dragging? lasts until after revert animation + isDelayEnded: boolean = false + isDistanceSurpassed: boolean = false + delayTimeoutId: number | null = null + + constructor(private containerEl: HTMLElement, selector?: string) { + super(containerEl) + + let 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() + } + + destroy() { + 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({} as any) + } + + onPointerDown = (ev: PointerDragEvent) => { + 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 as HTMLElement, ev.pageX, ev.pageY) // must happen on first pointer down + + this.startDelay(ev) + + if (!this.minDistance) { + this.handleDistanceSurpassed(ev) + } + } + } + } + + onPointerMove = (ev: PointerDragEvent) => { + if (this.isInteracting) { + this.emitter.trigger('pointermove', ev) + + if (!this.isDistanceSurpassed) { + let minDistance = this.minDistance + let distanceSq // current distance from the origin, squared + let { deltaX, deltaY } = ev + + 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) + } + } + } + + onPointerUp = (ev: PointerDragEvent) => { + 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 + } + } + } + + startDelay(ev: PointerDragEvent) { + if (typeof this.delay === 'number') { + this.delayTimeoutId = setTimeout(() => { + this.delayTimeoutId = null + this.handleDelayEnd(ev) + }, this.delay) as any // not assignable to number! + } else { + this.handleDelayEnd(ev) + } + } + + handleDelayEnd(ev: PointerDragEvent) { + this.isDelayEnded = true + this.tryStartDrag(ev) + } + + handleDistanceSurpassed(ev: PointerDragEvent) { + this.isDistanceSurpassed = true + this.tryStartDrag(ev) + } + + tryStartDrag(ev: PointerDragEvent) { + 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() + } + } + } + } + + tryStopDrag(ev: PointerDragEvent) { + // .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), // bound with args + ) + } + + stopDrag(ev: PointerDragEvent) { + this.isDragging = false + this.emitter.trigger('dragend', ev) + } + + // fill in the implementations... + + setIgnoreMove(bool: boolean) { + this.pointer.shouldIgnoreMove = bool + } + + setMirrorIsVisible(bool: boolean) { + this.mirror.setIsVisible(bool) + } + + setMirrorNeedsRevert(bool: boolean) { + this.mirrorNeedsRevert = bool + } + + setAutoScrollEnabled(bool: boolean) { + this.autoScroller.isEnabled = bool + } +} diff --git a/apps/schoolCalendar/fullcalendar/interaction/src/dnd/PointerDragging.ts b/apps/schoolCalendar/fullcalendar/interaction/src/dnd/PointerDragging.ts new file mode 100644 index 000000000..dbe7d8abb --- /dev/null +++ b/apps/schoolCalendar/fullcalendar/interaction/src/dnd/PointerDragging.ts @@ -0,0 +1,344 @@ +import { config, elementClosest, Emitter, PointerDragEvent } from '@fullcalendar/common' + +config.touchMouseIgnoreWait = 500 + +let ignoreMouseDepth = 0 +let listenerCnt = 0 +let 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 +*/ +export class PointerDragging { + containerEl: EventTarget + subjectEl: HTMLElement | null = null + emitter: Emitter + + // options that can be directly assigned by caller + selector: string = '' // will cause subjectEl in all emitted events to be this element + handleSelector: string = '' + shouldIgnoreMove: boolean = false + shouldWatchScroll: boolean = true // for simulating pointermove on scroll + + // internal states + isDragging: boolean = false + isTouchDragging: boolean = false + wasTouchScroll: boolean = false + origPageX: number + origPageY: number + prevPageX: number + prevPageY: number + prevScrollX: number // at time of last pointer pageX/pageY capture + prevScrollY: number // " + + constructor(containerEl: EventTarget) { + this.containerEl = containerEl + this.emitter = new Emitter() + containerEl.addEventListener('mousedown', this.handleMouseDown as EventListener) + containerEl.addEventListener('touchstart', this.handleTouchStart as EventListener, { passive: true }) + listenerCreated() + } + + destroy() { + this.containerEl.removeEventListener('mousedown', this.handleMouseDown as EventListener) + this.containerEl.removeEventListener('touchstart', this.handleTouchStart as EventListener, { passive: true } as AddEventListenerOptions) + listenerDestroyed() + } + + tryStart(ev: UIEvent): boolean { + let subjectEl = this.querySubjectEl(ev) + let downEl = ev.target as HTMLElement + + 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 + } + + cleanup() { + isWindowTouchMoveCancelled = false + this.isDragging = false + this.subjectEl = null + // keep wasTouchScroll around for later access + this.destroyScrollWatch() + } + + querySubjectEl(ev: UIEvent): HTMLElement { + if (this.selector) { + return elementClosest(ev.target as HTMLElement, this.selector) + } + return this.containerEl as HTMLElement + } + + // Mouse + // ---------------------------------------------------------------------------------------------------- + + handleMouseDown = (ev: MouseEvent) => { + if ( + !this.shouldIgnoreMouse() && + isPrimaryMouseButton(ev) && + this.tryStart(ev) + ) { + let 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) + } + } + + handleMouseMove = (ev: MouseEvent) => { + let pev = this.createEventFromMouse(ev) + this.recordCoords(pev) + this.emitter.trigger('pointermove', pev) + } + + handleMouseUp = (ev: MouseEvent) => { + 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 + } + + shouldIgnoreMouse() { + return ignoreMouseDepth || this.isTouchDragging + } + + // Touch + // ---------------------------------------------------------------------------------------------------- + + handleTouchStart = (ev: TouchEvent) => { + if (this.tryStart(ev)) { + this.isTouchDragging = true + + let 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 + let targetEl = ev.target as HTMLElement + + 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, // useCapture + ) + } + } + + handleTouchMove = (ev: TouchEvent) => { + let pev = this.createEventFromTouch(ev) + this.recordCoords(pev) + this.emitter.trigger('pointermove', pev) + } + + handleTouchEnd = (ev: TouchEvent) => { + if (this.isDragging) { // done to guard against touchend followed by touchcancel + let targetEl = ev.target as HTMLElement + + 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() + } + } + + handleTouchScroll = () => { + this.wasTouchScroll = true + } + + // can be called by user of this class, to cancel touch-based scrolling for the current drag + cancelTouchScroll() { + if (this.isDragging) { + isWindowTouchMoveCancelled = true + } + } + + // Scrolling that simulates pointermoves + // ---------------------------------------------------------------------------------------------------- + + initScrollWatch(ev: PointerDragEvent) { + if (this.shouldWatchScroll) { + this.recordCoords(ev) + window.addEventListener('scroll', this.handleScroll, true) // useCapture=true + } + } + + recordCoords(ev: PointerDragEvent) { + if (this.shouldWatchScroll) { + this.prevPageX = (ev as any).pageX + this.prevPageY = (ev as any).pageY + this.prevScrollX = window.pageXOffset + this.prevScrollY = window.pageYOffset + } + } + + handleScroll = (ev: UIEvent) => { + if (!this.shouldIgnoreMove) { + let pageX = (window.pageXOffset - this.prevScrollX) + this.prevPageX + let pageY = (window.pageYOffset - this.prevScrollY) + this.prevPageY + + this.emitter.trigger('pointermove', { + origEvent: ev, + isTouch: this.isTouchDragging, + subjectEl: this.subjectEl, + pageX, + pageY, + deltaX: pageX - this.origPageX, + deltaY: pageY - this.origPageY, + } as PointerDragEvent) + } + } + + destroyScrollWatch() { + if (this.shouldWatchScroll) { + window.removeEventListener('scroll', this.handleScroll, true) // useCaptured=true + } + } + + // Event Normalization + // ---------------------------------------------------------------------------------------------------- + + createEventFromMouse(ev: MouseEvent, isFirst?: boolean): PointerDragEvent { + let deltaX = 0 + let 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, + deltaY, + } + } + + createEventFromTouch(ev: TouchEvent, isFirst?: boolean): PointerDragEvent { + let touches = ev.touches + let pageX + let pageY + let deltaX = 0 + let 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 as any).pageX + pageY = (ev as any).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, + pageY, + deltaX, + deltaY, + } + } +} + +// Returns a boolean whether this was a left mouse click and no ctrl key (which means right click on Mac) +function isPrimaryMouseButton(ev: MouseEvent) { + return ev.button === 0 && !ev.ctrlKey +} + +// Ignoring fake mouse events generated by touch +// ---------------------------------------------------------------------------------------------------- + +function startIgnoringMouse() { // can be made non-class function + ignoreMouseDepth += 1 + + setTimeout(() => { + 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 } as AddEventListenerOptions) + } +} + +function onWindowTouchMove(ev: UIEvent) { + if (isWindowTouchMoveCancelled) { + ev.preventDefault() + } +} diff --git a/apps/schoolCalendar/fullcalendar/interaction/src/interactions-external/ExternalDraggable.ts b/apps/schoolCalendar/fullcalendar/interaction/src/interactions-external/ExternalDraggable.ts new file mode 100644 index 000000000..22aba108d --- /dev/null +++ b/apps/schoolCalendar/fullcalendar/interaction/src/interactions-external/ExternalDraggable.ts @@ -0,0 +1,70 @@ +import { BASE_OPTION_DEFAULTS, PointerDragEvent } from '@fullcalendar/common' +import { FeaturefulElementDragging } from '../dnd/FeaturefulElementDragging' +import { ExternalElementDragging, DragMetaGenerator } from './ExternalElementDragging' + +export interface ExternalDraggableSettings { + eventData?: DragMetaGenerator + itemSelector?: string + minDistance?: number + longPressDelay?: number + appendTo?: HTMLElement +} + +/* +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. +*/ +export class ExternalDraggable { + dragging: FeaturefulElementDragging + settings: ExternalDraggableSettings + + constructor(el: HTMLElement, settings: ExternalDraggableSettings = {}) { + this.settings = settings + + let 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 + } + + handlePointerDown = (ev: PointerDragEvent) => { + let { dragging } = this + let { minDistance, longPressDelay } = this.settings + + 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 + } + + handleDragStart = (ev: PointerDragEvent) => { + if ( + ev.isTouch && + this.dragging.delay && + (ev.subjectEl as HTMLElement).classList.contains('fc-event') + ) { + this.dragging.mirror.getMirrorEl().classList.add('fc-event-selected') + } + } + + destroy() { + this.dragging.destroy() + } +} diff --git a/apps/schoolCalendar/fullcalendar/interaction/src/interactions-external/ExternalElementDragging.ts b/apps/schoolCalendar/fullcalendar/interaction/src/interactions-external/ExternalElementDragging.ts new file mode 100644 index 000000000..95ac7e0c2 --- /dev/null +++ b/apps/schoolCalendar/fullcalendar/interaction/src/interactions-external/ExternalElementDragging.ts @@ -0,0 +1,268 @@ +import { + Hit, + interactionSettingsStore, + PointerDragEvent, + parseEventDef, createEventInstance, EventTuple, + createEmptyEventStore, eventTupleToStore, + config, + DateSpan, DatePointApi, + EventInteractionState, + DragMetaInput, DragMeta, parseDragMeta, + EventApi, + elementMatches, + enableCursor, disableCursor, + isInteractionValid, + ElementDragging, + ViewApi, + CalendarContext, + getDefaultEventEnd, + refineEventDef, +} from '@fullcalendar/common' +import { __assign } from 'tslib' +import { HitDragging } from '../interactions/HitDragging' +import { buildDatePointApiWithContext } from '../utils' + +export type DragMetaGenerator = DragMetaInput | ((el: HTMLElement) => DragMetaInput) + +export interface ExternalDropApi extends DatePointApi { + draggedEl: HTMLElement + jsEvent: UIEvent + view: ViewApi +} + +/* +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. +*/ +export class ExternalElementDragging { + hitDragging: HitDragging + receivingContext: CalendarContext | null = null + droppableEvent: EventTuple | null = null // will exist for all drags, even if create:false + suppliedDragMeta: DragMetaGenerator | null = null + dragMeta: DragMeta | null = null + + constructor(dragging: ElementDragging, suppliedDragMeta?: DragMetaGenerator) { + let 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 + } + + handleDragStart = (ev: PointerDragEvent) => { + this.dragMeta = this.buildDragMeta(ev.subjectEl as HTMLElement) + } + + buildDragMeta(subjectEl: HTMLElement) { + if (typeof this.suppliedDragMeta === 'object') { + return parseDragMeta(this.suppliedDragMeta) + } + if (typeof this.suppliedDragMeta === 'function') { + return parseDragMeta(this.suppliedDragMeta(subjectEl)) + } + return getDragMetaFromEl(subjectEl) + } + + handleHitUpdate = (hit: Hit | null, isFinal: boolean, ev: PointerDragEvent) => { + let { dragging } = this.hitDragging + let receivingContext: CalendarContext | null = null + let droppableEvent: EventTuple | null = null + let isInvalid = false + let interaction: EventInteractionState = { + affectedEvents: createEmptyEventStore(), + mutatedEvents: createEmptyEventStore(), + isEvent: this.dragMeta!.create, + } + + if (hit) { + receivingContext = hit.context + + if (this.canDropElOnCalendar(ev.subjectEl as HTMLElement, 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'), // TODO: turn className into constant + // TODO: somehow query FullCalendars WITHIN shadow-roots for existing event-mirror els + ) + + if (!isInvalid) { + enableCursor() + } else { + disableCursor() + } + + if (!isFinal) { + dragging.setMirrorNeedsRevert(!droppableEvent) + + this.receivingContext = receivingContext + this.droppableEvent = droppableEvent + } + } + + handleDragEnd = (pev: PointerDragEvent) => { + let { receivingContext, droppableEvent } = this + + this.clearDrag() + + if (receivingContext && droppableEvent) { + let finalHit = this.hitDragging.finalHit! + let finalView = finalHit.context.viewApi + let dragMeta = this.dragMeta! + + receivingContext.emitter.trigger('drop', { + ...buildDatePointApiWithContext(finalHit.dateSpan, receivingContext), + draggedEl: pev.subjectEl as HTMLElement, + jsEvent: pev.origEvent as MouseEvent, // Is this always a mouse event? See #4655 + view: finalView, + }) + + if (dragMeta.create) { + let addingEvents = eventTupleToStore(droppableEvent) + + receivingContext.dispatch({ + type: 'MERGE_EVENTS', + eventStore: addingEvents, + }) + + 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() { + receivingContext.dispatch({ + type: 'REMOVE_EVENTS', + eventStore: addingEvents, + }) + }, + draggedEl: pev.subjectEl as HTMLElement, + view: finalView, + }) + } + } + + this.receivingContext = null + this.droppableEvent = null + } + + displayDrag(nextContext: CalendarContext | null, state: EventInteractionState) { + let prevContext = this.receivingContext + + if (prevContext && prevContext !== nextContext) { + prevContext.dispatch({ type: 'UNSET_EVENT_DRAG' }) + } + + if (nextContext) { + nextContext.dispatch({ type: 'SET_EVENT_DRAG', state }) + } + } + + clearDrag() { + if (this.receivingContext) { + this.receivingContext.dispatch({ type: 'UNSET_EVENT_DRAG' }) + } + } + + canDropElOnCalendar(el: HTMLElement, receivingContext: CalendarContext): boolean { + let 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 + } +} + +// Utils for computing event store from the DragMeta +// ---------------------------------------------------------------------------------------------------- + +function computeEventForDateSpan(dateSpan: DateSpan, dragMeta: DragMeta, context: CalendarContext): EventTuple { + let defProps = { ...dragMeta.leftoverProps } + + for (let transform of context.pluginHooks.externalDefTransforms) { + __assign(defProps, transform(dateSpan, dragMeta)) + } + + let { refined, extra } = refineEventDef(defProps, context) + let def = parseEventDef( + refined, + extra, + dragMeta.sourceId, + dateSpan.allDay, + context.options.forceEventDuration || Boolean(dragMeta.duration), // hasEnd + context, + ) + + let 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) + } + + let end = dragMeta.duration ? + context.dateEnv.add(start, dragMeta.duration) : + getDefaultEventEnd(dateSpan.allDay, start, context) + + let instance = createEventInstance(def.defId, { start, end }) + + return { def, instance } +} + +// Utils for extracting data from element +// ---------------------------------------------------------------------------------------------------- + +function getDragMetaFromEl(el: HTMLElement): DragMeta { + let str = getEmbeddedElData(el, 'event') + let obj = str ? + JSON.parse(str) : + { create: false } // if no embedded data, assume no event creation + + return parseDragMeta(obj) +} + +config.dataAttrPrefix = '' + +function getEmbeddedElData(el: HTMLElement, name: string): string { + let prefix = config.dataAttrPrefix + let prefixedName = (prefix ? prefix + '-' : '') + name + + return el.getAttribute('data-' + prefixedName) || '' +} diff --git a/apps/schoolCalendar/fullcalendar/interaction/src/interactions-external/InferredElementDragging.ts b/apps/schoolCalendar/fullcalendar/interaction/src/interactions-external/InferredElementDragging.ts new file mode 100644 index 000000000..ab03cec00 --- /dev/null +++ b/apps/schoolCalendar/fullcalendar/interaction/src/interactions-external/InferredElementDragging.ts @@ -0,0 +1,77 @@ +import { PointerDragEvent, ElementDragging } from '@fullcalendar/common' +import { PointerDragging } from '../dnd/PointerDragging' + +/* +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. +*/ +export class InferredElementDragging extends ElementDragging { + pointer: PointerDragging + shouldIgnoreMove: boolean = false + mirrorSelector: string = '' + currentMirrorEl: HTMLElement | null = null + + constructor(containerEl: HTMLElement) { + super(containerEl) + + let pointer = this.pointer = new PointerDragging(containerEl) + pointer.emitter.on('pointerdown', this.handlePointerDown) + pointer.emitter.on('pointermove', this.handlePointerMove) + pointer.emitter.on('pointerup', this.handlePointerUp) + } + + destroy() { + this.pointer.destroy() + } + + handlePointerDown = (ev: PointerDragEvent) => { + this.emitter.trigger('pointerdown', ev) + + if (!this.shouldIgnoreMove) { + // fire dragstart right away. does not support delay or min-distance + this.emitter.trigger('dragstart', ev) + } + } + + handlePointerMove = (ev: PointerDragEvent) => { + if (!this.shouldIgnoreMove) { + this.emitter.trigger('dragmove', ev) + } + } + + handlePointerUp = (ev: PointerDragEvent) => { + this.emitter.trigger('pointerup', ev) + + if (!this.shouldIgnoreMove) { + // fire dragend right away. does not support a revert animation + this.emitter.trigger('dragend', ev) + } + } + + setIgnoreMove(bool: boolean) { + this.shouldIgnoreMove = bool + } + + setMirrorIsVisible(bool: boolean) { + 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 { + let mirrorEl = this.mirrorSelector + // TODO: somehow query FullCalendars WITHIN shadow-roots + ? document.querySelector(this.mirrorSelector) as HTMLElement + : null + + if (mirrorEl) { + this.currentMirrorEl = mirrorEl + mirrorEl.style.visibility = 'hidden' + } + } + } +} diff --git a/apps/schoolCalendar/fullcalendar/interaction/src/interactions-external/ThirdPartyDraggable.ts b/apps/schoolCalendar/fullcalendar/interaction/src/interactions-external/ThirdPartyDraggable.ts new file mode 100644 index 000000000..324b22255 --- /dev/null +++ b/apps/schoolCalendar/fullcalendar/interaction/src/interactions-external/ThirdPartyDraggable.ts @@ -0,0 +1,52 @@ +import { ExternalElementDragging, DragMetaGenerator } from './ExternalElementDragging' +import { InferredElementDragging } from './InferredElementDragging' + +export interface ThirdPartyDraggableSettings { + eventData?: DragMetaGenerator + itemSelector?: string + mirrorSelector?: string +} + +/* +Bridges third-party drag-n-drop systems with FullCalendar. +Must be instantiated and destroyed by caller. +*/ +export class ThirdPartyDraggable { + dragging: InferredElementDragging + + constructor( + containerOrSettings?: EventTarget | ThirdPartyDraggableSettings, + settings?: ThirdPartyDraggableSettings, + ) { + let containerEl: EventTarget = document + + if ( + // wish we could just test instanceof EventTarget, but doesn't work in IE11 + containerOrSettings === document || + containerOrSettings instanceof Element + ) { + containerEl = containerOrSettings as EventTarget + settings = settings || {} + } else { + settings = (containerOrSettings || {}) as ThirdPartyDraggableSettings + } + + let dragging = this.dragging = new InferredElementDragging(containerEl as HTMLElement) + + 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 + } + + destroy() { + this.dragging.destroy() + } +} diff --git a/apps/schoolCalendar/fullcalendar/interaction/src/interactions/DateClicking.ts b/apps/schoolCalendar/fullcalendar/interaction/src/interactions/DateClicking.ts new file mode 100644 index 000000000..06b352de9 --- /dev/null +++ b/apps/schoolCalendar/fullcalendar/interaction/src/interactions/DateClicking.ts @@ -0,0 +1,71 @@ +import { + PointerDragEvent, Interaction, InteractionSettings, interactionSettingsToStore, + DatePointApi, + ViewApi, +} from '@fullcalendar/common' +import { FeaturefulElementDragging } from '../dnd/FeaturefulElementDragging' +import { HitDragging, isHitsEqual } from './HitDragging' +import { buildDatePointApiWithContext } from '../utils' + +export interface DateClickArg extends DatePointApi { + dayEl: HTMLElement + jsEvent: MouseEvent + view: ViewApi +} + +/* +Monitors when the user clicks on a specific date/time of a component. +A pointerdown+pointerup on the same "hit" constitutes a click. +*/ +export class DateClicking extends Interaction { + dragging: FeaturefulElementDragging + hitDragging: HitDragging + + constructor(settings: InteractionSettings) { + super(settings) + + // 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 + + let hitDragging = this.hitDragging = new HitDragging(this.dragging, interactionSettingsToStore(settings)) + hitDragging.emitter.on('pointerdown', this.handlePointerDown) + hitDragging.emitter.on('dragend', this.handleDragEnd) + } + + destroy() { + this.dragging.destroy() + } + + handlePointerDown = (pev: PointerDragEvent) => { + let { dragging } = this + let downEl = pev.origEvent.target as HTMLElement + + // 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 + handleDragEnd = (ev: PointerDragEvent) => { + let { component } = this + let { pointer } = this.dragging + + if (!pointer.wasTouchScroll) { + let { initialHit, finalHit } = this.hitDragging + + if (initialHit && finalHit && isHitsEqual(initialHit, finalHit)) { + let { context } = component + let arg: DateClickArg = { + ...buildDatePointApiWithContext(initialHit.dateSpan, context), + dayEl: initialHit.dayEl, + jsEvent: ev.origEvent as MouseEvent, + view: context.viewApi || context.calendarApi.view, + } + + context.emitter.trigger('dateClick', arg) + } + } + } +} diff --git a/apps/schoolCalendar/fullcalendar/interaction/src/interactions/DateSelecting.ts b/apps/schoolCalendar/fullcalendar/interaction/src/interactions/DateSelecting.ts new file mode 100644 index 000000000..fa6b3ff09 --- /dev/null +++ b/apps/schoolCalendar/fullcalendar/interaction/src/interactions/DateSelecting.ts @@ -0,0 +1,152 @@ +import { + compareNumbers, enableCursor, disableCursor, DateComponent, Hit, + DateSpan, PointerDragEvent, dateSelectionJoinTransformer, + Interaction, InteractionSettings, interactionSettingsToStore, + triggerDateSelect, isDateSelectionValid, +} from '@fullcalendar/common' +import { __assign } from 'tslib' +import { HitDragging } from './HitDragging' +import { FeaturefulElementDragging } from '../dnd/FeaturefulElementDragging' + +/* +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. +*/ +export class DateSelecting extends Interaction { + dragging: FeaturefulElementDragging + hitDragging: HitDragging + dragSelection: DateSpan | null = null + + constructor(settings: InteractionSettings) { + super(settings) + let { component } = settings + let { options } = component.context + + let dragging = this.dragging = new FeaturefulElementDragging(settings.el) + dragging.touchScrollAllowed = false + dragging.minDistance = options.selectMinDistance || 0 + dragging.autoScroller.isEnabled = options.dragScroll + + let 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) + } + + destroy() { + this.dragging.destroy() + } + + handlePointerDown = (ev: PointerDragEvent) => { + let { component, dragging } = this + let { options } = component.context + + let canSelect = options.selectable && + component.isValidDateDownEl(ev.origEvent.target as HTMLElement) + + // 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(component) : null + } + + handleDragStart = (ev: PointerDragEvent) => { + this.component.context.calendarApi.unselect(ev) // unselect previous selections + } + + handleHitUpdate = (hit: Hit | null, isFinal: boolean) => { + let { context } = this.component + let dragSelection: DateSpan | null = null + let isInvalid = false + + if (hit) { + let initialHit = this.hitDragging.initialHit! + let 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 + } + } + + handlePointerUp = (pev: PointerDragEvent) => { + if (this.dragSelection) { + // selection is already rendered, so just need to report selection + triggerDateSelect(this.dragSelection, pev, this.component.context) + + this.dragSelection = null + } + } +} + +function getComponentTouchDelay(component: DateComponent): number { + let { options } = component.context + let delay = options.selectLongPressDelay + + if (delay == null) { + delay = options.longPressDelay + } + + return delay +} + +function joinHitsIntoSelection(hit0: Hit, hit1: Hit, dateSelectionTransformers: dateSelectionJoinTransformer[]): DateSpan { + let dateSpan0 = hit0.dateSpan + let dateSpan1 = hit1.dateSpan + let ms = [ + dateSpan0.range.start, + dateSpan0.range.end, + dateSpan1.range.start, + dateSpan1.range.end, + ] + + ms.sort(compareNumbers) + + let props = {} as DateSpan + + for (let transformer of dateSelectionTransformers) { + let 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 +} diff --git a/apps/schoolCalendar/fullcalendar/interaction/src/interactions/EventDragging.ts b/apps/schoolCalendar/fullcalendar/interaction/src/interactions/EventDragging.ts new file mode 100644 index 000000000..9a9ecfc49 --- /dev/null +++ b/apps/schoolCalendar/fullcalendar/interaction/src/interactions/EventDragging.ts @@ -0,0 +1,482 @@ +import { + DateComponent, Seg, + PointerDragEvent, Hit, + EventMutation, applyMutationToEventStore, + startOfDay, + elementClosest, + EventStore, getRelevantEvents, createEmptyEventStore, + EventInteractionState, + diffDates, enableCursor, disableCursor, + EventRenderRange, getElSeg, + EventApi, + eventDragMutationMassager, + Interaction, InteractionSettings, interactionSettingsStore, + EventDropTransformers, + CalendarContext, + ViewApi, + EventChangeArg, + buildEventApis, + EventAddArg, + EventRemoveArg, + isInteractionValid, + getElRoot, +} from '@fullcalendar/common' +import { __assign } from 'tslib' +import { HitDragging, isHitsEqual } from './HitDragging' +import { FeaturefulElementDragging } from '../dnd/FeaturefulElementDragging' +import { buildDatePointApiWithContext } from '../utils' + +export type EventDragStopArg = EventDragArg +export type EventDragStartArg = EventDragArg + +export interface EventDragArg { + el: HTMLElement + event: EventApi + jsEvent: MouseEvent + view: ViewApi +} + +export class EventDragging extends Interaction { // TODO: rename to EventSelectingAndDragging + // TODO: test this in IE11 + // QUESTION: why do we need it on the resizable??? + static SELECTOR = '.fc-event-draggable, .fc-event-resizable' + + dragging: FeaturefulElementDragging + hitDragging: HitDragging + + // internal state + subjectEl: HTMLElement | null = null + subjectSeg: Seg | null = null // the seg being selected/dragged + isDragging: boolean = false + eventRange: EventRenderRange | null = null + relevantEvents: EventStore | null = null // the events being dragged + receivingContext: CalendarContext | null = null + validMutation: EventMutation | null = null + mutatedRelevantEvents: EventStore | null = null + + constructor(settings: InteractionSettings) { + super(settings) + let { component } = this + let { options } = component.context + + let dragging = this.dragging = new FeaturefulElementDragging(settings.el) + dragging.pointer.selector = EventDragging.SELECTOR + dragging.touchScrollAllowed = false + dragging.autoScroller.isEnabled = options.dragScroll + + let 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) + } + + destroy() { + this.dragging.destroy() + } + + handlePointerDown = (ev: PointerDragEvent) => { + let origTarget = ev.origEvent.target as HTMLElement + let { component, dragging } = this + let { mirror } = dragging + let { options } = component.context + let initialContext = component.context + this.subjectEl = ev.subjectEl as HTMLElement + let subjectSeg = this.subjectSeg = getElSeg(ev.subjectEl as HTMLElement)! + let eventRange = this.eventRange = subjectSeg.eventRange! + let 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 + + let 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 as HTMLElement).classList.contains('fc-event-draggable') + } + + handleDragStart = (ev: PointerDragEvent) => { + let initialContext = this.component.context + let eventRange = this.eventRange! + let 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 }) + } + } 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 as MouseEvent, // Is this always a mouse event? See #4655 + view: initialContext.viewApi, + } as EventDragStartArg) + } + } + + handleHitUpdate = (hit: Hit | null, isFinal: boolean) => { + if (!this.isDragging) { + return + } + + let relevantEvents = this.relevantEvents! + let initialHit = this.hitDragging.initialHit! + let initialContext = this.component.context + + // states based on new hit + let receivingContext: CalendarContext | null = null + let mutation: EventMutation | null = null + let mutatedRelevantEvents: EventStore | null = null + let isInvalid = false + let interaction: EventInteractionState = { + affectedEvents: relevantEvents, + mutatedEvents: createEmptyEventStore(), + isEvent: true, + } + + if (hit) { + receivingContext = hit.context + let 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'), // TODO: turn className into constant + ) + + // assign states based on new hit + this.receivingContext = receivingContext + this.validMutation = mutation + this.mutatedRelevantEvents = mutatedRelevantEvents + } + } + + handlePointerUp = () => { + if (!this.isDragging) { + this.cleanup() // because handleDragEnd won't fire + } + } + + handleDragEnd = (ev: PointerDragEvent) => { + if (this.isDragging) { + let initialContext = this.component.context + let initialView = initialContext.viewApi + let { receivingContext, validMutation } = this + let eventDef = this.eventRange!.def + let eventInstance = this.eventRange!.instance + let eventApi = new EventApi(initialContext, eventDef, eventInstance) + let relevantEvents = this.relevantEvents! + let mutatedRelevantEvents = this.mutatedRelevantEvents! + let { finalHit } = this.hitDragging + + this.clearDrag() // must happen after revert animation + + initialContext.emitter.trigger('eventDragStop', { + el: this.subjectEl, + event: eventApi, + jsEvent: ev.origEvent as MouseEvent, // Is this always a mouse event? See #4655 + view: initialView, + } as EventDragStopArg) + + if (validMutation) { + // dropped within same calendar + if (receivingContext === initialContext) { + let updatedEventApi = new EventApi( + initialContext, + mutatedRelevantEvents.defs[eventDef.defId], + eventInstance ? mutatedRelevantEvents.instances[eventInstance.instanceId] : null, + ) + + initialContext.dispatch({ + type: 'MERGE_EVENTS', + eventStore: mutatedRelevantEvents, + }) + + let eventChangeArg: EventChangeArg = { + oldEvent: eventApi, + event: updatedEventApi, + relatedEvents: buildEventApis(mutatedRelevantEvents, initialContext, eventInstance), + revert() { + initialContext.dispatch({ + type: 'MERGE_EVENTS', + eventStore: relevantEvents, // the pre-change data + }) + }, + } + + let transformed: ReturnType = {} + for (let transformer of initialContext.getCurrentData().pluginHooks.eventDropTransformers) { + __assign(transformed, transformer(validMutation, initialContext)) + } + + initialContext.emitter.trigger('eventDrop', { + ...eventChangeArg, + ...transformed, + el: ev.subjectEl as HTMLElement, + delta: validMutation.datesDelta!, + jsEvent: ev.origEvent as MouseEvent, // bad + view: initialView, + }) + + initialContext.emitter.trigger('eventChange', eventChangeArg) + + // dropped in different calendar + } else if (receivingContext) { + let eventRemoveArg: EventRemoveArg = { + event: eventApi, + relatedEvents: buildEventApis(relevantEvents, initialContext, eventInstance), + revert() { + initialContext.dispatch({ + type: 'MERGE_EVENTS', + eventStore: relevantEvents, + }) + }, + } + + initialContext.emitter.trigger('eventLeave', { + ...eventRemoveArg, + draggedEl: ev.subjectEl as HTMLElement, + view: initialView, + }) + + initialContext.dispatch({ + type: 'REMOVE_EVENTS', + eventStore: relevantEvents, + }) + + initialContext.emitter.trigger('eventRemove', eventRemoveArg) + + let addedEventDef = mutatedRelevantEvents.defs[eventDef.defId] + let addedEventInstance = mutatedRelevantEvents.instances[eventInstance.instanceId] + let addedEventApi = new EventApi(receivingContext, addedEventDef, addedEventInstance) + + receivingContext.dispatch({ + type: 'MERGE_EVENTS', + eventStore: mutatedRelevantEvents, + }) + + let eventAddArg: EventAddArg = { + event: addedEventApi, + relatedEvents: buildEventApis(mutatedRelevantEvents, receivingContext, addedEventInstance), + revert() { + receivingContext.dispatch({ + type: 'REMOVE_EVENTS', + eventStore: mutatedRelevantEvents, + }) + }, + } + + receivingContext.emitter.trigger('eventAdd', eventAddArg) + + if (ev.isTouch) { + receivingContext.dispatch({ + type: 'SELECT_EVENT', + eventInstanceId: eventInstance.instanceId, + }) + } + + receivingContext.emitter.trigger('drop', { + ...buildDatePointApiWithContext(finalHit.dateSpan, receivingContext), + draggedEl: ev.subjectEl as HTMLElement, + jsEvent: ev.origEvent as MouseEvent, // Is this always a mouse event? See #4655 + view: finalHit.context.viewApi, + }) + + receivingContext.emitter.trigger('eventReceive', { + ...eventAddArg, + draggedEl: ev.subjectEl as HTMLElement, + view: finalHit.context.viewApi, + }) + } + } else { + initialContext.emitter.trigger('_noEventDrop') + } + } + + this.cleanup() + } + + // render a drag state on the next receivingCalendar + displayDrag(nextContext: CalendarContext | null, state: EventInteractionState) { + let initialContext = this.component.context + let 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 }) + } + } + + clearDrag() { + let initialCalendar = this.component.context + let { receivingContext } = this + + 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' }) + } + } + + cleanup() { // reset all internal state + this.subjectSeg = null + this.isDragging = false + this.eventRange = null + this.relevantEvents = null + this.receivingContext = null + this.validMutation = null + this.mutatedRelevantEvents = null + } +} + +function computeEventMutation(hit0: Hit, hit1: Hit, massagers: eventDragMutationMassager[]): EventMutation { + let dateSpan0 = hit0.dateSpan + let dateSpan1 = hit1.dateSpan + let date0 = dateSpan0.range.start + let date1 = dateSpan1.range.start + let standardProps = {} as any + + 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) + } + } + + let delta = diffDates( + date0, date1, + hit0.context.dateEnv, + hit0.componentId === hit1.componentId ? + hit0.largeUnit : + null, + ) + + if (delta.milliseconds) { // has hours/minutes/seconds + standardProps.allDay = false + } + + let mutation: EventMutation = { + datesDelta: delta, + standardProps, + } + + for (let massager of massagers) { + massager(mutation, hit0, hit1) + } + + return mutation +} + +function getComponentTouchDelay(component: DateComponent): number | null { + let { options } = component.context + let delay = options.eventLongPressDelay + + if (delay == null) { + delay = options.longPressDelay + } + + return delay +} diff --git a/apps/schoolCalendar/fullcalendar/interaction/src/interactions/EventResizing.ts b/apps/schoolCalendar/fullcalendar/interaction/src/interactions/EventResizing.ts new file mode 100644 index 000000000..013b399ae --- /dev/null +++ b/apps/schoolCalendar/fullcalendar/interaction/src/interactions/EventResizing.ts @@ -0,0 +1,263 @@ +import { + Seg, Hit, + EventMutation, applyMutationToEventStore, + elementClosest, + PointerDragEvent, + EventStore, getRelevantEvents, createEmptyEventStore, + diffDates, enableCursor, disableCursor, + DateRange, + EventApi, + EventRenderRange, getElSeg, + createDuration, + EventInteractionState, + Interaction, InteractionSettings, interactionSettingsToStore, ViewApi, Duration, EventChangeArg, buildEventApis, isInteractionValid, +} from '@fullcalendar/common' +import { __assign } from 'tslib' +import { HitDragging, isHitsEqual } from './HitDragging' +import { FeaturefulElementDragging } from '../dnd/FeaturefulElementDragging' + +export type EventResizeStartArg = EventResizeStartStopArg +export type EventResizeStopArg = EventResizeStartStopArg + +export interface EventResizeStartStopArg { + el: HTMLElement + event: EventApi + jsEvent: MouseEvent + view: ViewApi +} + +export interface EventResizeDoneArg extends EventChangeArg { + el: HTMLElement + startDelta: Duration + endDelta: Duration + jsEvent: MouseEvent + view: ViewApi +} + +export class EventResizing extends Interaction { + dragging: FeaturefulElementDragging + hitDragging: HitDragging + + // internal state + draggingSegEl: HTMLElement | null = null + draggingSeg: Seg | null = null // TODO: rename to resizingSeg? subjectSeg? + eventRange: EventRenderRange | null = null + relevantEvents: EventStore | null = null + validMutation: EventMutation | null = null + mutatedRelevantEvents: EventStore | null = null + + constructor(settings: InteractionSettings) { + super(settings) + let { component } = settings + + let dragging = this.dragging = new FeaturefulElementDragging(settings.el) + dragging.pointer.selector = '.fc-event-resizer' + dragging.touchScrollAllowed = false + dragging.autoScroller.isEnabled = component.context.options.dragScroll + + let 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) + } + + destroy() { + this.dragging.destroy() + } + + handlePointerDown = (ev: PointerDragEvent) => { + let { component } = this + let segEl = this.querySegEl(ev) + let seg = getElSeg(segEl) + let 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 as HTMLElement) || + (ev.isTouch && this.component.props.eventSelection !== eventRange.instance!.instanceId), + ) + } + + handleDragStart = (ev: PointerDragEvent) => { + let { context } = this.component + let eventRange = this.eventRange! + + this.relevantEvents = getRelevantEvents( + context.getCurrentData().eventStore, + this.eventRange.instance!.instanceId, + ) + + let 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 as MouseEvent, // Is this always a mouse event? See #4655 + view: context.viewApi, + } as EventResizeStartArg) + } + + handleHitUpdate = (hit: Hit | null, isFinal: boolean, ev: PointerDragEvent) => { + let { context } = this.component + let relevantEvents = this.relevantEvents! + let initialHit = this.hitDragging.initialHit! + let eventInstance = this.eventRange.instance! + let mutation: EventMutation | null = null + let mutatedRelevantEvents: EventStore | null = null + let isInvalid = false + let interaction: EventInteractionState = { + affectedEvents: relevantEvents, + mutatedEvents: createEmptyEventStore(), + isEvent: true, + } + + if (hit) { + let disallowed = hit.componentId === initialHit.componentId + && this.isHitComboAllowed + && !this.isHitComboAllowed(initialHit, hit) + + if (!disallowed) { + mutation = computeMutation( + initialHit, + hit, + (ev.subjectEl as HTMLElement).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 + } + } + + handleDragEnd = (ev: PointerDragEvent) => { + let { context } = this.component + let eventDef = this.eventRange!.def + let eventInstance = this.eventRange!.instance + let eventApi = new EventApi(context, eventDef, eventInstance) + let relevantEvents = this.relevantEvents! + let mutatedRelevantEvents = this.mutatedRelevantEvents! + + context.emitter.trigger('eventResizeStop', { + el: this.draggingSegEl, + event: eventApi, + jsEvent: ev.origEvent as MouseEvent, // Is this always a mouse event? See #4655 + view: context.viewApi, + } as EventResizeStopArg) + + if (this.validMutation) { + let updatedEventApi = new EventApi( + context, + mutatedRelevantEvents.defs[eventDef.defId], + eventInstance ? mutatedRelevantEvents.instances[eventInstance.instanceId] : null, + ) + + context.dispatch({ + type: 'MERGE_EVENTS', + eventStore: mutatedRelevantEvents, + }) + + let eventChangeArg: EventChangeArg = { + oldEvent: eventApi, + event: updatedEventApi, + relatedEvents: buildEventApis(mutatedRelevantEvents, context, eventInstance), + revert() { + context.dispatch({ + type: 'MERGE_EVENTS', + eventStore: relevantEvents, // the pre-change events + }) + }, + } + + context.emitter.trigger('eventResize', { + ...eventChangeArg, + el: this.draggingSegEl, + startDelta: this.validMutation.startDelta || createDuration(0), + endDelta: this.validMutation.endDelta || createDuration(0), + jsEvent: ev.origEvent as MouseEvent, + 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 + } + + querySegEl(ev: PointerDragEvent) { + return elementClosest(ev.subjectEl as HTMLElement, '.fc-event') + } +} + +function computeMutation( + hit0: Hit, + hit1: Hit, + isFromStart: boolean, + instanceRange: DateRange, +): EventMutation | null { + let dateEnv = hit0.context.dateEnv + let date0 = hit0.dateSpan.range.start + let date1 = hit1.dateSpan.range.start + + let 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 +} diff --git a/apps/schoolCalendar/fullcalendar/interaction/src/interactions/HitDragging.ts b/apps/schoolCalendar/fullcalendar/interaction/src/interactions/HitDragging.ts new file mode 100644 index 000000000..d0a329e9c --- /dev/null +++ b/apps/schoolCalendar/fullcalendar/interaction/src/interactions/HitDragging.ts @@ -0,0 +1,220 @@ +import { + Emitter, PointerDragEvent, + isDateSpansEqual, + computeRect, + constrainPoint, intersectRects, getRectCenter, diffPoints, Point, + rangeContainsRange, + Hit, + InteractionSettingsStore, + mapHash, + ElementDragging, +} from '@fullcalendar/common' +import { OffsetTracker } from '../OffsetTracker' + +/* +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 +*/ +export class HitDragging { + droppableStore: InteractionSettingsStore + dragging: ElementDragging + emitter: Emitter + + // options that can be set by caller + useSubjectCenter: boolean = false + requireInitial: boolean = true // if doesn't start out on a hit, won't emit any events + + // internal state + offsetTrackers: { [componentUid: string]: OffsetTracker } + initialHit: Hit | null = null + movingHit: Hit | null = null + finalHit: Hit | null = null // won't ever be populated if shouldIgnoreMove + coordAdjust?: Point + + constructor(dragging: ElementDragging, droppableStore: InteractionSettingsStore) { + 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() + } + + handlePointerDown = (ev: PointerDragEvent) => { + let { dragging } = this + + 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) + } + } + + // sets initialHit + // sets coordAdjust + processFirstCoord(ev: PointerDragEvent) { + let origPoint = { left: ev.pageX, top: ev.pageY } + let adjustedPoint = origPoint + let subjectEl = ev.subjectEl + let subjectRect + + if (subjectEl instanceof HTMLElement) { // i.e. not a Document/ShadowRoot + subjectRect = computeRect(subjectEl) + adjustedPoint = constrainPoint(adjustedPoint, subjectRect) + } + + let initialHit = this.initialHit = this.queryHitForOffset(adjustedPoint.left, adjustedPoint.top) + if (initialHit) { + if (this.useSubjectCenter && subjectRect) { + let slicedSubjectRect = intersectRects(subjectRect, initialHit.rect) + if (slicedSubjectRect) { + adjustedPoint = getRectCenter(slicedSubjectRect) + } + } + + this.coordAdjust = diffPoints(adjustedPoint, origPoint) + } else { + this.coordAdjust = { left: 0, top: 0 } + } + } + + handleDragStart = (ev: PointerDragEvent) => { + this.emitter.trigger('dragstart', ev) + this.handleMove(ev, true) // force = fire even if initially null + } + + handleDragMove = (ev: PointerDragEvent) => { + this.emitter.trigger('dragmove', ev) + this.handleMove(ev) + } + + handlePointerUp = (ev: PointerDragEvent) => { + this.releaseHits() + this.emitter.trigger('pointerup', ev) + } + + handleDragEnd = (ev: PointerDragEvent) => { + if (this.movingHit) { + this.emitter.trigger('hitupdate', null, true, ev) + } + + this.finalHit = this.movingHit + this.movingHit = null + this.emitter.trigger('dragend', ev) + } + + handleMove(ev: PointerDragEvent, forceHandle?: boolean) { + let 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) + } + } + + prepareHits() { + this.offsetTrackers = mapHash(this.droppableStore, (interactionSettings) => { + interactionSettings.component.prepareHits() + return new OffsetTracker(interactionSettings.el) + }) + } + + releaseHits() { + let { offsetTrackers } = this + + for (let id in offsetTrackers) { + offsetTrackers[id].destroy() + } + + this.offsetTrackers = {} + } + + queryHitForOffset(offsetLeft: number, offsetTop: number): Hit | null { + let { droppableStore, offsetTrackers } = this + let bestHit: Hit | null = null + + for (let id in droppableStore) { + let component = droppableStore[id].component + let offsetTracker = offsetTrackers[id] + + if ( + offsetTracker && // wasn't destroyed mid-drag + offsetTracker.isWithinClipping(offsetLeft, offsetTop) + ) { + let originLeft = offsetTracker.computeLeft() + let originTop = offsetTracker.computeTop() + let positionLeft = offsetLeft - originLeft + let positionTop = offsetTop - originTop + let { origRect } = offsetTracker + let width = origRect.right - origRect.left + let height = origRect.bottom - origRect.top + + if ( + // must be within the element's bounds + positionLeft >= 0 && positionLeft < width && + positionTop >= 0 && positionTop < height + ) { + let 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 + } +} + +export function isHitsEqual(hit0: Hit | null, hit1: Hit | null): boolean { + if (!hit0 && !hit1) { + return true + } + + if (Boolean(hit0) !== Boolean(hit1)) { + return false + } + + return isDateSpansEqual(hit0!.dateSpan, hit1!.dateSpan) +} diff --git a/apps/schoolCalendar/fullcalendar/interaction/src/interactions/UnselectAuto.ts b/apps/schoolCalendar/fullcalendar/interaction/src/interactions/UnselectAuto.ts new file mode 100644 index 000000000..23e5b47cb --- /dev/null +++ b/apps/schoolCalendar/fullcalendar/interaction/src/interactions/UnselectAuto.ts @@ -0,0 +1,77 @@ +import { + DateSelectionApi, + PointerDragEvent, + elementClosest, + CalendarContext, + getEventTargetViaRoot, +} from '@fullcalendar/common' +import { PointerDragging } from '../dnd/PointerDragging' +import { EventDragging } from './EventDragging' + +export class UnselectAuto { + documentPointer: PointerDragging // for unfocusing + isRecentPointerDateSelect = false // wish we could use a selector to detect date selection, but uses hit system + matchesCancel = false + matchesEvent = false + + constructor(private context: CalendarContext) { + let 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) + } + + destroy() { + this.context.emitter.off('select', this.onSelect) + this.documentPointer.destroy() + } + + onSelect = (selectInfo: DateSelectionApi) => { + if (selectInfo.jsEvent) { + this.isRecentPointerDateSelect = true + } + } + + onDocumentPointerDown = (pev: PointerDragEvent) => { + let unselectCancel = this.context.options.unselectCancel + let downEl = getEventTargetViaRoot(pev.origEvent) as HTMLElement + + this.matchesCancel = !!elementClosest(downEl, unselectCancel) + this.matchesEvent = !!elementClosest(downEl, EventDragging.SELECTOR) // interaction started on an event? + } + + onDocumentPointerUp = (pev: PointerDragEvent) => { + let { context } = this + let { documentPointer } = this + let 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? + ) { + let 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 + } +} diff --git a/apps/schoolCalendar/fullcalendar/interaction/src/main.global.ts b/apps/schoolCalendar/fullcalendar/interaction/src/main.global.ts new file mode 100644 index 000000000..0af579774 --- /dev/null +++ b/apps/schoolCalendar/fullcalendar/interaction/src/main.global.ts @@ -0,0 +1,7 @@ +import { globalPlugins } from '@fullcalendar/common' +import plugin from './main' + +globalPlugins.push(plugin) + +export default plugin +export * from './main' diff --git a/apps/schoolCalendar/fullcalendar/interaction/src/main.ts b/apps/schoolCalendar/fullcalendar/interaction/src/main.ts new file mode 100644 index 000000000..f57049ca3 --- /dev/null +++ b/apps/schoolCalendar/fullcalendar/interaction/src/main.ts @@ -0,0 +1,23 @@ +import { createPlugin } from '@fullcalendar/common' +import { DateClicking } from './interactions/DateClicking' +import { DateSelecting } from './interactions/DateSelecting' +import { EventDragging } from './interactions/EventDragging' +import { EventResizing } from './interactions/EventResizing' +import { UnselectAuto } from './interactions/UnselectAuto' +import { FeaturefulElementDragging } from './dnd/FeaturefulElementDragging' +import { OPTION_REFINERS, LISTENER_REFINERS } from './options' +import './options-declare' + +export default createPlugin({ + componentInteractions: [DateClicking, DateSelecting, EventDragging, EventResizing], + calendarInteractions: [UnselectAuto], + elementDraggingImpl: FeaturefulElementDragging, + optionRefiners: OPTION_REFINERS, + listenerRefiners: LISTENER_REFINERS, +}) + +export * from './api-type-deps' +export { FeaturefulElementDragging } +export { PointerDragging } from './dnd/PointerDragging' +export { ExternalDraggable as Draggable } from './interactions-external/ExternalDraggable' +export { ThirdPartyDraggable } from './interactions-external/ThirdPartyDraggable' diff --git a/apps/schoolCalendar/fullcalendar/interaction/src/options-declare.ts b/apps/schoolCalendar/fullcalendar/interaction/src/options-declare.ts new file mode 100644 index 000000000..9cbc5a4a8 --- /dev/null +++ b/apps/schoolCalendar/fullcalendar/interaction/src/options-declare.ts @@ -0,0 +1,9 @@ +import { OPTION_REFINERS, LISTENER_REFINERS } from './options' + +type ExtraOptionRefiners = typeof OPTION_REFINERS +type ExtraListenerRefiners = typeof LISTENER_REFINERS + +declare module '@fullcalendar/common' { + interface BaseOptionRefiners extends ExtraOptionRefiners {} + interface CalendarListenerRefiners extends ExtraListenerRefiners {} +} diff --git a/apps/schoolCalendar/fullcalendar/interaction/src/options.ts b/apps/schoolCalendar/fullcalendar/interaction/src/options.ts new file mode 100644 index 000000000..a11ce5399 --- /dev/null +++ b/apps/schoolCalendar/fullcalendar/interaction/src/options.ts @@ -0,0 +1,26 @@ +import { identity, Identity, EventDropArg } from '@fullcalendar/common' + +// public +import { + DateClickArg, + EventDragStartArg, EventDragStopArg, + EventResizeStartArg, EventResizeStopArg, EventResizeDoneArg, + DropArg, EventReceiveArg, EventLeaveArg, +} from './api-type-deps' + +export const OPTION_REFINERS = { + fixedMirrorParent: identity as Identity, +} + +export const LISTENER_REFINERS = { + dateClick: identity as Identity<(arg: DateClickArg) => void>, + eventDragStart: identity as Identity<(arg: EventDragStartArg) => void>, + eventDragStop: identity as Identity<(arg: EventDragStopArg) => void>, + eventDrop: identity as Identity<(arg: EventDropArg) => void>, + eventResizeStart: identity as Identity<(arg: EventResizeStartArg) => void>, + eventResizeStop: identity as Identity<(arg: EventResizeStopArg) => void>, + eventResize: identity as Identity<(arg: EventResizeDoneArg) => void>, + drop: identity as Identity<(arg: DropArg) => void>, + eventReceive: identity as Identity<(arg: EventReceiveArg) => void>, + eventLeave: identity as Identity<(arg: EventLeaveArg) => void>, +} diff --git a/apps/schoolCalendar/fullcalendar/interaction/src/utils.ts b/apps/schoolCalendar/fullcalendar/interaction/src/utils.ts new file mode 100644 index 000000000..056a0040d --- /dev/null +++ b/apps/schoolCalendar/fullcalendar/interaction/src/utils.ts @@ -0,0 +1,38 @@ +import { DateSpan, CalendarContext, DatePointApi, DateEnv, ViewApi, EventApi } from '@fullcalendar/common' +import { __assign } from 'tslib' + +export interface DropArg extends DatePointApi { + draggedEl: HTMLElement + jsEvent: MouseEvent + view: ViewApi +} + +export type EventReceiveArg = EventReceiveLeaveArg +export type EventLeaveArg = EventReceiveLeaveArg +export interface EventReceiveLeaveArg { // will this become public? + draggedEl: HTMLElement + event: EventApi + relatedEvents: EventApi[] + revert: () => void + view: ViewApi +} + +export function buildDatePointApiWithContext(dateSpan: DateSpan, context: CalendarContext) { + let props = {} as DatePointApi + + for (let transform of context.pluginHooks.datePointTransforms) { + __assign(props, transform(dateSpan, context)) + } + + __assign(props, buildDatePointApi(dateSpan, context.dateEnv)) + + return props +} + +export function buildDatePointApi(span: DateSpan, dateEnv: DateEnv): DatePointApi { + return { + date: dateEnv.toDate(span.range.start), + dateStr: dateEnv.formatIso(span.range.start, { omitTime: span.allDay }), + allDay: span.allDay, + } +} diff --git a/apps/schoolCalendar/fullcalendar/interaction/tsconfig.json b/apps/schoolCalendar/fullcalendar/interaction/tsconfig.json new file mode 100644 index 000000000..6172b1a45 --- /dev/null +++ b/apps/schoolCalendar/fullcalendar/interaction/tsconfig.json @@ -0,0 +1,13 @@ +{ + "extends": "../../tsconfig.base", + "compilerOptions": { + "rootDir": "src", + "outDir": "tsc" + }, + "include": [ + "src/**/*" + ], + "references": [ + { "path": "../common" } + ] +} diff --git a/apps/schoolCalendar/fullcalendar/locales-all.js b/apps/schoolCalendar/fullcalendar/locales-all.js new file mode 100644 index 000000000..f8be47ef2 --- /dev/null +++ b/apps/schoolCalendar/fullcalendar/locales-all.js @@ -0,0 +1,1622 @@ +[].push.apply(FullCalendar.globalLocales, function () { + 'use strict'; + + var l0 = { + code: 'af', + week: { + dow: 1, // Maandag is die eerste dag van die week. + doy: 4, // Die week wat die 4de Januarie bevat is die eerste week van die jaar. + }, + buttonText: { + prev: 'Vorige', + next: 'Volgende', + today: 'Vandag', + year: 'Jaar', + month: 'Maand', + week: 'Week', + day: 'Dag', + list: 'Agenda', + }, + allDayText: 'Heeldag', + moreLinkText: 'Addisionele', + noEventsText: 'Daar is geen gebeurtenisse nie', + }; + + var l1 = { + code: 'ar-dz', + week: { + dow: 0, // Sunday is the first day of the week. + doy: 4, // The week that contains Jan 1st is the first week of the year. + }, + direction: 'rtl', + buttonText: { + prev: 'السابق', + next: 'التالي', + today: 'اليوم', + month: 'شهر', + week: 'أسبوع', + day: 'يوم', + list: 'أجندة', + }, + weekText: 'أسبوع', + allDayText: 'اليوم كله', + moreLinkText: 'أخرى', + noEventsText: 'أي أحداث لعرض', + }; + + var l2 = { + code: 'ar-kw', + week: { + dow: 0, // Sunday is the first day of the week. + doy: 12, // The week that contains Jan 1st is the first week of the year. + }, + direction: 'rtl', + buttonText: { + prev: 'السابق', + next: 'التالي', + today: 'اليوم', + month: 'شهر', + week: 'أسبوع', + day: 'يوم', + list: 'أجندة', + }, + weekText: 'أسبوع', + allDayText: 'اليوم كله', + moreLinkText: 'أخرى', + noEventsText: 'أي أحداث لعرض', + }; + + var l3 = { + code: 'ar-ly', + week: { + dow: 6, // Saturday is the first day of the week. + doy: 12, // The week that contains Jan 1st is the first week of the year. + }, + direction: 'rtl', + buttonText: { + prev: 'السابق', + next: 'التالي', + today: 'اليوم', + month: 'شهر', + week: 'أسبوع', + day: 'يوم', + list: 'أجندة', + }, + weekText: 'أسبوع', + allDayText: 'اليوم كله', + moreLinkText: 'أخرى', + noEventsText: 'أي أحداث لعرض', + }; + + var l4 = { + code: 'ar-ma', + week: { + dow: 6, // Saturday is the first day of the week. + doy: 12, // The week that contains Jan 1st is the first week of the year. + }, + direction: 'rtl', + buttonText: { + prev: 'السابق', + next: 'التالي', + today: 'اليوم', + month: 'شهر', + week: 'أسبوع', + day: 'يوم', + list: 'أجندة', + }, + weekText: 'أسبوع', + allDayText: 'اليوم كله', + moreLinkText: 'أخرى', + noEventsText: 'أي أحداث لعرض', + }; + + var l5 = { + code: 'ar-sa', + week: { + dow: 0, // Sunday is the first day of the week. + doy: 6, // The week that contains Jan 1st is the first week of the year. + }, + direction: 'rtl', + buttonText: { + prev: 'السابق', + next: 'التالي', + today: 'اليوم', + month: 'شهر', + week: 'أسبوع', + day: 'يوم', + list: 'أجندة', + }, + weekText: 'أسبوع', + allDayText: 'اليوم كله', + moreLinkText: 'أخرى', + noEventsText: 'أي أحداث لعرض', + }; + + var l6 = { + code: 'ar-tn', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + direction: 'rtl', + buttonText: { + prev: 'السابق', + next: 'التالي', + today: 'اليوم', + month: 'شهر', + week: 'أسبوع', + day: 'يوم', + list: 'أجندة', + }, + weekText: 'أسبوع', + allDayText: 'اليوم كله', + moreLinkText: 'أخرى', + noEventsText: 'أي أحداث لعرض', + }; + + var l7 = { + code: 'ar', + week: { + dow: 6, // Saturday is the first day of the week. + doy: 12, // The week that contains Jan 1st is the first week of the year. + }, + direction: 'rtl', + buttonText: { + prev: 'السابق', + next: 'التالي', + today: 'اليوم', + month: 'شهر', + week: 'أسبوع', + day: 'يوم', + list: 'أجندة', + }, + weekText: 'أسبوع', + allDayText: 'اليوم كله', + moreLinkText: 'أخرى', + noEventsText: 'أي أحداث لعرض', + }; + + var l8 = { + code: 'az', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + buttonText: { + prev: 'Əvvəl', + next: 'Sonra', + today: 'Bu Gün', + month: 'Ay', + week: 'Həftə', + day: 'Gün', + list: 'Gündəm', + }, + weekText: 'Həftə', + allDayText: 'Bütün Gün', + moreLinkText: function(n) { + return '+ daha çox ' + n + }, + noEventsText: 'Göstərmək üçün hadisə yoxdur', + }; + + var l9 = { + code: 'bg', + week: { + dow: 1, // Monday is the first day of the week. + doy: 7, // The week that contains Jan 1st is the first week of the year. + }, + buttonText: { + prev: 'назад', + next: 'напред', + today: 'днес', + month: 'Месец', + week: 'Седмица', + day: 'Ден', + list: 'График', + }, + allDayText: 'Цял ден', + moreLinkText: function(n) { + return '+още ' + n + }, + noEventsText: 'Няма събития за показване', + }; + + var l10 = { + code: 'bn', + week: { + dow: 0, // Sunday is the first day of the week. + doy: 6, // The week that contains Jan 1st is the first week of the year. + }, + buttonText: { + prev: 'পেছনে', + next: 'সামনে', + today: 'আজ', + month: 'মাস', + week: 'সপ্তাহ', + day: 'দিন', + list: 'তালিকা', + }, + weekText: 'সপ্তাহ', + allDayText: 'সারাদিন', + moreLinkText: function(n) { + return '+অন্যান্য ' + n + }, + noEventsText: 'কোনো ইভেন্ট নেই', + }; + + var l11 = { + code: 'bs', + week: { + dow: 1, // Monday is the first day of the week. + doy: 7, // The week that contains Jan 1st is the first week of the year. + }, + buttonText: { + prev: 'Prošli', + next: 'Sljedeći', + today: 'Danas', + month: 'Mjesec', + week: 'Sedmica', + day: 'Dan', + list: 'Raspored', + }, + weekText: 'Sed', + allDayText: 'Cijeli dan', + moreLinkText: function(n) { + return '+ još ' + n + }, + noEventsText: 'Nema događaja za prikazivanje', + }; + + var l12 = { + code: 'ca', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + buttonText: { + prev: 'Anterior', + next: 'Següent', + today: 'Avui', + month: 'Mes', + week: 'Setmana', + day: 'Dia', + list: 'Agenda', + }, + weekText: 'Set', + allDayText: 'Tot el dia', + moreLinkText: 'més', + noEventsText: 'No hi ha esdeveniments per mostrar', + }; + + var l13 = { + code: 'cs', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + buttonText: { + prev: 'Dříve', + next: 'Později', + today: 'Nyní', + month: 'Měsíc', + week: 'Týden', + day: 'Den', + list: 'Agenda', + }, + weekText: 'Týd', + allDayText: 'Celý den', + moreLinkText: function(n) { + return '+další: ' + n + }, + noEventsText: 'Žádné akce k zobrazení', + }; + + var l14 = { + code: 'cy', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + buttonText: { + prev: 'Blaenorol', + next: 'Nesaf', + today: 'Heddiw', + year: 'Blwyddyn', + month: 'Mis', + week: 'Wythnos', + day: 'Dydd', + list: 'Rhestr', + }, + weekText: 'Wythnos', + allDayText: 'Trwy\'r dydd', + moreLinkText: 'Mwy', + noEventsText: 'Dim digwyddiadau', + }; + + var l15 = { + code: 'da', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + buttonText: { + prev: 'Forrige', + next: 'Næste', + today: 'I dag', + month: 'Måned', + week: 'Uge', + day: 'Dag', + list: 'Agenda', + }, + weekText: 'Uge', + allDayText: 'Hele dagen', + moreLinkText: 'flere', + noEventsText: 'Ingen arrangementer at vise', + }; + + var l16 = { + code: 'de-at', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + buttonText: { + prev: 'Zurück', + next: 'Vor', + today: 'Heute', + year: 'Jahr', + month: 'Monat', + week: 'Woche', + day: 'Tag', + list: 'Terminübersicht', + }, + weekText: 'KW', + allDayText: 'Ganztägig', + moreLinkText: function(n) { + return '+ weitere ' + n + }, + noEventsText: 'Keine Ereignisse anzuzeigen', + }; + + var l17 = { + code: 'de', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + buttonText: { + prev: 'Zurück', + next: 'Vor', + today: 'Heute', + year: 'Jahr', + month: 'Monat', + week: 'Woche', + day: 'Tag', + list: 'Terminübersicht', + }, + weekText: 'KW', + allDayText: 'Ganztägig', + moreLinkText: function(n) { + return '+ weitere ' + n + }, + noEventsText: 'Keine Ereignisse anzuzeigen', + }; + + var l18 = { + code: 'el', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4st is the first week of the year. + }, + buttonText: { + prev: 'Προηγούμενος', + next: 'Επόμενος', + today: 'Σήμερα', + month: 'Μήνας', + week: 'Εβδομάδα', + day: 'Ημέρα', + list: 'Ατζέντα', + }, + weekText: 'Εβδ', + allDayText: 'Ολοήμερο', + moreLinkText: 'περισσότερα', + noEventsText: 'Δεν υπάρχουν γεγονότα προς εμφάνιση', + }; + + var l19 = { + code: 'en-au', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + }; + + var l20 = { + code: 'en-gb', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + }; + + var l21 = { + code: 'en-nz', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + }; + + var l22 = { + code: 'eo', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + buttonText: { + prev: 'Antaŭa', + next: 'Sekva', + today: 'Hodiaŭ', + month: 'Monato', + week: 'Semajno', + day: 'Tago', + list: 'Tagordo', + }, + weekText: 'Sm', + allDayText: 'Tuta tago', + moreLinkText: 'pli', + noEventsText: 'Neniuj eventoj por montri', + }; + + var l23 = { + code: 'es', + week: { + dow: 0, // Sunday is the first day of the week. + doy: 6, // The week that contains Jan 1st is the first week of the year. + }, + buttonText: { + prev: 'Ant', + next: 'Sig', + today: 'Hoy', + month: 'Mes', + week: 'Semana', + day: 'Día', + list: 'Agenda', + }, + weekText: 'Sm', + allDayText: 'Todo el día', + moreLinkText: 'más', + noEventsText: 'No hay eventos para mostrar', + }; + + var l24 = { + code: 'es', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + buttonText: { + prev: 'Ant', + next: 'Sig', + today: 'Hoy', + month: 'Mes', + week: 'Semana', + day: 'Día', + list: 'Agenda', + }, + weekText: 'Sm', + allDayText: 'Todo el día', + moreLinkText: 'más', + noEventsText: 'No hay eventos para mostrar', + }; + + var l25 = { + code: 'et', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + buttonText: { + prev: 'Eelnev', + next: 'Järgnev', + today: 'Täna', + month: 'Kuu', + week: 'Nädal', + day: 'Päev', + list: 'Päevakord', + }, + weekText: 'näd', + allDayText: 'Kogu päev', + moreLinkText: function(n) { + return '+ veel ' + n + }, + noEventsText: 'Kuvamiseks puuduvad sündmused', + }; + + var l26 = { + code: 'eu', + week: { + dow: 1, // Monday is the first day of the week. + doy: 7, // The week that contains Jan 1st is the first week of the year. + }, + buttonText: { + prev: 'Aur', + next: 'Hur', + today: 'Gaur', + month: 'Hilabetea', + week: 'Astea', + day: 'Eguna', + list: 'Agenda', + }, + weekText: 'As', + allDayText: 'Egun osoa', + moreLinkText: 'gehiago', + noEventsText: 'Ez dago ekitaldirik erakusteko', + }; + + var l27 = { + code: 'fa', + week: { + dow: 6, // Saturday is the first day of the week. + doy: 12, // The week that contains Jan 1st is the first week of the year. + }, + direction: 'rtl', + buttonText: { + prev: 'قبلی', + next: 'بعدی', + today: 'امروز', + month: 'ماه', + week: 'هفته', + day: 'روز', + list: 'برنامه', + }, + weekText: 'هف', + allDayText: 'تمام روز', + moreLinkText: function(n) { + return 'بیش از ' + n + }, + noEventsText: 'هیچ رویدادی به نمایش', + }; + + var l28 = { + code: 'fi', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + buttonText: { + prev: 'Edellinen', + next: 'Seuraava', + today: 'Tänään', + month: 'Kuukausi', + week: 'Viikko', + day: 'Päivä', + list: 'Tapahtumat', + }, + weekText: 'Vk', + allDayText: 'Koko päivä', + moreLinkText: 'lisää', + noEventsText: 'Ei näytettäviä tapahtumia', + }; + + var l29 = { + code: 'fr', + buttonText: { + prev: 'Précédent', + next: 'Suivant', + today: "Aujourd'hui", + year: 'Année', + month: 'Mois', + week: 'Semaine', + day: 'Jour', + list: 'Mon planning', + }, + weekText: 'Sem.', + allDayText: 'Toute la journée', + moreLinkText: 'en plus', + noEventsText: 'Aucun événement à afficher', + }; + + var l30 = { + code: 'fr-ch', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + buttonText: { + prev: 'Précédent', + next: 'Suivant', + today: 'Courant', + year: 'Année', + month: 'Mois', + week: 'Semaine', + day: 'Jour', + list: 'Mon planning', + }, + weekText: 'Sm', + allDayText: 'Toute la journée', + moreLinkText: 'en plus', + noEventsText: 'Aucun événement à afficher', + }; + + var l31 = { + code: 'fr', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + buttonText: { + prev: 'Précédent', + next: 'Suivant', + today: "Aujourd'hui", + year: 'Année', + month: 'Mois', + week: 'Semaine', + day: 'Jour', + list: 'Planning', + }, + weekText: 'Sem.', + allDayText: 'Toute la journée', + moreLinkText: 'en plus', + noEventsText: 'Aucun événement à afficher', + }; + + var l32 = { + code: 'gl', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + buttonText: { + prev: 'Ant', + next: 'Seg', + today: 'Hoxe', + month: 'Mes', + week: 'Semana', + day: 'Día', + list: 'Axenda', + }, + weekText: 'Sm', + allDayText: 'Todo o día', + moreLinkText: 'máis', + noEventsText: 'Non hai eventos para amosar', + }; + + var l33 = { + code: 'he', + direction: 'rtl', + buttonText: { + prev: 'הקודם', + next: 'הבא', + today: 'היום', + month: 'חודש', + week: 'שבוע', + day: 'יום', + list: 'סדר יום', + }, + allDayText: 'כל היום', + moreLinkText: 'אחר', + noEventsText: 'אין אירועים להצגה', + weekText: 'שבוע', + }; + + var l34 = { + code: 'hi', + week: { + dow: 0, // Sunday is the first day of the week. + doy: 6, // The week that contains Jan 1st is the first week of the year. + }, + buttonText: { + prev: 'पिछला', + next: 'अगला', + today: 'आज', + month: 'महीना', + week: 'सप्ताह', + day: 'दिन', + list: 'कार्यसूची', + }, + weekText: 'हफ्ता', + allDayText: 'सभी दिन', + moreLinkText: function(n) { + return '+अधिक ' + n + }, + noEventsText: 'कोई घटनाओं को प्रदर्शित करने के लिए', + }; + + var l35 = { + code: 'hr', + week: { + dow: 1, // Monday is the first day of the week. + doy: 7, // The week that contains Jan 1st is the first week of the year. + }, + buttonText: { + prev: 'Prijašnji', + next: 'Sljedeći', + today: 'Danas', + month: 'Mjesec', + week: 'Tjedan', + day: 'Dan', + list: 'Raspored', + }, + weekText: 'Tje', + allDayText: 'Cijeli dan', + moreLinkText: function(n) { + return '+ još ' + n + }, + noEventsText: 'Nema događaja za prikaz', + }; + + var l36 = { + code: 'hu', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + buttonText: { + prev: 'vissza', + next: 'előre', + today: 'ma', + month: 'Hónap', + week: 'Hét', + day: 'Nap', + list: 'Lista', + }, + weekText: 'Hét', + allDayText: 'Egész nap', + moreLinkText: 'további', + noEventsText: 'Nincs megjeleníthető esemény', + }; + + var l37 = { + code: 'hy-am', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + buttonText: { + prev: 'Նախորդ', + next: 'Հաջորդ', + today: 'Այսօր', + month: 'Ամիս', + week: 'Շաբաթ', + day: 'Օր', + list: 'Օրվա ցուցակ', + }, + weekText: 'Շաբ', + allDayText: 'Ամբողջ օր', + moreLinkText: function(n) { + return '+ ևս ' + n + }, + noEventsText: 'Բացակայում է իրադարձությունը ցուցադրելու', + }; + + var l38 = { + code: 'id', + week: { + dow: 1, // Monday is the first day of the week. + doy: 7, // The week that contains Jan 1st is the first week of the year. + }, + buttonText: { + prev: 'mundur', + next: 'maju', + today: 'hari ini', + month: 'Bulan', + week: 'Minggu', + day: 'Hari', + list: 'Agenda', + }, + weekText: 'Mg', + allDayText: 'Sehari penuh', + moreLinkText: 'lebih', + noEventsText: 'Tidak ada acara untuk ditampilkan', + }; + + var l39 = { + code: 'is', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + buttonText: { + prev: 'Fyrri', + next: 'Næsti', + today: 'Í dag', + month: 'Mánuður', + week: 'Vika', + day: 'Dagur', + list: 'Dagskrá', + }, + weekText: 'Vika', + allDayText: 'Allan daginn', + moreLinkText: 'meira', + noEventsText: 'Engir viðburðir til að sýna', + }; + + var l40 = { + code: 'it', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + buttonText: { + prev: 'Prec', + next: 'Succ', + today: 'Oggi', + month: 'Mese', + week: 'Settimana', + day: 'Giorno', + list: 'Agenda', + }, + weekText: 'Sm', + allDayText: 'Tutto il giorno', + moreLinkText: function(n) { + return '+altri ' + n + }, + noEventsText: 'Non ci sono eventi da visualizzare', + }; + + var l41 = { + code: 'ja', + buttonText: { + prev: '前', + next: '次', + today: '今日', + month: '月', + week: '週', + day: '日', + list: '予定リスト', + }, + weekText: '週', + allDayText: '終日', + moreLinkText: function(n) { + return '他 ' + n + ' 件' + }, + noEventsText: '表示する予定はありません', + }; + + var l42 = { + code: 'ka', + week: { + dow: 1, + doy: 7, + }, + buttonText: { + prev: 'წინა', + next: 'შემდეგი', + today: 'დღეს', + month: 'თვე', + week: 'კვირა', + day: 'დღე', + list: 'დღის წესრიგი', + }, + weekText: 'კვ', + allDayText: 'მთელი დღე', + moreLinkText: function(n) { + return '+ კიდევ ' + n + }, + noEventsText: 'ღონისძიებები არ არის', + }; + + var l43 = { + code: 'kk', + week: { + dow: 1, // Monday is the first day of the week. + doy: 7, // The week that contains Jan 1st is the first week of the year. + }, + buttonText: { + prev: 'Алдыңғы', + next: 'Келесі', + today: 'Бүгін', + month: 'Ай', + week: 'Апта', + day: 'Күн', + list: 'Күн тәртібі', + }, + weekText: 'Не', + allDayText: 'Күні бойы', + moreLinkText: function(n) { + return '+ тағы ' + n + }, + noEventsText: 'Көрсету үшін оқиғалар жоқ', + }; + + var l44 = { + code: 'km', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + buttonText: { + prev: 'មុន', + next: 'បន្ទាប់', + today: 'ថ្ងៃនេះ', + year: 'ឆ្នាំ', + month: 'ខែ', + week: 'សប្តាហ៍', + day: 'ថ្ងៃ', + list: 'បញ្ជី', + }, + weekText: 'សប្តាហ៍', + allDayText: 'ពេញមួយថ្ងៃ', + moreLinkText: 'ច្រើនទៀត', + noEventsText: 'គ្មានព្រឹត្តិការណ៍ត្រូវបង្ហាញ', + }; + + var l45 = { + code: 'ko', + buttonText: { + prev: '이전달', + next: '다음달', + today: '오늘', + month: '월', + week: '주', + day: '일', + list: '일정목록', + }, + weekText: '주', + allDayText: '종일', + moreLinkText: '개', + noEventsText: '일정이 없습니다', + }; + + var l46 = { + code: 'ku', + week: { + dow: 6, // Saturday is the first day of the week. + doy: 12, // The week that contains Jan 1st is the first week of the year. + }, + direction: 'rtl', + buttonText: { + prev: 'پێشتر', + next: 'دواتر', + today: 'ئەمڕو', + month: 'مانگ', + week: 'هەفتە', + day: 'ڕۆژ', + list: 'بەرنامە', + }, + weekText: 'هەفتە', + allDayText: 'هەموو ڕۆژەکە', + moreLinkText: 'زیاتر', + noEventsText: 'هیچ ڕووداوێك نیە', + }; + + var l47 = { + code: 'lb', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + buttonText: { + prev: 'Zréck', + next: 'Weider', + today: 'Haut', + month: 'Mount', + week: 'Woch', + day: 'Dag', + list: 'Terminiwwersiicht', + }, + weekText: 'W', + allDayText: 'Ganzen Dag', + moreLinkText: 'méi', + noEventsText: 'Nee Evenementer ze affichéieren', + }; + + var l48 = { + code: 'lt', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + buttonText: { + prev: 'Atgal', + next: 'Pirmyn', + today: 'Šiandien', + month: 'Mėnuo', + week: 'Savaitė', + day: 'Diena', + list: 'Darbotvarkė', + }, + weekText: 'SAV', + allDayText: 'Visą dieną', + moreLinkText: 'daugiau', + noEventsText: 'Nėra įvykių rodyti', + }; + + var l49 = { + code: 'lv', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + buttonText: { + prev: 'Iepr.', + next: 'Nāk.', + today: 'Šodien', + month: 'Mēnesis', + week: 'Nedēļa', + day: 'Diena', + list: 'Dienas kārtība', + }, + weekText: 'Ned.', + allDayText: 'Visu dienu', + moreLinkText: function(n) { + return '+vēl ' + n + }, + noEventsText: 'Nav notikumu', + }; + + var l50 = { + code: 'mk', + buttonText: { + prev: 'претходно', + next: 'следно', + today: 'Денес', + month: 'Месец', + week: 'Недела', + day: 'Ден', + list: 'График', + }, + weekText: 'Сед', + allDayText: 'Цел ден', + moreLinkText: function(n) { + return '+повеќе ' + n + }, + noEventsText: 'Нема настани за прикажување', + }; + + var l51 = { + code: 'ms', + week: { + dow: 1, // Monday is the first day of the week. + doy: 7, // The week that contains Jan 1st is the first week of the year. + }, + buttonText: { + prev: 'Sebelum', + next: 'Selepas', + today: 'hari ini', + month: 'Bulan', + week: 'Minggu', + day: 'Hari', + list: 'Agenda', + }, + weekText: 'Mg', + allDayText: 'Sepanjang hari', + moreLinkText: function(n) { + return 'masih ada ' + n + ' acara' + }, + noEventsText: 'Tiada peristiwa untuk dipaparkan', + }; + + var l52 = { + code: 'nb', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + buttonText: { + prev: 'Forrige', + next: 'Neste', + today: 'I dag', + month: 'Måned', + week: 'Uke', + day: 'Dag', + list: 'Agenda', + }, + weekText: 'Uke', + allDayText: 'Hele dagen', + moreLinkText: 'til', + noEventsText: 'Ingen hendelser å vise', + }; + + var l53 = { + code: 'ne', // code for nepal + week: { + dow: 7, // Sunday is the first day of the week. + doy: 1, // The week that contains Jan 1st is the first week of the year. + }, + buttonText: { + prev: 'अघिल्लो', + next: 'अर्को', + today: 'आज', + month: 'महिना', + week: 'हप्ता', + day: 'दिन', + list: 'सूची', + }, + weekText: 'हप्ता', + allDayText: 'दिनभरि', + moreLinkText: 'थप लिंक', + noEventsText: 'देखाउनको लागि कुनै घटनाहरू छैनन्', + }; + + var l54 = { + code: 'nl', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + buttonText: { + prev: 'Vorige', + next: 'Volgende', + today: 'Vandaag', + year: 'Jaar', + month: 'Maand', + week: 'Week', + day: 'Dag', + list: 'Agenda', + }, + allDayText: 'Hele dag', + moreLinkText: 'extra', + noEventsText: 'Geen evenementen om te laten zien', + }; + + var l55 = { + code: 'nn', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + buttonText: { + prev: 'Førre', + next: 'Neste', + today: 'I dag', + month: 'Månad', + week: 'Veke', + day: 'Dag', + list: 'Agenda', + }, + weekText: 'Veke', + allDayText: 'Heile dagen', + moreLinkText: 'til', + noEventsText: 'Ingen hendelser å vise', + }; + + var l56 = { + code: 'pl', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + buttonText: { + prev: 'Poprzedni', + next: 'Następny', + today: 'Dziś', + month: 'Miesiąc', + week: 'Tydzień', + day: 'Dzień', + list: 'Plan dnia', + }, + weekText: 'Tydz', + allDayText: 'Cały dzień', + moreLinkText: 'więcej', + noEventsText: 'Brak wydarzeń do wyświetlenia', + }; + + var l57 = { + code: 'pt-br', + buttonText: { + prev: 'Anterior', + next: 'Próximo', + today: 'Hoje', + month: 'Mês', + week: 'Semana', + day: 'Dia', + list: 'Lista', + }, + weekText: 'Sm', + allDayText: 'dia inteiro', + moreLinkText: function(n) { + return 'mais +' + n + }, + noEventsText: 'Não há eventos para mostrar', + }; + + var l58 = { + code: 'pt', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + buttonText: { + prev: 'Anterior', + next: 'Seguinte', + today: 'Hoje', + month: 'Mês', + week: 'Semana', + day: 'Dia', + list: 'Agenda', + }, + weekText: 'Sem', + allDayText: 'Todo o dia', + moreLinkText: 'mais', + noEventsText: 'Não há eventos para mostrar', + }; + + var l59 = { + code: 'ro', + week: { + dow: 1, // Monday is the first day of the week. + doy: 7, // The week that contains Jan 1st is the first week of the year. + }, + buttonText: { + prev: 'precedentă', + next: 'următoare', + today: 'Azi', + month: 'Lună', + week: 'Săptămână', + day: 'Zi', + list: 'Agendă', + }, + weekText: 'Săpt', + allDayText: 'Toată ziua', + moreLinkText: function(n) { + return '+alte ' + n + }, + noEventsText: 'Nu există evenimente de afișat', + }; + + var l60 = { + code: 'ru', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + buttonText: { + prev: 'Пред', + next: 'След', + today: 'Сегодня', + month: 'Месяц', + week: 'Неделя', + day: 'День', + list: 'Повестка дня', + }, + weekText: 'Нед', + allDayText: 'Весь день', + moreLinkText: function(n) { + return '+ ещё ' + n + }, + noEventsText: 'Нет событий для отображения', + }; + + var l61 = { + code: 'sk', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + buttonText: { + prev: 'Predchádzajúci', + next: 'Nasledujúci', + today: 'Dnes', + month: 'Mesiac', + week: 'Týždeň', + day: 'Deň', + list: 'Rozvrh', + }, + weekText: 'Ty', + allDayText: 'Celý deň', + moreLinkText: function(n) { + return '+ďalšie: ' + n + }, + noEventsText: 'Žiadne akcie na zobrazenie', + }; + + var l62 = { + code: 'sl', + week: { + dow: 1, // Monday is the first day of the week. + doy: 7, // The week that contains Jan 1st is the first week of the year. + }, + buttonText: { + prev: 'Prejšnji', + next: 'Naslednji', + today: 'Trenutni', + month: 'Mesec', + week: 'Teden', + day: 'Dan', + list: 'Dnevni red', + }, + weekText: 'Teden', + allDayText: 'Ves dan', + moreLinkText: 'več', + noEventsText: 'Ni dogodkov za prikaz', + }; + + var l63 = { + code: 'sm', + buttonText: { + prev: 'Talu ai', + next: 'Mulimuli atu', + today: 'Aso nei', + month: 'Masina', + week: 'Vaiaso', + day: 'Aso', + list: 'Faasologa', + }, + weekText: 'Vaiaso', + allDayText: 'Aso atoa', + moreLinkText: 'sili atu', + noEventsText: 'Leai ni mea na tutupu', + }; + + var l64 = { + code: 'sq', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + buttonText: { + prev: 'mbrapa', + next: 'Përpara', + today: 'sot', + month: 'Muaj', + week: 'Javë', + day: 'Ditë', + list: 'Listë', + }, + weekText: 'Ja', + allDayText: 'Gjithë ditën', + moreLinkText: function(n) { + return '+më tepër ' + n + }, + noEventsText: 'Nuk ka evente për të shfaqur', + }; + + var l65 = { + code: 'sr-cyrl', + week: { + dow: 1, // Monday is the first day of the week. + doy: 7, // The week that contains Jan 1st is the first week of the year. + }, + buttonText: { + prev: 'Претходна', + next: 'следећи', + today: 'Данас', + month: 'Месец', + week: 'Недеља', + day: 'Дан', + list: 'Планер', + }, + weekText: 'Сед', + allDayText: 'Цео дан', + moreLinkText: function(n) { + return '+ још ' + n + }, + noEventsText: 'Нема догађаја за приказ', + }; + + var l66 = { + code: 'sr', + week: { + dow: 1, // Monday is the first day of the week. + doy: 7, // The week that contains Jan 1st is the first week of the year. + }, + buttonText: { + prev: 'Prethodna', + next: 'Sledeći', + today: 'Danas', + month: 'Mеsеc', + week: 'Nеdеlja', + day: 'Dan', + list: 'Planеr', + }, + weekText: 'Sed', + allDayText: 'Cеo dan', + moreLinkText: function(n) { + return '+ još ' + n + }, + noEventsText: 'Nеma događaja za prikaz', + }; + + var l67 = { + code: 'sv', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + buttonText: { + prev: 'Förra', + next: 'Nästa', + today: 'Idag', + month: 'Månad', + week: 'Vecka', + day: 'Dag', + list: 'Program', + }, + weekText: 'v.', + allDayText: 'Heldag', + moreLinkText: 'till', + noEventsText: 'Inga händelser att visa', + }; + + var l68 = { + code: 'ta-in', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + buttonText: { + prev: 'முந்தைய', + next: 'அடுத்தது', + today: 'இன்று', + month: 'மாதம்', + week: 'வாரம்', + day: 'நாள்', + list: 'தினசரி அட்டவணை', + }, + weekText: 'வாரம்', + allDayText: 'நாள் முழுவதும்', + moreLinkText: function(n) { + return '+ மேலும் ' + n + }, + noEventsText: 'காண்பிக்க நிகழ்வுகள் இல்லை', + }; + + var l69 = { + code: 'th', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + buttonText: { + prev: 'ก่อนหน้า', + next: 'ถัดไป', + prevYear: 'ปีก่อนหน้า', + nextYear: 'ปีถัดไป', + year: 'ปี', + today: 'วันนี้', + month: 'เดือน', + week: 'สัปดาห์', + day: 'วัน', + list: 'กำหนดการ', + }, + weekText: 'สัปดาห์', + allDayText: 'ตลอดวัน', + moreLinkText: 'เพิ่มเติม', + noEventsText: 'ไม่มีกิจกรรมที่จะแสดง', + }; + + var l70 = { + code: 'tr', + week: { + dow: 1, // Monday is the first day of the week. + doy: 7, // The week that contains Jan 1st is the first week of the year. + }, + buttonText: { + prev: 'geri', + next: 'ileri', + today: 'bugün', + month: 'Ay', + week: 'Hafta', + day: 'Gün', + list: 'Ajanda', + }, + weekText: 'Hf', + allDayText: 'Tüm gün', + moreLinkText: 'daha fazla', + noEventsText: 'Gösterilecek etkinlik yok', + }; + + var l71 = { + code: 'ug', + buttonText: { + month: 'ئاي', + week: 'ھەپتە', + day: 'كۈن', + list: 'كۈنتەرتىپ', + }, + allDayText: 'پۈتۈن كۈن', + }; + + var l72 = { + code: 'uk', + week: { + dow: 1, // Monday is the first day of the week. + doy: 7, // The week that contains Jan 1st is the first week of the year. + }, + buttonText: { + prev: 'Попередній', + next: 'далі', + today: 'Сьогодні', + month: 'Місяць', + week: 'Тиждень', + day: 'День', + list: 'Порядок денний', + }, + weekText: 'Тиж', + allDayText: 'Увесь день', + moreLinkText: function(n) { + return '+ще ' + n + '...' + }, + noEventsText: 'Немає подій для відображення', + }; + + var l73 = { + code: 'uz', + buttonText: { + month: 'Oy', + week: 'Xafta', + day: 'Kun', + list: 'Kun tartibi', + }, + allDayText: "Kun bo'yi", + moreLinkText: function(n) { + return '+ yana ' + n + }, + noEventsText: "Ko'rsatish uchun voqealar yo'q", + }; + + var l74 = { + code: 'vi', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + buttonText: { + prev: 'Trước', + next: 'Tiếp', + today: 'Hôm nay', + month: 'Tháng', + week: 'Tuần', + day: 'Ngày', + list: 'Lịch biểu', + }, + weekText: 'Tu', + allDayText: 'Cả ngày', + moreLinkText: function(n) { + return '+ thêm ' + n + }, + noEventsText: 'Không có sự kiện để hiển thị', + }; + + var l75 = { + code: 'zh-cn', + week: { + // GB/T 7408-1994《数据元和交换格式·信息交换·日期和时间表示法》与ISO 8601:1988等效 + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + buttonText: { + prev: '上月', + next: '下月', + today: '今天', + month: '月', + week: '周', + day: '日', + list: '日程', + }, + weekText: '周', + allDayText: '全天', + moreLinkText: function(n) { + return '另外 ' + n + ' 个' + }, + noEventsText: '没有事件显示', + }; + + var l76 = { + code: 'zh-tw', + buttonText: { + prev: '上月', + next: '下月', + today: '今天', + month: '月', + week: '週', + day: '天', + list: '活動列表', + }, + weekText: '周', + allDayText: '整天', + moreLinkText: '顯示更多', + noEventsText: '没有任何活動', + }; + + /* eslint max-len: off */ + + var localesAll = [ + l0, l1, l2, l3, l4, l5, l6, l7, l8, l9, l10, l11, l12, l13, l14, l15, l16, l17, l18, l19, l20, l21, l22, l23, l24, l25, l26, l27, l28, l29, l30, l31, l32, l33, l34, l35, l36, l37, l38, l39, l40, l41, l42, l43, l44, l45, l46, l47, l48, l49, l50, l51, l52, l53, l54, l55, l56, l57, l58, l59, l60, l61, l62, l63, l64, l65, l66, l67, l68, l69, l70, l71, l72, l73, l74, l75, l76, + ]; + + return localesAll; + +}()); diff --git a/apps/schoolCalendar/fullcalendar/main.css b/apps/schoolCalendar/fullcalendar/main.css new file mode 100644 index 000000000..957887b56 --- /dev/null +++ b/apps/schoolCalendar/fullcalendar/main.css @@ -0,0 +1,1446 @@ + +/* classes attached to */ +/* TODO: make fc-event selector work when calender in shadow DOM */ +.fc-not-allowed, +.fc-not-allowed .fc-event { /* override events' custom cursors */ + cursor: not-allowed; +} + +/* TODO: not attached to body. attached to specific els. move */ +.fc-unselectable { + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + -webkit-touch-callout: none; + -webkit-tap-highlight-color: rgba(0, 0, 0, 0); +} +.fc { + /* layout of immediate children */ + display: flex; + flex-direction: column; + + font-size: 1em +} +.fc, + .fc *, + .fc *:before, + .fc *:after { + box-sizing: border-box; + } +.fc table { + border-collapse: collapse; + border-spacing: 0; + font-size: 1em; /* normalize cross-browser */ + } +.fc th { + text-align: center; + } +.fc th, + .fc td { + vertical-align: top; + padding: 0; + } +.fc a[data-navlink] { + cursor: pointer; + } +.fc a[data-navlink]:hover { + text-decoration: underline; + } +.fc-direction-ltr { + direction: ltr; + text-align: left; +} +.fc-direction-rtl { + direction: rtl; + text-align: right; +} +.fc-theme-standard td, + .fc-theme-standard th { + border: 1px solid #ddd; + border: 1px solid var(--fc-border-color, #ddd); + } +/* for FF, which doesn't expand a 100% div within a table cell. use absolute positioning */ +/* inner-wrappers are responsible for being absolute */ +/* TODO: best place for this? */ +.fc-liquid-hack td, + .fc-liquid-hack th { + position: relative; + } + +@font-face { + font-family: 'fcicons'; + src: url("data:application/x-font-ttf;charset=utf-8;base64,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") format('truetype'); + font-weight: normal; + font-style: normal; +} + +.fc-icon { + /* added for fc */ + display: inline-block; + width: 1em; + height: 1em; + text-align: center; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + + /* use !important to prevent issues with browser extensions that change fonts */ + font-family: 'fcicons' !important; + speak: none; + font-style: normal; + font-weight: normal; + font-variant: normal; + text-transform: none; + line-height: 1; + + /* Better Font Rendering =========== */ + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +.fc-icon-chevron-left:before { + content: "\e900"; +} + +.fc-icon-chevron-right:before { + content: "\e901"; +} + +.fc-icon-chevrons-left:before { + content: "\e902"; +} + +.fc-icon-chevrons-right:before { + content: "\e903"; +} + +.fc-icon-minus-square:before { + content: "\e904"; +} + +.fc-icon-plus-square:before { + content: "\e905"; +} + +.fc-icon-x:before { + content: "\e906"; +} +/* +Lots taken from Flatly (MIT): https://bootswatch.com/4/flatly/bootstrap.css + +These styles only apply when the standard-theme is activated. +When it's NOT activated, the fc-button classes won't even be in the DOM. +*/ +.fc { + + /* reset */ + +} +.fc .fc-button { + border-radius: 0; + overflow: visible; + text-transform: none; + margin: 0; + font-family: inherit; + font-size: inherit; + line-height: inherit; + } +.fc .fc-button:focus { + outline: 1px dotted; + outline: 5px auto -webkit-focus-ring-color; + } +.fc .fc-button { + -webkit-appearance: button; + } +.fc .fc-button:not(:disabled) { + cursor: pointer; + } +.fc .fc-button::-moz-focus-inner { + padding: 0; + border-style: none; + } +.fc { + + /* theme */ + +} +.fc .fc-button { + display: inline-block; + font-weight: 400; + text-align: center; + vertical-align: middle; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + background-color: transparent; + border: 1px solid transparent; + padding: 0.4em 0.65em; + font-size: 1em; + line-height: 1.5; + border-radius: 0.25em; + } +.fc .fc-button:hover { + text-decoration: none; + } +.fc .fc-button:focus { + outline: 0; + box-shadow: 0 0 0 0.2rem rgba(44, 62, 80, 0.25); + } +.fc .fc-button:disabled { + opacity: 0.65; + } +.fc { + + /* "primary" coloring */ + +} +.fc .fc-button-primary { + color: #fff; + color: var(--fc-button-text-color, #fff); + background-color: #2C3E50; + background-color: var(--fc-button-bg-color, #2C3E50); + border-color: #2C3E50; + border-color: var(--fc-button-border-color, #2C3E50); + } +.fc .fc-button-primary:hover { + color: #fff; + color: var(--fc-button-text-color, #fff); + background-color: #1e2b37; + background-color: var(--fc-button-hover-bg-color, #1e2b37); + border-color: #1a252f; + border-color: var(--fc-button-hover-border-color, #1a252f); + } +.fc .fc-button-primary:disabled { /* not DRY */ + color: #fff; + color: var(--fc-button-text-color, #fff); + background-color: #2C3E50; + background-color: var(--fc-button-bg-color, #2C3E50); + border-color: #2C3E50; + border-color: var(--fc-button-border-color, #2C3E50); /* overrides :hover */ + } +.fc .fc-button-primary:focus { + box-shadow: 0 0 0 0.2rem rgba(76, 91, 106, 0.5); + } +.fc .fc-button-primary:not(:disabled):active, + .fc .fc-button-primary:not(:disabled).fc-button-active { + color: #fff; + color: var(--fc-button-text-color, #fff); + background-color: #1a252f; + background-color: var(--fc-button-active-bg-color, #1a252f); + border-color: #151e27; + border-color: var(--fc-button-active-border-color, #151e27); + } +.fc .fc-button-primary:not(:disabled):active:focus, + .fc .fc-button-primary:not(:disabled).fc-button-active:focus { + box-shadow: 0 0 0 0.2rem rgba(76, 91, 106, 0.5); + } +.fc { + + /* icons within buttons */ + +} +.fc .fc-button .fc-icon { + vertical-align: middle; + font-size: 1.5em; /* bump up the size (but don't make it bigger than line-height of button, which is 1.5em also) */ + } +.fc .fc-button-group { + position: relative; + display: inline-flex; + vertical-align: middle; + } +.fc .fc-button-group > .fc-button { + position: relative; + flex: 1 1 auto; + } +.fc .fc-button-group > .fc-button:hover { + z-index: 1; + } +.fc .fc-button-group > .fc-button:focus, + .fc .fc-button-group > .fc-button:active, + .fc .fc-button-group > .fc-button.fc-button-active { + z-index: 1; + } +.fc-direction-ltr .fc-button-group > .fc-button:not(:first-child) { + margin-left: -1px; + border-top-left-radius: 0; + border-bottom-left-radius: 0; + } +.fc-direction-ltr .fc-button-group > .fc-button:not(:last-child) { + border-top-right-radius: 0; + border-bottom-right-radius: 0; + } +.fc-direction-rtl .fc-button-group > .fc-button:not(:first-child) { + margin-right: -1px; + border-top-right-radius: 0; + border-bottom-right-radius: 0; + } +.fc-direction-rtl .fc-button-group > .fc-button:not(:last-child) { + border-top-left-radius: 0; + border-bottom-left-radius: 0; + } +.fc .fc-toolbar { + display: flex; + justify-content: space-between; + align-items: center; + } +.fc .fc-toolbar.fc-header-toolbar { + margin-bottom: 1.5em; + } +.fc .fc-toolbar.fc-footer-toolbar { + margin-top: 1.5em; + } +.fc .fc-toolbar-title { + font-size: 1.75em; + margin: 0; + } +.fc-direction-ltr .fc-toolbar > * > :not(:first-child) { + margin-left: .75em; /* space between */ + } +.fc-direction-rtl .fc-toolbar > * > :not(:first-child) { + margin-right: .75em; /* space between */ + } +.fc-direction-rtl .fc-toolbar-ltr { /* when the toolbar-chunk positioning system is explicitly left-to-right */ + flex-direction: row-reverse; + } +.fc .fc-scroller { + -webkit-overflow-scrolling: touch; + position: relative; /* for abs-positioned elements within */ + } +.fc .fc-scroller-liquid { + height: 100%; + } +.fc .fc-scroller-liquid-absolute { + position: absolute; + top: 0; + right: 0; + left: 0; + bottom: 0; + } +.fc .fc-scroller-harness { + position: relative; + overflow: hidden; + direction: ltr; + /* hack for chrome computing the scroller's right/left wrong for rtl. undone below... */ + /* TODO: demonstrate in codepen */ + } +.fc .fc-scroller-harness-liquid { + height: 100%; + } +.fc-direction-rtl .fc-scroller-harness > .fc-scroller { /* undo above hack */ + direction: rtl; + } +.fc-theme-standard .fc-scrollgrid { + border: 1px solid #ddd; + border: 1px solid var(--fc-border-color, #ddd); /* bootstrap does this. match */ + } +.fc .fc-scrollgrid, + .fc .fc-scrollgrid table { /* all tables (self included) */ + width: 100%; /* because tables don't normally do this */ + table-layout: fixed; + } +.fc .fc-scrollgrid table { /* inner tables */ + border-top-style: hidden; + border-left-style: hidden; + border-right-style: hidden; + } +.fc .fc-scrollgrid { + + border-collapse: separate; + border-right-width: 0; + border-bottom-width: 0; + + } +.fc .fc-scrollgrid-liquid { + height: 100%; + } +.fc .fc-scrollgrid-section { /* a
*/ + height: 1px /* better than 0, for firefox */ + + } +.fc .fc-scrollgrid-section > td { + height: 1px; /* needs a height so inner div within grow. better than 0, for firefox */ + } +.fc .fc-scrollgrid-section table { + height: 1px; + /* for most browsers, if a height isn't set on the table, can't do liquid-height within cells */ + /* serves as a min-height. harmless */ + } +.fc .fc-scrollgrid-section-liquid > td { + height: 100%; /* better than `auto`, for firefox */ + } +.fc .fc-scrollgrid-section > * { + border-top-width: 0; + border-left-width: 0; + } +.fc .fc-scrollgrid-section-header > *, + .fc .fc-scrollgrid-section-footer > * { + border-bottom-width: 0; + } +.fc .fc-scrollgrid-section-body table, + .fc .fc-scrollgrid-section-footer table { + border-bottom-style: hidden; /* head keeps its bottom border tho */ + } +.fc { + + /* stickiness */ + +} +.fc .fc-scrollgrid-section-sticky > * { + background: #fff; + background: var(--fc-page-bg-color, #fff); + position: sticky; + z-index: 3; /* TODO: var */ + /* TODO: box-shadow when sticking */ + } +.fc .fc-scrollgrid-section-header.fc-scrollgrid-section-sticky > * { + top: 0; /* because border-sharing causes a gap at the top */ + /* TODO: give safari -1. has bug */ + } +.fc .fc-scrollgrid-section-footer.fc-scrollgrid-section-sticky > * { + bottom: 0; /* known bug: bottom-stickiness doesn't work in safari */ + } +.fc .fc-scrollgrid-sticky-shim { /* for horizontal scrollbar */ + height: 1px; /* needs height to create scrollbars */ + margin-bottom: -1px; + } +.fc-sticky { /* no .fc wrap because used as child of body */ + position: sticky; +} +.fc .fc-view-harness { + flex-grow: 1; /* because this harness is WITHIN the .fc's flexbox */ + position: relative; + } +.fc { + + /* when the harness controls the height, make the view liquid */ + +} +.fc .fc-view-harness-active > .fc-view { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + } +.fc .fc-col-header-cell-cushion { + display: inline-block; /* x-browser for when sticky (when multi-tier header) */ + padding: 2px 4px; + } +.fc .fc-bg-event, + .fc .fc-non-business, + .fc .fc-highlight { + /* will always have a harness with position:relative/absolute, so absolutely expand */ + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + } +.fc .fc-non-business { + background: rgba(215, 215, 215, 0.3); + background: var(--fc-non-business-color, rgba(215, 215, 215, 0.3)); + } +.fc .fc-bg-event { + background: rgb(143, 223, 130); + background: var(--fc-bg-event-color, rgb(143, 223, 130)); + opacity: 0.3; + opacity: var(--fc-bg-event-opacity, 0.3) + } +.fc .fc-bg-event .fc-event-title { + margin: .5em; + font-size: .85em; + font-size: var(--fc-small-font-size, .85em); + font-style: italic; + } +.fc .fc-highlight { + background: rgba(188, 232, 241, 0.3); + background: var(--fc-highlight-color, rgba(188, 232, 241, 0.3)); + } +.fc .fc-cell-shaded, + .fc .fc-day-disabled { + background: rgba(208, 208, 208, 0.3); + background: var(--fc-neutral-bg-color, rgba(208, 208, 208, 0.3)); + } +/* link resets */ +/* ---------------------------------------------------------------------------------------------------- */ +a.fc-event, +a.fc-event:hover { + text-decoration: none; +} +/* cursor */ +.fc-event[href], +.fc-event.fc-event-draggable { + cursor: pointer; +} +/* event text content */ +/* ---------------------------------------------------------------------------------------------------- */ +.fc-event .fc-event-main { + position: relative; + z-index: 2; + } +/* dragging */ +/* ---------------------------------------------------------------------------------------------------- */ +.fc-event-dragging:not(.fc-event-selected) { /* MOUSE */ + opacity: 0.75; + } +.fc-event-dragging.fc-event-selected { /* TOUCH */ + box-shadow: 0 2px 7px rgba(0, 0, 0, 0.3); + } +/* resizing */ +/* ---------------------------------------------------------------------------------------------------- */ +/* (subclasses should hone positioning for touch and non-touch) */ +.fc-event .fc-event-resizer { + display: none; + position: absolute; + z-index: 4; + } +.fc-event:hover, /* MOUSE */ +.fc-event-selected { /* TOUCH */ + +} +.fc-event:hover .fc-event-resizer, .fc-event-selected .fc-event-resizer { + display: block; + } +.fc-event-selected .fc-event-resizer { + border-radius: 4px; + border-radius: calc(var(--fc-event-resizer-dot-total-width, 8px) / 2); + border-width: 1px; + border-width: var(--fc-event-resizer-dot-border-width, 1px); + width: 8px; + width: var(--fc-event-resizer-dot-total-width, 8px); + height: 8px; + height: var(--fc-event-resizer-dot-total-width, 8px); + border-style: solid; + border-color: inherit; + background: #fff; + background: var(--fc-page-bg-color, #fff) + + /* expand hit area */ + + } +.fc-event-selected .fc-event-resizer:before { + content: ''; + position: absolute; + top: -20px; + left: -20px; + right: -20px; + bottom: -20px; + } +/* selecting (always TOUCH) */ +/* ---------------------------------------------------------------------------------------------------- */ +.fc-event-selected { + box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2) + + /* expand hit area (subclasses should expand) */ + +} +.fc-event-selected:before { + content: ""; + position: absolute; + z-index: 3; + top: 0; + left: 0; + right: 0; + bottom: 0; + } +.fc-event-selected { + + /* dimmer effect */ + +} +.fc-event-selected:after { + content: ""; + background: rgba(0, 0, 0, 0.25); + background: var(--fc-event-selected-overlay-color, rgba(0, 0, 0, 0.25)); + position: absolute; + z-index: 1; + + /* assume there's a border on all sides. overcome it. */ + /* sometimes there's NOT a border, in which case the dimmer will go over */ + /* an adjacent border, which looks fine. */ + top: -1px; + left: -1px; + right: -1px; + bottom: -1px; + } +/* +A HORIZONTAL event +*/ +.fc-h-event { /* allowed to be top-level */ + display: block; + border: 1px solid #3788d8; + border: 1px solid var(--fc-event-border-color, #3788d8); + background-color: #3788d8; + background-color: var(--fc-event-bg-color, #3788d8) + +} +.fc-h-event .fc-event-main { + color: #fff; + color: var(--fc-event-text-color, #fff); + } +.fc-h-event .fc-event-main-frame { + display: flex; /* for make fc-event-title-container expand */ + } +.fc-h-event .fc-event-time { + max-width: 100%; /* clip overflow on this element */ + overflow: hidden; + } +.fc-h-event .fc-event-title-container { /* serves as a container for the sticky cushion */ + flex-grow: 1; + flex-shrink: 1; + min-width: 0; /* important for allowing to shrink all the way */ + } +.fc-h-event .fc-event-title { + display: inline-block; /* need this to be sticky cross-browser */ + vertical-align: top; /* for not messing up line-height */ + left: 0; /* for sticky */ + right: 0; /* for sticky */ + max-width: 100%; /* clip overflow on this element */ + overflow: hidden; + } +.fc-h-event.fc-event-selected:before { + /* expand hit area */ + top: -10px; + bottom: -10px; + } +/* adjust border and border-radius (if there is any) for non-start/end */ +.fc-direction-ltr .fc-daygrid-block-event:not(.fc-event-start), +.fc-direction-rtl .fc-daygrid-block-event:not(.fc-event-end) { + border-top-left-radius: 0; + border-bottom-left-radius: 0; + border-left-width: 0; +} +.fc-direction-ltr .fc-daygrid-block-event:not(.fc-event-end), +.fc-direction-rtl .fc-daygrid-block-event:not(.fc-event-start) { + border-top-right-radius: 0; + border-bottom-right-radius: 0; + border-right-width: 0; +} +/* resizers */ +.fc-h-event:not(.fc-event-selected) .fc-event-resizer { + top: 0; + bottom: 0; + width: 8px; + width: var(--fc-event-resizer-thickness, 8px); +} +.fc-direction-ltr .fc-h-event:not(.fc-event-selected) .fc-event-resizer-start, +.fc-direction-rtl .fc-h-event:not(.fc-event-selected) .fc-event-resizer-end { + cursor: w-resize; + left: -4px; + left: calc(var(--fc-event-resizer-thickness, 8px) / -2); +} +.fc-direction-ltr .fc-h-event:not(.fc-event-selected) .fc-event-resizer-end, +.fc-direction-rtl .fc-h-event:not(.fc-event-selected) .fc-event-resizer-start { + cursor: e-resize; + right: -4px; + right: calc(var(--fc-event-resizer-thickness, 8px) / -2); +} +/* resizers for TOUCH */ +.fc-h-event.fc-event-selected .fc-event-resizer { + top: 50%; + margin-top: -4px; + margin-top: calc(var(--fc-event-resizer-dot-total-width, 8px) / -2); +} +.fc-direction-ltr .fc-h-event.fc-event-selected .fc-event-resizer-start, +.fc-direction-rtl .fc-h-event.fc-event-selected .fc-event-resizer-end { + left: -4px; + left: calc(var(--fc-event-resizer-dot-total-width, 8px) / -2); +} +.fc-direction-ltr .fc-h-event.fc-event-selected .fc-event-resizer-end, +.fc-direction-rtl .fc-h-event.fc-event-selected .fc-event-resizer-start { + right: -4px; + right: calc(var(--fc-event-resizer-dot-total-width, 8px) / -2); +} +.fc .fc-popover { + position: absolute; + z-index: 9999; + box-shadow: 0 2px 6px rgba(0,0,0,.15); + } +.fc .fc-popover-header { + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; + padding: 3px 4px; + } +.fc .fc-popover-title { + margin: 0 2px; + } +.fc .fc-popover-close { + cursor: pointer; + opacity: 0.65; + font-size: 1.1em; + } +.fc-theme-standard .fc-popover { + border: 1px solid #ddd; + border: 1px solid var(--fc-border-color, #ddd); + background: #fff; + background: var(--fc-page-bg-color, #fff); + } +.fc-theme-standard .fc-popover-header { + background: rgba(208, 208, 208, 0.3); + background: var(--fc-neutral-bg-color, rgba(208, 208, 208, 0.3)); + } + + +:root { + --fc-daygrid-event-dot-width: 8px; +} +/* help things clear margins of inner content */ +.fc-daygrid-day-frame, +.fc-daygrid-day-events, +.fc-daygrid-event-harness { /* for event top/bottom margins */ +} +.fc-daygrid-day-frame:before, .fc-daygrid-day-events:before, .fc-daygrid-event-harness:before { + content: ""; + clear: both; + display: table; } +.fc-daygrid-day-frame:after, .fc-daygrid-day-events:after, .fc-daygrid-event-harness:after { + content: ""; + clear: both; + display: table; } +.fc .fc-daygrid-body { /* a
that wraps the table */ + position: relative; + z-index: 1; /* container inner z-index's because
s can't do it */ + } +.fc .fc-daygrid-day.fc-day-today { + background-color: rgba(255, 220, 40, 0.15); + background-color: var(--fc-today-bg-color, rgba(255, 220, 40, 0.15)); + } +.fc .fc-daygrid-day-frame { + position: relative; + min-height: 100%; /* seems to work better than `height` because sets height after rows/cells naturally do it */ + } +.fc { + + /* cell top */ + +} +.fc .fc-daygrid-day-top { + display: flex; + flex-direction: row-reverse; + } +.fc .fc-day-other .fc-daygrid-day-top { + opacity: 0.3; + } +.fc { + + /* day number (within cell top) */ + +} +.fc .fc-daygrid-day-number { + position: relative; + z-index: 4; + padding: 4px; + } +.fc { + + /* event container */ + +} +.fc .fc-daygrid-day-events { + margin-top: 1px; /* needs to be margin, not padding, so that available cell height can be computed */ + } +.fc { + + /* positioning for balanced vs natural */ + +} +.fc .fc-daygrid-body-balanced .fc-daygrid-day-events { + position: absolute; + left: 0; + right: 0; + } +.fc .fc-daygrid-body-unbalanced .fc-daygrid-day-events { + position: relative; /* for containing abs positioned event harnesses */ + min-height: 2em; /* in addition to being a min-height during natural height, equalizes the heights a little bit */ + } +.fc .fc-daygrid-body-natural { /* can coexist with -unbalanced */ + } +.fc .fc-daygrid-body-natural .fc-daygrid-day-events { + margin-bottom: 1em; + } +.fc { + + /* event harness */ + +} +.fc .fc-daygrid-event-harness { + position: relative; + } +.fc .fc-daygrid-event-harness-abs { + position: absolute; + top: 0; /* fallback coords for when cannot yet be computed */ + left: 0; /* */ + right: 0; /* */ + } +.fc .fc-daygrid-bg-harness { + position: absolute; + top: 0; + bottom: 0; + } +.fc { + + /* bg content */ + +} +.fc .fc-daygrid-day-bg .fc-non-business { z-index: 1 } +.fc .fc-daygrid-day-bg .fc-bg-event { z-index: 2 } +.fc .fc-daygrid-day-bg .fc-highlight { z-index: 3 } +.fc { + + /* events */ + +} +.fc .fc-daygrid-event { + z-index: 6; + margin-top: 1px; + } +.fc .fc-daygrid-event.fc-event-mirror { + z-index: 7; + } +.fc { + + /* cell bottom (within day-events) */ + +} +.fc .fc-daygrid-day-bottom { + font-size: .85em; + padding: 2px 3px 0 + } +.fc .fc-daygrid-day-bottom:before { + content: ""; + clear: both; + display: table; } +.fc .fc-daygrid-more-link { + position: relative; + z-index: 4; + cursor: pointer; + } +.fc { + + /* week number (within frame) */ + +} +.fc .fc-daygrid-week-number { + position: absolute; + z-index: 5; + top: 0; + padding: 2px; + min-width: 1.5em; + text-align: center; + background-color: rgba(208, 208, 208, 0.3); + background-color: var(--fc-neutral-bg-color, rgba(208, 208, 208, 0.3)); + color: #808080; + color: var(--fc-neutral-text-color, #808080); + } +.fc { + + /* popover */ + +} +.fc .fc-more-popover .fc-popover-body { + min-width: 220px; + padding: 10px; + } +.fc-direction-ltr .fc-daygrid-event.fc-event-start, +.fc-direction-rtl .fc-daygrid-event.fc-event-end { + margin-left: 2px; +} +.fc-direction-ltr .fc-daygrid-event.fc-event-end, +.fc-direction-rtl .fc-daygrid-event.fc-event-start { + margin-right: 2px; +} +.fc-direction-ltr .fc-daygrid-week-number { + left: 0; + border-radius: 0 0 3px 0; + } +.fc-direction-rtl .fc-daygrid-week-number { + right: 0; + border-radius: 0 0 0 3px; + } +.fc-liquid-hack .fc-daygrid-day-frame { + position: static; /* will cause inner absolute stuff to expand to
/ elements with colspans. + SOLUTION: making individual
+ _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
*/ + } +.fc-daygrid-event { /* make root-level, because will be dragged-and-dropped outside of a component root */ + position: relative; /* for z-indexes assigned later */ + white-space: nowrap; + border-radius: 3px; /* dot event needs this to when selected */ + font-size: .85em; + font-size: var(--fc-small-font-size, .85em); +} +/* --- the rectangle ("block") style of event --- */ +.fc-daygrid-block-event .fc-event-time { + font-weight: bold; + } +.fc-daygrid-block-event .fc-event-time, + .fc-daygrid-block-event .fc-event-title { + padding: 1px; + } +/* --- the dot style of event --- */ +.fc-daygrid-dot-event { + display: flex; + align-items: center; + padding: 2px 0 + +} +.fc-daygrid-dot-event .fc-event-title { + flex-grow: 1; + flex-shrink: 1; + min-width: 0; /* important for allowing to shrink all the way */ + overflow: hidden; + font-weight: bold; + } +.fc-daygrid-dot-event:hover, + .fc-daygrid-dot-event.fc-event-mirror { + background: rgba(0, 0, 0, 0.1); + } +.fc-daygrid-dot-event.fc-event-selected:before { + /* expand hit area */ + top: -10px; + bottom: -10px; + } +.fc-daygrid-event-dot { /* the actual dot */ + margin: 0 4px; + box-sizing: content-box; + width: 0; + height: 0; + border: 4px solid #3788d8; + border: calc(var(--fc-daygrid-event-dot-width, 8px) / 2) solid var(--fc-event-border-color, #3788d8); + border-radius: 4px; + border-radius: calc(var(--fc-daygrid-event-dot-width, 8px) / 2); +} +/* --- spacing between time and title --- */ +.fc-direction-ltr .fc-daygrid-event .fc-event-time { + margin-right: 3px; + } +.fc-direction-rtl .fc-daygrid-event .fc-event-time { + margin-left: 3px; + } + + +/* +A VERTICAL event +*/ + +.fc-v-event { /* allowed to be top-level */ + display: block; + border: 1px solid #3788d8; + border: 1px solid var(--fc-event-border-color, #3788d8); + background-color: #3788d8; + background-color: var(--fc-event-bg-color, #3788d8) + +} + +.fc-v-event .fc-event-main { + color: #fff; + color: var(--fc-event-text-color, #fff); + height: 100%; + } + +.fc-v-event .fc-event-main-frame { + height: 100%; + display: flex; + flex-direction: column; + } + +.fc-v-event .fc-event-time { + flex-grow: 0; + flex-shrink: 0; + max-height: 100%; + overflow: hidden; + } + +.fc-v-event .fc-event-title-container { /* a container for the sticky cushion */ + flex-grow: 1; + flex-shrink: 1; + min-height: 0; /* important for allowing to shrink all the way */ + } + +.fc-v-event .fc-event-title { /* will have fc-sticky on it */ + top: 0; + bottom: 0; + max-height: 100%; /* clip overflow */ + overflow: hidden; + } + +.fc-v-event:not(.fc-event-start) { + border-top-width: 0; + border-top-left-radius: 0; + border-top-right-radius: 0; + } + +.fc-v-event:not(.fc-event-end) { + border-bottom-width: 0; + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; + } + +.fc-v-event.fc-event-selected:before { + /* expand hit area */ + left: -10px; + right: -10px; + } + +.fc-v-event { + + /* resizer (mouse AND touch) */ + +} + +.fc-v-event .fc-event-resizer-start { + cursor: n-resize; + } + +.fc-v-event .fc-event-resizer-end { + cursor: s-resize; + } + +.fc-v-event { + + /* resizer for MOUSE */ + +} + +.fc-v-event:not(.fc-event-selected) .fc-event-resizer { + height: 8px; + height: var(--fc-event-resizer-thickness, 8px); + left: 0; + right: 0; + } + +.fc-v-event:not(.fc-event-selected) .fc-event-resizer-start { + top: -4px; + top: calc(var(--fc-event-resizer-thickness, 8px) / -2); + } + +.fc-v-event:not(.fc-event-selected) .fc-event-resizer-end { + bottom: -4px; + bottom: calc(var(--fc-event-resizer-thickness, 8px) / -2); + } + +.fc-v-event { + + /* resizer for TOUCH (when event is "selected") */ + +} + +.fc-v-event.fc-event-selected .fc-event-resizer { + left: 50%; + margin-left: -4px; + margin-left: calc(var(--fc-event-resizer-dot-total-width, 8px) / -2); + } + +.fc-v-event.fc-event-selected .fc-event-resizer-start { + top: -4px; + top: calc(var(--fc-event-resizer-dot-total-width, 8px) / -2); + } + +.fc-v-event.fc-event-selected .fc-event-resizer-end { + bottom: -4px; + bottom: calc(var(--fc-event-resizer-dot-total-width, 8px) / -2); + } +.fc .fc-timegrid .fc-daygrid-body { /* the all-day daygrid within the timegrid view */ + z-index: 2; /* put above the timegrid-body so that more-popover is above everything. TODO: better solution */ + } +.fc .fc-timegrid-divider { + padding: 0 0 2px; /* browsers get confused when you set height. use padding instead */ + } +.fc .fc-timegrid-body { + position: relative; + z-index: 1; /* scope the z-indexes of slots and cols */ + min-height: 100%; /* fill height always, even when slat table doesn't grow */ + } +.fc .fc-timegrid-axis-chunk { /* for advanced ScrollGrid */ + position: relative /* offset parent for now-indicator-container */ + + } +.fc .fc-timegrid-axis-chunk > table { + position: relative; + z-index: 1; /* above the now-indicator-container */ + } +.fc .fc-timegrid-slots { + position: relative; + z-index: 1; + } +.fc .fc-timegrid-slot { /* a */ + height: 1.5em; + border-bottom: 0 /* each cell owns its top border */ + } +.fc .fc-timegrid-slot:empty:before { + content: '\00a0'; /* make sure there's at least an empty space to create height for height syncing */ + } +.fc .fc-timegrid-slot-minor { + border-top-style: dotted; + } +.fc .fc-timegrid-slot-label-cushion { + display: inline-block; + white-space: nowrap; + } +.fc .fc-timegrid-slot-label { + vertical-align: middle; /* vertical align the slots */ + } +.fc { + + + /* slots AND axis cells (top-left corner of view including the "all-day" text) */ + +} +.fc .fc-timegrid-axis-cushion, + .fc .fc-timegrid-slot-label-cushion { + padding: 0 4px; + } +.fc { + + + /* axis cells (top-left corner of view including the "all-day" text) */ + /* vertical align is more complicated, uses flexbox */ + +} +.fc .fc-timegrid-axis-frame-liquid { + height: 100%; /* will need liquid-hack in FF */ + } +.fc .fc-timegrid-axis-frame { + overflow: hidden; + display: flex; + align-items: center; /* vertical align */ + justify-content: flex-end; /* horizontal align. matches text-align below */ + } +.fc .fc-timegrid-axis-cushion { + max-width: 60px; /* limits the width of the "all-day" text */ + flex-shrink: 0; /* allows text to expand how it normally would, regardless of constrained width */ + } +.fc-direction-ltr .fc-timegrid-slot-label-frame { + text-align: right; + } +.fc-direction-rtl .fc-timegrid-slot-label-frame { + text-align: left; + } +.fc-liquid-hack .fc-timegrid-axis-frame-liquid { + height: auto; + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + } +.fc .fc-timegrid-col.fc-day-today { + background-color: rgba(255, 220, 40, 0.15); + background-color: var(--fc-today-bg-color, rgba(255, 220, 40, 0.15)); + } +.fc .fc-timegrid-col-frame { + min-height: 100%; /* liquid-hack is below */ + position: relative; + } +.fc-media-screen.fc-liquid-hack .fc-timegrid-col-frame { + height: auto; + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + } +.fc-media-screen .fc-timegrid-cols { + position: absolute; /* no z-index. children will decide and go above slots */ + top: 0; + left: 0; + right: 0; + bottom: 0 + } +.fc-media-screen .fc-timegrid-cols > table { + height: 100%; + } +.fc-media-screen .fc-timegrid-col-bg, + .fc-media-screen .fc-timegrid-col-events, + .fc-media-screen .fc-timegrid-now-indicator-container { + position: absolute; + top: 0; + left: 0; + right: 0; + } +.fc { + + /* bg */ + +} +.fc .fc-timegrid-col-bg { + z-index: 2; /* TODO: kill */ + } +.fc .fc-timegrid-col-bg .fc-non-business { z-index: 1 } +.fc .fc-timegrid-col-bg .fc-bg-event { z-index: 2 } +.fc .fc-timegrid-col-bg .fc-highlight { z-index: 3 } +.fc .fc-timegrid-bg-harness { + position: absolute; /* top/bottom will be set by JS */ + left: 0; + right: 0; + } +.fc { + + /* fg events */ + /* (the mirror segs are put into a separate container with same classname, */ + /* and they must be after the normal seg container to appear at a higher z-index) */ + +} +.fc .fc-timegrid-col-events { + z-index: 3; + /* child event segs have z-indexes that are scoped within this div */ + } +.fc { + + /* now indicator */ + +} +.fc .fc-timegrid-now-indicator-container { + bottom: 0; + overflow: hidden; /* don't let overflow of lines/arrows cause unnecessary scrolling */ + /* z-index is set on the individual elements */ + } +.fc-direction-ltr .fc-timegrid-col-events { + margin: 0 2.5% 0 2px; + } +.fc-direction-rtl .fc-timegrid-col-events { + margin: 0 2px 0 2.5%; + } +.fc-timegrid-event-harness { + position: absolute /* top/left/right/bottom will all be set by JS */ +} +.fc-timegrid-event-harness > .fc-timegrid-event { + position: absolute; /* absolute WITHIN the harness */ + top: 0; /* for when not yet positioned */ + bottom: 0; /* " */ + left: 0; + right: 0; + } +.fc-timegrid-event-harness-inset .fc-timegrid-event, +.fc-timegrid-event.fc-event-mirror, +.fc-timegrid-more-link { + box-shadow: 0px 0px 0px 1px #fff; + box-shadow: 0px 0px 0px 1px var(--fc-page-bg-color, #fff); +} +.fc-timegrid-event, +.fc-timegrid-more-link { /* events need to be root */ + font-size: .85em; + font-size: var(--fc-small-font-size, .85em); + border-radius: 3px; +} +.fc-timegrid-event { /* events need to be root */ + margin-bottom: 1px /* give some space from bottom */ +} +.fc-timegrid-event .fc-event-main { + padding: 1px 1px 0; + } +.fc-timegrid-event .fc-event-time { + white-space: nowrap; + font-size: .85em; + font-size: var(--fc-small-font-size, .85em); + margin-bottom: 1px; + } +.fc-timegrid-event-short .fc-event-main-frame { + flex-direction: row; + overflow: hidden; + } +.fc-timegrid-event-short .fc-event-time:after { + content: '\00a0-\00a0'; /* dash surrounded by non-breaking spaces */ + } +.fc-timegrid-event-short .fc-event-title { + font-size: .85em; + font-size: var(--fc-small-font-size, .85em) + } +.fc-timegrid-more-link { /* does NOT inherit from fc-timegrid-event */ + position: absolute; + z-index: 9999; /* hack */ + color: inherit; + color: var(--fc-more-link-text-color, inherit); + background: #d0d0d0; + background: var(--fc-more-link-bg-color, #d0d0d0); + cursor: pointer; + margin-bottom: 1px; /* match space below fc-timegrid-event */ +} +.fc-timegrid-more-link-inner { /* has fc-sticky */ + padding: 3px 2px; + top: 0; +} +.fc-direction-ltr .fc-timegrid-more-link { + right: 0; + } +.fc-direction-rtl .fc-timegrid-more-link { + left: 0; + } +.fc { + + /* line */ + +} +.fc .fc-timegrid-now-indicator-line { + position: absolute; + z-index: 4; + left: 0; + right: 0; + border-style: solid; + border-color: red; + border-color: var(--fc-now-indicator-color, red); + border-width: 1px 0 0; + } +.fc { + + /* arrow */ + +} +.fc .fc-timegrid-now-indicator-arrow { + position: absolute; + z-index: 4; + margin-top: -5px; /* vertically center on top coordinate */ + border-style: solid; + border-color: red; + border-color: var(--fc-now-indicator-color, red); + } +.fc-direction-ltr .fc-timegrid-now-indicator-arrow { + left: 0; + + /* triangle pointing right. TODO: mixin */ + border-width: 5px 0 5px 6px; + border-top-color: transparent; + border-bottom-color: transparent; + } +.fc-direction-rtl .fc-timegrid-now-indicator-arrow { + right: 0; + + /* triangle pointing left. TODO: mixin */ + border-width: 5px 6px 5px 0; + border-top-color: transparent; + border-bottom-color: transparent; + } + + +:root { + --fc-list-event-dot-width: 10px; + --fc-list-event-hover-bg-color: #f5f5f5; +} +.fc-theme-standard .fc-list { + border: 1px solid #ddd; + border: 1px solid var(--fc-border-color, #ddd); + } +.fc { + + /* message when no events */ + +} +.fc .fc-list-empty { + background-color: rgba(208, 208, 208, 0.3); + background-color: var(--fc-neutral-bg-color, rgba(208, 208, 208, 0.3)); + height: 100%; + display: flex; + justify-content: center; + align-items: center; /* vertically aligns fc-list-empty-inner */ + } +.fc .fc-list-empty-cushion { + margin: 5em 0; + } +.fc { + + /* table within the scroller */ + /* ---------------------------------------------------------------------------------------------------- */ + +} +.fc .fc-list-table { + width: 100%; + border-style: hidden; /* kill outer border on theme */ + } +.fc .fc-list-table tr > * { + border-left: 0; + border-right: 0; + } +.fc .fc-list-sticky .fc-list-day > * { /* the cells */ + position: sticky; + top: 0; + background: #fff; + background: var(--fc-page-bg-color, #fff); /* for when headers are styled to be transparent and sticky */ + } +.fc .fc-list-table th { + padding: 0; /* uses an inner-wrapper instead... */ + } +.fc .fc-list-table td, + .fc .fc-list-day-cushion { + padding: 8px 14px; + } +.fc { + + + /* date heading rows */ + /* ---------------------------------------------------------------------------------------------------- */ + +} +.fc .fc-list-day-cushion:after { + content: ""; + clear: both; + display: table; /* clear floating */ + } +.fc-theme-standard .fc-list-day-cushion { + background-color: rgba(208, 208, 208, 0.3); + background-color: var(--fc-neutral-bg-color, rgba(208, 208, 208, 0.3)); + } +.fc-direction-ltr .fc-list-day-text, +.fc-direction-rtl .fc-list-day-side-text { + float: left; +} +.fc-direction-ltr .fc-list-day-side-text, +.fc-direction-rtl .fc-list-day-text { + float: right; +} +/* make the dot closer to the event title */ +.fc-direction-ltr .fc-list-table .fc-list-event-graphic { padding-right: 0 } +.fc-direction-rtl .fc-list-table .fc-list-event-graphic { padding-left: 0 } +.fc .fc-list-event.fc-event-forced-url { + cursor: pointer; /* whole row will seem clickable */ + } +.fc .fc-list-event:hover td { + background-color: #f5f5f5; + background-color: var(--fc-list-event-hover-bg-color, #f5f5f5); + } +.fc { + + /* shrink certain cols */ + +} +.fc .fc-list-event-graphic, + .fc .fc-list-event-time { + white-space: nowrap; + width: 1px; + } +.fc .fc-list-event-dot { + display: inline-block; + box-sizing: content-box; + width: 0; + height: 0; + border: 5px solid #3788d8; + border: calc(var(--fc-list-event-dot-width, 10px) / 2) solid var(--fc-event-border-color, #3788d8); + border-radius: 5px; + border-radius: calc(var(--fc-list-event-dot-width, 10px) / 2); + } +.fc { + + /* reset styling */ + +} +.fc .fc-list-event-title a { + color: inherit; + text-decoration: none; + } +.fc { + + /* underline link when hovering over any part of row */ + +} +.fc .fc-list-event.fc-event-forced-url:hover a { + text-decoration: underline; + } + + + + .fc-theme-bootstrap a:not([href]) { + color: inherit; /* natural color for navlinks */ + } + diff --git a/apps/schoolCalendar/fullcalendar/main.js b/apps/schoolCalendar/fullcalendar/main.js new file mode 100644 index 000000000..54bf45d3f --- /dev/null +++ b/apps/schoolCalendar/fullcalendar/main.js @@ -0,0 +1,14738 @@ +/*! +FullCalendar v5.9.0 +Docs & License: https://fullcalendar.io/ +(c) 2021 Adam Shaw +*/ +var FullCalendar = (function (exports) { + 'use strict'; + + /*! ***************************************************************************** + Copyright (c) Microsoft Corporation. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH + REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, + INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR + OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + PERFORMANCE OF THIS SOFTWARE. + ***************************************************************************** */ + /* global Reflect, Promise */ + + var extendStatics = function(d, b) { + extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; }; + return extendStatics(d, b); + }; + + function __extends(d, b) { + if (typeof b !== "function" && b !== null) + throw new TypeError("Class extends value " + String(b) + " is not a constructor or null"); + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + } + + var __assign = function() { + __assign = Object.assign || function __assign(t) { + for (var s, i = 1, n = arguments.length; i < n; i++) { + s = arguments[i]; + for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; + } + return t; + }; + return __assign.apply(this, arguments); + }; + + function __spreadArray(to, from, pack) { + if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) { + if (ar || !(i in from)) { + if (!ar) ar = Array.prototype.slice.call(from, 0, i); + ar[i] = from[i]; + } + } + return to.concat(ar || from); + } + + var n,u,i$1,t,o,r$1={},f$1=[],e$1=/acit|ex(?:s|g|n|p|$)|rph|grid|ows|mnc|ntw|ine[ch]|zoo|^ord|itera/i;function c$1(n,l){for(var u in l)n[u]=l[u];return n}function s(n){var l=n.parentNode;l&&l.removeChild(n);}function a$1(n,l,u){var i,t,o,r=arguments,f={};for(o in l)"key"==o?i=l[o]:"ref"==o?t=l[o]:f[o]=l[o];if(arguments.length>3)for(u=[u],o=3;o0?v$1(k.type,k.props,k.key,null,k.__v):k)){if(k.__=u,k.__b=u.__b+1,null===(_=A[h])||_&&k.key==_.key&&k.type===_.type)A[h]=void 0;else for(p=0;p3;)e.pop()();if(e[1]>>1,1),t.i.removeChild(n);}}),N(a$1(T,{context:t.context},n.__v),t.l)):t.l&&t.componentWillUnmount();}function I(n,t){return a$1(j,{__v:n,i:t})}(F.prototype=new p).__e=function(n){var t=this,e=U(t.__v),r=t.o.get(n);return r[0]++,function(u){var o=function(){t.props.revealOrder?(r.push(u),M(t,n,r)):u();};e?e(o):o();}},F.prototype.render=function(n){this.u=null,this.o=new Map;var t=w$1(n.children);n.revealOrder&&"b"===n.revealOrder[0]&&t.reverse();for(var e=t.length;e--;)this.o.set(t[e],this.u=[1,0,this.u]);return n.children},F.prototype.componentDidUpdate=F.prototype.componentDidMount=function(){var n=this;this.o.forEach(function(t,e){M(n,e,t);});};var W="undefined"!=typeof Symbol&&Symbol.for&&Symbol.for("react.element")||60103,P=/^(?:accent|alignment|arabic|baseline|cap|clip(?!PathU)|color|fill|flood|font|glyph(?!R)|horiz|marker(?!H|W|U)|overline|paint|stop|strikethrough|stroke|text(?!L)|underline|unicode|units|v|vector|vert|word|writing|x(?!C))[A-Z]/,V=function(n){return ("undefined"!=typeof Symbol&&"symbol"==typeof Symbol()?/fil|che|rad/i:/fil|che|ra/i).test(n)};p.prototype.isReactComponent={},["componentWillMount","componentWillReceiveProps","componentWillUpdate"].forEach(function(n){Object.defineProperty(p.prototype,n,{configurable:!0,get:function(){return this["UNSAFE_"+n]},set:function(t){Object.defineProperty(this,n,{configurable:!0,writable:!0,value:t});}});});var H=n.event;function Z(){}function Y(){return this.cancelBubble}function $(){return this.defaultPrevented}n.event=function(n){return H&&(n=H(n)),n.persist=Z,n.isPropagationStopped=Y,n.isDefaultPrevented=$,n.nativeEvent=n};var G={configurable:!0,get:function(){return this.class}},J=n.vnode;n.vnode=function(n){var t=n.type,e=n.props,r=e;if("string"==typeof t){for(var u in r={},e){var o=e[u];"value"===u&&"defaultValue"in e&&null==o||("defaultValue"===u&&"value"in e&&null==e.value?u="value":"download"===u&&!0===o?o="":/ondoubleclick/i.test(u)?u="ondblclick":/^onchange(textarea|input)/i.test(u+t)&&!V(e.type)?u="oninput":/^on(Ani|Tra|Tou|BeforeInp)/.test(u)?u=u.toLowerCase():P.test(u)?u=u.replace(/[A-Z0-9]/,"-$&").toLowerCase():null===o&&(o=void 0),r[u]=o);}"select"==t&&r.multiple&&Array.isArray(r.value)&&(r.value=w$1(e.children).forEach(function(n){n.props.selected=-1!=r.value.indexOf(n.props.value);})),"select"==t&&null!=r.defaultValue&&(r.value=w$1(e.children).forEach(function(n){n.props.selected=r.multiple?-1!=r.defaultValue.indexOf(n.props.value):r.defaultValue==n.props.value;})),n.props=r;}t&&e.class!=e.className&&(G.enumerable="className"in e,null!=e.className&&(r.class=e.className),Object.defineProperty(r,"className",G)),n.$$typeof=W,J&&J(n);};var K=n.__r;n.__r=function(n){K&&K(n);};"object"==typeof performance&&"function"==typeof performance.now?performance.now.bind(performance):function(){return Date.now()}; + + var globalObj = typeof globalThis !== 'undefined' ? globalThis : window; // // TODO: streamline when killing IE11 support + if (globalObj.FullCalendarVDom) { + console.warn('FullCalendar VDOM already loaded'); + } + else { + globalObj.FullCalendarVDom = { + Component: p, + createElement: a$1, + render: N, + createRef: h, + Fragment: y, + createContext: createContext$1, + createPortal: I, + flushToDom: flushToDom$1, + unmountComponentAtNode: unmountComponentAtNode$1, + }; + } + // HACKS... + // TODO: lock version + // TODO: link gh issues + function flushToDom$1() { + var oldDebounceRendering = n.debounceRendering; // orig + var callbackQ = []; + function execCallbackSync(callback) { + callbackQ.push(callback); + } + n.debounceRendering = execCallbackSync; + N(a$1(FakeComponent, {}), document.createElement('div')); + while (callbackQ.length) { + callbackQ.shift()(); + } + n.debounceRendering = oldDebounceRendering; + } + var FakeComponent = /** @class */ (function (_super) { + __extends(FakeComponent, _super); + function FakeComponent() { + return _super !== null && _super.apply(this, arguments) || this; + } + FakeComponent.prototype.render = function () { return a$1('div', {}); }; + FakeComponent.prototype.componentDidMount = function () { this.setState({}); }; + return FakeComponent; + }(p)); + function createContext$1(defaultValue) { + var ContextType = q(defaultValue); + var origProvider = ContextType.Provider; + ContextType.Provider = function () { + var _this = this; + var isNew = !this.getChildContext; + var children = origProvider.apply(this, arguments); // eslint-disable-line prefer-rest-params + if (isNew) { + var subs_1 = []; + this.shouldComponentUpdate = function (_props) { + if (_this.props.value !== _props.value) { + subs_1.forEach(function (c) { + c.context = _props.value; + c.forceUpdate(); + }); + } + }; + this.sub = function (c) { + subs_1.push(c); + var old = c.componentWillUnmount; + c.componentWillUnmount = function () { + subs_1.splice(subs_1.indexOf(c), 1); + old && old.call(c); + }; + }; + } + return children; + }; + return ContextType; + } + function unmountComponentAtNode$1(node) { + N(null, node); + } + + // no public types yet. when there are, export from: + // import {} from './api-type-deps' + var EventSourceApi = /** @class */ (function () { + function EventSourceApi(context, internalEventSource) { + this.context = context; + this.internalEventSource = internalEventSource; + } + EventSourceApi.prototype.remove = function () { + this.context.dispatch({ + type: 'REMOVE_EVENT_SOURCE', + sourceId: this.internalEventSource.sourceId, + }); + }; + EventSourceApi.prototype.refetch = function () { + this.context.dispatch({ + type: 'FETCH_EVENT_SOURCES', + sourceIds: [this.internalEventSource.sourceId], + isRefetch: true, + }); + }; + Object.defineProperty(EventSourceApi.prototype, "id", { + get: function () { + return this.internalEventSource.publicId; + }, + enumerable: false, + configurable: true + }); + Object.defineProperty(EventSourceApi.prototype, "url", { + get: function () { + return this.internalEventSource.meta.url; + }, + enumerable: false, + configurable: true + }); + Object.defineProperty(EventSourceApi.prototype, "format", { + get: function () { + return this.internalEventSource.meta.format; // TODO: bad. not guaranteed + }, + enumerable: false, + configurable: true + }); + return EventSourceApi; + }()); + + function removeElement(el) { + if (el.parentNode) { + el.parentNode.removeChild(el); + } + } + // Querying + // ---------------------------------------------------------------------------------------------------------------- + function elementClosest(el, selector) { + if (el.closest) { + return el.closest(selector); + // really bad fallback for IE + // from https://developer.mozilla.org/en-US/docs/Web/API/Element/closest + } + if (!document.documentElement.contains(el)) { + return null; + } + do { + if (elementMatches(el, selector)) { + return el; + } + el = (el.parentElement || el.parentNode); + } while (el !== null && el.nodeType === 1); + return null; + } + function elementMatches(el, selector) { + var method = el.matches || el.matchesSelector || el.msMatchesSelector; + return method.call(el, selector); + } + // accepts multiple subject els + // returns a real array. good for methods like forEach + // TODO: accept the document + function findElements(container, selector) { + var containers = container instanceof HTMLElement ? [container] : container; + var allMatches = []; + for (var i = 0; i < containers.length; i += 1) { + var matches = containers[i].querySelectorAll(selector); + for (var j = 0; j < matches.length; j += 1) { + allMatches.push(matches[j]); + } + } + return allMatches; + } + // accepts multiple subject els + // only queries direct child elements // TODO: rename to findDirectChildren! + function findDirectChildren(parent, selector) { + var parents = parent instanceof HTMLElement ? [parent] : parent; + var allMatches = []; + for (var i = 0; i < parents.length; i += 1) { + var childNodes = parents[i].children; // only ever elements + for (var j = 0; j < childNodes.length; j += 1) { + var childNode = childNodes[j]; + if (!selector || elementMatches(childNode, selector)) { + allMatches.push(childNode); + } + } + } + return allMatches; + } + // Style + // ---------------------------------------------------------------------------------------------------------------- + var PIXEL_PROP_RE = /(top|left|right|bottom|width|height)$/i; + function applyStyle(el, props) { + for (var propName in props) { + applyStyleProp(el, propName, props[propName]); + } + } + function applyStyleProp(el, name, val) { + if (val == null) { + el.style[name] = ''; + } + else if (typeof val === 'number' && PIXEL_PROP_RE.test(name)) { + el.style[name] = val + "px"; + } + else { + el.style[name] = val; + } + } + // Event Handling + // ---------------------------------------------------------------------------------------------------------------- + // if intercepting bubbled events at the document/window/body level, + // and want to see originating element (the 'target'), use this util instead + // of `ev.target` because it goes within web-component boundaries. + function getEventTargetViaRoot(ev) { + var _a, _b; + return (_b = (_a = ev.composedPath) === null || _a === void 0 ? void 0 : _a.call(ev)[0]) !== null && _b !== void 0 ? _b : ev.target; + } + // Shadow DOM consuderations + // ---------------------------------------------------------------------------------------------------------------- + function getElRoot(el) { + return el.getRootNode ? el.getRootNode() : document; + } + + // Stops a mouse/touch event from doing it's native browser action + function preventDefault(ev) { + ev.preventDefault(); + } + // Event Delegation + // ---------------------------------------------------------------------------------------------------------------- + function buildDelegationHandler(selector, handler) { + return function (ev) { + var matchedChild = elementClosest(ev.target, selector); + if (matchedChild) { + handler.call(matchedChild, ev, matchedChild); + } + }; + } + function listenBySelector(container, eventType, selector, handler) { + var attachedHandler = buildDelegationHandler(selector, handler); + container.addEventListener(eventType, attachedHandler); + return function () { + container.removeEventListener(eventType, attachedHandler); + }; + } + function listenToHoverBySelector(container, selector, onMouseEnter, onMouseLeave) { + var currentMatchedChild; + return listenBySelector(container, 'mouseover', selector, function (mouseOverEv, matchedChild) { + if (matchedChild !== currentMatchedChild) { + currentMatchedChild = matchedChild; + onMouseEnter(mouseOverEv, matchedChild); + var realOnMouseLeave_1 = function (mouseLeaveEv) { + currentMatchedChild = null; + onMouseLeave(mouseLeaveEv, matchedChild); + matchedChild.removeEventListener('mouseleave', realOnMouseLeave_1); + }; + // listen to the next mouseleave, and then unattach + matchedChild.addEventListener('mouseleave', realOnMouseLeave_1); + } + }); + } + // Animation + // ---------------------------------------------------------------------------------------------------------------- + var transitionEventNames = [ + 'webkitTransitionEnd', + 'otransitionend', + 'oTransitionEnd', + 'msTransitionEnd', + 'transitionend', + ]; + // triggered only when the next single subsequent transition finishes + function whenTransitionDone(el, callback) { + var realCallback = function (ev) { + callback(ev); + transitionEventNames.forEach(function (eventName) { + el.removeEventListener(eventName, realCallback); + }); + }; + transitionEventNames.forEach(function (eventName) { + el.addEventListener(eventName, realCallback); // cross-browser way to determine when the transition finishes + }); + } + + var guidNumber = 0; + function guid() { + guidNumber += 1; + return String(guidNumber); + } + /* FullCalendar-specific DOM Utilities + ----------------------------------------------------------------------------------------------------------------------*/ + // Make the mouse cursor express that an event is not allowed in the current area + function disableCursor() { + document.body.classList.add('fc-not-allowed'); + } + // Returns the mouse cursor to its original look + function enableCursor() { + document.body.classList.remove('fc-not-allowed'); + } + /* Selection + ----------------------------------------------------------------------------------------------------------------------*/ + function preventSelection(el) { + el.classList.add('fc-unselectable'); + el.addEventListener('selectstart', preventDefault); + } + function allowSelection(el) { + el.classList.remove('fc-unselectable'); + el.removeEventListener('selectstart', preventDefault); + } + /* Context Menu + ----------------------------------------------------------------------------------------------------------------------*/ + function preventContextMenu(el) { + el.addEventListener('contextmenu', preventDefault); + } + function allowContextMenu(el) { + el.removeEventListener('contextmenu', preventDefault); + } + function parseFieldSpecs(input) { + var specs = []; + var tokens = []; + var i; + var token; + if (typeof input === 'string') { + tokens = input.split(/\s*,\s*/); + } + else if (typeof input === 'function') { + tokens = [input]; + } + else if (Array.isArray(input)) { + tokens = input; + } + for (i = 0; i < tokens.length; i += 1) { + token = tokens[i]; + if (typeof token === 'string') { + specs.push(token.charAt(0) === '-' ? + { field: token.substring(1), order: -1 } : + { field: token, order: 1 }); + } + else if (typeof token === 'function') { + specs.push({ func: token }); + } + } + return specs; + } + function compareByFieldSpecs(obj0, obj1, fieldSpecs) { + var i; + var cmp; + for (i = 0; i < fieldSpecs.length; i += 1) { + cmp = compareByFieldSpec(obj0, obj1, fieldSpecs[i]); + if (cmp) { + return cmp; + } + } + return 0; + } + function compareByFieldSpec(obj0, obj1, fieldSpec) { + if (fieldSpec.func) { + return fieldSpec.func(obj0, obj1); + } + return flexibleCompare(obj0[fieldSpec.field], obj1[fieldSpec.field]) + * (fieldSpec.order || 1); + } + function flexibleCompare(a, b) { + if (!a && !b) { + return 0; + } + if (b == null) { + return -1; + } + if (a == null) { + return 1; + } + if (typeof a === 'string' || typeof b === 'string') { + return String(a).localeCompare(String(b)); + } + return a - b; + } + /* String Utilities + ----------------------------------------------------------------------------------------------------------------------*/ + function padStart(val, len) { + var s = String(val); + return '000'.substr(0, len - s.length) + s; + } + /* Number Utilities + ----------------------------------------------------------------------------------------------------------------------*/ + function compareNumbers(a, b) { + return a - b; + } + function isInt(n) { + return n % 1 === 0; + } + /* FC-specific DOM dimension stuff + ----------------------------------------------------------------------------------------------------------------------*/ + function computeSmallestCellWidth(cellEl) { + var allWidthEl = cellEl.querySelector('.fc-scrollgrid-shrink-frame'); + var contentWidthEl = cellEl.querySelector('.fc-scrollgrid-shrink-cushion'); + if (!allWidthEl) { + throw new Error('needs fc-scrollgrid-shrink-frame className'); // TODO: use const + } + if (!contentWidthEl) { + throw new Error('needs fc-scrollgrid-shrink-cushion className'); + } + return cellEl.getBoundingClientRect().width - allWidthEl.getBoundingClientRect().width + // the cell padding+border + contentWidthEl.getBoundingClientRect().width; + } + + var DAY_IDS = ['sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat']; + // Adding + function addWeeks(m, n) { + var a = dateToUtcArray(m); + a[2] += n * 7; + return arrayToUtcDate(a); + } + function addDays(m, n) { + var a = dateToUtcArray(m); + a[2] += n; + return arrayToUtcDate(a); + } + function addMs(m, n) { + var a = dateToUtcArray(m); + a[6] += n; + return arrayToUtcDate(a); + } + // Diffing (all return floats) + // TODO: why not use ranges? + function diffWeeks(m0, m1) { + return diffDays(m0, m1) / 7; + } + function diffDays(m0, m1) { + return (m1.valueOf() - m0.valueOf()) / (1000 * 60 * 60 * 24); + } + function diffHours(m0, m1) { + return (m1.valueOf() - m0.valueOf()) / (1000 * 60 * 60); + } + function diffMinutes(m0, m1) { + return (m1.valueOf() - m0.valueOf()) / (1000 * 60); + } + function diffSeconds(m0, m1) { + return (m1.valueOf() - m0.valueOf()) / 1000; + } + function diffDayAndTime(m0, m1) { + var m0day = startOfDay(m0); + var m1day = startOfDay(m1); + return { + years: 0, + months: 0, + days: Math.round(diffDays(m0day, m1day)), + milliseconds: (m1.valueOf() - m1day.valueOf()) - (m0.valueOf() - m0day.valueOf()), + }; + } + // Diffing Whole Units + function diffWholeWeeks(m0, m1) { + var d = diffWholeDays(m0, m1); + if (d !== null && d % 7 === 0) { + return d / 7; + } + return null; + } + function diffWholeDays(m0, m1) { + if (timeAsMs(m0) === timeAsMs(m1)) { + return Math.round(diffDays(m0, m1)); + } + return null; + } + // Start-Of + function startOfDay(m) { + return arrayToUtcDate([ + m.getUTCFullYear(), + m.getUTCMonth(), + m.getUTCDate(), + ]); + } + function startOfHour(m) { + return arrayToUtcDate([ + m.getUTCFullYear(), + m.getUTCMonth(), + m.getUTCDate(), + m.getUTCHours(), + ]); + } + function startOfMinute(m) { + return arrayToUtcDate([ + m.getUTCFullYear(), + m.getUTCMonth(), + m.getUTCDate(), + m.getUTCHours(), + m.getUTCMinutes(), + ]); + } + function startOfSecond(m) { + return arrayToUtcDate([ + m.getUTCFullYear(), + m.getUTCMonth(), + m.getUTCDate(), + m.getUTCHours(), + m.getUTCMinutes(), + m.getUTCSeconds(), + ]); + } + // Week Computation + function weekOfYear(marker, dow, doy) { + var y = marker.getUTCFullYear(); + var w = weekOfGivenYear(marker, y, dow, doy); + if (w < 1) { + return weekOfGivenYear(marker, y - 1, dow, doy); + } + var nextW = weekOfGivenYear(marker, y + 1, dow, doy); + if (nextW >= 1) { + return Math.min(w, nextW); + } + return w; + } + function weekOfGivenYear(marker, year, dow, doy) { + var firstWeekStart = arrayToUtcDate([year, 0, 1 + firstWeekOffset(year, dow, doy)]); + var dayStart = startOfDay(marker); + var days = Math.round(diffDays(firstWeekStart, dayStart)); + return Math.floor(days / 7) + 1; // zero-indexed + } + // start-of-first-week - start-of-year + function firstWeekOffset(year, dow, doy) { + // first-week day -- which january is always in the first week (4 for iso, 1 for other) + var fwd = 7 + dow - doy; + // first-week day local weekday -- which local weekday is fwd + var fwdlw = (7 + arrayToUtcDate([year, 0, fwd]).getUTCDay() - dow) % 7; + return -fwdlw + fwd - 1; + } + // Array Conversion + function dateToLocalArray(date) { + return [ + date.getFullYear(), + date.getMonth(), + date.getDate(), + date.getHours(), + date.getMinutes(), + date.getSeconds(), + date.getMilliseconds(), + ]; + } + function arrayToLocalDate(a) { + return new Date(a[0], a[1] || 0, a[2] == null ? 1 : a[2], // day of month + a[3] || 0, a[4] || 0, a[5] || 0); + } + function dateToUtcArray(date) { + return [ + date.getUTCFullYear(), + date.getUTCMonth(), + date.getUTCDate(), + date.getUTCHours(), + date.getUTCMinutes(), + date.getUTCSeconds(), + date.getUTCMilliseconds(), + ]; + } + function arrayToUtcDate(a) { + // according to web standards (and Safari), a month index is required. + // massage if only given a year. + if (a.length === 1) { + a = a.concat([0]); + } + return new Date(Date.UTC.apply(Date, a)); + } + // Other Utils + function isValidDate(m) { + return !isNaN(m.valueOf()); + } + function timeAsMs(m) { + return m.getUTCHours() * 1000 * 60 * 60 + + m.getUTCMinutes() * 1000 * 60 + + m.getUTCSeconds() * 1000 + + m.getUTCMilliseconds(); + } + + function createEventInstance(defId, range, forcedStartTzo, forcedEndTzo) { + return { + instanceId: guid(), + defId: defId, + range: range, + forcedStartTzo: forcedStartTzo == null ? null : forcedStartTzo, + forcedEndTzo: forcedEndTzo == null ? null : forcedEndTzo, + }; + } + + var hasOwnProperty = Object.prototype.hasOwnProperty; + // Merges an array of objects into a single object. + // The second argument allows for an array of property names who's object values will be merged together. + function mergeProps(propObjs, complexPropsMap) { + var dest = {}; + if (complexPropsMap) { + for (var name_1 in complexPropsMap) { + var complexObjs = []; + // collect the trailing object values, stopping when a non-object is discovered + for (var i = propObjs.length - 1; i >= 0; i -= 1) { + var val = propObjs[i][name_1]; + if (typeof val === 'object' && val) { // non-null object + complexObjs.unshift(val); + } + else if (val !== undefined) { + dest[name_1] = val; // if there were no objects, this value will be used + break; + } + } + // if the trailing values were objects, use the merged value + if (complexObjs.length) { + dest[name_1] = mergeProps(complexObjs); + } + } + } + // copy values into the destination, going from last to first + for (var i = propObjs.length - 1; i >= 0; i -= 1) { + var props = propObjs[i]; + for (var name_2 in props) { + if (!(name_2 in dest)) { // if already assigned by previous props or complex props, don't reassign + dest[name_2] = props[name_2]; + } + } + } + return dest; + } + function filterHash(hash, func) { + var filtered = {}; + for (var key in hash) { + if (func(hash[key], key)) { + filtered[key] = hash[key]; + } + } + return filtered; + } + function mapHash(hash, func) { + var newHash = {}; + for (var key in hash) { + newHash[key] = func(hash[key], key); + } + return newHash; + } + function arrayToHash(a) { + var hash = {}; + for (var _i = 0, a_1 = a; _i < a_1.length; _i++) { + var item = a_1[_i]; + hash[item] = true; + } + return hash; + } + function buildHashFromArray(a, func) { + var hash = {}; + for (var i = 0; i < a.length; i += 1) { + var tuple = func(a[i], i); + hash[tuple[0]] = tuple[1]; + } + return hash; + } + function hashValuesToArray(obj) { + var a = []; + for (var key in obj) { + a.push(obj[key]); + } + return a; + } + function isPropsEqual(obj0, obj1) { + if (obj0 === obj1) { + return true; + } + for (var key in obj0) { + if (hasOwnProperty.call(obj0, key)) { + if (!(key in obj1)) { + return false; + } + } + } + for (var key in obj1) { + if (hasOwnProperty.call(obj1, key)) { + if (obj0[key] !== obj1[key]) { + return false; + } + } + } + return true; + } + function getUnequalProps(obj0, obj1) { + var keys = []; + for (var key in obj0) { + if (hasOwnProperty.call(obj0, key)) { + if (!(key in obj1)) { + keys.push(key); + } + } + } + for (var key in obj1) { + if (hasOwnProperty.call(obj1, key)) { + if (obj0[key] !== obj1[key]) { + keys.push(key); + } + } + } + return keys; + } + function compareObjs(oldProps, newProps, equalityFuncs) { + if (equalityFuncs === void 0) { equalityFuncs = {}; } + if (oldProps === newProps) { + return true; + } + for (var key in newProps) { + if (key in oldProps && isObjValsEqual(oldProps[key], newProps[key], equalityFuncs[key])) ; + else { + return false; + } + } + // check for props that were omitted in the new + for (var key in oldProps) { + if (!(key in newProps)) { + return false; + } + } + return true; + } + /* + assumed "true" equality for handler names like "onReceiveSomething" + */ + function isObjValsEqual(val0, val1, comparator) { + if (val0 === val1 || comparator === true) { + return true; + } + if (comparator) { + return comparator(val0, val1); + } + return false; + } + function collectFromHash(hash, startIndex, endIndex, step) { + if (startIndex === void 0) { startIndex = 0; } + if (step === void 0) { step = 1; } + var res = []; + if (endIndex == null) { + endIndex = Object.keys(hash).length; + } + for (var i = startIndex; i < endIndex; i += step) { + var val = hash[i]; + if (val !== undefined) { // will disregard undefined for sparse arrays + res.push(val); + } + } + return res; + } + + function parseRecurring(refined, defaultAllDay, dateEnv, recurringTypes) { + for (var i = 0; i < recurringTypes.length; i += 1) { + var parsed = recurringTypes[i].parse(refined, dateEnv); + if (parsed) { + var allDay = refined.allDay; + if (allDay == null) { + allDay = defaultAllDay; + if (allDay == null) { + allDay = parsed.allDayGuess; + if (allDay == null) { + allDay = false; + } + } + } + return { + allDay: allDay, + duration: parsed.duration, + typeData: parsed.typeData, + typeId: i, + }; + } + } + return null; + } + function expandRecurring(eventStore, framingRange, context) { + var dateEnv = context.dateEnv, pluginHooks = context.pluginHooks, options = context.options; + var defs = eventStore.defs, instances = eventStore.instances; + // remove existing recurring instances + // TODO: bad. always expand events as a second step + instances = filterHash(instances, function (instance) { return !defs[instance.defId].recurringDef; }); + for (var defId in defs) { + var def = defs[defId]; + if (def.recurringDef) { + var duration = def.recurringDef.duration; + if (!duration) { + duration = def.allDay ? + options.defaultAllDayEventDuration : + options.defaultTimedEventDuration; + } + var starts = expandRecurringRanges(def, duration, framingRange, dateEnv, pluginHooks.recurringTypes); + for (var _i = 0, starts_1 = starts; _i < starts_1.length; _i++) { + var start = starts_1[_i]; + var instance = createEventInstance(defId, { + start: start, + end: dateEnv.add(start, duration), + }); + instances[instance.instanceId] = instance; + } + } + } + return { defs: defs, instances: instances }; + } + /* + Event MUST have a recurringDef + */ + function expandRecurringRanges(eventDef, duration, framingRange, dateEnv, recurringTypes) { + var typeDef = recurringTypes[eventDef.recurringDef.typeId]; + var markers = typeDef.expand(eventDef.recurringDef.typeData, { + start: dateEnv.subtract(framingRange.start, duration), + end: framingRange.end, + }, dateEnv); + // the recurrence plugins don't guarantee that all-day events are start-of-day, so we have to + if (eventDef.allDay) { + markers = markers.map(startOfDay); + } + return markers; + } + + var INTERNAL_UNITS = ['years', 'months', 'days', 'milliseconds']; + var PARSE_RE = /^(-?)(?:(\d+)\.)?(\d+):(\d\d)(?::(\d\d)(?:\.(\d\d\d))?)?/; + // Parsing and Creation + function createDuration(input, unit) { + var _a; + if (typeof input === 'string') { + return parseString(input); + } + if (typeof input === 'object' && input) { // non-null object + return parseObject(input); + } + if (typeof input === 'number') { + return parseObject((_a = {}, _a[unit || 'milliseconds'] = input, _a)); + } + return null; + } + function parseString(s) { + var m = PARSE_RE.exec(s); + if (m) { + var sign = m[1] ? -1 : 1; + return { + years: 0, + months: 0, + days: sign * (m[2] ? parseInt(m[2], 10) : 0), + milliseconds: sign * ((m[3] ? parseInt(m[3], 10) : 0) * 60 * 60 * 1000 + // hours + (m[4] ? parseInt(m[4], 10) : 0) * 60 * 1000 + // minutes + (m[5] ? parseInt(m[5], 10) : 0) * 1000 + // seconds + (m[6] ? parseInt(m[6], 10) : 0) // ms + ), + }; + } + return null; + } + function parseObject(obj) { + var duration = { + years: obj.years || obj.year || 0, + months: obj.months || obj.month || 0, + days: obj.days || obj.day || 0, + milliseconds: (obj.hours || obj.hour || 0) * 60 * 60 * 1000 + // hours + (obj.minutes || obj.minute || 0) * 60 * 1000 + // minutes + (obj.seconds || obj.second || 0) * 1000 + // seconds + (obj.milliseconds || obj.millisecond || obj.ms || 0), // ms + }; + var weeks = obj.weeks || obj.week; + if (weeks) { + duration.days += weeks * 7; + duration.specifiedWeeks = true; + } + return duration; + } + // Equality + function durationsEqual(d0, d1) { + return d0.years === d1.years && + d0.months === d1.months && + d0.days === d1.days && + d0.milliseconds === d1.milliseconds; + } + function asCleanDays(dur) { + if (!dur.years && !dur.months && !dur.milliseconds) { + return dur.days; + } + return 0; + } + // Simple Math + function addDurations(d0, d1) { + return { + years: d0.years + d1.years, + months: d0.months + d1.months, + days: d0.days + d1.days, + milliseconds: d0.milliseconds + d1.milliseconds, + }; + } + function subtractDurations(d1, d0) { + return { + years: d1.years - d0.years, + months: d1.months - d0.months, + days: d1.days - d0.days, + milliseconds: d1.milliseconds - d0.milliseconds, + }; + } + function multiplyDuration(d, n) { + return { + years: d.years * n, + months: d.months * n, + days: d.days * n, + milliseconds: d.milliseconds * n, + }; + } + // Conversions + // "Rough" because they are based on average-case Gregorian months/years + function asRoughYears(dur) { + return asRoughDays(dur) / 365; + } + function asRoughMonths(dur) { + return asRoughDays(dur) / 30; + } + function asRoughDays(dur) { + return asRoughMs(dur) / 864e5; + } + function asRoughMinutes(dur) { + return asRoughMs(dur) / (1000 * 60); + } + function asRoughSeconds(dur) { + return asRoughMs(dur) / 1000; + } + function asRoughMs(dur) { + return dur.years * (365 * 864e5) + + dur.months * (30 * 864e5) + + dur.days * 864e5 + + dur.milliseconds; + } + // Advanced Math + function wholeDivideDurations(numerator, denominator) { + var res = null; + for (var i = 0; i < INTERNAL_UNITS.length; i += 1) { + var unit = INTERNAL_UNITS[i]; + if (denominator[unit]) { + var localRes = numerator[unit] / denominator[unit]; + if (!isInt(localRes) || (res !== null && res !== localRes)) { + return null; + } + res = localRes; + } + else if (numerator[unit]) { + // needs to divide by something but can't! + return null; + } + } + return res; + } + function greatestDurationDenominator(dur) { + var ms = dur.milliseconds; + if (ms) { + if (ms % 1000 !== 0) { + return { unit: 'millisecond', value: ms }; + } + if (ms % (1000 * 60) !== 0) { + return { unit: 'second', value: ms / 1000 }; + } + if (ms % (1000 * 60 * 60) !== 0) { + return { unit: 'minute', value: ms / (1000 * 60) }; + } + if (ms) { + return { unit: 'hour', value: ms / (1000 * 60 * 60) }; + } + } + if (dur.days) { + if (dur.specifiedWeeks && dur.days % 7 === 0) { + return { unit: 'week', value: dur.days / 7 }; + } + return { unit: 'day', value: dur.days }; + } + if (dur.months) { + return { unit: 'month', value: dur.months }; + } + if (dur.years) { + return { unit: 'year', value: dur.years }; + } + return { unit: 'millisecond', value: 0 }; + } + + // timeZoneOffset is in minutes + function buildIsoString(marker, timeZoneOffset, stripZeroTime) { + if (stripZeroTime === void 0) { stripZeroTime = false; } + var s = marker.toISOString(); + s = s.replace('.000', ''); + if (stripZeroTime) { + s = s.replace('T00:00:00Z', ''); + } + if (s.length > 10) { // time part wasn't stripped, can add timezone info + if (timeZoneOffset == null) { + s = s.replace('Z', ''); + } + else if (timeZoneOffset !== 0) { + s = s.replace('Z', formatTimeZoneOffset(timeZoneOffset, true)); + } + // otherwise, its UTC-0 and we want to keep the Z + } + return s; + } + // formats the date, but with no time part + // TODO: somehow merge with buildIsoString and stripZeroTime + // TODO: rename. omit "string" + function formatDayString(marker) { + return marker.toISOString().replace(/T.*$/, ''); + } + // TODO: use Date::toISOString and use everything after the T? + function formatIsoTimeString(marker) { + return padStart(marker.getUTCHours(), 2) + ':' + + padStart(marker.getUTCMinutes(), 2) + ':' + + padStart(marker.getUTCSeconds(), 2); + } + function formatTimeZoneOffset(minutes, doIso) { + if (doIso === void 0) { doIso = false; } + var sign = minutes < 0 ? '-' : '+'; + var abs = Math.abs(minutes); + var hours = Math.floor(abs / 60); + var mins = Math.round(abs % 60); + if (doIso) { + return sign + padStart(hours, 2) + ":" + padStart(mins, 2); + } + return "GMT" + sign + hours + (mins ? ":" + padStart(mins, 2) : ''); + } + + // TODO: new util arrayify? + function removeExact(array, exactVal) { + var removeCnt = 0; + var i = 0; + while (i < array.length) { + if (array[i] === exactVal) { + array.splice(i, 1); + removeCnt += 1; + } + else { + i += 1; + } + } + return removeCnt; + } + function isArraysEqual(a0, a1, equalityFunc) { + if (a0 === a1) { + return true; + } + var len = a0.length; + var i; + if (len !== a1.length) { // not array? or not same length? + return false; + } + for (i = 0; i < len; i += 1) { + if (!(equalityFunc ? equalityFunc(a0[i], a1[i]) : a0[i] === a1[i])) { + return false; + } + } + return true; + } + + function memoize(workerFunc, resEquality, teardownFunc) { + var currentArgs; + var currentRes; + return function () { + var newArgs = []; + for (var _i = 0; _i < arguments.length; _i++) { + newArgs[_i] = arguments[_i]; + } + if (!currentArgs) { + currentRes = workerFunc.apply(this, newArgs); + } + else if (!isArraysEqual(currentArgs, newArgs)) { + if (teardownFunc) { + teardownFunc(currentRes); + } + var res = workerFunc.apply(this, newArgs); + if (!resEquality || !resEquality(res, currentRes)) { + currentRes = res; + } + } + currentArgs = newArgs; + return currentRes; + }; + } + function memoizeObjArg(workerFunc, resEquality, teardownFunc) { + var _this = this; + var currentArg; + var currentRes; + return function (newArg) { + if (!currentArg) { + currentRes = workerFunc.call(_this, newArg); + } + else if (!isPropsEqual(currentArg, newArg)) { + if (teardownFunc) { + teardownFunc(currentRes); + } + var res = workerFunc.call(_this, newArg); + if (!resEquality || !resEquality(res, currentRes)) { + currentRes = res; + } + } + currentArg = newArg; + return currentRes; + }; + } + function memoizeArraylike(// used at all? + workerFunc, resEquality, teardownFunc) { + var _this = this; + var currentArgSets = []; + var currentResults = []; + return function (newArgSets) { + var currentLen = currentArgSets.length; + var newLen = newArgSets.length; + var i = 0; + for (; i < currentLen; i += 1) { + if (!newArgSets[i]) { // one of the old sets no longer exists + if (teardownFunc) { + teardownFunc(currentResults[i]); + } + } + else if (!isArraysEqual(currentArgSets[i], newArgSets[i])) { + if (teardownFunc) { + teardownFunc(currentResults[i]); + } + var res = workerFunc.apply(_this, newArgSets[i]); + if (!resEquality || !resEquality(res, currentResults[i])) { + currentResults[i] = res; + } + } + } + for (; i < newLen; i += 1) { + currentResults[i] = workerFunc.apply(_this, newArgSets[i]); + } + currentArgSets = newArgSets; + currentResults.splice(newLen); // remove excess + return currentResults; + }; + } + function memoizeHashlike(// used? + workerFunc, resEquality, teardownFunc) { + var _this = this; + var currentArgHash = {}; + var currentResHash = {}; + return function (newArgHash) { + var newResHash = {}; + for (var key in newArgHash) { + if (!currentResHash[key]) { + newResHash[key] = workerFunc.apply(_this, newArgHash[key]); + } + else if (!isArraysEqual(currentArgHash[key], newArgHash[key])) { + if (teardownFunc) { + teardownFunc(currentResHash[key]); + } + var res = workerFunc.apply(_this, newArgHash[key]); + newResHash[key] = (resEquality && resEquality(res, currentResHash[key])) + ? currentResHash[key] + : res; + } + else { + newResHash[key] = currentResHash[key]; + } + } + currentArgHash = newArgHash; + currentResHash = newResHash; + return newResHash; + }; + } + + var EXTENDED_SETTINGS_AND_SEVERITIES = { + week: 3, + separator: 0, + omitZeroMinute: 0, + meridiem: 0, + omitCommas: 0, + }; + var STANDARD_DATE_PROP_SEVERITIES = { + timeZoneName: 7, + era: 6, + year: 5, + month: 4, + day: 2, + weekday: 2, + hour: 1, + minute: 1, + second: 1, + }; + var MERIDIEM_RE = /\s*([ap])\.?m\.?/i; // eats up leading spaces too + var COMMA_RE = /,/g; // we need re for globalness + var MULTI_SPACE_RE = /\s+/g; + var LTR_RE = /\u200e/g; // control character + var UTC_RE = /UTC|GMT/; + var NativeFormatter = /** @class */ (function () { + function NativeFormatter(formatSettings) { + var standardDateProps = {}; + var extendedSettings = {}; + var severity = 0; + for (var name_1 in formatSettings) { + if (name_1 in EXTENDED_SETTINGS_AND_SEVERITIES) { + extendedSettings[name_1] = formatSettings[name_1]; + severity = Math.max(EXTENDED_SETTINGS_AND_SEVERITIES[name_1], severity); + } + else { + standardDateProps[name_1] = formatSettings[name_1]; + if (name_1 in STANDARD_DATE_PROP_SEVERITIES) { // TODO: what about hour12? no severity + severity = Math.max(STANDARD_DATE_PROP_SEVERITIES[name_1], severity); + } + } + } + this.standardDateProps = standardDateProps; + this.extendedSettings = extendedSettings; + this.severity = severity; + this.buildFormattingFunc = memoize(buildFormattingFunc); + } + NativeFormatter.prototype.format = function (date, context) { + return this.buildFormattingFunc(this.standardDateProps, this.extendedSettings, context)(date); + }; + NativeFormatter.prototype.formatRange = function (start, end, context, betterDefaultSeparator) { + var _a = this, standardDateProps = _a.standardDateProps, extendedSettings = _a.extendedSettings; + var diffSeverity = computeMarkerDiffSeverity(start.marker, end.marker, context.calendarSystem); + if (!diffSeverity) { + return this.format(start, context); + } + var biggestUnitForPartial = diffSeverity; + if (biggestUnitForPartial > 1 && // the two dates are different in a way that's larger scale than time + (standardDateProps.year === 'numeric' || standardDateProps.year === '2-digit') && + (standardDateProps.month === 'numeric' || standardDateProps.month === '2-digit') && + (standardDateProps.day === 'numeric' || standardDateProps.day === '2-digit')) { + biggestUnitForPartial = 1; // make it look like the dates are only different in terms of time + } + var full0 = this.format(start, context); + var full1 = this.format(end, context); + if (full0 === full1) { + return full0; + } + var partialDateProps = computePartialFormattingOptions(standardDateProps, biggestUnitForPartial); + var partialFormattingFunc = buildFormattingFunc(partialDateProps, extendedSettings, context); + var partial0 = partialFormattingFunc(start); + var partial1 = partialFormattingFunc(end); + var insertion = findCommonInsertion(full0, partial0, full1, partial1); + var separator = extendedSettings.separator || betterDefaultSeparator || context.defaultSeparator || ''; + if (insertion) { + return insertion.before + partial0 + separator + partial1 + insertion.after; + } + return full0 + separator + full1; + }; + NativeFormatter.prototype.getLargestUnit = function () { + switch (this.severity) { + case 7: + case 6: + case 5: + return 'year'; + case 4: + return 'month'; + case 3: + return 'week'; + case 2: + return 'day'; + default: + return 'time'; // really? + } + }; + return NativeFormatter; + }()); + function buildFormattingFunc(standardDateProps, extendedSettings, context) { + var standardDatePropCnt = Object.keys(standardDateProps).length; + if (standardDatePropCnt === 1 && standardDateProps.timeZoneName === 'short') { + return function (date) { return (formatTimeZoneOffset(date.timeZoneOffset)); }; + } + if (standardDatePropCnt === 0 && extendedSettings.week) { + return function (date) { return (formatWeekNumber(context.computeWeekNumber(date.marker), context.weekText, context.locale, extendedSettings.week)); }; + } + return buildNativeFormattingFunc(standardDateProps, extendedSettings, context); + } + function buildNativeFormattingFunc(standardDateProps, extendedSettings, context) { + standardDateProps = __assign({}, standardDateProps); // copy + extendedSettings = __assign({}, extendedSettings); // copy + sanitizeSettings(standardDateProps, extendedSettings); + standardDateProps.timeZone = 'UTC'; // we leverage the only guaranteed timeZone for our UTC markers + var normalFormat = new Intl.DateTimeFormat(context.locale.codes, standardDateProps); + var zeroFormat; // needed? + if (extendedSettings.omitZeroMinute) { + var zeroProps = __assign({}, standardDateProps); + delete zeroProps.minute; // seconds and ms were already considered in sanitizeSettings + zeroFormat = new Intl.DateTimeFormat(context.locale.codes, zeroProps); + } + return function (date) { + var marker = date.marker; + var format; + if (zeroFormat && !marker.getUTCMinutes()) { + format = zeroFormat; + } + else { + format = normalFormat; + } + var s = format.format(marker); + return postProcess(s, date, standardDateProps, extendedSettings, context); + }; + } + function sanitizeSettings(standardDateProps, extendedSettings) { + // deal with a browser inconsistency where formatting the timezone + // requires that the hour/minute be present. + if (standardDateProps.timeZoneName) { + if (!standardDateProps.hour) { + standardDateProps.hour = '2-digit'; + } + if (!standardDateProps.minute) { + standardDateProps.minute = '2-digit'; + } + } + // only support short timezone names + if (standardDateProps.timeZoneName === 'long') { + standardDateProps.timeZoneName = 'short'; + } + // if requesting to display seconds, MUST display minutes + if (extendedSettings.omitZeroMinute && (standardDateProps.second || standardDateProps.millisecond)) { + delete extendedSettings.omitZeroMinute; + } + } + function postProcess(s, date, standardDateProps, extendedSettings, context) { + s = s.replace(LTR_RE, ''); // remove left-to-right control chars. do first. good for other regexes + if (standardDateProps.timeZoneName === 'short') { + s = injectTzoStr(s, (context.timeZone === 'UTC' || date.timeZoneOffset == null) ? + 'UTC' : // important to normalize for IE, which does "GMT" + formatTimeZoneOffset(date.timeZoneOffset)); + } + if (extendedSettings.omitCommas) { + s = s.replace(COMMA_RE, '').trim(); + } + if (extendedSettings.omitZeroMinute) { + s = s.replace(':00', ''); // zeroFormat doesn't always achieve this + } + // ^ do anything that might create adjacent spaces before this point, + // because MERIDIEM_RE likes to eat up loading spaces + if (extendedSettings.meridiem === false) { + s = s.replace(MERIDIEM_RE, '').trim(); + } + else if (extendedSettings.meridiem === 'narrow') { // a/p + s = s.replace(MERIDIEM_RE, function (m0, m1) { return m1.toLocaleLowerCase(); }); + } + else if (extendedSettings.meridiem === 'short') { // am/pm + s = s.replace(MERIDIEM_RE, function (m0, m1) { return m1.toLocaleLowerCase() + "m"; }); + } + else if (extendedSettings.meridiem === 'lowercase') { // other meridiem transformers already converted to lowercase + s = s.replace(MERIDIEM_RE, function (m0) { return m0.toLocaleLowerCase(); }); + } + s = s.replace(MULTI_SPACE_RE, ' '); + s = s.trim(); + return s; + } + function injectTzoStr(s, tzoStr) { + var replaced = false; + s = s.replace(UTC_RE, function () { + replaced = true; + return tzoStr; + }); + // IE11 doesn't include UTC/GMT in the original string, so append to end + if (!replaced) { + s += " " + tzoStr; + } + return s; + } + function formatWeekNumber(num, weekText, locale, display) { + var parts = []; + if (display === 'narrow') { + parts.push(weekText); + } + else if (display === 'short') { + parts.push(weekText, ' '); + } + // otherwise, considered 'numeric' + parts.push(locale.simpleNumberFormat.format(num)); + if (locale.options.direction === 'rtl') { // TODO: use control characters instead? + parts.reverse(); + } + return parts.join(''); + } + // Range Formatting Utils + // 0 = exactly the same + // 1 = different by time + // and bigger + function computeMarkerDiffSeverity(d0, d1, ca) { + if (ca.getMarkerYear(d0) !== ca.getMarkerYear(d1)) { + return 5; + } + if (ca.getMarkerMonth(d0) !== ca.getMarkerMonth(d1)) { + return 4; + } + if (ca.getMarkerDay(d0) !== ca.getMarkerDay(d1)) { + return 2; + } + if (timeAsMs(d0) !== timeAsMs(d1)) { + return 1; + } + return 0; + } + function computePartialFormattingOptions(options, biggestUnit) { + var partialOptions = {}; + for (var name_2 in options) { + if (!(name_2 in STANDARD_DATE_PROP_SEVERITIES) || // not a date part prop (like timeZone) + STANDARD_DATE_PROP_SEVERITIES[name_2] <= biggestUnit) { + partialOptions[name_2] = options[name_2]; + } + } + return partialOptions; + } + function findCommonInsertion(full0, partial0, full1, partial1) { + var i0 = 0; + while (i0 < full0.length) { + var found0 = full0.indexOf(partial0, i0); + if (found0 === -1) { + break; + } + var before0 = full0.substr(0, found0); + i0 = found0 + partial0.length; + var after0 = full0.substr(i0); + var i1 = 0; + while (i1 < full1.length) { + var found1 = full1.indexOf(partial1, i1); + if (found1 === -1) { + break; + } + var before1 = full1.substr(0, found1); + i1 = found1 + partial1.length; + var after1 = full1.substr(i1); + if (before0 === before1 && after0 === after1) { + return { + before: before0, + after: after0, + }; + } + } + } + return null; + } + + function expandZonedMarker(dateInfo, calendarSystem) { + var a = calendarSystem.markerToArray(dateInfo.marker); + return { + marker: dateInfo.marker, + timeZoneOffset: dateInfo.timeZoneOffset, + array: a, + year: a[0], + month: a[1], + day: a[2], + hour: a[3], + minute: a[4], + second: a[5], + millisecond: a[6], + }; + } + + function createVerboseFormattingArg(start, end, context, betterDefaultSeparator) { + var startInfo = expandZonedMarker(start, context.calendarSystem); + var endInfo = end ? expandZonedMarker(end, context.calendarSystem) : null; + return { + date: startInfo, + start: startInfo, + end: endInfo, + timeZone: context.timeZone, + localeCodes: context.locale.codes, + defaultSeparator: betterDefaultSeparator || context.defaultSeparator, + }; + } + + /* + TODO: fix the terminology of "formatter" vs "formatting func" + */ + /* + At the time of instantiation, this object does not know which cmd-formatting system it will use. + It receives this at the time of formatting, as a setting. + */ + var CmdFormatter = /** @class */ (function () { + function CmdFormatter(cmdStr) { + this.cmdStr = cmdStr; + } + CmdFormatter.prototype.format = function (date, context, betterDefaultSeparator) { + return context.cmdFormatter(this.cmdStr, createVerboseFormattingArg(date, null, context, betterDefaultSeparator)); + }; + CmdFormatter.prototype.formatRange = function (start, end, context, betterDefaultSeparator) { + return context.cmdFormatter(this.cmdStr, createVerboseFormattingArg(start, end, context, betterDefaultSeparator)); + }; + return CmdFormatter; + }()); + + var FuncFormatter = /** @class */ (function () { + function FuncFormatter(func) { + this.func = func; + } + FuncFormatter.prototype.format = function (date, context, betterDefaultSeparator) { + return this.func(createVerboseFormattingArg(date, null, context, betterDefaultSeparator)); + }; + FuncFormatter.prototype.formatRange = function (start, end, context, betterDefaultSeparator) { + return this.func(createVerboseFormattingArg(start, end, context, betterDefaultSeparator)); + }; + return FuncFormatter; + }()); + + function createFormatter(input) { + if (typeof input === 'object' && input) { // non-null object + return new NativeFormatter(input); + } + if (typeof input === 'string') { + return new CmdFormatter(input); + } + if (typeof input === 'function') { + return new FuncFormatter(input); + } + return null; + } + + // base options + // ------------ + var BASE_OPTION_REFINERS = { + navLinkDayClick: identity, + navLinkWeekClick: identity, + duration: createDuration, + bootstrapFontAwesome: identity, + buttonIcons: identity, + customButtons: identity, + defaultAllDayEventDuration: createDuration, + defaultTimedEventDuration: createDuration, + nextDayThreshold: createDuration, + scrollTime: createDuration, + scrollTimeReset: Boolean, + slotMinTime: createDuration, + slotMaxTime: createDuration, + dayPopoverFormat: createFormatter, + slotDuration: createDuration, + snapDuration: createDuration, + headerToolbar: identity, + footerToolbar: identity, + defaultRangeSeparator: String, + titleRangeSeparator: String, + forceEventDuration: Boolean, + dayHeaders: Boolean, + dayHeaderFormat: createFormatter, + dayHeaderClassNames: identity, + dayHeaderContent: identity, + dayHeaderDidMount: identity, + dayHeaderWillUnmount: identity, + dayCellClassNames: identity, + dayCellContent: identity, + dayCellDidMount: identity, + dayCellWillUnmount: identity, + initialView: String, + aspectRatio: Number, + weekends: Boolean, + weekNumberCalculation: identity, + weekNumbers: Boolean, + weekNumberClassNames: identity, + weekNumberContent: identity, + weekNumberDidMount: identity, + weekNumberWillUnmount: identity, + editable: Boolean, + viewClassNames: identity, + viewDidMount: identity, + viewWillUnmount: identity, + nowIndicator: Boolean, + nowIndicatorClassNames: identity, + nowIndicatorContent: identity, + nowIndicatorDidMount: identity, + nowIndicatorWillUnmount: identity, + showNonCurrentDates: Boolean, + lazyFetching: Boolean, + startParam: String, + endParam: String, + timeZoneParam: String, + timeZone: String, + locales: identity, + locale: identity, + themeSystem: String, + dragRevertDuration: Number, + dragScroll: Boolean, + allDayMaintainDuration: Boolean, + unselectAuto: Boolean, + dropAccept: identity, + eventOrder: parseFieldSpecs, + eventOrderStrict: Boolean, + handleWindowResize: Boolean, + windowResizeDelay: Number, + longPressDelay: Number, + eventDragMinDistance: Number, + expandRows: Boolean, + height: identity, + contentHeight: identity, + direction: String, + weekNumberFormat: createFormatter, + eventResizableFromStart: Boolean, + displayEventTime: Boolean, + displayEventEnd: Boolean, + weekText: String, + progressiveEventRendering: Boolean, + businessHours: identity, + initialDate: identity, + now: identity, + eventDataTransform: identity, + stickyHeaderDates: identity, + stickyFooterScrollbar: identity, + viewHeight: identity, + defaultAllDay: Boolean, + eventSourceFailure: identity, + eventSourceSuccess: identity, + eventDisplay: String, + eventStartEditable: Boolean, + eventDurationEditable: Boolean, + eventOverlap: identity, + eventConstraint: identity, + eventAllow: identity, + eventBackgroundColor: String, + eventBorderColor: String, + eventTextColor: String, + eventColor: String, + eventClassNames: identity, + eventContent: identity, + eventDidMount: identity, + eventWillUnmount: identity, + selectConstraint: identity, + selectOverlap: identity, + selectAllow: identity, + droppable: Boolean, + unselectCancel: String, + slotLabelFormat: identity, + slotLaneClassNames: identity, + slotLaneContent: identity, + slotLaneDidMount: identity, + slotLaneWillUnmount: identity, + slotLabelClassNames: identity, + slotLabelContent: identity, + slotLabelDidMount: identity, + slotLabelWillUnmount: identity, + dayMaxEvents: identity, + dayMaxEventRows: identity, + dayMinWidth: Number, + slotLabelInterval: createDuration, + allDayText: String, + allDayClassNames: identity, + allDayContent: identity, + allDayDidMount: identity, + allDayWillUnmount: identity, + slotMinWidth: Number, + navLinks: Boolean, + eventTimeFormat: createFormatter, + rerenderDelay: Number, + moreLinkText: identity, + selectMinDistance: Number, + selectable: Boolean, + selectLongPressDelay: Number, + eventLongPressDelay: Number, + selectMirror: Boolean, + eventMaxStack: Number, + eventMinHeight: Number, + eventMinWidth: Number, + eventShortHeight: Number, + slotEventOverlap: Boolean, + plugins: identity, + firstDay: Number, + dayCount: Number, + dateAlignment: String, + dateIncrement: createDuration, + hiddenDays: identity, + monthMode: Boolean, + fixedWeekCount: Boolean, + validRange: identity, + visibleRange: identity, + titleFormat: identity, + // only used by list-view, but languages define the value, so we need it in base options + noEventsText: String, + moreLinkClick: identity, + moreLinkClassNames: identity, + moreLinkContent: identity, + moreLinkDidMount: identity, + moreLinkWillUnmount: identity, + }; + // do NOT give a type here. need `typeof BASE_OPTION_DEFAULTS` to give real results. + // raw values. + var BASE_OPTION_DEFAULTS = { + eventDisplay: 'auto', + defaultRangeSeparator: ' - ', + titleRangeSeparator: ' \u2013 ', + defaultTimedEventDuration: '01:00:00', + defaultAllDayEventDuration: { day: 1 }, + forceEventDuration: false, + nextDayThreshold: '00:00:00', + dayHeaders: true, + initialView: '', + aspectRatio: 1.35, + headerToolbar: { + start: 'title', + center: '', + end: 'today prev,next', + }, + weekends: true, + weekNumbers: false, + weekNumberCalculation: 'local', + editable: false, + nowIndicator: false, + scrollTime: '06:00:00', + scrollTimeReset: true, + slotMinTime: '00:00:00', + slotMaxTime: '24:00:00', + showNonCurrentDates: true, + lazyFetching: true, + startParam: 'start', + endParam: 'end', + timeZoneParam: 'timeZone', + timeZone: 'local', + locales: [], + locale: '', + themeSystem: 'standard', + dragRevertDuration: 500, + dragScroll: true, + allDayMaintainDuration: false, + unselectAuto: true, + dropAccept: '*', + eventOrder: 'start,-duration,allDay,title', + dayPopoverFormat: { month: 'long', day: 'numeric', year: 'numeric' }, + handleWindowResize: true, + windowResizeDelay: 100, + longPressDelay: 1000, + eventDragMinDistance: 5, + expandRows: false, + navLinks: false, + selectable: false, + eventMinHeight: 15, + eventMinWidth: 30, + eventShortHeight: 30, + }; + // calendar listeners + // ------------------ + var CALENDAR_LISTENER_REFINERS = { + datesSet: identity, + eventsSet: identity, + eventAdd: identity, + eventChange: identity, + eventRemove: identity, + windowResize: identity, + eventClick: identity, + eventMouseEnter: identity, + eventMouseLeave: identity, + select: identity, + unselect: identity, + loading: identity, + // internal + _unmount: identity, + _beforeprint: identity, + _afterprint: identity, + _noEventDrop: identity, + _noEventResize: identity, + _resize: identity, + _scrollRequest: identity, + }; + // calendar-specific options + // ------------------------- + var CALENDAR_OPTION_REFINERS = { + buttonText: identity, + views: identity, + plugins: identity, + initialEvents: identity, + events: identity, + eventSources: identity, + }; + var COMPLEX_OPTION_COMPARATORS = { + headerToolbar: isBoolComplexEqual, + footerToolbar: isBoolComplexEqual, + buttonText: isBoolComplexEqual, + buttonIcons: isBoolComplexEqual, + }; + function isBoolComplexEqual(a, b) { + if (typeof a === 'object' && typeof b === 'object' && a && b) { // both non-null objects + return isPropsEqual(a, b); + } + return a === b; + } + // view-specific options + // --------------------- + var VIEW_OPTION_REFINERS = { + type: String, + component: identity, + buttonText: String, + buttonTextKey: String, + dateProfileGeneratorClass: identity, + usesMinMaxTime: Boolean, + classNames: identity, + content: identity, + didMount: identity, + willUnmount: identity, + }; + // util funcs + // ---------------------------------------------------------------------------------------------------- + function mergeRawOptions(optionSets) { + return mergeProps(optionSets, COMPLEX_OPTION_COMPARATORS); + } + function refineProps(input, refiners) { + var refined = {}; + var extra = {}; + for (var propName in refiners) { + if (propName in input) { + refined[propName] = refiners[propName](input[propName]); + } + } + for (var propName in input) { + if (!(propName in refiners)) { + extra[propName] = input[propName]; + } + } + return { refined: refined, extra: extra }; + } + function identity(raw) { + return raw; + } + + function parseEvents(rawEvents, eventSource, context, allowOpenRange) { + var eventStore = createEmptyEventStore(); + var eventRefiners = buildEventRefiners(context); + for (var _i = 0, rawEvents_1 = rawEvents; _i < rawEvents_1.length; _i++) { + var rawEvent = rawEvents_1[_i]; + var tuple = parseEvent(rawEvent, eventSource, context, allowOpenRange, eventRefiners); + if (tuple) { + eventTupleToStore(tuple, eventStore); + } + } + return eventStore; + } + function eventTupleToStore(tuple, eventStore) { + if (eventStore === void 0) { eventStore = createEmptyEventStore(); } + eventStore.defs[tuple.def.defId] = tuple.def; + if (tuple.instance) { + eventStore.instances[tuple.instance.instanceId] = tuple.instance; + } + return eventStore; + } + // retrieves events that have the same groupId as the instance specified by `instanceId` + // or they are the same as the instance. + // why might instanceId not be in the store? an event from another calendar? + function getRelevantEvents(eventStore, instanceId) { + var instance = eventStore.instances[instanceId]; + if (instance) { + var def_1 = eventStore.defs[instance.defId]; + // get events/instances with same group + var newStore = filterEventStoreDefs(eventStore, function (lookDef) { return isEventDefsGrouped(def_1, lookDef); }); + // add the original + // TODO: wish we could use eventTupleToStore or something like it + newStore.defs[def_1.defId] = def_1; + newStore.instances[instance.instanceId] = instance; + return newStore; + } + return createEmptyEventStore(); + } + function isEventDefsGrouped(def0, def1) { + return Boolean(def0.groupId && def0.groupId === def1.groupId); + } + function createEmptyEventStore() { + return { defs: {}, instances: {} }; + } + function mergeEventStores(store0, store1) { + return { + defs: __assign(__assign({}, store0.defs), store1.defs), + instances: __assign(__assign({}, store0.instances), store1.instances), + }; + } + function filterEventStoreDefs(eventStore, filterFunc) { + var defs = filterHash(eventStore.defs, filterFunc); + var instances = filterHash(eventStore.instances, function (instance) { return (defs[instance.defId] // still exists? + ); }); + return { defs: defs, instances: instances }; + } + function excludeSubEventStore(master, sub) { + var defs = master.defs, instances = master.instances; + var filteredDefs = {}; + var filteredInstances = {}; + for (var defId in defs) { + if (!sub.defs[defId]) { // not explicitly excluded + filteredDefs[defId] = defs[defId]; + } + } + for (var instanceId in instances) { + if (!sub.instances[instanceId] && // not explicitly excluded + filteredDefs[instances[instanceId].defId] // def wasn't filtered away + ) { + filteredInstances[instanceId] = instances[instanceId]; + } + } + return { + defs: filteredDefs, + instances: filteredInstances, + }; + } + + function normalizeConstraint(input, context) { + if (Array.isArray(input)) { + return parseEvents(input, null, context, true); // allowOpenRange=true + } + if (typeof input === 'object' && input) { // non-null object + return parseEvents([input], null, context, true); // allowOpenRange=true + } + if (input != null) { + return String(input); + } + return null; + } + + function parseClassNames(raw) { + if (Array.isArray(raw)) { + return raw; + } + if (typeof raw === 'string') { + return raw.split(/\s+/); + } + return []; + } + + // TODO: better called "EventSettings" or "EventConfig" + // TODO: move this file into structs + // TODO: separate constraint/overlap/allow, because selection uses only that, not other props + var EVENT_UI_REFINERS = { + display: String, + editable: Boolean, + startEditable: Boolean, + durationEditable: Boolean, + constraint: identity, + overlap: identity, + allow: identity, + className: parseClassNames, + classNames: parseClassNames, + color: String, + backgroundColor: String, + borderColor: String, + textColor: String, + }; + var EMPTY_EVENT_UI = { + display: null, + startEditable: null, + durationEditable: null, + constraints: [], + overlap: null, + allows: [], + backgroundColor: '', + borderColor: '', + textColor: '', + classNames: [], + }; + function createEventUi(refined, context) { + var constraint = normalizeConstraint(refined.constraint, context); + return { + display: refined.display || null, + startEditable: refined.startEditable != null ? refined.startEditable : refined.editable, + durationEditable: refined.durationEditable != null ? refined.durationEditable : refined.editable, + constraints: constraint != null ? [constraint] : [], + overlap: refined.overlap != null ? refined.overlap : null, + allows: refined.allow != null ? [refined.allow] : [], + backgroundColor: refined.backgroundColor || refined.color || '', + borderColor: refined.borderColor || refined.color || '', + textColor: refined.textColor || '', + classNames: (refined.className || []).concat(refined.classNames || []), // join singular and plural + }; + } + // TODO: prevent against problems with <2 args! + function combineEventUis(uis) { + return uis.reduce(combineTwoEventUis, EMPTY_EVENT_UI); + } + function combineTwoEventUis(item0, item1) { + return { + display: item1.display != null ? item1.display : item0.display, + startEditable: item1.startEditable != null ? item1.startEditable : item0.startEditable, + durationEditable: item1.durationEditable != null ? item1.durationEditable : item0.durationEditable, + constraints: item0.constraints.concat(item1.constraints), + overlap: typeof item1.overlap === 'boolean' ? item1.overlap : item0.overlap, + allows: item0.allows.concat(item1.allows), + backgroundColor: item1.backgroundColor || item0.backgroundColor, + borderColor: item1.borderColor || item0.borderColor, + textColor: item1.textColor || item0.textColor, + classNames: item0.classNames.concat(item1.classNames), + }; + } + + var EVENT_NON_DATE_REFINERS = { + id: String, + groupId: String, + title: String, + url: String, + }; + var EVENT_DATE_REFINERS = { + start: identity, + end: identity, + date: identity, + allDay: Boolean, + }; + var EVENT_REFINERS = __assign(__assign(__assign({}, EVENT_NON_DATE_REFINERS), EVENT_DATE_REFINERS), { extendedProps: identity }); + function parseEvent(raw, eventSource, context, allowOpenRange, refiners) { + if (refiners === void 0) { refiners = buildEventRefiners(context); } + var _a = refineEventDef(raw, context, refiners), refined = _a.refined, extra = _a.extra; + var defaultAllDay = computeIsDefaultAllDay(eventSource, context); + var recurringRes = parseRecurring(refined, defaultAllDay, context.dateEnv, context.pluginHooks.recurringTypes); + if (recurringRes) { + var def = parseEventDef(refined, extra, eventSource ? eventSource.sourceId : '', recurringRes.allDay, Boolean(recurringRes.duration), context); + def.recurringDef = { + typeId: recurringRes.typeId, + typeData: recurringRes.typeData, + duration: recurringRes.duration, + }; + return { def: def, instance: null }; + } + var singleRes = parseSingle(refined, defaultAllDay, context, allowOpenRange); + if (singleRes) { + var def = parseEventDef(refined, extra, eventSource ? eventSource.sourceId : '', singleRes.allDay, singleRes.hasEnd, context); + var instance = createEventInstance(def.defId, singleRes.range, singleRes.forcedStartTzo, singleRes.forcedEndTzo); + return { def: def, instance: instance }; + } + return null; + } + function refineEventDef(raw, context, refiners) { + if (refiners === void 0) { refiners = buildEventRefiners(context); } + return refineProps(raw, refiners); + } + function buildEventRefiners(context) { + return __assign(__assign(__assign({}, EVENT_UI_REFINERS), EVENT_REFINERS), context.pluginHooks.eventRefiners); + } + /* + Will NOT populate extendedProps with the leftover properties. + Will NOT populate date-related props. + */ + function parseEventDef(refined, extra, sourceId, allDay, hasEnd, context) { + var def = { + title: refined.title || '', + groupId: refined.groupId || '', + publicId: refined.id || '', + url: refined.url || '', + recurringDef: null, + defId: guid(), + sourceId: sourceId, + allDay: allDay, + hasEnd: hasEnd, + ui: createEventUi(refined, context), + extendedProps: __assign(__assign({}, (refined.extendedProps || {})), extra), + }; + for (var _i = 0, _a = context.pluginHooks.eventDefMemberAdders; _i < _a.length; _i++) { + var memberAdder = _a[_i]; + __assign(def, memberAdder(refined)); + } + // help out EventApi from having user modify props + Object.freeze(def.ui.classNames); + Object.freeze(def.extendedProps); + return def; + } + function parseSingle(refined, defaultAllDay, context, allowOpenRange) { + var allDay = refined.allDay; + var startMeta; + var startMarker = null; + var hasEnd = false; + var endMeta; + var endMarker = null; + var startInput = refined.start != null ? refined.start : refined.date; + startMeta = context.dateEnv.createMarkerMeta(startInput); + if (startMeta) { + startMarker = startMeta.marker; + } + else if (!allowOpenRange) { + return null; + } + if (refined.end != null) { + endMeta = context.dateEnv.createMarkerMeta(refined.end); + } + if (allDay == null) { + if (defaultAllDay != null) { + allDay = defaultAllDay; + } + else { + // fall back to the date props LAST + allDay = (!startMeta || startMeta.isTimeUnspecified) && + (!endMeta || endMeta.isTimeUnspecified); + } + } + if (allDay && startMarker) { + startMarker = startOfDay(startMarker); + } + if (endMeta) { + endMarker = endMeta.marker; + if (allDay) { + endMarker = startOfDay(endMarker); + } + if (startMarker && endMarker <= startMarker) { + endMarker = null; + } + } + if (endMarker) { + hasEnd = true; + } + else if (!allowOpenRange) { + hasEnd = context.options.forceEventDuration || false; + endMarker = context.dateEnv.add(startMarker, allDay ? + context.options.defaultAllDayEventDuration : + context.options.defaultTimedEventDuration); + } + return { + allDay: allDay, + hasEnd: hasEnd, + range: { start: startMarker, end: endMarker }, + forcedStartTzo: startMeta ? startMeta.forcedTzo : null, + forcedEndTzo: endMeta ? endMeta.forcedTzo : null, + }; + } + function computeIsDefaultAllDay(eventSource, context) { + var res = null; + if (eventSource) { + res = eventSource.defaultAllDay; + } + if (res == null) { + res = context.options.defaultAllDay; + } + return res; + } + + /* Date stuff that doesn't belong in datelib core + ----------------------------------------------------------------------------------------------------------------------*/ + // given a timed range, computes an all-day range that has the same exact duration, + // but whose start time is aligned with the start of the day. + function computeAlignedDayRange(timedRange) { + var dayCnt = Math.floor(diffDays(timedRange.start, timedRange.end)) || 1; + var start = startOfDay(timedRange.start); + var end = addDays(start, dayCnt); + return { start: start, end: end }; + } + // given a timed range, computes an all-day range based on how for the end date bleeds into the next day + // TODO: give nextDayThreshold a default arg + function computeVisibleDayRange(timedRange, nextDayThreshold) { + if (nextDayThreshold === void 0) { nextDayThreshold = createDuration(0); } + var startDay = null; + var endDay = null; + if (timedRange.end) { + endDay = startOfDay(timedRange.end); + var endTimeMS = timedRange.end.valueOf() - endDay.valueOf(); // # of milliseconds into `endDay` + // If the end time is actually inclusively part of the next day and is equal to or + // beyond the next day threshold, adjust the end to be the exclusive end of `endDay`. + // Otherwise, leaving it as inclusive will cause it to exclude `endDay`. + if (endTimeMS && endTimeMS >= asRoughMs(nextDayThreshold)) { + endDay = addDays(endDay, 1); + } + } + if (timedRange.start) { + startDay = startOfDay(timedRange.start); // the beginning of the day the range starts + // If end is within `startDay` but not past nextDayThreshold, assign the default duration of one day. + if (endDay && endDay <= startDay) { + endDay = addDays(startDay, 1); + } + } + return { start: startDay, end: endDay }; + } + // spans from one day into another? + function isMultiDayRange(range) { + var visibleRange = computeVisibleDayRange(range); + return diffDays(visibleRange.start, visibleRange.end) > 1; + } + function diffDates(date0, date1, dateEnv, largeUnit) { + if (largeUnit === 'year') { + return createDuration(dateEnv.diffWholeYears(date0, date1), 'year'); + } + if (largeUnit === 'month') { + return createDuration(dateEnv.diffWholeMonths(date0, date1), 'month'); + } + return diffDayAndTime(date0, date1); // returns a duration + } + + function parseRange(input, dateEnv) { + var start = null; + var end = null; + if (input.start) { + start = dateEnv.createMarker(input.start); + } + if (input.end) { + end = dateEnv.createMarker(input.end); + } + if (!start && !end) { + return null; + } + if (start && end && end < start) { + return null; + } + return { start: start, end: end }; + } + // SIDE-EFFECT: will mutate ranges. + // Will return a new array result. + function invertRanges(ranges, constraintRange) { + var invertedRanges = []; + var start = constraintRange.start; // the end of the previous range. the start of the new range + var i; + var dateRange; + // ranges need to be in order. required for our date-walking algorithm + ranges.sort(compareRanges); + for (i = 0; i < ranges.length; i += 1) { + dateRange = ranges[i]; + // add the span of time before the event (if there is any) + if (dateRange.start > start) { // compare millisecond time (skip any ambig logic) + invertedRanges.push({ start: start, end: dateRange.start }); + } + if (dateRange.end > start) { + start = dateRange.end; + } + } + // add the span of time after the last event (if there is any) + if (start < constraintRange.end) { // compare millisecond time (skip any ambig logic) + invertedRanges.push({ start: start, end: constraintRange.end }); + } + return invertedRanges; + } + function compareRanges(range0, range1) { + return range0.start.valueOf() - range1.start.valueOf(); // earlier ranges go first + } + function intersectRanges(range0, range1) { + var start = range0.start, end = range0.end; + var newRange = null; + if (range1.start !== null) { + if (start === null) { + start = range1.start; + } + else { + start = new Date(Math.max(start.valueOf(), range1.start.valueOf())); + } + } + if (range1.end != null) { + if (end === null) { + end = range1.end; + } + else { + end = new Date(Math.min(end.valueOf(), range1.end.valueOf())); + } + } + if (start === null || end === null || start < end) { + newRange = { start: start, end: end }; + } + return newRange; + } + function rangesEqual(range0, range1) { + return (range0.start === null ? null : range0.start.valueOf()) === (range1.start === null ? null : range1.start.valueOf()) && + (range0.end === null ? null : range0.end.valueOf()) === (range1.end === null ? null : range1.end.valueOf()); + } + function rangesIntersect(range0, range1) { + return (range0.end === null || range1.start === null || range0.end > range1.start) && + (range0.start === null || range1.end === null || range0.start < range1.end); + } + function rangeContainsRange(outerRange, innerRange) { + return (outerRange.start === null || (innerRange.start !== null && innerRange.start >= outerRange.start)) && + (outerRange.end === null || (innerRange.end !== null && innerRange.end <= outerRange.end)); + } + function rangeContainsMarker(range, date) { + return (range.start === null || date >= range.start) && + (range.end === null || date < range.end); + } + // If the given date is not within the given range, move it inside. + // (If it's past the end, make it one millisecond before the end). + function constrainMarkerToRange(date, range) { + if (range.start != null && date < range.start) { + return range.start; + } + if (range.end != null && date >= range.end) { + return new Date(range.end.valueOf() - 1); + } + return date; + } + + /* + Specifying nextDayThreshold signals that all-day ranges should be sliced. + */ + function sliceEventStore(eventStore, eventUiBases, framingRange, nextDayThreshold) { + var inverseBgByGroupId = {}; + var inverseBgByDefId = {}; + var defByGroupId = {}; + var bgRanges = []; + var fgRanges = []; + var eventUis = compileEventUis(eventStore.defs, eventUiBases); + for (var defId in eventStore.defs) { + var def = eventStore.defs[defId]; + var ui = eventUis[def.defId]; + if (ui.display === 'inverse-background') { + if (def.groupId) { + inverseBgByGroupId[def.groupId] = []; + if (!defByGroupId[def.groupId]) { + defByGroupId[def.groupId] = def; + } + } + else { + inverseBgByDefId[defId] = []; + } + } + } + for (var instanceId in eventStore.instances) { + var instance = eventStore.instances[instanceId]; + var def = eventStore.defs[instance.defId]; + var ui = eventUis[def.defId]; + var origRange = instance.range; + var normalRange = (!def.allDay && nextDayThreshold) ? + computeVisibleDayRange(origRange, nextDayThreshold) : + origRange; + var slicedRange = intersectRanges(normalRange, framingRange); + if (slicedRange) { + if (ui.display === 'inverse-background') { + if (def.groupId) { + inverseBgByGroupId[def.groupId].push(slicedRange); + } + else { + inverseBgByDefId[instance.defId].push(slicedRange); + } + } + else if (ui.display !== 'none') { + (ui.display === 'background' ? bgRanges : fgRanges).push({ + def: def, + ui: ui, + instance: instance, + range: slicedRange, + isStart: normalRange.start && normalRange.start.valueOf() === slicedRange.start.valueOf(), + isEnd: normalRange.end && normalRange.end.valueOf() === slicedRange.end.valueOf(), + }); + } + } + } + for (var groupId in inverseBgByGroupId) { // BY GROUP + var ranges = inverseBgByGroupId[groupId]; + var invertedRanges = invertRanges(ranges, framingRange); + for (var _i = 0, invertedRanges_1 = invertedRanges; _i < invertedRanges_1.length; _i++) { + var invertedRange = invertedRanges_1[_i]; + var def = defByGroupId[groupId]; + var ui = eventUis[def.defId]; + bgRanges.push({ + def: def, + ui: ui, + instance: null, + range: invertedRange, + isStart: false, + isEnd: false, + }); + } + } + for (var defId in inverseBgByDefId) { + var ranges = inverseBgByDefId[defId]; + var invertedRanges = invertRanges(ranges, framingRange); + for (var _a = 0, invertedRanges_2 = invertedRanges; _a < invertedRanges_2.length; _a++) { + var invertedRange = invertedRanges_2[_a]; + bgRanges.push({ + def: eventStore.defs[defId], + ui: eventUis[defId], + instance: null, + range: invertedRange, + isStart: false, + isEnd: false, + }); + } + } + return { bg: bgRanges, fg: fgRanges }; + } + function hasBgRendering(def) { + return def.ui.display === 'background' || def.ui.display === 'inverse-background'; + } + function setElSeg(el, seg) { + el.fcSeg = seg; + } + function getElSeg(el) { + return el.fcSeg || + el.parentNode.fcSeg || // for the harness + null; + } + // event ui computation + function compileEventUis(eventDefs, eventUiBases) { + return mapHash(eventDefs, function (eventDef) { return compileEventUi(eventDef, eventUiBases); }); + } + function compileEventUi(eventDef, eventUiBases) { + var uis = []; + if (eventUiBases['']) { + uis.push(eventUiBases['']); + } + if (eventUiBases[eventDef.defId]) { + uis.push(eventUiBases[eventDef.defId]); + } + uis.push(eventDef.ui); + return combineEventUis(uis); + } + function sortEventSegs(segs, eventOrderSpecs) { + var objs = segs.map(buildSegCompareObj); + objs.sort(function (obj0, obj1) { return compareByFieldSpecs(obj0, obj1, eventOrderSpecs); }); + return objs.map(function (c) { return c._seg; }); + } + // returns a object with all primitive props that can be compared + function buildSegCompareObj(seg) { + var eventRange = seg.eventRange; + var eventDef = eventRange.def; + var range = eventRange.instance ? eventRange.instance.range : eventRange.range; + var start = range.start ? range.start.valueOf() : 0; // TODO: better support for open-range events + var end = range.end ? range.end.valueOf() : 0; // " + return __assign(__assign(__assign({}, eventDef.extendedProps), eventDef), { id: eventDef.publicId, start: start, + end: end, duration: end - start, allDay: Number(eventDef.allDay), _seg: seg }); + } + function computeSegDraggable(seg, context) { + var pluginHooks = context.pluginHooks; + var transformers = pluginHooks.isDraggableTransformers; + var _a = seg.eventRange, def = _a.def, ui = _a.ui; + var val = ui.startEditable; + for (var _i = 0, transformers_1 = transformers; _i < transformers_1.length; _i++) { + var transformer = transformers_1[_i]; + val = transformer(val, def, ui, context); + } + return val; + } + function computeSegStartResizable(seg, context) { + return seg.isStart && seg.eventRange.ui.durationEditable && context.options.eventResizableFromStart; + } + function computeSegEndResizable(seg, context) { + return seg.isEnd && seg.eventRange.ui.durationEditable; + } + function buildSegTimeText(seg, timeFormat, context, defaultDisplayEventTime, // defaults to true + defaultDisplayEventEnd, // defaults to true + startOverride, endOverride) { + var dateEnv = context.dateEnv, options = context.options; + var displayEventTime = options.displayEventTime, displayEventEnd = options.displayEventEnd; + var eventDef = seg.eventRange.def; + var eventInstance = seg.eventRange.instance; + if (displayEventTime == null) { + displayEventTime = defaultDisplayEventTime !== false; + } + if (displayEventEnd == null) { + displayEventEnd = defaultDisplayEventEnd !== false; + } + var wholeEventStart = eventInstance.range.start; + var wholeEventEnd = eventInstance.range.end; + var segStart = startOverride || seg.start || seg.eventRange.range.start; + var segEnd = endOverride || seg.end || seg.eventRange.range.end; + var isStartDay = startOfDay(wholeEventStart).valueOf() === startOfDay(segStart).valueOf(); + var isEndDay = startOfDay(addMs(wholeEventEnd, -1)).valueOf() === startOfDay(addMs(segEnd, -1)).valueOf(); + if (displayEventTime && !eventDef.allDay && (isStartDay || isEndDay)) { + segStart = isStartDay ? wholeEventStart : segStart; + segEnd = isEndDay ? wholeEventEnd : segEnd; + if (displayEventEnd && eventDef.hasEnd) { + return dateEnv.formatRange(segStart, segEnd, timeFormat, { + forcedStartTzo: startOverride ? null : eventInstance.forcedStartTzo, + forcedEndTzo: endOverride ? null : eventInstance.forcedEndTzo, + }); + } + return dateEnv.format(segStart, timeFormat, { + forcedTzo: startOverride ? null : eventInstance.forcedStartTzo, // nooooo, same + }); + } + return ''; + } + function getSegMeta(seg, todayRange, nowDate) { + var segRange = seg.eventRange.range; + return { + isPast: segRange.end < (nowDate || todayRange.start), + isFuture: segRange.start >= (nowDate || todayRange.end), + isToday: todayRange && rangeContainsMarker(todayRange, segRange.start), + }; + } + function getEventClassNames(props) { + var classNames = ['fc-event']; + if (props.isMirror) { + classNames.push('fc-event-mirror'); + } + if (props.isDraggable) { + classNames.push('fc-event-draggable'); + } + if (props.isStartResizable || props.isEndResizable) { + classNames.push('fc-event-resizable'); + } + if (props.isDragging) { + classNames.push('fc-event-dragging'); + } + if (props.isResizing) { + classNames.push('fc-event-resizing'); + } + if (props.isSelected) { + classNames.push('fc-event-selected'); + } + if (props.isStart) { + classNames.push('fc-event-start'); + } + if (props.isEnd) { + classNames.push('fc-event-end'); + } + if (props.isPast) { + classNames.push('fc-event-past'); + } + if (props.isToday) { + classNames.push('fc-event-today'); + } + if (props.isFuture) { + classNames.push('fc-event-future'); + } + return classNames; + } + function buildEventRangeKey(eventRange) { + return eventRange.instance + ? eventRange.instance.instanceId + : eventRange.def.defId + ":" + eventRange.range.start.toISOString(); + // inverse-background events don't have specific instances. TODO: better solution + } + + var STANDARD_PROPS = { + start: identity, + end: identity, + allDay: Boolean, + }; + function parseDateSpan(raw, dateEnv, defaultDuration) { + var span = parseOpenDateSpan(raw, dateEnv); + var range = span.range; + if (!range.start) { + return null; + } + if (!range.end) { + if (defaultDuration == null) { + return null; + } + range.end = dateEnv.add(range.start, defaultDuration); + } + return span; + } + /* + TODO: somehow combine with parseRange? + Will return null if the start/end props were present but parsed invalidly. + */ + function parseOpenDateSpan(raw, dateEnv) { + var _a = refineProps(raw, STANDARD_PROPS), standardProps = _a.refined, extra = _a.extra; + var startMeta = standardProps.start ? dateEnv.createMarkerMeta(standardProps.start) : null; + var endMeta = standardProps.end ? dateEnv.createMarkerMeta(standardProps.end) : null; + var allDay = standardProps.allDay; + if (allDay == null) { + allDay = (startMeta && startMeta.isTimeUnspecified) && + (!endMeta || endMeta.isTimeUnspecified); + } + return __assign({ range: { + start: startMeta ? startMeta.marker : null, + end: endMeta ? endMeta.marker : null, + }, allDay: allDay }, extra); + } + function isDateSpansEqual(span0, span1) { + return rangesEqual(span0.range, span1.range) && + span0.allDay === span1.allDay && + isSpanPropsEqual(span0, span1); + } + // the NON-DATE-RELATED props + function isSpanPropsEqual(span0, span1) { + for (var propName in span1) { + if (propName !== 'range' && propName !== 'allDay') { + if (span0[propName] !== span1[propName]) { + return false; + } + } + } + // are there any props that span0 has that span1 DOESN'T have? + // both have range/allDay, so no need to special-case. + for (var propName in span0) { + if (!(propName in span1)) { + return false; + } + } + return true; + } + function buildDateSpanApi(span, dateEnv) { + return __assign(__assign({}, buildRangeApi(span.range, dateEnv, span.allDay)), { allDay: span.allDay }); + } + function buildRangeApiWithTimeZone(range, dateEnv, omitTime) { + return __assign(__assign({}, buildRangeApi(range, dateEnv, omitTime)), { timeZone: dateEnv.timeZone }); + } + function buildRangeApi(range, dateEnv, omitTime) { + return { + start: dateEnv.toDate(range.start), + end: dateEnv.toDate(range.end), + startStr: dateEnv.formatIso(range.start, { omitTime: omitTime }), + endStr: dateEnv.formatIso(range.end, { omitTime: omitTime }), + }; + } + function fabricateEventRange(dateSpan, eventUiBases, context) { + var res = refineEventDef({ editable: false }, context); + var def = parseEventDef(res.refined, res.extra, '', // sourceId + dateSpan.allDay, true, // hasEnd + context); + return { + def: def, + ui: compileEventUi(def, eventUiBases), + instance: createEventInstance(def.defId, dateSpan.range), + range: dateSpan.range, + isStart: true, + isEnd: true, + }; + } + + function triggerDateSelect(selection, pev, context) { + context.emitter.trigger('select', __assign(__assign({}, buildDateSpanApiWithContext(selection, context)), { jsEvent: pev ? pev.origEvent : null, view: context.viewApi || context.calendarApi.view })); + } + function triggerDateUnselect(pev, context) { + context.emitter.trigger('unselect', { + jsEvent: pev ? pev.origEvent : null, + view: context.viewApi || context.calendarApi.view, + }); + } + function buildDateSpanApiWithContext(dateSpan, context) { + var props = {}; + for (var _i = 0, _a = context.pluginHooks.dateSpanTransforms; _i < _a.length; _i++) { + var transform = _a[_i]; + __assign(props, transform(dateSpan, context)); + } + __assign(props, buildDateSpanApi(dateSpan, context.dateEnv)); + return props; + } + // Given an event's allDay status and start date, return what its fallback end date should be. + // TODO: rename to computeDefaultEventEnd + function getDefaultEventEnd(allDay, marker, context) { + var dateEnv = context.dateEnv, options = context.options; + var end = marker; + if (allDay) { + end = startOfDay(end); + end = dateEnv.add(end, options.defaultAllDayEventDuration); + } + else { + end = dateEnv.add(end, options.defaultTimedEventDuration); + } + return end; + } + + // applies the mutation to ALL defs/instances within the event store + function applyMutationToEventStore(eventStore, eventConfigBase, mutation, context) { + var eventConfigs = compileEventUis(eventStore.defs, eventConfigBase); + var dest = createEmptyEventStore(); + for (var defId in eventStore.defs) { + var def = eventStore.defs[defId]; + dest.defs[defId] = applyMutationToEventDef(def, eventConfigs[defId], mutation, context); + } + for (var instanceId in eventStore.instances) { + var instance = eventStore.instances[instanceId]; + var def = dest.defs[instance.defId]; // important to grab the newly modified def + dest.instances[instanceId] = applyMutationToEventInstance(instance, def, eventConfigs[instance.defId], mutation, context); + } + return dest; + } + function applyMutationToEventDef(eventDef, eventConfig, mutation, context) { + var standardProps = mutation.standardProps || {}; + // if hasEnd has not been specified, guess a good value based on deltas. + // if duration will change, there's no way the default duration will persist, + // and thus, we need to mark the event as having a real end + if (standardProps.hasEnd == null && + eventConfig.durationEditable && + (mutation.startDelta || mutation.endDelta)) { + standardProps.hasEnd = true; // TODO: is this mutation okay? + } + var copy = __assign(__assign(__assign({}, eventDef), standardProps), { ui: __assign(__assign({}, eventDef.ui), standardProps.ui) }); + if (mutation.extendedProps) { + copy.extendedProps = __assign(__assign({}, copy.extendedProps), mutation.extendedProps); + } + for (var _i = 0, _a = context.pluginHooks.eventDefMutationAppliers; _i < _a.length; _i++) { + var applier = _a[_i]; + applier(copy, mutation, context); + } + if (!copy.hasEnd && context.options.forceEventDuration) { + copy.hasEnd = true; + } + return copy; + } + function applyMutationToEventInstance(eventInstance, eventDef, // must first be modified by applyMutationToEventDef + eventConfig, mutation, context) { + var dateEnv = context.dateEnv; + var forceAllDay = mutation.standardProps && mutation.standardProps.allDay === true; + var clearEnd = mutation.standardProps && mutation.standardProps.hasEnd === false; + var copy = __assign({}, eventInstance); + if (forceAllDay) { + copy.range = computeAlignedDayRange(copy.range); + } + if (mutation.datesDelta && eventConfig.startEditable) { + copy.range = { + start: dateEnv.add(copy.range.start, mutation.datesDelta), + end: dateEnv.add(copy.range.end, mutation.datesDelta), + }; + } + if (mutation.startDelta && eventConfig.durationEditable) { + copy.range = { + start: dateEnv.add(copy.range.start, mutation.startDelta), + end: copy.range.end, + }; + } + if (mutation.endDelta && eventConfig.durationEditable) { + copy.range = { + start: copy.range.start, + end: dateEnv.add(copy.range.end, mutation.endDelta), + }; + } + if (clearEnd) { + copy.range = { + start: copy.range.start, + end: getDefaultEventEnd(eventDef.allDay, copy.range.start, context), + }; + } + // in case event was all-day but the supplied deltas were not + // better util for this? + if (eventDef.allDay) { + copy.range = { + start: startOfDay(copy.range.start), + end: startOfDay(copy.range.end), + }; + } + // handle invalid durations + if (copy.range.end < copy.range.start) { + copy.range.end = getDefaultEventEnd(eventDef.allDay, copy.range.start, context); + } + return copy; + } + + // no public types yet. when there are, export from: + // import {} from './api-type-deps' + var ViewApi = /** @class */ (function () { + function ViewApi(type, getCurrentData, dateEnv) { + this.type = type; + this.getCurrentData = getCurrentData; + this.dateEnv = dateEnv; + } + Object.defineProperty(ViewApi.prototype, "calendar", { + get: function () { + return this.getCurrentData().calendarApi; + }, + enumerable: false, + configurable: true + }); + Object.defineProperty(ViewApi.prototype, "title", { + get: function () { + return this.getCurrentData().viewTitle; + }, + enumerable: false, + configurable: true + }); + Object.defineProperty(ViewApi.prototype, "activeStart", { + get: function () { + return this.dateEnv.toDate(this.getCurrentData().dateProfile.activeRange.start); + }, + enumerable: false, + configurable: true + }); + Object.defineProperty(ViewApi.prototype, "activeEnd", { + get: function () { + return this.dateEnv.toDate(this.getCurrentData().dateProfile.activeRange.end); + }, + enumerable: false, + configurable: true + }); + Object.defineProperty(ViewApi.prototype, "currentStart", { + get: function () { + return this.dateEnv.toDate(this.getCurrentData().dateProfile.currentRange.start); + }, + enumerable: false, + configurable: true + }); + Object.defineProperty(ViewApi.prototype, "currentEnd", { + get: function () { + return this.dateEnv.toDate(this.getCurrentData().dateProfile.currentRange.end); + }, + enumerable: false, + configurable: true + }); + ViewApi.prototype.getOption = function (name) { + return this.getCurrentData().options[name]; // are the view-specific options + }; + return ViewApi; + }()); + + var EVENT_SOURCE_REFINERS$1 = { + id: String, + defaultAllDay: Boolean, + url: String, + format: String, + events: identity, + eventDataTransform: identity, + // for any network-related sources + success: identity, + failure: identity, + }; + function parseEventSource(raw, context, refiners) { + if (refiners === void 0) { refiners = buildEventSourceRefiners(context); } + var rawObj; + if (typeof raw === 'string') { + rawObj = { url: raw }; + } + else if (typeof raw === 'function' || Array.isArray(raw)) { + rawObj = { events: raw }; + } + else if (typeof raw === 'object' && raw) { // not null + rawObj = raw; + } + if (rawObj) { + var _a = refineProps(rawObj, refiners), refined = _a.refined, extra = _a.extra; + var metaRes = buildEventSourceMeta(refined, context); + if (metaRes) { + return { + _raw: raw, + isFetching: false, + latestFetchId: '', + fetchRange: null, + defaultAllDay: refined.defaultAllDay, + eventDataTransform: refined.eventDataTransform, + success: refined.success, + failure: refined.failure, + publicId: refined.id || '', + sourceId: guid(), + sourceDefId: metaRes.sourceDefId, + meta: metaRes.meta, + ui: createEventUi(refined, context), + extendedProps: extra, + }; + } + } + return null; + } + function buildEventSourceRefiners(context) { + return __assign(__assign(__assign({}, EVENT_UI_REFINERS), EVENT_SOURCE_REFINERS$1), context.pluginHooks.eventSourceRefiners); + } + function buildEventSourceMeta(raw, context) { + var defs = context.pluginHooks.eventSourceDefs; + for (var i = defs.length - 1; i >= 0; i -= 1) { // later-added plugins take precedence + var def = defs[i]; + var meta = def.parseMeta(raw); + if (meta) { + return { sourceDefId: i, meta: meta }; + } + } + return null; + } + + function reduceCurrentDate(currentDate, action) { + switch (action.type) { + case 'CHANGE_DATE': + return action.dateMarker; + default: + return currentDate; + } + } + function getInitialDate(options, dateEnv) { + var initialDateInput = options.initialDate; + // compute the initial ambig-timezone date + if (initialDateInput != null) { + return dateEnv.createMarker(initialDateInput); + } + return getNow(options.now, dateEnv); // getNow already returns unzoned + } + function getNow(nowInput, dateEnv) { + if (typeof nowInput === 'function') { + nowInput = nowInput(); + } + if (nowInput == null) { + return dateEnv.createNowMarker(); + } + return dateEnv.createMarker(nowInput); + } + + var CalendarApi = /** @class */ (function () { + function CalendarApi() { + } + CalendarApi.prototype.getCurrentData = function () { + return this.currentDataManager.getCurrentData(); + }; + CalendarApi.prototype.dispatch = function (action) { + return this.currentDataManager.dispatch(action); + }; + Object.defineProperty(CalendarApi.prototype, "view", { + get: function () { return this.getCurrentData().viewApi; } // for public API + , + enumerable: false, + configurable: true + }); + CalendarApi.prototype.batchRendering = function (callback) { + callback(); + }; + CalendarApi.prototype.updateSize = function () { + this.trigger('_resize', true); + }; + // Options + // ----------------------------------------------------------------------------------------------------------------- + CalendarApi.prototype.setOption = function (name, val) { + this.dispatch({ + type: 'SET_OPTION', + optionName: name, + rawOptionValue: val, + }); + }; + CalendarApi.prototype.getOption = function (name) { + return this.currentDataManager.currentCalendarOptionsInput[name]; + }; + CalendarApi.prototype.getAvailableLocaleCodes = function () { + return Object.keys(this.getCurrentData().availableRawLocales); + }; + // Trigger + // ----------------------------------------------------------------------------------------------------------------- + CalendarApi.prototype.on = function (handlerName, handler) { + var currentDataManager = this.currentDataManager; + if (currentDataManager.currentCalendarOptionsRefiners[handlerName]) { + currentDataManager.emitter.on(handlerName, handler); + } + else { + console.warn("Unknown listener name '" + handlerName + "'"); + } + }; + CalendarApi.prototype.off = function (handlerName, handler) { + this.currentDataManager.emitter.off(handlerName, handler); + }; + // not meant for public use + CalendarApi.prototype.trigger = function (handlerName) { + var _a; + var args = []; + for (var _i = 1; _i < arguments.length; _i++) { + args[_i - 1] = arguments[_i]; + } + (_a = this.currentDataManager.emitter).trigger.apply(_a, __spreadArray([handlerName], args)); + }; + // View + // ----------------------------------------------------------------------------------------------------------------- + CalendarApi.prototype.changeView = function (viewType, dateOrRange) { + var _this = this; + this.batchRendering(function () { + _this.unselect(); + if (dateOrRange) { + if (dateOrRange.start && dateOrRange.end) { // a range + _this.dispatch({ + type: 'CHANGE_VIEW_TYPE', + viewType: viewType, + }); + _this.dispatch({ + type: 'SET_OPTION', + optionName: 'visibleRange', + rawOptionValue: dateOrRange, + }); + } + else { + var dateEnv = _this.getCurrentData().dateEnv; + _this.dispatch({ + type: 'CHANGE_VIEW_TYPE', + viewType: viewType, + dateMarker: dateEnv.createMarker(dateOrRange), + }); + } + } + else { + _this.dispatch({ + type: 'CHANGE_VIEW_TYPE', + viewType: viewType, + }); + } + }); + }; + // Forces navigation to a view for the given date. + // `viewType` can be a specific view name or a generic one like "week" or "day". + // needs to change + CalendarApi.prototype.zoomTo = function (dateMarker, viewType) { + var state = this.getCurrentData(); + var spec; + viewType = viewType || 'day'; // day is default zoom + spec = state.viewSpecs[viewType] || this.getUnitViewSpec(viewType); + this.unselect(); + if (spec) { + this.dispatch({ + type: 'CHANGE_VIEW_TYPE', + viewType: spec.type, + dateMarker: dateMarker, + }); + } + else { + this.dispatch({ + type: 'CHANGE_DATE', + dateMarker: dateMarker, + }); + } + }; + // Given a duration singular unit, like "week" or "day", finds a matching view spec. + // Preference is given to views that have corresponding buttons. + CalendarApi.prototype.getUnitViewSpec = function (unit) { + var _a = this.getCurrentData(), viewSpecs = _a.viewSpecs, toolbarConfig = _a.toolbarConfig; + var viewTypes = [].concat(toolbarConfig.viewsWithButtons); + var i; + var spec; + for (var viewType in viewSpecs) { + viewTypes.push(viewType); + } + for (i = 0; i < viewTypes.length; i += 1) { + spec = viewSpecs[viewTypes[i]]; + if (spec) { + if (spec.singleUnit === unit) { + return spec; + } + } + } + return null; + }; + // Current Date + // ----------------------------------------------------------------------------------------------------------------- + CalendarApi.prototype.prev = function () { + this.unselect(); + this.dispatch({ type: 'PREV' }); + }; + CalendarApi.prototype.next = function () { + this.unselect(); + this.dispatch({ type: 'NEXT' }); + }; + CalendarApi.prototype.prevYear = function () { + var state = this.getCurrentData(); + this.unselect(); + this.dispatch({ + type: 'CHANGE_DATE', + dateMarker: state.dateEnv.addYears(state.currentDate, -1), + }); + }; + CalendarApi.prototype.nextYear = function () { + var state = this.getCurrentData(); + this.unselect(); + this.dispatch({ + type: 'CHANGE_DATE', + dateMarker: state.dateEnv.addYears(state.currentDate, 1), + }); + }; + CalendarApi.prototype.today = function () { + var state = this.getCurrentData(); + this.unselect(); + this.dispatch({ + type: 'CHANGE_DATE', + dateMarker: getNow(state.calendarOptions.now, state.dateEnv), + }); + }; + CalendarApi.prototype.gotoDate = function (zonedDateInput) { + var state = this.getCurrentData(); + this.unselect(); + this.dispatch({ + type: 'CHANGE_DATE', + dateMarker: state.dateEnv.createMarker(zonedDateInput), + }); + }; + CalendarApi.prototype.incrementDate = function (deltaInput) { + var state = this.getCurrentData(); + var delta = createDuration(deltaInput); + if (delta) { // else, warn about invalid input? + this.unselect(); + this.dispatch({ + type: 'CHANGE_DATE', + dateMarker: state.dateEnv.add(state.currentDate, delta), + }); + } + }; + // for external API + CalendarApi.prototype.getDate = function () { + var state = this.getCurrentData(); + return state.dateEnv.toDate(state.currentDate); + }; + // Date Formatting Utils + // ----------------------------------------------------------------------------------------------------------------- + CalendarApi.prototype.formatDate = function (d, formatter) { + var dateEnv = this.getCurrentData().dateEnv; + return dateEnv.format(dateEnv.createMarker(d), createFormatter(formatter)); + }; + // `settings` is for formatter AND isEndExclusive + CalendarApi.prototype.formatRange = function (d0, d1, settings) { + var dateEnv = this.getCurrentData().dateEnv; + return dateEnv.formatRange(dateEnv.createMarker(d0), dateEnv.createMarker(d1), createFormatter(settings), settings); + }; + CalendarApi.prototype.formatIso = function (d, omitTime) { + var dateEnv = this.getCurrentData().dateEnv; + return dateEnv.formatIso(dateEnv.createMarker(d), { omitTime: omitTime }); + }; + // Date Selection / Event Selection / DayClick + // ----------------------------------------------------------------------------------------------------------------- + // this public method receives start/end dates in any format, with any timezone + // NOTE: args were changed from v3 + CalendarApi.prototype.select = function (dateOrObj, endDate) { + var selectionInput; + if (endDate == null) { + if (dateOrObj.start != null) { + selectionInput = dateOrObj; + } + else { + selectionInput = { + start: dateOrObj, + end: null, + }; + } + } + else { + selectionInput = { + start: dateOrObj, + end: endDate, + }; + } + var state = this.getCurrentData(); + var selection = parseDateSpan(selectionInput, state.dateEnv, createDuration({ days: 1 })); + if (selection) { // throw parse error otherwise? + this.dispatch({ type: 'SELECT_DATES', selection: selection }); + triggerDateSelect(selection, null, state); + } + }; + // public method + CalendarApi.prototype.unselect = function (pev) { + var state = this.getCurrentData(); + if (state.dateSelection) { + this.dispatch({ type: 'UNSELECT_DATES' }); + triggerDateUnselect(pev, state); + } + }; + // Public Events API + // ----------------------------------------------------------------------------------------------------------------- + CalendarApi.prototype.addEvent = function (eventInput, sourceInput) { + if (eventInput instanceof EventApi) { + var def = eventInput._def; + var instance = eventInput._instance; + var currentData = this.getCurrentData(); + // not already present? don't want to add an old snapshot + if (!currentData.eventStore.defs[def.defId]) { + this.dispatch({ + type: 'ADD_EVENTS', + eventStore: eventTupleToStore({ def: def, instance: instance }), // TODO: better util for two args? + }); + this.triggerEventAdd(eventInput); + } + return eventInput; + } + var state = this.getCurrentData(); + var eventSource; + if (sourceInput instanceof EventSourceApi) { + eventSource = sourceInput.internalEventSource; + } + else if (typeof sourceInput === 'boolean') { + if (sourceInput) { // true. part of the first event source + eventSource = hashValuesToArray(state.eventSources)[0]; + } + } + else if (sourceInput != null) { // an ID. accepts a number too + var sourceApi = this.getEventSourceById(sourceInput); // TODO: use an internal function + if (!sourceApi) { + console.warn("Could not find an event source with ID \"" + sourceInput + "\""); // TODO: test + return null; + } + eventSource = sourceApi.internalEventSource; + } + var tuple = parseEvent(eventInput, eventSource, state, false); + if (tuple) { + var newEventApi = new EventApi(state, tuple.def, tuple.def.recurringDef ? null : tuple.instance); + this.dispatch({ + type: 'ADD_EVENTS', + eventStore: eventTupleToStore(tuple), + }); + this.triggerEventAdd(newEventApi); + return newEventApi; + } + return null; + }; + CalendarApi.prototype.triggerEventAdd = function (eventApi) { + var _this = this; + var emitter = this.getCurrentData().emitter; + emitter.trigger('eventAdd', { + event: eventApi, + relatedEvents: [], + revert: function () { + _this.dispatch({ + type: 'REMOVE_EVENTS', + eventStore: eventApiToStore(eventApi), + }); + }, + }); + }; + // TODO: optimize + CalendarApi.prototype.getEventById = function (id) { + var state = this.getCurrentData(); + var _a = state.eventStore, defs = _a.defs, instances = _a.instances; + id = String(id); + for (var defId in defs) { + var def = defs[defId]; + if (def.publicId === id) { + if (def.recurringDef) { + return new EventApi(state, def, null); + } + for (var instanceId in instances) { + var instance = instances[instanceId]; + if (instance.defId === def.defId) { + return new EventApi(state, def, instance); + } + } + } + } + return null; + }; + CalendarApi.prototype.getEvents = function () { + var currentData = this.getCurrentData(); + return buildEventApis(currentData.eventStore, currentData); + }; + CalendarApi.prototype.removeAllEvents = function () { + this.dispatch({ type: 'REMOVE_ALL_EVENTS' }); + }; + // Public Event Sources API + // ----------------------------------------------------------------------------------------------------------------- + CalendarApi.prototype.getEventSources = function () { + var state = this.getCurrentData(); + var sourceHash = state.eventSources; + var sourceApis = []; + for (var internalId in sourceHash) { + sourceApis.push(new EventSourceApi(state, sourceHash[internalId])); + } + return sourceApis; + }; + CalendarApi.prototype.getEventSourceById = function (id) { + var state = this.getCurrentData(); + var sourceHash = state.eventSources; + id = String(id); + for (var sourceId in sourceHash) { + if (sourceHash[sourceId].publicId === id) { + return new EventSourceApi(state, sourceHash[sourceId]); + } + } + return null; + }; + CalendarApi.prototype.addEventSource = function (sourceInput) { + var state = this.getCurrentData(); + if (sourceInput instanceof EventSourceApi) { + // not already present? don't want to add an old snapshot + if (!state.eventSources[sourceInput.internalEventSource.sourceId]) { + this.dispatch({ + type: 'ADD_EVENT_SOURCES', + sources: [sourceInput.internalEventSource], + }); + } + return sourceInput; + } + var eventSource = parseEventSource(sourceInput, state); + if (eventSource) { // TODO: error otherwise? + this.dispatch({ type: 'ADD_EVENT_SOURCES', sources: [eventSource] }); + return new EventSourceApi(state, eventSource); + } + return null; + }; + CalendarApi.prototype.removeAllEventSources = function () { + this.dispatch({ type: 'REMOVE_ALL_EVENT_SOURCES' }); + }; + CalendarApi.prototype.refetchEvents = function () { + this.dispatch({ type: 'FETCH_EVENT_SOURCES', isRefetch: true }); + }; + // Scroll + // ----------------------------------------------------------------------------------------------------------------- + CalendarApi.prototype.scrollToTime = function (timeInput) { + var time = createDuration(timeInput); + if (time) { + this.trigger('_scrollRequest', { time: time }); + } + }; + return CalendarApi; + }()); + + var EventApi = /** @class */ (function () { + // instance will be null if expressing a recurring event that has no current instances, + // OR if trying to validate an incoming external event that has no dates assigned + function EventApi(context, def, instance) { + this._context = context; + this._def = def; + this._instance = instance || null; + } + /* + TODO: make event struct more responsible for this + */ + EventApi.prototype.setProp = function (name, val) { + var _a, _b; + if (name in EVENT_DATE_REFINERS) { + console.warn('Could not set date-related prop \'name\'. Use one of the date-related methods instead.'); + // TODO: make proper aliasing system? + } + else if (name === 'id') { + val = EVENT_NON_DATE_REFINERS[name](val); + this.mutate({ + standardProps: { publicId: val }, // hardcoded internal name + }); + } + else if (name in EVENT_NON_DATE_REFINERS) { + val = EVENT_NON_DATE_REFINERS[name](val); + this.mutate({ + standardProps: (_a = {}, _a[name] = val, _a), + }); + } + else if (name in EVENT_UI_REFINERS) { + var ui = EVENT_UI_REFINERS[name](val); + if (name === 'color') { + ui = { backgroundColor: val, borderColor: val }; + } + else if (name === 'editable') { + ui = { startEditable: val, durationEditable: val }; + } + else { + ui = (_b = {}, _b[name] = val, _b); + } + this.mutate({ + standardProps: { ui: ui }, + }); + } + else { + console.warn("Could not set prop '" + name + "'. Use setExtendedProp instead."); + } + }; + EventApi.prototype.setExtendedProp = function (name, val) { + var _a; + this.mutate({ + extendedProps: (_a = {}, _a[name] = val, _a), + }); + }; + EventApi.prototype.setStart = function (startInput, options) { + if (options === void 0) { options = {}; } + var dateEnv = this._context.dateEnv; + var start = dateEnv.createMarker(startInput); + if (start && this._instance) { // TODO: warning if parsed bad + var instanceRange = this._instance.range; + var startDelta = diffDates(instanceRange.start, start, dateEnv, options.granularity); // what if parsed bad!? + if (options.maintainDuration) { + this.mutate({ datesDelta: startDelta }); + } + else { + this.mutate({ startDelta: startDelta }); + } + } + }; + EventApi.prototype.setEnd = function (endInput, options) { + if (options === void 0) { options = {}; } + var dateEnv = this._context.dateEnv; + var end; + if (endInput != null) { + end = dateEnv.createMarker(endInput); + if (!end) { + return; // TODO: warning if parsed bad + } + } + if (this._instance) { + if (end) { + var endDelta = diffDates(this._instance.range.end, end, dateEnv, options.granularity); + this.mutate({ endDelta: endDelta }); + } + else { + this.mutate({ standardProps: { hasEnd: false } }); + } + } + }; + EventApi.prototype.setDates = function (startInput, endInput, options) { + if (options === void 0) { options = {}; } + var dateEnv = this._context.dateEnv; + var standardProps = { allDay: options.allDay }; + var start = dateEnv.createMarker(startInput); + var end; + if (!start) { + return; // TODO: warning if parsed bad + } + if (endInput != null) { + end = dateEnv.createMarker(endInput); + if (!end) { // TODO: warning if parsed bad + return; + } + } + if (this._instance) { + var instanceRange = this._instance.range; + // when computing the diff for an event being converted to all-day, + // compute diff off of the all-day values the way event-mutation does. + if (options.allDay === true) { + instanceRange = computeAlignedDayRange(instanceRange); + } + var startDelta = diffDates(instanceRange.start, start, dateEnv, options.granularity); + if (end) { + var endDelta = diffDates(instanceRange.end, end, dateEnv, options.granularity); + if (durationsEqual(startDelta, endDelta)) { + this.mutate({ datesDelta: startDelta, standardProps: standardProps }); + } + else { + this.mutate({ startDelta: startDelta, endDelta: endDelta, standardProps: standardProps }); + } + } + else { // means "clear the end" + standardProps.hasEnd = false; + this.mutate({ datesDelta: startDelta, standardProps: standardProps }); + } + } + }; + EventApi.prototype.moveStart = function (deltaInput) { + var delta = createDuration(deltaInput); + if (delta) { // TODO: warning if parsed bad + this.mutate({ startDelta: delta }); + } + }; + EventApi.prototype.moveEnd = function (deltaInput) { + var delta = createDuration(deltaInput); + if (delta) { // TODO: warning if parsed bad + this.mutate({ endDelta: delta }); + } + }; + EventApi.prototype.moveDates = function (deltaInput) { + var delta = createDuration(deltaInput); + if (delta) { // TODO: warning if parsed bad + this.mutate({ datesDelta: delta }); + } + }; + EventApi.prototype.setAllDay = function (allDay, options) { + if (options === void 0) { options = {}; } + var standardProps = { allDay: allDay }; + var maintainDuration = options.maintainDuration; + if (maintainDuration == null) { + maintainDuration = this._context.options.allDayMaintainDuration; + } + if (this._def.allDay !== allDay) { + standardProps.hasEnd = maintainDuration; + } + this.mutate({ standardProps: standardProps }); + }; + EventApi.prototype.formatRange = function (formatInput) { + var dateEnv = this._context.dateEnv; + var instance = this._instance; + var formatter = createFormatter(formatInput); + if (this._def.hasEnd) { + return dateEnv.formatRange(instance.range.start, instance.range.end, formatter, { + forcedStartTzo: instance.forcedStartTzo, + forcedEndTzo: instance.forcedEndTzo, + }); + } + return dateEnv.format(instance.range.start, formatter, { + forcedTzo: instance.forcedStartTzo, + }); + }; + EventApi.prototype.mutate = function (mutation) { + var instance = this._instance; + if (instance) { + var def = this._def; + var context_1 = this._context; + var eventStore_1 = context_1.getCurrentData().eventStore; + var relevantEvents = getRelevantEvents(eventStore_1, instance.instanceId); + var eventConfigBase = { + '': { + display: '', + startEditable: true, + durationEditable: true, + constraints: [], + overlap: null, + allows: [], + backgroundColor: '', + borderColor: '', + textColor: '', + classNames: [], + }, + }; + relevantEvents = applyMutationToEventStore(relevantEvents, eventConfigBase, mutation, context_1); + var oldEvent = new EventApi(context_1, def, instance); // snapshot + this._def = relevantEvents.defs[def.defId]; + this._instance = relevantEvents.instances[instance.instanceId]; + context_1.dispatch({ + type: 'MERGE_EVENTS', + eventStore: relevantEvents, + }); + context_1.emitter.trigger('eventChange', { + oldEvent: oldEvent, + event: this, + relatedEvents: buildEventApis(relevantEvents, context_1, instance), + revert: function () { + context_1.dispatch({ + type: 'RESET_EVENTS', + eventStore: eventStore_1, + }); + }, + }); + } + }; + EventApi.prototype.remove = function () { + var context = this._context; + var asStore = eventApiToStore(this); + context.dispatch({ + type: 'REMOVE_EVENTS', + eventStore: asStore, + }); + context.emitter.trigger('eventRemove', { + event: this, + relatedEvents: [], + revert: function () { + context.dispatch({ + type: 'MERGE_EVENTS', + eventStore: asStore, + }); + }, + }); + }; + Object.defineProperty(EventApi.prototype, "source", { + get: function () { + var sourceId = this._def.sourceId; + if (sourceId) { + return new EventSourceApi(this._context, this._context.getCurrentData().eventSources[sourceId]); + } + return null; + }, + enumerable: false, + configurable: true + }); + Object.defineProperty(EventApi.prototype, "start", { + get: function () { + return this._instance ? + this._context.dateEnv.toDate(this._instance.range.start) : + null; + }, + enumerable: false, + configurable: true + }); + Object.defineProperty(EventApi.prototype, "end", { + get: function () { + return (this._instance && this._def.hasEnd) ? + this._context.dateEnv.toDate(this._instance.range.end) : + null; + }, + enumerable: false, + configurable: true + }); + Object.defineProperty(EventApi.prototype, "startStr", { + get: function () { + var instance = this._instance; + if (instance) { + return this._context.dateEnv.formatIso(instance.range.start, { + omitTime: this._def.allDay, + forcedTzo: instance.forcedStartTzo, + }); + } + return ''; + }, + enumerable: false, + configurable: true + }); + Object.defineProperty(EventApi.prototype, "endStr", { + get: function () { + var instance = this._instance; + if (instance && this._def.hasEnd) { + return this._context.dateEnv.formatIso(instance.range.end, { + omitTime: this._def.allDay, + forcedTzo: instance.forcedEndTzo, + }); + } + return ''; + }, + enumerable: false, + configurable: true + }); + Object.defineProperty(EventApi.prototype, "id", { + // computable props that all access the def + // TODO: find a TypeScript-compatible way to do this at scale + get: function () { return this._def.publicId; }, + enumerable: false, + configurable: true + }); + Object.defineProperty(EventApi.prototype, "groupId", { + get: function () { return this._def.groupId; }, + enumerable: false, + configurable: true + }); + Object.defineProperty(EventApi.prototype, "allDay", { + get: function () { return this._def.allDay; }, + enumerable: false, + configurable: true + }); + Object.defineProperty(EventApi.prototype, "title", { + get: function () { return this._def.title; }, + enumerable: false, + configurable: true + }); + Object.defineProperty(EventApi.prototype, "url", { + get: function () { return this._def.url; }, + enumerable: false, + configurable: true + }); + Object.defineProperty(EventApi.prototype, "display", { + get: function () { return this._def.ui.display || 'auto'; } // bad. just normalize the type earlier + , + enumerable: false, + configurable: true + }); + Object.defineProperty(EventApi.prototype, "startEditable", { + get: function () { return this._def.ui.startEditable; }, + enumerable: false, + configurable: true + }); + Object.defineProperty(EventApi.prototype, "durationEditable", { + get: function () { return this._def.ui.durationEditable; }, + enumerable: false, + configurable: true + }); + Object.defineProperty(EventApi.prototype, "constraint", { + get: function () { return this._def.ui.constraints[0] || null; }, + enumerable: false, + configurable: true + }); + Object.defineProperty(EventApi.prototype, "overlap", { + get: function () { return this._def.ui.overlap; }, + enumerable: false, + configurable: true + }); + Object.defineProperty(EventApi.prototype, "allow", { + get: function () { return this._def.ui.allows[0] || null; }, + enumerable: false, + configurable: true + }); + Object.defineProperty(EventApi.prototype, "backgroundColor", { + get: function () { return this._def.ui.backgroundColor; }, + enumerable: false, + configurable: true + }); + Object.defineProperty(EventApi.prototype, "borderColor", { + get: function () { return this._def.ui.borderColor; }, + enumerable: false, + configurable: true + }); + Object.defineProperty(EventApi.prototype, "textColor", { + get: function () { return this._def.ui.textColor; }, + enumerable: false, + configurable: true + }); + Object.defineProperty(EventApi.prototype, "classNames", { + // NOTE: user can't modify these because Object.freeze was called in event-def parsing + get: function () { return this._def.ui.classNames; }, + enumerable: false, + configurable: true + }); + Object.defineProperty(EventApi.prototype, "extendedProps", { + get: function () { return this._def.extendedProps; }, + enumerable: false, + configurable: true + }); + EventApi.prototype.toPlainObject = function (settings) { + if (settings === void 0) { settings = {}; } + var def = this._def; + var ui = def.ui; + var _a = this, startStr = _a.startStr, endStr = _a.endStr; + var res = {}; + if (def.title) { + res.title = def.title; + } + if (startStr) { + res.start = startStr; + } + if (endStr) { + res.end = endStr; + } + if (def.publicId) { + res.id = def.publicId; + } + if (def.groupId) { + res.groupId = def.groupId; + } + if (def.url) { + res.url = def.url; + } + if (ui.display && ui.display !== 'auto') { + res.display = ui.display; + } + // TODO: what about recurring-event properties??? + // TODO: include startEditable/durationEditable/constraint/overlap/allow + if (settings.collapseColor && ui.backgroundColor && ui.backgroundColor === ui.borderColor) { + res.color = ui.backgroundColor; + } + else { + if (ui.backgroundColor) { + res.backgroundColor = ui.backgroundColor; + } + if (ui.borderColor) { + res.borderColor = ui.borderColor; + } + } + if (ui.textColor) { + res.textColor = ui.textColor; + } + if (ui.classNames.length) { + res.classNames = ui.classNames; + } + if (Object.keys(def.extendedProps).length) { + if (settings.collapseExtendedProps) { + __assign(res, def.extendedProps); + } + else { + res.extendedProps = def.extendedProps; + } + } + return res; + }; + EventApi.prototype.toJSON = function () { + return this.toPlainObject(); + }; + return EventApi; + }()); + function eventApiToStore(eventApi) { + var _a, _b; + var def = eventApi._def; + var instance = eventApi._instance; + return { + defs: (_a = {}, _a[def.defId] = def, _a), + instances: instance + ? (_b = {}, _b[instance.instanceId] = instance, _b) : {}, + }; + } + function buildEventApis(eventStore, context, excludeInstance) { + var defs = eventStore.defs, instances = eventStore.instances; + var eventApis = []; + var excludeInstanceId = excludeInstance ? excludeInstance.instanceId : ''; + for (var id in instances) { + var instance = instances[id]; + var def = defs[instance.defId]; + if (instance.instanceId !== excludeInstanceId) { + eventApis.push(new EventApi(context, def, instance)); + } + } + return eventApis; + } + + var calendarSystemClassMap = {}; + function registerCalendarSystem(name, theClass) { + calendarSystemClassMap[name] = theClass; + } + function createCalendarSystem(name) { + return new calendarSystemClassMap[name](); + } + var GregorianCalendarSystem = /** @class */ (function () { + function GregorianCalendarSystem() { + } + GregorianCalendarSystem.prototype.getMarkerYear = function (d) { + return d.getUTCFullYear(); + }; + GregorianCalendarSystem.prototype.getMarkerMonth = function (d) { + return d.getUTCMonth(); + }; + GregorianCalendarSystem.prototype.getMarkerDay = function (d) { + return d.getUTCDate(); + }; + GregorianCalendarSystem.prototype.arrayToMarker = function (arr) { + return arrayToUtcDate(arr); + }; + GregorianCalendarSystem.prototype.markerToArray = function (marker) { + return dateToUtcArray(marker); + }; + return GregorianCalendarSystem; + }()); + registerCalendarSystem('gregory', GregorianCalendarSystem); + + var ISO_RE = /^\s*(\d{4})(-?(\d{2})(-?(\d{2})([T ](\d{2}):?(\d{2})(:?(\d{2})(\.(\d+))?)?(Z|(([-+])(\d{2})(:?(\d{2}))?))?)?)?)?$/; + function parse(str) { + var m = ISO_RE.exec(str); + if (m) { + var marker = new Date(Date.UTC(Number(m[1]), m[3] ? Number(m[3]) - 1 : 0, Number(m[5] || 1), Number(m[7] || 0), Number(m[8] || 0), Number(m[10] || 0), m[12] ? Number("0." + m[12]) * 1000 : 0)); + if (isValidDate(marker)) { + var timeZoneOffset = null; + if (m[13]) { + timeZoneOffset = (m[15] === '-' ? -1 : 1) * (Number(m[16] || 0) * 60 + + Number(m[18] || 0)); + } + return { + marker: marker, + isTimeUnspecified: !m[6], + timeZoneOffset: timeZoneOffset, + }; + } + } + return null; + } + + var DateEnv = /** @class */ (function () { + function DateEnv(settings) { + var timeZone = this.timeZone = settings.timeZone; + var isNamedTimeZone = timeZone !== 'local' && timeZone !== 'UTC'; + if (settings.namedTimeZoneImpl && isNamedTimeZone) { + this.namedTimeZoneImpl = new settings.namedTimeZoneImpl(timeZone); + } + this.canComputeOffset = Boolean(!isNamedTimeZone || this.namedTimeZoneImpl); + this.calendarSystem = createCalendarSystem(settings.calendarSystem); + this.locale = settings.locale; + this.weekDow = settings.locale.week.dow; + this.weekDoy = settings.locale.week.doy; + if (settings.weekNumberCalculation === 'ISO') { + this.weekDow = 1; + this.weekDoy = 4; + } + if (typeof settings.firstDay === 'number') { + this.weekDow = settings.firstDay; + } + if (typeof settings.weekNumberCalculation === 'function') { + this.weekNumberFunc = settings.weekNumberCalculation; + } + this.weekText = settings.weekText != null ? settings.weekText : settings.locale.options.weekText; + this.cmdFormatter = settings.cmdFormatter; + this.defaultSeparator = settings.defaultSeparator; + } + // Creating / Parsing + DateEnv.prototype.createMarker = function (input) { + var meta = this.createMarkerMeta(input); + if (meta === null) { + return null; + } + return meta.marker; + }; + DateEnv.prototype.createNowMarker = function () { + if (this.canComputeOffset) { + return this.timestampToMarker(new Date().valueOf()); + } + // if we can't compute the current date val for a timezone, + // better to give the current local date vals than UTC + return arrayToUtcDate(dateToLocalArray(new Date())); + }; + DateEnv.prototype.createMarkerMeta = function (input) { + if (typeof input === 'string') { + return this.parse(input); + } + var marker = null; + if (typeof input === 'number') { + marker = this.timestampToMarker(input); + } + else if (input instanceof Date) { + input = input.valueOf(); + if (!isNaN(input)) { + marker = this.timestampToMarker(input); + } + } + else if (Array.isArray(input)) { + marker = arrayToUtcDate(input); + } + if (marker === null || !isValidDate(marker)) { + return null; + } + return { marker: marker, isTimeUnspecified: false, forcedTzo: null }; + }; + DateEnv.prototype.parse = function (s) { + var parts = parse(s); + if (parts === null) { + return null; + } + var marker = parts.marker; + var forcedTzo = null; + if (parts.timeZoneOffset !== null) { + if (this.canComputeOffset) { + marker = this.timestampToMarker(marker.valueOf() - parts.timeZoneOffset * 60 * 1000); + } + else { + forcedTzo = parts.timeZoneOffset; + } + } + return { marker: marker, isTimeUnspecified: parts.isTimeUnspecified, forcedTzo: forcedTzo }; + }; + // Accessors + DateEnv.prototype.getYear = function (marker) { + return this.calendarSystem.getMarkerYear(marker); + }; + DateEnv.prototype.getMonth = function (marker) { + return this.calendarSystem.getMarkerMonth(marker); + }; + // Adding / Subtracting + DateEnv.prototype.add = function (marker, dur) { + var a = this.calendarSystem.markerToArray(marker); + a[0] += dur.years; + a[1] += dur.months; + a[2] += dur.days; + a[6] += dur.milliseconds; + return this.calendarSystem.arrayToMarker(a); + }; + DateEnv.prototype.subtract = function (marker, dur) { + var a = this.calendarSystem.markerToArray(marker); + a[0] -= dur.years; + a[1] -= dur.months; + a[2] -= dur.days; + a[6] -= dur.milliseconds; + return this.calendarSystem.arrayToMarker(a); + }; + DateEnv.prototype.addYears = function (marker, n) { + var a = this.calendarSystem.markerToArray(marker); + a[0] += n; + return this.calendarSystem.arrayToMarker(a); + }; + DateEnv.prototype.addMonths = function (marker, n) { + var a = this.calendarSystem.markerToArray(marker); + a[1] += n; + return this.calendarSystem.arrayToMarker(a); + }; + // Diffing Whole Units + DateEnv.prototype.diffWholeYears = function (m0, m1) { + var calendarSystem = this.calendarSystem; + if (timeAsMs(m0) === timeAsMs(m1) && + calendarSystem.getMarkerDay(m0) === calendarSystem.getMarkerDay(m1) && + calendarSystem.getMarkerMonth(m0) === calendarSystem.getMarkerMonth(m1)) { + return calendarSystem.getMarkerYear(m1) - calendarSystem.getMarkerYear(m0); + } + return null; + }; + DateEnv.prototype.diffWholeMonths = function (m0, m1) { + var calendarSystem = this.calendarSystem; + if (timeAsMs(m0) === timeAsMs(m1) && + calendarSystem.getMarkerDay(m0) === calendarSystem.getMarkerDay(m1)) { + return (calendarSystem.getMarkerMonth(m1) - calendarSystem.getMarkerMonth(m0)) + + (calendarSystem.getMarkerYear(m1) - calendarSystem.getMarkerYear(m0)) * 12; + } + return null; + }; + // Range / Duration + DateEnv.prototype.greatestWholeUnit = function (m0, m1) { + var n = this.diffWholeYears(m0, m1); + if (n !== null) { + return { unit: 'year', value: n }; + } + n = this.diffWholeMonths(m0, m1); + if (n !== null) { + return { unit: 'month', value: n }; + } + n = diffWholeWeeks(m0, m1); + if (n !== null) { + return { unit: 'week', value: n }; + } + n = diffWholeDays(m0, m1); + if (n !== null) { + return { unit: 'day', value: n }; + } + n = diffHours(m0, m1); + if (isInt(n)) { + return { unit: 'hour', value: n }; + } + n = diffMinutes(m0, m1); + if (isInt(n)) { + return { unit: 'minute', value: n }; + } + n = diffSeconds(m0, m1); + if (isInt(n)) { + return { unit: 'second', value: n }; + } + return { unit: 'millisecond', value: m1.valueOf() - m0.valueOf() }; + }; + DateEnv.prototype.countDurationsBetween = function (m0, m1, d) { + // TODO: can use greatestWholeUnit + var diff; + if (d.years) { + diff = this.diffWholeYears(m0, m1); + if (diff !== null) { + return diff / asRoughYears(d); + } + } + if (d.months) { + diff = this.diffWholeMonths(m0, m1); + if (diff !== null) { + return diff / asRoughMonths(d); + } + } + if (d.days) { + diff = diffWholeDays(m0, m1); + if (diff !== null) { + return diff / asRoughDays(d); + } + } + return (m1.valueOf() - m0.valueOf()) / asRoughMs(d); + }; + // Start-Of + // these DON'T return zoned-dates. only UTC start-of dates + DateEnv.prototype.startOf = function (m, unit) { + if (unit === 'year') { + return this.startOfYear(m); + } + if (unit === 'month') { + return this.startOfMonth(m); + } + if (unit === 'week') { + return this.startOfWeek(m); + } + if (unit === 'day') { + return startOfDay(m); + } + if (unit === 'hour') { + return startOfHour(m); + } + if (unit === 'minute') { + return startOfMinute(m); + } + if (unit === 'second') { + return startOfSecond(m); + } + return null; + }; + DateEnv.prototype.startOfYear = function (m) { + return this.calendarSystem.arrayToMarker([ + this.calendarSystem.getMarkerYear(m), + ]); + }; + DateEnv.prototype.startOfMonth = function (m) { + return this.calendarSystem.arrayToMarker([ + this.calendarSystem.getMarkerYear(m), + this.calendarSystem.getMarkerMonth(m), + ]); + }; + DateEnv.prototype.startOfWeek = function (m) { + return this.calendarSystem.arrayToMarker([ + this.calendarSystem.getMarkerYear(m), + this.calendarSystem.getMarkerMonth(m), + m.getUTCDate() - ((m.getUTCDay() - this.weekDow + 7) % 7), + ]); + }; + // Week Number + DateEnv.prototype.computeWeekNumber = function (marker) { + if (this.weekNumberFunc) { + return this.weekNumberFunc(this.toDate(marker)); + } + return weekOfYear(marker, this.weekDow, this.weekDoy); + }; + // TODO: choke on timeZoneName: long + DateEnv.prototype.format = function (marker, formatter, dateOptions) { + if (dateOptions === void 0) { dateOptions = {}; } + return formatter.format({ + marker: marker, + timeZoneOffset: dateOptions.forcedTzo != null ? + dateOptions.forcedTzo : + this.offsetForMarker(marker), + }, this); + }; + DateEnv.prototype.formatRange = function (start, end, formatter, dateOptions) { + if (dateOptions === void 0) { dateOptions = {}; } + if (dateOptions.isEndExclusive) { + end = addMs(end, -1); + } + return formatter.formatRange({ + marker: start, + timeZoneOffset: dateOptions.forcedStartTzo != null ? + dateOptions.forcedStartTzo : + this.offsetForMarker(start), + }, { + marker: end, + timeZoneOffset: dateOptions.forcedEndTzo != null ? + dateOptions.forcedEndTzo : + this.offsetForMarker(end), + }, this, dateOptions.defaultSeparator); + }; + /* + DUMB: the omitTime arg is dumb. if we omit the time, we want to omit the timezone offset. and if we do that, + might as well use buildIsoString or some other util directly + */ + DateEnv.prototype.formatIso = function (marker, extraOptions) { + if (extraOptions === void 0) { extraOptions = {}; } + var timeZoneOffset = null; + if (!extraOptions.omitTimeZoneOffset) { + if (extraOptions.forcedTzo != null) { + timeZoneOffset = extraOptions.forcedTzo; + } + else { + timeZoneOffset = this.offsetForMarker(marker); + } + } + return buildIsoString(marker, timeZoneOffset, extraOptions.omitTime); + }; + // TimeZone + DateEnv.prototype.timestampToMarker = function (ms) { + if (this.timeZone === 'local') { + return arrayToUtcDate(dateToLocalArray(new Date(ms))); + } + if (this.timeZone === 'UTC' || !this.namedTimeZoneImpl) { + return new Date(ms); + } + return arrayToUtcDate(this.namedTimeZoneImpl.timestampToArray(ms)); + }; + DateEnv.prototype.offsetForMarker = function (m) { + if (this.timeZone === 'local') { + return -arrayToLocalDate(dateToUtcArray(m)).getTimezoneOffset(); // convert "inverse" offset to "normal" offset + } + if (this.timeZone === 'UTC') { + return 0; + } + if (this.namedTimeZoneImpl) { + return this.namedTimeZoneImpl.offsetForArray(dateToUtcArray(m)); + } + return null; + }; + // Conversion + DateEnv.prototype.toDate = function (m, forcedTzo) { + if (this.timeZone === 'local') { + return arrayToLocalDate(dateToUtcArray(m)); + } + if (this.timeZone === 'UTC') { + return new Date(m.valueOf()); // make sure it's a copy + } + if (!this.namedTimeZoneImpl) { + return new Date(m.valueOf() - (forcedTzo || 0)); + } + return new Date(m.valueOf() - + this.namedTimeZoneImpl.offsetForArray(dateToUtcArray(m)) * 1000 * 60); + }; + return DateEnv; + }()); + + var globalLocales = []; + + var RAW_EN_LOCALE = { + code: 'en', + week: { + dow: 0, + doy: 4, // 4 days need to be within the year to be considered the first week + }, + direction: 'ltr', + buttonText: { + prev: 'prev', + next: 'next', + prevYear: 'prev year', + nextYear: 'next year', + year: 'year', + today: 'today', + month: 'month', + week: 'week', + day: 'day', + list: 'list', + }, + weekText: 'W', + allDayText: 'all-day', + moreLinkText: 'more', + noEventsText: 'No events to display', + }; + function organizeRawLocales(explicitRawLocales) { + var defaultCode = explicitRawLocales.length > 0 ? explicitRawLocales[0].code : 'en'; + var allRawLocales = globalLocales.concat(explicitRawLocales); + var rawLocaleMap = { + en: RAW_EN_LOCALE, // necessary? + }; + for (var _i = 0, allRawLocales_1 = allRawLocales; _i < allRawLocales_1.length; _i++) { + var rawLocale = allRawLocales_1[_i]; + rawLocaleMap[rawLocale.code] = rawLocale; + } + return { + map: rawLocaleMap, + defaultCode: defaultCode, + }; + } + function buildLocale(inputSingular, available) { + if (typeof inputSingular === 'object' && !Array.isArray(inputSingular)) { + return parseLocale(inputSingular.code, [inputSingular.code], inputSingular); + } + return queryLocale(inputSingular, available); + } + function queryLocale(codeArg, available) { + var codes = [].concat(codeArg || []); // will convert to array + var raw = queryRawLocale(codes, available) || RAW_EN_LOCALE; + return parseLocale(codeArg, codes, raw); + } + function queryRawLocale(codes, available) { + for (var i = 0; i < codes.length; i += 1) { + var parts = codes[i].toLocaleLowerCase().split('-'); + for (var j = parts.length; j > 0; j -= 1) { + var simpleId = parts.slice(0, j).join('-'); + if (available[simpleId]) { + return available[simpleId]; + } + } + } + return null; + } + function parseLocale(codeArg, codes, raw) { + var merged = mergeProps([RAW_EN_LOCALE, raw], ['buttonText']); + delete merged.code; // don't want this part of the options + var week = merged.week; + delete merged.week; + return { + codeArg: codeArg, + codes: codes, + week: week, + simpleNumberFormat: new Intl.NumberFormat(codeArg), + options: merged, + }; + } + + function formatDate(dateInput, options) { + if (options === void 0) { options = {}; } + var dateEnv = buildDateEnv$1(options); + var formatter = createFormatter(options); + var dateMeta = dateEnv.createMarkerMeta(dateInput); + if (!dateMeta) { // TODO: warning? + return ''; + } + return dateEnv.format(dateMeta.marker, formatter, { + forcedTzo: dateMeta.forcedTzo, + }); + } + function formatRange(startInput, endInput, options) { + var dateEnv = buildDateEnv$1(typeof options === 'object' && options ? options : {}); // pass in if non-null object + var formatter = createFormatter(options); + var startMeta = dateEnv.createMarkerMeta(startInput); + var endMeta = dateEnv.createMarkerMeta(endInput); + if (!startMeta || !endMeta) { // TODO: warning? + return ''; + } + return dateEnv.formatRange(startMeta.marker, endMeta.marker, formatter, { + forcedStartTzo: startMeta.forcedTzo, + forcedEndTzo: endMeta.forcedTzo, + isEndExclusive: options.isEndExclusive, + defaultSeparator: BASE_OPTION_DEFAULTS.defaultRangeSeparator, + }); + } + // TODO: more DRY and optimized + function buildDateEnv$1(settings) { + var locale = buildLocale(settings.locale || 'en', organizeRawLocales([]).map); // TODO: don't hardcode 'en' everywhere + return new DateEnv(__assign(__assign({ timeZone: BASE_OPTION_DEFAULTS.timeZone, calendarSystem: 'gregory' }, settings), { locale: locale })); + } + + var DEF_DEFAULTS = { + startTime: '09:00', + endTime: '17:00', + daysOfWeek: [1, 2, 3, 4, 5], + display: 'inverse-background', + classNames: 'fc-non-business', + groupId: '_businessHours', // so multiple defs get grouped + }; + /* + TODO: pass around as EventDefHash!!! + */ + function parseBusinessHours(input, context) { + return parseEvents(refineInputs(input), null, context); + } + function refineInputs(input) { + var rawDefs; + if (input === true) { + rawDefs = [{}]; // will get DEF_DEFAULTS verbatim + } + else if (Array.isArray(input)) { + // if specifying an array, every sub-definition NEEDS a day-of-week + rawDefs = input.filter(function (rawDef) { return rawDef.daysOfWeek; }); + } + else if (typeof input === 'object' && input) { // non-null object + rawDefs = [input]; + } + else { // is probably false + rawDefs = []; + } + rawDefs = rawDefs.map(function (rawDef) { return (__assign(__assign({}, DEF_DEFAULTS), rawDef)); }); + return rawDefs; + } + + function pointInsideRect(point, rect) { + return point.left >= rect.left && + point.left < rect.right && + point.top >= rect.top && + point.top < rect.bottom; + } + // Returns a new rectangle that is the intersection of the two rectangles. If they don't intersect, returns false + function intersectRects(rect1, rect2) { + var res = { + left: Math.max(rect1.left, rect2.left), + right: Math.min(rect1.right, rect2.right), + top: Math.max(rect1.top, rect2.top), + bottom: Math.min(rect1.bottom, rect2.bottom), + }; + if (res.left < res.right && res.top < res.bottom) { + return res; + } + return false; + } + function translateRect(rect, deltaX, deltaY) { + return { + left: rect.left + deltaX, + right: rect.right + deltaX, + top: rect.top + deltaY, + bottom: rect.bottom + deltaY, + }; + } + // Returns a new point that will have been moved to reside within the given rectangle + function constrainPoint(point, rect) { + return { + left: Math.min(Math.max(point.left, rect.left), rect.right), + top: Math.min(Math.max(point.top, rect.top), rect.bottom), + }; + } + // Returns a point that is the center of the given rectangle + function getRectCenter(rect) { + return { + left: (rect.left + rect.right) / 2, + top: (rect.top + rect.bottom) / 2, + }; + } + // Subtracts point2's coordinates from point1's coordinates, returning a delta + function diffPoints(point1, point2) { + return { + left: point1.left - point2.left, + top: point1.top - point2.top, + }; + } + + var canVGrowWithinCell; + function getCanVGrowWithinCell() { + if (canVGrowWithinCell == null) { + canVGrowWithinCell = computeCanVGrowWithinCell(); + } + return canVGrowWithinCell; + } + function computeCanVGrowWithinCell() { + // for SSR, because this function is call immediately at top-level + // TODO: just make this logic execute top-level, immediately, instead of doing lazily + if (typeof document === 'undefined') { + return true; + } + var el = document.createElement('div'); + el.style.position = 'absolute'; + el.style.top = '0px'; + el.style.left = '0px'; + el.innerHTML = '
'; + el.querySelector('table').style.height = '100px'; + el.querySelector('div').style.height = '100%'; + document.body.appendChild(el); + var div = el.querySelector('div'); + var possible = div.offsetHeight > 0; + document.body.removeChild(el); + return possible; + } + + var EMPTY_EVENT_STORE = createEmptyEventStore(); // for purecomponents. TODO: keep elsewhere + var Splitter = /** @class */ (function () { + function Splitter() { + this.getKeysForEventDefs = memoize(this._getKeysForEventDefs); + this.splitDateSelection = memoize(this._splitDateSpan); + this.splitEventStore = memoize(this._splitEventStore); + this.splitIndividualUi = memoize(this._splitIndividualUi); + this.splitEventDrag = memoize(this._splitInteraction); + this.splitEventResize = memoize(this._splitInteraction); + this.eventUiBuilders = {}; // TODO: typescript protection + } + Splitter.prototype.splitProps = function (props) { + var _this = this; + var keyInfos = this.getKeyInfo(props); + var defKeys = this.getKeysForEventDefs(props.eventStore); + var dateSelections = this.splitDateSelection(props.dateSelection); + var individualUi = this.splitIndividualUi(props.eventUiBases, defKeys); // the individual *bases* + var eventStores = this.splitEventStore(props.eventStore, defKeys); + var eventDrags = this.splitEventDrag(props.eventDrag); + var eventResizes = this.splitEventResize(props.eventResize); + var splitProps = {}; + this.eventUiBuilders = mapHash(keyInfos, function (info, key) { return _this.eventUiBuilders[key] || memoize(buildEventUiForKey); }); + for (var key in keyInfos) { + var keyInfo = keyInfos[key]; + var eventStore = eventStores[key] || EMPTY_EVENT_STORE; + var buildEventUi = this.eventUiBuilders[key]; + splitProps[key] = { + businessHours: keyInfo.businessHours || props.businessHours, + dateSelection: dateSelections[key] || null, + eventStore: eventStore, + eventUiBases: buildEventUi(props.eventUiBases[''], keyInfo.ui, individualUi[key]), + eventSelection: eventStore.instances[props.eventSelection] ? props.eventSelection : '', + eventDrag: eventDrags[key] || null, + eventResize: eventResizes[key] || null, + }; + } + return splitProps; + }; + Splitter.prototype._splitDateSpan = function (dateSpan) { + var dateSpans = {}; + if (dateSpan) { + var keys = this.getKeysForDateSpan(dateSpan); + for (var _i = 0, keys_1 = keys; _i < keys_1.length; _i++) { + var key = keys_1[_i]; + dateSpans[key] = dateSpan; + } + } + return dateSpans; + }; + Splitter.prototype._getKeysForEventDefs = function (eventStore) { + var _this = this; + return mapHash(eventStore.defs, function (eventDef) { return _this.getKeysForEventDef(eventDef); }); + }; + Splitter.prototype._splitEventStore = function (eventStore, defKeys) { + var defs = eventStore.defs, instances = eventStore.instances; + var splitStores = {}; + for (var defId in defs) { + for (var _i = 0, _a = defKeys[defId]; _i < _a.length; _i++) { + var key = _a[_i]; + if (!splitStores[key]) { + splitStores[key] = createEmptyEventStore(); + } + splitStores[key].defs[defId] = defs[defId]; + } + } + for (var instanceId in instances) { + var instance = instances[instanceId]; + for (var _b = 0, _c = defKeys[instance.defId]; _b < _c.length; _b++) { + var key = _c[_b]; + if (splitStores[key]) { // must have already been created + splitStores[key].instances[instanceId] = instance; + } + } + } + return splitStores; + }; + Splitter.prototype._splitIndividualUi = function (eventUiBases, defKeys) { + var splitHashes = {}; + for (var defId in eventUiBases) { + if (defId) { // not the '' key + for (var _i = 0, _a = defKeys[defId]; _i < _a.length; _i++) { + var key = _a[_i]; + if (!splitHashes[key]) { + splitHashes[key] = {}; + } + splitHashes[key][defId] = eventUiBases[defId]; + } + } + } + return splitHashes; + }; + Splitter.prototype._splitInteraction = function (interaction) { + var splitStates = {}; + if (interaction) { + var affectedStores_1 = this._splitEventStore(interaction.affectedEvents, this._getKeysForEventDefs(interaction.affectedEvents)); + // can't rely on defKeys because event data is mutated + var mutatedKeysByDefId = this._getKeysForEventDefs(interaction.mutatedEvents); + var mutatedStores_1 = this._splitEventStore(interaction.mutatedEvents, mutatedKeysByDefId); + var populate = function (key) { + if (!splitStates[key]) { + splitStates[key] = { + affectedEvents: affectedStores_1[key] || EMPTY_EVENT_STORE, + mutatedEvents: mutatedStores_1[key] || EMPTY_EVENT_STORE, + isEvent: interaction.isEvent, + }; + } + }; + for (var key in affectedStores_1) { + populate(key); + } + for (var key in mutatedStores_1) { + populate(key); + } + } + return splitStates; + }; + return Splitter; + }()); + function buildEventUiForKey(allUi, eventUiForKey, individualUi) { + var baseParts = []; + if (allUi) { + baseParts.push(allUi); + } + if (eventUiForKey) { + baseParts.push(eventUiForKey); + } + var stuff = { + '': combineEventUis(baseParts), + }; + if (individualUi) { + __assign(stuff, individualUi); + } + return stuff; + } + + function getDateMeta(date, todayRange, nowDate, dateProfile) { + return { + dow: date.getUTCDay(), + isDisabled: Boolean(dateProfile && !rangeContainsMarker(dateProfile.activeRange, date)), + isOther: Boolean(dateProfile && !rangeContainsMarker(dateProfile.currentRange, date)), + isToday: Boolean(todayRange && rangeContainsMarker(todayRange, date)), + isPast: Boolean(nowDate ? (date < nowDate) : todayRange ? (date < todayRange.start) : false), + isFuture: Boolean(nowDate ? (date > nowDate) : todayRange ? (date >= todayRange.end) : false), + }; + } + function getDayClassNames(meta, theme) { + var classNames = [ + 'fc-day', + "fc-day-" + DAY_IDS[meta.dow], + ]; + if (meta.isDisabled) { + classNames.push('fc-day-disabled'); + } + else { + if (meta.isToday) { + classNames.push('fc-day-today'); + classNames.push(theme.getClass('today')); + } + if (meta.isPast) { + classNames.push('fc-day-past'); + } + if (meta.isFuture) { + classNames.push('fc-day-future'); + } + if (meta.isOther) { + classNames.push('fc-day-other'); + } + } + return classNames; + } + function getSlotClassNames(meta, theme) { + var classNames = [ + 'fc-slot', + "fc-slot-" + DAY_IDS[meta.dow], + ]; + if (meta.isDisabled) { + classNames.push('fc-slot-disabled'); + } + else { + if (meta.isToday) { + classNames.push('fc-slot-today'); + classNames.push(theme.getClass('today')); + } + if (meta.isPast) { + classNames.push('fc-slot-past'); + } + if (meta.isFuture) { + classNames.push('fc-slot-future'); + } + } + return classNames; + } + + function buildNavLinkData(date, type) { + if (type === void 0) { type = 'day'; } + return JSON.stringify({ + date: formatDayString(date), + type: type, + }); + } + + var _isRtlScrollbarOnLeft = null; + function getIsRtlScrollbarOnLeft() { + if (_isRtlScrollbarOnLeft === null) { + _isRtlScrollbarOnLeft = computeIsRtlScrollbarOnLeft(); + } + return _isRtlScrollbarOnLeft; + } + function computeIsRtlScrollbarOnLeft() { + var outerEl = document.createElement('div'); + applyStyle(outerEl, { + position: 'absolute', + top: -1000, + left: 0, + border: 0, + padding: 0, + overflow: 'scroll', + direction: 'rtl', + }); + outerEl.innerHTML = '
'; + document.body.appendChild(outerEl); + var innerEl = outerEl.firstChild; + var res = innerEl.getBoundingClientRect().left > outerEl.getBoundingClientRect().left; + removeElement(outerEl); + return res; + } + + var _scrollbarWidths; + function getScrollbarWidths() { + if (!_scrollbarWidths) { + _scrollbarWidths = computeScrollbarWidths(); + } + return _scrollbarWidths; + } + function computeScrollbarWidths() { + var el = document.createElement('div'); + el.style.overflow = 'scroll'; + el.style.position = 'absolute'; + el.style.top = '-9999px'; + el.style.left = '-9999px'; + document.body.appendChild(el); + var res = computeScrollbarWidthsForEl(el); + document.body.removeChild(el); + return res; + } + // WARNING: will include border + function computeScrollbarWidthsForEl(el) { + return { + x: el.offsetHeight - el.clientHeight, + y: el.offsetWidth - el.clientWidth, + }; + } + + function computeEdges(el, getPadding) { + if (getPadding === void 0) { getPadding = false; } + var computedStyle = window.getComputedStyle(el); + var borderLeft = parseInt(computedStyle.borderLeftWidth, 10) || 0; + var borderRight = parseInt(computedStyle.borderRightWidth, 10) || 0; + var borderTop = parseInt(computedStyle.borderTopWidth, 10) || 0; + var borderBottom = parseInt(computedStyle.borderBottomWidth, 10) || 0; + var badScrollbarWidths = computeScrollbarWidthsForEl(el); // includes border! + var scrollbarLeftRight = badScrollbarWidths.y - borderLeft - borderRight; + var scrollbarBottom = badScrollbarWidths.x - borderTop - borderBottom; + var res = { + borderLeft: borderLeft, + borderRight: borderRight, + borderTop: borderTop, + borderBottom: borderBottom, + scrollbarBottom: scrollbarBottom, + scrollbarLeft: 0, + scrollbarRight: 0, + }; + if (getIsRtlScrollbarOnLeft() && computedStyle.direction === 'rtl') { // is the scrollbar on the left side? + res.scrollbarLeft = scrollbarLeftRight; + } + else { + res.scrollbarRight = scrollbarLeftRight; + } + if (getPadding) { + res.paddingLeft = parseInt(computedStyle.paddingLeft, 10) || 0; + res.paddingRight = parseInt(computedStyle.paddingRight, 10) || 0; + res.paddingTop = parseInt(computedStyle.paddingTop, 10) || 0; + res.paddingBottom = parseInt(computedStyle.paddingBottom, 10) || 0; + } + return res; + } + function computeInnerRect(el, goWithinPadding, doFromWindowViewport) { + if (goWithinPadding === void 0) { goWithinPadding = false; } + var outerRect = doFromWindowViewport ? el.getBoundingClientRect() : computeRect(el); + var edges = computeEdges(el, goWithinPadding); + var res = { + left: outerRect.left + edges.borderLeft + edges.scrollbarLeft, + right: outerRect.right - edges.borderRight - edges.scrollbarRight, + top: outerRect.top + edges.borderTop, + bottom: outerRect.bottom - edges.borderBottom - edges.scrollbarBottom, + }; + if (goWithinPadding) { + res.left += edges.paddingLeft; + res.right -= edges.paddingRight; + res.top += edges.paddingTop; + res.bottom -= edges.paddingBottom; + } + return res; + } + function computeRect(el) { + var rect = el.getBoundingClientRect(); + return { + left: rect.left + window.pageXOffset, + top: rect.top + window.pageYOffset, + right: rect.right + window.pageXOffset, + bottom: rect.bottom + window.pageYOffset, + }; + } + function computeClippedClientRect(el) { + var clippingParents = getClippingParents(el); + var rect = el.getBoundingClientRect(); + for (var _i = 0, clippingParents_1 = clippingParents; _i < clippingParents_1.length; _i++) { + var clippingParent = clippingParents_1[_i]; + var intersection = intersectRects(rect, clippingParent.getBoundingClientRect()); + if (intersection) { + rect = intersection; + } + else { + return null; + } + } + return rect; + } + function computeHeightAndMargins(el) { + return el.getBoundingClientRect().height + computeVMargins(el); + } + function computeVMargins(el) { + var computed = window.getComputedStyle(el); + return parseInt(computed.marginTop, 10) + + parseInt(computed.marginBottom, 10); + } + // does not return window + function getClippingParents(el) { + var parents = []; + while (el instanceof HTMLElement) { // will stop when gets to document or null + var computedStyle = window.getComputedStyle(el); + if (computedStyle.position === 'fixed') { + break; + } + if ((/(auto|scroll)/).test(computedStyle.overflow + computedStyle.overflowY + computedStyle.overflowX)) { + parents.push(el); + } + el = el.parentNode; + } + return parents; + } + + // given a function that resolves a result asynchronously. + // the function can either call passed-in success and failure callbacks, + // or it can return a promise. + // if you need to pass additional params to func, bind them first. + function unpromisify(func, success, failure) { + // guard against success/failure callbacks being called more than once + // and guard against a promise AND callback being used together. + var isResolved = false; + var wrappedSuccess = function () { + if (!isResolved) { + isResolved = true; + success.apply(this, arguments); // eslint-disable-line prefer-rest-params + } + }; + var wrappedFailure = function () { + if (!isResolved) { + isResolved = true; + if (failure) { + failure.apply(this, arguments); // eslint-disable-line prefer-rest-params + } + } + }; + var res = func(wrappedSuccess, wrappedFailure); + if (res && typeof res.then === 'function') { + res.then(wrappedSuccess, wrappedFailure); + } + } + + var Emitter = /** @class */ (function () { + function Emitter() { + this.handlers = {}; + this.thisContext = null; + } + Emitter.prototype.setThisContext = function (thisContext) { + this.thisContext = thisContext; + }; + Emitter.prototype.setOptions = function (options) { + this.options = options; + }; + Emitter.prototype.on = function (type, handler) { + addToHash(this.handlers, type, handler); + }; + Emitter.prototype.off = function (type, handler) { + removeFromHash(this.handlers, type, handler); + }; + Emitter.prototype.trigger = function (type) { + var args = []; + for (var _i = 1; _i < arguments.length; _i++) { + args[_i - 1] = arguments[_i]; + } + var attachedHandlers = this.handlers[type] || []; + var optionHandler = this.options && this.options[type]; + var handlers = [].concat(optionHandler || [], attachedHandlers); + for (var _a = 0, handlers_1 = handlers; _a < handlers_1.length; _a++) { + var handler = handlers_1[_a]; + handler.apply(this.thisContext, args); + } + }; + Emitter.prototype.hasHandlers = function (type) { + return (this.handlers[type] && this.handlers[type].length) || + (this.options && this.options[type]); + }; + return Emitter; + }()); + function addToHash(hash, type, handler) { + (hash[type] || (hash[type] = [])) + .push(handler); + } + function removeFromHash(hash, type, handler) { + if (handler) { + if (hash[type]) { + hash[type] = hash[type].filter(function (func) { return func !== handler; }); + } + } + else { + delete hash[type]; // remove all handler funcs for this type + } + } + + /* + Records offset information for a set of elements, relative to an origin element. + Can record the left/right OR the top/bottom OR both. + Provides methods for querying the cache by position. + */ + var PositionCache = /** @class */ (function () { + function PositionCache(originEl, els, isHorizontal, isVertical) { + this.els = els; + var originClientRect = this.originClientRect = originEl.getBoundingClientRect(); // relative to viewport top-left + if (isHorizontal) { + this.buildElHorizontals(originClientRect.left); + } + if (isVertical) { + this.buildElVerticals(originClientRect.top); + } + } + // Populates the left/right internal coordinate arrays + PositionCache.prototype.buildElHorizontals = function (originClientLeft) { + var lefts = []; + var rights = []; + for (var _i = 0, _a = this.els; _i < _a.length; _i++) { + var el = _a[_i]; + var rect = el.getBoundingClientRect(); + lefts.push(rect.left - originClientLeft); + rights.push(rect.right - originClientLeft); + } + this.lefts = lefts; + this.rights = rights; + }; + // Populates the top/bottom internal coordinate arrays + PositionCache.prototype.buildElVerticals = function (originClientTop) { + var tops = []; + var bottoms = []; + for (var _i = 0, _a = this.els; _i < _a.length; _i++) { + var el = _a[_i]; + var rect = el.getBoundingClientRect(); + tops.push(rect.top - originClientTop); + bottoms.push(rect.bottom - originClientTop); + } + this.tops = tops; + this.bottoms = bottoms; + }; + // Given a left offset (from document left), returns the index of the el that it horizontally intersects. + // If no intersection is made, returns undefined. + PositionCache.prototype.leftToIndex = function (leftPosition) { + var _a = this, lefts = _a.lefts, rights = _a.rights; + var len = lefts.length; + var i; + for (i = 0; i < len; i += 1) { + if (leftPosition >= lefts[i] && leftPosition < rights[i]) { + return i; + } + } + return undefined; // TODO: better + }; + // Given a top offset (from document top), returns the index of the el that it vertically intersects. + // If no intersection is made, returns undefined. + PositionCache.prototype.topToIndex = function (topPosition) { + var _a = this, tops = _a.tops, bottoms = _a.bottoms; + var len = tops.length; + var i; + for (i = 0; i < len; i += 1) { + if (topPosition >= tops[i] && topPosition < bottoms[i]) { + return i; + } + } + return undefined; // TODO: better + }; + // Gets the width of the element at the given index + PositionCache.prototype.getWidth = function (leftIndex) { + return this.rights[leftIndex] - this.lefts[leftIndex]; + }; + // Gets the height of the element at the given index + PositionCache.prototype.getHeight = function (topIndex) { + return this.bottoms[topIndex] - this.tops[topIndex]; + }; + return PositionCache; + }()); + + /* eslint max-classes-per-file: "off" */ + /* + An object for getting/setting scroll-related information for an element. + Internally, this is done very differently for window versus DOM element, + so this object serves as a common interface. + */ + var ScrollController = /** @class */ (function () { + function ScrollController() { + } + ScrollController.prototype.getMaxScrollTop = function () { + return this.getScrollHeight() - this.getClientHeight(); + }; + ScrollController.prototype.getMaxScrollLeft = function () { + return this.getScrollWidth() - this.getClientWidth(); + }; + ScrollController.prototype.canScrollVertically = function () { + return this.getMaxScrollTop() > 0; + }; + ScrollController.prototype.canScrollHorizontally = function () { + return this.getMaxScrollLeft() > 0; + }; + ScrollController.prototype.canScrollUp = function () { + return this.getScrollTop() > 0; + }; + ScrollController.prototype.canScrollDown = function () { + return this.getScrollTop() < this.getMaxScrollTop(); + }; + ScrollController.prototype.canScrollLeft = function () { + return this.getScrollLeft() > 0; + }; + ScrollController.prototype.canScrollRight = function () { + return this.getScrollLeft() < this.getMaxScrollLeft(); + }; + return ScrollController; + }()); + var ElementScrollController = /** @class */ (function (_super) { + __extends(ElementScrollController, _super); + function ElementScrollController(el) { + var _this = _super.call(this) || this; + _this.el = el; + return _this; + } + ElementScrollController.prototype.getScrollTop = function () { + return this.el.scrollTop; + }; + ElementScrollController.prototype.getScrollLeft = function () { + return this.el.scrollLeft; + }; + ElementScrollController.prototype.setScrollTop = function (top) { + this.el.scrollTop = top; + }; + ElementScrollController.prototype.setScrollLeft = function (left) { + this.el.scrollLeft = left; + }; + ElementScrollController.prototype.getScrollWidth = function () { + return this.el.scrollWidth; + }; + ElementScrollController.prototype.getScrollHeight = function () { + return this.el.scrollHeight; + }; + ElementScrollController.prototype.getClientHeight = function () { + return this.el.clientHeight; + }; + ElementScrollController.prototype.getClientWidth = function () { + return this.el.clientWidth; + }; + return ElementScrollController; + }(ScrollController)); + var WindowScrollController = /** @class */ (function (_super) { + __extends(WindowScrollController, _super); + function WindowScrollController() { + return _super !== null && _super.apply(this, arguments) || this; + } + WindowScrollController.prototype.getScrollTop = function () { + return window.pageYOffset; + }; + WindowScrollController.prototype.getScrollLeft = function () { + return window.pageXOffset; + }; + WindowScrollController.prototype.setScrollTop = function (n) { + window.scroll(window.pageXOffset, n); + }; + WindowScrollController.prototype.setScrollLeft = function (n) { + window.scroll(n, window.pageYOffset); + }; + WindowScrollController.prototype.getScrollWidth = function () { + return document.documentElement.scrollWidth; + }; + WindowScrollController.prototype.getScrollHeight = function () { + return document.documentElement.scrollHeight; + }; + WindowScrollController.prototype.getClientHeight = function () { + return document.documentElement.clientHeight; + }; + WindowScrollController.prototype.getClientWidth = function () { + return document.documentElement.clientWidth; + }; + return WindowScrollController; + }(ScrollController)); + + var Theme = /** @class */ (function () { + function Theme(calendarOptions) { + if (this.iconOverrideOption) { + this.setIconOverride(calendarOptions[this.iconOverrideOption]); + } + } + Theme.prototype.setIconOverride = function (iconOverrideHash) { + var iconClassesCopy; + var buttonName; + if (typeof iconOverrideHash === 'object' && iconOverrideHash) { // non-null object + iconClassesCopy = __assign({}, this.iconClasses); + for (buttonName in iconOverrideHash) { + iconClassesCopy[buttonName] = this.applyIconOverridePrefix(iconOverrideHash[buttonName]); + } + this.iconClasses = iconClassesCopy; + } + else if (iconOverrideHash === false) { + this.iconClasses = {}; + } + }; + Theme.prototype.applyIconOverridePrefix = function (className) { + var prefix = this.iconOverridePrefix; + if (prefix && className.indexOf(prefix) !== 0) { // if not already present + className = prefix + className; + } + return className; + }; + Theme.prototype.getClass = function (key) { + return this.classes[key] || ''; + }; + Theme.prototype.getIconClass = function (buttonName, isRtl) { + var className; + if (isRtl && this.rtlIconClasses) { + className = this.rtlIconClasses[buttonName] || this.iconClasses[buttonName]; + } + else { + className = this.iconClasses[buttonName]; + } + if (className) { + return this.baseIconClass + " " + className; + } + return ''; + }; + Theme.prototype.getCustomButtonIconClass = function (customButtonProps) { + var className; + if (this.iconOverrideCustomButtonOption) { + className = customButtonProps[this.iconOverrideCustomButtonOption]; + if (className) { + return this.baseIconClass + " " + this.applyIconOverridePrefix(className); + } + } + return ''; + }; + return Theme; + }()); + Theme.prototype.classes = {}; + Theme.prototype.iconClasses = {}; + Theme.prototype.baseIconClass = ''; + Theme.prototype.iconOverridePrefix = ''; + + /// + if (typeof FullCalendarVDom === 'undefined') { + throw new Error('Please import the top-level fullcalendar lib before attempting to import a plugin.'); + } + var Component = FullCalendarVDom.Component; + var createElement = FullCalendarVDom.createElement; + var render = FullCalendarVDom.render; + var createRef = FullCalendarVDom.createRef; + var Fragment = FullCalendarVDom.Fragment; + var createContext = FullCalendarVDom.createContext; + var createPortal = FullCalendarVDom.createPortal; + var flushToDom = FullCalendarVDom.flushToDom; + var unmountComponentAtNode = FullCalendarVDom.unmountComponentAtNode; + /* eslint-enable */ + + var ScrollResponder = /** @class */ (function () { + function ScrollResponder(execFunc, emitter, scrollTime, scrollTimeReset) { + var _this = this; + this.execFunc = execFunc; + this.emitter = emitter; + this.scrollTime = scrollTime; + this.scrollTimeReset = scrollTimeReset; + this.handleScrollRequest = function (request) { + _this.queuedRequest = __assign({}, _this.queuedRequest || {}, request); + _this.drain(); + }; + emitter.on('_scrollRequest', this.handleScrollRequest); + this.fireInitialScroll(); + } + ScrollResponder.prototype.detach = function () { + this.emitter.off('_scrollRequest', this.handleScrollRequest); + }; + ScrollResponder.prototype.update = function (isDatesNew) { + if (isDatesNew && this.scrollTimeReset) { + this.fireInitialScroll(); // will drain + } + else { + this.drain(); + } + }; + ScrollResponder.prototype.fireInitialScroll = function () { + this.handleScrollRequest({ + time: this.scrollTime, + }); + }; + ScrollResponder.prototype.drain = function () { + if (this.queuedRequest && this.execFunc(this.queuedRequest)) { + this.queuedRequest = null; + } + }; + return ScrollResponder; + }()); + + var ViewContextType = createContext({}); // for Components + function buildViewContext(viewSpec, viewApi, viewOptions, dateProfileGenerator, dateEnv, theme, pluginHooks, dispatch, getCurrentData, emitter, calendarApi, registerInteractiveComponent, unregisterInteractiveComponent) { + return { + dateEnv: dateEnv, + options: viewOptions, + pluginHooks: pluginHooks, + emitter: emitter, + dispatch: dispatch, + getCurrentData: getCurrentData, + calendarApi: calendarApi, + viewSpec: viewSpec, + viewApi: viewApi, + dateProfileGenerator: dateProfileGenerator, + theme: theme, + isRtl: viewOptions.direction === 'rtl', + addResizeHandler: function (handler) { + emitter.on('_resize', handler); + }, + removeResizeHandler: function (handler) { + emitter.off('_resize', handler); + }, + createScrollResponder: function (execFunc) { + return new ScrollResponder(execFunc, emitter, createDuration(viewOptions.scrollTime), viewOptions.scrollTimeReset); + }, + registerInteractiveComponent: registerInteractiveComponent, + unregisterInteractiveComponent: unregisterInteractiveComponent, + }; + } + + /* eslint max-classes-per-file: off */ + var PureComponent = /** @class */ (function (_super) { + __extends(PureComponent, _super); + function PureComponent() { + return _super !== null && _super.apply(this, arguments) || this; + } + PureComponent.prototype.shouldComponentUpdate = function (nextProps, nextState) { + if (this.debug) { + // eslint-disable-next-line no-console + console.log(getUnequalProps(nextProps, this.props), getUnequalProps(nextState, this.state)); + } + return !compareObjs(this.props, nextProps, this.propEquality) || + !compareObjs(this.state, nextState, this.stateEquality); + }; + PureComponent.addPropsEquality = addPropsEquality; + PureComponent.addStateEquality = addStateEquality; + PureComponent.contextType = ViewContextType; + return PureComponent; + }(Component)); + PureComponent.prototype.propEquality = {}; + PureComponent.prototype.stateEquality = {}; + var BaseComponent = /** @class */ (function (_super) { + __extends(BaseComponent, _super); + function BaseComponent() { + return _super !== null && _super.apply(this, arguments) || this; + } + BaseComponent.contextType = ViewContextType; + return BaseComponent; + }(PureComponent)); + function addPropsEquality(propEquality) { + var hash = Object.create(this.prototype.propEquality); + __assign(hash, propEquality); + this.prototype.propEquality = hash; + } + function addStateEquality(stateEquality) { + var hash = Object.create(this.prototype.stateEquality); + __assign(hash, stateEquality); + this.prototype.stateEquality = hash; + } + // use other one + function setRef(ref, current) { + if (typeof ref === 'function') { + ref(current); + } + else if (ref) { + // see https://github.com/facebook/react/issues/13029 + ref.current = current; + } + } + + /* + an INTERACTABLE date component + + PURPOSES: + - hook up to fg, fill, and mirror renderers + - interface for dragging and hits + */ + var DateComponent = /** @class */ (function (_super) { + __extends(DateComponent, _super); + function DateComponent() { + var _this = _super !== null && _super.apply(this, arguments) || this; + _this.uid = guid(); + return _this; + } + // Hit System + // ----------------------------------------------------------------------------------------------------------------- + DateComponent.prototype.prepareHits = function () { + }; + DateComponent.prototype.queryHit = function (positionLeft, positionTop, elWidth, elHeight) { + return null; // this should be abstract + }; + // Pointer Interaction Utils + // ----------------------------------------------------------------------------------------------------------------- + DateComponent.prototype.isValidSegDownEl = function (el) { + return !this.props.eventDrag && // HACK + !this.props.eventResize && // HACK + !elementClosest(el, '.fc-event-mirror'); + }; + DateComponent.prototype.isValidDateDownEl = function (el) { + return !elementClosest(el, '.fc-event:not(.fc-bg-event)') && + !elementClosest(el, '.fc-more-link') && // a "more.." link + !elementClosest(el, 'a[data-navlink]') && // a clickable nav link + !elementClosest(el, '.fc-popover'); // hack + }; + return DateComponent; + }(BaseComponent)); + + // TODO: easier way to add new hooks? need to update a million things + function createPlugin(input) { + return { + id: guid(), + deps: input.deps || [], + reducers: input.reducers || [], + isLoadingFuncs: input.isLoadingFuncs || [], + contextInit: [].concat(input.contextInit || []), + eventRefiners: input.eventRefiners || {}, + eventDefMemberAdders: input.eventDefMemberAdders || [], + eventSourceRefiners: input.eventSourceRefiners || {}, + isDraggableTransformers: input.isDraggableTransformers || [], + eventDragMutationMassagers: input.eventDragMutationMassagers || [], + eventDefMutationAppliers: input.eventDefMutationAppliers || [], + dateSelectionTransformers: input.dateSelectionTransformers || [], + datePointTransforms: input.datePointTransforms || [], + dateSpanTransforms: input.dateSpanTransforms || [], + views: input.views || {}, + viewPropsTransformers: input.viewPropsTransformers || [], + isPropsValid: input.isPropsValid || null, + externalDefTransforms: input.externalDefTransforms || [], + viewContainerAppends: input.viewContainerAppends || [], + eventDropTransformers: input.eventDropTransformers || [], + componentInteractions: input.componentInteractions || [], + calendarInteractions: input.calendarInteractions || [], + themeClasses: input.themeClasses || {}, + eventSourceDefs: input.eventSourceDefs || [], + cmdFormatter: input.cmdFormatter, + recurringTypes: input.recurringTypes || [], + namedTimeZonedImpl: input.namedTimeZonedImpl, + initialView: input.initialView || '', + elementDraggingImpl: input.elementDraggingImpl, + optionChangeHandlers: input.optionChangeHandlers || {}, + scrollGridImpl: input.scrollGridImpl || null, + contentTypeHandlers: input.contentTypeHandlers || {}, + listenerRefiners: input.listenerRefiners || {}, + optionRefiners: input.optionRefiners || {}, + propSetHandlers: input.propSetHandlers || {}, + }; + } + function buildPluginHooks(pluginDefs, globalDefs) { + var isAdded = {}; + var hooks = { + reducers: [], + isLoadingFuncs: [], + contextInit: [], + eventRefiners: {}, + eventDefMemberAdders: [], + eventSourceRefiners: {}, + isDraggableTransformers: [], + eventDragMutationMassagers: [], + eventDefMutationAppliers: [], + dateSelectionTransformers: [], + datePointTransforms: [], + dateSpanTransforms: [], + views: {}, + viewPropsTransformers: [], + isPropsValid: null, + externalDefTransforms: [], + viewContainerAppends: [], + eventDropTransformers: [], + componentInteractions: [], + calendarInteractions: [], + themeClasses: {}, + eventSourceDefs: [], + cmdFormatter: null, + recurringTypes: [], + namedTimeZonedImpl: null, + initialView: '', + elementDraggingImpl: null, + optionChangeHandlers: {}, + scrollGridImpl: null, + contentTypeHandlers: {}, + listenerRefiners: {}, + optionRefiners: {}, + propSetHandlers: {}, + }; + function addDefs(defs) { + for (var _i = 0, defs_1 = defs; _i < defs_1.length; _i++) { + var def = defs_1[_i]; + if (!isAdded[def.id]) { + isAdded[def.id] = true; + addDefs(def.deps); + hooks = combineHooks(hooks, def); + } + } + } + if (pluginDefs) { + addDefs(pluginDefs); + } + addDefs(globalDefs); + return hooks; + } + function buildBuildPluginHooks() { + var currentOverrideDefs = []; + var currentGlobalDefs = []; + var currentHooks; + return function (overrideDefs, globalDefs) { + if (!currentHooks || !isArraysEqual(overrideDefs, currentOverrideDefs) || !isArraysEqual(globalDefs, currentGlobalDefs)) { + currentHooks = buildPluginHooks(overrideDefs, globalDefs); + } + currentOverrideDefs = overrideDefs; + currentGlobalDefs = globalDefs; + return currentHooks; + }; + } + function combineHooks(hooks0, hooks1) { + return { + reducers: hooks0.reducers.concat(hooks1.reducers), + isLoadingFuncs: hooks0.isLoadingFuncs.concat(hooks1.isLoadingFuncs), + contextInit: hooks0.contextInit.concat(hooks1.contextInit), + eventRefiners: __assign(__assign({}, hooks0.eventRefiners), hooks1.eventRefiners), + eventDefMemberAdders: hooks0.eventDefMemberAdders.concat(hooks1.eventDefMemberAdders), + eventSourceRefiners: __assign(__assign({}, hooks0.eventSourceRefiners), hooks1.eventSourceRefiners), + isDraggableTransformers: hooks0.isDraggableTransformers.concat(hooks1.isDraggableTransformers), + eventDragMutationMassagers: hooks0.eventDragMutationMassagers.concat(hooks1.eventDragMutationMassagers), + eventDefMutationAppliers: hooks0.eventDefMutationAppliers.concat(hooks1.eventDefMutationAppliers), + dateSelectionTransformers: hooks0.dateSelectionTransformers.concat(hooks1.dateSelectionTransformers), + datePointTransforms: hooks0.datePointTransforms.concat(hooks1.datePointTransforms), + dateSpanTransforms: hooks0.dateSpanTransforms.concat(hooks1.dateSpanTransforms), + views: __assign(__assign({}, hooks0.views), hooks1.views), + viewPropsTransformers: hooks0.viewPropsTransformers.concat(hooks1.viewPropsTransformers), + isPropsValid: hooks1.isPropsValid || hooks0.isPropsValid, + externalDefTransforms: hooks0.externalDefTransforms.concat(hooks1.externalDefTransforms), + viewContainerAppends: hooks0.viewContainerAppends.concat(hooks1.viewContainerAppends), + eventDropTransformers: hooks0.eventDropTransformers.concat(hooks1.eventDropTransformers), + calendarInteractions: hooks0.calendarInteractions.concat(hooks1.calendarInteractions), + componentInteractions: hooks0.componentInteractions.concat(hooks1.componentInteractions), + themeClasses: __assign(__assign({}, hooks0.themeClasses), hooks1.themeClasses), + eventSourceDefs: hooks0.eventSourceDefs.concat(hooks1.eventSourceDefs), + cmdFormatter: hooks1.cmdFormatter || hooks0.cmdFormatter, + recurringTypes: hooks0.recurringTypes.concat(hooks1.recurringTypes), + namedTimeZonedImpl: hooks1.namedTimeZonedImpl || hooks0.namedTimeZonedImpl, + initialView: hooks0.initialView || hooks1.initialView, + elementDraggingImpl: hooks0.elementDraggingImpl || hooks1.elementDraggingImpl, + optionChangeHandlers: __assign(__assign({}, hooks0.optionChangeHandlers), hooks1.optionChangeHandlers), + scrollGridImpl: hooks1.scrollGridImpl || hooks0.scrollGridImpl, + contentTypeHandlers: __assign(__assign({}, hooks0.contentTypeHandlers), hooks1.contentTypeHandlers), + listenerRefiners: __assign(__assign({}, hooks0.listenerRefiners), hooks1.listenerRefiners), + optionRefiners: __assign(__assign({}, hooks0.optionRefiners), hooks1.optionRefiners), + propSetHandlers: __assign(__assign({}, hooks0.propSetHandlers), hooks1.propSetHandlers), + }; + } + + var StandardTheme = /** @class */ (function (_super) { + __extends(StandardTheme, _super); + function StandardTheme() { + return _super !== null && _super.apply(this, arguments) || this; + } + return StandardTheme; + }(Theme)); + StandardTheme.prototype.classes = { + root: 'fc-theme-standard', + tableCellShaded: 'fc-cell-shaded', + buttonGroup: 'fc-button-group', + button: 'fc-button fc-button-primary', + buttonActive: 'fc-button-active', + }; + StandardTheme.prototype.baseIconClass = 'fc-icon'; + StandardTheme.prototype.iconClasses = { + close: 'fc-icon-x', + prev: 'fc-icon-chevron-left', + next: 'fc-icon-chevron-right', + prevYear: 'fc-icon-chevrons-left', + nextYear: 'fc-icon-chevrons-right', + }; + StandardTheme.prototype.rtlIconClasses = { + prev: 'fc-icon-chevron-right', + next: 'fc-icon-chevron-left', + prevYear: 'fc-icon-chevrons-right', + nextYear: 'fc-icon-chevrons-left', + }; + StandardTheme.prototype.iconOverrideOption = 'buttonIcons'; // TODO: make TS-friendly + StandardTheme.prototype.iconOverrideCustomButtonOption = 'icon'; + StandardTheme.prototype.iconOverridePrefix = 'fc-icon-'; + + function compileViewDefs(defaultConfigs, overrideConfigs) { + var hash = {}; + var viewType; + for (viewType in defaultConfigs) { + ensureViewDef(viewType, hash, defaultConfigs, overrideConfigs); + } + for (viewType in overrideConfigs) { + ensureViewDef(viewType, hash, defaultConfigs, overrideConfigs); + } + return hash; + } + function ensureViewDef(viewType, hash, defaultConfigs, overrideConfigs) { + if (hash[viewType]) { + return hash[viewType]; + } + var viewDef = buildViewDef(viewType, hash, defaultConfigs, overrideConfigs); + if (viewDef) { + hash[viewType] = viewDef; + } + return viewDef; + } + function buildViewDef(viewType, hash, defaultConfigs, overrideConfigs) { + var defaultConfig = defaultConfigs[viewType]; + var overrideConfig = overrideConfigs[viewType]; + var queryProp = function (name) { return ((defaultConfig && defaultConfig[name] !== null) ? defaultConfig[name] : + ((overrideConfig && overrideConfig[name] !== null) ? overrideConfig[name] : null)); }; + var theComponent = queryProp('component'); + var superType = queryProp('superType'); + var superDef = null; + if (superType) { + if (superType === viewType) { + throw new Error('Can\'t have a custom view type that references itself'); + } + superDef = ensureViewDef(superType, hash, defaultConfigs, overrideConfigs); + } + if (!theComponent && superDef) { + theComponent = superDef.component; + } + if (!theComponent) { + return null; // don't throw a warning, might be settings for a single-unit view + } + return { + type: viewType, + component: theComponent, + defaults: __assign(__assign({}, (superDef ? superDef.defaults : {})), (defaultConfig ? defaultConfig.rawOptions : {})), + overrides: __assign(__assign({}, (superDef ? superDef.overrides : {})), (overrideConfig ? overrideConfig.rawOptions : {})), + }; + } + + /* eslint max-classes-per-file: off */ + // NOTE: in JSX, you should always use this class with arg. otherwise, will default to any??? + var RenderHook = /** @class */ (function (_super) { + __extends(RenderHook, _super); + function RenderHook() { + var _this = _super !== null && _super.apply(this, arguments) || this; + _this.rootElRef = createRef(); + _this.handleRootEl = function (el) { + setRef(_this.rootElRef, el); + if (_this.props.elRef) { + setRef(_this.props.elRef, el); + } + }; + return _this; + } + RenderHook.prototype.render = function () { + var _this = this; + var props = this.props; + var hookProps = props.hookProps; + return (createElement(MountHook, { hookProps: hookProps, didMount: props.didMount, willUnmount: props.willUnmount, elRef: this.handleRootEl }, function (rootElRef) { return (createElement(ContentHook, { hookProps: hookProps, content: props.content, defaultContent: props.defaultContent, backupElRef: _this.rootElRef }, function (innerElRef, innerContent) { return props.children(rootElRef, normalizeClassNames(props.classNames, hookProps), innerElRef, innerContent); })); })); + }; + return RenderHook; + }(BaseComponent)); + // TODO: rename to be about function, not default. use in above type + // for forcing rerender of components that use the ContentHook + var CustomContentRenderContext = createContext(0); + function ContentHook(props) { + return (createElement(CustomContentRenderContext.Consumer, null, function (renderId) { return (createElement(ContentHookInner, __assign({ renderId: renderId }, props))); })); + } + var ContentHookInner = /** @class */ (function (_super) { + __extends(ContentHookInner, _super); + function ContentHookInner() { + var _this = _super !== null && _super.apply(this, arguments) || this; + _this.innerElRef = createRef(); + return _this; + } + ContentHookInner.prototype.render = function () { + return this.props.children(this.innerElRef, this.renderInnerContent()); + }; + ContentHookInner.prototype.componentDidMount = function () { + this.updateCustomContent(); + }; + ContentHookInner.prototype.componentDidUpdate = function () { + this.updateCustomContent(); + }; + ContentHookInner.prototype.componentWillUnmount = function () { + if (this.customContentInfo && this.customContentInfo.destroy) { + this.customContentInfo.destroy(); + } + }; + ContentHookInner.prototype.renderInnerContent = function () { + var customContentInfo = this.customContentInfo; // only populated if using non-[p]react node(s) + var innerContent = this.getInnerContent(); + var meta = this.getContentMeta(innerContent); + // initial run, or content-type changing? (from vue -> react for example) + if (!customContentInfo || customContentInfo.contentKey !== meta.contentKey) { + // clearing old value + if (customContentInfo) { + if (customContentInfo.destroy) { + customContentInfo.destroy(); + } + customContentInfo = this.customContentInfo = null; + } + // assigning new value + if (meta.contentKey) { + customContentInfo = this.customContentInfo = __assign({ contentKey: meta.contentKey, contentVal: innerContent[meta.contentKey] }, meta.buildLifecycleFuncs()); + } + // updating + } + else if (customContentInfo) { + customContentInfo.contentVal = innerContent[meta.contentKey]; + } + return customContentInfo + ? [] // signal that something was specified + : innerContent; // assume a [p]react vdom node. use it + }; + ContentHookInner.prototype.getInnerContent = function () { + var props = this.props; + var innerContent = normalizeContent(props.content, props.hookProps); + if (innerContent === undefined) { // use the default + innerContent = normalizeContent(props.defaultContent, props.hookProps); + } + return innerContent == null ? null : innerContent; // convert undefined to null (better for React) + }; + ContentHookInner.prototype.getContentMeta = function (innerContent) { + var contentTypeHandlers = this.context.pluginHooks.contentTypeHandlers; + var contentKey = ''; + var buildLifecycleFuncs = null; + if (innerContent) { // allowed to be null, for convenience to caller + for (var searchKey in contentTypeHandlers) { + if (innerContent[searchKey] !== undefined) { + contentKey = searchKey; + buildLifecycleFuncs = contentTypeHandlers[searchKey]; + break; + } + } + } + return { contentKey: contentKey, buildLifecycleFuncs: buildLifecycleFuncs }; + }; + ContentHookInner.prototype.updateCustomContent = function () { + if (this.customContentInfo) { // for non-[p]react + this.customContentInfo.render(this.innerElRef.current || this.props.backupElRef.current, // the element to render into + this.customContentInfo.contentVal); + } + }; + return ContentHookInner; + }(BaseComponent)); + var MountHook = /** @class */ (function (_super) { + __extends(MountHook, _super); + function MountHook() { + var _this = _super !== null && _super.apply(this, arguments) || this; + _this.handleRootEl = function (rootEl) { + _this.rootEl = rootEl; + if (_this.props.elRef) { + setRef(_this.props.elRef, rootEl); + } + }; + return _this; + } + MountHook.prototype.render = function () { + return this.props.children(this.handleRootEl); + }; + MountHook.prototype.componentDidMount = function () { + var callback = this.props.didMount; + if (callback) { + callback(__assign(__assign({}, this.props.hookProps), { el: this.rootEl })); + } + }; + MountHook.prototype.componentWillUnmount = function () { + var callback = this.props.willUnmount; + if (callback) { + callback(__assign(__assign({}, this.props.hookProps), { el: this.rootEl })); + } + }; + return MountHook; + }(BaseComponent)); + function buildClassNameNormalizer() { + var currentGenerator; + var currentHookProps; + var currentClassNames = []; + return function (generator, hookProps) { + if (!currentHookProps || !isPropsEqual(currentHookProps, hookProps) || generator !== currentGenerator) { + currentGenerator = generator; + currentHookProps = hookProps; + currentClassNames = normalizeClassNames(generator, hookProps); + } + return currentClassNames; + }; + } + function normalizeClassNames(classNames, hookProps) { + if (typeof classNames === 'function') { + classNames = classNames(hookProps); + } + return parseClassNames(classNames); + } + function normalizeContent(input, hookProps) { + if (typeof input === 'function') { + return input(hookProps, createElement); // give the function the vdom-creation func + } + return input; + } + + var ViewRoot = /** @class */ (function (_super) { + __extends(ViewRoot, _super); + function ViewRoot() { + var _this = _super !== null && _super.apply(this, arguments) || this; + _this.normalizeClassNames = buildClassNameNormalizer(); + return _this; + } + ViewRoot.prototype.render = function () { + var _a = this, props = _a.props, context = _a.context; + var options = context.options; + var hookProps = { view: context.viewApi }; + var customClassNames = this.normalizeClassNames(options.viewClassNames, hookProps); + return (createElement(MountHook, { hookProps: hookProps, didMount: options.viewDidMount, willUnmount: options.viewWillUnmount, elRef: props.elRef }, function (rootElRef) { return props.children(rootElRef, ["fc-" + props.viewSpec.type + "-view", 'fc-view'].concat(customClassNames)); })); + }; + return ViewRoot; + }(BaseComponent)); + + function parseViewConfigs(inputs) { + return mapHash(inputs, parseViewConfig); + } + function parseViewConfig(input) { + var rawOptions = typeof input === 'function' ? + { component: input } : + input; + var component = rawOptions.component; + if (rawOptions.content) { + component = createViewHookComponent(rawOptions); + // TODO: remove content/classNames/didMount/etc from options? + } + return { + superType: rawOptions.type, + component: component, + rawOptions: rawOptions, + }; + } + function createViewHookComponent(options) { + return function (viewProps) { return (createElement(ViewContextType.Consumer, null, function (context) { return (createElement(ViewRoot, { viewSpec: context.viewSpec }, function (viewElRef, viewClassNames) { + var hookProps = __assign(__assign({}, viewProps), { nextDayThreshold: context.options.nextDayThreshold }); + return (createElement(RenderHook, { hookProps: hookProps, classNames: options.classNames, content: options.content, didMount: options.didMount, willUnmount: options.willUnmount, elRef: viewElRef }, function (rootElRef, customClassNames, innerElRef, innerContent) { return (createElement("div", { className: viewClassNames.concat(customClassNames).join(' '), ref: rootElRef }, innerContent)); })); + })); })); }; + } + + function buildViewSpecs(defaultInputs, optionOverrides, dynamicOptionOverrides, localeDefaults) { + var defaultConfigs = parseViewConfigs(defaultInputs); + var overrideConfigs = parseViewConfigs(optionOverrides.views); + var viewDefs = compileViewDefs(defaultConfigs, overrideConfigs); + return mapHash(viewDefs, function (viewDef) { return buildViewSpec(viewDef, overrideConfigs, optionOverrides, dynamicOptionOverrides, localeDefaults); }); + } + function buildViewSpec(viewDef, overrideConfigs, optionOverrides, dynamicOptionOverrides, localeDefaults) { + var durationInput = viewDef.overrides.duration || + viewDef.defaults.duration || + dynamicOptionOverrides.duration || + optionOverrides.duration; + var duration = null; + var durationUnit = ''; + var singleUnit = ''; + var singleUnitOverrides = {}; + if (durationInput) { + duration = createDurationCached(durationInput); + if (duration) { // valid? + var denom = greatestDurationDenominator(duration); + durationUnit = denom.unit; + if (denom.value === 1) { + singleUnit = durationUnit; + singleUnitOverrides = overrideConfigs[durationUnit] ? overrideConfigs[durationUnit].rawOptions : {}; + } + } + } + var queryButtonText = function (optionsSubset) { + var buttonTextMap = optionsSubset.buttonText || {}; + var buttonTextKey = viewDef.defaults.buttonTextKey; + if (buttonTextKey != null && buttonTextMap[buttonTextKey] != null) { + return buttonTextMap[buttonTextKey]; + } + if (buttonTextMap[viewDef.type] != null) { + return buttonTextMap[viewDef.type]; + } + if (buttonTextMap[singleUnit] != null) { + return buttonTextMap[singleUnit]; + } + return null; + }; + return { + type: viewDef.type, + component: viewDef.component, + duration: duration, + durationUnit: durationUnit, + singleUnit: singleUnit, + optionDefaults: viewDef.defaults, + optionOverrides: __assign(__assign({}, singleUnitOverrides), viewDef.overrides), + buttonTextOverride: queryButtonText(dynamicOptionOverrides) || + queryButtonText(optionOverrides) || // constructor-specified buttonText lookup hash takes precedence + viewDef.overrides.buttonText, + buttonTextDefault: queryButtonText(localeDefaults) || + viewDef.defaults.buttonText || + queryButtonText(BASE_OPTION_DEFAULTS) || + viewDef.type, // fall back to given view name + }; + } + // hack to get memoization working + var durationInputMap = {}; + function createDurationCached(durationInput) { + var json = JSON.stringify(durationInput); + var res = durationInputMap[json]; + if (res === undefined) { + res = createDuration(durationInput); + durationInputMap[json] = res; + } + return res; + } + + var DateProfileGenerator = /** @class */ (function () { + function DateProfileGenerator(props) { + this.props = props; + this.nowDate = getNow(props.nowInput, props.dateEnv); + this.initHiddenDays(); + } + /* Date Range Computation + ------------------------------------------------------------------------------------------------------------------*/ + // Builds a structure with info about what the dates/ranges will be for the "prev" view. + DateProfileGenerator.prototype.buildPrev = function (currentDateProfile, currentDate, forceToValid) { + var dateEnv = this.props.dateEnv; + var prevDate = dateEnv.subtract(dateEnv.startOf(currentDate, currentDateProfile.currentRangeUnit), // important for start-of-month + currentDateProfile.dateIncrement); + return this.build(prevDate, -1, forceToValid); + }; + // Builds a structure with info about what the dates/ranges will be for the "next" view. + DateProfileGenerator.prototype.buildNext = function (currentDateProfile, currentDate, forceToValid) { + var dateEnv = this.props.dateEnv; + var nextDate = dateEnv.add(dateEnv.startOf(currentDate, currentDateProfile.currentRangeUnit), // important for start-of-month + currentDateProfile.dateIncrement); + return this.build(nextDate, 1, forceToValid); + }; + // Builds a structure holding dates/ranges for rendering around the given date. + // Optional direction param indicates whether the date is being incremented/decremented + // from its previous value. decremented = -1, incremented = 1 (default). + DateProfileGenerator.prototype.build = function (currentDate, direction, forceToValid) { + if (forceToValid === void 0) { forceToValid = true; } + var props = this.props; + var validRange; + var currentInfo; + var isRangeAllDay; + var renderRange; + var activeRange; + var isValid; + validRange = this.buildValidRange(); + validRange = this.trimHiddenDays(validRange); + if (forceToValid) { + currentDate = constrainMarkerToRange(currentDate, validRange); + } + currentInfo = this.buildCurrentRangeInfo(currentDate, direction); + isRangeAllDay = /^(year|month|week|day)$/.test(currentInfo.unit); + renderRange = this.buildRenderRange(this.trimHiddenDays(currentInfo.range), currentInfo.unit, isRangeAllDay); + renderRange = this.trimHiddenDays(renderRange); + activeRange = renderRange; + if (!props.showNonCurrentDates) { + activeRange = intersectRanges(activeRange, currentInfo.range); + } + activeRange = this.adjustActiveRange(activeRange); + activeRange = intersectRanges(activeRange, validRange); // might return null + // it's invalid if the originally requested date is not contained, + // or if the range is completely outside of the valid range. + isValid = rangesIntersect(currentInfo.range, validRange); + return { + // constraint for where prev/next operations can go and where events can be dragged/resized to. + // an object with optional start and end properties. + validRange: validRange, + // range the view is formally responsible for. + // for example, a month view might have 1st-31st, excluding padded dates + currentRange: currentInfo.range, + // name of largest unit being displayed, like "month" or "week" + currentRangeUnit: currentInfo.unit, + isRangeAllDay: isRangeAllDay, + // dates that display events and accept drag-n-drop + // will be `null` if no dates accept events + activeRange: activeRange, + // date range with a rendered skeleton + // includes not-active days that need some sort of DOM + renderRange: renderRange, + // Duration object that denotes the first visible time of any given day + slotMinTime: props.slotMinTime, + // Duration object that denotes the exclusive visible end time of any given day + slotMaxTime: props.slotMaxTime, + isValid: isValid, + // how far the current date will move for a prev/next operation + dateIncrement: this.buildDateIncrement(currentInfo.duration), + // pass a fallback (might be null) ^ + }; + }; + // Builds an object with optional start/end properties. + // Indicates the minimum/maximum dates to display. + // not responsible for trimming hidden days. + DateProfileGenerator.prototype.buildValidRange = function () { + var input = this.props.validRangeInput; + var simpleInput = typeof input === 'function' + ? input.call(this.props.calendarApi, this.nowDate) + : input; + return this.refineRange(simpleInput) || + { start: null, end: null }; // completely open-ended + }; + // Builds a structure with info about the "current" range, the range that is + // highlighted as being the current month for example. + // See build() for a description of `direction`. + // Guaranteed to have `range` and `unit` properties. `duration` is optional. + DateProfileGenerator.prototype.buildCurrentRangeInfo = function (date, direction) { + var props = this.props; + var duration = null; + var unit = null; + var range = null; + var dayCount; + if (props.duration) { + duration = props.duration; + unit = props.durationUnit; + range = this.buildRangeFromDuration(date, direction, duration, unit); + } + else if ((dayCount = this.props.dayCount)) { + unit = 'day'; + range = this.buildRangeFromDayCount(date, direction, dayCount); + } + else if ((range = this.buildCustomVisibleRange(date))) { + unit = props.dateEnv.greatestWholeUnit(range.start, range.end).unit; + } + else { + duration = this.getFallbackDuration(); + unit = greatestDurationDenominator(duration).unit; + range = this.buildRangeFromDuration(date, direction, duration, unit); + } + return { duration: duration, unit: unit, range: range }; + }; + DateProfileGenerator.prototype.getFallbackDuration = function () { + return createDuration({ day: 1 }); + }; + // Returns a new activeRange to have time values (un-ambiguate) + // slotMinTime or slotMaxTime causes the range to expand. + DateProfileGenerator.prototype.adjustActiveRange = function (range) { + var _a = this.props, dateEnv = _a.dateEnv, usesMinMaxTime = _a.usesMinMaxTime, slotMinTime = _a.slotMinTime, slotMaxTime = _a.slotMaxTime; + var start = range.start, end = range.end; + if (usesMinMaxTime) { + // expand active range if slotMinTime is negative (why not when positive?) + if (asRoughDays(slotMinTime) < 0) { + start = startOfDay(start); // necessary? + start = dateEnv.add(start, slotMinTime); + } + // expand active range if slotMaxTime is beyond one day (why not when negative?) + if (asRoughDays(slotMaxTime) > 1) { + end = startOfDay(end); // necessary? + end = addDays(end, -1); + end = dateEnv.add(end, slotMaxTime); + } + } + return { start: start, end: end }; + }; + // Builds the "current" range when it is specified as an explicit duration. + // `unit` is the already-computed greatestDurationDenominator unit of duration. + DateProfileGenerator.prototype.buildRangeFromDuration = function (date, direction, duration, unit) { + var _a = this.props, dateEnv = _a.dateEnv, dateAlignment = _a.dateAlignment; + var start; + var end; + var res; + // compute what the alignment should be + if (!dateAlignment) { + var dateIncrement = this.props.dateIncrement; + if (dateIncrement) { + // use the smaller of the two units + if (asRoughMs(dateIncrement) < asRoughMs(duration)) { + dateAlignment = greatestDurationDenominator(dateIncrement).unit; + } + else { + dateAlignment = unit; + } + } + else { + dateAlignment = unit; + } + } + // if the view displays a single day or smaller + if (asRoughDays(duration) <= 1) { + if (this.isHiddenDay(start)) { + start = this.skipHiddenDays(start, direction); + start = startOfDay(start); + } + } + function computeRes() { + start = dateEnv.startOf(date, dateAlignment); + end = dateEnv.add(start, duration); + res = { start: start, end: end }; + } + computeRes(); + // if range is completely enveloped by hidden days, go past the hidden days + if (!this.trimHiddenDays(res)) { + date = this.skipHiddenDays(date, direction); + computeRes(); + } + return res; + }; + // Builds the "current" range when a dayCount is specified. + DateProfileGenerator.prototype.buildRangeFromDayCount = function (date, direction, dayCount) { + var _a = this.props, dateEnv = _a.dateEnv, dateAlignment = _a.dateAlignment; + var runningCount = 0; + var start = date; + var end; + if (dateAlignment) { + start = dateEnv.startOf(start, dateAlignment); + } + start = startOfDay(start); + start = this.skipHiddenDays(start, direction); + end = start; + do { + end = addDays(end, 1); + if (!this.isHiddenDay(end)) { + runningCount += 1; + } + } while (runningCount < dayCount); + return { start: start, end: end }; + }; + // Builds a normalized range object for the "visible" range, + // which is a way to define the currentRange and activeRange at the same time. + DateProfileGenerator.prototype.buildCustomVisibleRange = function (date) { + var props = this.props; + var input = props.visibleRangeInput; + var simpleInput = typeof input === 'function' + ? input.call(props.calendarApi, props.dateEnv.toDate(date)) + : input; + var range = this.refineRange(simpleInput); + if (range && (range.start == null || range.end == null)) { + return null; + } + return range; + }; + // Computes the range that will represent the element/cells for *rendering*, + // but which may have voided days/times. + // not responsible for trimming hidden days. + DateProfileGenerator.prototype.buildRenderRange = function (currentRange, currentRangeUnit, isRangeAllDay) { + return currentRange; + }; + // Compute the duration value that should be added/substracted to the current date + // when a prev/next operation happens. + DateProfileGenerator.prototype.buildDateIncrement = function (fallback) { + var dateIncrement = this.props.dateIncrement; + var customAlignment; + if (dateIncrement) { + return dateIncrement; + } + if ((customAlignment = this.props.dateAlignment)) { + return createDuration(1, customAlignment); + } + if (fallback) { + return fallback; + } + return createDuration({ days: 1 }); + }; + DateProfileGenerator.prototype.refineRange = function (rangeInput) { + if (rangeInput) { + var range = parseRange(rangeInput, this.props.dateEnv); + if (range) { + range = computeVisibleDayRange(range); + } + return range; + } + return null; + }; + /* Hidden Days + ------------------------------------------------------------------------------------------------------------------*/ + // Initializes internal variables related to calculating hidden days-of-week + DateProfileGenerator.prototype.initHiddenDays = function () { + var hiddenDays = this.props.hiddenDays || []; // array of day-of-week indices that are hidden + var isHiddenDayHash = []; // is the day-of-week hidden? (hash with day-of-week-index -> bool) + var dayCnt = 0; + var i; + if (this.props.weekends === false) { + hiddenDays.push(0, 6); // 0=sunday, 6=saturday + } + for (i = 0; i < 7; i += 1) { + if (!(isHiddenDayHash[i] = hiddenDays.indexOf(i) !== -1)) { + dayCnt += 1; + } + } + if (!dayCnt) { + throw new Error('invalid hiddenDays'); // all days were hidden? bad. + } + this.isHiddenDayHash = isHiddenDayHash; + }; + // Remove days from the beginning and end of the range that are computed as hidden. + // If the whole range is trimmed off, returns null + DateProfileGenerator.prototype.trimHiddenDays = function (range) { + var start = range.start, end = range.end; + if (start) { + start = this.skipHiddenDays(start); + } + if (end) { + end = this.skipHiddenDays(end, -1, true); + } + if (start == null || end == null || start < end) { + return { start: start, end: end }; + } + return null; + }; + // Is the current day hidden? + // `day` is a day-of-week index (0-6), or a Date (used for UTC) + DateProfileGenerator.prototype.isHiddenDay = function (day) { + if (day instanceof Date) { + day = day.getUTCDay(); + } + return this.isHiddenDayHash[day]; + }; + // Incrementing the current day until it is no longer a hidden day, returning a copy. + // DOES NOT CONSIDER validRange! + // If the initial value of `date` is not a hidden day, don't do anything. + // Pass `isExclusive` as `true` if you are dealing with an end date. + // `inc` defaults to `1` (increment one day forward each time) + DateProfileGenerator.prototype.skipHiddenDays = function (date, inc, isExclusive) { + if (inc === void 0) { inc = 1; } + if (isExclusive === void 0) { isExclusive = false; } + while (this.isHiddenDayHash[(date.getUTCDay() + (isExclusive ? inc : 0) + 7) % 7]) { + date = addDays(date, inc); + } + return date; + }; + return DateProfileGenerator; + }()); + + function reduceViewType(viewType, action) { + switch (action.type) { + case 'CHANGE_VIEW_TYPE': + viewType = action.viewType; + } + return viewType; + } + + function reduceDynamicOptionOverrides(dynamicOptionOverrides, action) { + var _a; + switch (action.type) { + case 'SET_OPTION': + return __assign(__assign({}, dynamicOptionOverrides), (_a = {}, _a[action.optionName] = action.rawOptionValue, _a)); + default: + return dynamicOptionOverrides; + } + } + + function reduceDateProfile(currentDateProfile, action, currentDate, dateProfileGenerator) { + var dp; + switch (action.type) { + case 'CHANGE_VIEW_TYPE': + return dateProfileGenerator.build(action.dateMarker || currentDate); + case 'CHANGE_DATE': + return dateProfileGenerator.build(action.dateMarker); + case 'PREV': + dp = dateProfileGenerator.buildPrev(currentDateProfile, currentDate); + if (dp.isValid) { + return dp; + } + break; + case 'NEXT': + dp = dateProfileGenerator.buildNext(currentDateProfile, currentDate); + if (dp.isValid) { + return dp; + } + break; + } + return currentDateProfile; + } + + function initEventSources(calendarOptions, dateProfile, context) { + var activeRange = dateProfile ? dateProfile.activeRange : null; + return addSources({}, parseInitialSources(calendarOptions, context), activeRange, context); + } + function reduceEventSources(eventSources, action, dateProfile, context) { + var activeRange = dateProfile ? dateProfile.activeRange : null; // need this check? + switch (action.type) { + case 'ADD_EVENT_SOURCES': // already parsed + return addSources(eventSources, action.sources, activeRange, context); + case 'REMOVE_EVENT_SOURCE': + return removeSource(eventSources, action.sourceId); + case 'PREV': // TODO: how do we track all actions that affect dateProfile :( + case 'NEXT': + case 'CHANGE_DATE': + case 'CHANGE_VIEW_TYPE': + if (dateProfile) { + return fetchDirtySources(eventSources, activeRange, context); + } + return eventSources; + case 'FETCH_EVENT_SOURCES': + return fetchSourcesByIds(eventSources, action.sourceIds ? // why no type? + arrayToHash(action.sourceIds) : + excludeStaticSources(eventSources, context), activeRange, action.isRefetch || false, context); + case 'RECEIVE_EVENTS': + case 'RECEIVE_EVENT_ERROR': + return receiveResponse(eventSources, action.sourceId, action.fetchId, action.fetchRange); + case 'REMOVE_ALL_EVENT_SOURCES': + return {}; + default: + return eventSources; + } + } + function reduceEventSourcesNewTimeZone(eventSources, dateProfile, context) { + var activeRange = dateProfile ? dateProfile.activeRange : null; // need this check? + return fetchSourcesByIds(eventSources, excludeStaticSources(eventSources, context), activeRange, true, context); + } + function computeEventSourcesLoading(eventSources) { + for (var sourceId in eventSources) { + if (eventSources[sourceId].isFetching) { + return true; + } + } + return false; + } + function addSources(eventSourceHash, sources, fetchRange, context) { + var hash = {}; + for (var _i = 0, sources_1 = sources; _i < sources_1.length; _i++) { + var source = sources_1[_i]; + hash[source.sourceId] = source; + } + if (fetchRange) { + hash = fetchDirtySources(hash, fetchRange, context); + } + return __assign(__assign({}, eventSourceHash), hash); + } + function removeSource(eventSourceHash, sourceId) { + return filterHash(eventSourceHash, function (eventSource) { return eventSource.sourceId !== sourceId; }); + } + function fetchDirtySources(sourceHash, fetchRange, context) { + return fetchSourcesByIds(sourceHash, filterHash(sourceHash, function (eventSource) { return isSourceDirty(eventSource, fetchRange, context); }), fetchRange, false, context); + } + function isSourceDirty(eventSource, fetchRange, context) { + if (!doesSourceNeedRange(eventSource, context)) { + return !eventSource.latestFetchId; + } + return !context.options.lazyFetching || + !eventSource.fetchRange || + eventSource.isFetching || // always cancel outdated in-progress fetches + fetchRange.start < eventSource.fetchRange.start || + fetchRange.end > eventSource.fetchRange.end; + } + function fetchSourcesByIds(prevSources, sourceIdHash, fetchRange, isRefetch, context) { + var nextSources = {}; + for (var sourceId in prevSources) { + var source = prevSources[sourceId]; + if (sourceIdHash[sourceId]) { + nextSources[sourceId] = fetchSource(source, fetchRange, isRefetch, context); + } + else { + nextSources[sourceId] = source; + } + } + return nextSources; + } + function fetchSource(eventSource, fetchRange, isRefetch, context) { + var options = context.options, calendarApi = context.calendarApi; + var sourceDef = context.pluginHooks.eventSourceDefs[eventSource.sourceDefId]; + var fetchId = guid(); + sourceDef.fetch({ + eventSource: eventSource, + range: fetchRange, + isRefetch: isRefetch, + context: context, + }, function (res) { + var rawEvents = res.rawEvents; + if (options.eventSourceSuccess) { + rawEvents = options.eventSourceSuccess.call(calendarApi, rawEvents, res.xhr) || rawEvents; + } + if (eventSource.success) { + rawEvents = eventSource.success.call(calendarApi, rawEvents, res.xhr) || rawEvents; + } + context.dispatch({ + type: 'RECEIVE_EVENTS', + sourceId: eventSource.sourceId, + fetchId: fetchId, + fetchRange: fetchRange, + rawEvents: rawEvents, + }); + }, function (error) { + console.warn(error.message, error); + if (options.eventSourceFailure) { + options.eventSourceFailure.call(calendarApi, error); + } + if (eventSource.failure) { + eventSource.failure(error); + } + context.dispatch({ + type: 'RECEIVE_EVENT_ERROR', + sourceId: eventSource.sourceId, + fetchId: fetchId, + fetchRange: fetchRange, + error: error, + }); + }); + return __assign(__assign({}, eventSource), { isFetching: true, latestFetchId: fetchId }); + } + function receiveResponse(sourceHash, sourceId, fetchId, fetchRange) { + var _a; + var eventSource = sourceHash[sourceId]; + if (eventSource && // not already removed + fetchId === eventSource.latestFetchId) { + return __assign(__assign({}, sourceHash), (_a = {}, _a[sourceId] = __assign(__assign({}, eventSource), { isFetching: false, fetchRange: fetchRange }), _a)); + } + return sourceHash; + } + function excludeStaticSources(eventSources, context) { + return filterHash(eventSources, function (eventSource) { return doesSourceNeedRange(eventSource, context); }); + } + function parseInitialSources(rawOptions, context) { + var refiners = buildEventSourceRefiners(context); + var rawSources = [].concat(rawOptions.eventSources || []); + var sources = []; // parsed + if (rawOptions.initialEvents) { + rawSources.unshift(rawOptions.initialEvents); + } + if (rawOptions.events) { + rawSources.unshift(rawOptions.events); + } + for (var _i = 0, rawSources_1 = rawSources; _i < rawSources_1.length; _i++) { + var rawSource = rawSources_1[_i]; + var source = parseEventSource(rawSource, context, refiners); + if (source) { + sources.push(source); + } + } + return sources; + } + function doesSourceNeedRange(eventSource, context) { + var defs = context.pluginHooks.eventSourceDefs; + return !defs[eventSource.sourceDefId].ignoreRange; + } + + function reduceEventStore(eventStore, action, eventSources, dateProfile, context) { + switch (action.type) { + case 'RECEIVE_EVENTS': // raw + return receiveRawEvents(eventStore, eventSources[action.sourceId], action.fetchId, action.fetchRange, action.rawEvents, context); + case 'ADD_EVENTS': // already parsed, but not expanded + return addEvent(eventStore, action.eventStore, // new ones + dateProfile ? dateProfile.activeRange : null, context); + case 'RESET_EVENTS': + return action.eventStore; + case 'MERGE_EVENTS': // already parsed and expanded + return mergeEventStores(eventStore, action.eventStore); + case 'PREV': // TODO: how do we track all actions that affect dateProfile :( + case 'NEXT': + case 'CHANGE_DATE': + case 'CHANGE_VIEW_TYPE': + if (dateProfile) { + return expandRecurring(eventStore, dateProfile.activeRange, context); + } + return eventStore; + case 'REMOVE_EVENTS': + return excludeSubEventStore(eventStore, action.eventStore); + case 'REMOVE_EVENT_SOURCE': + return excludeEventsBySourceId(eventStore, action.sourceId); + case 'REMOVE_ALL_EVENT_SOURCES': + return filterEventStoreDefs(eventStore, function (eventDef) { return (!eventDef.sourceId // only keep events with no source id + ); }); + case 'REMOVE_ALL_EVENTS': + return createEmptyEventStore(); + default: + return eventStore; + } + } + function receiveRawEvents(eventStore, eventSource, fetchId, fetchRange, rawEvents, context) { + if (eventSource && // not already removed + fetchId === eventSource.latestFetchId // TODO: wish this logic was always in event-sources + ) { + var subset = parseEvents(transformRawEvents(rawEvents, eventSource, context), eventSource, context); + if (fetchRange) { + subset = expandRecurring(subset, fetchRange, context); + } + return mergeEventStores(excludeEventsBySourceId(eventStore, eventSource.sourceId), subset); + } + return eventStore; + } + function transformRawEvents(rawEvents, eventSource, context) { + var calEachTransform = context.options.eventDataTransform; + var sourceEachTransform = eventSource ? eventSource.eventDataTransform : null; + if (sourceEachTransform) { + rawEvents = transformEachRawEvent(rawEvents, sourceEachTransform); + } + if (calEachTransform) { + rawEvents = transformEachRawEvent(rawEvents, calEachTransform); + } + return rawEvents; + } + function transformEachRawEvent(rawEvents, func) { + var refinedEvents; + if (!func) { + refinedEvents = rawEvents; + } + else { + refinedEvents = []; + for (var _i = 0, rawEvents_1 = rawEvents; _i < rawEvents_1.length; _i++) { + var rawEvent = rawEvents_1[_i]; + var refinedEvent = func(rawEvent); + if (refinedEvent) { + refinedEvents.push(refinedEvent); + } + else if (refinedEvent == null) { + refinedEvents.push(rawEvent); + } // if a different falsy value, do nothing + } + } + return refinedEvents; + } + function addEvent(eventStore, subset, expandRange, context) { + if (expandRange) { + subset = expandRecurring(subset, expandRange, context); + } + return mergeEventStores(eventStore, subset); + } + function rezoneEventStoreDates(eventStore, oldDateEnv, newDateEnv) { + var defs = eventStore.defs; + var instances = mapHash(eventStore.instances, function (instance) { + var def = defs[instance.defId]; + if (def.allDay || def.recurringDef) { + return instance; // isn't dependent on timezone + } + return __assign(__assign({}, instance), { range: { + start: newDateEnv.createMarker(oldDateEnv.toDate(instance.range.start, instance.forcedStartTzo)), + end: newDateEnv.createMarker(oldDateEnv.toDate(instance.range.end, instance.forcedEndTzo)), + }, forcedStartTzo: newDateEnv.canComputeOffset ? null : instance.forcedStartTzo, forcedEndTzo: newDateEnv.canComputeOffset ? null : instance.forcedEndTzo }); + }); + return { defs: defs, instances: instances }; + } + function excludeEventsBySourceId(eventStore, sourceId) { + return filterEventStoreDefs(eventStore, function (eventDef) { return eventDef.sourceId !== sourceId; }); + } + // QUESTION: why not just return instances? do a general object-property-exclusion util + function excludeInstances(eventStore, removals) { + return { + defs: eventStore.defs, + instances: filterHash(eventStore.instances, function (instance) { return !removals[instance.instanceId]; }), + }; + } + + function reduceDateSelection(currentSelection, action) { + switch (action.type) { + case 'UNSELECT_DATES': + return null; + case 'SELECT_DATES': + return action.selection; + default: + return currentSelection; + } + } + + function reduceSelectedEvent(currentInstanceId, action) { + switch (action.type) { + case 'UNSELECT_EVENT': + return ''; + case 'SELECT_EVENT': + return action.eventInstanceId; + default: + return currentInstanceId; + } + } + + function reduceEventDrag(currentDrag, action) { + var newDrag; + switch (action.type) { + case 'UNSET_EVENT_DRAG': + return null; + case 'SET_EVENT_DRAG': + newDrag = action.state; + return { + affectedEvents: newDrag.affectedEvents, + mutatedEvents: newDrag.mutatedEvents, + isEvent: newDrag.isEvent, + }; + default: + return currentDrag; + } + } + + function reduceEventResize(currentResize, action) { + var newResize; + switch (action.type) { + case 'UNSET_EVENT_RESIZE': + return null; + case 'SET_EVENT_RESIZE': + newResize = action.state; + return { + affectedEvents: newResize.affectedEvents, + mutatedEvents: newResize.mutatedEvents, + isEvent: newResize.isEvent, + }; + default: + return currentResize; + } + } + + function parseToolbars(calendarOptions, calendarOptionOverrides, theme, viewSpecs, calendarApi) { + var viewsWithButtons = []; + var headerToolbar = calendarOptions.headerToolbar ? parseToolbar(calendarOptions.headerToolbar, calendarOptions, calendarOptionOverrides, theme, viewSpecs, calendarApi, viewsWithButtons) : null; + var footerToolbar = calendarOptions.footerToolbar ? parseToolbar(calendarOptions.footerToolbar, calendarOptions, calendarOptionOverrides, theme, viewSpecs, calendarApi, viewsWithButtons) : null; + return { headerToolbar: headerToolbar, footerToolbar: footerToolbar, viewsWithButtons: viewsWithButtons }; + } + function parseToolbar(sectionStrHash, calendarOptions, calendarOptionOverrides, theme, viewSpecs, calendarApi, viewsWithButtons) { + return mapHash(sectionStrHash, function (sectionStr) { return parseSection(sectionStr, calendarOptions, calendarOptionOverrides, theme, viewSpecs, calendarApi, viewsWithButtons); }); + } + /* + BAD: querying icons and text here. should be done at render time + */ + function parseSection(sectionStr, calendarOptions, calendarOptionOverrides, theme, viewSpecs, calendarApi, viewsWithButtons) { + var isRtl = calendarOptions.direction === 'rtl'; + var calendarCustomButtons = calendarOptions.customButtons || {}; + var calendarButtonTextOverrides = calendarOptionOverrides.buttonText || {}; + var calendarButtonText = calendarOptions.buttonText || {}; + var sectionSubstrs = sectionStr ? sectionStr.split(' ') : []; + return sectionSubstrs.map(function (buttonGroupStr) { return (buttonGroupStr.split(',').map(function (buttonName) { + if (buttonName === 'title') { + return { buttonName: buttonName }; + } + var customButtonProps; + var viewSpec; + var buttonClick; + var buttonIcon; // only one of these will be set + var buttonText; // " + if ((customButtonProps = calendarCustomButtons[buttonName])) { + buttonClick = function (ev) { + if (customButtonProps.click) { + customButtonProps.click.call(ev.target, ev, ev.target); // TODO: use Calendar this context? + } + }; + (buttonIcon = theme.getCustomButtonIconClass(customButtonProps)) || + (buttonIcon = theme.getIconClass(buttonName, isRtl)) || + (buttonText = customButtonProps.text); + } + else if ((viewSpec = viewSpecs[buttonName])) { + viewsWithButtons.push(buttonName); + buttonClick = function () { + calendarApi.changeView(buttonName); + }; + (buttonText = viewSpec.buttonTextOverride) || + (buttonIcon = theme.getIconClass(buttonName, isRtl)) || + (buttonText = viewSpec.buttonTextDefault); + } + else if (calendarApi[buttonName]) { // a calendarApi method + buttonClick = function () { + calendarApi[buttonName](); + }; + (buttonText = calendarButtonTextOverrides[buttonName]) || + (buttonIcon = theme.getIconClass(buttonName, isRtl)) || + (buttonText = calendarButtonText[buttonName]); + // ^ everything else is considered default + } + return { buttonName: buttonName, buttonClick: buttonClick, buttonIcon: buttonIcon, buttonText: buttonText }; + })); }); + } + + var eventSourceDef$3 = { + ignoreRange: true, + parseMeta: function (refined) { + if (Array.isArray(refined.events)) { + return refined.events; + } + return null; + }, + fetch: function (arg, success) { + success({ + rawEvents: arg.eventSource.meta, + }); + }, + }; + var arrayEventSourcePlugin = createPlugin({ + eventSourceDefs: [eventSourceDef$3], + }); + + var eventSourceDef$2 = { + parseMeta: function (refined) { + if (typeof refined.events === 'function') { + return refined.events; + } + return null; + }, + fetch: function (arg, success, failure) { + var dateEnv = arg.context.dateEnv; + var func = arg.eventSource.meta; + unpromisify(func.bind(null, buildRangeApiWithTimeZone(arg.range, dateEnv)), function (rawEvents) { + success({ rawEvents: rawEvents }); // needs an object response + }, failure); + }, + }; + var funcEventSourcePlugin = createPlugin({ + eventSourceDefs: [eventSourceDef$2], + }); + + function requestJson(method, url, params, successCallback, failureCallback) { + method = method.toUpperCase(); + var body = null; + if (method === 'GET') { + url = injectQueryStringParams(url, params); + } + else { + body = encodeParams(params); + } + var xhr = new XMLHttpRequest(); + xhr.open(method, url, true); + if (method !== 'GET') { + xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); + } + xhr.onload = function () { + if (xhr.status >= 200 && xhr.status < 400) { + var parsed = false; + var res = void 0; + try { + res = JSON.parse(xhr.responseText); + parsed = true; + } + catch (err) { + // will handle parsed=false + } + if (parsed) { + successCallback(res, xhr); + } + else { + failureCallback('Failure parsing JSON', xhr); + } + } + else { + failureCallback('Request failed', xhr); + } + }; + xhr.onerror = function () { + failureCallback('Request failed', xhr); + }; + xhr.send(body); + } + function injectQueryStringParams(url, params) { + return url + + (url.indexOf('?') === -1 ? '?' : '&') + + encodeParams(params); + } + function encodeParams(params) { + var parts = []; + for (var key in params) { + parts.push(encodeURIComponent(key) + "=" + encodeURIComponent(params[key])); + } + return parts.join('&'); + } + + var JSON_FEED_EVENT_SOURCE_REFINERS = { + method: String, + extraParams: identity, + startParam: String, + endParam: String, + timeZoneParam: String, + }; + + var eventSourceDef$1 = { + parseMeta: function (refined) { + if (refined.url && (refined.format === 'json' || !refined.format)) { + return { + url: refined.url, + format: 'json', + method: (refined.method || 'GET').toUpperCase(), + extraParams: refined.extraParams, + startParam: refined.startParam, + endParam: refined.endParam, + timeZoneParam: refined.timeZoneParam, + }; + } + return null; + }, + fetch: function (arg, success, failure) { + var meta = arg.eventSource.meta; + var requestParams = buildRequestParams$1(meta, arg.range, arg.context); + requestJson(meta.method, meta.url, requestParams, function (rawEvents, xhr) { + success({ rawEvents: rawEvents, xhr: xhr }); + }, function (errorMessage, xhr) { + failure({ message: errorMessage, xhr: xhr }); + }); + }, + }; + var jsonFeedEventSourcePlugin = createPlugin({ + eventSourceRefiners: JSON_FEED_EVENT_SOURCE_REFINERS, + eventSourceDefs: [eventSourceDef$1], + }); + function buildRequestParams$1(meta, range, context) { + var dateEnv = context.dateEnv, options = context.options; + var startParam; + var endParam; + var timeZoneParam; + var customRequestParams; + var params = {}; + startParam = meta.startParam; + if (startParam == null) { + startParam = options.startParam; + } + endParam = meta.endParam; + if (endParam == null) { + endParam = options.endParam; + } + timeZoneParam = meta.timeZoneParam; + if (timeZoneParam == null) { + timeZoneParam = options.timeZoneParam; + } + // retrieve any outbound GET/POST data from the options + if (typeof meta.extraParams === 'function') { + // supplied as a function that returns a key/value object + customRequestParams = meta.extraParams(); + } + else { + // probably supplied as a straight key/value object + customRequestParams = meta.extraParams || {}; + } + __assign(params, customRequestParams); + params[startParam] = dateEnv.formatIso(range.start); + params[endParam] = dateEnv.formatIso(range.end); + if (dateEnv.timeZone !== 'local') { + params[timeZoneParam] = dateEnv.timeZone; + } + return params; + } + + var SIMPLE_RECURRING_REFINERS = { + daysOfWeek: identity, + startTime: createDuration, + endTime: createDuration, + duration: createDuration, + startRecur: identity, + endRecur: identity, + }; + + var recurring = { + parse: function (refined, dateEnv) { + if (refined.daysOfWeek || refined.startTime || refined.endTime || refined.startRecur || refined.endRecur) { + var recurringData = { + daysOfWeek: refined.daysOfWeek || null, + startTime: refined.startTime || null, + endTime: refined.endTime || null, + startRecur: refined.startRecur ? dateEnv.createMarker(refined.startRecur) : null, + endRecur: refined.endRecur ? dateEnv.createMarker(refined.endRecur) : null, + }; + var duration = void 0; + if (refined.duration) { + duration = refined.duration; + } + if (!duration && refined.startTime && refined.endTime) { + duration = subtractDurations(refined.endTime, refined.startTime); + } + return { + allDayGuess: Boolean(!refined.startTime && !refined.endTime), + duration: duration, + typeData: recurringData, // doesn't need endTime anymore but oh well + }; + } + return null; + }, + expand: function (typeData, framingRange, dateEnv) { + var clippedFramingRange = intersectRanges(framingRange, { start: typeData.startRecur, end: typeData.endRecur }); + if (clippedFramingRange) { + return expandRanges(typeData.daysOfWeek, typeData.startTime, clippedFramingRange, dateEnv); + } + return []; + }, + }; + var simpleRecurringEventsPlugin = createPlugin({ + recurringTypes: [recurring], + eventRefiners: SIMPLE_RECURRING_REFINERS, + }); + function expandRanges(daysOfWeek, startTime, framingRange, dateEnv) { + var dowHash = daysOfWeek ? arrayToHash(daysOfWeek) : null; + var dayMarker = startOfDay(framingRange.start); + var endMarker = framingRange.end; + var instanceStarts = []; + while (dayMarker < endMarker) { + var instanceStart + // if everyday, or this particular day-of-week + = void 0; + // if everyday, or this particular day-of-week + if (!dowHash || dowHash[dayMarker.getUTCDay()]) { + if (startTime) { + instanceStart = dateEnv.add(dayMarker, startTime); + } + else { + instanceStart = dayMarker; + } + instanceStarts.push(instanceStart); + } + dayMarker = addDays(dayMarker, 1); + } + return instanceStarts; + } + + var changeHandlerPlugin = createPlugin({ + optionChangeHandlers: { + events: function (events, context) { + handleEventSources([events], context); + }, + eventSources: handleEventSources, + }, + }); + /* + BUG: if `event` was supplied, all previously-given `eventSources` will be wiped out + */ + function handleEventSources(inputs, context) { + var unfoundSources = hashValuesToArray(context.getCurrentData().eventSources); + var newInputs = []; + for (var _i = 0, inputs_1 = inputs; _i < inputs_1.length; _i++) { + var input = inputs_1[_i]; + var inputFound = false; + for (var i = 0; i < unfoundSources.length; i += 1) { + if (unfoundSources[i]._raw === input) { + unfoundSources.splice(i, 1); // delete + inputFound = true; + break; + } + } + if (!inputFound) { + newInputs.push(input); + } + } + for (var _a = 0, unfoundSources_1 = unfoundSources; _a < unfoundSources_1.length; _a++) { + var unfoundSource = unfoundSources_1[_a]; + context.dispatch({ + type: 'REMOVE_EVENT_SOURCE', + sourceId: unfoundSource.sourceId, + }); + } + for (var _b = 0, newInputs_1 = newInputs; _b < newInputs_1.length; _b++) { + var newInput = newInputs_1[_b]; + context.calendarApi.addEventSource(newInput); + } + } + + function handleDateProfile(dateProfile, context) { + context.emitter.trigger('datesSet', __assign(__assign({}, buildRangeApiWithTimeZone(dateProfile.activeRange, context.dateEnv)), { view: context.viewApi })); + } + + function handleEventStore(eventStore, context) { + var emitter = context.emitter; + if (emitter.hasHandlers('eventsSet')) { + emitter.trigger('eventsSet', buildEventApis(eventStore, context)); + } + } + + /* + this array is exposed on the root namespace so that UMD plugins can add to it. + see the rollup-bundles script. + */ + var globalPlugins = [ + arrayEventSourcePlugin, + funcEventSourcePlugin, + jsonFeedEventSourcePlugin, + simpleRecurringEventsPlugin, + changeHandlerPlugin, + createPlugin({ + isLoadingFuncs: [ + function (state) { return computeEventSourcesLoading(state.eventSources); }, + ], + contentTypeHandlers: { + html: function () { return ({ render: injectHtml }); }, + domNodes: function () { return ({ render: injectDomNodes }); }, + }, + propSetHandlers: { + dateProfile: handleDateProfile, + eventStore: handleEventStore, + }, + }), + ]; + function injectHtml(el, html) { + el.innerHTML = html; + } + function injectDomNodes(el, domNodes) { + var oldNodes = Array.prototype.slice.call(el.childNodes); // TODO: use array util + var newNodes = Array.prototype.slice.call(domNodes); // TODO: use array util + if (!isArraysEqual(oldNodes, newNodes)) { + for (var _i = 0, newNodes_1 = newNodes; _i < newNodes_1.length; _i++) { + var newNode = newNodes_1[_i]; + el.appendChild(newNode); + } + oldNodes.forEach(removeElement); + } + } + + var DelayedRunner = /** @class */ (function () { + function DelayedRunner(drainedOption) { + this.drainedOption = drainedOption; + this.isRunning = false; + this.isDirty = false; + this.pauseDepths = {}; + this.timeoutId = 0; + } + DelayedRunner.prototype.request = function (delay) { + this.isDirty = true; + if (!this.isPaused()) { + this.clearTimeout(); + if (delay == null) { + this.tryDrain(); + } + else { + this.timeoutId = setTimeout(// NOT OPTIMAL! TODO: look at debounce + this.tryDrain.bind(this), delay); + } + } + }; + DelayedRunner.prototype.pause = function (scope) { + if (scope === void 0) { scope = ''; } + var pauseDepths = this.pauseDepths; + pauseDepths[scope] = (pauseDepths[scope] || 0) + 1; + this.clearTimeout(); + }; + DelayedRunner.prototype.resume = function (scope, force) { + if (scope === void 0) { scope = ''; } + var pauseDepths = this.pauseDepths; + if (scope in pauseDepths) { + if (force) { + delete pauseDepths[scope]; + } + else { + pauseDepths[scope] -= 1; + var depth = pauseDepths[scope]; + if (depth <= 0) { + delete pauseDepths[scope]; + } + } + this.tryDrain(); + } + }; + DelayedRunner.prototype.isPaused = function () { + return Object.keys(this.pauseDepths).length; + }; + DelayedRunner.prototype.tryDrain = function () { + if (!this.isRunning && !this.isPaused()) { + this.isRunning = true; + while (this.isDirty) { + this.isDirty = false; + this.drained(); // might set isDirty to true again + } + this.isRunning = false; + } + }; + DelayedRunner.prototype.clear = function () { + this.clearTimeout(); + this.isDirty = false; + this.pauseDepths = {}; + }; + DelayedRunner.prototype.clearTimeout = function () { + if (this.timeoutId) { + clearTimeout(this.timeoutId); + this.timeoutId = 0; + } + }; + DelayedRunner.prototype.drained = function () { + if (this.drainedOption) { + this.drainedOption(); + } + }; + return DelayedRunner; + }()); + + var TaskRunner = /** @class */ (function () { + function TaskRunner(runTaskOption, drainedOption) { + this.runTaskOption = runTaskOption; + this.drainedOption = drainedOption; + this.queue = []; + this.delayedRunner = new DelayedRunner(this.drain.bind(this)); + } + TaskRunner.prototype.request = function (task, delay) { + this.queue.push(task); + this.delayedRunner.request(delay); + }; + TaskRunner.prototype.pause = function (scope) { + this.delayedRunner.pause(scope); + }; + TaskRunner.prototype.resume = function (scope, force) { + this.delayedRunner.resume(scope, force); + }; + TaskRunner.prototype.drain = function () { + var queue = this.queue; + while (queue.length) { + var completedTasks = []; + var task = void 0; + while ((task = queue.shift())) { + this.runTask(task); + completedTasks.push(task); + } + this.drained(completedTasks); + } // keep going, in case new tasks were added in the drained handler + }; + TaskRunner.prototype.runTask = function (task) { + if (this.runTaskOption) { + this.runTaskOption(task); + } + }; + TaskRunner.prototype.drained = function (completedTasks) { + if (this.drainedOption) { + this.drainedOption(completedTasks); + } + }; + return TaskRunner; + }()); + + // Computes what the title at the top of the calendarApi should be for this view + function buildTitle(dateProfile, viewOptions, dateEnv) { + var range; + // for views that span a large unit of time, show the proper interval, ignoring stray days before and after + if (/^(year|month)$/.test(dateProfile.currentRangeUnit)) { + range = dateProfile.currentRange; + } + else { // for day units or smaller, use the actual day range + range = dateProfile.activeRange; + } + return dateEnv.formatRange(range.start, range.end, createFormatter(viewOptions.titleFormat || buildTitleFormat(dateProfile)), { + isEndExclusive: dateProfile.isRangeAllDay, + defaultSeparator: viewOptions.titleRangeSeparator, + }); + } + // Generates the format string that should be used to generate the title for the current date range. + // Attempts to compute the most appropriate format if not explicitly specified with `titleFormat`. + function buildTitleFormat(dateProfile) { + var currentRangeUnit = dateProfile.currentRangeUnit; + if (currentRangeUnit === 'year') { + return { year: 'numeric' }; + } + if (currentRangeUnit === 'month') { + return { year: 'numeric', month: 'long' }; // like "September 2014" + } + var days = diffWholeDays(dateProfile.currentRange.start, dateProfile.currentRange.end); + if (days !== null && days > 1) { + // multi-day range. shorter, like "Sep 9 - 10 2014" + return { year: 'numeric', month: 'short', day: 'numeric' }; + } + // one day. longer, like "September 9 2014" + return { year: 'numeric', month: 'long', day: 'numeric' }; + } + + // in future refactor, do the redux-style function(state=initial) for initial-state + // also, whatever is happening in constructor, have it happen in action queue too + var CalendarDataManager = /** @class */ (function () { + function CalendarDataManager(props) { + var _this = this; + this.computeOptionsData = memoize(this._computeOptionsData); + this.computeCurrentViewData = memoize(this._computeCurrentViewData); + this.organizeRawLocales = memoize(organizeRawLocales); + this.buildLocale = memoize(buildLocale); + this.buildPluginHooks = buildBuildPluginHooks(); + this.buildDateEnv = memoize(buildDateEnv); + this.buildTheme = memoize(buildTheme); + this.parseToolbars = memoize(parseToolbars); + this.buildViewSpecs = memoize(buildViewSpecs); + this.buildDateProfileGenerator = memoizeObjArg(buildDateProfileGenerator); + this.buildViewApi = memoize(buildViewApi); + this.buildViewUiProps = memoizeObjArg(buildViewUiProps); + this.buildEventUiBySource = memoize(buildEventUiBySource, isPropsEqual); + this.buildEventUiBases = memoize(buildEventUiBases); + this.parseContextBusinessHours = memoizeObjArg(parseContextBusinessHours); + this.buildTitle = memoize(buildTitle); + this.emitter = new Emitter(); + this.actionRunner = new TaskRunner(this._handleAction.bind(this), this.updateData.bind(this)); + this.currentCalendarOptionsInput = {}; + this.currentCalendarOptionsRefined = {}; + this.currentViewOptionsInput = {}; + this.currentViewOptionsRefined = {}; + this.currentCalendarOptionsRefiners = {}; + this.getCurrentData = function () { return _this.data; }; + this.dispatch = function (action) { + _this.actionRunner.request(action); // protects against recursive calls to _handleAction + }; + this.props = props; + this.actionRunner.pause(); + var dynamicOptionOverrides = {}; + var optionsData = this.computeOptionsData(props.optionOverrides, dynamicOptionOverrides, props.calendarApi); + var currentViewType = optionsData.calendarOptions.initialView || optionsData.pluginHooks.initialView; + var currentViewData = this.computeCurrentViewData(currentViewType, optionsData, props.optionOverrides, dynamicOptionOverrides); + // wire things up + // TODO: not DRY + props.calendarApi.currentDataManager = this; + this.emitter.setThisContext(props.calendarApi); + this.emitter.setOptions(currentViewData.options); + var currentDate = getInitialDate(optionsData.calendarOptions, optionsData.dateEnv); + var dateProfile = currentViewData.dateProfileGenerator.build(currentDate); + if (!rangeContainsMarker(dateProfile.activeRange, currentDate)) { + currentDate = dateProfile.currentRange.start; + } + var calendarContext = { + dateEnv: optionsData.dateEnv, + options: optionsData.calendarOptions, + pluginHooks: optionsData.pluginHooks, + calendarApi: props.calendarApi, + dispatch: this.dispatch, + emitter: this.emitter, + getCurrentData: this.getCurrentData, + }; + // needs to be after setThisContext + for (var _i = 0, _a = optionsData.pluginHooks.contextInit; _i < _a.length; _i++) { + var callback = _a[_i]; + callback(calendarContext); + } + // NOT DRY + var eventSources = initEventSources(optionsData.calendarOptions, dateProfile, calendarContext); + var initialState = { + dynamicOptionOverrides: dynamicOptionOverrides, + currentViewType: currentViewType, + currentDate: currentDate, + dateProfile: dateProfile, + businessHours: this.parseContextBusinessHours(calendarContext), + eventSources: eventSources, + eventUiBases: {}, + eventStore: createEmptyEventStore(), + renderableEventStore: createEmptyEventStore(), + dateSelection: null, + eventSelection: '', + eventDrag: null, + eventResize: null, + selectionConfig: this.buildViewUiProps(calendarContext).selectionConfig, + }; + var contextAndState = __assign(__assign({}, calendarContext), initialState); + for (var _b = 0, _c = optionsData.pluginHooks.reducers; _b < _c.length; _b++) { + var reducer = _c[_b]; + __assign(initialState, reducer(null, null, contextAndState)); + } + if (computeIsLoading(initialState, calendarContext)) { + this.emitter.trigger('loading', true); // NOT DRY + } + this.state = initialState; + this.updateData(); + this.actionRunner.resume(); + } + CalendarDataManager.prototype.resetOptions = function (optionOverrides, append) { + var props = this.props; + props.optionOverrides = append + ? __assign(__assign({}, props.optionOverrides), optionOverrides) : optionOverrides; + this.actionRunner.request({ + type: 'NOTHING', + }); + }; + CalendarDataManager.prototype._handleAction = function (action) { + var _a = this, props = _a.props, state = _a.state, emitter = _a.emitter; + var dynamicOptionOverrides = reduceDynamicOptionOverrides(state.dynamicOptionOverrides, action); + var optionsData = this.computeOptionsData(props.optionOverrides, dynamicOptionOverrides, props.calendarApi); + var currentViewType = reduceViewType(state.currentViewType, action); + var currentViewData = this.computeCurrentViewData(currentViewType, optionsData, props.optionOverrides, dynamicOptionOverrides); + // wire things up + // TODO: not DRY + props.calendarApi.currentDataManager = this; + emitter.setThisContext(props.calendarApi); + emitter.setOptions(currentViewData.options); + var calendarContext = { + dateEnv: optionsData.dateEnv, + options: optionsData.calendarOptions, + pluginHooks: optionsData.pluginHooks, + calendarApi: props.calendarApi, + dispatch: this.dispatch, + emitter: emitter, + getCurrentData: this.getCurrentData, + }; + var currentDate = state.currentDate, dateProfile = state.dateProfile; + if (this.data && this.data.dateProfileGenerator !== currentViewData.dateProfileGenerator) { // hack + dateProfile = currentViewData.dateProfileGenerator.build(currentDate); + } + currentDate = reduceCurrentDate(currentDate, action); + dateProfile = reduceDateProfile(dateProfile, action, currentDate, currentViewData.dateProfileGenerator); + if (action.type === 'PREV' || // TODO: move this logic into DateProfileGenerator + action.type === 'NEXT' || // " + !rangeContainsMarker(dateProfile.currentRange, currentDate)) { + currentDate = dateProfile.currentRange.start; + } + var eventSources = reduceEventSources(state.eventSources, action, dateProfile, calendarContext); + var eventStore = reduceEventStore(state.eventStore, action, eventSources, dateProfile, calendarContext); + var isEventsLoading = computeEventSourcesLoading(eventSources); // BAD. also called in this func in computeIsLoading + var renderableEventStore = (isEventsLoading && !currentViewData.options.progressiveEventRendering) ? + (state.renderableEventStore || eventStore) : // try from previous state + eventStore; + var _b = this.buildViewUiProps(calendarContext), eventUiSingleBase = _b.eventUiSingleBase, selectionConfig = _b.selectionConfig; // will memoize obj + var eventUiBySource = this.buildEventUiBySource(eventSources); + var eventUiBases = this.buildEventUiBases(renderableEventStore.defs, eventUiSingleBase, eventUiBySource); + var newState = { + dynamicOptionOverrides: dynamicOptionOverrides, + currentViewType: currentViewType, + currentDate: currentDate, + dateProfile: dateProfile, + eventSources: eventSources, + eventStore: eventStore, + renderableEventStore: renderableEventStore, + selectionConfig: selectionConfig, + eventUiBases: eventUiBases, + businessHours: this.parseContextBusinessHours(calendarContext), + dateSelection: reduceDateSelection(state.dateSelection, action), + eventSelection: reduceSelectedEvent(state.eventSelection, action), + eventDrag: reduceEventDrag(state.eventDrag, action), + eventResize: reduceEventResize(state.eventResize, action), + }; + var contextAndState = __assign(__assign({}, calendarContext), newState); + for (var _i = 0, _c = optionsData.pluginHooks.reducers; _i < _c.length; _i++) { + var reducer = _c[_i]; + __assign(newState, reducer(state, action, contextAndState)); // give the OLD state, for old value + } + var wasLoading = computeIsLoading(state, calendarContext); + var isLoading = computeIsLoading(newState, calendarContext); + // TODO: use propSetHandlers in plugin system + if (!wasLoading && isLoading) { + emitter.trigger('loading', true); + } + else if (wasLoading && !isLoading) { + emitter.trigger('loading', false); + } + this.state = newState; + if (props.onAction) { + props.onAction(action); + } + }; + CalendarDataManager.prototype.updateData = function () { + var _a = this, props = _a.props, state = _a.state; + var oldData = this.data; + var optionsData = this.computeOptionsData(props.optionOverrides, state.dynamicOptionOverrides, props.calendarApi); + var currentViewData = this.computeCurrentViewData(state.currentViewType, optionsData, props.optionOverrides, state.dynamicOptionOverrides); + var data = this.data = __assign(__assign(__assign({ viewTitle: this.buildTitle(state.dateProfile, currentViewData.options, optionsData.dateEnv), calendarApi: props.calendarApi, dispatch: this.dispatch, emitter: this.emitter, getCurrentData: this.getCurrentData }, optionsData), currentViewData), state); + var changeHandlers = optionsData.pluginHooks.optionChangeHandlers; + var oldCalendarOptions = oldData && oldData.calendarOptions; + var newCalendarOptions = optionsData.calendarOptions; + if (oldCalendarOptions && oldCalendarOptions !== newCalendarOptions) { + if (oldCalendarOptions.timeZone !== newCalendarOptions.timeZone) { + // hack + state.eventSources = data.eventSources = reduceEventSourcesNewTimeZone(data.eventSources, state.dateProfile, data); + state.eventStore = data.eventStore = rezoneEventStoreDates(data.eventStore, oldData.dateEnv, data.dateEnv); + } + for (var optionName in changeHandlers) { + if (oldCalendarOptions[optionName] !== newCalendarOptions[optionName]) { + changeHandlers[optionName](newCalendarOptions[optionName], data); + } + } + } + if (props.onData) { + props.onData(data); + } + }; + CalendarDataManager.prototype._computeOptionsData = function (optionOverrides, dynamicOptionOverrides, calendarApi) { + // TODO: blacklist options that are handled by optionChangeHandlers + var _a = this.processRawCalendarOptions(optionOverrides, dynamicOptionOverrides), refinedOptions = _a.refinedOptions, pluginHooks = _a.pluginHooks, localeDefaults = _a.localeDefaults, availableLocaleData = _a.availableLocaleData, extra = _a.extra; + warnUnknownOptions(extra); + var dateEnv = this.buildDateEnv(refinedOptions.timeZone, refinedOptions.locale, refinedOptions.weekNumberCalculation, refinedOptions.firstDay, refinedOptions.weekText, pluginHooks, availableLocaleData, refinedOptions.defaultRangeSeparator); + var viewSpecs = this.buildViewSpecs(pluginHooks.views, optionOverrides, dynamicOptionOverrides, localeDefaults); + var theme = this.buildTheme(refinedOptions, pluginHooks); + var toolbarConfig = this.parseToolbars(refinedOptions, optionOverrides, theme, viewSpecs, calendarApi); + return { + calendarOptions: refinedOptions, + pluginHooks: pluginHooks, + dateEnv: dateEnv, + viewSpecs: viewSpecs, + theme: theme, + toolbarConfig: toolbarConfig, + localeDefaults: localeDefaults, + availableRawLocales: availableLocaleData.map, + }; + }; + // always called from behind a memoizer + CalendarDataManager.prototype.processRawCalendarOptions = function (optionOverrides, dynamicOptionOverrides) { + var _a = mergeRawOptions([ + BASE_OPTION_DEFAULTS, + optionOverrides, + dynamicOptionOverrides, + ]), locales = _a.locales, locale = _a.locale; + var availableLocaleData = this.organizeRawLocales(locales); + var availableRawLocales = availableLocaleData.map; + var localeDefaults = this.buildLocale(locale || availableLocaleData.defaultCode, availableRawLocales).options; + var pluginHooks = this.buildPluginHooks(optionOverrides.plugins || [], globalPlugins); + var refiners = this.currentCalendarOptionsRefiners = __assign(__assign(__assign(__assign(__assign({}, BASE_OPTION_REFINERS), CALENDAR_LISTENER_REFINERS), CALENDAR_OPTION_REFINERS), pluginHooks.listenerRefiners), pluginHooks.optionRefiners); + var extra = {}; + var raw = mergeRawOptions([ + BASE_OPTION_DEFAULTS, + localeDefaults, + optionOverrides, + dynamicOptionOverrides, + ]); + var refined = {}; + var currentRaw = this.currentCalendarOptionsInput; + var currentRefined = this.currentCalendarOptionsRefined; + var anyChanges = false; + for (var optionName in raw) { + if (optionName !== 'plugins') { // because plugins is special-cased + if (raw[optionName] === currentRaw[optionName] || + (COMPLEX_OPTION_COMPARATORS[optionName] && + (optionName in currentRaw) && + COMPLEX_OPTION_COMPARATORS[optionName](currentRaw[optionName], raw[optionName]))) { + refined[optionName] = currentRefined[optionName]; + } + else if (refiners[optionName]) { + refined[optionName] = refiners[optionName](raw[optionName]); + anyChanges = true; + } + else { + extra[optionName] = currentRaw[optionName]; + } + } + } + if (anyChanges) { + this.currentCalendarOptionsInput = raw; + this.currentCalendarOptionsRefined = refined; + } + return { + rawOptions: this.currentCalendarOptionsInput, + refinedOptions: this.currentCalendarOptionsRefined, + pluginHooks: pluginHooks, + availableLocaleData: availableLocaleData, + localeDefaults: localeDefaults, + extra: extra, + }; + }; + CalendarDataManager.prototype._computeCurrentViewData = function (viewType, optionsData, optionOverrides, dynamicOptionOverrides) { + var viewSpec = optionsData.viewSpecs[viewType]; + if (!viewSpec) { + throw new Error("viewType \"" + viewType + "\" is not available. Please make sure you've loaded all neccessary plugins"); + } + var _a = this.processRawViewOptions(viewSpec, optionsData.pluginHooks, optionsData.localeDefaults, optionOverrides, dynamicOptionOverrides), refinedOptions = _a.refinedOptions, extra = _a.extra; + warnUnknownOptions(extra); + var dateProfileGenerator = this.buildDateProfileGenerator({ + dateProfileGeneratorClass: viewSpec.optionDefaults.dateProfileGeneratorClass, + duration: viewSpec.duration, + durationUnit: viewSpec.durationUnit, + usesMinMaxTime: viewSpec.optionDefaults.usesMinMaxTime, + dateEnv: optionsData.dateEnv, + calendarApi: this.props.calendarApi, + slotMinTime: refinedOptions.slotMinTime, + slotMaxTime: refinedOptions.slotMaxTime, + showNonCurrentDates: refinedOptions.showNonCurrentDates, + dayCount: refinedOptions.dayCount, + dateAlignment: refinedOptions.dateAlignment, + dateIncrement: refinedOptions.dateIncrement, + hiddenDays: refinedOptions.hiddenDays, + weekends: refinedOptions.weekends, + nowInput: refinedOptions.now, + validRangeInput: refinedOptions.validRange, + visibleRangeInput: refinedOptions.visibleRange, + monthMode: refinedOptions.monthMode, + fixedWeekCount: refinedOptions.fixedWeekCount, + }); + var viewApi = this.buildViewApi(viewType, this.getCurrentData, optionsData.dateEnv); + return { viewSpec: viewSpec, options: refinedOptions, dateProfileGenerator: dateProfileGenerator, viewApi: viewApi }; + }; + CalendarDataManager.prototype.processRawViewOptions = function (viewSpec, pluginHooks, localeDefaults, optionOverrides, dynamicOptionOverrides) { + var raw = mergeRawOptions([ + BASE_OPTION_DEFAULTS, + viewSpec.optionDefaults, + localeDefaults, + optionOverrides, + viewSpec.optionOverrides, + dynamicOptionOverrides, + ]); + var refiners = __assign(__assign(__assign(__assign(__assign(__assign({}, BASE_OPTION_REFINERS), CALENDAR_LISTENER_REFINERS), CALENDAR_OPTION_REFINERS), VIEW_OPTION_REFINERS), pluginHooks.listenerRefiners), pluginHooks.optionRefiners); + var refined = {}; + var currentRaw = this.currentViewOptionsInput; + var currentRefined = this.currentViewOptionsRefined; + var anyChanges = false; + var extra = {}; + for (var optionName in raw) { + if (raw[optionName] === currentRaw[optionName]) { + refined[optionName] = currentRefined[optionName]; + } + else { + if (raw[optionName] === this.currentCalendarOptionsInput[optionName]) { + if (optionName in this.currentCalendarOptionsRefined) { // might be an "extra" prop + refined[optionName] = this.currentCalendarOptionsRefined[optionName]; + } + } + else if (refiners[optionName]) { + refined[optionName] = refiners[optionName](raw[optionName]); + } + else { + extra[optionName] = raw[optionName]; + } + anyChanges = true; + } + } + if (anyChanges) { + this.currentViewOptionsInput = raw; + this.currentViewOptionsRefined = refined; + } + return { + rawOptions: this.currentViewOptionsInput, + refinedOptions: this.currentViewOptionsRefined, + extra: extra, + }; + }; + return CalendarDataManager; + }()); + function buildDateEnv(timeZone, explicitLocale, weekNumberCalculation, firstDay, weekText, pluginHooks, availableLocaleData, defaultSeparator) { + var locale = buildLocale(explicitLocale || availableLocaleData.defaultCode, availableLocaleData.map); + return new DateEnv({ + calendarSystem: 'gregory', + timeZone: timeZone, + namedTimeZoneImpl: pluginHooks.namedTimeZonedImpl, + locale: locale, + weekNumberCalculation: weekNumberCalculation, + firstDay: firstDay, + weekText: weekText, + cmdFormatter: pluginHooks.cmdFormatter, + defaultSeparator: defaultSeparator, + }); + } + function buildTheme(options, pluginHooks) { + var ThemeClass = pluginHooks.themeClasses[options.themeSystem] || StandardTheme; + return new ThemeClass(options); + } + function buildDateProfileGenerator(props) { + var DateProfileGeneratorClass = props.dateProfileGeneratorClass || DateProfileGenerator; + return new DateProfileGeneratorClass(props); + } + function buildViewApi(type, getCurrentData, dateEnv) { + return new ViewApi(type, getCurrentData, dateEnv); + } + function buildEventUiBySource(eventSources) { + return mapHash(eventSources, function (eventSource) { return eventSource.ui; }); + } + function buildEventUiBases(eventDefs, eventUiSingleBase, eventUiBySource) { + var eventUiBases = { '': eventUiSingleBase }; + for (var defId in eventDefs) { + var def = eventDefs[defId]; + if (def.sourceId && eventUiBySource[def.sourceId]) { + eventUiBases[defId] = eventUiBySource[def.sourceId]; + } + } + return eventUiBases; + } + function buildViewUiProps(calendarContext) { + var options = calendarContext.options; + return { + eventUiSingleBase: createEventUi({ + display: options.eventDisplay, + editable: options.editable, + startEditable: options.eventStartEditable, + durationEditable: options.eventDurationEditable, + constraint: options.eventConstraint, + overlap: typeof options.eventOverlap === 'boolean' ? options.eventOverlap : undefined, + allow: options.eventAllow, + backgroundColor: options.eventBackgroundColor, + borderColor: options.eventBorderColor, + textColor: options.eventTextColor, + color: options.eventColor, + // classNames: options.eventClassNames // render hook will handle this + }, calendarContext), + selectionConfig: createEventUi({ + constraint: options.selectConstraint, + overlap: typeof options.selectOverlap === 'boolean' ? options.selectOverlap : undefined, + allow: options.selectAllow, + }, calendarContext), + }; + } + function computeIsLoading(state, context) { + for (var _i = 0, _a = context.pluginHooks.isLoadingFuncs; _i < _a.length; _i++) { + var isLoadingFunc = _a[_i]; + if (isLoadingFunc(state)) { + return true; + } + } + return false; + } + function parseContextBusinessHours(calendarContext) { + return parseBusinessHours(calendarContext.options.businessHours, calendarContext); + } + function warnUnknownOptions(options, viewName) { + for (var optionName in options) { + console.warn("Unknown option '" + optionName + "'" + + (viewName ? " for view '" + viewName + "'" : '')); + } + } + + // TODO: move this to react plugin? + var CalendarDataProvider = /** @class */ (function (_super) { + __extends(CalendarDataProvider, _super); + function CalendarDataProvider(props) { + var _this = _super.call(this, props) || this; + _this.handleData = function (data) { + if (!_this.dataManager) { // still within initial run, before assignment in constructor + // eslint-disable-next-line react/no-direct-mutation-state + _this.state = data; // can't use setState yet + } + else { + _this.setState(data); + } + }; + _this.dataManager = new CalendarDataManager({ + optionOverrides: props.optionOverrides, + calendarApi: props.calendarApi, + onData: _this.handleData, + }); + return _this; + } + CalendarDataProvider.prototype.render = function () { + return this.props.children(this.state); + }; + CalendarDataProvider.prototype.componentDidUpdate = function (prevProps) { + var newOptionOverrides = this.props.optionOverrides; + if (newOptionOverrides !== prevProps.optionOverrides) { // prevent recursive handleData + this.dataManager.resetOptions(newOptionOverrides); + } + }; + return CalendarDataProvider; + }(Component)); + + // HELPERS + /* + if nextDayThreshold is specified, slicing is done in an all-day fashion. + you can get nextDayThreshold from context.nextDayThreshold + */ + function sliceEvents(props, allDay) { + return sliceEventStore(props.eventStore, props.eventUiBases, props.dateProfile.activeRange, allDay ? props.nextDayThreshold : null).fg; + } + + var NamedTimeZoneImpl = /** @class */ (function () { + function NamedTimeZoneImpl(timeZoneName) { + this.timeZoneName = timeZoneName; + } + return NamedTimeZoneImpl; + }()); + + var SegHierarchy = /** @class */ (function () { + function SegHierarchy() { + // settings + this.strictOrder = false; + this.allowReslicing = false; + this.maxCoord = -1; // -1 means no max + this.maxStackCnt = -1; // -1 means no max + this.levelCoords = []; // ordered + this.entriesByLevel = []; // parallel with levelCoords + this.stackCnts = {}; // TODO: use better technique!? + } + SegHierarchy.prototype.addSegs = function (inputs) { + var hiddenEntries = []; + for (var _i = 0, inputs_1 = inputs; _i < inputs_1.length; _i++) { + var input = inputs_1[_i]; + this.insertEntry(input, hiddenEntries); + } + return hiddenEntries; + }; + SegHierarchy.prototype.insertEntry = function (entry, hiddenEntries) { + var insertion = this.findInsertion(entry); + if (this.isInsertionValid(insertion, entry)) { + this.insertEntryAt(entry, insertion); + return 1; + } + return this.handleInvalidInsertion(insertion, entry, hiddenEntries); + }; + SegHierarchy.prototype.isInsertionValid = function (insertion, entry) { + return (this.maxCoord === -1 || insertion.levelCoord + entry.thickness <= this.maxCoord) && + (this.maxStackCnt === -1 || insertion.stackCnt < this.maxStackCnt); + }; + // returns number of new entries inserted + SegHierarchy.prototype.handleInvalidInsertion = function (insertion, entry, hiddenEntries) { + if (this.allowReslicing && insertion.touchingEntry) { + return this.splitEntry(entry, insertion.touchingEntry, hiddenEntries); + } + hiddenEntries.push(entry); + return 0; + }; + SegHierarchy.prototype.splitEntry = function (entry, barrier, hiddenEntries) { + var partCnt = 0; + var splitHiddenEntries = []; + var entrySpan = entry.span; + var barrierSpan = barrier.span; + if (entrySpan.start < barrierSpan.start) { + partCnt += this.insertEntry({ + index: entry.index, + thickness: entry.thickness, + span: { start: entrySpan.start, end: barrierSpan.start }, + }, splitHiddenEntries); + } + if (entrySpan.end > barrierSpan.end) { + partCnt += this.insertEntry({ + index: entry.index, + thickness: entry.thickness, + span: { start: barrierSpan.end, end: entrySpan.end }, + }, splitHiddenEntries); + } + if (partCnt) { + hiddenEntries.push.apply(hiddenEntries, __spreadArray([{ + index: entry.index, + thickness: entry.thickness, + span: intersectSpans(barrierSpan, entrySpan), // guaranteed to intersect + }], splitHiddenEntries)); + return partCnt; + } + hiddenEntries.push(entry); + return 0; + }; + SegHierarchy.prototype.insertEntryAt = function (entry, insertion) { + var _a = this, entriesByLevel = _a.entriesByLevel, levelCoords = _a.levelCoords; + if (insertion.lateral === -1) { + // create a new level + insertAt(levelCoords, insertion.level, insertion.levelCoord); + insertAt(entriesByLevel, insertion.level, [entry]); + } + else { + // insert into existing level + insertAt(entriesByLevel[insertion.level], insertion.lateral, entry); + } + this.stackCnts[buildEntryKey(entry)] = insertion.stackCnt; + }; + SegHierarchy.prototype.findInsertion = function (newEntry) { + var _a = this, levelCoords = _a.levelCoords, entriesByLevel = _a.entriesByLevel, strictOrder = _a.strictOrder, stackCnts = _a.stackCnts; + var levelCnt = levelCoords.length; + var candidateCoord = 0; + var touchingLevel = -1; + var touchingLateral = -1; + var touchingEntry = null; + var stackCnt = 0; + for (var trackingLevel = 0; trackingLevel < levelCnt; trackingLevel += 1) { + var trackingCoord = levelCoords[trackingLevel]; + // if the current level is past the placed entry, we have found a good empty space and can stop. + // if strictOrder, keep finding more lateral intersections. + if (!strictOrder && trackingCoord >= candidateCoord + newEntry.thickness) { + break; + } + var trackingEntries = entriesByLevel[trackingLevel]; + var trackingEntry = void 0; + var searchRes = binarySearch(trackingEntries, newEntry.span.start, getEntrySpanEnd); // find first entry after newEntry's end + var lateralIndex = searchRes[0] + searchRes[1]; // if exact match (which doesn't collide), go to next one + while ( // loop through entries that horizontally intersect + (trackingEntry = trackingEntries[lateralIndex]) && // but not past the whole entry list + trackingEntry.span.start < newEntry.span.end // and not entirely past newEntry + ) { + var trackingEntryBottom = trackingCoord + trackingEntry.thickness; + // intersects into the top of the candidate? + if (trackingEntryBottom > candidateCoord) { + candidateCoord = trackingEntryBottom; + touchingEntry = trackingEntry; + touchingLevel = trackingLevel; + touchingLateral = lateralIndex; + } + // butts up against top of candidate? (will happen if just intersected as well) + if (trackingEntryBottom === candidateCoord) { + // accumulate the highest possible stackCnt of the trackingEntries that butt up + stackCnt = Math.max(stackCnt, stackCnts[buildEntryKey(trackingEntry)] + 1); + } + lateralIndex += 1; + } + } + // the destination level will be after touchingEntry's level. find it + var destLevel = 0; + if (touchingEntry) { + destLevel = touchingLevel + 1; + while (destLevel < levelCnt && levelCoords[destLevel] < candidateCoord) { + destLevel += 1; + } + } + // if adding to an existing level, find where to insert + var destLateral = -1; + if (destLevel < levelCnt && levelCoords[destLevel] === candidateCoord) { + destLateral = binarySearch(entriesByLevel[destLevel], newEntry.span.end, getEntrySpanEnd)[0]; + } + return { + touchingLevel: touchingLevel, + touchingLateral: touchingLateral, + touchingEntry: touchingEntry, + stackCnt: stackCnt, + levelCoord: candidateCoord, + level: destLevel, + lateral: destLateral, + }; + }; + // sorted by levelCoord (lowest to highest) + SegHierarchy.prototype.toRects = function () { + var _a = this, entriesByLevel = _a.entriesByLevel, levelCoords = _a.levelCoords; + var levelCnt = entriesByLevel.length; + var rects = []; + for (var level = 0; level < levelCnt; level += 1) { + var entries = entriesByLevel[level]; + var levelCoord = levelCoords[level]; + for (var _i = 0, entries_1 = entries; _i < entries_1.length; _i++) { + var entry = entries_1[_i]; + rects.push(__assign(__assign({}, entry), { levelCoord: levelCoord })); + } + } + return rects; + }; + return SegHierarchy; + }()); + function getEntrySpanEnd(entry) { + return entry.span.end; + } + function buildEntryKey(entry) { + return entry.index + ':' + entry.span.start; + } + // returns groups with entries sorted by input order + function groupIntersectingEntries(entries) { + var merges = []; + for (var _i = 0, entries_2 = entries; _i < entries_2.length; _i++) { + var entry = entries_2[_i]; + var filteredMerges = []; + var hungryMerge = { + span: entry.span, + entries: [entry], + }; + for (var _a = 0, merges_1 = merges; _a < merges_1.length; _a++) { + var merge = merges_1[_a]; + if (intersectSpans(merge.span, hungryMerge.span)) { + hungryMerge = { + entries: merge.entries.concat(hungryMerge.entries), + span: joinSpans(merge.span, hungryMerge.span), + }; + } + else { + filteredMerges.push(merge); + } + } + filteredMerges.push(hungryMerge); + merges = filteredMerges; + } + return merges; + } + function joinSpans(span0, span1) { + return { + start: Math.min(span0.start, span1.start), + end: Math.max(span0.end, span1.end), + }; + } + function intersectSpans(span0, span1) { + var start = Math.max(span0.start, span1.start); + var end = Math.min(span0.end, span1.end); + if (start < end) { + return { start: start, end: end }; + } + return null; + } + // general util + // --------------------------------------------------------------------------------------------------------------------- + function insertAt(arr, index, item) { + arr.splice(index, 0, item); + } + function binarySearch(a, searchVal, getItemVal) { + var startIndex = 0; + var endIndex = a.length; // exclusive + if (!endIndex || searchVal < getItemVal(a[startIndex])) { // no items OR before first item + return [0, 0]; + } + if (searchVal > getItemVal(a[endIndex - 1])) { // after last item + return [endIndex, 0]; + } + while (startIndex < endIndex) { + var middleIndex = Math.floor(startIndex + (endIndex - startIndex) / 2); + var middleVal = getItemVal(a[middleIndex]); + if (searchVal < middleVal) { + endIndex = middleIndex; + } + else if (searchVal > middleVal) { + startIndex = middleIndex + 1; + } + else { // equal! + return [middleIndex, 1]; + } + } + return [startIndex, 0]; + } + + var Interaction = /** @class */ (function () { + function Interaction(settings) { + this.component = settings.component; + this.isHitComboAllowed = settings.isHitComboAllowed || null; + } + Interaction.prototype.destroy = function () { + }; + return Interaction; + }()); + function parseInteractionSettings(component, input) { + return { + component: component, + el: input.el, + useEventCenter: input.useEventCenter != null ? input.useEventCenter : true, + isHitComboAllowed: input.isHitComboAllowed || null, + }; + } + function interactionSettingsToStore(settings) { + var _a; + return _a = {}, + _a[settings.component.uid] = settings, + _a; + } + // global state + var interactionSettingsStore = {}; + + /* + An abstraction for a dragging interaction originating on an event. + Does higher-level things than PointerDragger, such as possibly: + - a "mirror" that moves with the pointer + - a minimum number of pixels or other criteria for a true drag to begin + + subclasses must emit: + - pointerdown + - dragstart + - dragmove + - pointerup + - dragend + */ + var ElementDragging = /** @class */ (function () { + function ElementDragging(el, selector) { + this.emitter = new Emitter(); + } + ElementDragging.prototype.destroy = function () { + }; + ElementDragging.prototype.setMirrorIsVisible = function (bool) { + // optional if subclass doesn't want to support a mirror + }; + ElementDragging.prototype.setMirrorNeedsRevert = function (bool) { + // optional if subclass doesn't want to support a mirror + }; + ElementDragging.prototype.setAutoScrollEnabled = function (bool) { + // optional + }; + return ElementDragging; + }()); + + // TODO: get rid of this in favor of options system, + // tho it's really easy to access this globally rather than pass thru options. + var config = {}; + + /* + Information about what will happen when an external element is dragged-and-dropped + onto a calendar. Contains information for creating an event. + */ + var DRAG_META_REFINERS = { + startTime: createDuration, + duration: createDuration, + create: Boolean, + sourceId: String, + }; + function parseDragMeta(raw) { + var _a = refineProps(raw, DRAG_META_REFINERS), refined = _a.refined, extra = _a.extra; + return { + startTime: refined.startTime || null, + duration: refined.duration || null, + create: refined.create != null ? refined.create : true, + sourceId: refined.sourceId, + leftoverProps: extra, + }; + } + + var ToolbarSection = /** @class */ (function (_super) { + __extends(ToolbarSection, _super); + function ToolbarSection() { + return _super !== null && _super.apply(this, arguments) || this; + } + ToolbarSection.prototype.render = function () { + var _this = this; + var children = this.props.widgetGroups.map(function (widgetGroup) { return _this.renderWidgetGroup(widgetGroup); }); + return createElement.apply(void 0, __spreadArray(['div', { className: 'fc-toolbar-chunk' }], children)); + }; + ToolbarSection.prototype.renderWidgetGroup = function (widgetGroup) { + var props = this.props; + var theme = this.context.theme; + var children = []; + var isOnlyButtons = true; + for (var _i = 0, widgetGroup_1 = widgetGroup; _i < widgetGroup_1.length; _i++) { + var widget = widgetGroup_1[_i]; + var buttonName = widget.buttonName, buttonClick = widget.buttonClick, buttonText = widget.buttonText, buttonIcon = widget.buttonIcon; + if (buttonName === 'title') { + isOnlyButtons = false; + children.push(createElement("h2", { className: "fc-toolbar-title" }, props.title)); + } + else { + var ariaAttrs = buttonIcon ? { 'aria-label': buttonName } : {}; + var buttonClasses = ["fc-" + buttonName + "-button", theme.getClass('button')]; + if (buttonName === props.activeButton) { + buttonClasses.push(theme.getClass('buttonActive')); + } + var isDisabled = (!props.isTodayEnabled && buttonName === 'today') || + (!props.isPrevEnabled && buttonName === 'prev') || + (!props.isNextEnabled && buttonName === 'next'); + children.push(createElement("button", __assign({ disabled: isDisabled, className: buttonClasses.join(' '), onClick: buttonClick, type: "button" }, ariaAttrs), buttonText || (buttonIcon ? createElement("span", { className: buttonIcon }) : ''))); + } + } + if (children.length > 1) { + var groupClassName = (isOnlyButtons && theme.getClass('buttonGroup')) || ''; + return createElement.apply(void 0, __spreadArray(['div', { className: groupClassName }], children)); + } + return children[0]; + }; + return ToolbarSection; + }(BaseComponent)); + + var Toolbar = /** @class */ (function (_super) { + __extends(Toolbar, _super); + function Toolbar() { + return _super !== null && _super.apply(this, arguments) || this; + } + Toolbar.prototype.render = function () { + var _a = this.props, model = _a.model, extraClassName = _a.extraClassName; + var forceLtr = false; + var startContent; + var endContent; + var centerContent = model.center; + if (model.left) { + forceLtr = true; + startContent = model.left; + } + else { + startContent = model.start; + } + if (model.right) { + forceLtr = true; + endContent = model.right; + } + else { + endContent = model.end; + } + var classNames = [ + extraClassName || '', + 'fc-toolbar', + forceLtr ? 'fc-toolbar-ltr' : '', + ]; + return (createElement("div", { className: classNames.join(' ') }, + this.renderSection('start', startContent || []), + this.renderSection('center', centerContent || []), + this.renderSection('end', endContent || []))); + }; + Toolbar.prototype.renderSection = function (key, widgetGroups) { + var props = this.props; + return (createElement(ToolbarSection, { key: key, widgetGroups: widgetGroups, title: props.title, activeButton: props.activeButton, isTodayEnabled: props.isTodayEnabled, isPrevEnabled: props.isPrevEnabled, isNextEnabled: props.isNextEnabled })); + }; + return Toolbar; + }(BaseComponent)); + + // TODO: do function component? + var ViewContainer = /** @class */ (function (_super) { + __extends(ViewContainer, _super); + function ViewContainer() { + var _this = _super !== null && _super.apply(this, arguments) || this; + _this.state = { + availableWidth: null, + }; + _this.handleEl = function (el) { + _this.el = el; + setRef(_this.props.elRef, el); + _this.updateAvailableWidth(); + }; + _this.handleResize = function () { + _this.updateAvailableWidth(); + }; + return _this; + } + ViewContainer.prototype.render = function () { + var _a = this, props = _a.props, state = _a.state; + var aspectRatio = props.aspectRatio; + var classNames = [ + 'fc-view-harness', + (aspectRatio || props.liquid || props.height) + ? 'fc-view-harness-active' // harness controls the height + : 'fc-view-harness-passive', // let the view do the height + ]; + var height = ''; + var paddingBottom = ''; + if (aspectRatio) { + if (state.availableWidth !== null) { + height = state.availableWidth / aspectRatio; + } + else { + // while waiting to know availableWidth, we can't set height to *zero* + // because will cause lots of unnecessary scrollbars within scrollgrid. + // BETTER: don't start rendering ANYTHING yet until we know container width + // NOTE: why not always use paddingBottom? Causes height oscillation (issue 5606) + paddingBottom = (1 / aspectRatio) * 100 + "%"; + } + } + else { + height = props.height || ''; + } + return (createElement("div", { ref: this.handleEl, onClick: props.onClick, className: classNames.join(' '), style: { height: height, paddingBottom: paddingBottom } }, props.children)); + }; + ViewContainer.prototype.componentDidMount = function () { + this.context.addResizeHandler(this.handleResize); + }; + ViewContainer.prototype.componentWillUnmount = function () { + this.context.removeResizeHandler(this.handleResize); + }; + ViewContainer.prototype.updateAvailableWidth = function () { + if (this.el && // needed. but why? + this.props.aspectRatio // aspectRatio is the only height setting that needs availableWidth + ) { + this.setState({ availableWidth: this.el.offsetWidth }); + } + }; + return ViewContainer; + }(BaseComponent)); + + /* + Detects when the user clicks on an event within a DateComponent + */ + var EventClicking = /** @class */ (function (_super) { + __extends(EventClicking, _super); + function EventClicking(settings) { + var _this = _super.call(this, settings) || this; + _this.handleSegClick = function (ev, segEl) { + var component = _this.component; + var context = component.context; + var seg = getElSeg(segEl); + if (seg && // might be the
surrounding the more link + component.isValidSegDownEl(ev.target)) { + // our way to simulate a link click for elements that can't be tags + // grab before trigger fired in case trigger trashes DOM thru rerendering + var hasUrlContainer = elementClosest(ev.target, '.fc-event-forced-url'); + var url = hasUrlContainer ? hasUrlContainer.querySelector('a[href]').href : ''; + context.emitter.trigger('eventClick', { + el: segEl, + event: new EventApi(component.context, seg.eventRange.def, seg.eventRange.instance), + jsEvent: ev, + view: context.viewApi, + }); + if (url && !ev.defaultPrevented) { + window.location.href = url; + } + } + }; + _this.destroy = listenBySelector(settings.el, 'click', '.fc-event', // on both fg and bg events + _this.handleSegClick); + return _this; + } + return EventClicking; + }(Interaction)); + + /* + Triggers events and adds/removes core classNames when the user's pointer + enters/leaves event-elements of a component. + */ + var EventHovering = /** @class */ (function (_super) { + __extends(EventHovering, _super); + function EventHovering(settings) { + var _this = _super.call(this, settings) || this; + // for simulating an eventMouseLeave when the event el is destroyed while mouse is over it + _this.handleEventElRemove = function (el) { + if (el === _this.currentSegEl) { + _this.handleSegLeave(null, _this.currentSegEl); + } + }; + _this.handleSegEnter = function (ev, segEl) { + if (getElSeg(segEl)) { // TODO: better way to make sure not hovering over more+ link or its wrapper + _this.currentSegEl = segEl; + _this.triggerEvent('eventMouseEnter', ev, segEl); + } + }; + _this.handleSegLeave = function (ev, segEl) { + if (_this.currentSegEl) { + _this.currentSegEl = null; + _this.triggerEvent('eventMouseLeave', ev, segEl); + } + }; + _this.removeHoverListeners = listenToHoverBySelector(settings.el, '.fc-event', // on both fg and bg events + _this.handleSegEnter, _this.handleSegLeave); + return _this; + } + EventHovering.prototype.destroy = function () { + this.removeHoverListeners(); + }; + EventHovering.prototype.triggerEvent = function (publicEvName, ev, segEl) { + var component = this.component; + var context = component.context; + var seg = getElSeg(segEl); + if (!ev || component.isValidSegDownEl(ev.target)) { + context.emitter.trigger(publicEvName, { + el: segEl, + event: new EventApi(context, seg.eventRange.def, seg.eventRange.instance), + jsEvent: ev, + view: context.viewApi, + }); + } + }; + return EventHovering; + }(Interaction)); + + var CalendarContent = /** @class */ (function (_super) { + __extends(CalendarContent, _super); + function CalendarContent() { + var _this = _super !== null && _super.apply(this, arguments) || this; + _this.buildViewContext = memoize(buildViewContext); + _this.buildViewPropTransformers = memoize(buildViewPropTransformers); + _this.buildToolbarProps = memoize(buildToolbarProps); + _this.handleNavLinkClick = buildDelegationHandler('a[data-navlink]', _this._handleNavLinkClick.bind(_this)); + _this.headerRef = createRef(); + _this.footerRef = createRef(); + _this.interactionsStore = {}; + // Component Registration + // ----------------------------------------------------------------------------------------------------------------- + _this.registerInteractiveComponent = function (component, settingsInput) { + var settings = parseInteractionSettings(component, settingsInput); + var DEFAULT_INTERACTIONS = [ + EventClicking, + EventHovering, + ]; + var interactionClasses = DEFAULT_INTERACTIONS.concat(_this.props.pluginHooks.componentInteractions); + var interactions = interactionClasses.map(function (TheInteractionClass) { return new TheInteractionClass(settings); }); + _this.interactionsStore[component.uid] = interactions; + interactionSettingsStore[component.uid] = settings; + }; + _this.unregisterInteractiveComponent = function (component) { + for (var _i = 0, _a = _this.interactionsStore[component.uid]; _i < _a.length; _i++) { + var listener = _a[_i]; + listener.destroy(); + } + delete _this.interactionsStore[component.uid]; + delete interactionSettingsStore[component.uid]; + }; + // Resizing + // ----------------------------------------------------------------------------------------------------------------- + _this.resizeRunner = new DelayedRunner(function () { + _this.props.emitter.trigger('_resize', true); // should window resizes be considered "forced" ? + _this.props.emitter.trigger('windowResize', { view: _this.props.viewApi }); + }); + _this.handleWindowResize = function (ev) { + var options = _this.props.options; + if (options.handleWindowResize && + ev.target === window // avoid jqui events + ) { + _this.resizeRunner.request(options.windowResizeDelay); + } + }; + return _this; + } + /* + renders INSIDE of an outer div + */ + CalendarContent.prototype.render = function () { + var props = this.props; + var toolbarConfig = props.toolbarConfig, options = props.options; + var toolbarProps = this.buildToolbarProps(props.viewSpec, props.dateProfile, props.dateProfileGenerator, props.currentDate, getNow(props.options.now, props.dateEnv), // TODO: use NowTimer???? + props.viewTitle); + var viewVGrow = false; + var viewHeight = ''; + var viewAspectRatio; + if (props.isHeightAuto || props.forPrint) { + viewHeight = ''; + } + else if (options.height != null) { + viewVGrow = true; + } + else if (options.contentHeight != null) { + viewHeight = options.contentHeight; + } + else { + viewAspectRatio = Math.max(options.aspectRatio, 0.5); // prevent from getting too tall + } + var viewContext = this.buildViewContext(props.viewSpec, props.viewApi, props.options, props.dateProfileGenerator, props.dateEnv, props.theme, props.pluginHooks, props.dispatch, props.getCurrentData, props.emitter, props.calendarApi, this.registerInteractiveComponent, this.unregisterInteractiveComponent); + return (createElement(ViewContextType.Provider, { value: viewContext }, + toolbarConfig.headerToolbar && (createElement(Toolbar, __assign({ ref: this.headerRef, extraClassName: "fc-header-toolbar", model: toolbarConfig.headerToolbar }, toolbarProps))), + createElement(ViewContainer, { liquid: viewVGrow, height: viewHeight, aspectRatio: viewAspectRatio, onClick: this.handleNavLinkClick }, + this.renderView(props), + this.buildAppendContent()), + toolbarConfig.footerToolbar && (createElement(Toolbar, __assign({ ref: this.footerRef, extraClassName: "fc-footer-toolbar", model: toolbarConfig.footerToolbar }, toolbarProps))))); + }; + CalendarContent.prototype.componentDidMount = function () { + var props = this.props; + this.calendarInteractions = props.pluginHooks.calendarInteractions + .map(function (CalendarInteractionClass) { return new CalendarInteractionClass(props); }); + window.addEventListener('resize', this.handleWindowResize); + var propSetHandlers = props.pluginHooks.propSetHandlers; + for (var propName in propSetHandlers) { + propSetHandlers[propName](props[propName], props); + } + }; + CalendarContent.prototype.componentDidUpdate = function (prevProps) { + var props = this.props; + var propSetHandlers = props.pluginHooks.propSetHandlers; + for (var propName in propSetHandlers) { + if (props[propName] !== prevProps[propName]) { + propSetHandlers[propName](props[propName], props); + } + } + }; + CalendarContent.prototype.componentWillUnmount = function () { + window.removeEventListener('resize', this.handleWindowResize); + this.resizeRunner.clear(); + for (var _i = 0, _a = this.calendarInteractions; _i < _a.length; _i++) { + var interaction = _a[_i]; + interaction.destroy(); + } + this.props.emitter.trigger('_unmount'); + }; + CalendarContent.prototype._handleNavLinkClick = function (ev, anchorEl) { + var _a = this.props, dateEnv = _a.dateEnv, options = _a.options, calendarApi = _a.calendarApi; + var navLinkOptions = anchorEl.getAttribute('data-navlink'); + navLinkOptions = navLinkOptions ? JSON.parse(navLinkOptions) : {}; + var dateMarker = dateEnv.createMarker(navLinkOptions.date); + var viewType = navLinkOptions.type; + var customAction = viewType === 'day' ? options.navLinkDayClick : + viewType === 'week' ? options.navLinkWeekClick : null; + if (typeof customAction === 'function') { + customAction.call(calendarApi, dateEnv.toDate(dateMarker), ev); + } + else { + if (typeof customAction === 'string') { + viewType = customAction; + } + calendarApi.zoomTo(dateMarker, viewType); + } + }; + CalendarContent.prototype.buildAppendContent = function () { + var props = this.props; + var children = props.pluginHooks.viewContainerAppends.map(function (buildAppendContent) { return buildAppendContent(props); }); + return createElement.apply(void 0, __spreadArray([Fragment, {}], children)); + }; + CalendarContent.prototype.renderView = function (props) { + var pluginHooks = props.pluginHooks; + var viewSpec = props.viewSpec; + var viewProps = { + dateProfile: props.dateProfile, + businessHours: props.businessHours, + eventStore: props.renderableEventStore, + eventUiBases: props.eventUiBases, + dateSelection: props.dateSelection, + eventSelection: props.eventSelection, + eventDrag: props.eventDrag, + eventResize: props.eventResize, + isHeightAuto: props.isHeightAuto, + forPrint: props.forPrint, + }; + var transformers = this.buildViewPropTransformers(pluginHooks.viewPropsTransformers); + for (var _i = 0, transformers_1 = transformers; _i < transformers_1.length; _i++) { + var transformer = transformers_1[_i]; + __assign(viewProps, transformer.transform(viewProps, props)); + } + var ViewComponent = viewSpec.component; + return (createElement(ViewComponent, __assign({}, viewProps))); + }; + return CalendarContent; + }(PureComponent)); + function buildToolbarProps(viewSpec, dateProfile, dateProfileGenerator, currentDate, now, title) { + // don't force any date-profiles to valid date profiles (the `false`) so that we can tell if it's invalid + var todayInfo = dateProfileGenerator.build(now, undefined, false); // TODO: need `undefined` or else INFINITE LOOP for some reason + var prevInfo = dateProfileGenerator.buildPrev(dateProfile, currentDate, false); + var nextInfo = dateProfileGenerator.buildNext(dateProfile, currentDate, false); + return { + title: title, + activeButton: viewSpec.type, + isTodayEnabled: todayInfo.isValid && !rangeContainsMarker(dateProfile.currentRange, now), + isPrevEnabled: prevInfo.isValid, + isNextEnabled: nextInfo.isValid, + }; + } + // Plugin + // ----------------------------------------------------------------------------------------------------------------- + function buildViewPropTransformers(theClasses) { + return theClasses.map(function (TheClass) { return new TheClass(); }); + } + + var CalendarRoot = /** @class */ (function (_super) { + __extends(CalendarRoot, _super); + function CalendarRoot() { + var _this = _super !== null && _super.apply(this, arguments) || this; + _this.state = { + forPrint: false, + }; + _this.handleBeforePrint = function () { + _this.setState({ forPrint: true }); + }; + _this.handleAfterPrint = function () { + _this.setState({ forPrint: false }); + }; + return _this; + } + CalendarRoot.prototype.render = function () { + var props = this.props; + var options = props.options; + var forPrint = this.state.forPrint; + var isHeightAuto = forPrint || options.height === 'auto' || options.contentHeight === 'auto'; + var height = (!isHeightAuto && options.height != null) ? options.height : ''; + var classNames = [ + 'fc', + forPrint ? 'fc-media-print' : 'fc-media-screen', + "fc-direction-" + options.direction, + props.theme.getClass('root'), + ]; + if (!getCanVGrowWithinCell()) { + classNames.push('fc-liquid-hack'); + } + return props.children(classNames, height, isHeightAuto, forPrint); + }; + CalendarRoot.prototype.componentDidMount = function () { + var emitter = this.props.emitter; + emitter.on('_beforeprint', this.handleBeforePrint); + emitter.on('_afterprint', this.handleAfterPrint); + }; + CalendarRoot.prototype.componentWillUnmount = function () { + var emitter = this.props.emitter; + emitter.off('_beforeprint', this.handleBeforePrint); + emitter.off('_afterprint', this.handleAfterPrint); + }; + return CalendarRoot; + }(BaseComponent)); + + // Computes a default column header formatting string if `colFormat` is not explicitly defined + function computeFallbackHeaderFormat(datesRepDistinctDays, dayCnt) { + // if more than one week row, or if there are a lot of columns with not much space, + // put just the day numbers will be in each cell + if (!datesRepDistinctDays || dayCnt > 10) { + return createFormatter({ weekday: 'short' }); // "Sat" + } + if (dayCnt > 1) { + return createFormatter({ weekday: 'short', month: 'numeric', day: 'numeric', omitCommas: true }); // "Sat 11/12" + } + return createFormatter({ weekday: 'long' }); // "Saturday" + } + + var CLASS_NAME = 'fc-col-header-cell'; // do the cushion too? no + function renderInner$1(hookProps) { + return hookProps.text; + } + + var TableDateCell = /** @class */ (function (_super) { + __extends(TableDateCell, _super); + function TableDateCell() { + return _super !== null && _super.apply(this, arguments) || this; + } + TableDateCell.prototype.render = function () { + var _a = this.context, dateEnv = _a.dateEnv, options = _a.options, theme = _a.theme, viewApi = _a.viewApi; + var props = this.props; + var date = props.date, dateProfile = props.dateProfile; + var dayMeta = getDateMeta(date, props.todayRange, null, dateProfile); + var classNames = [CLASS_NAME].concat(getDayClassNames(dayMeta, theme)); + var text = dateEnv.format(date, props.dayHeaderFormat); + // if colCnt is 1, we are already in a day-view and don't need a navlink + var navLinkAttrs = (options.navLinks && !dayMeta.isDisabled && props.colCnt > 1) + ? { 'data-navlink': buildNavLinkData(date), tabIndex: 0 } + : {}; + var hookProps = __assign(__assign(__assign({ date: dateEnv.toDate(date), view: viewApi }, props.extraHookProps), { text: text }), dayMeta); + return (createElement(RenderHook, { hookProps: hookProps, classNames: options.dayHeaderClassNames, content: options.dayHeaderContent, defaultContent: renderInner$1, didMount: options.dayHeaderDidMount, willUnmount: options.dayHeaderWillUnmount }, function (rootElRef, customClassNames, innerElRef, innerContent) { return (createElement("th", __assign({ ref: rootElRef, className: classNames.concat(customClassNames).join(' '), "data-date": !dayMeta.isDisabled ? formatDayString(date) : undefined, colSpan: props.colSpan }, props.extraDataAttrs), + createElement("div", { className: "fc-scrollgrid-sync-inner" }, !dayMeta.isDisabled && (createElement("a", __assign({ ref: innerElRef, className: [ + 'fc-col-header-cell-cushion', + props.isSticky ? 'fc-sticky' : '', + ].join(' ') }, navLinkAttrs), innerContent))))); })); + }; + return TableDateCell; + }(BaseComponent)); + + var TableDowCell = /** @class */ (function (_super) { + __extends(TableDowCell, _super); + function TableDowCell() { + return _super !== null && _super.apply(this, arguments) || this; + } + TableDowCell.prototype.render = function () { + var props = this.props; + var _a = this.context, dateEnv = _a.dateEnv, theme = _a.theme, viewApi = _a.viewApi, options = _a.options; + var date = addDays(new Date(259200000), props.dow); // start with Sun, 04 Jan 1970 00:00:00 GMT + var dateMeta = { + dow: props.dow, + isDisabled: false, + isFuture: false, + isPast: false, + isToday: false, + isOther: false, + }; + var classNames = [CLASS_NAME].concat(getDayClassNames(dateMeta, theme), props.extraClassNames || []); + var text = dateEnv.format(date, props.dayHeaderFormat); + var hookProps = __assign(__assign(__assign(__assign({ // TODO: make this public? + date: date }, dateMeta), { view: viewApi }), props.extraHookProps), { text: text }); + return (createElement(RenderHook, { hookProps: hookProps, classNames: options.dayHeaderClassNames, content: options.dayHeaderContent, defaultContent: renderInner$1, didMount: options.dayHeaderDidMount, willUnmount: options.dayHeaderWillUnmount }, function (rootElRef, customClassNames, innerElRef, innerContent) { return (createElement("th", __assign({ ref: rootElRef, className: classNames.concat(customClassNames).join(' '), colSpan: props.colSpan }, props.extraDataAttrs), + createElement("div", { className: "fc-scrollgrid-sync-inner" }, + createElement("a", { className: [ + 'fc-col-header-cell-cushion', + props.isSticky ? 'fc-sticky' : '', + ].join(' '), ref: innerElRef }, innerContent)))); })); + }; + return TableDowCell; + }(BaseComponent)); + + var NowTimer = /** @class */ (function (_super) { + __extends(NowTimer, _super); + function NowTimer(props, context) { + var _this = _super.call(this, props, context) || this; + _this.initialNowDate = getNow(context.options.now, context.dateEnv); + _this.initialNowQueriedMs = new Date().valueOf(); + _this.state = _this.computeTiming().currentState; + return _this; + } + NowTimer.prototype.render = function () { + var _a = this, props = _a.props, state = _a.state; + return props.children(state.nowDate, state.todayRange); + }; + NowTimer.prototype.componentDidMount = function () { + this.setTimeout(); + }; + NowTimer.prototype.componentDidUpdate = function (prevProps) { + if (prevProps.unit !== this.props.unit) { + this.clearTimeout(); + this.setTimeout(); + } + }; + NowTimer.prototype.componentWillUnmount = function () { + this.clearTimeout(); + }; + NowTimer.prototype.computeTiming = function () { + var _a = this, props = _a.props, context = _a.context; + var unroundedNow = addMs(this.initialNowDate, new Date().valueOf() - this.initialNowQueriedMs); + var currentUnitStart = context.dateEnv.startOf(unroundedNow, props.unit); + var nextUnitStart = context.dateEnv.add(currentUnitStart, createDuration(1, props.unit)); + var waitMs = nextUnitStart.valueOf() - unroundedNow.valueOf(); + // there is a max setTimeout ms value (https://stackoverflow.com/a/3468650/96342) + // ensure no longer than a day + waitMs = Math.min(1000 * 60 * 60 * 24, waitMs); + return { + currentState: { nowDate: currentUnitStart, todayRange: buildDayRange(currentUnitStart) }, + nextState: { nowDate: nextUnitStart, todayRange: buildDayRange(nextUnitStart) }, + waitMs: waitMs, + }; + }; + NowTimer.prototype.setTimeout = function () { + var _this = this; + var _a = this.computeTiming(), nextState = _a.nextState, waitMs = _a.waitMs; + this.timeoutId = setTimeout(function () { + _this.setState(nextState, function () { + _this.setTimeout(); + }); + }, waitMs); + }; + NowTimer.prototype.clearTimeout = function () { + if (this.timeoutId) { + clearTimeout(this.timeoutId); + } + }; + NowTimer.contextType = ViewContextType; + return NowTimer; + }(Component)); + function buildDayRange(date) { + var start = startOfDay(date); + var end = addDays(start, 1); + return { start: start, end: end }; + } + + var DayHeader = /** @class */ (function (_super) { + __extends(DayHeader, _super); + function DayHeader() { + var _this = _super !== null && _super.apply(this, arguments) || this; + _this.createDayHeaderFormatter = memoize(createDayHeaderFormatter); + return _this; + } + DayHeader.prototype.render = function () { + var context = this.context; + var _a = this.props, dates = _a.dates, dateProfile = _a.dateProfile, datesRepDistinctDays = _a.datesRepDistinctDays, renderIntro = _a.renderIntro; + var dayHeaderFormat = this.createDayHeaderFormatter(context.options.dayHeaderFormat, datesRepDistinctDays, dates.length); + return (createElement(NowTimer, { unit: "day" }, function (nowDate, todayRange) { return (createElement("tr", null, + renderIntro && renderIntro('day'), + dates.map(function (date) { return (datesRepDistinctDays ? (createElement(TableDateCell, { key: date.toISOString(), date: date, dateProfile: dateProfile, todayRange: todayRange, colCnt: dates.length, dayHeaderFormat: dayHeaderFormat })) : (createElement(TableDowCell, { key: date.getUTCDay(), dow: date.getUTCDay(), dayHeaderFormat: dayHeaderFormat }))); }))); })); + }; + return DayHeader; + }(BaseComponent)); + function createDayHeaderFormatter(explicitFormat, datesRepDistinctDays, dateCnt) { + return explicitFormat || computeFallbackHeaderFormat(datesRepDistinctDays, dateCnt); + } + + var DaySeriesModel = /** @class */ (function () { + function DaySeriesModel(range, dateProfileGenerator) { + var date = range.start; + var end = range.end; + var indices = []; + var dates = []; + var dayIndex = -1; + while (date < end) { // loop each day from start to end + if (dateProfileGenerator.isHiddenDay(date)) { + indices.push(dayIndex + 0.5); // mark that it's between indices + } + else { + dayIndex += 1; + indices.push(dayIndex); + dates.push(date); + } + date = addDays(date, 1); + } + this.dates = dates; + this.indices = indices; + this.cnt = dates.length; + } + DaySeriesModel.prototype.sliceRange = function (range) { + var firstIndex = this.getDateDayIndex(range.start); // inclusive first index + var lastIndex = this.getDateDayIndex(addDays(range.end, -1)); // inclusive last index + var clippedFirstIndex = Math.max(0, firstIndex); + var clippedLastIndex = Math.min(this.cnt - 1, lastIndex); + // deal with in-between indices + clippedFirstIndex = Math.ceil(clippedFirstIndex); // in-between starts round to next cell + clippedLastIndex = Math.floor(clippedLastIndex); // in-between ends round to prev cell + if (clippedFirstIndex <= clippedLastIndex) { + return { + firstIndex: clippedFirstIndex, + lastIndex: clippedLastIndex, + isStart: firstIndex === clippedFirstIndex, + isEnd: lastIndex === clippedLastIndex, + }; + } + return null; + }; + // Given a date, returns its chronolocial cell-index from the first cell of the grid. + // If the date lies between cells (because of hiddenDays), returns a floating-point value between offsets. + // If before the first offset, returns a negative number. + // If after the last offset, returns an offset past the last cell offset. + // Only works for *start* dates of cells. Will not work for exclusive end dates for cells. + DaySeriesModel.prototype.getDateDayIndex = function (date) { + var indices = this.indices; + var dayOffset = Math.floor(diffDays(this.dates[0], date)); + if (dayOffset < 0) { + return indices[0] - 1; + } + if (dayOffset >= indices.length) { + return indices[indices.length - 1] + 1; + } + return indices[dayOffset]; + }; + return DaySeriesModel; + }()); + + var DayTableModel = /** @class */ (function () { + function DayTableModel(daySeries, breakOnWeeks) { + var dates = daySeries.dates; + var daysPerRow; + var firstDay; + var rowCnt; + if (breakOnWeeks) { + // count columns until the day-of-week repeats + firstDay = dates[0].getUTCDay(); + for (daysPerRow = 1; daysPerRow < dates.length; daysPerRow += 1) { + if (dates[daysPerRow].getUTCDay() === firstDay) { + break; + } + } + rowCnt = Math.ceil(dates.length / daysPerRow); + } + else { + rowCnt = 1; + daysPerRow = dates.length; + } + this.rowCnt = rowCnt; + this.colCnt = daysPerRow; + this.daySeries = daySeries; + this.cells = this.buildCells(); + this.headerDates = this.buildHeaderDates(); + } + DayTableModel.prototype.buildCells = function () { + var rows = []; + for (var row = 0; row < this.rowCnt; row += 1) { + var cells = []; + for (var col = 0; col < this.colCnt; col += 1) { + cells.push(this.buildCell(row, col)); + } + rows.push(cells); + } + return rows; + }; + DayTableModel.prototype.buildCell = function (row, col) { + var date = this.daySeries.dates[row * this.colCnt + col]; + return { + key: date.toISOString(), + date: date, + }; + }; + DayTableModel.prototype.buildHeaderDates = function () { + var dates = []; + for (var col = 0; col < this.colCnt; col += 1) { + dates.push(this.cells[0][col].date); + } + return dates; + }; + DayTableModel.prototype.sliceRange = function (range) { + var colCnt = this.colCnt; + var seriesSeg = this.daySeries.sliceRange(range); + var segs = []; + if (seriesSeg) { + var firstIndex = seriesSeg.firstIndex, lastIndex = seriesSeg.lastIndex; + var index = firstIndex; + while (index <= lastIndex) { + var row = Math.floor(index / colCnt); + var nextIndex = Math.min((row + 1) * colCnt, lastIndex + 1); + segs.push({ + row: row, + firstCol: index % colCnt, + lastCol: (nextIndex - 1) % colCnt, + isStart: seriesSeg.isStart && index === firstIndex, + isEnd: seriesSeg.isEnd && (nextIndex - 1) === lastIndex, + }); + index = nextIndex; + } + } + return segs; + }; + return DayTableModel; + }()); + + var Slicer = /** @class */ (function () { + function Slicer() { + this.sliceBusinessHours = memoize(this._sliceBusinessHours); + this.sliceDateSelection = memoize(this._sliceDateSpan); + this.sliceEventStore = memoize(this._sliceEventStore); + this.sliceEventDrag = memoize(this._sliceInteraction); + this.sliceEventResize = memoize(this._sliceInteraction); + this.forceDayIfListItem = false; // hack + } + Slicer.prototype.sliceProps = function (props, dateProfile, nextDayThreshold, context) { + var extraArgs = []; + for (var _i = 4; _i < arguments.length; _i++) { + extraArgs[_i - 4] = arguments[_i]; + } + var eventUiBases = props.eventUiBases; + var eventSegs = this.sliceEventStore.apply(this, __spreadArray([props.eventStore, eventUiBases, dateProfile, nextDayThreshold], extraArgs)); + return { + dateSelectionSegs: this.sliceDateSelection.apply(this, __spreadArray([props.dateSelection, eventUiBases, context], extraArgs)), + businessHourSegs: this.sliceBusinessHours.apply(this, __spreadArray([props.businessHours, dateProfile, nextDayThreshold, context], extraArgs)), + fgEventSegs: eventSegs.fg, + bgEventSegs: eventSegs.bg, + eventDrag: this.sliceEventDrag.apply(this, __spreadArray([props.eventDrag, eventUiBases, dateProfile, nextDayThreshold], extraArgs)), + eventResize: this.sliceEventResize.apply(this, __spreadArray([props.eventResize, eventUiBases, dateProfile, nextDayThreshold], extraArgs)), + eventSelection: props.eventSelection, + }; // TODO: give interactionSegs? + }; + Slicer.prototype.sliceNowDate = function (// does not memoize + date, context) { + var extraArgs = []; + for (var _i = 2; _i < arguments.length; _i++) { + extraArgs[_i - 2] = arguments[_i]; + } + return this._sliceDateSpan.apply(this, __spreadArray([{ range: { start: date, end: addMs(date, 1) }, allDay: false }, + {}, + context], extraArgs)); + }; + Slicer.prototype._sliceBusinessHours = function (businessHours, dateProfile, nextDayThreshold, context) { + var extraArgs = []; + for (var _i = 4; _i < arguments.length; _i++) { + extraArgs[_i - 4] = arguments[_i]; + } + if (!businessHours) { + return []; + } + return this._sliceEventStore.apply(this, __spreadArray([expandRecurring(businessHours, computeActiveRange(dateProfile, Boolean(nextDayThreshold)), context), + {}, + dateProfile, + nextDayThreshold], extraArgs)).bg; + }; + Slicer.prototype._sliceEventStore = function (eventStore, eventUiBases, dateProfile, nextDayThreshold) { + var extraArgs = []; + for (var _i = 4; _i < arguments.length; _i++) { + extraArgs[_i - 4] = arguments[_i]; + } + if (eventStore) { + var rangeRes = sliceEventStore(eventStore, eventUiBases, computeActiveRange(dateProfile, Boolean(nextDayThreshold)), nextDayThreshold); + return { + bg: this.sliceEventRanges(rangeRes.bg, extraArgs), + fg: this.sliceEventRanges(rangeRes.fg, extraArgs), + }; + } + return { bg: [], fg: [] }; + }; + Slicer.prototype._sliceInteraction = function (interaction, eventUiBases, dateProfile, nextDayThreshold) { + var extraArgs = []; + for (var _i = 4; _i < arguments.length; _i++) { + extraArgs[_i - 4] = arguments[_i]; + } + if (!interaction) { + return null; + } + var rangeRes = sliceEventStore(interaction.mutatedEvents, eventUiBases, computeActiveRange(dateProfile, Boolean(nextDayThreshold)), nextDayThreshold); + return { + segs: this.sliceEventRanges(rangeRes.fg, extraArgs), + affectedInstances: interaction.affectedEvents.instances, + isEvent: interaction.isEvent, + }; + }; + Slicer.prototype._sliceDateSpan = function (dateSpan, eventUiBases, context) { + var extraArgs = []; + for (var _i = 3; _i < arguments.length; _i++) { + extraArgs[_i - 3] = arguments[_i]; + } + if (!dateSpan) { + return []; + } + var eventRange = fabricateEventRange(dateSpan, eventUiBases, context); + var segs = this.sliceRange.apply(this, __spreadArray([dateSpan.range], extraArgs)); + for (var _a = 0, segs_1 = segs; _a < segs_1.length; _a++) { + var seg = segs_1[_a]; + seg.eventRange = eventRange; + } + return segs; + }; + /* + "complete" seg means it has component and eventRange + */ + Slicer.prototype.sliceEventRanges = function (eventRanges, extraArgs) { + var segs = []; + for (var _i = 0, eventRanges_1 = eventRanges; _i < eventRanges_1.length; _i++) { + var eventRange = eventRanges_1[_i]; + segs.push.apply(segs, this.sliceEventRange(eventRange, extraArgs)); + } + return segs; + }; + /* + "complete" seg means it has component and eventRange + */ + Slicer.prototype.sliceEventRange = function (eventRange, extraArgs) { + var dateRange = eventRange.range; + // hack to make multi-day events that are being force-displayed as list-items to take up only one day + if (this.forceDayIfListItem && eventRange.ui.display === 'list-item') { + dateRange = { + start: dateRange.start, + end: addDays(dateRange.start, 1), + }; + } + var segs = this.sliceRange.apply(this, __spreadArray([dateRange], extraArgs)); + for (var _i = 0, segs_2 = segs; _i < segs_2.length; _i++) { + var seg = segs_2[_i]; + seg.eventRange = eventRange; + seg.isStart = eventRange.isStart && seg.isStart; + seg.isEnd = eventRange.isEnd && seg.isEnd; + } + return segs; + }; + return Slicer; + }()); + /* + for incorporating slotMinTime/slotMaxTime if appropriate + TODO: should be part of DateProfile! + TimelineDateProfile already does this btw + */ + function computeActiveRange(dateProfile, isComponentAllDay) { + var range = dateProfile.activeRange; + if (isComponentAllDay) { + return range; + } + return { + start: addMs(range.start, dateProfile.slotMinTime.milliseconds), + end: addMs(range.end, dateProfile.slotMaxTime.milliseconds - 864e5), // 864e5 = ms in a day + }; + } + + // high-level segmenting-aware tester functions + // ------------------------------------------------------------------------------------------------------------------------ + function isInteractionValid(interaction, dateProfile, context) { + var instances = interaction.mutatedEvents.instances; + for (var instanceId in instances) { + if (!rangeContainsRange(dateProfile.validRange, instances[instanceId].range)) { + return false; + } + } + return isNewPropsValid({ eventDrag: interaction }, context); // HACK: the eventDrag props is used for ALL interactions + } + function isDateSelectionValid(dateSelection, dateProfile, context) { + if (!rangeContainsRange(dateProfile.validRange, dateSelection.range)) { + return false; + } + return isNewPropsValid({ dateSelection: dateSelection }, context); + } + function isNewPropsValid(newProps, context) { + var calendarState = context.getCurrentData(); + var props = __assign({ businessHours: calendarState.businessHours, dateSelection: '', eventStore: calendarState.eventStore, eventUiBases: calendarState.eventUiBases, eventSelection: '', eventDrag: null, eventResize: null }, newProps); + return (context.pluginHooks.isPropsValid || isPropsValid)(props, context); + } + function isPropsValid(state, context, dateSpanMeta, filterConfig) { + if (dateSpanMeta === void 0) { dateSpanMeta = {}; } + if (state.eventDrag && !isInteractionPropsValid(state, context, dateSpanMeta, filterConfig)) { + return false; + } + if (state.dateSelection && !isDateSelectionPropsValid(state, context, dateSpanMeta, filterConfig)) { + return false; + } + return true; + } + // Moving Event Validation + // ------------------------------------------------------------------------------------------------------------------------ + function isInteractionPropsValid(state, context, dateSpanMeta, filterConfig) { + var currentState = context.getCurrentData(); + var interaction = state.eventDrag; // HACK: the eventDrag props is used for ALL interactions + var subjectEventStore = interaction.mutatedEvents; + var subjectDefs = subjectEventStore.defs; + var subjectInstances = subjectEventStore.instances; + var subjectConfigs = compileEventUis(subjectDefs, interaction.isEvent ? + state.eventUiBases : + { '': currentState.selectionConfig }); + if (filterConfig) { + subjectConfigs = mapHash(subjectConfigs, filterConfig); + } + // exclude the subject events. TODO: exclude defs too? + var otherEventStore = excludeInstances(state.eventStore, interaction.affectedEvents.instances); + var otherDefs = otherEventStore.defs; + var otherInstances = otherEventStore.instances; + var otherConfigs = compileEventUis(otherDefs, state.eventUiBases); + for (var subjectInstanceId in subjectInstances) { + var subjectInstance = subjectInstances[subjectInstanceId]; + var subjectRange = subjectInstance.range; + var subjectConfig = subjectConfigs[subjectInstance.defId]; + var subjectDef = subjectDefs[subjectInstance.defId]; + // constraint + if (!allConstraintsPass(subjectConfig.constraints, subjectRange, otherEventStore, state.businessHours, context)) { + return false; + } + // overlap + var eventOverlap = context.options.eventOverlap; + var eventOverlapFunc = typeof eventOverlap === 'function' ? eventOverlap : null; + for (var otherInstanceId in otherInstances) { + var otherInstance = otherInstances[otherInstanceId]; + // intersect! evaluate + if (rangesIntersect(subjectRange, otherInstance.range)) { + var otherOverlap = otherConfigs[otherInstance.defId].overlap; + // consider the other event's overlap. only do this if the subject event is a "real" event + if (otherOverlap === false && interaction.isEvent) { + return false; + } + if (subjectConfig.overlap === false) { + return false; + } + if (eventOverlapFunc && !eventOverlapFunc(new EventApi(context, otherDefs[otherInstance.defId], otherInstance), // still event + new EventApi(context, subjectDef, subjectInstance))) { + return false; + } + } + } + // allow (a function) + var calendarEventStore = currentState.eventStore; // need global-to-calendar, not local to component (splittable)state + for (var _i = 0, _a = subjectConfig.allows; _i < _a.length; _i++) { + var subjectAllow = _a[_i]; + var subjectDateSpan = __assign(__assign({}, dateSpanMeta), { range: subjectInstance.range, allDay: subjectDef.allDay }); + var origDef = calendarEventStore.defs[subjectDef.defId]; + var origInstance = calendarEventStore.instances[subjectInstanceId]; + var eventApi = void 0; + if (origDef) { // was previously in the calendar + eventApi = new EventApi(context, origDef, origInstance); + } + else { // was an external event + eventApi = new EventApi(context, subjectDef); // no instance, because had no dates + } + if (!subjectAllow(buildDateSpanApiWithContext(subjectDateSpan, context), eventApi)) { + return false; + } + } + } + return true; + } + // Date Selection Validation + // ------------------------------------------------------------------------------------------------------------------------ + function isDateSelectionPropsValid(state, context, dateSpanMeta, filterConfig) { + var relevantEventStore = state.eventStore; + var relevantDefs = relevantEventStore.defs; + var relevantInstances = relevantEventStore.instances; + var selection = state.dateSelection; + var selectionRange = selection.range; + var selectionConfig = context.getCurrentData().selectionConfig; + if (filterConfig) { + selectionConfig = filterConfig(selectionConfig); + } + // constraint + if (!allConstraintsPass(selectionConfig.constraints, selectionRange, relevantEventStore, state.businessHours, context)) { + return false; + } + // overlap + var selectOverlap = context.options.selectOverlap; + var selectOverlapFunc = typeof selectOverlap === 'function' ? selectOverlap : null; + for (var relevantInstanceId in relevantInstances) { + var relevantInstance = relevantInstances[relevantInstanceId]; + // intersect! evaluate + if (rangesIntersect(selectionRange, relevantInstance.range)) { + if (selectionConfig.overlap === false) { + return false; + } + if (selectOverlapFunc && !selectOverlapFunc(new EventApi(context, relevantDefs[relevantInstance.defId], relevantInstance), null)) { + return false; + } + } + } + // allow (a function) + for (var _i = 0, _a = selectionConfig.allows; _i < _a.length; _i++) { + var selectionAllow = _a[_i]; + var fullDateSpan = __assign(__assign({}, dateSpanMeta), selection); + if (!selectionAllow(buildDateSpanApiWithContext(fullDateSpan, context), null)) { + return false; + } + } + return true; + } + // Constraint Utils + // ------------------------------------------------------------------------------------------------------------------------ + function allConstraintsPass(constraints, subjectRange, otherEventStore, businessHoursUnexpanded, context) { + for (var _i = 0, constraints_1 = constraints; _i < constraints_1.length; _i++) { + var constraint = constraints_1[_i]; + if (!anyRangesContainRange(constraintToRanges(constraint, subjectRange, otherEventStore, businessHoursUnexpanded, context), subjectRange)) { + return false; + } + } + return true; + } + function constraintToRanges(constraint, subjectRange, // for expanding a recurring constraint, or expanding business hours + otherEventStore, // for if constraint is an even group ID + businessHoursUnexpanded, // for if constraint is 'businessHours' + context) { + if (constraint === 'businessHours') { + return eventStoreToRanges(expandRecurring(businessHoursUnexpanded, subjectRange, context)); + } + if (typeof constraint === 'string') { // an group ID + return eventStoreToRanges(filterEventStoreDefs(otherEventStore, function (eventDef) { return eventDef.groupId === constraint; })); + } + if (typeof constraint === 'object' && constraint) { // non-null object + return eventStoreToRanges(expandRecurring(constraint, subjectRange, context)); + } + return []; // if it's false + } + // TODO: move to event-store file? + function eventStoreToRanges(eventStore) { + var instances = eventStore.instances; + var ranges = []; + for (var instanceId in instances) { + ranges.push(instances[instanceId].range); + } + return ranges; + } + // TODO: move to geom file? + function anyRangesContainRange(outerRanges, innerRange) { + for (var _i = 0, outerRanges_1 = outerRanges; _i < outerRanges_1.length; _i++) { + var outerRange = outerRanges_1[_i]; + if (rangeContainsRange(outerRange, innerRange)) { + return true; + } + } + return false; + } + + var VISIBLE_HIDDEN_RE = /^(visible|hidden)$/; + var Scroller = /** @class */ (function (_super) { + __extends(Scroller, _super); + function Scroller() { + var _this = _super !== null && _super.apply(this, arguments) || this; + _this.handleEl = function (el) { + _this.el = el; + setRef(_this.props.elRef, el); + }; + return _this; + } + Scroller.prototype.render = function () { + var props = this.props; + var liquid = props.liquid, liquidIsAbsolute = props.liquidIsAbsolute; + var isAbsolute = liquid && liquidIsAbsolute; + var className = ['fc-scroller']; + if (liquid) { + if (liquidIsAbsolute) { + className.push('fc-scroller-liquid-absolute'); + } + else { + className.push('fc-scroller-liquid'); + } + } + return (createElement("div", { ref: this.handleEl, className: className.join(' '), style: { + overflowX: props.overflowX, + overflowY: props.overflowY, + left: (isAbsolute && -(props.overcomeLeft || 0)) || '', + right: (isAbsolute && -(props.overcomeRight || 0)) || '', + bottom: (isAbsolute && -(props.overcomeBottom || 0)) || '', + marginLeft: (!isAbsolute && -(props.overcomeLeft || 0)) || '', + marginRight: (!isAbsolute && -(props.overcomeRight || 0)) || '', + marginBottom: (!isAbsolute && -(props.overcomeBottom || 0)) || '', + maxHeight: props.maxHeight || '', + } }, props.children)); + }; + Scroller.prototype.needsXScrolling = function () { + if (VISIBLE_HIDDEN_RE.test(this.props.overflowX)) { + return false; + } + // testing scrollWidth>clientWidth is unreliable cross-browser when pixel heights aren't integers. + // much more reliable to see if children are taller than the scroller, even tho doesn't account for + // inner-child margins and absolute positioning + var el = this.el; + var realClientWidth = this.el.getBoundingClientRect().width - this.getYScrollbarWidth(); + var children = el.children; + for (var i = 0; i < children.length; i += 1) { + var childEl = children[i]; + if (childEl.getBoundingClientRect().width > realClientWidth) { + return true; + } + } + return false; + }; + Scroller.prototype.needsYScrolling = function () { + if (VISIBLE_HIDDEN_RE.test(this.props.overflowY)) { + return false; + } + // testing scrollHeight>clientHeight is unreliable cross-browser when pixel heights aren't integers. + // much more reliable to see if children are taller than the scroller, even tho doesn't account for + // inner-child margins and absolute positioning + var el = this.el; + var realClientHeight = this.el.getBoundingClientRect().height - this.getXScrollbarWidth(); + var children = el.children; + for (var i = 0; i < children.length; i += 1) { + var childEl = children[i]; + if (childEl.getBoundingClientRect().height > realClientHeight) { + return true; + } + } + return false; + }; + Scroller.prototype.getXScrollbarWidth = function () { + if (VISIBLE_HIDDEN_RE.test(this.props.overflowX)) { + return 0; + } + return this.el.offsetHeight - this.el.clientHeight; // only works because we guarantee no borders. TODO: add to CSS with important? + }; + Scroller.prototype.getYScrollbarWidth = function () { + if (VISIBLE_HIDDEN_RE.test(this.props.overflowY)) { + return 0; + } + return this.el.offsetWidth - this.el.clientWidth; // only works because we guarantee no borders. TODO: add to CSS with important? + }; + return Scroller; + }(BaseComponent)); + + /* + TODO: somehow infer OtherArgs from masterCallback? + TODO: infer RefType from masterCallback if provided + */ + var RefMap = /** @class */ (function () { + function RefMap(masterCallback) { + var _this = this; + this.masterCallback = masterCallback; + this.currentMap = {}; + this.depths = {}; + this.callbackMap = {}; + this.handleValue = function (val, key) { + var _a = _this, depths = _a.depths, currentMap = _a.currentMap; + var removed = false; + var added = false; + if (val !== null) { + // for bug... ACTUALLY: can probably do away with this now that callers don't share numeric indices anymore + removed = (key in currentMap); + currentMap[key] = val; + depths[key] = (depths[key] || 0) + 1; + added = true; + } + else { + depths[key] -= 1; + if (!depths[key]) { + delete currentMap[key]; + delete _this.callbackMap[key]; + removed = true; + } + } + if (_this.masterCallback) { + if (removed) { + _this.masterCallback(null, String(key)); + } + if (added) { + _this.masterCallback(val, String(key)); + } + } + }; + } + RefMap.prototype.createRef = function (key) { + var _this = this; + var refCallback = this.callbackMap[key]; + if (!refCallback) { + refCallback = this.callbackMap[key] = function (val) { + _this.handleValue(val, String(key)); + }; + } + return refCallback; + }; + // TODO: check callers that don't care about order. should use getAll instead + // NOTE: this method has become less valuable now that we are encouraged to map order by some other index + // TODO: provide ONE array-export function, buildArray, which fails on non-numeric indexes. caller can manipulate and "collect" + RefMap.prototype.collect = function (startIndex, endIndex, step) { + return collectFromHash(this.currentMap, startIndex, endIndex, step); + }; + RefMap.prototype.getAll = function () { + return hashValuesToArray(this.currentMap); + }; + return RefMap; + }()); + + function computeShrinkWidth(chunkEls) { + var shrinkCells = findElements(chunkEls, '.fc-scrollgrid-shrink'); + var largestWidth = 0; + for (var _i = 0, shrinkCells_1 = shrinkCells; _i < shrinkCells_1.length; _i++) { + var shrinkCell = shrinkCells_1[_i]; + largestWidth = Math.max(largestWidth, computeSmallestCellWidth(shrinkCell)); + } + return Math.ceil(largestWidth); // 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 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 ? + createElement("tr", { className: "fc-scrollgrid-section" }, + createElement("td", { className: 'fc-timegrid-divider ' + context.theme.getClass('tableCellShaded') }))), + }); + } + sections.push({ + type: 'body', + key: 'body', + liquid: true, + expandRows: Boolean(context.options.expandRows), + chunk: { + scrollerElRef: this.scrollerElRef, + content: timeContent, + }, + }); + return (createElement(ViewRoot, { viewSpec: context.viewSpec, elRef: this.rootElRef }, function (rootElRef, classNames) { return (createElement("div", { className: ['fc-timegrid'].concat(classNames).join(' '), ref: rootElRef }, + createElement(SimpleScrollGrid, { liquid: !props.isHeightAuto && !props.forPrint, collapsibleWidth: props.forPrint, cols: [{ width: 'shrink' }], sections: sections }))); })); + }; + TimeColsView.prototype.renderHScrollLayout = function (headerRowContent, allDayContent, timeContent, colCnt, dayMinWidth, slatMetas, slatCoords) { + var _this = this; + var ScrollGrid = this.context.pluginHooks.scrollGridImpl; + if (!ScrollGrid) { + throw new Error('No ScrollGrid implementation'); + } + var _a = this, context = _a.context, props = _a.props; + 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, + syncRowHeights: true, + chunks: [ + { + key: 'axis', + rowContent: function (arg) { return (createElement("tr", null, _this.renderHeadAxis('day', arg.rowSyncHeights[0]))); }, + }, + { + key: 'cols', + elRef: this.headerElRef, + tableClassName: 'fc-col-header', + rowContent: headerRowContent, + }, + ], + }); + } + if (allDayContent) { + sections.push({ + type: 'body', + key: 'all-day', + syncRowHeights: true, + chunks: [ + { + key: 'axis', + rowContent: function (contentArg) { return (createElement("tr", null, _this.renderTableRowAxis(contentArg.rowSyncHeights[0]))); }, + }, + { + key: 'cols', + content: allDayContent, + }, + ], + }); + sections.push({ + key: 'all-day-divider', + type: 'body', + outerContent: ( // TODO: rename to cellContent so don't need to define ? + createElement("tr", { className: "fc-scrollgrid-section" }, + createElement("td", { colSpan: 2, className: 'fc-timegrid-divider ' + context.theme.getClass('tableCellShaded') }))), + }); + } + var isNowIndicator = context.options.nowIndicator; + sections.push({ + type: 'body', + key: 'body', + liquid: true, + expandRows: Boolean(context.options.expandRows), + chunks: [ + { + key: 'axis', + content: function (arg) { return ( + // TODO: make this now-indicator arrow more DRY with TimeColsContent + createElement("div", { className: "fc-timegrid-axis-chunk" }, + createElement("table", { style: { height: arg.expandRows ? arg.clientHeight : '' } }, + arg.tableColGroupNode, + createElement("tbody", null, + createElement(TimeBodyAxis, { slatMetas: slatMetas }))), + createElement("div", { className: "fc-timegrid-now-indicator-container" }, + createElement(NowTimer, { unit: isNowIndicator ? 'minute' : 'day' /* hacky */ }, function (nowDate) { + var nowIndicatorTop = isNowIndicator && + slatCoords && + slatCoords.safeComputeTop(nowDate); // might return void + if (typeof nowIndicatorTop === 'number') { + return (createElement(NowIndicatorRoot, { isAxis: true, date: nowDate }, function (rootElRef, classNames, innerElRef, innerContent) { return (createElement("div", { ref: rootElRef, className: ['fc-timegrid-now-indicator-arrow'].concat(classNames).join(' '), style: { top: nowIndicatorTop } }, innerContent)); })); + } + return null; + })))); }, + }, + { + key: 'cols', + scrollerElRef: this.scrollerElRef, + content: timeContent, + }, + ], + }); + if (stickyFooterScrollbar) { + sections.push({ + key: 'footer', + type: 'footer', + isSticky: true, + chunks: [ + { + key: 'axis', + content: renderScrollShim, + }, + { + key: 'cols', + content: renderScrollShim, + }, + ], + }); + } + return (createElement(ViewRoot, { viewSpec: context.viewSpec, elRef: this.rootElRef }, function (rootElRef, classNames) { return (createElement("div", { className: ['fc-timegrid'].concat(classNames).join(' '), ref: rootElRef }, + createElement(ScrollGrid, { liquid: !props.isHeightAuto && !props.forPrint, collapsibleWidth: false, colGroups: [ + { width: 'shrink', cols: [{ width: 'shrink' }] }, + { cols: [{ span: colCnt, minWidth: dayMinWidth }] }, + ], sections: sections }))); })); + }; + /* Dimensions + ------------------------------------------------------------------------------------------------------------------*/ + TimeColsView.prototype.getAllDayMaxEventProps = function () { + var _a = this.context.options, dayMaxEvents = _a.dayMaxEvents, dayMaxEventRows = _a.dayMaxEventRows; + if (dayMaxEvents === true || dayMaxEventRows === true) { // is auto? + dayMaxEvents = undefined; + dayMaxEventRows = AUTO_ALL_DAY_MAX_EVENT_ROWS; // make sure "auto" goes to a real number + } + return { dayMaxEvents: dayMaxEvents, dayMaxEventRows: dayMaxEventRows }; + }; + return TimeColsView; + }(DateComponent)); + function renderAllDayInner$1(hookProps) { + return hookProps.text; + } + + var TimeColsSlatsCoords = /** @class */ (function () { + function TimeColsSlatsCoords(positions, dateProfile, slotDuration) { + this.positions = positions; + this.dateProfile = dateProfile; + this.slotDuration = slotDuration; + } + TimeColsSlatsCoords.prototype.safeComputeTop = function (date) { + var dateProfile = this.dateProfile; + if (rangeContainsMarker(dateProfile.currentRange, date)) { + var startOfDayDate = startOfDay(date); + var timeMs = date.valueOf() - startOfDayDate.valueOf(); + if (timeMs >= asRoughMs(dateProfile.slotMinTime) && + timeMs < asRoughMs(dateProfile.slotMaxTime)) { + return this.computeTimeTop(createDuration(timeMs)); + } + } + return null; + }; + // Computes the top coordinate, relative to the bounds of the grid, of the given date. + // A `startOfDayDate` must be given for avoiding ambiguity over how to treat midnight. + TimeColsSlatsCoords.prototype.computeDateTop = function (when, startOfDayDate) { + if (!startOfDayDate) { + startOfDayDate = startOfDay(when); + } + return this.computeTimeTop(createDuration(when.valueOf() - startOfDayDate.valueOf())); + }; + // Computes the top coordinate, relative to the bounds of the grid, of the given time (a Duration). + // This is a makeshify way to compute the time-top. Assumes all slatMetas dates are uniform. + // Eventually allow computation with arbirary slat dates. + TimeColsSlatsCoords.prototype.computeTimeTop = function (duration) { + var _a = this, positions = _a.positions, dateProfile = _a.dateProfile; + var len = positions.els.length; + // floating-point value of # of slots covered + var slatCoverage = (duration.milliseconds - asRoughMs(dateProfile.slotMinTime)) / asRoughMs(this.slotDuration); + var slatIndex; + var slatRemainder; + // compute a floating-point number for how many slats should be progressed through. + // from 0 to number of slats (inclusive) + // constrained because slotMinTime/slotMaxTime might be customized. + slatCoverage = Math.max(0, slatCoverage); + slatCoverage = Math.min(len, slatCoverage); + // an integer index of the furthest whole slat + // from 0 to number slats (*exclusive*, so len-1) + slatIndex = Math.floor(slatCoverage); + slatIndex = Math.min(slatIndex, len - 1); + // how much further through the slatIndex slat (from 0.0-1.0) must be covered in addition. + // could be 1.0 if slatCoverage is covering *all* the slots + slatRemainder = slatCoverage - slatIndex; + return positions.tops[slatIndex] + + positions.getHeight(slatIndex) * slatRemainder; + }; + return TimeColsSlatsCoords; + }()); + + var TimeColsSlatsBody = /** @class */ (function (_super) { + __extends(TimeColsSlatsBody, _super); + function TimeColsSlatsBody() { + return _super !== null && _super.apply(this, arguments) || this; + } + TimeColsSlatsBody.prototype.render = function () { + var _a = this, props = _a.props, context = _a.context; + var options = context.options; + var slatElRefs = props.slatElRefs; + return (createElement("tbody", null, props.slatMetas.map(function (slatMeta, i) { + var hookProps = { + time: slatMeta.time, + date: context.dateEnv.toDate(slatMeta.date), + view: context.viewApi, + }; + var classNames = [ + 'fc-timegrid-slot', + 'fc-timegrid-slot-lane', + slatMeta.isLabeled ? '' : 'fc-timegrid-slot-minor', + ]; + return (createElement("tr", { key: slatMeta.key, ref: slatElRefs.createRef(slatMeta.key) }, + props.axis && (createElement(TimeColsAxisCell, __assign({}, slatMeta))), + createElement(RenderHook, { hookProps: hookProps, classNames: options.slotLaneClassNames, content: options.slotLaneContent, didMount: options.slotLaneDidMount, willUnmount: options.slotLaneWillUnmount }, function (rootElRef, customClassNames, innerElRef, innerContent) { return (createElement("td", { ref: rootElRef, className: classNames.concat(customClassNames).join(' '), "data-time": slatMeta.isoTimeStr }, innerContent)); }))); + }))); + }; + return TimeColsSlatsBody; + }(BaseComponent)); + + /* + for the horizontal "slats" that run width-wise. Has a time axis on a side. Depends on RTL. + */ + var TimeColsSlats = /** @class */ (function (_super) { + __extends(TimeColsSlats, _super); + function TimeColsSlats() { + var _this = _super !== null && _super.apply(this, arguments) || this; + _this.rootElRef = createRef(); + _this.slatElRefs = new RefMap(); + return _this; + } + TimeColsSlats.prototype.render = function () { + var _a = this, props = _a.props, context = _a.context; + return (createElement("div", { className: "fc-timegrid-slots", ref: this.rootElRef }, + createElement("table", { className: context.theme.getClass('table'), style: { + minWidth: props.tableMinWidth, + width: props.clientWidth, + height: props.minHeight, + } }, + props.tableColGroupNode /* relies on there only being a single for the axis */, + createElement(TimeColsSlatsBody, { slatElRefs: this.slatElRefs, axis: props.axis, slatMetas: props.slatMetas })))); + }; + TimeColsSlats.prototype.componentDidMount = function () { + this.updateSizing(); + }; + TimeColsSlats.prototype.componentDidUpdate = function () { + this.updateSizing(); + }; + TimeColsSlats.prototype.componentWillUnmount = function () { + if (this.props.onCoords) { + this.props.onCoords(null); + } + }; + TimeColsSlats.prototype.updateSizing = function () { + var _a = this, context = _a.context, props = _a.props; + if (props.onCoords && + props.clientWidth !== null // means sizing has stabilized + ) { + var rootEl = this.rootElRef.current; + if (rootEl.offsetHeight) { // not hidden by css + props.onCoords(new TimeColsSlatsCoords(new PositionCache(this.rootElRef.current, collectSlatEls(this.slatElRefs.currentMap, props.slatMetas), false, true), this.props.dateProfile, context.options.slotDuration)); + } + } + }; + return TimeColsSlats; + }(BaseComponent)); + function collectSlatEls(elMap, slatMetas) { + return slatMetas.map(function (slatMeta) { return elMap[slatMeta.key]; }); + } + + function splitSegsByCol(segs, colCnt) { + var segsByCol = []; + var i; + for (i = 0; i < colCnt; i += 1) { + segsByCol.push([]); + } + if (segs) { + for (i = 0; i < segs.length; i += 1) { + segsByCol[segs[i].col].push(segs[i]); + } + } + return segsByCol; + } + function splitInteractionByCol(ui, colCnt) { + var byRow = []; + if (!ui) { + for (var i = 0; i < colCnt; i += 1) { + byRow[i] = null; + } + } + else { + for (var i = 0; i < colCnt; 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.col].segs.push(seg); + } + } + return byRow; + } + + var TimeColMoreLink = /** @class */ (function (_super) { + __extends(TimeColMoreLink, _super); + function TimeColMoreLink() { + var _this = _super !== null && _super.apply(this, arguments) || this; + _this.rootElRef = createRef(); + return _this; + } + TimeColMoreLink.prototype.render = function () { + var _this = this; + var props = this.props; + return (createElement(MoreLinkRoot, { allDayDate: null, moreCnt: props.hiddenSegs.length, allSegs: props.hiddenSegs, hiddenSegs: props.hiddenSegs, alignmentElRef: this.rootElRef, defaultContent: renderMoreLinkInner, extraDateSpan: props.extraDateSpan, dateProfile: props.dateProfile, todayRange: props.todayRange, popoverContent: function () { return renderPlainFgSegs(props.hiddenSegs, props); } }, function (rootElRef, classNames, innerElRef, innerContent, handleClick) { return (createElement("a", { ref: function (el) { + setRef(rootElRef, el); + setRef(_this.rootElRef, el); + }, className: ['fc-timegrid-more-link'].concat(classNames).join(' '), style: { top: props.top, bottom: props.bottom }, onClick: handleClick }, + createElement("div", { ref: innerElRef, className: "fc-timegrid-more-link-inner fc-sticky" }, innerContent))); })); + }; + return TimeColMoreLink; + }(BaseComponent)); + function renderMoreLinkInner(props) { + return props.shortText; + } + + // segInputs assumed sorted + function buildPositioning(segInputs, strictOrder, maxStackCnt) { + var hierarchy = new SegHierarchy(); + if (strictOrder != null) { + hierarchy.strictOrder = strictOrder; + } + if (maxStackCnt != null) { + hierarchy.maxStackCnt = maxStackCnt; + } + var hiddenEntries = hierarchy.addSegs(segInputs); + var hiddenGroups = groupIntersectingEntries(hiddenEntries); + var web = buildWeb(hierarchy); + web = stretchWeb(web, 1); // all levelCoords/thickness will have 0.0-1.0 + var segRects = webToRects(web); + return { segRects: segRects, hiddenGroups: hiddenGroups }; + } + function buildWeb(hierarchy) { + var entriesByLevel = hierarchy.entriesByLevel; + var buildNode = cacheable(function (level, lateral) { return level + ':' + lateral; }, function (level, lateral) { + var siblingRange = findNextLevelSegs(hierarchy, level, lateral); + var nextLevelRes = buildNodes(siblingRange, buildNode); + var entry = entriesByLevel[level][lateral]; + return [ + __assign(__assign({}, entry), { nextLevelNodes: nextLevelRes[0] }), + entry.thickness + nextLevelRes[1], // the pressure builds + ]; + }); + return buildNodes(entriesByLevel.length + ? { level: 0, lateralStart: 0, lateralEnd: entriesByLevel[0].length } + : null, buildNode)[0]; + } + function buildNodes(siblingRange, buildNode) { + if (!siblingRange) { + return [[], 0]; + } + var level = siblingRange.level, lateralStart = siblingRange.lateralStart, lateralEnd = siblingRange.lateralEnd; + var lateral = lateralStart; + var pairs = []; + while (lateral < lateralEnd) { + pairs.push(buildNode(level, lateral)); + lateral += 1; + } + pairs.sort(cmpDescPressures); + return [ + pairs.map(extractNode), + pairs[0][1], // first item's pressure + ]; + } + function cmpDescPressures(a, b) { + return b[1] - a[1]; + } + function extractNode(a) { + return a[0]; + } + function findNextLevelSegs(hierarchy, subjectLevel, subjectLateral) { + var levelCoords = hierarchy.levelCoords, entriesByLevel = hierarchy.entriesByLevel; + var subjectEntry = entriesByLevel[subjectLevel][subjectLateral]; + var afterSubject = levelCoords[subjectLevel] + subjectEntry.thickness; + var levelCnt = levelCoords.length; + var level = subjectLevel; + // skip past levels that are too high up + for (; level < levelCnt && levelCoords[level] < afterSubject; level += 1) + ; // do nothing + for (; level < levelCnt; level += 1) { + var entries = entriesByLevel[level]; + var entry = void 0; + var searchIndex = binarySearch(entries, subjectEntry.span.start, getEntrySpanEnd); + var lateralStart = searchIndex[0] + searchIndex[1]; // if exact match (which doesn't collide), go to next one + var lateralEnd = lateralStart; + while ( // loop through entries that horizontally intersect + (entry = entries[lateralEnd]) && // but not past the whole seg list + entry.span.start < subjectEntry.span.end) { + lateralEnd += 1; + } + if (lateralStart < lateralEnd) { + return { level: level, lateralStart: lateralStart, lateralEnd: lateralEnd }; + } + } + return null; + } + function stretchWeb(topLevelNodes, totalThickness) { + var stretchNode = cacheable(function (node, startCoord, prevThickness) { return buildEntryKey(node); }, function (node, startCoord, prevThickness) { + var nextLevelNodes = node.nextLevelNodes, thickness = node.thickness; + var allThickness = thickness + prevThickness; + var thicknessFraction = thickness / allThickness; + var endCoord; + var newChildren = []; + if (!nextLevelNodes.length) { + endCoord = totalThickness; + } + else { + for (var _i = 0, nextLevelNodes_1 = nextLevelNodes; _i < nextLevelNodes_1.length; _i++) { + var childNode = nextLevelNodes_1[_i]; + if (endCoord === undefined) { + var res = stretchNode(childNode, startCoord, allThickness); + endCoord = res[0]; + newChildren.push(res[1]); + } + else { + var res = stretchNode(childNode, endCoord, 0); + newChildren.push(res[1]); + } + } + } + var newThickness = (endCoord - startCoord) * thicknessFraction; + return [endCoord - newThickness, __assign(__assign({}, node), { thickness: newThickness, nextLevelNodes: newChildren })]; + }); + return topLevelNodes.map(function (node) { return stretchNode(node, 0, 0)[1]; }); + } + // not sorted in any particular order + function webToRects(topLevelNodes) { + var rects = []; + var processNode = cacheable(function (node, levelCoord, stackDepth) { return buildEntryKey(node); }, function (node, levelCoord, stackDepth) { + var rect = __assign(__assign({}, node), { levelCoord: levelCoord, + stackDepth: stackDepth, stackForward: 0 }); + rects.push(rect); + return (rect.stackForward = processNodes(node.nextLevelNodes, levelCoord + node.thickness, stackDepth + 1) + 1); + }); + function processNodes(nodes, levelCoord, stackDepth) { + var stackForward = 0; + for (var _i = 0, nodes_1 = nodes; _i < nodes_1.length; _i++) { + var node = nodes_1[_i]; + stackForward = Math.max(processNode(node, levelCoord, stackDepth), stackForward); + } + return stackForward; + } + processNodes(topLevelNodes, 0, 0); + return rects; // TODO: sort rects by levelCoord to be consistent with toRects? + } + // TODO: move to general util + function cacheable(keyFunc, workFunc) { + var cache = {}; + return function () { + var args = []; + for (var _i = 0; _i < arguments.length; _i++) { + args[_i] = arguments[_i]; + } + var key = keyFunc.apply(void 0, args); + return (key in cache) + ? cache[key] + : (cache[key] = workFunc.apply(void 0, args)); + }; + } + + function computeSegVCoords(segs, colDate, slatCoords, eventMinHeight) { + if (slatCoords === void 0) { slatCoords = null; } + if (eventMinHeight === void 0) { eventMinHeight = 0; } + var vcoords = []; + if (slatCoords) { + for (var i = 0; i < segs.length; i += 1) { + var seg = segs[i]; + var spanStart = slatCoords.computeDateTop(seg.start, colDate); + var spanEnd = Math.max(spanStart + (eventMinHeight || 0), // :( + slatCoords.computeDateTop(seg.end, colDate)); + vcoords.push({ + start: Math.round(spanStart), + end: Math.round(spanEnd), // + }); + } + } + return vcoords; + } + function computeFgSegPlacements(segs, segVCoords, // might not have for every seg + eventOrderStrict, eventMaxStack) { + var segInputs = []; + var dumbSegs = []; // segs without coords + for (var i = 0; i < segs.length; i += 1) { + var vcoords = segVCoords[i]; + if (vcoords) { + segInputs.push({ + index: i, + thickness: 1, + span: vcoords, + }); + } + else { + dumbSegs.push(segs[i]); + } + } + var _a = buildPositioning(segInputs, eventOrderStrict, eventMaxStack), segRects = _a.segRects, hiddenGroups = _a.hiddenGroups; + var segPlacements = []; + for (var _i = 0, segRects_1 = segRects; _i < segRects_1.length; _i++) { + var segRect = segRects_1[_i]; + segPlacements.push({ + seg: segs[segRect.index], + rect: segRect, + }); + } + for (var _b = 0, dumbSegs_1 = dumbSegs; _b < dumbSegs_1.length; _b++) { + var dumbSeg = dumbSegs_1[_b]; + segPlacements.push({ seg: dumbSeg, rect: null }); + } + return { segPlacements: segPlacements, hiddenGroups: hiddenGroups }; + } + + var DEFAULT_TIME_FORMAT$1 = createFormatter({ + hour: 'numeric', + minute: '2-digit', + meridiem: false, + }); + var TimeColEvent = /** @class */ (function (_super) { + __extends(TimeColEvent, _super); + function TimeColEvent() { + return _super !== null && _super.apply(this, arguments) || this; + } + TimeColEvent.prototype.render = function () { + var classNames = [ + 'fc-timegrid-event', + 'fc-v-event', + ]; + if (this.props.isShort) { + classNames.push('fc-timegrid-event-short'); + } + return (createElement(StandardEvent, __assign({}, this.props, { defaultTimeFormat: DEFAULT_TIME_FORMAT$1, extraClassNames: classNames }))); + }; + return TimeColEvent; + }(BaseComponent)); + + var TimeColMisc = /** @class */ (function (_super) { + __extends(TimeColMisc, _super); + function TimeColMisc() { + return _super !== null && _super.apply(this, arguments) || this; + } + TimeColMisc.prototype.render = function () { + var props = this.props; + return (createElement(DayCellContent, { date: props.date, dateProfile: props.dateProfile, todayRange: props.todayRange, extraHookProps: props.extraHookProps }, function (innerElRef, innerContent) { return (innerContent && + createElement("div", { className: "fc-timegrid-col-misc", ref: innerElRef }, innerContent)); })); + }; + return TimeColMisc; + }(BaseComponent)); + + var TimeCol = /** @class */ (function (_super) { + __extends(TimeCol, _super); + function TimeCol() { + var _this = _super !== null && _super.apply(this, arguments) || this; + _this.sortEventSegs = memoize(sortEventSegs); + return _this; + } + // TODO: memoize event-placement? + TimeCol.prototype.render = function () { + var _this = this; + var _a = this, props = _a.props, context = _a.context; + var isSelectMirror = context.options.selectMirror; + var mirrorSegs = (props.eventDrag && props.eventDrag.segs) || + (props.eventResize && props.eventResize.segs) || + (isSelectMirror && props.dateSelectionSegs) || + []; + var interactionAffectedInstances = // TODO: messy way to compute this + (props.eventDrag && props.eventDrag.affectedInstances) || + (props.eventResize && props.eventResize.affectedInstances) || + {}; + var sortedFgSegs = this.sortEventSegs(props.fgEventSegs, context.options.eventOrder); + return (createElement(DayCellRoot, { elRef: props.elRef, date: props.date, dateProfile: props.dateProfile, todayRange: props.todayRange, extraHookProps: props.extraHookProps }, function (rootElRef, classNames, dataAttrs) { return (createElement("td", __assign({ ref: rootElRef, className: ['fc-timegrid-col'].concat(classNames, props.extraClassNames || []).join(' ') }, dataAttrs, props.extraDataAttrs), + createElement("div", { className: "fc-timegrid-col-frame" }, + createElement("div", { className: "fc-timegrid-col-bg" }, + _this.renderFillSegs(props.businessHourSegs, 'non-business'), + _this.renderFillSegs(props.bgEventSegs, 'bg-event'), + _this.renderFillSegs(props.dateSelectionSegs, 'highlight')), + createElement("div", { className: "fc-timegrid-col-events" }, _this.renderFgSegs(sortedFgSegs, interactionAffectedInstances, false, false, false)), + createElement("div", { className: "fc-timegrid-col-events" }, _this.renderFgSegs(mirrorSegs, {}, Boolean(props.eventDrag), Boolean(props.eventResize), Boolean(isSelectMirror))), + createElement("div", { className: "fc-timegrid-now-indicator-container" }, _this.renderNowIndicator(props.nowIndicatorSegs)), + createElement(TimeColMisc, { date: props.date, dateProfile: props.dateProfile, todayRange: props.todayRange, extraHookProps: props.extraHookProps })))); })); + }; + TimeCol.prototype.renderFgSegs = function (sortedFgSegs, segIsInvisible, isDragging, isResizing, isDateSelecting) { + var props = this.props; + if (props.forPrint) { + return renderPlainFgSegs(sortedFgSegs, props); + } + return this.renderPositionedFgSegs(sortedFgSegs, segIsInvisible, isDragging, isResizing, isDateSelecting); + }; + TimeCol.prototype.renderPositionedFgSegs = function (segs, // if not mirror, needs to be sorted + segIsInvisible, isDragging, isResizing, isDateSelecting) { + var _this = this; + var _a = this.context.options, eventMaxStack = _a.eventMaxStack, eventShortHeight = _a.eventShortHeight, eventOrderStrict = _a.eventOrderStrict, eventMinHeight = _a.eventMinHeight; + var _b = this.props, date = _b.date, slatCoords = _b.slatCoords, eventSelection = _b.eventSelection, todayRange = _b.todayRange, nowDate = _b.nowDate; + var isMirror = isDragging || isResizing || isDateSelecting; + var segVCoords = computeSegVCoords(segs, date, slatCoords, eventMinHeight); + var _c = computeFgSegPlacements(segs, segVCoords, eventOrderStrict, eventMaxStack), segPlacements = _c.segPlacements, hiddenGroups = _c.hiddenGroups; + return (createElement(Fragment, null, + this.renderHiddenGroups(hiddenGroups, segs), + segPlacements.map(function (segPlacement) { + var seg = segPlacement.seg, rect = segPlacement.rect; + var instanceId = seg.eventRange.instance.instanceId; + var isVisible = isMirror || Boolean(!segIsInvisible[instanceId] && rect); + var vStyle = computeSegVStyle(rect && rect.span); + var hStyle = (!isMirror && rect) ? _this.computeSegHStyle(rect) : { left: 0, right: 0 }; + var isInset = Boolean(rect) && rect.stackForward > 0; + var isShort = Boolean(rect) && (rect.span.end - rect.span.start) < eventShortHeight; // look at other places for this problem + return (createElement("div", { className: 'fc-timegrid-event-harness' + + (isInset ? ' fc-timegrid-event-harness-inset' : ''), key: instanceId, style: __assign(__assign({ visibility: isVisible ? '' : 'hidden' }, vStyle), hStyle) }, + createElement(TimeColEvent, __assign({ seg: seg, isDragging: isDragging, isResizing: isResizing, isDateSelecting: isDateSelecting, isSelected: instanceId === eventSelection, isShort: isShort }, getSegMeta(seg, todayRange, nowDate))))); + }))); + }; + // will already have eventMinHeight applied because segInputs already had it + TimeCol.prototype.renderHiddenGroups = function (hiddenGroups, segs) { + var _a = this.props, extraDateSpan = _a.extraDateSpan, dateProfile = _a.dateProfile, todayRange = _a.todayRange, nowDate = _a.nowDate, eventSelection = _a.eventSelection, eventDrag = _a.eventDrag, eventResize = _a.eventResize; + return (createElement(Fragment, null, hiddenGroups.map(function (hiddenGroup) { + var positionCss = computeSegVStyle(hiddenGroup.span); + var hiddenSegs = compileSegsFromEntries(hiddenGroup.entries, segs); + return (createElement(TimeColMoreLink, { key: buildIsoString(computeEarliestSegStart(hiddenSegs)), hiddenSegs: hiddenSegs, top: positionCss.top, bottom: positionCss.bottom, extraDateSpan: extraDateSpan, dateProfile: dateProfile, todayRange: todayRange, nowDate: nowDate, eventSelection: eventSelection, eventDrag: eventDrag, eventResize: eventResize })); + }))); + }; + TimeCol.prototype.renderFillSegs = function (segs, fillType) { + var _a = this, props = _a.props, context = _a.context; + var segVCoords = computeSegVCoords(segs, props.date, props.slatCoords, context.options.eventMinHeight); // don't assume all populated + var children = segVCoords.map(function (vcoords, i) { + var seg = segs[i]; + return (createElement("div", { key: buildEventRangeKey(seg.eventRange), className: "fc-timegrid-bg-harness", style: computeSegVStyle(vcoords) }, fillType === 'bg-event' ? + createElement(BgEvent, __assign({ seg: seg }, getSegMeta(seg, props.todayRange, props.nowDate))) : + renderFill(fillType))); + }); + return createElement(Fragment, null, children); + }; + TimeCol.prototype.renderNowIndicator = function (segs) { + var _a = this.props, slatCoords = _a.slatCoords, date = _a.date; + if (!slatCoords) { + return null; + } + return segs.map(function (seg, i) { return (createElement(NowIndicatorRoot, { isAxis: false, date: date, + // key doesn't matter. will only ever be one + key: i }, function (rootElRef, classNames, innerElRef, innerContent) { return (createElement("div", { ref: rootElRef, className: ['fc-timegrid-now-indicator-line'].concat(classNames).join(' '), style: { top: slatCoords.computeDateTop(seg.start, date) } }, innerContent)); })); }); + }; + TimeCol.prototype.computeSegHStyle = function (segHCoords) { + var _a = this.context, isRtl = _a.isRtl, options = _a.options; + var shouldOverlap = options.slotEventOverlap; + var nearCoord = segHCoords.levelCoord; // the left side if LTR. the right side if RTL. floating-point + var farCoord = segHCoords.levelCoord + segHCoords.thickness; // the right side if LTR. the left side if RTL. floating-point + var left; // amount of space from left edge, a fraction of the total width + var right; // amount of space from right edge, a fraction of the total width + if (shouldOverlap) { + // double the width, but don't go beyond the maximum forward coordinate (1.0) + farCoord = Math.min(1, nearCoord + (farCoord - nearCoord) * 2); + } + if (isRtl) { + left = 1 - farCoord; + right = nearCoord; + } + else { + left = nearCoord; + right = 1 - farCoord; + } + var props = { + zIndex: segHCoords.stackDepth + 1, + left: left * 100 + '%', + right: right * 100 + '%', + }; + if (shouldOverlap && !segHCoords.stackForward) { + // add padding to the edge so that forward stacked events don't cover the resizer's icon + props[isRtl ? 'marginLeft' : 'marginRight'] = 10 * 2; // 10 is a guesstimate of the icon's width + } + return props; + }; + return TimeCol; + }(BaseComponent)); + function renderPlainFgSegs(sortedFgSegs, _a) { + var todayRange = _a.todayRange, nowDate = _a.nowDate, eventSelection = _a.eventSelection, eventDrag = _a.eventDrag, eventResize = _a.eventResize; + var hiddenInstances = (eventDrag ? eventDrag.affectedInstances : null) || + (eventResize ? eventResize.affectedInstances : null) || + {}; + return (createElement(Fragment, null, sortedFgSegs.map(function (seg) { + var instanceId = seg.eventRange.instance.instanceId; + return (createElement("div", { key: instanceId, style: { visibility: hiddenInstances[instanceId] ? 'hidden' : '' } }, + createElement(TimeColEvent, __assign({ seg: seg, isDragging: false, isResizing: false, isDateSelecting: false, isSelected: instanceId === eventSelection, isShort: false }, getSegMeta(seg, todayRange, nowDate))))); + }))); + } + function computeSegVStyle(segVCoords) { + if (!segVCoords) { + return { top: '', bottom: '' }; + } + return { + top: segVCoords.start, + bottom: -segVCoords.end, + }; + } + function compileSegsFromEntries(segEntries, allSegs) { + return segEntries.map(function (segEntry) { return allSegs[segEntry.index]; }); + } + + var TimeColsContent = /** @class */ (function (_super) { + __extends(TimeColsContent, _super); + function TimeColsContent() { + var _this = _super !== null && _super.apply(this, arguments) || this; + _this.splitFgEventSegs = memoize(splitSegsByCol); + _this.splitBgEventSegs = memoize(splitSegsByCol); + _this.splitBusinessHourSegs = memoize(splitSegsByCol); + _this.splitNowIndicatorSegs = memoize(splitSegsByCol); + _this.splitDateSelectionSegs = memoize(splitSegsByCol); + _this.splitEventDrag = memoize(splitInteractionByCol); + _this.splitEventResize = memoize(splitInteractionByCol); + _this.rootElRef = createRef(); + _this.cellElRefs = new RefMap(); + return _this; + } + TimeColsContent.prototype.render = function () { + var _this = this; + var _a = this, props = _a.props, context = _a.context; + var nowIndicatorTop = context.options.nowIndicator && + props.slatCoords && + props.slatCoords.safeComputeTop(props.nowDate); // might return void + var colCnt = props.cells.length; + var fgEventSegsByRow = this.splitFgEventSegs(props.fgEventSegs, colCnt); + var bgEventSegsByRow = this.splitBgEventSegs(props.bgEventSegs, colCnt); + var businessHourSegsByRow = this.splitBusinessHourSegs(props.businessHourSegs, colCnt); + var nowIndicatorSegsByRow = this.splitNowIndicatorSegs(props.nowIndicatorSegs, colCnt); + var dateSelectionSegsByRow = this.splitDateSelectionSegs(props.dateSelectionSegs, colCnt); + var eventDragByRow = this.splitEventDrag(props.eventDrag, colCnt); + var eventResizeByRow = this.splitEventResize(props.eventResize, colCnt); + return (createElement("div", { className: "fc-timegrid-cols", ref: this.rootElRef }, + createElement("table", { style: { + minWidth: props.tableMinWidth, + width: props.clientWidth, + } }, + props.tableColGroupNode, + createElement("tbody", null, + createElement("tr", null, + props.axis && (createElement("td", { className: "fc-timegrid-col fc-timegrid-axis" }, + createElement("div", { className: "fc-timegrid-col-frame" }, + createElement("div", { className: "fc-timegrid-now-indicator-container" }, typeof nowIndicatorTop === 'number' && (createElement(NowIndicatorRoot, { isAxis: true, date: props.nowDate }, function (rootElRef, classNames, innerElRef, innerContent) { return (createElement("div", { ref: rootElRef, className: ['fc-timegrid-now-indicator-arrow'].concat(classNames).join(' '), style: { top: nowIndicatorTop } }, innerContent)); })))))), + props.cells.map(function (cell, i) { return (createElement(TimeCol, { key: cell.key, elRef: _this.cellElRefs.createRef(cell.key), dateProfile: props.dateProfile, date: cell.date, nowDate: props.nowDate, todayRange: props.todayRange, extraHookProps: cell.extraHookProps, extraDataAttrs: cell.extraDataAttrs, extraClassNames: cell.extraClassNames, extraDateSpan: cell.extraDateSpan, fgEventSegs: fgEventSegsByRow[i], bgEventSegs: bgEventSegsByRow[i], businessHourSegs: businessHourSegsByRow[i], nowIndicatorSegs: nowIndicatorSegsByRow[i], dateSelectionSegs: dateSelectionSegsByRow[i], eventDrag: eventDragByRow[i], eventResize: eventResizeByRow[i], slatCoords: props.slatCoords, eventSelection: props.eventSelection, forPrint: props.forPrint })); })))))); + }; + TimeColsContent.prototype.componentDidMount = function () { + this.updateCoords(); + }; + TimeColsContent.prototype.componentDidUpdate = function () { + this.updateCoords(); + }; + TimeColsContent.prototype.updateCoords = function () { + var props = this.props; + if (props.onColCoords && + props.clientWidth !== null // means sizing has stabilized + ) { + props.onColCoords(new PositionCache(this.rootElRef.current, collectCellEls(this.cellElRefs.currentMap, props.cells), true, // horizontal + false)); + } + }; + return TimeColsContent; + }(BaseComponent)); + function collectCellEls(elMap, cells) { + return cells.map(function (cell) { return elMap[cell.key]; }); + } + + /* A component that renders one or more columns of vertical time slots + ----------------------------------------------------------------------------------------------------------------------*/ + var TimeCols = /** @class */ (function (_super) { + __extends(TimeCols, _super); + function TimeCols() { + var _this = _super !== null && _super.apply(this, arguments) || this; + _this.processSlotOptions = memoize(processSlotOptions); + _this.state = { + slatCoords: null, + }; + _this.handleRootEl = function (el) { + if (el) { + _this.context.registerInteractiveComponent(_this, { + el: el, + isHitComboAllowed: _this.props.isHitComboAllowed, + }); + } + else { + _this.context.unregisterInteractiveComponent(_this); + } + }; + _this.handleScrollRequest = function (request) { + var onScrollTopRequest = _this.props.onScrollTopRequest; + var slatCoords = _this.state.slatCoords; + if (onScrollTopRequest && slatCoords) { + if (request.time) { + var top_1 = slatCoords.computeTimeTop(request.time); + top_1 = Math.ceil(top_1); // zoom can give weird floating-point values. rather scroll a little bit further + if (top_1) { + top_1 += 1; // to overcome top border that slots beyond the first have. looks better + } + onScrollTopRequest(top_1); + } + return true; + } + return false; + }; + _this.handleColCoords = function (colCoords) { + _this.colCoords = colCoords; + }; + _this.handleSlatCoords = function (slatCoords) { + _this.setState({ slatCoords: slatCoords }); + if (_this.props.onSlatCoords) { + _this.props.onSlatCoords(slatCoords); + } + }; + return _this; + } + TimeCols.prototype.render = function () { + var _a = this, props = _a.props, state = _a.state; + return (createElement("div", { className: "fc-timegrid-body", 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(TimeColsSlats, { axis: props.axis, dateProfile: props.dateProfile, slatMetas: props.slatMetas, clientWidth: props.clientWidth, minHeight: props.expandRows ? props.clientHeight : '', tableMinWidth: props.tableMinWidth, tableColGroupNode: props.axis ? props.tableColGroupNode : null /* axis depends on the colgroup's shrinking */, onCoords: this.handleSlatCoords }), + createElement(TimeColsContent, { cells: props.cells, axis: props.axis, dateProfile: props.dateProfile, businessHourSegs: props.businessHourSegs, bgEventSegs: props.bgEventSegs, fgEventSegs: props.fgEventSegs, dateSelectionSegs: props.dateSelectionSegs, eventSelection: props.eventSelection, eventDrag: props.eventDrag, eventResize: props.eventResize, todayRange: props.todayRange, nowDate: props.nowDate, nowIndicatorSegs: props.nowIndicatorSegs, clientWidth: props.clientWidth, tableMinWidth: props.tableMinWidth, tableColGroupNode: props.tableColGroupNode, slatCoords: state.slatCoords, onColCoords: this.handleColCoords, forPrint: props.forPrint }))); + }; + TimeCols.prototype.componentDidMount = function () { + this.scrollResponder = this.context.createScrollResponder(this.handleScrollRequest); + }; + TimeCols.prototype.componentDidUpdate = function (prevProps) { + this.scrollResponder.update(prevProps.dateProfile !== this.props.dateProfile); + }; + TimeCols.prototype.componentWillUnmount = function () { + this.scrollResponder.detach(); + }; + TimeCols.prototype.queryHit = function (positionLeft, positionTop) { + var _a = this.context, dateEnv = _a.dateEnv, options = _a.options; + var colCoords = this.colCoords; + var dateProfile = this.props.dateProfile; + var slatCoords = this.state.slatCoords; + var _b = this.processSlotOptions(this.props.slotDuration, options.snapDuration), snapDuration = _b.snapDuration, snapsPerSlot = _b.snapsPerSlot; + var colIndex = colCoords.leftToIndex(positionLeft); + var slatIndex = slatCoords.positions.topToIndex(positionTop); + if (colIndex != null && slatIndex != null) { + var cell = this.props.cells[colIndex]; + var slatTop = slatCoords.positions.tops[slatIndex]; + var slatHeight = slatCoords.positions.getHeight(slatIndex); + var partial = (positionTop - slatTop) / slatHeight; // floating point number between 0 and 1 + var localSnapIndex = Math.floor(partial * snapsPerSlot); // the snap # relative to start of slat + var snapIndex = slatIndex * snapsPerSlot + localSnapIndex; + var dayDate = this.props.cells[colIndex].date; + var time = addDurations(dateProfile.slotMinTime, multiplyDuration(snapDuration, snapIndex)); + var start = dateEnv.add(dayDate, time); + var end = dateEnv.add(start, snapDuration); + return { + dateProfile: dateProfile, + dateSpan: __assign({ range: { start: start, end: end }, allDay: false }, cell.extraDateSpan), + dayEl: colCoords.els[colIndex], + rect: { + left: colCoords.lefts[colIndex], + right: colCoords.rights[colIndex], + top: slatTop, + bottom: slatTop + slatHeight, + }, + layer: 0, + }; + } + return null; + }; + return TimeCols; + }(DateComponent)); + function processSlotOptions(slotDuration, snapDurationOverride) { + var snapDuration = snapDurationOverride || slotDuration; + var snapsPerSlot = wholeDivideDurations(slotDuration, snapDuration); + if (snapsPerSlot === null) { + snapDuration = slotDuration; + snapsPerSlot = 1; + // TODO: say warning? + } + return { snapDuration: snapDuration, snapsPerSlot: snapsPerSlot }; + } + + var DayTimeColsSlicer = /** @class */ (function (_super) { + __extends(DayTimeColsSlicer, _super); + function DayTimeColsSlicer() { + return _super !== null && _super.apply(this, arguments) || this; + } + DayTimeColsSlicer.prototype.sliceRange = function (range, dayRanges) { + var segs = []; + for (var col = 0; col < dayRanges.length; col += 1) { + var segRange = intersectRanges(range, dayRanges[col]); + if (segRange) { + segs.push({ + start: segRange.start, + end: segRange.end, + isStart: segRange.start.valueOf() === range.start.valueOf(), + isEnd: segRange.end.valueOf() === range.end.valueOf(), + col: col, + }); + } + } + return segs; + }; + return DayTimeColsSlicer; + }(Slicer)); + + var DayTimeCols = /** @class */ (function (_super) { + __extends(DayTimeCols, _super); + function DayTimeCols() { + var _this = _super !== null && _super.apply(this, arguments) || this; + _this.buildDayRanges = memoize(buildDayRanges); + _this.slicer = new DayTimeColsSlicer(); + _this.timeColsRef = createRef(); + return _this; + } + DayTimeCols.prototype.render = function () { + var _this = this; + var _a = this, props = _a.props, context = _a.context; + var dateProfile = props.dateProfile, dayTableModel = props.dayTableModel; + var isNowIndicator = context.options.nowIndicator; + var dayRanges = this.buildDayRanges(dayTableModel, dateProfile, context.dateEnv); + // give it the first row of cells + // TODO: would move this further down hierarchy, but sliceNowDate needs it + return (createElement(NowTimer, { unit: isNowIndicator ? 'minute' : 'day' }, function (nowDate, todayRange) { return (createElement(TimeCols, __assign({ ref: _this.timeColsRef }, _this.slicer.sliceProps(props, dateProfile, null, context, dayRanges), { forPrint: props.forPrint, axis: props.axis, dateProfile: dateProfile, slatMetas: props.slatMetas, slotDuration: props.slotDuration, cells: dayTableModel.cells[0], tableColGroupNode: props.tableColGroupNode, tableMinWidth: props.tableMinWidth, clientWidth: props.clientWidth, clientHeight: props.clientHeight, expandRows: props.expandRows, nowDate: nowDate, nowIndicatorSegs: isNowIndicator && _this.slicer.sliceNowDate(nowDate, context, dayRanges), todayRange: todayRange, onScrollTopRequest: props.onScrollTopRequest, onSlatCoords: props.onSlatCoords }))); })); + }; + return DayTimeCols; + }(DateComponent)); + function buildDayRanges(dayTableModel, dateProfile, dateEnv) { + var ranges = []; + for (var _i = 0, _a = dayTableModel.headerDates; _i < _a.length; _i++) { + var date = _a[_i]; + ranges.push({ + start: dateEnv.add(date, dateProfile.slotMinTime), + end: dateEnv.add(date, dateProfile.slotMaxTime), + }); + } + return ranges; + } + + // potential nice values for the slot-duration and interval-duration + // from largest to smallest + var STOCK_SUB_DURATIONS = [ + { hours: 1 }, + { minutes: 30 }, + { minutes: 15 }, + { seconds: 30 }, + { seconds: 15 }, + ]; + function buildSlatMetas(slotMinTime, slotMaxTime, explicitLabelInterval, slotDuration, dateEnv) { + var dayStart = new Date(0); + var slatTime = slotMinTime; + var slatIterator = createDuration(0); + var labelInterval = explicitLabelInterval || computeLabelInterval(slotDuration); + var metas = []; + while (asRoughMs(slatTime) < asRoughMs(slotMaxTime)) { + var date = dateEnv.add(dayStart, slatTime); + var isLabeled = wholeDivideDurations(slatIterator, labelInterval) !== null; + metas.push({ + date: date, + time: slatTime, + key: date.toISOString(), + isoTimeStr: formatIsoTimeString(date), + isLabeled: isLabeled, + }); + slatTime = addDurations(slatTime, slotDuration); + slatIterator = addDurations(slatIterator, slotDuration); + } + return metas; + } + // Computes an automatic value for slotLabelInterval + function computeLabelInterval(slotDuration) { + var i; + var labelInterval; + var slotsPerLabel; + // find the smallest stock label interval that results in more than one slots-per-label + for (i = STOCK_SUB_DURATIONS.length - 1; i >= 0; i -= 1) { + labelInterval = createDuration(STOCK_SUB_DURATIONS[i]); + slotsPerLabel = wholeDivideDurations(labelInterval, slotDuration); + if (slotsPerLabel !== null && slotsPerLabel > 1) { + return labelInterval; + } + } + return slotDuration; // fall back + } + + var DayTimeColsView = /** @class */ (function (_super) { + __extends(DayTimeColsView, _super); + function DayTimeColsView() { + var _this = _super !== null && _super.apply(this, arguments) || this; + _this.buildTimeColsModel = memoize(buildTimeColsModel); + _this.buildSlatMetas = memoize(buildSlatMetas); + return _this; + } + DayTimeColsView.prototype.render = function () { + var _this = this; + var _a = this.context, options = _a.options, dateEnv = _a.dateEnv, dateProfileGenerator = _a.dateProfileGenerator; + var props = this.props; + var dateProfile = props.dateProfile; + var dayTableModel = this.buildTimeColsModel(dateProfile, dateProfileGenerator); + var splitProps = this.allDaySplitter.splitProps(props); + var slatMetas = this.buildSlatMetas(dateProfile.slotMinTime, dateProfile.slotMaxTime, options.slotLabelInterval, options.slotDuration, dateEnv); + var dayMinWidth = options.dayMinWidth; + var hasAttachedAxis = !dayMinWidth; + var hasDetachedAxis = dayMinWidth; + var headerContent = options.dayHeaders && (createElement(DayHeader, { dates: dayTableModel.headerDates, dateProfile: dateProfile, datesRepDistinctDays: true, renderIntro: hasAttachedAxis ? this.renderHeadAxis : null })); + var allDayContent = (options.allDaySlot !== false) && (function (contentArg) { return (createElement(DayTable, __assign({}, splitProps.allDay, { dateProfile: dateProfile, dayTableModel: dayTableModel, nextDayThreshold: options.nextDayThreshold, tableMinWidth: contentArg.tableMinWidth, colGroupNode: contentArg.tableColGroupNode, renderRowIntro: hasAttachedAxis ? _this.renderTableRowAxis : null, showWeekNumbers: false, expandRows: false, headerAlignElRef: _this.headerElRef, clientWidth: contentArg.clientWidth, clientHeight: contentArg.clientHeight, forPrint: props.forPrint }, _this.getAllDayMaxEventProps()))); }); + var timeGridContent = function (contentArg) { return (createElement(DayTimeCols, __assign({}, splitProps.timed, { dayTableModel: dayTableModel, dateProfile: dateProfile, axis: hasAttachedAxis, slotDuration: options.slotDuration, slatMetas: slatMetas, forPrint: props.forPrint, tableColGroupNode: contentArg.tableColGroupNode, tableMinWidth: contentArg.tableMinWidth, clientWidth: contentArg.clientWidth, clientHeight: contentArg.clientHeight, onSlatCoords: _this.handleSlatCoords, expandRows: contentArg.expandRows, onScrollTopRequest: _this.handleScrollTopRequest }))); }; + return hasDetachedAxis + ? this.renderHScrollLayout(headerContent, allDayContent, timeGridContent, dayTableModel.colCnt, dayMinWidth, slatMetas, this.state.slatCoords) + : this.renderSimpleLayout(headerContent, allDayContent, timeGridContent); + }; + return DayTimeColsView; + }(TimeColsView)); + function buildTimeColsModel(dateProfile, dateProfileGenerator) { + var daySeries = new DaySeriesModel(dateProfile.renderRange, dateProfileGenerator); + return new DayTableModel(daySeries, false); + } + + var OPTION_REFINERS$2 = { + allDaySlot: Boolean, + }; + + var timeGridPlugin = createPlugin({ + initialView: 'timeGridWeek', + optionRefiners: OPTION_REFINERS$2, + views: { + timeGrid: { + component: DayTimeColsView, + usesMinMaxTime: true, + allDaySlot: true, + slotDuration: '00:30:00', + slotEventOverlap: true, // a bad name. confused with overlap/constraint system + }, + timeGridDay: { + type: 'timeGrid', + duration: { days: 1 }, + }, + timeGridWeek: { + type: 'timeGrid', + duration: { weeks: 1 }, + }, + }, + }); + + var ListViewHeaderRow = /** @class */ (function (_super) { + __extends(ListViewHeaderRow, _super); + function ListViewHeaderRow() { + return _super !== null && _super.apply(this, arguments) || this; + } + ListViewHeaderRow.prototype.render = function () { + var _a = this.props, dayDate = _a.dayDate, todayRange = _a.todayRange; + var _b = this.context, theme = _b.theme, dateEnv = _b.dateEnv, options = _b.options, viewApi = _b.viewApi; + var dayMeta = getDateMeta(dayDate, todayRange); + // will ever be falsy? + var text = options.listDayFormat ? dateEnv.format(dayDate, options.listDayFormat) : ''; + // will ever be falsy? also, BAD NAME "alt" + var sideText = options.listDaySideFormat ? dateEnv.format(dayDate, options.listDaySideFormat) : ''; + var navLinkData = options.navLinks + ? buildNavLinkData(dayDate) + : null; + var hookProps = __assign({ date: dateEnv.toDate(dayDate), view: viewApi, text: text, + sideText: sideText, + navLinkData: navLinkData }, dayMeta); + var classNames = ['fc-list-day'].concat(getDayClassNames(dayMeta, theme)); + // TODO: make a reusable HOC for dayHeader (used in daygrid/timegrid too) + return (createElement(RenderHook, { hookProps: hookProps, classNames: options.dayHeaderClassNames, content: options.dayHeaderContent, defaultContent: renderInnerContent, didMount: options.dayHeaderDidMount, willUnmount: options.dayHeaderWillUnmount }, function (rootElRef, customClassNames, innerElRef, innerContent) { return (createElement("tr", { ref: rootElRef, className: classNames.concat(customClassNames).join(' '), "data-date": formatDayString(dayDate) }, + createElement("th", { colSpan: 3 }, + createElement("div", { className: 'fc-list-day-cushion ' + theme.getClass('tableCellShaded'), ref: innerElRef }, innerContent)))); })); + }; + return ListViewHeaderRow; + }(BaseComponent)); + function renderInnerContent(props) { + var navLinkAttrs = props.navLinkData // is there a type for this? + ? { 'data-navlink': props.navLinkData, tabIndex: 0 } + : {}; + return (createElement(Fragment, null, + props.text && (createElement("a", __assign({ className: "fc-list-day-text" }, navLinkAttrs), props.text)), + props.sideText && (createElement("a", __assign({ className: "fc-list-day-side-text" }, navLinkAttrs), props.sideText)))); + } + + var DEFAULT_TIME_FORMAT = createFormatter({ + hour: 'numeric', + minute: '2-digit', + meridiem: 'short', + }); + var ListViewEventRow = /** @class */ (function (_super) { + __extends(ListViewEventRow, _super); + function ListViewEventRow() { + return _super !== null && _super.apply(this, arguments) || this; + } + ListViewEventRow.prototype.render = function () { + var _a = this, props = _a.props, context = _a.context; + var seg = props.seg; + var timeFormat = context.options.eventTimeFormat || DEFAULT_TIME_FORMAT; + return (createElement(EventRoot, { seg: seg, timeText: "" // BAD. because of all-day content + , disableDragging: true, disableResizing: true, defaultContent: renderEventInnerContent, isPast: props.isPast, isFuture: props.isFuture, isToday: props.isToday, isSelected: props.isSelected, isDragging: props.isDragging, isResizing: props.isResizing, isDateSelecting: props.isDateSelecting }, function (rootElRef, classNames, innerElRef, innerContent, hookProps) { return (createElement("tr", { className: ['fc-list-event', hookProps.event.url ? 'fc-event-forced-url' : ''].concat(classNames).join(' '), ref: rootElRef }, + buildTimeContent(seg, timeFormat, context), + createElement("td", { className: "fc-list-event-graphic" }, + createElement("span", { className: "fc-list-event-dot", style: { borderColor: hookProps.borderColor || hookProps.backgroundColor } })), + createElement("td", { className: "fc-list-event-title", ref: innerElRef }, innerContent))); })); + }; + return ListViewEventRow; + }(BaseComponent)); + function renderEventInnerContent(props) { + var event = props.event; + var url = event.url; + var anchorAttrs = url ? { href: url } : {}; + return (createElement("a", __assign({}, anchorAttrs), event.title)); + } + function buildTimeContent(seg, timeFormat, context) { + var options = context.options; + if (options.displayEventTime !== false) { + var eventDef = seg.eventRange.def; + var eventInstance = seg.eventRange.instance; + var doAllDay = false; + var timeText = void 0; + if (eventDef.allDay) { + doAllDay = true; + } + else if (isMultiDayRange(seg.eventRange.range)) { // TODO: use (!isStart || !isEnd) instead? + if (seg.isStart) { + timeText = buildSegTimeText(seg, timeFormat, context, null, null, eventInstance.range.start, seg.end); + } + else if (seg.isEnd) { + timeText = buildSegTimeText(seg, timeFormat, context, null, null, seg.start, eventInstance.range.end); + } + else { + doAllDay = true; + } + } + else { + timeText = buildSegTimeText(seg, timeFormat, context); + } + if (doAllDay) { + var hookProps = { + text: context.options.allDayText, + view: context.viewApi, + }; + return (createElement(RenderHook, { hookProps: hookProps, classNames: options.allDayClassNames, content: options.allDayContent, defaultContent: renderAllDayInner, didMount: options.allDayDidMount, willUnmount: options.allDayWillUnmount }, function (rootElRef, classNames, innerElRef, innerContent) { return (createElement("td", { className: ['fc-list-event-time'].concat(classNames).join(' '), ref: rootElRef }, innerContent)); })); + } + return (createElement("td", { className: "fc-list-event-time" }, timeText)); + } + return null; + } + function renderAllDayInner(hookProps) { + return hookProps.text; + } + + /* + Responsible for the scroller, and forwarding event-related actions into the "grid". + */ + var ListView = /** @class */ (function (_super) { + __extends(ListView, _super); + function ListView() { + var _this = _super !== null && _super.apply(this, arguments) || this; + _this.computeDateVars = memoize(computeDateVars); + _this.eventStoreToSegs = memoize(_this._eventStoreToSegs); + _this.setRootEl = function (rootEl) { + if (rootEl) { + _this.context.registerInteractiveComponent(_this, { + el: rootEl, + }); + } + else { + _this.context.unregisterInteractiveComponent(_this); + } + }; + return _this; + } + ListView.prototype.render = function () { + var _this = this; + var _a = this, props = _a.props, context = _a.context; + var extraClassNames = [ + 'fc-list', + context.theme.getClass('table'), + context.options.stickyHeaderDates !== false ? 'fc-list-sticky' : '', + ]; + var _b = this.computeDateVars(props.dateProfile), dayDates = _b.dayDates, dayRanges = _b.dayRanges; + var eventSegs = this.eventStoreToSegs(props.eventStore, props.eventUiBases, dayRanges); + return (createElement(ViewRoot, { viewSpec: context.viewSpec, elRef: this.setRootEl }, function (rootElRef, classNames) { return (createElement("div", { ref: rootElRef, className: extraClassNames.concat(classNames).join(' ') }, + createElement(Scroller, { liquid: !props.isHeightAuto, overflowX: props.isHeightAuto ? 'visible' : 'hidden', overflowY: props.isHeightAuto ? 'visible' : 'auto' }, eventSegs.length > 0 ? + _this.renderSegList(eventSegs, dayDates) : + _this.renderEmptyMessage()))); })); + }; + ListView.prototype.renderEmptyMessage = function () { + var _a = this.context, options = _a.options, viewApi = _a.viewApi; + var hookProps = { + text: options.noEventsText, + view: viewApi, + }; + return (createElement(RenderHook, { hookProps: hookProps, classNames: options.noEventsClassNames, content: options.noEventsContent, defaultContent: renderNoEventsInner, didMount: options.noEventsDidMount, willUnmount: options.noEventsWillUnmount }, function (rootElRef, classNames, innerElRef, innerContent) { return (createElement("div", { className: ['fc-list-empty'].concat(classNames).join(' '), ref: rootElRef }, + createElement("div", { className: "fc-list-empty-cushion", ref: innerElRef }, innerContent))); })); + }; + ListView.prototype.renderSegList = function (allSegs, dayDates) { + var _a = this.context, theme = _a.theme, options = _a.options; + var segsByDay = groupSegsByDay(allSegs); // sparse array + return (createElement(NowTimer, { unit: "day" }, function (nowDate, todayRange) { + var innerNodes = []; + for (var dayIndex = 0; dayIndex < segsByDay.length; dayIndex += 1) { + var daySegs = segsByDay[dayIndex]; + if (daySegs) { // sparse array, so might be undefined + var dayStr = dayDates[dayIndex].toISOString(); + // append a day header + innerNodes.push(createElement(ListViewHeaderRow, { key: dayStr, dayDate: dayDates[dayIndex], todayRange: todayRange })); + daySegs = sortEventSegs(daySegs, options.eventOrder); + for (var _i = 0, daySegs_1 = daySegs; _i < daySegs_1.length; _i++) { + var seg = daySegs_1[_i]; + innerNodes.push(createElement(ListViewEventRow, __assign({ key: dayStr + ':' + seg.eventRange.instance.instanceId /* are multiple segs for an instanceId */, seg: seg, isDragging: false, isResizing: false, isDateSelecting: false, isSelected: false }, getSegMeta(seg, todayRange, nowDate)))); + } + } + } + return (createElement("table", { className: 'fc-list-table ' + theme.getClass('table') }, + createElement("tbody", null, innerNodes))); + })); + }; + ListView.prototype._eventStoreToSegs = function (eventStore, eventUiBases, dayRanges) { + return this.eventRangesToSegs(sliceEventStore(eventStore, eventUiBases, this.props.dateProfile.activeRange, this.context.options.nextDayThreshold).fg, dayRanges); + }; + ListView.prototype.eventRangesToSegs = function (eventRanges, dayRanges) { + var segs = []; + for (var _i = 0, eventRanges_1 = eventRanges; _i < eventRanges_1.length; _i++) { + var eventRange = eventRanges_1[_i]; + segs.push.apply(segs, this.eventRangeToSegs(eventRange, dayRanges)); + } + return segs; + }; + ListView.prototype.eventRangeToSegs = function (eventRange, dayRanges) { + var dateEnv = this.context.dateEnv; + var nextDayThreshold = this.context.options.nextDayThreshold; + var range = eventRange.range; + var allDay = eventRange.def.allDay; + var dayIndex; + var segRange; + var seg; + var segs = []; + for (dayIndex = 0; dayIndex < dayRanges.length; dayIndex += 1) { + segRange = intersectRanges(range, dayRanges[dayIndex]); + if (segRange) { + seg = { + component: this, + eventRange: eventRange, + start: segRange.start, + end: segRange.end, + isStart: eventRange.isStart && segRange.start.valueOf() === range.start.valueOf(), + isEnd: eventRange.isEnd && segRange.end.valueOf() === range.end.valueOf(), + dayIndex: dayIndex, + }; + segs.push(seg); + // detect when range won't go fully into the next day, + // and mutate the latest seg to the be the end. + if (!seg.isEnd && !allDay && + dayIndex + 1 < dayRanges.length && + range.end < + dateEnv.add(dayRanges[dayIndex + 1].start, nextDayThreshold)) { + seg.end = range.end; + seg.isEnd = true; + break; + } + } + } + return segs; + }; + return ListView; + }(DateComponent)); + function renderNoEventsInner(hookProps) { + return hookProps.text; + } + function computeDateVars(dateProfile) { + var dayStart = startOfDay(dateProfile.renderRange.start); + var viewEnd = dateProfile.renderRange.end; + var dayDates = []; + var dayRanges = []; + while (dayStart < viewEnd) { + dayDates.push(dayStart); + dayRanges.push({ + start: dayStart, + end: addDays(dayStart, 1), + }); + dayStart = addDays(dayStart, 1); + } + return { dayDates: dayDates, dayRanges: dayRanges }; + } + // Returns a sparse array of arrays, segs grouped by their dayIndex + function groupSegsByDay(segs) { + var segsByDay = []; // sparse array + var i; + var seg; + for (i = 0; i < segs.length; i += 1) { + seg = segs[i]; + (segsByDay[seg.dayIndex] || (segsByDay[seg.dayIndex] = [])) + .push(seg); + } + return segsByDay; + } + + var OPTION_REFINERS$1 = { + listDayFormat: createFalsableFormatter, + listDaySideFormat: createFalsableFormatter, + noEventsClassNames: identity, + noEventsContent: identity, + noEventsDidMount: identity, + noEventsWillUnmount: identity, + // noEventsText is defined in base options + }; + function createFalsableFormatter(input) { + return input === false ? null : createFormatter(input); + } + + var listPlugin = createPlugin({ + optionRefiners: OPTION_REFINERS$1, + views: { + list: { + component: ListView, + buttonTextKey: 'list', + listDayFormat: { month: 'long', day: 'numeric', year: 'numeric' }, // like "January 1, 2016" + }, + listDay: { + type: 'list', + duration: { days: 1 }, + listDayFormat: { weekday: 'long' }, // day-of-week is all we need. full date is probably in headerToolbar + }, + listWeek: { + type: 'list', + duration: { weeks: 1 }, + listDayFormat: { weekday: 'long' }, + listDaySideFormat: { month: 'long', day: 'numeric', year: 'numeric' }, + }, + listMonth: { + type: 'list', + duration: { month: 1 }, + listDaySideFormat: { weekday: 'long' }, // day-of-week is nice-to-have + }, + listYear: { + type: 'list', + duration: { year: 1 }, + listDaySideFormat: { weekday: 'long' }, // day-of-week is nice-to-have + }, + }, + }); + + var BootstrapTheme = /** @class */ (function (_super) { + __extends(BootstrapTheme, _super); + function BootstrapTheme() { + return _super !== null && _super.apply(this, arguments) || this; + } + return BootstrapTheme; + }(Theme)); + BootstrapTheme.prototype.classes = { + root: 'fc-theme-bootstrap', + table: 'table-bordered', + tableCellShaded: 'table-active', + buttonGroup: 'btn-group', + button: 'btn btn-primary', + buttonActive: 'active', + popover: 'popover', + popoverHeader: 'popover-header', + popoverContent: 'popover-body', + }; + BootstrapTheme.prototype.baseIconClass = 'fa'; + BootstrapTheme.prototype.iconClasses = { + close: 'fa-times', + prev: 'fa-chevron-left', + next: 'fa-chevron-right', + prevYear: 'fa-angle-double-left', + nextYear: 'fa-angle-double-right', + }; + BootstrapTheme.prototype.rtlIconClasses = { + prev: 'fa-chevron-right', + next: 'fa-chevron-left', + prevYear: 'fa-angle-double-right', + nextYear: 'fa-angle-double-left', + }; + BootstrapTheme.prototype.iconOverrideOption = 'bootstrapFontAwesome'; // TODO: make TS-friendly. move the option-processing into this plugin + BootstrapTheme.prototype.iconOverrideCustomButtonOption = 'bootstrapFontAwesome'; + BootstrapTheme.prototype.iconOverridePrefix = 'fa-'; + var plugin = createPlugin({ + themeClasses: { + bootstrap: BootstrapTheme, + }, + }); + + // rename this file to options.ts like other packages? + var OPTION_REFINERS = { + googleCalendarApiKey: String, + }; + + var EVENT_SOURCE_REFINERS = { + googleCalendarApiKey: String, + googleCalendarId: String, + googleCalendarApiBase: String, + extraParams: identity, + }; + + // TODO: expose somehow + var API_BASE = 'https://www.googleapis.com/calendar/v3/calendars'; + var eventSourceDef = { + parseMeta: function (refined) { + var googleCalendarId = refined.googleCalendarId; + if (!googleCalendarId && refined.url) { + googleCalendarId = parseGoogleCalendarId(refined.url); + } + if (googleCalendarId) { + return { + googleCalendarId: googleCalendarId, + googleCalendarApiKey: refined.googleCalendarApiKey, + googleCalendarApiBase: refined.googleCalendarApiBase, + extraParams: refined.extraParams, + }; + } + return null; + }, + fetch: function (arg, onSuccess, onFailure) { + var _a = arg.context, dateEnv = _a.dateEnv, options = _a.options; + var meta = arg.eventSource.meta; + var apiKey = meta.googleCalendarApiKey || options.googleCalendarApiKey; + if (!apiKey) { + onFailure({ + message: 'Specify a googleCalendarApiKey. See http://fullcalendar.io/docs/google_calendar/', + }); + } + else { + var url = buildUrl(meta); + // TODO: make DRY with json-feed-event-source + var extraParams = meta.extraParams; + var extraParamsObj = typeof extraParams === 'function' ? extraParams() : extraParams; + var requestParams_1 = buildRequestParams(arg.range, apiKey, extraParamsObj, dateEnv); + requestJson('GET', url, requestParams_1, function (body, xhr) { + if (body.error) { + onFailure({ + message: 'Google Calendar API: ' + body.error.message, + errors: body.error.errors, + xhr: xhr, + }); + } + else { + onSuccess({ + rawEvents: gcalItemsToRawEventDefs(body.items, requestParams_1.timeZone), + xhr: xhr, + }); + } + }, function (message, xhr) { + onFailure({ message: message, xhr: xhr }); + }); + } + }, + }; + function parseGoogleCalendarId(url) { + var match; + // detect if the ID was specified as a single string. + // will match calendars like "asdf1234@calendar.google.com" in addition to person email calendars. + if (/^[^/]+@([^/.]+\.)*(google|googlemail|gmail)\.com$/.test(url)) { + return url; + } + if ((match = /^https:\/\/www.googleapis.com\/calendar\/v3\/calendars\/([^/]*)/.exec(url)) || + (match = /^https?:\/\/www.google.com\/calendar\/feeds\/([^/]*)/.exec(url))) { + return decodeURIComponent(match[1]); + } + return null; + } + function buildUrl(meta) { + var apiBase = meta.googleCalendarApiBase; + if (!apiBase) { + apiBase = API_BASE; + } + return apiBase + '/' + encodeURIComponent(meta.googleCalendarId) + '/events'; + } + function buildRequestParams(range, apiKey, extraParams, dateEnv) { + var params; + var startStr; + var endStr; + if (dateEnv.canComputeOffset) { + // strings will naturally have offsets, which GCal needs + startStr = dateEnv.formatIso(range.start); + endStr = dateEnv.formatIso(range.end); + } + else { + // when timezone isn't known, we don't know what the UTC offset should be, so ask for +/- 1 day + // from the UTC day-start to guarantee we're getting all the events + // (start/end will be UTC-coerced dates, so toISOString is okay) + startStr = addDays(range.start, -1).toISOString(); + endStr = addDays(range.end, 1).toISOString(); + } + params = __assign(__assign({}, (extraParams || {})), { key: apiKey, timeMin: startStr, timeMax: endStr, singleEvents: true, maxResults: 9999 }); + if (dateEnv.timeZone !== 'local') { + params.timeZone = dateEnv.timeZone; + } + return params; + } + function gcalItemsToRawEventDefs(items, gcalTimezone) { + return items.map(function (item) { return gcalItemToRawEventDef(item, gcalTimezone); }); + } + function gcalItemToRawEventDef(item, gcalTimezone) { + var url = item.htmlLink || null; + // make the URLs for each event show times in the correct timezone + if (url && gcalTimezone) { + url = injectQsComponent(url, 'ctz=' + gcalTimezone); + } + return { + id: item.id, + title: item.summary, + start: item.start.dateTime || item.start.date, + end: item.end.dateTime || item.end.date, + url: url, + location: item.location, + description: item.description, + attachments: item.attachments || [], + extendedProps: (item.extendedProperties || {}).shared || {}, + }; + } + // Injects a string like "arg=value" into the querystring of a URL + // TODO: move to a general util file? + function injectQsComponent(url, component) { + // inject it after the querystring but before the fragment + return url.replace(/(\?.*?)?(#|$)/, function (whole, qs, hash) { return (qs ? qs + '&' : '?') + component + hash; }); + } + var googleCalendarPlugin = createPlugin({ + eventSourceDefs: [eventSourceDef], + optionRefiners: OPTION_REFINERS, + eventSourceRefiners: EVENT_SOURCE_REFINERS, + }); + + globalPlugins.push(interactionPlugin, dayGridPlugin, timeGridPlugin, listPlugin, plugin, googleCalendarPlugin); + + exports.BASE_OPTION_DEFAULTS = BASE_OPTION_DEFAULTS; + exports.BASE_OPTION_REFINERS = BASE_OPTION_REFINERS; + exports.BaseComponent = BaseComponent; + exports.BgEvent = BgEvent; + exports.BootstrapTheme = BootstrapTheme; + exports.Calendar = Calendar; + exports.CalendarApi = CalendarApi; + exports.CalendarContent = CalendarContent; + exports.CalendarDataManager = CalendarDataManager; + exports.CalendarDataProvider = CalendarDataProvider; + exports.CalendarRoot = CalendarRoot; + exports.Component = Component; + exports.ContentHook = ContentHook; + exports.CustomContentRenderContext = CustomContentRenderContext; + exports.DateComponent = DateComponent; + exports.DateEnv = DateEnv; + exports.DateProfileGenerator = DateProfileGenerator; + exports.DayCellContent = DayCellContent; + exports.DayCellRoot = DayCellRoot; + exports.DayGridView = DayTableView; + exports.DayHeader = DayHeader; + exports.DaySeriesModel = DaySeriesModel; + exports.DayTable = DayTable; + exports.DayTableModel = DayTableModel; + exports.DayTableSlicer = DayTableSlicer; + exports.DayTimeCols = DayTimeCols; + exports.DayTimeColsSlicer = DayTimeColsSlicer; + exports.DayTimeColsView = DayTimeColsView; + exports.DelayedRunner = DelayedRunner; + exports.Draggable = ExternalDraggable; + exports.ElementDragging = ElementDragging; + exports.ElementScrollController = ElementScrollController; + exports.Emitter = Emitter; + exports.EventApi = EventApi; + exports.EventRoot = EventRoot; + exports.EventSourceApi = EventSourceApi; + exports.FeaturefulElementDragging = FeaturefulElementDragging; + exports.Fragment = Fragment; + exports.Interaction = Interaction; + exports.ListView = ListView; + exports.MoreLinkRoot = MoreLinkRoot; + exports.MountHook = MountHook; + exports.NamedTimeZoneImpl = NamedTimeZoneImpl; + exports.NowIndicatorRoot = NowIndicatorRoot; + exports.NowTimer = NowTimer; + exports.PointerDragging = PointerDragging; + exports.PositionCache = PositionCache; + exports.RefMap = RefMap; + exports.RenderHook = RenderHook; + exports.ScrollController = ScrollController; + exports.ScrollResponder = ScrollResponder; + exports.Scroller = Scroller; + exports.SegHierarchy = SegHierarchy; + exports.SimpleScrollGrid = SimpleScrollGrid; + exports.Slicer = Slicer; + exports.Splitter = Splitter; + exports.StandardEvent = StandardEvent; + exports.Table = Table; + exports.TableDateCell = TableDateCell; + exports.TableDowCell = TableDowCell; + exports.TableView = TableView; + exports.Theme = Theme; + exports.ThirdPartyDraggable = ThirdPartyDraggable; + exports.TimeCols = TimeCols; + exports.TimeColsSlatsCoords = TimeColsSlatsCoords; + exports.TimeColsView = TimeColsView; + exports.ViewApi = ViewApi; + exports.ViewContextType = ViewContextType; + exports.ViewRoot = ViewRoot; + exports.WeekNumberRoot = WeekNumberRoot; + exports.WindowScrollController = WindowScrollController; + exports.addDays = addDays; + exports.addDurations = addDurations; + exports.addMs = addMs; + exports.addWeeks = addWeeks; + exports.allowContextMenu = allowContextMenu; + exports.allowSelection = allowSelection; + exports.applyMutationToEventStore = applyMutationToEventStore; + exports.applyStyle = applyStyle; + exports.applyStyleProp = applyStyleProp; + exports.asCleanDays = asCleanDays; + exports.asRoughMinutes = asRoughMinutes; + exports.asRoughMs = asRoughMs; + exports.asRoughSeconds = asRoughSeconds; + exports.binarySearch = binarySearch; + exports.buildClassNameNormalizer = buildClassNameNormalizer; + exports.buildDayRanges = buildDayRanges; + exports.buildDayTableModel = buildDayTableModel; + exports.buildEntryKey = buildEntryKey; + exports.buildEventApis = buildEventApis; + exports.buildEventRangeKey = buildEventRangeKey; + exports.buildHashFromArray = buildHashFromArray; + exports.buildIsoString = buildIsoString; + exports.buildNavLinkData = buildNavLinkData; + exports.buildSegCompareObj = buildSegCompareObj; + exports.buildSegTimeText = buildSegTimeText; + exports.buildSlatMetas = buildSlatMetas; + exports.buildTimeColsModel = buildTimeColsModel; + exports.collectFromHash = collectFromHash; + exports.combineEventUis = combineEventUis; + exports.compareByFieldSpec = compareByFieldSpec; + exports.compareByFieldSpecs = compareByFieldSpecs; + exports.compareNumbers = compareNumbers; + exports.compareObjs = compareObjs; + exports.computeEarliestSegStart = computeEarliestSegStart; + exports.computeEdges = computeEdges; + exports.computeFallbackHeaderFormat = computeFallbackHeaderFormat; + exports.computeHeightAndMargins = computeHeightAndMargins; + exports.computeInnerRect = computeInnerRect; + exports.computeRect = computeRect; + exports.computeSegDraggable = computeSegDraggable; + exports.computeSegEndResizable = computeSegEndResizable; + exports.computeSegStartResizable = computeSegStartResizable; + exports.computeShrinkWidth = computeShrinkWidth; + exports.computeSmallestCellWidth = computeSmallestCellWidth; + exports.computeVisibleDayRange = computeVisibleDayRange; + exports.config = config; + exports.constrainPoint = constrainPoint; + exports.createContext = createContext; + exports.createDuration = createDuration; + exports.createElement = createElement; + exports.createEmptyEventStore = createEmptyEventStore; + exports.createEventInstance = createEventInstance; + exports.createEventUi = createEventUi; + exports.createFormatter = createFormatter; + exports.createPlugin = createPlugin; + exports.createPortal = createPortal; + exports.createRef = createRef; + exports.diffDates = diffDates; + exports.diffDayAndTime = diffDayAndTime; + exports.diffDays = diffDays; + exports.diffPoints = diffPoints; + exports.diffWeeks = diffWeeks; + exports.diffWholeDays = diffWholeDays; + exports.diffWholeWeeks = diffWholeWeeks; + exports.disableCursor = disableCursor; + exports.elementClosest = elementClosest; + exports.elementMatches = elementMatches; + exports.enableCursor = enableCursor; + exports.eventTupleToStore = eventTupleToStore; + exports.filterEventStoreDefs = filterEventStoreDefs; + exports.filterHash = filterHash; + exports.findDirectChildren = findDirectChildren; + exports.findElements = findElements; + exports.flexibleCompare = flexibleCompare; + exports.flushToDom = flushToDom; + exports.formatDate = formatDate; + exports.formatDayString = formatDayString; + exports.formatIsoTimeString = formatIsoTimeString; + exports.formatRange = formatRange; + exports.getAllowYScrolling = getAllowYScrolling; + exports.getCanVGrowWithinCell = getCanVGrowWithinCell; + exports.getClippingParents = getClippingParents; + exports.getDateMeta = getDateMeta; + exports.getDayClassNames = getDayClassNames; + exports.getDefaultEventEnd = getDefaultEventEnd; + exports.getElRoot = getElRoot; + exports.getElSeg = getElSeg; + exports.getEntrySpanEnd = getEntrySpanEnd; + exports.getEventClassNames = getEventClassNames; + exports.getEventTargetViaRoot = getEventTargetViaRoot; + exports.getIsRtlScrollbarOnLeft = getIsRtlScrollbarOnLeft; + exports.getRectCenter = getRectCenter; + exports.getRelevantEvents = getRelevantEvents; + exports.getScrollGridClassNames = getScrollGridClassNames; + exports.getScrollbarWidths = getScrollbarWidths; + exports.getSectionClassNames = getSectionClassNames; + exports.getSectionHasLiquidHeight = getSectionHasLiquidHeight; + exports.getSegMeta = getSegMeta; + exports.getSlotClassNames = getSlotClassNames; + exports.getStickyFooterScrollbar = getStickyFooterScrollbar; + exports.getStickyHeaderDates = getStickyHeaderDates; + exports.getUnequalProps = getUnequalProps; + exports.globalLocales = globalLocales; + exports.globalPlugins = globalPlugins; + exports.greatestDurationDenominator = greatestDurationDenominator; + exports.groupIntersectingEntries = groupIntersectingEntries; + exports.guid = guid; + exports.hasBgRendering = hasBgRendering; + exports.hasShrinkWidth = hasShrinkWidth; + exports.identity = identity; + exports.interactionSettingsStore = interactionSettingsStore; + exports.interactionSettingsToStore = interactionSettingsToStore; + exports.intersectRanges = intersectRanges; + exports.intersectRects = intersectRects; + exports.intersectSpans = intersectSpans; + exports.isArraysEqual = isArraysEqual; + exports.isColPropsEqual = isColPropsEqual; + exports.isDateSelectionValid = isDateSelectionValid; + exports.isDateSpansEqual = isDateSpansEqual; + exports.isInt = isInt; + exports.isInteractionValid = isInteractionValid; + exports.isMultiDayRange = isMultiDayRange; + exports.isPropsEqual = isPropsEqual; + exports.isPropsValid = isPropsValid; + exports.isValidDate = isValidDate; + exports.joinSpans = joinSpans; + exports.listenBySelector = listenBySelector; + exports.mapHash = mapHash; + exports.memoize = memoize; + exports.memoizeArraylike = memoizeArraylike; + exports.memoizeHashlike = memoizeHashlike; + exports.memoizeObjArg = memoizeObjArg; + exports.mergeEventStores = mergeEventStores; + exports.multiplyDuration = multiplyDuration; + exports.padStart = padStart; + exports.parseBusinessHours = parseBusinessHours; + exports.parseClassNames = parseClassNames; + exports.parseDragMeta = parseDragMeta; + exports.parseEventDef = parseEventDef; + exports.parseFieldSpecs = parseFieldSpecs; + exports.parseMarker = parse; + exports.pointInsideRect = pointInsideRect; + exports.preventContextMenu = preventContextMenu; + exports.preventDefault = preventDefault; + exports.preventSelection = preventSelection; + exports.rangeContainsMarker = rangeContainsMarker; + exports.rangeContainsRange = rangeContainsRange; + exports.rangesEqual = rangesEqual; + exports.rangesIntersect = rangesIntersect; + exports.refineEventDef = refineEventDef; + exports.refineProps = refineProps; + exports.removeElement = removeElement; + exports.removeExact = removeExact; + exports.render = render; + exports.renderChunkContent = renderChunkContent; + exports.renderFill = renderFill; + exports.renderMicroColGroup = renderMicroColGroup; + exports.renderScrollShim = renderScrollShim; + exports.requestJson = requestJson; + exports.sanitizeShrinkWidth = sanitizeShrinkWidth; + exports.setElSeg = setElSeg; + exports.setRef = setRef; + exports.sliceEventStore = sliceEventStore; + exports.sliceEvents = sliceEvents; + exports.sortEventSegs = sortEventSegs; + exports.startOfDay = startOfDay; + exports.translateRect = translateRect; + exports.triggerDateSelect = triggerDateSelect; + exports.unmountComponentAtNode = unmountComponentAtNode; + exports.unpromisify = unpromisify; + exports.version = version; + exports.whenTransitionDone = whenTransitionDone; + exports.wholeDivideDurations = wholeDivideDurations; + + Object.defineProperty(exports, '__esModule', { value: true }); + + return exports; + +}({})); diff --git a/apps/schoolCalendar/interface.html b/apps/schoolCalendar/interface.html new file mode 100644 index 000000000..b26c07723 --- /dev/null +++ b/apps/schoolCalendar/interface.html @@ -0,0 +1,82 @@ + + + +
+

Create your events on the current week. Keep in note that your events repeat weekly.

+

One you have created your events, Click

+
+ + + + + + + +
+ + diff --git a/apps/schoolCalendar/schoolCalendar.js b/apps/schoolCalendar/schoolCalendar.js new file mode 100644 index 000000000..be4c45d45 --- /dev/null +++ b/apps/schoolCalendar/schoolCalendar.js @@ -0,0 +1,229 @@ +require("FontTeletext5x9Mode7").add(Graphics); +Bangle.setLCDMode(); + +function getBackgroundImage() { + return require("heatshrink").decompress(atob("gMwyEgBAsAgQBCgcAggBCgsAgwBCg8AhABChMAhQBChcAhgBChsAhwBCh8AiEAiIBCiUAiYBCikAioBCi0Ai4BCjEAjIBCjUAjYBCjkAjoBCj0Aj4BBA")); +} + +Graphics.prototype.setFontAudiowide = function() { + // Actual height 33 (36 - 4) + var widths = atob("BxYfDBkYGhkZFRkZCA=="); + var font = atob("AAAAAAAAA8AAAAHgAAAB8AAAAHgAAAA4AAAAAAAAAAEAAAABgAAAA8AAAAPgAAAH8AAAB/gAAA/4AAAf+AAAH/AAAD/wAAA/4AAAf8AAAP/AAAD/gAAB/4AAAf8AAAD+AAAAfgAAADwAAAAcAAAAAAAAAAAAAAAAAAAAAAP/AAAH//AAB//8AAf//wAH///AA///4APwD/gB8A/8APgP/gB8D98AfAfvgD4H58AfB/PgD4Px8AfD8PgD4/h8AfH4PgD5+B8AP/wPgB/8B8AP/APgB/4D8AH8B/AA///4AD//+AAP//gAA//4AAB/8AAAAAAAAAAAAAAAAAAD4AAAAfAAAAD4AAAAfAAAAD///8Af///gD///8Af///gD///8AAAAAAAAAAAAAAAAAAAA/8AAAf/gD4H/8AfA//gD4P/8AfB+PgD4Ph8AfB8PgD4Ph8AfB8PgD4Ph8AfB8PgD4Ph8AfB8PgD4Ph8AfB8PgB8Ph8AP/8PgB//B8AP/4PgA/+B8AD/gPgABgA8AAAAAAAAAAAAPA4HgD4Ph8AfB8PgD4Ph8AfB8PgD4Ph8AfB8PgD4Ph8AfB8PgD4Ph8AfB8PgD4Ph8AfB8PgD4Ph8AfB8PgB8Ph8AP///gB///8AH///AA///wAB//8AAAAAAAAAAAAB/8AAAf/4AAD//gAAf/8AAD//gAAf/8AAAAPgAAAB8AAAAPgAAAB8AAAAPgAAAB8AAAAPgAAAB8AAAAPgAAAB8AAAAPgAAP///gD///8Af///gD///8Af///gD///8AAAAAAAAAAAAAAAAAAAAAAAAAAAAD/8B8Af/wPgD//B8Af/4PgD//h8AfB8PgD4Ph8AfB8PgD4Ph8AfB8PgD4Ph8AfB8PgD4Ph8AfB8PgD4Ph8AfB8PgD4Ph8AfB//gD4H/8AfA//AAAD/4AAAP8AAAAAAAAAAAAAH//AAB//8AAf//wAH///AB///8AP58/gB8Ph8APh8PgD4Ph8AfB8PgD4Ph8AfB8PgD4Ph8AfB8PgD4Ph8AfB8PgD4Ph8AfB//gD4H/8AAA//AAAD/4AAAP8AAAAAAAAAAAAD4AAAAfAAAAD4AAAAfAABgD4AA8AfAAPgD4AH8AfAD/gD4A/8AfAf/AD4P/gAfH/wAD5/8AAf/+AAD//AAAf/gAAD/4AAAf8AAAB+AAAAPAAAAAAAAAAAAAAAAAB/gAAAf+AAP//4AH///gA///8AP/+PgB//h8APh8PgD4Ph8AfB8PgD4Ph8AfB8PgD4Ph8AfB8PgB8Ph8AP/8PgB//h8AP///gA///8AD///AADz/4AAAP8AAAAMAAAAAAAAAAAAAB/gAAA/+AAAH/4AAB//B8AP/8PgB8Ph8AfB8PgD4Ph8AfB8PgD4Ph8AfB8PgD4Ph8AfB8PgD4Ph8APh8PgB8Ph8APx8fgB///8AH///AAf//wAD//8AAH//AAAAAAAAAAAAAAAAAAAAAAAAAOAHgAD4A8AAfAPgAD4A8AAOAHAAAAAAA=="); + var scale = 1; // size multiplier for this font + g.setFontCustom(font, 46, widths, 33+(scale<<8)+(1<<16)); +}; + +function logDebug(message){ + //console.log(message); +} + +var NEXTCLASS = 4; +var CURRRENTCLASS = 3; +var NEXTNEXTCLASS = 5; +var BEHINDCLASS = 2; +var BEHINDBEHINDCLASS = 1; +var NEXTNEXTNEXTCLASS = 6; +var stage = 3; + +function drawInfo(){ + var currentDate = new Date(); + var currentDayOfWeek = currentDate.getDay(); + var currentHour = currentDate.getHours(); + var currentMinute = currentDate.getMinutes(); + var currentMinuteUpdated; + var currentHourUpdated; + if (currentMinute<10){ + currentMinuteUpdated = "0"+currentMinute; + }else{ + currentMinuteUpdated = currentMinute; + }if(currentHour >= 13){ + currentHourUpdated = currentHour-12; + }else{ + currentHourUpdated = currentHour; + } + for(var i = 0;i<=240;i++){ + g.drawImage(getBackgroundImage(),i,120,{scale:5,rotate:0}); + } + g.setColor(255,255,255); + g.setFont("Audiowide"); + g.drawString(currentHourUpdated+":"+currentMinuteUpdated, 145, 16); + g.setFont("Teletext5x9Mode7", 2); + foundClass = processDay(); + if (foundClass.startingTimeMinute<10){ + classMinuteUpdated = "0"+foundClass.startingTimeMinute; + }else{ + classMinuteUpdated = foundClass.startingTimeMinute; + } + if (foundClass.endingTimeMinute<10){ + classEndingMinuteUpdated = "0"+foundClass.endingTimeMinute; + }else{ + classEndingMinuteUpdated = foundClass.endingTimeMinute; + }if(foundClass.startingTimeHour >= 13){ + classHourUpdated = foundClass.startingTimeHour-12; + }else{ + classHourUpdated = foundClass.startingTimeHour; + }if(foundClass.endingTimeHour >= 13){ + classEndingHourUpdated = foundClass.endingTimeHour-12; + }else{ + classEndingHourUpdated = foundClass.endingTimeHour; + } + switch (foundClass.dayOfWeek) { + case 0: + updatedDay = "Sun"; + break; + case 1: + updatedDay = "Mon"; + break; + case 2: + updatedDay = "Tue"; + break; + case 3: + updatedDay = "Wed"; + break; + case 4: + updatedDay = "Thur"; + break; + case 5: + updatedDay = "Fri"; + break; + case 6: + updatedDay = "Sat"; +} + if (foundClass != null) { + g.drawString(classHourUpdated+":"+classMinuteUpdated+" - "+classEndingHourUpdated+":"+classEndingMinuteUpdated+" "+updatedDay, 25, 50); + g.drawString(foundClass.className, 25, 80); + g.drawString(foundClass.teacher, 25, 110); + g.drawString(foundClass.roomNumber, 25, 140); + } +} +setInterval(drawInfo, 60000); + +function processDay(){ + let schedule = [ + //Sunday + + //Monday: + {className: "Biblical Theology", dayOfWeek:1, startingTimeHour: 8, startingTimeMinute: 10, endingTimeHour:9, endingTimeMinute: 5, description:"Biblical Theology 7B 3B Mr. Besaw Block 3B M207", roomNumber:"207", teacher:"Mr. Besaw"}, + {className: "English", dayOfWeek:1, startingTimeHour: 9, startingTimeMinute: 5, endingTimeHour:10, endingTimeMinute: 0, description:"English 7B 4B Dr. Wong Block 4B M206", teacher:"Dr. Wong"}, + {className: "Break", dayOfWeek:1, startingTimeHour: 10, startingTimeMinute: 0, endingTimeHour:10, endingTimeMinute: 10, description:"Break MF MS", teacher:""}, + {className: "MS Robotics", dayOfWeek:1, startingTimeHour: 10, startingTimeMinute: 10, endingTimeHour:11, endingTimeMinute: 0, description:"MS Robotics S1A Mr. Broyles MS MF Elective Block A M211", roomNumber:"211", teacher:"Mr. Broyles"}, + {className: "MS Physical Education Boys", dayOfWeek:1, startingTimeHour: 11, startingTimeMinute: 0, endingTimeHour:11, endingTimeMinute: 50, description:"MS Physical Education Boys S1B Mr. Mendezona MS MF Elective Block B Gym", roomNumber:"GYM", teacher:"Mr. Mendezona"}, + {className: "Office Hours Besaw/Nunez", dayOfWeek:1, startingTimeHour: 11, startingTimeMinute: 50, endingTimeHour:12, endingTimeMinute: 25, description:"Office Hours Besaw/Nunez Mr. Besaw, Dr. Nunez, Mrs.McDonough, Mr. Pettit Office Hours MF MS M203", roomNumber:"203", teacher:"Besaw/Nunez"}, + {className: "Lunch", dayOfWeek:1, startingTimeHour: 12, startingTimeMinute: 25, endingTimeHour:12, endingTimeMinute: 50, description:"Lunch MF MS", roomNumber:"Commence or Advisory", teacher:""}, + {className: "Activity Period", dayOfWeek:1, startingTimeHour: 12, startingTimeMinute: 50, endingTimeHour:13, endingTimeMinute: 0, description:"Activity Period MF MS", roomNumber:"Outside", teacher:""}, + {className: "Latin", dayOfWeek:1, startingTimeHour: 13, startingTimeMinute: 5, endingTimeHour:14, endingTimeMinute: 0, description:"Latin 7B 5B Mrs. Scrivner Block 5B M208", roomNumber:"208", teacher:"Mrs.Scrivner"}, + {className: "Algebra 1", dayOfWeek:1, startingTimeHour: 14, startingTimeMinute: 0, endingTimeHour:15, endingTimeMinute: 0, description:"Algebra 1 7B 6B Mr. Benson Block 6B M204", roomNumber:"204", teacher:"Mr. Benson"}, + + //Tuesday: + {className: "Logic", dayOfWeek:2, startingTimeHour: 8, startingTimeMinute: 10, endingTimeHour:9, endingTimeMinute: 0, description:"Logic 7B 5B Mrs. Scrivner Block 5B M208", roomNumber:"208", teacher:"Mrs.Scrivner"}, + {className: "Algebra 1", dayOfWeek:2, startingTimeHour: 9, startingTimeMinute: 0, endingTimeHour:10, endingTimeMinute: 0, description:"Algebra 1 7B 6B Mr. Benson Block 6B M204", roomNumber:"204", teacher:"Mr. Benson"}, + {className: "Chapel", dayOfWeek:2, startingTimeHour: 10, startingTimeMinute: 0, endingTimeHour:10, endingTimeMinute: 25, description:"Chapel MF MS", roomNumber:"Advisory", teacher:""}, + {className: "Break", dayOfWeek:2, startingTimeHour: 10, startingTimeMinute: 25, endingTimeHour:10, endingTimeMinute: 35, description:"Break MF MS", roomNumber:"Outside", teacher:""}, + {className: "Advisory Besaw", dayOfWeek:2, startingTimeHour: 10, startingTimeMinute: 35, endingTimeHour:11, endingTimeMinute: 0, description:"Advisory Besaw Mr. Besaw Advisory MF MS M207", roomNumber:"207", teacher:"Mr. Besaw"}, + {className: "MS Robotics", dayOfWeek:2, startingTimeHour: 11, startingTimeMinute: 0, endingTimeHour:11, endingTimeMinute: 50, description:"MS Robotics S1A Mr. Broyles MS MF Elective Block A M211", roomNumber:"211", teacher:"Mr. Broyles"}, + {className: "Office Hours Besaw/Nunez", dayOfWeek:2, startingTimeHour: 11, startingTimeMinute: 50, endingTimeHour:12, endingTimeMinute: 25, description:"Office Hours Besaw/Nunez Mr. Besaw, Dr. Nunez, Mrs.McDonough, Mr. Pettit Office Hours MF MS M203", roomNumber:"203", teacher:" Besaw/Nunez"}, + {className: "Lunch", dayOfWeek:2, startingTimeHour: 12, startingTimeMinute: 25, endingTimeHour:12, endingTimeMinute: 50, description:"Lunch MF MS", roomNumber:"Commence or Advisory", teacher:""}, + {className: "Activity Period", dayOfWeek:2, startingTimeHour: 12, startingTimeMinute: 50, endingTimeHour:13, endingTimeMinute: 5, description:"Activity Period MF MS", roomNumber:"Outside", teacher:""}, + {className: "Medieval Western Civilization", dayOfWeek:2, startingTimeHour: 13, startingTimeMinute: 5, endingTimeHour:14, endingTimeMinute: 0, description:"Medieval Western Civilization 7B 1B Mr. Kuhle Block 1BM205", roomNumber:"205", teacher:"Mr. Khule"}, + {className: "Introductory Biology and Epidemiology", dayOfWeek:2, startingTimeHour: 14, startingTimeMinute: 0, endingTimeHour:15, endingTimeMinute: 0, description:"Introductory Biology and Epidemiology 7B 2B Mrs. Brown Block 2B M202", roomNumber:"202", teacher:"Mrs. Brown"}, + + //Wensday: + {className: "English", dayOfWeek:3, startingTimeHour: 9, startingTimeMinute: 0, endingTimeHour:9, endingTimeMinute: 55, description:"English 7B 4B Dr. Wong Block 4B M206", roomNumber:"206", teacher:"Dr. Wong"}, + {className: "Biblical Theology", dayOfWeek:3, startingTimeHour: 9, startingTimeMinute: 55, endingTimeHour:10, endingTimeMinute: 50, description:"Biblical Theology 7B 3B Mr. Besaw Block 3B M207", roomNumber:"207", teacher:"Mr. Besaw"}, + {className: "Break", dayOfWeek:3, startingTimeHour: 10, startingTimeMinute: 50, endingTimeHour:11, endingTimeMinute: 0, description:"Break MF MS", roomNumber:"Outside", teacher:""}, + {className: "MS Physical Education Boys", dayOfWeek:3, startingTimeHour: 11, startingTimeMinute: 0, endingTimeHour:11, endingTimeMinute: 50, description:"MS Physical Education Boys S1B Mr. Mendezona MS MF Elective Block B Gym", roomNumber:"GYM", teacher:"Mr. Mendezona"}, + {className: "Office Hours Besaw/Nunez", dayOfWeek:3, startingTimeHour: 11, startingTimeMinute: 50, endingTimeHour:12, endingTimeMinute: 25, description:"Office Hours Besaw/Nunez Mr. Besaw, Dr. Nunez, Mrs.McDonough, Mr. Pettit Office Hours MF MS M203", roomNumber:"203", teacher:" Besaw/Nunez"}, + {className: "Lunch", dayOfWeek:3, startingTimeHour: 12, startingTimeMinute: 25, endingTimeHour:12, endingTimeMinute: 50, description:"Lunch MF MS", roomNumber:"Commence or Advisory", teacher:""}, + {className: "Activity Period", dayOfWeek:2, startingTimeHour: 12, startingTimeMinute: 50, endingTimeHour:13, endingTimeMinute: 0, description:"Activity Period MF MS", roomNumber:"Outside", teacher:""}, + {className: "Introductory Biology and Epidemiology", dayOfWeek:3, startingTimeHour: 13, startingTimeMinute: 0, endingTimeHour:14, endingTimeMinute: 0, description:"Introductory Biology and Epidemiology 7B 2B Mrs. Brown Block 2B M202", roomNumber:"202", teacher:"Mrs. Brown"}, + {className: "Medieval Western Civilization", dayOfWeek:3, startingTimeHour: 14, startingTimeMinute: 0, endingTimeHour:15, endingTimeMinute: 0, description:"Medieval Western Civilization 7B 1B Mr. Kuhle Block 1B M205", roomNumber:"205", teacher:"Mr. Khule"}, + + //Thursday: + {className: "Algebra 1", dayOfWeek:4, startingTimeHour: 8, startingTimeMinute: 10, endingTimeHour:9, endingTimeMinute: 5, description:"Algebra 1 7B 6B Mr. Benson Block 6B M204", roomNumber:"204", teacher:"Mr. Benson"}, + {className: "Latin", dayOfWeek:4, startingTimeHour: 9, startingTimeMinute: 5, endingTimeHour:10, endingTimeMinute: 0, description:"Latin 7B 5B Mrs. Scrivner Block 5B M208", roomNumber:"208", teacher:"Mrs.Scrivner"}, + {className: "Break", dayOfWeek:4, startingTimeHour: 10, startingTimeMinute: 0, endingTimeHour:10, endingTimeMinute: 10, description:"Break MF MS", roomNumber:"Outside", teacher:""}, + {className: "MS Robotics", dayOfWeek:4, startingTimeHour: 10, startingTimeMinute: 10, endingTimeHour:11, endingTimeMinute: 0, description:"MS Robotics S1A Mr. Broyles MS MF Elective Block A M211", roomNumber:"211", teacher:"Mr. Broyles"}, + {className: "Advisory Besaw", dayOfWeek:4, startingTimeHour: 11, startingTimeMinute: 50, endingTimeHour:12, endingTimeMinute: 25, description:"Advisory Besaw Mr. Besaw Advisory MF MS M207", roomNumber:"207", teacher:"Mr. Besaw"}, + {className: "Lunch", dayOfWeek:4, startingTimeHour: 12, startingTimeMinute: 25, endingTimeHour:12, endingTimeMinute: 50, description:"Lunch MF MS", roomNumber:"Commence or Advisory", teacher:""}, + {className: "Activity Period", dayOfWeek:4, startingTimeHour: 12, startingTimeMinute: 50, endingTimeHour:13, endingTimeMinute: 0, description:"Activity Period MF MS", roomNumber:"Outside", teacher:""}, + {className: "Biblical Theology", dayOfWeek:4, startingTimeHour: 13, startingTimeMinute: 5, endingTimeHour:14, endingTimeMinute: 0, description:"Biblical Theology 7B 3B Mr. Besaw Block 3B M207", roomNumber:"207", teacher:"Mr. Besaw"}, + {className: "English", dayOfWeek:4, startingTimeHour: 14, startingTimeMinute: 0, endingTimeHour:15, endingTimeMinute: 0, description:"English 7B 4B Dr. Wong Block 4B M206", roomNumber:"206", teacher:"Dr. Wong"}, + + //Friday: + {className: "Medieval Western Civilization", dayOfWeek:5, startingTimeHour: 8, startingTimeMinute: 10, endingTimeHour:9, endingTimeMinute: 5, description:"Medieval Western Civilization 7B 1B Mr. Kuhle Block 1B M205", roomNumber:"205", teacher:"Mr. Khule"}, + {className: "Introductory Biology and Epidemiology", dayOfWeek:5, startingTimeHour: 9, startingTimeMinute: 5, endingTimeHour:10, endingTimeMinute: 0, description:"Introductory Biology and Epidemiology 7B 2B Mrs. Brown Block 2B M202", roomNumber:"202", teacher:"Mrs. Brown"}, + {className: "Break", dayOfWeek:5, startingTimeHour: 10, startingTimeMinute: 0, endingTimeHour:10, endingTimeMinute: 10, description:"Break MF MS", roomNumber:"Outside", teacher:""}, + {className: "MS Robotics", dayOfWeek:5, startingTimeHour: 10, startingTimeMinute: 10, endingTimeHour:11, endingTimeMinute: 0, description:"MS Robotics S1A Mr. Broyles MS MF Elective Block A M211", roomNumber:"211", teacher:"Mr. Broyles"}, + {className: "Office Hours Besaw/Nunez", dayOfWeek:5, startingTimeHour: 11, startingTimeMinute: 50, endingTimeHour:12, endingTimeMinute: 25, description:"Office Hours Besaw/Nunez Mr. Besaw, Dr. Nunez, Mrs.McDonough, Mr. Pettit Office Hours MF MS M203", roomNumber:"203", teacher:" Besaw/Nunez"}, + {className: "Lunch", dayOfWeek:5, startingTimeHour: 12, startingTimeMinute: 25, endingTimeHour:12, endingTimeMinute: 50, description:"Lunch MF MS", roomNumber:"Commence or Advisory", teacher:""}, + {className: "Activity Period", dayOfWeek:5, startingTimeHour: 12, startingTimeMinute: 50, endingTimeHour:13, endingTimeMinute: 0, description:"Activity Period MF MS", roomNumber:"Outside", teacher:""}, + {className: "Algebra 1", dayOfWeek:5, startingTimeHour: 13, startingTimeMinute: 5, endingTimeHour:14, endingTimeMinute: 0, description:"Algebra 1 7B 6B Mr. Benson Block 6B M204", roomNumber:"204", teacher:"Mr. Benson"}, + {className: "Logic", dayOfWeek:5, startingTimeHour: 14, startingTimeMinute: 0, endingTimeHour:15, endingTimeMinute: 0, description:"Logic 7B 5B Mrs. Scrivner Block 5B M208", roomNumber:"208", teacher:"Mrs.Scrivner"}, + + //Sataturday: + ]; + + var currentDate = new Date(); + var currentDayOfWeek = currentDate.getDay(); + var currentHour = currentDate.getHours(); + var currentMinute = currentDate.getMinutes(); + var minofDay = (currentHour*60)+currentMinute; + var i; + var currentPositon; + for(i = 0;i= (schedule[i].startingTimeHour*60+schedule[i].startingTimeMinute) && minofDay < (schedule[i].endingTimeHour*60+schedule[i].endingTimeMinute) ){ + console.log("Match:" + schedule[i].className); + console.log("stage:" + stage); + if(stage == 3){ + return schedule[i]; + }else if(stage == 4 && ++currentPositon <= schedule.length){ + return schedule[currentPositon]; + }else if(stage == 5 && (currentPositon+=2) <= schedule.length){ + return schedule[currentPositon]; + }else if(stage == 6 && (currentPositon+=3) <= schedule.length){ + return schedule[currentPositon]; + }else if(stage == 2 && (currentPositon-=1) <= schedule.length){ + return schedule[currentPositon]; + }else if(stage == 1 && (currentPositon-=2) <= schedule.length){ + return schedule[currentPositon]; + } + } + } + } + return null; +} + + +setWatch(() => { + if(stage<=1){ + }else{ + stage -= 1; + drawInfo(); + } +}, BTN1, {repeat:true}); + +setWatch(() => { +}, BTN2, {repeat:true}); + +setWatch(() => { + if(stage>=6){ + }else{ + stage += 1; + drawInfo(); + } +}, BTN3, {repeat:true}); + +setWatch(() => { + +}, BTN4, {repeat:true}); + +setWatch(() => { + +}, BTN5, {repeat:true}); + +drawInfo(); From 3c9e46c2160f4ff58708afc0d08ce2e5d27df097 Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Mon, 4 Oct 2021 15:59:03 -0700 Subject: [PATCH 032/155] Update apps.json --- apps.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps.json b/apps.json index 7e7540581..6fcd930c2 100644 --- a/apps.json +++ b/apps.json @@ -3499,9 +3499,9 @@ {"name":"widclkbttm.wid.js","url":"widclkbttm.wid.js"} ] }, -{ "id": "schoolCalender", +{ "id": "schoolCalendar", "name": "School Calendar", - "shortName":"SCalender", + "shortName":"SCalendar", "icon": "CalenderLogo.png", "version":"0.01", "description": "A simple calendar that you can see your upcoming events. Keep in note that your events reapeat weekly.", @@ -3509,7 +3509,7 @@ "readme": "README.md", "custom":"interface.html", "storage": [ - {"name":"schoolCalender.app.js","url":"app.js"}, + {"name":"schoolCalendar.app.js","url":"app.js"}, {"name":"schoolCalendar.img","url":"app-icon.js","evaluate":true} ], "data": [ From a61879753285b4469772f51474c29fe86a5aa942 Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Mon, 4 Oct 2021 19:55:30 -0700 Subject: [PATCH 033/155] Update schoolCalendar.js --- apps/schoolCalendar/schoolCalendar.js | 354 +++++++++++++------------- 1 file changed, 172 insertions(+), 182 deletions(-) diff --git a/apps/schoolCalendar/schoolCalendar.js b/apps/schoolCalendar/schoolCalendar.js index be4c45d45..a6537aa66 100644 --- a/apps/schoolCalendar/schoolCalendar.js +++ b/apps/schoolCalendar/schoolCalendar.js @@ -1,197 +1,106 @@ -require("FontTeletext5x9Mode7").add(Graphics); -Bangle.setLCDMode(); +require("Font8x12").add(Graphics); -function getBackgroundImage() { - return require("heatshrink").decompress(atob("gMwyEgBAsAgQBCgcAggBCgsAgwBCg8AhABChMAhQBChcAhgBChsAhwBCh8AiEAiIBCiUAiYBCikAioBCi0Ai4BCjEAjIBCjUAjYBCjkAjoBCj0Aj4BBA")); -} - -Graphics.prototype.setFontAudiowide = function() { - // Actual height 33 (36 - 4) +Graphics.prototype.setFontAudiowide = function () { var widths = atob("BxYfDBkYGhkZFRkZCA=="); var font = atob("AAAAAAAAA8AAAAHgAAAB8AAAAHgAAAA4AAAAAAAAAAEAAAABgAAAA8AAAAPgAAAH8AAAB/gAAA/4AAAf+AAAH/AAAD/wAAA/4AAAf8AAAP/AAAD/gAAB/4AAAf8AAAD+AAAAfgAAADwAAAAcAAAAAAAAAAAAAAAAAAAAAAP/AAAH//AAB//8AAf//wAH///AA///4APwD/gB8A/8APgP/gB8D98AfAfvgD4H58AfB/PgD4Px8AfD8PgD4/h8AfH4PgD5+B8AP/wPgB/8B8AP/APgB/4D8AH8B/AA///4AD//+AAP//gAA//4AAB/8AAAAAAAAAAAAAAAAAAD4AAAAfAAAAD4AAAAfAAAAD///8Af///gD///8Af///gD///8AAAAAAAAAAAAAAAAAAAA/8AAAf/gD4H/8AfA//gD4P/8AfB+PgD4Ph8AfB8PgD4Ph8AfB8PgD4Ph8AfB8PgD4Ph8AfB8PgD4Ph8AfB8PgB8Ph8AP/8PgB//B8AP/4PgA/+B8AD/gPgABgA8AAAAAAAAAAAAPA4HgD4Ph8AfB8PgD4Ph8AfB8PgD4Ph8AfB8PgD4Ph8AfB8PgD4Ph8AfB8PgD4Ph8AfB8PgD4Ph8AfB8PgB8Ph8AP///gB///8AH///AA///wAB//8AAAAAAAAAAAAB/8AAAf/4AAD//gAAf/8AAD//gAAf/8AAAAPgAAAB8AAAAPgAAAB8AAAAPgAAAB8AAAAPgAAAB8AAAAPgAAAB8AAAAPgAAP///gD///8Af///gD///8Af///gD///8AAAAAAAAAAAAAAAAAAAAAAAAAAAAD/8B8Af/wPgD//B8Af/4PgD//h8AfB8PgD4Ph8AfB8PgD4Ph8AfB8PgD4Ph8AfB8PgD4Ph8AfB8PgD4Ph8AfB8PgD4Ph8AfB//gD4H/8AfA//AAAD/4AAAP8AAAAAAAAAAAAAH//AAB//8AAf//wAH///AB///8AP58/gB8Ph8APh8PgD4Ph8AfB8PgD4Ph8AfB8PgD4Ph8AfB8PgD4Ph8AfB8PgD4Ph8AfB//gD4H/8AAA//AAAD/4AAAP8AAAAAAAAAAAAD4AAAAfAAAAD4AAAAfAABgD4AA8AfAAPgD4AH8AfAD/gD4A/8AfAf/AD4P/gAfH/wAD5/8AAf/+AAD//AAAf/gAAD/4AAAf8AAAB+AAAAPAAAAAAAAAAAAAAAAAB/gAAAf+AAP//4AH///gA///8AP/+PgB//h8APh8PgD4Ph8AfB8PgD4Ph8AfB8PgD4Ph8AfB8PgB8Ph8AP/8PgB//h8AP///gA///8AD///AADz/4AAAP8AAAAMAAAAAAAAAAAAAB/gAAA/+AAAH/4AAB//B8AP/8PgB8Ph8AfB8PgD4Ph8AfB8PgD4Ph8AfB8PgD4Ph8AfB8PgD4Ph8APh8PgB8Ph8APx8fgB///8AH///AAf//wAD//8AAH//AAAAAAAAAAAAAAAAAAAAAAAAAOAHgAD4A8AAfAPgAD4A8AAOAHAAAAAAA=="); var scale = 1; // size multiplier for this font g.setFontCustom(font, 46, widths, 33+(scale<<8)+(1<<16)); }; -function logDebug(message){ - //console.log(message); -} +function getBackgroundImage() {return require("heatshrink").decompress(atob("gMwyEgBAsAgQBCgcAggBCgsAgwBCg8AhABChMAhQBChcAhgBChsAhwBCh8AiEAiIBCiUAiYBCikAioBCi0Ai4BCjEAjIBCjUAjYBCjkAjoBCj0Aj4BBA"));} + +Bangle.setLCDMode("doublebuffered"); +g.clear(); +g.setFont("Audiowide"); +g.drawString("...",115,60); +g.flip(); + +LIST = 1; +INFORMATION = 2; +currentStage = LIST; -var NEXTCLASS = 4; -var CURRRENTCLASS = 3; -var NEXTNEXTCLASS = 5; -var BEHINDCLASS = 2; -var BEHINDBEHINDCLASS = 1; -var NEXTNEXTNEXTCLASS = 6; var stage = 3; - -function drawInfo(){ - var currentDate = new Date(); - var currentDayOfWeek = currentDate.getDay(); - var currentHour = currentDate.getHours(); - var currentMinute = currentDate.getMinutes(); - var currentMinuteUpdated; - var currentHourUpdated; - if (currentMinute<10){ - currentMinuteUpdated = "0"+currentMinute; - }else{ - currentMinuteUpdated = currentMinute; - }if(currentHour >= 13){ - currentHourUpdated = currentHour-12; - }else{ - currentHourUpdated = currentHour; - } - for(var i = 0;i<=240;i++){ - g.drawImage(getBackgroundImage(),i,120,{scale:5,rotate:0}); - } - g.setColor(255,255,255); - g.setFont("Audiowide"); - g.drawString(currentHourUpdated+":"+currentMinuteUpdated, 145, 16); - g.setFont("Teletext5x9Mode7", 2); - foundClass = processDay(); - if (foundClass.startingTimeMinute<10){ - classMinuteUpdated = "0"+foundClass.startingTimeMinute; - }else{ - classMinuteUpdated = foundClass.startingTimeMinute; - } - if (foundClass.endingTimeMinute<10){ - classEndingMinuteUpdated = "0"+foundClass.endingTimeMinute; - }else{ - classEndingMinuteUpdated = foundClass.endingTimeMinute; - }if(foundClass.startingTimeHour >= 13){ - classHourUpdated = foundClass.startingTimeHour-12; - }else{ - classHourUpdated = foundClass.startingTimeHour; - }if(foundClass.endingTimeHour >= 13){ - classEndingHourUpdated = foundClass.endingTimeHour-12; - }else{ - classEndingHourUpdated = foundClass.endingTimeHour; - } - switch (foundClass.dayOfWeek) { - case 0: - updatedDay = "Sun"; - break; - case 1: - updatedDay = "Mon"; - break; - case 2: - updatedDay = "Tue"; - break; - case 3: - updatedDay = "Wed"; - break; - case 4: - updatedDay = "Thur"; - break; - case 5: - updatedDay = "Fri"; - break; - case 6: - updatedDay = "Sat"; -} - if (foundClass != null) { - g.drawString(classHourUpdated+":"+classMinuteUpdated+" - "+classEndingHourUpdated+":"+classEndingMinuteUpdated+" "+updatedDay, 25, 50); - g.drawString(foundClass.className, 25, 80); - g.drawString(foundClass.teacher, 25, 110); - g.drawString(foundClass.roomNumber, 25, 140); - } -} -setInterval(drawInfo, 60000); - -function processDay(){ +function getScheduleTable() { let schedule = [ //Sunday //Monday: - {className: "Biblical Theology", dayOfWeek:1, startingTimeHour: 8, startingTimeMinute: 10, endingTimeHour:9, endingTimeMinute: 5, description:"Biblical Theology 7B 3B Mr. Besaw Block 3B M207", roomNumber:"207", teacher:"Mr. Besaw"}, - {className: "English", dayOfWeek:1, startingTimeHour: 9, startingTimeMinute: 5, endingTimeHour:10, endingTimeMinute: 0, description:"English 7B 4B Dr. Wong Block 4B M206", teacher:"Dr. Wong"}, - {className: "Break", dayOfWeek:1, startingTimeHour: 10, startingTimeMinute: 0, endingTimeHour:10, endingTimeMinute: 10, description:"Break MF MS", teacher:""}, - {className: "MS Robotics", dayOfWeek:1, startingTimeHour: 10, startingTimeMinute: 10, endingTimeHour:11, endingTimeMinute: 0, description:"MS Robotics S1A Mr. Broyles MS MF Elective Block A M211", roomNumber:"211", teacher:"Mr. Broyles"}, - {className: "MS Physical Education Boys", dayOfWeek:1, startingTimeHour: 11, startingTimeMinute: 0, endingTimeHour:11, endingTimeMinute: 50, description:"MS Physical Education Boys S1B Mr. Mendezona MS MF Elective Block B Gym", roomNumber:"GYM", teacher:"Mr. Mendezona"}, - {className: "Office Hours Besaw/Nunez", dayOfWeek:1, startingTimeHour: 11, startingTimeMinute: 50, endingTimeHour:12, endingTimeMinute: 25, description:"Office Hours Besaw/Nunez Mr. Besaw, Dr. Nunez, Mrs.McDonough, Mr. Pettit Office Hours MF MS M203", roomNumber:"203", teacher:"Besaw/Nunez"}, - {className: "Lunch", dayOfWeek:1, startingTimeHour: 12, startingTimeMinute: 25, endingTimeHour:12, endingTimeMinute: 50, description:"Lunch MF MS", roomNumber:"Commence or Advisory", teacher:""}, - {className: "Activity Period", dayOfWeek:1, startingTimeHour: 12, startingTimeMinute: 50, endingTimeHour:13, endingTimeMinute: 0, description:"Activity Period MF MS", roomNumber:"Outside", teacher:""}, - {className: "Latin", dayOfWeek:1, startingTimeHour: 13, startingTimeMinute: 5, endingTimeHour:14, endingTimeMinute: 0, description:"Latin 7B 5B Mrs. Scrivner Block 5B M208", roomNumber:"208", teacher:"Mrs.Scrivner"}, - {className: "Algebra 1", dayOfWeek:1, startingTimeHour: 14, startingTimeMinute: 0, endingTimeHour:15, endingTimeMinute: 0, description:"Algebra 1 7B 6B Mr. Benson Block 6B M204", roomNumber:"204", teacher:"Mr. Benson"}, + {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: - {className: "Logic", dayOfWeek:2, startingTimeHour: 8, startingTimeMinute: 10, endingTimeHour:9, endingTimeMinute: 0, description:"Logic 7B 5B Mrs. Scrivner Block 5B M208", roomNumber:"208", teacher:"Mrs.Scrivner"}, - {className: "Algebra 1", dayOfWeek:2, startingTimeHour: 9, startingTimeMinute: 0, endingTimeHour:10, endingTimeMinute: 0, description:"Algebra 1 7B 6B Mr. Benson Block 6B M204", roomNumber:"204", teacher:"Mr. Benson"}, - {className: "Chapel", dayOfWeek:2, startingTimeHour: 10, startingTimeMinute: 0, endingTimeHour:10, endingTimeMinute: 25, description:"Chapel MF MS", roomNumber:"Advisory", teacher:""}, - {className: "Break", dayOfWeek:2, startingTimeHour: 10, startingTimeMinute: 25, endingTimeHour:10, endingTimeMinute: 35, description:"Break MF MS", roomNumber:"Outside", teacher:""}, - {className: "Advisory Besaw", dayOfWeek:2, startingTimeHour: 10, startingTimeMinute: 35, endingTimeHour:11, endingTimeMinute: 0, description:"Advisory Besaw Mr. Besaw Advisory MF MS M207", roomNumber:"207", teacher:"Mr. Besaw"}, - {className: "MS Robotics", dayOfWeek:2, startingTimeHour: 11, startingTimeMinute: 0, endingTimeHour:11, endingTimeMinute: 50, description:"MS Robotics S1A Mr. Broyles MS MF Elective Block A M211", roomNumber:"211", teacher:"Mr. Broyles"}, - {className: "Office Hours Besaw/Nunez", dayOfWeek:2, startingTimeHour: 11, startingTimeMinute: 50, endingTimeHour:12, endingTimeMinute: 25, description:"Office Hours Besaw/Nunez Mr. Besaw, Dr. Nunez, Mrs.McDonough, Mr. Pettit Office Hours MF MS M203", roomNumber:"203", teacher:" Besaw/Nunez"}, - {className: "Lunch", dayOfWeek:2, startingTimeHour: 12, startingTimeMinute: 25, endingTimeHour:12, endingTimeMinute: 50, description:"Lunch MF MS", roomNumber:"Commence or Advisory", teacher:""}, - {className: "Activity Period", dayOfWeek:2, startingTimeHour: 12, startingTimeMinute: 50, endingTimeHour:13, endingTimeMinute: 5, description:"Activity Period MF MS", roomNumber:"Outside", teacher:""}, - {className: "Medieval Western Civilization", dayOfWeek:2, startingTimeHour: 13, startingTimeMinute: 5, endingTimeHour:14, endingTimeMinute: 0, description:"Medieval Western Civilization 7B 1B Mr. Kuhle Block 1BM205", roomNumber:"205", teacher:"Mr. Khule"}, - {className: "Introductory Biology and Epidemiology", dayOfWeek:2, startingTimeHour: 14, startingTimeMinute: 0, endingTimeHour:15, endingTimeMinute: 0, description:"Introductory Biology and Epidemiology 7B 2B Mrs. Brown Block 2B M202", roomNumber:"202", teacher:"Mrs. Brown"}, + {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: - {className: "English", dayOfWeek:3, startingTimeHour: 9, startingTimeMinute: 0, endingTimeHour:9, endingTimeMinute: 55, description:"English 7B 4B Dr. Wong Block 4B M206", roomNumber:"206", teacher:"Dr. Wong"}, - {className: "Biblical Theology", dayOfWeek:3, startingTimeHour: 9, startingTimeMinute: 55, endingTimeHour:10, endingTimeMinute: 50, description:"Biblical Theology 7B 3B Mr. Besaw Block 3B M207", roomNumber:"207", teacher:"Mr. Besaw"}, - {className: "Break", dayOfWeek:3, startingTimeHour: 10, startingTimeMinute: 50, endingTimeHour:11, endingTimeMinute: 0, description:"Break MF MS", roomNumber:"Outside", teacher:""}, - {className: "MS Physical Education Boys", dayOfWeek:3, startingTimeHour: 11, startingTimeMinute: 0, endingTimeHour:11, endingTimeMinute: 50, description:"MS Physical Education Boys S1B Mr. Mendezona MS MF Elective Block B Gym", roomNumber:"GYM", teacher:"Mr. Mendezona"}, - {className: "Office Hours Besaw/Nunez", dayOfWeek:3, startingTimeHour: 11, startingTimeMinute: 50, endingTimeHour:12, endingTimeMinute: 25, description:"Office Hours Besaw/Nunez Mr. Besaw, Dr. Nunez, Mrs.McDonough, Mr. Pettit Office Hours MF MS M203", roomNumber:"203", teacher:" Besaw/Nunez"}, - {className: "Lunch", dayOfWeek:3, startingTimeHour: 12, startingTimeMinute: 25, endingTimeHour:12, endingTimeMinute: 50, description:"Lunch MF MS", roomNumber:"Commence or Advisory", teacher:""}, - {className: "Activity Period", dayOfWeek:2, startingTimeHour: 12, startingTimeMinute: 50, endingTimeHour:13, endingTimeMinute: 0, description:"Activity Period MF MS", roomNumber:"Outside", teacher:""}, - {className: "Introductory Biology and Epidemiology", dayOfWeek:3, startingTimeHour: 13, startingTimeMinute: 0, endingTimeHour:14, endingTimeMinute: 0, description:"Introductory Biology and Epidemiology 7B 2B Mrs. Brown Block 2B M202", roomNumber:"202", teacher:"Mrs. Brown"}, - {className: "Medieval Western Civilization", dayOfWeek:3, startingTimeHour: 14, startingTimeMinute: 0, endingTimeHour:15, endingTimeMinute: 0, description:"Medieval Western Civilization 7B 1B Mr. Kuhle Block 1B M205", roomNumber:"205", teacher:"Mr. Khule"}, + {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: - {className: "Algebra 1", dayOfWeek:4, startingTimeHour: 8, startingTimeMinute: 10, endingTimeHour:9, endingTimeMinute: 5, description:"Algebra 1 7B 6B Mr. Benson Block 6B M204", roomNumber:"204", teacher:"Mr. Benson"}, - {className: "Latin", dayOfWeek:4, startingTimeHour: 9, startingTimeMinute: 5, endingTimeHour:10, endingTimeMinute: 0, description:"Latin 7B 5B Mrs. Scrivner Block 5B M208", roomNumber:"208", teacher:"Mrs.Scrivner"}, - {className: "Break", dayOfWeek:4, startingTimeHour: 10, startingTimeMinute: 0, endingTimeHour:10, endingTimeMinute: 10, description:"Break MF MS", roomNumber:"Outside", teacher:""}, - {className: "MS Robotics", dayOfWeek:4, startingTimeHour: 10, startingTimeMinute: 10, endingTimeHour:11, endingTimeMinute: 0, description:"MS Robotics S1A Mr. Broyles MS MF Elective Block A M211", roomNumber:"211", teacher:"Mr. Broyles"}, - {className: "Advisory Besaw", dayOfWeek:4, startingTimeHour: 11, startingTimeMinute: 50, endingTimeHour:12, endingTimeMinute: 25, description:"Advisory Besaw Mr. Besaw Advisory MF MS M207", roomNumber:"207", teacher:"Mr. Besaw"}, - {className: "Lunch", dayOfWeek:4, startingTimeHour: 12, startingTimeMinute: 25, endingTimeHour:12, endingTimeMinute: 50, description:"Lunch MF MS", roomNumber:"Commence or Advisory", teacher:""}, - {className: "Activity Period", dayOfWeek:4, startingTimeHour: 12, startingTimeMinute: 50, endingTimeHour:13, endingTimeMinute: 0, description:"Activity Period MF MS", roomNumber:"Outside", teacher:""}, - {className: "Biblical Theology", dayOfWeek:4, startingTimeHour: 13, startingTimeMinute: 5, endingTimeHour:14, endingTimeMinute: 0, description:"Biblical Theology 7B 3B Mr. Besaw Block 3B M207", roomNumber:"207", teacher:"Mr. Besaw"}, - {className: "English", dayOfWeek:4, startingTimeHour: 14, startingTimeMinute: 0, endingTimeHour:15, endingTimeMinute: 0, description:"English 7B 4B Dr. Wong Block 4B M206", roomNumber:"206", teacher:"Dr. Wong"}, + {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: - {className: "Medieval Western Civilization", dayOfWeek:5, startingTimeHour: 8, startingTimeMinute: 10, endingTimeHour:9, endingTimeMinute: 5, description:"Medieval Western Civilization 7B 1B Mr. Kuhle Block 1B M205", roomNumber:"205", teacher:"Mr. Khule"}, - {className: "Introductory Biology and Epidemiology", dayOfWeek:5, startingTimeHour: 9, startingTimeMinute: 5, endingTimeHour:10, endingTimeMinute: 0, description:"Introductory Biology and Epidemiology 7B 2B Mrs. Brown Block 2B M202", roomNumber:"202", teacher:"Mrs. Brown"}, - {className: "Break", dayOfWeek:5, startingTimeHour: 10, startingTimeMinute: 0, endingTimeHour:10, endingTimeMinute: 10, description:"Break MF MS", roomNumber:"Outside", teacher:""}, - {className: "MS Robotics", dayOfWeek:5, startingTimeHour: 10, startingTimeMinute: 10, endingTimeHour:11, endingTimeMinute: 0, description:"MS Robotics S1A Mr. Broyles MS MF Elective Block A M211", roomNumber:"211", teacher:"Mr. Broyles"}, - {className: "Office Hours Besaw/Nunez", dayOfWeek:5, startingTimeHour: 11, startingTimeMinute: 50, endingTimeHour:12, endingTimeMinute: 25, description:"Office Hours Besaw/Nunez Mr. Besaw, Dr. Nunez, Mrs.McDonough, Mr. Pettit Office Hours MF MS M203", roomNumber:"203", teacher:" Besaw/Nunez"}, - {className: "Lunch", dayOfWeek:5, startingTimeHour: 12, startingTimeMinute: 25, endingTimeHour:12, endingTimeMinute: 50, description:"Lunch MF MS", roomNumber:"Commence or Advisory", teacher:""}, - {className: "Activity Period", dayOfWeek:5, startingTimeHour: 12, startingTimeMinute: 50, endingTimeHour:13, endingTimeMinute: 0, description:"Activity Period MF MS", roomNumber:"Outside", teacher:""}, - {className: "Algebra 1", dayOfWeek:5, startingTimeHour: 13, startingTimeMinute: 5, endingTimeHour:14, endingTimeMinute: 0, description:"Algebra 1 7B 6B Mr. Benson Block 6B M204", roomNumber:"204", teacher:"Mr. Benson"}, - {className: "Logic", dayOfWeek:5, startingTimeHour: 14, startingTimeMinute: 0, endingTimeHour:15, endingTimeMinute: 0, description:"Logic 7B 5B Mrs. Scrivner Block 5B M208", roomNumber:"208", teacher:"Mrs.Scrivner"}, - + {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: ]; + return schedule; +} +function processDay() { + var schedule = getScheduleTable(); var currentDate = new Date(); - var currentDayOfWeek = currentDate.getDay(); - var currentHour = currentDate.getHours(); - var currentMinute = currentDate.getMinutes(); + var currentDayOfWeek = 2;//currentDate.getDay(); + var currentHour = 9;//currentDate.getHours(); + var currentMinute = 30;//currentDate.getMinutes(); var minofDay = (currentHour*60)+currentMinute; var i; var currentPositon; for(i = 0;i= (schedule[i].startingTimeHour*60+schedule[i].startingTimeMinute) && minofDay < (schedule[i].endingTimeHour*60+schedule[i].endingTimeMinute) ){ - console.log("Match:" + schedule[i].className); - console.log("stage:" + stage); - if(stage == 3){ - return schedule[i]; - }else if(stage == 4 && ++currentPositon <= schedule.length){ - return schedule[currentPositon]; - }else if(stage == 5 && (currentPositon+=2) <= schedule.length){ - return schedule[currentPositon]; - }else if(stage == 6 && (currentPositon+=3) <= schedule.length){ - return schedule[currentPositon]; - }else if(stage == 2 && (currentPositon-=1) <= schedule.length){ - return schedule[currentPositon]; - }else if(stage == 1 && (currentPositon-=2) <= schedule.length){ - return schedule[currentPositon]; - } + if(schedule[i].dow == currentDayOfWeek){ + if(minofDay >= (schedule[i].sh*60+schedule[i].sm) && minofDay < (schedule[i].eh*60+schedule[i].em) ){ + return currentPositon; } } } @@ -199,31 +108,112 @@ function processDay(){ } -setWatch(() => { - if(stage<=1){ +var currentPositionTable = 0; +var numberOfItemsShown = 5; + +function logDebug(message) {console.log(message);} + +function updateMinutesToCurrentTime(currentMinuteFunction) { + if (currentMinuteFunction<10){ + currentMinuteUpdatedFunction = "0"+currentMinuteFunction; }else{ - stage -= 1; - drawInfo(); + currentMinuteUpdatedFunction = currentMinuteFunction; } -}, BTN1, {repeat:true}); + return currentMinuteUpdatedFunction; +} -setWatch(() => { -}, BTN2, {repeat:true}); - -setWatch(() => { - if(stage>=6){ +function updateHoursToCurrentTime(currentHourFunction) { + if(currentHourFunction >= 13){ + currentHourUpdatedFunction = currentHourFunction-12; }else{ - stage += 1; - drawInfo(); + currentHourUpdatedFunction = currentHourFunction; } -}, BTN3, {repeat:true}); + return currentHourUpdatedFunction; +} -setWatch(() => { -}, BTN4, {repeat:true}); -setWatch(() => { +function RedRectDown() { + if(currentPositionTable > 0){ + currentPositionTable -= 1; + displayClock(); + } +} -}, BTN5, {repeat:true}); +function RedRectUp() { + if(currentPositionTable < numberOfItemsShown){ + currentPositionTable += 1; + displayClock(); + } +} -drawInfo(); +function changeScene(){ + if(currentStage == INFORMATION){ + currentStage = LIST; + }else if(currentStage == LIST){ + currentStage = INFORMATION; + } +displayClock(); +} + +function displayClock() { + var currentDate = new Date(); + var currentDayOfWeek = currentDate.getDay(); + var currentHour = currentDate.getHours(); + var currentMinute = currentDate.getMinutes(); + var currentMinuteUpdated; + var currentHourUpdated; + currentMinuteUpdated = updateMinutesToCurrentTime(currentMinute); + currentHourUpdated = updateHoursToCurrentTime(currentHour); + g.setColor(255,255,255); + g.setFont("Audiowide"); + g.clear(); + var foundNumber = processDay(); + var foundSchedule = getScheduleTable(); + var scheduleHourUpdated; + var scheduleMinuteUpdated; + for(var i = 0;i<=240;i++){ + g.drawImage(getBackgroundImage(),i,120,{scale:5,rotate:0}); + } + g.drawString(currentHourUpdated+":"+currentMinuteUpdated, 150, 0); + if(currentStage == LIST){ + for(var x = 0;x<=numberOfItemsShown;x++){ + scheduleMinuteUpdatedStart = updateMinutesToCurrentTime(foundSchedule[((foundNumber-2)+x)].sm); + scheduleHourUpdatedStart = updateHoursToCurrentTime(foundSchedule[((foundNumber-2)+x)].sh); + scheduleMinuteUpdatedEnd = updateMinutesToCurrentTime(foundSchedule[((foundNumber-2)+x)].em); + scheduleHourUpdatedEnd = updateHoursToCurrentTime(foundSchedule[((foundNumber-2)+x)].eh); + g.setColor(255,255,255); + g.drawRect(10,30+(x*20),230,50+(20*x)); + g.reset(); + g.setFont("8x12"); + g.drawString(scheduleHourUpdatedStart+":"+scheduleMinuteUpdatedStart+"-"+scheduleHourUpdatedEnd+":"+scheduleMinuteUpdatedEnd+" "+foundSchedule[((foundNumber-2)+x)].cn,13,35+(x*20)); + g.setColor(255,0,0); + g.drawRect(10,30+(currentPositionTable*20),230,50+(20*currentPositionTable)); + } + }else if(currentStage == INFORMATION){ + scheduleMinuteUpdatedStart = updateMinutesToCurrentTime(foundSchedule[((foundNumber-2)+currentPositionTable)].sm); + scheduleHourUpdatedStart = updateHoursToCurrentTime(foundSchedule[((foundNumber-2)+currentPositionTable)].sh); + scheduleMinuteUpdatedEnd = updateMinutesToCurrentTime(foundSchedule[((foundNumber-2)+currentPositionTable)].em); + scheduleHourUpdatedEnd = updateHoursToCurrentTime(foundSchedule[((foundNumber-2)+currentPositionTable)].eh); + g.setColor(255,255,255); + g.reset(); + g.setFont("8x12"); + g.drawString(foundSchedule[((foundNumber-2)+currentPositionTable)].cn,13,30); + } + g.flip(); +} + +var currentMinuteUpdatedFunction = "00"; +var currentHourUpdatedFunction = 11; +var scheduleMinuteUpdatedStart = 35; +var scheduleHourUpdatedStart = 10; +var scheduleMinuteUpdatedEnd = currentMinuteUpdatedFunction; +var scheduleHourUpdatedEnd = 11; + +setWatch(RedRectUp, D23, { repeat:true, edge:'rising', debounce : 50 }); +setWatch(RedRectDown, D24, { repeat:true, edge:'rising', debounce : 50 }); +setWatch(changeScene, BTN2, { repeat:true, edge:'rising', debounce : 50 }); + +displayClock(); + +setInterval(displayClock, 5000); From 7107d510a5301f50ca5dc1bf9d4463ad99a33af8 Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Mon, 4 Oct 2021 20:06:28 -0700 Subject: [PATCH 034/155] Update schoolCalendar.js --- apps/schoolCalendar/schoolCalendar.js | 54 ++++++++++++++++++++++++++- 1 file changed, 52 insertions(+), 2 deletions(-) diff --git a/apps/schoolCalendar/schoolCalendar.js b/apps/schoolCalendar/schoolCalendar.js index a6537aa66..36f9110a7 100644 --- a/apps/schoolCalendar/schoolCalendar.js +++ b/apps/schoolCalendar/schoolCalendar.js @@ -1,4 +1,5 @@ require("Font8x12").add(Graphics); +require("Font8x16").add(Graphics); Graphics.prototype.setFontAudiowide = function () { var widths = atob("BxYfDBkYGhkZFRkZCA=="); @@ -131,7 +132,55 @@ function updateHoursToCurrentTime(currentHourFunction) { return currentHourUpdatedFunction; } - +function updateDay(ffunction,day){ + if(ffunction == 1){ + switch (day) { + case 0: + return "Sunday"; + case 1: + day = "Monday"; + break; + case 2: + day = "Tuesday"; + break; + case 3: + day = "Wednesday"; + break; + case 4: + day = "Thursday"; + break; + case 5: + day = "Friday"; + break; + case 6: + day = "Saturday"; + return day; +} + }else if(ffunction == 2){ + switch (day) { + case 0: + return "Sun"; + case 1: + day = "Mon"; + break; + case 2: + day = "Tue"; + break; + case 3: + day = "Wed"; + break; + case 4: + day = "Thu"; + break; + case 5: + day = "Fri"; + break; + case 6: + day = "Sat"; + return day; + } + } +} function RedRectDown() { if(currentPositionTable > 0){ @@ -197,8 +246,9 @@ function displayClock() { scheduleHourUpdatedEnd = updateHoursToCurrentTime(foundSchedule[((foundNumber-2)+currentPositionTable)].eh); g.setColor(255,255,255); g.reset(); - g.setFont("8x12"); + g.setFont("8x16"); g.drawString(foundSchedule[((foundNumber-2)+currentPositionTable)].cn,13,30); + g.drawString(scheduleHourUpdatedStart+":"+scheduleMinuteUpdatedStart+"-"+scheduleHourUpdatedEnd+":"+scheduleMinuteUpdatedEnd,13,50); } g.flip(); } From e799550544e55ef94ef48f905283625e50f73e97 Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Thu, 7 Oct 2021 17:02:57 -0700 Subject: [PATCH 035/155] Update schoolCalendar.js --- apps/schoolCalendar/schoolCalendar.js | 95 +++++++++++++++------------ 1 file changed, 53 insertions(+), 42 deletions(-) diff --git a/apps/schoolCalendar/schoolCalendar.js b/apps/schoolCalendar/schoolCalendar.js index 36f9110a7..f1901b7d1 100644 --- a/apps/schoolCalendar/schoolCalendar.js +++ b/apps/schoolCalendar/schoolCalendar.js @@ -1,19 +1,23 @@ require("Font8x12").add(Graphics); require("Font8x16").add(Graphics); - -Graphics.prototype.setFontAudiowide = function () { - var widths = atob("BxYfDBkYGhkZFRkZCA=="); - var font = atob("AAAAAAAAA8AAAAHgAAAB8AAAAHgAAAA4AAAAAAAAAAEAAAABgAAAA8AAAAPgAAAH8AAAB/gAAA/4AAAf+AAAH/AAAD/wAAA/4AAAf8AAAP/AAAD/gAAB/4AAAf8AAAD+AAAAfgAAADwAAAAcAAAAAAAAAAAAAAAAAAAAAAP/AAAH//AAB//8AAf//wAH///AA///4APwD/gB8A/8APgP/gB8D98AfAfvgD4H58AfB/PgD4Px8AfD8PgD4/h8AfH4PgD5+B8AP/wPgB/8B8AP/APgB/4D8AH8B/AA///4AD//+AAP//gAA//4AAB/8AAAAAAAAAAAAAAAAAAD4AAAAfAAAAD4AAAAfAAAAD///8Af///gD///8Af///gD///8AAAAAAAAAAAAAAAAAAAA/8AAAf/gD4H/8AfA//gD4P/8AfB+PgD4Ph8AfB8PgD4Ph8AfB8PgD4Ph8AfB8PgD4Ph8AfB8PgD4Ph8AfB8PgB8Ph8AP/8PgB//B8AP/4PgA/+B8AD/gPgABgA8AAAAAAAAAAAAPA4HgD4Ph8AfB8PgD4Ph8AfB8PgD4Ph8AfB8PgD4Ph8AfB8PgD4Ph8AfB8PgD4Ph8AfB8PgD4Ph8AfB8PgB8Ph8AP///gB///8AH///AA///wAB//8AAAAAAAAAAAAB/8AAAf/4AAD//gAAf/8AAD//gAAf/8AAAAPgAAAB8AAAAPgAAAB8AAAAPgAAAB8AAAAPgAAAB8AAAAPgAAAB8AAAAPgAAP///gD///8Af///gD///8Af///gD///8AAAAAAAAAAAAAAAAAAAAAAAAAAAAD/8B8Af/wPgD//B8Af/4PgD//h8AfB8PgD4Ph8AfB8PgD4Ph8AfB8PgD4Ph8AfB8PgD4Ph8AfB8PgD4Ph8AfB8PgD4Ph8AfB//gD4H/8AfA//AAAD/4AAAP8AAAAAAAAAAAAAH//AAB//8AAf//wAH///AB///8AP58/gB8Ph8APh8PgD4Ph8AfB8PgD4Ph8AfB8PgD4Ph8AfB8PgD4Ph8AfB8PgD4Ph8AfB//gD4H/8AAA//AAAD/4AAAP8AAAAAAAAAAAAD4AAAAfAAAAD4AAAAfAABgD4AA8AfAAPgD4AH8AfAD/gD4A/8AfAf/AD4P/gAfH/wAD5/8AAf/+AAD//AAAf/gAAD/4AAAf8AAAB+AAAAPAAAAAAAAAAAAAAAAAB/gAAAf+AAP//4AH///gA///8AP/+PgB//h8APh8PgD4Ph8AfB8PgD4Ph8AfB8PgD4Ph8AfB8PgB8Ph8AP/8PgB//h8AP///gA///8AD///AADz/4AAAP8AAAAMAAAAAAAAAAAAAB/gAAA/+AAAH/4AAB//B8AP/8PgB8Ph8AfB8PgD4Ph8AfB8PgD4Ph8AfB8PgD4Ph8AfB8PgD4Ph8APh8PgB8Ph8APx8fgB///8AH///AAf//wAD//8AAH//AAAAAAAAAAAAAAAAAAAAAAAAAOAHgAD4A8AAfAPgAD4A8AAOAHAAAAAAA=="); - var scale = 1; // size multiplier for this font - g.setFontCustom(font, 46, widths, 33+(scale<<8)+(1<<16)); -}; +require("Font6x8").add(Graphics); +require("Font7x11Numeric7Seg").add(Graphics); +require("FontHaxorNarrow7x17").add(Graphics); function getBackgroundImage() {return require("heatshrink").decompress(atob("gMwyEgBAsAgQBCgcAggBCgsAgwBCg8AhABChMAhQBChcAhgBChsAhwBCh8AiEAiIBCiUAiYBCikAioBCi0Ai4BCjEAjIBCjUAjYBCjkAjoBCj0Aj4BBA"));} +function getUpArrow() {return require("heatshrink").decompress(atob("hkOyANKmv9AIIjRCoYZRlvdAI8U3YVK3oBJC4Mc7YVRC4sc7gVCzoBNC4oZDGowXGR58lvoBFC9FcAIoXongBFC58dngBFC6EcAIoPHA"));} + +function getDownArrow() {return require("heatshrink").decompress(atob("hkOyALImv9AIojPmvdAIoXPlvdAIoXQ3oBFC9GdAIoXnkt9AIoPPAI8U3cc7cc7gBBDIVcAJYXFGYwXOLpU8AI4XBO5sdjgBFR54ZFBpIA=="));} + +function getMenuIcon() {return require("heatshrink").decompress(atob("iEQyBC/AEU+rwBEn02js17st3stvklrkljkc/cc3cUzYBBD5AdUD4oA/P/4A/P/4A/ADoA=="));} + +function getDotIcon() {return require("heatshrink").decompress(atob("iEQyBC/AA0t3oBBA4ndAIIPGA4gAFkt9lt9AYIHEzoBBBIwRED41cks8AYIJGA44RGP8xtGP44RJBYh1CAIIHHBJJ/KroBBPoqBFB4YRDAA8dngHHBJKdq3oBDBI4RNP4l9AIYHHBJJBJks8AIIHTAH4ABA="));} + Bangle.setLCDMode("doublebuffered"); g.clear(); -g.setFont("Audiowide"); -g.drawString("...",115,60); +g.setFont("HaxorNarrow7x17"); +g.drawString("Loading...",115,60); g.flip(); LIST = 1; @@ -91,9 +95,9 @@ function getScheduleTable() { function processDay() { var schedule = getScheduleTable(); var currentDate = new Date(); - var currentDayOfWeek = 2;//currentDate.getDay(); - var currentHour = 9;//currentDate.getHours(); - var currentMinute = 30;//currentDate.getMinutes(); + var currentDayOfWeek = currentDate.getDay(); + var currentHour = currentDate.getHours(); + var currentMinute = currentDate.getMinutes(); var minofDay = (currentHour*60)+currentMinute; var i; var currentPositon; @@ -134,7 +138,7 @@ function updateHoursToCurrentTime(currentHourFunction) { function updateDay(ffunction,day){ if(ffunction == 1){ - switch (day) { + switch (day) { case 0: return "Sunday"; case 1: @@ -154,31 +158,31 @@ function updateDay(ffunction,day){ break; case 6: day = "Saturday"; - return day; -} + } + return day; }else if(ffunction == 2){ switch (day) { - case 0: - return "Sun"; - case 1: - day = "Mon"; - break; - case 2: - day = "Tue"; - break; - case 3: - day = "Wed"; - break; - case 4: - day = "Thu"; - break; - case 5: - day = "Fri"; - break; - case 6: - day = "Sat"; - return day; + case 0: + return "Sun"; + case 1: + day = "Mon"; + break; + case 2: + day = "Tue"; + break; + case 3: + day = "Wed"; + break; + case 4: + day = "Thu"; + break; + case 5: + day = "Fri"; + break; + case 6: + day = "Sat"; } + return day; } } @@ -215,7 +219,7 @@ function displayClock() { currentMinuteUpdated = updateMinutesToCurrentTime(currentMinute); currentHourUpdated = updateHoursToCurrentTime(currentHour); g.setColor(255,255,255); - g.setFont("Audiowide"); + g.setFont("7x11Numeric7Seg",2); g.clear(); var foundNumber = processDay(); var foundSchedule = getScheduleTable(); @@ -224,31 +228,38 @@ function displayClock() { for(var i = 0;i<=240;i++){ g.drawImage(getBackgroundImage(),i,120,{scale:5,rotate:0}); } - g.drawString(currentHourUpdated+":"+currentMinuteUpdated, 150, 0); + g.drawString(currentHourUpdated+":"+currentMinuteUpdated, 160, 0); + + g.drawImage(getUpArrow(),225,5); + g.drawImage(getDownArrow(),225,140); if(currentStage == LIST){ for(var x = 0;x<=numberOfItemsShown;x++){ + g.drawImage(getDotIcon(),223.5,66); scheduleMinuteUpdatedStart = updateMinutesToCurrentTime(foundSchedule[((foundNumber-2)+x)].sm); scheduleHourUpdatedStart = updateHoursToCurrentTime(foundSchedule[((foundNumber-2)+x)].sh); scheduleMinuteUpdatedEnd = updateMinutesToCurrentTime(foundSchedule[((foundNumber-2)+x)].em); scheduleHourUpdatedEnd = updateHoursToCurrentTime(foundSchedule[((foundNumber-2)+x)].eh); + schduleDay = updateDay(2,foundSchedule[((foundNumber-2)+x)].dow); g.setColor(255,255,255); - g.drawRect(10,30+(x*20),230,50+(20*x)); + g.drawRect(10,30+(x*20),220,50+(20*x)); g.reset(); g.setFont("8x12"); - g.drawString(scheduleHourUpdatedStart+":"+scheduleMinuteUpdatedStart+"-"+scheduleHourUpdatedEnd+":"+scheduleMinuteUpdatedEnd+" "+foundSchedule[((foundNumber-2)+x)].cn,13,35+(x*20)); + g.drawString(scheduleHourUpdatedStart+":"+scheduleMinuteUpdatedStart+"-"+scheduleHourUpdatedEnd+":"+scheduleMinuteUpdatedEnd+" "+schduleDay+" "+foundSchedule[((foundNumber-2)+x)].cn,13,35+(x*20)); g.setColor(255,0,0); - g.drawRect(10,30+(currentPositionTable*20),230,50+(20*currentPositionTable)); + g.drawRect(10,30+(currentPositionTable*20),220,50+(20*currentPositionTable)); } }else if(currentStage == INFORMATION){ + g.drawImage(getMenuIcon(),223.5,66); scheduleMinuteUpdatedStart = updateMinutesToCurrentTime(foundSchedule[((foundNumber-2)+currentPositionTable)].sm); scheduleHourUpdatedStart = updateHoursToCurrentTime(foundSchedule[((foundNumber-2)+currentPositionTable)].sh); scheduleMinuteUpdatedEnd = updateMinutesToCurrentTime(foundSchedule[((foundNumber-2)+currentPositionTable)].em); scheduleHourUpdatedEnd = updateHoursToCurrentTime(foundSchedule[((foundNumber-2)+currentPositionTable)].eh); + schduleDay = updateDay(1,foundSchedule[((foundNumber-2)+currentPositionTable)].dow); g.setColor(255,255,255); g.reset(); - g.setFont("8x16"); + g.setFont("HaxorNarrow7x17"); g.drawString(foundSchedule[((foundNumber-2)+currentPositionTable)].cn,13,30); - g.drawString(scheduleHourUpdatedStart+":"+scheduleMinuteUpdatedStart+"-"+scheduleHourUpdatedEnd+":"+scheduleMinuteUpdatedEnd,13,50); + g.drawString(scheduleHourUpdatedStart+":"+scheduleMinuteUpdatedStart+"-"+scheduleHourUpdatedEnd+":"+scheduleMinuteUpdatedEnd+" "+schduleDay+" ",13,45); } g.flip(); } From 36a52b8b001a4515d715950db56e411a6c4842b2 Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Mon, 11 Oct 2021 17:43:52 -0700 Subject: [PATCH 036/155] Update README.md --- apps/schoolCalendar/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/schoolCalendar/README.md b/apps/schoolCalendar/README.md index 1cd152864..58cb86d2b 100644 --- a/apps/schoolCalendar/README.md +++ b/apps/schoolCalendar/README.md @@ -4,6 +4,6 @@ School Calendar is a calendar that you can see your upcoming classes or schedule ## Versions: -Version 1.00: Get Design Working +Version 1.0: Get Design Working -Version 2.00: Update Graphics +Version 2.0: Update Graphics From 5bbfb1109910856575cc348012dbc44d489a8005 Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Mon, 18 Oct 2021 19:43:11 -0700 Subject: [PATCH 038/155] Update interface.html update interface --- apps/schoolCalendar/interface.html | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/apps/schoolCalendar/interface.html b/apps/schoolCalendar/interface.html index b26c07723..0ff6a2af3 100644 --- a/apps/schoolCalendar/interface.html +++ b/apps/schoolCalendar/interface.html @@ -22,12 +22,28 @@

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 ada94b11cfbebfeaa010cee1b39d0f92591d4e13 Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Wed, 3 Nov 2021 08:06:50 -0700 Subject: [PATCH 048/155] Update interface.html --- apps/schoolCalendar/interface.html | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/apps/schoolCalendar/interface.html b/apps/schoolCalendar/interface.html index 90eaacc85..4c2db202b 100644 --- a/apps/schoolCalendar/interface.html +++ b/apps/schoolCalendar/interface.html @@ -69,6 +69,29 @@ var calendarEvents = calendar.getEvents(); let schedule = [] + + 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) + } + + content = schedule + + document.getElementById("upload").addEventListener("click", function() { + sendCustomizedApp({ + id : "schoolCalender", + storage:[ + {"name":"customFile.app.json",url:"app.js",content:content} + ] + }); + }); + From df7f9cc530194025708b712565a410aed1d73e95 Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Wed, 3 Nov 2021 08:20:09 -0700 Subject: [PATCH 049/155] Update interface.html --- apps/schoolCalendar/interface.html | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/schoolCalendar/interface.html b/apps/schoolCalendar/interface.html index 4c2db202b..9c527591e 100644 --- a/apps/schoolCalendar/interface.html +++ b/apps/schoolCalendar/interface.html @@ -81,11 +81,10 @@ schedule.push(calendarEntry) } - content = schedule document.getElementById("upload").addEventListener("click", function() { + content = schedule sendCustomizedApp({ - id : "schoolCalender", storage:[ {"name":"customFile.app.json",url:"app.js",content:content} ] From cd7579269efe8d54a5a5680b8126ea38f9ce54be Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Wed, 3 Nov 2021 08:22:04 -0700 Subject: [PATCH 050/155] Update apps.json --- apps.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps.json b/apps.json index 810128f45..4ff587714 100644 --- a/apps.json +++ b/apps.json @@ -3521,7 +3521,7 @@ "description": "A simple calendar that you can see your upcoming events. Keep in note that your events reapeat weekly.", "tags": "tool", "readme": "README.md", - "custom":"interface.html", + "custom":"custom.html", "storage": [ {"name":"schoolCalendar.app.js","url":"app.js"}, {"name":"schoolCalendar.img","url":"app-icon.js","evaluate":true} From e2928498690565ba7d0454aaa4265bc8adfda712 Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Wed, 3 Nov 2021 08:22:32 -0700 Subject: [PATCH 051/155] Rename interface.html to custom.html --- apps/schoolCalendar/{interface.html => custom.html} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename apps/schoolCalendar/{interface.html => custom.html} (100%) diff --git a/apps/schoolCalendar/interface.html b/apps/schoolCalendar/custom.html similarity index 100% rename from apps/schoolCalendar/interface.html rename to apps/schoolCalendar/custom.html From 7a7175fa483e4e9fcd5912d83e9519274d67b4f0 Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Thu, 4 Nov 2021 16:00:41 -0700 Subject: [PATCH 052/155] Update custom.html --- apps/schoolCalendar/custom.html | 106 +++++++------------------------- 1 file changed, 21 insertions(+), 85 deletions(-) diff --git a/apps/schoolCalendar/custom.html b/apps/schoolCalendar/custom.html index 9c527591e..83d1bb939 100644 --- a/apps/schoolCalendar/custom.html +++ b/apps/schoolCalendar/custom.html @@ -1,99 +1,35 @@ - -
-

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.

-
- - - + + + +

Some text:

+

Click

+ + - - -
From ae85c1cee709457b10233205c057f946ea15fe83 Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Sat, 20 Nov 2021 12:14:33 -0800 Subject: [PATCH 053/155] Update custom.html --- apps/schoolCalendar/custom.html | 84 +++++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) diff --git a/apps/schoolCalendar/custom.html b/apps/schoolCalendar/custom.html index 83d1bb939..8eac0baa1 100644 --- a/apps/schoolCalendar/custom.html +++ b/apps/schoolCalendar/custom.html @@ -1,9 +1,88 @@ +@@ -1,99 +1,35 @@ + +
+

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 c8ad92a3dcb7b8a0df83f2e720b6730dd785e829 Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Sat, 20 Nov 2021 12:20:33 -0800 Subject: [PATCH 054/155] Update apps.json --- apps.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps.json b/apps.json index 4ff587714..4d16a3cef 100644 --- a/apps.json +++ b/apps.json @@ -3518,10 +3518,10 @@ "shortName":"SCalendar", "icon": "CalenderLogo.png", "version":"0.01", - "description": "A simple calendar that you can see your upcoming events. Keep in note that your events reapeat weekly.", + "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", - "custom":"custom.html", + "custom":"interface.html", "storage": [ {"name":"schoolCalendar.app.js","url":"app.js"}, {"name":"schoolCalendar.img","url":"app-icon.js","evaluate":true} From 3a3962bfdc0365e75f8804b61293ec91084f6698 Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Sat, 20 Nov 2021 12:30:12 -0800 Subject: [PATCH 055/155] Rename custom.html to interface.html --- apps/schoolCalendar/{custom.html => interface.html} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename apps/schoolCalendar/{custom.html => interface.html} (100%) diff --git a/apps/schoolCalendar/custom.html b/apps/schoolCalendar/interface.html similarity index 100% rename from apps/schoolCalendar/custom.html rename to apps/schoolCalendar/interface.html From 993bb7298c8fbc9348e6668b70a555c51dc54a09 Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Sat, 20 Nov 2021 12:31:41 -0800 Subject: [PATCH 056/155] Rename interface.html to custom.html --- apps/schoolCalendar/{interface.html => custom.html} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename apps/schoolCalendar/{interface.html => custom.html} (100%) diff --git a/apps/schoolCalendar/interface.html b/apps/schoolCalendar/custom.html similarity index 100% rename from apps/schoolCalendar/interface.html rename to apps/schoolCalendar/custom.html From 1a53e8d34af89f0e6c889653961350a898deea81 Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Sat, 20 Nov 2021 12:33:43 -0800 Subject: [PATCH 057/155] 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 8eac0baa1..e9ca2d3da 100644 --- a/apps/schoolCalendar/custom.html +++ b/apps/schoolCalendar/custom.html @@ -105,7 +105,7 @@ g.drawString(txt,120,120); // send finished app (in addition to contents of app.json) sendCustomizedApp({ storage:[ - {"name":"customFile.app.json",url:"app.js",content:content} + {"name":"customFile.app.json",url:"app.js",content:content}, {name:"myapp.app.js", url:"app.js", content:app}, ] }); From 98048dc23cb1caf3aafbf1d1ecfa461ba5943925 Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Sat, 20 Nov 2021 12:37:23 -0800 Subject: [PATCH 058/155] Update apps.json --- apps.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps.json b/apps.json index 4d16a3cef..32b2ba209 100644 --- a/apps.json +++ b/apps.json @@ -3521,10 +3521,11 @@ "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", - "custom":"interface.html", + "custom":"custom.html", "storage": [ {"name":"schoolCalendar.app.js","url":"app.js"}, {"name":"schoolCalendar.img","url":"app-icon.js","evaluate":true} + {"name":"schoolCalendar.img","url":"app-icon.js","evaluate":true} ], "data": [ {"name":"app.json"} From ba2aee642bf90307757bb85fda6aa0db01bed5e5 Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Sat, 20 Nov 2021 12:39:06 -0800 Subject: [PATCH 059/155] Update custom.html --- apps/schoolCalendar/custom.html | 84 --------------------------------- 1 file changed, 84 deletions(-) diff --git a/apps/schoolCalendar/custom.html b/apps/schoolCalendar/custom.html index e9ca2d3da..83d1bb939 100644 --- a/apps/schoolCalendar/custom.html +++ b/apps/schoolCalendar/custom.html @@ -1,88 +1,9 @@ -@@ -1,99 +1,35 @@ - -
-

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 37f4329ec57bc4a96a1b2a0847406f6a05cad520 Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Sat, 20 Nov 2021 12:39:49 -0800 Subject: [PATCH 060/155] Update apps.json --- apps.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps.json b/apps.json index 32b2ba209..16a768a91 100644 --- a/apps.json +++ b/apps.json @@ -3524,7 +3524,7 @@ "custom":"custom.html", "storage": [ {"name":"schoolCalendar.app.js","url":"app.js"}, - {"name":"schoolCalendar.img","url":"app-icon.js","evaluate":true} + {"name":"schoolCalendar.img","url":"app-icon.js","evaluate":true}, {"name":"schoolCalendar.img","url":"app-icon.js","evaluate":true} ], "data": [ From 99112920b287568db32c5e43b47cfc2ee0cf71a3 Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Sat, 20 Nov 2021 12:47:34 -0800 Subject: [PATCH 061/155] Update apps.json --- apps.json | 1 - 1 file changed, 1 deletion(-) diff --git a/apps.json b/apps.json index 16a768a91..f5d60d3e9 100644 --- a/apps.json +++ b/apps.json @@ -3525,7 +3525,6 @@ "storage": [ {"name":"schoolCalendar.app.js","url":"app.js"}, {"name":"schoolCalendar.img","url":"app-icon.js","evaluate":true}, - {"name":"schoolCalendar.img","url":"app-icon.js","evaluate":true} ], "data": [ {"name":"app.json"} From d871855f8e217b27ea73ed28b389bbc8aea59d62 Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Sat, 20 Nov 2021 12:55:58 -0800 Subject: [PATCH 062/155] Update apps.json --- apps.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps.json b/apps.json index f5d60d3e9..d4d3a04fa 100644 --- a/apps.json +++ b/apps.json @@ -3524,7 +3524,7 @@ "custom":"custom.html", "storage": [ {"name":"schoolCalendar.app.js","url":"app.js"}, - {"name":"schoolCalendar.img","url":"app-icon.js","evaluate":true}, + {"name":"schoolCalendar.img","url":"app-icon.js","evaluate":true} ], "data": [ {"name":"app.json"} From b108bc45fb8bfa266e4d4ad3e66a9f65cec96c92 Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Sat, 20 Nov 2021 12:58:24 -0800 Subject: [PATCH 063/155] Update custom.html --- apps/schoolCalendar/custom.html | 84 +++++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) diff --git a/apps/schoolCalendar/custom.html b/apps/schoolCalendar/custom.html index 83d1bb939..8eac0baa1 100644 --- a/apps/schoolCalendar/custom.html +++ b/apps/schoolCalendar/custom.html @@ -1,9 +1,88 @@ +@@ -1,99 +1,35 @@ + +
+

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 9c07800c5ef948a1b9b2071e7bdfe46809fefa1d Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Sat, 20 Nov 2021 13:07:38 -0800 Subject: [PATCH 064/155] Update custom.html --- apps/schoolCalendar/custom.html | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/apps/schoolCalendar/custom.html b/apps/schoolCalendar/custom.html index 8eac0baa1..73da4e6b5 100644 --- a/apps/schoolCalendar/custom.html +++ b/apps/schoolCalendar/custom.html @@ -30,6 +30,8 @@ + + + + + +

Some text:

+

Click

+ - - - - - -
- + From 58df0a5ca26684b4da84929c8c388ca5c37acabd Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Sat, 20 Nov 2021 15:01:03 -0800 Subject: [PATCH 066/155] Update apps.json --- apps.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/apps.json b/apps.json index d4d3a04fa..c41c1268e 100644 --- a/apps.json +++ b/apps.json @@ -3523,8 +3523,7 @@ "readme": "README.md", "custom":"custom.html", "storage": [ - {"name":"schoolCalendar.app.js","url":"app.js"}, - {"name":"schoolCalendar.img","url":"app-icon.js","evaluate":true} + {"name":"schoolCalendar.app.js"} ], "data": [ {"name":"app.json"} From dc9db8a4767d4c16d92645131d977263e2468521 Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Sat, 20 Nov 2021 15:01:58 -0800 Subject: [PATCH 067/155] Update schoolCalendar.js --- apps/schoolCalendar/schoolCalendar.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/schoolCalendar/schoolCalendar.js b/apps/schoolCalendar/schoolCalendar.js index 29de1aec4..2b517533d 100644 --- a/apps/schoolCalendar/schoolCalendar.js +++ b/apps/schoolCalendar/schoolCalendar.js @@ -152,8 +152,8 @@ function getScheduleTable() { function findNextScheduleIndex() { var schedule = getScheduleTable(); var currentDate = new Date(); - //var minuteOfWeek = (currentDate.getDay()*3600)+(currentDate.getHours()*60)+currentDate.getMinutes(); - var minuteOfWeek = (4*3600)+(16*60)+0; + var minuteOfWeek = (currentDate.getDay()*3600)+(currentDate.getHours()*60)+currentDate.getMinutes(); + //var minuteOfWeek = (4*3600)+(16*60)+0; var currentPosition; for(currentPosition = 0;currentPosition < schedule.length; currentPosition++){ var scheduleItemStartMinuteOfWeek = schedule[currentPosition].dow*3600 + schedule[currentPosition].eh*60+schedule[currentPosition].em; From 345d9c155135fc4d0a2b8d00b3528b363a584ced Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Sat, 20 Nov 2021 15:02:12 -0800 Subject: [PATCH 068/155] Delete schoolCalendar.js --- apps/schoolCalendar/schoolCalendar.js | 400 -------------------------- 1 file changed, 400 deletions(-) delete mode 100644 apps/schoolCalendar/schoolCalendar.js diff --git a/apps/schoolCalendar/schoolCalendar.js b/apps/schoolCalendar/schoolCalendar.js deleted file mode 100644 index 2b517533d..000000000 --- a/apps/schoolCalendar/schoolCalendar.js +++ /dev/null @@ -1,400 +0,0 @@ -require("Font8x12").add(Graphics); -require("Font7x11Numeric7Seg", 2).add(Graphics); - -let nIntervId; - -function redrawScreen() { - layout.render(layout.background); - layout.render(layout.buttons); - draw(); -} - -function updateDay(ffunction,day){ - if(ffunction == 1){ - switch (day) { - case 0: - return "Sunday"; - case 1: - day = "Monday"; - break; - case 2: - day = "Tuesday"; - break; - case 3: - day = "Wednesday"; - break; - case 4: - day = "Thursday"; - break; - case 5: - day = "Friday"; - break; - case 6: - day = "Saturday"; - } - return day; - }else if(ffunction == 2){ - switch (day) { - case 0: - return "Sun"; - case 1: - day = "Mon"; - break; - case 2: - day = "Tue"; - break; - case 3: - day = "Wed"; - break; - case 4: - day = "Thu"; - break; - case 5: - day = "Fri"; - break; - case 6: - day = "Sat"; - } - return day; - }else if(ffunction == 3){ - switch (day) { - case 0: - return "S"; - case 1: - day = "M"; - break; - case 2: - day = "T"; - break; - case 3: - day = "W"; - break; - case 4: - day = "R"; - break; - case 5: - day = "F"; - break; - case 6: - day = "S"; - } - return 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: - ]; - return schedule; -} - -function findNextScheduleIndex() { - var schedule = getScheduleTable(); - var currentDate = new Date(); - var minuteOfWeek = (currentDate.getDay()*3600)+(currentDate.getHours()*60)+currentDate.getMinutes(); - //var minuteOfWeek = (4*3600)+(16*60)+0; - var currentPosition; - for(currentPosition = 0;currentPosition < schedule.length; currentPosition++){ - var scheduleItemStartMinuteOfWeek = schedule[currentPosition].dow*3600 + schedule[currentPosition].eh*60+schedule[currentPosition].em; - if(scheduleItemStartMinuteOfWeek > minuteOfWeek) { - return currentPosition; - } - } - return 0; -} - - -function getUpArrow() {return require("heatshrink").decompress(atob("hkOyANKmv9AIIjRCoYZRlvdAI8U3YVK3oBJC4Mc7YVRC4sc7gVCzoBNC4oZDGowXGR58lvoBFC9FcAIoXongBFC58dngBFC6EcAIoPHA"));} - -function getDownArrow() {return require("heatshrink").decompress(atob("hkOyALImv9AIojPmvdAIoXPlvdAIoXQ3oBFC9GdAIoXnkt9AIoPPAI8U3cc7cc7gBBDIVcAJYXFGYwXOLpU8AI4XBO5sdjgBFR54ZFBpIA=="));} - -function getMenuIcon() {return require("heatshrink").decompress(atob("iEQyBC/AEU+rwBEn02js17st3stvklrkljkc/cc3cUzYBBD5AdUD4oA/P/4A/P/4A/ADoA=="));} - -function getDotIcon() {return require("heatshrink").decompress(atob("iEQyBC/AA0t3oBBA4ndAIIPGA4gAFkt9lt9AYIHEzoBBBIwRED41cks8AYIJGA44RGP8xtGP44RJBYh1CAIIHHBJJ/KroBBPoqBFB4YRDAA8dngHHBJKdq3oBDBI4RNP4l9AIYHHBJJBJks8AIIHTAH4ABA="));} - -var currentPositionTable = 0; -var numberOfItemsShown = 8; -//Table Positions: -var rectStart = 45; -var rectEnd = 65; -var rectStartX = 10; -var rectEndX = 210; -//Scences: -LIST = 1; -INFORMATION = 2; -currentStage = LIST; - -function splitter(str, l){ - var strs = []; - while(str.length > l){ - var pos = str.substring(0, l).lastIndexOf(' '); - pos = pos <= 0 ? l : pos; - strs.push(str.substring(0, pos)); - var i = str.indexOf(' ', pos)+1; - if(i < pos || i > pos+l) - i = pos; - str = str.substring(i); - } - strs.push(str); - return strs; -} - -function updateMinutesToCurrentTime(currentMinuteFunction) { - if (currentMinuteFunction<10){ - currentMinuteUpdatedFunction = "0"+currentMinuteFunction; - }else{ - currentMinuteUpdatedFunction = currentMinuteFunction; - } - return currentMinuteUpdatedFunction; -} - -function renderBackground(l) { - g.clearRect(0,0,240,20); - g.drawImage(getBackgroundImage(),110,130,{scale:9,rotate:0}); -} - -function renderTable(l) { - for(var x = 0;x<=numberOfItemsShown;x++){ - g.setColor(255,255,255); - g.drawRect(rectStartX,rectStart+(x*20),rectEndX,rectEnd+(20*x)); - g.setColor(255,205,0); - g.drawRect(rectStartX,rectStart+(2*20),rectEndX,rectEnd+(2*20)); - g.setColor(255,0,0); - g.drawRect(rectStartX,rectStart+(currentPositionTable*20),rectEndX,rectEnd+(20*currentPositionTable)); - } -} - -function renderTableText(l){ - var foundNumber = findNextScheduleIndex(); - var foundSchedule = getScheduleTable(); - var scheduleHourUpdated; - var scheduleMinuteUpdated; - var beforeFoundNumber = foundNumber - 2; - for(var x = 0;x<=numberOfItemsShown;x++){ - var currentNumber = beforeFoundNumber + x; - if (beforeFoundNumber + x < 0) { - currentNumber = foundSchedule.length + beforeFoundNumber + x; - } else if (beforeFoundNumber + x > foundSchedule.length - 1) { - currentNumber = beforeFoundNumber + x - foundSchedule.length; - } - - scheduleMinuteUpdatedStart = updateMinutesToCurrentTime(foundSchedule[currentNumber].sm); - scheduleHourUpdatedStart = foundSchedule[currentNumber].sh; - scheduleMinuteUpdatedEnd = updateMinutesToCurrentTime(foundSchedule[currentNumber].em); - scheduleHourUpdatedEnd = foundSchedule[currentNumber].eh; - scheduleDecriptionUpdated = foundSchedule[currentNumber].cn.substring(0, 20); - if(foundSchedule[currentNumber].cn.length >= 15){ - scheduleDecriptionUpdated = foundSchedule[currentNumber].cn.substring(0, 20)+"..."; - } - schduleDay = updateDay(3,foundSchedule[currentNumber].dow); - g.setFont("8x12"); - g.drawString(scheduleHourUpdatedStart+":"+scheduleMinuteUpdatedStart+"-"+scheduleHourUpdatedEnd+":"+scheduleMinuteUpdatedEnd+" "+schduleDay+" "+scheduleDecriptionUpdated,13,50+(x*20)); - } -} - -function buttonsF(l){ - if(currentStage == LIST){ - g.drawImage(getDotIcon(),223.5,115); - }else{ - g.drawImage(getMenuIcon(),223.5,115); - } - g.drawImage(getUpArrow(),225,30); - g.drawImage(getDownArrow(),225,215); -} - -function draw() { - var currentDate = new Date(); - var currentDayOfWeek = currentDate.getDay(); - var currentHour = currentDate.getHours(); - var currentMinute = currentDate.getMinutes(); - var currentMinuteUpdated = updateMinutesToCurrentTime(currentMinute); - if (layout) { - if(currentStage == LIST){ - layout.time.label = currentHour+":"+currentMinuteUpdated; - layout.time.x = 147; - layout.time.y = 10; - layout.render(layout.table); - layout.render(layout.tableText); - logDebug("Rendered"+currentPositionTable); - }else{ - layout.time.label = currentHour+":"+currentMinuteUpdated; - layout.time.x = 147; - layout.time.y = 10; - layout.render(layout.info); - logDebug("Rendered"+currentPositionTable); - } - g.clearRect(150,0,220,35); - layout.render(layout.time); - } -} - -function RedRectDown() { - if(currentPositionTable > 0){ - currentPositionTable -= 1; - if(currentStage == INFORMATION){ - redrawScreen(); - }else{ - draw(); - } - } -} - -function RedRectUp() { - if(currentPositionTable < numberOfItemsShown){ - currentPositionTable += 1; - if(currentStage == INFORMATION){ - redrawScreen(); - }else{ - draw(); - } - } -} - -function renderMiniBackground(l){ - for(var i = 233;i<=240;i++){ - g.drawImage(getBackgroundImage(),i,123,{scale:10,rotate:0}); - } -} - -function renderLoading(l){ - g.setFont("8x12"); - g.drawString("Loading...",240/2-20,240/2-20); -} - -function renderInformation(l){ - var foundNumber = findNextScheduleIndex(); - var foundSchedule = getScheduleTable(); - scheduleMinuteUpdatedStart = updateMinutesToCurrentTime(foundSchedule[foundNumber].sm); - scheduleHourUpdatedStart = foundSchedule[foundNumber].sh; - scheduleMinuteUpdatedEnd = updateMinutesToCurrentTime(foundSchedule[foundNumber].em); - scheduleHourUpdatedEnd = foundSchedule[foundNumber].eh; - scheduleDay = updateDay(1,foundSchedule[((foundNumber-2)+currentPositionTable)].dow); - g.setColor(255,255,255); - g.setFont("8x12",2); - var splitClassNames = splitter(foundSchedule[((foundNumber-2)+currentPositionTable)].cn, 15); - var currentY = 5; - for (var j=0; j < splitClassNames.length; j++) { - g.drawString(splitClassNames[j],13,currentY+50); - currentY = currentY + 25; - } - g.setFont("8x12"); - g.drawString(schduleDay,13,currentY+50); - g.drawString(scheduleHourUpdatedStart+":"+scheduleMinuteUpdatedStart+"-"+scheduleHourUpdatedEnd+":"+scheduleMinuteUpdatedEnd,13,currentY+15+50); -} - -var Layout = require("Layout"); -var layout = new Layout( - {type:"h", c: [ - {type:"custom", render:renderTableText, id:"tableText"}, - {type:"custom", render:buttonsF, id:"buttons"}, - {type:"custom", render:renderBackground, id:"background"}, - {type:"custom", render:renderTable, id:"table"}, - {type:"custom", render:renderMiniBackground, id:"miniBackground"}, - {type:"custom", render:renderLoading, id:"loading"}, - {type:"custom", render:renderInformation, id:"info"}, - {type:"txt", font:"7x11Numeric7Seg:2", label:"00:00", id:"time"}, - ]}, - {type:"v", c:[ - ]}, - {btns:[ - {label:"", cb: RedRectUp()}, - {label:"", cb: l=>print("Two")}, - {label:"", cb: RedRectDown()} -]}); - - -function getBackgroundImage() {return require("heatshrink").decompress(atob("j0ZyEKIf4A4gIB6gQB6gYB6ggB6goB6gwB6g4B6hAABAYIBHBZIVLAK8IhIBXgAThhQB6hYB6hgB6hoB6hwB6h4B6iAB6iIB6iQBHiAJOB54XSiYB6igB6ioB6iwB6i4B5A="));} - -function logDebug(message) {console.log(message);} - -function changeScene(){ - layout.render(layout.buttons); - if(currentStage == INFORMATION){ - currentStage = LIST; - nIntervId = setInterval(redrawScreen, 100000); - }else if(currentStage == LIST){ - currentStage = INFORMATION; - clearInterval(); - } - layout.render(layout.background); - layout.render(layout.buttons); - draw(); -} - -// timeout used to update every minute -var drawTimeout; - -setInterval(draw, 15000); - - -setWatch(RedRectUp, BTN3, { repeat:true, edge:'rising', debounce : 50 }); -setWatch(RedRectDown, BTN1, { repeat:true, edge:'rising', debounce : 50 }); -setWatch(changeScene, BTN2, { repeat:true, edge:'rising', debounce : 50 }); - -layout.update(); -layout.render(layout.loading); -layout.render(layout.background); -layout.render(layout.buttons); - -draw(); From e965bd7d962108a2944824eb6579fce435876966 Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Sat, 20 Nov 2021 15:02:43 -0800 Subject: [PATCH 069/155] Update custom.html --- apps/schoolCalendar/custom.html | 405 +++++++++++++++++++++++++++++++- 1 file changed, 400 insertions(+), 5 deletions(-) diff --git a/apps/schoolCalendar/custom.html b/apps/schoolCalendar/custom.html index f26deb61c..36e8ad7c7 100644 --- a/apps/schoolCalendar/custom.html +++ b/apps/schoolCalendar/custom.html @@ -16,11 +16,406 @@ var text = document.getElementById("mytext").value; // build the app's text using a templated String var app = ` -var txt = ${JSON.stringify(text)}; -g.clear(1); -g.setFontAlign(0,0); -g.setFont("6x8"); -g.drawString(txt,120,120); +require("Font8x12").add(Graphics); +require("Font7x11Numeric7Seg", 2).add(Graphics); + +let nIntervId; + +function redrawScreen() { + layout.render(layout.background); + layout.render(layout.buttons); + draw(); +} + +function updateDay(ffunction,day){ + if(ffunction == 1){ + switch (day) { + case 0: + return "Sunday"; + case 1: + day = "Monday"; + break; + case 2: + day = "Tuesday"; + break; + case 3: + day = "Wednesday"; + break; + case 4: + day = "Thursday"; + break; + case 5: + day = "Friday"; + break; + case 6: + day = "Saturday"; + } + return day; + }else if(ffunction == 2){ + switch (day) { + case 0: + return "Sun"; + case 1: + day = "Mon"; + break; + case 2: + day = "Tue"; + break; + case 3: + day = "Wed"; + break; + case 4: + day = "Thu"; + break; + case 5: + day = "Fri"; + break; + case 6: + day = "Sat"; + } + return day; + }else if(ffunction == 3){ + switch (day) { + case 0: + return "S"; + case 1: + day = "M"; + break; + case 2: + day = "T"; + break; + case 3: + day = "W"; + break; + case 4: + day = "R"; + break; + case 5: + day = "F"; + break; + case 6: + day = "S"; + } + return 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: + ]; + return schedule; +} + +function findNextScheduleIndex() { + var schedule = getScheduleTable(); + var currentDate = new Date(); + var minuteOfWeek = (currentDate.getDay()*3600)+(currentDate.getHours()*60)+currentDate.getMinutes(); + //var minuteOfWeek = (4*3600)+(16*60)+0; + var currentPosition; + for(currentPosition = 0;currentPosition < schedule.length; currentPosition++){ + var scheduleItemStartMinuteOfWeek = schedule[currentPosition].dow*3600 + schedule[currentPosition].eh*60+schedule[currentPosition].em; + if(scheduleItemStartMinuteOfWeek > minuteOfWeek) { + return currentPosition; + } + } + return 0; +} + + +function getUpArrow() {return require("heatshrink").decompress(atob("hkOyANKmv9AIIjRCoYZRlvdAI8U3YVK3oBJC4Mc7YVRC4sc7gVCzoBNC4oZDGowXGR58lvoBFC9FcAIoXongBFC58dngBFC6EcAIoPHA"));} + +function getDownArrow() {return require("heatshrink").decompress(atob("hkOyALImv9AIojPmvdAIoXPlvdAIoXQ3oBFC9GdAIoXnkt9AIoPPAI8U3cc7cc7gBBDIVcAJYXFGYwXOLpU8AI4XBO5sdjgBFR54ZFBpIA=="));} + +function getMenuIcon() {return require("heatshrink").decompress(atob("iEQyBC/AEU+rwBEn02js17st3stvklrkljkc/cc3cUzYBBD5AdUD4oA/P/4A/P/4A/ADoA=="));} + +function getDotIcon() {return require("heatshrink").decompress(atob("iEQyBC/AA0t3oBBA4ndAIIPGA4gAFkt9lt9AYIHEzoBBBIwRED41cks8AYIJGA44RGP8xtGP44RJBYh1CAIIHHBJJ/KroBBPoqBFB4YRDAA8dngHHBJKdq3oBDBI4RNP4l9AIYHHBJJBJks8AIIHTAH4ABA="));} + +var currentPositionTable = 0; +var numberOfItemsShown = 8; +//Table Positions: +var rectStart = 45; +var rectEnd = 65; +var rectStartX = 10; +var rectEndX = 210; +//Scences: +LIST = 1; +INFORMATION = 2; +currentStage = LIST; + +function splitter(str, l){ + var strs = []; + while(str.length > l){ + var pos = str.substring(0, l).lastIndexOf(' '); + pos = pos <= 0 ? l : pos; + strs.push(str.substring(0, pos)); + var i = str.indexOf(' ', pos)+1; + if(i < pos || i > pos+l) + i = pos; + str = str.substring(i); + } + strs.push(str); + return strs; +} + +function updateMinutesToCurrentTime(currentMinuteFunction) { + if (currentMinuteFunction<10){ + currentMinuteUpdatedFunction = "0"+currentMinuteFunction; + }else{ + currentMinuteUpdatedFunction = currentMinuteFunction; + } + return currentMinuteUpdatedFunction; +} + +function renderBackground(l) { + g.clearRect(0,0,240,20); + g.drawImage(getBackgroundImage(),110,130,{scale:9,rotate:0}); +} + +function renderTable(l) { + for(var x = 0;x<=numberOfItemsShown;x++){ + g.setColor(255,255,255); + g.drawRect(rectStartX,rectStart+(x*20),rectEndX,rectEnd+(20*x)); + g.setColor(255,205,0); + g.drawRect(rectStartX,rectStart+(2*20),rectEndX,rectEnd+(2*20)); + g.setColor(255,0,0); + g.drawRect(rectStartX,rectStart+(currentPositionTable*20),rectEndX,rectEnd+(20*currentPositionTable)); + } +} + +function renderTableText(l){ + var foundNumber = findNextScheduleIndex(); + var foundSchedule = getScheduleTable(); + var scheduleHourUpdated; + var scheduleMinuteUpdated; + var beforeFoundNumber = foundNumber - 2; + for(var x = 0;x<=numberOfItemsShown;x++){ + var currentNumber = beforeFoundNumber + x; + if (beforeFoundNumber + x < 0) { + currentNumber = foundSchedule.length + beforeFoundNumber + x; + } else if (beforeFoundNumber + x > foundSchedule.length - 1) { + currentNumber = beforeFoundNumber + x - foundSchedule.length; + } + + scheduleMinuteUpdatedStart = updateMinutesToCurrentTime(foundSchedule[currentNumber].sm); + scheduleHourUpdatedStart = foundSchedule[currentNumber].sh; + scheduleMinuteUpdatedEnd = updateMinutesToCurrentTime(foundSchedule[currentNumber].em); + scheduleHourUpdatedEnd = foundSchedule[currentNumber].eh; + scheduleDecriptionUpdated = foundSchedule[currentNumber].cn.substring(0, 20); + if(foundSchedule[currentNumber].cn.length >= 15){ + scheduleDecriptionUpdated = foundSchedule[currentNumber].cn.substring(0, 20)+"..."; + } + schduleDay = updateDay(3,foundSchedule[currentNumber].dow); + g.setFont("8x12"); + g.drawString(scheduleHourUpdatedStart+":"+scheduleMinuteUpdatedStart+"-"+scheduleHourUpdatedEnd+":"+scheduleMinuteUpdatedEnd+" "+schduleDay+" "+scheduleDecriptionUpdated,13,50+(x*20)); + } +} + +function buttonsF(l){ + if(currentStage == LIST){ + g.drawImage(getDotIcon(),223.5,115); + }else{ + g.drawImage(getMenuIcon(),223.5,115); + } + g.drawImage(getUpArrow(),225,30); + g.drawImage(getDownArrow(),225,215); +} + +function draw() { + var currentDate = new Date(); + var currentDayOfWeek = currentDate.getDay(); + var currentHour = currentDate.getHours(); + var currentMinute = currentDate.getMinutes(); + var currentMinuteUpdated = updateMinutesToCurrentTime(currentMinute); + if (layout) { + if(currentStage == LIST){ + layout.time.label = currentHour+":"+currentMinuteUpdated; + layout.time.x = 147; + layout.time.y = 10; + layout.render(layout.table); + layout.render(layout.tableText); + logDebug("Rendered"+currentPositionTable); + }else{ + layout.time.label = currentHour+":"+currentMinuteUpdated; + layout.time.x = 147; + layout.time.y = 10; + layout.render(layout.info); + logDebug("Rendered"+currentPositionTable); + } + g.clearRect(150,0,220,35); + layout.render(layout.time); + } +} + +function RedRectDown() { + if(currentPositionTable > 0){ + currentPositionTable -= 1; + if(currentStage == INFORMATION){ + redrawScreen(); + }else{ + draw(); + } + } +} + +function RedRectUp() { + if(currentPositionTable < numberOfItemsShown){ + currentPositionTable += 1; + if(currentStage == INFORMATION){ + redrawScreen(); + }else{ + draw(); + } + } +} + +function renderMiniBackground(l){ + for(var i = 233;i<=240;i++){ + g.drawImage(getBackgroundImage(),i,123,{scale:10,rotate:0}); + } +} + +function renderLoading(l){ + g.setFont("8x12"); + g.drawString("Loading...",240/2-20,240/2-20); +} + +function renderInformation(l){ + var foundNumber = findNextScheduleIndex(); + var foundSchedule = getScheduleTable(); + scheduleMinuteUpdatedStart = updateMinutesToCurrentTime(foundSchedule[foundNumber].sm); + scheduleHourUpdatedStart = foundSchedule[foundNumber].sh; + scheduleMinuteUpdatedEnd = updateMinutesToCurrentTime(foundSchedule[foundNumber].em); + scheduleHourUpdatedEnd = foundSchedule[foundNumber].eh; + scheduleDay = updateDay(1,foundSchedule[((foundNumber-2)+currentPositionTable)].dow); + g.setColor(255,255,255); + g.setFont("8x12",2); + var splitClassNames = splitter(foundSchedule[((foundNumber-2)+currentPositionTable)].cn, 15); + var currentY = 5; + for (var j=0; j < splitClassNames.length; j++) { + g.drawString(splitClassNames[j],13,currentY+50); + currentY = currentY + 25; + } + g.setFont("8x12"); + g.drawString(schduleDay,13,currentY+50); + g.drawString(scheduleHourUpdatedStart+":"+scheduleMinuteUpdatedStart+"-"+scheduleHourUpdatedEnd+":"+scheduleMinuteUpdatedEnd,13,currentY+15+50); +} + +var Layout = require("Layout"); +var layout = new Layout( + {type:"h", c: [ + {type:"custom", render:renderTableText, id:"tableText"}, + {type:"custom", render:buttonsF, id:"buttons"}, + {type:"custom", render:renderBackground, id:"background"}, + {type:"custom", render:renderTable, id:"table"}, + {type:"custom", render:renderMiniBackground, id:"miniBackground"}, + {type:"custom", render:renderLoading, id:"loading"}, + {type:"custom", render:renderInformation, id:"info"}, + {type:"txt", font:"7x11Numeric7Seg:2", label:"00:00", id:"time"}, + ]}, + {type:"v", c:[ + ]}, + {btns:[ + {label:"", cb: RedRectUp()}, + {label:"", cb: l=>print("Two")}, + {label:"", cb: RedRectDown()} +]}); + + +function getBackgroundImage() {return require("heatshrink").decompress(atob("j0ZyEKIf4A4gIB6gQB6gYB6ggB6goB6gwB6g4B6hAABAYIBHBZIVLAK8IhIBXgAThhQB6hYB6hgB6hoB6hwB6h4B6iAB6iIB6iQBHiAJOB54XSiYB6igB6ioB6iwB6i4B5A="));} + +function logDebug(message) {console.log(message);} + +function changeScene(){ + layout.render(layout.buttons); + if(currentStage == INFORMATION){ + currentStage = LIST; + nIntervId = setInterval(redrawScreen, 100000); + }else if(currentStage == LIST){ + currentStage = INFORMATION; + clearInterval(); + } + layout.render(layout.background); + layout.render(layout.buttons); + draw(); +} + +// timeout used to update every minute +var drawTimeout; + +setInterval(draw, 15000); + + +setWatch(RedRectUp, BTN3, { repeat:true, edge:'rising', debounce : 50 }); +setWatch(RedRectDown, BTN1, { repeat:true, edge:'rising', debounce : 50 }); +setWatch(changeScene, BTN2, { repeat:true, edge:'rising', debounce : 50 }); + +layout.update(); +layout.render(layout.loading); +layout.render(layout.background); +layout.render(layout.buttons); + +draw(); `; // send finished app (in addition to contents of app.json) sendCustomizedApp({ From 5d24a09ffa2d9cad10a5fae3c7b4d5fafc944aa9 Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Sat, 20 Nov 2021 15:04:41 -0800 Subject: [PATCH 070/155] Update apps.json --- apps.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps.json b/apps.json index c41c1268e..81ecbb177 100644 --- a/apps.json +++ b/apps.json @@ -3517,7 +3517,7 @@ "name": "School Calendar", "shortName":"SCalendar", "icon": "CalenderLogo.png", - "version":"0.01", + "version":"0.02", "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 dfd9e59993944f6323120f8af660d880c63c6793 Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Sat, 20 Nov 2021 15:08:52 -0800 Subject: [PATCH 071/155] 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 36e8ad7c7..9a9abcbd3 100644 --- a/apps/schoolCalendar/custom.html +++ b/apps/schoolCalendar/custom.html @@ -420,7 +420,7 @@ draw(); // send finished app (in addition to contents of app.json) sendCustomizedApp({ storage: [ - { name: "myapp.app.js", url: "app.js", content: app }, + { name: "schoolCalendar.app.js", url: "app.js", content: app }, ] }); }); From d3ca682cff08be0bb2a64ab057f6efdf36b4e094 Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Sat, 20 Nov 2021 15:09:56 -0800 Subject: [PATCH 072/155] Update apps.json --- apps.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/apps.json b/apps.json index 81ecbb177..a6f8a9ee8 100644 --- a/apps.json +++ b/apps.json @@ -3517,13 +3517,14 @@ "name": "School Calendar", "shortName":"SCalendar", "icon": "CalenderLogo.png", - "version":"0.02", + "version":"0.01", "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", "custom":"custom.html", "storage": [ - {"name":"schoolCalendar.app.js"} + {"name":"schoolCalendar.app.js"}, + {"name":"schoolCalendar.img","url":"app-icon.js","evaluate":true} ], "data": [ {"name":"app.json"} From 92707b9d13ed269a41684ce45111376694cb6e58 Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Sat, 20 Nov 2021 15:12:50 -0800 Subject: [PATCH 073/155] Update ChangeLog.md --- apps/schoolCalendar/ChangeLog.md | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/schoolCalendar/ChangeLog.md b/apps/schoolCalendar/ChangeLog.md index c1cd00ff7..85a7cc698 100644 --- a/apps/schoolCalendar/ChangeLog.md +++ b/apps/schoolCalendar/ChangeLog.md @@ -1,2 +1 @@ -# Changelog for the bangle.hs-calenderapp repository: 0.01: App is created with gradient background. From 37da3c68aeeb5fec1ef3d8965fa3d8170074d791 Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Sat, 20 Nov 2021 15:19:46 -0800 Subject: [PATCH 074/155] Update custom.html --- apps/schoolCalendar/custom.html | 73 +++++++++++++++++++++++++++++++-- 1 file changed, 69 insertions(+), 4 deletions(-) diff --git a/apps/schoolCalendar/custom.html b/apps/schoolCalendar/custom.html index 9a9abcbd3..e1c2d0c03 100644 --- a/apps/schoolCalendar/custom.html +++ b/apps/schoolCalendar/custom.html @@ -1,15 +1,79 @@ + + + + +
+

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.

+
-

Some text:

-

Click

- - +
+ From 6c43de3766433c1f9fa1d01fd24cbfe0839e19c0 Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Sat, 20 Nov 2021 15:21:11 -0800 Subject: [PATCH 075/155] Update apps.json --- apps.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps.json b/apps.json index a6f8a9ee8..cf9c446fe 100644 --- a/apps.json +++ b/apps.json @@ -3517,7 +3517,7 @@ "name": "School Calendar", "shortName":"SCalendar", "icon": "CalenderLogo.png", - "version":"0.01", + "version":"0.02", "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 ebc2d8096144e6150920efc4b63e8f1e3f52eb00 Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Sat, 20 Nov 2021 15:33:27 -0800 Subject: [PATCH 076/155] Update custom.html --- apps/schoolCalendar/custom.html | 35 ++++++--------------------------- 1 file changed, 6 insertions(+), 29 deletions(-) diff --git a/apps/schoolCalendar/custom.html b/apps/schoolCalendar/custom.html index e1c2d0c03..d97f17066 100644 --- a/apps/schoolCalendar/custom.html +++ b/apps/schoolCalendar/custom.html @@ -1,35 +1,12 @@ - - - + - -
-

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 5625414e90c78677040420f342f29e96f088469b Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Sat, 20 Nov 2021 15:34:30 -0800 Subject: [PATCH 077/155] Update apps.json --- apps.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps.json b/apps.json index cf9c446fe..859f66b5c 100644 --- a/apps.json +++ b/apps.json @@ -3517,7 +3517,7 @@ "name": "School Calendar", "shortName":"SCalendar", "icon": "CalenderLogo.png", - "version":"0.02", + "version":"0.03", "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 aa20fac50ed19adfb9248fd9cda1ebb7c31f95b1 Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Sat, 20 Nov 2021 16:02:52 -0800 Subject: [PATCH 078/155] Update apps.json --- apps.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps.json b/apps.json index 859f66b5c..72ff998ee 100644 --- a/apps.json +++ b/apps.json @@ -3517,7 +3517,7 @@ "name": "School Calendar", "shortName":"SCalendar", "icon": "CalenderLogo.png", - "version":"0.03", + "version":"0.04", "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 1a6c1acce50fe2fe2e49bfc26b3ac98c1db5aa13 Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Sat, 20 Nov 2021 16:04:06 -0800 Subject: [PATCH 079/155] Update custom.html --- apps/schoolCalendar/custom.html | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/apps/schoolCalendar/custom.html b/apps/schoolCalendar/custom.html index d97f17066..8a214009e 100644 --- a/apps/schoolCalendar/custom.html +++ b/apps/schoolCalendar/custom.html @@ -1,7 +1,7 @@ - - + // + //

Create your events on the current week. Keep in note that your events repeat weekly.

@@ -467,6 +467,5 @@ draw(); }); -
From e206bd4e1cad4aab266fd376fa525500941878a8 Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Sat, 20 Nov 2021 16:08:00 -0800 Subject: [PATCH 080/155] Update custom.html --- apps/schoolCalendar/custom.html | 45 +-------------------------------- 1 file changed, 1 insertion(+), 44 deletions(-) diff --git a/apps/schoolCalendar/custom.html b/apps/schoolCalendar/custom.html index 8a214009e..86b1bae56 100644 --- a/apps/schoolCalendar/custom.html +++ b/apps/schoolCalendar/custom.html @@ -1,7 +1,6 @@ - // - // +

Create your events on the current week. Keep in note that your events repeat weekly.

@@ -11,50 +10,8 @@ +
From a1a387dd600f7e5c3879d74cd5a516d7566fb7bb Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Sat, 20 Nov 2021 17:47:52 -0800 Subject: [PATCH 089/155] Update custom.html --- apps/schoolCalendar/custom.html | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/apps/schoolCalendar/custom.html b/apps/schoolCalendar/custom.html index e9276b1bf..5353e4bf0 100644 --- a/apps/schoolCalendar/custom.html +++ b/apps/schoolCalendar/custom.html @@ -23,9 +23,11 @@ -

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 090/155] 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 091/155] 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 092/155] 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 093/155] 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 094/155] 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 095/155] 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 096/155] 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 097/155] 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 115/155] 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 116/155] 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 117/155] 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 118/155] 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 119/155] 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 120/155] 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 121/155] 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 122/155] 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 123/155] 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 124/155] 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 125/155] 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 126/155] 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 127/155] 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 128/155] 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 129/155] 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 130/155] 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 131/155] 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 132/155] 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 133/155] 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 134/155] 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 135/155] 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 136/155] 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) ================================ -[![Build Status](https://travis-ci.org/espruino/BangleApps.svg?branch=master)](https://travis-ci.org/espruino/BangleApps) +[![Build Status](https://app.travis-ci.com/espruino/BangleApps.svg?branch=master)](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 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 } + ]}); + + +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 + +![](screenshot1.png) +![](screenshot2.png) +![](screenshot3.png) +![](screenshot4.png) 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 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 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 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 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 -![](anaface.jpg) - -### Digital Clock Face -![](digiface.jpg) - -### Big Digit Clock Face -![](bigface.jpg) - -### Text Clock Face -![](txtface.jpg) - -### 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 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 GfB)IuGe&O! diff --git a/apps/multiclock/apps_entry.json b/apps/multiclock/apps_entry.json deleted file mode 100644 index 6383609c1..000000000 --- a/apps/multiclock/apps_entry.json +++ /dev/null @@ -1,19 +0,0 @@ -{ "id": "multiclock", - "name": "Multi Clock", - "icon": "multiclock.png", - "version":"0.06", - "description": "Clock with multiple faces - Big, Analogue, Digital, Text.\n Switch between faces with BT1 & BTN3", - "readme": "README.md", - "tags": "clock", - "type":"clock", - "allow_emulator":false, - "storage": [ - {"name":"multiclock.app.js","url":"clock.min.js"}, - {"name":"big.face.js","url":"big.min.js"}, - {"name":"ana.face.js","url":"ana.min.js"}, - {"name":"digi.face.js","url":"digi.min.js"}, - {"name":"txt.face.js","url":"txt.min.js"}, - {"name":"ped.face.js","url":"ped.js"}, - {"name":"multiclock.img","url":"multiclock-icon.js","evaluate":true} - ] - }, diff --git a/apps/multiclock/big.face.js b/apps/multiclock/big.face.js new file mode 100644 index 000000000..2db4ee4d4 --- /dev/null +++ b/apps/multiclock/big.face.js @@ -0,0 +1,31 @@ +(() => { + + function getFace(){ + + const W = g.getWidth(); + const H = g.getHeight(); + const F = 132*H/240; // reasonable approximation + + function drawTime() { + d = new Date() + g.reset(); + var da = d.toString().split(" "); + var time = da[4].substr(0, 5).split(":"); + var hours = time[0], + minutes = time[1]; + g.clearRect(0,24,W-1,H-1); + g.setColor(g.theme.fg); + g.setFont("Vector",F); + g.setFontAlign(0,-1); + g.drawString(hours,W/2,24,true); + g.setColor(g.theme.fg2); + g.drawString(minutes,W/2,12+H/2,true); + } + + + return {init:drawTime, tick:drawTime, tickpersecond:false}; + } + + return getFace; + +})(); \ No newline at end of file diff --git a/apps/multiclock/big.js b/apps/multiclock/big.js deleted file mode 100644 index 2e83d8fb5..000000000 --- a/apps/multiclock/big.js +++ /dev/null @@ -1,32 +0,0 @@ -(() => { - - function getFace(){ - - function drawTime(d) { - g.reset(); - var da = d.toString().split(" "); - var time = da[4].substr(0, 5).split(":"); - var hours = time[0], - minutes = time[1]; - g.clearRect(0,24,239,239); - g.setColor(1,1,1); - g.setFont("Vector",132); - g.drawString(hours,50,24,true); - g.drawString(minutes,50,132,true); - } - - function onSecond(){ - var t = new Date(); - if (t.getSeconds() === 0) drawTime(t); - } - - function drawAll(){ - drawTime(new Date()); - } - - return {init:drawAll, tick:onSecond}; - } - - return getFace; - -})(); \ No newline at end of file diff --git a/apps/multiclock/bigface.jpg b/apps/multiclock/bigface.jpg deleted file mode 100644 index 6857268644e9f09aca81bdd2b8ef32c9633b2f66..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 40453 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 diff --git a/apps/multiclock/clock.info b/apps/multiclock/clock.info new file mode 100644 index 000000000..441de1463 --- /dev/null +++ b/apps/multiclock/clock.info @@ -0,0 +1 @@ +{"id":"clock","name":"Clock","type":"clock","src":"clock.app.js","icon":"clock.img","version":"0.06","files":"clock.info,clock.app.js,big.face.js,ana.face.js,digi.face.js,txt.face.js"} \ No newline at end of file diff --git a/apps/multiclock/clock.js b/apps/multiclock/clock.js deleted file mode 100644 index 50410f096..000000000 --- a/apps/multiclock/clock.js +++ /dev/null @@ -1,69 +0,0 @@ -var FACES = []; -var STOR = require("Storage"); -STOR.list(/\.face\.js$/).forEach(face=>FACES.push(eval(require("Storage").read(face)))); -var lastface = STOR.readJSON("multiclock.json")||{pinned:0}; -var iface = lastface.pinned; -var face = FACES[iface](); -var intervalRefSec; - -function stopdraw() { - if(intervalRefSec) {intervalRefSec=clearInterval(intervalRefSec);} -} - -function startdraw() { - g.clear(); - g.reset(); - Bangle.drawWidgets(); - face.init(); - intervalRefSec = setInterval(face.tick,1000); -} - -function setButtons(){ - function newFace(inc){ - var n = FACES.length-1; - iface+=inc; - iface = iface>n?0:iface<0?n:iface; - stopdraw(); - face = FACES[iface](); - startdraw(); - } - function finish(){ - if (lastface.pinned!=iface){ - lastface.pinned=iface; - STOR.write("multiclock.json",lastface); - } - Bangle.showLauncher(); - } - setWatch(finish, BTN2, {repeat:false,edge:"falling"}); - setWatch(newFace.bind(null,1), BTN1, {repeat:true,edge:"rising"}); - setWatch(newFace.bind(null,-1), BTN3, {repeat:true,edge:"rising"}); -} - -var SCREENACCESS = { - withApp:true, - request:function(){ - this.withApp=false; - stopdraw(); - clearWatch(); - }, - release:function(){ - this.withApp=true; - startdraw(); - setButtons(); - } -}; - -Bangle.on('lcdPower',function(on) { - if (!SCREENACCESS.withApp) return; - if (on) { - startdraw(); - } else { - stopdraw(); - } -}); - -g.clear(); -Bangle.loadWidgets(); -startdraw(); -setButtons(); - diff --git a/apps/multiclock/digi.face.js b/apps/multiclock/digi.face.js new file mode 100644 index 000000000..21f339afc --- /dev/null +++ b/apps/multiclock/digi.face.js @@ -0,0 +1,38 @@ +(() => { + +function getFace(){ + + var W = g.getWidth(); + var H = g.getHeight(); + var scale = W/240; + + var buf = Graphics.createArrayBuffer(W,92,1,{msb:true}); + function flip() { + g.setColor(g.theme.fg); + g.drawImage({width:buf.getWidth(),height:buf.getHeight(),buffer:buf.buffer},0,H/2-34); + } + + var W = g.getWidth(); + var H = g.getHeight(); + + function drawTime() { + buf.clear(); + buf.setColor(1); + var d = new Date(); + var da = d.toString().split(" "); + var time = da[4]; + buf.setFont("Vector",54*scale); + buf.setFontAlign(0,-1); + buf.drawString(time,W/2,0); + buf.setFont("6x8",scale<1?1:2); + buf.setFontAlign(0,-1); + var date = d.toString().substr(0,15); + buf.drawString(date, W/2, 70*scale); + flip(); + } + return {init:drawTime, tick:drawTime, tickpersec:true}; +} + +return getFace; + +})(); \ No newline at end of file diff --git a/apps/multiclock/digi.js b/apps/multiclock/digi.js deleted file mode 100644 index 0b2ca4aaa..000000000 --- a/apps/multiclock/digi.js +++ /dev/null @@ -1,33 +0,0 @@ -(() => { - -var locale = require("locale"); - -function getFace(){ - - var buf = Graphics.createArrayBuffer(240,92,1,{msb:true}); - function flip() { - g.setColor(1,1,1); - g.drawImage({width:buf.getWidth(),height:buf.getHeight(),buffer:buf.buffer},0,85); - } - - function drawTime() { - buf.clear(); - buf.setColor(1); - var d = new Date(); - var da = d.toString().split(" "); - var time = da[4]; - buf.setFont("Vector",54); - buf.setFontAlign(0,-1); - buf.drawString(time,buf.getWidth()/2,0); - buf.setFont("6x8",2); - buf.setFontAlign(0,-1); - var date = locale.dow(d, 1) + " " + locale.date(d, 1); - buf.drawString(date, buf.getWidth()/2, 70); - flip(); - } - return {init:drawTime, tick:drawTime}; -} - -return getFace; - -})(); \ No newline at end of file diff --git a/apps/multiclock/digiface.jpg b/apps/multiclock/digiface.jpg deleted file mode 100644 index b0323bd55d1e9947e5573171b77d851445c5b8f9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 48073 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
+ + + + + diff --git a/apps/recorder/settings.js b/apps/recorder/settings.js new file mode 100644 index 000000000..2a9a7a0d8 --- /dev/null +++ b/apps/recorder/settings.js @@ -0,0 +1,4 @@ +(function(back) { + // just go right to our app - we need all the memory + load("record.app.js"); +})(); diff --git a/apps/recorder/widget.js b/apps/recorder/widget.js new file mode 100644 index 000000000..09893bbb7 --- /dev/null +++ b/apps/recorder/widget.js @@ -0,0 +1,222 @@ +(() => { + var storageFile; // file for GPS track + var entriesWritten = 0; + var activeRecorders = []; + var writeInterval; + + function loadSettings() { + var settings = require("Storage").readJSON("recorder.json",1)||{}; + settings.period = settings.period||10; + if (!settings.file || !settings.file.startsWith("recorder.log")) + settings.recording = false; + return settings; + } + + function getRecorders() { + var recorders = { + gps:function() { + var lat = 0; + var lon = 0; + var alt = 0; + var samples = 0; + var hasFix = 0; + function onGPS(f) { + hasFix = f.fix; + if (!hasFix) return; + lat += f.lat; + lon += f.lon; + alt += f.alt; + samples++; + } + return { + name : "GPS", + fields : ["Latitude","Longitude","Altitude"], + getValues : () => { + var r = ["","",""]; + if (samples) + r = [(lat/samples).toFixed(6),(lon/samples).toFixed(6),Math.round(alt/samples)]; + samples = 0; lat = 0; lon = 0; alt = 0; + return r; + }, + start : () => { + hasFix = false; + Bangle.on('GPS', onGPS); + Bangle.setGPSPower(1,"recorder"); + }, + stop : () => { + hasFix = false; + Bangle.removeListener('GPS', onGPS); + Bangle.setGPSPower(0,"recorder"); + }, + draw : (x,y) => g.setColor(hasFix?"#f00":"#888").drawImage(atob("DAyBAAACADgDuBOAeA4AzAHADgAAAA=="),x,y) + }; + }, + hrm:function() { + var bpm = 0, bpmConfidence = 0; + var hasBPM = false; + function onHRM(h) { + if (h.confidence >= bpmConfidence) { + bpmConfidence = h.confidence; + bpm = h.bpm; + if (bpmConfidence) hasBPM = true; + } + } + return { + name : "HR", + fields : ["Heartrate"], + getValues : () => { + var r = [bpmConfidence?bpm:""]; + bpm = 0; bpmConfidence = 0; + return r; + }, + start : () => { + hasBPM = false; + Bangle.on('HRM', onHRM); + Bangle.setHRMPower(1,"recorder"); + }, + stop : () => { + hasBPM = false; + Bangle.removeListener('HRM', onHRM); + Bangle.setHRMPower(0,"recorder"); + }, + draw : (x,y) => g.setColor(hasBPM?"#f00":"#888").drawImage(atob("DAyBAAAAAD/H/n/n/j/D/B+AYAAAAA=="),x,y) + }; + }, + steps:function() { + var lastSteps = 0; + return { + name : "Steps", + fields : ["Steps"], + getValues : () => { + var c = Bangle.getStepCount(), r=[c-lastSteps]; + lastSteps = c; + return r; + }, + start : () => { lastSteps = Bangle.getStepCount(); }, + stop : () => {}, + draw : (x,y) => g.reset().drawImage(atob("DAyBAAADDHnnnnnnnnnnjDmDnDnAAA=="),x,y) + }; + } + // TODO: recAltitude from pressure sensor + }; + /* eg. foobar.recorder.js + (function(recorders) { + recorders.foobar = { + name : "Foobar", + fields : ["foobar"], + getValues : () => [123], + start : () => {}, + stop : () => {}, + draw (x,y) => {} // draw 12x12px status image + } + }) + */ + require("Storage").list(/^.*\.recorder\.js$/).forEach(fn=>eval(fn)(recorders)); + return recorders; + } + + function writeLog() { + entriesWritten++; + WIDGETS["recorder"].draw(); + try { + var fields = [Math.round(getTime())]; + activeRecorders.forEach(recorder => fields.push.apply(fields,recorder.getValues())); + if (storageFile) storageFile.write(fields.join(",")+"\n"); + } catch(e) { + // If storage.write caused an error, disable + // GPS recording so we don't keep getting errors! + console.log("recorder: error", e); + var settings = loadSettings(); + settings.recording = false; + require("Storage").write("recorder.json", settings); + reload(); + } + } + + // Called by the GPS app to reload settings and decide what to do + function reload() { + var settings = loadSettings(); + if (writeInterval) clearInterval(writeInterval); + writeInterval = undefined; + + activeRecorders.forEach(rec => rec.stop()); + activeRecorders = []; + entriesWritten = 0; + + if (settings.recording) { + // set up recorders + var recorders = getRecorders(); // TODO: order?? + settings.record.forEach(r => { + var recorder = recorders[r]; + if (!recorder) { + console.log("Recorder for "+E.toJS(r)+"+not found"); + return; + } + var activeRecorder = recorder(); + activeRecorder.start(); + activeRecorders.push(activeRecorder); + // TODO: write field names? + }); + WIDGETS["recorder"].width = 15 + ((activeRecorders.length+1)>>1)*12; // 12px per recorder + // open/create file + if (require("Storage").list(settings.file).length) { // Append + storageFile = require("Storage").open(settings.file,"a"); + // TODO: what if loaded modules are different?? + } else { + storageFile = require("Storage").open(settings.file,"w"); + // New file - write headers + var fields = ["Time"]; + activeRecorders.forEach(recorder => fields.push.apply(fields,recorder.fields)); + storageFile.write(fields.join(",")+"\n"); + } + // start recording... + WIDGETS["recorder"].draw(); + writeInterval = setInterval(writeLog, settings.period*1000); + } else { + WIDGETS["recorder"].width = 0; + storageFile = undefined; + } + } + // add the widget + WIDGETS["recorder"]={area:"tl",width:0,draw:function() { + if (!writeInterval) return; + g.reset(); g.drawImage(atob("DRSBAAGAHgDwAwAAA8B/D/hvx38zzh4w8A+AbgMwGYDMDGBjAA=="),this.x+1,this.y+2); + activeRecorders.forEach((recorder,i)=>{ + recorder.draw(this.x+15+(i>>1)*12, this.y+(i&1)*12); + }); + },getRecorders:getRecorders,reload:function() { + reload(); + Bangle.drawWidgets(); // relayout all widgets + },setRecording:function(isOn) { + var settings = loadSettings(); + if (isOn && !settings.recording && require("Storage").list(settings.file).length) + return E.showPrompt("Overwrite\nLog 0?",{title:"Recorder",buttons:{Yes:"yes",No:"no"}}).then(selection=>{ + if (selection=="no") return false; // just cancel + if (selection=="yes") require("Storage").open(settings.file,"r").erase(); + // TODO: Add 'new file' option + return WIDGETS["recorder"].setRecording(1); + }); + settings.recording = isOn; + require("Storage").write("recorder.json", settings); + WIDGETS["recorder"].reload(); + return Promise.resolve(settings.recording); + }/*,plotTrack:function(m) { // m=instance of openstmap module + // if we're here, settings was already loaded + var f = require("Storage").open(settings.file,"r"); + var l = f.readLine(f); + if (l===undefined) return; + var c = l.split(","); + var mp = m.latLonToXY(+c[1], +c[2]); + g.moveTo(mp.x,mp.y); + l = f.readLine(f); + while(l!==undefined) { + c = l.split(","); + mp = m.latLonToXY(+c[1], +c[2]); + g.lineTo(mp.x,mp.y); + g.fillCircle(mp.x,mp.y,2); // make the track more visible + l = f.readLine(f); + } + }*/}; + // load settings, set correct widget width + reload(); +})() diff --git a/apps/s7clk/README.md b/apps/s7clk/README.md new file mode 100644 index 000000000..6b91abfe3 --- /dev/null +++ b/apps/s7clk/README.md @@ -0,0 +1,4 @@ +# Simple 7 Segment Clock + +![](screenshot_s7segment.png) + diff --git a/apps/s7clk/screenshot_s7segment.png b/apps/s7clk/screenshot_s7segment.png new file mode 100644 index 0000000000000000000000000000000000000000..a0386e540ffbcc711c41c9ec10213e6121dc6c8d GIT binary patch literal 2144 zcmeH}>pR;C7RTe)&`?FFsi7`$DW&Mru1h6Nk)*1w)u3)My=dLF8maql%VLda8J+4N zDmrZ?b(bJSmJ&MDQcbm#5VwkuGK8^Yc(?l>?2CPI&Uv2i^Z7pKJm~g``d#~2+2TX1+*Dz;-_lnG2Z=55f`rWaw5C}}u#reDsDOglGb%$!Nh%J<) zEsVPaY#djbYj_A(wc8F0`$tqC?jZL`d}5k|PQI2@&+{jaHkgYiH>Sb##7A>uJEZPx z$@j@2nuei`41YA=u-ncc&-dI%LoDCA%CSEb@Bk^(8~AAqzd^T55S^&}| zBVUHvbJ^vT`wMMPE*uH1vg#^dI-b-tSkItb-N{wlII$oF@*@!_ zJVB_HL7YYXAdBVaj(#rgp}pi~HLC*5HJzX>EK_AdJ@5&v;s|>Jub^(Knt)U}^$MkemB)nXAM;w{?HRP>L z9E@J2k&q&x%_soeP{Uz=sKw5WvW9Y18*=)(TVUYLl}E&s;(7Y@94#EWR5m|aQepL1 zCq=^Rz~VJkH>zyAZm#svZz>1$p; zp3W(zIYk#h09WVJy_X=MwQttAp$znK_N|A-5Fo5tsl^3=orDkRS<67*1!~_ZgsS-I zSDe2Iz)D)|XO7G3)jB&r=!dG<-o4lBriv!yWXt`JH6(mDy3OpOr@gneFJtKER*mHBgGqo>M8^k3*%iKonf*U=DzLk(vY&ZYa9ayu^6@%GI z^#350%}$w#SqET|ZO(BkuaC;4OMqeB8+g?S>YXfQ;ZT13$c$R0>4Pfmf4GJA@S62x-8 z$}ig2gzY<6n!X2u{Txl@jO6BqTk2zlEZ;YKvJm7@EA2&C+1S6^(OGZPT2e>yzG~}& zrQs$rC0)7`L9;9Bm)tmKQGi+JqFzd@@>j*XehE;NqNoJC0=9XPbtZqIEY_?_svlCn zNus^{K=n{OUl&M{y-aGif$h0qKXTl0fnPr0DkH>ZZGEaqq!oo%n~B^oS$u{+=ns~X zn_C-z7=hMogCz6KL@E^%CaYug;kJ!wx^;i(7u?#U+4P>O_84Fuc$MKp5WbI$cPQ#; z4K@!sLm3i^f_W^aHkromf54%dq>UlOE0kC?|6EL!yq9^%nCMf?fPzCaw-I1Q`_l!K z9r@XT1iaJ=7<_t45SR-ALTF_nHd@f#)`F0TPFV8m#MCd5Dz@b_9=#2^i)0OJNTywe zeUQuc*|@o4ozZ7S-KK*Vwm&-N^`fli%nBwplcp^M?75b(e?PL*6GQ8mP@NOw)|uuf zUJIC*E1i;_w4JTqLlB{@!J2oAhsh+u=}Cx@Zb3J{+!c{1wd`vAI7X3l=4ICrms!kB z>Y5jcE-*`TM!s6P5+rfrgbK;~r{ z)pTSCkW4ej~h zgBSO%+VHHweP|Cj3{`24-@2d!#+9Fzfm#Hi3UGvD|40981DncRto&^fT)lAbWq`O~ L+?}f(34i+&w))`% literal 0 HcmV?d00001 diff --git a/apps/sclock/ChangeLog b/apps/sclock/ChangeLog index 44a0ec504..dc76b8299 100644 --- a/apps/sclock/ChangeLog +++ b/apps/sclock/ChangeLog @@ -3,3 +3,4 @@ 0.04: Make this clock do 12h and 24h 0.05: setUI, screen size changes 0.06: Use Bangle.setUI for button/launcher handling +0.07: Update *on* the minute rather than every 15 secs diff --git a/apps/sclock/clock-simple.js b/apps/sclock/clock-simple.js index 8fb204d22..a399b05a7 100644 --- a/apps/sclock/clock-simple.js +++ b/apps/sclock/clock-simple.js @@ -1,4 +1,3 @@ -/* jshint esversion: 6 */ const big = g.getWidth()>200; const timeFontSize = big?6:5; const dateFontSize = big?3:2; @@ -14,7 +13,19 @@ const yposGMT = xyCenter*1.9; // Check settings for what type our clock should be var is12Hour = (require("Storage").readJSON("setting.json",1)||{})["12hour"]; -function drawSimpleClock() { +// timeout used to update every minute +var drawTimeout; + +// schedule a draw for the next minute +function queueDraw() { + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = setTimeout(function() { + drawTimeout = undefined; + draw(); + }, 60000 - (Date.now() % 60000)); +} + +function draw() { // get date var d = new Date(); var da = d.toString().split(" "); @@ -60,11 +71,18 @@ function drawSimpleClock() { var gmt = da[5]; g.setFont(font, gmtFontSize); g.drawString(gmt, xyCenter, yposGMT, true); + + queueDraw(); } -// handle switch display on by pressing BTN1 -Bangle.on('lcdPower', function(on) { - if (on) drawSimpleClock(); +// 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; + } }); // clean app screen @@ -74,8 +92,5 @@ Bangle.setUI("clock"); Bangle.loadWidgets(); Bangle.drawWidgets(); -// refesh every 15 sec -setInterval(drawSimpleClock, 15E3); - // draw now -drawSimpleClock(); +draw(); diff --git a/apps/sclock/screenshot_simplec.png b/apps/sclock/screenshot_simplec.png new file mode 100644 index 0000000000000000000000000000000000000000..a12db3ec82a8b9443b565eca26eeed33a53b1a79 GIT binary patch literal 2217 zcmc&$iBr?}694{wA%p~Iz<>dy5sM0l1nNA5a>!9MfDaIlfO17?^$7@#60V2@D;~87 zSP)C`A?1(=0R$ox4GD@+Jg^*ra0diALc(Rj5fYmB{(|@By_xRJ?#|Bc?Ck9EnZ0$y zm!yp`!T3 zpyTFdr?ahd;clPPPrpHjkI@KiHN+rIWPbOURH33*DdtCxr{V!#Y)x|~7jnB-qP8Fw>cpl`w1oxq;a0H+PS`iKq5PwU_Kz=r zaTZrq@yvKuH=%|r1>60bJFki1gkOpyx}rrVWpeb0Xj6AMxbCKNOGv%WN!Ms+fjn{z zB|OL+&QYKjcVVfRh2(8|ya>)@j6A-{^D<$Tlo7_NrL0(}0T=9jL(7_csNf1Np3l6R z*U+i?P3zgrpTZy@i)_8)rKd6M9_$PSVIxOwG~H9|bT&!%MZUmN!4T7F@CBxrP{fRq zP1TX*iix55V^i5MH_<3tVV+sy+Gj^S*}V8Y?2ja_ z?Iz~w$^Chc8IyYj3ZHyi`#5u*6ATxNANKsNoYAMiexqM(aXJPVV?}i^2yRT?iRIZG zIRydY@PWH1ih(%`fZXWq?FbvKY%*XRuFsf&4Ai|X0k33`v;?#eCHg?vB{oe2*HgPN z#n90D4!zqB{VrHq_Tf0K!yy5Jx5dl;$wh3G(9-V7dV!k< zIKXYG+V~U>2ep+q6EE(#sCimvm8Zs#$2txnd2MHW?NV`c<%ib;t5MIM%~rR*tL&S# zUOKOH>ok9|yD}19QF9m!w{jSiR60~pYenpJ1SRrDK9<_69w^Q~@4Y`NTrKHz9>80j|sD0Y2 z-Axz=_T{@a6+_5UTO#198om8x4049(2-Jn$Pm=NXdmB^@0LIUkXB$y>f(x53`wY&% zwsx$P{`MkC^X$9#Mtwa$UeB^Ur;kvL_YTd{gq%yxtgs1Ou5D%aQYYJSfYew$7>R5# z>cVURLHfyq-`c%449y$O35yF>)u=oJN2b5s3Zm}KzFB7$I+a2daAIEurp|X~OogBP zW_3RZ%3xMV4@K0r8Wc94BZB9mzZbWg=HY+4Y08GGPo&m=j#~}10ZNu8?ml|X*lOyd z>4CI{H2WF1iSRg&<@-D|&R%-=l_5q=Z@Mu}qb1xDQ@jW8m>0*RRIB84iaK8Mmz&*) z=L-GkiK;Zx?L6HWej{f9st$PKCIeBpBzs=(sI6ePq#`|vpC$F8(uPMiII{g4<7Cy< zjq$aSl%VkIi06KXZM}Ww6fL#uq6gU=C5uNFvDs_f9J`b%j$J{SGjP9+)Ar}( zLJhWGuDD9!vpu3WqOkt@ahasHcPu@U1Bw}X zbeRjET9xu`3Z6$cIiZXQnK2AG2RrH?*Dl|e3JLmGN4$R0{&DHi>!b2>sKCjp17puc z-tVU!a02E-7%(1ORe!e~wNTntw%o%}&ej$4t>>Gjzj``eN^6Q{1+2Y4g%bMiVy$F$ zYQEk^$|)kwU2p-GyH1i))sWEzJhZLw%xNS@82ji_7z?hr9py})fQQNj~wokJ0T0`4qal8;P6WL(F(U9|jGl!EFmnz#9+M0bj>h2#Uk z4RzU4&h#+p!i4ngr6a^69tuBFF386g&veEeuun&AUBg=9EFI~?gK|`$E zlfXFr@c#g*|3z9?Lk25m&EKVcH0e#|?3qtKKlg5|B$g%Zr&pBb?889q74a-9lK65uWS%6@%aMR^7clGQH11Q7s90w41y+ z8~gQS(IH*2UQwY`5=D^^H4qCWTndN~OE?T=})^_(LgRyBlv zK4sYJh~{Q<$&HRx>l(-0OWNn1!bGP-#R+dO;C9qO8zDDamsfc>prcm12gv&0nWTW@ zMhPfT+Lp0HgQi}^0Sw=kyipjN+5b79PcG)$F)S{%!2v~Te;I>E`vd@}_O;6g(B(LZ z1B3*nt0AQ08VUn7v0wBcbP)d(-L;?IF4fMwIQP;DAP7OIp%*B6!=eQfR?42{9E&j? z!gV4kUB%d5B*pm(HhloUlWr;Uq^YoB9itn7Kue}P8Z~-*?E2bSYZIcgXk|=0oLm+x z_H4n#@@3wE&UdyQ+(z6A&18Ar)19^1&~kcKEyiSkeT`kJ6xy?I`c9SX265&M5gv48 zrk-uMP>sCsM#6CEua%bo>XliI?j|e893>kuJGK8=h7n8$s!{ko5vFRrju%Elbp@5C z0y!#fmt=mJ-)y>99H{pM83A{=!{+Ud)@&}t&{T8LF&BA#X`CQ_PEm$jZ0B}!;vqXD z(94i7DweX!2c2VvK`)D7|7f9PpUV`_o7pGs$g(+~IUdvVl)w0L3$I$O?(X|TF?@lm z-BGnUr;w^&_)k%@w{HM6-rE?yr7I)3YLJAS%=p7M(wnBph7a`DDwPvdpMr2mBj?;=cR%^$C{@`8uG}EBH^LW!F z2k_eb40l;FC8T6mtww-n96Dfl44~^-WQz>|5m+^%iKcHA>w)$S0+FwTHUZNS?!#0k9WxW^g%n_MzGwDO+d_?!@*NIll^;GIi z3R-qD$P#7`zm{EMfRTBja)&xSj4C{OW6PIPt-S)oqPWAuz+(4+hEaG&I zP9g;xH9=s&?9?NLou=2SN36LIi1S|_p4^%yOX153zsD^TA<{+rAAZCoHN7k{2#Du5 z#`}bE$AY)ZtI~0rtxyR@-e}Lvi)m|HoaRyaN{0~3RWow!c=bs7*%7CZ8NJ<2)W3V^ z!^O(c&Ci~n0SI18&$HC^b^uLR3z;eKaP;Vgsmq3TI!zTauH+(gAY*zZC6grOhNYS{ zJFG>O$x|pDTLNyK96NPnsd$N4|Lubca3Xf8FxI)cq84`bXU%SI7z~7UbYB&aKv)}J e+(G!4$IH6DqRVPDJzuVRYryRgh0Gz*vi}6(DCm;_ literal 0 HcmV?d00001 diff --git a/apps/setting/ChangeLog b/apps/setting/ChangeLog index 49915ee21..faa50405f 100644 --- a/apps/setting/ChangeLog +++ b/apps/setting/ChangeLog @@ -32,4 +32,7 @@ 0.27: Add Theme menu 0.28: Update Quiet Mode widget (if present) 0.29: Add Customize to Theme menu -0.30: Move '< Back' to the top of menus \ No newline at end of file +0.30: Move '< Back' to the top of menus +0.31: Remove Bangle 1 settings when running on Bangle 2 +0.32: Fix 'beep' menu on Bangle.js 2 +0.33: Really fix 'beep' menu on Bangle.js 2 this time diff --git a/apps/setting/settings-icon.js b/apps/setting/settings-icon.js index 7b68f80c0..abc7a3060 100644 --- a/apps/setting/settings-icon.js +++ b/apps/setting/settings-icon.js @@ -1 +1 @@ -require("heatshrink").decompress(atob("mEwghC/AFEiAAgX/C/4SFkADBgQXFBIgECAAYSCkAWGBIoXGyQTHABBZLkUhiMRiQXLIQwVBAAZlIC44tCAAYxGIxIWFGA4XIFwwwHXBAWHGAwXHFxAwGPAYXTX44XDiAJBgIXGyDAHFAYKDMAq+EGAgXNCwwX/C453XU6IWHa6ZFCC6JJCC4hgEAAoOEC5AwIFwhgEBAgwIBoqmGGBIuFVAgXFGAwLFYAoLFGIYtFeA4MGABMpC4pICkBMGBIpGFC4SuIBIoWFAAxZLC/4X/AFQ")) +require("heatshrink").decompress(atob("mEw4UA///+Nj5lCt9TH+cBqtVoALWqALTgoLUiALFgoLDqoBBAAQGCHAdRBYdFKwZECqv614ECGQQsCr2q1W1BYkVAoPqBYOrAoNUBYdXBQIAB6oLDEQgkEBYdaBYelBYt6BYetBYYvBtWq0EK1WpF4ZfBIwIFBJATCDBY6PDiuq1AEBlWqBdA7KKZZrLQZabNWZLLLcZb7LBYVV/WvAgRfCNYNRBAVVoq/FJQRECR4gnBEwQEBggLDGQg4CBag4DBaBWBBaoATA")) diff --git a/apps/setting/settings.js b/apps/setting/settings.js index a0e535df7..fcf651b6f 100644 --- a/apps/setting/settings.js +++ b/apps/setting/settings.js @@ -1,6 +1,7 @@ Bangle.loadWidgets(); Bangle.drawWidgets(); +const BANGLEJS2 = process.env.HWVERSION==2; const storage = require('Storage'); let settings; @@ -37,7 +38,7 @@ function resetSettings() { quiet: 0, // quiet mode: 0: off, 1: priority only, 2: total silence timeout: 10, // Default LCD timeout in seconds vibrate: true, // Vibration enabled by default. App must support - beep: "vib", // Beep enabled by default. App must support + beep: BANGLEJS2?true:"vib", // Beep enabled by default. App must support timezone: 0, // Set the timezone for the device HID: false, // BLE HID mode, off by default clock: null, // a string for the default clock's name @@ -71,8 +72,37 @@ if (!('qmOptions' in settings)) settings.qmOptions = {}; // easier if this alway const boolFormat = v => v ? "On" : "Off"; function showMainMenu() { - var beepV = [false, true, "vib"]; - var beepN = ["Off", "Piezo", "Vibrate"]; + var beepMenuItem; + if (BANGLEJS2) { + beepMenuItem = { + value: settings.beep!=false, + format: boolFormat, + onchange: v => { + settings.beep = v; + updateSettings(); + if (settings.beep) { + analogWrite(VIBRATE,0.1,{freq:2000}); + setTimeout(()=>VIBRATE.reset(),200); + } // beep with vibration moter + } + }; + } else { // Bangle.js 1 + var beepV = [false, true, "vib"]; + var beepN = ["Off", "Piezo", "Vibrate"]; + beepMenuItem = { + value: Math.max(0 | beepV.indexOf(settings.beep),0), + min: 0, max: beepV.length-1, + format: v => beepN[v], + onchange: v => { + settings.beep = beepV[v]; + if (v==1) { analogWrite(D18,0.5,{freq:2000});setTimeout(()=>D18.reset(),200); } // piezo on Bangle.js 1 + else if (v==2) { analogWrite(VIBRATE,0.1,{freq:2000});setTimeout(()=>VIBRATE.reset(),200); } // vibrate + updateSettings(); + } + }; + } + + const mainmenu = { '': { 'title': 'Settings' }, '< Back': ()=>load(), @@ -87,17 +117,7 @@ function showMainMenu() { updateSettings(); } }, - 'Beep': { - value: 0 | beepV.indexOf(settings.beep), - min: 0, max: 2, - format: v => beepN[v], - onchange: v => { - settings.beep = beepV[v]; - if (v==1) { analogWrite(D18,0.5,{freq:2000});setTimeout(()=>D18.reset(),200); } // piezo - else if (v==2) { analogWrite(D13,0.1,{freq:2000});setTimeout(()=>D13.reset(),200); } // vibrate - updateSettings(); - } - }, + 'Beep': beepMenuItem, 'Vibration': { value: settings.vibrate, format: boolFormat, @@ -119,6 +139,7 @@ function showMainMenu() { 'Reset Settings': ()=>showResetMenu(), 'Turn Off': ()=>{ if (Bangle.softOff) Bangle.softOff(); else Bangle.off() }, }; + return E.showMenu(mainmenu); } @@ -144,7 +165,7 @@ function showBLEMenu() { } }, 'HID': { - value: 0 | hidV.indexOf(settings.HID), + value: Math.max(0,0 | hidV.indexOf(settings.HID)), min: 0, max: 3, format: v => hidN[v], onchange: v => { @@ -191,7 +212,7 @@ function showThemeMenu() { 'Light BW': ()=>{ upd({ fg:cl("#000"), bg:cl("#fff"), - fg2:cl("#00f"), bg2:cl("#0ff"), + fg2:cl("#000"), bg2:cl("#cff"), fgH:cl("#000"), bgH:cl("#0ff"), dark:false }); @@ -356,7 +377,10 @@ function showLCDMenu() { settings.options.wakeOnBTN1 = !settings.options.wakeOnBTN1; updateOptions(); } - }, + } + }; + if (!BANGLEJS2) + Object.assign(lcdMenu, { 'Wake on BTN2': { value: settings.options.wakeOnBTN2, format: boolFormat, @@ -372,7 +396,8 @@ function showLCDMenu() { settings.options.wakeOnBTN3 = !settings.options.wakeOnBTN3; updateOptions(); } - }, + }}); + Object.assign(lcdMenu, { 'Wake on FaceUp': { value: settings.options.wakeOnFaceUp, format: boolFormat, @@ -427,7 +452,7 @@ function showLCDMenu() { updateOptions(); } } - } + }); return E.showMenu(lcdMenu) } function showQuietModeMenu() { diff --git a/apps/simplest/ChangeLog b/apps/simplest/ChangeLog index d69da4ddc..f37015d6a 100644 --- a/apps/simplest/ChangeLog +++ b/apps/simplest/ChangeLog @@ -1,2 +1,3 @@ 0.01: Modified for use with new bootloader and firmware 0.02: Use Bangle.setUI for button/launcher handling +0.03: Fix display for Bangle 2 diff --git a/apps/simplest/app.js b/apps/simplest/app.js index 2ed4e5580..68564ff33 100644 --- a/apps/simplest/app.js +++ b/apps/simplest/app.js @@ -1,14 +1,17 @@ +const h = g.getHeight(); +const w = g.getWidth(); + function draw() { var d = new Date(); var da = d.toString().split(" "); var time = da[4].substr(0,5); g.reset(); - g.clearRect(0, 30, 239, 99); + g.clearRect(0, 30, w, 99); g.setFontAlign(0, -1); - g.setFont("Vector", 80); - g.drawString(time, 120, 40); + g.setFont("Vector", w/3); + g.drawString(time, w/2, 40); } // handle switch display on by pressing BTN1 diff --git a/apps/simplest/screenshot_simplest.png b/apps/simplest/screenshot_simplest.png new file mode 100644 index 0000000000000000000000000000000000000000..9affc3d1cbb9be291270cb57cd1474e03b685ea3 GIT binary patch literal 2180 zcmeHJ`!m~#7LSNFq7QG4TD79mCYD=m?;?bf2t`|^K}kwI(xOFMA!V(vUOPogqtPx) zwBp$kY0?!DtKZ#A>(yv(SCxupNUsVNgnHLaXZH8|2i$vR&iR}<^O^a~oSE~PlXvO_ zX(z%Q0f9hv`uY$~YmoOf;oCH|S*W0DK?8cLOw|IFED~c|;Q4-U9~) zer&36Ls`wW^pT=>0|PeRk*>q{zySHX*^Cw=)L&!OKwFX}`itSpWNT98-xPg%uxZ<5 zs4MIF3-B+3s;8nnb6!6g&&Cpa+netS1h0@1+9x^o^0CFRvPq};>_>>_ZU z^1+0+VpY!T_nuaB-G<4tc0hj`xNIi=Nb0$X6B|dI`!*XiiH{KIX!W3AIF)I_YHC0? zbzFaCdNd5rTTD0tqjs>*4Q6j_N5VJGd`Wap>^nK$QqHQy7kg+re6g|DC0~!VyRF}$ zs~d`J@2uK8Z5(?}GCJSdq&tTthCA$jc@WOi#vUksati`=@H!c#qXi?MvWZ4&!Kgp6 z|3E!L|0sk!f72+0wg`jAzle+B=E23oV;>ea@8C&%B^1r0nM8 zj{NDAH%t<8(mwMdaQM{Y0)|fDd%PJ!m6*RfW70f=EWgz*_4EDvM7(22ZUfb9eyW$U z2A9r5)rc5$JIRf7+o#yyL0S+xWI+-2!RS5HBLL3q0QT~c1e>I5oBgs!cZ)U+tc$FNDaHL27xA$IjEyF>+Y?g%2qrelFW z)-KN!oDW9fX!kErM5lo?mQdZY$Crc0_b!XFyvOaOZC7HUMSr{mgLPl&o*d*@qtD*h zp|&SeM6aQwR~T{j`9lKXSbx={^{M`XH>fh9-&5}BK>!ts>t6RQ(hr-b#gvLZH$LNz z^1%@1@?ITwspu=E7(^|23ma#+j#sJZ_R$xT8^kK&-M~u%3G#-?Xy%yPt0R-FC6w=o6mQcEU8hw(~o#iq-) zJkx57KP%jg`l*f|i5M$sOGL3tkeoy^VTuV2W8b>0M{SpgGTak# zL6mh`f#X`vq!PUZKg5%iBebwuu$J-KpUWW7$9cuH{n;RS)Hq1v-A4l#8e#fx^M5%X zuQ4p!#XN#^;Ro4;+U8O|1uO zZL`7nDn*!vVe);~pBkGBV7dNZ?0;vCD`qy|{M+>()y}q>w*ulzJVD@kM&|qnB+=s~ literal 0 HcmV?d00001 diff --git a/apps/slomoclock/ChangeLog b/apps/slomoclock/ChangeLog new file mode 100644 index 000000000..cfab5da55 --- /dev/null +++ b/apps/slomoclock/ChangeLog @@ -0,0 +1,2 @@ +0.01: Created app +0.10: Different colour schemes selectable in SloMo Clock settings. diff --git a/apps/slomoclock/README.md b/apps/slomoclock/README.md new file mode 100644 index 000000000..9a6bbbdd2 --- /dev/null +++ b/apps/slomoclock/README.md @@ -0,0 +1,6 @@ +# SloMo Clock + +Simple 24h clock with large digits. + +![](Screenshot.JPG) + diff --git a/apps/slomoclock/app-icon.js b/apps/slomoclock/app-icon.js new file mode 100644 index 000000000..22e264124 --- /dev/null +++ b/apps/slomoclock/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("oFAwhC/ABOIABgfymYAKD+Z/9hGDL5c4wAf/XzjASTxqgQhAfPMB2IPxiACIBo+BDxqACIBg+CLxpANHwQPBABgvCIBT8CJ5owDD5iPOOAQfLBojiDCYQGFGIQfICIQfdBYJNMOI6SHD8jeNOIYzID8hfRD9LfEAoTdFBIifLAAIffBoQRBAJpxMD84JCD+S/GL56fID8ALBb6ZhID8qtJCZ4fgT4YDBABq/PD7RNEL6IRKD8WID5pfCD5kzNhKSFmYfMBwSeOGBoPDABgvCJ5wAON5pADABivPIAIAOd5xABABweOD4J+OD58IQBj8LD/6gUDyAfhXzgfiP/wA2")) diff --git a/apps/slomoclock/app.js b/apps/slomoclock/app.js new file mode 100644 index 000000000..e3933af1b --- /dev/null +++ b/apps/slomoclock/app.js @@ -0,0 +1,118 @@ +/* +Simple watch [slomoclock] +Mike Bennett mike[at]kereru.com +0.01 : Initial +0.03 : Use Layout library +*/ + +var v='0.10'; + +// Colours +const col = []; +col[2] = 0xF800; +col[3] = 0xFAE0; +col[4] = 0xF7E0; +col[5] = 0x4FE0; +col[6] = 0x019F; +col[7] = 0x681F; +col[8] = 0xFFFF; + +const colH = []; +colH[0]= 0x001F; +colH[1]= 0x023F; +colH[2]= 0x039F; +colH[3]= 0x051F; +colH[4]= 0x067F; +colH[5]= 0x07FD; +colH[6]= 0x07F6; +colH[7]= 0x07EF; +colH[8]= 0x07E8; +colH[9]= 0x07E3; +colH[10]= 0x07E0; +colH[11]= 0x5FE0; +colH[12]= 0x97E0; +colH[13]= 0xCFE0; +colH[14]= 0xFFE0; +colH[15]= 0xFE60; +colH[16]= 0xFC60; +colH[17]= 0xFAA0; +colH[18]= 0xF920; +colH[19]= 0xF803; +colH[20]= 0xF80E; +colH[21]= 0x981F; +colH[22]= 0x681F; +colH[23]= 0x301F; + +// Colour incremented with every 10 sec timer event +var colNum = 0; +var lastMin = -1; + +var Layout = require("Layout"); +var layout = new Layout( { + type:"h", c: [ + {type:"v", c: [ + {type:"txt", font:"40%", label:"", id:"hour", valign:1}, + {type:"txt", font:"40%", label:"", id:"min", valign:-1}, + ]}, + {type:"v", c: [ + {type:"txt", font:"10%", label:"", id:"day", col:0xEFE0, halign:1}, + {type:"txt", font:"10%", label:"", id:"mon", col:0xEFE0, halign:1}, + ]} + ] +}, {lazy:true}); + +// update the screen +function draw() { + var date = new Date(); + + // Update time + var timeStr = require("locale").time(date,1); + var hh = parseFloat(timeStr.substring(0,2)); + var mm = parseFloat(timeStr.substring(3,5)); + + // Surprise colours + if ( lastMin != mm ) colNum = Math.floor(Math.random() * 24); + lastMin = mm; + + layout.hour.label = timeStr.substring(0,2); + layout.min.label = timeStr.substring(3,5); + + // Mysterion (0) different colour each hour. Surprise (1) different colour every 10 secs. + layout.hour.col = cfg.colour==0 ? colH[hh] : cfg.colour==1 ? colH[colNum] : col[cfg.colour]; + layout.min.col = cfg.colour==0 ? colH[hh] : cfg.colour==1 ? colH[colNum] :col[cfg.colour]; + + // Update date + layout.day.label = date.getDate(); + layout.mon.label = require("locale").month(date,1); + + layout.render(); +} + +// Events + +// Stop updates when LCD is off, restart when on +Bangle.on('lcdPower',on=>{ + if (secondInterval) clearInterval(secondInterval); + secondInterval = undefined; + if (on) { + secondInterval = setInterval(draw, 10000); + draw(); // draw immediately + } +}); + +var secondInterval = setInterval(draw, 10000); + +// Configuration +let cfg = require('Storage').readJSON('slomoclock.json',1)||{}; +cfg.colour = cfg.colour||0; // Colours + +// update time and draw +g.clear(); +draw(); + +// Show launcher when middle button pressed +Bangle.setUI("clock"); + +// Load widgets +Bangle.loadWidgets(); +Bangle.drawWidgets(); diff --git a/apps/slomoclock/settings.js b/apps/slomoclock/settings.js new file mode 100644 index 000000000..af67069dc --- /dev/null +++ b/apps/slomoclock/settings.js @@ -0,0 +1,38 @@ +(function(back) { + + let settings = require('Storage').readJSON('slomoclock.json',1)||{}; + + function writeSettings() { + require('Storage').write('slomoclock.json',settings); + } + + function setColour(c) { + settings.colour = c; + writeSettings(); + } + + const appMenu = { + '': {'title': 'SloMo Clock'}, + '< Back': back, + 'Colours' : function() { E.showMenu(colMenu); } + //,'Widget Space Top' : {value : settings.widTop, format : v => v?"On":"Off",onchange : () => { settings.widTop = !settings.widTop; writeSettings(); } + //,'Widget Space Bottom' : {value : settings.widBot, format : v => v?"On":"Off",onchange : () => { settings.widBot = !settings.widBot; writeSettings(); } + }; + + const colMenu = { + '': {'title': 'Colours'}, + '< Back': function() { E.showMenu(appMenu); }, + 'Mysterion' : function() { setColour(0); }, + 'Surprise' : function() { setColour(1); }, + 'Red' : function() { setColour(2); }, + 'Orange' : function() { setColour(3); }, + 'Yellow' : function() { setColour(4); }, + 'Green' : function() { setColour(5); }, + 'Blue' : function() { setColour(6); }, + 'Violet' : function() { setColour(7); }, + 'White' : function() { setColour(8); } + }; + + E.showMenu(appMenu); + +}); diff --git a/apps/slomoclock/watch.png b/apps/slomoclock/watch.png new file mode 100644 index 0000000000000000000000000000000000000000..b77f302d5291d39650ffd4c5400d0a7dd3e30d70 GIT binary patch literal 1439 zcmV;Q1z`G#P)ZZKIyp>XUbK#j8aEHE7Ds0zs-Qe=?maLsuD zcwH7zo-85*pPUNHrrgZ`eM~B!Lz#R329Fd4q?A-M035B_i!(14%b?7NZzmsOB(?~JQq2Hvi}HypfQSL?Z2&l2ULZ_o zg+qaL8-!-vwitv1pv#|LDDtV;SL79?vqWW)7sdH`07Fnl8VCo#kpJO8fxNdW6EFY= z%l80)kp{wij?s8|GN7v7I{+#~JK_+3YT9bUtq*JBP6-?m=93VkXj=a=!t=$vcL13+R`L*M8e?$7=E zR0)1v31I3q9xl!|L4C>F+7N)>qhMlw8GU1O(1^eoyEN2R*(Kxs+`J3Kj`vPq)QCE- zj?bf|t_%lC^U>N6z^B7A;9ZtGng#qG{kDPV0<@Hi&I15rG}Knv#TdJ!r3eGjg-w0p zg{1;ccK=qJf^vcwJDGmXkbS!Iq41PxAR--ATaG=p?_-RHh`K^Ou};y{GYvo}92?X? zr!86Bc=B7>3{MHuSSUbuzV5jKR2Td3_5FWrk!E_r+jU<$(v)Pne`qUl&K@eoD;0&p z9m^6-_YbA)2Uz8<+c5Se?d(qF_5@{+$jV(_$WWfG09?B_kLqHdsI46d^8he52%0NtVRREr&>#+Z@)vNV}NW(Tno5*=D%GKUkmWOTLadkx!v}5ucsrMYk}ePKP={9tMkm8@Yd~E zwU<=~WQN=|V{8idvY={d+dx^FjR^heGZAfZwtTQ-FP;P>i> zqQ`ruFS;6p_0yq^eQ*j#0Z1fM0Dz|25~vCT5p}v2gmveMs8dxKnrcfn^@$f;9X8em z0l(+}^TEl5W%T_rhc$9{B2XE_J9VXascesaLVPEVcp?da-HLPskJ;tnQh=tdyaix1 zo=D=epW}G4qM@cFAMe(e2}_X&ey@s_`m(3a-_@Nbuqa47%frPj+0d2Qa;zn@ULXO; zsS= z_SOj-_-5iEZci_Q7%mLASA8tPC0;4Ylzsphk1xTEiI4#NI=5sP?-%704#3T}eLn*1 zkH;$-rXR10(ph5W(JCG-YXBiO+)??HD4+ORfH4B-O8~eRw>~}-#rWU44N`DbMrDee z?**9BuTbjzNNf=!u|*k_c8m(kACUFg91#ok#GJj1J4B>CsO%U8Y%ExE?W-|Kg;}{h tLD__^eKjTtG8N$3{-Nx%fgE--{sVHTSvKO1ec%89002ovPDHLkV1gjPojw2n literal 0 HcmV?d00001 diff --git a/apps/speedalt/settings.js b/apps/speedalt/settings.js index 488ba3b81..63d77971e 100644 --- a/apps/speedalt/settings.js +++ b/apps/speedalt/settings.js @@ -32,7 +32,7 @@ const appMenu = { - '': {'title': 'GPS Speed Alt'}, + '': {'title': 'GPS Adv Sprt'}, '< Back': back, '< Load GPS Adv Sport': ()=>{load('speedalt.app.js');}, 'Units' : function() { E.showMenu(unitsMenu); }, diff --git a/apps/speedalt2/ChangeLog b/apps/speedalt2/ChangeLog new file mode 100644 index 000000000..91f01988e --- /dev/null +++ b/apps/speedalt2/ChangeLog @@ -0,0 +1,2 @@ +0.01: Initial import. +0.07: Add swipe to change screens. diff --git a/apps/speedalt2/README.md b/apps/speedalt2/README.md new file mode 100644 index 000000000..30a706b7b --- /dev/null +++ b/apps/speedalt2/README.md @@ -0,0 +1,134 @@ +# GPS Speed, Altimeter and Distance to Waypoint + +What is the difference between **GPS Adventure Sports** and **GPS Adventure Sports II** ? + +**GPS Adventure Sports** has 3 screens, each of which display different sets of information. + +**GPS Adventure Sports II** has 5 screens, each of which displays just one of Speed, Altitude, Distance to waypoint, Position or Time. + +In all other respect they perform the same functions. + +The waypoints list is the same as that used with the [GPS Navigation](https://banglejs.com/apps/#gps%20navigation) app so the same set of waypoints can be used across both apps. Refer to that app for waypoint file information. + +## Buttons and Controls + +**BTN1** ( Speed and Altitude ) Short press < 2 secs toggles the display between last reading and maximum recorded. Long press > 2 secs resets the recorded maximum values. + +**BTN1** ( Distance ) Select next waypoint. Last fix distance from selected waypoint is displayed. + +**BTN2** : Disables/Restores power saving timeout. Locks the screen on and GPS in SuperE mode to enable reading for longer periods but uses maximum battery drain. Red LED (dot) at top of screen when screen is locked on. Press again to restore power saving timeouts. + +**BTN3** : Cycles the screens between Speed, Altitude, Distance to waypoint, Position and Time + +**BTN3** : Long press exit and return to watch. + +**Touch Screen** If the 'Touch' setting is ON then : + +Swipe Left/Right cycles between the five screens. + +Touch functions as BTN1 short press. + + +## App Settings + +Select the desired display units. Speed can be as per the default locale, kph, knots, mph or m/s. Distance can be km, miles or nautical miles. Altitude can be feet or metres. Select one of three colour schemes. Default (three colours), high contrast (all white on black) or night ( all red on black ). + +## Kalman Filter + +This filter smooths the altitude and the speed values and reduces these values 'jumping around' from one GPS fix to the next. The down side of this is that if these values change rapidly ( eg. a quick change in altitude ) then it can take a few GPS fixes for the values to move to the new values. Disabling the Kalman filter in the settings will cause the raw values to be displayed from each GPS fix as they are found. + +## Loss of fix + +When the GPS obtains a fix the number of satellites is displayed as 'Sats:nn'. When unable to obtain a fix then the last known fix is used and the age of that fix in seconds is displayed as 'Age:nn'. Seeing 'Sats' or 'Age' indicates whether the GPS has a current fix or not. + +## Power Saving + +The The GPS Adv Sport app obeys the watch screen off timeouts as a power saving measure. Restore the screen as per any of the colck/watch apps. Use BTN2 to lock the screen on but doing this will use more battery. + +This app will work quite happily on its own but will use the [GPS Setup App](https://banglejs.com/apps/#gps%20setup) if it is installed. You may choose to use the GPS Setup App to gain significantly longer battery life while the GPS is on. Please read the Low Power GPS Setup App Readme to understand what this does. + +When using the GPS Setup App this app switches the GPS to SuperE (default) mode while the display is lit and showing fix information. This ensures that that fixes are updated every second or so. 10 seconds after the display is blanked by the watch this app will switch the GPS to PSMOO mode and will only attempt to get a fix every two minutes. This improves power saving while the display is off and the delay gives an opportunity to restore the display before the GPS power mode is switched. + +The MAX values continue to be collected with the display off so may appear a little odd after the intermittent fixes of the low power mode. + +## Waypoints + +Waypoints are used in [D]istance mode. Create a file waypoints.json and write to storage on the Bangle.js using the IDE. The first 6 characters of the name are displayed in Speed+[D]istance mode. + +The [GPS Navigation](https://banglejs.com/apps/#gps%20navigation) app in the App Loader has a really nice waypoints file editor. (Must be connected to your Bangle.JS and then click on the Download icon.) + +Sample waypoints.json (My sailing waypoints) + +
+[
+  {
+  "name":"NONE"
+  },
+  {
+  "name":"Omori",
+  "lat":-38.9058670,
+  "lon":175.7613350
+  },
+  {
+  "name":"DeltaW",
+  "lat":-38.9438550,
+  "lon":175.7676930
+  },
+  {
+  "name":"DeltaE",
+  "lat":-38.9395240,
+  "lon":175.7814420
+  },
+  {
+  "name":"BtClub",
+  "lat":-38.9446020,
+  "lon":175.8475720
+  },
+  {
+  "name":"Hapua",
+  "lat":-38.8177750,
+  "lon":175.8088720
+  },
+  {
+  "name":"Nook",
+  "lat":-38.7848090,
+  "lon":175.7839440
+  },
+  {
+  "name":"ChryBy",
+  "lat":-38.7975050,
+  "lon":175.7551960
+  },
+  {
+  "name":"Waiha",
+  "lat":-38.7219630,
+  "lon":175.7481520
+  },
+  {
+  "name":"KwaKwa",
+  "lat":-38.6632310,
+  "lon":175.8670320
+  },
+  {
+  "name":"Hatepe",
+  "lat":-38.8547420,
+  "lon":176.0089124
+  },
+  {
+  "name":"Kinloc",
+  "lat":-38.6614442,
+  "lon":175.9161607
+  }
+]
+
+ +## Comments and Feedback + +Developed for my use in sailing, cycling and motorcycling. If you find this software useful or have feedback drop me a line mike[at]kereru.com. Enjoy! + +## Thanks + +Many thanks to Gordon Williams. Awesome job. + +Special thanks also to @jeffmer, for the [GPS Navigation](https://banglejs.com/apps/#gps%20navigation) app and @hughbarney for the Low power GPS code development and Wouter Bulten for the Kalman filter code. + diff --git a/apps/speedalt2/app-icon.js b/apps/speedalt2/app-icon.js new file mode 100644 index 000000000..f4f24a18b --- /dev/null +++ b/apps/speedalt2/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwxH+AH4A/AE+sFtoABF12swItsF9QuFR4IwmFwwvnFw4vCGEYuIF4JgjFxIvkFxQvCGBfOAAQvqFwYwRFxYvDGBIvUFxgv/F6IuNF4n+0nB4TvXFxwvF4XBAALlPF7ZfBGC4uPF4rABGAYAGTQwvad4YwKFzYvIGBQvfFwgAE3Qvt4IvEFzgvCLxO7Lx7vULzIzTFwIvgGZheFRAiNRGSQvpGYouesYAGmQAKq3CE4PIC4wviq2eFwPCroveCRSGEC6Qv0DAwRLcoouWC4VdVYQXkr1eAgVdAoIABroNEB4gHHC5QvHwQSDAAOCA74vH1uICQIABxGtA74vIAEwv/F/4vXAH4A/AHY")) diff --git a/apps/speedalt2/app.js b/apps/speedalt2/app.js new file mode 100644 index 000000000..0db9629c7 --- /dev/null +++ b/apps/speedalt2/app.js @@ -0,0 +1,725 @@ +/* +Speed and Altitude [speedalt2] +Mike Bennett mike[at]kereru.com +0.01 : Initial +0.06 : Add Posn screen +0.07 : Add swipe to change screens same as BTN3 +*/ +var v = '1.05'; + +/*kalmanjs, Wouter Bulten, MIT, https://github.com/wouterbulten/kalmanjs */ +var KalmanFilter = (function () { + 'use strict'; + + function _classCallCheck(instance, Constructor) { + if (!(instance instanceof Constructor)) { + throw new TypeError("Cannot call a class as a function"); + } + } + + function _defineProperties(target, props) { + for (var i = 0; i < props.length; i++) { + var descriptor = props[i]; + descriptor.enumerable = descriptor.enumerable || false; + descriptor.configurable = true; + if ("value" in descriptor) descriptor.writable = true; + Object.defineProperty(target, descriptor.key, descriptor); + } + } + + function _createClass(Constructor, protoProps, staticProps) { + if (protoProps) _defineProperties(Constructor.prototype, protoProps); + if (staticProps) _defineProperties(Constructor, staticProps); + return Constructor; + } + + /** + * KalmanFilter + * @class + * @author Wouter Bulten + * @see {@link http://github.com/wouterbulten/kalmanjs} + * @version Version: 1.0.0-beta + * @copyright Copyright 2015-2018 Wouter Bulten + * @license MIT License + * @preserve + */ + var KalmanFilter = + /*#__PURE__*/ + function () { + /** + * Create 1-dimensional kalman filter + * @param {Number} options.R Process noise + * @param {Number} options.Q Measurement noise + * @param {Number} options.A State vector + * @param {Number} options.B Control vector + * @param {Number} options.C Measurement vector + * @return {KalmanFilter} + */ + function KalmanFilter() { + var _ref = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}, + _ref$R = _ref.R, + R = _ref$R === void 0 ? 1 : _ref$R, + _ref$Q = _ref.Q, + Q = _ref$Q === void 0 ? 1 : _ref$Q, + _ref$A = _ref.A, + A = _ref$A === void 0 ? 1 : _ref$A, + _ref$B = _ref.B, + B = _ref$B === void 0 ? 0 : _ref$B, + _ref$C = _ref.C, + C = _ref$C === void 0 ? 1 : _ref$C; + + _classCallCheck(this, KalmanFilter); + + this.R = R; // noise power desirable + + this.Q = Q; // noise power estimated + + this.A = A; + this.C = C; + this.B = B; + this.cov = NaN; + this.x = NaN; // estimated signal without noise + } + /** + * Filter a new value + * @param {Number} z Measurement + * @param {Number} u Control + * @return {Number} + */ + + + _createClass(KalmanFilter, [{ + key: "filter", + value: function filter(z) { + var u = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0; + + if (isNaN(this.x)) { + this.x = 1 / this.C * z; + this.cov = 1 / this.C * this.Q * (1 / this.C); + } else { + // Compute prediction + var predX = this.predict(u); + var predCov = this.uncertainty(); // Kalman gain + + var K = predCov * this.C * (1 / (this.C * predCov * this.C + this.Q)); // Correction + + this.x = predX + K * (z - this.C * predX); + this.cov = predCov - K * this.C * predCov; + } + + return this.x; + } + /** + * Predict next value + * @param {Number} [u] Control + * @return {Number} + */ + + }, { + key: "predict", + value: function predict() { + var u = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0; + return this.A * this.x + this.B * u; + } + /** + * Return uncertainty of filter + * @return {Number} + */ + + }, { + key: "uncertainty", + value: function uncertainty() { + return this.A * this.cov * this.A + this.R; + } + /** + * Return the last filtered measurement + * @return {Number} + */ + + }, { + key: "lastMeasurement", + value: function lastMeasurement() { + return this.x; + } + /** + * Set measurement noise Q + * @param {Number} noise + */ + + }, { + key: "setMeasurementNoise", + value: function setMeasurementNoise(noise) { + this.Q = noise; + } + /** + * Set the process noise R + * @param {Number} noise + */ + + }, { + key: "setProcessNoise", + value: function setProcessNoise(noise) { + this.R = noise; + } + }]); + + return KalmanFilter; + }(); + + return KalmanFilter; + +}()); + + +var buf = Graphics.createArrayBuffer(240,160,2,{msb:true}); + +// Load fonts +//require("Font7x11Numeric7Seg").add(Graphics); + +var lf = {fix:0,satellites:0}; +var showMax = 0; // 1 = display the max values. 0 = display the cur fix +var pwrSav = 1; // 1 = default power saving with watch screen off and GPS to PMOO mode. 0 = screen kept on. +var canDraw = 1; +var time = ''; // Last time string displayed. Re displayed in background colour to remove before drawing new time. +var tmrLP; // Timer for delay in switching to low power after screen turns off + +var max = {}; +max.spd = 0; +max.alt = 0; +max.n = 0; // counter. Only start comparing for max after a certain number of fixes to allow kalman filter to have smoohed the data. + +var emulator = (process.env.BOARD=="EMSCRIPTEN")?1:0; // 1 = running in emulator. Supplies test values; + +var wp = {}; // Waypoint to use for distance from cur position. + +function nxtWp(inc){ + cfg.wp+=inc; + loadWp(); +} + +function loadWp() { + var w = require("Storage").readJSON('waypoints.json')||[{name:"NONE"}]; + if (cfg.wp>=w.length) cfg.wp=0; + if (cfg.wp<0) cfg.wp = w.length-1; + savSettings(); + wp = w[cfg.wp]; +} + +function radians(a) { + return a*Math.PI/180; +} + +function distance(a,b){ + var x = radians(a.lon-b.lon) * Math.cos(radians((a.lat+b.lat)/2)); + var y = radians(b.lat-a.lat); + + // Distance in selected units + var d = Math.sqrt(x*x + y*y) * 6371000; + d = (d/parseFloat(cfg.dist)).toFixed(2); + if ( d >= 100 ) d = parseFloat(d).toFixed(1); + if ( d >= 1000 ) d = parseFloat(d).toFixed(0); + + return d; +} + +function drawScrn(dat) { + + if (!canDraw) return; + + buf.clear(); + + var n; + n = dat.val.toString(); + + var s=50; // Font size + var l=n.length; + + if ( l <= 7 ) s=55; + if ( l <= 6 ) s=60; + if ( l <= 5 ) s=80; + if ( l <= 4 ) s=100; + if ( l <= 3 ) s=120; + + buf.setFontAlign(0,0); //Centre + buf.setColor(1); + buf.setFontVector(s); + buf.drawString(n,126,52); + + + // Primary Units + buf.setFontAlign(-1,1); //left, bottom + buf.setColor(2); + buf.setFontVector(35); + buf.drawString(dat.unit,5,164); + + if ( dat.max ) drawMax(); // MAX display indicator + if ( dat.wp ) drawWP(); // Waypoint name + + //Sats + if ( dat.sat ) { + if ( dat.age > 10 ) { + if ( dat.age > 90 ) dat.age = '>90'; + drawSats('Age:'+dat.age); + } + else drawSats('Sats:'+dat.sats); + } + + g.reset(); + g.drawImage(img,0,40); + + if ( pwrSav ) LED1.reset(); + else LED1.set(); + +} + +function drawPosn(dat) { + if (!canDraw) return; + buf.clear(); + + var x, y; + x=210; + y=0; + buf.setFontAlign(1,-1); + buf.setFontVector(60); + buf.setColor(1); + + buf.drawString(dat.lat,x,y); + buf.drawString(dat.lon,x,y+70); + + x = 240; + buf.setColor(2); + buf.setFontVector(40); + buf.drawString(dat.ns,x,y); + buf.drawString(dat.ew,x,y+70); + + + //Sats + if ( dat.sat ) { + if ( dat.age > 10 ) { + if ( dat.age > 90 ) dat.age = '>90'; + drawSats('Age:'+dat.age); + } + else drawSats('Sats:'+dat.sats); + } + + g.reset(); + g.drawImage(img,0,40); + + if ( pwrSav ) LED1.reset(); + else LED1.set(); + +} + +function drawClock() { + if (!canDraw) return; + + buf.clear(); + var x, y; + x=185; + y=0; + buf.setFontAlign(1,-1); + buf.setFontVector(94); + time = require("locale").time(new Date(),1); + + buf.setColor(1); + + buf.drawString(time.substring(0,2),x,y); + buf.drawString(time.substring(3,5),x,y+80); + + g.reset(); + g.drawImage(img,0,40); + + if ( pwrSav ) LED1.reset(); + else LED1.set(); +} + +function drawWP() { + var nm = wp.name; + if ( nm == undefined || nm == 'NONE' || cfg.modeA ==1 ) nm = ''; + buf.setColor(2); + + buf.setFontAlign(0,1); //left, bottom + buf.setFontVector(48); + buf.drawString(nm.substring(0,8),120,140); + +} + +function drawSats(sats) { + buf.setColor(3); + buf.setFont("6x8", 2); + buf.setFontAlign(1,1); //right, bottom + buf.drawString(sats,240,160); +} + +function drawMax() { + buf.setFontVector(30); + buf.setColor(2); + buf.setFontAlign(0,1); //centre, bottom + buf.drawString('MAX',120,164); +} + +function onGPS(fix) { + + if ( emulator ) { + fix.fix = 1; + fix.speed = 10 + (Math.random()*5); + fix.alt = 354 + (Math.random()*50); + fix.lat = -38.92; + fix.lon = 175.7613350; + fix.course = 245; + fix.satellites = 12; + fix.time = new Date(); + fix.smoothed = 0; + } + + var m; + + var sp = '---'; + var al = '---'; + var di = '---'; + var age = '---'; + var lat = '---.--'; + var ns = ''; + var ew = ''; + var lon = '---.--'; + + + if (fix.fix) lf = fix; + + if (lf.fix) { + + // Smooth data + if ( lf.smoothed !== 1 ) { + if ( cfg.spdFilt ) lf.speed = spdFilter.filter(lf.speed); + if ( cfg.altFilt ) lf.alt = altFilter.filter(lf.alt); + lf.smoothed = 1; + if ( max.n <= 15 ) max.n++; + } + + + // Speed + if ( cfg.spd == 0 ) { + m = require("locale").speed(lf.speed).match(/([0-9,\.]+)(.*)/); // regex splits numbers from units + sp = parseFloat(m[1]); + cfg.spd_unit = m[2]; + } + else sp = parseFloat(lf.speed)/parseFloat(cfg.spd); // Calculate for selected units + + if ( sp < 10 ) sp = sp.toFixed(1); + else sp = Math.round(sp); + + if (parseFloat(sp) > parseFloat(max.spd) && max.n > 15 ) max.spd = sp; + + // Altitude + al = lf.alt; + al = Math.round(parseFloat(al)/parseFloat(cfg.alt)); + + if (parseFloat(al) > parseFloat(max.alt) && max.n > 15 ) max.alt = al; + + // Distance to waypoint + di = distance(lf,wp); + if (isNaN(di)) di = 0; + + // Age of last fix (secs) + age = Math.max(0,Math.round(getTime())-(lf.time.getTime()/1000)); + + // Lat / Lon + ns = 'N'; + if ( lf.lat < 0 ) ns = 'S'; + lat = Math.abs(lf.lat.toFixed(2)); + + ew = 'E'; + if ( lf.lon < 0 ) ew = 'W'; + lon = Math.abs(lf.lon.toFixed(2)); + + } + + if ( cfg.modeA == 0 ) { + // Speed + if ( showMax ) + drawScrn({ + val:max.spd, + unit:cfg.spd_unit, + sats:lf.satellites, + age:age, + max:true, + wp:false, + sat:true + }); // Speed maximums + else + drawScrn({ + val:sp, + unit:cfg.spd_unit, + sats:lf.satellites, + age:age, + max:false, + wp:false, + sat:true + }); + } + + if ( cfg.modeA == 1 ) { + // Alt + if ( showMax ) + drawScrn({ + val:max.alt, + unit:cfg.alt_unit, + sats:lf.satellites, + age:age, + max:true, + wp:false, + sat:true + }); // Alt maximums + else + drawScrn({ + val:al, + unit:cfg.alt_unit, + sats:lf.satellites, + age:age, + max:false, + wp:false, + sat:true + }); + } + + if ( cfg.modeA == 2 ) { + // Dist + drawScrn({ + val:di, + unit:cfg.dist_unit, + sats:lf.satellites, + age:age, + max:false, + wp:true, + sat:true + }); + } + + if ( cfg.modeA == 3 ) { + // Position + drawPosn({ + sats:lf.satellites, + age:age, + lat:lat, + lon:lon, + ns:ns, + ew:ew, + sat:true + }); + } + + if ( cfg.modeA == 4 ) { + // Large clock + drawClock(); + } + +} + +function prevScrn() { + cfg.modeA = cfg.modeA-1; + if ( cfg.modeA < 0 ) cfg.modeA = 4; + savSettings(); + onGPS(lf); +} + +function nextScrn() { + cfg.modeA = cfg.modeA+1; + if ( cfg.modeA > 4 ) cfg.modeA = 0; + savSettings(); + onGPS(lf); +} + +// Next function on a screen +function nextFunc(dur) { + if ( cfg.modeA == 0 || cfg.modeA == 1 ) { + // Spd+Alt mode - Switch between fix and MAX + if ( dur < 2 ) showMax = !showMax; // Short press toggle fix/max display + else { max.spd = 0; max.alt = 0; } // Long press resets max values. + } + else if ( cfg.modeA == 2) nxtWp(1); // Dist mode - Select next waypoint + onGPS(lf); +} + + +function updateClock() { + if (!canDraw) return; + if ( cfg.modeA != 4 ) return; + drawClock(); + if ( emulator ) {max.spd++;max.alt++;} +} + +function startDraw(){ + canDraw=true; + g.clear(); + Bangle.drawWidgets(); + setLpMode('SuperE'); // off + onGPS(lf); // draw app screen +} + +function stopDraw() { + canDraw=false; + if (!tmrLP) tmrLP=setInterval(function () {if (lf.fix) setLpMode('PSMOO');}, 10000); //Drop to low power in 10 secs. Keep lp mode off until we have a first fix. +} + +function savSettings() { + require("Storage").write('speedalt2.json',cfg); +} + +function setLpMode(m) { + if (tmrLP) {clearInterval(tmrLP);tmrLP = false;} // Stop any scheduled drop to low power + if ( !gpssetup ) return; + gpssetup.setPowerMode({power_mode:m}); +} + +// == Events + +function setButtons(){ + + // BTN1 - Max speed/alt or next waypoint + setWatch(function(e) { + var dur = e.time - e.lastTime; + nextFunc(dur); + }, BTN1, { edge:"falling",repeat:true}); + + // Power saving on/off + setWatch(function(e){ + pwrSav=!pwrSav; + if ( pwrSav ) { + LED1.reset(); + var s = require('Storage').readJSON('setting.json',1)||{}; + var t = s.timeout||10; + Bangle.setLCDTimeout(t); + } + else { + Bangle.setLCDTimeout(0); +// Bangle.setLCDPower(1); + LED1.set(); + } + }, BTN2, {repeat:true,edge:"falling"}); + + // BTN3 - next screen + setWatch(function(e){ + nextScrn(); + }, BTN3, {repeat:true,edge:"falling"}); + +/* + // Touch screen same as BTN1 short + setWatch(function(e){ + nextFunc(1); // Same as BTN1 short + }, BTN4, {repeat:true,edge:"falling"}); + setWatch(function(e){ + nextFunc(1); // Same as BTN1 short + }, BTN5, {repeat:true,edge:"falling"}); +*/ + +} + +Bangle.on('lcdPower',function(on) { + if (!SCREENACCESS.withApp) return; + if (on) startDraw(); + else stopDraw(); +}); + +Bangle.on('swipe',function(dir) { + if ( ! cfg.touch ) return; + if(dir == 1) prevScrn(); + else nextScrn(); +}); + +Bangle.on('touch', function(button){ + if ( ! cfg.touch ) return; + nextFunc(0); // Same function as short BTN1 +/* + switch(button){ + case 1: // BTN4 +console.log('BTN4'); + prevScrn(); + break; + case 2: // BTN5 +console.log('BTN5'); + nextScrn(); + break; + case 3: +console.log('MDL'); + nextFunc(0); // Centre - same function as short BTN1 + break; + } +*/ + }); + + + +// == Main Prog + +// Read settings. +let cfg = require('Storage').readJSON('speedalt2.json',1)||{}; + +cfg.spd = cfg.spd||0; // Multiplier for speed unit conversions. 0 = use the locale values for speed +cfg.spd_unit = cfg.spd_unit||''; // Displayed speed unit +cfg.alt = cfg.alt||0.3048;// Multiplier for altitude unit conversions. +cfg.alt_unit = cfg.alt_unit||'feet'; // Displayed altitude units +cfg.dist = cfg.dist||1000;// Multiplier for distnce unit conversions. +cfg.dist_unit = cfg.dist_unit||'km'; // Displayed altitude units +cfg.colour = cfg.colour||0; // Colour scheme. +cfg.wp = cfg.wp||0; // Last selected waypoint for dist +cfg.modeA = cfg.modeA||0; // 0=Speed 1=Alt 2=Dist 3=Position 4=Clock +cfg.primSpd = cfg.primSpd||0; // 1 = Spd in primary, 0 = Spd in secondary + +cfg.spdFilt = cfg.spdFilt==undefined?true:cfg.spdFilt; +cfg.altFilt = cfg.altFilt==undefined?true:cfg.altFilt; +cfg.touch = cfg.touch==undefined?true:cfg.touch; + +if ( cfg.spdFilt ) var spdFilter = new KalmanFilter({R: 0.1 , Q: 1 }); +if ( cfg.altFilt ) var altFilter = new KalmanFilter({R: 0.01, Q: 2 }); + +loadWp(); + +/* +Colour Pallet Idx +0 : Background (black) +1 : Speed/Alt +2 : Units +3 : Sats +*/ +var img = { + width:buf.getWidth(), + height:buf.getHeight(), + bpp:2, + buffer:buf.buffer, + palette:new Uint16Array([0,0x4FE0,0xEFE0,0x07DB]) +}; + +if ( cfg.colour == 1 ) img.palette = new Uint16Array([0,0xFFFF,0xFFF6,0xDFFF]); +if ( cfg.colour == 2 ) img.palette = new Uint16Array([0,0xFF800,0xFAE0,0xF813]); + +var SCREENACCESS = { + withApp:true, + request:function(){this.withApp=false;stopDraw();}, + release:function(){this.withApp=true;startDraw();} +}; + +var gpssetup; +try { + gpssetup = require("gpssetup"); +} catch(e) { + gpssetup = false; +} + +// All set up. Lets go. +g.clear(); +Bangle.loadWidgets(); +Bangle.drawWidgets(); +onGPS(lf); +Bangle.setGPSPower(1); + +if ( gpssetup ) { + gpssetup.setPowerMode({power_mode:"SuperE"}).then(function() { Bangle.setGPSPower(1); }); +} +else { + Bangle.setGPSPower(1); +} + +Bangle.on('GPS', onGPS); + +setButtons(); +setInterval(updateClock, 10000); diff --git a/apps/speedalt2/app.png b/apps/speedalt2/app.png new file mode 100644 index 0000000000000000000000000000000000000000..93d8e57dcbfaf905321fffcf06df042aa49d7dc8 GIT binary patch literal 1639 zcmV-t2AKJYP)@pC8}P=li+m^Env72)@5?yIePa+S>ZbmYsjOa=2m` z!{OB3)^@Gi>3mz!4w$0Hsk)+sbUjipG!&a5|mi z0)`wD_))?j-{$=p(Hmdb{pQ4>#*6Vi6tc_Zs;~=3feFd%R;{|DPtl#K#zRAi7Y`X1 z;B-32+0egXk^BI1gjay&Ter42GL0Dz85MB$^yxyo$U$Iys+>m`&vy%a=qU``n@P+> zNP8DjRdIK?-TImuq^&ql&*I#FWmi3gE><5sB1FaOuOpr#vV?n<_ z(dFmE1qqjs%jGHp7J&{q>2Qby*D7qRpED;gC=2aCEBXiDExut%@3&{)$jNhX)r2YG z^dnNg&zDjGMkx7$Y$pHGo;N24gYr|qS+FpfUBG`~4S=I0`nS$#h_+3*gamysqb!pk zXv%7YEpd4d5ubDqMVujc>)h5J>qvFn-dc4qzLwO;v4OF4uHK zDiy5+ZXrpN_sZ@=#Xj{d&k>z5AX5;YnD;7bECtO;8eHUgNwAw_bgd#HLExkQ@!Xj+ zXB>X7_a+mwQjkhu24U}4pZZo9%HcI2rhoVL=_y*H6IEB8WWWn~`V z-Tpy)d;4g+$ZY~Y$B;^am0~yN4W?9AbqDCW6kH$2X1iauedJGMVA;$qJ+`S27!^;a zLFHfD)9b&=?EzjY{WpaYdh*?c4qo{24Fn`I6~AH7lb74K>t{82$^4}<13f@#JcasV z&KF6MZz%O8J%h>nRCsynRJse|UsOmac2ZauAatVf3o!C+e}8AsYqKqV^h0Z_b|Kt1 z@O_l9{PWVHU!u&4Ymd6Dd|G#zCMOdsSK06%Q^1oACnNf8o85N^@B&=+bs3j^T{fWdc+A|x(cEZ$IG!5wbnEk_ zzpY`zq1m!&pEnc#(LQ5#p(i)~!vyvkhHU0Pfl~n8(?8u^c&_xWf^(r)p||8Ly(MS& zk3BghbW*$AKFu`sF)m((MDVTj>G)q)J0s$u#}x4D?$7k|?)Yu_xT{0E#ii6gwD`J+ zoMU#ODHE@txF{F*m*+X}3AOBn4m;z^3mIFQ4{*u#;fR@m_fLG4-4jffF?;5ih@6Mz lPrm;rMhY0g2u5&e@jt$gS?$`E2({load('speedalt2.app.js');}, + 'Units' : function() { E.showMenu(unitsMenu); }, + 'Colours' : function() { E.showMenu(colMenu); }, + 'Kalman Filter' : function() { E.showMenu(kalMenu); }, + 'Touch' : { + value : settings.touch, + format : v => v?"On":"Off", + onchange : () => { settings.touch = !settings.touch; writeSettings(); } + } + }; + + const unitsMenu = { + '': {'title': 'Units'}, + '< Back': function() { E.showMenu(appMenu); }, + 'default (spd)' : function() { setUnits(0,''); }, + 'Kph (spd)' : function() { setUnits(1,'kph'); }, + 'Knots (spd)' : function() { setUnits(1.852,'kts'); }, + 'Mph (spd)' : function() { setUnits(1.60934,'mph'); }, + 'm/s (spd)' : function() { setUnits(3.6,'m/s'); }, + 'Km (dist)' : function() { setUnitsDist(1000,'km'); }, + 'Miles (dist)' : function() { setUnitsDist(1609.344,'mi'); }, + 'Nm (dist)' : function() { setUnitsDist(1852.001,'nm'); }, + 'Meters (alt)' : function() { setUnitsAlt(1,'m'); }, + 'Feet (alt)' : function() { setUnitsAlt(0.3048,'ft'); } + }; + + const colMenu = { + '': {'title': 'Colours'}, + '< Back': function() { E.showMenu(appMenu); }, + 'Default' : function() { setColour(0); }, + 'Hi Contrast' : function() { setColour(1); }, + 'Night' : function() { setColour(2); } + }; + + const kalMenu = { + '': {'title': 'Kalman Filter'}, + '< Back': function() { E.showMenu(appMenu); }, + 'Speed' : { + value : settings.spdFilt, + format : v => v?"On":"Off", + onchange : () => { settings.spdFilt = !settings.spdFilt; writeSettings(); } + }, + 'Altitude' : { + value : settings.altFilt, + format : v => v?"On":"Off", + onchange : () => { settings.altFilt = !settings.altFilt; writeSettings(); } + } + }; + + + E.showMenu(appMenu); + +}); diff --git a/apps/stopwatch/A.jpg b/apps/stopwatch/A.jpg new file mode 100644 index 0000000000000000000000000000000000000000..9155b9986e05f68312ad383e52490d45f8584542 GIT binary patch literal 77328 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

5%2T4Dz|IU;f>liup3Kanl0p2s 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 Z{{Z>S*8c$2^MCJuM&kbfW^T}<|Jf0-Ls0+# literal 0 HcmV?d00001 diff --git a/apps/stopwatch/B.jpg b/apps/stopwatch/B.jpg new file mode 100644 index 0000000000000000000000000000000000000000..639ff5d42b66c0d14fe451e0834cf174fb626050 GIT binary patch literal 69988 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 literal 0 HcmV?d00001 diff --git a/apps/stopwatch/ChangeLog b/apps/stopwatch/ChangeLog new file mode 100644 index 000000000..9db0e26c5 --- /dev/null +++ b/apps/stopwatch/ChangeLog @@ -0,0 +1 @@ +0.01: first release diff --git a/apps/stopwatch/README.md b/apps/stopwatch/README.md new file mode 100644 index 000000000..30a9306d1 --- /dev/null +++ b/apps/stopwatch/README.md @@ -0,0 +1,33 @@ +# Stopwatch Touch + +A touch screen based stop watch for Bangle 2 + +## Screenshots + +![](screenshot1.png) +![](screenshot2.png) +![](screenshot3.png) + +## Features + +* Attractive UI design +* Will run up to 99 hours +* Shows 10th of seconds up to 1 hour +* Start / Pause button +* Reset button + +## Future features + +I'm keen to complete this project with + +* Ability to dismiss the app and leave it running in the background +* A small widget to show the elapsed time on the current active clock +* Laptimes, with a way to view all the laptimes on a scrollable screen + + +## One of these is a genuine Bangle Js 2 Open Source Smartwatch, the other isn't + +Which one is which ? + +![](A.jpg) +![](B.jpg) diff --git a/apps/stopwatch/pause-24.png b/apps/stopwatch/pause-24.png new file mode 100644 index 0000000000000000000000000000000000000000..eb3d8feaa578ddf2868ca2ebfe6c1016b89076b6 GIT binary patch literal 161 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`GjY)RhkE)4%caKYZ?lYt^(o-U3d z9-YYv609r?%6x@>Y;0_-CL6c&@bIi(kYfnsC7S$X2lDQBZoM(BvGJk3)I0Y{?~g0x zcb`>IOUhLd?};+VI=F&$u~<)(1Fu1X=sArBW(KQ1!Ho<&4aPtl7(8A5T-G@yGywo& C|1Q-4 literal 0 HcmV?d00001 diff --git a/apps/stopwatch/pause-24a.png b/apps/stopwatch/pause-24a.png new file mode 100644 index 0000000000000000000000000000000000000000..7838ef640b48c569532e978aa4ea89960d1b0533 GIT binary patch literal 123 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`GjY)RhkE)4%caKYZ?lYt@zo-U3d z9-YYv60C>i3?5DV?eCzgSjh$gYC*9sS`u3mt~3R5yXi{si1joHGB8XvWs;ff*UPx#<4Ht8R7gv;*0D;&KoADtFVAWvTu`u3ff&Q zddffoheq6(ovFkBk;%?%8p+ip=j#J@@r2_VcpvbIcRXMd*JGgHfc4CCOxm~l7>bnu zk@<#w%y2gZDFwRBN1S4cml9|w5Sc5yVh=}uA^!o^Gw-p1+qR8Ays1~!ihxHwMs{eCbxGP!q{{F+Bi9GAC zwDJ$YVMPp+Sz`h7R3{$)<=R|kq2{ybIIAV z#{M|Yd9|K>_im)WlW*Misd4vReJkpk0+Pgj%D+cI=n`X&`I%TKcU>|F9=!+TaBSLuVICewX|Pq#Db6J6?Nl`gXZd8>VX z&$;&p`uYAozpc#w?|2d0g;W0oC+s$Ub8r7)NoC1J*?zUHKWbihF-q<%doRzp-TLsm zzt(dMCn;`v7S7VgvNLw_PkmSI^A+3bL*}H*?=d#A+07F6t?rv>&eNuwwa2c0wu!Fa zb>J$~ruWr>3ajP&L~ouy@nN&O>jKU6o=;U<`wp0wEHdA~|8is1#O@ar(;tgQvz)u5 zG?RJ#ouG+-!&%CX9WQdVkk6Kk-x&J9<7B$bkEtiLf2RY*^_jHiJg$;k|3|d3KRKe& zp^u@l_>PaMqQe6<*N%zG4i99^gMB#!c6bUVJ>d|rIDR%nn29AXs71n(iRE0O@zoZF z#@Ql@Zll@=1<421569i}Ziz673oCp$%34v=%g~tp$0y#W;efdKgO8?6EbBfR*BUZ1 z#m6)pzYYxXW9PpdW#OnPWR!~sMuU|7WlW7%neN@c|3_mVm!ZA?1DoCJPx?33|NAj_ zlGfR$l}!4|yQO!C&NvY|>-1-qI5#l?Aam8jN~ZQe+sGd;*!IQ0y1K~g=S!Q$)fvi6 z^>)oiLND<@=ZrZjC3+*r*%O0mWPOmSjtv28A{c-T>4_tvq zBSmlgE?mjTH2uTfzY5B`r(f^X*=f$S{9-MCSboB%X;KOWobeCY)bghOnP15iZn*B= zfg@{${@(}MX0ez_c)QoF??4N9YUF3^iM~+XYQg+^we*Y~=>l~-+rvAwlJ@&Wsz%IG zFW{UXpRxYY>T|+7d@j^$u2NcaMtFzQ`8Q92;Zn)U6aow-YViKBKlrQl{6c1w2Cyb$ N@O1TaS?83{1OV%-^rrv- literal 0 HcmV?d00001 diff --git a/apps/stopwatch/screenshot2.png b/apps/stopwatch/screenshot2.png new file mode 100644 index 0000000000000000000000000000000000000000..0baa733313973f63c117f606e7eaedf43cd46a21 GIT binary patch literal 1765 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 V;!)cRW&mwu@O1TaS?83{1OQ$|I{5$q literal 0 HcmV?d00001 diff --git a/apps/stopwatch/stop-24a.png b/apps/stopwatch/stop-24a.png new file mode 100644 index 0000000000000000000000000000000000000000..e89ddae054cbda36b088abab6577f064049dbdf9 GIT binary patch literal 192 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`GjY)RhkE)4%caKYZ?lYt_oo-U3d z9-VKedh<0X@VLyE^E>)`zQZ1Ny$-idk)JA?UU=`)n(L`3>2NZIRp$ZMX(l(L6@vEJ zySe!q8&BV{IJVT%o@zpKGUG`7qs_<9thpacs;)sXzG!+_r}04vQBZNXTE1 nxpdjV=apyUC!A4#dXRD61xvr9nF3)z2QYZL`njxgN@xNA(C= this.x && x<= (this.x + this.w) && y>= this.y && y<= (this.y + this.h)) { + log_debug(this.name + ":callback\n"); + this.callback(); + return true; + } + return false; +}; + +BUTTON.prototype.draw = function() { + g.setColor(this.color); + g.fillRect(this.x, this.y, this.x + this.w, this.y + this.h); + g.setColor("#000"); // the icons and boxes are drawn black + if (this.img != undefined) { + let iw = iconScale * 24; // the images were loaded as 24 pixels, we will scale + let ix = this.x + ((this.w - iw) /2); + let iy = this.y + ((this.h - iw) /2); + log_debug("g.drawImage(" + ix + "," + iy + "{scale: " + iconScale + "})"); + g.drawImage(this.img, ix, iy, {scale: iconScale}); + } + g.drawRect(this.x, this.y, this.x + this.w, this.y + this.h); +}; + + +var bigPlayPauseBtn = new BUTTON("big",0, 3*h/4 ,w, h/4, "#0ff", stopStart, play_img); +var smallPlayPauseBtn = new BUTTON("small",w/2, 3*h/4 ,w/2, h/4, "#0ff", stopStart, play_img); +var resetBtn = new BUTTON("rst",0, 3*h/4, w/2, h/4, "#ff0", lapReset, pause_img); + +bigPlayPauseBtn.setImage(play_img); +smallPlayPauseBtn.setImage(play_img); +resetBtn.setImage(pause_img); + + +Bangle.on('touch', function(button, xy) { + // not running, and reset + if (!running && tCurrent == tTotal && bigPlayPauseBtn.check(xy.x, xy.y)) return; + + // paused and hit play + if (!running && tCurrent != tTotal && smallPlayPauseBtn.check(xy.x, xy.y)) return; + + // paused and press reset + if (!running && tCurrent != tTotal && resetBtn.check(xy.x, xy.y)) return; + + // must be running + if (running && bigPlayPauseBtn.check(xy.x, xy.y)) return; +}); + +// 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; + } +}); + +// Clear the screen once, at startup +g.setTheme({bg:"#000",fg:"#fff",dark:true}).clear(); +// above not working, hence using next 2 lines +g.setColor("#000"); +g.fillRect(0,0,w,h); + +Bangle.loadWidgets(); +Bangle.drawWidgets(); +draw(); +Bangle.setUI("clock"); // Show launcher when button pressed diff --git a/apps/stopwatch/stopwatch.icon.js b/apps/stopwatch/stopwatch.icon.js new file mode 100644 index 000000000..32281b7ab --- /dev/null +++ b/apps/stopwatch/stopwatch.icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEw4UA///vvvvEF/muH+cDHgPABf4AElWoKhILClALH1WqAQIWHBYIABwAKEgQKD1WgBYkK1X1r4XHlWtqtVvQLG1XVBYNXHYsC1YJBBoPqC4kKEQILCvQ7EhW1BYdeBYkqytVqwCCQwkqCgILCq4LFIoILCqoLEIwIsBGQJIBBZ+pA4Na0oDBtQLGvSFCBaYjIHYR3CI5AADBYhrCAAaDHAASDGQASGCBYizCAASzFZYQACZYrjCIwb7QHgIkCvQ6EGAWq+tf1QuEGAWqAAQuFEgQKBEQw9DHIwAuA=")) diff --git a/apps/stopwatch/stopwatch.png b/apps/stopwatch/stopwatch.png new file mode 100644 index 0000000000000000000000000000000000000000..92ffe73b7f172f7019486231d5bc0ba327c13a67 GIT binary patch literal 1566 zcmV+(2I2XMP)rZGih zXw&#$)jrm=iAGIK8xtz&C7L#UsP>_)mll=wMNO+ffoiD_T1#yUim(D(ys%vMfQPei zrVqOZR|L*1ZtO$(Kh4bfZvQzm-^@2KjcNS9p~TbJ{=+BFgR;y*?EJRZOY-C8(-tp7 zVORza@KQWI#+kt5$25We8u2D@-cmyFb37f!H4B+o`PybN-uT(0hvM!pG2RAaMai~Z zb8H5+fL|F7l^*}>gRWTg<2NIR8$39)H7FE$0)fri3@0JG9e^ZV$1ylSfBMXW2&%E$ zPO&yWxOMB+q<~{)6;Ked;unf2IeFmyUmqWV%KAJEV+T+Q%#hR?1dbK`*5J(O?<*bi zO;Z%!IDVk)sEnbh6R5b5f)d(`!dpynDcXcVZikO&eb-4ajOeKPP8=vpld&G6#BUZr!wEAw+((0R3 zrXbzLH_tucuqlj0hL$qu+9ey79D&OEyhyeIpghm3SOpJZ0ylh<6M!&@vIX9#5%D$^ zHQ6$u$q@+W`9uhB*iAW^QhKevc3RtRO1aoFFEin3XYxR#>%x|>M@G*&ki!ig!iN+9 z<#}G0?8a$r^U>1QCMBx?`|`Zb`y*l_6*ZQ>*_wbuTSMf8&2iX+vZ=DKzn?&FFTtQn zcOW38Q<&~zjy+M0be$yF)>!FS6Hq8Jo3G6BI3^Q~Myv1s(rIb4-UlX1veWFN9dY0H zM6f0xl*MMVIK!PJ8_3nan@8J?SO1b#qd3!TkLoU-P%r|SKQP~H&P}mTCOL|}{(ipr z>d0vS%cB+Cx!WzJJ2&|OAM+=YGa>N@@R-ePyWG|3Y)vhL_wLbMu#!7PtICQ=hz{kT!_!z`=nUlJl03sr&j0}&?P-3E-D?`>v1Tssg-)!E}hoy|JDk@}lRu&l- zYRIfV%j~QyRj3EpMT1a)jt8Ss%SYLw_Cy$ zv}^iU-;=_cfKaZ0SOQSht?Gs8E~%aDal2W+_GKB-2XL;tKLmucox&9>d6`XIZ8j@! z_e$Lm9|hmt>bvj55SBaNntrAr?+kNN?GIAf(_O<-+) z5IAObcQpIe7!!pQ3<2AlI-_PsIm(6=ma`>W50nETfTn>V*P-_A5exa4aj@g=$hAS; zoP8Zd*(akXM__&7H4yvF?#}Ce>f^YS>UvamU;ih1=wsFuSY{xIr6LQ8JwOA1AxJ1~ z^l9-RQP-pLYNJmx>tf&nGX~z3)sL;HKUp^iAua-QAzWIPE%35s-uOlRWH1CA?7VC2 zZtlWBm=oEKVJu<83hC#?DLkvxEdp_h2nm2Zr(G@2&2bfG$kht9Ju2IqI@PE(573Um z8a5O*#uCodUgj!lva#8|1C~Y)XWJ9ib=hh5;!L+aCpigk)WcS%7NR;3)K7Nx>QSlE zjN%tYjFMPp{?P~koIaf#emxNH08UH!@u4HcM>q%HlYK@Ri${Olt*FTk7vN$e@QR9l#uqzNePq-K{A831lR^T#FgJGPOIi#jBjcHipUslrouSl-W Q%m4rY07*qoM6N<$g48qYF8}}l literal 0 HcmV?d00001 diff --git a/apps/svclock/ChangeLog b/apps/svclock/ChangeLog index 671de492c..fb71fbeb8 100644 --- a/apps/svclock/ChangeLog +++ b/apps/svclock/ChangeLog @@ -1,2 +1,4 @@ 0.01: Modification of SimpleClock 0.04 to use Vectorfont 0.02: Use Bangle.setUI for button/launcher handling +0.03: Scale to BangleJS 2 and add locale +0.04: Fix rendering issue on real hardware, now update *on* the minute rather than every 15 secs diff --git a/apps/svclock/vclock-simple.js b/apps/svclock/vclock-simple.js index f3ab911bc..e08c6fa2c 100644 --- a/apps/svclock/vclock-simple.js +++ b/apps/svclock/vclock-simple.js @@ -1,84 +1,109 @@ -/* jshint esversion: 6 */ -const timeFontSize = 65; -const dateFontSize = 20; -const gmtFontSize = 10; -const font = "Vector"; +const locale = require("locale"); -const xyCenter = g.getWidth() / 2; -const yposTime = 75; -const yposDate = 130; -const yposYear = 175; -const yposGMT = 220; +var timeFontSize; +var dateFontSize; +var gmtFontSize; +var font = "Vector"; +var xyCenter = g.getWidth() / 2; +var yposTime; +var yposDate; +var yposYear; +var yposGMT; + +if (g.getWidth() > 200) { + timeFontSize = 65; + dateFontSize = 20; + gmtFontSize = 10; + + yposTime = 75; + yposDate = 130; + yposYear = 175; + yposGMT = 220; +} else { + timeFontSize = 48; + dateFontSize = 15; + gmtFontSize = 10; + + yposTime = 55; + yposDate = 95; + yposYear = 128; + yposGMT = 161; +} // Check settings for what type our clock should be var is12Hour = (require("Storage").readJSON("setting.json",1)||{})["12hour"]; -function drawSimpleClock() { +// timeout used to update every minute +var drawTimeout; + +// schedule a draw for the next minute +function queueDraw() { + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = setTimeout(function() { + drawTimeout = undefined; + draw(); + }, 60000 - (Date.now() % 60000)); +} + +function draw() { g.clear(); Bangle.drawWidgets(); // get date var d = new Date(); - var da = d.toString().split(" "); g.reset(); // default draw styles // drawSting centered g.setFontAlign(0, 0); - // draw time - var time = da[4].substr(0, 5).split(":"); - var hours = time[0], - minutes = time[1]; - var meridian = ""; + // drawTime + var hours; if (is12Hour) { - hours = parseInt(hours,10); - meridian = "AM"; - if (hours == 0) { - hours = 12; - meridian = "AM"; - } else if (hours >= 12) { - meridian = "PM"; - if (hours>12) hours -= 12; - } - hours = (" "+hours).substr(-2); + hours = ("0" + d.getHours()%12).slice(-2); + } else { + hours = ("0" + d.getHours()).slice(-2); } + var minutes = ("0" + d.getMinutes()).slice(-2); g.setFont(font, timeFontSize); g.drawString(`${hours}:${minutes}`, xyCenter, yposTime, true); - g.setFont(font, gmtFontSize); - g.drawString(meridian, xyCenter + 102, yposTime + 10, true); + + if (is12Hour) { + g.setFont(font, gmtFontSize); + g.drawString(locale.meridian(d), xyCenter + 102, yposTime + 10, true); + } // draw Day, name of month, Date - var date = [da[0], da[1], da[2]].join(" "); g.setFont(font, dateFontSize); - - g.drawString(date, xyCenter, yposDate, true); + g.drawString([locale.dow(d,1), locale.month(d,1), d.getDate()].join(" "), xyCenter, yposDate, true); // draw year g.setFont(font, dateFontSize); g.drawString(d.getFullYear(), xyCenter, yposYear, true); // draw gmt - var gmt = da[5]; g.setFont(font, gmtFontSize); - g.drawString(gmt, xyCenter, yposGMT, true); + g.drawString(d.toString().match(/GMT[+-]\d+/), xyCenter, yposGMT, true); + + queueDraw(); } -// handle switch display on by pressing BTN1 -Bangle.on('lcdPower', function(on) { - if (on) drawSimpleClock(); +// 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 button pressed +Bangle.setUI("clock"); // clean app screen g.clear(); Bangle.loadWidgets(); Bangle.drawWidgets(); -// refesh every 15 sec -setInterval(drawSimpleClock, 15E3); - // draw now -drawSimpleClock(); - -// Show launcher when button pressed -Bangle.setUI("clock"); +draw(); diff --git a/apps/swiperclocklaunch/ChangeLog b/apps/swiperclocklaunch/ChangeLog new file mode 100644 index 000000000..2286a7f70 --- /dev/null +++ b/apps/swiperclocklaunch/ChangeLog @@ -0,0 +1 @@ +0.01: New App! \ No newline at end of file diff --git a/apps/swiperclocklaunch/boot.js b/apps/swiperclocklaunch/boot.js new file mode 100644 index 000000000..0bb8d588a --- /dev/null +++ b/apps/swiperclocklaunch/boot.js @@ -0,0 +1,17 @@ +// clock -> launcher +(function() { + var sui = Bangle.setUI; + Bangle.setUI = function(mode, cb) { + sui(mode,cb); + if (!mode.startsWith("clock")) return; + Bangle.swipeHandler = dir => { if (dir<0) Bangle.showLauncher(); }; + Bangle.on("swipe", Bangle.swipeHandler); + }; +})(); +// launcher -> clock +setTimeout(function() { + if (global.__FILE__ && __FILE__.endsWith(".app.js") && (require("Storage").readJSON(__FILE__.slice(0,-6)+"info",1)||{}).type=="launch") { + Bangle.swipeHandler = dir => { if (dir>0) load(); }; + Bangle.on("swipe", Bangle.swipeHandler); + } +}, 10); \ No newline at end of file diff --git a/apps/swiperclocklaunch/icon.js b/apps/swiperclocklaunch/icon.js new file mode 100644 index 000000000..c9089ce5c --- /dev/null +++ b/apps/swiperclocklaunch/icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("lEoxH+AB8WAAwYQEaQrdEp4pWEyYoRC49kxGs2fX6+z1mIsgpUCQtAxAjCAA+zxFAFCAQFxAkJAAuIFBxMF1oeHgEABI+sFBomEORInJPgJ7EEyonLFAJQJBIh0IE5x6GE47CME5nXsgnGOojmME5p5HJyAnO6+IE5LEKE6JQEE4lkC5gnPUIh2SE6B4EAAesC5oAP1gnHTxpPDAQIAFeJQACH5wnP64nWAA3CBJB3WAA203fQBAp3IY4plENQ4HC2gABkjHNxAnX2nJBYeIEYf+AYVkE5oDGE4e0UgdkEwYnDUAITEACikBTwgnFxAnZFAJ2FE4lAJ7dAE4pQFY6yfCToYmDE4kW1jvX1geEE4YoF2YfFABRzD67EEEwqiGFCAmETg5QJPQYAMTQJ0GE5AoGshSPYQgmKFA72BFJWzxBzEExgoIKYOI1grC2esxBLGExwpKABolPFCwmSFKQlVFZoXP")) \ No newline at end of file diff --git a/apps/swiperclocklaunch/swiperclocklaunch.png b/apps/swiperclocklaunch/swiperclocklaunch.png new file mode 100644 index 0000000000000000000000000000000000000000..c7f16c2b18ed67170e425ec92af33da5da40a295 GIT binary patch literal 889 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA3?vioaBc-sjKx9jPK-BC>eK@{oCO|{#S9GG z!XV7ZFl&wkQ1DEEPlzj!X4rg$VbuYKH3v}xyxJbZy+<7uF2O5096dHv?yyLU|&ZykC33~1G^2T#N{9o5@; z>hklKT02ex)l{Fm@%+sjAme9bNl-ptoK!J3d9KezuImvO-+7QVE=9u}?IR}Rw{eoa~A#?;&0A)Me7rJ`|R zdEB3^{FytZZ0cgUaW!p{L-~xhg!5|G^n?;b|Kv3GPdy>jvZK4nhl542RpB)+hm)21 zStg6Q0)N$>{BxXOBDY#sP+-&3eo^l0{#=LnxlSm@-VQuvCnOS?EueW#`kGXG$mf%+ zPc*OAc=6tjK38Ao_}QXMZ-PeVr&I9@9(3(;I(S9!L|L z(^Qjt#7tP~=9}5?mmWB=$0Yt_<{7Q&Cuej03rd@+ZanX1q4Bn-X=~=Z-Iu?6+56e+ z_Hw_yH`#Yx*ynI&R{!gd-KR}A+@#^P^+|wD8o%o_ufVHeRf`v{-}3k2ryGBd&wt3i XqO?C%)!fY;ltVmS{an^LB{Ts5&i}k1 literal 0 HcmV?d00001 diff --git a/apps/toucher/ChangeLog b/apps/toucher/ChangeLog index 494110d55..7b5c53de7 100644 --- a/apps/toucher/ChangeLog +++ b/apps/toucher/ChangeLog @@ -3,4 +3,5 @@ 0.03: Close launcher when lcd turn off 0.04: Complete rewrite to add animation and loop ( issue #210 ) 0.05: Improve perf -0.06: Complete rewrite in 80x80, better perf, add settings \ No newline at end of file +0.06: Complete rewrite in 80x80, better perf, add settings +0.07: Added suppport for Bangle 2, added README file diff --git a/apps/toucher/README.md b/apps/toucher/README.md new file mode 100644 index 000000000..27cb32eeb --- /dev/null +++ b/apps/toucher/README.md @@ -0,0 +1,22 @@ +# Toucher - A touch based launcher, swipe left, swipe right, tap to launch + +* Designed specifically for Bangle 1 and Bangle 2 + +## Installation +- Use the App loader to install toucher +- Then delete the existing launcher +- When you restart the new launcher will be loaded +- To return to the default launcher, delete toucher and install the default launcher. + +## Bangle 1 +In the settings menu 'Low Res' refers to setting the Bangle 1 screen into 80x80 mode. +This significantly improves the animation performance. + +## Bangle 2 +The Hires/Lowres settings is ignored. +Touch the top third of the screen to launch the selected app. +Press button 1 to launch the selected app. + +## Screenshots + +![](screenshot1.jpg) diff --git a/apps/toucher/app.js b/apps/toucher/app.js index 455a29c5d..8ac198f52 100644 --- a/apps/toucher/app.js +++ b/apps/toucher/app.js @@ -7,8 +7,11 @@ let settings = Storage.readJSON(filename,1) || { debug: false }; -if(!settings.highres) Bangle.setLCDMode("80x80"); -else Bangle.setLCDMode(); +// this means that setFont('6x8',1) is actually setFont('6x8',3) +if (process.env.HWVERSION == 1) { + if(!settings.highres) Bangle.setLCDMode("80x80"); + else Bangle.setLCDMode(); +} g.clear(); g.flip(); @@ -23,7 +26,7 @@ const ORIGINAL_ICON_SIZE = 48; const STATE = { settings_open: false, index: 0, - target: 240, + target: g.getWidth(), offset: 0 }; @@ -63,7 +66,7 @@ const APPS = getApps(); function noIcon(x, y, scale){ if(scale < 0.2) return; - g.setColor(scale, scale, scale); + g.setColor(g.theme.fg); g.setFontAlign(0,0); g.setFont('6x8',settings.highres ? 6:3); g.drawString('x_x', x+1.5, y); @@ -81,23 +84,24 @@ function render(){ g.clear(); const visibleApps = APPS.filter(app => app.x >= STATE.offset-HALF && app.x <= STATE.offset+WIDTH-HALF ); + let cycle = 0; + let lastCycle = visibleApps.length; + visibleApps.forEach(app => { - - const x = app.x+HALF-STATE.offset; - const y = HALF - (HALF*0.3); + cycle++; + const x = app.x + HALF - STATE.offset; + const y = HALF; let dist = HALF - x; if(dist < 0) dist *= -1; - const scale = 1 - (dist / HALF); if(!scale) return; if(app.special){ - const font = settings.highres ? '6x8' : '4x6'; - const fontSize = settings.highres ? 2 : 1; - g.setFont(font, fontSize); - g.setColor(scale,scale,scale); + const fontSize = (process.env.HWVERSION == 2) ? 4 : (settings.highres ? 6 : 2); + g.setFont('6x8', fontSize); + g.setColor(g.theme.fg); g.setFontAlign(0,0); g.drawString(app.name, HALF, HALF); return; @@ -111,26 +115,69 @@ function render(){ if(icon){ icons[app.name] = icon; try { - const rescale = settings.highres ? scale*ORIGINAL_ICON_SIZE : (scale*(ORIGINAL_ICON_SIZE/2)); - const imageScale = settings.highres ? scale*2 : scale; + let rescale; + let imageScale; + + if (process.env.HWVERSION == 1) { + // on a bangle 1 !highres means 80x80 + rescale = settings.highres ? scale*ORIGINAL_ICON_SIZE : (scale*(ORIGINAL_ICON_SIZE/2)); + imageScale = settings.highres ? scale*2 : scale; + } else { + // !highres mode is meaningless on a bangle 2 at present + rescale = 1.25*scale*ORIGINAL_ICON_SIZE; + imageScale = 2.5*scale; + } + g.drawImage(icon, x-rescale, y-rescale, { scale: imageScale }); - } catch(e){ + } catch(e) { noIcon(x, y, scale); } - }else{ + } else { noIcon(x, y, scale); } //draw text - g.setColor(scale,scale,scale); - if(scale > 0.1){ - const font = settings.highres ? '6x8': '4x6'; - const fontSize = settings.highres ? 2 : 1; - g.setFont(font, fontSize); - g.setFontAlign(0,0); - g.drawString(app.name, HALF, HEIGHT/4*3); - } + g.setColor(g.theme.fg); + if (cycle == 2 && scale > 0.1) { + let fontSize = (process.env.HWVERSION == 2) ? 2 : 1; + if (process.env.HWVERSION == 1) { + fontSize = (settings.highres) ? 3 : 1; + } + + if (app.name.length <= 12) { + g.setFont("6x8", fontSize); + g.setFontAlign(0,1); + g.drawString(app.name, HALF, HEIGHT); + } else { + // some app names are too long for one line + var name = app.name; + var first = name.substring(0, name.indexOf(" ")); + var last = name.substring(name.indexOf(" ") + 1, name.length); + + // all this to handle long names like + // Simple 7 Segment Clock + if (last.length > 12 && process.env.HWVERSION == 1) { + g.setFont((settings.highres ? "6x8" : "4x6"),(settings.highres ? 2 : 1) ); + } else { + g.setFont("6x8", fontSize); + } + + g.setFontAlign(0,-1); + g.drawString(first, HALF, 0); + + if (last.length > 12 && process.env.HWVERSION == 1) { + g.setFont((settings.highres ? "6x8" : "4x6"),(settings.highres ? 2 : 1) ); + } else { + g.setFont("6x8", fontSize); + } + + g.setFontAlign(0,1); + g.drawString(last, HALF, HEIGHT); + } + } + + /* if(settings.highres){ const type = app.type ? app.type : 'App'; const version = app.version ? app.version : '0.00'; @@ -138,18 +185,21 @@ function render(){ g.setFontAlign(0,1); g.setFont('6x8', 1.5); g.setColor(scale,scale,scale); - g.drawString(info, HALF, 215, { scale: scale }); + g.drawString(info, HALF, HEIGHT/8*7, { scale: scale }); } + */ }); const duration = Math.floor(Date.now()-start); if(settings.debug){ g.setFontAlign(0,1); - g.setColor(0, 1, 0); - const fontSize = settings.highres ? 2 : 1; - g.setFont('4x6',fontSize); - g.drawString('Render: '+duration+'ms', HALF, HEIGHT); + g.setColor(g.theme.fgH); + const fontSize = (process.env.HWVERSION == 2) ? 2 : (settings.highres ? 2 : 1); + g.setFont(((process.env.HWVERSION == 2) ? '6x8' : (settings.highres ? '6x8' :'4x6')), fontSize); + // steal the bottom line, and print the duration + g.clearRect(0, HEIGHT - (process.env.HWVERSION == 1 && !settings.highres ? 8 : 24), WIDTH, HEIGHT); + g.drawString('Render: '+duration+' ms', HALF, HEIGHT); } g.flip(); if(STATE.offset == STATE.target) return; @@ -202,7 +252,7 @@ function run(){ E.showMessage("App Source\nNot found"); setTimeout(render, 2000); } else { - Bangle.setLCDMode(); + if (process.env.HWVERSION == 1) Bangle.setLCDMode(); g.clear(); g.flip(); E.showMessage("Loading..."); @@ -211,10 +261,11 @@ function run(){ } -// Screen event -Bangle.on('touch', function(button){ - if(STATE.settings_open) return; - switch(button){ +if (process.env.HWVERSION == 1) { + // Screen event + Bangle.on('touch', function(button){ + if(STATE.settings_open) return; + switch(button){ case 1: prev(); break; @@ -224,8 +275,17 @@ Bangle.on('touch', function(button){ case 3: run(); break; - } -}); + } + }); +} + +if (process.env.HWVERSION == 2) { + // tap at top 1/3 of screen to launch app + Bangle.on('touch', function(button, xy) { + if (xy.y < HEIGHT / 3) + run(); + }); +} Bangle.on('swipe', dir => { if(STATE.settings_open) return; @@ -238,9 +298,12 @@ Bangle.on('lcdPower', on => { if(!on) return load(); }); +if (process.env.HWVERSION == 1) { + setWatch(prev, BTN1, { repeat: true }); + setWatch(next, BTN3, { repeat: true }); + setWatch(run, BTN2, { repeat:true }); +} else { + setWatch(run, BTN1, { repeat:true }); +} -setWatch(prev, BTN1, { repeat: true }); -setWatch(next, BTN3, { repeat: true }); -setWatch(run, BTN2, { repeat:true }); - -jumpTo(1); \ No newline at end of file +jumpTo(1); diff --git a/apps/toucher/screenshot1.jpg b/apps/toucher/screenshot1.jpg new file mode 100644 index 0000000000000000000000000000000000000000..698121cbe9790999983c97c5d1503c749c65f28e GIT binary patch literal 21329 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

    +
    @@ -125,6 +136,10 @@ Pretokenise apps before upload (smaller, faster apps) +
    diff --git a/loader.js b/loader.js index 6528ffc98..a28f7fe78 100644 --- a/loader.js +++ b/loader.js @@ -14,6 +14,10 @@ if (window.location.host=="banglejs.com") { var RECOMMENDED_VERSION = "2v10"; // could check http://www.espruino.com/json/BANGLEJS.json for this +// We're only interested in Bangles +DEVICEINFO = DEVICEINFO.filter(x=>x.id.startsWith("BANGLEJS")); + +// Set up source code URL (function() { let username = "espruino"; let githubMatch = window.location.href.match(/\/(\w+)\.github\.io/); @@ -21,16 +25,169 @@ var RECOMMENDED_VERSION = "2v10"; Const.APP_SOURCECODE_URL = `https://github.com/${username}/BangleApps/tree/master/apps`; })(); +// When a device is found, filter the apps accordingly function onFoundDeviceInfo(deviceId, deviceVersion) { - if (deviceId != "BANGLEJS" && deviceId != "BANGLEJS2") { - showToast(`You're using ${deviceId}, not a Bangle.js. Did you want espruino.com/apps instead?` ,"warning", 20000); - } else if (versionLess(deviceVersion, RECOMMENDED_VERSION)) { - showToast(`You're using an old Bangle.js firmware (${deviceVersion}). You can update with the instructions here` ,"warning", 20000); - } + var fwURL = "#"; if (deviceId == "BANGLEJS") { + fwURL = "https://www.espruino.com/Bangle.js#firmware-updates"; Const.MESSAGE_RELOAD = 'Hold BTN3\nto reload'; } if (deviceId == "BANGLEJS2") { + fwURL = "https://www.espruino.com/Bangle.js2#firmware-updates"; Const.MESSAGE_RELOAD = 'Hold button\nto reload'; } + + if (deviceId != "BANGLEJS" && deviceId != "BANGLEJS2") { + showToast(`You're using ${deviceId}, not a Bangle.js. Did you want espruino.com/apps instead?` ,"warning", 20000); + } else if (versionLess(deviceVersion, RECOMMENDED_VERSION)) { + showToast(`You're using an old Bangle.js firmware (${deviceVersion}). You can update with the instructions here` ,"warning", 20000); + } + + + // check against features shown? + filterAppsForDevice(deviceId); + /* if we'd saved a device ID but this device is different, ensure + we ask again next time */ + var savedDeviceId = getSavedDeviceId(); + if (savedDeviceId!==undefined && savedDeviceId!=deviceId) + setSavedDeviceId(undefined); +} + +var originalAppJSON = undefined; +function filterAppsForDevice(deviceId) { + if (originalAppJSON===undefined && appJSON.length) + originalAppJSON = appJSON; + + var device = DEVICEINFO.find(d=>d.id==deviceId); + // set the device dropdown + document.querySelector(".devicetype-nav span").innerText = device ? device.name : "All apps"; + + if (!device) { + if (deviceId!==undefined) + showToast(`Device ID ${deviceId} not recognised. Some apps may not work`, "warning"); + appJSON = originalAppJSON; + } else { + // Now filter apps + appJSON = originalAppJSON.filter(app => { + var supported = ["BANGLEJS"]; + if (!app.supports) { + console.log(`App ${app.id} doesn't include a 'supports' field - ignoring`); + return false; + } + if (app.supports.includes(deviceId)) return true; + //console.log(`Dropping ${app.id} because ${deviceId} is not in supported list ${app.supports.join(",")}`); + return false; + }); + } + refreshLibrary(); +} + +// If 'remember' was checked in the window below, this is the device +function getSavedDeviceId() { + let deviceId = localStorage.getItem("deviceId"); + if (("string"==typeof deviceId) && DEVICEINFO.find(d=>d.id == deviceId)) + return deviceId; + return undefined; +} + +function setSavedDeviceId(deviceId) { + localStorage.setItem("deviceId", deviceId); +} + +// At boot, show a window to choose which type of device you have... +window.addEventListener('load', (event) => { + let deviceId = getSavedDeviceId() + if (deviceId !== undefined) return; // already chosen + + var html = `
    + ${DEVICEINFO.map(d=>` +
    +
    +
    +
    ${d.name}
    + +
    +
    + ${d.name} +
    +
    +
    `).join("\n")} +
    +
    +
    + +
    +
    +
    `; + showPrompt("Which Bangle.js?",html,{},false); + htmlToArray(document.querySelectorAll(".devicechooser")).forEach(button => { + button.addEventListener("click",event => { + let rememberDevice = document.getElementById("remember_device").checked; + + let button = event.currentTarget; + let deviceId = button.getAttribute("deviceid"); + hidePrompt(); + console.log("Chosen device", deviceId); + setSavedDeviceId(rememberDevice ? deviceId : undefined); + filterAppsForDevice(deviceId); + }); + }); +}); + +window.addEventListener('load', (event) => { + // Hook onto device chooser dropdown + htmlToArray(document.querySelectorAll(".devicetype-nav .menu-item")).forEach(button => { + button.addEventListener("click", event => { + var a = event.target; + var deviceId = a.getAttribute("dt")||undefined; + filterAppsForDevice(deviceId); // also sets the device dropdown + setSavedDeviceId(undefined); // ask at startup next time + document.querySelector(".devicetype-nav span").innerText = a.innerText; + }); + }); + + // Button to install all default apps in one go + document.getElementById("installdefault").addEventListener("click",event=>{ + getInstalledApps().then(() => { + if (device.id == "BANGLEJS") + return httpGet("defaultapps_banglejs1.json"); + if (device.id == "BANGLEJS2") + return httpGet("defaultapps_banglejs2.json"); + throw new Error("Unknown device "+device.id); + }).then(json=>{ + return installMultipleApps(JSON.parse(json), "default"); + }).catch(err=>{ + Progress.hide({sticky:true}); + showToast("App Install failed, "+err,"error"); + }); + }); +}); + +function onAppJSONLoaded() { + let deviceId = getSavedDeviceId() + if (deviceId !== undefined) + filterAppsForDevice(deviceId); + + return new Promise(resolve => { + httpGet("screenshots.json").then(screenshotJSON=>{ + var screenshots = []; + try { + screenshots = JSON.parse(screenshotJSON); + } catch(e) { + console.error("Screenshot JSON Corrupted", e); + } + screenshots.forEach(s => { + var app = appJSON.find(a=>a.id==s.id); + if (!app) return; + if (!app.screenshots) app.screenshots = []; + app.screenshots.push({url:s.url}); + }) + }).catch(err=>{ + console.log("No screenshots.json found"); + resolve(); + }); + }); } diff --git a/modules/Layout.js b/modules/Layout.js index 03aa6249b..6dc4b6368 100644 --- a/modules/Layout.js +++ b/modules/Layout.js @@ -29,7 +29,9 @@ layoutObject has: * `undefined` - blank, can be used for padding * `"txt"` - a text label, with value `label` and `r` for text rotation. 'font' is required * `"btn"` - a button, with value `label` and callback `cb` - * `"img"` - an image where `src` is an image, or a function which is called to return an image to draw + optional `src` specifies an image (like img) in which case label is ignored + * `"img"` - an image where `src` is an image, or a function which is called to return an image to draw. + optional `scale` specifies if image should be scaled up or not * `"custom"` - a custom block where `render(layoutObj)` is called to render * `"h"` - Horizontal layout, `c` is an array of more `layoutObject` * `"v"` - Veritical layout, `c` is an array of more `layoutObject` @@ -80,12 +82,12 @@ function Layout(layout, options) { this._l = this.l = layout; // Do we have >1 physical buttons? this.physBtns = (process.env.HWVERSION==2) ? 1 : 3; - this.yOffset = Object.keys(global.WIDGETS).length ? 24 : 0; options = options || {}; this.lazy = options.lazy || false; var btnList; + Bangle.setUI(); // remove all existing input handlers if (process.env.HWVERSION!=2) { // no touchscreen, find any buttons in 'layout' btnList = []; @@ -107,9 +109,7 @@ function Layout(layout, options) { delete this.buttons[s].selected; this.render(this.buttons[s]); } - s += dir; - if (s<0) s+=lh; - if (s>=l) s-=l; + s = (s+l+dir) % l; if (this.buttons[s]) { this.buttons[s].selected = 1; this.render(this.buttons[s]); @@ -131,7 +131,7 @@ function Layout(layout, options) { if (this.b[btn].cb) this.b[btn].cb(e); } // enough physical buttons - let btnHeight = Math.floor((g.getHeight()-this.yOffset) / this.physBtns); + let btnHeight = Math.floor(Bangle.appRect.h / this.physBtns); if (Bangle.btnWatch) Bangle.btnWatch.forEach(clearWatch); Bangle.btnWatch = []; if (this.physBtns > 2 && buttons.length==1) @@ -158,6 +158,7 @@ function Layout(layout, options) { } } if (process.env.HWVERSION==2) { + // Handler for touch events function touchHandler(l,e) { if (l.type=="btn" && l.cb && e.x>=l.x && e.y>=l.y && e.x<=l.x+l.w && e.y<=l.y+l.h) { @@ -169,14 +170,23 @@ function Layout(layout, options) { Bangle.on('touch',Bangle.touchHandler); } - // add IDs + // recurse over layout doing some fixing up if needed var ll = this; - function idRecurser(l) { + function recurser(l) { + // add IDs if (l.id) ll[l.id] = l; + // fix type up if (!l.type) l.type=""; - if (l.c) l.c.forEach(idRecurser); + // FIXME ':'/fsz not needed in new firmwares - Font:12 is handled internally + // fix fonts for pre-2v11 firmware + if (l.font && l.font.includes(":")) { + var f = l.font.split(":"); + l.font = f[0]; + l.fsz = f[1]; + } + if (l.c) l.c.forEach(recurser); } - idRecurser(layout); + recurser(this._l); this.updateNeeded = true; } @@ -237,10 +247,8 @@ Layout.prototype.render = function (l) { g.setFont(l.font,l.fsz).setFontAlign(0,0,l.r).drawString(l.label, l.x+(l.w>>1), l.y+(l.h>>1)); } }, "btn":function(l){ - var x = l.x+(0|l.pad); - var y = l.y+(0|l.pad); - var w = l.w-(l.pad<<1); - var h = l.h-(l.pad<<1); + var x = l.x+(0|l.pad), y = l.y+(0|l.pad), + w = l.w-(l.pad<<1), h = l.h-(l.pad<<1); var poly = [ x,y+4, x+4,y, @@ -251,10 +259,12 @@ Layout.prototype.render = function (l) { x+4,y+h-1, x,y+h-5, x,y+4 - ]; - g.setColor(l.selected?g.theme.bgH:g.theme.bg2).fillPoly(poly).setColor(l.selected ? g.theme.fgH : g.theme.fg2).drawPoly(poly).setFont("6x8",2).setFontAlign(0,0,l.r).drawString(l.label,l.x+l.w/2,l.y+l.h/2); + ], bg = l.selected?g.theme.bgH:g.theme.bg2; + g.setColor(bg).fillPoly(poly).setColor(l.selected ? g.theme.fgH : g.theme.fg2).drawPoly(poly); + if (l.src) g.setBgColor(bg).drawImage("function"==typeof l.src?l.src():l.src, l.x + 10 + (0|l.pad), l.y + 8 + (0|l.pad)); + else g.setFont("6x8",2).setFontAlign(0,0,l.r).drawString(l.label,l.x+l.w/2,l.y+l.h/2); }, "img":function(l){ - g.drawImage("function"==typeof l.src?l.src():l.src, l.x + (0|l.pad), l.y + (0|l.pad)); + g.drawImage("function"==typeof l.src?l.src():l.src, l.x + (0|l.pad), l.y + (0|l.pad), l.scale?{scale:l.scale}:undefined); }, "custom":function(l){ l.render(l); },"h":function(l) { l.c.forEach(render); }, @@ -335,10 +345,6 @@ Layout.prototype.debug = function(l,c) { }; Layout.prototype.update = function() { delete this.updateNeeded; - var l = this._l; - var w = g.getWidth(); - var y = this.yOffset; - var h = g.getHeight()-y; // update sizes function updateMin(l) {"ram" cb[l.type](l); @@ -352,12 +358,6 @@ Layout.prototype.update = function() { "txt" : function(l) { if (l.font.endsWith("%")) l.font = "Vector"+Math.round(g.getHeight()*l.font.slice(0,-1)/100); - // FIXME ':'/fsz not needed in new firmwares - it's handled internally - if (l.font.includes(":")) { - var f = l.font.split(":"); - l.font = f[0]; - l.fsz = f[1]; - } if (l.wrap) { l._h = l._w = 0; } else { @@ -365,12 +365,13 @@ Layout.prototype.update = function() { l._w = m.width; l._h = m.height; } }, "btn": function(l) { - l._h = 32; - l._w = 20 + l.label.length*12; + var m = l.src?g.imageMetrics("function"==typeof l.src?l.src():l.src):g.setFont("6x8",2).stringMetrics(l.label); + l._h = 16 + m.height; + l._w = 20 + m.width; }, "img": function(l) { - var m = g.imageMetrics("function"==typeof l.src?l.src():l.src); // get width and height out of image - l._w = m.width; - l._h = m.height; + var m = g.imageMetrics("function"==typeof l.src?l.src():l.src), s=l.scale||1; // get width and height out of image + l._w = m.width*s; + l._h = m.height*s; }, "": function(l) { // size should already be set up in width/height l._w = 0; @@ -393,18 +394,19 @@ Layout.prototype.update = function() { if (l.filly == null && l.c.some(c=>c.filly)) l.filly = 1; } }; + + var l = this._l; updateMin(l); - // center - if (l.fillx || l.filly) { - l.w = w; - l.h = h; - l.x = 0; - l.y = y; - } else { + if (l.fillx || l.filly) { // fill all + l.w = Bangle.appRect.w; + l.h = Bangle.appRect.h; + l.x = Bangle.appRect.x; + l.y = Bangle.appRect.y; + } else { // or center l.w = l._w; l.h = l._h; - l.x = (w-l.w)>>1; - l.y = y+((h-l.h)>>1); + l.x = (Bangle.appRect.w-l.w)>>1; + l.y = Bangle.appRect.y+((Bangle.appRect.h-l.h)>>1); } // layout children this.layout(l); From 7c74c5d43f9f66b6374cabd4e247d1560e5f29a1 Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Sun, 21 Nov 2021 18:15:40 -0800 Subject: [PATCH 137/155] Update apps.json --- apps.json | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/apps.json b/apps.json index 7fe259899..2b5888acf 100644 --- a/apps.json +++ b/apps.json @@ -4307,5 +4307,22 @@ {"name":"binwatch.app.js","url":"app.js"}, {"name":"binwatch.img","url":"app-icon.js","evaluate":true} ] + }, + { "id": "schoolCalendar", + "name": "School Calendar", + "shortName":"SCalendar", + "icon": "CalenderLogo.png", + "version": "beta1", + "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", + "custom":"custom.html", + "storage": [ + {"name":"schoolCalendar.app.js"}, + {"name":"schoolCalendar.img","url":"app-icon.js","evaluate":true} + ], + "data": [ + {"name":"app.json"} + ] } ] From bf994d9acc8b79ebda1e47dc5ebc5158210d6205 Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Sun, 21 Nov 2021 18:24:29 -0800 Subject: [PATCH 138/155] Update apps.json --- apps.json | 1 + 1 file changed, 1 insertion(+) diff --git a/apps.json b/apps.json index 2b5888acf..ce1f346be 100644 --- a/apps.json +++ b/apps.json @@ -4317,6 +4317,7 @@ "tags": "tool", "readme": "README.md", "custom":"custom.html", + "supports": ["BANGLEJS"], "storage": [ {"name":"schoolCalendar.app.js"}, {"name":"schoolCalendar.img","url":"app-icon.js","evaluate":true} From 426768eb11e34ea7a04010e6fe7da1a5a5187f40 Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Sun, 21 Nov 2021 20:32:14 -0800 Subject: [PATCH 139/155] Update custom.html --- apps/schoolCalendar/custom.html | 68 ++++++++++++++++++--------------- 1 file changed, 37 insertions(+), 31 deletions(-) diff --git a/apps/schoolCalendar/custom.html b/apps/schoolCalendar/custom.html index de3511e19..6cdba962d 100644 --- a/apps/schoolCalendar/custom.html +++ b/apps/schoolCalendar/custom.html @@ -254,30 +254,31 @@ function renderBackground(l) { } function renderTable(l) { + var foundNumber = findNextScheduleIndex(); + var yellowIndex = 3; + if (foundNumber < 3) { yellowIndex = foundNumber; } for(var x = 0;x<=numberOfItemsShown;x++){ g.setColor(255,255,255); g.drawRect(rectStartX,rectStart+(x*20),rectEndX,rectEnd+(20*x)); - g.setColor(255,205,0); - g.drawRect(rectStartX,rectStart+(2*20),rectEndX,rectEnd+(2*20)); - g.setColor(255,0,0); - g.drawRect(rectStartX,rectStart+(currentPositionTable*20),rectEndX,rectEnd+(20*currentPositionTable)); } + g.setColor(255,205,0); + g.drawRect(rectStartX,rectStart+(yellowIndex*20),rectEndX,rectEnd+(20*yellowIndex)); + g.setColor(255,0,0); + g.drawRect(rectStartX,rectStart+(currentPositionTable*20),rectEndX,rectEnd+(20*currentPositionTable)); } -function renderTableText(l){ - var foundNumber = findNextScheduleIndex(); +function renderTableText(l) { var foundSchedule = getScheduleTable(); + var foundNumber = findNextScheduleIndex(); + var startNumber = foundNumber - 2; + if (startNumber < 0) { startNumber = 0; } + var endNumber = startNumber + 8 - (foundNumber - startNumber); + if (endNumber > foundSchedule.length-1) { endNumber = foundSchedule.length-1; } + + var scheduleHourUpdated; var scheduleMinuteUpdated; - var beforeFoundNumber = foundNumber - 2; - for(var x = 0;x<=numberOfItemsShown;x++){ - var currentNumber = beforeFoundNumber + x; - if (beforeFoundNumber + x < 0) { - currentNumber = foundSchedule.length + beforeFoundNumber + x; - } else if (beforeFoundNumber + x > foundSchedule.length - 1) { - currentNumber = beforeFoundNumber + x - foundSchedule.length; - } - + for(var currentNumber = startNumber; currentNumber<=endNumber; currentNumber++){ scheduleMinuteUpdatedStart = updateMinutesToCurrentTime(foundSchedule[currentNumber].sm); scheduleHourUpdatedStart = foundSchedule[currentNumber].sh; scheduleMinuteUpdatedEnd = updateMinutesToCurrentTime(foundSchedule[currentNumber].em); @@ -288,7 +289,7 @@ function renderTableText(l){ } schduleDay = updateDay(3,foundSchedule[currentNumber].dow); g.setFont("8x12"); - g.drawString(scheduleHourUpdatedStart+":"+scheduleMinuteUpdatedStart+"-"+scheduleHourUpdatedEnd+":"+scheduleMinuteUpdatedEnd+" "+schduleDay+" "+scheduleDecriptionUpdated,13,50+(x*20)); + g.drawString(scheduleHourUpdatedStart+":"+scheduleMinuteUpdatedStart+"-"+scheduleHourUpdatedEnd+":"+scheduleMinuteUpdatedEnd+" "+schduleDay+" "+scheduleDecriptionUpdated,13,50+(currentNumber*20)); } } @@ -364,22 +365,27 @@ function renderLoading(l){ function renderInformation(l){ var foundNumber = findNextScheduleIndex(); var foundSchedule = getScheduleTable(); - scheduleMinuteUpdatedStart = updateMinutesToCurrentTime(foundSchedule[foundNumber].sm); - scheduleHourUpdatedStart = foundSchedule[foundNumber].sh; - scheduleMinuteUpdatedEnd = updateMinutesToCurrentTime(foundSchedule[foundNumber].em); - scheduleHourUpdatedEnd = foundSchedule[foundNumber].eh; - scheduleDay = updateDay(1,foundSchedule[((foundNumber-2)+currentPositionTable)].dow); - g.setColor(255,255,255); - g.setFont("8x12",2); - var splitClassNames = splitter(foundSchedule[((foundNumber-2)+currentPositionTable)].cn, 15); - var currentY = 5; - for (var j=0; j < splitClassNames.length; j++) { - g.drawString(splitClassNames[j],13,currentY+50); - currentY = currentY + 25; + var startNumber = foundNumber - 2; + if (startNumber < 0) { startNumber = 0; } + + if ((startNumber+currentPositionTable) <= foundSchedule.length-1) { + scheduleMinuteUpdatedStart = updateMinutesToCurrentTime(foundSchedule[foundNumber].sm); + scheduleHourUpdatedStart = foundSchedule[foundNumber].sh; + scheduleMinuteUpdatedEnd = updateMinutesToCurrentTime(foundSchedule[foundNumber].em); + scheduleHourUpdatedEnd = foundSchedule[foundNumber].eh; + scheduleDay = updateDay(1,foundSchedule[(startNumber+currentPositionTable)].dow); + g.setColor(255,255,255); + g.setFont("8x12",2); + var splitClassNames = splitter(foundSchedule[(startNumber+currentPositionTable)].cn, 15); + var currentY = 5; + for (var j=0; j < splitClassNames.length; j++) { + g.drawString(splitClassNames[j],13,currentY+50); + currentY = currentY + 25; + } + g.setFont("8x12"); + g.drawString(schduleDay,13,currentY+50); + g.drawString(scheduleHourUpdatedStart+":"+scheduleMinuteUpdatedStart+"-"+scheduleHourUpdatedEnd+":"+scheduleMinuteUpdatedEnd,13,currentY+15+50); } - g.setFont("8x12"); - g.drawString(schduleDay,13,currentY+50); - g.drawString(scheduleHourUpdatedStart+":"+scheduleMinuteUpdatedStart+"-"+scheduleHourUpdatedEnd+":"+scheduleMinuteUpdatedEnd,13,currentY+15+50); } var Layout = require("Layout"); From 854ae5361d1b2400f6b6d1b2c2239f46d1e16bc9 Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Sun, 21 Nov 2021 20:34:28 -0800 Subject: [PATCH 140/155] Update README.md --- apps/schoolCalendar/README.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/apps/schoolCalendar/README.md b/apps/schoolCalendar/README.md index 47815cc87..fd95ddf59 100644 --- a/apps/schoolCalendar/README.md +++ b/apps/schoolCalendar/README.md @@ -7,8 +7,11 @@ Enter your calendar events on the customizer then upload. (all day events are no Once uploaded on the watch when in the table mode you can use BTN1 and BTN3 to scroll up and down on the list. (The red rectangle indicates your current position on the table and your yellow rectangle indicates your current schedule item or your next schedule item.) -If you press BTN2 it will go into detail mode, and you can see additional information about your schedule item. Also, in this mode you can scroll up and down with BTN1 and BTN3 to move around in the table. +If you press BTN2 it will go into detail mode, and you can see additional information about your schedule item. Also, in this mode you can scroll up and down with BTN1 and BTN3 to move around in the table. To exit detail mode press BTN2 again. +## Screenshots: +![screenshot (5)](https://user-images.githubusercontent.com/89286474/142801592-485aa0b0-c417-44c8-8097-befa81d2599c.png) +![screenshot (6)](https://user-images.githubusercontent.com/89286474/142801595-47f73c63-501a-4221-baba-84dd97b65bf9.png) ## Updates Coming Soon: - [ ] Notifications @@ -17,7 +20,7 @@ If you press BTN2 it will go into detail mode, and you can see additional inform - [ ] Better Graphics - [ ] Scrolling Table - [ ] Bangle.js V2 Compatibility -- [ ] Orginal Calendar +- [ ] Full Calendar (Calendar that does not have repeating weekly events.) ## Creator Ronin0000 From 939d8f6a9ae71b49c13f7dbf1455bb2fec60462a Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Sun, 21 Nov 2021 20:37:21 -0800 Subject: [PATCH 141/155] Add files via upload --- apps/schoolCalendar/screenshot_basic.png | Bin 0 -> 6482 bytes apps/schoolCalendar/screenshot_info.png | Bin 0 -> 4021 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 apps/schoolCalendar/screenshot_basic.png create mode 100644 apps/schoolCalendar/screenshot_info.png diff --git a/apps/schoolCalendar/screenshot_basic.png b/apps/schoolCalendar/screenshot_basic.png new file mode 100644 index 0000000000000000000000000000000000000000..4872f02c4abc79547e948b31cd4d2cdba6a0bbdd GIT binary patch literal 6482 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 Gr~d_o@GQOn literal 0 HcmV?d00001 diff --git a/apps/schoolCalendar/screenshot_info.png b/apps/schoolCalendar/screenshot_info.png new file mode 100644 index 0000000000000000000000000000000000000000..c539b1b1c5c035e5970deb8347568f545bd96510 GIT binary patch literal 4021 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 Date: Sun, 21 Nov 2021 20:37:40 -0800 Subject: [PATCH 142/155] Update apps.json --- apps.json | 1 + 1 file changed, 1 insertion(+) diff --git a/apps.json b/apps.json index ce1f346be..a6badbce3 100644 --- a/apps.json +++ b/apps.json @@ -4318,6 +4318,7 @@ "readme": "README.md", "custom":"custom.html", "supports": ["BANGLEJS"], + "screenshots": [{"url":"screenshot_basic.png"},{"url":"screenshot_info.png"}], "storage": [ {"name":"schoolCalendar.app.js"}, {"name":"schoolCalendar.img","url":"app-icon.js","evaluate":true} From 6e07f282b8918ac3b8f333c7c4156980a44ff1d6 Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Sun, 21 Nov 2021 20:41:26 -0800 Subject: [PATCH 143/155] Add files via upload --- apps/schoolCalendar/screenshot_basic.png | Bin 6482 -> 6464 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/apps/schoolCalendar/screenshot_basic.png b/apps/schoolCalendar/screenshot_basic.png index 4872f02c4abc79547e948b31cd4d2cdba6a0bbdd..879ad6a3c7cc7e054a29ef66fb48fc7edee929ed 100644 GIT binary patch delta 5816 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 Date: Sun, 21 Nov 2021 20:48:14 -0800 Subject: [PATCH 144/155] Rename ChangeLog.md to ChangeLog --- apps/schoolCalendar/{ChangeLog.md => ChangeLog} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename apps/schoolCalendar/{ChangeLog.md => ChangeLog} (100%) diff --git a/apps/schoolCalendar/ChangeLog.md b/apps/schoolCalendar/ChangeLog similarity index 100% rename from apps/schoolCalendar/ChangeLog.md rename to apps/schoolCalendar/ChangeLog From 3ee0e9bae6ee137c6e36672c9aa6a20d1c072caf Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Sun, 21 Nov 2021 20:49:48 -0800 Subject: [PATCH 145/155] Update apps.json --- apps.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps.json b/apps.json index a6badbce3..51e5978ee 100644 --- a/apps.json +++ b/apps.json @@ -4312,7 +4312,7 @@ "name": "School Calendar", "shortName":"SCalendar", "icon": "CalenderLogo.png", - "version": "beta1", + "version": "0.01", "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 f950fd2ee88486e3355efd30823d50de799e8418 Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Sun, 21 Nov 2021 20:57:52 -0800 Subject: [PATCH 146/155] Update custom.html --- apps/schoolCalendar/custom.html | 369 +++++++++++++++++++++++++++++++- 1 file changed, 361 insertions(+), 8 deletions(-) diff --git a/apps/schoolCalendar/custom.html b/apps/schoolCalendar/custom.html index 6cdba962d..6c288d600 100644 --- a/apps/schoolCalendar/custom.html +++ b/apps/schoolCalendar/custom.html @@ -79,8 +79,6 @@ var calendarEvents = calendar.getEvents(); let schedule = [] //-------------------- - console.log = console.log || function(){}; - //-------------------- for(i=0;i minuteOfWeek) { + return currentPosition; + } + } + return 0; +} + + +function getUpArrow() {return require("heatshrink").decompress(atob("hkOyANKmv9AIIjRCoYZRlvdAI8U3YVK3oBJC4Mc7YVRC4sc7gVCzoBNC4oZDGowXGR58lvoBFC9FcAIoXongBFC58dngBFC6EcAIoPHA"));} + +function getDownArrow() {return require("heatshrink").decompress(atob("hkOyALImv9AIojPmvdAIoXPlvdAIoXQ3oBFC9GdAIoXnkt9AIoPPAI8U3cc7cc7gBBDIVcAJYXFGYwXOLpU8AI4XBO5sdjgBFR54ZFBpIA=="));} + +function getMenuIcon() {return require("heatshrink").decompress(atob("iEQyBC/AEU+rwBEn02js17st3stvklrkljkc/cc3cUzYBBD5AdUD4oA/P/4A/P/4A/ADoA=="));} + +function getDotIcon() {return require("heatshrink").decompress(atob("iEQyBC/AA0t3oBBA4ndAIIPGA4gAFkt9lt9AYIHEzoBBBIwRED41cks8AYIJGA44RGP8xtGP44RJBYh1CAIIHHBJJ/KroBBPoqBFB4YRDAA8dngHHBJKdq3oBDBI4RNP4l9AIYHHBJJBJks8AIIHTAH4ABA="));} + +var currentPositionTable = 0; +var numberOfItemsShown = 8; +//Table Positions: +var rectStart = 45; +var rectEnd = 65; +var rectStartX = 10; +var rectEndX = 210; +//Scences: +LIST = 1; +INFORMATION = 2; +currentStage = LIST; + +function splitter(str, l){ + var strs = []; + while(str.length > l){ + var pos = str.substring(0, l).lastIndexOf(' '); + pos = pos <= 0 ? l : pos; + strs.push(str.substring(0, pos)); + var i = str.indexOf(' ', pos)+1; + if(i < pos || i > pos+l) + i = pos; + str = str.substring(i); + } + strs.push(str); + return strs; +} + +function updateMinutesToCurrentTime(currentMinuteFunction) { + if (currentMinuteFunction<10){ + currentMinuteUpdatedFunction = "0"+currentMinuteFunction; + }else{ + currentMinuteUpdatedFunction = currentMinuteFunction; + } + return currentMinuteUpdatedFunction; +} + +function renderBackground(l) { + g.clearRect(0,0,240,20); + g.drawImage(getBackgroundImage(),110,130,{scale:9,rotate:0}); +} + +function renderTable(l) { + var foundNumber = findNextScheduleIndex(); + var yellowIndex = 3; + if (foundNumber < 3) { yellowIndex = foundNumber; } + for(var x = 0;x<=numberOfItemsShown;x++){ + g.setColor(255,255,255); + g.drawRect(rectStartX,rectStart+(x*20),rectEndX,rectEnd+(20*x)); + } + g.setColor(255,205,0); + g.drawRect(rectStartX,rectStart+(yellowIndex*20),rectEndX,rectEnd+(20*yellowIndex)); + g.setColor(255,0,0); + g.drawRect(rectStartX,rectStart+(currentPositionTable*20),rectEndX,rectEnd+(20*currentPositionTable)); +} + +function renderTableText(l) { + var foundSchedule = getScheduleTable(); + var foundNumber = findNextScheduleIndex(); + var startNumber = foundNumber - 2; + if (startNumber < 0) { startNumber = 0; } + var endNumber = startNumber + 8 - (foundNumber - startNumber); + if (endNumber > foundSchedule.length-1) { endNumber = foundSchedule.length-1; } + + + var scheduleHourUpdated; + var scheduleMinuteUpdated; + for(var currentNumber = startNumber; currentNumber<=endNumber; currentNumber++){ + scheduleMinuteUpdatedStart = updateMinutesToCurrentTime(foundSchedule[currentNumber].sm); + scheduleHourUpdatedStart = foundSchedule[currentNumber].sh; + scheduleMinuteUpdatedEnd = updateMinutesToCurrentTime(foundSchedule[currentNumber].em); + scheduleHourUpdatedEnd = foundSchedule[currentNumber].eh; + scheduleDecriptionUpdated = foundSchedule[currentNumber].cn.substring(0, 20); + if(foundSchedule[currentNumber].cn.length >= 15){ + scheduleDecriptionUpdated = foundSchedule[currentNumber].cn.substring(0, 20)+"..."; + } + schduleDay = updateDay(3,foundSchedule[currentNumber].dow); + g.setFont("8x12"); + g.drawString(scheduleHourUpdatedStart+":"+scheduleMinuteUpdatedStart+"-"+scheduleHourUpdatedEnd+":"+scheduleMinuteUpdatedEnd+" "+schduleDay+" "+scheduleDecriptionUpdated,13,50+(currentNumber*20)); + } +} + +function buttonsF(l){ + if(currentStage == LIST){ + g.drawImage(getDotIcon(),223.5,115); + }else{ + g.drawImage(getMenuIcon(),223.5,115); + } + g.drawImage(getUpArrow(),225,30); + g.drawImage(getDownArrow(),225,215); +} + +function draw() { + var currentDate = new Date(); + var currentDayOfWeek = currentDate.getDay(); + var currentHour = currentDate.getHours(); + var currentMinute = currentDate.getMinutes(); + var currentMinuteUpdated = updateMinutesToCurrentTime(currentMinute); + if (layout) { + if(currentStage == LIST){ + layout.time.label = currentHour+":"+currentMinuteUpdated; + layout.time.x = 147; + layout.time.y = 10; + layout.render(layout.table); + layout.render(layout.tableText); + logDebug("Rendered"+currentPositionTable); + }else{ + layout.time.label = currentHour+":"+currentMinuteUpdated; + layout.time.x = 147; + layout.time.y = 10; + layout.render(layout.info); + logDebug("Rendered"+currentPositionTable); + } + g.clearRect(150,0,220,35); + layout.render(layout.time); + } +} + +function RedRectDown() { + if(currentPositionTable > 0){ + currentPositionTable -= 1; + if(currentStage == INFORMATION){ + redrawScreen(); + }else{ + draw(); + } + } +} + +function RedRectUp() { + if(currentPositionTable < numberOfItemsShown){ + currentPositionTable += 1; + if(currentStage == INFORMATION){ + redrawScreen(); + }else{ + draw(); + } + } +} + +function renderMiniBackground(l){ + for(var i = 233;i<=240;i++){ + g.drawImage(getBackgroundImage(),i,123,{scale:10,rotate:0}); + } +} + +function renderLoading(l){ + g.setFont("8x12"); + g.drawString("Loading...",240/2-20,240/2-20); +} + +function renderInformation(l){ + var foundNumber = findNextScheduleIndex(); + var foundSchedule = getScheduleTable(); + var startNumber = foundNumber - 2; + if (startNumber < 0) { startNumber = 0; } + + if ((startNumber+currentPositionTable) <= foundSchedule.length-1) { + scheduleMinuteUpdatedStart = updateMinutesToCurrentTime(foundSchedule[foundNumber].sm); + scheduleHourUpdatedStart = foundSchedule[foundNumber].sh; + scheduleMinuteUpdatedEnd = updateMinutesToCurrentTime(foundSchedule[foundNumber].em); + scheduleHourUpdatedEnd = foundSchedule[foundNumber].eh; + scheduleDay = updateDay(1,foundSchedule[(startNumber+currentPositionTable)].dow); + g.setColor(255,255,255); + g.setFont("8x12",2); + var splitClassNames = splitter(foundSchedule[(startNumber+currentPositionTable)].cn, 15); + var currentY = 5; + for (var j=0; j < splitClassNames.length; j++) { + g.drawString(splitClassNames[j],13,currentY+50); + currentY = currentY + 25; + } + g.setFont("8x12"); + g.drawString(schduleDay,13,currentY+50); + g.drawString(scheduleHourUpdatedStart+":"+scheduleMinuteUpdatedStart+"-"+scheduleHourUpdatedEnd+":"+scheduleMinuteUpdatedEnd,13,currentY+15+50); + } +} + +var Layout = require("Layout"); +var layout = new Layout( + {type:"h", c: [ + {type:"custom", render:renderTableText, id:"tableText"}, + {type:"custom", render:buttonsF, id:"buttons"}, + {type:"custom", render:renderBackground, id:"background"}, + {type:"custom", render:renderTable, id:"table"}, + {type:"custom", render:renderMiniBackground, id:"miniBackground"}, + {type:"custom", render:renderLoading, id:"loading"}, + {type:"custom", render:renderInformation, id:"info"}, + {type:"txt", font:"7x11Numeric7Seg:2", label:"00:00", id:"time"}, + ]}, + {type:"v", c:[ + ]}, + {btns:[ + {label:"", cb: RedRectUp()}, + {label:"", cb: l=>print("Two")}, + {label:"", cb: RedRectDown()} +]}); + + +function getBackgroundImage() {return require("heatshrink").decompress(atob("j0ZyEKIf4A4gIB6gQB6gYB6ggB6goB6gwB6g4B6hAABAYIBHBZIVLAK8IhIBXgAThhQB6hYB6hgB6hoB6hwB6h4B6iAB6iIB6iQBHiAJOB54XSiYB6igB6ioB6iwB6i4B5A="));} + +function logDebug(message) {console.log(message);} + +function changeScene(){ + layout.render(layout.buttons); + if(currentStage == INFORMATION){ + currentStage = LIST; + nIntervId = setInterval(redrawScreen, 100000); + }else if(currentStage == LIST){ + currentStage = INFORMATION; + clearInterval(); + } + layout.render(layout.background); + layout.render(layout.buttons); + draw(); +} + +// timeout used to update every minute +var drawTimeout; + +setInterval(draw, 15000); + + +setWatch(RedRectUp, BTN3, { repeat:true, edge:'rising', debounce : 50 }); +setWatch(RedRectDown, BTN1, { repeat:true, edge:'rising', debounce : 50 }); +setWatch(changeScene, BTN2, { repeat:true, edge:'rising', debounce : 50 }); + +layout.update(); +layout.render(layout.loading); +layout.render(layout.background); +layout.render(layout.buttons); + +draw(); +`; window.open("https://www.espruino.com/ide/emulator.html?code="+encodeURIComponent(getApp())+"&upload"); }); From adfeb5e535564f8851fcdc080e2fa10d9acced9a Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Sun, 21 Nov 2021 21:00:21 -0800 Subject: [PATCH 147/155] 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 6c288d600..76388c01e 100644 --- a/apps/schoolCalendar/custom.html +++ b/apps/schoolCalendar/custom.html @@ -25,7 +25,7 @@

    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

    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.

    @@ -444,7 +444,7 @@ draw(); }); }); - document.getElementById("upload").addEventListener("click", function () { + document.getElementById("emulator").addEventListener("click", function () { //Cacultate data: var calendarEvents = calendar.getEvents(); let schedule = [] From 618c5b7a10ebe610205ab463c395f65948e5a763 Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Sun, 21 Nov 2021 21:02:29 -0800 Subject: [PATCH 148/155] Update custom.html --- apps/schoolCalendar/custom.html | 367 +------------------------------- 1 file changed, 1 insertion(+), 366 deletions(-) diff --git a/apps/schoolCalendar/custom.html b/apps/schoolCalendar/custom.html index 76388c01e..49e0acb13 100644 --- a/apps/schoolCalendar/custom.html +++ b/apps/schoolCalendar/custom.html @@ -25,7 +25,7 @@

    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

    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.

    @@ -443,371 +443,6 @@ draw(); ] }); }); - - document.getElementById("emulator").addEventListener("click", function () { - //Cacultate data: - var calendarEvents = calendar.getEvents(); - let schedule = [] - //-------------------- - for(i=0;i minuteOfWeek) { - return currentPosition; - } - } - return 0; -} - - -function getUpArrow() {return require("heatshrink").decompress(atob("hkOyANKmv9AIIjRCoYZRlvdAI8U3YVK3oBJC4Mc7YVRC4sc7gVCzoBNC4oZDGowXGR58lvoBFC9FcAIoXongBFC58dngBFC6EcAIoPHA"));} - -function getDownArrow() {return require("heatshrink").decompress(atob("hkOyALImv9AIojPmvdAIoXPlvdAIoXQ3oBFC9GdAIoXnkt9AIoPPAI8U3cc7cc7gBBDIVcAJYXFGYwXOLpU8AI4XBO5sdjgBFR54ZFBpIA=="));} - -function getMenuIcon() {return require("heatshrink").decompress(atob("iEQyBC/AEU+rwBEn02js17st3stvklrkljkc/cc3cUzYBBD5AdUD4oA/P/4A/P/4A/ADoA=="));} - -function getDotIcon() {return require("heatshrink").decompress(atob("iEQyBC/AA0t3oBBA4ndAIIPGA4gAFkt9lt9AYIHEzoBBBIwRED41cks8AYIJGA44RGP8xtGP44RJBYh1CAIIHHBJJ/KroBBPoqBFB4YRDAA8dngHHBJKdq3oBDBI4RNP4l9AIYHHBJJBJks8AIIHTAH4ABA="));} - -var currentPositionTable = 0; -var numberOfItemsShown = 8; -//Table Positions: -var rectStart = 45; -var rectEnd = 65; -var rectStartX = 10; -var rectEndX = 210; -//Scences: -LIST = 1; -INFORMATION = 2; -currentStage = LIST; - -function splitter(str, l){ - var strs = []; - while(str.length > l){ - var pos = str.substring(0, l).lastIndexOf(' '); - pos = pos <= 0 ? l : pos; - strs.push(str.substring(0, pos)); - var i = str.indexOf(' ', pos)+1; - if(i < pos || i > pos+l) - i = pos; - str = str.substring(i); - } - strs.push(str); - return strs; -} - -function updateMinutesToCurrentTime(currentMinuteFunction) { - if (currentMinuteFunction<10){ - currentMinuteUpdatedFunction = "0"+currentMinuteFunction; - }else{ - currentMinuteUpdatedFunction = currentMinuteFunction; - } - return currentMinuteUpdatedFunction; -} - -function renderBackground(l) { - g.clearRect(0,0,240,20); - g.drawImage(getBackgroundImage(),110,130,{scale:9,rotate:0}); -} - -function renderTable(l) { - var foundNumber = findNextScheduleIndex(); - var yellowIndex = 3; - if (foundNumber < 3) { yellowIndex = foundNumber; } - for(var x = 0;x<=numberOfItemsShown;x++){ - g.setColor(255,255,255); - g.drawRect(rectStartX,rectStart+(x*20),rectEndX,rectEnd+(20*x)); - } - g.setColor(255,205,0); - g.drawRect(rectStartX,rectStart+(yellowIndex*20),rectEndX,rectEnd+(20*yellowIndex)); - g.setColor(255,0,0); - g.drawRect(rectStartX,rectStart+(currentPositionTable*20),rectEndX,rectEnd+(20*currentPositionTable)); -} - -function renderTableText(l) { - var foundSchedule = getScheduleTable(); - var foundNumber = findNextScheduleIndex(); - var startNumber = foundNumber - 2; - if (startNumber < 0) { startNumber = 0; } - var endNumber = startNumber + 8 - (foundNumber - startNumber); - if (endNumber > foundSchedule.length-1) { endNumber = foundSchedule.length-1; } - - - var scheduleHourUpdated; - var scheduleMinuteUpdated; - for(var currentNumber = startNumber; currentNumber<=endNumber; currentNumber++){ - scheduleMinuteUpdatedStart = updateMinutesToCurrentTime(foundSchedule[currentNumber].sm); - scheduleHourUpdatedStart = foundSchedule[currentNumber].sh; - scheduleMinuteUpdatedEnd = updateMinutesToCurrentTime(foundSchedule[currentNumber].em); - scheduleHourUpdatedEnd = foundSchedule[currentNumber].eh; - scheduleDecriptionUpdated = foundSchedule[currentNumber].cn.substring(0, 20); - if(foundSchedule[currentNumber].cn.length >= 15){ - scheduleDecriptionUpdated = foundSchedule[currentNumber].cn.substring(0, 20)+"..."; - } - schduleDay = updateDay(3,foundSchedule[currentNumber].dow); - g.setFont("8x12"); - g.drawString(scheduleHourUpdatedStart+":"+scheduleMinuteUpdatedStart+"-"+scheduleHourUpdatedEnd+":"+scheduleMinuteUpdatedEnd+" "+schduleDay+" "+scheduleDecriptionUpdated,13,50+(currentNumber*20)); - } -} - -function buttonsF(l){ - if(currentStage == LIST){ - g.drawImage(getDotIcon(),223.5,115); - }else{ - g.drawImage(getMenuIcon(),223.5,115); - } - g.drawImage(getUpArrow(),225,30); - g.drawImage(getDownArrow(),225,215); -} - -function draw() { - var currentDate = new Date(); - var currentDayOfWeek = currentDate.getDay(); - var currentHour = currentDate.getHours(); - var currentMinute = currentDate.getMinutes(); - var currentMinuteUpdated = updateMinutesToCurrentTime(currentMinute); - if (layout) { - if(currentStage == LIST){ - layout.time.label = currentHour+":"+currentMinuteUpdated; - layout.time.x = 147; - layout.time.y = 10; - layout.render(layout.table); - layout.render(layout.tableText); - logDebug("Rendered"+currentPositionTable); - }else{ - layout.time.label = currentHour+":"+currentMinuteUpdated; - layout.time.x = 147; - layout.time.y = 10; - layout.render(layout.info); - logDebug("Rendered"+currentPositionTable); - } - g.clearRect(150,0,220,35); - layout.render(layout.time); - } -} - -function RedRectDown() { - if(currentPositionTable > 0){ - currentPositionTable -= 1; - if(currentStage == INFORMATION){ - redrawScreen(); - }else{ - draw(); - } - } -} - -function RedRectUp() { - if(currentPositionTable < numberOfItemsShown){ - currentPositionTable += 1; - if(currentStage == INFORMATION){ - redrawScreen(); - }else{ - draw(); - } - } -} - -function renderMiniBackground(l){ - for(var i = 233;i<=240;i++){ - g.drawImage(getBackgroundImage(),i,123,{scale:10,rotate:0}); - } -} - -function renderLoading(l){ - g.setFont("8x12"); - g.drawString("Loading...",240/2-20,240/2-20); -} - -function renderInformation(l){ - var foundNumber = findNextScheduleIndex(); - var foundSchedule = getScheduleTable(); - var startNumber = foundNumber - 2; - if (startNumber < 0) { startNumber = 0; } - - if ((startNumber+currentPositionTable) <= foundSchedule.length-1) { - scheduleMinuteUpdatedStart = updateMinutesToCurrentTime(foundSchedule[foundNumber].sm); - scheduleHourUpdatedStart = foundSchedule[foundNumber].sh; - scheduleMinuteUpdatedEnd = updateMinutesToCurrentTime(foundSchedule[foundNumber].em); - scheduleHourUpdatedEnd = foundSchedule[foundNumber].eh; - scheduleDay = updateDay(1,foundSchedule[(startNumber+currentPositionTable)].dow); - g.setColor(255,255,255); - g.setFont("8x12",2); - var splitClassNames = splitter(foundSchedule[(startNumber+currentPositionTable)].cn, 15); - var currentY = 5; - for (var j=0; j < splitClassNames.length; j++) { - g.drawString(splitClassNames[j],13,currentY+50); - currentY = currentY + 25; - } - g.setFont("8x12"); - g.drawString(schduleDay,13,currentY+50); - g.drawString(scheduleHourUpdatedStart+":"+scheduleMinuteUpdatedStart+"-"+scheduleHourUpdatedEnd+":"+scheduleMinuteUpdatedEnd,13,currentY+15+50); - } -} - -var Layout = require("Layout"); -var layout = new Layout( - {type:"h", c: [ - {type:"custom", render:renderTableText, id:"tableText"}, - {type:"custom", render:buttonsF, id:"buttons"}, - {type:"custom", render:renderBackground, id:"background"}, - {type:"custom", render:renderTable, id:"table"}, - {type:"custom", render:renderMiniBackground, id:"miniBackground"}, - {type:"custom", render:renderLoading, id:"loading"}, - {type:"custom", render:renderInformation, id:"info"}, - {type:"txt", font:"7x11Numeric7Seg:2", label:"00:00", id:"time"}, - ]}, - {type:"v", c:[ - ]}, - {btns:[ - {label:"", cb: RedRectUp()}, - {label:"", cb: l=>print("Two")}, - {label:"", cb: RedRectDown()} -]}); - - -function getBackgroundImage() {return require("heatshrink").decompress(atob("j0ZyEKIf4A4gIB6gQB6gYB6ggB6goB6gwB6g4B6hAABAYIBHBZIVLAK8IhIBXgAThhQB6hYB6hgB6hoB6hwB6h4B6iAB6iIB6iQBHiAJOB54XSiYB6igB6ioB6iwB6i4B5A="));} - -function logDebug(message) {console.log(message);} - -function changeScene(){ - layout.render(layout.buttons); - if(currentStage == INFORMATION){ - currentStage = LIST; - nIntervId = setInterval(redrawScreen, 100000); - }else if(currentStage == LIST){ - currentStage = INFORMATION; - clearInterval(); - } - layout.render(layout.background); - layout.render(layout.buttons); - draw(); -} - -// timeout used to update every minute -var drawTimeout; - -setInterval(draw, 15000); - - -setWatch(RedRectUp, BTN3, { repeat:true, edge:'rising', debounce : 50 }); -setWatch(RedRectDown, BTN1, { repeat:true, edge:'rising', debounce : 50 }); -setWatch(changeScene, BTN2, { repeat:true, edge:'rising', debounce : 50 }); - -layout.update(); -layout.render(layout.loading); -layout.render(layout.background); -layout.render(layout.buttons); - -draw(); -`; - window.open("https://www.espruino.com/ide/emulator.html?code="+encodeURIComponent(getApp())+"&upload"); - });
    From 3863be18301cec9fec480db7fe28a19439c708d2 Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Mon, 22 Nov 2021 09:06:52 -0800 Subject: [PATCH 150/155] Delete apps/schoolCalendar/fullcalendar/interaction/src directory --- .../interaction/src/ElementScrollGeomCache.ts | 16 - .../interaction/src/OffsetTracker.ts | 76 --- .../interaction/src/ScrollGeomCache.ts | 107 ---- .../interaction/src/WindowScrollGeomCache.ts | 27 - .../interaction/src/api-type-deps.ts | 6 - .../interaction/src/dnd/AutoScroller.ts | 217 -------- .../interaction/src/dnd/ElementMirror.ts | 146 ------ .../src/dnd/FeaturefulElementDragging.ts | 213 -------- .../interaction/src/dnd/PointerDragging.ts | 344 ------------- .../ExternalDraggable.ts | 70 --- .../ExternalElementDragging.ts | 268 ---------- .../InferredElementDragging.ts | 77 --- .../ThirdPartyDraggable.ts | 52 -- .../src/interactions/DateClicking.ts | 71 --- .../src/interactions/DateSelecting.ts | 152 ------ .../src/interactions/EventDragging.ts | 482 ------------------ .../src/interactions/EventResizing.ts | 263 ---------- .../src/interactions/HitDragging.ts | 220 -------- .../src/interactions/UnselectAuto.ts | 77 --- .../interaction/src/main.global.ts | 7 - .../fullcalendar/interaction/src/main.ts | 23 - .../interaction/src/options-declare.ts | 9 - .../fullcalendar/interaction/src/options.ts | 26 - .../fullcalendar/interaction/src/utils.ts | 38 -- 24 files changed, 2987 deletions(-) delete mode 100644 apps/schoolCalendar/fullcalendar/interaction/src/ElementScrollGeomCache.ts delete mode 100644 apps/schoolCalendar/fullcalendar/interaction/src/OffsetTracker.ts delete mode 100644 apps/schoolCalendar/fullcalendar/interaction/src/ScrollGeomCache.ts delete mode 100644 apps/schoolCalendar/fullcalendar/interaction/src/WindowScrollGeomCache.ts delete mode 100644 apps/schoolCalendar/fullcalendar/interaction/src/api-type-deps.ts delete mode 100644 apps/schoolCalendar/fullcalendar/interaction/src/dnd/AutoScroller.ts delete mode 100644 apps/schoolCalendar/fullcalendar/interaction/src/dnd/ElementMirror.ts delete mode 100644 apps/schoolCalendar/fullcalendar/interaction/src/dnd/FeaturefulElementDragging.ts delete mode 100644 apps/schoolCalendar/fullcalendar/interaction/src/dnd/PointerDragging.ts delete mode 100644 apps/schoolCalendar/fullcalendar/interaction/src/interactions-external/ExternalDraggable.ts delete mode 100644 apps/schoolCalendar/fullcalendar/interaction/src/interactions-external/ExternalElementDragging.ts delete mode 100644 apps/schoolCalendar/fullcalendar/interaction/src/interactions-external/InferredElementDragging.ts delete mode 100644 apps/schoolCalendar/fullcalendar/interaction/src/interactions-external/ThirdPartyDraggable.ts delete mode 100644 apps/schoolCalendar/fullcalendar/interaction/src/interactions/DateClicking.ts delete mode 100644 apps/schoolCalendar/fullcalendar/interaction/src/interactions/DateSelecting.ts delete mode 100644 apps/schoolCalendar/fullcalendar/interaction/src/interactions/EventDragging.ts delete mode 100644 apps/schoolCalendar/fullcalendar/interaction/src/interactions/EventResizing.ts delete mode 100644 apps/schoolCalendar/fullcalendar/interaction/src/interactions/HitDragging.ts delete mode 100644 apps/schoolCalendar/fullcalendar/interaction/src/interactions/UnselectAuto.ts delete mode 100644 apps/schoolCalendar/fullcalendar/interaction/src/main.global.ts delete mode 100644 apps/schoolCalendar/fullcalendar/interaction/src/main.ts delete mode 100644 apps/schoolCalendar/fullcalendar/interaction/src/options-declare.ts delete mode 100644 apps/schoolCalendar/fullcalendar/interaction/src/options.ts delete mode 100644 apps/schoolCalendar/fullcalendar/interaction/src/utils.ts diff --git a/apps/schoolCalendar/fullcalendar/interaction/src/ElementScrollGeomCache.ts b/apps/schoolCalendar/fullcalendar/interaction/src/ElementScrollGeomCache.ts deleted file mode 100644 index 83be540cd..000000000 --- a/apps/schoolCalendar/fullcalendar/interaction/src/ElementScrollGeomCache.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { computeInnerRect, ElementScrollController } from '@fullcalendar/common' -import { ScrollGeomCache } from './ScrollGeomCache' - -export class ElementScrollGeomCache extends ScrollGeomCache { - constructor(el: HTMLElement, doesListening: boolean) { - super(new ElementScrollController(el), doesListening) - } - - getEventTarget(): EventTarget { - return (this.scrollController as ElementScrollController).el - } - - computeClientRect() { - return computeInnerRect((this.scrollController as ElementScrollController).el) - } -} diff --git a/apps/schoolCalendar/fullcalendar/interaction/src/OffsetTracker.ts b/apps/schoolCalendar/fullcalendar/interaction/src/OffsetTracker.ts deleted file mode 100644 index b1fac23e6..000000000 --- a/apps/schoolCalendar/fullcalendar/interaction/src/OffsetTracker.ts +++ /dev/null @@ -1,76 +0,0 @@ -import { - getClippingParents, computeRect, - pointInsideRect, Rect, -} from '@fullcalendar/common' -import { ElementScrollGeomCache } from './ElementScrollGeomCache' - -/* -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. -*/ -export class OffsetTracker { // ElementOffsetTracker - scrollCaches: ElementScrollGeomCache[] - origRect: Rect - - constructor(el: HTMLElement) { - this.origRect = computeRect(el) - - // will work fine for divs that have overflow:hidden - this.scrollCaches = getClippingParents(el).map( - (scrollEl) => new ElementScrollGeomCache(scrollEl, true), // listen=true - ) - } - - destroy() { - for (let scrollCache of this.scrollCaches) { - scrollCache.destroy() - } - } - - computeLeft() { - let left = this.origRect.left - - for (let scrollCache of this.scrollCaches) { - left += scrollCache.origScrollLeft - scrollCache.getScrollLeft() - } - - return left - } - - computeTop() { - let top = this.origRect.top - - for (let scrollCache of this.scrollCaches) { - top += scrollCache.origScrollTop - scrollCache.getScrollTop() - } - - return top - } - - isWithinClipping(pageX: number, pageY: number): boolean { - let point = { left: pageX, top: pageY } - - for (let scrollCache of this.scrollCaches) { - if ( - !isIgnoredClipping(scrollCache.getEventTarget()) && - !pointInsideRect(point, scrollCache.clientRect) - ) { - return false - } - } - - return true - } -} - -// certain clipping containers should never constrain interactions, like and -// https://github.com/fullcalendar/fullcalendar/issues/3615 -function isIgnoredClipping(node: EventTarget) { - let tagName = (node as HTMLElement).tagName - - return tagName === 'HTML' || tagName === 'BODY' -} diff --git a/apps/schoolCalendar/fullcalendar/interaction/src/ScrollGeomCache.ts b/apps/schoolCalendar/fullcalendar/interaction/src/ScrollGeomCache.ts deleted file mode 100644 index 63ec6a5e1..000000000 --- a/apps/schoolCalendar/fullcalendar/interaction/src/ScrollGeomCache.ts +++ /dev/null @@ -1,107 +0,0 @@ -import { Rect, ScrollController } from '@fullcalendar/common' - -/* -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 -*/ -export abstract class ScrollGeomCache extends ScrollController { - clientRect: Rect - origScrollTop: number - origScrollLeft: number - - protected scrollController: ScrollController - protected doesListening: boolean - protected scrollTop: number - protected scrollLeft: number - protected scrollWidth: number - protected scrollHeight: number - protected clientWidth: number - protected clientHeight: number - - constructor(scrollController: ScrollController, doesListening: boolean) { - super() - 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) - } - } - - abstract getEventTarget(): EventTarget - abstract computeClientRect(): Rect - - destroy() { - if (this.doesListening) { - this.getEventTarget().removeEventListener('scroll', this.handleScroll) - } - } - - handleScroll = () => { - this.scrollTop = this.scrollController.getScrollTop() - this.scrollLeft = this.scrollController.getScrollLeft() - this.handleScrollChange() - } - - getScrollTop() { - return this.scrollTop - } - - getScrollLeft() { - return this.scrollLeft - } - - setScrollTop(top: number) { - 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() - } - } - - setScrollLeft(top: number) { - 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() - } - } - - getClientWidth() { - return this.clientWidth - } - - getClientHeight() { - return this.clientHeight - } - - getScrollWidth() { - return this.scrollWidth - } - - getScrollHeight() { - return this.scrollHeight - } - - handleScrollChange() { - } -} diff --git a/apps/schoolCalendar/fullcalendar/interaction/src/WindowScrollGeomCache.ts b/apps/schoolCalendar/fullcalendar/interaction/src/WindowScrollGeomCache.ts deleted file mode 100644 index ca65dee6e..000000000 --- a/apps/schoolCalendar/fullcalendar/interaction/src/WindowScrollGeomCache.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { Rect, WindowScrollController } from '@fullcalendar/common' -import { ScrollGeomCache } from './ScrollGeomCache' - -export class WindowScrollGeomCache extends ScrollGeomCache { - constructor(doesListening: boolean) { - super(new WindowScrollController(), doesListening) - } - - getEventTarget(): EventTarget { - return window - } - - computeClientRect(): Rect { - 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 - handleScrollChange() { - this.clientRect = this.computeClientRect() - } -} diff --git a/apps/schoolCalendar/fullcalendar/interaction/src/api-type-deps.ts b/apps/schoolCalendar/fullcalendar/interaction/src/api-type-deps.ts deleted file mode 100644 index 2b1b51b06..000000000 --- a/apps/schoolCalendar/fullcalendar/interaction/src/api-type-deps.ts +++ /dev/null @@ -1,6 +0,0 @@ -// TODO: rename file to public-types.ts - -export { DateClickArg } from './interactions/DateClicking' -export { EventDragStartArg, EventDragStopArg } from './interactions/EventDragging' -export { EventResizeStartArg, EventResizeStopArg, EventResizeDoneArg } from './interactions/EventResizing' -export { DropArg, EventReceiveArg, EventLeaveArg } from './utils' diff --git a/apps/schoolCalendar/fullcalendar/interaction/src/dnd/AutoScroller.ts b/apps/schoolCalendar/fullcalendar/interaction/src/dnd/AutoScroller.ts deleted file mode 100644 index 8d4e8f015..000000000 --- a/apps/schoolCalendar/fullcalendar/interaction/src/dnd/AutoScroller.ts +++ /dev/null @@ -1,217 +0,0 @@ -import { getElRoot } from '@fullcalendar/common' -import { ScrollGeomCache } from '../ScrollGeomCache' -import { ElementScrollGeomCache } from '../ElementScrollGeomCache' -import { WindowScrollGeomCache } from '../WindowScrollGeomCache' - -interface Edge { - scrollCache: ScrollGeomCache - name: 'top' | 'left' | 'right' | 'bottom' - distance: number // how many pixels the current pointer is from the edge -} - -// 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 -const getTime = typeof performance === 'function' ? (performance as any).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. -*/ -export class AutoScroller { - // options that can be set by caller - isEnabled: boolean = true - scrollQuery: (Window | string)[] = [window, '.fc-scroller'] - edgeThreshold: number = 50 // pixels - maxVelocity: number = 300 // pixels per second - - // internal state - pointerScreenX: number | null = null - pointerScreenY: number | null = null - isAnimating: boolean = false - scrollCaches: ScrollGeomCache[] | null = null - msSinceRequest?: number - - // protect against the initial pointerdown being too close to an edge and starting the scroll - everMovedUp: boolean = false - everMovedDown: boolean = false - everMovedLeft: boolean = false - everMovedRight: boolean = false - - start(pageX: number, pageY: number, scrollStartEl: HTMLElement) { - 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) - } - } - - handleMove(pageX: number, pageY: number) { - if (this.isEnabled) { - let pointerScreenX = pageX - window.pageXOffset - let pointerScreenY = pageY - window.pageYOffset - - let yDelta = this.pointerScreenY === null ? 0 : pointerScreenY - this.pointerScreenY - let 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()) - } - } - } - - stop() { - if (this.isEnabled) { - this.isAnimating = false // will stop animation - - for (let scrollCache of this.scrollCaches!) { - scrollCache.destroy() - } - - this.scrollCaches = null - } - } - - requestAnimation(now: number) { - this.msSinceRequest = now - requestAnimationFrame(this.animate) - } - - private animate = () => { - if (this.isAnimating) { // wasn't cancelled between animation calls - let edge = this.computeBestEdge( - this.pointerScreenX! + window.pageXOffset, - this.pointerScreenY! + window.pageYOffset, - ) - - if (edge) { - let now = getTime() - this.handleSide(edge, (now - this.msSinceRequest!) / 1000) - this.requestAnimation(now) - } else { - this.isAnimating = false // will stop animation - } - } - } - - private handleSide(edge: Edge, seconds: number) { - let { scrollCache } = edge - let { edgeThreshold } = this - let invDistance = edgeThreshold - edge.distance - let velocity = // the closer to the edge, the faster we scroll - ((invDistance * invDistance) / (edgeThreshold * edgeThreshold)) * // quadratic - this.maxVelocity * seconds - let 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 - private computeBestEdge(left: number, top: number): Edge | null { - let { edgeThreshold } = this - let bestSide: Edge | null = null - - for (let scrollCache of this.scrollCaches!) { - let rect = scrollCache.clientRect - let leftDist = left - rect.left - let rightDist = rect.right - left - let topDist = top - rect.top - let 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, name: 'top', distance: topDist } - } - - if ( - bottomDist <= edgeThreshold && this.everMovedDown && scrollCache.canScrollDown() && - (!bestSide || bestSide.distance > bottomDist) - ) { - bestSide = { scrollCache, name: 'bottom', distance: bottomDist } - } - - if ( - leftDist <= edgeThreshold && this.everMovedLeft && scrollCache.canScrollLeft() && - (!bestSide || bestSide.distance > leftDist) - ) { - bestSide = { scrollCache, name: 'left', distance: leftDist } - } - - if ( - rightDist <= edgeThreshold && this.everMovedRight && scrollCache.canScrollRight() && - (!bestSide || bestSide.distance > rightDist) - ) { - bestSide = { scrollCache, name: 'right', distance: rightDist } - } - } - } - - return bestSide - } - - private buildCaches(scrollStartEl: HTMLElement) { - return this.queryScrollEls(scrollStartEl).map((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 - }) - } - - private queryScrollEls(scrollStartEl: HTMLElement) { - let els = [] - - for (let query of this.scrollQuery) { - if (typeof query === 'object') { - els.push(query) - } else { - els.push(...Array.prototype.slice.call( - getElRoot(scrollStartEl).querySelectorAll(query), - )) - } - } - - return els - } -} diff --git a/apps/schoolCalendar/fullcalendar/interaction/src/dnd/ElementMirror.ts b/apps/schoolCalendar/fullcalendar/interaction/src/dnd/ElementMirror.ts deleted file mode 100644 index 6c1e9f4a1..000000000 --- a/apps/schoolCalendar/fullcalendar/interaction/src/dnd/ElementMirror.ts +++ /dev/null @@ -1,146 +0,0 @@ -import { removeElement, applyStyle, whenTransitionDone, Rect } from '@fullcalendar/common' - -/* -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. -*/ -export class ElementMirror { - isVisible: boolean = false // must be explicitly enabled - origScreenX?: number - origScreenY?: number - deltaX?: number - deltaY?: number - sourceEl: HTMLElement | null = null - mirrorEl: HTMLElement | null = null - sourceElRect: Rect | null = null // screen coords relative to viewport - - // options that can be set directly by caller - parentNode: HTMLElement = document.body // HIGHLY SUGGESTED to set this to sidestep ShadowDOM issues - zIndex: number = 9999 - revertDuration: number = 0 - - start(sourceEl: HTMLElement, pageX: number, pageY: number) { - 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() - } - - handleMove(pageX: number, pageY: number) { - this.deltaX = (pageX - window.pageXOffset) - this.origScreenX! - this.deltaY = (pageY - window.pageYOffset) - this.origScreenY! - this.updateElPosition() - } - - // can be called before start - setIsVisible(bool: boolean) { - 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 - stop(needsRevertAnimation: boolean, callback: () => void) { - let done = () => { - 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) - } - } - - doRevertAnimation(callback: () => void, revertDuration: number) { - let mirrorEl = this.mirrorEl! - let 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, () => { - mirrorEl.style.transition = '' - callback() - }) - } - - cleanup() { - if (this.mirrorEl) { - removeElement(this.mirrorEl) - this.mirrorEl = null - } - - this.sourceEl = null - } - - updateElPosition() { - if (this.sourceEl && this.isVisible) { - applyStyle(this.getMirrorEl(), { - left: this.sourceElRect!.left + this.deltaX!, - top: this.sourceElRect!.top + this.deltaY!, - }) - } - } - - getMirrorEl(): HTMLElement { - let sourceElRect = this.sourceElRect! - let mirrorEl = this.mirrorEl - - if (!mirrorEl) { - mirrorEl = this.mirrorEl = this.sourceEl!.cloneNode(true) as HTMLElement // 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: '', // in case original element was hidden by the drag effect - boxSizing: 'border-box', // for easy width/height - width: sourceElRect.right - sourceElRect.left, // explicit height in case there was a 'right' value - height: sourceElRect.bottom - sourceElRect.top, // explicit width in case there was a 'bottom' value - right: 'auto', // erase and set width instead - bottom: 'auto', // erase and set height instead - margin: 0, - }) - - this.parentNode.appendChild(mirrorEl) - } - - return mirrorEl - } -} diff --git a/apps/schoolCalendar/fullcalendar/interaction/src/dnd/FeaturefulElementDragging.ts b/apps/schoolCalendar/fullcalendar/interaction/src/dnd/FeaturefulElementDragging.ts deleted file mode 100644 index 3f1c7826b..000000000 --- a/apps/schoolCalendar/fullcalendar/interaction/src/dnd/FeaturefulElementDragging.ts +++ /dev/null @@ -1,213 +0,0 @@ -import { - PointerDragEvent, - preventSelection, - allowSelection, - preventContextMenu, - allowContextMenu, - ElementDragging, -} from '@fullcalendar/common' -import { PointerDragging } from './PointerDragging' -import { ElementMirror } from './ElementMirror' -import { AutoScroller } from './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 -*/ -export class FeaturefulElementDragging extends ElementDragging { - pointer: PointerDragging - mirror: ElementMirror - autoScroller: AutoScroller - - // options that can be directly set by caller - // the caller can also set the PointerDragging's options as well - delay: number | null = null - minDistance: number = 0 - touchScrollAllowed: boolean = true // prevents drag from starting and blocks scrolling during drag - - mirrorNeedsRevert: boolean = false - isInteracting: boolean = false // is the user validly moving the pointer? lasts until pointerup - isDragging: boolean = false // is it INTENTFULLY dragging? lasts until after revert animation - isDelayEnded: boolean = false - isDistanceSurpassed: boolean = false - delayTimeoutId: number | null = null - - constructor(private containerEl: HTMLElement, selector?: string) { - super(containerEl) - - let 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() - } - - destroy() { - 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({} as any) - } - - onPointerDown = (ev: PointerDragEvent) => { - 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 as HTMLElement, ev.pageX, ev.pageY) // must happen on first pointer down - - this.startDelay(ev) - - if (!this.minDistance) { - this.handleDistanceSurpassed(ev) - } - } - } - } - - onPointerMove = (ev: PointerDragEvent) => { - if (this.isInteracting) { - this.emitter.trigger('pointermove', ev) - - if (!this.isDistanceSurpassed) { - let minDistance = this.minDistance - let distanceSq // current distance from the origin, squared - let { deltaX, deltaY } = ev - - 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) - } - } - } - - onPointerUp = (ev: PointerDragEvent) => { - 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 - } - } - } - - startDelay(ev: PointerDragEvent) { - if (typeof this.delay === 'number') { - this.delayTimeoutId = setTimeout(() => { - this.delayTimeoutId = null - this.handleDelayEnd(ev) - }, this.delay) as any // not assignable to number! - } else { - this.handleDelayEnd(ev) - } - } - - handleDelayEnd(ev: PointerDragEvent) { - this.isDelayEnded = true - this.tryStartDrag(ev) - } - - handleDistanceSurpassed(ev: PointerDragEvent) { - this.isDistanceSurpassed = true - this.tryStartDrag(ev) - } - - tryStartDrag(ev: PointerDragEvent) { - 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() - } - } - } - } - - tryStopDrag(ev: PointerDragEvent) { - // .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), // bound with args - ) - } - - stopDrag(ev: PointerDragEvent) { - this.isDragging = false - this.emitter.trigger('dragend', ev) - } - - // fill in the implementations... - - setIgnoreMove(bool: boolean) { - this.pointer.shouldIgnoreMove = bool - } - - setMirrorIsVisible(bool: boolean) { - this.mirror.setIsVisible(bool) - } - - setMirrorNeedsRevert(bool: boolean) { - this.mirrorNeedsRevert = bool - } - - setAutoScrollEnabled(bool: boolean) { - this.autoScroller.isEnabled = bool - } -} diff --git a/apps/schoolCalendar/fullcalendar/interaction/src/dnd/PointerDragging.ts b/apps/schoolCalendar/fullcalendar/interaction/src/dnd/PointerDragging.ts deleted file mode 100644 index dbe7d8abb..000000000 --- a/apps/schoolCalendar/fullcalendar/interaction/src/dnd/PointerDragging.ts +++ /dev/null @@ -1,344 +0,0 @@ -import { config, elementClosest, Emitter, PointerDragEvent } from '@fullcalendar/common' - -config.touchMouseIgnoreWait = 500 - -let ignoreMouseDepth = 0 -let listenerCnt = 0 -let 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 -*/ -export class PointerDragging { - containerEl: EventTarget - subjectEl: HTMLElement | null = null - emitter: Emitter - - // options that can be directly assigned by caller - selector: string = '' // will cause subjectEl in all emitted events to be this element - handleSelector: string = '' - shouldIgnoreMove: boolean = false - shouldWatchScroll: boolean = true // for simulating pointermove on scroll - - // internal states - isDragging: boolean = false - isTouchDragging: boolean = false - wasTouchScroll: boolean = false - origPageX: number - origPageY: number - prevPageX: number - prevPageY: number - prevScrollX: number // at time of last pointer pageX/pageY capture - prevScrollY: number // " - - constructor(containerEl: EventTarget) { - this.containerEl = containerEl - this.emitter = new Emitter() - containerEl.addEventListener('mousedown', this.handleMouseDown as EventListener) - containerEl.addEventListener('touchstart', this.handleTouchStart as EventListener, { passive: true }) - listenerCreated() - } - - destroy() { - this.containerEl.removeEventListener('mousedown', this.handleMouseDown as EventListener) - this.containerEl.removeEventListener('touchstart', this.handleTouchStart as EventListener, { passive: true } as AddEventListenerOptions) - listenerDestroyed() - } - - tryStart(ev: UIEvent): boolean { - let subjectEl = this.querySubjectEl(ev) - let downEl = ev.target as HTMLElement - - 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 - } - - cleanup() { - isWindowTouchMoveCancelled = false - this.isDragging = false - this.subjectEl = null - // keep wasTouchScroll around for later access - this.destroyScrollWatch() - } - - querySubjectEl(ev: UIEvent): HTMLElement { - if (this.selector) { - return elementClosest(ev.target as HTMLElement, this.selector) - } - return this.containerEl as HTMLElement - } - - // Mouse - // ---------------------------------------------------------------------------------------------------- - - handleMouseDown = (ev: MouseEvent) => { - if ( - !this.shouldIgnoreMouse() && - isPrimaryMouseButton(ev) && - this.tryStart(ev) - ) { - let 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) - } - } - - handleMouseMove = (ev: MouseEvent) => { - let pev = this.createEventFromMouse(ev) - this.recordCoords(pev) - this.emitter.trigger('pointermove', pev) - } - - handleMouseUp = (ev: MouseEvent) => { - 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 - } - - shouldIgnoreMouse() { - return ignoreMouseDepth || this.isTouchDragging - } - - // Touch - // ---------------------------------------------------------------------------------------------------- - - handleTouchStart = (ev: TouchEvent) => { - if (this.tryStart(ev)) { - this.isTouchDragging = true - - let 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 - let targetEl = ev.target as HTMLElement - - 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, // useCapture - ) - } - } - - handleTouchMove = (ev: TouchEvent) => { - let pev = this.createEventFromTouch(ev) - this.recordCoords(pev) - this.emitter.trigger('pointermove', pev) - } - - handleTouchEnd = (ev: TouchEvent) => { - if (this.isDragging) { // done to guard against touchend followed by touchcancel - let targetEl = ev.target as HTMLElement - - 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() - } - } - - handleTouchScroll = () => { - this.wasTouchScroll = true - } - - // can be called by user of this class, to cancel touch-based scrolling for the current drag - cancelTouchScroll() { - if (this.isDragging) { - isWindowTouchMoveCancelled = true - } - } - - // Scrolling that simulates pointermoves - // ---------------------------------------------------------------------------------------------------- - - initScrollWatch(ev: PointerDragEvent) { - if (this.shouldWatchScroll) { - this.recordCoords(ev) - window.addEventListener('scroll', this.handleScroll, true) // useCapture=true - } - } - - recordCoords(ev: PointerDragEvent) { - if (this.shouldWatchScroll) { - this.prevPageX = (ev as any).pageX - this.prevPageY = (ev as any).pageY - this.prevScrollX = window.pageXOffset - this.prevScrollY = window.pageYOffset - } - } - - handleScroll = (ev: UIEvent) => { - if (!this.shouldIgnoreMove) { - let pageX = (window.pageXOffset - this.prevScrollX) + this.prevPageX - let pageY = (window.pageYOffset - this.prevScrollY) + this.prevPageY - - this.emitter.trigger('pointermove', { - origEvent: ev, - isTouch: this.isTouchDragging, - subjectEl: this.subjectEl, - pageX, - pageY, - deltaX: pageX - this.origPageX, - deltaY: pageY - this.origPageY, - } as PointerDragEvent) - } - } - - destroyScrollWatch() { - if (this.shouldWatchScroll) { - window.removeEventListener('scroll', this.handleScroll, true) // useCaptured=true - } - } - - // Event Normalization - // ---------------------------------------------------------------------------------------------------- - - createEventFromMouse(ev: MouseEvent, isFirst?: boolean): PointerDragEvent { - let deltaX = 0 - let 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, - deltaY, - } - } - - createEventFromTouch(ev: TouchEvent, isFirst?: boolean): PointerDragEvent { - let touches = ev.touches - let pageX - let pageY - let deltaX = 0 - let 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 as any).pageX - pageY = (ev as any).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, - pageY, - deltaX, - deltaY, - } - } -} - -// Returns a boolean whether this was a left mouse click and no ctrl key (which means right click on Mac) -function isPrimaryMouseButton(ev: MouseEvent) { - return ev.button === 0 && !ev.ctrlKey -} - -// Ignoring fake mouse events generated by touch -// ---------------------------------------------------------------------------------------------------- - -function startIgnoringMouse() { // can be made non-class function - ignoreMouseDepth += 1 - - setTimeout(() => { - 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 } as AddEventListenerOptions) - } -} - -function onWindowTouchMove(ev: UIEvent) { - if (isWindowTouchMoveCancelled) { - ev.preventDefault() - } -} diff --git a/apps/schoolCalendar/fullcalendar/interaction/src/interactions-external/ExternalDraggable.ts b/apps/schoolCalendar/fullcalendar/interaction/src/interactions-external/ExternalDraggable.ts deleted file mode 100644 index 22aba108d..000000000 --- a/apps/schoolCalendar/fullcalendar/interaction/src/interactions-external/ExternalDraggable.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { BASE_OPTION_DEFAULTS, PointerDragEvent } from '@fullcalendar/common' -import { FeaturefulElementDragging } from '../dnd/FeaturefulElementDragging' -import { ExternalElementDragging, DragMetaGenerator } from './ExternalElementDragging' - -export interface ExternalDraggableSettings { - eventData?: DragMetaGenerator - itemSelector?: string - minDistance?: number - longPressDelay?: number - appendTo?: HTMLElement -} - -/* -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. -*/ -export class ExternalDraggable { - dragging: FeaturefulElementDragging - settings: ExternalDraggableSettings - - constructor(el: HTMLElement, settings: ExternalDraggableSettings = {}) { - this.settings = settings - - let 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 - } - - handlePointerDown = (ev: PointerDragEvent) => { - let { dragging } = this - let { minDistance, longPressDelay } = this.settings - - 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 - } - - handleDragStart = (ev: PointerDragEvent) => { - if ( - ev.isTouch && - this.dragging.delay && - (ev.subjectEl as HTMLElement).classList.contains('fc-event') - ) { - this.dragging.mirror.getMirrorEl().classList.add('fc-event-selected') - } - } - - destroy() { - this.dragging.destroy() - } -} diff --git a/apps/schoolCalendar/fullcalendar/interaction/src/interactions-external/ExternalElementDragging.ts b/apps/schoolCalendar/fullcalendar/interaction/src/interactions-external/ExternalElementDragging.ts deleted file mode 100644 index 95ac7e0c2..000000000 --- a/apps/schoolCalendar/fullcalendar/interaction/src/interactions-external/ExternalElementDragging.ts +++ /dev/null @@ -1,268 +0,0 @@ -import { - Hit, - interactionSettingsStore, - PointerDragEvent, - parseEventDef, createEventInstance, EventTuple, - createEmptyEventStore, eventTupleToStore, - config, - DateSpan, DatePointApi, - EventInteractionState, - DragMetaInput, DragMeta, parseDragMeta, - EventApi, - elementMatches, - enableCursor, disableCursor, - isInteractionValid, - ElementDragging, - ViewApi, - CalendarContext, - getDefaultEventEnd, - refineEventDef, -} from '@fullcalendar/common' -import { __assign } from 'tslib' -import { HitDragging } from '../interactions/HitDragging' -import { buildDatePointApiWithContext } from '../utils' - -export type DragMetaGenerator = DragMetaInput | ((el: HTMLElement) => DragMetaInput) - -export interface ExternalDropApi extends DatePointApi { - draggedEl: HTMLElement - jsEvent: UIEvent - view: ViewApi -} - -/* -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. -*/ -export class ExternalElementDragging { - hitDragging: HitDragging - receivingContext: CalendarContext | null = null - droppableEvent: EventTuple | null = null // will exist for all drags, even if create:false - suppliedDragMeta: DragMetaGenerator | null = null - dragMeta: DragMeta | null = null - - constructor(dragging: ElementDragging, suppliedDragMeta?: DragMetaGenerator) { - let 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 - } - - handleDragStart = (ev: PointerDragEvent) => { - this.dragMeta = this.buildDragMeta(ev.subjectEl as HTMLElement) - } - - buildDragMeta(subjectEl: HTMLElement) { - if (typeof this.suppliedDragMeta === 'object') { - return parseDragMeta(this.suppliedDragMeta) - } - if (typeof this.suppliedDragMeta === 'function') { - return parseDragMeta(this.suppliedDragMeta(subjectEl)) - } - return getDragMetaFromEl(subjectEl) - } - - handleHitUpdate = (hit: Hit | null, isFinal: boolean, ev: PointerDragEvent) => { - let { dragging } = this.hitDragging - let receivingContext: CalendarContext | null = null - let droppableEvent: EventTuple | null = null - let isInvalid = false - let interaction: EventInteractionState = { - affectedEvents: createEmptyEventStore(), - mutatedEvents: createEmptyEventStore(), - isEvent: this.dragMeta!.create, - } - - if (hit) { - receivingContext = hit.context - - if (this.canDropElOnCalendar(ev.subjectEl as HTMLElement, 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'), // TODO: turn className into constant - // TODO: somehow query FullCalendars WITHIN shadow-roots for existing event-mirror els - ) - - if (!isInvalid) { - enableCursor() - } else { - disableCursor() - } - - if (!isFinal) { - dragging.setMirrorNeedsRevert(!droppableEvent) - - this.receivingContext = receivingContext - this.droppableEvent = droppableEvent - } - } - - handleDragEnd = (pev: PointerDragEvent) => { - let { receivingContext, droppableEvent } = this - - this.clearDrag() - - if (receivingContext && droppableEvent) { - let finalHit = this.hitDragging.finalHit! - let finalView = finalHit.context.viewApi - let dragMeta = this.dragMeta! - - receivingContext.emitter.trigger('drop', { - ...buildDatePointApiWithContext(finalHit.dateSpan, receivingContext), - draggedEl: pev.subjectEl as HTMLElement, - jsEvent: pev.origEvent as MouseEvent, // Is this always a mouse event? See #4655 - view: finalView, - }) - - if (dragMeta.create) { - let addingEvents = eventTupleToStore(droppableEvent) - - receivingContext.dispatch({ - type: 'MERGE_EVENTS', - eventStore: addingEvents, - }) - - 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() { - receivingContext.dispatch({ - type: 'REMOVE_EVENTS', - eventStore: addingEvents, - }) - }, - draggedEl: pev.subjectEl as HTMLElement, - view: finalView, - }) - } - } - - this.receivingContext = null - this.droppableEvent = null - } - - displayDrag(nextContext: CalendarContext | null, state: EventInteractionState) { - let prevContext = this.receivingContext - - if (prevContext && prevContext !== nextContext) { - prevContext.dispatch({ type: 'UNSET_EVENT_DRAG' }) - } - - if (nextContext) { - nextContext.dispatch({ type: 'SET_EVENT_DRAG', state }) - } - } - - clearDrag() { - if (this.receivingContext) { - this.receivingContext.dispatch({ type: 'UNSET_EVENT_DRAG' }) - } - } - - canDropElOnCalendar(el: HTMLElement, receivingContext: CalendarContext): boolean { - let 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 - } -} - -// Utils for computing event store from the DragMeta -// ---------------------------------------------------------------------------------------------------- - -function computeEventForDateSpan(dateSpan: DateSpan, dragMeta: DragMeta, context: CalendarContext): EventTuple { - let defProps = { ...dragMeta.leftoverProps } - - for (let transform of context.pluginHooks.externalDefTransforms) { - __assign(defProps, transform(dateSpan, dragMeta)) - } - - let { refined, extra } = refineEventDef(defProps, context) - let def = parseEventDef( - refined, - extra, - dragMeta.sourceId, - dateSpan.allDay, - context.options.forceEventDuration || Boolean(dragMeta.duration), // hasEnd - context, - ) - - let 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) - } - - let end = dragMeta.duration ? - context.dateEnv.add(start, dragMeta.duration) : - getDefaultEventEnd(dateSpan.allDay, start, context) - - let instance = createEventInstance(def.defId, { start, end }) - - return { def, instance } -} - -// Utils for extracting data from element -// ---------------------------------------------------------------------------------------------------- - -function getDragMetaFromEl(el: HTMLElement): DragMeta { - let str = getEmbeddedElData(el, 'event') - let obj = str ? - JSON.parse(str) : - { create: false } // if no embedded data, assume no event creation - - return parseDragMeta(obj) -} - -config.dataAttrPrefix = '' - -function getEmbeddedElData(el: HTMLElement, name: string): string { - let prefix = config.dataAttrPrefix - let prefixedName = (prefix ? prefix + '-' : '') + name - - return el.getAttribute('data-' + prefixedName) || '' -} diff --git a/apps/schoolCalendar/fullcalendar/interaction/src/interactions-external/InferredElementDragging.ts b/apps/schoolCalendar/fullcalendar/interaction/src/interactions-external/InferredElementDragging.ts deleted file mode 100644 index ab03cec00..000000000 --- a/apps/schoolCalendar/fullcalendar/interaction/src/interactions-external/InferredElementDragging.ts +++ /dev/null @@ -1,77 +0,0 @@ -import { PointerDragEvent, ElementDragging } from '@fullcalendar/common' -import { PointerDragging } from '../dnd/PointerDragging' - -/* -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. -*/ -export class InferredElementDragging extends ElementDragging { - pointer: PointerDragging - shouldIgnoreMove: boolean = false - mirrorSelector: string = '' - currentMirrorEl: HTMLElement | null = null - - constructor(containerEl: HTMLElement) { - super(containerEl) - - let pointer = this.pointer = new PointerDragging(containerEl) - pointer.emitter.on('pointerdown', this.handlePointerDown) - pointer.emitter.on('pointermove', this.handlePointerMove) - pointer.emitter.on('pointerup', this.handlePointerUp) - } - - destroy() { - this.pointer.destroy() - } - - handlePointerDown = (ev: PointerDragEvent) => { - this.emitter.trigger('pointerdown', ev) - - if (!this.shouldIgnoreMove) { - // fire dragstart right away. does not support delay or min-distance - this.emitter.trigger('dragstart', ev) - } - } - - handlePointerMove = (ev: PointerDragEvent) => { - if (!this.shouldIgnoreMove) { - this.emitter.trigger('dragmove', ev) - } - } - - handlePointerUp = (ev: PointerDragEvent) => { - this.emitter.trigger('pointerup', ev) - - if (!this.shouldIgnoreMove) { - // fire dragend right away. does not support a revert animation - this.emitter.trigger('dragend', ev) - } - } - - setIgnoreMove(bool: boolean) { - this.shouldIgnoreMove = bool - } - - setMirrorIsVisible(bool: boolean) { - 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 { - let mirrorEl = this.mirrorSelector - // TODO: somehow query FullCalendars WITHIN shadow-roots - ? document.querySelector(this.mirrorSelector) as HTMLElement - : null - - if (mirrorEl) { - this.currentMirrorEl = mirrorEl - mirrorEl.style.visibility = 'hidden' - } - } - } -} diff --git a/apps/schoolCalendar/fullcalendar/interaction/src/interactions-external/ThirdPartyDraggable.ts b/apps/schoolCalendar/fullcalendar/interaction/src/interactions-external/ThirdPartyDraggable.ts deleted file mode 100644 index 324b22255..000000000 --- a/apps/schoolCalendar/fullcalendar/interaction/src/interactions-external/ThirdPartyDraggable.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { ExternalElementDragging, DragMetaGenerator } from './ExternalElementDragging' -import { InferredElementDragging } from './InferredElementDragging' - -export interface ThirdPartyDraggableSettings { - eventData?: DragMetaGenerator - itemSelector?: string - mirrorSelector?: string -} - -/* -Bridges third-party drag-n-drop systems with FullCalendar. -Must be instantiated and destroyed by caller. -*/ -export class ThirdPartyDraggable { - dragging: InferredElementDragging - - constructor( - containerOrSettings?: EventTarget | ThirdPartyDraggableSettings, - settings?: ThirdPartyDraggableSettings, - ) { - let containerEl: EventTarget = document - - if ( - // wish we could just test instanceof EventTarget, but doesn't work in IE11 - containerOrSettings === document || - containerOrSettings instanceof Element - ) { - containerEl = containerOrSettings as EventTarget - settings = settings || {} - } else { - settings = (containerOrSettings || {}) as ThirdPartyDraggableSettings - } - - let dragging = this.dragging = new InferredElementDragging(containerEl as HTMLElement) - - 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 - } - - destroy() { - this.dragging.destroy() - } -} diff --git a/apps/schoolCalendar/fullcalendar/interaction/src/interactions/DateClicking.ts b/apps/schoolCalendar/fullcalendar/interaction/src/interactions/DateClicking.ts deleted file mode 100644 index 06b352de9..000000000 --- a/apps/schoolCalendar/fullcalendar/interaction/src/interactions/DateClicking.ts +++ /dev/null @@ -1,71 +0,0 @@ -import { - PointerDragEvent, Interaction, InteractionSettings, interactionSettingsToStore, - DatePointApi, - ViewApi, -} from '@fullcalendar/common' -import { FeaturefulElementDragging } from '../dnd/FeaturefulElementDragging' -import { HitDragging, isHitsEqual } from './HitDragging' -import { buildDatePointApiWithContext } from '../utils' - -export interface DateClickArg extends DatePointApi { - dayEl: HTMLElement - jsEvent: MouseEvent - view: ViewApi -} - -/* -Monitors when the user clicks on a specific date/time of a component. -A pointerdown+pointerup on the same "hit" constitutes a click. -*/ -export class DateClicking extends Interaction { - dragging: FeaturefulElementDragging - hitDragging: HitDragging - - constructor(settings: InteractionSettings) { - super(settings) - - // 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 - - let hitDragging = this.hitDragging = new HitDragging(this.dragging, interactionSettingsToStore(settings)) - hitDragging.emitter.on('pointerdown', this.handlePointerDown) - hitDragging.emitter.on('dragend', this.handleDragEnd) - } - - destroy() { - this.dragging.destroy() - } - - handlePointerDown = (pev: PointerDragEvent) => { - let { dragging } = this - let downEl = pev.origEvent.target as HTMLElement - - // 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 - handleDragEnd = (ev: PointerDragEvent) => { - let { component } = this - let { pointer } = this.dragging - - if (!pointer.wasTouchScroll) { - let { initialHit, finalHit } = this.hitDragging - - if (initialHit && finalHit && isHitsEqual(initialHit, finalHit)) { - let { context } = component - let arg: DateClickArg = { - ...buildDatePointApiWithContext(initialHit.dateSpan, context), - dayEl: initialHit.dayEl, - jsEvent: ev.origEvent as MouseEvent, - view: context.viewApi || context.calendarApi.view, - } - - context.emitter.trigger('dateClick', arg) - } - } - } -} diff --git a/apps/schoolCalendar/fullcalendar/interaction/src/interactions/DateSelecting.ts b/apps/schoolCalendar/fullcalendar/interaction/src/interactions/DateSelecting.ts deleted file mode 100644 index fa6b3ff09..000000000 --- a/apps/schoolCalendar/fullcalendar/interaction/src/interactions/DateSelecting.ts +++ /dev/null @@ -1,152 +0,0 @@ -import { - compareNumbers, enableCursor, disableCursor, DateComponent, Hit, - DateSpan, PointerDragEvent, dateSelectionJoinTransformer, - Interaction, InteractionSettings, interactionSettingsToStore, - triggerDateSelect, isDateSelectionValid, -} from '@fullcalendar/common' -import { __assign } from 'tslib' -import { HitDragging } from './HitDragging' -import { FeaturefulElementDragging } from '../dnd/FeaturefulElementDragging' - -/* -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. -*/ -export class DateSelecting extends Interaction { - dragging: FeaturefulElementDragging - hitDragging: HitDragging - dragSelection: DateSpan | null = null - - constructor(settings: InteractionSettings) { - super(settings) - let { component } = settings - let { options } = component.context - - let dragging = this.dragging = new FeaturefulElementDragging(settings.el) - dragging.touchScrollAllowed = false - dragging.minDistance = options.selectMinDistance || 0 - dragging.autoScroller.isEnabled = options.dragScroll - - let 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) - } - - destroy() { - this.dragging.destroy() - } - - handlePointerDown = (ev: PointerDragEvent) => { - let { component, dragging } = this - let { options } = component.context - - let canSelect = options.selectable && - component.isValidDateDownEl(ev.origEvent.target as HTMLElement) - - // 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(component) : null - } - - handleDragStart = (ev: PointerDragEvent) => { - this.component.context.calendarApi.unselect(ev) // unselect previous selections - } - - handleHitUpdate = (hit: Hit | null, isFinal: boolean) => { - let { context } = this.component - let dragSelection: DateSpan | null = null - let isInvalid = false - - if (hit) { - let initialHit = this.hitDragging.initialHit! - let 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 - } - } - - handlePointerUp = (pev: PointerDragEvent) => { - if (this.dragSelection) { - // selection is already rendered, so just need to report selection - triggerDateSelect(this.dragSelection, pev, this.component.context) - - this.dragSelection = null - } - } -} - -function getComponentTouchDelay(component: DateComponent): number { - let { options } = component.context - let delay = options.selectLongPressDelay - - if (delay == null) { - delay = options.longPressDelay - } - - return delay -} - -function joinHitsIntoSelection(hit0: Hit, hit1: Hit, dateSelectionTransformers: dateSelectionJoinTransformer[]): DateSpan { - let dateSpan0 = hit0.dateSpan - let dateSpan1 = hit1.dateSpan - let ms = [ - dateSpan0.range.start, - dateSpan0.range.end, - dateSpan1.range.start, - dateSpan1.range.end, - ] - - ms.sort(compareNumbers) - - let props = {} as DateSpan - - for (let transformer of dateSelectionTransformers) { - let 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 -} diff --git a/apps/schoolCalendar/fullcalendar/interaction/src/interactions/EventDragging.ts b/apps/schoolCalendar/fullcalendar/interaction/src/interactions/EventDragging.ts deleted file mode 100644 index 9a9ecfc49..000000000 --- a/apps/schoolCalendar/fullcalendar/interaction/src/interactions/EventDragging.ts +++ /dev/null @@ -1,482 +0,0 @@ -import { - DateComponent, Seg, - PointerDragEvent, Hit, - EventMutation, applyMutationToEventStore, - startOfDay, - elementClosest, - EventStore, getRelevantEvents, createEmptyEventStore, - EventInteractionState, - diffDates, enableCursor, disableCursor, - EventRenderRange, getElSeg, - EventApi, - eventDragMutationMassager, - Interaction, InteractionSettings, interactionSettingsStore, - EventDropTransformers, - CalendarContext, - ViewApi, - EventChangeArg, - buildEventApis, - EventAddArg, - EventRemoveArg, - isInteractionValid, - getElRoot, -} from '@fullcalendar/common' -import { __assign } from 'tslib' -import { HitDragging, isHitsEqual } from './HitDragging' -import { FeaturefulElementDragging } from '../dnd/FeaturefulElementDragging' -import { buildDatePointApiWithContext } from '../utils' - -export type EventDragStopArg = EventDragArg -export type EventDragStartArg = EventDragArg - -export interface EventDragArg { - el: HTMLElement - event: EventApi - jsEvent: MouseEvent - view: ViewApi -} - -export class EventDragging extends Interaction { // TODO: rename to EventSelectingAndDragging - // TODO: test this in IE11 - // QUESTION: why do we need it on the resizable??? - static SELECTOR = '.fc-event-draggable, .fc-event-resizable' - - dragging: FeaturefulElementDragging - hitDragging: HitDragging - - // internal state - subjectEl: HTMLElement | null = null - subjectSeg: Seg | null = null // the seg being selected/dragged - isDragging: boolean = false - eventRange: EventRenderRange | null = null - relevantEvents: EventStore | null = null // the events being dragged - receivingContext: CalendarContext | null = null - validMutation: EventMutation | null = null - mutatedRelevantEvents: EventStore | null = null - - constructor(settings: InteractionSettings) { - super(settings) - let { component } = this - let { options } = component.context - - let dragging = this.dragging = new FeaturefulElementDragging(settings.el) - dragging.pointer.selector = EventDragging.SELECTOR - dragging.touchScrollAllowed = false - dragging.autoScroller.isEnabled = options.dragScroll - - let 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) - } - - destroy() { - this.dragging.destroy() - } - - handlePointerDown = (ev: PointerDragEvent) => { - let origTarget = ev.origEvent.target as HTMLElement - let { component, dragging } = this - let { mirror } = dragging - let { options } = component.context - let initialContext = component.context - this.subjectEl = ev.subjectEl as HTMLElement - let subjectSeg = this.subjectSeg = getElSeg(ev.subjectEl as HTMLElement)! - let eventRange = this.eventRange = subjectSeg.eventRange! - let 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 - - let 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 as HTMLElement).classList.contains('fc-event-draggable') - } - - handleDragStart = (ev: PointerDragEvent) => { - let initialContext = this.component.context - let eventRange = this.eventRange! - let 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 }) - } - } 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 as MouseEvent, // Is this always a mouse event? See #4655 - view: initialContext.viewApi, - } as EventDragStartArg) - } - } - - handleHitUpdate = (hit: Hit | null, isFinal: boolean) => { - if (!this.isDragging) { - return - } - - let relevantEvents = this.relevantEvents! - let initialHit = this.hitDragging.initialHit! - let initialContext = this.component.context - - // states based on new hit - let receivingContext: CalendarContext | null = null - let mutation: EventMutation | null = null - let mutatedRelevantEvents: EventStore | null = null - let isInvalid = false - let interaction: EventInteractionState = { - affectedEvents: relevantEvents, - mutatedEvents: createEmptyEventStore(), - isEvent: true, - } - - if (hit) { - receivingContext = hit.context - let 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'), // TODO: turn className into constant - ) - - // assign states based on new hit - this.receivingContext = receivingContext - this.validMutation = mutation - this.mutatedRelevantEvents = mutatedRelevantEvents - } - } - - handlePointerUp = () => { - if (!this.isDragging) { - this.cleanup() // because handleDragEnd won't fire - } - } - - handleDragEnd = (ev: PointerDragEvent) => { - if (this.isDragging) { - let initialContext = this.component.context - let initialView = initialContext.viewApi - let { receivingContext, validMutation } = this - let eventDef = this.eventRange!.def - let eventInstance = this.eventRange!.instance - let eventApi = new EventApi(initialContext, eventDef, eventInstance) - let relevantEvents = this.relevantEvents! - let mutatedRelevantEvents = this.mutatedRelevantEvents! - let { finalHit } = this.hitDragging - - this.clearDrag() // must happen after revert animation - - initialContext.emitter.trigger('eventDragStop', { - el: this.subjectEl, - event: eventApi, - jsEvent: ev.origEvent as MouseEvent, // Is this always a mouse event? See #4655 - view: initialView, - } as EventDragStopArg) - - if (validMutation) { - // dropped within same calendar - if (receivingContext === initialContext) { - let updatedEventApi = new EventApi( - initialContext, - mutatedRelevantEvents.defs[eventDef.defId], - eventInstance ? mutatedRelevantEvents.instances[eventInstance.instanceId] : null, - ) - - initialContext.dispatch({ - type: 'MERGE_EVENTS', - eventStore: mutatedRelevantEvents, - }) - - let eventChangeArg: EventChangeArg = { - oldEvent: eventApi, - event: updatedEventApi, - relatedEvents: buildEventApis(mutatedRelevantEvents, initialContext, eventInstance), - revert() { - initialContext.dispatch({ - type: 'MERGE_EVENTS', - eventStore: relevantEvents, // the pre-change data - }) - }, - } - - let transformed: ReturnType = {} - for (let transformer of initialContext.getCurrentData().pluginHooks.eventDropTransformers) { - __assign(transformed, transformer(validMutation, initialContext)) - } - - initialContext.emitter.trigger('eventDrop', { - ...eventChangeArg, - ...transformed, - el: ev.subjectEl as HTMLElement, - delta: validMutation.datesDelta!, - jsEvent: ev.origEvent as MouseEvent, // bad - view: initialView, - }) - - initialContext.emitter.trigger('eventChange', eventChangeArg) - - // dropped in different calendar - } else if (receivingContext) { - let eventRemoveArg: EventRemoveArg = { - event: eventApi, - relatedEvents: buildEventApis(relevantEvents, initialContext, eventInstance), - revert() { - initialContext.dispatch({ - type: 'MERGE_EVENTS', - eventStore: relevantEvents, - }) - }, - } - - initialContext.emitter.trigger('eventLeave', { - ...eventRemoveArg, - draggedEl: ev.subjectEl as HTMLElement, - view: initialView, - }) - - initialContext.dispatch({ - type: 'REMOVE_EVENTS', - eventStore: relevantEvents, - }) - - initialContext.emitter.trigger('eventRemove', eventRemoveArg) - - let addedEventDef = mutatedRelevantEvents.defs[eventDef.defId] - let addedEventInstance = mutatedRelevantEvents.instances[eventInstance.instanceId] - let addedEventApi = new EventApi(receivingContext, addedEventDef, addedEventInstance) - - receivingContext.dispatch({ - type: 'MERGE_EVENTS', - eventStore: mutatedRelevantEvents, - }) - - let eventAddArg: EventAddArg = { - event: addedEventApi, - relatedEvents: buildEventApis(mutatedRelevantEvents, receivingContext, addedEventInstance), - revert() { - receivingContext.dispatch({ - type: 'REMOVE_EVENTS', - eventStore: mutatedRelevantEvents, - }) - }, - } - - receivingContext.emitter.trigger('eventAdd', eventAddArg) - - if (ev.isTouch) { - receivingContext.dispatch({ - type: 'SELECT_EVENT', - eventInstanceId: eventInstance.instanceId, - }) - } - - receivingContext.emitter.trigger('drop', { - ...buildDatePointApiWithContext(finalHit.dateSpan, receivingContext), - draggedEl: ev.subjectEl as HTMLElement, - jsEvent: ev.origEvent as MouseEvent, // Is this always a mouse event? See #4655 - view: finalHit.context.viewApi, - }) - - receivingContext.emitter.trigger('eventReceive', { - ...eventAddArg, - draggedEl: ev.subjectEl as HTMLElement, - view: finalHit.context.viewApi, - }) - } - } else { - initialContext.emitter.trigger('_noEventDrop') - } - } - - this.cleanup() - } - - // render a drag state on the next receivingCalendar - displayDrag(nextContext: CalendarContext | null, state: EventInteractionState) { - let initialContext = this.component.context - let 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 }) - } - } - - clearDrag() { - let initialCalendar = this.component.context - let { receivingContext } = this - - 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' }) - } - } - - cleanup() { // reset all internal state - this.subjectSeg = null - this.isDragging = false - this.eventRange = null - this.relevantEvents = null - this.receivingContext = null - this.validMutation = null - this.mutatedRelevantEvents = null - } -} - -function computeEventMutation(hit0: Hit, hit1: Hit, massagers: eventDragMutationMassager[]): EventMutation { - let dateSpan0 = hit0.dateSpan - let dateSpan1 = hit1.dateSpan - let date0 = dateSpan0.range.start - let date1 = dateSpan1.range.start - let standardProps = {} as any - - 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) - } - } - - let delta = diffDates( - date0, date1, - hit0.context.dateEnv, - hit0.componentId === hit1.componentId ? - hit0.largeUnit : - null, - ) - - if (delta.milliseconds) { // has hours/minutes/seconds - standardProps.allDay = false - } - - let mutation: EventMutation = { - datesDelta: delta, - standardProps, - } - - for (let massager of massagers) { - massager(mutation, hit0, hit1) - } - - return mutation -} - -function getComponentTouchDelay(component: DateComponent): number | null { - let { options } = component.context - let delay = options.eventLongPressDelay - - if (delay == null) { - delay = options.longPressDelay - } - - return delay -} diff --git a/apps/schoolCalendar/fullcalendar/interaction/src/interactions/EventResizing.ts b/apps/schoolCalendar/fullcalendar/interaction/src/interactions/EventResizing.ts deleted file mode 100644 index 013b399ae..000000000 --- a/apps/schoolCalendar/fullcalendar/interaction/src/interactions/EventResizing.ts +++ /dev/null @@ -1,263 +0,0 @@ -import { - Seg, Hit, - EventMutation, applyMutationToEventStore, - elementClosest, - PointerDragEvent, - EventStore, getRelevantEvents, createEmptyEventStore, - diffDates, enableCursor, disableCursor, - DateRange, - EventApi, - EventRenderRange, getElSeg, - createDuration, - EventInteractionState, - Interaction, InteractionSettings, interactionSettingsToStore, ViewApi, Duration, EventChangeArg, buildEventApis, isInteractionValid, -} from '@fullcalendar/common' -import { __assign } from 'tslib' -import { HitDragging, isHitsEqual } from './HitDragging' -import { FeaturefulElementDragging } from '../dnd/FeaturefulElementDragging' - -export type EventResizeStartArg = EventResizeStartStopArg -export type EventResizeStopArg = EventResizeStartStopArg - -export interface EventResizeStartStopArg { - el: HTMLElement - event: EventApi - jsEvent: MouseEvent - view: ViewApi -} - -export interface EventResizeDoneArg extends EventChangeArg { - el: HTMLElement - startDelta: Duration - endDelta: Duration - jsEvent: MouseEvent - view: ViewApi -} - -export class EventResizing extends Interaction { - dragging: FeaturefulElementDragging - hitDragging: HitDragging - - // internal state - draggingSegEl: HTMLElement | null = null - draggingSeg: Seg | null = null // TODO: rename to resizingSeg? subjectSeg? - eventRange: EventRenderRange | null = null - relevantEvents: EventStore | null = null - validMutation: EventMutation | null = null - mutatedRelevantEvents: EventStore | null = null - - constructor(settings: InteractionSettings) { - super(settings) - let { component } = settings - - let dragging = this.dragging = new FeaturefulElementDragging(settings.el) - dragging.pointer.selector = '.fc-event-resizer' - dragging.touchScrollAllowed = false - dragging.autoScroller.isEnabled = component.context.options.dragScroll - - let 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) - } - - destroy() { - this.dragging.destroy() - } - - handlePointerDown = (ev: PointerDragEvent) => { - let { component } = this - let segEl = this.querySegEl(ev) - let seg = getElSeg(segEl) - let 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 as HTMLElement) || - (ev.isTouch && this.component.props.eventSelection !== eventRange.instance!.instanceId), - ) - } - - handleDragStart = (ev: PointerDragEvent) => { - let { context } = this.component - let eventRange = this.eventRange! - - this.relevantEvents = getRelevantEvents( - context.getCurrentData().eventStore, - this.eventRange.instance!.instanceId, - ) - - let 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 as MouseEvent, // Is this always a mouse event? See #4655 - view: context.viewApi, - } as EventResizeStartArg) - } - - handleHitUpdate = (hit: Hit | null, isFinal: boolean, ev: PointerDragEvent) => { - let { context } = this.component - let relevantEvents = this.relevantEvents! - let initialHit = this.hitDragging.initialHit! - let eventInstance = this.eventRange.instance! - let mutation: EventMutation | null = null - let mutatedRelevantEvents: EventStore | null = null - let isInvalid = false - let interaction: EventInteractionState = { - affectedEvents: relevantEvents, - mutatedEvents: createEmptyEventStore(), - isEvent: true, - } - - if (hit) { - let disallowed = hit.componentId === initialHit.componentId - && this.isHitComboAllowed - && !this.isHitComboAllowed(initialHit, hit) - - if (!disallowed) { - mutation = computeMutation( - initialHit, - hit, - (ev.subjectEl as HTMLElement).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 - } - } - - handleDragEnd = (ev: PointerDragEvent) => { - let { context } = this.component - let eventDef = this.eventRange!.def - let eventInstance = this.eventRange!.instance - let eventApi = new EventApi(context, eventDef, eventInstance) - let relevantEvents = this.relevantEvents! - let mutatedRelevantEvents = this.mutatedRelevantEvents! - - context.emitter.trigger('eventResizeStop', { - el: this.draggingSegEl, - event: eventApi, - jsEvent: ev.origEvent as MouseEvent, // Is this always a mouse event? See #4655 - view: context.viewApi, - } as EventResizeStopArg) - - if (this.validMutation) { - let updatedEventApi = new EventApi( - context, - mutatedRelevantEvents.defs[eventDef.defId], - eventInstance ? mutatedRelevantEvents.instances[eventInstance.instanceId] : null, - ) - - context.dispatch({ - type: 'MERGE_EVENTS', - eventStore: mutatedRelevantEvents, - }) - - let eventChangeArg: EventChangeArg = { - oldEvent: eventApi, - event: updatedEventApi, - relatedEvents: buildEventApis(mutatedRelevantEvents, context, eventInstance), - revert() { - context.dispatch({ - type: 'MERGE_EVENTS', - eventStore: relevantEvents, // the pre-change events - }) - }, - } - - context.emitter.trigger('eventResize', { - ...eventChangeArg, - el: this.draggingSegEl, - startDelta: this.validMutation.startDelta || createDuration(0), - endDelta: this.validMutation.endDelta || createDuration(0), - jsEvent: ev.origEvent as MouseEvent, - 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 - } - - querySegEl(ev: PointerDragEvent) { - return elementClosest(ev.subjectEl as HTMLElement, '.fc-event') - } -} - -function computeMutation( - hit0: Hit, - hit1: Hit, - isFromStart: boolean, - instanceRange: DateRange, -): EventMutation | null { - let dateEnv = hit0.context.dateEnv - let date0 = hit0.dateSpan.range.start - let date1 = hit1.dateSpan.range.start - - let 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 -} diff --git a/apps/schoolCalendar/fullcalendar/interaction/src/interactions/HitDragging.ts b/apps/schoolCalendar/fullcalendar/interaction/src/interactions/HitDragging.ts deleted file mode 100644 index d0a329e9c..000000000 --- a/apps/schoolCalendar/fullcalendar/interaction/src/interactions/HitDragging.ts +++ /dev/null @@ -1,220 +0,0 @@ -import { - Emitter, PointerDragEvent, - isDateSpansEqual, - computeRect, - constrainPoint, intersectRects, getRectCenter, diffPoints, Point, - rangeContainsRange, - Hit, - InteractionSettingsStore, - mapHash, - ElementDragging, -} from '@fullcalendar/common' -import { OffsetTracker } from '../OffsetTracker' - -/* -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 -*/ -export class HitDragging { - droppableStore: InteractionSettingsStore - dragging: ElementDragging - emitter: Emitter - - // options that can be set by caller - useSubjectCenter: boolean = false - requireInitial: boolean = true // if doesn't start out on a hit, won't emit any events - - // internal state - offsetTrackers: { [componentUid: string]: OffsetTracker } - initialHit: Hit | null = null - movingHit: Hit | null = null - finalHit: Hit | null = null // won't ever be populated if shouldIgnoreMove - coordAdjust?: Point - - constructor(dragging: ElementDragging, droppableStore: InteractionSettingsStore) { - 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() - } - - handlePointerDown = (ev: PointerDragEvent) => { - let { dragging } = this - - 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) - } - } - - // sets initialHit - // sets coordAdjust - processFirstCoord(ev: PointerDragEvent) { - let origPoint = { left: ev.pageX, top: ev.pageY } - let adjustedPoint = origPoint - let subjectEl = ev.subjectEl - let subjectRect - - if (subjectEl instanceof HTMLElement) { // i.e. not a Document/ShadowRoot - subjectRect = computeRect(subjectEl) - adjustedPoint = constrainPoint(adjustedPoint, subjectRect) - } - - let initialHit = this.initialHit = this.queryHitForOffset(adjustedPoint.left, adjustedPoint.top) - if (initialHit) { - if (this.useSubjectCenter && subjectRect) { - let slicedSubjectRect = intersectRects(subjectRect, initialHit.rect) - if (slicedSubjectRect) { - adjustedPoint = getRectCenter(slicedSubjectRect) - } - } - - this.coordAdjust = diffPoints(adjustedPoint, origPoint) - } else { - this.coordAdjust = { left: 0, top: 0 } - } - } - - handleDragStart = (ev: PointerDragEvent) => { - this.emitter.trigger('dragstart', ev) - this.handleMove(ev, true) // force = fire even if initially null - } - - handleDragMove = (ev: PointerDragEvent) => { - this.emitter.trigger('dragmove', ev) - this.handleMove(ev) - } - - handlePointerUp = (ev: PointerDragEvent) => { - this.releaseHits() - this.emitter.trigger('pointerup', ev) - } - - handleDragEnd = (ev: PointerDragEvent) => { - if (this.movingHit) { - this.emitter.trigger('hitupdate', null, true, ev) - } - - this.finalHit = this.movingHit - this.movingHit = null - this.emitter.trigger('dragend', ev) - } - - handleMove(ev: PointerDragEvent, forceHandle?: boolean) { - let 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) - } - } - - prepareHits() { - this.offsetTrackers = mapHash(this.droppableStore, (interactionSettings) => { - interactionSettings.component.prepareHits() - return new OffsetTracker(interactionSettings.el) - }) - } - - releaseHits() { - let { offsetTrackers } = this - - for (let id in offsetTrackers) { - offsetTrackers[id].destroy() - } - - this.offsetTrackers = {} - } - - queryHitForOffset(offsetLeft: number, offsetTop: number): Hit | null { - let { droppableStore, offsetTrackers } = this - let bestHit: Hit | null = null - - for (let id in droppableStore) { - let component = droppableStore[id].component - let offsetTracker = offsetTrackers[id] - - if ( - offsetTracker && // wasn't destroyed mid-drag - offsetTracker.isWithinClipping(offsetLeft, offsetTop) - ) { - let originLeft = offsetTracker.computeLeft() - let originTop = offsetTracker.computeTop() - let positionLeft = offsetLeft - originLeft - let positionTop = offsetTop - originTop - let { origRect } = offsetTracker - let width = origRect.right - origRect.left - let height = origRect.bottom - origRect.top - - if ( - // must be within the element's bounds - positionLeft >= 0 && positionLeft < width && - positionTop >= 0 && positionTop < height - ) { - let 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 - } -} - -export function isHitsEqual(hit0: Hit | null, hit1: Hit | null): boolean { - if (!hit0 && !hit1) { - return true - } - - if (Boolean(hit0) !== Boolean(hit1)) { - return false - } - - return isDateSpansEqual(hit0!.dateSpan, hit1!.dateSpan) -} diff --git a/apps/schoolCalendar/fullcalendar/interaction/src/interactions/UnselectAuto.ts b/apps/schoolCalendar/fullcalendar/interaction/src/interactions/UnselectAuto.ts deleted file mode 100644 index 23e5b47cb..000000000 --- a/apps/schoolCalendar/fullcalendar/interaction/src/interactions/UnselectAuto.ts +++ /dev/null @@ -1,77 +0,0 @@ -import { - DateSelectionApi, - PointerDragEvent, - elementClosest, - CalendarContext, - getEventTargetViaRoot, -} from '@fullcalendar/common' -import { PointerDragging } from '../dnd/PointerDragging' -import { EventDragging } from './EventDragging' - -export class UnselectAuto { - documentPointer: PointerDragging // for unfocusing - isRecentPointerDateSelect = false // wish we could use a selector to detect date selection, but uses hit system - matchesCancel = false - matchesEvent = false - - constructor(private context: CalendarContext) { - let 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) - } - - destroy() { - this.context.emitter.off('select', this.onSelect) - this.documentPointer.destroy() - } - - onSelect = (selectInfo: DateSelectionApi) => { - if (selectInfo.jsEvent) { - this.isRecentPointerDateSelect = true - } - } - - onDocumentPointerDown = (pev: PointerDragEvent) => { - let unselectCancel = this.context.options.unselectCancel - let downEl = getEventTargetViaRoot(pev.origEvent) as HTMLElement - - this.matchesCancel = !!elementClosest(downEl, unselectCancel) - this.matchesEvent = !!elementClosest(downEl, EventDragging.SELECTOR) // interaction started on an event? - } - - onDocumentPointerUp = (pev: PointerDragEvent) => { - let { context } = this - let { documentPointer } = this - let 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? - ) { - let 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 - } -} diff --git a/apps/schoolCalendar/fullcalendar/interaction/src/main.global.ts b/apps/schoolCalendar/fullcalendar/interaction/src/main.global.ts deleted file mode 100644 index 0af579774..000000000 --- a/apps/schoolCalendar/fullcalendar/interaction/src/main.global.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { globalPlugins } from '@fullcalendar/common' -import plugin from './main' - -globalPlugins.push(plugin) - -export default plugin -export * from './main' diff --git a/apps/schoolCalendar/fullcalendar/interaction/src/main.ts b/apps/schoolCalendar/fullcalendar/interaction/src/main.ts deleted file mode 100644 index f57049ca3..000000000 --- a/apps/schoolCalendar/fullcalendar/interaction/src/main.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { createPlugin } from '@fullcalendar/common' -import { DateClicking } from './interactions/DateClicking' -import { DateSelecting } from './interactions/DateSelecting' -import { EventDragging } from './interactions/EventDragging' -import { EventResizing } from './interactions/EventResizing' -import { UnselectAuto } from './interactions/UnselectAuto' -import { FeaturefulElementDragging } from './dnd/FeaturefulElementDragging' -import { OPTION_REFINERS, LISTENER_REFINERS } from './options' -import './options-declare' - -export default createPlugin({ - componentInteractions: [DateClicking, DateSelecting, EventDragging, EventResizing], - calendarInteractions: [UnselectAuto], - elementDraggingImpl: FeaturefulElementDragging, - optionRefiners: OPTION_REFINERS, - listenerRefiners: LISTENER_REFINERS, -}) - -export * from './api-type-deps' -export { FeaturefulElementDragging } -export { PointerDragging } from './dnd/PointerDragging' -export { ExternalDraggable as Draggable } from './interactions-external/ExternalDraggable' -export { ThirdPartyDraggable } from './interactions-external/ThirdPartyDraggable' diff --git a/apps/schoolCalendar/fullcalendar/interaction/src/options-declare.ts b/apps/schoolCalendar/fullcalendar/interaction/src/options-declare.ts deleted file mode 100644 index 9cbc5a4a8..000000000 --- a/apps/schoolCalendar/fullcalendar/interaction/src/options-declare.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { OPTION_REFINERS, LISTENER_REFINERS } from './options' - -type ExtraOptionRefiners = typeof OPTION_REFINERS -type ExtraListenerRefiners = typeof LISTENER_REFINERS - -declare module '@fullcalendar/common' { - interface BaseOptionRefiners extends ExtraOptionRefiners {} - interface CalendarListenerRefiners extends ExtraListenerRefiners {} -} diff --git a/apps/schoolCalendar/fullcalendar/interaction/src/options.ts b/apps/schoolCalendar/fullcalendar/interaction/src/options.ts deleted file mode 100644 index a11ce5399..000000000 --- a/apps/schoolCalendar/fullcalendar/interaction/src/options.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { identity, Identity, EventDropArg } from '@fullcalendar/common' - -// public -import { - DateClickArg, - EventDragStartArg, EventDragStopArg, - EventResizeStartArg, EventResizeStopArg, EventResizeDoneArg, - DropArg, EventReceiveArg, EventLeaveArg, -} from './api-type-deps' - -export const OPTION_REFINERS = { - fixedMirrorParent: identity as Identity, -} - -export const LISTENER_REFINERS = { - dateClick: identity as Identity<(arg: DateClickArg) => void>, - eventDragStart: identity as Identity<(arg: EventDragStartArg) => void>, - eventDragStop: identity as Identity<(arg: EventDragStopArg) => void>, - eventDrop: identity as Identity<(arg: EventDropArg) => void>, - eventResizeStart: identity as Identity<(arg: EventResizeStartArg) => void>, - eventResizeStop: identity as Identity<(arg: EventResizeStopArg) => void>, - eventResize: identity as Identity<(arg: EventResizeDoneArg) => void>, - drop: identity as Identity<(arg: DropArg) => void>, - eventReceive: identity as Identity<(arg: EventReceiveArg) => void>, - eventLeave: identity as Identity<(arg: EventLeaveArg) => void>, -} diff --git a/apps/schoolCalendar/fullcalendar/interaction/src/utils.ts b/apps/schoolCalendar/fullcalendar/interaction/src/utils.ts deleted file mode 100644 index 056a0040d..000000000 --- a/apps/schoolCalendar/fullcalendar/interaction/src/utils.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { DateSpan, CalendarContext, DatePointApi, DateEnv, ViewApi, EventApi } from '@fullcalendar/common' -import { __assign } from 'tslib' - -export interface DropArg extends DatePointApi { - draggedEl: HTMLElement - jsEvent: MouseEvent - view: ViewApi -} - -export type EventReceiveArg = EventReceiveLeaveArg -export type EventLeaveArg = EventReceiveLeaveArg -export interface EventReceiveLeaveArg { // will this become public? - draggedEl: HTMLElement - event: EventApi - relatedEvents: EventApi[] - revert: () => void - view: ViewApi -} - -export function buildDatePointApiWithContext(dateSpan: DateSpan, context: CalendarContext) { - let props = {} as DatePointApi - - for (let transform of context.pluginHooks.datePointTransforms) { - __assign(props, transform(dateSpan, context)) - } - - __assign(props, buildDatePointApi(dateSpan, context.dateEnv)) - - return props -} - -export function buildDatePointApi(span: DateSpan, dateEnv: DateEnv): DatePointApi { - return { - date: dateEnv.toDate(span.range.start), - dateStr: dateEnv.formatIso(span.range.start, { omitTime: span.allDay }), - allDay: span.allDay, - } -} From 740f4a95665fd92ad87e83dd381da7e55c2e4b18 Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Mon, 22 Nov 2021 10:52:27 -0800 Subject: [PATCH 151/155] Create boot.js not part of the current merge --- apps/schoolCalendar/boot.js | 1 + 1 file changed, 1 insertion(+) create mode 100644 apps/schoolCalendar/boot.js diff --git a/apps/schoolCalendar/boot.js b/apps/schoolCalendar/boot.js new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/apps/schoolCalendar/boot.js @@ -0,0 +1 @@ + From 84d9ca4b337e36c4f62ffce4b3059f8f2909d482 Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Mon, 22 Nov 2021 10:57:14 -0800 Subject: [PATCH 152/155] Update boot.js --- apps/schoolCalendar/boot.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/apps/schoolCalendar/boot.js b/apps/schoolCalendar/boot.js index 8b1378917..e4f223c0c 100644 --- a/apps/schoolCalendar/boot.js +++ b/apps/schoolCalendar/boot.js @@ -1 +1,6 @@ - +// check for alarms +(function() { + var alarms = require('Storage').readJSON('schoolCalendarAlarms.json',1)||[]; + var time = new Date(); + E.showPrompt(School Calendar Alarm Test) +})(); From 7fd37f7e4e669c0aea7b5324b85f9d85ca394dcb Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Mon, 22 Nov 2021 12:18:44 -0800 Subject: [PATCH 153/155] Update custom.html --- apps/schoolCalendar/custom.html | 38 ++++----------------------------- 1 file changed, 4 insertions(+), 34 deletions(-) diff --git a/apps/schoolCalendar/custom.html b/apps/schoolCalendar/custom.html index 49e0acb13..225049b28 100644 --- a/apps/schoolCalendar/custom.html +++ b/apps/schoolCalendar/custom.html @@ -94,14 +94,14 @@ require("Font8x12").add(Graphics); require("Font7x11Numeric7Seg", 2).add(Graphics); -let nIntervId; +var file = require("Storage").open("calendarItems.csv","w"); +let nIntervId; function redrawScreen() { layout.render(layout.background); layout.render(layout.buttons); draw(); } - function updateDay(ffunction,day){ if(ffunction == 1){ switch (day) { @@ -174,12 +174,13 @@ function updateDay(ffunction,day){ return day; } } - function getScheduleTable() { let schedule = ${JSON.stringify(schedule)}; return schedule; } +file.write(JSON.stringify(schedule)); + function findNextScheduleIndex() { var schedule = getScheduleTable(); var currentDate = new Date(); @@ -194,16 +195,10 @@ function findNextScheduleIndex() { } return 0; } - - function getUpArrow() {return require("heatshrink").decompress(atob("hkOyANKmv9AIIjRCoYZRlvdAI8U3YVK3oBJC4Mc7YVRC4sc7gVCzoBNC4oZDGowXGR58lvoBFC9FcAIoXongBFC58dngBFC6EcAIoPHA"));} - function getDownArrow() {return require("heatshrink").decompress(atob("hkOyALImv9AIojPmvdAIoXPlvdAIoXQ3oBFC9GdAIoXnkt9AIoPPAI8U3cc7cc7gBBDIVcAJYXFGYwXOLpU8AI4XBO5sdjgBFR54ZFBpIA=="));} - function getMenuIcon() {return require("heatshrink").decompress(atob("iEQyBC/AEU+rwBEn02js17st3stvklrkljkc/cc3cUzYBBD5AdUD4oA/P/4A/P/4A/ADoA=="));} - function getDotIcon() {return require("heatshrink").decompress(atob("iEQyBC/AA0t3oBBA4ndAIIPGA4gAFkt9lt9AYIHEzoBBBIwRED41cks8AYIJGA44RGP8xtGP44RJBYh1CAIIHHBJJ/KroBBPoqBFB4YRDAA8dngHHBJKdq3oBDBI4RNP4l9AIYHHBJJBJks8AIIHTAH4ABA="));} - var currentPositionTable = 0; var numberOfItemsShown = 8; //Table Positions: @@ -215,7 +210,6 @@ var rectEndX = 210; LIST = 1; INFORMATION = 2; currentStage = LIST; - function splitter(str, l){ var strs = []; while(str.length > l){ @@ -230,7 +224,6 @@ function splitter(str, l){ strs.push(str); return strs; } - function updateMinutesToCurrentTime(currentMinuteFunction) { if (currentMinuteFunction<10){ currentMinuteUpdatedFunction = "0"+currentMinuteFunction; @@ -239,12 +232,10 @@ function updateMinutesToCurrentTime(currentMinuteFunction) { } return currentMinuteUpdatedFunction; } - function renderBackground(l) { g.clearRect(0,0,240,20); g.drawImage(getBackgroundImage(),110,130,{scale:9,rotate:0}); } - function renderTable(l) { var foundNumber = findNextScheduleIndex(); var yellowIndex = 3; @@ -258,7 +249,6 @@ function renderTable(l) { g.setColor(255,0,0); g.drawRect(rectStartX,rectStart+(currentPositionTable*20),rectEndX,rectEnd+(20*currentPositionTable)); } - function renderTableText(l) { var foundSchedule = getScheduleTable(); var foundNumber = findNextScheduleIndex(); @@ -266,8 +256,6 @@ function renderTableText(l) { if (startNumber < 0) { startNumber = 0; } var endNumber = startNumber + 8 - (foundNumber - startNumber); if (endNumber > foundSchedule.length-1) { endNumber = foundSchedule.length-1; } - - var scheduleHourUpdated; var scheduleMinuteUpdated; for(var currentNumber = startNumber; currentNumber<=endNumber; currentNumber++){ @@ -284,7 +272,6 @@ function renderTableText(l) { g.drawString(scheduleHourUpdatedStart+":"+scheduleMinuteUpdatedStart+"-"+scheduleHourUpdatedEnd+":"+scheduleMinuteUpdatedEnd+" "+schduleDay+" "+scheduleDecriptionUpdated,13,50+(currentNumber*20)); } } - function buttonsF(l){ if(currentStage == LIST){ g.drawImage(getDotIcon(),223.5,115); @@ -294,7 +281,6 @@ function buttonsF(l){ g.drawImage(getUpArrow(),225,30); g.drawImage(getDownArrow(),225,215); } - function draw() { var currentDate = new Date(); var currentDayOfWeek = currentDate.getDay(); @@ -320,7 +306,6 @@ function draw() { layout.render(layout.time); } } - function RedRectDown() { if(currentPositionTable > 0){ currentPositionTable -= 1; @@ -331,7 +316,6 @@ function RedRectDown() { } } } - function RedRectUp() { if(currentPositionTable < numberOfItemsShown){ currentPositionTable += 1; @@ -342,24 +326,20 @@ function RedRectUp() { } } } - function renderMiniBackground(l){ for(var i = 233;i<=240;i++){ g.drawImage(getBackgroundImage(),i,123,{scale:10,rotate:0}); } } - function renderLoading(l){ g.setFont("8x12"); g.drawString("Loading...",240/2-20,240/2-20); } - function renderInformation(l){ var foundNumber = findNextScheduleIndex(); var foundSchedule = getScheduleTable(); var startNumber = foundNumber - 2; if (startNumber < 0) { startNumber = 0; } - if ((startNumber+currentPositionTable) <= foundSchedule.length-1) { scheduleMinuteUpdatedStart = updateMinutesToCurrentTime(foundSchedule[foundNumber].sm); scheduleHourUpdatedStart = foundSchedule[foundNumber].sh; @@ -379,7 +359,6 @@ function renderInformation(l){ g.drawString(scheduleHourUpdatedStart+":"+scheduleMinuteUpdatedStart+"-"+scheduleHourUpdatedEnd+":"+scheduleMinuteUpdatedEnd,13,currentY+15+50); } } - var Layout = require("Layout"); var layout = new Layout( {type:"h", c: [ @@ -399,12 +378,8 @@ var layout = new Layout( {label:"", cb: l=>print("Two")}, {label:"", cb: RedRectDown()} ]}); - - function getBackgroundImage() {return require("heatshrink").decompress(atob("j0ZyEKIf4A4gIB6gQB6gYB6ggB6goB6gwB6g4B6hAABAYIBHBZIVLAK8IhIBXgAThhQB6hYB6hgB6hoB6hwB6h4B6iAB6iIB6iQBHiAJOB54XSiYB6igB6ioB6iwB6i4B5A="));} - function logDebug(message) {console.log(message);} - function changeScene(){ layout.render(layout.buttons); if(currentStage == INFORMATION){ @@ -418,17 +393,12 @@ function changeScene(){ layout.render(layout.buttons); draw(); } - // timeout used to update every minute var drawTimeout; - setInterval(draw, 15000); - - setWatch(RedRectUp, BTN3, { repeat:true, edge:'rising', debounce : 50 }); setWatch(RedRectDown, BTN1, { repeat:true, edge:'rising', debounce : 50 }); setWatch(changeScene, BTN2, { repeat:true, edge:'rising', debounce : 50 }); - layout.update(); layout.render(layout.loading); layout.render(layout.background); From 0664a44c454107d4e3ff039d12baffea3e90cf5f Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Mon, 22 Nov 2021 18:52:43 -0800 Subject: [PATCH 154/155] Update apps.json --- apps.json | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/apps.json b/apps.json index 8d90a1d1a..0b58abd97 100644 --- a/apps.json +++ b/apps.json @@ -4348,5 +4348,24 @@ {"name":"authentiwatch.img","url":"app-icon.js","evaluate":true} ], "data": [{"name":"authentiwatch.json"}] + }, + { "id": "schoolCalendar", + "name": "School Calendar", + "shortName":"SCalendar", + "icon": "CalenderLogo.png", + "version": "0.01", + "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", + "custom":"custom.html", + "supports": ["BANGLEJS"], + "screenshots": [{"url":"screenshot_basic.png"},{"url":"screenshot_info.png"}], + "storage": [ + {"name":"schoolCalendar.app.js"}, + {"name":"schoolCalendar.img","url":"app-icon.js","evaluate":true} + ], + "data": [ + {"name":"app.json"} + ] } ] From be083113316ec7ba07d780b6593cb9adfd748298 Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Mon, 22 Nov 2021 19:11:40 -0800 Subject: [PATCH 155/155] Update custom.html --- apps/schoolCalendar/custom.html | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/apps/schoolCalendar/custom.html b/apps/schoolCalendar/custom.html index 225049b28..6017b4396 100644 --- a/apps/schoolCalendar/custom.html +++ b/apps/schoolCalendar/custom.html @@ -93,9 +93,7 @@ var app = ` require("Font8x12").add(Graphics); require("Font7x11Numeric7Seg", 2).add(Graphics); - var file = require("Storage").open("calendarItems.csv","w"); - let nIntervId; function redrawScreen() { layout.render(layout.background); @@ -175,12 +173,15 @@ function updateDay(ffunction,day){ } } function getScheduleTable() { - let schedule = ${JSON.stringify(schedule)}; + let schedule = [//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"}];//${JSON.stringify(schedule)}; + logDebug(JSON.stringify(schedule)); return schedule; } -file.write(JSON.stringify(schedule)); - function findNextScheduleIndex() { var schedule = getScheduleTable(); var currentDate = new Date(); @@ -403,8 +404,8 @@ layout.update(); layout.render(layout.loading); layout.render(layout.background); layout.render(layout.buttons); - draw(); +file.write(JSON.stringify(schedule)); `; // send finished app (in addition to contents of app.json) sendCustomizedApp({

    Wm?3!Mt<_!7vupX1HX?3>?LHW#^ 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 OaCrVtk_Ynp^8WyThLmdn literal 0 HcmV?d00001 diff --git a/apps/trex/README.md b/apps/trex/README.md new file mode 100644 index 000000000..03e3f5883 --- /dev/null +++ b/apps/trex/README.md @@ -0,0 +1,4 @@ +# T-Rex + +![](screenshot_trex.png) + diff --git a/apps/trex/screenshot_trex.png b/apps/trex/screenshot_trex.png new file mode 100644 index 0000000000000000000000000000000000000000..a66cc013fa349cf661d12c4e511d0fd7529f0bda GIT binary patch literal 668 zcmV;N0%QG&P)Px%R!KxbRCr$P+~JagAPfXxegB8HV^4Z%iny#qN7(+kn>Nt$86#-zye!Ky=>ZGO z^E@T8_5bx@3GM6aYqNl1+bIY7M%>W-UjgH`pU9sKH5qNOQ-hEUPFs5K^FO&NdEm=4 z_y{+lKM~oEZT{?xNKcHRfNKUiYUWm->_M_M2QT0Xfm-B>u4{IRN5#S=;*;{*S$t%a z0w%9$Aek)OzZ9&_U?h`;`@etFz-dx#r*3<5Wu%<+8q);WUsbhh@m}H3ah(QMIVe+~ z23EEP0;9Xh$Z@AKcw{(q>;gum$T$I$aA-3Dqf%s?fJr#CnSfC#GETrG9NJ94s1z9| zU=j{(CSX*Gj1w>khc**1Dn-T#n1n-{2^f_k;{;5?q0I!0N|ABH0$+83R=~k`5)TJ# zzwW>8_0bn?hXT&s!)I^oj=&c1RwAOl!z^F{lk=lYGnI&Tmf4<4cR{H{G{X1DI>(gc-s#zT%ii6e&P4=o0Ram*SRlB2Ww3yQ1%kW3 zEQ8lmqQPSsx_fvY?0=Ugw=!xqr)+Uv;HnJuix+S31DqS2f9~klPmb32HT|*a?-Qal zuD$V~<1Br~H?ZvpeKR)8(EeFiWY6-aElYdeS>zWB@m^wF=+_(o0000=3) lastBreaths.shift(); // keep length small + lastBreaths.push(value); + value = E.sum(lastBreaths) / lastBreaths.length; + // draw value + g.reset(); + g.clearRect(0,g.getHeight()-100,g.getWidth(),g.getHeight()-50); + g.setFont("6x8").setFontAlign(0,0); + g.drawString("Calculated measurement", g.getWidth()/2, g.getHeight()-95); + g.setFont("Vector",40).setFontAlign(0,0); + g.drawString(value.toFixed(2), g.getWidth()/2, g.getHeight()-70); + // set vibration IF we're doing it from our calculations + if (settings.vibrate == "calculated") + setVibrate(value > settings.vibrateBPM); + } + lastBreath = t; +} + +function onData(n, value) { + g.reset(); + if (n==2) { + function scale(v) { + return Math.max(graphHeight - (1+v*4),24); + } + if (avrValue==undefined) avrValue=value; + avrValue = avrValue*0.95 + value*0.05; + if (avrValue < 1) avrValue = 1; + if (value > avrValue) { + if (!aboveAvr) onBreath(); + aboveAvr = true; + } else aboveAvr = false; + + var t = Date.now(); + var x = Math.round((t - last.time) / 100) // 10 per second + if (last.x>=g.getWidth()) { + x = 0; + last.x = 0; + last.time = t; + g.clearRect(0,24,g.getWidth(),graphHeight); + } + var y = scale(value); + g.setPixel(x, scale(avrValue), "#f00"); + g.drawLine(last.x, last.y, x, y); + last.x = x; + last.y = y; + } + if (n==4) { + g.clearRect(0,g.getHeight()-50,g.getWidth(),g.getHeight()); + g.setFont("6x8").setFontAlign(0,0); + g.drawString("GoDirect measurement", g.getWidth()/2, g.getHeight()-45); + g.setFont("Vector",40).setFontAlign(0,0); + g.drawString(value.toFixed(2), g.getWidth()/2, g.getHeight()-20); + // set vibration IF we're doing it from our calculations + if (settings.vibrate == "vernier") + setVibrate(value > settings.vibrateBPM); + } + Bangle.setLCDPower(1); // ensure LCD is on +} + +function connect() { + var gatt, service, rx, tx; + var rollingCounter = 0xFF; + + // any button to exit + Bangle.setUI("updown", function() { + setVibrate(false); + Bangle.buzz(); + try { + if (gatt) gatt.disconnect(); + } catch (e) { + } + setTimeout(mainMenu, 1000); + }); + + function sendCommand(subCommand) { + const command = new Uint8Array(4 + subCommand.length); + command.set(new Uint8Array(subCommand), 4); + // Populate the packet header bytes + command[0] = 0x58; // header + command[1] = command.length; + command[2] = --rollingCounter; + command[3] = E.sum(command) & 0xFF; // checksum + return tx.writeValue(command); + } + function firstSetBit(v) { + return v & -v; + } + function handleResponse(dv) { + //print(dv.buffer); + var resType = dv.getUint8(0); + if (resType==0x20) { + // [32, 25, 207, 216, 6, 6, 0, 2, 252, 128, 138, 7, 191, 0, 0, 192, 127, 128, 49, 8, 191, 0, 0, 192, 127]) + // 6 = data type = real + // 6,0 = bit mask for sensors + // 2 = value count + if (dv.getUint8(4)!=6) return; //throw "Not float32 data"; + var sensorIds = dv.getUint16(5, true); + // var count = dv.getUint8(7); doesn't seem right + var offs = 9; + while (sensorIds) { + var value = dv.getFloat32(offs, true); + var s = firstSetBit(sensorIds); + if (isFinite(value)) onData(s,value); + //else print(s,value); + sensorIds &= ~s; + offs += 4; + } + } else { + var cmd = dv.getUint8(4); // cmd + //print("CMD",dv.buffer); + } + } + + onMsg("Searching..."); + NRF.requestDevice({ filters: [{ namePrefix: 'GDX-RB' }] }).then(function(device) { + device.on("gattserverdisconnected", function() { + onMsg("Device disconnected"); + }); + onMsg("Found. Connecting..."); + return device.gatt.connect({minInterval:20, maxInterval:20}); + }).then(function(g) { + gatt = g; + return gatt.getPrimaryService("d91714ef-28b9-4f91-ba16-f0d9a604f112"); + }).then(function(s) { + service = s; + return service.getCharacteristic("f4bf14a6-c7d5-4b6d-8aa8-df1a7c83adcb"); + }).then(function(c) { + tx = c; + return service.getCharacteristic("b41e6675-a329-40e0-aa01-44d2f444babe"); + }).then(function(c) { + rx = c; + rx.on('characteristicvaluechanged', function(event) { + //print("EVT",event.target.value.buffer); + handleResponse(event.target.value); + }); + return rx.startNotifications(); + }).then(function() { + onMsg("Init"); + sendCommand([ // init + 0x1a, 0xa5, 0x4a, 0x06, + 0x49, 0x07, 0x48, 0x08, + 0x47, 0x09, 0x46, 0x0a, + 0x45, 0x0b, 0x44, 0x0c, + 0x43, 0x0d, 0x42, 0x0e, + 0x41, + ]); + /*setTimeout(function() { + print("Set measurement period"); + var us = 100000; // period in us + sendCommand([0x1b, 0xff, 0x00, + us & 255, + (us >> 8) & 255, + (us >> 16) & 255, + (us >> 24) & 255, + 0x00, + 0x00, + 0x00, + 0x00]); + }, 100);*/ + + /* setTimeout(function() { + print("Get sensor info"); + sendCommand([0x51, 0]); // get sensor IDs + // returns [152, 10, 1, 39, 81, 253, 54, 0, 0, 0] + // 54 is the bit mask of available channels + //sendCommand([106, 16]); // get sensor info + }, 2000);*/ + + setTimeout(function() { + onMsg("Start measurements"); + //https://github.com/VernierST/godirect-js/blob/main/src/Device.js#L588 + var channels = 6; // data channels 4 and 2 + sendCommand([ // start measurements + 0x18, 0xff, 0x01, channels, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00 + ]); + }, 500); + }).catch(function() { + onMsg("Connect Fail"); + }); +} + +Bangle.loadWidgets(); +Bangle.drawWidgets(); + +function mainMenu() { + var vibText = ["No","Calculated","Vernier"]; + var vibValue = ["","calculated","vernier"]; + E.showMenu({"":{title:"Respiration Belt"}, + "< Back" : () => { saveSettings(); load(); }, + "Connect" : () => { saveSettings(); E.showMenu(); connect(); }, + "Vib" : { + value : Math.max(vibValue.indexOf(settings.vibrate),0), + format : v => vibText[v], + min:0,max:2, + onchange : v => { settings.vibrate=vibValue[v]; } + }, + "BPM" : { + value : settings.vibrateBPM, + min:10,max:50, + onchange : v => { settings.vibrateBPM=v; } + } + }); +} + +mainMenu(); diff --git a/apps/vernierrespirate/app.png b/apps/vernierrespirate/app.png new file mode 100644 index 0000000000000000000000000000000000000000..0f6b22af17e76005f58c052a2c72f7e43f90959f GIT binary patch literal 1986 zcmV;z2R-R7Gvns%k)0zo3?cw!x?&4iT7|;ukj9 zYnwP=hZ+o zbVaO?x(w1%1t$kWyY`h{Z#rv&4mJEtDRq#kpC3ip`T`24kSjr63x{^SnysU( z-Y4Ojo!>>&+cW8vT|XZ4H~Py{E+VnD>u4qUKiN725Dqm+Ci^5}qIQ<{G&aqoH)96+ ztLkeJb!;ZRM2sGJw>FVHfTp{fXX8VZ{Cc3KVeL$M(+1E}_3fb2^gjbRF@ws5pv{mt zr5(&5eMR1U)0moxLMu(WSpk7}I+xT3rrT=T01Q2UFR&tU6Jt#cxKrPN(W561fn@3? zwRgwMUz^g<6?|W=Nj@B5Wzg~MosQ#_0Sr~WP^pAh5@mX2F_@Vfm(q1cj-L80@Y5tY z+)W^~cl5}~*Qd&$3YzE@#mO=y`))4V9h!9jj%FMN@{*LROVEP;G+jMZLU4N>3fYyi zdy^=>30Mgcy&Qb@YiMN!>12W?T4+(KI7hdg1G5TXXvyv`0^8D*^Zcmt0<;3JBvg){ zz?Me{Jo6~3C@)!A)ZcQ0eJ|#{^weoVwgKZYKb6G{Tn&Z~ab2_pK~Nduj#%I~4{MtBM$QkO>CJR7^6DSWy9NH2h*&?z%+QcT za7V^bng*6l;GsQYetiwT<%<&zz-H|CXOZDiZ*BO{D;FZ8lej~-qim0s zbszunlmT=usSo71`VC-Vl9HjLFDpY8=cDBX@vW}*KDa{UUstd$TmzCP@pOnzV&kh{ zz2>!b5kvm53Ue7eGwU( z^fu5mR9*nFY^>(10O;w1Cu``{B>-5>R}srbxneC5joo$ z_miK4{Yf-aaw>cN?z;f?CtYayxiN9%Y%7uGHvCVo_qtD}iJ!tXp^Zvk+CGL4bHhr+ zahYg3+oVYl`mqZ0t_r6s;UWXpeO!N@y6yNr%wU+F~^-3vkltEGd9rZb4+ zVs2Q8GBQ2iPEPgAr1TGVyo zv->5U37;<8T@bO{CzX;qg{%`)72f}v0zf~a&WXPx?j!8s8RCr$Po!fTXI1EMG|NrRBa$;o^*38D0rtH&~SuIEqIM{?p*~$F<^XJdU zAN`R67ggY|0KRDD6!xbA0$3pItH8(oaPaqr>Ek~Fc;E6VvIF4HR{I!Y0W3`RRp18j z4cuQku>jt;e2VM<_@=}J;wiu%=k|S)^6#XT?>1i5e*p!~08bzmz%Nkemq_7Jhm8Py zbW-ZHhrMe2*ukUzU!s`rn#2fk;}0~jTGxI3J74~aajd2 zM~LnYIJ>NQFMYG|mo1tC|56}(i4(vg>1hg7zDA=Y#`m<=@nh#4=h6FT>v)Ps_RSQi z{vd}drepTwmXl>;eIr9kA9xT>|~M+&4B==dZq9h4kM0jmOrcftBTuvJWr zTd#nAq-ee7T5+uc>cDID)@sEH2;gEtdwPFQI0ls||GxIZQ$9(#)RRfBL?BhBVSJdAupQt~Ib2t60@jMz= z2C!Fb!SVZM_v%T|M*Veft_-}lxn%&~oOwl};IsZ{KG|oRjN-Q1N9d~8^XBJW-|K%i z-s|TaXBohmARZMw>yPG>-CIm6w8uEX`=x}p4x`Q`0FOargBpwMg=0298|UmXy-R#u zn=Y%HwVn}f(g0T_S1mrpRc|1`kWoyq#RA&9y4TxTB?57}EfFfB@UDt31M?7|fF8=dFv zz+Ib|^d@^Yf7-OP{v&(115+|&r(m=51#j*{rBxj`Tjwyq=d*9L!>ool+u3HvrRbTJ zibwWU`ek*iZYJlv!vK@GWM3sdorNh55?}Dz6zsYyg#osOtx-~-R{;UstFOE_D1q5)fzVhCzKncKA2Xwmlk)5=vnqQqSz1^U^x-Xj5`BuKQN&~!ea%Z6gukXEZ z)uyt(f{WHC1u!j2cJIq_uRpJU>U6X|*|Yw;=X>??=1b$r{wV)2z_b|IyT$VQ&%)u& zcXfP}pEn+FzBHcfv-raR-yG7SWbYPj_W1I~Wj$khA@Sx<{;Q62-T791xO9s%3@|}J zc5m^#{sgb@%sZ<`*QT(ZjcL<{0ls1f9*aTZtlDH%#f)z9nGWQS?4)mY{#$im!Am&( zv=dxpr`59a5C1J4I2s(yBYStpr1i<(UDWLQ-THa`lU^+yI4UB|BfD3eXZ!QUOU3l& zll7NfFFU`a1J7=&*)v_$#eqa3yB9LAKd*l)bTj?D@zHp)TjQm1zu8kqi1S5mOeUI~ z;Ek1&p!L>+#*>}0$j&eAz|mVQB`LcUAO+GLSn!g>vUe)b25@K4@+}3H93dpL6zEnU z+<`xgk=+*X{F(WU`kBo;*0K8S$d@iMWG7vpJ%9F=g)W?w*V!(BjcQc`OmLB%!hZJr z74Y8HNCRx`U?liI3E%%A>Yq9+*{kDuls`d6cGC3O^JfrK+#b6CHmc?EtR&B(A!nSAURk zi;s#*_Eiwmh0_X~Hy?{%0c=s})PZTZwL?=e$u4-m6bJ)Mg_KmRBeFHCj{wE z_EoRurW$z1O?#H z=r`ksASJu?tm^e+b5IqZsCa>*{YL)CPMv+UN6+ZvEn~d#RO@BUcPDsnF^2&@cVJ6_B4e^eo$d6f-{?GR?pgoi z{A`n}Ujc6lV0BB18h1WlgCp#&Sou2}@ zVsUlg{}ssUN%P6>JyU!AdHoaohkeU4aQ5srOTJ3)DoRfUCq3yg$n(cx@P`0QQP(3#snq z{;U&M#hvv-^U3agRaousS@k@x8wS`b#xbr(UGRP>5C-@xVD&M=dWL=0 zrl^XK&Vo^htLjnZr}}tU(t$gJy7~4Nk6_--pWyZX?*tyzxTFJD-SWMUMS@mm;F^n} zRc9Jk-9*Zc5RU@S2AiEPcwZmhG{6KI*}Hcvtxxu=#Yg#&z3T~Bb!R9&LXa3_zZx9X z#U*{n?v1C~AJxwrpV!|pzT^zN>ur^aMs^ald%o8{jVJpII=XO@PITcP{agK|j}SKp zg4ZMW|6>UzvU@vR)ZfnVmH-?ppJqvc8U;#@5H+F5ixluHAb|avX~sqh2;hy1p-}t^ zqyVM^e)QrnOC#!!;JlgM3D4i{((k7}-pzH)PIX}K zs~jphB|-M;cwWulF8a;l@Wz+wz}3a2;K^Pc&#U>{MZZdTO98$)^NK`}jQ+g-yFO&0 zCyCX*I=f?!ZyesR&uW*-dY~%}3XX z&a=jKj2oXs9$m{WaoJ>PM|%Bx$CGHg`x{}H{hjj6&RYrZb{%-OBct#{=Xr}@g`&Ho zUlroarrD+g3*P89_&JZBWY$$yZ~o|dWdK*4fx9}$?6&U}jr=^@Uv-D?%D3_fR|c@R zc-ADMe!Ijczupd4Gl?ph|(V4qO$G{734oD+ zZ045|_uj(2@ln3+_yq7n$(8|Z>2U6%e*FFm-=fe>y9}(?xhckuG!`z}fxjC8Y!IqV zb$r=;X7Q#wFbQF`6P#r4gw3*9-SG_p3t%fLKgWduJ{NU-yv9P|?-RQrY3k?|$DFYv z(citAW;-6m(oz@B1J&_4=$fAv-U zYA)-K@+G_XjOqpCd$Uf&_JTp{lYM)DJy2?_6zEbQ+=06Sl3yv{Q9uBD)Y91P6cE7M zl|%t~6cE53wKR4+1qASRB~d^g1q851Esfny0Rg;SNfeMrfwu!V`n5jqi+gJktnpYs z@ArOSVU2UwaRT^W87jO0-V@=s0hj<>{q@1s>+OkOJ}Lox*KjpZtJlk;vZsf)0hr_; zfug^A{f*{ }, BTN2, {repeat:true,edge:"falling"}); setWatch(()=>move(-1), BTN1, {repeat:true}); -(function migrateSettings(){ - let global_settings = require('Storage').readJSON('setting.json', 1) - if (global_settings) { - delete global_settings.welcomed - require('Storage').write('setting.json', global_settings) - } -})() - Bangle.setLCDTimeout(0); Bangle.setLCDPower(1); move(0); diff --git a/apps/welcome/app-bangle2.js b/apps/welcome/app-bangle2.js new file mode 100644 index 000000000..93d1c5657 --- /dev/null +++ b/apps/welcome/app-bangle2.js @@ -0,0 +1,248 @@ +// exec each function from seq one after the other +function animate(seq,period) { + var c = g.getColor(); + var i = setInterval(function() { + if (seq.length) { + var f = seq.shift(); + g.setColor(c); + if (f) f(); + } else clearInterval(i); + },period); +} + +// Fade in to FG color with angled lines +function fade(col, callback) { + var n = 0; + function f() {"ram" + g.setColor(col); + for (var i=n;i<240;i+=10) g.drawLine(i,0,0,i).drawLine(i,240,240,i); + g.flip(); + n++; + if (n<10) setTimeout(f,0); + else callback(); + } + f(); +} + + +var SCENE_COUNT=10; +function getScene(n) { + if (n==0) return function() { + g.reset().setBgColor(0).clearRect(0,0,176,176); + g.setFont("6x15"); + var n=0; + var l = Bangle.getLogo(); + var im = g.imageMetrics(l); + var i = setInterval(function() { + n+=0.1; + g.setColor(n,n,n); + g.drawImage(l,(176-im.width)/2,(176-im.height)/2); + if (n>=1) { + clearInterval(i); + setTimeout(()=>g.drawString("Open",44,104), 500); + setTimeout(()=>g.drawString("Hackable",44,116), 1000); + setTimeout(()=>g.drawString("Smart Watch",44,128), 1500); + } + },50); + }; + if (n==1) return function() { + var img = require("heatshrink").decompress(atob("ptR4n/j/4gH+8H5wl+jOukVVoHZ8dt/n//n37OtgH9sHhwHp4H5xmkGiH72MRje/LL/7iIAEE7sPEgoAC+AlagIlIiMQErPxDwUYxAABwIHCj8N7nOl3uEqa6BEggnFjfM5nCkUil3gEq5KDAAQmC6QmBE4JxSEhIABiQmB8QmSXoQlCYRMdEwIlCAAIlNhYlOiO85nNEyMPEoZwIAAcsYIYmPXoYlMiKaFExX/u9VEqLBBOYrCH+czmtVqJyDEpiaCOYsgSYszmc3qtTEqMR7hzG8AlGmd1OQglOOY6aEgYlCmmZoJMCTBrnD6SaIEoU/zOUuolSjbnBJgqaCEoU5zOXX4RyQYBBzCS4X5zNDqqZCJiERJg5zBEoVJEoM1JgYlQjhMHc4JLEmZMEEp6ZIJgPzS4WTmZMVTILmFYAK+BmglCmd1JgUYJiPNEorABEIOZygDBm5MCiJMQlhMH8ByBXwIlBJgUxJiMd5nOTIzlBTAK+BAANVq4jPAAS/HJgJyCTATAEACC/B4S/IJgIlCYAgAPiS/Kn5yEYANTEyPc5niOQxMB/LlCOapyJJgbpBYAZzROQK/Gl0ATIWfEoZzBc6IlB6SYGgBJBJgpzSlhyH8EAh5MBTIjnCuIlOjjlHTAJzC/LmDTSSYIEoTABOYIlETSKYHXwIABOYM0yYmETSCYHEobnDOYqaBExu8TAwlEc4U5EoiaCmK+NTAolFEwX0TQzBMXwXiEpTBCAAomNEoS+EEo4mIYIImKEoS+EEpDoBEyUbEo3gEo4mJdAImIJY4lJEycdEoPOOBYmPuIlE+HcJYhKKTZ1fhYkB2EAhnNcYMuEhomMr8A3YABEoJyB5gjOAAYmHm9VgELEoJMBEoXAEyXzE45YBJgXwEqx1I+ByDOYJyVJw5yCgEB3cQGgJMWJwQnCu6/CgFBigDB13S/glVAAf1qomCglEoADB1QDBADEPEoNVqEAolEgEKolKErJMDYAJMD0lE0AmaEoNaAgJMCFIYAahV/IgIiDOTgABNYJMEOToiCIoJMCOTzfCN4RMBOTxsDJIRyfIwZMBKQZzfJgRyfOYZMBOUBzCJgNKOT5zDJgLoCADxKBOAIABOT6aCAARyfOYRyjOYRyjOYlKEsBzEEsBzEOUJzDOUIABOUiaDOURzCOUZzCEscKCiY")); + var im = g.imageMetrics(img); + g.reset(); + g.setBgColor("#ff00ff"); + var y = 176, speed = 5; + function balloon(callback) { + y-=speed; + var x = (176-im.width)/2; + g.drawImage(img,x,y); + g.clearRect(x,y+81,x+77,y+81+speed); + if (y>30) setTimeout(balloon,0,callback); + else callback(); + } + fade("#ff00ff", function() { + balloon(function() { + g.setColor(-1).setFont("6x15:2").setFontAlign(0,0); + g.drawString("Welcome.",88,130); + }); + }); + setTimeout(function() { + var n=0; + var i = setInterval(function() { + n+=4; + g.scroll(0,-4); + if (n>150) + clearInterval(i); + },20); + },3500); + + }; + if (n==2) return function() { + g.reset(); + g.setBgColor("#ffff00").setColor(0).clear(); + g.setFont("12x20").setFontAlign(0,0); + var x = 70, y = 25, h=25; + animate([ + ()=>g.drawString("Your",x,y+=h), + ()=>g.drawString("Bangle.js",x,y+=h), + ()=>g.drawString("has one",x,y+=h), + ()=>g.drawString("button",x,y+=h), + ()=>{g.setFont("12x20:2").setFontAlign(0,0,1).drawString("HERE!",150,88);} + ],200); + }; + if (n==3) return function() { + g.reset(); + g.setBgColor("#00ffff").setColor(0).clear(); + g.setFontAlign(0,0).setFont("6x15:2"); + g.drawString("Press",88,40).setFontAlign(0,-1); + g.setFont("12x20"); + g.drawString("To wake the\nscreen up, or to\nselect", 88,60); + }; + if (n==4) return function() { + g.reset(); + g.setBgColor("#00ffff").setColor(0).clear(); + g.setFontAlign(0,0).setFont("6x15:2"); + g.drawString("Long Press",88,40).setFontAlign(0,-1); + g.setFont("12x20"); + g.drawString("To go back to\nthe clock", 88,60); + }; + if (n==5) return function() { + g.reset(); + g.setBgColor("#ff0000").setColor(0).clear(); + g.setFontAlign(0,0).setFont("12x20"); + g.drawString("If Bangle.js ever\nstops, hold the\nbutton for\nten seconds.\n\nBangle.js will\nthen reboot.", 88,78); + }; + if (n==6) return function() { + g.reset(); + g.setBgColor("#0000ff").setColor(-1).clear(); + g.setFont("12x20").setFontAlign(0,0); + var x = 88, y = -20, h=60; + animate([ + ()=>{g.drawString("Bangle.js has a\nfull touchscreen",x,y+=h);}, + 0,0, + ()=>{g.drawString("Drag up and down\nto scroll and\ntap to select",x,y+=h);}, + ],300); + }; + if (n==7) return function() { + g.reset(); + g.setBgColor("#00ff00").setColor(0).clear(); + g.setFont("12x20").setFontAlign(0,0); + var x = 88, y = -35, h=80; + animate([ + ()=>{g.drawString("Bangle.js comes\nwith a few\napps installed",x,y+=h);}, + 0,0, + ()=>{g.drawString("To add more, visit\nbanglejs.com/apps",x,y+=h);}, + ],400); + }; + if (n==8) return function() { + g.reset(); + g.setBgColor("#ff0000").setColor(0).clear(); + g.setFont("12x20").setFontAlign(0,0); + var x = 88; + g.drawString("You can also make\nyour own apps!",x,30); + g.drawString("Check out\nbanglejs.com",x,130); + + var rx = 0, ry = 0; + // draw a cube + function draw() { + // rotate + rx += 0.1; + ry += 0.11; + var rcx=Math.cos(rx), + rsx=Math.sin(rx), + rcy=Math.cos(ry), + rsy=Math.sin(ry); + // Project 3D coordinates into 2D + function p(x,y,z) { + var t; + t = x*rcy + z*rsy; + z = z*rcy - x*rsy; + x=t; + t = y*rcx + z*rsx; + z = z*rcx - y*rsx; + y=t; + z += 4; + return [88 + 60*x/z, 78+ 60*y/z]; + } + + var a; + // draw a series of lines to make up our cube + var s = 30; + g.clearRect(88-s,78-s,88+s,78+s); + a = p(-1,-1,-1); g.moveTo(a[0],a[1]); + a = p(1,-1,-1); g.lineTo(a[0],a[1]); + a = p(1,1,-1); g.lineTo(a[0],a[1]); + a = p(-1,1,-1); g.lineTo(a[0],a[1]); + a = p(-1,-1,-1); g.lineTo(a[0],a[1]); + a = p(-1,-1,1); g.moveTo(a[0],a[1]); + a = p(1,-1,1); g.lineTo(a[0],a[1]); + a = p(1,1,1); g.lineTo(a[0],a[1]); + a = p(-1,1,1); g.lineTo(a[0],a[1]); + a = p(-1,-1,1); g.lineTo(a[0],a[1]); + a = p(-1,-1,-1); g.moveTo(a[0],a[1]); + a = p(-1,-1,1); g.lineTo(a[0],a[1]); + a = p(1,-1,-1); g.moveTo(a[0],a[1]); + a = p(1,-1,1); g.lineTo(a[0],a[1]); + a = p(1,1,-1); g.moveTo(a[0],a[1]); + a = p(1,1,1); g.lineTo(a[0],a[1]); + a = p(-1,1,-1); g.moveTo(a[0],a[1]); + a = p(-1,1,1); g.lineTo(a[0],a[1]); + } + + setInterval(draw,50); + }; + if (n==9) return function() { + g.reset(); + g.setBgColor("#ffffff");g.clear(); + g.setFontAlign(0,0); + g.setFont("12x20"); + + var x = 88, y = 10, h=21; + animate([ + ()=>g.drawString("That's it!",x,y+=h), + ()=>{g.drawString("Press",x,y+=h*2); + g.drawString("the button",x,y+=h); + g.drawString("to start",x,y+=h); + g.drawString("Bangle.js",x,y+=h);} + ],400); + } +} + +var sceneNumber = 0; + +function move(dir) { + if (dir>0 && sceneNumber+1 == SCENE_COUNT) return; // at the end + sceneNumber = (sceneNumber+dir)%SCENE_COUNT; + if (sceneNumber<0) sceneNumber=0; + clearInterval(); + getScene(sceneNumber)(); + if (sceneNumber>1) { + var l = SCENE_COUNT; + for (var i=0;i move(dir)); +setWatch(()=>{ + if (sceneNumber == SCENE_COUNT-1) + load(); + else + move(1); +}, BTN1, {repeat:true}); + +Bangle.setLCDTimeout(0); +Bangle.setLCDPower(1); +move(0); diff --git a/apps/welcome/screenshot_welcome.png b/apps/welcome/screenshot_welcome.png new file mode 100644 index 0000000000000000000000000000000000000000..4c574839df6876870221b4354b179c23ef10c972 GIT binary patch literal 2618 zcmchZ`9IYA7sub@gJEKDZ82l$mVJx321yNFA-klADc`P8cCsWhjf}*&w(Dj~;Syp{ zQHf91t1MT!V{BtEcCL}^+t>G(?;r5};rqimuV2pcc%1V%kMn$FZ)+ipl0^Xkge@IzN1HFxyk3c~^rk z4LtjKBj$;|_1V+6ZN1KZ81t{|NH&!aD1+rl6dHE)z8Zi@qE`_BZE`OZN4l=?iU(k$ zHfK+Rmb16`0RB6-FA~I)|3ZPl+3~_vAOt;=1nj?BEM^c#`3dR(enw5{fldm=xd4)x zhmuj;cYeIMC2b6k6p zX9Av-rM)6SnEvQy-$3S!^k&kOpt=zB02*$U4RTw00$j`c%du$pzJ+9J`UeG z9-1TA8=Hx~roxb&R&QefcgVF%D`WQSI}6oHm45wr`A%9TX96Tae%_JIPNwFtmm7SNayLwXk+rGdmorPInKBM}NEWAVZ}C#&c7tA|!*@m+b|`m^XC+ zHv5LlSE@0VoIMqR>@(5;kJweb^Eq9?~5h$IVhl zCNtE%hbo$H8BU_|eknT-ZS{kI4Ud%J$?&GJfE0_pv4O;>ZR3K~H{Y%orYn?-z}Y^H zr9$Z2(z2wbY?~J=q0HgEsrs!ZN#y>>0yCH`-`0?(i`LNr*3XS`b$EZ=k)|53Q?NPO zmp|&29?Nih&-kYIy|e+GE-NU$pBSXx<2J0h9XvbaiG$=LE(EceJLCmgd3}(2Wr1Wx z$dr{EHDutO_X;^?5V9MXP;BcVu;Fc+l~0`r_=D4!ebrRMmf5W;iL2`h<{?u{CM6=c zoqr6fdfihWPq*vHxrVazgsMKP&ycN8DWxP)HdDleMJ=Rbz)PXcGRy|09+uI^df)0gvU#|Kfct zfvkTbJVVtm@TJVCRc;3IpIOelnr(GJQAtiqoG1o!AXjg?uE@{mtr4iBQ;`vWMt19P zL$VuUOV*&h+BAG%QcnGo>VRm|!2FYEnERe>gz>JdsZU}vIt?XEK;_g;Jxuh=XvMqFYg-=MsjA#^tE?_ z_hfGAvl7YWc0`q@e_j~xm^l?>`rdixk(`nY7^a@9&^dMR7^<7%CyH6PA+Ql8S(M4? z6mDuXb^E@CORiLThBX?bZv%17tkK}SUgvJRz z6LGwosKJE6XgRQXp$UVhN8QwXs0r?!AdSVyL>_-Hv9s?xcCyD0()^deN{%aYMaGc& z`^I7qM)yc+Z?`9&{y8!4#z(tJYj0uks{=G>xykp$Jv;yHQ=#d38yB~(b;sGLel!c1 z_PHj2-hF{aSG4Vuru3FY@^j>S#&#>ki#cg_NFA+scQM9S4%x4C_e<()_3%riu-$`9 z9(I;QwHMLnFYl?xnChVU=AR<G5n8#~ z3@s?IPydJ~cX9Ceig3u*Wa8h!nLDQ4z~e*LO@zx-vZQ**THr)Tct#DXie9*T1*#E! z_agEN>G5KykuJJ|(p}hJj9>_Ciy2E}-!j`LS27*c??<@0*?#SN{2PMU23v1&t|M7v zM2{c~+6^J!^T1V91c@u~J9i1VBxsljCqwGyp=vxZo-TgXFPo1@>3JR4JP$`2$7|W^ zIRR4gg@$h(@3qyWH!K*nqLF(u@i_mHq(W{J9$<=E$ig=?)ljh1v)7TsKO171Ki0cT zk!~jMTd`sOYAH!RHtw0CayvQNd8rJ7b-e(p%u?{9}QwZit^2im{8RolK^%EfXqKpR4oI3LNU~6ke!6npaf8Vn3gyW zrxDI{AR?Yu;<@HJnKlTzqsoh;Xbn|P2$rNgpZMQUUCH$IBRR5#fz}8R&Zg`&p`W{C z^RmOPhA1KV0P~!4wtLATQhd!mWvBvR^z$zqBR&w3$H?tGcbFiWsWhMp;DuMy7tts{ zrNjd{%S&*w^?Z9#Y+3)Vh@!_xKuWJp@|5`{>?bKegSfKkfTTT`4o138F zPpP#$%KCnH?0B4wwAPg8SH1%GkmuRy4E~F#V~!?w-tL}`{J`=Q)ug{(FJ4IC(F2&n w!)L#Q0hj44D8gkYk~WIDGETS["bat"].draw(), 60000) : undefined; @@ -37,6 +22,16 @@ } } }); - WIDGETS["bat"]={area:"tr",width:40,draw:draw}; + WIDGETS["bat"]={area:"tr",width:40,draw:function() { + var s = 39; + var x = this.x, y = this.y; + g.reset(); + if (Bangle.isCharging()) { + g.setColor("#0f0").drawImage(atob("DhgBHOBzgc4HOP////////////////////3/4HgB4AeAHgB4AeAHgB4AeAHg"),x,y); + x+=16; + } + g.setColor(g.theme.fg).fillRect(x,y+2,x+s-4,y+21).clearRect(x+2,y+4,x+s-6,y+19).fillRect(x+s-3,y+10,x+s,y+14); + g.setColor("#0f0").fillRect(x+4,y+6,x+4+E.getBattery()*(s-12)/100,y+17); + }}; setWidth(); })() diff --git a/apps/widbat/widget.png b/apps/widbat/widget.png index 630692e38e3b9ba5fbb62b2bc3a33cd61be04836..4f7491ee9f2b43d2eb9d76638d0ac7236f71b3c3 100644 GIT binary patch literal 280 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1SD0tpLGH$&H|6fVg?3oVGw3ym^DWND9B#o z>Fdh=luL|VRno>I?jle~HZvrm#5q4VH#M&W$Yo$~E=o--Nlj5G&n(GMaQE~LNYP7W z2a5N3x;Tbp+k%W^7#OjF z>xR6YxZwP~msj(vrfOZ-XeRq|Me#Trz%PXw%s%enD+*Q(4(O$7{!;ZftnH z;OBlT>zT*Z6fWiIJ=^fB#+Gq&KYwNU9G>d#i1%lbc&mQzv1$1Hd4@jEzQ$ig(U1P} ztw_>iy0bUgRQAhv#r8w_3THnth|Y_Bu~2P(K-W{of(yL1!oTHzSg}U@{@LL7qVaI@ z+@$_vb^q8z5_}o2;UU)6GFFD!1nXQXDJyW8*}q*=>C>8uX+JZBtJj^_U(L8LQa1YE T%Q{h@PZ>O2{an^LB{Ts5c#wMk diff --git a/apps/widbatv/ChangeLog b/apps/widbatv/ChangeLog new file mode 100644 index 000000000..55cda0f21 --- /dev/null +++ b/apps/widbatv/ChangeLog @@ -0,0 +1 @@ +0.01: New widget diff --git a/apps/widbatv/widget.js b/apps/widbatv/widget.js new file mode 100644 index 000000000..cc52a0f8e --- /dev/null +++ b/apps/widbatv/widget.js @@ -0,0 +1,19 @@ +Bangle.on('charging',function(charging) { + if(charging) Bangle.buzz(); + WIDGETS["batv"].draw(); +}); +setInterval(()=>WIDGETS["batv"].draw(), 60000); +Bangle.on('lcdPower', function(on) { + if (on) WIDGETS["batv"].draw(); // refresh at power on +}); +WIDGETS["batv"]={area:"tr",width:14,draw:function() { + var x = this.x, y = this.y; + g.reset(); + if (Bangle.isCharging()) { + g.setColor("#0f0").drawImage(atob("DhgBHOBzgc4HOP////////////////////3/4HgB4AeAHgB4AeAHgB4AeAHg"),x,y); + } else { + g.clearRect(x,y,x+14,y+24); + g.setColor(g.theme.fg).fillRect(x+2,y+2,x+12,y+22).clearRect(x+4,y+4,x+10,y+20).fillRect(x+5,y+1,x+9,y+2); + g.setColor("#0f0").fillRect(x+4,y+20-(E.getBattery()*16/100),x+10,y+20); + } +}}; diff --git a/apps/widbatv/widget.png b/apps/widbatv/widget.png new file mode 100644 index 0000000000000000000000000000000000000000..e31704d7bb7d90eeee09b4d116729f723a5f9490 GIT binary patch literal 221 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1SJ1Ryj={WI14-?iy0WWg+Z8+Vb&Z8pdfpR zr>`sfQ!X)fMY&@Rd}Tl(+02lL66gHf+|;}hAeVu`xhOTUBsE2$JhLQ2!QIn0AVn{g z9Vi~`>EamTas2HxOD+Zl9v0(A|M$y82O63^I+&;4;^W8jF3);u&Sn;lsp4-Ne(kiA zI^!$7f3MReSyibvNwwvMwORZISC|=D1RngEf99y8) ? "#07f" : (g.theme.dark ? "#0ff" : "#00f")); - else - g.setColor(g.theme.dark ? "#666" : "#999"); - g.drawImage(atob("CxQBBgDgFgJgR4jZMawfAcA4D4NYybEYIwTAsBwDAA=="),2+this.x,2+this.y); - } - function changed() { - WIDGETS["bluetooth"].draw(); - g.flip();// turns screen on - } - NRF.on('connect',changed); - NRF.on('disconnect',changed); - WIDGETS["bluetooth"]={area:"tr",width:15,draw:draw}; -})() +WIDGETS["bluetooth"]={area:"tr",width:15,draw:function() { + g.reset(); + if (NRF.getSecurityStatus().connected) + g.setColor((g.getBPP()>8) ? "#07f" : (g.theme.dark ? "#0ff" : "#00f")); + else + g.setColor(g.theme.dark ? "#666" : "#999"); + g.drawImage(atob("CxQBBgDgFgJgR4jZMawfAcA4D4NYybEYIwTAsBwDAA=="),2+this.x,2+this.y); +},changed:function() { + WIDGETS["bluetooth"].draw(); + Bangle.setLCDPower(1); // turn screen on +}}; +NRF.on('connect',WIDGETS["bluetooth"].changed); +NRF.on('disconnect',WIDGETS["bluetooth"].changed); diff --git a/apps/widcom/ChangeLog b/apps/widcom/ChangeLog new file mode 100644 index 000000000..5d08e91e6 --- /dev/null +++ b/apps/widcom/ChangeLog @@ -0,0 +1,3 @@ +0.02: Works with light theme + Doesn't drain battery by updating every 2 secs + Fix alignment diff --git a/apps/widcom/widget.js b/apps/widcom/widget.js index b9c911dbf..bce9453c5 100644 --- a/apps/widcom/widget.js +++ b/apps/widcom/widget.js @@ -1,30 +1,17 @@ (function(){ - //var img = E.toArrayBuffer(atob("FBSBAAAAAAAAA/wAf+AP/wH/2D/zw/w8PwfD9nw+b8Pg/Dw/w8/8G/+A//AH/gA/wAAAAAAA")); - //var img = E.toArrayBuffer(atob("GBiBAAB+AAP/wAeB4A4AcBgAGDAADHAADmABhmAHhsAfA8A/A8BmA8BmA8D8A8D4A2HgBmGABnAADjAADBgAGA4AcAeB4AP/wAB+AA==")); - var img = E.toArrayBuffer(atob("FBSBAAH4AH/gHAODgBwwAMYABkAMLAPDwPg8CYPBkDwfA8PANDACYABjAAw4AcHAOAf+AB+A")); - - function draw() { + var cp = Bangle.setCompassPower; + Bangle.setCompassPower = () => { + cp.apply(Bangle, arguments); + WIDGETS.compass.draw(); + }; + + WIDGETS.compass={area:"tr",width:24,draw:function() { g.reset(); if (Bangle.isCompassOn()) { - g.setColor(1,0.8,0); // on = amber + g.setColor(g.theme.dark ? "#FC0" : "#F00"); } else { - g.setColor(0.3,0.3,0.3); // off = grey + g.setColor(g.theme.dark ? "#333" : "#CCC"); } - g.drawImage(img, 10+this.x, 2+this.var); - } - - var timerInterval; - Bangle.on('lcdPower', function(on) { - if (on) { - WIDGETS.compass.draw(); - if (!timerInterval) timerInterval = setInterval(()=>WIDGETS.compass.draw(), 2000); - } else { - if (timerInterval) { - clearInterval(timerInterval); - timerInterval = undefined; - } - } - }); - - WIDGETS.compass={area:"tr",width:24,draw:draw}; + g.drawImage(atob("FBSBAAH4AH/gHAODgBwwAMYABkAMLAPDwPg8CYPBkDwfA8PANDACYABjAAw4AcHAOAf+AB+A"), 2+this.x, 2+this.var); + }}; })(); diff --git a/apps/widgps/ChangeLog b/apps/widgps/ChangeLog index d80e09912..57bb53bb7 100644 --- a/apps/widgps/ChangeLog +++ b/apps/widgps/ChangeLog @@ -1,2 +1,3 @@ 0.01: First version 0.02: Don't break if running on 2v08 firmware (just don't display anything) +0.03: Fix positioning diff --git a/apps/widgps/widget.js b/apps/widgps/widget.js index 19be2abaf..6ef55e27b 100644 --- a/apps/widgps/widget.js +++ b/apps/widgps/widget.js @@ -4,11 +4,11 @@ function draw() { g.reset(); if (Bangle.isGPSOn()) { - g.setColor(1,0.8,0); // on = amber + g.setColor("#FD0"); // on = amber } else { - g.setColor(0.3,0.3,0.3); // off = grey + g.setColor("#888"); // off = grey } - g.drawImage(atob("GBiBAAAAAAAAAAAAAA//8B//+BgYGBgYGBgYGBgYGBgYGBgYGB//+B//+BgYGBgYGBgYGBgYGBgYGBgYGB//+A//8AAAAAAAAAAAAA=="), 10+this.x, 2+this.y); + g.drawImage(atob("GBiBAAAAAAAAAAAAAA//8B//+BgYGBgYGBgYGBgYGBgYGBgYGB//+B//+BgYGBgYGBgYGBgYGBgYGBgYGB//+A//8AAAAAAAAAAAAA=="), this.x, 2+this.y); } var timerInterval; diff --git a/apps/widhrm/ChangeLog b/apps/widhrm/ChangeLog index 45dcfa87e..93e2eaf66 100644 --- a/apps/widhrm/ChangeLog +++ b/apps/widhrm/ChangeLog @@ -2,3 +2,4 @@ 0.02: Tweaks for variable size widget system 0.03: Ensure redrawing works with variable size widget system 0.04: tag HRM power requests to allow this ot work alongside other widgets/apps (fix #799) +0.05: Use new 'lock' event, not LCD (so it works on Bangle.js 2) diff --git a/apps/widhrm/widget.js b/apps/widhrm/widget.js index 54b105d1e..7ffe1aa6d 100644 --- a/apps/widhrm/widget.js +++ b/apps/widhrm/widget.js @@ -1,13 +1,38 @@ (() => { - var currentBPM = undefined; - var lastBPM = undefined; - var firstBPM = true; // first reading since sensor turned on + if (!Bangle.isLocked) return; // old firmware + var currentBPM; + var lastBPM; + var isHRMOn = false; - function draw() { + // turn on sensor when the LCD is unlocked + Bangle.on('lock', function(isLocked) { + if (!isLocked) { + Bangle.setHRMPower(1,"widhrm"); + currentBPM = undefined; + WIDGETS["hrm"].draw(); + } else { + Bangle.setHRMPower(0,"widhrm"); + } + }); + + var hp = Bangle.setHRMPower; + Bangle.setHRMPower = () => { + hp.apply(Bangle, arguments); + isHRMOn = Bangle.isHRMOn(); + WIDGETS["hrm"].draw(); + }; + + Bangle.on('HRM',function(d) { + currentBPM = d.bpm; + lastBPM = currentBPM; + WIDGETS["hrm"].draw(); + }); + + // add your widget + WIDGETS["hrm"]={area:"tl",width:24,draw:function() { var width = 24; g.reset(); - g.setFont("6x8", 1); - g.setFontAlign(0, 0); + g.setFont("6x8", 1).setFontAlign(0, 0); g.clearRect(this.x,this.y+15,this.x+width,this.y+23); // erase background var bpm = currentBPM, isCurrent = true; if (bpm===undefined) { @@ -16,36 +41,12 @@ } if (bpm===undefined) bpm = "--"; - g.setColor(isCurrent ? "#ffffff" : "#808080"); + g.setColor(isCurrent ? g.theme.fg : "#808080"); g.drawString(bpm, this.x+width/2, this.y+19); - g.setColor(isCurrent ? "#ff0033" : "#808080"); + g.setColor(isHRMOn ? "#ff0033" : "#808080"); g.drawImage(atob("CgoCAAABpaQ//9v//r//5//9L//A/+AC+AAFAA=="),this.x+(width-10)/2,this.y+1); g.setColor(-1); - } + }}; - // redraw when the LCD turns on - Bangle.on('lcdPower', function(on) { - if (on) { - Bangle.setHRMPower(1,"widhrm"); - firstBPM = true; - currentBPM = undefined; - WIDGETS["hrm"].draw(); - } else { - Bangle.setHRMPower(0,"widhrm"); - } - }); - - Bangle.on('HRM',function(d) { - if (firstBPM) - firstBPM=false; // ignore the first one as it's usually rubbish - else { - currentBPM = d.bpm; - lastBPM = currentBPM; - } - WIDGETS["hrm"].draw(); - }); - Bangle.setHRMPower(Bangle.isLCDOn(),"widhrm"); - - // add your widget - WIDGETS["hrm"]={area:"tl",width:24,draw:draw}; + Bangle.setHRMPower(!Bangle.isLocked(),"widhrm"); })(); diff --git a/apps/widhrt/ChangeLog b/apps/widhrt/ChangeLog index fdb495797..39520ad6a 100644 --- a/apps/widhrt/ChangeLog +++ b/apps/widhrt/ChangeLog @@ -1,3 +1,5 @@ 0.01: First version 0.02: Don't break if running on 2v08 firmware (just don't display anything) - +0.03: Works with light theme + Doesn't drain battery by updating every 2 secs + fix alignment diff --git a/apps/widhrt/widget.js b/apps/widhrt/widget.js index 8ac76def8..d9716fa24 100644 --- a/apps/widhrt/widget.js +++ b/apps/widhrt/widget.js @@ -1,28 +1,18 @@ (function(){ if (!Bangle.isHRMOn) return; // old firmware + var hp = Bangle.setHRMPower; + Bangle.setHRMPower = () => { + hp.apply(Bangle, arguments); + WIDGETS.widhrt.draw(); + }; - function draw() { + WIDGETS.widhrt={area:"tr",width:24,draw:function() { g.reset(); if (Bangle.isHRMOn()) { - g.setColor(1,0,0); // on = red + g.setColor("#f00"); // on = red } else { - g.setColor(0.3,0.3,0.3); // off = grey + g.setColor(g.theme.dark ? "#333" : "#CCC"); // off = grey } - g.drawImage(atob("FhaBAAAAAAAAAAAAAcDgD8/AYeGDAwMMDAwwADDAAMOABwYAGAwAwBgGADAwAGGAAMwAAeAAAwAAAAAAAAAAAAA="), 10+this.x, 2+this.y); - } - - var timerInterval; - Bangle.on('lcdPower', function(on) { - if (on) { - WIDGETS.widhrt.draw(); - if (!timerInterval) timerInterval = setInterval(()=>WIDGETS["widhrt"].draw(), 2000); - } else { - if (timerInterval) { - clearInterval(timerInterval); - timerInterval = undefined; - } - } - }); - - WIDGETS.widhrt={area:"tr",width:24,draw:draw}; + g.drawImage(atob("FhaBAAAAAAAAAAAAAcDgD8/AYeGDAwMMDAwwADDAAMOABwYAGAwAwBgGADAwAGGAAMwAAeAAAwAAAAAAAAAAAAA="), 1+this.x, 1+this.y); + }}; })(); diff --git a/apps/widmp/ChangeLog b/apps/widmp/ChangeLog index 5560f00bc..3996d9e74 100644 --- a/apps/widmp/ChangeLog +++ b/apps/widmp/ChangeLog @@ -1 +1,3 @@ 0.01: New App! +0.02: Fix position and overdraw bugs + Better memory usage, theme support diff --git a/apps/widmp/widget.js b/apps/widmp/widget.js index cebdb60f5..d8abc3a9c 100644 --- a/apps/widmp/widget.js +++ b/apps/widmp/widget.js @@ -1,20 +1,6 @@ -/* jshint esversion: 6 */ -(() => { - - const BLACK = 0, MOON = 0x41f, MC = 29.5305882, NM = 694039.09; - var r = 12, mx = 0, my = 0; - - var moon = { - 0: () => { g.reset().setColor(BLACK).fillRect(mx - r, my - r, mx + r, my + r);}, - 1: () => { moon[0](); g.setColor(MOON).drawCircle(mx, my, r);}, - 2: () => { moon[3](); g.setColor(BLACK).fillEllipse(mx - r / 2, my - r, mx + r / 2, my + r);}, - 3: () => { moon[0](); g.setColor(MOON).fillCircle(mx, my, r).setColor(BLACK).fillRect(mx - r, my - r, mx, my + r);}, - 4: () => { moon[3](); g.setColor(MOON).fillEllipse(mx - r / 2, my - r, mx + r / 2, my + r);}, - 5: () => { moon[0](); g.setColor(MOON).fillCircle(mx, my, r);}, - 6: () => { moon[7](); g.setColor(MOON).fillEllipse(mx - r / 2, my - r, mx + r / 2, my + r);}, - 7: () => { moon[0](); g.setColor(MOON).fillCircle(mx, my, r).setColor(BLACK).fillRect(mx, my - r, mx + r + r, my + r);}, - 8: () => { moon[7](); g.setColor(BLACK).fillEllipse(mx - r / 2, my - r, mx + r / 2, my + r);} - }; +WIDGETS["widmoon"] = { area: "tr", width: 24, draw: function() { + const MC = 29.5305882, NM = 694039.09; + var r = 11, mx = this.x + 12; my = this.y + 12; function moonPhase(d) { var tmp, month = d.getMonth(), year = d.getFullYear(), day = d.getDate(); @@ -23,11 +9,18 @@ return Math.round(((tmp - (tmp | 0)) * 7)+1); } - function draw() { - mx = this.x; my = this.y + 12; - moon[moonPhase(Date())](); - } + const BLACK = g.theme.bg, MOON = 0x41f; + var moon = { + 0: () => { g.reset().setColor(BLACK).fillRect(mx - r, my - r, mx + r, my + r);}, + 1: () => { moon[0](); g.setColor(MOON).drawCircle(mx, my, r);}, + 2: () => { moon[3](); g.setColor(BLACK).fillEllipse(mx - r / 2, my - r, mx + r / 2, my + r);}, + 3: () => { moon[0](); g.setColor(MOON).fillCircle(mx, my, r).setColor(BLACK).fillRect(mx - r, my - r, mx, my + r);}, + 4: () => { moon[3](); g.setColor(MOON).fillEllipse(mx - r / 2, my - r, mx + r / 2, my + r);}, + 5: () => { moon[0](); g.setColor(MOON).fillCircle(mx, my, r);}, + 6: () => { moon[7](); g.setColor(MOON).fillEllipse(mx - r / 2, my - r, mx + r / 2, my + r);}, + 7: () => { moon[0](); g.setColor(MOON).fillCircle(mx, my, r).setColor(BLACK).fillRect(mx, my - r, mx + r, my + r);}, + 8: () => { moon[7](); g.setColor(BLACK).fillEllipse(mx - r / 2, my - r, mx + r / 2, my + r);} + }; + moon[moonPhase(Date())](); +} }; - WIDGETS["widmoon"] = { area: "tr", width: 24, draw: draw }; - -})(); diff --git a/apps/widtbat/ChangeLog b/apps/widtbat/ChangeLog index 4c21f3ace..37513808a 100644 --- a/apps/widtbat/ChangeLog +++ b/apps/widtbat/ChangeLog @@ -1 +1,2 @@ 0.01: New Widget! +0.02: Theme support, memory savings diff --git a/apps/widtbat/widget.js b/apps/widtbat/widget.js index 6d5aded8b..c78653358 100644 --- a/apps/widtbat/widget.js +++ b/apps/widtbat/widget.js @@ -1,17 +1,11 @@ -/* jshint esversion: 6 */ -(() => { - const CBS = 0x41f, CBC = 0x07E0; - var xo = 6, xl = 22, yo = 9, h = 17; - - function draw() { - g.reset().setColor(CBS).drawImage(require("heatshrink").decompress(atob("j0TwIHEv///kD////EfAYPwuEAgPB4EAg/HCgMfzgDBvwOC/IOC84ONDoUcFgc/AYOAHYRDE")), this.x + 1, this.y + 4); - g.setColor(0).fillRect(this.x + xo, this.y + yo, this.x + xl, this.y + h); - var cbc = (Bangle.isCharging()) ? CBC : CBS; - g.setColor(cbc).fillRect(this.x + xo, this.y + yo, this.x + (xl - xo) / 100 * E.getBattery() + xo, this.y + h); - } - Bangle.on('charging', function(charging) { - if (charging) Bangle.buzz(); - Bangle.drawWidgets(); - }); - WIDGETS["widtbat"] = { area:"tr", width:32, draw: draw }; -})(); +Bangle.on('charging', function(charging) { + if (charging) Bangle.buzz(); + WIDGETS["widtbat"].draw(); +}); +WIDGETS["widtbat"] = { area:"tr", width:32, draw: function() { + const xo = 6, xl = 22, yo = 9, h = 17; + g.reset().setColor("#08f").drawImage(require("heatshrink").decompress(atob("j0TwIHEv///kD////EfAYPwuEAgPB4EAg/HCgMfzgDBvwOC/IOC84ONDoUcFgc/AYOAHYRDE")), this.x + 1, this.y + 4); + g.clearRect(this.x + xo, this.y + yo, this.x + xl, this.y + h); + var cbc = (Bangle.isCharging()) ? "#0f0" : "#08f"; + g.setColor(cbc).fillRect(this.x + xo, this.y + yo, this.x + (xl - xo) / 100 * E.getBattery() + xo, this.y + h); +} }; diff --git a/apps/widtbat/widget.png b/apps/widtbat/widget.png index 4294f0ca32299687f44c631a14109629f857f4a1..f2943bc52013b52db5cba8383076118faf2cee42 100644 GIT binary patch delta 210 zcmeBYf5$jMrJl3EBeIx*fm;}a85w5Hkzin8U@!6Xb!C6bCB|;Z|IEndDp07`)5S3) zc47kxh(UJ5u}CVu5^^4XIdJ^guk&< uHnkdA$(@|PJx4l!-G_RZkwCiPK9iWFx9rgeFQONMggssTT-G@yGywoz*h~We literal 911 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA1|-9oezpTC#^NA%Cx&(BWL^R}0jUw5X}-P; zT0k}j0~4bV12aeo5Hhr9GO&Qz3=C>Ont_3N0V6_o0TW!-U;#6N4N`dWm6H(AkjTuC zh>{3jAFJg2T)o7U{G?R9irfN_0tTB3D*7iAWdWaj57fXq!y$}cUk zRZ;?31P2gzmSmu1yMhW`HkzTTSoaJ~2f8+kKR6OrFMCe`Z3#?B8 zC49*z`DeL<{|n(yEW*2#>kN&)@-iK7o}>EZyS}siuHP4aO2{#|b+YeT#Q0>l@TS?U z-Aq$V>eZY@m1ibibT(C-EV=!^vgEPuBgal1dB(`on$ebYVzQRQSqH^a%uB2$%z8DE z>HW$2WB(=7FJIlJG`oKy`vg^X$>_F+_K$r^%z0J$4H%3Uf0^)&Vb^2FKZ>Hvi{%c8 zF){0!&1!K=Za5?$k>$y;xZYrj&4(F{orb)&huCVPI|2^(Yw!ygvDk*ZIC=b8qrU_D zf!{11YaOe0ai@u|sP7R^IL*BK!>a|WRUYR#T}!NZa6kT-*Meo`Ehd*7|H&FNC$lP6 z8`u=+ML*@cApFdsH@o47vLbVa!hgXV%VUoI`al2PNuH;-O$_R { - var width = 28, - ver = process.env.VERSION.split('.'); - function draw() { - g.reset().setColor(0, 0.5, 1).setFont("6x8", 1); - g.drawString(ver[0], this.x + 2, this.y + 4, true); - g.setFontAlign(0, -1, 0).drawString(ver[1], this.x + width / 2, this.y + 14, true); - } - WIDGETS["version"] = { area: "tr", width: width, draw: draw }; -})(); +WIDGETS["version"] = { area: "tr", width: 28, draw: function() { + var ver = process.env.VERSION.split('.'); + // Example: if ver is 2v11 instead of 2v10.142 write "Rel" (Release) instead of Build number + if(typeof ver[1] === 'undefined') ver[1] = "Rel"; + + g.reset().setColor((g.getBPP()<8)?(g.theme.dark?"#0ff":"#00f"):"#08f").setFont("6x8"); + g.drawString(ver[0], this.x + 2, this.y + 4, true); + g.setFontAlign(0, -1, 0).drawString(ver[1], this.x + this.width / 2, this.y + 14, true); +} }; diff --git a/apps/worldclock/ChangeLog b/apps/worldclock/ChangeLog index e922ef2a4..831dd3b5c 100644 --- a/apps/worldclock/ChangeLog +++ b/apps/worldclock/ChangeLog @@ -2,3 +2,5 @@ 0.02: Update custom.html for refactor; add README 0.03: Update for larger secondary timezone display (#610) 0.04: setUI, different screen sizes +0.05: Now update *on* the minute rather than every 15 secs + Fix rendering of single extra timezone on Bangle.js 2 diff --git a/apps/worldclock/app.js b/apps/worldclock/app.js index 84cb29874..2627e056c 100644 --- a/apps/worldclock/app.js +++ b/apps/worldclock/app.js @@ -1,5 +1,3 @@ -/* jshint esversion: 6 */ - const big = g.getWidth()>200; // Font for primary time and date const primaryTimeFontSize = big?6:5; @@ -16,8 +14,13 @@ const xcol2 = g.getWidth() - xcol1; const font = "6x8"; +/* TODO: we could totally use 'Layout' here and +avoid a whole bunch of hard-coded offsets */ + + const xyCenter = g.getWidth() / 2; const yposTime = big ? 75 : 60; +const yposTime2 = yposTime + (big ? 100 : 60); const yposDate = big ? 130 : 90; const yposWorld = big ? 170 : 120; @@ -29,41 +32,52 @@ var offsets = require("Storage").readJSON("worldclock.settings.json") || []; // TESTING CODE // Used to test offset array values during development. // Uncomment to override secondary offsets value - -// const mockOffsets = { -// zeroOffsets: [], -// oneOffset: [["UTC", 0]], -// twoOffsets: [ -// ["Tokyo", 9], -// ["UTC", 0], -// ], -// fourOffsets: [ -// ["Tokyo", 9], -// ["UTC", 0], -// ["Denver", -7], -// ["Miami", -5], -// ], -// fiveOffsets: [ -// ["Tokyo", 9], -// ["UTC", 0], -// ["Denver", -7], -// ["Chicago", -6], -// ["Miami", -5], -// ], -// }; +/* +const mockOffsets = { + zeroOffsets: [], + oneOffset: [["UTC", 0]], + twoOffsets: [ + ["Tokyo", 9], + ["UTC", 0], + ], + fourOffsets: [ + ["Tokyo", 9], + ["UTC", 0], + ["Denver", -7], + ["Miami", -5], + ], + fiveOffsets: [ + ["Tokyo", 9], + ["UTC", 0], + ["Denver", -7], + ["Chicago", -6], + ["Miami", -5], + ], +};*/ // Uncomment one at a time to test various offsets array scenarios -// offsets = mockOffsets.zeroOffsets; // should render nothing below primary time -// offsets = mockOffsets.oneOffset; // should render larger in two rows -// offsets = mockOffsets.twoOffsets; // should render two in columns -// offsets = mockOffsets.fourOffsets; // should render in columns -// offsets = mockOffsets.fiveOffsets; // should render first four in columns +//offsets = mockOffsets.zeroOffsets; // should render nothing below primary time +//offsets = mockOffsets.oneOffset; // should render larger in two rows +//offsets = mockOffsets.twoOffsets; // should render two in columns +//offsets = mockOffsets.fourOffsets; // should render in columns +//offsets = mockOffsets.fiveOffsets; // should render first four in columns // END TESTING CODE // Check settings for what type our clock should be //var is12Hour = (require("Storage").readJSON("setting.json",1)||{})["12hour"]; -var secondInterval; + +// timeout used to update every minute +var drawTimeout; + +// schedule a draw for the next minute +function queueDraw() { + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = setTimeout(function() { + drawTimeout = undefined; + draw(); + }, 60000 - (Date.now() % 60000)); +} function doublenum(x) { return x < 10 ? "0" + x : "" + x; @@ -73,7 +87,7 @@ function getCurrentTimeFromOffset(dt, offset) { return new Date(dt.getTime() + offset * 60 * 60 * 1000); } -function drawSimpleClock() { +function draw() { // get date var d = new Date(); var da = d.toString().split(" "); @@ -111,9 +125,9 @@ function drawSimpleClock() { // For a single secondary timezone, draw it bigger and drop time zone to second line const xOffset = 30; g.setFont(font, secondaryTimeFontSize); - g.drawString(`${hours}:${minutes}`, xyCenter, yposTime + 100, true); + g.drawString(`${hours}:${minutes}`, xyCenter, yposTime2, true); g.setFont(font, secondaryTimeZoneFontSize); - g.drawString(offset[OFFSET_TIME_ZONE], xyCenter, yposTime + 130, true); + g.drawString(offset[OFFSET_TIME_ZONE], xyCenter, yposTime2 + 30, true); // draw Day, name of month, Date g.setFont(font, secondaryTimeZoneFontSize); @@ -132,6 +146,8 @@ function drawSimpleClock() { g.drawString(`${hours}:${minutes}`, xcol2, yposWorld + index * 15, true); } }); + + queueDraw(); } // clean app screen @@ -141,18 +157,15 @@ Bangle.setUI("clock"); Bangle.loadWidgets(); Bangle.drawWidgets(); -// refesh every 15 sec when screen is on -Bangle.on("lcdPower", (on) => { - if (secondInterval) clearInterval(secondInterval); - secondInterval = undefined; +// Stop updates when LCD is off, restart when on +Bangle.on('lcdPower',on=>{ if (on) { - secondInterval = setInterval(drawSimpleClock, 15e3); - drawSimpleClock(); // draw immediately + draw(); // draw immediately, queue redraw + } else { // stop draw timer + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = undefined; } }); -// draw now and every 15 sec until display goes off -drawSimpleClock(); -if (Bangle.isLCDOn()) { - secondInterval = setInterval(drawSimpleClock, 15e3); -} +// draw now +draw(); diff --git a/apps/worldclock/screenshot_world.png b/apps/worldclock/screenshot_world.png new file mode 100644 index 0000000000000000000000000000000000000000..ebae637b729adbcde712442d85a1493df5830286 GIT binary patch literal 2937 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 literal 0 HcmV?d00001 diff --git a/bin/create_app_supports_field.js b/bin/create_app_supports_field.js new file mode 100644 index 000000000..d6aada357 --- /dev/null +++ b/bin/create_app_supports_field.js @@ -0,0 +1,99 @@ +#!/usr/bin/nodejs +/* Quick hack to add proper 'supports' field to apps.json +*/ + +var fs = require("fs"); + +var BASEDIR = __dirname+"/../"; + +var appsFile, apps; +try { + appsFile = fs.readFileSync(BASEDIR+"apps.json").toString(); +} catch (e) { + ERROR("apps.json not found"); +} +try{ + apps = JSON.parse(appsFile); +} catch (e) { + console.log(e); + var m = e.toString().match(/in JSON at position (\d+)/); + if (m) { + var char = parseInt(m[1]); + console.log("==============================================="); + console.log("LINE "+appsFile.substr(0,char).split("\n").length); + console.log("==============================================="); + console.log(appsFile.substr(char-10, 20)); + console.log("==============================================="); + } + console.log(m); + ERROR("apps.json not valid JSON"); + +} + +apps = apps.map((app,appIdx) => { + if (app.supports) return app; // already sorted + var tags = []; + if (app.tags) tags = app.tags.split(",").map(t=>t.trim()); + var supportsB1 = true; + var supportsB2 = false; + if (tags.includes("b2")) { + tags = tags.filter(x=>x!="b2"); + supportsB2 = true; + } + if (tags.includes("bno2")) { + tags = tags.filter(x=>x!="bno2"); + supportsB2 = false; + } + if (tags.includes("bno1")) { + tags = tags.filter(x=>x!="bno1"); + supportsB1 = false; + } + app.tags = tags.join(","); + app.supports = []; + if (supportsB1) app.supports.push("BANGLEJS"); + if (supportsB2) app.supports.push("BANGLEJS2"); + return app; +}); + +// search for screenshots +apps = apps.map((app,appIdx) => { + if (app.screenshots) return app; // already sorted + + var files = require("fs").readdirSync(__dirname+"/../apps/"+app.id); + var screenshots = files.filter(fn=>fn.startsWith("screenshot") && fn.endsWith(".png")); + if (screenshots.length) + app.screenshots = screenshots.map(fn => ({url:fn})); + return app; +}); + +var KEY_ORDER = [ + "id","name","shortName","version","description","icon","screenshots","type","tags","supports", + "dependencies", "readme", "custom", "customConnect", "interface", + "allow_emulator", "storage", "data", "sortorder" +]; + +var JS = JSON.stringify; +var json = "[\n "+apps.map(app=>{ + var keys = KEY_ORDER.filter(k=>k in app); + Object.keys(app).forEach(k=>{ + if (!KEY_ORDER.includes(k)) + throw new Error(`Key named ${k} not known!`); + }); + //var keys = Object.keys(app); // don't re-order + + return "{\n "+keys.map(k=>{ + var js = JS(app[k]); + if (k=="storage") { + if (app.storage.length) + js = "[\n "+app.storage.map(s=>JS(s)).join(",\n ")+"\n ]"; + else + js = "[]"; + } + return JS(k)+": "+js; + }).join(",\n ")+"\n }"; +}).join(",\n ")+"\n]\n"; + +//console.log(json); + +console.log("new apps.json written"); +fs.writeFileSync(BASEDIR+"apps.json", json); diff --git a/bin/firmwaremaker.js b/bin/firmwaremaker.js index 4e22dd168..4bc2a70b2 100755 --- a/bin/firmwaremaker.js +++ b/bin/firmwaremaker.js @@ -12,6 +12,7 @@ var ROOTDIR = path.join(__dirname, '..'); var APPDIR = ROOTDIR+'/apps'; var APPJSON = ROOTDIR+'/apps.json'; var OUTFILE = ROOTDIR+'/firmware.js'; +var DEVICE = "BANGLEJS"; var APPS = [ // IDs of apps to install "boot","launch","mclock","setting", "about","alarm","widbat","widbt","welcome" @@ -61,7 +62,8 @@ Promise.all(APPS.map(appid => { if (app===undefined) throw new Error(`App ${appid} not found`); return AppInfo.getFiles(app, { fileGetter : fileGetter, - settings : SETTINGS + settings : SETTINGS, + device : { id : DEVICE } }).then(files => { appfiles = appfiles.concat(files); }); diff --git a/bin/firmwaremaker_c.js b/bin/firmwaremaker_c.js index 2cb993d00..14ced9ef8 100755 --- a/bin/firmwaremaker_c.js +++ b/bin/firmwaremaker_c.js @@ -29,8 +29,8 @@ if (DEVICE=="BANGLEJS") { } else if (DEVICE=="BANGLEJS2") { var OUTFILE = path.join(ROOTDIR, '../Espruino/libs/banglejs/banglejs2_storage_default.c'); var APPS = [ // IDs of apps to install - "boot","launchb2","s7clk","setting", - "about","alarm","widlock","widbat","widbt" + "boot","launch","antonclk","setting", + "about","alarm","health","widlock","widbat","widbt","widid","welcome" ]; } else { console.log("USAGE:"); @@ -102,7 +102,13 @@ function fileGetter(url) { fs.writeFileSync(url, code); } } - return Promise.resolve(fs.readFileSync(url).toString("binary")); + var blob = fs.readFileSync(url); + var data; + if (url.endsWith(".js") || url.endsWith(".json")) + data = blob.toString(); // allow JS/etc to be written in UTF-8 + else + data = blob.toString("binary") + return Promise.resolve(data); } // If file should be evaluated, try and do it... @@ -132,7 +138,8 @@ Promise.all(APPS.map(appid => { if (app===undefined) throw new Error(`App ${appid} not found`); return AppInfo.getFiles(app, { fileGetter : fileGetter, - settings : SETTINGS + settings : SETTINGS, + device : { id : DEVICE } }).then(files => { appfiles = appfiles.concat(files); }); diff --git a/bin/sanitycheck.js b/bin/sanitycheck.js index b98aa9ef3..a84d26efd 100755 --- a/bin/sanitycheck.js +++ b/bin/sanitycheck.js @@ -50,11 +50,12 @@ try{ } const APP_KEYS = [ - 'id', 'name', 'shortName', 'version', 'icon', 'description', 'tags', 'type', - 'sortorder', 'readme', 'custom', 'customConnect', 'interface', 'storage', 'data', 'allow_emulator', + 'id', 'name', 'shortName', 'version', 'icon', 'screenshots', 'description', 'tags', 'type', + 'sortorder', 'readme', 'custom', 'customConnect', 'interface', 'storage', 'data', + 'supports', 'allow_emulator', 'dependencies' ]; -const STORAGE_KEYS = ['name', 'url', 'content', 'evaluate', 'noOverwite']; +const STORAGE_KEYS = ['name', 'url', 'content', 'evaluate', 'noOverwite', 'supports']; const DATA_KEYS = ['name', 'wildcard', 'storageFile', 'url', 'content', 'evaluate']; const FORBIDDEN_FILE_NAME_CHARS = /[,;]/; // used as separators in appid.info const VALID_DUPLICATES = [ '.tfmodel', '.tfnames' ]; @@ -81,6 +82,14 @@ apps.forEach((app,appIdx) => { if (!app.name) ERROR(`App ${app.id} has no name`); var isApp = !app.type || app.type=="app"; if (app.name.length>20 && !app.shortName && isApp) ERROR(`App ${app.id} has a long name, but no shortName`); + if (!Array.isArray(app.supports)) ERROR(`App ${app.id} has no 'supports' field or it's not an array`); + else { + app.supports.forEach(dev => { + if (!["BANGLEJS","BANGLEJS2"].includes(dev)) + ERROR(`App ${app.id} has unknown device in 'supports' field - ${dev}`); + }); + } + if (!app.version) WARN(`App ${app.id} has no version`); else { if (!fs.existsSync(appDir+"ChangeLog")) { @@ -98,6 +107,13 @@ apps.forEach((app,appIdx) => { if (!app.description) ERROR(`App ${app.id} has no description`); if (!app.icon) ERROR(`App ${app.id} has no icon`); if (!fs.existsSync(appDir+app.icon)) ERROR(`App ${app.id} icon doesn't exist`); + if (app.screenshots) { + if (!Array.isArray(app.screenshots)) ERROR(`App ${app.id} screenshots is not an array`); + app.screenshots.forEach(screenshot => { + if (!fs.existsSync(appDir+screenshot.url)) + ERROR(`App ${app.id} screenshot file ${screenshot.url} not found`); + }); + } if (app.readme && !fs.existsSync(appDir+app.readme)) ERROR(`App ${app.id} README file doesn't exist`); if (app.custom && !fs.existsSync(appDir+app.custom)) ERROR(`App ${app.id} custom HTML doesn't exist`); if (app.customConnect && !app.custom) ERROR(`App ${app.id} has customConnect but no customn HTML`); @@ -105,8 +121,8 @@ apps.forEach((app,appIdx) => { if (app.dependencies) { if (("object"==typeof app.dependencies) && !Array.isArray(app.dependencies)) { Object.keys(app.dependencies).forEach(dependency => { - if (app.dependencies[dependency]!="type") - ERROR(`App ${app.id} 'dependencies' must all be tagged 'type' right now`); + if (!["type","app"].includes(app.dependencies[dependency])) + ERROR(`App ${app.id} 'dependencies' must all be tagged 'type' or 'app' right now`); }); } else ERROR(`App ${app.id} 'dependencies' must be an object`); @@ -117,7 +133,7 @@ apps.forEach((app,appIdx) => { if (isGlob(file.name)) ERROR(`App ${app.id} storage file ${file.name} contains wildcards`); let char = file.name.match(FORBIDDEN_FILE_NAME_CHARS) if (char) ERROR(`App ${app.id} storage file ${file.name} contains invalid character "${char[0]}"`) - if (fileNames.includes(file.name)) + if (fileNames.includes(file.name) && !file.supports) // assume that there aren't duplicates if 'supports' is set ERROR(`App ${app.id} file ${file.name} is a duplicate`); fileNames.push(file.name); allFiles.push({app: app.id, file: file.name}); @@ -126,6 +142,7 @@ apps.forEach((app,appIdx) => { var fileContents = ""; if (file.content) fileContents = file.content; if (file.url) fileContents = fs.readFileSync(appDir+file.url).toString(); + if (file.supports && !Array.isArray(file.supports)) ERROR(`App ${app.id} file ${file.name} supports field is not an array`); if (file.evaluate) { try { acorn.parse("("+fileContents+")"); @@ -156,7 +173,7 @@ apps.forEach((app,appIdx) => { } } for (const key in file) { - if (!STORAGE_KEYS.includes(key)) ERROR(`App ${app.id}'s ${file.name} has unknown key ${key}`); + if (!STORAGE_KEYS.includes(key)) ERROR(`App ${app.id} file ${file.name} has unknown key ${key}`); } }); let dataNames = []; diff --git a/bin/thumbnailer.js b/bin/thumbnailer.js new file mode 100755 index 000000000..b6862741a --- /dev/null +++ b/bin/thumbnailer.js @@ -0,0 +1,164 @@ +#!/usr/bin/node + +/* +var EMULATOR = "banglejs2"; +var DEVICEID = "BANGLEJS2"; +*/ +var EMULATOR = "banglejs1"; +var DEVICEID = "BANGLEJS"; + +var singleAppId; + +if (process.argv.length!=3 && process.argv.length!=2) { + console.log("USAGE:"); + console.log(" bin/thumbnailer.js"); + console.log(" - all thumbnails"); + console.log(" bin/thumbnailer.js APP_ID"); + console.log(" - just one app"); + process.exit(1); +} +if (process.argv.length==3) + singleAppId = process.argv[2]; + +if (!require("fs").existsSync(__dirname + "/../../EspruinoWebIDE")) { + console.log("You need to:"); + console.log(" git clone https://github.com/espruino/EspruinoWebIDE"); + console.log("At the same level as this project"); + process.exit(1); +} + +eval(require("fs").readFileSync(__dirname + "/../../EspruinoWebIDE/emu/emulator_"+EMULATOR+".js").toString()); +eval(require("fs").readFileSync(__dirname + "/../../EspruinoWebIDE/emu/emu_"+EMULATOR+".js").toString()); +eval(require("fs").readFileSync(__dirname + "/../../EspruinoWebIDE/emu/common.js").toString()); + +var SETTINGS = { + pretokenise : true +}; +var Const = { +}; +module = undefined; +eval(require("fs").readFileSync(__dirname + "/../core/lib/espruinotools.js").toString()); +eval(require("fs").readFileSync(__dirname + "/../core/js/utils.js").toString()); +eval(require("fs").readFileSync(__dirname + "/../core/js/appinfo.js").toString()); +var apps = JSON.parse(require("fs").readFileSync(__dirname+"/../apps.json")); + +/* we factory reset ONCE, get this, then we can use it to reset +state quickly for each new app */ +var factoryFlashMemory = new Uint8Array(FLASH_SIZE); +// Log of messages from app +var appLog = ""; +// List of apps that errored +var erroredApps = []; + +jsRXCallback = function() {}; +jsUpdateGfx = function() {}; + +function ERROR(s) { + console.error(s); + process.exit(1); +} + +function onConsoleOutput(txt) { + appLog += txt + "\n"; +} + +function getThumbnail(appId, imageFn) { + console.log("Thumbnail for "+appId); + var app = apps.find(a=>a.id==appId); + if (!app) ERROR(`App ${JSON.stringify(appId)} not found`); + if (app.custom) ERROR(`App ${JSON.stringify(appId)} requires HTML customisation`); + + + return new Promise(resolve => { + AppInfo.getFiles(app, { + fileGetter:function(url) { + console.log(__dirname+"/"+url); + return Promise.resolve(require("fs").readFileSync(__dirname+"/../"+url).toString("binary")); + }, + settings : SETTINGS, + device : { id : DEVICEID } + }).then(files => { + console.log("AppInfo returned");//, files); + flashMemory.set(factoryFlashMemory); + jsTransmitString("reset()\n"); + console.log("Uploading..."); + jsTransmitString("g.clear()\n"); + var command = files.map(f=>f.cmd).join("\n")+"\n"; + command += `load("${appId}.app.js")\n`; + appLog = ""; + jsTransmitString(command); + console.log("Done."); + jsStopIdle(); + + var rgba = new Uint8Array(GFX_WIDTH*GFX_HEIGHT*4); + jsGetGfxContents(rgba); + var rgba32 = new Uint32Array(rgba.buffer); + var firstPixel = rgba32[0]; + var blankImage = rgba32.every(col=>col==firstPixel) + + if (appLog.indexOf("Uncaught")>=0) + erroredApps.push( { id : app.id, log : appLog } ); + + if (!blankImage) { + var Jimp = require("jimp"); + let image = new Jimp(GFX_WIDTH, GFX_HEIGHT, function (err, image) { + if (err) throw err; + let buffer = image.bitmap.data; + buffer.set(rgba); + image.write(imageFn, (err) => { + if (err) throw err; + console.log("Image written as "+imageFn); + resolve(true); + }); + }); + } else { + console.log("Image is empty"); + resolve(false); + } + + }); + }); +} + +var screenshots = []; + +// wait until loaded... +setTimeout(function() { + console.log("Loaded..."); + jsInit(); + jsIdle(); + console.log("Factory reset"); + jsTransmitString("Bangle.factoryReset()\n"); + factoryFlashMemory.set(flashMemory); + console.log("Ready!"); + + if (singleAppId) { + getThumbnail(singleAppId, "screenshots/"+singleAppId+"-"+EMULATOR+".png"); + return; + } + + var appList = apps.filter(app => (!app.type || app.type=="clock") && !app.custom); + appList = appList.filter(app => !app.screenshots && app.supports.includes(DEVICEID)); + + var promise = Promise.resolve(); + appList.forEach(app => { + promise = promise.then(() => { + var imageFile = "screenshots/"+app.id+"-"+EMULATOR+".png"; + return getThumbnail(app.id, imageFile).then(ok => { + screenshots.push({ + id : app.id, + url : imageFile, + version: app.version + }); + }); + }); + }); + + promise.then(function() { + console.log("Complete!"); + require("fs").writeFileSync("screenshots.json", JSON.stringify(screenshots,null,2)); + console.log("Errored Apps", erroredApps); + }); + + +}); diff --git a/core b/core index 0fd608f08..59f80bb52 160000 --- a/core +++ b/core @@ -1 +1 @@ -Subproject commit 0fd608f085deff9b39f2db3559ecc88edb232aba +Subproject commit 59f80bb52a38da12cb272f9106cb3951b49dab2e diff --git a/css/main.css b/css/main.css index 0dbe8da14..82f6fbcfe 100644 --- a/css/main.css +++ b/css/main.css @@ -23,15 +23,48 @@ .filter-nav { display: inline-block; } +.device-nav { + display: inline-block; +} .sort-nav { float: right; } + +.app-tile { + position: relative; + border-bottom: 1px solid #EEE; + margin-bottom: 4px; + min-height: 8em; +} + .tile-content { position: relative; } .link-github { position:absolute; top: 36px; left: -24px; } -.btn-favourite { - color: red; +.tile-screenshot { + position:absolute;bottom:1em;right:1em; + width:4em;height:4em; + padding:2px;border:1px solid black; + cursor:pointer; +} + +.btn.btn-favourite { color: red; } +.btn.btn-favourite:hover { color: red; } + +.icon.icon-emulator { text-indent: 0px; } /*override spectre*/ +.icon.icon-emulator { + content: url("data:image/svg+xml,%3Csvg fill='rgb(87, 85, 217)' xmlns='http://www.w3.org/2000/svg' viewBox='4 4 40 40' width='1em' height='1em'%3E%3Cpath d='M 8.5 5 C 6.0324991 5 4 7.0324991 4 9.5 L 4 30.5 C 4 32.967501 6.0324991 35 8.5 35 L 17 35 L 17 40 L 13.5 40 A 1.50015 1.50015 0 1 0 13.5 43 L 18.253906 43 A 1.50015 1.50015 0 0 0 18.740234 43 L 29.253906 43 A 1.50015 1.50015 0 0 0 29.740234 43 L 34.5 43 A 1.50015 1.50015 0 1 0 34.5 40 L 31 40 L 31 35 L 39.5 35 C 41.967501 35 44 32.967501 44 30.5 L 44 9.5 C 44 7.0324991 41.967501 5 39.5 5 L 8.5 5 z M 8.5 8 L 39.5 8 C 40.346499 8 41 8.6535009 41 9.5 L 41 30.5 C 41 31.346499 40.346499 32 39.5 32 L 29.746094 32 A 1.50015 1.50015 0 0 0 29.259766 32 L 18.746094 32 A 1.50015 1.50015 0 0 0 18.259766 32 L 8.5 32 C 7.6535009 32 7 31.346499 7 30.5 L 7 9.5 C 7 8.6535009 7.6535009 8 8.5 8 z M 17.5 12 C 16.136406 12 15 13.136406 15 14.5 L 15 25.5 C 15 26.863594 16.136406 28 17.5 28 L 30.5 28 C 31.863594 28 33 26.863594 33 25.5 L 33 14.5 C 33 13.136406 31.863594 12 30.5 12 L 17.5 12 z M 18 18 L 30 18 L 30 25 L 18 25 L 18 18 z M 20 35 L 28 35 L 28 40 L 20 40 L 20 35 z'/%3E%3C/svg%3E"); +} +.icon.icon-favourite { text-indent: 0px; } /*override spectre*/ +.icon.icon-favourite::before { + content: "\02661"; /* 0x2661 = empty heart; 0x2606 = empty star */ +} +.icon.icon-favourite-active::before { + content: "\02665"; /* 0x2665 = solid heart; 0x2605 = solid star */ +} +.icon.icon-interface {text-indent: 0px;font-size: 130%;vertical-align: -30%;} /*override spectre*/ +.icon.icon-interface::before { + content: "\01F5AB"; } diff --git a/css/spectre-exp.min.css b/css/spectre-exp.min.css index 942cf59bf..d3137743a 100644 --- a/css/spectre-exp.min.css +++ b/css/spectre-exp.min.css @@ -1 +1 @@ -/*! Spectre.css Experimentals v0.5.8 | MIT License | github.com/picturepan2/spectre */.form-autocomplete{position:relative}.form-autocomplete .form-autocomplete-input{align-content:flex-start;display:flex;display:-ms-flexbox;-ms-flex-line-pack:start;-ms-flex-wrap:wrap;flex-wrap:wrap;height:auto;min-height:1.6rem;padding:.1rem}.form-autocomplete .form-autocomplete-input.is-focused{border-color:#5755d9;box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.form-autocomplete .form-autocomplete-input .form-input{border-color:transparent;box-shadow:none;display:inline-block;-ms-flex:1 0 auto;flex:1 0 auto;height:1.2rem;line-height:.8rem;margin:.1rem;width:auto}.form-autocomplete .menu{left:0;position:absolute;top:100%;width:100%}.form-autocomplete.autocomplete-oneline .form-autocomplete-input{-ms-flex-wrap:nowrap;flex-wrap:nowrap;overflow-x:auto}.form-autocomplete.autocomplete-oneline .chip{-ms-flex:1 0 auto;flex:1 0 auto}.calendar{border:.05rem solid #dadee4;border-radius:.1rem;display:block;min-width:280px}.calendar .calendar-nav{align-items:center;background:#f7f8f9;border-top-left-radius:.1rem;border-top-right-radius:.1rem;display:flex;display:-ms-flexbox;-ms-flex-align:center;font-size:.9rem;padding:.4rem}.calendar .calendar-body,.calendar .calendar-header{display:flex;display:-ms-flexbox;-ms-flex-pack:center;-ms-flex-wrap:wrap;flex-wrap:wrap;justify-content:center;padding:.4rem 0}.calendar .calendar-body .calendar-date,.calendar .calendar-header .calendar-date{-ms-flex:0 0 14.28%;flex:0 0 14.28%;max-width:14.28%}.calendar .calendar-header{background:#f7f8f9;border-bottom:.05rem solid #dadee4;color:#bcc3ce;font-size:.7rem;text-align:center}.calendar .calendar-body{color:#66758c}.calendar .calendar-date{border:0;padding:.2rem}.calendar .calendar-date .date-item{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:0 0;border:.05rem solid transparent;border-radius:50%;color:#66758c;cursor:pointer;font-size:.7rem;height:1.4rem;line-height:1rem;outline:0;padding:.1rem;position:relative;text-align:center;text-decoration:none;transition:background .2s,border .2s,box-shadow .2s,color .2s;vertical-align:middle;white-space:nowrap;width:1.4rem}.calendar .calendar-date .date-item.date-today{border-color:#e5e5f9;color:#5755d9}.calendar .calendar-date .date-item:focus{box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.calendar .calendar-date .date-item:focus,.calendar .calendar-date .date-item:hover{background:#fefeff;border-color:#e5e5f9;color:#5755d9;text-decoration:none}.calendar .calendar-date .date-item.active,.calendar .calendar-date .date-item:active{background:#4b48d6;border-color:#3634d2;color:#fff}.calendar .calendar-date .date-item.badge::after{position:absolute;right:3px;top:3px;transform:translate(50%,-50%)}.calendar .calendar-date .calendar-event.disabled,.calendar .calendar-date .calendar-event:disabled,.calendar .calendar-date .date-item.disabled,.calendar .calendar-date .date-item:disabled{cursor:default;opacity:.25;pointer-events:none}.calendar .calendar-date.next-month .calendar-event,.calendar .calendar-date.next-month .date-item,.calendar .calendar-date.prev-month .calendar-event,.calendar .calendar-date.prev-month .date-item{opacity:.25}.calendar .calendar-range{position:relative}.calendar .calendar-range::before{background:#f1f1fc;content:"";height:1.4rem;left:0;position:absolute;right:0;top:50%;transform:translateY(-50%)}.calendar .calendar-range.range-start::before{left:50%}.calendar .calendar-range.range-end::before{right:50%}.calendar .calendar-range.range-end .date-item,.calendar .calendar-range.range-start .date-item{background:#4b48d6;border-color:#3634d2;color:#fff}.calendar .calendar-range .date-item{color:#5755d9}.calendar.calendar-lg .calendar-body{padding:0}.calendar.calendar-lg .calendar-body .calendar-date{border-bottom:.05rem solid #dadee4;border-right:.05rem solid #dadee4;display:flex;display:-ms-flexbox;-ms-flex-direction:column;flex-direction:column;height:5.5rem;padding:0}.calendar.calendar-lg .calendar-body .calendar-date:nth-child(7n){border-right:0}.calendar.calendar-lg .calendar-body .calendar-date:nth-last-child(-n+7){border-bottom:0}.calendar.calendar-lg .date-item{align-self:flex-end;-ms-flex-item-align:end;height:1.4rem;margin-right:.2rem;margin-top:.2rem}.calendar.calendar-lg .calendar-range::before{top:19px}.calendar.calendar-lg .calendar-range.range-start::before{left:auto;width:19px}.calendar.calendar-lg .calendar-range.range-end::before{right:19px}.calendar.calendar-lg .calendar-events{flex-grow:1;-ms-flex-positive:1;line-height:1;overflow-y:auto;padding:.2rem}.calendar.calendar-lg .calendar-event{border-radius:.1rem;display:block;font-size:.7rem;margin:.1rem auto;overflow:hidden;padding:3px 4px;text-overflow:ellipsis;white-space:nowrap}.carousel .carousel-locator:nth-of-type(1):checked~.carousel-container .carousel-item:nth-of-type(1),.carousel .carousel-locator:nth-of-type(2):checked~.carousel-container .carousel-item:nth-of-type(2),.carousel .carousel-locator:nth-of-type(3):checked~.carousel-container .carousel-item:nth-of-type(3),.carousel .carousel-locator:nth-of-type(4):checked~.carousel-container .carousel-item:nth-of-type(4),.carousel .carousel-locator:nth-of-type(5):checked~.carousel-container .carousel-item:nth-of-type(5),.carousel .carousel-locator:nth-of-type(6):checked~.carousel-container .carousel-item:nth-of-type(6),.carousel .carousel-locator:nth-of-type(7):checked~.carousel-container .carousel-item:nth-of-type(7),.carousel .carousel-locator:nth-of-type(8):checked~.carousel-container .carousel-item:nth-of-type(8){animation:carousel-slidein .75s ease-in-out 1;opacity:1;z-index:100}.carousel .carousel-locator:nth-of-type(1):checked~.carousel-nav .nav-item:nth-of-type(1),.carousel .carousel-locator:nth-of-type(2):checked~.carousel-nav .nav-item:nth-of-type(2),.carousel .carousel-locator:nth-of-type(3):checked~.carousel-nav .nav-item:nth-of-type(3),.carousel .carousel-locator:nth-of-type(4):checked~.carousel-nav .nav-item:nth-of-type(4),.carousel .carousel-locator:nth-of-type(5):checked~.carousel-nav .nav-item:nth-of-type(5),.carousel .carousel-locator:nth-of-type(6):checked~.carousel-nav .nav-item:nth-of-type(6),.carousel .carousel-locator:nth-of-type(7):checked~.carousel-nav .nav-item:nth-of-type(7),.carousel .carousel-locator:nth-of-type(8):checked~.carousel-nav .nav-item:nth-of-type(8){color:#f7f8f9}.carousel{background:#f7f8f9;display:block;overflow:hidden;-webkit-overflow-scrolling:touch;position:relative;width:100%;z-index:1}.carousel .carousel-container{height:100%;left:0;position:relative}.carousel .carousel-container::before{content:"";display:block;padding-bottom:56.25%}.carousel .carousel-container .carousel-item{animation:carousel-slideout 1s ease-in-out 1;height:100%;left:0;margin:0;opacity:0;position:absolute;top:0;width:100%}.carousel .carousel-container .carousel-item:hover .item-next,.carousel .carousel-container .carousel-item:hover .item-prev{opacity:1}.carousel .carousel-container .item-next,.carousel .carousel-container .item-prev{background:rgba(247,248,249,.25);border-color:rgba(247,248,249,.5);color:#f7f8f9;opacity:0;position:absolute;top:50%;transform:translateY(-50%);transition:all .4s;z-index:100}.carousel .carousel-container .item-prev{left:1rem}.carousel .carousel-container .item-next{right:1rem}.carousel .carousel-nav{bottom:.4rem;display:flex;display:-ms-flexbox;-ms-flex-pack:center;justify-content:center;left:50%;position:absolute;transform:translateX(-50%);width:10rem;z-index:100}.carousel .carousel-nav .nav-item{color:rgba(247,248,249,.5);display:block;-ms-flex:1 0 auto;flex:1 0 auto;height:1.6rem;margin:.2rem;max-width:2.5rem;position:relative}.carousel .carousel-nav .nav-item::before{background:currentColor;content:"";display:block;height:.1rem;position:absolute;top:.5rem;width:100%}@keyframes carousel-slidein{0%{transform:translateX(100%)}100%{transform:translateX(0)}}@keyframes carousel-slideout{0%{opacity:1;transform:translateX(0)}100%{opacity:1;transform:translateX(-50%)}}.comparison-slider{height:50vh;overflow:hidden;-webkit-overflow-scrolling:touch;position:relative;width:100%}.comparison-slider .comparison-after,.comparison-slider .comparison-before{height:100%;left:0;margin:0;overflow:hidden;position:absolute;top:0}.comparison-slider .comparison-after img,.comparison-slider .comparison-before img{height:100%;object-fit:cover;object-position:left center;position:absolute;width:100%}.comparison-slider .comparison-before{width:100%;z-index:1}.comparison-slider .comparison-before .comparison-label{right:.8rem}.comparison-slider .comparison-after{max-width:100%;min-width:0;z-index:2}.comparison-slider .comparison-after::before{background:0 0;content:"";cursor:default;height:100%;left:0;position:absolute;right:.8rem;top:0;z-index:1}.comparison-slider .comparison-after::after{background:currentColor;border-radius:50%;box-shadow:0 -5px,0 5px;color:#fff;content:"";height:3px;position:absolute;right:.4rem;top:50%;transform:translate(50%,-50%);width:3px}.comparison-slider .comparison-after .comparison-label{left:.8rem}.comparison-slider .comparison-resizer{animation:first-run 1.5s 1 ease-in-out;cursor:ew-resize;height:.8rem;left:0;max-width:100%;min-width:.8rem;opacity:0;outline:0;position:relative;resize:horizontal;top:50%;transform:translateY(-50%) scaleY(30);width:0}.comparison-slider .comparison-label{background:rgba(48,55,66,.5);bottom:.8rem;color:#fff;padding:.2rem .4rem;position:absolute;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}@keyframes first-run{0%{width:0}25%{width:2.4rem}50%{width:.8rem}75%{width:1.2rem}100%{width:0}}.filter .filter-tag#tag-0:checked~.filter-nav .chip[for=tag-0],.filter .filter-tag#tag-1:checked~.filter-nav .chip[for=tag-1],.filter .filter-tag#tag-2:checked~.filter-nav .chip[for=tag-2],.filter .filter-tag#tag-3:checked~.filter-nav .chip[for=tag-3],.filter .filter-tag#tag-4:checked~.filter-nav .chip[for=tag-4],.filter .filter-tag#tag-5:checked~.filter-nav .chip[for=tag-5],.filter .filter-tag#tag-6:checked~.filter-nav .chip[for=tag-6],.filter .filter-tag#tag-7:checked~.filter-nav .chip[for=tag-7],.filter .filter-tag#tag-8:checked~.filter-nav .chip[for=tag-8]{background:#5755d9;color:#fff}.filter .filter-tag#tag-1:checked~.filter-body .filter-item:not([data-tag~=tag-1]),.filter .filter-tag#tag-2:checked~.filter-body .filter-item:not([data-tag~=tag-2]),.filter .filter-tag#tag-3:checked~.filter-body .filter-item:not([data-tag~=tag-3]),.filter .filter-tag#tag-4:checked~.filter-body .filter-item:not([data-tag~=tag-4]),.filter .filter-tag#tag-5:checked~.filter-body .filter-item:not([data-tag~=tag-5]),.filter .filter-tag#tag-6:checked~.filter-body .filter-item:not([data-tag~=tag-6]),.filter .filter-tag#tag-7:checked~.filter-body .filter-item:not([data-tag~=tag-7]),.filter .filter-tag#tag-8:checked~.filter-body .filter-item:not([data-tag~=tag-8]){display:none}.filter .filter-nav{margin:.4rem 0}.filter .filter-body{display:flex;display:-ms-flexbox;-ms-flex-wrap:wrap;flex-wrap:wrap}.meter{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#f7f8f9;border:0;border-radius:.1rem;display:block;height:.8rem;width:100%}.meter::-webkit-meter-inner-element{display:block}.meter::-webkit-meter-bar,.meter::-webkit-meter-even-less-good-value,.meter::-webkit-meter-optimum-value,.meter::-webkit-meter-suboptimum-value{border-radius:.1rem}.meter::-webkit-meter-bar{background:#f7f8f9}.meter::-webkit-meter-optimum-value{background:#32b643}.meter::-webkit-meter-suboptimum-value{background:#ffb700}.meter::-webkit-meter-even-less-good-value{background:#e85600}.meter:-moz-meter-optimum,.meter:-moz-meter-sub-optimum,.meter:-moz-meter-sub-sub-optimum,.meter::-moz-meter-bar{border-radius:.1rem}.meter:-moz-meter-optimum::-moz-meter-bar{background:#32b643}.meter:-moz-meter-sub-optimum::-moz-meter-bar{background:#ffb700}.meter:-moz-meter-sub-sub-optimum::-moz-meter-bar{background:#e85600}.off-canvas{display:flex;display:-ms-flexbox;-ms-flex-flow:nowrap;flex-flow:nowrap;height:100%;position:relative;width:100%}.off-canvas .off-canvas-toggle{display:block;left:.4rem;position:absolute;top:.4rem;transition:none;z-index:1}.off-canvas .off-canvas-sidebar{background:#f7f8f9;bottom:0;left:0;min-width:10rem;overflow-y:auto;position:fixed;top:0;transform:translateX(-100%);transition:transform .25s;z-index:200}.off-canvas .off-canvas-content{-ms-flex:1 1 auto;flex:1 1 auto;height:100%;padding:.4rem .4rem .4rem 4rem}.off-canvas .off-canvas-overlay{background:rgba(48,55,66,.1);border-color:transparent;border-radius:0;bottom:0;display:none;height:100%;left:0;position:fixed;right:0;top:0;width:100%}.off-canvas .off-canvas-sidebar.active,.off-canvas .off-canvas-sidebar:target{transform:translateX(0)}.off-canvas .off-canvas-sidebar.active~.off-canvas-overlay,.off-canvas .off-canvas-sidebar:target~.off-canvas-overlay{display:block;z-index:100}@media (min-width:960px){.off-canvas.off-canvas-sidebar-show .off-canvas-toggle{display:none}.off-canvas.off-canvas-sidebar-show .off-canvas-sidebar{-ms-flex:0 0 auto;flex:0 0 auto;position:relative;transform:none}.off-canvas.off-canvas-sidebar-show .off-canvas-overlay{display:none!important}}.parallax{display:block;height:auto;position:relative;width:auto}.parallax .parallax-content{box-shadow:0 1rem 2.1rem rgba(48,55,66,.3);height:auto;transform:perspective(1000px);transform-style:preserve-3d;transition:all .4s ease;width:100%}.parallax .parallax-content::before{content:"";display:block;height:100%;left:0;position:absolute;top:0;width:100%}.parallax .parallax-front{align-items:center;color:#fff;display:flex;display:-ms-flexbox;-ms-flex-align:center;-ms-flex-pack:center;height:100%;justify-content:center;left:0;position:absolute;text-align:center;text-shadow:0 0 20px rgba(48,55,66,.75);top:0;transform:translateZ(50px) scale(.95);transition:transform .4s;width:100%;z-index:1}.parallax .parallax-top-left{height:50%;left:0;outline:0;position:absolute;top:0;width:50%;z-index:100}.parallax .parallax-top-left:focus~.parallax-content,.parallax .parallax-top-left:hover~.parallax-content{transform:perspective(1000px) rotateX(3deg) rotateY(-3deg)}.parallax .parallax-top-left:focus~.parallax-content::before,.parallax .parallax-top-left:hover~.parallax-content::before{background:linear-gradient(135deg,rgba(255,255,255,.35) 0,transparent 50%)}.parallax .parallax-top-left:focus~.parallax-content .parallax-front,.parallax .parallax-top-left:hover~.parallax-content .parallax-front{transform:translate3d(4.5px,4.5px,50px) scale(.95)}.parallax .parallax-top-right{height:50%;outline:0;position:absolute;right:0;top:0;width:50%;z-index:100}.parallax .parallax-top-right:focus~.parallax-content,.parallax .parallax-top-right:hover~.parallax-content{transform:perspective(1000px) rotateX(3deg) rotateY(3deg)}.parallax .parallax-top-right:focus~.parallax-content::before,.parallax .parallax-top-right:hover~.parallax-content::before{background:linear-gradient(-135deg,rgba(255,255,255,.35) 0,transparent 50%)}.parallax .parallax-top-right:focus~.parallax-content .parallax-front,.parallax .parallax-top-right:hover~.parallax-content .parallax-front{transform:translate3d(-4.5px,4.5px,50px) scale(.95)}.parallax .parallax-bottom-left{bottom:0;height:50%;left:0;outline:0;position:absolute;width:50%;z-index:100}.parallax .parallax-bottom-left:focus~.parallax-content,.parallax .parallax-bottom-left:hover~.parallax-content{transform:perspective(1000px) rotateX(-3deg) rotateY(-3deg)}.parallax .parallax-bottom-left:focus~.parallax-content::before,.parallax .parallax-bottom-left:hover~.parallax-content::before{background:linear-gradient(45deg,rgba(255,255,255,.35) 0,transparent 50%)}.parallax .parallax-bottom-left:focus~.parallax-content .parallax-front,.parallax .parallax-bottom-left:hover~.parallax-content .parallax-front{transform:translate3d(4.5px,-4.5px,50px) scale(.95)}.parallax .parallax-bottom-right{bottom:0;height:50%;outline:0;position:absolute;right:0;width:50%;z-index:100}.parallax .parallax-bottom-right:focus~.parallax-content,.parallax .parallax-bottom-right:hover~.parallax-content{transform:perspective(1000px) rotateX(-3deg) rotateY(3deg)}.parallax .parallax-bottom-right:focus~.parallax-content::before,.parallax .parallax-bottom-right:hover~.parallax-content::before{background:linear-gradient(-45deg,rgba(255,255,255,.35) 0,transparent 50%)}.parallax .parallax-bottom-right:focus~.parallax-content .parallax-front,.parallax .parallax-bottom-right:hover~.parallax-content .parallax-front{transform:translate3d(-4.5px,-4.5px,50px) scale(.95)}.progress{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#eef0f3;border:0;border-radius:.1rem;color:#5755d9;height:.2rem;position:relative;width:100%}.progress::-webkit-progress-bar{background:0 0;border-radius:.1rem}.progress::-webkit-progress-value{background:#5755d9;border-radius:.1rem}.progress::-moz-progress-bar{background:#5755d9;border-radius:.1rem}.progress:indeterminate{animation:progress-indeterminate 1.5s linear infinite;background:#eef0f3 linear-gradient(to right,#5755d9 30%,#eef0f3 30%) top left/150% 150% no-repeat}.progress:indeterminate::-moz-progress-bar{background:0 0}@keyframes progress-indeterminate{0%{background-position:200% 0}100%{background-position:-200% 0}}.slider{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:0 0;display:block;height:1.2rem;width:100%}.slider:focus{box-shadow:0 0 0 .1rem rgba(87,85,217,.2);outline:0}.slider.tooltip:not([data-tooltip])::after{content:attr(value)}.slider::-webkit-slider-thumb{-webkit-appearance:none;background:#5755d9;border:0;border-radius:50%;height:.6rem;margin-top:-.25rem;transition:transform .2s;width:.6rem}.slider::-moz-range-thumb{background:#5755d9;border:0;border-radius:50%;height:.6rem;transition:transform .2s;width:.6rem}.slider::-ms-thumb{background:#5755d9;border:0;border-radius:50%;height:.6rem;transition:transform .2s;width:.6rem}.slider:active::-webkit-slider-thumb{transform:scale(1.25)}.slider:active::-moz-range-thumb{transform:scale(1.25)}.slider:active::-ms-thumb{transform:scale(1.25)}.slider.disabled::-webkit-slider-thumb,.slider:disabled::-webkit-slider-thumb{background:#f7f8f9;transform:scale(1)}.slider.disabled::-moz-range-thumb,.slider:disabled::-moz-range-thumb{background:#f7f8f9;transform:scale(1)}.slider.disabled::-ms-thumb,.slider:disabled::-ms-thumb{background:#f7f8f9;transform:scale(1)}.slider::-webkit-slider-runnable-track{background:#eef0f3;border-radius:.1rem;height:.1rem;width:100%}.slider::-moz-range-track{background:#eef0f3;border-radius:.1rem;height:.1rem;width:100%}.slider::-ms-track{background:#eef0f3;border-radius:.1rem;height:.1rem;width:100%}.slider::-ms-fill-lower{background:#5755d9}.timeline .timeline-item{display:flex;display:-ms-flexbox;margin-bottom:1.2rem;position:relative}.timeline .timeline-item::before{background:#dadee4;content:"";height:100%;left:11px;position:absolute;top:1.2rem;width:2px}.timeline .timeline-item .timeline-left{-ms-flex:0 0 auto;flex:0 0 auto}.timeline .timeline-item .timeline-content{-ms-flex:1 1 auto;flex:1 1 auto;padding:2px 0 2px .8rem}.timeline .timeline-item .timeline-icon{align-items:center;border-radius:50%;color:#fff;display:-ms-flexbox;display:flex;-ms-flex-align:center;-ms-flex-pack:center;height:1.2rem;justify-content:center;text-align:center;width:1.2rem}.timeline .timeline-item .timeline-icon::before{border:.1rem solid #5755d9;border-radius:50%;content:"";display:block;height:.4rem;left:.4rem;position:absolute;top:.4rem;width:.4rem}.timeline .timeline-item .timeline-icon.icon-lg{background:#5755d9;line-height:1.2rem}.timeline .timeline-item .timeline-icon.icon-lg::before{content:none}.viewer-360{align-items:center;display:flex;display:-ms-flexbox;-ms-flex-align:center;-ms-flex-direction:column;flex-direction:column}.viewer-360 .viewer-slider[max="36"][value="1"]+.viewer-image{background-position-y:0}.viewer-360 .viewer-slider[max="36"][value="2"]+.viewer-image{background-position-y:2.8571428571%}.viewer-360 .viewer-slider[max="36"][value="3"]+.viewer-image{background-position-y:5.7142857143%}.viewer-360 .viewer-slider[max="36"][value="4"]+.viewer-image{background-position-y:8.5714285714%}.viewer-360 .viewer-slider[max="36"][value="5"]+.viewer-image{background-position-y:11.4285714286%}.viewer-360 .viewer-slider[max="36"][value="6"]+.viewer-image{background-position-y:14.2857142857%}.viewer-360 .viewer-slider[max="36"][value="7"]+.viewer-image{background-position-y:17.1428571429%}.viewer-360 .viewer-slider[max="36"][value="8"]+.viewer-image{background-position-y:20%}.viewer-360 .viewer-slider[max="36"][value="9"]+.viewer-image{background-position-y:22.8571428571%}.viewer-360 .viewer-slider[max="36"][value="10"]+.viewer-image{background-position-y:25.7142857143%}.viewer-360 .viewer-slider[max="36"][value="11"]+.viewer-image{background-position-y:28.5714285714%}.viewer-360 .viewer-slider[max="36"][value="12"]+.viewer-image{background-position-y:31.4285714286%}.viewer-360 .viewer-slider[max="36"][value="13"]+.viewer-image{background-position-y:34.2857142857%}.viewer-360 .viewer-slider[max="36"][value="14"]+.viewer-image{background-position-y:37.1428571429%}.viewer-360 .viewer-slider[max="36"][value="15"]+.viewer-image{background-position-y:40%}.viewer-360 .viewer-slider[max="36"][value="16"]+.viewer-image{background-position-y:42.8571428571%}.viewer-360 .viewer-slider[max="36"][value="17"]+.viewer-image{background-position-y:45.7142857143%}.viewer-360 .viewer-slider[max="36"][value="18"]+.viewer-image{background-position-y:48.5714285714%}.viewer-360 .viewer-slider[max="36"][value="19"]+.viewer-image{background-position-y:51.4285714286%}.viewer-360 .viewer-slider[max="36"][value="20"]+.viewer-image{background-position-y:54.2857142857%}.viewer-360 .viewer-slider[max="36"][value="21"]+.viewer-image{background-position-y:57.1428571429%}.viewer-360 .viewer-slider[max="36"][value="22"]+.viewer-image{background-position-y:60%}.viewer-360 .viewer-slider[max="36"][value="23"]+.viewer-image{background-position-y:62.8571428571%}.viewer-360 .viewer-slider[max="36"][value="24"]+.viewer-image{background-position-y:65.7142857143%}.viewer-360 .viewer-slider[max="36"][value="25"]+.viewer-image{background-position-y:68.5714285714%}.viewer-360 .viewer-slider[max="36"][value="26"]+.viewer-image{background-position-y:71.4285714286%}.viewer-360 .viewer-slider[max="36"][value="27"]+.viewer-image{background-position-y:74.2857142857%}.viewer-360 .viewer-slider[max="36"][value="28"]+.viewer-image{background-position-y:77.1428571429%}.viewer-360 .viewer-slider[max="36"][value="29"]+.viewer-image{background-position-y:80%}.viewer-360 .viewer-slider[max="36"][value="30"]+.viewer-image{background-position-y:82.8571428571%}.viewer-360 .viewer-slider[max="36"][value="31"]+.viewer-image{background-position-y:85.7142857143%}.viewer-360 .viewer-slider[max="36"][value="32"]+.viewer-image{background-position-y:88.5714285714%}.viewer-360 .viewer-slider[max="36"][value="33"]+.viewer-image{background-position-y:91.4285714286%}.viewer-360 .viewer-slider[max="36"][value="34"]+.viewer-image{background-position-y:94.2857142857%}.viewer-360 .viewer-slider[max="36"][value="35"]+.viewer-image{background-position-y:97.1428571429%}.viewer-360 .viewer-slider[max="36"][value="36"]+.viewer-image{background-position-y:100%}.viewer-360 .viewer-slider{cursor:ew-resize;-ms-flex-order:2;margin:1rem;order:2;width:60%}.viewer-360 .viewer-image{background-position-y:0;background-repeat:no-repeat;background-size:100%;-ms-flex-order:1;max-width:100%;order:1} \ No newline at end of file +/*! Spectre.css Experimentals v0.5.9 | MIT License | github.com/picturepan2/spectre */.form-autocomplete{position:relative}.form-autocomplete .form-autocomplete-input{align-content:flex-start;display:-ms-flexbox;display:flex;-ms-flex-line-pack:start;-ms-flex-wrap:wrap;flex-wrap:wrap;height:auto;min-height:1.6rem;padding:.1rem}.form-autocomplete .form-autocomplete-input.is-focused{border-color:#5755d9;box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.form-autocomplete .form-autocomplete-input .form-input{border-color:transparent;box-shadow:none;display:inline-block;-ms-flex:1 0 auto;flex:1 0 auto;height:1.2rem;line-height:.8rem;margin:.1rem;width:auto}.form-autocomplete .menu{left:0;position:absolute;top:100%;width:100%}.form-autocomplete.autocomplete-oneline .form-autocomplete-input{-ms-flex-wrap:nowrap;flex-wrap:nowrap;overflow-x:auto}.form-autocomplete.autocomplete-oneline .chip{-ms-flex:1 0 auto;flex:1 0 auto}.calendar{border:.05rem solid #dadee4;border-radius:.1rem;display:block;min-width:280px}.calendar .calendar-nav{align-items:center;background:#f7f8f9;border-top-left-radius:.1rem;border-top-right-radius:.1rem;display:-ms-flexbox;display:flex;-ms-flex-align:center;font-size:.9rem;padding:.4rem}.calendar .calendar-body,.calendar .calendar-header{display:-ms-flexbox;display:flex;-ms-flex-pack:center;-ms-flex-wrap:wrap;flex-wrap:wrap;justify-content:center;padding:.4rem 0}.calendar .calendar-body .calendar-date,.calendar .calendar-header .calendar-date{-ms-flex:0 0 14.28%;flex:0 0 14.28%;max-width:14.28%}.calendar .calendar-header{background:#f7f8f9;border-bottom:.05rem solid #dadee4;color:#bcc3ce;font-size:.7rem;text-align:center}.calendar .calendar-body{color:#66758c}.calendar .calendar-date{border:0;padding:.2rem}.calendar .calendar-date .date-item{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:0 0;border:.05rem solid transparent;border-radius:50%;color:#66758c;cursor:pointer;font-size:.7rem;height:1.4rem;line-height:1rem;outline:0;padding:.1rem;position:relative;text-align:center;text-decoration:none;transition:background .2s,border .2s,box-shadow .2s,color .2s;vertical-align:middle;white-space:nowrap;width:1.4rem}.calendar .calendar-date .date-item.date-today{border-color:#e5e5f9;color:#5755d9}.calendar .calendar-date .date-item:focus{box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.calendar .calendar-date .date-item:focus,.calendar .calendar-date .date-item:hover{background:#fefeff;border-color:#e5e5f9;color:#5755d9;text-decoration:none}.calendar .calendar-date .date-item.active,.calendar .calendar-date .date-item:active{background:#4b48d6;border-color:#3634d2;color:#fff}.calendar .calendar-date .date-item.badge::after{position:absolute;right:3px;top:3px;transform:translate(50%,-50%)}.calendar .calendar-date .calendar-event.disabled,.calendar .calendar-date .calendar-event:disabled,.calendar .calendar-date .date-item.disabled,.calendar .calendar-date .date-item:disabled{cursor:default;opacity:.25;pointer-events:none}.calendar .calendar-date.next-month .calendar-event,.calendar .calendar-date.next-month .date-item,.calendar .calendar-date.prev-month .calendar-event,.calendar .calendar-date.prev-month .date-item{opacity:.25}.calendar .calendar-range{position:relative}.calendar .calendar-range::before{background:#f1f1fc;content:"";height:1.4rem;left:0;position:absolute;right:0;top:50%;transform:translateY(-50%)}.calendar .calendar-range.range-start::before{left:50%}.calendar .calendar-range.range-end::before{right:50%}.calendar .calendar-range.range-end .date-item,.calendar .calendar-range.range-start .date-item{background:#4b48d6;border-color:#3634d2;color:#fff}.calendar .calendar-range .date-item{color:#5755d9}.calendar.calendar-lg .calendar-body{padding:0}.calendar.calendar-lg .calendar-body .calendar-date{border-bottom:.05rem solid #dadee4;border-right:.05rem solid #dadee4;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;height:5.5rem;padding:0}.calendar.calendar-lg .calendar-body .calendar-date:nth-child(7n){border-right:0}.calendar.calendar-lg .calendar-body .calendar-date:nth-last-child(-n+7){border-bottom:0}.calendar.calendar-lg .date-item{align-self:flex-end;-ms-flex-item-align:end;height:1.4rem;margin-right:.2rem;margin-top:.2rem}.calendar.calendar-lg .calendar-range::before{top:19px}.calendar.calendar-lg .calendar-range.range-start::before{left:auto;width:19px}.calendar.calendar-lg .calendar-range.range-end::before{right:19px}.calendar.calendar-lg .calendar-events{flex-grow:1;-ms-flex-positive:1;line-height:1;overflow-y:auto;padding:.2rem}.calendar.calendar-lg .calendar-event{border-radius:.1rem;display:block;font-size:.7rem;margin:.1rem auto;overflow:hidden;padding:3px 4px;text-overflow:ellipsis;white-space:nowrap}.carousel .carousel-locator:nth-of-type(1):checked~.carousel-container .carousel-item:nth-of-type(1),.carousel .carousel-locator:nth-of-type(2):checked~.carousel-container .carousel-item:nth-of-type(2),.carousel .carousel-locator:nth-of-type(3):checked~.carousel-container .carousel-item:nth-of-type(3),.carousel .carousel-locator:nth-of-type(4):checked~.carousel-container .carousel-item:nth-of-type(4),.carousel .carousel-locator:nth-of-type(5):checked~.carousel-container .carousel-item:nth-of-type(5),.carousel .carousel-locator:nth-of-type(6):checked~.carousel-container .carousel-item:nth-of-type(6),.carousel .carousel-locator:nth-of-type(7):checked~.carousel-container .carousel-item:nth-of-type(7),.carousel .carousel-locator:nth-of-type(8):checked~.carousel-container .carousel-item:nth-of-type(8){animation:carousel-slidein .75s ease-in-out 1;opacity:1;z-index:100}.carousel .carousel-locator:nth-of-type(1):checked~.carousel-nav .nav-item:nth-of-type(1),.carousel .carousel-locator:nth-of-type(2):checked~.carousel-nav .nav-item:nth-of-type(2),.carousel .carousel-locator:nth-of-type(3):checked~.carousel-nav .nav-item:nth-of-type(3),.carousel .carousel-locator:nth-of-type(4):checked~.carousel-nav .nav-item:nth-of-type(4),.carousel .carousel-locator:nth-of-type(5):checked~.carousel-nav .nav-item:nth-of-type(5),.carousel .carousel-locator:nth-of-type(6):checked~.carousel-nav .nav-item:nth-of-type(6),.carousel .carousel-locator:nth-of-type(7):checked~.carousel-nav .nav-item:nth-of-type(7),.carousel .carousel-locator:nth-of-type(8):checked~.carousel-nav .nav-item:nth-of-type(8){color:#f7f8f9}.carousel{background:#f7f8f9;display:block;overflow:hidden;-webkit-overflow-scrolling:touch;position:relative;width:100%;z-index:1}.carousel .carousel-container{height:100%;left:0;position:relative}.carousel .carousel-container::before{content:"";display:block;padding-bottom:56.25%}.carousel .carousel-container .carousel-item{animation:carousel-slideout 1s ease-in-out 1;height:100%;left:0;margin:0;opacity:0;position:absolute;top:0;width:100%}.carousel .carousel-container .carousel-item:hover .item-next,.carousel .carousel-container .carousel-item:hover .item-prev{opacity:1}.carousel .carousel-container .item-next,.carousel .carousel-container .item-prev{background:rgba(247,248,249,.25);border-color:rgba(247,248,249,.5);color:#f7f8f9;opacity:0;position:absolute;top:50%;transform:translateY(-50%);transition:all .4s;z-index:100}.carousel .carousel-container .item-prev{left:1rem}.carousel .carousel-container .item-next{right:1rem}.carousel .carousel-nav{bottom:.4rem;display:-ms-flexbox;display:flex;-ms-flex-pack:center;justify-content:center;left:50%;position:absolute;transform:translateX(-50%);width:10rem;z-index:100}.carousel .carousel-nav .nav-item{color:rgba(247,248,249,.5);display:block;-ms-flex:1 0 auto;flex:1 0 auto;height:1.6rem;margin:.2rem;max-width:2.5rem;position:relative}.carousel .carousel-nav .nav-item::before{background:currentColor;content:"";display:block;height:.1rem;position:absolute;top:.5rem;width:100%}@keyframes carousel-slidein{0%{transform:translateX(100%)}100%{transform:translateX(0)}}@keyframes carousel-slideout{0%{opacity:1;transform:translateX(0)}100%{opacity:1;transform:translateX(-50%)}}.comparison-slider{height:50vh;overflow:hidden;-webkit-overflow-scrolling:touch;position:relative;width:100%}.comparison-slider .comparison-after,.comparison-slider .comparison-before{height:100%;left:0;margin:0;overflow:hidden;position:absolute;top:0}.comparison-slider .comparison-after img,.comparison-slider .comparison-before img{height:100%;object-fit:cover;object-position:left center;position:absolute;width:100%}.comparison-slider .comparison-before{width:100%;z-index:1}.comparison-slider .comparison-before .comparison-label{right:.8rem}.comparison-slider .comparison-after{max-width:100%;min-width:0;z-index:2}.comparison-slider .comparison-after::before{background:0 0;content:"";cursor:default;height:100%;left:0;position:absolute;right:.8rem;top:0;z-index:1}.comparison-slider .comparison-after::after{background:currentColor;border-radius:50%;box-shadow:0 -5px,0 5px;color:#fff;content:"";height:3px;pointer-events:none;position:absolute;right:.4rem;top:50%;transform:translate(50%,-50%);width:3px}.comparison-slider .comparison-after .comparison-label{left:.8rem}.comparison-slider .comparison-resizer{animation:first-run 1.5s 1 ease-in-out;cursor:ew-resize;height:.8rem;left:0;max-width:100%;min-width:.8rem;opacity:0;outline:0;position:relative;resize:horizontal;top:50%;transform:translateY(-50%) scaleY(30);width:0}.comparison-slider .comparison-label{background:rgba(48,55,66,.5);bottom:.8rem;color:#fff;padding:.2rem .4rem;position:absolute;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}@keyframes first-run{0%{width:0}25%{width:2.4rem}50%{width:.8rem}75%{width:1.2rem}100%{width:0}}.filter .filter-tag#tag-0:checked~.filter-nav .chip[for=tag-0],.filter .filter-tag#tag-1:checked~.filter-nav .chip[for=tag-1],.filter .filter-tag#tag-2:checked~.filter-nav .chip[for=tag-2],.filter .filter-tag#tag-3:checked~.filter-nav .chip[for=tag-3],.filter .filter-tag#tag-4:checked~.filter-nav .chip[for=tag-4],.filter .filter-tag#tag-5:checked~.filter-nav .chip[for=tag-5],.filter .filter-tag#tag-6:checked~.filter-nav .chip[for=tag-6],.filter .filter-tag#tag-7:checked~.filter-nav .chip[for=tag-7],.filter .filter-tag#tag-8:checked~.filter-nav .chip[for=tag-8]{background:#5755d9;color:#fff}.filter .filter-tag#tag-1:checked~.filter-body .filter-item:not([data-tag~=tag-1]),.filter .filter-tag#tag-2:checked~.filter-body .filter-item:not([data-tag~=tag-2]),.filter .filter-tag#tag-3:checked~.filter-body .filter-item:not([data-tag~=tag-3]),.filter .filter-tag#tag-4:checked~.filter-body .filter-item:not([data-tag~=tag-4]),.filter .filter-tag#tag-5:checked~.filter-body .filter-item:not([data-tag~=tag-5]),.filter .filter-tag#tag-6:checked~.filter-body .filter-item:not([data-tag~=tag-6]),.filter .filter-tag#tag-7:checked~.filter-body .filter-item:not([data-tag~=tag-7]),.filter .filter-tag#tag-8:checked~.filter-body .filter-item:not([data-tag~=tag-8]){display:none}.filter .filter-nav{margin:.4rem 0}.filter .filter-body{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap}.meter{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#f7f8f9;border:0;border-radius:.1rem;display:block;height:.8rem;width:100%}.meter::-webkit-meter-inner-element{display:block}.meter::-webkit-meter-bar,.meter::-webkit-meter-even-less-good-value,.meter::-webkit-meter-optimum-value,.meter::-webkit-meter-suboptimum-value{border-radius:.1rem}.meter::-webkit-meter-bar{background:#f7f8f9}.meter::-webkit-meter-optimum-value{background:#32b643}.meter::-webkit-meter-suboptimum-value{background:#ffb700}.meter::-webkit-meter-even-less-good-value{background:#e85600}.meter:-moz-meter-optimum,.meter:-moz-meter-sub-optimum,.meter:-moz-meter-sub-sub-optimum,.meter::-moz-meter-bar{border-radius:.1rem}.meter:-moz-meter-optimum::-moz-meter-bar{background:#32b643}.meter:-moz-meter-sub-optimum::-moz-meter-bar{background:#ffb700}.meter:-moz-meter-sub-sub-optimum::-moz-meter-bar{background:#e85600}.off-canvas{display:-ms-flexbox;display:flex;-ms-flex-flow:nowrap;flex-flow:nowrap;height:100%;position:relative;width:100%}.off-canvas .off-canvas-toggle{display:block;left:.4rem;position:absolute;top:.4rem;transition:none;z-index:1}.off-canvas .off-canvas-sidebar{background:#f7f8f9;bottom:0;left:0;min-width:10rem;overflow-y:auto;position:fixed;top:0;transform:translateX(-100%);transition:transform .25s;z-index:200}.off-canvas .off-canvas-content{-ms-flex:1 1 auto;flex:1 1 auto;height:100%;padding:.4rem .4rem .4rem 4rem}.off-canvas .off-canvas-overlay{background:rgba(48,55,66,.1);border-color:transparent;border-radius:0;bottom:0;display:none;height:100%;left:0;position:fixed;right:0;top:0;width:100%}.off-canvas .off-canvas-sidebar.active,.off-canvas .off-canvas-sidebar:target{transform:translateX(0)}.off-canvas .off-canvas-sidebar.active~.off-canvas-overlay,.off-canvas .off-canvas-sidebar:target~.off-canvas-overlay{display:block;z-index:100}@media (min-width:960px){.off-canvas.off-canvas-sidebar-show .off-canvas-toggle{display:none}.off-canvas.off-canvas-sidebar-show .off-canvas-sidebar{-ms-flex:0 0 auto;flex:0 0 auto;position:relative;transform:none}.off-canvas.off-canvas-sidebar-show .off-canvas-overlay{display:none!important}}.parallax{display:block;height:auto;position:relative;width:auto}.parallax .parallax-content{box-shadow:0 1rem 2.1rem rgba(48,55,66,.3);height:auto;transform:perspective(1000px);transform-style:preserve-3d;transition:all .4s ease;width:100%}.parallax .parallax-content::before{content:"";display:block;height:100%;left:0;position:absolute;top:0;width:100%}.parallax .parallax-front{align-items:center;color:#fff;display:-ms-flexbox;display:flex;-ms-flex-align:center;-ms-flex-pack:center;height:100%;justify-content:center;left:0;position:absolute;text-align:center;text-shadow:0 0 20px rgba(48,55,66,.75);top:0;transform:translateZ(50px) scale(.95);transition:transform .4s;width:100%;z-index:1}.parallax .parallax-top-left{height:50%;left:0;outline:0;position:absolute;top:0;width:50%;z-index:100}.parallax .parallax-top-left:focus~.parallax-content,.parallax .parallax-top-left:hover~.parallax-content{transform:perspective(1000px) rotateX(3deg) rotateY(-3deg)}.parallax .parallax-top-left:focus~.parallax-content::before,.parallax .parallax-top-left:hover~.parallax-content::before{background:linear-gradient(135deg,rgba(255,255,255,.35) 0,transparent 50%)}.parallax .parallax-top-left:focus~.parallax-content .parallax-front,.parallax .parallax-top-left:hover~.parallax-content .parallax-front{transform:translate3d(4.5px,4.5px,50px) scale(.95)}.parallax .parallax-top-right{height:50%;outline:0;position:absolute;right:0;top:0;width:50%;z-index:100}.parallax .parallax-top-right:focus~.parallax-content,.parallax .parallax-top-right:hover~.parallax-content{transform:perspective(1000px) rotateX(3deg) rotateY(3deg)}.parallax .parallax-top-right:focus~.parallax-content::before,.parallax .parallax-top-right:hover~.parallax-content::before{background:linear-gradient(-135deg,rgba(255,255,255,.35) 0,transparent 50%)}.parallax .parallax-top-right:focus~.parallax-content .parallax-front,.parallax .parallax-top-right:hover~.parallax-content .parallax-front{transform:translate3d(-4.5px,4.5px,50px) scale(.95)}.parallax .parallax-bottom-left{bottom:0;height:50%;left:0;outline:0;position:absolute;width:50%;z-index:100}.parallax .parallax-bottom-left:focus~.parallax-content,.parallax .parallax-bottom-left:hover~.parallax-content{transform:perspective(1000px) rotateX(-3deg) rotateY(-3deg)}.parallax .parallax-bottom-left:focus~.parallax-content::before,.parallax .parallax-bottom-left:hover~.parallax-content::before{background:linear-gradient(45deg,rgba(255,255,255,.35) 0,transparent 50%)}.parallax .parallax-bottom-left:focus~.parallax-content .parallax-front,.parallax .parallax-bottom-left:hover~.parallax-content .parallax-front{transform:translate3d(4.5px,-4.5px,50px) scale(.95)}.parallax .parallax-bottom-right{bottom:0;height:50%;outline:0;position:absolute;right:0;width:50%;z-index:100}.parallax .parallax-bottom-right:focus~.parallax-content,.parallax .parallax-bottom-right:hover~.parallax-content{transform:perspective(1000px) rotateX(-3deg) rotateY(3deg)}.parallax .parallax-bottom-right:focus~.parallax-content::before,.parallax .parallax-bottom-right:hover~.parallax-content::before{background:linear-gradient(-45deg,rgba(255,255,255,.35) 0,transparent 50%)}.parallax .parallax-bottom-right:focus~.parallax-content .parallax-front,.parallax .parallax-bottom-right:hover~.parallax-content .parallax-front{transform:translate3d(-4.5px,-4.5px,50px) scale(.95)}.progress{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#eef0f3;border:0;border-radius:.1rem;color:#5755d9;height:.2rem;position:relative;width:100%}.progress::-webkit-progress-bar{background:0 0;border-radius:.1rem}.progress::-webkit-progress-value{background:#5755d9;border-radius:.1rem}.progress::-moz-progress-bar{background:#5755d9;border-radius:.1rem}.progress:indeterminate{animation:progress-indeterminate 1.5s linear infinite;background:#eef0f3 linear-gradient(to right,#5755d9 30%,#eef0f3 30%) top left/150% 150% no-repeat}.progress:indeterminate::-moz-progress-bar{background:0 0}@keyframes progress-indeterminate{0%{background-position:200% 0}100%{background-position:-200% 0}}.slider{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:0 0;display:block;height:1.2rem;width:100%}.slider:focus{box-shadow:0 0 0 .1rem rgba(87,85,217,.2);outline:0}.slider.tooltip:not([data-tooltip])::after{content:attr(value)}.slider::-webkit-slider-thumb{-webkit-appearance:none;background:#5755d9;border:0;border-radius:50%;height:.6rem;margin-top:-.25rem;-webkit-transition:transform .2s;transition:transform .2s;width:.6rem}.slider::-moz-range-thumb{background:#5755d9;border:0;border-radius:50%;height:.6rem;-moz-transition:transform .2s;transition:transform .2s;width:.6rem}.slider::-ms-thumb{background:#5755d9;border:0;border-radius:50%;height:.6rem;-ms-transition:transform .2s;transition:transform .2s;width:.6rem}.slider:active::-webkit-slider-thumb{transform:scale(1.25)}.slider:active::-moz-range-thumb{transform:scale(1.25)}.slider:active::-ms-thumb{transform:scale(1.25)}.slider.disabled::-webkit-slider-thumb,.slider:disabled::-webkit-slider-thumb{background:#f7f8f9;transform:scale(1)}.slider.disabled::-moz-range-thumb,.slider:disabled::-moz-range-thumb{background:#f7f8f9;transform:scale(1)}.slider.disabled::-ms-thumb,.slider:disabled::-ms-thumb{background:#f7f8f9;transform:scale(1)}.slider::-webkit-slider-runnable-track{background:#eef0f3;border-radius:.1rem;height:.1rem;width:100%}.slider::-moz-range-track{background:#eef0f3;border-radius:.1rem;height:.1rem;width:100%}.slider::-ms-track{background:#eef0f3;border-radius:.1rem;height:.1rem;width:100%}.slider::-ms-fill-lower{background:#5755d9}.timeline .timeline-item{display:-ms-flexbox;display:flex;margin-bottom:1.2rem;position:relative}.timeline .timeline-item::before{background:#dadee4;content:"";height:100%;left:11px;position:absolute;top:1.2rem;width:2px}.timeline .timeline-item .timeline-left{-ms-flex:0 0 auto;flex:0 0 auto}.timeline .timeline-item .timeline-content{-ms-flex:1 1 auto;flex:1 1 auto;padding:2px 0 2px .8rem}.timeline .timeline-item .timeline-icon{align-items:center;border-radius:50%;color:#fff;display:-ms-flexbox;display:flex;-ms-flex-align:center;-ms-flex-pack:center;height:1.2rem;justify-content:center;text-align:center;width:1.2rem}.timeline .timeline-item .timeline-icon::before{border:.1rem solid #5755d9;border-radius:50%;content:"";display:block;height:.4rem;left:.4rem;position:absolute;top:.4rem;width:.4rem}.timeline .timeline-item .timeline-icon.icon-lg{background:#5755d9;line-height:1.2rem}.timeline .timeline-item .timeline-icon.icon-lg::before{content:none}.viewer-360{align-items:center;display:-ms-flexbox;display:flex;-ms-flex-align:center;-ms-flex-direction:column;flex-direction:column}.viewer-360 .viewer-slider[max="36"][value="1"]+.viewer-image{background-position-y:0}.viewer-360 .viewer-slider[max="36"][value="2"]+.viewer-image{background-position-y:2.8571428571%}.viewer-360 .viewer-slider[max="36"][value="3"]+.viewer-image{background-position-y:5.7142857143%}.viewer-360 .viewer-slider[max="36"][value="4"]+.viewer-image{background-position-y:8.5714285714%}.viewer-360 .viewer-slider[max="36"][value="5"]+.viewer-image{background-position-y:11.4285714286%}.viewer-360 .viewer-slider[max="36"][value="6"]+.viewer-image{background-position-y:14.2857142857%}.viewer-360 .viewer-slider[max="36"][value="7"]+.viewer-image{background-position-y:17.1428571429%}.viewer-360 .viewer-slider[max="36"][value="8"]+.viewer-image{background-position-y:20%}.viewer-360 .viewer-slider[max="36"][value="9"]+.viewer-image{background-position-y:22.8571428571%}.viewer-360 .viewer-slider[max="36"][value="10"]+.viewer-image{background-position-y:25.7142857143%}.viewer-360 .viewer-slider[max="36"][value="11"]+.viewer-image{background-position-y:28.5714285714%}.viewer-360 .viewer-slider[max="36"][value="12"]+.viewer-image{background-position-y:31.4285714286%}.viewer-360 .viewer-slider[max="36"][value="13"]+.viewer-image{background-position-y:34.2857142857%}.viewer-360 .viewer-slider[max="36"][value="14"]+.viewer-image{background-position-y:37.1428571429%}.viewer-360 .viewer-slider[max="36"][value="15"]+.viewer-image{background-position-y:40%}.viewer-360 .viewer-slider[max="36"][value="16"]+.viewer-image{background-position-y:42.8571428571%}.viewer-360 .viewer-slider[max="36"][value="17"]+.viewer-image{background-position-y:45.7142857143%}.viewer-360 .viewer-slider[max="36"][value="18"]+.viewer-image{background-position-y:48.5714285714%}.viewer-360 .viewer-slider[max="36"][value="19"]+.viewer-image{background-position-y:51.4285714286%}.viewer-360 .viewer-slider[max="36"][value="20"]+.viewer-image{background-position-y:54.2857142857%}.viewer-360 .viewer-slider[max="36"][value="21"]+.viewer-image{background-position-y:57.1428571429%}.viewer-360 .viewer-slider[max="36"][value="22"]+.viewer-image{background-position-y:60%}.viewer-360 .viewer-slider[max="36"][value="23"]+.viewer-image{background-position-y:62.8571428571%}.viewer-360 .viewer-slider[max="36"][value="24"]+.viewer-image{background-position-y:65.7142857143%}.viewer-360 .viewer-slider[max="36"][value="25"]+.viewer-image{background-position-y:68.5714285714%}.viewer-360 .viewer-slider[max="36"][value="26"]+.viewer-image{background-position-y:71.4285714286%}.viewer-360 .viewer-slider[max="36"][value="27"]+.viewer-image{background-position-y:74.2857142857%}.viewer-360 .viewer-slider[max="36"][value="28"]+.viewer-image{background-position-y:77.1428571429%}.viewer-360 .viewer-slider[max="36"][value="29"]+.viewer-image{background-position-y:80%}.viewer-360 .viewer-slider[max="36"][value="30"]+.viewer-image{background-position-y:82.8571428571%}.viewer-360 .viewer-slider[max="36"][value="31"]+.viewer-image{background-position-y:85.7142857143%}.viewer-360 .viewer-slider[max="36"][value="32"]+.viewer-image{background-position-y:88.5714285714%}.viewer-360 .viewer-slider[max="36"][value="33"]+.viewer-image{background-position-y:91.4285714286%}.viewer-360 .viewer-slider[max="36"][value="34"]+.viewer-image{background-position-y:94.2857142857%}.viewer-360 .viewer-slider[max="36"][value="35"]+.viewer-image{background-position-y:97.1428571429%}.viewer-360 .viewer-slider[max="36"][value="36"]+.viewer-image{background-position-y:100%}.viewer-360 .viewer-slider{cursor:ew-resize;-ms-flex-order:2;margin:1rem;order:2;width:60%}.viewer-360 .viewer-image{background-position-y:0;background-repeat:no-repeat;background-size:100%;-ms-flex-order:1;max-width:100%;order:1} \ No newline at end of file diff --git a/css/spectre-icons.min.css b/css/spectre-icons.min.css index 9b6167caa..0276f7b84 100644 --- a/css/spectre-icons.min.css +++ b/css/spectre-icons.min.css @@ -1 +1 @@ -/*! Spectre.css Icons v0.5.8 | MIT License | github.com/picturepan2/spectre */.icon{box-sizing:border-box;display:inline-block;font-size:inherit;font-style:normal;height:1em;position:relative;text-indent:-9999px;vertical-align:middle;width:1em}.icon::after,.icon::before{content:"";display:block;left:50%;position:absolute;top:50%;transform:translate(-50%,-50%)}.icon.icon-2x{font-size:1.6rem}.icon.icon-3x{font-size:2.4rem}.icon.icon-4x{font-size:3.2rem}.accordion .icon,.btn .icon,.menu .icon,.toast .icon{vertical-align:-10%}.btn-lg .icon{vertical-align:-15%}.icon-arrow-down::before,.icon-arrow-left::before,.icon-arrow-right::before,.icon-arrow-up::before,.icon-back::before,.icon-downward::before,.icon-forward::before,.icon-upward::before{border:.1rem solid currentColor;border-bottom:0;border-right:0;height:.65em;width:.65em}.icon-arrow-down::before{transform:translate(-50%,-75%) rotate(225deg)}.icon-arrow-left::before{transform:translate(-25%,-50%) rotate(-45deg)}.icon-arrow-right::before{transform:translate(-75%,-50%) rotate(135deg)}.icon-arrow-up::before{transform:translate(-50%,-25%) rotate(45deg)}.icon-back::after,.icon-forward::after{background:currentColor;height:.1rem;width:.8em}.icon-downward::after,.icon-upward::after{background:currentColor;height:.8em;width:.1rem}.icon-back::after{left:55%}.icon-back::before{transform:translate(-50%,-50%) rotate(-45deg)}.icon-downward::after{top:45%}.icon-downward::before{transform:translate(-50%,-50%) rotate(-135deg)}.icon-forward::after{left:45%}.icon-forward::before{transform:translate(-50%,-50%) rotate(135deg)}.icon-upward::after{top:55%}.icon-upward::before{transform:translate(-50%,-50%) rotate(45deg)}.icon-caret::before{border-left:.3em solid transparent;border-right:.3em solid transparent;border-top:.3em solid currentColor;height:0;transform:translate(-50%,-25%);width:0}.icon-menu::before{background:currentColor;box-shadow:0 -.35em,0 .35em;height:.1rem;width:100%}.icon-apps::before{background:currentColor;box-shadow:-.35em -.35em,-.35em 0,-.35em .35em,0 -.35em,0 .35em,.35em -.35em,.35em 0,.35em .35em;height:3px;width:3px}.icon-resize-horiz::after,.icon-resize-horiz::before,.icon-resize-vert::after,.icon-resize-vert::before{border:.1rem solid currentColor;border-bottom:0;border-right:0;height:.45em;width:.45em}.icon-resize-horiz::before,.icon-resize-vert::before{transform:translate(-50%,-90%) rotate(45deg)}.icon-resize-horiz::after,.icon-resize-vert::after{transform:translate(-50%,-10%) rotate(225deg)}.icon-resize-horiz::before{transform:translate(-90%,-50%) rotate(-45deg)}.icon-resize-horiz::after{transform:translate(-10%,-50%) rotate(135deg)}.icon-more-horiz::before,.icon-more-vert::before{background:currentColor;border-radius:50%;box-shadow:-.4em 0,.4em 0;height:3px;width:3px}.icon-more-vert::before{box-shadow:0 -.4em,0 .4em}.icon-cross::before,.icon-minus::before,.icon-plus::before{background:currentColor;height:.1rem;width:100%}.icon-cross::after,.icon-plus::after{background:currentColor;height:100%;width:.1rem}.icon-cross::before{width:100%}.icon-cross::after{height:100%}.icon-cross::after,.icon-cross::before{transform:translate(-50%,-50%) rotate(45deg)}.icon-check::before{border:.1rem solid currentColor;border-right:0;border-top:0;height:.5em;transform:translate(-50%,-75%) rotate(-45deg);width:.9em}.icon-stop{border:.1rem solid currentColor;border-radius:50%}.icon-stop::before{background:currentColor;height:.1rem;transform:translate(-50%,-50%) rotate(45deg);width:1em}.icon-shutdown{border:.1rem solid currentColor;border-radius:50%;border-top-color:transparent}.icon-shutdown::before{background:currentColor;content:"";height:.5em;top:.1em;width:.1rem}.icon-refresh::before{border:.1rem solid currentColor;border-radius:50%;border-right-color:transparent;height:1em;width:1em}.icon-refresh::after{border:.2em solid currentColor;border-left-color:transparent;border-top-color:transparent;height:0;left:80%;top:20%;width:0}.icon-search::before{border:.1rem solid currentColor;border-radius:50%;height:.75em;left:5%;top:5%;transform:translate(0,0) rotate(45deg);width:.75em}.icon-search::after{background:currentColor;height:.1rem;left:80%;top:80%;transform:translate(-50%,-50%) rotate(45deg);width:.4em}.icon-edit::before{border:.1rem solid currentColor;height:.4em;transform:translate(-40%,-60%) rotate(-45deg);width:.85em}.icon-edit::after{border:.15em solid currentColor;border-right-color:transparent;border-top-color:transparent;height:0;left:5%;top:95%;transform:translate(0,-100%);width:0}.icon-delete::before{border:.1rem solid currentColor;border-bottom-left-radius:.1rem;border-bottom-right-radius:.1rem;border-top:0;height:.75em;top:60%;width:.75em}.icon-delete::after{background:currentColor;box-shadow:-.25em .2em,.25em .2em;height:.1rem;top:.05rem;width:.5em}.icon-share{border:.1rem solid currentColor;border-radius:.1rem;border-right:0;border-top:0}.icon-share::before{border:.1rem solid currentColor;border-left:0;border-top:0;height:.4em;left:100%;top:.25em;transform:translate(-125%,-50%) rotate(-45deg);width:.4em}.icon-share::after{border:.1rem solid currentColor;border-bottom:0;border-radius:75% 0;border-right:0;height:.5em;width:.6em}.icon-flag::before{background:currentColor;height:1em;left:15%;width:.1rem}.icon-flag::after{border:.1rem solid currentColor;border-bottom-right-radius:.1rem;border-left:0;border-top-right-radius:.1rem;height:.65em;left:60%;top:35%;width:.8em}.icon-bookmark::before{border:.1rem solid currentColor;border-bottom:0;border-top-left-radius:.1rem;border-top-right-radius:.1rem;height:.9em;width:.8em}.icon-bookmark::after{border:.1rem solid currentColor;border-bottom:0;border-left:0;border-radius:.1rem;height:.5em;transform:translate(-50%,35%) rotate(-45deg) skew(15deg,15deg);width:.5em}.icon-download,.icon-upload{border-bottom:.1rem solid currentColor}.icon-download::before,.icon-upload::before{border:.1rem solid currentColor;border-bottom:0;border-right:0;height:.5em;transform:translate(-50%,-60%) rotate(-135deg);width:.5em}.icon-download::after,.icon-upload::after{background:currentColor;height:.6em;top:40%;width:.1rem}.icon-upload::before{transform:translate(-50%,-60%) rotate(45deg)}.icon-upload::after{top:50%}.icon-copy::before{border:.1rem solid currentColor;border-bottom:0;border-radius:.1rem;border-right:0;height:.8em;left:40%;top:35%;width:.8em}.icon-copy::after{border:.1rem solid currentColor;border-radius:.1rem;height:.8em;left:60%;top:60%;width:.8em}.icon-time{border:.1rem solid currentColor;border-radius:50%}.icon-time::before{background:currentColor;height:.4em;transform:translate(-50%,-75%);width:.1rem}.icon-time::after{background:currentColor;height:.3em;transform:translate(-50%,-75%) rotate(90deg);transform-origin:50% 90%;width:.1rem}.icon-mail::before{border:.1rem solid currentColor;border-radius:.1rem;height:.8em;width:1em}.icon-mail::after{border:.1rem solid currentColor;border-right:0;border-top:0;height:.5em;transform:translate(-50%,-90%) rotate(-45deg) skew(10deg,10deg);width:.5em}.icon-people::before{border:.1rem solid currentColor;border-radius:50%;height:.45em;top:25%;width:.45em}.icon-people::after{border:.1rem solid currentColor;border-radius:50% 50% 0 0;height:.4em;top:75%;width:.9em}.icon-message{border:.1rem solid currentColor;border-bottom:0;border-radius:.1rem;border-right:0}.icon-message::before{border:.1rem solid currentColor;border-bottom-right-radius:.1rem;border-left:0;border-top:0;height:.8em;left:65%;top:40%;width:.7em}.icon-message::after{background:currentColor;border-radius:.1rem;height:.3em;left:10%;top:100%;transform:translate(0,-90%) rotate(45deg);width:.1rem}.icon-photo{border:.1rem solid currentColor;border-radius:.1rem}.icon-photo::before{border:.1rem solid currentColor;border-radius:50%;height:.25em;left:35%;top:35%;width:.25em}.icon-photo::after{border:.1rem solid currentColor;border-bottom:0;border-left:0;height:.5em;left:60%;transform:translate(-50%,25%) rotate(-45deg);width:.5em}.icon-link::after,.icon-link::before{border:.1rem solid currentColor;border-radius:5em 0 0 5em;border-right:0;height:.5em;width:.75em}.icon-link::before{transform:translate(-70%,-45%) rotate(-45deg)}.icon-link::after{transform:translate(-30%,-55%) rotate(135deg)}.icon-location::before{border:.1rem solid currentColor;border-radius:50% 50% 50% 0;height:.8em;transform:translate(-50%,-60%) rotate(-45deg);width:.8em}.icon-location::after{border:.1rem solid currentColor;border-radius:50%;height:.2em;transform:translate(-50%,-80%);width:.2em}.icon-emoji{border:.1rem solid currentColor;border-radius:50%}.icon-emoji::before{border-radius:50%;box-shadow:-.17em -.1em,.17em -.1em;height:.15em;width:.15em}.icon-emoji::after{border:.1rem solid currentColor;border-bottom-color:transparent;border-radius:50%;border-right-color:transparent;height:.5em;transform:translate(-50%,-40%) rotate(-135deg);width:.5em} \ No newline at end of file +/*! Spectre.css Icons v0.5.9 | MIT License | github.com/picturepan2/spectre */.icon{box-sizing:border-box;display:inline-block;font-size:inherit;font-style:normal;height:1em;position:relative;text-indent:-9999px;vertical-align:middle;width:1em}.icon::after,.icon::before{content:"";display:block;left:50%;position:absolute;top:50%;transform:translate(-50%,-50%)}.icon.icon-2x{font-size:1.6rem}.icon.icon-3x{font-size:2.4rem}.icon.icon-4x{font-size:3.2rem}.accordion .icon,.btn .icon,.menu .icon,.toast .icon{vertical-align:-10%}.btn-lg .icon{vertical-align:-15%}.icon-arrow-down::before,.icon-arrow-left::before,.icon-arrow-right::before,.icon-arrow-up::before,.icon-back::before,.icon-downward::before,.icon-forward::before,.icon-upward::before{border:.1rem solid currentColor;border-bottom:0;border-right:0;height:.65em;width:.65em}.icon-arrow-down::before{transform:translate(-50%,-75%) rotate(225deg)}.icon-arrow-left::before{transform:translate(-25%,-50%) rotate(-45deg)}.icon-arrow-right::before{transform:translate(-75%,-50%) rotate(135deg)}.icon-arrow-up::before{transform:translate(-50%,-25%) rotate(45deg)}.icon-back::after,.icon-forward::after{background:currentColor;height:.1rem;width:.8em}.icon-downward::after,.icon-upward::after{background:currentColor;height:.8em;width:.1rem}.icon-back::after{left:55%}.icon-back::before{transform:translate(-50%,-50%) rotate(-45deg)}.icon-downward::after{top:45%}.icon-downward::before{transform:translate(-50%,-50%) rotate(-135deg)}.icon-forward::after{left:45%}.icon-forward::before{transform:translate(-50%,-50%) rotate(135deg)}.icon-upward::after{top:55%}.icon-upward::before{transform:translate(-50%,-50%) rotate(45deg)}.icon-caret::before{border-left:.3em solid transparent;border-right:.3em solid transparent;border-top:.3em solid currentColor;height:0;transform:translate(-50%,-25%);width:0}.icon-menu::before{background:currentColor;box-shadow:0 -.35em,0 .35em;height:.1rem;width:100%}.icon-apps::before{background:currentColor;box-shadow:-.35em -.35em,-.35em 0,-.35em .35em,0 -.35em,0 .35em,.35em -.35em,.35em 0,.35em .35em;height:3px;width:3px}.icon-resize-horiz::after,.icon-resize-horiz::before,.icon-resize-vert::after,.icon-resize-vert::before{border:.1rem solid currentColor;border-bottom:0;border-right:0;height:.45em;width:.45em}.icon-resize-horiz::before,.icon-resize-vert::before{transform:translate(-50%,-90%) rotate(45deg)}.icon-resize-horiz::after,.icon-resize-vert::after{transform:translate(-50%,-10%) rotate(225deg)}.icon-resize-horiz::before{transform:translate(-90%,-50%) rotate(-45deg)}.icon-resize-horiz::after{transform:translate(-10%,-50%) rotate(135deg)}.icon-more-horiz::before,.icon-more-vert::before{background:currentColor;border-radius:50%;box-shadow:-.4em 0,.4em 0;height:3px;width:3px}.icon-more-vert::before{box-shadow:0 -.4em,0 .4em}.icon-cross::before,.icon-minus::before,.icon-plus::before{background:currentColor;height:.1rem;width:100%}.icon-cross::after,.icon-plus::after{background:currentColor;height:100%;width:.1rem}.icon-cross::before{width:100%}.icon-cross::after{height:100%}.icon-cross::after,.icon-cross::before{transform:translate(-50%,-50%) rotate(45deg)}.icon-check::before{border:.1rem solid currentColor;border-right:0;border-top:0;height:.5em;transform:translate(-50%,-75%) rotate(-45deg);width:.9em}.icon-stop{border:.1rem solid currentColor;border-radius:50%}.icon-stop::before{background:currentColor;height:.1rem;transform:translate(-50%,-50%) rotate(45deg);width:1em}.icon-shutdown{border:.1rem solid currentColor;border-radius:50%;border-top-color:transparent}.icon-shutdown::before{background:currentColor;content:"";height:.5em;top:.1em;width:.1rem}.icon-refresh::before{border:.1rem solid currentColor;border-radius:50%;border-right-color:transparent;height:1em;width:1em}.icon-refresh::after{border:.2em solid currentColor;border-left-color:transparent;border-top-color:transparent;height:0;left:80%;top:20%;width:0}.icon-search::before{border:.1rem solid currentColor;border-radius:50%;height:.75em;left:5%;top:5%;transform:translate(0,0) rotate(45deg);width:.75em}.icon-search::after{background:currentColor;height:.1rem;left:80%;top:80%;transform:translate(-50%,-50%) rotate(45deg);width:.4em}.icon-edit::before{border:.1rem solid currentColor;height:.4em;transform:translate(-40%,-60%) rotate(-45deg);width:.85em}.icon-edit::after{border:.15em solid currentColor;border-right-color:transparent;border-top-color:transparent;height:0;left:5%;top:95%;transform:translate(0,-100%);width:0}.icon-delete::before{border:.1rem solid currentColor;border-bottom-left-radius:.1rem;border-bottom-right-radius:.1rem;border-top:0;height:.75em;top:60%;width:.75em}.icon-delete::after{background:currentColor;box-shadow:-.25em .2em,.25em .2em;height:.1rem;top:.05rem;width:.5em}.icon-share{border:.1rem solid currentColor;border-radius:.1rem;border-right:0;border-top:0}.icon-share::before{border:.1rem solid currentColor;border-left:0;border-top:0;height:.4em;left:100%;top:.25em;transform:translate(-125%,-50%) rotate(-45deg);width:.4em}.icon-share::after{border:.1rem solid currentColor;border-bottom:0;border-radius:75% 0;border-right:0;height:.5em;width:.6em}.icon-flag::before{background:currentColor;height:1em;left:15%;width:.1rem}.icon-flag::after{border:.1rem solid currentColor;border-bottom-right-radius:.1rem;border-left:0;border-top-right-radius:.1rem;height:.65em;left:60%;top:35%;width:.8em}.icon-bookmark::before{border:.1rem solid currentColor;border-bottom:0;border-top-left-radius:.1rem;border-top-right-radius:.1rem;height:.9em;width:.8em}.icon-bookmark::after{border:.1rem solid currentColor;border-bottom:0;border-left:0;border-radius:.1rem;height:.5em;transform:translate(-50%,35%) rotate(-45deg) skew(15deg,15deg);width:.5em}.icon-download,.icon-upload{border-bottom:.1rem solid currentColor}.icon-download::before,.icon-upload::before{border:.1rem solid currentColor;border-bottom:0;border-right:0;height:.5em;transform:translate(-50%,-60%) rotate(-135deg);width:.5em}.icon-download::after,.icon-upload::after{background:currentColor;height:.6em;top:40%;width:.1rem}.icon-upload::before{transform:translate(-50%,-60%) rotate(45deg)}.icon-upload::after{top:50%}.icon-copy::before{border:.1rem solid currentColor;border-bottom:0;border-radius:.1rem;border-right:0;height:.8em;left:40%;top:35%;width:.8em}.icon-copy::after{border:.1rem solid currentColor;border-radius:.1rem;height:.8em;left:60%;top:60%;width:.8em}.icon-time{border:.1rem solid currentColor;border-radius:50%}.icon-time::before{background:currentColor;height:.4em;transform:translate(-50%,-75%);width:.1rem}.icon-time::after{background:currentColor;height:.3em;transform:translate(-50%,-75%) rotate(90deg);transform-origin:50% 90%;width:.1rem}.icon-mail::before{border:.1rem solid currentColor;border-radius:.1rem;height:.8em;width:1em}.icon-mail::after{border:.1rem solid currentColor;border-right:0;border-top:0;height:.5em;transform:translate(-50%,-90%) rotate(-45deg) skew(10deg,10deg);width:.5em}.icon-people::before{border:.1rem solid currentColor;border-radius:50%;height:.45em;top:25%;width:.45em}.icon-people::after{border:.1rem solid currentColor;border-radius:50% 50% 0 0;height:.4em;top:75%;width:.9em}.icon-message{border:.1rem solid currentColor;border-bottom:0;border-radius:.1rem;border-right:0}.icon-message::before{border:.1rem solid currentColor;border-bottom-right-radius:.1rem;border-left:0;border-top:0;height:.8em;left:65%;top:40%;width:.7em}.icon-message::after{background:currentColor;border-radius:.1rem;height:.3em;left:10%;top:100%;transform:translate(0,-90%) rotate(45deg);width:.1rem}.icon-photo{border:.1rem solid currentColor;border-radius:.1rem}.icon-photo::before{border:.1rem solid currentColor;border-radius:50%;height:.25em;left:35%;top:35%;width:.25em}.icon-photo::after{border:.1rem solid currentColor;border-bottom:0;border-left:0;height:.5em;left:60%;transform:translate(-50%,25%) rotate(-45deg);width:.5em}.icon-link::after,.icon-link::before{border:.1rem solid currentColor;border-radius:5em 0 0 5em;border-right:0;height:.5em;width:.75em}.icon-link::before{transform:translate(-70%,-45%) rotate(-45deg)}.icon-link::after{transform:translate(-30%,-55%) rotate(135deg)}.icon-location::before{border:.1rem solid currentColor;border-radius:50% 50% 50% 0;height:.8em;transform:translate(-50%,-60%) rotate(-45deg);width:.8em}.icon-location::after{border:.1rem solid currentColor;border-radius:50%;height:.2em;transform:translate(-50%,-80%);width:.2em}.icon-emoji{border:.1rem solid currentColor;border-radius:50%}.icon-emoji::before{border-radius:50%;box-shadow:-.17em -.1em,.17em -.1em;height:.15em;width:.15em}.icon-emoji::after{border:.1rem solid currentColor;border-bottom-color:transparent;border-radius:50%;border-right-color:transparent;height:.5em;transform:translate(-50%,-40%) rotate(-135deg);width:.5em} \ No newline at end of file diff --git a/css/spectre.min.css b/css/spectre.min.css index 8df0bf64f..0fe23d9c0 100644 --- a/css/spectre.min.css +++ b/css/spectre.min.css @@ -1 +1 @@ -/*! Spectre.css v0.5.8 | MIT License | github.com/picturepan2/spectre */html{font-family:sans-serif;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}article,aside,footer,header,nav,section{display:block}h1{font-size:2em;margin:.67em 0}figcaption,figure,main{display:block}hr{box-sizing:content-box;height:0;overflow:visible}a{background-color:transparent;-webkit-text-decoration-skip:objects}a:active,a:hover{outline-width:0}address{font-style:normal}b,strong{font-weight:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:"SF Mono","Segoe UI Mono","Roboto Mono",Menlo,Courier,monospace;font-size:1em}dfn{font-style:italic}small{font-size:80%;font-weight:400}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}audio,video{display:inline-block}audio:not([controls]){display:none;height:0}img{border-style:none}svg:not(:root){overflow:hidden}button,input,optgroup,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit;margin:0}button,input{overflow:visible}button,select{text-transform:none}[type=reset],[type=submit],button,html [type=button]{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}fieldset{border:0;margin:0;padding:0}legend{box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}progress{display:inline-block;vertical-align:baseline}textarea{overflow:auto}[type=checkbox],[type=radio]{box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-cancel-button,[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details,menu{display:block}summary{display:list-item;outline:0}canvas{display:inline-block}template{display:none}[hidden]{display:none}*,::after,::before{box-sizing:inherit}html{box-sizing:border-box;font-size:20px;line-height:1.5;-webkit-tap-highlight-color:transparent}body{background:#fff;color:#3b4351;font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",sans-serif;font-size:.8rem;overflow-x:hidden;text-rendering:optimizeLegibility}a{color:#5755d9;outline:0;text-decoration:none}a:focus{box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}a.active,a:active,a:focus,a:hover{color:#302ecd;text-decoration:underline}a:visited{color:#807fe2}h1,h2,h3,h4,h5,h6{color:inherit;font-weight:500;line-height:1.2;margin-bottom:.5em;margin-top:0}.h1,.h2,.h3,.h4,.h5,.h6{font-weight:500}.h1,h1{font-size:2rem}.h2,h2{font-size:1.6rem}.h3,h3{font-size:1.4rem}.h4,h4{font-size:1.2rem}.h5,h5{font-size:1rem}.h6,h6{font-size:.8rem}p{margin:0 0 1.2rem}a,ins,u{-webkit-text-decoration-skip:ink edges;text-decoration-skip:ink edges}abbr[title]{border-bottom:.05rem dotted;cursor:help;text-decoration:none}kbd{background:#303742;border-radius:.1rem;color:#fff;font-size:.7rem;line-height:1.25;padding:.1rem .2rem}mark{background:#ffe9b3;border-bottom:.05rem solid #ffd367;border-radius:.1rem;color:#3b4351;padding:.05rem .1rem 0}blockquote{border-left:.1rem solid #dadee4;margin-left:0;padding:.4rem .8rem}blockquote p:last-child{margin-bottom:0}ol,ul{margin:.8rem 0 .8rem .8rem;padding:0}ol ol,ol ul,ul ol,ul ul{margin:.8rem 0 .8rem .8rem}ol li,ul li{margin-top:.4rem}ul{list-style:disc inside}ul ul{list-style-type:circle}ol{list-style:decimal inside}ol ol{list-style-type:lower-alpha}dl dt{font-weight:700}dl dd{margin:.4rem 0 .8rem 0}.lang-zh,.lang-zh-hans,html:lang(zh),html:lang(zh-Hans){font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"PingFang SC","Hiragino Sans GB","Microsoft YaHei","Helvetica Neue",sans-serif}.lang-zh-hant,html:lang(zh-Hant){font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"PingFang TC","Hiragino Sans CNS","Microsoft JhengHei","Helvetica Neue",sans-serif}.lang-ja,html:lang(ja){font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"Hiragino Sans","Hiragino Kaku Gothic Pro","Yu Gothic",YuGothic,Meiryo,"Helvetica Neue",sans-serif}.lang-ko,html:lang(ko){font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"Malgun Gothic","Helvetica Neue",sans-serif}.lang-cjk ins,.lang-cjk u,:lang(ja) ins,:lang(ja) u,:lang(zh) ins,:lang(zh) u{border-bottom:.05rem solid;text-decoration:none}.lang-cjk del+del,.lang-cjk del+s,.lang-cjk ins+ins,.lang-cjk ins+u,.lang-cjk s+del,.lang-cjk s+s,.lang-cjk u+ins,.lang-cjk u+u,:lang(ja) del+del,:lang(ja) del+s,:lang(ja) ins+ins,:lang(ja) ins+u,:lang(ja) s+del,:lang(ja) s+s,:lang(ja) u+ins,:lang(ja) u+u,:lang(zh) del+del,:lang(zh) del+s,:lang(zh) ins+ins,:lang(zh) ins+u,:lang(zh) s+del,:lang(zh) s+s,:lang(zh) u+ins,:lang(zh) u+u{margin-left:.125em}.table{border-collapse:collapse;border-spacing:0;text-align:left;width:100%}.table.table-striped tbody tr:nth-of-type(odd){background:#f7f8f9}.table tbody tr.active,.table.table-striped tbody tr.active{background:#eef0f3}.table.table-hover tbody tr:hover{background:#eef0f3}.table.table-scroll{display:block;overflow-x:auto;padding-bottom:.75rem;white-space:nowrap}.table td,.table th{border-bottom:.05rem solid #dadee4;padding:.6rem .4rem}.table th{border-bottom-width:.1rem}.btn{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#fff;border:.05rem solid #5755d9;border-radius:.1rem;color:#5755d9;cursor:pointer;display:inline-block;font-size:.8rem;height:1.8rem;line-height:1.2rem;outline:0;padding:.25rem .4rem;text-align:center;text-decoration:none;transition:background .2s,border .2s,box-shadow .2s,color .2s;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;vertical-align:middle;white-space:nowrap}.btn:focus{box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.btn:focus,.btn:hover{background:#f1f1fc;border-color:#4b48d6;text-decoration:none}.btn.active,.btn:active{background:#4b48d6;border-color:#3634d2;color:#fff;text-decoration:none}.btn.active.loading::after,.btn:active.loading::after{border-bottom-color:#fff;border-left-color:#fff}.btn.disabled,.btn:disabled,.btn[disabled]{cursor:default;opacity:.5;pointer-events:none}.btn.btn-primary{background:#5755d9;border-color:#4b48d6;color:#fff}.btn.btn-primary:focus,.btn.btn-primary:hover{background:#4240d4;border-color:#3634d2;color:#fff}.btn.btn-primary.active,.btn.btn-primary:active{background:#3a38d2;border-color:#302ecd;color:#fff}.btn.btn-primary.loading::after{border-bottom-color:#fff;border-left-color:#fff}.btn.btn-success{background:#32b643;border-color:#2faa3f;color:#fff}.btn.btn-success:focus{box-shadow:0 0 0 .1rem rgba(50,182,67,.2)}.btn.btn-success:focus,.btn.btn-success:hover{background:#30ae40;border-color:#2da23c;color:#fff}.btn.btn-success.active,.btn.btn-success:active{background:#2a9a39;border-color:#278e34;color:#fff}.btn.btn-success.loading::after{border-bottom-color:#fff;border-left-color:#fff}.btn.btn-error{background:#e85600;border-color:#d95000;color:#fff}.btn.btn-error:focus{box-shadow:0 0 0 .1rem rgba(232,86,0,.2)}.btn.btn-error:focus,.btn.btn-error:hover{background:#de5200;border-color:#cf4d00;color:#fff}.btn.btn-error.active,.btn.btn-error:active{background:#c44900;border-color:#b54300;color:#fff}.btn.btn-error.loading::after{border-bottom-color:#fff;border-left-color:#fff}.btn.btn-link{background:0 0;border-color:transparent;color:#5755d9}.btn.btn-link.active,.btn.btn-link:active,.btn.btn-link:focus,.btn.btn-link:hover{color:#302ecd}.btn.btn-sm{font-size:.7rem;height:1.4rem;padding:.05rem .3rem}.btn.btn-lg{font-size:.9rem;height:2rem;padding:.35rem .6rem}.btn.btn-block{display:block;width:100%}.btn.btn-action{padding-left:0;padding-right:0;width:1.8rem}.btn.btn-action.btn-sm{width:1.4rem}.btn.btn-action.btn-lg{width:2rem}.btn.btn-clear{background:0 0;border:0;color:currentColor;height:1rem;line-height:.8rem;margin-left:.2rem;margin-right:-2px;opacity:1;padding:.1rem;text-decoration:none;width:1rem}.btn.btn-clear:focus,.btn.btn-clear:hover{background:rgba(247,248,249,.5);opacity:.95}.btn.btn-clear::before{content:"\2715"}.btn-group{display:inline-flex;display:-ms-inline-flexbox;-ms-flex-wrap:wrap;flex-wrap:wrap}.btn-group .btn{-ms-flex:1 0 auto;flex:1 0 auto}.btn-group .btn:first-child:not(:last-child){border-bottom-right-radius:0;border-top-right-radius:0}.btn-group .btn:not(:first-child):not(:last-child){border-radius:0;margin-left:-.05rem}.btn-group .btn:last-child:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0;margin-left:-.05rem}.btn-group .btn.active,.btn-group .btn:active,.btn-group .btn:focus,.btn-group .btn:hover{z-index:1}.btn-group.btn-group-block{display:flex;display:-ms-flexbox}.btn-group.btn-group-block .btn{-ms-flex:1 0 0;flex:1 0 0}.form-group:not(:last-child){margin-bottom:.4rem}fieldset{margin-bottom:.8rem}legend{font-size:.9rem;font-weight:500;margin-bottom:.8rem}.form-label{display:block;line-height:1.2rem;padding:.3rem 0}.form-label.label-sm{font-size:.7rem;padding:.1rem 0}.form-label.label-lg{font-size:.9rem;padding:.4rem 0}.form-input{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#fff;background-image:none;border:.05rem solid #bcc3ce;border-radius:.1rem;color:#3b4351;display:block;font-size:.8rem;height:1.8rem;line-height:1.2rem;max-width:100%;outline:0;padding:.25rem .4rem;position:relative;transition:background .2s,border .2s,box-shadow .2s,color .2s;width:100%}.form-input:focus{border-color:#5755d9;box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.form-input:-ms-input-placeholder{color:#bcc3ce}.form-input::-ms-input-placeholder{color:#bcc3ce}.form-input::placeholder{color:#bcc3ce}.form-input.input-sm{font-size:.7rem;height:1.4rem;padding:.05rem .3rem}.form-input.input-lg{font-size:.9rem;height:2rem;padding:.35rem .6rem}.form-input.input-inline{display:inline-block;vertical-align:middle;width:auto}.form-input[type=file]{height:auto}textarea.form-input,textarea.form-input.input-lg,textarea.form-input.input-sm{height:auto}.form-input-hint{color:#bcc3ce;font-size:.7rem;margin-top:.2rem}.has-success .form-input-hint,.is-success+.form-input-hint{color:#32b643}.has-error .form-input-hint,.is-error+.form-input-hint{color:#e85600}.form-select{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#fff;border:.05rem solid #bcc3ce;border-radius:.1rem;color:inherit;font-size:.8rem;height:1.8rem;line-height:1.2rem;outline:0;padding:.25rem .4rem;vertical-align:middle;width:100%}.form-select:focus{border-color:#5755d9;box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.form-select::-ms-expand{display:none}.form-select.select-sm{font-size:.7rem;height:1.4rem;padding:.05rem 1.1rem .05rem .3rem}.form-select.select-lg{font-size:.9rem;height:2rem;padding:.35rem 1.4rem .35rem .6rem}.form-select[multiple],.form-select[size]{height:auto;padding:.25rem .4rem}.form-select[multiple] option,.form-select[size] option{padding:.1rem .2rem}.form-select:not([multiple]):not([size]){background:#fff url("data:image/svg+xml;charset=utf8,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%204%205'%3E%3Cpath%20fill='%23667189'%20d='M2%200L0%202h4zm0%205L0%203h4z'/%3E%3C/svg%3E") no-repeat right .35rem center/.4rem .5rem;padding-right:1.2rem}.has-icon-left,.has-icon-right{position:relative}.has-icon-left .form-icon,.has-icon-right .form-icon{height:.8rem;margin:0 .25rem;position:absolute;top:50%;transform:translateY(-50%);width:.8rem;z-index:2}.has-icon-left .form-icon{left:.05rem}.has-icon-left .form-input{padding-left:1.3rem}.has-icon-right .form-icon{right:.05rem}.has-icon-right .form-input{padding-right:1.3rem}.form-checkbox,.form-radio,.form-switch{display:block;line-height:1.2rem;margin:.2rem 0;min-height:1.4rem;padding:.1rem .4rem .1rem 1.2rem;position:relative}.form-checkbox input,.form-radio input,.form-switch input{clip:rect(0,0,0,0);height:1px;margin:-1px;overflow:hidden;position:absolute;width:1px}.form-checkbox input:focus+.form-icon,.form-radio input:focus+.form-icon,.form-switch input:focus+.form-icon{border-color:#5755d9;box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.form-checkbox input:checked+.form-icon,.form-radio input:checked+.form-icon,.form-switch input:checked+.form-icon{background:#5755d9;border-color:#5755d9}.form-checkbox .form-icon,.form-radio .form-icon,.form-switch .form-icon{border:.05rem solid #bcc3ce;cursor:pointer;display:inline-block;position:absolute;transition:background .2s,border .2s,box-shadow .2s,color .2s}.form-checkbox.input-sm,.form-radio.input-sm,.form-switch.input-sm{font-size:.7rem;margin:0}.form-checkbox.input-lg,.form-radio.input-lg,.form-switch.input-lg{font-size:.9rem;margin:.3rem 0}.form-checkbox .form-icon,.form-radio .form-icon{background:#fff;height:.8rem;left:0;top:.3rem;width:.8rem}.form-checkbox input:active+.form-icon,.form-radio input:active+.form-icon{background:#eef0f3}.form-checkbox .form-icon{border-radius:.1rem}.form-checkbox input:checked+.form-icon::before{background-clip:padding-box;border:.1rem solid #fff;border-left-width:0;border-top-width:0;content:"";height:9px;left:50%;margin-left:-3px;margin-top:-6px;position:absolute;top:50%;transform:rotate(45deg);width:6px}.form-checkbox input:indeterminate+.form-icon{background:#5755d9;border-color:#5755d9}.form-checkbox input:indeterminate+.form-icon::before{background:#fff;content:"";height:2px;left:50%;margin-left:-5px;margin-top:-1px;position:absolute;top:50%;width:10px}.form-radio .form-icon{border-radius:50%}.form-radio input:checked+.form-icon::before{background:#fff;border-radius:50%;content:"";height:6px;left:50%;position:absolute;top:50%;transform:translate(-50%,-50%);width:6px}.form-switch{padding-left:2rem}.form-switch .form-icon{background:#bcc3ce;background-clip:padding-box;border-radius:.45rem;height:.9rem;left:0;top:.25rem;width:1.6rem}.form-switch .form-icon::before{background:#fff;border-radius:50%;content:"";display:block;height:.8rem;left:0;position:absolute;top:0;transition:background .2s,border .2s,box-shadow .2s,color .2s,left .2s;width:.8rem}.form-switch input:checked+.form-icon::before{left:14px}.form-switch input:active+.form-icon::before{background:#f7f8f9}.input-group{display:flex;display:-ms-flexbox}.input-group .input-group-addon{background:#f7f8f9;border:.05rem solid #bcc3ce;border-radius:.1rem;line-height:1.2rem;padding:.25rem .4rem;white-space:nowrap}.input-group .input-group-addon.addon-sm{font-size:.7rem;padding:.05rem .3rem}.input-group .input-group-addon.addon-lg{font-size:.9rem;padding:.35rem .6rem}.input-group .form-input,.input-group .form-select{-ms-flex:1 1 auto;flex:1 1 auto;width:1%}.input-group .input-group-btn{z-index:1}.input-group .form-input:first-child:not(:last-child),.input-group .form-select:first-child:not(:last-child),.input-group .input-group-addon:first-child:not(:last-child),.input-group .input-group-btn:first-child:not(:last-child){border-bottom-right-radius:0;border-top-right-radius:0}.input-group .form-input:not(:first-child):not(:last-child),.input-group .form-select:not(:first-child):not(:last-child),.input-group .input-group-addon:not(:first-child):not(:last-child),.input-group .input-group-btn:not(:first-child):not(:last-child){border-radius:0;margin-left:-.05rem}.input-group .form-input:last-child:not(:first-child),.input-group .form-select:last-child:not(:first-child),.input-group .input-group-addon:last-child:not(:first-child),.input-group .input-group-btn:last-child:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0;margin-left:-.05rem}.input-group .form-input:focus,.input-group .form-select:focus,.input-group .input-group-addon:focus,.input-group .input-group-btn:focus{z-index:2}.input-group .form-select{width:auto}.input-group.input-inline{display:inline-flex;display:-ms-inline-flexbox}.form-input.is-success,.form-select.is-success,.has-success .form-input,.has-success .form-select{background:#f9fdfa;border-color:#32b643}.form-input.is-success:focus,.form-select.is-success:focus,.has-success .form-input:focus,.has-success .form-select:focus{box-shadow:0 0 0 .1rem rgba(50,182,67,.2)}.form-input.is-error,.form-select.is-error,.has-error .form-input,.has-error .form-select{background:#fffaf7;border-color:#e85600}.form-input.is-error:focus,.form-select.is-error:focus,.has-error .form-input:focus,.has-error .form-select:focus{box-shadow:0 0 0 .1rem rgba(232,86,0,.2)}.form-checkbox.is-error .form-icon,.form-radio.is-error .form-icon,.form-switch.is-error .form-icon,.has-error .form-checkbox .form-icon,.has-error .form-radio .form-icon,.has-error .form-switch .form-icon{border-color:#e85600}.form-checkbox.is-error input:checked+.form-icon,.form-radio.is-error input:checked+.form-icon,.form-switch.is-error input:checked+.form-icon,.has-error .form-checkbox input:checked+.form-icon,.has-error .form-radio input:checked+.form-icon,.has-error .form-switch input:checked+.form-icon{background:#e85600;border-color:#e85600}.form-checkbox.is-error input:focus+.form-icon,.form-radio.is-error input:focus+.form-icon,.form-switch.is-error input:focus+.form-icon,.has-error .form-checkbox input:focus+.form-icon,.has-error .form-radio input:focus+.form-icon,.has-error .form-switch input:focus+.form-icon{border-color:#e85600;box-shadow:0 0 0 .1rem rgba(232,86,0,.2)}.form-checkbox.is-error input:indeterminate+.form-icon,.has-error .form-checkbox input:indeterminate+.form-icon{background:#e85600;border-color:#e85600}.form-input:not(:placeholder-shown):invalid{border-color:#e85600}.form-input:not(:placeholder-shown):invalid:focus{background:#fffaf7;box-shadow:0 0 0 .1rem rgba(232,86,0,.2)}.form-input:not(:placeholder-shown):invalid+.form-input-hint{color:#e85600}.form-input.disabled,.form-input:disabled,.form-select.disabled,.form-select:disabled{background-color:#eef0f3;cursor:not-allowed;opacity:.5}.form-input[readonly]{background-color:#f7f8f9}input.disabled+.form-icon,input:disabled+.form-icon{background:#eef0f3;cursor:not-allowed;opacity:.5}.form-switch input.disabled+.form-icon::before,.form-switch input:disabled+.form-icon::before{background:#fff}.form-horizontal{padding:.4rem 0}.form-horizontal .form-group{display:flex;display:-ms-flexbox;-ms-flex-wrap:wrap;flex-wrap:wrap}.form-inline{display:inline-block}.label{background:#eef0f3;border-radius:.1rem;color:#455060;display:inline-block;line-height:1.25;padding:.1rem .2rem}.label.label-rounded{border-radius:5rem;padding-left:.4rem;padding-right:.4rem}.label.label-primary{background:#5755d9;color:#fff}.label.label-secondary{background:#f1f1fc;color:#5755d9}.label.label-success{background:#32b643;color:#fff}.label.label-warning{background:#ffb700;color:#fff}.label.label-error{background:#e85600;color:#fff}code{background:#fcf2f2;border-radius:.1rem;color:#d73e48;font-size:85%;line-height:1.25;padding:.1rem .2rem}.code{border-radius:.1rem;color:#3b4351;position:relative}.code::before{color:#bcc3ce;content:attr(data-lang);font-size:.7rem;position:absolute;right:.4rem;top:.1rem}.code code{background:#f7f8f9;color:inherit;display:block;line-height:1.5;overflow-x:auto;padding:1rem;width:100%}.img-responsive{display:block;height:auto;max-width:100%}.img-fit-cover{object-fit:cover}.img-fit-contain{object-fit:contain}.video-responsive{display:block;overflow:hidden;padding:0;position:relative;width:100%}.video-responsive::before{content:"";display:block;padding-bottom:56.25%}.video-responsive embed,.video-responsive iframe,.video-responsive object{border:0;bottom:0;height:100%;left:0;position:absolute;right:0;top:0;width:100%}video.video-responsive{height:auto;max-width:100%}video.video-responsive::before{content:none}.video-responsive-4-3::before{padding-bottom:75%}.video-responsive-1-1::before{padding-bottom:100%}.figure{margin:0 0 .4rem 0}.figure .figure-caption{color:#66758c;margin-top:.4rem}.container{margin-left:auto;margin-right:auto;padding-left:.4rem;padding-right:.4rem;width:100%}.container.grid-xl{max-width:1296px}.container.grid-lg{max-width:976px}.container.grid-md{max-width:856px}.container.grid-sm{max-width:616px}.container.grid-xs{max-width:496px}.show-lg,.show-md,.show-sm,.show-xl,.show-xs{display:none!important}.columns{display:flex;display:-ms-flexbox;-ms-flex-wrap:wrap;flex-wrap:wrap;margin-left:-.4rem;margin-right:-.4rem}.columns.col-gapless{margin-left:0;margin-right:0}.columns.col-gapless>.column{padding-left:0;padding-right:0}.columns.col-oneline{-ms-flex-wrap:nowrap;flex-wrap:nowrap;overflow-x:auto}.column{-ms-flex:1;flex:1;max-width:100%;padding-left:.4rem;padding-right:.4rem}.column.col-1,.column.col-10,.column.col-11,.column.col-12,.column.col-2,.column.col-3,.column.col-4,.column.col-5,.column.col-6,.column.col-7,.column.col-8,.column.col-9,.column.col-auto{-ms-flex:none;flex:none}.col-12{width:100%}.col-11{width:91.66666667%}.col-10{width:83.33333333%}.col-9{width:75%}.col-8{width:66.66666667%}.col-7{width:58.33333333%}.col-6{width:50%}.col-5{width:41.66666667%}.col-4{width:33.33333333%}.col-3{width:25%}.col-2{width:16.66666667%}.col-1{width:8.33333333%}.col-auto{-ms-flex:0 0 auto;flex:0 0 auto;max-width:none;width:auto}.col-mx-auto{margin-left:auto;margin-right:auto}.col-ml-auto{margin-left:auto}.col-mr-auto{margin-right:auto}@media (max-width:1280px){.col-xl-1,.col-xl-10,.col-xl-11,.col-xl-12,.col-xl-2,.col-xl-3,.col-xl-4,.col-xl-5,.col-xl-6,.col-xl-7,.col-xl-8,.col-xl-9,.col-xl-auto{-ms-flex:none;flex:none}.col-xl-12{width:100%}.col-xl-11{width:91.66666667%}.col-xl-10{width:83.33333333%}.col-xl-9{width:75%}.col-xl-8{width:66.66666667%}.col-xl-7{width:58.33333333%}.col-xl-6{width:50%}.col-xl-5{width:41.66666667%}.col-xl-4{width:33.33333333%}.col-xl-3{width:25%}.col-xl-2{width:16.66666667%}.col-xl-1{width:8.33333333%}.col-xl-auto{width:auto}.hide-xl{display:none!important}.show-xl{display:block!important}}@media (max-width:960px){.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-lg-auto{-ms-flex:none;flex:none}.col-lg-12{width:100%}.col-lg-11{width:91.66666667%}.col-lg-10{width:83.33333333%}.col-lg-9{width:75%}.col-lg-8{width:66.66666667%}.col-lg-7{width:58.33333333%}.col-lg-6{width:50%}.col-lg-5{width:41.66666667%}.col-lg-4{width:33.33333333%}.col-lg-3{width:25%}.col-lg-2{width:16.66666667%}.col-lg-1{width:8.33333333%}.col-lg-auto{width:auto}.hide-lg{display:none!important}.show-lg{display:block!important}}@media (max-width:840px){.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-md-auto{-ms-flex:none;flex:none}.col-md-12{width:100%}.col-md-11{width:91.66666667%}.col-md-10{width:83.33333333%}.col-md-9{width:75%}.col-md-8{width:66.66666667%}.col-md-7{width:58.33333333%}.col-md-6{width:50%}.col-md-5{width:41.66666667%}.col-md-4{width:33.33333333%}.col-md-3{width:25%}.col-md-2{width:16.66666667%}.col-md-1{width:8.33333333%}.col-md-auto{width:auto}.hide-md{display:none!important}.show-md{display:block!important}}@media (max-width:600px){.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-sm-auto{-ms-flex:none;flex:none}.col-sm-12{width:100%}.col-sm-11{width:91.66666667%}.col-sm-10{width:83.33333333%}.col-sm-9{width:75%}.col-sm-8{width:66.66666667%}.col-sm-7{width:58.33333333%}.col-sm-6{width:50%}.col-sm-5{width:41.66666667%}.col-sm-4{width:33.33333333%}.col-sm-3{width:25%}.col-sm-2{width:16.66666667%}.col-sm-1{width:8.33333333%}.col-sm-auto{width:auto}.hide-sm{display:none!important}.show-sm{display:block!important}}@media (max-width:480px){.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9,.col-xs-auto{-ms-flex:none;flex:none}.col-xs-12{width:100%}.col-xs-11{width:91.66666667%}.col-xs-10{width:83.33333333%}.col-xs-9{width:75%}.col-xs-8{width:66.66666667%}.col-xs-7{width:58.33333333%}.col-xs-6{width:50%}.col-xs-5{width:41.66666667%}.col-xs-4{width:33.33333333%}.col-xs-3{width:25%}.col-xs-2{width:16.66666667%}.col-xs-1{width:8.33333333%}.col-xs-auto{width:auto}.hide-xs{display:none!important}.show-xs{display:block!important}}.hero{display:flex;display:-ms-flexbox;-ms-flex-direction:column;flex-direction:column;-ms-flex-pack:justify;justify-content:space-between;padding-bottom:4rem;padding-top:4rem}.hero.hero-sm{padding-bottom:2rem;padding-top:2rem}.hero.hero-lg{padding-bottom:8rem;padding-top:8rem}.hero .hero-body{padding:.4rem}.navbar{align-items:stretch;display:flex;display:-ms-flexbox;-ms-flex-align:stretch;-ms-flex-pack:justify;-ms-flex-wrap:wrap;flex-wrap:wrap;justify-content:space-between}.navbar .navbar-section{align-items:center;display:flex;display:-ms-flexbox;-ms-flex:1 0 0;flex:1 0 0;-ms-flex-align:center}.navbar .navbar-section:not(:first-child):last-child{-ms-flex-pack:end;justify-content:flex-end}.navbar .navbar-center{align-items:center;display:flex;display:-ms-flexbox;-ms-flex:0 0 auto;flex:0 0 auto;-ms-flex-align:center}.navbar .navbar-brand{font-size:.9rem;text-decoration:none}.accordion input:checked~.accordion-header .icon,.accordion[open] .accordion-header .icon{transform:rotate(90deg)}.accordion input:checked~.accordion-body,.accordion[open] .accordion-body{max-height:50rem}.accordion .accordion-header{display:block;padding:.2rem .4rem}.accordion .accordion-header .icon{transition:transform .25s}.accordion .accordion-body{margin-bottom:.4rem;max-height:0;overflow:hidden;transition:max-height .25s}summary.accordion-header::-webkit-details-marker{display:none}.avatar{background:#5755d9;border-radius:50%;color:rgba(255,255,255,.85);display:inline-block;font-size:.8rem;font-weight:300;height:1.6rem;line-height:1.25;margin:0;position:relative;vertical-align:middle;width:1.6rem}.avatar.avatar-xs{font-size:.4rem;height:.8rem;width:.8rem}.avatar.avatar-sm{font-size:.6rem;height:1.2rem;width:1.2rem}.avatar.avatar-lg{font-size:1.2rem;height:2.4rem;width:2.4rem}.avatar.avatar-xl{font-size:1.6rem;height:3.2rem;width:3.2rem}.avatar img{border-radius:50%;height:100%;position:relative;width:100%;z-index:1}.avatar .avatar-icon,.avatar .avatar-presence{background:#fff;bottom:14.64%;height:50%;padding:.1rem;position:absolute;right:14.64%;transform:translate(50%,50%);width:50%;z-index:2}.avatar .avatar-presence{background:#bcc3ce;border-radius:50%;box-shadow:0 0 0 .1rem #fff;height:.5em;width:.5em}.avatar .avatar-presence.online{background:#32b643}.avatar .avatar-presence.busy{background:#e85600}.avatar .avatar-presence.away{background:#ffb700}.avatar[data-initial]::before{color:currentColor;content:attr(data-initial);left:50%;position:absolute;top:50%;transform:translate(-50%,-50%);z-index:1}.badge{position:relative;white-space:nowrap}.badge:not([data-badge])::after,.badge[data-badge]::after{background:#5755d9;background-clip:padding-box;border-radius:.5rem;box-shadow:0 0 0 .1rem #fff;color:#fff;content:attr(data-badge);display:inline-block;transform:translate(-.05rem,-.5rem)}.badge[data-badge]::after{font-size:.7rem;height:.9rem;line-height:1;min-width:.9rem;padding:.1rem .2rem;text-align:center;white-space:nowrap}.badge:not([data-badge])::after,.badge[data-badge=""]::after{height:6px;min-width:6px;padding:0;width:6px}.badge.btn::after{position:absolute;right:0;top:0;transform:translate(50%,-50%)}.badge.avatar::after{position:absolute;right:14.64%;top:14.64%;transform:translate(50%,-50%);z-index:100}.breadcrumb{list-style:none;margin:.2rem 0;padding:.2rem 0}.breadcrumb .breadcrumb-item{color:#66758c;display:inline-block;margin:0;padding:.2rem 0}.breadcrumb .breadcrumb-item:not(:last-child){margin-right:.2rem}.breadcrumb .breadcrumb-item:not(:last-child) a{color:#66758c}.breadcrumb .breadcrumb-item:not(:first-child)::before{color:#66758c;content:"/";padding-right:.4rem}.bar{background:#eef0f3;border-radius:.1rem;display:flex;display:-ms-flexbox;-ms-flex-wrap:nowrap;flex-wrap:nowrap;height:.8rem;width:100%}.bar.bar-sm{height:.2rem}.bar .bar-item{background:#5755d9;color:#fff;display:block;-ms-flex-negative:0;flex-shrink:0;font-size:.7rem;height:100%;line-height:.8rem;position:relative;text-align:center;width:0}.bar .bar-item:first-child{border-bottom-left-radius:.1rem;border-top-left-radius:.1rem}.bar .bar-item:last-child{border-bottom-right-radius:.1rem;border-top-right-radius:.1rem;-ms-flex-negative:1;flex-shrink:1}.bar-slider{height:.1rem;margin:.4rem 0;position:relative}.bar-slider .bar-item{left:0;padding:0;position:absolute}.bar-slider .bar-item:not(:last-child):first-child{background:#eef0f3;z-index:1}.bar-slider .bar-slider-btn{background:#5755d9;border:0;border-radius:50%;height:.6rem;padding:0;position:absolute;right:0;top:50%;transform:translate(50%,-50%);width:.6rem}.bar-slider .bar-slider-btn:active{box-shadow:0 0 0 .1rem #5755d9}.card{background:#fff;border:.05rem solid #dadee4;border-radius:.1rem;display:flex;display:-ms-flexbox;-ms-flex-direction:column;flex-direction:column}.card .card-body,.card .card-footer,.card .card-header{padding:.8rem;padding-bottom:0}.card .card-body:last-child,.card .card-footer:last-child,.card .card-header:last-child{padding-bottom:.8rem}.card .card-body{-ms-flex:1 1 auto;flex:1 1 auto}.card .card-image{padding-top:.8rem}.card .card-image:first-child{padding-top:0}.card .card-image:first-child img{border-top-left-radius:.1rem;border-top-right-radius:.1rem}.card .card-image:last-child img{border-bottom-left-radius:.1rem;border-bottom-right-radius:.1rem}.chip{align-items:center;background:#eef0f3;border-radius:5rem;display:inline-flex;display:-ms-inline-flexbox;-ms-flex-align:center;font-size:90%;height:1.2rem;line-height:.8rem;margin:.1rem;max-width:320px;overflow:hidden;padding:.2rem .4rem;text-decoration:none;text-overflow:ellipsis;vertical-align:middle;white-space:nowrap}.chip.active{background:#5755d9;color:#fff}.chip .avatar{margin-left:-.4rem;margin-right:.2rem}.chip .btn-clear{border-radius:50%;transform:scale(.75)}.dropdown{display:inline-block;position:relative}.dropdown .menu{animation:slide-down .15s ease 1;display:none;left:0;max-height:50vh;overflow-y:auto;position:absolute;top:100%}.dropdown.dropdown-right .menu{left:auto;right:0}.dropdown .dropdown-toggle:focus+.menu,.dropdown .menu:hover,.dropdown.active .menu{display:block}.dropdown .btn-group .dropdown-toggle:nth-last-child(2){border-bottom-right-radius:.1rem;border-top-right-radius:.1rem}.empty{background:#f7f8f9;border-radius:.1rem;color:#66758c;padding:3.2rem 1.6rem;text-align:center}.empty .empty-icon{margin-bottom:.8rem}.empty .empty-subtitle,.empty .empty-title{margin:.4rem auto}.empty .empty-action{margin-top:.8rem}.menu{background:#fff;border-radius:.1rem;box-shadow:0 .05rem .2rem rgba(48,55,66,.3);list-style:none;margin:0;min-width:180px;padding:.4rem;transform:translateY(.2rem);z-index:300}.menu.menu-nav{background:0 0;box-shadow:none}.menu .menu-item{margin-top:0;padding:0 .4rem;position:relative;text-decoration:none}.menu .menu-item>a{border-radius:.1rem;color:inherit;display:block;margin:0 -.4rem;padding:.2rem .4rem;text-decoration:none}.menu .menu-item>a:focus,.menu .menu-item>a:hover{background:#f1f1fc;color:#5755d9}.menu .menu-item>a.active,.menu .menu-item>a:active{background:#f1f1fc;color:#5755d9}.menu .menu-item .form-checkbox,.menu .menu-item .form-radio,.menu .menu-item .form-switch{margin:.1rem 0}.menu .menu-item+.menu-item{margin-top:.2rem}.menu .menu-badge{align-items:center;display:flex;display:-ms-flexbox;-ms-flex-align:center;height:100%;position:absolute;right:0;top:0}.menu .menu-badge .label{margin-right:.4rem}.modal{align-items:center;bottom:0;display:none;-ms-flex-align:center;-ms-flex-pack:center;justify-content:center;left:0;opacity:0;overflow:hidden;padding:.4rem;position:fixed;right:0;top:0}.modal.active,.modal:target{display:flex;display:-ms-flexbox;opacity:1;z-index:400}.modal.active .modal-overlay,.modal:target .modal-overlay{background:rgba(247,248,249,.75);bottom:0;cursor:default;display:block;left:0;position:absolute;right:0;top:0}.modal.active .modal-container,.modal:target .modal-container{animation:slide-down .2s ease 1;z-index:1}.modal.modal-sm .modal-container{max-width:320px;padding:0 .4rem}.modal.modal-lg .modal-overlay{background:#fff}.modal.modal-lg .modal-container{box-shadow:none;max-width:960px}.modal-container{background:#fff;border-radius:.1rem;box-shadow:0 .2rem .5rem rgba(48,55,66,.3);display:flex;display:-ms-flexbox;-ms-flex-direction:column;flex-direction:column;max-height:75vh;max-width:640px;padding:0 .8rem;width:100%}.modal-container.modal-fullheight{max-height:100vh}.modal-container .modal-header{color:#303742;padding:.8rem}.modal-container .modal-body{overflow-y:auto;padding:.8rem;position:relative}.modal-container .modal-footer{padding:.8rem;text-align:right}.nav{display:flex;display:-ms-flexbox;-ms-flex-direction:column;flex-direction:column;list-style:none;margin:.2rem 0}.nav .nav-item a{color:#66758c;padding:.2rem .4rem;text-decoration:none}.nav .nav-item a:focus,.nav .nav-item a:hover{color:#5755d9}.nav .nav-item.active>a{color:#505c6e;font-weight:700}.nav .nav-item.active>a:focus,.nav .nav-item.active>a:hover{color:#5755d9}.nav .nav{margin-bottom:.4rem;margin-left:.8rem}.pagination{display:flex;display:-ms-flexbox;list-style:none;margin:.2rem 0;padding:.2rem 0}.pagination .page-item{margin:.2rem .05rem}.pagination .page-item span{display:inline-block;padding:.2rem .2rem}.pagination .page-item a{border-radius:.1rem;display:inline-block;padding:.2rem .4rem;text-decoration:none}.pagination .page-item a:focus,.pagination .page-item a:hover{color:#5755d9}.pagination .page-item.disabled a{cursor:default;opacity:.5;pointer-events:none}.pagination .page-item.active a{background:#5755d9;color:#fff}.pagination .page-item.page-next,.pagination .page-item.page-prev{-ms-flex:1 0 50%;flex:1 0 50%}.pagination .page-item.page-next{text-align:right}.pagination .page-item .page-item-title{margin:0}.pagination .page-item .page-item-subtitle{margin:0;opacity:.5}.panel{border:.05rem solid #dadee4;border-radius:.1rem;display:flex;display:-ms-flexbox;-ms-flex-direction:column;flex-direction:column}.panel .panel-footer,.panel .panel-header{-ms-flex:0 0 auto;flex:0 0 auto;padding:.8rem}.panel .panel-nav{-ms-flex:0 0 auto;flex:0 0 auto}.panel .panel-body{-ms-flex:1 1 auto;flex:1 1 auto;overflow-y:auto;padding:0 .8rem}.popover{display:inline-block;position:relative}.popover .popover-container{left:50%;opacity:0;padding:.4rem;position:absolute;top:0;transform:translate(-50%,-50%) scale(0);transition:transform .2s;width:320px;z-index:300}.popover :focus+.popover-container,.popover:hover .popover-container{display:block;opacity:1;transform:translate(-50%,-100%) scale(1)}.popover.popover-right .popover-container{left:100%;top:50%}.popover.popover-right :focus+.popover-container,.popover.popover-right:hover .popover-container{transform:translate(0,-50%) scale(1)}.popover.popover-bottom .popover-container{left:50%;top:100%}.popover.popover-bottom :focus+.popover-container,.popover.popover-bottom:hover .popover-container{transform:translate(-50%,0) scale(1)}.popover.popover-left .popover-container{left:0;top:50%}.popover.popover-left :focus+.popover-container,.popover.popover-left:hover .popover-container{transform:translate(-100%,-50%) scale(1)}.popover .card{border:0;box-shadow:0 .2rem .5rem rgba(48,55,66,.3)}.step{display:flex;display:-ms-flexbox;-ms-flex-wrap:nowrap;flex-wrap:nowrap;list-style:none;margin:.2rem 0;width:100%}.step .step-item{-ms-flex:1 1 0;flex:1 1 0;margin-top:0;min-height:1rem;position:relative;text-align:center}.step .step-item:not(:first-child)::before{background:#5755d9;content:"";height:2px;left:-50%;position:absolute;top:9px;width:100%}.step .step-item a{color:#5755d9;display:inline-block;padding:20px 10px 0;text-decoration:none}.step .step-item a::before{background:#5755d9;border:.1rem solid #fff;border-radius:50%;content:"";display:block;height:.6rem;left:50%;position:absolute;top:.2rem;transform:translateX(-50%);width:.6rem;z-index:1}.step .step-item.active a::before{background:#fff;border:.1rem solid #5755d9}.step .step-item.active~.step-item::before{background:#dadee4}.step .step-item.active~.step-item a{color:#bcc3ce}.step .step-item.active~.step-item a::before{background:#dadee4}.tab{align-items:center;border-bottom:.05rem solid #dadee4;display:flex;display:-ms-flexbox;-ms-flex-align:center;-ms-flex-wrap:wrap;flex-wrap:wrap;list-style:none;margin:.2rem 0 .15rem 0}.tab .tab-item{margin-top:0}.tab .tab-item a{border-bottom:.1rem solid transparent;color:inherit;display:block;margin:0 .4rem 0 0;padding:.4rem .2rem .3rem .2rem;text-decoration:none}.tab .tab-item a:focus,.tab .tab-item a:hover{color:#5755d9}.tab .tab-item a.active,.tab .tab-item.active a{border-bottom-color:#5755d9;color:#5755d9}.tab .tab-item.tab-action{-ms-flex:1 0 auto;flex:1 0 auto;text-align:right}.tab .tab-item .btn-clear{margin-top:-.2rem}.tab.tab-block .tab-item{-ms-flex:1 0 0;flex:1 0 0;text-align:center}.tab.tab-block .tab-item a{margin:0}.tab.tab-block .tab-item .badge[data-badge]::after{position:absolute;right:.1rem;top:.1rem;transform:translate(0,0)}.tab:not(.tab-block) .badge{padding-right:0}.tile{align-content:space-between;align-items:flex-start;display:flex;display:-ms-flexbox;-ms-flex-align:start;-ms-flex-line-pack:justify}.tile .tile-action,.tile .tile-icon{-ms-flex:0 0 auto;flex:0 0 auto}.tile .tile-content{-ms-flex:1 1 auto;flex:1 1 auto}.tile .tile-content:not(:first-child){padding-left:.4rem}.tile .tile-content:not(:last-child){padding-right:.4rem}.tile .tile-subtitle,.tile .tile-title{line-height:1.2rem}.tile.tile-centered{align-items:center;-ms-flex-align:center}.tile.tile-centered .tile-content{overflow:hidden}.tile.tile-centered .tile-subtitle,.tile.tile-centered .tile-title{margin-bottom:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.toast{background:rgba(48,55,66,.95);border:.05rem solid #303742;border-color:#303742;border-radius:.1rem;color:#fff;display:block;padding:.4rem;width:100%}.toast.toast-primary{background:rgba(87,85,217,.95);border-color:#5755d9}.toast.toast-success{background:rgba(50,182,67,.95);border-color:#32b643}.toast.toast-warning{background:rgba(255,183,0,.95);border-color:#ffb700}.toast.toast-error{background:rgba(232,86,0,.95);border-color:#e85600}.toast a{color:#fff;text-decoration:underline}.toast a.active,.toast a:active,.toast a:focus,.toast a:hover{opacity:.75}.toast .btn-clear{margin:.1rem}.toast p:last-child{margin-bottom:0}.tooltip{position:relative}.tooltip::after{background:rgba(48,55,66,.95);border-radius:.1rem;bottom:100%;color:#fff;content:attr(data-tooltip);display:block;font-size:.7rem;left:50%;max-width:320px;opacity:0;overflow:hidden;padding:.2rem .4rem;pointer-events:none;position:absolute;text-overflow:ellipsis;transform:translate(-50%,.4rem);transition:opacity .2s,transform .2s;white-space:pre;z-index:300}.tooltip:focus::after,.tooltip:hover::after{opacity:1;transform:translate(-50%,-.2rem)}.tooltip.disabled,.tooltip[disabled]{pointer-events:auto}.tooltip.tooltip-right::after{bottom:50%;left:100%;transform:translate(-.2rem,50%)}.tooltip.tooltip-right:focus::after,.tooltip.tooltip-right:hover::after{transform:translate(.2rem,50%)}.tooltip.tooltip-bottom::after{bottom:auto;top:100%;transform:translate(-50%,-.4rem)}.tooltip.tooltip-bottom:focus::after,.tooltip.tooltip-bottom:hover::after{transform:translate(-50%,.2rem)}.tooltip.tooltip-left::after{bottom:50%;left:auto;right:100%;transform:translate(.4rem,50%)}.tooltip.tooltip-left:focus::after,.tooltip.tooltip-left:hover::after{transform:translate(-.2rem,50%)}@keyframes loading{0%{transform:rotate(0)}100%{transform:rotate(360deg)}}@keyframes slide-down{0%{opacity:0;transform:translateY(-1.6rem)}100%{opacity:1;transform:translateY(0)}}.text-primary{color:#5755d9!important}a.text-primary:focus,a.text-primary:hover{color:#4240d4}a.text-primary:visited{color:#6c6ade}.text-secondary{color:#e5e5f9!important}a.text-secondary:focus,a.text-secondary:hover{color:#d1d0f4}a.text-secondary:visited{color:#fafafe}.text-gray{color:#bcc3ce!important}a.text-gray:focus,a.text-gray:hover{color:#adb6c4}a.text-gray:visited{color:#cbd0d9}.text-light{color:#fff!important}a.text-light:focus,a.text-light:hover{color:#f2f2f2}a.text-light:visited{color:#fff}.text-dark{color:#3b4351!important}a.text-dark:focus,a.text-dark:hover{color:#303742}a.text-dark:visited{color:#455060}.text-success{color:#32b643!important}a.text-success:focus,a.text-success:hover{color:#2da23c}a.text-success:visited{color:#39c94b}.text-warning{color:#ffb700!important}a.text-warning:focus,a.text-warning:hover{color:#e6a500}a.text-warning:visited{color:#ffbe1a}.text-error{color:#e85600!important}a.text-error:focus,a.text-error:hover{color:#cf4d00}a.text-error:visited{color:#ff6003}.bg-primary{background:#5755d9!important;color:#fff}.bg-secondary{background:#f1f1fc!important}.bg-dark{background:#303742!important;color:#fff}.bg-gray{background:#f7f8f9!important}.bg-success{background:#32b643!important;color:#fff}.bg-warning{background:#ffb700!important;color:#fff}.bg-error{background:#e85600!important;color:#fff}.c-hand{cursor:pointer}.c-move{cursor:move}.c-zoom-in{cursor:zoom-in}.c-zoom-out{cursor:zoom-out}.c-not-allowed{cursor:not-allowed}.c-auto{cursor:auto}.d-block{display:block}.d-inline{display:inline}.d-inline-block{display:inline-block}.d-flex{display:flex;display:-ms-flexbox}.d-inline-flex{display:inline-flex;display:-ms-inline-flexbox}.d-hide,.d-none{display:none!important}.d-visible{visibility:visible}.d-invisible{visibility:hidden}.text-hide{background:0 0;border:0;color:transparent;font-size:0;line-height:0;text-shadow:none}.text-assistive{border:0;clip:rect(0,0,0,0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.divider,.divider-vert{display:block;position:relative}.divider-vert[data-content]::after,.divider[data-content]::after{background:#fff;color:#bcc3ce;content:attr(data-content);display:inline-block;font-size:.7rem;padding:0 .4rem;transform:translateY(-.65rem)}.divider{border-top:.05rem solid #f1f3f5;height:.05rem;margin:.4rem 0}.divider[data-content]{margin:.8rem 0}.divider-vert{display:block;padding:.8rem}.divider-vert::before{border-left:.05rem solid #dadee4;bottom:.4rem;content:"";display:block;left:50%;position:absolute;top:.4rem;transform:translateX(-50%)}.divider-vert[data-content]::after{left:50%;padding:.2rem 0;position:absolute;top:50%;transform:translate(-50%,-50%)}.loading{color:transparent!important;min-height:.8rem;pointer-events:none;position:relative}.loading::after{animation:loading .5s infinite linear;border:.1rem solid #5755d9;border-radius:50%;border-right-color:transparent;border-top-color:transparent;content:"";display:block;height:.8rem;left:50%;margin-left:-.4rem;margin-top:-.4rem;position:absolute;top:50%;width:.8rem;z-index:1}.loading.loading-lg{min-height:2rem}.loading.loading-lg::after{height:1.6rem;margin-left:-.8rem;margin-top:-.8rem;width:1.6rem}.clearfix::after{clear:both;content:"";display:table}.float-left{float:left!important}.float-right{float:right!important}.p-relative{position:relative!important}.p-absolute{position:absolute!important}.p-fixed{position:fixed!important}.p-sticky{position:sticky!important;position:-webkit-sticky!important}.p-centered{display:block;float:none;margin-left:auto;margin-right:auto}.flex-centered{align-items:center;display:flex;display:-ms-flexbox;-ms-flex-align:center;-ms-flex-pack:center;justify-content:center}.m-0{margin:0!important}.mb-0{margin-bottom:0!important}.ml-0{margin-left:0!important}.mr-0{margin-right:0!important}.mt-0{margin-top:0!important}.mx-0{margin-left:0!important;margin-right:0!important}.my-0{margin-bottom:0!important;margin-top:0!important}.m-1{margin:.2rem!important}.mb-1{margin-bottom:.2rem!important}.ml-1{margin-left:.2rem!important}.mr-1{margin-right:.2rem!important}.mt-1{margin-top:.2rem!important}.mx-1{margin-left:.2rem!important;margin-right:.2rem!important}.my-1{margin-bottom:.2rem!important;margin-top:.2rem!important}.m-2{margin:.4rem!important}.mb-2{margin-bottom:.4rem!important}.ml-2{margin-left:.4rem!important}.mr-2{margin-right:.4rem!important}.mt-2{margin-top:.4rem!important}.mx-2{margin-left:.4rem!important;margin-right:.4rem!important}.my-2{margin-bottom:.4rem!important;margin-top:.4rem!important}.p-0{padding:0!important}.pb-0{padding-bottom:0!important}.pl-0{padding-left:0!important}.pr-0{padding-right:0!important}.pt-0{padding-top:0!important}.px-0{padding-left:0!important;padding-right:0!important}.py-0{padding-bottom:0!important;padding-top:0!important}.p-1{padding:.2rem!important}.pb-1{padding-bottom:.2rem!important}.pl-1{padding-left:.2rem!important}.pr-1{padding-right:.2rem!important}.pt-1{padding-top:.2rem!important}.px-1{padding-left:.2rem!important;padding-right:.2rem!important}.py-1{padding-bottom:.2rem!important;padding-top:.2rem!important}.p-2{padding:.4rem!important}.pb-2{padding-bottom:.4rem!important}.pl-2{padding-left:.4rem!important}.pr-2{padding-right:.4rem!important}.pt-2{padding-top:.4rem!important}.px-2{padding-left:.4rem!important;padding-right:.4rem!important}.py-2{padding-bottom:.4rem!important;padding-top:.4rem!important}.s-rounded{border-radius:.1rem}.s-circle{border-radius:50%}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}.text-justify{text-align:justify}.text-lowercase{text-transform:lowercase}.text-uppercase{text-transform:uppercase}.text-capitalize{text-transform:capitalize}.text-normal{font-weight:400}.text-bold{font-weight:700}.text-italic{font-style:italic}.text-large{font-size:1.2em}.text-ellipsis{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.text-clip{overflow:hidden;text-overflow:clip;white-space:nowrap}.text-break{-webkit-hyphens:auto;-ms-hyphens:auto;hyphens:auto;word-break:break-word;word-wrap:break-word} \ No newline at end of file +/*! Spectre.css v0.5.9 | MIT License | github.com/picturepan2/spectre */html{font-family:sans-serif;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}article,aside,footer,header,nav,section{display:block}h1{font-size:2em;margin:.67em 0}figcaption,figure,main{display:block}hr{box-sizing:content-box;height:0;overflow:visible}a{background-color:transparent;-webkit-text-decoration-skip:objects}a:active,a:hover{outline-width:0}address{font-style:normal}b,strong{font-weight:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:"SF Mono","Segoe UI Mono","Roboto Mono",Menlo,Courier,monospace;font-size:1em}dfn{font-style:italic}small{font-size:80%;font-weight:400}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}audio,video{display:inline-block}audio:not([controls]){display:none;height:0}img{border-style:none}svg:not(:root){overflow:hidden}button,input,optgroup,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit;margin:0}button,input{overflow:visible}button,select{text-transform:none}[type=reset],[type=submit],button,html [type=button]{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}fieldset{border:0;margin:0;padding:0}legend{box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}progress{display:inline-block;vertical-align:baseline}textarea{overflow:auto}[type=checkbox],[type=radio]{box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-cancel-button,[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details,menu{display:block}summary{display:list-item;outline:0}canvas{display:inline-block}template{display:none}[hidden]{display:none}*,::after,::before{box-sizing:inherit}html{box-sizing:border-box;font-size:20px;line-height:1.5;-webkit-tap-highlight-color:transparent}body{background:#fff;color:#3b4351;font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",sans-serif;font-size:.8rem;overflow-x:hidden;text-rendering:optimizeLegibility}a{color:#5755d9;outline:0;text-decoration:none}a:focus{box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}a.active,a:active,a:focus,a:hover{color:#302ecd;text-decoration:underline}a:visited{color:#807fe2}h1,h2,h3,h4,h5,h6{color:inherit;font-weight:500;line-height:1.2;margin-bottom:.5em;margin-top:0}.h1,.h2,.h3,.h4,.h5,.h6{font-weight:500}.h1,h1{font-size:2rem}.h2,h2{font-size:1.6rem}.h3,h3{font-size:1.4rem}.h4,h4{font-size:1.2rem}.h5,h5{font-size:1rem}.h6,h6{font-size:.8rem}p{margin:0 0 1.2rem}a,ins,u{-webkit-text-decoration-skip:ink edges;text-decoration-skip:ink edges}abbr[title]{border-bottom:.05rem dotted;cursor:help;text-decoration:none}kbd{background:#303742;border-radius:.1rem;color:#fff;font-size:.7rem;line-height:1.25;padding:.1rem .2rem}mark{background:#ffe9b3;border-bottom:.05rem solid #ffd367;border-radius:.1rem;color:#3b4351;padding:.05rem .1rem 0}blockquote{border-left:.1rem solid #dadee4;margin-left:0;padding:.4rem .8rem}blockquote p:last-child{margin-bottom:0}ol,ul{margin:.8rem 0 .8rem .8rem;padding:0}ol ol,ol ul,ul ol,ul ul{margin:.8rem 0 .8rem .8rem}ol li,ul li{margin-top:.4rem}ul{list-style:disc inside}ul ul{list-style-type:circle}ol{list-style:decimal inside}ol ol{list-style-type:lower-alpha}dl dt{font-weight:700}dl dd{margin:.4rem 0 .8rem 0}.lang-zh,.lang-zh-hans,html:lang(zh),html:lang(zh-Hans){font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"PingFang SC","Hiragino Sans GB","Microsoft YaHei","Helvetica Neue",sans-serif}.lang-zh-hant,html:lang(zh-Hant){font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"PingFang TC","Hiragino Sans CNS","Microsoft JhengHei","Helvetica Neue",sans-serif}.lang-ja,html:lang(ja){font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"Hiragino Sans","Hiragino Kaku Gothic Pro","Yu Gothic",YuGothic,Meiryo,"Helvetica Neue",sans-serif}.lang-ko,html:lang(ko){font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"Malgun Gothic","Helvetica Neue",sans-serif}.lang-cjk ins,.lang-cjk u,:lang(ja) ins,:lang(ja) u,:lang(zh) ins,:lang(zh) u{border-bottom:.05rem solid;text-decoration:none}.lang-cjk del+del,.lang-cjk del+s,.lang-cjk ins+ins,.lang-cjk ins+u,.lang-cjk s+del,.lang-cjk s+s,.lang-cjk u+ins,.lang-cjk u+u,:lang(ja) del+del,:lang(ja) del+s,:lang(ja) ins+ins,:lang(ja) ins+u,:lang(ja) s+del,:lang(ja) s+s,:lang(ja) u+ins,:lang(ja) u+u,:lang(zh) del+del,:lang(zh) del+s,:lang(zh) ins+ins,:lang(zh) ins+u,:lang(zh) s+del,:lang(zh) s+s,:lang(zh) u+ins,:lang(zh) u+u{margin-left:.125em}.table{border-collapse:collapse;border-spacing:0;text-align:left;width:100%}.table.table-striped tbody tr:nth-of-type(odd){background:#f7f8f9}.table tbody tr.active,.table.table-striped tbody tr.active{background:#eef0f3}.table.table-hover tbody tr:hover{background:#eef0f3}.table.table-scroll{display:block;overflow-x:auto;padding-bottom:.75rem;white-space:nowrap}.table td,.table th{border-bottom:.05rem solid #dadee4;padding:.6rem .4rem}.table th{border-bottom-width:.1rem}.btn{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#fff;border:.05rem solid #5755d9;border-radius:.1rem;color:#5755d9;cursor:pointer;display:inline-block;font-size:.8rem;height:1.8rem;line-height:1.2rem;outline:0;padding:.25rem .4rem;text-align:center;text-decoration:none;transition:background .2s,border .2s,box-shadow .2s,color .2s;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;vertical-align:middle;white-space:nowrap}.btn:focus{box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.btn:focus,.btn:hover{background:#f1f1fc;border-color:#4b48d6;text-decoration:none}.btn.active,.btn:active{background:#4b48d6;border-color:#3634d2;color:#fff;text-decoration:none}.btn.active.loading::after,.btn:active.loading::after{border-bottom-color:#fff;border-left-color:#fff}.btn.disabled,.btn:disabled,.btn[disabled]{cursor:default;opacity:.5;pointer-events:none}.btn.btn-primary{background:#5755d9;border-color:#4b48d6;color:#fff}.btn.btn-primary:focus,.btn.btn-primary:hover{background:#4240d4;border-color:#3634d2;color:#fff}.btn.btn-primary.active,.btn.btn-primary:active{background:#3a38d2;border-color:#302ecd;color:#fff}.btn.btn-primary.loading::after{border-bottom-color:#fff;border-left-color:#fff}.btn.btn-success{background:#32b643;border-color:#2faa3f;color:#fff}.btn.btn-success:focus{box-shadow:0 0 0 .1rem rgba(50,182,67,.2)}.btn.btn-success:focus,.btn.btn-success:hover{background:#30ae40;border-color:#2da23c;color:#fff}.btn.btn-success.active,.btn.btn-success:active{background:#2a9a39;border-color:#278e34;color:#fff}.btn.btn-success.loading::after{border-bottom-color:#fff;border-left-color:#fff}.btn.btn-error{background:#e85600;border-color:#d95000;color:#fff}.btn.btn-error:focus{box-shadow:0 0 0 .1rem rgba(232,86,0,.2)}.btn.btn-error:focus,.btn.btn-error:hover{background:#de5200;border-color:#cf4d00;color:#fff}.btn.btn-error.active,.btn.btn-error:active{background:#c44900;border-color:#b54300;color:#fff}.btn.btn-error.loading::after{border-bottom-color:#fff;border-left-color:#fff}.btn.btn-link{background:0 0;border-color:transparent;color:#5755d9}.btn.btn-link.active,.btn.btn-link:active,.btn.btn-link:focus,.btn.btn-link:hover{color:#302ecd}.btn.btn-sm{font-size:.7rem;height:1.4rem;padding:.05rem .3rem}.btn.btn-lg{font-size:.9rem;height:2rem;padding:.35rem .6rem}.btn.btn-block{display:block;width:100%}.btn.btn-action{padding-left:0;padding-right:0;width:1.8rem}.btn.btn-action.btn-sm{width:1.4rem}.btn.btn-action.btn-lg{width:2rem}.btn.btn-clear{background:0 0;border:0;color:currentColor;height:1rem;line-height:.8rem;margin-left:.2rem;margin-right:-2px;opacity:1;padding:.1rem;text-decoration:none;width:1rem}.btn.btn-clear:focus,.btn.btn-clear:hover{background:rgba(247,248,249,.5);opacity:.95}.btn.btn-clear::before{content:"\2715"}.btn-group{display:-ms-inline-flexbox;display:inline-flex;-ms-flex-wrap:wrap;flex-wrap:wrap}.btn-group .btn{-ms-flex:1 0 auto;flex:1 0 auto}.btn-group .btn:first-child:not(:last-child){border-bottom-right-radius:0;border-top-right-radius:0}.btn-group .btn:not(:first-child):not(:last-child){border-radius:0;margin-left:-.05rem}.btn-group .btn:last-child:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0;margin-left:-.05rem}.btn-group .btn.active,.btn-group .btn:active,.btn-group .btn:focus,.btn-group .btn:hover{z-index:1}.btn-group.btn-group-block{display:-ms-flexbox;display:flex}.btn-group.btn-group-block .btn{-ms-flex:1 0 0;flex:1 0 0}.form-group:not(:last-child){margin-bottom:.4rem}fieldset{margin-bottom:.8rem}legend{font-size:.9rem;font-weight:500;margin-bottom:.8rem}.form-label{display:block;line-height:1.2rem;padding:.3rem 0}.form-label.label-sm{font-size:.7rem;padding:.1rem 0}.form-label.label-lg{font-size:.9rem;padding:.4rem 0}.form-input{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#fff;background-image:none;border:.05rem solid #bcc3ce;border-radius:.1rem;color:#3b4351;display:block;font-size:.8rem;height:1.8rem;line-height:1.2rem;max-width:100%;outline:0;padding:.25rem .4rem;position:relative;transition:background .2s,border .2s,box-shadow .2s,color .2s;width:100%}.form-input:focus{border-color:#5755d9;box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.form-input:-ms-input-placeholder{color:#bcc3ce}.form-input::-ms-input-placeholder{color:#bcc3ce}.form-input::placeholder{color:#bcc3ce}.form-input.input-sm{font-size:.7rem;height:1.4rem;padding:.05rem .3rem}.form-input.input-lg{font-size:.9rem;height:2rem;padding:.35rem .6rem}.form-input.input-inline{display:inline-block;vertical-align:middle;width:auto}.form-input[type=file]{height:auto}textarea.form-input,textarea.form-input.input-lg,textarea.form-input.input-sm{height:auto}.form-input-hint{color:#bcc3ce;font-size:.7rem;margin-top:.2rem}.has-success .form-input-hint,.is-success+.form-input-hint{color:#32b643}.has-error .form-input-hint,.is-error+.form-input-hint{color:#e85600}.form-select{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#fff;border:.05rem solid #bcc3ce;border-radius:.1rem;color:inherit;font-size:.8rem;height:1.8rem;line-height:1.2rem;outline:0;padding:.25rem .4rem;vertical-align:middle;width:100%}.form-select:focus{border-color:#5755d9;box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.form-select::-ms-expand{display:none}.form-select.select-sm{font-size:.7rem;height:1.4rem;padding:.05rem 1.1rem .05rem .3rem}.form-select.select-lg{font-size:.9rem;height:2rem;padding:.35rem 1.4rem .35rem .6rem}.form-select[multiple],.form-select[size]{height:auto;padding:.25rem .4rem}.form-select[multiple] option,.form-select[size] option{padding:.1rem .2rem}.form-select:not([multiple]):not([size]){background:#fff url("data:image/svg+xml;charset=utf8,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%204%205'%3E%3Cpath%20fill='%23667189'%20d='M2%200L0%202h4zm0%205L0%203h4z'/%3E%3C/svg%3E") no-repeat right .35rem center/.4rem .5rem;padding-right:1.2rem}.has-icon-left,.has-icon-right{position:relative}.has-icon-left .form-icon,.has-icon-right .form-icon{height:.8rem;margin:0 .25rem;position:absolute;top:50%;transform:translateY(-50%);width:.8rem;z-index:2}.has-icon-left .form-icon{left:.05rem}.has-icon-left .form-input{padding-left:1.3rem}.has-icon-right .form-icon{right:.05rem}.has-icon-right .form-input{padding-right:1.3rem}.form-checkbox,.form-radio,.form-switch{display:block;line-height:1.2rem;margin:.2rem 0;min-height:1.4rem;padding:.1rem .4rem .1rem 1.2rem;position:relative}.form-checkbox input,.form-radio input,.form-switch input{clip:rect(0,0,0,0);height:1px;margin:-1px;overflow:hidden;position:absolute;width:1px}.form-checkbox input:focus+.form-icon,.form-radio input:focus+.form-icon,.form-switch input:focus+.form-icon{border-color:#5755d9;box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.form-checkbox input:checked+.form-icon,.form-radio input:checked+.form-icon,.form-switch input:checked+.form-icon{background:#5755d9;border-color:#5755d9}.form-checkbox .form-icon,.form-radio .form-icon,.form-switch .form-icon{border:.05rem solid #bcc3ce;cursor:pointer;display:inline-block;position:absolute;transition:background .2s,border .2s,box-shadow .2s,color .2s}.form-checkbox.input-sm,.form-radio.input-sm,.form-switch.input-sm{font-size:.7rem;margin:0}.form-checkbox.input-lg,.form-radio.input-lg,.form-switch.input-lg{font-size:.9rem;margin:.3rem 0}.form-checkbox .form-icon,.form-radio .form-icon{background:#fff;height:.8rem;left:0;top:.3rem;width:.8rem}.form-checkbox input:active+.form-icon,.form-radio input:active+.form-icon{background:#eef0f3}.form-checkbox .form-icon{border-radius:.1rem}.form-checkbox input:checked+.form-icon::before{background-clip:padding-box;border:.1rem solid #fff;border-left-width:0;border-top-width:0;content:"";height:9px;left:50%;margin-left:-3px;margin-top:-6px;position:absolute;top:50%;transform:rotate(45deg);width:6px}.form-checkbox input:indeterminate+.form-icon{background:#5755d9;border-color:#5755d9}.form-checkbox input:indeterminate+.form-icon::before{background:#fff;content:"";height:2px;left:50%;margin-left:-5px;margin-top:-1px;position:absolute;top:50%;width:10px}.form-radio .form-icon{border-radius:50%}.form-radio input:checked+.form-icon::before{background:#fff;border-radius:50%;content:"";height:6px;left:50%;position:absolute;top:50%;transform:translate(-50%,-50%);width:6px}.form-switch{padding-left:2rem}.form-switch .form-icon{background:#bcc3ce;background-clip:padding-box;border-radius:.45rem;height:.9rem;left:0;top:.25rem;width:1.6rem}.form-switch .form-icon::before{background:#fff;border-radius:50%;content:"";display:block;height:.8rem;left:0;position:absolute;top:0;transition:background .2s,border .2s,box-shadow .2s,color .2s,left .2s;width:.8rem}.form-switch input:checked+.form-icon::before{left:14px}.form-switch input:active+.form-icon::before{background:#f7f8f9}.input-group{display:-ms-flexbox;display:flex}.input-group .input-group-addon{background:#f7f8f9;border:.05rem solid #bcc3ce;border-radius:.1rem;line-height:1.2rem;padding:.25rem .4rem;white-space:nowrap}.input-group .input-group-addon.addon-sm{font-size:.7rem;padding:.05rem .3rem}.input-group .input-group-addon.addon-lg{font-size:.9rem;padding:.35rem .6rem}.input-group .form-input,.input-group .form-select{-ms-flex:1 1 auto;flex:1 1 auto;width:1%}.input-group .input-group-btn{z-index:1}.input-group .form-input:first-child:not(:last-child),.input-group .form-select:first-child:not(:last-child),.input-group .input-group-addon:first-child:not(:last-child),.input-group .input-group-btn:first-child:not(:last-child){border-bottom-right-radius:0;border-top-right-radius:0}.input-group .form-input:not(:first-child):not(:last-child),.input-group .form-select:not(:first-child):not(:last-child),.input-group .input-group-addon:not(:first-child):not(:last-child),.input-group .input-group-btn:not(:first-child):not(:last-child){border-radius:0;margin-left:-.05rem}.input-group .form-input:last-child:not(:first-child),.input-group .form-select:last-child:not(:first-child),.input-group .input-group-addon:last-child:not(:first-child),.input-group .input-group-btn:last-child:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0;margin-left:-.05rem}.input-group .form-input:focus,.input-group .form-select:focus,.input-group .input-group-addon:focus,.input-group .input-group-btn:focus{z-index:2}.input-group .form-select{width:auto}.input-group.input-inline{display:-ms-inline-flexbox;display:inline-flex}.form-input.is-success,.form-select.is-success,.has-success .form-input,.has-success .form-select{background:#f9fdfa;border-color:#32b643}.form-input.is-success:focus,.form-select.is-success:focus,.has-success .form-input:focus,.has-success .form-select:focus{box-shadow:0 0 0 .1rem rgba(50,182,67,.2)}.form-input.is-error,.form-select.is-error,.has-error .form-input,.has-error .form-select{background:#fffaf7;border-color:#e85600}.form-input.is-error:focus,.form-select.is-error:focus,.has-error .form-input:focus,.has-error .form-select:focus{box-shadow:0 0 0 .1rem rgba(232,86,0,.2)}.form-checkbox.is-error .form-icon,.form-radio.is-error .form-icon,.form-switch.is-error .form-icon,.has-error .form-checkbox .form-icon,.has-error .form-radio .form-icon,.has-error .form-switch .form-icon{border-color:#e85600}.form-checkbox.is-error input:checked+.form-icon,.form-radio.is-error input:checked+.form-icon,.form-switch.is-error input:checked+.form-icon,.has-error .form-checkbox input:checked+.form-icon,.has-error .form-radio input:checked+.form-icon,.has-error .form-switch input:checked+.form-icon{background:#e85600;border-color:#e85600}.form-checkbox.is-error input:focus+.form-icon,.form-radio.is-error input:focus+.form-icon,.form-switch.is-error input:focus+.form-icon,.has-error .form-checkbox input:focus+.form-icon,.has-error .form-radio input:focus+.form-icon,.has-error .form-switch input:focus+.form-icon{border-color:#e85600;box-shadow:0 0 0 .1rem rgba(232,86,0,.2)}.form-checkbox.is-error input:indeterminate+.form-icon,.has-error .form-checkbox input:indeterminate+.form-icon{background:#e85600;border-color:#e85600}.form-input:not(:-ms-input-placeholder):invalid{border-color:#e85600}.form-input:not(:placeholder-shown):invalid{border-color:#e85600}.form-input:not(:-ms-input-placeholder):invalid:focus{background:#fffaf7;box-shadow:0 0 0 .1rem rgba(232,86,0,.2)}.form-input:not(:placeholder-shown):invalid:focus{background:#fffaf7;box-shadow:0 0 0 .1rem rgba(232,86,0,.2)}.form-input:not(:-ms-input-placeholder):invalid+.form-input-hint{color:#e85600}.form-input:not(:placeholder-shown):invalid+.form-input-hint{color:#e85600}.form-input.disabled,.form-input:disabled,.form-select.disabled,.form-select:disabled{background-color:#eef0f3;cursor:not-allowed;opacity:.5}.form-input[readonly]{background-color:#f7f8f9}input.disabled+.form-icon,input:disabled+.form-icon{background:#eef0f3;cursor:not-allowed;opacity:.5}.form-switch input.disabled+.form-icon::before,.form-switch input:disabled+.form-icon::before{background:#fff}.form-horizontal{padding:.4rem 0}.form-horizontal .form-group{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap}.form-inline{display:inline-block}.label{background:#eef0f3;border-radius:.1rem;color:#455060;display:inline-block;line-height:1.25;padding:.1rem .2rem}.label.label-rounded{border-radius:5rem;padding-left:.4rem;padding-right:.4rem}.label.label-primary{background:#5755d9;color:#fff}.label.label-secondary{background:#f1f1fc;color:#5755d9}.label.label-success{background:#32b643;color:#fff}.label.label-warning{background:#ffb700;color:#fff}.label.label-error{background:#e85600;color:#fff}code{background:#fcf2f2;border-radius:.1rem;color:#d73e48;font-size:85%;line-height:1.25;padding:.1rem .2rem}.code{border-radius:.1rem;color:#3b4351;position:relative}.code::before{color:#bcc3ce;content:attr(data-lang);font-size:.7rem;position:absolute;right:.4rem;top:.1rem}.code code{background:#f7f8f9;color:inherit;display:block;line-height:1.5;overflow-x:auto;padding:1rem;width:100%}.img-responsive{display:block;height:auto;max-width:100%}.img-fit-cover{object-fit:cover}.img-fit-contain{object-fit:contain}.video-responsive{display:block;overflow:hidden;padding:0;position:relative;width:100%}.video-responsive::before{content:"";display:block;padding-bottom:56.25%}.video-responsive embed,.video-responsive iframe,.video-responsive object{border:0;bottom:0;height:100%;left:0;position:absolute;right:0;top:0;width:100%}video.video-responsive{height:auto;max-width:100%}video.video-responsive::before{content:none}.video-responsive-4-3::before{padding-bottom:75%}.video-responsive-1-1::before{padding-bottom:100%}.figure{margin:0 0 .4rem 0}.figure .figure-caption{color:#66758c;margin-top:.4rem}.container{margin-left:auto;margin-right:auto;padding-left:.4rem;padding-right:.4rem;width:100%}.container.grid-xl{max-width:1296px}.container.grid-lg{max-width:976px}.container.grid-md{max-width:856px}.container.grid-sm{max-width:616px}.container.grid-xs{max-width:496px}.show-lg,.show-md,.show-sm,.show-xl,.show-xs{display:none!important}.cols,.columns{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;margin-left:-.4rem;margin-right:-.4rem}.cols.col-gapless,.columns.col-gapless{margin-left:0;margin-right:0}.cols.col-gapless>.column,.columns.col-gapless>.column{padding-left:0;padding-right:0}.cols.col-oneline,.columns.col-oneline{-ms-flex-wrap:nowrap;flex-wrap:nowrap;overflow-x:auto}.column,[class~=col-]{-ms-flex:1;flex:1;max-width:100%;padding-left:.4rem;padding-right:.4rem}.column.col-1,.column.col-10,.column.col-11,.column.col-12,.column.col-2,.column.col-3,.column.col-4,.column.col-5,.column.col-6,.column.col-7,.column.col-8,.column.col-9,.column.col-auto,[class~=col-].col-1,[class~=col-].col-10,[class~=col-].col-11,[class~=col-].col-12,[class~=col-].col-2,[class~=col-].col-3,[class~=col-].col-4,[class~=col-].col-5,[class~=col-].col-6,[class~=col-].col-7,[class~=col-].col-8,[class~=col-].col-9,[class~=col-].col-auto{-ms-flex:none;flex:none}.col-12{width:100%}.col-11{width:91.66666667%}.col-10{width:83.33333333%}.col-9{width:75%}.col-8{width:66.66666667%}.col-7{width:58.33333333%}.col-6{width:50%}.col-5{width:41.66666667%}.col-4{width:33.33333333%}.col-3{width:25%}.col-2{width:16.66666667%}.col-1{width:8.33333333%}.col-auto{-ms-flex:0 0 auto;flex:0 0 auto;max-width:none;width:auto}.col-mx-auto{margin-left:auto;margin-right:auto}.col-ml-auto{margin-left:auto}.col-mr-auto{margin-right:auto}@media (max-width:1280px){.col-xl-1,.col-xl-10,.col-xl-11,.col-xl-12,.col-xl-2,.col-xl-3,.col-xl-4,.col-xl-5,.col-xl-6,.col-xl-7,.col-xl-8,.col-xl-9,.col-xl-auto{-ms-flex:none;flex:none}.col-xl-12{width:100%}.col-xl-11{width:91.66666667%}.col-xl-10{width:83.33333333%}.col-xl-9{width:75%}.col-xl-8{width:66.66666667%}.col-xl-7{width:58.33333333%}.col-xl-6{width:50%}.col-xl-5{width:41.66666667%}.col-xl-4{width:33.33333333%}.col-xl-3{width:25%}.col-xl-2{width:16.66666667%}.col-xl-1{width:8.33333333%}.col-xl-auto{width:auto}.hide-xl{display:none!important}.show-xl{display:block!important}}@media (max-width:960px){.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-lg-auto{-ms-flex:none;flex:none}.col-lg-12{width:100%}.col-lg-11{width:91.66666667%}.col-lg-10{width:83.33333333%}.col-lg-9{width:75%}.col-lg-8{width:66.66666667%}.col-lg-7{width:58.33333333%}.col-lg-6{width:50%}.col-lg-5{width:41.66666667%}.col-lg-4{width:33.33333333%}.col-lg-3{width:25%}.col-lg-2{width:16.66666667%}.col-lg-1{width:8.33333333%}.col-lg-auto{width:auto}.hide-lg{display:none!important}.show-lg{display:block!important}}@media (max-width:840px){.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-md-auto{-ms-flex:none;flex:none}.col-md-12{width:100%}.col-md-11{width:91.66666667%}.col-md-10{width:83.33333333%}.col-md-9{width:75%}.col-md-8{width:66.66666667%}.col-md-7{width:58.33333333%}.col-md-6{width:50%}.col-md-5{width:41.66666667%}.col-md-4{width:33.33333333%}.col-md-3{width:25%}.col-md-2{width:16.66666667%}.col-md-1{width:8.33333333%}.col-md-auto{width:auto}.hide-md{display:none!important}.show-md{display:block!important}}@media (max-width:600px){.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-sm-auto{-ms-flex:none;flex:none}.col-sm-12{width:100%}.col-sm-11{width:91.66666667%}.col-sm-10{width:83.33333333%}.col-sm-9{width:75%}.col-sm-8{width:66.66666667%}.col-sm-7{width:58.33333333%}.col-sm-6{width:50%}.col-sm-5{width:41.66666667%}.col-sm-4{width:33.33333333%}.col-sm-3{width:25%}.col-sm-2{width:16.66666667%}.col-sm-1{width:8.33333333%}.col-sm-auto{width:auto}.hide-sm{display:none!important}.show-sm{display:block!important}}@media (max-width:480px){.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9,.col-xs-auto{-ms-flex:none;flex:none}.col-xs-12{width:100%}.col-xs-11{width:91.66666667%}.col-xs-10{width:83.33333333%}.col-xs-9{width:75%}.col-xs-8{width:66.66666667%}.col-xs-7{width:58.33333333%}.col-xs-6{width:50%}.col-xs-5{width:41.66666667%}.col-xs-4{width:33.33333333%}.col-xs-3{width:25%}.col-xs-2{width:16.66666667%}.col-xs-1{width:8.33333333%}.col-xs-auto{width:auto}.hide-xs{display:none!important}.show-xs{display:block!important}}.hero{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;-ms-flex-pack:justify;justify-content:space-between;padding-bottom:4rem;padding-top:4rem}.hero.hero-sm{padding-bottom:2rem;padding-top:2rem}.hero.hero-lg{padding-bottom:8rem;padding-top:8rem}.hero .hero-body{padding:.4rem}.navbar{align-items:stretch;display:-ms-flexbox;display:flex;-ms-flex-align:stretch;-ms-flex-pack:justify;-ms-flex-wrap:wrap;flex-wrap:wrap;justify-content:space-between}.navbar .navbar-section{align-items:center;display:-ms-flexbox;display:flex;-ms-flex:1 0 0;flex:1 0 0;-ms-flex-align:center}.navbar .navbar-section:not(:first-child):last-child{-ms-flex-pack:end;justify-content:flex-end}.navbar .navbar-center{align-items:center;display:-ms-flexbox;display:flex;-ms-flex:0 0 auto;flex:0 0 auto;-ms-flex-align:center}.navbar .navbar-brand{font-size:.9rem;text-decoration:none}.accordion input:checked~.accordion-header>.icon:first-child,.accordion[open] .accordion-header>.icon:first-child{transform:rotate(90deg)}.accordion input:checked~.accordion-body,.accordion[open] .accordion-body{max-height:50rem}.accordion .accordion-header{display:block;padding:.2rem .4rem}.accordion .accordion-header .icon{transition:transform .25s}.accordion .accordion-body{margin-bottom:.4rem;max-height:0;overflow:hidden;transition:max-height .25s}summary.accordion-header::-webkit-details-marker{display:none}.avatar{background:#5755d9;border-radius:50%;color:rgba(255,255,255,.85);display:inline-block;font-size:.8rem;font-weight:300;height:1.6rem;line-height:1.25;margin:0;position:relative;vertical-align:middle;width:1.6rem}.avatar.avatar-xs{font-size:.4rem;height:.8rem;width:.8rem}.avatar.avatar-sm{font-size:.6rem;height:1.2rem;width:1.2rem}.avatar.avatar-lg{font-size:1.2rem;height:2.4rem;width:2.4rem}.avatar.avatar-xl{font-size:1.6rem;height:3.2rem;width:3.2rem}.avatar img{border-radius:50%;height:100%;position:relative;width:100%;z-index:1}.avatar .avatar-icon,.avatar .avatar-presence{background:#fff;bottom:14.64%;height:50%;padding:.1rem;position:absolute;right:14.64%;transform:translate(50%,50%);width:50%;z-index:2}.avatar .avatar-presence{background:#bcc3ce;border-radius:50%;box-shadow:0 0 0 .1rem #fff;height:.5em;width:.5em}.avatar .avatar-presence.online{background:#32b643}.avatar .avatar-presence.busy{background:#e85600}.avatar .avatar-presence.away{background:#ffb700}.avatar[data-initial]::before{color:currentColor;content:attr(data-initial);left:50%;position:absolute;top:50%;transform:translate(-50%,-50%);z-index:1}.badge{position:relative;white-space:nowrap}.badge:not([data-badge])::after,.badge[data-badge]::after{background:#5755d9;background-clip:padding-box;border-radius:.5rem;box-shadow:0 0 0 .1rem #fff;color:#fff;content:attr(data-badge);display:inline-block;transform:translate(-.05rem,-.5rem)}.badge[data-badge]::after{font-size:.7rem;height:.9rem;line-height:1;min-width:.9rem;padding:.1rem .2rem;text-align:center;white-space:nowrap}.badge:not([data-badge])::after,.badge[data-badge=""]::after{height:6px;min-width:6px;padding:0;width:6px}.badge.btn::after{position:absolute;right:0;top:0;transform:translate(50%,-50%)}.badge.avatar::after{position:absolute;right:14.64%;top:14.64%;transform:translate(50%,-50%);z-index:100}.breadcrumb{list-style:none;margin:.2rem 0;padding:.2rem 0}.breadcrumb .breadcrumb-item{color:#66758c;display:inline-block;margin:0;padding:.2rem 0}.breadcrumb .breadcrumb-item:not(:last-child){margin-right:.2rem}.breadcrumb .breadcrumb-item:not(:last-child) a{color:#66758c}.breadcrumb .breadcrumb-item:not(:first-child)::before{color:#66758c;content:"/";padding-right:.4rem}.bar{background:#eef0f3;border-radius:.1rem;display:-ms-flexbox;display:flex;-ms-flex-wrap:nowrap;flex-wrap:nowrap;height:.8rem;width:100%}.bar.bar-sm{height:.2rem}.bar .bar-item{background:#5755d9;color:#fff;display:block;-ms-flex-negative:0;flex-shrink:0;font-size:.7rem;height:100%;line-height:.8rem;position:relative;text-align:center;width:0}.bar .bar-item:first-child{border-bottom-left-radius:.1rem;border-top-left-radius:.1rem}.bar .bar-item:last-child{border-bottom-right-radius:.1rem;border-top-right-radius:.1rem;-ms-flex-negative:1;flex-shrink:1}.bar-slider{height:.1rem;margin:.4rem 0;position:relative}.bar-slider .bar-item{left:0;padding:0;position:absolute}.bar-slider .bar-item:not(:last-child):first-child{background:#eef0f3;z-index:1}.bar-slider .bar-slider-btn{background:#5755d9;border:0;border-radius:50%;height:.6rem;padding:0;position:absolute;right:0;top:50%;transform:translate(50%,-50%);width:.6rem}.bar-slider .bar-slider-btn:active{box-shadow:0 0 0 .1rem #5755d9}.card{background:#fff;border:.05rem solid #dadee4;border-radius:.1rem;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column}.card .card-body,.card .card-footer,.card .card-header{padding:.8rem;padding-bottom:0}.card .card-body:last-child,.card .card-footer:last-child,.card .card-header:last-child{padding-bottom:.8rem}.card .card-body{-ms-flex:1 1 auto;flex:1 1 auto}.card .card-image{padding-top:.8rem}.card .card-image:first-child{padding-top:0}.card .card-image:first-child img{border-top-left-radius:.1rem;border-top-right-radius:.1rem}.card .card-image:last-child img{border-bottom-left-radius:.1rem;border-bottom-right-radius:.1rem}.chip{align-items:center;background:#eef0f3;border-radius:5rem;display:-ms-inline-flexbox;display:inline-flex;-ms-flex-align:center;font-size:90%;height:1.2rem;line-height:.8rem;margin:.1rem;max-width:320px;overflow:hidden;padding:.2rem .4rem;text-decoration:none;text-overflow:ellipsis;vertical-align:middle;white-space:nowrap}.chip.active{background:#5755d9;color:#fff}.chip .avatar{margin-left:-.4rem;margin-right:.2rem}.chip .btn-clear{border-radius:50%;transform:scale(.75)}.dropdown{display:inline-block;position:relative}.dropdown .menu{animation:slide-down .15s ease 1;display:none;left:0;max-height:50vh;overflow-y:auto;position:absolute;top:100%}.dropdown.dropdown-right .menu{left:auto;right:0}.dropdown .dropdown-toggle:focus+.menu,.dropdown .menu:hover,.dropdown.active .menu{display:block}.dropdown .btn-group .dropdown-toggle:nth-last-child(2){border-bottom-right-radius:.1rem;border-top-right-radius:.1rem}.empty{background:#f7f8f9;border-radius:.1rem;color:#66758c;padding:3.2rem 1.6rem;text-align:center}.empty .empty-icon{margin-bottom:.8rem}.empty .empty-subtitle,.empty .empty-title{margin:.4rem auto}.empty .empty-action{margin-top:.8rem}.menu{background:#fff;border-radius:.1rem;box-shadow:0 .05rem .2rem rgba(48,55,66,.3);list-style:none;margin:0;min-width:180px;padding:.4rem;transform:translateY(.2rem);z-index:300}.menu.menu-nav{background:0 0;box-shadow:none}.menu .menu-item{margin-top:0;padding:0 .4rem;position:relative;text-decoration:none}.menu .menu-item>a{border-radius:.1rem;color:inherit;display:block;margin:0 -.4rem;padding:.2rem .4rem;text-decoration:none}.menu .menu-item>a:focus,.menu .menu-item>a:hover{background:#f1f1fc;color:#5755d9}.menu .menu-item>a.active,.menu .menu-item>a:active{background:#f1f1fc;color:#5755d9}.menu .menu-item .form-checkbox,.menu .menu-item .form-radio,.menu .menu-item .form-switch{margin:.1rem 0}.menu .menu-item+.menu-item{margin-top:.2rem}.menu .menu-badge{align-items:center;display:-ms-flexbox;display:flex;-ms-flex-align:center;height:100%;position:absolute;right:0;top:0}.menu .menu-badge .label{margin-right:.4rem}.modal{align-items:center;bottom:0;display:none;-ms-flex-align:center;-ms-flex-pack:center;justify-content:center;left:0;opacity:0;overflow:hidden;padding:.4rem;position:fixed;right:0;top:0}.modal.active,.modal:target{display:-ms-flexbox;display:flex;opacity:1;z-index:400}.modal.active .modal-overlay,.modal:target .modal-overlay{background:rgba(247,248,249,.75);bottom:0;cursor:default;display:block;left:0;position:absolute;right:0;top:0}.modal.active .modal-container,.modal:target .modal-container{animation:slide-down .2s ease 1;z-index:1}.modal.modal-sm .modal-container{max-width:320px;padding:0 .4rem}.modal.modal-lg .modal-overlay{background:#fff}.modal.modal-lg .modal-container{box-shadow:none;max-width:960px}.modal-container{background:#fff;border-radius:.1rem;box-shadow:0 .2rem .5rem rgba(48,55,66,.3);display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;max-height:75vh;max-width:640px;padding:0 .8rem;width:100%}.modal-container.modal-fullheight{max-height:100vh}.modal-container .modal-header{color:#303742;padding:.8rem}.modal-container .modal-body{overflow-y:auto;padding:.8rem;position:relative}.modal-container .modal-footer{padding:.8rem;text-align:right}.nav{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;list-style:none;margin:.2rem 0}.nav .nav-item a{color:#66758c;padding:.2rem .4rem;text-decoration:none}.nav .nav-item a:focus,.nav .nav-item a:hover{color:#5755d9}.nav .nav-item.active>a{color:#505c6e;font-weight:700}.nav .nav-item.active>a:focus,.nav .nav-item.active>a:hover{color:#5755d9}.nav .nav{margin-bottom:.4rem;margin-left:.8rem}.pagination{display:-ms-flexbox;display:flex;list-style:none;margin:.2rem 0;padding:.2rem 0}.pagination .page-item{margin:.2rem .05rem}.pagination .page-item span{display:inline-block;padding:.2rem .2rem}.pagination .page-item a{border-radius:.1rem;display:inline-block;padding:.2rem .4rem;text-decoration:none}.pagination .page-item a:focus,.pagination .page-item a:hover{color:#5755d9}.pagination .page-item.disabled a{cursor:default;opacity:.5;pointer-events:none}.pagination .page-item.active a{background:#5755d9;color:#fff}.pagination .page-item.page-next,.pagination .page-item.page-prev{-ms-flex:1 0 50%;flex:1 0 50%}.pagination .page-item.page-next{text-align:right}.pagination .page-item .page-item-title{margin:0}.pagination .page-item .page-item-subtitle{margin:0;opacity:.5}.panel{border:.05rem solid #dadee4;border-radius:.1rem;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column}.panel .panel-footer,.panel .panel-header{-ms-flex:0 0 auto;flex:0 0 auto;padding:.8rem}.panel .panel-nav{-ms-flex:0 0 auto;flex:0 0 auto}.panel .panel-body{-ms-flex:1 1 auto;flex:1 1 auto;overflow-y:auto;padding:0 .8rem}.popover{display:inline-block;position:relative}.popover .popover-container{left:50%;opacity:0;padding:.4rem;position:absolute;top:0;transform:translate(-50%,-50%) scale(0);transition:transform .2s;width:320px;z-index:300}.popover :focus+.popover-container,.popover:hover .popover-container{display:block;opacity:1;transform:translate(-50%,-100%) scale(1)}.popover.popover-right .popover-container{left:100%;top:50%}.popover.popover-right :focus+.popover-container,.popover.popover-right:hover .popover-container{transform:translate(0,-50%) scale(1)}.popover.popover-bottom .popover-container{left:50%;top:100%}.popover.popover-bottom :focus+.popover-container,.popover.popover-bottom:hover .popover-container{transform:translate(-50%,0) scale(1)}.popover.popover-left .popover-container{left:0;top:50%}.popover.popover-left :focus+.popover-container,.popover.popover-left:hover .popover-container{transform:translate(-100%,-50%) scale(1)}.popover .card{border:0;box-shadow:0 .2rem .5rem rgba(48,55,66,.3)}.step{display:-ms-flexbox;display:flex;-ms-flex-wrap:nowrap;flex-wrap:nowrap;list-style:none;margin:.2rem 0;width:100%}.step .step-item{-ms-flex:1 1 0;flex:1 1 0;margin-top:0;min-height:1rem;position:relative;text-align:center}.step .step-item:not(:first-child)::before{background:#5755d9;content:"";height:2px;left:-50%;position:absolute;top:9px;width:100%}.step .step-item a{color:#5755d9;display:inline-block;padding:20px 10px 0;text-decoration:none}.step .step-item a::before{background:#5755d9;border:.1rem solid #fff;border-radius:50%;content:"";display:block;height:.6rem;left:50%;position:absolute;top:.2rem;transform:translateX(-50%);width:.6rem;z-index:1}.step .step-item.active a::before{background:#fff;border:.1rem solid #5755d9}.step .step-item.active~.step-item::before{background:#dadee4}.step .step-item.active~.step-item a{color:#bcc3ce}.step .step-item.active~.step-item a::before{background:#dadee4}.tab{align-items:center;border-bottom:.05rem solid #dadee4;display:-ms-flexbox;display:flex;-ms-flex-align:center;-ms-flex-wrap:wrap;flex-wrap:wrap;list-style:none;margin:.2rem 0 .15rem 0}.tab .tab-item{margin-top:0}.tab .tab-item a{border-bottom:.1rem solid transparent;color:inherit;display:block;margin:0 .4rem 0 0;padding:.4rem .2rem .3rem .2rem;text-decoration:none}.tab .tab-item a:focus,.tab .tab-item a:hover{color:#5755d9}.tab .tab-item a.active,.tab .tab-item.active a{border-bottom-color:#5755d9;color:#5755d9}.tab .tab-item.tab-action{-ms-flex:1 0 auto;flex:1 0 auto;text-align:right}.tab .tab-item .btn-clear{margin-top:-.2rem}.tab.tab-block .tab-item{-ms-flex:1 0 0;flex:1 0 0;text-align:center}.tab.tab-block .tab-item a{margin:0}.tab.tab-block .tab-item .badge[data-badge]::after{position:absolute;right:.1rem;top:.1rem;transform:translate(0,0)}.tab:not(.tab-block) .badge{padding-right:0}.tile{align-content:space-between;align-items:flex-start;display:-ms-flexbox;display:flex;-ms-flex-align:start;-ms-flex-line-pack:justify}.tile .tile-action,.tile .tile-icon{-ms-flex:0 0 auto;flex:0 0 auto}.tile .tile-content{-ms-flex:1 1 auto;flex:1 1 auto}.tile .tile-content:not(:first-child){padding-left:.4rem}.tile .tile-content:not(:last-child){padding-right:.4rem}.tile .tile-subtitle,.tile .tile-title{line-height:1.2rem}.tile.tile-centered{align-items:center;-ms-flex-align:center}.tile.tile-centered .tile-content{overflow:hidden}.tile.tile-centered .tile-subtitle,.tile.tile-centered .tile-title{margin-bottom:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.toast{background:rgba(48,55,66,.95);border:.05rem solid #303742;border-color:#303742;border-radius:.1rem;color:#fff;display:block;padding:.4rem;width:100%}.toast.toast-primary{background:rgba(87,85,217,.95);border-color:#5755d9}.toast.toast-success{background:rgba(50,182,67,.95);border-color:#32b643}.toast.toast-warning{background:rgba(255,183,0,.95);border-color:#ffb700}.toast.toast-error{background:rgba(232,86,0,.95);border-color:#e85600}.toast a{color:#fff;text-decoration:underline}.toast a.active,.toast a:active,.toast a:focus,.toast a:hover{opacity:.75}.toast .btn-clear{margin:.1rem}.toast p:last-child{margin-bottom:0}.tooltip{position:relative}.tooltip::after{background:rgba(48,55,66,.95);border-radius:.1rem;bottom:100%;color:#fff;content:attr(data-tooltip);display:block;font-size:.7rem;left:50%;max-width:320px;opacity:0;overflow:hidden;padding:.2rem .4rem;pointer-events:none;position:absolute;text-overflow:ellipsis;transform:translate(-50%,.4rem);transition:opacity .2s,transform .2s;white-space:pre;z-index:300}.tooltip:focus::after,.tooltip:hover::after{opacity:1;transform:translate(-50%,-.2rem)}.tooltip.disabled,.tooltip[disabled]{pointer-events:auto}.tooltip.tooltip-right::after{bottom:50%;left:100%;transform:translate(-.2rem,50%)}.tooltip.tooltip-right:focus::after,.tooltip.tooltip-right:hover::after{transform:translate(.2rem,50%)}.tooltip.tooltip-bottom::after{bottom:auto;top:100%;transform:translate(-50%,-.4rem)}.tooltip.tooltip-bottom:focus::after,.tooltip.tooltip-bottom:hover::after{transform:translate(-50%,.2rem)}.tooltip.tooltip-left::after{bottom:50%;left:auto;right:100%;transform:translate(.4rem,50%)}.tooltip.tooltip-left:focus::after,.tooltip.tooltip-left:hover::after{transform:translate(-.2rem,50%)}@keyframes loading{0%{transform:rotate(0)}100%{transform:rotate(360deg)}}@keyframes slide-down{0%{opacity:0;transform:translateY(-1.6rem)}100%{opacity:1;transform:translateY(0)}}.text-primary{color:#5755d9!important}a.text-primary:focus,a.text-primary:hover{color:#4240d4}a.text-primary:visited{color:#6c6ade}.text-secondary{color:#e5e5f9!important}a.text-secondary:focus,a.text-secondary:hover{color:#d1d0f4}a.text-secondary:visited{color:#fafafe}.text-gray{color:#bcc3ce!important}a.text-gray:focus,a.text-gray:hover{color:#adb6c4}a.text-gray:visited{color:#cbd0d9}.text-light{color:#fff!important}a.text-light:focus,a.text-light:hover{color:#f2f2f2}a.text-light:visited{color:#fff}.text-dark{color:#3b4351!important}a.text-dark:focus,a.text-dark:hover{color:#303742}a.text-dark:visited{color:#455060}.text-success{color:#32b643!important}a.text-success:focus,a.text-success:hover{color:#2da23c}a.text-success:visited{color:#39c94b}.text-warning{color:#ffb700!important}a.text-warning:focus,a.text-warning:hover{color:#e6a500}a.text-warning:visited{color:#ffbe1a}.text-error{color:#e85600!important}a.text-error:focus,a.text-error:hover{color:#cf4d00}a.text-error:visited{color:#ff6003}.bg-primary{background:#5755d9!important;color:#fff}.bg-secondary{background:#f1f1fc!important}.bg-dark{background:#303742!important;color:#fff}.bg-gray{background:#f7f8f9!important}.bg-success{background:#32b643!important;color:#fff}.bg-warning{background:#ffb700!important;color:#fff}.bg-error{background:#e85600!important;color:#fff}.c-hand{cursor:pointer}.c-move{cursor:move}.c-zoom-in{cursor:zoom-in}.c-zoom-out{cursor:zoom-out}.c-not-allowed{cursor:not-allowed}.c-auto{cursor:auto}.d-block{display:block}.d-inline{display:inline}.d-inline-block{display:inline-block}.d-flex{display:-ms-flexbox;display:flex}.d-inline-flex{display:-ms-inline-flexbox;display:inline-flex}.d-hide,.d-none{display:none!important}.d-visible{visibility:visible}.d-invisible{visibility:hidden}.text-hide{background:0 0;border:0;color:transparent;font-size:0;line-height:0;text-shadow:none}.text-assistive{border:0;clip:rect(0,0,0,0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.divider,.divider-vert{display:block;position:relative}.divider-vert[data-content]::after,.divider[data-content]::after{background:#fff;color:#bcc3ce;content:attr(data-content);display:inline-block;font-size:.7rem;padding:0 .4rem;transform:translateY(-.65rem)}.divider{border-top:.05rem solid #f1f3f5;height:.05rem;margin:.4rem 0}.divider[data-content]{margin:.8rem 0}.divider-vert{display:block;padding:.8rem}.divider-vert::before{border-left:.05rem solid #dadee4;bottom:.4rem;content:"";display:block;left:50%;position:absolute;top:.4rem;transform:translateX(-50%)}.divider-vert[data-content]::after{left:50%;padding:.2rem 0;position:absolute;top:50%;transform:translate(-50%,-50%)}.loading{color:transparent!important;min-height:.8rem;pointer-events:none;position:relative}.loading::after{animation:loading .5s infinite linear;background:0 0;border:.1rem solid #5755d9;border-radius:50%;border-right-color:transparent;border-top-color:transparent;content:"";display:block;height:.8rem;left:50%;margin-left:-.4rem;margin-top:-.4rem;opacity:1;padding:0;position:absolute;top:50%;width:.8rem;z-index:1}.loading.loading-lg{min-height:2rem}.loading.loading-lg::after{height:1.6rem;margin-left:-.8rem;margin-top:-.8rem;width:1.6rem}.clearfix::after{clear:both;content:"";display:table}.float-left{float:left!important}.float-right{float:right!important}.p-relative{position:relative!important}.p-absolute{position:absolute!important}.p-fixed{position:fixed!important}.p-sticky{position:-webkit-sticky!important;position:sticky!important}.p-centered{display:block;float:none;margin-left:auto;margin-right:auto}.flex-centered{align-items:center;display:-ms-flexbox;display:flex;-ms-flex-align:center;-ms-flex-pack:center;justify-content:center}.m-0{margin:0!important}.mb-0{margin-bottom:0!important}.ml-0{margin-left:0!important}.mr-0{margin-right:0!important}.mt-0{margin-top:0!important}.mx-0{margin-left:0!important;margin-right:0!important}.my-0{margin-bottom:0!important;margin-top:0!important}.m-1{margin:.2rem!important}.mb-1{margin-bottom:.2rem!important}.ml-1{margin-left:.2rem!important}.mr-1{margin-right:.2rem!important}.mt-1{margin-top:.2rem!important}.mx-1{margin-left:.2rem!important;margin-right:.2rem!important}.my-1{margin-bottom:.2rem!important;margin-top:.2rem!important}.m-2{margin:.4rem!important}.mb-2{margin-bottom:.4rem!important}.ml-2{margin-left:.4rem!important}.mr-2{margin-right:.4rem!important}.mt-2{margin-top:.4rem!important}.mx-2{margin-left:.4rem!important;margin-right:.4rem!important}.my-2{margin-bottom:.4rem!important;margin-top:.4rem!important}.p-0{padding:0!important}.pb-0{padding-bottom:0!important}.pl-0{padding-left:0!important}.pr-0{padding-right:0!important}.pt-0{padding-top:0!important}.px-0{padding-left:0!important;padding-right:0!important}.py-0{padding-bottom:0!important;padding-top:0!important}.p-1{padding:.2rem!important}.pb-1{padding-bottom:.2rem!important}.pl-1{padding-left:.2rem!important}.pr-1{padding-right:.2rem!important}.pt-1{padding-top:.2rem!important}.px-1{padding-left:.2rem!important;padding-right:.2rem!important}.py-1{padding-bottom:.2rem!important;padding-top:.2rem!important}.p-2{padding:.4rem!important}.pb-2{padding-bottom:.4rem!important}.pl-2{padding-left:.4rem!important}.pr-2{padding-right:.4rem!important}.pt-2{padding-top:.4rem!important}.px-2{padding-left:.4rem!important;padding-right:.4rem!important}.py-2{padding-bottom:.4rem!important;padding-top:.4rem!important}.s-rounded{border-radius:.1rem}.s-circle{border-radius:50%}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}.text-justify{text-align:justify}.text-lowercase{text-transform:lowercase}.text-uppercase{text-transform:uppercase}.text-capitalize{text-transform:capitalize}.text-normal{font-weight:400}.text-bold{font-weight:700}.text-italic{font-style:italic}.text-large{font-size:1.2em}.text-small{font-size:.9em}.text-tiny{font-size:.8em}.text-muted{opacity:.8}.text-ellipsis{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.text-clip{overflow:hidden;text-overflow:clip;white-space:nowrap}.text-break{-webkit-hyphens:auto;-ms-hyphens:auto;hyphens:auto;word-break:break-word;word-wrap:break-word} \ No newline at end of file diff --git a/defaultapps.json b/defaultapps_banglejs1.json similarity index 100% rename from defaultapps.json rename to defaultapps_banglejs1.json diff --git a/defaultapps_banglejs2.json b/defaultapps_banglejs2.json new file mode 100644 index 000000000..04bd44504 --- /dev/null +++ b/defaultapps_banglejs2.json @@ -0,0 +1 @@ +["boot","launch","antonclk","health","setting","about","widbat","widbt","widlock","widid"] diff --git a/index.html b/index.html index a5ae7bff0..e7c7c31cd 100644 --- a/index.html +++ b/index.html @@ -60,6 +60,17 @@

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 { + function getFace(){ + + const locale = require("locale"); + + var W = g.getWidth(); + var H = g.getHeight(); + var scale = W/240; + + function drawClock(){ + var now=Date(); + d=now.toString().split(' '); + var min=d[4].substr(3,2); + var sec=d[4].substr(-2); + var tm=d[4].substring(0,5); + var hr=d[4].substr(0,2); + lastmin=min; + g.reset(); + g.clearRect(0,24,W-1,H-1); + g.setColor(g.theme.fg); + g.setFontAlign(0,-1); + g.setFontVector(80*scale); + g.drawString(tm,4+W/2,H/2+24-80*scale); + g.setFontVector(36*scale); + g.setColor(g.theme.fg2); + d[1] = locale.month(now,3); + d[0] = locale.dow(now,3); + var dt=d[0]+" "+d[1]+" "+d[2];//+" "+d[3]; + g.drawString(dt,W/2,H/2+24); + g.flip(); + } + + + return {init:drawClock, tick:drawClock, tickpersec:false}; + } + + return getFace; + +})(); + diff --git a/apps/multiclock/multiclock-icon.img b/apps/multiclock/multiclock-icon.img new file mode 100644 index 0000000000000000000000000000000000000000..57e0a935f35d622d52dfb29a3e2af584d16eb56f GIT binary patch literal 1156 zcmchVy-veG5QNQ7K}V4nu>1mvce z)9La!{o86-w`QbVSCN1>fqCVZO@-spY-D&7!0yd_z2H*?v#=QgDnQN|*&omN`P>w= z_D&J!_9Y?65GOYYm3rjUA)G^9`EYPvGNn^ObDcePAmk^V<22I2iGJi|EB!PU@9(`0 zw#)-%SGIKBF#^Sh!P|X$FACES.push(eval(require("Storage").read(face)))); +var lastface = STOR.readJSON("clock.json") || {pinned:0} +var iface = lastface.pinned; +var face = FACES[iface](); +var intervalRefSec; +var intervalRefSec; +var tickTimeout; + +function stopdraw() { + if(intervalRefSec) {intervalRefSec=clearInterval(intervalRefSec);} + if(tickTimeout) {tickTimeout=clearTimeout(tickTimeout);} + g.clear(); +} + +function queueMinuteTick() { + if (tickTimeout) clearTimeout(tickTimeout); + tickTimeout = setTimeout(function() { + tickTimeout = undefined; + face.tick(); + queueMinuteTick(); + }, 60000 - (Date.now() % 60000)); +} + +function startdraw() { + g.reset(); + face.init(); + if (face.tickpersec) + intervalRefSec = setInterval(face.tick,1000); + else + queueMinuteTick(); + Bangle.drawWidgets(); +} + +var SCREENACCESS = { + withApp:true, + request:function(){ + this.withApp=false; + stopdraw(); + }, + release:function(){ + this.withapp=true; + startdraw(); + setButtons(); + } +}; + +Bangle.on('lcdPower',function(b) { + if (!SCREENACCESS.withApp) return; + if (b) { + startdraw(); + } else { + stopdraw(); + } +}); + +function setButtons(){ + function newFace(inc){ + if (!inc) Bangle.showLauncher(); + else { + var n = FACES.length-1; + iface+=inc; + iface = iface>n?0:iface<0?n:iface; + stopdraw(); + face = FACES[iface](); + startdraw(); + } + } + Bangle.setUI("leftright", newFace); +} + +E.on('kill',()=>{ + if (iface!=lastface.pinned){ + lastface.pinned=iface; + STOR.write("clock.json",lastface); + } +}); + +Bangle.loadWidgets(); +g.clear(); +startdraw(); +setButtons(); + + + diff --git a/apps/multiclock/nifty.face.js b/apps/multiclock/nifty.face.js new file mode 100644 index 000000000..2c2af6063 --- /dev/null +++ b/apps/multiclock/nifty.face.js @@ -0,0 +1,55 @@ +(() => { + function getFace(){ + + const locale = require("locale"); + const is12Hour = (require("Storage").readJSON("setting.json", 1) || {})["12hour"]; + + const scale = g.getWidth() / 176; + + const widget = 24; + + const viewport = { + width: g.getWidth(), + height: g.getHeight(), + } + + const center = { + x: viewport.width / 2, + y: Math.round(((viewport.height - widget) / 2) + widget), + } + + function d02(value) { + return ('0' + value).substr(-2); + } + + function drawClock() { + g.reset(); + g.clearRect(0, widget, viewport.width, viewport.height); + var now = new Date(); + const hour = d02(now.getHours() - (is12Hour && now.getHours() > 12 ? 12 : 0)); + const minutes = d02(now.getMinutes()); + const day = d02(now.getDay()); + const month = d02(now.getMonth() + 1); + const year = now.getFullYear(); + const month2 = locale.month(now, 3); + const day2 = locale.dow(now, 3); + g.setFontAlign(1, 0).setFont("Vector", 90 * scale); + g.drawString(hour, center.x + 32 * scale, center.y - 31 * scale); + g.drawString(minutes, center.x + 32 * scale, center.y + 46 * scale); + g.fillRect(center.x + 30 * scale, center.y - 72 * scale, center.x + 32 * scale, center.y + 74 * scale); + g.setFontAlign(-1, 0).setFont("Vector", 16 * scale); + g.drawString(year, center.x + 40 * scale, center.y - 62 * scale); + g.drawString(month, center.x + 40 * scale, center.y - 44 * scale); + g.drawString(day, center.x + 40 * scale, center.y - 26 * scale); + g.drawString(month2, center.x + 40 * scale, center.y + 48 * scale); + g.drawString(day2, center.x + 40 * scale, center.y + 66 * scale); + } + + + return {init:drawClock, tick:drawClock, tickpersec:false}; + } + + return getFace; + +})(); + diff --git a/apps/multiclock/ped.js b/apps/multiclock/ped.js deleted file mode 100644 index a0f81e2e5..000000000 --- a/apps/multiclock/ped.js +++ /dev/null @@ -1,41 +0,0 @@ -(() => { - - function getFace(){ - - function draw() { - let steps = "-"; - let show_steps = false; - - // only attempt to get steps if activepedom is loaded - if (WIDGETS.activepedom !== undefined) { - steps = WIDGETS.activepedom.getSteps(); - } else if (WIDGETS.wpedom !== undefined) { - steps = WIDGETS.wpedom.getSteps(); - } - - var d = new Date(); - var da = d.toString().split(" "); - var time = da[4].substr(0,5); - - g.reset(); - g.clearRect(0,24,239,239); - g.setFont("Vector", 80); - g.setColor(1,1,1); // white - g.setFontAlign(0, -1); - g.drawString(time, g.getWidth()/2, 60); - g.setColor(0,255,0); // green - g.setFont("Vector", 60); - g.drawString(steps, g.getWidth()/2, 160); - } - - function onSecond(){ - var t = new Date(); - if ((t.getSeconds() % 5) === 0) draw(); - } - - return {init:draw, tick:onSecond}; - } - - return getFace; - -})(); diff --git a/apps/multiclock/timdat.js b/apps/multiclock/timdat.js deleted file mode 100644 index a4a93a691..000000000 --- a/apps/multiclock/timdat.js +++ /dev/null @@ -1,47 +0,0 @@ -(() => { - var locale = require("locale"); - var dayFirst = ["en_GB", "en_IN", "en_NAV", "de_DE", "nl_NL", "fr_FR", "en_NZ", "en_AU", "de_AT", "en_IL", "es_ES", "fr_BE", "de_CH", "fr_CH", "it_CH", "it_IT", "tr_TR", "pt_BR", "cs_CZ", "pt_PT"]; - var withDot = ["de_DE", "nl_NL", "de_AT", "de_CH", "hu_HU", "cs_CZ", "sl_SI"]; - - function getFace(){ - - var lastmin=-1; - function drawClock(){ - var d=Date(); - if (d.getMinutes()==lastmin) return; - var tm=d.toString().split(' ')[4].substring(0,5); - lastmin=d.getMinutes(); - g.reset(); - g.clearRect(0,24,239,239); - var w=g.getWidth(); - g.setColor(0xffff); - g.setFontVector(80); - g.drawString(tm,4+(w-g.stringWidth(tm))/2,64); - g.setFontVector(36); - g.setColor(0x07ff); - var dt=locale.dow(d, 1) + " "; - if (dayFirst.includes(locale.name)) { - dt+=d.getDate(); - if (withDot.includes(locale.name)) { - dt+="."; - } - dt+=" " + locale.month(d, 1); - } else { - dt+=locale.month(d, 1) + " " + d.getDate(); - } - g.drawString(dt,(w-g.stringWidth(dt))/2,160); - g.flip(); - } - - function drawFirst(){ - lastmin=-1; - drawClock(); - } - - return {init:drawFirst, tick:drawClock}; - } - - return getFace; - -})(); - diff --git a/apps/multiclock/txt.js b/apps/multiclock/txt.face.js similarity index 53% rename from apps/multiclock/txt.js rename to apps/multiclock/txt.face.js index 130455176..fddc07214 100644 --- a/apps/multiclock/txt.js +++ b/apps/multiclock/txt.face.js @@ -1,8 +1,14 @@ (() => { function getFace(){ - - function drawTime(d) { + + + var W = g.getWidth(); + var H = g.getHeight(); + var scale = W/240; + var F = 44 * scale; + + function drawTime() { function convert(n){ var t0 = [" ","one","two","three","four","five","six","seven","eight","nine"]; var t1 = ["ten","eleven","twelve","thirteen","fourteen","fifteen","sixteen","seventeen","eighteen","nineteen"]; @@ -13,28 +19,25 @@ return "error"; } g.reset(); - g.clearRect(0,40,239,210); - g.setColor(1,1,1); + g.clearRect(0,24,W-1,H-1); + var d = new Date(); + g.setColor(g.theme.fg); g.setFontAlign(0,0); - g.setFont("Vector",44); + g.setFont("Vector",F); var txt = convert(d.getHours()); - g.drawString(txt.top,120,60); - g.drawString(txt.bot,120,100); + g.setColor(g.theme.fg); + g.drawString(txt.top,W/2,H/2+24-2*F); + g.setColor(g.theme.fg2); + g.drawString(txt.bot,W/2,H/2+24-F); txt = convert(d.getMinutes()); - g.drawString(txt.top,120,140); - g.drawString(txt.bot,120,180); + g.setColor(g.theme.fg); + g.drawString(txt.top,W/2,H/2+24); + g.setColor(g.theme.fg2); + g.drawString(txt.bot,W/2,H/2+24+F); } - function onSecond(){ - var t = new Date(); - if (t.getSeconds() === 0) drawTime(t); - } - function drawAll(){ - drawTime(new Date()); - } - - return {init:drawAll, tick:onSecond}; + return {init:drawTime, tick:drawTime, tickpersec:false}; } return getFace; diff --git a/apps/multiclock/txtface.jpg b/apps/multiclock/txtface.jpg deleted file mode 100644 index e3834125780c16c697d9fb22d5d3eead3cee2d30..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 51801 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 diff --git a/apps/pastel/ChangeLog b/apps/pastel/ChangeLog index e0e967166..1277f0d9d 100644 --- a/apps/pastel/ChangeLog +++ b/apps/pastel/ChangeLog @@ -2,3 +2,4 @@ 0.02: Display 12 hour clock as 12:xx not 00:xx when just into PM 0.03: Make it work with Gadgetbridge, Notifications fullscreen on a Bangle 2 0.04: Leave space at the bottom for Chrono widget, set back option at first option +0.05: Added 2 new fonts diff --git a/apps/pastel/README.md b/apps/pastel/README.md index 9e8c133ec..324c3915a 100644 --- a/apps/pastel/README.md +++ b/apps/pastel/README.md @@ -1,7 +1,7 @@ # Pastel Clock - a configurable clock with custom fonts and background * Designed specifically for Bangle 1 and Bangle 2 -* A choice of 5 different custom fonts +* A choice of 7 different custom fonts * Supports the Light and Dark themes * Has a settings menu, change font, enable/disable the grid and the date display @@ -15,3 +15,6 @@ I came up with the name Pastel due to the shade of the grid background. ![](screenshot_b1_light.jpg) ![](screenshot_b2_dark.jpg) +![](screenshot_monoton.jpg) +![](screenshot_elite.jpg) + diff --git a/apps/pastel/pastel.app.js b/apps/pastel/pastel.app.js index 98f8af7f9..1fe3e4a58 100644 --- a/apps/pastel/pastel.app.js +++ b/apps/pastel/pastel.app.js @@ -47,6 +47,16 @@ var scale = 1; // size multiplier for this font g.setFontCustom(font, 46, widths, 58+(scale<<8)+(1<<16)); }; +Graphics.prototype.setFontMonoton = function(scale) { + // Actual height 44 (43 - 0) + g.setFontCustom(atob("AAAAAAAAAAAAAAAAAAAABmwAAAAAAzYAAAAAAZsAAAAAAM2AAAAAAGbAAAAAADNgAAAAABmwAAAAAAzYAAAAAAZsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcAAAAAAD+AAAAAAf8AAAAAD/ggAAAAf8HwAAAD/g/4AAAf8H/AAAD/g/4OAAf8H/B/AD/g/4P+Af8H/B/wAfg/4P+AAMH/B/wAAA/4H+AAAD/A/4AAAB4H/AAAAAA/4AAAAAH/AAAAAAP4AAAAAAHAAAAAAAAAAAAAAAAAAAAAAAAAAA/gAAAAAH//gAAAAf//8AAAA/AAPgAAA8f/x8AAB4//+PAAB5+APxwABzwfwecAAzj//jnAA7n+P85gA7ngAPO4AbnH/xzsAdnP/+c3AN3PAHndgGzOAA5m4DbuAAO7MD9mAADN2Bs3AAB2bA2bAAAbNgbNgAANmwNmwAAGzYGzYAADZsDdmAADN2B+7AABuzAbMwABmbgNneAD3NgHZ3+/3MwBuc//nO4A7nB8HGYAM58AfOcAHeP/+OcABzx/8ecAAc+AA+cAAHH//8cAAB4//48AAAPg+B8AAAD+AP4AAAAP//wAAAAA/+AAAAAAAAAAAAAAAAAAABsAAAAAAA2AAAAAAAbAAAAAAANgAAAAAAGwAAAAAADf////8ABv////+AA3/////AAbAAAAAAAN/////wAG/////4ADYAAAAAABv////+AA3/////AAb/////gANgAAAAAAG/////4ADf////8AAAAAAAAAAAAAAAAAAAAAAAAAAGAAAAAAADcAAAA2wBs2AADbYA2bAADtsAbZgADm2AftwAHjbAN24AHNtgGzYAPO2wDZsAPebYBs2AOeNsA2bAec22AbNge87bANmwc55tgG7c8542wD9355zbYA2Z5zztsAbODzjm2ANz/nnjbADc/nnhtgBnCPHA2wA74fPAbYAOf+OANsADj8eAG2AA8A+ADbAAP/8AAAAAB/4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAAABgG6AAAAuwDdsAAG3YBs2AADZuA27AABu3A/ZgAA7dgbtwAAduwN2w2zG3YGzYbZjZsDZsNsxs2Bs2G2Y2bA2bDbMbNgbNhtmNmwNmw2zGzYG7MbdnbsD939m/d2A2Z/7PM3AbuBtwO7AOz73eeZgDc/9n+dwB3H2Y8cwAZ4HnA84AGf/5/84ADz/OP44AAeALwB4AAH/+//4AAA/+H/wAAADwAfAAAAAAAAAAAAAAAAAAAAAAAZsAAAAAB82AAAAAD+bAAAAAHzNgAAAAPjmwAAAAfHzYAAAB+P5sAAAD8fM2AAAHw+ObAAAPj8fNgAAfH4/mwAA+Ph8zYAAcfH4ZsAAA+Px82AAB8fD+bAAD4+HzNgABh8PhmwAAH4/AzYAAPx+AZsAAPD4AM2AAGHwP+bfgAfgH/NvwA/AABmwAA8AAAzYAAYAA/5t+AAAAf82/AAAAAGbAAAAAADNgAAAAAAAAAAAAAAAAAAAAAAAGAAE///ADAAGf//gBwADP//wCcABmAAADmAAz//8C7gAZ//+DMwAMwAAA3YAGf//hZsADP//xu3ABn//4zdgAzDNsNuwAZhu2GzYAMw2bDZsAGYbNhs2ADMNmw2bABmGzYbNgAzDZsdmwAZhs2M3YAMw3d+zcAGYZm+ZsADMOzgd2ABmDM883AAzB3P87AAZgZx47gAMwOcB5gAGYDn/5gADMA4/zwAAAAPADwAAAAD8fgAAAAAf/gAAAAAB+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAB///4AAAD////AAAHwAADwAAHH//8eAAHP///ngAHfgAB8wAHeH/8PcADcf//x3ADsf//+ZgBu8AADu4B2c//8zMA3d///M2AbNwAB2bgduxs2bswP2Z2/O3YGzYzbDZsDZsbths2Bs2Nmw2bA2bGzYbNgbNjZsNmwNmxs2GzYH7c2bHbsDtmbtzdmA2bM3fs3AbMHZnO7AM3BuYOZgHZAzP+dgBmAMx+cwA7gHeAcwAMgB3584AHAAc/84ABgAHHx4AAAAB4D4AAAAAf/wAAAAAD/gAAAAAAAAAAAAAAAAAAZsAAAAAAM2AAAAAAGbAAAAAADNgAAAAABmwAAAAAAzYAAAAAAZsAAAAAAM2AAAAPAGbAAAB/gDNgAAP+ABmwAD/wYAzYAf+D8AZsD/wf8AM2f8D/gAGT/gf8HgAf8D/g/wB/g/8H/AA8H/g/4MAA/8H/B+AH/g/4P+AH4H/B/wADA/4P+AAAH/B/wAAA/4P+AAAAfB/wAAAAAP+AAAAAB/wAAAAAD+AAAAAABwAAAAAAAAAAAAAAAAAAAAAAAAAGAAwAAAA/8H/gAAB//v/8AAB4B/APgADz+PP54ABn/x//OABng8eDzAB3HHOcdwA3P9z/nYA7P/d/5mAbOBmYO7ANmebvzNwP3fs392YGzc3ZmbsDZsZsxs2Bs2M2Y2bA2bGbMbNgbNjNmNmwNmxmzGzYGzYzZjZsDZsZsxs2Bs2M2Y2bA2bGbMbNgbNzNmNmwP2Zm7s3YDbv7M+7MBszt3OZuA3MGZwd2ANn/uf8zAGY+zn47gDvAc4A7gA78/Pj5gAOf/z/zwADj8cPjwAA+A/gHgAAH/9//gAAA/4P/AAAAAAAAAAAAAAAAAAAAAAAAAAAAB+AAAAAAD/4AAAAAHw/AAAAAHADwADAAHP8cABgAHf/nAA4AHeB5gDuAHcfOYAzADc/7uBdwBs8ezBmYBu4DdgbsA2Z924s3AbN+bM3bgfsxt2duwNm4zbG3YGzYZtjZsDZsM2xs2Bs2GbY2bA2bDNsbNgbNhv2NmwP242zO3YHbszbm7sBs3AAHZuA2Z///M2Abuf//O7AGzh/8ObgDc8AA+dgB3P//+dwAdx//8cwAGeAAA8wADn///44AA8///54AAPgAAB4AAB+AAPwAAAP///gAAAA//+AAAAAAAAAAAAAAAAAAAAAAAAAAAADbBmwAAABtgzYAAAA2wZsAAAAbYM2AAAANsGbAAAAG2DNgAAADbBmwAAABtgzYAAAA2wZsAAAAAAAAAAAAAAAAAAA="), 46, atob("DRYpFR0eHiImHygmDQ=="), 49+(scale<<8)+(1<<16)); +} + +Graphics.prototype.setFontSpecialElite = function(scale) { + // Actual height 40 (39 - 0) + g.setFontCustom(atob("AAAAAAAAAAAAAAAAAAAAAYAAAAAAAfwAAAAAAP/AAAAAAH/4AAAAAB/+AAAAAAf/gAAAAAH/4AAAAAB/+AAAAAAf/gAAAAAH/4AAAAAAv8AAAAAAN6AAAAAAAeAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4AAAAAAAfAAAAAAAPwAAAAAAP8AAAAAAH+AAAAAAH+AAAAAAD+AAAAAAD/AAAAAAD/AAAAAAB/AAAAAAB/AAAAAAB/AAAAAAB/gAAAAAB/gAAAAAB/gAAAAAA/gAAAAAB/wAAAAAA/4AAAAAA/wAAAAAA/4AAAAAA/4AAAAAAf8AAAAAAP8AAAAAAD8AAAAAAA8AAAAAAAGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//wAAAAP///gAAAH/9/+AAAD/gAf4AAB/AAB+AAA/AAAHwAAPAAAA+AADgAAAPgAAwAAAD4AAcAAAAfAAHAAAAHwABwAAAB8AA4AAAAfAAOAAAAHwABwAAAB8AAcAAAA/AAHgAAAPgAB+AAAH4AAPgAAD8AAD+AAD+AAA/4Af/AAAB////AAAAP///wAAAAP//gAAAAAP8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAOAAAAHAADgAAAD4AB4AAAA+AAeAAAAPgAHgAAAD4AB4AAAAeAAeAAAAHgAHgAAAB4AB4AAAAcAAeAAAAPAAH4AAP/wAB/////+AAf/////gAH/////4AB///+/+AAAAQAAPgAAAAAAB4AAAAAAAeAAAAAAAHgAAAAAAD4AAAAAAA+AAAAAAAPgAAAAAADwAAAAAAA+AAAAAAAPgAAAAAAB4AAAAAAAAAAAAAAAAAAAAAAAcAAAB+AAfwAAA/wAf/AAA/8Af/wAAf/AP/8AAGPwP//AADh8D48AAA4OB8OAAAOAAfDgAAHAAPg4AABwADwPAAAcAB8DwAAHAAeAeAABwAHAHgAAcADwB8AAHAB4APgAB4A+AB4AAPAfAAeAAD4fgADgAAf/4AA4AAD/8AAeAAAf+AAfAAAB8AAPwAAAAAAD4AAAAAAA8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/gAAAAAAf8AAB/wAH/wAAf8AB/8AAH+AAffgAB4AcDx4AAcAPAAfAAHAPwAHwABwHwAA8AAcB+AAPAAHA/gADwABw/4AA8AAcf+AAPAAHP/gAHwABz74AB8AAf8fAA/AAH8DwAPgAD/A8AHwAA/gHwP8AAPwA//+AADwAH//AAAAAA//gAAAAAD/wAAAAAAHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPgAAAAAAP8AAAAAAD/AAAAAAD/wAAAAAD/8AAAAAB/PAAAAAA/jwAAAAAfg8AAAAAPwPAAAAAH4DwAAAAD4A8HAAAD8APBwAAB+ADw8AAA+AA8PAAA/AAPDgAAPgADw8AAHwAB8/AAD+B///wAA/////8AAP/////AAB+f///wAAAAAHx8AAAAAB8PAAAAAAPDwAAAAADw8AAAAAA4PAAAAAAODwAAAAADgcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwAAAAAAB/gAAH//wf8AAB//+H/gAAf//h/4AAHJ/wf/AABwD4D/4AAeA+AAeAAHgPAAHgAB4DwAB4AAeA4AAeAAHgOAAHgAA4DgAB4AAOA8AAeAADgPAAHgAB4D4ADwAAeAeAB4AAHAHwAeAABgAfAPgAAYAD8fgAAAAA//wAAAAAH/4AAAAAAfgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB8AAAAAA//wAAAAB///AAAAB////AAAB/7//wAAA/AfB+AAAfAPAPwAAPgHgB+AAHwBwAPgAB4A8AB8AAeAPAAfAAPADwAHwADgA8AB8AA8APAAfAAPADwAHwADwA8AB8AA8AHgA+AAP8B4APgAD/wfAH4AA/8D4D8AAH/A///AAB/wH//gAAH8A//wAAA8AH/4AAAAAA/4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAPgAAAAAB/4AAAAAA/8AAAAAAP+AAAAAAD+AAAAAAAeAAAAAAAHAAAAAAADwAAAAAAB8AAAAAAAfAAAAAAAHwAAA/8AD+AAB//AA/gAB//wAP4AB//gAB+AB//AAAfwB//AAAH8A/wAAAA/A/wAAAAHw/wAAAAB8/wAAAAAffwAAAAAP/wAAAAAD/4AAAAAA/4AAAAAAP8AAAAAAD4AAAAAAAYAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAAAAAP/wAAAH8H/+AAAH/z//wAAD////+AAB///h/gAA/B/gH4AAPAP4A/AAHwB8AHwAB4APAB8AAeADgAfAAHgA4ADwABwAOAA8AAcADgAPAAHAA4ADwAB4AeAA8AAfAHwAPAADwB8AHgAA+A/gD4AAPgP4B+AAB+P/h/gAAP////wAAB/8f/4AAAP8D/8AAAAAAf8AAAAAAAYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeAAAAAAA/4APAAAA//gH4AAAP/8D/gAAP4Pg/8AADwB8P/AAB4AfD/4AAeAD4d+AAHAA+AfwADwAHgH8AA4AA8A/AAOAAPAPgADgADwD4AA8AA4B+AAPAAeAfAAB4AHgHgAAeADwD4AAHwA8A8AAA+AfA/AAAHp/h/gAAA3//+gAAAB//+gAAAAd//gAAAACf/wAAAAAH/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABwAAAABwB/AAAAB/A/8AAAA/wf/gAAAP+H/4AAAH/h/+AAAB/8f/gAAAP+H/4AAAD/h/+AAAAfwf/gAAAH8C/wAAAAAA3oAAAAAABwAAAAAAAAAAAAAAAAAAAA=="), 46, atob("ERwfHB0cHxsdHB4dEQ=="), 50+(scale<<8)+(1<<16)); +} + const SETTINGS_FILE = "pastel.json"; let settings = undefined; @@ -113,6 +123,10 @@ function draw() { g.setFontCabinSketch(); else if (settings.font == "Orbitron") g.setFontOrbitron(); + else if (settings.font == "Monoton") + g.setFontMonoton(); + else if (settings.font == "Elite") + g.setFontSpecialElite(); else g.setFontLato(); diff --git a/apps/pastel/pastel.settings.js b/apps/pastel/pastel.settings.js index 2e4afadc8..a8aadd58f 100644 --- a/apps/pastel/pastel.settings.js +++ b/apps/pastel/pastel.settings.js @@ -22,14 +22,14 @@ storage.write(SETTINGS_FILE, settings) } - var font_options = ["Lato","Architect","GochiHand","CabinSketch","Orbitron"]; + var font_options = ["Lato","Architect","GochiHand","CabinSketch","Orbitron","Monoton","Elite"]; E.showMenu({ '': { 'title': 'Pastel Clock' }, '< Back': back, 'Font': { value: 0 | font_options.indexOf(s.font), - min: 0, max: 4, + min: 0, max: 6, format: v => font_options[v], onchange: v => { s.font = font_options[v]; diff --git a/apps/pastel/screenshot_elite.jpg b/apps/pastel/screenshot_elite.jpg new file mode 100644 index 0000000000000000000000000000000000000000..b881830ed9c15b927ac46fda6b7ad4b0eb3c300c GIT binary patch literal 9486 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 xeFAyxBs-Y|mReSOecL1idHXHzv=cbKkvx zyf^Qyd9!BLRIl#-R@JV(cllS<{WABm24Kla$w~n*FaQ7py?~c>SVmb12_qFXWhq&O z_kT-(B?6!U77GCE9b8@1q~B3#>*!Mb759%|Vg`2l2mW{7P`%ePe`yDRDYkzn{XdIB zHn#wqL4{7Cx2p@(IMi8lD8~EvckKBWHvKz}`3t*fs7XL&zCtnM$A7`5|AIYSom`>i_`mH~^sN z{ln3J=KW_|{0C*Fg61KEmeUb>eFW?P3xEoc1snh~fE|iBf!DwrfcIq)kbriN|6ce@ z$$w7+H1|t8fQx z2n2$Kg@;E)Muv(YAi@K1NFZbaSbRb}A{Z20;#c$xyaJw$J2-SaeEhOD?p{9zut{hc z1+62$G%;!GxOaBzr!7l9_h zf)H`>V6buU36KbBXzA#wUvcpeF>vxC6MxVuDE>YBvH+lgpjxp(*nk+&76k%Kw^j4N z?{UzRZpXo!1<5SQPWgtMrsOM)&XGtkl*?Ubf7IrVmvmV~aIb68!^vx7%lDzJ<8HzruDE8GJNK$TS7)p>F;)QgOBm@?i@HQu!TrJmvrw zu}}p#{hWQGq7minnw$yk^~u)ITawzUk&GIvPppx?j^?7<&65c(@v#dl;V_9j8CWuB zXGkX3)~U~$%s*-y$5M!;q172ex02;*mL%%#6!ql#4#&p zOsc*p_h7khdb$vNFWBVzsNXa-#-9U!>;iG6ezUo{?Qs}~mVKTU5-M7l<(IVmOn_Iz zXX)35?dH$U(;Iej^h)$*q}?O8@KS|*Mp{{ODo-r#{`xYTHQ3xPy(6XJG)8e+b5P1M|Y%Xn$ga7s9Wt#iEkq2sr2&FnJ|9l-Y9G+Av{wZ%u0ZQxO#=+T}AYcLx!~lFRR0TQaTb zTDo=ae5B=Yj2#WbD7CGjPN-;6sE$MS%8J$2%64vA(S76~qp<%2A9Hi8ML9mNHxJv@ zV$)QKLv*dLduhcgh5NCpR+kZl{+;ud7KSkYM>0-_?YOmPnd`8N4_@GIU$K6pYtkQp z=L;aZC9Hfq8uPcNOb&p#;dr~~KY8g75u292o0_IazMNiPxf=b&F(R}Y^HlvN@rHx! zh#!wrH2ts|+b=*|+_Df$TApF4z8PR#s6zODASx|PBd*q@&WO^EP;dY3>~Ol= z57UE{kb|!6-~e7I%K$GM1&2IPN(yacAXMp~$2Kd2174;ULB^FQ_Up#_Hoa+~z7e29 zn_~)>M6?uq$VI(bMC9!@quQD$PgnE>&8t;ug3{AAUS_(~O+4|bbz#W)nu(>$qR(>Z z^Xo=JowZKiB)^U)Xw!JIrjPZ)!M@+($DJX=Tee`y5l_%n%|Wk*!+-| z`g)_j3;0s(MqUe#rw&LVADOBT3I`Ho7W%CsQi7v5$Ky|Vd= z+gCakb@oZ7b|r)X!lQX*VWt?pM=QWgZDMMS=-_g|iz&QSt4F-9A$e_~kWS@PKfDLQkK{B$e)n z7(#W*F8h=y`_4gnj8mn`?CA-f8mG)2O?qlIaUDbzdTYy0~oMldZxD5B%F4 zj8wXK!R22WpJGG+x#@k@KcmcS!}JP=*(7^#&VL`j^^t)4NTfK!V{G6HhtZ;siPv(Ry(@UtIYjY z-?G}3^&n)PI3xkCKYRfgw4^I_-j!086nhjGYgJzE?@UeIg7^RQG1_e}5?eHlF5H!H zWQp$Rzs@;pt!5p&o=T8)a9pas`Jfz0xxZ&a`}aBcGDR#XueoKX3Y_|JqJQv8)>ZnT zMfAQp)m!OGHE6;Z)A}3gQ~WdWYvty~8Lv6sCg#XgtLKl)-U`PD%LY?gZ=Q{XRjZE; zMez0&;)8V|dv2yx_q}`S6;F!fL%*61HD68dOZNzQOgJ_4F1JSey1xLjj`3PjH|aiEn@nO`yAn%V#U$qyjF_5Ub2+` zyh2;`$-~g-bT!*h8eJNnNL*Mn0biUvk2?~N`$KtawoU7gpKf#2-%Oh;efLYI_of=A z4fOe)Oj3z`EY2kavifh}(*@wWp2jT@G0&tkA5{mXlX8PYdbswlDq}xfoE?mH_1_wr z&3l=Abf!+6ot^louy@=RExGROXz!HDbK*Z~s&un03bQTpZP4fDAX&n-{==5q%oe9( z)91=VpXcF`Iy-rX6W-2M@K!s||Sl6}k5>_x1NH zDug}d9JamKex(YXR5rf=)ARxfaAaL`#f_No>#hVU-b!NOfrd<5B73}xYtQ*D5Fdu3 zjmOMgi|5SdvnTe&8$h$O!NVyTV-_Q|M(0tswg!%kY>elCO+W2@;8N)ulxue7!V9K^ zKF%<4r-a>YGODa4*jrxTTe#0x$FF(yvyF=))WYTk;t5M9f~AKXBPs`Pd9*@vUEpM+ zQmuuU2gpnOZIr4R$lQ~O`zC7o)5`m?3r`ry%qwFj21qqLD1-zdBOBmP0~_p&1Fl2U z5PBbl{olQw0!u^Z+QK!9Lqo z_>*LlUS^z;Ggxjm$gIEo@>FmT~D(O z*OibK%!;I|l6w`+U}PkG^eU7Jky~bana{T;ue$`LBu-z}Dz5^TPfCw>3%Ego;R>OB0I@1(j3P z2+|iIOpIBHaGf-b4Dw6hB6DpXA)N}*Yy}f-Q?MY#=ae~qylz@hdnvtVUC?UoNmS3? z>0Kl1{`4nZ83+Lw{H6x9L1NA~ejTWB^v_BCZ^YW|#H*nf`Ri$snMtmoCkK>Rr(NZ( z3l1fC@pOC6&pc$>p8joWw-yg5U`e}L&TqcF$26KIwT05^!e6-iT2NXYd{-QH1EtkD zW@JaL(to4XVc}lC4%v(v9qJ#93dd*F%j@iXz#nIie$+2!i#R6b+!)3|Y4yU-si{&Z zt@gU|)xT38;?E&*a2au><|JC(ia2pVGlkOX=;vlTzt}$%)_65YmVQClw#Mx81Md&n zXr6C6m9HK1uBym?NULj}R6uDp@?|zIlvZ1IM7d+d)Ie!9r`L?!ht)^)?T1J8i&r)H z>_-MA%Zgh8NgVd8Zo2}Gxa#jSTu$UD9e>C`_K)ir`AFq6HC*=`%1`S8)(XW?-L4jk z!^AkF?!CbIMU~wY|NHKhlV9TQ2{OR-&y9rO9a0m2?DlInzbN}$+ICAia&ud}EpN$e zu<%p8Idkv@pfg--Js5Xcp)ZQ&uKX{1H!6C@m-hm6c|aKiJyXD4RQwAtxH_KpK>Pw& zZgV^bTX@yxJ}E!r20TkF+tcKZj`?NQSk~K`tU_J@Z;EbVtJ)ic?EbdR-E8xcnASU{ zf*0UN@(bV?h3cpJ+DHI3Iq^)#2bWlcdeZ6%^zR1D%kLn9cY2{#7mvXaOO?a=KLE31CCCPec zvB8H}6))bXzqZK#GN}LWLC|N?bVv9YQXA=auqnPW(F~lDs~17D|puj-aZi#jW@Y};}vRMn|t@<-G)5$ z?e$Q?_Pz@qS^&6p^oYOl>631jMK%0cwYP72F!9@#qS<#s_9&x@{TayZ%z`O!jTKX@ zX?s*6Y0+lUdo>V~n4oUAOQ$o%2kLlxaIw807QC|kM9Y2+kFN7v*P1vvp4pE z@VML&PJeWgP)38fqDgrTi8r;kvs6-5IIcoN?lD|rLc{YAQTy1DB9qk=>zZ$;BXOXs z(9VYJTwIr3sN2TUu~>AC`oTkQY9y~}FQ4TE?bqms1K2*ey7@47totP zXEiY4Ht)tvi|S3iZiD;6R|)+3$Hjb${A+W3j!Zf@588pVr2f;Rr0X#Hkq*5hbkMtg zaHnPC^+rNid5G_9%*>ruBj(+rdt2=7T{;jax35RAWd;bu=VzyutJ{Cf46Q|Gdj2_en~& z{q{ti!0h9wxJ!NBal{beB_03V3)#2uXTEyN=D1?~1+Q#~%$W;goa2-IhLkir4K;xh z=6eeV2_Iom^ina~A^~_dVNN-nGK~EQm7v9a5MU9rH`mH)Ud>tmHVA8kyzho?fB%D0 zdAMio2F5y}qzf!3s^mAkEUJf~AoI75lrALY*NV~BIxm}_d;6RI3x?TaLn9W3#uD>xB4zjiv35M}R)K!g9-=~%ahZ|E>+-DH zc!fyDr}*B*KVUBMLKg(8n$e<#_PJvuPWT9n!r>Fz@f?ExDdbv2zwy@Gh57 zWh+O!ugc_hk!sF&C#~M%r)#;caBTNA<{LKfP%o*_pHvxK2cyG;5IVx!+)J-tJ?(8y zeU_4TbPoAZ^1E;BW#rT#8dW4S@20rm42xC2mKk(i5yBzgCm~aDHDd9DuG(Gz1&N2& zfEq!V{{X|j0RMB$cio{J;P=+zcF|(w_rD5OVY=2U>t$7%5xo5&^Ffo4+f{;s%j^vc z2;1|m7dE_?Bf2e<06MudZHaxp9}BttY^hfs;wX-OcKS+O|6%!1@jR@*5!YFeZHCWa zeRa$07HV!eV>96UTj>VN8KQJgG^vgzU4E`p`YmtoNuvHq9_SGp392-Ooc_&rHS%L( ztZPsLtAmR_*>?`&nbY~USO*D{=TE^5W-Td^;b+>VI5Qt1{H;H<1^67s7Qbt0?(~pNN%sukXBXx>UT2w2PQfc}>%bs&ml-2GVSWd3hb4 zxw-uyeHgcrym|x&!{MFC7fe*_VC%;f{N)RK{H{Mm7`eaFY%r^F=lSD5CB~U^*r9y2 zD271~k%xJdbhM{ffGRT12B+M7u7EdOcJFH&P1= zA&waYF=5uF7XU+Pv@_EVx?Ao`wX2{qr|tH@Z_ppaLf@$JaK4nIp)9OR;oov~ZPoCzgvxt>B;<5d*4E1FEpakMIZ`AUy`)Xu#^791Y@?nr zi_i8uQPYqI?lpA%KphWdXY$1kpXv49vFwuxPp)y7T$lBNm31M<(qD&JEEAr|vqR^~ z>YHf4<-6SGep z<|G8OTB+_8P4Y*os{IFhDtZToP42=J^??*2+d_|fZL9%%F97X*k1;w7m2BJg=5ssw zzF6O6;K}uK?jhu`5?Xo6_ijy$7{=yz*mIx!sG{*pD_?WHf3{tojYyMIGQem%k6fb7 zx4EOnpB14QZ=yur-XxgV5N>u~SSNFAy7?l4uE)aWC;FA~;c*8o>IG=T00NGM7KUDc z>aT)gQ9ft%=_eD9uSQ+DLhOE>GEm*dgwsGsrVQ;ZHxgU8EhFOfH@2m_IwV4C5OLJtG_SmxbPQVBEqsY*545cHYs*FVQ`|4mrv@P z{KbTrk{*l4-6Po*_n&^ARty{RZ^>gW23Qbpd0J` zm_`hL$)Z3Z4MQ&*5($9+Zm$w68bo!Z4nMl1zME zg)Ti+jPakUY~{!rxNq`4sVPQ`o~v0}s-4dWvJ1{^-{0@NTa?h!Du9;8*kWMiP*LoYmSMzeyub?K zfc{rGgocqZSyR${XKE?qp5*;OeF@pAi zU^o?tO!1G3V5C;(#EluGwXpZ(hIrf`d0 zLMEMG0CF$AXm^c8$B_HD>-rP_;`npNZxQzt2YpvQRzv}|k3Z+AAD=id78;d_s2~_T zVxkIWo}W8u6l|G)E9zu$toPd&U=(4={j@fDuCY$pE+5bNg1+5y85g6`r@+&jMNod} zM7DMp_W}qy4pH6*^gmZ?gT!TU9PgUMst~>7&n~|TJ+&1-u>NF$?s<}*vmN9>5klXc z$C!pMFe%SCF5(+Hw?uW$bEElpXcy&M?l?LEVq3+Ia!S8=dYmc8d2moX9Ub8z1l*Ly zL72x7!b_etXekZb&cth_AuUsy<(!oo0^QWKeoHNQ`*E=CSx?L5m5s9mvgMv7}z z99SoB*?q_m+O2K!WL4R$26*IQ%<4FMg5vi@`sMy?wenl*h3^WpW|$_=lx zH{I;I$N8A8`dE?J?X*U%Gll+$*Vw9_3)#c+SPhgMirlF?yrqraOb-FcVKMtx~mNdJocFa|2-O+=~sLL|# z%_q~dvG<#7{iGdjhVrFv0<0dlXsqSEmDx5P_(%vGg5bk8N7<=VmLyZywiT$Pk%aMTtw%*Gs)=h*Kk3f3uBoj~?SN#T-&n}}Y- zZ<6#%n_ZsXG$UyINX0$)mcR9!52Ml^GLX=Fz93M-R)pbU*?@=9uy-C2^=J-lF=u=7 zZ|QoA4YSm=`@_Dt1CPFoBAk`=0?Z=JWN`~gadU@({Dse6Oq#&!^$tA6~7 zsSX9CAJ&UWQHE%PR$QR#g5a12=_cKHgN}Rs@dbdrzA>iK$xP6V7TN}lo`=&$#Eq^e z`)Gyrb$$-^h&$^*&d|ZIVep)4e%n~Id!Vs8N8``a63YW&~@G}*~k^mxTP zoj#H%$coo2C%z^tVQpa=JuKBL?&a#@4WI3%b7CbML6~V^=GByT4HM>8VsaJ#zARq5d0^r$LiSM4pQBB zUNXm6HZ%VPwZR6mbUYvRBR(b}(5+M-4HotjLc-%0ky^F-bCLxLJD0;U$hxxBCB5j( zC3?XBauN~E^-+4ZWvhiQFlc;F23p*Rj9fn3NT_->FjMJCS~1v%ik!#k}U>-|;obd~sm=}k1E&so(%9}3S4_YV4GN4(Q@U))fvfxwUWQXzE9O<{vAIV+@Nz9uG)||el=4;f2%S=TX1vI<6CW_Q+)dWb{br+Yh@i_o8Fy6TO1>%aCyUZ)_1VZ_5vd`YIeCUN);XD@qIA@E z3vqn2u=#)9iEE8;4GWmk(Y1FS1or#3oZ?jCouobFW0nf_KQH&3HBBPd^FGeeVu1eW z#}T|Imj8KKN1%hy);Gk)Ta`DSqWx_U5FfE6=*Zw{hzmJQu+py93T)BxEmfY zT;KcX6mP~bc%ZIOy1UWuW`_N6UZ+k+cnd>b4-t5KhMb1wgd`vOYjCoNHvNujF;z6% z7^8^Of@a`0)@?OtXZ@Q*Fw$v3G}_ry5I)aj8_U!jq9bHWj{bsEZ|vhB1Qva$#eE~$ z!dr3c5$BOQ5V4@Aw$N(TKl1uFTqrWGrHTH7TGu z(ObBQ9Te3k;;<}30Gm;W*nqYP7}n8wZ*aF`AflROS?51N`GJ+Us>wb% z*rmUGVK$Jdp0U^##96qS5K^yOHeBM<5K!k!VY24>;&lV>!Xj@&6kEFQ@>%0kN*RPJ z+IQDjp7G3Ac0z4VC;`t-8L?1Q_XNfV+t_=EJU6(SI2Q3vrxwI0o3gobh#ws>GAEHU zr7s}ZAy&24?ZuvrFDvc7_eM{^)WKH7xy6bM0ma1yz{DfVhw=QTP9k2b^4nw5_Z!_HUDtq1GHh}NC zUvhm}RNr8jwopE+2+KD`P098QIYDae!h9mb0L($x z;Z{vBNf?h!1-F>VY`Re%p=F-Sh9O9bh=rUJ+gw#_6*HbS6v%`H``t__lGP<@P+uyj zZ>SG`JBy%zUo=y{$Bt!+@0D$-)1u1K-a<`i`|6|qqM6H4=Taq?gmYG|zJ62kiLjf) zarDn9fBIGGuC%gpjCUGe7N_h^LQqC&ar@>HF;ZbQYTI?49UjB^`1X*7bj!>GqJNx8hiTM;emm z=`%-aN6i;}0nQ^A$MbH_r%iN-5;VT-!Z_jtk3(RsT!gl|N0T`7&PrjA8u?f>60syn zWBENKhz9U;yu$iB`XS#;g3uucNEO|N^MYc=^>iqKt+`%&oA2H3JG-gU`lqoVb4sJT zm}_QzLzRt535U8{POl&ssP~pYK7lg5s_d`4v0rhB=~`VGGL_tTK@XX$^|Ncw--39> zIK?HaSrxN)4KUGhUDK3K8&CJD7(MJ;3UKwjWpW#~NHEZvMi4!3%}F=7UCcA}j|R=i z7@1!oo*`f)r_Ov%gH^tgkDMFrgImG*l44_}9o|mPsjWJM-=I=OPH7kDWuQHzd7%kA zJ0O2Sg-KJ=e+yPXOplYrxcKnRBQJ5i490L{RYK;$?*%ZUt%^;))G|!l6>ML}{7v|L zANvBdZSy^lex+0~df4ZCegV=~4;=#M(DWB*7arz__pWjspubL#Q6~FiY*Eh$~K5uQkuL!te52-`kE&MoXgQ+6$%% zGc8QS))YvOoN#( zWbfzK1A%jH9J+{ws}^~m!a7ES=9TsWkG&zo89E>HvDL|skpn7haX7DIsZFFi^X=?| zyoap`!8Ez-XpH48nRyU&a7$Wc{_=~-n-m5Vf6QXiZ)SQ5$w#!$7_h2p&Sk-n1ZY!LYii-?6Q-)_5$;9oHV2cE>27KY9% z1>nS|aTq2%_Se1#*xGQwXg!Szh8=n#(M!^GI@NVTKr*kePFk^M%O42P8N_orjVN0A z{XQTAKo)vxkmh&VRTpN5G|GWGLngL|;F7Ff1_(7(V}nVyi`P%M02>D`I*t6qjaiN z1P2S`66@!y9FKZ}Z}&b8Erq&)EMStXHk~r8HeGLm--~{!p!O2tV~qm%{kM;tj024; zc-^i75DQLwJ6W!rODpM@nFbIykPWP1I#~ek_$42hLAB_rWt&=sRv!g5pEJkj^IIkp zAz@naQVrmz#^^Vh^1zo}FGWxO@L@Emn3qP|u1Xda)IoNcu1T=AnBjlYJWQb{X^z}& zXYt$C*YY~w9XNq1sK`B6#|nn#Un_@c|G?uXWoyAtIZA`jkV_ViCjlt^O0Qu9g@`C) z&n!vezS0Sc0?1{EAQho_uLj>yN~J$~Qrr^jz4!~O!P#&mHcZF5GuPv2P2ps@afu(x z>$X*fnDg`dt;eHBsj8%DR~IzM_nUlFon>x$C~-Q#hoAMA_|a;Rm!!FJ-o{!jw$yY1 zOe>n#9nm4IHvKW_A;@~r4WOH~aq5j)as?)6%X@@i;vTm`gv2IYq>#&{b%pDh&u8_A zcNSU^m-j-SI+wN;jPi~R@xo=2|L#YzuZmvG#kA$z-=#M3P$<_85$7hmA^2=%*a&sn zGnK2Sd1EMRaD#M9mxn|Zd|opB#FFDv8q%mx^6*WbCtN|tsq+-H#kHm1H`Bb|YiH^x zERSIC>r|LkaBkFK{tDW3wqzlf1sT_L2>X#OuTvgh8&~~Tmf+Qp!t}l+D*}Ws9Gkfw zB&kB1a<51r`|i(dp1jdr2~ALw z7B2cm9iG`DZoB~d!KTS=DI+^&gY$ARy73tW_2~KO)bPZ?et5uUKz<4CMm}uxH_@Wm zeX+xbH~ZWuJ&LX`-ymc2gDn9S#0&Kz_jF`aT>zphS00^LYbJZ7vg~W^D|eK z;m^w}dt4IVBrei_;PM|Rg!85p)~_em@=eVqGe=sR>j&N(hmCga&zB} zMGjk!A9(g@1HCjqbw3EDFG?4W{Z8U z6<;R1D{i=In6& zGmFdNaLr^9C@l1sDp?EYHeV>II<3}3!P9zKv0#2uih4y%06Qd&m46A|{IRR|A(^ma z*>5j!G!=(FqnA;A;Z>$y8ksOoy#F@q@x32Ov!6trYslaO2W?&L6^W*-6pZ<6)}Jj} zZ#g^b4%F2(P&#cB1CXg%mDKWzro>nf4@z$usp=?&bPKoxvs+G}Uy>gh<5;b1dZq{g znJUejKg=~b+z?SKZ=buPpqinX)EiPu2Cc_24)>Re@46x5MGGY(oQ_?B4V!z+XR->} z-#eScYPk`AatXp2^5eMIazmJ@67BOj+0v>+&|znH?LJXC3` zfq5?ped0FS%p`T^xBRaIXiU{ei~-NZa5)y7KUVPN^HZbo-;1d9U6Iiq_rtqW;nFz= zzH!U4w3|R!Eo1i-YSao-5{HP!?w&Gi6uJAcuXv6V@>{U7^+PO-cAvK~P-d|qmYL`L zy-bb!A;bli;m2x^@a>r249qeS)6io)(_YtHKeBYX7;L87er%oR6;T zkp`j=zUH*OUf;EcLDn}2t7t3riP7^P%kGrJc*)6I)2%$R;Kf%Uv4p@c%k*XTeY=Qk z>U0dmLfTYB%%152R$P(5pb%({i2CYP1DKdHrDM*1OnYTH6cmk6@}+zBwA_-j+EXa>?J_T-biY*0AyA7p` zZ7|;b+N=Y)g`3^G{sT>!!h_+_j{cgbAW9l5(z*1b+jAjzrx9H{1sybxgvDt1unWWp0QOM-Ku;~3vWiC@qT55 zPj>nbKW-d$q**2uvFAN`-YZJpmeCBm>65JRHT=>S^^8lhs?JHK!RS5;8QecDyRT+` zI8V7|Oo%vOiWY5}1B45X-azm-v1=X~5t;zvUK&;N;8lJ*jdwD^O9h@z6^RxJMYf10 zI3FMuM2{R6^0)Z$wmj5HhocZ2@jv&>TPy(mCk;gA0l6Z-mp$4r}Ao#^1v zT5>*6NJJ`e0&tDnP-QV_YeH*!@T60@)97f+b`RXY74HO=mL{zv%2;ozv_5S$q}^Zggdc3NK&cA0w( zT(-AMW>2jholXT$Dn)3#-@QG)BdT%kmo3PYH;sBBd4C_n{?pwX^7ud_Bqh-uX&i?` z;mjdLqAeRHGCiSln6J%{upJOTKBKg^rmigD^sk_QY*Tgd-rhb##26%qM=o^aSOq~| zBCp+0o4GKe_(xY@ia9uNiIm^M+zr%QDg|dszN24ZGluKJve#{8K*quc?>&s_DclPGG1uaTf6R5ikI3r8 zQJYZ3%Zqo6SQB;K8ds0hJh;6*2(C_+XM0{F>1y?xd?;|j1qBkXPA!9MUcBfteojaL zUjuCp43+oK5p&4R(ewR-JEM%wCMSSPHHTui>` zhYII^SGAY~6-KTOckFgj5{F*<43ErZP&d&vTXS+~RB693l^ePV3>I4Crb(v?!yGHZxGtOaWrCk7 zT%UfBk{tNinnN#rhUIfs?%+GwaoZ}=-%%5((J&s#4>g|IxyvCycrj!CdWtam z3-uZ}!xM~nLMgRIeb+(gP2lK%i?c~>>VV#(y4VUVhRr6awOJ&?5|yI&v}qK`>k+l& z-L}%7r6u|w(xI4xxy!bFT*z++nGL^00yP-Y;_BlsULN8iafrG(tyLZu6Q>&c$(oW2 zglj49#6q#M3#)TV4!^hsQwS*3PRhBy9C>AjgSO>1SRic>b;jt#{{nWedZPdU literal 0 HcmV?d00001 diff --git a/apps/pomodo/CHANGELOG.md b/apps/pomodo/CHANGELOG.md index b8c5dd621..b4667aff8 100644 --- a/apps/pomodo/CHANGELOG.md +++ b/apps/pomodo/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## 2021-11-18 + +- [Feature] Ported to Banglejs2 + ## 2019-11-27 - [Feature] App now saves the last interval value diff --git a/apps/pomodo/ChangeLog b/apps/pomodo/ChangeLog index 5560f00bc..3630ae7b6 100644 --- a/apps/pomodo/ChangeLog +++ b/apps/pomodo/ChangeLog @@ -1 +1,2 @@ +0.02: Ported to Banglejs2. 0.01: New App! diff --git a/apps/pomodo/pomodoro.js b/apps/pomodo/pomodoro.js index 3e11739da..96d2e8d6a 100644 --- a/apps/pomodo/pomodoro.js +++ b/apps/pomodo/pomodoro.js @@ -11,8 +11,9 @@ const STATES = { var counterInterval; class State { - constructor (state) { + constructor (state, device) { this.state = state; + this.device = device; this.next = null; } @@ -47,8 +48,8 @@ class State { } class InitState extends State { - constructor (time) { - super(STATES.INIT); + constructor (device) { + super(STATES.INIT, device); this.timeCounter = parseInt(storage.read(".pomodo") || DEFAULT_TIME, 10); } @@ -58,7 +59,7 @@ class InitState extends State { } setButtons () { - setWatch(() => { + this.device.setBTN1(() => { if (this.timeCounter + 300 > 3599) { this.timeCounter = 3599; } else { @@ -67,23 +68,23 @@ class InitState extends State { this.draw(); - }, BTN1, { repeat: true }); + }); - setWatch(() => { + this.device.setBTN3(() => { if (this.timeCounter - 300 > 0) { this.timeCounter -= 300; this.draw(); } - }, BTN3, { repeat: true }); + }); - setWatch(() => { + this.device.setBTN4(() => { if (this.timeCounter - 60 > 0) { this.timeCounter -= 60; this.draw(); } - }, BTN4, { repeat: true }); + }); - setWatch(() => { + this.device.setBTN5(() => { if (this.timeCounter + 60 > 3599) { this.timeCounter = 3599; } else { @@ -92,15 +93,15 @@ class InitState extends State { this.draw(); - }, BTN5, { repeat: true }); + }); - setWatch(() => { + this.device.setBTN2(() => { this.saveTime(); - const startedState = new StartedState(this.timeCounter); + const startedState = new StartedState(this.timeCounter, this.device); this.setNext(startedState); this.next.go(); - }, BTN2, { repeat: true }); + }); } draw () { @@ -112,14 +113,14 @@ class InitState extends State { } class StartedState extends State { - constructor (timeCounter) { - super(STATES.STARTED); + constructor (timeCounter, buttons) { + super(STATES.STARTED, buttons); this.timeCounter = timeCounter; } draw () { - drawCounter(this.timeCounter, 120, 120); + drawCounter(this.timeCounter, g.getWidth() / 2, g.getHeight() / 2); } init () { @@ -137,15 +138,15 @@ class StartedState extends State { this.draw(); } - const doneState = new DoneState(); + const doneState = new DoneState(this.device); this.setNext(doneState); counterInterval = setInterval(countDown.bind(this), 1000); } } class BreakState extends State { - constructor () { - super(STATES.BREAK); + constructor (buttons) { + super(STATES.BREAK, buttons); } draw () { @@ -153,44 +154,40 @@ class BreakState extends State { } init () { - const startedState = new StartedState(TIME_BREAK); + const startedState = new StartedState(TIME_BREAK, this.device); this.setNext(startedState); this.next.go(); } } + class DoneState extends State { - constructor () { - super(STATES.DONE); + constructor (device) { + super(STATES.DONE, device); } setButtons () { - setWatch(() => { - const initState = new InitState(); - clearTimeout(this.timeout); - initState.go(); - }, BTN1, { repeat: true }); + this.device.setBTN1(() => { + }); - setWatch(() => { - const breakState = new BreakState(); - clearTimeout(this.timeout); - breakState.go(); - }, BTN3, { repeat: true }); + this.device.setBTN3(() => { + }); - setWatch(() => { - }, BTN2, { repeat: true }); + this.device.setBTN2(() => { + }); } draw () { g.clear(); - g.setFont("6x8", 2); - g.setFontAlign(0, 0, 3); - g.drawString("AGAIN", 230, 50); - g.drawString("BREAK", 230, 190); - g.setFont("Vector", 45); - g.setFontAlign(-1, -1); - - g.drawString('You\nare\na\nhero!', 50, 40); + E.showPrompt("You are a hero!", { + buttons : {"AGAIN":1,"BREAK":2} + }).then((v) => { + var nextSate = (v == 1 + ? new InitState(this.device) + : new BreakState(this.device)); + clearTimeout(this.timeout); + nextSate.go(); + }); } init () { @@ -215,13 +212,61 @@ class DoneState extends State { } } +class Bangle1 { + setBTN1(callback) { + setWatch(callback, BTN1, { repeat: true }); + } + + setBTN2(callback) { + setWatch(callback, BTN2, { repeat: true }); + } + + setBTN3(callback) { + setWatch(callback, BTN3, { repeat: true }); + } + + setBTN4(callback) { + setWatch(callback, BTN4, { repeat: true }); + } + + setBTN5(callback) { + setWatch(callback, BTN5, { repeat: true }); + } +} + +class Bangle2 { + setBTN1(callback) { + Bangle.on('touch', function(zone, e) { + if (e.y < g.getHeight() / 2) { + callback(); + } + }); + } + + setBTN2(callback) { + setWatch(callback, BTN1, { repeat: true }); + } + + setBTN3(callback) { + Bangle.on('touch', function(zone, e) { + if (e.y > g.getHeight() / 2) { + callback(); + } + }); + } + + setBTN4(callback) { } + + setBTN5(callback) { } +} + function drawCounter (currentValue, x, y) { if (currentValue < 0) { return; } - x = x || 120; - y = y || 120; + x = x || g.getWidth() / 2; + y = y || g.getHeight() / 2; let minutes = 0; let seconds = 0; @@ -249,7 +294,10 @@ function drawCounter (currentValue, x, y) { } function init () { - const initState = new InitState(); + device = (process.env.HWVERSION==1 + ? new Bangle1() + : new Bangle2()); + const initState = new InitState(device); initState.go(); } diff --git a/apps/qalarm/ChangeLog b/apps/qalarm/ChangeLog new file mode 100644 index 000000000..135e69d23 --- /dev/null +++ b/apps/qalarm/ChangeLog @@ -0,0 +1,2 @@ +0.01: First version! +0.02: Fixed alarms not working and localised days of week. \ No newline at end of file diff --git a/apps/qalarm/app-icon.js b/apps/qalarm/app-icon.js new file mode 100644 index 000000000..1a014b796 --- /dev/null +++ b/apps/qalarm/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("/wA/AH4A/AH4AF0WiF1wwtF73GB53MAAgkY4wABFqIxPEhQuXGB4vUFxYwMEpBpGBwouNGAwfFF5I1KF6ZQHGAwNLFx4wHF/4v/F/4v/AoYGDF6gaFF5AwHL7QuMBJQvWEpwvxBQ4uRGBAkJT4wuWGBIuIRjKRNF8wwXFy4wWFzIwU53NFzPN5wuR5/PGK4tBDYSNQ5wVCCwIzBAAQoIAAQWGSJ5HFDYYAQIYTCRKRIeBAAYmDAAZsJMCQAbeCAybFiQ0XFTQAIzgAGFcYvz0QAGF84wGF1AwFF1QA/AH4A/ADQ=")) diff --git a/apps/qalarm/app.js b/apps/qalarm/app.js new file mode 100644 index 000000000..64f601bf6 --- /dev/null +++ b/apps/qalarm/app.js @@ -0,0 +1,271 @@ +Bangle.loadWidgets(); +Bangle.drawWidgets(); + +let alarms = require("Storage").readJSON("qalarm.json", 1) || []; +/* +Alarm format: +{ + on : true, + t : 23400000, // Time of day since midnight in ms + msg : "Eat chocolate", // (optional) Must be set manually from the IDE + last : 0, // Last day of the month we alarmed on - so we don't alarm twice in one day! + rp : true, // Repeat + as : false, // Auto snooze + hard: true, // Whether the alarm will be like HardAlarm or not + timer : 300, // (optional) If set, this is a timer and it's the time in seconds + daysOfWeek: [true,true,true,true,true,true,true] // What days of the week the alarm is on. First item is Sunday, 2nd is Monday, etc. +} +*/ + +function formatTime(t) { + mins = 0 | (t / 60000) % 60; + hrs = 0 | (t / 3600000); + return hrs + ":" + ("0" + mins).substr(-2); +} + +function formatTimer(t) { + mins = 0 | (t / 60) % 60; + hrs = 0 | (t / 3600); + return hrs + ":" + ("0" + mins).substr(-2); +} + +function getCurrentTime() { + let time = new Date(); + return ( + time.getHours() * 3600000 + + time.getMinutes() * 60000 + + time.getSeconds() * 1000 + ); +} + +function showMainMenu() { + const menu = { + "": { title: "Alarms" }, + "New Alarm": () => showEditAlarmMenu(-1), + "New Timer": () => showEditTimerMenu(-1), + }; + alarms.forEach((alarm, idx) => { + let txt = + (alarm.timer ? "TIMER " : "ALARM ") + + (alarm.on ? "on " : "off ") + + (alarm.timer ? formatTimer(alarm.timer) : formatTime(alarm.t)); + menu[txt] = function () { + if (alarm.timer) showEditTimerMenu(idx); + else showEditAlarmMenu(idx); + }; + }); + menu["< Back"] = () => { + load(); + }; + + if (WIDGETS["qalarm"]) WIDGETS["qalarm"].reload(); + return E.showMenu(menu); +} + +function showEditAlarmMenu(alarmIndex, alarm) { + const newAlarm = alarmIndex < 0; + + if (!alarm) { + if (newAlarm) { + alarm = { + t: 43200000, + on: true, + rp: true, + as: false, + hard: false, + daysOfWeek: new Array(7).fill(true), + }; + } else { + alarm = Object.assign({}, alarms[alarmIndex]); // Copy object in case we don't save it + } + } + + let hrs = 0 | (alarm.t / 3600000); + let mins = 0 | (alarm.t / 60000) % 60; + let secs = 0 | (alarm.t / 1000) % 60; + + const menu = { + "": { title: alarm.msg ? alarm.msg : "Alarms" }, + Hours: { + value: hrs, + onchange: function (v) { + if (v < 0) v = 23; + if (v > 23) v = 0; + hrs = v; + this.value = v; + }, // no arrow fn -> preserve 'this' + }, + Minutes: { + value: mins, + onchange: function (v) { + if (v < 0) v = 59; + if (v > 59) v = 0; + mins = v; + this.value = v; + }, // no arrow fn -> preserve 'this' + }, + Seconds: { + value: secs, + onchange: function (v) { + if (v < 0) v = 59; + if (v > 59) v = 0; + secs = v; + this.value = v; + }, // no arrow fn -> preserve 'this' + }, + Enabled: { + value: alarm.on, + format: (v) => (v ? "On" : "Off"), + onchange: (v) => (alarm.on = v), + }, + Repeat: { + value: alarm.rp, + format: (v) => (v ? "Yes" : "No"), + onchange: (v) => (alarm.rp = v), + }, + "Auto snooze": { + value: alarm.as, + format: (v) => (v ? "Yes" : "No"), + onchange: (v) => (alarm.as = v), + }, + Hard: { + value: alarm.hard, + format: (v) => (v ? "Yes" : "No"), + onchange: (v) => (alarm.hard = v), + }, + "Days of week": () => showDaysMenu(alarmIndex, getAlarm()), + }; + + function getAlarm() { + alarm.t = hrs * 3600000 + mins * 60000 + secs * 1000; + + alarm.last = 0; + // If alarm is for tomorrow not today (eg, in the past), set day + if (alarm.t < getCurrentTime()) alarm.last = new Date().getDate(); + + return alarm; + } + + menu["> Save"] = function () { + if (newAlarm) alarms.push(getAlarm()); + else alarms[alarmIndex] = getAlarm(); + require("Storage").write("qalarm.json", JSON.stringify(alarms)); + eval(require("Storage").read("qalarmcheck.js")); + showMainMenu(); + }; + + if (!newAlarm) { + menu["> Delete"] = function () { + alarms.splice(alarmIndex, 1); + require("Storage").write("qalarm.json", JSON.stringify(alarms)); + eval(require("Storage").read("qalarmcheck.js")); + showMainMenu(); + }; + } + menu["< Back"] = showMainMenu; + return E.showMenu(menu); +} + +function showDaysMenu(alarmIndex, alarm) { + const menu = { + "": { title: alarm.msg ? alarm.msg : "Alarms" }, + "< Back": () => showEditAlarmMenu(alarmIndex, alarm), + }; + + for (let i = 0; i < 7; i++) { + let dayOfWeek = require("locale").dow({ getDay: () => i }); + menu[dayOfWeek] = { + value: alarm.daysOfWeek[i], + format: (v) => (v ? "Yes" : "No"), + onchange: (v) => (alarm.daysOfWeek[i] = v), + }; + } + + return E.showMenu(menu); +} + +function showEditTimerMenu(timerIndex) { + var newAlarm = timerIndex < 0; + + let alarm; + if (newAlarm) { + alarm = { + timer: 300, + on: true, + rp: false, + as: false, + hard: false, + }; + } else { + alarm = alarms[timerIndex]; + } + + let hrs = 0 | (alarm.timer / 3600); + let mins = 0 | (alarm.timer / 60) % 60; + let secs = (0 | alarm.timer) % 60; + + const menu = { + "": { title: "Timer" }, + Hours: { + value: hrs, + onchange: function (v) { + if (v < 0) v = 23; + if (v > 23) v = 0; + hrs = v; + this.value = v; + }, // no arrow fn -> preserve 'this' + }, + Minutes: { + value: mins, + onchange: function (v) { + if (v < 0) v = 59; + if (v > 59) v = 0; + mins = v; + this.value = v; + }, // no arrow fn -> preserve 'this' + }, + Seconds: { + value: secs, + onchange: function (v) { + if (v < 0) v = 59; + if (v > 59) v = 0; + secs = v; + this.value = v; + }, // no arrow fn -> preserve 'this' + }, + Enabled: { + value: alarm.on, + format: (v) => (v ? "On" : "Off"), + onchange: (v) => (alarm.on = v), + }, + Hard: { + value: alarm.hard, + format: (v) => (v ? "On" : "Off"), + onchange: (v) => (alarm.hard = v), + }, + }; + function getTimer() { + alarm.timer = hrs * 3600 + mins * 60 + secs; + alarm.t = (getCurrentTime() + alarm.timer * 1000) % 86400000; + return alarm; + } + menu["> Save"] = function () { + if (newAlarm) alarms.push(getTimer()); + else alarms[timerIndex] = getTimer(); + require("Storage").write("qalarm.json", JSON.stringify(alarms)); + eval(require("Storage").read("qalarmcheck.js")); + showMainMenu(); + }; + if (!newAlarm) { + menu["> Delete"] = function () { + alarms.splice(timerIndex, 1); + require("Storage").write("qalarm.json", JSON.stringify(alarms)); + eval(require("Storage").read("qalarmcheck.js")); + showMainMenu(); + }; + } + menu["< Back"] = showMainMenu; + return E.showMenu(menu); +} + +showMainMenu(); diff --git a/apps/qalarm/app.png b/apps/qalarm/app.png new file mode 100644 index 0000000000000000000000000000000000000000..14edf415069c0df4b92316a58719ad356affa395 GIT binary patch literal 1531 zcmVYq`X4DteAXTyb(wC0gV-yH-YTe z9r4F1jgQS2-fWq+5-C*qSHNTeP!AlFr5wu>OcT`&j4n!kSF8cp3|dz)A+!!*v$n=2 z71eHFw5Eyne3zFwj&K~P=OXh;7vQkSn{D;UO%@R4ux|1@dp_%`BcG>PkCDH!z0yt0 zBg$d#ViqX)*_$mY6bH}1OwV|K!1-yE@i;8@iQNOcuzK=&H0sWHb0F8VUaqGC@`C_~ z$I*ko0AZ>pJsxm=#FOs~*uQkM%zI;N%JyjKOvmK;D@kSmW06;gfmE>nF@0NP>%Y|{n+k(KIxO% z>>^k?v0Tx7h%ot+iwFJeRPKEVHo#DR zL|bcSVHmrC+MA+QIXZEiMp>9UREcp#1!(KA(HPkSz{CMjs~rE3i_DeBZ(Qilu-^jo z&~pjm<_S7}OOid`pLsZlj)9>Mo~7HqPafKmDQ6lfrvxA|VmFC~BtvII*BR*i6EY{k zx*8f^m&r&N(g3wnA(jM4VxHU$%r56vb_KL8-iTSdgW>aqVfTZ?dX-iOWY0nF667v} za|5glXa<}dI9>i=MjVV9FcM%Uz^sMHD5x3j5;hB-sM$U^3rI{+f} z5WN#3bx<=B%sPl9+*OhYh}pYh+6`)hTJGG+fPDpWSHZdh+5f<5FU7(tN3nbXR*APHKJN}X*n5s|1Nig;_LHaIE>+HIJeMzkHI9XJPi3OI#u zg!9=G!H2cb3+NBqS0)^%#6y`O=%P4D&7{qOk{{g3Sz02%OoB#j-002ovPDHLkV1h(U(Ov)m literal 0 HcmV?d00001 diff --git a/apps/qalarm/boot.js b/apps/qalarm/boot.js new file mode 100644 index 000000000..6713ad9e1 --- /dev/null +++ b/apps/qalarm/boot.js @@ -0,0 +1 @@ +eval(require("Storage").read("qalarmcheck.js")); diff --git a/apps/qalarm/qalarm.js b/apps/qalarm/qalarm.js new file mode 100644 index 000000000..6b31ba645 --- /dev/null +++ b/apps/qalarm/qalarm.js @@ -0,0 +1,153 @@ +// This file shows the alarm + +function formatTime(t) { + let hrs = Math.floor(t / 3600000); + let mins = Math.round((t / 60000) % 60); + return hrs + ":" + ("0" + mins).substr(-2); +} + +function getCurrentTime() { + let time = new Date(); + return ( + time.getHours() * 3600000 + + time.getMinutes() * 60000 + + time.getSeconds() * 1000 + ); +} + +function getRandomInt(max) { + return Math.floor(Math.random() * Math.floor(max)); +} + +function getRandomFromRange( + lowerRangeMin, + lowerRangeMax, + higherRangeMin, + higherRangeMax +) { + let lowerRange = lowerRangeMax - lowerRangeMin; + let higherRange = higherRangeMax - higherRangeMin; + let fullRange = lowerRange + higherRange; + let randomNum = getRandomInt(fullRange); + if (randomNum <= lowerRangeMax - lowerRangeMin) { + return randomNum + lowerRangeMin; + } else { + return randomNum + (higherRangeMin - lowerRangeMax); + } +} + +function showNumberPicker(currentGuess, randomNum) { + if (currentGuess == randomNum) { + E.showMessage("" + currentGuess + "\n PRESS ENTER", "Get to " + randomNum); + } else { + E.showMessage("" + currentGuess, "Get to " + randomNum); + } +} + +function showPrompt(msg, buzzCount, alarm) { + E.showPrompt(msg, { + title: alarm.timer ? "TIMER!" : "ALARM!", + buttons: { Sleep: true, Ok: false }, // default is sleep so it'll come back in 10 mins + }).then(function (sleep) { + buzzCount = 0; + if (sleep) { + if (alarm.ohr === undefined) alarm.ohr = alarm.t; + alarm.t += 10 / 60; // 10 minutes + require("Storage").write("qalarm.json", JSON.stringify(alarms)); + load(); + } else { + alarm.last = new Date().getDate(); + if (alarm.ohr !== undefined) { + alarm.t = alarm.ohr; + delete alarm.ohr; + } + if (!alarm.rp) alarm.on = false; + require("Storage").write("qalarm.json", JSON.stringify(alarms)); + load(); + } + }); +} + +function showAlarm(alarm) { + if ((require("Storage").readJSON("setting.json", 1) || {}).quiet > 1) return; // total silence + let msg = formatTime(alarm.t); + let buzzCount = 20; + if (alarm.msg) msg += "\n" + alarm.msg + "!"; + + if (alarm.hard) { + let okClicked = false; + let currentGuess = 10; + let randomNum = getRandomFromRange(0, 7, 13, 20); + showNumberPicker(currentGuess, randomNum); + setWatch( + (o) => { + if (!okClicked && currentGuess < 20) { + currentGuess = currentGuess + 1; + showNumberPicker(currentGuess, randomNum); + } + }, + BTN1, + { repeat: true, edge: "rising" } + ); + + setWatch( + (o) => { + if (currentGuess == randomNum) { + okClicked = true; + showPrompt(msg, buzzCount, alarm); + } + }, + BTN2, + { repeat: true, edge: "rising" } + ); + + setWatch( + (o) => { + if (!okClicked && currentGuess > 0) { + currentGuess = currentGuess - 1; + showNumberPicker(currentGuess, randomNum); + } + }, + BTN3, + { repeat: true, edge: "rising" } + ); + } else { + showPrompt(msg, buzzCount, alarm); + } + + function buzz() { + Bangle.buzz(500).then(() => { + setTimeout(() => { + Bangle.buzz(500).then(function () { + setTimeout(() => { + Bangle.buzz(2000).then(function () { + if (buzzCount--) setTimeout(buzz, 2000); + else if (alarm.as) { + // auto-snooze + buzzCount = 20; + setTimeout(buzz, 600000); // 10 minutes + } + }); + }, 100); + }); + }, 100); + }); + } + buzz(); +} + +let time = new Date(); +let t = getCurrentTime(); +let alarms = require("Storage").readJSON("qalarm.json", 1) || []; + +let active = alarms.filter( + (alarm) => + alarm.on && + alarm.t < t && + alarm.last != time.getDate() && + (alarm.timer || alarm.daysOfWeek[time.getDay()]) +); + +if (active.length) { + showAlarm(active.sort((a, b) => a.t - b.t)[0]); +} diff --git a/apps/qalarm/qalarmcheck.js b/apps/qalarm/qalarmcheck.js new file mode 100644 index 000000000..9a3f10d5e --- /dev/null +++ b/apps/qalarm/qalarmcheck.js @@ -0,0 +1,41 @@ +/** + * This file checks for upcoming alarms and schedules qalarm.js to deal with them and itself to continue doing these checks. + */ + +print("Checking for alarms..."); + +clearInterval(); + +function getCurrentTime() { + let time = new Date(); + return ( + time.getHours() * 3600000 + + time.getMinutes() * 60000 + + time.getSeconds() * 1000 + ); +} + +let time = new Date(); +let t = getCurrentTime(); + +let nextAlarms = (require("Storage").readJSON("qalarm.json", 1) || []) + .filter( + (alarm) => + alarm.on && + alarm.t > t && + alarm.last != time.getDate() && + (alarm.timer || alarm.daysOfWeek[time.getDay()]) + ) + .sort((a, b) => a.t - b.t); + +if (nextAlarms[0]) { + setTimeout(() => { + eval(require("Storage").read("qalarmcheck.js")); + load("qalarm.js"); + }, nextAlarms[0].t - t); +} else { + // No alarms found: will re-check at midnight + setTimeout(() => { + eval(require("Storage").read("qalarmcheck.js")); + }, 86400000 - t); +} diff --git a/apps/qalarm/widget.js b/apps/qalarm/widget.js new file mode 100644 index 000000000..f80aff653 --- /dev/null +++ b/apps/qalarm/widget.js @@ -0,0 +1,22 @@ +WIDGETS["qalarm"] = { + area: "tl", + width: 0, + draw: function () { + if (this.width) + g.reset().drawImage( + atob( + "GBgBAAAAAAAAABgADhhwDDwwGP8YGf+YMf+MM//MM//MA//AA//AA//AA//AA//AA//AB//gD//wD//wAAAAADwAABgAAAAAAAAA" + ), + this.x, + this.y + ); + }, + reload: function () { + WIDGETS["qalarm"].width = ( + require("Storage").readJSON("qalarm.json", 1) || [] + ).some((alarm) => alarm.on) + ? 24 + : 0; + }, +}; +WIDGETS["qalarm"].reload(); diff --git a/apps/recorder/ChangeLog b/apps/recorder/ChangeLog new file mode 100644 index 000000000..2ea6e9fa8 --- /dev/null +++ b/apps/recorder/ChangeLog @@ -0,0 +1,4 @@ +0.01: New App! +0.02: Use 'recorder.log..' rather than 'record.log..' + Fix interface.html +0.03: Fix theme and maps/graphing if no GPS diff --git a/apps/recorder/README.md b/apps/recorder/README.md new file mode 100644 index 000000000..ba53a99f2 --- /dev/null +++ b/apps/recorder/README.md @@ -0,0 +1,27 @@ +# Recorder + +![icon](app.png) + +This app allows you to record data every few seconds - it can run in background. + +Usually you'd record GPS (but this is not required). The data can later be exported as CSV, KML or GPX files via the Download button in the Bangle.js App Store entry for Recorder. + +## Usage + +First run the `Recorder` app, here you can configure what you want to record, how often, +and you can start and stop recordings. + +You can record + +* **Time** The current time +* **GPS** GPS Latitude, Longitude and Altitude +* **Steps** Steps counted by the step counter +* **HR** Heart rate + +**Note:** It is possible for other apps to record information using this app +as well. They need to define a `foobar.recorder.js` file - see the `getRecorders` +function in `widget.js` for more information. + +## Tips + +When recording GPS, it usually takes several minutes for the watch to get a [GPS fix](https://en.wikipedia.org/wiki/Time_to_first_fix). There is a grey satellite symbol, which you will see turn red when you get an actual GPS Fix. You can [upload assistant files](https://banglejs.com/apps/#assisted%20gps%20update) to speed up the time spent on getting a GPS fix. diff --git a/apps/recorder/app-icon.js b/apps/recorder/app-icon.js new file mode 100644 index 000000000..4181d2b12 --- /dev/null +++ b/apps/recorder/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEw4cA///vPWvN8kvkuu14/s3OMjN0Kf4AQ2vaCB0Ftu3oARfgdt23AGsO2NaHQT+MB2XJCJ1MyXJsAQMgUky3JkARMjIRBpIRNvMl2VJlAQLldvtmSpN+CJcbt2ECJsBregggRBv4RRdJfbgEkyVLq3ACJ1Jq3dCBMKBYIRBpFW7d0CJUDsgRBhdbtvQCJHYgUTm1IgEttuwCI8GCIMSpMwA4MVEZPoCIUkaxj7BoQRPiQRCwARNpARByARNpMJCJyNBgKjBCJy1CCJ79BfZYNDCJoxBBoQnCABBVFN4IRJPIoRLV4sCpMgCJTbECJYKFCJUJBQsJfpoA/A")) diff --git a/apps/recorder/app-settings.json b/apps/recorder/app-settings.json new file mode 100644 index 000000000..4a3117a17 --- /dev/null +++ b/apps/recorder/app-settings.json @@ -0,0 +1,6 @@ +{ + "recording":false, + "file":"record.log0.csv", + "period":10, + "record" : ["gps"] +} diff --git a/apps/recorder/app.js b/apps/recorder/app.js new file mode 100644 index 000000000..d29959e25 --- /dev/null +++ b/apps/recorder/app.js @@ -0,0 +1,412 @@ +Bangle.loadWidgets(); +Bangle.drawWidgets(); + +var settings; + +var osm; +try { // if it's installed, use the OpenStreetMap module + osm = require("openstmap"); +} catch (e) {} + +function loadSettings() { + settings = require("Storage").readJSON("recorder.json",1)||{}; + var changed = false; + if (!settings.file) { + changed = true; + settings.file = "recorder.log0.csv"; + } + if (!Array.isArray(settings.record)) { + settings.record = ["gps"]; + changed = true; + } + if (changed) + require("Storage").writeJSON("recorder.json", settings); +} +loadSettings(); + +function updateSettings() { + require("Storage").writeJSON("recorder.json", settings); + if (WIDGETS["recorder"]) + WIDGETS["recorder"].reload(); +} + +function getTrackNumber(filename) { + return parseInt(filename.match(/^recorder\.log(.*)\.csv$/)[1]||0); +} + +function showMainMenu() { + function boolFormat(v) { return v?"Yes":"No"; } + function menuRecord(id) { + return { + value: settings.record.includes(id), + format: boolFormat, + onchange: v => { + settings.recording = false; // stop recording if we change anything + settings.record = settings.record.filter(r=>r!=id); + if (v) settings.record.push(id); + updateSettings(); + } + }; + } + const mainmenu = { + '': { 'title': 'Recorder' }, + '< Back': ()=>{load();}, + 'RECORD': { + value: !!settings.recording, + format: v=>v?"On":"Off", + onchange: v => { + setTimeout(function() { + E.showMenu(); + WIDGETS["recorder"].setRecording(v).then(function() { + print("Complete"); + loadSettings(); + print(settings.recording); + showMainMenu(); + }); + }, 1); + } + }, + 'File #': { + value: getTrackNumber(settings.file), + min: 0, + max: 99, + step: 1, + onchange: v => { + settings.recording = false; // stop recording if we change anything + settings.file = "recorder.log"+v+".csv"; + updateSettings(); + } + }, + 'View Tracks': ()=>{viewTracks();}, + 'Time Period': { + value: settings.period||10, + min: 1, + max: 120, + step: 1, + format: v=>v+"s", + onchange: v => { + settings.recording = false; // stop recording if we change anything + settings.period = v; + updateSettings(); + } + } + }; + var recorders = WIDGETS["recorder"].getRecorders(); + Object.keys(recorders).forEach(id=>{ + mainmenu["Log "+recorders[id]().name] = menuRecord(id); + }); + return E.showMenu(mainmenu); +} + + + +function viewTracks() { + const menu = { + '': { 'title': 'Tracks' } + }; + var found = false; + require("Storage").list(/^recorder\.log.*\.csv$/,{sf:true}).forEach(filename=>{ + found = true; + menu["Track "+getTrackNumber(filename)] = ()=>viewTrack(filename,false); + }); + if (!found) + menu["No Tracks found"] = function(){}; + menu['< Back'] = () => { showMainMenu(); }; + return E.showMenu(menu); +} + +function getTrackInfo(filename) { + "ram" + var minLat = 90; + var maxLat = -90; + var minLong = 180; + var maxLong = -180; + var starttime, duration=0; + var f = require("Storage").open(filename,"r"); + if (f===undefined) return; + var l = f.readLine(f); + var fields, timeIdx, latIdx, lonIdx; + var nl = 0, c, n; + if (l!==undefined) { + fields = l.trim().split(","); + timeIdx = fields.indexOf("Time"); + latIdx = fields.indexOf("Latitude"); + lonIdx = fields.indexOf("Longitude"); + l = f.readLine(f); + } + if (l!==undefined) { + c = l.split(","); + starttime = parseInt(c[timeIdx]); + } + // pushed this loop together to try and bump loading speed a little + while(l!==undefined) { + ++nl;c=l.split(",");l = f.readLine(f); + if (c[latIdx]=="")continue; + n = +c[latIdx];if(n>maxLat)maxLat=n;if(nmaxLong)maxLong=n;if(nylen ? screenSize/xlen : screenSize/ylen; + return { + fn : getTrackNumber(filename), + fields : fields, + filename : filename, + time : new Date(starttime*1000), + records : nl, + minLat : minLat, maxLat : maxLat, + minLong : minLong, maxLong : maxLong, + lat : (minLat+maxLat)/2, lon : (minLong+maxLong)/2, + lfactor : lfactor, + scale : scale, + duration : Math.round(duration) + }; +} + +function asTime(v){ + var mins = Math.floor(v/60); + var secs = v-mins*60; + return ""+mins.toString()+"m "+secs.toString()+"s"; +} + +function viewTrack(filename, info) { + if (!info) { + E.showMessage("Loading...","Track "+getTrackNumber(filename)); + info = getTrackInfo(filename); + } + //console.log(info); + const menu = { + '': { 'title': 'Track '+info.fn } + }; + if (info.time) + menu[info.time.toISOString().substr(0,16).replace("T"," ")] = function(){}; + menu["Duration"] = { value : asTime(info.duration)}; + menu["Records"] = { value : ""+info.records }; + if (info.fields.includes("Latitude")) + menu['Plot Map'] = function() { + info.qOSTM = false; + plotTrack(info); + }; + if (osm && info.fields.includes("Latitude")) + menu['Plot OpenStMap'] = function() { + info.qOSTM = true; + plotTrack(info); + } + if (info.fields.includes("Altitude")) + menu['Plot Alt.'] = function() { + plotGraph(info, "Altitude"); + }; + menu['Plot Speed'] = function() { + plotGraph(info, "Speed"); + }; + // TODO: steps, heart rate? + menu['Erase'] = function() { + E.showPrompt("Delete Track?").then(function(v) { + if (v) { + settings.recording = false; + updateSettings(); + var f = require("Storage").open(filename,"r"); + f.erase(); + viewTracks(); + } else + viewTrack(n, info); + }); + }; + menu['< Back'] = () => { viewTracks(); }; + return E.showMenu(menu); +} + +function plotTrack(info) { + "ram" + + function distance(lat1,long1,lat2,long2) { "ram" + var x = (long1-long2) * Math.cos((lat1+lat2)*Math.PI/360); + var y = lat2 - lat1; + return Math.sqrt(x*x + y*y) * 6371000 * Math.PI / 180; + } + + // Function to convert lat/lon to XY + var getMapXY; + if (info.qOSTM) { + getMapXY = osm.latLonToXY.bind(osm); + } else { + getMapXY = function(lat, lon) { "ram" + return {x:cx + Math.round((long - info.lon)*info.lfactor*info.scale), + y:cy + Math.round((info.lat - lat)*info.scale)}; + }; + } + + E.showMenu(); // remove menu + E.showMessage("Drawing...","Track "+info.fn); + g.flip(); // on buffered screens, draw a not saying we're busy + g.clear(1); + var s = require("Storage"); + var W = g.getWidth(); + var H = g.getHeight(); + var cx = W/2; + var cy = 24 + (H-24)/2; + if (!info.qOSTM) { + g.setColor("#f00").fillRect(9,80,11,120).fillPoly([9,60,19,80,0,80]); + g.setColor(g.theme.fg).setFont("6x8").setFontAlign(0,0).drawString("N",10,50); + } else { + osm.lat = info.lat; + osm.lon = info.lon; + osm.draw(); + g.setColor("#000"); + } + var latIdx = info.fields.indexOf("Latitude"); + var lonIdx = info.fields.indexOf("Longitude"); + g.drawString(asTime(info.duration),10,220); + var f = require("Storage").open(info.filename,"r"); + if (f===undefined) return; + var l = f.readLine(f); + l = f.readLine(f); // skip headers + var ox=0; + var oy=0; + var olat,olong,dist=0; + var i=0, c = l.split(","); + // skip until we find our first data + while(l!==undefined && c[latIdx]=="") { + c = l.split(","); + l = f.readLine(f); + } + // now start plotting + var lat = +c[latIdx]; + var long = +c[lonIdx]; + var mp = getMapXY(lat, long); + g.moveTo(mp.x,mp.y); + g.setColor("#0f0"); + g.fillCircle(mp.x,mp.y,5); + if (info.qOSTM) g.setColor("#f09"); + else g.setColor(g.theme.fg); + l = f.readLine(f); + g.flip(); // force update + while(l!==undefined) { + c = l.split(",");l = f.readLine(f); + if (c[latIdx]=="")continue; + lat = +c[latIdx]; + long = +c[lonIdx]; + mp = getMapXY(lat, long); + g.lineTo(mp.x,mp.y); + if (info.qOSTM) g.fillCircle(mp.x,mp.y,2); // make the track more visible + var d = distance(olat,olong,lat,long); + if (!isNaN(d)) dist+=d; + olat = lat; + olong = long; + ox = mp.x; + oy = mp.y; + if (++i > 100) { g.flip();i=0; } + } + g.setColor("#f00"); + g.fillCircle(ox,oy,5); + if (info.qOSTM) g.setColor("#000"); + else g.setColor(g.theme.fg); + g.drawString(require("locale").distance(dist),120,220); + g.setFont("6x8",2); + g.setFontAlign(0,0,3); + g.drawString("Back",230,200); + setWatch(function() { + viewTrack(info.fn, info); + }, global.BTN3||BTN1); + Bangle.drawWidgets(); + g.flip(); +} + +function plotGraph(info, style) { + "ram" + E.showMenu(); // remove menu + E.showMessage("Calculating...","Track "+info.fn); + var filename = info.filename; + var infn = new Float32Array(80); + var infc = new Uint16Array(80); + var title; + var lt = 0; // last time + var tn = 0; // count for each time period + var strt, dur = info.duration; + var f = require("Storage").open(filename,"r"); + if (f===undefined) return; + var l = f.readLine(f); + l = f.readLine(f); // skip headers + var nl = 0, c, i; + var timeIdx = info.fields.indexOf("Time"); + if (l!==undefined) { + c = l.split(","); + strt = c[timeIdx]; + } + if (style=="Altitude") { + title = "Altitude (m)"; + var altIdx = info.fields.indexOf("Altitude"); + while(l!==undefined) { + ++nl;c=l.split(",");l = f.readLine(f); + if (c[altIdx]=="") continue; + i = Math.round(80*(c[timeIdx] - strt)/dur); + infn[i]+=+c[altIdx]; + infc[i]++; + } + } else if (style=="Speed") { + title = "Speed (m/s)"; + var latIdx = info.fields.indexOf("Latitude"); + var lonIdx = info.fields.indexOf("Longitude"); + // skip until we find our first data + while(l!==undefined && c[latIdx]=="") { + c = l.split(","); + l = f.readLine(f); + } + // now iterate + var p,lp = Bangle.project({lat:c[1],lon:c[2]}); + var t,dx,dy,d,lt = c[timeIdx]; + while(l!==undefined) { + ++nl;c=l.split(","); + t = c[timeIdx]; + i = Math.round(80*(t - strt)/dur); + p = Bangle.project({lat:c[latIdx],lon:c[lonIdx]}); + dx = p.x-lp.x; + dy = p.y-lp.y; + d = Math.sqrt(dx*dx+dy*dy); + if (t!=lt) { + infn[i]+=d / (t-lt); // speed + infc[i]++; + } + lp = p; + lt = t; + l = f.readLine(f); + } + } else throw new Error("Unknown type "+style); + var min=100000,max=-100000; + for (var i=0;i0) infn[i]/=infc[i]; + var n = infn[i]; + if (n>max) max=n; + if (n 8) { + grid*=2; + } + // draw + g.clear(1).setFont("6x8",1); + var r = require("graph").drawLine(g, infn, { + x:4,y:24, + width: g.getWidth()-24, + height: g.getHeight()-(24+8), + axes : true, + gridy : grid, + gridx : 50, + title: title, + xlabel : x=>Math.round(x*dur/(60*infn.length))+" min" // minutes + }); + g.setFont("6x8",2); + g.setFontAlign(0,0,3); + g.drawString("Back",230,200); + setWatch(function() { + viewTrack(info.filename, info); + }, global.BTN3||BTN1); + g.flip(); +} + +showMainMenu(); diff --git a/apps/recorder/app.png b/apps/recorder/app.png new file mode 100644 index 0000000000000000000000000000000000000000..036f5d132181624c52b034f07732d39a958903fa GIT binary patch literal 1530 zcmV*0sE5goeq8BC!bd2RBEJ`$A<#X(ZKv9FiT; z;*uCW9uG-1z*)G#XmK+iJsuB9HDGbtcSno!F6%C%G?KpxeYCJb^aaW#QF54h^C0iC z?n+;KsxsNSqx@eO{sNjj4VHs@}e3_BNyS3^mYDRek&q zdCnpMw^zxDbO|@u3@`#x<&Ng^t;?S_Qqycik-EByl;E{V|D2a+yGgUKStx)WHU$7c z4DeD$p%Hpo5?)4+)ofeVbuGL9FXV+a4*+BEH$s|UgE$)wytBPDQB5rg8Ss8sQ1-|` z^HDPiVUJ=ebz?kTK_`1KY`&~0@c3QZ6VZ$$ZUenF^S|=-`l=y`rYA#t;Uo`u2$)0{ zWgkn!O*U;rjvy$wE@WmU)NurH8Sv~Ycq`!XF9Qu-JJ64(_|I<4;f}59U^7A#DvKnD?%@Kw zsebsL!j{I$v%=?JlU9W_=~b9U7wr8XKUSc>-I3@v;PrN_BxUH~=gcO>U3%iQdXE@p z#>x{+X$%o;~@$c|Tp4ueS#+`hNkLR;XjnX6aJLW!3F20Pd(s0EnT% z;;?)K?F)eBH1ak{tjCr;XsuBuvdz0090V!_`i%Pg{eP)Q13+11R_IE7%zFi`&FNXo5A> z-j;L&Hka*0&>LK2b=zAV8@}&Q|@N<}sL>`8=#CQB5rg8z7>U5uw4N;^NwzoSZ)at^^F>Z#P2z zg|lZ?z#tMb(A3mWN(^CNE{f*QIx8=)k$^WM^Ayl0iRfyH+dyMuV?JcIPrGCipt!xh zE!uh6bMoH>P#=~|7i+E=m`rl zrGkhOJ{8fw8znLcgU&Vp_V74P78Mt7yhGmlh>-o<*)ws!fmL)HINEslClID+W5jU% z*YoEezf)fJ+<6TEyR3~d!rJ!sc9-5BwCFO>c=&KNBNq836|gz{xfK-^(tqXiob-Iy zlEh#R_InQK=^+wb1{lN3+G_$7#l$TsEPUbS-Q+)`Yh#C(dn28QBW*_XK$t7s*~7$QkMaOsS*q_7?VJ#EGjOp)hn2tz4!_Py$*r|KxPUI gJ*gXIlzWYT0cWfgM6O1!vj6}907*qoM6N<$f+S4TzyJUM literal 0 HcmV?d00001 diff --git a/apps/recorder/interface.html b/apps/recorder/interface.html new file mode 100644 index 000000000..ad0de4887 --- /dev/null +++ b/apps/recorder/interface.html @@ -0,0 +1,255 @@ + + + + + +

/ elements with colspans. + SOLUTION: making individual
+ _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