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