feat(engine): live sync status in Settings panel
The Settings panel now shows a "Sync" section with a live status line: - "Status: idle" (no SyncPlugin installed) - "Status: syncing…" (pull in progress) - "Last synced: Xs ago" (successful pull) - "Sync error: <msg>" (failed pull) The text is snapshotted from SyncStatusResource when the panel opens and updated reactively via update_sync_status_text whenever SyncStatusResource changes while the panel is visible. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -15,6 +15,8 @@ use bevy::prelude::*;
|
|||||||
use solitaire_core::game_state::DrawMode;
|
use solitaire_core::game_state::DrawMode;
|
||||||
use solitaire_data::{load_settings_from, save_settings_to, settings_file_path, settings::Theme, Settings};
|
use solitaire_data::{load_settings_from, save_settings_to, settings_file_path, settings::Theme, Settings};
|
||||||
|
|
||||||
|
use crate::resources::{SyncStatus, SyncStatusResource};
|
||||||
|
|
||||||
/// Volume adjustment step applied by the `[` / `]` hotkeys.
|
/// Volume adjustment step applied by the `[` / `]` hotkeys.
|
||||||
pub const SFX_STEP: f32 = 0.1;
|
pub const SFX_STEP: f32 = 0.1;
|
||||||
|
|
||||||
@@ -54,6 +56,10 @@ struct DrawModeText;
|
|||||||
#[derive(Component, Debug)]
|
#[derive(Component, Debug)]
|
||||||
struct ThemeText;
|
struct ThemeText;
|
||||||
|
|
||||||
|
/// Marks the `Text` node showing the live sync status.
|
||||||
|
#[derive(Component, Debug)]
|
||||||
|
struct SyncStatusText;
|
||||||
|
|
||||||
/// Tags interactive buttons inside the Settings panel.
|
/// Tags interactive buttons inside the Settings panel.
|
||||||
#[derive(Component, Debug)]
|
#[derive(Component, Debug)]
|
||||||
enum SettingsButton {
|
enum SettingsButton {
|
||||||
@@ -109,7 +115,11 @@ impl Plugin for SettingsPlugin {
|
|||||||
if self.ui_enabled {
|
if self.ui_enabled {
|
||||||
app.add_systems(
|
app.add_systems(
|
||||||
Update,
|
Update,
|
||||||
(sync_settings_panel_visibility, handle_settings_buttons),
|
(
|
||||||
|
sync_settings_panel_visibility,
|
||||||
|
handle_settings_buttons,
|
||||||
|
update_sync_status_text,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -172,13 +182,17 @@ fn sync_settings_panel_visibility(
|
|||||||
panels: Query<Entity, With<SettingsPanel>>,
|
panels: Query<Entity, With<SettingsPanel>>,
|
||||||
mut commands: Commands,
|
mut commands: Commands,
|
||||||
settings: Res<SettingsResource>,
|
settings: Res<SettingsResource>,
|
||||||
|
sync_status: Option<Res<SyncStatusResource>>,
|
||||||
) {
|
) {
|
||||||
if !screen.is_changed() {
|
if !screen.is_changed() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if screen.0 {
|
if screen.0 {
|
||||||
if panels.is_empty() {
|
if panels.is_empty() {
|
||||||
spawn_settings_panel(&mut commands, &settings.0);
|
let status_label = sync_status
|
||||||
|
.map(|s| sync_status_label(&s.0))
|
||||||
|
.unwrap_or_else(|| "Status: not configured".to_string());
|
||||||
|
spawn_settings_panel(&mut commands, &settings.0, &status_label);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for entity in &panels {
|
for entity in &panels {
|
||||||
@@ -187,6 +201,42 @@ fn sync_settings_panel_visibility(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Keeps the sync-status text node current while the panel is open.
|
||||||
|
fn update_sync_status_text(
|
||||||
|
sync_status: Option<Res<SyncStatusResource>>,
|
||||||
|
mut text_nodes: Query<&mut Text, With<SyncStatusText>>,
|
||||||
|
) {
|
||||||
|
let Some(status) = sync_status else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
if !status.is_changed() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let label = sync_status_label(&status.0);
|
||||||
|
for mut text in &mut text_nodes {
|
||||||
|
**text = label.clone();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sync_status_label(status: &SyncStatus) -> String {
|
||||||
|
match status {
|
||||||
|
SyncStatus::Idle => "Status: idle".to_string(),
|
||||||
|
SyncStatus::Syncing => "Status: syncing…".to_string(),
|
||||||
|
SyncStatus::LastSynced(t) => {
|
||||||
|
let secs = chrono::Utc::now()
|
||||||
|
.signed_duration_since(*t)
|
||||||
|
.num_seconds()
|
||||||
|
.max(0);
|
||||||
|
if secs < 60 {
|
||||||
|
format!("Last synced: {secs}s ago")
|
||||||
|
} else {
|
||||||
|
format!("Last synced: {}m ago", secs / 60)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SyncStatus::Error(e) => format!("Sync error: {e}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Reacts to button presses inside the Settings panel.
|
/// Reacts to button presses inside the Settings panel.
|
||||||
#[allow(clippy::too_many_arguments, clippy::type_complexity)]
|
#[allow(clippy::too_many_arguments, clippy::type_complexity)]
|
||||||
fn handle_settings_buttons(
|
fn handle_settings_buttons(
|
||||||
@@ -298,7 +348,7 @@ fn theme_label(theme: &Theme) -> String {
|
|||||||
// UI construction
|
// UI construction
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
fn spawn_settings_panel(commands: &mut Commands, settings: &Settings) {
|
fn spawn_settings_panel(commands: &mut Commands, settings: &Settings, sync_status: &str) {
|
||||||
commands
|
commands
|
||||||
.spawn((
|
.spawn((
|
||||||
SettingsPanel,
|
SettingsPanel,
|
||||||
@@ -401,6 +451,15 @@ fn spawn_settings_panel(commands: &mut Commands, settings: &Settings) {
|
|||||||
icon_button(row, "⇄", SettingsButton::ToggleTheme);
|
icon_button(row, "⇄", SettingsButton::ToggleTheme);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// --- Sync section ---
|
||||||
|
section_label(card, "Sync");
|
||||||
|
card.spawn((
|
||||||
|
SyncStatusText,
|
||||||
|
Text::new(sync_status.to_string()),
|
||||||
|
TextFont { font_size: 16.0, ..default() },
|
||||||
|
TextColor(Color::srgb(0.65, 0.65, 0.70)),
|
||||||
|
));
|
||||||
|
|
||||||
// Done button
|
// Done button
|
||||||
card.spawn((
|
card.spawn((
|
||||||
SettingsButton::Done,
|
SettingsButton::Done,
|
||||||
|
|||||||
Reference in New Issue
Block a user