kanban-tui 0.4.1

Terminal user interface for the kanban project management tool
Documentation
use crate::app::App;
use ratatui::{
    layout::{Constraint, Direction, Layout, Rect},
    style::{Color, Style},
    text::{Line, Span},
    widgets::Paragraph,
    Frame,
};

pub fn render_settings_view(app: &App, frame: &mut Frame, area: Rect) {
    let columns = Layout::default()
        .direction(Direction::Horizontal)
        .constraints([Constraint::Percentage(50), Constraint::Percentage(50)])
        .split(area);

    let left_sections = Layout::default()
        .direction(Direction::Vertical)
        .constraints([Constraint::Percentage(80), Constraint::Percentage(20)])
        .split(columns[0]);

    let config_location = kanban_service::config::effective_configuration_location(&app.app_config);

    render_settings_configuration(app, frame, left_sections[0], &config_location);
    render_settings_config_file(app, frame, left_sections[1], &config_location);
    render_settings_storage(app, frame, columns[1]);
}

fn render_settings_configuration(app: &App, frame: &mut Frame, area: Rect, config_location: &str) {
    use crate::app::SettingsFocus;
    use crate::components::detail_view::{
        metadata_line_selectable, metadata_line_styled, FieldSectionConfig,
    };

    let config_focused = app.focus.settings_focus == SettingsFocus::Configuration;
    let config_section = FieldSectionConfig::new(" Configuration ")
        .with_focus_indicator(" Configuration [1] ")
        .focused(config_focused);
    let config_block = config_section.block();
    let is_config_selected =
        |i: usize| config_focused && app.selection.settings_config.is_selected(i);
    let mut config_lines = vec![
        metadata_line_selectable(
            "Configuration Format",
            app.app_config.effective_configuration_format(),
            is_config_selected(0),
        ),
        metadata_line_selectable(
            "Configuration Location",
            config_location,
            is_config_selected(1),
        ),
        metadata_line_selectable(
            "Default Card Prefix",
            app.app_config.effective_default_card_prefix(),
            is_config_selected(2),
        ),
        metadata_line_selectable(
            "Default Sprint Prefix",
            app.app_config.effective_default_sprint_prefix(),
            is_config_selected(3),
        ),
        metadata_line_selectable(
            "Editing Format",
            app.app_config.effective_editing_format(),
            is_config_selected(4),
        ),
    ];
    if app.has_data_file {
        let active_storage_location =
            kanban_service::config::resolve_storage_location(&app.app_config);
        if app.cli_file_override {
            let storage_configured =
                app.original_storage_backend.is_some() || app.original_storage_location.is_some();
            if storage_configured {
                config_lines.push(metadata_line_styled(
                    "Storage Backend",
                    &app.config_storage_backend,
                    Style::default().fg(Color::DarkGray),
                ));
                config_lines.push(metadata_line_styled(
                    "Storage Location",
                    &app.config_storage_location,
                    Style::default().fg(Color::DarkGray),
                ));
            }
            config_lines.push(metadata_line_selectable(
                "Active Storage Backend",
                app.app_config.effective_storage_backend(),
                is_config_selected(7),
            ));
            config_lines.push(metadata_line_selectable(
                "Active Storage Location",
                &active_storage_location,
                is_config_selected(8),
            ));
        } else if app.cli_file_provided {
            config_lines.push(metadata_line_selectable(
                "Active Storage Backend",
                app.app_config.effective_storage_backend(),
                is_config_selected(5),
            ));
            config_lines.push(metadata_line_selectable(
                "Active Storage Location",
                &active_storage_location,
                is_config_selected(6),
            ));
        } else {
            config_lines.push(metadata_line_selectable(
                "Storage Backend",
                app.app_config.effective_storage_backend(),
                is_config_selected(5),
            ));
            config_lines.push(metadata_line_selectable(
                "Storage Location",
                &active_storage_location,
                is_config_selected(6),
            ));
        }
    }
    let config_paragraph = Paragraph::new(config_lines).block(config_block);
    frame.render_widget(config_paragraph, area);
}

fn render_settings_config_file(app: &App, frame: &mut Frame, area: Rect, config_location: &str) {
    use crate::app::SettingsFocus;
    use crate::components::detail_view::{metadata_line_selectable, FieldSectionConfig};

    let config_file_focused = app.focus.settings_focus == SettingsFocus::ConfigFile;
    let config_file_section = FieldSectionConfig::new(" Config File ")
        .with_focus_indicator(" Config File [2] ")
        .focused(config_file_focused);
    let config_file_block = config_file_section.block();
    let is_cf_selected =
        |i: usize| config_file_focused && app.selection.settings_config_file.is_selected(i);
    let config_path_display = if config_location.is_empty() {
        "(unknown)".to_string()
    } else {
        config_location.to_string()
    };
    let config_exists =
        !config_location.is_empty() && std::path::Path::new(config_location).exists();
    let status = if config_exists { "Loaded" } else { "Not found" };
    let config_format = app.app_config.effective_configuration_format();
    let config_file_lines = vec![
        metadata_line_selectable("Path", &config_path_display, is_cf_selected(0)),
        metadata_line_selectable("Status", status, is_cf_selected(1)),
        metadata_line_selectable("Configuration Format", config_format, is_cf_selected(2)),
    ];
    let config_file_paragraph = Paragraph::new(config_file_lines).block(config_file_block);
    frame.render_widget(config_file_paragraph, area);
}

fn render_settings_storage(app: &App, frame: &mut Frame, area: Rect) {
    use crate::app::SettingsFocus;
    use crate::components::detail_view::{metadata_line_selectable, FieldSectionConfig};
    use crate::theme::colors::SELECTED_BG;

    let storage_focused = app.focus.settings_focus == SettingsFocus::Storage;
    let storage_section = FieldSectionConfig::new(" Storage ")
        .with_focus_indicator(" Storage [3] ")
        .focused(storage_focused);
    let storage_block = storage_section.block();
    let is_storage_selected =
        |i: usize| storage_focused && app.selection.settings_storage.is_selected(i);
    let file_path = app.persistence.save_file.as_deref().unwrap_or("(none)");
    let backend = if app.persistence.save_file.is_some() {
        app.app_config.effective_storage_backend()
    } else {
        "(none)"
    };
    let instance_id = app.ctx.backend().instance_id().to_string();
    let export_selected = is_storage_selected(3);
    let export_checkbox_style = if export_selected {
        Style::default().fg(Color::Yellow).bg(SELECTED_BG)
    } else {
        Style::default().fg(Color::Yellow)
    };
    let export_text_style = if export_selected {
        Style::default().fg(Color::White).bg(SELECTED_BG)
    } else {
        Style::default().fg(Color::White)
    };
    let storage_lines = vec![
        metadata_line_selectable("File", file_path, is_storage_selected(0)),
        metadata_line_selectable("Backend", backend, is_storage_selected(1)),
        metadata_line_selectable("Instance ID", &instance_id, is_storage_selected(2)),
        Line::from(""),
        Line::from(vec![
            Span::styled("  [x] ", export_checkbox_style),
            Span::styled("Export Boards", export_text_style),
        ]),
    ];
    let storage_paragraph = Paragraph::new(storage_lines).block(storage_block);
    frame.render_widget(storage_paragraph, area);
}