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;
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}