Rende usabili calendario, overlay ospite e stato diretta del cartellone.

Il calendario non deve più scorrere in orizzontale, il logo a destra non copre il nome e una diretta YouTube o in collegamento compare come In diretta.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-09-06 09:55:08 +02:00
co-authored by Cursor
parent b09e83db09
commit c3ef8e91a6
13 changed files with 296 additions and 108 deletions
@@ -38,19 +38,21 @@ module Public
.includes( .includes(
:tournament_group, :tournament_group,
:tournament_round, :tournament_round,
:stream_sessions,
home_participant: { logo_file_attachment: :blob }, home_participant: { logo_file_attachment: :blob },
away_participant: { logo_file_attachment: :blob } away_participant: { logo_file_attachment: :blob }
) )
.order(Arel.sql("scheduled_at ASC NULLS LAST"), created_at: :asc) .order(Arel.sql("scheduled_at ASC NULLS LAST"), created_at: :asc)
@live_sessions = StreamSession broadcasting = StreamSession
.broadcasting .broadcasting
.publicly_listed
.where(platform: "matchlivetv")
.includes(:score_state, match: [:home_participant, :away_participant, { team: :club }]) .includes(:score_state, match: [:home_participant, :away_participant, { team: :club }])
.where(match_id: @tournament.matches.select(:id)) .where(match_id: @tournament.matches.select(:id))
.order(Arel.sql("started_at DESC NULLS LAST"), created_at: :desc) .order(Arel.sql("started_at DESC NULLS LAST"), created_at: :desc)
@live_by_match_id = @live_sessions.index_by(&:match_id) .to_a
@live_by_match_id = {}
broadcasting.each { |session| @live_by_match_id[session.match_id] ||= session }
@live_sessions = broadcasting.select(&:public_watchable?)
@recordings = Recording.ready.publicly_listed @recordings = Recording.ready.publicly_listed
.joins(stream_session: :match) .joins(stream_session: :match)
@@ -67,7 +67,7 @@ module Public
sessions = match.stream_sessions.to_a sessions = match.stream_sessions.to_a
latest = sessions.max_by(&:created_at) latest = sessions.max_by(&:created_at)
if latest && !latest.status.in?(%w[ended error]) if latest && !latest.status.in?(%w[ended error])
return :live if latest.status.in?(%w[live reconnecting paused]) return :live if latest.status.in?(%w[live reconnecting paused connecting])
return :waiting_operator return :waiting_operator
end end
@@ -106,6 +106,18 @@ module Public
:waiting :waiting
end end
def tournament_public_watch_target(session)
return unless session
if session.matchlivetv_platform? && session.publicly_listed?
return [public_live_path(session), t("tournaments.page.watch_live"), {}]
end
if session.youtube_watch_url.present?
return [session.youtube_watch_url, t("live.show.youtube_watch_link"), { target: "_blank", rel: "noopener" }]
end
nil
end
def tournament_public_score_label(match) def tournament_public_score_label(match)
return "#{match.home_score}#{match.away_score}" if match.home_score.present? && match.away_score.present? return "#{match.home_score}#{match.away_score}" if match.home_score.present? && match.away_score.present?
+6
View File
@@ -186,6 +186,12 @@ class StreamSession < ApplicationRecord
platform == "youtube" && youtube_watch_url.present? platform == "youtube" && youtube_watch_url.present?
end end
def public_watchable?
return true if matchlivetv_platform? && publicly_listed?
youtube_ready?
end
def link_only? def link_only?
privacy_status.in?(%w[unlisted private]) privacy_status.in?(%w[unlisted private])
end end
+2 -2
View File
@@ -10,7 +10,7 @@
<%= render "shared/analytics_suppress" %> <%= render "shared/analytics_suppress" %>
<%= yield :head %> <%= yield :head %>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.2/css/all.min.css" integrity="sha512-SnH5WK+bZxgPHs44uWIX+LLJAJ9/2PkPKZ5QiAj6Ta86w+fsb2TkcmfRyVX3pBnMFcV7oQPJkl9QevSCWr3W6A==" crossorigin="anonymous" referrerpolicy="no-referrer"> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.2/css/all.min.css" integrity="sha512-SnH5WK+bZxgPHs44uWIX+LLJAJ9/2PkPKZ5QiAj6Ta86w+fsb2TkcmfRyVX3pBnMFcV7oQPJkl9QevSCWr3W6A==" crossorigin="anonymous" referrerpolicy="no-referrer">
<link rel="stylesheet" href="/marketing.css?v=101"> <link rel="stylesheet" href="/marketing.css?v=102">
</head> </head>
<body data-confirm-i18n='<%= raw confirm_dialog_i18n_json %>'<% if MatchLiveTv.google_analytics_configured? %> data-ga-id="<%= MatchLiveTv.google_analytics_measurement_id %>"<% end %>> <body data-confirm-i18n='<%= raw confirm_dialog_i18n_json %>'<% if MatchLiveTv.google_analytics_configured? %> data-ga-id="<%= MatchLiveTv.google_analytics_measurement_id %>"<% end %>>
<%= render "shared/cookie_banner" %> <%= render "shared/cookie_banner" %>
@@ -24,7 +24,7 @@
<%= render(@app_store_review_chrome ? "shared/marketing_footer_app_store" : "shared/marketing_footer") %> <%= render(@app_store_review_chrome ? "shared/marketing_footer_app_store" : "shared/marketing_footer") %>
<script src="/branding-form.js?v=1" defer></script> <script src="/branding-form.js?v=1" defer></script>
<script src="/tournament-invite-editor.js?v=2" defer></script> <script src="/tournament-invite-editor.js?v=2" defer></script>
<script src="/tournament-calendar.js?v=1" defer></script> <script src="/tournament-calendar.js?v=2" defer></script>
<script src="/roster-form.js?v=1" defer></script> <script src="/roster-form.js?v=1" defer></script>
<script src="/password-toggle.js?v=2" defer></script> <script src="/password-toggle.js?v=2" defer></script>
<link rel="stylesheet" href="/confirm-forms.css?v=4"> <link rel="stylesheet" href="/confirm-forms.css?v=4">
@@ -8,7 +8,7 @@
<%= render "shared/meta_tags" %> <%= render "shared/meta_tags" %>
<%= render "shared/analytics_suppress" %> <%= render "shared/analytics_suppress" %>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.2/css/all.min.css" integrity="sha512-SnH5WK+bZxgPHs44uWIX+LLJAJ9/2PkPKZ5QiAj6Ta86w+fsb2TkcmfRyVX3pBnMFcV7oQPJkl9QevSCWr3W6A==" crossorigin="anonymous" referrerpolicy="no-referrer"> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.2/css/all.min.css" integrity="sha512-SnH5WK+bZxgPHs44uWIX+LLJAJ9/2PkPKZ5QiAj6Ta86w+fsb2TkcmfRyVX3pBnMFcV7oQPJkl9QevSCWr3W6A==" crossorigin="anonymous" referrerpolicy="no-referrer">
<link rel="stylesheet" href="/marketing.css?v=101"> <link rel="stylesheet" href="/marketing.css?v=102">
<link rel="stylesheet" href="/live.css?v=26"> <link rel="stylesheet" href="/live.css?v=26">
<%= yield :head %> <%= yield :head %>
</head> </head>
@@ -37,8 +37,12 @@
</span> </span>
</td> </td>
<td class="tournament-board__actions"> <td class="tournament-board__actions">
<% if live_session %> <% watch = tournament_public_watch_target(live_session) %>
<%= link_to t("tournaments.page.watch_live"), public_live_path(live_session), class: "tournament-board__cta tournament-board__cta--live" %> <% if watch %>
<% url, label, html_opts = watch %>
<%= link_to label, url, { class: "tournament-board__cta tournament-board__cta--live" }.merge(html_opts) %>
<% elsif live_session %>
<span class="muted"><%= t("tournaments.page.no_media") %></span>
<% elsif recording %> <% elsif recording %>
<%= link_to t("tournaments.page.watch_replay"), public_replay_path(recording.stream_session_id), class: "tournament-board__cta" %> <%= link_to t("tournaments.page.watch_replay"), public_replay_path(recording.stream_session_id), class: "tournament-board__cta" %>
<% else %> <% else %>
@@ -64,7 +64,11 @@
<span class="badge badge-live"><%= t("live.index.badge_live") %></span> <span class="badge badge-live"><%= t("live.index.badge_live") %></span>
<% end %> <% end %>
</div> </div>
<%= link_to t("tournaments.page.watch_live"), public_live_path(session), class: "btn-watch" %> <% watch = tournament_public_watch_target(session) %>
<% if watch %>
<% url, label, html_opts = watch %>
<%= link_to label, url, { class: "btn-watch" }.merge(html_opts) %>
<% end %>
</article> </article>
<% end %> <% end %>
</div> </div>
@@ -8,40 +8,35 @@
<% if @matches.any? %> <% if @matches.any? %>
<p class="tournament-hub-hint" style="margin-top:16px"><%= t("tournaments.hub.calendar_edit_hint") %></p> <p class="tournament-hub-hint" style="margin-top:16px"><%= t("tournaments.hub.calendar_edit_hint") %></p>
<div class="tournament-table-wrap"> <div class="tournament-match-list tournament-match-table" data-swap-error="<%= t("flash.tournaments.sides_swap_failed") %>">
<table class="data tournament-match-table" data-swap-error="<%= t("flash.tournaments.sides_swap_failed") %>">
<thead>
<tr>
<th><%= t("tournaments.hub.datetime") %></th>
<th><%= t("tournaments.hub.court") %></th>
<th><%= t("tournaments.hub.round") %></th>
<th><%= t("tournaments.hub.home") %></th>
<th class="tournament-match-swap-col"></th>
<th><%= t("tournaments.hub.away") %></th>
<th><%= t("tournaments.hub.result") %></th>
<th></th>
</tr>
</thead>
<tbody>
<% @matches.each do |match| %> <% @matches.each do |match| %>
<% form_id = "match-edit-#{match.id}" %> <% form_id = "match-edit-#{match.id}" %>
<% courts = (@tournament.court_list + [match.court]).compact_blank.uniq %> <% courts = (@tournament.court_list + [match.court]).compact_blank.uniq %>
<%= form_with url: public_tournament_match_path(@tournament, match), method: :patch, html: { id: form_id, class: "visually-hidden" } do %> <%= form_with url: public_tournament_match_path(@tournament, match), method: :patch, html: { id: form_id, class: "visually-hidden" } do %>
<%= hidden_field_tag :tab, @tab, form: form_id %> <%= hidden_field_tag :tab, @tab, form: form_id %>
<% end %> <% end %>
<tr> <article class="tournament-match-row">
<% if @writable %> <% if @writable %>
<td> <div class="tournament-match-row__field">
<label for="match_at_<%= match.id %>"><%= t("tournaments.hub.datetime") %></label>
<%= datetime_local_field_tag "match[scheduled_at]", tournament_datetime_local(match.scheduled_at), <%= datetime_local_field_tag "match[scheduled_at]", tournament_datetime_local(match.scheduled_at),
id: "match_at_#{match.id}", form: form_id, required: true, class: "tournament-match-form__datetime" %> id: "match_at_#{match.id}", form: form_id, required: true, class: "tournament-match-form__datetime" %>
</td> </div>
<td> <div class="tournament-match-row__field">
<label for="match_court_<%= match.id %>"><%= t("tournaments.hub.court") %></label>
<%= select_tag "match[court]", options_for_select(courts, match.court), <%= select_tag "match[court]", options_for_select(courts, match.court),
id: "match_court_#{match.id}", form: form_id, include_blank: true, class: "tournament-match-form__court" %> id: "match_court_#{match.id}", form: form_id, include_blank: true, class: "tournament-match-form__court" %>
</td> </div>
<td><%= tournament_phase_label(match) %></td> <div class="tournament-match-row__field tournament-match-row__field--phase">
<td data-swap-side="home"><%= render "public/tournaments/match_side_select", match: match, side: :home, form_id: form_id %></td> <span class="tournament-match-row__label"><%= t("tournaments.hub.round") %></span>
<td class="tournament-match-swap-col"> <p class="tournament-match-row__phase"><%= tournament_phase_label(match) %></p>
</div>
<div class="tournament-match-row__sides">
<div class="tournament-match-row__side">
<span class="tournament-match-row__label"><%= t("tournaments.hub.home") %></span>
<div data-swap-side="home"><%= render "public/tournaments/match_side_select", match: match, side: :home, form_id: form_id %></div>
</div>
<div class="tournament-match-swap-col">
<%= button_to public_swap_tournament_match_path(@tournament, match), method: :post, <%= button_to public_swap_tournament_match_path(@tournament, match), method: :post,
class: "tournament-match-swap__btn", class: "tournament-match-swap__btn",
title: t("tournaments.hub.swap_sides"), title: t("tournaments.hub.swap_sides"),
@@ -49,11 +44,15 @@
<i class="fa-solid fa-right-left" aria-hidden="true"></i> <i class="fa-solid fa-right-left" aria-hidden="true"></i>
<span class="visually-hidden"><%= t("tournaments.hub.swap_sides") %></span> <span class="visually-hidden"><%= t("tournaments.hub.swap_sides") %></span>
<% end %> <% end %>
</td> </div>
<td data-swap-side="away"><%= render "public/tournaments/match_side_select", match: match, side: :away, form_id: form_id %></td> <div class="tournament-match-row__side">
<td> <span class="tournament-match-row__label"><%= t("tournaments.hub.away") %></span>
<div data-swap-side="away"><%= render "public/tournaments/match_side_select", match: match, side: :away, form_id: form_id %></div>
</div>
<div class="tournament-match-row__score">
<span class="tournament-match-row__label"><%= t("tournaments.hub.result") %></span>
<% if match.played? || match.result_status.to_s.start_with?("walkover") %> <% if match.played? || match.result_status.to_s.start_with?("walkover") %>
<span data-swap-played-score><%= match.home_score %><%= match.away_score %></span> <p data-swap-played-score><%= match.home_score %><%= match.away_score %></p>
<% else %> <% else %>
<span class="tournament-match-form__score"> <span class="tournament-match-form__score">
<%= number_field_tag :home_score, match.home_score, id: "home_score_#{match.id}", form: form_id, data: { swap_score: "home" } %> <%= number_field_tag :home_score, match.home_score, id: "home_score_#{match.id}", form: form_id, data: { swap_score: "home" } %>
@@ -61,35 +60,53 @@
<%= number_field_tag :away_score, match.away_score, id: "away_score_#{match.id}", form: form_id, data: { swap_score: "away" } %> <%= number_field_tag :away_score, match.away_score, id: "away_score_#{match.id}", form: form_id, data: { swap_score: "away" } %>
</span> </span>
<% end %> <% end %>
</td> </div>
<td class="tournament-match-form__actions"> </div>
<div class="tournament-match-form__actions tournament-match-row__actions">
<%= submit_tag t("tournaments.hub.save_match"), form: form_id, class: "btn btn-secondary tournament-match-form__save" %> <%= submit_tag t("tournaments.hub.save_match"), form: form_id, class: "btn btn-secondary tournament-match-form__save" %>
<% if match.deletable? %> <% if match.deletable? %>
<%= button_to t("matches.index.delete"), public_tournament_match_path(@tournament, match), <%= button_to t("matches.index.delete"), public_tournament_match_path(@tournament, match),
method: :delete, class: "btn btn-secondary tournament-match-form__save", method: :delete, class: "btn btn-secondary tournament-match-form__save",
form: { class: "tournament-match-delete-form", data: { turbo_confirm: match.matchup_label } } %> form: { class: "tournament-match-delete-form", data: { turbo_confirm: match.matchup_label } } %>
<% end %> <% end %>
</td> </div>
<% else %> <% else %>
<td><%= match.scheduled_at ? l(match.scheduled_at, format: :short) : "—" %></td> <div class="tournament-match-row__field">
<td><%= match.court || "—" %></td> <span class="tournament-match-row__label"><%= t("tournaments.hub.datetime") %></span>
<td><%= tournament_phase_label(match) %></td> <p class="tournament-match-row__phase"><%= match.scheduled_at ? l(match.scheduled_at, format: :short) : "—" %></p>
<td><%= tournament_team_chip(match.home_participant, name: match.home_display_name) %></td> </div>
<td class="tournament-match-swap-col"></td> <div class="tournament-match-row__field">
<td><%= tournament_team_chip(match.away_participant, name: match.away_display_name) %></td> <span class="tournament-match-row__label"><%= t("tournaments.hub.court") %></span>
<td> <p class="tournament-match-row__phase"><%= match.court || "—" %></p>
</div>
<div class="tournament-match-row__field tournament-match-row__field--phase">
<span class="tournament-match-row__label"><%= t("tournaments.hub.round") %></span>
<p class="tournament-match-row__phase"><%= tournament_phase_label(match) %></p>
</div>
<div class="tournament-match-row__sides">
<div class="tournament-match-row__side">
<span class="tournament-match-row__label"><%= t("tournaments.hub.home") %></span>
<%= tournament_team_chip(match.home_participant, name: match.home_display_name) %>
</div>
<div class="tournament-match-swap-col" aria-hidden="true"></div>
<div class="tournament-match-row__side">
<span class="tournament-match-row__label"><%= t("tournaments.hub.away") %></span>
<%= tournament_team_chip(match.away_participant, name: match.away_display_name) %>
</div>
<div class="tournament-match-row__score">
<span class="tournament-match-row__label"><%= t("tournaments.hub.result") %></span>
<p>
<% if match.home_score.present? %> <% if match.home_score.present? %>
<%= match.home_score %><%= match.away_score %> <%= match.home_score %><%= match.away_score %>
<% else %> <% else %>
<% end %> <% end %>
</td> </p>
<td></td> </div>
</div>
<% end %> <% end %>
</tr> </article>
<% end %> <% end %>
</tbody>
</table>
</div> </div>
<% else %> <% else %>
<p class="tournament-hub-hint" style="margin-top:16px"><%= t("tournaments.hub.no_matches") %></p> <p class="tournament-hub-hint" style="margin-top:16px"><%= t("tournaments.hub.no_matches") %></p>
+92 -3
View File
@@ -1183,6 +1183,93 @@ body.invite-editor--resizing {
.tournament-hub-hint { color: #888; margin: 0 0 12px; } .tournament-hub-hint { color: #888; margin: 0 0 12px; }
.tournament-table-wrap { overflow-x: auto; margin-top: 8px; } .tournament-table-wrap { overflow-x: auto; margin-top: 8px; }
.tournament-match-list {
display: flex;
flex-direction: column;
gap: 12px;
margin-top: 8px;
min-width: 0;
overflow-x: clip;
}
.tournament-match-row {
display: grid;
grid-template-columns: minmax(0, 1.4fr) minmax(0, 0.9fr) minmax(0, 0.7fr);
gap: 10px 12px;
align-items: end;
min-width: 0;
padding: 14px;
border: 1px solid #2a2a36;
border-radius: 12px;
background: #14141c;
}
.tournament-match-row__field,
.tournament-match-row__side,
.tournament-match-row__score {
min-width: 0;
}
.tournament-match-row__field label,
.tournament-match-row__label {
display: block;
margin-bottom: 6px;
color: #9a9aa8;
font-size: 0.75rem;
letter-spacing: 0.02em;
}
.tournament-match-row__phase {
margin: 0;
padding: 8px 0;
color: #e8e8ee;
font-size: 0.95rem;
}
.tournament-match-row__sides {
grid-column: 1 / -1;
display: grid;
grid-template-columns: minmax(0, 1fr) 44px minmax(0, 1fr) auto;
gap: 8px 10px;
align-items: end;
min-width: 0;
}
.tournament-match-row__actions {
grid-column: 1 / -1;
justify-content: flex-end;
}
.tournament-match-row .tournament-match-form__datetime,
.tournament-match-row .tournament-match-form__court,
.tournament-match-row .tournament-match-form__team {
width: 100%;
max-width: 100%;
min-width: 0;
margin-bottom: 0;
padding: 8px 10px;
font-size: 0.9rem;
box-sizing: border-box;
}
@media (max-width: 799px) {
.tournament-match-row {
grid-template-columns: 1fr 1fr;
}
.tournament-match-row__field--phase {
grid-column: 1 / -1;
}
.tournament-match-row__sides {
grid-template-columns: minmax(0, 1fr) 44px minmax(0, 1fr);
}
.tournament-match-row__score {
grid-column: 1 / -1;
}
}
@media (max-width: 560px) {
.tournament-match-row,
.tournament-match-row__sides {
grid-template-columns: minmax(0, 1fr);
}
.tournament-match-row__field--phase {
grid-column: auto;
}
.tournament-match-swap-col {
justify-self: center;
}
}
.tournament-index-actions { .tournament-index-actions {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
@@ -1205,19 +1292,19 @@ body.invite-editor--resizing {
background: #12121a; background: #12121a;
flex-shrink: 0; flex-shrink: 0;
} }
.tournament-match-table .tournament-match-form__datetime { table.tournament-match-table .tournament-match-form__datetime {
min-width: 190px; min-width: 190px;
margin-bottom: 0; margin-bottom: 0;
padding: 8px 10px; padding: 8px 10px;
font-size: 0.9rem; font-size: 0.9rem;
} }
.tournament-match-table .tournament-match-form__court { table.tournament-match-table .tournament-match-form__court {
min-width: 120px; min-width: 120px;
margin-bottom: 0; margin-bottom: 0;
padding: 8px 10px; padding: 8px 10px;
font-size: 0.9rem; font-size: 0.9rem;
} }
.tournament-match-table .tournament-match-form__team { table.tournament-match-table .tournament-match-form__team {
min-width: 160px; min-width: 160px;
max-width: 220px; max-width: 220px;
margin-bottom: 0; margin-bottom: 0;
@@ -1260,6 +1347,8 @@ body.invite-editor--resizing {
padding: 4px 10px; padding: 4px 10px;
font-size: 0.8rem; font-size: 0.8rem;
margin: 0; margin: 0;
flex-shrink: 0;
white-space: nowrap;
} }
.tournament-match-swap-col { .tournament-match-swap-col {
width: 44px; width: 44px;
+1 -1
View File
@@ -10,7 +10,7 @@
event.stopImmediatePropagation(); event.stopImmediatePropagation();
if (form.dataset.swapBusy === "1") return; if (form.dataset.swapBusy === "1") return;
var row = form.closest("tr"); var row = form.closest(".tournament-match-row") || form.closest("tr");
if (!row) return; if (!row) return;
form.dataset.swapBusy = "1"; form.dataset.swapBusy = "1";
@@ -366,6 +366,7 @@ RSpec.describe "Tournaments", type: :request do
get public_tournament_path(tournament, tab: "calendario") get public_tournament_path(tournament, tab: "calendario")
expect(response.body).to include("tournament-match-swap__btn") expect(response.body).to include("tournament-match-swap__btn")
expect(response.body).to include("tournament-match-row")
expect(response.body).to include(public_swap_tournament_match_path(tournament, match)) expect(response.body).to include(public_swap_tournament_match_path(tournament, match))
expect(response.body).to include("tournament-calendar.js") expect(response.body).to include("tournament-calendar.js")
@@ -782,6 +783,50 @@ RSpec.describe "Tournaments", type: :request do
expect(response.body).to include(public_tournament_page_path(tournament.slug)) expect(response.body).to include(public_tournament_page_path(tournament.slug))
end end
it "marca In diretta una gara YouTube o in collegamento, non In attesa" do
assign_plan!("premium_full")
login!
tournament = Tournaments::Create.call(
club: club,
attrs: {
name: "Open Diretta YouTube",
sport_key: "basket",
starts_on: Date.current,
ends_on: Date.current,
format_kind: "free",
courts: "Campo A"
}
)
home = tournament.participants.create!(name: "Squadra 1")
away = tournament.participants.create!(name: "Squadra 3")
match = Tournaments::ScheduleMatch.call(
tournament: tournament,
attrs: {
home_participant_id: home.id,
away_participant_id: away.id,
court: "Campo A",
scheduled_at: 30.minutes.ago
}
)
post public_publish_tournament_path(tournament)
session = StreamSession.create!(
match: match, user: coach, platform: "youtube", status: "connecting",
privacy_status: "unlisted", youtube_broadcast_id: "ytLiveBoard1"
)
get public_tournament_page_path(tournament.slug)
expect(response.body).to include("tournament-live-status--live")
expect(response.body).to include("In diretta")
expect(response.body).not_to include("tournament-live-status--waiting")
expect(response.body).to include("Guarda su YouTube")
expect(response.body).to include(session.youtube_watch_url)
get public_tournament_path(tournament, tab: "dirette")
expect(response.body).to include("tournament-live-status--live")
expect(response.body).to include("In diretta")
expect(response.body).not_to include("tournament-live-status--waiting_operator")
end
it "marca Terminata una gara con risultato anche se l'orario è ancora futuro" do it "marca Terminata una gara con risultato anche se l'orario è ancora futuro" do
assign_plan!("premium_full") assign_plan!("premium_full")
login! login!
@@ -114,34 +114,38 @@ class CompactScoreboardElement : OverlayElement {
val accentColor = if (isHome) compact.homeAccentColor else compact.awayAccentColor val accentColor = if (isHome) compact.homeAccentColor else compact.awayAccentColor
val logoUrl = if (isHome) compact.homeLogoUrl else compact.awayLogoUrl val logoUrl = if (isHome) compact.homeLogoUrl else compact.awayLogoUrl
val logo = OverlayLogoCache.get(logoUrl) val logo = OverlayLogoCache.get(logoUrl)
val rawName = if (isHome) compact.homeTeamName else compact.awayTeamName val rawName = (if (isHome) compact.homeTeamName else compact.awayTeamName).trim()
val name = ellipsize(rawName.trim(), labelPaint, maxWidth * 0.55f) val barHalfH = logoSize * 0.45f
val textY = centerY + labelPaint.textSize * 0.35f
if (isHome) { if (isHome) {
var x = rectLeft var x = rectLeft
accentBarPaint.color = accentColor accentBarPaint.color = accentColor
canvas.drawRect(x, centerY - logoSize * 0.45f, x + barW, centerY + logoSize * 0.45f, accentBarPaint) canvas.drawRect(x, centerY - barHalfH, x + barW, centerY + barHalfH, accentBarPaint)
x += barW + gap x += barW + gap
if (logo != null) { if (logo != null) {
val dest = RectF(x, centerY - logoSize / 2f, x + logoSize, centerY + logoSize / 2f) val dest = RectF(x, centerY - logoSize / 2f, x + logoSize, centerY + logoSize / 2f)
canvas.drawBitmap(logo, null, dest, logoPaint) canvas.drawBitmap(logo, null, dest, logoPaint)
x += logoSize + gap x += logoSize + gap
} }
canvas.drawText(name, x, centerY + labelPaint.textSize * 0.35f, labelPaint) val name = ellipsize(rawName, labelPaint, (rectRight - x).coerceAtMost(maxWidth))
canvas.drawText(name, x, textY, labelPaint)
} else { } else {
// Specchio del lato casa: barra, logo, poi nome a sinistra del logo.
var x = rectRight var x = rectRight
accentBarPaint.color = accentColor accentBarPaint.color = accentColor
canvas.drawRect(x - barW, centerY - logoSize * 0.45f, x, centerY + logoSize * 0.45f, accentBarPaint) canvas.drawRect(x - barW, centerY - barHalfH, x, centerY + barHalfH, accentBarPaint)
x -= barW + gap x -= barW + gap
labelPaint.textAlign = Paint.Align.RIGHT
canvas.drawText(name, x, centerY + labelPaint.textSize * 0.35f, labelPaint)
labelPaint.textAlign = Paint.Align.LEFT
x -= gap
if (logo != null) { if (logo != null) {
x -= logoSize x -= logoSize
val dest = RectF(x, centerY - logoSize / 2f, x + logoSize, centerY + logoSize / 2f) val dest = RectF(x, centerY - logoSize / 2f, x + logoSize, centerY + logoSize / 2f)
canvas.drawBitmap(logo, null, dest, logoPaint) canvas.drawBitmap(logo, null, dest, logoPaint)
x -= gap
} }
val name = ellipsize(rawName, labelPaint, (x - rectLeft).coerceAtMost(maxWidth))
labelPaint.textAlign = Paint.Align.RIGHT
canvas.drawText(name, x, textY, labelPaint)
labelPaint.textAlign = Paint.Align.LEFT
} }
} }
@@ -67,32 +67,37 @@ struct CompactScoreboardElement: OverlayElement {
let accent = isHome ? compact.homeAccentColor : compact.awayAccentColor let accent = isHome ? compact.homeAccentColor : compact.awayAccentColor
let logoUrl = isHome ? compact.homeLogoUrl : compact.awayLogoUrl let logoUrl = isHome ? compact.homeLogoUrl : compact.awayLogoUrl
let logo = OverlayLogoCache.get(logoUrl) let logo = OverlayLogoCache.get(logoUrl)
let rawName = isHome ? compact.homeTeamName : compact.awayTeamName let rawName = (isHome ? compact.homeTeamName : compact.awayTeamName)
let name = ellipsize(rawName.trimmingCharacters(in: .whitespacesAndNewlines), font: labelFont, maxWidth: maxWidth * 0.55) .trimmingCharacters(in: .whitespacesAndNewlines)
let attrs: [NSAttributedString.Key: Any] = [.font: labelFont, .foregroundColor: UIColor.white] let attrs: [NSAttributedString.Key: Any] = [.font: labelFont, .foregroundColor: UIColor.white]
let textY = centerY + labelFont.pointSize * 0.35 - labelFont.lineHeight
let barHalfH = CGFloat(logoSize) * 0.45
if isHome { if isHome {
var x = rectLeft var x = rectLeft
context.setFillColor(accent.cgColor) context.setFillColor(accent.cgColor)
context.fill(CGRect(x: x, y: centerY - CGFloat(logoSize) * 0.45, width: CGFloat(barW), height: CGFloat(logoSize) * 0.9)) context.fill(CGRect(x: x, y: centerY - barHalfH, width: CGFloat(barW), height: CGFloat(logoSize) * 0.9))
x += CGFloat(barW + gap) x += CGFloat(barW + gap)
if let logo, let cg = logo.cgImage { if let logo, let cg = logo.cgImage {
context.draw(cg, in: CGRect(x: x, y: centerY - CGFloat(logoSize) / 2, width: CGFloat(logoSize), height: CGFloat(logoSize))) context.draw(cg, in: CGRect(x: x, y: centerY - CGFloat(logoSize) / 2, width: CGFloat(logoSize), height: CGFloat(logoSize)))
x += CGFloat(logoSize + gap) x += CGFloat(logoSize + gap)
} }
(name as NSString).draw(at: CGPoint(x: x, y: centerY + labelFont.pointSize * 0.35 - labelFont.lineHeight), withAttributes: attrs) let name = ellipsize(rawName, font: labelFont, maxWidth: min(rectRight - x, maxWidth))
(name as NSString).draw(at: CGPoint(x: x, y: textY), withAttributes: attrs)
} else { } else {
// Specchio del lato casa: barra, logo, poi nome a sinistra del logo.
var x = rectRight var x = rectRight
context.setFillColor(accent.cgColor) context.setFillColor(accent.cgColor)
context.fill(CGRect(x: x - CGFloat(barW), y: centerY - CGFloat(logoSize) * 0.45, width: CGFloat(barW), height: CGFloat(logoSize) * 0.9)) context.fill(CGRect(x: x - CGFloat(barW), y: centerY - barHalfH, width: CGFloat(barW), height: CGFloat(logoSize) * 0.9))
x -= CGFloat(barW + gap) x -= CGFloat(barW + gap)
let nameSize = (name as NSString).size(withAttributes: attrs)
(name as NSString).draw(at: CGPoint(x: x - nameSize.width, y: centerY + labelFont.pointSize * 0.35 - labelFont.lineHeight), withAttributes: attrs)
x -= CGFloat(gap)
if let logo, let cg = logo.cgImage { if let logo, let cg = logo.cgImage {
x -= CGFloat(logoSize) x -= CGFloat(logoSize)
context.draw(cg, in: CGRect(x: x, y: centerY - CGFloat(logoSize) / 2, width: CGFloat(logoSize), height: CGFloat(logoSize))) context.draw(cg, in: CGRect(x: x, y: centerY - CGFloat(logoSize) / 2, width: CGFloat(logoSize), height: CGFloat(logoSize)))
x -= CGFloat(gap)
} }
let name = ellipsize(rawName, font: labelFont, maxWidth: min(x - rectLeft, maxWidth))
let nameSize = (name as NSString).size(withAttributes: attrs)
(name as NSString).draw(at: CGPoint(x: x - nameSize.width, y: textY), withAttributes: attrs)
} }
} }