2026-02-10 00:42:48 +01:00
|
|
|
<template>
|
|
|
|
|
<section class="display-page">
|
|
|
|
|
<div class="campo">
|
|
|
|
|
<span v-if="state.order">
|
2026-02-10 09:54:10 +01:00
|
|
|
<!-- Ordine visualizzazione: home / guest -->
|
2026-02-10 00:42:48 +01:00
|
|
|
<div class="hea home">
|
|
|
|
|
<span :style="{ 'float': 'left' }">
|
|
|
|
|
{{ state.sp.nomi.home }}
|
|
|
|
|
<span class="serv-slot">
|
2026-06-20 15:23:53 +02:00
|
|
|
<img v-show="servHome" src="/serv.png" width="25" alt="Servizio" />
|
2026-02-10 00:42:48 +01:00
|
|
|
</span>
|
2026-06-20 15:23:53 +02:00
|
|
|
<span v-if="state.visuForm" class="score-inline">{{ punt.home }}</span>
|
2026-02-10 00:42:48 +01:00
|
|
|
</span>
|
2026-06-20 15:23:53 +02:00
|
|
|
<span class="mr3" :style="{ 'float': 'right' }">set {{ set.home }}</span>
|
2026-02-10 00:42:48 +01:00
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="hea guest">
|
|
|
|
|
<span :style="{ 'float': 'right' }">
|
2026-06-20 15:23:53 +02:00
|
|
|
<span v-if="state.visuForm" class="score-inline">{{ punt.guest }}</span>
|
2026-02-10 00:42:48 +01:00
|
|
|
<span class="serv-slot">
|
2026-06-20 15:23:53 +02:00
|
|
|
<img v-show="!servHome" src="/serv.png" width="25" alt="Servizio" />
|
2026-02-10 00:42:48 +01:00
|
|
|
</span>
|
|
|
|
|
{{ state.sp.nomi.guest }}
|
|
|
|
|
</span>
|
2026-06-20 15:23:53 +02:00
|
|
|
<span class="ml3" :style="{ 'float': 'left' }">set {{ set.guest }}</span>
|
2026-02-10 00:42:48 +01:00
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<span v-if="state.visuForm">
|
|
|
|
|
<div class="col form home">
|
|
|
|
|
<div class="formdiv" v-for="x in [3, 2, 1, 4, 5, 0]" :key="'hf'+x">
|
|
|
|
|
{{ state.sp.form.home[x] }}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="col form guest">
|
|
|
|
|
<div class="formdiv" v-for="x in [3, 2, 1, 4, 5, 0]" :key="'gf'+x">
|
|
|
|
|
{{ state.sp.form.guest[x] }}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</span>
|
|
|
|
|
<span v-else>
|
|
|
|
|
<div class="punteggio-container">
|
2026-06-20 15:23:53 +02:00
|
|
|
<div class="col punt home">{{ punt.home }}</div>
|
|
|
|
|
<div class="col punt guest">{{ punt.guest }}</div>
|
2026-02-10 00:42:48 +01:00
|
|
|
</div>
|
|
|
|
|
</span>
|
|
|
|
|
</span>
|
|
|
|
|
|
|
|
|
|
<span v-else>
|
2026-02-10 09:54:10 +01:00
|
|
|
<!-- Ordine visualizzazione: guest / home -->
|
2026-02-10 00:42:48 +01:00
|
|
|
<div class="hea guest">
|
|
|
|
|
<span :style="{ 'float': 'left' }">
|
|
|
|
|
{{ state.sp.nomi.guest }}
|
|
|
|
|
<span class="serv-slot">
|
2026-06-20 15:23:53 +02:00
|
|
|
<img v-show="!servHome" src="/serv.png" width="25" alt="Servizio" />
|
2026-02-10 00:42:48 +01:00
|
|
|
</span>
|
2026-06-20 15:23:53 +02:00
|
|
|
<span v-if="state.visuForm" class="score-inline">{{ punt.guest }}</span>
|
2026-02-10 00:42:48 +01:00
|
|
|
</span>
|
2026-06-20 15:23:53 +02:00
|
|
|
<span class="mr3" :style="{ 'float': 'right' }">set {{ set.guest }}</span>
|
2026-02-10 00:42:48 +01:00
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="hea home">
|
|
|
|
|
<span :style="{ 'float': 'right' }">
|
2026-06-20 15:23:53 +02:00
|
|
|
<span v-if="state.visuForm" class="score-inline">{{ punt.home }}</span>
|
2026-02-10 00:42:48 +01:00
|
|
|
<span class="serv-slot">
|
2026-06-20 15:23:53 +02:00
|
|
|
<img v-show="servHome" src="/serv.png" width="25" alt="Servizio" />
|
2026-02-10 00:42:48 +01:00
|
|
|
</span>
|
|
|
|
|
{{ state.sp.nomi.home }}
|
|
|
|
|
</span>
|
2026-06-20 15:23:53 +02:00
|
|
|
<span class="ml3" :style="{ 'float': 'left' }">set {{ set.home }}</span>
|
2026-02-10 00:42:48 +01:00
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<span v-if="state.visuForm">
|
|
|
|
|
<div class="col form guest">
|
|
|
|
|
<div class="formdiv" v-for="x in [3, 2, 1, 4, 5, 0]" :key="'gf2'+x">
|
|
|
|
|
{{ state.sp.form.guest[x] }}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="col form home">
|
|
|
|
|
<div class="formdiv" v-for="x in [3, 2, 1, 4, 5, 0]" :key="'hf2'+x">
|
|
|
|
|
{{ state.sp.form.home[x] }}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</span>
|
|
|
|
|
<span v-else>
|
|
|
|
|
<div class="punteggio-container">
|
2026-06-20 15:23:53 +02:00
|
|
|
<div class="col punt guest">{{ punt.guest }}</div>
|
|
|
|
|
<div class="col punt home">{{ punt.home }}</div>
|
2026-02-10 00:42:48 +01:00
|
|
|
</div>
|
|
|
|
|
</span>
|
|
|
|
|
</span>
|
|
|
|
|
|
|
|
|
|
<div class="striscia" v-if="state.visuStriscia">
|
2026-02-21 11:19:38 +01:00
|
|
|
<span class="striscia-nome text-bold">{{ state.sp.nomi.home }}</span>
|
|
|
|
|
<div class="striscia-items" ref="homeItems">
|
2026-05-12 13:49:47 +02:00
|
|
|
<div v-for="(h, i) in stricciaStrip.home" :key="'sh'+i"
|
|
|
|
|
class="item" :class="{ 'item-vuoto': h === ' ' }">
|
|
|
|
|
{{ h }}
|
2026-02-10 00:42:48 +01:00
|
|
|
</div>
|
|
|
|
|
</div>
|
2026-02-21 11:19:38 +01:00
|
|
|
<span class="striscia-nome text-bold guest-striscia">{{ state.sp.nomi.guest }}</span>
|
|
|
|
|
<div class="striscia-items guest-striscia" ref="guestItems">
|
2026-05-12 13:49:47 +02:00
|
|
|
<div v-for="(h, i) in stricciaStrip.guest" :key="'sg'+i"
|
|
|
|
|
class="item" :class="{ 'item-vuoto': h === ' ' }">
|
|
|
|
|
{{ h }}
|
2026-02-10 00:42:48 +01:00
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
2026-02-10 09:54:10 +01:00
|
|
|
<!-- Indicatore stato connessione -->
|
2026-02-10 00:42:48 +01:00
|
|
|
<div class="connection-status" :class="{ connected: wsConnected, disconnected: !wsConnected }">
|
|
|
|
|
<span class="dot"></span>
|
|
|
|
|
{{ wsConnected ? '' : 'Disconnesso' }}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</section>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<script>
|
2026-06-20 15:23:53 +02:00
|
|
|
import { createWsMixin } from '../wsMixin.js'
|
|
|
|
|
|
2026-02-10 00:42:48 +01:00
|
|
|
export default {
|
|
|
|
|
name: "DisplayPage",
|
2026-06-20 15:23:53 +02:00
|
|
|
mixins: [createWsMixin('display')],
|
2026-02-10 00:42:48 +01:00
|
|
|
mounted() {
|
|
|
|
|
if (this.isMobile()) {
|
|
|
|
|
try { document.documentElement.requestFullscreen() } catch (e) {}
|
|
|
|
|
}
|
|
|
|
|
},
|
2026-05-12 13:49:47 +02:00
|
|
|
computed: {
|
|
|
|
|
stricciaStrip() {
|
|
|
|
|
const currentSet = this.state.sp.striscia.at(-1)
|
|
|
|
|
if (!currentSet) return { home: [], guest: [] }
|
|
|
|
|
let h = 0, g = 0
|
|
|
|
|
const home = [], guest = []
|
2026-05-13 09:35:13 +02:00
|
|
|
for (const scorer of currentSet.ris) {
|
|
|
|
|
if (scorer === 'h') { h++; home.push(h); guest.push(' ') }
|
2026-05-12 13:49:47 +02:00
|
|
|
else { g++; guest.push(g); home.push(' ') }
|
2026-02-21 11:19:38 +01:00
|
|
|
}
|
2026-05-12 13:49:47 +02:00
|
|
|
return { home, guest }
|
2026-02-21 11:19:38 +01:00
|
|
|
},
|
2026-05-12 13:49:47 +02:00
|
|
|
},
|
|
|
|
|
watch: {
|
|
|
|
|
'state.sp.striscia': {
|
2026-02-21 11:19:38 +01:00
|
|
|
deep: true,
|
|
|
|
|
handler() {
|
|
|
|
|
this.$nextTick(() => {
|
2026-05-12 13:49:47 +02:00
|
|
|
if (this.$refs.homeItems) this.$refs.homeItems.scrollLeft = this.$refs.homeItems.scrollWidth
|
2026-02-21 11:19:38 +01:00
|
|
|
if (this.$refs.guestItems) this.$refs.guestItems.scrollLeft = this.$refs.guestItems.scrollWidth
|
|
|
|
|
})
|
|
|
|
|
}
|
2026-05-12 13:49:47 +02:00
|
|
|
},
|
2026-02-21 11:19:38 +01:00
|
|
|
},
|
2026-02-10 00:42:48 +01:00
|
|
|
methods: {
|
2026-06-20 15:23:53 +02:00
|
|
|
onWsMessage(msg) {
|
|
|
|
|
if (msg.type === 'speak') this.speakOnDisplay(msg.text)
|
|
|
|
|
else if (msg.type === 'error') console.error('[Display] Server error:', msg.message)
|
|
|
|
|
},
|
2026-02-10 00:42:48 +01:00
|
|
|
isMobile() {
|
|
|
|
|
return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)
|
|
|
|
|
},
|
2026-02-11 19:35:09 +01:00
|
|
|
speakOnDisplay(text) {
|
2026-06-20 15:23:53 +02:00
|
|
|
if (typeof text !== 'string' || !text.trim() || !('speechSynthesis' in window)) return
|
2026-02-11 19:35:09 +01:00
|
|
|
const utterance = new SpeechSynthesisUtterance(text.trim())
|
|
|
|
|
const voices = window.speechSynthesis.getVoices()
|
2026-06-20 15:23:53 +02:00
|
|
|
utterance.voice = voices.find(v => v.name === 'Google italiano')
|
|
|
|
|
|| voices.find(v => v.lang?.toLowerCase().startsWith('it'))
|
|
|
|
|
|| null
|
2026-02-11 19:35:09 +01:00
|
|
|
utterance.lang = 'it-IT'
|
|
|
|
|
window.speechSynthesis.cancel()
|
|
|
|
|
window.speechSynthesis.speak(utterance)
|
2026-06-20 15:23:53 +02:00
|
|
|
},
|
|
|
|
|
},
|
2026-02-10 00:42:48 +01:00
|
|
|
}
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<style scoped>
|
|
|
|
|
.display-page {
|
|
|
|
|
width: 100%;
|
|
|
|
|
height: 100vh;
|
|
|
|
|
background: #000;
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.connection-status {
|
|
|
|
|
position: fixed;
|
|
|
|
|
top: 8px;
|
|
|
|
|
right: 8px;
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
padding: 4px 10px;
|
|
|
|
|
border-radius: 12px;
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
gap: 5px;
|
|
|
|
|
z-index: 100;
|
|
|
|
|
transition: opacity 0.5s;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.connection-status.connected {
|
|
|
|
|
opacity: 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.connection-status.disconnected {
|
|
|
|
|
background: rgba(255, 50, 50, 0.8);
|
|
|
|
|
color: white;
|
|
|
|
|
opacity: 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.dot {
|
|
|
|
|
width: 8px;
|
|
|
|
|
height: 8px;
|
|
|
|
|
border-radius: 50%;
|
|
|
|
|
display: inline-block;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.connected .dot {
|
|
|
|
|
background: #4caf50;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.disconnected .dot {
|
|
|
|
|
background: #f44336;
|
|
|
|
|
animation: blink 1s infinite;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@keyframes blink {
|
|
|
|
|
0%, 100% { opacity: 1; }
|
|
|
|
|
50% { opacity: 0.3; }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.guest-striscia {
|
|
|
|
|
color: white;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.punteggio-container {
|
|
|
|
|
width: 100%;
|
|
|
|
|
display: flex;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.punt {
|
|
|
|
|
font-size: 60vh;
|
|
|
|
|
flex: 1;
|
|
|
|
|
display: flex;
|
|
|
|
|
justify-content: center;
|
|
|
|
|
align-items: center;
|
|
|
|
|
min-height: 50vh;
|
|
|
|
|
min-width: 50vw;
|
|
|
|
|
max-width: 50vw;
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
box-sizing: border-box;
|
|
|
|
|
}
|
|
|
|
|
</style>
|