hgame 0.26.4

CG production management structs, e.g. of assets, personnels, progress, etc.
Documentation
use super::*;

#[async_trait]
/// CRUD with a project's default workflows for different purposes,
/// e.g., internal or outgoing.
pub trait ProjectDefaultDelivery: DynClone + fmt::Debug + Send + Sync {
    /// Lists all default [`DeliveryTemplate`]s as members of the project's
    /// [`ProductionDefaultWorkflow`].
    async fn default_workflow(
        &self,
        project: &Project,
    ) -> Result<ProductionDefaultWorkflow, DatabaseError>;

    /// Sets the given [`ProductionDefault`] as default workflow for the project.
    async fn make_default(
        &self,
        project: &Project,
        default: ProductionDefault,
    ) -> Result<(), ModificationError>;

    /// Removes all references to the [`DeliveryTemplate`] in the project's default workflows.
    async fn unlink_default(
        &self,
        project: &Project,
        template: DeliveryTemplate,
    ) -> Result<(), ModificationError>;
}

dyn_clone::clone_trait_object!(ProjectDefaultDelivery);

// -------------------------------------------------------------------------------
#[async_trait]
/// CRUD with an asset's `Stage`s with reference to the project's
/// default workflows of different purposes, via `Stage` indices.
pub trait AssetStage: DynClone + fmt::Debug + Send + Sync {
    /// Gets `Stage` indices of the given asset.
    async fn stage_indices(
        &mut self,
        project: &Project,
        asset: &ProductionAsset,
        // project_definition: &ProductionDefaultWorkflow,
    ) -> Result<StageRef, DatabaseError>;

    async fn set_stage(
        &mut self,
        project: &Project,
        asset: &ProductionAsset,
        index: StageIndex,
    ) -> Result<(), ModificationError>;

    async fn unset_stage(
        &mut self,
        project: &Project,
        asset: &ProductionAsset,
        index: StageIndex,
    ) -> Result<(), ModificationError>;
}

dyn_clone::clone_trait_object!(AssetStage);

// -------------------------------------------------------------------------------
#[derive(Debug, Clone, Default)]
#[cfg_attr(feature = "mongo", derive(Serialize, Deserialize))]
pub struct ProductionDefaultWorkflow {
    #[cfg_attr(
        feature = "mongo",
        serde(rename = "_id", skip_serializing_if = "Option::is_none")
    )]
    id: Option<ObjectId>,

    /// This is used to get/set a singleton document.
    default_workflow: bool,

    #[cfg_attr(feature = "mongo", serde(rename = "internal"))]
    /// Corresponds with [`ProductionDefault::Internal(_)`].
    pub internal_oid: Option<ObjectId>,

    #[cfg_attr(feature = "mongo", serde(skip))]
    /// Constructed template for Internal
    pub internal: Option<Result<DeliveryTemplate, DatabaseError>>,

    #[cfg_attr(feature = "mongo", serde(rename = "outgoing"))]
    /// Corresponds with [`ProductionDefault::Outgoing(_)`].
    pub outoing_oid: Option<ObjectId>,

    #[cfg_attr(feature = "mongo", serde(skip))]
    /// Constructed template for Outgoing
    pub outgoing: Option<Result<DeliveryTemplate, DatabaseError>>,
}

impl ProductionDefaultWorkflow {
    pub fn empty() -> Self {
        Self {
            default_workflow: true,
            ..Default::default()
        }
    }

    pub fn match_stage_ref(&mut self, stage: &StageRef) {
        if let Some(indices) = &stage.internal_indices {
            self.match_internal_stage_ref(indices);
        };
        if let Some(indices) = &stage.outgoing_indices {
            self.match_outgoing_stage_ref(indices);
        };
    }

    /// Resets all `Stage::has_reference_to` values in `Self::internal` and `Self::outgoing`.
    pub fn unmatch_stage_ref(&mut self) {
        if let Some(Ok(workflow)) = self.internal.as_mut() {
            workflow.reset_reference();
        };
        if let Some(Ok(workflow)) = self.outgoing.as_mut() {
            workflow.reset_reference();
        };
    }

