<commit_before>/**
 * @copyright Copyright 2014 Google Inc. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * @fileoverview A class that translates a query config model to a set of
 * query properties for producing SQL statements.
 * @author joemu@google.com (Joe Allan Muharsky)
 */

goog.provide('p3rf.perfkit.explorer.models.perfkit_simple_builder.Aggregation');
goog.provide('p3rf.perfkit.explorer.models.perfkit_simple_builder.QueryBuilderService');

goog.require('p3rf.perfkit.explorer.components.query_builder.Filter');
goog.require('p3rf.perfkit.explorer.components.query_builder.FilterClause');
goog.require('p3rf.perfkit.explorer.components.query_builder.QueryBuilder');
goog.require('p3rf.perfkit.explorer.components.query_builder.QueryProperties');
goog.require('p3rf.perfkit.explorer.dateUtil');
goog.require('p3rf.perfkit.explorer.models.perfkit_simple_builder.DateFilter');
goog.require('p3rf.perfkit.explorer.models.perfkit_simple_builder.MetadataFilter');
goog.require('p3rf.perfkit.explorer.models.perfkit_simple_builder.QueryColumnModel');
goog.require('p3rf.perfkit.explorer.models.perfkit_simple_builder.QueryConfigModel');
goog.require('p3rf.perfkit.explorer.models.perfkit_simple_builder.QueryFilterModel');
goog.require('p3rf.perfkit.explorer.models.perfkit_simple_builder.QueryTablePartitioning');

goog.scope(function() {

var explorer = p3rf.perfkit.explorer;
var DateFilter = explorer.models.perfkit_simple_builder.DateFilter;
var DateFilterType = explorer.models.perfkit_simple_builder.DateFilterType;
var Filter = explorer.components.query_builder.Filter;
var FilterClause = explorer.components.query_builder.FilterClause;
var BigQueryBuilder = explorer.components.query_builder.QueryBuilder;
var QueryProperties = explorer.components.query_builder.QueryProperties;
var dateUtil = explorer.dateUtil;
var MetadataFilter = explorer.models.perfkit_simple_builder.MetadataFilter;
var QueryColumnModel = explorer.models.perfkit_simple_builder.QueryColumnModel;
var QueryConfigModel = explorer.models.perfkit_simple_builder.QueryConfigModel;
var QueryFilterModel = explorer.models.perfkit_simple_builder.QueryFilterModel;
var QueryTablePartitioning = explorer.models.perfkit_simple_builder.QueryTablePartitioning;

/**
 * Enum for QueryProperties aggregations.  The supported aggregations are
 * either natively supported by big query or explicitly supported by our query
 * builders.  We also supported arbitrary percentile aggregations of the form
 * INTEGER|FLOAT% so '50%' or '.01%'.
 * @enum {string}
 */
explorer.models.perfkit_simple_builder.Aggregation = {
  AVERAGE: 'avg',
  COUNT: 'count',
  LAST: 'last',
  MAX: 'max',
  MEAN: 'mean',
  MIN: 'min',
  STDDEV: 'stddev',
  SUM: 'sum',
  VARIANCE: 'variance'
};
var Aggregation = explorer.models.perfkit_simple_builder.Aggregation;

/**
 * The QueryBuilder service transforms a query model into SQL.
 *
 * @param {!Angular.FilterService} $filter
 * @constructor
 *
 */
explorer.models.perfkit_simple_builder.QueryBuilderService = function(
    $filter) {
};
var QueryBuilderService =
    explorer.models.perfkit_simple_builder.QueryBuilderService;

/**
 * Returns a display mode based on the value of a filter.
 * @param {Array.<(string|number|null)>=} opt_values The values to match.
 * @return {Filter.DisplayMode} The display mode to use for the column.
 */
QueryBuilderService.prototype.getColumnDisplayMode = function(opt_values) {
  var visibility = Filter.DisplayMode.COLUMN;
  var value = (
      goog.isDef(opt_values) &&
      !goog.isNull(opt_values) &&
      opt_values.length > 0) ?
      opt_values[0] : null;

  if (goog.isDef(value) && !goog.isNull(value) &&
      !(goog.isString(value) && goog.string.isEmptySafe(value))) {
    visibility = Filter.DisplayMode.HIDDEN;
  }

  return visibility;
};

/**
 * Creates a filter that deals with a single value and clause.  This improves
 * readability of filtering logic, as the UX doesn't presently support multi-
 * values or clauses.  Columns with explicit filters are hidden by default,
 * as they're the same value across the result set.
 * @param {string} fieldName The name of the field to filter on.
 * @param {Array.<(string|number|null)>=} opt_values The list of values to
 *     filter on.
 * @param {?FilterClause.MatchRule=} opt_matchRule The type of matching to
 *     perform.  Defaults to EQ (equals).
 * @param {?Filter.DisplayMode=} opt_displayMode The display mode for the
 *     filter.  Defaults to COLUMN if no value is specified, and HIDDEN if a
 *     value is provided.
 * @param {string=} opt_fieldAlias The alias to use for the field.
 * @return {Filter} A Filter expression.
 */
QueryBuilderService.prototype.createSimpleFilter = function(
    fieldName, opt_values, opt_matchRule, opt_displayMode, opt_fieldAlias) {
  var matchRule = opt_matchRule ? opt_matchRule : FilterClause.MatchRule.EQ;
  var displayMode = opt_displayMode ? opt_displayMode :
      this.getColumnDisplayMode(opt_values);

  var clauses = [];

  if (goog.isDef(opt_values) && !goog.isNull(opt_values)) {
    for (var ctr = 0, len = opt_values.length; ctr < len; ctr++) {
      var value = opt_values[ctr];

      if (!goog.string.isEmptySafe(value)) {
        clauses.push(new FilterClause([value], matchRule));
      }
    }
  }
  var filter = new Filter(fieldName, clauses, displayMode, opt_fieldAlias);
  return filter;
};

/**
 * Returns a date filter clause based on a relative date (last n days, etc.).
 * @param {*} dateFilter A date filter expression.
 * @return {string} A BigQuery function representing the relative date.
 */
QueryBuilderService.prototype.getRelativeDateFunction = function(dateFilter) {
  return ('DATE_ADD(CURRENT_TIMESTAMP(), -' +
      dateFilter.filter_value + ', "' +
      dateFilter.filter_type + '")');
};

/**
 * Returns a date filter clause based on a relative date (last n days, etc.).
 * @param {*} dateFilter A date filter expression.
 * @return {string} A BigQuery function representing the relative date.
 */
QueryBuilderService.prototype.getAbsoluteDateFunction = function(dateFilter) {
  return ('TIMESTAMP(\'' + dateFilter.text + '\')');
};

/**
 * Returns a SQL statement based on the state of a query.
 * @param {!QueryConfigModel} model a QueryConfigModel that describes a query.
 * @param {?string} defaultProjectId
 * @param {?string} defaultDatasetName
 * @param {?string} defaultTableName
 * @return {string} A formatted SQL statement.
 */
QueryBuilderService.prototype.getSql = function(
    model, defaultProjectId, defaultDatasetName, defaultTableName, defaultTablePartition) {
  var fieldFilters = [];
  var startFilter = null;
  var endFilter = null;

  if (model.filters.start_date) {
    var startDateClause = null;

    switch (model.filters.start_date.filter_type) {
      case DateFilterType.CUSTOM:
        startFilter = this.getAbsoluteDateFunction(model.filters.start_date);
        startDateClause = new FilterClause(
            ['TIMESTAMP_TO_SEC(' + startFilter + ')'], FilterClause.MatchRule.GE, true);

        break;
      default:
        startFilter = this.getRelativeDateFunction(model.filters.start_date);
        startDateClause = new FilterClause(
            ['TIMESTAMP_TO_SEC(' + startFilter + ')'], FilterClause.MatchRule.GE, true);

        break;
    }
  }

  if (model.filters.end_date) {
    var endDateClause = null;

    switch (model.filters.end_date.filter_type) {
      case DateFilterType.CUSTOM:
        endFilter = this.getAbsoluteDateFunction(model.filters.end_date);
        endDateClause = new FilterClause(
            ['TIMESTAMP_TO_SEC(' + endFilter + ')'], FilterClause.MatchRule.LE, true);

        break;
      default:
        endDateClause = new FilterClause(
            ['TIMESTAMP_TO_SEC(' + endFilter + ')'], FilterClause.MatchRule.LE, true);

        break;
    }
  }

  startDateClause && fieldFilters.push(
      new Filter('timestamp', [startDateClause], Filter.DisplayMode.HIDDEN));

  endDateClause && fieldFilters.push(
      new Filter('timestamp', [endDateClause], Filter.DisplayMode.HIDDEN));

  model.filters.product_name && fieldFilters.push(
      this.createSimpleFilter('product_name', [model.filters.product_name], null, Filter.DisplayMode.HIDDEN));

  model.filters.test && fieldFilters.push(
      this.createSimpleFilter('test', [model.filters.test], null, Filter.DisplayMode.HIDDEN));

  model.filters.metric && fieldFilters.push(
      this.createSimpleFilter('metric', [model.filters.metric], null, Filter.DisplayMode.HIDDEN));

  model.filters.runby && fieldFilters.push(
      this.createSimpleFilter('owner', [model.filters.runby], null, Filter.DisplayMode.HIDDEN));

  var fieldSortOrders = [];

  angular.forEach(model.results.fields, angular.bind(this, function(field) {
    fieldFilters.push(
        new Filter(field.name, [], Filter.DisplayMode.COLUMN));
  }));

  if (model.results.show_date === true) {
    switch (model.results.date_group.toUpperCase()) {
      case '':
        fieldFilters.push(this.createSimpleFilter(
            'SEC_TO_TIMESTAMP(INTEGER(timestamp))',
            null, null, null, 'date'));
        break;
      case 'WEEK':
        fieldFilters.push(this.createSimpleFilter(
            ('USEC_TO_TIMESTAMP(UTC_USEC_TO_WEEK(INTEGER(timestamp * ' +
             dateUtil.BQ_TIMESTAMP_MULTIPLIER + '), ' +
             dateUtil.BQ_FIRST_DAY_OF_WEEK + '))'),
            null, null, null, 'date'));
        break;
      default:
        fieldFilters.push(this.createSimpleFilter(
            ('USEC_TO_TIMESTAMP(UTC_USEC_TO_' + model.results.date_group.toUpperCase() +
             '(INTEGER(timestamp * ' +
             dateUtil.BQ_TIMESTAMP_MULTIPLIER + ')))'),
            null, null, null, 'date'));
        break;
    }
  }

  if (!model.filters.product_name) { fieldSortOrders.push('product_name'); }
  if (!model.filters.test) { fieldSortOrders.push('test'); }
  if (!model.filters.metric) { fieldSortOrders.push('metric'); }
  if (model.results.show_date) {
    fieldSortOrders.push('date');
  }

  for (var ctr = 0, len = model.results.labels.length;
       ctr < len; ctr++) {
    var label = model.results.labels[ctr]['label'];
    if (goog.isDef(label) && !goog.string.isEmpty(label)) {
      var field = 'REGEXP_EXTRACT(labels, r\'\\|' + label + ':(.*?)\\|\')';
      fieldFilters.push(this.createSimpleFilter(
          field, null, null, null, label));
    }
  }

  if (model.filters.official == 'true') {
    fieldFilters.push(this.createSimpleFilter('official', [true]));
  } else if (model.filters.official == 'false') {
    fieldFilters.push(this.createSimpleFilter('official', [false]));
  }

  for (var ctr = 0, len = model.filters.metadata.length;
       ctr < len; ctr++) {
    var label = '|' + model.filters.metadata[ctr]['text'] + '|';

    fieldFilters.push(this.createSimpleFilter(
        'labels', [label],
        FilterClause.MatchRule.CT,
        Filter.DisplayMode.HIDDEN));
  }

  var aggregations = [];

  if (model.results.measure_values) {
    aggregations = [];

    angular.forEach(model.results.measures, function(measure) {
      aggregations.push(measure.name);
    });
  } else {
    fieldFilters.push(this.createSimpleFilter('value'));
  }

  var queryProperties = new QueryProperties(
      aggregations,
      fieldFilters,
      []);

  var projectId = model.results.project_id || defaultProjectId;
  var datasetName = model.results.dataset_name || defaultDatasetName;
  var tableName = model.results.table_name || defaultTableName;
  var tablePartition = model.results.table_partition || defaultTablePartition;

  var tableId = datasetName + '.' + tableName;

  if (projectId) {
    tableId = projectId + ':' + tableId;
  }

  var tableExpr = '';

  if (model.results.table_partition == QueryTablePartitioning.PERDAY) {
    if (!startFilter) {
      throw 'Start date is required when PERDAY table partitioning is used.';
    }

    if (!endFilter) {
      endFilter = 'CURRENT_TIMESTAMP()';
    }

    tableExpression = '(TABLE_DATE_RANGE([' + tableId + '], ' + startFilter + ', ' + endFilter + '))';
  } else {
    tableExpression = '[' + tableId + ']';
  }

  var sql = BigQueryBuilder.formatQuery(
      BigQueryBuilder.buildSelectArgs(queryProperties),
      [tableExpression],
      BigQueryBuilder.buildWhereArgs(queryProperties),
      BigQueryBuilder.buildGroupArgs(queryProperties),
      fieldSortOrders,
      model.results.row_limit);

  return sql;
};

});  // goog.scope
<commit_msg>Updated logic to use defaultConfig correctly in rewriteQuery().<commit_after>/**
 * @copyright Copyright 2014 Google Inc. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * @fileoverview A class that translates a query config model to a set of
 * query properties for producing SQL statements.
 * @author joemu@google.com (Joe Allan Muharsky)
 */

