Skip to main content

powhttp_sdk/
context_menu.rs

1use std::sync::Arc;
2use serde::Serialize;
3
4use crate::runtime::handlers::{MultiEntryContext, SingleEntryContext};
5use crate::runtime::state::ContextMenuHandler;
6use crate::runtime::handle::ExtensionHandle;
7use crate::error::Error;
8
9/// A clickable item in the session context menu.
10///
11/// The type parameter `C` determines whether this item operates on a single
12/// entry ([`SingleEntryContext`]) or multiple entries ([`MultiEntryContext`]).
13///
14/// ```
15/// use powhttp_sdk::{ContextMenuItemSingle, ExtensionHandle, SingleEntryContext, Error};
16///
17/// let item = ContextMenuItemSingle::new(
18///     "copy-url",
19///     "Copy URL",
20///     async |ctx: SingleEntryContext, handle: ExtensionHandle| {
21///         // handler logic here
22///         Ok(())
23///     },
24/// );
25/// ```
26#[derive(Serialize)]
27#[serde(bound(serialize = ""))]
28#[serde(rename_all = "camelCase")]
29pub struct ContextMenuItem<C> {
30    id: String,
31    label: String,
32    #[serde(skip)]
33    handler: ContextMenuHandler<C>,
34}
35
36impl<C> ContextMenuItem<C> {
37    /// Creates a new context-menu item with the given `id`, display `label` and async handler.
38    pub fn new<F, Fut>(id: impl Into<String>, label: impl Into<String>, handler: F) -> Self
39    where
40        F: Fn(C, ExtensionHandle) -> Fut + Send + Sync + 'static,
41        Fut: Future<Output = Result<(), Error>> + Send + 'static,
42    {
43        Self {
44            id: id.into(),
45            label: label.into(),
46            handler: Arc::new(move |ctx, handle| {
47                let fut = handler(ctx, handle);
48                Box::pin(async move { fut.await.map_err(Error::into_jrpc) })
49            }),
50        }
51    }
52}
53
54/// A submenu that groups context-menu items under a shared label.
55#[derive(Serialize)]
56#[serde(bound(serialize = ""))]
57#[serde(rename_all = "camelCase")]
58pub struct ContextMenuSubmenu<C> {
59    label: String,
60    children: Vec<ContextMenuNode<C>>,
61}
62
63impl<C> ContextMenuSubmenu<C> {
64    /// Creates an empty submenu with the given display `label`.
65    pub fn new(label: impl Into<String>) -> Self {
66        Self {
67            label: label.into(),
68            children: Vec::new()
69        }
70    }
71
72    /// Appends all nodes from `children`, draining the provided vec.
73    pub fn with_children(mut self, children: &mut Vec<ContextMenuNode<C>>) -> Self {
74        self.children.append(children);
75        self
76    }
77
78    /// Appends a single child node (item or nested submenu).
79    pub fn with_child(mut self, child: ContextMenuNode<C>) -> Self {
80        self.children.push(child);
81        self
82    }
83}
84
85/// A node in the context-menu tree, either a leaf [`Item`](ContextMenuNode::Item)
86/// or a [`Submenu`](ContextMenuNode::Submenu).
87#[derive(Serialize)]
88#[serde(bound(serialize = ""))]
89#[serde(tag = "type", rename_all = "snake_case")]
90pub enum ContextMenuNode<C> {
91    Submenu(ContextMenuSubmenu<C>),
92    Item(ContextMenuItem<C>),
93}
94
95impl<C> ContextMenuNode<C> {
96    pub(crate) fn extract_handlers(&self) -> Vec<(String, ContextMenuHandler<C>)> {
97        match self {
98            Self::Item(item) => vec![(item.id.clone(), Arc::clone(&item.handler))],
99            Self::Submenu(sub) => sub
100                .children
101                .iter()
102                .flat_map(|child| child.extract_handlers())
103                .collect(),
104        }
105    }
106}
107
108impl<C> From<ContextMenuItem<C>> for ContextMenuNode<C> {
109    fn from(item: ContextMenuItem<C>) -> Self {
110        ContextMenuNode::Item(item)
111    }
112}
113
114impl<C> From<ContextMenuSubmenu<C>> for ContextMenuNode<C> {
115    fn from(submenu: ContextMenuSubmenu<C>) -> Self {
116        ContextMenuNode::Submenu(submenu)
117    }
118}
119
120/// Context-menu item for single-entry selections.
121pub type ContextMenuItemSingle = ContextMenuItem<SingleEntryContext>;
122/// Context-menu submenu for single-entry selections.
123pub type ContextMenuSubmenuSingle = ContextMenuSubmenu<SingleEntryContext>;
124/// Context-menu node for single-entry selections.
125pub type ContextMenuNodeSingle = ContextMenuNode<SingleEntryContext>;
126
127/// Context-menu item for multi-entry selections.
128pub type ContextMenuItemMulti = ContextMenuItem<MultiEntryContext>;
129/// Context-menu submenu for multi-entry selections.
130pub type ContextMenuSubmenuMulti = ContextMenuSubmenu<MultiEntryContext>;
131/// Context-menu node for multi-entry selections.
132pub type ContextMenuNodeMulti = ContextMenuNode<MultiEntryContext>;