perspective_viewer/custom_elements/
copy_dropdown.rs1use std::cell::RefCell;
14use std::rc::Rc;
15
16use perspective_js::utils::global;
17use wasm_bindgen::prelude::*;
18use wasm_bindgen_futures::spawn_local;
19use web_sys::*;
20use yew::prelude::*;
21
22use super::viewer::PerspectiveViewerElement;
23use crate::components::copy_dropdown::CopyDropDownMenu;
24use crate::components::portal::PortalModal;
25use crate::components::style::{StyleProvider, StyleSurface};
26use crate::config::*;
27use crate::js::*;
28use crate::presentation::Presentation;
29use crate::renderer::*;
30use crate::session::Session;
31use crate::tasks::*;
32use crate::utils::*;
33use crate::*;
34
35type TargetState = Rc<RefCell<Option<HtmlElement>>>;
36
37#[derive(Properties, PartialEq)]
38struct CopyDropDownWrapperProps {
39 renderer: Renderer,
40 callback: Callback<ExportFile>,
41 target: TargetState,
42 custom_element: HtmlElement,
43 #[prop_or_default]
44 theme: String,
45}
46
47enum CopyDropDownWrapperMsg {
48 Open,
49 Close,
50}
51
52struct CopyDropDownWrapper {
53 target: Option<HtmlElement>,
54}
55
56impl Component for CopyDropDownWrapper {
57 type Message = CopyDropDownWrapperMsg;
58 type Properties = CopyDropDownWrapperProps;
59
60 fn create(_ctx: &Context<Self>) -> Self {
61 Self { target: None }
62 }
63
64 fn update(&mut self, ctx: &Context<Self>, msg: Self::Message) -> bool {
65 match msg {
66 CopyDropDownWrapperMsg::Open => {
67 self.target = ctx.props().target.borrow().clone();
68 true
69 },
70 CopyDropDownWrapperMsg::Close => {
71 self.target = None;
72 true
73 },
74 }
75 }
76
77 fn view(&self, ctx: &Context<Self>) -> Html {
78 let on_close = ctx.link().callback(|_| CopyDropDownWrapperMsg::Close);
79 html! {
80 <StyleProvider
81 root={ctx.props().custom_element.clone()}
82 surface={StyleSurface::DropdownMenu}
83 >
84 <PortalModal
85 tag_name="perspective-copy-menu"
86 surface={StyleSurface::DropdownMenu}
87 target={self.target.clone()}
88 own_focus=true
89 {on_close}
90 theme={ctx.props().theme.clone()}
91 >
92 <CopyDropDownMenu
93 renderer={ctx.props().renderer.clone()}
94 callback={ctx.props().callback.clone()}
95 />
96 </PortalModal>
97 </StyleProvider>
98 }
99 }
100}
101
102#[wasm_bindgen]
103#[derive(Clone)]
104pub struct CopyDropDownMenuElement {
105 elem: HtmlElement,
106 target: TargetState,
107 root: Rc<RefCell<Option<AppHandle<CopyDropDownWrapper>>>>,
108}
109
110impl CustomElementMetadata for CopyDropDownMenuElement {
111 const CUSTOM_ELEMENT_NAME: &'static str = "perspective-copy-menu";
112}
113
114#[wasm_bindgen]
115impl CopyDropDownMenuElement {
116 #[wasm_bindgen(constructor)]
117 pub fn new(elem: HtmlElement) -> Self {
118 Self {
119 elem,
120 target: Default::default(),
121 root: Default::default(),
122 }
123 }
124
125 pub fn open(&self, target: HtmlElement) {
126 *self.target.borrow_mut() = Some(target);
127 if let Some(root) = self.root.borrow().as_ref() {
128 root.send_message(CopyDropDownWrapperMsg::Open);
129 }
130 }
131
132 pub fn hide(&self) -> ApiResult<()> {
133 if let Some(root) = self.root.borrow().as_ref() {
134 root.send_message(CopyDropDownWrapperMsg::Close);
135 }
136 Ok(())
137 }
138
139 pub fn __set_model(&self, parent: &PerspectiveViewerElement) {
140 if let Some(panel) = parent.workspace.active_panel() {
141 self.set_config_model(&panel.session, &panel.renderer, &parent.presentation);
142 }
143 }
144
145 pub fn connected_callback(&self) {}
146}
147
148impl CopyDropDownMenuElement {
149 pub fn new_from_model(
150 session: &Session,
151 renderer: &Renderer,
152 presentation: &Presentation,
153 ) -> Self {
154 let dropdown = global::document()
155 .create_element("perspective-copy-menu")
156 .unwrap()
157 .unchecked_into::<HtmlElement>();
158
159 let elem = Self::new(dropdown);
160 elem.set_config_model(session, renderer, presentation);
161 elem
162 }
163
164 pub fn set_config_model(
165 &self,
166 session: &Session,
167 renderer: &Renderer,
168 presentation: &Presentation,
169 ) {
170 let callback = Callback::from({
171 let session = session.clone();
172 let renderer = renderer.clone();
173 let presentation = presentation.clone();
174 let target = self.target.clone();
175 let root = self.root.clone();
176 move |x: ExportFile| {
177 let session = session.clone();
178 let renderer = renderer.clone();
179 let presentation = presentation.clone();
180 let target = target.clone();
181 let root = root.clone();
182 spawn_local(async move {
183 let mime = x.method.mimetype(x.is_chart);
184 let task = export_method_to_blob(&session, &renderer, &presentation, x.method);
185 let result = copy_to_clipboard(task, mime).await;
186 let r = (|| -> ApiResult<()> {
187 result?;
188 *target.borrow_mut() = None;
189 if let Some(root) = root.borrow().as_ref() {
190 root.send_message(CopyDropDownWrapperMsg::Close);
191 }
192 Ok(())
193 })();
194 if let Err(e) = r {
195 web_sys::console::warn_1(&e.into());
196 }
197 })
198 }
199 });
200
201 let renderer = renderer.clone();
202 let init = ShadowRootInit::new(ShadowRootMode::Open);
203 let shadow_root = self
204 .elem
205 .attach_shadow(&init)
206 .unwrap()
207 .unchecked_into::<Element>();
208
209 let props = yew::props!(CopyDropDownWrapperProps {
210 renderer,
211 callback,
212 target: self.target.clone(),
213 custom_element: self.elem.clone()
214 });
215
216 let handle = yew::Renderer::with_root_and_props(shadow_root, props).render();
217 *self.root.borrow_mut() = Some(handle);
218 }
219}