diff --git a/packages/common/src/event-placement.ts b/packages/common/src/event-placement.ts index 39577325e9..140888c6c0 100644 --- a/packages/common/src/event-placement.ts +++ b/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 { diff --git a/packages/daygrid/src/Table.tsx b/packages/daygrid/src/Table.tsx index 9ef6922f99..3bcda6a064 100644 --- a/packages/daygrid/src/Table.tsx +++ b/packages/daygrid/src/Table.tsx @@ -160,6 +160,7 @@ export class Table extends DateComponent { onMoreClick={(arg) => { this.handleMoreLinkClick({ ...arg, fromRow: row }) }} + forPrint={props.forPrint} /> ))} diff --git a/packages/daygrid/src/TableCell.tsx b/packages/daygrid/src/TableCell.tsx index 263f5f1aeb..e5dddf7619 100644 --- a/packages/daygrid/src/TableCell.tsx +++ b/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 { } 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 -} diff --git a/packages/daygrid/src/TableRow.tsx b/packages/daygrid/src/TableRow.tsx index 91c775dd45..ef3f7a267e 100644 --- a/packages/daygrid/src/TableRow.tsx +++ b/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 { 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 { {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 { 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 { 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 { /* 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(
{ )}
, ) - - topsByInstanceId[instanceId] = placement.absoluteTop } } - return [nodes, topsByInstanceId] + return nodes } renderFillSegs(segs: TableSeg[], fillType: string): VNode { @@ -306,7 +305,10 @@ export class TableRow extends DateComponent { 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 +} diff --git a/packages/daygrid/src/event-placement.ts b/packages/daygrid/src/event-placement.ts index 849a170605..bfff5aae35 100644 --- a/packages/daygrid/src/event-placement.ts +++ b/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 {