goog.provide('p3rf.perfkit.explorer.models.perfkit_simple_builder.Aggregation');
goog.provide('p3rf.perfkit.explorer.models.perfkit_simple_builder.QueryBuilderService');

goog.require('p3rf.perfkit.explorer.components.query_builder.Filter');
goog.require('p3rf.perfkit.explorer.components.query_builder.FilterClause');
goog.require('p3rf.perfkit.explorer.components.query_builder.QueryBuilder');
goog.require('p3rf.perfkit.explorer.components.query_builder.QueryProperties');
goog.require('p3rf.perfkit.explorer.dateUtil');
goog.require('p3rf.perfkit.explorer.models.perfkit_simple_builder.DateFilter');
goog.require('p3rf.perfkit.explorer.models.perfkit_simple_builder.MetadataFilter');
goog.require('p3rf.perfkit.explorer.models.perfkit_simple_builder.QueryColumnModel');
goog.require('p3rf.perfkit.explorer.models.perfkit_simple_builder.QueryConfigModel');
goog.require('p3rf.perfkit.explorer.models.perfkit_simple_builder.QueryFilterModel');
goog.require('p3rf.perfkit.explorer.models.perfkit_simple_builder.QueryTablePartitioning');

goog.scope(function() {

var explorer = p3rf.perfkit.explorer;
var DateFilter = explorer.models.perfkit_simple_builder.DateFilter;
var DateFilterType = explorer.models.perfkit_simple_builder.DateFilterType;
var Filter = explorer.components.query_builder.Filter;
var FilterClause = explorer.components.query_builder.FilterClause;
var BigQueryBuilder = explorer.components.query_builder.QueryBuilder;
var QueryProperties = explorer.components.query_builder.QueryProperties;
var dateUtil = explorer.dateUtil;
var MetadataFilter = explorer.models.perfkit_simple_builder.MetadataFilter;
var QueryColumnModel = explorer.models.perfkit_simple_builder.QueryColumnModel;
var QueryConfigModel = explorer.models.perfkit_simple_builder.QueryConfigModel;
var QueryFilterModel = explorer.models.perfkit_simple_builder.QueryFilterModel;
var QueryTablePartitioning = explorer.models.perfkit_simple_builder.QueryTablePartitioning;

/**
 * Enum for QueryProperties aggregations.  The supported aggregations are
 * either natively supported by big query or explicitly supported by our query
 * builders.  We also supported arbitrary percentile aggregations of the form
 * INTEGER|FLOAT% so '50%' or '.01%'.
 * @enum {string}
 */
explorer.models.perfkit_simple_builder.Aggregation = {
  AVERAGE: 'avg',
  COUNT: 'count',
  LAST: 'last',
  MAX: 'max',
  MEAN: 'mean',
  MIN: 'min',
  STDDEV: 'stddev',
  SUM: 'sum',
  VARIANCE: 'variance'
};
var Aggregation = explorer.models.perfkit_simple_builder.Aggregation;

/**
 * The QueryBuilder service transforms a query model into SQL.
 *
 * @param {!Angular.FilterService} $filter
 * @constructor
 *
 */
explorer.models.perfkit_simple_builder.QueryBuilderService = function(
    $filter) {
};
var QueryBuilderService =
    explorer.models.perfkit_simple_builder.QueryBuilderService;

/**
 * Returns a display mode based on the value of a filter.
 * @param {Array.<(string|number|null)>=} opt_values The values to match.
 * @return {Filter.DisplayMode} The display mode to use for the column.
 */
QueryBuilderService.prototype.getColumnDisplayMode = function(opt_values) {
  var visibility = Filter.DisplayMode.COLUMN;
  var value = (
      goog.isDef(opt_values) &&
      !goog.isNull(opt_values) &&
      opt_values.length > 0) ?
      opt_values[0] : null;

  if (goog.isDef(value) && !goog.isNull(value) &&
      !(goog.isString(value) && goog.string.isEmptySafe(value))) {
    visibility = Filter.DisplayMode.HIDDEN;
  }

  return visibility;
};

/**
 * Creates a filter that deals with a single value and clause.  This improves
 * readability of filtering logic, as the UX doesn't presently support multi-
 * values or clauses.  Columns with explicit filters are hidden by default,
 * as they're the same value across the result set.
 * @param {string} fieldName The name of the field to filter on.
 * @param {Array.<(string|number|null)>=} opt_values The list of values to
 *     filter on.
 * @param {?FilterClause.MatchRule=} opt_matchRule The type of matching to
 *     perform.  Defaults to EQ (equals).
 * @param {?Filter.DisplayMode=} opt_displayMode The display mode for the
 *     filter.  Defaults to COLUMN if no value is specified, and HIDDEN if a
 *     value is provided.
 * @param {string=} opt_fieldAlias The alias to use for the field.
 * @return {Filter} A Filter expression.
 */
QueryBuilderService.prototype.createSimpleFilter = function(
    fieldName, opt_values, opt_matchRule, opt_displayMode, opt_fieldAlias) {
  var matchRule = opt_matchRule ? opt_matchRule : FilterClause.MatchRule.EQ;
  var displayMode = opt_displayMode ? opt_displayMode :
      this.getColumnDisplayMode(opt_values);

  var clauses = [];

  if (goog.isDef(opt_values) && !goog.isNull(opt_values)) {
    for (var ctr = 0, len = opt_values.length; ctr < len; ctr++) {
      var value = opt_values[ctr];

      if (!goog.string.isEmptySafe(value)) {
        clauses.push(new FilterClause([value], matchRule));
      }
    }
  }
  var filter = new Filter(fieldName, clauses, displayMode, opt_fieldAlias);
  return filter;
};

/**
 * Returns a date filter clause based on a relative date (last n days, etc.).
 * @param {*} dateFilter A date filter expression.
 * @return {string} A BigQuery function representing the relative date.
 */
QueryBuilderService.prototype.getRelativeDateFunction = function(dateFilter) {
  return ('DATE_ADD(CURRENT_TIMESTAMP(), -' +
      dateFilter.filter_value + ', "' +
      dateFilter.filter_type + '")');
};

/**
 * Returns a date filter clause based on a relative date (last n days, etc.).
 * @param {*} dateFilter A date filter expression.
 * @return {string} A BigQuery function representing the relative date.
 */
QueryBuilderService.prototype.getAbsoluteDateFunction = function(dateFilter) {
  return ('TIMESTAMP(\'' + dateFilter.text + '\')');
};

/**
 * Returns a SQL statement based on the state of a query.
 * @param {!QueryConfigModel} model a QueryConfigModel that describes a query.
 * @param {?string} defaultProjectId
 * @param {?string} defaultDatasetName
 * @param {?string} defaultTableName
 * @return {string} A formatted SQL statement.
 */
QueryBuilderService.prototype.getSql = function(
    model, defaultProjectId, defaultDatasetName, defaultTableName, defaultTablePartition) {
  var fieldFilters = [];
  var startFilter = null;
  var endFilter = null;

  if (model.filters.start_date) {
    var startDateClause = null;

    switch (model.filters.start_date.filter_type) {
      case DateFilterType.CUSTOM:
        startFilter = this.getAbsoluteDateFunction(model.filters.start_date);
        startDateClause = new FilterClause(
            ['TIMESTAMP_TO_SEC(' + startFilter + ')'], FilterClause.MatchRule.GE, true);

        break;
      default:
        startFilter = this.getRelativeDateFunction(model.filters.start_date);
        startDateClause = new FilterClause(
            ['TIMESTAMP_TO_SEC(' + startFilter + ')'], FilterClause.MatchRule.GE, true);

        break;
    }
  }

  if (model.filters.end_date) {
    var endDateClause = null;

    switch (model.filters.end_date.filter_type) {
      case DateFilterType.CUSTOM:
        endFilter = this.getAbsoluteDateFunction(model.filters.end_date);
        endDateClause = new FilterClause(
            ['TIMESTAMP_TO_SEC(' + endFilter + ')'], FilterClause.MatchRule.LE, true);

        break;
      default:
        endDateClause = new FilterClause(
            ['TIMESTAMP_TO_SEC(' + endFilter + ')'], FilterClause.MatchRule.LE, true);

        break;
    }
  }

  startDateClause && fieldFilters.push(
      new Filter('timestamp', [startDateClause], Filter.DisplayMode.HIDDEN));

  endDateClause && fieldFilters.push(
      new Filter('timestamp', [endDateClause], Filter.DisplayMode.HIDDEN));

  model.filters.product_name && fieldFilters.push(
      this.createSimpleFilter('product_name', [model.filters.product_name], null, Filter.DisplayMode.HIDDEN));

  model.filters.test && fieldFilters.push(
      this.createSimpleFilter('test', [model.filters.test], null, Filter.DisplayMode.HIDDEN));

  model.filters.metric && fieldFilters.push(
      this.createSimpleFilter('metric', [model.filters.metric], null, Filter.DisplayMode.HIDDEN));

  model.filters.runby && fieldFilters.push(
      this.createSimpleFilter('owner', [model.filters.runby], null, Filter.DisplayMode.HIDDEN));

  var fieldSortOrders = [];

  angular.forEach(model.results.fields, angular.bind(this, function(field) {
    fieldFilters.push(
        new Filter(field.name, [], Filter.DisplayMode.COLUMN));
  }));

  if (model.results.show_date === true) {
    switch (model.results.date_group.toUpperCase()) {
      case '':
        fieldFilters.push(this.createSimpleFilter(
            'SEC_TO_TIMESTAMP(INTEGER(timestamp))',
            null, null, null, 'date'));
        break;
      case 'WEEK':
        fieldFilters.push(this.createSimpleFilter(
            ('USEC_TO_TIMESTAMP(UTC_USEC_TO_WEEK(INTEGER(timestamp * ' +
             dateUtil.BQ_TIMESTAMP_MULTIPLIER + '), ' +
             dateUtil.BQ_FIRST_DAY_OF_WEEK + '))'),
            null, null, null, 'date'));
        break;
      default:
        fieldFilters.push(this.createSimpleFilter(
            ('USEC_TO_TIMESTAMP(UTC_USEC_TO_' + model.results.date_group.toUpperCase() +
             '(INTEGER(timestamp * ' +
             dateUtil.BQ_TIMESTAMP_MULTIPLIER + ')))'),
            null, null, null, 'date'));
        break;
    }
  }

  if (!model.filters.product_name) { fieldSortOrders.push('product_name'); }
  if (!model.filters.test) { fieldSortOrders.push('test'); }
  if (!model.filters.metric) { fieldSortOrders.push('metric'); }
  if (model.results.show_date) {
    fieldSortOrders.push('date');
  }

  for (var ctr = 0, len = model.results.labels.length;
       ctr < len; ctr++) {
    var label = model.results.labels[ctr]['label'];
    if (goog.isDef(label) && !goog.string.isEmpty(label)) {
      var field = 'REGEXP_EXTRACT(labels, r\'\\|' + label + ':(.*?)\\|\')';
      fieldFilters.push(this.createSimpleFilter(
          field, null, null, null, label));
    }
  }

  if (model.filters.official == 'true') {
    fieldFilters.push(this.createSimpleFilter('official', [true]));
  } else if (model.filters.official == 'false') {
    fieldFilters.push(this.createSimpleFilter('official', [false]));
  }

  for (var ctr = 0, len = model.filters.metadata.length;
       ctr < len; ctr++) {
    var label = '|' + model.filters.metadata[ctr]['text'] + '|';

    fieldFilters.push(this.createSimpleFilter(
        'labels', [label],
        FilterClause.MatchRule.CT,
        Filter.DisplayMode.HIDDEN));
  }

  var aggregations = [];

  if (model.results.measure_values) {
    aggregations = [];

    angular.forEach(model.results.measures, function(measure) {
      aggregations.push(measure.name);
    });
  } else {
    fieldFilters.push(this.createSimpleFilter('value'));
  }

  var queryProperties = new QueryProperties(
      aggregations,
      fieldFilters,
      []);

  var projectId = defaultProjectId;
  var datasetName = defaultDatasetName;
  var tableName = defaultTableName;
  var tablePartition = defaultTablePartition;

  var tableId = datasetName + '.' + tableName;

  if (projectId) {
    tableId = projectId + ':' + tableId;
  }

  var tableExpr = '';

  if (model.results.table_partition == QueryTablePartitioning.PERDAY) {
    if (!startFilter) {
      throw 'Start date is required when PERDAY table partitioning is used.';
    }

    if (!endFilter) {
      endFilter = 'CURRENT_TIMESTAMP()';
    }

    tableExpression = '(TABLE_DATE_RANGE([' + tableId + '], ' + startFilter + ', ' + endFilter + '))';
  } else {
    tableExpression = '[' + tableId + ']';
  }

  var sql = BigQueryBuilder.formatQuery(
      BigQueryBuilder.buildSelectArgs(queryProperties),
      [tableExpression],
      BigQueryBuilder.buildWhereArgs(queryProperties),
      BigQueryBuilder.buildGroupArgs(queryProperties),
      fieldSortOrders,
      model.results.row_limit);

  return sql;
};

});  // goog.scope
<|endoftext|>


