Skip to content

Commit

Permalink
Merge pull request #2411 from finos/intersection-observer
Browse files Browse the repository at this point in the history
Add `setAutoPause()` method
  • Loading branch information
texodus authored Nov 1, 2023
2 parents 9415075 + 217e817 commit 6006c1b
Show file tree
Hide file tree
Showing 9 changed files with 276 additions and 76 deletions.
Binary file added default.profraw
Binary file not shown.
99 changes: 24 additions & 75 deletions rust/perspective-viewer/src/rust/custom_elements/viewer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,78 +33,6 @@ use crate::session::Session;
use crate::utils::*;
use crate::*;

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

impl ResizeObserverHandle {
fn new(elem: &HtmlElement, renderer: &Renderer, root: &AppHandle<PerspectiveViewer>) -> Self {
let on_resize = root.callback(|()| PerspectiveViewerMsg::Resize);
let mut state = ResizeObserverState {
elem: elem.clone(),
renderer: renderer.clone(),
width: elem.offset_width(),
height: elem.offset_height(),
on_resize,
};

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);
Self {
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,
on_resize: Callback<()>,
}

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 {
clone!(self.on_resize, self.renderer);
ApiFuture::spawn(async move {
renderer.resize().await?;
on_resize.emit(());
Ok(())
});
}

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

/// A `customElements` class which encapsulates both the `<perspective-viewer>`
/// public API, as well as the Rust component state.
///
Expand All @@ -131,6 +59,7 @@ pub struct PerspectiveViewerElement {
elem: HtmlElement,
root: Rc<RefCell<Option<AppHandle<PerspectiveViewer>>>>,
resize_handle: Rc<RefCell<Option<ResizeObserverHandle>>>,
intersection_handle: Rc<RefCell<Option<IntersectionObserverHandle>>>,
session: Session,
renderer: Renderer,
presentation: Presentation,
Expand Down Expand Up @@ -192,6 +121,7 @@ impl PerspectiveViewerElement {
renderer,
presentation,
resize_handle: Rc::new(RefCell::new(Some(resize_handle))),
intersection_handle: Rc::new(RefCell::new(None)),
_events: events,
_subscriptions: Rc::new(update_sub),
}
Expand Down Expand Up @@ -485,13 +415,12 @@ impl PerspectiveViewerElement {
ApiFuture::new(async move { renderer.resize().await })
}

/// Sets the auto-size behavior of this component. When `true`, this
/// 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.
/// - `autosize` Whether to enable `auto-size` behavior or not.
#[wasm_bindgen(js_name = "setAutoSize")]
pub fn set_auto_size(&mut self, autosize: bool) {
if autosize {
Expand All @@ -506,6 +435,26 @@ impl PerspectiveViewerElement {
}
}

/// Sets the auto-pause behavior of this component. When `true`, this
/// `<perspective-viewer>` will register an `IntersectionObserver` on
/// itself and call `pause()` whenever its viewport visibility changes.
///
/// # Arguments
/// - `autopause` Whether to enable `auto-pause` behavior or not.
#[wasm_bindgen(js_name = "setAutoPause")]
pub fn set_auto_pause(&mut self, autopause: bool) {
if autopause {
let handle = Some(IntersectionObserverHandle::new(
&self.elem,
&self.session,
&self.renderer,
));
*self.intersection_handle.borrow_mut() = handle;
} else {
*self.intersection_handle.borrow_mut() = None;
}
}

/// Get this viewer's edit port for the currently loaded `Table`.
#[wasm_bindgen(js_name = "getEditPort")]
pub fn get_edit_port(&self) -> Result<f64, JsValue> {
Expand Down
32 changes: 32 additions & 0 deletions rust/perspective-viewer/src/rust/js/intersection_observer.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
// ┃ This file is part of the Perspective library, distributed under the terms ┃
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛

use wasm_bindgen::prelude::*;

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

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

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

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

pub type IntersectionObserverEntry;

#[wasm_bindgen(method, getter, js_name = "isIntersecting")]
pub fn is_intersecting(this: &IntersectionObserverEntry) -> bool;
}
2 changes: 2 additions & 0 deletions rust/perspective-viewer/src/rust/js/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
mod clipboard;
pub mod clipboard_item;
mod intersection_observer;
mod mimetype;
pub mod perspective;
pub mod plugin;
Expand All @@ -26,6 +27,7 @@ mod testing;
mod tests;

pub use self::clipboard::*;
pub use self::intersection_observer::*;
pub use self::mimetype::*;
pub use self::perspective::*;
pub use self::plugin::*;
Expand Down
83 changes: 83 additions & 0 deletions rust/perspective-viewer/src/rust/model/intersection_observer.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
// ┃ This file is part of the Perspective library, distributed under the terms ┃
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛

use wasm_bindgen::prelude::*;
use wasm_bindgen::JsCast;
use web_sys::*;

use crate::config::*;
use crate::js::*;
use crate::model::*;
use crate::renderer::*;
use crate::session::Session;
use crate::utils::*;
use crate::*;

pub struct IntersectionObserverHandle {
elem: HtmlElement,
observer: IntersectionObserver,
_callback: Closure<dyn FnMut(js_sys::Array)>,
}

impl IntersectionObserverHandle {
pub fn new(elem: &HtmlElement, session: &Session, renderer: &Renderer) -> Self {
clone!(session, renderer);
let _callback = (move |xs: js_sys::Array| {
let intersect = xs
.get(0)
.unchecked_into::<IntersectionObserverEntry>()
.is_intersecting();

clone!(session, renderer);
let state = IntersectionObserverState { session, renderer };
ApiFuture::spawn(state.set_pause(intersect));
})
.into_closure_mut();

let func = _callback.as_ref().unchecked_ref::<js_sys::Function>();
let observer = IntersectionObserver::new(func);
observer.observe(elem);
Self {
elem: elem.clone(),
_callback,
observer,
}
}
}

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

struct IntersectionObserverState {
session: Session,
renderer: Renderer,
}

impl IntersectionObserverState {
async fn set_pause(self, intersect: bool) -> ApiResult<()> {
if intersect {
if self.session.set_pause(false) {
tracing::error!("Shellac-ed");
self.update_and_render(ViewConfigUpdate::default()).await?;
}
} else {
self.session.set_pause(true);
};

Ok(())
}
}

derive_model!(Renderer, Session for IntersectionObserverState);
4 changes: 4 additions & 0 deletions rust/perspective-viewer/src/rust/model/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,14 +70,18 @@ mod copy_export;
mod export_app;
mod export_method;
mod get_viewer_config;
mod intersection_observer;
mod plugin_config;
mod resize_observer;
mod structural;
mod update_and_render;

pub use self::columns_iter_set::*;
pub use self::copy_export::*;
pub use self::export_method::*;
pub use self::get_viewer_config::*;
pub use self::intersection_observer::*;
pub use self::plugin_config::*;
pub use self::resize_observer::*;
pub use self::structural::*;
pub use self::update_and_render::*;
98 changes: 98 additions & 0 deletions rust/perspective-viewer/src/rust/model/resize_observer.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
// ┃ This file is part of the Perspective library, distributed under the terms ┃
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛

use wasm_bindgen::prelude::*;
use wasm_bindgen::JsCast;
use web_sys::*;
use yew::prelude::*;

use crate::components::viewer::{PerspectiveViewer, PerspectiveViewerMsg};
use crate::js::*;
use crate::renderer::*;
use crate::utils::*;
use crate::*;

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

impl ResizeObserverHandle {
pub fn new(
elem: &HtmlElement,
renderer: &Renderer,
root: &AppHandle<PerspectiveViewer>,
) -> Self {
let on_resize = root.callback(|()| PerspectiveViewerMsg::Resize);
let mut state = ResizeObserverState {
elem: elem.clone(),
renderer: renderer.clone(),
width: elem.offset_width(),
height: elem.offset_height(),
on_resize,
};

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);
Self {
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,
on_resize: Callback<()>,
}

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 {
clone!(self.on_resize, self.renderer);
ApiFuture::spawn(async move {
renderer.resize().await?;
on_resize.emit(());
Ok(())
});
}

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

0 comments on commit 6006c1b

Please sign in to comment.