    /// Changes `Stage::has_reference_to` boolean value in all `Self::template::stage_order`
    /// by checking against the indices.
    fn match_internal_stage_ref(&mut self, indices: &[StageIndex]) {
        if let Some(Ok(workflow)) = self.internal.as_mut() {
            match_stage_ref(&mut workflow.template.stage_order, indices);
        };
    }

    /// Changes `Stage::has_reference_to` boolean value in all `Self::template::stage_order`
    /// by checking against the indices.
    fn match_outgoing_stage_ref(&mut self, indices: &[StageIndex]) {
        if let Some(Ok(workflow)) = self.outgoing.as_mut() {
            match_stage_ref(&mut workflow.template.stage_order, indices);
        };
    }
}

fn match_stage_ref(stage_order: &mut Vec<Stage>, indices: &[StageIndex]) {
    stage_order.iter_mut().for_each(|stage| {
        if let Some(idx) = &stage.index {
            stage.has_reference_to = indices.contains(idx);
        };
    });
}

impl BsonId for ProductionDefaultWorkflow {
    fn bson_id_as_ref(&self) -> Option<&ObjectId> {
        self.id.as_ref()
    }

    fn bson_id(&self) -> AnyResult<&ObjectId> {
        self.id
            .as_ref()
            .context("ProductionDefaultWorkflow without BSON ObjectId")
    }
}

// -------------------------------------------------------------------------------
#[derive(Debug, Clone, strum::AsRefStr)]
/// Default [`DeliveryTemplate`] -- workflow definition -- for each purpose.
pub enum ProductionDefault {
    #[strum(serialize = "🎨 Internal Production")]
    /// E.g., for artists within the studio.
    Internal(DeliveryTemplate),

    #[strum(serialize = "🚚 Outgoing Delivery")]
    /// E.g., for clients.
    Outgoing(DeliveryTemplate),
}

impl BsonId for ProductionDefault {
    fn bson_id_as_ref(&self) -> Option<&ObjectId> {
        match self {
            Self::Internal(t) | Self::Outgoing(t) => t.bson_id_as_ref(),
        }
    }

    fn bson_id(&self) -> AnyResult<&ObjectId> {
        match self {
            Self::Internal(t) | Self::Outgoing(t) => t.bson_id(),
        }
    }
}

impl ProductionDefault {
    pub fn internal_as_str() -> &'static str {
        "🎨 Internal Production"
    }

    pub fn outgoing_as_str() -> &'static str {
        "🚚 Outgoing Delivery"
    }
}

// -------------------------------------------------------------------------------
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
#[cfg_attr(feature = "mongo", derive(Serialize, Deserialize))]

pub enum StageIndex {
    /// Index of a [`Stage`] with no known purpose.
    Generic(u32),

    /// Index of a [`Stage`] in a project's default Internal workflow.
    Internal(u32),

    /// Index of a [`Stage`] in a project's default Outgoing workflow.
    Outgoing(u32),
}

impl StageIndex {
    pub fn field_key(&self) -> &str {
        match &self {
            Self::Generic(_) => "stage.generic",
            Self::Internal(_) => "stage.internal",
            Self::Outgoing(_) => "stage.outgoing",
        }
    }

    pub fn value(&self) -> u32 {
        match &self {
            Self::Generic(inner) | Self::Internal(inner) | Self::Outgoing(inner) => *inner,
        }
    }
}

// -------------------------------------------------------------------------------
#[derive(Clone, Default)]
#[cfg_attr(feature = "mongo", derive(Serialize, Deserialize))]
pub struct StageRef {
    #[cfg_attr(feature = "mongo", serde(rename = "internal"))]
    /// Indices of [`Stage`]s of a project's default Internal workflow.
    internal_indices: Option<Vec<StageIndex>>,

    #[cfg(feature = "gui")]
    #[cfg_attr(feature = "mongo", serde(skip))]
    /// The `Err` variant refers to that of the project's default workflow.
    internal: Option<Result<Vec<RichText>, DatabaseError>>,

    #[cfg_attr(feature = "mongo", serde(rename = "outgoing"))]
    /// Indices of [`Stage`]s of a project's default Outgoing workflow.
    outgoing_indices: Option<Vec<StageIndex>>,

