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 if let Some(panel) = parent.workspace.active_panel() {
142 self.set_config_model(&panel.session, &panel.renderer, &parent.presentation);
143 }
144 }
145
146 pub fn connected_callback(&self) {}
147}
148
149impl ExportDropDownMenuElement {
150 pub fn new_from_model(
151 session: &Session,
152 renderer: &Renderer,
153 presentation: &Presentation,
154 ) -> Self {
155 let dropdown = global::document()
156 .create_element("perspective-export-menu")
157 .unwrap()
158 .unchecked_into::<HtmlElement>();
159
160 let elem = Self::new(dropdown);
161 elem.set_config_model(session, renderer, presentation);
162 elem
163 }
164
165 fn set_config_model(
166 &self,
167 session: &Session,
168 renderer: &Renderer,
169 presentation: &Presentation,
170 ) {
171 let callback = Callback::from({
172 let session = session.clone();
173 let renderer = renderer.clone();
174 let presentation = presentation.clone();
175 let target = self.target.clone();
176 let root = self.root.clone();
177 move |x: ExportFile| {
178 if !x.name.is_empty() {
179 clone!(target, root, session, renderer, presentation);
180 spawn_local(async move {
181 let val =
182 export_method_to_blob(&session, &renderer, &presentation, x.method)
183 .await
184 .unwrap();
185 let is_chart = renderer.is_chart();
186 download(&x.as_filename(is_chart), &val).unwrap();
187 *target.borrow_mut() = None;
188 if let Some(root) = root.borrow().as_ref() {
189 root.send_message(ExportDropDownWrapperMsg::Close);
190 }
191 })
192 }
193 }
194 });
195
196 let renderer = renderer.clone();
197 let session = session.clone();
198 let init = ShadowRootInit::new(ShadowRootMode::Open);
199 let shadow_root = self
200 .elem
201 .attach_shadow(&init)
202 .unwrap()
203 .unchecked_into::<Element>();
204
205 let props = yew::props!(ExportDropDownWrapperProps {
206 renderer,
207 session,
208 callback,
209 target: self.target.clone(),
210 custom_element: self.elem.clone(),
211 });
212
213 let handle = yew::Renderer::with_root_and_props(shadow_root, props).render();
214 *self.root.borrow_mut() = Some(handle);
215 }
216}