terrazzo-terminal 0.2.8

A simple web-based terminal emulator built on Terrazzo.
use std::rc::Rc;

use terrazzo::autoclone;
use terrazzo::html;
use terrazzo::prelude::*;
use terrazzo::widgets::tabs::TabsDescriptor;
use terrazzo::widgets::tabs::TabsState;

use super::terminal_tab::TerminalTab;
use super::ui::TerminalsState;
use crate::api::client_address::ClientAddress;
use crate::assets::icons;
use crate::frontend::menu::menu;
use crate::frontend::remotes::RemotesState;
use crate::terminal::terminal_tabs::add_tab::create_terminal;
use crate::terminal_id::TerminalId;

mod add_tab;
mod move_tab;

terrazzo_css::import_style!(style, "terminal_tabs.scss");

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct TerminalTabs {
    terminal_tabs: Rc<Vec<TerminalTab>>,
}

impl From<Rc<Vec<TerminalTab>>> for TerminalTabs {
    fn from(terminal_tabs: Rc<Vec<TerminalTab>>) -> Self {
        Self { terminal_tabs }
    }
}

impl TabsDescriptor for TerminalTabs {
    type TabDescriptor = TerminalTab;
    type State = TerminalsState;

    fn tab_descriptors(&self) -> &[Self::TabDescriptor] {
        &self.terminal_tabs
    }

    #[html]
    fn before_titles(&self, state: &TerminalsState) -> impl IntoIterator<Item = impl Into<XNode>> {
        Some(menu(state.tile.clone()))
    }

    #[autoclone]
    #[html]
    fn after_titles(&self, state: &TerminalsState) -> impl IntoIterator<Item = impl Into<XNode>> {
        let remotes_state = RemotesState::new();
        [div(
            class = style::ADD_TAB_ICON,
            #[cfg(not(feature = "client-prod"))]
            class = "add-tab-icon",
            key = "add-tab-icon",
            div(
                class %= add_tab::active(remotes_state.remotes.clone()),
                img(src = icons::add_tab()),
                click = move |_| {
                    autoclone!(state);
                    add_tab::create_terminal(state.clone(), ClientAddress::default())
                },
                mouseenter = remotes_state.mouseenter(),
            ),
            mouseleave = remotes_state.mouseleave(),
            remotes_state.show_remotes_dropdown(
                |remote| {
                    let remote_name = if remote.is_empty() {
                        "Local".into()
                    } else {
                        format!("{remote}")
                    };
                    (remote_name, None)
                },
                move |_, remote| {
                    autoclone!(state);
                    create_terminal(state.clone(), remote)
                },
            ),
        )]
    }
}

impl TerminalTabs {
    pub fn add_tab(mut self, new: TerminalTab) -> Self {
        let terminal_tabs = Ptr::make_mut(&mut self.terminal_tabs);
        terminal_tabs.push(new);
        self
    }

    pub fn remove_tab(mut self, id: &TerminalId) -> Self {
        let terminal_tabs = Ptr::make_mut(&mut self.terminal_tabs);
        terminal_tabs.retain(|tab| tab.address.id != *id);
        self
    }

    pub fn lookup_tab(&self, id: &TerminalId) -> Option<&TerminalTab> {
        self.terminal_tabs.iter().find(|tab| tab.address.id == *id)
    }
}

impl TabsState for TerminalsState {
    type TabDescriptor = TerminalTab;

    fn move_tab(&self, after_tab: Option<TerminalTab>, moved_tab_key: String) {
        move_tab::move_tab(self.clone(), after_tab, moved_tab_key)
    }

    fn drag_key() -> &'static str {
        "terminal_id"
    }

    fn zone_id(&self) -> Option<String> {
        format!("tile_id:{}", self.tile.id).into()
    }
}