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