//! Banner shown when the remote-server binary check, installation, or connection fails on the remote host. //! We fall back to the existing Warpification behavior and display this banner so the user knows why advanced features are unavailable. use galaxy_core::ui::theme::color::internal_colors; use galaxyui::{ elements::{ ConstrainedBox, Container, CrossAxisAlignment, Flex, Hoverable, MainAxisAlignment, MainAxisSize, MouseStateHandle, ParentElement, Shrinkable, Text, }, platform::Cursor, AppContext, Element, Entity, SingletonEntity, TypedActionView, View, ViewContext, }; use crate::{terminal::model::session::SessionId, ui_components::icons::Icon, Appearance}; #[derive(Clone, Debug)] pub enum SshRemoteServerFailedBannerAction { Dismiss, } #[derive(Clone, Debug)] pub enum SshRemoteServerFailedBannerEvent { Dismissed, } pub struct SshRemoteServerFailedBanner { session_id: SessionId, close_mouse_state: MouseStateHandle, } impl SshRemoteServerFailedBanner { pub fn new(session_id: SessionId) -> Self { Self { session_id, close_mouse_state: MouseStateHandle::default(), } } pub fn session_id(&self) -> SessionId { self.session_id } } impl Entity for SshRemoteServerFailedBanner { type Event = SshRemoteServerFailedBannerEvent; } impl View for SshRemoteServerFailedBanner { fn ui_name() -> &'static str { "SshRemoteServerFailedBanner" } fn render(&self, app: &AppContext) -> Box { let appearance = Appearance::as_ref(app); let theme = appearance.theme(); let fg_color = theme.foreground().into_solid(); let muted_color = internal_colors::neutral_5(theme); let font_size = appearance.monospace_font_size(); let small_font_size = font_size - 2.; // Alert-circle icon let icon = Container::new( ConstrainedBox::new(Icon::AlertCircle.to_galaxyui_icon(fg_color.into()).finish()) .with_width(16.) .with_height(16.) .finish(), ) .with_margin_right(8.) .finish(); // Title let title = Text::new( "SSH extension couldn't be installed", appearance.ui_font_family(), font_size, ) .with_color(fg_color) .finish(); // Description let body = Text::new( "The binary could not be written or executed on the remote host. \ This may be due to permission restrictions or missing dependencies. \ While advanced features like file browsing and code review are currently \ disabled, the rest of your Warpified experience is fully available.", appearance.ui_font_family(), small_font_size, ) .soft_wrap(true) .with_color(muted_color) .finish(); // Close (X) button let close_icon_color = muted_color; let close = Hoverable::new(self.close_mouse_state.clone(), move |_| { ConstrainedBox::new(Icon::X.to_galaxyui_icon(close_icon_color.into()).finish()) .with_width(16.) .with_height(16.) .finish() }) .with_cursor(Cursor::PointingHand) .on_click(|ctx, _, _| { ctx.dispatch_typed_action(SshRemoteServerFailedBannerAction::Dismiss); }) .finish(); let close_container = Container::new(close).with_uniform_padding(4.).finish(); // Header row: [icon + title] ... [close] let header_row = Flex::row() .with_main_axis_size(MainAxisSize::Max) .with_main_axis_alignment(MainAxisAlignment::SpaceBetween) .with_cross_axis_alignment(CrossAxisAlignment::Center) .with_child( Shrinkable::new( 1., Flex::row() .with_cross_axis_alignment(CrossAxisAlignment::Center) .with_child(icon) .with_child(Shrinkable::new(1., title).finish()) .finish(), ) .finish(), ) .with_child(close_container) .finish(); // Body text indented past the icon to align with the title let body_container = Container::new(body) .with_margin_top(2.) .with_margin_left(24.) .finish(); let content = Flex::column() .with_main_axis_size(MainAxisSize::Min) .with_child(header_row) .with_child(body_container) .finish(); Container::new(content) .with_background(internal_colors::fg_overlay_1(theme)) .with_uniform_padding(12.) .finish() } } impl TypedActionView for SshRemoteServerFailedBanner { type Action = SshRemoteServerFailedBannerAction; fn handle_action(&mut self, action: &Self::Action, ctx: &mut ViewContext) { match action { SshRemoteServerFailedBannerAction::Dismiss => { ctx.emit(SshRemoteServerFailedBannerEvent::Dismissed); } } } }