node_launchpad/components/
help.rs

1// Copyright 2025 MaidSafe.net limited.
2//
3// This SAFE Network Software is licensed to you under The General Public License (GPL), version 3.
4// Unless required by applicable law or agreed to in writing, the SAFE Network Software distributed
5// under the GPL Licence is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
6// KIND, either express or implied. Please review the Licences for the specific language governing
7// permissions and limitations relating to use of the SAFE Network Software.
8
9use super::header::SelectedMenuItem;
10use color_eyre::eyre::Result;
11use ratatui::{
12    layout::{Constraint, Direction, Layout, Rect},
13    style::{Style, Stylize},
14    text::Span,
15    widgets::{Block, Borders, Padding},
16    Frame,
17};
18use tokio::sync::mpsc::UnboundedSender;
19
20use super::Component;
21use crate::{
22    action::Action,
23    components::header::Header,
24    mode::{InputMode, Scene},
25    style::{COOL_GREY, GHOST_WHITE, VIVID_SKY_BLUE},
26    widgets::hyperlink::Hyperlink,
27};
28
29#[derive(Clone)]
30pub struct Help {
31    pub active: bool,
32    pub action_tx: Option<UnboundedSender<Action>>,
33}
34
35impl Help {
36    pub async fn new() -> Result<Self> {
37        Ok(Self {
38            active: false,
39            action_tx: None,
40        })
41    }
42}
43
44impl Component for Help {
45    fn init(&mut self, _area: Rect) -> Result<()> {
46        Ok(())
47    }
48
49    fn draw(&mut self, f: &mut Frame<'_>, area: Rect) -> Result<()> {
50        if !self.active {
51            return Ok(());
52        }
53        // We define a layout, top and down box.
54        let layout = Layout::default()
55            .direction(Direction::Vertical)
56            .constraints(vec![Constraint::Length(1), Constraint::Length(9)])
57            .split(area);
58
59        // ==== Header =====
60        let header = Header::new();
61        f.render_stateful_widget(header, layout[0], &mut SelectedMenuItem::Help);
62
63        // ---- Get Help & Support ----
64        // Links
65
66        // Create a new layout as a table, so we can render hyperlinks
67        let columns_layout = Layout::default()
68            .direction(Direction::Horizontal)
69            .constraints(vec![Constraint::Percentage(50), Constraint::Percentage(50)])
70            .split(layout[1]);
71
72        let padded_area_left = Rect {
73            x: columns_layout[0].x + 2,
74            y: columns_layout[0].y + 2,
75            width: columns_layout[0].width - 2,
76            height: columns_layout[0].height - 2,
77        };
78
79        let left_column = Layout::default()
80            .direction(Direction::Vertical)
81            .constraints(vec![
82                Constraint::Max(1),
83                Constraint::Max(2),
84                Constraint::Max(1),
85                Constraint::Max(2),
86            ])
87            .split(padded_area_left);
88
89        let padded_area_right = Rect {
90            x: columns_layout[1].x + 2,
91            y: columns_layout[1].y + 2,
92            width: columns_layout[1].width - 2,
93            height: columns_layout[1].height - 2,
94        };
95        let right_column = Layout::default()
96            .direction(Direction::Vertical)
97            .constraints(vec![
98                Constraint::Max(1),
99                Constraint::Max(2),
100                Constraint::Max(1),
101                Constraint::Max(2),
102            ])
103            .split(padded_area_right);
104
105        let quickstart_guide_link = Hyperlink::new(
106            Span::styled(
107                "autonomi.com/getstarted",
108                Style::default().fg(VIVID_SKY_BLUE).underlined(),
109            ),
110            "https://autonomi.com/getstarted",
111        );
112        let terms_and_conditions_link = Hyperlink::new(
113            Span::styled(
114                "autonomi.com/terms",
115                Style::default().fg(VIVID_SKY_BLUE).underlined(),
116            ),
117            "https://autonomi.com/terms",
118        );
119        let get_direct_support_link = Hyperlink::new(
120            Span::styled(
121                "autonomi.com/support",
122                Style::default().fg(VIVID_SKY_BLUE).underlined(),
123            ),
124            "https://autonomi.com/support",
125        );
126        let download_latest_link = Hyperlink::new(
127            Span::styled(
128                "autonomi.com/downloads",
129                Style::default().fg(VIVID_SKY_BLUE).underlined(),
130            ),
131            "https://autonomi.com/downloads",
132        );
133
134        let block = Block::new()
135            .borders(Borders::ALL)
136            .border_style(Style::default().fg(COOL_GREY))
137            .padding(Padding::uniform(1))
138            .title(" Get Help & Support ")
139            .bold()
140            .title_style(Style::default().bold().fg(GHOST_WHITE));
141
142        // Render hyperlinks in the new area
143        f.render_widget(
144            Span::styled(
145                "Read the quick start guides:",
146                Style::default().fg(GHOST_WHITE),
147            ),
148            left_column[0],
149        );
150        f.render_widget_ref(quickstart_guide_link, left_column[1]);
151        f.render_widget(
152            Span::styled("Get Direct Support:", Style::default().fg(GHOST_WHITE)),
153            left_column[2],
154        );
155        f.render_widget_ref(get_direct_support_link, left_column[3]);
156        f.render_widget(
157            Span::styled(
158                "Download the latest launchpad:",
159                Style::default().fg(GHOST_WHITE),
160            ),
161            right_column[0],
162        );
163        f.render_widget_ref(download_latest_link, right_column[1]);
164        f.render_widget(
165            Span::styled("Terms & Conditions:", Style::default().fg(GHOST_WHITE)),
166            right_column[2],
167        );
168        f.render_widget_ref(terms_and_conditions_link, right_column[3]);
169
170        f.render_widget(block, layout[1]);
171
172        Ok(())
173    }
174
175    fn update(&mut self, action: Action) -> Result<Option<Action>> {
176        if let Action::SwitchScene(scene) = action {
177            if let Scene::Help = scene {
178                self.active = true;
179                // make sure we're in navigation mode
180                return Ok(Some(Action::SwitchInputMode(InputMode::Navigation)));
181            } else {
182                self.active = false;
183            }
184        }
185        Ok(None)
186    }
187}