Skip to content

Commit

Permalink
Port to rust
Browse files Browse the repository at this point in the history
  • Loading branch information
texodus committed Apr 22, 2021
1 parent 0a3c7dd commit cd45723
Show file tree
Hide file tree
Showing 30 changed files with 929 additions and 403 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -177,3 +177,5 @@ rust/perspective-vieux/pkg
rust/perspective-vieux/target
rust/perspective-vieux/target2
.emsdk
docs/static/css/material.dark.css
docs/i18n/en.json
7 changes: 7 additions & 0 deletions docs/static/css/custom.css
Original file line number Diff line number Diff line change
Expand Up @@ -239,4 +239,11 @@ pre code.hljs {
.blockContent code.hljs.css.language-bash {
background: #000000;
color: #fff;
}

perspective-column-style {
background-color: #2a2c2f !important;
color: #c5c9d0 !important;
border: 1px solid #3b3f46 !important;
--input--border-color :#3b3f46 !important;
}
1 change: 1 addition & 0 deletions packages/perspective-viewer-datagrid/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
"dependencies": {
"@finos/perspective": "^0.7.0",
"@finos/perspective-viewer": "^0.7.0",
"chroma-js": "^1.3.4",
"regular-table": "=0.3.3"
},
"devDependencies": {
Expand Down
12 changes: 4 additions & 8 deletions packages/perspective-viewer-datagrid/src/js/color_utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,8 @@

export const PLUGIN_SYMBOL = Symbol("Plugin Symbol");

export function hexToRgb(hex) {
var bigint = parseInt(hex.trim().slice(1), 16);
var r = (bigint >> 16) & 255;
var g = (bigint >> 8) & 255;
var b = bigint & 255;
return [r, g, b];
}

// AFAICT `chroma-js` has no alpha-aware blending? So we need a function to get
// the color of a heatmap cell over the background.
export function rgbaToRgb([r, g, b, a], source = [255, 255, 255]) {
function f(i, c) {
return ((1 - a) * (source[i] / 255) + a * (c / 255)) * 255;
Expand All @@ -25,6 +19,8 @@ export function rgbaToRgb([r, g, b, a], source = [255, 255, 255]) {
return [f(0, r), f(1, g), f(2, b)];
}

// Chroma does this but why bother?
export function infer_foreground_from_background([r, g, b]) {
// TODO Implement dark/light themes.
return Math.sqrt(r * r * 0.299 + g * g * 0.587 + b * b * 0.114) > 130 ? "#161616" : "#ffffff";
}
302 changes: 54 additions & 248 deletions packages/perspective-viewer-datagrid/src/js/plugin_menu.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,261 +7,67 @@
*
*/

import {hexToRgb} from "./color_utils.js";
import chroma from "chroma-js";

export const PLUGIN_SYMBOL = Symbol("Plugin Symbol");

class PluginMenu extends HTMLElement {
constructor() {
super();
export function activate_plugin_menu(regularTable, target, maxes) {
const target_meta = regularTable.getMeta(target);
const column_name = target_meta.column_header[target_meta.column_header.length - 1];
const column_type = this._schema[column_name];
const column_max = maxes[column_name];
const default_config = {
gradient: column_max,
pos_color: this._pos_color[0],
neg_color: this._neg_color[0]
};

if (column_type === "float") {
default_config.fixed = 2;
} else if (column_type === "integer") {
default_config.fixed = 0;
} else {
this._open_column_styles_menu.pop();
regularTable.draw();
return;
}

open(pv_plugin, regularTable, target, column_meta) {
this._pv_plugin = pv_plugin;
this._regularTable = regularTable;

const rect = target.getBoundingClientRect();
this.setAttribute("tabindex", "0");
this.className = "column-plugin-menu";
this.attachShadow({mode: "open"});
const pset = regularTable[PLUGIN_SYMBOL] || {};
const meta = regularTable.getMeta(target);
const column_name = meta.column_header[meta.column_header.length - 1];
const column_type = pv_plugin._schema[column_name];
const column_styles = (pset[column_name] = pset[column_name] || {});
const default_prec = column_type === "float" ? 2 : 0;
const is_numeric = column_type === "float" || column_type === "integer";
if (!is_numeric) {
this.drop();
return;
const menu = document.createElement("perspective-column-style");
const scroll_handler = () => menu.blur();
const update_handler = event => {
const config = event.detail;
if (config.pos_color) {
config.pos_color = [config.pos_color, ...chroma(config.pos_color).rgb()];
config.neg_color = [config.neg_color, ...chroma(config.neg_color).rgb()];
}

const fixed = (column_styles.fixed + 1 || default_prec + 1) - 1;
const fixed_example = fixed > 0 ? "0." + "0".repeat(fixed - 1) + "1" : "1";

let html = `
<style>
:host {
position: absolute;
left: ${rect.left}px;
top: ${rect.bottom}px;
padding: 6px;
outline: none;
font-family: "Open Sans";
font-size: 12px;
font-weight: 300;
border: inherit;
box-shadow: 0 2px 4px 0 rgb(0 0 0 / 10%);
}
:host input.parameter {
max-width: 65px;
background: none;
color: inherit;
border: 0px solid transparent;
border-bottom-width: 1px;
border-color: var(--input--border-color);
outline: none;
}
:host input[type=radio], :host input[type=checkbox], :host > div > div > span:first-child {
width: 24px;
margin: 0;
}
:host > div > div {
display: flex;
align-items: center;
height: 24px;
}
:host div.section {
margin-bottom: 8px;
}
:host input[type=color] {
width: 24px;
}
:host .operator {
font-family: "Roboto Mono", monospace;
white-space:pre;
}
:host input.parameter[disabled] {
opacity: 0.5;
}
:host .indent1 {
margin-left: 24px;
}
</style>
<div>
<div>
<input type="checkbox" checked disabled>
</input>
<span id="fixed-examples">Prec ${fixed_example}</span>
</div>
<div class="section">
<span></span>
<input
id="fixed-param"
value="${fixed}"
class="parameter"
${is_numeric ? "" : "disabled"}
type="number"
min="0"
step="1">
</input>
</div>
<div>
<input
id="color-selected"
type="checkbox"
${column_styles.color_mode !== undefined ? "checked" : ""}>
</input>
<input
id="color-param"
value="${column_styles.color_mode !== undefined ? column_styles.pos_color[0] : pv_plugin._pos_color[0]}"
${column_styles.color_mode !== undefined ? "" : "disabled"}
class="parameter"
type="color">
</input>
<span class="operator"> + / - </span>
<input
id="neg-color-param"
value="${column_styles.color_mode !== undefined ? column_styles.neg_color[0] : pv_plugin._neg_color[0]}"
${column_styles.color_mode !== undefined ? "" : "disabled"}
class="parameter"
type="color">
</input>
</div>
<div class="indent1">
<input
id="color-mode-1"
name="color-mode"
type="radio"
value="foreground"
class="parameter"
${column_styles.color_mode !== undefined ? "" : "disabled"}
${column_styles.color_mode !== "background" && column_styles.color_mode !== "gradient" ? "checked" : ""}>
</input>
<span>Foreground</span>
</div>
<div class="indent1">
<input
id="color-mode-2"
name="color-mode"
type="radio"
value="background"
class="parameter"
${column_styles.color_mode !== undefined ? "" : "disabled"}
${column_styles.color_mode === "background" ? "checked" : ""}>
</input>
<span>Background</span>
</div>
<div class="indent1">
<input
id="color-mode-3"
name="color-mode"
type="radio"
value="gradient"
class="parameter"
${column_styles.color_mode !== undefined ? "" : "disabled"}
${column_styles.color_mode === "gradient" ? "checked" : ""}>
</input>
<span>Gradient</span>
</div>
<div class="indent1">
<span></span>
<input
id="gradient-param"
value="${Math.ceil(column_styles.opacity + 1 || column_meta[column_name] + 1) - 1}"
class="parameter"
${column_styles.opacity !== undefined ? "" : "disabled"}
type="number"
min="0">
</input>
</div>
</div>
`;

this.shadowRoot.innerHTML += html;
document.body.appendChild(this);
this.shadowRoot.addEventListener(
"input",
(this._changeListener = () => {
const config = {};
const fixed = parseInt(this.shadowRoot.querySelector(`#fixed-param`).value);
const fixed_example = fixed > 0 ? "Prec 0." + "0".repeat(fixed - 1) + "1" : "Prec 1";
this.shadowRoot.querySelector(`#fixed-examples`).textContent = fixed_example;
if (isFinite(fixed) && fixed !== default_prec && fixed > -1) {
config.fixed = fixed;
} else {
// TODO validation
}

const is_color = this.shadowRoot.querySelector(`#color-selected`).checked;
const color = this.shadowRoot.querySelector(`#color-param`);
const neg_color = this.shadowRoot.querySelector(`#neg-color-param`);
const opacity = this.shadowRoot.querySelector(`#gradient-param`);
const color_mode_1 = this.shadowRoot.querySelector(`#color-mode-1`);
const color_mode_2 = this.shadowRoot.querySelector(`#color-mode-2`);
const color_mode_3 = this.shadowRoot.querySelector(`#color-mode-3`);
if (is_color) {
color_mode_1.disabled = color_mode_2.disabled = color_mode_3.disabled = false;
if (color_mode_1.checked) {
config.color_mode = "foreground";
} else if (color_mode_2.checked) {
config.color_mode = "background";
} else {
config.color_mode = "gradient";
}

if (config.color_mode === "gradient") {
opacity.disabled = false;
config.opacity = parseInt(opacity.value);
} else {
opacity.disabled = true;
opacity.value = Math.ceil(column_meta[column_name]);
}

color.disabled = neg_color.disabled = false;
config.pos_color = [color.value].concat(hexToRgb(color.value));
config.neg_color = [neg_color.value].concat(hexToRgb(neg_color.value));
} else {
color_mode_1.disabled = color_mode_2.disabled = color_mode_3.disabled = true;
color.disabled = neg_color.disabled = true;
opacity.disabled = true;
}

regularTable[PLUGIN_SYMBOL] = regularTable[PLUGIN_SYMBOL] || {};
regularTable[PLUGIN_SYMBOL][column_name] = config;
regularTable.draw({preserve_width: true});
regularTable.parentElement.dispatchEvent(new Event("perspective-config-update"));
})
);

this.addEventListener("blur", (this._drop_handler = this.drop.bind(this)));
regularTable.addEventListener("regular-table-scroll", this._drop_handler);
this.focus();
regularTable[PLUGIN_SYMBOL] = regularTable[PLUGIN_SYMBOL] || {};
regularTable[PLUGIN_SYMBOL][column_name] = config;
regularTable.draw({preserve_width: true});
regularTable.parentElement.dispatchEvent(new Event("perspective-config-update"));
};

const blur_handler = async () => {
regularTable.removeEventListener("regular-table-scroll", scroll_handler);
menu.removeEventListener("perspective-column-style-change", update_handler);
menu.removeEventListener("blur", blur_handler);
this._open_column_styles_menu.pop();
await regularTable.draw();
regularTable.parentElement.dispatchEvent(new Event("perspective-config-update"));
};

menu.addEventListener("perspective-column-style-change", update_handler);
menu.addEventListener("blur", blur_handler);
regularTable.addEventListener("regular-table-scroll", scroll_handler);

// Get the current column style config
const pset = regularTable[PLUGIN_SYMBOL] || {};
const config = Object.assign({}, (pset[column_name] = pset[column_name] || {}));
if (config.pos_color) {
config.pos_color = config.pos_color[0];
config.neg_color = config.neg_color[0];
}

drop() {
if (document.body.contains(this)) {
this.removeEventListener("blur", this._drop_handler);
this._regularTable?.removeEventListener("regular-table-scroll", this._drop_handler);
document.body.removeChild(this);
this.shadowRoot.removeEventListener("input", this._changeListener);
}
this._pv_plugin._open_column_styles_menu.pop();
this._regularTable.draw();
}
}

customElements.define("perspective-viewer-datagrid-plugin-menu", PluginMenu);

export function activate_plugin_menu(regularTable, target, meta) {
const menu = document.createElement("perspective-viewer-datagrid-plugin-menu");
menu.open(this, regularTable, target, meta);
// open the menu
menu.open(target, config, default_config);
}
Loading

0 comments on commit cd45723

Please sign in to comment.