Skip to main content

modde_ui/views/
mod_details.rs

1//! Right-rail "mod details" panel — rendered at the bottom of the left nav
2//! sidebar when a mod with Nexus metadata is selected in the mod list.
3//!
4//! The state is populated asynchronously from the Nexus v1 REST API (basic
5//! metadata + primary `picture_url`) plus the v2 GraphQL endpoint (full image
6//! gallery). See `crates/modde-ui/src/app.rs` for the fetch flow.
7
8use iced::widget::image;
9
10/// Live state for the currently-selected mod's detail panel.
11#[derive(Debug, Clone)]
12pub struct ModDetailsState {
13    /// Nexus mod id — used to reject stale async results when the user
14    /// clicks on a different mod before the previous fetch completes.
15    pub nexus_mod_id: i64,
16    /// Nexus game domain (e.g. `"skyrimspecialedition"`).
17    pub game_domain: String,
18    /// Full URL to the mod page on nexusmods.com — the "Open in Nexus" link
19    /// opens this in the system browser.
20    pub mod_page_url: String,
21
22    /// Loaded metadata. Until the initial fetch returns, these carry
23    /// whatever we knew locally from `EnabledMod` (display_name, version).
24    pub name: String,
25    pub author: String,
26    pub version: String,
27    pub summary: Option<String>,
28
29    /// True between sending the initial `get_mod` request and receiving the
30    /// response. The panel renders a "Loading…" placeholder in this state.
31    pub loading: bool,
32    /// If set, the initial fetch failed — we render the error text instead
33    /// of the metadata block.
34    pub error: Option<String>,
35
36    /// Image URLs for the gallery. Index 0 is typically the primary
37    /// `picture_url`. Empty until at least the v1 response arrives.
38    pub gallery: Vec<String>,
39    /// Which gallery index is currently displayed. Clicking the thumbnail
40    /// advances this (mod `gallery.len()`).
41    pub gallery_index: usize,
42    /// Decoded bytes of the image at `gallery_index`, ready for rendering.
43    /// `None` while the image is being fetched.
44    pub thumbnail: Option<image::Handle>,
45
46    /// User's current endorsement status for this mod. Values from Nexus:
47    /// `"Undecided"`, `"Abstained"`, `"Endorsed"`. `None` until fetched.
48    pub endorse_status: Option<String>,
49    /// Total endorsements on the mod (not user-specific).
50    pub endorsement_count: u64,
51    /// Whether the current user is tracking this mod. `None` = not yet
52    /// fetched, `Some(true)` = tracked, `Some(false)` = not tracked.
53    pub is_tracked: Option<bool>,
54    /// True while an endorse/track request is in flight. Disables both
55    /// buttons to prevent double-submits.
56    pub action_pending: bool,
57}
58
59impl ModDetailsState {
60    /// Construct the initial "loading" state as soon as a Nexus-tracked mod
61    /// is selected, before any HTTP requests complete.
62    pub fn loading(
63        nexus_mod_id: i64,
64        game_domain: String,
65        name: String,
66        version: String,
67    ) -> Self {
68        let mod_page_url = format!("https://www.nexusmods.com/{game_domain}/mods/{nexus_mod_id}");
69        Self {
70            nexus_mod_id,
71            game_domain,
72            mod_page_url,
73            name,
74            author: String::new(),
75            version,
76            summary: None,
77            loading: true,
78            error: None,
79            gallery: Vec::new(),
80            gallery_index: 0,
81            thumbnail: None,
82            endorse_status: None,
83            endorsement_count: 0,
84            is_tracked: None,
85            action_pending: false,
86        }
87    }
88
89    /// The URL of the image currently displayed in the thumbnail slot, if any.
90    pub fn current_image_url(&self) -> Option<&str> {
91        self.gallery.get(self.gallery_index).map(|s| s.as_str())
92    }
93}