Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2,294 changes: 1,341 additions & 953 deletions package-lock.json

Large diffs are not rendered by default.

40 changes: 20 additions & 20 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,32 +22,32 @@
"dependencies": {
"lodash": "^4.17.23",
"lodash-es": "^4.17.23",
"pinia": "^3.0.3",
"qrcode.vue": "^3.6.0",
"vue": "^3.5.22",
"vue-router": "^4.5.1"
"pinia": "^3.0.4",
"qrcode.vue": "^3.8.0",
"vue": "^3.5.29",
"vue-router": "^5.0.3"
},
"devDependencies": {
"@types/lodash-es": "^4.17.12",
"@types/node": "^24.7.0",
"@vitejs/plugin-vue": "^6.0.1",
"@types/node": "^24.11.0",
"@vitejs/plugin-vue": "^6.0.4",
"@vitest/coverage-v8": "4.0.18",
"@vitest/ui": "^4.0.13",
"@vitest/ui": "^4.0.18",
"@vue/eslint-config-prettier": "^10.2.0",
"@vue/eslint-config-typescript": "^14.6.0",
"@vue/tsconfig": "^0.8.1",
"eslint": "^9.39.1",
"eslint-plugin-oxlint": "^1.20.0",
"eslint-plugin-vue": "^10.5.0",
"happy-dom": "^20.5.0",
"@vue/eslint-config-typescript": "^14.7.0",
"@vue/tsconfig": "^0.9.0",
"eslint": "^9.39.3",
"eslint-plugin-oxlint": "^1.51.0",
"eslint-plugin-vue": "^10.8.0",
"happy-dom": "^20.8.3",
"jiti": "^2.6.1",
"npm-run-all2": "^7.0.2",
"oxlint": "^1.20.0",
"prettier": "^3.6.2",
"npm-run-all2": "^8.0.4",
"oxlint": "^1.51.0",
"prettier": "^3.8.1",
"typescript": "~5.9.3",
"vite": "^7.1.12",
"vite-plugin-vue-devtools": "^8.0.2",
"vite": "^7.3.1",
"vite-plugin-vue-devtools": "^8.0.7",
"vitest": "^4.0.18",
"vue-tsc": "^3.1.1"
"vue-tsc": "^3.2.5"
}
}
}
2 changes: 1 addition & 1 deletion src/assets/css/design/colors.css
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@

--color-surface: #1d1d1d;
--color-surface-hover: rgb(37, 37, 37);
--color-background: #050505;
--color-background: #0f0f0f;

--color-border: #282828;

Expand Down
5 changes: 4 additions & 1 deletion src/components/DropdownSelector.vue
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import type { Option, ActionOption } from "@/types/common";
const props = defineProps<{
modelValue: string;
options: string[] | Option[];
selectedValueComponent?: string;
}>();

