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, StyleSurface};
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
81                root={ctx.props().custom_element.clone()}
82                surface={StyleSurface::DropdownMenu}
83            >
84                <PortalModal
85                    tag_name="perspective-copy-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                    <CopyDropDownMenu
93                        renderer={ctx.props().renderer.clone()}
94                        callback={ctx.props().callback.clone()}
95                    />
96                </PortalModal>
97            </StyleProvider>
98        }
99    }
100}
101
102#[wasm_bindgen]
103#[derive(Clone)]
104pub struct CopyDropDownMenuElement {
105    elem: HtmlElement,
106    target: TargetState,
107    root: Rc<RefCell<Option<AppHandle<CopyDropDownWrapper>>>>,
108}
109
110impl CustomElementMetadata for CopyDropDownMenuElement {
111    const CUSTOM_ELEMENT_NAME: &'static str = "perspective-copy-menu";
112}
113
114#[wasm_bindgen]
115impl CopyDropDownMenuElement {
116    #[wasm_bindgen(constructor)]
117    pub fn new(elem: HtmlElement) -> Self {
118        Self {
119            elem,
120            target: Default::default(),
121            root: Default::default(),
122        }
123    }
124
125    pub fn open(&self, target: HtmlElement) {
126        *self.target.borrow_mut() = Some(target);
127        if let Some(root) = self.root.borrow().as_ref() {
128            root.send_message(CopyDropDownWrapperMsg::Open);
129        }
130    }
131
132    pub fn hide(&self) -> ApiResult<()> {
133        if let Some(root) = self.root.borrow().as_ref() {
134            root.send_message(CopyDropDownWrapperMsg::Close);
135        }
136        Ok(())
137    }
138
139    pub fn __set_model(&self, parent: &PerspectiveViewerElement) {
140        self.set_config_model(&parent.session, &parent.renderer, &parent.presentation)
141    }
142
143    pub fn connected_callback(&self) {}
144}
145
146impl CopyDropDownMenuElement {
147    pub fn new_from_model(
148        session: &Session,
149        renderer: &Renderer,
150        presentation: &Presentation,
151    ) -> Self {
152        let dropdown = global::document()
153            .create_element("perspective-copy-menu")
154            .unwrap()
155            .unchecked_into::<HtmlElement>();
156
157        let elem = Self::new(dropdown);
158        elem.set_config_model(session, renderer, presentation);
159        elem
160    }
161
162    pub fn set_config_model(
163        &self,
164        session: &Session,
165        renderer: &Renderer,
166        presentation: &Presentation,
167    ) {
168        let callback = Callback::from({
169            let session = session.clone();
170            let renderer = renderer.clone();
171            let presentation = presentation.clone();
172            let target = self.target.clone();
173            let root = self.root.clone();
174            move |x: ExportFile| {
175                let session = session.clone();
176                let renderer = renderer.clone();
177                let presentation = presentation.clone();
178                let target = target.clone();
179                let root = root.clone();
180                spawn_local(async move {
181                    let mime = x.method.mimetype(x.is_chart);
182                    let task = export_method_to_blob(&session, &renderer, &presentation, x.method);
183                    let result = copy_to_clipboard(task, mime).await;
184                    let r = (|| -> ApiResult<()> {
185                        result?;
186                        *target.borrow_mut() = None;
187                        if let Some(root) = root.borrow().as_ref() {
188                            root.send_message(CopyDropDownWrapperMsg::Close);
189                        }
190                        Ok(())
191                    })();
192                    if let Err(e) = r {
193                        web_sys::console::warn_1(&e.into());
194                    }
195                })
196            }
197        });
198
199        let renderer = renderer.clone();
200        let init = ShadowRootInit::new(ShadowRootMode::Open);
201        let shadow_root = self
202            .elem
203            .attach_shadow(&init)
204            .unwrap()
205            .unchecked_into::<Element>();
206
207        let props = yew::props!(CopyDropDownWrapperProps {
208            renderer,
209            callback,
210            target: self.target.clone(),
211            custom_element: self.elem.clone()
212        });
213
214        let handle = yew::Renderer::with_root_and_props(shadow_root, props).render();
215        *self.root.borrow_mut() = Some(handle);
216    }
217}