use crate::chrome::{ChromeStatus, ChromeTitle};
use crate::report::ReportCommand;
use crate::wizard::WizardCommand;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ButtonsPreset {
Ok,
OkCancel,
YesNo,
YesNoCancel,
RetryCancel,
Custom,
}
impl ButtonsPreset {
pub fn parse(value: &str) -> Option<Self> {
match value {
"ok" => Some(Self::Ok),
"ok_cancel" => Some(Self::OkCancel),
"yes_no" => Some(Self::YesNo),
"yes_no_cancel" => Some(Self::YesNoCancel),
"retry_cancel" => Some(Self::RetryCancel),
"custom" => Some(Self::Custom),
_ => None,
}
}
pub fn all_names() -> &'static [&'static str] {
&[
"ok",
"ok_cancel",
"yes_no",
"yes_no_cancel",
"retry_cancel",
"custom",
]
}
pub fn display_labels(self, custom_buttons: Option<&[String]>) -> Vec<String> {
match self {
Self::Ok => vec!["OK".into()],
Self::OkCancel => vec!["OK".into(), "Cancel".into()],
Self::YesNo => vec!["Yes".into(), "No".into()],
Self::YesNoCancel => vec!["Yes".into(), "No".into(), "Cancel".into()],
Self::RetryCancel => vec!["Retry".into(), "Cancel".into()],
Self::Custom => custom_buttons.unwrap_or(&[]).to_vec(),
}
}
pub fn wire_labels(self, custom_buttons: Option<&[String]>) -> Vec<String> {
match self {
Self::Ok => vec!["ok".into()],
Self::OkCancel => vec!["ok".into(), "cancel".into()],
Self::YesNo => vec!["yes".into(), "no".into()],
Self::YesNoCancel => vec!["yes".into(), "no".into(), "cancel".into()],
Self::RetryCancel => vec!["retry".into(), "cancel".into()],
Self::Custom => custom_buttons.unwrap_or(&[]).to_vec(),
}
}
pub fn button_count(self, custom_buttons: Option<&[String]>) -> usize {
self.wire_labels(custom_buttons).len()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MessageLevel {
Info,
Warning,
Error,
Question,
}
impl MessageLevel {
pub fn parse(value: &str) -> Option<Self> {
match value {
"info" => Some(Self::Info),
"warning" => Some(Self::Warning),
"error" => Some(Self::Error),
"question" => Some(Self::Question),
_ => None,
}
}
pub fn all_names() -> &'static [&'static str] {
&["info", "warning", "error", "question"]
}
pub fn as_str(self) -> &'static str {
match self {
Self::Info => "info",
Self::Warning => "warning",
Self::Error => "error",
Self::Question => "question",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum InputMode {
Text,
File,
Folder,
}
impl InputMode {
pub fn parse(value: &str) -> Option<Self> {
match value {
"text" => Some(Self::Text),
"file" => Some(Self::File),
"folder" => Some(Self::Folder),
_ => None,
}
}
pub fn all_names() -> &'static [&'static str] {
&["text", "file", "folder"]
}
pub fn as_str(self) -> &'static str {
match self {
Self::Text => "text",
Self::File => "file",
Self::Folder => "folder",
}
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct WindowSizeHint {
pub width: Option<u32>,
pub height: Option<u32>,
}
impl WindowSizeHint {
pub fn is_some(&self) -> bool {
self.width.is_some() || self.height.is_some()
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Command {
Chrome {
title: ChromeTitle,
status: Option<ChromeStatus>,
width: Option<u32>,
height: Option<u32>,
},
Message {
title: ChromeTitle,
message: String,
status: Option<ChromeStatus>,
buttons: ButtonsPreset,
custom_buttons: Option<Vec<String>>,
default_button: Option<u32>,
level: Option<MessageLevel>,
icon: Option<crate::MediaRef>,
image: Option<crate::MediaRef>,
markdown: bool,
width: Option<u32>,
height: Option<u32>,
},
Input {
title: ChromeTitle,
message: String,
status: Option<ChromeStatus>,
icon: Option<crate::MediaRef>,
markdown: bool,
multiline: bool,
placeholder: Option<String>,
default: Option<String>,
password: bool,
mode: InputMode,
filter: Option<Vec<String>>,
multiple: bool,
start_path: Option<String>,
buttons: ButtonsPreset,
width: Option<u32>,
height: Option<u32>,
},
Markdown {
title: Option<ChromeTitle>,
file: Option<String>,
content: Option<String>,
status: Option<ChromeStatus>,
buttons: ButtonsPreset,
width: Option<u32>,
height: Option<u32>,
},
Question {
questions: Vec<QuestionCard>,
questions_raw: Vec<serde_json::Value>,
width: Option<u32>,
height: Option<u32>,
},
Wizard(WizardCommand),
Report(ReportCommand),
}
impl Command {
pub fn window_width(&self) -> Option<u32> {
match self {
Self::Chrome { width, .. }
| Self::Message { width, .. }
| Self::Input { width, .. }
| Self::Markdown { width, .. }
| Self::Question { width, .. } => *width,
Self::Wizard(cmd) => cmd.width,
Self::Report(cmd) => cmd.width,
}
}
pub fn window_height(&self) -> Option<u32> {
match self {
Self::Chrome { height, .. }
| Self::Message { height, .. }
| Self::Input { height, .. }
| Self::Markdown { height, .. }
| Self::Question { height, .. } => *height,
Self::Wizard(cmd) => cmd.height,
Self::Report(cmd) => cmd.height,
}
}
pub fn window_size_hint(&self) -> WindowSizeHint {
WindowSizeHint {
width: self.window_width(),
height: self.window_height(),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct QuestionOption {
pub label: String,
pub description: String,
pub preview: Option<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum QuestionPromptError {
Empty,
}
impl std::fmt::Display for QuestionPromptError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Empty => f.write_str("question prompt must be a non-empty string"),
}
}
}
impl std::error::Error for QuestionPromptError {}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct QuestionPrompt(String);
impl QuestionPrompt {
pub fn new(value: impl Into<String>) -> Self {
Self(value.into())
}
pub fn try_new(value: impl Into<String>) -> Result<Self, QuestionPromptError> {
let value = value.into();
if value.is_empty() {
return Err(QuestionPromptError::Empty);
}
Ok(Self(value))
}
pub fn as_str(&self) -> &str {
&self.0
}
pub fn into_inner(self) -> String {
self.0
}
}
impl std::ops::Deref for QuestionPrompt {
type Target = str;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl AsRef<str> for QuestionPrompt {
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl std::fmt::Display for QuestionPrompt {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.0.fmt(f)
}
}
impl From<String> for QuestionPrompt {
fn from(value: String) -> Self {
Self::new(value)
}
}
impl From<&str> for QuestionPrompt {
fn from(value: &str) -> Self {
Self::new(value)
}
}
impl PartialEq<str> for QuestionPrompt {
fn eq(&self, other: &str) -> bool {
self.0 == other
}
}
impl PartialEq<&str> for QuestionPrompt {
fn eq(&self, other: &&str) -> bool {
self.0 == *other
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct QuestionCard {
pub question: QuestionPrompt,
pub header: String,
pub options: Vec<QuestionOption>,
pub multi_select: bool,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn preset_label_mapping_table() {
assert_eq!(ButtonsPreset::Ok.display_labels(None), ["OK"]);
assert_eq!(ButtonsPreset::Ok.wire_labels(None), ["ok"]);
assert_eq!(
ButtonsPreset::OkCancel.display_labels(None),
["OK", "Cancel"]
);
assert_eq!(ButtonsPreset::OkCancel.wire_labels(None), ["ok", "cancel"]);
assert_eq!(ButtonsPreset::YesNo.display_labels(None), ["Yes", "No"]);
assert_eq!(ButtonsPreset::YesNo.wire_labels(None), ["yes", "no"]);
assert_eq!(
ButtonsPreset::YesNoCancel.display_labels(None),
["Yes", "No", "Cancel"]
);
assert_eq!(
ButtonsPreset::YesNoCancel.wire_labels(None),
["yes", "no", "cancel"]
);
assert_eq!(
ButtonsPreset::RetryCancel.display_labels(None),
["Retry", "Cancel"]
);
assert_eq!(
ButtonsPreset::RetryCancel.wire_labels(None),
["retry", "cancel"]
);
}
#[test]
fn custom_labels_are_verbatim() {
let custom = vec!["Save".into(), "Discard".into()];
assert_eq!(ButtonsPreset::Custom.display_labels(Some(&custom)), custom);
assert_eq!(ButtonsPreset::Custom.wire_labels(Some(&custom)), custom);
}
#[test]
fn question_prompt_try_new_rejects_empty() {
assert_eq!(QuestionPrompt::try_new(""), Err(QuestionPromptError::Empty));
assert_eq!(QuestionPrompt::try_new("Q?").unwrap().as_str(), "Q?");
}
}