<commit_before>/*
Example usage:
{{gh-url-preview prefix="tag" slug=theSlugValue tagName="p" classNames="description"}}
*/
var urlPreview = Ember.Component.extend({
    classNames: 'ghost-url-preview',
    prefix: null,
    slug: null,
    theUrl: null,

    generateUrl: function () {
        // Get the blog URL and strip the scheme
        var blogUrl = this.get('config').blogUrl,
            noSchemeBlogUrl = blogUrl.substr(blogUrl.indexOf('://') + 3), // Remove `http[s]://`

            // Get the prefix and slug values
            prefix = this.get('prefix') ? this.get('prefix') + '/' : '',
            slug = this.get('slug') ? this.get('slug') : '',

            // Join parts of the URL together with slashes
            theUrl = noSchemeBlogUrl + '/' + prefix + slug;

        this.set('the-url', theUrl);
    }.on('didInsertElement').observes('slug')
});

export default urlPreview;
<commit_msg>Make preview URLs include trailing slash when slug is given<commit_after>/*
Example usage:
{{gh-url-preview prefix="tag" slug=theSlugValue tagName="p" classNames="description"}}
*/
var urlPreview = Ember.Component.extend({
    classNames: 'ghost-url-preview',
    prefix: null,
    slug: null,
    theUrl: null,

    generateUrl: function () {
        // Get the blog URL and strip the scheme
        var blogUrl = this.get('config').blogUrl,
            noSchemeBlogUrl = blogUrl.substr(blogUrl.indexOf('://') + 3), // Remove `http[s]://`

            // Get the prefix and slug values
            prefix = this.get('prefix') ? this.get('prefix') + '/' : '',
            slug = this.get('slug') ? this.get('slug') : '',

            // Join parts of the URL together with slashes
            theUrl = noSchemeBlogUrl + '/' + prefix + (slug ? slug + '/' : '');

        this.set('the-url', theUrl);
    }.on('didInsertElement').observes('slug')
});

export default urlPreview;
<|endoftext|>


<commit_before>function luisIntent (session, messages, configObject) {
  if (typeof configObject.luisRecognizer === 'undefined') {
    return null
  }
  return new Promise(resolve=>{
    configObject.luisRecognizer.onRecognize(session.toRecognizeContext(), function (result) {
      resolve(result.intent)
    })
  })
}

module.exports = luisIntent<commit_msg>updated promise<commit_after>function luisIntent (session, messages, configObject) {
  if (typeof configObject.luisRecognizer === 'undefined') {
    return null
  }
  return new Promise(resolve=>{
    configObject.luisRecognizer.onRecognize(session.toRecognizeContext(), function (err,result) {
      resolve(result.intent)
    })
  })
}

module.exports = luisIntent<|endoftext|>


<commit_before>/* See license.txt for terms of usage */

define(function(require, exports, module) {

// Dependencies
const React = require("react");
const { Reps } = require("reps/reps");

// Reps
// xxxHonza: list of reps should be included automatically
require("reps/undefined");
require("reps/string");
require("reps/number");
require("reps/array");
require("reps/object");

/**
 * Post message to the chrome through DOM event dispatcher.
 * (there is no message manager for the markupview.xhtml frame).
 */
function postChromeMessage(type, args) {
  var data = {
    type: type,
    args: args,
  };

  var event = new MessageEvent("firequery/content/message", {
    bubbles: true,
    cancelable: true,
    data: data,
  });

  dispatchEvent(event);
}

/**
 * xxxHonza TODO docs
 */
addEventListener("firequery/chrome/message", event => {
  var data = event.data;
  switch (data.type) {
  case "render":
    renderData(data.args);
    break;
  }
}, true);

/**
 * xxxHonza TODO docs
 *
 * @param nodes
 */
function renderData(nodes) {
  for (var i=0; i<nodes.length; i++) {
    var node = nodes[i];
    var value = node.cache;
    var element = node.element;
    var parentNode = element.querySelector(".fireQueryNodeData");

    // Get proper template for given value and render.
    var rep = Reps.getRep(value);
    React.render(rep({object: value}), parentNode);
  }
}

// Final initialize message posted to the chrome indicating that
// all content modules has been successfully loaded.
postChromeMessage("initialize");

// End of inspector.js
});
<commit_msg>Simplify reps usage (Firebug.SDK updated)<commit_after>/* See license.txt for terms of usage */

define(function(require, exports, module) {

// Dependencies
const { Reps } = require("reps/repository");

/**
 * Post message to the chrome through DOM event dispatcher.
 * (there is no message manager for the markupview.xhtml frame).
 */
function postChromeMessage(type, args) {
  var data = {
    type: type,
    args: args,
  };

  var event = new MessageEvent("firequery/content/message", {
    bubbles: true,
    cancelable: true,
    data: data,
  });

  dispatchEvent(event);
}

/**
 * xxxHonza TODO docs
 */
addEventListener("firequery/chrome/message", event => {
  var data = event.data;
  switch (data.type) {
  case "render":
    renderData(data.args);
    break;
  }
}, true);

/**
 * xxxHonza TODO docs
 *
 * @param nodes
 */
function renderData(nodes) {
  for (var i=0; i<nodes.length; i++) {
    var node = nodes[i];
    var value = node.cache;
    var element = node.element;
    var parentNode = element.querySelector(".fireQueryNodeData");

    // Get proper template for given value and render.
    var rep = Reps.getRep(value);
    Reps.render(rep, value, parentNode);
  }
}

// Final initialize message posted to the chrome indicating that
// all content modules has been successfully loaded.
postChromeMessage("initialize");

// End of inspector.js
});
<|endoftext|>


<commit_before>/*
 * Copyright (C) 2011 McGill University
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

// Module for updating colours on models currently being displayed.
BrainBrowser.SurfaceViewer.modules.color = function(viewer) {
  "use strict";

  ///////////////////////////
  // INTERFACE
  ///////////////////////////
  /**
  * @doc function
  * @name viewer.color:updateColors
  * @param {object} data Data object.
  * @param {object} options Options for the color update, which include the following:
  *
  * * **min** Minimum value of the color samples.
  * * **max** Maximum value of the color samples.
  * * **spectrum** Spectrum object used to create the color array.
  * * **flip** Should the colors be flipped?
  * * **clamped** Should values be clampled to the min/max range?
  * * **blend** Are the colors being blended with already loaded values?
  * * **complete** Callback function to call when the color update is done.
  *
  * @description
  * Update the vertex colors of the model based on data object passed as argument.
  */
  viewer.updateColors = function(data, options) {
    options = options || {};
    var min = options.min;
    var max = options.max;
    var spectrum = options.spectrum;
    var flip = options.flip;
    var clamped = options.clamped;
    var blend = options.blend;
    var complete = options.complete;

    function applyColorArray(color_array) {
      var shapes;

      if(viewer.model_data.split) {
        color_hemispheres(color_array);
      } else {
        if (data.apply_to_shape) {
          shapes = [viewer.model.getObjectByName(data.apply_to_shape, true)];
        } else {
          shapes = viewer.model.children;
        }
        color_model(color_array, shapes);
      }

      viewer.triggerEvent("updatecolors", data, min, max, spectrum);

      if (complete) {
        complete();
      }

    }

    viewer.clamped = clamped;
    if (blend) {
      applyColorArray(blendColorMap(spectrum, data, 0, 1));
    } else {
      data.createColorArray(min, max, spectrum, flip, clamped, viewer.model_data.colorArray, viewer.model_data, applyColorArray);
    }
  };

  /**
   * @doc function
   * @name viewer.color:rangeChange
   * @param {number} min Minimum value of the range.
   * @param {number} max Maximum value of the range.
   * @param {boolean} clamped Should values be clampled to the min/max range?
   * @description
   * Update the range of colors being applied to the current model.
   */
  viewer.rangeChange = function(min, max, clamped, options) {
    options = options || {};
    var data = viewer.model_data.data;

    data.rangeMin = min;
    data.rangeMax = max;
    viewer.updateColors(data, {
      min: data.rangeMin,
      max: data.rangeMax,
      spectrum: viewer.spectrum,
      flip: viewer.flip,
      clamped: clamped,
      complete: options.complete
    });

    viewer.triggerEvent("rangechange", data);
  };

  /**
  * @doc function
  * @name viewer.color:blend
  * @param {number} blend Blend ratio between two loaded color maps (between 0 and 1);
  *
  * @description
  * Blend two loaded color maps using the supplied ratio.
  */
  viewer.blend = function(value) {
    var blendData = viewer.blendData;
    var blendDataLength = blendData.length;
    var i;

    blendData[0].alpha = value;
    blendData[1].alpha = 1.0 - value;
    for(i = 2; i < blendDataLength; i++) {
      blendData[i].alpha = 0.0;
    }

    viewer.updateColors(blendData, {
      spectrum: viewer.spectrum,
      flip: viewer.flip,
      clamped: viewer.clamped,
      blend: true
    });
  };

  ///////////////////////////
  // PRIVATE FUNCTIONS
  ///////////////////////////

  //Coloring for brain models with two separate hemispheres.
  function color_hemispheres(color_array) {
    var model = viewer.model;
    var left_hem = model.getObjectByName("left");
    var right_hem = model.getObjectByName("right");
    var i, count;
    var ic, iwc;

    var left_indices = left_hem.geometry.original_data.indices;
    var right_indices = right_hem.geometry.original_data.indices;

    var left_color_attribute = left_hem.geometry.attributes.color;
    var left_color_attribute_array = left_color_attribute.array;
    var right_color_attribute = right_hem.geometry.attributes.color;
    var right_color_attribute_array = right_color_attribute.array;

    var left_wireframe = left_hem.getObjectByName("__wireframe__");
    var right_wireframe = right_hem.getObjectByName("__wireframe__");
    var left_wireframe_color;
    var right_wireframe_color;

    var has_wireframe = !!left_wireframe;

    if (has_wireframe) {
      left_wireframe_color = left_wireframe.geometry.attributes.color.array;
      right_wireframe_color = right_wireframe.geometry.attributes.color.array;
    }

    for (i = 0, count = left_indices.length; i < count; i += 3) {
      ic = i * 4;
      iwc = ic * 2;

      // This is a little messy but it's just going from an indexed color map
      // to an unindexed geometry.
      // And it's skipping the alphas (every 4th element).
      left_color_attribute_array[ic]    = color_array[left_indices[i]*4];
      left_color_attribute_array[ic+1]  = color_array[left_indices[i]*4+1];
      left_color_attribute_array[ic+2]  = color_array[left_indices[i]*4+2];
      left_color_attribute_array[ic+4]  = color_array[left_indices[i+1]*4];
      left_color_attribute_array[ic+5]  = color_array[left_indices[i+1]*4+1];
      left_color_attribute_array[ic+6]  = color_array[left_indices[i+1]*4+2];
      left_color_attribute_array[ic+8]  = color_array[left_indices[i+2]*4];
      left_color_attribute_array[ic+9]  = color_array[left_indices[i+2]*4+1];
      left_color_attribute_array[ic+10] = color_array[left_indices[i+2]*4+2];

      right_color_attribute_array[ic]    = color_array[right_indices[i]*4];
      right_color_attribute_array[ic+1]  = color_array[right_indices[i]*4+1];
      right_color_attribute_array[ic+2]  = color_array[right_indices[i]*4+2];
      right_color_attribute_array[ic+4]  = color_array[right_indices[i+1]*4];
      right_color_attribute_array[ic+5]  = color_array[right_indices[i+1]*4+1];
      right_color_attribute_array[ic+6]  = color_array[right_indices[i+1]*4+2];
      right_color_attribute_array[ic+8]  = color_array[right_indices[i+2]*4];
      right_color_attribute_array[ic+9]  = color_array[right_indices[i+2]*4+1];
      right_color_attribute_array[ic+10] = color_array[right_indices[i+2]*4+2];

      if (has_wireframe) {
        // v1 -v2
        left_wireframe_color[iwc] = left_color_attribute_array[ic];
        left_wireframe_color[iwc + 1] = left_color_attribute_array[ic + 1];
        left_wireframe_color[iwc + 2] = left_color_attribute_array[ic + 2];
        left_wireframe_color[iwc + 3] = left_color_attribute_array[ic + 3];
        left_wireframe_color[iwc + 4] = left_color_attribute_array[ic + 4];
        left_wireframe_color[iwc + 5] = left_color_attribute_array[ic + 5];
        left_wireframe_color[iwc + 6] = left_color_attribute_array[ic + 6];
        left_wireframe_color[iwc + 7] = left_color_attribute_array[ic + 7];

        // v2 - v3
        left_wireframe_color[iwc + 8] = left_color_attribute_array[ic + 4];
        left_wireframe_color[iwc + 9] = left_color_attribute_array[ic + 5];
        left_wireframe_color[iwc + 10] = left_color_attribute_array[ic + 6];
        left_wireframe_color[iwc + 11] = left_color_attribute_array[ic + 7];
        left_wireframe_color[iwc + 12] = left_color_attribute_array[ic + 8];
        left_wireframe_color[iwc + 13] = left_color_attribute_array[ic + 9];
        left_wireframe_color[iwc + 14] = left_color_attribute_array[ic + 10];
        left_wireframe_color[iwc + 15] = left_color_attribute_array[ic + 11];

        // v3 - v1
        left_wireframe_color[iwc + 16] = left_color_attribute_array[ic + 8];
        left_wireframe_color[iwc + 17] = left_color_attribute_array[ic + 9];
        left_wireframe_color[iwc + 18] = left_color_attribute_array[ic + 10];
        left_wireframe_color[iwc + 19] = left_color_attribute_array[ic + 11];
        left_wireframe_color[iwc + 20] = left_color_attribute_array[ic];
        left_wireframe_color[iwc + 21] = left_color_attribute_array[ic + 1];
        left_wireframe_color[iwc + 22] = left_color_attribute_array[ic + 2];
        left_wireframe_color[iwc + 23] = left_color_attribute_array[ic + 3];

        // v1 -v2
        right_wireframe_color[iwc]     = right_color_attribute_array[ic];
        right_wireframe_color[iwc + 1] = right_color_attribute_array[ic + 1];
        right_wireframe_color[iwc + 2] = right_color_attribute_array[ic + 2];
        right_wireframe_color[iwc + 3] = right_color_attribute_array[ic + 3];
        right_wireframe_color[iwc + 4] = right_color_attribute_array[ic + 4];
        right_wireframe_color[iwc + 5] = right_color_attribute_array[ic + 5];
        right_wireframe_color[iwc + 6] = right_color_attribute_array[ic + 6];
        right_wireframe_color[iwc + 7] = right_color_attribute_array[ic + 7];

        // v2 - v3
        right_wireframe_color[iwc + 8]  = right_color_attribute_array[ic + 4];
        right_wireframe_color[iwc + 9]  = right_color_attribute_array[ic + 5];
        right_wireframe_color[iwc + 10] = right_color_attribute_array[ic + 6];
        right_wireframe_color[iwc + 11] = right_color_attribute_array[ic + 7];
        right_wireframe_color[iwc + 12] = right_color_attribute_array[ic + 8];
        right_wireframe_color[iwc + 13] = right_color_attribute_array[ic + 9];
        right_wireframe_color[iwc + 14] = right_color_attribute_array[ic + 10];
        right_wireframe_color[iwc + 15] = right_color_attribute_array[ic + 11];

        // v3 - v1
        right_wireframe_color[iwc + 16] = right_color_attribute_array[ic + 8];
        right_wireframe_color[iwc + 17] = right_color_attribute_array[ic + 9];
        right_wireframe_color[iwc + 18] = right_color_attribute_array[ic + 10];
        right_wireframe_color[iwc + 19] = right_color_attribute_array[ic + 11];
        right_wireframe_color[iwc + 20] = right_color_attribute_array[ic];
        right_wireframe_color[iwc + 21] = right_color_attribute_array[ic + 1];
        right_wireframe_color[iwc + 22] = right_color_attribute_array[ic + 2];
        right_wireframe_color[iwc + 23] = right_color_attribute_array[ic + 3];
      }

    }

    left_color_attribute.needsUpdate = true;
    right_color_attribute.needsUpdate = true;
    if (has_wireframe) {
      left_wireframe.geometry.attributes.color.needsUpdate = true;
      right_wireframe.geometry.attributes.color.needsUpdate = true;
    }
  }

  //Coloring for regular models.
  function color_model(color_array, shapes) {
    var geometry, shape, indices;
    var color_attribute, colors;
    var i, count;
    var wireframe;
    var wireframe_color;
    var ic, iwc;

    var has_wireframe;

    for (i = 0, count = shapes.length; i < count; i++) {
      shape = shapes[i];
      wireframe = shape.getObjectByName("__wireframe__");

      has_wireframe = !!wireframe;

      if (has_wireframe) {
        wireframe_color = wireframe.geometry.attributes.color.array;
      }

      geometry = shape.geometry;
      indices = shape.geometry.original_data.indices;
      color_attribute = geometry.attributes.color;
      colors = color_attribute.array;

      for (i = 0, count = indices.length; i < count; i += 3) {
        ic = i * 4;
        iwc = ic * 2;

        colors[ic]    = color_array[indices[i]*4];
        colors[ic+1]  = color_array[indices[i]*4+1];
        colors[ic+2]  = color_array[indices[i]*4+2];
        colors[ic+4]  = color_array[indices[i+1]*4];
        colors[ic+5]  = color_array[indices[i+1]*4+1];
        colors[ic+6]  = color_array[indices[i+1]*4+2];
        colors[ic+8]  = color_array[indices[i+2]*4];
        colors[ic+9]  = color_array[indices[i+2]*4+1];
        colors[ic+10] = color_array[indices[i+2]*4+2];

        if (has_wireframe) {
          // v1 -v2
          wireframe_color[iwc] = colors[ic];
          wireframe_color[iwc + 1] = colors[ic + 1];
          wireframe_color[iwc + 2] = colors[ic + 2];
          wireframe_color[iwc + 3] = colors[ic + 3];
          wireframe_color[iwc + 4] = colors[ic + 4];
          wireframe_color[iwc + 5] = colors[ic + 5];
          wireframe_color[iwc + 6] = colors[ic + 6];
          wireframe_color[iwc + 7] = colors[ic + 7];

          // v2 - v3
          wireframe_color[iwc + 8] = colors[ic + 4];
          wireframe_color[iwc + 9] = colors[ic + 5];
          wireframe_color[iwc + 10] = colors[ic + 6];
          wireframe_color[iwc + 11] = colors[ic + 7];
          wireframe_color[iwc + 12] = colors[ic + 8];
          wireframe_color[iwc + 13] = colors[ic + 9];
          wireframe_color[iwc + 14] = colors[ic + 10];
          wireframe_color[iwc + 15] = colors[ic + 11];

          // v3 - v1
          wireframe_color[iwc + 16] = colors[ic + 8];
          wireframe_color[iwc + 17] = colors[ic + 9];
          wireframe_color[iwc + 18] = colors[ic + 10];
          wireframe_color[iwc + 19] = colors[ic + 11];
          wireframe_color[iwc + 20] = colors[ic];
          wireframe_color[iwc + 21] = colors[ic + 1];
          wireframe_color[iwc + 22] = colors[ic + 2];
          wireframe_color[iwc + 23] = colors[ic + 3];
        }
      }

      color_attribute.needsUpdate = true;

      if (has_wireframe) {
        wireframe.geometry.attributes.color.needsUpdate = true;
      }
    }

  }

  function blendColors(color_arrays) {
    var final_color = color_arrays[0];
    var i, j, count1, count2;
    var old_alpha, new_alpha;

    for (i = 0, count1 = color_arrays[0].length/4; i < count1; i++){
      for (j = 1, count2 = color_arrays.length;  j < count2; j++) {
        old_alpha = final_color[i*4+3];
        new_alpha = color_arrays[j][i*4+3];
        final_color[i*4]   = final_color[i*4] * old_alpha+color_arrays[j][i*4] * new_alpha;
        final_color[i*4+1] = final_color[i*4+1] * old_alpha+color_arrays[j][i*4+1] * new_alpha;
        final_color[i*4+2] = final_color[i*4+2] * old_alpha+color_arrays[j][i*4+2] * new_alpha;
        final_color[i*4+3] = old_alpha + new_alpha;
      }
    }
    return final_color;

  }

  /**
  * Blends two or more arrays of values into one color array
  */
  function blendColorMap(spectrum, value_arrays) {
    var count = value_arrays.length;
    var color_arrays = new Array(count);
    var i;
    var value_array;

    for(i = 0; i < count; i++){
      value_array = value_arrays[i];
      color_arrays[i] = spectrum.createColorMap(value_array.values, {
        min: value_array.rangeMin,
        max: value_array.rangeMax,
        alpha: value_array.alpha
      });
    }

    return blendColors(color_arrays);
  }

};

<commit_msg>Coloring code consolidated.<commit_after>/*
 * Copyright (C) 2011 McGill University
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

// Module for updating colours on models currently being displayed.
BrainBrowser.SurfaceViewer.modules.color = function(viewer) {
  "use strict";

  ///////////////////////////
  // INTERFACE
  ///////////////////////////
  /**
  * @doc function
  * @name viewer.color:updateColors
  * @param {object} data Data object.
  * @param {object} options Options for the color update, which include the following:
  *
  * * **min** Minimum value of the color samples.
  * * **max** Maximum value of the color samples.
  * * **spectrum** Spectrum object used to create the color array.
  * * **flip** Should the colors be flipped?
  * * **clamped** Should values be clampled to the min/max range?
  * * **blend** Are the colors being blended with already loaded values?
  * * **complete** Callback function to call when the color update is done.
  *
  * @description
  * Update the vertex colors of the model based on data object passed as argument.
  */
  viewer.updateColors = function(data, options) {
    options = options || {};
    var min = options.min;
    var max = options.max;
    var spectrum = options.spectrum;
    var flip = options.flip;
    var clamped = options.clamped;
    var blend = options.blend;
    var complete = options.complete;

    function applyColorArray(color_array) {
      var shapes;

      if (data.apply_to_shape) {
        shapes = [viewer.model.getObjectByName(data.apply_to_shape, true)];
      } else {
        shapes = viewer.model.children;
      }
      colorModel(color_array, shapes);

      viewer.triggerEvent("updatecolors", data, min, max, spectrum);

      if (complete) {
        complete();
      }

    }

    viewer.clamped = clamped;
    if (blend) {
      applyColorArray(blendColorMap(spectrum, data, 0, 1));
    } else {
      data.createColorArray(min, max, spectrum, flip, clamped, viewer.model_data.colorArray, viewer.model_data, applyColorArray);
    }
  };

  /**
   * @doc function
   * @name viewer.color:rangeChange
   * @param {number} min Minimum value of the range.
   * @param {number} max Maximum value of the range.
   * @param {boolean} clamped Should values be clampled to the min/max range?
   * @description
   * Update the range of colors being applied to the current model.
   */
  viewer.rangeChange = function(min, max, clamped, options) {
    options = options || {};
    var data = viewer.model_data.data;

    data.rangeMin = min;
    data.rangeMax = max;
    viewer.updateColors(data, {
      min: data.rangeMin,
      max: data.rangeMax,
      spectrum: viewer.spectrum,
      flip: viewer.flip,
      clamped: clamped,
      complete: options.complete
    });

    viewer.triggerEvent("rangechange", data);
  };

  /**
  * @doc function
  * @name viewer.color:blend
  * @param {number} blend Blend ratio between two loaded color maps (between 0 and 1);
  *
  * @description
  * Blend two loaded color maps using the supplied ratio.
  */
  viewer.blend = function(value) {
    var blendData = viewer.blendData;
    var blendDataLength = blendData.length;
    var i;

    blendData[0].alpha = value;
    blendData[1].alpha = 1.0 - value;
    for(i = 2; i < blendDataLength; i++) {
      blendData[i].alpha = 0.0;
    }

    viewer.updateColors(blendData, {
      spectrum: viewer.spectrum,
      flip: viewer.flip,
      clamped: viewer.clamped,
      blend: true
    });
  };

  ///////////////////////////
  // PRIVATE FUNCTIONS
  ///////////////////////////

  //Coloring for regular models.
  function colorModel(color_array, shapes) {
    var geometry, shape, indices;
    var color_attribute, colors;
    var i, j, count, shape_count;
    var wireframe;
    var wireframe_color;
    var ic, iwc;

    var has_wireframe;

    for (j = 0, shape_count = shapes.length; j < shape_count; j++) {
      shape = shapes[j];
      wireframe = shape.getObjectByName("__wireframe__");

      has_wireframe = !!wireframe;

      if (has_wireframe) {
        wireframe_color = wireframe.geometry.attributes.color.array;
      }

      geometry = shape.geometry;
      indices = shape.geometry.original_data.indices;
      color_attribute = geometry.attributes.color;
      colors = color_attribute.array;

      // This looks a little messy but it's just going from an indexed color map
      // to an unindexed geometry.
      // And it's skipping the alphas (every 4th element).
      for (i = 0, count = indices.length; i < count; i += 3) {
        ic = i * 4;
        iwc = ic * 2;

        colors[ic]    = color_array[indices[i]*4];
        colors[ic+1]  = color_array[indices[i]*4+1];
        colors[ic+2]  = color_array[indices[i]*4+2];
        colors[ic+4]  = color_array[indices[i+1]*4];
        colors[ic+5]  = color_array[indices[i+1]*4+1];
        colors[ic+6]  = color_array[indices[i+1]*4+2];
        colors[ic+8]  = color_array[indices[i+2]*4];
        colors[ic+9]  = color_array[indices[i+2]*4+1];
        colors[ic+10] = color_array[indices[i+2]*4+2];

        if (has_wireframe) {
          // v1 -v2
          wireframe_color[iwc] = colors[ic];
          wireframe_color[iwc + 1] = colors[ic + 1];
          wireframe_color[iwc + 2] = colors[ic + 2];
          wireframe_color[iwc + 3] = colors[ic + 3];
          wireframe_color[iwc + 4] = colors[ic + 4];
          wireframe_color[iwc + 5] = colors[ic + 5];
          wireframe_color[iwc + 6] = colors[ic + 6];
          wireframe_color[iwc + 7] = colors[ic + 7];

          // v2 - v3
          wireframe_color[iwc + 8] = colors[ic + 4];
          wireframe_color[iwc + 9] = colors[ic + 5];
          wireframe_color[iwc + 10] = colors[ic + 6];
          wireframe_color[iwc + 11] = colors[ic + 7];
          wireframe_color[iwc + 12] = colors[ic + 8];
          wireframe_color[iwc + 13] = colors[ic + 9];
          wireframe_color[iwc + 14] = colors[ic + 10];
          wireframe_color[iwc + 15] = colors[ic + 11];

          // v3 - v1
          wireframe_color[iwc + 16] = colors[ic + 8];
          wireframe_color[iwc + 17] = colors[ic + 9];
          wireframe_color[iwc + 18] = colors[ic + 10];
          wireframe_color[iwc + 19] = colors[ic + 11];
          wireframe_color[iwc + 20] = colors[ic];
          wireframe_color[iwc + 21] = colors[ic + 1];
          wireframe_color[iwc + 22] = colors[ic + 2];
          wireframe_color[iwc + 23] = colors[ic + 3];
        }
      }

      color_attribute.needsUpdate = true;

      if (has_wireframe) {
        wireframe.geometry.attributes.color.needsUpdate = true;
      }
    }

  }

  function blendColors(color_arrays) {
    var final_color = color_arrays[0];
    var i, j, count1, count2;
    var old_alpha, new_alpha;

    for (i = 0, count1 = color_arrays[0].length/4; i < count1; i++){
      for (j = 1, count2 = color_arrays.length;  j < count2; j++) {
        old_alpha = final_color[i*4+3];
        new_alpha = color_arrays[j][i*4+3];
        final_color[i*4]   = final_color[i*4] * old_alpha+color_arrays[j][i*4] * new_alpha;
        final_color[i*4+1] = final_color[i*4+1] * old_alpha+color_arrays[j][i*4+1] * new_alpha;
        final_color[i*4+2] = final_color[i*4+2] * old_alpha+color_arrays[j][i*4+2] * new_alpha;
        final_color[i*4+3] = old_alpha + new_alpha;
      }
    }
    return final_color;

  }

  /**
  * Blends two or more arrays of values into one color array
  */
  function blendColorMap(spectrum, value_arrays) {
    var count = value_arrays.length;
    var color_arrays = new Array(count);
    var i;
    var value_array;

    for(i = 0; i < count; i++){
      value_array = value_arrays[i];
      color_arrays[i] = spectrum.createColorMap(value_array.values, {
        min: value_array.rangeMin,
        max: value_array.rangeMax,
        alpha: value_array.alpha
      });
    }

    return blendColors(color_arrays);
  }

};

<|endoftext|>


<commit_before>export default [
  {
    name: 'befolkning_hovedgruppe',
    query: {
      year: 'latest',
      tableName: 'befolkning_hovedgruppe',
      unit: ['prosent'],
      dimensions: [
        {
          name: 'innvkat5',
          variables: ['innvandrere']
        },
        {
          name: 'kjonn',
          variables: ['alle']
        }
      ]
    },
    drillDown: {
      page: 'befolkning',
      card: 'befolkning_hovedgruppe',
      buttonTitle: 'Gå til befolkningssammensetning'
    },
    chartKind: 'benchmark',
    compareWithSimilarRegions: true,
    additionalTitleParams: ['aar'],
    relevantFor: ['borough', 'municipality', 'county', 'commerceRegion'],
    title: options => {
      return `Innvandrerandel i befolkningen i ${options.aar}`
    },
    subTitle: options => {
      return `For Norge var tallet ${options.share}`
    }
  },
  {
    name: 'flyktninger_befolkningsandel',
    query: {
      year: 'latest',
      tableName: 'bosatt_befolkning',
      unit: ['promille'],
      dimensions: [
        {
          name: 'bosetting',
          variables: ['bosatt_per_1000']
        }
      ]
    },
    drillDown: {
      page: 'befolkning',
      card: 'flyktninger_befolkningsandel',
      buttonTitle: 'Gå til andel bosatte'
    },
    chartKind: 'benchmark',
    compareWithSimilarRegions: true,
    additionalTitleParams: ['aar'],
    relevantFor: ['borough', 'municipality', 'county', 'commerceRegion'],
    title: options => {
      return `Bosatte flyktninger per innbygger i ${options.aar}`
    },
    subTitle: options => {
      return `For hele landet er tallet ${options.share}`
    },
  },
  {
    name: 'anmodning_vedtak',
    query: {
      year: ['2012', '2013', '2014', '2015', '2016'],
      tableName: 'bosatt_anmodede',
      unit: ['personer'],
      dimensions: [
        {
          name: 'bosetting',
          variables: ['anmodning', 'vedtak']
        }
      ]
    },
    drillDown: {
      page: 'befolkning',
      card: 'bosatt_anmodede',
      buttonTitle: 'Gå til bosettingsstatistikk'
    },
    chartKind: 'line',
    compareWithSimilarRegions: false,
    additionalTitleParams: [],
    relevantFor: ['municipality', 'county', 'commerceRegion'],
    title: options => {
      return `Anmodning og vedtak om bosetting over tid`
    },
    subTitle: options => {
      return null
    },
  },
  {
    name: 'tilskudd',
    query: {
      year: 'latest',
      tableName: 'tilskudd',
      unit: ['kroner'],
      dimensions: [
        {
          name: 'tilskuddTilKommuner',
          variables: ['totalt']
        }
      ]
    },
    drillDown: {
      page: 'tilskudd',
      card: 'tilskudd',
      buttonTitle: 'Gå til tilskudd'
    },
    chartKind: 'benchmark',
    compareWithSimilarRegions: true,
    additionalTitleParams: ['aar'],
    relevantFor: ['municipality', 'county', 'commerceRegion'],
    title: options => {
      return `Utbetalt tilskudd i ${options.aar}`
    },
    subTitle: options => {
      return null
    },
  },
  {
    name: 'innvandrere_intro_arbeid',
    query: {
      year: 'latest',
      tableName: 'intro_status_arbutd',
      unit: ['prosent'],
      dimensions: [
        {
          name: 'avslutta',
          variables: ['ettaar']
        },
        {
          name: 'avslstat4',
          variables: ['arbutd']
        },
        {
          name: 'kjonn',
          variables: ['alle']
        }
      ]
    },
    drillDown: {
      page: 'kvalifisering',
      card: 'status-etter-intro',
      buttonTitle: 'Gå til resultater fra introprogrammet'
    },
    chartKind: 'benchmark',
    compareWithSimilarRegions: true,
    additionalTitleParams: ['aar'],
    relevantFor: ['borough', 'municipality', 'county', 'commerceRegion'],
    title: options => {
      return `Andel  av deltakerne som avsluttet intro året før i arbeid eller utdanning i ${options.aar}`
    },
    subTitle: options => {
      return `Gjennomsnittet i Norge er ${options.share}`
    },
  },
  {
    name: 'i_arbeid',
    query: {
      year: 'latest',
      tableName: 'sysselsatte_innvkat',
      unit: ['prosent'],
      dimensions: [
        {
          name: 'innvkat3',
          variables: ['innvandrere', 'befolkningen_ellers']
        }
      ]
    },
    drillDown: {
      page: 'arbeid',
      card: 'sysselsatte-innvkat',
      buttonTitle: 'Gå til sysselsetting'
    },
    chartKind: 'bar',
    compareWithSimilarRegions: false,
    additionalTitleParams: ['aar'],
    relevantFor: ['borough', 'municipality', 'county', 'commerceRegion'],
    title: options => {
      return `Andel av innvandrere og befolkningen ellers i arbeid i ${options.aar}`
    },
    subTitle: options => {
      return null
    },
  }
]
<commit_msg>fix double title for i_arbeid<commit_after>export default [
  {
    name: 'befolkning_hovedgruppe',
    query: {
      year: 'latest',
      tableName: 'befolkning_hovedgruppe',
      unit: ['prosent'],
      dimensions: [
        {
          name: 'innvkat5',
          variables: ['innvandrere']
        },
        {
          name: 'kjonn',
          variables: ['alle']
        }
      ]
    },
    drillDown: {
      page: 'befolkning',
      card: 'befolkning_hovedgruppe',
      buttonTitle: 'Gå til befolkningssammensetning'
    },
    chartKind: 'benchmark',
    compareWithSimilarRegions: true,
    additionalTitleParams: ['aar'],
    relevantFor: ['borough', 'municipality', 'county', 'commerceRegion'],
    title: options => {
      return `Innvandrere som andel av befolkningen i ${options.aar}`
    },
    subTitle: options => {
      return `For Norge var tallet ${options.share}`
    }
  },
  {
    name: 'flyktninger_befolkningsandel',
    query: {
      year: 'latest',
      tableName: 'bosatt_befolkning',
      unit: ['promille'],
      dimensions: [
        {
          name: 'bosetting',
          variables: ['bosatt_per_1000']
        }
      ]
    },
    drillDown: {
      page: 'befolkning',
      card: 'flyktninger_befolkningsandel',
      buttonTitle: 'Gå til andel bosatte'
    },
    chartKind: 'benchmark',
    compareWithSimilarRegions: true,
    additionalTitleParams: ['aar'],
    relevantFor: ['borough', 'municipality', 'county', 'commerceRegion'],
    title: options => {
      return `Bosatte flyktninger per innbygger i ${options.aar}`
    },
    subTitle: options => {
      return `For hele landet er tallet ${options.share}`
    },
  },
  {
    name: 'anmodning_vedtak',
    query: {
      year: ['2012', '2013', '2014', '2015', '2016'],
      tableName: 'bosatt_anmodede',
      unit: ['personer'],
      dimensions: [
        {
          name: 'bosetting',
          variables: ['anmodning', 'vedtak']
        }
      ]
    },
    drillDown: {
      page: 'befolkning',
      card: 'bosatt_anmodede',
      buttonTitle: 'Gå til bosettingsstatistikk'
    },
    chartKind: 'line',
    compareWithSimilarRegions: false,
    additionalTitleParams: [],
    relevantFor: ['municipality', 'county', 'commerceRegion'],
    title: options => {
      return `Anmodning og vedtak om bosetting over tid`
    },
    subTitle: options => {
      return null
    },
  },
  {
    name: 'tilskudd',
    query: {
      year: 'latest',
      tableName: 'tilskudd',
      unit: ['kroner'],
      dimensions: [
        {
          name: 'tilskuddTilKommuner',
          variables: ['totalt']
        }
      ]
    },
    drillDown: {
      page: 'tilskudd',
      card: 'tilskudd',
      buttonTitle: 'Gå til tilskudd'
    },
    chartKind: 'benchmark',
    compareWithSimilarRegions: true,
    additionalTitleParams: ['aar'],
    relevantFor: ['municipality', 'county', 'commerceRegion'],
    title: options => {
      return `Utbetalt tilskudd i ${options.aar}`
    },
    subTitle: options => {
      return null
    },
  },
  {
    name: 'innvandrere_intro_arbeid',
    query: {
      year: 'latest',
      tableName: 'intro_status_arbutd',
      unit: ['prosent'],
      dimensions: [
        {
          name: 'avslutta',
          variables: ['ettaar']
        },
        {
          name: 'avslstat4',
          variables: ['arbutd']
        },
        {
          name: 'kjonn',
          variables: ['alle']
        }
      ]
    },
    drillDown: {
      page: 'kvalifisering',
      card: 'status-etter-intro',
      buttonTitle: 'Gå til resultater fra introprogrammet'
    },
    chartKind: 'benchmark',
    compareWithSimilarRegions: true,
    additionalTitleParams: [],
    relevantFor: ['borough', 'municipality', 'county', 'commerceRegion'],
    title: options => {
      return `Deltakere som avsluttet introprogram i 2013 - andel i arbeid og utdanning i 2014`
    },
    subTitle: options => {
      return `Gjennomsnittet i Norge er ${options.share}`
    },
  },
  {
    name: 'i_arbeid',
    query: {
      year: 'latest',
      tableName: 'sysselsatte_innvkat',
      unit: ['prosent'],
      dimensions: [
        {
          name: 'innvkat3',
          variables: ['innvandrere', 'befolkningen_ellers']
        }
      ]
    },
    drillDown: {
      page: 'arbeid',
      card: 'sysselsatte-innvkat',
      buttonTitle: 'Gå til sysselsetting'
    },
    chartKind: 'bar',
    compareWithSimilarRegions: false,
    additionalTitleParams: [],
    relevantFor: ['borough', 'municipality', 'county', 'commerceRegion'],
    title: options => {
      return `Andel av innvandrere og befolkningen ellers i arbeid i 2013`
    },
    subTitle: options => {
      return null
    },
  }
]
<|endoftext|>


<commit_before>var DEFAULT_VALUES = {
  top: 0,
  left: 0
};
var MIN_GAP = 10;
var MIN_HEIGHT = 205;
var MIN_WIDTH = 244;

/**
 * Would you like to know where to open a context menu?
 * Just provide:
 * - Parent element view.
 * - Position X and Y.
 * - (optional) elementWidth
 * - (optional) elementHeight
 * - (optional) offsetX
 * - (optional) offsetY
 *
 * By default, it will try to positionate to the right (inside) and to
 * the top (below).
 */

var MagicPositioner = function (params) {
  if (!params.parentView) { throw new Error('parentView within params is required'); }
  if (isNaN(params.posX)) { throw new Error('posX within params is required'); }
  if (isNaN(params.posY)) { throw new Error('posY within params is required'); }

  var $parentView = params.parentView;
  var parentViewHeight = $parentView.outerHeight();
  var parentViewWidth = $parentView.outerWidth();
  var posX = params.posX;
  var posY = params.posY;
  var offsetX = params.offsetX || 0;
  var offsetY = params.offsetY || 0;
  var elementWidth = params.elementWidth || MIN_WIDTH;
  var elementHeight = params.elementHeight || MIN_HEIGHT;
  var cssProps = DEFAULT_VALUES;

  if ((posX - elementWidth) > MIN_GAP) {
    cssProps.left = 'auto';
    cssProps.right = (parentViewWidth - posX + offsetX) + 'px';
  } else if ((posX + elementWidth) > elementWidth) {
    cssProps.right = 'auto';
    cssProps.left = (posX + offsetX) + 'px';
  } else {
    cssProps.left = 'auto';
    cssProps.right = (parentViewWidth - posX + offsetX) + 'px';
  }

  if ((posY + elementHeight) < parentViewHeight) {
    cssProps.bottom = 'auto';
    cssProps.top = (posY + offsetY) + 'px';
  } else if ((posY + elementHeight) > parentViewHeight) {
    cssProps.top = 'auto';
    cssProps.bottom = (parentViewHeight - posY + offsetY) + 'px';
  } else {
    cssProps.bottom = 'auto';
    cssProps.top = (posY + offsetY) + 'px';
  }

  return cssProps;
};

module.exports = MagicPositioner;
<commit_msg>Improvements<commit_after>var _ = require('underscore');
var DEFAULT_VALUES = {
  top: 0,
  left: 0
};
var MIN_GAP = 10;
var MIN_HEIGHT = 205;
var MIN_WIDTH = 244;

/**
 * Would you like to know where to open a context menu?
 * Just provide:
 * @param {Object} options.parentView               Parent element view
 * @param {Number} options.posX                     Position X of the context menu
 * @param {Number} options.posY                     Position Y of the context menu
 * @param {Number} options.elementWidth (optional)  Context menu width
 * @param {Number} options.elementHeight (optional) Context menu height
 * @param {Number} options.offsetX (optional)       Context menu offsetX
 * @param {Number} options.offsetY (optional)       Context menu offsetY
 *
 * By default, it will try to positionate to the right (inside) and to
 * the top (below).
 */

var MagicPositioner = function (params) {
  if (!params.parentView) throw new Error('parentView within params is required');
  if (!_.isNumber(params.posX)) throw new Error('posX within params is required');
  if (!_.isNumber(params.posY)) throw new Error('posY within params is required');

  var $parentView = params.parentView;
  var parentViewHeight = $parentView.outerHeight();
  var parentViewWidth = $parentView.outerWidth();
  var posX = params.posX;
  var posY = params.posY;
  var offsetX = params.offsetX || 0;
  var offsetY = params.offsetY || 0;
  var elementWidth = params.elementWidth || MIN_WIDTH;
  var elementHeight = params.elementHeight || MIN_HEIGHT;
  var cssProps = DEFAULT_VALUES;

  if ((posX + elementWidth) > elementWidth || (posX - elementWidth) > MIN_GAP) {
    cssProps.left = (posX + offsetX) + 'px';
    cssProps.right = 'auto';
  } else {
    cssProps.left = 'auto';
    cssProps.right = (parentViewWidth - posX + offsetX) + 'px';
  }

  if ((posY + elementHeight) > parentViewHeight || (posY + elementHeight) < parentViewHeight) {
    cssProps.top = 'auto';
    cssProps.bottom = (parentViewHeight - posY + offsetY) + 'px';
  } else {
    cssProps.bottom = 'auto';
    cssProps.top = (posY + offsetY) + 'px';
  }

  return cssProps;
};