    #[cfg(feature = "gui")]
    #[cfg_attr(feature = "mongo", serde(skip))]
    /// The `Err` variant refers to that of the project's default workflow.
    outgoing: Option<Result<Vec<RichText>, DatabaseError>>,
}

impl fmt::Debug for StageRef {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_struct("StageRef")
            .field("internal_indices", &self.internal_indices)
            // .field("internal", &self.internal)
            .field("outgoing_indices", &self.outgoing_indices)
            // .field("outgoing", &self.outgoing)
            .finish()
    }
}

impl StageRef {
    pub fn contains_index(&self, idx: &StageIndex) -> bool {
        match idx {
            StageIndex::Generic(_) => true,
            StageIndex::Internal(_) => match &self.internal_indices {
                Some(indices) => indices.contains(idx),
                None => false,
            },
            StageIndex::Outgoing(_) => match &self.outgoing_indices {
                Some(indices) => indices.contains(idx),
                None => false,
            },
        }
    }
}

#[cfg(feature = "gui")]
impl StageRef {
    fn internal_mut(&mut self, template: Option<&Result<DeliveryTemplate, DatabaseError>>) {
        // skips if the project's Internal definition cannot be found
        if let Some(template) = template {
            epxand_indices(self.internal_indices.as_ref(), template, &mut self.internal);
        };
    }

    fn outgoing_mut(&mut self, template: Option<&Result<DeliveryTemplate, DatabaseError>>) {
        // skips if the project's Outgoing definition cannot be found
        if let Some(template) = template {
            epxand_indices(self.outgoing_indices.as_ref(), template, &mut self.outgoing);
        };
    }

    pub fn ui(&self, ui: &mut egui::Ui) {
        ui.group(|ui| {
            stages_name_ui(self.internal.as_ref(), ui);
            stages_name_ui(self.outgoing.as_ref(), ui);
        });
    }
}

// -------------------------------------------------------------------------------
#[cfg(feature = "gui")]
#[derive(Debug)]
pub struct StageRefReadBuilder(StageRef);

#[cfg(feature = "gui")]
impl StageRefReadBuilder {
    pub fn new(stage: StageRef) -> Self {
        Self(stage)
    }

    /// Gets each `Stage`'s name from its index.
    pub fn epxand_indices(mut self, project_definition: &ProductionDefaultWorkflow) -> Self {
        self.0.internal_mut(project_definition.internal.as_ref());
        self.0.outgoing_mut(project_definition.outgoing.as_ref());
        self
    }

    pub fn finish(self) -> StageRef {
        self.0
    }
}

// -------------------------------------------------------------------------------
#[cfg(feature = "gui")]
fn epxand_indices(
    indices: Option<&Vec<StageIndex>>,
    template: &Result<DeliveryTemplate, DatabaseError>,
    expanded: &mut Option<Result<Vec<RichText>, DatabaseError>>,
) {
    // skips if an asset hasn't stored any references to any `Stage`s
    if let Some(indices) = indices {
        match template {
            Ok(template) => {
                // iterates over indices and constructing a `Vec` from start to finish
                let mut expanding = vec![];

                indices.iter().for_each(|i| {
                    if let Some(Some(stage)) = template.stages.get(i.value() as usize) {
                        expanding.push(RichText::new(&stage.name).color(stage.color));
                    };
                });

                *expanded = Some(Ok(expanding));
            }
            Err(e) => {
                // clones the error for ease of debugging
                *expanded = Some(Err(anyhow!(
                    "Project's DefaultWorkflow Definition Error: {}",
                    e
                )
                .into()));
            }
        }
    };
}

#[cfg(feature = "gui")]
fn stages_name_ui(stages: Option<&Result<Vec<RichText>, DatabaseError>>, ui: &mut egui::Ui) {
    match stages {
        Some(Ok(stages)) => {
            ui.horizontal(|ui| {
                stages.iter().for_each(|s| {
                    // TODO: avoid cloning
                    ui.label(s.clone());
                });
            });
        }
        Some(Err(e)) => {
            ui.colored_label(Color32::RED, "🚫 Stages")
                .on_hover_text(e.to_string());
        }
        None => {
            // not display anything
            // ui.label("No Stages");
        }
    }
}