<commit_before>/**
 * @license
 * Copyright 2019 Google LLC
 *
 * 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.
 */

// ################################################################
// ### FUNCTIONS ###
// ################################################################

// Define coefficients supplied by Roy et. al. 2016 for translating ETM+
// surface reflectance to OLI surface reflectance.
var coefficients = {
  itcps: ee.Image.constant([0.0003, 0.0088, 0.0061, 0.0412, 0.0254, 0.0172]).multiply(10000),
  slopes: ee.Image.constant([0.8474, 0.8483, 0.9047, 0.8462, 0.8937, 0.9071])
};

// Define function to get consistent band names (OLI).
function oliBands(img) {
  return ee.Image(ee.Algorithms.If(
    ee.Algorithms.IsEqual(img.get('SATELLITE'), ee.String('LANDSAT_8')),
    img.select(['B2', 'B3', 'B4', 'B5', 'B6', 'B7', 'pixel_qa']),
    img.select(['B1', 'B2', 'B3', 'B4', 'B5', 'B7', 'pixel_qa'], ['B2', 'B3', 'B4', 'B5', 'B6', 'B7', 'pixel_qa'])
  ));
}

// Define function to apply harmonization transformation.
function etm2oli(img) {
  return img.select(['B2', 'B3', 'B4', 'B5', 'B6', 'B7'])
    .multiply(coefficients.slopes)
    .add(coefficients.itcps)
    .round()
    .toShort()
    .addBands(img.select('pixel_qa')
  );
}

// Define function to mask out clouds and cloud shadows.
function fmask(img) {
  var cloudShadowBitMask = 1 << 3;
  var cloudsBitMask = 1 << 5;
  var qa = img.select('pixel_qa');
  var mask = qa.bitwiseAnd(cloudShadowBitMask).eq(0)
    .and(qa.bitwiseAnd(cloudsBitMask).eq(0));
  return img.updateMask(mask);
}

// Define function to calculate NBR.
function calcNBR(img) {
  return img.normalizedDifference(['B5', 'B7']).rename('NBR');
}

// Define wrapper function to prepare images using above functions.
function prepImg(img) {
  var orig = img;
  img = oliBands(img);
  img = fmask(img);
  img = etm2oli(img);
  img = calcNBR(img);
  return ee.Image(img.copyProperties(orig, orig.propertyNames()));
}

// ################################################################
// ### APPLICATION ###
// ################################################################

// Define a point on the north slope of Mount Hood, OR
// to extract time series for.
var point = ee.Geometry.Point([-121.70938, 45.43185]);

// Get Landsat surface reflectance collections for OLI, ETM+ and TM sensors.
var oliCol = ee.ImageCollection('LANDSAT/LC08/C01/T1_SR');
var etmCol= ee.ImageCollection('LANDSAT/LE07/C01/T1_SR');
var tmCol= ee.ImageCollection('LANDSAT/LT05/C01/T1_SR');

// Define a collection filter.
var colFilter = ee.Filter.and(
  ee.Filter.bounds(point),
  ee.Filter.calendarRange(182, 244, 'day_of_year'),
  ee.Filter.lt('CLOUD_COVER', 50),
  ee.Filter.lt('GEOMETRIC_RMSE_MODEL', 10),
  ee.Filter.or(
    ee.Filter.eq('IMAGE_QUALITY', 9),
    ee.Filter.eq('IMAGE_QUALITY_OLI', 9)
  )
);

// Merge collections, apply filter, and prepare images.
var col = oliCol
  .merge(etmCol)
  .merge(tmCol)
  .filter(colFilter)
  .map(prepImg);

// Extract NBR values for all pixels in the collection that
// intersect the point. Returns FeatureCollection - copy image
// properties to each feature.
var allObs = col.map(function(img) {
  return ee.Feature(null, img.reduceRegion({
    geometry: point,
    reducer: ee.Reducer.first(),
    scale: 30
  })).copyProperties(img, img.propertyNames());
});

// Make a chart of all observations in FeatureCollection.
var chartAllObs = ui.Chart.feature.groups(
  allObs, 'system:time_start', 'NBR', 'SATELLITE'
)
.setChartType('ScatterChart')
.setSeriesNames(['TM', 'ETM+', 'OLI'])
.setOptions({
  title: 'All Observations',
  colors: ['f8766d', '00ba38', '619cff'],
  hAxis: {title: 'Date'},
  vAxis: {title: 'NBR'},
  pointSize: 6,
  dataOpacity: 0.5
});
print(chartAllObs);

// Reduce the ImageCollection to intra-annual median.
// Need to identify same-year images by a join.
// Start by adding a 'year' property to each image.
var col = col.map(function(img) {
  return img.set('year', img.date().get('year'));
});

// Subset collection to a set of distinct year representatives.
var distinctYearCol = col.distinct('year');

// Define a filter that identifies images from the complete
// collection that match the 'year' from the distinct year collection.
var filter = ee.Filter.equals({leftField: 'year', rightField: 'year'});

// Define a join.
var join = ee.Join.saveAll('year_matches');

// Apply the join and convert the resulting FeatureCollection to an ImageCollection.
var joinCol = ee.ImageCollection(join.apply(distinctYearCol, col, filter));

// Apply median reduction among matching year collections.
var medianComp = joinCol.map(function(img) {
  var yearCol = ee.ImageCollection.fromImages(
    img.get('year_matches')
  );
  return yearCol.reduce(ee.Reducer.median())
    .set('system:time_start', img.date().update({month:8, day:1}));
});

// Make a chart that displays the annual median NBR composite.
var chartMedianComp = ui.Chart.image.series({
  imageCollection: medianComp,
  region: point,
  reducer: ee.Reducer.first(),
  scale: 30,
  xProperty: 'system:time_start',
})
.setSeriesNames(['NBR Median'])
.setOptions({
  title: 'Intra-annual Median',
  colors: ['619cff'],
  hAxis: {title: 'Date'},
  vAxis: {title: 'NBR'},
  lineWidth: 6
});
print(chartMedianComp);

// ################################################################
// ### ALTERNATIVE TRANSFORMATION FUNCTIONS ###
// ################################################################

// Roy et al. (2016) Table 2 provides OLS and RMA regression coefficients
// to translate ETM+ surface reflectance to OLI surface reflectance and vice
// versa. The above tutorial demonstrates only ETM+ to OLI transformation by OLS
// regression. Below are functions for all translation options. Replace above
// lines: 67 (band name standardization) and 69 (transformation) of the `prepImg`
// function to apply different transformation options.

// Define OLS and RMA surface regression coefficients.
var coefficients = {
  etm2oli_ols: {
    itcps: ee.Image.constant([0.0003, 0.0088, 0.0061, 0.0412, 0.0254, 0.0172]).multiply(10000),
    slopes: ee.Image.constant([0.8474, 0.8483, 0.9047, 0.8462, 0.8937, 0.9071])
  },
  oli2etm_ols: {
    itcps: ee.Image.constant([0.0183, 0.0123, 0.0123, 0.0448, 0.0306, 0.0116]).multiply(10000),
    slopes: ee.Image.constant([0.885, 0.9317, 0.9372, 0.8339, 0.8639, 0.9165])
  },
  rma: {
    itcps: ee.Image.constant([-0.0095, -0.0016, -0.0022, -0.0021, -0.0030, 0.0029]).multiply(10000),
    slopes: ee.Image.constant([0.9785, 0.9542, 0.9825, 1.0073, 1.0171, 0.9949])
  }
};

// Define function to apply OLS ETM+ to OLI transformation.
function etm2oli_ols(img) {
  return img.select(['B2', 'B3', 'B4', 'B5', 'B6', 'B7'])
    .multiply(coefficients.etm2oli_ols.slopes)
    .add(coefficients.etm2oli_ols.itcps)
    .round()
    .toShort()
    .addBands(img.select('pixel_qa')
  );
}

// Define function to apply OLS OLI to ETM+ transformation.
function oli2etm_ols(img) {
  return ee.Image(
    img.select(['B1', 'B2', 'B3', 'B4', 'B5', 'B7'])
      .multiply(coefficients.oli2etm_ols.slopes)
      .add(coefficients.oli2etm_ols.itcps)
      .round()
      .toShort()
      .addBands(img.select('pixel_qa'))
      .copyProperties(img, ['system:time_start'])
  );
}

// Define function to apply RMA OLI to ETM+ transformation.
function oli2etm_rma(img) {
  return ee.Image(
    img.select(['B1', 'B2', 'B3', 'B4', 'B5', 'B7'])
      .subtract(coefficients.rma.itcps)
      .divide(coefficients.rma.slopes)
      .round()
      .toShort()
      .addBands(img.select('pixel_qa'))
      .copyProperties(img, ['system:time_start'])
  );
}

// Define function to apply RMA ETM+ to OLI transformation.
function etm2oli_rma(img) {
  return ee.Image(
    img.select(['B2', 'B3', 'B4', 'B5', 'B6', 'B7'])
      .multiply(coefficients.rma.slopes)
      .add(coefficients.rma.itcps)
      .round()
      .toShort()
      .addBands(img.select('pixel_qa'))
      .copyProperties(img, ['system:time_start'])
  );
}

// Define function to get consistent band names (OLI).
function oliBands(img) {
  return ee.Image(ee.Algorithms.If(
    ee.Algorithms.IsEqual(img.get('SATELLITE'), ee.String('LANDSAT_8')),
    img.select(['B2', 'B3', 'B4', 'B5', 'B6', 'B7', 'pixel_qa']),
    img.select(['B1', 'B2', 'B3', 'B4', 'B5', 'B7', 'pixel_qa'], ['B2', 'B3', 'B4', 'B5', 'B6', 'B7', 'pixel_qa'])
  ));
}

// Define function to get consistent band names (ETM+).
function etmBands(img) {
  return ee.Image(ee.Algorithms.If(
    ee.Algorithms.IsEqual(img.get('SATELLITE'), ee.String('LANDSAT_8')),
    img.select(['B2', 'B3', 'B4', 'B5', 'B6', 'B7', 'pixel_qa'], ['B1', 'B2', 'B3', 'B4', 'B5', 'B7', 'pixel_qa']),
    img.select(['B1', 'B2', 'B3', 'B4', 'B5', 'B7', 'pixel_qa'])
  ));
}
<commit_msg>Add point display<commit_after>/**
 * @license
 * Copyright 2019 Google LLC
 *
 * 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.
 */

// ################################################################
// ### FUNCTIONS ###
// ################################################################

// Define coefficients supplied by Roy et. al. 2016 for translating ETM+
// surface reflectance to OLI surface reflectance.
var coefficients = {
  itcps: ee.Image.constant([0.0003, 0.0088, 0.0061, 0.0412, 0.0254, 0.0172]).multiply(10000),
  slopes: ee.Image.constant([0.8474, 0.8483, 0.9047, 0.8462, 0.8937, 0.9071])
};

// Define function to get consistent band names (OLI).
function oliBands(img) {
  return ee.Image(ee.Algorithms.If(
    ee.Algorithms.IsEqual(img.get('SATELLITE'), ee.String('LANDSAT_8')),
    img.select(['B2', 'B3', 'B4', 'B5', 'B6', 'B7', 'pixel_qa']),
    img.select(['B1', 'B2', 'B3', 'B4', 'B5', 'B7', 'pixel_qa'], ['B2', 'B3', 'B4', 'B5', 'B6', 'B7', 'pixel_qa'])
  ));
}

// Define function to apply harmonization transformation.
function etm2oli(img) {
  return img.select(['B2', 'B3', 'B4', 'B5', 'B6', 'B7'])
    .multiply(coefficients.slopes)
    .add(coefficients.itcps)
    .round()
    .toShort()
    .addBands(img.select('pixel_qa')
  );
}

// Define function to mask out clouds and cloud shadows.
function fmask(img) {
  var cloudShadowBitMask = 1 << 3;
  var cloudsBitMask = 1 << 5;
  var qa = img.select('pixel_qa');
  var mask = qa.bitwiseAnd(cloudShadowBitMask).eq(0)
    .and(qa.bitwiseAnd(cloudsBitMask).eq(0));
  return img.updateMask(mask);
}

// Define function to calculate NBR.
function calcNBR(img) {
  return img.normalizedDifference(['B5', 'B7']).rename('NBR');
}

// Define wrapper function to prepare images using above functions.
function prepImg(img) {
  var orig = img;
  img = oliBands(img);
  img = fmask(img);
  img = etm2oli(img);
  img = calcNBR(img);
  return ee.Image(img.copyProperties(orig, orig.propertyNames()));
}

// ################################################################
// ### APPLICATION ###
// ################################################################

