Skip to main content

perspective_viewer/custom_elements/
export_dropdown.rs

1// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
2// ┃ ██████ ██████ ██████       █      █      █      █      █ █▄  ▀███ █       ┃
3// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█  ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄  ▀█ █ ▀▀▀▀▀ ┃
4// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄   █ ▄▄▄▄▄ ┃
5// ┃ █      ██████ █  ▀█▄       █ ██████      █      ███▌▐███ ███████▄ █       ┃
6// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
7// ┃ Copyright (c) 2017, the Perspective Authors.                              ┃
8// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
9// ┃ This file is part of the Perspective library, distributed under the terms ┃
10// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
11// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
12
13use 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}