perspective_viewer/custom_elements/
export_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::export_dropdown::ExportDropDownMenu;
24use crate::components::portal::PortalModal;
25use crate::components::style::StyleProvider;
26use crate::config::*;
27use crate::presentation::Presentation;
28use crate::renderer::*;
29use crate::session::*;
30use crate::tasks::*;
31use crate::utils::*;
32use crate::*;
33
34type TargetState = Rc<RefCell<Option<HtmlElement>>>;
35
36#[derive(Properties, PartialEq)]
37struct ExportDropDownWrapperProps {
38 renderer: Renderer,
39 session: Session,
40 callback: Callback<ExportFile>,
41 target: TargetState,
42 custom_element: HtmlElement,
43 #[prop_or_default]
44 theme: String,
45}
46
47enum ExportDropDownWrapperMsg {
48 Open,
49 Close,
50}
51
52struct ExportDropDownWrapper {
53 target: Option<HtmlElement>,
54}
55
56impl Component for ExportDropDownWrapper {
57 type Message = ExportDropDownWrapperMsg;
58 type Properties = ExportDropDownWrapperProps;
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 ExportDropDownWrapperMsg::Open => {
67 self.target = ctx.props().target.borrow().clone();
68 true
69 },
70 ExportDropDownWrapperMsg::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(|_| ExportDropDownWrapperMsg::Close);
79 html! {
80 <StyleProvider root={ctx.props().custom_element.clone()}>
81 <PortalModal
82 tag_name="perspective-export-menu"
83 target={self.target.clone()}
84 own_focus=true
85 {on_close}
86 theme={ctx.props().theme.clone()}
87 >
88 <ExportDropDownMenu
89 renderer={ctx.props().renderer.clone()}
90 session={ctx.props().session.clone()}
91 callback={ctx.props().callback.clone()}
92 />
93 </PortalModal>
94 </StyleProvider>
95 }
96 }
97}
98
99#[wasm_bindgen]
100#[derive(Clone)]
101pub struct ExportDropDownMenuElement {
102 elem: HtmlElement,
103 target: TargetState,
104 root: Rc<RefCell<Option<AppHandle<ExportDropDownWrapper>>>>,
105}
106
107impl CustomElementMetadata for ExportDropDownMenuElement {
108 const CUSTOM_ELEMENT_NAME: &'static str = "perspective-export-menu";
109}
110
111#[wasm_bindgen]
112impl ExportDropDownMenuElement {
113 #[wasm_bindgen(constructor)]
114 pub fn new(elem: HtmlElement) -> Self {
115 Self {
116 elem,
117 target: Default::default(),
118 root: Default::default(),
119 }
120 }
121
122 pub fn open(&self, target: HtmlElement) {
123 *self.target.borrow_mut() = Some(target);
124 if let Some(root) = self.root.borrow().as_ref() {
125 root.send_message(ExportDropDownWrapperMsg::Open);
126 }
127 }
128
129 pub fn hide(&self) -> ApiResult<()> {
130 if let Some(root) = self.root.borrow().as_ref() {
131 root.send_message(ExportDropDownWrapperMsg::Close);
132 }
133 Ok(())
134 }
135
136 pub fn __set_model(&self, parent: &PerspectiveViewerElement) {
137 self.set_config_model(&parent.session, &parent.renderer, &parent.presentation)
138 }
139
140 pub fn connected_callback(&self) {}
141}
142
143impl ExportDropDownMenuElement {
144 pub fn new_from_model(
145 session: &Session,
146 renderer: &Renderer,
147 presentation: &Presentation,
148 ) -> Self {
149 let dropdown = global::document()
150 .create_element("perspective-export-menu")
151 .unwrap()
152 .unchecked_into::<HtmlElement>();
153
154 let elem = Self::new(dropdown);
155 elem.set_config_model(session, renderer, presentation);
156 elem
157 }
158
159 fn set_config_model(
160 &self,
161 session: &Session,
162 renderer: &Renderer,
163 presentation: &Presentation,
164 ) {
165 let callback = Callback::from({
166 let session = session.clone();
167 let renderer = renderer.clone();
168 let presentation = presentation.clone();
169 let target = self.target.clone();
170 let root = self.root.clone();
171 move |x: ExportFile| {
172 if !x.name.is_empty() {
173 clone!(target, root, session, renderer, presentation);
174 spawn_local(async move {
175 let val =
176 export_method_to_blob(&session, &renderer, &presentation, x.method)
177 .await
178 .unwrap();
179 let is_chart = renderer.is_chart();
180 download(&x.as_filename(is_chart), &val).unwrap();
181 *target.borrow_mut() = None;
182 if let Some(root) = root.borrow().as_ref() {
183 root.send_message(ExportDropDownWrapperMsg::Close);
184 }
185 })
186 }
187 }
188 });
189
190 let renderer = renderer.clone();
191 let session = session.clone();
192 let init = ShadowRootInit::new(ShadowRootMode::Open);
193 let shadow_root = self
194 .elem
195 .attach_shadow(&init)
196 .unwrap()
197 .unchecked_into::<Element>();
198
199 let props = yew::props!(ExportDropDownWrapperProps {
200 renderer,
201 session,
202 callback,
203 target: self.target.clone(),
204 custom_element: self.elem.clone(),
205 });
206
207 let handle = yew::Renderer::with_root_and_props(shadow_root, props).render();
208 *self.root.borrow_mut() = Some(handle);
209 }
210}