module.exports = MagicPositioner;
<|endoftext|>


<commit_before>import React from 'react'
import schema from 'part:@sanity/base/schema?'
import client from 'part:@sanity/base/client?'
import Preview from 'part:@sanity/base/preview?'
import Multicast from '@sanity/observable/multicast'
import {IntentLink, withRouterHOC} from 'part:@sanity/base/router'
import {union, flatten} from 'lodash'
import SearchIcon from 'part:@sanity/base/search-icon'
import Spinner from 'part:@sanity/components/loading/spinner'

import styles from './styles/Search.css'

export const DRAFTS_FOLDER = 'drafts'
const DRAFTS_PREFIX = `${DRAFTS_FOLDER}.`

function isDraftId(id) {
  return id.startsWith(DRAFTS_PREFIX)
}

function getPublishedId(id) {
  return isDraftId(id) ? id.slice(DRAFTS_PREFIX.length) : id
}

function getDraftId(id) {
  return isDraftId(id) ? id : DRAFTS_PREFIX + id
}

// Removes published documents that also has a draft
function removeDupes(documents) {
  const drafts = documents.map(doc => doc._id).filter(isDraftId)

  return documents
    .filter(doc => {
      const draftId = getDraftId(doc._id)
      const publishedId = getPublishedId(doc._id)
      const hasDraft = drafts.includes(draftId)
      const isPublished = doc._id === publishedId
      return isPublished ? !hasDraft : true
    })
}

function search(query) {
  if (!client) {
    console.error('Sanity client is missing. (Search is disabled)') // eslint-disable-line
    return
  }

  // Get all fields that we want to search in (text and string)
  const searchableFields = flatten(
    schema.getTypeNames()
      .map(typeName => schema.get(typeName))
      .filter(type => type.type && type.type.name === 'object')
      .map(type => type.fields
        .filter(field => field.type.jsonType === 'string')
        .map(field => field.name)
      )
  )

  const terms = query.split(/\s+/)
  const uniqueFields = union(searchableFields)
  const constraints = flatten(uniqueFields.map(field => terms.map(term => `${field} match '${term}**'`)))

  return client.observable.fetch(`*[${constraints.join(' || ')}][0...10]`)
}

export default class Search extends React.Component {
  input$ = new Multicast()
  componentWillUnmount$ = new Multicast()

  state = {
    isOpen: false,
    hits: [],
    activeIndex: -1
  }

  componentDidMount() {
    this.input$.asObservable()
      .map(event => event.target.value)
      .debounceTime(100)
      .do(() => {
        this.setState({
          isSearching: true
        })
      })
      .switchMap(search)
      // we need this filtering because the search may return documents of types not in schema
      .map(hits => hits.filter(hit => schema.has(hit._type)))
      .map(removeDupes)
      .do(hits => {
        this.setState({
          isSearching: false,
          hits: hits
        })
      })
      .takeUntil(this.componentWillUnmount$.asObservable())
      .subscribe()
  }

  componentWillUnmount() {
    this.componentWillUnmount$.next()
    this.componentWillUnmount$.complete()
  }

  handleInputChange = event => {
    this.input$.next(event)
  }

  handleKeyPress = event => {
    this.inputElement.focus()
  }

  handleKeyDown = event => {
    if (event.key === 'Backspace') {
      this.inputElement.focus()
    }
    if (event.key === 'Enter') {
      this.listContainer.querySelector(`[data-hit-index="${this.state.activeIndex}"]`).click()
    }
    const {hits, activeIndex} = this.state
    const lastIndex = hits.length - 1
    if (['ArrowUp', 'ArrowDown'].includes(event.key)) {
      event.preventDefault()
      let nextIndex = activeIndex + (event.key === 'ArrowUp' ? - 1 : 1)
      if (nextIndex < 0) nextIndex = lastIndex
      if (nextIndex > lastIndex) nextIndex = 0
      this.setState({activeIndex: nextIndex})
    }
  }

  handleBlur = el => {
    setTimeout(() => this.setState({isOpen: false}), 10)
  }

  handleFocus = el => {
    this.setState({isOpen: true})
  }

  setInput = el => {
    this.inputElement = el
  }

  setListContainer = el => {
    this.listContainer = el
  }

  renderItem = (item, index) => {
    const type = schema.get(item._type)
    const {activeIndex} = this.state
    return (
      <IntentLink
        intent="edit"
        params={{id: item._id, type: type.name}}
        className={activeIndex === index ? styles.activeLink : styles.link}
        data-hit-index={index}
      >
        <Preview
          value={item}
          layout="default"
          type={type}
        />
      </IntentLink>
    )
  }

  render() {
    const {isSearching, hits, isOpen} = this.state
    const {placeholder} = this.props
    return (

              <SearchIcon />

          <input
            className={styles.input}
            type="search"
            onInput={this.handleInputChange}
            onBlur={this.handleBlur}
            onFocus={this.handleFocus}
            onKeyDown={this.handleKeyDown}
            placeholder="Search…"
            ref={this.setInput}

          />

            {isSearching && <Spinner />}

        {isOpen && (

              {hits.map((hit, index) => (

                 {this.renderItem(hit, index)}

              ))}

        )}

    )
  }
}
<commit_msg>[components] Misc. minor lint-fixes<commit_after>import React from 'react'
import schema from 'part:@sanity/base/schema?'
import client from 'part:@sanity/base/client?'
import Preview from 'part:@sanity/base/preview?'
import Multicast from '@sanity/observable/multicast'
import {IntentLink} from 'part:@sanity/base/router'
import {union, flatten} from 'lodash'
import SearchIcon from 'part:@sanity/base/search-icon'
import Spinner from 'part:@sanity/components/loading/spinner'

import styles from './styles/Search.css'

export const DRAFTS_FOLDER = 'drafts'
const DRAFTS_PREFIX = `${DRAFTS_FOLDER}.`

function isDraftId(id) {
  return id.startsWith(DRAFTS_PREFIX)
}

function getPublishedId(id) {
  return isDraftId(id) ? id.slice(DRAFTS_PREFIX.length) : id
}

function getDraftId(id) {
  return isDraftId(id) ? id : DRAFTS_PREFIX + id
}

// Removes published documents that also has a draft
function removeDupes(documents) {
  const drafts = documents.map(doc => doc._id).filter(isDraftId)

  return documents
    .filter(doc => {
      const draftId = getDraftId(doc._id)
      const publishedId = getPublishedId(doc._id)
      const hasDraft = drafts.includes(draftId)
      const isPublished = doc._id === publishedId
      return isPublished ? !hasDraft : true
    })
}

function search(query) {
  if (!client) {
    throw new Error('Sanity client is missing')
  }

  // Get all fields that we want to search in (text and string)
  const searchableFields = flatten(
    schema.getTypeNames()
      .map(typeName => schema.get(typeName))
      .filter(type => type.type && type.type.name === 'object')
      .map(type => type.fields
        .filter(field => field.type.jsonType === 'string')
        .map(field => field.name)
      )
  )

  const terms = query.split(/\s+/)
  const uniqueFields = union(searchableFields)
  const constraints = flatten(uniqueFields.map(field => terms.map(term => `${field} match '${term}**'`)))

  return client.observable.fetch(`*[${constraints.join(' || ')}][0...10]`)
}

export default class Search extends React.Component {
  input$ = new Multicast()
  componentWillUnmount$ = new Multicast()

  state = {
    isOpen: false,
    hits: [],
    activeIndex: -1
  }

  componentDidMount() {
    this.input$.asObservable()
      .map(event => event.target.value)
      .debounceTime(100)
      .do(() => {
        this.setState({
          isSearching: true
        })
      })
      .switchMap(search)
      // we need this filtering because the search may return documents of types not in schema
      .map(hits => hits.filter(hit => schema.has(hit._type)))
      .map(removeDupes)
      .do(hits => {
        this.setState({
          isSearching: false,
          hits: hits
        })
      })
      .takeUntil(this.componentWillUnmount$.asObservable())
      .subscribe()
  }

  componentWillUnmount() {
    this.componentWillUnmount$.next()
    this.componentWillUnmount$.complete()
  }

  handleInputChange = event => {
    this.input$.next(event)
  }

  handleKeyPress = event => {
    this.inputElement.focus()
  }

  handleKeyDown = event => {
    if (event.key === 'Backspace') {
      this.inputElement.focus()
    }
    if (event.key === 'Enter') {
      this.listContainer.querySelector(`[data-hit-index="${this.state.activeIndex}"]`).click()
    }
    const {hits, activeIndex} = this.state
    const lastIndex = hits.length - 1
    if (['ArrowUp', 'ArrowDown'].includes(event.key)) {
      event.preventDefault()
      let nextIndex = activeIndex + (event.key === 'ArrowUp' ? - 1 : 1)
      if (nextIndex < 0) nextIndex = lastIndex
      if (nextIndex > lastIndex) nextIndex = 0
      this.setState({activeIndex: nextIndex})
    }
  }

  handleBlur = el => {
    setTimeout(() => this.setState({isOpen: false}), 10)
  }

  handleFocus = el => {
    this.setState({isOpen: true})
  }

  setInput = el => {
    this.inputElement = el
  }

  setListContainer = el => {
    this.listContainer = el
  }

  renderItem = (item, index) => {
    const type = schema.get(item._type)
    const {activeIndex} = this.state
    return (
      <IntentLink
        intent="edit"
        params={{id: item._id, type: type.name}}
        className={activeIndex === index ? styles.activeLink : styles.link}
        data-hit-index={index}
      >
        <Preview
          value={item}
          layout="default"
          type={type}
        />
      </IntentLink>
    )
  }

  render() {
    const {isSearching, hits, isOpen} = this.state
    return (

              <SearchIcon />

          <input
            className={styles.input}
            type="search"
            onInput={this.handleInputChange}
            onBlur={this.handleBlur}
            onFocus={this.handleFocus}
            onKeyDown={this.handleKeyDown}
            placeholder="Search…"
            ref={this.setInput}

          />

            {isSearching && <Spinner />}

        {isOpen && (

              {hits.map((hit, index) => (

                 {this.renderItem(hit, index)}

              ))}

        )}

    )
  }
}
<|endoftext|>


<commit_before>import Browser from 'mobiledoc-kit/utils/browser';
import Component from '@glimmer/component';
import {action} from '@ember/object';
import {formatTextReplacementHtml} from './koenig-text-replacement-html-input';
import {isBlank} from '@ember/utils';
import {run} from '@ember/runloop';
import {inject as service} from '@ember/service';
import {set} from '@ember/object';
import {tracked} from '@glimmer/tracking';

export default class KoenigCardEmailCtaComponent extends Component {
    @service ui;

    @tracked buttonFocused = false;
    @tracked contentFocused = false;

    get formattedHtml() {
        return formatTextReplacementHtml(this.args.payload.html);
    }

    get segments() {
        return [{
            name: 'free members',
            filter: 'status:free'
        }, {
            name: 'paid members',
            filter: 'status:-free'
        }];
    }

    get selectedSegment() {
        return this.segments.find(segment => segment.filter === this.args.payload.segment);
    }

    get hasTopBorder() {
        const border = this.args.payload?.border;
        return border === 'top' || border === 'both';
    }

    get hasBottomBorder() {
        const border = this.args.payload?.border;
        return border === 'bottom' || border === 'both';
    }

