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, StyleSurface};
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
81 root={ctx.props().custom_element.clone()}
82 surface={StyleSurface::DropdownMenu}
83 >
84 <PortalModal
85 tag_name="perspective-export-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 <ExportDropDownMenu
93 renderer={ctx.props().renderer.clone()}
94 session={ctx.props().session.clone()}
95 callback={ctx.props().callback.clone()}
96 />
97 </PortalModal>
98 </StyleProvider>
99 }
100 }
101}
102
103#[wasm_bindgen]
104#[derive(Clone)]
105pub struct ExportDropDownMenuElement {
106 elem: HtmlElement,
107 target: TargetState,
108 root: Rc<RefCell<Option<AppHandle<ExportDropDownWrapper>>>>,
109}
110
111impl CustomElementMetadata for ExportDropDownMenuElement {
112 const CUSTOM_ELEMENT_NAME: &'static str = "perspective-export-menu";
113}
114
115#[wasm_bindgen]
116impl ExportDropDownMenuElement {
117 #[wasm_bindgen(constructor)]
118 pub fn new(elem: HtmlElement) -> Self {
119 Self {
120 elem,
121 target: Default::default(),
122 root: Default::default(),
123 }
124 }
125
126 pub fn open(&self, target: HtmlElement) {
127 *self.target.borrow_mut() = Some(target);
128 if let Some(root) = self.root.borrow().as_ref() {
129 root.send_message(ExportDropDownWrapperMsg::Open);
130 }
131 }
132
133 pub fn hide(&self) -> ApiResult<()> {
134 if let Some(root) = self.root.borrow().as_ref() {
135 root.send_message(ExportDropDownWrapperMsg::Close);
136 }
137 Ok(())
138 }
139
140 pub fn __set_model(&self, parent: &PerspectiveViewerElement) {
141 self.set_config_model(&parent.session, &parent.renderer, &parent.presentation)
142 }
143
144 pub fn connected_callback(&self) {}
145}
146
147impl ExportDropDownMenuElement {
148 pub fn new_from_model(
149 session: &Session,
150 renderer: &Renderer,
151 presentation: &Presentation,
152 ) -> Self {
153 let dropdown = global::document()
154 .create_element("perspective-export-menu")
155 .unwrap()
156 .unchecked_into::<HtmlElement>();
157
158 let elem = Self::new(dropdown);
159 elem.set_config_model(session, renderer, presentation);
160 elem
161 }
162
163 fn set_config_model(
164 &self,
165 session: &Session,
166 renderer: &Renderer,
167 presentation: &Presentation,
168 ) {
169 let callback = Callback::from({
170 let session = session.clone();
171 let renderer = renderer.clone();
172 let presentation = presentation.clone();
173 let target = self.target.clone();
174 let root = self.root.clone();
175 move |x: ExportFile| {
176 if !x.name.is_empty() {
177 clone!(target, root, session, renderer, presentation);
178 spawn_local(async move {
179 let val =
180 export_method_to_blob(&session, &renderer, &presentation, x.method)
181 .await
182 .unwrap();
183 let is_chart = renderer.is_chart();
184 download(&x.as_filename(is_chart), &val).unwrap();
185 *target.borrow_mut() = None;
186 if let Some(root) = root.borrow().as_ref() {
187 root.send_message(ExportDropDownWrapperMsg::Close);
188 }
189 })
190 }
191 }
192 });
193
194 let renderer = renderer.clone();
195 let session = session.clone();
196 let init = ShadowRootInit::new(ShadowRootMode::Open);
197 let shadow_root = self
198 .elem
199 .attach_shadow(&init)
200 .unwrap()
201 .unchecked_into::<Element>();
202
203 let props = yew::props!(ExportDropDownWrapperProps {
204 renderer,
205 session,
206 callback,
207 target: self.target.clone(),
208 custom_element: self.elem.clone(),
209 });
210
211 let handle = yew::Renderer::with_root_and_props(shadow_root, props).render();
212 *self.root.borrow_mut() = Some(handle);
213 }
214}