Merge "veyron/examples/pipetobrowser: Search, filter and sort for the Git Status plugin"
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
index 8a2cebe..34574cf 100644
--- a/examples/pipetobrowser/browser/libs/ui-components/data-grid/grid/component.css
+++ b/examples/pipetobrowser/browser/libs/ui-components/data-grid/grid/component.css
@@ -70,4 +70,16 @@
font-size: 0.8em;
color: #616161;
float: right;
+}
+
+.info-column {
+ text-align: center;
+}
+
+[moreInfoOnly] {
+ display: none;
+}
+
+.more-dialog-content [moreInfoOnly] {
+ display: initial;
}
\ No newline at end of file
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
index 526e44e..3ff76c0 100644
--- a/examples/pipetobrowser/browser/libs/ui-components/data-grid/grid/component.html
+++ b/examples/pipetobrowser/browser/libs/ui-components/data-grid/grid/component.html
@@ -42,7 +42,7 @@
<td is="p2b-grid-cell-renderer" data="{{ col.columnData }}" repeat="{{ col in columns }}" template>
<template ref="{{ col.cellTemplateId }}" bind></template>
</td>
- <td>
+ <td class="info-column">
<paper-icon-button on-tap="{{ showMoreInfo }}" class="more-icon" icon="more-vert" title="more info"></paper-icon-button
>
</td>
@@ -250,7 +250,7 @@
var previousTableWidth = self.$.table.offsetWidth;
onResizeHandler = function() {
var newWidth = self.$.table.offsetWidth;
- if (newWidth != previousTableWidth) {
+ if (newWidth != previousTableWidth && newWidth > 0) {
self.adjustFlexWidths();
}
previousTableWidth = newWidth;
diff --git a/examples/pipetobrowser/browser/pipe-viewers/builtin/git/status/component.css b/examples/pipetobrowser/browser/pipe-viewers/builtin/git/status/component.css
index bc9c2b3..9b266fe 100644
--- a/examples/pipetobrowser/browser/pipe-viewers/builtin/git/status/component.css
+++ b/examples/pipetobrowser/browser/pipe-viewers/builtin/git/status/component.css
@@ -1,19 +1,28 @@
-tr.notstaged {
- background-color: #FF9700;
+::shadow /deep/ .state-icon.notstaged {
+ fill: #f57c00;
}
-tr.conflicted {
- background-color: #DA4336;
+::shadow /deep/ .state-icon.staged {
+ fill: #689f38;
}
-tr.untracked {
- background-color: #FFEA3A;
+::shadow /deep/ .state-icon.conflicted {
+ fill: #e51c23;
}
-tr.ignored {
- background-color: #E0E0E0;
+::shadow /deep/ .state-icon.untracked {
+ fill: #bf360c;
}
-tr.staged {
- background-color: #8AC249;
-}
\ No newline at end of file
+::shadow /deep/ .state-icon.ignored {
+ fill: #bf360c;
+}
+
+::shadow /deep/ .action-icon {
+ fill: #91a7ff;
+}
+
+::shadow /deep/ .file-parent {
+ font-size: 0.8em;
+ color: rgba(0,0,0,.54);
+}
diff --git a/examples/pipetobrowser/browser/pipe-viewers/builtin/git/status/component.html b/examples/pipetobrowser/browser/pipe-viewers/builtin/git/status/component.html
index 2c517ba..05cd8d2 100644
--- a/examples/pipetobrowser/browser/pipe-viewers/builtin/git/status/component.html
+++ b/examples/pipetobrowser/browser/pipe-viewers/builtin/git/status/component.html
@@ -1,37 +1,68 @@
<link rel="import" href="/libs/vendor/polymer/polymer/polymer.html">
+<link rel="import" href="/libs/vendor/polymer/core-icon/core-icon.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-git-status">
<template>
- <link rel="stylesheet" href="component.css">
- <table summary="Data Grid displaying status of modified file for the git repository.">
- <thead>
- <tr>
- <th>Status</th>
- <th>Action</th>
- <th>File</th>
- <th>Summary</th>
- </tr>
- </thead>
- <tbody>
- <template repeat="{{ item in statusItems }}">
- <tr class="{{ item.fileState }}">
- <td>{{ item.fileState }}</td>
- <td>{{ item.fileAction }}</td>
- <th scope="row">{{ item.filePath }}</th>
- <td>{{ item.summary }}</td>
- </tr>
- </template>
- </tbody>
- </table>
+ <link rel="stylesheet" href="/libs/css/common-style.css">
+ <link rel="stylesheet" href="component.css">
+
+ <p2b-grid id="grid" defaultSortKey="state" defaultSortAscending dataSource="{{ dataSource }}" summary="Data Grid displaying status of modified file for the git repository.">
+
+ <!-- Search -->
+ <p2b-grid-search label="Search Logs"></p2b-grid-search>
+
+ <!-- State Filter (multiple allowed) -->
+ <p2b-grid-filter-select multiple key="state" label="Show state">
+ <p2b-grid-filter-select-item checked label="Staged" value="staged"></p2b-grid-filter-select-item>
+ <p2b-grid-filter-select-item checked label="Not Staged" value="notstaged"></p2b-grid-filter-select-item>
+ <p2b-grid-filter-select-item checked label="Conflicted" value="conflicted"></p2b-grid-filter-select-item>
+ <p2b-grid-filter-select-item checked label="Untracked" value="untracked"></p2b-grid-filter-select-item>
+ <p2b-grid-filter-select-item checked label="Ignored" value="ignored"></p2b-grid-filter-select-item>
+ </p2b-grid-filter-select>
+
+ <!-- Action Filter (multiple allowed) -->
+ <p2b-grid-filter-select multiple key="action" label="Show actions">
+ <p2b-grid-filter-select-item checked label="Added" value="added"></p2b-grid-filter-select-item>
+ <p2b-grid-filter-select-item checked label="Deleted" value="deleted"></p2b-grid-filter-select-item>
+ <p2b-grid-filter-select-item checked label="Modified" value="modified"></p2b-grid-filter-select-item>
+ <p2b-grid-filter-select-item checked label="Renamed" value="renamed"></p2b-grid-filter-select-item>
+ <p2b-grid-filter-select-item checked label="Copied" value="copied"></p2b-grid-filter-select-item>
+ <p2b-grid-filter-select-item checked label="Unknown" value="unknown"></p2b-grid-filter-select-item>
+ </p2b-grid-filter-select>
+
+ <!-- Columns, sorting and cell templates -->
+ <p2b-grid-column label="State" key="state" sortable flex="2" priority="2" >
+ <template>
+ <core-icon class="state-icon {{ item.state }}" icon="{{ item.stateIcon }}" title="{{item.state}}"></core-icon>
+ <span moreInfoOnly style="vertical-align:middle">{{item.state}}</span>
+ </template>
+ </p2b-grid-column>
+ <p2b-grid-column label="Action" key="action" sortable flex="2" priority="3" >
+ <template>
+ <core-icon class="action-icon {{ item.action }}" icon="{{ item.actionIcon }}" title="{{item.action}}"></core-icon>
+ <span moreInfoOnly style="vertical-align:middle">{{item.action}}</span>
+ </template>
+ </p2b-grid-column>
+ <p2b-grid-column label="File" key="filename" sortable primary flex="8" minFlex="5" priority="1" >
+ <template>{{ item.filename }}
+ <div class="file-parent" title="folder: {{item.fileParent}}">{{ item.fileParent }}</div>
+ </template>
+ </p2b-grid-column>
+ <p2b-grid-column label="Summary" flex="7" minFlex="3" priority="4" >
+ <template>{{ item.summary }}</template>
+ </p2b-grid-column>
+
+ </p2b-grid>
</template>
<script>
Polymer('p2b-plugin-git-status', {
- /*
- * List of git status items to display
- * @type {object}
- */
- statusItems: []
});
</script>
</polymer-element>
\ No newline at end of file
diff --git a/examples/pipetobrowser/browser/pipe-viewers/builtin/git/status/data-source.js b/examples/pipetobrowser/browser/pipe-viewers/builtin/git/status/data-source.js
new file mode 100644
index 0000000..3e6a271
--- /dev/null
+++ b/examples/pipetobrowser/browser/pipe-viewers/builtin/git/status/data-source.js
@@ -0,0 +1,51 @@
+/*
+ * Implement the data source which handles searching, filtering
+ * and sorting of the git status items
+ * @fileoverview
+ */
+
+import { gitStatusSort } from './sorter';
+import { gitStatusSearch } from './searcher';
+import { gitStatusFilter } from './filterer';
+
+export class gitStatusDataSource {
+ constructor(items) {
+
+ /*
+ * all items, unlimited buffer for now.
+ * @private
+ */
+ this.allItems = items;
+ }
+
+ /*
+ * Implements the fetch method expected by the grid components.
+ * handles 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.allItems.
+ filter((item) => {
+ return gitStatusFilter(item, filters);
+ }).
+ filter((item) => {
+ return gitStatusSearch(item, search.keyword);
+ }).
+ sort((item1, item2) => {
+ return gitStatusSort(item1, item2, sort.key, sort.ascending);
+ });
+
+ return filteredSortedItems;
+
+ }
+}
\ No newline at end of file
diff --git a/examples/pipetobrowser/browser/pipe-viewers/builtin/git/status/filterer.js b/examples/pipetobrowser/browser/pipe-viewers/builtin/git/status/filterer.js
new file mode 100644
index 0000000..4976e66
--- /dev/null
+++ b/examples/pipetobrowser/browser/pipe-viewers/builtin/git/status/filterer.js
@@ -0,0 +1,43 @@
+/*
+ * Returns whether the given git status items matches the map of filters.
+ * @param {Object} item A single git status 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:{'state':['staged'], 'action':['added','modified']}
+ * @return {boolean} Whether the item satisfies ALL of the given filters.
+ */
+export function gitStatusFilter(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 git status item matches a single filter
+ * @param {Object} item A single git status item as defined by parser.item
+ * @param {string} key filter key e.g. 'state'
+ * @param {string} value filter value e.g. '['staged','untracked']
+ * @return {boolean} Whether the item satisfies then the given filter key value pair
+ * @private
+ */
+function applyFilter(item, key, value) {
+ switch (key) {
+ case 'state':
+ case 'action':
+ return value.indexOf(item[key]) >= 0;
+ default:
+ // ignore unknown filters
+ return true;
+ }
+}
\ No newline at end of file
diff --git a/examples/pipetobrowser/browser/pipe-viewers/builtin/git/status/parser.js b/examples/pipetobrowser/browser/pipe-viewers/builtin/git/status/parser.js
index cf0135d..b9e2c50 100644
--- a/examples/pipetobrowser/browser/pipe-viewers/builtin/git/status/parser.js
+++ b/examples/pipetobrowser/browser/pipe-viewers/builtin/git/status/parser.js
@@ -57,18 +57,18 @@
/*
* A structure representing the status of a git file.
- * @param {string} fileAction, one of added, deleted, renamed, copied, modified, unknown
- * @param {string} fileState, one staged, notstaged, conflicted, untracked, ignored
- * @param {string} filePath filename and path
+ * @param {string} action, one of added, deleted, renamed, copied, modified, unknown
+ * @param {string} state, one staged, notstaged, conflicted, untracked, ignored
+ * @param {string} file filename and path
* @param {string} summary A summary text for what these states mean
* @class
* @private
*/
class item {
- constructor(fileAction, fileState, filePath, summary) {
- this.fileAction = fileAction;
- this.fileState = fileState;
- this.filePath = filePath;
+ constructor(action, state, file, summary) {
+ this.action = action;
+ this.state = state;
+ this.file = file;
this.summary = summary;
}
}
diff --git a/examples/pipetobrowser/browser/pipe-viewers/builtin/git/status/plugin.js b/examples/pipetobrowser/browser/pipe-viewers/builtin/git/status/plugin.js
index b9d6e55..86db6ce 100644
--- a/examples/pipetobrowser/browser/pipe-viewers/builtin/git/status/plugin.js
+++ b/examples/pipetobrowser/browser/pipe-viewers/builtin/git/status/plugin.js
@@ -7,8 +7,11 @@
*/
import { View } from 'view';
import { PipeViewer } from 'pipe-viewer';
-
+import { Logger } from 'logger'
import { parse } from './parser';
+import { gitStatusDataSource } from './data-source';
+
+var log = new Logger('pipe-viewers/builtin/git/status');
var streamUtil = require('event-stream');
@@ -18,27 +21,139 @@
}
play(stream) {
- // TODO(aghassemi) let's have the plugin specify if they expect data in
- // in binary or text so p2b can set the proper encoding for them rather
- // than each plugin doing it like this.
- // read data as UTF8
stream.setEncoding('utf8');
// split by new line
stream = stream.pipe(streamUtil.split(/\r?\n/));
- var statusItems = [];
- var statusView = document.createElement('p2b-plugin-git-status');
- statusView.statusItems = statusItems;
-
- stream.on('data', (line) => {
- if (line.trim().length > 0) {
- statusItems.push( parse(line) );
+ // parse the git status items
+ stream = stream.pipe(streamUtil.map((line, cb) => {
+ if (line.trim() === '') {
+ // eliminate the item
+ cb();
+ return;
}
+ var item;
+ try {
+ item = parse(line);
+ } catch(e) {
+ log.debug(e);
+ }
+ if (item) {
+ addAdditionalUIProperties(item);
+ cb(null, item);
+ } else {
+ // eliminate the item
+ cb();
+ }
+ }));
+
+ // we return a view promise instead of a view since we want to wait
+ // until all items arrive before showing the data.
+ var viewPromise = new Promise(function(resolve,reject) {
+ // write into an array when stream is done return the UI component
+ stream.pipe(streamUtil.writeArray((err, items) => {
+ if (err) {
+ reject(err);
+ } else {
+ var statusView = document.createElement('p2b-plugin-git-status');
+ statusView.dataSource = new gitStatusDataSource(items);
+ resolve(new View(statusView));
+ }
+ }));
});
- return new View(statusView);
+ return viewPromise;
}
}
+/*
+ * Adds additional UI specific properties to the item
+ * @private
+ */
+function addAdditionalUIProperties(item) {
+ addActionIconProperty(item);
+ addStateIconProperty(item);
+ addFileNameFileParentProperty(item);
+}
+
+/*
+ * Adds an icon property to the item specifying what icon to display
+ * based on state
+ * @private
+ */
+function addStateIconProperty(item) {
+ var iconName;
+ switch (item.state) {
+ case 'staged':
+ iconName = 'check-circle';
+ break;
+ case 'notstaged':
+ iconName = 'warning';
+ break;
+ case 'conflicted':
+ iconName = 'error';
+ break;
+ case 'untracked':
+ iconName = 'report';
+ break;
+ case 'ignored':
+ iconName = 'visibility-off';
+ break;
+ }
+
+ item.stateIcon = iconName;
+}
+
+/*
+ * Adds an icon property to the item specifying what icon to display
+ * based on action
+ * @private
+ */
+function addActionIconProperty(item) {
+ var iconName;
+ switch (item.action) {
+ case 'added':
+ iconName = 'add';
+ break;
+ case 'deleted':
+ iconName = 'clear';
+ break;
+ case 'modified':
+ iconName = 'translate';
+ break;
+ case 'renamed':
+ iconName = 'sync';
+ break;
+ case 'copied':
+ iconName = 'content-copy';
+ break;
+ case 'unknown':
+ iconName = 'remove';
+ break;
+ }
+
+ item.actionIcon = iconName;
+}
+
+/*
+ * Splits file into filename and fileParent
+ * @private
+ */
+function addFileNameFileParentProperty(item) {
+
+ var filename = item.file;
+ var fileParent = "./";
+
+ var slashIndex = item.file.lastIndexOf('/');
+
+ if (slashIndex > 0) {
+ filename = item.file.substr(slashIndex + 1);
+ fileParent = item.file.substring(0, slashIndex);
+ }
+
+ item.filename = filename;
+ item.fileParent = fileParent;
+}
+
export default GitStatusPipeViewer;
\ No newline at end of file
diff --git a/examples/pipetobrowser/browser/pipe-viewers/builtin/git/status/searcher.js b/examples/pipetobrowser/browser/pipe-viewers/builtin/git/status/searcher.js
new file mode 100644
index 0000000..57e8a01
--- /dev/null
+++ b/examples/pipetobrowser/browser/pipe-viewers/builtin/git/status/searcher.js
@@ -0,0 +1,12 @@
+export function gitStatusSearch(item, keyword) {
+ if (!keyword) {
+ return true;
+ }
+
+ // we only search file
+ if (item.file.indexOf(keyword) >= 0) {
+ return true
+ }
+
+ return false;
+};
\ No newline at end of file
diff --git a/examples/pipetobrowser/browser/pipe-viewers/builtin/git/status/sorter.js b/examples/pipetobrowser/browser/pipe-viewers/builtin/git/status/sorter.js
new file mode 100644
index 0000000..9dec432
--- /dev/null
+++ b/examples/pipetobrowser/browser/pipe-viewers/builtin/git/status/sorter.js
@@ -0,0 +1,41 @@
+var stateSortPriority = {
+ 'conflicted' : 1,
+ 'untracked' : 2,
+ 'notstaged' : 3,
+ 'staged': 4,
+ 'ignored': 5
+}
+
+var actionSortPriority = {
+ 'added' : 1,
+ 'deleted' : 2,
+ 'modified' : 3,
+ 'renamed': 4,
+ 'copied': 5,
+ 'unknown': 6
+}
+
+export function gitStatusSort(item1, item2, key, ascending) {
+ var first = item1[key];
+ var second = item2[key];
+ if (!ascending) {
+ first = item2[key];
+ second = item1[key];
+ }
+
+ if (key === 'state') {
+ first = stateSortPriority[first];
+ second = stateSortPriority[second];
+ }
+
+ if (key === 'action') {
+ first = actionSortPriority[first];
+ second = actionSortPriority[second];
+ }
+
+ if (typeof first === 'string') {
+ return first.localeCompare(second);
+ } else {
+ return first - second;
+ }
+};
\ 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 972c7cc..196036e 100644
--- a/examples/pipetobrowser/browser/pipe-viewers/builtin/vlog/component.html
+++ b/examples/pipetobrowser/browser/pipe-viewers/builtin/vlog/component.html
@@ -36,18 +36,19 @@
<p2b-grid-filter-toggle key="autorefresh" label="Live Refresh" checked></p2b-grid-filter-toggle>
<!-- Columns, sorting and cell templates -->
- <p2b-grid-column label="Level" key="level" sortable flex="1" priority="2" >
+ <p2b-grid-column label="Level" key="level" sortable flex="2" priority="2" >
<template>
<core-icon class="level-icon {{ item.level }}" icon="{{ item.icon }}" title="{{item.level}}"></core-icon>
+ <span moreInfoOnly style="vertical-align:middle">{{item.level}}</span>
</template>
</p2b-grid-column>
- <p2b-grid-column label="File" key="file" sortable flex="3" minFlex="2" priority="4" >
+ <p2b-grid-column label="File" key="file" sortable flex="4" minFlex="2" priority="4" >
<template>{{ item.file }}<span class="line-number">{{ item.fileLine }}</span></template>
</p2b-grid-column>
- <p2b-grid-column label="Message" key="message" primary flex="7" minFlex="5" priority="1" >
+ <p2b-grid-column label="Message" key="message" primary flex="8" minFlex="5" priority="1" >
<template><div class="message-text">{{ item.message }}</div></template>
</p2b-grid-column>
- <p2b-grid-column label="Date" key="date" sortable flex="5" minFlex="3" priority="3">
+ <p2b-grid-column label="Date" key="date" sortable flex="6" minFlex="3" priority="3">
<template><span class="smaller-text">{{ item.date }}</span></template>
</p2b-grid-column>
<p2b-grid-column label="Threadid" key="threadid" sortable flex="0" priority="5">
diff --git a/examples/pipetobrowser/browser/pipe-viewers/builtin/vlog/plugin.js b/examples/pipetobrowser/browser/pipe-viewers/builtin/vlog/plugin.js
index dfb2dbd..1891d5e 100644
--- a/examples/pipetobrowser/browser/pipe-viewers/builtin/vlog/plugin.js
+++ b/examples/pipetobrowser/browser/pipe-viewers/builtin/vlog/plugin.js
@@ -8,8 +8,9 @@
*/
import { View } from 'view';
import { PipeViewer } from 'pipe-viewer';
-import { vLogDataSource } from './data-source';
import { Logger } from 'logger'
+import { vLogDataSource } from './data-source';
+
var log = new Logger('pipe-viewers/builtin/vlog');
var streamUtil = require('event-stream');
diff --git a/examples/pipetobrowser/browser/pipe-viewers/builtin/vlog/sorter.js b/examples/pipetobrowser/browser/pipe-viewers/builtin/vlog/sorter.js
index f2663f4..0e0a2db 100644
--- a/examples/pipetobrowser/browser/pipe-viewers/builtin/vlog/sorter.js
+++ b/examples/pipetobrowser/browser/pipe-viewers/builtin/vlog/sorter.js
@@ -1,14 +1,26 @@
-export function vLogSort(logItem1, logItem2, key, ascending) {
- var first = logItem1;
- var second = logItem2;
+var levelSortPriority = {
+ 'fatal' : 1,
+ 'error' : 2,
+ 'warning' : 3,
+ 'info': 4
+}
+
+export function vLogSort(item1, item2, key, ascending) {
+ var first = item1[key];
+ var second = item2[key];
if (!ascending) {
- first = logItem2;
- second = logItem1;
+ first = item2[key];
+ second = item1[key];
}
- if (typeof first[key] === 'string') {
- return first[key].localeCompare(second[key]);
+ if (key === 'level') {
+ first = levelSortPriority[first];
+ second = levelSortPriority[second];
+ }
+
+ if (typeof first === 'string') {
+ return first.localeCompare(second);
} else {
- return first[key] - second[key];
+ return first - second;
}
};
\ No newline at end of file