veyron/examples/pipetobrowser: Supporting search (in message,
file path, threadid ), filtering (log levels, date),
sorting (all columns except message) for veyron log viewer.
Also adding ability to pause/resume the log viewer.
To support these features for veyron log viewer and other plugins,
a new reusable data grid component was created (libs/ui-components/data-grid)
data grid can host search, filters and supports sortable columns and custom
cell renderer
Please see documentation in (libs/ui-components/data-grid/grid/component.html)
for usage and other details.
still TODO: UI cleanup
Change-Id: I638e71dfac5246c4abb19da7c3b3a270eaf3fb87
diff --git a/examples/pipetobrowser/browser/libs/ui-components/blackhole/component.html b/examples/pipetobrowser/browser/libs/ui-components/blackhole/component.html
index 2b8a370..6ad8264 100644
--- a/examples/pipetobrowser/browser/libs/ui-components/blackhole/component.html
+++ b/examples/pipetobrowser/browser/libs/ui-components/blackhole/component.html
@@ -3,7 +3,7 @@
p2b-ui-components-blackhole is a simple image of a blackhole in the middle of a galaxy
when blackhole is running, a spinner gif is displayed in the middle of the blackhole.
-->
-<polymer-element name="p2b-ui-components-blackhole">
+<polymer-element name="p2b-blackhole">
<template>
<link rel="stylesheet" href="../common/common.css">
<link rel="stylesheet" href="component.css">
@@ -11,7 +11,7 @@
<cite class="attribution">Photo from Wikipedia <a target="_blank" href="http://en.wikipedia.org/wiki/Black_hole#mediaviewer/File:BH_LMC.png">by Alan R</a> (CC BY-SA 2.5)</cite>
</template>
<script>
- Polymer('p2b-ui-components-blackhole', {
+ Polymer('p2b-blackhole', {
/*
* Sets the blackhole in motion.
*/
diff --git a/examples/pipetobrowser/browser/libs/ui-components/data-grid/filter/select/component.css b/examples/pipetobrowser/browser/libs/ui-components/data-grid/filter/select/component.css
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/examples/pipetobrowser/browser/libs/ui-components/data-grid/filter/select/component.css
diff --git a/examples/pipetobrowser/browser/libs/ui-components/data-grid/filter/select/component.html b/examples/pipetobrowser/browser/libs/ui-components/data-grid/filter/select/component.html
new file mode 100644
index 0000000..f005eb3
--- /dev/null
+++ b/examples/pipetobrowser/browser/libs/ui-components/data-grid/filter/select/component.html
@@ -0,0 +1,67 @@
+<link rel="import" href="/libs/vendor/polymer/polymer/polymer.html">
+<link rel="import" href="/libs/vendor/polymer/paper-checkbox/paper-checkbox.html">
+<link rel="import" href="/libs/vendor/polymer/paper-radio-button/paper-radio-button.html">
+<link rel="import" href="/libs/vendor/polymer/paper-radio-group/paper-radio-group.html">
+<polymer-element name="p2b-grid-filter-select" attributes="multiple key" grid-filter expects-grid-state>
+ <template>
+ <link rel="stylesheet" href="component.css">
+ <content id="content" select="*"></content>
+ <div>
+ <template if="{{ multiple }}" bind>
+ <core-selector id="multiSelector" multi selectedAttribute="checked" valueattr="value" core-select="{{updateGridState}}" selected="{{ selected }}">
+ <template repeat="{{ item in items }}">
+ <paper-checkbox value="{{ item.value }}" on-change="{{ updateGridState }}" label="{{ item.label }}"></paper-checkbox>
+ </template>
+ </core-selector>
+ </template>
+
+ <template if="{{ !multiple }}" bind>
+ <paper-radio-group id="singleSelector" valueattr="value" selected="{{ selected }}">
+ <template repeat="{{ item in items }}">
+ <paper-radio-button value="{{ item.value }}" on-change="{{ updateGridState }}" label="{{ item.label }}"></paper-radio-button>
+ </template>
+ </paper-radio-group>
+ </template>
+ </div>
+ </template>
+ <script>
+ Polymer('p2b-grid-filter-select', {
+ /*
+ * Whether multiple items can be selected
+ * @type {boolean}
+ */
+ multiple: false,
+
+ /*
+ * Key that will be added to filters map passed to the fetch() function of your data source.
+ * @type {string}
+ */
+ key: '',
+
+ ready: function() {
+ // find the selected items from the child nodes
+ this.items = Array.prototype.slice.call(this.$.content.getDistributedNodes());
+ for(var i=0; i < this.items.length; i++){
+ if(this.items[i].checked) {
+ if(this.multiple) {
+ this.selected = this.selected || [];
+ this.selected.push(this.items[i].value);
+ } else {
+ this.selected = this.items[i].value;
+ }
+ }
+ }
+ },
+
+ updateGridState: function() {
+ if( this.multiple ) {
+ // quirk: we need to copy the array so change is observed. .slice() does that
+ this.gridState.filters[this.key] = this.$.multiSelector.selected.slice();
+ } else {
+ this.gridState.filters[this.key] = this.$.singleSelector.selected.slice();
+ }
+ }
+
+ });
+ </script>
+</polymer-element>
\ No newline at end of file
diff --git a/examples/pipetobrowser/browser/libs/ui-components/data-grid/filter/select/item/component.css b/examples/pipetobrowser/browser/libs/ui-components/data-grid/filter/select/item/component.css
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/examples/pipetobrowser/browser/libs/ui-components/data-grid/filter/select/item/component.css
diff --git a/examples/pipetobrowser/browser/libs/ui-components/data-grid/filter/select/item/component.html b/examples/pipetobrowser/browser/libs/ui-components/data-grid/filter/select/item/component.html
new file mode 100644
index 0000000..e338bd9
--- /dev/null
+++ b/examples/pipetobrowser/browser/libs/ui-components/data-grid/filter/select/item/component.html
@@ -0,0 +1,25 @@
+<link rel="import" href="/libs/vendor/polymer/polymer/polymer.html">
+<polymer-element name="p2b-grid-filter-select-item" attributes="label checked value">
+ <script>
+ Polymer('p2b-grid-filter-select-item', {
+ /*
+ * Label text for the item
+ * @type {string}
+ */
+ label: '',
+
+ /*
+ * Whether toggle is checked or not
+ * @type {boolean}
+ */
+ checked: false,
+
+ /*
+ * Value that will available as filters[key] in the fetch() function of your data source.
+ * Where key is the key of the select filter that contains this item
+ * @type {string}
+ */
+ value: ''
+ });
+ </script>
+</polymer-element>
\ No newline at end of file
diff --git a/examples/pipetobrowser/browser/libs/ui-components/data-grid/filter/toggle/component.css b/examples/pipetobrowser/browser/libs/ui-components/data-grid/filter/toggle/component.css
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/examples/pipetobrowser/browser/libs/ui-components/data-grid/filter/toggle/component.css
diff --git a/examples/pipetobrowser/browser/libs/ui-components/data-grid/filter/toggle/component.html b/examples/pipetobrowser/browser/libs/ui-components/data-grid/filter/toggle/component.html
new file mode 100644
index 0000000..70b8bfc
--- /dev/null
+++ b/examples/pipetobrowser/browser/libs/ui-components/data-grid/filter/toggle/component.html
@@ -0,0 +1,32 @@
+<link rel="import" href="/libs/vendor/polymer/polymer/polymer.html">
+<link rel="import" href="/libs/vendor/polymer/paper-toggle-button/paper-toggle-button.html">
+<polymer-element name="p2b-grid-filter-toggle" attributes="key checked label" grid-filter expects-grid-state>
+ <template>
+ <link rel="stylesheet" href="component.css">
+ <paper-toggle-button id="toggle" on-change="{{ updateGridState }}" checked?="{{ checked }}"></paper-toggle-button>
+ </template>
+ <script>
+ Polymer('p2b-grid-filter-toggle', {
+ /*
+ * Label text for the toggle filter
+ * @type {string}
+ */
+ label: '',
+
+ /*
+ * Whether toggle is checked or not
+ * @type {boolean}
+ */
+ checked: false,
+
+ /*
+ * Key that will be added to filters map passed to the fetch() function of your data source.
+ * @type {string}
+ */
+ key: '',
+ updateGridState: function() {
+ this.gridState.filters[this.key] = this.$.toggle.checked;
+ }
+ });
+ </script>
+</polymer-element>
\ No newline at end of file
diff --git a/examples/pipetobrowser/browser/libs/ui-components/data-grid/grid/cell/renderer.css b/examples/pipetobrowser/browser/libs/ui-components/data-grid/grid/cell/renderer.css
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/examples/pipetobrowser/browser/libs/ui-components/data-grid/grid/cell/renderer.css
diff --git a/examples/pipetobrowser/browser/libs/ui-components/data-grid/grid/cell/renderer.html b/examples/pipetobrowser/browser/libs/ui-components/data-grid/grid/cell/renderer.html
new file mode 100644
index 0000000..fd2a2cb
--- /dev/null
+++ b/examples/pipetobrowser/browser/libs/ui-components/data-grid/grid/cell/renderer.html
@@ -0,0 +1,14 @@
+<link rel="import" href="/libs/vendor/polymer/polymer/polymer.html">
+<polymer-element name="p2b-grid-cell-renderer" extends="td">
+ <template>
+ <link rel="stylesheet" href="renderer.css">
+ <content></content>
+ </template>
+ <script>
+ /*
+ * @private
+ */
+ Polymer('p2b-grid-cell-renderer', {
+ });
+ </script>
+</polymer-element>
diff --git a/examples/pipetobrowser/browser/libs/ui-components/data-grid/grid/column/component.html b/examples/pipetobrowser/browser/libs/ui-components/data-grid/grid/column/component.html
new file mode 100644
index 0000000..d2067a2
--- /dev/null
+++ b/examples/pipetobrowser/browser/libs/ui-components/data-grid/grid/column/component.html
@@ -0,0 +1,24 @@
+<link rel="import" href="/libs/vendor/polymer/polymer/polymer.html">
+<polymer-element name="p2b-grid-column" grid-column attributes="label sortable key">
+ <script>
+ Polymer('p2b-grid-column', {
+ /*
+ * Label text for the column
+ * @type {string}
+ */
+ label: '',
+
+ /*
+ * whether this column is sortable
+ * @type {boolean}
+ */
+ sortable: false,
+
+ /*
+ * Key that will be pass as sort.key to fetch() function of your data source.
+ * @type {string}
+ */
+ key: ''
+ });
+ </script>
+</polymer-element>
diff --git a/examples/pipetobrowser/browser/libs/ui-components/data-grid/grid/column/renderer.css b/examples/pipetobrowser/browser/libs/ui-components/data-grid/grid/column/renderer.css
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/examples/pipetobrowser/browser/libs/ui-components/data-grid/grid/column/renderer.css
diff --git a/examples/pipetobrowser/browser/libs/ui-components/data-grid/grid/column/renderer.html b/examples/pipetobrowser/browser/libs/ui-components/data-grid/grid/column/renderer.html
new file mode 100644
index 0000000..7b57b8a
--- /dev/null
+++ b/examples/pipetobrowser/browser/libs/ui-components/data-grid/grid/column/renderer.html
@@ -0,0 +1,32 @@
+<link rel="import" href="/libs/vendor/polymer/polymer/polymer.html">
+<polymer-element name="p2b-grid-column-renderer" extends="th" attributes="data gridState" expects-grid-state on-tap="{{ updateGridState }}">
+ <template>
+ <link rel="stylesheet" href="renderer.css">
+ <span> {{ data.label }}</span>
+ <template if="{{ data.sortable && gridState.sort.key == data.key }}">
+
+ <template if="{{ gridState.sort.ascending }}">
+ <span>▲</span>
+ </template>
+
+ <template if="{{ !gridState.sort.ascending }}">
+ <span>▼</span>
+ </template>
+ </template>
+
+ </template>
+ <script>
+ /*
+ * @private
+ */
+ Polymer('p2b-grid-column-renderer', {
+ updateGridState:function() {
+ if( !this.data.sortable ) {
+ return;
+ }
+ this.gridState.sort.ascending = !this.gridState.sort.ascending;
+ this.gridState.sort.key = this.data.key;
+ }
+ });
+ </script>
+</polymer-element>
diff --git a/examples/pipetobrowser/browser/libs/ui-components/data-grid/grid/component.css b/examples/pipetobrowser/browser/libs/ui-components/data-grid/grid/component.css
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/examples/pipetobrowser/browser/libs/ui-components/data-grid/grid/component.css
diff --git a/examples/pipetobrowser/browser/libs/ui-components/data-grid/grid/component.html b/examples/pipetobrowser/browser/libs/ui-components/data-grid/grid/component.html
new file mode 100644
index 0000000..178f529
--- /dev/null
+++ b/examples/pipetobrowser/browser/libs/ui-components/data-grid/grid/component.html
@@ -0,0 +1,230 @@
+<link rel="import" href="/libs/vendor/polymer/polymer/polymer.html">
+<link rel="import" href="/libs/ui-components/data-grid/grid/row/renderer.html">
+<link rel="import" href="/libs/ui-components/data-grid/grid/cell/renderer.html">
+<link rel="import" href="/libs/ui-components/data-grid/grid/column/renderer.html">
+
+<polymer-element name="p2b-grid" attributes="summary dataSource defaultSortKey defaultSortAscending">
+ <template>
+ <link rel="stylesheet" href="component.css">
+ <div id="templates"></div>
+ <div>
+ <content select="[grid-search]"></content>
+ </div>
+ <div>
+ <content select="[grid-filter]"></content>
+ </div>
+ <div>{{ dataSourceResult.length }} items</div>
+ <table summary="{{ summary }}">
+ <thead>
+ <tr>
+ <th is="p2b-grid-column-renderer" gridState="{{ gridState }}" data="{{ col.columnData }}" repeat="{{ col in columns }}" template></th>
+ </tr>
+ </thead>
+ <tbody>
+ <!-- quirk: Shadow Dom breaks parent-child relationships in HTML, this causes issues with
+ elements like table. Ideally we could have had <grid><grid-row><grid-cell> but we can't do
+ that yet since the tr and td rendered by <grid-row> <grid-cell> will be in shadow Dom and isolated.
+ Chromium bug: https://code.google.com/p/chromium/issues/detail?id=374315
+ W3C Spec bug: https://www.w3.org/Bugs/Public/show_bug.cgi?id=15616
+ -->
+ <tr is="p2b-grid-row-renderer" repeat="{{ item in dataSourceResult }}" template>
+ <td is="p2b-grid-cell-renderer" repeat="{{ col in columns }}" template>
+ <template ref="{{ col.cellTemplateId }}" bind></template>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ </template>
+ <script>
+ /*
+ * Reusable grid that can host search, filters and supports sortable columns and custom cell renderer
+ * @example usage:
+
+ <p2b-grid defaultSortKey="firstName"
+ defaultSortAscending
+ dataSource="{{ myContactsDataSource }}"
+ summary="Displays your contacts in a tabular format">
+
+ <!-- Search contacts-->
+ <p2b-grid-search label="Search Contacts"></p2b-grid-search>
+
+ <!-- Filter for circles -->
+ <p2b-grid-filter-select multiple key="circle" label="Circles">
+ <p2b-grid-filter-select-item checked label="Close Friends" value="close"></p2b-grid-filter-select-item>
+ <p2b-grid-filter-select-item label="Colleagues" value="far"></p2b-grid-filter-select-item>
+ </p2b-grid-filter-select>
+
+ <!-- Toggle to allow filtering by online mode-->
+ <p2b-grid-filter-toggle key="online" label="Show online only" checked></p2b-grid-filter-toggle>
+
+ <!-- Columns, sorting and cell templates -->
+ <p2b-grid-column sortable label="First Name" key="firstName" />
+ <template>{{ item.firstName }}</template>
+ </p2b-grid-column>
+
+ <p2b-grid-column sortable label="Last Name" key="lastName" />
+ <template>
+ <span style="text-transform:uppercase;">
+ {{ item.lastName }}
+ </span>
+ </template>
+ </p2b-grid-column>
+
+ <p2b-grid-column label="Circle" key="circle"/>
+ <template>
+ <img src="images\circls\{{ item.circle }}.jpg" alt="in {{ item.circle }} circle"><img>
+ </template>
+ </p2b-grid-column>
+
+ </p2b-grid>
+
+ * DataSource attribute expects an object that has a fetch(search, sort, filters) method. Please see
+ * documentation on DataSource property for details.
+ */
+ Polymer('p2b-grid', {
+ /*
+ * DataSource is an object that has a fetch(search, sort, filters) method where
+ * search{key<string>} is current search keyword
+ * sort{key<string>, ascending<bool>} current sort key and direction
+ * filter{map{key<string>, values<Array>}} Map of filter keys to currently selected filter values
+ * search, sort and filters are provided by the grid control whenever they are changed by the user.
+ * DataSource is called automatically by the grid when user interacts with the component
+ * Grid does some batching of user actions and only calls fetch when needed in a requestAnimationFrame
+ * Keys provided for sort and filters correspond to keys set in the markup when constructing the grid.
+ * DataSource.fetch() is expected to return an array of filtered sorted results of the items.
+ */
+ dataSource: null,
+
+ /*
+ * Summary for the grid.
+ * @type {string}
+ */
+ summary: '',
+
+ /*
+ * Initial sort key
+ * @type {string}
+ */
+ defaultSortKey: '',
+
+ /*
+ * Initial sort direction
+ * @type {string}
+ */
+ defaultSortAscending: false,
+
+ ready: function() {
+
+ // private property fields
+ this.columns = [];
+ this.shouldRefetchData = true;
+ this.dataSource = null;
+ this.cachedDataSourceResult = [];
+ this.gridState = {
+ sort: {
+ key: '',
+ ascending: false
+ },
+ search: {
+ keyword: ''
+ },
+ filters: {}
+ },
+
+ // set the default sort and direction on the state object
+ this.gridState.sort.key = this.defaultSortKey;
+ this.gridState.sort.ascending = this.defaultSortAscending;
+
+ this.initTemplates(); // loads cell templates
+ this.initGridStateDependents(); // initialize filters and search
+ this.initGridStateObserver(); // observe changes to grid state by filters
+ },
+
+ dataSourceChanged: function() {
+ this.refresh();
+ },
+
+ /*
+ * Sets up an object observer to get any mutations on the grid state object.
+ * Filters or sortable columns can change the state and we like to refresh
+ * when changes happens.
+ * @private
+ */
+ initGridStateObserver: function() {
+ var self = this;
+ for (key in this.gridState) {
+ var observer = new ObjectObserver(this.gridState[key])
+ observer.open(function() {
+ // refresh the grid on any mutations
+ self.refresh();
+ });
+ }
+ },
+
+ /*
+ * Copies the cell templates as defined by the user for each column into
+ * the grid so that we can reference them in a loop.
+ * quirk: Need to reference them by Id so a new Id is generated for each one
+ * Ids are scoped in the shadow DOM so no collisions.
+ * @private
+ */
+ initTemplates: function() {
+ var columnNodes = this.querySelectorAll('[grid-column]');
+ for (var i = 0; i < columnNodes.length; i++) {
+ var col = columnNodes[i];
+ var cellTemplate = col.querySelector('template');
+ var cellTemplateId = "userProvidedCellTemplate" + i;
+ cellTemplate.id = cellTemplateId;
+ this.$.templates.appendChild(cellTemplate);
+ this.columns.push({
+ cellTemplateId: cellTemplateId,
+ columnData: col
+ });
+ }
+ },
+
+ /*
+ * Provide the grid state to any component that expects it so they can mutate
+ * without the grid needing to know about them at all.
+ * @private
+ */
+ initGridStateDependents: function() {
+ var gridStateDependents = this.querySelectorAll('[expects-grid-state]');
+ for (var i = 0; i < gridStateDependents.length; i++) {
+ gridStateDependents[i].gridState = this.gridState;
+ }
+ },
+
+ /*
+ * Refreshed the grid by fetching the data again and updating the UI in the next render tick
+ */
+ refresh: function() {
+ var self = this;
+ requestAnimationFrame(function() {
+ self.shouldRefetchData = true;
+ });
+ },
+
+ /*
+ * dataSourceResult is what the UI binds to and integrate over.
+ * Only fetches data if scheduled to do so
+ * @private
+ */
+ get dataSourceResult() {
+ if (!this.shouldRefetchData || !this.dataSource) {
+ return this.cachedDataSourceResult;
+ }
+
+ this.cachedDataSourceResult = this.dataSource.fetch(
+ this.gridState.search,
+ this.gridState.sort,
+ this.gridState.filters
+ );
+
+ this.shouldRefetchData = false;
+
+ return this.cachedDataSourceResult;
+ }
+ });
+ </script>
+</polymer-element>
diff --git a/examples/pipetobrowser/browser/libs/ui-components/data-grid/grid/row/renderer.css b/examples/pipetobrowser/browser/libs/ui-components/data-grid/grid/row/renderer.css
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/examples/pipetobrowser/browser/libs/ui-components/data-grid/grid/row/renderer.css
diff --git a/examples/pipetobrowser/browser/libs/ui-components/data-grid/grid/row/renderer.html b/examples/pipetobrowser/browser/libs/ui-components/data-grid/grid/row/renderer.html
new file mode 100644
index 0000000..e6ad86f
--- /dev/null
+++ b/examples/pipetobrowser/browser/libs/ui-components/data-grid/grid/row/renderer.html
@@ -0,0 +1,14 @@
+<link rel="import" href="/libs/vendor/polymer/polymer/polymer.html">
+<polymer-element name="p2b-grid-row-renderer" extends="tr">
+ <template>
+ <link rel="stylesheet" href="renderer.css">
+ <content></content>
+ </template>
+ <script>
+ /*
+ * @private
+ */
+ Polymer('p2b-grid-row-renderer', {
+ });
+ </script>
+</polymer-element>
diff --git a/examples/pipetobrowser/browser/libs/ui-components/data-grid/search/component.css b/examples/pipetobrowser/browser/libs/ui-components/data-grid/search/component.css
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/examples/pipetobrowser/browser/libs/ui-components/data-grid/search/component.css
diff --git a/examples/pipetobrowser/browser/libs/ui-components/data-grid/search/component.html b/examples/pipetobrowser/browser/libs/ui-components/data-grid/search/component.html
new file mode 100644
index 0000000..3ede3b1
--- /dev/null
+++ b/examples/pipetobrowser/browser/libs/ui-components/data-grid/search/component.html
@@ -0,0 +1,23 @@
+<link rel="import" href="/libs/vendor/polymer/polymer/polymer.html">
+<link rel="import" href="/libs/vendor/polymer/paper-input/paper-input.html">
+<polymer-element name="p2b-grid-search" grid-search expects-grid-state attributes="label">
+ <template>
+ <link rel="stylesheet" href="component.css">
+ <paper-input on-change="{{ updateGridState }}" id="search" label="{{ label }}"></paper-input>
+ </template>
+ <script>
+ /*
+ * Renders a search box inside the grid components
+ */
+ Polymer('p2b-grid-search', {
+ /*
+ * Label text for the search
+ * @type {string}
+ */
+ label: '',
+ updateGridState: function() {
+ this.gridState.search.keyword = this.$.search.value.trim();
+ }
+ });
+ </script>
+</polymer-element>
diff --git a/examples/pipetobrowser/browser/pipe-viewers/builtin/console/component.html b/examples/pipetobrowser/browser/pipe-viewers/builtin/console/component.html
index 176c0bd..1b3732c 100644
--- a/examples/pipetobrowser/browser/pipe-viewers/builtin/console/component.html
+++ b/examples/pipetobrowser/browser/pipe-viewers/builtin/console/component.html
@@ -17,4 +17,4 @@
}
});
</script>
-</polymer-element>
\ No newline at end of file
+</polymer-element>
diff --git a/examples/pipetobrowser/browser/pipe-viewers/builtin/dev/null/plugin.js b/examples/pipetobrowser/browser/pipe-viewers/builtin/dev/null/plugin.js
index baa2c8f..06546dc 100644
--- a/examples/pipetobrowser/browser/pipe-viewers/builtin/dev/null/plugin.js
+++ b/examples/pipetobrowser/browser/pipe-viewers/builtin/dev/null/plugin.js
@@ -15,7 +15,7 @@
play(stream) {
- var blackhole = document.createElement('p2b-ui-components-blackhole');
+ var blackhole = document.createElement('p2b-blackhole');
blackhole.start();
stream.on('data', () => {
diff --git a/examples/pipetobrowser/browser/pipe-viewers/builtin/vlog/component.css b/examples/pipetobrowser/browser/pipe-viewers/builtin/vlog/component.css
index e99e5f8..b0b01e3 100644
--- a/examples/pipetobrowser/browser/pipe-viewers/builtin/vlog/component.css
+++ b/examples/pipetobrowser/browser/pipe-viewers/builtin/vlog/component.css
@@ -1,24 +1,7 @@
-tr.error {
- background-color: #FF9700;
-}
-
-tr.fatal {
- background-color: #DA4336;
-}
-
-tr.warning {
- background-color: #FFEA3A;
-}
-
-tr.info {
- background-color: #FAFAFA;
-}
-
-.lineNumber::before {
+::shadow /deep/ .lineNumber::before {
content: '#';
}
-
-.lineNumber {
+::shadow /deep/ .lineNumber {
color: rgb(51, 103, 214);
margin-left: 0.1em;
-}
\ No newline at end of file
+}
diff --git a/examples/pipetobrowser/browser/pipe-viewers/builtin/vlog/component.html b/examples/pipetobrowser/browser/pipe-viewers/builtin/vlog/component.html
index d1a0fb7..7f31ef6 100644
--- a/examples/pipetobrowser/browser/pipe-viewers/builtin/vlog/component.html
+++ b/examples/pipetobrowser/browser/pipe-viewers/builtin/vlog/component.html
@@ -1,39 +1,63 @@
<link rel="import" href="/libs/vendor/polymer/polymer/polymer.html">
+<link rel="import" href="/libs/ui-components/data-grid/grid/component.html">
+<link rel="import" href="/libs/ui-components/data-grid/grid/column/component.html">
+<link rel="import" href="/libs/ui-components/data-grid/filter/select/component.html">
+<link rel="import" href="/libs/ui-components/data-grid/filter/select/item/component.html">
+<link rel="import" href="/libs/ui-components/data-grid/filter/toggle/component.html">
+<link rel="import" href="/libs/ui-components/data-grid/search/component.html">
<polymer-element name="p2b-plugin-vlog">
<template>
<link rel="stylesheet" href="component.css">
- <table summary="Data Grid displaying veyron log items in a tabular format.">
- <thead>
- <tr>
- <th>Log Level</th>
- <th>File</th>
- <th>Message</th>
- <th>Date</th>
- <th>ThreadId</th>
- </tr>
- </thead>
- <tbody>
- <template repeat="{{ item in logItems }}">
- <tr class="{{ item.level }}">
- <td>{{ item.level }}</td>
- <th scope="row">{{ item.file }}<span class="lineNumber">{{ item.fileLine }}<span/></th>
- <td>{{ item.message }}</td>
- <td>{{ item.date }}</td>
- <td>{{ item.threadId }}</td>
- </tr>
- </template>
- </tbody>
- </table>
+ <p2b-grid id="grid" defaultSortKey="date" defaultSortAscending dataSource="{{ dataSource }}" summary="Data Grid displaying veyron log items in a tabular format with filters and search options.">
+
+ <!-- Search -->
+ <p2b-grid-search label="Search Logs"></p2b-grid-search>
+
+ <!-- Filter to select log level (multiple allowed) -->
+ <p2b-grid-filter-select multiple key="level" label="Levels">
+ <p2b-grid-filter-select-item checked label="Fatal" value="fatal"></p2b-grid-filter-select-item>
+ <p2b-grid-filter-select-item checked label="Error" value="error"></p2b-grid-filter-select-item>
+ <p2b-grid-filter-select-item checked label="Warning" value="warning"></p2b-grid-filter-select-item>
+ <p2b-grid-filter-select-item checked label="Info" value="info"></p2b-grid-filter-select-item>
+ </p2b-grid-filter-select>
+
+ <!-- Filter to select date range -->
+ <p2b-grid-filter-select key="date" label="Date">
+ <p2b-grid-filter-select-item checked label="Any time" value="all"></p2b-grid-filter-select-item>
+ <p2b-grid-filter-select-item label="Past 1 hour" value="1"></p2b-grid-filter-select-item>
+ <p2b-grid-filter-select-item label="Past 24 hours" value="24"></p2b-grid-filter-select-item>
+ </p2b-grid-filter-select>
+
+ <!-- Toggle to allow one to pause the display of incoming logs -->
+ <p2b-grid-filter-toggle key="autorefresh" label="Live Refresh" checked></p2b-grid-filter-toggle>
+
+ <!-- Columns, sorting and cell templates -->
+ <p2b-grid-column sortable label="Level" key="level">
+ <template>{{ item.level }}</template>
+ </p2b-grid-column>
+ <p2b-grid-column sortable label="File" key="file">
+ <template>{{ item.file }}<span class="lineNumber">{{ item.fileLine }}</template>
+ </p2b-grid-column>
+ <p2b-grid-column label="Message" key="message">
+ <template>{{ item.message }}</template>
+ </p2b-grid-column>
+ <p2b-grid-column sortable label="Date" key="date">
+ <template>{{ item.date }}</template>
+ </p2b-grid-column>
+ <p2b-grid-column sortable label="threadid" key="threadid">
+ <template>{{ item.threadid }}</template>
+ </p2b-grid-column>
+
+ </p2b-grid>
</template>
+
<script>
Polymer('p2b-plugin-vlog', {
- /*
- * List of log items to display
- * @type {object}
- */
- logItems: []
+ refreshGrid: function() {
+ this.$.grid.refresh();
+ }
});
- </script>
-</polymer-element>
\ No newline at end of file
+ </script>
+</polymer-element>
diff --git a/examples/pipetobrowser/browser/pipe-viewers/builtin/vlog/data-source.js b/examples/pipetobrowser/browser/pipe-viewers/builtin/vlog/data-source.js
new file mode 100644
index 0000000..be602c0
--- /dev/null
+++ b/examples/pipetobrowser/browser/pipe-viewers/builtin/vlog/data-source.js
@@ -0,0 +1,87 @@
+/*
+ * Implement the data source which handles parsing the stream, searching, filtering
+ * and sorting of the vLog items.
+ * @fileoverview
+ */
+import { parse } from './parser';
+import { vLogSort } from './sorter';
+import { vLogSearch } from './searcher';
+import { vLogFilter } from './filterer';
+
+export class vLogDataSource {
+ constructor(stream, onNewItem, onError) {
+
+ /*
+ * all logs, unlimited buffer for now.
+ * @private
+ */
+ this.allLogItems = [];
+
+ /*
+ * Raw stream of log data
+ * @private
+ */
+ this.stream = stream;
+
+ stream.on('data', (line) => {
+ if (line.trim() === '') {
+ return;
+ }
+ var logItem = null;
+ // try to parse and display as much as we can.
+ try {
+ logItem = parse(line);
+ } catch (e) {
+ if (onError) {
+ onError(e);
+ }
+ }
+
+ if (logItem) {
+ this.allLogItems.push(logItem);
+ if (onNewItem) {
+ onNewItem(logItem);
+ }
+ }
+ });
+
+ }
+
+ /*
+ * Implements the fetch method expected by the grid components.
+ * handles parsing the stream, searching, filtering and sorting of the data.
+ * search, sort and filters are provided by the grid control whenever they are
+ * changed by the user.
+ * DataSource is called automatically by the grid when user interacts with the component
+ * Grid does some batching of user actions and only calls fetch when needed.
+ * keys provided for sort and filters correspond to keys set in the markup
+ * when constructing the grid.
+ * @param {object} search search{key<string>} current search keyword
+ * @param {object} sort sort{key<string>, ascending<bool>} current sort key and direction
+ * @param {map} filters map{key<string>, values<Array>} Map of filter keys to currently selected filter values
+ * @return {Array<object>} Returns an array of filtered sorted results of the items.
+ */
+ fetch(search, sort, filters) {
+
+ var filteredSortedItems = this.allLogItems.
+ filter((item) => {
+ return vLogFilter(item, filters);
+ }).
+ filter((item) => {
+ return vLogSearch(item, search.keyword);
+ }).
+ sort((item1, item2) => {
+ return vLogSort(item1, item2, sort.key, sort.ascending);
+ });
+
+ // pause or resume the stream when auto-refresh filter changes
+ if (filters['autorefresh'] && this.stream.paused) {
+ this.stream.resume();
+ }
+ if (!filters['autorefresh'] && !this.stream.paused) {
+ this.stream.pause();
+ }
+
+ return filteredSortedItems;
+ }
+}
\ No newline at end of file
diff --git a/examples/pipetobrowser/browser/pipe-viewers/builtin/vlog/filterer.js b/examples/pipetobrowser/browser/pipe-viewers/builtin/vlog/filterer.js
new file mode 100644
index 0000000..e40ebb0
--- /dev/null
+++ b/examples/pipetobrowser/browser/pipe-viewers/builtin/vlog/filterer.js
@@ -0,0 +1,73 @@
+/*
+ * Returns whether the given vLogItem matches the map of filters.
+ * @param {Object} item A single veyron log item as defined by parser.item
+ * @param {map} filters Map of keys to selected filter values as defined
+ * when constructing the filters in the grid components.
+ * e.g. filters:{'date':'all', 'levels':['info','warning']}
+ * @return {boolean} Whether the item satisfies ALL of the given filters.
+ */
+export function vLogFilter(item, filters) {
+ if (Object.keys(filters).length === 0) {
+ return true;
+ }
+
+ for (var key in filters) {
+ var isMatch = applyFilter(item, key, filters[key]);
+ // we AND all the filters, short-circuit for early termination
+ if (!isMatch) {
+ return false;
+ }
+ }
+
+ // matches all filters
+ return true;
+};
+
+/*
+ * Returns whether the given vLogItem matches a single filter
+ * @param {Object} item A single veyron log item as defined by parser.item
+ * @param {string} key filter key e.g. 'date'
+ * @param {string} value filter value e.g. 'all'
+ * @return {boolean} Whether the item satisfies then the given filter key value pair
+ * @private
+ */
+function applyFilter(item, key, value) {
+ switch (key) {
+ case 'date':
+ return filterDate(item, value);
+ case 'level':
+ return filterLevel(item, value);
+ default:
+ // ignore unknown filters
+ return true;
+ }
+}
+
+/*
+ * Returns whether item's date satisfies the date filter
+ * @param {Object} item A single veyron log item as defined by parser.item
+ * @param {string} since One of 'all', '1' or '24'. for Anytime, past one hour, past 24 hours.
+ * @return {boolean} whether item's date satisfies the date filter
+ * @private
+ */
+function filterDate(item, since) {
+ if (since === 'all') {
+ return true;
+ } else {
+ var hours = parseInt(since);
+ var targetDate = new Date();
+ targetDate.setHours(targetDate.getHours() - hours);
+ return item.date > targetDate;
+ }
+}
+
+/*
+ * Returns whether item's level is one of the given values
+ * @param {Object} item A single veyron log item as defined by parser.item
+ * @param {Array<string>} levels Array of level values e.g. ['info','warning']
+ * @return {boolean} whether item's level is one of the given values
+ * @private
+ */
+function filterLevel(item, levels) {
+ return levels.indexOf(item.level) >= 0;
+}
diff --git a/examples/pipetobrowser/browser/pipe-viewers/builtin/vlog/parser.js b/examples/pipetobrowser/browser/pipe-viewers/builtin/vlog/parser.js
index 340b8af..d28e108 100644
--- a/examples/pipetobrowser/browser/pipe-viewers/builtin/vlog/parser.js
+++ b/examples/pipetobrowser/browser/pipe-viewers/builtin/vlog/parser.js
@@ -17,16 +17,15 @@
* file The file name
* line The line number
* msg The user-supplied message
- * @param {string} vlogLine A single line of
- * by git status --short command
+ * @param {string} vlogLine A single line of veyron log
* @return {parser.item} A parsed object containing log level, date, file,
* line number, thread id and message.
*/
export function parse(vlogLine) {
- var validLogLineRegEx = /^([IWEF])(\d{2})(\d{2})\s(\d{2}:\d{2}:\d{2}\.\d+)\s(\d+)\s(.*):(\d+)]\s+(.*)$/
+ var validLogLineRegEx = /^([IWEF])(\d{2})(\d{2})\s(\d{2}:\d{2}:\d{2}\.\d+)\s(\d+)\s(.*):(\d+)]\s+(.*)$/
var logParts = vlogLine.match(validLogLineRegEx);
- if(!logParts || logParts.length != 8+1) { // 8 parts + 1 whole match
+ if (!logParts || logParts.length != 8 + 1) { // 8 parts + 1 whole match
throw new Error('Invalid vlog line format. ' + vlogLine +
' Lmmdd hh:mm:ss.uuuuuu threadid file:line] msg.. pattern');
}
@@ -44,7 +43,7 @@
var year = now.getFullYear();
var thisMonth = now.getMonth() + 1; // JS months are 0-11
// Year flip edge case, if log month > this month, we assume log line is from previous year
- if(parseInt(month) > thisMonth) {
+ if (parseInt(month) > thisMonth) {
year--;
}
diff --git a/examples/pipetobrowser/browser/pipe-viewers/builtin/vlog/plugin.js b/examples/pipetobrowser/browser/pipe-viewers/builtin/vlog/plugin.js
index e7dc5af..28ff69c 100644
--- a/examples/pipetobrowser/browser/pipe-viewers/builtin/vlog/plugin.js
+++ b/examples/pipetobrowser/browser/pipe-viewers/builtin/vlog/plugin.js
@@ -8,8 +8,10 @@
*/
import { View } from 'view';
import { PipeViewer } from 'pipe-viewer';
+import { vLogDataSource } from './data-source';
+import { Logger } from 'logger'
-import { parse } from './parser';
+var log = new Logger('pipe-viewers/builtin/vlog');
var streamUtil = require('event-stream');
@@ -23,17 +25,24 @@
// split by new line
stream = stream.pipe(streamUtil.split(/\r?\n/));
-
- var logItems = [];
var logView = document.createElement('p2b-plugin-vlog');
- logView.logItems = logItems;
- stream.on('data', (line) => {
- // try to parse and display as much as we can.
- try {
- logItems.push( parse(line) );
- } catch(e) {}
+ // create a new data source from the stream and set it.
+ logView.dataSource = new vLogDataSource(
+ stream,
+ function onNewItem() {
+ // also refresh the grid when new data comes in.
+ // grid component batches requests and refreshes UI on the next animation frame.
+ logView.refreshGrid();
+ },
+ function onError(err) {
+ log.debug(err);
+ });
+ // also refresh the grid when new data comes in.
+ // grid component batches requests and refreshes UI on the next animation frame.
+ stream.on('data', () => {
+ logView.refreshGrid();
});
return new View(logView);
diff --git a/examples/pipetobrowser/browser/pipe-viewers/builtin/vlog/searcher.js b/examples/pipetobrowser/browser/pipe-viewers/builtin/vlog/searcher.js
new file mode 100644
index 0000000..35e346c
--- /dev/null
+++ b/examples/pipetobrowser/browser/pipe-viewers/builtin/vlog/searcher.js
@@ -0,0 +1,14 @@
+export function vLogSearch(logItem, keyword) {
+ if (!keyword) {
+ return true;
+ }
+
+ // we do a contains for message, file and threadId fields only
+ if (logItem.message.indexOf(keyword) >= 0 ||
+ logItem.file.indexOf(keyword) >= 0 ||
+ logItem.threadId.toString().indexOf(keyword) >= 0) {
+ return true
+ }
+
+ return false;
+};
\ No newline at end of file
diff --git a/examples/pipetobrowser/browser/pipe-viewers/builtin/vlog/sorter.js b/examples/pipetobrowser/browser/pipe-viewers/builtin/vlog/sorter.js
new file mode 100644
index 0000000..f2663f4
--- /dev/null
+++ b/examples/pipetobrowser/browser/pipe-viewers/builtin/vlog/sorter.js
@@ -0,0 +1,14 @@
+export function vLogSort(logItem1, logItem2, key, ascending) {
+ var first = logItem1;
+ var second = logItem2;
+ if (!ascending) {
+ first = logItem2;
+ second = logItem1;
+ }
+
+ if (typeof first[key] === 'string') {
+ return first[key].localeCompare(second[key]);
+ } else {
+ return first[key] - second[key];
+ }
+};
\ No newline at end of file
diff --git a/examples/pipetobrowser/browser/runtime/init.js b/examples/pipetobrowser/browser/runtime/init.js
index 281a0d2..b5b1cc3 100644
--- a/examples/pipetobrowser/browser/runtime/init.js
+++ b/examples/pipetobrowser/browser/runtime/init.js
@@ -9,7 +9,8 @@
'*': '*.js',
'pipe-viewer': 'pipe-viewers/pipe-viewer.js',
'pipe-viewer-delegation': 'pipe-viewers/pipe-viewer-delegation.js',
- 'view': 'libs/mvc/view.js'
+ 'view': 'libs/mvc/view.js',
+ 'logger': 'libs/logs/logger.js'
};
System.import('runtime/app').then(function(app) {
diff --git a/examples/pipetobrowser/browser/views/publish/view.js b/examples/pipetobrowser/browser/views/publish/view.js
index 3318d51..bc3843f 100644
--- a/examples/pipetobrowser/browser/views/publish/view.js
+++ b/examples/pipetobrowser/browser/views/publish/view.js
@@ -23,3 +23,56 @@
});
}
}
+
+
+
+/*veyron/examples/pipetobrowser: Supporting search (in message, file path, threadid),
+filtering (log levels, date), sorting (all columns except message) for
+veyron log viewer. Also adding ability to pause/resume the log viewer.
+
+To support these features for veyron log viewer and other plugins,
+a new reusable data grid component was created (libs/ui-components/data-grid)
+data grid can host search, filters and supports sortable columns and custom cell renderer
+example usage:
+
+ <p2b-grid defaultSortKey="firstName"
+ defaultSortAscending
+ dataSource="{{ myContactsDataSource }}"
+ summary="Displays your contacts in a tabular format">
+
+ <!-- Search contacts-->
+ <p2b-grid-search label="Search Contacts"></p2b-grid-search>
+
+ <!-- Filter for circles -->
+ <p2b-grid-filter-select multiple key="circle" label="Circles">
+ <p2b-grid-filter-select-item checked label="Close Friends" value="close"></p2b-grid-filter-select-item>
+ <p2b-grid-filter-select-item label="Colleagues" value="far"></p2b-grid-filter-select-item>
+ </p2b-grid-filter-select>
+
+ <!-- Toggle to allow filtering by online mode-->
+ <p2b-grid-filter-toggle key="online" label="Show online only" checked></p2b-grid-filter-toggle>
+
+ <!-- Columns, sorting and cell templates -->
+ <p2b-grid-column sortable label="First Name" key="firstName" />
+ <template>{{ item.firstName }}</template>
+ </p2b-grid-column>
+
+ <p2b-grid-column sortable label="Last Name" key="lastName" />
+ <template>
+ <span style="text-transform:uppercase;">
+ {{ item.lastName }}
+ </span>
+ </template>
+ </p2b-grid-column>
+
+ <p2b-grid-column label="Circle" key="circle"/>
+ <template>
+ <img src="images\circls\{{ item.circle }}.jpg" alt="in {{ item.circle }} circle"><img>
+ </template>
+ </p2b-grid-column>
+
+</p2b-grid>
+Please see documentation in (libs/ui-components/data-grid/grid/component.html) for more details.
+
+still TODO: UI cleanup
+*/