Skip to main content
Glama
view.rs17.3 kB
use std::{ collections::{ HashMap, HashSet, }, sync::Arc, }; use chrono::Utc; use petgraph::Outgoing; use serde::{ Deserialize, Serialize, }; use si_events::{ ComponentId, ContentHash, Timestamp, ulid::Ulid, }; use si_frontend_types::RawGeometry; pub use si_id::ViewId; use crate::{ ChangeSetId, DalContext, EdgeWeightKind, EdgeWeightKindDiscriminants, WorkspaceSnapshotError, WsEvent, WsEventResult, WsPayload, diagram::{ DiagramError, DiagramResult, diagram_object::DiagramObject, geometry::{ Geometry, GeometryId, }, }, implement_add_edge_to, layer_db_types::{ ViewContent, ViewContentV1, }, workspace_snapshot::{ node_weight::{ NodeWeight, category_node_weight::CategoryNodeKind, traits::SiVersionedNodeWeight, view_node_weight::ViewNodeWeight, }, traits::diagram::view::ViewExt, }, }; /// Represents spatial data for something to be shown on a view #[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq)] pub struct View { id: ViewId, name: String, #[serde(flatten)] timestamp: Timestamp, } impl View { implement_add_edge_to!( source_id: ViewId, destination_id: GeometryId, add_fn: add_edge_to_geometry, discriminant: EdgeWeightKindDiscriminants::Use, result: DiagramResult, ); implement_add_edge_to!( source_id: Ulid, destination_id: ViewId, add_fn: add_category_edge, discriminant: EdgeWeightKindDiscriminants::Use, result: DiagramResult, ); pub fn name(&self) -> &str { self.name.as_ref() } pub fn id(&self) -> ViewId { self.id } pub fn timestamp(&self) -> &Timestamp { &self.timestamp } pub async fn is_default(&self, ctx: &DalContext) -> DiagramResult<bool> { let default_id = Self::get_id_for_default(ctx).await?; Ok(default_id == self.id) } fn assemble(node_weight: ViewNodeWeight, content: ViewContent) -> Self { let content = content.extract(); Self { id: node_weight.id().into(), timestamp: content.timestamp, name: content.name, } } pub async fn new(ctx: &DalContext, name: impl AsRef<str>) -> DiagramResult<Self> { let snap = ctx.workspace_snapshot()?; let id = snap.generate_ulid().await?; let lineage_id = snap.generate_ulid().await?; let content = ViewContent::V1(ViewContentV1 { timestamp: Timestamp::now(), name: name.as_ref().to_owned(), }); let (content_address, _) = ctx.layer_db().cas().write( Arc::new(content.clone().into()), None, ctx.events_tenancy(), ctx.events_actor(), )?; let node_weight = NodeWeight::new_view(id, lineage_id, content_address); snap.add_or_replace_node(node_weight.clone()).await?; // Root --> View Category --> View (this) let view_category_id = snap .get_category_node_or_err(CategoryNodeKind::View) .await?; Self::add_category_edge(ctx, view_category_id, id.into(), EdgeWeightKind::new_use()) .await?; // View (this) --DiagramObject-> DiagramObject <-Represents-- Geometry DiagramObject::new_for_view(ctx, id.into()).await?; Ok(Self::assemble(node_weight.get_view_node_weight()?, content)) } pub async fn get_by_id(ctx: &DalContext, view_id: ViewId) -> DiagramResult<Self> { Self::try_get_by_id(ctx, view_id) .await? .ok_or(DiagramError::ViewNotFound(view_id)) } pub async fn try_get_by_id(ctx: &DalContext, view_id: ViewId) -> DiagramResult<Option<Self>> { let Some((node_weight, content)) = Self::try_get_node_weight_and_content(ctx, view_id).await? else { return Ok(None); }; Ok(Some(Self::assemble(node_weight, content))) } pub async fn find_by_name(ctx: &DalContext, name: &str) -> DiagramResult<Option<Self>> { for view_node_weight in Self::list_node_weights(ctx).await? { let content = Self::try_get_content(ctx, &view_node_weight.content_hash()) .await? .ok_or(WorkspaceSnapshotError::MissingContentFromStore( view_node_weight.id(), ))?; let view = Self::assemble(view_node_weight, content); if view.name == name { return Ok(Some(view)); } } Ok(None) } pub async fn list_ids(ctx: &DalContext) -> DiagramResult<Vec<ViewId>> { Ok(Self::list_node_weights(ctx) .await? .into_iter() .map(|w| ViewId::from(w.id())) .collect()) } async fn list_node_weights(ctx: &DalContext) -> DiagramResult<Vec<ViewNodeWeight>> { let snap = ctx.workspace_snapshot()?; let category_node = snap .get_category_node(CategoryNodeKind::View) .await? .ok_or(DiagramError::ViewCategoryNotFound)?; let mut views = vec![]; for view_idx in snap .outgoing_targets_for_edge_weight_kind(category_node, EdgeWeightKindDiscriminants::Use) .await? { let view_node_weight = snap .get_node_weight(view_idx) .await? .get_view_node_weight()?; views.push(view_node_weight); } Ok(views) } pub async fn list(ctx: &DalContext) -> DiagramResult<Vec<Self>> { let mut views = vec![]; for view_node_weight in Self::list_node_weights(ctx).await? { let content = Self::try_get_content(ctx, &view_node_weight.content_hash()) .await? .ok_or(WorkspaceSnapshotError::MissingContentFromStore( view_node_weight.id(), ))?; views.push(Self::assemble(view_node_weight, content)); } Ok(views) } pub async fn get_id_for_default(ctx: &DalContext) -> DiagramResult<ViewId> { let snap = ctx.workspace_snapshot()?; let view_category_id = snap .get_category_node(CategoryNodeKind::View) .await? .ok_or(DiagramError::ViewCategoryNotFound)?; let mut maybe_default_view = None; for (edge, _from_idx, to_idx) in snap .edges_directed_for_edge_weight_kind( view_category_id, Outgoing, EdgeWeightKindDiscriminants::Use, ) .await? { if *edge.kind() == EdgeWeightKind::new_use_default() { maybe_default_view = Some(snap.get_node_weight(to_idx).await?.id()) } } let Some(default_view) = maybe_default_view else { let mut current_views = Self::list(ctx).await?; current_views.sort_by_key(|f| f.id); // We should get the view with the lowest ID and return that! if let Some(view) = current_views.first() { return Ok(view.id); } else { return Err(DiagramError::DefaultViewNotFound); } }; Ok(default_view.into()) } async fn try_get_node_weight_and_content( ctx: &DalContext, view_id: ViewId, ) -> DiagramResult<Option<(ViewNodeWeight, ViewContent)>> { let id: Ulid = view_id.into(); let node_weight = match ctx.workspace_snapshot()?.get_node_weight_opt(id).await { Some(node_weight) => node_weight.get_view_node_weight()?, None => return Ok(None), }; let hash = node_weight.content_hash(); let content = Self::try_get_content(ctx, &hash).await?.ok_or( WorkspaceSnapshotError::MissingContentFromStore(view_id.into()), )?; Ok(Some((node_weight, content))) } async fn try_get_content( ctx: &DalContext, hash: &ContentHash, ) -> DiagramResult<Option<ViewContent>> { Ok(ctx.layer_db().cas().try_read_as(hash).await?) } pub async fn add_geometry_by_id( ctx: &DalContext, view_id: ViewId, geometry_id: GeometryId, ) -> DiagramResult<()> { Self::add_edge_to_geometry( ctx, view_id, geometry_id, EdgeWeightKind::Use { is_default: false }, ) .await } pub async fn add_to_another_view( ctx: &DalContext, object_view_id: ViewId, container_view_id: ViewId, raw_geometry: RawGeometry, ) -> DiagramResult<()> { let mut geometry = Geometry::new_for_view(ctx, object_view_id, container_view_id).await?; geometry.update(ctx, raw_geometry).await?; Ok(()) } pub async fn set_name(&mut self, ctx: &DalContext, name: impl AsRef<str>) -> DiagramResult<()> { let (hash, _) = ctx.layer_db().cas().write( Arc::new( ViewContent::V1(ViewContentV1 { timestamp: Timestamp { created_at: self.timestamp.created_at, updated_at: Utc::now(), }, name: name.as_ref().to_owned(), }) .into(), ), None, ctx.events_tenancy(), ctx.events_actor(), )?; ctx.workspace_snapshot()? .update_content(self.id.into(), hash) .await?; self.name = name.as_ref().to_string(); Ok(()) } pub async fn set_default(ctx: &DalContext, view_id: ViewId) -> DiagramResult<()> { let snap = ctx.workspace_snapshot()?; let view_category_id = snap .get_category_node(CategoryNodeKind::View) .await? .ok_or(DiagramError::ViewCategoryNotFound)?; // Update edge to old default for (edge, from_idx, to_idx) in snap .edges_directed_for_edge_weight_kind( view_category_id, Outgoing, EdgeWeightKindDiscriminants::Use, ) .await? { if *edge.kind() == EdgeWeightKind::new_use_default() { // We have found the existing previous default view // we now need to update that edge to be a Use snap.remove_edge(from_idx, to_idx, edge.kind().into()) .await?; Self::add_category_edge( ctx, view_category_id, snap.get_node_weight(to_idx).await?.id().into(), EdgeWeightKind::new_use(), ) .await?; } } snap.remove_edge(view_category_id, view_id, EdgeWeightKindDiscriminants::Use) .await?; Self::add_category_edge( ctx, view_category_id, view_id, EdgeWeightKind::new_use_default(), ) .await?; Ok(()) } pub async fn geometry(&self, ctx: &DalContext, view_id: ViewId) -> DiagramResult<Geometry> { Geometry::get_by_object_view_and_container_view(ctx, self.id, view_id).await } pub async fn set_geometry( &mut self, ctx: &DalContext, view_id: ViewId, x: impl Into<isize>, y: impl Into<isize>, width: Option<impl Into<isize>>, height: Option<impl Into<isize>>, ) -> DiagramResult<Geometry> { let new_geometry = RawGeometry { x: x.into(), y: y.into(), width: width.map(|w| w.into()), height: height.map(|h| h.into()), }; self.set_raw_geometry(ctx, new_geometry, view_id).await } pub async fn set_raw_geometry( &mut self, ctx: &DalContext, raw_geometry: RawGeometry, view_id: ViewId, ) -> DiagramResult<Geometry> { let mut geometry_pre = self.geometry(ctx, view_id).await?; if geometry_pre.into_raw() != raw_geometry { geometry_pre.update(ctx, raw_geometry).await?; } Ok(geometry_pre) } pub async fn remove(ctx: &DalContext, view_id: ViewId) -> DiagramResult<()> { ctx.workspace_snapshot()? .view_remove(view_id) .await .map_err(Into::into) } } /// Frontend representation for a [View](View) with a geometry. #[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq)] pub struct ViewObjectView { view: ViewView, geometry: RawGeometry, } impl ViewObjectView { pub async fn from_view_and_geometry( ctx: &DalContext, view: View, geometry: RawGeometry, ) -> DiagramResult<Self> { Ok(Self { view: ViewView::from_view(ctx, view).await?, geometry, }) } } /// Frontend representation for a [View](View). /// Yeah, it's a silly name, but all the other frontend representation structs are *View, /// so we either keep it or change everything. #[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq)] #[serde(rename_all = "camelCase")] pub struct ViewView { id: ViewId, name: String, is_default: bool, #[serde(flatten)] timestamp: Timestamp, } impl ViewView { pub async fn from_view(ctx: &DalContext, view: View) -> DiagramResult<Self> { Ok(ViewView { id: view.id(), name: view.name().to_owned(), is_default: view.is_default(ctx).await?, timestamp: view.timestamp().to_owned(), }) } } #[derive(Debug, Deserialize, Eq, PartialEq, Serialize, Clone, Default)] pub struct ViewComponentsUpdateSingle { pub added: HashMap<ComponentId, RawGeometry>, pub removed: HashSet<ComponentId>, } pub type ViewComponentsUpdateList = HashMap<ViewId, ViewComponentsUpdateSingle>; #[derive(Debug, Deserialize, Eq, PartialEq, Serialize, Clone)] #[serde(rename_all = "camelCase")] pub struct ViewComponentsUpdatePayload { change_set_id: ChangeSetId, updates_by_view: ViewComponentsUpdateList, } #[derive(Debug, Deserialize, Eq, PartialEq, Serialize, Clone)] #[serde(rename_all = "camelCase")] pub struct ViewWsPayload { change_set_id: ChangeSetId, view: ViewView, } #[derive(Debug, Deserialize, Eq, PartialEq, Serialize, Clone)] #[serde(rename_all = "camelCase")] pub struct ViewDeletedPayload { change_set_id: ChangeSetId, view_id: ViewId, } #[derive(Debug, Deserialize, Eq, PartialEq, Serialize, Clone)] #[serde(rename_all = "camelCase")] pub struct ViewObjectRemovedPayload { view_id: ViewId, view_object_id: ViewId, } #[derive(Debug, Deserialize, Eq, PartialEq, Serialize, Clone)] #[serde(rename_all = "camelCase")] pub struct ViewObjectCreatedPayload { view_id: ViewId, view_object_id: ViewId, geometry: RawGeometry, } impl WsEvent { pub async fn view_created(ctx: &DalContext, view: ViewView) -> WsEventResult<Self> { WsEvent::new( ctx, WsPayload::ViewCreated(ViewWsPayload { change_set_id: ctx.change_set_id(), view, }), ) .await } pub async fn view_updated(ctx: &DalContext, view: ViewView) -> WsEventResult<Self> { WsEvent::new( ctx, WsPayload::ViewUpdated(ViewWsPayload { change_set_id: ctx.change_set_id(), view, }), ) .await } pub async fn view_deleted(ctx: &DalContext, view_id: ViewId) -> WsEventResult<Self> { WsEvent::new( ctx, WsPayload::ViewDeleted(ViewDeletedPayload { change_set_id: ctx.change_set_id(), view_id, }), ) .await } pub async fn view_components_update( ctx: &DalContext, updates_by_view: ViewComponentsUpdateList, ) -> WsEventResult<Self> { WsEvent::new( ctx, WsPayload::ViewComponentsUpdate(ViewComponentsUpdatePayload { change_set_id: ctx.change_set_id(), updates_by_view, }), ) .await } pub async fn view_object_erased( ctx: &DalContext, view_id: ViewId, view_object_id: ViewId, ) -> WsEventResult<Self> { WsEvent::new( ctx, WsPayload::ViewObjectRemoved(ViewObjectRemovedPayload { view_id, view_object_id, }), ) .await } pub async fn view_object_created( ctx: &DalContext, view_id: ViewId, view_object_id: ViewId, geometry: RawGeometry, ) -> WsEventResult<Self> { WsEvent::new( ctx, WsPayload::ViewObjectCreated(ViewObjectCreatedPayload { view_id, view_object_id, geometry, }), ) .await } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/systeminit/si'

If you have feedback or need assistance with the MCP directory API, please join our Discord server