use eframe::egui;
use super::components::icons::Icon;
use super::store::Store;
use crate::docker::{Change, ComposeOp, FileOp, ImageOp, Resource, RunSpec, Task, TermOp};
use crate::theme::Palette;
#[derive(Debug, Clone, PartialEq)]
pub enum Intent {
Run(Task),
Confirm(Confirmation),
Inspect(Resource, String),
OpenPage(&'static str),
Open { page: &'static str, id: String },
ClearSearch,
Notify { text: String, is_error: bool },
Change(Change),
Processes(String),
History(String),
Pull(String),
RunImage(RunSpec),
Files(FileOp),
Terminal(TermOp),
Image(ImageOp),
Compose(ComposeOp),
OpenComposeFile,
}
#[derive(Debug, Clone, PartialEq, Default)]
pub struct Confirmation {
pub title: String,
pub message: String,
pub action_label: String,
pub tasks: Vec<Task>,
pub option: Option<ConfirmOption>,
pub typed: Option<TypedCheck>,
}
#[derive(Debug, Clone, PartialEq, Default)]
pub struct ConfirmOption {
pub label: String,
pub hint: String,
pub on: bool,
pub tasks: Vec<Task>,
}
#[derive(Debug, Clone, PartialEq, Default)]
pub struct TypedCheck {
pub word: String,
pub entered: String,
}
impl TypedCheck {
pub fn new(word: &str) -> Self {
Self {
word: word.to_owned(),
entered: String::new(),
}
}
pub fn matches(&self) -> bool {
self.entered.trim() == self.word
}
}
impl Confirmation {
pub fn chosen_tasks(self) -> Vec<Task> {
match self.option {
Some(option) if option.on => option.tasks,
_ => self.tasks,
}
}
pub fn ready(&self) -> bool {
self.typed.as_ref().is_none_or(TypedCheck::matches)
}
}
pub struct Command {
pub label: String,
pub detail: String,
pub run: CommandRun,
}
pub enum CommandRun {
Select(String),
Intents(Vec<Intent>),
}
impl Command {
pub fn select(
label: impl Into<String>,
detail: impl Into<String>,
id: impl Into<String>,
) -> Self {
Self {
label: label.into(),
detail: detail.into(),
run: CommandRun::Select(id.into()),
}
}
pub fn intent(label: impl Into<String>, detail: impl Into<String>, intent: Intent) -> Self {
Self {
label: label.into(),
detail: detail.into(),
run: CommandRun::Intents(vec![intent]),
}
}
}
pub struct PageCtx<'a> {
pub store: &'a Store,
pub palette: &'static Palette,
search: &'a str,
intents: &'a mut Vec<Intent>,
}
impl<'a> PageCtx<'a> {
pub fn new(
store: &'a Store,
palette: &'static Palette,
search: &'a str,
intents: &'a mut Vec<Intent>,
) -> Self {
Self {
store,
palette,
search,
intents,
}
}
pub fn request(&mut self, intent: Intent) {
self.intents.push(intent);
}
pub fn search_is_empty(&self) -> bool {
self.search.trim().is_empty()
}
pub fn matches_search(&self, fields: &[&str]) -> bool {
let needle = self.search.trim().to_lowercase();
needle.is_empty() || fields.iter().any(|f| f.to_lowercase().contains(&needle))
}
}
pub trait Page {
fn title(&self) -> &'static str;
fn icon(&self) -> Icon;
fn resource(&self) -> Resource;
fn badge(&self, store: &Store) -> String;
fn summary(&self, store: &Store) -> String;
fn search_hint(&self) -> &'static str {
"Search"
}
fn bottom(&self) -> bool {
false
}
fn toolbar(&mut self, _ui: &mut egui::Ui, _ctx: &mut PageCtx) {}
fn show(&mut self, ui: &mut egui::Ui, ctx: &mut PageCtx);
fn has_selection(&self, store: &Store) -> bool;
fn show_details(&mut self, ui: &mut egui::Ui, ctx: &mut PageCtx);
fn clear_selection(&mut self);
fn select(&mut self, id: &str);
fn select_all(&mut self, _ctx: &PageCtx) {}
fn commands(&self, _store: &Store) -> Vec<Command> {
Vec::new()
}
fn has_overlay(&self) -> bool {
false
}
fn shortcut(&mut self, _key: egui::Key, _ctx: &mut PageCtx) {}
fn watched_container(&self, _store: &Store) -> Option<String> {
None
}
}
pub fn toggle_selection(selected: &mut Option<String>, id: String) {
*selected = if selected.as_ref() == Some(&id) {
None
} else {
Some(id)
};
}