From 7dc62a7b9c561f8645319dc06cbee749a80f76ba Mon Sep 17 00:00:00 2001 From: Andrew Stein Date: Sun, 14 Feb 2021 16:55:25 -0500 Subject: [PATCH] Ported to Yew, reimplemented split-panel --- .../test/js/resize.spec.js | 6 +- packages/perspective-test/src/js/index.js | 3 +- .../src/less/chart.less | 1 + .../test/js/integration/candlestick.spec.js | 2 +- .../test/js/integration/line.spec.js | 2 +- .../test/js/integration/ohlc.spec.js | 2 +- .../test/js/integration/scatter.spec.js | 6 +- .../test/js/integration/sunburst.spec.js | 4 +- .../test/js/integration/treemap.spec.js | 8 +- .../test/results/linux.docker.json | 4 +- .../test/js/superstore.spec.js | 8 +- .../src/config/rollup.config.js | 3 +- .../perspective-viewer/src/html/viewer.html | 85 +-- packages/perspective-viewer/src/js/viewer.js | 13 +- .../src/js/viewer/action_element.js | 40 +- .../src/js/viewer/dom_element.js | 6 +- .../src/js/viewer/perspective_element.js | 4 +- .../perspective-viewer/src/less/default.less | 18 - .../perspective-viewer/src/less/viewer.less | 56 +- .../src/themes/material-dense.less | 2 +- .../src/themes/material.less | 2 +- .../test/js/computed_expressions.spec.js | 122 +-- .../test/js/filters.spec.js | 22 +- .../perspective-viewer/test/js/leaks.spec.js | 6 +- .../test/js/regressions.spec.js | 2 +- .../test/js/render_warning_tests.js | 12 +- .../test/js/responsive_tests.js | 4 +- .../test/js/simple_tests.js | 26 +- .../perspective-viewer/test/js/slow.spec.js | 2 +- .../perspective/src/config/common.config.js | 8 +- rust/perspective-vieux/Cargo.lock | 703 +----------------- rust/perspective-vieux/Cargo.toml | 10 +- rust/perspective-vieux/package.json | 4 +- rust/perspective-vieux/rollup.config.js | 4 +- rust/perspective-vieux/src/js/bootstrap.js | 41 +- rust/perspective-vieux/src/js/index.js | 4 +- .../perspective-vieux/src/less/container.less | 138 ---- .../src/less/perspective-vieux.less | 73 ++ .../src/less/split-panel.less | 45 ++ .../src/less/status-bar.less | 140 ++++ .../src/rust/components/mod.rs | 16 + .../src/rust/components/perspective_vieux.rs | 295 ++++++++ .../src/rust/components/split_panel.rs | 206 +++++ .../src/rust/components/status_bar.rs | 118 +++ .../src/rust/components/status_bar_counter.rs | 87 +++ .../src/rust/components/tests/mod.rs | 11 + .../src/rust/components/tests/split_panel.rs | 81 ++ .../src/rust/components/tests/status_bar.rs | 156 ++++ .../components/tests/status_bar_counter.rs | 101 +++ rust/perspective-vieux/src/rust/lib.rs | 70 +- rust/perspective-vieux/src/rust/session.rs | 96 +++ .../src/rust/{ => session}/copy.rs | 0 .../src/rust/{ => session}/download.rs | 1 - .../src/rust/session/view_subscription.rs | 82 ++ rust/perspective-vieux/src/rust/status_bar.rs | 314 -------- rust/perspective-vieux/src/rust/utils.rs | 276 +++---- .../src/rust/utils/perspective.rs | 9 + .../tests/perspective_vieux.rs | 182 +++++ rust/perspective-vieux/tests/status_bar.rs | 178 ----- 59 files changed, 2139 insertions(+), 1781 deletions(-) delete mode 100644 rust/perspective-vieux/src/less/container.less create mode 100644 rust/perspective-vieux/src/less/perspective-vieux.less create mode 100644 rust/perspective-vieux/src/less/split-panel.less create mode 100644 rust/perspective-vieux/src/less/status-bar.less create mode 100644 rust/perspective-vieux/src/rust/components/mod.rs create mode 100644 rust/perspective-vieux/src/rust/components/perspective_vieux.rs create mode 100644 rust/perspective-vieux/src/rust/components/split_panel.rs create mode 100644 rust/perspective-vieux/src/rust/components/status_bar.rs create mode 100644 rust/perspective-vieux/src/rust/components/status_bar_counter.rs create mode 100644 rust/perspective-vieux/src/rust/components/tests/mod.rs create mode 100644 rust/perspective-vieux/src/rust/components/tests/split_panel.rs create mode 100644 rust/perspective-vieux/src/rust/components/tests/status_bar.rs create mode 100644 rust/perspective-vieux/src/rust/components/tests/status_bar_counter.rs create mode 100644 rust/perspective-vieux/src/rust/session.rs rename rust/perspective-vieux/src/rust/{ => session}/copy.rs (100%) rename rust/perspective-vieux/src/rust/{ => session}/download.rs (98%) create mode 100644 rust/perspective-vieux/src/rust/session/view_subscription.rs delete mode 100644 rust/perspective-vieux/src/rust/status_bar.rs create mode 100644 rust/perspective-vieux/tests/perspective_vieux.rs delete mode 100644 rust/perspective-vieux/tests/status_bar.rs diff --git a/packages/perspective-jupyterlab/test/js/resize.spec.js b/packages/perspective-jupyterlab/test/js/resize.spec.js index 08d7b0168e..f77db0c98f 100644 --- a/packages/perspective-jupyterlab/test/js/resize.spec.js +++ b/packages/perspective-jupyterlab/test/js/resize.spec.js @@ -17,7 +17,7 @@ utils.with_server({}, () => { test.capture( "Basic widget functions", async page => { - await page.shadow_click("perspective-viewer", "#config_button"); + await page.evaluate(async () => await document.querySelector("perspective-viewer").toggleConfig()); await page.waitForSelector("perspective-viewer[settings]"); await page.waitForSelector("perspective-viewer:not([updating])"); }, @@ -27,7 +27,7 @@ utils.with_server({}, () => { test.capture( "Resize the container causes the widget to resize", async page => { - await page.shadow_click("perspective-viewer", "#config_button"); + await page.evaluate(async () => await document.querySelector("perspective-viewer").toggleConfig()); await page.waitForSelector("perspective-viewer:not([updating])"); await page.evaluate(async () => { document.querySelector(".PSPContainer").style = "position:absolute;top:0;left:0;width:300px;height:300px"; @@ -45,7 +45,7 @@ utils.with_server({}, () => { test.capture( "row_pivots traitlet works", async page => { - await page.shadow_click("perspective-viewer", "#config_button"); + await page.evaluate(async () => await document.querySelector("perspective-viewer").toggleConfig()); await page.evaluate(() => { window.__WIDGET__.row_pivots = ["State"]; }); diff --git a/packages/perspective-test/src/js/index.js b/packages/perspective-test/src/js/index.js index babb78b3cc..ba47a1b99e 100644 --- a/packages/perspective-test/src/js/index.js +++ b/packages/perspective-test/src/js/index.js @@ -338,10 +338,9 @@ test.capture = function capture(name, body, {timeout = 60000, viewport = null, w } else { await page.evaluate(async x => { const viewer = document.querySelector("perspective-viewer"); - viewer._show_config = true; viewer.restore(x); await viewer.notifyResize(); - await viewer.toggleConfig(); + await viewer.toggleConfig(false); }, OLD_SETTINGS[test_root + _url]); } } diff --git a/packages/perspective-viewer-d3fc/src/less/chart.less b/packages/perspective-viewer-d3fc/src/less/chart.less index 66fa376860..6a16af68f9 100644 --- a/packages/perspective-viewer-d3fc/src/less/chart.less +++ b/packages/perspective-viewer-d3fc/src/less/chart.less @@ -190,6 +190,7 @@ & .x-label { height: 1.2em !important; + line-height: 1em !important; } & d3fc-canvas.plot-area { diff --git a/packages/perspective-viewer-d3fc/test/js/integration/candlestick.spec.js b/packages/perspective-viewer-d3fc/test/js/integration/candlestick.spec.js index 0dc3524044..b699650b9e 100644 --- a/packages/perspective-viewer-d3fc/test/js/integration/candlestick.spec.js +++ b/packages/perspective-viewer-d3fc/test/js/integration/candlestick.spec.js @@ -26,7 +26,7 @@ utils.with_server({}, () => { test.capture("filter to date range.", async page => { const viewer = await page.$("perspective-viewer"); - await page.shadow_click("perspective-viewer", "#config_button"); + await page.evaluate(async () => await document.querySelector("perspective-viewer").toggleConfig()); await page.evaluate(element => element.setAttribute("column-pivots", '["Name"]'), viewer); await page.evaluate(element => element.setAttribute("filters", '[["Date", ">", "2019-01-01"]]'), viewer); await page.evaluate(() => document.activeElement.blur()); diff --git a/packages/perspective-viewer-d3fc/test/js/integration/line.spec.js b/packages/perspective-viewer-d3fc/test/js/integration/line.spec.js index bfeaa88402..abfd029688 100644 --- a/packages/perspective-viewer-d3fc/test/js/integration/line.spec.js +++ b/packages/perspective-viewer-d3fc/test/js/integration/line.spec.js @@ -23,7 +23,7 @@ utils.with_server({}, () => { test.capture("Sets a category axis when pivoted by a computed datetime", async page => { const viewer = await page.$("perspective-viewer"); - await page.shadow_click("perspective-viewer", "#config_button"); + await page.evaluate(async () => await document.querySelector("perspective-viewer").toggleConfig()); await page.evaluate(element => element.setAttribute("computed-columns", JSON.stringify(["hour_bucket('Ship Date')"])), viewer); await page.waitForSelector("perspective-viewer:not([updating])"); await page.evaluate(element => element.setAttribute("row-pivots", '["hour_bucket(Ship Date)"]'), viewer); diff --git a/packages/perspective-viewer-d3fc/test/js/integration/ohlc.spec.js b/packages/perspective-viewer-d3fc/test/js/integration/ohlc.spec.js index 9dde90055a..d7ef3a1e40 100644 --- a/packages/perspective-viewer-d3fc/test/js/integration/ohlc.spec.js +++ b/packages/perspective-viewer-d3fc/test/js/integration/ohlc.spec.js @@ -27,7 +27,7 @@ utils.with_server({}, () => { test.capture("filter to date range.", async page => { const viewer = await page.$("perspective-viewer"); - await page.shadow_click("perspective-viewer", "#config_button"); + await page.evaluate(async () => await document.querySelector("perspective-viewer").toggleConfig()); await page.evaluate(element => element.setAttribute("column-pivots", '["Name"]'), viewer); await page.evaluate(element => element.setAttribute("filters", '[["Date", ">", "2019-01-01"]]'), viewer); await page.waitForSelector("perspective-viewer:not([updating])"); diff --git a/packages/perspective-viewer-d3fc/test/js/integration/scatter.spec.js b/packages/perspective-viewer-d3fc/test/js/integration/scatter.spec.js index 50c69d8d29..6123e8d171 100644 --- a/packages/perspective-viewer-d3fc/test/js/integration/scatter.spec.js +++ b/packages/perspective-viewer-d3fc/test/js/integration/scatter.spec.js @@ -25,7 +25,7 @@ utils.with_server({}, () => { "tooltips with no color and size.", async page => { const viewer = await page.$("perspective-viewer"); - await page.shadow_click("perspective-viewer", "#config_button"); + await page.evaluate(async () => await document.querySelector("perspective-viewer").toggleConfig()); await page.evaluate(element => element.setAttribute("columns", '["Sales", "Profit", null, null, "Quantity"]'), viewer); await page.waitForSelector("perspective-viewer:not([updating])"); const columns = JSON.parse(await page.evaluate(element => element.getAttribute("columns"), viewer)); @@ -51,7 +51,7 @@ utils.with_server({}, () => { "tooltip columns works", async page => { const viewer = await page.$("perspective-viewer"); - await page.shadow_click("perspective-viewer", "#config_button"); + await page.evaluate(async () => await document.querySelector("perspective-viewer").toggleConfig()); await page.evaluate(element => element.setAttribute("columns", '["Sales", "Profit", "Discount", "Quantity", "State"]'), viewer); const columns = JSON.parse(await page.evaluate(element => element.getAttribute("columns"), viewer)); expect(columns).toEqual(["Sales", "Profit", "Discount", "Quantity", "State"]); @@ -77,7 +77,7 @@ utils.with_server({}, () => { "tooltip columns works when color column is null", async page => { const viewer = await page.$("perspective-viewer"); - await page.shadow_click("perspective-viewer", "#config_button"); + await page.evaluate(async () => await document.querySelector("perspective-viewer").toggleConfig()); await page.evaluate(element => element.setAttribute("columns", '["Sales", "Profit", null, "Quantity", "State"]'), viewer); const columns = JSON.parse(await page.evaluate(element => element.getAttribute("columns"), viewer)); expect(columns).toEqual(["Sales", "Profit", null, "Quantity", "State"]); diff --git a/packages/perspective-viewer-d3fc/test/js/integration/sunburst.spec.js b/packages/perspective-viewer-d3fc/test/js/integration/sunburst.spec.js index 634310a3a9..09503042a6 100644 --- a/packages/perspective-viewer-d3fc/test/js/integration/sunburst.spec.js +++ b/packages/perspective-viewer-d3fc/test/js/integration/sunburst.spec.js @@ -23,7 +23,7 @@ utils.with_server({}, () => { test.skip("sunburst label shows formatted date", async page => { const viewer = await page.$("perspective-viewer"); - await page.shadow_click("perspective-viewer", "#config_button"); + await page.evaluate(async () => await document.querySelector("perspective-viewer").toggleConfig()); await page.evaluate(element => element.setAttribute("row-pivots", '["Ship Date"]'), viewer); await page.evaluate(element => element.setAttribute("columns", '["Sales", "Profit"]'), viewer); await page.evaluate(element => element.setAttribute("filters", '[["Product ID", "==", "FUR-BO-10001798"]]'), viewer); @@ -45,7 +45,7 @@ utils.with_server({}, () => { test.skip("sunburst parent button shows formatted date", async page => { const viewer = await page.$("perspective-viewer"); - await page.shadow_click("perspective-viewer", "#config_button"); + await page.evaluate(async () => await document.querySelector("perspective-viewer").toggleConfig()); await page.evaluate(element => element.setAttribute("row-pivots", '["Ship Date", "City"]'), viewer); await page.evaluate(element => element.setAttribute("columns", '["Sales", "Profit"]'), viewer); await page.evaluate(element => element.setAttribute("filters", '[["Product ID", "==", "FUR-BO-10001798"]]'), viewer); diff --git a/packages/perspective-viewer-d3fc/test/js/integration/treemap.spec.js b/packages/perspective-viewer-d3fc/test/js/integration/treemap.spec.js index 645d387387..ceed9e0dbd 100644 --- a/packages/perspective-viewer-d3fc/test/js/integration/treemap.spec.js +++ b/packages/perspective-viewer-d3fc/test/js/integration/treemap.spec.js @@ -25,7 +25,7 @@ utils.with_server({}, () => { "with column position 1 set to null.", async page => { const viewer = await page.$("perspective-viewer"); - await page.shadow_click("perspective-viewer", "#config_button"); + await page.evaluate(async () => await document.querySelector("perspective-viewer").toggleConfig()); await page.evaluate(element => element.setAttribute("row-pivots", '["State"]'), viewer); await page.evaluate(element => element.setAttribute("columns", '["Sales", null, "Quantity"]'), viewer); const columns = JSON.parse(await page.evaluate(element => element.getAttribute("columns"), viewer)); @@ -38,7 +38,7 @@ utils.with_server({}, () => { "tooltip columns works", async page => { const viewer = await page.$("perspective-viewer"); - await page.shadow_click("perspective-viewer", "#config_button"); + await page.evaluate(async () => await document.querySelector("perspective-viewer").toggleConfig()); await page.evaluate(element => element.setAttribute("row-pivots", '["State"]'), viewer); await page.evaluate(element => element.setAttribute("columns", '["Sales", "Profit", "State"]'), viewer); const columns = JSON.parse(await page.evaluate(element => element.getAttribute("columns"), viewer)); @@ -65,7 +65,7 @@ utils.with_server({}, () => { "treemap label shows formatted date", async page => { const viewer = await page.$("perspective-viewer"); - await page.shadow_click("perspective-viewer", "#config_button"); + await page.evaluate(async () => await document.querySelector("perspective-viewer").toggleConfig()); await page.evaluate(element => element.setAttribute("row-pivots", '["Ship Date"]'), viewer); await page.evaluate(element => element.setAttribute("columns", '["Sales", "Profit"]'), viewer); await page.waitForSelector("perspective-viewer:not([updating])"); @@ -75,7 +75,7 @@ utils.with_server({}, () => { test.skip("treemap parent button shows formatted date", async page => { const viewer = await page.$("perspective-viewer"); - await page.shadow_click("perspective-viewer", "#config_button"); + await page.evaluate(async () => await document.querySelector("perspective-viewer").toggleConfig()); await page.evaluate(element => element.setAttribute("row-pivots", '["Ship Date", "Ship Mode"]'), viewer); await page.evaluate(element => element.setAttribute("columns", '["Sales", "Profit"]'), viewer); await page.waitForSelector("perspective-viewer:not([updating])"); diff --git a/packages/perspective-viewer-d3fc/test/results/linux.docker.json b/packages/perspective-viewer-d3fc/test/results/linux.docker.json index 01044f7632..3636185898 100644 --- a/packages/perspective-viewer-d3fc/test/results/linux.docker.json +++ b/packages/perspective-viewer-d3fc/test/results/linux.docker.json @@ -115,8 +115,8 @@ "treemap_pivots_by_a_row_": "c5d7eaebd161205e5a2d106a635fa4a5", "treemap_pivots_by_two_rows_": "d53fc9cbf1c3ba82a80a099c84288048", "treemap_pivots_by_a_column_": "1b57aca80b9495c7b866de0656777990", - "treemap_pivots_by_a_row_and_a_column_": "7a11c95626c72cdee1d30dab5a6c5a1a", - "treemap_pivots_by_two_rows_and_two_columns_": "b74e47e2d31817be0a989fa9e16d54d6", + "treemap_pivots_by_a_row_and_a_column_": "5e1753eca1af5cd4bc3b4985ec609f2c", + "treemap_pivots_by_two_rows_and_two_columns_": "a4b00806e97899d5a1420f53d0adc09a", "treemap_sorts_by_a_hidden_column_": "a24671528348e3d960a290bd12845f17", "treemap_sorts_by_a_numeric_column_": "3cc8682c3f574d9bf726fa498a33461a", "treemap_filters_by_a_numeric_column_": "de32cdf7b7008ac9883859b8e8a90a92", diff --git a/packages/perspective-viewer-datagrid/test/js/superstore.spec.js b/packages/perspective-viewer-datagrid/test/js/superstore.spec.js index e364d815b0..761c9b0b4b 100644 --- a/packages/perspective-viewer-datagrid/test/js/superstore.spec.js +++ b/packages/perspective-viewer-datagrid/test/js/superstore.spec.js @@ -20,7 +20,7 @@ utils.with_server({}, () => { test.capture("resets viewable area when the logical size expands.", async page => { const viewer = await page.$("perspective-viewer"); - await page.shadow_click("perspective-viewer", "#config_button"); + await page.evaluate(async () => await document.querySelector("perspective-viewer").toggleConfig()); await page.evaluate(element => element.setAttribute("column-pivots", '["Category"]'), viewer); await page.waitForSelector("perspective-viewer:not([updating])"); await page.evaluate(element => element.setAttribute("row-pivots", '["City"]'), viewer); @@ -28,11 +28,11 @@ utils.with_server({}, () => { test.capture("resets viewable area when the physical size expands.", async page => { const viewer = await page.$("perspective-viewer"); - await page.shadow_click("perspective-viewer", "#config_button"); + await page.evaluate(async () => await document.querySelector("perspective-viewer").toggleConfig()); await page.evaluate(element => element.setAttribute("row-pivots", '["Category"]'), viewer); await page.waitForSelector("perspective-viewer:not([updating])"); await page.evaluate(element => element.setAttribute("row-pivots", "[]"), viewer); - await page.shadow_click("perspective-viewer", "#config_button"); + await page.evaluate(async () => await document.querySelector("perspective-viewer").toggleConfig()); }); test.capture("perspective dispatches perspective-click event with correct details", async page => { @@ -66,7 +66,7 @@ utils.with_server({}, () => { test.capture("perspective dispatches perspective-click event with correct details when filter is set", async page => { const viewer = await page.$("perspective-viewer"); - await page.shadow_click("perspective-viewer", "#config_button"); + await page.evaluate(async () => await document.querySelector("perspective-viewer").toggleConfig()); await page.evaluate(element => element.setAttribute("row-pivots", '["State", "Category"]'), viewer); await page.waitForSelector("perspective-viewer:not([updating])"); const detail = await click_details(page, 310, 320); diff --git a/packages/perspective-viewer/src/config/rollup.config.js b/packages/perspective-viewer/src/config/rollup.config.js index d130d3528d..759a3f57e3 100644 --- a/packages/perspective-viewer/src/config/rollup.config.js +++ b/packages/perspective-viewer/src/config/rollup.config.js @@ -34,7 +34,8 @@ export default () => { { input: "src/js/viewer.js", external: id => { - // `@finos/perspective-vieux` is inlined for now. So we need to bundle: + // `@finos/perspective-vieux` is inlined for now. So we need to + // bundle: // * Files in this package (starting with '.') // * Anything else in the package root .. // * .. including the inlined `@finos/perspective-vieux` .. diff --git a/packages/perspective-viewer/src/html/viewer.html b/packages/perspective-viewer/src/html/viewer.html index 3a5b8a4ba8..6cdb900a36 100644 --- a/packages/perspective-viewer/src/html/viewer.html +++ b/packages/perspective-viewer/src/html/viewer.html @@ -9,8 +9,9 @@ \ No newline at end of file diff --git a/packages/perspective-viewer/src/js/viewer.js b/packages/perspective-viewer/src/js/viewer.js index 670d770327..2b92cf31c6 100755 --- a/packages/perspective-viewer/src/js/viewer.js +++ b/packages/perspective-viewer/src/js/viewer.js @@ -90,7 +90,6 @@ class PerspectiveViewer extends ActionElement { this._register_ids(); this._register_callbacks(); this._register_view_options(); - this.toggleConfig(); this._check_loaded_table(); } @@ -572,11 +571,11 @@ class PerspectiveViewer extends ActionElement { async load(data) { let table; if (data instanceof Promise) { - this._status_bar.set_table(data); + this._vieux.load(data); table = await data; } else { if (data.type === "table") { - this._status_bar.set_table(Promise.resolve(data)); + this._vieux.load(Promise.resolve(data)); table = data; } else { throw new Error(`Unrecognized input type ${typeof data}. Please use a \`perspective.Table()\``); @@ -609,10 +608,6 @@ class PerspectiveViewer extends ActionElement { * @param {any} widget A `` instance to clone. */ clone(widget) { - if (this._inner_drop_target) { - this._inner_drop_target.innerHTML = widget._inner_drop_target.innerHTML; - } - this._load_table(widget.table); this.restore(widget.save()); } @@ -775,8 +770,8 @@ class PerspectiveViewer extends ActionElement { * * @async */ - async toggleConfig() { - await this._toggle_config(); + async toggleConfig(force) { + await this._vieux.toggle_config(force); } /** diff --git a/packages/perspective-viewer/src/js/viewer/action_element.js b/packages/perspective-viewer/src/js/viewer/action_element.js index 95a976cf6d..c573568f32 100644 --- a/packages/perspective-viewer/src/js/viewer/action_element.js +++ b/packages/perspective-viewer/src/js/viewer/action_element.js @@ -271,31 +271,6 @@ export class ActionElement extends DomElement { } } - _reset_sidepanel() { - this._side_panel.style.width = ""; - } - - _resize_sidepanel(event) { - const initial = document.body.style.cursor; - document.body.style.cursor = "col-resize"; - const start = event.clientX; - const width = this._side_panel.offsetWidth; - const resize = event => { - const new_width = Math.max(0, Math.min(width + (event.clientX - start), this.offsetWidth - 10)); - this._side_panel.style.width = `${new_width}px`; - if (this._plugin) { - this.notifyResize(); - } - }; - const stop = () => { - document.body.style.cursor = initial; - document.removeEventListener("mousemove", resize); - document.removeEventListener("mouseup", stop); - }; - document.addEventListener("mousemove", resize); - document.addEventListener("mouseup", stop); - } - _vis_selector_changed() { this._plugin_information.classList.add("hidden"); this.setAttribute("plugin", this._vis_selector.value); @@ -336,17 +311,20 @@ export class ActionElement extends DomElement { this._active_columns.addEventListener("dragleave", column_dragleave.bind(this)); this._add_computed_expression_button.addEventListener("click", this._open_computed_expression_widget.bind(this)); this._computed_expression_widget.addEventListener("perspective-computed-expression-save", this._save_computed_expression.bind(this)); - this._computed_expression_widget.addEventListener("perspective-computed-expression-resize", this._reset_sidepanel.bind(this)); + + // TODO WIP + // this._computed_expression_widget.addEventListener( + // "perspective-computed-expression-resize", + // this._reset_sidepanel.bind(this) + // ); + this._computed_expression_widget.addEventListener("perspective-computed-expression-type-check", this._type_check_computed_expression.bind(this)); this._computed_expression_widget.addEventListener("perspective-computed-expression-remove", this._clear_all_computed_expressions.bind(this)); this._computed_expression_widget.addEventListener("perspective-computed-expression-update", this._set_computed_expression.bind(this)); - this._config_button.addEventListener("mousedown", this._toggle_config.bind(this)); this._transpose_button.addEventListener("click", this._transpose.bind(this)); - this._drop_target.addEventListener("dragover", dragover.bind(this)); - this._resize_bar.addEventListener("mousedown", this._resize_sidepanel.bind(this)); - this._resize_bar.addEventListener("dblclick", this._reset_sidepanel.bind(this)); - this._status_bar.addEventListener("perspective-statusbar-reset", this.reset.bind(this)); this._vis_selector.addEventListener("change", this._vis_selector_changed.bind(this)); + this._vieux.addEventListener("perspective-vieux-reset", () => this.reset()); + this._vieux.addEventListener("perspective-vieux-resize", () => this._plugin.resize.call(this)); this._plugin_information_action.addEventListener("click", () => { this._debounce_update({ignore_size_check: true, limit_points: false}); diff --git a/packages/perspective-viewer/src/js/viewer/dom_element.js b/packages/perspective-viewer/src/js/viewer/dom_element.js index 4a1c05448f..b33f34f8e7 100644 --- a/packages/perspective-viewer/src/js/viewer/dom_element.js +++ b/packages/perspective-viewer/src/js/viewer/dom_element.js @@ -510,9 +510,6 @@ export class DomElement extends PerspectiveElement { this._side_panel_actions = this.shadowRoot.querySelector("#side_panel__actions"); this._add_computed_expression_button = this.shadowRoot.querySelector("#add-computed-expression"); this._computed_expression_widget = this.shadowRoot.querySelector("perspective-computed-expression-widget"); - this._inner_drop_target = this.shadowRoot.querySelector("#drop_target_inner"); - this._drop_target = this.shadowRoot.querySelector("#drop_target"); - this._config_button = this.shadowRoot.querySelector("#config_button"); this._side_panel = this.shadowRoot.querySelector("#side_panel"); this._top_panel = this.shadowRoot.querySelector("#top_panel"); this._sort = this.shadowRoot.querySelector("#sort"); @@ -520,9 +517,8 @@ export class DomElement extends PerspectiveElement { this._plugin_information = this.shadowRoot.querySelector(".plugin_information"); this._plugin_information_action = this.shadowRoot.querySelector(".plugin_information__action"); this._plugin_information_message = this.shadowRoot.querySelector("#plugin_information_count"); - this._resize_bar = this.shadowRoot.querySelector("#resize_bar"); this._columns_container = this.shadowRoot.querySelector("#columns_container"); - this._status_bar = this.shadowRoot.querySelector("perspective-statusbar"); + this._vieux = this.shadowRoot.querySelector("perspective-vieux"); } // sets state, manipulates DOM diff --git a/packages/perspective-viewer/src/js/viewer/perspective_element.js b/packages/perspective-viewer/src/js/viewer/perspective_element.js index ac02f98645..d0cc8166da 100644 --- a/packages/perspective-viewer/src/js/viewer/perspective_element.js +++ b/packages/perspective-viewer/src/js/viewer/perspective_element.js @@ -489,7 +489,7 @@ export class PerspectiveElement extends StateElement { if (this._view) { this._view.remove_update(this._view_updater); - this._status_bar.remove_on_update_callback(); + await this._vieux.delete_view(); this._view.delete(); this._view = undefined; } @@ -508,7 +508,7 @@ export class PerspectiveElement extends StateElement { throw e; } - this._status_bar.set_view(this._view); + this._vieux.set_view(this._view); const timer = this._render_time(); this._render_count = (this._render_count || 0) + 1; diff --git a/packages/perspective-viewer/src/less/default.less b/packages/perspective-viewer/src/less/default.less index 9f43d1743a..fc26c87d76 100644 --- a/packages/perspective-viewer/src/less/default.less +++ b/packages/perspective-viewer/src/less/default.less @@ -392,24 +392,6 @@ font-weight: 300; } - #config_button { - font-weight: normal; - font-family: var(--button--font-family, Arial); - color: var(--inactive--color, #999); - font-size: var(--button--font-size, 16px); - transition: opacity 0.3s; - overflow: hidden; - - &:hover { - color: var(--active--color, inherit); - } - } - - #config_button:before { - font-feature-settings: "liga"; - content: var(--settings-button--content, "\1f527"); - } - #close_button:before { font-family: var(--button--font-family, Arial); font-feature-settings: "liga"; diff --git a/packages/perspective-viewer/src/less/viewer.less b/packages/perspective-viewer/src/less/viewer.less index e40ad6d3de..6013ed1331 100644 --- a/packages/perspective-viewer/src/less/viewer.less +++ b/packages/perspective-viewer/src/less/viewer.less @@ -9,25 +9,13 @@ @import "./column_labels.less"; @import "./variables.less"; -@import "./status_bar.less"; +// @import "./status_bar.less"; -:host:hover #config_button { - opacity: 1; -} - -#config_button { - background: none; - opacity: 1 !important; -} .button { padding: var(--button--padding, 12px 14px 24px 8px); } -[settings="true"] #config_button { - opacity: 1 !important; -} - @keyframes expand { from { opacity: 0; @@ -113,25 +101,7 @@ } #app { #side_panel { - display: none; - } - #top_panel { - display: none; - } - perspective-statusbar { - display: none; - } - } - #app.settings-open { - padding-bottom: 36px; - #side_panel { - display: flex; - } - #top_panel { - display: flex; - } - perspective-statusbar { - display: flex; + width: 100%; } } #app { @@ -150,7 +120,11 @@ } #pivot_chart_container { flex-grow: 1; - position: relative; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; border: var(--plugin--border, none); overflow: hidden; } @@ -353,19 +327,7 @@ #drop_target_inner h3 { font-weight: 300; } - #config_button { - position: absolute; - top: 0; - left: 0; - opacity: 0; - display: flex; - align-items: center; - justify-content: center; - transition: opacity 0.2s ease-out; - &:hover { - color: var(--active--color, inherit); - } - } + .button { cursor: pointer; font-size: 16px; @@ -424,7 +386,7 @@ #side_panel { position: relative; flex: 0 0 auto; - padding: var(--side_panel--padding, 10px 10px 0px 11px); + padding: var(--side_panel--padding, 10px 2px 0px 11px); } #side_panel > div { display: flex; diff --git a/packages/perspective-viewer/src/themes/material-dense.less b/packages/perspective-viewer/src/themes/material-dense.less index c0a66fe392..e1798bf5d2 100644 --- a/packages/perspective-viewer/src/themes/material-dense.less +++ b/packages/perspective-viewer/src/themes/material-dense.less @@ -11,7 +11,7 @@ @import (reference) "material"; .perspective-viewer-material-dense-base { - --side_panel--padding: 12px 12px 12px 10px; + --side_panel--padding: 12px 4px 12px 10px; --top_panel--padding: 0px 0px 12px 0px; --column-drop-label--margin: -10px 0px 0px 0px; --column-drop-container--margin: 12px 12px 0px 0px; diff --git a/packages/perspective-viewer/src/themes/material.less b/packages/perspective-viewer/src/themes/material.less index 7394f681da..703ef754a6 100644 --- a/packages/perspective-viewer/src/themes/material.less +++ b/packages/perspective-viewer/src/themes/material.less @@ -79,7 +79,7 @@ perspective-viewer, .perspective-viewer-material { --column_selector--width: 31px; --column_selector--font-size: 16px; - --side_panel--padding: 24px 24px 24px 17px; + --side_panel--padding: 24px 16px 24px 17px; --button--padding: 24px 17px 0px 17px; --button--font-size: 16px; diff --git a/packages/perspective-viewer/test/js/computed_expressions.spec.js b/packages/perspective-viewer/test/js/computed_expressions.spec.js index 7ecc0104aa..006bbca92f 100644 --- a/packages/perspective-viewer/test/js/computed_expressions.spec.js +++ b/packages/perspective-viewer/test/js/computed_expressions.spec.js @@ -40,14 +40,14 @@ utils.with_server({}, () => { "superstore.html", () => { test.capture("click on add column button opens the computed expression UI.", async page => { - await page.shadow_click("perspective-viewer", "#config_button"); + await page.evaluate(async () => await document.querySelector("perspective-viewer").toggleConfig()); await page.$("perspective-viewer"); await page.shadow_click("perspective-viewer", "#add-computed-expression"); }); test.capture("click on close button closes the computed expression UI.", async page => { const viewer = await page.$("perspective-viewer"); - await page.shadow_click("perspective-viewer", "#config_button"); + await page.evaluate(async () => await document.querySelector("perspective-viewer").toggleConfig()); await page.$("perspective-viewer"); await page.shadow_click("perspective-viewer", "#add-computed-expression"); await page.shadow_click("perspective-viewer", "perspective-computed-expression-widget", "#psp-computed-expression-widget-close"); @@ -56,7 +56,7 @@ utils.with_server({}, () => { }); test.capture("Typing an expression in the textarea should work even when pushed down to page bottom.", async page => { - await page.shadow_click("perspective-viewer", "#config_button"); + await page.evaluate(async () => await document.querySelector("perspective-viewer").toggleConfig()); await page.$("perspective-viewer"); await page.shadow_click("perspective-viewer", "#add-computed-expression"); await page.shadow_type( @@ -76,7 +76,7 @@ utils.with_server({}, () => { // Autocomplete test.capture("Typing a numeric function should show autocomplete for numeric columns", async page => { - await page.shadow_click("perspective-viewer", "#config_button"); + await page.evaluate(async () => await document.querySelector("perspective-viewer").toggleConfig()); await page.$("perspective-viewer"); await page.shadow_click("perspective-viewer", "#add-computed-expression"); await page.shadow_type("sqrt('", "perspective-viewer", "perspective-computed-expression-widget", "perspective-expression-editor", ".perspective-expression-editor__edit_area"); @@ -89,7 +89,7 @@ utils.with_server({}, () => { }); test.capture("Typing a string function should show autocomplete for string columns", async page => { - await page.shadow_click("perspective-viewer", "#config_button"); + await page.evaluate(async () => await document.querySelector("perspective-viewer").toggleConfig()); await page.$("perspective-viewer"); await page.shadow_click("perspective-viewer", "#add-computed-expression"); await page.shadow_type("uppercase('", "perspective-viewer", "perspective-computed-expression-widget", "perspective-expression-editor", ".perspective-expression-editor__edit_area"); @@ -102,7 +102,7 @@ utils.with_server({}, () => { }); test.capture("Typing a datetime function should show autocomplete for datetime columns", async page => { - await page.shadow_click("perspective-viewer", "#config_button"); + await page.evaluate(async () => await document.querySelector("perspective-viewer").toggleConfig()); await page.$("perspective-viewer"); await page.shadow_click("perspective-viewer", "#add-computed-expression"); await page.shadow_type("month_bucket('", "perspective-viewer", "perspective-computed-expression-widget", "perspective-expression-editor", ".perspective-expression-editor__edit_area"); @@ -115,7 +115,7 @@ utils.with_server({}, () => { }); test.capture("Typing a partial column name should show autocomplete", async page => { - await page.shadow_click("perspective-viewer", "#config_button"); + await page.evaluate(async () => await document.querySelector("perspective-viewer").toggleConfig()); await page.$("perspective-viewer"); await page.shadow_click("perspective-viewer", "#add-computed-expression"); await page.shadow_type('"S', "perspective-viewer", "perspective-computed-expression-widget", "perspective-expression-editor", ".perspective-expression-editor__edit_area"); @@ -128,7 +128,7 @@ utils.with_server({}, () => { }); test.capture("Typing a long expression should dock the autocomplete", async page => { - await page.shadow_click("perspective-viewer", "#config_button"); + await page.evaluate(async () => await document.querySelector("perspective-viewer").toggleConfig()); await page.$("perspective-viewer"); await page.shadow_click("perspective-viewer", "#add-computed-expression"); await page.shadow_type( @@ -141,7 +141,7 @@ utils.with_server({}, () => { }); test.capture("Typing a long expression should dock the autocomplete, and the details panel should show", async page => { - await page.shadow_click("perspective-viewer", "#config_button"); + await page.evaluate(async () => await document.querySelector("perspective-viewer").toggleConfig()); await page.$("perspective-viewer"); await page.shadow_click("perspective-viewer", "#add-computed-expression"); await page.shadow_type( @@ -155,7 +155,7 @@ utils.with_server({}, () => { }); test.capture("An expression that doesn't reach max-width should undock the autocomplete", async page => { - await page.shadow_click("perspective-viewer", "#config_button"); + await page.evaluate(async () => await document.querySelector("perspective-viewer").toggleConfig()); await page.$("perspective-viewer"); await page.shadow_click("perspective-viewer", "#add-computed-expression"); await page.shadow_type( @@ -175,21 +175,21 @@ utils.with_server({}, () => { // Prediction/search test.capture("Typing a partial expression should search by expression label and value", async page => { - await page.shadow_click("perspective-viewer", "#config_button"); + await page.evaluate(async () => await document.querySelector("perspective-viewer").toggleConfig()); await page.$("perspective-viewer"); await page.shadow_click("perspective-viewer", "#add-computed-expression"); await page.shadow_type("day", "perspective-viewer", "perspective-computed-expression-widget", "perspective-expression-editor", ".perspective-expression-editor__edit_area"); }); test.capture("Typing a column name followed by a partial function should not show autocomplete", async page => { - await page.shadow_click("perspective-viewer", "#config_button"); + await page.evaluate(async () => await document.querySelector("perspective-viewer").toggleConfig()); await page.$("perspective-viewer"); await page.shadow_click("perspective-viewer", "#add-computed-expression"); await page.shadow_type("'Sales' a", "perspective-viewer", "perspective-computed-expression-widget", "perspective-expression-editor", ".perspective-expression-editor__edit_area"); }); test.capture("Typing an alias should not show autocomplete", async page => { - await page.shadow_click("perspective-viewer", "#config_button"); + await page.evaluate(async () => await document.querySelector("perspective-viewer").toggleConfig()); await page.$("perspective-viewer"); await page.shadow_click("perspective-viewer", "#add-computed-expression"); await page.shadow_type( @@ -202,7 +202,7 @@ utils.with_server({}, () => { }); test.capture("Pressing arrow down should select the next autocomplete item", async page => { - await page.shadow_click("perspective-viewer", "#config_button"); + await page.evaluate(async () => await document.querySelector("perspective-viewer").toggleConfig()); await page.$("perspective-viewer"); await page.shadow_click("perspective-viewer", "#add-computed-expression"); await page.shadow_type("con", "perspective-viewer", "perspective-computed-expression-widget", "perspective-expression-editor", ".perspective-expression-editor__edit_area"); @@ -212,7 +212,7 @@ utils.with_server({}, () => { }); test.capture("Pressing arrow down on the last item should select the first autocomplete item", async page => { - await page.shadow_click("perspective-viewer", "#config_button"); + await page.evaluate(async () => await document.querySelector("perspective-viewer").toggleConfig()); await page.$("perspective-viewer"); await page.shadow_click("perspective-viewer", "#add-computed-expression"); await page.shadow_type("con", "perspective-viewer", "perspective-computed-expression-widget", "perspective-expression-editor", ".perspective-expression-editor__edit_area"); @@ -222,7 +222,7 @@ utils.with_server({}, () => { }); test.capture("Pressing arrow up should select the previous autocomplete item", async page => { - await page.shadow_click("perspective-viewer", "#config_button"); + await page.evaluate(async () => await document.querySelector("perspective-viewer").toggleConfig()); await page.$("perspective-viewer"); await page.shadow_click("perspective-viewer", "#add-computed-expression"); await page.shadow_type("con", "perspective-viewer", "perspective-computed-expression-widget", "perspective-expression-editor", ".perspective-expression-editor__edit_area"); @@ -233,7 +233,7 @@ utils.with_server({}, () => { }); test.capture("Pressing arrow up from the first item should select the last autocomplete item", async page => { - await page.shadow_click("perspective-viewer", "#config_button"); + await page.evaluate(async () => await document.querySelector("perspective-viewer").toggleConfig()); await page.$("perspective-viewer"); await page.shadow_click("perspective-viewer", "#add-computed-expression"); await page.shadow_type("con", "perspective-viewer", "perspective-computed-expression-widget", "perspective-expression-editor", ".perspective-expression-editor__edit_area"); @@ -241,7 +241,7 @@ utils.with_server({}, () => { }); test.capture("Pressing arrow down on an undocked autocomplete should select the next autocomplete item", async page => { - await page.shadow_click("perspective-viewer", "#config_button"); + await page.evaluate(async () => await document.querySelector("perspective-viewer").toggleConfig()); await page.$("perspective-viewer"); await page.shadow_click("perspective-viewer", "#add-computed-expression"); await page.shadow_type('"S', "perspective-viewer", "perspective-computed-expression-widget", "perspective-expression-editor", ".perspective-expression-editor__edit_area"); @@ -250,7 +250,7 @@ utils.with_server({}, () => { }); test.capture("Pressing arrow down on the last item on an undocked autocomplete should select the first autocomplete item", async page => { - await page.shadow_click("perspective-viewer", "#config_button"); + await page.evaluate(async () => await document.querySelector("perspective-viewer").toggleConfig()); await page.$("perspective-viewer"); await page.shadow_click("perspective-viewer", "#add-computed-expression"); await page.shadow_type('"S', "perspective-viewer", "perspective-computed-expression-widget", "perspective-expression-editor", ".perspective-expression-editor__edit_area"); @@ -259,7 +259,7 @@ utils.with_server({}, () => { }); test.capture("Pressing arrow up on an undocked autocomplete should select the previous autocomplete item", async page => { - await page.shadow_click("perspective-viewer", "#config_button"); + await page.evaluate(async () => await document.querySelector("perspective-viewer").toggleConfig()); await page.$("perspective-viewer"); await page.shadow_click("perspective-viewer", "#add-computed-expression"); await page.shadow_type('"S', "perspective-viewer", "perspective-computed-expression-widget", "perspective-expression-editor", ".perspective-expression-editor__edit_area"); @@ -268,7 +268,7 @@ utils.with_server({}, () => { }); test.capture("Pressing arrow up from the first item on an undocked autocomplete should select the last autocomplete item", async page => { - await page.shadow_click("perspective-viewer", "#config_button"); + await page.evaluate(async () => await document.querySelector("perspective-viewer").toggleConfig()); await page.$("perspective-viewer"); await page.shadow_click("perspective-viewer", "#add-computed-expression"); await page.shadow_type('"S', "perspective-viewer", "perspective-computed-expression-widget", "perspective-expression-editor", ".perspective-expression-editor__edit_area"); @@ -277,7 +277,7 @@ utils.with_server({}, () => { // Replace items test.capture("Pressing enter should apply the autocomplete item", async page => { - await page.shadow_click("perspective-viewer", "#config_button"); + await page.evaluate(async () => await document.querySelector("perspective-viewer").toggleConfig()); await page.$("perspective-viewer"); await page.shadow_click("perspective-viewer", "#add-computed-expression"); await page.shadow_type("con", "perspective-viewer", "perspective-computed-expression-widget", "perspective-expression-editor", ".perspective-expression-editor__edit_area"); @@ -288,7 +288,7 @@ utils.with_server({}, () => { }); test.capture("Pressing enter should apply the selected column", async page => { - await page.shadow_click("perspective-viewer", "#config_button"); + await page.evaluate(async () => await document.querySelector("perspective-viewer").toggleConfig()); await page.$("perspective-viewer"); await page.shadow_click("perspective-viewer", "#add-computed-expression"); await page.shadow_type("'S", "perspective-viewer", "perspective-computed-expression-widget", "perspective-expression-editor", ".perspective-expression-editor__edit_area"); @@ -297,7 +297,7 @@ utils.with_server({}, () => { }); test.capture("Column replace should work for a fragment", async page => { - await page.shadow_click("perspective-viewer", "#config_button"); + await page.evaluate(async () => await document.querySelector("perspective-viewer").toggleConfig()); await page.$("perspective-viewer"); await page.shadow_click("perspective-viewer", "#add-computed-expression"); await page.shadow_type('"Pro', "perspective-viewer", "perspective-computed-expression-widget", "perspective-expression-editor", ".perspective-expression-editor__edit_area"); @@ -306,7 +306,7 @@ utils.with_server({}, () => { }); test.skip("Column replace should work for a fragment with spaces", async page => { - await page.shadow_click("perspective-viewer", "#config_button"); + await page.evaluate(async () => await document.querySelector("perspective-viewer").toggleConfig()); await page.$("perspective-viewer"); await page.shadow_click("perspective-viewer", "#add-computed-expression"); await page.shadow_type('"Product ', "perspective-viewer", "perspective-computed-expression-widget", "perspective-expression-editor", ".perspective-expression-editor__edit_area"); @@ -317,7 +317,7 @@ utils.with_server({}, () => { // Functionality - make sure the UI will validate error cases so // the engine is not affected. test.capture("A type-invalid expression should show an error message", async page => { - await page.shadow_click("perspective-viewer", "#config_button"); + await page.evaluate(async () => await document.querySelector("perspective-viewer").toggleConfig()); await page.$("perspective-viewer"); await page.shadow_click("perspective-viewer", "#add-computed-expression"); await page.shadow_type( @@ -331,7 +331,7 @@ utils.with_server({}, () => { }); test.capture("An expression with invalid inputs should show an error message", async page => { - await page.shadow_click("perspective-viewer", "#config_button"); + await page.evaluate(async () => await document.querySelector("perspective-viewer").toggleConfig()); await page.$("perspective-viewer"); await page.shadow_click("perspective-viewer", "#add-computed-expression"); await page.shadow_type( @@ -345,7 +345,7 @@ utils.with_server({}, () => { }); test.capture("An expression that overwrites a real column should show an error message", async page => { - await page.shadow_click("perspective-viewer", "#config_button"); + await page.evaluate(async () => await document.querySelector("perspective-viewer").toggleConfig()); await page.$("perspective-viewer"); await page.shadow_click("perspective-viewer", "#add-computed-expression"); await page.shadow_type( @@ -359,7 +359,7 @@ utils.with_server({}, () => { }); test.capture("An expression that overwrites a computed column with a different type should show an error message", async page => { - await page.shadow_click("perspective-viewer", "#config_button"); + await page.evaluate(async () => await document.querySelector("perspective-viewer").toggleConfig()); await page.$("perspective-viewer"); await page.shadow_click("perspective-viewer", "#add-computed-expression"); await page.shadow_type( @@ -384,7 +384,7 @@ utils.with_server({}, () => { }); test.capture("Typing enter should save a valid expression", async page => { - await page.shadow_click("perspective-viewer", "#config_button"); + await page.evaluate(async () => await document.querySelector("perspective-viewer").toggleConfig()); await page.$("perspective-viewer"); await page.shadow_click("perspective-viewer", "#add-computed-expression"); await page.shadow_type( @@ -400,7 +400,7 @@ utils.with_server({}, () => { }); test.capture("Typing enter should not save an invalid expression", async page => { - await page.shadow_click("perspective-viewer", "#config_button"); + await page.evaluate(async () => await document.querySelector("perspective-viewer").toggleConfig()); await page.$("perspective-viewer"); await page.shadow_click("perspective-viewer", "#add-computed-expression"); await page.shadow_type( @@ -416,7 +416,7 @@ utils.with_server({}, () => { }); test.skip("Typing a large expression in the textarea should work even when pushed down to page bottom.", async page => { - await page.shadow_click("perspective-viewer", "#config_button"); + await page.evaluate(async () => await document.querySelector("perspective-viewer").toggleConfig()); await page.$("perspective-viewer"); await page.shadow_click("perspective-viewer", "#add-computed-expression"); await page.shadow_type( @@ -437,7 +437,7 @@ utils.with_server({}, () => { // Remove test.capture("Removing computed columns should reset active columns, pivots, sort, and filter.", async page => { const viewer = await page.$("perspective-viewer"); - await page.shadow_click("perspective-viewer", "#config_button"); + await page.evaluate(async () => await document.querySelector("perspective-viewer").toggleConfig()); await add_computed_expression(page, 'sqrt("Profit") as "first"'); await page.waitForSelector("perspective-viewer:not([updating])"); await add_computed_expression(page, 'sqrt("Sales") as "second"'); @@ -481,7 +481,7 @@ utils.with_server({}, () => { // reset test.capture("Resetting the viewer with computed columns should place columns in the inactive list.", async page => { const viewer = await page.$("perspective-viewer"); - await page.shadow_click("perspective-viewer", "#config_button"); + await page.evaluate(async () => await document.querySelector("perspective-viewer").toggleConfig()); await add_computed_expression(page, 'sqrt("Profit")'); await page.waitForSelector("perspective-viewer:not([updating])"); await add_computed_expression(page, 'sqrt("Sales")'); @@ -494,7 +494,7 @@ utils.with_server({}, () => { test.capture("Resetting the viewer with computed columns in active columns should reset columns but not delete columns.", async page => { const viewer = await page.$("perspective-viewer"); - await page.shadow_click("perspective-viewer", "#config_button"); + await page.evaluate(async () => await document.querySelector("perspective-viewer").toggleConfig()); await add_computed_expression(page, 'day_of_week("Order Date") as "Computed"'); await page.waitForSelector("perspective-viewer:not([updating])"); await add_computed_expression(page, 'month_of_year("Ship Date") as "Computed2"'); @@ -509,7 +509,7 @@ utils.with_server({}, () => { test.capture("Resetting the viewer with computed columns set as pivots should reset pivots but not delete columns.", async page => { const viewer = await page.$("perspective-viewer"); - await page.shadow_click("perspective-viewer", "#config_button"); + await page.evaluate(async () => await document.querySelector("perspective-viewer").toggleConfig()); await add_computed_expression(page, 'day_of_week("Order Date") as "Computed"'); await page.waitForSelector("perspective-viewer:not([updating])"); await add_computed_expression(page, 'month_of_year("Ship Date") as "Computed2"'); @@ -525,7 +525,7 @@ utils.with_server({}, () => { test.capture("Resetting the viewer with computed columns set as filters should reset filters but not delete columns.", async page => { const viewer = await page.$("perspective-viewer"); - await page.shadow_click("perspective-viewer", "#config_button"); + await page.evaluate(async () => await document.querySelector("perspective-viewer").toggleConfig()); await add_computed_expression(page, 'day_of_week("Order Date") as "Computed"'); await page.waitForSelector("perspective-viewer:not([updating])"); await add_computed_expression(page, 'month_of_year("Ship Date") as "Computed2"'); @@ -550,7 +550,7 @@ utils.with_server({}, () => { test.capture("Resetting the viewer with computed columns set as sort should reset sort but not delete columns.", async page => { const viewer = await page.$("perspective-viewer"); - await page.shadow_click("perspective-viewer", "#config_button"); + await page.evaluate(async () => await document.querySelector("perspective-viewer").toggleConfig()); await add_computed_expression(page, 'day_of_week("Order Date") as "Computed"'); await page.waitForSelector("perspective-viewer:not([updating])"); await add_computed_expression(page, 'month_of_year("Ship Date") as "Computed2"'); @@ -576,7 +576,7 @@ utils.with_server({}, () => { // save test.capture("saving without an expression should fail as button is disabled.", async page => { const viewer = await page.$("perspective-viewer"); - await page.shadow_click("perspective-viewer", "#config_button"); + await page.evaluate(async () => await document.querySelector("perspective-viewer").toggleConfig()); await page.shadow_click("perspective-viewer", "#add-computed-expression"); await page.evaluate( element => @@ -590,27 +590,27 @@ utils.with_server({}, () => { test.capture("saving a single computed expression should add it to inactive columns.", async page => { const viewer = await page.$("perspective-viewer"); - await page.shadow_click("perspective-viewer", "#config_button"); + await page.evaluate(async () => await document.querySelector("perspective-viewer").toggleConfig()); await add_computed_expression(page, 'month_bucket("Ship Date")'); await page.evaluate(element => element.setAttribute("columns", JSON.stringify(["Sales", "Profit"])), viewer); }); test.capture("saving a single computed expression with dependencies should add all columns to inactive columns.", async page => { const viewer = await page.$("perspective-viewer"); - await page.shadow_click("perspective-viewer", "#config_button"); + await page.evaluate(async () => await document.querySelector("perspective-viewer").toggleConfig()); await add_computed_expression(page, '"Sales" + (pow2("Sales")) as "new column"'); await page.evaluate(element => element.setAttribute("columns", JSON.stringify(["Sales", "Profit"])), viewer); }); test.skip("saving a duplicate expression should fail with error message.", async page => { - await page.shadow_click("perspective-viewer", "#config_button"); + await page.evaluate(async () => await document.querySelector("perspective-viewer").toggleConfig()); await add_computed_expression(page, 'month_bucket("Ship Date")'); await add_computed_expression(page, 'month_bucket("Ship Date")'); }); // Transforms test.capture("Computed expression columns should persist when new views are created.", async page => { - await page.shadow_click("perspective-viewer", "#config_button"); + await page.evaluate(async () => await document.querySelector("perspective-viewer").toggleConfig()); await add_computed_expression(page, 'month_bucket("Ship Date")'); const viewer = await page.$("perspective-viewer"); await page.evaluate(element => element.setAttribute("row-pivots", '["State", "City"]'), viewer); @@ -618,7 +618,7 @@ utils.with_server({}, () => { }); test.capture("Computed expression columns should persist when new computed columns are added.", async page => { - await page.shadow_click("perspective-viewer", "#config_button"); + await page.evaluate(async () => await document.querySelector("perspective-viewer").toggleConfig()); await add_computed_expression(page, 'month_bucket("Ship Date")'); await add_computed_expression(page, '"Sales" % "Profit"'); const viewer = await page.$("perspective-viewer"); @@ -627,7 +627,7 @@ utils.with_server({}, () => { // usage test.capture("aggregates by computed expression column.", async page => { - await page.shadow_click("perspective-viewer", "#config_button"); + await page.evaluate(async () => await document.querySelector("perspective-viewer").toggleConfig()); await add_computed_expression(page, 'concat_comma("State", "City") as "Computed"'); const viewer = await page.$("perspective-viewer"); await page.evaluate(element => { @@ -640,7 +640,7 @@ utils.with_server({}, () => { }); test.capture("row pivots by computed expression column.", async page => { - await page.shadow_click("perspective-viewer", "#config_button"); + await page.evaluate(async () => await document.querySelector("perspective-viewer").toggleConfig()); await add_computed_expression(page, 'concat_comma("State", "City") as "Computed"'); const viewer = await page.$("perspective-viewer"); await page.evaluate(element => element.setAttribute("row-pivots", '["Computed"]'), viewer); @@ -649,7 +649,7 @@ utils.with_server({}, () => { }); test.capture("column pivots by computed expression column.", async page => { - await page.shadow_click("perspective-viewer", "#config_button"); + await page.evaluate(async () => await document.querySelector("perspective-viewer").toggleConfig()); await add_computed_expression(page, 'concat_comma("State", "City") as "Computed"'); const viewer = await page.$("perspective-viewer"); await page.evaluate(element => element.setAttribute("column-pivots", '["Computed"]'), viewer); @@ -659,7 +659,7 @@ utils.with_server({}, () => { }); test.capture("row and column pivots by computed expression column.", async page => { - await page.shadow_click("perspective-viewer", "#config_button"); + await page.evaluate(async () => await document.querySelector("perspective-viewer").toggleConfig()); await add_computed_expression(page, 'concat_comma("State", "City") as "Computed"'); await page.waitForSelector("perspective-viewer:not([updating])"); await add_computed_expression(page, 'uppercase("City") as "Computed2"'); @@ -674,7 +674,7 @@ utils.with_server({}, () => { }); test.capture("sorts by computed expression column.", async page => { - await page.shadow_click("perspective-viewer", "#config_button"); + await page.evaluate(async () => await document.querySelector("perspective-viewer").toggleConfig()); await add_computed_expression(page, 'pow2("Sales") as "Computed"'); const viewer = await page.$("perspective-viewer"); await page.evaluate(element => element.setAttribute("sort", JSON.stringify([["Computed", "desc"]])), viewer); @@ -682,7 +682,7 @@ utils.with_server({}, () => { }); test.capture("filters by computed expression column.", async page => { - await page.shadow_click("perspective-viewer", "#config_button"); + await page.evaluate(async () => await document.querySelector("perspective-viewer").toggleConfig()); await add_computed_expression(page, 'day_of_week("Order Date") as "Computed"'); const viewer = await page.$("perspective-viewer"); await page.evaluate(element => element.setAttribute("filters", '[["Computed", "==", "2 Monday"]]'), viewer); @@ -692,7 +692,7 @@ utils.with_server({}, () => { }); test.capture("computed expression column aggregates should persist.", async page => { - await page.shadow_click("perspective-viewer", "#config_button"); + await page.evaluate(async () => await document.querySelector("perspective-viewer").toggleConfig()); await add_computed_expression(page, 'day_of_week("Order Date") as "Computed"'); const viewer = await page.$("perspective-viewer"); await page.evaluate(element => element.setAttribute("row-pivots", '["Quantity"]'), viewer); @@ -704,7 +704,7 @@ utils.with_server({}, () => { // Attributes test.capture("adds computed expression via attribute", async page => { - await page.shadow_click("perspective-viewer", "#config_button"); + await page.evaluate(async () => await document.querySelector("perspective-viewer").toggleConfig()); const viewer = await page.$("perspective-viewer"); await page.evaluate(element => { const computed = ['"Sales" + "Profit" as "First"', 'sqrt((pow2("Row ID"))) as "Second"']; @@ -715,7 +715,7 @@ utils.with_server({}, () => { }); test.capture("adds computed expression via attribute in classic syntax", async page => { - await page.shadow_click("perspective-viewer", "#config_button"); + await page.evaluate(async () => await document.querySelector("perspective-viewer").toggleConfig()); const viewer = await page.$("perspective-viewer"); await page.evaluate(element => { const computed = [ @@ -741,7 +741,7 @@ utils.with_server({}, () => { // Save and restore test.capture("Computed expressions are saved without changes", async page => { - await page.shadow_click("perspective-viewer", "#config_button"); + await page.evaluate(async () => await document.querySelector("perspective-viewer").toggleConfig()); await add_computed_expression(page, 'day_of_week("Order Date") as "Computed"'); await add_computed_expression(page, 'month_of_year("Ship Date") as "Computed2"'); const viewer = await page.$("perspective-viewer"); @@ -759,7 +759,7 @@ utils.with_server({}, () => { }); test.skip("Computed expressions are restored without changes", async page => { - await page.shadow_click("perspective-viewer", "#config_button"); + await page.evaluate(async () => await document.querySelector("perspective-viewer").toggleConfig()); await page.waitForSelector("perspective-viewer:not([updating])"); const viewer = await page.$("perspective-viewer"); await page.evaluate(async element => { @@ -773,7 +773,7 @@ utils.with_server({}, () => { }); test.skip("On restore, computed expressions in the active columns list are restored correctly.", async page => { - await page.shadow_click("perspective-viewer", "#config_button"); + await page.evaluate(async () => await document.querySelector("perspective-viewer").toggleConfig()); await page.waitForSelector("perspective-viewer:not([updating])"); const viewer = await page.$("perspective-viewer"); await page.evaluate(async element => { @@ -787,7 +787,7 @@ utils.with_server({}, () => { }); test.skip("On restore, computed expressions in pivots are restored correctly.", async page => { - await page.shadow_click("perspective-viewer", "#config_button"); + await page.evaluate(async () => await document.querySelector("perspective-viewer").toggleConfig()); await page.waitForSelector("perspective-viewer:not([updating])"); const viewer = await page.$("perspective-viewer"); await page.evaluate(async element => { @@ -803,7 +803,7 @@ utils.with_server({}, () => { }); test.skip("On restore, computed expressions in filter are restored correctly.", async page => { - await page.shadow_click("perspective-viewer", "#config_button"); + await page.evaluate(async () => await document.querySelector("perspective-viewer").toggleConfig()); await page.waitForSelector("perspective-viewer:not([updating])"); const viewer = await page.$("perspective-viewer"); await page.evaluate(async element => { @@ -818,7 +818,7 @@ utils.with_server({}, () => { }); test.skip("On restore, computed expressions in sort are restored correctly.", async page => { - await page.shadow_click("perspective-viewer", "#config_button"); + await page.evaluate(async () => await document.querySelector("perspective-viewer").toggleConfig()); await page.waitForSelector("perspective-viewer:not([updating])"); const viewer = await page.$("perspective-viewer"); await page.evaluate(async element => { @@ -833,7 +833,7 @@ utils.with_server({}, () => { }); test.skip("On restore, computed expressions in classic syntax are parsed correctly.", async page => { - await page.shadow_click("perspective-viewer", "#config_button"); + await page.evaluate(async () => await document.querySelector("perspective-viewer").toggleConfig()); await page.waitForSelector("perspective-viewer:not([updating])"); const viewer = await page.$("perspective-viewer"); await page.evaluate(async element => { @@ -859,7 +859,7 @@ utils.with_server({}, () => { }); test.skip("On restore, user defined aggregates are maintained on computed expression columns", async page => { - await page.shadow_click("perspective-viewer", "#config_button"); + await page.evaluate(async () => await document.querySelector("perspective-viewer").toggleConfig()); const viewer = await page.$("perspective-viewer"); await page.evaluate(async element => { const config = { diff --git a/packages/perspective-viewer/test/js/filters.spec.js b/packages/perspective-viewer/test/js/filters.spec.js index 39e672c824..78a10cab35 100644 --- a/packages/perspective-viewer/test/js/filters.spec.js +++ b/packages/perspective-viewer/test/js/filters.spec.js @@ -16,21 +16,21 @@ utils.with_server({}, () => { () => { test.capture("autocomplete on date column", async page => { const viewer = await page.$("perspective-viewer"); - await await page.shadow_click("perspective-viewer", "#config_button"); + await await page.evaluate(async () => await document.querySelector("perspective-viewer").toggleConfig()); await page.evaluate(element => element.setAttribute("filters", '[["v", "==", ""]]'), viewer); await page.waitForSelector("perspective-viewer:not([updating])"); }); test.capture("autocomplete on datetime column", async page => { const viewer = await page.$("perspective-viewer"); - await await page.shadow_click("perspective-viewer", "#config_button"); + await await page.evaluate(async () => await document.querySelector("perspective-viewer").toggleConfig()); await page.evaluate(element => element.setAttribute("filters", '[["w", "==", ""]]'), viewer); await page.waitForSelector("perspective-viewer:not([updating])"); }); test.capture("equals on date column", async page => { const viewer = await page.$("perspective-viewer"); - await await page.shadow_click("perspective-viewer", "#config_button"); + await await page.evaluate(async () => await document.querySelector("perspective-viewer").toggleConfig()); await page.evaluate(element => element.setAttribute("filters", '[["v", "==", "11/1/2020"]]'), viewer); await page.waitForSelector("perspective-viewer:not([updating])"); await page.evaluate(() => document.activeElement.blur()); @@ -38,7 +38,7 @@ utils.with_server({}, () => { test.capture("equals ISO string on datetime column", async page => { const viewer = await page.$("perspective-viewer"); - await await page.shadow_click("perspective-viewer", "#config_button"); + await await page.evaluate(async () => await document.querySelector("perspective-viewer").toggleConfig()); await page.evaluate(element => { const dt = new Date(2020, 11, 1, 23, 30, 55).toISOString(); element.setAttribute("filters", '[["w", "==", "' + dt + '"]]'); @@ -49,7 +49,7 @@ utils.with_server({}, () => { test.capture("equals US locale string on datetime column", async page => { const viewer = await page.$("perspective-viewer"); - await await page.shadow_click("perspective-viewer", "#config_button"); + await await page.evaluate(async () => await document.querySelector("perspective-viewer").toggleConfig()); await page.evaluate(element => { element.setAttribute("filters", '[["w", "==", "12/01/2020, 11:30:55 PM"]]'); }, viewer); @@ -59,7 +59,7 @@ utils.with_server({}, () => { test.capture("greater than on date column", async page => { const viewer = await page.$("perspective-viewer"); - await await page.shadow_click("perspective-viewer", "#config_button"); + await await page.evaluate(async () => await document.querySelector("perspective-viewer").toggleConfig()); await page.evaluate(element => element.setAttribute("filters", '[["v", ">", "03/01/2020"]]'), viewer); await page.waitForSelector("perspective-viewer:not([updating])"); await page.evaluate(() => document.activeElement.blur()); @@ -67,7 +67,7 @@ utils.with_server({}, () => { test.capture("greater than ISO string on datetime column", async page => { const viewer = await page.$("perspective-viewer"); - await await page.shadow_click("perspective-viewer", "#config_button"); + await await page.evaluate(async () => await document.querySelector("perspective-viewer").toggleConfig()); await page.evaluate(element => { const dt = new Date(2020, 9, 1, 15, 30, 55).toISOString(); element.setAttribute("filters", '[["w", ">", "' + dt + '"]]'); @@ -78,7 +78,7 @@ utils.with_server({}, () => { test.capture("greater than US locale string on datetime column", async page => { const viewer = await page.$("perspective-viewer"); - await await page.shadow_click("perspective-viewer", "#config_button"); + await await page.evaluate(async () => await document.querySelector("perspective-viewer").toggleConfig()); await page.evaluate(element => { element.setAttribute("filters", '[["w", ">", "10/01/2020, 03:30:55 PM"]]'); }, viewer); @@ -88,7 +88,7 @@ utils.with_server({}, () => { test.capture("less than on date column", async page => { const viewer = await page.$("perspective-viewer"); - await await page.shadow_click("perspective-viewer", "#config_button"); + await await page.evaluate(async () => await document.querySelector("perspective-viewer").toggleConfig()); await page.evaluate(element => element.setAttribute("filters", '[["v", "<", "10/01/2020"]]'), viewer); await page.waitForSelector("perspective-viewer:not([updating])"); await page.evaluate(() => document.activeElement.blur()); @@ -96,7 +96,7 @@ utils.with_server({}, () => { test.capture("less than ISO string on datetime column", async page => { const viewer = await page.$("perspective-viewer"); - await await page.shadow_click("perspective-viewer", "#config_button"); + await await page.evaluate(async () => await document.querySelector("perspective-viewer").toggleConfig()); await page.evaluate(element => { const dt = new Date(2020, 9, 1, 15, 30, 55).toISOString(); element.setAttribute("filters", '[["w", "<", "' + dt + '"]]'); @@ -107,7 +107,7 @@ utils.with_server({}, () => { test.capture("less than US locale string on datetime column", async page => { const viewer = await page.$("perspective-viewer"); - await await page.shadow_click("perspective-viewer", "#config_button"); + await await page.evaluate(async () => await document.querySelector("perspective-viewer").toggleConfig()); await page.evaluate(element => { element.setAttribute("filters", '[["w", "<", "10/01/2020, 03:30:55 PM"]]'); }, viewer); diff --git a/packages/perspective-viewer/test/js/leaks.spec.js b/packages/perspective-viewer/test/js/leaks.spec.js index 6f82d8fa6e..e2c0a628be 100644 --- a/packages/perspective-viewer/test/js/leaks.spec.js +++ b/packages/perspective-viewer/test/js/leaks.spec.js @@ -31,7 +31,7 @@ utils.with_server({}, () => { }, viewer); await page.waitForSelector("perspective-viewer:not([updating])"); } - await page.shadow_click("perspective-viewer", "#config_button"); + await page.evaluate(async () => await document.querySelector("perspective-viewer").toggleConfig()); await page.evaluate(async () => document.getElementsByTagName("perspective-viewer")[0].load( window.__WORKER__.table( @@ -51,7 +51,7 @@ utils.with_server({}, () => { "doesn't leak views when setting row pivots.", async page => { const viewer = await page.$("perspective-viewer"); - await page.shadow_click("perspective-viewer", "#config_button"); + await page.evaluate(async () => await document.querySelector("perspective-viewer").toggleConfig()); for (var i = 0; i < 100; i++) { await page.evaluate(element => { let pivots = ["State", "City", "Segment", "Ship Mode", "Region", "Category"]; @@ -71,7 +71,7 @@ utils.with_server({}, () => { "doesn't leak views when setting filters.", async page => { const viewer = await page.$("perspective-viewer"); - await page.shadow_click("perspective-viewer", "#config_button"); + await page.evaluate(async () => await document.querySelector("perspective-viewer").toggleConfig()); for (var i = 0; i < 100; i++) { await page.evaluate(element => { element.setAttribute("filters", JSON.stringify([["Sales", ">", Math.random() * 100 + 100]])); diff --git a/packages/perspective-viewer/test/js/regressions.spec.js b/packages/perspective-viewer/test/js/regressions.spec.js index 57137105be..6b01f38748 100644 --- a/packages/perspective-viewer/test/js/regressions.spec.js +++ b/packages/perspective-viewer/test/js/regressions.spec.js @@ -47,7 +47,7 @@ utils.with_server({}, () => { schema ); await page.waitForSelector("perspective-viewer:not([updating])"); - await page.shadow_click("perspective-viewer", "#config_button"); + await page.evaluate(async () => await document.querySelector("perspective-viewer").toggleConfig()); await page.evaluate( async (viewer, data, schema) => { diff --git a/packages/perspective-viewer/test/js/render_warning_tests.js b/packages/perspective-viewer/test/js/render_warning_tests.js index 8c847d4eda..113a8740fc 100644 --- a/packages/perspective-viewer/test/js/render_warning_tests.js +++ b/packages/perspective-viewer/test/js/render_warning_tests.js @@ -26,7 +26,7 @@ exports.default = function(plugin_name, columns) { plugin.max_cells = 1; }, plugin_name); - await page.shadow_click("perspective-viewer", "#config_button"); + await page.evaluate(async () => await document.querySelector("perspective-viewer").toggleConfig()); await page.evaluate((element, view_columns) => element.setAttribute("columns", view_columns), viewer, view_columns); await page.evaluate(element => element.setAttribute("column-pivots", '["Profit"]'), viewer); await page.waitForSelector("perspective-viewer:not([updating])"); @@ -39,7 +39,7 @@ exports.default = function(plugin_name, columns) { plugin.max_cells = 1; }, plugin_name); - await page.shadow_click("perspective-viewer", "#config_button"); + await page.evaluate(async () => await document.querySelector("perspective-viewer").toggleConfig()); await page.evaluate((element, view_columns) => element.setAttribute("columns", view_columns), viewer, view_columns); await page.evaluate(element => element.setAttribute("column-pivots", '["Profit"]'), viewer); await page.waitForSelector("perspective-viewer:not([updating])"); @@ -52,7 +52,7 @@ exports.default = function(plugin_name, columns) { plugin.max_columns = 1; }, plugin_name); - await page.shadow_click("perspective-viewer", "#config_button"); + await page.evaluate(async () => await document.querySelector("perspective-viewer").toggleConfig()); await page.evaluate((element, view_columns) => element.setAttribute("columns", view_columns), viewer, view_columns); await page.evaluate(element => element.setAttribute("column-pivots", '["Profit"]'), viewer); await page.waitForSelector("perspective-viewer:not([updating])"); @@ -65,7 +65,7 @@ exports.default = function(plugin_name, columns) { plugin.max_columns = 1; }, plugin_name); - await page.shadow_click("perspective-viewer", "#config_button"); + await page.evaluate(async () => await document.querySelector("perspective-viewer").toggleConfig()); await page.evaluate((element, view_columns) => element.setAttribute("columns", view_columns), viewer, view_columns); await page.evaluate(element => element.setAttribute("column-pivots", '["Row ID"]'), viewer); await page.waitForSelector("perspective-viewer:not([updating])"); @@ -80,7 +80,7 @@ exports.default = function(plugin_name, columns) { plugin.max_columns = 5; }, plugin_name); - await page.shadow_click("perspective-viewer", "#config_button"); + await page.evaluate(async () => await document.querySelector("perspective-viewer").toggleConfig()); await page.evaluate((element, view_columns) => element.setAttribute("columns", view_columns), viewer, view_columns); await page.evaluate(element => element.setAttribute("column-pivots", '["Profit"]'), viewer); await page.waitForSelector("perspective-viewer:not([updating])"); @@ -95,7 +95,7 @@ exports.default = function(plugin_name, columns) { plugin.max_columns = 1; }, plugin_name); - await page.shadow_click("perspective-viewer", "#config_button"); + await page.evaluate(async () => await document.querySelector("perspective-viewer").toggleConfig()); await page.evaluate((element, view_columns) => element.setAttribute("columns", view_columns), viewer, view_columns); await page.evaluate(element => element.setAttribute("column-pivots", '["Row ID"]'), viewer); await page.waitForSelector("perspective-viewer:not([updating])"); diff --git a/packages/perspective-viewer/test/js/responsive_tests.js b/packages/perspective-viewer/test/js/responsive_tests.js index c3d19fb99f..00247b936f 100644 --- a/packages/perspective-viewer/test/js/responsive_tests.js +++ b/packages/perspective-viewer/test/js/responsive_tests.js @@ -13,7 +13,7 @@ exports.default = function() { "shows horizontal columns on small vertical viewports.", async page => { const viewer = await page.$("perspective-viewer"); - await page.shadow_click("perspective-viewer", "#config_button"); + await page.evaluate(async () => await document.querySelector("perspective-viewer").toggleConfig()); await page.evaluate(element => element.setAttribute("columns", '["Discount","Profit","Sales"]'), viewer); }, { @@ -29,7 +29,7 @@ exports.default = function() { "shows horizontal columns on small vertical and horizontal viewports.", async page => { const viewer = await page.$("perspective-viewer"); - await page.shadow_click("perspective-viewer", "#config_button"); + await page.evaluate(async () => await document.querySelector("perspective-viewer").toggleConfig()); await page.evaluate(element => element.setAttribute("columns", '["Discount","Profit","Sales"]'), viewer); }, { diff --git a/packages/perspective-viewer/test/js/simple_tests.js b/packages/perspective-viewer/test/js/simple_tests.js index b60df1fdbb..5210c8530e 100644 --- a/packages/perspective-viewer/test/js/simple_tests.js +++ b/packages/perspective-viewer/test/js/simple_tests.js @@ -14,25 +14,25 @@ exports.default = function(method = "capture") { test[method]("pivots by a row.", async page => { const viewer = await page.$("perspective-viewer"); - await page.shadow_click("perspective-viewer", "#config_button"); + await page.evaluate(async () => await document.querySelector("perspective-viewer").toggleConfig()); await page.evaluate(element => element.setAttribute("row-pivots", '["State"]'), viewer); }); test[method]("pivots by two rows.", async page => { const viewer = await page.$("perspective-viewer"); - await page.shadow_click("perspective-viewer", "#config_button"); + await page.evaluate(async () => await document.querySelector("perspective-viewer").toggleConfig()); await page.evaluate(element => element.setAttribute("row-pivots", '["Category","Sub-Category"]'), viewer); }); test[method]("pivots by a column.", async page => { const viewer = await page.$("perspective-viewer"); - await page.shadow_click("perspective-viewer", "#config_button"); + await page.evaluate(async () => await document.querySelector("perspective-viewer").toggleConfig()); await page.evaluate(element => element.setAttribute("column-pivots", '["Category"]'), viewer); }); test[method]("pivots by a row and a column.", async page => { const viewer = await page.$("perspective-viewer"); - await page.shadow_click("perspective-viewer", "#config_button"); + await page.evaluate(async () => await document.querySelector("perspective-viewer").toggleConfig()); await page.evaluate(element => element.setAttribute("row-pivots", '["State"]'), viewer); await page.waitForSelector("perspective-viewer:not([updating])"); await page.evaluate(element => element.setAttribute("column-pivots", '["Category"]'), viewer); @@ -40,7 +40,7 @@ exports.default = function(method = "capture") { test[method]("pivots by two rows and two columns.", async page => { const viewer = await page.$("perspective-viewer"); - await page.shadow_click("perspective-viewer", "#config_button"); + await page.evaluate(async () => await document.querySelector("perspective-viewer").toggleConfig()); await page.evaluate(element => element.setAttribute("row-pivots", '["Region","State"]'), viewer); await page.waitForSelector("perspective-viewer:not([updating])"); await page.evaluate(element => element.setAttribute("column-pivots", '["Category","Sub-Category"]'), viewer); @@ -48,27 +48,27 @@ exports.default = function(method = "capture") { test[method]("sorts by a hidden column.", async page => { const viewer = await page.$("perspective-viewer"); - await page.shadow_click("perspective-viewer", "#config_button"); + await page.evaluate(async () => await document.querySelector("perspective-viewer").toggleConfig()); await page.evaluate(element => element.setAttribute("columns", '["Row ID","Quantity"]'), viewer); await page.evaluate(element => element.setAttribute("sort", '[["Sales", "asc"]]'), viewer); }); test[method]("sorts by a numeric column.", async page => { const viewer = await page.$("perspective-viewer"); - await page.shadow_click("perspective-viewer", "#config_button"); + await page.evaluate(async () => await document.querySelector("perspective-viewer").toggleConfig()); await page.evaluate(element => element.setAttribute("sort", '[["Sales", "asc"]]'), viewer); }); test[method]("filters by a numeric column.", async page => { const viewer = await page.$("perspective-viewer"); - await page.shadow_click("perspective-viewer", "#config_button"); + await page.evaluate(async () => await document.querySelector("perspective-viewer").toggleConfig()); await page.evaluate(element => element.setAttribute("filters", '[["Sales", ">", 500]]'), viewer); await page.evaluate(() => document.activeElement.blur()); }); test[method]("filters by a datetime column.", async page => { const viewer = await page.$("perspective-viewer"); - await page.shadow_click("perspective-viewer", "#config_button"); + await page.evaluate(async () => await document.querySelector("perspective-viewer").toggleConfig()); await page.evaluate(element => element.setAttribute("filters", '[["Order Date", ">", "01/01/2012"]]'), viewer); await page.waitForSelector("perspective-viewer:not([updating])"); await page.evaluate(() => document.activeElement.blur()); @@ -76,25 +76,25 @@ exports.default = function(method = "capture") { test[method]("highlights invalid filter.", async page => { const viewer = await page.$("perspective-viewer"); - await page.shadow_click("perspective-viewer", "#config_button"); + await page.evaluate(async () => await document.querySelector("perspective-viewer").toggleConfig()); await page.evaluate(element => element.setAttribute("filters", '[["Sales", "==", null]]'), viewer); await page.evaluate(() => document.activeElement.blur()); }); test[method]("sorts by an alpha column.", async page => { const viewer = await page.$("perspective-viewer"); - await page.shadow_click("perspective-viewer", "#config_button"); + await page.evaluate(async () => await document.querySelector("perspective-viewer").toggleConfig()); await page.evaluate(element => element.setAttribute("sort", '[["State", "asc"]]'), viewer); }); test[method]("displays visible columns.", async page => { const viewer = await page.$("perspective-viewer"); - await page.shadow_click("perspective-viewer", "#config_button"); + await page.evaluate(async () => await document.querySelector("perspective-viewer").toggleConfig()); await page.evaluate(element => element.setAttribute("columns", '["Discount","Profit","Sales","Quantity"]'), viewer); }); test.skip("pivots by row when drag-and-dropped.", async page => { - await page.shadow_click("perspective-viewer", "#config_button"); + await page.evaluate(async () => await document.querySelector("perspective-viewer").toggleConfig()); await drag_drop(page, "perspective-row[name=Category]", "#row_pivots"); }); }; diff --git a/packages/perspective-viewer/test/js/slow.spec.js b/packages/perspective-viewer/test/js/slow.spec.js index 534365731d..8ae223bd13 100644 --- a/packages/perspective-viewer/test/js/slow.spec.js +++ b/packages/perspective-viewer/test/js/slow.spec.js @@ -17,7 +17,7 @@ utils.with_server({}, () => { function() { test.capture("replaces all rows.", async page => { const viewer = await page.$("perspective-viewer"); - await page.shadow_click("perspective-viewer", "#config_button"); + await page.evaluate(async () => await document.querySelector("perspective-viewer").toggleConfig()); const json = await page.evaluate(async element => { let json = await element.view.to_json(); return json.slice(10, 20); diff --git a/packages/perspective/src/config/common.config.js b/packages/perspective/src/config/common.config.js index c448a391ff..6b9bd094b1 100644 --- a/packages/perspective/src/config/common.config.js +++ b/packages/perspective/src/config/common.config.js @@ -70,9 +70,11 @@ function common({no_minify, inline} = {}) { test: /perspective\.worker\.js$/, type: "javascript/auto", loader: "worker-loader", - options: { - inline: "no-fallback" - } + options: inline + ? { + inline: "no-fallback" + } + : undefined } ] }, diff --git a/rust/perspective-vieux/Cargo.lock b/rust/perspective-vieux/Cargo.lock index ada3c5f480..bf33b523a0 100644 --- a/rust/perspective-vieux/Cargo.lock +++ b/rust/perspective-vieux/Cargo.lock @@ -1,23 +1,5 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -[[package]] -name = "aho-corasick" -version = "0.7.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7404febffaa47dac81aa44dba71523c9d069b1bdc50a77db41195149e17f68e5" -dependencies = [ - "memchr", -] - -[[package]] -name = "ansi_term" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" -dependencies = [ - "winapi", -] - [[package]] name = "anyhow" version = "1.0.38" @@ -30,12 +12,6 @@ version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33954243bd79057c2de7338850b85983a44588021f8a5fee574a8888c6de4344" -[[package]] -name = "arrayref" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544" - [[package]] name = "arrayvec" version = "0.4.12" @@ -45,50 +21,12 @@ dependencies = [ "nodrop", ] -[[package]] -name = "arrayvec" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" - -[[package]] -name = "ascii-canvas" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff8eb72df928aafb99fe5d37b383f2fe25bd2a765e3e5f7c365916b6f2463a29" -dependencies = [ - "term", -] - -[[package]] -name = "atty" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" -dependencies = [ - "hermit-abi", - "libc", - "winapi", -] - -[[package]] -name = "autocfg" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d49d90015b3c36167a20fe2810c5cd875ad504b39cff3d4eae7977e6b7c1cb2" - [[package]] name = "autocfg" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" -[[package]] -name = "base64" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" - [[package]] name = "bincode" version = "1.3.1" @@ -99,59 +37,6 @@ dependencies = [ "serde", ] -[[package]] -name = "bit-set" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e11e16035ea35e4e5997b393eacbf6f63983188f7a2ad25bfb13465f5ad59de" -dependencies = [ - "bit-vec", -] - -[[package]] -name = "bit-vec" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" - -[[package]] -name = "bitflags" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" - -[[package]] -name = "blake2b_simd" -version = "0.5.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afa748e348ad3be8263be728124b24a24f268266f6f5d58af9d75f6a40b5c587" -dependencies = [ - "arrayref", - "arrayvec 0.5.2", - "constant_time_eq", -] - -[[package]] -name = "block-buffer" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b" -dependencies = [ - "block-padding", - "byte-tools", - "byteorder", - "generic-array", -] - -[[package]] -name = "block-padding" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa79dedbb091f449f1f39e53edf88d5dbe95f895dae6135a8d7b881fb5af73f5" -dependencies = [ - "byte-tools", -] - [[package]] name = "boolinator" version = "2.4.0" @@ -164,12 +49,6 @@ version = "3.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "099e596ef14349721d9016f6b80dd3419ea1bf289ab9b44df8e4dfd3a005d5d9" -[[package]] -name = "byte-tools" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" - [[package]] name = "byteorder" version = "1.4.2" @@ -194,21 +73,6 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" -[[package]] -name = "cfg-match" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8100e46ff92eb85bf6dc2930c73f2a4f7176393c84a9446b3d501e1b354e7b34" - -[[package]] -name = "cloudabi" -version = "0.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" -dependencies = [ - "bitflags", -] - [[package]] name = "console_error_panic_hook" version = "0.1.6" @@ -219,100 +83,12 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "constant_time_eq" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" - -[[package]] -name = "crossbeam-utils" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02d96d1e189ef58269ebe5b97953da3274d83a93af647c2ddd6f9dab28cedb8d" -dependencies = [ - "autocfg 1.0.1", - "cfg-if 1.0.0", - "lazy_static", -] - -[[package]] -name = "diff" -version = "0.1.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e25ea47919b1560c4e3b7fe0aaab9becf5b84a10325ddf7db0f0ba5e1026499" - -[[package]] -name = "digest" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5" -dependencies = [ - "generic-array", -] - -[[package]] -name = "dirs" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fd78930633bd1c6e35c4b42b1df7b0cbc6bc191146e512bb3bedf243fcc3901" -dependencies = [ - "libc", - "redox_users", - "winapi", -] - -[[package]] -name = "docopt" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f525a586d310c87df72ebcd98009e57f1cc030c8c268305287a476beb653969" -dependencies = [ - "lazy_static", - "regex", - "serde", - "strsim", -] - -[[package]] -name = "either" -version = "1.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" - -[[package]] -name = "ena" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8944dc8fa28ce4a38f778bd46bf7d923fe73eed5a439398507246c8e017e6f36" -dependencies = [ - "log", -] - -[[package]] -name = "fake-simd" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" - -[[package]] -name = "fixedbitset" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86d4de0081402f5e88cdac65c8dcdcc73118c1a7a465e2a05f0da05843a8ea33" - [[package]] name = "fnv" version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" -[[package]] -name = "fuchsia-cprng" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" - [[package]] name = "futures" version = "0.3.12" @@ -408,26 +184,6 @@ dependencies = [ "slab", ] -[[package]] -name = "generic-array" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c68f0274ae0e023facc3c97b2e00f076be70e254bc851d972503b328db79b2ec" -dependencies = [ - "typenum", -] - -[[package]] -name = "getrandom" -version = "0.1.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" -dependencies = [ - "cfg-if 1.0.0", - "libc", - "wasi", -] - [[package]] name = "gloo" version = "0.2.1" @@ -488,30 +244,6 @@ version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04" -[[package]] -name = "heck" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87cbf45460356b7deeb5e3415b5563308c0a9b057c85e12b06ad551f98d0a6ac" -dependencies = [ - "unicode-segmentation", -] - -[[package]] -name = "hermit-abi" -version = "0.1.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "322f4de77956e22ed0e5032c359a0f1273f1f7f0d79bfa3b8ffbc730d7fbcc5c" -dependencies = [ - "libc", -] - -[[package]] -name = "htmlescape" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9025058dae765dee5070ec375f591e2ba14638c63feff74f13805a72e523163" - [[package]] name = "http" version = "0.2.3" @@ -529,19 +261,10 @@ version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4fb1fa934250de4de8aef298d81c729a7d33d8c239daa3a7575e6b92bfc7313b" dependencies = [ - "autocfg 1.0.1", + "autocfg", "hashbrown", ] -[[package]] -name = "itertools" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f56a2d0bc861f9165be4eb3442afd3c236d8a98afd426f65d92324ae1091a484" -dependencies = [ - "either", -] - [[package]] name = "itoa" version = "0.4.7" @@ -589,43 +312,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "lalrpop" -version = "0.17.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64dc3698e75d452867d9bd86f4a723f452ce9d01fe1d55990b79f0c790aa67db" -dependencies = [ - "ascii-canvas", - "atty", - "bit-set", - "diff", - "docopt", - "ena", - "itertools", - "lalrpop-util", - "petgraph", - "regex", - "regex-syntax", - "serde", - "serde_derive", - "sha2", - "string_cache", - "term", - "unicode-xid 0.1.0", -] - -[[package]] -name = "lalrpop-util" -version = "0.17.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c277d18683b36349ab5cd030158b54856fca6bb2d5dc5263b06288f486958b7c" - -[[package]] -name = "language-tags" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a91d884b6667cd606bb5a69aa0c99ba811a115fc68915e7056ec08a46e93199a" - [[package]] name = "lazy_static" version = "1.4.0" @@ -659,18 +345,6 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8452105ba047068f40ff7093dd1d9da90898e63dd61736462e9cdda6a90ad3c3" -[[package]] -name = "mime" -version = "0.3.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" - -[[package]] -name = "new_debug_unreachable" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54" - [[package]] name = "nodrop" version = "0.1.14" @@ -683,7 +357,7 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bafe4179722c2894288ee77a9f044f02811c86af699344c498b0840c698a2465" dependencies = [ - "arrayvec 0.4.12", + "arrayvec", "itoa", ] @@ -693,18 +367,6 @@ version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13bd41f508810a131401606d54ac32a467c97172d74ba7662562ebba5ad07fa0" -[[package]] -name = "opaque-debug" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" - -[[package]] -name = "ordermap" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a86ed3f5f244b372d6b1a00b72ef7f8876d0bc6a78a4c9985c53614041512063" - [[package]] name = "perspective_vieux" version = "0.6.2" @@ -714,7 +376,6 @@ dependencies = [ "js-intern", "js-sys", "num-format", - "typed-html", "wasm-bindgen", "wasm-bindgen-futures", "wasm-bindgen-test", @@ -723,35 +384,6 @@ dependencies = [ "yew", ] -[[package]] -name = "petgraph" -version = "0.4.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c3659d1ee90221741f65dd128d9998311b0e40c5d3c23a62445938214abce4f" -dependencies = [ - "fixedbitset", - "ordermap", -] - -[[package]] -name = "phf_generator" -version = "0.7.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09364cc93c159b8b06b1f4dd8a4398984503483891b0c26b867cf431fb132662" -dependencies = [ - "phf_shared", - "rand", -] - -[[package]] -name = "phf_shared" -version = "0.7.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "234f71a15de2288bcb7e3b6515828d22af7ec8598ee6d24c3b526fa0a80b67a0" -dependencies = [ - "siphasher", -] - [[package]] name = "pin-project-lite" version = "0.2.4" @@ -764,12 +396,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" -[[package]] -name = "precomputed-hash" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" - [[package]] name = "proc-macro-hack" version = "0.5.19" @@ -818,168 +444,6 @@ dependencies = [ "proc-macro2 1.0.24", ] -[[package]] -name = "rand" -version = "0.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d71dacdc3c88c1fde3885a3be3fbab9f35724e6ce99467f7d9c5026132184ca" -dependencies = [ - "autocfg 0.1.7", - "libc", - "rand_chacha", - "rand_core 0.4.2", - "rand_hc", - "rand_isaac", - "rand_jitter", - "rand_os", - "rand_pcg", - "rand_xorshift", - "winapi", -] - -[[package]] -name = "rand_chacha" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "556d3a1ca6600bfcbab7c7c91ccb085ac7fbbcd70e008a98742e7847f4f7bcef" -dependencies = [ - "autocfg 0.1.7", - "rand_core 0.3.1", -] - -[[package]] -name = "rand_core" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" -dependencies = [ - "rand_core 0.4.2", -] - -[[package]] -name = "rand_core" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" - -[[package]] -name = "rand_hc" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b40677c7be09ae76218dc623efbf7b18e34bced3f38883af07bb75630a21bc4" -dependencies = [ - "rand_core 0.3.1", -] - -[[package]] -name = "rand_isaac" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ded997c9d5f13925be2a6fd7e66bf1872597f759fd9dd93513dd7e92e5a5ee08" -dependencies = [ - "rand_core 0.3.1", -] - -[[package]] -name = "rand_jitter" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1166d5c91dc97b88d1decc3285bb0a99ed84b05cfd0bc2341bdf2d43fc41e39b" -dependencies = [ - "libc", - "rand_core 0.4.2", - "winapi", -] - -[[package]] -name = "rand_os" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b75f676a1e053fc562eafbb47838d67c84801e38fc1ba459e8f180deabd5071" -dependencies = [ - "cloudabi", - "fuchsia-cprng", - "libc", - "rand_core 0.4.2", - "rdrand", - "winapi", -] - -[[package]] -name = "rand_pcg" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abf9b09b01790cfe0364f52bf32995ea3c39f4d2dd011eac241d2914146d0b44" -dependencies = [ - "autocfg 0.1.7", - "rand_core 0.4.2", -] - -[[package]] -name = "rand_xorshift" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbf7e9e623549b0e21f6e97cf8ecf247c1a8fd2e8a992ae265314300b2455d5c" -dependencies = [ - "rand_core 0.3.1", -] - -[[package]] -name = "rdrand" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" -dependencies = [ - "rand_core 0.3.1", -] - -[[package]] -name = "redox_syscall" -version = "0.1.57" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" - -[[package]] -name = "redox_users" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de0737333e7a9502c789a36d7c7fa6092a49895d4faa31ca5df163857ded2e9d" -dependencies = [ - "getrandom", - "redox_syscall", - "rust-argon2", -] - -[[package]] -name = "regex" -version = "1.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9251239e129e16308e70d853559389de218ac275b515068abc96829d05b948a" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", - "thread_local", -] - -[[package]] -name = "regex-syntax" -version = "0.6.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5eb417147ba9860a96cfe72a0b93bf88fee1744b5636ec99ab20c1aa9376581" - -[[package]] -name = "rust-argon2" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b18820d944b33caa75a71378964ac46f58517c92b6ae5f762636247c09e78fb" -dependencies = [ - "base64", - "blake2b_simd", - "constant_time_eq", - "crossbeam-utils", -] - [[package]] name = "ryu" version = "1.0.5" @@ -1023,88 +487,12 @@ dependencies = [ "serde", ] -[[package]] -name = "sha2" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a256f46ea78a0c0d9ff00077504903ac881a1dafdc20da66545699e7776b3e69" -dependencies = [ - "block-buffer", - "digest", - "fake-simd", - "opaque-debug", -] - -[[package]] -name = "siphasher" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b8de496cf83d4ed58b6be86c3a275b8602f6ffe98d3024a869e124147a9a3ac" - [[package]] name = "slab" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" -[[package]] -name = "string_cache" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89c058a82f9fd69b1becf8c274f412281038877c553182f1d02eb027045a2d67" -dependencies = [ - "lazy_static", - "new_debug_unreachable", - "phf_shared", - "precomputed-hash", - "serde", - "string_cache_codegen", - "string_cache_shared", -] - -[[package]] -name = "string_cache_codegen" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0f45ed1b65bf9a4bf2f7b7dc59212d1926e9eaf00fa998988e420fd124467c6" -dependencies = [ - "phf_generator", - "phf_shared", - "proc-macro2 1.0.24", - "quote 1.0.8", - "string_cache_shared", -] - -[[package]] -name = "string_cache_shared" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1884d1bc09741d466d9b14e6d37ac89d6909cbcac41dd9ae982d4d063bbedfc" - -[[package]] -name = "strsim" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6446ced80d6c486436db5c078dde11a9f73d42b57fb273121e160b84f63d894c" - -[[package]] -name = "strum" -version = "0.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57bd81eb48f4c437cadc685403cad539345bf703d78e63707418431cecd4522b" - -[[package]] -name = "strum_macros" -version = "0.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87c85aa3f8ea653bfd3ddf25f7ee357ee4d204731f6aa9ad04002306f6e2774c" -dependencies = [ - "heck", - "proc-macro2 1.0.24", - "quote 1.0.8", - "syn 1.0.60", -] - [[package]] name = "syn" version = "0.15.44" @@ -1127,17 +515,6 @@ dependencies = [ "unicode-xid 0.2.1", ] -[[package]] -name = "term" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edd106a334b7657c10b7c540a0106114feadeb4dc314513e97df481d5d966f42" -dependencies = [ - "byteorder", - "dirs", - "winapi", -] - [[package]] name = "thiserror" version = "1.0.23" @@ -1158,57 +535,6 @@ dependencies = [ "syn 1.0.60", ] -[[package]] -name = "thread_local" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8208a331e1cb318dd5bd76951d2b8fc48ca38a69f5f4e4af1b6a9f8c6236915" -dependencies = [ - "once_cell", -] - -[[package]] -name = "typed-html" -version = "0.2.2" -source = "git+https://github.com/bodil/typed-html?rev=4c13ecca#4c13ecca506887d07638cdf12d6ea6d51cd3b29a" -dependencies = [ - "htmlescape", - "language-tags", - "mime", - "proc-macro-hack", - "proc-macro-nested", - "strum", - "strum_macros", - "typed-html-macros", -] - -[[package]] -name = "typed-html-macros" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44a4dba17ed65147f4780560f1078de857f3f4d48f5aeb2197bace8e103a7356" -dependencies = [ - "ansi_term", - "lalrpop", - "lalrpop-util", - "proc-macro-hack", - "proc-macro2 1.0.24", - "quote 1.0.8", - "version_check", -] - -[[package]] -name = "typenum" -version = "1.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33" - -[[package]] -name = "unicode-segmentation" -version = "1.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb0d2e7be6ae3a5fa87eed5fb451aff96f2573d2694942e40543ae0bbe19c796" - [[package]] name = "unicode-xid" version = "0.1.0" @@ -1221,18 +547,6 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" -[[package]] -name = "version_check" -version = "0.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed" - -[[package]] -name = "wasi" -version = "0.9.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" - [[package]] name = "wasm-bindgen" version = "0.2.70" @@ -1370,24 +684,17 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "yew" version = "0.17.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d8703eb5b883e816cd74c65e2f6dd4144eeedb77c1b3e0284e8f3f593b80ab1" +source = "git+https://github.com/yewstack/yew?rev=b074b4b#b074b4b87d62b65147547bba8407f1d74df8de4c" dependencies = [ "anyhow", "anymap", "bincode", - "cfg-if 0.1.10", - "cfg-match", "console_error_panic_hook", - "futures", "gloo", "http", "indexmap", "js-sys", "log", - "proc-macro-hack", - "proc-macro-nested", - "ryu", "serde", "serde_json", "slab", @@ -1401,12 +708,10 @@ dependencies = [ [[package]] name = "yew-macro" version = "0.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61a9a452e63b6222b28b426dafbc6b207192e0127cdb93324cc7407b8c7e1768" +source = "git+https://github.com/yewstack/yew?rev=b074b4b#b074b4b87d62b65147547bba8407f1d74df8de4c" dependencies = [ "boolinator", "lazy_static", - "proc-macro-hack", "proc-macro2 1.0.24", "quote 1.0.8", "syn 1.0.60", diff --git a/rust/perspective-vieux/Cargo.toml b/rust/perspective-vieux/Cargo.toml index 2841caffc9..b6a63721de 100644 --- a/rust/perspective-vieux/Cargo.toml +++ b/rust/perspective-vieux/Cargo.toml @@ -15,14 +15,12 @@ path = "src/rust/lib.rs" default = ["wee_alloc", "console_error_panic_hook"] [dependencies] -# serde = { version = "1.0", features = ["derive"] } -wasm-bindgen = { version = "0.2.63" } #, features = ["serde-serialize"] } -wasm-bindgen-futures = "0.4.20" +futures = "0.3.12" js-intern = "0.3.1" -yew = "0.17.3" -typed-html = { git = "https://github.com/bodil/typed-html", rev = "4c13ecca" } num-format = "0.4.0" -futures = "0.3.12" +wasm-bindgen = { version = "0.2.63" } #, features = ["serde-serialize"] } +wasm-bindgen-futures = "0.4.20" +yew = { git = "https://github.com/yewstack/yew", rev = "b074b4b" } # "0.17.4" # The `console_error_panic_hook` crate provides better debugging of panics by # logging them with `console.error`. This is great for development, but requires diff --git a/rust/perspective-vieux/package.json b/rust/perspective-vieux/package.json index aca049904b..6bec0c9b30 100644 --- a/rust/perspective-vieux/package.json +++ b/rust/perspective-vieux/package.json @@ -17,7 +17,7 @@ "src/**/*" ], "scripts": { - "build:wasm": "wasm-pack build", + "build:wasm": "wasm-pack build --debug", "build:rollup": "rollup --config rollup.config.js", "build": "npm-run-all build:rollup build:wasm && cpx 'src/less/*' dist/less", "postbuild": "rimraf pkg/package.json pkg/.gitignore", @@ -27,7 +27,7 @@ "lint": "eslint src examples/*.md examples/*.html", "test:build": "cpx ../../packages/perspective/dist/umd/perspective.inline.js pkg/", "test:run": "echo 'No Jest tests'", - "test:clean": "rm perspective.csv", + "test:clean": "rm perspective.csv || true", "test": "yarn test:build && wasm-pack test --chrome --headless && yarn test:clean", "watch": "npm-run-all -p watch:*" }, diff --git a/rust/perspective-vieux/rollup.config.js b/rust/perspective-vieux/rollup.config.js index 4cb11abcc2..f820bd9b48 100644 --- a/rust/perspective-vieux/rollup.config.js +++ b/rust/perspective-vieux/rollup.config.js @@ -7,14 +7,14 @@ import path from "path"; export default () => { return [ { - input: `src/less/container.less`, + input: `src/less/perspective-vieux.less`, output: { dir: "dist/css" }, plugins: [ postcss({ inject: false, - extract: path.resolve(`dist/css/container.css`), + extract: path.resolve(`dist/css/perspective-vieux.css`), minimize: {preset: "lite"} }) ] diff --git a/rust/perspective-vieux/src/js/bootstrap.js b/rust/perspective-vieux/src/js/bootstrap.js index e810c6a696..0bce248b94 100644 --- a/rust/perspective-vieux/src/js/bootstrap.js +++ b/rust/perspective-vieux/src/js/bootstrap.js @@ -12,39 +12,50 @@ export const wasm = import("./index.js"); let _index = undefined; async function _await_index(f) { + await new Promise(setTimeout); if (!_index) { _index = await wasm; } return f(); } -function handle_view_errors(e) { - if (e.message !== "View is not initialized") { - throw e; - } -} +// function handle_view_errors(e) { +// if (e.message !== "View is not initialized") { +// throw e; +// } +// } -class PerspectiveStatusBarElement extends HTMLElement { +class PerspectiveVieuxElement extends HTMLElement { constructor() { super(); _await_index(() => { - this._instance = new _index.StatusBarElement(this); + this._instance = new _index.PerspectiveVieuxElement(this); + }); + } + + connectedCallback() { + _await_index(() => { + this._instance.connected_callback(); }); } - set_view(...args) { - return _await_index(() => this._instance.set_view(...args).catch(handle_view_errors)); + load(table) { + _await_index(() => this._instance.load(table)); + } + + set_view(view) { + _await_index(() => this._instance.set_view(view)); } - set_table(...args) { - return _await_index(() => this._instance.set_table(...args)); + delete_view() { + return _await_index(() => this._instance.delete_view()); } - remove_on_update_callback(...args) { - return _await_index(() => this._instance.remove_on_update_callback(...args)); + toggle_config(force) { + return _await_index(() => this._instance.toggle_config(force)); } } -if (document.createElement("perspective-statusbar").constructor === HTMLElement) { - window.customElements.define("perspective-statusbar", PerspectiveStatusBarElement); +if (document.createElement("perspective-vieux").constructor === HTMLElement) { + window.customElements.define("perspective-vieux", PerspectiveVieuxElement); } diff --git a/rust/perspective-vieux/src/js/index.js b/rust/perspective-vieux/src/js/index.js index 085baa11d1..51c7e43178 100644 --- a/rust/perspective-vieux/src/js/index.js +++ b/rust/perspective-vieux/src/js/index.js @@ -8,7 +8,7 @@ * */ -import {StatusBarElement, download, download_flat, copy, copy_flat, set_panic_hook} from "../../pkg/perspective_vieux_bg.js"; -export {StatusBarElement, download, download_flat, copy, copy_flat, set_panic_hook}; +import {PerspectiveVieuxElement, download, download_flat, copy, copy_flat, set_panic_hook} from "../../pkg/perspective_vieux_bg.js"; +export {PerspectiveVieuxElement, download, download_flat, copy, copy_flat, set_panic_hook}; set_panic_hook(); diff --git a/rust/perspective-vieux/src/less/container.less b/rust/perspective-vieux/src/less/container.less deleted file mode 100644 index 3e401b40ad..0000000000 --- a/rust/perspective-vieux/src/less/container.less +++ /dev/null @@ -1,138 +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. - * - */ - -:host { - overflow: hidden; - display: flex; - align-items: center; - - .section { - box-shadow: 8px 0px 0px -7px #ccc; - display: flex; - align-items: center; - - &:nth-child(3) { - flex: 1; - } - - &:nth-child(4) { - span:first-child:not(:last-child) { - margin-right: 5px; - } - - span:last-child:not(:first-child) { - margin-left: 5px; - } - } - } - - span { - font-family: "Open Sans"; - font-size: 12px; - margin: 0px 10px; - user-select: none; - height: 100%; - line-height: 36px; - - &:before { - text-transform: none; - margin-right: 4px; - font-family: "Material Icons"; - vertical-align: bottom; - } - - &:hover { - color: inherit; - } - } - - span.icon { - font-family: "Material Icons"; - height: 100%; - line-height: 36px; - margin: 0; - } - - span#status { - opacity: 0.5; - - &:before { - color: #ccc; - content: "circle"; - } - - &.connected, &.error, &.initializing { - opacity: 1; - } - - &.connected:before { - color: rgb(51, 159, 77); - } - - &.initializing:before { - color: rgb(223, 198, 57); - } - - &.error:before { - color: rgb(252, 64, 52); - } - } - - span#export { - &:before { - content: "download"; - } - } - - span#lock { - &:before { - content: "lock_open"; - } - } - - span#reset { - &:before { - content: "refresh"; - } - } - - span#copy { - &:before { - content: "content_copy"; - } - } - - span.button { - text-transform: uppercase; - margin: 0px; - padding: 0 5px 0px 10px; - transition: background-color 0.2s; - span { - pointer-events: none; - overflow: hidden; - display: none; - } - - &:before { - font-size: 14px; - } - - &:hover { - span { - display: contents; - } - - min-width: 75px; - cursor: pointer; - background-color: rgba(0,0,0,0.1); - transition: none; - } - } -} \ No newline at end of file diff --git a/rust/perspective-vieux/src/less/perspective-vieux.less b/rust/perspective-vieux/src/less/perspective-vieux.less new file mode 100644 index 0000000000..015aded1ee --- /dev/null +++ b/rust/perspective-vieux/src/less/perspective-vieux.less @@ -0,0 +1,73 @@ +/****************************************************************************** + * + * 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 "./split-panel.less"; + @import "./status-bar.less"; + +:host { + display: flex; + flex-direction: column; + align-items: stretch; + + #app_panel { + position: absolute; + bottom: 36px; + left: 0 ; + right: 0; + top: 0; + } + + #status_bar { + position: absolute; + bottom: 0; + left:0 ; + right: 0; + height: 36px; + } + + #main_column { + display: flex; + flex-direction: column; + + #main_panel_container { + flex: 1 1 auto; + position: relative; + overflow: hidden; + } + } + + #config_button { + position: absolute; + top: 0; + left: 0; + padding: var(--button--padding, 12px 14px 24px 8px); + overflow: hidden; + display: flex; + align-items: center; + justify-content: center; + z-index: 10; + + background: none; + color: var(--inactive--color, #999); + font-family: var(--button--font-family, Arial); + font-size: var(--button--font-size, 16px); + font-weight: normal; + transition: opacity 0.3s; + + &:hover { + color: var(--active--color, inherit); + cursor: pointer; + } + &:before { + font-feature-settings: "liga"; + content: var(--settings-button--content, "\1f527"); + } + } +} \ No newline at end of file diff --git a/rust/perspective-vieux/src/less/split-panel.less b/rust/perspective-vieux/src/less/split-panel.less new file mode 100644 index 0000000000..baf6db4053 --- /dev/null +++ b/rust/perspective-vieux/src/less/split-panel.less @@ -0,0 +1,45 @@ +/****************************************************************************** + * + * 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. + * + */ + +:host { + + .split-panel { + display: flex; + + & > * { + flex: 1 1 auto; + } + + & > *:first-child { + flex: 0 0 auto; + } + + // The thing you click to drag the panel size. + .split-panel-divider { + flex: 0 0 8px; + transition: background-color 0.2s ease-out; + + &:hover { + background-color: rgba(0,0,0,0.05); + cursor: col-resize; + } + } + + // Make the elements embedded in each child stretch to fill the + // container + .split-panel-child { + display: flex; + + & > * { + flex: 1 1 auto; + } + } + } +} \ No newline at end of file diff --git a/rust/perspective-vieux/src/less/status-bar.less b/rust/perspective-vieux/src/less/status-bar.less new file mode 100644 index 0000000000..175b9eda50 --- /dev/null +++ b/rust/perspective-vieux/src/less/status-bar.less @@ -0,0 +1,140 @@ +/****************************************************************************** + * + * 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. + * + */ + +:host { + #status_bar { + overflow: hidden; + display: flex; + align-items: center; + + .section { + box-shadow: 8px 0px 0px -7px #ccc; + display: flex; + align-items: center; + + &:nth-child(2) { + flex: 1; + } + + &:nth-child(3) { + span:first-child:not(:last-child) { + margin-right: 5px; + } + + span:last-child:not(:first-child) { + margin-left: 5px; + } + } + } + + span { + font-family: "Open Sans"; + font-size: 12px; + margin: 0px 10px; + user-select: none; + height: 100%; + line-height: 36px; + + &:before { + text-transform: none; + margin-right: 4px; + font-family: "Material Icons"; + vertical-align: bottom; + } + + &:hover { + color: inherit; + } + } + + span.icon { + font-family: "Material Icons"; + height: 100%; + line-height: 36px; + margin: 0; + } + + span#status { + opacity: 0.5; + + &:before { + color: #ccc; + content: "circle"; + } + + &.connected, &.error, &.initializing { + opacity: 1; + } + + &.connected:before { + color: rgb(51, 159, 77); + } + + &.initializing:before { + color: rgb(223, 198, 57); + } + + &.error:before { + color: rgb(252, 64, 52); + } + } + + span#export { + &:before { + content: "download"; + } + } + + span#lock { + &:before { + content: "lock_open"; + } + } + + span#reset { + &:before { + content: "refresh"; + } + } + + span#copy { + &:before { + content: "content_copy"; + } + } + + span.button { + text-transform: uppercase; + margin: 0px; + padding: 0 5px 0px 10px; + transition: background-color 0.2s; + span { + pointer-events: none; + overflow: hidden; + display: none; + } + + &:before { + font-size: 14px; + } + + &:hover { + span { + display: contents; + } + + min-width: 75px; + cursor: pointer; + background-color: rgba(0,0,0,0.1); + transition: none; + } + } + } +} \ No newline at end of file diff --git a/rust/perspective-vieux/src/rust/components/mod.rs b/rust/perspective-vieux/src/rust/components/mod.rs new file mode 100644 index 0000000000..81eb05ae04 --- /dev/null +++ b/rust/perspective-vieux/src/rust/components/mod.rs @@ -0,0 +1,16 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// 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. + +pub mod perspective_vieux; +pub mod split_panel; +pub mod status_bar; + +mod status_bar_counter; + +#[cfg(test)] +mod tests; diff --git a/rust/perspective-vieux/src/rust/components/perspective_vieux.rs b/rust/perspective-vieux/src/rust/components/perspective_vieux.rs new file mode 100644 index 0000000000..70194eb101 --- /dev/null +++ b/rust/perspective-vieux/src/rust/components/perspective_vieux.rs @@ -0,0 +1,295 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// 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. + +use crate::components::split_panel::SplitPanel; +use crate::components::status_bar::StatusBar; +use crate::session::{Session, TableStats}; +use crate::utils::perspective::PerspectiveJsView; +use crate::utils::WeakComponentLink; + +use futures::channel::oneshot::*; +use js_sys::*; +use wasm_bindgen::{prelude::*, JsCast}; +use wasm_bindgen_futures::{future_to_promise, JsFuture}; +use yew::prelude::*; + +pub static CSS: &str = include_str!("../../../dist/css/perspective-vieux.css"); + +#[derive(Properties, Clone)] +pub struct PerspectiveVieuxProps { + pub elem: web_sys::HtmlElement, + pub panels: (web_sys::HtmlElement, web_sys::HtmlElement), + + #[prop_or_default] + pub weak_link: WeakComponentLink, +} + +pub enum Msg { + LoadTable(Promise, Sender>), + Reset, + Export(bool), + Copy(bool), + ViewLoaded(PerspectiveJsView), + ViewDeleted, + TableStats(TableStats), + ToggleConfig(Option, Option>>), + ConfigToggled(bool, Option>>), +} + +pub struct PerspectiveVieux { + link: ComponentLink, + props: PerspectiveVieuxProps, + stats: Option, + session: Session, + config_open: bool, + on_rendered: Option>>, +} + +impl Component for PerspectiveVieux { + type Message = Msg; + type Properties = PerspectiveVieuxProps; + fn create(props: Self::Properties, link: ComponentLink) -> Self { + *props.weak_link.borrow_mut() = Some(link.clone()); + Self { + props, + link: link.clone(), + stats: None, + session: Session::new(link.callback(Msg::TableStats)), + config_open: false, + on_rendered: None, + } + } + + /// TODO would like a cleaner abstraction for `Msg` which contains a Promise + /// resolving `Sender`. Also, likewise for on_rendered, which will silently + /// drop any async-overlap bugs in this function. + fn update(&mut self, msg: Self::Message) -> ShouldRender { + match msg { + Msg::LoadTable(table, sender) => { + self.set_session_table(sender, table); + false + } + Msg::Reset => { + let event = web_sys::CustomEvent::new("perspective-vieux-reset"); + self.props.elem.dispatch_event(&event.unwrap()).unwrap(); + false + } + Msg::Export(flat) => { + self.session.download_as_csv(flat); + false + } + Msg::Copy(flat) => { + self.session.copy_to_clipboard(flat); + false + } + Msg::ViewDeleted => { + self.session.clear_view(); + false + } + Msg::ViewLoaded(view) => { + self.session.set_view(view); + false + } + Msg::ToggleConfig(force, resolve) => { + self.toggle_config(force, resolve); + force.unwrap_or(self.config_open) + } + Msg::ConfigToggled(is_open, resolve) => { + if !is_open { + self.on_rendered = resolve; + } else if let Some(resolve) = resolve { + resolve.send(Ok(JsValue::UNDEFINED)).unwrap(); + } + !is_open + } + Msg::TableStats(stats) => { + self.stats = Some(stats); + true + } + } + } + + /// This top-level component is mounted to the Custom Element, so it has no API + /// to provide props - but for sanity if needed, just return true on change. + fn change(&mut self, _props: Self::Properties) -> ShouldRender { + true + } + + /// On rendered call notify_resize(). This also triggers any registered async + /// callbacks to the Custom Element API. + fn rendered(&mut self, _first_render: bool) { + let mut resolve: Option>> = None; + std::mem::swap(&mut resolve, &mut self.on_rendered); + if let Some(resolve) = resolve { + resolve.send(Ok(JsValue::UNDEFINED)).unwrap(); + } + } + + /// `PerspectiveVieux` has two basic UI modes - "open" and "closed". + // TODO these may be expensive to buil dbecause they will generate recursively from + // `PerspectiveJsConfig` - they may need caching as in the JavaScript version. + fn view(&self) -> Html { + let config = self.link.callback(|_| Msg::ToggleConfig(None, None)); + if self.config_open { + html! { + <> + + + +
+ +
+ +
+
+
+ + +
+ + } + } else { + html! { + <> + + +
+ + } + } + } + + fn destroy(&mut self) {} +} + +impl PerspectiveVieux { + /// Toggle the config, or force the config panel either open (true) or closed + /// (false) explicitly. In order to reduce apparent screen-shear, `toggle_config()` + /// uses a somewhat complex render order: it first resize the plugin's `
` + /// without moving it, using `overflow: hidden` to hide the extra draw area; then, + /// after the _async_ drawing of the plugin is complete, it will send a message to + /// complete the toggle action and re-render the element with the config removed. + /// + /// # Arguments + /// * `force` - Whether to explicitly set the config panel state to Open/Close + /// (`Some(true)`/`Some(false)`), or to just toggle the current state (`None`). + fn toggle_config( + &mut self, + force: Option, + sender: Option>>, + ) { + match force { + Some(force) if self.config_open == force => { + if let Some(sender) = sender { + sender.send(Ok(JsValue::UNDEFINED)).unwrap(); + } + } + Some(_) | None => { + let force = !self.config_open; + self.config_open = force; + let callback = self + .link + .callback_once(move |_| Msg::ConfigToggled(force, sender)); + + let task = toggle_config_task(force, self.props.clone(), callback); + let _ = future_to_promise(task); + } + }; + } + + /// Helper to `await` the propvided `Table` and then trigger the resulting state + /// and UI changes. + fn set_session_table( + &mut self, + sender: Sender>, + table: Promise, + ) { + let msg = Msg::TableStats(TableStats::default()); + self.link.send_message(msg); + let session = self.session.clone(); + let _ = future_to_promise(async move { + sender.send(session.set_table(table).await).unwrap(); + Ok(JsValue::UNDEFINED) + }); + } +} + +/// An `async` task to pre-resize the `main_panel` to avoid screen shear. +async fn toggle_config_task( + open: bool, + props: PerspectiveVieuxProps, + callback: Callback<()>, +) -> Result { + let element = find_custom_element(&props, &callback).ok_or(JsValue::UNDEFINED)?; + if open { + callback.emit(()); + plugin_resize(element).await; + } else { + let main_panel: web_sys::HtmlElement = props + .elem + .query_selector("[slot=main_panel]") + .unwrap() + .unwrap() + .unchecked_into(); + + let new_width = format!("{}px", element.client_width()); + let new_height = format!("{}px", element.client_height()); + main_panel.style().set_property("width", &new_width)?; + main_panel.style().set_property("height", &new_height)?; + plugin_resize(element).await; + main_panel.style().set_property("width", "")?; + main_panel.style().set_property("height", "")?; + callback.emit(()); + } + + Ok(JsValue::UNDEFINED) +} + +/// Find the root `` Custom Element from the embedded +/// `` element reference by piercing the parent Shadow Dom. +fn find_custom_element( + props: &PerspectiveVieuxProps, + callback: &Callback<()>, +) -> Option { + let elem = props.elem.parent_node(); + if elem.is_none() { + callback.emit(()); + None + } else { + Some( + elem.unwrap() + .get_root_node() + .unchecked_into::() + .host(), + ) + } +} + +/// FFI to the wrapper Custom Element, find the `_plugin` and call `resize()`. +/// +/// # TODO +/// * Not all `resize` are `async`? +/// * Not all `_plugin` have `resize`? +async fn plugin_resize(custom_element: web_sys::Element) { + let plugin = Reflect::get(&custom_element, &JsValue::from("_plugin")).unwrap(); + let resize: Function = Reflect::get(&plugin, &JsValue::from("resize")) + .unwrap() + .unchecked_into(); + + if !resize.is_undefined() { + let fut = resize.call0(&custom_element).unwrap(); + if !fut.is_undefined() { + let _ = JsFuture::from(fut.unchecked_into::()).await; + } + } +} diff --git a/rust/perspective-vieux/src/rust/components/split_panel.rs b/rust/perspective-vieux/src/rust/components/split_panel.rs new file mode 100644 index 0000000000..373cc142ed --- /dev/null +++ b/rust/perspective-vieux/src/rust/components/split_panel.rs @@ -0,0 +1,206 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// 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. + +use crate::utils::*; +use crate::*; + +use std::cmp::max; +use wasm_bindgen::prelude::*; +use wasm_bindgen::JsCast; +use web_sys::HtmlElement; +use yew::prelude::*; + +/// The state for the `Resizing` action, including the `MouseEvent` callbacks and +/// panel starting dimensions. +struct ResizingState { + mousemove: Closure, + mouseup: Closure, + cursor: String, + start: i32, + width: i32, + body_style: web_sys::CssStyleDeclaration, +} + +impl Drop for ResizingState { + /// On `drop`, we must remove these event listeners from the document `body`. + /// Without this, the `Closure` objects would not leak, but the document will + /// continue to call them, causing runtime exceptions. + fn drop(&mut self) { + maybe! { + let document = web_sys::window().unwrap().document().unwrap(); + let body = document.body().unwrap(); + let mousemove = self.mousemove.as_ref().unchecked_ref(); + body.remove_event_listener_with_callback("mousemove", mousemove)?; + + let mouseup = self.mouseup.as_ref().unchecked_ref(); + body.remove_event_listener_with_callback("mouseup", mouseup)?; + + self.release_cursor()?; + Ok(()) + } + } +} + +/// When the instantiated, capture the initial dimensions and create the MouseEvent +/// callbacks. +impl ResizingState { + pub fn new( + client_x: i32, + split_panel: &ComponentLink, + first_elem: &HtmlElement, + ) -> Result { + let document = web_sys::window().unwrap().document().unwrap(); + let body = document.body().unwrap(); + let mut state = ResizingState { + cursor: "".to_owned(), + start: client_x, + width: first_elem.offset_width(), + body_style: body.style(), + mouseup: split_panel.to_closure(|_| SplitPanelMsg::StopResizing), + mousemove: split_panel.to_closure(|event| { + let client_x = event.client_x(); + SplitPanelMsg::MoveResizing(client_x) + }), + }; + + state.capture_cursor()?; + state.register_listeners()?; + + Ok(state) + } + + /// Adds the event listeners, the corollary of `Drop`. + fn register_listeners(&self) -> Result<(), JsValue> { + let document = web_sys::window().unwrap().document().unwrap(); + let body = document.body().unwrap(); + let mousemove = self.mousemove.as_ref().unchecked_ref(); + body.add_event_listener_with_callback("mousemove", mousemove)?; + + let mouseup = self.mouseup.as_ref().unchecked_ref(); + body.add_event_listener_with_callback("mouseup", mouseup) + } + + /// Helpr functions capture and release the global cursor while dragging is + /// occurring. + fn capture_cursor(&mut self) -> Result<(), JsValue> { + self.cursor = self.body_style.get_property_value("cursor")?; + self.body_style.set_property("cursor", "col-resize") + } + + /// " but for release + fn release_cursor(&self) -> Result<(), JsValue> { + self.body_style.set_property("cursor", &self.cursor) + } +} + +#[derive(Properties, Clone, Default)] +pub struct SplitPanelProps { + pub id: String, + pub children: Children, + + #[cfg(test)] + #[prop_or_default] + pub weak_link: WeakComponentLink, +} + +pub enum SplitPanelMsg { + StartResizing(i32), + MoveResizing(i32), + StopResizing, + Reset, +} + +fn validate(props: &SplitPanelProps) -> bool { + props.children.len() == 2 +} + +/// A panel with 2 sub panels and a mouse-draggable divider which allows apportioning +/// the panel's width. +pub struct SplitPanel { + link: ComponentLink, + props: SplitPanelProps, + resize_state: Option, + first_elem: NodeRef, + style: Option, +} + +impl Component for SplitPanel { + type Message = SplitPanelMsg; + type Properties = SplitPanelProps; + + fn create(props: Self::Properties, link: ComponentLink) -> Self { + assert!(validate(&props)); + enable_weak_link_test!(props, link); + Self { + props, + link, + resize_state: None, + first_elem: NodeRef::default(), + style: None, + } + } + + fn update(&mut self, msg: Self::Message) -> ShouldRender { + match msg { + SplitPanelMsg::Reset => self.style = None, + SplitPanelMsg::StartResizing(client_x) => { + let first = self.first_elem.cast::().unwrap(); + let state = ResizingState::new(client_x, &self.link, &first).ok(); + self.resize_state = state; + } + SplitPanelMsg::StopResizing => { + self.resize_state = None; + } + SplitPanelMsg::MoveResizing(client_x) => { + self.style = self.resize_state.as_ref().map(|state| { + let width = max(0, state.width + (client_x - state.start)); + format!("width: {}px", width) + }) + } + }; + true + } + + fn change(&mut self, props: Self::Properties) -> ShouldRender { + assert!(validate(&props)); + false + } + + fn view(&self) -> Html { + let mut iter = self.props.children.iter().take(2); + let _ref = self.first_elem.clone(); + let style = self.style.clone(); + let onmousedown = self.link.callback(|event: MouseEvent| { + event.prevent_default(); + event.stop_propagation(); + SplitPanelMsg::StartResizing(event.client_x()) + }); + + let ondblclick = self.link.callback(|event: MouseEvent| { + event.prevent_default(); + event.stop_propagation(); + SplitPanelMsg::Reset + }); + + html! { +
+
+ { iter.next().unwrap() } +
+
+
+
+ { iter.next().unwrap() } +
+
+ } + } +} diff --git a/rust/perspective-vieux/src/rust/components/status_bar.rs b/rust/perspective-vieux/src/rust/components/status_bar.rs new file mode 100644 index 0000000000..19da5e48ce --- /dev/null +++ b/rust/perspective-vieux/src/rust/components/status_bar.rs @@ -0,0 +1,118 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// 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. + +use crate::components::status_bar_counter::StatusBarRowsCounter; +use crate::session::TableStats; +use crate::*; + +#[cfg(test)] +use crate::utils::WeakComponentLink; + +use yew::prelude::*; + +#[derive(Properties, Clone)] +pub struct StatusBarProps { + pub id: String, + pub on_reset: Callback<()>, + pub on_download: Callback, + pub on_copy: Callback, + + #[prop_or(None)] + pub stats: Option, + + #[cfg(test)] + #[prop_or_default] + pub weak_link: WeakComponentLink, +} + +pub enum StatusBarMsg { + Reset, + Export(bool), + Copy(bool), +} + +/// A toolbar with buttons, and `Table` & `View` status information. +pub struct StatusBar { + link: ComponentLink, + pub props: StatusBarProps, +} + +impl Component for StatusBar { + type Message = StatusBarMsg; + type Properties = StatusBarProps; + + fn create(props: Self::Properties, link: ComponentLink) -> Self { + enable_weak_link_test!(props, link); + Self { props, link } + } + + fn update(&mut self, msg: Self::Message) -> ShouldRender { + match msg { + StatusBarMsg::Reset => self.props.on_reset.emit(()), + StatusBarMsg::Export(flat) => self.props.on_download.emit(flat), + StatusBarMsg::Copy(flat) => self.props.on_copy.emit(flat), + } + false + } + + fn change(&mut self, props: Self::Properties) -> ShouldRender { + let should_render = props.stats != self.props.stats; + self.props = props; + should_render + } + + fn view(&self) -> Html { + let class_name = self.status_class_name(); + let reset = self.link.callback(|_| StatusBarMsg::Reset); + let export = self + .link + .callback(|event: MouseEvent| StatusBarMsg::Export(event.shift_key())); + let copy = self + .link + .callback(|event: MouseEvent| StatusBarMsg::Copy(event.shift_key())); + + html! { +
+
+ +
+
+ + { "Reset" } + + + { "Export" } + + + { "Copy" } + +
+
+ +
+
+ } + } +} + +impl StatusBar { + fn status_class_name(&self) -> &'static str { + match &self.props.stats { + Some(TableStats { + num_rows: Some(_), + virtual_rows: Some(_), + is_pivot: true, + }) + | Some(TableStats { + num_rows: Some(_), .. + }) => "connected", + Some(TableStats { num_rows: None, .. }) => "initializing", + None => "uninitialized", + } + } +} diff --git a/rust/perspective-vieux/src/rust/components/status_bar_counter.rs b/rust/perspective-vieux/src/rust/components/status_bar_counter.rs new file mode 100644 index 0000000000..77d951485f --- /dev/null +++ b/rust/perspective-vieux/src/rust/components/status_bar_counter.rs @@ -0,0 +1,87 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// 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. + +use crate::session::TableStats; +use crate::*; + +#[cfg(test)] +use crate::utils::*; + +use num_format::{Locale, ToFormattedString}; +use yew::prelude::*; + +#[derive(Properties, Clone)] +pub struct StatusBarRowsCounterProps { + pub stats: Option, + + #[cfg(test)] + #[prop_or_default] + pub weak_link: WeakComponentLink, +} + +/// A label widget which displays a row count and a "projection" count, the number of +/// rows in the `View` which includes aggregate rows. +pub struct StatusBarRowsCounter { + stats: Option, +} + +impl Component for StatusBarRowsCounter { + type Message = (); + type Properties = StatusBarRowsCounterProps; + + fn create(props: Self::Properties, _link: ComponentLink) -> Self { + enable_weak_link_test!(props, _link); + StatusBarRowsCounter { stats: props.stats } + } + + fn update(&mut self, _msg: Self::Message) -> ShouldRender { + false + } + + fn change(&mut self, _props: Self::Properties) -> ShouldRender { + self.stats = _props.stats; + true + } + + fn view(&self) -> Html { + match &self.stats { + Some(TableStats { + num_rows: Some(num_rows), + virtual_rows: Some(virtual_rows), + is_pivot: true, + }) => { + let vrows = virtual_rows.to_formatted_string(&Locale::en); + let nrows = num_rows.to_formatted_string(&Locale::en); + html! { + <> + { format!("{} ", vrows) } + { "arrow_back" } + { format!(" {} rows", nrows) } + + } + } + + Some(TableStats { + num_rows: Some(num_rows), + .. + }) => html! { + + { format!("{} rows", &num_rows.to_formatted_string(&Locale::en)) } + + }, + + Some(TableStats { num_rows: None, .. }) => html! { + { "- rows" } + }, + + None => html! { + { "- rows" } + }, + } + } +} diff --git a/rust/perspective-vieux/src/rust/components/tests/mod.rs b/rust/perspective-vieux/src/rust/components/tests/mod.rs new file mode 100644 index 0000000000..27b83da2c5 --- /dev/null +++ b/rust/perspective-vieux/src/rust/components/tests/mod.rs @@ -0,0 +1,11 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// 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. + +mod split_panel; +mod status_bar; +mod status_bar_counter; diff --git a/rust/perspective-vieux/src/rust/components/tests/split_panel.rs b/rust/perspective-vieux/src/rust/components/tests/split_panel.rs new file mode 100644 index 0000000000..ffcd4ac511 --- /dev/null +++ b/rust/perspective-vieux/src/rust/components/tests/split_panel.rs @@ -0,0 +1,81 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// 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. + +use wasm_bindgen_test::*; +use web_sys::HtmlElement; +use yew::prelude::*; + +use crate::components::split_panel::{SplitPanel, SplitPanelMsg}; +use crate::utils::WeakComponentLink; +use crate::*; + +wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser); + +#[wasm_bindgen_test] +pub fn test_resizes_larger() { + let link: WeakComponentLink = WeakComponentLink::default(); + let panel_div = NodeRef::default(); + test_html! { + +
+
+
+ }; + + let split_panel = link.borrow().clone().unwrap(); + split_panel.send_message(SplitPanelMsg::StartResizing(10)); + split_panel.send_message(SplitPanelMsg::MoveResizing(100)); + split_panel.send_message(SplitPanelMsg::StopResizing); + + let width = panel_div.cast::().unwrap().offset_width(); + assert_eq!(width, 90); +} + +#[wasm_bindgen_test] +pub async fn test_resizes_narrower() { + let link: WeakComponentLink = WeakComponentLink::default(); + let panel_div = NodeRef::default(); + test_html! { + +
+
+
+ }; + + let split_panel = link.borrow().clone().unwrap(); + split_panel.send_message(SplitPanelMsg::StartResizing(10)); + split_panel.send_message(SplitPanelMsg::MoveResizing(100)); + split_panel.send_message(SplitPanelMsg::StopResizing); + split_panel.send_message(SplitPanelMsg::StartResizing(100)); + split_panel.send_message(SplitPanelMsg::MoveResizing(50)); + split_panel.send_message(SplitPanelMsg::StopResizing); + + let width = panel_div.cast::().unwrap().offset_width(); + assert_eq!(width, 40); +} + +#[wasm_bindgen_test] +pub async fn test_double_click_reset() { + let link: WeakComponentLink = WeakComponentLink::default(); + let panel_div = NodeRef::default(); + test_html! { + +
+
+
+ }; + + let split_panel = link.borrow().clone().unwrap(); + split_panel.send_message(SplitPanelMsg::StartResizing(10)); + split_panel.send_message(SplitPanelMsg::MoveResizing(100)); + split_panel.send_message(SplitPanelMsg::StopResizing); + split_panel.send_message(SplitPanelMsg::Reset); + + let width = panel_div.cast::().unwrap().offset_width(); + assert_eq!(width, 0); +} diff --git a/rust/perspective-vieux/src/rust/components/tests/status_bar.rs b/rust/perspective-vieux/src/rust/components/tests/status_bar.rs new file mode 100644 index 0000000000..8191028e95 --- /dev/null +++ b/rust/perspective-vieux/src/rust/components/tests/status_bar.rs @@ -0,0 +1,156 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// 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. + +use crate::components::status_bar::*; +use crate::session::TableStats; +use crate::utils::*; +use crate::*; + +use std::cell::Cell; +use std::rc::Rc; +use wasm_bindgen_test::*; +use web_sys::*; +use yew::prelude::*; + +wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser); + +#[wasm_bindgen_test] +pub fn test_callbacks_invoked() { + let link: WeakComponentLink = WeakComponentLink::default(); + let token = Rc::new(Cell::new(0)); + let on_reset = Callback::from({ + let _token = token.clone(); + move |()| _token.set(1) + }); + + let on_download = Callback::from({ + let _token = token.clone(); + move |_: bool| _token.set(2) + }); + + let on_copy = Callback::from({ + let _token = token.clone(); + move |_: bool| _token.set(3) + }); + + test_html! { + + + }; + + assert_eq!(token.get(), 0); + let status_bar = link.borrow().clone().unwrap(); + status_bar.send_message(StatusBarMsg::Reset); + assert_eq!(token.get(), 1); + let status_bar = link.borrow().clone().unwrap(); + status_bar.send_message(StatusBarMsg::Export(false)); + assert_eq!(token.get(), 2); + let status_bar = link.borrow().clone().unwrap(); + status_bar.send_message(StatusBarMsg::Copy(false)); + assert_eq!(token.get(), 3); +} + +fn gen(stats: &Option) -> (WeakComponentLink, HtmlElement) { + let link: WeakComponentLink = WeakComponentLink::default(); + let div = NodeRef::default(); + let on_reset = Callback::from(|()| ()); + let on_download = Callback::from(|_: bool| ()); + let on_copy = Callback::from(|_: bool| ()); + test_html! { + + + }; + + (link, div.cast::().unwrap()) +} + +#[wasm_bindgen_test] +pub fn test_status_uninitialized() { + let stats = None; + let (link, div) = gen(&stats); + let status_bar = link.borrow().clone().unwrap(); + let component = &status_bar.get_component(); + let status_bar_props = &component.as_ref().unwrap().props; + assert_eq!(status_bar_props.stats, stats); + let status_class = div.query_selector("#status").unwrap().unwrap().class_name(); + assert_eq!(status_class, "uninitialized"); +} + +#[wasm_bindgen_test] +pub fn test_status_initializing() { + let stats = Some(TableStats { + is_pivot: false, + num_rows: None, + virtual_rows: None, + }); + + let (link, div) = gen(&stats); + let status_bar = link.borrow().clone().unwrap(); + let component = &status_bar.get_component(); + let status_bar_props = &component.as_ref().unwrap().props; + assert_eq!(status_bar_props.stats, stats); + let status_class = div.query_selector("#status").unwrap().unwrap().class_name(); + assert_eq!(status_class, "initializing"); +} + +#[wasm_bindgen_test] +pub fn test_status_table_loaded() { + let stats = Some(TableStats { + is_pivot: false, + num_rows: Some(12345678), + virtual_rows: None, + }); + + let (link, div) = gen(&stats); + let status_bar = link.borrow().clone().unwrap(); + let component = &status_bar.get_component(); + let status_bar_props = &component.as_ref().unwrap().props; + assert_eq!(status_bar_props.stats, stats); + let status_class = div.query_selector("#status").unwrap().unwrap().class_name(); + assert_eq!(status_class, "connected"); + let rows = div.query_selector("#rows").unwrap().unwrap().inner_html(); + assert_eq!(rows, "12,345,678 rows"); +} + +#[wasm_bindgen_test] +pub fn test_status_table_and_view_loaded() { + let stats = Some(TableStats { + is_pivot: true, + num_rows: Some(12345678), + virtual_rows: Some(54321), + }); + + let (link, div) = gen(&stats); + let status_bar = link.borrow().clone().unwrap(); + let component = &status_bar.get_component(); + let status_bar_props = &component.as_ref().unwrap().props; + assert_eq!(status_bar_props.stats, stats); + let status_class = div.query_selector("#status").unwrap().unwrap().class_name(); + assert_eq!(status_class, "connected"); + let rows = div.query_selector("#rows").unwrap().unwrap().inner_html(); + assert_eq!( + rows, + "\ +54,321 \ +arrow_back\ + 12,345,678 rows" + ); +} diff --git a/rust/perspective-vieux/src/rust/components/tests/status_bar_counter.rs b/rust/perspective-vieux/src/rust/components/tests/status_bar_counter.rs new file mode 100644 index 0000000000..7137219e27 --- /dev/null +++ b/rust/perspective-vieux/src/rust/components/tests/status_bar_counter.rs @@ -0,0 +1,101 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// 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. + +use wasm_bindgen_test::*; +use web_sys::HtmlElement; +use yew::prelude::*; + +use crate::components::status_bar_counter::*; +use crate::session::TableStats; +use crate::*; + +wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser); + +#[wasm_bindgen_test] +pub fn test_counter_none() { + let div = NodeRef::default(); + test_html! { + + + }; + + let div = div.cast::().unwrap(); + assert_eq!(div.inner_html(), "- rows"); +} + +#[wasm_bindgen_test] +pub fn test_counter_initializing() { + let div = NodeRef::default(); + let stats = Some(TableStats { + is_pivot: false, + num_rows: None, + virtual_rows: None, + }); + + test_html! { + + + }; + + let div = div.cast::().unwrap(); + assert_eq!(div.inner_html(), "- rows"); +} + +#[wasm_bindgen_test] +pub fn test_counter_some_connected_no_view() { + let div = NodeRef::default(); + let stats = Some(TableStats { + is_pivot: false, + num_rows: Some(123456789), + virtual_rows: None, + }); + + test_html! { + + + }; + + let div = div.cast::().unwrap(); + assert_eq!(div.inner_html(), "123,456,789 rows"); +} + +#[wasm_bindgen_test] +pub fn test_counter_some_connected_no_pivot() { + let div = NodeRef::default(); + let stats = Some(TableStats { + is_pivot: false, + num_rows: Some(123456789), + virtual_rows: Some(54321), + }); + + test_html! { + + + }; + + let div = div.cast::().unwrap(); + assert_eq!(div.inner_html(), "123,456,789 rows"); +} + +#[wasm_bindgen_test] +pub fn test_counter_some_connected_pivot() { + let div = NodeRef::default(); + let stats = Some(TableStats { + is_pivot: true, + num_rows: Some(123456789), + virtual_rows: Some(54321), + }); + + test_html! { + + + }; + + let div = div.cast::().unwrap(); + assert_eq!(div.inner_html(), "54,321 "); +} diff --git a/rust/perspective-vieux/src/rust/lib.rs b/rust/perspective-vieux/src/rust/lib.rs index 271e4964cb..79fa1f6f4e 100644 --- a/rust/perspective-vieux/src/rust/lib.rs +++ b/rust/perspective-vieux/src/rust/lib.rs @@ -8,12 +8,19 @@ #![recursion_limit = "256"] -pub mod copy; -pub mod download; -pub mod status_bar; +pub mod components; +pub mod session; pub mod utils; +use crate::components::perspective_vieux::*; +use crate::utils::perspective::*; + +use futures::channel::oneshot::*; +use utils::WeakComponentLink; use wasm_bindgen::prelude::*; +use wasm_bindgen::JsCast; +use wasm_bindgen_futures::future_to_promise; +use yew::prelude::*; #[cfg(feature = "wee_alloc")] #[global_allocator] @@ -24,3 +31,60 @@ pub fn set_panic_hook() { #[cfg(feature = "console_error_panic_hook")] console_error_panic_hook::set_once(); } + +/// A `customElements` external API. +#[wasm_bindgen] +pub struct PerspectiveVieuxElement { + root: ComponentLink, +} + +#[wasm_bindgen] +impl PerspectiveVieuxElement { + #[wasm_bindgen(constructor)] + pub fn new(elem: web_sys::HtmlElement) -> PerspectiveVieuxElement { + let children = elem.children(); + let init = web_sys::ShadowRootInit::new(web_sys::ShadowRootMode::Open); + let shadow_root = elem + .attach_shadow(&init) + .unwrap() + .unchecked_into::(); + + let props = PerspectiveVieuxProps { + elem, + panels: ( + children.item(0).unwrap().unchecked_into(), + children.item(1).unwrap().unchecked_into(), + ), + weak_link: WeakComponentLink::default(), + }; + + let app = App::::new(); + let root = app.mount_with_props(shadow_root, props); + PerspectiveVieuxElement { root } + } + + pub fn connected_callback(&self) {} + + pub fn load(&self, table: js_sys::Promise) -> js_sys::Promise { + assert!(!table.is_undefined()); + + let (sender, receiver) = channel::>(); + self.root.send_message(Msg::LoadTable(table, sender)); + future_to_promise(async move { receiver.await.unwrap() }) + } + + pub fn set_view(&self, view: PerspectiveJsView) { + self.root.send_message(Msg::ViewLoaded(view)); + } + + pub fn delete_view(&self) { + self.root.send_message(Msg::ViewDeleted); + } + + pub fn toggle_config(&self, force: Option) -> js_sys::Promise { + let (sender, receiver) = channel::>(); + let msg = Msg::ToggleConfig(force, Some(sender)); + self.root.send_message(msg); + future_to_promise(async move { receiver.await.unwrap() }) + } +} diff --git a/rust/perspective-vieux/src/rust/session.rs b/rust/perspective-vieux/src/rust/session.rs new file mode 100644 index 0000000000..5e7b4a0a9f --- /dev/null +++ b/rust/perspective-vieux/src/rust/session.rs @@ -0,0 +1,96 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// 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. + +mod copy; +mod download; +mod view_subscription; + +use crate::session::view_subscription::*; +use crate::utils::perspective::*; +pub use view_subscription::TableStats; + +use copy::*; +use download::*; +use std::cell::RefCell; +use std::ops::Deref; +use std::rc::Rc; +use wasm_bindgen::{prelude::*, JsCast}; +use wasm_bindgen_futures::JsFuture; +use yew::prelude::*; + +pub struct SessionData { + table: Option, + view_sub: Option, + callback: Callback, +} + +#[derive(Clone)] +pub struct Session(Rc>); + +impl Deref for Session { + type Target = Rc>; + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl Session { + pub fn new(callback: Callback) -> Session { + Session(Rc::new(RefCell::new(SessionData { + table: None, + view_sub: None, + callback, + }))) + } + + pub async fn set_table(self, table: js_sys::Promise) -> Result { + let promise = JsFuture::from(table).await?; + let table: PerspectiveJsTable = promise.unchecked_into(); + self.borrow_mut().table = Some(table); + self.update_table_stats().await?; + Ok(JsValue::UNDEFINED) + } + + pub fn copy_to_clipboard(&self, flat: bool) { + let _ = if flat { + copy_flat(&self.borrow().table.clone().unwrap()) + } else { + copy(self.borrow().view_sub.as_ref().unwrap().view()) + }; + } + + pub fn download_as_csv(&self, flat: bool) { + let _ = if flat { + download_flat(&self.borrow().table.clone().unwrap()) + } else { + download(self.borrow().view_sub.as_ref().unwrap().view()) + }; + } + + pub fn clear_view(&mut self) { + self.borrow_mut().view_sub = None; + } + + pub fn set_view(&mut self, view: PerspectiveJsView) { + let table = self.borrow().table.clone().unwrap(); + let callback = self.borrow().callback.clone(); + let sub = ViewSubscription::new(table, view, callback); + self.borrow_mut().view_sub = Some(sub); + } + + async fn update_table_stats(self) -> Result { + let table = self.borrow().table.clone(); + let num_rows = table.unwrap().size().await? as u32; + self.borrow().callback.emit(TableStats { + is_pivot: false, + num_rows: Some(num_rows), + virtual_rows: None, + }); + Ok(JsValue::UNDEFINED) + } +} diff --git a/rust/perspective-vieux/src/rust/copy.rs b/rust/perspective-vieux/src/rust/session/copy.rs similarity index 100% rename from rust/perspective-vieux/src/rust/copy.rs rename to rust/perspective-vieux/src/rust/session/copy.rs diff --git a/rust/perspective-vieux/src/rust/download.rs b/rust/perspective-vieux/src/rust/session/download.rs similarity index 98% rename from rust/perspective-vieux/src/rust/download.rs rename to rust/perspective-vieux/src/rust/session/download.rs index 4c54dae824..d389ced6fd 100644 --- a/rust/perspective-vieux/src/rust/download.rs +++ b/rust/perspective-vieux/src/rust/session/download.rs @@ -35,7 +35,6 @@ pub fn download(view: &PerspectiveJsView) -> Promise { let view = view.clone(); future_to_promise(async move { download_async(&view).await?; - view.delete().await?; Ok(JsValue::NULL) }) } diff --git a/rust/perspective-vieux/src/rust/session/view_subscription.rs b/rust/perspective-vieux/src/rust/session/view_subscription.rs new file mode 100644 index 0000000000..f8b7991d6e --- /dev/null +++ b/rust/perspective-vieux/src/rust/session/view_subscription.rs @@ -0,0 +1,82 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// 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. + +use crate::utils::perspective::*; +use crate::utils::*; + +use wasm_bindgen::prelude::*; +use wasm_bindgen::JsCast; +use wasm_bindgen_futures::future_to_promise; +use yew::prelude::*; + +#[derive(Clone, Debug, Default, PartialEq)] +pub struct TableStats { + pub is_pivot: bool, + pub num_rows: Option, + pub virtual_rows: Option, +} + +type ViewSubscriptionData = + (PerspectiveJsTable, PerspectiveJsView, Callback); + +pub struct ViewSubscription { + data: ViewSubscriptionData, + closure: Closure js_sys::Promise>, +} + +thread_local! { + static NULL_C: Closure = Closure::wrap(Box::new(|_| ())); +} + +impl ViewSubscription { + pub fn new( + table: PerspectiveJsTable, + view: PerspectiveJsView, + callback: Callback, + ) -> ViewSubscription { + let data = (table, view.clone(), callback); + let closure = async_method_to_jsfunction(&data, Self::update_view_stats); + + view.on_update(closure.as_ref().unchecked_ref()); + let _ = NULL_C.with(|x| { + future_to_promise(Self::update_view_stats(data.clone())).catch(x) + }); + ViewSubscription { data, closure } + } + + /// TODO Use serde to serialize the full view config, instead of calculating + /// `is_pivot` here. + pub async fn update_view_stats( + (table, view, callback): ViewSubscriptionData, + ) -> Result { + let config = view.get_config().await?; + let num_rows = table.size().await? as u32; + let virtual_rows = view.num_rows().await? as u32; + let stats = TableStats { + num_rows: Some(num_rows), + virtual_rows: Some(virtual_rows), + is_pivot: config.row_pivots().length() > 0 + || config.column_pivots().length() > 0 + || virtual_rows != num_rows, + }; + + callback.emit(stats); + Ok(JsValue::UNDEFINED) + } + + pub fn view(&self) -> &PerspectiveJsView { + &self.data.1 + } +} + +impl Drop for ViewSubscription { + fn drop(&mut self) { + let update = self.closure.as_ref().unchecked_ref(); + self.data.1.remove_update(update); + } +} diff --git a/rust/perspective-vieux/src/rust/status_bar.rs b/rust/perspective-vieux/src/rust/status_bar.rs deleted file mode 100644 index b0dbba1f2d..0000000000 --- a/rust/perspective-vieux/src/rust/status_bar.rs +++ /dev/null @@ -1,314 +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. - -use crate::copy::{copy, copy_flat}; -use crate::download::{download, download_flat}; -use crate::utils::perspective::*; -use crate::utils::*; - -use js_sys::Promise; -use num_format::{Locale, ToFormattedString}; -use std::cell::RefCell; -use std::future::Future; -use std::rc::Rc; -use typed_html::{dom::DOMTree, html, text}; -use wasm_bindgen::prelude::*; -use wasm_bindgen::JsCast; -use wasm_bindgen_futures::JsFuture; -use web_sys::{Element, HtmlElement, MessageEvent}; - -static CSS: &str = include_str!("../../dist/css/container.css"); - -type JsResult = Result; - -/// The overall status, which will be represented in the UI by a color coded button. -#[derive(Debug, Clone)] -pub enum Status { - Connected, - Initializing, - Uninitialized, - Error, -} - -impl Status { - /// The CSS class name to use for a given `Status` - pub fn class_name(&self) -> &str { - match self { - Status::Connected => "connected", - Status::Initializing => "initializing", - Status::Error => "error", - _ => "", - } - } -} - -impl Default for Status { - fn default() -> Self { - Status::Uninitialized - } -} - -/// The data state of a `StatusBarElement`, which will consult this struct to determine -/// how to render itself. `web_sys::*` values from the actual DOM state are not -/// restricted from living on this struct, as long as they are relevent to the current -/// data state, but `elem` and the `ShadowRoot` do not live here. -#[derive(Default)] -struct StatusBarState { - rows: u32, - virtual_rows: u32, - is_pivot: bool, - status: Status, - view: Option, - table: Option, - update_callback: Option js_sys::Promise>>, -} - -impl StatusBarState { - /// Given a JavaScript `Promise`, await the result and set this state's - /// `table`, `rows`, and `status` fields along the way. `status` specifically is - /// set twice; first to `Initializing` synchronously when the method is called, - /// and later to the resolution state after the async `Promise` has resolved. - pub fn set_table( - this: Rc>, - table: Promise, - ) -> impl Future> { - this.borrow_mut().status = Status::Initializing; - async move { - let table = JsFuture::from(table).await; - if let Err(_) = table { - let mut state = this.borrow_mut(); - state.rows = 0; - state.table = None; - state.status = Status::Error; - } else if let Ok(js_table) = table { - let table: PerspectiveJsTable = js_table.unchecked_into(); - let mut state = this.borrow_mut(); - state.table = Some(table); - state.status = Status::Connected; - } - Ok(()) - } - } - - /// Set the `PerspectiveJsView` and update other state values from this and the - /// previously set `PerspectiveJsTable`. - pub async fn set_view( - this: &RefCell, - view: PerspectiveJsView, - ) -> Result<(), JsValue> { - this.borrow_mut().view = Some(view.clone()); - Self::update_state(&this).await?; - this.borrow_mut().is_pivot = Self::is_pivot(&this).await?; - Ok(()) - } - - /// Update the row count values from the `Perspective` JavaScript objects. - pub async fn update_state(this: &RefCell) -> Result<(), JsValue> { - let view = this.borrow().view.clone().unwrap(); - let table = this.borrow().table.clone().unwrap(); - let vrows = view.num_rows().await?; - let rows = table.size().await?; - - this.borrow_mut().virtual_rows = vrows as u32; - this.borrow_mut().rows = rows as u32; - Ok(()) - } - - async fn is_pivot(this: &RefCell) -> Result { - let view = this.borrow().view.clone().unwrap(); - let config = view.get_config().await?; - Ok(config.row_pivots().length() > 0 - || config.column_pivots().length() > 0 - || this.borrow().virtual_rows != this.borrow().rows) - } -} - -#[wasm_bindgen] -#[derive(Clone)] -pub struct StatusBarElement { - css: String, - elem: web_sys::HtmlElement, - root: Element, - state: Rc>, -} - -impl PerspectiveComponent for StatusBarElement { - fn get_root(&self) -> &web_sys::HtmlElement { - &self.elem - } -} - -#[wasm_bindgen] -impl StatusBarElement { - #[wasm_bindgen(constructor)] - pub fn new(elem: HtmlElement) -> Result { - let _self = { - let state = Rc::new(RefCell::new(StatusBarState::default())); - let init = web_sys::ShadowRootInit::new(web_sys::ShadowRootMode::Open); - let root = elem - .attach_shadow(&init) - .unwrap() - .unchecked_into::(); - - apply_style_node(&root, CSS)?; - apply_dom_tree(&root, &mut Self::template())?; - StatusBarElement { - css: CSS.to_owned(), - elem, - root, - state, - } - }; - - _self.get_root().add_event_listener_with_callback( - "click", - &_self.method_to_jsfunction_arg1(|this, e: MessageEvent| { - this.on_click(e.unchecked_into()) - }), - )?; - - _self.render()?; - Ok(_self) - } - - /// The HTML template for this component. - fn template() -> DOMTree { - html! { -
-
- -
-
- "Reset" - "Export" - "Copy" -
-
-
- } - } - - /// The HTML template function for the `rows` box, which shows the data set total rows - /// and query rows. This is called every draw loop, so it should be as cheap as - /// possible! - fn rows_template(state: &StatusBarState) -> DOMTree { - let nrows = &(state.rows as u32).to_formatted_string(&Locale::en); - if state.is_pivot { - let vrows = (state.virtual_rows as u32).to_formatted_string(&Locale::en); - html! { -
- { text!("{} ", &vrows) } - { text!("arrow_back") } - { text!(" {} rows", nrows) } -
- } - } else { - html! { -
{ text!("{} rows", &nrows) }
- } - } - } - - /// Set a `PerspectiveJsView` for this element to display status state from. Must - /// not be called until after `set_table()` with the `PerspectiveJsTable` this view - /// was presumably instanced from. - pub fn set_view(&mut self, view: PerspectiveJsView) -> Result { - let cb = self.async_method_to_jsfunction(|this| async move { - StatusBarState::update_state(&this.state).await?; - this.render() - }); - - view.on_update(cb.as_ref().unchecked_ref()); - self.state.borrow_mut().update_callback = Some(cb); - Ok(self.async_method_to_jspromise(|this| async move { - StatusBarState::set_view(&this.state, view).await?; - this.render() - })) - } - - /// Set a `PerspectiveJsTable` for this element. `StatusBarState::set_table` is - /// called without `await` to ensure the status is set to `Initializing` before - /// `render()` is called. - pub fn set_table(&self, table: Promise) -> Result { - let table_promise = StatusBarState::set_table(self.state.clone(), table); - self.render()?; - Ok(self.async_method_to_jspromise(|this| async move { - table_promise.await?; - this.render() - })) - } - - /// Render's this widget from it's `state` property using the DOM API directly. - fn render(&self) -> Result { - let state = self.state.borrow(); - let mut rows_template = Self::rows_template(&state); - - let status = self.root.query_selector("#status")?.unwrap(); - status.set_class_name(state.status.class_name()); - - let rows = self.root.query_selector("#rows")?.unwrap(); - rows.set_text_content(Some("")); - apply_dom_tree(&rows, &mut rows_template)?; - Ok(JsValue::from_bool(true)) - } - - /// Click dispatch. - fn on_click(&self, event: web_sys::KeyboardEvent) -> JsResult<()> { - event.stop_propagation(); - event.prevent_default(); - let target: web_sys::HtmlElement = - event.composed_path().get(0).unchecked_into(); - match &target.get_attribute("id").unwrap()[..] { - "export" if event.shift_key() => { - if let Some(table) = &self.state.borrow().table { - let _ = download_flat(table); - } - } - "export" => { - if let Some(view) = &self.state.borrow().view { - let _ = download(view); - } - } - "copy" if event.shift_key() => { - if let Some(table) = &self.state.borrow().table { - let _ = copy_flat(table); - } - } - "copy" => { - if let Some(view) = &self.state.borrow().view { - let _ = copy(view); - } - } - "reset" => { - let _ = self.reset()?; - } - _ => (), - }; - Ok(()) - } - - /// Cleans up the callback registered to the current `view.on_update()`. - pub fn remove_on_update_callback(&self) { - if let StatusBarState { - update_callback: Some(ref cb), - view: Some(ref view), - .. - } = *self.state.borrow() - { - view.remove_update(cb.as_ref().unchecked_ref()); - } - } - - /// Dispatch a `Reset` event. This action cannot be handled by the status bar - /// currently as it does not have access to the `` it is - /// embedded in. - fn reset(&self) -> Result { - let event = web_sys::CustomEvent::new("perspective-statusbar-reset")?; - self.elem.dispatch_event(&event) - } -} diff --git a/rust/perspective-vieux/src/rust/utils.rs b/rust/perspective-vieux/src/rust/utils.rs index 022f190575..d706110e29 100644 --- a/rust/perspective-vieux/src/rust/utils.rs +++ b/rust/perspective-vieux/src/rust/utils.rs @@ -10,12 +10,13 @@ pub mod perspective; +use std::cell::RefCell; use std::future::Future; -use typed_html::dom::{DOMTree, VNode}; +use std::ops::Deref; +use std::rc::Rc; use wasm_bindgen::prelude::*; -use wasm_bindgen::JsCast; use wasm_bindgen_futures::future_to_promise; -use web_sys::{Document, Element}; +use yew::prelude::*; pub type JsResult = Result; @@ -32,144 +33,37 @@ extern "C" { pub fn log_str(s: &str); } -/// Apply a style node to the target `elem`. -pub fn apply_style_node(elem: &Element, css: &str) -> Result<(), JsValue> { - let document = &web_sys::window().unwrap().document().unwrap(); - let style = document.create_element("style")?; - style.set_text_content(Some(css)); - elem.append_child(&style)?; - Ok(()) -} - -pub fn apply_dom_tree( - elem: &Element, - tree: &mut DOMTree, -) -> Result<(), JsValue> { - let document = &web_sys::window().unwrap().document().unwrap(); - match tree.vnode() { - VNode::Element(x) => { - for child in x.children { - apply_vnode(document, elem, &child)?; - } - } - _ => unimplemented!(), - } - - Ok(()) -} - -fn apply_vnode( - document: &Document, - elem: &Element, - node: &VNode<'_, String>, -) -> Result<(), JsValue> { - match node { - VNode::Text(text) | VNode::UnsafeText(text) => { - let node = document.create_text_node(&text); - elem.append_child(&node).map(|_| ())?; - Ok(()) - } - VNode::Element(element) => { - let node = document.create_element(element.name)?; - for (key, value) in &element.attributes { - node.set_attribute(&key, &value)?; - } - - for child in &element.children { - apply_vnode(document, &node, &child)?; - } - - elem.append_child(&node)?; - Ok(()) - } - } -} - -pub trait PerspectiveComponent: Clone { - /// The root `HtmlElement` to which this component renders. - fn get_root(&self) -> &web_sys::HtmlElement; - - /// Convenience function for injecting `self` into ` closure which returns a - /// `Future`, the Rust equivalent of an `AsyncFn`. It handles both the lifetime of - /// `self` as well as wrapping the inner `Future` in a JavaScript `Promise` (or it - /// would not execute). - fn async_method_to_jsfunction( - &self, - f: F, - ) -> Closure js_sys::Promise> - where - T: Future> + 'static, - F: Fn(Self) -> T + 'static, - Self: 'static, - { - let this = self.clone(); - let cb = move || future_to_promise(f(this.clone())); - let box_cb: Box js_sys::Promise> = Box::new(cb); - Closure::wrap(box_cb) - } - - /// Convenience function for wrapping and injecting `self` into a `Future`. - fn async_method_to_jspromise(&self, f: F) -> js_sys::Promise +pub trait ToClosure { + fn to_closure(&self, f: F) -> Closure where - T: Future> + 'static, - F: FnOnce(Self) -> T + 'static, - Self: 'static, - { - let this = self.clone(); - future_to_promise(f(this.clone())) - } + F: Fn(MouseEvent) -> T::Message + 'static; +} - /// Convenience function for wrapping and injecting `self` into a closure with an - /// argument (a common pattern for event handles in JavaScript). - fn method_to_jsfunction_arg1(&self, f: F) -> js_sys::Function +impl ToClosure for ComponentLink { + fn to_closure(&self, f: F) -> Closure where - T: wasm_bindgen::convert::FromWasmAbi + 'static, - F: Fn(&Self, T) -> Result<(), JsValue> + 'static, - Self: 'static, + F: Fn(MouseEvent) -> T::Message + 'static, { - let this = self.clone(); - let box_cb: Box Result<(), JsValue>> = - Box::new(move |e| f(&this, e)); - Closure::wrap(box_cb).into_js_value().unchecked_into() + let callback = self.callback(f); + Closure::wrap(Box::new(move |event: MouseEvent| { + callback.emit(event); + }) as Box) } } -#[cfg(test)] -mod perspective_component_tests { - use crate::utils::*; - use wasm_bindgen_test::*; - - wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser); - - #[derive(Clone)] - struct Test {} - - impl PerspectiveComponent for Test { - fn get_root(&self) -> &web_sys::HtmlElement { - unimplemented!() - } - } - - #[wasm_bindgen_test] - fn test_async_method_to_jsfunction() { - async fn f(_: Test) -> Result { - Ok(JsValue::UNDEFINED) - } - - let _: &js_sys::Function = (Test {}) - .async_method_to_jsfunction(f) - .as_ref() - .unchecked_ref(); - } - - #[wasm_bindgen_test] - fn test_async_method_to_jspromise() { - async fn f(_: Test) -> Result { - Ok(JsValue::UNDEFINED) - } - - let _: js_sys::Promise = (Test {}).async_method_to_jspromise(f); - } +pub fn async_method_to_jsfunction( + this: &U, + f: F, +) -> Closure js_sys::Promise> +where + T: Future> + 'static, + F: Fn(U) -> T + 'static, + U: Clone + 'static, +{ + let this = this.clone(); + let cb = move || future_to_promise(f(this.clone())); + let box_cb: Box js_sys::Promise> = Box::new(cb); + Closure::wrap(box_cb) } #[macro_export] @@ -195,3 +89,117 @@ macro_rules! js_object { $o }}; } + +#[macro_export] +macro_rules! maybe { + ($($exp:stmt);* $(;)*) => {{ + let x: Result<_, JsValue> = (|| { + $( + $exp + )* + })(); + x.unwrap() + }}; +} + +pub struct WeakComponentLink(Rc>>>); + +impl Clone for WeakComponentLink { + fn clone(&self) -> Self { + Self(Rc::clone(&self.0)) + } +} + +impl Default for WeakComponentLink { + fn default() -> Self { + Self(Rc::default()) + } +} + +impl Deref for WeakComponentLink { + type Target = Rc>>>; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl PartialEq for WeakComponentLink { + fn eq(&self, other: &Self) -> bool { + Rc::ptr_eq(&self.0, &other.0) + } +} + +#[macro_export] +macro_rules! enable_weak_link_test { + ($props:expr, $link:expr) => { + #[cfg(test)] + { + *$props.weak_link.borrow_mut() = Some($link.clone()); + } + }; +} + +#[macro_export] +macro_rules! test_html { + ($($html:tt)*) => {{ + use crate::components::perspective_vieux::CSS; + use wasm_bindgen::JsCast; + use yew::prelude::*; + + struct TestElement { + html: Html + } + + #[derive(Properties, Clone)] + struct TestElementProps { + html: Html + } + + impl Component for TestElement { + type Message = (); + type Properties = TestElementProps; + + fn create(_props: Self::Properties, _link: ComponentLink) -> Self { + TestElement { + html: _props.html, + } + } + + fn update(&mut self, _msg: Self::Message) -> ShouldRender { + false + } + + fn change(&mut self, _props: Self::Properties) -> ShouldRender { + true + } + + fn view(&self) -> Html { + html! { + <> + + { self.html.clone() } + + } + } + } + + let window = web_sys::window().unwrap(); + let document = window.document().unwrap(); + let body = document.body().unwrap(); + let div = document.create_element("div").unwrap(); + body.append_child(&div).unwrap(); + + let init = web_sys::ShadowRootInit::new(web_sys::ShadowRootMode::Open); + let shadow_root = div + .attach_shadow(&init) + .unwrap() + .unchecked_into::(); + + let app = App::::new(); + app.mount_with_props(shadow_root, TestElementProps { html: html!{ $($html)* } }) + }} +} diff --git a/rust/perspective-vieux/src/rust/utils/perspective.rs b/rust/perspective-vieux/src/rust/utils/perspective.rs index 30cf8574da..01fd83a351 100644 --- a/rust/perspective-vieux/src/rust/utils/perspective.rs +++ b/rust/perspective-vieux/src/rust/utils/perspective.rs @@ -28,8 +28,12 @@ macro_rules! async_typed { #[rustfmt::skip] extern "C" { + #[derive(Clone)] pub type PerspectiveJsWorker; + #[wasm_bindgen(method, catch, js_name = delete)] + pub async fn _delete(this: &PerspectiveJsWorker) -> Result; + #[wasm_bindgen(method, catch, js_name = table)] pub async fn _table( this: &PerspectiveJsWorker, @@ -39,6 +43,9 @@ extern "C" { #[derive(Clone)] pub type PerspectiveJsTable; + #[wasm_bindgen(method, catch, js_name = delete)] + pub async fn _delete(this: &PerspectiveJsTable) -> Result; + #[wasm_bindgen(method, catch, js_name = size)] pub async fn _size(this: &PerspectiveJsTable) -> Result; @@ -82,10 +89,12 @@ extern "C" { } impl PerspectiveJsWorker { + async_typed!(_delete, delete() -> ()); async_typed!(_table, table(data: js_sys::Object) -> PerspectiveJsTable); } impl PerspectiveJsTable { + async_typed!(_delete, delete() -> ()); async_typed!(_view, view(config: js_sys::Object) -> PerspectiveJsView); async_typed!(_size, size() -> f64); } diff --git a/rust/perspective-vieux/tests/perspective_vieux.rs b/rust/perspective-vieux/tests/perspective_vieux.rs new file mode 100644 index 0000000000..310f15504b --- /dev/null +++ b/rust/perspective-vieux/tests/perspective_vieux.rs @@ -0,0 +1,182 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// 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. + +extern crate perspective_vieux; + +use crate::utils::perspective::*; +use perspective_vieux::components::perspective_vieux::*; +use perspective_vieux::utils::*; +use perspective_vieux::*; +use wasm_bindgen_futures::JsFuture; + +use futures::channel::oneshot::*; +use std::cell::RefCell; +use std::iter::FromIterator; +use wasm_bindgen::prelude::*; +use wasm_bindgen::JsCast; +use wasm_bindgen_test::*; +use web_sys::*; +use yew::prelude::*; + +wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser); + +#[wasm_bindgen(inline_js = " + + export async function worker() { + await import('/pkg/perspective.inline.js'); + return window.perspective.worker(); + } + +")] +extern "C" { + fn worker() -> js_sys::Promise; +} + +fn set_up_html() -> (WeakComponentLink, web_sys::ShadowRoot) { + let link: WeakComponentLink = WeakComponentLink::default(); + let root = NodeRef::default(); + let document = window().unwrap().document().unwrap(); + let elem: HtmlElement = document.create_element("div").unwrap().unchecked_into(); + let div1: HtmlElement = document.create_element("div").unwrap().unchecked_into(); + let div2: HtmlElement = document.create_element("div").unwrap().unchecked_into(); + test_html! { + + + }; + + let root: web_sys::ShadowRoot = root + .cast::() + .unwrap() + .parent_node() + .unwrap() + .unchecked_into(); + + (link, root) +} + +#[wasm_bindgen_test] +pub fn test_settings_closed() { + let (_, root) = set_up_html(); + for selector in ["slot[name=main_panel", "#config_button"].iter() { + assert!(root + .query_selector(selector) + .unwrap() + .unwrap() + .is_connected()); + } + + assert_eq!(root.query_selector("#app_panel").unwrap(), None); +} + +#[wasm_bindgen_test] +pub fn test_settings_open() { + let (link, root) = set_up_html(); + let vieux = link.borrow().clone().unwrap(); + vieux.send_message(Msg::ToggleConfig(Some(true), None)); + for selector in [ + "#app_panel", + "slot[name=main_panel", + "#config_button", + "#status_bar", + ] + .iter() + { + assert!(root + .query_selector(selector) + .unwrap() + .unwrap() + .is_connected()); + } +} + +/// Generate a test `Table`, but only create teh webworker once or the tests will +/// figuratively literally run forever. +async fn get_table() -> PerspectiveJsTable { + thread_local! { + static WORKER: RefCell> = RefCell::new(None); + } + + let worker: PerspectiveJsWorker = match WORKER.with(|x| x.borrow().clone()) { + Some(x) => x, + None => JsFuture::from(worker()).await.unwrap().unchecked_into(), + }; + + WORKER.with(|x| { + *x.borrow_mut() = Some(worker.clone()); + }); + + worker + .table(js_object!( + "A", + js_sys::Array::from_iter( + [JsValue::from(1), JsValue::from(2), JsValue::from(3)].iter() + ) + )) + .await + .unwrap() +} + +#[wasm_bindgen_test] +pub async fn test_load_table() { + let (link, root) = set_up_html(); + let table = get_table().await; + let (sender, receiver) = channel::>(); + let vieux = link.borrow().clone().unwrap(); + vieux.send_message(Msg::ToggleConfig(Some(true), None)); + vieux.send_message(Msg::LoadTable(js_sys::Promise::resolve(&table), sender)); + receiver.await.unwrap().unwrap(); + assert_eq!( + root.query_selector("#rows").unwrap().unwrap().inner_html(), + "3 rows" + ); +} + +#[wasm_bindgen_test] +pub async fn test_export_button() { + let document = window().unwrap().document().unwrap(); + let (link, _) = set_up_html(); + let table = get_table().await; + let (sender, receiver) = channel::>(); + let vieux = link.borrow().clone().unwrap(); + vieux.send_message(Msg::ToggleConfig(Some(true), None)); + vieux.send_message(Msg::LoadTable(js_sys::Promise::resolve(&table), sender)); + let _ = receiver.await.unwrap().unwrap(); + + // Create a `MutationListener` and async channel to signal us when the download + // button attaches the invisible button to `document.body`. + let (sender, receiver) = channel::(); + let b: Box = + Box::new(|_mut_list, _observer| { + let elem = _mut_list + .get(0) + .unchecked_into::() + .added_nodes() + .get(0) + .unwrap() + .unchecked_into::(); + let href = elem.get_attribute("href").unwrap(); + sender.send(href).unwrap(); + }); + + let mut config = web_sys::MutationObserverInit::new(); + config.child_list(true); + web_sys::MutationObserver::new(&Closure::once(b).into_js_value().unchecked_into()) + .unwrap() + .observe_with_options(&document.body().unwrap(), &config) + .unwrap(); + + vieux.send_message(Msg::Export(true)); + + // Await the `MutationObserver` we set up earlier, and validate that its `href` + // attribute is at least a blob. + assert_eq!(&receiver.await.unwrap()[..5], "blob:"); +} diff --git a/rust/perspective-vieux/tests/status_bar.rs b/rust/perspective-vieux/tests/status_bar.rs deleted file mode 100644 index 9ab770592b..0000000000 --- a/rust/perspective-vieux/tests/status_bar.rs +++ /dev/null @@ -1,178 +0,0 @@ -extern crate perspective_vieux; - -use core::iter::FromIterator; -use futures::channel::oneshot::*; -use wasm_bindgen::prelude::*; -use wasm_bindgen::JsCast; -use wasm_bindgen_futures::JsFuture; -use wasm_bindgen_test::*; - -use perspective_vieux::js_object; -use perspective_vieux::status_bar::StatusBarElement; -use perspective_vieux::utils::perspective::*; - -wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser); - -#[cfg(test)] -fn create_element() -> (web_sys::Element, StatusBarElement) { - let window = web_sys::window().unwrap(); - let document = window.document().unwrap(); - let elem = document.create_element("div").unwrap(); - let status_bar = StatusBarElement::new(elem.clone().unchecked_into()).unwrap(); - (elem, status_bar) -} - -#[cfg(test)] -#[wasm_bindgen(inline_js = "export async function worker() { - await import('/pkg/perspective.inline.js'); - return window.perspective.worker(); - }")] -extern "C" { - #[wasm_bindgen(inline_js = "window.perspective.worker()")] - fn worker() -> js_sys::Promise; -} - -/// Just assert that attaching the element renders the default HTML to the `shadowRoot`. -#[cfg(test)] -#[wasm_bindgen_test] -pub fn test_html() { - let (elem, _) = create_element(); - let txt = elem.shadow_root().unwrap().inner_html(); - assert_eq!( - txt, - format!( - "
ResetExport\ -Copy
0 rows
", - include_str!("../dist/css/container.css") - ) - ); -} - -/// Tests that the rows count is set correctly when a `table` is loaded. -#[cfg(test)] -#[wasm_bindgen_test] -pub async fn test_rows_count() { - // Create the status bar - let (elem, mut status_bar) = create_element(); - assert_eq!(elem.shadow_root().unwrap().children().length(), 4); - - // Create a perspective Worker, Table, View - let worker: PerspectiveJsWorker = - JsFuture::from(worker()).await.unwrap().unchecked_into(); - - let table = worker - .table(js_object!( - "A", - js_sys::Array::from_iter( - [JsValue::from(1), JsValue::from(2), JsValue::from(3)].iter() - ) - )) - .await - .unwrap() - .unchecked_into::(); - - let view = table.view(js_object!()).await.unwrap(); - - // Set these perspective parts to the status bar - let table_promise = js_sys::Promise::resolve(&table); - JsFuture::from(status_bar.set_table(table_promise).unwrap()) - .await - .unwrap(); - - JsFuture::from(status_bar.set_view(view).unwrap()) - .await - .unwrap(); - - // Find the `rows` span. - let span: web_sys::HtmlElement = elem - .shadow_root() - .unwrap() - .query_selector("#rows span") - .unwrap() - .unwrap() - .unchecked_into(); - - assert_eq!(span.text_content().unwrap(), "3 rows"); -} - -/// Tests that the `EXPORT` button works by clicking on it and looking for the DOM side -/// effects. -#[cfg(test)] -#[wasm_bindgen_test] -pub async fn test_download() { - // Create the status bar - let window = web_sys::window().unwrap(); - let document = window.document().unwrap(); - let (elem, mut status_bar) = create_element(); - assert_eq!(elem.shadow_root().unwrap().children().length(), 4); - - // Create a `MutationListener` and async channel to signal us when the download - // button attaches the invisible button to `document.body`. - let (sender, receiver) = channel::(); - let b: Box = - Box::new(|_mut_list, _observer| { - let elem = _mut_list - .get(0) - .unchecked_into::() - .added_nodes() - .get(0) - .unwrap() - .unchecked_into::(); - let href = elem.get_attribute("href").unwrap(); - sender.send(href).unwrap(); - }); - - let mut config = web_sys::MutationObserverInit::new(); - config.child_list(true); - web_sys::MutationObserver::new(&Closure::once(b).into_js_value().unchecked_into()) - .unwrap() - .observe_with_options(&document.body().unwrap(), &config) - .unwrap(); - - // Create a perspective Worker, Table, View - let worker: PerspectiveJsWorker = - JsFuture::from(worker()).await.unwrap().unchecked_into(); - - let table = worker - .table(js_object!( - "A", - js_sys::Array::from_iter( - [JsValue::from(1), JsValue::from(2), JsValue::from(3)].iter() - ) - )) - .await - .unwrap() - .unchecked_into::(); - let size = table.size().await.unwrap() as usize; - assert_eq!(size, 3); - - let view = table.view(js_object!()).await.unwrap(); - - // Set these perspective parts to the status bar - let table_promise = js_sys::Promise::resolve(&table); - JsFuture::from(status_bar.set_table(table_promise).unwrap()) - .await - .unwrap(); - - JsFuture::from(status_bar.set_view(view).unwrap()) - .await - .unwrap(); - - // Find and click the `EXPORT` button. - let button: web_sys::HtmlElement = elem - .shadow_root() - .unwrap() - .query_selector("span.button#export") - .unwrap() - .unwrap() - .unchecked_into(); - - button.click(); - - // Await the `MutationObserver` we set up earlier, and validate that its `href` - // attribute is at least a blob. - assert_eq!(&receiver.await.unwrap()[..5], "blob:"); -}