const emit = defineEmits<(e: "update:modelValue", value: string) => void>();
Expand Down Expand Up @@ -76,7 +77,9 @@ const actionOptions = computed<ActionOption[]>(() => {
<template #activator>
<div class="dropdown__selected">
<div class="flex flex-row gap-2">
<strong>{{ selectedValueLabel }}</strong>
<component :is="selectedValueComponent || 'strong'">{{
selectedValueLabel
}}</component>
</div>
<ion-icon
name="chevron-down"
Expand Down
19 changes: 16 additions & 3 deletions src/components/TabSelector.vue
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import type { Option } from "@/types/common";
const props = defineProps<{
modelValue: string;
options: string[] | Option[];
showNavigation?: boolean;
selectedValueComponent?: string;
}>();

const emit = defineEmits<(e: "update:modelValue", value: string) => void>();
Expand Down Expand Up @@ -40,7 +42,10 @@ const index = computed({
</script>
<template>
<div class="tab-selector">
<span class="navigation-option">
<span
class="navigation-option"
v-if="props.showNavigation"
>
<button
class="ghost"
@click="index--"
Expand All @@ -53,9 +58,13 @@ const index = computed({
<DropdownSelector
v-model="selectedValue"
:options="props.options"
:selected-value-component="selectedValueComponent"
/>
</span>
<span class="navigation-option">
<span
class="navigation-option"
v-if="props.showNavigation"
>
<button
class="ghost"
@click="index++"
Expand All @@ -71,8 +80,12 @@ const index = computed({
.tab-selector {
display: flex;
align-items: center;
justify-content: space-between;
justify-content: center;
margin: 0 1rem;

&:has(.navigation-option) {
justify-content: space-between;
}
}

.navigation-option button {
Expand Down
32 changes: 27 additions & 5 deletions src/components/TournamentLive.vue
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { updateKnockoutMatches } from "@/helpers/matchplan/knockoutPhase";
import GroupedTournamentList from "./GroupedTournamentList.vue";
import { tournamentRichMatches } from "@/helpers/matches";
import { adjustStartTimes } from "@/helpers/matchplan/common";
import TabSelector from "./TabSelector.vue";

const props = defineProps<{
tournament: Tournament;
Expand Down Expand Up @@ -141,6 +142,10 @@ onUnmounted(() => {
clearInterval(updateCountdownTask);
});

const BREAK_VIEW_OPTIONS = ["Results", "Upcoming"];
type BreakViewOption = (typeof BREAK_VIEW_OPTIONS)[number];
const breakView = ref<BreakViewOption>("Results");

const latestResults = computed<RichMatch[]>(() => {
if (previousStartTime.value) {
return groupedByTime.value[previousStartTime.value] || [];
Expand All @@ -154,6 +159,7 @@ const teamMatchesRouteName = computed(() => {
});

const adjustAndSkip = () => {
breakView.value = "Results";
const veryNextRound = new Date(nextStartTime.value!);
const rawTournament = toRaw(tournament.value);
updateKnockoutMatches(rawTournament);
Expand All @@ -178,7 +184,7 @@ const proceed = () => {
}
groupedByTime.value[key] = matches;
updateKnockoutMatches(tournament.value);
tournamentStore.share(tournament.value);
tournamentStore.push(tournament.value);
};

const adjustAndSkipText = computed(() => {
Expand Down Expand Up @@ -250,8 +256,15 @@ const currentPhase = computed(() => {
/>
</template>
<template v-else-if="latestResults.length">
<h3>Results</h3>
<TabSelector
v-if="nextStartTime && groupedByTime[nextStartTime]"
v-model="breakView"
:options="BREAK_VIEW_OPTIONS"
selected-value-component="h3"
/>
<h3 v-else>Latest results</h3>
<GroupedTournamentList
v-if="breakView == 'Results'"
v-model="latestResults"
:tournament="tournament"
:readonly="readonly"
Expand All @@ -260,6 +273,16 @@ const currentPhase = computed(() => {
show-phase
show-round
/>
<GroupedTournamentList
v-else
v-model="groupedByTime[nextStartTime!]!"
:tournament="tournament"
:readonly="readonly"
@update:model-value="onChanged"
show-group
show-phase
show-round
/>
</template>
<template v-else>
<!-- start of tournament -->
Expand All @@ -276,11 +299,11 @@ const currentPhase = computed(() => {
</template>

<!--finish round, proceed to next round-->
<template v-if="!readonly && nextRoundCountdown">
<template v-if="!readonly && (nextRoundCountdown || currentStartTime)">
<div
class="actions"
:class="{ center: !previousStartTime && !currentStartTime }"
v-if="nextRoundCountdown && !readonly"
v-if="(nextRoundCountdown || currentStartTime) && !readonly"
>
<button
class="secondary"
Expand Down Expand Up @@ -351,7 +374,6 @@ const currentPhase = computed(() => {
flex-direction: row;
align-items: center;
justify-content: space-between;
margin-bottom: 1em;
padding: 0.5em 1em;
border-bottom: 1px solid var(--color-border);

Expand Down
1 change: 1 addition & 0 deletions src/components/TournamentMatches.vue
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,7 @@ onMounted(() => {
<TabSelector
v-model="selectedGroup"
:options="tabOptions"
show-navigation
/>
<div class="round">
<GroupedTournamentList
Expand Down
53 changes: 53 additions & 0 deletions src/stores/tournaments.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ describe("Tournaments Store", () => {
// Clear localStorage before each test
localStorage.clear();

// Clear all mocks before each test
vi.clearAllMocks();

// Mock crypto.randomUUID
let counter = 0;
vi.stubGlobal("crypto", {
Expand Down Expand Up @@ -424,6 +427,56 @@ describe("Tournaments Store", () => {
});
});

describe("push method", () => {
it("should not push a tournament if no remote is set", async () => {
const store = useTournamentsStore();
const tournament: Tournament = {
id: "no-remote",
version: 3,
name: "No Remote",
teams: [],
phases: [],
config: mockConfig,
};
store.add(tournament);

const mockedPush = vi.mocked(push);

const result = await store.push(tournament);
expect(result).toBe(false);
expect(mockedPush).not.toHaveBeenCalled();
});

it("should push a tournament if remote is set", async () => {
const store = useTournamentsStore();
const tournament: Tournament = {
id: "has-remote",
version: 3,
name: "Has Remote",
teams: [],
phases: [],
config: mockConfig,
remote: [{ identifier: "remote-id" }],
};
store.add(tournament);

const mockedPush = vi.mocked(push);
mockedPush.mockResolvedValue({
author: "test-author",
link: "https://example.com/share",
tournament: {
...tournament,
remote: [{ identifier: "remote-1" }],
},
date: new Date(),
});

const result = await store.push(tournament);
expect(result).toBe(true);
expect(mockedPush).toHaveBeenCalledWith(tournament, false);
});
});

describe("addFromUpload method", () => {
it("should add uploaded tournaments to the store", async () => {
const store = useTournamentsStore();
Expand Down
9 changes: 9 additions & 0 deletions src/stores/tournaments.ts
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,14 @@ export const useTournamentsStore = defineStore("tournaments", () => {
tournaments.forEach((x) => add(x));
};

const pushToRemote = async (tournament: Tournament) => {
if (!tournament.remote || tournament.remote.length === 0) {
return false;
}
await share(tournament);
return true;
};

const pullFromRemote = async (options: { tournament?: Tournament; remote?: IRemote }) => {
const { tournament, remote } = options;

Expand Down Expand Up @@ -197,6 +205,7 @@ export const useTournamentsStore = defineStore("tournaments", () => {
download,
addFromUpload,
pull: pullFromRemote,
push: pushToRemote,
disableThrottling: () => {
console.warn("Disabling throttling for tournaments store");
throttlingEnabled.value = false;
Expand Down