mod deli;
mod graph;
mod template;
mod workflow;
pub use graph::*;
pub use template::*;
pub use workflow::*;
use super::*;
#[cfg(all(feature = "easy_mark", feature = "gui"))]
use egui_demo_lib::easy_mark;
use mkutil::{
dag::{self, NamedNode},
text,
};
#[async_trait]
pub trait WorkflowDefinition: DeliveryStages + ProjectDefaultDelivery {}
dyn_clone::clone_trait_object!(WorkflowDefinition);
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct Stage {
#[serde(skip)]
mode: MediaMode,
#[serde(skip)]
pub index: Option<StageIndex>,
#[serde(skip)]
pub has_reference_to: bool,
pub name: String,
pub description: String,
is_internal: bool,
#[cfg(feature = "gui")]
pub color: Color32,
depends_on: Vec<String>,
#[serde(skip)]
depends_on_draft: Option<String>,
}
impl Stage {
fn empty() -> Self {
Self {
#[cfg(feature = "gui")]
color: Color32::GRAY,
..Default::default()
}
}
fn draft() -> Self {
let mut stage = Self {
mode: MediaMode::WriteCompose,
..Self::empty()
};
stage.init_empty_draft();
stage
}
fn init_empty_draft(&mut self) {
if self.depends_on_draft.is_none() {
self.depends_on_draft = Some(String::new());
};
}
fn init_draft_from_existing(&mut self) {
if self.depends_on_draft.is_none() {
self.depends_on_draft = Some(self.depends_on.join(", "));
};
}
fn parse_dependency_draft(&mut self) {
if let Some(draft) = &self.depends_on_draft {
self.depends_on = text::relaxed_from_comma_sep_singleline(draft, 50);
};
}
pub fn no_dependency(name: &str, description: &str) -> Self {
Self {
name: name.to_owned(),
description: description.to_owned(),
..Self::empty()
}
}
pub fn depends_on(mut self, parent: &str) -> Self {
self.depends_on.push(parent.to_owned());
self
}
}
#[cfg(feature = "gui")]
impl Stage {
fn name_and_hint(&self, ui: &mut egui::Ui) {
if self.is_internal {
ui.heading(format!("{} 🏠", self.name))
.on_hover_text(RichText::new("Internal").color(Color32::LIGHT_GREEN))
} else {
ui.heading(format!("{} 🌏", self.name))
.on_hover_text(RichText::new("Client-facing").color(Color32::LIGHT_RED))
};
}
fn depends_on_ui(&self, ui: &mut egui::Ui) {
if self.depends_on.is_empty() {
ui.weak("Depends on nothing");
} else {
ui.strong(format!("Depends on: {}", self.depends_on.join(", ")));
};
}
pub fn color_ui(&self, ui: &mut egui::Ui) {
ui.colored_label(self.color, "⬛");
}
pub fn ui(&mut self, ui: &mut egui::Ui) {
match &self.mode {
MediaMode::Read => self.read_mode_ui(ui),
MediaMode::WriteSuggest => {
self.write_suggest_ui(ui);
}
MediaMode::WriteCompose => {
self.write_compose_ui(ui);
}
MediaMode::WriteEdit => {
self.write_edit_ui(ui);
}
}
}
}
impl NamedNode for Stage {
fn name(&self) -> &String {
&self.name
}
}
impl ReadWriteSuggest for Stage {
fn write_suggest() -> Self {
Self {
mode: MediaMode::WriteSuggest,
depends_on_draft: Some(String::new()),
..Self::empty()
}
}
fn with_mode(mut self, mode: MediaMode) -> Self {
self.mode_mut(mode);
self
}
fn mode(&self) -> &MediaMode {
&self.mode
}
fn mode_mut(&mut self, mode: MediaMode) {
self.mode = mode;
}
#[cfg(feature = "gui")]
fn read_mode_ui(&mut self, ui: &mut egui::Ui) {
ui.group(|ui| {
ui.horizontal(|ui| {
#[cfg(feature = "persistence")]
self.color_ui(ui);
self.name_and_hint(ui);
});
#[cfg(feature = "easy_mark")]
easy_mark::easy_mark(ui, &self.description);
#[cfg(not(feature = "easy_mark"))]
ui.label(&self.description);
self.depends_on_ui(ui);
});
}
#[cfg(feature = "gui")]
fn write_compose_ui(&mut self, ui: &mut egui::Ui) {
ui.horizontal(|ui| {
ui.label("Tag:");
ui.color_edit_button_srgba(&mut self.color);
});
let text = if self.is_internal {
RichText::new("Internal").color(Color32::LIGHT_GREEN)
} else {
RichText::new("Client-facing").color(Color32::LIGHT_RED)
};
ui.checkbox(&mut self.is_internal, text)
.on_hover_text("Stages marked as NOT Internal are used for Outgoing Delivery");
ui.horizontal(|ui| {
ui.label("Stage name:");
ui.add(
egui::TextEdit::singleline(&mut self.name)
.hint_text("Special characters are allowed"),
);
});
ui.horizontal(|ui| {
#[cfg(feature = "easy_mark")]
{
ui.label("Description:")
.on_hover_text(crate::locale::EASYMARK_SYNTAX_HELP);
ui.add(
egui::TextEdit::multiline(&mut self.description)
.desired_rows(2)
.hint_text("EasyMark syntax is supported"),
);
}
#[cfg(not(feature = "easy_mark"))]
{
ui.label("Description:");
ui.add(egui::TextEdit::multiline(&mut self.description).desired_rows(2));
}
});
ui.horizontal(|ui| {
ui.label("Depends on:")
.on_hover_text("Make sure the dependencies' names match");
ui.add(
egui::TextEdit::singleline(self.depends_on_draft.as_mut().unwrap())
.hint_text("Separated with commas"),
);
});
}
}
#[derive(Debug, Clone, failure::Fail)]
pub enum GraphError {
#[fail(display = "Validity of dependency graph is unchecked")]
NotChecked,
#[fail(display = "Stage is None")]
StageIsNone,
#[fail(display = "Missing root node in dependency graph")]
NonExistentRoot,
#[fail(display = "Stage \"{}\" is defined multiple times", _0)]
StageNameDuplication(String),
#[fail(
display = "Dependency \"{}\" of stage \"{}\" must be defined first",
_0, _1
)]
UndefinedDependency(String, String),
#[fail(display = "Failed to add edge between {} and {}: {}", _0, _1, _2)]
CycleEdge(String, String, String),
}
impl Into<anyhow::Error> for GraphError {
fn into(self) -> anyhow::Error {
anyhow!("{}", self)
}
}
impl Into<anyhow::Error> for &GraphError {
fn into(self) -> anyhow::Error {
anyhow!("{}", self)
}
}