use std::sync::Arc;
use myko::{
command::{CommandContext, CommandError, CommandHandler},
myko_command, myko_item, myko_query,
prelude::*,
};
use crate::{Project, ProjectId};
#[myko_item]
pub struct Session {
#[searchable]
#[default_value("Main Session")]
#[myko_rename("SetSessionName")]
pub name: String,
#[default_value("#0096c7")]
#[myko_setter]
pub color: String,
#[belongs_to(Project)]
#[ensure_for(Project)]
pub scope_id: ProjectId,
#[serde(default)]
pub calendar_id: Option<String>,
#[serde(default)]
pub overview_scene_id: Option<String>,
}
#[myko_query(Session)]
pub struct GetSessionsByScopeId {
pub scope_id: ProjectId,
}
impl QueryHandler for GetSessionsByScopeId {
fn test_entity(ctx: QueryTestCtx<Self>) -> bool {
ctx.item.scope_id.as_ref() == ctx.query.scope_id.as_ref()
}
}
#[myko_command]
pub struct SetCalendar {
pub id: SessionId,
#[serde(default)]
pub calendar_id: Option<Arc<str>>,
}
impl CommandHandler for SetCalendar {
fn execute(self, ctx: CommandContext) -> Result<(), CommandError> {
let session = ctx
.exec_query_first(GetSessionsByIds {
ids: vec![self.id.clone()],
})?
.ok_or_else(|| CommandError {
tx: ctx.tx().to_string(),
command_id: ctx.command_id.to_string(),
message: format!("Session {} not found", self.id),
})?;
let updated = Session {
calendar_id: self.calendar_id.map(|s| s.to_string()),
..session.as_ref().clone()
};
ctx.emit_set(&updated)?;
Ok(())
}
}
#[myko_command(Arc<str>)]
pub struct CreateSession {
pub name: String,
pub scope_id: ProjectId,
#[serde(default)]
pub color: Option<String>,
}
impl CommandHandler for CreateSession {
fn execute(self, ctx: CommandContext) -> Result<Arc<str>, CommandError> {
let id: Arc<str> = Uuid::new_v4().to_string().into();
let random_bright_color = bright_color_from_id(id.as_ref());
let session = Session {
id: SessionId(id.clone()),
name: self.name,
color: self.color.unwrap_or(random_bright_color),
scope_id: self.scope_id,
calendar_id: None,
overview_scene_id: None,
};
ctx.emit_set(&session)?;
Ok(id)
}
}
fn bright_color_from_id(seed: &str) -> String {
let mut hash: u32 = 0x811C9DC5;
for b in seed.bytes() {
hash ^= u32::from(b);
hash = hash.wrapping_mul(0x01000193);
}
let hue = (hash % 360) as f32;
let saturation = 0.82_f32;
let value = 0.95_f32;
let (r, g, b) = hsv_to_rgb(hue, saturation, value);
format!("#{r:02X}{g:02X}{b:02X}")
}
fn hsv_to_rgb(h: f32, s: f32, v: f32) -> (u8, u8, u8) {
let c = v * s;
let h_prime = (h / 60.0) % 6.0;
let x = c * (1.0 - ((h_prime % 2.0) - 1.0).abs());
let (r1, g1, b1) = if h_prime < 1.0 {
(c, x, 0.0)
} else if h_prime < 2.0 {
(x, c, 0.0)
} else if h_prime < 3.0 {
(0.0, c, x)
} else if h_prime < 4.0 {
(0.0, x, c)
} else if h_prime < 5.0 {
(x, 0.0, c)
} else {
(c, 0.0, x)
};
let m = v - c;
let to_u8 = |channel: f32| ((channel + m) * 255.0).round().clamp(0.0, 255.0) as u8;
(to_u8(r1), to_u8(g1), to_u8(b1))
}
#[myko_command]
pub struct CloneSession {
pub id: SessionId,
}
impl CommandHandler for CloneSession {
fn execute(self, ctx: CommandContext) -> Result<(), CommandError> {
Err(CommandError {
tx: ctx.tx().to_string(),
command_id: ctx.command_id.to_string(),
message: "CloneSession handler is not implemented".to_string(),
})
}
}
#[myko_command]
pub struct SetOverviewScene {
pub id: SessionId,
#[serde(default)]
pub overview_scene_id: Option<Arc<str>>,
}
impl CommandHandler for SetOverviewScene {
fn execute(self, ctx: CommandContext) -> Result<(), CommandError> {
Err(CommandError {
tx: ctx.tx().to_string(),
command_id: ctx.command_id.to_string(),
message: "SetOverviewScene handler is not implemented".to_string(),
})
}
}