This commit is contained in:
2025-07-06 20:54:40 +08:00
parent f665eaab54
commit ced488a958
4076 changed files with 1108355 additions and 0 deletions

69
node_modules/vue-tournament-bracket/src/App.vue generated vendored Normal file
View File

@@ -0,0 +1,69 @@
<template>
<bracket :rounds="rounds">
<template #player="{ player }">
{{ player.name }}
</template>
</bracket>
</template>
<script>
import Bracket from "./Bracket";
const rounds = [
//Quarter
{
games: [
{
player1: { id: "1", name: "Competitor 1", winner: true },
player2: { id: "2", name: "Competitor 2", winner: false }
},
{
player1: { id: "3", name: "Competitor 3", winner: false },
player2: { id: "4", name: "Competitor 4", winner: true }
},
{
player1: { id: "5", name: "Competitor 5", winner: true },
player2: { id: "6", name: "Competitor 6", winner: false }
},
{
player1: { id: "7", name: "Competitor 7", winner: false },
player2: { id: "8", name: "Competitor 8", winner: true }
}
]
},
//Semi
{
games: [
{
player1: { id: "1", name: "Competitor 1", winner: false },
player2: { id: "4", name: "Competitor 4", winner: true }
},
{
player1: { id: "5", name: "Competitor 5", winner: false },
player2: { id: "8", name: "Competitor 8", winner: true }
}
]
},
//Final
{
games: [
{
player1: { id: "4", name: "Competitor 4", winner: false },
player2: { id: "8", name: "Competitor 8", winner: true }
}
]
}
];
export default {
name: "app",
components: {
Bracket
},
data() {
return {
rounds: rounds
};
}
};
</script>

58
node_modules/vue-tournament-bracket/src/Bracket.vue generated vendored Normal file
View File

@@ -0,0 +1,58 @@
<template>
<div class="vtb-wrapper" v-if="recursiveBracket">
<bracket-node
:bracket-node="recursiveBracket"
@onSelectedPlayer="highlightPlayer"
@onDeselectedPlayer="unhighlightPlayer"
:highlighted-player-id="highlightedPlayerId"
>
<template #player="{ player }">
<slot name="player" :player="player" />
</template>
<template #player-extension-bottom="{ match }">
<slot name="player-extension-bottom" :match="match" />
</template>
</bracket-node>
</div>
</template>
<script>
import BracketNode from "./components/BracketNode";
import recursiveBracket from "./components/recursiveBracket";
export default {
name: "bracket",
components: {
"bracket-node": BracketNode,
},
props: ["rounds", "flatTree"],
data() {
return {
highlightedPlayerId: null,
};
},
computed: {
recursiveBracket() {
if (this.flatTree) {
return recursiveBracket.transformFlatTree(this.flatTree);
}
return recursiveBracket.transform(this.rounds);
},
},
methods: {
highlightPlayer(id) {
this.highlightedPlayerId = id;
},
unhighlightPlayer() {
this.highlightedPlayerId = null;
},
},
};
</script>
<style>
.vtb-wrapper {
display: flex;
}
</style>

View File

@@ -0,0 +1,201 @@
<template>
<div class="vtb-item" v-if="playersArePresent">
<div :class="getBracketNodeClass(bracketNode)">
<game-players
:bracket-node="bracketNode"
:highlighted-player-id="highlightedPlayerId"
@onSelectedPlayer="highlightPlayer"
@onDeselectedPlayer="unhighlightPlayer"
>
<template #player="{ player }">
<slot name="player" :player="player" />
</template>
<template #player-extension-bottom="{ match }">
<slot name="player-extension-bottom" :match="match" />
</template>
</game-players>
</div>
<div v-if="bracketNode.games[0] || bracketNode.games[1]" class="vtb-item-children">
<div class="vtb-item-child" v-if="bracketNode.games[0]">
<bracket-node
:bracket-node="bracketNode.games[0]"
:highlighted-player-id="highlightedPlayerId"
@onSelectedPlayer="highlightPlayer"
@onDeselectedPlayer="unhighlightPlayer"
>
<template #player="{ player }">
<slot name="player" :player="player" />
</template>
<template #player-extension-bottom="{ match }">
<slot name="player-extension-bottom" :match="match" />
</template>
</bracket-node>
</div>
<div class="vtb-item-child" v-if="bracketNode.games[1]">
<bracket-node
:bracket-node="bracketNode.games[1]"
:highlighted-player-id="highlightedPlayerId"
@onSelectedPlayer="highlightPlayer"
@onDeselectedPlayer="unhighlightPlayer"
>
<template #player="{ player }">
<slot name="player" :player="player" />
</template>
<template #player-extension-bottom="{ match }">
<slot name="player-extension-bottom" :match="match" />
</template>
</bracket-node>
</div>
</div>
</div>
</template>
<script>
import GamePlayers from "./GamePlayers";
export default {
name: "bracket-node",
components: { GamePlayers },
props: ["bracketNode", "highlightedPlayerId"],
computed: {
playersArePresent() {
return this.bracketNode.player1 && this.bracketNode.player1;
},
},
methods: {
getBracketNodeClass(bracketNode) {
if (bracketNode.games[0] || bracketNode.games[1]) {
return "vtb-item-parent";
}
if (bracketNode.hasParent) {
return "vtb-item-child";
}
return "";
},
getPlayerClass(player) {
if (player.winner === null || player.winner === undefined) {
return "";
}
let clazz = player.winner ? "winner" : "defeated";
if (this.highlightedPlayerId === player.id) {
clazz += " highlight";
}
return clazz;
},
highlightPlayer(playerId) {
this.$emit("onSelectedPlayer", playerId);
},
unhighlightPlayer() {
this.$emit("onDeselectedPlayer");
},
},
};
</script>
<style>
.vtb-item {
display: flex;
flex-direction: row-reverse;
}
.vtb-item p {
padding: 20px;
margin: 0;
background-color: #999999;
}
.vtb-item-parent {
position: relative;
margin-left: 50px;
display: flex;
align-items: center;
}
.vtb-item-players {
flex-direction: column;
background-color: #999999;
margin: 0;
color: white;
}
.vtb-item-players .vtb-player {
padding: 10px;
}
.vtb-item-players .winner {
background-color: darkgreen;
}
.vtb-item-players .defeated {
background-color: firebrick;
}
.vtb-item-players .winner.highlight {
background-color: darkseagreen;
}
.vtb-item-players .defeated.highlight {
background-color: indianred;
}
.vtb-item-parent:after {
position: absolute;
content: "";
width: 25px;
height: 2px;
left: 0;
top: 50%;
background-color: gray;
transform: translateX(-100%);
}
.vtb-item-children {
display: flex;
flex-direction: column;
justify-content: center;
}
.vtb-item-child {
display: flex;
align-items: flex-start;
justify-content: flex-end;
margin-top: 10px;
margin-bottom: 10px;
position: relative;
}
.vtb-item-child:before {
content: "";
position: absolute;
background-color: gray;
right: 0;
top: 50%;
transform: translateX(100%);
width: 25px;
height: 2px;
}
.vtb-item-child:after {
content: "";
position: absolute;
background-color: gray;
right: -25px;
height: calc(50% + 22px);
width: 2px;
top: 50%;
}
.vtb-item-child:last-child:after {
transform: translateY(-100%);
}
.vtb-item-child:only-child:after {
display: none;
}
</style>

