p5 0.10.0

A tui client for Pulumi
Documentation
use ratatui::{
    layout::{Alignment, Direction, Layout},
    style::Style,
    widgets::{Block, Paragraph, StatefulWidget, Widget},
};

use crate::state::{
    AppContext, AppState, Loadable, OperationContext, ProgramOperation, StackContext,
};

use super::{ResourceListState, resource_list::ResourceList, theme::color};

#[derive(Default, Clone)]
pub struct StackOperation {}

#[derive(Debug, Clone, strum::Display, Eq, PartialEq)]
enum StackOperationType {
    Update,
    Refresh,
    Destroy,
    Preview,
}

impl StatefulWidget for StackOperation {
    type State = AppState;

    fn render(
        self,
        area: ratatui::prelude::Rect,
        buf: &mut ratatui::prelude::Buffer,
        state: &mut Self::State,
    ) {
        let background_context = state.background_context();
        if let Some((operation, selection)) = state.stack_operation_state() {
            let change_summary = operation.change_summary.as_ref();
            let events = operation.events.as_ref();
            let operation_view = match operation.operation {
                ProgramOperation::Update if operation.is_preview() => StackOperationType::Preview,
                ProgramOperation::Update => StackOperationType::Update,
                ProgramOperation::Refresh => StackOperationType::Refresh,
                ProgramOperation::Destroy => StackOperationType::Destroy,
            };

            let title = format!(
                "{} - {}",
                operation_view,
                match change_summary {
                    Loadable::Loaded(_) => match events {
                        Loadable::Loaded(e) => {
                            if e.done { "Complete" } else { "In Progress" }
                        }
                        Loadable::Loading => "Loading Events",
                        Loadable::NotLoaded => "Waiting for Events",
                    },
                    Loadable::Loading => "Loading Summary",
                    Loadable::NotLoaded => "No Summary Available",
                }
            );

            let should_split = events.is_loaded() && operation_view != StackOperationType::Preview;

            let layout_constraints = if should_split {
                vec![
                    ratatui::layout::Constraint::Percentage(90),
                    ratatui::layout::Constraint::Percentage(10),
                ]
            } else {
                vec![ratatui::layout::Constraint::Percentage(100)]
            };

            let layout = Layout::default()
                .direction(Direction::Vertical)
                .constraints(layout_constraints)
                .split(area);

            let summary_area = if should_split { layout[1] } else { layout[0] };

            if let Loadable::Loaded(events) = events {
                ResourceList::from_operations(
                    Block::bordered()
                        .title(title.to_string())
                        .border_type(ratatui::widgets::BorderType::Rounded)
                        .border_style(Style::default().fg(
                            if let AppContext::Stack(StackContext::Operation(
                                OperationContext::Events,
                            )) = background_context
                            {
                                color::BORDER_HIGHLIGHT
                            } else {
                                color::BORDER_DEFAULT
                            },
                        )),
                    &events.states,
                )
                .render(layout[0], buf, &mut ResourceListState::default());
            }

            let block = Block::bordered()
                .title(if events.is_loaded() {
                    "Preview - Summary".to_string()
                } else {
                    "Preview".to_string()
                })
                .border_type(ratatui::widgets::BorderType::Rounded)
                .border_style(Style::default().fg(
                    if let AppContext::Stack(StackContext::Operation(OperationContext::Summary)) =
                        background_context
                    {
                        color::BORDER_HIGHLIGHT
                    } else {
                        color::BORDER_DEFAULT
                    },
                ));

            match change_summary {
                Loadable::Loaded(change_summary) => {
                    ResourceList::from_summary(block, change_summary).render(
                        summary_area,
                        buf,
                        selection,
                    );
                }
                Loadable::Loading => {
                    Paragraph::new("Loading...".to_string())
                        .block(block)
                        .alignment(Alignment::Left)
                        .render(summary_area, buf);
                }
                Loadable::NotLoaded => {
                    Paragraph::new("No Stack Selected".to_string())
                        .block(block)
                        .alignment(Alignment::Left)
                        .render(summary_area, buf);
                }
            };
        } else {
            Paragraph::new("No Operation in Progress")
                .block(
                    Block::bordered()
                        .title("Stack Operation")
                        .border_type(ratatui::widgets::BorderType::Rounded),
                )
                .alignment(Alignment::Center)
                .render(area, buf);
        }
    }
}