mirror of
https://github.com/jcwimer/wrestlingApp
synced 2026-03-25 01:14:43 +00:00
Added Stimulus and moved the matstats vanilla js to stimulus controllers. Same with the spectate page.
This commit is contained in:
@@ -10,128 +10,146 @@
|
||||
</ul>
|
||||
</div>
|
||||
<% end %>
|
||||
<div id="cable-status-indicator" class="alert alert-secondary" style="padding: 5px; margin-bottom: 10px; border-radius: 4px;"></div>
|
||||
|
||||
<div data-controller="match-data"
|
||||
data-match-data-tournament-id-value="<%= @match.tournament.id %>"
|
||||
data-match-data-bout-number-value="<%= @match.bout_number %>"
|
||||
data-match-data-match-id-value="<%= @match.id %>">
|
||||
|
||||
<div id="cable-status-indicator" data-match-data-target="statusIndicator" class="alert alert-secondary" style="padding: 5px; margin-bottom: 10px; border-radius: 4px;"></div>
|
||||
<h4>Bout <strong><%= @match.bout_number %></strong></h4>
|
||||
<% if @show_next_bout_button && @next_match %>
|
||||
<%= link_to "Skip to Next Match for Mat #{@mat.name}", mat_path(@mat, bout_number: @next_match.bout_number), class: "btn btn-primary" %>
|
||||
<% end %>
|
||||
<h4>Bracket Position: <strong><%= @match.bracket_position %></strong></h4>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name: <%= @wrestler1_name %> <select id="w1-color" onchange="changeW1Color(this)">
|
||||
<option value="green">Green</option>
|
||||
<option value="red">Red</option>
|
||||
</select>
|
||||
<br>School: <%= @wrestler1_school_name %>
|
||||
<br>Last Match: <%= @wrestler1_last_match && @wrestler1_last_match.finished_at ? time_ago_in_words(@wrestler1_last_match.finished_at) : "N/A" %></th>
|
||||
<th>Name: <%= @wrestler2_name %> <select id="w2-color" onchange="changeW2Color(this)">
|
||||
<option value="red">Red</option>
|
||||
<option value="green">Green</option>
|
||||
</select>
|
||||
<br>School: <%= @wrestler2_school_name %>
|
||||
<br>Last Match: <%= @wrestler2_last_match && @wrestler2_last_match.finished_at ? time_ago_in_words(@wrestler2_last_match.finished_at) : "N/A" %></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><%= @wrestler1_name %> Stats: <br><%= f.text_area :w1_stat, cols: "30", rows: "10" %></td>
|
||||
<td><%= @wrestler2_name %> Stats: <br><%= f.text_area :w2_stat, cols: "30", rows: "10" %></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><%= @wrestler1_name %> Scoring <br><button id="w1-takedown" type="button" class="btn btn-success btn-sm" onclick="updateStats(w1,'T3');updateStats(w2,'__');">T3</button>
|
||||
<button id="w1-escape" type="button" class="btn btn-success btn-sm" onclick="updateStats(w1,'E1');updateStats(w2,'__');">E1</button>
|
||||
<button id="w1-reversal" type="button" class="btn btn-success btn-sm" onclick="updateStats(w1,'R2');updateStats(w2,'__');">R2</button>
|
||||
<button id="w1-nf2" type="button" class="btn btn-success btn-sm" onclick="updateStats(w1,'N2');updateStats(w2,'__');">N2 </button>
|
||||
<button id="w1-nf3" type="button" class="btn btn-success btn-sm" onclick="updateStats(w1,'N3');updateStats(w2,'__');">N3</button>
|
||||
<button id="w1-nf4" type="button" class="btn btn-success btn-sm" onclick="updateStats(w1,'N4');updateStats(w2,'__');">N4</button>
|
||||
<button id="w1-nf5" type="button" class="btn btn-success btn-sm" onclick="updateStats(w1,'N5');updateStats(w2,'__');">N5</button>
|
||||
<button id="w1-penalty" type="button" class="btn btn-success btn-sm" onclick="updateStats(w1,'P1');updateStats(w2,'__');">P1</button>
|
||||
<button id="w1-penalty2" type="button" class="btn btn-success btn-sm" onclick="updateStats(w1,'P2');updateStats(w2,'__');">P2</button></td>
|
||||
<td><%= @wrestler2_name %> Scoring <br><button id="w2-takedown" type="button" class="btn btn-danger btn-sm" onclick="updateStats(w2,'T3');updateStats(w1,'__');">T3</button>
|
||||
<button id="w2-escape" type="button" class="btn btn-danger btn-sm" onclick="updateStats(w2,'E1');updateStats(w1,'__');">E1</button>
|
||||
<button id="w2-reversal" type="button" class="btn btn-danger btn-sm" onclick="updateStats(w2,'R2');updateStats(w1,'__');">R2</button>
|
||||
<button id="w2-nf2" type="button" class="btn btn-danger btn-sm" onclick="updateStats(w2,'N2');updateStats(w1,'__');">N2</button>
|
||||
<button id="w2-nf3" type="button" class="btn btn-danger btn-sm" onclick="updateStats(w2,'N3');updateStats(w1,'__');">N3</button>
|
||||
<button id="w2-nf4" type="button" class="btn btn-danger btn-sm" onclick="updateStats(w2,'N4');updateStats(w1,'__');">N4</button>
|
||||
<button id="w2-nf5" type="button" class="btn btn-danger btn-sm" onclick="updateStats(w2,'N5');updateStats(w1,'__');">N5</button>
|
||||
<button id="w2-penalty" type="button" class="btn btn-danger btn-sm" onclick="updateStats(w2,'P1');updateStats(w1,'__');">P1</button>
|
||||
<button id="w2-penalty2" type="button" class="btn btn-danger btn-sm" onclick="updateStats(w2,'P2');updateStats(w1,'__');">P2</button></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><%= @wrestler1_name %> Choice <br><button id="w1-top" type="button" class="btn btn-success btn-sm" onclick="updateStats(w1,'|Chose Top|')">Chose Top</button>
|
||||
<button id="w1-bottom" type="button" class="btn btn-success btn-sm" onclick="updateStats(w1,'|Chose Bottom|')">Chose Bottom</button>
|
||||
<button id="w1-neutral" type="button" class="btn btn-success btn-sm" onclick="updateStats(w1,'|Chose Neutral|')">Chose Neutral</button>
|
||||
<button id="w1-defer" type="button" class="btn btn-success btn-sm" onclick="updateStats(w1,'|Deferred|')">Deferred</button></td>
|
||||
<td><%= @wrestler2_name %> Choice <br><button id="w2-top" type="button" class="btn btn-danger btn-sm" onclick="updateStats(w2,'|Chose Top|')">Chose Top</button>
|
||||
<button id="w2-bottom" type="button" class="btn btn-danger btn-sm" onclick="updateStats(w2,'|Chose Bottom|')">Chose Bottom</button>
|
||||
<button id="w2-neutral" type="button" class="btn btn-danger btn-sm" onclick="updateStats(w2,'|Chose Neutral|')">Chose Neutral</button>
|
||||
<button id="w2-defer" type="button" class="btn btn-danger btn-sm" onclick="updateStats(w2,'|Deferred|')">Deferred</button></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><%= @wrestler1_name %> Warnings <br><button id="w1-stalling" type="button" class="btn btn-success btn-sm" onclick="updateStats(w1,'S');updateStats(w2,'_');">Stalling</button>
|
||||
<button id="w1-caution" type="button" class="btn btn-success btn-sm" onclick="updateStats(w1,'C');updateStats(w2,'_');">Caution</button></td>
|
||||
<td><%= @wrestler2_name %> Warnings <br><button id="w2-stalling" type="button" class="btn btn-danger btn-sm" onclick="updateStats(w2,'S');updateStats(w1,'_');">Stalling</button>
|
||||
<button id="w2-caution" type="button" class="btn btn-danger btn-sm" onclick="updateStats(w2,'C');updateStats(w1,'_');">Caution</button></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Match Options <br><button type="button" class="btn btn-primary btn-sm" onclick="updateStats(w2,'|End Period|'); updateStats(w1,'|End Period|');">End Period</button></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<h5><%= @wrestler1_name %> Timer Controls</h5>
|
||||
Injury Time (90 second max): <span id="w1-injury-time">0 sec</span>
|
||||
<button type="button" onclick="startTimer(w1, 'injury')" class="btn btn-primary btn-sm">Start</button>
|
||||
<button type="button" onclick="stopTimer(w1, 'injury')" class="btn btn-primary btn-sm">Stop</button>
|
||||
<button type="button" onclick="resetTimer(w1, 'injury')" class="btn btn-primary btn-sm">Reset</button>
|
||||
<br><br>
|
||||
Blood Time (600 second max): <span id="w1-blood-time">0 sec</span>
|
||||
<button type="button" onclick="startTimer(w1, 'blood')" class="btn btn-primary btn-sm">Start</button>
|
||||
<button type="button" onclick="stopTimer(w1, 'blood')" class="btn btn-primary btn-sm">Stop</button>
|
||||
<button type="button" onclick="resetTimer(w1, 'blood')" class="btn btn-primary btn-sm">Reset</button>
|
||||
</td>
|
||||
<td>
|
||||
<h5><%= @wrestler2_name %> Timer Controls</h5>
|
||||
Injury Time (90 second max): <span id="w2-injury-time">0 sec</span>
|
||||
<button type="button" onclick="startTimer(w2, 'injury')" class="btn btn-primary btn-sm">Start</button>
|
||||
<button type="button" onclick="stopTimer(w2, 'injury')" class="btn btn-primary btn-sm">Stop</button>
|
||||
<button type="button" onclick="resetTimer(w2, 'injury')" class="btn btn-primary btn-sm">Reset</button>
|
||||
<br><br>
|
||||
Blood Time (600 second max): <span id="w2-blood-time">0 sec</span>
|
||||
<button type="button" onclick="startTimer(w2, 'blood')" class="btn btn-primary btn-sm">Start</button>
|
||||
<button type="button" onclick="stopTimer(w2, 'blood')" class="btn btn-primary btn-sm">Stop</button>
|
||||
<button type="button" onclick="resetTimer(w2, 'blood')" class="btn btn-primary btn-sm">Reset</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<br>
|
||||
<br>
|
||||
<br>
|
||||
<h4>Match Results</h4>
|
||||
<br>
|
||||
<div class="field">
|
||||
<%= f.label "Win Type" %><br>
|
||||
<%= f.select(:win_type, Match::WIN_TYPES) %>
|
||||
</div>
|
||||
|
||||
<div data-controller="wrestler-color">
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name: <%= @wrestler1_name %> <select id="w1-color" data-wrestler-color-target="w1ColorSelect" data-action="change->wrestler-color#changeW1Color">
|
||||
<option value="green">Green</option>
|
||||
<option value="red">Red</option>
|
||||
</select>
|
||||
<br>School: <%= @wrestler1_school_name %>
|
||||
<br>Last Match: <%= @wrestler1_last_match && @wrestler1_last_match.finished_at ? time_ago_in_words(@wrestler1_last_match.finished_at) : "N/A" %></th>
|
||||
<th>Name: <%= @wrestler2_name %> <select id="w2-color" data-wrestler-color-target="w2ColorSelect" data-action="change->wrestler-color#changeW2Color">
|
||||
<option value="red">Red</option>
|
||||
<option value="green">Green</option>
|
||||
</select>
|
||||
<br>School: <%= @wrestler2_school_name %>
|
||||
<br>Last Match: <%= @wrestler2_last_match && @wrestler2_last_match.finished_at ? time_ago_in_words(@wrestler2_last_match.finished_at) : "N/A" %></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><%= @wrestler1_name %> Stats: <br><%= f.text_area :w1_stat, cols: "30", rows: "10", data: { match_data_target: "w1Stat" } %></td>
|
||||
<td><%= @wrestler2_name %> Stats: <br><%= f.text_area :w2_stat, cols: "30", rows: "10", data: { match_data_target: "w2Stat" } %></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><%= @wrestler1_name %> Scoring <br><button id="w1-takedown" type="button" class="btn btn-success btn-sm" data-wrestler-color-target="w1Takedown" data-action="click->match-data#updateW1Stats" data-match-data-text="T3">T3</button>
|
||||
<button id="w1-escape" type="button" class="btn btn-success btn-sm" data-wrestler-color-target="w1Escape" data-action="click->match-data#updateW1Stats" data-match-data-text="E1">E1</button>
|
||||
<button id="w1-reversal" type="button" class="btn btn-success btn-sm" data-wrestler-color-target="w1Reversal" data-action="click->match-data#updateW1Stats" data-match-data-text="R2">R2</button>
|
||||
<button id="w1-nf2" type="button" class="btn btn-success btn-sm" data-wrestler-color-target="w1Nf2" data-action="click->match-data#updateW1Stats" data-match-data-text="N2">N2 </button>
|
||||
<button id="w1-nf3" type="button" class="btn btn-success btn-sm" data-wrestler-color-target="w1Nf3" data-action="click->match-data#updateW1Stats" data-match-data-text="N3">N3</button>
|
||||
<button id="w1-nf4" type="button" class="btn btn-success btn-sm" data-wrestler-color-target="w1Nf4" data-action="click->match-data#updateW1Stats" data-match-data-text="N4">N4</button>
|
||||
<button id="w1-nf5" type="button" class="btn btn-success btn-sm" data-wrestler-color-target="w1Nf5" data-action="click->match-data#updateW1Stats" data-match-data-text="N5">N5</button>
|
||||
<button id="w1-penalty" type="button" class="btn btn-success btn-sm" data-wrestler-color-target="w1Penalty" data-action="click->match-data#updateW1Stats" data-match-data-text="P1">P1</button>
|
||||
<button id="w1-penalty2" type="button" class="btn btn-success btn-sm" data-wrestler-color-target="w1Penalty2" data-action="click->match-data#updateW1Stats" data-match-data-text="P2">P2</button></td>
|
||||
<td><%= @wrestler2_name %> Scoring <br><button id="w2-takedown" type="button" class="btn btn-danger btn-sm" data-wrestler-color-target="w2Takedown" data-action="click->match-data#updateW2Stats" data-match-data-text="T3">T3</button>
|
||||
<button id="w2-escape" type="button" class="btn btn-danger btn-sm" data-wrestler-color-target="w2Escape" data-action="click->match-data#updateW2Stats" data-match-data-text="E1">E1</button>
|
||||
<button id="w2-reversal" type="button" class="btn btn-danger btn-sm" data-wrestler-color-target="w2Reversal" data-action="click->match-data#updateW2Stats" data-match-data-text="R2">R2</button>
|
||||
<button id="w2-nf2" type="button" class="btn btn-danger btn-sm" data-wrestler-color-target="w2Nf2" data-action="click->match-data#updateW2Stats" data-match-data-text="N2">N2</button>
|
||||
<button id="w2-nf3" type="button" class="btn btn-danger btn-sm" data-wrestler-color-target="w2Nf3" data-action="click->match-data#updateW2Stats" data-match-data-text="N3">N3</button>
|
||||
<button id="w2-nf4" type="button" class="btn btn-danger btn-sm" data-wrestler-color-target="w2Nf4" data-action="click->match-data#updateW2Stats" data-match-data-text="N4">N4</button>
|
||||
<button id="w2-nf5" type="button" class="btn btn-danger btn-sm" data-wrestler-color-target="w2Nf5" data-action="click->match-data#updateW2Stats" data-match-data-text="N5">N5</button>
|
||||
<button id="w2-penalty" type="button" class="btn btn-danger btn-sm" data-wrestler-color-target="w2Penalty" data-action="click->match-data#updateW2Stats" data-match-data-text="P1">P1</button>
|
||||
<button id="w2-penalty2" type="button" class="btn btn-danger btn-sm" data-wrestler-color-target="w2Penalty2" data-action="click->match-data#updateW2Stats" data-match-data-text="P2">P2</button></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><%= @wrestler1_name %> Choice <br><button id="w1-top" type="button" class="btn btn-success btn-sm" data-wrestler-color-target="w1Top" data-action="click->match-data#updateW1Stats" data-match-data-text="|Chose Top|">Chose Top</button>
|
||||
<button id="w1-bottom" type="button" class="btn btn-success btn-sm" data-wrestler-color-target="w1Bottom" data-action="click->match-data#updateW1Stats" data-match-data-text="|Chose Bottom|">Chose Bottom</button>
|
||||
<button id="w1-neutral" type="button" class="btn btn-success btn-sm" data-wrestler-color-target="w1Neutral" data-action="click->match-data#updateW1Stats" data-match-data-text="|Chose Neutral|">Chose Neutral</button>
|
||||
<button id="w1-defer" type="button" class="btn btn-success btn-sm" data-wrestler-color-target="w1Defer" data-action="click->match-data#updateW1Stats" data-match-data-text="|Deferred|">Deferred</button></td>
|
||||
<td><%= @wrestler2_name %> Choice <br><button id="w2-top" type="button" class="btn btn-danger btn-sm" data-wrestler-color-target="w2Top" data-action="click->match-data#updateW2Stats" data-match-data-text="|Chose Top|">Chose Top</button>
|
||||
<button id="w2-bottom" type="button" class="btn btn-danger btn-sm" data-wrestler-color-target="w2Bottom" data-action="click->match-data#updateW2Stats" data-match-data-text="|Chose Bottom|">Chose Bottom</button>
|
||||
<button id="w2-neutral" type="button" class="btn btn-danger btn-sm" data-wrestler-color-target="w2Neutral" data-action="click->match-data#updateW2Stats" data-match-data-text="|Chose Neutral|">Chose Neutral</button>
|
||||
<button id="w2-defer" type="button" class="btn btn-danger btn-sm" data-wrestler-color-target="w2Defer" data-action="click->match-data#updateW2Stats" data-match-data-text="|Deferred|">Deferred</button></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><%= @wrestler1_name %> Warnings <br><button id="w1-stalling" type="button" class="btn btn-success btn-sm" data-wrestler-color-target="w1Stalling" data-action="click->match-data#updateW1Stats" data-match-data-text="S">Stalling</button>
|
||||
<button id="w1-caution" type="button" class="btn btn-success btn-sm" data-wrestler-color-target="w1Caution" data-action="click->match-data#updateW1Stats" data-match-data-text="C">Caution</button></td>
|
||||
<td><%= @wrestler2_name %> Warnings <br><button id="w2-stalling" type="button" class="btn btn-danger btn-sm" data-wrestler-color-target="w2Stalling" data-action="click->match-data#updateW2Stats" data-match-data-text="S">Stalling</button>
|
||||
<button id="w2-caution" type="button" class="btn btn-danger btn-sm" data-wrestler-color-target="w2Caution" data-action="click->match-data#updateW2Stats" data-match-data-text="C">Caution</button></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Match Options <br><button type="button" class="btn btn-primary btn-sm" data-action="click->match-data#endPeriod">End Period</button></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<h5><%= @wrestler1_name %> Timer Controls</h5>
|
||||
Injury Time (90 second max): <span id="w1-injury-time">0 sec</span>
|
||||
<button type="button" data-action="click->match-data#startW1InjuryTimer" class="btn btn-primary btn-sm">Start</button>
|
||||
<button type="button" data-action="click->match-data#stopW1InjuryTimer" class="btn btn-primary btn-sm">Stop</button>
|
||||
<button type="button" data-action="click->match-data#resetW1InjuryTimer" class="btn btn-primary btn-sm">Reset</button>
|
||||
<br><br>
|
||||
Blood Time (600 second max): <span id="w1-blood-time">0 sec</span>
|
||||
<button type="button" data-action="click->match-data#startW1BloodTimer" class="btn btn-primary btn-sm">Start</button>
|
||||
<button type="button" data-action="click->match-data#stopW1BloodTimer" class="btn btn-primary btn-sm">Stop</button>
|
||||
<button type="button" data-action="click->match-data#resetW1BloodTimer" class="btn btn-primary btn-sm">Reset</button>
|
||||
</td>
|
||||
<td>
|
||||
<h5><%= @wrestler2_name %> Timer Controls</h5>
|
||||
Injury Time (90 second max): <span id="w2-injury-time">0 sec</span>
|
||||
<button type="button" data-action="click->match-data#startW2InjuryTimer" class="btn btn-primary btn-sm">Start</button>
|
||||
<button type="button" data-action="click->match-data#stopW2InjuryTimer" class="btn btn-primary btn-sm">Stop</button>
|
||||
<button type="button" data-action="click->match-data#resetW2InjuryTimer" class="btn btn-primary btn-sm">Reset</button>
|
||||
<br><br>
|
||||
Blood Time (600 second max): <span id="w2-blood-time">0 sec</span>
|
||||
<button type="button" data-action="click->match-data#startW2BloodTimer" class="btn btn-primary btn-sm">Start</button>
|
||||
<button type="button" data-action="click->match-data#stopW2BloodTimer" class="btn btn-primary btn-sm">Stop</button>
|
||||
<button type="button" data-action="click->match-data#resetW2BloodTimer" class="btn btn-primary btn-sm">Reset</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div><!-- End of wrestler-color controller div -->
|
||||
|
||||
<br>
|
||||
<div class="field">
|
||||
<%= f.label "Overtime Type" %> Leave blank if not overtime. For High School the 1st overtime is SV-1, second overtime is TB-1, third overtime is UTB.<br>
|
||||
<%= f.select(:overtime_type, Match::OVERTIME_TYPES) %>
|
||||
</div>
|
||||
<br>
|
||||
<div class="field">
|
||||
<%= f.label "Winner" %> Please choose the winner<br>
|
||||
<%= f.collection_select :winner_id, @wrestlers, :id, :name_with_school, include_blank: true %>
|
||||
</div>
|
||||
<br>
|
||||
<% if @match.finished && @match.finished == 1 %>
|
||||
<h4>Match Results</h4>
|
||||
<br>
|
||||
<div data-controller="match-score">
|
||||
<div class="field">
|
||||
<%= f.label "Final Score" %> For decision, major, or tech fall put the score here in Number-Number format. If pin, put the accumulated pin time in the format MM:SS. If default, injury default, dq, bye, or forfeit, leave blank. Examples: 7-2, 17-2, 0:30, or 2:34.<br>
|
||||
<%= f.text_field :score %>
|
||||
<%= f.label "Win type" %><br>
|
||||
<%= f.select :win_type, Match::WIN_TYPES, { include_blank: false }, {
|
||||
data: {
|
||||
match_score_target: "winType",
|
||||
action: "change->match-score#winTypeChanged"
|
||||
}
|
||||
} %>
|
||||
</div>
|
||||
<% else %>
|
||||
<br>
|
||||
<div class="field">
|
||||
<%= f.label "Overtime Type" %> Leave blank if not overtime. For High School the 1st overtime is SV-1, second overtime is TB-1, third overtime is UTB.<br>
|
||||
<%= f.select(:overtime_type, Match::OVERTIME_TYPES) %>
|
||||
</div>
|
||||
<br>
|
||||
<div class="field">
|
||||
<%= f.label "Winner" %> Please choose the winner<br>
|
||||
<%= f.collection_select :winner_id, @wrestlers, :id, :name_with_school,
|
||||
{ include_blank: true },
|
||||
{
|
||||
data: {
|
||||
match_score_target: "winnerSelect",
|
||||
action: "change->match-score#winnerChanged"
|
||||
}
|
||||
}
|
||||
%>
|
||||
</div>
|
||||
<br>
|
||||
<div class="field">
|
||||
<%= f.label "Final Score" %>
|
||||
<br>
|
||||
@@ -139,383 +157,27 @@
|
||||
The input will adjust based on the selected win type.
|
||||
</span>
|
||||
<br>
|
||||
<div id="dynamic-score-input"></div>
|
||||
<p id="pin-time-tip" class="text-muted mt-2" style="display: none;">
|
||||
<div id="dynamic-score-input" data-match-score-target="dynamicScoreInput"></div>
|
||||
<p id="pin-time-tip" class="text-muted mt-2" style="display: none;" data-match-score-target="pinTimeTip">
|
||||
<strong>Tip:</strong> Pin time is an accumulation over the match, not how much time was left in the current period.
|
||||
<br>For example, if all 3 periods are 2 minutes and a pin happened with 1:27 left in the second period,
|
||||
the pin time would be <strong>2:33</strong> (2 minutes for the first period + 33 seconds elapsed in the second period).
|
||||
</p>
|
||||
<div id="validation-alerts" class="text-danger mt-2"></div>
|
||||
<%= f.hidden_field :score, id: "final-score-field" %>
|
||||
<div id="validation-alerts" class="text-danger mt-2" data-match-score-target="validationAlerts"></div>
|
||||
<%= f.hidden_field :score, id: "final-score-field", data: { match_score_target: "finalScoreField" } %>
|
||||
|
||||
<br>
|
||||
<%= f.submit "Update Match", id: "update-match-btn",
|
||||
data: {
|
||||
match_score_target: "submitButton",
|
||||
action: "click->match-score#confirmWinner"
|
||||
},
|
||||
class: "btn btn-success" %>
|
||||
</div>
|
||||
<%= render 'matches/matchstats_variable_score_input' %>
|
||||
<% end %>
|
||||
<br>
|
||||
</div><!-- End of match-score controller -->
|
||||
</div><!-- End of match-data controller div -->
|
||||
|
||||
<br>
|
||||
<%= f.hidden_field :finished, :value => 1 %>
|
||||
<%= f.hidden_field :round, :value => @match.round %>
|
||||
|
||||
<br>
|
||||
|
||||
<div class="actions">
|
||||
<%= f.submit "Update Match", id: "update-match-btn", onclick: "return confirm('Is the name of the winner ' + document.getElementById('match_winner_id').options[document.getElementById('match_winner_id').selectedIndex].text + '?')", class: "btn btn-success" %>
|
||||
</div>
|
||||
|
||||
<% end %>
|
||||
|
||||
<%= render 'matches/matchstats_color_change' %>
|
||||
|
||||
<script>
|
||||
// ############### CORE STATE & HELPERS (Define First) #############
|
||||
var tournament = <%= @match.tournament.id %>;
|
||||
var bout = <%= @match.bout_number %>;
|
||||
var match_finsihed = "<%= @match.finished %>";
|
||||
|
||||
function Person(stats, name) {
|
||||
this.name = name;
|
||||
this.stats = stats;
|
||||
this.updated_at = null; // Track last updated timestamp
|
||||
this.timers = {
|
||||
"injury": { time: 0, startTime: null, interval: null },
|
||||
"blood": { time: 0, startTime: null, interval: null },
|
||||
};
|
||||
}
|
||||
var w1 = new Person("", "w1");
|
||||
var w2 = new Person("", "w2");
|
||||
|
||||
function generateKey(wrestler_name) {
|
||||
return `${wrestler_name}-${tournament}-${bout}`;
|
||||
}
|
||||
function loadFromLocalStorage(wrestler_name) {
|
||||
const key = generateKey(wrestler_name);
|
||||
const data = localStorage.getItem(key);
|
||||
return data ? JSON.parse(data) : null;
|
||||
}
|
||||
function saveToLocalStorage(person) {
|
||||
const key = generateKey(person.name);
|
||||
const data = {
|
||||
stats: person.stats,
|
||||
updated_at: person.updated_at,
|
||||
timers: person.timers, // Save all timers
|
||||
};
|
||||
localStorage.setItem(key, JSON.stringify(data));
|
||||
}
|
||||
function updateHtmlValues() {
|
||||
document.getElementById("match_w1_stat").value = w1.stats;
|
||||
document.getElementById("match_w2_stat").value = w2.stats;
|
||||
}
|
||||
function updateJsValues() {
|
||||
w1.stats = document.getElementById("match_w1_stat").value;
|
||||
w2.stats = document.getElementById("match_w2_stat").value;
|
||||
}
|
||||
function debounce(func, wait) {
|
||||
let timeout;
|
||||
return function executedFunction(...args) {
|
||||
const later = () => {
|
||||
clearTimeout(timeout);
|
||||
func(...args);
|
||||
};
|
||||
clearTimeout(timeout);
|
||||
timeout = setTimeout(later, wait);
|
||||
};
|
||||
}
|
||||
function handleTextAreaInput(textAreaElement, wrestler) {
|
||||
const newValue = textAreaElement.value;
|
||||
console.log(`Text area input detected for ${wrestler.name}:`, newValue.substring(0, 50) + "..."); // Log input
|
||||
|
||||
// Update the internal JS object
|
||||
wrestler.stats = newValue;
|
||||
wrestler.updated_at = new Date().toISOString();
|
||||
|
||||
// Save to localStorage
|
||||
saveToLocalStorage(wrestler);
|
||||
|
||||
// Send the update via Action Cable if subscribed
|
||||
if (matchSubscription) {
|
||||
let payload = {};
|
||||
if (wrestler.name === 'w1') {
|
||||
payload.new_w1_stat = wrestler.stats;
|
||||
} else if (wrestler.name === 'w2') {
|
||||
payload.new_w2_stat = wrestler.stats;
|
||||
}
|
||||
if (Object.keys(payload).length > 0) {
|
||||
console.log('[ActionCable] Performing send_stat from textarea with payload:', payload);
|
||||
matchSubscription.perform('send_stat', payload);
|
||||
}
|
||||
}
|
||||
}
|
||||
function updateStats(wrestler, text) {
|
||||
if (!wrestler) { console.error("updateStats called with undefined wrestler"); return; }
|
||||
wrestler.stats += text + " ";
|
||||
wrestler.updated_at = new Date().toISOString();
|
||||
updateHtmlValues();
|
||||
saveToLocalStorage(wrestler);
|
||||
|
||||
// Reference the global matchSubscription
|
||||
if (matchSubscription) {
|
||||
let payload = {};
|
||||
if (wrestler.name === 'w1') payload.new_w1_stat = wrestler.stats;
|
||||
else if (wrestler.name === 'w2') payload.new_w2_stat = wrestler.stats;
|
||||
if (Object.keys(payload).length > 0) {
|
||||
console.log('[ActionCable] updateStats performing send_stat:', payload);
|
||||
matchSubscription.perform('send_stat', payload);
|
||||
}
|
||||
} else {
|
||||
console.warn('[ActionCable] updateStats called but matchSubscription is null.');
|
||||
}
|
||||
}
|
||||
|
||||
var debouncedW1Handler = debounce((el) => { if(typeof w1 !== 'undefined') handleTextAreaInput(el, w1); }, 400);
|
||||
var debouncedW2Handler = debounce((el) => { if(typeof w2 !== 'undefined') handleTextAreaInput(el, w2); }, 400);
|
||||
|
||||
function startTimer(wrestler, timerKey) {
|
||||
const timer = wrestler.timers[timerKey];
|
||||
if (timer.interval) return; // Prevent multiple intervals
|
||||
timer.startTime = Date.now(); // Record the start time
|
||||
timer.interval = setInterval(() => {
|
||||
const elapsedSeconds = Math.floor((Date.now() - timer.startTime) / 1000);
|
||||
updateTimerDisplay(wrestler, timerKey, timer.time + elapsedSeconds); // Show total time
|
||||
}, 1000);
|
||||
}
|
||||
function stopTimer(wrestler, timerKey) {
|
||||
const timer = wrestler.timers[timerKey];
|
||||
if (!timer.interval || !timer.startTime) return; // Timer not running
|
||||
clearInterval(timer.interval);
|
||||
|
||||
const elapsedSeconds = Math.floor((Date.now() - timer.startTime) / 1000); // Calculate elapsed time
|
||||
timer.time += elapsedSeconds; // Add elapsed time to total
|
||||
timer.interval = null;
|
||||
timer.startTime = null;
|
||||
|
||||
saveToLocalStorage(wrestler); // Save wrestler data
|
||||
updateTimerDisplay(wrestler, timerKey, timer.time); // Update final display
|
||||
updateStatsBox(wrestler, timerKey, elapsedSeconds); // Update wrestler stats
|
||||
}
|
||||
function resetTimer(wrestler, timerKey) {
|
||||
const timer = wrestler.timers[timerKey];
|
||||
stopTimer(wrestler, timerKey); // Stop if running
|
||||
timer.time = 0; // Reset time
|
||||
updateTimerDisplay(wrestler, timerKey, 0); // Update display
|
||||
saveToLocalStorage(wrestler); // Save wrestler data
|
||||
}
|
||||
function updateTimerDisplay(wrestler, timerKey, totalTime) {
|
||||
const elementId = `${wrestler.name}-${timerKey}-time`; // Construct element ID
|
||||
const element = document.getElementById(elementId);
|
||||
if (element) {
|
||||
element.innerText = `${Math.floor(totalTime / 60)}m ${totalTime % 60}s`;
|
||||
}
|
||||
}
|
||||
function updateStatsBox(wrestler, timerKey, elapsedSeconds) {
|
||||
const timerType = timerKey.includes("injury") ? "Injury Time" : "Blood Time";
|
||||
const formattedTime = `${Math.floor(elapsedSeconds / 60)}m ${elapsedSeconds % 60}s`;
|
||||
updateStats(wrestler, `${timerType}: ${formattedTime}`);
|
||||
}
|
||||
|
||||
// Function to initialize timer displays based on loaded data
|
||||
function initializeTimers(wrestler) {
|
||||
if (!wrestler || !wrestler.timers) return;
|
||||
updateTimerDisplay(wrestler, 'injury', wrestler.timers.injury.time || 0);
|
||||
updateTimerDisplay(wrestler, 'blood', wrestler.timers.blood.time || 0);
|
||||
}
|
||||
|
||||
// Modified function to load from local storage conditionally
|
||||
function initializeFromLocalStorage() {
|
||||
console.log("[Init] Initializing stats state...");
|
||||
const now = new Date().toISOString(); // Get current time for potential updates
|
||||
|
||||
// Process Wrestler 1
|
||||
const localDataW1 = loadFromLocalStorage('w1');
|
||||
// Check if local data exists, has non-blank stats, and an updated_at timestamp
|
||||
const useLocalW1 = localDataW1 && localDataW1.stats && typeof localDataW1.stats === 'string' && localDataW1.stats.trim() !== '' && localDataW1.updated_at;
|
||||
|
||||
if (useLocalW1) {
|
||||
console.log("[Init W1] Using valid data from local storage.");
|
||||
w1.stats = localDataW1.stats;
|
||||
w1.updated_at = localDataW1.updated_at;
|
||||
// Ensure timers object exists and has the expected structure
|
||||
w1.timers = localDataW1.timers && localDataW1.timers.injury && localDataW1.timers.blood
|
||||
? localDataW1.timers
|
||||
: { injury: { time: 0, startTime: null, interval: null }, blood: { time: 0, startTime: null, interval: null } };
|
||||
} else {
|
||||
// Use server data (already in w1.stats from updateJsValues)
|
||||
// Check if local data exists but is invalid/old, or doesn't exist at all
|
||||
if (localDataW1) {
|
||||
console.log("[Init W1] Local storage data invalid/blank/missing timestamp. Overwriting with server data.");
|
||||
} else {
|
||||
console.log("[Init W1] No local storage data found. Using server data.");
|
||||
}
|
||||
// w1.stats already holds server value
|
||||
w1.updated_at = now; // Mark as updated now
|
||||
w1.timers = { injury: { time: 0, startTime: null, interval: null }, blood: { time: 0, startTime: null, interval: null } }; // Reset timers
|
||||
saveToLocalStorage(w1); // Save the server state to local storage
|
||||
}
|
||||
|
||||
// Process Wrestler 2
|
||||
const localDataW2 = loadFromLocalStorage('w2');
|
||||
// Check if local data exists, has non-blank stats, and an updated_at timestamp
|
||||
const useLocalW2 = localDataW2 && localDataW2.stats && typeof localDataW2.stats === 'string' && localDataW2.stats.trim() !== '' && localDataW2.updated_at;
|
||||
|
||||
if (useLocalW2) {
|
||||
console.log("[Init W2] Using valid data from local storage.");
|
||||
w2.stats = localDataW2.stats;
|
||||
w2.updated_at = localDataW2.updated_at;
|
||||
// Ensure timers object exists
|
||||
w2.timers = localDataW2.timers && localDataW2.timers.injury && localDataW2.timers.blood
|
||||
? localDataW2.timers
|
||||
: { injury: { time: 0, startTime: null, interval: null }, blood: { time: 0, startTime: null, interval: null } };
|
||||
} else {
|
||||
// Use server data (already in w2.stats from updateJsValues)
|
||||
if (localDataW2) {
|
||||
console.log("[Init W2] Local storage data invalid/blank/missing timestamp. Overwriting with server data.");
|
||||
} else {
|
||||
console.log("[Init W2] No local storage data found. Using server data.");
|
||||
}
|
||||
// w2.stats already holds server value
|
||||
w2.updated_at = now; // Mark as updated now
|
||||
w2.timers = { injury: { time: 0, startTime: null, interval: null }, blood: { time: 0, startTime: null, interval: null } }; // Reset timers
|
||||
saveToLocalStorage(w2); // Save the server state to local storage
|
||||
}
|
||||
|
||||
// After deciding state, update HTML elements and timer displays
|
||||
updateHtmlValues();
|
||||
initializeTimers(w1);
|
||||
initializeTimers(w2);
|
||||
console.log("[Init] State initialization complete.");
|
||||
}
|
||||
|
||||
// ############### ACTION CABLE LIFECYCLE (Define Before Listeners) #############
|
||||
var matchSubscription = null; // Use var for safety with Turbo re-evaluation / page navigation
|
||||
|
||||
function cleanupSubscription() {
|
||||
if (matchSubscription) {
|
||||
console.log('[AC Cleanup] Unsubscribing...');
|
||||
matchSubscription.unsubscribe();
|
||||
matchSubscription = null;
|
||||
}
|
||||
}
|
||||
|
||||
function setupSubscription(matchId) {
|
||||
cleanupSubscription(); // Ensure clean state
|
||||
console.log(`[Stats AC Setup] Attempting subscription for match ID: ${matchId}`);
|
||||
|
||||
const statusIndicator = document.getElementById("cable-status-indicator"); // Get indicator
|
||||
|
||||
if (typeof App === 'undefined' || typeof App.cable === 'undefined') {
|
||||
console.error("[Stats AC Setup] Action Cable consumer not found.");
|
||||
if(statusIndicator) {
|
||||
statusIndicator.textContent = "Error: AC Not Loaded";
|
||||
statusIndicator.classList.remove('text-dark', 'text-success');
|
||||
statusIndicator.classList.add('alert-danger', 'text-danger');
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Set initial connecting state
|
||||
if(statusIndicator) {
|
||||
statusIndicator.textContent = "Connecting to backend for live updates...";
|
||||
statusIndicator.classList.remove('alert-danger', 'alert-success', 'text-danger', 'text-success');
|
||||
statusIndicator.classList.add('alert-secondary', 'text-dark');
|
||||
}
|
||||
|
||||
// Assign to the global var
|
||||
matchSubscription = App.cable.subscriptions.create(
|
||||
{ channel: "MatchChannel", match_id: matchId },
|
||||
{
|
||||
initialized() {
|
||||
console.log(`[Stats AC Callback] Initialized: ${matchId}`);
|
||||
// Set connecting state again in case of retry
|
||||
if(statusIndicator) {
|
||||
statusIndicator.textContent = "Connecting to backend for live updates...";
|
||||
statusIndicator.classList.remove('alert-danger', 'alert-success', 'text-danger', 'text-success');
|
||||
statusIndicator.classList.add('alert-secondary', 'text-dark');
|
||||
}
|
||||
},
|
||||
connected() {
|
||||
console.log(`[Stats AC Callback] CONNECTED: ${matchId}`);
|
||||
if(statusIndicator) {
|
||||
statusIndicator.textContent = "Connected to backend for live updates...";
|
||||
statusIndicator.classList.remove('alert-danger', 'alert-secondary', 'text-danger', 'text-dark');
|
||||
statusIndicator.classList.add('alert-success');
|
||||
}
|
||||
},
|
||||
disconnected() {
|
||||
console.log(`[Stats AC Callback] Disconnected: ${matchId}`);
|
||||
if(statusIndicator) {
|
||||
statusIndicator.textContent = "Disconnected from backend for live updates. Retrying...";
|
||||
statusIndicator.classList.remove('alert-success', 'alert-secondary', 'text-success', 'text-dark');
|
||||
statusIndicator.classList.add('alert-danger');
|
||||
}
|
||||
},
|
||||
rejected() {
|
||||
console.error(`[Stats AC Callback] REJECTED: ${matchId}`);
|
||||
if(statusIndicator) {
|
||||
statusIndicator.textContent = "Connection to backend rejected";
|
||||
statusIndicator.classList.remove('alert-success', 'alert-secondary', 'text-success', 'text-dark');
|
||||
statusIndicator.classList.add('alert-danger');
|
||||
}
|
||||
matchSubscription = null;
|
||||
},
|
||||
received(data) {
|
||||
console.log("[AC Callback] Received:", data);
|
||||
const currentW1TextArea = document.getElementById("match_w1_stat");
|
||||
const currentW2TextArea = document.getElementById("match_w2_stat");
|
||||
if (data.w1_stat !== undefined && currentW1TextArea) {
|
||||
currentW1TextArea.value = data.w1_stat;
|
||||
if(w1) w1.stats = data.w1_stat;
|
||||
}
|
||||
if (data.w2_stat !== undefined && currentW2TextArea) {
|
||||
currentW2TextArea.value = data.w2_stat;
|
||||
if(w2) w2.stats = data.w2_stat;
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// Re-attach listeners AFTER subscription is attempted
|
||||
const w1TextArea = document.getElementById("match_w1_stat");
|
||||
const w2TextArea = document.getElementById("match_w2_stat");
|
||||
if (w1TextArea) {
|
||||
w1TextArea.addEventListener('input', (event) => { debouncedW1Handler(event.target); });
|
||||
} else { console.warn('[AC Setup] w1StatsTextArea not found for listener'); }
|
||||
if (w2TextArea) {
|
||||
w2TextArea.addEventListener('input', (event) => { debouncedW2Handler(event.target); });
|
||||
} else { console.warn('[AC Setup] w2StatsTextArea not found for listener'); }
|
||||
}
|
||||
|
||||
// ############### EVENT LISTENERS (Define Last) #############
|
||||
|
||||
document.addEventListener("turbo:load", () => {
|
||||
console.log("Stats Event: turbo:load fired.");
|
||||
|
||||
// --- Check if we are actually on the match stats page ---
|
||||
const statsElementCheck = document.getElementById('match_w1_stat'); // Check for stats textarea
|
||||
if (!statsElementCheck) {
|
||||
console.log("Stats Event: Not on match stats page, skipping init and AC setup.");
|
||||
cleanupSubscription(); // Cleanup just in case
|
||||
return;
|
||||
}
|
||||
// --- End Check ---
|
||||
|
||||
// 1. Initialize JS objects with server-rendered values from HTML first
|
||||
updateJsValues();
|
||||
|
||||
// 2. Attempt to load from local storage, overwriting server values only if local is valid and non-blank
|
||||
initializeFromLocalStorage(); // This now contains the core logic
|
||||
|
||||
// 3. Setup ActionCable
|
||||
const matchId = <%= @match.id %>;
|
||||
if (matchId) {
|
||||
setupSubscription(matchId);
|
||||
} else {
|
||||
console.warn("Stats Event: turbo:load - Could not determine match ID for AC setup.");
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener("turbo:before-cache", () => {
|
||||
console.log("Event: turbo:before-cache fired. Cleaning up subscription.");
|
||||
cleanupSubscription();
|
||||
});
|
||||
|
||||
// Optional: Cleanup on full page unload too
|
||||
window.addEventListener('beforeunload', cleanupSubscription);
|
||||
</script>
|
||||
<% end %><!-- End of form_for -->
|
||||
|
||||
@@ -1,108 +0,0 @@
|
||||
<script>
|
||||
// ############### Button color change red/green
|
||||
function changeW1Color(color){
|
||||
if (color.value == "red") {
|
||||
w1Red();
|
||||
w2Green();
|
||||
document.getElementById("w2-color").value = "green";
|
||||
}
|
||||
if (color.value == "green") {
|
||||
w1Green();
|
||||
w2Red();
|
||||
document.getElementById("w2-color").value = "red";
|
||||
}
|
||||
}
|
||||
|
||||
function changeW2Color(color){
|
||||
if (color.value == "red") {
|
||||
w2Red();
|
||||
w1Green();
|
||||
document.getElementById("w1-color").value = "green";
|
||||
}
|
||||
if (color.value == "green") {
|
||||
w2Green();
|
||||
w1Red();
|
||||
document.getElementById("w1-color").value = "red";
|
||||
}
|
||||
}
|
||||
|
||||
function redColor(id){
|
||||
document.getElementById(id).className = "btn btn-danger btn-sm";
|
||||
}
|
||||
|
||||
function greenColor(id){
|
||||
document.getElementById(id).className = "btn btn-success btn-sm";
|
||||
}
|
||||
|
||||
function w1Red(){
|
||||
redColor("w1-takedown");
|
||||
redColor("w1-escape");
|
||||
redColor("w1-reversal");
|
||||
redColor("w1-penalty");
|
||||
redColor("w1-penalty2");
|
||||
redColor("w1-nf5");
|
||||
redColor("w1-nf4");
|
||||
redColor("w1-nf3");
|
||||
redColor("w1-nf2");
|
||||
redColor("w1-top");
|
||||
redColor("w1-bottom");
|
||||
redColor("w1-neutral");
|
||||
redColor("w1-defer");
|
||||
redColor("w1-stalling");
|
||||
redColor("w1-caution");
|
||||
}
|
||||
|
||||
function w1Green(){
|
||||
greenColor("w1-takedown");
|
||||
greenColor("w1-escape");
|
||||
greenColor("w1-reversal");
|
||||
greenColor("w1-penalty");
|
||||
greenColor("w1-penalty2");
|
||||
greenColor("w1-nf5");
|
||||
greenColor("w1-nf4");
|
||||
greenColor("w1-nf3");
|
||||
greenColor("w1-nf2");
|
||||
greenColor("w1-top");
|
||||
greenColor("w1-bottom");
|
||||
greenColor("w1-neutral");
|
||||
greenColor("w1-defer");
|
||||
greenColor("w1-stalling");
|
||||
greenColor("w1-caution");
|
||||
}
|
||||
|
||||
function w2Red(){
|
||||
redColor("w2-takedown");
|
||||
redColor("w2-escape");
|
||||
redColor("w2-reversal");
|
||||
redColor("w2-penalty");
|
||||
redColor("w2-penalty2");
|
||||
redColor("w2-nf5");
|
||||
redColor("w2-nf4");
|
||||
redColor("w2-nf3");
|
||||
redColor("w2-nf2");
|
||||
redColor("w2-top");
|
||||
redColor("w2-bottom");
|
||||
redColor("w2-neutral");
|
||||
redColor("w2-defer");
|
||||
redColor("w2-stalling");
|
||||
redColor("w2-caution");
|
||||
}
|
||||
|
||||
function w2Green(){
|
||||
greenColor("w2-takedown");
|
||||
greenColor("w2-escape");
|
||||
greenColor("w2-reversal");
|
||||
greenColor("w2-penalty");
|
||||
greenColor("w2-penalty2");
|
||||
greenColor("w2-nf5");
|
||||
greenColor("w2-nf4");
|
||||
greenColor("w2-nf3");
|
||||
greenColor("w2-nf2");
|
||||
greenColor("w2-top");
|
||||
greenColor("w2-bottom");
|
||||
greenColor("w2-neutral");
|
||||
greenColor("w2-defer");
|
||||
greenColor("w2-stalling");
|
||||
greenColor("w2-caution");
|
||||
}
|
||||
</script>
|
||||
@@ -1,244 +0,0 @@
|
||||
<script>
|
||||
// ############### Score field changer and form validation
|
||||
function initializeScoreFields() {
|
||||
const winTypeSelect = document.getElementById("match_win_type");
|
||||
const winnerSelect = document.getElementById("match_winner_id");
|
||||
const submitButton = document.getElementById("update-match-btn");
|
||||
const dynamicScoreInput = document.getElementById("dynamic-score-input");
|
||||
const finalScoreField = document.getElementById("final-score-field");
|
||||
const validationAlerts = document.getElementById("validation-alerts");
|
||||
const pinTimeTip = document.getElementById("pin-time-tip");
|
||||
|
||||
// If elements don't exist, don't proceed
|
||||
if (!winTypeSelect || !dynamicScoreInput || !finalScoreField) return;
|
||||
|
||||
// Variables to persist scores across win type changes
|
||||
let storedScores = {
|
||||
winnerScore: "0",
|
||||
loserScore: "0",
|
||||
};
|
||||
|
||||
function updateScoreInput() {
|
||||
const winType = winTypeSelect.value;
|
||||
|
||||
if (winType === "Pin") {
|
||||
// Clear existing validation state and stored scores
|
||||
dynamicScoreInput.innerHTML = "";
|
||||
pinTimeTip.style.display = "block";
|
||||
|
||||
const minuteInput = createTextInput("minutes", "Minutes (MM)", "Pin Time Minutes");
|
||||
const secondInput = createTextInput("seconds", "Seconds (SS)", "Pin Time Seconds");
|
||||
|
||||
dynamicScoreInput.appendChild(minuteInput);
|
||||
dynamicScoreInput.appendChild(secondInput);
|
||||
|
||||
const updateFinalScore = () => {
|
||||
// Ensure inputs are defined and have valid values
|
||||
const minutes = (minuteInput.value || "0").padStart(2, "0");
|
||||
const seconds = (secondInput.value || "0").padStart(2, "0");
|
||||
finalScoreField.value = `${minutes}:${seconds}`;
|
||||
validateForm();
|
||||
};
|
||||
|
||||
[minuteInput, secondInput].forEach((input) => {
|
||||
input.addEventListener("input", updateFinalScore);
|
||||
});
|
||||
|
||||
// Safely initialize the final score
|
||||
updateFinalScore(); // Set initial value
|
||||
validateForm(); // Trigger validation
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
winType === "Decision" ||
|
||||
winType === "Major" ||
|
||||
winType === "Tech Fall"
|
||||
) {
|
||||
if (
|
||||
dynamicScoreInput.querySelector("#winner-score") &&
|
||||
dynamicScoreInput.querySelector("#loser-score")
|
||||
) {
|
||||
validateForm(); // Trigger validation
|
||||
return;
|
||||
}
|
||||
|
||||
// Clear existing form and create Score inputs
|
||||
dynamicScoreInput.innerHTML = "";
|
||||
pinTimeTip.style.display = "none";
|
||||
|
||||
const winnerScoreInput = createTextInput(
|
||||
"winner-score",
|
||||
"Winner's Score",
|
||||
"Enter the winner's score"
|
||||
);
|
||||
const loserScoreInput = createTextInput(
|
||||
"loser-score",
|
||||
"Loser's Score",
|
||||
"Enter the loser's score"
|
||||
);
|
||||
|
||||
dynamicScoreInput.appendChild(winnerScoreInput);
|
||||
dynamicScoreInput.appendChild(loserScoreInput);
|
||||
|
||||
// Restore stored values
|
||||
winnerScoreInput.value = storedScores.winnerScore;
|
||||
loserScoreInput.value = storedScores.loserScore;
|
||||
|
||||
const updateFinalScore = () => {
|
||||
const winnerScore = winnerScoreInput.value || "0";
|
||||
const loserScore = loserScoreInput.value || "0";
|
||||
finalScoreField.value = `${winnerScore}-${loserScore}`;
|
||||
validateForm();
|
||||
};
|
||||
|
||||
[winnerScoreInput, loserScoreInput].forEach((input) => {
|
||||
input.addEventListener("input", (event) => {
|
||||
storedScores[event.target.id === "winner-score" ? "winnerScore" : "loserScore"] =
|
||||
event.target.value || "0";
|
||||
updateFinalScore();
|
||||
});
|
||||
});
|
||||
|
||||
updateFinalScore(); // Set initial value
|
||||
validateForm(); // Trigger validation
|
||||
return;
|
||||
}
|
||||
|
||||
// For other types, clear everything
|
||||
dynamicScoreInput.innerHTML = "";
|
||||
pinTimeTip.style.display = "none";
|
||||
finalScoreField.value = ""; // Clear the final score for other win types
|
||||
validateForm(); // Trigger validation
|
||||
}
|
||||
|
||||
function validateForm() {
|
||||
const winType = winTypeSelect.value;
|
||||
const winner = winnerSelect ? winnerSelect.value : null;
|
||||
let isValid = true;
|
||||
let alertMessage = "";
|
||||
|
||||
let winTypeShouldBe = "Decision";
|
||||
|
||||
if (winType === "Decision" || winType === "Major" || winType === "Tech Fall") {
|
||||
const winnerScoreInput = document.getElementById("winner-score");
|
||||
const loserScoreInput = document.getElementById("loser-score");
|
||||
|
||||
if (!winnerScoreInput || !loserScoreInput) return;
|
||||
|
||||
const winnerScore = parseInt(winnerScoreInput.value || "0", 10);
|
||||
const loserScore = parseInt(loserScoreInput.value || "0", 10);
|
||||
const scoreDifference = winnerScore - loserScore;
|
||||
|
||||
if (winnerScore <= loserScore) {
|
||||
isValid = false;
|
||||
alertMessage += "Winner's score must be higher than loser's score.<br>";
|
||||
}
|
||||
|
||||
if (scoreDifference < 8) {
|
||||
winTypeShouldBe = "Decision";
|
||||
} else if (scoreDifference >= 8 && scoreDifference < 15) {
|
||||
winTypeShouldBe = "Major";
|
||||
} else if (scoreDifference >= 15) {
|
||||
winTypeShouldBe = "Tech Fall";
|
||||
}
|
||||
|
||||
if (winTypeShouldBe !== winType) {
|
||||
isValid = false;
|
||||
alertMessage += `
|
||||
Win type should be <strong>${winTypeShouldBe}</strong>.
|
||||
Decisions are wins with a score difference less than 8.
|
||||
Majors are wins with a score difference between 8 and 14.
|
||||
Tech Falls are wins with a score difference of 15 or more.<br>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
if (!winner) {
|
||||
isValid = false;
|
||||
alertMessage += "Please select a winner.<br>";
|
||||
}
|
||||
|
||||
if (validationAlerts) {
|
||||
if (!isValid) {
|
||||
validationAlerts.innerHTML = alertMessage;
|
||||
validationAlerts.style.display = "block";
|
||||
} else {
|
||||
validationAlerts.innerHTML = ""; // Clear alerts
|
||||
validationAlerts.style.display = "none";
|
||||
}
|
||||
}
|
||||
|
||||
if (submitButton) {
|
||||
submitButton.disabled = !isValid;
|
||||
}
|
||||
}
|
||||
|
||||
if (document.querySelector("form")) {
|
||||
document.querySelector("form").addEventListener("submit", (event) => {
|
||||
const winType = winTypeSelect.value;
|
||||
|
||||
if (winType === "Pin") {
|
||||
const minuteInput = document.getElementById("minutes");
|
||||
const secondInput = document.getElementById("seconds");
|
||||
|
||||
if (minuteInput && secondInput) {
|
||||
const minutes = minuteInput.value.padStart(2, "0");
|
||||
const seconds = secondInput.value.padStart(2, "0");
|
||||
finalScoreField.value = `${minutes}:${seconds}`;
|
||||
} else {
|
||||
finalScoreField.value = ""; // Clear if no inputs
|
||||
}
|
||||
} else if (
|
||||
winType === "Decision" ||
|
||||
winType === "Major" ||
|
||||
winType === "Tech Fall"
|
||||
) {
|
||||
const winnerScoreInput = document.getElementById("winner-score");
|
||||
const loserScoreInput = document.getElementById("loser-score");
|
||||
|
||||
if (winnerScoreInput && loserScoreInput) {
|
||||
const winnerScore = winnerScoreInput.value || "0";
|
||||
const loserScore = loserScoreInput.value || "0";
|
||||
finalScoreField.value = `${winnerScore}-${loserScore}`;
|
||||
} else {
|
||||
finalScoreField.value = ""; // Clear if no inputs
|
||||
}
|
||||
} else {
|
||||
finalScoreField.value = ""; // Reset for other win types
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
winTypeSelect.addEventListener("change", updateScoreInput);
|
||||
if (winnerSelect) {
|
||||
winnerSelect.addEventListener("change", validateForm);
|
||||
}
|
||||
|
||||
updateScoreInput();
|
||||
validateForm();
|
||||
}
|
||||
|
||||
// Helper function to create text inputs
|
||||
function createTextInput(id, placeholder, label) {
|
||||
const container = document.createElement("div");
|
||||
container.classList.add("form-group");
|
||||
|
||||
const inputLabel = document.createElement("label");
|
||||
inputLabel.innerText = label;
|
||||
|
||||
const input = document.createElement("input");
|
||||
input.type = "text";
|
||||
input.id = id;
|
||||
input.placeholder = placeholder;
|
||||
input.classList.add("form-control", "form-control-sm");
|
||||
|
||||
container.appendChild(inputLabel);
|
||||
container.appendChild(input);
|
||||
return container;
|
||||
}
|
||||
|
||||
// Initialize on both DOMContentLoaded and turbo:load
|
||||
document.addEventListener("DOMContentLoaded", initializeScoreFields);
|
||||
document.addEventListener("turbo:load", initializeScoreFields);
|
||||
</script>
|
||||
@@ -2,32 +2,40 @@
|
||||
<h2><%= @match.weight.max %> lbs</h2>
|
||||
<h3><%= @tournament.name %></h3>
|
||||
|
||||
<div id="cable-status-indicator" class="alert alert-secondary" style="padding: 5px; margin-bottom: 10px; border-radius: 4px;"></div>
|
||||
<div data-controller="match-spectate"
|
||||
data-match-spectate-match-id-value="<%= @match.id %>">
|
||||
|
||||
<div class="match-details">
|
||||
<div class="wrestler-info wrestler1">
|
||||
<h4><%= @wrestler1_name %> (<%= @wrestler1_school_name %>)</h4>
|
||||
<div class="stats">
|
||||
<strong>Stats:</strong>
|
||||
<pre id="w1-stats-display"><%= @match.w1_stat %></pre>
|
||||
<div id="cable-status-indicator"
|
||||
data-match-spectate-target="statusIndicator"
|
||||
class="alert alert-secondary"
|
||||
style="padding: 5px; margin-bottom: 10px; border-radius: 4px;"></div>
|
||||
|
||||
<div class="match-details">
|
||||
<div class="wrestler-info wrestler1">
|
||||
<h4><%= @wrestler1_name %> (<%= @wrestler1_school_name %>)</h4>
|
||||
<div class="stats">
|
||||
<strong>Stats:</strong>
|
||||
<pre data-match-spectate-target="w1Stats"><%= @match.w1_stat %></pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="wrestler-info wrestler2">
|
||||
<h4><%= @wrestler2_name %> (<%= @wrestler2_school_name %>)</h4>
|
||||
<div class="stats">
|
||||
<strong>Stats:</strong>
|
||||
<pre data-match-spectate-target="w2Stats"><%= @match.w2_stat %></pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="match-result">
|
||||
<h4>Result</h4>
|
||||
<p><strong>Winner:</strong> <span data-match-spectate-target="winner"><%= @match.winner_id ? @match.winner.name : '-' %></span></p>
|
||||
<p><strong>Win Type:</strong> <span data-match-spectate-target="winType"><%= @match.win_type || '-' %></span></p>
|
||||
<p><strong>Score:</strong> <span data-match-spectate-target="score"><%= @match.score || '-' %></span></p>
|
||||
<p><strong>Finished:</strong> <span data-match-spectate-target="finished"><%= @match.finished ? 'Yes' : 'No' %></span></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="wrestler-info wrestler2">
|
||||
<h4><%= @wrestler2_name %> (<%= @wrestler2_school_name %>)</h4>
|
||||
<div class="stats">
|
||||
<strong>Stats:</strong>
|
||||
<pre id="w2-stats-display"><%= @match.w2_stat %></pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="match-result">
|
||||
<h4>Result</h4>
|
||||
<p><strong>Winner:</strong> <span id="winner-display"><%= @match.winner_id ? @match.winner.name : '-' %></span></p>
|
||||
<p><strong>Win Type:</strong> <span id="win-type-display"><%= @match.win_type || '-' %></span></p>
|
||||
<p><strong>Score:</strong> <span id="score-display"><%= @match.score || '-' %></span></p>
|
||||
<p><strong>Finished:</strong> <span id="finished-display"><%= @match.finished ? 'Yes' : 'No' %></span></p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<style>
|
||||
@@ -75,147 +83,4 @@
|
||||
color: white;
|
||||
}
|
||||
*/
|
||||
</style>
|
||||
|
||||
<script>
|
||||
// ############### ACTION CABLE LIFECYCLE #############
|
||||
var matchSubscription = null; // Use var for handling Turbo page navigations
|
||||
|
||||
// Function to tear down the existing subscription
|
||||
function cleanupSubscription() {
|
||||
if (matchSubscription) {
|
||||
console.log('[Spectator AC Cleanup] Unsubscribing...');
|
||||
matchSubscription.unsubscribe();
|
||||
matchSubscription = null;
|
||||
}
|
||||
}
|
||||
|
||||
// Function to set up the Action Cable subscription for a given matchId
|
||||
function setupSubscription(matchId) {
|
||||
cleanupSubscription(); // Ensure clean state
|
||||
console.log(`[Spectator AC Setup] Attempting subscription for match ID: ${matchId}`);
|
||||
|
||||
const statusIndicator = document.getElementById("cable-status-indicator"); // Get indicator
|
||||
|
||||
if (typeof App === 'undefined' || typeof App.cable === 'undefined') {
|
||||
console.error("[Spectator AC Setup] Action Cable consumer not found.");
|
||||
if(statusIndicator) {
|
||||
statusIndicator.textContent = "Error: AC Not Loaded";
|
||||
statusIndicator.classList.remove('text-dark', 'text-success');
|
||||
statusIndicator.classList.add('alert-danger', 'text-danger'); // Use alert-danger for error state too
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Set initial connecting state for indicator
|
||||
if(statusIndicator) {
|
||||
statusIndicator.textContent = "Connecting to backend for live updates...";
|
||||
statusIndicator.classList.remove('alert-danger', 'alert-success', 'text-danger', 'text-success');
|
||||
statusIndicator.classList.add('alert-secondary', 'text-dark'); // Keep grey, dark text
|
||||
}
|
||||
|
||||
// Get references to display elements (needed inside received)
|
||||
const w1StatsDisplay = document.getElementById("w1-stats-display");
|
||||
const w2StatsDisplay = document.getElementById("w2-stats-display");
|
||||
const winnerDisplay = document.getElementById("winner-display");
|
||||
const winTypeDisplay = document.getElementById("win-type-display");
|
||||
const scoreDisplay = document.getElementById("score-display");
|
||||
const finishedDisplay = document.getElementById("finished-display");
|
||||
|
||||
// Assign to the global var
|
||||
matchSubscription = App.cable.subscriptions.create(
|
||||
{ channel: "MatchChannel", match_id: matchId },
|
||||
{
|
||||
initialized() {
|
||||
console.log(`[Spectator AC Callback] Initialized: ${matchId}`);
|
||||
// Set connecting state again in case of retry
|
||||
if(statusIndicator) {
|
||||
statusIndicator.textContent = "Connecting to backend for live updates...";
|
||||
statusIndicator.classList.remove('alert-danger', 'alert-success', 'text-danger', 'text-success');
|
||||
statusIndicator.classList.add('alert-secondary', 'text-dark');
|
||||
}
|
||||
},
|
||||
connected() {
|
||||
console.log(`[Spectator AC Callback] CONNECTED: ${matchId}`);
|
||||
if(statusIndicator) {
|
||||
statusIndicator.textContent = "Connected to backend for live updates...";
|
||||
statusIndicator.classList.remove('alert-danger', 'alert-secondary', 'text-danger', 'text-dark');
|
||||
statusIndicator.classList.add('alert-success'); // Use alert-success for connected
|
||||
}
|
||||
},
|
||||
disconnected() {
|
||||
console.log(`[Spectator AC Callback] Disconnected: ${matchId}`);
|
||||
if(statusIndicator) {
|
||||
statusIndicator.textContent = "Disconnected from backend for live updates. Retrying...";
|
||||
statusIndicator.classList.remove('alert-success', 'alert-secondary', 'text-success', 'text-dark');
|
||||
statusIndicator.classList.add('alert-danger'); // Use alert-danger for disconnected
|
||||
}
|
||||
},
|
||||
rejected() {
|
||||
console.error(`[Spectator AC Callback] REJECTED: ${matchId}`);
|
||||
if(statusIndicator) {
|
||||
statusIndicator.textContent = "Connection to backend rejected";
|
||||
statusIndicator.classList.remove('alert-success', 'alert-secondary', 'text-success', 'text-dark');
|
||||
statusIndicator.classList.add('alert-danger'); // Use alert-danger for rejected
|
||||
}
|
||||
matchSubscription = null;
|
||||
},
|
||||
received(data) {
|
||||
console.log("[Spectator AC Callback] Received:", data);
|
||||
// Update display elements if they exist
|
||||
if (data.w1_stat !== undefined && w1StatsDisplay) {
|
||||
w1StatsDisplay.textContent = data.w1_stat;
|
||||
}
|
||||
if (data.w2_stat !== undefined && w2StatsDisplay) {
|
||||
w2StatsDisplay.textContent = data.w2_stat;
|
||||
}
|
||||
if (data.score !== undefined && scoreDisplay) {
|
||||
scoreDisplay.textContent = data.score || '-';
|
||||
}
|
||||
if (data.win_type !== undefined && winTypeDisplay) {
|
||||
winTypeDisplay.textContent = data.win_type || '-';
|
||||
}
|
||||
if (data.winner_name !== undefined && winnerDisplay) {
|
||||
winnerDisplay.textContent = data.winner_name || (data.winner_id ? `ID: ${data.winner_id}` : '-');
|
||||
} else if (data.winner_id !== undefined && winnerDisplay) {
|
||||
winnerDisplay.textContent = data.winner_id ? `ID: ${data.winner_id}` : '-';
|
||||
}
|
||||
if (data.finished !== undefined && finishedDisplay) {
|
||||
finishedDisplay.textContent = data.finished ? 'Yes' : 'No';
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// ############### EVENT LISTENERS #############
|
||||
|
||||
document.addEventListener("turbo:load", () => {
|
||||
console.log("Spectator Event: turbo:load fired.");
|
||||
|
||||
// Check for necessary elements before proceeding
|
||||
const spectatorElementCheck = document.getElementById('w1-stats-display');
|
||||
if (!spectatorElementCheck) {
|
||||
console.log("Spectator Event: Not on spectator page, skipping AC setup.");
|
||||
// Ensure any potentially lingering subscription is cleaned up just in case
|
||||
cleanupSubscription();
|
||||
return;
|
||||
}
|
||||
|
||||
const matchId = <%= @match.id %>; // Get match ID from ERB
|
||||
if (matchId) {
|
||||
setupSubscription(matchId);
|
||||
} else {
|
||||
console.warn("Spectator Event: turbo:load - Could not determine match ID");
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener("turbo:before-cache", () => {
|
||||
console.log("Spectator Event: turbo:before-cache fired. Cleaning up subscription.");
|
||||
cleanupSubscription();
|
||||
});
|
||||
|
||||
// Optional: Cleanup on full page unload too
|
||||
window.addEventListener('beforeunload', cleanupSubscription);
|
||||
|
||||
</script>
|
||||
</style>
|
||||
Reference in New Issue
Block a user