Skip to main content

perspective_viewer/custom_elements/
copy_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::copy_dropdown::CopyDropDownMenu;
24use crate::components::portal::PortalModal;
25use crate::components::style::StyleProvider;
26use crate::config::*;
27use crate::js::*;
28use crate::presentation::Presentation;
29use crate::renderer::*;
30use crate::session::Session;
31use crate::tasks::*;
32use crate::utils::*;
33use crate::*;
34
35type TargetState = Rc<RefCell<Option<HtmlElement>>>;
36
37#[derive(Properties, PartialEq)]
38struct CopyDropDownWrapperProps {
39    renderer: Renderer,
40    callback: Callback<ExportFile>,
41    target: TargetState,
42    custom_element: HtmlElement,
43    #[prop_or_default]
44    theme: String,
45}
46
47enum CopyDropDownWrapperMsg {
48    Open,
49    Close,
50}
51
52struct CopyDropDownWrapper {
53    target: Option<HtmlElement>,
54}
55
56impl Component for CopyDropDownWrapper {
57    type Message = CopyDropDownWrapperMsg;
58    type Properties = CopyDropDownWrapperProps;
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            CopyDropDownWrapperMsg::Open => {
67                self.target = ctx.props().target.borrow().clone();
68                true
69            },
70            CopyDropDownWrapperMsg::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(|_| CopyDropDownWrapperMsg::Close);
79        html! {
80            <StyleProvider root={ctx.props().custom_element.clone()}>
81                <PortalModal
82                    tag_name="perspective-copy-menu"
83                    target={self.target.clone()}
84                    own_focus=true
85                    {on_close}
86                    theme={ctx.props().theme.clone()}
87                >
88                    <CopyDropDownMenu
89                        renderer={ctx.props().renderer.clone()}
90                        callback={ctx.props().callback.clone()}
91                    />
92                </PortalModal>
93            </StyleProvider>
94        }
95    }
96}
97
98#[wasm_bindgen]
99#[derive(Clone)]
100pub struct CopyDropDownMenuElement {
101    elem: HtmlElement,
102    target: TargetState,
103    root: Rc<RefCell<Option<AppHandle<CopyDropDownWrapper>>>>,
104}
105
106impl CustomElementMetadata for CopyDropDownMenuElement {
107    const CUSTOM_ELEMENT_NAME: &'static str = "perspective-copy-menu";
108}
109
110#[wasm_bindgen]
111impl CopyDropDownMenuElement {
112    #[wasm_bindgen(constructor)]
113    pub fn new(elem: HtmlElement) -> Self {
114        Self {
115            elem,
116            target: Default::default(),
117            root: Default::default(),
118        }
119    }
120
121    pub fn open(&self, target: HtmlElement) {
122        *self.target.borrow_mut() = Some(target);
123        if let Some(root) = self.root.borrow().as_ref() {
124            root.send_message(CopyDropDownWrapperMsg::Open);
125        }
126    }
127
128    pub fn hide(&self) -> ApiResult<()> {
129        if let Some(root) = self.root.borrow().as_ref() {
130            root.send_message(CopyDropDownWrapperMsg::Close);
131        }
132        Ok(())
133    }
134
135    pub fn __set_model(&self, parent: &PerspectiveViewerElement) {
136        self.set_config_model(&parent.session, &parent.renderer, &parent.presentation)
137    }
138
139    pub fn connected_callback(&self) {}
140}
141
142impl CopyDropDownMenuElement {
143    pub fn new_from_model(
144        session: &Session,
145        renderer: &Renderer,
146        presentation: &Presentation,
147    ) -> Self {
148        let dropdown = global::document()
149            .create_element("perspective-copy-menu")
150            .unwrap()
151            .unchecked_into::<HtmlElement>();
152
153        let elem = Self::new(dropdown);
154        elem.set_config_model(session, renderer, presentation);
155        elem
156    }
157
158    pub fn set_config_model(
159        &self,
160        session: &Session,
161        renderer: &Renderer,
162        presentation: &Presentation,
163    ) {
164        let callback = Callback::from({
165            let session = session.clone();
166            let renderer = renderer.clone();
167            let presentation = presentation.clone();
168            let target = self.target.clone();
169            let root = self.root.clone();
170            move |x: ExportFile| {
171                let session = session.clone();
172                let renderer = renderer.clone();
173                let presentation = presentation.clone();
174                let target = target.clone();
175                let root = root.clone();
176                spawn_local(async move {
177                    let mime = x.method.mimetype(x.is_chart);
178                    let task = export_method_to_blob(&session, &renderer, &presentation, x.method);
179                    let result = copy_to_clipboard(task, mime).await;
180                    let r = (|| -> ApiResult<()> {
181                        result?;
182                        *target.borrow_mut() = None;
183                        if let Some(root) = root.borrow().as_ref() {
184                            root.send_message(CopyDropDownWrapperMsg::Close);
185                        }
186                        Ok(())
187                    })();
188                    if let Err(e) = r {
189                        web_sys::console::warn_1(&e.into());
190                    }
191                })
192            }
193        });
194
195        let renderer = renderer.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!(CopyDropDownWrapperProps {
204            renderer,
205            callback,
206            target: self.target.clone(),
207            custom_element: self.elem.clone()
208        });
209
210        let handle = yew::Renderer::with_root_and_props(shadow_root, props).render();
211        *self.root.borrow_mut() = Some(handle);
212    }
213}