// Define a point on the north slope of Mount Hood, OR
// to extract time series for.
var point = ee.Geometry.Point([-121.70938, 45.43185]);

// Display point on the map.
Map.centerObject(point, 9);
Map.addLayer(point, {color: 'f8766d'}, 'AOI');
Map.setOptions('HYBRID');

// Get Landsat surface reflectance collections for OLI, ETM+ and TM sensors.
var oliCol = ee.ImageCollection('LANDSAT/LC08/C01/T1_SR');
var etmCol= ee.ImageCollection('LANDSAT/LE07/C01/T1_SR');
var tmCol= ee.ImageCollection('LANDSAT/LT05/C01/T1_SR');

// Define a collection filter.
var colFilter = ee.Filter.and(
  ee.Filter.bounds(point),
  ee.Filter.calendarRange(182, 244, 'day_of_year'),
  ee.Filter.lt('CLOUD_COVER', 50),
  ee.Filter.lt('GEOMETRIC_RMSE_MODEL', 10),
  ee.Filter.or(
    ee.Filter.eq('IMAGE_QUALITY', 9),
    ee.Filter.eq('IMAGE_QUALITY_OLI', 9)
  )
);

// Merge collections, apply filter, and prepare images.
var col = oliCol
  .merge(etmCol)
  .merge(tmCol)
  .filter(colFilter)
  .map(prepImg);

// Extract NBR values for all pixels in the collection that
// intersect the point. Returns FeatureCollection - copy image
// properties to each feature.
var allObs = col.map(function(img) {
  return ee.Feature(null, img.reduceRegion({
    geometry: point,
    reducer: ee.Reducer.first(),
    scale: 30
  })).copyProperties(img, img.propertyNames());
});

// Make a chart of all observations in FeatureCollection.
var chartAllObs = ui.Chart.feature.groups(
  allObs, 'system:time_start', 'NBR', 'SATELLITE'
)
.setChartType('ScatterChart')
.setSeriesNames(['TM', 'ETM+', 'OLI'])
.setOptions({
  title: 'All Observations',
  colors: ['f8766d', '00ba38', '619cff'],
  hAxis: {title: 'Date'},
  vAxis: {title: 'NBR'},
  pointSize: 6,
  dataOpacity: 0.5
});
print(chartAllObs);

// Reduce the ImageCollection to intra-annual median.
// Need to identify same-year images by a join.
// Start by adding a 'year' property to each image.
var col = col.map(function(img) {
  return img.set('year', img.date().get('year'));
});

// Subset collection to a set of distinct year representatives.
var distinctYearCol = col.distinct('year');

// Define a filter that identifies images from the complete
// collection that match the 'year' from the distinct year collection.
var filter = ee.Filter.equals({leftField: 'year', rightField: 'year'});

// Define a join.
var join = ee.Join.saveAll('year_matches');

// Apply the join and convert the resulting FeatureCollection to an ImageCollection.
var joinCol = ee.ImageCollection(join.apply(distinctYearCol, col, filter));

// Apply median reduction among matching year collections.
var medianComp = joinCol.map(function(img) {
  var yearCol = ee.ImageCollection.fromImages(
    img.get('year_matches')
  );
  return yearCol.reduce(ee.Reducer.median())
    .set('system:time_start', img.date().update({month:8, day:1}));
});

// Make a chart that displays the annual median NBR composite.
var chartMedianComp = ui.Chart.image.series({
  imageCollection: medianComp,
  region: point,
  reducer: ee.Reducer.first(),
  scale: 30,
  xProperty: 'system:time_start',
})
.setSeriesNames(['NBR Median'])
.setOptions({
  title: 'Intra-annual Median',
  colors: ['619cff'],
  hAxis: {title: 'Date'},
  vAxis: {title: 'NBR'},
  lineWidth: 6
});
print(chartMedianComp);

// ################################################################
// ### ALTERNATIVE TRANSFORMATION FUNCTIONS ###
// ################################################################

// Roy et al. (2016) Table 2 provides OLS and RMA regression coefficients
// to translate ETM+ surface reflectance to OLI surface reflectance and vice
// versa. The above tutorial demonstrates only ETM+ to OLI transformation by OLS
// regression. Below are functions for all translation options. Replace above
// lines: 67 (band name standardization) and 69 (transformation) of the `prepImg`
// function to apply different transformation options.

// Define OLS and RMA surface regression coefficients.
var coefficients = {
  etm2oli_ols: {
    itcps: ee.Image.constant([0.0003, 0.0088, 0.0061, 0.0412, 0.0254, 0.0172]).multiply(10000),
    slopes: ee.Image.constant([0.8474, 0.8483, 0.9047, 0.8462, 0.8937, 0.9071])
  },
  oli2etm_ols: {
    itcps: ee.Image.constant([0.0183, 0.0123, 0.0123, 0.0448, 0.0306, 0.0116]).multiply(10000),
    slopes: ee.Image.constant([0.885, 0.9317, 0.9372, 0.8339, 0.8639, 0.9165])
  },
  rma: {
    itcps: ee.Image.constant([-0.0095, -0.0016, -0.0022, -0.0021, -0.0030, 0.0029]).multiply(10000),
    slopes: ee.Image.constant([0.9785, 0.9542, 0.9825, 1.0073, 1.0171, 0.9949])
  }
};

// Define function to apply OLS ETM+ to OLI transformation.
function etm2oli_ols(img) {
  return img.select(['B2', 'B3', 'B4', 'B5', 'B6', 'B7'])
    .multiply(coefficients.etm2oli_ols.slopes)
    .add(coefficients.etm2oli_ols.itcps)
    .round()
    .toShort()
    .addBands(img.select('pixel_qa')
  );
}

// Define function to apply OLS OLI to ETM+ transformation.
function oli2etm_ols(img) {
  return ee.Image(
    img.select(['B1', 'B2', 'B3', 'B4', 'B5', 'B7'])
      .multiply(coefficients.oli2etm_ols.slopes)
      .add(coefficients.oli2etm_ols.itcps)
      .round()
      .toShort()
      .addBands(img.select('pixel_qa'))
      .copyProperties(img, ['system:time_start'])
  );
}

// Define function to apply RMA OLI to ETM+ transformation.
function oli2etm_rma(img) {
  return ee.Image(
    img.select(['B1', 'B2', 'B3', 'B4', 'B5', 'B7'])
      .subtract(coefficients.rma.itcps)
      .divide(coefficients.rma.slopes)
      .round()
      .toShort()
      .addBands(img.select('pixel_qa'))
      .copyProperties(img, ['system:time_start'])
  );
}

// Define function to apply RMA ETM+ to OLI transformation.
function etm2oli_rma(img) {
  return ee.Image(
    img.select(['B2', 'B3', 'B4', 'B5', 'B6', 'B7'])
      .multiply(coefficients.rma.slopes)
      .add(coefficients.rma.itcps)
      .round()
      .toShort()
      .addBands(img.select('pixel_qa'))
      .copyProperties(img, ['system:time_start'])
  );
}

// Define function to get consistent band names (OLI).
function oliBands(img) {
  return ee.Image(ee.Algorithms.If(
    ee.Algorithms.IsEqual(img.get('SATELLITE'), ee.String('LANDSAT_8')),
    img.select(['B2', 'B3', 'B4', 'B5', 'B6', 'B7', 'pixel_qa']),
    img.select(['B1', 'B2', 'B3', 'B4', 'B5', 'B7', 'pixel_qa'], ['B2', 'B3', 'B4', 'B5', 'B6', 'B7', 'pixel_qa'])
  ));
}

// Define function to get consistent band names (ETM+).
function etmBands(img) {
  return ee.Image(ee.Algorithms.If(
    ee.Algorithms.IsEqual(img.get('SATELLITE'), ee.String('LANDSAT_8')),
    img.select(['B2', 'B3', 'B4', 'B5', 'B6', 'B7', 'pixel_qa'], ['B1', 'B2', 'B3', 'B4', 'B5', 'B7', 'pixel_qa']),
    img.select(['B1', 'B2', 'B3', 'B4', 'B5', 'B7', 'pixel_qa'])
  ));
}
<|endoftext|>


<commit_before>const { URL } = require(`url`)
const { createRemoteFileNode } = require(`gatsby-source-filesystem`)
const path = require(`path`)
const probeImageSize = require(`probe-image-size`)

const { getOptions } = require(`./plugin-options`)
const getHref = link => {
  if (typeof link === `object`) {
    return link.href
  }
  return link
}

exports.getHref = getHref

const imageCDNState = {
  foundPlaceholderStyle: false,
  hasLoggedNoPlaceholderStyle: false,
}

exports.imageCDNState = imageCDNState

/**
 * This FN takes in node data and returns Gatsby Image CDN fields that should be added to that node. If the input node isn't an image an empty object is returned.
 */
const getGatsbyImageCdnFields = async ({
  node,
  type,
  pluginOptions,
  fileNodesExtendedData,
  reporter,
}) => {
  if (!pluginOptions?.skipFileDownloads) {
    return {}
  }

  const isFile = isFileNode({
    internal: {
      type,
    },
  })

  if (!isFile) {
    return {}
  }

  const mimeType = node.attributes.filemime

  if (!mimeType) {
    return {}
  }

  const url = getFileUrl(node.attributes, pluginOptions.baseUrl)?.href

  if (!url) {
    return {}
  }

  if (!mimeType.includes(`image/`)) {
    return {
      mimeType,
      url,
    }
  }

  const extraNodeData = fileNodesExtendedData?.get?.(node.id) || null

  try {
    const { placeholderStyleName } = getOptions()
    const placeholderUrl =
      extraNodeData?.imageDerivatives?.links?.[placeholderStyleName]?.href ||
      extraNodeData?.imageDerivatives?.links?.placeholder?.href ||
      url

    if (placeholderUrl !== url) {
      imageCDNState.foundPlaceholderStyle = true
    }

    const hasRequiredData = input => input && input.width && input.height

    const imageSize = hasRequiredData(extraNodeData)
      ? extraNodeData
      : await probeImageSize(url)

    if (!hasRequiredData(imageSize) || !placeholderUrl) {
      return {}
    }

    const gatsbyImageCdnFields = {
      filename: node.attributes?.filename,
      url,
      placeholderUrl,
      width: imageSize.width,
      height: imageSize.height,
      mimeType,
    }

    return gatsbyImageCdnFields
  } catch (e) {
    reporter.error(e)
    reporter.info(
      JSON.stringify(
        {
          extraNodeData,
          url,
          attributes: node.attributes,
        },
        null,
        2
      )
    )
    reporter.panic(
      `Encountered an unrecoverable error while generating Gatsby Image CDN fields. See above for additional information.`
    )
  }

  return {}
}

const nodeFromData = async (
  datum,
  createNodeId,
  entityReferenceRevisions = [],
  pluginOptions,
  fileNodesExtendedData,
  reporter
) => {
  const { attributes: { id: attributeId, ...attributes } = {} } = datum
  const preservedId =
    typeof attributeId !== `undefined` ? { _attributes_id: attributeId } : {}
  const langcode = attributes.langcode || `und`
  const type = datum.type.replace(/-|__|:|\.|\s/g, `_`)

  const gatsbyImageCdnFields = await getGatsbyImageCdnFields({
    node: datum,
    type,
    pluginOptions,
    fileNodesExtendedData,
    reporter,
  })

  return {
    id: createNodeId(
      createNodeIdWithVersion(
        datum.id,
        datum.type,
        langcode,
        attributes.drupal_internal__revision_id,
        entityReferenceRevisions
      )
    ),
    drupal_id: datum.id,
    parent: null,
    drupal_parent_menu_item: attributes.parent,
    children: [],
    ...attributes,
    ...preservedId,
    ...gatsbyImageCdnFields,
    drupal_relationships: datum.relationships,
    relationships: {},
    internal: {
      type,
    },
  }
}

exports.nodeFromData = nodeFromData

const isEntityReferenceRevision = (type, entityReferenceRevisions = []) =>
  entityReferenceRevisions.findIndex(
    revisionType => type.indexOf(revisionType) === 0
  ) !== -1

