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

improvements to daygrid event print rendering

Adam Shaw 4 лет назад
Родитель
Сommit
cf423ccbc0

+ 0 - 1
packages/common/src/event-placement.ts

@@ -4,7 +4,6 @@ export interface SegInput {
   spanStart: number
   spanEnd: number
   thickness: number
-  forceAbsolute?: boolean // TODO: kill. not used within this file
 }
 
 export interface SegEntry {

+ 1 - 0
packages/daygrid/src/Table.tsx

@@ -160,6 +160,7 @@ export class Table extends DateComponent<TableProps, TableState> {
                       onMoreClick={(arg) => {
                         this.handleMoreLinkClick({ ...arg, fromRow: row })
                       }}
+                      forPrint={props.forPrint}
                     />
                   ))}
                 </tbody>

+ 6 - 38
packages/daygrid/src/TableCell.tsx

@@ -17,9 +17,6 @@ import {
   ViewApi,
   Dictionary,
   MountArg,
-  addDays,
-  intersectRanges,
-  EventRenderRange,
 } from '@fullcalendar/common'
 import { TableSeg } from './TableSeg'
 import { TableCellTop } from './TableCellTop'
@@ -45,7 +42,7 @@ export interface TableCellProps {
   todayRange: DateRange
   buildMoreLinkText: (num: number) => string
   onMoreClick?: (arg: MoreLinkArg) => void
-  segPlacements: TableSegPlacement[]
+  singlePlacements: TableSegPlacement[]
 }
 
 export interface TableCellModel { // TODO: move somewhere else. combine with DayTableCell?
@@ -186,22 +183,17 @@ export class TableCell extends DateComponent<TableCellProps> {
   }
 
   handleMoreLinkClick = (ev: VUIEvent) => {
-    let { segPlacements, onMoreClick, date, moreCnt } = this.props
-    let dayRange: DateRange = { start: date, end: addDays(date, 1) }
+    let { singlePlacements, onMoreClick, date, moreCnt } = this.props
 
     if (onMoreClick) {
       let allSegs: TableSeg[] = []
       let hiddenSegs: TableSeg[] = []
 
-      for (let placement of segPlacements) {
-        let reslicedSeg = resliceSeg(placement.seg, dayRange)
+      for (let placement of singlePlacements) {
+        allSegs.push(placement.seg)
 
-        if (reslicedSeg) {
-          allSegs.push(reslicedSeg)
-
-          if (placement.isHidden) {
-            hiddenSegs.push(reslicedSeg)
-          }
+        if (placement.isHidden) {
+          hiddenSegs.push(placement.seg)
         }
       }
 
@@ -224,27 +216,3 @@ TableCell.addPropsEquality({
 function renderMoreLinkInner(props) {
   return props.text
 }
-
-function resliceSeg(seg: TableSeg, constraint: DateRange): TableSeg | null {
-  let eventRange = seg.eventRange
-  let origRange = eventRange.range
-  let slicedRange = intersectRanges(origRange, constraint)
-
-  if (slicedRange) {
-    return {
-      ...seg,
-      firstCol: -1, // we don't know. caller doesn't care
-      lastCol: -1, // we don't know. caller doesn't care
-      eventRange: {
-        def: eventRange.def,
-        ui: { ...eventRange.ui, durationEditable: false }, // hack to disable resizing
-        instance: eventRange.instance,
-        range: slicedRange,
-      } as EventRenderRange,
-      isStart: seg.isStart && slicedRange.start.valueOf() === origRange.start.valueOf(),
-      isEnd: seg.isEnd && slicedRange.end.valueOf() === origRange.end.valueOf(),
-    }
-  }
-
-  return null
-}

+ 32 - 14
packages/daygrid/src/TableRow.tsx

@@ -46,6 +46,7 @@ export interface TableRowProps {
   showDayNumbers: boolean
   showWeekNumbers: boolean
   buildMoreLinkText: (num: number) => string
+  forPrint: boolean
 }
 
 interface TableRowState {
@@ -76,13 +77,13 @@ export class TableRow extends DateComponent<TableRowProps, TableRowState> {
     let highlightSegsByCol = splitSegsByFirstCol(this.getHighlightSegs(), colCnt)
     let mirrorSegsByCol = splitSegsByFirstCol(this.getMirrorSegs(), colCnt)
 
-    let { placementsByFirstCol, placementsByEachCol, moreCnts, moreMarginTops, cellPaddingBottoms } = computeFgSegPlacement(
+    let { singleColPlacements, multiColPlacements, moreCnts, moreMarginTops, cellPaddingBottoms } = computeFgSegPlacement(
       sortEventSegs(props.fgEventSegs, context.options.eventOrder) as TableSeg[],
       props.dayMaxEvents,
       props.dayMaxEventRows,
       state.eventInstanceHeights,
       state.maxContentHeight,
-      colCnt,
+      props.cells
     )
 
     let selectedInstanceHash = // TODO: messy way to compute this
@@ -94,14 +95,14 @@ export class TableRow extends DateComponent<TableRowProps, TableRowState> {
       <tr ref={this.rootElRef}>
         {props.renderIntro && props.renderIntro()}
         {props.cells.map((cell, col) => {
-          let [normalFgNodes, topsByInstanceId] = this.renderFgSegs(
-            placementsByFirstCol[col],
+          let normalFgNodes = this.renderFgSegs(
+            props.forPrint ? singleColPlacements[col] : multiColPlacements[col],
             selectedInstanceHash,
             props.todayRange,
           )
 
-          let [mirrorFgNodes] = this.renderFgSegs(
-            buildMirrorPlacements(mirrorSegsByCol[col], topsByInstanceId),
+          let mirrorFgNodes = this.renderFgSegs(
+            buildMirrorPlacements(mirrorSegsByCol[col], multiColPlacements),
             {},
             props.todayRange,
             Boolean(props.eventDrag),
@@ -129,7 +130,7 @@ export class TableRow extends DateComponent<TableRowProps, TableRowState> {
                 props.onMoreClick({ ...arg, fromCol: col })
               }}
               moreMarginTop={moreMarginTops[col]}
-              segPlacements={placementsByEachCol[col]}
+              singlePlacements={singleColPlacements[col]}
               fgPaddingBottom={cellPaddingBottoms[col]}
               fgContentElRef={this.fgElRefs.createRef(cell.key)}
               fgContent={( // Fragment scopes the keys
@@ -195,13 +196,12 @@ export class TableRow extends DateComponent<TableRowProps, TableRowState> {
     isDragging?: boolean,
     isResizing?: boolean,
     isDateSelecting?: boolean,
-  ): [VNode[], { [instanceId: string]: number }] { // [nodes, topsByInstanceId]
+  ): VNode[] {
     let { context } = this
     let { eventSelection } = this.props
     let { framePositions } = this.state
     let defaultDisplayEventEnd = this.props.cells.length === 1 // colCnt === 1
     let nodes: VNode[] = []
-    let topsByInstanceId: { [instanceId: string]: number } = {}
 
     if (framePositions) {
       for (let placement of segPlacements) {
@@ -227,6 +227,7 @@ export class TableRow extends DateComponent<TableRowProps, TableRowState> {
 
         /*
         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(
           <div
@@ -262,12 +263,10 @@ export class TableRow extends DateComponent<TableRowProps, TableRowState> {
             )}
           </div>,
         )
-
-        topsByInstanceId[instanceId] = placement.absoluteTop
       }
     }
 
-    return [nodes, topsByInstanceId]
+    return nodes
   }
 
   renderFillSegs(segs: TableSeg[], fillType: string): VNode {
@@ -306,7 +305,10 @@ export class TableRow extends DateComponent<TableRowProps, TableRowState> {
   updateSizing(isExternalSizingChange) {
     let { props, frameElRefs } = this
 
-    if (props.clientWidth !== null) { // positioning ready?
+    if (
+      !props.forPrint &&
+      props.clientWidth !== null // positioning ready?
+    ) {
       if (isExternalSizingChange) {
         let frameEls = props.cells.map((cell) => frameElRefs.currentMap[cell.key])
 
@@ -371,7 +373,11 @@ TableRow.addStateEquality({
   eventInstanceHeights: isPropsEqual,
 })
 
-function buildMirrorPlacements(mirrorSegs: TableSeg[], topsByInstanceId: { [instanceId: string]: number }): TableSegPlacement[] {
+function buildMirrorPlacements(mirrorSegs: TableSeg[], colPlacements: TableSegPlacement[][]): TableSegPlacement[] {
+  if (!mirrorSegs.length) {
+    return []
+  }
+  let topsByInstanceId = buildAbsoluteTopHash(colPlacements)
   return mirrorSegs.map((seg: TableSeg) => ({
     seg,
     partIndex: 0,
@@ -381,3 +387,15 @@ function buildMirrorPlacements(mirrorSegs: TableSeg[], topsByInstanceId: { [inst
     marginTop: 0
   }))
 }
+
+function buildAbsoluteTopHash(colPlacements: TableSegPlacement[][]) {
+  let topsByInstanceId: { [instanceId: string]: number } = {}
+
+  for (let placements of colPlacements) {
+    for (let placement of placements) {
+      topsByInstanceId[placement.seg.eventRange.instance.instanceId] = placement.absoluteTop
+    }
+  }
+
+  return colPlacements
+}

+ 162 - 85
packages/daygrid/src/event-placement.ts

@@ -5,7 +5,11 @@ import {
   SegEntry,
   SegInsertion,
   buildEntryKey,
+  EventRenderRange,
+  intersectRanges,
+  addDays,
 } from '@fullcalendar/common'
+import { TableCellModel } from './TableCell'
 import { TableSeg } from './TableSeg'
 
 // TODO: print-mode where every placement is non-absolute?
@@ -15,8 +19,8 @@ export interface TableSegPlacement {
   partIndex: number
   isHidden: boolean
   isAbsolute: boolean
-  absoluteTop: number // always populated regardless of isAbsolute
-  marginTop: number // only populated if !isAbsolute
+  absoluteTop: number // populated regardless of isAbsolute
+  marginTop: number
 }
 
 export function computeFgSegPlacement(
@@ -25,7 +29,7 @@ export function computeFgSegPlacement(
   dayMaxEventRows: boolean | number,
   eventInstanceHeights: { [instanceId: string]: number },
   maxContentHeight: number | null,
-  colCnt: number
+  cells: TableCellModel[]
 ) {
   let hierarchy = new DayGridSegHierarchy()
   hierarchy.allowReslicing = true
@@ -40,143 +44,216 @@ export function computeFgSegPlacement(
     hierarchy.hiddenConsumes = true
   }
 
-  let hiddenEntries: SegEntry[]  = []
+  // create segInputs only for segs with known heights
   let segInputs: SegInput[] = []
-
+  let unknownHeightSegs: TableSeg[] = []
   for (let i = 0; i < segs.length; i++) {
     let seg = segs[i]
     let { instanceId } = seg.eventRange.instance
     let eventHeight = eventInstanceHeights[instanceId]
-    let geomProps = {
-      spanStart: seg.firstCol,
-      spanEnd: seg.lastCol + 1,
-      thickness: eventHeight || 0,
-    }
 
-    if (eventHeight == null) {
-      hiddenEntries.push({
-        segInput: { index: i, ...geomProps, forceAbsolute: true },
-        ...geomProps
-      })
-    } else {
+    if (eventHeight != null) {
       segInputs.push({
         index: i,
-        ...geomProps,
-        forceAbsolute: seg.isStart || seg.isEnd,
+        spanStart: seg.firstCol,
+        spanEnd: seg.lastCol + 1,
+        thickness: eventHeight
       })
+    } else {
+      unknownHeightSegs.push(seg)
     }
   }
 
-  hiddenEntries.push(...hierarchy.addSegs(segInputs))
+  let hiddenEntries = hierarchy.addSegs(segInputs)
   let segRects = hierarchy.toRects()
-  let { placementsByFirstCol, placementsByEachCol, leftoverMarginsByCol } = placeRects(segRects, segs, colCnt)
+  let { singleColPlacements, multiColPlacements, leftoverMargins } = placeRects(segRects, segs, cells)
 
   let moreCnts: number[] = []
   let moreMarginTops: number[] = []
   let cellPaddingBottoms: number[] = []
 
-  for (let col = 0; col < colCnt; col++) {
-    moreCnts.push(0)
+  // add segs with unknown heights
+  for (let seg of unknownHeightSegs) {
+    multiColPlacements[seg.firstCol].push({
+      seg,
+      partIndex: 0,
+      isHidden: true,
+      isAbsolute: true,
+      absoluteTop: 0,
+      marginTop: 0,
+    })
+
+    for (let col = seg.firstCol; col <= seg.lastCol; col++) {
+      moreCnts[col]++
+      singleColPlacements[col].push({
+        seg: resliceSeg(seg, col, col + 1, cells),
+        partIndex: 0,
+        isHidden: true,
+        isAbsolute: false,
+        absoluteTop: 0,
+        marginTop: 0,
+      })
+    }
   }
 
   // add the hidden entries
+  for (let col = 0; col < cells.length; col++) {
+    moreCnts.push(0)
+  }
   for (let hiddenEntry of hiddenEntries) {
-    let placement: TableSegPlacement = {
-      seg: segs[hiddenEntry.segInput.index],
+    let seg = segs[hiddenEntry.segInput.index]
+
+    multiColPlacements[hiddenEntry.spanStart].push({
+      seg,
       partIndex: 0,
-      isAbsolute: true,
       isHidden: true,
+      isAbsolute: true,
       absoluteTop: 0,
-      marginTop: 0
-    }
-
-    placementsByFirstCol[hiddenEntry.spanStart].push(placement)
+      marginTop: 0,
+    })
 
     for (let col = hiddenEntry.spanStart; col < hiddenEntry.spanEnd; col++) {
-      placementsByEachCol[col].push(placement)
       moreCnts[col]++
+      singleColPlacements[col].push({
+        seg: resliceSeg(seg, col, col + 1, cells),
+        partIndex: 0,
+        isHidden: true,
+        isAbsolute: false,
+        absoluteTop: 0,
+        marginTop: 0,
+      })
     }
   }
 
-  for (let col = 0; col < colCnt; col++) {
+  // deal with leftover margins
+  for (let col = 0; col < cells.length; col++) {
     if (moreCnts[col]) {
-      moreMarginTops.push(leftoverMarginsByCol[col])
+      moreMarginTops.push(leftoverMargins[col])
       cellPaddingBottoms.push(0)
     } else {
       moreMarginTops.push(0)
-      cellPaddingBottoms.push(leftoverMarginsByCol[col])
+      cellPaddingBottoms.push(leftoverMargins[col])
     }
   }
 
-  return { placementsByFirstCol, placementsByEachCol, moreCnts, moreMarginTops, cellPaddingBottoms }
+  return { singleColPlacements, multiColPlacements, moreCnts, moreMarginTops, cellPaddingBottoms }
 }
 
 // rects ordered by top coord, then left
-function placeRects(rects: SegRect[], segs: TableSeg[], colCnt: number) {
-  let placementsByFirstCol: TableSegPlacement[][] = []
-  let placementsByEachCol: TableSegPlacement[][] = []
-  let leftoverMarginsByCol: number[] = []
+function placeRects(rects: SegRect[], segs: TableSeg[], cells: TableCellModel[]) {
+  let rectsByEachCol = groupRectsByEachCol(rects, cells.length)
+  let singleColPlacements: TableSegPlacement[][] = []
+  let multiColPlacements: TableSegPlacement[][] = []
+  let leftoverMargins: number[] = []
 
-  for (let col = 0; col < colCnt; col++) {
-    placementsByFirstCol.push([])
-    placementsByEachCol.push([])
-  }
+  for (let col = 0; col < cells.length; col++) {
+    let rects = rectsByEachCol[col]
 
-  for (let rect of rects) {
-    let seg = segs[rect.segInput.index]
-
-    if ( // a subdivided part? create a fake seg
-      seg.firstCol !== rect.spanStart ||
-      seg.lastCol !== rect.spanEnd - 1
-    ) {
-      seg = {
-        ...seg,
-        firstCol: rect.spanStart,
-        lastCol: rect.spanEnd - 1,
-        isStart: seg.isStart && (rect.spanStart === rect.segInput.spanStart), // keep isStart if not trimmed
-        isEnd: seg.isEnd && (rect.spanEnd === rect.segInput.spanEnd) // keep isEnd if not trimmed
-      }
+    // compute all static segs in singlePlacements
+    let singlePlacements: TableSegPlacement[] = []
+    let currentHeight = 0
+    let currentMarginTop = 0
+    for (let rect of rects) {
+      let seg = segs[rect.segInput.index]
+      singlePlacements.push({
+        seg: resliceSeg(seg, col, col + 1, cells),
+        partIndex: rect.partIndex,
+        isHidden: false,
+        isAbsolute: false,
+        absoluteTop: 0,
+        marginTop: rect.levelCoord - currentHeight
+      })
+      currentHeight = rect.levelCoord + rect.thickness
     }
 
-    let placement: TableSegPlacement & { height: number } = {
-      seg,
-      partIndex: rect.partIndex,
-      isAbsolute: rect.spanEnd - rect.spanStart > 1 || rect.segInput.forceAbsolute,
-      isHidden: false,
-      absoluteTop: rect.levelCoord,
-      marginTop: 0, // will compute later
-      height: rect.thickness // hack. only for this function
+    // compute mixed static/absolute segs in multiPlacements
+    let multiPlacements: TableSegPlacement[] = []
+    currentHeight = 0
+    currentMarginTop = 0
+    for (let rect of rects) {
+      let seg = segs[rect.segInput.index]
+      let isAbsolute = rect.spanEnd - rect.spanStart > 1 // multi-column?
+      let isFirstCol = rect.spanStart === 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.spanStart, rect.spanEnd, cells),
+            partIndex: rect.partIndex,
+            isHidden: false,
+            isAbsolute: true,
+            absoluteTop: rect.levelCoord,
+            marginTop: 0,
+          })
+        }
+      } else {
+        if (isFirstCol) {
+          multiPlacements.push({
+            seg: resliceSeg(seg, rect.spanStart, rect.spanEnd, cells),
+            partIndex: rect.partIndex,
+            isHidden: false,
+            isAbsolute: false,
+            absoluteTop: 0,
+            marginTop: currentMarginTop // claim the margin
+          })
+          currentMarginTop = 0
+        }
+      }
     }
 
-    placementsByFirstCol[rect.spanStart].push(placement)
+    singleColPlacements.push(singlePlacements)
+    multiColPlacements.push(multiPlacements)
+    leftoverMargins.push(currentMarginTop)
+  }
 
+  return { singleColPlacements, multiColPlacements, leftoverMargins }
+}
+
+function groupRectsByEachCol(rects: SegRect[], colCnt: number): SegRect[][] {
+  let rectsByEachCol: SegRect[][] = []
+
+  for (let col = 0; col < colCnt; col++) {
+    rectsByEachCol.push([])
+  }
+
+  for (let rect of rects) {
     for (let col = rect.spanStart; col < rect.spanEnd; col++) {
-      placementsByEachCol[col].push(placement)
+      rectsByEachCol[col].push(rect)
     }
   }
 
-  // compute the marginTops on the non-absolute placements
-  for (let col = 0; col < colCnt; col++) {
-    let currentHeight = 0
-    let currentMargin = 0
-
-    for (let placement of placementsByEachCol[col]) {
-      let placementHeight = (placement as any).height as number // hack
-      currentMargin += placement.absoluteTop - currentHeight // amount of space since bottom of previous seg
-      currentHeight = placement.absoluteTop + placementHeight // height will now be bottom of current seg
-
-      if (placement.isAbsolute) {
-        currentMargin += placementHeight
-      } else if (placement.seg.firstCol === col) { // non-absolute seg rooted in this col
-        placement.marginTop = currentMargin // claim the margin
-        currentMargin = 0
-      }
-    }
+  return rectsByEachCol
+}
 
-    leftoverMarginsByCol.push(currentMargin)
+function resliceSeg(seg: TableSeg, spanStart: number, spanEnd: number, cells: TableCellModel[]): TableSeg {
+  if (seg.firstCol === spanStart && seg.lastCol === spanEnd - 1) {
+    return seg
   }
 
-  return { placementsByFirstCol, placementsByEachCol, leftoverMarginsByCol }
+  let eventRange = seg.eventRange
+  let origRange = eventRange.range
+  let slicedRange = intersectRanges(origRange, {
+    start: cells[spanStart].date,
+    end: addDays(cells[spanEnd - 1].date, 1)
+  })
+
+  return {
+    ...seg,
+    firstCol: spanStart,
+    lastCol: spanEnd - 1,
+    eventRange: {
+      def: eventRange.def,
+      ui: { ...eventRange.ui, durationEditable: false }, // hack to disable resizing
+      instance: eventRange.instance,
+      range: slicedRange,
+    } as EventRenderRange,
+    isStart: seg.isStart && slicedRange.start.valueOf() === origRange.start.valueOf(),
+    isEnd: seg.isEnd && slicedRange.end.valueOf() === origRange.end.valueOf(),
+  }
 }
 
 class DayGridSegHierarchy extends SegHierarchy {