Add commentary panel and Tekken-inspired OBS overlay

This commit is contained in:
Pandipipas
2026-02-12 00:13:18 +01:00
parent 6d28cfb87e
commit 3ee36c03df
11 changed files with 419 additions and 8 deletions
@@ -0,0 +1,77 @@
<script setup lang="ts">
import { useCommentaryStore } from '../stores/commentary';
const commentaryStore = useCommentaryStore();
</script>
<template>
<div class="commentary-panel">
<div class="row items-center q-mb-md">
<div class="text-h4">
Commentary
</div>
<QSpace />
<QBtn
color="secondary"
outline
icon="swap_horiz"
label="Intercambiar lados"
@click="commentaryStore.swapCommentators"
/>
</div>
<div class="row q-col-gutter-lg">
<div class="col-12 col-md-6">
<QCard
flat
bordered
>
<QCardSection>
<div class="text-subtitle1 text-weight-bold">
Left side
</div>
</QCardSection>
<QSeparator />
<QCardSection>
<QInput
v-model="commentaryStore.leftCommentator"
label="Commentator"
dense
outlined
/>
</QCardSection>
</QCard>
</div>
<div class="col-12 col-md-6">
<QCard
flat
bordered
>
<QCardSection>
<div class="text-subtitle1 text-weight-bold">
Right side
</div>
</QCardSection>
<QSeparator />
<QCardSection>
<QInput
v-model="commentaryStore.rightCommentator"
label="Commentator"
dense
outlined
/>
</QCardSection>
</QCard>
</div>
</div>
</div>
</template>
<style scoped>
.commentary-panel {
display: flex;
flex-direction: column;
gap: 16px;
}
</style>
@@ -0,0 +1,84 @@
import { defineStore } from 'pinia';
import { computed, ref, watch } from 'vue';
import { commentaryReplicant } from '../../../browser_shared/replicants';
import type { Schemas } from '../../../types';
type Commentary = Schemas.Commentary;
const defaultCommentary: Commentary = {
leftCommentator: '',
rightCommentator: '',
};
const normalizeCommentary = (input: unknown): Commentary => {
const candidate = typeof input === 'object' && input !== null ? (input as Record<string, unknown>) : {};
return {
leftCommentator: typeof candidate.leftCommentator === 'string' ? candidate.leftCommentator : '',
rightCommentator: typeof candidate.rightCommentator === 'string' ? candidate.rightCommentator : '',
};
};
export const useCommentaryStore = defineStore('commentary', () => {
const commentary = ref<Commentary>({ ...defaultCommentary });
const replicant = commentaryReplicant;
const isApplyingReplicant = ref(false);
watch(
() => replicant?.data,
(value) => {
if (!value) {
return;
}
isApplyingReplicant.value = true;
commentary.value = normalizeCommentary(value);
isApplyingReplicant.value = false;
},
{ deep: true, immediate: true },
);
watch(
commentary,
(value) => {
if (isApplyingReplicant.value || !replicant) {
return;
}
replicant.data = normalizeCommentary(value);
replicant.save();
},
{ deep: true, flush: 'sync' },
);
const leftCommentator = computed({
get: () => commentary.value.leftCommentator,
set: (value: string) => {
commentary.value = {
...commentary.value,
leftCommentator: value,
};
},
});
const rightCommentator = computed({
get: () => commentary.value.rightCommentator,
set: (value: string) => {
commentary.value = {
...commentary.value,
rightCommentator: value,
};
},
});
const swapCommentators = () => {
commentary.value = {
leftCommentator: commentary.value.rightCommentator,
rightCommentator: commentary.value.leftCommentator,
};
};
return {
commentary,
leftCommentator,
rightCommentator,
swapCommentators,
};
});
+20 -8
View File
@@ -1,6 +1,7 @@
<script setup lang="ts">
import { useHead } from '@unhead/vue';
import BracketPanel from '../components/BracketPanel.vue';
import CommentaryPanel from '../components/CommentaryPanel.vue';
import ScoreboardPanel from '../components/ScoreboardPanel.vue';
defineOptions({ name: 'DashboardView' });
@@ -12,14 +13,25 @@ useHead({ title: 'Dashboard' });
<QPage class="q-pa-lg">
<div class="row q-col-gutter-lg items-start dashboard-panels q-mt-lg">
<div class="col-12 col-lg-6">
<QCard
bordered
class="dashboard-panel-card"
>
<QCardSection class="dashboard-panel-content">
<ScoreboardPanel />
</QCardSection>
</QCard>
<div class="column q-gutter-lg">
<QCard
bordered
class="dashboard-panel-card"
>
<QCardSection class="dashboard-panel-content">
<ScoreboardPanel />
</QCardSection>
</QCard>
<QCard
bordered
class="dashboard-panel-card"
>
<QCardSection class="dashboard-panel-content">
<CommentaryPanel />
</QCardSection>
</QCard>
</div>
</div>
<div class="col-12 col-lg-6">
<QCard