1use std::cell::RefCell;
13use std::rc::Rc;
14
15use tilo_actions::InvocationUpdate;
16use tilo_actions::remote::{ActionHostError, InvokeArgument};
17use tilo_reference::ResourceRef;
18use ui_core::widget::ChipLead;
19use ui_core::{Arrival, InvocationCall, RemoteActions};
20
21pub(crate) fn open_link(opener: &Opener, href: &str) -> Result<(), String> {
25 let link = tilo_reference::link::Link::parse_native(href).map_err(|error| error.to_string())?;
26 let address = link
27 .target
28 .resource
29 .ok_or_else(|| "The link names no resource".to_owned())?;
30 let app = match &link.target.record {
31 Some(_) => link.target.app.as_ref().map(|app| app.as_str().to_owned()),
32 None => ui_core::app_surface::app_of_kind(&address.kind),
33 }
34 .ok_or_else(|| "No app of this desktop keeps what the link names".to_owned())?;
35 let record = link
37 .target
38 .record
39 .as_ref()
40 .map(tilo_reference::link::RecordSegment::provider_part)
41 .filter(|id| !id.is_empty());
42 opener.open(&app, record, &address.into_ref(String::new()))
43}
44
45#[derive(Clone, Debug, PartialEq)]
50pub struct Call {
51 app: String,
52 record: Option<String>,
53 reference: ResourceRef,
54 action: String,
55 arguments: Result<serde_json::Map<String, serde_json::Value>, String>,
56 host: Option<String>,
59}
60
61impl Call {
62 #[must_use]
66 pub fn on(app: &str, record: Option<&str>, reference: &ResourceRef, action: &str) -> Self {
67 Self {
68 app: app.to_owned(),
69 record: record.map(str::to_owned),
70 reference: reference.clone(),
71 action: action.to_owned(),
72 arguments: Ok(serde_json::Map::new()),
73 host: None,
74 }
75 }
76
77 #[must_use]
81 pub fn host(provider: &str, resource: &str, action: &str) -> Self {
82 Self {
83 app: String::new(),
84 record: None,
85 reference: ResourceRef::whole(resource, resource, String::new()),
86 action: action.to_owned(),
87 arguments: Ok(serde_json::Map::new()),
88 host: Some(provider.to_owned()),
89 }
90 }
91
92 #[must_use]
95 pub fn with(mut self, arguments: impl serde::Serialize) -> Self {
96 self.arguments = match serde_json::to_value(arguments) {
97 Ok(serde_json::Value::Object(arguments)) => Ok(arguments),
98 Ok(_) => Err("an action's arguments are named".to_owned()),
99 Err(error) => Err(error.to_string()),
100 };
101 self
102 }
103
104 #[must_use]
106 pub fn action(&self) -> &str {
107 &self.action
108 }
109
110 pub(crate) fn request(
112 &self,
113 ) -> Result<(tilo_actions::remote::RemoteActionTarget, InvokeArgument), String> {
114 let arguments = self.arguments.clone()?;
115 let target = match &self.host {
116 Some(provider) => tilo_actions::remote::RemoteActionTarget {
117 provider: provider.clone(),
118 address: tilo_actions::ActionAddress::new(
119 self.reference.id.clone(),
120 self.action.clone(),
121 ),
122 },
123 None => self.reference.action_target(
124 &workspace()?,
125 &self.app,
126 self.record.as_deref(),
127 &self.action,
128 ),
129 };
130 Ok((target, InvokeArgument::Json(arguments)))
131 }
132}
133
134pub(crate) const OPEN: &str = tilo_actions::remote::APPLICATION_OPEN_ACTION;
136
137pub const LEAD: ChipLead = ui_core::widget::REFERENCE_LEAD;
139
140pub fn workspace() -> Result<String, String> {
142 if let Some(workspace) = ui_core::window_toolbar::workspace() {
145 return Ok(workspace);
146 }
147 let workspace = ui_core::collaboration::local_cluster_id();
148 if workspace.is_nil() {
149 Err("This window is in no workspace".into())
150 } else {
151 Ok(workspace.to_string())
152 }
153}
154
155#[derive(Clone, Default)]
160pub(crate) struct Opener(Rc<RefCell<Opening>>);
161
162#[derive(Default)]
163struct Opening {
164 door: Option<RemoteActions>,
165 calls: Vec<(String, InvocationCall)>,
167 reads: Vec<(std::rc::Weak<()>, Rc<dyn std::any::Any>)>,
170}
171
172impl Opener {
173 pub(crate) fn open(
175 &self,
176 app: &str,
177 record: Option<&str>,
178 reference: &ResourceRef,
179 ) -> Result<(), String> {
180 let target = reference.open_target(&workspace()?, app, record);
181 self.invoke(target, InvokeArgument::None);
182 Ok(())
183 }
184
185 pub(crate) fn invoke_own(
188 &self,
189 address: tilo_actions::ActionAddress,
190 arguments: serde_json::Map<String, serde_json::Value>,
191 ) {
192 self.invoke(
193 tilo_actions::remote::RemoteActionTarget {
194 provider: tilo_actions::remote::OWN_PROVIDER.into(),
195 address,
196 },
197 InvokeArgument::Json(arguments),
198 );
199 }
200
201 pub(crate) fn invoke(
202 &self,
203 target: tilo_actions::remote::RemoteActionTarget,
204 argument: InvokeArgument,
205 ) {
206 let action = target.address.action.clone();
207 let mut opening = self.0.borrow_mut();
208 let call = opening
209 .door
210 .get_or_insert_with(RemoteActions::current)
211 .invoke(target, argument, None);
212 opening.calls.push((action, call));
213 }
214
215 pub(crate) fn call(
220 &self,
221 parent: Option<tilo_actions::InvocationId>,
222 target: tilo_actions::remote::RemoteActionTarget,
223 argument: InvokeArgument,
224 ) -> InvocationCall {
225 self.0
226 .borrow_mut()
227 .door
228 .get_or_insert_with(RemoteActions::current)
229 .invoke_continuing(
230 parent,
231 tilo_actions::InvocationId::new(),
232 target,
233 argument,
234 None,
235 )
236 }
237
238 pub(crate) fn follow(
241 &self,
242 target: tilo_actions::remote::RemoteActionTarget,
243 argument: InvokeArgument,
244 then: impl FnMut(&Arrival<InvocationUpdate, ActionHostError>) + Send + 'static,
245 ) -> ui_core::Followed {
246 self.0
247 .borrow_mut()
248 .door
249 .get_or_insert_with(RemoteActions::current)
250 .follow_with(target, argument, then)
251 }
252
253 pub(crate) fn keep(&self, alive: std::rc::Weak<()>, read: Rc<dyn std::any::Any>) {
255 self.0.borrow_mut().reads.push((alive, read));
256 }
257
258 pub(crate) fn take_refusals(&self) -> Vec<(String, String)> {
261 let mut refusals = Vec::new();
262 self.0.borrow_mut().calls.retain(|(action, call)| {
263 call.with(|call| match call {
264 Arrival::Pending { .. }
265 | Arrival::Ready(
266 InvocationUpdate::Accepted { .. }
267 | InvocationUpdate::Progress { .. }
268 | InvocationUpdate::Item { .. },
269 ) => true,
270 Arrival::Ready(InvocationUpdate::Failed { error, .. }) => {
271 refusals.push((action.clone(), error.message.clone()));
272 false
273 }
274 Arrival::Failed(error) => {
275 refusals.push((action.clone(), error.to_string()));
276 false
277 }
278 Arrival::Ready(_) => false,
279 })
280 });
281 refusals
282 }
283
284 pub(crate) fn door(&self) -> Option<RemoteActions> {
287 let gone: Vec<_> = {
290 let mut opening = self.0.borrow_mut();
291 let (kept, gone) = std::mem::take(&mut opening.reads)
292 .into_iter()
293 .partition(|(alive, _)| alive.strong_count() > 0);
294 opening.reads = kept;
295 gone
296 };
297 drop(gone);
298 self.0.borrow().door.clone()
299 }
300}