Skip to content

Commit

Permalink
Adds auto-resizing to <perspective-viewer> and `<perspective-worksp…
Browse files Browse the repository at this point in the history
…ace>`
  • Loading branch information
texodus committed Dec 2, 2021
1 parent 1fe8a58 commit 87470f5
Show file tree
Hide file tree
Showing 13 changed files with 172 additions and 56 deletions.
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

0 comments on commit 87470f5

Please sign in to comment.