Permite cambiar skin de scoreboard en tiempo real con misma URL

This commit is contained in:
Pandipipas
2026-02-20 22:21:13 +01:00
parent 3fdcad5cc6
commit 31c1886a48
7 changed files with 100 additions and 17 deletions
+15
View File
@@ -0,0 +1,15 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"additionalProperties": false,
"properties": {
"scoreboardSkin": {
"type": "string",
"default": "scoreboard/main.html"
}
},
"required": ["scoreboardSkin"],
"default": {
"scoreboardSkin": "scoreboard/main.html"
}
}
+1
View File
@@ -12,5 +12,6 @@ const thisBundle = 'scoreko-dev';
export const exampleReplicant = useReplicant<Schemas.ExampleReplicant>('exampleReplicant', thisBundle); export const exampleReplicant = useReplicant<Schemas.ExampleReplicant>('exampleReplicant', thisBundle);
export const playersReplicant = useReplicant<Schemas.Players>('players', thisBundle); export const playersReplicant = useReplicant<Schemas.Players>('players', thisBundle);
export const scoreboardReplicant = useReplicant<Schemas.Scoreboard>('scoreboard', thisBundle); export const scoreboardReplicant = useReplicant<Schemas.Scoreboard>('scoreboard', thisBundle);
export const graphicsSettingsReplicant = useReplicant<Schemas.GraphicsSettings>('graphicsSettings', thisBundle);
export const commentaryReplicant = useReplicant<Schemas.Commentary>('commentary', thisBundle); export const commentaryReplicant = useReplicant<Schemas.Commentary>('commentary', thisBundle);
+42 -15
View File
@@ -1,6 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { useHead } from '@unhead/vue'; import { useHead } from '@unhead/vue';
import { computed, ref, watchEffect } from 'vue'; import { computed, ref, watch, watchEffect } from 'vue';
import { graphicsSettingsReplicant } from '../../../browser_shared/replicants';
import { t } from '../i18n'; import { t } from '../i18n';
defineOptions({ name: 'GraphicsView' }); defineOptions({ name: 'GraphicsView' });
@@ -24,17 +25,14 @@ type GraphicCard = {
useHead(() => ({ title: t('graphicsTitle') })); useHead(() => ({ title: t('graphicsTitle') }));
const graphics = computed<GraphicConfig[]>( const graphics = computed<GraphicConfig[]>(() => bundlePackage.nodecg?.graphics ?? []);
() => bundlePackage.nodecg?.graphics ?? [],
);
const baseUrl = computed(() => { const baseUrl = computed(() => {
const bundleName = bundlePackage.name ?? 'bundle'; const bundleName = bundlePackage.name ?? 'bundle';
return `${window.location.origin}/bundles/${bundleName}/graphics/`; return `${window.location.origin}/bundles/${bundleName}/graphics/`;
}); });
const buildGraphicUrl = (graphic: GraphicConfig) => const buildGraphicUrl = (graphic: GraphicConfig) => `${baseUrl.value}${graphic.file}`;
`${baseUrl.value}${graphic.file}`;
const buildGraphicName = (graphic: GraphicConfig) => { const buildGraphicName = (graphic: GraphicConfig) => {
const cleaned = graphic.file.replace(/\/?main\.html$/i, ''); const cleaned = graphic.file.replace(/\/?main\.html$/i, '');
@@ -49,6 +47,13 @@ const scoreboardGraphics = computed(() =>
graphics.value.filter((graphic) => getGraphicKey(graphic).includes('scoreboard')), graphics.value.filter((graphic) => getGraphicKey(graphic).includes('scoreboard')),
); );
const canonicalScoreboardGraphic = computed(() => {
const explicitDefault = scoreboardGraphics.value.find(
(graphic) => getGraphicKey(graphic) === 'scoreboard',
);
return explicitDefault ?? scoreboardGraphics.value[0];
});
const commentaryGraphic = computed(() => const commentaryGraphic = computed(() =>
graphics.value.find((graphic) => getGraphicKey(graphic).includes('commentary')), graphics.value.find((graphic) => getGraphicKey(graphic).includes('commentary')),
); );
@@ -56,34 +61,56 @@ const commentaryGraphic = computed(() =>
const selectedScoreboardSkin = ref<string>(''); const selectedScoreboardSkin = ref<string>('');
watchEffect(() => { watchEffect(() => {
if (scoreboardGraphics.value.length === 0) { const availableSkins = scoreboardGraphics.value;
if (availableSkins.length === 0) {
selectedScoreboardSkin.value = ''; selectedScoreboardSkin.value = '';
return; return;
} }
const hasCurrentSkin = scoreboardGraphics.value.some( const replicatedSkin = graphicsSettingsReplicant?.data?.scoreboardSkin ?? '';
const hasReplicatedSkin = availableSkins.some((graphic) => graphic.file === replicatedSkin);
if (hasReplicatedSkin && selectedScoreboardSkin.value !== replicatedSkin) {
selectedScoreboardSkin.value = replicatedSkin;
return;
}
const hasCurrentSkin = availableSkins.some(
(graphic) => graphic.file === selectedScoreboardSkin.value, (graphic) => graphic.file === selectedScoreboardSkin.value,
); );
if (!hasCurrentSkin) { if (!hasCurrentSkin) {
selectedScoreboardSkin.value = scoreboardGraphics.value[0]!.file; selectedScoreboardSkin.value = availableSkins[0]!.file;
} }
}); });
const selectedScoreboardGraphic = computed(() => watch(
scoreboardGraphics.value.find( selectedScoreboardSkin,
(graphic) => graphic.file === selectedScoreboardSkin.value, (value) => {
) ?? scoreboardGraphics.value[0], if (!value || !graphicsSettingsReplicant) {
return;
}
if (graphicsSettingsReplicant.data?.scoreboardSkin === value) {
return;
}
graphicsSettingsReplicant.data = {
scoreboardSkin: value,
};
graphicsSettingsReplicant.save();
},
{ immediate: true },
); );
const cards = computed<GraphicCard[]>(() => { const cards = computed<GraphicCard[]>(() => {
const result: GraphicCard[] = []; const result: GraphicCard[] = [];
if (selectedScoreboardGraphic.value) { if (canonicalScoreboardGraphic.value) {
result.push({ result.push({
id: 'scoreboard', id: 'scoreboard',
label: t('graphicsScoreboard'), label: t('graphicsScoreboard'),
graphic: selectedScoreboardGraphic.value, graphic: canonicalScoreboardGraphic.value,
skinOptions: scoreboardGraphics.value.map((graphic) => ({ skinOptions: scoreboardGraphics.value.map((graphic) => ({
label: graphic.title ?? buildGraphicName(graphic), label: graphic.title ?? buildGraphicName(graphic),
value: graphic.file, value: graphic.file,
+15 -1
View File
@@ -1,7 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { useHead } from '@unhead/vue'; import { useHead } from '@unhead/vue';
import { computed, ref, watch } from 'vue'; import { computed, ref, watch } from 'vue';
import { playersReplicant, scoreboardReplicant } from '../../browser_shared/replicants'; import { graphicsSettingsReplicant, playersReplicant, scoreboardReplicant } from '../../browser_shared/replicants';
import { resolveCountryCode } from '../../shared/countries'; import { resolveCountryCode } from '../../shared/countries';
import { getCharactersByGame } from '../../shared/fighting-characters'; import { getCharactersByGame } from '../../shared/fighting-characters';
import type { Schemas } from '../../types'; import type { Schemas } from '../../types';
@@ -15,6 +15,20 @@ const defaultScoreboard: Schemas.Scoreboard = {
const players = computed<Schemas.Players>(() => playersReplicant?.data ?? {}); const players = computed<Schemas.Players>(() => playersReplicant?.data ?? {});
const scoreboard = computed<Schemas.Scoreboard>(() => scoreboardReplicant?.data ?? defaultScoreboard); const scoreboard = computed<Schemas.Scoreboard>(() => scoreboardReplicant?.data ?? defaultScoreboard);
const scoreboardSkin = computed(() => graphicsSettingsReplicant?.data?.scoreboardSkin ?? 'scoreboard-2xko/main.html');
watch(
scoreboardSkin,
(skin) => {
if (skin !== 'scoreboard-2xko/main.html') {
const targetUrl = new URL('../scoreboard/main.html', window.location.href).toString();
if (window.location.href !== targetUrl) {
window.location.replace(targetUrl);
}
}
},
{ immediate: true },
);
const leftName = computed(() => scoreboard.value.leftNameOverride || players.value[scoreboard.value.leftPlayerId]?.gamertag || 'PLAYER 1'); const leftName = computed(() => scoreboard.value.leftNameOverride || players.value[scoreboard.value.leftPlayerId]?.gamertag || 'PLAYER 1');
const rightName = computed(() => scoreboard.value.rightNameOverride || players.value[scoreboard.value.rightPlayerId]?.gamertag || 'PLAYER 2'); const rightName = computed(() => scoreboard.value.rightNameOverride || players.value[scoreboard.value.rightPlayerId]?.gamertag || 'PLAYER 2');
+15 -1
View File
@@ -1,7 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { useHead } from '@unhead/vue'; import { useHead } from '@unhead/vue';
import { computed, nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue'; import { computed, nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue';
import { playersReplicant, scoreboardReplicant } from '../../browser_shared/replicants'; import { graphicsSettingsReplicant, playersReplicant, scoreboardReplicant } from '../../browser_shared/replicants';
import { resolveCountryCode } from '../../shared/countries'; import { resolveCountryCode } from '../../shared/countries';
import type { Schemas } from '../../types'; import type { Schemas } from '../../types';
@@ -26,6 +26,20 @@ const defaultScoreboard: Schemas.Scoreboard = {
const players = computed<Schemas.Players>(() => playersReplicant?.data ?? {}); const players = computed<Schemas.Players>(() => playersReplicant?.data ?? {});
const scoreboard = computed<Schemas.Scoreboard>(() => scoreboardReplicant?.data ?? defaultScoreboard); const scoreboard = computed<Schemas.Scoreboard>(() => scoreboardReplicant?.data ?? defaultScoreboard);
const scoreboardSkin = computed(() => graphicsSettingsReplicant?.data?.scoreboardSkin ?? 'scoreboard/main.html');
watch(
scoreboardSkin,
(skin) => {
if (skin !== 'scoreboard/main.html') {
const targetUrl = new URL('../scoreboard-2xko/main.html', window.location.href).toString();
if (window.location.href !== targetUrl) {
window.location.replace(targetUrl);
}
}
},
{ immediate: true },
);
const leftName = computed(() => { const leftName = computed(() => {
if (scoreboard.value.leftNameOverride) { if (scoreboard.value.leftNameOverride) {
+1
View File
@@ -7,5 +7,6 @@
export type { Commentary } from './schemas/commentary.d.ts'; export type { Commentary } from './schemas/commentary.d.ts';
export type { Configschema } from './schemas/configschema.d.ts'; export type { Configschema } from './schemas/configschema.d.ts';
export type { ExampleReplicant } from './schemas/exampleReplicant.d.ts'; export type { ExampleReplicant } from './schemas/exampleReplicant.d.ts';
export type { GraphicsSettings } from './schemas/graphicsSettings.d.ts';
export type { Players } from './schemas/players.d.ts'; export type { Players } from './schemas/players.d.ts';
export type { Scoreboard } from './schemas/scoreboard.d.ts'; export type { Scoreboard } from './schemas/scoreboard.d.ts';
+11
View File
@@ -0,0 +1,11 @@
/* prettier-ignore */
/* eslint-disable */
/**
* This file was automatically generated by json-schema-to-typescript.
* DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file,
* and run json-schema-to-typescript to regenerate this file.
*/
export interface GraphicsSettings {
scoreboardSkin: string;
}