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