Files
chesscubing/ChessCubing.App/Pages/ApplicationPage.razor

355 lines
16 KiB
Plaintext

@page "/application"
@page "/application.html"
@attribute [Authorize]
@inject BrowserBridge Browser
@inject MatchStore Store
@inject NavigationManager Navigation
<PageTitle>ChessCubing Arena | Application</PageTitle>
<PageBody Page="setup" BodyClass="@SetupBodyClass" />
<div class="ambient ambient-left"></div>
<div class="ambient ambient-right"></div>
<div class="setup-shell">
<header class="hero hero-setup">
<div class="hero-copy">
<a class="logo-lockup" href="index.html" aria-label="Accueil ChessCubing">
<img class="hero-logo-icon" src="logo.png" alt="Icone ChessCubing" />
<img class="hero-logo" src="transparent.png" alt="Logo ChessCubing" />
</a>
<p class="eyebrow">Application officielle de match</p>
<h1>ChessCubing Arena</h1>
<div class="hero-actions">
<a class="button ghost" href="index.html">Accueil du site</a>
<a class="button secondary" href="reglement.html">Consulter le reglement</a>
</div>
<UserAccessBar />
</div>
<aside class="hero-preview">
<div class="preview-card">
<p class="micro-label">Flux de match</p>
<ol class="phase-list">
<li>Configurer la rencontre</li>
<li>Passer a la page chrono</li>
<li>Basculer automatiquement sur la page cube</li>
<li>Revenir sur la page chrono pour le Block suivant</li>
</ol>
</div>
</aside>
</header>
<main class="setup-grid">
<section class="panel">
<div class="section-heading">
<div>
<p class="eyebrow">Nouvelle rencontre</p>
<h2>Configuration</h2>
</div>
<p class="section-copy">
Les reglages ci-dessous preparent les pages chrono et cube.
</p>
</div>
<form class="setup-form" @onsubmit="HandleSubmit">
<label class="option-card competition-option span-2 @(Form.CompetitionMode ? "is-selected" : string.Empty)">
<input @bind="Form.CompetitionMode" id="competitionMode" name="competitionMode" type="checkbox" />
<strong>Mode competition</strong>
<span>
Affiche le nom de la rencontre, l'arbitre, le club ou l'evenement
et les notes d'organisation.
</span>
</label>
@if (Form.CompetitionMode)
{
<label class="field span-2" id="matchLabelField">
<span>Nom de la rencontre</span>
<input @bind="Form.MatchLabel" @bind:event="oninput" name="matchLabel" type="text" maxlength="80" placeholder="Open ChessCubing de Paris" />
</label>
}
<fieldset class="field span-2">
<legend>Mode officiel</legend>
<div class="option-grid mode-grid">
<label class="option-card @(Form.Mode == MatchEngine.ModeTwice ? "is-selected" : string.Empty)">
<input type="radio" name="mode" checked="@(Form.Mode == MatchEngine.ModeTwice)" @onchange="() => SetMode(MatchEngine.ModeTwice)" />
<strong>ChessCubing Twice</strong>
<span>
Le gagnant du cube ouvre le Block suivant et peut obtenir un
double coup V2.
</span>
</label>
<label class="option-card @(Form.Mode == MatchEngine.ModeTime ? "is-selected" : string.Empty)">
<input type="radio" name="mode" checked="@(Form.Mode == MatchEngine.ModeTime)" @onchange="() => SetMode(MatchEngine.ModeTime)" />
<strong>ChessCubing Time</strong>
<span>
Meme structure de Blocks, avec chronos cumules et alternance
bloc - / bloc +.
</span>
</label>
</div>
</fieldset>
<fieldset class="field span-2">
<legend>Cadence du match</legend>
<div class="option-grid preset-grid">
<label class="option-card @(Form.Preset == MatchEngine.PresetFast ? "is-selected" : string.Empty)">
<input type="radio" name="preset" checked="@(Form.Preset == MatchEngine.PresetFast)" @onchange="() => SetPreset(MatchEngine.PresetFast)" />
<strong>FAST</strong>
<span>6 coups par joueur</span>
</label>
<label class="option-card @(Form.Preset == MatchEngine.PresetFreeze ? "is-selected" : string.Empty)">
<input type="radio" name="preset" checked="@(Form.Preset == MatchEngine.PresetFreeze)" @onchange="() => SetPreset(MatchEngine.PresetFreeze)" />
<strong>FREEZE</strong>
<span>8 coups par joueur</span>
</label>
<label class="option-card @(Form.Preset == MatchEngine.PresetMasters ? "is-selected" : string.Empty)">
<input type="radio" name="preset" checked="@(Form.Preset == MatchEngine.PresetMasters)" @onchange="() => SetPreset(MatchEngine.PresetMasters)" />
<strong>MASTERS</strong>
<span>10 coups par joueur</span>
</label>
</div>
</fieldset>
<fieldset class="field span-2">
<legend>Temps personnalises</legend>
<div class="timing-grid">
<label class="field">
<span>Temps du Block (minutes)</span>
<input @bind="Form.BlockMinutes" name="blockMinutes" type="number" min="1" max="180" step="1" />
</label>
@if (!UsesMoveLimit)
{
<label class="field" id="timeInitialField">
<span>Temps de chaque joueur (minutes)</span>
<input @bind="Form.TimeInitialMinutes" name="timeInitialMinutes" type="number" min="1" max="180" step="1" />
</label>
}
@if (UsesMoveLimit)
{
<label class="field" id="moveSecondsField">
<span>Temps coup (secondes)</span>
<input @bind="Form.MoveSeconds" name="moveSeconds" type="number" min="5" max="300" step="1" />
</label>
}
</div>
</fieldset>
<label class="field">
<span>Joueur blanc</span>
<input @bind="Form.WhiteName" @bind:event="oninput" name="whiteName" type="text" maxlength="40" placeholder="Blanc" />
</label>
<label class="field">
<span>Joueur noir</span>
<input @bind="Form.BlackName" @bind:event="oninput" name="blackName" type="text" maxlength="40" placeholder="Noir" />
</label>
@if (Form.CompetitionMode)
{
<label class="field" id="arbiterField">
<span>Arbitre</span>
<input @bind="Form.ArbiterName" @bind:event="oninput" name="arbiterName" type="text" maxlength="40" placeholder="Arbitre principal" />
</label>
<label class="field" id="eventField">
<span>Club / evenement</span>
<input @bind="Form.EventName" @bind:event="oninput" name="eventName" type="text" maxlength="60" placeholder="Club, tournoi, demonstration" />
</label>
<label class="field span-2" id="notesField">
<span>Notes</span>
<textarea @bind="Form.Notes" @bind:event="oninput" name="notes" rows="3" placeholder="Tirage au sort effectue, 8 cubes verifies, variante prete..."></textarea>
</label>
}
<div id="setupSummary" class="setup-summary span-2">
<strong>@CurrentMode.Label</strong>
<span>@CurrentPreset.Description</span>
<span>@TimingText</span>
<span>@TimeImpact</span>
<span>@QuotaText</span>
</div>
<div class="setup-actions span-2">
<button class="button primary" type="submit">Ouvrir la page chrono</button>
<button class="button secondary" id="loadDemoButton" type="button" @onclick="LoadDemo">Charger une demo</button>
<a class="button ghost" href="reglement.html">Consulter le reglement</a>
</div>
</form>
</section>
<aside class="panel side-panel">
<div class="section-heading">
<div>
<p class="eyebrow">Match en memoire</p>
<h2>Reprise rapide</h2>
</div>
</div>
@if (!_ready || CurrentMatch is null)
{
<div id="resumeCard" class="resume-card empty">
<p>Aucun match en cours pour l'instant.</p>
</div>
}
else
{
<div id="resumeCard" class="resume-card">
<strong>@ResumeMatchLabel(CurrentMatch)</strong>
<p>@CurrentMatchMode(CurrentMatch)</p>
<p>@($"{CurrentMatch.Config.WhiteName} vs {CurrentMatch.Config.BlackName}")</p>
<p>@ResumePhaseLabel(CurrentMatch)</p>
<div class="resume-actions">
<button class="button primary" id="resumeMatchButton" type="button" @onclick="ResumeMatch">
@(string.IsNullOrEmpty(CurrentMatch.Result) ? "Reprendre la phase" : "Voir le match")
</button>
<button class="button ghost" id="clearMatchButton" type="button" @onclick="ClearMatchAsync">Effacer le match</button>
</div>
</div>
}
<div class="rules-stack">
<article class="rule-card">
<span class="micro-label">Page chrono</span>
<strong>Gros boutons uniquement</strong>
<p>
Chaque joueur dispose d'une grande zone tactile pour signaler la
fin de son coup, puis l'app ouvre automatiquement la phase cube
quand le Block d'echecs est termine.
</p>
</article>
<article class="rule-card">
<span class="micro-label">Page cube</span>
<strong>Une page dediee</strong>
<p>
Les deux joueurs lancent et arretent leur propre chrono cube sur
un ecran separe, toujours en face-a-face sur mobile.
</p>
</article>
<article class="rule-card">
<span class="micro-label">Sources</span>
<strong>Reglements integres</strong>
<p>
<a href="reglement.html">Page reglement du site</a>
<br />
<a href="ChessCubing_Twice_Reglement_Officiel_V2-1.pdf" target="_blank">Reglement ChessCubing Twice</a>
<br />
<a href="ChessCubing_Time_Reglement_Officiel_V1-1.pdf" target="_blank">Reglement ChessCubing Time</a>
</p>
</article>
</div>
</aside>
</main>
<div class="setup-refresh-footer">
<button class="refresh-link-button" id="refreshAppButton" type="button" @onclick="ForceRefreshAsync">
Rafraichir l'app
</button>
</div>
</div>
@code {
private SetupFormModel Form { get; set; } = new();
private bool _ready;
private MatchState? CurrentMatch => Store.Current;
private string SetupBodyClass => UsesMoveLimit ? string.Empty : "time-setup-mode";
private bool UsesMoveLimit => MatchEngine.UsesMoveLimit(Form.Mode);
private MatchPresetInfo CurrentPreset =>
MatchEngine.Presets.TryGetValue(Form.Preset, out var preset)
? preset
: MatchEngine.Presets[MatchEngine.PresetFast];
private MatchModeInfo CurrentMode =>
MatchEngine.Modes.TryGetValue(Form.Mode, out var mode)
? mode
: MatchEngine.Modes[MatchEngine.ModeTwice];
private string TimingText =>
UsesMoveLimit
? $"Temps configures : Block {MatchEngine.FormatClock(Form.BlockMinutes * 60_000L)}, coup {MatchEngine.FormatClock(Form.MoveSeconds * 1_000L)}."
: $"Temps configures : Block {MatchEngine.FormatClock(Form.BlockMinutes * 60_000L)}, temps de chaque joueur {MatchEngine.FormatClock(Form.TimeInitialMinutes * 60_000L)}.";
private string TimeImpact =>
Form.Mode == MatchEngine.ModeTime
? $"Chronos cumules de {MatchEngine.FormatClock(Form.TimeInitialMinutes * 60_000L)} par joueur, ajustes apres chaque phase cube avec plafond de 120 s pris en compte. Aucun temps par coup en mode Time."
: "Le gagnant du cube commence le Block suivant, avec double coup V2 possible.";
private string QuotaText =>
UsesMoveLimit
? $"Quota actif : {CurrentPreset.Quota} coups par joueur."
: $"Quota actif : {CurrentPreset.Quota} coups par joueur et par Block.";
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (!firstRender)
{
return;
}
await Store.EnsureLoadedAsync();
_ready = true;
StateHasChanged();
}
private async Task HandleSubmit()
{
var match = MatchEngine.CreateMatch(Form.ToMatchConfig());
Store.SetCurrent(match);
await Store.SaveAsync();
Navigation.NavigateTo("/chrono.html");
}
private void LoadDemo()
=> Form = SetupFormModel.CreateDemo();
private void SetMode(string mode)
=> Form.Mode = mode;
private void SetPreset(string preset)
=> Form.Preset = preset;
private void ResumeMatch()
{
if (CurrentMatch is null)
{
return;
}
Navigation.NavigateTo(MatchEngine.RouteForMatch(CurrentMatch));
}
private async Task ClearMatchAsync()
{
await Store.ClearAsync();
StateHasChanged();
}
private Task ForceRefreshAsync()
=> Browser.ForceRefreshAsync("/application.html").AsTask();
private static string ResumeMatchLabel(MatchState match)
=> string.IsNullOrWhiteSpace(match.Config.MatchLabel) ? "Rencontre ChessCubing" : match.Config.MatchLabel;
private static string CurrentMatchMode(MatchState match)
=> MatchEngine.Modes.TryGetValue(match.Config.Mode, out var mode) ? mode.Label : "ChessCubing Twice";
private static string ResumePhaseLabel(MatchState match)
{
if (!string.IsNullOrEmpty(match.Result))
{
return MatchEngine.ResultText(match);
}
return match.Phase == MatchEngine.PhaseCube ? "Page cube prete" : "Page chrono prete";
}
}