use std::time::Instant;
use ratatui::widgets::ListState;
use tokio::sync::mpsc;
mod actions;
mod context;
mod dispatch;
mod events;
mod footer_spec;
mod input_field;
mod logs;
mod machine;
mod types;
mod ui_specs;
mod view;
pub use footer_spec::{FooterSpec, FooterTone};
pub use types::{
ActiveTab, FocusPane, HostField, InputMode, JoinField, LOG_CAP, RELAYS, Step, TAB_TITLES,
TunnelPhase,
};
pub use ui_specs::{FieldSpec, HelpLineSpec, PanelSpec, RelayOptionSpec};
pub(crate) use context::AppContext;
use input_field::InputField;
pub struct AppState {
pub show_help: bool,
pub confirm_stop: bool,
pub tick: u64,
pub tab: ActiveTab,
pub focus: FocusPane,
pub input_mode: InputMode,
pub quit_pressed_at: Option<Instant>,
pub logs: Vec<String>,
pub log_state: ListState,
pub relay_state: ListState,
pub relay_idx: usize,
pub phase: TunnelPhase,
pub active_mode: Option<ActiveTab>,
pub ticket: Option<String>,
pub connections: Vec<sculk::tunnel::ConnectionSnapshot>,
pub host_port: InputField,
pub host_password: InputField,
pub host_field: HostField,
pub join_ticket: InputField,
pub join_port: InputField,
pub join_password: InputField,
pub join_field: JoinField,
pub relay_url: InputField,
pub(crate) ctx: AppContext,
}
impl AppState {
pub fn new(app_tx: mpsc::UnboundedSender<crate::services::tunnel::AppEvent>) -> Self {
let (profile, profile_err) = crate::services::persist::load_profile();
let relay_idx = if profile.relay.custom { 1 } else { 0 };
let relay_url_value: String = profile.relay.url.clone().unwrap_or_default();
let host_port_str = profile.host.port.to_string();
let join_port_str = profile.join.port.to_string();
let last_ticket: String = profile.join.last_ticket.clone().unwrap_or_default();
let mut state = Self {
show_help: false,
confirm_stop: false,
tick: 0,
tab: ActiveTab::Host,
focus: FocusPane::Profile,
input_mode: InputMode::Normal,
quit_pressed_at: None,
logs: Vec::new(),
log_state: ListState::default(),
relay_state: ListState::default(),
relay_idx,
phase: TunnelPhase::Idle,
active_mode: None,
ticket: None,
connections: Vec::new(),
host_port: InputField::with_value("端口", &host_port_str),
host_password: InputField::new("密码"),
host_field: HostField::Port,
join_ticket: InputField::with_value("票据", &last_ticket),
join_port: InputField::with_value("端口", &join_port_str),
join_password: InputField::new("密码"),
join_field: JoinField::Ticket,
relay_url: InputField::with_value("URL", &relay_url_value),
ctx: AppContext::new(app_tx, profile),
};
state.relay_state.select(Some(relay_idx));
state.add_log("已就绪,按 Enter 执行当前模式");
if let Some(err) = profile_err {
state.add_log(&err);
}
state
}
pub fn handle_key(&mut self, key: crossterm::event::KeyEvent) -> Step {
dispatch::handle_key(self, key)
}
pub fn primary_action(&mut self) {
actions::primary_action(self);
}
pub fn handle_app_event(&mut self, event: crate::services::tunnel::AppEvent) {
events::handle_app_event(self, event);
}
pub fn on_tick(&mut self) {
events::on_tick(self);
}
pub fn status_label(&self) -> (&str, crate::ui::theme::StatusColor) {
view::status_label(self)
}
pub fn route_strength(&self) -> u8 {
view::route_strength(self)
}
pub fn route_info(&self) -> &str {
view::route_info(self)
}
pub fn gauge_label(&self) -> String {
view::gauge_label(self)
}
pub fn connection_label(&self) -> String {
view::connection_label(self)
}
pub fn relay_label(&self) -> &str {
view::relay_label(self)
}
pub fn esc_action_label(&self) -> &'static str {
view::esc_action_label(self)
}
pub fn esc_can_exit(&self) -> bool {
view::esc_can_exit(self)
}
pub fn footer_spec(&self) -> FooterSpec {
footer_spec::footer_spec(self)
}
pub(crate) fn header_spec(&self) -> ui_specs::HeaderSpec {
ui_specs::header_spec(self)
}
pub(crate) fn logs_spec(
&self,
visible_height: usize,
message_width: usize,
) -> ui_specs::LogsSpec {
ui_specs::logs_spec(self, visible_height, message_width)
}
pub(crate) fn tabs_spec(&self) -> ui_specs::TabsSpec {
ui_specs::tabs_spec(self)
}
pub(crate) fn help_popup_spec(&self) -> ui_specs::HelpPopupSpec {
ui_specs::help_popup_spec(self)
}
pub(crate) fn confirm_stop_popup_spec(&self) -> ui_specs::ConfirmStopPopupSpec {
ui_specs::confirm_stop_popup_spec(self)
}
pub(crate) fn edit_popup_spec(&self) -> ui_specs::EditPopupSpec {
ui_specs::edit_popup_spec(self)
}
pub fn clear_logs(&mut self) {
logs::clear_logs(self);
}
pub fn add_log(&mut self, msg: &str) {
logs::add_log(self, msg);
}
pub fn next_log(&mut self) {
logs::next_log(self);
}
pub fn prev_log(&mut self) {
logs::prev_log(self);
}
pub fn next_relay_selection(&mut self) {
logs::next_relay_selection(self);
}
pub fn prev_relay_selection(&mut self) {
logs::prev_relay_selection(self);
}
}
#[cfg(test)]
mod tests;