use super::*;
use chrono::prelude::{DateTime, Utc};
#[derive(Serialize, Deserialize, Debug, Clone, Default, Eq, Hash)]
pub struct AssetCategory {
#[serde(skip)]
semantic: Semantic,
#[serde(rename = "main_type", skip_serializing_if = "Option::is_none")]
semantic_str: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
criteria: Option<Vec<ObjectId>>,
}
impl AssetCategory {
pub fn from_semantic(sem: &Semantic) -> Self {
Self {
semantic_str: Some(sem.to_string()),
semantic: sem.to_owned(),
..Self::empty()
}
}
pub fn empty() -> Self {
Self {
semantic: Semantic::Other(None),
semantic_str: None,
criteria: None,
}
}
pub fn semantic(&self) -> &Semantic {
&self.semantic
}
pub fn is_semantic_empty(&self) -> bool {
self.semantic_str.is_none()
}
pub fn semantic_mut_from_str(&mut self) {
self.semantic = match &self.semantic_str {
None => Semantic::Other(None),
Some(sem) => sem.as_str().into(),
};
}
pub fn criteria(&self) -> Option<&Vec<ObjectId>> {
self.criteria.as_ref()
}
pub fn with_criteria(mut self, criteria: &[&ObjectId]) -> Self {
self.criteria = Some(criteria.to_vec().into_iter().map(|id| id.clone()).collect());
self
}
pub fn is_criteria_empty(&self) -> bool {
self.criteria.is_none()
}
pub fn take_criteria(mut self) -> Self {
self.criteria.take();
self
}
}
impl PartialEq for AssetCategory {
fn eq(&self, other: &Self) -> bool {
self.semantic == other.semantic
}
}
impl Ord for AssetCategory {
fn cmp(&self, other: &Self) -> Ordering {
self.semantic.cmp(&other.semantic)
}
}
impl PartialOrd for AssetCategory {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
#[derive(
Deserialize_repr,
Serialize_repr,
Debug,
Clone,
Default,
Hash,
PartialEq,
Eq,
PartialOrd,
Ord,
strum::AsRefStr,
strum::EnumIter,
)]
#[repr(u8)]
pub enum CriterionType {
#[default]
Batch = 0,
}
#[cfg(feature = "gui")]
fn show_criterion_type_options(ui: &mut egui::Ui, typ: &mut CriterionType) {
ui.horizontal(|ui| {
ui.label("Type:");
for t in CriterionType::iter() {
if ui
.add(egui::SelectableLabel::new(*typ == t, t.as_ref()))
.clicked()
{
*typ = t;
}
}
});
}
#[derive(Debug, Clone, Hash, Eq, Ord)]
#[cfg_attr(feature = "mongo", derive(Serialize, Deserialize))]
pub struct Criterion {
#[cfg_attr(feature = "mongo", serde(skip))]
mode: MediaMode,
#[cfg_attr(
feature = "mongo",
serde(rename = "_id", skip_serializing_if = "Option::is_none")
)]
id: Option<ObjectId>,
name: String,
typ: CriterionType,
active: bool,
#[cfg_attr(feature = "mongo", serde(skip))]
forbid_active_mut: bool,
#[cfg_attr(
feature = "mongo",
serde(with = "bson::serde_helpers::chrono_datetime_as_bson_datetime")
)]
created_at: DateTime<Utc>,
#[cfg_attr(
feature = "mongo",
serde(with = "bson::serde_helpers::chrono_datetime_as_bson_datetime")
)]
updated_at: DateTime<Utc>,
comment: String,
}
impl Criterion {
pub fn empty() -> Self {
Self {
mode: MediaMode::default(),
id: None,
name: String::new(),
typ: CriterionType::default(),
active: true,
forbid_active_mut: false,
created_at: Utc::now(),
updated_at: Utc::now(),
comment: String::new(),
}
}
pub fn new(name: &str) -> Self {
Self {
name: name.to_owned(),
..Self::empty()
}
}
pub fn owned_name(self) -> String {
self.name
}
pub fn name(&self) -> &String {
&self.name
}
pub fn comment(mut self, cmt: &str) -> Self {
self.comment = cmt.to_owned();
self
}
pub fn created_now(&mut self) {
self.created_at = Utc::now();
}
pub fn updated_now(&mut self) {
self.updated_at = Utc::now();
}
#[cfg(feature = "gui")]
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 BsonId for Criterion {
fn bson_id_as_ref(&self) -> Option<&ObjectId> {
self.id.as_ref()
}
fn bson_id(&self) -> AnyResult<&ObjectId> {
self.id.as_ref().context("Criterion without BSON ObjectId")
}
}
impl PartialEq for Criterion {
fn eq(&self, other: &Self) -> bool {
self.id == other.id
}
}
impl PartialOrd for Criterion {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
self.id.partial_cmp(&other.id)
}
}
impl ReadWriteSuggest for Criterion {
fn write_suggest() -> Self {
let mut crit = Self::empty().with_mode(MediaMode::WriteSuggest);
crit.forbid_active_mut = true;
crit
}
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.vertical(|ui| {
match self.active {
true => {
ui.heading(&self.name);
ui.label(&self.comment);
}
false => {
ui.label(
RichText::new(format!("[Deactivated] {}", &self.name))
.heading()
.weak(),
);
ui.weak(&self.comment);
}
};
});
});
}
#[cfg(feature = "gui")]
fn write_suggest_ui(&mut self, ui: &mut egui::Ui) {
ui.vertical_centered(|ui| {
if ui
.button(RichText::new("➕ New").heading())
.on_hover_text("Draft a new category")
.clicked()
{
self.mode = MediaMode::WriteCompose;
};
});
}
#[cfg(feature = "gui")]
fn write_compose_ui(&mut self, ui: &mut egui::Ui) {
ui.group(|ui| {
ui.vertical(|ui| {
show_criterion_type_options(ui, &mut self.typ);
ui.horizontal(|ui| {
ui.monospace("Name: ");
ui.add(
egui::TextEdit::singleline(&mut self.name)
.hint_text("Name of the new category, e.g. Batch 3")
.desired_width(300.),
);
});
ui.horizontal(|ui| {
ui.monospace("Comment:");
ui.add(egui::TextEdit::singleline(&mut self.comment).desired_width(300.));
});
});
});
}
}
impl BsonId for &Criterion {
fn bson_id_as_ref(&self) -> Option<&ObjectId> {
self.id.as_ref()
}
fn bson_id(&self) -> AnyResult<&ObjectId> {
self.id.as_ref().context("Criterion without BSON ObjectId")
}
}
#[derive(Debug, Clone, Default)]
pub struct CriterionOptions {
pub dbi: Option<Box<dyn MakeCriteria>>,
pub active: Vec<Criterion>,
pub select_state: BTreeSet<Criterion>,
}
impl CriterionOptions {
#[cfg(feature = "gui")]
pub fn select_criteria_ui(&mut self, ui: &mut egui::Ui) {
for c in self.active.iter() {
let mut selected = self.select_state.contains(c);
ui.checkbox(&mut selected, c.name())
.on_hover_text(&c.comment);
toggle_criterion(&mut self.select_state, c, selected);
}
}
pub fn selected_bson_ids(&self) -> Vec<&ObjectId> {
self.select_state
.iter()
.filter_map(|c| c.id.as_ref())
.collect()
}
}
#[derive(
Deserialize,
Serialize,
Debug,
Clone,
PartialEq,
Eq,
Hash,
PartialOrd,
Ord,
strum::AsRefStr,
strum::EnumIter,
)]
pub enum Semantic {
#[strum(serialize = "char")]
Character,
#[strum(serialize = "prop")]
Prop,
#[strum(serialize = "vehicles")]
Vehicle,
#[strum(serialize = "set")]
Set,
#[strum(serialize = "setdress")]
SetDress,
#[strum(serialize = "fx")]
Fx,
#[strum(serialize = "env")]
Env,
#[strum(serialize = "cam")]
Camera,
#[strum(serialize = "scinsert")]
ScInsert,
#[strum(serialize = "location")]
Location,
#[strum(serialize = "sector")]
Sector,
#[strum(serialize = "dmp")]
Dmp,
Other(Option<String>),
}
impl Default for Semantic {
fn default() -> Self {
Self::Other(None)
}
}
impl Semantic {
pub fn to_string(&self) -> String {
let repr = match self {
Self::Other(Some(typ)) => typ,
_ => self.as_ref(),
};
repr.to_owned()
}
}
#[cfg(feature = "gui")]
pub fn semantic_options_ui(ui: &mut egui::Ui, sem: &mut Semantic) {
egui::ComboBox::from_label("Group")
.selected_text(sem.as_ref())
.show_ui(ui, |ui| {
for s in Semantic::iter() {
if let Semantic::Other(_) = s {
continue;
} else {
if ui.selectable_label(sem == &s, s.as_ref()).clicked() {
*sem = s;
};
}
}
});
}
impl From<&str> for Semantic {
fn from(typ: &str) -> Self {
match typ {
"char" => Self::Character,
"prop" => Self::Prop,
"vehicles" => Self::Vehicle,
"set" => Self::Set,
"setdress" => Self::SetDress,
"fx" => Self::Fx,
"env" => Self::Env,
"cam" => Self::Camera,
"scinsert" => Self::ScInsert,
"location" => Self::Location,
"sector" => Self::Sector,
"dmp" => Self::Dmp,
_ => Self::Other(Some(typ.to_owned())),
}
}
}
#[async_trait]
pub trait MakeCriteria: DynClone + fmt::Debug + Send + Sync {
async fn criteria(&self, project: &Project) -> Result<Vec<Criterion>, DatabaseError>;
async fn add_criterion(
&self,
project: &Project,
crit: &Criterion,
) -> Result<(), ModificationError>;
async fn delete_criterion(
&self,
project: &Project,
crit: &Criterion,
) -> Result<(), ModificationError>;
async fn update_criterion(
&self,
project: &Project,
existing: Option<&Criterion>,
updated: &Criterion,
) -> Result<(), ModificationError>;
async fn assign_criterion(
&self,
project: &Project,
assets: &[ProductionAsset],
crit: &Criterion,
) -> Result<(), ModificationError>;
async fn unassign_criterion(
&self,
project: &Project,
assets: &[ProductionAsset],
crit: &Criterion,
) -> Result<(), ModificationError>;
async fn assigned_assets(
&self,
project: &Project,
crit: &Criterion,
) -> Result<Vec<ProductionAsset>, DatabaseError>;
async fn obliterate_assignments(&self, project: &Project) -> Result<(), ModificationError>;
}
dyn_clone::clone_trait_object!(MakeCriteria);
#[cfg(feature = "gui")]
fn toggle_criterion(select_state: &mut BTreeSet<Criterion>, crit: &Criterion, selected: bool) {
if selected {
if !select_state.contains(crit) {
select_state.insert(crit.to_owned());
}
} else {
select_state.remove(crit);
}
}
#[cfg(test)]
mod tests {}