Separa app in client-server con WebSocket
- Aggiunto server Express + WebSocket (server.js) - Creata pagina Display (solo visualizzazione punteggio) - Creata pagina Controller (pannello comandi da mobile) - Aggiunto Vue Router con rotte / e /controller - Estratta logica di gioco condivisa in gameState.js
This commit is contained in:
269
src/components/DisplayPage.vue
Normal file
269
src/components/DisplayPage.vue
Normal file
@@ -0,0 +1,269 @@
|
||||
<template>
|
||||
<section class="display-page">
|
||||
<div class="campo">
|
||||
<span v-if="state.order">
|
||||
<!-- home guest -->
|
||||
<div class="hea home">
|
||||
<span :style="{ 'float': 'left' }">
|
||||
{{ state.sp.nomi.home }}
|
||||
<span class="serv-slot">
|
||||
<img v-show="state.sp.servHome" src="/serv.png" width="25" />
|
||||
</span>
|
||||
<span v-if="state.visuForm" class="score-inline">{{ state.sp.punt.home }}</span>
|
||||
</span>
|
||||
<span class="mr3" :style="{ 'float': 'right' }">set {{ state.sp.set.home }}</span>
|
||||
</div>
|
||||
|
||||
<div class="hea guest">
|
||||
<span :style="{ 'float': 'right' }">
|
||||
<span v-if="state.visuForm" class="score-inline">{{ state.sp.punt.guest }}</span>
|
||||
<span class="serv-slot">
|
||||
<img v-show="!state.sp.servHome" src="/serv.png" width="25" />
|
||||
</span>
|
||||
{{ state.sp.nomi.guest }}
|
||||
</span>
|
||||
<span class="ml3" :style="{ 'float': 'left' }">set {{ state.sp.set.guest }}</span>
|
||||
</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">
|
||||
<div class="col punt home">{{ state.sp.punt.home }}</div>
|
||||
<div class="col punt guest">{{ state.sp.punt.guest }}</div>
|
||||
</div>
|
||||
</span>
|
||||
</span>
|
||||
|
||||
<span v-else>
|
||||
<!-- guest home -->
|
||||
<div class="hea guest">
|
||||
<span :style="{ 'float': 'left' }">
|
||||
{{ state.sp.nomi.guest }}
|
||||
<span class="serv-slot">
|
||||
<img v-show="!state.sp.servHome" src="/serv.png" width="25" />
|
||||
</span>
|
||||
<span v-if="state.visuForm" class="score-inline">{{ state.sp.punt.guest }}</span>
|
||||
</span>
|
||||
<span class="mr3" :style="{ 'float': 'right' }">set {{ state.sp.set.guest }}</span>
|
||||
</div>
|
||||
|
||||
<div class="hea home">
|
||||
<span :style="{ 'float': 'right' }">
|
||||
<span v-if="state.visuForm" class="score-inline">{{ state.sp.punt.home }}</span>
|
||||
<span class="serv-slot">
|
||||
<img v-show="state.sp.servHome" src="/serv.png" width="25" />
|
||||
</span>
|
||||
{{ state.sp.nomi.home }}
|
||||
</span>
|
||||
<span class="ml3" :style="{ 'float': 'left' }">set {{ state.sp.set.home }}</span>
|
||||
</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">
|
||||
<div class="col punt guest">{{ state.sp.punt.guest }}</div>
|
||||
<div class="col punt home">{{ state.sp.punt.home }}</div>
|
||||
</div>
|
||||
</span>
|
||||
</span>
|
||||
|
||||
<div class="striscia" v-if="state.visuStriscia">
|
||||
<div>
|
||||
<span class="text-bold mr1">{{ state.sp.nomi.home }}</span>
|
||||
<div v-for="(h, i) in state.sp.striscia.home" :key="'sh'+i" class="item">
|
||||
{{ String(h) }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="guest-striscia">
|
||||
<span class="text-bold mr1">{{ state.sp.nomi.guest }}</span>
|
||||
<div v-for="(h, i) in state.sp.striscia.guest" :key="'sg'+i" class="item">
|
||||
{{ String(h) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Connection status indicator -->
|
||||
<div class="connection-status" :class="{ connected: wsConnected, disconnected: !wsConnected }">
|
||||
<span class="dot"></span>
|
||||
{{ wsConnected ? '' : 'Disconnesso' }}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "DisplayPage",
|
||||
data() {
|
||||
return {
|
||||
ws: null,
|
||||
wsConnected: false,
|
||||
state: {
|
||||
order: true,
|
||||
visuForm: false,
|
||||
visuStriscia: true,
|
||||
modalitaPartita: "3/5",
|
||||
sp: {
|
||||
striscia: { home: [0], guest: [0] },
|
||||
servHome: true,
|
||||
punt: { home: 0, guest: 0 },
|
||||
set: { home: 0, guest: 0 },
|
||||
nomi: { home: "Antoniana", guest: "Guest" },
|
||||
form: {
|
||||
home: ["1", "2", "3", "4", "5", "6"],
|
||||
guest: ["1", "2", "3", "4", "5", "6"],
|
||||
},
|
||||
storicoServizio: [],
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.connectWebSocket()
|
||||
// Fullscreen on mobile
|
||||
if (this.isMobile()) {
|
||||
try { document.documentElement.requestFullscreen() } catch (e) {}
|
||||
}
|
||||
},
|
||||
beforeUnmount() {
|
||||
if (this.ws) {
|
||||
this.ws.close()
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
isMobile() {
|
||||
return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)
|
||||
},
|
||||
connectWebSocket() {
|
||||
const protocol = location.protocol === 'https:' ? 'wss:' : 'ws:'
|
||||
const wsUrl = `${protocol}//${location.host}`
|
||||
this.ws = new WebSocket(wsUrl)
|
||||
|
||||
this.ws.onopen = () => {
|
||||
this.wsConnected = true
|
||||
// Register as display
|
||||
this.ws.send(JSON.stringify({ type: 'register', role: 'display' }))
|
||||
}
|
||||
|
||||
this.ws.onmessage = (event) => {
|
||||
try {
|
||||
const msg = JSON.parse(event.data)
|
||||
if (msg.type === 'state') {
|
||||
this.state = msg.state
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Error parsing WS message:', e)
|
||||
}
|
||||
}
|
||||
|
||||
this.ws.onclose = () => {
|
||||
this.wsConnected = false
|
||||
// Auto-reconnect after 2 seconds
|
||||
setTimeout(() => this.connectWebSocket(), 2000)
|
||||
}
|
||||
|
||||
this.ws.onerror = () => {
|
||||
this.wsConnected = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</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>
|
||||
Reference in New Issue
Block a user