@ethlete/cdk 4.68.0 → 4.69.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1572,2010 +1572,2010 @@ const generateBracketDataForGg = (source) => {
1572
1572
  return bracketData;
1573
1573
  };
1574
1574
 
1575
- class NewBracketDefaultMatchComponent {
1576
- constructor() {
1577
- this.bracketRound = input.required(...(ngDevMode ? [{ debugName: "bracketRound" }] : []));
1578
- this.bracketMatch = input.required(...(ngDevMode ? [{ debugName: "bracketMatch" }] : []));
1579
- this.bracketRoundSwissGroup = input.required(...(ngDevMode ? [{ debugName: "bracketRoundSwissGroup" }] : []));
1580
- }
1581
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.0", ngImport: i0, type: NewBracketDefaultMatchComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1582
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "20.3.0", type: NewBracketDefaultMatchComponent, isStandalone: true, selector: "et-new-bracket-default-match", inputs: { bracketRound: { classPropertyName: "bracketRound", publicName: "bracketRound", isSignal: true, isRequired: true, transformFunction: null }, bracketMatch: { classPropertyName: "bracketMatch", publicName: "bracketMatch", isSignal: true, isRequired: true, transformFunction: null }, bracketRoundSwissGroup: { classPropertyName: "bracketRoundSwissGroup", publicName: "bracketRoundSwissGroup", isSignal: true, isRequired: true, transformFunction: null } }, host: { classAttribute: "et-new-bracket-default-match-host" }, ngImport: i0, template: ` {{ bracketMatch().id }} `, isInline: true, styles: [".et-new-bracket-default-match-host{display:block;padding:8px;border:1px solid yellow;inline-size:100%;block-size:100%;display:flex;justify-content:center;align-items:center;box-sizing:border-box;font-size:12px}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); }
1583
- }
1584
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.0", ngImport: i0, type: NewBracketDefaultMatchComponent, decorators: [{
1585
- type: Component,
1586
- args: [{ selector: 'et-new-bracket-default-match', template: ` {{ bracketMatch().id }} `, standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, host: {
1587
- class: 'et-new-bracket-default-match-host',
1588
- }, styles: [".et-new-bracket-default-match-host{display:block;padding:8px;border:1px solid yellow;inline-size:100%;block-size:100%;display:flex;justify-content:center;align-items:center;box-sizing:border-box;font-size:12px}\n"] }]
1589
- }] });
1590
-
1591
- class NewBracketDefaultRoundHeaderComponent {
1592
- constructor() {
1593
- this.bracketRound = input.required(...(ngDevMode ? [{ debugName: "bracketRound" }] : []));
1594
- this.bracketRoundSwissGroup = input.required(...(ngDevMode ? [{ debugName: "bracketRoundSwissGroup" }] : []));
1595
- }
1596
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.0", ngImport: i0, type: NewBracketDefaultRoundHeaderComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1597
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.0", type: NewBracketDefaultRoundHeaderComponent, isStandalone: true, selector: "et-new-bracket-default-round-header", inputs: { bracketRound: { classPropertyName: "bracketRound", publicName: "bracketRound", isSignal: true, isRequired: true, transformFunction: null }, bracketRoundSwissGroup: { classPropertyName: "bracketRoundSwissGroup", publicName: "bracketRoundSwissGroup", isSignal: true, isRequired: true, transformFunction: null } }, host: { classAttribute: "et-new-bracket-default-round-header-host" }, ngImport: i0, template: `
1598
- {{ bracketRound().name }}
1599
-
1600
- @if (bracketRoundSwissGroup()) {
1601
- ({{ bracketRoundSwissGroup()?.name }})
1602
- }
1603
- `, isInline: true, styles: [".et-new-bracket-default-round-header-host{display:block;padding:8px;border:1px solid green;inline-size:100%;block-size:100%;display:flex;justify-content:center;align-items:center;box-sizing:border-box}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); }
1604
- }
1605
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.0", ngImport: i0, type: NewBracketDefaultRoundHeaderComponent, decorators: [{
1606
- type: Component,
1607
- args: [{ selector: 'et-new-bracket-default-round-header', template: `
1608
- {{ bracketRound().name }}
1609
-
1610
- @if (bracketRoundSwissGroup()) {
1611
- ({{ bracketRoundSwissGroup()?.name }})
1612
- }
1613
- `, standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, host: {
1614
- class: 'et-new-bracket-default-round-header-host',
1615
- }, styles: [".et-new-bracket-default-round-header-host{display:block;padding:8px;border:1px solid green;inline-size:100%;block-size:100%;display:flex;justify-content:center;align-items:center;box-sizing:border-box}\n"] }]
1616
- }] });
1617
-
1618
- const path = (d, options) => `<path d="${d.replace(/\s+/g, ' ').trim()}" stroke="currentColor" fill="none" stroke-width="${options.width}" stroke-dasharray="${options.dashArray}" stroke-dashoffset="${options.dashOffset}" class="${options.className}" />`;
1619
-
1620
- const curvePath = (from, to, direction, options) => {
1621
- const inverted = options.inverted ?? false;
1622
- // Inline/block coordinates depending on direction and inversion
1623
- const fromInline = inverted ? from.inline.start : from.inline.end;
1624
- const toInline = inverted ? to.inline.end : to.inline.start;
1625
- const fromBlock = from.block.center;
1626
- const toBlock = to.block.center;
1627
- // Curve parameters
1628
- const startCurve = options.lineStartingCurveAmount;
1629
- const endCurve = options.lineEndingCurveAmount;
1630
- const totalInline = Math.abs(toInline - fromInline);
1631
- const straightLength = (totalInline - startCurve - endCurve) / 2;
1632
- // Calculate key points for the path
1633
- const straightEnd = inverted ? fromInline - straightLength : fromInline + straightLength;
1634
- const straightStart = inverted ? toInline + straightLength : toInline - straightLength;
1635
- // First curve (from start)
1636
- const firstCurveEndX = inverted ? straightEnd - startCurve : straightEnd + startCurve;
1637
- const firstCurveEndY = direction === 'down' ? fromBlock + startCurve : fromBlock - startCurve;
1638
- // Second curve (to end)
1639
- const secondCurveStartY = direction === 'down' ? toBlock - endCurve : toBlock + endCurve;
1640
- const secondCurveEndX = straightStart;
1641
- const secondCurveEndY = toBlock;
1642
- const secondCurveBezierX = inverted ? straightStart + endCurve : straightStart - endCurve;
1643
- // SVG path string
1644
- const d = [
1645
- `M ${fromInline} ${fromBlock}`,
1646
- `H ${straightEnd}`,
1647
- `Q ${firstCurveEndX} ${fromBlock}, ${firstCurveEndX} ${firstCurveEndY}`,
1648
- `V ${secondCurveStartY}`,
1649
- `Q ${secondCurveBezierX} ${toBlock}, ${secondCurveEndX} ${secondCurveEndY}`,
1650
- `H ${toInline}`,
1651
- ].join(' ');
1652
- return path(d, options.path);
1653
- };
1654
-
1655
- const linePath = (from, to, options) => {
1656
- return path(`M ${from.inline.end} ${from.block.center} L ${to.inline.start} ${to.block.center}`, options.path);
1657
- };
1658
-
1659
- const makePos = (dimensions) => ({
1660
- block: {
1661
- start: dimensions.top,
1662
- end: dimensions.top + dimensions.height,
1663
- center: dimensions.top + dimensions.height / 2,
1664
- },
1665
- inline: {
1666
- start: dimensions.left,
1667
- end: dimensions.left + dimensions.width,
1668
- center: dimensions.left + dimensions.width / 2,
1669
- },
1670
- });
1671
- const drawMan = (dimensions) => {
1672
- const svgParts = [];
1673
- for (const col of dimensions.bracketGrid.columns) {
1674
- for (const el of col.elements) {
1675
- if (el.type === 'header')
1676
- continue;
1677
- const currentMatchParticipantsShortIds = [el.match.home?.shortId, el.match.away?.shortId]
1678
- .filter((id) => !!id)
1679
- .join(' ');
1680
- const pathOptions = { ...dimensions.path, className: currentMatchParticipantsShortIds };
1681
- const currentPos = makePos(el.dimensions);
1682
- // No lines for the third place match
1683
- if (el.round.type === COMMON_BRACKET_ROUND_TYPE.THIRD_PLACE)
1684
- continue;
1685
- switch (el.match.relation.type) {
1686
- case 'nothing-to-one': {
1687
- continue;
1688
- }
1689
- case 'one-to-nothing':
1690
- case 'one-to-one': {
1691
- const prev = dimensions.bracketGrid.matchElementMap.getOrThrow(el.match.relation.previousMatch.id);
1692
- const prevPos = makePos(prev.dimensions);
1693
- // draw a straight line
1694
- svgParts.push(linePath(prevPos, currentPos, { path: pathOptions }));
1695
- break;
1696
- }
1697
- case 'two-to-nothing':
1698
- case 'two-to-one': {
1699
- const prevUpper = dimensions.bracketGrid.matchElementMap.getOrThrow(el.match.relation.previousUpperMatch.id);
1700
- const prevLower = dimensions.bracketGrid.matchElementMap.getOrThrow(el.match.relation.previousLowerMatch.id);
1701
- const prevUpperPos = makePos(prevUpper.dimensions);
1702
- const prevLowerPos = makePos(prevLower.dimensions);
1703
- const isLowerUpperMerger = el.match.relation.previousLowerRound.id !== el.match.relation.previousUpperRound.id;
1704
- const invertCurve = el.round.mirrorRoundType === BRACKET_ROUND_MIRROR_TYPE.RIGHT;
1705
- const curveOptions = {
1706
- ...dimensions.curve,
1707
- inverted: invertCurve,
1708
- path: { ...dimensions.path, className: '' },
1709
- };
1710
- if (isLowerUpperMerger) {
1711
- svgParts.push(linePath(prevUpperPos, currentPos, { path: pathOptions }));
1712
- }
1713
- else {
1714
- // draw two lines that merge into one in the middle
1715
- svgParts.push(curvePath(prevUpperPos, currentPos, 'down', {
1716
- ...curveOptions,
1717
- path: {
1718
- ...curveOptions.path,
1719
- className: el.match.relation.previousUpperMatch.winner?.shortId || '',
1720
- },
1721
- }));
1722
- }
1723
- svgParts.push(curvePath(prevLowerPos, currentPos, 'up', {
1724
- ...curveOptions,
1725
- path: {
1726
- ...curveOptions.path,
1727
- className: el.match.relation.previousLowerMatch.winner?.shortId || '',
1728
- },
1729
- }));
1730
- if (el.round.mirrorRoundType === BRACKET_ROUND_MIRROR_TYPE.RIGHT &&
1731
- el.match.relation.type === 'two-to-one' &&
1732
- el.match.relation.nextRound.mirrorRoundType === null) {
1733
- // draw a straight line for the special case of connecting the final match to the mirrored semi final match
1734
- const next = dimensions.bracketGrid.matchElementMap.getOrThrow(el.match.relation.nextMatch.id);
1735
- const nextPos = makePos(next.dimensions);
1736
- svgParts.push(linePath(nextPos, currentPos, { path: pathOptions }));
1737
- }
1738
- break;
1739
- }
1740
- }
1575
+ const FALLBACK_MATCH_RELATION_POSITION = -1;
1576
+ const generateMatchPosition = (match, factor) => Math.ceil(match.position * factor);
1577
+ const generateMatchRelationPositions = (relation, match) => {
1578
+ switch (relation.type) {
1579
+ case 'nothing-to-one':
1580
+ return {
1581
+ nextRoundMatchPosition: generateMatchPosition(match, relation.nextRoundMatchFactor),
1582
+ previousUpperRoundMatchPosition: FALLBACK_MATCH_RELATION_POSITION,
1583
+ previousLowerRoundMatchPosition: FALLBACK_MATCH_RELATION_POSITION,
1584
+ };
1585
+ case 'one-to-nothing': {
1586
+ const double = relation.previousRoundMatchFactor === 2 ? 1 : 0;
1587
+ return {
1588
+ nextRoundMatchPosition: FALLBACK_MATCH_RELATION_POSITION,
1589
+ previousUpperRoundMatchPosition: (generateMatchPosition(match, relation.previousRoundMatchFactor) -
1590
+ double),
1591
+ previousLowerRoundMatchPosition: generateMatchPosition(match, relation.previousRoundMatchFactor),
1592
+ };
1593
+ }
1594
+ case 'one-to-one': {
1595
+ const double = relation.previousRoundMatchFactor === 2 ? 1 : 0;
1596
+ return {
1597
+ nextRoundMatchPosition: generateMatchPosition(match, relation.nextRoundMatchFactor),
1598
+ previousUpperRoundMatchPosition: (generateMatchPosition(match, relation.previousRoundMatchFactor) -
1599
+ double),
1600
+ previousLowerRoundMatchPosition: generateMatchPosition(match, relation.previousRoundMatchFactor),
1601
+ };
1741
1602
  }
1603
+ case 'two-to-one':
1604
+ return {
1605
+ nextRoundMatchPosition: generateMatchPosition(match, relation.nextRoundMatchFactor),
1606
+ previousUpperRoundMatchPosition: generateMatchPosition(match, relation.previousUpperRoundMatchFactor),
1607
+ previousLowerRoundMatchPosition: generateMatchPosition(match, relation.previousLowerRoundMatchFactor),
1608
+ };
1609
+ case 'two-to-nothing':
1610
+ return {
1611
+ nextRoundMatchPosition: FALLBACK_MATCH_RELATION_POSITION,
1612
+ previousUpperRoundMatchPosition: generateMatchPosition(match, relation.previousUpperRoundMatchFactor),
1613
+ previousLowerRoundMatchPosition: generateMatchPosition(match, relation.previousLowerRoundMatchFactor),
1614
+ };
1742
1615
  }
1743
- return svgParts.join('');
1744
1616
  };
1745
-
1746
- const createBracketElementPart = (config) => {
1747
- const { elementPartHeight } = config;
1748
- const newElementPart = {
1749
- dimensions: {
1750
- width: 0,
1751
- height: elementPartHeight,
1752
- top: 0,
1753
- left: 0,
1754
- },
1755
- };
1617
+ const createNothingToOneRelation$1 = (params) => {
1618
+ const { match, relation, matchPositionMaps, nextRoundMatchPosition } = params;
1619
+ const nextMatch = matchPositionMaps.get(relation.nextRound.id)?.get(nextRoundMatchPosition);
1620
+ if (!nextMatch)
1621
+ throw new Error('Next round match not found');
1756
1622
  return {
1757
- elementPart: newElementPart,
1623
+ type: 'nothing-to-one',
1624
+ currentMatch: match,
1625
+ currentRound: relation.currentRound,
1626
+ nextMatch,
1627
+ nextRound: relation.nextRound,
1758
1628
  };
1759
1629
  };
1760
-
1761
- const createBracketElement = (config) => {
1762
- const { type, area, elementHeight, partHeights } = config;
1763
- const parts = [];
1764
- const newElementBase = {
1765
- dimensions: {
1766
- width: 0,
1767
- height: elementHeight,
1768
- top: 0,
1769
- left: 0,
1770
- },
1771
- containerDimensions: {
1772
- width: 0,
1773
- height: 0,
1774
- top: 0,
1775
- left: 0,
1776
- },
1777
- parts,
1778
- area,
1779
- };
1780
- const newElement = (() => {
1781
- switch (type) {
1782
- case 'header':
1783
- return {
1784
- ...newElementBase,
1785
- type,
1786
- component: config.component,
1787
- round: config.round,
1788
- roundSwissGroup: config.roundSwissGroup,
1789
- };
1790
- case 'match':
1791
- return {
1792
- ...newElementBase,
1793
- type,
1794
- component: config.component,
1795
- match: config.match,
1796
- round: config.round,
1797
- roundSwissGroup: config.roundSwissGroup,
1798
- };
1799
- case 'matchGap':
1800
- case 'roundHeaderGap':
1801
- case 'roundGap':
1802
- case 'colGap':
1803
- return {
1804
- ...newElementBase,
1805
- type,
1806
- };
1807
- default:
1808
- throw new Error(`Unknown element type: ${type}`);
1809
- }
1810
- })();
1811
- const pushPart = (...newParts) => {
1812
- parts.push(...newParts);
1813
- };
1814
- for (const partHeight of partHeights) {
1815
- const { elementPart } = createBracketElementPart({
1816
- elementPartHeight: partHeight,
1817
- });
1818
- pushPart(elementPart);
1630
+ const createOneToNothingOrTwoToNothingRelation = (params) => {
1631
+ const { match, relation, matchPositionMaps, previousUpperRoundMatchPosition, previousLowerRoundMatchPosition } = params;
1632
+ const previousUpperMatch = matchPositionMaps.get(relation.previousRound.id)?.get(previousUpperRoundMatchPosition);
1633
+ const previousLowerMatch = matchPositionMaps.get(relation.previousRound.id)?.get(previousLowerRoundMatchPosition);
1634
+ if (!previousUpperMatch)
1635
+ throw new Error('Previous round match not found');
1636
+ if (previousUpperRoundMatchPosition !== previousLowerRoundMatchPosition) {
1637
+ if (!previousLowerMatch)
1638
+ throw new Error('Previous lower round match not found');
1639
+ return {
1640
+ type: 'two-to-nothing',
1641
+ currentMatch: match,
1642
+ currentRound: relation.currentRound,
1643
+ previousUpperMatch,
1644
+ previousUpperRound: relation.previousRound,
1645
+ previousLowerMatch,
1646
+ previousLowerRound: relation.previousRound,
1647
+ };
1648
+ }
1649
+ else {
1650
+ return {
1651
+ type: 'one-to-nothing',
1652
+ currentMatch: match,
1653
+ currentRound: relation.currentRound,
1654
+ previousMatch: previousUpperMatch,
1655
+ previousRound: relation.previousRound,
1656
+ };
1819
1657
  }
1820
- return {
1821
- element: newElement,
1822
- };
1823
1658
  };
1824
-
1825
- const finalizeBracketGrid = (grid) => {
1826
- const finalizedColumns = [];
1827
- const finalizedElementMap = new BracketMap();
1828
- const ignoredSections = [];
1829
- for (const [masterColumnIndex, masterColumn] of grid.grid.masterColumns.entries()) {
1830
- for (const [sectionIndex, section] of masterColumn.sections.entries()) {
1831
- if (ignoredSections.some((s) => s.masterColumnIndex === masterColumnIndex && s.sectionIndex === sectionIndex)) {
1832
- continue;
1833
- }
1834
- const elements = [];
1835
- const firstSubColumn = section.subColumns[0];
1836
- const lastSubColumn = section.subColumns[section.subColumns.length - 1];
1837
- if (!firstSubColumn || !lastSubColumn)
1838
- continue;
1839
- let sectionWidth = section.dimensions.width;
1840
- for (const element of firstSubColumn.elements) {
1841
- if (element.type === 'header') {
1842
- elements.push({
1843
- type: 'header',
1844
- dimensions: element.dimensions,
1845
- component: element.component,
1846
- round: element.round,
1847
- roundSwissGroup: element.roundSwissGroup,
1848
- });
1849
- }
1850
- else if (element.type === 'match') {
1851
- const matchEl = {
1852
- type: 'match',
1853
- dimensions: element.dimensions,
1854
- component: element.component,
1855
- match: element.match,
1856
- round: element.round,
1857
- classes: [element.match.home?.shortId, element.match.away?.shortId].filter((v) => !!v).join(' '),
1858
- roundSwissGroup: element.roundSwissGroup,
1859
- };
1860
- elements.push(matchEl);
1861
- finalizedElementMap.set(element.match.id, matchEl);
1862
- }
1863
- }
1864
- if (!lastSubColumn.span.isEnd) {
1865
- for (let index = masterColumnIndex + 1; index < grid.grid.masterColumns.length; index++) {
1866
- const nextMaster = grid.grid.masterColumns[index];
1867
- const nextSection = nextMaster?.sections[sectionIndex];
1868
- if (!nextSection)
1869
- break;
1870
- sectionWidth += nextSection.dimensions.width;
1871
- ignoredSections.push({ masterColumnIndex: index, sectionIndex });
1872
- if (nextSection.subColumns.some((sc) => sc.span.isEnd)) {
1873
- break;
1874
- }
1875
- }
1876
- }
1877
- if (!elements.length)
1878
- continue;
1879
- finalizedColumns.push({
1880
- dimensions: {
1881
- ...section.dimensions,
1882
- width: sectionWidth,
1883
- },
1884
- elements,
1885
- });
1886
- }
1659
+ const createOneToOneOrTwoToOneRelation = (params) => {
1660
+ const { match, relation, matchPositionMaps, nextRoundMatchPosition, previousUpperRoundMatchPosition, previousLowerRoundMatchPosition, } = params;
1661
+ const nextMatch = matchPositionMaps.get(relation.nextRound.id)?.get(nextRoundMatchPosition);
1662
+ const previousUpperMatch = matchPositionMaps.get(relation.previousRound.id)?.get(previousUpperRoundMatchPosition);
1663
+ const previousLowerMatch = matchPositionMaps.get(relation.previousRound.id)?.get(previousLowerRoundMatchPosition);
1664
+ if (!nextMatch)
1665
+ throw new Error('Next round match not found');
1666
+ if (!previousUpperMatch)
1667
+ throw new Error('Previous upper round match not found');
1668
+ if (!previousLowerMatch)
1669
+ throw new Error('Previous lower round match not found');
1670
+ if (previousUpperRoundMatchPosition === previousLowerRoundMatchPosition) {
1671
+ return {
1672
+ type: 'one-to-one',
1673
+ currentMatch: match,
1674
+ currentRound: relation.currentRound,
1675
+ previousMatch: previousUpperMatch,
1676
+ previousRound: relation.previousRound,
1677
+ nextMatch,
1678
+ nextRound: relation.nextRound,
1679
+ };
1680
+ }
1681
+ else {
1682
+ return {
1683
+ type: 'two-to-one',
1684
+ currentMatch: match,
1685
+ currentRound: relation.currentRound,
1686
+ previousUpperMatch,
1687
+ previousUpperRound: relation.previousRound,
1688
+ previousLowerMatch,
1689
+ previousLowerRound: relation.previousRound,
1690
+ nextMatch,
1691
+ nextRound: relation.nextRound,
1692
+ };
1887
1693
  }
1694
+ };
1695
+ const createTwoToOneRelation$1 = (params) => {
1696
+ const { match, relation, matchPositionMaps, nextRoundMatchPosition, previousUpperRoundMatchPosition, previousLowerRoundMatchPosition, } = params;
1697
+ const nextMatch = matchPositionMaps.get(relation.nextRound.id)?.get(nextRoundMatchPosition);
1698
+ const previousUpperMatch = matchPositionMaps
1699
+ .get(relation.previousUpperRound.id)
1700
+ ?.get(previousUpperRoundMatchPosition);
1701
+ const previousLowerMatch = matchPositionMaps
1702
+ .get(relation.previousLowerRound.id)
1703
+ ?.get(previousLowerRoundMatchPosition);
1704
+ if (!nextMatch)
1705
+ throw new Error('Next round match not found');
1706
+ if (!previousUpperMatch)
1707
+ throw new Error('Previous upper round match not found');
1708
+ if (!previousLowerMatch)
1709
+ throw new Error('Previous lower round match not found');
1888
1710
  return {
1889
- columns: finalizedColumns,
1890
- elementMap: finalizedElementMap,
1711
+ type: 'two-to-one',
1712
+ currentMatch: match,
1713
+ currentRound: relation.currentRound,
1714
+ previousUpperMatch,
1715
+ previousUpperRound: relation.previousUpperRound,
1716
+ previousLowerMatch,
1717
+ previousLowerRound: relation.previousLowerRound,
1718
+ nextMatch,
1719
+ nextRound: relation.nextRound,
1891
1720
  };
1892
1721
  };
1893
-
1894
- const createBracketGrid = (config) => {
1895
- const masterColumns = [];
1896
- const spannedWidthCache = new Map();
1897
- const spanStartLeftCache = new Map();
1898
- const newGrid = {
1899
- dimensions: { width: 0, height: 0, top: 0, left: 0 },
1900
- masterColumns,
1901
- };
1902
- const pushMasterColumn = (...newMasterColumns) => {
1903
- masterColumns.push(...newMasterColumns);
1722
+ const createTwoToNothingRelation$1 = (params) => {
1723
+ const { match, relation, matchPositionMaps, previousUpperRoundMatchPosition, previousLowerRoundMatchPosition } = params;
1724
+ const previousUpperMatch = matchPositionMaps
1725
+ .get(relation.previousUpperRound.id)
1726
+ ?.get(previousUpperRoundMatchPosition);
1727
+ const previousLowerMatch = matchPositionMaps
1728
+ .get(relation.previousUpperRound.id)
1729
+ ?.get(previousLowerRoundMatchPosition);
1730
+ if (!previousUpperMatch)
1731
+ throw new Error('Previous upper round match not found');
1732
+ if (!previousLowerMatch)
1733
+ throw new Error('Previous lower round match not found');
1734
+ return {
1735
+ type: 'two-to-nothing',
1736
+ currentMatch: match,
1737
+ currentRound: relation.currentRound,
1738
+ previousUpperMatch,
1739
+ previousUpperRound: relation.previousUpperRound,
1740
+ previousLowerMatch,
1741
+ previousLowerRound: relation.previousUpperRound,
1904
1742
  };
1905
- const calculateDimensions = () => {
1906
- let currentMasterColumnLeft = 0;
1907
- let maxGridHeight = 0;
1908
- const masterCols = newGrid.masterColumns;
1909
- for (let mcIdx = 0; mcIdx < masterCols.length; mcIdx++) {
1910
- const masterColumn = masterCols[mcIdx];
1911
- const { padding } = masterColumn;
1912
- masterColumn.dimensions.left = currentMasterColumnLeft;
1913
- masterColumn.dimensions.top = 0;
1914
- masterColumn.dimensions.width += padding.left + padding.right;
1915
- const sections = masterColumn.sections;
1916
- let runningTop = 0;
1917
- let firstSectionIsHeader = false;
1918
- if (sections.length > 0) {
1919
- const firstSubColumn = sections[0].subColumns[0];
1920
- const firstElement = firstSubColumn?.elements[0];
1921
- firstSectionIsHeader = !!(firstElement && firstElement.type === 'header');
1922
- }
1923
- for (let secIdx = 0; secIdx < sections.length; secIdx++) {
1924
- const section = sections[secIdx];
1925
- let sectionPaddingTop;
1926
- if (secIdx === 0) {
1927
- sectionPaddingTop = firstSectionIsHeader ? 0 : padding.top;
1928
- }
1929
- else {
1930
- sectionPaddingTop = padding.top;
1931
- }
1932
- section.dimensions.width = masterColumn.dimensions.width;
1933
- section.dimensions.left = masterColumn.dimensions.left;
1934
- section.dimensions.top = runningTop;
1935
- runningTop += sectionPaddingTop;
1936
- const contentWidth = masterColumn.dimensions.width - padding.left - padding.right;
1937
- const subColumns = section.subColumns;
1938
- const totalSubColumns = subColumns.length;
1939
- const subColumnWidth = contentWidth / totalSubColumns;
1940
- let currentSubColumnLeft = masterColumn.dimensions.left + padding.left;
1941
- let maxSectionHeight = 0;
1942
- for (let scIdx = 0; scIdx < totalSubColumns; scIdx++) {
1943
- const subColumn = subColumns[scIdx];
1944
- subColumn.dimensions.width = subColumnWidth;
1945
- subColumn.dimensions.left = currentSubColumnLeft;
1946
- // TODO: The problem is here somewhere
1947
- subColumn.dimensions.top = section.dimensions.top + sectionPaddingTop;
1948
- let totalSubColumnHeight = 0;
1949
- const elements = subColumn.elements;
1950
- for (let elIdx = 0; elIdx < elements.length; elIdx++) {
1951
- const element = elements[elIdx];
1952
- let totalElementHeight = 0;
1953
- const parts = element.parts;
1954
- for (let pIdx = 0; pIdx < parts.length; pIdx++) {
1955
- const part = parts[pIdx];
1956
- part.dimensions.width = subColumnWidth;
1957
- part.dimensions.left = currentSubColumnLeft;
1958
- part.dimensions.top = subColumn.dimensions.top + totalSubColumnHeight + totalElementHeight;
1959
- totalElementHeight += part.dimensions.height;
1960
- }
1961
- element.containerDimensions.height = totalElementHeight;
1962
- element.containerDimensions.width = subColumnWidth;
1963
- element.containerDimensions.left = currentSubColumnLeft;
1964
- element.containerDimensions.top = subColumn.dimensions.top + totalSubColumnHeight;
1965
- element.dimensions.width = subColumnWidth;
1966
- element.dimensions.left = currentSubColumnLeft;
1967
- element.dimensions.top =
1968
- element.containerDimensions.top + (totalElementHeight - element.dimensions.height) * 0.5;
1969
- totalSubColumnHeight += totalElementHeight;
1970
- }
1971
- subColumn.dimensions.height = totalSubColumnHeight;
1972
- if (totalSubColumnHeight > maxSectionHeight)
1973
- maxSectionHeight = totalSubColumnHeight;
1974
- currentSubColumnLeft += subColumnWidth;
1975
- }
1976
- section.dimensions.height = maxSectionHeight + padding.bottom;
1977
- runningTop += maxSectionHeight + padding.bottom;
1978
- }
1979
- masterColumn.dimensions.height = runningTop;
1980
- if (masterColumn.dimensions.height > maxGridHeight)
1981
- maxGridHeight = masterColumn.dimensions.height;
1982
- currentMasterColumnLeft += masterColumn.dimensions.width;
1743
+ };
1744
+ const generateMatchRelationsNew = (bracketData) => {
1745
+ const matchRelations = [];
1746
+ const matchPositionMaps = new Map();
1747
+ for (const round of bracketData.rounds.values()) {
1748
+ const matchMap = new Map([...round.matches.values()].map((m) => [m.position, m]));
1749
+ matchPositionMaps.set(round.id, matchMap);
1750
+ }
1751
+ for (const match of bracketData.matches.values()) {
1752
+ const relation = match.round.relation;
1753
+ const { nextRoundMatchPosition, previousUpperRoundMatchPosition, previousLowerRoundMatchPosition } = generateMatchRelationPositions(relation, match);
1754
+ switch (relation.type) {
1755
+ case 'nothing-to-one':
1756
+ matchRelations.push(createNothingToOneRelation$1({
1757
+ match,
1758
+ relation,
1759
+ matchPositionMaps,
1760
+ nextRoundMatchPosition,
1761
+ }));
1762
+ break;
1763
+ case 'one-to-nothing':
1764
+ matchRelations.push(createOneToNothingOrTwoToNothingRelation({
1765
+ match,
1766
+ relation,
1767
+ matchPositionMaps,
1768
+ previousUpperRoundMatchPosition,
1769
+ previousLowerRoundMatchPosition,
1770
+ }));
1771
+ break;
1772
+ case 'one-to-one':
1773
+ matchRelations.push(createOneToOneOrTwoToOneRelation({
1774
+ match,
1775
+ relation,
1776
+ matchPositionMaps,
1777
+ nextRoundMatchPosition,
1778
+ previousUpperRoundMatchPosition,
1779
+ previousLowerRoundMatchPosition,
1780
+ }));
1781
+ break;
1782
+ case 'two-to-one':
1783
+ matchRelations.push(createTwoToOneRelation$1({
1784
+ match,
1785
+ relation,
1786
+ matchPositionMaps,
1787
+ nextRoundMatchPosition,
1788
+ previousUpperRoundMatchPosition,
1789
+ previousLowerRoundMatchPosition,
1790
+ }));
1791
+ break;
1792
+ case 'two-to-nothing':
1793
+ matchRelations.push(createTwoToNothingRelation$1({
1794
+ match,
1795
+ relation,
1796
+ matchPositionMaps,
1797
+ previousUpperRoundMatchPosition,
1798
+ previousLowerRoundMatchPosition,
1799
+ }));
1800
+ break;
1983
1801
  }
1984
- newGrid.dimensions.width = currentMasterColumnLeft;
1985
- newGrid.dimensions.height = maxGridHeight;
1986
- calculateSpanningElementDimensions();
1802
+ }
1803
+ return matchRelations;
1804
+ };
1805
+
1806
+ const calculateMatchFactor = (numeratorRound, denominatorRound) => numeratorRound.matchCount / denominatorRound.matchCount;
1807
+ const createNothingToOneRelation = (params) => ({
1808
+ type: 'nothing-to-one',
1809
+ currentRound: params.currentRound,
1810
+ nextRound: params.nextRound,
1811
+ nextRoundMatchFactor: calculateMatchFactor(params.nextRound, params.currentRound),
1812
+ });
1813
+ const createOneToNothingRelation = (params) => ({
1814
+ type: 'one-to-nothing',
1815
+ currentRound: params.currentRound,
1816
+ previousRound: params.previousRound,
1817
+ previousRoundMatchFactor: calculateMatchFactor(params.previousRound, params.currentRound),
1818
+ rootRoundMatchFactor: calculateMatchFactor(params.rootRound, params.currentRound),
1819
+ });
1820
+ const createOneToOneRelation = (params) => ({
1821
+ type: 'one-to-one',
1822
+ currentRound: params.currentRound,
1823
+ previousRound: params.previousRound,
1824
+ nextRound: params.nextRound,
1825
+ nextRoundMatchFactor: calculateMatchFactor(params.nextRound, params.currentRound),
1826
+ previousRoundMatchFactor: calculateMatchFactor(params.previousRound, params.currentRound),
1827
+ rootRoundMatchFactor: calculateMatchFactor(params.rootRound, params.currentRound),
1828
+ });
1829
+ const createTwoToOneRelation = (params) => ({
1830
+ type: 'two-to-one',
1831
+ currentRound: params.currentRound,
1832
+ previousUpperRound: params.previousUpperRound,
1833
+ previousLowerRound: params.previousLowerRound,
1834
+ nextRound: params.nextRound,
1835
+ nextRoundMatchFactor: calculateMatchFactor(params.nextRound, params.currentRound),
1836
+ previousUpperRoundMatchFactor: calculateMatchFactor(params.previousUpperRound, params.currentRound),
1837
+ previousLowerRoundMatchFactor: calculateMatchFactor(params.previousLowerRound, params.currentRound),
1838
+ upperRootRoundMatchFactor: calculateMatchFactor(params.firstUpperRound, params.currentRound),
1839
+ lowerRootRoundMatchFactor: calculateMatchFactor(params.firstLowerRound, params.currentRound),
1840
+ });
1841
+ const createTwoToNothingRelation = (params) => ({
1842
+ type: 'two-to-nothing',
1843
+ currentRound: params.currentRound,
1844
+ previousUpperRound: params.previousUpperRound,
1845
+ previousLowerRound: params.previousLowerRound,
1846
+ previousUpperRoundMatchFactor: calculateMatchFactor(params.previousUpperRound, params.currentRound),
1847
+ previousLowerRoundMatchFactor: calculateMatchFactor(params.previousLowerRound, params.currentRound),
1848
+ upperRootRoundMatchFactor: calculateMatchFactor(params.firstUpperRound, params.currentRound),
1849
+ lowerRootRoundMatchFactor: calculateMatchFactor(params.firstLowerRound, params.currentRound),
1850
+ });
1851
+ const getNavigationContext = (params) => {
1852
+ const { upperRounds, currentUpperRoundIndex } = params;
1853
+ const currentUpperRound = upperRounds[currentUpperRoundIndex];
1854
+ if (!currentUpperRound)
1855
+ throw new Error('currentUpperRound is null');
1856
+ const isLeftToRight = !currentUpperRound.mirrorRoundType || currentUpperRound.mirrorRoundType === BRACKET_ROUND_MIRROR_TYPE.LEFT;
1857
+ const relativePrevious = upperRounds[currentUpperRoundIndex - 1] || null;
1858
+ const relativeNext = upperRounds[currentUpperRoundIndex + 1] || null;
1859
+ const previousUpperRound = isLeftToRight ? relativePrevious : relativeNext;
1860
+ const nextUpperRound = isLeftToRight ? relativeNext : relativePrevious;
1861
+ const isLastUpperRound = !nextUpperRound ||
1862
+ (nextUpperRound.mirrorRoundType === BRACKET_ROUND_MIRROR_TYPE.RIGHT && !currentUpperRound.mirrorRoundType);
1863
+ const isFinal = currentUpperRound.type === COMMON_BRACKET_ROUND_TYPE.FINAL;
1864
+ return {
1865
+ currentUpperRound,
1866
+ previousUpperRound,
1867
+ nextUpperRound,
1868
+ isLastUpperRound,
1869
+ isFinal,
1987
1870
  };
1988
- const calculateSpanningElementDimensions = () => {
1989
- const spanDimensions = new Map();
1990
- const masterCols = newGrid.masterColumns;
1991
- for (let mcIdx = 0; mcIdx < masterCols.length; mcIdx++) {
1992
- const masterColumn = masterCols[mcIdx];
1993
- const sections = masterColumn.sections;
1994
- for (let secIdx = 0; secIdx < sections.length; secIdx++) {
1995
- const section = sections[secIdx];
1996
- const subColumns = section.subColumns;
1997
- for (let scIdx = 0; scIdx < subColumns.length; scIdx++) {
1998
- const subColumn = subColumns[scIdx];
1999
- const elements = subColumn.elements;
2000
- for (let elIdx = 0; elIdx < elements.length; elIdx++) {
2001
- const element = elements[elIdx];
2002
- if (!element.span)
2003
- continue;
2004
- const span = element.span;
2005
- const isStartPosition = mcIdx === span.masterColumnStart && secIdx === span.sectionStart && scIdx === span.subColumnStart;
2006
- const spanKey = `${span.masterColumnStart}-${span.masterColumnEnd}-${span.sectionStart}-${span.sectionEnd}-${span.subColumnStart}-${span.subColumnEnd}`;
2007
- if (isStartPosition && !spanDimensions.has(spanKey)) {
2008
- const totalSpannedWidth = calculateSpannedWidth(span, masterCols);
2009
- const spanStartLeft = calculateSpanStartLeft(span, masterCols);
2010
- const width = config.spanElementWidth;
2011
- spanDimensions.set(spanKey, { width, left: spanStartLeft + (totalSpannedWidth - width) * 0.5 });
2012
- }
2013
- const storedDimensions = spanDimensions.get(spanKey);
2014
- if (storedDimensions) {
2015
- element.dimensions.width = storedDimensions.width;
2016
- element.dimensions.left = storedDimensions.left;
2017
- element.isHidden = !isStartPosition;
2018
- }
2019
- }
2020
- }
2021
- }
1871
+ };
1872
+ const handleFinalRound = (params) => {
1873
+ const { relations, currentUpperRound, previousUpperRound, nextUpperRound, lowerRounds, currentUpperRoundIndex, firstUpperRound, firstLowerRound, lastLowerRound, } = params;
1874
+ const currentLowerRound = lowerRounds[currentUpperRoundIndex] || null;
1875
+ const nextLowerRound = lowerRounds[currentUpperRoundIndex + 1] || null;
1876
+ const previousLowerRound = lowerRounds[currentUpperRoundIndex - 1] || null;
1877
+ if (!currentLowerRound)
1878
+ throw new Error('currentLowerRound is null');
1879
+ const isAsyncBracket = currentLowerRound.id !== lastLowerRound.id;
1880
+ const finalLowerRound = isAsyncBracket ? nextLowerRound : currentLowerRound;
1881
+ if (!finalLowerRound)
1882
+ throw new Error('finalLowerRound is null');
1883
+ if (finalLowerRound.id !== lastLowerRound.id)
1884
+ throw new Error('finalLowerRound is not the last lower round');
1885
+ if (nextUpperRound) {
1886
+ relations.push(createTwoToOneRelation({
1887
+ currentRound: currentUpperRound,
1888
+ previousUpperRound,
1889
+ previousLowerRound: finalLowerRound,
1890
+ nextRound: nextUpperRound,
1891
+ firstUpperRound,
1892
+ firstLowerRound,
1893
+ }));
1894
+ }
1895
+ else {
1896
+ relations.push(createTwoToNothingRelation({
1897
+ currentRound: currentUpperRound,
1898
+ previousUpperRound,
1899
+ previousLowerRound: finalLowerRound,
1900
+ firstUpperRound,
1901
+ firstLowerRound,
1902
+ }));
1903
+ }
1904
+ if (isAsyncBracket) {
1905
+ const preFinalLowerRound = lowerRounds[lowerRounds.length - 2];
1906
+ const prePreFinalLowerRound = lowerRounds[lowerRounds.length - 3] || null;
1907
+ if (!preFinalLowerRound)
1908
+ throw new Error('preFinalLowerRound is null');
1909
+ relations.push(createOneToOneRelation({
1910
+ currentRound: finalLowerRound,
1911
+ previousRound: preFinalLowerRound,
1912
+ nextRound: currentUpperRound,
1913
+ rootRound: firstLowerRound,
1914
+ }));
1915
+ if (prePreFinalLowerRound) {
1916
+ relations.push(createOneToOneRelation({
1917
+ currentRound: preFinalLowerRound,
1918
+ previousRound: prePreFinalLowerRound,
1919
+ nextRound: finalLowerRound,
1920
+ rootRound: firstLowerRound,
1921
+ }));
2022
1922
  }
2023
- };
2024
- const calculateSpannedWidth = (span, masterColumns) => {
2025
- const key = `${span.masterColumnStart}-${span.masterColumnEnd}-${span.sectionStart}-${span.sectionEnd}-${span.subColumnStart}-${span.subColumnEnd}`;
2026
- if (spannedWidthCache.has(key))
2027
- return spannedWidthCache.get(key);
2028
- if (span.masterColumnStart === span.masterColumnEnd) {
2029
- const masterColumn = masterColumns[span.masterColumnStart];
2030
- if (masterColumn) {
2031
- const section = masterColumn.sections[span.sectionStart];
2032
- if (section) {
2033
- const subColumnWidth = section.dimensions.width / section.subColumns.length;
2034
- const totalWidth = subColumnWidth * (span.subColumnEnd - span.subColumnStart + 1);
2035
- spannedWidthCache.set(key, totalWidth);
2036
- return totalWidth;
2037
- }
2038
- }
2039
- spannedWidthCache.set(key, 0);
2040
- return 0;
1923
+ else {
1924
+ relations.push(createNothingToOneRelation({
1925
+ currentRound: preFinalLowerRound,
1926
+ nextRound: finalLowerRound,
1927
+ }));
2041
1928
  }
2042
- let totalWidth = 0;
2043
- for (let mcIdx = span.masterColumnStart; mcIdx <= span.masterColumnEnd; mcIdx++) {
2044
- const masterColumn = masterColumns[mcIdx];
2045
- if (!masterColumn)
2046
- continue;
2047
- if (mcIdx === span.masterColumnStart) {
2048
- const section = masterColumn.sections[span.sectionStart];
2049
- if (section) {
2050
- const subColumnWidth = section.dimensions.width / section.subColumns.length;
2051
- totalWidth += subColumnWidth * (section.subColumns.length - span.subColumnStart);
2052
- }
2053
- }
2054
- else if (mcIdx === span.masterColumnEnd) {
2055
- const section = masterColumn.sections[span.sectionEnd];
2056
- if (section) {
2057
- const subColumnWidth = section.dimensions.width / section.subColumns.length;
2058
- totalWidth += subColumnWidth * (span.subColumnEnd + 1);
2059
- }
2060
- }
2061
- else {
2062
- totalWidth += masterColumn.dimensions.width;
2063
- }
1929
+ }
1930
+ else {
1931
+ if (!previousLowerRound)
1932
+ throw new Error('previousLowerRound is null');
1933
+ relations.push(createOneToOneRelation({
1934
+ currentRound: finalLowerRound,
1935
+ previousRound: previousLowerRound,
1936
+ nextRound: currentUpperRound,
1937
+ rootRound: firstLowerRound,
1938
+ }));
1939
+ }
1940
+ };
1941
+ const handleFirstRound = (params) => {
1942
+ const { relations, currentUpperRound, nextUpperRound, lowerRounds, currentUpperRoundIndex } = params;
1943
+ relations.push(createNothingToOneRelation({
1944
+ currentRound: currentUpperRound,
1945
+ nextRound: nextUpperRound,
1946
+ }));
1947
+ const currentLowerRound = lowerRounds[currentUpperRoundIndex] || null;
1948
+ const nextLowerRound = lowerRounds[currentUpperRoundIndex + 1] || null;
1949
+ if (currentLowerRound && nextLowerRound) {
1950
+ relations.push(createNothingToOneRelation({
1951
+ currentRound: currentLowerRound,
1952
+ nextRound: nextLowerRound,
1953
+ }));
1954
+ }
1955
+ };
1956
+ const handleRegularRound = (params) => {
1957
+ const { relations, currentUpperRound, previousUpperRound, nextUpperRound, lowerRounds, currentUpperRoundIndex, firstUpperRound, firstLowerRound, } = params;
1958
+ relations.push(createOneToOneRelation({
1959
+ currentRound: currentUpperRound,
1960
+ previousRound: previousUpperRound,
1961
+ nextRound: nextUpperRound,
1962
+ rootRound: firstUpperRound,
1963
+ }));
1964
+ const currentLowerRound = lowerRounds[currentUpperRoundIndex] || null;
1965
+ const previousLowerRound = lowerRounds[currentUpperRoundIndex - 1] || null;
1966
+ const nextLowerRound = lowerRounds[currentUpperRoundIndex + 1] || null;
1967
+ if (currentLowerRound &&
1968
+ currentUpperRound.type === DOUBLE_ELIMINATION_BRACKET_ROUND_TYPE.UPPER_BRACKET &&
1969
+ previousLowerRound &&
1970
+ nextLowerRound &&
1971
+ firstLowerRound) {
1972
+ relations.push(createOneToOneRelation({
1973
+ currentRound: currentLowerRound,
1974
+ previousRound: previousLowerRound,
1975
+ nextRound: nextLowerRound,
1976
+ rootRound: firstLowerRound,
1977
+ }));
1978
+ }
1979
+ };
1980
+ const generateRoundRelationsNew = (bracketData) => {
1981
+ const relations = [];
1982
+ const allRounds = [...bracketData.rounds.values()];
1983
+ const upperRounds = allRounds.filter((r) => r.type !== DOUBLE_ELIMINATION_BRACKET_ROUND_TYPE.LOWER_BRACKET);
1984
+ const lowerRounds = allRounds.filter((r) => r.type === DOUBLE_ELIMINATION_BRACKET_ROUND_TYPE.LOWER_BRACKET);
1985
+ const firstUpperRound = upperRounds[0];
1986
+ const firstLowerRound = lowerRounds[0] || null;
1987
+ const lastLowerRound = lowerRounds[lowerRounds.length - 1] || null;
1988
+ if (!firstUpperRound)
1989
+ throw new Error('No upper rounds found');
1990
+ const hasLowerRounds = lowerRounds.length > 0;
1991
+ for (const [currentUpperRoundIndex] of upperRounds.entries()) {
1992
+ const nav = getNavigationContext({
1993
+ upperRounds,
1994
+ currentUpperRoundIndex,
1995
+ });
1996
+ if (nav.isFinal && hasLowerRounds && lastLowerRound && firstLowerRound && nav.previousUpperRound) {
1997
+ handleFinalRound({
1998
+ relations,
1999
+ currentUpperRound: nav.currentUpperRound,
2000
+ previousUpperRound: nav.previousUpperRound,
2001
+ nextUpperRound: nav.nextUpperRound,
2002
+ lowerRounds,
2003
+ currentUpperRoundIndex,
2004
+ firstUpperRound,
2005
+ firstLowerRound,
2006
+ lastLowerRound,
2007
+ });
2008
+ }
2009
+ else if (nav.isLastUpperRound && nav.previousUpperRound) {
2010
+ relations.push(createOneToNothingRelation({
2011
+ currentRound: nav.currentUpperRound,
2012
+ previousRound: nav.previousUpperRound,
2013
+ rootRound: firstUpperRound,
2014
+ }));
2015
+ }
2016
+ else if (nav.currentUpperRound.isFirstRound && nav.nextUpperRound) {
2017
+ handleFirstRound({
2018
+ relations,
2019
+ currentUpperRound: nav.currentUpperRound,
2020
+ nextUpperRound: nav.nextUpperRound,
2021
+ lowerRounds,
2022
+ currentUpperRoundIndex,
2023
+ });
2024
+ }
2025
+ else if (nav.previousUpperRound && nav.nextUpperRound) {
2026
+ handleRegularRound({
2027
+ relations,
2028
+ currentUpperRound: nav.currentUpperRound,
2029
+ previousUpperRound: nav.previousUpperRound,
2030
+ nextUpperRound: nav.nextUpperRound,
2031
+ lowerRounds,
2032
+ currentUpperRoundIndex,
2033
+ firstUpperRound,
2034
+ firstLowerRound,
2035
+ });
2036
+ }
2037
+ }
2038
+ return relations;
2039
+ };
2040
+
2041
+ const createNewBracket = (source, options) => {
2042
+ const bracketNewBase = createNewBracketBase(source, options);
2043
+ const rounds = new BracketMap();
2044
+ const roundsByType = new BracketMap();
2045
+ for (const roundBase of bracketNewBase.rounds.values()) {
2046
+ const newRound = {
2047
+ ...roundBase,
2048
+ matches: new BracketMap(),
2049
+ relation: { type: 'dummy' },
2050
+ };
2051
+ rounds.set(roundBase.id, newRound);
2052
+ if (!roundsByType.has(roundBase.type)) {
2053
+ roundsByType.set(roundBase.type, new BracketMap());
2054
+ }
2055
+ roundsByType.getOrThrow(roundBase.type).set(roundBase.id, newRound);
2056
+ }
2057
+ const participants = new BracketMap();
2058
+ for (const participantBase of bracketNewBase.participants.values()) {
2059
+ participants.set(participantBase.id, {
2060
+ ...participantBase,
2061
+ matches: new BracketMap(),
2062
+ });
2063
+ }
2064
+ const matches = new BracketMap();
2065
+ for (const matchBase of bracketNewBase.matches.values()) {
2066
+ const round = rounds.getOrThrow(matchBase.roundId);
2067
+ const homeParticipant = matchBase.home
2068
+ ? { ...matchBase.home, matches: new BracketMap() }
2069
+ : null;
2070
+ const awayParticipant = matchBase.away
2071
+ ? { ...matchBase.away, matches: new BracketMap() }
2072
+ : null;
2073
+ const newMatch = {
2074
+ ...matchBase,
2075
+ home: homeParticipant,
2076
+ away: awayParticipant,
2077
+ winner: null,
2078
+ round,
2079
+ relation: { type: 'dummy' },
2080
+ };
2081
+ if (matchBase.winner) {
2082
+ const winnerParticipant = homeParticipant?.id === matchBase.winner.id ? homeParticipant : awayParticipant;
2083
+ if (!winnerParticipant)
2084
+ throw new Error(`Winner participant with id ${matchBase.winner.id} not found in match base`);
2085
+ newMatch.winner = winnerParticipant;
2064
2086
  }
2065
- spannedWidthCache.set(key, totalWidth);
2066
- return totalWidth;
2067
- };
2068
- const calculateSpanStartLeft = (span, masterColumns) => {
2069
- const key = `${span.masterColumnStart}-${span.sectionStart}-${span.subColumnStart}`;
2070
- if (spanStartLeftCache.has(key))
2071
- return spanStartLeftCache.get(key);
2072
- const startMasterColumn = masterColumns[span.masterColumnStart];
2073
- if (!startMasterColumn) {
2074
- spanStartLeftCache.set(key, 0);
2075
- return 0;
2087
+ matches.set(matchBase.id, newMatch);
2088
+ round.matches.set(matchBase.id, newMatch);
2089
+ if (homeParticipant) {
2090
+ const participant = participants.getOrThrow(homeParticipant.id);
2091
+ participant.matches.set(matchBase.id, {
2092
+ ...newMatch,
2093
+ me: homeParticipant,
2094
+ opponent: awayParticipant,
2095
+ });
2076
2096
  }
2077
- let startLeft = startMasterColumn.dimensions.left;
2078
- const section = startMasterColumn.sections[span.sectionStart];
2079
- if (section) {
2080
- startLeft += (section.dimensions.width / section.subColumns.length) * span.subColumnStart;
2097
+ if (awayParticipant) {
2098
+ const participant = participants.getOrThrow(awayParticipant.id);
2099
+ participant.matches.set(matchBase.id, {
2100
+ ...newMatch,
2101
+ me: awayParticipant,
2102
+ opponent: homeParticipant,
2103
+ });
2081
2104
  }
2082
- spanStartLeftCache.set(key, startLeft);
2083
- return startLeft;
2084
- };
2085
- const setupElementSpans = () => {
2086
- const masterCols = newGrid.masterColumns;
2087
- for (let mcIdx = 0; mcIdx < masterCols.length; mcIdx++) {
2088
- const masterColumn = masterCols[mcIdx];
2089
- const sections = masterColumn.sections;
2090
- for (let secIdx = 0; secIdx < sections.length; secIdx++) {
2091
- const section = sections[secIdx];
2092
- const subColumns = section.subColumns;
2093
- for (let scIdx = 0; scIdx < subColumns.length; scIdx++) {
2094
- const subColumn = subColumns[scIdx];
2095
- if (subColumn.span.isStart && subColumn.span.isEnd)
2096
- continue;
2097
- let spanStart = { masterColumnIndex: mcIdx, sectionIndex: secIdx, subColumnIndex: scIdx };
2098
- if (!subColumn.span.isStart) {
2099
- outer: for (let m = mcIdx; m >= 0; m--) {
2100
- const mc = masterCols[m];
2101
- if (!mc)
2102
- continue;
2103
- const sec = mc.sections[secIdx];
2104
- if (!sec)
2105
- continue;
2106
- const end = m === mcIdx ? scIdx : sec.subColumns.length - 1;
2107
- for (let s = end; s >= 0; s--) {
2108
- if (sec.subColumns[s]?.span.isStart) {
2109
- spanStart = { masterColumnIndex: m, sectionIndex: secIdx, subColumnIndex: s };
2110
- break outer;
2111
- }
2112
- }
2113
- }
2114
- }
2115
- let spanEnd = { masterColumnIndex: mcIdx, sectionIndex: secIdx, subColumnIndex: scIdx };
2116
- if (!subColumn.span.isEnd) {
2117
- outer: for (let m = mcIdx; m < masterCols.length; m++) {
2118
- const mc = masterCols[m];
2119
- if (!mc)
2120
- continue;
2121
- const sec = mc.sections[secIdx];
2122
- if (!sec)
2123
- continue;
2124
- const start = m === mcIdx ? scIdx : 0;
2125
- for (let s = start; s < sec.subColumns.length; s++) {
2126
- if (sec.subColumns[s]?.span.isEnd) {
2127
- spanEnd = { masterColumnIndex: m, sectionIndex: secIdx, subColumnIndex: s };
2128
- break outer;
2129
- }
2130
- }
2131
- }
2132
- }
2133
- const elements = subColumn.elements;
2134
- for (let elIdx = 0; elIdx < elements.length; elIdx++) {
2135
- elements[elIdx].span = {
2136
- masterColumnStart: spanStart.masterColumnIndex,
2137
- masterColumnEnd: spanEnd.masterColumnIndex,
2138
- sectionStart: spanStart.sectionIndex,
2139
- sectionEnd: spanEnd.sectionIndex,
2140
- subColumnStart: spanStart.subColumnIndex,
2141
- subColumnEnd: spanEnd.subColumnIndex,
2142
- };
2143
- }
2144
- }
2145
- }
2105
+ }
2106
+ for (const participant of participants.values()) {
2107
+ for (const match of participant.matches.values()) {
2108
+ if (match.home?.id === participant.id)
2109
+ match.home.matches = participant.matches;
2110
+ if (match.away?.id === participant.id)
2111
+ match.away.matches = participant.matches;
2112
+ if (match.winner?.id === participant.id)
2113
+ match.winner.matches = participant.matches;
2146
2114
  }
2115
+ }
2116
+ const newBracket = {
2117
+ matches,
2118
+ participants,
2119
+ rounds,
2120
+ roundsByType,
2121
+ mode: bracketNewBase.mode,
2147
2122
  };
2148
- return {
2149
- grid: newGrid,
2150
- pushMasterColumn,
2151
- calculateDimensions,
2152
- setupElementSpans,
2153
- };
2154
- };
2155
-
2156
- const createBracketMasterColumn = (config) => {
2157
- const { columnWidth, padding } = config;
2158
- const sections = [];
2159
- const newMasterColumn = {
2160
- dimensions: {
2161
- width: columnWidth,
2162
- height: 0,
2163
- top: 0,
2164
- left: 0,
2165
- },
2166
- padding,
2167
- sections,
2168
- };
2169
- const pushSection = (...newSections) => {
2170
- sections.push(...newSections);
2171
- };
2172
- return {
2173
- masterColumn: newMasterColumn,
2174
- pushSection,
2175
- };
2176
- };
2177
-
2178
- const createBracketMasterColumnSection = (config) => {
2179
- const { type } = config;
2180
- const subColumns = [];
2181
- const newMasterColumnSection = {
2182
- dimensions: {
2183
- width: 0,
2184
- height: 0,
2185
- top: 0,
2186
- left: 0,
2187
- },
2188
- subColumns,
2189
- type,
2190
- };
2191
- const pushSubColumn = (...newSubColumns) => {
2192
- subColumns.push(...newSubColumns);
2193
- };
2194
- return {
2195
- masterColumnSection: newMasterColumnSection,
2196
- pushSubColumn,
2197
- };
2123
+ const roundRelations = generateRoundRelationsNew(newBracket);
2124
+ for (const roundRelation of roundRelations) {
2125
+ roundRelation.currentRound.relation = roundRelation;
2126
+ }
2127
+ const matchRelations = generateMatchRelationsNew(newBracket);
2128
+ for (const matchRelation of matchRelations) {
2129
+ matchRelation.currentMatch.relation = matchRelation;
2130
+ }
2131
+ return newBracket;
2198
2132
  };
2199
2133
 
2200
- const createBracketSubColumn = (config) => {
2201
- const { span } = config;
2202
- const elements = [];
2203
- const newSubColumn = {
2204
- dimensions: {
2205
- width: 0,
2206
- height: 0,
2207
- top: 0,
2208
- left: 0,
2209
- },
2210
- elements,
2211
- span,
2212
- };
2213
- const pushElement = (...newElements) => {
2214
- elements.push(...newElements);
2215
- };
2216
- return {
2217
- subColumn: newSubColumn,
2218
- pushElement,
2134
+ const pad = (str, len) => str.padEnd(len, ' ');
2135
+ const color = (str, code) => `\x1b[${code}m${str}\x1b[0m`;
2136
+ const roundColor = (str) => color(str, 36); // cyan
2137
+ const arrowColor = (str) => color(str, 90); // gray
2138
+ const labelColor = (str) => color(str, 33); // yellow
2139
+ const factorColor = (str) => color(str, 32); // green
2140
+ const logRoundRelations = (bracketData) => {
2141
+ // Find max round name length for alignment
2142
+ let maxNameLen = 0;
2143
+ for (const round of bracketData.rounds.values()) {
2144
+ maxNameLen = Math.max(maxNameLen, round.name.length);
2145
+ }
2146
+ const colWidth = maxNameLen + 2;
2147
+ // Build rows
2148
+ const rows = [];
2149
+ for (const round of bracketData.rounds.values()) {
2150
+ const relation = round.relation;
2151
+ switch (relation.type) {
2152
+ case 'nothing-to-one':
2153
+ rows.push([
2154
+ labelColor(pad('START', colWidth)),
2155
+ arrowColor('──▶'),
2156
+ roundColor(pad(round.name, colWidth)),
2157
+ arrowColor('──▶'),
2158
+ roundColor(pad(relation.nextRound.name, colWidth)),
2159
+ factorColor(`[${relation.nextRoundMatchFactor}]`),
2160
+ ]);
2161
+ break;
2162
+ case 'one-to-nothing':
2163
+ rows.push([
2164
+ roundColor(pad(relation.previousRound.name, colWidth)),
2165
+ arrowColor('──▶'),
2166
+ roundColor(pad(round.name, colWidth)),
2167
+ arrowColor('──▶'),
2168
+ labelColor(pad('END', colWidth)),
2169
+ factorColor(`[${relation.previousRoundMatchFactor}]`),
2170
+ ]);
2171
+ break;
2172
+ case 'one-to-one':
2173
+ rows.push([
2174
+ roundColor(pad(relation.previousRound.name, colWidth)),
2175
+ arrowColor('──▶'),
2176
+ roundColor(pad(round.name, colWidth)),
2177
+ arrowColor('──▶'),
2178
+ roundColor(pad(relation.nextRound.name, colWidth)),
2179
+ factorColor(`[Prev: ${relation.previousRoundMatchFactor}, Next: ${relation.nextRoundMatchFactor}]`),
2180
+ ]);
2181
+ break;
2182
+ case 'two-to-one':
2183
+ rows.push([
2184
+ roundColor(pad(relation.previousUpperRound.name, colWidth)),
2185
+ arrowColor(' │ '),
2186
+ roundColor(pad(relation.previousLowerRound.name, colWidth)),
2187
+ arrowColor('──▶'),
2188
+ roundColor(pad(round.name, colWidth)),
2189
+ arrowColor('──▶'),
2190
+ roundColor(pad(relation.nextRound.name, colWidth)),
2191
+ factorColor(`[Upper: ${relation.previousUpperRoundMatchFactor}, Lower: ${relation.previousLowerRoundMatchFactor}, Next: ${relation.nextRoundMatchFactor}]`),
2192
+ ]);
2193
+ break;
2194
+ case 'two-to-nothing':
2195
+ rows.push([
2196
+ roundColor(pad(relation.previousUpperRound.name, colWidth)),
2197
+ arrowColor(' │'),
2198
+ roundColor(pad(relation.previousLowerRound.name, colWidth)),
2199
+ arrowColor(' ▼'),
2200
+ roundColor(pad(round.name, colWidth)),
2201
+ arrowColor('──▶'),
2202
+ labelColor(pad('END', colWidth)),
2203
+ factorColor(`[Upper: ${relation.previousUpperRoundMatchFactor}, Lower: ${relation.previousLowerRoundMatchFactor}]`),
2204
+ ]);
2205
+ break;
2206
+ }
2207
+ }
2208
+ // Print header
2209
+ const divider = (label) => {
2210
+ // eslint-disable-next-line no-control-regex
2211
+ const width = rows[0]?.reduce((w, col) => w + col.replace(/\x1b\[[0-9;]*m/g, '').length + 2, 0) || 60;
2212
+ return `\n${'='.repeat(width)}\n${labelColor(label)}\n${'='.repeat(width)}\n`;
2219
2213
  };
2214
+ console.log(divider('Bracket Structure'));
2215
+ // Print rows
2216
+ for (const row of rows) {
2217
+ console.log(row.join(' '));
2218
+ }
2219
+ console.log();
2220
2220
  };
2221
2221
 
2222
- function logGridAreasFormatted(gridTemplateAreas) {
2223
- if (!gridTemplateAreas) {
2224
- console.log('No grid areas to display');
2225
- return;
2226
- }
2227
- // Parse the grid template areas string
2228
- const rows = gridTemplateAreas
2229
- .split('\n')
2230
- .map((row) => row.replace(/^"/, '').replace(/"$/, '')) // Remove quotes
2231
- .map((row) => row.split(' ')); // Split into individual areas
2232
- if (rows.length === 0) {
2233
- console.log('No grid areas to display');
2234
- return;
2222
+ const factorialCache = new Map();
2223
+ const getAvailableSwissGroupsForRound = (roundNumber, totalMatchesInRound) => {
2224
+ const ADVANCE_WINS = 3;
2225
+ const ELIMINATE_LOSSES = 3;
2226
+ // Cache factorial calculations
2227
+ const getFactorial = (n) => {
2228
+ if (n <= 1)
2229
+ return 1;
2230
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
2231
+ if (factorialCache.has(n))
2232
+ return factorialCache.get(n);
2233
+ const result = n * getFactorial(n - 1);
2234
+ factorialCache.set(n, result);
2235
+ return result;
2236
+ };
2237
+ // Pre-calculate roundFactorial
2238
+ const roundFactorial = getFactorial(roundNumber);
2239
+ let totalCombinations = 0;
2240
+ const validGroups = [];
2241
+ // Single loop to gather valid groups and total combinations
2242
+ for (let wins = roundNumber; wins >= 0; wins--) {
2243
+ const losses = roundNumber - wins;
2244
+ const remainingGames = ADVANCE_WINS + ELIMINATE_LOSSES - (wins + losses) - 1;
2245
+ const notYetEliminated = losses < ELIMINATE_LOSSES;
2246
+ const canStillAdvance = wins < ADVANCE_WINS && remainingGames >= 0;
2247
+ if (!canStillAdvance || !notYetEliminated)
2248
+ continue;
2249
+ const combinations = roundFactorial / (getFactorial(wins) * getFactorial(losses));
2250
+ totalCombinations += combinations;
2251
+ validGroups.push({ wins, losses, combinations });
2235
2252
  }
2236
- // Find the maximum width for each column to align properly
2237
- const columnCount = rows[0]?.length ?? 0;
2238
- const columnWidths = [];
2239
- for (let colIndex = 0; colIndex < columnCount; colIndex++) {
2240
- const maxWidth = Math.max(...rows.map((row) => (row[colIndex] || '').length));
2241
- columnWidths.push(Math.max(maxWidth, 3)); // Minimum width of 3
2253
+ // Create final groups with calculated proportions
2254
+ return validGroups.map(({ wins, losses, combinations }) => ({
2255
+ id: `${wins}-${losses}`,
2256
+ name: `${wins}-${losses}`,
2257
+ matchesInGroup: Math.round((combinations / totalCombinations) * totalMatchesInRound),
2258
+ }));
2259
+ };
2260
+ const generateBracketRoundSwissGroupMaps = (bracketData) => {
2261
+ if (bracketData.mode !== TOURNAMENT_MODE.SWISS_WITH_ELIMINATION) {
2262
+ return null;
2242
2263
  }
2243
- console.log('\n📋 Grid Template Areas (Formatted):');
2244
- console.log('═'.repeat(columnWidths.reduce((sum, width) => sum + width + 3, 0)));
2245
- // Log each row with proper spacing
2246
- rows.forEach((row, rowIndex) => {
2247
- const formattedRow = row
2248
- .map((area, colIndex) => {
2249
- const width = columnWidths[colIndex] ?? 0;
2250
- return area.padEnd(width);
2251
- })
2252
- .join(' | ');
2253
- console.log(`${String(rowIndex).padStart(2, '0')}: ${formattedRow}`);
2254
- });
2255
- console.log('═'.repeat(columnWidths.reduce((sum, width) => sum + width + 3, 0)));
2256
- console.log(`📊 Grid dimensions: ${rows.length} rows × ${columnCount} columns\n`);
2257
- }
2258
- function gridColumnsToGridProperty(grid) {
2259
- if (!grid.length) {
2260
- return {
2261
- gridTemplateAreas: '',
2262
- gridTemplateRows: '',
2263
- gridTemplateColumns: '',
2264
+ const roundsWithSwissGroups = new Map();
2265
+ let roundNumber = 0;
2266
+ for (const bracketRound of bracketData.rounds.values()) {
2267
+ const availableGroups = getAvailableSwissGroupsForRound(roundNumber, bracketRound.matchCount);
2268
+ const roundSwissData = {
2269
+ groups: new Map(),
2264
2270
  };
2265
- }
2266
- // Calculate the total number of columns (subcolumns across all master columns)
2267
- const totalColumns = grid.reduce((total, masterColumn) => {
2268
- return total + Math.max(...masterColumn.sections.map((section) => section.subColumns.length));
2269
- }, 0);
2270
- // Find all unique sections across all master columns to determine rows
2271
- const allSections = [];
2272
- grid.forEach((masterColumn, masterColumnIndex) => {
2273
- masterColumn.sections.forEach((section, sectionIndex) => {
2274
- // For each section, we need to process all its subcolumns
2275
- section.subColumns.forEach((subColumn) => {
2276
- allSections.push({
2277
- elements: subColumn.elements,
2278
- sectionIndex,
2279
- masterColumnIndex,
2280
- });
2281
- });
2282
- });
2283
- });
2284
- // Group sections by their index to create rows
2285
- const sectionsByIndex = new Map();
2286
- let globalColumnIndex = 0;
2287
- grid.forEach((masterColumn, masterColumnIndex) => {
2288
- const maxSubColumns = Math.max(...masterColumn.sections.map((section) => section.subColumns.length));
2289
- masterColumn.sections.forEach((section, sectionIndex) => {
2290
- if (!sectionsByIndex.has(sectionIndex)) {
2291
- sectionsByIndex.set(sectionIndex, []);
2271
+ for (const group of availableGroups) {
2272
+ const subGroup = {
2273
+ id: group.id,
2274
+ name: group.name,
2275
+ matches: new BracketMap(),
2276
+ allowedMatchCount: group.matchesInGroup,
2277
+ };
2278
+ roundSwissData.groups.set(group.id, subGroup);
2279
+ }
2280
+ const emptyMatchIds = [];
2281
+ for (const match of bracketRound.matches.values()) {
2282
+ const anyParticipant = match.home || match.away;
2283
+ if (!anyParticipant) {
2284
+ emptyMatchIds.push(match.id);
2285
+ continue;
2292
2286
  }
2293
- section.subColumns.forEach((subColumn, subColumnIndex) => {
2294
- sectionsByIndex.get(sectionIndex).push({
2295
- elements: subColumn.elements,
2296
- columnIndex: globalColumnIndex + subColumnIndex,
2297
- masterColumnIndex,
2298
- });
2299
- });
2300
- });
2301
- globalColumnIndex += maxSubColumns;
2302
- });
2303
- // Build the grid matrix - sections become rows, subcolumns become columns
2304
- const areaMatrix = [];
2305
- const rowHeights = [];
2306
- // Process each section (which becomes a row group)
2307
- for (const [sectionIndex, sectionColumns] of sectionsByIndex.entries()) {
2308
- // Find max rows needed for this section
2309
- const maxRowsInSection = Math.max(...sectionColumns.map((col) => col.elements.reduce((total, element) => total + element.parts.length, 0)), 0);
2310
- // Create rows for this section
2311
- for (let rowIndex = 0; rowIndex < maxRowsInSection; rowIndex++) {
2312
- const areaRow = new Array(totalColumns).fill('.');
2313
- let currentRowHeight;
2314
- // Fill in areas for each column in this section
2315
- for (const sectionColumn of sectionColumns) {
2316
- let accumulatedRows = 0;
2317
- let area = '.';
2318
- let height;
2319
- // Find which element this row belongs to
2320
- for (const element of sectionColumn.elements) {
2321
- const elementRowCount = element.parts.length;
2322
- if (rowIndex >= accumulatedRows && rowIndex < accumulatedRows + elementRowCount) {
2323
- const relativeRowIndex = rowIndex - accumulatedRows;
2324
- area = element.area;
2325
- height = element.parts[relativeRowIndex]?.dimensions.height;
2326
- break;
2327
- }
2328
- accumulatedRows += elementRowCount;
2329
- }
2330
- areaRow[sectionColumn.columnIndex] = area;
2331
- // Use the first defined height for this row
2332
- if (!currentRowHeight && height !== undefined) {
2333
- currentRowHeight = `${height}px`;
2287
+ const wins = anyParticipant.winCount;
2288
+ const losses = anyParticipant.lossCount;
2289
+ const group = roundSwissData.groups.get(`${wins}-${losses}`);
2290
+ if (!group)
2291
+ throw new Error('Group not found for match: ' + match.id);
2292
+ group.matches.set(match.id, match);
2293
+ }
2294
+ for (const emptyMatchId of emptyMatchIds) {
2295
+ const match = bracketRound.matches.getOrThrow(emptyMatchId);
2296
+ let groupFound = false;
2297
+ for (const group of roundSwissData.groups.values()) {
2298
+ if (group.matches.size < group.allowedMatchCount) {
2299
+ group.matches.set(match.id, match);
2300
+ groupFound = true;
2301
+ break;
2334
2302
  }
2335
2303
  }
2336
- areaMatrix.push(areaRow);
2337
- rowHeights.push(currentRowHeight ?? 'auto');
2304
+ if (!groupFound) {
2305
+ throw new Error('No group found for empty match');
2306
+ }
2338
2307
  }
2308
+ roundsWithSwissGroups.set(bracketRound.id, roundSwissData);
2309
+ roundNumber++;
2339
2310
  }
2340
- // Calculate column widths
2341
- const columnWidths = [];
2342
- for (const masterColumn of grid) {
2343
- const maxSubColumns = Math.max(...masterColumn.sections.map((section) => section.subColumns.length));
2344
- for (let i = 0; i < maxSubColumns; i++) {
2345
- // Find the width from any section that has this subcolumn
2346
- const subColumnWidth = masterColumn.sections.find((section) => section.subColumns[i])?.subColumns[i]?.dimensions.width ?? 0;
2347
- columnWidths.push(`${subColumnWidth}px`);
2348
- }
2311
+ return roundsWithSwissGroups;
2312
+ };
2313
+
2314
+ class NewBracketDefaultMatchComponent {
2315
+ constructor() {
2316
+ this.bracketRound = input.required(...(ngDevMode ? [{ debugName: "bracketRound" }] : []));
2317
+ this.bracketMatch = input.required(...(ngDevMode ? [{ debugName: "bracketMatch" }] : []));
2318
+ this.bracketRoundSwissGroup = input.required(...(ngDevMode ? [{ debugName: "bracketRoundSwissGroup" }] : []));
2349
2319
  }
2350
- // Build the CSS properties
2351
- const gridTemplateAreas = areaMatrix.map((row) => `"${row.join(' ')}"`).join('\n');
2352
- const gridTemplateRows = rowHeights.join(' ');
2353
- const gridTemplateColumns = columnWidths.join(' ');
2354
- const result = {
2355
- gridTemplateAreas,
2356
- gridTemplateRows,
2357
- gridTemplateColumns,
2358
- };
2359
- // logGridAreasFormatted(gridTemplateAreas);
2360
- return result;
2320
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.0", ngImport: i0, type: NewBracketDefaultMatchComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
2321
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "20.3.0", type: NewBracketDefaultMatchComponent, isStandalone: true, selector: "et-new-bracket-default-match", inputs: { bracketRound: { classPropertyName: "bracketRound", publicName: "bracketRound", isSignal: true, isRequired: true, transformFunction: null }, bracketMatch: { classPropertyName: "bracketMatch", publicName: "bracketMatch", isSignal: true, isRequired: true, transformFunction: null }, bracketRoundSwissGroup: { classPropertyName: "bracketRoundSwissGroup", publicName: "bracketRoundSwissGroup", isSignal: true, isRequired: true, transformFunction: null } }, host: { classAttribute: "et-new-bracket-default-match-host" }, ngImport: i0, template: ` {{ bracketMatch().id }} `, isInline: true, styles: [".et-new-bracket-default-match-host{display:block;padding:8px;border:1px solid yellow;inline-size:100%;block-size:100%;display:flex;justify-content:center;align-items:center;box-sizing:border-box;font-size:12px}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); }
2361
2322
  }
2362
-
2363
- const calculateUpperLowerRatio = (upperRoundsCount, lowerRoundsCount) => {
2364
- // In double elimination, after the first aligned column,
2365
- // we need to distribute the remaining upper rounds across the remaining lower rounds
2366
- const remainingUpperRounds = upperRoundsCount - 1; // Subtract the first round
2367
- const remainingLowerRounds = lowerRoundsCount - 1; // Subtract the first round
2368
- if (remainingUpperRounds === 0) {
2369
- return 1; // Edge case: only one upper round
2323
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.0", ngImport: i0, type: NewBracketDefaultMatchComponent, decorators: [{
2324
+ type: Component,
2325
+ args: [{ selector: 'et-new-bracket-default-match', template: ` {{ bracketMatch().id }} `, standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, host: {
2326
+ class: 'et-new-bracket-default-match-host',
2327
+ }, styles: [".et-new-bracket-default-match-host{display:block;padding:8px;border:1px solid yellow;inline-size:100%;block-size:100%;display:flex;justify-content:center;align-items:center;box-sizing:border-box;font-size:12px}\n"] }]
2328
+ }] });
2329
+
2330
+ class NewBracketDefaultRoundHeaderComponent {
2331
+ constructor() {
2332
+ this.bracketRound = input.required(...(ngDevMode ? [{ debugName: "bracketRound" }] : []));
2333
+ this.bracketRoundSwissGroup = input.required(...(ngDevMode ? [{ debugName: "bracketRoundSwissGroup" }] : []));
2370
2334
  }
2371
- return remainingLowerRounds / remainingUpperRounds;
2372
- };
2373
- const calculateColumnSplitFactor = (upperToLowerRatio) => {
2374
- // The split factor determines how many sub-columns each match column will have
2375
- // For example, if the ratio is 1.5, we split each match column
2376
- // into 2 sub-columns so that matches can be centered correctly
2377
- if (upperToLowerRatio === 1.5) {
2378
- return 2; // Split into 2 sub-columns for 1.5 ratio
2335
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.0", ngImport: i0, type: NewBracketDefaultRoundHeaderComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
2336
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.0", type: NewBracketDefaultRoundHeaderComponent, isStandalone: true, selector: "et-new-bracket-default-round-header", inputs: { bracketRound: { classPropertyName: "bracketRound", publicName: "bracketRound", isSignal: true, isRequired: true, transformFunction: null }, bracketRoundSwissGroup: { classPropertyName: "bracketRoundSwissGroup", publicName: "bracketRoundSwissGroup", isSignal: true, isRequired: true, transformFunction: null } }, host: { classAttribute: "et-new-bracket-default-round-header-host" }, ngImport: i0, template: `
2337
+ {{ bracketRound().name }}
2338
+
2339
+ @if (bracketRoundSwissGroup()) {
2340
+ ({{ bracketRoundSwissGroup()?.name }})
2379
2341
  }
2380
- if (upperToLowerRatio === 2) {
2381
- return 1; // Split into 1 sub-columns for 2 ratio
2342
+ `, isInline: true, styles: [".et-new-bracket-default-round-header-host{display:block;padding:8px;border:1px solid green;inline-size:100%;block-size:100%;display:flex;justify-content:center;align-items:center;box-sizing:border-box}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); }
2343
+ }
2344
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.0", ngImport: i0, type: NewBracketDefaultRoundHeaderComponent, decorators: [{
2345
+ type: Component,
2346
+ args: [{ selector: 'et-new-bracket-default-round-header', template: `
2347
+ {{ bracketRound().name }}
2348
+
2349
+ @if (bracketRoundSwissGroup()) {
2350
+ ({{ bracketRoundSwissGroup()?.name }})
2382
2351
  }
2383
- return 1;
2352
+ `, standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, host: {
2353
+ class: 'et-new-bracket-default-round-header-host',
2354
+ }, styles: [".et-new-bracket-default-round-header-host{display:block;padding:8px;border:1px solid green;inline-size:100%;block-size:100%;display:flex;justify-content:center;align-items:center;box-sizing:border-box}\n"] }]
2355
+ }] });
2356
+
2357
+ const path = (d, options) => `<path d="${d.replace(/\s+/g, ' ').trim()}" stroke="currentColor" fill="none" stroke-width="${options.width}" stroke-dasharray="${options.dashArray}" stroke-dashoffset="${options.dashOffset}" class="${options.className}" />`;
2358
+
2359
+ const curvePath = (from, to, direction, options) => {
2360
+ const inverted = options.inverted ?? false;
2361
+ // Inline/block coordinates depending on direction and inversion
2362
+ const fromInline = inverted ? from.inline.start : from.inline.end;
2363
+ const toInline = inverted ? to.inline.end : to.inline.start;
2364
+ const fromBlock = from.block.center;
2365
+ const toBlock = to.block.center;
2366
+ // Curve parameters
2367
+ const startCurve = options.lineStartingCurveAmount;
2368
+ const endCurve = options.lineEndingCurveAmount;
2369
+ const totalInline = Math.abs(toInline - fromInline);
2370
+ const straightLength = (totalInline - startCurve - endCurve) / 2;
2371
+ // Calculate key points for the path
2372
+ const straightEnd = inverted ? fromInline - straightLength : fromInline + straightLength;
2373
+ const straightStart = inverted ? toInline + straightLength : toInline - straightLength;
2374
+ // First curve (from start)
2375
+ const firstCurveEndX = inverted ? straightEnd - startCurve : straightEnd + startCurve;
2376
+ const firstCurveEndY = direction === 'down' ? fromBlock + startCurve : fromBlock - startCurve;
2377
+ // Second curve (to end)
2378
+ const secondCurveStartY = direction === 'down' ? toBlock - endCurve : toBlock + endCurve;
2379
+ const secondCurveEndX = straightStart;
2380
+ const secondCurveEndY = toBlock;
2381
+ const secondCurveBezierX = inverted ? straightStart + endCurve : straightStart - endCurve;
2382
+ // SVG path string
2383
+ const d = [
2384
+ `M ${fromInline} ${fromBlock}`,
2385
+ `H ${straightEnd}`,
2386
+ `Q ${firstCurveEndX} ${fromBlock}, ${firstCurveEndX} ${firstCurveEndY}`,
2387
+ `V ${secondCurveStartY}`,
2388
+ `Q ${secondCurveBezierX} ${toBlock}, ${secondCurveEndX} ${secondCurveEndY}`,
2389
+ `H ${toInline}`,
2390
+ ].join(' ');
2391
+ return path(d, options.path);
2384
2392
  };
2385
- const calculateLowerRoundIndex = (subColumnIndex, splitFactor) => Math.floor(subColumnIndex / splitFactor);
2386
- const calculateUpperRoundIndex = (subColumnIndex, upperToLowerRatio, splitFactor) => {
2387
- // Calculate which complete column we're in
2388
- const completeColumnIndex = Math.floor(subColumnIndex / splitFactor);
2389
- // For the first complete column (index 0), always use the first upper round (index 0)
2390
- if (completeColumnIndex === 0) {
2391
- return 0;
2392
- }
2393
- // For subsequent columns, map based on the ratio
2394
- // We subtract 1 because the first column is handled separately
2395
- const adjustedColumnIndex = completeColumnIndex - 1;
2396
- // Calculate which upper round this column should use
2397
- // We add 1 because we start from the second upper round (index 1)
2398
- return Math.floor(adjustedColumnIndex / upperToLowerRatio) + 1;
2393
+
2394
+ const linePath = (from, to, options) => {
2395
+ return path(`M ${from.inline.end} ${from.block.center} L ${to.inline.start} ${to.block.center}`, options.path);
2399
2396
  };
2400
2397
 
2401
- const createBracketGapMasterColumn = (config) => {
2402
- const { existingMasterColumns, columnGap } = config;
2403
- const lastMasterColumn = existingMasterColumns[existingMasterColumns.length - 1];
2404
- if (!lastMasterColumn) {
2405
- throw new Error('No last master column found in existing master columns');
2406
- }
2407
- const { masterColumn, pushSection } = createBracketMasterColumn({
2408
- columnWidth: columnGap,
2409
- padding: {
2410
- bottom: 0,
2411
- left: 0,
2412
- right: 0,
2413
- top: 0,
2414
- },
2415
- });
2416
- for (const section of lastMasterColumn.sections) {
2417
- const { masterColumnSection, pushSubColumn } = createBracketMasterColumnSection({
2418
- type: 'gap',
2419
- });
2420
- const lastSubColumn = section.subColumns[section.subColumns.length - 1];
2421
- if (!lastSubColumn) {
2422
- throw new Error('No last sub column found in section');
2423
- }
2424
- const { pushElement, subColumn } = createBracketSubColumn({
2425
- span: {
2426
- isStart: lastSubColumn.span.isEnd ? true : false,
2427
- isEnd: lastSubColumn.span.isEnd ? true : false,
2428
- },
2429
- });
2430
- for (const lastSubColumnElement of lastSubColumn.elements) {
2431
- const partHeights = lastSubColumnElement.parts.map((part) => part.dimensions.height);
2432
- const elementToCreate = (() => {
2433
- switch (lastSubColumnElement.type) {
2434
- case 'matchGap':
2435
- case 'roundHeaderGap':
2436
- case 'roundGap':
2437
- case 'colGap': {
2438
- return {
2439
- area: !lastSubColumn.span.isEnd ? lastSubColumnElement.area : `.`,
2440
- type: !lastSubColumn.span.isEnd ? lastSubColumnElement.type : 'colGap',
2441
- elementHeight: lastSubColumnElement.dimensions.height,
2442
- partHeights,
2443
- };
2398
+ const makePos = (dimensions) => ({
2399
+ block: {
2400
+ start: dimensions.top,
2401
+ end: dimensions.top + dimensions.height,
2402
+ center: dimensions.top + dimensions.height / 2,
2403
+ },
2404
+ inline: {
2405
+ start: dimensions.left,
2406
+ end: dimensions.left + dimensions.width,
2407
+ center: dimensions.left + dimensions.width / 2,
2408
+ },
2409
+ });
2410
+ const drawMan = (dimensions) => {
2411
+ const svgParts = [];
2412
+ for (const col of dimensions.bracketGrid.columns) {
2413
+ for (const el of col.elements) {
2414
+ if (el.type === 'header')
2415
+ continue;
2416
+ const currentMatchParticipantsShortIds = [el.match.home?.shortId, el.match.away?.shortId]
2417
+ .filter((id) => !!id)
2418
+ .join(' ');
2419
+ const pathOptions = { ...dimensions.path, className: currentMatchParticipantsShortIds };
2420
+ const currentPos = makePos(el.dimensions);
2421
+ // No lines for the third place match
2422
+ if (el.round.type === COMMON_BRACKET_ROUND_TYPE.THIRD_PLACE)
2423
+ continue;
2424
+ switch (el.match.relation.type) {
2425
+ case 'nothing-to-one': {
2426
+ continue;
2427
+ }
2428
+ case 'one-to-nothing':
2429
+ case 'one-to-one': {
2430
+ const prev = dimensions.bracketGrid.matchElementMap.getOrThrow(el.match.relation.previousMatch.id);
2431
+ const prevPos = makePos(prev.dimensions);
2432
+ // draw a straight line
2433
+ svgParts.push(linePath(prevPos, currentPos, { path: pathOptions }));
2434
+ break;
2435
+ }
2436
+ case 'two-to-nothing':
2437
+ case 'two-to-one': {
2438
+ const prevUpper = dimensions.bracketGrid.matchElementMap.getOrThrow(el.match.relation.previousUpperMatch.id);
2439
+ const prevLower = dimensions.bracketGrid.matchElementMap.getOrThrow(el.match.relation.previousLowerMatch.id);
2440
+ const prevUpperPos = makePos(prevUpper.dimensions);
2441
+ const prevLowerPos = makePos(prevLower.dimensions);
2442
+ const isLowerUpperMerger = el.match.relation.previousLowerRound.id !== el.match.relation.previousUpperRound.id;
2443
+ const invertCurve = el.round.mirrorRoundType === BRACKET_ROUND_MIRROR_TYPE.RIGHT;
2444
+ const curveOptions = {
2445
+ ...dimensions.curve,
2446
+ inverted: invertCurve,
2447
+ path: { ...dimensions.path, className: '' },
2448
+ };
2449
+ if (isLowerUpperMerger) {
2450
+ svgParts.push(linePath(prevUpperPos, currentPos, { path: pathOptions }));
2444
2451
  }
2445
- case 'header':
2446
- if (lastSubColumn.span.isEnd)
2447
- break;
2448
- return {
2449
- area: lastSubColumnElement.area,
2450
- type: lastSubColumnElement.type,
2451
- elementHeight: lastSubColumnElement.dimensions.height,
2452
- partHeights,
2453
- component: lastSubColumnElement.component,
2454
- round: lastSubColumnElement.round,
2455
- roundSwissGroup: lastSubColumnElement.roundSwissGroup,
2456
- };
2457
- case 'match':
2458
- if (lastSubColumn.span.isEnd)
2459
- break;
2460
- return {
2461
- area: lastSubColumnElement.area,
2462
- type: lastSubColumnElement.type,
2463
- elementHeight: lastSubColumnElement.dimensions.height,
2464
- partHeights,
2465
- component: lastSubColumnElement.component,
2466
- round: lastSubColumnElement.round,
2467
- match: lastSubColumnElement.match,
2468
- roundSwissGroup: lastSubColumnElement.roundSwissGroup,
2469
- };
2452
+ else {
2453
+ // draw two lines that merge into one in the middle
2454
+ svgParts.push(curvePath(prevUpperPos, currentPos, 'down', {
2455
+ ...curveOptions,
2456
+ path: {
2457
+ ...curveOptions.path,
2458
+ className: el.match.relation.previousUpperMatch.winner?.shortId || '',
2459
+ },
2460
+ }));
2461
+ }
2462
+ svgParts.push(curvePath(prevLowerPos, currentPos, 'up', {
2463
+ ...curveOptions,
2464
+ path: {
2465
+ ...curveOptions.path,
2466
+ className: el.match.relation.previousLowerMatch.winner?.shortId || '',
2467
+ },
2468
+ }));
2469
+ if (el.round.mirrorRoundType === BRACKET_ROUND_MIRROR_TYPE.RIGHT &&
2470
+ el.match.relation.type === 'two-to-one' &&
2471
+ el.match.relation.nextRound.mirrorRoundType === null) {
2472
+ // draw a straight line for the special case of connecting the final match to the mirrored semi final match
2473
+ const next = dimensions.bracketGrid.matchElementMap.getOrThrow(el.match.relation.nextMatch.id);
2474
+ const nextPos = makePos(next.dimensions);
2475
+ svgParts.push(linePath(nextPos, currentPos, { path: pathOptions }));
2476
+ }
2477
+ break;
2470
2478
  }
2471
- return {
2472
- area: `.`,
2473
- type: 'colGap',
2474
- elementHeight: lastSubColumnElement.dimensions.height,
2475
- partHeights,
2476
- };
2477
- })();
2478
- pushElement(createBracketElement(elementToCreate).element);
2479
+ }
2479
2480
  }
2480
- pushSubColumn(subColumn);
2481
- pushSection(masterColumnSection);
2482
2481
  }
2483
- return masterColumn;
2482
+ return svgParts.join('');
2483
+ };
2484
+
2485
+ const createBracketElementPart = (config) => {
2486
+ const { elementPartHeight } = config;
2487
+ const newElementPart = {
2488
+ dimensions: {
2489
+ width: 0,
2490
+ height: elementPartHeight,
2491
+ top: 0,
2492
+ left: 0,
2493
+ },
2494
+ };
2495
+ return {
2496
+ elementPart: newElementPart,
2497
+ };
2484
2498
  };
2485
2499
 
2486
- const createRoundBracketSubColumnRelativeToFirstRound = (config) => {
2487
- const { firstRound, round, options, span, hasReverseFinal } = config;
2488
- const { subColumn, pushElement } = createBracketSubColumn({
2489
- span,
2490
- });
2491
- const matchFactor = firstRound.matchCount / round.matchCount;
2492
- const matches = Array.from(round.matches.values());
2493
- const elementsToCreate = [];
2494
- // Only include a header row if headers exist
2495
- if (options.roundHeaderHeight > 0) {
2496
- elementsToCreate.push({
2497
- type: 'header',
2498
- area: `h${round.shortId}`,
2499
- partHeights: [options.roundHeaderHeight],
2500
- elementHeight: options.roundHeaderHeight,
2501
- component: config.components.roundHeader,
2502
- round,
2503
- roundSwissGroup: null,
2504
- }, {
2505
- type: 'roundHeaderGap',
2506
- area: '.',
2507
- partHeights: [options.roundHeaderGap],
2508
- elementHeight: options.roundHeaderGap,
2509
- });
2510
- }
2511
- // Add match elements to create
2512
- for (const [matchIndex, match] of matches.entries()) {
2513
- const isLastMatch = matchIndex === matches.length - 1;
2514
- const matchRows = [];
2515
- for (let factorIndex = 0; factorIndex < matchFactor; factorIndex++) {
2516
- const isLastFactor = factorIndex === matchFactor - 1;
2517
- // Add the match height
2518
- matchRows.push(options.matchHeight);
2519
- if (isLastFactor)
2520
- continue;
2521
- // Add gap between match factors (except after the last one)
2522
- matchRows.push(options.rowGap);
2500
+ const createBracketElement = (config) => {
2501
+ const { type, area, elementHeight, partHeights } = config;
2502
+ const parts = [];
2503
+ const newElementBase = {
2504
+ dimensions: {
2505
+ width: 0,
2506
+ height: elementHeight,
2507
+ top: 0,
2508
+ left: 0,
2509
+ },
2510
+ containerDimensions: {
2511
+ width: 0,
2512
+ height: 0,
2513
+ top: 0,
2514
+ left: 0,
2515
+ },
2516
+ parts,
2517
+ area,
2518
+ };
2519
+ const newElement = (() => {
2520
+ switch (type) {
2521
+ case 'header':
2522
+ return {
2523
+ ...newElementBase,
2524
+ type,
2525
+ component: config.component,
2526
+ round: config.round,
2527
+ roundSwissGroup: config.roundSwissGroup,
2528
+ };
2529
+ case 'match':
2530
+ return {
2531
+ ...newElementBase,
2532
+ type,
2533
+ component: config.component,
2534
+ match: config.match,
2535
+ round: config.round,
2536
+ roundSwissGroup: config.roundSwissGroup,
2537
+ };
2538
+ case 'matchGap':
2539
+ case 'roundHeaderGap':
2540
+ case 'roundGap':
2541
+ case 'colGap':
2542
+ return {
2543
+ ...newElementBase,
2544
+ type,
2545
+ };
2546
+ default:
2547
+ throw new Error(`Unknown element type: ${type}`);
2523
2548
  }
2524
- const isFinalMatch = hasReverseFinal
2525
- ? round.type === DOUBLE_ELIMINATION_BRACKET_ROUND_TYPE.REVERSE_FINAL
2526
- : round.type === COMMON_BRACKET_ROUND_TYPE.FINAL;
2527
- elementsToCreate.push({
2528
- type: 'match',
2529
- area: `m${match.shortId}`,
2530
- partHeights: matchRows,
2531
- elementHeight: isFinalMatch ? options.finalMatchHeight : options.matchHeight,
2532
- component: isFinalMatch ? config.components.finalMatch : config.components.match,
2533
- match,
2534
- round,
2535
- roundSwissGroup: null,
2549
+ })();
2550
+ const pushPart = (...newParts) => {
2551
+ parts.push(...newParts);
2552
+ };
2553
+ for (const partHeight of partHeights) {
2554
+ const { elementPart } = createBracketElementPart({
2555
+ elementPartHeight: partHeight,
2536
2556
  });
2537
- if (!isLastMatch) {
2538
- elementsToCreate.push({
2539
- type: 'matchGap',
2540
- area: '.',
2541
- partHeights: [options.rowGap],
2542
- elementHeight: options.rowGap,
2543
- });
2544
- }
2545
- }
2546
- // Create all elements at once
2547
- for (const elementData of elementsToCreate) {
2548
- const { element } = createBracketElement(elementData);
2549
- pushElement(element);
2557
+ pushPart(elementPart);
2550
2558
  }
2551
- return subColumn;
2559
+ return {
2560
+ element: newElement,
2561
+ };
2552
2562
  };
2553
2563
 
2554
- const createDoubleEliminationGrid = (bracketData, options, components) => {
2555
- const grid = createBracketGrid({ spanElementWidth: options.columnWidth });
2556
- const upperBracketRounds = Array.from(bracketData.roundsByType.getOrThrow(DOUBLE_ELIMINATION_BRACKET_ROUND_TYPE.UPPER_BRACKET).values());
2557
- const lowerBracketRounds = Array.from(bracketData.roundsByType.getOrThrow(DOUBLE_ELIMINATION_BRACKET_ROUND_TYPE.LOWER_BRACKET).values());
2558
- const remainingRounds = Array.from(bracketData.rounds.values()).filter((r) => r.type !== DOUBLE_ELIMINATION_BRACKET_ROUND_TYPE.UPPER_BRACKET &&
2559
- r.type !== DOUBLE_ELIMINATION_BRACKET_ROUND_TYPE.LOWER_BRACKET &&
2560
- r.type !== COMMON_BRACKET_ROUND_TYPE.THIRD_PLACE);
2561
- const thirdPlaceRound = bracketData.roundsByType.get(COMMON_BRACKET_ROUND_TYPE.THIRD_PLACE)?.first() ?? null;
2562
- const hasReverseFinal = !!bracketData.roundsByType.get(DOUBLE_ELIMINATION_BRACKET_ROUND_TYPE.REVERSE_FINAL)?.first();
2563
- const firstUpperRound = upperBracketRounds[0];
2564
- const firstLowerRound = lowerBracketRounds[0];
2565
- if (!firstUpperRound || !firstLowerRound) {
2566
- throw new Error('No upper or lower rounds found in bracket data');
2567
- }
2568
- const upperToLowerRatio = calculateUpperLowerRatio(upperBracketRounds.length, lowerBracketRounds.length);
2569
- const columnSplitFactor = calculateColumnSplitFactor(upperToLowerRatio);
2570
- let lastRoundLastSubColumnUpperIndex = -1;
2571
- let lastRoundLastSubColumnLowerIndex = -1;
2572
- for (const [lowerRoundIndex, lowerRound] of lowerBracketRounds.entries()) {
2573
- const isLastLowerRound = lowerRoundIndex === lowerBracketRounds.length - 1;
2574
- const { masterColumn, pushSection } = createBracketMasterColumn({
2575
- columnWidth: options.columnWidth,
2576
- padding: {
2577
- bottom: 0,
2578
- left: 0,
2579
- right: 0,
2580
- top: 0,
2581
- },
2582
- });
2583
- const { masterColumnSection: upperSection, pushSubColumn: pushUpperSubColumn } = createBracketMasterColumnSection({
2584
- type: 'round',
2585
- });
2586
- const { masterColumnSection: upperLowerGapSection, pushSubColumn: pushUpperLowerSubColumn } = createBracketMasterColumnSection({
2587
- type: 'gap',
2588
- });
2589
- const { masterColumnSection: lowerSection, pushSubColumn: pushLowerSubColumn } = createBracketMasterColumnSection({
2590
- type: 'round',
2591
- });
2592
- for (let currentColumnSplitFactor = 1; currentColumnSplitFactor <= columnSplitFactor; currentColumnSplitFactor++) {
2593
- const subColumnIndex = lowerRoundIndex * columnSplitFactor + (currentColumnSplitFactor - 1);
2594
- const currentUpperRoundIndex = calculateUpperRoundIndex(subColumnIndex, upperToLowerRatio, columnSplitFactor);
2595
- const currentLowerRoundIndex = lowerRoundIndex;
2596
- const isFirstSubColumnInMasterColumn = currentColumnSplitFactor === 1;
2597
- const isLastSubColumnInMasterColumn = currentColumnSplitFactor === columnSplitFactor;
2598
- const previousSubColumnIndex = subColumnIndex - 1;
2599
- const nextSubColumnIndex = subColumnIndex + 1;
2600
- const previousUpperRoundIndex = previousSubColumnIndex >= 0
2601
- ? calculateUpperRoundIndex(previousSubColumnIndex, upperToLowerRatio, columnSplitFactor)
2602
- : -1;
2603
- const nextUpperRoundIndex = calculateUpperRoundIndex(nextSubColumnIndex, upperToLowerRatio, columnSplitFactor);
2604
- const previousLowerRoundIndex = previousSubColumnIndex >= 0 ? calculateLowerRoundIndex(previousSubColumnIndex, columnSplitFactor) : -1;
2605
- const nextLowerRoundIndex = calculateLowerRoundIndex(nextSubColumnIndex, columnSplitFactor);
2606
- const upperRound = upperBracketRounds[currentUpperRoundIndex];
2607
- if (!upperRound) {
2608
- throw new Error('Upper round not found for subColumnIndex: ' + subColumnIndex);
2564
+ const finalizeBracketGrid = (grid) => {
2565
+ const finalizedColumns = [];
2566
+ const finalizedElementMap = new BracketMap();
2567
+ const ignoredSections = [];
2568
+ for (const [masterColumnIndex, masterColumn] of grid.grid.masterColumns.entries()) {
2569
+ for (const [sectionIndex, section] of masterColumn.sections.entries()) {
2570
+ if (ignoredSections.some((s) => s.masterColumnIndex === masterColumnIndex && s.sectionIndex === sectionIndex)) {
2571
+ continue;
2609
2572
  }
2610
- // For upper bracket spans - check if this round is different from the previous occurrence
2611
- const isUpperSpanStart = isFirstSubColumnInMasterColumn
2612
- ? lowerRoundIndex === 0 || lastRoundLastSubColumnUpperIndex !== currentUpperRoundIndex
2613
- : previousUpperRoundIndex !== currentUpperRoundIndex;
2614
- // For upper bracket spans - check if this round will be different in the next occurrence
2615
- const isUpperSpanEnd = isLastSubColumnInMasterColumn
2616
- ? isLastLowerRound ||
2617
- calculateUpperRoundIndex((lowerRoundIndex + 1) * columnSplitFactor, upperToLowerRatio, columnSplitFactor) !==
2618
- currentUpperRoundIndex
2619
- : nextUpperRoundIndex !== currentUpperRoundIndex;
2620
- // For lower bracket spans - similar logic
2621
- const isLowerSpanStart = isFirstSubColumnInMasterColumn
2622
- ? lowerRoundIndex === 0 || lastRoundLastSubColumnLowerIndex !== currentLowerRoundIndex
2623
- : previousLowerRoundIndex !== currentLowerRoundIndex;
2624
- const isLowerSpanEnd = isLastSubColumnInMasterColumn
2625
- ? isLastLowerRound ||
2626
- calculateLowerRoundIndex((lowerRoundIndex + 1) * columnSplitFactor, columnSplitFactor) !==
2627
- currentLowerRoundIndex
2628
- : nextLowerRoundIndex !== currentLowerRoundIndex;
2629
- const upperSubColumn = createRoundBracketSubColumnRelativeToFirstRound({
2630
- firstRound: firstUpperRound,
2631
- round: upperRound,
2632
- options,
2633
- hasReverseFinal,
2634
- span: {
2635
- isStart: isUpperSpanStart,
2636
- isEnd: isUpperSpanEnd,
2637
- },
2638
- components,
2639
- });
2640
- pushUpperSubColumn(upperSubColumn);
2641
- const upperLowerGapSubColumn = createBracketSubColumn({
2642
- span: {
2643
- isStart: true,
2644
- isEnd: true,
2645
- },
2646
- });
2647
- const upperLowerGapElement = createBracketElement({
2648
- area: '.',
2649
- type: 'roundGap',
2650
- elementHeight: options.rowRoundGap,
2651
- partHeights: [options.rowRoundGap],
2652
- });
2653
- upperLowerGapSubColumn.pushElement(upperLowerGapElement.element);
2654
- pushUpperLowerSubColumn(upperLowerGapSubColumn.subColumn);
2655
- const lowerSubColumn = createRoundBracketSubColumnRelativeToFirstRound({
2656
- firstRound: firstLowerRound,
2657
- round: lowerRound,
2658
- options,
2659
- hasReverseFinal,
2660
- span: {
2661
- isStart: isLowerSpanStart,
2662
- isEnd: isLowerSpanEnd,
2573
+ const elements = [];
2574
+ const firstSubColumn = section.subColumns[0];
2575
+ const lastSubColumn = section.subColumns[section.subColumns.length - 1];
2576
+ if (!firstSubColumn || !lastSubColumn)
2577
+ continue;
2578
+ let sectionWidth = section.dimensions.width;
2579
+ for (const element of firstSubColumn.elements) {
2580
+ if (element.type === 'header') {
2581
+ elements.push({
2582
+ type: 'header',
2583
+ dimensions: element.dimensions,
2584
+ component: element.component,
2585
+ round: element.round,
2586
+ roundSwissGroup: element.roundSwissGroup,
2587
+ });
2588
+ }
2589
+ else if (element.type === 'match') {
2590
+ const matchEl = {
2591
+ type: 'match',
2592
+ dimensions: element.dimensions,
2593
+ component: element.component,
2594
+ match: element.match,
2595
+ round: element.round,
2596
+ classes: [element.match.home?.shortId, element.match.away?.shortId].filter((v) => !!v).join(' '),
2597
+ roundSwissGroup: element.roundSwissGroup,
2598
+ };
2599
+ elements.push(matchEl);
2600
+ finalizedElementMap.set(element.match.id, matchEl);
2601
+ }
2602
+ }
2603
+ if (!lastSubColumn.span.isEnd) {
2604
+ for (let index = masterColumnIndex + 1; index < grid.grid.masterColumns.length; index++) {
2605
+ const nextMaster = grid.grid.masterColumns[index];
2606
+ const nextSection = nextMaster?.sections[sectionIndex];
2607
+ if (!nextSection)
2608
+ break;
2609
+ sectionWidth += nextSection.dimensions.width;
2610
+ ignoredSections.push({ masterColumnIndex: index, sectionIndex });
2611
+ if (nextSection.subColumns.some((sc) => sc.span.isEnd)) {
2612
+ break;
2613
+ }
2614
+ }
2615
+ }
2616
+ if (!elements.length)
2617
+ continue;
2618
+ finalizedColumns.push({
2619
+ dimensions: {
2620
+ ...section.dimensions,
2621
+ width: sectionWidth,
2663
2622
  },
2664
- components,
2623
+ elements,
2665
2624
  });
2666
- pushLowerSubColumn(lowerSubColumn);
2667
- if (isLastSubColumnInMasterColumn) {
2668
- lastRoundLastSubColumnUpperIndex = currentUpperRoundIndex;
2669
- lastRoundLastSubColumnLowerIndex = currentLowerRoundIndex;
2625
+ }
2626
+ }
2627
+ return {
2628
+ columns: finalizedColumns,
2629
+ elementMap: finalizedElementMap,
2630
+ };
2631
+ };
2632
+
2633
+ const createBracketGrid = (config) => {
2634
+ const masterColumns = [];
2635
+ const spannedWidthCache = new Map();
2636
+ const spanStartLeftCache = new Map();
2637
+ const newGrid = {
2638
+ dimensions: { width: 0, height: 0, top: 0, left: 0 },
2639
+ masterColumns,
2640
+ };
2641
+ const pushMasterColumn = (...newMasterColumns) => {
2642
+ masterColumns.push(...newMasterColumns);
2643
+ };
2644
+ const calculateDimensions = () => {
2645
+ let currentMasterColumnLeft = 0;
2646
+ let maxGridHeight = 0;
2647
+ const masterCols = newGrid.masterColumns;
2648
+ for (let mcIdx = 0; mcIdx < masterCols.length; mcIdx++) {
2649
+ const masterColumn = masterCols[mcIdx];
2650
+ const { padding } = masterColumn;
2651
+ masterColumn.dimensions.left = currentMasterColumnLeft;
2652
+ masterColumn.dimensions.top = 0;
2653
+ masterColumn.dimensions.width += padding.left + padding.right;
2654
+ const sections = masterColumn.sections;
2655
+ let runningTop = 0;
2656
+ let firstSectionIsHeader = false;
2657
+ if (sections.length > 0) {
2658
+ const firstSubColumn = sections[0].subColumns[0];
2659
+ const firstElement = firstSubColumn?.elements[0];
2660
+ firstSectionIsHeader = !!(firstElement && firstElement.type === 'header');
2661
+ }
2662
+ for (let secIdx = 0; secIdx < sections.length; secIdx++) {
2663
+ const section = sections[secIdx];
2664
+ let sectionPaddingTop;
2665
+ if (secIdx === 0) {
2666
+ sectionPaddingTop = firstSectionIsHeader ? 0 : padding.top;
2667
+ }
2668
+ else {
2669
+ sectionPaddingTop = padding.top;
2670
+ }
2671
+ section.dimensions.width = masterColumn.dimensions.width;
2672
+ section.dimensions.left = masterColumn.dimensions.left;
2673
+ section.dimensions.top = runningTop;
2674
+ runningTop += sectionPaddingTop;
2675
+ const contentWidth = masterColumn.dimensions.width - padding.left - padding.right;
2676
+ const subColumns = section.subColumns;
2677
+ const totalSubColumns = subColumns.length;
2678
+ const subColumnWidth = contentWidth / totalSubColumns;
2679
+ let currentSubColumnLeft = masterColumn.dimensions.left + padding.left;
2680
+ let maxSectionHeight = 0;
2681
+ for (let scIdx = 0; scIdx < totalSubColumns; scIdx++) {
2682
+ const subColumn = subColumns[scIdx];
2683
+ subColumn.dimensions.width = subColumnWidth;
2684
+ subColumn.dimensions.left = currentSubColumnLeft;
2685
+ // TODO: The problem is here somewhere
2686
+ subColumn.dimensions.top = section.dimensions.top + sectionPaddingTop;
2687
+ let totalSubColumnHeight = 0;
2688
+ const elements = subColumn.elements;
2689
+ for (let elIdx = 0; elIdx < elements.length; elIdx++) {
2690
+ const element = elements[elIdx];
2691
+ let totalElementHeight = 0;
2692
+ const parts = element.parts;
2693
+ for (let pIdx = 0; pIdx < parts.length; pIdx++) {
2694
+ const part = parts[pIdx];
2695
+ part.dimensions.width = subColumnWidth;
2696
+ part.dimensions.left = currentSubColumnLeft;
2697
+ part.dimensions.top = subColumn.dimensions.top + totalSubColumnHeight + totalElementHeight;
2698
+ totalElementHeight += part.dimensions.height;
2699
+ }
2700
+ element.containerDimensions.height = totalElementHeight;
2701
+ element.containerDimensions.width = subColumnWidth;
2702
+ element.containerDimensions.left = currentSubColumnLeft;
2703
+ element.containerDimensions.top = subColumn.dimensions.top + totalSubColumnHeight;
2704
+ element.dimensions.width = subColumnWidth;
2705
+ element.dimensions.left = currentSubColumnLeft;
2706
+ element.dimensions.top =
2707
+ element.containerDimensions.top + (totalElementHeight - element.dimensions.height) * 0.5;
2708
+ totalSubColumnHeight += totalElementHeight;
2709
+ }
2710
+ subColumn.dimensions.height = totalSubColumnHeight;
2711
+ if (totalSubColumnHeight > maxSectionHeight)
2712
+ maxSectionHeight = totalSubColumnHeight;
2713
+ currentSubColumnLeft += subColumnWidth;
2714
+ }
2715
+ section.dimensions.height = maxSectionHeight + padding.bottom;
2716
+ runningTop += maxSectionHeight + padding.bottom;
2670
2717
  }
2718
+ masterColumn.dimensions.height = runningTop;
2719
+ if (masterColumn.dimensions.height > maxGridHeight)
2720
+ maxGridHeight = masterColumn.dimensions.height;
2721
+ currentMasterColumnLeft += masterColumn.dimensions.width;
2671
2722
  }
2672
- pushSection(upperSection, upperLowerGapSection, lowerSection);
2673
- grid.pushMasterColumn(masterColumn);
2674
- if (remainingRounds.length) {
2675
- grid.pushMasterColumn(createBracketGapMasterColumn({
2676
- existingMasterColumns: grid.grid.masterColumns,
2677
- columnGap: options.columnGap,
2678
- }));
2723
+ newGrid.dimensions.width = currentMasterColumnLeft;
2724
+ newGrid.dimensions.height = maxGridHeight;
2725
+ calculateSpanningElementDimensions();
2726
+ };
2727
+ const calculateSpanningElementDimensions = () => {
2728
+ const spanDimensions = new Map();
2729
+ const masterCols = newGrid.masterColumns;
2730
+ for (let mcIdx = 0; mcIdx < masterCols.length; mcIdx++) {
2731
+ const masterColumn = masterCols[mcIdx];
2732
+ const sections = masterColumn.sections;
2733
+ for (let secIdx = 0; secIdx < sections.length; secIdx++) {
2734
+ const section = sections[secIdx];
2735
+ const subColumns = section.subColumns;
2736
+ for (let scIdx = 0; scIdx < subColumns.length; scIdx++) {
2737
+ const subColumn = subColumns[scIdx];
2738
+ const elements = subColumn.elements;
2739
+ for (let elIdx = 0; elIdx < elements.length; elIdx++) {
2740
+ const element = elements[elIdx];
2741
+ if (!element.span)
2742
+ continue;
2743
+ const span = element.span;
2744
+ const isStartPosition = mcIdx === span.masterColumnStart && secIdx === span.sectionStart && scIdx === span.subColumnStart;
2745
+ const spanKey = `${span.masterColumnStart}-${span.masterColumnEnd}-${span.sectionStart}-${span.sectionEnd}-${span.subColumnStart}-${span.subColumnEnd}`;
2746
+ if (isStartPosition && !spanDimensions.has(spanKey)) {
2747
+ const totalSpannedWidth = calculateSpannedWidth(span, masterCols);
2748
+ const spanStartLeft = calculateSpanStartLeft(span, masterCols);
2749
+ const width = config.spanElementWidth;
2750
+ spanDimensions.set(spanKey, { width, left: spanStartLeft + (totalSpannedWidth - width) * 0.5 });
2751
+ }
2752
+ const storedDimensions = spanDimensions.get(spanKey);
2753
+ if (storedDimensions) {
2754
+ element.dimensions.width = storedDimensions.width;
2755
+ element.dimensions.left = storedDimensions.left;
2756
+ element.isHidden = !isStartPosition;
2757
+ }
2758
+ }
2759
+ }
2760
+ }
2679
2761
  }
2680
- }
2681
- for (const [roundIndex, round] of remainingRounds.entries()) {
2682
- const isLastRound = roundIndex === remainingRounds.length - 1;
2683
- const isFirstRound = roundIndex === 0;
2684
- const isAnyFinal = hasReverseFinal
2685
- ? round.type === DOUBLE_ELIMINATION_BRACKET_ROUND_TYPE.REVERSE_FINAL
2686
- : round.type === COMMON_BRACKET_ROUND_TYPE.FINAL;
2687
- const { masterColumn, pushSection } = createBracketMasterColumn({
2688
- columnWidth: isAnyFinal ? options.finalColumnWidth : options.columnWidth,
2689
- padding: {
2690
- bottom: 0,
2691
- left: 0,
2692
- right: 0,
2693
- top: 0,
2694
- },
2695
- });
2696
- const { masterColumnSection: upperSection, pushSubColumn: pushUpperSubColumn } = createBracketMasterColumnSection({
2697
- type: 'round',
2698
- });
2699
- const { masterColumnSection: upperLowerGapSection, pushSubColumn: pushUpperLowerSubColumn } = createBracketMasterColumnSection({
2700
- type: 'gap',
2701
- });
2702
- const { masterColumnSection: lowerSection, pushSubColumn: pushLowerSubColumn } = createBracketMasterColumnSection({
2703
- type: 'round',
2704
- });
2705
- const upperSubColumn = createRoundBracketSubColumnRelativeToFirstRound({
2706
- firstRound: firstUpperRound,
2707
- round,
2708
- options,
2709
- hasReverseFinal,
2710
- span: {
2711
- isStart: true,
2712
- isEnd: true,
2713
- },
2714
- components,
2715
- });
2716
- pushUpperSubColumn(upperSubColumn);
2717
- const upperLowerGapSubColumn = createBracketSubColumn({
2718
- span: {
2719
- isStart: true,
2720
- isEnd: true,
2721
- },
2722
- });
2723
- const upperLowerGapElement = createBracketElement({
2724
- area: '.',
2725
- type: 'roundGap',
2726
- elementHeight: options.rowRoundGap,
2727
- partHeights: [options.rowRoundGap],
2728
- });
2729
- upperLowerGapSubColumn.pushElement(upperLowerGapElement.element);
2730
- pushUpperLowerSubColumn(upperLowerGapSubColumn.subColumn);
2731
- if (thirdPlaceRound) {
2732
- const lowerSubColumn = createRoundBracketSubColumnRelativeToFirstRound({
2733
- firstRound: firstLowerRound,
2734
- round: thirdPlaceRound,
2735
- options,
2736
- hasReverseFinal,
2737
- span: {
2738
- isStart: isFirstRound,
2739
- isEnd: isLastRound,
2740
- },
2741
- components,
2742
- });
2743
- pushLowerSubColumn(lowerSubColumn);
2762
+ };
2763
+ const calculateSpannedWidth = (span, masterColumns) => {
2764
+ const key = `${span.masterColumnStart}-${span.masterColumnEnd}-${span.sectionStart}-${span.sectionEnd}-${span.subColumnStart}-${span.subColumnEnd}`;
2765
+ if (spannedWidthCache.has(key))
2766
+ return spannedWidthCache.get(key);
2767
+ if (span.masterColumnStart === span.masterColumnEnd) {
2768
+ const masterColumn = masterColumns[span.masterColumnStart];
2769
+ if (masterColumn) {
2770
+ const section = masterColumn.sections[span.sectionStart];
2771
+ if (section) {
2772
+ const subColumnWidth = section.dimensions.width / section.subColumns.length;
2773
+ const totalWidth = subColumnWidth * (span.subColumnEnd - span.subColumnStart + 1);
2774
+ spannedWidthCache.set(key, totalWidth);
2775
+ return totalWidth;
2776
+ }
2777
+ }
2778
+ spannedWidthCache.set(key, 0);
2779
+ return 0;
2744
2780
  }
2745
- else {
2746
- const lowerSubColumn = createBracketSubColumn({
2747
- span: {
2748
- isStart: true,
2749
- isEnd: true,
2750
- },
2751
- });
2752
- const firstMasterRound = grid.grid.masterColumns[0];
2753
- if (!firstMasterRound)
2754
- throw new Error('No first master round found in grid');
2755
- const lastMasterColumnSection = firstMasterRound.sections[firstMasterRound.sections.length - 1];
2756
- if (!lastMasterColumnSection)
2757
- throw new Error('No last master column section found in grid');
2758
- const firstSubColumn = lastMasterColumnSection.subColumns[0];
2759
- if (!firstSubColumn)
2760
- throw new Error('No first sub column found in grid');
2761
- for (const element of firstSubColumn.elements) {
2762
- const el = createBracketElement({
2763
- area: '.',
2764
- type: 'colGap',
2765
- elementHeight: element.dimensions.height,
2766
- partHeights: element.parts.map((p) => p.dimensions.height),
2767
- });
2768
- lowerSubColumn.pushElement(el.element);
2781
+ let totalWidth = 0;
2782
+ for (let mcIdx = span.masterColumnStart; mcIdx <= span.masterColumnEnd; mcIdx++) {
2783
+ const masterColumn = masterColumns[mcIdx];
2784
+ if (!masterColumn)
2785
+ continue;
2786
+ if (mcIdx === span.masterColumnStart) {
2787
+ const section = masterColumn.sections[span.sectionStart];
2788
+ if (section) {
2789
+ const subColumnWidth = section.dimensions.width / section.subColumns.length;
2790
+ totalWidth += subColumnWidth * (section.subColumns.length - span.subColumnStart);
2791
+ }
2792
+ }
2793
+ else if (mcIdx === span.masterColumnEnd) {
2794
+ const section = masterColumn.sections[span.sectionEnd];
2795
+ if (section) {
2796
+ const subColumnWidth = section.dimensions.width / section.subColumns.length;
2797
+ totalWidth += subColumnWidth * (span.subColumnEnd + 1);
2798
+ }
2799
+ }
2800
+ else {
2801
+ totalWidth += masterColumn.dimensions.width;
2769
2802
  }
2770
- pushLowerSubColumn(lowerSubColumn.subColumn);
2771
2803
  }
2772
- pushSection(upperSection, upperLowerGapSection, lowerSection);
2773
- grid.pushMasterColumn(masterColumn);
2774
- if (!isLastRound) {
2775
- grid.pushMasterColumn(createBracketGapMasterColumn({
2776
- existingMasterColumns: grid.grid.masterColumns,
2777
- columnGap: options.columnGap,
2778
- }));
2804
+ spannedWidthCache.set(key, totalWidth);
2805
+ return totalWidth;
2806
+ };
2807
+ const calculateSpanStartLeft = (span, masterColumns) => {
2808
+ const key = `${span.masterColumnStart}-${span.sectionStart}-${span.subColumnStart}`;
2809
+ if (spanStartLeftCache.has(key))
2810
+ return spanStartLeftCache.get(key);
2811
+ const startMasterColumn = masterColumns[span.masterColumnStart];
2812
+ if (!startMasterColumn) {
2813
+ spanStartLeftCache.set(key, 0);
2814
+ return 0;
2779
2815
  }
2780
- }
2781
- grid.setupElementSpans();
2782
- grid.calculateDimensions();
2783
- const finalizedGrid = finalizeBracketGrid(grid);
2784
- return {
2785
- raw: grid,
2786
- columns: finalizedGrid.columns,
2787
- matchElementMap: finalizedGrid.elementMap,
2816
+ let startLeft = startMasterColumn.dimensions.left;
2817
+ const section = startMasterColumn.sections[span.sectionStart];
2818
+ if (section) {
2819
+ startLeft += (section.dimensions.width / section.subColumns.length) * span.subColumnStart;
2820
+ }
2821
+ spanStartLeftCache.set(key, startLeft);
2822
+ return startLeft;
2788
2823
  };
2789
- };
2790
-
2791
- const createSingleEliminationGrid = (bracketData, options, components) => {
2792
- const grid = createBracketGrid({ spanElementWidth: options.columnWidth });
2793
- const rounds = Array.from(bracketData.rounds.values());
2794
- const firstRound = bracketData.rounds.first();
2795
- if (!firstRound) {
2796
- throw new Error('No rounds found in bracket data');
2797
- }
2798
- for (const [roundIndex, round] of rounds.entries()) {
2799
- const isLastRound = roundIndex === rounds.length - 1;
2800
- const { masterColumn, ...mutableMasterColumn } = createBracketMasterColumn({
2801
- columnWidth: round.type === COMMON_BRACKET_ROUND_TYPE.FINAL ? options.finalColumnWidth : options.columnWidth,
2802
- padding: {
2803
- bottom: 0,
2804
- left: 0,
2805
- right: 0,
2806
- top: 0,
2807
- },
2808
- });
2809
- const { masterColumnSection, pushSubColumn } = createBracketMasterColumnSection({
2810
- type: 'round',
2811
- });
2812
- const sub = createRoundBracketSubColumnRelativeToFirstRound({
2813
- firstRound,
2814
- round,
2815
- options,
2816
- hasReverseFinal: false,
2817
- span: {
2818
- isStart: true,
2819
- isEnd: true,
2820
- },
2821
- components,
2822
- });
2823
- pushSubColumn(sub);
2824
- mutableMasterColumn.pushSection(masterColumnSection);
2825
- grid.pushMasterColumn(masterColumn);
2826
- if (!isLastRound) {
2827
- grid.pushMasterColumn(createBracketGapMasterColumn({
2828
- existingMasterColumns: grid.grid.masterColumns,
2829
- columnGap: options.columnGap,
2830
- }));
2824
+ const setupElementSpans = () => {
2825
+ const masterCols = newGrid.masterColumns;
2826
+ for (let mcIdx = 0; mcIdx < masterCols.length; mcIdx++) {
2827
+ const masterColumn = masterCols[mcIdx];
2828
+ const sections = masterColumn.sections;
2829
+ for (let secIdx = 0; secIdx < sections.length; secIdx++) {
2830
+ const section = sections[secIdx];
2831
+ const subColumns = section.subColumns;
2832
+ for (let scIdx = 0; scIdx < subColumns.length; scIdx++) {
2833
+ const subColumn = subColumns[scIdx];
2834
+ if (subColumn.span.isStart && subColumn.span.isEnd)
2835
+ continue;
2836
+ let spanStart = { masterColumnIndex: mcIdx, sectionIndex: secIdx, subColumnIndex: scIdx };
2837
+ if (!subColumn.span.isStart) {
2838
+ outer: for (let m = mcIdx; m >= 0; m--) {
2839
+ const mc = masterCols[m];
2840
+ if (!mc)
2841
+ continue;
2842
+ const sec = mc.sections[secIdx];
2843
+ if (!sec)
2844
+ continue;
2845
+ const end = m === mcIdx ? scIdx : sec.subColumns.length - 1;
2846
+ for (let s = end; s >= 0; s--) {
2847
+ if (sec.subColumns[s]?.span.isStart) {
2848
+ spanStart = { masterColumnIndex: m, sectionIndex: secIdx, subColumnIndex: s };
2849
+ break outer;
2850
+ }
2851
+ }
2852
+ }
2853
+ }
2854
+ let spanEnd = { masterColumnIndex: mcIdx, sectionIndex: secIdx, subColumnIndex: scIdx };
2855
+ if (!subColumn.span.isEnd) {
2856
+ outer: for (let m = mcIdx; m < masterCols.length; m++) {
2857
+ const mc = masterCols[m];
2858
+ if (!mc)
2859
+ continue;
2860
+ const sec = mc.sections[secIdx];
2861
+ if (!sec)
2862
+ continue;
2863
+ const start = m === mcIdx ? scIdx : 0;
2864
+ for (let s = start; s < sec.subColumns.length; s++) {
2865
+ if (sec.subColumns[s]?.span.isEnd) {
2866
+ spanEnd = { masterColumnIndex: m, sectionIndex: secIdx, subColumnIndex: s };
2867
+ break outer;
2868
+ }
2869
+ }
2870
+ }
2871
+ }
2872
+ const elements = subColumn.elements;
2873
+ for (let elIdx = 0; elIdx < elements.length; elIdx++) {
2874
+ elements[elIdx].span = {
2875
+ masterColumnStart: spanStart.masterColumnIndex,
2876
+ masterColumnEnd: spanEnd.masterColumnIndex,
2877
+ sectionStart: spanStart.sectionIndex,
2878
+ sectionEnd: spanEnd.sectionIndex,
2879
+ subColumnStart: spanStart.subColumnIndex,
2880
+ subColumnEnd: spanEnd.subColumnIndex,
2881
+ };
2882
+ }
2883
+ }
2884
+ }
2831
2885
  }
2832
- }
2833
- grid.calculateDimensions();
2834
- const finalizedGrid = finalizeBracketGrid(grid);
2886
+ };
2835
2887
  return {
2836
- raw: grid,
2837
- columns: finalizedGrid.columns,
2838
- matchElementMap: finalizedGrid.elementMap,
2888
+ grid: newGrid,
2889
+ pushMasterColumn,
2890
+ calculateDimensions,
2891
+ setupElementSpans,
2839
2892
  };
2840
2893
  };
2841
2894
 
2842
- const FALLBACK_MATCH_RELATION_POSITION = -1;
2843
- const generateMatchPosition = (match, factor) => Math.ceil(match.position * factor);
2844
- const generateMatchRelationPositions = (relation, match) => {
2845
- switch (relation.type) {
2846
- case 'nothing-to-one':
2847
- return {
2848
- nextRoundMatchPosition: generateMatchPosition(match, relation.nextRoundMatchFactor),
2849
- previousUpperRoundMatchPosition: FALLBACK_MATCH_RELATION_POSITION,
2850
- previousLowerRoundMatchPosition: FALLBACK_MATCH_RELATION_POSITION,
2851
- };
2852
- case 'one-to-nothing': {
2853
- const double = relation.previousRoundMatchFactor === 2 ? 1 : 0;
2854
- return {
2855
- nextRoundMatchPosition: FALLBACK_MATCH_RELATION_POSITION,
2856
- previousUpperRoundMatchPosition: (generateMatchPosition(match, relation.previousRoundMatchFactor) -
2857
- double),
2858
- previousLowerRoundMatchPosition: generateMatchPosition(match, relation.previousRoundMatchFactor),
2859
- };
2860
- }
2861
- case 'one-to-one': {
2862
- const double = relation.previousRoundMatchFactor === 2 ? 1 : 0;
2863
- return {
2864
- nextRoundMatchPosition: generateMatchPosition(match, relation.nextRoundMatchFactor),
2865
- previousUpperRoundMatchPosition: (generateMatchPosition(match, relation.previousRoundMatchFactor) -
2866
- double),
2867
- previousLowerRoundMatchPosition: generateMatchPosition(match, relation.previousRoundMatchFactor),
2868
- };
2869
- }
2870
- case 'two-to-one':
2871
- return {
2872
- nextRoundMatchPosition: generateMatchPosition(match, relation.nextRoundMatchFactor),
2873
- previousUpperRoundMatchPosition: generateMatchPosition(match, relation.previousUpperRoundMatchFactor),
2874
- previousLowerRoundMatchPosition: generateMatchPosition(match, relation.previousLowerRoundMatchFactor),
2875
- };
2876
- case 'two-to-nothing':
2877
- return {
2878
- nextRoundMatchPosition: FALLBACK_MATCH_RELATION_POSITION,
2879
- previousUpperRoundMatchPosition: generateMatchPosition(match, relation.previousUpperRoundMatchFactor),
2880
- previousLowerRoundMatchPosition: generateMatchPosition(match, relation.previousLowerRoundMatchFactor),
2881
- };
2882
- }
2895
+ const createBracketMasterColumn = (config) => {
2896
+ const { columnWidth, padding } = config;
2897
+ const sections = [];
2898
+ const newMasterColumn = {
2899
+ dimensions: {
2900
+ width: columnWidth,
2901
+ height: 0,
2902
+ top: 0,
2903
+ left: 0,
2904
+ },
2905
+ padding,
2906
+ sections,
2907
+ };
2908
+ const pushSection = (...newSections) => {
2909
+ sections.push(...newSections);
2910
+ };
2911
+ return {
2912
+ masterColumn: newMasterColumn,
2913
+ pushSection,
2914
+ };
2883
2915
  };
2884
- const createNothingToOneRelation$1 = (params) => {
2885
- const { match, relation, matchPositionMaps, nextRoundMatchPosition } = params;
2886
- const nextMatch = matchPositionMaps.get(relation.nextRound.id)?.get(nextRoundMatchPosition);
2887
- if (!nextMatch)
2888
- throw new Error('Next round match not found');
2916
+
2917
+ const createBracketMasterColumnSection = (config) => {
2918
+ const { type } = config;
2919
+ const subColumns = [];
2920
+ const newMasterColumnSection = {
2921
+ dimensions: {
2922
+ width: 0,
2923
+ height: 0,
2924
+ top: 0,
2925
+ left: 0,
2926
+ },
2927
+ subColumns,
2928
+ type,
2929
+ };
2930
+ const pushSubColumn = (...newSubColumns) => {
2931
+ subColumns.push(...newSubColumns);
2932
+ };
2889
2933
  return {
2890
- type: 'nothing-to-one',
2891
- currentMatch: match,
2892
- currentRound: relation.currentRound,
2893
- nextMatch,
2894
- nextRound: relation.nextRound,
2934
+ masterColumnSection: newMasterColumnSection,
2935
+ pushSubColumn,
2895
2936
  };
2896
2937
  };
2897
- const createOneToNothingOrTwoToNothingRelation = (params) => {
2898
- const { match, relation, matchPositionMaps, previousUpperRoundMatchPosition, previousLowerRoundMatchPosition } = params;
2899
- const previousUpperMatch = matchPositionMaps.get(relation.previousRound.id)?.get(previousUpperRoundMatchPosition);
2900
- const previousLowerMatch = matchPositionMaps.get(relation.previousRound.id)?.get(previousLowerRoundMatchPosition);
2901
- if (!previousUpperMatch)
2902
- throw new Error('Previous round match not found');
2903
- if (previousUpperRoundMatchPosition !== previousLowerRoundMatchPosition) {
2904
- if (!previousLowerMatch)
2905
- throw new Error('Previous lower round match not found');
2906
- return {
2907
- type: 'two-to-nothing',
2908
- currentMatch: match,
2909
- currentRound: relation.currentRound,
2910
- previousUpperMatch,
2911
- previousUpperRound: relation.previousRound,
2912
- previousLowerMatch,
2913
- previousLowerRound: relation.previousRound,
2914
- };
2938
+
2939
+ const createBracketSubColumn = (config) => {
2940
+ const { span } = config;
2941
+ const elements = [];
2942
+ const newSubColumn = {
2943
+ dimensions: {
2944
+ width: 0,
2945
+ height: 0,
2946
+ top: 0,
2947
+ left: 0,
2948
+ },
2949
+ elements,
2950
+ span,
2951
+ };
2952
+ const pushElement = (...newElements) => {
2953
+ elements.push(...newElements);
2954
+ };
2955
+ return {
2956
+ subColumn: newSubColumn,
2957
+ pushElement,
2958
+ };
2959
+ };
2960
+
2961
+ function logGridAreasFormatted(gridTemplateAreas) {
2962
+ if (!gridTemplateAreas) {
2963
+ console.log('No grid areas to display');
2964
+ return;
2915
2965
  }
2916
- else {
2917
- return {
2918
- type: 'one-to-nothing',
2919
- currentMatch: match,
2920
- currentRound: relation.currentRound,
2921
- previousMatch: previousUpperMatch,
2922
- previousRound: relation.previousRound,
2923
- };
2966
+ // Parse the grid template areas string
2967
+ const rows = gridTemplateAreas
2968
+ .split('\n')
2969
+ .map((row) => row.replace(/^"/, '').replace(/"$/, '')) // Remove quotes
2970
+ .map((row) => row.split(' ')); // Split into individual areas
2971
+ if (rows.length === 0) {
2972
+ console.log('No grid areas to display');
2973
+ return;
2924
2974
  }
2925
- };
2926
- const createOneToOneOrTwoToOneRelation = (params) => {
2927
- const { match, relation, matchPositionMaps, nextRoundMatchPosition, previousUpperRoundMatchPosition, previousLowerRoundMatchPosition, } = params;
2928
- const nextMatch = matchPositionMaps.get(relation.nextRound.id)?.get(nextRoundMatchPosition);
2929
- const previousUpperMatch = matchPositionMaps.get(relation.previousRound.id)?.get(previousUpperRoundMatchPosition);
2930
- const previousLowerMatch = matchPositionMaps.get(relation.previousRound.id)?.get(previousLowerRoundMatchPosition);
2931
- if (!nextMatch)
2932
- throw new Error('Next round match not found');
2933
- if (!previousUpperMatch)
2934
- throw new Error('Previous upper round match not found');
2935
- if (!previousLowerMatch)
2936
- throw new Error('Previous lower round match not found');
2937
- if (previousUpperRoundMatchPosition === previousLowerRoundMatchPosition) {
2938
- return {
2939
- type: 'one-to-one',
2940
- currentMatch: match,
2941
- currentRound: relation.currentRound,
2942
- previousMatch: previousUpperMatch,
2943
- previousRound: relation.previousRound,
2944
- nextMatch,
2945
- nextRound: relation.nextRound,
2946
- };
2975
+ // Find the maximum width for each column to align properly
2976
+ const columnCount = rows[0]?.length ?? 0;
2977
+ const columnWidths = [];
2978
+ for (let colIndex = 0; colIndex < columnCount; colIndex++) {
2979
+ const maxWidth = Math.max(...rows.map((row) => (row[colIndex] || '').length));
2980
+ columnWidths.push(Math.max(maxWidth, 3)); // Minimum width of 3
2947
2981
  }
2948
- else {
2982
+ console.log('\n📋 Grid Template Areas (Formatted):');
2983
+ console.log('═'.repeat(columnWidths.reduce((sum, width) => sum + width + 3, 0)));
2984
+ // Log each row with proper spacing
2985
+ rows.forEach((row, rowIndex) => {
2986
+ const formattedRow = row
2987
+ .map((area, colIndex) => {
2988
+ const width = columnWidths[colIndex] ?? 0;
2989
+ return area.padEnd(width);
2990
+ })
2991
+ .join(' | ');
2992
+ console.log(`${String(rowIndex).padStart(2, '0')}: ${formattedRow}`);
2993
+ });
2994
+ console.log('═'.repeat(columnWidths.reduce((sum, width) => sum + width + 3, 0)));
2995
+ console.log(`📊 Grid dimensions: ${rows.length} rows × ${columnCount} columns\n`);
2996
+ }
2997
+ function gridColumnsToGridProperty(grid) {
2998
+ if (!grid.length) {
2949
2999
  return {
2950
- type: 'two-to-one',
2951
- currentMatch: match,
2952
- currentRound: relation.currentRound,
2953
- previousUpperMatch,
2954
- previousUpperRound: relation.previousRound,
2955
- previousLowerMatch,
2956
- previousLowerRound: relation.previousRound,
2957
- nextMatch,
2958
- nextRound: relation.nextRound,
3000
+ gridTemplateAreas: '',
3001
+ gridTemplateRows: '',
3002
+ gridTemplateColumns: '',
2959
3003
  };
2960
3004
  }
2961
- };
2962
- const createTwoToOneRelation$1 = (params) => {
2963
- const { match, relation, matchPositionMaps, nextRoundMatchPosition, previousUpperRoundMatchPosition, previousLowerRoundMatchPosition, } = params;
2964
- const nextMatch = matchPositionMaps.get(relation.nextRound.id)?.get(nextRoundMatchPosition);
2965
- const previousUpperMatch = matchPositionMaps
2966
- .get(relation.previousUpperRound.id)
2967
- ?.get(previousUpperRoundMatchPosition);
2968
- const previousLowerMatch = matchPositionMaps
2969
- .get(relation.previousLowerRound.id)
2970
- ?.get(previousLowerRoundMatchPosition);
2971
- if (!nextMatch)
2972
- throw new Error('Next round match not found');
2973
- if (!previousUpperMatch)
2974
- throw new Error('Previous upper round match not found');
2975
- if (!previousLowerMatch)
2976
- throw new Error('Previous lower round match not found');
2977
- return {
2978
- type: 'two-to-one',
2979
- currentMatch: match,
2980
- currentRound: relation.currentRound,
2981
- previousUpperMatch,
2982
- previousUpperRound: relation.previousUpperRound,
2983
- previousLowerMatch,
2984
- previousLowerRound: relation.previousLowerRound,
2985
- nextMatch,
2986
- nextRound: relation.nextRound,
2987
- };
2988
- };
2989
- const createTwoToNothingRelation$1 = (params) => {
2990
- const { match, relation, matchPositionMaps, previousUpperRoundMatchPosition, previousLowerRoundMatchPosition } = params;
2991
- const previousUpperMatch = matchPositionMaps
2992
- .get(relation.previousUpperRound.id)
2993
- ?.get(previousUpperRoundMatchPosition);
2994
- const previousLowerMatch = matchPositionMaps
2995
- .get(relation.previousUpperRound.id)
2996
- ?.get(previousLowerRoundMatchPosition);
2997
- if (!previousUpperMatch)
2998
- throw new Error('Previous upper round match not found');
2999
- if (!previousLowerMatch)
3000
- throw new Error('Previous lower round match not found');
3001
- return {
3002
- type: 'two-to-nothing',
3003
- currentMatch: match,
3004
- currentRound: relation.currentRound,
3005
- previousUpperMatch,
3006
- previousUpperRound: relation.previousUpperRound,
3007
- previousLowerMatch,
3008
- previousLowerRound: relation.previousUpperRound,
3005
+ // Calculate the total number of columns (subcolumns across all master columns)
3006
+ const totalColumns = grid.reduce((total, masterColumn) => {
3007
+ return total + Math.max(...masterColumn.sections.map((section) => section.subColumns.length));
3008
+ }, 0);
3009
+ // Find all unique sections across all master columns to determine rows
3010
+ const allSections = [];
3011
+ grid.forEach((masterColumn, masterColumnIndex) => {
3012
+ masterColumn.sections.forEach((section, sectionIndex) => {
3013
+ // For each section, we need to process all its subcolumns
3014
+ section.subColumns.forEach((subColumn) => {
3015
+ allSections.push({
3016
+ elements: subColumn.elements,
3017
+ sectionIndex,
3018
+ masterColumnIndex,
3019
+ });
3020
+ });
3021
+ });
3022
+ });
3023
+ // Group sections by their index to create rows
3024
+ const sectionsByIndex = new Map();
3025
+ let globalColumnIndex = 0;
3026
+ grid.forEach((masterColumn, masterColumnIndex) => {
3027
+ const maxSubColumns = Math.max(...masterColumn.sections.map((section) => section.subColumns.length));
3028
+ masterColumn.sections.forEach((section, sectionIndex) => {
3029
+ if (!sectionsByIndex.has(sectionIndex)) {
3030
+ sectionsByIndex.set(sectionIndex, []);
3031
+ }
3032
+ section.subColumns.forEach((subColumn, subColumnIndex) => {
3033
+ sectionsByIndex.get(sectionIndex).push({
3034
+ elements: subColumn.elements,
3035
+ columnIndex: globalColumnIndex + subColumnIndex,
3036
+ masterColumnIndex,
3037
+ });
3038
+ });
3039
+ });
3040
+ globalColumnIndex += maxSubColumns;
3041
+ });
3042
+ // Build the grid matrix - sections become rows, subcolumns become columns
3043
+ const areaMatrix = [];
3044
+ const rowHeights = [];
3045
+ // Process each section (which becomes a row group)
3046
+ for (const [sectionIndex, sectionColumns] of sectionsByIndex.entries()) {
3047
+ // Find max rows needed for this section
3048
+ const maxRowsInSection = Math.max(...sectionColumns.map((col) => col.elements.reduce((total, element) => total + element.parts.length, 0)), 0);
3049
+ // Create rows for this section
3050
+ for (let rowIndex = 0; rowIndex < maxRowsInSection; rowIndex++) {
3051
+ const areaRow = new Array(totalColumns).fill('.');
3052
+ let currentRowHeight;
3053
+ // Fill in areas for each column in this section
3054
+ for (const sectionColumn of sectionColumns) {
3055
+ let accumulatedRows = 0;
3056
+ let area = '.';
3057
+ let height;
3058
+ // Find which element this row belongs to
3059
+ for (const element of sectionColumn.elements) {
3060
+ const elementRowCount = element.parts.length;
3061
+ if (rowIndex >= accumulatedRows && rowIndex < accumulatedRows + elementRowCount) {
3062
+ const relativeRowIndex = rowIndex - accumulatedRows;
3063
+ area = element.area;
3064
+ height = element.parts[relativeRowIndex]?.dimensions.height;
3065
+ break;
3066
+ }
3067
+ accumulatedRows += elementRowCount;
3068
+ }
3069
+ areaRow[sectionColumn.columnIndex] = area;
3070
+ // Use the first defined height for this row
3071
+ if (!currentRowHeight && height !== undefined) {
3072
+ currentRowHeight = `${height}px`;
3073
+ }
3074
+ }
3075
+ areaMatrix.push(areaRow);
3076
+ rowHeights.push(currentRowHeight ?? 'auto');
3077
+ }
3078
+ }
3079
+ // Calculate column widths
3080
+ const columnWidths = [];
3081
+ for (const masterColumn of grid) {
3082
+ const maxSubColumns = Math.max(...masterColumn.sections.map((section) => section.subColumns.length));
3083
+ for (let i = 0; i < maxSubColumns; i++) {
3084
+ // Find the width from any section that has this subcolumn
3085
+ const subColumnWidth = masterColumn.sections.find((section) => section.subColumns[i])?.subColumns[i]?.dimensions.width ?? 0;
3086
+ columnWidths.push(`${subColumnWidth}px`);
3087
+ }
3088
+ }
3089
+ // Build the CSS properties
3090
+ const gridTemplateAreas = areaMatrix.map((row) => `"${row.join(' ')}"`).join('\n');
3091
+ const gridTemplateRows = rowHeights.join(' ');
3092
+ const gridTemplateColumns = columnWidths.join(' ');
3093
+ const result = {
3094
+ gridTemplateAreas,
3095
+ gridTemplateRows,
3096
+ gridTemplateColumns,
3009
3097
  };
3098
+ // logGridAreasFormatted(gridTemplateAreas);
3099
+ return result;
3100
+ }
3101
+
3102
+ const calculateUpperLowerRatio = (upperRoundsCount, lowerRoundsCount) => {
3103
+ // In double elimination, after the first aligned column,
3104
+ // we need to distribute the remaining upper rounds across the remaining lower rounds
3105
+ const remainingUpperRounds = upperRoundsCount - 1; // Subtract the first round
3106
+ const remainingLowerRounds = lowerRoundsCount - 1; // Subtract the first round
3107
+ if (remainingUpperRounds === 0) {
3108
+ return 1; // Edge case: only one upper round
3109
+ }
3110
+ return remainingLowerRounds / remainingUpperRounds;
3010
3111
  };
3011
- const generateMatchRelationsNew = (bracketData) => {
3012
- const matchRelations = [];
3013
- const matchPositionMaps = new Map();
3014
- for (const round of bracketData.rounds.values()) {
3015
- const matchMap = new Map([...round.matches.values()].map((m) => [m.position, m]));
3016
- matchPositionMaps.set(round.id, matchMap);
3112
+ const calculateColumnSplitFactor = (upperToLowerRatio) => {
3113
+ // The split factor determines how many sub-columns each match column will have
3114
+ // For example, if the ratio is 1.5, we split each match column
3115
+ // into 2 sub-columns so that matches can be centered correctly
3116
+ if (upperToLowerRatio === 1.5) {
3117
+ return 2; // Split into 2 sub-columns for 1.5 ratio
3017
3118
  }
3018
- for (const match of bracketData.matches.values()) {
3019
- const relation = match.round.relation;
3020
- const { nextRoundMatchPosition, previousUpperRoundMatchPosition, previousLowerRoundMatchPosition } = generateMatchRelationPositions(relation, match);
3021
- switch (relation.type) {
3022
- case 'nothing-to-one':
3023
- matchRelations.push(createNothingToOneRelation$1({
3024
- match,
3025
- relation,
3026
- matchPositionMaps,
3027
- nextRoundMatchPosition,
3028
- }));
3029
- break;
3030
- case 'one-to-nothing':
3031
- matchRelations.push(createOneToNothingOrTwoToNothingRelation({
3032
- match,
3033
- relation,
3034
- matchPositionMaps,
3035
- previousUpperRoundMatchPosition,
3036
- previousLowerRoundMatchPosition,
3037
- }));
3038
- break;
3039
- case 'one-to-one':
3040
- matchRelations.push(createOneToOneOrTwoToOneRelation({
3041
- match,
3042
- relation,
3043
- matchPositionMaps,
3044
- nextRoundMatchPosition,
3045
- previousUpperRoundMatchPosition,
3046
- previousLowerRoundMatchPosition,
3047
- }));
3048
- break;
3049
- case 'two-to-one':
3050
- matchRelations.push(createTwoToOneRelation$1({
3051
- match,
3052
- relation,
3053
- matchPositionMaps,
3054
- nextRoundMatchPosition,
3055
- previousUpperRoundMatchPosition,
3056
- previousLowerRoundMatchPosition,
3057
- }));
3058
- break;
3059
- case 'two-to-nothing':
3060
- matchRelations.push(createTwoToNothingRelation$1({
3061
- match,
3062
- relation,
3063
- matchPositionMaps,
3064
- previousUpperRoundMatchPosition,
3065
- previousLowerRoundMatchPosition,
3066
- }));
3067
- break;
3068
- }
3119
+ if (upperToLowerRatio === 2) {
3120
+ return 1; // Split into 1 sub-columns for 2 ratio
3069
3121
  }
3070
- return matchRelations;
3122
+ return 1;
3071
3123
  };
3072
-
3073
- const calculateMatchFactor = (numeratorRound, denominatorRound) => numeratorRound.matchCount / denominatorRound.matchCount;
3074
- const createNothingToOneRelation = (params) => ({
3075
- type: 'nothing-to-one',
3076
- currentRound: params.currentRound,
3077
- nextRound: params.nextRound,
3078
- nextRoundMatchFactor: calculateMatchFactor(params.nextRound, params.currentRound),
3079
- });
3080
- const createOneToNothingRelation = (params) => ({
3081
- type: 'one-to-nothing',
3082
- currentRound: params.currentRound,
3083
- previousRound: params.previousRound,
3084
- previousRoundMatchFactor: calculateMatchFactor(params.previousRound, params.currentRound),
3085
- rootRoundMatchFactor: calculateMatchFactor(params.rootRound, params.currentRound),
3086
- });
3087
- const createOneToOneRelation = (params) => ({
3088
- type: 'one-to-one',
3089
- currentRound: params.currentRound,
3090
- previousRound: params.previousRound,
3091
- nextRound: params.nextRound,
3092
- nextRoundMatchFactor: calculateMatchFactor(params.nextRound, params.currentRound),
3093
- previousRoundMatchFactor: calculateMatchFactor(params.previousRound, params.currentRound),
3094
- rootRoundMatchFactor: calculateMatchFactor(params.rootRound, params.currentRound),
3095
- });
3096
- const createTwoToOneRelation = (params) => ({
3097
- type: 'two-to-one',
3098
- currentRound: params.currentRound,
3099
- previousUpperRound: params.previousUpperRound,
3100
- previousLowerRound: params.previousLowerRound,
3101
- nextRound: params.nextRound,
3102
- nextRoundMatchFactor: calculateMatchFactor(params.nextRound, params.currentRound),
3103
- previousUpperRoundMatchFactor: calculateMatchFactor(params.previousUpperRound, params.currentRound),
3104
- previousLowerRoundMatchFactor: calculateMatchFactor(params.previousLowerRound, params.currentRound),
3105
- upperRootRoundMatchFactor: calculateMatchFactor(params.firstUpperRound, params.currentRound),
3106
- lowerRootRoundMatchFactor: calculateMatchFactor(params.firstLowerRound, params.currentRound),
3107
- });
3108
- const createTwoToNothingRelation = (params) => ({
3109
- type: 'two-to-nothing',
3110
- currentRound: params.currentRound,
3111
- previousUpperRound: params.previousUpperRound,
3112
- previousLowerRound: params.previousLowerRound,
3113
- previousUpperRoundMatchFactor: calculateMatchFactor(params.previousUpperRound, params.currentRound),
3114
- previousLowerRoundMatchFactor: calculateMatchFactor(params.previousLowerRound, params.currentRound),
3115
- upperRootRoundMatchFactor: calculateMatchFactor(params.firstUpperRound, params.currentRound),
3116
- lowerRootRoundMatchFactor: calculateMatchFactor(params.firstLowerRound, params.currentRound),
3117
- });
3118
- const getNavigationContext = (params) => {
3119
- const { upperRounds, currentUpperRoundIndex } = params;
3120
- const currentUpperRound = upperRounds[currentUpperRoundIndex];
3121
- if (!currentUpperRound)
3122
- throw new Error('currentUpperRound is null');
3123
- const isLeftToRight = !currentUpperRound.mirrorRoundType || currentUpperRound.mirrorRoundType === BRACKET_ROUND_MIRROR_TYPE.LEFT;
3124
- const relativePrevious = upperRounds[currentUpperRoundIndex - 1] || null;
3125
- const relativeNext = upperRounds[currentUpperRoundIndex + 1] || null;
3126
- const previousUpperRound = isLeftToRight ? relativePrevious : relativeNext;
3127
- const nextUpperRound = isLeftToRight ? relativeNext : relativePrevious;
3128
- const isLastUpperRound = !nextUpperRound ||
3129
- (nextUpperRound.mirrorRoundType === BRACKET_ROUND_MIRROR_TYPE.RIGHT && !currentUpperRound.mirrorRoundType);
3130
- const isFinal = currentUpperRound.type === COMMON_BRACKET_ROUND_TYPE.FINAL;
3131
- return {
3132
- currentUpperRound,
3133
- previousUpperRound,
3134
- nextUpperRound,
3135
- isLastUpperRound,
3136
- isFinal,
3137
- };
3124
+ const calculateLowerRoundIndex = (subColumnIndex, splitFactor) => Math.floor(subColumnIndex / splitFactor);
3125
+ const calculateUpperRoundIndex = (subColumnIndex, upperToLowerRatio, splitFactor) => {
3126
+ // Calculate which complete column we're in
3127
+ const completeColumnIndex = Math.floor(subColumnIndex / splitFactor);
3128
+ // For the first complete column (index 0), always use the first upper round (index 0)
3129
+ if (completeColumnIndex === 0) {
3130
+ return 0;
3131
+ }
3132
+ // For subsequent columns, map based on the ratio
3133
+ // We subtract 1 because the first column is handled separately
3134
+ const adjustedColumnIndex = completeColumnIndex - 1;
3135
+ // Calculate which upper round this column should use
3136
+ // We add 1 because we start from the second upper round (index 1)
3137
+ return Math.floor(adjustedColumnIndex / upperToLowerRatio) + 1;
3138
3138
  };
3139
- const handleFinalRound = (params) => {
3140
- const { relations, currentUpperRound, previousUpperRound, nextUpperRound, lowerRounds, currentUpperRoundIndex, firstUpperRound, firstLowerRound, lastLowerRound, } = params;
3141
- const currentLowerRound = lowerRounds[currentUpperRoundIndex] || null;
3142
- const nextLowerRound = lowerRounds[currentUpperRoundIndex + 1] || null;
3143
- const previousLowerRound = lowerRounds[currentUpperRoundIndex - 1] || null;
3144
- if (!currentLowerRound)
3145
- throw new Error('currentLowerRound is null');
3146
- const isAsyncBracket = currentLowerRound.id !== lastLowerRound.id;
3147
- const finalLowerRound = isAsyncBracket ? nextLowerRound : currentLowerRound;
3148
- if (!finalLowerRound)
3149
- throw new Error('finalLowerRound is null');
3150
- if (finalLowerRound.id !== lastLowerRound.id)
3151
- throw new Error('finalLowerRound is not the last lower round');
3152
- if (nextUpperRound) {
3153
- relations.push(createTwoToOneRelation({
3154
- currentRound: currentUpperRound,
3155
- previousUpperRound,
3156
- previousLowerRound: finalLowerRound,
3157
- nextRound: nextUpperRound,
3158
- firstUpperRound,
3159
- firstLowerRound,
3160
- }));
3139
+
3140
+ const createBracketGapMasterColumn = (config) => {
3141
+ const { existingMasterColumns, columnGap } = config;
3142
+ const lastMasterColumn = existingMasterColumns[existingMasterColumns.length - 1];
3143
+ if (!lastMasterColumn) {
3144
+ throw new Error('No last master column found in existing master columns');
3145
+ }
3146
+ const { masterColumn, pushSection } = createBracketMasterColumn({
3147
+ columnWidth: columnGap,
3148
+ padding: {
3149
+ bottom: 0,
3150
+ left: 0,
3151
+ right: 0,
3152
+ top: 0,
3153
+ },
3154
+ });
3155
+ for (const section of lastMasterColumn.sections) {
3156
+ const { masterColumnSection, pushSubColumn } = createBracketMasterColumnSection({
3157
+ type: 'gap',
3158
+ });
3159
+ const lastSubColumn = section.subColumns[section.subColumns.length - 1];
3160
+ if (!lastSubColumn) {
3161
+ throw new Error('No last sub column found in section');
3162
+ }
3163
+ const { pushElement, subColumn } = createBracketSubColumn({
3164
+ span: {
3165
+ isStart: lastSubColumn.span.isEnd ? true : false,
3166
+ isEnd: lastSubColumn.span.isEnd ? true : false,
3167
+ },
3168
+ });
3169
+ for (const lastSubColumnElement of lastSubColumn.elements) {
3170
+ const partHeights = lastSubColumnElement.parts.map((part) => part.dimensions.height);
3171
+ const elementToCreate = (() => {
3172
+ switch (lastSubColumnElement.type) {
3173
+ case 'matchGap':
3174
+ case 'roundHeaderGap':
3175
+ case 'roundGap':
3176
+ case 'colGap': {
3177
+ return {
3178
+ area: !lastSubColumn.span.isEnd ? lastSubColumnElement.area : `.`,
3179
+ type: !lastSubColumn.span.isEnd ? lastSubColumnElement.type : 'colGap',
3180
+ elementHeight: lastSubColumnElement.dimensions.height,
3181
+ partHeights,
3182
+ };
3183
+ }
3184
+ case 'header':
3185
+ if (lastSubColumn.span.isEnd)
3186
+ break;
3187
+ return {
3188
+ area: lastSubColumnElement.area,
3189
+ type: lastSubColumnElement.type,
3190
+ elementHeight: lastSubColumnElement.dimensions.height,
3191
+ partHeights,
3192
+ component: lastSubColumnElement.component,
3193
+ round: lastSubColumnElement.round,
3194
+ roundSwissGroup: lastSubColumnElement.roundSwissGroup,
3195
+ };
3196
+ case 'match':
3197
+ if (lastSubColumn.span.isEnd)
3198
+ break;
3199
+ return {
3200
+ area: lastSubColumnElement.area,
3201
+ type: lastSubColumnElement.type,
3202
+ elementHeight: lastSubColumnElement.dimensions.height,
3203
+ partHeights,
3204
+ component: lastSubColumnElement.component,
3205
+ round: lastSubColumnElement.round,
3206
+ match: lastSubColumnElement.match,
3207
+ roundSwissGroup: lastSubColumnElement.roundSwissGroup,
3208
+ };
3209
+ }
3210
+ return {
3211
+ area: `.`,
3212
+ type: 'colGap',
3213
+ elementHeight: lastSubColumnElement.dimensions.height,
3214
+ partHeights,
3215
+ };
3216
+ })();
3217
+ pushElement(createBracketElement(elementToCreate).element);
3218
+ }
3219
+ pushSubColumn(subColumn);
3220
+ pushSection(masterColumnSection);
3161
3221
  }
3162
- else {
3163
- relations.push(createTwoToNothingRelation({
3164
- currentRound: currentUpperRound,
3165
- previousUpperRound,
3166
- previousLowerRound: finalLowerRound,
3167
- firstUpperRound,
3168
- firstLowerRound,
3169
- }));
3222
+ return masterColumn;
3223
+ };
3224
+
3225
+ const createRoundBracketSubColumnRelativeToFirstRound = (config) => {
3226
+ const { firstRound, round, options, span, hasReverseFinal } = config;
3227
+ const { subColumn, pushElement } = createBracketSubColumn({
3228
+ span,
3229
+ });
3230
+ const matchFactor = firstRound.matchCount / round.matchCount;
3231
+ const matches = Array.from(round.matches.values());
3232
+ const elementsToCreate = [];
3233
+ // Only include a header row if headers exist
3234
+ if (options.roundHeaderHeight > 0) {
3235
+ elementsToCreate.push({
3236
+ type: 'header',
3237
+ area: `h${round.shortId}`,
3238
+ partHeights: [options.roundHeaderHeight],
3239
+ elementHeight: options.roundHeaderHeight,
3240
+ component: config.components.roundHeader,
3241
+ round,
3242
+ roundSwissGroup: null,
3243
+ }, {
3244
+ type: 'roundHeaderGap',
3245
+ area: '.',
3246
+ partHeights: [options.roundHeaderGap],
3247
+ elementHeight: options.roundHeaderGap,
3248
+ });
3170
3249
  }
3171
- if (isAsyncBracket) {
3172
- const preFinalLowerRound = lowerRounds[lowerRounds.length - 2];
3173
- const prePreFinalLowerRound = lowerRounds[lowerRounds.length - 3] || null;
3174
- if (!preFinalLowerRound)
3175
- throw new Error('preFinalLowerRound is null');
3176
- relations.push(createOneToOneRelation({
3177
- currentRound: finalLowerRound,
3178
- previousRound: preFinalLowerRound,
3179
- nextRound: currentUpperRound,
3180
- rootRound: firstLowerRound,
3181
- }));
3182
- if (prePreFinalLowerRound) {
3183
- relations.push(createOneToOneRelation({
3184
- currentRound: preFinalLowerRound,
3185
- previousRound: prePreFinalLowerRound,
3186
- nextRound: finalLowerRound,
3187
- rootRound: firstLowerRound,
3188
- }));
3250
+ // Add match elements to create
3251
+ for (const [matchIndex, match] of matches.entries()) {
3252
+ const isLastMatch = matchIndex === matches.length - 1;
3253
+ const matchRows = [];
3254
+ for (let factorIndex = 0; factorIndex < matchFactor; factorIndex++) {
3255
+ const isLastFactor = factorIndex === matchFactor - 1;
3256
+ // Add the match height
3257
+ matchRows.push(options.matchHeight);
3258
+ if (isLastFactor)
3259
+ continue;
3260
+ // Add gap between match factors (except after the last one)
3261
+ matchRows.push(options.rowGap);
3189
3262
  }
3190
- else {
3191
- relations.push(createNothingToOneRelation({
3192
- currentRound: preFinalLowerRound,
3193
- nextRound: finalLowerRound,
3194
- }));
3263
+ const isFinalMatch = hasReverseFinal
3264
+ ? round.type === DOUBLE_ELIMINATION_BRACKET_ROUND_TYPE.REVERSE_FINAL
3265
+ : round.type === COMMON_BRACKET_ROUND_TYPE.FINAL;
3266
+ elementsToCreate.push({
3267
+ type: 'match',
3268
+ area: `m${match.shortId}`,
3269
+ partHeights: matchRows,
3270
+ elementHeight: isFinalMatch ? options.finalMatchHeight : options.matchHeight,
3271
+ component: isFinalMatch ? config.components.finalMatch : config.components.match,
3272
+ match,
3273
+ round,
3274
+ roundSwissGroup: null,
3275
+ });
3276
+ if (!isLastMatch) {
3277
+ elementsToCreate.push({
3278
+ type: 'matchGap',
3279
+ area: '.',
3280
+ partHeights: [options.rowGap],
3281
+ elementHeight: options.rowGap,
3282
+ });
3195
3283
  }
3196
3284
  }
3197
- else {
3198
- if (!previousLowerRound)
3199
- throw new Error('previousLowerRound is null');
3200
- relations.push(createOneToOneRelation({
3201
- currentRound: finalLowerRound,
3202
- previousRound: previousLowerRound,
3203
- nextRound: currentUpperRound,
3204
- rootRound: firstLowerRound,
3205
- }));
3206
- }
3207
- };
3208
- const handleFirstRound = (params) => {
3209
- const { relations, currentUpperRound, nextUpperRound, lowerRounds, currentUpperRoundIndex } = params;
3210
- relations.push(createNothingToOneRelation({
3211
- currentRound: currentUpperRound,
3212
- nextRound: nextUpperRound,
3213
- }));
3214
- const currentLowerRound = lowerRounds[currentUpperRoundIndex] || null;
3215
- const nextLowerRound = lowerRounds[currentUpperRoundIndex + 1] || null;
3216
- if (currentLowerRound && nextLowerRound) {
3217
- relations.push(createNothingToOneRelation({
3218
- currentRound: currentLowerRound,
3219
- nextRound: nextLowerRound,
3220
- }));
3285
+ // Create all elements at once
3286
+ for (const elementData of elementsToCreate) {
3287
+ const { element } = createBracketElement(elementData);
3288
+ pushElement(element);
3221
3289
  }
3290
+ return subColumn;
3222
3291
  };
3223
- const handleRegularRound = (params) => {
3224
- const { relations, currentUpperRound, previousUpperRound, nextUpperRound, lowerRounds, currentUpperRoundIndex, firstUpperRound, firstLowerRound, } = params;
3225
- relations.push(createOneToOneRelation({
3226
- currentRound: currentUpperRound,
3227
- previousRound: previousUpperRound,
3228
- nextRound: nextUpperRound,
3229
- rootRound: firstUpperRound,
3230
- }));
3231
- const currentLowerRound = lowerRounds[currentUpperRoundIndex] || null;
3232
- const previousLowerRound = lowerRounds[currentUpperRoundIndex - 1] || null;
3233
- const nextLowerRound = lowerRounds[currentUpperRoundIndex + 1] || null;
3234
- if (currentLowerRound &&
3235
- currentUpperRound.type === DOUBLE_ELIMINATION_BRACKET_ROUND_TYPE.UPPER_BRACKET &&
3236
- previousLowerRound &&
3237
- nextLowerRound &&
3238
- firstLowerRound) {
3239
- relations.push(createOneToOneRelation({
3240
- currentRound: currentLowerRound,
3241
- previousRound: previousLowerRound,
3242
- nextRound: nextLowerRound,
3243
- rootRound: firstLowerRound,
3244
- }));
3292
+
3293
+ const createDoubleEliminationGrid = (bracketData, options, components) => {
3294
+ const grid = createBracketGrid({ spanElementWidth: options.columnWidth });
3295
+ const upperBracketRounds = Array.from(bracketData.roundsByType.getOrThrow(DOUBLE_ELIMINATION_BRACKET_ROUND_TYPE.UPPER_BRACKET).values());
3296
+ const lowerBracketRounds = Array.from(bracketData.roundsByType.getOrThrow(DOUBLE_ELIMINATION_BRACKET_ROUND_TYPE.LOWER_BRACKET).values());
3297
+ const remainingRounds = Array.from(bracketData.rounds.values()).filter((r) => r.type !== DOUBLE_ELIMINATION_BRACKET_ROUND_TYPE.UPPER_BRACKET &&
3298
+ r.type !== DOUBLE_ELIMINATION_BRACKET_ROUND_TYPE.LOWER_BRACKET &&
3299
+ r.type !== COMMON_BRACKET_ROUND_TYPE.THIRD_PLACE);
3300
+ const thirdPlaceRound = bracketData.roundsByType.get(COMMON_BRACKET_ROUND_TYPE.THIRD_PLACE)?.first() ?? null;
3301
+ const hasReverseFinal = !!bracketData.roundsByType.get(DOUBLE_ELIMINATION_BRACKET_ROUND_TYPE.REVERSE_FINAL)?.first();
3302
+ const firstUpperRound = upperBracketRounds[0];
3303
+ const firstLowerRound = lowerBracketRounds[0];
3304
+ if (!firstUpperRound || !firstLowerRound) {
3305
+ throw new Error('No upper or lower rounds found in bracket data');
3245
3306
  }
3246
- };
3247
- const generateRoundRelationsNew = (bracketData) => {
3248
- const relations = [];
3249
- const allRounds = [...bracketData.rounds.values()];
3250
- const upperRounds = allRounds.filter((r) => r.type !== DOUBLE_ELIMINATION_BRACKET_ROUND_TYPE.LOWER_BRACKET);
3251
- const lowerRounds = allRounds.filter((r) => r.type === DOUBLE_ELIMINATION_BRACKET_ROUND_TYPE.LOWER_BRACKET);
3252
- const firstUpperRound = upperRounds[0];
3253
- const firstLowerRound = lowerRounds[0] || null;
3254
- const lastLowerRound = lowerRounds[lowerRounds.length - 1] || null;
3255
- if (!firstUpperRound)
3256
- throw new Error('No upper rounds found');
3257
- const hasLowerRounds = lowerRounds.length > 0;
3258
- for (const [currentUpperRoundIndex] of upperRounds.entries()) {
3259
- const nav = getNavigationContext({
3260
- upperRounds,
3261
- currentUpperRoundIndex,
3307
+ const upperToLowerRatio = calculateUpperLowerRatio(upperBracketRounds.length, lowerBracketRounds.length);
3308
+ const columnSplitFactor = calculateColumnSplitFactor(upperToLowerRatio);
3309
+ let lastRoundLastSubColumnUpperIndex = -1;
3310
+ let lastRoundLastSubColumnLowerIndex = -1;
3311
+ for (const [lowerRoundIndex, lowerRound] of lowerBracketRounds.entries()) {
3312
+ const isLastLowerRound = lowerRoundIndex === lowerBracketRounds.length - 1;
3313
+ const { masterColumn, pushSection } = createBracketMasterColumn({
3314
+ columnWidth: options.columnWidth,
3315
+ padding: {
3316
+ bottom: 0,
3317
+ left: 0,
3318
+ right: 0,
3319
+ top: 0,
3320
+ },
3262
3321
  });
3263
- if (nav.isFinal && hasLowerRounds && lastLowerRound && firstLowerRound && nav.previousUpperRound) {
3264
- handleFinalRound({
3265
- relations,
3266
- currentUpperRound: nav.currentUpperRound,
3267
- previousUpperRound: nav.previousUpperRound,
3268
- nextUpperRound: nav.nextUpperRound,
3269
- lowerRounds,
3270
- currentUpperRoundIndex,
3271
- firstUpperRound,
3272
- firstLowerRound,
3273
- lastLowerRound,
3322
+ const { masterColumnSection: upperSection, pushSubColumn: pushUpperSubColumn } = createBracketMasterColumnSection({
3323
+ type: 'round',
3324
+ });
3325
+ const { masterColumnSection: upperLowerGapSection, pushSubColumn: pushUpperLowerSubColumn } = createBracketMasterColumnSection({
3326
+ type: 'gap',
3327
+ });
3328
+ const { masterColumnSection: lowerSection, pushSubColumn: pushLowerSubColumn } = createBracketMasterColumnSection({
3329
+ type: 'round',
3330
+ });
3331
+ for (let currentColumnSplitFactor = 1; currentColumnSplitFactor <= columnSplitFactor; currentColumnSplitFactor++) {
3332
+ const subColumnIndex = lowerRoundIndex * columnSplitFactor + (currentColumnSplitFactor - 1);
3333
+ const currentUpperRoundIndex = calculateUpperRoundIndex(subColumnIndex, upperToLowerRatio, columnSplitFactor);
3334
+ const currentLowerRoundIndex = lowerRoundIndex;
3335
+ const isFirstSubColumnInMasterColumn = currentColumnSplitFactor === 1;
3336
+ const isLastSubColumnInMasterColumn = currentColumnSplitFactor === columnSplitFactor;
3337
+ const previousSubColumnIndex = subColumnIndex - 1;
3338
+ const nextSubColumnIndex = subColumnIndex + 1;
3339
+ const previousUpperRoundIndex = previousSubColumnIndex >= 0
3340
+ ? calculateUpperRoundIndex(previousSubColumnIndex, upperToLowerRatio, columnSplitFactor)
3341
+ : -1;
3342
+ const nextUpperRoundIndex = calculateUpperRoundIndex(nextSubColumnIndex, upperToLowerRatio, columnSplitFactor);
3343
+ const previousLowerRoundIndex = previousSubColumnIndex >= 0 ? calculateLowerRoundIndex(previousSubColumnIndex, columnSplitFactor) : -1;
3344
+ const nextLowerRoundIndex = calculateLowerRoundIndex(nextSubColumnIndex, columnSplitFactor);
3345
+ const upperRound = upperBracketRounds[currentUpperRoundIndex];
3346
+ if (!upperRound) {
3347
+ throw new Error('Upper round not found for subColumnIndex: ' + subColumnIndex);
3348
+ }
3349
+ // For upper bracket spans - check if this round is different from the previous occurrence
3350
+ const isUpperSpanStart = isFirstSubColumnInMasterColumn
3351
+ ? lowerRoundIndex === 0 || lastRoundLastSubColumnUpperIndex !== currentUpperRoundIndex
3352
+ : previousUpperRoundIndex !== currentUpperRoundIndex;
3353
+ // For upper bracket spans - check if this round will be different in the next occurrence
3354
+ const isUpperSpanEnd = isLastSubColumnInMasterColumn
3355
+ ? isLastLowerRound ||
3356
+ calculateUpperRoundIndex((lowerRoundIndex + 1) * columnSplitFactor, upperToLowerRatio, columnSplitFactor) !==
3357
+ currentUpperRoundIndex
3358
+ : nextUpperRoundIndex !== currentUpperRoundIndex;
3359
+ // For lower bracket spans - similar logic
3360
+ const isLowerSpanStart = isFirstSubColumnInMasterColumn
3361
+ ? lowerRoundIndex === 0 || lastRoundLastSubColumnLowerIndex !== currentLowerRoundIndex
3362
+ : previousLowerRoundIndex !== currentLowerRoundIndex;
3363
+ const isLowerSpanEnd = isLastSubColumnInMasterColumn
3364
+ ? isLastLowerRound ||
3365
+ calculateLowerRoundIndex((lowerRoundIndex + 1) * columnSplitFactor, columnSplitFactor) !==
3366
+ currentLowerRoundIndex
3367
+ : nextLowerRoundIndex !== currentLowerRoundIndex;
3368
+ const upperSubColumn = createRoundBracketSubColumnRelativeToFirstRound({
3369
+ firstRound: firstUpperRound,
3370
+ round: upperRound,
3371
+ options,
3372
+ hasReverseFinal,
3373
+ span: {
3374
+ isStart: isUpperSpanStart,
3375
+ isEnd: isUpperSpanEnd,
3376
+ },
3377
+ components,
3274
3378
  });
3275
- }
3276
- else if (nav.isLastUpperRound && nav.previousUpperRound) {
3277
- relations.push(createOneToNothingRelation({
3278
- currentRound: nav.currentUpperRound,
3279
- previousRound: nav.previousUpperRound,
3280
- rootRound: firstUpperRound,
3281
- }));
3282
- }
3283
- else if (nav.currentUpperRound.isFirstRound && nav.nextUpperRound) {
3284
- handleFirstRound({
3285
- relations,
3286
- currentUpperRound: nav.currentUpperRound,
3287
- nextUpperRound: nav.nextUpperRound,
3288
- lowerRounds,
3289
- currentUpperRoundIndex,
3379
+ pushUpperSubColumn(upperSubColumn);
3380
+ const upperLowerGapSubColumn = createBracketSubColumn({
3381
+ span: {
3382
+ isStart: true,
3383
+ isEnd: true,
3384
+ },
3290
3385
  });
3291
- }
3292
- else if (nav.previousUpperRound && nav.nextUpperRound) {
3293
- handleRegularRound({
3294
- relations,
3295
- currentUpperRound: nav.currentUpperRound,
3296
- previousUpperRound: nav.previousUpperRound,
3297
- nextUpperRound: nav.nextUpperRound,
3298
- lowerRounds,
3299
- currentUpperRoundIndex,
3300
- firstUpperRound,
3301
- firstLowerRound,
3386
+ const upperLowerGapElement = createBracketElement({
3387
+ area: '.',
3388
+ type: 'roundGap',
3389
+ elementHeight: options.rowRoundGap,
3390
+ partHeights: [options.rowRoundGap],
3391
+ });
3392
+ upperLowerGapSubColumn.pushElement(upperLowerGapElement.element);
3393
+ pushUpperLowerSubColumn(upperLowerGapSubColumn.subColumn);
3394
+ const lowerSubColumn = createRoundBracketSubColumnRelativeToFirstRound({
3395
+ firstRound: firstLowerRound,
3396
+ round: lowerRound,
3397
+ options,
3398
+ hasReverseFinal,
3399
+ span: {
3400
+ isStart: isLowerSpanStart,
3401
+ isEnd: isLowerSpanEnd,
3402
+ },
3403
+ components,
3302
3404
  });
3405
+ pushLowerSubColumn(lowerSubColumn);
3406
+ if (isLastSubColumnInMasterColumn) {
3407
+ lastRoundLastSubColumnUpperIndex = currentUpperRoundIndex;
3408
+ lastRoundLastSubColumnLowerIndex = currentLowerRoundIndex;
3409
+ }
3303
3410
  }
3304
- }
3305
- return relations;
3306
- };
3307
-
3308
- const createNewBracket = (source, options) => {
3309
- const bracketNewBase = createNewBracketBase(source, options);
3310
- const rounds = new BracketMap();
3311
- const roundsByType = new BracketMap();
3312
- for (const roundBase of bracketNewBase.rounds.values()) {
3313
- const newRound = {
3314
- ...roundBase,
3315
- matches: new BracketMap(),
3316
- relation: { type: 'dummy' },
3317
- };
3318
- rounds.set(roundBase.id, newRound);
3319
- if (!roundsByType.has(roundBase.type)) {
3320
- roundsByType.set(roundBase.type, new BracketMap());
3411
+ pushSection(upperSection, upperLowerGapSection, lowerSection);
3412
+ grid.pushMasterColumn(masterColumn);
3413
+ if (remainingRounds.length) {
3414
+ grid.pushMasterColumn(createBracketGapMasterColumn({
3415
+ existingMasterColumns: grid.grid.masterColumns,
3416
+ columnGap: options.columnGap,
3417
+ }));
3321
3418
  }
3322
- roundsByType.getOrThrow(roundBase.type).set(roundBase.id, newRound);
3323
3419
  }
3324
- const participants = new BracketMap();
3325
- for (const participantBase of bracketNewBase.participants.values()) {
3326
- participants.set(participantBase.id, {
3327
- ...participantBase,
3328
- matches: new BracketMap(),
3420
+ for (const [roundIndex, round] of remainingRounds.entries()) {
3421
+ const isLastRound = roundIndex === remainingRounds.length - 1;
3422
+ const isFirstRound = roundIndex === 0;
3423
+ const isAnyFinal = hasReverseFinal
3424
+ ? round.type === DOUBLE_ELIMINATION_BRACKET_ROUND_TYPE.REVERSE_FINAL
3425
+ : round.type === COMMON_BRACKET_ROUND_TYPE.FINAL;
3426
+ const { masterColumn, pushSection } = createBracketMasterColumn({
3427
+ columnWidth: isAnyFinal ? options.finalColumnWidth : options.columnWidth,
3428
+ padding: {
3429
+ bottom: 0,
3430
+ left: 0,
3431
+ right: 0,
3432
+ top: 0,
3433
+ },
3329
3434
  });
3330
- }
3331
- const matches = new BracketMap();
3332
- for (const matchBase of bracketNewBase.matches.values()) {
3333
- const round = rounds.getOrThrow(matchBase.roundId);
3334
- const homeParticipant = matchBase.home
3335
- ? { ...matchBase.home, matches: new BracketMap() }
3336
- : null;
3337
- const awayParticipant = matchBase.away
3338
- ? { ...matchBase.away, matches: new BracketMap() }
3339
- : null;
3340
- const newMatch = {
3341
- ...matchBase,
3342
- home: homeParticipant,
3343
- away: awayParticipant,
3344
- winner: null,
3435
+ const { masterColumnSection: upperSection, pushSubColumn: pushUpperSubColumn } = createBracketMasterColumnSection({
3436
+ type: 'round',
3437
+ });
3438
+ const { masterColumnSection: upperLowerGapSection, pushSubColumn: pushUpperLowerSubColumn } = createBracketMasterColumnSection({
3439
+ type: 'gap',
3440
+ });
3441
+ const { masterColumnSection: lowerSection, pushSubColumn: pushLowerSubColumn } = createBracketMasterColumnSection({
3442
+ type: 'round',
3443
+ });
3444
+ const upperSubColumn = createRoundBracketSubColumnRelativeToFirstRound({
3445
+ firstRound: firstUpperRound,
3345
3446
  round,
3346
- relation: { type: 'dummy' },
3347
- };
3348
- if (matchBase.winner) {
3349
- const winnerParticipant = homeParticipant?.id === matchBase.winner.id ? homeParticipant : awayParticipant;
3350
- if (!winnerParticipant)
3351
- throw new Error(`Winner participant with id ${matchBase.winner.id} not found in match base`);
3352
- newMatch.winner = winnerParticipant;
3353
- }
3354
- matches.set(matchBase.id, newMatch);
3355
- round.matches.set(matchBase.id, newMatch);
3356
- if (homeParticipant) {
3357
- const participant = participants.getOrThrow(homeParticipant.id);
3358
- participant.matches.set(matchBase.id, {
3359
- ...newMatch,
3360
- me: homeParticipant,
3361
- opponent: awayParticipant,
3447
+ options,
3448
+ hasReverseFinal,
3449
+ span: {
3450
+ isStart: true,
3451
+ isEnd: true,
3452
+ },
3453
+ components,
3454
+ });
3455
+ pushUpperSubColumn(upperSubColumn);
3456
+ const upperLowerGapSubColumn = createBracketSubColumn({
3457
+ span: {
3458
+ isStart: true,
3459
+ isEnd: true,
3460
+ },
3461
+ });
3462
+ const upperLowerGapElement = createBracketElement({
3463
+ area: '.',
3464
+ type: 'roundGap',
3465
+ elementHeight: options.rowRoundGap,
3466
+ partHeights: [options.rowRoundGap],
3467
+ });
3468
+ upperLowerGapSubColumn.pushElement(upperLowerGapElement.element);
3469
+ pushUpperLowerSubColumn(upperLowerGapSubColumn.subColumn);
3470
+ if (thirdPlaceRound) {
3471
+ const lowerSubColumn = createRoundBracketSubColumnRelativeToFirstRound({
3472
+ firstRound: firstLowerRound,
3473
+ round: thirdPlaceRound,
3474
+ options,
3475
+ hasReverseFinal,
3476
+ span: {
3477
+ isStart: isFirstRound,
3478
+ isEnd: isLastRound,
3479
+ },
3480
+ components,
3362
3481
  });
3482
+ pushLowerSubColumn(lowerSubColumn);
3363
3483
  }
3364
- if (awayParticipant) {
3365
- const participant = participants.getOrThrow(awayParticipant.id);
3366
- participant.matches.set(matchBase.id, {
3367
- ...newMatch,
3368
- me: awayParticipant,
3369
- opponent: homeParticipant,
3484
+ else {
3485
+ const lowerSubColumn = createBracketSubColumn({
3486
+ span: {
3487
+ isStart: true,
3488
+ isEnd: true,
3489
+ },
3370
3490
  });
3491
+ const firstMasterRound = grid.grid.masterColumns[0];
3492
+ if (!firstMasterRound)
3493
+ throw new Error('No first master round found in grid');
3494
+ const lastMasterColumnSection = firstMasterRound.sections[firstMasterRound.sections.length - 1];
3495
+ if (!lastMasterColumnSection)
3496
+ throw new Error('No last master column section found in grid');
3497
+ const firstSubColumn = lastMasterColumnSection.subColumns[0];
3498
+ if (!firstSubColumn)
3499
+ throw new Error('No first sub column found in grid');
3500
+ for (const element of firstSubColumn.elements) {
3501
+ const el = createBracketElement({
3502
+ area: '.',
3503
+ type: 'colGap',
3504
+ elementHeight: element.dimensions.height,
3505
+ partHeights: element.parts.map((p) => p.dimensions.height),
3506
+ });
3507
+ lowerSubColumn.pushElement(el.element);
3508
+ }
3509
+ pushLowerSubColumn(lowerSubColumn.subColumn);
3371
3510
  }
3372
- }
3373
- for (const participant of participants.values()) {
3374
- for (const match of participant.matches.values()) {
3375
- if (match.home?.id === participant.id)
3376
- match.home.matches = participant.matches;
3377
- if (match.away?.id === participant.id)
3378
- match.away.matches = participant.matches;
3379
- if (match.winner?.id === participant.id)
3380
- match.winner.matches = participant.matches;
3381
- }
3382
- }
3383
- const newBracket = {
3384
- matches,
3385
- participants,
3386
- rounds,
3387
- roundsByType,
3388
- mode: bracketNewBase.mode,
3389
- };
3390
- const roundRelations = generateRoundRelationsNew(newBracket);
3391
- for (const roundRelation of roundRelations) {
3392
- roundRelation.currentRound.relation = roundRelation;
3393
- }
3394
- const matchRelations = generateMatchRelationsNew(newBracket);
3395
- for (const matchRelation of matchRelations) {
3396
- matchRelation.currentMatch.relation = matchRelation;
3397
- }
3398
- return newBracket;
3399
- };
3400
-
3401
- const pad = (str, len) => str.padEnd(len, ' ');
3402
- const color = (str, code) => `\x1b[${code}m${str}\x1b[0m`;
3403
- const roundColor = (str) => color(str, 36); // cyan
3404
- const arrowColor = (str) => color(str, 90); // gray
3405
- const labelColor = (str) => color(str, 33); // yellow
3406
- const factorColor = (str) => color(str, 32); // green
3407
- const logRoundRelations = (bracketData) => {
3408
- // Find max round name length for alignment
3409
- let maxNameLen = 0;
3410
- for (const round of bracketData.rounds.values()) {
3411
- maxNameLen = Math.max(maxNameLen, round.name.length);
3412
- }
3413
- const colWidth = maxNameLen + 2;
3414
- // Build rows
3415
- const rows = [];
3416
- for (const round of bracketData.rounds.values()) {
3417
- const relation = round.relation;
3418
- switch (relation.type) {
3419
- case 'nothing-to-one':
3420
- rows.push([
3421
- labelColor(pad('START', colWidth)),
3422
- arrowColor('──▶'),
3423
- roundColor(pad(round.name, colWidth)),
3424
- arrowColor('──▶'),
3425
- roundColor(pad(relation.nextRound.name, colWidth)),
3426
- factorColor(`[${relation.nextRoundMatchFactor}]`),
3427
- ]);
3428
- break;
3429
- case 'one-to-nothing':
3430
- rows.push([
3431
- roundColor(pad(relation.previousRound.name, colWidth)),
3432
- arrowColor('──▶'),
3433
- roundColor(pad(round.name, colWidth)),
3434
- arrowColor('──▶'),
3435
- labelColor(pad('END', colWidth)),
3436
- factorColor(`[${relation.previousRoundMatchFactor}]`),
3437
- ]);
3438
- break;
3439
- case 'one-to-one':
3440
- rows.push([
3441
- roundColor(pad(relation.previousRound.name, colWidth)),
3442
- arrowColor('──▶'),
3443
- roundColor(pad(round.name, colWidth)),
3444
- arrowColor('──▶'),
3445
- roundColor(pad(relation.nextRound.name, colWidth)),
3446
- factorColor(`[Prev: ${relation.previousRoundMatchFactor}, Next: ${relation.nextRoundMatchFactor}]`),
3447
- ]);
3448
- break;
3449
- case 'two-to-one':
3450
- rows.push([
3451
- roundColor(pad(relation.previousUpperRound.name, colWidth)),
3452
- arrowColor(' │ '),
3453
- roundColor(pad(relation.previousLowerRound.name, colWidth)),
3454
- arrowColor('──▶'),
3455
- roundColor(pad(round.name, colWidth)),
3456
- arrowColor('──▶'),
3457
- roundColor(pad(relation.nextRound.name, colWidth)),
3458
- factorColor(`[Upper: ${relation.previousUpperRoundMatchFactor}, Lower: ${relation.previousLowerRoundMatchFactor}, Next: ${relation.nextRoundMatchFactor}]`),
3459
- ]);
3460
- break;
3461
- case 'two-to-nothing':
3462
- rows.push([
3463
- roundColor(pad(relation.previousUpperRound.name, colWidth)),
3464
- arrowColor(' │'),
3465
- roundColor(pad(relation.previousLowerRound.name, colWidth)),
3466
- arrowColor(' ▼'),
3467
- roundColor(pad(round.name, colWidth)),
3468
- arrowColor('──▶'),
3469
- labelColor(pad('END', colWidth)),
3470
- factorColor(`[Upper: ${relation.previousUpperRoundMatchFactor}, Lower: ${relation.previousLowerRoundMatchFactor}]`),
3471
- ]);
3472
- break;
3511
+ pushSection(upperSection, upperLowerGapSection, lowerSection);
3512
+ grid.pushMasterColumn(masterColumn);
3513
+ if (!isLastRound) {
3514
+ grid.pushMasterColumn(createBracketGapMasterColumn({
3515
+ existingMasterColumns: grid.grid.masterColumns,
3516
+ columnGap: options.columnGap,
3517
+ }));
3473
3518
  }
3474
3519
  }
3475
- // Print header
3476
- const divider = (label) => {
3477
- // eslint-disable-next-line no-control-regex
3478
- const width = rows[0]?.reduce((w, col) => w + col.replace(/\x1b\[[0-9;]*m/g, '').length + 2, 0) || 60;
3479
- return `\n${'='.repeat(width)}\n${labelColor(label)}\n${'='.repeat(width)}\n`;
3520
+ grid.setupElementSpans();
3521
+ grid.calculateDimensions();
3522
+ const finalizedGrid = finalizeBracketGrid(grid);
3523
+ return {
3524
+ raw: grid,
3525
+ columns: finalizedGrid.columns,
3526
+ matchElementMap: finalizedGrid.elementMap,
3480
3527
  };
3481
- console.log(divider('Bracket Structure'));
3482
- // Print rows
3483
- for (const row of rows) {
3484
- console.log(row.join(' '));
3485
- }
3486
- console.log();
3487
3528
  };
3488
3529
 
3489
- const factorialCache = new Map();
3490
- const getAvailableSwissGroupsForRound = (roundNumber, totalMatchesInRound) => {
3491
- const ADVANCE_WINS = 3;
3492
- const ELIMINATE_LOSSES = 3;
3493
- // Cache factorial calculations
3494
- const getFactorial = (n) => {
3495
- if (n <= 1)
3496
- return 1;
3497
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
3498
- if (factorialCache.has(n))
3499
- return factorialCache.get(n);
3500
- const result = n * getFactorial(n - 1);
3501
- factorialCache.set(n, result);
3502
- return result;
3503
- };
3504
- // Pre-calculate roundFactorial
3505
- const roundFactorial = getFactorial(roundNumber);
3506
- let totalCombinations = 0;
3507
- const validGroups = [];
3508
- // Single loop to gather valid groups and total combinations
3509
- for (let wins = roundNumber; wins >= 0; wins--) {
3510
- const losses = roundNumber - wins;
3511
- const remainingGames = ADVANCE_WINS + ELIMINATE_LOSSES - (wins + losses) - 1;
3512
- const notYetEliminated = losses < ELIMINATE_LOSSES;
3513
- const canStillAdvance = wins < ADVANCE_WINS && remainingGames >= 0;
3514
- if (!canStillAdvance || !notYetEliminated)
3515
- continue;
3516
- const combinations = roundFactorial / (getFactorial(wins) * getFactorial(losses));
3517
- totalCombinations += combinations;
3518
- validGroups.push({ wins, losses, combinations });
3519
- }
3520
- // Create final groups with calculated proportions
3521
- return validGroups.map(({ wins, losses, combinations }) => ({
3522
- id: `${wins}-${losses}`,
3523
- name: `${wins}-${losses}`,
3524
- matchesInGroup: Math.round((combinations / totalCombinations) * totalMatchesInRound),
3525
- }));
3526
- };
3527
- const generateBracketRoundSwissGroupMaps = (bracketData) => {
3528
- if (bracketData.mode !== TOURNAMENT_MODE.SWISS_WITH_ELIMINATION) {
3529
- return null;
3530
+ const createSingleEliminationGrid = (bracketData, options, components) => {
3531
+ const grid = createBracketGrid({ spanElementWidth: options.columnWidth });
3532
+ const rounds = Array.from(bracketData.rounds.values());
3533
+ const firstRound = bracketData.rounds.first();
3534
+ if (!firstRound) {
3535
+ throw new Error('No rounds found in bracket data');
3530
3536
  }
3531
- const roundsWithSwissGroups = new Map();
3532
- let roundNumber = 0;
3533
- for (const bracketRound of bracketData.rounds.values()) {
3534
- const availableGroups = getAvailableSwissGroupsForRound(roundNumber, bracketRound.matchCount);
3535
- const roundSwissData = {
3536
- groups: new Map(),
3537
- };
3538
- for (const group of availableGroups) {
3539
- const subGroup = {
3540
- id: group.id,
3541
- name: group.name,
3542
- matches: new BracketMap(),
3543
- allowedMatchCount: group.matchesInGroup,
3544
- };
3545
- roundSwissData.groups.set(group.id, subGroup);
3546
- }
3547
- const emptyMatchIds = [];
3548
- for (const match of bracketRound.matches.values()) {
3549
- const anyParticipant = match.home || match.away;
3550
- if (!anyParticipant) {
3551
- emptyMatchIds.push(match.id);
3552
- continue;
3553
- }
3554
- const wins = anyParticipant.winCount;
3555
- const losses = anyParticipant.lossCount;
3556
- const group = roundSwissData.groups.get(`${wins}-${losses}`);
3557
- if (!group)
3558
- throw new Error('Group not found for match: ' + match.id);
3559
- group.matches.set(match.id, match);
3560
- }
3561
- for (const emptyMatchId of emptyMatchIds) {
3562
- const match = bracketRound.matches.getOrThrow(emptyMatchId);
3563
- let groupFound = false;
3564
- for (const group of roundSwissData.groups.values()) {
3565
- if (group.matches.size < group.allowedMatchCount) {
3566
- group.matches.set(match.id, match);
3567
- groupFound = true;
3568
- break;
3569
- }
3570
- }
3571
- if (!groupFound) {
3572
- throw new Error('No group found for empty match');
3573
- }
3537
+ for (const [roundIndex, round] of rounds.entries()) {
3538
+ const isLastRound = roundIndex === rounds.length - 1;
3539
+ const { masterColumn, ...mutableMasterColumn } = createBracketMasterColumn({
3540
+ columnWidth: round.type === COMMON_BRACKET_ROUND_TYPE.FINAL ? options.finalColumnWidth : options.columnWidth,
3541
+ padding: {
3542
+ bottom: 0,
3543
+ left: 0,
3544
+ right: 0,
3545
+ top: 0,
3546
+ },
3547
+ });
3548
+ const { masterColumnSection, pushSubColumn } = createBracketMasterColumnSection({
3549
+ type: 'round',
3550
+ });
3551
+ const sub = createRoundBracketSubColumnRelativeToFirstRound({
3552
+ firstRound,
3553
+ round,
3554
+ options,
3555
+ hasReverseFinal: false,
3556
+ span: {
3557
+ isStart: true,
3558
+ isEnd: true,
3559
+ },
3560
+ components,
3561
+ });
3562
+ pushSubColumn(sub);
3563
+ mutableMasterColumn.pushSection(masterColumnSection);
3564
+ grid.pushMasterColumn(masterColumn);
3565
+ if (!isLastRound) {
3566
+ grid.pushMasterColumn(createBracketGapMasterColumn({
3567
+ existingMasterColumns: grid.grid.masterColumns,
3568
+ columnGap: options.columnGap,
3569
+ }));
3574
3570
  }
3575
- roundsWithSwissGroups.set(bracketRound.id, roundSwissData);
3576
- roundNumber++;
3577
3571
  }
3578
- return roundsWithSwissGroups;
3572
+ grid.calculateDimensions();
3573
+ const finalizedGrid = finalizeBracketGrid(grid);
3574
+ return {
3575
+ raw: grid,
3576
+ columns: finalizedGrid.columns,
3577
+ matchElementMap: finalizedGrid.elementMap,
3578
+ };
3579
3579
  };
3580
3580
 
3581
3581
  // TODO
@@ -3857,16 +3857,24 @@ var index = /*#__PURE__*/Object.freeze({
3857
3857
  TOURNAMENT_MODE: TOURNAMENT_MODE,
3858
3858
  canRenderLayoutInTournamentMode: canRenderLayoutInTournamentMode,
3859
3859
  createMatchesMapBase: createMatchesMapBase,
3860
+ createNewBracket: createNewBracket,
3860
3861
  createNewBracketBase: createNewBracketBase,
3861
3862
  createNewMatchParticipantBase: createNewMatchParticipantBase,
3862
3863
  createParticipantsMapBase: createParticipantsMapBase,
3863
3864
  createRoundsMapBase: createRoundsMapBase,
3864
3865
  generateBracketDataForEthlete: generateBracketDataForEthlete,
3865
3866
  generateBracketDataForGg: generateBracketDataForGg,
3867
+ generateBracketRoundSwissGroupMaps: generateBracketRoundSwissGroupMaps,
3868
+ generateMatchPosition: generateMatchPosition,
3869
+ generateMatchRelationPositions: generateMatchRelationPositions,
3870
+ generateMatchRelationsNew: generateMatchRelationsNew,
3871
+ generateRoundRelationsNew: generateRoundRelationsNew,
3866
3872
  generateRoundTypeFromEthleteRoundType: generateRoundTypeFromEthleteRoundType,
3867
3873
  generateRoundTypeFromGgMatch: generateRoundTypeFromGgMatch,
3868
3874
  generateTournamentModeFormEthleteRounds: generateTournamentModeFormEthleteRounds,
3869
- generateTournamentModeFormGgData: generateTournamentModeFormGgData
3875
+ generateTournamentModeFormGgData: generateTournamentModeFormGgData,
3876
+ getAvailableSwissGroupsForRound: getAvailableSwissGroupsForRound,
3877
+ logRoundRelations: logRoundRelations
3870
3878
  });
3871
3879
 
3872
3880
  const VALIDATOR_ERROR_SERVICE_TOKEN = new InjectionToken('VALIDATOR_ERROR_SERVICE');