View File

@@ -0,0 +1,55 @@
<template>
<div class="vtb-item-players">
<div>
<div
:class="['vtb-player', 'vtb-player1', getPlayerClass(bracketNode.player1)]"
@mouseover="highlightPlayer(bracketNode.player1.id)"
@mouseleave="unhighlightPlayer"
>
<slot :player="bracketNode.player1" name="player" />
</div>
<div
:class="['vtb-player', 'vtb-player2', getPlayerClass(bracketNode.player2)]"
@mouseover="highlightPlayer(bracketNode.player2.id)"
@mouseleave="unhighlightPlayer"
>
<slot :player="bracketNode.player2" name="player" />
</div>
</div>
<slot name="player-extension-bottom" :match="matchProperties" />
</div>
</template>
<script>
export default {
name: "game-players",
props: ["bracketNode", "highlightedPlayerId"],
computed: {
matchProperties() {
return Object.assign({}, this.bracketNode, { games: undefined, hasParent: undefined });
}
},
methods: {
getPlayerClass(player) {
if (player.winner === null || player.winner === undefined) {
return "";
}
let clazz = player.winner ? "winner" : "defeated";
if (this.highlightedPlayerId === player.id) {
clazz += " highlight";
}
return clazz;
},
highlightPlayer(playerId) {
this.$emit("onSelectedPlayer", playerId);
},
unhighlightPlayer() {
this.$emit("onDeselectedPlayer");
}
}
};
</script>

View File

@@ -0,0 +1,86 @@
module.exports = {
transform(rounds) {
if (!rounds) {
return null;
}
const totalRounds = rounds.length;
let currentRound = [];
let previousRound = [];
for (let i = 0; i < totalRounds; i++) {
currentRound = rounds[i].games.map((game) => {
return {
...game,
title: "round " + i,
games: [],
hasParent: !!rounds[i + 1],
};
});
if (previousRound.length === 0) {
previousRound = currentRound;
continue;
}
for (let j = 0; j < previousRound.length; j++) {
const matchForCurrentGame = Math.floor(j / 2);
currentRound[matchForCurrentGame].games.push(previousRound[j]);
}
previousRound = currentRound;
}
return currentRound[0] || null;
},
transformFlatTree(games) {
const mapOfGamesPerParent = {};
let root = null;
games.forEach((game) => {
if (!game.next && !root) {
root = game;
return;
}
if (!mapOfGamesPerParent[game.next]) {
mapOfGamesPerParent[game.next] = [];
}
mapOfGamesPerParent[game.next].push(game);
});
const tree = {
...root,
title: "round",
games: [],
hasParent: false,
};
return constructTree(tree, mapOfGamesPerParent, Object.keys(mapOfGamesPerParent).length);
},
};
function constructTree(tree, mapOfChildren, processedRound) {
const totalChildren = mapOfChildren[tree.id] || [];
tree.title = "round " + processedRound;
for (let i = 0; i < totalChildren.length; i++) {
const childGame = totalChildren[i];
const treeChild = {
...childGame,
title: `round ${[processedRound]}`,
hasParent: true,
games: [],
};
constructTree(treeChild, mapOfChildren, processedRound - 1);
tree.games.push(treeChild);
}
return tree;
}

4
node_modules/vue-tournament-bracket/src/main.js generated vendored Normal file
View File

@@ -0,0 +1,4 @@
import { createApp } from "vue";
import App from "./App.vue";
createApp(App).mount("#app");