%PDF-1.5 %���� ºaâÚÎΞ-ÌE1ÍØÄ÷{òò2ÿ ÛÖ^ÔÀá TÎ{¦?§®¥kuµùÕ5sLOšuY
| Server IP : 14.207.165.8 / Your IP : 216.73.216.26 Web Server : Apache/2.4.18 (Ubuntu) System : Linux 246 4.4.0-210-generic #242-Ubuntu SMP Fri Apr 16 09:57:56 UTC 2021 x86_64 User : root ( 0) PHP Version : 7.0.33-0ubuntu0.16.04.16 Disable Function : exec,passthru,shell_exec,system,proc_open,popen,pcntl_exec MySQL : OFF | cURL : ON | WGET : ON | Perl : ON | Python : ON | Sudo : ON | Pkexec : ON Directory : /proc/thread-self/root/var/www/html/ppaobm/vendor/bower-asset/fullcalendar/src/core/ |
Upload File : |
import { listenBySelector } from './util/dom-event'
import { capitaliseFirstLetter, debounce } from './util/misc'
import { default as EmitterMixin, EmitterInterface } from './common/EmitterMixin'
import OptionsManager from './OptionsManager'
import View from './View'
import Theme from './theme/Theme'
import { OptionsInput } from './types/input-types'
import { Locale, buildLocale, parseRawLocales, RawLocaleMap } from './datelib/locale'
import { DateEnv, DateInput } from './datelib/env'
import { DateMarker, startOfDay } from './datelib/marker'
import { createFormatter } from './datelib/formatting'
import { Duration, createDuration, DurationInput } from './datelib/duration'
import reduce from './reducers/main'
import { parseDateSpan, DateSpanInput, DateSpan, buildDateSpanApi, DateSpanApi, buildDatePointApi, DatePointApi } from './structs/date-span'
import { memoize, memoizeOutput } from './util/memoize'
import { mapHash } from './util/object'
import { isObjectsSimilar, anyKeysRemoved, computeChangedProps } from './util/object-similarity'
import { DateRangeInput } from './datelib/date-range'
import DateProfileGenerator from './DateProfileGenerator'
import { EventSourceInput, parseEventSource, EventSourceHash } from './structs/event-source'
import { EventInput, parseEvent, EventDefHash } from './structs/event'
import { CalendarState, Action } from './reducers/types'
import EventSourceApi from './api/EventSourceApi'
import EventApi from './api/EventApi'
import { createEmptyEventStore, EventStore, eventTupleToStore } from './structs/event-store'
import { processScopedUiProps, EventUiHash, EventUi } from './component/event-ui'
import { buildViewSpecs, ViewSpecHash, ViewSpec } from './structs/view-spec'
import { PluginSystem } from './plugin-system'
import CalendarComponent from './CalendarComponent'
import { __assign } from 'tslib'
import { refinePluginDefs } from './options'
import DateComponent from './component/DateComponent'
import { PointerDragEvent } from './interactions/pointer'
import { InteractionSettingsInput, parseInteractionSettings, Interaction, interactionSettingsStore, InteractionClass } from './interactions/interaction'
import EventClicking from './interactions/EventClicking'
import EventHovering from './interactions/EventHovering'
import StandardTheme from './theme/StandardTheme'
import { CmdFormatterFunc } from './datelib/formatting-cmd'
import { NamedTimeZoneImplClass } from './datelib/timezone'
export interface DateClickApi extends DatePointApi {
dayEl: HTMLElement
jsEvent: UIEvent
view: View
}
export interface DateSelectionApi extends DateSpanApi {
jsEvent: UIEvent
view: View
}
export type DatePointTransform = (dateSpan: DateSpan, calendar: Calendar) => any
export type DateSpanTransform = (dateSpan: DateSpan, calendar: Calendar) => any
export type CalendarInteraction = { destroy() }
export type CalendarInteractionClass = { new(calendar: Calendar): CalendarInteraction }
export type OptionChangeHandler = (propValue: any, calendar: Calendar) => void
export type OptionChangeHandlerMap = { [propName: string]: OptionChangeHandler }
export default class Calendar {
// global handler registry
static on: EmitterInterface['on']
static off: EmitterInterface['off']
static trigger: EmitterInterface['trigger']
on: EmitterInterface['on']
one: EmitterInterface['one']
off: EmitterInterface['off']
trigger: EmitterInterface['trigger']
triggerWith: EmitterInterface['triggerWith']
hasHandlers: EmitterInterface['hasHandlers']
private parseRawLocales = memoize(parseRawLocales)
private buildLocale = memoize(buildLocale)
private buildDateEnv = memoize(buildDateEnv)
private buildTheme = memoize(buildTheme)
private buildEventUiSingleBase = memoize(this._buildEventUiSingleBase)
private buildSelectionConfig = memoize(this._buildSelectionConfig)
private buildEventUiBySource = memoizeOutput(buildEventUiBySource, isObjectsSimilar)
private buildEventUiBases = memoize(buildEventUiBases)
eventUiBases: EventUiHash // solely for validation system
selectionConfig: EventUi // doesn't need all the info EventUi provides. only validation-related. TODO: separate data structs
optionsManager: OptionsManager
viewSpecs: ViewSpecHash
dateProfileGenerators: { [viewName: string]: DateProfileGenerator }
theme: Theme
dateEnv: DateEnv
availableRawLocales: RawLocaleMap
pluginSystem: PluginSystem
defaultAllDayEventDuration: Duration
defaultTimedEventDuration: Duration
calendarInteractions: CalendarInteraction[]
interactionsStore: { [componentUid: string]: Interaction[] } = {}
removeNavLinkListener: any
windowResizeProxy: any
isHandlingWindowResize: boolean
state: CalendarState
actionQueue = []
isReducing: boolean = false
// isDisplaying: boolean = false // installed in DOM? accepting renders?
needsRerender: boolean = false // needs a render?
needsFullRerender: boolean = false
isRendering: boolean = false // currently in the executeRender function?
renderingPauseDepth: number = 0
renderableEventStore: EventStore
buildDelayedRerender = memoize(buildDelayedRerender)
delayedRerender: any
afterSizingTriggers: any = {}
isViewUpdated: boolean = false
isDatesUpdated: boolean = false
isEventsUpdated: boolean = false
el: HTMLElement
component: CalendarComponent
constructor(el: HTMLElement, overrides?: OptionsInput) {
this.el = el
this.optionsManager = new OptionsManager(overrides || {})
this.pluginSystem = new PluginSystem()
// only do once. don't do in handleOptions. because can't remove plugins
this.addPluginInputs(this.optionsManager.computed.plugins || [])
this.handleOptions(this.optionsManager.computed)
this.publiclyTrigger('_init') // for tests
this.hydrate()
this.calendarInteractions = this.pluginSystem.hooks.calendarInteractions
.map((calendarInteractionClass) => {
return new calendarInteractionClass(this)
})
}
addPluginInputs(pluginInputs) {
let pluginDefs = refinePluginDefs(pluginInputs)
for (let pluginDef of pluginDefs) {
this.pluginSystem.add(pluginDef)
}
}
// public API
get view(): View {
return this.component ? this.component.view : null
}
// Public API for rendering
// -----------------------------------------------------------------------------------------------------------------
render() {
if (!this.component) {
this.renderableEventStore = createEmptyEventStore()
this.bindHandlers()
this.executeRender()
} else {
this.requestRerender(true)
}
}
destroy() {
if (this.component) {
this.unbindHandlers()
this.component.destroy() // don't null-out. in case API needs access
this.component = null // umm ???
for (let interaction of this.calendarInteractions) {
interaction.destroy()
}
this.publiclyTrigger('_destroyed')
}
}
// Handlers
// -----------------------------------------------------------------------------------------------------------------
bindHandlers() {
// event delegation for nav links
this.removeNavLinkListener = listenBySelector(this.el, 'click', 'a[data-goto]', (ev, anchorEl) => {
let gotoOptions: any = anchorEl.getAttribute('data-goto')
gotoOptions = gotoOptions ? JSON.parse(gotoOptions) : {}
let { dateEnv } = this
let dateMarker = dateEnv.createMarker(gotoOptions.date)
let viewType = gotoOptions.type
// property like "navLinkDayClick". might be a string or a function
let customAction = this.viewOpt('navLink' + capitaliseFirstLetter(viewType) + 'Click')
if (typeof customAction === 'function') {
customAction(dateEnv.toDate(dateMarker), ev)
} else {
if (typeof customAction === 'string') {
viewType = customAction
}
this.zoomTo(dateMarker, viewType)
}
})
if (this.opt('handleWindowResize')) {
window.addEventListener('resize',
this.windowResizeProxy = debounce( // prevents rapid calls
this.windowResize.bind(this),
this.opt('windowResizeDelay')
)
)
}
}
unbindHandlers() {
this.removeNavLinkListener()
if (this.windowResizeProxy) {
window.removeEventListener('resize', this.windowResizeProxy)
this.windowResizeProxy = null
}
}
// Dispatcher
// -----------------------------------------------------------------------------------------------------------------
hydrate() {
this.state = this.buildInitialState()
let rawSources = this.opt('eventSources') || []
let singleRawSource = this.opt('events')
let sources = [] // parsed
if (singleRawSource) {
rawSources.unshift(singleRawSource)
}
for (let rawSource of rawSources) {
let source = parseEventSource(rawSource, this)
if (source) {
sources.push(source)
}
}
this.batchRendering(() => {
this.dispatch({ type: 'INIT' }) // pass in sources here?
this.dispatch({ type: 'ADD_EVENT_SOURCES', sources })
this.dispatch({
type: 'SET_VIEW_TYPE',
viewType: this.opt('defaultView') || this.pluginSystem.hooks.defaultView
})
})
}
buildInitialState(): CalendarState {
return {
viewType: null,
loadingLevel: 0,
eventSourceLoadingLevel: 0,
currentDate: this.getInitialDate(),
dateProfile: null,
eventSources: {},
eventStore: createEmptyEventStore(),
dateSelection: null,
eventSelection: '',
eventDrag: null,
eventResize: null
}
}
dispatch(action: Action) {
this.actionQueue.push(action)
if (!this.isReducing) {
this.isReducing = true
let oldState = this.state
while (this.actionQueue.length) {
this.state = this.reduce(
this.state,
this.actionQueue.shift(),
this
)
}
let newState = this.state
this.isReducing = false
if (!oldState.loadingLevel && newState.loadingLevel) {
this.publiclyTrigger('loading', [ true ])
} else if (oldState.loadingLevel && !newState.loadingLevel) {
this.publiclyTrigger('loading', [ false ])
}
let view = this.component && this.component.view
if (oldState.eventStore !== newState.eventStore || this.needsFullRerender) {
if (oldState.eventStore) {
this.isEventsUpdated = true
}
}
if (oldState.dateProfile !== newState.dateProfile || this.needsFullRerender) {
if (oldState.dateProfile && view) { // why would view be null!?
this.publiclyTrigger('datesDestroy', [
{
view,
el: view.el
}
])
}
this.isDatesUpdated = true
}
if (oldState.viewType !== newState.viewType || this.needsFullRerender) {
if (oldState.viewType && view) { // why would view be null!?
this.publiclyTrigger('viewSkeletonDestroy', [
{
view,
el: view.el
}
])
}
this.isViewUpdated = true
}
this.requestRerender()
}
}
reduce(state: CalendarState, action: Action, calendar: Calendar): CalendarState {
return reduce(state, action, calendar)
}
// Render Queue
// -----------------------------------------------------------------------------------------------------------------
requestRerender(needsFull = false) {
this.needsRerender = true
this.needsFullRerender = this.needsFullRerender || needsFull
this.delayedRerender() // will call a debounced-version of tryRerender
}
tryRerender() {
if (
this.component && // must be accepting renders
this.needsRerender && // indicates that a rerender was requested
!this.renderingPauseDepth && // not paused
!this.isRendering // not currently in the render loop
) {
this.executeRender()
}
}
batchRendering(func) {
this.renderingPauseDepth++
func()
this.renderingPauseDepth--
if (this.needsRerender) {
this.requestRerender()
}
}
// Rendering
// -----------------------------------------------------------------------------------------------------------------
executeRender() {
let { needsFullRerender } = this // save before clearing
// clear these BEFORE the render so that new values will accumulate during render
this.needsRerender = false
this.needsFullRerender = false
this.isRendering = true
this.renderComponent(needsFullRerender)
this.isRendering = false
// received a rerender request while rendering
if (this.needsRerender) {
this.delayedRerender()
}
}
/*
don't call this directly. use executeRender instead
*/
renderComponent(needsFull) {
let { state, component } = this
let { viewType } = state
let viewSpec = this.viewSpecs[viewType]
let savedScroll = (needsFull && component) ? component.view.queryScroll() : null
if (!viewSpec) {
throw new Error(`View type "${viewType}" is not valid`)
}
// if event sources are still loading and progressive rendering hasn't been enabled,
// keep rendering the last fully loaded set of events
let renderableEventStore = this.renderableEventStore =
(state.eventSourceLoadingLevel && !this.opt('progressiveEventRendering')) ?
this.renderableEventStore :
state.eventStore
let eventUiSingleBase = this.buildEventUiSingleBase(viewSpec.options)
let eventUiBySource = this.buildEventUiBySource(state.eventSources)
let eventUiBases = this.eventUiBases = this.buildEventUiBases(renderableEventStore.defs, eventUiSingleBase, eventUiBySource)
if (needsFull || !component) {
if (component) {
component.freezeHeight() // next component will unfreeze it
component.destroy()
}
component = this.component = new CalendarComponent({
calendar: this,
view: null, // HACK. will get populated by Component
dateEnv: this.dateEnv,
theme: this.theme,
options: this.optionsManager.computed
}, this.el)
}
component.receiveProps({
...state,
viewSpec,
dateProfile: state.dateProfile,
dateProfileGenerator: this.dateProfileGenerators[viewType],
eventStore: renderableEventStore,
eventUiBases,
dateSelection: state.dateSelection,
eventSelection: state.eventSelection,
eventDrag: state.eventDrag,
eventResize: state.eventResize
})
if (savedScroll) {
component.view.applyScroll(savedScroll, false)
}
if (this.isViewUpdated) {
this.isViewUpdated = false
this.publiclyTrigger('viewSkeletonRender', [
{
view: component.view,
el: component.view.el
}
])
}
if (this.isDatesUpdated) {
this.isDatesUpdated = false
this.publiclyTrigger('datesRender', [
{
view: component.view,
el: component.view.el
}
])
}
if (this.isEventsUpdated) {
this.isEventsUpdated = false
}
this.releaseAfterSizingTriggers()
}
// Options
// -----------------------------------------------------------------------------------------------------------------
/*
Not meant for public API
*/
resetOptions(options) {
let changeHandlers = this.pluginSystem.hooks.optionChangeHandlers
let oldOptions = this.optionsManager.overrides
let oldNormalOptions = {}
let normalOptions = {}
let specialOptions = {}
for (let name in oldOptions) {
if (!changeHandlers[name]) {
oldNormalOptions[name] = oldOptions[name]
}
}
for (let name in options) {
if (changeHandlers[name]) {
specialOptions[name] = options[name]
} else {
normalOptions[name] = options[name]
}
}
this.batchRendering(() => { // for if both setOptions and special options want to rerender
if (anyKeysRemoved(oldNormalOptions, normalOptions)) {
this.processOptions(options, 'reset')
} else {
this.processOptions(computeChangedProps(oldNormalOptions, normalOptions))
}
// handle special options last
for (let name in specialOptions) {
changeHandlers[name](specialOptions[name], this)
}
})
}
/*
Not meant for public API. Won't give the same precedence that setOption does
*/
setOptions(options) {
let changeHandlers = this.pluginSystem.hooks.optionChangeHandlers
let normalOptions = {}
let specialOptions = {}
for (let name in options) {
if (changeHandlers[name]) {
specialOptions[name] = options[name]
} else {
normalOptions[name] = options[name]
}
}
this.batchRendering(() => { // for if both setOptions and special options want to rerender
this.processOptions(normalOptions)
// handle special options last
for (let name in specialOptions) {
changeHandlers[name](specialOptions[name], this)
}
})
}
processOptions(options, mode?: 'dynamic' | 'reset') {
let oldDateEnv = this.dateEnv // do this before handleOptions
let isTimeZoneDirty = false
let isSizeDirty = false
let anyDifficultOptions = false
for (let name in options) {
if (/^(height|contentHeight|aspectRatio)$/.test(name)) {
isSizeDirty = true
} else if (/^(defaultDate|defaultView)$/.test(name)) {
// can't change date this way. use gotoDate instead
} else {
anyDifficultOptions = true
if (name === 'timeZone') {
isTimeZoneDirty = true
}
}
}
if (mode === 'reset') {
anyDifficultOptions = true
this.optionsManager.reset(options)
} else if (mode === 'dynamic') {
this.optionsManager.addDynamic(options) // takes higher precedence
} else {
this.optionsManager.add(options)
}
if (anyDifficultOptions) {
this.handleOptions(this.optionsManager.computed) // only for "difficult" options
this.needsFullRerender = true
this.batchRendering(() => {
if (isTimeZoneDirty) {
this.dispatch({
type: 'CHANGE_TIMEZONE',
oldDateEnv
})
}
/* HACK
has the same effect as calling this.requestRerender(true)
but recomputes the state's dateProfile
*/
this.dispatch({
type: 'SET_VIEW_TYPE',
viewType: this.state.viewType
})
})
} if (isSizeDirty) {
this.updateSize()
}
}
setOption(name: string, val) {
this.processOptions({ [name]: val }, 'dynamic')
}
getOption(name: string) { // getter, used externally
return this.optionsManager.computed[name]
}
opt(name: string) { // getter, used internally
return this.optionsManager.computed[name]
}
viewOpt(name: string) { // getter, used internally
return this.viewOpts()[name]
}
viewOpts() {
return this.viewSpecs[this.state.viewType].options
}
/*
rebuilds things based off of a complete set of refined options
*/
handleOptions(options) {
let pluginHooks = this.pluginSystem.hooks
this.defaultAllDayEventDuration = createDuration(options.defaultAllDayEventDuration)
this.defaultTimedEventDuration = createDuration(options.defaultTimedEventDuration)
this.delayedRerender = this.buildDelayedRerender(options.rerenderDelay)
this.theme = this.buildTheme(options)
let available = this.parseRawLocales(options.locales)
this.availableRawLocales = available.map
let locale = this.buildLocale(options.locale || available.defaultCode, available.map)
this.dateEnv = this.buildDateEnv(
locale,
options.timeZone,
pluginHooks.namedTimeZonedImpl,
options.firstDay,
options.weekNumberCalculation,
options.weekLabel,
pluginHooks.cmdFormatter
)
this.selectionConfig = this.buildSelectionConfig(options) // needs dateEnv. do after :(
// ineffecient to do every time?
this.viewSpecs = buildViewSpecs(
pluginHooks.views,
this.optionsManager
)
// ineffecient to do every time?
this.dateProfileGenerators = mapHash(this.viewSpecs, (viewSpec) => {
return new viewSpec.class.prototype.dateProfileGeneratorClass(viewSpec, this)
})
}
getAvailableLocaleCodes() {
return Object.keys(this.availableRawLocales)
}
_buildSelectionConfig(rawOpts) {
return processScopedUiProps('select', rawOpts, this)
}
_buildEventUiSingleBase(rawOpts) {
if (rawOpts.editable) { // so 'editable' affected events
rawOpts = { ...rawOpts, eventEditable: true }
}
return processScopedUiProps('event', rawOpts, this)
}
// Trigger
// -----------------------------------------------------------------------------------------------------------------
hasPublicHandlers(name: string): boolean {
return this.hasHandlers(name) ||
this.opt(name) // handler specified in options
}
publiclyTrigger(name: string, args?) {
let optHandler = this.opt(name)
this.triggerWith(name, this, args)
if (optHandler) {
return optHandler.apply(this, args)
}
}
publiclyTriggerAfterSizing(name, args) {
let { afterSizingTriggers } = this;
(afterSizingTriggers[name] || (afterSizingTriggers[name] = [])).push(args)
}
releaseAfterSizingTriggers() {
let { afterSizingTriggers } = this
for (let name in afterSizingTriggers) {
for (let args of afterSizingTriggers[name]) {
this.publiclyTrigger(name, args)
}
}
this.afterSizingTriggers = {}
}
// View
// -----------------------------------------------------------------------------------------------------------------
// Returns a boolean about whether the view is okay to instantiate at some point
isValidViewType(viewType: string): boolean {
return Boolean(this.viewSpecs[viewType])
}
changeView(viewType: string, dateOrRange?: DateRangeInput | DateInput) {
let dateMarker = null
if (dateOrRange) {
if ((dateOrRange as DateRangeInput).start && (dateOrRange as DateRangeInput).end) { // a range
this.optionsManager.addDynamic({ visibleRange: dateOrRange }) // will not rerender
this.handleOptions(this.optionsManager.computed) // ...but yuck
} else { // a date
dateMarker = this.dateEnv.createMarker(dateOrRange as DateInput) // just like gotoDate
}
}
this.unselect()
this.dispatch({
type: 'SET_VIEW_TYPE',
viewType,
dateMarker
})
}
// 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
zoomTo(dateMarker: DateMarker, viewType?: string) {
let spec
viewType = viewType || 'day' // day is default zoom
spec = this.viewSpecs[viewType] ||
this.getUnitViewSpec(viewType)
this.unselect()
if (spec) {
this.dispatch({
type: 'SET_VIEW_TYPE',
viewType: spec.type,
dateMarker
})
} else {
this.dispatch({
type: 'SET_DATE',
dateMarker
})
}
}
// Given a duration singular unit, like "week" or "day", finds a matching view spec.
// Preference is given to views that have corresponding buttons.
getUnitViewSpec(unit: string): ViewSpec | null {
let { component } = this
let viewTypes = []
let i
let spec
// put views that have buttons first. there will be duplicates, but oh
if (component.header) {
viewTypes.push(...component.header.viewsWithButtons)
}
if (component.footer) {
viewTypes.push(...component.footer.viewsWithButtons)
}
for (let viewType in this.viewSpecs) {
viewTypes.push(viewType)
}
for (i = 0; i < viewTypes.length; i++) {
spec = this.viewSpecs[viewTypes[i]]
if (spec) {
if (spec.singleUnit === unit) {
return spec
}
}
}
}
// Current Date
// -----------------------------------------------------------------------------------------------------------------
getInitialDate() {
let defaultDateInput = this.opt('defaultDate')
// compute the initial ambig-timezone date
if (defaultDateInput != null) {
return this.dateEnv.createMarker(defaultDateInput)
} else {
return this.getNow() // getNow already returns unzoned
}
}
prev() {
this.unselect()
this.dispatch({ type: 'PREV' })
}
next() {
this.unselect()
this.dispatch({ type: 'NEXT' })
}
prevYear() {
this.unselect()
this.dispatch({
type: 'SET_DATE',
dateMarker: this.dateEnv.addYears(this.state.currentDate, -1)
})
}
nextYear() {
this.unselect()
this.dispatch({
type: 'SET_DATE',
dateMarker: this.dateEnv.addYears(this.state.currentDate, 1)
})
}
today() {
this.unselect()
this.dispatch({
type: 'SET_DATE',
dateMarker: this.getNow()
})
}
gotoDate(zonedDateInput) {
this.unselect()
this.dispatch({
type: 'SET_DATE',
dateMarker: this.dateEnv.createMarker(zonedDateInput)
})
}
incrementDate(deltaInput) { // is public facing
let delta = createDuration(deltaInput)
if (delta) { // else, warn about invalid input?
this.unselect()
this.dispatch({
type: 'SET_DATE',
dateMarker: this.dateEnv.add(this.state.currentDate, delta)
})
}
}
// for external API
getDate(): Date {
return this.dateEnv.toDate(this.state.currentDate)
}
// Date Formatting Utils
// -----------------------------------------------------------------------------------------------------------------
formatDate(d: DateInput, formatter): string {
const { dateEnv } = this
return dateEnv.format(
dateEnv.createMarker(d),
createFormatter(formatter)
)
}
// `settings` is for formatter AND isEndExclusive
formatRange(d0: DateInput, d1: DateInput, settings) {
const { dateEnv } = this
return dateEnv.formatRange(
dateEnv.createMarker(d0),
dateEnv.createMarker(d1),
createFormatter(settings, this.opt('defaultRangeSeparator')),
settings
)
}
formatIso(d: DateInput, omitTime?: boolean) {
const { dateEnv } = this
return dateEnv.formatIso(dateEnv.createMarker(d), { omitTime })
}
// Sizing
// -----------------------------------------------------------------------------------------------------------------
windowResize(ev: Event) {
if (
!this.isHandlingWindowResize &&
this.component && // why?
(ev as any).target === window // not a jqui resize event
) {
this.isHandlingWindowResize = true
this.updateSize()
this.publiclyTrigger('windowResize', [ this.view ])
this.isHandlingWindowResize = false
}
}
updateSize() { // public
if (this.component) { // when?
this.component.updateSize(true)
}
}
// Component Registration
// -----------------------------------------------------------------------------------------------------------------
registerInteractiveComponent(component: DateComponent<any>, settingsInput: InteractionSettingsInput) {
let settings = parseInteractionSettings(component, settingsInput)
let DEFAULT_INTERACTIONS: InteractionClass[] = [
EventClicking,
EventHovering
]
let interactionClasses: InteractionClass[] = DEFAULT_INTERACTIONS.concat(
this.pluginSystem.hooks.componentInteractions
)
let interactions = interactionClasses.map((interactionClass) => {
return new interactionClass(settings)
})
this.interactionsStore[component.uid] = interactions
interactionSettingsStore[component.uid] = settings
}
unregisterInteractiveComponent(component: DateComponent<any>) {
for (let listener of this.interactionsStore[component.uid]) {
listener.destroy()
}
delete this.interactionsStore[component.uid]
delete interactionSettingsStore[component.uid]
}
// Date Selection / Event Selection / DayClick
// -----------------------------------------------------------------------------------------------------------------
// this public method receives start/end dates in any format, with any timezone
// NOTE: args were changed from v3
select(dateOrObj: DateInput | any, endDate?: DateInput) {
let selectionInput: DateSpanInput
if (endDate == null) {
if (dateOrObj.start != null) {
selectionInput = dateOrObj as DateSpanInput
} else {
selectionInput = {
start: dateOrObj,
end: null
}
}
} else {
selectionInput = {
start: dateOrObj,
end: endDate
} as DateSpanInput
}
let selection = parseDateSpan(
selectionInput,
this.dateEnv,
createDuration({ days: 1 }) // TODO: cache this?
)
if (selection) { // throw parse error otherwise?
this.dispatch({ type: 'SELECT_DATES', selection })
this.triggerDateSelect(selection)
}
}
// public method
unselect(pev?: PointerDragEvent) {
if (this.state.dateSelection) {
this.dispatch({ type: 'UNSELECT_DATES' })
this.triggerDateUnselect(pev)
}
}
triggerDateSelect(selection: DateSpan, pev?: PointerDragEvent) {
let arg = this.buildDateSpanApi(selection) as DateSelectionApi
arg.jsEvent = pev ? pev.origEvent : null
arg.view = this.view
this.publiclyTrigger('select', [ arg ])
}
triggerDateUnselect(pev?: PointerDragEvent) {
this.publiclyTrigger('unselect', [
{
jsEvent: pev ? pev.origEvent : null,
view: this.view
}
])
}
// TODO: receive pev?
triggerDateClick(dateSpan: DateSpan, dayEl: HTMLElement, view: View, ev: UIEvent) {
let arg = this.buildDatePointApi(dateSpan) as DateClickApi
arg.dayEl = dayEl
arg.jsEvent = ev
arg.view = view
this.publiclyTrigger('dateClick', [ arg ])
}
buildDatePointApi(dateSpan: DateSpan) {
let props = {} as DatePointApi
for (let transform of this.pluginSystem.hooks.datePointTransforms) {
__assign(props, transform(dateSpan, this))
}
__assign(props, buildDatePointApi(dateSpan, this.dateEnv))
return props
}
buildDateSpanApi(dateSpan: DateSpan) {
let props = {} as DateSpanApi
for (let transform of this.pluginSystem.hooks.dateSpanTransforms) {
__assign(props, transform(dateSpan, this))
}
__assign(props, buildDateSpanApi(dateSpan, this.dateEnv))
return props
}
// Date Utils
// -----------------------------------------------------------------------------------------------------------------
// Returns a DateMarker for the current date, as defined by the client's computer or from the `now` option
getNow(): DateMarker {
let now = this.opt('now')
if (typeof now === 'function') {
now = now()
}
if (now == null) {
return this.dateEnv.createNowMarker()
}
return this.dateEnv.createMarker(now)
}
// Event-Date Utilities
// -----------------------------------------------------------------------------------------------------------------
// Given an event's allDay status and start date, return what its fallback end date should be.
// TODO: rename to computeDefaultEventEnd
getDefaultEventEnd(allDay: boolean, marker: DateMarker): DateMarker {
let end = marker
if (allDay) {
end = startOfDay(end)
end = this.dateEnv.add(end, this.defaultAllDayEventDuration)
} else {
end = this.dateEnv.add(end, this.defaultTimedEventDuration)
}
return end
}
// Public Events API
// -----------------------------------------------------------------------------------------------------------------
addEvent(eventInput: EventInput, sourceInput?: EventSourceApi | string | number): EventApi | null {
if (eventInput instanceof EventApi) {
let def = eventInput._def
let instance = eventInput._instance
// not already present? don't want to add an old snapshot
if (!this.state.eventStore.defs[def.defId]) {
this.dispatch({
type: 'ADD_EVENTS',
eventStore: eventTupleToStore({ def, instance }) // TODO: better util for two args?
})
}
return eventInput
}
let sourceId
if (sourceInput instanceof EventSourceApi) {
sourceId = sourceInput.internalEventSource.sourceId
} else if (sourceInput != null) {
let 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
} else {
sourceId = sourceApi.internalEventSource.sourceId
}
}
let tuple = parseEvent(eventInput, sourceId, this)
if (tuple) {
this.dispatch({
type: 'ADD_EVENTS',
eventStore: eventTupleToStore(tuple)
})
return new EventApi(
this,
tuple.def,
tuple.def.recurringDef ? null : tuple.instance
)
}
return null
}
// TODO: optimize
getEventById(id: string): EventApi | null {
let { defs, instances } = this.state.eventStore
id = String(id)
for (let defId in defs) {
let def = defs[defId]
if (def.publicId === id) {
if (def.recurringDef) {
return new EventApi(this, def, null)
} else {
for (let instanceId in instances) {
let instance = instances[instanceId]
if (instance.defId === def.defId) {
return new EventApi(this, def, instance)
}
}
}
}
}
return null
}
getEvents(): EventApi[] {
let { defs, instances } = this.state.eventStore
let eventApis: EventApi[] = []
for (let id in instances) {
let instance = instances[id]
let def = defs[instance.defId]
eventApis.push(new EventApi(this, def, instance))
}
return eventApis
}
removeAllEvents() {
this.dispatch({ type: 'REMOVE_ALL_EVENTS' })
}
rerenderEvents() { // API method. destroys old events if previously rendered.
this.dispatch({ type: 'RESET_EVENTS' })
}
// Public Event Sources API
// -----------------------------------------------------------------------------------------------------------------
getEventSources(): EventSourceApi[] {
let sourceHash = this.state.eventSources
let sourceApis: EventSourceApi[] = []
for (let internalId in sourceHash) {
sourceApis.push(new EventSourceApi(this, sourceHash[internalId]))
}
return sourceApis
}
getEventSourceById(id: string | number): EventSourceApi | null {
let sourceHash = this.state.eventSources
id = String(id)
for (let sourceId in sourceHash) {
if (sourceHash[sourceId].publicId === id) {
return new EventSourceApi(this, sourceHash[sourceId])
}
}
return null
}
addEventSource(sourceInput: EventSourceInput): EventSourceApi {
if (sourceInput instanceof EventSourceApi) {
// not already present? don't want to add an old snapshot
if (!this.state.eventSources[sourceInput.internalEventSource.sourceId]) {
this.dispatch({
type: 'ADD_EVENT_SOURCES',
sources: [ sourceInput.internalEventSource ]
})
}
return sourceInput
}
let eventSource = parseEventSource(sourceInput, this)
if (eventSource) { // TODO: error otherwise?
this.dispatch({ type: 'ADD_EVENT_SOURCES', sources: [ eventSource ] })
return new EventSourceApi(this, eventSource)
}
return null
}
removeAllEventSources() {
this.dispatch({ type: 'REMOVE_ALL_EVENT_SOURCES' })
}
refetchEvents() {
this.dispatch({ type: 'FETCH_EVENT_SOURCES' })
}
// Scroll
// -----------------------------------------------------------------------------------------------------------------
scrollToTime(timeInput: DurationInput) {
let time = createDuration(timeInput)
if (time) {
this.component.view.scrollToTime(time)
}
}
}
EmitterMixin.mixInto(Calendar)
// for memoizers
// -----------------------------------------------------------------------------------------------------------------
function buildDateEnv(locale: Locale, timeZone, namedTimeZoneImpl: NamedTimeZoneImplClass, firstDay, weekNumberCalculation, weekLabel, cmdFormatter: CmdFormatterFunc) {
return new DateEnv({
calendarSystem: 'gregory', // TODO: make this a setting
timeZone,
namedTimeZoneImpl,
locale,
weekNumberCalculation,
firstDay,
weekLabel,
cmdFormatter
})
}
function buildTheme(this: Calendar, calendarOptions) {
let themeClass = this.pluginSystem.hooks.themeClasses[calendarOptions.themeSystem] || StandardTheme
return new themeClass(calendarOptions)
}
function buildDelayedRerender(this: Calendar, wait) {
let func = this.tryRerender.bind(this)
if (wait != null) {
func = debounce(func, wait)
}
return func
}
function buildEventUiBySource(eventSources: EventSourceHash): EventUiHash {
return mapHash(eventSources, function(eventSource) {
return eventSource.ui
})
}
function buildEventUiBases(eventDefs: EventDefHash, eventUiSingleBase: EventUi, eventUiBySource: EventUiHash) {
let eventUiBases: EventUiHash = { '': eventUiSingleBase }
for (let defId in eventDefs) {
let def = eventDefs[defId]
if (def.sourceId && eventUiBySource[def.sourceId]) {
eventUiBases[defId] = eventUiBySource[def.sourceId]
}
}
return eventUiBases
}