const createNodeIdWithVersion = (
  id,
  type,
  langcode,
  revisionId,
  entityReferenceRevisions = []
) => {
  // Fallback to default language for entities that don't translate.
  if (getOptions()?.languageConfig?.nonTranslatableEntities.includes(type)) {
    langcode = getOptions().languageConfig.defaultLanguage
  }

  // If the source plugin hasn't enabled `translation` then always just set langcode
  // to "undefined".
  let langcodeNormalized = getOptions().languageConfig ? langcode : `und`

  if (
    getOptions().languageConfig &&
    !getOptions().languageConfig?.enabledLanguages.includes(langcodeNormalized)
  ) {
    langcodeNormalized = getOptions().languageConfig.defaultLanguage
  }

  // The relationship between an entity and another entity also depends on the revision ID if the field is of type
  // entity reference revision such as for paragraphs.
  return isEntityReferenceRevision(type, entityReferenceRevisions)
    ? `${langcodeNormalized}.${id}.${revisionId || 0}`
    : `${langcodeNormalized}.${id}`
}

exports.createNodeIdWithVersion = createNodeIdWithVersion

const isFileNode = node => {
  const type = node?.internal?.type
  return type === `files` || type === `file__file`
}

exports.isFileNode = isFileNode

const getFileUrl = (node, baseUrl) => {
  let fileUrl = node.url

  if (typeof node.uri === `object`) {
    // Support JSON API 2.x file URI format https://www.drupal.org/node/2982209
    fileUrl = node.uri.url
  }

  // Resolve w/ baseUrl if node.uri isn't absolute.
  const url = new URL(fileUrl, baseUrl)

  return url
}

exports.downloadFile = async (
  { node, store, cache, createNode, createNodeId, getCache, reporter },
  { basicAuth, baseUrl }
) => {
  // handle file downloads
  if (isFileNode(node)) {
    let fileType

    if (typeof node.uri === `object`) {
      // get file type from uri prefix ("S3:", "public:", etc.)
      const uriPrefix = node.uri.value.match(/^\w*:/)
      fileType = uriPrefix ? uriPrefix[0] : null
    }

    const url = getFileUrl(node, baseUrl)

    // If we have basicAuth credentials, add them to the request.
    const basicAuthFileSystems = [`public:`, `private:`, `temporary:`]
    const auth =
      typeof basicAuth === `object` && basicAuthFileSystems.includes(fileType)
        ? {
            htaccess_user: basicAuth.username,
            htaccess_pass: basicAuth.password,
          }
        : {}
    const fileNode = await createRemoteFileNode({
      url: url.href,
      name: path.parse(decodeURIComponent(url.pathname)).name,
      store,
      cache,
      createNode,
      createNodeId,
      getCache,
      parentNodeId: node.id,
      auth,
      reporter,
    })
    if (fileNode) {
      node.localFile___NODE = fileNode.id
    }
  }
}
<commit_msg>Changes from Wards review at https://github.com/gatsbyjs/gatsby/pull/35265#pullrequestreview-949950960 (#35475)<commit_after>const { URL } = require(`url`)
const { createRemoteFileNode } = require(`gatsby-source-filesystem`)
const path = require(`path`)
const probeImageSize = require(`probe-image-size`)

const { getOptions } = require(`./plugin-options`)
const getHref = link => {
  if (typeof link === `object`) {
    return link.href
  }
  return link
}

exports.getHref = getHref

const imageCDNState = {
  foundPlaceholderStyle: false,
  hasLoggedNoPlaceholderStyle: false,
}

exports.imageCDNState = imageCDNState

/**
 * This FN takes in node data and returns Gatsby Image CDN fields that should be added to that node. If the input node isn't an image an empty object is returned.
 */
const getGatsbyImageCdnFields = async ({
  node,
  type,
  pluginOptions,
  fileNodesExtendedData,
  reporter,
}) => {
  if (!pluginOptions?.skipFileDownloads) {
    return {}
  }

  const isFile = isFileNode({
    internal: {
      type,
    },
  })

  if (!isFile) {
    return {}
  }

  const mimeType = node.attributes.filemime
  const { filename } = node.attributes

  if (!mimeType || !filename) {
    return {}
  }

  const url = getFileUrl(node.attributes, pluginOptions.baseUrl)?.href

  if (!url) {
    return {}
  }

  if (!mimeType.includes(`image/`)) {
    return {
      mimeType,
      filename,
      url,
    }
  }

  const extraNodeData = fileNodesExtendedData?.get?.(node.id) || null

  try {
    const { placeholderStyleName } = getOptions()
    const placeholderUrl =
      extraNodeData?.imageDerivatives?.links?.[placeholderStyleName]?.href ||
      extraNodeData?.imageDerivatives?.links?.placeholder?.href ||
      url

    if (placeholderUrl !== url) {
      imageCDNState.foundPlaceholderStyle = true
    }

    const hasRequiredData = input => input && input.width && input.height

    // extraNodeData comes from the fileNodesExtendedData Map which is built up in sourceNodes in gatsby-node. The data in this Map is found by looking at connections to file nodes from other node types. This is needed because Drupal's JSON API doesn't provide image widths/heights and placeholder urls for file nodes when querying directly for file nodes. This data can only be found on other nodes with relationships to file nodes. In the case that we don't have this data, we use probe-image-size to find the width/height of the image so that image CDN still works even if we don't have the data.
    const imageSize = hasRequiredData(extraNodeData)
      ? extraNodeData
      : await probeImageSize(url)

    if (!hasRequiredData(imageSize) || !placeholderUrl) {
      return {}
    }

    const gatsbyImageCdnFields = {
      filename,
      url,
      placeholderUrl,
      width: imageSize.width,
      height: imageSize.height,
      mimeType,
    }

    return gatsbyImageCdnFields
  } catch (e) {
    reporter.error(e)
    reporter.info(
      JSON.stringify(
        {
          extraNodeData,
          url,
          attributes: node.attributes,
        },
        null,
        2
      )
    )
    reporter.panic(
      `Encountered an unrecoverable error while generating Gatsby Image CDN fields. See above for additional information.`
    )
  }

  return {}
}

const nodeFromData = async (
  datum,
  createNodeId,
  entityReferenceRevisions = [],
  pluginOptions,
  fileNodesExtendedData,
  reporter
) => {
  const { attributes: { id: attributeId, ...attributes } = {} } = datum
  const preservedId =
    typeof attributeId !== `undefined` ? { _attributes_id: attributeId } : {}
  const langcode = attributes.langcode || `und`
  const type = datum.type.replace(/-|__|:|\.|\s/g, `_`)

  const gatsbyImageCdnFields = await getGatsbyImageCdnFields({
    node: datum,
    type,
    pluginOptions,
    fileNodesExtendedData,
    reporter,
  })

  return {
    id: createNodeId(
      createNodeIdWithVersion(
        datum.id,
        datum.type,
        langcode,
        attributes.drupal_internal__revision_id,
        entityReferenceRevisions
      )
    ),
    drupal_id: datum.id,
    parent: null,
    drupal_parent_menu_item: attributes.parent,
    children: [],
    ...attributes,
    ...preservedId,
    ...gatsbyImageCdnFields,
    drupal_relationships: datum.relationships,
    relationships: {},
    internal: {
      type,
    },
  }
}

exports.nodeFromData = nodeFromData

const isEntityReferenceRevision = (type, entityReferenceRevisions = []) =>
  entityReferenceRevisions.findIndex(
    revisionType => type.indexOf(revisionType) === 0
  ) !== -1

const createNodeIdWithVersion = (
  id,
  type,
  langcode,
  revisionId,
  entityReferenceRevisions = []
) => {
  // Fallback to default language for entities that don't translate.
  if (getOptions()?.languageConfig?.nonTranslatableEntities.includes(type)) {
    langcode = getOptions().languageConfig.defaultLanguage
  }

  // If the source plugin hasn't enabled `translation` then always just set langcode
  // to "undefined".
  let langcodeNormalized = getOptions().languageConfig ? langcode : `und`

  if (
    getOptions().languageConfig &&
    !getOptions().languageConfig?.enabledLanguages.includes(langcodeNormalized)
  ) {
    langcodeNormalized = getOptions().languageConfig.defaultLanguage
  }

  // The relationship between an entity and another entity also depends on the revision ID if the field is of type
  // entity reference revision such as for paragraphs.
  return isEntityReferenceRevision(type, entityReferenceRevisions)
    ? `${langcodeNormalized}.${id}.${revisionId || 0}`
    : `${langcodeNormalized}.${id}`
}

exports.createNodeIdWithVersion = createNodeIdWithVersion

const isFileNode = node => {
  const type = node?.internal?.type
  return type === `files` || type === `file__file`
}

exports.isFileNode = isFileNode

const getFileUrl = (node, baseUrl) => {
  let fileUrl = node.url

  if (typeof node.uri === `object`) {
    // Support JSON API 2.x file URI format https://www.drupal.org/node/2982209
    fileUrl = node.uri.url
  }

  // Resolve w/ baseUrl if node.uri isn't absolute.
  const url = new URL(fileUrl, baseUrl)

  return url
}

exports.downloadFile = async (
  { node, store, cache, createNode, createNodeId, getCache, reporter },
  { basicAuth, baseUrl }
) => {
  // handle file downloads
  if (isFileNode(node)) {
    let fileType

    if (typeof node.uri === `object`) {
      // get file type from uri prefix ("S3:", "public:", etc.)
      const uriPrefix = node.uri.value.match(/^\w*:/)
      fileType = uriPrefix ? uriPrefix[0] : null
    }

    const url = getFileUrl(node, baseUrl)

    // If we have basicAuth credentials, add them to the request.
    const basicAuthFileSystems = [`public:`, `private:`, `temporary:`]
    const auth =
      typeof basicAuth === `object` && basicAuthFileSystems.includes(fileType)
        ? {
            htaccess_user: basicAuth.username,
            htaccess_pass: basicAuth.password,
          }
        : {}
    const fileNode = await createRemoteFileNode({
      url: url.href,
      name: path.parse(decodeURIComponent(url.pathname)).name,
      store,
      cache,
      createNode,
      createNodeId,
      getCache,
      parentNodeId: node.id,
      auth,
      reporter,
    })
    if (fileNode) {
      node.localFile___NODE = fileNode.id
    }
  }
}
<|endoftext|>


<commit_before>import electron from 'electron';
import path from 'path';
import { Curl } from 'node-libcurl';
import fs from 'fs';
import LocalStorage from './local-storage';
import {
  changelogUrl,
  getAppLongName,
  getAppName,
  getAppReleaseDate,
  getAppVersion,
  isDevelopment,
  isLinux,
  isMac,
  MNEMONIC_SYM,
} from '../common/constants';
import * as misc from '../common/misc';
import * as log from '../common/log';
import * as os from 'os';
import { docsBase } from '../common/documentation';

const { app, Menu, BrowserWindow, shell, dialog, clipboard } = electron;

// So we can use native modules in renderer
// NOTE: This will be deprecated in Electron 10 and impossible in 11
//   https://github.com/electron/electron/issues/18397
app.allowRendererProcessReuse = false;

const DEFAULT_WIDTH = 1280;
const DEFAULT_HEIGHT = 700;
const MINIMUM_WIDTH = 500;
const MINIMUM_HEIGHT = 400;

let mainWindow = null;
let localStorage = null;

export function init() {
  initLocalStorage();
  initContextMenus();
}

