From 7562bf5b8cb838f98809d72a8ec947bd16315afe Mon Sep 17 00:00:00 2001 From: Andrew Stein Date: Fri, 3 Sep 2021 04:30:49 -0400 Subject: [PATCH 1/4] Use TypeScript for rust/wasm wrapper to preserve wasm_bindgen generated types, add jsdoc --- examples/react/src/index.tsx | 25 +- examples/react/webpack.config.js | 2 +- package.json | 1 + rust/perspective-viewer/babel.config.js | 17 - rust/perspective-viewer/index.d.ts | 77 ---- rust/perspective-viewer/package.json | 3 +- rust/perspective-viewer/rollup.config.js | 19 +- rust/perspective-viewer/src/js/index.js | 143 ------- rust/perspective-viewer/src/js/monaco.js | 62 --- rust/perspective-viewer/src/rust/js/monaco.rs | 2 +- rust/perspective-viewer/src/ts/index.ts | 353 ++++++++++++++++++ rust/perspective-viewer/src/ts/monaco.ts | 63 ++++ .../test/results/results.json | 2 +- yarn.lock | 16 +- 14 files changed, 458 insertions(+), 327 deletions(-) delete mode 100644 rust/perspective-viewer/babel.config.js delete mode 100644 rust/perspective-viewer/index.d.ts delete mode 100644 rust/perspective-viewer/src/js/index.js delete mode 100644 rust/perspective-viewer/src/js/monaco.js create mode 100644 rust/perspective-viewer/src/ts/index.ts create mode 100644 rust/perspective-viewer/src/ts/monaco.ts diff --git a/examples/react/src/index.tsx b/examples/react/src/index.tsx index 3a7dba14cb..01491e092b 100644 --- a/examples/react/src/index.tsx +++ b/examples/react/src/index.tsx @@ -9,43 +9,44 @@ import * as React from "react"; import * as ReactDOM from "react-dom"; -import {useEffect, useRef} from "react"; -import perspective, {Table} from "@finos/perspective"; +import * as perspective from "@finos/perspective"; + import "@finos/perspective-viewer"; import "@finos/perspective-viewer-datagrid"; import "@finos/perspective-viewer-d3fc"; import "./index.css"; -import "@finos/perspective-viewer/dist/umd/material.css"; -import {HTMLPerspectiveViewerElement, PerspectiveViewerOptions} from "@finos/perspective-viewer"; +import "@finos/perspective-viewer/dist/umd/material-dense.css"; +import {PerspectiveViewerElement} from "@finos/perspective-viewer"; -const worker = perspective.shared_worker(); +const worker = perspective.default.shared_worker(); -const getTable = async (): Promise => { +const getTable = async (): Promise => { const req = fetch("./superstore.arrow"); const resp = await req; const buffer = await resp.arrayBuffer(); return await worker.table(buffer as any); }; -const config: PerspectiveViewerOptions = { - "row-pivots": ["State"] +const config = { + row_pivots: ["State"] }; const App = (): React.ReactElement => { - const viewer = useRef(null); + const viewer = React.useRef(null); - useEffect(() => { + React.useEffect(() => { getTable().then(table => { if (viewer.current) { - viewer.current.load(table); + viewer.current.load(Promise.resolve(table)); viewer.current.restore(config); } }); }, []); // You can also the use the stringified config values as attributes - return ; + return ; }; + window.addEventListener("load", () => { ReactDOM.render(, document.getElementById("root")); }); diff --git a/examples/react/webpack.config.js b/examples/react/webpack.config.js index 5492dc1505..d19a7f20fe 100644 --- a/examples/react/webpack.config.js +++ b/examples/react/webpack.config.js @@ -35,12 +35,12 @@ module.exports = { }, { test: /\.css$/, + exclude: /node_modules/, use: [{loader: "style-loader"}, {loader: "css-loader"}] } ] }, devServer: { - // superstore.arrow is served from here contentBase: [path.join(__dirname, "dist"), path.join(__dirname, "../../node_modules/superstore-arrow")] } }; diff --git a/package.json b/package.json index 771cf48fb5..0a4611cb6d 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,7 @@ "@babel/preset-env": "^7.8.4", "@rollup/plugin-babel": "^5.2.3", "@rollup/plugin-node-resolve": "^11.1.1", + "@rollup/plugin-typescript": "^8.2.5", "@types/ws": "^7.2.2", "@typescript-eslint/eslint-plugin": "^2.4.0", "@typescript-eslint/parser": "^2.4.0", diff --git a/rust/perspective-viewer/babel.config.js b/rust/perspective-viewer/babel.config.js deleted file mode 100644 index fe6e7ee13d..0000000000 --- a/rust/perspective-viewer/babel.config.js +++ /dev/null @@ -1,17 +0,0 @@ -module.exports = { - presets: [ - [ - "@babel/preset-env", - { - targets: { - chrome: "70", - node: "12", - ios: "13" - }, - modules: process.env.BABEL_MODULE || false - } - ] - ], - plugins: [["@babel/plugin-proposal-decorators", {legacy: true}], "transform-custom-element-classes", "@babel/plugin-proposal-class-properties", "@babel/plugin-proposal-optional-chaining"], - sourceMaps: true -}; diff --git a/rust/perspective-viewer/index.d.ts b/rust/perspective-viewer/index.d.ts deleted file mode 100644 index b3839f5258..0000000000 --- a/rust/perspective-viewer/index.d.ts +++ /dev/null @@ -1,77 +0,0 @@ -/****************************************************************************** - * - * Copyright (c) 2017, 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. - * - */ - -import React from "react"; -import {Table, View} from "@finos/perspective"; - -export interface HTMLPerspectiveViewerElement extends PerspectiveViewerOptions, HTMLElement { - load(data: Promise
): void; - delete(): Promise; - flush(): Promise; - getEditPort(): Promise; - toggleConfig(): Promise; - download(flat: boolean): Promise; - getTable(): Promise
; - copy(flat: boolean): Promise; - save(): Promise; - restore(x: PerspectiveViewerOptions): Promise; - reset(): void; - notifyResize(): void; - restyleElement(): void; - - readonly table?: Table; - readonly view?: View; -} - -export type Filters = Array<[string, string, string]>; -export type Sort = Array<[string, string] | string>; -export type Expressions = string[]; -export type Aggregates = {[column_name: string]: string}; -export type Pivots = string[]; -export type Columns = string[]; - -export interface PerspectiveViewerOptions { - plugin?: string; - columns?: Columns; - row_pivots?: Pivots; - column_pivots?: Pivots; - aggregates?: Aggregates; - filter?: Filters; - sort?: Sort; - expressions?: Expressions; - plugin_config?: object; - editable?: boolean; - selectable?: boolean; -} - -interface PerspectiveViewerHTMLAttributes extends Pick { - aggregates?: string; - expressions?: string; - row_pivots?: string; - column_pivots?: string; - filters?: string; - sort?: string; - columns?: string; -} - -interface ReactPerspectiveViewerHTMLAttributes extends PerspectiveViewerHTMLAttributes, React.HTMLAttributes {} - -type PerspectiveElement = {class?: string} & React.DetailedHTMLProps, HTMLPerspectiveViewerElement>; - -declare global { - namespace JSX { - interface IntrinsicElements { - "perspective-viewer": PerspectiveElement; - } - } - - interface Document { - createElement(tagName: "perspective-viewer", options?: ElementCreationOptions): HTMLPerspectiveViewerElement; - } -} diff --git a/rust/perspective-viewer/package.json b/rust/perspective-viewer/package.json index e6f5973d86..e5661714cb 100644 --- a/rust/perspective-viewer/package.json +++ b/rust/perspective-viewer/package.json @@ -19,7 +19,8 @@ "src/**/*", "index.d.ts" ], - "typings": "index.d.ts", + "types": "dist/esm/index.d.ts", + "typings": "dist/esm/index.d.ts", "scripts": { "build:wasm": "wasm-pack build --out-dir dist/pkg --target web && rm dist/pkg/package.json", "build:rollup": "rollup --config rollup.config.js", diff --git a/rust/perspective-viewer/rollup.config.js b/rust/perspective-viewer/rollup.config.js index ba745e45d5..586485f71a 100644 --- a/rust/perspective-viewer/rollup.config.js +++ b/rust/perspective-viewer/rollup.config.js @@ -1,7 +1,7 @@ -import babel from "@rollup/plugin-babel"; import filesize from "rollup-plugin-filesize"; import postcss from "rollup-plugin-postcss"; import sourcemaps from "rollup-plugin-sourcemaps"; +import typescript from "@rollup/plugin-typescript"; import path from "path"; import fs from "fs"; @@ -59,17 +59,20 @@ export default () => { }) ] }, - { - input: "src/js/index.js", - external: [/pkg/, /monaco\-editor/], + ...["index", "monaco"].map(name => ({ + input: `src/ts/${name}.ts`, + external: [/pkg/, /node_modules/, /monaco\-editor/], output: { sourcemap: true, dir: "dist/esm/" }, plugins: [ - babel({ - exclude: "node_modules/**", - babelHelpers: "bundled" + typescript({ + target: "es2018", + declaration: true, + outDir: "dist/esm", + rootDir: "src/ts", + allowSyntheticDefaultImports: true }), filesize(), postcss({ @@ -82,7 +85,7 @@ export default () => { watch: { clearScreen: false } - }, + })), ...generate_themes() ]; }; diff --git a/rust/perspective-viewer/src/js/index.js b/rust/perspective-viewer/src/js/index.js deleted file mode 100644 index bf60baf4ef..0000000000 --- a/rust/perspective-viewer/src/js/index.js +++ /dev/null @@ -1,143 +0,0 @@ -/****************************************************************************** - * - * 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. - * - */ - -import "./dragdrop.js"; -import init, * as internal from "../pkg/perspective_viewer.js"; - -const WASM_INTERNAL = import( - /* webpackChunkName: "perspective-viewer.custom-element" */ - /* webpackMode: "eager" */ - "../pkg/perspective_viewer_bg.wasm" -); - -async function init_wasm() { - const {default: wasm_internal} = await WASM_INTERNAL; - await init(wasm_internal); - internal.set_panic_hook(); - return internal; -} - -export const wasm = init_wasm(); - -let _index = undefined; -async function _await_index(f) { - await new Promise(setTimeout); - if (!_index) { - _index = await wasm; - } - return f(); -} - -class PerspectiveViewerElement extends HTMLElement { - constructor() { - super(); - _await_index(() => { - this._instance = new _index.PerspectiveViewerElement(this); - }); - } - - connectedCallback() { - _await_index(() => { - this._instance.connected_callback(); - }); - } - - static registerPlugin(name) { - _await_index(() => { - _index.register_plugin(name); - }); - } - - load(table) { - return _await_index(() => this._instance.js_load(table)); - } - - notifyResize() { - return _await_index(() => this._instance.js_resize()); - } - - getTable() { - return _await_index(() => this._instance.js_get_table()); - } - - restore(...args) { - return _await_index(() => this._instance.js_restore(...args)); - } - - flush() { - return _await_index(() => this._instance.js_flush()); - } - - reset() { - return _await_index(() => this._instance.js_reset()); - } - - save(...args) { - return _await_index(() => this._instance.js_save(...args)); - } - - delete() { - return _await_index(() => this._instance.js_delete()); - } - - download(...args) { - return _await_index(() => this._instance.js_download(...args)); - } - - copy(...args) { - return _await_index(() => this._instance.js_copy(...args)); - } - - getEditPort() { - return _await_index(() => console.error("Not Implemented")); - } - - setThrottle(...args) { - return _await_index(() => this._instance.js_set_throttle(...args)); - } - - toggleConfig(force) { - return _await_index(() => this._instance.js_toggle_config(force)); - } - - get_plugin(name) { - return _await_index(() => this._instance.js_get_plugin(name)); - } - - get_all_plugins() { - return _await_index(() => this._instance.js_get_all_plugins()); - } -} - -if (document.createElement("perspective-viewer").constructor === HTMLElement) { - window.customElements.define("perspective-viewer", PerspectiveViewerElement); -} - -class PerspectiveColumnStyleElement extends HTMLElement { - constructor() { - super(); - } - - open(target, config, default_config) { - _await_index(() => { - if (this._instance) { - this._instance.reset(config, default_config); - } else { - this._instance = new _index.PerspectiveColumnStyleElement(this, config, default_config); - } - - this._instance.open(target); - }); - } -} - -if (document.createElement("perspective-column-style").constructor === HTMLElement) { - window.customElements.define("perspective-column-style", PerspectiveColumnStyleElement); -} diff --git a/rust/perspective-viewer/src/js/monaco.js b/rust/perspective-viewer/src/js/monaco.js deleted file mode 100644 index fd42d7e72f..0000000000 --- a/rust/perspective-viewer/src/js/monaco.js +++ /dev/null @@ -1,62 +0,0 @@ -/****************************************************************************** - * - * 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. - * - */ - -import "monaco-editor/esm/vs/editor/browser/controller/coreCommands.js"; -import "monaco-editor/esm/vs/editor/browser/widget/codeEditorWidget.js"; -import "monaco-editor/esm/vs/editor/browser/widget/diffEditorWidget.js"; -import "monaco-editor/esm/vs/editor/browser/widget/diffNavigator.js"; -import "monaco-editor/esm/vs/editor/contrib/anchorSelect/anchorSelect.js"; -import "monaco-editor/esm/vs/editor/contrib/bracketMatching/bracketMatching.js"; -import "monaco-editor/esm/vs/editor/contrib/caretOperations/caretOperations.js"; -import "monaco-editor/esm/vs/editor/contrib/caretOperations/transpose.js"; -import "monaco-editor/esm/vs/editor/contrib/clipboard/clipboard.js"; -import "monaco-editor/esm/vs/editor/contrib/codeAction/codeActionContributions.js"; -import "monaco-editor/esm/vs/editor/contrib/codelens/codelensController.js"; -import "monaco-editor/esm/vs/editor/contrib/colorPicker/colorContributions.js"; -import "monaco-editor/esm/vs/editor/contrib/comment/comment.js"; -import "monaco-editor/esm/vs/editor/contrib/contextmenu/contextmenu.js"; -import "monaco-editor/esm/vs/editor/contrib/cursorUndo/cursorUndo.js"; -import "monaco-editor/esm/vs/editor/contrib/dnd/dnd.js"; -import "monaco-editor/esm/vs/editor/contrib/documentSymbols/documentSymbols.js"; -import "monaco-editor/esm/vs/editor/contrib/find/findController.js"; -import "monaco-editor/esm/vs/editor/contrib/folding/folding.js"; -import "monaco-editor/esm/vs/editor/contrib/fontZoom/fontZoom.js"; -import "monaco-editor/esm/vs/editor/contrib/format/formatActions.js"; -import "monaco-editor/esm/vs/editor/contrib/gotoError/gotoError.js"; -import "monaco-editor/esm/vs/editor/contrib/gotoSymbol/goToCommands.js"; -import "monaco-editor/esm/vs/editor/contrib/gotoSymbol/link/goToDefinitionAtPosition.js"; -import "monaco-editor/esm/vs/editor/contrib/hover/hover.js"; -import "monaco-editor/esm/vs/editor/contrib/inPlaceReplace/inPlaceReplace.js"; -import "monaco-editor/esm/vs/editor/contrib/indentation/indentation.js"; -import "monaco-editor/esm/vs/editor/contrib/inlineHints/inlineHintsController.js"; -import "monaco-editor/esm/vs/editor/contrib/linesOperations/linesOperations.js"; -import "monaco-editor/esm/vs/editor/contrib/linkedEditing/linkedEditing.js"; -import "monaco-editor/esm/vs/editor/contrib/links/links.js"; -import "monaco-editor/esm/vs/editor/contrib/multicursor/multicursor.js"; -import "monaco-editor/esm/vs/editor/contrib/parameterHints/parameterHints.js"; -import "monaco-editor/esm/vs/editor/contrib/rename/rename.js"; -import "monaco-editor/esm/vs/editor/contrib/smartSelect/smartSelect.js"; -import "monaco-editor/esm/vs/editor/contrib/snippet/snippetController2.js"; -import "monaco-editor/esm/vs/editor/contrib/suggest/suggestController.js"; -import "monaco-editor/esm/vs/editor/contrib/toggleTabFocusMode/toggleTabFocusMode.js"; -import "monaco-editor/esm/vs/editor/contrib/unusualLineTerminators/unusualLineTerminators.js"; -import "monaco-editor/esm/vs/editor/contrib/viewportSemanticTokens/viewportSemanticTokens.js"; -import "monaco-editor/esm/vs/editor/contrib/wordHighlighter/wordHighlighter.js"; -import "monaco-editor/esm/vs/editor/contrib/wordOperations/wordOperations.js"; -import "monaco-editor/esm/vs/editor/contrib/wordPartOperations/wordPartOperations.js"; -import "monaco-editor/esm/vs/editor/standalone/browser/accessibilityHelp/accessibilityHelp.js"; -import "monaco-editor/esm/vs/editor/standalone/browser/iPadShowKeyboard/iPadShowKeyboard.js"; -import "monaco-editor/esm/vs/editor/standalone/browser/inspectTokens/inspectTokens.js"; -import "monaco-editor/esm/vs/editor/standalone/browser/quickAccess/standaloneCommandsQuickAccess.js"; -import "monaco-editor/esm/vs/editor/standalone/browser/quickAccess/standaloneGotoLineQuickAccess.js"; -import "monaco-editor/esm/vs/editor/standalone/browser/quickAccess/standaloneGotoSymbolQuickAccess.js"; -import "monaco-editor/esm/vs/editor/standalone/browser/quickAccess/standaloneHelpQuickAccess.js"; -import "monaco-editor/esm/vs/editor/standalone/browser/referenceSearch/standaloneReferenceSearch.js"; -import "monaco-editor/esm/vs/editor/standalone/browser/toggleHighContrast/toggleHighContrast.js"; diff --git a/rust/perspective-viewer/src/rust/js/monaco.rs b/rust/perspective-viewer/src/rust/js/monaco.rs index fc69b6f0d7..87a8cb70d9 100644 --- a/rust/perspective-viewer/src/rust/js/monaco.rs +++ b/rust/perspective-viewer/src/rust/js/monaco.rs @@ -41,7 +41,7 @@ extern "C" { return import( /* webpackChunkName: \"monaco-exts\" */ /* webpackMode: \"eager\" */ - '../../../../src/js/monaco.js' + '../../../../dist/esm/monaco.js' ); } ") diff --git a/rust/perspective-viewer/src/ts/index.ts b/rust/perspective-viewer/src/ts/index.ts new file mode 100644 index 0000000000..913fdefc18 --- /dev/null +++ b/rust/perspective-viewer/src/ts/index.ts @@ -0,0 +1,353 @@ +/****************************************************************************** + * + * 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. + * + */ + +import init, * as internal from "../../dist/pkg/perspective_viewer.js"; +import * as perspective from "@finos/perspective"; + +/** + * Module for the `` custom element. Though + * `` is written mostly in Rust, the nature of WebAssembly's + * compilation makes it a dynamic module; in order to guarantee that the + * Custom Elements extension methods are registered synchronously with this + * package's import, we need perform said registration within this wrapper module. + * + * This module has no (real) exports, but importing it has a side + * effect: the {@link module:perspective_viewer~PerspectiveViewer} class is + * registered as a custom element, after which it can be used as a standard DOM + * element. + * + * The documentation in this module defines the instance structure of a + * `` DOM object instantiated typically, through HTML or any + * relevent DOM method e.g. `document.createElement("perspective-viewer")` or + * `document.getElementsByTagName("perspective-viewer")`. + * + * @module perspective-viewer + */ + + +// 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 +// filter out cancelled query messages. +window.addEventListener("unhandledrejection", event => { + if (event.reason?.message === "View method cancelled") { + event.preventDefault(); + } +}); + +const WASM_MODULE = import( + /* webpackChunkName: "perspective-viewer.custom-element" */ + /* webpackMode: "eager" */ + "../../dist/pkg/perspective_viewer_bg.wasm" +).then(init_wasm); + +async function init_wasm({default: wasm_module}) { + await init(wasm_module); + internal.set_panic_hook(); + return internal; +} + +export type PerspectiveViewerConfig = perspective.ViewConfig & { + plugin?: string; + settings?: boolean; +}; + +export class PerspectiveViewerElement extends HTMLElement { + private instance: internal.PerspectiveViewerElement; + + constructor() { + super(); + this.load_wasm(); + } + + private async load_wasm() { + const module = await WASM_MODULE; + if (!this.instance) { + this.instance = new module.PerspectiveViewerElement(this); + } + } + + /** + * Part of the Custom Elements API. This method is called by the browser, and + * should not be called directly by applications. + */ + async connectedCallback() { + await this.load_wasm(); + this.instance.connected_callback(); + } + + /** + * Register a new plugin via its custom element name. This method is called + * automatically as a side effect of importing a plugin module, so this method + * should only typically be called by plugin authors. + * + * @param name The `name` of the custom element to register + */ + static async registerPlugin(name) { + const module = await WASM_MODULE; + module.register_plugin(name); + } + + /** + * Load a `perspective.Table`. If `load` or `update` have already been called + * on this element, its internal `perspective.Table` will _not_ be deleted. + * + * @async + * @param data A `Promise` which resolves to the `perspective.Table` + * @returns {Promise} A promise which resolves once the data is loaded, + * a `perspective.View` has been created, and the active plugin has rendered. + * @example + * const my_viewer = document.getElementById('#my_viewer'); + * const tbl = perspective.table("x,y\n1,a\n2,b"); + * my_viewer.load(tbl); + * @example + * const my_viewer = document.getElementById('#my_viewer'); + * const tbl = perspective.table("x,y\n1,a\n2,b"); + * my_viewer.load(tbl); + */ + async load(table: Promise) { + await this.load_wasm(); + await this.instance.js_load(table); + } + + /** + * Redraw this `` and plugin when its dimensions or visibility + * have been updated. + * + * @returns A `Promise` which resolves when this resize event has finished + * rendering. + */ + async notifyResize(): Promise { + await this.load_wasm(); + await this.instance.js_resize(); + } + + /** + * Returns the `perspective.Table()` which was supplied to `load()`. If `load()` + * has been called but the supplied `Promise` has not resolved, + * `getTable()` will `await`; if `load()` has not yet been called, an `Error` + * will be thrown. + * + * @returns A `Promise` which resolves to a `perspective.Table` + */ + async getTable(): Promise { + await this.load_wasm(); + const table = await this.instance.js_get_table(); + return table; + } + + /** + * Restore this element to a state as generated by a reciprocal call to + * `save`. + * + * @param config returned by `save()`. + * @returns A promise which resolves when the changes have been applied and + * rendered. + */ + async restore(config: PerspectiveViewerConfig | string | ArrayBuffer): Promise { + await this.load_wasm(); + await this.instance.js_restore(config); + } + + /** + * Flush any pending modifications to this ``. + * + * @returns {Promise} A promise which resolves when the current pending + * state changes have been applied and rendered. + */ + async flush(): Promise { + await this.load_wasm(); + await this.instance.js_flush(); + } + + /** + * Reset's this element's view state and attributes to default. Does not + * delete this element's `perspective.table` or otherwise modify the data + * state. + */ + async reset(): Promise { + await this.load_wasm(); + await this.instance.js_reset(); + } + + /** + * Serialize this element's attribute/interaction state. + * + * @param format The serialization format - `json` (JavaScript object), + * `arraybuffer` or `string`. `restore()` uses the returned config's type + * to infer format. + * @returns {object} a serialized element. + */ + async save(format?: "json" | "arraybuffer" | "string"): Promise { + await this.load_wasm(); + const config = await this.instance.js_save(format); + return config; + } + + /** + * Deletes this element and clears it's internal state (but not its + * user state). This (or the underlying `perspective.view`'s equivalent + * method) must be called in order for its memory to be reclaimed, as well + * as the reciprocal method on the `perspective.table` which this viewer is + * bound to. + */ + async delete(): Promise { + await this.load_wasm(); + await this.instance.js_delete(); + } + + /** + * Download this element's data as a CSV file. + * + * @param flat Whether to use the element's current view + * config, or to use a default "flat" view. + */ + async download(flat: boolean): Promise { + await this.load_wasm(); + await this.instance.js_download(flat); + } + + /** + * Copies this element's view data (as a CSV) to the clipboard. This method + * must be called from an event handler, subject to the browser's + * restrictions on clipboard access. See + * {@link https://www.w3.org/TR/clipboard-apis/#allow-read-clipboard}. + * + * @param flat Whether to use the element's current view + * config, or to use a default "flat" view. + */ + async copy(flat: boolean): Promise { + await this.load_wasm(); + await this.instance.js_copy(flat); + } + + /** + * Restyles the elements and to pick up any style changes + */ + async restyleElement(): Promise { + console.error("Not Implemented"); + } + + /** + * Gets the edit port, the port number for which `Table` updates from this + * `` are generated. + * + * @returns A promise which resolves to the current edit port. + */ + async getEditPort(): Promise { + console.error("Not Implemented"); + return -1; + } + + /** + * Determines the render throttling behavior. Can be an integer, for + * millisecond window to throttle render event; or, if `undefined`, + * will try to determine the optimal throttle time from this component's + * render framerate. + * + * @param value an optional throttle rate in milliseconds (integer). If not + * supplied, adaptive throttling is calculated from the average plugin render + * time. + * @example + * // Only draws at most 1 frame/sec. + * await viewer.setThrottle(1000); + */ + async setThrottle(value?: number): Promise { + await this.load_wasm(); + await this.instance.js_set_throttle(value); + } + + /** + * Opens/closes the element's config menu. + * + * @param force If supplied, explicitly set the config state to "open" (`true`) + * or "closed" (`false`). + */ + async toggleConfig(force): Promise { + await this.load_wasm(); + await this.instance.js_toggle_config(force); + } + + /** + * Get the currently active plugin custom element instance, or a specific + * named instance if requested. `getPlugin(name)` does not activate the plugin + * requested, so if this plugin is not active the returned `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. + * + * @param name Optionally a specific plugin name, defaulting to the current + * active plugin. + * @returns The active or requested plugin instance. + */ + async getPlugin(name): Promise { + await this.load_wasm(); + const plugin = await this.instance.js_get_plugin(name); + return plugin; + } + + /** + * 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 as + * a side effect. + * + * @returns An `Array` of the plugin instances for this ``. + */ + async getAllPlugins(): Promise> { + await this.load_wasm(); + const plugins = await this.instance.js_get_all_plugins(); + return plugins; + } +} + +if (document.createElement("perspective-viewer").constructor === HTMLElement) { + window.customElements.define("perspective-viewer", PerspectiveViewerElement); +} + +class PerspectiveColumnStyleElement extends HTMLElement { + private instance: internal.PerspectiveColumnStyleElement; + + constructor() { + super(); + } + + async open(target, config, default_config) { + if (this.instance) { + this.instance.reset(config, default_config); + } else { + this.instance = new internal.PerspectiveColumnStyleElement(this, config, default_config); + } + + this.instance.open(target); + } +} + +if (document.createElement("perspective-column-style").constructor === HTMLElement) { + window.customElements.define("perspective-column-style", PerspectiveColumnStyleElement); +} + +interface ReactPerspectiveViewerAttributes extends React.HTMLAttributes {} + +type JsxPerspectiveViewerElement = {class?: string} & React.DetailedHTMLProps, PerspectiveViewerElement>; + +declare global { + namespace JSX { + interface IntrinsicElements { + "perspective-viewer": JsxPerspectiveViewerElement; + } + } + + interface Document { + createElement(tagName: "perspective-viewer", options?: ElementCreationOptions): PerspectiveViewerElement; + } +} \ No newline at end of file diff --git a/rust/perspective-viewer/src/ts/monaco.ts b/rust/perspective-viewer/src/ts/monaco.ts new file mode 100644 index 0000000000..2b4e000792 --- /dev/null +++ b/rust/perspective-viewer/src/ts/monaco.ts @@ -0,0 +1,63 @@ +/****************************************************************************** + * + * 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. + * + */ + + import "monaco-editor/esm/vs/editor/browser/controller/coreCommands.js"; + import "monaco-editor/esm/vs/editor/browser/widget/codeEditorWidget.js"; + import "monaco-editor/esm/vs/editor/browser/widget/diffEditorWidget.js"; + import "monaco-editor/esm/vs/editor/browser/widget/diffNavigator.js"; + import "monaco-editor/esm/vs/editor/contrib/anchorSelect/anchorSelect.js"; + import "monaco-editor/esm/vs/editor/contrib/bracketMatching/bracketMatching.js"; + import "monaco-editor/esm/vs/editor/contrib/caretOperations/caretOperations.js"; + import "monaco-editor/esm/vs/editor/contrib/caretOperations/transpose.js"; + import "monaco-editor/esm/vs/editor/contrib/clipboard/clipboard.js"; + import "monaco-editor/esm/vs/editor/contrib/codeAction/codeActionContributions.js"; + import "monaco-editor/esm/vs/editor/contrib/codelens/codelensController.js"; + import "monaco-editor/esm/vs/editor/contrib/colorPicker/colorContributions.js"; + import "monaco-editor/esm/vs/editor/contrib/comment/comment.js"; + import "monaco-editor/esm/vs/editor/contrib/contextmenu/contextmenu.js"; + import "monaco-editor/esm/vs/editor/contrib/cursorUndo/cursorUndo.js"; + import "monaco-editor/esm/vs/editor/contrib/dnd/dnd.js"; + import "monaco-editor/esm/vs/editor/contrib/documentSymbols/documentSymbols.js"; + import "monaco-editor/esm/vs/editor/contrib/find/findController.js"; + import "monaco-editor/esm/vs/editor/contrib/folding/folding.js"; + import "monaco-editor/esm/vs/editor/contrib/fontZoom/fontZoom.js"; + import "monaco-editor/esm/vs/editor/contrib/format/formatActions.js"; + import "monaco-editor/esm/vs/editor/contrib/gotoError/gotoError.js"; + import "monaco-editor/esm/vs/editor/contrib/gotoSymbol/goToCommands.js"; + import "monaco-editor/esm/vs/editor/contrib/gotoSymbol/link/goToDefinitionAtPosition.js"; + import "monaco-editor/esm/vs/editor/contrib/hover/hover.js"; + import "monaco-editor/esm/vs/editor/contrib/inPlaceReplace/inPlaceReplace.js"; + import "monaco-editor/esm/vs/editor/contrib/indentation/indentation.js"; + import "monaco-editor/esm/vs/editor/contrib/inlineHints/inlineHintsController.js"; + import "monaco-editor/esm/vs/editor/contrib/linesOperations/linesOperations.js"; + import "monaco-editor/esm/vs/editor/contrib/linkedEditing/linkedEditing.js"; + import "monaco-editor/esm/vs/editor/contrib/links/links.js"; + import "monaco-editor/esm/vs/editor/contrib/multicursor/multicursor.js"; + import "monaco-editor/esm/vs/editor/contrib/parameterHints/parameterHints.js"; + import "monaco-editor/esm/vs/editor/contrib/rename/rename.js"; + import "monaco-editor/esm/vs/editor/contrib/smartSelect/smartSelect.js"; + import "monaco-editor/esm/vs/editor/contrib/snippet/snippetController2.js"; + import "monaco-editor/esm/vs/editor/contrib/suggest/suggestController.js"; + import "monaco-editor/esm/vs/editor/contrib/toggleTabFocusMode/toggleTabFocusMode.js"; + import "monaco-editor/esm/vs/editor/contrib/unusualLineTerminators/unusualLineTerminators.js"; + import "monaco-editor/esm/vs/editor/contrib/viewportSemanticTokens/viewportSemanticTokens.js"; + import "monaco-editor/esm/vs/editor/contrib/wordHighlighter/wordHighlighter.js"; + import "monaco-editor/esm/vs/editor/contrib/wordOperations/wordOperations.js"; + import "monaco-editor/esm/vs/editor/contrib/wordPartOperations/wordPartOperations.js"; + import "monaco-editor/esm/vs/editor/standalone/browser/accessibilityHelp/accessibilityHelp.js"; + import "monaco-editor/esm/vs/editor/standalone/browser/iPadShowKeyboard/iPadShowKeyboard.js"; + import "monaco-editor/esm/vs/editor/standalone/browser/inspectTokens/inspectTokens.js"; + import "monaco-editor/esm/vs/editor/standalone/browser/quickAccess/standaloneCommandsQuickAccess.js"; + import "monaco-editor/esm/vs/editor/standalone/browser/quickAccess/standaloneGotoLineQuickAccess.js"; + import "monaco-editor/esm/vs/editor/standalone/browser/quickAccess/standaloneGotoSymbolQuickAccess.js"; + import "monaco-editor/esm/vs/editor/standalone/browser/quickAccess/standaloneHelpQuickAccess.js"; + import "monaco-editor/esm/vs/editor/standalone/browser/referenceSearch/standaloneReferenceSearch.js"; + import "monaco-editor/esm/vs/editor/standalone/browser/toggleHighContrast/toggleHighContrast.js"; + \ No newline at end of file diff --git a/rust/perspective-viewer/test/results/results.json b/rust/perspective-viewer/test/results/results.json index 58a93351fa..03f0fbfac6 100644 --- a/rust/perspective-viewer/test/results/results.json +++ b/rust/perspective-viewer/test/results/results.json @@ -3,7 +3,7 @@ "superstore.html/doesn't leak elements.": "d0fd18b3d4d7c183c5ed155b4bf37972", "superstore.html/doesn't leak views when setting row pivots.": "54daaa4bbbe59f6ed4acc301ba871bab", "superstore.html/doesn't leak views when setting filters.": "6dfc1e505f1428424c3265f0236f22fc", - "__GIT_COMMIT__": "a3256b28c3261b64d89ddc993298a8d049c39886", + "__GIT_COMMIT__": "9a0427a1de46d2da89ee3d5290d0baba3fd31aa2", "blank.html/Handles reloading with a schema.": "e58c62f6e0ff16dc4d753f99e0fc39c3", "superstore_shows_a_grid_without_any_settings_applied_": "ae1c4690d978598ca14c8669244ce604", "superstore_Responsive_Layout_shows_horizontal_columns_on_small_vertical_viewports_": "57ba3ad341cf8a0e4df6ab96715ff2a0", diff --git a/yarn.lock b/yarn.lock index 31a6845650..da59a4d8e8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2864,6 +2864,14 @@ is-module "^1.0.0" resolve "^1.19.0" +"@rollup/plugin-typescript@^8.2.5": + version "8.2.5" + resolved "https://registry.yarnpkg.com/@rollup/plugin-typescript/-/plugin-typescript-8.2.5.tgz#e0319761b2b5105615e5a0c371ae05bc2984b7de" + integrity sha512-QL/LvDol/PAGB2O0S7/+q2HpSUNodpw7z6nGn9BfoVCPOZ0r4EALrojFU29Bkoi2Hr2jgTocTejJ5GGWZfOxbQ== + dependencies: + "@rollup/pluginutils" "^3.1.0" + resolve "^1.17.0" + "@rollup/pluginutils@^3.0.9", "@rollup/pluginutils@^3.1.0": version "3.1.0" resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-3.1.0.tgz#706b4524ee6dc8b103b3c995533e5ad680c02b9b" @@ -11844,10 +11852,10 @@ mkdirp@0.x, mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@^0.5.5, mkdirp@~0.5.1: dependencies: minimist "^1.2.5" -mobile-drag-drop@^2.3.0-rc.2: - version "2.3.0-rc.2" - resolved "https://registry.yarnpkg.com/mobile-drag-drop/-/mobile-drag-drop-2.3.0-rc.2.tgz#00d6e85e04512a620fd5357366e8786bd29aa7aa" - integrity sha512-4rHP0PUeWkSp0O3waNHPQZCHeZnLu8bE59MerWOnZJ249BCyICXL1WWp3xqkMKXEDFYuhfk3bS42bKB9IeN9uw== +mobile-drag-drop-shadow-dom@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/mobile-drag-drop-shadow-dom/-/mobile-drag-drop-shadow-dom-3.0.0.tgz#f91014af66a0331338d4dfef30ecc5ddcb5a0687" + integrity sha512-cNDH83lfYhllESH+ddjyxXdnjunLec0ldnygm3nWY1zMtfeA1tHoVsAasZy63QhnRuN4ps0pGuL8TolUK/sYjA== modify-values@^1.0.0: version "1.0.1" From 9ae46b8d12ce5818efdb56070922ba31f099a386 Mon Sep 17 00:00:00 2001 From: Andrew Stein Date: Fri, 3 Sep 2021 05:04:16 -0400 Subject: [PATCH 2/4] Port drag drop polyfill to standalone library --- rust/perspective-viewer/package.json | 2 +- rust/perspective-viewer/src/js/dragdrop.js | 825 --------------------- rust/perspective-viewer/src/ts/index.ts | 1 + 3 files changed, 2 insertions(+), 826 deletions(-) delete mode 100644 rust/perspective-viewer/src/js/dragdrop.js diff --git a/rust/perspective-viewer/package.json b/rust/perspective-viewer/package.json index e5661714cb..e9192fc3a4 100644 --- a/rust/perspective-viewer/package.json +++ b/rust/perspective-viewer/package.json @@ -48,7 +48,7 @@ }, "dependencies": { "@finos/perspective": "^0.10.3", - "mobile-drag-drop": "^2.3.0-rc.2", + "mobile-drag-drop-shadow-dom": "3.0.0", "monaco-editor": "0.24.0", "monaco-editor-webpack-plugin": "3.1.0" } diff --git a/rust/perspective-viewer/src/js/dragdrop.js b/rust/perspective-viewer/src/js/dragdrop.js deleted file mode 100644 index 2ec5e102ea..0000000000 --- a/rust/perspective-viewer/src/js/dragdrop.js +++ /dev/null @@ -1,825 +0,0 @@ -/* - -Forked from https://github.com/timruffles/mobile-drag-drop/ v2.3.0-rc.2 - -Copyright (c) 2013 Tim Ruffles - -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software is furnished to do so, -subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ - -const DRAG_DROP_STYLE = ` -.dnd-poly-drag-image { - opacity: .5 !important; -} -.dnd-poly-drag-image.dnd-poly-snapback { - transition-property: transform, -webkit-transform !important; - transition-duration: 250ms !important; - transition-timing-function: ease-out !important; -} -`; - -const style_elem = document.createElement("style"); -style_elem.textContent = DRAG_DROP_STYLE; -document.head.appendChild(style_elem); - -const CLASS_PREFIX = "dnd-poly-"; -const CLASS_DRAG_IMAGE = CLASS_PREFIX + "drag-image"; -const CLASS_DRAG_IMAGE_SNAPBACK = CLASS_PREFIX + "snapback"; -const CLASS_DRAG_OPERATION_ICON = CLASS_PREFIX + "icon"; -const EVENT_PREFIX = "dnd-poly-"; -const EVENT_DRAG_DRAGSTART_PENDING = EVENT_PREFIX + "dragstart-pending"; -const EVENT_DRAG_DRAGSTART_CANCEL = EVENT_PREFIX + "dragstart-cancel"; -const ALLOWED_EFFECTS = ["none", "copy", "copyLink", "copyMove", "link", "linkMove", "move", "all"]; -const DROP_EFFECTS = ["none", "copy", "move", "link"]; - -function detectFeatures() { - const features = { - dragEvents: "ondragstart" in document.documentElement, - draggable: "draggable" in document.documentElement, - userAgentSupportingNativeDnD: undefined - }; - const isBlinkEngine = !!window.chrome || /chrome/i.test(navigator.userAgent); - features.userAgentSupportingNativeDnD = !(/iPad|iPhone|iPod|Android/.test(navigator.userAgent) || (isBlinkEngine && "ontouchstart" in document.documentElement)); - return features; -} -function supportsPassiveEventListener() { - let supportsPassiveEventListeners = false; - try { - const opts = Object.defineProperty({}, "passive", { - get: function() { - supportsPassiveEventListeners = true; - } - }); - window.addEventListener("test", null, opts); - } catch (e) {} - return supportsPassiveEventListeners; -} - -const supportsPassive = supportsPassiveEventListener(); -function isDOMElement(object) { - return object && object.tagName; -} -function addDocumentListener(ev, handler, passive) { - if (passive === void 0) { - passive = true; - } - document.addEventListener(ev, handler, supportsPassive ? {passive: passive} : false); -} -function removeDocumentListener(ev, handler) { - document.removeEventListener(ev, handler); -} -function onEvt(el, event, handler, capture) { - if (capture === void 0) { - capture = false; - } - const options = supportsPassive ? {passive: true, capture: capture} : capture; - el.addEventListener(event, handler, options); - return { - off: function() { - el.removeEventListener(event, handler, options); - } - }; -} -function prepareNodeCopyAsDragImage(srcNode, dstNode) { - if (srcNode.nodeType === 1) { - const cs = getComputedStyle(srcNode); - for (let i = 0; i < cs.length; i++) { - const csName = cs[i]; - dstNode.style.setProperty(csName, cs.getPropertyValue(csName), cs.getPropertyPriority(csName)); - } - dstNode.style.pointerEvents = "none"; - dstNode.removeAttribute("id"); - dstNode.removeAttribute("class"); - dstNode.removeAttribute("draggable"); - if (dstNode.nodeName === "CANVAS") { - const canvasSrc = srcNode; - const canvasDst = dstNode; - const canvasSrcImgData = canvasSrc.getContext("2d").getImageData(0, 0, canvasSrc.width, canvasSrc.height); - canvasDst.getContext("2d").putImageData(canvasSrcImgData, 0, 0); - } - } - if (srcNode.hasChildNodes()) { - for (let i = 0; i < srcNode.childNodes.length; i++) { - prepareNodeCopyAsDragImage(srcNode.childNodes[i], dstNode.childNodes[i]); - } - } -} -function createDragImage(sourceNode) { - const dragImage = sourceNode.cloneNode(true); - prepareNodeCopyAsDragImage(sourceNode, dragImage); - return dragImage; -} -function average(array) { - if (array.length === 0) { - return 0; - } - return ( - array.reduce(function(s, v) { - return v + s; - }, 0) / array.length - ); -} -function isTouchIdentifierContainedInTouchEvent(touchEvent, touchIdentifier) { - for (let i = 0; i < touchEvent.changedTouches.length; i++) { - const touch = touchEvent.changedTouches[i]; - if (touch.identifier === touchIdentifier) { - return true; - } - } - return false; -} -function updateCentroidCoordinatesOfTouchesIn(coordinateProp, event, outPoint) { - const pageXs = [], - pageYs = []; - for (let i = 0; i < event.touches.length; i++) { - const touch = event.touches[i]; - pageXs.push(touch[coordinateProp + "X"]); - pageYs.push(touch[coordinateProp + "Y"]); - } - outPoint.x = average(pageXs); - outPoint.y = average(pageYs); -} -const TRANSFORM_CSS_VENDOR_PREFIXES = ["", "-webkit-"]; -function extractTransformStyles(sourceNode) { - return TRANSFORM_CSS_VENDOR_PREFIXES.map(function(prefix) { - const transform = sourceNode.style[prefix + "transform"]; - if (!transform || transform === "none") { - return ""; - } - return transform.replace(/translate\(\D*\d+[^,]*,\D*\d+[^,]*\)\s*/g, ""); - }); -} -function translateElementToPoint(element, pnt, originalTransforms, offset, centerOnCoordinates) { - if (centerOnCoordinates === void 0) { - centerOnCoordinates = true; - } - let x = pnt.x, - y = pnt.y; - if (offset) { - x += offset.x; - y += offset.y; - } - if (centerOnCoordinates) { - x -= parseInt(element.offsetWidth, 10) / 2; - y -= parseInt(element.offsetHeight, 10) / 2; - } - const translate = "translate3d(" + x + "px," + y + "px, 0)"; - for (let i = 0; i < TRANSFORM_CSS_VENDOR_PREFIXES.length; i++) { - const transformProp = TRANSFORM_CSS_VENDOR_PREFIXES[i] + "transform"; - element.style[transformProp] = translate + " " + originalTransforms[i]; - } -} -function applyDragImageSnapback(sourceEl, dragImage, dragImageTransforms, transitionEndCb) { - const cs = getComputedStyle(sourceEl); - if (cs.visibility === "hidden" || cs.display === "none") { - console.log("dnd-poly: source node is not visible. skipping snapback transition."); - transitionEndCb(); - return; - } - dragImage.classList.add(CLASS_DRAG_IMAGE_SNAPBACK); - const csDragImage = getComputedStyle(dragImage); - const durationInS = parseFloat(csDragImage.transitionDuration); - if (isNaN(durationInS) || durationInS === 0) { - console.log("dnd-poly: no transition used - skipping snapback"); - transitionEndCb(); - return; - } - console.log("dnd-poly: starting dragimage snap back"); - const rect = sourceEl.getBoundingClientRect(); - const pnt = { - x: rect.left, - y: rect.top - }; - pnt.x += document.body.scrollLeft || document.documentElement.scrollLeft; - pnt.y += document.body.scrollTop || document.documentElement.scrollTop; - pnt.x -= parseInt(cs.marginLeft, 10); - pnt.y -= parseInt(cs.marginTop, 10); - const delayInS = parseFloat(csDragImage.transitionDelay); - const durationInMs = Math.round((durationInS + delayInS) * 1000); - translateElementToPoint(dragImage, pnt, dragImageTransforms, undefined, false); - setTimeout(transitionEndCb, durationInMs); -} - -const DataTransfer = (function() { - function DataTransfer(_dataStore, _setDragImageHandler) { - this._dataStore = _dataStore; - this._setDragImageHandler = _setDragImageHandler; - this._dropEffect = DROP_EFFECTS[0]; - } - Object.defineProperty(DataTransfer.prototype, "dropEffect", { - get: function() { - return this._dropEffect; - }, - set: function(value) { - if (this._dataStore.mode !== 0 && ALLOWED_EFFECTS.indexOf(value) > -1) { - this._dropEffect = value; - } - }, - enumerable: true, - configurable: true - }); - Object.defineProperty(DataTransfer.prototype, "types", { - get: function() { - if (this._dataStore.mode !== 0) { - return Object.freeze(this._dataStore.types); - } - }, - enumerable: true, - configurable: true - }); - Object.defineProperty(DataTransfer.prototype, "effectAllowed", { - get: function() { - return this._dataStore.effectAllowed; - }, - set: function(value) { - if (this._dataStore.mode === 2 && ALLOWED_EFFECTS.indexOf(value) > -1) { - this._dataStore.effectAllowed = value; - } - }, - enumerable: true, - configurable: true - }); - DataTransfer.prototype.setData = function(type, data) { - if (this._dataStore.mode === 2) { - if (type.indexOf(" ") > -1) { - throw new Error("illegal arg: type contains space"); - } - this._dataStore.data[type] = data; - if (this._dataStore.types.indexOf(type) === -1) { - this._dataStore.types.push(type); - } - } - }; - DataTransfer.prototype.getData = function(type) { - if (this._dataStore.mode === 1 || this._dataStore.mode === 2) { - return this._dataStore.data[type] || ""; - } - }; - DataTransfer.prototype.clearData = function(format) { - if (this._dataStore.mode === 2) { - if (format && this._dataStore.data[format]) { - delete this._dataStore.data[format]; - const index = this._dataStore.types.indexOf(format); - if (index > -1) { - this._dataStore.types.splice(index, 1); - } - return; - } - this._dataStore.data = {}; - this._dataStore.types = []; - } - }; - DataTransfer.prototype.setDragImage = function(image, x, y) { - if (this._dataStore.mode === 2) { - this._setDragImageHandler(image, x, y); - } - }; - return DataTransfer; -})(); - -function tryFindDraggableTarget(event) { - let el = event.target; - do { - if (el.draggable === false) { - continue; - } - if (el.draggable === true) { - return el; - } - if (el.getAttribute && el.getAttribute("draggable") === "true") { - return el; - } - } while ((el = el.parentNode) && el !== document.body); -} -function determineDropEffect(effectAllowed, sourceNode) { - if (!effectAllowed) { - if (sourceNode.nodeType === 3 && sourceNode.tagName === "A") { - return DROP_EFFECTS[3]; - } - return DROP_EFFECTS[1]; - } - if (effectAllowed === ALLOWED_EFFECTS[0]) { - return DROP_EFFECTS[0]; - } - if (effectAllowed.indexOf(ALLOWED_EFFECTS[1]) === 0 || effectAllowed === ALLOWED_EFFECTS[7]) { - return DROP_EFFECTS[1]; - } - if (effectAllowed.indexOf(ALLOWED_EFFECTS[4]) === 0) { - return DROP_EFFECTS[3]; - } - if (effectAllowed === ALLOWED_EFFECTS[6]) { - return DROP_EFFECTS[2]; - } - return DROP_EFFECTS[1]; -} -function createDragEventFromTouch(targetElement, e, type, cancelable, window, dataTransfer, relatedTarget) { - if (relatedTarget === void 0) { - relatedTarget = null; - } - const touch = e.changedTouches[0]; - const dndEvent = new Event(type, { - bubbles: true, - cancelable: cancelable - }); - dndEvent.dataTransfer = dataTransfer; - dndEvent.relatedTarget = relatedTarget; - dndEvent.screenX = touch.screenX; - dndEvent.screenY = touch.screenY; - dndEvent.clientX = touch.clientX; - dndEvent.clientY = touch.clientY; - dndEvent.pageX = touch.pageX; - dndEvent.pageY = touch.pageY; - const targetRect = targetElement.getBoundingClientRect(); - dndEvent.offsetX = dndEvent.clientX - targetRect.left; - dndEvent.offsetY = dndEvent.clientY - targetRect.top; - return dndEvent; -} -function dispatchDragEvent(dragEvent, targetElement, touchEvent, dataStore, dataTransfer, cancelable, relatedTarget) { - if (cancelable === void 0) { - cancelable = true; - } - if (relatedTarget === void 0) { - relatedTarget = null; - } - console.log("dnd-poly: dispatching " + dragEvent); - const leaveEvt = createDragEventFromTouch(targetElement, touchEvent, dragEvent, cancelable, document.defaultView, dataTransfer, relatedTarget); - const cancelled = !targetElement.dispatchEvent(leaveEvt); - dataStore.mode = 0; - return cancelled; -} -function determineDragOperation(effectAllowed, dropEffect) { - if (!effectAllowed || effectAllowed === ALLOWED_EFFECTS[7]) { - return dropEffect; - } - if (dropEffect === DROP_EFFECTS[1]) { - if (effectAllowed.indexOf(DROP_EFFECTS[1]) === 0) { - return DROP_EFFECTS[1]; - } - } else if (dropEffect === DROP_EFFECTS[3]) { - if (effectAllowed.indexOf(DROP_EFFECTS[3]) === 0 || effectAllowed.indexOf("Link") > -1) { - return DROP_EFFECTS[3]; - } - } else if (dropEffect === DROP_EFFECTS[2]) { - if (effectAllowed.indexOf(DROP_EFFECTS[2]) === 0 || effectAllowed.indexOf("Move") > -1) { - return DROP_EFFECTS[2]; - } - } - return DROP_EFFECTS[0]; -} - -const DragOperationController = (function() { - function DragOperationController(_initialEvent, _config, _sourceNode, _dragOperationEndedCb) { - this._initialEvent = _initialEvent; - this._config = _config; - this._sourceNode = _sourceNode; - this._dragOperationEndedCb = _dragOperationEndedCb; - this._dragOperationState = 0; - this._immediateUserSelection = null; - this._currentDropTarget = null; - console.log("dnd-poly: setting up potential drag operation.."); - this._lastTouchEvent = _initialEvent; - this._initialTouch = _initialEvent.changedTouches[0]; - this._touchMoveHandler = this._onTouchMove.bind(this); - this._touchEndOrCancelHandler = this._onTouchEndOrCancel.bind(this); - addDocumentListener("touchmove", this._touchMoveHandler, false); - addDocumentListener("touchend", this._touchEndOrCancelHandler, false); - addDocumentListener("touchcancel", this._touchEndOrCancelHandler, false); - } - DragOperationController.prototype._setup = function() { - const _this = this; - console.log("dnd-poly: starting drag and drop operation"); - this._dragOperationState = 1; - this._currentDragOperation = DROP_EFFECTS[0]; - this._dragDataStore = { - data: {}, - effectAllowed: undefined, - mode: 3, - types: [] - }; - this._currentHotspotCoordinates = { - x: null, - y: null - }; - this._dragImagePageCoordinates = { - x: null, - y: null - }; - let dragImageSrc = this._sourceNode; - this._dataTransfer = new DataTransfer(this._dragDataStore, function(element, x, y) { - dragImageSrc = element; - if (typeof x === "number" || typeof y === "number") { - _this._dragImageOffset = { - x: x || 0, - y: y || 0 - }; - } - }); - this._dragDataStore.mode = 2; - this._dataTransfer.dropEffect = DROP_EFFECTS[0]; - if (dispatchDragEvent("dragstart", this._sourceNode, this._lastTouchEvent, this._dragDataStore, this._dataTransfer)) { - console.log("dnd-poly: dragstart cancelled"); - this._dragOperationState = 3; - this._cleanup(); - return false; - } - updateCentroidCoordinatesOfTouchesIn("page", this._lastTouchEvent, this._dragImagePageCoordinates); - const dragImage = this._config.dragImageSetup(dragImageSrc); - this._dragImageTransforms = extractTransformStyles(dragImage); - dragImage.style.position = "absolute"; - dragImage.style.left = "0px"; - dragImage.style.top = "0px"; - dragImage.style.zIndex = "999999"; - dragImage.classList.add(CLASS_DRAG_IMAGE); - dragImage.classList.add(CLASS_DRAG_OPERATION_ICON); - this._dragImage = dragImage; - if (!this._dragImageOffset) { - if (this._config.dragImageOffset) { - this._dragImageOffset = { - x: this._config.dragImageOffset.x, - y: this._config.dragImageOffset.y - }; - } else if (this._config.dragImageCenterOnTouch) { - const cs = getComputedStyle(dragImageSrc); - this._dragImageOffset = { - x: 0 - parseInt(cs.marginLeft, 10), - y: 0 - parseInt(cs.marginTop, 10) - }; - } else { - const targetRect = dragImageSrc.getBoundingClientRect(); - const cs = getComputedStyle(dragImageSrc); - this._dragImageOffset = { - x: targetRect.left - this._initialTouch.clientX - parseInt(cs.marginLeft, 10) + targetRect.width / 2, - y: targetRect.top - this._initialTouch.clientY - parseInt(cs.marginTop, 10) + targetRect.height / 2 - }; - } - } - translateElementToPoint(this._dragImage, this._dragImagePageCoordinates, this._dragImageTransforms, this._dragImageOffset, this._config.dragImageCenterOnTouch); - document.body.appendChild(this._dragImage); - this._iterationIntervalId = window.setInterval(function() { - if (_this._iterationLock) { - console.log("dnd-poly: iteration skipped because previous iteration hast not yet finished."); - return; - } - _this._iterationLock = true; - _this._dragAndDropProcessModelIteration(); - _this._iterationLock = false; - }, this._config.iterationInterval); - return true; - }; - DragOperationController.prototype._cleanup = function() { - console.log("dnd-poly: cleanup"); - if (this._iterationIntervalId) { - clearInterval(this._iterationIntervalId); - this._iterationIntervalId = null; - } - removeDocumentListener("touchmove", this._touchMoveHandler); - removeDocumentListener("touchend", this._touchEndOrCancelHandler); - removeDocumentListener("touchcancel", this._touchEndOrCancelHandler); - if (this._dragImage) { - this._dragImage.parentNode.removeChild(this._dragImage); - this._dragImage = null; - } - this._dragOperationEndedCb(this._config, this._lastTouchEvent, this._dragOperationState); - }; - DragOperationController.prototype._onTouchMove = function(event) { - const _this = this; - if (isTouchIdentifierContainedInTouchEvent(event, this._initialTouch.identifier) === false) { - return; - } - this._lastTouchEvent = event; - if (this._dragOperationState === 0) { - let startDrag = void 0; - if (this._config.dragStartConditionOverride) { - try { - startDrag = this._config.dragStartConditionOverride(event); - } catch (e) { - console.error("dnd-poly: error in dragStartConditionOverride hook: " + e); - startDrag = false; - } - } else { - startDrag = event.touches.length === 1; - } - if (!startDrag) { - this._cleanup(); - return; - } - if (this._setup() === true) { - this._initialEvent.preventDefault(); - event.preventDefault(); - } - return; - } - console.log("dnd-poly: moving draggable.."); - event.preventDefault(); - updateCentroidCoordinatesOfTouchesIn("client", event, this._currentHotspotCoordinates); - updateCentroidCoordinatesOfTouchesIn("page", event, this._dragImagePageCoordinates); - if (this._config.dragImageTranslateOverride) { - try { - let handledDragImageTranslate_1 = false; - this._config.dragImageTranslateOverride( - event, - { - x: this._currentHotspotCoordinates.x, - y: this._currentHotspotCoordinates.y - }, - this._immediateUserSelection, - function(offsetX, offsetY) { - if (!_this._dragImage) { - return; - } - handledDragImageTranslate_1 = true; - _this._currentHotspotCoordinates.x += offsetX; - _this._currentHotspotCoordinates.y += offsetY; - _this._dragImagePageCoordinates.x += offsetX; - _this._dragImagePageCoordinates.y += offsetY; - translateElementToPoint(_this._dragImage, _this._dragImagePageCoordinates, _this._dragImageTransforms, _this._dragImageOffset, _this._config.dragImageCenterOnTouch); - } - ); - if (handledDragImageTranslate_1) { - return; - } - } catch (e) { - console.log("dnd-poly: error in dragImageTranslateOverride hook: " + e); - } - } - translateElementToPoint(this._dragImage, this._dragImagePageCoordinates, this._dragImageTransforms, this._dragImageOffset, this._config.dragImageCenterOnTouch); - }; - DragOperationController.prototype._onTouchEndOrCancel = function(event) { - if (isTouchIdentifierContainedInTouchEvent(event, this._initialTouch.identifier) === false) { - return; - } - if (this._config.dragImageTranslateOverride) { - try { - this._config.dragImageTranslateOverride(undefined, undefined, undefined, function() {}); - } catch (e) { - console.log("dnd-poly: error in dragImageTranslateOverride hook: " + e); - } - } - if (this._dragOperationState === 0) { - this._cleanup(); - return; - } - event.preventDefault(); - this._dragOperationState = event.type === "touchcancel" ? 3 : 2; - }; - DragOperationController.prototype._dragAndDropProcessModelIteration = function() { - const _this = this; - const previousDragOperation = this._currentDragOperation; - this._dragDataStore.mode = 3; - this._dataTransfer.dropEffect = DROP_EFFECTS[0]; - const dragCancelled = dispatchDragEvent("drag", this._sourceNode, this._lastTouchEvent, this._dragDataStore, this._dataTransfer); - if (dragCancelled) { - console.log("dnd-poly: drag event cancelled."); - this._currentDragOperation = DROP_EFFECTS[0]; - } - if (dragCancelled || this._dragOperationState === 2 || this._dragOperationState === 3) { - const dragFailed = this._dragOperationEnded(this._dragOperationState); - if (dragFailed) { - applyDragImageSnapback(this._sourceNode, this._dragImage, this._dragImageTransforms, function() { - _this._finishDragOperation(); - }); - return; - } - this._finishDragOperation(); - return; - } - const newUserSelection = this._config.elementFromPoint(this._currentHotspotCoordinates.x, this._currentHotspotCoordinates.y); - console.log("dnd-poly: new immediate user selection is: " + newUserSelection); - const previousTargetElement = this._currentDropTarget; - if (newUserSelection !== this._immediateUserSelection && newUserSelection !== this._currentDropTarget) { - this._immediateUserSelection = newUserSelection; - if (this._currentDropTarget !== null) { - this._dragDataStore.mode = 3; - this._dataTransfer.dropEffect = DROP_EFFECTS[0]; - dispatchDragEvent("dragexit", this._currentDropTarget, this._lastTouchEvent, this._dragDataStore, this._dataTransfer, false); - } - if (this._immediateUserSelection === null) { - this._currentDropTarget = this._immediateUserSelection; - console.log("dnd-poly: current drop target changed to null"); - } else { - this._dragDataStore.mode = 3; - this._dataTransfer.dropEffect = determineDropEffect(this._dragDataStore.effectAllowed, this._sourceNode); - if (dispatchDragEvent("dragenter", this._immediateUserSelection, this._lastTouchEvent, this._dragDataStore, this._dataTransfer)) { - console.log("dnd-poly: dragenter default prevented"); - this._currentDropTarget = this._immediateUserSelection; - this._currentDragOperation = determineDragOperation(this._dataTransfer.effectAllowed, this._dataTransfer.dropEffect); - } else { - if (this._immediateUserSelection !== document.body) { - this._currentDropTarget = document.body; - } - } - } - } - if (previousTargetElement !== this._currentDropTarget && isDOMElement(previousTargetElement)) { - console.log("dnd-poly: current drop target changed."); - this._dragDataStore.mode = 3; - this._dataTransfer.dropEffect = DROP_EFFECTS[0]; - dispatchDragEvent("dragleave", previousTargetElement, this._lastTouchEvent, this._dragDataStore, this._dataTransfer, false, this._currentDropTarget); - } - if (isDOMElement(this._currentDropTarget)) { - this._dragDataStore.mode = 3; - this._dataTransfer.dropEffect = determineDropEffect(this._dragDataStore.effectAllowed, this._sourceNode); - if (dispatchDragEvent("dragover", this._currentDropTarget, this._lastTouchEvent, this._dragDataStore, this._dataTransfer) === false) { - console.log("dnd-poly: dragover not prevented on possible drop-target."); - this._currentDragOperation = DROP_EFFECTS[0]; - } else { - console.log("dnd-poly: dragover prevented."); - this._currentDragOperation = determineDragOperation(this._dataTransfer.effectAllowed, this._dataTransfer.dropEffect); - } - } - console.log("dnd-poly: d'n'd iteration ended. current drag operation: " + this._currentDragOperation); - if (previousDragOperation !== this._currentDragOperation) { - this._dragImage.classList.remove(CLASS_PREFIX + previousDragOperation); - } - const currentDragOperationClass = CLASS_PREFIX + this._currentDragOperation; - this._dragImage.classList.add(currentDragOperationClass); - }; - DragOperationController.prototype._dragOperationEnded = function(state) { - console.log("dnd-poly: drag operation end detected with " + this._currentDragOperation); - const dragFailed = this._currentDragOperation === DROP_EFFECTS[0] || this._currentDropTarget === null || state === 3; - if (dragFailed) { - if (isDOMElement(this._currentDropTarget)) { - this._dragDataStore.mode = 3; - this._dataTransfer.dropEffect = DROP_EFFECTS[0]; - dispatchDragEvent("dragleave", this._currentDropTarget, this._lastTouchEvent, this._dragDataStore, this._dataTransfer, false); - } - } else { - if (isDOMElement(this._currentDropTarget)) { - this._dragDataStore.mode = 1; - this._dataTransfer.dropEffect = this._currentDragOperation; - if (dispatchDragEvent("drop", this._currentDropTarget, this._lastTouchEvent, this._dragDataStore, this._dataTransfer) === true) { - this._currentDragOperation = this._dataTransfer.dropEffect; - } else { - this._currentDragOperation = DROP_EFFECTS[0]; - } - } - } - return dragFailed; - }; - DragOperationController.prototype._finishDragOperation = function() { - console.log("dnd-poly: dragimage snap back transition ended"); - this._dragDataStore.mode = 3; - this._dataTransfer.dropEffect = this._currentDragOperation; - dispatchDragEvent("dragend", this._sourceNode, this._lastTouchEvent, this._dragDataStore, this._dataTransfer, false); - this._dragOperationState = 2; - this._cleanup(); - }; - return DragOperationController; -})(); - -const config = { - iterationInterval: 150, - tryFindDraggableTarget: tryFindDraggableTarget, - dragImageSetup: createDragImage, - elementFromPoint: function(x, y) { - return document.elementFromPoint(x, y); - } -}; -let activeDragOperation; -function onTouchstart(e, composePath) { - console.log("dnd-poly: global touchstart"); - if (activeDragOperation) { - console.log("dnd-poly: drag operation already active"); - return; - } - const dragTarget = config.tryFindDraggableTarget(e, composePath); - if (!dragTarget) { - console.log("dnd-poly: no draggable at touchstart coordinates"); - return; - } - try { - activeDragOperation = new DragOperationController(e, config, dragTarget, dragOperationEnded); - } catch (err) { - dragOperationEnded(config, e, 3); - throw err; - } -} -function onDelayTouchstart(evt) { - console.log("dnd-poly: setup delayed dragstart.."); - const el = evt.target; - const composePath = evt.composedPath(); - const heldItem = function() { - console.log("dnd-poly: starting delayed drag.."); - end.off(); - cancel.off(); - move.off(); - scroll.off(); - onTouchstart(evt, composePath); - }; - const onReleasedItem = function(event) { - console.log("dnd-poly: aborting delayed drag because of " + event.type); - end.off(); - cancel.off(); - move.off(); - scroll.off(); - if (el) { - el.dispatchEvent(new CustomEvent(EVENT_DRAG_DRAGSTART_CANCEL, {bubbles: true, cancelable: true})); - } - clearTimeout(timer); - }; - if (el) { - el.dispatchEvent(new CustomEvent(EVENT_DRAG_DRAGSTART_PENDING, {bubbles: true, cancelable: true})); - } - const timer = window.setTimeout(heldItem, config.holdToDrag); - const end = onEvt(el, "touchend", onReleasedItem); - const cancel = onEvt(el, "touchcancel", onReleasedItem); - const move = onEvt(el, "touchmove", onReleasedItem); - const scroll = onEvt(window, "scroll", onReleasedItem, true); -} - -function dragOperationEnded(_config, event, state) { - if (state === 0) { - console.log("dnd-poly: Drag never started. Last event was " + event.type); - if (_config.defaultActionOverride) { - try { - _config.defaultActionOverride(event); - if (event.defaultPrevented) { - console.log("dnd-poly: defaultActionOverride has taken care of triggering the default action. preventing default on original event"); - } - } catch (e) { - console.log("dnd-poly: error in defaultActionOverride: " + e); - } - } - } - activeDragOperation = null; -} - -function polyfill(override) { - if (override) { - Object.keys(override).forEach(function(key) { - config[key] = override[key]; - }); - } - if (!config.forceApply) { - const detectedFeatures = detectFeatures(); - if (detectedFeatures.userAgentSupportingNativeDnD && detectedFeatures.draggable && detectedFeatures.dragEvents) { - return false; - } - } - console.log("dnd-poly: Applying mobile drag and drop polyfill."); - if (config.holdToDrag) { - console.log("dnd-poly: holdToDrag set to " + config.holdToDrag); - addDocumentListener("touchstart", onDelayTouchstart, false); - } else { - addDocumentListener("touchstart", onTouchstart, false); - } - return true; -} - -function tryFindDraggableTarget_override(event, composePath) { - const cp = composePath || event.composedPath(); - for (let o of cp) { - let el = o; - do { - if (el.draggable === false) { - continue; - } - if (el.getAttribute && el.getAttribute("draggable") === "true") { - return el; - } - } while ((el = el.parentNode) && el !== document.body); - } -} - -function elementFromPoint(x, y) { - for (let o of this._path) { - if (o.elementFromPoint) { - let el = o.elementFromPoint(x, y); - if (el) { - while (el.shadowRoot) { - el = el.shadowRoot.elementFromPoint(x, y); - } - return el; - } - } - } -} - -function dragStartConditionOverride(event) { - this._path = event.composedPath(); - return true; -} - -window.addEventListener("touchmove", function() {}); - -polyfill({ - tryFindDraggableTarget: tryFindDraggableTarget_override, - elementFromPoint: elementFromPoint, - dragStartConditionOverride: dragStartConditionOverride, - holdToDrag: 500 -}); diff --git a/rust/perspective-viewer/src/ts/index.ts b/rust/perspective-viewer/src/ts/index.ts index 913fdefc18..edc132ad20 100644 --- a/rust/perspective-viewer/src/ts/index.ts +++ b/rust/perspective-viewer/src/ts/index.ts @@ -8,6 +8,7 @@ * */ +import "mobile-drag-drop-shadow-dom"; import init, * as internal from "../../dist/pkg/perspective_viewer.js"; import * as perspective from "@finos/perspective"; From 9a5a5d40ca0445d99326d4348949290ed781de6e Mon Sep 17 00:00:00 2001 From: Andrew Stein Date: Sat, 4 Sep 2021 13:13:01 -0400 Subject: [PATCH 3/4] Fix `@finos/perspective` hand-coded types --- examples/react/src/index.tsx | 9 +- .../src/ts/psp_widget.ts | 67 ++++---- .../perspective-jupyterlab/src/ts/renderer.ts | 10 +- .../perspective-jupyterlab/src/ts/widget.ts | 4 +- packages/perspective/index.d.ts | 143 ++++++------------ packages/perspective/package.json | 2 +- .../src/rust/config/filters.rs | 5 + rust/perspective-viewer/src/ts/index.ts | 7 +- 8 files changed, 100 insertions(+), 147 deletions(-) diff --git a/examples/react/src/index.tsx b/examples/react/src/index.tsx index 01491e092b..bc008ec383 100644 --- a/examples/react/src/index.tsx +++ b/examples/react/src/index.tsx @@ -14,9 +14,10 @@ import * as perspective from "@finos/perspective"; import "@finos/perspective-viewer"; import "@finos/perspective-viewer-datagrid"; import "@finos/perspective-viewer-d3fc"; -import "./index.css"; +import {PerspectiveViewerConfig, PerspectiveViewerElement} from "@finos/perspective-viewer"; + import "@finos/perspective-viewer/dist/umd/material-dense.css"; -import {PerspectiveViewerElement} from "@finos/perspective-viewer"; +import "./index.css"; const worker = perspective.default.shared_worker(); @@ -27,7 +28,7 @@ const getTable = async (): Promise => { return await worker.table(buffer as any); }; -const config = { +const config: PerspectiveViewerConfig = { row_pivots: ["State"] }; @@ -44,7 +45,7 @@ const App = (): React.ReactElement => { }, []); // You can also the use the stringified config values as attributes - return ; + return ; }; window.addEventListener("load", () => { diff --git a/packages/perspective-jupyterlab/src/ts/psp_widget.ts b/packages/perspective-jupyterlab/src/ts/psp_widget.ts index 391107f60a..02d1b2b1f0 100644 --- a/packages/perspective-jupyterlab/src/ts/psp_widget.ts +++ b/packages/perspective-jupyterlab/src/ts/psp_widget.ts @@ -9,16 +9,16 @@ import "@finos/perspective-viewer"; -import {Table, TableData} from "@finos/perspective"; +import {Table, TableData, Aggregate, Sort, Expression, Filter, ColumnName} from "@finos/perspective"; import {Message} from "@lumino/messaging"; import {Widget} from "@lumino/widgets"; import {MIME_TYPE, PSP_CLASS, PSP_CONTAINER_CLASS, PSP_CONTAINER_CLASS_DARK} from "./utils"; -import {HTMLPerspectiveViewerElement, Pivots, Aggregates, Sort, Expressions, PerspectiveViewerOptions, Filters, Columns} from "@finos/perspective-viewer"; +import {PerspectiveViewerElement, PerspectiveViewerConfig} from "@finos/perspective-viewer"; let _increment = 0; -export interface PerspectiveWidgetOptions extends PerspectiveViewerOptions { +export interface PerspectiveWidgetOptions extends PerspectiveViewerConfig { dark?: boolean; client?: boolean; server?: boolean; @@ -27,9 +27,9 @@ export interface PerspectiveWidgetOptions extends PerspectiveViewerOptions { // these shouldn't exist, PerspectiveViewerOptions should be sufficient e.g. // ["row-pivots"] - column_pivots?: Pivots; - row_pivots?: Pivots; - expressions?: Expressions; + column_pivots?: Array; + row_pivots?: Array; + expressions?: Array; editable?: boolean; } @@ -56,21 +56,21 @@ export class PerspectiveWidget extends Widget { * * @param options */ - _set_attributes(options: PerspectiveViewerOptions & PerspectiveWidgetOptions): void { + _set_attributes(options: PerspectiveViewerConfig & PerspectiveWidgetOptions): void { const plugin: string = options.plugin || "datagrid"; - const columns: Columns = options.columns || []; - const row_pivots: Pivots = options.row_pivots || options.row_pivots || []; - const column_pivots: Pivots = options.column_pivots || options.column_pivots || []; - const aggregates: Aggregates = options.aggregates || {}; - const sort: Sort = options.sort || []; - const filter: Filters = options.filter || []; - const expressions: Expressions = options.expressions || options.expressions || []; + const columns: ColumnName[] = options.columns || []; + const row_pivots: ColumnName[] = options.row_pivots || options.row_pivots || []; + const column_pivots: ColumnName[] = options.column_pivots || options.column_pivots || []; + const aggregates: {[column: string]: Aggregate} = options.aggregates || {}; + const sort: Sort[] = options.sort || []; + const filter: Filter[] = options.filter || []; + const expressions: Expression[] = options.expressions || options.expressions || []; const plugin_config: object = options.plugin_config || {}; const dark: boolean = options.dark || false; const editable: boolean = options.editable || false; const server: boolean = options.server || false; const client: boolean = options.client || false; - const selectable: boolean = options.selectable || false; + // const selectable: boolean = options.selectable || false; this.server = server; this.client = client; @@ -89,7 +89,7 @@ export class PerspectiveWidget extends Widget { }; // this.plugin_config = plugin_config; - this.selectable = selectable; + // this.selectable = selectable; } /**********************/ @@ -122,22 +122,18 @@ export class PerspectiveWidget extends Widget { } async notifyResize(): Promise { - if (this.isVisible) { - await this.viewer.notifyResize(); - } + await this.viewer.notifyResize(); } async toggleConfig(): Promise { - if (this.isVisible) { - await this.viewer.toggleConfig(); - } + await this.viewer.toggleConfig(); } - async save(): Promise { + async save(): Promise { return await this.viewer.save(); } - async restore(config: PerspectiveViewerOptions): Promise { + async restore(config: PerspectiveViewerConfig): Promise { return await this.viewer.restore(config); } @@ -156,15 +152,17 @@ export class PerspectiveWidget extends Widget { * * @param data */ - _update(data: TableData): void { - this.viewer.table.update(data); + async _update(data: TableData): Promise { + const table = await this.viewer.getTable(); + await table.update(data); } /** * Removes all rows from the viewer's table. Does not reset viewer state. */ async clear(): Promise { - await this.viewer.table.clear(); + const table = await this.viewer.getTable(); + await table.clear(); } /** @@ -174,7 +172,8 @@ export class PerspectiveWidget extends Widget { * @param data */ async replace(data: TableData): Promise { - await this.viewer.table.replace(data); + const table = await this.viewer.getTable(); + await table.replace(data); } /** @@ -194,8 +193,8 @@ export class PerspectiveWidget extends Widget { return await this.viewer.getEditPort(); } - get table(): Table { - return this.viewer.table; + async getTable(): Promise
Load perspective.tableLoad Promise
{ + return await this.viewer.getTable(); } /*************************************************************************** @@ -209,7 +208,7 @@ export class PerspectiveWidget extends Widget { * * @returns {PerspectiveViewer} The widget's viewer instance. */ - get viewer(): HTMLPerspectiveViewerElement { + get viewer(): PerspectiveViewerElement { return this._viewer; } @@ -306,7 +305,7 @@ export class PerspectiveWidget extends Widget { } } - static createNode(node: HTMLDivElement): HTMLPerspectiveViewerElement { + static createNode(node: HTMLDivElement): PerspectiveViewerElement { node.classList.add("p-Widget"); node.classList.add(PSP_CONTAINER_CLASS); const viewer = document.createElement("perspective-viewer"); @@ -342,11 +341,11 @@ export class PerspectiveWidget extends Widget { return viewer; } - private _viewer: HTMLPerspectiveViewerElement; + private _viewer: PerspectiveViewerElement; private _plugin_config: object; private _client: boolean; private _server: boolean; private _dark: boolean; private _editable: boolean; - private _viewer_config: PerspectiveViewerOptions; + private _viewer_config: PerspectiveViewerConfig; } diff --git a/packages/perspective-jupyterlab/src/ts/renderer.ts b/packages/perspective-jupyterlab/src/ts/renderer.ts index 06dfb40c65..42055d84f7 100644 --- a/packages/perspective-jupyterlab/src/ts/renderer.ts +++ b/packages/perspective-jupyterlab/src/ts/renderer.ts @@ -83,7 +83,10 @@ export class PerspectiveDocumentWidget extends DocumentWidget throw "Not handled"; } - if (this._psp.viewer.table === undefined) { + try { + const table = await this._psp.viewer.getTable(); + table.replace(data); + } catch (e) { // construct new table const table_promise = WORKER.table(data); @@ -109,10 +112,7 @@ export class PerspectiveDocumentWidget extends DocumentWidget this.context.model.fromJSON(result); this.context.save(); } - }); - } else { - // replace existing table for whatever reason - this._psp.replace(data); + }); } } catch (e) { baddialog(); diff --git a/packages/perspective-jupyterlab/src/ts/widget.ts b/packages/perspective-jupyterlab/src/ts/widget.ts index e01dc99b7f..7058cb9564 100644 --- a/packages/perspective-jupyterlab/src/ts/widget.ts +++ b/packages/perspective-jupyterlab/src/ts/widget.ts @@ -10,7 +10,7 @@ import {Message} from "@lumino/messaging"; import {DOMWidgetView} from "@jupyter-widgets/base"; -import {PerspectiveViewerOptions} from "@finos/perspective-viewer"; +import {PerspectiveViewerConfig} from "@finos/perspective-viewer"; import {PerspectiveWidget, PerspectiveWidgetOptions} from "./psp_widget"; export type PerspectiveJupyterWidgetOptions = { @@ -21,7 +21,7 @@ export type PerspectiveJupyterWidgetOptions = { * PerspectiveJupyterWidget is the ipywidgets front-end for the Perspective Jupyterlab plugin. */ export class PerspectiveJupyterWidget extends PerspectiveWidget { - constructor(name = "Perspective", options: PerspectiveViewerOptions & PerspectiveJupyterWidgetOptions & PerspectiveWidgetOptions) { + constructor(name = "Perspective", options: PerspectiveViewerConfig & PerspectiveJupyterWidgetOptions & PerspectiveWidgetOptions) { const view = options.view; delete options.view; super(name, options); diff --git a/packages/perspective/index.d.ts b/packages/perspective/index.d.ts index e4ea8fc04c..73e65c8e25 100644 --- a/packages/perspective/index.d.ts +++ b/packages/perspective/index.d.ts @@ -1,85 +1,39 @@ declare module "@finos/perspective" { import * as ws from "ws"; - /**** object types ****/ - export enum TypeNames { - STRING = "string", - FLOAT = "float", - INTEGER = "integer", - BOOLEAN = "boolean", - DATE = "date", - DATETIME = "datetime", - OBJECT = "object" - } - - export type ValuesByType = { - [key in TypeNames]: Array; - }; - - export type ValueByType = { - TypeNames: Array; - }; - - export enum SortOrders { - ASC = "asc", - ASC_ABS = "asc abs", - DESC = "desc", - DESC_ABS = "desc abs", - NONE = "none" - } - - enum NUMBER_AGGREGATES { - ABS_SUM = "abs sum", - ANY = "any", - AVERAGE = "avg", - COUNT = "count", - DISTINCT_COUNT = "distinct count", - DOMINANT = "dominant", - FIRST = "first", - LAST = "last", - HIGH = "high", - LOW = "low", - MEAN = "mean", - MEDIAN = "median", - PCT_SUM_PARENT = "pct sum parent", - PCT_SUM_TOTAL = "pct sum grand total", - STANDARD_DEVIATION = "stddev", - SUM = "sum", - SUM_ABS = "sum abs", - SUM_NOT_NULL = "sum not null", - UNIQUE = "unique", - VARIANCE = "var" - } - - enum STRING_AGGREGATES { - ANY = "any", - COUNT = "count", - DISTINCT_COUNT = "distinct count", - DISTINCT_LEAF = "distinct leaf", - DOMINANT = "dominant", - FIRST = "first", - LAST = "last", - UNIQUE = "unique" - } - - enum BOOLEAN_AGGREGATES { - AND = "and", - ANY = "any", - COUNT = "count", - DISTINCT_COUNT = "distinct count", - DISTINCT_LEAF = "distinct leaf", - DOMINANT = "dominant", - FIRST = "first", - LAST = "last", - OR = "or", - UNIQUE = "unique" - } - - /**** Schema ****/ - type SchemaType = TypeNames | NUMBER_AGGREGATES | STRING_AGGREGATES | BOOLEAN_AGGREGATES; + export type Type = "string" | "float" | "integer" | "boolean" | "date" | "datetime" | "object"; + + export type SortDir = "asc" | "asc abs" | "desc" | "desc abs" | "col asc" | "col asc abs" | "col desc" | "col desc abs"; + + export type Aggregate = + | "abs sum" + | "and" + | "any" + | "avg" + | "count" + | "distinct count" + | "distinct leaf" + | "dominant" + | "first" + | "high" + | "last" + | "low" + | "or" + | "median" + | "pct sum parent" + | "pct sum grand total" + | "stddev" + | "sum" + | "sum abs" + | "sum not null" + | "unique" + | "var" + | ["weighted mean", ColumnName]; + + export type FilterOp = "<" | ">" | "<=" | ">=" | "==" | "!=" | "is null" | "is not null" | "in" | "not in" | "begins with" | "contains"; export type Schema = { - [key: string]: TypeNames; + [key: ColumnName]: Type; }; export interface SerializeConfig { @@ -127,14 +81,19 @@ declare module "@finos/perspective" { limit?: number; }; + export type ColumnName = string; + export type Expression = string; + export type Filter = [ColumnName, FilterOp, string | number | Date | boolean | Array]; + export type Sort = [ColumnName, SortDir]; + export type ViewConfig = { - columns?: Array; - row_pivots?: Array; - column_pivots?: Array; - aggregates?: {[column_name: string]: string}; - sort?: Array>; - filter?: Array>; - expressions?: Array; + columns?: Array; + row_pivots?: Array; + column_pivots?: Array; + aggregates?: {[column_name: string]: Aggregate}; + sort?: Array; + filter?: Array; + expressions?: Array; }; export type Table = { @@ -192,9 +151,6 @@ declare module "@finos/perspective" { export function perspective_assets(assets: string[], host_psp: boolean): (request: any, response: any) => void; type perspective = { - TYPE_AGGREGATES: ValuesByType; - TYPE_FILTERS: ValuesByType; - SORT_ORDERS: SortOrders; table(data_or_schema: TableData | Schema, options?: TableOptions): Promise
; worker(): PerspectiveWorker; shared_worker(): PerspectiveWorker; @@ -206,16 +162,3 @@ declare module "@finos/perspective" { export default impl; } - -declare module "@finos/perspective/build/psp.async.wasm" { - const impl: ArrayBuffer; - export default impl; -} - -declare module "@finos/perspective/build/psp.sync.wasm" { - const impl: ArrayBuffer; - export default impl; -} - -declare module "@finos/perspective/build/perspective.wasm.worker.js" {} -declare module "@finos/perspective/build/perspective.asmjs.worker.js" {} diff --git a/packages/perspective/package.json b/packages/perspective/package.json index 5b511feb23..c3635776c6 100644 --- a/packages/perspective/package.json +++ b/packages/perspective/package.json @@ -18,7 +18,7 @@ "index.d.ts", "babel.config.js" ], - "typings": "index.d.ts", + "types": "index.d.ts", "scripts": { "build:babel:esm": "babel src/js --source-maps --out-dir dist/esm", "build:babel:cjs": "BABEL_MODULE=auto babel dist/esm --source-maps --out-dir dist/cjs", diff --git a/rust/perspective-viewer/src/rust/config/filters.rs b/rust/perspective-viewer/src/rust/config/filters.rs index e33f69cdf3..433157a3e8 100644 --- a/rust/perspective-viewer/src/rust/config/filters.rs +++ b/rust/perspective-viewer/src/rust/config/filters.rs @@ -46,6 +46,9 @@ pub enum FilterOp { #[serde(rename = "contains")] Contains, + #[serde(rename = "not in")] + NotIn, + #[serde(rename = "in")] In, @@ -88,6 +91,7 @@ impl Display for FilterOp { let op = match self { FilterOp::Contains => "contains", FilterOp::In => "in", + FilterOp::NotIn => "not in", FilterOp::BeginsWith => "begins with", FilterOp::EndsWith => "ends with", FilterOp::IsNull => "is null", @@ -112,6 +116,7 @@ impl FromStr for FilterOp { match input { "contains" => Ok(FilterOp::Contains), "in" => Ok(FilterOp::In), + "not in" => Ok(FilterOp::NotIn), "begins with" => Ok(FilterOp::BeginsWith), "ends with" => Ok(FilterOp::EndsWith), "is null" => Ok(FilterOp::IsNull), diff --git a/rust/perspective-viewer/src/ts/index.ts b/rust/perspective-viewer/src/ts/index.ts index edc132ad20..202b7eaf6a 100644 --- a/rust/perspective-viewer/src/ts/index.ts +++ b/rust/perspective-viewer/src/ts/index.ts @@ -57,6 +57,7 @@ async function init_wasm({default: wasm_module}) { export type PerspectiveViewerConfig = perspective.ViewConfig & { plugin?: string; settings?: boolean; + plugin_config?: object; }; export class PerspectiveViewerElement extends HTMLElement { @@ -185,6 +186,10 @@ export class PerspectiveViewerElement extends HTMLElement { * to infer format. * @returns {object} a serialized element. */ + async save(): Promise; + async save(format: "json"): Promise; + async save(format: "arraybuffer"): Promise; + async save(format: "string"): Promise; async save(format?: "json" | "arraybuffer" | "string"): Promise { await this.load_wasm(); const config = await this.instance.js_save(format); @@ -270,7 +275,7 @@ export class PerspectiveViewerElement extends HTMLElement { * @param force If supplied, explicitly set the config state to "open" (`true`) * or "closed" (`false`). */ - async toggleConfig(force): Promise { + async toggleConfig(force?: boolean): Promise { await this.load_wasm(); await this.instance.js_toggle_config(force); } From f7f6097db4f5cdcbbec76b9a6b401fcfdc9c6fa8 Mon Sep 17 00:00:00 2001 From: Andrew Stein Date: Sun, 5 Sep 2021 19:10:27 -0400 Subject: [PATCH 4/4] Add `typedoc` generated docs --- examples/react/package.json | 3 +- examples/react/src/index.css | 6 +- examples/react/src/index.tsx | 2 - package.json | 9 +- packages/perspective-jupyterlab/package.json | 3 +- packages/perspective/index.d.ts | 2 +- rust/perspective-viewer/README.md | 673 +++++++++++++++++++ rust/perspective-viewer/concat-md.js | 66 ++ rust/perspective-viewer/package.json | 5 +- rust/perspective-viewer/rollup.config.js | 8 +- rust/perspective-viewer/src/ts/index.ts | 331 ++++++--- rust/perspective-viewer/tsconfig.json | 15 + yarn.lock | 181 ++--- 13 files changed, 1120 insertions(+), 184 deletions(-) create mode 100644 rust/perspective-viewer/README.md create mode 100644 rust/perspective-viewer/concat-md.js create mode 100644 rust/perspective-viewer/tsconfig.json diff --git a/examples/react/package.json b/examples/react/package.json index 48124c2e6f..cf6789f3c4 100644 --- a/examples/react/package.json +++ b/examples/react/package.json @@ -22,7 +22,6 @@ "@types/react": "^16.8.6", "@types/react-dom": "^16.9.4", "source-map-loader": "^0.2.4", - "ts-loader": "^6.2.1", - "typescript": "^3.7.4" + "ts-loader": "^6.2.1" } } diff --git a/examples/react/src/index.css b/examples/react/src/index.css index 00e2c3884c..485f4aa6c6 100644 --- a/examples/react/src/index.css +++ b/examples/react/src/index.css @@ -6,12 +6,14 @@ * the Apache License 2.0. The full license can be found in the LICENSE file. * */ + + @import "~@finos/perspective-viewer/dist/umd/material-dense.css"; - perspective-viewer { +perspective-viewer { position: absolute; top: 0; left: 0; right: 0; bottom: 0; - } +} \ No newline at end of file diff --git a/examples/react/src/index.tsx b/examples/react/src/index.tsx index bc008ec383..2a7b250936 100644 --- a/examples/react/src/index.tsx +++ b/examples/react/src/index.tsx @@ -16,7 +16,6 @@ import "@finos/perspective-viewer-datagrid"; import "@finos/perspective-viewer-d3fc"; import {PerspectiveViewerConfig, PerspectiveViewerElement} from "@finos/perspective-viewer"; -import "@finos/perspective-viewer/dist/umd/material-dense.css"; import "./index.css"; const worker = perspective.default.shared_worker(); @@ -44,7 +43,6 @@ const App = (): React.ReactElement => { }); }, []); - // You can also the use the stringified config values as attributes return ; }; diff --git a/package.json b/package.json index 0a4611cb6d..42a3dfe3db 100644 --- a/package.json +++ b/package.json @@ -73,11 +73,7 @@ "npm-pack-here": "^1.2.0", "npm-run-all": "^4.1.3", "postcss": "^8.2.6", - "postcss-copy-assets": "^0.3.1", - "postcss-easy-import": "^3.0.0", - "postcss-font-grabber": "^3.0.2", "postcss-loader": "^4.1.0", - "postcss-url": "^10.1.3", "pre-commit": "^1.2.2", "prettier": "^1.19.1", "puppeteer": "^10.2.0", @@ -92,7 +88,10 @@ "term-img": "^4.1.0", "ts-jest": "^25.1.0", "ts-loader": "^6.2.0", - "typescript": "^3.7.4", + "typedoc": "^0.21.9", + "typedoc-plugin-markdown": "^3.10.4", + "typedoc-plugin-no-inherit": "^1.3.0", + "typescript": "4.3.3", "webfontloader": "^1.6.28", "webpack": "^5.14.0", "webpack-cli": "^4.3.1", diff --git a/packages/perspective-jupyterlab/package.json b/packages/perspective-jupyterlab/package.json index 0d3f02fcd8..58dfe20488 100644 --- a/packages/perspective-jupyterlab/package.json +++ b/packages/perspective-jupyterlab/package.json @@ -53,8 +53,7 @@ "isomorphic-fetch": "^2.2.1", "jest-transform-css": "^2.0.0", "source-map-support": "^0.5.9", - "ts-jest": "^25.1.0", - "typescript": "^3.7.4" + "ts-jest": "^25.1.0" }, "jupyterlab": { "extension": true diff --git a/packages/perspective/index.d.ts b/packages/perspective/index.d.ts index 73e65c8e25..6b74133f05 100644 --- a/packages/perspective/index.d.ts +++ b/packages/perspective/index.d.ts @@ -33,7 +33,7 @@ declare module "@finos/perspective" { export type FilterOp = "<" | ">" | "<=" | ">=" | "==" | "!=" | "is null" | "is not null" | "in" | "not in" | "begins with" | "contains"; export type Schema = { - [key: ColumnName]: Type; + [key: string]: Type; }; export interface SerializeConfig { diff --git a/rust/perspective-viewer/README.md b/rust/perspective-viewer/README.md new file mode 100644 index 0000000000..c20750ad75 --- /dev/null +++ b/rust/perspective-viewer/README.md @@ -0,0 +1,673 @@ +# @finos/perspective-viewer + +Module for the `` custom element. This module has no +(real) exports, but importing it has a side effect: the +`PerspectiveViewerElement`class is registered as a custom element, after +which it can be used as a standard DOM element. + +Though `` is written mostly in Rust, the nature +of WebAssembly's compilation makes it a dynamic module; in order to +guarantee that the Custom Elements extension methods are registered +synchronously with this package's import, we need perform said registration +within this wrapper module. + +The documentation in this module defines the instance structure of a +`` DOM object instantiated typically, through HTML or any +relevent DOM method e.g. `document.createElement("perspective-viewer")` or +`document.getElementsByTagName("perspective-viewer")`. + +## Table of contents + +### Classes + +- [PerspectiveViewerElement](#PerspectiveViewerElement) + +### Type aliases + +- [PerspectiveViewerConfig](#perspectiveviewerconfig) + +## Type aliases + +### PerspectiveViewerConfig + +Ƭ **PerspectiveViewerConfig**: `perspective.ViewConfig` & { `plugin?`: `string` ; `plugin_config?`: `object` ; `settings?`: `boolean` } + +#### Defined in + +[rust/perspective-viewer/src/ts/index.ts:56](https://github.com/finos/perspective/blob/4e5bed7d/rust/perspective-viewer/src/ts/index.ts#L56) + + +# Class: PerspectiveViewerElement + +The Custom Elements implementation for ``, as well at its +API. `PerspectiveViewerElement` should not be constructed directly (like its +parent class `HTMLElement`); instead, use `document.createElement()` or +declare your `` element in HTML. Once instantiated, +`` works just like a standard `HTMLElement`, with a few +extra perspective-specific methods. + +**`example`** +```javascript +const viewer = document.createElement("perspective-viewer"); +``` + +**`example`** +```javascript +document.body.innerHTML = ` + +`; +const viewer = document.body.querySelector("#viewer"); +``` + +## Hierarchy + +- `HTMLElement` + + ↳ **`PerspectiveViewerElement`** + +## Table of contents + +### Data Methods + +- [getTable](#gettable) +- [load](#load) + +### Persistence Methods + +- [reset](#reset) +- [restore](#restore) +- [save](#save) + +### Plugin Methods + +- [getAllPlugins](#getallplugins) +- [getPlugin](#getplugin) +- [registerPlugin](#registerplugin) + +### UI Action Methods + +- [copy](#copy) +- [download](#download) +- [toggleConfig](#toggleconfig) + +### Util Methods + +- [delete](#delete) +- [flush](#flush) +- [getEditPort](#geteditport) +- [notifyResize](#notifyresize) +- [restyleElement](#restyleelement) +- [setThrottle](#setthrottle) + +## Data Methods + +### getTable + +▸ **getTable**(): `Promise`<`Table`\> + +Returns the `perspective.Table()` which was supplied to `load()`. If +`load()` has been called but the supplied `Promise` +has not resolved, `getTable()` will `await`; if `load()` has not yet +been called, an `Error` will be thrown. + +**`example`** +```javascript +const viewers = document.querySelectorAll("perspective-viewer"); +const [viewer1, viewer2] = Array.from(viewers); +const table = await viewer1.getTable(); +await viewer2.load(table); +``` + +#### Returns + +`Promise`<`Table`\> + +A `Promise` which resolves to a `perspective.Table` + +#### Defined in + +[rust/perspective-viewer/src/ts/index.ts:194](https://github.com/finos/perspective/blob/4e5bed7d/rust/perspective-viewer/src/ts/index.ts#L194) + +___ + +### load + +▸ **load**(`table`): `Promise`<`void`\> + +Load a `perspective.Table`. If `load` or `update` have already been +called on this element, its internal `perspective.Table` will _not_ be +deleted, but it will bed de-referenced by this ``. + +**`example`** +```javascript +const my_viewer = document.getElementById('#my_viewer'); +const tbl = perspective.table("x,y\n1,a\n2,b"); +my_viewer.load(tbl); +``` + +**`example`** +```javascript +const my_viewer = document.getElementById('#my_viewer'); +const tbl = perspective.table("x,y\n1,a\n2,b"); +my_viewer.load(tbl); +``` + +#### Parameters + +| Name | Type | +| :------ | :------ | +| `table` | `Promise`<`Table`\> | + +#### Returns + +`Promise`<`void`\> + +A promise which resolves once the data is +loaded, a `perspective.View` has been created, and the active plugin has +rendered. + +#### Defined in + +[rust/perspective-viewer/src/ts/index.ts:151](https://github.com/finos/perspective/blob/4e5bed7d/rust/perspective-viewer/src/ts/index.ts#L151) + +___ + +## Persistence Methods + +### reset + +▸ **reset**(): `Promise`<`void`\> + +Reset's this element's view state and attributes to default. Does not +delete this element's `perspective.table` or otherwise modify the data +state. + +**`example`** +```javascript +const viewer = document.querySelector("perspective-viewer"); +await viewer.reset(); +``` + +#### Returns + +`Promise`<`void`\> + +#### Defined in + +[rust/perspective-viewer/src/ts/index.ts:308](https://github.com/finos/perspective/blob/4e5bed7d/rust/perspective-viewer/src/ts/index.ts#L308) + +___ + +### restore + +▸ **restore**(`config`): `Promise`<`void`\> + +Restore this element to a state as generated by a reciprocal call to +`save`. In `json` (default) format, `PerspectiveViewerConfig`'s fields +have specific semantics: + + - When a key is missing, this field is ignored; `` + will maintain whatever settings for this field is currently applied. + - When the key is supplied, but the value is `undefined`, the field is + reset to its default value for this current `View`, i.e. the state it + would be in after `load()` resolves. + - When the key is defined to a value, the value is applied for this + field. + +This behavior is convenient for explicitly controlling current vs desired +UI state in a single request, but it does make it a bit inconvenient to +use `restore()` to reset a `` to default as you must +do so explicitly for each key; for this case, use `reset()` instead of +restore. + +As noted in `save()`, this configuration state does not include the +`Table` or its `Schema`. In order for `restore()` to work correctly, it +must be called on a `` that has a `Table already +`load()`-ed, with the same (or a type-compatible superset) `Schema`. +It does not need have the same rows, or even be populated. + +**`example`** +```javascript +const viewer = document.querySelector("perspective-viewer"); +const token = localStorage.getItem("viewer_state"); +await viewer.restore(token); +``` + +#### Parameters + +| Name | Type | Description | +| :------ | :------ | :------ | +| `config` | `string` \| [`PerspectiveViewerConfig`](#perspectiveviewerconfig) \| `ArrayBuffer` | returned by `save()`. This can be any format returned by `save()`; the specific deserialization is chosen by `typeof config`. | + +#### Returns + +`Promise`<`void`\> + +A promise which resolves when the changes have been applied and +rendered. + +#### Defined in + +[rust/perspective-viewer/src/ts/index.ts:237](https://github.com/finos/perspective/blob/4e5bed7d/rust/perspective-viewer/src/ts/index.ts#L237) + +___ + +### save + +▸ **save**(): `Promise`<[`PerspectiveViewerConfig`](#perspectiveviewerconfig)\> + +Serialize this element's attribute/interaction state, but _not_ the +`perspective.Table` or its `Schema`. `save()` is designed to be used in +conjunction with `restore()` to persist user settings and bookmarks, but +the `PerspectiveViewerConfig` object returned in `json` format can also +be written by hand quite easily, which is useful for authoring +pre-conceived configs. + +**`example`** +```javascript +const viewer = document.querySelector("perspective-viewer"); +const token = await viewer.save("string"); +localStorage.setItem("viewer_state", token); +``` + +#### Returns + +`Promise`<[`PerspectiveViewerConfig`](#perspectiveviewerconfig)\> + +a serialized element in the chosen format. + +#### Defined in + +[rust/perspective-viewer/src/ts/index.ts:262](https://github.com/finos/perspective/blob/4e5bed7d/rust/perspective-viewer/src/ts/index.ts#L262) + +▸ **save**(`format`): `Promise`<[`PerspectiveViewerConfig`](#perspectiveviewerconfig)\> + +#### Parameters + +| Name | Type | +| :------ | :------ | +| `format` | ``"json"`` | + +#### Returns + +`Promise`<[`PerspectiveViewerConfig`](#perspectiveviewerconfig)\> + +#### Defined in + +[rust/perspective-viewer/src/ts/index.ts:263](https://github.com/finos/perspective/blob/4e5bed7d/rust/perspective-viewer/src/ts/index.ts#L263) + +▸ **save**(`format`): `Promise`<`ArrayBuffer`\> + +#### Parameters + +| Name | Type | +| :------ | :------ | +| `format` | ``"arraybuffer"`` | + +#### Returns + +`Promise`<`ArrayBuffer`\> + +#### Defined in + +[rust/perspective-viewer/src/ts/index.ts:264](https://github.com/finos/perspective/blob/4e5bed7d/rust/perspective-viewer/src/ts/index.ts#L264) + +▸ **save**(`format`): `Promise`<`string`\> + +#### Parameters + +| Name | Type | +| :------ | :------ | +| `format` | ``"string"`` | + +#### Returns + +`Promise`<`string`\> + +#### Defined in + +[rust/perspective-viewer/src/ts/index.ts:265](https://github.com/finos/perspective/blob/4e5bed7d/rust/perspective-viewer/src/ts/index.ts#L265) + +___ + +## Plugin Methods + +### getAllPlugins + +▸ **getAllPlugins**(): `Promise`<`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 +as a side effect. + +#### Returns + +`Promise`<`HTMLElement`[]\> + +An `Array` of the plugin instances for this +``. + +#### Defined in + +[rust/perspective-viewer/src/ts/index.ts:478](https://github.com/finos/perspective/blob/4e5bed7d/rust/perspective-viewer/src/ts/index.ts#L478) + +___ + +### getPlugin + +▸ **getPlugin**(`name`): `Promise`<`HTMLElement`\> + +Get the currently active plugin custom element instance, or a specific +named instance if requested. `getPlugin(name)` does not activate the +plugin requested, so if this plugin is not active the returned +`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. + +#### Parameters + +| Name | Type | Description | +| :------ | :------ | :------ | +| `name` | `any` | Optionally a specific plugin name, defaulting to the current active plugin. | + +#### Returns + +`Promise`<`HTMLElement`\> + +The active or requested plugin instance. + +#### Defined in + +[rust/perspective-viewer/src/ts/index.ts:461](https://github.com/finos/perspective/blob/4e5bed7d/rust/perspective-viewer/src/ts/index.ts#L461) + +___ + +### registerPlugin + +▸ `Static` **registerPlugin**(`name`): `Promise`<`void`\> + +Register a new plugin via its custom element name. This method is called +automatically as a side effect of importing a plugin module, so this +method should only typically be called by plugin authors. + +#### Parameters + +| Name | Type | Description | +| :------ | :------ | :------ | +| `name` | `any` | The `name` of the custom element to register, as supplied to the `customElements.define(name)` method. | + +#### Returns + +`Promise`<`void`\> + +#### Defined in + +[rust/perspective-viewer/src/ts/index.ts:123](https://github.com/finos/perspective/blob/4e5bed7d/rust/perspective-viewer/src/ts/index.ts#L123) + +___ + +## UI Action Methods + +### copy + +▸ **copy**(`flat`): `Promise`<`void`\> + +Copies this element's view data (as a CSV) to the clipboard. This method +must be called from an event handler, subject to the browser's +restrictions on clipboard access. See +[https://www.w3.org/TR/clipboard-apis/#allow-read-clipboard](https://www.w3.org/TR/clipboard-apis/#allow-read-clipboard). + +**`example`** +```javascript +const viewer = document.querySelector("perspective-viewer"); +const button = document.querySelector("button"); +button.addEventListener("click", async () => { + await viewer.copy(); +}); +``` + +#### Parameters + +| Name | Type | Description | +| :------ | :------ | :------ | +| `flat` | `boolean` | Whether to use the element's current view config, or to use a default "flat" view. | + +#### Returns + +`Promise`<`void`\> + +#### Defined in + +[rust/perspective-viewer/src/ts/index.ts:357](https://github.com/finos/perspective/blob/4e5bed7d/rust/perspective-viewer/src/ts/index.ts#L357) + +___ + +### download + +▸ **download**(`flat`): `Promise`<`void`\> + +Download this element's data as a CSV file. + +#### Parameters + +| Name | Type | Description | +| :------ | :------ | :------ | +| `flat` | `boolean` | Whether to use the element's current view config, or to use a default "flat" view. | + +#### Returns + +`Promise`<`void`\> + +#### Defined in + +[rust/perspective-viewer/src/ts/index.ts:334](https://github.com/finos/perspective/blob/4e5bed7d/rust/perspective-viewer/src/ts/index.ts#L334) + +___ + +### toggleConfig + +▸ **toggleConfig**(`force?`): `Promise`<`void`\> + +Opens/closes the element's config menu, equivalent to clicking the +settings button in the UI. This method is equivalent to +`viewer.restore({settings: force})` when `force` is present, but +`restore()` cannot toggle as `toggleConfig()` can, you would need to +first read the settings state from `save()` otherwise. + +Calling `toggleConfig()` may be delayed if an async render is currently +in process, and it may only partially render the UI if `load()` has not +yet resolved. + +**`example`** +```javascript +await viewer.toggleConfig(); +``` + +#### Parameters + +| Name | Type | Description | +| :------ | :------ | :------ | +| `force?` | `boolean` | If supplied, explicitly set the config state to "open" (`true`) or "closed" (`false`). | + +#### Returns + +`Promise`<`void`\> + +#### Defined in + +[rust/perspective-viewer/src/ts/index.ts:441](https://github.com/finos/perspective/blob/4e5bed7d/rust/perspective-viewer/src/ts/index.ts#L441) + +___ + +## Util Methods + +### delete + +▸ **delete**(): `Promise`<`void`\> + +Deletes this element and clears it's internal state (but not its +user state). This (or the underlying `perspective.view`'s equivalent +method) must be called in order for its memory to be reclaimed, as well +as the reciprocal method on the `perspective.table` which this viewer is +bound to. + +#### Returns + +`Promise`<`void`\> + +#### Defined in + +[rust/perspective-viewer/src/ts/index.ts:322](https://github.com/finos/perspective/blob/4e5bed7d/rust/perspective-viewer/src/ts/index.ts#L322) + +___ + +### flush + +▸ **flush**(): `Promise`<`void`\> + +Flush any pending modifications to this ``. Since +``'s API is almost entirely `async`, it may take +some milliseconds before any method call such as `restore()` affects +the rendered element. If you want to make sure any invoked method which +affects the rendered has had its results rendered, call and await +`flush()` + +**`example`** +```javascript +const viewer = document.querySelector("perspective-viewer"); +viewer.restore({row_pivots: ["State"]}); +await viewer.flush(); +console.log("Viewer has been rendered with a pivot!"); +``` + +#### Returns + +`Promise`<`void`\> + +A promise which resolves when the current +pending state changes have been applied and rendered. + +#### Defined in + +[rust/perspective-viewer/src/ts/index.ts:291](https://github.com/finos/perspective/blob/4e5bed7d/rust/perspective-viewer/src/ts/index.ts#L291) + +___ + +### getEditPort + +▸ **getEditPort**(): `Promise`<`number`\> + +Gets the edit port, the port number for which `Table` updates from this +`` are generated. This port number will be present +in the options object for a `View.on_update()` callback for any update +which was originated by the ``/user, which can be +used to distinguish server-originated updates from user edits. + +**`example`** +```javascript +const viewer = document.querySelector("perspective-viewer"); +const editport = await viewer.getEditPort(); +const table = await viewer.getTable(); +const view = await table.view(); +view.on_update(obj => { + if (obj.port_id = editport) { + console.log("User edit detected"); + } +}); +``` + +#### Returns + +`Promise`<`number`\> + +A promise which resolves to the current edit port. + +#### Defined in + +[rust/perspective-viewer/src/ts/index.ts:397](https://github.com/finos/perspective/blob/4e5bed7d/rust/perspective-viewer/src/ts/index.ts#L397) + +___ + +### notifyResize + +▸ **notifyResize**(): `Promise`<`void`\> + +Redraw this `` 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). + +**`example`** +```javascript +const viewer = document.querySelector("perspective-viewer"); +window.addEventListener("resize", () => viewer.notifyResize()); +``` + +#### Returns + +`Promise`<`void`\> + +A `Promise` which resolves when this resize event has +finished rendering. + +#### Defined in + +[rust/perspective-viewer/src/ts/index.ts:173](https://github.com/finos/perspective/blob/4e5bed7d/rust/perspective-viewer/src/ts/index.ts#L173) + +___ + +### restyleElement + +▸ **restyleElement**(): `Promise`<`void`\> + +Restyles the elements and to pick up any style changes. While most of +perspective styling is plain CSS and can be updated at any time, some +CSS rules are read and cached, e.g. the series colors for +`@finos/perspective-viewer-d3fc` which are read from CSS then reapplied +as SVG and Canvas attributes. + +#### Returns + +`Promise`<`void`\> + +#### Defined in + +[rust/perspective-viewer/src/ts/index.ts:371](https://github.com/finos/perspective/blob/4e5bed7d/rust/perspective-viewer/src/ts/index.ts#L371) + +___ + +### setThrottle + +▸ **setThrottle**(`value?`): `Promise`<`void`\> + +Determines the render throttling behavior. Can be an integer, for +millisecond window to throttle render event; or, if `undefined`, +will try to determine the optimal throttle time from this component's +render framerate. + +**`example`** +```javascript +await viewer.setThrottle(1000); +``` + +#### Parameters + +| Name | Type | Description | +| :------ | :------ | :------ | +| `value?` | `number` | an optional throttle rate in milliseconds (integer). If not supplied, adaptive throttling is calculated from the average plugin render time. | + +#### Returns + +`Promise`<`void`\> + +#### Defined in + +[rust/perspective-viewer/src/ts/index.ts:417](https://github.com/finos/perspective/blob/4e5bed7d/rust/perspective-viewer/src/ts/index.ts#L417) + + diff --git a/rust/perspective-viewer/concat-md.js b/rust/perspective-viewer/concat-md.js new file mode 100644 index 0000000000..e810980a88 --- /dev/null +++ b/rust/perspective-viewer/concat-md.js @@ -0,0 +1,66 @@ +/****************************************************************************** + * + * 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. + * + */ + +// Forked from gist +// https://gist.github.com/davideicardi/787df4a9dc0de66c1db8f5a57e511230 + +const fs = require("fs"); +const {Transform, PassThrough} = require("stream"); + +function concatStreams(streams) { + let pass = new PassThrough(); + let waiting = streams.length; + for (let stream of streams) { + pass = stream.pipe(pass, {end: false}); + stream.once("end", () => --waiting === 0 && pass.emit("end")); + } + return pass; +} + +class AddEndOfLine extends Transform { + constructor(options) { + super(options); + } + _transform(data, encoding, callback) { + this.push(data); + this.push("\n\n"); + callback(); + } +} + +class ConvertLinksToAnchors extends Transform { + constructor(options) { + super(options); + } + _transform(data, encoding, callback) { + const pattern = /\[(.+)\]\((.+\.md)?(#(.+))?\)/g; + const newData = data.toString().replace(pattern, (m, p1, p2, p3) => { + const anchor = `${p3 && p3.length > 0 ? p3.slice(1) : p1}`; + return `[${p1}](#${anchor})`; + }); + + this.push(newData); + callback(); + } +} + +const inPaths = ["./dist/docs/README.md", "./dist/docs/classes/PerspectiveViewerElement.md"]; + +const outPath = "./README.md"; +const inputs = inPaths.map(x => fs.createReadStream(x)); +const output = fs.createWriteStream(outPath); + +concatStreams(inputs) + .pipe(new AddEndOfLine()) + .pipe(new ConvertLinksToAnchors()) + .pipe(output) + .on("finish", function() { + console.log("Done merging!"); + }); diff --git a/rust/perspective-viewer/package.json b/rust/perspective-viewer/package.json index e9192fc3a4..de782a09dd 100644 --- a/rust/perspective-viewer/package.json +++ b/rust/perspective-viewer/package.json @@ -20,7 +20,6 @@ "index.d.ts" ], "types": "dist/esm/index.d.ts", - "typings": "dist/esm/index.d.ts", "scripts": { "build:wasm": "wasm-pack build --out-dir dist/pkg --target web && rm dist/pkg/package.json", "build:rollup": "rollup --config rollup.config.js", @@ -33,6 +32,10 @@ "postbuild": "rimraf pkg/package.json pkg/.gitignore", "clean": "rimraf dist && rimraf pkg && rimraf target", "clean:screenshots": "rimraf \"test/screenshots/**/*.@(failed|diff).png\"", + "docs": "npm-run-all docs:build docs:concat docs:deploy", + "docs:build": "typedoc --hideBreadcrumbs --out dist/docs --readme none --excludePrivate src/ts/index.ts", + "docs:concat": "node ./concat-md.js", + "docs:deploy": "(echo \"---\nid: perspective\ntitle: perspective-viewer API\n---\n\n\"; cat README.md) > ../../docs/obj/perspective-viewer.md", "fix": "yarn lint --fix", "lint": "eslint src examples/*.md examples/*.html", "test:build:rust": "cpx ../../packages/perspective/dist/umd/perspective.inline.js pkg/", diff --git a/rust/perspective-viewer/rollup.config.js b/rust/perspective-viewer/rollup.config.js index 586485f71a..d0a635328c 100644 --- a/rust/perspective-viewer/rollup.config.js +++ b/rust/perspective-viewer/rollup.config.js @@ -67,13 +67,7 @@ export default () => { dir: "dist/esm/" }, plugins: [ - typescript({ - target: "es2018", - declaration: true, - outDir: "dist/esm", - rootDir: "src/ts", - allowSyntheticDefaultImports: true - }), + typescript({tsconfig: "./tsconfig.json"}), filesize(), postcss({ inject: false, diff --git a/rust/perspective-viewer/src/ts/index.ts b/rust/perspective-viewer/src/ts/index.ts index 202b7eaf6a..e20bc6026c 100644 --- a/rust/perspective-viewer/src/ts/index.ts +++ b/rust/perspective-viewer/src/ts/index.ts @@ -8,21 +8,17 @@ * */ -import "mobile-drag-drop-shadow-dom"; -import init, * as internal from "../../dist/pkg/perspective_viewer.js"; -import * as perspective from "@finos/perspective"; - /** - * Module for the `` custom element. Though - * `` is written mostly in Rust, the nature of WebAssembly's - * compilation makes it a dynamic module; in order to guarantee that the - * Custom Elements extension methods are registered synchronously with this - * package's import, we need perform said registration within this wrapper module. + * Module for the `` custom element. This module has no + * (real) exports, but importing it has a side effect: the + * `PerspectiveViewerElement`class is registered as a custom element, after + * which it can be used as a standard DOM element. * - * This module has no (real) exports, but importing it has a side - * effect: the {@link module:perspective_viewer~PerspectiveViewer} class is - * registered as a custom element, after which it can be used as a standard DOM - * element. + * Though `` is written mostly in Rust, the nature + * of WebAssembly's compilation makes it a dynamic module; in order to + * guarantee that the Custom Elements extension methods are registered + * synchronously with this package's import, we need perform said registration + * within this wrapper module. * * The documentation in this module defines the instance structure of a * `` DOM object instantiated typically, through HTML or any @@ -32,6 +28,9 @@ import * as perspective from "@finos/perspective"; * @module perspective-viewer */ +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 @@ -42,33 +41,59 @@ window.addEventListener("unhandledrejection", event => { } }); +async function init_wasm({default: wasm_module}): Promise { + await init(wasm_module); + internal.set_panic_hook(); + return internal; +} + const WASM_MODULE = import( /* webpackChunkName: "perspective-viewer.custom-element" */ /* webpackMode: "eager" */ "../../dist/pkg/perspective_viewer_bg.wasm" ).then(init_wasm); -async function init_wasm({default: wasm_module}) { - await init(wasm_module); - internal.set_panic_hook(); - return internal; -} - export type PerspectiveViewerConfig = perspective.ViewConfig & { plugin?: string; settings?: boolean; plugin_config?: object; }; +/** + * The Custom Elements implementation for ``, as well at its + * API. `PerspectiveViewerElement` should not be constructed directly (like its + * parent class `HTMLElement`); instead, use `document.createElement()` or + * declare your `` element in HTML. Once instantiated, + * `` works just like a standard `HTMLElement`, with a few + * extra perspective-specific methods. + * + * @example + * ```javascript + * const viewer = document.createElement("perspective-viewer"); + * ``` + * @example + * ```javascript + * document.body.innerHTML = ` + * + * `; + * const viewer = document.body.querySelector("#viewer"); + * ``` + * @noInheritDoc + */ export class PerspectiveViewerElement extends HTMLElement { private instance: internal.PerspectiveViewerElement; + /** + * Should not be called directly (will throw `TypeError: Illegal + * constructor`). + * @ignore + */ constructor() { super(); this.load_wasm(); } - private async load_wasm() { + private async load_wasm(): Promise { const module = await WASM_MODULE; if (!this.instance) { this.instance = new module.PerspectiveViewerElement(this); @@ -76,54 +101,74 @@ export class PerspectiveViewerElement extends HTMLElement { } /** - * Part of the Custom Elements API. This method is called by the browser, and - * should not be called directly by applications. + * Part of the Custom Elements API. This method is called by the browser, + * and should not be called directly by applications. + * + * @ignore */ - async connectedCallback() { + async connectedCallback(): Promise { await this.load_wasm(); this.instance.connected_callback(); } /** * Register a new plugin via its custom element name. This method is called - * automatically as a side effect of importing a plugin module, so this method - * should only typically be called by plugin authors. + * automatically as a side effect of importing a plugin module, so this + * method should only typically be called by plugin authors. * - * @param name The `name` of the custom element to register + * @category Plugin + * @param name The `name` of the custom element to register, as supplied + * to the `customElements.define(name)` method. */ - static async registerPlugin(name) { + static async registerPlugin(name): Promise { const module = await WASM_MODULE; module.register_plugin(name); } /** - * Load a `perspective.Table`. If `load` or `update` have already been called - * on this element, its internal `perspective.Table` will _not_ be deleted. + * Load a `perspective.Table`. If `load` or `update` have already been + * called on this element, its internal `perspective.Table` will _not_ be + * deleted, but it will bed de-referenced by this ``. * - * @async + * @category Data * @param data A `Promise` which resolves to the `perspective.Table` - * @returns {Promise} A promise which resolves once the data is loaded, - * a `perspective.View` has been created, and the active plugin has rendered. + * @returns {Promise} A promise which resolves once the data is + * loaded, a `perspective.View` has been created, and the active plugin has + * rendered. * @example + * ```javascript * const my_viewer = document.getElementById('#my_viewer'); * const tbl = perspective.table("x,y\n1,a\n2,b"); * my_viewer.load(tbl); + * ``` * @example + * ```javascript * const my_viewer = document.getElementById('#my_viewer'); * const tbl = perspective.table("x,y\n1,a\n2,b"); * my_viewer.load(tbl); + * ``` */ - async load(table: Promise) { + async load(table: Promise): Promise { await this.load_wasm(); await this.instance.js_load(table); } /** - * Redraw this `` and plugin when its dimensions or visibility - * have been updated. + * Redraw this `` 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). * - * @returns A `Promise` which resolves when this resize event has finished - * rendering. + * @category Util + * @returns A `Promise` which resolves when this resize event has + * finished rendering. + * @example + * ```javascript + * const viewer = document.querySelector("perspective-viewer"); + * window.addEventListener("resize", () => viewer.notifyResize()); + * ``` */ async notifyResize(): Promise { await this.load_wasm(); @@ -131,12 +176,20 @@ export class PerspectiveViewerElement extends HTMLElement { } /** - * Returns the `perspective.Table()` which was supplied to `load()`. If `load()` - * has been called but the supplied `Promise` has not resolved, - * `getTable()` will `await`; if `load()` has not yet been called, an `Error` - * will be thrown. + * Returns the `perspective.Table()` which was supplied to `load()`. If + * `load()` has been called but the supplied `Promise` + * has not resolved, `getTable()` will `await`; if `load()` has not yet + * been called, an `Error` will be thrown. * + * @category Data * @returns A `Promise` which resolves to a `perspective.Table` + * @example + * ```javascript + * const viewers = document.querySelectorAll("perspective-viewer"); + * const [viewer1, viewer2] = Array.from(viewers); + * const table = await viewer1.getTable(); + * await viewer2.load(table); + * ``` */ async getTable(): Promise { await this.load_wasm(); @@ -146,11 +199,40 @@ export class PerspectiveViewerElement extends HTMLElement { /** * Restore this element to a state as generated by a reciprocal call to - * `save`. + * `save`. In `json` (default) format, `PerspectiveViewerConfig`'s fields + * have specific semantics: + * + * - When a key is missing, this field is ignored; `` + * will maintain whatever settings for this field is currently applied. + * - When the key is supplied, but the value is `undefined`, the field is + * reset to its default value for this current `View`, i.e. the state it + * would be in after `load()` resolves. + * - When the key is defined to a value, the value is applied for this + * field. + * + * This behavior is convenient for explicitly controlling current vs desired + * UI state in a single request, but it does make it a bit inconvenient to + * use `restore()` to reset a `` to default as you must + * do so explicitly for each key; for this case, use `reset()` instead of + * restore. + * + * As noted in `save()`, this configuration state does not include the + * `Table` or its `Schema`. In order for `restore()` to work correctly, it + * must be called on a `` that has a `Table already + * `load()`-ed, with the same (or a type-compatible superset) `Schema`. + * It does not need have the same rows, or even be populated. * - * @param config returned by `save()`. + * @category Persistence + * @param config returned by `save()`. This can be any format returned by + * `save()`; the specific deserialization is chosen by `typeof config`. * @returns A promise which resolves when the changes have been applied and * rendered. + * @example + * ```javascript + * const viewer = document.querySelector("perspective-viewer"); + * const token = localStorage.getItem("viewer_state"); + * await viewer.restore(token); + * ``` */ async restore(config: PerspectiveViewerConfig | string | ArrayBuffer): Promise { await this.load_wasm(); @@ -158,10 +240,53 @@ export class PerspectiveViewerElement extends HTMLElement { } /** - * Flush any pending modifications to this ``. + * Serialize this element's attribute/interaction state, but _not_ the + * `perspective.Table` or its `Schema`. `save()` is designed to be used in + * conjunction with `restore()` to persist user settings and bookmarks, but + * the `PerspectiveViewerConfig` object returned in `json` format can also + * be written by hand quite easily, which is useful for authoring + * pre-conceived configs. * - * @returns {Promise} A promise which resolves when the current pending - * state changes have been applied and rendered. + * @category Persistence + * @param format The serialization format - `json` (JavaScript object), + * `arraybuffer` or `string`. `restore()` uses the returned config's type + * to infer format. + * @returns a serialized element in the chosen format. + * @example + * ```javascript + * const viewer = document.querySelector("perspective-viewer"); + * const token = await viewer.save("string"); + * localStorage.setItem("viewer_state", token); + * ``` + */ + async save(): Promise; + async save(format: "json"): Promise; + async save(format: "arraybuffer"): Promise; + async save(format: "string"): Promise; + async save(format?: "json" | "arraybuffer" | "string"): Promise { + await this.load_wasm(); + const config = await this.instance.js_save(format); + return config; + } + + /** + * Flush any pending modifications to this ``. Since + * ``'s API is almost entirely `async`, it may take + * some milliseconds before any method call such as `restore()` affects + * the rendered element. If you want to make sure any invoked method which + * affects the rendered has had its results rendered, call and await + * `flush()` + * + * @category Util + * @returns {Promise} A promise which resolves when the current + * pending state changes have been applied and rendered. + * @example + * ```javascript + * const viewer = document.querySelector("perspective-viewer"); + * viewer.restore({row_pivots: ["State"]}); + * await viewer.flush(); + * console.log("Viewer has been rendered with a pivot!"); + * ``` */ async flush(): Promise { await this.load_wasm(); @@ -172,36 +297,27 @@ export class PerspectiveViewerElement extends HTMLElement { * Reset's this element's view state and attributes to default. Does not * delete this element's `perspective.table` or otherwise modify the data * state. + * + * @category Persistence + * @example + * ```javascript + * const viewer = document.querySelector("perspective-viewer"); + * await viewer.reset(); + * ``` */ async reset(): Promise { await this.load_wasm(); await this.instance.js_reset(); } - /** - * Serialize this element's attribute/interaction state. - * - * @param format The serialization format - `json` (JavaScript object), - * `arraybuffer` or `string`. `restore()` uses the returned config's type - * to infer format. - * @returns {object} a serialized element. - */ - async save(): Promise; - async save(format: "json"): Promise; - async save(format: "arraybuffer"): Promise; - async save(format: "string"): Promise; - async save(format?: "json" | "arraybuffer" | "string"): Promise { - await this.load_wasm(); - const config = await this.instance.js_save(format); - return config; - } - /** * Deletes this element and clears it's internal state (but not its * user state). This (or the underlying `perspective.view`'s equivalent * method) must be called in order for its memory to be reclaimed, as well * as the reciprocal method on the `perspective.table` which this viewer is * bound to. + * + * @category Util */ async delete(): Promise { await this.load_wasm(); @@ -211,6 +327,7 @@ export class PerspectiveViewerElement extends HTMLElement { /** * Download this element's data as a CSV file. * + * @category UI Action * @param flat Whether to use the element's current view * config, or to use a default "flat" view. */ @@ -225,8 +342,17 @@ export class PerspectiveViewerElement extends HTMLElement { * restrictions on clipboard access. See * {@link https://www.w3.org/TR/clipboard-apis/#allow-read-clipboard}. * + * @category UI Action * @param flat Whether to use the element's current view * config, or to use a default "flat" view. + * @example + * ```javascript + * const viewer = document.querySelector("perspective-viewer"); + * const button = document.querySelector("button"); + * button.addEventListener("click", async () => { + * await viewer.copy(); + * }); + * ``` */ async copy(flat: boolean): Promise { await this.load_wasm(); @@ -234,7 +360,13 @@ export class PerspectiveViewerElement extends HTMLElement { } /** - * Restyles the elements and to pick up any style changes + * Restyles the elements and to pick up any style changes. While most of + * perspective styling is plain CSS and can be updated at any time, some + * CSS rules are read and cached, e.g. the series colors for + * `@finos/perspective-viewer-d3fc` which are read from CSS then reapplied + * as SVG and Canvas attributes. + * + * @category Util */ async restyleElement(): Promise { console.error("Not Implemented"); @@ -242,9 +374,25 @@ export class PerspectiveViewerElement extends HTMLElement { /** * Gets the edit port, the port number for which `Table` updates from this - * `` are generated. + * `` are generated. This port number will be present + * in the options object for a `View.on_update()` callback for any update + * which was originated by the ``/user, which can be + * used to distinguish server-originated updates from user edits. * + * @category Util * @returns A promise which resolves to the current edit port. + * @example + * ```javascript + * const viewer = document.querySelector("perspective-viewer"); + * const editport = await viewer.getEditPort(); + * const table = await viewer.getTable(); + * const view = await table.view(); + * view.on_update(obj => { + * if (obj.port_id = editport) { + * console.log("User edit detected"); + * } + * }); + * ``` */ async getEditPort(): Promise { console.error("Not Implemented"); @@ -257,12 +405,14 @@ export class PerspectiveViewerElement extends HTMLElement { * will try to determine the optimal throttle time from this component's * render framerate. * + * @category Util * @param value an optional throttle rate in milliseconds (integer). If not - * supplied, adaptive throttling is calculated from the average plugin render - * time. - * @example - * // Only draws at most 1 frame/sec. + * supplied, adaptive throttling is calculated from the average plugin + * render time. + * @example + * ```javascript * await viewer.setThrottle(1000); + * ``` */ async setThrottle(value?: number): Promise { await this.load_wasm(); @@ -270,10 +420,23 @@ export class PerspectiveViewerElement extends HTMLElement { } /** - * Opens/closes the element's config menu. + * Opens/closes the element's config menu, equivalent to clicking the + * settings button in the UI. This method is equivalent to + * `viewer.restore({settings: force})` when `force` is present, but + * `restore()` cannot toggle as `toggleConfig()` can, you would need to + * first read the settings state from `save()` otherwise. + * + * Calling `toggleConfig()` may be delayed if an async render is currently + * in process, and it may only partially render the UI if `load()` has not + * yet resolved. * - * @param force If supplied, explicitly set the config state to "open" (`true`) - * or "closed" (`false`). + * @category UI Action + * @param force If supplied, explicitly set the config state to "open" + * (`true`) or "closed" (`false`). + * @example + * ```javascript + * await viewer.toggleConfig(); + * ``` */ async toggleConfig(force?: boolean): Promise { await this.load_wasm(); @@ -282,14 +445,15 @@ export class PerspectiveViewerElement extends HTMLElement { /** * Get the currently active plugin custom element instance, or a specific - * named instance if requested. `getPlugin(name)` does not activate the plugin - * requested, so if this plugin is not active the returned `HTMLElement` will - * not have a `parentElement`. + * named instance if requested. `getPlugin(name)` does not activate the + * plugin requested, so if this plugin is not active the returned + * `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. * + * @category Plugin * @param name Optionally a specific plugin name, defaulting to the current * active plugin. * @returns The active or requested plugin instance. @@ -304,10 +468,12 @@ 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 as - * a side effect. + * `getAllPlugins()` will cause `perspective-viewer-debug` to be registered + * as a side effect. * - * @returns An `Array` of the plugin instances for this ``. + * @category Plugin + * @returns An `Array` of the plugin instances for this + * ``. */ async getAllPlugins(): Promise> { await this.load_wasm(); @@ -327,7 +493,7 @@ class PerspectiveColumnStyleElement extends HTMLElement { super(); } - async open(target, config, default_config) { + async open(target: HTMLElement, config: any, default_config: any): Promise { if (this.instance) { this.instance.reset(config, default_config); } else { @@ -342,11 +508,12 @@ if (document.createElement("perspective-column-style").constructor === HTMLEleme window.customElements.define("perspective-column-style", PerspectiveColumnStyleElement); } -interface ReactPerspectiveViewerAttributes extends React.HTMLAttributes {} +type ReactPerspectiveViewerAttributes = React.HTMLAttributes; type JsxPerspectiveViewerElement = {class?: string} & React.DetailedHTMLProps, PerspectiveViewerElement>; declare global { + // eslint-disable-next-line @typescript-eslint/no-namespace namespace JSX { interface IntrinsicElements { "perspective-viewer": JsxPerspectiveViewerElement; @@ -356,4 +523,4 @@ declare global { interface Document { createElement(tagName: "perspective-viewer", options?: ElementCreationOptions): PerspectiveViewerElement; } -} \ No newline at end of file +} diff --git a/rust/perspective-viewer/tsconfig.json b/rust/perspective-viewer/tsconfig.json new file mode 100644 index 0000000000..99954ff72c --- /dev/null +++ b/rust/perspective-viewer/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "module": "esnext", + "target": "es2018", + "declaration": true, + "outDir": "dist/esm", + "rootDir": "src/ts", + "allowSyntheticDefaultImports": true, + "moduleResolution": "node" + }, + "files": [ + "src/ts/index.ts", + "src/ts/monaco.ts" + ] +} diff --git a/yarn.lock b/yarn.lock index da59a4d8e8..9093ffee77 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6012,11 +6012,6 @@ csstype@^3.0.2, csstype@~3.0.3: resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.0.6.tgz#865d0b5833d7d8d40f4e5b8a6d76aea3de4725ef" integrity sha512-+ZAmfyWMT7TiIlzdqJgjMb7S4f1beorDbWbsocyK4RaiqA5RTX3K14bnBWmmA9QEM0gRdsjyyrEmcyga8Zsxmw== -cuint@^0.2.2: - version "0.2.2" - resolved "https://registry.yarnpkg.com/cuint/-/cuint-0.2.2.tgz#408086d409550c2631155619e9fa7bcadc3b991b" - integrity sha1-QICG1AlVDCYxFVYZ6fp7ytw7mRs= - currently-unhandled@^0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/currently-unhandled/-/currently-unhandled-0.4.1.tgz#988df33feab191ef799a61369dd76c17adf957ea" @@ -8547,6 +8542,18 @@ glob@^7.0.0, glob@^7.0.3, glob@^7.0.5, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, gl once "^1.3.0" path-is-absolute "^1.0.0" +glob@^7.1.7: + version "7.1.7" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.7.tgz#3b193e9233f01d42d0b3f78294bbeeb418f94a90" + integrity sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + global-modules@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-2.0.0.tgz#997605ad2345f27f51539bea26574421215c7780" @@ -8742,6 +8749,18 @@ handlebars@^4.0.11, handlebars@^4.7.6: optionalDependencies: uglify-js "^3.1.4" +handlebars@^4.7.7: + version "4.7.7" + resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.7.7.tgz#9ce33416aad02dbd6c8fafa8240d5d98004945a1" + integrity sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA== + dependencies: + minimist "^1.2.5" + neo-async "^2.6.0" + source-map "^0.6.1" + wordwrap "^1.0.0" + optionalDependencies: + uglify-js "^3.1.4" + har-schema@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92" @@ -10751,6 +10770,13 @@ json5@^1.0.1: dependencies: minimist "^1.2.0" +json5@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.0.tgz#2dfefe720c6ba525d9ebd909950f0515316c89a3" + integrity sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA== + dependencies: + minimist "^1.2.5" + jsonfile@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb" @@ -11325,6 +11351,11 @@ lru-cache@^6.0.0: dependencies: yallist "^4.0.0" +lunr@^2.3.9: + version "2.3.9" + resolved "https://registry.yarnpkg.com/lunr/-/lunr-2.3.9.tgz#18b123142832337dd6e964df1a5a7707b25d35e1" + integrity sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow== + macos-release@^2.2.0: version "2.4.1" resolved "https://registry.yarnpkg.com/macos-release/-/macos-release-2.4.1.tgz#64033d0ec6a5e6375155a74b1a1eba8e509820ac" @@ -11345,7 +11376,7 @@ make-dir@^2.0.0, make-dir@^2.1.0: pify "^4.0.1" semver "^5.6.0" -make-dir@^3.0.0, make-dir@^3.0.2, make-dir@^3.1.0, make-dir@~3.1.0: +make-dir@^3.0.0, make-dir@^3.0.2, make-dir@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw== @@ -11467,6 +11498,11 @@ marked@^1.1.1: resolved "https://registry.yarnpkg.com/marked/-/marked-1.2.7.tgz#6e14b595581d2319cdcf033a24caaf41455a01fb" integrity sha512-No11hFYcXr/zkBvL6qFmAp1z6BKY3zqLMHny/JN/ey+al7qwCM2+CMBL9BOgqMxZU36fz4cCWfn2poWIf7QRXA== +marked@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/marked/-/marked-3.0.2.tgz#60ce97d6aec34dd882ab4bb4df82494666854e17" + integrity sha512-TMJQQ79Z0e3rJYazY0tIoMsFzteUGw9fB3FD+gzuIT3zLuG9L9ckIvUfF51apdJkcqc208jJN2KbtPbOvXtbjA== + math-expression-evaluator@^1.2.14: version "1.3.7" resolved "https://registry.yarnpkg.com/math-expression-evaluator/-/math-expression-evaluator-1.3.7.tgz#1b62225db86af06f7ea1fd9576a34af605a5b253" @@ -11654,11 +11690,6 @@ mime@^2.4.4: resolved "https://registry.yarnpkg.com/mime/-/mime-2.4.7.tgz#962aed9be0ed19c91fd7dc2ece5d7f4e89a90d74" integrity sha512-dhNd1uA2u397uQk3Nv5LM4lm93WYDUXFn3Fu291FJerns4jyTudqhIWe4W04YLy7Uk1tm1Ore04NpjRvQp/NPA== -mime@~2.5.2: - version "2.5.2" - resolved "https://registry.yarnpkg.com/mime/-/mime-2.5.2.tgz#6e3dc6cc2b9510643830e5f19d5cb753da5eeabe" - integrity sha512-tqkh47FzKeCPD2PUiPB6pkbMzsCasjxAfC62/Wap5qrUWcb+sFasXUC5I3gYM5iBM8v/Qpn4UK0x+j0iHyFPDg== - mimic-fn@^1.0.0: version "1.2.0" resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.2.0.tgz#820c86a39334640e99516928bd03fca88057d022" @@ -11693,7 +11724,7 @@ minimalistic-assert@^1.0.0: resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A== -minimatch@3.0.4, minimatch@^3.0.2, minimatch@^3.0.4, minimatch@~3.0.2, minimatch@~3.0.4: +minimatch@3.0.4, minimatch@^3.0.0, minimatch@^3.0.2, minimatch@^3.0.4, minimatch@~3.0.2: version "3.0.4" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== @@ -12543,6 +12574,13 @@ onetime@^5.1.0, onetime@^5.1.2: dependencies: mimic-fn "^2.1.0" +onigasm@^2.2.5: + version "2.2.5" + resolved "https://registry.yarnpkg.com/onigasm/-/onigasm-2.2.5.tgz#cc4d2a79a0fa0b64caec1f4c7ea367585a676892" + integrity sha512-F+th54mPc0l1lp1ZcFMyL/jTs2Tlq4SqIHKIXGZOR/VkHkF9A7Fr5rRr5+ZG/lWeRsyrClLYRq7s/yFQ/XhWCA== + dependencies: + lru-cache "^5.1.1" + open@^6.3.0: version "6.4.0" resolved "https://registry.yarnpkg.com/open/-/open-6.4.0.tgz#5c13e96d0dc894686164f18965ecfe889ecfc8a9" @@ -13262,14 +13300,6 @@ postcss-convert-values@^4.0.1: postcss "^7.0.0" postcss-value-parser "^3.0.0" -postcss-copy-assets@^0.3.1: - version "0.3.1" - resolved "https://registry.yarnpkg.com/postcss-copy-assets/-/postcss-copy-assets-0.3.1.tgz#f64275406966e48d02e7617d31b2aae7f921d944" - integrity sha1-9kJ1QGlm5I0C52F9MbKq5/kh2UQ= - dependencies: - mkdirp "^0.5.1" - postcss "^5.0.2" - postcss-discard-comments@^2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/postcss-discard-comments/-/postcss-discard-comments-2.0.4.tgz#befe89fafd5b3dace5ccce51b76b81514be00e3d" @@ -13334,20 +13364,6 @@ postcss-discard-unused@^2.2.1: postcss "^5.0.14" uniqs "^2.0.0" -postcss-easy-import@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/postcss-easy-import/-/postcss-easy-import-3.0.0.tgz#8eaaf5ae59566083d0cae98735dfd803e3ab194d" - integrity sha512-cfNsear/v8xlkl9v5Wm8y4Do/puiDQTFF+WX2Fo++h7oKt1fKWVVW/5Ca8hslYDQWnjndrg813cA23Pt1jsYdg== - dependencies: - globby "^6.1.0" - is-glob "^4.0.0" - lodash "^4.17.4" - object-assign "^4.0.1" - pify "^3.0.0" - postcss "^6.0.11" - postcss-import "^10.0.0" - resolve "^1.1.7" - postcss-filter-plugins@^2.0.0: version "2.0.3" resolved "https://registry.yarnpkg.com/postcss-filter-plugins/-/postcss-filter-plugins-2.0.3.tgz#82245fdf82337041645e477114d8e593aa18b8ec" @@ -13355,22 +13371,6 @@ postcss-filter-plugins@^2.0.0: dependencies: postcss "^5.0.4" -postcss-font-grabber@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/postcss-font-grabber/-/postcss-font-grabber-3.0.2.tgz#6ec0cc4b2296f9d2a94f5f6a09367d9076e3f20c" - integrity sha512-zt4JD9W6oThxxs1VFyzRCdHhsLzz5tuoPpNr22ZlSYPpaV3dPXXDuGQSo9hXASvTm4ATN6X2x6GS/1eGI2rqLg== - -postcss-import@^10.0.0: - version "10.0.0" - resolved "https://registry.yarnpkg.com/postcss-import/-/postcss-import-10.0.0.tgz#4c85c97b099136cc5ea0240dc1dfdbfde4e2ebbe" - integrity sha1-TIXJewmRNsxeoCQNwd/b/eTi674= - dependencies: - object-assign "^4.0.1" - postcss "^6.0.1" - postcss-value-parser "^3.2.3" - read-cache "^1.0.0" - resolve "^1.1.7" - postcss-load-config@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/postcss-load-config/-/postcss-load-config-2.0.0.tgz#f1312ddbf5912cd747177083c5ef7a19d62ee484" @@ -13845,16 +13845,6 @@ postcss-unique-selectors@^4.0.1: postcss "^7.0.0" uniqs "^2.0.0" -postcss-url@^10.1.3: - version "10.1.3" - resolved "https://registry.yarnpkg.com/postcss-url/-/postcss-url-10.1.3.tgz#54120cc910309e2475ec05c2cfa8f8a2deafdf1e" - integrity sha512-FUzyxfI5l2tKmXdYc6VTu3TWZsInayEKPbiyW+P6vmmIrrb4I6CGX0BFoewgYHLK+oIL5FECEK02REYRpBvUCw== - dependencies: - make-dir "~3.1.0" - mime "~2.5.2" - minimatch "~3.0.4" - xxhashjs "~0.2.2" - postcss-value-parser@^3.0.0, postcss-value-parser@^3.0.1, postcss-value-parser@^3.0.2, postcss-value-parser@^3.1.1, postcss-value-parser@^3.1.2, postcss-value-parser@^3.2.3, postcss-value-parser@^3.3.0: version "3.3.1" resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz#9ff822547e2893213cf1c30efa51ac5fd1ba8281" @@ -13893,7 +13883,7 @@ postcss@^5.0.10, postcss@^5.0.11, postcss@^5.0.12, postcss@^5.0.13, postcss@^5.0 source-map "^0.5.6" supports-color "^3.2.3" -postcss@^6.0.1, postcss@^6.0.11: +postcss@^6.0.1: version "6.0.23" resolved "https://registry.yarnpkg.com/postcss/-/postcss-6.0.23.tgz#61c82cc328ac60e677645f979054eb98bc0e3324" integrity sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag== @@ -14004,7 +13994,7 @@ progress@2.0.1: resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.1.tgz#c9242169342b1c29d275889c95734621b1952e31" integrity sha512-OE+a6vzqazc+K6LxJrX5UPyKFvGnL5CYmq2jFGNIBWHpc4QyE49/YOumcrpQFJpfejmvRtbJzgO1zPmMCqlbBg== -progress@^2.0.0: +progress@^2.0.0, progress@^2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== @@ -14366,13 +14356,6 @@ react@16.8.6, react@^16.8.4, react@^16.8.6, react@^17.0.1: prop-types "^15.6.2" scheduler "^0.13.6" -read-cache@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/read-cache/-/read-cache-1.0.0.tgz#e664ef31161166c9751cdbe8dbcf86b5fb58f774" - integrity sha1-5mTvMRYRZsl1HNvo28+GtftY93Q= - dependencies: - pify "^2.3.0" - read-cmd-shim@^1.0.1: version "1.0.5" resolved "https://registry.yarnpkg.com/read-cmd-shim/-/read-cmd-shim-1.0.5.tgz#87e43eba50098ba5a32d0ceb583ab8e43b961c16" @@ -15377,6 +15360,15 @@ shellwords@^0.1.1: resolved "https://registry.yarnpkg.com/shellwords/-/shellwords-0.1.1.tgz#d6b9181c1a48d397324c84871efbcfc73fc0654b" integrity sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww== +shiki@^0.9.8: + version "0.9.10" + resolved "https://registry.yarnpkg.com/shiki/-/shiki-0.9.10.tgz#feb8d4938b5dd71c5c8b1c1c7cd28fbbd37da087" + integrity sha512-xeM7Oc6hY+6iW5O/T5hor8ul7mEprzyl5y4r5zthEHToQNw7MIhREMgU3r2gKDB0NaMLNrkcEQagudCdzE13Lg== + dependencies: + json5 "^2.2.0" + onigasm "^2.2.5" + vscode-textmate "5.2.0" + signal-exit@^3.0.0, signal-exit@^3.0.2, signal-exit@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c" @@ -16759,10 +16751,41 @@ typedarray@^0.0.6: resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= -typescript@^3.7.4: - version "3.9.7" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.7.tgz#98d600a5ebdc38f40cb277522f12dc800e9e25fa" - integrity sha512-BLbiRkiBzAwsjut4x/dsibSTB6yWpwT5qWmC2OfuCg3GgVQCSgMs4vEctYPhsaGtd0AeuuHMkjZ2h2WG8MSzRw== +typedoc-default-themes@^0.12.10: + version "0.12.10" + resolved "https://registry.yarnpkg.com/typedoc-default-themes/-/typedoc-default-themes-0.12.10.tgz#614c4222fe642657f37693ea62cad4dafeddf843" + integrity sha512-fIS001cAYHkyQPidWXmHuhs8usjP5XVJjWB8oZGqkTowZaz3v7g3KDZeeqE82FBrmkAnIBOY3jgy7lnPnqATbA== + +typedoc-plugin-markdown@^3.10.4: + version "3.10.4" + resolved "https://registry.yarnpkg.com/typedoc-plugin-markdown/-/typedoc-plugin-markdown-3.10.4.tgz#4e0e9c584a1e421beafa4c3666896615f069da6b" + integrity sha512-if9w7S9fXLg73AYi/EoRSEhTOZlg3I8mIP8YEmvzSE33VrNXC9/hA0nVcLEwFVJeQY7ay6z67I6kW0KIv7LjeA== + dependencies: + handlebars "^4.7.7" + +typedoc-plugin-no-inherit@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/typedoc-plugin-no-inherit/-/typedoc-plugin-no-inherit-1.3.0.tgz#8fd3b01d7fe48767bd827f933ff53e00f90005c0" + integrity sha512-Om4yu8sDHkHWi6FcOYUbg6fv2jh7kS51xbuZ/zs4VEtO5bJwTkO8FlzRUyDrTA3puxvF9XcoottntD0mpEgULg== + +typedoc@^0.21.9: + version "0.21.9" + resolved "https://registry.yarnpkg.com/typedoc/-/typedoc-0.21.9.tgz#6fbdc7152024a00f03af53a0ca40f44e91f0f129" + integrity sha512-VRo7aII4bnYaBBM1lhw4bQFmUcDQV8m8tqgjtc7oXl87jc1Slbhfw2X5MccfcR2YnEClHDWgsiQGgNB8KJXocA== + dependencies: + glob "^7.1.7" + handlebars "^4.7.7" + lunr "^2.3.9" + marked "^3.0.2" + minimatch "^3.0.0" + progress "^2.0.3" + shiki "^0.9.8" + typedoc-default-themes "^0.12.10" + +typescript@4.3.3: + version "4.3.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.3.3.tgz#5401db69bd3203daf1851a1a74d199cb3112c11a" + integrity sha512-rUvLW0WtF7PF2b9yenwWUi9Da9euvDRhmH7BLyBG4DCFfOJ850LGNknmRpp8Z8kXNUPObdZQEfKOiHtXuQHHKA== typestyle@^2.0.4: version "2.1.0" @@ -17168,6 +17191,11 @@ vfile@^2.0.0: unist-util-stringify-position "^1.0.0" vfile-message "^1.0.0" +vscode-textmate@5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/vscode-textmate/-/vscode-textmate-5.2.0.tgz#01f01760a391e8222fe4f33fbccbd1ad71aed74e" + integrity sha512-Uw5ooOQxRASHgu6C7GVvUxisKXfSgW4oFlO+aa+PAkgmH89O3CXxEEzNRNtHSqtXFTl0nAC1uYj0GMSH27uwtQ== + w3c-hr-time@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz#0a89cdf5cc15822df9c360543676963e0cc308cd" @@ -17679,13 +17707,6 @@ xtend@^4.0.0, xtend@^4.0.1, xtend@~4.0.1: resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== -xxhashjs@~0.2.2: - version "0.2.2" - resolved "https://registry.yarnpkg.com/xxhashjs/-/xxhashjs-0.2.2.tgz#8a6251567621a1c46a5ae204da0249c7f8caa9d8" - integrity sha512-AkTuIuVTET12tpsVIQo+ZU6f/qDmKuRUcjaqR+OIvm+aCBsZ95i7UVY5WJ9TMsSaZ0DA2WxoZ4acu0sPH+OKAw== - dependencies: - cuint "^0.2.2" - y18n@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.1.tgz#8db2b83c31c5d75099bb890b23f3094891e247d4"
Share a `Table`Load perspective.tableLoad PromiseRestore a viewer from `localStorage`Save a viewer to `localStorage`Flush an unawaited `restore()`Bind `notfyResize()` to browser dimensionsLimit FPS to 1 frame per secondLoad perspective.tableLoad PromiseBind `notfyResize()` to browser dimensionsShare a `Table`Restore a viewer from `localStorage`Save a viewer to `localStorage`Flush an unawaited `restore()`Limit FPS to 1 frame per second