Skip to content

Commit

Permalink
Add copy dropdown menu
Browse files Browse the repository at this point in the history
  • Loading branch information
texodus committed Mar 8, 2022
1 parent d0a1823 commit 85997ce
Show file tree
Hide file tree
Showing 24 changed files with 383 additions and 147 deletions.
33 changes: 33 additions & 0 deletions rust/perspective-viewer/src/rust/components/copy_dropdown.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
////////////////////////////////////////////////////////////////////////////////
//
// Copyright (c) 2018, the Perspective Authors.
//
// This file is part of the Perspective library, distributed under the terms
// of the Apache License 2.0. The full license can be found in the LICENSE
// file.

use super::containers::dropdown_menu::*;
use crate::model::*;

pub fn get_menu_items(has_render: bool) -> Vec<CopyDropDownMenuItem> {
vec![
CopyDropDownMenuItem::OptGroup(
"Current View",
if has_render {
vec![ExportMethod::Csv, ExportMethod::Json, ExportMethod::Png]
} else {
vec![ExportMethod::Csv, ExportMethod::Json]
},
),
CopyDropDownMenuItem::OptGroup(
"All",
vec![ExportMethod::CsvAll, ExportMethod::JsonAll],
),
CopyDropDownMenuItem::OptGroup("Config", vec![ExportMethod::JsonConfig]),
]
}

pub type CopyDropDownMenu = DropDownMenu<ExportMethod>;
pub type CopyDropDownMenuProps = DropDownMenuProps<ExportMethod>;
pub type CopyDropDownMenuMsg = DropDownMenuMsg;
pub type CopyDropDownMenuItem = DropDownMenuItem<ExportMethod>;
1 change: 1 addition & 0 deletions rust/perspective-viewer/src/rust/components/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
//! `components` contains all Yew `Component` types, but only exports the 4 necessary
//! for public Custom Elements. The rest are internal components of these 4.

pub mod copy_dropdown;
pub mod export_dropdown;
pub mod expression_editor;
pub mod filter_dropdown;
Expand Down
48 changes: 30 additions & 18 deletions rust/perspective-viewer/src/rust/components/status_bar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,13 @@

use crate::components::containers::select::*;
use crate::components::status_bar_counter::StatusBarRowsCounter;
use crate::custom_elements::copy_dropdown::*;
use crate::custom_elements::export_dropdown::*;
use crate::renderer::*;
use crate::session::*;
use crate::utils::*;
use crate::*;
use wasm_bindgen_futures::spawn_local;

use web_sys::*;
use yew::prelude::*;

