qualia_client_core/view_host/
mod.rs1mod session;
6
7pub use session::{
8 morph_flatten, morph_spatialize, project_library_for_observer, project_web_locus, MorphMode,
9 ViewSession,
10};
11
12use qualia_core_db::entity_view::{Circumstance, EntityId, ObserverStatus, PresentationLevel};
13use serde::{Deserialize, Serialize};
14use std::sync::Mutex;
15
16static SESSION: Mutex<Option<ViewSession>> = Mutex::new(None);
18
19pub fn with_session<R>(f: impl FnOnce(&mut ViewSession) -> R) -> R {
20 let mut g = SESSION.lock().unwrap_or_else(|e| e.into_inner());
21 if g.is_none() {
22 *g = Some(ViewSession::default());
23 }
24 f(g.as_mut().expect("session just set"))
25}
26
27pub fn get_session_snapshot() -> ViewSession {
28 with_session(|s| s.clone())
29}
30
31pub fn set_observer(status: ObserverStatus) {
32 with_session(|s| s.observer = status);
33}
34
35pub fn set_presentation_level(level: u8) {
36 with_session(|s| s.presentation_level = PresentationLevel::from_u8(level));
37}
38
39pub fn select_entity(entity_id: u64) {
40 with_session(|s| {
41 s.selection.clear();
42 s.selection.push(EntityId::from_raw(entity_id));
43 });
44}
45
46pub fn select_entity_uri(uri: &str) {
48 let id = EntityId::from_uri(uri);
49 with_session(|s| {
50 s.selection.clear();
51 s.selection.push(id);
52 s.attention_url = Some(uri.to_string());
53 });
54}
55
56pub fn clear_selection() {
57 with_session(|s| {
58 s.selection.clear();
59 s.attention_url = None;
60 });
61}
62
63pub fn set_circumstance(c: Circumstance) {
65 with_session(|s| s.circumstance = c);
66}
67
68#[derive(Debug, Clone, Serialize, Deserialize)]
70pub struct ProjectLibraryRequest {
71 pub storage_path: String,
72 pub section: Option<String>,
73 pub observer: Option<String>,
74 pub presentation_level: Option<u8>,
75}
76
77#[derive(Debug, Clone, Serialize, Deserialize)]
78pub struct ProjectWebLocusRequest {
79 pub url: String,
80 pub observer: Option<String>,
81}
82
83pub fn parse_observer(s: &str) -> ObserverStatus {
84 match s.trim().to_ascii_lowercase().as_str() {
85 "peer" => ObserverStatus::Peer,
86 "guardian" => ObserverStatus::Guardian,
87 "steward" => ObserverStatus::Steward,
88 "public" | "anonymous" => ObserverStatus::Public,
89 "instrument" | "agent" => ObserverStatus::Instrument,
90 "auditor" => ObserverStatus::Auditor,
91 _ => ObserverStatus::Principal,
92 }
93}
94
95pub fn project_library_json(
97 storage_path: &str,
98 section: Option<&str>,
99 observer: ObserverStatus,
100 level: PresentationLevel,
101) -> Result<serde_json::Value, String> {
102 with_session(|s| {
103 s.observer = observer;
104 s.presentation_level = level;
105 let result = project_library_for_observer(storage_path, section, observer, level)?;
106 s.last_projection = Some(result.clone());
107 s.morph_mode = MorphMode::Both;
108 serde_json::to_value(&result).map_err(|e| e.to_string())
109 })
110}
111
112pub fn project_web_locus_json(
113 url: &str,
114 observer: ObserverStatus,
115) -> Result<serde_json::Value, String> {
116 with_session(|s| {
117 s.observer = observer;
118 let card = project_web_locus(url, observer);
119 if let Some(id) = card.get("entity_id").and_then(|v| v.as_u64()) {
120 s.selection.clear();
121 s.selection.push(EntityId::from_raw(id));
122 }
123 s.attention_url = Some(url.to_string());
124 Ok(card)
125 })
126}
127
128pub fn morph_json(mode: &str) -> Result<serde_json::Value, String> {
129 with_session(|s| {
130 let Some(ref proj) = s.last_projection else {
131 return Err("no projection yet - call view_project_library first".into());
132 };
133 match mode.trim().to_ascii_lowercase().as_str() {
134 "flatten" | "flat" => {
135 s.morph_mode = MorphMode::Flatten;
136 Ok(morph_flatten(proj))
137 }
138 "spatialize" | "spatial" | "scene" => {
139 s.morph_mode = MorphMode::Spatialize;
140 Ok(morph_spatialize(proj))
141 }
142 "both" => {
143 s.morph_mode = MorphMode::Both;
144 serde_json::to_value(proj).map_err(|e| e.to_string())
145 }
146 _ => Err(format!(
147 "unknown morph mode '{mode}' (flatten|spatialize|both)"
148 )),
149 }
150 })
151}
152
153pub fn pick_scene_node_at(nx: f64, ny: f64, max_dist: f64) -> Option<u64> {
156 with_session(|s| {
157 let proj = s.last_projection.as_ref()?;
158 let mut best: Option<(f64, u64)> = None;
159 for n in &proj.scene_nodes {
160 if n.entity_id == 0 {
161 continue;
162 }
163 let dx = n.x - nx;
164 let dy = n.y - ny;
165 let d = (dx * dx + dy * dy).sqrt();
166 if d <= max_dist {
167 if best.map(|(bd, _)| d < bd).unwrap_or(true) {
168 best = Some((d, n.entity_id));
169 }
170 }
171 }
172 let id = best.map(|(_, id)| id)?;
173 s.selection.clear();
174 s.selection.push(EntityId::from_raw(id));
175 Some(id)
176 })
177}