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