Skip to content

Commit

Permalink
Add customRoundName config property
Browse files Browse the repository at this point in the history
  • Loading branch information
Drarig29 committed Apr 9, 2023
1 parent 14eb416 commit 668aae1
Show file tree
Hide file tree
Showing 7 changed files with 86 additions and 32 deletions.
8 changes: 8 additions & 0 deletions demo/with-api.html
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,14 @@
matchGames: data.match_game,
participants: data.participant,
}, {
// This is optional.
customRoundName: (info, t) => {
// You have a reference to `t` in order to translate things.
// Returning `undefined` will fallback to the default round name in the current language.

if (info.fractionOfFinal === 1 / 2)
return `${t(`abbreviations.${info.group}`)} Semi Finals`
},
selector: '#example',
participantOriginPlacement: 'before',
separatedChildCountLabel: true,
Expand Down
2 changes: 1 addition & 1 deletion dist/brackets-viewer.min.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/stage-form-creator.min.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
],
"homepage": "https://github.com/Drarig29/brackets-viewer.js#readme",
"dependencies": {
"brackets-manager": "^1.4.2",
"brackets-manager": "^1.5.7",
"brackets-memory-db": "^1.0.4",
"brackets-model": "^1.4.0",
"i18next": "^21.6.14",
Expand Down
42 changes: 23 additions & 19 deletions src/lang.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import LanguageDetector from 'i18next-browser-languagedetector';

import { Stage, Status, FinalType, GroupType } from 'brackets-model';
import { isMajorRound } from './helpers';
import { OriginHint } from './types';
import { OriginHint, RoundNameInfo } from './types';

import en from './i18n/en/translation.json';
import fr from './i18n/fr/translation.json';
Expand Down Expand Up @@ -51,6 +51,21 @@ export function t<Scope extends keyof Locale, SubKey extends string & keyof Loca
return i18next.t(key, options);
}

export type Translator = typeof t;

export type ToI18nKey<S extends string> = S extends `${infer A}_${infer B}`
? `${A}-${B}`
: never;

/**
* Converts a type to a valid i18n key.
*
* @param key The key to convert.
*/
export function toI18nKey<S extends `${string}_${string}`>(key: S): ToI18nKey<S> {
return key.replace('_', '-') as ToI18nKey<S>;
}

