import { SegEntry , SegHierarchy , SegRect , buildEntryKey , getEntrySpanEnd , binarySearch , SegEntryGroup , groupIntersectingEntries , } from '@fullcalendar/common' ; interface SegNode extends SegEntry { nextLevelNodes : SegNode [ ] ; } type SegNodeAndPressure = [ SegNode , number ] ; interface SegSiblingRange { level : number ; lateralStart : number ; lateralEnd : number ; } export interface SegWebRect extends SegRect { stackDepth : number ; stackForward : number ; } export function buildPositioning ( segInputs , strictOrder ? , maxStackCnt ? ) { let hierarchy = new SegHierarchy ( ) ; if ( strictOrder != null ) { hierarchy . strictOrder = strictOrder ; } if ( maxStackCnt != null ) { hierarchy . maxStackCnt = maxStackCnt ; } let hiddenEntries = hierarchy . addSegs ( segInputs ) ; let hiddenGroups = groupIntersectingEntries ( hiddenEntries ) ; let web = buildWeb ( hierarchy ) ; web = stretchWeb ( web , 1 ) ; let segRects = webToRects ( web ) ; return { segRects , hiddenGroups } ; } function buildWeb ( hierarchy ) { const { entriesByLevel } = hierarchy ; const buildNode = cacheable ( ( level , lateral ) => level + ':' + lateral , ( level , lateral ) : SegNodeAndPressure => { let siblingRange = findNextLevelSegs ( hierarchy , level , lateral ) ; let nextLevelRes = buildNodes ( siblingRange , buildNode ) ; let entry = entriesByLevel [ level ] [ lateral ] ; return [ { ... entry , nextLevelNodes : nextLevelRes [ 0 ] } , entry . thickness + nextLevelRes [ 1 ] , ] ; } ) ; return buildNodes ( entriesByLevel . length ? { level : 0 , lateralStart : 0 , lateralEnd : entriesByLevel [ 0 ] . length } : null , buildNode ) [ 0 ] ; } function buildNodes ( siblingRange , buildNode ) { if ( ! siblingRange ) { return [ [ ] , 0 ] ; } let { level , lateralStart , lateralEnd } = siblingRange ; let lateral = lateralStart ; let pairs = [ ] ; while ( lateral < lateralEnd ) { pairs . push ( buildNode ( level , lateral ) ) ; lateral += 1 ; } pairs . sort ( cmpDescPressures ) ; return [ pairs . map ( extractNode ) , pairs [ 0 ] [ 1 ] , ] ; } function cmpDescPressures ( a , b ) { return b [ 1 ] - a [ 1 ] ; } function extractNode ( a ) { return a [ 0 ] ; } function findNextLevelSegs ( hierarchy , subjectLevel , subjectLateral ) { let { levelCoords , entriesByLevel } = hierarchy ; let subjectEntry = entriesByLevel [ subjectLevel ] [ subjectLateral ] ; let afterSubject = levelCoords [ subjectLevel ] + subjectEntry . thickness ; let levelCnt = levelCoords . length ; let level = subjectLevel ; for ( ; level < levelCnt && levelCoords [ level ] < afterSubject ; level += 1 ) ; for ( ; level < levelCnt ; level += 1 ) { let entries = entriesByLevel [ level ] ; let entry ; let searchIndex = binarySearch ( entries , subjectEntry . span . start , getEntrySpanEnd ) ; let lateralStart = searchIndex [ 0 ] + searchIndex [ 1 ] ; let lateralEnd = lateralStart ; while ( ( entry = entries [ lateralEnd ] ) && entry . span . start < subjectEntry . span . end ) { lateralEnd += 1 ; } if ( lateralStart < lateralEnd ) { return { level , lateralStart , lateralEnd } ; } } return null ; } function stretchWeb ( topLevelNodes , totalThickness ) { const stretchNode = cacheable ( ( node , startCoord , prevThickness ) => buildEntryKey ( node ) , ( node , startCoord , prevThickness ) : [ number , SegNode ] => { let { nextLevelNodes , thickness } = node ; let allThickness = thickness + prevThickness ; let thicknessFraction = thickness / allThickness ; let endCoord ; let newChildren = [ ] ; if ( ! nextLevelNodes . length ) { endCoord = totalThickness ; } else { for ( let childNode of nextLevelNodes ) { if ( endCoord === undefined ) { let res = stretchNode ( childNode , startCoord , allThickness ) ; endCoord = res [ 0 ] ; newChildren . push ( res [ 1 ] ) ; } else { let res = stretchNode ( childNode , endCoord , 0 ) ; newChildren . push ( res [ 1 ] ) ; } } } let newThickness = ( endCoord - startCoord ) * thicknessFraction ; return [ endCoord - newThickness , { ... node , thickness : newThickness , nextLevelNodes : newChildren , } ] ; } ) ; return topLevelNodes . map ( ( node ) => stretchNode ( node , 0 , 0 ) [ 1 ] ) ; } function webToRects ( topLevelNodes ) { let rects = [ ] ; const processNode = cacheable ( ( node , levelCoord , stackDepth ) => buildEntryKey ( node ) , ( node , levelCoord , stackDepth ) => { let rect = { ... node , levelCoord , stackDepth , stackForward : 0 , } ; rects . push ( rect ) ; return ( rect . stackForward = processNodes ( node . nextLevelNodes , levelCoord + node . thickness , stackDepth + 1 ) + 1 ) ; } ) ; function processNodes ( nodes , levelCoord , stackDepth ) { let stackForward = 0 ; for ( let node of nodes ) { stackForward = Math . max ( processNode ( node , levelCoord , stackDepth ) , stackForward ) ; } return stackForward ; } processNodes ( topLevelNodes , 0 , 0 ) ; return rects ; } function cacheable < Args extends any [ ] , Res > ( keyFunc , workFunc ) { const cache = { } ; return ( ... args ) => { let key = keyFunc ( ... args ) ; return ( key in cache ) ? cache [ key ] : ( cache [ key ] = workFunc ( ... args ) ) ; } ; }