perspective_viewer/custom_elements/
copy_dropdown.rs1use 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}