fix(web client): show jans arrows
This commit is contained in:
parent
e61448b627
commit
5328b8e5aa
2 changed files with 40 additions and 4 deletions
|
|
@ -176,7 +176,7 @@ pub fn GameScreen(state: GameUiState) -> impl IntoView {
|
||||||
|
|
||||||
let last_moves = state.last_moves;
|
let last_moves = state.last_moves;
|
||||||
|
|
||||||
// §6e — fields where a battue (hit) was scored; ripple animation shown there.
|
// fields where a battue (hit) was scored; ripple animation shown there.
|
||||||
let hit_fields: Vec<u8> = {
|
let hit_fields: Vec<u8> = {
|
||||||
let is_hit_jan = |jan: &Jan| {
|
let is_hit_jan = |jan: &Jan| {
|
||||||
matches!(
|
matches!(
|
||||||
|
|
@ -337,7 +337,7 @@ pub fn GameScreen(state: GameUiState) -> impl IntoView {
|
||||||
hit_fields=hit_fields
|
hit_fields=hit_fields
|
||||||
/>
|
/>
|
||||||
|
|
||||||
// ── Status, hints, and actions — cream strip below board (§10b/c) ─
|
// ── Status, hints, and actions — cream strip below board ─
|
||||||
<div class="game-bottom-strip">
|
<div class="game-bottom-strip">
|
||||||
<div class="game-status">
|
<div class="game-status">
|
||||||
{move || {
|
{move || {
|
||||||
|
|
|
||||||
|
|
@ -80,8 +80,7 @@ pub fn ScoringPanel(
|
||||||
"scoring-panel"
|
"scoring-panel"
|
||||||
};
|
};
|
||||||
|
|
||||||
// minimized: starts false (expanded), becomes true after 3.4 s unless
|
// minimized: starts false (expanded)
|
||||||
// the Hold/Go choice still needs the player's attention.
|
|
||||||
let minimized = RwSignal::new(false);
|
let minimized = RwSignal::new(false);
|
||||||
|
|
||||||
// Collect all moves from all jans for automatic arrow display.
|
// Collect all moves from all jans for automatic arrow display.
|
||||||
|
|
@ -97,6 +96,43 @@ pub fn ScoringPanel(
|
||||||
let hovered_ctx = use_context::<RwSignal<Vec<(CheckerMove, CheckerMove)>>>();
|
let hovered_ctx = use_context::<RwSignal<Vec<(CheckerMove, CheckerMove)>>>();
|
||||||
let jan_rows: Vec<_> = event.jans.into_iter().map(scoring_jan_row).collect();
|
let jan_rows: Vec<_> = event.jans.into_iter().map(scoring_jan_row).collect();
|
||||||
|
|
||||||
|
// On mount: show all this event's moves as board arrows immediately,
|
||||||
|
// then after 3.4 s slide to peek and clear the arrows.
|
||||||
|
//
|
||||||
|
// Two important constraints:
|
||||||
|
// 1. The initial hm.set() must be deferred (spawn_local, not sync in body)
|
||||||
|
// to avoid writing a reactive signal mid-render while Board reads it —
|
||||||
|
// that triggers Leptos's cycle guard → `unreachable` WASM panic.
|
||||||
|
// 2. The cancellation flag must be Rc<Cell<bool>>, NOT RwSignal<bool>.
|
||||||
|
// RwSignal is a NodeId into Leptos's arena; the arena slot is freed
|
||||||
|
// when ScoringPanel's owner drops (on every GameScreen remount). If the
|
||||||
|
// 3.4 s future outlives the component and calls is_alive.get_untracked()
|
||||||
|
// on a freed slot, that also panics with `unreachable`. Rc<Cell<bool>>
|
||||||
|
// is reference-counted outside the arena and stays valid for as long as
|
||||||
|
// the future holds onto it.
|
||||||
|
#[cfg(target_arch = "wasm32")]
|
||||||
|
if let Some(hm) = hovered_ctx {
|
||||||
|
let is_alive = Arc::new(AtomicBool::new(true));
|
||||||
|
let is_alive_cleanup = is_alive.clone();
|
||||||
|
// on_cleanup requires Send + Sync; Arc<AtomicBool> satisfies both.
|
||||||
|
on_cleanup(move || is_alive_cleanup.store(false, Ordering::Relaxed));
|
||||||
|
|
||||||
|
spawn_local(async move {
|
||||||
|
// Show arrows (runs in the next microtask, after render settles).
|
||||||
|
hm.set(all_moves);
|
||||||
|
|
||||||
|
TimeoutFuture::new(3_400).await;
|
||||||
|
// Guard: component may have been destroyed while we were waiting.
|
||||||
|
// is_alive was set to false by on_cleanup, which runs before Leptos
|
||||||
|
// frees the signal arena slots — so peeked is still valid iff this
|
||||||
|
// returns true.
|
||||||
|
if !is_alive.load(Ordering::Relaxed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
hm.set(vec![]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
view! {
|
view! {
|
||||||
<div
|
<div
|
||||||
class="scoring-panel-wrapper"
|
class="scoring-panel-wrapper"
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue