Merge pull request #2110 from kravets-levko/feature/table-v2

Table v2
This commit is contained in:
Arik Fraimovich
2017-12-04 16:15:56 +02:00
committed by GitHub
27 changed files with 1167 additions and 946 deletions

View File

@@ -67,4 +67,8 @@ strong {
}
}
.scrollbox {
overflow: auto;
position: relative;
}

View File

@@ -1,95 +1,99 @@
.table {
margin-bottom: 0;
&:not(.table-striped) > thead > tr > th {
background-color: #FAFAFA;
}
[class*="bg-"] {
& > tr > th {
color: #fff;
border-bottom: 0;
background: transparent !important;
}
& + tbody > tr:first-child > td {
border-top: 0;
}
}
& > thead > tr > th {
vertical-align: middle;
font-weight: 500;
color: #333;
border-width: 1px;
text-transform: uppercase;
padding: 15px 10px;
}
& > thead > tr,
& > tbody > tr,
& > tfoot > tr {
& > th, & > td {
&:first-child {
padding-left: 30px;
}
&:last-child {
padding-right: 30px;
}
}
}
tbody > tr:last-child > td {
padding-bottom: 20px;
}
}
.table-bordered {
border: 0;
& > tbody > tr {
& > td, & > th {
border-bottom: 0;
border-left: 0;
&:last-child {
border-right: 0;
}
}
}
& > thead > tr > th {
border-left: 0;
&:last-child {
border-right: 0;
}
}
}
.table-vmiddle {
td {
vertical-align: middle !important;
}
}
.table-responsive {
border: 0;
}
.tile .table {
& > thead:not([class*="bg-"]) > tr > th {
border-top: 1px solid @table-border-color;
}
}
.table-hover > tbody > tr:hover {
background-color: #f4f4f4;
}
.table {
margin-bottom: 0;
th.sortable-column {
cursor: pointer;
}
&:not(.table-striped) > thead > tr > th {
background-color: #FAFAFA;
}
[class*="bg-"] {
& > tr > th {
color: #fff;
border-bottom: 0;
background: transparent !important;
}
& + tbody > tr:first-child > td {
border-top: 0;
}
}
& > thead > tr > th {
vertical-align: middle;
font-weight: 500;
color: #333;
border-width: 1px;
text-transform: uppercase;
padding: 15px 10px;
}
& > thead > tr,
& > tbody > tr,
& > tfoot > tr {
& > th, & > td {
&:first-child {
padding-left: 30px;
}
&:last-child {
padding-right: 30px;
}
}
}
tbody > tr:last-child > td {
padding-bottom: 20px;
}
}
.table-bordered {
border: 0;
& > tbody > tr {
& > td, & > th {
border-bottom: 0;
border-left: 0;
&:last-child {
border-right: 0;
}
}
}
& > thead > tr > th {
border-left: 0;
&:last-child {
border-right: 0;
}
}
}
.table-vmiddle {
td {
vertical-align: middle !important;
}
}
.table-responsive {
border: 0;
}
.tile .table {
& > thead:not([class*="bg-"]) > tr > th {
border-top: 1px solid @table-border-color;
}
}
.table-hover > tbody > tr:hover {
background-color: #f4f4f4;
}

View File

@@ -68,10 +68,14 @@ page-header, .page-header--new {
padding: 3px 6px 4px;
}
.tab-nav > li > a, .table > thead > tr > th {
.tab-nav > li > a {
text-transform: capitalize;
}
.table > thead > tr > th {
text-transform: none;
}
.dashboard__control {
margin: 8px 0;
}

View File

@@ -29,11 +29,6 @@
}
}
.scrollbox {
overflow: auto;
position: relative;
}
.dropdown-header {
padding: 0;

View File

@@ -0,0 +1,16 @@
import template from './template.html';
export default function init(ngModule) {
ngModule.directive('dynamicTableDefaultCell', $sanitize => ({
template,
restrict: 'E',
replace: true,
scope: {
column: '=',
value: '=',
},
link: ($scope) => {
$scope.sanitize = value => $sanitize(value);
},
}));
}

View File

@@ -0,0 +1,4 @@
<td ng-class="'content-align-' + column.alignContent">
<div ng-if="column.allowHTML" ng-bind-html="sanitize(column.formatFunction(value))"></div>
<div ng-if="!column.allowHTML" ng-bind="column.formatFunction(value)"></div>
</td>

View File

@@ -0,0 +1,25 @@
import { isFunction } from 'underscore';
export default function init(ngModule) {
ngModule.directive('dynamicTableRow', () => ({
template: '',
// AngularJS has a strange love to table-related tags, therefore
// we should use this directive as an attribute
restrict: 'A',
replace: false,
scope: {
columns: '=',
row: '=',
render: '=',
},
link: ($scope, $element) => {
$scope.$watch('render', () => {
if (isFunction($scope.render)) {
$scope.render($scope, (clonedElement) => {
$element.empty().append(clonedElement);
});
}
});
},
}));
}

View File

@@ -1,3 +0,0 @@
th.sortable-column {
cursor: pointer;
}

View File

@@ -1,30 +1,45 @@
<div>
<div class="dynamic-table-container">
<table class="table table-condensed table-hover">
<thead>
<tr>
<th ng-repeat="column in $ctrl.columns" ng-click="$ctrl.orderBy(column)" class="sortable-column">
{{column.title}} <span ng-if="$ctrl.sortIcon(column)"><i class="fa fa-sort-{{$ctrl.sortIcon(column)}}"></i></span>
<tr>
<th ng-repeat="column in $ctrl.columns" ng-click="$ctrl.onColumnHeaderClick($event, column)"
class="sortable-column" ng-class="'content-align-' + column.alignContent">
<span ng-if="($ctrl.orderBy.length > 1) && ($ctrl.orderByColumnsIndex[column.name] > 0)"
class="sort-order-indicator">{{ $ctrl.orderByColumnsIndex[column.name] }}</span>
<span>{{column.title}}</span>
<i ng-if="$ctrl.orderByColumnsIndex[column.name] > 0"
ng-class="{
'fa': true,
'fa-caret-down': $ctrl.orderByColumnsDirection[column.name] > 0,
'fa-caret-up': $ctrl.orderByColumnsDirection[column.name] < 0
}"></i>
</th>
</tr>
</thead>
<thead ng-if="$ctrl.searchColumns.length > 0">
<th class="p-t-10 p-b-10 p-l-15 p-r-15" colspan="{{ $ctrl.columns.length }}">
<input type="text" class="form-control" placeholder="Search..."
ng-model="$ctrl.searchTerm" ng-model-options="{ allowInvalid: true, debounce: 200 }"
ng-change="$ctrl.onSearchTermChanged()">
</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="row in $ctrl.rowsToDisplay">
<td ng-repeat="column in $ctrl.columns" ng-bind-html="$ctrl.sanitize(column.formatFunction(row[column.name]))">
</td>
</tr>
<tr ng-repeat="row in $ctrl.rowsToDisplay"
dynamic-table-row columns="$ctrl.columns" row="row"
render="$ctrl.renderSingleRow"
></tr>
</tbody>
</table>
</div>
<div class="text-center" ng-if="$ctrl.rowsCount > $ctrl.itemsPerPage">
<ul uib-pagination total-items="$ctrl.rowsCount"
<div class="text-center" ng-if="$ctrl.preparedRows.length > $ctrl.itemsPerPage">
<ul uib-pagination total-items="$ctrl.preparedRows.length"
items-per-page="$ctrl.itemsPerPage"
ng-model="$ctrl.page"
ng-model="$ctrl.currentPage"
max-size="6"
class="pagination"
boundary-link-numbers="true"
rotate="false"
next-text='&#8594;'
previous-text='&#8592;'
ng-change="$ctrl.pageChanged()"></ul>
ng-change="$ctrl.onPageChanged()"></ul>
</div>

View File

