Migliora overlay basket Android, regia e catalogo sport senza timer.
Fix crash e race su +3, layout tabellone con squadre ai lati, safe area in diretta e rimozione overlay cronometro; backend con sync punteggio periodo, link diretta in regia e init score_state basket. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -167,6 +167,7 @@ module Api
|
||||
def score_sync_params
|
||||
params.permit(
|
||||
:home_sets, :away_sets, :home_points, :away_points, :current_set,
|
||||
:clock_secs, :clock_running, :period, :home_score, :away_score, :overtime,
|
||||
set_partials: %i[set home away]
|
||||
)
|
||||
end
|
||||
|
||||
@@ -18,8 +18,10 @@ module Public
|
||||
@rules = Scoring::Rules.from_match(@match)
|
||||
@stream_closed = @session.terminal?
|
||||
@on_air = !@stream_closed && mediamtx_stream_playable?(@session)
|
||||
@share_url = request.original_url
|
||||
@share_title = "Regia — #{@team.name} vs #{@match.opponent_name}"
|
||||
@regia_share_url = request.original_url
|
||||
@regia_share_title = "Regia — #{@team.name} vs #{@match.opponent_name}"
|
||||
@live_share_url = @session.share_url
|
||||
@live_share_title = "#{@team.name} vs #{@match.opponent_name} — Diretta Match Live TV"
|
||||
end
|
||||
|
||||
def status
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
module Sports
|
||||
class OverlayKind
|
||||
ALL = %w[none timer volley basket timed racket].freeze
|
||||
ALL = %w[none volley basket timed racket].freeze
|
||||
|
||||
def self.valid?(value)
|
||||
ALL.include?(value.to_s)
|
||||
|
||||
@@ -99,6 +99,7 @@ class Match < ApplicationRecord
|
||||
|
||||
def effective_overlay_kind
|
||||
kind = overlay_kind.presence || Sports::Catalog.overlay_for(sport_key)
|
||||
kind = "none" if kind == "timer"
|
||||
allowed = Sports::Catalog.allowed_overlays_for(sport_key)
|
||||
allowed.include?(kind) ? kind : Sports::Catalog.overlay_for(sport_key)
|
||||
end
|
||||
|
||||
@@ -35,7 +35,7 @@ module Sessions
|
||||
|
||||
StreamSession.transaction do
|
||||
session.save!
|
||||
session.create_score_state!
|
||||
Scoring::Engine.ensure_score_for(session)
|
||||
mtx = Mediamtx::Client.new
|
||||
mtx.create_path(session)
|
||||
log_event(session, "pairing", { created: true, platform: session.platform })
|
||||
|
||||
@@ -19,8 +19,10 @@
|
||||
data-hls-url="<%= j @session.hls_playback_url %>"
|
||||
data-home-name="<%= j @team.name %>"
|
||||
data-away-name="<%= j @match.opponent_name %>"
|
||||
data-share-url="<%= j @share_url %>"
|
||||
data-share-title="<%= j @share_title %>"
|
||||
data-regia-share-url="<%= j @regia_share_url %>"
|
||||
data-regia-share-title="<%= j @regia_share_title %>"
|
||||
data-live-share-url="<%= j @live_share_url %>"
|
||||
data-live-share-title="<%= j @live_share_title %>"
|
||||
data-points-target="<%= @match.effective_board_type.in?(%w[volley racket]) ? Scoring::Rules.from_match(@match).points_target(@score.current_set) : 0 %>"
|
||||
data-stream-closed="<%= @stream_closed %>"
|
||||
data-initial-paused="<%= @session.paused? %>">
|
||||
@@ -33,13 +35,30 @@
|
||||
</span>
|
||||
</header>
|
||||
|
||||
<div class="regia-share">
|
||||
<button type="button" class="regia-btn regia-btn--outline" id="btn-share" style="margin:0">
|
||||
Condividi link regia
|
||||
</button>
|
||||
<button type="button" class="regia-btn regia-btn--outline" id="btn-copy" style="margin:0">
|
||||
Copia link
|
||||
</button>
|
||||
<% if @live_share_url.present? %>
|
||||
<div class="regia-share-group">
|
||||
<p class="regia-share-label">Link diretta (spettatori)</p>
|
||||
<div class="regia-share">
|
||||
<button type="button" class="regia-btn regia-btn--outline" id="btn-share-live" style="margin:0">
|
||||
Condividi diretta
|
||||
</button>
|
||||
<button type="button" class="regia-btn regia-btn--outline" id="btn-copy-live" style="margin:0">
|
||||
Copia link
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<div class="regia-share-group">
|
||||
<p class="regia-share-label">Link regia (punteggio)</p>
|
||||
<div class="regia-share">
|
||||
<button type="button" class="regia-btn regia-btn--outline" id="btn-share-regia" style="margin:0">
|
||||
Condividi link regia
|
||||
</button>
|
||||
<button type="button" class="regia-btn regia-btn--outline" id="btn-copy-regia" style="margin:0">
|
||||
Copia link
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="regia-preview">
|
||||
@@ -74,4 +93,4 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="/regia.js?v=5"></script>
|
||||
<script src="/regia.js?v=6"></script>
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
# Catalogo sport Match Live TV — board e overlay impliciti, regole standard.
|
||||
# board: volley | basket | timed | racket | timer | generic
|
||||
# overlay: none | timer | volley | basket | timed | racket
|
||||
# overlay: none | volley | basket | timed | racket
|
||||
|
||||
pallavolo:
|
||||
label: Pallavolo
|
||||
board: volley
|
||||
overlay: volley
|
||||
allowed_overlays: [volley, timer, none]
|
||||
allowed_overlays: [volley, none]
|
||||
default_rules:
|
||||
sets_to_win: 3
|
||||
points_per_set: 25
|
||||
@@ -17,7 +17,7 @@ beach_volley:
|
||||
label: Beach Volley
|
||||
board: volley
|
||||
overlay: volley
|
||||
allowed_overlays: [volley, timer, none]
|
||||
allowed_overlays: [volley, none]
|
||||
default_rules:
|
||||
sets_to_win: 2
|
||||
points_per_set: 21
|
||||
@@ -28,7 +28,7 @@ sitting_volley:
|
||||
label: Sitting Volley
|
||||
board: volley
|
||||
overlay: volley
|
||||
allowed_overlays: [volley, timer, none]
|
||||
allowed_overlays: [volley, none]
|
||||
default_rules:
|
||||
sets_to_win: 3
|
||||
points_per_set: 25
|
||||
@@ -39,7 +39,7 @@ basket:
|
||||
label: Basket
|
||||
board: basket
|
||||
overlay: basket
|
||||
allowed_overlays: [basket, timer, none]
|
||||
allowed_overlays: [basket, none]
|
||||
default_rules:
|
||||
periods: 4
|
||||
period_duration_secs: 600
|
||||
@@ -49,7 +49,7 @@ calcio_a_5:
|
||||
label: Calcio a 5
|
||||
board: timed
|
||||
overlay: timed
|
||||
allowed_overlays: [timed, timer, none]
|
||||
allowed_overlays: [timed, none]
|
||||
default_rules:
|
||||
periods: 2
|
||||
period_duration_secs: 1200
|
||||
@@ -59,7 +59,7 @@ pallamano:
|
||||
label: Pallamano
|
||||
board: timed
|
||||
overlay: timed
|
||||
allowed_overlays: [timed, timer, none]
|
||||
allowed_overlays: [timed, none]
|
||||
default_rules:
|
||||
periods: 2
|
||||
period_duration_secs: 1800
|
||||
@@ -69,7 +69,7 @@ hockey_indoor:
|
||||
label: Hockey Indoor
|
||||
board: timed
|
||||
overlay: timed
|
||||
allowed_overlays: [timed, timer, none]
|
||||
allowed_overlays: [timed, none]
|
||||
default_rules:
|
||||
periods: 2
|
||||
period_duration_secs: 1200
|
||||
@@ -79,7 +79,7 @@ floorball:
|
||||
label: Floorball
|
||||
board: timed
|
||||
overlay: timed
|
||||
allowed_overlays: [timed, timer, none]
|
||||
allowed_overlays: [timed, none]
|
||||
default_rules:
|
||||
periods: 3
|
||||
period_duration_secs: 1200
|
||||
@@ -89,7 +89,7 @@ tennis:
|
||||
label: Tennis
|
||||
board: racket
|
||||
overlay: racket
|
||||
allowed_overlays: [racket, timer, none]
|
||||
allowed_overlays: [racket, none]
|
||||
default_rules:
|
||||
sets_to_win: 2
|
||||
points_per_set: 6
|
||||
@@ -100,7 +100,7 @@ tennis_tavolo:
|
||||
label: Tennis Tavolo
|
||||
board: racket
|
||||
overlay: racket
|
||||
allowed_overlays: [racket, timer, none]
|
||||
allowed_overlays: [racket, none]
|
||||
default_rules:
|
||||
sets_to_win: 3
|
||||
points_per_set: 11
|
||||
@@ -111,7 +111,7 @@ badminton:
|
||||
label: Badminton
|
||||
board: racket
|
||||
overlay: racket
|
||||
allowed_overlays: [racket, timer, none]
|
||||
allowed_overlays: [racket, none]
|
||||
default_rules:
|
||||
sets_to_win: 2
|
||||
points_per_set: 21
|
||||
@@ -122,7 +122,7 @@ padel:
|
||||
label: Padel
|
||||
board: racket
|
||||
overlay: racket
|
||||
allowed_overlays: [racket, timer, none]
|
||||
allowed_overlays: [racket, none]
|
||||
default_rules:
|
||||
sets_to_win: 2
|
||||
points_per_set: 6
|
||||
@@ -133,7 +133,7 @@ pickleball:
|
||||
label: Pickleball
|
||||
board: racket
|
||||
overlay: racket
|
||||
allowed_overlays: [racket, timer, none]
|
||||
allowed_overlays: [racket, none]
|
||||
default_rules:
|
||||
sets_to_win: 2
|
||||
points_per_set: 11
|
||||
@@ -143,41 +143,41 @@ pickleball:
|
||||
ginnastica_artistica:
|
||||
label: Ginnastica Artistica
|
||||
board: timer
|
||||
overlay: timer
|
||||
allowed_overlays: [timer, none]
|
||||
overlay: none
|
||||
allowed_overlays: [none]
|
||||
default_rules: {}
|
||||
|
||||
ginnastica_ritmica:
|
||||
label: Ginnastica Ritmica
|
||||
board: timer
|
||||
overlay: timer
|
||||
allowed_overlays: [timer, none]
|
||||
overlay: none
|
||||
allowed_overlays: [none]
|
||||
default_rules: {}
|
||||
|
||||
danza_sportiva:
|
||||
label: Danza Sportiva
|
||||
board: timer
|
||||
overlay: timer
|
||||
allowed_overlays: [timer, none]
|
||||
overlay: none
|
||||
allowed_overlays: [none]
|
||||
default_rules: {}
|
||||
|
||||
cheerleading:
|
||||
label: Cheerleading
|
||||
board: timer
|
||||
overlay: timer
|
||||
allowed_overlays: [timer, none]
|
||||
overlay: none
|
||||
allowed_overlays: [none]
|
||||
default_rules: {}
|
||||
|
||||
pattinaggio:
|
||||
label: Pattinaggio
|
||||
board: timer
|
||||
overlay: timer
|
||||
allowed_overlays: [timer, none]
|
||||
overlay: none
|
||||
allowed_overlays: [none]
|
||||
default_rules: {}
|
||||
|
||||
altro:
|
||||
label: Altro
|
||||
board: generic
|
||||
overlay: none
|
||||
allowed_overlays: [none, timer]
|
||||
allowed_overlays: [none]
|
||||
default_rules: {}
|
||||
|
||||
@@ -173,10 +173,22 @@
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.regia-share-group {
|
||||
margin: 12px 0;
|
||||
}
|
||||
|
||||
.regia-share-label {
|
||||
margin: 0 0 6px;
|
||||
font-size: 0.72rem;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.04em;
|
||||
text-transform: uppercase;
|
||||
color: var(--regia-muted);
|
||||
}
|
||||
|
||||
.regia-share {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin: 12px 0;
|
||||
}
|
||||
|
||||
.regia-share .regia-btn { flex: 1; font-size: 0.9rem; padding: 12px; }
|
||||
|
||||
@@ -14,8 +14,10 @@
|
||||
hlsUrl: root.dataset.hlsUrl,
|
||||
homeName: root.dataset.homeName,
|
||||
awayName: root.dataset.awayName,
|
||||
shareUrl: root.dataset.shareUrl,
|
||||
shareTitle: root.dataset.shareTitle,
|
||||
regiaShareUrl: root.dataset.regiaShareUrl,
|
||||
regiaShareTitle: root.dataset.regiaShareTitle,
|
||||
liveShareUrl: root.dataset.liveShareUrl,
|
||||
liveShareTitle: root.dataset.liveShareTitle,
|
||||
streamClosed: root.dataset.streamClosed === "true"
|
||||
};
|
||||
|
||||
@@ -360,12 +362,12 @@
|
||||
els.btnPause?.addEventListener("click", () => togglePauseStream().catch((e) => toast(e.message)));
|
||||
document.getElementById("btn-stop")?.addEventListener("click", () => stopStream().catch((e) => toast(e.message)));
|
||||
|
||||
async function shareLink() {
|
||||
const payload = {
|
||||
title: cfg.shareTitle,
|
||||
text: "Apri questo link per gestire il punteggio della diretta:",
|
||||
url: cfg.shareUrl
|
||||
};
|
||||
async function shareLink(url, title, text) {
|
||||
if (!url) {
|
||||
toast("Link non disponibile");
|
||||
return;
|
||||
}
|
||||
const payload = { title, text, url };
|
||||
if (navigator.share) {
|
||||
try {
|
||||
await navigator.share(payload);
|
||||
@@ -374,20 +376,38 @@
|
||||
if (err.name === "AbortError") return;
|
||||
}
|
||||
}
|
||||
await copyLink();
|
||||
await copyLink(url);
|
||||
}
|
||||
|
||||
async function copyLink() {
|
||||
async function copyLink(url) {
|
||||
if (!url) {
|
||||
toast("Link non disponibile");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await navigator.clipboard.writeText(cfg.shareUrl);
|
||||
await navigator.clipboard.writeText(url);
|
||||
toast("Link copiato");
|
||||
} catch (_) {
|
||||
prompt("Copia il link:", cfg.shareUrl);
|
||||
prompt("Copia il link:", url);
|
||||
}
|
||||
}
|
||||
|
||||
document.getElementById("btn-share")?.addEventListener("click", () => shareLink());
|
||||
document.getElementById("btn-copy")?.addEventListener("click", () => copyLink());
|
||||
document.getElementById("btn-share-regia")?.addEventListener("click", () =>
|
||||
shareLink(
|
||||
cfg.regiaShareUrl,
|
||||
cfg.regiaShareTitle,
|
||||
"Apri questo link per gestire il punteggio della diretta:"
|
||||
)
|
||||
);
|
||||
document.getElementById("btn-copy-regia")?.addEventListener("click", () => copyLink(cfg.regiaShareUrl));
|
||||
document.getElementById("btn-share-live")?.addEventListener("click", () =>
|
||||
shareLink(
|
||||
cfg.liveShareUrl,
|
||||
cfg.liveShareTitle,
|
||||
"Guarda la diretta su Match Live TV:"
|
||||
)
|
||||
);
|
||||
document.getElementById("btn-copy-live")?.addEventListener("click", () => copyLink(cfg.liveShareUrl));
|
||||
|
||||
function bindPreviewStallRecovery() {
|
||||
if (!els.preview) return;
|
||||
|
||||
@@ -14,6 +14,7 @@ RSpec.describe "Api::V1::Sports", type: :request do
|
||||
expect(body).to be_an(Array)
|
||||
pallavolo = body.find { |s| s["key"] == "pallavolo" }
|
||||
expect(pallavolo["board"]).to eq("volley")
|
||||
expect(pallavolo["allowed_overlays"]).to include("volley", "timer", "none")
|
||||
expect(pallavolo["allowed_overlays"]).to include("volley", "none")
|
||||
expect(pallavolo["allowed_overlays"]).not_to include("timer")
|
||||
end
|
||||
end
|
||||
|
||||
@@ -11,7 +11,9 @@ RSpec.describe "Public regia", type: :request do
|
||||
token = Sessions::RegiaAccess.new(session).issue_token!
|
||||
get public_regia_path(token)
|
||||
expect(response).to have_http_status(:ok)
|
||||
expect(response.body).to include("Condividi diretta")
|
||||
expect(response.body).to include("Condividi link regia")
|
||||
expect(response.body).to include("/live/#{session.id}")
|
||||
expect(response.body).not_to include("Aruba")
|
||||
end
|
||||
|
||||
|
||||
Reference in New Issue
Block a user