diff --git a/src/components/ControllerPage.vue b/src/components/ControllerPage.vue index c643eb9..9c2dca7 100644 --- a/src/components/ControllerPage.vue +++ b/src/components/ControllerPage.vue @@ -7,7 +7,9 @@ -
⏱ MODALITÀ TIME OUT ATTIVA
+
+ ⏱ TIME OUT {{ state.sp.nomi[state.timeoutTeam] }} — {{ timeoutCountdown }} +
@@ -304,6 +345,13 @@ export default { formGuest: ["1", "2", "3", "4", "5", "6"], }, isLandscape: window.innerWidth > window.innerHeight, + showTimeoutModal: false, + timeoutTeamSel: null, + timeoutVideoSel: null, + spotList: [], + spotDurations: {}, + nowTick: Date.now(), + timeoutTicker: null, } }, computed: { @@ -338,6 +386,17 @@ export default { } return hasComplete }, + timeoutRemaining() { + if (!this.state.timeout || !this.state.timeoutStartedAt) return 30 + const trascorsi = (this.nowTick - this.state.timeoutStartedAt) / 1000 + return Math.max(0, Math.ceil(30 - trascorsi)) + }, + timeoutCountdown() { + const r = this.timeoutRemaining + const m = String(Math.floor(r / 60)).padStart(2, '0') + const s = String(r % 60).padStart(2, '0') + return `${m}:${s}` + }, }, watch: { squadraVincente(val) { @@ -346,6 +405,12 @@ export default { this.showSetVinto = true } }, + 'state.timeout': { + immediate: true, + handler(attivo) { + attivo ? this.avviaTimeoutTicker() : this.fermaTimeoutTicker() + }, + }, }, mounted() { this._resizeHandler = () => { this.isLandscape = window.innerWidth > window.innerHeight } @@ -355,6 +420,7 @@ export default { beforeUnmount() { window.removeEventListener('resize', this._resizeHandler) window.removeEventListener('orientationchange', this._resizeHandler) + this.fermaTimeoutTicker() }, methods: { generaReferto, @@ -458,6 +524,47 @@ export default { : this.servHome ? `${home} a ${guest}` : `${guest} a ${home}` this.sendWs({ type: 'speak', text }) }, + avviaTimeoutTicker() { + this.fermaTimeoutTicker() + this.nowTick = Date.now() + this.timeoutTicker = setInterval(() => { this.nowTick = Date.now() }, 250) + }, + fermaTimeoutTicker() { + if (this.timeoutTicker) { clearInterval(this.timeoutTicker); this.timeoutTicker = null } + }, + async openTimeout() { + this.timeoutTeamSel = null + this.timeoutVideoSel = null + this.spotList = [] + this.spotDurations = {} + this.showTimeoutModal = true + try { + const res = await fetch('/spot/list') + this.spotList = await res.json() + } catch { + this.spotList = [] + } + this.spotList.forEach(file => this.probeDurata(file)) + }, + probeDurata(file) { + const video = document.createElement('video') + video.preload = 'metadata' + video.onloadedmetadata = () => { + this.spotDurations = { ...this.spotDurations, [file]: video.duration } + } + video.src = '/spot/' + encodeURIComponent(file) + }, + formatDurata(sec) { + return typeof sec === 'number' && isFinite(sec) ? `${Math.round(sec)}s` : '…' + }, + avviaTimeout() { + if (!this.timeoutTeamSel) return + this.sendAction({ type: 'startTimeout', team: this.timeoutTeamSel, video: this.timeoutVideoSel }) + this.showTimeoutModal = false + }, + terminaTimeout() { + this.sendAction({ type: 'stopTimeout' }) + }, }, } @@ -1070,4 +1177,37 @@ export default { padding: 8px 0; font-weight: 600; } + +/* Modal avvio time out */ +.dialog-timeout { + max-height: 85vh; + overflow-y: auto; +} +.btn-set.team-sel-active { + box-shadow: 0 0 0 3px rgba(255,255,255,0.6) inset; +} +.timeout-video-list { + display: flex; + flex-direction: column; + gap: 8px; + max-height: 40vh; + overflow-y: auto; +} +.timeout-video-option { + display: flex; + align-items: center; + gap: 8px; + background: rgba(255,255,255,0.06); + border: 1px solid rgba(255,255,255,0.12); + border-radius: 10px; + padding: 10px 12px; + font-size: 14px; + cursor: pointer; +} +.video-duration { + margin-left: auto; + color: #aaa; + font-size: 12px; + font-variant-numeric: tabular-nums; +}