Skip to content

Commit

Permalink
Outline perspective-viewer-plugin web component and TypeScript inte…
Browse files Browse the repository at this point in the history
…rface
  • Loading branch information
texodus committed Sep 12, 2021
1 parent 6dd569a commit ddd6078
Show file tree
Hide file tree
Showing 8 changed files with 599 additions and 112 deletions.
384 changes: 356 additions & 28 deletions rust/perspective-viewer/README.md

Large diffs are not rendered by default.

9 changes: 1 addition & 8 deletions rust/perspective-viewer/src/rust/components/active_column.rs
Original file line number Diff line number Diff line change
Expand Up @@ -217,14 +217,7 @@ impl Component for ActiveColumn {
}
}
((label, Some(name)), Some(col_type)) => {
let min_cols = self
.props
.renderer
.get_active_plugin()
.unwrap()
.min_config_columns()
.unwrap_or(0);

let min_cols = self.props.renderer.metadata().min.unwrap_or(0);
let remove_column = if self.props.idx < min_cols {
None
} else {
Expand Down
2 changes: 1 addition & 1 deletion rust/perspective-viewer/src/rust/components/viewer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -294,4 +294,4 @@ fn dispatch_settings_event(
viewer_elem.dispatch_event(&event.unwrap()).unwrap();

Ok(())
}
}
64 changes: 1 addition & 63 deletions rust/perspective-viewer/src/rust/js/plugin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,71 +14,9 @@ use wasm_bindgen::prelude::*;
use super::perspective::JsPerspectiveView;

/// Perspective FFI
#[wasm_bindgen(inline_js = "
class DebugPlugin extends HTMLElement {
constructor() {
super();
}
get name() {
return 'Debug';
}
get select_mode() {
return 'select';
}
get min_config_columns() {
return undefined;
}
get config_column_names() {
return undefined;
}
update(...args) {
return this.draw(...args);
}
async draw(view) {
this.style.backgroundColor = '#fff';
let perspective_viewer = this.parentElement;
const csv = await view.to_csv({config: {delimiter: '|'}});
const css = `margin:0;overflow:scroll;position:absolute;width:100%;height:100%`;
this.innerHTML = `<pre style='${css}'>${csv}</pre>`;
}
async clear() {
this.innerHtml = '';
}
async resize() {}
async restyle() {}
save() {}
restore() {}
delete() {}
}
export function register_default_plugin_web_component() {
if (!window.__test_plugin__) {
window.__test_plugin__ = true;
customElements.define('perspective-viewer-debug', DebugPlugin);
}
}
")]
#[wasm_bindgen]
#[rustfmt::skip]
extern "C" {

#[wasm_bindgen(js_name = "register_default_plugin_web_component", catch)]
pub fn register_default_plugin_web_component() -> Result<(), JsValue>;

#[derive(Clone)]
pub type JsPerspectiveViewerPlugin;

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

use crate::js::plugin::register_default_plugin_web_component;
use crate::js::plugin::*;

use std::cell::RefCell;
Expand Down Expand Up @@ -108,10 +107,9 @@ impl PluginRegistry for LocalKey<Rc<RefCell<Vec<PluginRecord>>>> {
fn register_default() {
PLUGIN_REGISTRY.with(|plugins| {
if plugins.borrow().len() == 0 {
register_default_plugin_web_component().unwrap();
plugins.borrow_mut().push(PluginRecord {
name: "Debug".to_owned(),
tag_name: "perspective-viewer-debug".to_owned(),
tag_name: "perspective-viewer-plugin".to_owned(),
})
}
})
Expand Down
238 changes: 234 additions & 4 deletions rust/perspective-viewer/src/ts/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,9 @@
* @module perspective-viewer
*/

import type * as perspective from "@finos/perspective";
import "mobile-drag-drop-shadow-dom";
import init, * as internal from "../../dist/pkg/perspective_viewer.js";
import * as perspective from "@finos/perspective";

// There is no way to provide a default rejection handler within a promise and
// also not lock the await-er, so this module attaches a global handler to
Expand Down Expand Up @@ -456,8 +456,8 @@ export class PerspectiveViewerElement extends HTMLElement {
* `HTMLElement` will not have a `parentElement`.
*
* If no plugins have been registered (via `registerPlugin()`), calling
* `getPlugin()` will cause `perspective-viewer-debug` to be registered as a
* side effect.
* `getPlugin()` will cause `perspective-viewer-plugin` to be registered as
* a side effect.
*
* @category Plugin
* @param name Optionally a specific plugin name, defaulting to the current
Expand All @@ -474,7 +474,7 @@ export class PerspectiveViewerElement extends HTMLElement {
* Get all plugin custom element instances, in order of registration.
*
* If no plugins have been registered (via `registerPlugin()`), calling
* `getAllPlugins()` will cause `perspective-viewer-debug` to be registered
* `getAllPlugins()` will cause `perspective-viewer-plugin` to be registered
* as a side effect.
*
* @category Plugin
Expand All @@ -495,6 +495,225 @@ if (document.createElement("perspective-viewer").constructor === HTMLElement) {
);
}

/**
* The `IPerspectiveViewerPlugin` interface defines the necessary API for a
* `<perspective-viewer>` plugin, which also must be an `HTMLElement` via the
* Custom Elements API or otherwise. Rather than implement this API from
* scratch however, the simplest way is to inherit from
* `<perspective-viewer-plugin>`, which implements `IPerspectiveViewerPlugin`
* with non-offensive default implementations, where only the `draw()` and
* `name()` methods need be overridden to get started with a simple plugin.
*
* Note that plugins are frozen once a `<perspective-viewer>` has been
* instantiated, so generally new plugin code must be executed at the module
* level (if packaged as a library), or during application init to ensure global
* availability of a plugin.
*
* @example
* ```javascript
* const BasePlugin = customElements.get("perspective-viewer-plugin");
* class MyPlugin extends BasePlugin {
* get name() {
* return "My Plugin";
* }
* async draw(view) {
* const count = await view.num_rows();
* this.innerHTML = `View has ${count} rows`;
* }
* }
*
* customElements.define("my-plugin", MyPlugin);
* const Viewer = customElements.get("perspective-viewer");
* Viewer.registerPlugin("my-plugin");
* ```
* @noInheritDoc
*/
export interface IPerspectiveViewerPlugin extends HTMLElement {
/**
* The name for this plugin, which is used as both it's unique key for use
* as a parameter for the `plugin` field of a `ViewerConfig`, and as the
* display name for this plugin in the `<perspective-viewer>` UI.
*/
get name(): string;

/**
* Select mode determines how column add/remove buttons behave for this
* plugin. `"select"` mode exclusively selects the added column, removing
* other columns. `"toggle"` mode toggles the column on or off (dependent
* on column state), leaving existing columns alone.
*/
get select_mode(): "select" | "toggle" | undefined;

/**
* The minimum number of columns required for this plugin to operate.
* This mostly affects drag/drop and column remove button behavior,
* preventing the use from applying configs which violate this min.
*
* While this option can technically be `undefined` (as in the case of
* `@finos/perspective-viewer-datagrid`), doing so currently has nearly
* identical behavior to 1.
*/
get min_config_columns(): number | undefined;

/**
* The named column labels, if desired. Named columns behave differently
* in drag/drop mode than unnamed columns, having replace/swap behavior
* rather than insert. If provided, the length of `config_column_names`
* _must_ be `>= min_config_columns`, as this is assumed by the drag/drop
* logic.
*/
get config_column_names(): string[] | undefined;

/**
* Render this plugin using the provided `View`. While there is no
* provision to cancel a render in progress per se, calling a method on
* a `View` which has been deleted will throw an exception.
*
* @example
* ```
* async draw(view: perspective.View): Promise<void> {
* const csv = await view.to_csv();
* this.innerHTML = `<pre>${csv}</pre>`;
* }
* ```
*/
draw(view: perspective.View): Promise<void>;

/**
* Draw under the assumption that the `ViewConfig` has not changed since
* the previous call to `draw()`, but the underlying data has. Defaults to
* dispatch to `draw()`.
*
* @example
* ```javascript
* async update(view: perspective.View): Promise<void> {
* return this.draw(view);
* }
* ```
*/
update(view: perspective.View): Promise<void>;

/**
* Clear this plugin, though it is up to the discretion of the plugin
* author to determine what this means. Defaults to resetting this
* element's `innerHTML`, so be sure to override if you want custom
* behavior.
*
* @example
* ```javascript
* async clear(): Promise<void> {
* this.innerHTML = "";
* }
* ```
*/
clear(): Promise<void>;

/**
* Like `update()`, but for when the dimensions of the plugin have changed
* and the underlying data has not.
*/
resize(): Promise<void>;

/**
* Notify the plugin that the style environment has changed. Useful for
* plugins which read CSS styles via `window.getComputedStyle()`.
*/
restyle(): Promise<void>;

/**
* Save this plugin's state to a JSON-serializable value. While this value
* can be anything, it should work reciprocally with `restore()` to return
* this plugin's renderer to the same state, though potentially with a
* different `View`.
*
* `save()` should be used for user-persistent settings that are
* data-agnostic, so the user can have persistent view during refresh or
* reload. For example, `@finos/perspective-viewer-d3fc` uses
* `plugin_config` to remember the user-repositionable legend coordinates.
*/
save(): Promise<any>;

/**
* Restore this plugin to a state previously returned by `save()`.
*/
restore(config: any): Promise<void>;

/**
* Free any resources acquired by this plugin and prepare to be deleted.
*/
delete(): Promise<void>;
}

class PerspectiveViewerPluginElement
extends HTMLElement
implements IPerspectiveViewerPlugin
{
constructor() {
super();
}

get name(): string {
return "Debug";
}

get select_mode(): "select" | "toggle" {
return "select";
}

get min_config_columns(): number | undefined {
return undefined;
}

get config_column_names(): string[] | undefined {
return undefined;
}

async update(view: perspective.View): Promise<void> {
return this.draw(view);
}

async draw(view: perspective.View): Promise<void> {
this.style.backgroundColor = "#fff";
const csv = await view.to_csv({config: {delimiter: "|"}});
const css = `margin:0;overflow:scroll;position:absolute;width:100%;height:100%`;
this.innerHTML = `<pre style='${css}'>${csv}</pre>`;
}

async clear(): Promise<void> {
this.innerHTML = "";
}

async resize(): Promise<void> {
// Not Implemented
}

async restyle(): Promise<void> {
// Not Implemented
}

async save(): Promise<any> {
// Not Implemented
}

async restore(): Promise<void> {
// Not Implemented
}

async delete(): Promise<void> {
// Not Implemented
}
}

if (
document.createElement("perspective-viewer-plugin").constructor ===
HTMLElement
) {
window.customElements.define(
"perspective-viewer-plugin",
PerspectiveViewerPluginElement
);
}

class PerspectiveColumnStyleElement extends HTMLElement {
private instance: internal.PerspectiveColumnStyleElement;

Expand Down Expand Up @@ -551,5 +770,16 @@ declare global {
tagName: "perspective-viewer",
options?: ElementCreationOptions
): PerspectiveViewerElement;
createElement(
tagName: "perspective-viewer-plugin",
options?: ElementCreationOptions
): PerspectiveViewerPluginElement;
}

interface CustomElementRegistry {
get(tagName: "perspective-viewer"): typeof PerspectiveViewerElement;
get(
tagName: "perspective-viewer-plugin"
): typeof PerspectiveViewerPluginElement;
}
}
2 changes: 1 addition & 1 deletion rust/perspective-viewer/test/js/simple_tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
async function get_contents_default(page) {
return await page.evaluate(async () => {
const viewer = document.querySelector(
"perspective-viewer perspective-viewer-debug"
"perspective-viewer perspective-viewer-plugin"
);
return viewer.innerHTML;
});
Expand Down
Loading

0 comments on commit ddd6078

Please sign in to comment.