export function createWindow() {
  const zoomFactor = getZoomFactor();
  const { bounds, fullscreen, maximize } = getBounds();
  const { x, y, width, height } = bounds;
  const appLogo = 'static/insomnia-core-logo_16x.png';

  let isVisibleOnAnyDisplay = true;
  for (const d of electron.screen.getAllDisplays()) {
    const isVisibleOnDisplay =
      x >= d.bounds.x &&
      y >= d.bounds.y &&
      x + width <= d.bounds.x + d.bounds.width &&
      y + height <= d.bounds.y + d.bounds.height;

    if (!isVisibleOnDisplay) {
      isVisibleOnAnyDisplay = false;
    }
  }

  mainWindow = new BrowserWindow({
    // Make sure we don't initialize the window outside the bounds
    x: isVisibleOnAnyDisplay ? x : undefined,
    y: isVisibleOnAnyDisplay ? y : undefined,

    // Other options
    fullscreen: fullscreen,
    fullscreenable: true,
    title: getAppName(),
    width: width || DEFAULT_WIDTH,
    height: height || DEFAULT_HEIGHT,
    minHeight: MINIMUM_HEIGHT,
    minWidth: MINIMUM_WIDTH,
    acceptFirstMouse: true,
    icon: path.resolve(__dirname, appLogo),
    webPreferences: {
      zoomFactor: zoomFactor,
      nodeIntegration: true,
      webviewTag: true,
      enableRemoteModule: true,
    },
  });

  // BrowserWindow doesn't have an option for this, so we have to do it manually :(
  if (maximize) {
    mainWindow.maximize();
  }

  mainWindow.on('resize', e => saveBounds());

  mainWindow.on('maximize', e => saveBounds());

  mainWindow.on('unmaximize', e => saveBounds());

  mainWindow.on('move', e => saveBounds());

  mainWindow.on('unresponsive', e => {
    showUnresponsiveModal();
  });

  // Open generic links (<a .../>) in default browser
  mainWindow.webContents.on('will-navigate', (e, url) => {
    if (url === appUrl) {
      return;
    }

    console.log('[app] Navigate to ' + url);
    e.preventDefault();
    electron.shell.openExternal(url);
  });

  // Load the html of the app.
  const url = process.env.APP_RENDER_URL;
  const appUrl = url || `file://${app.getAppPath()}/renderer.html`;
  console.log(`[main] Loading ${appUrl}`);
  mainWindow.loadURL(appUrl);

  // Emitted when the window is closed.
  mainWindow.on('closed', () => {
    // Dereference the window object, usually you would store windows
    // in an array if your app supports multi windows, this is the time
    // when you should delete the corresponding element.
    mainWindow = null;
  });

  const applicationMenu = {
    label: `${MNEMONIC_SYM}Application`,
    submenu: [
      {
        label: `${MNEMONIC_SYM}Preferences`,
        click: function(menuItem, window, e) {
          if (!window || !window.webContents) {
            return;
          }
          window.webContents.send('toggle-preferences');
        },
      },
      {
        label: `${MNEMONIC_SYM}Changelog`,
        click: function(menuItem, window, e) {
          if (!window || !window.webContents) {
            return;
          }
          misc.clickLink(changelogUrl());
        },
      },
      { type: 'separator' },
      { role: 'hide' },
      { role: 'hideothers' },
      { type: 'separator' },
      { label: `${MNEMONIC_SYM}Quit`, accelerator: 'CmdOrCtrl+Q', click: () => app.quit() },
    ],
  };

  const editMenu = {
    label: `${MNEMONIC_SYM}Edit`,
    submenu: [
      { label: `${MNEMONIC_SYM}Undo`, accelerator: 'CmdOrCtrl+Z', selector: 'undo:' },
      { label: `${MNEMONIC_SYM}Redo`, accelerator: 'Shift+CmdOrCtrl+Z', selector: 'redo:' },
      { type: 'separator' },
      { label: `Cu${MNEMONIC_SYM}t`, accelerator: 'CmdOrCtrl+X', selector: 'cut:' },
      { label: `${MNEMONIC_SYM}Copy`, accelerator: 'CmdOrCtrl+C', selector: 'copy:' },
      { label: `${MNEMONIC_SYM}Paste`, accelerator: 'CmdOrCtrl+V', selector: 'paste:' },
      {
        label: `Select ${MNEMONIC_SYM}All`,
        accelerator: 'CmdOrCtrl+A',
        selector: 'selectAll:',
      },
    ],
  };

  const viewMenu = {
    label: `${MNEMONIC_SYM}View`,
    submenu: [
      { label: `Toggle ${MNEMONIC_SYM}Full Screen`, role: 'togglefullscreen' },
      {
        label: `${MNEMONIC_SYM}Actual Size`,
        accelerator: 'CmdOrCtrl+0',
        click: () => {
          const w = BrowserWindow.getFocusedWindow();
          if (!w || !w.webContents) {
            return;
          }

          const zoomFactor = 1;
          w.webContents.setZoomFactor(zoomFactor);
          saveZoomFactor(zoomFactor);
        },
      },
      {
        label: `Zoom ${MNEMONIC_SYM}In`,
        accelerator: 'CmdOrCtrl+=',
        click: () => {
          const w = BrowserWindow.getFocusedWindow();
          if (!w || !w.webContents) {
            return;
          }

          const zoomFactor = Math.min(1.8, getZoomFactor() + 0.05);
          w.webContents.setZoomFactor(zoomFactor);

          saveZoomFactor(zoomFactor);
        },
      },
      {
        label: `Zoom ${MNEMONIC_SYM}Out`,
        accelerator: 'CmdOrCtrl+-',
        click: () => {
          const w = BrowserWindow.getFocusedWindow();
          if (!w || !w.webContents) {
            return;
          }

          const zoomFactor = Math.max(0.5, getZoomFactor() - 0.05);
          w.webContents.setZoomFactor(zoomFactor);
          saveZoomFactor(zoomFactor);
        },
      },
      {
        label: 'Toggle Sidebar',
        click: () => {
          const w = BrowserWindow.getFocusedWindow();
          if (!w || !w.webContents) {
            return;
          }

          w.webContents.send('toggle-sidebar');
        },
      },
      {
        label: `Toggle ${MNEMONIC_SYM}DevTools`,
        accelerator: 'Alt+CmdOrCtrl+I',
        click: () => mainWindow.toggleDevTools(),
      },
    ],
  };

  const windowMenu = {
    label: `${MNEMONIC_SYM}Window`,
    role: 'window',
    submenu: [
      { label: `${MNEMONIC_SYM}Minimize`, role: 'minimize' },
      ...(isMac() ? [{ label: `${MNEMONIC_SYM}Close`, role: 'close' }] : []),
    ],
  };

  const helpMenu = {
    label: `${MNEMONIC_SYM}Help`,
    role: 'help',
    id: 'help',
    submenu: [
      {
        label: `${MNEMONIC_SYM}Help and Support`,
        accelerator: !isMac() ? 'F1' : null,
        click: () => {
          shell.openExternal(docsBase);
        },
      },
      {
        label: `${MNEMONIC_SYM}Keyboard Shortcuts`,
        accelerator: 'CmdOrCtrl+Shift+?',
        click: (menuItem, w, e) => {
          if (!w || !w.webContents) {
            return;
          }
          w.webContents.send('toggle-preferences-shortcuts');
        },
      },
      {
        label: `Show App ${MNEMONIC_SYM}Data Folder`,
        click: (menuItem, w, e) => {
          const directory = misc.getDataDirectory();
          shell.showItemInFolder(directory);
        },
      },
      {
        label: `Show App ${MNEMONIC_SYM}Logs Folder`,
        click: (menuItem, w, e) => {
          const directory = log.getLogDirectory();
          shell.showItemInFolder(directory);
        },
      },
      {
        label: 'Show Open Source Licenses',
        click: (menuItem, w, e) => {
          const licensePath = path.resolve(app.getAppPath(), '../opensource-licenses.txt');
          shell.openPath(licensePath);
        },
      },
      {
        label: 'Show Software License',
        click: () => {
          shell.openExternal('https://insomnia.rest/license');
        },
      },
    ],
  };

  const aboutMenuClickHandler = async () => {
    const copy = 'Copy';
    const ok = 'OK';

    const buttons = isLinux() ? [copy, ok] : [ok, copy];

    const detail = [
      `Version: ${getAppLongName()} ${getAppVersion()}`,
      `Release date: ${getAppReleaseDate()}`,
      `OS: ${os.type()} ${os.arch()} ${os.release()}`,
      `Electron: ${process.versions.electron}`,
      `Node: ${process.versions.node}`,
      `V8: ${process.versions.v8}`,
      `Architecture: ${process.arch}`,
      `node-libcurl: ${Curl.getVersion()}`,
    ].join('\n');

    const msgBox = await dialog.showMessageBox({
      type: 'info',
      title: getAppName(),
      message: getAppLongName(),
      detail,
      buttons,
      defaultId: buttons.indexOf(ok),
      cancelId: buttons.indexOf(ok),
      noLink: true,
    });

    if (msgBox.response === buttons.indexOf(copy)) {
      clipboard.writeText(detail);
    }
  };

  if (isMac()) {
    applicationMenu.submenu.unshift(
      {
        label: `A${MNEMONIC_SYM}bout ${getAppName()}`,
        click: aboutMenuClickHandler,
      },
      { type: 'separator' },
    );
  } else {
    helpMenu.submenu.push({
      label: `${MNEMONIC_SYM}About`,
      click: aboutMenuClickHandler,
    });
  }

  const developerMenu = {
    label: `${MNEMONIC_SYM}Developer`,
    position: 'before=help',
    submenu: [
      {
        label: `${MNEMONIC_SYM}Reload`,
        accelerator: 'Shift+F5',
        click: () => mainWindow.reload(),
      },
      {
        label: `Resize to Defaul${MNEMONIC_SYM}t`,
        click: () =>
          mainWindow.setBounds({
            x: 100,
            y: 100,
            width: DEFAULT_WIDTH,
            height: DEFAULT_HEIGHT,
          }),
      },
      {
        label: `Take ${MNEMONIC_SYM}Screenshot`,
        click: function() {
          mainWindow.capturePage(image => {
            const buffer = image.toPNG();
            const dir = app.getPath('desktop');
            fs.writeFileSync(path.join(dir, `Screenshot-${new Date()}.png`), buffer);
          });
        },
      },
      {
        label: `${MNEMONIC_SYM}Restart`,
        click: function() {
          const { app } = electron.remote || electron;
          app.relaunch();
          app.exit();
        },
      },
    ],
  };

  const toolsMenu = {
    label: `${MNEMONIC_SYM}Tools`,
    submenu: [
      {
        label: `${MNEMONIC_SYM}Reload Plugins`,
        click: () => {
          const w = BrowserWindow.getFocusedWindow();
          if (!w || !w.webContents) {
            return;
          }

          w.webContents.send('reload-plugins');
        },
      },
    ],
  };

  const template = [];

  template.push(applicationMenu);
  template.push(editMenu);
  template.push(viewMenu);
  template.push(windowMenu);
  template.push(toolsMenu);
  template.push(helpMenu);

  if (isDevelopment() || process.env.INSOMNIA_FORCE_DEBUG) {
    template.push(developerMenu);
  }

  Menu.setApplicationMenu(Menu.buildFromTemplate(template));

  return mainWindow;
}

async function showUnresponsiveModal() {
  const id = await dialog.showMessageBox({
    type: 'info',
    buttons: ['Cancel', 'Reload'],
    defaultId: 1,
    cancelId: 0,
    title: 'Unresponsive',
    message: 'Insomnia has become unresponsive. Do you want to reload?',
  });

  if (id === 1) {
    mainWindow.destroy();
    createWindow();
  }
}

function saveBounds() {
  if (!mainWindow) {
    return;
  }

  const fullscreen = mainWindow.isFullScreen();

  // Only save the size if we're not in fullscreen
  if (!fullscreen) {
    localStorage.setItem('bounds', mainWindow.getBounds());
    localStorage.setItem('maximize', mainWindow.isMaximized());
    localStorage.setItem('fullscreen', false);
  } else {
    localStorage.setItem('fullscreen', true);
  }
}

function getBounds() {
  let bounds = {};
  let fullscreen = false;
  let maximize = false;
  try {
    bounds = localStorage.getItem('bounds', {});
    fullscreen = localStorage.getItem('fullscreen', false);
    maximize = localStorage.getItem('maximize', false);
  } catch (e) {
    // This should never happen, but if it does...!
    console.error('Failed to parse window bounds', e);
  }

  return { bounds, fullscreen, maximize };
}

function saveZoomFactor(zoomFactor) {
  localStorage.setItem('zoomFactor', zoomFactor);
}

function getZoomFactor() {
  let zoomFactor = 1;
  try {
    zoomFactor = localStorage.getItem('zoomFactor', 1);
  } catch (e) {
    // This should never happen, but if it does...!
    console.error('Failed to parse zoomFactor', e);
  }

  return zoomFactor;
}

function initLocalStorage() {
  const localStoragePath = path.join(misc.getDataDirectory(), 'localStorage');
  localStorage = new LocalStorage(localStoragePath);
}

