mirror of
https://github.com/Pandipipas/scoreko-dev.git
synced 2026-06-06 03:32:06 +00:00
Refina la vista de Graphics con selector de skin
This commit is contained in:
@@ -46,8 +46,9 @@ type Translations = {
|
||||
graphicsNoConfigured: string;
|
||||
graphicsCopyUrl: string;
|
||||
graphicsDragObs: string;
|
||||
graphicsOverlayPreview: string;
|
||||
graphicsPreviewTitle: string;
|
||||
graphicsScoreboard: string;
|
||||
graphicsCommentary: string;
|
||||
graphicsSkinLabel: string;
|
||||
commentaryTitle: string;
|
||||
commentaryCommentator1: string;
|
||||
commentaryCommentator2: string;
|
||||
@@ -120,8 +121,9 @@ const messages: Record<Locale, Translations> = {
|
||||
graphicsNoConfigured: 'There are no graphics configured in this bundle.',
|
||||
graphicsCopyUrl: 'Copy URL',
|
||||
graphicsDragObs: 'Drag into OBS',
|
||||
graphicsOverlayPreview: 'Overlay preview (real)',
|
||||
graphicsPreviewTitle: 'Graphic preview',
|
||||
graphicsScoreboard: 'Scoreboard',
|
||||
graphicsCommentary: 'Commentary',
|
||||
graphicsSkinLabel: 'Skin',
|
||||
commentaryTitle: 'Commentary',
|
||||
commentaryCommentator1: 'Commentator #1',
|
||||
commentaryCommentator2: 'Commentator #2',
|
||||
@@ -190,8 +192,9 @@ const messages: Record<Locale, Translations> = {
|
||||
graphicsNoConfigured: 'No hay gráficos configurados en este bundle.',
|
||||
graphicsCopyUrl: 'Copiar URL',
|
||||
graphicsDragObs: 'Arrastrar a OBS',
|
||||
graphicsOverlayPreview: 'Vista previa del overlay (real)',
|
||||
graphicsPreviewTitle: 'Vista previa del gráfico',
|
||||
graphicsScoreboard: 'Scoreboard',
|
||||
graphicsCommentary: 'Comentario',
|
||||
graphicsSkinLabel: 'Skin',
|
||||
commentaryTitle: 'Comentario',
|
||||
commentaryCommentator1: 'Comentarista #1',
|
||||
commentaryCommentator2: 'Comentarista #2',
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { useHead } from '@unhead/vue';
|
||||
import { computed } from 'vue';
|
||||
import { computed, ref, watchEffect } from 'vue';
|
||||
import { t } from '../i18n';
|
||||
|
||||
defineOptions({ name: 'GraphicsView' });
|
||||
@@ -8,12 +8,19 @@ defineOptions({ name: 'GraphicsView' });
|
||||
import bundlePackage from '../../../../package.json';
|
||||
|
||||
type GraphicConfig = {
|
||||
name?: string;
|
||||
title?: string;
|
||||
file: string;
|
||||
width?: number;
|
||||
height?: number;
|
||||
};
|
||||
|
||||
type PreviewKind = 'scoreboard' | 'commentary' | null;
|
||||
type GraphicCard = {
|
||||
id: 'scoreboard' | 'commentary';
|
||||
label: string;
|
||||
graphic: GraphicConfig;
|
||||
skinOptions?: Array<{ label: string; value: string }>;
|
||||
};
|
||||
|
||||
useHead(() => ({ title: t('graphicsTitle') }));
|
||||
|
||||
@@ -35,6 +42,66 @@ const buildGraphicName = (graphic: GraphicConfig) => {
|
||||
return parts.at(-1) ?? graphic.file;
|
||||
};
|
||||
|
||||
const getGraphicKey = (graphic: GraphicConfig) =>
|
||||
(graphic.name ?? buildGraphicName(graphic)).toLowerCase();
|
||||
|
||||
const scoreboardGraphics = computed(() =>
|
||||
graphics.value.filter((graphic) => getGraphicKey(graphic).includes('scoreboard')),
|
||||
);
|
||||
|
||||
const commentaryGraphic = computed(() =>
|
||||
graphics.value.find((graphic) => getGraphicKey(graphic).includes('commentary')),
|
||||
);
|
||||
|
||||
const selectedScoreboardSkin = ref<string>('');
|
||||
|
||||
watchEffect(() => {
|
||||
if (scoreboardGraphics.value.length === 0) {
|
||||
selectedScoreboardSkin.value = '';
|
||||
return;
|
||||
}
|
||||
|
||||
const hasCurrentSkin = scoreboardGraphics.value.some(
|
||||
(graphic) => graphic.file === selectedScoreboardSkin.value,
|
||||
);
|
||||
|
||||
if (!hasCurrentSkin) {
|
||||
selectedScoreboardSkin.value = scoreboardGraphics.value[0]!.file;
|
||||
}
|
||||
});
|
||||
|
||||
const selectedScoreboardGraphic = computed(() =>
|
||||
scoreboardGraphics.value.find(
|
||||
(graphic) => graphic.file === selectedScoreboardSkin.value,
|
||||
) ?? scoreboardGraphics.value[0],
|
||||
);
|
||||
|
||||
const cards = computed<GraphicCard[]>(() => {
|
||||
const result: GraphicCard[] = [];
|
||||
|
||||
if (selectedScoreboardGraphic.value) {
|
||||
result.push({
|
||||
id: 'scoreboard',
|
||||
label: t('graphicsScoreboard'),
|
||||
graphic: selectedScoreboardGraphic.value,
|
||||
skinOptions: scoreboardGraphics.value.map((graphic) => ({
|
||||
label: graphic.title ?? buildGraphicName(graphic),
|
||||
value: graphic.file,
|
||||
})),
|
||||
});
|
||||
}
|
||||
|
||||
if (commentaryGraphic.value) {
|
||||
result.push({
|
||||
id: 'commentary',
|
||||
label: t('graphicsCommentary'),
|
||||
graphic: commentaryGraphic.value,
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
});
|
||||
|
||||
const copyUrl = async (graphic: GraphicConfig) => {
|
||||
const url = buildGraphicUrl(graphic);
|
||||
if (navigator.clipboard?.writeText) {
|
||||
@@ -50,20 +117,6 @@ const copyUrl = async (graphic: GraphicConfig) => {
|
||||
document.body.removeChild(input);
|
||||
};
|
||||
|
||||
const getPreviewKind = (graphic: GraphicConfig): PreviewKind => {
|
||||
const name = buildGraphicName(graphic).toLowerCase();
|
||||
|
||||
if (name.includes('scoreboard')) {
|
||||
return 'scoreboard';
|
||||
}
|
||||
|
||||
if (name.includes('commentary')) {
|
||||
return 'commentary';
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
const onDragStart = (event: DragEvent, graphic: GraphicConfig) => {
|
||||
const url = buildGraphicUrl(graphic);
|
||||
const name = buildGraphicName(graphic);
|
||||
@@ -92,7 +145,7 @@ const onDragStart = (event: DragEvent, graphic: GraphicConfig) => {
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="graphics.length === 0"
|
||||
v-if="cards.length === 0"
|
||||
class="text-body2 text-grey-5"
|
||||
>
|
||||
{{ t('graphicsNoConfigured') }}
|
||||
@@ -100,8 +153,8 @@ const onDragStart = (event: DragEvent, graphic: GraphicConfig) => {
|
||||
|
||||
<div class="row q-col-gutter-md">
|
||||
<div
|
||||
v-for="graphic in graphics"
|
||||
:key="graphic.file"
|
||||
v-for="card in cards"
|
||||
:key="card.id"
|
||||
class="col-12 col-md-6"
|
||||
>
|
||||
<QCard
|
||||
@@ -111,96 +164,54 @@ const onDragStart = (event: DragEvent, graphic: GraphicConfig) => {
|
||||
<QCardSection class="row items-center justify-between">
|
||||
<div>
|
||||
<div class="text-h6">
|
||||
{{ buildGraphicName(graphic) }}
|
||||
{{ card.label }}
|
||||
</div>
|
||||
<div class="text-caption text-grey-5">
|
||||
{{ graphic.file }}
|
||||
{{ card.graphic.file }}
|
||||
</div>
|
||||
</div>
|
||||
<QBadge
|
||||
color="primary"
|
||||
outline
|
||||
>
|
||||
{{ graphic.width ?? 1920 }}x{{ graphic.height ?? 1080 }}
|
||||
{{ card.graphic.width ?? 1920 }}x{{ card.graphic.height ?? 1080 }}
|
||||
</QBadge>
|
||||
</QCardSection>
|
||||
|
||||
<QSeparator />
|
||||
|
||||
<QCardSection>
|
||||
<QSelect
|
||||
v-if="card.skinOptions"
|
||||
v-model="selectedScoreboardSkin"
|
||||
class="q-mb-md"
|
||||
dark
|
||||
dense
|
||||
emit-value
|
||||
map-options
|
||||
outlined
|
||||
:label="t('graphicsSkinLabel')"
|
||||
:options="card.skinOptions"
|
||||
/>
|
||||
|
||||
<div class="row items-center q-gutter-sm">
|
||||
<QBtn
|
||||
color="primary"
|
||||
icon="content_copy"
|
||||
:label="t('graphicsCopyUrl')"
|
||||
@click="copyUrl(graphic)"
|
||||
@click="copyUrl(card.graphic)"
|
||||
/>
|
||||
<QBtn
|
||||
color="secondary"
|
||||
icon="open_with"
|
||||
:label="t('graphicsDragObs')"
|
||||
draggable="true"
|
||||
@dragstart="onDragStart($event, graphic)"
|
||||
@dragstart="onDragStart($event, card.graphic)"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="getPreviewKind(graphic)"
|
||||
class="graphics-preview q-mt-md"
|
||||
>
|
||||
<div class="graphics-preview__label text-caption text-grey-5 q-mb-sm">
|
||||
{{ t('graphicsOverlayPreview') }}
|
||||
</div>
|
||||
|
||||
<div class="graphics-preview__frame-wrap">
|
||||
<iframe
|
||||
class="graphics-preview__frame"
|
||||
:src="buildGraphicUrl(graphic)"
|
||||
:title="t('graphicsPreviewTitle')"
|
||||
loading="lazy"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</QCardSection>
|
||||
</QCard>
|
||||
</div>
|
||||
</div>
|
||||
</QPage>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.graphics-preview {
|
||||
border-top: 1px solid rgb(255 255 255 / 10%);
|
||||
padding-top: 14px;
|
||||
}
|
||||
|
||||
.graphics-preview__frame-wrap {
|
||||
width: 100%;
|
||||
aspect-ratio: 16 / 9;
|
||||
container-type: inline-size;
|
||||
border-radius: 8px;
|
||||
border: 1px solid rgb(255 255 255 / 12%);
|
||||
background:
|
||||
linear-gradient(45deg, rgb(255 255 255 / 8%) 25%, transparent 25%),
|
||||
linear-gradient(-45deg, rgb(255 255 255 / 8%) 25%, transparent 25%),
|
||||
linear-gradient(45deg, transparent 75%, rgb(255 255 255 / 8%) 75%),
|
||||
linear-gradient(-45deg, transparent 75%, rgb(255 255 255 / 8%) 75%),
|
||||
rgb(8 8 8 / 70%);
|
||||
background-size: 20px 20px;
|
||||
background-position: 0 0, 0 10px, 10px -10px, -10px 0;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.graphics-preview__frame {
|
||||
--preview-zoom: 0.50;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
width: calc(100% / var(--preview-zoom));
|
||||
height: calc(100% / var(--preview-zoom));
|
||||
border: 0;
|
||||
transform: translate(-50%, -50%) scale(var(--preview-zoom));
|
||||
transform-origin: center;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user