@@ -0,0 +1,38 @@
.dynamic-table-container {
th {
white-space: nowrap;
span {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.sort-order-indicator {
@size: 12px;
display: inline-block;
vertical-align: middle;
min-width: @size;
height: @size;
font-size: @size * 3/4;
border-radius: @size / 2;
background: #c0c0c0;
text-align: center;
line-height: @size;
color: #fff;
padding: 0 @size * 1/4;
}
}
th, td {
&.content-align-left {
text-align: left;
}
&.content-align-right {
text-align: right;
}
&.content-align-center {
text-align: center;
}
}
}

View File

@@ -1,66 +1,217 @@
import { sortBy } from 'underscore';
import { find, filter, map, each } from 'underscore';
import template from './dynamic-table.html';
import './dynamic-table.css';
import './dynamic-table.less';
function DynamicTable($sanitize) {
function filterRows(rows, searchTerm, columns) {
if ((searchTerm === '') || (columns.length === 0) || (rows.length === 0)) {
return rows;
}
searchTerm = searchTerm.toUpperCase();
return filter(rows, (row) => {
for (let i = 0; i < columns.length; i += 1) {
const columnName = columns[i].name;
const formatFunction = columns[i].formatFunction;
if (row[columnName] !== undefined) {
let value = formatFunction ? formatFunction(row[columnName]) : row[columnName];
value = ('' + value).toUpperCase();
if (value.indexOf(searchTerm) >= 0) {
return true;
}
}
}
return false;
});
}
function sortRows(rows, orderBy) {
if ((orderBy.length === 0) || (rows.length === 0)) {
return rows;
}
// Create a copy of array before sorting, because .sort() will modify original array
return [].concat(rows).sort((a, b) => {
let va;
let vb;
for (let i = 0; i < orderBy.length; i += 1) {
va = a[orderBy[i].name];
vb = b[orderBy[i].name];
if (va < vb) {
// if a < b - we should return -1, but take in account direction
return orderBy[i].direction * -1;
}
if (va > vb) {
// if a > b - we should return 1, but take in account direction
return orderBy[i].direction * 1;
}
}
return 0;
});
}
function validateItemsPerPage(value, defaultValue) {
defaultValue = defaultValue || 10;
value = parseInt(value, 10) || defaultValue;
return value > 0 ? value : defaultValue;
}
// Optimized rendering
// Instead of using two nested `ng-repeat`s by rows and columns,
// we'll create a template for row (and update it when columns changed),
// compile it, and then use `ng-repeat` by rows and bind this template
// to each row's scope. The goal is to reduce amount of scopes and watchers
// from `count(rows) * count(cols)` to `count(rows)`. The major disadvantage
// is that cell markup should be specified here instead of template.
function createRowRenderTemplate(columns, $compile) {
const rowTemplate = map(columns, (column, index) => {
switch (column.displayAs) {
case 'json':
return `
<dynamic-table-json-cell column="columns[${index}]"
value="row[columns[${index}].name]"></dynamic-table-json-cell>
`;
default:
return `
<dynamic-table-default-cell column="columns[${index}]"
value="row[columns[${index}].name]"></dynamic-table-default-cell>
`;
}
}).join('');
return $compile(rowTemplate);
}
function DynamicTable($compile) {
'ngInject';
this.itemsPerPage = this.count = 15;
this.page = 1;
this.rowsCount = 0;
this.orderByField = undefined;
this.orderByReverse = false;
this.itemsPerPage = validateItemsPerPage(this.itemsPerPage);
this.currentPage = 1;
this.searchTerm = '';
this.pageChanged = () => {
const first = this.count * (this.page - 1);
const last = this.count * (this.page);
this.columns = [];
this.rows = [];
this.preparedRows = [];
this.rowsToDisplay = [];
this.orderBy = [];
this.orderByColumnsIndex = {};
this.orderByColumnsDirection = {};
this.rowsToDisplay = this.rows.slice(first, last);
this.searchColumns = [];
const updateOrderByColumnsInfo = () => {
this.orderByColumnsIndex = {};
this.orderByColumnsDirection = {};
each(this.orderBy, (column, index) => {
this.orderByColumnsIndex[column.name] = index + 1;
this.orderByColumnsDirection[column.name] = column.direction;
});
};
const updateRowsToDisplay = (performFilterAndSort) => {
if (performFilterAndSort) {
this.preparedRows = sortRows(
filterRows(this.rows, this.searchTerm, this.searchColumns),
this.orderBy,
);
}
const first = (this.currentPage - 1) * this.itemsPerPage;
const last = first + this.itemsPerPage;
this.rowsToDisplay = this.preparedRows.slice(first, last);
};
const setColumns = (columns) => {
// 1. reset sorting
// 2. reset current page
// 3. reset search
// 4. get columns for search
// 5. update row rendering template
// 6. prepare rows
this.columns = columns;
updateOrderByColumnsInfo();
this.orderBy = [];
this.currentPage = 1;
this.searchTerm = '';
this.searchColumns = filter(this.columns, 'allowSearch');
this.renderSingleRow = createRowRenderTemplate(this.columns, $compile);
updateRowsToDisplay(true);
};
const setRows = (rows) => {
// 1. reset current page
// 2. prepare rows
this.rows = rows;
this.currentPage = 1;
updateRowsToDisplay(true);
};
this.renderSingleRow = null;
this.onColumnHeaderClick = ($event, column) => {
const orderBy = find(this.orderBy, item => item.name === column.name);
if (orderBy) {
// ASC -> DESC -> off
if (orderBy.direction === 1) {
orderBy.direction = -1;
if (!$event.shiftKey) {
this.orderBy = [orderBy];
}
} else {
if ($event.shiftKey) {
this.orderBy = filter(this.orderBy, item => item.name !== column.name);
} else {
this.orderBy = [];
}
}
} else {
if (!$event.shiftKey) {
this.orderBy = [];
}
this.orderBy.push({
name: column.name,
direction: 1,
});
}
updateOrderByColumnsInfo();
updateRowsToDisplay(true);
// Remove text selection - may occur accidentally
if ($event.shiftKey) {
document.getSelection().removeAllRanges();
}
};
this.onPageChanged = () => {
updateRowsToDisplay(false);
};
this.onSearchTermChanged = () => {
this.preparedRows = sortRows(
filterRows(this.rows, this.searchTerm, this.searchColumns),
this.orderBy,
);
this.currentPage = 1;
updateRowsToDisplay(true);
};
this.$onChanges = (changes) => {
if (changes.columns) {
this.columns = changes.columns.currentValue;
if (changes.rows) {
// if rows also changed - temporarily set if to empty array - to avoid
// filtering and sorting
this.rows = [];
}
setColumns(changes.columns.currentValue);
}
if (changes.rows) {
this.rows = changes.rows.currentValue;
setRows(changes.rows.currentValue);
}
this.rowsCount = this.rows.length;
this.pageChanged();
};
this.orderBy = (column) => {
if (column === this.orderByField) {
this.orderByReverse = !this.orderByReverse;
} else {
this.orderByField = column;
this.orderByReverse = false;
if (changes.itemsPerPage) {
this.itemsPerPage = validateItemsPerPage(this.itemsPerPage);
this.currentPage = 1;
updateRowsToDisplay(false);
}
if (this.orderByField) {
this.rows = sortBy(this.rows, this.orderByField.name);
if (this.orderByReverse) {
this.rows = this.rows.reverse();
}
this.pageChanged();
}
};
this.sanitize = value => $sanitize(value);
this.sortIcon = (column) => {
if (column !== this.orderByField) {
return null;
}
if (this.orderByReverse) {
return 'desc';
}
return 'asc';
};
}
@@ -71,7 +222,7 @@ export default function init(ngModule) {
bindings: {
rows: '<',
columns: '<',
count: '<',
itemsPerPage: '<',
},
});
}

View File

@@ -0,0 +1,40 @@
import { isUndefined, isString } from 'underscore';
import renderJsonView from './json-view-interactive';
import template from './template.html';
const MAX_JSON_SIZE = 50000;
function parseValue(value) {
if (isString(value) && (value.length <= MAX_JSON_SIZE)) {
try {
return JSON.parse(value);
} catch (e) {
return undefined;
}
}
}
export default function init(ngModule) {
ngModule.directive('dynamicTableJsonCell', () => ({
template,
restrict: 'E',
replace: true,
scope: {
column: '=',
value: '=',
},
link: ($scope, $element) => {
const container = $element.find('.json-cell-valid');
$scope.isValid = false;
$scope.parsedValue = null;
$scope.$watch('value', () => {
$scope.parsedValue = parseValue($scope.value);
$scope.isValid = !isUndefined($scope.parsedValue);
container.empty();
renderJsonView(container, $scope.parsedValue);
});
},
}));
}

View File

@@ -0,0 +1,176 @@
import { isFunction, isArray, isObject, isString, isNumber, isUndefined, each, keys, filter } from 'underscore';
import $ from 'jquery';
import './json-view-interactive.less';
function isPrimitive(value) {
return (value === null) || (value === false) || (value === true) ||
(isNumber(value) && isFinite(value));
}
function combine(...functions) {
functions = filter(functions, isFunction);
return (...args) => {
each(functions, (fn) => {
fn(...args);
});
};
}
function initToggle(toggle, toggleBlockFn) {
if (isFunction(toggleBlockFn)) {
let visible = false;
const icon = $('<i>').addClass('fa fa-caret-right').appendTo(toggle.empty());
toggleBlockFn(visible);
toggle.on('click', () => {
visible = !visible;
icon.toggleClass('fa-caret-right fa-caret-down');
toggleBlockFn(visible);
});
} else {
toggle.addClass('hidden');
}
}
function createRenderNestedBlock(block, ellipsis, values, renderKeys) {
return (show) => {
if (show) {
ellipsis.addClass('hidden');
block.removeClass('hidden').empty();
let firstItem = null;
let lastItem = null;
each(values, (val, key) => {
const nestedBlock = $('<span>').addClass('jvi-item').appendTo(block);
firstItem = firstItem || nestedBlock;
lastItem = nestedBlock;
const toggle = $('<span>').addClass('jvi-toggle').appendTo(nestedBlock);
if (renderKeys) {
const keyWrapper = $('<span>').addClass('jvi-object-key').appendTo(nestedBlock);
// eslint-disable-next-line no-use-before-define
renderString(keyWrapper, key);
$('<span>').addClass('jvi-punctuation').text(': ').appendTo(nestedBlock);
}
// eslint-disable-next-line no-use-before-define
const toggleBlockFn = renderValue(nestedBlock, val, true);
initToggle(toggle, toggleBlockFn);
});
if (firstItem) {
firstItem.addClass('jvi-nested-first');
}
if (lastItem) {
lastItem.addClass('jvi-nested-last');
}
} else {
block.addClass('hidden').empty();
ellipsis.removeClass('hidden');
}
};
}
function renderComma($element) {
return $('<span>').addClass('jvi-punctuation jvi-comma').text(',').appendTo($element);
}
function renderEllipsis($element) {
const result = $('<span>')
.addClass('jvi-punctuation jvi-ellipsis')
.html('&hellip;')
.appendTo($element)
.on('click', () => {
result.parents('.jvi-item').eq(0).find('.jvi-toggle').trigger('click');
});
return result;
}
function renderPrimitive($element, value, comma) {
$('<span>').addClass('jvi-value jvi-primitive').text('' + value).appendTo($element);
if (comma) {
renderComma($element);
}
return null;
}
function renderString($element, value, comma) {
$('<span>').addClass('jvi-punctuation jvi-string').text('"').appendTo($element);
$('<span>').addClass('jvi-value jvi-string').text(value).appendTo($element);
$('<span>').addClass('jvi-punctuation jvi-string').text('"').appendTo($element);
if (comma) {
renderComma($element);
}
return null;
}
function renderComment($element, count) {
const text = ' // ' + count + ' ' + (count === 1 ? 'item' : 'items');
const comment = $('<span>').addClass('jvi-comment').text(text).appendTo($element);
return (show) => {
if (show) {
comment.addClass('hidden');
} else {
comment.removeClass('hidden');
}
};
}
function renderBrace($element, isForArray, isOpening) {
const openingBrace = isForArray ? '[' : '{';
const closingBrace = isForArray ? ']' : '}';
const brace = isOpening ? openingBrace : closingBrace;
return $('<span>').addClass('jvi-punctuation jvi-braces').text(brace).appendTo($element);
}
function renderWithNested($element, values, comma, valuesIsArray) {
const count = valuesIsArray ? values.length : keys(values).length;
let result = null;
renderBrace($element, valuesIsArray, true);
if (count > 0) {
const ellipsis = renderEllipsis($element);
const block = $('<span>').addClass('jvi-block hidden').appendTo($element);
result = createRenderNestedBlock(block, ellipsis, values, !valuesIsArray);
}
renderBrace($element, valuesIsArray, false);
if (comma) {
renderComma($element);
}
if (count > 0) {
result = combine(renderComment($element, count), result);
}
return result;
}
function renderArray($element, values, comma) {
return renderWithNested($element, values, comma, true);
}
function renderObject($element, value, comma) {
return renderWithNested($element, value, comma, false);
}
function renderValue($element, value, comma) {
$element = $('<span>').appendTo($element);
if (isPrimitive(value)) {
return renderPrimitive($element, value, comma);
} else if (isString(value)) {
return renderString($element, value, comma);
} else if (isArray(value)) {
return renderArray($element, value, comma);
} else if (isObject(value)) {
return renderObject($element, value, comma);
}
}
export default function renderJsonView(container, value) {
if ((container instanceof $) && !isUndefined(value) && !isFunction(value)) {
const block = $('<span>').addClass('jvi-item').appendTo(container);
const toggle = $('<span>').addClass('jvi-toggle').appendTo(block);
const toggleBlockFn = renderValue(block, value);
initToggle(toggle, toggleBlockFn);
}
}

View File

@@ -0,0 +1,110 @@
@import (reference) "~bootstrap/less/variables.less";
@jvi-gutter: 20px;
@jvi-spacing: 2px;
.jvi-block {
display: block;
border-left: 1px dotted @table-border-color;
margin: 0 0 0 2px;
font-family: @font-family-monospace;
&.hidden {
display: none;
}
}
.jvi-item {
display: block;
position: relative;
padding: 0 0 0 @jvi-gutter;
white-space: nowrap;
.jvi-item {
margin: @jvi-spacing 0;
}
&.jvi-nested-last > span > .jvi-punctuation.jvi-comma {
display: none;
}
}
.jvi-toggle {
position: absolute;
left: 0;
top: 0;
width: @jvi-gutter;
height: @jvi-gutter;
line-height: @jvi-gutter;
text-align: center;
cursor: pointer;
z-index: 1;
color: @text-color;
opacity: 0.5;
&:hover {
opacity: 0.8;
}
i {
vertical-align: middle;
}
&.hidden {
display: none;
}
}
.jvi-punctuation {
color: @text-color;
&.jvi-string {
color: @state-success-text;
}
&.jvi-braces {
margin: 0 @jvi-spacing;
}
&.jvi-ellipsis {
padding: 0 @jvi-spacing;
cursor: pointer;
&:hover {
text-decoration: underline;
}
}
&.hidden {
display: none;
}
}
.jvi-value {
color: @state-success-text;
&.jvi-primitive {
color: @state-warning-text;
}
&.jvi-string {
white-space: normal;
}
}
.jvi-object-key {
.jvi-value, .jvi-punctuation {
color: @brand-primary;
}
}
.jvi-comment {
color: @text-muted;
font-style: italic;
margin: 0 0 0 2 * @jvi-spacing;
opacity: 0.5;
&.hidden {
display: none;
}
}

View File

@@ -0,0 +1,4 @@
<td>
<div ng-if="!isValid" class="json-cell-invalid">{{ value }}</div>
<div ng-show="isValid" class="json-cell-valid"></div>
</td>

View File

@@ -2,9 +2,6 @@ import moment from 'moment';
import _capitalize from 'underscore.string/capitalize';
import { isEmpty } from 'underscore';
// eslint-disable-next-line
const urlPattern = /(^|[\s\n]|<br\/?>)((?:https?|ftp):\/\/[\-A-Z0-9+\u0026\u2019@#\/%?=()~_|!:,.;]*[\-A-Z0-9+\u0026@#\/%=~()_|])/gi;
export function durationHumanize(duration) {
let humanized = '';
@@ -67,10 +64,6 @@ export function capitalize(text) {
return null;
}
export function linkify(text) {
return text.replace(urlPattern, "$1<a href='$2' target='_blank'>$2</a>");
}
export function remove(items, item) {
if (items === undefined) {
return items;

View File

@@ -20,6 +20,7 @@ angular.module('ui.sortable', [])
// ng-repeat items
// if the user uses
items: '> [ng-repeat],> [data-ng-repeat],> [x-ng-repeat]',
cancel: 'input, textarea, button, select, option, .ui-sortable-bypass',
})
.directive('uiSortable', [
'uiSortableConfig', '$timeout', '$log',

View File

@@ -96,10 +96,13 @@
<span class="zmdi zmdi-format-indent-increase"></span> Format Query
</button>
<i class="fa fa-database"></i>
<span class="text-muted">Data Source</span>
<span class="m-l-10">
<i class="fa fa-database"></i>
<span class="text-muted">Data Source</span>
</span>
<select ng-disabled="!isQueryOwner" ng-model="query.data_source_id" ng-change="updateDataSource()"
ng-options="ds.id as ds.name for ds in dataSources"></select>
ng-options="ds.id as ds.name for ds in dataSources"
class="form-control m-l-5" style="display: inline-block; width: auto;"></select>
<div class="pull-right">
<button class="btn btn-s btn-default" ng-click="togglePublished()" ng-if="query.is_draft && query.id != undefined && (isQueryOwner || currentUser.hasPermission('admin'))">
@@ -232,11 +235,12 @@
<div class="row">
<div class="col-lg-12">
<ul class="tab-nav">
<rd-tab tab-id="table" name="Table" base-path="query.getUrl(sourceMode)"></rd-tab>
<rd-tab tab-id="{{vis.id}}" name="{{vis.name}}" ng-if="vis.type!='TABLE'" base-path="query.getUrl(sourceMode)"
ng-repeat="vis in query.visualizations">
<span class="remove" ng-click="deleteVisualization($event, vis)"
ng-show="canEdit"> &times;</span>
<rd-tab ng-if="!query.visualizations.length"
tab-id="table" name="Table" base-path="query.getUrl(sourceMode)"></rd-tab>
<rd-tab tab-id="{{vis.id}}" name="{{vis.name}}" base-path="query.getUrl(sourceMode)"
ng-repeat="vis in query.visualizations | orderBy:'id'">
<span class="remove" ng-click="deleteVisualization($event, vis)"
ng-if="canEdit && !($first && (vis.type === 'TABLE'))">&times;</span>
</rd-tab>
<li class="rd-tab"><a ng-click="openVisualizationEditor()" ng-if="sourceMode && canEdit">&plus; New Visualization</a></li>
</ul>
@@ -244,14 +248,11 @@
</div>
<div class="row">
<div class="col-lg-12">
<div ng-show="selectedTab == 'table'">
<div ng-if="!query.visualizations.length">
<filters filters="filters"></filters>
<grid-renderer query-result="queryResult" items-per-page="50"></grid-renderer>
<!-- the ng-repeat is a lame hack to find the table visualization... -->
<div class="p-15" ng-repeat="vis in query.visualizations" ng-if="vis.type == 'TABLE'">
<button class="btn btn-default" ng-if="!query.isNew()" ng-click="showEmbedDialog(query, vis)"><i class="zmdi zmdi-code"></i> Embed</button>
</div>
</div>
<div ng-if="selectedTab == vis.id" ng-repeat="vis in query.visualizations">
<visualization-renderer visualization="vis" query-result="queryResult"></visualization-renderer>
<div class="p-15">

View File

@@ -1,4 +1,4 @@
import { pick, any, some, find } from 'underscore';
import { pick, any, some, find, min, isObject } from 'underscore';
import template from './query.html';
function QueryViewCtrl(
@@ -6,8 +6,6 @@ function QueryViewCtrl(
KeyboardShortcuts, Title, AlertDialog, Notifications, clientConfig, toastr, $uibModal,
currentUser, Query, DataSource,
) {
const DEFAULT_TAB = 'table';
function getQueryResult(maxAge) {
if (maxAge === undefined) {
maxAge = $location.search().maxAge;
@@ -112,7 +110,7 @@ function QueryViewCtrl(
Notifications.getPermissions();
};
$scope.selectedTab = 'table';
$scope.currentUser = currentUser;
$scope.dataSource = {};
$scope.query = $route.current.locals.query;
@@ -360,7 +358,15 @@ function QueryViewCtrl(
$scope.$watch(
() => $location.hash(),
(hash) => { $scope.selectedTab = hash || DEFAULT_TAB; },
(hash) => {
// eslint-disable-next-line eqeqeq
const exists = find($scope.query.visualizations, item => item.id == hash);
let visualization = min($scope.query.visualizations, viz => viz.id);
if (!isObject(visualization)) {
visualization = {};
}
$scope.selectedTab = (exists ? hash : visualization.id) || 'table';
},
);
$scope.showManagePermissionsModal = () => {

View File

@@ -0,0 +1,60 @@
import moment from 'moment/moment';
import numeral from 'numeral';
import _ from 'underscore';
// eslint-disable-next-line
const urlPattern = /(^|[\s\n]|<br\/?>)((?:https?|ftp):\/\/[\-A-Z0-9+\u0026\u2019@#\/%?=()~_|!:,.;]*[\-A-Z0-9+\u0026@#\/%=~()_|])/gi;
function createDefaultFormatter(highlightLinks) {
if (highlightLinks) {
return (value) => {
if (_.isString(value)) {
value = value.replace(urlPattern, '$1<a href="$2" target="_blank">$2</a>');
}
return value;
};
}
return value => value;
}
function createDateTimeFormatter(format) {
if (_.isString(format) && (format !== '')) {
return (value) => {
if (value && moment.isMoment(value)) {
return value.format(format);
}
return value;
};
}
return value => value;
}
function createBooleanFormatter(values) {
if (_.isArray(values)) {
if (values.length >= 2) {
// Both `true` and `false` specified
return value => '' + values[value ? 1 : 0];
} else if (values.length === 1) {
// Only `true`
return value => (value ? values[0] : '');
}
}
return value => (value ? 'true' : 'false');
}
function createNumberFormatter(format) {
if (_.isString(format) && (format !== '')) {
const n = numeral(0); // cache `numeral` instance
return value => n.set(value).format(format);
}
return value => value;
}
export default function createFormatter(column) {
switch (column.displayAs) {
case 'number': return createNumberFormatter(column.numberFormat);
case 'boolean': return createBooleanFormatter(column.booleanValues);
case 'datetime': return createDateTimeFormatter(column.dateTimeFormat);
default: return createDefaultFormatter(column.allowHTML && column.highlightLinks);
}
}

View File

@@ -1,40 +1,124 @@
import moment from 'moment';
import { _, partial, isString } from 'underscore';
import _ from 'underscore';
import { getColumnCleanName } from '@/services/query-result';
import createFormatter from './formats';
import template from './table.html';
import editorTemplate from './table-editor.html';
import './table-editor.less';
function formatValue($filter, clientConfig, value, type) {
let formattedValue = value;
switch (type) {
case 'integer':
formattedValue = $filter('number')(value, 0);
break;
case 'float':
formattedValue = $filter('number')(value, 2);
break;
case 'boolean':
if (value !== undefined) {
formattedValue = String(value);
}
break;
case 'date':
if (value && moment.isMoment(value)) {
formattedValue = value.format(clientConfig.dateFormat);
}
break;
case 'datetime':
if (value && moment.isMoment(value)) {
formattedValue = value.format(clientConfig.dateTimeFormat);
}
break;
default:
if (isString(value)) {
formattedValue = $filter('linkify')(value);
}
break;
const ALLOWED_ITEM_PER_PAGE = [5, 10, 15, 20, 25];
const DISPLAY_AS_OPTIONS = [
{ name: 'Text', value: 'string' },
{ name: 'Number', value: 'number' },
{ name: 'Date/Time', value: 'datetime' },
{ name: 'Boolean', value: 'boolean' },
{ name: 'JSON', value: 'json' },
];
const DEFAULT_OPTIONS = {
itemsPerPage: 15,
defaultRows: 14,
defaultColumns: 4,
minColumns: 2,
};
function getColumnContentAlignment(type) {
return ['integer', 'float', 'boolean', 'date', 'datetime'].indexOf(type) >= 0 ? 'right' : 'left';
}
function getDefaultColumnsOptions(columns) {
const displayAs = {
integer: 'number',
float: 'number',
boolean: 'boolean',
date: 'datetime',
datetime: 'datetime',
};
return _.map(columns, (col, index) => ({
name: col.name,
type: col.type,
displayAs: displayAs[col.type] || 'string',
visible: true,
order: 100000 + index,
title: getColumnCleanName(col.name),
allowSearch: false,
allowHTML: false,
highlightLinks: false,
alignContent: getColumnContentAlignment(col.type),
}));
}
function getDefaultFormatOptions(column, clientConfig) {
const dateTimeFormat = {
date: clientConfig.dateFormat || 'DD/MM/YY',
datetime: clientConfig.dateTimeFormat || 'DD/MM/YY HH:mm',
};
const numberFormat = {
integer: clientConfig.integerFormat || '0,0',
float: clientConfig.floatFormat || '0,0.00',
};
return {
dateTimeFormat: dateTimeFormat[column.type],
numberFormat: numberFormat[column.type],
booleanValues: clientConfig.booleanValues || ['false', 'true'],
};
}
function wereColumnsReordered(queryColumns, visualizationColumns) {
queryColumns = _.map(queryColumns, col => col.name);
visualizationColumns = _.map(visualizationColumns, col => col.name);
// Some columns may be removed - so skip them (but keep original order)
visualizationColumns = _.filter(visualizationColumns, col => _.includes(queryColumns, col));
// Pick query columns that were previously saved with viz (but keep order too)
queryColumns = _.filter(queryColumns, col => _.includes(visualizationColumns, col));
// Both array now have the same size as they both contains only common columns
// (in fact, it was an intersection, that kept order of items on both arrays).
// Now check for equality item-by-item; if common columns are in the same order -
// they were not reordered in editor
for (let i = 0; i < queryColumns.length; i += 1) {
if (visualizationColumns[i] !== queryColumns[i]) {
return true;
}
}
return false;
}
function getColumnsOptions(columns, visualizationColumns) {
const options = getDefaultColumnsOptions(columns);
if ((wereColumnsReordered(columns, visualizationColumns))) {
visualizationColumns = _.object(_.map(
visualizationColumns,
(col, index) => [col.name, _.extend({}, col, { order: index })],
));
} else {
visualizationColumns = _.object(_.map(
visualizationColumns,
col => [col.name, _.omit(col, 'order')],
));
}
return formattedValue;
_.each(options, col => _.extend(col, visualizationColumns[col.name]));
return _.sortBy(options, 'order');
}
function getColumnsToDisplay(columns, options, clientConfig) {
columns = _.object(_.map(columns, col => [col.name, col]));
let result = _.map(options, col => _.extend(
getDefaultFormatOptions(col, clientConfig),
col,
columns[col.name],
));
result = _.map(result, col => _.extend(col, {
formatFunction: createFormatter(col),
}));
return _.sortBy(_.filter(result, 'visible'), 'order');
}
function GridRenderer(clientConfig) {
@@ -42,33 +126,69 @@ function GridRenderer(clientConfig) {
restrict: 'E',
scope: {
queryResult: '=',
itemsPerPage: '=',
options: '=',
},
template,
replace: false,
controller($scope, $filter) {
controller($scope) {
$scope.gridColumns = [];
$scope.gridRows = [];
$scope.$watch('queryResult && queryResult.getData()', (queryResult) => {
if (!queryResult) {
return;
}
function update() {
if ($scope.queryResult.getData() == null) {
$scope.gridColumns = [];
$scope.filters = [];
} else {
$scope.filters = $scope.queryResult.getFilters();
const columns = $scope.queryResult.getColumns();
columns.forEach((col) => {
col.title = getColumnCleanName(col.name);
col.formatFunction = partial(formatValue, $filter, clientConfig, _, col.type);
});
$scope.gridRows = $scope.queryResult.getData();
$scope.gridColumns = columns;
const columns = $scope.queryResult.getColumns();
const columnsOptions = getColumnsOptions(columns, _.extend({}, $scope.options).columns);
$scope.gridColumns = getColumnsToDisplay(columns, columnsOptions, clientConfig);
}
}
$scope.$watch('queryResult && queryResult.getData()', (queryResult) => {
if (queryResult) {
update();
}
});
$scope.$watch('options', (newValue, oldValue) => {
if (newValue !== oldValue) {
update();
}
}, true);
},
};
}
function GridEditor(clientConfig) {
return {
restrict: 'E',
template: editorTemplate,
link: ($scope) => {
$scope.allowedItemsPerPage = ALLOWED_ITEM_PER_PAGE;
$scope.displayAsOptions = DISPLAY_AS_OPTIONS;
$scope.currentTab = 'columns';
$scope.setCurrentTab = (tab) => {
$scope.currentTab = tab;
};
$scope.$watch('visualization', () => {
if ($scope.visualization) {
// For existing visualization - set default options
$scope.visualization.options = _.extend({}, DEFAULT_OPTIONS, $scope.visualization.options);
}
});
$scope.$watch('queryResult && queryResult.getData()', (queryResult) => {
if (queryResult) {
const columns = $scope.queryResult.getData() !== null ? $scope.queryResult.getColumns() : [];
$scope.visualization.options.columns = _.map(
getColumnsOptions(columns, $scope.visualization.options.columns),
col => _.extend(getDefaultFormatOptions(col, clientConfig), col),
);
}
});
},
@@ -76,20 +196,18 @@ function GridRenderer(clientConfig) {
}
export default function init(ngModule) {
ngModule.directive('gridRenderer', GridRenderer);
ngModule.directive('gridEditor', GridEditor);
ngModule.config((VisualizationProvider) => {
const defaultOptions = {
defaultRows: 14,
defaultColumns: 4,
minColumns: 2,
};
const defaultOptions = DEFAULT_OPTIONS;
VisualizationProvider.registerVisualization({
type: 'TABLE',
name: 'Table',
renderTemplate: '<grid-renderer options="visualization.options" query-result="queryResult"></grid-renderer>',
skipTypes: true,
editorTemplate: '<grid-editor></grid-editor>',
defaultOptions,
});
});
ngModule.directive('gridRenderer', GridRenderer);
}

View File

@@ -0,0 +1,103 @@
<div class="table-editor-container">
<ul class="tab-nav">
<li ng-class="{active: currentTab == 'columns'}" ng-if="options.globalSeriesType != 'custom'">
<a ng-click="setCurrentTab('columns')">Columns</a>
</li>
<li ng-class="{active: currentTab == 'grid'}">
<a ng-click="setCurrentTab('grid')">Grid</a>
</li>
</ul>
<div ng-if="currentTab == 'grid'" class="m-t-10 m-b-10">
<div class="form-group">
<label>Items per page</label>
<select ng-options="value for value in allowedItemsPerPage"
ng-model="visualization.options.itemsPerPage" class="form-control">
</select>
</div>
</div>
<div ng-if="currentTab == 'columns'" class="table-editor-query-columns m-t-10 m-b-10"
ui-sortable ng-model="visualization.options.columns">
<div ng-repeat="column in visualization.options.columns">
<div class="table-editor-column-header form-group">
<div class="input-group">
<span class="input-group-addon"><input type="checkbox" ng-model="column.visible"></span>
<input class="form-control" ng-model="column.title" ng-model-options="{ allowInvalid: true, debounce: 200 }">
</div>
</div>
<div class="form-group">
<div class="btn-group btn-group-justified">
<button type="button" class="btn btn-default btn-xs"
ng-click="column.alignContent = 'left'"
ng-class="{active: column.alignContent == 'left'}"><i class="fa fa-align-left"></i></button>
<button type="button" class="btn btn-default btn-xs"
ng-click="column.alignContent = 'center'"
ng-class="{active: column.alignContent == 'center'}"><i class="fa fa-align-center"></i></button>
<button type="button" class="btn btn-default btn-xs"
ng-click="column.alignContent = 'right'"
ng-class="{active: column.alignContent == 'right'}"><i class="fa fa-align-right"></i></button>
</div>
</div>
<div class="form-group">
<label><input type="checkbox" ng-model="column.allowSearch"> Use for search</label>
</div>
<div class="form-group">
<label>Display as:</label>
<select ng-options="item.value as item.name for item in displayAsOptions"
ng-model="column.displayAs" class="form-control">
</select>
</div>
<div ng-if="column.displayAs == 'string'">
<div class="form-group">
<label class="ui-sortable-bypass"><input type="checkbox" ng-model="column.allowHTML"> Allow HTML content</label>
</div>
<div ng-if="column.allowHTML" class="form-group">
<label class="ui-sortable-bypass"><input type="checkbox" ng-model="column.highlightLinks"> Highlight links</label>
</div>
</div>
<div ng-if="column.displayAs == 'number'">
<div class="form-group">
<label for="table-editor-{{ column.name }}-number-format">
Number format
<span class="m-l-5"
uib-popover-html="'Format <a href=&quot;http://numeraljs.com/&quot; target=&quot;_blank&quot;>specs.</a>'"
popover-trigger="'click outsideClick'"><i class="fa fa-question-circle"></i></span>
</label>
<input class="form-control" ng-model="column.numberFormat" ng-model-options="{ allowInvalid: true, debounce: 200 }"
id="table-editor-{{ column.name }}-number-format">
</div>
</div>
<div ng-if="column.displayAs == 'datetime'">
<div class="form-group">
<label for="table-editor-{{ column.name }}-datetime-format">
Date/Time format
<span class="m-l-5"
uib-popover-html="'Format <a href=&quot;http://momentjs.com/docs/#/displaying/format/&quot; target=&quot;_blank&quot;>specs.</a>'"
popover-trigger="'click outsideClick'"><i class="fa fa-question-circle"></i></span>
</label>
<input class="form-control" ng-model="column.dateTimeFormat" ng-model-options="{ allowInvalid: true, debounce: 200 }"
id="table-editor-{{ column.name }}-datetime-format">
</div>
</div>
<div ng-if="column.displayAs == 'boolean'">
<div class="form-group">
<label for="table-editor-{{ column.name }}-boolean-false">Value for <code>false</code></label>
<input class="form-control" ng-model="column.booleanValues[0]" ng-model-options="{ allowInvalid: true, debounce: 200 }"
id="table-editor-{{ column.name }}-boolean-false">
</div>
<div class="form-group">
<label for="table-editor-{{ column.name }}-boolean-true">Value for <code>true</code></label>
<input class="form-control" ng-model="column.booleanValues[1]" ng-model-options="{ allowInvalid: true, debounce: 200 }"
id="table-editor-{{ column.name }}-boolean-true">
</div>
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,43 @@
.table-editor-container {
.btn-group.btn-group-justified {
display: flex;
align-items: stretch;
justify-content: stretch;
.btn {
flex-grow: 1;
}
.btn-xs {
padding: 4px;
}
}
.table-editor-query-columns {
display: flex;
flex-direction: row;
justify-content: flex-start;
align-items: stretch;
overflow: auto;
> div {
min-width: 200px;
width: 200px;
padding: 0 10px;
border-right: 1px solid #f0f0f0;
cursor: move;
&:last-child {
border-right: none;
}
}
.table-editor-column-header {
background: rgba(102, 136, 153, 0.05);
padding: 10px;
margin-left: -10px;
margin-right: -10px;
border-bottom: 1px solid #f0f0f0;
}
}
}

View File

@@ -1 +1 @@
<dynamic-table rows="gridRows" columns="gridColumns"></dynamic-table>
<dynamic-table rows="gridRows" columns="gridColumns" items-per-page="options.itemsPerPage"></dynamic-table>

708
package-lock.json generated
View File

@@ -207,7 +207,7 @@
"resolved": "https://registry.npmjs.org/angular-moment/-/angular-moment-1.1.0.tgz",
"integrity": "sha512-aWrKEfQRtkqhxx31RjPhmGMWuF77uPlIWRNOiJU3+Q3wzossBqzBlJOkagUGt3N5or6OJP5Pq83dIjH9HHhhng==",
"requires": {
"moment": "2.18.1"
"moment": "2.19.3"
}
},
"angular-resizable": {
@@ -289,54 +289,6 @@
"normalize-path": "2.1.1"
}
},
"aproba": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz",
"integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==",
"dev": true
},
"are-we-there-yet": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.4.tgz",
"integrity": "sha1-u13KOCu5TwXhUZQ3PRb9O6HKEQ0=",
"dev": true,
"requires": {
"delegates": "1.0.0",
"readable-stream": "2.3.3"
},
"dependencies": {
"isarray": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
"integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=",
"dev": true
},
"readable-stream": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz",
"integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==",
"dev": true,
"requires": {
"core-util-is": "1.0.2",
"inherits": "2.0.3",
"isarray": "1.0.0",
"process-nextick-args": "1.0.7",
"safe-buffer": "5.1.1",
"string_decoder": "1.0.3",
"util-deprecate": "1.0.2"
}
},
"string_decoder": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz",
"integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==",
"dev": true,
"requires": {
"safe-buffer": "5.1.1"
}
}
}
},
"argparse": {
"version": "1.0.9",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.9.tgz",
@@ -489,12 +441,6 @@
"integrity": "sha1-GdOGodntxufByF04iu28xW0zYC0=",
"dev": true
},
"async-foreach": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/async-foreach/-/async-foreach-0.1.3.tgz",
"integrity": "sha1-NhIfhFwFeBct5Bmpfb6x0W7DRUI=",
"dev": true
},
"asynckit": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
@@ -1481,15 +1427,6 @@
}
}
},
"block-stream": {
"version": "0.0.9",
"resolved": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.9.tgz",
"integrity": "sha1-E+v+d4oDIFz+A3UUgeu0szAMEmo=",
"dev": true,
"requires": {
"inherits": "2.0.3"
}
},
"bluebird": {
"version": "3.5.0",
"resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.0.tgz",
@@ -2144,18 +2081,6 @@
"integrity": "sha1-Jgt6meux7f4kdTgXX3gyQ8sZ0Uk=",
"dev": true
},
"clone-deep": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-0.3.0.tgz",
"integrity": "sha1-NIxhrpzb4O3+BT2R/0zFIdeQ7eg=",
"dev": true,
"requires": {
"for-own": "1.0.0",
"is-plain-object": "2.0.4",
"kind-of": "3.2.2",
"shallow-clone": "0.1.2"
}
},
"co": {
"version": "4.6.0",
"resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",
@@ -2409,12 +2334,6 @@
"date-now": "0.1.4"
}
},
"console-control-strings": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz",
"integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=",
"dev": true
},
"constants-browserify": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz",
@@ -2898,12 +2817,6 @@
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
"integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk="
},
"delegates": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz",
"integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=",
"dev": true
},
"depd": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/depd/-/depd-1.1.1.tgz",
@@ -4245,15 +4158,6 @@
"integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=",
"dev": true
},
"for-own": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/for-own/-/for-own-1.0.0.tgz",
"integrity": "sha1-xjMy9BXO3EsE2/5wz4NklMU8tEs=",
"dev": true,
"requires": {
"for-in": "1.0.2"
}
},
"foreach": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.5.tgz",
@@ -4329,18 +4233,6 @@
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
"integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8="
},
"fstream": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.11.tgz",
"integrity": "sha1-XB+x8RdHcRTwYyoOtLcbPLD9MXE=",
"dev": true,
"requires": {
"graceful-fs": "4.1.11",
"inherits": "2.0.3",
"mkdirp": "0.5.1",
"rimraf": "2.6.2"
}
},
"function-bind": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
@@ -4356,62 +4248,6 @@
"resolved": "https://registry.npmjs.org/gamma/-/gamma-0.1.0.tgz",
"integrity": "sha1-MxVkNAO/J5BsqAqzfDbs6UQO8zA="
},
"gauge": {
"version": "2.7.4",
"resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz",
"integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=",
"dev": true,
"requires": {
"aproba": "1.2.0",
"console-control-strings": "1.1.0",
"has-unicode": "2.0.1",
"object-assign": "4.1.1",
"signal-exit": "3.0.2",
"string-width": "1.0.2",
"strip-ansi": "3.0.1",
"wide-align": "1.1.2"
},
"dependencies": {
"is-fullwidth-code-point": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz",
"integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=",
"dev": true,
"requires": {
"number-is-nan": "1.0.1"
}
},
"string-width": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
"integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
"dev": true,
"requires": {
"code-point-at": "1.1.0",
"is-fullwidth-code-point": "1.0.0",
"strip-ansi": "3.0.1"
}
},
"strip-ansi": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
"integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
"dev": true,
"requires": {
"ansi-regex": "2.1.1"
}
}
}
},
"gaze": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/gaze/-/gaze-1.1.2.tgz",
"integrity": "sha1-hHIkZ3rbiHDWeSV+0ziP22HkAQU=",
"dev": true,
"requires": {
"globule": "1.2.0"
}
},
"geojson-area": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/geojson-area/-/geojson-area-0.1.0.tgz",
@@ -5669,17 +5505,6 @@
"pinkie-promise": "2.0.1"
}
},
"globule": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/globule/-/globule-1.2.0.tgz",
"integrity": "sha1-HcScaCLdnoovoAuiopUAboZkvQk=",
"dev": true,
"requires": {
"glob": "7.1.2",
"lodash": "4.17.4",
"minimatch": "3.0.4"
}
},
"glsl-inject-defines": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/glsl-inject-defines/-/glsl-inject-defines-1.0.3.tgz",
@@ -5925,12 +5750,6 @@
"is-browser": "2.0.1"
}
},
"has-unicode": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz",
"integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=",
"dev": true
},
"hash-base": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/hash-base/-/hash-base-2.0.2.tgz",
@@ -6337,12 +6156,6 @@
"integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=",
"dev": true
},
"in-publish": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/in-publish/-/in-publish-2.0.0.tgz",
"integrity": "sha1-4g/146KvwmkDILbcVSaCqcf631E=",
"dev": true
},
"incremental-convex-hull": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/incremental-convex-hull/-/incremental-convex-hull-1.0.1.tgz",
@@ -6680,15 +6493,6 @@
"resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz",
"integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4="
},
"is-plain-object": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz",
"integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==",
"dev": true,
"requires": {
"isobject": "3.0.1"
}
},
"is-posix-bracket": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz",
@@ -6796,12 +6600,6 @@
"integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=",
"dev": true
},
"isobject": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz",
"integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=",
"dev": true
},
"isstream": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
@@ -7387,12 +7185,6 @@
"integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY=",
"dev": true
},
"lodash.clonedeep": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz",
"integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=",
"dev": true
},
"lodash.cond": {
"version": "4.5.2",
"resolved": "https://registry.npmjs.org/lodash.cond/-/lodash.cond-4.5.2.tgz",
@@ -7449,18 +7241,6 @@
"integrity": "sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=",
"dev": true
},
"lodash.mergewith": {
"version": "4.6.0",
"resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.0.tgz",
"integrity": "sha1-FQzwoWeR9ZA7iJHqsVRgknS96lU=",
"dev": true
},
"lodash.tail": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/lodash.tail/-/lodash.tail-4.1.1.tgz",
"integrity": "sha1-0jM6NtnncXyK0vfKyv7HwytERmQ=",
"dev": true
},
"lodash.toarray": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/lodash.toarray/-/lodash.toarray-4.4.0.tgz",
@@ -8005,24 +7785,6 @@
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
"integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ="
},
"mixin-object": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/mixin-object/-/mixin-object-2.0.1.tgz",
"integrity": "sha1-T7lJRB2rGCVA8f4DW6YOGUel5X4=",
"dev": true,
"requires": {
"for-in": "0.1.8",
"is-extendable": "0.1.1"
},
"dependencies": {
"for-in": {
"version": "0.1.8",
"resolved": "https://registry.npmjs.org/for-in/-/for-in-0.1.8.tgz",
"integrity": "sha1-2Hc5COMSVhCZUrH9ubP6hn0ndeE=",
"dev": true
}
}
},
"mkdirp": {
"version": "0.5.1",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
@@ -8041,9 +7803,9 @@
}
},
"moment": {
"version": "2.18.1",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.18.1.tgz",
"integrity": "sha1-w2GT3Tzhwu7SrbfIAtu8d6gbHA8="
"version": "2.19.3",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.19.3.tgz",
"integrity": "sha1-vbmdJw1tf9p4zA+6zoVeJ/59pp8="
},
"monotone-convex-hull-2d": {
"version": "1.0.1",
@@ -8146,12 +7908,6 @@
"integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=",
"dev": true
},
"nan": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/nan/-/nan-2.7.0.tgz",
"integrity": "sha1-2Vv3IeyHfgjbJ27T/G63j5CDrUY=",
"dev": true
},
"natural-compare": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
@@ -8297,35 +8053,6 @@
"integrity": "sha1-RjgRh59XPUUVWtap9D3ClujoXrw=",
"dev": true
},
"node-gyp": {
"version": "3.6.2",
"resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-3.6.2.tgz",
"integrity": "sha1-m/vlRWIoYoSDjnUOrAUpWFP6HGA=",
"dev": true,
"requires": {
"fstream": "1.0.11",
"glob": "7.1.2",
"graceful-fs": "4.1.11",
"minimatch": "3.0.4",
"mkdirp": "0.5.1",
"nopt": "2.1.2",
"npmlog": "4.1.2",
"osenv": "0.1.4",
"request": "2.83.0",
"rimraf": "2.6.2",
"semver": "5.3.0",
"tar": "2.2.1",
"which": "1.3.0"
},
"dependencies": {
"semver": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz",
"integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8=",
"dev": true
}
}
},
"node-libs-browser": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-2.0.0.tgz",
@@ -8418,72 +8145,6 @@
}
}
},
"node-sass": {
"version": "4.5.3",
"resolved": "https://registry.npmjs.org/node-sass/-/node-sass-4.5.3.tgz",
"integrity": "sha1-0JydEXlkEjnRuX/8YjH9zsU+FWg=",
"dev": true,
"requires": {
"async-foreach": "0.1.3",
"chalk": "1.1.3",
"cross-spawn": "3.0.1",
"gaze": "1.1.2",
"get-stdin": "4.0.1",
"glob": "7.1.2",
"in-publish": "2.0.0",
"lodash.assign": "4.2.0",
"lodash.clonedeep": "4.5.0",
"lodash.mergewith": "4.6.0",
"meow": "3.7.0",
"mkdirp": "0.5.1",
"nan": "2.7.0",
"node-gyp": "3.6.2",
"npmlog": "4.1.2",
"request": "2.83.0",
"sass-graph": "2.2.4",
"stdout-stream": "1.4.0"
},
"dependencies": {
"ansi-styles": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz",
"integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=",
"dev": true
},
"chalk": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
"integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
"dev": true,
"requires": {
"ansi-styles": "2.2.1",
"escape-string-regexp": "1.0.5",
"has-ansi": "2.0.0",
"strip-ansi": "3.0.1",
"supports-color": "2.0.0"
}
},
"cross-spawn": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-3.0.1.tgz",
"integrity": "sha1-ElYDfsufDF9549bvE14wdwGEuYI=",
"dev": true,
"requires": {
"lru-cache": "4.1.1",
"which": "1.3.0"
}
},
"strip-ansi": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
"integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
"dev": true,
"requires": {
"ansi-regex": "2.1.1"
}
}
}
},
"nomnom": {
"version": "1.8.1",
"resolved": "https://registry.npmjs.org/nomnom/-/nomnom-1.8.1.tgz",
@@ -8561,18 +8222,6 @@
"path-key": "2.0.1"
}
},
"npmlog": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz",
"integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==",
"dev": true,
"requires": {
"are-we-there-yet": "1.1.4",
"console-control-strings": "1.1.0",
"gauge": "2.7.4",
"set-blocking": "2.0.0"
}
},
"nth-check": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.1.tgz",
@@ -8594,6 +8243,11 @@
"integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=",
"dev": true
},
"numeral": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/numeral/-/numeral-2.0.6.tgz",
"integrity": "sha1-StCAk21EPCVhrtnyGX7//iX05QY="
},
"numeric": {
"version": "1.2.6",
"resolved": "https://registry.npmjs.org/numeric/-/numeric-1.2.6.tgz",
@@ -8768,16 +8422,6 @@
"integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=",
"dev": true
},
"osenv": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.4.tgz",
"integrity": "sha1-Qv5tWVPfBsgGS+bxdsPQWqqjRkQ=",
"dev": true,
"requires": {
"os-homedir": "1.0.2",
"os-tmpdir": "1.0.2"
}
},
"p-finally": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz",
@@ -10577,181 +10221,6 @@
"resolved": "https://registry.npmjs.org/sane-topojson/-/sane-topojson-2.0.0.tgz",
"integrity": "sha1-QOJXNqKMTM6qojP0W7hjc6J4W4Q="
},
"sass-graph": {
"version": "2.2.4",
"resolved": "https://registry.npmjs.org/sass-graph/-/sass-graph-2.2.4.tgz",
"integrity": "sha1-E/vWPNHK8JCLn9k0dq1DpR0eC0k=",
"dev": true,
"requires": {
"glob": "7.1.2",
"lodash": "4.17.4",
"scss-tokenizer": "0.2.3",
"yargs": "7.1.0"
},
"dependencies": {
"camelcase": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz",
"integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo=",
"dev": true
},
"cliui": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz",
"integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=",
"dev": true,
"requires": {
"string-width": "1.0.2",
"strip-ansi": "3.0.1",
"wrap-ansi": "2.1.0"
}
},
"find-up": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz",
"integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=",
"dev": true,
"requires": {
"path-exists": "2.1.0",
"pinkie-promise": "2.0.1"
}
},
"is-fullwidth-code-point": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz",
"integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=",
"dev": true,
"requires": {
"number-is-nan": "1.0.1"
}
},
"load-json-file": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz",
"integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=",
"dev": true,
"requires": {
"graceful-fs": "4.1.11",
"parse-json": "2.2.0",
"pify": "2.3.0",
"pinkie-promise": "2.0.1",
"strip-bom": "2.0.0"
}
},
"path-exists": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz",
"integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=",
"dev": true,
"requires": {
"pinkie-promise": "2.0.1"
}
},
"path-type": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz",
"integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=",
"dev": true,
"requires": {
"graceful-fs": "4.1.11",
"pify": "2.3.0",
"pinkie-promise": "2.0.1"
}
},
"read-pkg": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz",
"integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=",
"dev": true,
"requires": {
"load-json-file": "1.1.0",
"normalize-package-data": "2.4.0",
"path-type": "1.1.0"
}
},
"read-pkg-up": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz",
"integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=",
"dev": true,
"requires": {
"find-up": "1.1.2",
"read-pkg": "1.1.0"
}
},
"string-width": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
"integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
"dev": true,
"requires": {
"code-point-at": "1.1.0",
"is-fullwidth-code-point": "1.0.0",
"strip-ansi": "3.0.1"
}
},
"strip-ansi": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
"integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
"dev": true,
"requires": {
"ansi-regex": "2.1.1"
}
},
"strip-bom": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz",
"integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=",
"dev": true,
"requires": {
"is-utf8": "0.2.1"
}
},
"yargs": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-7.1.0.tgz",
"integrity": "sha1-a6MY6xaWFyf10oT46gA+jWFU0Mg=",
"dev": true,
"requires": {
"camelcase": "3.0.0",
"cliui": "3.2.0",
"decamelize": "1.2.0",
"get-caller-file": "1.0.2",
"os-locale": "1.4.0",
"read-pkg-up": "1.0.1",
"require-directory": "2.1.1",
"require-main-filename": "1.0.1",
"set-blocking": "2.0.0",
"string-width": "1.0.2",
"which-module": "1.0.0",
"y18n": "3.2.1",
"yargs-parser": "5.0.0"
}
}
}
},
"sass-loader": {
"version": "6.0.6",
"resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-6.0.6.tgz",
"integrity": "sha512-c3/Zc+iW+qqDip6kXPYLEgsAu2lf4xz0EZDplB7EmSUMda12U1sGJPetH55B/j9eu0bTtKzKlNPWWyYC7wFNyQ==",
"dev": true,
"requires": {
"async": "2.5.0",
"clone-deep": "0.3.0",
"loader-utils": "1.1.0",
"lodash.tail": "4.1.1",
"pify": "3.0.0"
},
"dependencies": {
"pify": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz",
"integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=",
"dev": true
}
}
},
"sax": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz",
@@ -10767,27 +10236,6 @@
"ajv": "5.2.3"
}
},
"scss-tokenizer": {
"version": "0.2.3",
"resolved": "https://registry.npmjs.org/scss-tokenizer/-/scss-tokenizer-0.2.3.tgz",
"integrity": "sha1-jrBtualyMzOCTT9VMGQRSYR85dE=",
"dev": true,
"requires": {
"js-base64": "2.3.2",
"source-map": "0.4.4"
},
"dependencies": {
"source-map": {
"version": "0.4.4",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz",
"integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=",
"dev": true,
"requires": {
"amdefine": "1.0.1"
}
}
}
},
"select-hose": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz",
@@ -10919,35 +10367,6 @@
"safe-buffer": "5.1.1"
}
},
"shallow-clone": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-0.1.2.tgz",
"integrity": "sha1-WQnodLp3EG1zrEFM/sH/yofZcGA=",
"dev": true,
"requires": {
"is-extendable": "0.1.1",
"kind-of": "2.0.1",
"lazy-cache": "0.2.7",
"mixin-object": "2.0.1"
},
"dependencies": {
"kind-of": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-2.0.1.tgz",
"integrity": "sha1-AY7HpM5+OobLkUG+UZ0kyPqpgbU=",
"dev": true,
"requires": {
"is-buffer": "1.1.5"
}
},
"lazy-cache": {
"version": "0.2.7",
"resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-0.2.7.tgz",
"integrity": "sha1-f+3fLctu23fRHvHRF6tf/fCrG2U=",
"dev": true
}
}
},
"shallow-copy": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/shallow-copy/-/shallow-copy-0.0.1.tgz",
@@ -11426,47 +10845,6 @@
"integrity": "sha1-+vUbnrdKrvOzrPStX2Gr8ky3uT4=",
"dev": true
},
"stdout-stream": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/stdout-stream/-/stdout-stream-1.4.0.tgz",
"integrity": "sha1-osfIWH5U2UJ+qe2zrD8s1SLfN4s=",
"dev": true,
"requires": {
"readable-stream": "2.3.3"
},
"dependencies": {
"isarray": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
"integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=",
"dev": true
},
"readable-stream": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz",
"integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==",
"dev": true,
"requires": {
"core-util-is": "1.0.2",
"inherits": "2.0.3",
"isarray": "1.0.0",
"process-nextick-args": "1.0.7",
"safe-buffer": "5.1.1",
"string_decoder": "1.0.3",
"util-deprecate": "1.0.2"
}
},
"string_decoder": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz",
"integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==",
"dev": true,
"requires": {
"safe-buffer": "5.1.1"
}
}
}
},
"stream-browserify": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.1.tgz",
@@ -11778,17 +11156,6 @@
"through": "2.3.8"
}
},
"tar": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/tar/-/tar-2.2.1.tgz",
"integrity": "sha1-jk0qJWwOIYXGsYrWlK7JaLg8sdE=",
"dev": true,
"requires": {
"block-stream": "0.0.9",
"fstream": "1.0.11",
"inherits": "2.0.3"
}
},
"text-cache": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/text-cache/-/text-cache-4.1.0.tgz",
@@ -12904,46 +12271,6 @@
"resolved": "https://registry.npmjs.org/whoots-js/-/whoots-js-2.1.0.tgz",
"integrity": "sha1-vLIBw04OrzNfzOWuLPh0V5qZxIc="
},
"wide-align": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.2.tgz",
"integrity": "sha512-ijDLlyQ7s6x1JgCLur53osjm/UXUYD9+0PbYKrBsYisYXzCxN+HC3mYDNy/dWdmf3AwqwU3CXwDCvsNgGK1S0w==",
"dev": true,
"requires": {
"string-width": "1.0.2"
},
"dependencies": {
"is-fullwidth-code-point": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz",
"integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=",
"dev": true,
"requires": {
"number-is-nan": "1.0.1"
}
},
"string-width": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
"integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
"dev": true,
"requires": {
"code-point-at": "1.1.0",
"is-fullwidth-code-point": "1.0.0",
"strip-ansi": "3.0.1"
}
},
"strip-ansi": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
"integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
"dev": true,
"requires": {
"ansi-regex": "2.1.1"
}
}
}
},
"window-size": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.0.tgz",
@@ -13051,23 +12378,6 @@
"window-size": "0.1.0"
}
},
"yargs-parser": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-5.0.0.tgz",
"integrity": "sha1-J17PDX/+Bcd+ZOfIbkzZS/DhIoo=",
"dev": true,
"requires": {
"camelcase": "3.0.0"
},
"dependencies": {
"camelcase": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz",
"integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo=",
"dev": true
}
}
},
"zero-crossings": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/zero-crossings/-/zero-crossings-1.0.1.tgz",

View File

@@ -51,9 +51,10 @@
"leaflet.markercluster": "^1.1.0",
"markdown": "0.5.0",
"material-design-iconic-font": "^2.2.0",
"moment": "^2.18.1",
"moment": "^2.19.3",
"mousetrap": "^1.6.1",
"mustache": "^2.3.0",
"numeral": "^2.0.6",
"pace-progress": "git+https://github.com/getredash/pace.git",
"pivottable": "^2.15.0",
"plotly.js": "1.30.1",

View File

@@ -28,6 +28,8 @@ const config = {
new webpack.DefinePlugin({
ON_TEST: process.env.NODE_ENV === 'test'
}),
// Enforce angular to use jQuery instead of jqLite
new webpack.ProvidePlugin({'window.jQuery': 'jquery'}),
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor',
minChunks: function (module, count) {