Expand Down Expand Up @@ -41,7 +42,7 @@ impl PartialEq for StatusBarProps {
pub enum StatusBarMsg {
Reset(bool),
Export,
Copy(bool),
Copy,
SetThemeConfig((Vec<String>, Option<usize>)),
SetTheme(String),
TableStatsChanged,
Expand All @@ -54,7 +55,9 @@ pub struct StatusBar {
theme: Option<String>,
themes: Vec<String>,
export_ref: NodeRef,
copy_ref: NodeRef,
export_dropdown: Option<ExportDropDownMenuElement>,
copy_dropdown: Option<CopyDropDownMenuElement>,
_sub: [Subscription; 4],
}

Expand Down Expand Up @@ -91,6 +94,8 @@ impl Component for StatusBar {
_sub,
theme: None,
themes: vec![],
copy_dropdown: None,
copy_ref: NodeRef::default(),
export_dropdown: None,
export_ref: NodeRef::default(),
is_updating: 0,
Expand Down Expand Up @@ -128,20 +133,25 @@ impl Component for StatusBar {
false
}
StatusBarMsg::Export => {
let session = ctx.props().session.clone();
let renderer = ctx.props().renderer.clone();
let export_dropdown = ExportDropDownMenuElement::new(session, renderer);
let target = self.export_ref.cast::<HtmlElement>().unwrap();
export_dropdown.open(target);
self.export_dropdown = Some(export_dropdown);
self.export_dropdown
.get_or_insert_with(|| {
let session = ctx.props().session.clone();
let renderer = ctx.props().renderer.clone();
ExportDropDownMenuElement::new(session, renderer)
})
.open(target);
false
}
StatusBarMsg::Copy(flat) => {
let session = ctx.props().session.clone();
spawn_local(async move {
session.copy_to_clipboard(flat).await.expect("Copy failed");
});

StatusBarMsg::Copy => {
let target = self.copy_ref.cast::<HtmlElement>().unwrap();
self.copy_dropdown
.get_or_insert_with(|| {
let session = ctx.props().session.clone();
let renderer = ctx.props().renderer.clone();
CopyDropDownMenuElement::new(session, renderer)
})
.open(target);
false
}
}
Expand All @@ -161,10 +171,7 @@ impl Component for StatusBar {
.callback(|event: MouseEvent| StatusBarMsg::Reset(event.shift_key()));

let export = ctx.link().callback(|_: MouseEvent| StatusBarMsg::Export);

let copy = ctx
.link()
.callback(|event: MouseEvent| StatusBarMsg::Copy(event.shift_key()));
let copy = ctx.link().callback(|_: MouseEvent| StatusBarMsg::Copy);

let theme_button = match &self.theme {
None => html! {},
Expand Down Expand Up @@ -211,7 +218,12 @@ impl Component for StatusBar {

<span>{ "Export" }</span>
</span>
<span id="copy" class="button" onmousedown={ copy }>
<span
ref={ self.copy_ref.clone() }
id="copy"
class="button"
onmousedown={ copy }>

<span>{ "Copy" }</span>
</span>
{ theme_button }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ pub fn test_callbacks_invoked() {
status_bar.send_message(StatusBarMsg::Export);
assert_eq!(token.get(), 0);
let status_bar = link.borrow().clone().unwrap();
status_bar.send_message(StatusBarMsg::Copy(false));
status_bar.send_message(StatusBarMsg::Copy);
assert_eq!(token.get(), 0);
let status_bar = link.borrow().clone().unwrap();
status_bar.send_message(StatusBarMsg::Reset(false));
Expand Down
87 changes: 87 additions & 0 deletions rust/perspective-viewer/src/rust/custom_elements/copy_dropdown.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
////////////////////////////////////////////////////////////////////////////////
//
// Copyright (c) 2018, the Perspective Authors.
//
// This file is part of the Perspective library, distributed under the terms
// of the Apache License 2.0. The full license can be found in the LICENSE
// file.

use crate::components::copy_dropdown::*;
use crate::custom_elements::modal::*;
use crate::model::*;
use crate::renderer::Renderer;
use crate::session::Session;
use crate::utils::*;

use js_intern::*;
use std::cell::RefCell;
use std::rc::Rc;
use wasm_bindgen::prelude::*;
use wasm_bindgen::JsCast;
use wasm_bindgen_futures::spawn_local;
use web_sys::*;
use yew::prelude::*;

#[wasm_bindgen]
#[derive(Clone)]
pub struct CopyDropDownMenuElement {
modal: ModalElement<CopyDropDownMenu>,
target: Rc<RefCell<Option<HtmlElement>>>,
}

impl ResizableMessage for <CopyDropDownMenu as Component>::Message {
fn resize(y: i32, x: i32, _: bool) -> Self {
CopyDropDownMenuMsg::SetPos(y, x)
}
}

impl CopyDropDownMenuElement {
pub fn new(session: Session, renderer: Renderer) -> CopyDropDownMenuElement {
let document = window().unwrap().document().unwrap();
let dropdown = document
.create_element("perspective-copy-dropdown")
.unwrap()
.unchecked_into::<HtmlElement>();

let modal_rc: Rc<RefCell<Option<ModalElement<CopyDropDownMenu>>>> =
Default::default();

let callback = Callback::from({
let modal_rc = modal_rc.clone();
let renderer = renderer.clone();
move |x: ExportMethod| {
let js_task = (&session, &renderer).export_method_to_jsvalue(x);
let copy_task = copy_to_clipboard(js_task, x.mimetype());
let modal = modal_rc.borrow().clone().unwrap();
spawn_local(async move {
let result = copy_task.await;
crate::js_log_maybe! {
result?;
modal.hide()?;
}
})
}
});

let plugin = renderer.get_active_plugin().unwrap();
let has_render = js_sys::Reflect::has(&plugin, js_intern!("render")).unwrap();
let values = Rc::new(get_menu_items(has_render));
let props = CopyDropDownMenuProps { values, callback };
let modal = ModalElement::new(dropdown, props, true);
*modal_rc.borrow_mut() = Some(modal.clone());
CopyDropDownMenuElement {
modal,
target: Default::default(),
}
}

pub fn open(&self, target: HtmlElement) {
self.modal.open(target, None);
}

pub fn hide(&self) -> Result<(), JsValue> {
self.modal.hide()
}

pub fn connected_callback(&self) {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ impl ExportDropDownMenuElement {
pub fn new(session: Session, renderer: Renderer) -> ExportDropDownMenuElement {
let document = window().unwrap().document().unwrap();
let dropdown = document
.create_element("perspective-filter-dropdown")
.create_element("perspective-export-dropdown")
.unwrap()
.unchecked_into::<HtmlElement>();

Expand Down
1 change: 1 addition & 0 deletions rust/perspective-viewer/src/rust/custom_elements/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
// of the Apache License 2.0. The full license can be found in the LICENSE
// file.

pub mod copy_dropdown;
pub mod export_dropdown;
pub mod expression_editor;
pub mod filter_dropdown;
Expand Down
11 changes: 9 additions & 2 deletions rust/perspective-viewer/src/rust/custom_elements/viewer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -415,9 +415,16 @@ impl PerspectiveViewerElement {
/// - `flat` Whether to use the current `ViewConfig` to generate this data, or use
/// the default.
pub fn js_copy(&self, flat: bool) -> js_sys::Promise {
let session = self.session.clone();
let method = if flat {
ExportMethod::CsvAll
} else {
ExportMethod::Csv
};

let js_task = self.export_method_to_jsvalue(method);
let copy_task = copy_to_clipboard(js_task, MimeType::TextPlain);
future_to_promise(async move {
session.copy_to_clipboard(flat).await?;
copy_task.await?;
Ok(JsValue::UNDEFINED)
})
}
Expand Down
17 changes: 17 additions & 0 deletions rust/perspective-viewer/src/rust/js/clipboard_item.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
////////////////////////////////////////////////////////////////////////////////
//
// Copyright (c) 2018, the Perspective Authors.
//
// This file is part of the Perspective library, distributed under the terms
// of the Apache License 2.0. The full license can be found in the LICENSE
// file.

use wasm_bindgen::prelude::*;

#[wasm_bindgen(inline_js = "export const ClipboardItem = window.ClipboardItem")]
extern "C" {
pub type ClipboardItem;

#[wasm_bindgen(constructor, js_class = "ClipboardItem")]
pub fn new(files: &js_sys::Object) -> ClipboardItem;
}
1 change: 1 addition & 0 deletions rust/perspective-viewer/src/rust/js/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
// of the Apache License 2.0. The full license can be found in the LICENSE
// file.

pub mod clipboard_item;
pub mod monaco;
pub mod perspective;
// pub mod perspective_viewer;
Expand Down
4 changes: 2 additions & 2 deletions rust/perspective-viewer/src/rust/js/resize_observer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@
use wasm_bindgen::prelude::*;
// use web_sys::HtmlElement;

#[wasm_bindgen(inline_js = "export default ResizeObserver")]
#[wasm_bindgen(inline_js = "export const ResizeObserver = window.ResizeObserver")]
extern "C" {
pub type ResizeObserver;

#[wasm_bindgen(constructor, js_class = "default")]
#[wasm_bindgen(constructor, js_class = "ResizeObserver")]
pub fn new(callback: &js_sys::Function) -> ResizeObserver;

#[wasm_bindgen(method)]
Expand Down
22 changes: 15 additions & 7 deletions rust/perspective-viewer/src/rust/model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ pub trait SessionRendererModel {

fn html_as_jsvalue(
&self,
) -> Pin<Box<dyn Future<Output = Result<JsValue, JsValue>>>> {
) -> Pin<Box<dyn Future<Output = Result<web_sys::Blob, JsValue>>>> {
let view_config = self.get_viewer_config();
let session = self.session().clone();
Box::pin(async move {
Expand Down Expand Up @@ -108,7 +108,7 @@ window.viewer.restore(JSON.parse(window.layout.textContent));
</html>
", base64::encode(arrow), js_config));
let array = [html].iter().collect::<js_sys::Array>();
Ok(web_sys::Blob::new_with_u8_array_sequence(&array)?.into())
Ok(web_sys::Blob::new_with_u8_array_sequence(&array)?)
})
}

Expand Down Expand Up @@ -140,19 +140,23 @@ window.viewer.restore(JSON.parse(window.layout.textContent));

fn config_as_jsvalue(
&self,
) -> Pin<Box<dyn Future<Output = Result<JsValue, JsValue>>>> {
) -> Pin<Box<dyn Future<Output = Result<web_sys::Blob, JsValue>>>> {
let viewer_config = self.get_viewer_config();
Box::pin(async move {
viewer_config
let config = viewer_config
.await?
.encode(&Some(ViewerConfigEncoding::JSONString))
.encode(&Some(ViewerConfigEncoding::JSONString))?;
let array = [config].iter().collect::<js_sys::Array>();
let mut options = web_sys::BlobPropertyBag::new();
options.type_("text/plain");
web_sys::Blob::new_with_str_sequence_and_options(&array, &options)
})
}

fn export_method_to_jsvalue(
&self,
method: ExportMethod,
) -> Pin<Box<dyn Future<Output = Result<JsValue, JsValue>>>> {
) -> Pin<Box<dyn Future<Output = Result<web_sys::Blob, JsValue>>>> {
match method {
ExportMethod::Csv => {
let session = self.session().clone();
Expand Down Expand Up @@ -189,7 +193,11 @@ window.viewer.restore(JSON.parse(window.layout.textContent));
let render = js_sys::Reflect::get(&plugin, js_intern!("render"))?;
let render_fun = render.unchecked_into::<js_sys::Function>();
let png = render_fun.call0(&plugin)?;
JsFuture::from(png.unchecked_into::<js_sys::Promise>()).await
let result =
JsFuture::from(png.unchecked_into::<js_sys::Promise>())
.await?
.unchecked_into();
Ok(result)
})
}
ExportMethod::JsonConfig => {
Expand Down

0 comments on commit 85997ce

Please sign in to comment.