/**
* Returns an origin hint function based on rounds information.
*
Expand Down Expand Up @@ -192,10 +207,6 @@ export function getGroupName(groupNumber: number): string {
return t('common.group-name', { groupNumber });
}

type Replace<S extends string, Search extends string, Replace extends string> = S extends `${infer A}${Search}${infer B}`
? `${A}${Replace}${B}`
: never;

/**
* Returns the name of the bracket.
*
Expand All @@ -206,39 +217,32 @@ export function getBracketName(stage: Stage, type: GroupType): string | undefine
switch (type) {
case 'winner_bracket':
case 'loser_bracket':
const key = type.replace('_', '-') as Replace<typeof type, '_', '-'>;
return t(`common.group-name-${key}`, { stage });
return t(`common.group-name-${toI18nKey(type)}`, { stage });
default:
return undefined;
}
}

// eslint-disable-next-line jsdoc/require-param
/**
* Returns the name of a round.
*
* @param roundNumber Number of the round.
* @param roundCount Count of rounds.
*/
export function getRoundName(roundNumber: number, roundCount: number): string {
export function getRoundName({ roundNumber, roundCount }: RoundNameInfo, t: Translator): string {
return roundNumber === roundCount ? t('common.round-name-final') : t('common.round-name', { roundNumber });
}

// eslint-disable-next-line jsdoc/require-param
/**
* Returns the name of a round in the winner bracket of a double elimination stage.
*
* @param roundNumber Number of the round.
* @param roundCount Count of rounds.
*/
export function getWinnerBracketRoundName(roundNumber: number, roundCount: number): string {
export function getWinnerBracketRoundName({ roundNumber, roundCount }: RoundNameInfo, t: Translator): string {
return roundNumber === roundCount ? t('common.round-name-winner-bracket-final') : t('common.round-name-winner-bracket', { roundNumber });
}

// eslint-disable-next-line jsdoc/require-param
/**
* Returns the name of a round in the loser bracket of a double elimination stage.
*
* @param roundNumber Number of the round.
* @param roundCount Count of rounds.
*/
export function getLoserBracketRoundName(roundNumber: number, roundCount: number): string {
export function getLoserBracketRoundName({ roundNumber, roundCount }: RoundNameInfo, t: Translator): string {
return roundNumber === roundCount ? t('common.round-name-loser-bracket-final') : t('common.round-name-loser-bracket', { roundNumber });
}
37 changes: 29 additions & 8 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,19 @@ import { splitBy, getRanking, getOriginAbbreviation, findRoot, completeWithBlank
import * as dom from './dom';
import * as lang from './lang';
import { Locale } from './lang';
import { helpers } from 'brackets-manager';
import {
Config,
Connection,
OriginHint,
ParticipantContainers,
RankingItem,
RoundName,
RoundNameGetter,
ViewerData,
ParticipantImage,
Side,
MatchClickCallback,
RoundNameInfo,
} from './types';

export class BracketsViewer {
Expand All @@ -29,11 +31,16 @@ export class BracketsViewer {
private skipFirstRound = false;
private alwaysConnectFirstRound = false;

// eslint-disable-next-line jsdoc/require-jsdoc
private getRoundName(info: RoundNameInfo, fallbackGetter: RoundNameGetter): string {
return this.config.customRoundName?.(info, lang.t) || fallbackGetter(info, lang.t);
}

// eslint-disable-next-line @typescript-eslint/no-unused-vars
private _onMatchClick: MatchClickCallback = (match: Match): void => { };

/**
* @deprecated
* @deprecated Use `onMatchClick` in the `config` parameter of `viewer.render()`.
* @param callback A callback to be called when a match is clicked.
*/
public set onMatchClicked(callback: MatchClickCallback) {
Expand All @@ -53,6 +60,7 @@ export class BracketsViewer {
const root = document.createDocumentFragment();

this.config = {
customRoundName: config?.customRoundName,
participantOriginPlacement: config?.participantOriginPlacement || 'before',
separatedChildCountLabel: config?.separatedChildCountLabel !== undefined ? config.separatedChildCountLabel : false,
showSlotsOrigin: config?.showSlotsOrigin !== undefined ? config.showSlotsOrigin : true,
Expand Down Expand Up @@ -164,12 +172,19 @@ export class BracketsViewer {

for (const roundMatches of matchesByRound) {
const roundId = roundMatches[0].round_id;
const roundContainer = dom.createRoundContainer(roundId, lang.getRoundName(roundNumber++, 0));

const roundName = this.getRoundName({
roundNumber,
roundCount: 0,
fractionOfFinal: 0,
group: lang.toI18nKey('round_robin'),
}, lang.getRoundName);

const roundContainer = dom.createRoundContainer(roundId, roundName);
for (const match of roundMatches)
roundContainer.append(this.createMatch(match));

groupContainer.append(roundContainer);
roundNumber++;
}

if (this.config.showRankingTable)
Expand Down Expand Up @@ -247,11 +262,11 @@ export class BracketsViewer {
*
* @param container The container to render into.
* @param matchesByRound A list of matches for each round.
* @param roundName A function giving a round's name based on its number.
* @param getRoundName A function giving a round's name based on its number.
* @param bracketType Type of the bracket.
* @param connectFinal Whether to connect the last match of the bracket to the final.
*/
private renderBracket(container: HTMLElement, matchesByRound: Match[][], roundName: RoundName, bracketType: GroupType, connectFinal?: boolean): void {
private renderBracket(container: HTMLElement, matchesByRound: Match[][], getRoundName: RoundNameGetter, bracketType: GroupType, connectFinal?: boolean): void {
const groupId = matchesByRound[0][0].group_id;
const roundCount = matchesByRound.length;
const bracketContainer = dom.createBracketContainer(groupId, lang.getBracketName(this.stage, bracketType));
Expand All @@ -264,10 +279,16 @@ export class BracketsViewer {
for (let roundIndex = 0; roundIndex < matchesByRound.length; roundIndex++) {
const roundId = matchesByRound[roundIndex][0].round_id;
const roundNumber = roundIndex + 1;
const roundContainer = dom.createRoundContainer(roundId, roundName(roundNumber, roundCount));
const roundName = this.getRoundName({
roundNumber,
roundCount,
fractionOfFinal: helpers.getFractionOfFinal(roundNumber, roundCount),
group: lang.toI18nKey(bracketType),
}, getRoundName);

const roundMatches = fromToornament && roundNumber === 1 ? completedMatches : matchesByRound[roundIndex];
const roundContainer = dom.createRoundContainer(roundId, roundName);

const roundMatches = fromToornament && roundNumber === 1 ? completedMatches : matchesByRound[roundIndex];
for (const match of roundMatches)
roundContainer.append(match && this.createBracketMatch(roundNumber, roundCount, match, bracketType, connectFinal) || this.skipBracketMatch());

Expand Down
25 changes: 23 additions & 2 deletions src/types.d.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { Stage, Match, MatchGame, Participant } from 'brackets-model';
import { Stage, Match, MatchGame, Participant, GroupType } from 'brackets-model';
import { CallbackFunction, FormConfiguration } from './form';
import { InMemoryDatabase } from 'brackets-memory-db';
import { BracketsViewer } from './main';
import { BracketsManager } from 'brackets-manager';
import { ToI18nKey, Translator } from './lang';

declare global {
interface Window {
Expand Down Expand Up @@ -49,6 +50,12 @@ export interface Config {
*/
onMatchClick?: MatchClickCallback;

/**
* A function to deeply customize the names of the rounds.
* If you just want to **translate some words**, please use `addLocale()` instead.
*/
customRoundName?: (...args: Parameters<RoundNameGetter>) => ReturnType<RoundNameGetter> | undefined,

/**
* An optional selector to select the root element.
*/
Expand Down Expand Up @@ -100,10 +107,24 @@ export type ConnectionType = 'square' | 'straight' | false;
*/
export type OriginHint = (position: number) => string;

/**
* Info associated to a round in order to name its header.
*/
export interface RoundNameInfo {
group: ToI18nKey<GroupType | 'round_robin'>,
roundNumber: number,
roundCount: number,
/**
* - For elimination stages: `1` = final, `1/2` = semi finals, `1/4` = quarter finals, etc.
* - For round-robin: `0` because there is no final.
*/
fractionOfFinal: number,
}

/**
* A function returning a round name based on its number and the count of rounds.
*/
export type RoundName = (roundNumber: number, roundCount: number) => string;
export type RoundNameGetter = (info: RoundNameInfo, t: Translator) => string;

/**
* A function called when a match is clicked.
Expand Down

0 comments on commit 668aae1

Please sign in to comment.