Add sort-sk element to infra-sk.
Bug: skia:9219
Change-Id: Iac57e911a621f9860051a24effa7ae0c6eab08ca
Reviewed-on: https://skia-review.googlesource.com/c/buildbot/+/232717
Reviewed-by: Joe Gregorio <jcgregorio@google.com>
diff --git a/infra-sk/modules/sort-sk/index.js b/infra-sk/modules/sort-sk/index.js
new file mode 100644
index 0000000..6d80b81
--- /dev/null
+++ b/infra-sk/modules/sort-sk/index.js
@@ -0,0 +1,2 @@
+import './sort-sk.js'
+import './sort-sk.scss'
diff --git a/infra-sk/modules/sort-sk/sort-sk-demo.html b/infra-sk/modules/sort-sk/sort-sk-demo.html
new file mode 100644
index 0000000..f4dd1ca
--- /dev/null
+++ b/infra-sk/modules/sort-sk/sort-sk-demo.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>sort-sk</title>
+ <meta charset="utf-8" />
+ <meta http-equiv="X-UA-Compatible" content="IE=edge">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <style type="text/css" media="screen">
+button {
+ margin: 0 1em;
+}
+ </style>
+</head>
+<body>
+ <h1>sort-sk</h1>
+ <h2>Numeric</h2>
+ <sort-sk target=stuffToBeSorted>
+ <button data-key=clustersize data-default=up>Cluster Size</button>
+ <button data-key=stepsize>Step Size</button>
+ </sort-sk>
+
+ <div id=stuffToBeSorted>
+ <pre data-clustersize=10 data-stepsize=1.2>Size=10 Step=1.2</pre>
+ <pre data-clustersize=50 data-stepsize=0.5>Size=50 Step=0.5</pre>
+ <pre data-clustersize=100 data-stepsize=0.6>Size=100 Step=0.6</pre>
+ </div>
+
+ <h2>Alpha</h2>
+ <sort-sk target=stuffToBeSorted2>
+ <button data-key=name data-default=down data-sort-type=alpha>Name</button>
+ <button data-key=level data-sort-type=alpha>Level</button>
+ </sort-sk>
+
+ <div id=stuffToBeSorted2>
+ <pre data-name=foo data-level=beta >foo beta</pre>
+ <pre data-name=baz data-level=alpha>baz alpha</pre>
+ <pre data-name=bar data-level=gamma>bar gamma</pre>
+ </div>
+</body>
+</html>
diff --git a/infra-sk/modules/sort-sk/sort-sk-demo.js b/infra-sk/modules/sort-sk/sort-sk-demo.js
new file mode 100644
index 0000000..1c51332
--- /dev/null
+++ b/infra-sk/modules/sort-sk/sort-sk-demo.js
@@ -0,0 +1 @@
+import './index.js'
diff --git a/infra-sk/modules/sort-sk/sort-sk.js b/infra-sk/modules/sort-sk/sort-sk.js
new file mode 100644
index 0000000..e700eea
--- /dev/null
+++ b/infra-sk/modules/sort-sk/sort-sk.js
@@ -0,0 +1,143 @@
+/**
+ * @module module/sort-sk
+ * @description <h2><code>sort-sk</code></h2>
+ *
+ * Allows sorting the members of the indicated element by the values of the
+ * data attributes.
+ *
+ * Add children to <sort-sk> that generate click events and that have child
+ * content, such as buttons. Add a data-key * attribute to each child element
+ * that indicates which data-* attribute the children should be sorted on.
+ *
+ * Note that all sorting is done numerically, unless the
+ * 'data-sort-type=alpha' attribute is set on the element generating the
+ * click, in which case the sorting is done alphabetically.
+ *
+ * Additionally a single child element can have a data-default attribute with
+ * a value of 'up' or 'down' to indicate the default sorting that already
+ * exists in the data.
+ *
+ *
+ * @example An example usage, that will present two buttons to sort the contents of
+ * div#stuffToBeSorted.
+ *
+ * <sort-sk target=stuffToBeSorted>
+ * <button data-key=clustersize data-default=down>Cluster Size </button>
+ * <button data-key=stepsize data-sort-type=alpha>Name</button>
+ * </sort-sk>
+ *
+ * <div id=stuffToBeSorted>
+ * <div data-clustersize=10 data-name=foo></div>
+ * <div data-clustersize=50 data-name=bar></div>
+ * ...
+ * </div>
+ *
+ * @attr target - The id of the container element whose children are to be sorted.
+ *
+ */
+import { html, render } from 'lit-html'
+import { ElementSk } from '../../../infra-sk/modules/ElementSk'
+import { $, $$ } from 'common-sk/modules/dom'
+import 'elements-sk/icon/arrow-drop-down-icon-sk'
+import 'elements-sk/icon/arrow-drop-up-icon-sk'
+
+// The states to move each button through on a click.
+const toggle = {
+ '': 'down',
+ 'down': 'up',
+ 'up': 'down',
+};
+
+// Functions to pass to sort().
+const f_alpha_up = (x, y) => {
+ if (x.value === y.value) {
+ return 0;
+ }
+ return x.value > y.value ? 1 : -1;
+};
+const f_alpha_down = (x, y) => f_alpha_up(y, x);
+const f_num_up = (x, y) => (x.value - y.value);
+const f_num_down = (x, y) => f_num_up(y, x);
+
+window.customElements.define('sort-sk', class extends ElementSk {
+ connectedCallback() {
+ super.connectedCallback();
+ $('[data-key]', this).forEach((ele) => {
+ // Only attach the icons once.
+ if (ele.querySelector('arrow-drop-down-icon-sk')) {
+ return
+ }
+ ele.appendChild(document.createElement('arrow-drop-down-icon-sk'));
+ ele.appendChild(document.createElement('arrow-drop-up-icon-sk'));
+ ele.addEventListener('click', (e) => this._clickHandler(e));
+ });
+
+ // Handle a default value if one has been set.
+ const def = $$('[data-default]', this);
+ if (def) {
+ this._setSortClass(def, def.dataset.default);
+ }
+ }
+
+ _setSortClass(ele, value) {
+ ele.setAttribute('data-sort-sk', value);
+ }
+
+ _clearSortClass(ele) {
+ ele.removeAttribute('data-sort-sk');
+ }
+
+ _getSortClass(ele) {
+ return ele.getAttribute('data-sort-sk') || '';
+ }
+
+ _clickHandler(e) {
+ let ele = e.target;
+ while (ele.parentNode !== this) {
+ ele = ele.parentNode;
+ }
+
+ const dir = toggle[this._getSortClass(ele)];
+
+ $('[data-key]', this).forEach((e) => {
+ this._clearSortClass(e);
+ });
+ this._setSortClass(ele, dir);
+
+ // Remember the direction we are sorting in.
+ let up = dir === 'up';
+
+ // Are we sorting alphabetically or numerically.
+ let alpha = ele.dataset.sortType === 'alpha';
+
+ // Sort the children of the element at #target.
+ let sortBy = ele.dataset.key;
+ let container = this.parentElement.querySelector(`#${this.getAttribute('target')}`);
+ let arr = [];
+ for (const ele of container.children) {
+ let value = ele.dataset[sortBy];
+ if (!alpha) {
+ value = +value;
+ }
+ arr.push({
+ value: value,
+ node: ele
+ });
+ }
+
+ // Pick the desired sort function.
+ let f = f_alpha_up;
+ if (alpha) {
+ f = up ? f_alpha_up : f_alpha_down;
+ } else {
+ f = up ? f_num_up : f_num_down;
+ }
+ arr.sort(f);
+
+ // Rearrange the elements in the sorted order.
+ arr.forEach((e) => {
+ container.appendChild(e.node);
+ });
+ }
+});
+
diff --git a/infra-sk/modules/sort-sk/sort-sk.scss b/infra-sk/modules/sort-sk/sort-sk.scss
new file mode 100644
index 0000000..cc95479
--- /dev/null
+++ b/infra-sk/modules/sort-sk/sort-sk.scss
@@ -0,0 +1,34 @@
+@import '~elements-sk/colors';
+
+sort-sk {
+ display: flex;
+
+
+ [data-sort-sk="up"] {
+ arrow-drop-up-icon-sk {
+ visibility: visible;
+ display: inline-block;
+ }
+ arrow-drop-down-icon-sk {
+ display: none;
+ }
+ }
+
+ [data-sort-sk="down"] {
+ arrow-drop-up-icon-sk {
+ visibility: hidden;
+ display: none;
+ }
+ arrow-drop-down-icon-sk {
+ display: inline-block;
+ }
+ }
+
+ arrow-drop-down-icon-sk {
+ display: none;
+ }
+ arrow-drop-up-icon-sk {
+ visibility: hidden;
+ display: inline-block;
+ }
+}
diff --git a/infra-sk/modules/sort-sk/sort-sk_test.js b/infra-sk/modules/sort-sk/sort-sk_test.js
new file mode 100644
index 0000000..e941501
--- /dev/null
+++ b/infra-sk/modules/sort-sk/sort-sk_test.js
@@ -0,0 +1,72 @@
+import './index.js'
+import { $ } from 'common-sk/modules/dom'
+
+let container = document.createElement('div');
+document.body.appendChild(container);
+
+afterEach(function() {
+ container.innerHTML = "";
+});
+
+describe('sort-sk', function() {
+ it('sorts numerically by default', function() {
+ return window.customElements.whenDefined('sort-sk').then(() => {
+ container.innerHTML = `
+ <sort-sk target=stuffToBeSorted>
+ <button id=cluster data-key=clustersize data-default=up>Cluster Size</button>
+ <button id=size data-key=stepsize>Step Size</button>
+ </sort-sk>
+
+ <div id=stuffToBeSorted>
+ <pre data-clustersize=10 data-stepsize=1.2>Size=10 Step=1.2</pre>
+ <pre data-clustersize=50 data-stepsize=0.5>Size=50 Step=0.5</pre>
+ <pre data-clustersize=100 data-stepsize=0.6>Size=100 Step=0.6</pre>
+ </div>`;
+ const getValues = (name) => $('#stuffToBeSorted pre', container).map((ele) => +ele.dataset[name]);
+
+ const clusterButton = container.querySelector('#cluster');
+ const stepButton = container.querySelector('#size');
+
+ clusterButton.click();
+ assert.deepEqual([100, 50, 10], getValues('clustersize'), 'Defaults to up, so sort down on first click.');
+ clusterButton.click();
+ assert.deepEqual([10, 50, 100], getValues('clustersize'), 'Switch to up.');
+
+ stepButton.click();
+ assert.deepEqual([1.2, 0.6, 0.5], getValues('stepsize'), 'No default, so start sorting down.');
+ stepButton.click();
+ assert.deepEqual([0.5, 0.6, 1.2], getValues('stepsize'), 'Switch to up.');
+ })
+ });
+
+ it('sorts alphabetically with alpha attribute', function() {
+ return window.customElements.whenDefined('sort-sk').then(() => {
+ container.innerHTML = `
+ <sort-sk target=stuffToBeSorted2>
+ <button id=name data-key=name data-default=down data-sort-type=alpha>Name</button>
+ <button id=level data-key=level data-sort-type=alpha>Level</button>
+ </sort-sk>
+
+ <div id=stuffToBeSorted2>
+ <pre data-name=foo data-level=alpha>foo alpha</pre>
+ <pre data-name=baz data-level=beta >baz beta</pre>
+ <pre data-name=bar data-level=gamma>bar gamma</pre>
+ </div>
+ `;
+ const getValues = (name) => $('#stuffToBeSorted2 pre', container).map((ele) => ele.dataset[name]);
+
+ const nameButton = container.querySelector('#name');
+ const levelButton = container.querySelector('#level');
+
+ nameButton.click();
+ assert.deepEqual(['bar', 'baz', 'foo'], getValues('name'), 'Defaults to down, so sort up.');
+ nameButton.click();
+ assert.deepEqual(['foo', 'baz', 'bar'], getValues('name'), 'Now switch to down.');
+
+ levelButton.click();
+ assert.deepEqual(['gamma', 'beta', 'alpha'], getValues('level'), 'No default, so sort down.');
+ levelButton.click();
+ assert.deepEqual(['alpha', 'beta', 'gamma'], getValues('level'), 'Now switch to up.');
+ })
+ });
+});