Skip to content

Commit 668aae1

Browse files
committed
Add customRoundName config property
1 parent 14eb416 commit 668aae1

File tree

7 files changed

+86
-32
lines changed

7 files changed

+86
-32
lines changed

demo/with-api.html

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,14 @@
4545
matchGames: data.match_game,
4646
participants: data.participant,
4747
}, {
48+
// This is optional.
49+
customRoundName: (info, t) => {
50+
// You have a reference to `t` in order to translate things.
51+
// Returning `undefined` will fallback to the default round name in the current language.
52+
53+
if (info.fractionOfFinal === 1 / 2)
54+
return `${t(`abbreviations.${info.group}`)} Semi Finals`
55+
},
4856
selector: '#example',
4957
participantOriginPlacement: 'before',
5058
separatedChildCountLabel: true,

dist/brackets-viewer.min.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/stage-form-creator.min.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
],
3636
"homepage": "https://github.com/Drarig29/brackets-viewer.js#readme",
3737
"dependencies": {
38-
"brackets-manager": "^1.4.2",
38+
"brackets-manager": "^1.5.7",
3939
"brackets-memory-db": "^1.0.4",
4040
"brackets-model": "^1.4.0",
4141
"i18next": "^21.6.14",

src/lang.ts

Lines changed: 23 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import LanguageDetector from 'i18next-browser-languagedetector';
33

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

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

54+
export type Translator = typeof t;
55+
56+
export type ToI18nKey<S extends string> = S extends `${infer A}_${infer B}`
57+
? `${A}-${B}`
58+
: never;
59+
60+
/**
61+
* Converts a type to a valid i18n key.
62+
*
63+
* @param key The key to convert.
64+
*/
65+
export function toI18nKey<S extends `${string}_${string}`>(key: S): ToI18nKey<S> {
66+
return key.replace('_', '-') as ToI18nKey<S>;
67+
}
68+
5469
/**
5570
* Returns an origin hint function based on rounds information.
5671
*
@@ -192,10 +207,6 @@ export function getGroupName(groupNumber: number): string {
192207
return t('common.group-name', { groupNumber });
193208
}
194209

195-
type Replace<S extends string, Search extends string, Replace extends string> = S extends `${infer A}${Search}${infer B}`
196-
? `${A}${Replace}${B}`
197-
: never;
198-
199210
/**
200211
* Returns the name of the bracket.
201212
*
@@ -206,39 +217,32 @@ export function getBracketName(stage: Stage, type: GroupType): string | undefine
206217
switch (type) {
207218
case 'winner_bracket':
208219
case 'loser_bracket':
209-
const key = type.replace('_', '-') as Replace<typeof type, '_', '-'>;
210-
return t(`common.group-name-${key}`, { stage });
220+
return t(`common.group-name-${toI18nKey(type)}`, { stage });
211221
default:
212222
return undefined;
213223
}
214224
}
215225

226+
// eslint-disable-next-line jsdoc/require-param
216227
/**
217228
* Returns the name of a round.
218-
*
219-
* @param roundNumber Number of the round.
220-
* @param roundCount Count of rounds.
221229
*/
222-
export function getRoundName(roundNumber: number, roundCount: number): string {
230+
export function getRoundName({ roundNumber, roundCount }: RoundNameInfo, t: Translator): string {
223231
return roundNumber === roundCount ? t('common.round-name-final') : t('common.round-name', { roundNumber });
224232
}
225233

234+
// eslint-disable-next-line jsdoc/require-param
226235
/**
227236
* Returns the name of a round in the winner bracket of a double elimination stage.
228-
*
229-
* @param roundNumber Number of the round.
230-
* @param roundCount Count of rounds.
231237
*/
232-
export function getWinnerBracketRoundName(roundNumber: number, roundCount: number): string {
238+
export function getWinnerBracketRoundName({ roundNumber, roundCount }: RoundNameInfo, t: Translator): string {
233239
return roundNumber === roundCount ? t('common.round-name-winner-bracket-final') : t('common.round-name-winner-bracket', { roundNumber });
234240
}
235241

242+
// eslint-disable-next-line jsdoc/require-param
236243
/**
237244
* Returns the name of a round in the loser bracket of a double elimination stage.
238-
*
239-
* @param roundNumber Number of the round.
240-
* @param roundCount Count of rounds.
241245
*/
242-
export function getLoserBracketRoundName(roundNumber: number, roundCount: number): string {
246+
export function getLoserBracketRoundName({ roundNumber, roundCount }: RoundNameInfo, t: Translator): string {
243247
return roundNumber === roundCount ? t('common.round-name-loser-bracket-final') : t('common.round-name-loser-bracket', { roundNumber });
244248
}

src/main.ts

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,19 @@ import { splitBy, getRanking, getOriginAbbreviation, findRoot, completeWithBlank
44
import * as dom from './dom';
55
import * as lang from './lang';
66
import { Locale } from './lang';
7+
import { helpers } from 'brackets-manager';
78
import {
89
Config,
910
Connection,
1011
OriginHint,
1112
ParticipantContainers,
1213
RankingItem,
13-
RoundName,
14+
RoundNameGetter,
1415
ViewerData,
1516
ParticipantImage,
1617
Side,
1718
MatchClickCallback,
19+
RoundNameInfo,
1820
} from './types';
1921

2022
export class BracketsViewer {
@@ -29,11 +31,16 @@ export class BracketsViewer {
2931
private skipFirstRound = false;
3032
private alwaysConnectFirstRound = false;
3133

34+
// eslint-disable-next-line jsdoc/require-jsdoc
35+
private getRoundName(info: RoundNameInfo, fallbackGetter: RoundNameGetter): string {
36+
return this.config.customRoundName?.(info, lang.t) || fallbackGetter(info, lang.t);
37+
}
38+
3239
// eslint-disable-next-line @typescript-eslint/no-unused-vars
3340
private _onMatchClick: MatchClickCallback = (match: Match): void => { };
3441

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

5562
this.config = {
63+
customRoundName: config?.customRoundName,
5664
participantOriginPlacement: config?.participantOriginPlacement || 'before',
5765
separatedChildCountLabel: config?.separatedChildCountLabel !== undefined ? config.separatedChildCountLabel : false,
5866
showSlotsOrigin: config?.showSlotsOrigin !== undefined ? config.showSlotsOrigin : true,
@@ -164,12 +172,19 @@ export class BracketsViewer {
164172

165173
for (const roundMatches of matchesByRound) {
166174
const roundId = roundMatches[0].round_id;
167-
const roundContainer = dom.createRoundContainer(roundId, lang.getRoundName(roundNumber++, 0));
168-
175+
const roundName = this.getRoundName({
176+
roundNumber,
177+
roundCount: 0,
178+
fractionOfFinal: 0,
179+
group: lang.toI18nKey('round_robin'),
180+
}, lang.getRoundName);
181+
182+
const roundContainer = dom.createRoundContainer(roundId, roundName);
169183
for (const match of roundMatches)
170184
roundContainer.append(this.createMatch(match));
171185

172186
groupContainer.append(roundContainer);
187+
roundNumber++;
173188
}
174189

175190
if (this.config.showRankingTable)
@@ -247,11 +262,11 @@ export class BracketsViewer {
247262
*
248263
* @param container The container to render into.
249264
* @param matchesByRound A list of matches for each round.
250-
* @param roundName A function giving a round's name based on its number.
265+
* @param getRoundName A function giving a round's name based on its number.
251266
* @param bracketType Type of the bracket.
252267
* @param connectFinal Whether to connect the last match of the bracket to the final.
253268
*/
254-
private renderBracket(container: HTMLElement, matchesByRound: Match[][], roundName: RoundName, bracketType: GroupType, connectFinal?: boolean): void {
269+
private renderBracket(container: HTMLElement, matchesByRound: Match[][], getRoundName: RoundNameGetter, bracketType: GroupType, connectFinal?: boolean): void {
255270
const groupId = matchesByRound[0][0].group_id;
256271
const roundCount = matchesByRound.length;
257272
const bracketContainer = dom.createBracketContainer(groupId, lang.getBracketName(this.stage, bracketType));
@@ -264,10 +279,16 @@ export class BracketsViewer {
264279
for (let roundIndex = 0; roundIndex < matchesByRound.length; roundIndex++) {
265280
const roundId = matchesByRound[roundIndex][0].round_id;
266281
const roundNumber = roundIndex + 1;
267-
const roundContainer = dom.createRoundContainer(roundId, roundName(roundNumber, roundCount));
282+
const roundName = this.getRoundName({
283+
roundNumber,
284+
roundCount,
285+
fractionOfFinal: helpers.getFractionOfFinal(roundNumber, roundCount),
286+
group: lang.toI18nKey(bracketType),
287+
}, getRoundName);
268288

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

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

src/types.d.ts

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
import { Stage, Match, MatchGame, Participant } from 'brackets-model';
1+
import { Stage, Match, MatchGame, Participant, GroupType } from 'brackets-model';
22
import { CallbackFunction, FormConfiguration } from './form';
33
import { InMemoryDatabase } from 'brackets-memory-db';
44
import { BracketsViewer } from './main';
55
import { BracketsManager } from 'brackets-manager';
6+
import { ToI18nKey, Translator } from './lang';
67

78
declare global {
89
interface Window {
@@ -49,6 +50,12 @@ export interface Config {
4950
*/
5051
onMatchClick?: MatchClickCallback;
5152

53+
/**
54+
* A function to deeply customize the names of the rounds.
55+
* If you just want to **translate some words**, please use `addLocale()` instead.
56+
*/
57+
customRoundName?: (...args: Parameters<RoundNameGetter>) => ReturnType<RoundNameGetter> | undefined,
58+
5259
/**
5360
* An optional selector to select the root element.
5461
*/
@@ -100,10 +107,24 @@ export type ConnectionType = 'square' | 'straight' | false;
100107
*/
101108
export type OriginHint = (position: number) => string;
102109

110+
/**
111+
* Info associated to a round in order to name its header.
112+
*/
113+
export interface RoundNameInfo {
114+
group: ToI18nKey<GroupType | 'round_robin'>,
115+
roundNumber: number,
116+
roundCount: number,
117+
/**
118+
* - For elimination stages: `1` = final, `1/2` = semi finals, `1/4` = quarter finals, etc.
119+
* - For round-robin: `0` because there is no final.
120+
*/
121+
fractionOfFinal: number,
122+
}
123+
103124
/**
104125
* A function returning a round name based on its number and the count of rounds.
105126
*/
106-
export type RoundName = (roundNumber: number, roundCount: number) => string;
127+
export type RoundNameGetter = (info: RoundNameInfo, t: Translator) => string;
107128

108129
/**
109130
* A function called when a match is clicked.

0 commit comments

Comments
 (0)