Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add auto-resizing to <perspective-viewer> #1633

Merged
merged 1 commit into from
Dec 2, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/js/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,7 @@ window.addEventListener("DOMContentLoaded", async function() {
container.style.display = "flex";
await psp1.restore(config_defaults(EXAMPLES[key].config));
await psp1.toggleConfig(true);
await psp1.notifyResize();
await psp1.notifyResize(true);
container.style.opacity = 1;
container.style.pointerEvents = "";
});
Expand Down
31 changes: 2 additions & 29 deletions packages/perspective-jupyterlab/src/js/psp_widget.js
Original file line number Diff line number Diff line change
Expand Up @@ -86,31 +86,17 @@ export class PerspectiveWidget extends Widget {
*/

onAfterShow(msg) {
this.notifyResize();
this.viewer.notifyResize(true);
super.onAfterShow(msg);
}

/**
* Lumino: widget resize
*
*/

onResize(msg) {
this.notifyResize();
super.onResize(msg);
}

onActivateRequest(msg) {
if (this.isAttached) {
this.viewer.focus();
}
super.onActivateRequest(msg);
}

async notifyResize() {
await this.viewer.notifyResize();
}

async toggleConfig() {
await this.viewer.toggleConfig();
}
Expand Down Expand Up @@ -332,20 +318,7 @@ export class PerspectiveWidget extends Widget {
div.style.setProperty("display", "flex");
div.style.setProperty("flex-direction", "row");
node.appendChild(div);
if (!viewer.notifyResize) {
console.warn("Warning: not bound to real element");
} else {
const resize_observer = new MutationObserver((mutations) => {
if (mutations.some((x) => x.attributeName === "style")) {
viewer.notifyResize.call(viewer);
}
});

resize_observer.observe(node, {
attributes: true,
});
viewer.toggleConfig();
}
viewer.toggleConfig(true);

return viewer;
}
Expand Down
4 changes: 2 additions & 2 deletions packages/perspective-jupyterlab/test/js/resize.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,15 +41,15 @@ utils.with_server({}, () => {
"position:absolute;top:0;left:0;width:300px;height:300px";
await document
.querySelector("perspective-viewer")
.notifyResize();
.notifyResize(true);
});

return await page.evaluate(async () => {
document.querySelector(".PSPContainer").style =
"position:absolute;top:0;left:0;width:800px;height:600px";
await document
.querySelector("perspective-viewer")
.notifyResize();
.notifyResize(true);
return window.__WIDGET__.viewer.innerHTML;
});
}
Expand Down
2 changes: 1 addition & 1 deletion packages/perspective-workspace/src/js/workspace/tabbar.js
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ export class PerspectiveTabBar extends TabBar {

_addEventListeners() {
this.tabActivateRequested.connect(() => {
this.currentTitle.owner.notifyResize();
this.currentTitle.owner.viewer.notifyResize(true);
});
this.node.addEventListener("dblclick", this);
this.node.addEventListener("contextmenu", this);
Expand Down
11 changes: 0 additions & 11 deletions packages/perspective-workspace/src/js/workspace/widget.js
Original file line number Diff line number Diff line change
Expand Up @@ -148,15 +148,4 @@ export class PerspectiveViewerWidget extends Widget {
}
await this.viewer.delete();
}

onResize(msg) {
this.notifyResize();
super.onResize(msg);
}

async notifyResize() {
if (this.isVisible) {
await this.viewer.notifyResize();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -472,7 +472,6 @@ export class PerspectiveWorkspace extends DiscreteSplitPanel {
this._maximizedWidget = widget;
this.dockpanel.mode = "single-document";
this.dockpanel.activateWidget(widget);
widget.notifyResize();
}

_unmaximize() {
Expand Down
96 changes: 95 additions & 1 deletion rust/perspective-viewer/src/rust/custom_elements/viewer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ use crate::custom_elements::expression_editor::ExpressionEditorElement;
use crate::dragdrop::*;
use crate::js::perspective::*;
use crate::js::plugin::JsPerspectiveViewerPlugin;
use crate::js::resize_observer::*;
use crate::renderer::*;
use crate::session::Session;
use crate::utils::*;
Expand All @@ -35,6 +36,73 @@ use wasm_bindgen_futures::JsFuture;
use web_sys::*;
use yew::prelude::*;

struct ResizeObserverHandle {
elem: HtmlElement,
observer: ResizeObserver,
_callback: Closure<dyn FnMut(js_sys::Array)>,
}

impl ResizeObserverHandle {
fn new(elem: &HtmlElement, renderer: &Renderer) -> ResizeObserverHandle {
let mut state = ResizeObserverState {
elem: elem.clone(),
renderer: renderer.clone(),
width: elem.offset_width(),
height: elem.offset_height(),
};

let _callback = (move |xs| state.on_resize(&xs)).into_closure_mut();
let func = _callback.as_ref().unchecked_ref::<js_sys::Function>();
let observer = ResizeObserver::new(func);
observer.observe(elem);
ResizeObserverHandle {
elem: elem.clone(),
_callback,
observer,
}
}
}

impl Drop for ResizeObserverHandle {
fn drop(&mut self) {
self.observer.unobserve(&self.elem);
}
}

struct ResizeObserverState {
elem: HtmlElement,
renderer: Renderer,
width: i32,
height: i32,
}

impl ResizeObserverState {
fn on_resize(&mut self, entries: &js_sys::Array) {
let is_visible = self
.elem
.offset_parent()
.map(|x| !x.is_null())
.unwrap_or(false);

for y in entries.iter() {
let entry: ResizeObserverEntry = y.unchecked_into();
let content = entry.content_rect();
let content_width = content.width().floor() as i32;
let content_height = content.height().floor() as i32;
let resized = self.width != content_width || self.height != content_height;
if resized && is_visible {
let renderer = self.renderer.clone();
let _ = promisify_ignore_view_delete(
async move { renderer.resize().await },
);
}

self.width = content_width;
self.height = content_height;
}
}
}

/// A `customElements` external API.
#[wasm_bindgen]
#[derive(Clone)]
Expand All @@ -45,6 +113,7 @@ pub struct PerspectiveViewerElement {
renderer: Renderer,
subscriptions: Rc<[Subscription; 4]>,
expression_editor: Rc<RefCell<Option<ExpressionEditorElement>>>,
resize_handle: Rc<RefCell<Option<ResizeObserverHandle>>>,
}

#[wasm_bindgen]
Expand Down Expand Up @@ -105,13 +174,15 @@ impl PerspectiveViewerElement {
renderer.on_limits_changed.add_listener(callback)
};

let resize_handle = ResizeObserverHandle::new(&elem, &renderer);
PerspectiveViewerElement {
elem,
root,
session,
renderer,
expression_editor: Rc::new(RefCell::new(None)),
subscriptions: Rc::new([plugin_sub, update_sub, limit_sub, view_sub]),
resize_handle: Rc::new(RefCell::new(Some(resize_handle))),
}
}

Expand Down Expand Up @@ -245,6 +316,7 @@ impl PerspectiveViewerElement {
session
.set_update_column_defaults(&mut view_config, &renderer.metadata());
}

session.update_view_config(view_config);
let settings = Some(settings.clone());
let draw_task = renderer.draw(async {
Expand Down Expand Up @@ -367,11 +439,33 @@ impl PerspectiveViewerElement {
}

/// Recalculate the viewer's dimensions and redraw.
pub fn js_resize(&self) -> js_sys::Promise {
pub fn js_resize(&self, force: bool) -> js_sys::Promise {
if !force && self.resize_handle.borrow().is_some() {
let msg: JsValue = "`notifyResize(false)` called, disabling auto-size. It can be re-enabled with `setAutoSize(true)`.".into();
web_sys::console::warn_1(&msg);
*self.resize_handle.borrow_mut() = None;
}

let renderer = self.renderer.clone();
promisify_ignore_view_delete(async move { renderer.resize().await })
}

/// Sets the auto-size behavior of this component. When `true`, this
/// `<perspective-viewer>` will register a `ResizeObserver` on itself and
/// call `resize()` whenever its own dimensions change.
///
/// # Arguments
/// - `autosize` Whether to register a `ResizeObserver` on this element or
/// not.
pub fn js_set_auto_size(&mut self, autosize: bool) {
if autosize {
let handle = Some(ResizeObserverHandle::new(&self.elem, &self.renderer));
*self.resize_handle.borrow_mut() = handle;
} else {
*self.resize_handle.borrow_mut() = None;
}
}

/// Get this viewer's edit port for the currently loaded `Table`.
pub fn js_get_edit_port(&self) -> Result<f64, JsValue> {
self.session
Expand Down
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 @@ -10,3 +10,4 @@ pub mod monaco;
pub mod perspective;
// pub mod perspective_viewer;
pub mod plugin;
pub mod resize_observer;
33 changes: 33 additions & 0 deletions rust/perspective-viewer/src/rust/js/resize_observer.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.

// `rustfmt` removes `async` from extern blocks in rust stable
// [issue](https://github.com/rust-lang/rustfmt/issues/4288)

use wasm_bindgen::prelude::*;
// use web_sys::HtmlElement;

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

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

#[wasm_bindgen(method)]
pub fn observe(this: &ResizeObserver, elem: &web_sys::HtmlElement);

#[wasm_bindgen(method)]
pub fn unobserve(this: &ResizeObserver, elem: &web_sys::HtmlElement);

pub type ResizeObserverEntry;

#[wasm_bindgen(method, getter, js_name = "contentRect")]
pub fn content_rect(this: &ResizeObserverEntry) -> web_sys::DomRectReadOnly;

}
2 changes: 2 additions & 0 deletions rust/perspective-viewer/src/rust/renderer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -212,8 +212,10 @@ impl Renderer {

pub async fn resize(&self) -> Result<JsValue, JsValue> {
let draw_mutex = self.draw_lock();
let timer = self.render_timer();
draw_mutex
.debounce(async {
set_timeout(timer.get_avg()).await?;
let jsplugin = self.get_active_plugin()?;
jsplugin.resize().await?;
Ok(JsValue::from(true))
Expand Down
39 changes: 32 additions & 7 deletions rust/perspective-viewer/src/ts/viewer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,24 +121,49 @@ export class PerspectiveViewerElement extends HTMLElement {

/**
* Redraw this `<perspective-viewer>` and plugin when its dimensions or
* visibility have been updated. This method _must_ be called in these
* cases, and will not by default respond to dimension or style changes to
* its parent container. `notifyResize()` does not recalculate the current
* `View`, but all plugins will re-request the data window (which itself
* may be smaller or larger due to resize).
* visibility has been updated. By default, `<perspective-viewer>` will
* auto-size when its own dimensions change, so this method need not be
* called; when disabled via `setAutoSize(false)` however, this method
* _must_ be called, and will not respond to dimension or style changes to
* its parent container otherwise. `notifyResize()` does not recalculate
* the current `View`, but all plugins will re-request the data window
* (which itself may be smaller or larger due to resize).
*
* @category Util
* @param force Whether to re-render, even if the dimenions have not
* changed. When set to `false` and auto-size is enabled (the defaults),
* calling this method will automatically disable auto-size.
* @returns A `Promise<void>` which resolves when this resize event has
* finished rendering.
* @example <caption>Bind `notfyResize()` to browser dimensions</caption>
* ```javascript
* const viewer = document.querySelector("perspective-viewer");
* viewer.setAutoSize(false);
* window.addEventListener("resize", () => viewer.notifyResize());
* ```
*/
async notifyResize(): Promise<void> {
async notifyResize(force = false): Promise<void> {
await this.load_wasm();
await this.instance.js_resize();
await this.instance.js_resize(force);
}

/**
* Determines the auto-size behavior. When `true` (the default), this
* element will re-render itself whenever its own dimensions change,
* utilizing a `ResizeObserver`; when `false`, you must explicitly call
* `notifyResize()` when the element's dimensions have changed.
*
* @category Util
* @param autosize Whether to re-render when this element's dimensions
* change.
* @example <caption>Disable auto-size</caption>
* ```javascript
* await viewer.setAutoSize(false);
* ```
*/
async setAutoSize(autosize = true): Promise<void> {
await this.load_wasm();
await this.instance.js_set_auto_size(autosize);
}

/**
Expand Down
3 changes: 2 additions & 1 deletion rust/perspective-viewer/test/js/simple_tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,8 @@ exports.default = function (get_contents = get_contents_default) {
column_pivots: ["Category", "Sub-Category"],
settings: true,
});
await viewer.notifyResize();

await viewer.notifyResize(true);
});

return await get_contents(page);
Expand Down
3 changes: 1 addition & 2 deletions tools/perspective-test/src/js/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -469,8 +469,7 @@ test.capture = function capture(
const viewer =
document.querySelector("perspective-viewer");
if (viewer) {
viewer.restore(x);
await viewer.notifyResize?.();
await viewer.restore(x);
await viewer.toggleConfig?.(false);
}
}
Expand Down