Просмотр исходного кода

make axis optional. move slat state higher up

Adam Shaw 6 лет назад
Родитель
Сommit
108e9d72ce

+ 1 - 1
packages-premium

@@ -1 +1 @@
-Subproject commit 5778875cca3cf53c70d33d0fa9ccd442eaeb3878
+Subproject commit 9d340fbe09cac773793cf34ea06838a74355a255

+ 9 - 1
packages/timegrid/src/DayTimeCols.tsx

@@ -15,15 +15,20 @@ import {
   Hit,
   ComponentContext,
   NowTimer,
-  CssDimValue
+  CssDimValue,
+  Duration
 } from '@fullcalendar/core'
 import TimeColsSeg from './TimeColsSeg'
 import TimeCols from './TimeCols'
+import { TimeSlatMeta } from './TimeColsSlats'
 
 
 export interface DayTimeColsProps {
   dateProfile: DateProfile | null
   dayTableModel: DayTableModel
+  axis: boolean
+  slotDuration: Duration
+  slatMetas: TimeSlatMeta[]
   businessHours: EventStore
   eventStore: EventStore
   eventUiBases: EventUiHash
@@ -63,6 +68,9 @@ export default class DayTimeCols extends DateComponent<DayTimeColsProps> {
             rootElRef={this.handleRootEl}
             {...this.slicer.sliceProps(props, dateProfile, null, context.calendar, dayRanges)}
             dateProfile={dateProfile}
+            axis={props.axis}
+            slatMetas={props.slatMetas}
+            slotDuration={props.slotDuration}
             cells={dayTableModel.cells[0]}
             tableColGroupNode={props.tableColGroupNode}
             tableMinWidth={props.tableMinWidth}

+ 15 - 4
packages/timegrid/src/DayTimeColsView.tsx

@@ -7,31 +7,39 @@ import {
   DayTableModel,
   memoize,
   ViewProps,
-  ChunkContentCallbackArgs
+  ChunkContentCallbackArgs,
+  createDuration
 } from '@fullcalendar/core'
 import { DayTable } from '@fullcalendar/daygrid'
 import TimeColsView from './TimeColsView'
 import DayTimeCols from './DayTimeCols'
+import { buildSlatMetas } from './TimeColsSlats'
 
 
 export default class DayTimeColsView extends TimeColsView {
 
   private buildTimeColsModel = memoize(buildTimeColsModel)
+  private parseSlotDuration = memoize(createDuration)
+  private buildSlatMetas = memoize(buildSlatMetas)
 
 
   render(props: ViewProps, state: {}, context: ComponentContext) {
     let { dateProfile, dateProfileGenerator } = props
-    let { nextDayThreshold, options } = context
+    let { nextDayThreshold, options, dateEnv } = context
     let dayTableModel = this.buildTimeColsModel(dateProfile, dateProfileGenerator)
     let splitProps = this.allDaySplitter.splitProps(props)
+    let slotDuration = this.parseSlotDuration(options.slotDuration)
+    let slatMetas = this.buildSlatMetas(dateProfile, options.slotLabelInterval, slotDuration, dateEnv)
+    let axis = true
 
     return this.renderLayout(
+      axis,
       options.columnHeader &&
         <DayHeader
           dateProfile={dateProfile}
           dates={dayTableModel.headerDates}
           datesRepDistinctDays={true}
-          renderIntro={this.renderHeadIntro}
+          renderIntro={axis ? this.renderHeadAxis : null}
         />,
       options.allDaySlot && ((contentArg: ChunkContentCallbackArgs) => (
         <DayTable
@@ -40,7 +48,7 @@ export default class DayTimeColsView extends TimeColsView {
           dayTableModel={dayTableModel}
           nextDayThreshold={nextDayThreshold}
           colGroupNode={contentArg.tableColGroupNode}
-          renderRowIntro={this.renderTableRowIntro}
+          renderRowIntro={axis ? this.renderTableRowAxis : null}
           eventLimit={this.getAllDayEventLimit()}
           vGrowRows={false}
           headerAlignElRef={this.headerElRef}
@@ -53,6 +61,9 @@ export default class DayTimeColsView extends TimeColsView {
           {...splitProps['timed']}
           dateProfile={dateProfile}
           dayTableModel={dayTableModel}
+          axis={axis}
+          slotDuration={slotDuration}
+          slatMetas={slatMetas}
           forPrint={props.forPrint}
           tableColGroupNode={contentArg.tableColGroupNode}
           tableMinWidth={contentArg.tableMinWidth}

+ 16 - 21
packages/timegrid/src/TimeCols.tsx

@@ -12,13 +12,13 @@ import {
   memoize,
   CssDimValue,
   PositionCache,
-  Duration,
   ScrollResponder,
   ScrollRequest,
-  DateRange
+  DateRange,
+  Duration
 } from '@fullcalendar/core'
 import { TableCellModel } from '@fullcalendar/daygrid' // TODO: good to use this interface?
-import TimeColsSlats from './TimeColsSlats'
+import TimeColsSlats, { TimeSlatMeta } from './TimeColsSlats'
 import TimeColsContent from './TimeColsContent'
 import TimeColsSlatsCoords from './TimeColsSlatsCoords'
 import TimeColsSeg from './TimeColsSeg'
@@ -27,6 +27,7 @@ import TimeColsSeg from './TimeColsSeg'
 export interface TimeColsProps {
   dateProfile: DateProfile
   cells: TableCellModel[]
+  slotDuration: Duration
   nowDate: DateMarker
   todayRange: DateRange
   businessHourSegs: TimeColsSeg[]
@@ -45,6 +46,8 @@ export interface TimeColsProps {
   nowIndicatorSegs: TimeColsSeg[]
   onScrollTopRequest?: (scrollTop: number) => void
   forPrint: boolean
+  axis: boolean
+  slatMetas: TimeSlatMeta[]
 }
 
 interface TimeColsState {
@@ -58,25 +61,19 @@ interface TimeColsState {
 export default class TimeCols extends BaseComponent<TimeColsProps, TimeColsState> {
 
   private processSlotOptions = memoize(processSlotOptions)
-  private snapDuration: Duration
-  private snapsPerSlot: number
   private scrollResponder: ScrollResponder
   private colCoords: PositionCache
 
 
   render(props: TimeColsProps, state: TimeColsState, context: ComponentContext) {
-    let { options } = context
     let { dateProfile } = props
 
-    let { slotDuration, snapDuration, snapsPerSlot } = this.processSlotOptions(options)
-    this.snapDuration = snapDuration
-    this.snapsPerSlot = snapsPerSlot
-
     return (
       <div class='fc-timegrid' ref={props.rootElRef}>
         <TimeColsSlats
           dateProfile={dateProfile}
-          slotDuration={slotDuration}
+          axis={props.axis}
+          slatMetas={props.slatMetas}
           clientWidth={props.clientWidth}
           minHeight={props.vGrowRows ? props.clientHeight : ''}
           tableMinWidth={props.tableMinWidth}
@@ -86,6 +83,7 @@ export default class TimeCols extends BaseComponent<TimeColsProps, TimeColsState
         <TimeColsContent
           cells={props.cells}
           dateProfile={props.dateProfile}
+          axis={props.axis}
           businessHourSegs={props.businessHourSegs}
           bgEventSegs={props.bgEventSegs}
           fgEventSegs={props.fgEventSegs}
@@ -153,9 +151,10 @@ export default class TimeCols extends BaseComponent<TimeColsProps, TimeColsState
 
 
   positionToHit(positionLeft, positionTop) {
-    let { dateEnv } = this.context
-    let { snapsPerSlot, snapDuration, colCoords } = this
+    let { dateEnv, options } = this.context
+    let { colCoords } = this
     let { slatCoords } = this.state
+    let { snapDuration, snapsPerSlot } = this.processSlotOptions(this.props.slotDuration, options.snapDuration)
 
     let colIndex = colCoords.leftToIndex(positionLeft)
     let slatIndex = slatCoords.positions.topToIndex(positionTop)
@@ -196,13 +195,9 @@ export default class TimeCols extends BaseComponent<TimeColsProps, TimeColsState
 }
 
 
-function processSlotOptions(options) {
-  let { slotDuration, snapDuration } = options
-  let snapsPerSlot
-
-  slotDuration = createDuration(slotDuration)
-  snapDuration = snapDuration ? createDuration(snapDuration) : slotDuration
-  snapsPerSlot = wholeDivideDurations(slotDuration, snapDuration)
+function processSlotOptions(slotDuration: Duration, snapDurationInput) {
+  let snapDuration = snapDurationInput ? createDuration(snapDurationInput) : slotDuration
+  let snapsPerSlot = wholeDivideDurations(slotDuration, snapDuration)
 
   if (snapsPerSlot === null) {
     snapDuration = slotDuration
@@ -210,5 +205,5 @@ function processSlotOptions(options) {
     // TODO: say warning?
   }
 
-  return { slotDuration, snapDuration, snapsPerSlot }
+  return { snapDuration, snapsPerSlot }
 }

+ 4 - 1
packages/timegrid/src/TimeColsContent.tsx

@@ -19,6 +19,7 @@ import TimeCol from './TimeCol'
 
 
 export interface TimeColsContentProps {
+  axis: boolean
   cells: TableCellModel[]
   dateProfile: DateProfile
   nowDate: DateMarker
@@ -77,7 +78,9 @@ export default class TimeColsContent extends BaseComponent<TimeColsContentProps>
           {props.tableColGroupNode}
           <tbody>
             <tr>
-              <td class='fc-timegrid-cols-axis' />
+              {props.axis &&
+                <td class='fc-timegrid-cols-axis' />
+              }
               {props.cells.map((cell, i) => (
                 <TimeCol
                   key={cell.date.toISOString()}

+ 93 - 64
packages/timegrid/src/TimeColsSlats.tsx

@@ -11,11 +11,13 @@ import {
   wholeDivideDurations,
   Duration,
   createFormatter,
-  memoize,
   RefMap,
   CssDimValue,
   createRef,
-  PositionCache
+  PositionCache,
+  DateMarker,
+  DateEnv,
+  ComponentContextType
 } from '@fullcalendar/core'
 import TimeColsSlatsCoords from './TimeColsSlatsCoords'
 
@@ -30,7 +32,8 @@ export interface TimeColsSlatsProps extends TimeColsSlatsContentProps {
 
 interface TimeColsSlatsContentProps {
   dateProfile: DateProfile
-  slotDuration: Duration
+  axis: boolean
+  slatMetas: TimeSlatMeta[]
 }
 
 
@@ -71,8 +74,9 @@ export default class TimeColsSlats extends BaseComponent<TimeColsSlatsProps> {
           {props.tableColGroupNode /* relies on there only being a single <col> for the axis */}
           <TimeColsSlatsBody
             slatElRefs={this.slatElRefs}
+            axis={props.axis}
+            slatMetas={props.slatMetas}
             dateProfile={props.dateProfile}
-            slotDuration={props.slotDuration}
           />
         </table>
       </div>
@@ -110,7 +114,7 @@ export default class TimeColsSlats extends BaseComponent<TimeColsSlatsProps> {
             true // vertical
           ),
           props.dateProfile,
-          props.slotDuration
+          props.slatMetas
         )
       )
     }
@@ -126,64 +130,99 @@ export interface TimeColsSlatsBodyProps extends TimeColsSlatsContentProps {
 
 export class TimeColsSlatsBody extends BaseComponent<TimeColsSlatsBodyProps> {
 
-  private getLabelInterval = memoize(getLabelInterval)
-  private getLabelFormat = memoize(getLabelFormat)
+  render(props: TimeColsSlatsBodyProps, state: {}, context: ComponentContext) {
+    let { slatElRefs } = props
 
+    return (
+      <tbody>
+        {props.slatMetas.map((slatMeta, i) => (
+          <tr ref={slatElRefs.createRef(i)}>
+            {props.axis &&
+              <TimeColsAxisCell {...slatMeta} />
+            }
+            <td
+              className={'fc-time' + (!slatMeta.isLabeled ? ' fc-minor' : '')}
+              data-time={slatMeta.isoTimeStr}
+            />
+          </tr>
+        ))}
+      </tbody>
+    )
+  }
 
-  render(props: TimeColsSlatsBodyProps, state: {}, context: ComponentContext) {
-    let { dateEnv, options } = context
-    let { dateProfile, slotDuration, slatElRefs } = props
-
-    let labelInterval = this.getLabelInterval(options.slotLabelInterval, slotDuration)
-    let labelFormat = this.getLabelFormat(options.slotLabelFormat)
-
-    let dayStart = startOfDay(dateProfile.renderRange.start)
-    let slotTime = dateProfile.minTime
-    let slotIterator = createDuration(0)
-    let slotDate // will be on the view's first day, but we only care about its time
-    let isLabeled
-    let rowsNodes: VNode[] = []
-    let i = 0
-
-    // Calculate the time for each slot
-    while (asRoughMs(slotTime) < asRoughMs(dateProfile.maxTime)) {
-      slotDate = dateEnv.add(dayStart, slotTime)
-      isLabeled = wholeDivideDurations(slotIterator, labelInterval) !== null
-
-      let classNames = [ 'fc-axis', 'fc-time' ]
-      let axisNode =
-        isLabeled ?
-          <td class={classNames.concat('shrink').join(' ')}>
-            <div data-fc-width-all={1}>
-              <span data-fc-width-content={1}>
-                {dateEnv.format(slotDate, labelFormat)}
-              </span>
-            </div>
+}
+
+
+const DEFAULT_SLAT_LABEL_FORMAT = {
+  hour: 'numeric',
+  minute: '2-digit',
+  omitZeroMinute: true,
+  meridiem: 'short'
+}
+
+export function TimeColsAxisCell(props: TimeSlatMeta) {
+  let classNames = [ 'fc-time', props.isLabeled ? 'shrink' : 'fc-minor', 'fc-axis' ]
+
+  return (
+    <ComponentContextType.Consumer>
+      {(context: ComponentContext) => {
+        let labelFormat = createFormatter(context.options.slotLabelFormat || DEFAULT_SLAT_LABEL_FORMAT) // TODO: optimize!!!
+
+        return (
+          <td class={classNames.join(' ')} data-time={props.isoTimeStr}>
+            {props.isLabeled &&
+              <div data-fc-width-all={1}>
+                <span data-fc-width-content={1}>
+                  {context.dateEnv.format(props.date, labelFormat)}
+                </span>
+              </div>
+            }
           </td>
-          :
-          <td class={classNames.join(' ')} />
+        )
+      }}
+    </ComponentContextType.Consumer>
+  )
+}
 
-      // TODO: move nearly everything to <td>s
 
-      rowsNodes.push(
-        <tr
-          ref={slatElRefs.createRef(i)}
-          data-time={formatIsoTimeString(slotDate)}
-          class={isLabeled ? '' : 'fc-minor'}
-        >
-          {axisNode}
-          <td />
-        </tr>
-      )
+export function getSlatLabelFormat(optionInput) {
+  return createFormatter(optionInput || {
+    hour: 'numeric',
+    minute: '2-digit',
+    omitZeroMinute: true,
+    meridiem: 'short'
+  })
+}
 
-      slotTime = addDurations(slotTime, slotDuration)
-      slotIterator = addDurations(slotIterator, slotDuration)
-      i++
-    }
 
-    return (<tbody>{rowsNodes}</tbody>)
+export interface TimeSlatMeta {
+  date: DateMarker
+  isoTimeStr: string
+  isLabeled: boolean
+}
+
+export function buildSlatMetas(dateProfile: DateProfile, labelIntervalInput, slotDuration: Duration, dateEnv: DateEnv) {
+  let dayStart = startOfDay(dateProfile.renderRange.start)
+  let slatTime = dateProfile.minTime
+  let slatIterator = createDuration(0)
+  let labelInterval = getLabelInterval(labelIntervalInput, slotDuration)
+  let metas: TimeSlatMeta[] = []
+
+  while (asRoughMs(slatTime) < asRoughMs(dateProfile.maxTime)) {
+    let date = dateEnv.add(dayStart, slatTime)
+    let isLabeled = wholeDivideDurations(slatIterator, labelInterval) !== null
+
+    metas.push({
+      date,
+      isoTimeStr: formatIsoTimeString(date),
+      isLabeled
+    })
+
+    slatTime = addDurations(slatTime, slotDuration)
+    slatIterator = addDurations(slatIterator, slotDuration)
   }
 
+  return metas
 }
 
 
@@ -201,16 +240,6 @@ function getLabelInterval(optionInput, slotDuration: Duration) {
 }
 
 
-function getLabelFormat(optionInput) {
-  return createFormatter(optionInput || {
-    hour: 'numeric',
-    minute: '2-digit',
-    omitZeroMinute: true,
-    meridiem: 'short'
-  })
-}
-
-
 // Computes an automatic value for slotLabelInterval
 function computeLabelInterval(slotDuration) {
   let i

+ 7 - 3
packages/timegrid/src/TimeColsSlatsCoords.ts

@@ -1,4 +1,5 @@
 import { PositionCache, DateMarker, startOfDay, createDuration, asRoughMs, DateProfile, Duration, rangeContainsMarker } from '@fullcalendar/core'
+import { TimeSlatMeta } from './TimeColsSlats'
 
 
 export default class TimeColsSlatsCoords {
@@ -6,7 +7,7 @@ export default class TimeColsSlatsCoords {
   constructor(
     public positions: PositionCache,
     private dateProfile: DateProfile,
-    private slotDuration: Duration
+    private slatMetas: TimeSlatMeta[]
   ) {
   }
 
@@ -29,10 +30,13 @@ export default class TimeColsSlatsCoords {
 
 
   // 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.
   computeTimeTop(duration: Duration) {
-    let { positions, dateProfile, slotDuration } = this
+    let { positions, dateProfile, slatMetas } = this
     let len = positions.els.length
-    let slatCoverage = (duration.milliseconds - asRoughMs(dateProfile.minTime)) / asRoughMs(slotDuration) // floating-point value of # of slots covered
+    let slotDurationMs = slatMetas[1].date.valueOf() - slatMetas[0].date.valueOf() // we assume dates are uniform
+    let slatCoverage = (duration.milliseconds - asRoughMs(dateProfile.minTime)) / slotDurationMs // floating-point value of # of slots covered
     let slatIndex
     let slatRemainder
 

+ 9 - 3
packages/timegrid/src/TimeColsView.tsx

@@ -34,6 +34,7 @@ export default abstract class TimeColsView extends View {
 
 
   renderLayout(
+    hasAxis: boolean,
     headerRowContent: VNode | null,
     allDayContent: ((contentArg: ChunkContentCallbackArgs) => VNode) | null,
     timeContent: ((contentArg: ChunkContentCallbackArgs) => VNode) | null
@@ -82,12 +83,17 @@ export default abstract class TimeColsView extends View {
       }
     })
 
+    let colConfigs = []
+    if (hasAxis) {
+      colConfigs.push({ width: 'shrink' })
+    }
+
     return (
       <div class={classNames.join(' ')} ref={this.rootElRef}>
         <SimpleScrollGrid
           forPrint={props.forPrint}
           vGrow={!props.isHeightAuto}
-          cols={[ { width: 'shrink' } ]}
+          cols={colConfigs}
           sections={sections}
         />
       </div>
@@ -119,7 +125,7 @@ export default abstract class TimeColsView extends View {
 
 
   // Generates the HTML that will go before the day-of week header cells
-  renderHeadIntro = () => {
+  renderHeadAxis = () => {
     let { dateEnv, options } = this.context
     let range = this.props.dateProfile.renderRange
     let dayCnt = diffDays(range.start, range.end)
@@ -151,7 +157,7 @@ export default abstract class TimeColsView extends View {
   ------------------------------------------------------------------------------------------------------------------*/
 
 
-  renderTableRowIntro = () => {
+  renderTableRowAxis = () => {
     let { options } = this.context
     let spanAttrs = {} as any
     let child = options.allDayText

+ 1 - 0
packages/timegrid/src/main.ts

@@ -7,6 +7,7 @@ import './main.scss'
 
 export { DayTimeCols, DayTimeColsView, TimeColsView, buildTimeColsModel, buildDayRanges, DayTimeColsSlicer, TimeColsSeg }
 export { default as TimeCols } from './TimeCols'
+export { TimeSlatMeta, buildSlatMetas } from './TimeColsSlats'
 export { default as TimeColsSlatsCoords } from './TimeColsSlatsCoords'
 
 export default createPlugin({

+ 1 - 1
packages/timegrid/src/styles/_time-grid.scss

@@ -71,6 +71,6 @@
   border-bottom: 0; /* each cell is responsible for its top border */
 }
 
-.fc-timegrid-slats .fc-minor td {
+.fc-timegrid-slats td.fc-minor {
   border-top-style: dotted;
 }