    get toolbar() {
        if (this.args.isEditing) {
            return false;
        }

        return {
            items: [{
                title: 'No dividers',
                icon: 'koenig/kg-img-regular',
                iconClass: this.args.payload.border === 'none' ? 'fill-green-l2' : 'fill-white',
                action: run.bind(this, this.setBorder, null)
            }, {
                title: 'Top divider',
                icon: 'koenig/kg-img-regular',
                iconClass: this.args.payload.border === 'top' ? 'fill-green-l2' : 'fill-white',
                action: run.bind(this, this.setBorder, 'top')
            }, {
                title: 'Both dividers',
                icon: 'koenig/kg-img-regular',
                iconClass: this.args.payload.border === 'both' ? 'fill-green-l2' : 'fill-white',
                action: run.bind(this, this.setBorder, 'both')
            }, {
                title: 'Bottom divider',
                icon: 'koenig/kg-img-regular',
                iconClass: this.args.payload.border === 'bottom' ? 'fill-green-l2' : 'fill-white',
                action: run.bind(this, this.setBorder, 'bottom')
            }, {
                divider: true
            }, {
                buttonClass: 'fw4 flex items-center white',
                icon: 'koenig/kg-edit',
                iconClass: 'fill-white',
                title: 'Edit',
                text: '',
                action: run.bind(this, this.args.editCard)
            }]
        };
    }

    constructor() {
        super(...arguments);
        this.args.registerComponent(this);
        if (!this.args.payload.segment) {
            this._updatePayloadAttr('segment', 'status:free');
        }
    }

    @action
    updateHtml(html) {
        this._updatePayloadAttr('html', html);
    }

    @action
    setSegment(segment) {
        this._updatePayloadAttr('segment', segment.filter);
    }

    @action
    setButtonText(event) {
        this._updatePayloadAttr('buttonText', event.target.value);
    }

    @action
    setButtonUrl(event) {
        this._updatePayloadAttr('buttonUrl', event.target.value);
    }

    @action
    setBorder(border, event) {
        this._updatePayloadAttr('border', border);
    }

    @action
    registerEditor(textReplacementEditor) {
        let commands = {
            'META+ENTER': run.bind(this, this._enter, 'meta'),
            'CTRL+ENTER': run.bind(this, this._enter, 'ctrl')
        };

        Object.keys(commands).forEach((str) => {
            textReplacementEditor.registerKeyCommand({
                str,
                run() {
                    return commands[str](textReplacementEditor, str);
                }
            });
        });

        this._textReplacementEditor = textReplacementEditor;

        run.scheduleOnce('afterRender', this, this._placeCursorAtEnd);
    }

    @action
    leaveEditMode() {
        const {html, buttonText, buttonUrl} = this.args.payload;

        if (isBlank(html) && isBlank(buttonText) && isBlank(buttonUrl)) {
            // afterRender is required to avoid double modification of `isSelected`
            // TODO: see if there's a way to avoid afterRender
            run.scheduleOnce('afterRender', this, this.args.deleteCard);
        }
    }

    @action
    blurElement(event) {
        event.preventDefault();
        event.target.blur();
    }

    @action
    focusElement(selector, event) {
        event.preventDefault();
        document.querySelector(selector)?.focus();
    }

    _updatePayloadAttr(attr, value) {
        let payload = this.args.payload;

        set(payload, attr, value);

        // update the mobiledoc and stay in edit mode
        this.args.saveCard?.(payload, false);
    }

    /* key commands ----------------------------------------------------------*/

    _enter(modifier) {
        if (this.isEditing && (modifier === 'meta' || (modifier === 'crtl' && Browser.isWin()))) {
            this.args.editCard?.();
        }
    }

    _placeCursorAtEnd() {
        if (!this._textReplacementEditor) {
            return;
        }

        let tailPosition = this._textReplacementEditor.post.tailPosition();
        let rangeToSelect = tailPosition.toRange();
        this._textReplacementEditor.selectRange(rangeToSelect);
    }
}
<commit_msg>Fixed linter error<commit_after>import Browser from 'mobiledoc-kit/utils/browser';
import Component from '@glimmer/component';
import {action} from '@ember/object';
import {formatTextReplacementHtml} from './koenig-text-replacement-html-input';
import {isBlank} from '@ember/utils';
import {run} from '@ember/runloop';
import {inject as service} from '@ember/service';
import {set} from '@ember/object';
import {tracked} from '@glimmer/tracking';

export default class KoenigCardEmailCtaComponent extends Component {
    @service ui;

    @tracked buttonFocused = false;
    @tracked contentFocused = false;

    get formattedHtml() {
        return formatTextReplacementHtml(this.args.payload.html);
    }

    get segments() {
        return [{
            name: 'free members',
            filter: 'status:free'
        }, {
            name: 'paid members',
            filter: 'status:-free'
        }];
    }

    get selectedSegment() {
        return this.segments.find(segment => segment.filter === this.args.payload.segment);
    }

    get hasTopBorder() {
        const border = this.args.payload?.border;
        return border === 'top' || border === 'both';
    }

    get hasBottomBorder() {
        const border = this.args.payload?.border;
        return border === 'bottom' || border === 'both';
    }

    get toolbar() {
        if (this.args.isEditing) {
            return false;
        }

        return {
            items: [{
                title: 'No dividers',
                icon: 'koenig/kg-img-regular',
                iconClass: this.args.payload.border === 'none' ? 'fill-green-l2' : 'fill-white',
                action: run.bind(this, this.setBorder, null)
            }, {
                title: 'Top divider',
                icon: 'koenig/kg-img-regular',
                iconClass: this.args.payload.border === 'top' ? 'fill-green-l2' : 'fill-white',
                action: run.bind(this, this.setBorder, 'top')
            }, {
                title: 'Both dividers',
                icon: 'koenig/kg-img-regular',
                iconClass: this.args.payload.border === 'both' ? 'fill-green-l2' : 'fill-white',
                action: run.bind(this, this.setBorder, 'both')
            }, {
                title: 'Bottom divider',
                icon: 'koenig/kg-img-regular',
                iconClass: this.args.payload.border === 'bottom' ? 'fill-green-l2' : 'fill-white',
                action: run.bind(this, this.setBorder, 'bottom')
            }, {
                divider: true
            }, {
                buttonClass: 'fw4 flex items-center white',
                icon: 'koenig/kg-edit',
                iconClass: 'fill-white',
                title: 'Edit',
                text: '',
                action: run.bind(this, this.args.editCard)
            }]
        };
    }

    constructor() {
        super(...arguments);
        this.args.registerComponent(this);
        if (!this.args.payload.segment) {
            this._updatePayloadAttr('segment', 'status:free');
        }
    }

    @action
    updateHtml(html) {
        this._updatePayloadAttr('html', html);
    }

    @action
    setSegment(segment) {
        this._updatePayloadAttr('segment', segment.filter);
    }

    @action
    setButtonText(event) {
        this._updatePayloadAttr('buttonText', event.target.value);
    }

    @action
    setButtonUrl(event) {
        this._updatePayloadAttr('buttonUrl', event.target.value);
    }

    @action
    setBorder(border) {
        this._updatePayloadAttr('border', border);
    }

    @action
    registerEditor(textReplacementEditor) {
        let commands = {
            'META+ENTER': run.bind(this, this._enter, 'meta'),
            'CTRL+ENTER': run.bind(this, this._enter, 'ctrl')
        };

        Object.keys(commands).forEach((str) => {
            textReplacementEditor.registerKeyCommand({
                str,
                run() {
                    return commands[str](textReplacementEditor, str);
                }
            });
        });

        this._textReplacementEditor = textReplacementEditor;

        run.scheduleOnce('afterRender', this, this._placeCursorAtEnd);
    }

    @action
    leaveEditMode() {
        const {html, buttonText, buttonUrl} = this.args.payload;

        if (isBlank(html) && isBlank(buttonText) && isBlank(buttonUrl)) {
            // afterRender is required to avoid double modification of `isSelected`
            // TODO: see if there's a way to avoid afterRender
            run.scheduleOnce('afterRender', this, this.args.deleteCard);
        }
    }

    @action
    blurElement(event) {
        event.preventDefault();
        event.target.blur();
    }

    @action
    focusElement(selector, event) {
        event.preventDefault();
        document.querySelector(selector)?.focus();
    }

    _updatePayloadAttr(attr, value) {
        let payload = this.args.payload;

        set(payload, attr, value);

        // update the mobiledoc and stay in edit mode
        this.args.saveCard?.(payload, false);
    }

    /* key commands ----------------------------------------------------------*/

    _enter(modifier) {
        if (this.isEditing && (modifier === 'meta' || (modifier === 'crtl' && Browser.isWin()))) {
            this.args.editCard?.();
        }
    }

    _placeCursorAtEnd() {
        if (!this._textReplacementEditor) {
            return;
        }

        let tailPosition = this._textReplacementEditor.post.tailPosition();
        let rangeToSelect = tailPosition.toRange();
        this._textReplacementEditor.selectRange(rangeToSelect);
    }
}
<|endoftext|>


<commit_before>

Template.post_body.helpers({
	update:	function()	{
		// Variables
		var	text=	this.htmlBody;
		var	index=	-1;
		var	nested=	0;
		var	lp=	-1;
		var	rp=	-1;
		var	sp=	-1;
		var	ep=	-1;

		while(true)
		{
			index=	text.indexOf("plot(", index+1);
			if(index=== -1)
				break;
			nested=	0;
			sp=	index+5;
			do
			{
				lp=	text.indexOf("(", index+1);
				rp=	text.indexOf(")", index+1);
				if(lp!== -1 && lp< rp)
				{
					index=	lp;
					nested++;
				}
				else if(rp!== -1 && (lp=== -1 || rp< lp))
				{
					index=	rp;
					nested--;
				}
			}while(nested> 0 && rp!== -1);
			ep=	rp;
			text=	text.replace(text.substring(sp-5, ep+1), "

"+text.substring(sp, ep)+"

");
		}

		this.htmlBody=	text;
	}
});<commit_msg>slupdate<commit_after>

Template.post_body.helpers({
	update:	function()	{
		// Variables
		var	text=	this.htmlBody;
		var	index=	-1;
		var	nested=	0;
		var	lp=	-1;
		var	rp=	-1;
		var	sp=	-1;
		var	ep=	-1;
		var	url=	"";

		while(true)
		{
			index=	text.indexOf("plot(", index+1);
			if(index=== -1)
				break;
			nested=	0;
			sp=	index+5;
			do
			{
				lp=	text.indexOf("(", index+1);
				rp=	text.indexOf(")", index+1);
				if(lp!== -1 && lp< rp)
				{
					index=	lp;
					nested++;
				}
				else if(rp!== -1 && (lp=== -1 || rp< lp))
				{
					index=	rp;
					nested--;
				}
			}while(nested> 0 && rp!== -1);
			ep=	rp;
			url=	"https://rawgit.com/johnRedden/OpenGraph/master/index.html?readType=iframe&"+(text.substring(sp, ep).replace(/\$/g, ""));
			/*
				Text here does a couple of things:
					-First it tries to replace the plot(*) and it gets out of the paragraph.
					-Second it tries to center the MathJax
					-Third it tries to setup an iframe that is used by opengraphingcalculator, although I don't know if it is better than rendering it by jsxgraph
					-Lastly it gives you a link to open it up in opengraphingcalculator
			*/
			text=	text.replace(text.substring(sp-5, ep+1), "

"+
				((text.substring(sp, ep).indexOf("$")!== -1) ? text.substring(sp, ep) : "$ "+text.substring(sp, ep)+" $")+
				"
"+
				"
<button style='width: 100%;max-width:100%;' onclick='"+
				"location.assign(\""+url.replace("readType=iframe", "readType=text")+"\");'>Open in OpenGraphingCalculator.com</button>
");
		}

		this.htmlBody=	text;
	}
});<|endoftext|>