function initContextMenus() {
  require('electron-context-menu')({});
}
<commit_msg>fix: use insomnia default dark color during initial startup (#3303)<commit_after>import electron from 'electron';
import path from 'path';
import { Curl } from 'node-libcurl';
import fs from 'fs';
import LocalStorage from './local-storage';
import {
  changelogUrl,
  getAppLongName,
  getAppName,
  getAppReleaseDate,
  getAppVersion,
  isDevelopment,
  isLinux,
  isMac,
  MNEMONIC_SYM,
} from '../common/constants';
import * as misc from '../common/misc';
import * as log from '../common/log';
import * as os from 'os';
import { docsBase } from '../common/documentation';

const { app, Menu, BrowserWindow, shell, dialog, clipboard } = electron;

// So we can use native modules in renderer
// NOTE: This will be deprecated in Electron 10 and impossible in 11
//   https://github.com/electron/electron/issues/18397
app.allowRendererProcessReuse = false;

const DEFAULT_WIDTH = 1280;
const DEFAULT_HEIGHT = 700;
const MINIMUM_WIDTH = 500;
const MINIMUM_HEIGHT = 400;

let mainWindow = null;
let localStorage = null;

export function init() {
  initLocalStorage();
  initContextMenus();
}

export function createWindow() {
  const zoomFactor = getZoomFactor();
  const { bounds, fullscreen, maximize } = getBounds();
  const { x, y, width, height } = bounds;
  const appLogo = 'static/insomnia-core-logo_16x.png';

  let isVisibleOnAnyDisplay = true;
  for (const d of electron.screen.getAllDisplays()) {
    const isVisibleOnDisplay =
      x >= d.bounds.x &&
      y >= d.bounds.y &&
      x + width <= d.bounds.x + d.bounds.width &&
      y + height <= d.bounds.y + d.bounds.height;

    if (!isVisibleOnDisplay) {
      isVisibleOnAnyDisplay = false;
    }
  }

  mainWindow = new BrowserWindow({
    // Make sure we don't initialize the window outside the bounds
    x: isVisibleOnAnyDisplay ? x : undefined,
    y: isVisibleOnAnyDisplay ? y : undefined,

    // Other options
    backgroundColor: '#2C2C2C',
    fullscreen: fullscreen,
    fullscreenable: true,
    title: getAppName(),
    width: width || DEFAULT_WIDTH,
    height: height || DEFAULT_HEIGHT,
    minHeight: MINIMUM_HEIGHT,
    minWidth: MINIMUM_WIDTH,
    acceptFirstMouse: true,
    icon: path.resolve(__dirname, appLogo),
    webPreferences: {
      zoomFactor: zoomFactor,
      nodeIntegration: true,
      webviewTag: true,
      enableRemoteModule: true,
    },
  });

  // BrowserWindow doesn't have an option for this, so we have to do it manually :(
  if (maximize) {
    mainWindow.maximize();
  }

  mainWindow.on('resize', e => saveBounds());

  mainWindow.on('maximize', e => saveBounds());

  mainWindow.on('unmaximize', e => saveBounds());

  mainWindow.on('move', e => saveBounds());

  mainWindow.on('unresponsive', e => {
    showUnresponsiveModal();
  });

  // Open generic links (<a .../>) in default browser
  mainWindow.webContents.on('will-navigate', (e, url) => {
    if (url === appUrl) {
      return;
    }

    console.log('[app] Navigate to ' + url);
    e.preventDefault();
    electron.shell.openExternal(url);
  });

  // Load the html of the app.
  const url = process.env.APP_RENDER_URL;
  const appUrl = url || `file://${app.getAppPath()}/renderer.html`;
  console.log(`[main] Loading ${appUrl}`);
  mainWindow.loadURL(appUrl);

  // Emitted when the window is closed.
  mainWindow.on('closed', () => {
    // Dereference the window object, usually you would store windows
    // in an array if your app supports multi windows, this is the time
    // when you should delete the corresponding element.
    mainWindow = null;
  });

  const applicationMenu = {
    label: `${MNEMONIC_SYM}Application`,
    submenu: [
      {
        label: `${MNEMONIC_SYM}Preferences`,
        click: function(menuItem, window, e) {
          if (!window || !window.webContents) {
            return;
          }
          window.webContents.send('toggle-preferences');
        },
      },
      {
        label: `${MNEMONIC_SYM}Changelog`,
        click: function(menuItem, window, e) {
          if (!window || !window.webContents) {
            return;
          }
          misc.clickLink(changelogUrl());
        },
      },
      { type: 'separator' },
      { role: 'hide' },
      { role: 'hideothers' },
      { type: 'separator' },
      { label: `${MNEMONIC_SYM}Quit`, accelerator: 'CmdOrCtrl+Q', click: () => app.quit() },
    ],
  };

  const editMenu = {
    label: `${MNEMONIC_SYM}Edit`,
    submenu: [
      { label: `${MNEMONIC_SYM}Undo`, accelerator: 'CmdOrCtrl+Z', selector: 'undo:' },
      { label: `${MNEMONIC_SYM}Redo`, accelerator: 'Shift+CmdOrCtrl+Z', selector: 'redo:' },
      { type: 'separator' },
      { label: `Cu${MNEMONIC_SYM}t`, accelerator: 'CmdOrCtrl+X', selector: 'cut:' },
      { label: `${MNEMONIC_SYM}Copy`, accelerator: 'CmdOrCtrl+C', selector: 'copy:' },
      { label: `${MNEMONIC_SYM}Paste`, accelerator: 'CmdOrCtrl+V', selector: 'paste:' },
      {
        label: `Select ${MNEMONIC_SYM}All`,
        accelerator: 'CmdOrCtrl+A',
        selector: 'selectAll:',
      },
    ],
  };

  const viewMenu = {
    label: `${MNEMONIC_SYM}View`,
    submenu: [
      { label: `Toggle ${MNEMONIC_SYM}Full Screen`, role: 'togglefullscreen' },
      {
        label: `${MNEMONIC_SYM}Actual Size`,
        accelerator: 'CmdOrCtrl+0',
        click: () => {
          const w = BrowserWindow.getFocusedWindow();
          if (!w || !w.webContents) {
            return;
          }

          const zoomFactor = 1;
          w.webContents.setZoomFactor(zoomFactor);
          saveZoomFactor(zoomFactor);
        },
      },
      {
        label: `Zoom ${MNEMONIC_SYM}In`,
        accelerator: 'CmdOrCtrl+=',
        click: () => {
          const w = BrowserWindow.getFocusedWindow();
          if (!w || !w.webContents) {
            return;
          }

          const zoomFactor = Math.min(1.8, getZoomFactor() + 0.05);
          w.webContents.setZoomFactor(zoomFactor);

          saveZoomFactor(zoomFactor);
        },
      },
      {
        label: `Zoom ${MNEMONIC_SYM}Out`,
        accelerator: 'CmdOrCtrl+-',
        click: () => {
          const w = BrowserWindow.getFocusedWindow();
          if (!w || !w.webContents) {
            return;
          }

          const zoomFactor = Math.max(0.5, getZoomFactor() - 0.05);
          w.webContents.setZoomFactor(zoomFactor);
          saveZoomFactor(zoomFactor);
        },
      },
      {
        label: 'Toggle Sidebar',
        click: () => {
          const w = BrowserWindow.getFocusedWindow();
          if (!w || !w.webContents) {
            return;
          }

          w.webContents.send('toggle-sidebar');
        },
      },
      {
        label: `Toggle ${MNEMONIC_SYM}DevTools`,
        accelerator: 'Alt+CmdOrCtrl+I',
        click: () => mainWindow.toggleDevTools(),
      },
    ],
  };

  const windowMenu = {
    label: `${MNEMONIC_SYM}Window`,
    role: 'window',
    submenu: [
      { label: `${MNEMONIC_SYM}Minimize`, role: 'minimize' },
      ...(isMac() ? [{ label: `${MNEMONIC_SYM}Close`, role: 'close' }] : []),
    ],
  };

  const helpMenu = {
    label: `${MNEMONIC_SYM}Help`,
    role: 'help',
    id: 'help',
    submenu: [
      {
        label: `${MNEMONIC_SYM}Help and Support`,
        accelerator: !isMac() ? 'F1' : null,
        click: () => {
          shell.openExternal(docsBase);
        },
      },
      {
        label: `${MNEMONIC_SYM}Keyboard Shortcuts`,
        accelerator: 'CmdOrCtrl+Shift+?',
        click: (menuItem, w, e) => {
          if (!w || !w.webContents) {
            return;
          }
          w.webContents.send('toggle-preferences-shortcuts');
        },
      },
      {
        label: `Show App ${MNEMONIC_SYM}Data Folder`,
        click: (menuItem, w, e) => {
          const directory = misc.getDataDirectory();
          shell.showItemInFolder(directory);
        },
      },
      {
        label: `Show App ${MNEMONIC_SYM}Logs Folder`,
        click: (menuItem, w, e) => {
          const directory = log.getLogDirectory();
          shell.showItemInFolder(directory);
        },
      },
      {
        label: 'Show Open Source Licenses',
        click: (menuItem, w, e) => {
          const licensePath = path.resolve(app.getAppPath(), '../opensource-licenses.txt');
          shell.openPath(licensePath);
        },
      },
      {
        label: 'Show Software License',
        click: () => {
          shell.openExternal('https://insomnia.rest/license');
        },
      },
    ],
  };

  const aboutMenuClickHandler = async () => {
    const copy = 'Copy';
    const ok = 'OK';

    const buttons = isLinux() ? [copy, ok] : [ok, copy];

    const detail = [
      `Version: ${getAppLongName()} ${getAppVersion()}`,
      `Release date: ${getAppReleaseDate()}`,
      `OS: ${os.type()} ${os.arch()} ${os.release()}`,
      `Electron: ${process.versions.electron}`,
      `Node: ${process.versions.node}`,
      `V8: ${process.versions.v8}`,
      `Architecture: ${process.arch}`,
      `node-libcurl: ${Curl.getVersion()}`,
    ].join('\n');

    const msgBox = await dialog.showMessageBox({
      type: 'info',
      title: getAppName(),
      message: getAppLongName(),
      detail,
      buttons,
      defaultId: buttons.indexOf(ok),
      cancelId: buttons.indexOf(ok),
      noLink: true,
    });

    if (msgBox.response === buttons.indexOf(copy)) {
      clipboard.writeText(detail);
    }
  };

  if (isMac()) {
    applicationMenu.submenu.unshift(
      {
        label: `A${MNEMONIC_SYM}bout ${getAppName()}`,
        click: aboutMenuClickHandler,
      },
      { type: 'separator' },
    );
  } else {
    helpMenu.submenu.push({
      label: `${MNEMONIC_SYM}About`,
      click: aboutMenuClickHandler,
    });
  }

  const developerMenu = {
    label: `${MNEMONIC_SYM}Developer`,
    position: 'before=help',
    submenu: [
      {
        label: `${MNEMONIC_SYM}Reload`,
        accelerator: 'Shift+F5',
        click: () => mainWindow.reload(),
      },
      {
        label: `Resize to Defaul${MNEMONIC_SYM}t`,
        click: () =>
          mainWindow.setBounds({
            x: 100,
            y: 100,
            width: DEFAULT_WIDTH,
            height: DEFAULT_HEIGHT,
          }),
      },
      {
        label: `Take ${MNEMONIC_SYM}Screenshot`,
        click: function() {
          mainWindow.capturePage(image => {
            const buffer = image.toPNG();
            const dir = app.getPath('desktop');
            fs.writeFileSync(path.join(dir, `Screenshot-${new Date()}.png`), buffer);
          });
        },
      },
      {
        label: `${MNEMONIC_SYM}Restart`,
        click: function() {
          const { app } = electron.remote || electron;
          app.relaunch();
          app.exit();
        },
      },
    ],
  };

  const toolsMenu = {
    label: `${MNEMONIC_SYM}Tools`,
    submenu: [
      {
        label: `${MNEMONIC_SYM}Reload Plugins`,
        click: () => {
          const w = BrowserWindow.getFocusedWindow();
          if (!w || !w.webContents) {
            return;
          }

          w.webContents.send('reload-plugins');
        },
      },
    ],
  };

  const template = [];

  template.push(applicationMenu);
  template.push(editMenu);
  template.push(viewMenu);
  template.push(windowMenu);
  template.push(toolsMenu);
  template.push(helpMenu);

  if (isDevelopment() || process.env.INSOMNIA_FORCE_DEBUG) {
    template.push(developerMenu);
  }

  Menu.setApplicationMenu(Menu.buildFromTemplate(template));

  return mainWindow;
}

async function showUnresponsiveModal() {
  const id = await dialog.showMessageBox({
    type: 'info',
    buttons: ['Cancel', 'Reload'],
    defaultId: 1,
    cancelId: 0,
    title: 'Unresponsive',
    message: 'Insomnia has become unresponsive. Do you want to reload?',
  });

  if (id === 1) {
    mainWindow.destroy();
    createWindow();
  }
}

function saveBounds() {
  if (!mainWindow) {
    return;
  }

  const fullscreen = mainWindow.isFullScreen();

  // Only save the size if we're not in fullscreen
  if (!fullscreen) {
    localStorage.setItem('bounds', mainWindow.getBounds());
    localStorage.setItem('maximize', mainWindow.isMaximized());
    localStorage.setItem('fullscreen', false);
  } else {
    localStorage.setItem('fullscreen', true);
  }
}

function getBounds() {
  let bounds = {};
  let fullscreen = false;
  let maximize = false;
  try {
    bounds = localStorage.getItem('bounds', {});
    fullscreen = localStorage.getItem('fullscreen', false);
    maximize = localStorage.getItem('maximize', false);
  } catch (e) {
    // This should never happen, but if it does...!
    console.error('Failed to parse window bounds', e);
  }

  return { bounds, fullscreen, maximize };
}

function saveZoomFactor(zoomFactor) {
  localStorage.setItem('zoomFactor', zoomFactor);
}

function getZoomFactor() {
  let zoomFactor = 1;
  try {
    zoomFactor = localStorage.getItem('zoomFactor', 1);
  } catch (e) {
    // This should never happen, but if it does...!
    console.error('Failed to parse zoomFactor', e);
  }

  return zoomFactor;
}

function initLocalStorage() {
  const localStoragePath = path.join(misc.getDataDirectory(), 'localStorage');
  localStorage = new LocalStorage(localStoragePath);
}

function initContextMenus() {
  require('electron-context-menu')({});
}
<|endoftext|>


<commit_before>import React, {Component} from "react";
import gql from "graphql-tag.macro";
import {graphql} from "react-apollo";
import NavigationScanner from "components/views/Navigation/NavigationScanner";
import {Container, Row, Col, Card, CardBody} from "helpers/reactstrap";
import SubscriptionHelper from "helpers/subscriptionHelper";
import "./style.scss";

const NAV_SUB = gql`
  subscription NavigationUpdate($simulatorId: ID) {
    navigationUpdate(simulatorId: $simulatorId) {
      id
      scanning
      calculatedCourse {
        x
        y
        z
      }
      destination
    }
  }
`;

class CourseCalculationViewscreen extends Component {
  state = {};
  componentDidUpdate(prevProps) {
    const data = JSON.parse(this.props.viewscreen.data);
    const scanning = data.reactive
      ? this.props.data.navigation && this.props.data.navigation[0].scanning
      : data.scanning;
    if (scanning !== this.state.scanning) {
      this.setState(
        {
          scanning,
        },
        () => {
          cancelAnimationFrame(this.looping);
          this.loop();
        },
      );
    }
  }
  componentWillUnmount() {
    cancelAnimationFrame(this.looping);
  }
  loop = () => {
    if (this.state.scanning) {
      this.setState({
        x: Math.random(),
        y: Math.random(),
        z: Math.random(),
      });
      this.looping = requestAnimationFrame(this.loop);
    }
  };
  render() {
    const data = JSON.parse(this.props.viewscreen.data);
    if (this.props.data.loading || !this.props.data.navigation) return null;
    const {destination, calculatedCourse = {x: 0, y: 0, z: 0}} = data.reactive
      ? this.props.data.navigation[0]
      : data;
    const {scanning, x, y, z} = this.state;
    return (

        <SubscriptionHelper
          subscribe={() =>
            this.props.data.subscribeToMore({
              document: NAV_SUB,
              variables: {simulatorId: this.props.simulator.id},
              updateQuery: (previousResult, {subscriptionData}) => {
                return Object.assign({}, previousResult, {
                  navigation: subscriptionData.data.navigationUpdate,
                });
              },
            })
          }
        />
        <Container>

            Course Calculation
            {destination ? `: ${destination}` : ""}

          <Row>
            <Col sm={6}>
              <NavigationScanner scanning={scanning} />
            </Col>
            <Col sm={6}>
              <Card>
                {!data.reactive && data.thrusters ? (
                  <CardBody>

                      Yaw:
                      {"  "}

                        {scanning ? Math.floor(x * 360) : calculatedCourse.x}˚

                      Pitch:{" "}

                        {scanning ? Math.floor(y * 360) : calculatedCourse.y}˚

                      Roll:
                      {"  "}

                        {scanning ? Math.floor(z * 360) : calculatedCourse.z}˚

                  </CardBody>
                ) : (
                  <CardBody>

                      X:{" "}

                        {scanning
                          ? Math.floor(x * 99999) / 100
                          : calculatedCourse.x}

                      Y:{" "}

                        {scanning
                          ? Math.floor(y * 99999) / 100
                          : calculatedCourse.y}

                      Z:{" "}

                        {scanning
                          ? Math.floor(z * 99999) / 100
                          : calculatedCourse.z}

                  </CardBody>
                )}
              </Card>
            </Col>
          </Row>
        </Container>

    );
  }
}

const INTERNAL_QUERY = gql`
  query Navigation($simulatorId: ID) {
    navigation(simulatorId: $simulatorId) {
      id
      scanning
      calculatedCourse {
        x
        y
        z
      }
      destination
    }
  }
`;

export default graphql(INTERNAL_QUERY, {
  options: ownProps => ({
    fetchPolicy: "cache-and-network",
    variables: {
      simulatorId: ownProps.simulator.id,
    },
  }),
})(CourseCalculationViewscreen);
<commit_msg>fix(Viewscreen): Adds the course calculation image back to the course calculation viewscreen. Closes #2706<commit_after>import React, {Component} from "react";
import gql from "graphql-tag.macro";
import {graphql} from "react-apollo";
import NavigationScanner from "components/views/Navigation/NavigationScanner";
import {Container, Row, Col, Card, CardBody} from "helpers/reactstrap";
import SubscriptionHelper from "helpers/subscriptionHelper";
import "./style.scss";
import "../../views/Navigation/style.scss";

const NAV_SUB = gql`
  subscription NavigationUpdate($simulatorId: ID) {
    navigationUpdate(simulatorId: $simulatorId) {
      id
      scanning
      calculatedCourse {
        x
        y
        z
      }
      destination
    }
  }
`;

class CourseCalculationViewscreen extends Component {
  state = {};
  componentDidUpdate(prevProps) {
    const data = JSON.parse(this.props.viewscreen.data);
    const scanning = data.reactive
      ? this.props.data.navigation && this.props.data.navigation[0].scanning
      : data.scanning;
    if (scanning !== this.state.scanning) {
      this.setState(
        {
          scanning,
        },
        () => {
          cancelAnimationFrame(this.looping);
          this.loop();
        },
      );
    }
  }
  componentWillUnmount() {
    cancelAnimationFrame(this.looping);
  }
  loop = () => {
    if (this.state.scanning) {
      this.setState({
        x: Math.random(),
        y: Math.random(),
        z: Math.random(),
      });
      this.looping = requestAnimationFrame(this.loop);
    }
  };
  render() {
    const data = JSON.parse(this.props.viewscreen.data);
    if (this.props.data.loading || !this.props.data.navigation) return null;
    const {destination, calculatedCourse = {x: 0, y: 0, z: 0}} = data.reactive
      ? this.props.data.navigation[0]
      : data;
    const {scanning, x, y, z} = this.state;
    return (

        <SubscriptionHelper
          subscribe={() =>
            this.props.data.subscribeToMore({
              document: NAV_SUB,
              variables: {simulatorId: this.props.simulator.id},
              updateQuery: (previousResult, {subscriptionData}) => {
                return Object.assign({}, previousResult, {
                  navigation: subscriptionData.data.navigationUpdate,
                });
              },
            })
          }
        />
        <Container>

            Course Calculation
            {destination ? `: ${destination}` : ""}

          <Row>
            <Col sm={6}>
              <NavigationScanner scanning={scanning} />
            </Col>
            <Col sm={6}>
              <Card>
                {!data.reactive && data.thrusters ? (
                  <CardBody>

                      Yaw:
                      {"  "}

                        {scanning ? Math.floor(x * 360) : calculatedCourse.x}˚

                      Pitch:{" "}

                        {scanning ? Math.floor(y * 360) : calculatedCourse.y}˚

                      Roll:
                      {"  "}

                        {scanning ? Math.floor(z * 360) : calculatedCourse.z}˚

                  </CardBody>
                ) : (
                  <CardBody>

                      X:{" "}

                        {scanning
                          ? Math.floor(x * 99999) / 100
                          : calculatedCourse.x}

                      Y:{" "}

                        {scanning
                          ? Math.floor(y * 99999) / 100
                          : calculatedCourse.y}

                      Z:{" "}

                        {scanning
                          ? Math.floor(z * 99999) / 100
                          : calculatedCourse.z}

                  </CardBody>
                )}
              </Card>
            </Col>
          </Row>
        </Container>

    );
  }
}

const INTERNAL_QUERY = gql`
  query Navigation($simulatorId: ID) {
    navigation(simulatorId: $simulatorId) {
      id
      scanning
      calculatedCourse {
        x
        y
        z
      }
      destination
    }
  }
`;

export default graphql(INTERNAL_QUERY, {
  options: ownProps => ({
    fetchPolicy: "cache-and-network",
    variables: {
      simulatorId: ownProps.simulator.id,
    },
  }),
})(CourseCalculationViewscreen);
<|endoftext|>


<commit_before>var _ = require("lodash");

var wrapInOr = function(states){
  if(_.size(states) === 1){
    return _.head(states);
  }
  return ["or", _.head(states), wrapInOr(_.tail(states))];
};

var toLispArgs = function(ast, traverse){
  return _.map(ast.args, traverse);
};

var event_ops = {
  "or": {
    toLispArgs: toLispArgs,
    mkStateMachine: function(start, end, args, newState, evalEELisp){
      var stm = {};

      var stmPush = function(state, transition){
        if(!_.has(stm, state)){
          stm[state] = [];
        }
        stm[state].push(transition);
      };

      var a = evalEELisp(args[0], start, end);
      var b = evalEELisp(args[1], start, end);

      _.each(_.uniq(_.keys(a).concat(_.keys(b))), function(state){
        var iter = _.partial(stmPush, state);
        if(_.has(a, state)){
          _.each(a[state], iter);
        }else{
          _.each(a["start"], iter);
        }
        if(_.has(b, state)){
          _.each(b[state], iter);
        }else{
          _.each(b["start"], iter);
        }
      });
      return stm;
    }
  },
  "and": {
    toLispArgs: toLispArgs,
    mkStateMachine: function(start, end, args, newState, evalEELisp){
      var a = evalEELisp(args[0], "aaa-START", "aaa-END");
      var b = evalEELisp(args[1], "bbb-START", "bbb-END");

      var s1 = newState();
      var s2 = newState();

      var stm = {};
      stm[start] = [];
      stm[s1] = [];
      stm[s2] = [];

      _.each(a["aaa-START"], function(transition){
        var condition = transition[0];
        var next_state = transition[1];
        if(next_state === "aaa-END"){
          stm[start].push([condition, s1]);
          stm[s2].push([condition, end]);
        }else if(next_state === "aaa-START"){
          stm[s2].push([condition, s2]);
        }
      });

      _.each(b["bbb-START"], function(transition){
        var condition = transition[0];
        var next_state = transition[1];
        if(next_state === "bbb-END"){
          stm[start].push([condition, s2]);
          stm[s1].push([condition, end]);
        }else if(next_state === "bbb-START"){
          stm[s1].push([condition, s1]);
        }
      });

      return stm;
    }
  }
};

module.exports = function(ast, comp, e){
  if(ast.kind !== "when"){
    throw new Error("RuleSelect.kind not supported: " + ast.kind);
  }
  var ee_id = 0;
  var graph = {};
  var eventexprs = {};

  var onEE = function(ast){
    var domain = ast.event_domain.value;
    var type = ast.event_type.value;
    var id = "expr_" + (ee_id++);

    _.set(graph, [domain, type, id], true);

    eventexprs[id] = comp(ast);
    return id;
  };

  var traverse = function(ast){
    if(ast.type === "EventExpression"){
      return onEE(ast);
    }else if(ast.type === "EventOperator"){
      if(_.has(event_ops, ast.op)){
        return [ast.op].concat(event_ops[ast.op].toLispArgs(ast, traverse));
      }
      throw new Error("EventOperator.op not supported: " + ast.op);
    }
    throw new Error("invalid event ast node: " + ast.type);
  };

  var newState = (function(){
    var i = 0;
    return function(){
      var id = "state_" + i;
      i++;
      return id;
    };
  }());

  var evalEELisp = function(lisp, start, end){
    if(_.isString(lisp)){
      var stm = {};
      stm[start] = [
        [lisp, end]
      ];
      return stm;
    }
    if(_.has(event_ops, lisp[0])){
      return event_ops[lisp[0]].mkStateMachine(start, end, lisp.slice(1), newState, evalEELisp);
    }else{
      throw new Error("EventOperator.op not supported: " + ast.op);
    }
  };

  var lisp = traverse(ast.event);
  var state_machine = evalEELisp(lisp, "start", "end");

  //add all the loop-back conditions
  _.each(state_machine, function(arr, key){
    var away_paths = _.uniq(_.compact(_.map(arr, function(transition){
      var condition = transition[0];
      var next_state = transition[1];
      if(!_.isString(condition) && (next_state === key)){
        return;//ignore this
      }
      return condition;
    })));

    state_machine[key].push([
        [
          "not",
          wrapInOr(away_paths)
        ],
        key
    ]);
  });

  return e("obj", {
    graph: e("json", graph),
    eventexprs: e("obj", eventexprs),
    state_machine: e("json", state_machine)
  });
};
<commit_msg>cleanup<commit_after>var _ = require("lodash");

var wrapInOr = function(states){
  if(_.size(states) === 1){
    return _.head(states);
  }
  return ["or", _.head(states), wrapInOr(_.tail(states))];
};

var toLispArgs = function(ast, traverse){
  return _.map(ast.args, traverse);
};

var event_ops = {
  "or": {
    toLispArgs: toLispArgs,
    mkStateMachine: function(start, end, args, newState, evalEELisp){
      var stm = {};

      var stmPush = function(state, transition){
        if(!_.has(stm, state)){
          stm[state] = [];
        }
        stm[state].push(transition);
      };

      var a = evalEELisp(args[0], start, end);
      var b = evalEELisp(args[1], start, end);

      _.each(_.uniq(_.keys(a).concat(_.keys(b))), function(state){
        var iter = _.partial(stmPush, state);
        if(_.has(a, state)){
          _.each(a[state], iter);
        }else{
          _.each(a["start"], iter);
        }
        if(_.has(b, state)){
          _.each(b[state], iter);
        }else{
          _.each(b["start"], iter);
        }
      });
      return stm;
    }
  },
  "and": {
    toLispArgs: toLispArgs,
    mkStateMachine: function(start, end, args, newState, evalEELisp){
      var a = evalEELisp(args[0], start, end);
      var b = evalEELisp(args[1], start, end);

      var s1 = newState();
      var s2 = newState();

      var stm = {};
      var stmPush = function(state, transition){
        if(!_.has(stm, state)){
          stm[state] = [];
        }
        stm[state].push(transition);
      };

      stm[start] = [];
      stm[s1] = [];
      stm[s2] = [];

      _.each(a[start], function(transition){
        var condition = transition[0];
        var next_state = transition[1];
        if(next_state === end){
          stmPush(start, [condition, s1]);
          stmPush(s2, [condition, end]);
        }else if(next_state === start){
          stmPush(s2, [condition, s2]);
        }
      });

      _.each(b[start], function(transition){
        var condition = transition[0];
        var next_state = transition[1];
        if(next_state === end){
          stmPush(start, [condition, s2]);
          stmPush(s1, [condition, end]);
        }else if(next_state === start){
          stmPush(s1, [condition, s1]);
        }
      });

      return stm;
    }
  }
};

module.exports = function(ast, comp, e){
  if(ast.kind !== "when"){
    throw new Error("RuleSelect.kind not supported: " + ast.kind);
  }
  var ee_id = 0;
  var graph = {};
  var eventexprs = {};

  var onEE = function(ast){
    var domain = ast.event_domain.value;
    var type = ast.event_type.value;
    var id = "expr_" + (ee_id++);

    _.set(graph, [domain, type, id], true);

    eventexprs[id] = comp(ast);
    return id;
  };

  var traverse = function(ast){
    if(ast.type === "EventExpression"){
      return onEE(ast);
    }else if(ast.type === "EventOperator"){
      if(_.has(event_ops, ast.op)){
        return [ast.op].concat(event_ops[ast.op].toLispArgs(ast, traverse));
      }
      throw new Error("EventOperator.op not supported: " + ast.op);
    }
    throw new Error("invalid event ast node: " + ast.type);
  };

  var newState = (function(){
    var i = 0;
    return function(){
      var id = "state_" + i;
      i++;
      return id;
    };
  }());

  var evalEELisp = function(lisp, start, end){
    if(_.isString(lisp)){
      var stm = {};
      stm[start] = [
        [lisp, end]
      ];
      return stm;
    }
    if(_.has(event_ops, lisp[0])){
      return event_ops[lisp[0]].mkStateMachine(start, end, lisp.slice(1), newState, evalEELisp);
    }else{
      throw new Error("EventOperator.op not supported: " + ast.op);
    }
  };

  var lisp = traverse(ast.event);
  var state_machine = evalEELisp(lisp, "start", "end");

  //add all the loop-back conditions
  _.each(state_machine, function(arr, key){
    var away_paths = _.uniq(_.compact(_.map(arr, function(transition){
      var condition = transition[0];
      var next_state = transition[1];
      if(!_.isString(condition) && (next_state === key)){
        return;//ignore this
      }
      return condition;
    })));

    state_machine[key].push([
        [
          "not",
          wrapInOr(away_paths)
        ],
        key
    ]);
  });

  return e("obj", {
    graph: e("json", graph),
    eventexprs: e("obj", eventexprs),
    state_machine: e("json", state_machine)
  });
};
<|endoftext|>


<commit_before>//
// required packages
//
var fs = require('fs');

processJSONFile('./avalanche-org/api_output.json', './avalanche-org/api_output_processed.json', 3);

function processJSONFile(inputFilePath, outputFilePath, digits) {
    var input = fs.readFileSync(inputFilePath, 'utf8');
    var output = processRegions(input, digits);
    fs.writeFileSync(outputFilePath, output, 'utf8');
}

function processRegions(input, digits) {
    var data = JSON.parse(input);
    var regions = data.features;

    for (var i = 0; i < regions.length; i++) {

        regions[i].points = [];

        for (var j = 0; j < regions[i].geometry.coordinates[0].length; j++) {

            // do two things:
            // 1) trim digits in the geometry
            // 2) convert from KML-style to lat/lon style

            // NOTE latitude comes after longitude in KML ... weird
            var lon = regions[i].geometry.coordinates[0][j][0] = parseFloat(regions[i].geometry.coordinates[0][j][0]).toFixed(digits);
            var lat = regions[i].geometry.coordinates[0][j][1] = parseFloat(regions[i].geometry.coordinates[0][j][1]).toFixed(digits);

            regions[i].points[j] = {'lat': lat, 'lon': lon};
        }
    }

    return (JSON.stringify(data, null, 4));
}
<commit_msg>allow file specification<commit_after>//
// required packages
//
var fs = require('fs');

var inputFile = process.argv[2] || './avalanche-org/api_output.json';
var outputFile = process.argv[3] || './avalanche-org/api_output_processed.json';

processJSONFile(inputFile, outputFile, 3);

function processJSONFile(inputFilePath, outputFilePath, digits) {
    var input = fs.readFileSync(inputFilePath, 'utf8');
    var output = processRegions(input, digits);
    fs.writeFileSync(outputFilePath, output, 'utf8');
}

function processRegions(input, digits) {
    var data = JSON.parse(input);
    var regions = data.features;

    for (var i = 0; i < regions.length; i++) {

        regions[i].points = [];

        for (var j = 0; j < regions[i].geometry.coordinates[0].length; j++) {

            // do two things:
            // 1) trim digits in the geometry
            // 2) convert from KML-style to lat/lon style

            // NOTE latitude comes after longitude in KML ... weird
            var lon = regions[i].geometry.coordinates[0][j][0] = parseFloat(regions[i].geometry.coordinates[0][j][0]).toFixed(digits);
            var lat = regions[i].geometry.coordinates[0][j][1] = parseFloat(regions[i].geometry.coordinates[0][j][1]).toFixed(digits);

            regions[i].points[j] = {'lat': lat, 'lon': lon};
        }
    }

    return (JSON.stringify(data, null, 4));
}
<|endoftext|>


<commit_before>import { RangeFilter, RangeAccessor } from "searchkit";

export class EnabledSelectionRangeAccessor extends RangeAccessor {
  isDisabled() {
    return !this.state.hasValue() && super.isDisabled();
  }
}

export default class EnabledSelectionRangeFilter extends RangeFilter {
  defineAccessor() {
    const {
      id, title, min, max, field, fieldOptions,
      interval, showHistogram
    } = this.props;
    return new EnabledSelectionRangeAccessor(id, {
      id, min, max, title, field,
      interval, loadHistogram: showHistogram, fieldOptions
    });
  }
}
<commit_msg>Added missed flow flag<commit_after>// @flow
import { RangeFilter, RangeAccessor } from "searchkit";

export class EnabledSelectionRangeAccessor extends RangeAccessor {
  isDisabled() {
    return !this.state.hasValue() && super.isDisabled();
  }
}

export default class EnabledSelectionRangeFilter extends RangeFilter {
  defineAccessor() {
    const {
      id, title, min, max, field, fieldOptions,
      interval, showHistogram
    } = this.props;
    return new EnabledSelectionRangeAccessor(id, {
      id, min, max, title, field,
      interval, loadHistogram: showHistogram, fieldOptions
    });
  }
}
<|endoftext|>


<commit_before>/**
 * Copyright 2016 The AMP HTML Authors. 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.
 */
'use strict';

var $$ = require('gulp-load-plugins')();
var BBPromise = require('bluebird');
var babel = require('babelify');
var browserify = require('browserify');
var buffer = require('vinyl-buffer');
var depCheckConfig = require('../dep-check-config');
var fs = BBPromise.promisifyAll(require('fs-extra'));
var gulp = require('gulp-help')(require('gulp'));
var minimatch = require('minimatch');
var minimist = require('minimist');
var path = require('path');
var source = require('vinyl-source-stream');
var through = require('through2');
var util = require('gulp-util');

var root = process.cwd();
var absPathRegExp = new RegExp(`^${root}/`);
var argv = minimist(process.argv.slice(2), {boolean: ['strictBabelTransform']});
var red = (msg) => util.log(util.colors.red(msg));

/**
 * @typedef {{
 *   name: string,
 *   deps: ?Array<!Object<string, !ModuleDef>
 * }}
 */
var ModuleDef;

/**
 * @typedef {string}
 */
var GlobDef;

/**
 * @typedef {!Array<!GlobDef>}
 */
var GlobsDef;

/**
 * @constructor @final @struct
 */
function Rule(config) {

  /** @private @const {!RuleConfigDef} */
  this.config_ = config;

  /** @private @const {string} */
  this.type_ = config.type || 'forbidden';

  /**
   * Default to all files if none given.
   * @private @const {!GlobsDef}
   */
  this.filesMatching_ = toArrayOrDefault(config.filesMatching, ['**/*.js']);

  /** @private @const {!GlobsDef} */
  this.mustNotDependOn_ = toArrayOrDefault(config.mustNotDependOn, []);

  /** @private @const {!Array<string>} */
  this.whitelist_ = toArrayOrDefault(config.whitelist, []);
}

/**
 * @param {string} moduleName
 * @param {!Array<string>} deps
 * @return {!Array<string>}
 */
Rule.prototype.run = function(moduleName, deps) {
  var errors = [];

  // If forbidden rule and current module has no dependencies at all
  // then no need to match.
  if (this.type_ == 'forbidden' && !deps.length) {
    return errors;
  }

  return errors.concat(this.matchBadDeps(moduleName, deps));
};

/**
 * @param {string} moduleName
 * @param {!Array<string>} deps
 * @return {!Array<string>}
 */
Rule.prototype.matchBadDeps = function(moduleName, deps) {
  if (this.type_ != 'forbidden') {
    return [];
  }

  var isFilenameMatch = this.filesMatching_
      .some(x => minimatch(moduleName, x));
  if (!isFilenameMatch) {
    return [];
  }

  var mustNotDependErrors = [];
  // These nested loops are ok as we usually only have a few rules
  // to run against.
  deps.forEach(dep => {
    this.mustNotDependOn_.forEach(badDepPattern => {
      if (minimatch(dep, badDepPattern)) {
        var inWhitelist = this.whitelist_.some(entry => {
          var pair = entry.split('->');
          var whitelistedModuleName = pair[0];
          var whitelistedDep = pair[1];
          if (!minimatch(moduleName, whitelistedModuleName)) {
            return false;
          }
          return dep == whitelistedDep;
        });
        if (inWhitelist) {
          return;
        }
        mustNotDependErrors.push(`${moduleName} must not depend on ${dep}. ` +
            `Rule: ${JSON.stringify(this.config_)}.`);
      }
    });
  });
  return mustNotDependErrors;
};

var rules = depCheckConfig.rules.map(config => new Rule(config));

/**
 * Returns a list of entryPoint modules.
 * extenstions/0.1/*.js/amp.js/integration.js
 *
 * @return {!Promise<!Array<string>>}
 */
function getSrcs() {
  return fs.readdirAsync('extensions').then(dirItems => {
    // Look for extension entry points
    return dirItems
      .map(x => `extensions/${x}`)
      .filter(x => fs.statSync(x).isDirectory())
      // If we ever need more than 0.1, just return an array and flatten
      // the Array.
      .map(getEntryModule)
      // Concat the core binary and integration binary as entry points.
      .concat(`src/amp.js`, `3p/integration.js`);
  });
}

/**
 * @param {string} entryModule
 * @return {!Promise<!ModuleDef>}
 */
function getGraph(entryModule) {
  var resolve;
  var promise = new BBPromise(r => {
    resolve = r;
  });
  var module = Object.create(null);
  module.name = entryModule;
  module.deps = [];

  // TODO(erwinm): Try and work this in with `gulp build` so that
  // we're not running browserify twice on travis.
  var bundler = browserify(entryModule, {debug: true, deps: true})
      .transform(babel, {
        loose: argv.strictBabelTransform ? undefined : 'all'
      });

  bundler.pipeline.get('deps').push(through.obj(function(row, enc, next) {
    module.deps.push({
      name: row.file.replace(absPathRegExp, ''),
      deps: row.deps,
    });
    this.push(row);
    next();
  }));
  bundler.bundle()
      .pipe(source(entryModule))
      // Unfortunately we need to write the files out.
      .pipe(gulp.dest('./.amp-build'))
      .on('end', resolve.bind(null, module));
  return promise;
}

/**
 * @param {string} extensionFolder
 * @return {!Array<!ModuleDef>}
 */
function getEntryModule(extensionFolder) {
  // TODO (@zhouyx, #9642): Remove the special check and handle more than just 0.1
  if (extensionFolder == 'extensions/amp-sticky-ad') {
    return `${extensionFolder}/1.0/${path.basename(extensionFolder)}.js`;
  }
  return `${extensionFolder}/0.1/${path.basename(extensionFolder)}.js`;
}

/**
 * Flattens the graph to easily run through the Rules. Original graph
 * would be nested ModuleDef's wherein the top level are entry points
 * with nested dependencies. We flatten it so all we have are individual
 * modules and their imports as well as making the entries unique.
 *
 * @param {!ModuleDef} entryModule
 * @return {!ModuleDef}
 */
function flattenGraph(entryPoints) {
  // Flatten the graph by just getting all the deps from all
  // the entry points.
  entryPoints = entryPoints.map(entryPoint => entryPoint.deps);
  // Now make the graph have unique entries
  return flatten(entryPoints)
      .reduce((acc, cur, i, arr) => {
        var name = cur.name;
        if (!acc[name]) {
          acc[name] = Object.keys(cur.deps)
              // Get rid of the absolute path for minimatch'ing
              .map(x => cur.deps[x].replace(absPathRegExp, ''));
        }
        return acc;
      }, Object.create(null));
}

/**
 * Run Module dependency graph against the rules.
 *
 * @param {!Array<!ModuleDef>} modules
 */
function runRules(modules) {
  var errorsFound = false;
  Object.keys(modules).forEach(moduleName => {
    var deps = modules[moduleName];
    // Run Rules against the modules and flatten for reporting.
    var errors = flatten(rules.map(rule => rule.run(moduleName, deps)));

    if (errors.length) {
      errorsFound = true;
      // Report errors.
      errors.forEach(red);
    }
  });
  return errorsFound;
}

function depCheck() {
  var errorsFound = false;
  return getSrcs().then(entryPoints => {
    // This check is for extension folders that actually dont have
    // an extension entry point module yet.
    entryPoints = entryPoints.filter(x => fs.existsSync(x));
    return BBPromise.all(entryPoints.map(getGraph));
  })
  .then(flattenGraph)
  .then(runRules)
  .then(errorsFound => {
    if (errorsFound) {
      process.exit(1);
    }
  });
}

/**
 * Put value in Array context.
 *
 * @param {!GlobsDef|string} value
 * @param {!Array<string>} defaultValue
 * @return {!GlobsDef}
 */
function toArrayOrDefault(value, defaultValue) {
  if (Array.isArray(value)) {
    return value;
  }
  if (typeof value == 'string') {
    return [value];
  }
  return defaultValue;
}

/**
 * Flatten array of arrays.
 *
 * @type {!Array<!Array>}
 */
function flatten(arr) {
  return [].concat.apply([], arr);
}

gulp.task('dep-check', 'Runs a dependency check on each module', depCheck);
<commit_msg>Make sure css is compiled before running gulp dep-check (#9754)<commit_after>/**
 * Copyright 2016 The AMP HTML Authors. 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.
 */
'use strict';

var $$ = require('gulp-load-plugins')();
var BBPromise = require('bluebird');
var babel = require('babelify');
var browserify = require('browserify');
var buffer = require('vinyl-buffer');
var depCheckConfig = require('../dep-check-config');
var fs = BBPromise.promisifyAll(require('fs-extra'));
var gulp = require('gulp-help')(require('gulp'));
var minimatch = require('minimatch');
var minimist = require('minimist');
var path = require('path');
var source = require('vinyl-source-stream');
var through = require('through2');
var util = require('gulp-util');

var root = process.cwd();
var absPathRegExp = new RegExp(`^${root}/`);
var argv = minimist(process.argv.slice(2), {boolean: ['strictBabelTransform']});
var red = (msg) => util.log(util.colors.red(msg));

/**
 * @typedef {{
 *   name: string,
 *   deps: ?Array<!Object<string, !ModuleDef>
 * }}
 */
var ModuleDef;

/**
 * @typedef {string}
 */
var GlobDef;

/**
 * @typedef {!Array<!GlobDef>}
 */
var GlobsDef;

/**
 * @constructor @final @struct
 */
function Rule(config) {

  /** @private @const {!RuleConfigDef} */
  this.config_ = config;

  /** @private @const {string} */
  this.type_ = config.type || 'forbidden';

  /**
   * Default to all files if none given.
   * @private @const {!GlobsDef}
   */
  this.filesMatching_ = toArrayOrDefault(config.filesMatching, ['**/*.js']);

  /** @private @const {!GlobsDef} */
  this.mustNotDependOn_ = toArrayOrDefault(config.mustNotDependOn, []);

  /** @private @const {!Array<string>} */
  this.whitelist_ = toArrayOrDefault(config.whitelist, []);
}

/**
 * @param {string} moduleName
 * @param {!Array<string>} deps
 * @return {!Array<string>}
 */
Rule.prototype.run = function(moduleName, deps) {
  var errors = [];

  // If forbidden rule and current module has no dependencies at all
  // then no need to match.
  if (this.type_ == 'forbidden' && !deps.length) {
    return errors;
  }

  return errors.concat(this.matchBadDeps(moduleName, deps));
};

/**
 * @param {string} moduleName
 * @param {!Array<string>} deps
 * @return {!Array<string>}
 */
Rule.prototype.matchBadDeps = function(moduleName, deps) {
  if (this.type_ != 'forbidden') {
    return [];
  }

  var isFilenameMatch = this.filesMatching_
      .some(x => minimatch(moduleName, x));
  if (!isFilenameMatch) {
    return [];
  }

  var mustNotDependErrors = [];
  // These nested loops are ok as we usually only have a few rules
  // to run against.
  deps.forEach(dep => {
    this.mustNotDependOn_.forEach(badDepPattern => {
      if (minimatch(dep, badDepPattern)) {
        var inWhitelist = this.whitelist_.some(entry => {
          var pair = entry.split('->');
          var whitelistedModuleName = pair[0];
          var whitelistedDep = pair[1];
          if (!minimatch(moduleName, whitelistedModuleName)) {
            return false;
          }
          return dep == whitelistedDep;
        });
        if (inWhitelist) {
          return;
        }
        mustNotDependErrors.push(`${moduleName} must not depend on ${dep}. ` +
            `Rule: ${JSON.stringify(this.config_)}.`);
      }
    });
  });
  return mustNotDependErrors;
};

var rules = depCheckConfig.rules.map(config => new Rule(config));

/**
 * Returns a list of entryPoint modules.
 * extenstions/0.1/*.js/amp.js/integration.js
 *
 * @return {!Promise<!Array<string>>}
 */
function getSrcs() {
  return fs.readdirAsync('extensions').then(dirItems => {
    // Look for extension entry points
    return dirItems
      .map(x => `extensions/${x}`)
      .filter(x => fs.statSync(x).isDirectory())
      // If we ever need more than 0.1, just return an array and flatten
      // the Array.
      .map(getEntryModule)
      // Concat the core binary and integration binary as entry points.
      .concat(`src/amp.js`, `3p/integration.js`);
  });
}

/**
 * @param {string} entryModule
 * @return {!Promise<!ModuleDef>}
 */
function getGraph(entryModule) {
  var resolve;
  var promise = new BBPromise(r => {
    resolve = r;
  });
  var module = Object.create(null);
  module.name = entryModule;
  module.deps = [];

  // TODO(erwinm): Try and work this in with `gulp build` so that
  // we're not running browserify twice on travis.
  var bundler = browserify(entryModule, {debug: true, deps: true})
      .transform(babel, {
        loose: argv.strictBabelTransform ? undefined : 'all'
      });

  bundler.pipeline.get('deps').push(through.obj(function(row, enc, next) {
    module.deps.push({
      name: row.file.replace(absPathRegExp, ''),
      deps: row.deps,
    });
    this.push(row);
    next();
  }));
  bundler.bundle()
      .pipe(source(entryModule))
      // Unfortunately we need to write the files out.
      .pipe(gulp.dest('./.amp-build'))
      .on('end', resolve.bind(null, module));
  return promise;
}

/**
 * @param {string} extensionFolder
 * @return {!Array<!ModuleDef>}
 */
function getEntryModule(extensionFolder) {
  // TODO (@zhouyx, #9642): Remove the special check and handle more than just 0.1
  if (extensionFolder == 'extensions/amp-sticky-ad') {
    return `${extensionFolder}/1.0/${path.basename(extensionFolder)}.js`;
  }
  return `${extensionFolder}/0.1/${path.basename(extensionFolder)}.js`;
}

/**
 * Flattens the graph to easily run through the Rules. Original graph
 * would be nested ModuleDef's wherein the top level are entry points
 * with nested dependencies. We flatten it so all we have are individual
 * modules and their imports as well as making the entries unique.
 *
 * @param {!ModuleDef} entryModule
 * @return {!ModuleDef}
 */
function flattenGraph(entryPoints) {
  // Flatten the graph by just getting all the deps from all
  // the entry points.
  entryPoints = entryPoints.map(entryPoint => entryPoint.deps);
  // Now make the graph have unique entries
  return flatten(entryPoints)
      .reduce((acc, cur, i, arr) => {
        var name = cur.name;
        if (!acc[name]) {
          acc[name] = Object.keys(cur.deps)
              // Get rid of the absolute path for minimatch'ing
              .map(x => cur.deps[x].replace(absPathRegExp, ''));
        }
        return acc;
      }, Object.create(null));
}

/**
 * Run Module dependency graph against the rules.
 *
 * @param {!Array<!ModuleDef>} modules
 */
function runRules(modules) {
  var errorsFound = false;
  Object.keys(modules).forEach(moduleName => {
    var deps = modules[moduleName];
    // Run Rules against the modules and flatten for reporting.
    var errors = flatten(rules.map(rule => rule.run(moduleName, deps)));

    if (errors.length) {
      errorsFound = true;
      // Report errors.
      errors.forEach(red);
    }
  });
  return errorsFound;
}

function depCheck() {
  var errorsFound = false;
  return getSrcs().then(entryPoints => {
    // This check is for extension folders that actually dont have
    // an extension entry point module yet.
    entryPoints = entryPoints.filter(x => fs.existsSync(x));
    return BBPromise.all(entryPoints.map(getGraph));
  })
  .then(flattenGraph)
  .then(runRules)
  .then(errorsFound => {
    if (errorsFound) {
      process.exit(1);
    }
  });
}

/**
 * Put value in Array context.
 *
 * @param {!GlobsDef|string} value
 * @param {!Array<string>} defaultValue
 * @return {!GlobsDef}
 */
function toArrayOrDefault(value, defaultValue) {
  if (Array.isArray(value)) {
    return value;
  }
  if (typeof value == 'string') {
    return [value];
  }
  return defaultValue;
}

/**
 * Flatten array of arrays.
 *
 * @type {!Array<!Array>}
 */
function flatten(arr) {
  return [].concat.apply([], arr);
}

gulp.task(
    'dep-check',
    'Runs a dependency check on each module',
    ['css'],  // Defined in gulpfile.js, and must be run before dep-check.
    depCheck);
<|endoftext|>