<commit_before>'use strict'

import http from 'http'
import https from 'https'
import s3 from 's3'
import fs from 'fs'
import path from 'path'
import ProgressBar from 'progress'
import cdnizer from 'cdnizer'
import _ from 'lodash'

http.globalAgent.maxSockets = https.globalAgent.maxSockets = 50

const DEFAULT_UPLOAD_OPTIONS = {
  ACL: 'public-read'
}

const DEFAULT_S3_OPTIONS = {
  region: 'us-west-2'
}

export default class S3Plugin {
  constructor(options = {}) {
    var {s3Options = {}, s3UploadOptions = {}, directory, include, exclude, basePath, cdnizerOptions, htmlFiles} = options

    this.requiredS3Opts = ['accessKeyId', 'secretAccessKey']
    this.requiredS3UpOpts = ['Bucket']
    this.uploadOptions = s3UploadOptions
    this.isConnected = false
    this.cdnizerOptions = cdnizerOptions
    this.urlMappings = []
    this.uploadTotal = 0
    this.uploadProgress = 0

    this.options = {
      directory,
      include,
      exclude,
      htmlFiles: typeof htmlFiles === 'string' ? [htmlFiles] : htmlFiles
    }

    this.clientConfig = {
      maxAsyncS3: 50,
      s3Options: _.merge(s3Options, DEFAULT_S3_OPTIONS)
    }

    if (!this.cdnizerOptions.files)
      this.cdnizerOptions.files = []

    if (!this.cdnizerOptions)
      this.noCdnizer = true
  }

  apply(compiler) {
    var hasRequiredOptions = this.requiredS3Opts
      .every(type => this.clientConfig.s3Options[type])

    var hasRequiredUploadOpts = this.requiredS3UpOpts
      .every(type => this.uploadOptions[type])

    // Set directory to output dir or custom
    this.options.directory = this.options.directory || compiler.options.output.path || compiler.options.output.context || '.'

    compiler.plugin('after-emit', (compilation, cb) => {
      if (!hasRequiredOptions) {
        compilation.errors.push(new Error('S3Plugin: Must provide ' + this.requiredS3Opts.join(' and ')))
        cb()
      }

      if (!this.requiredS3UpOpts) {
        compilation.errors.push(new Error('S3Plugin-RequiredS3UploadOpts: ' + this.requiredS3UpOpts.join(' and ')))
        cb()
      }

      fs.readdir(this.options.directory, (error, files) => {
        if (error) {
          compilation.errors.push(new Error('S3Plugin-ReadOutputDir: ' + error))
          cb()
        } else {
          this.uploadFiles(this.getAssetFiles(compilation))
            .then(this.changeHtmlUrls.bind(this))
            .then(() => {
              cb()
            })
            .catch(e => {
              compilation.errors.push(new Error('S3Plugin: ' + e))
              cb()
            })
        }
      })
    })
  }

  getFileName(file = '') {
    return file.search('/') === -1 ? file : file.match(/[^\/]+$/)[0]
  }

  getAssetFiles({chunks, options}) {
    var publicPath = options.output.publicPath || options.output.path

    var files = _(chunks)
      .pluck('files')
      .flatten()
      .map(file => path.resolve(publicPath, file))
      .value()

    return this.filterAllowedFiles(files)
  }

  cdnizeHtml(htmlPath) {
    return new Promise((resolve, reject) => {
      fs.readFile(htmlPath, (err, data) => {
        if (err)
          return reject(err)

        fs.writeFile(htmlPath, this.cdnizer(data.toString()), function(err) {
          if (err)
            return reject(err)

          resolve()
        })
      })
    })
  }

  changeHtmlUrls() {
    if (this.noCdnizer)
      return Promise.resolve()

    var {directory, htmlFiles} = this.options

    var allHtml = (htmlFiles || fs.readdirSync(directory).filter(file => /\.html$/.test(file)))
      .map(file => path.resolve(directory, file))

    this.cdnizer = cdnizer(this.cdnizerOptions)

    return Promise.all(allHtml.map(file => this.cdnizeHtml(file)))
  }

  filterAllowedFiles(files) {
    return files.reduce((res, file) => {
      if (this.isIncludeOrExclude(file))
        res.push({
          name: file,
          path: path.resolve(this.options.directory, file)
        })

      return res
    }, [])
  }

  isIncludeOrExclude(file) {
    var isExclude,
        isInclude,
        {include, exclude} = this.options

    if (!include)
      isInclude = true
    else
      isInclude = include.test(file)

    if (!exclude)
      isExclude = false
    else
      isExclude = exclude.test(file)

    return isInclude && !isExclude
  }

  connect() {
    if (this.isConnected)
      return

    this.client = s3.createClient(this.clientConfig)
    this.isConnected = true
  }

  uploadFiles(files = []) {
    var sum = (array) => array.reduce((res, val) => res += val, 0)
    var uploadFiles = files.map(file => this.uploadFile(file.name, file.path))
    var progressAmount = Array(files.length)
    var progressTotal = Array(files.length)
    var finishedUploads = []

    console.log('Uploading Files: \n' + files.map(file => this.getFileName(file.name)).join('\n'))

    var progressBar = new ProgressBar('Uploading [:bar] :percent :etas', {
      complete: '>',
      incomplete: '-',
      total: 100
    })

    uploadFiles.forEach(function({upload}, i) {
      upload.on('end', function() {
        finishedUploads.push(true)

        if (finishedUploads.length === files.length)
          progressBar.update(100)
      })

      upload.on('progress', function() {
        progressTotal[i] = this.progressTotal
        progressAmount[i] = this.progressAmount

        progressBar.update(sum(progressAmount) / sum(progressTotal).toFixed(2))
      })
    })

    return Promise.all(uploadFiles.map(({promise}) => promise))
  }

  uploadFile(fileName, file) {
    if (fs.lstatSync(file).isDirectory())
      return this.uploadFiles(this.filterAllowedFiles(fs.readdirSync(file)))

    var upload,
        s3Params = _.merge({Key: fileName}, this.uploadOptions, DEFAULT_UPLOAD_OPTIONS)

    // Remove Gzip from encoding if ico
    if (/\.ico/.test(fileName) && s3Params.ContentEncoding === 'gzip')
      delete s3Params.ContentEncoding

    this.connect()
    upload = this.client.uploadFile({
      localFile: file,
      s3Params
    })

    this.cdnizerOptions.files.push(fileName)
    this.cdnizerOptions.files.push(fileName + '*')

    var promise = new Promise((resolve, reject) => {
      upload.on('error', reject)
      upload.on('end', () => resolve(file))
    })

    return {upload, promise}
  }
}
<commit_msg>Change required options to const<commit_after>'use strict'

import http from 'http'
import https from 'https'
import s3 from 's3'
import fs from 'fs'
import path from 'path'
import ProgressBar from 'progress'
import cdnizer from 'cdnizer'
import _ from 'lodash'

http.globalAgent.maxSockets = https.globalAgent.maxSockets = 50

const DEFAULT_UPLOAD_OPTIONS = {
  ACL: 'public-read'
}

const DEFAULT_S3_OPTIONS = {
  region: 'us-west-2'
}

const REQUIRED_S3_OPTS = ['accessKeyId', 'secretAccessKey'],
      REQUIRED_S3_UP_OPTS = ['Bucket']

export default class S3Plugin {
  constructor(options = {}) {
    var {s3Options = {}, s3UploadOptions = {}, directory, include, exclude, basePath, cdnizerOptions, htmlFiles} = options

    this.uploadOptions = s3UploadOptions
    this.isConnected = false
    this.cdnizerOptions = cdnizerOptions
    this.urlMappings = []
    this.uploadTotal = 0
    this.uploadProgress = 0

    this.options = {
      directory,
      include,
      exclude,
      htmlFiles: typeof htmlFiles === 'string' ? [htmlFiles] : htmlFiles
    }

    this.clientConfig = {
      maxAsyncS3: 50,
      s3Options: _.merge(s3Options, DEFAULT_S3_OPTIONS)
    }

    if (!this.cdnizerOptions.files)
      this.cdnizerOptions.files = []

    if (!this.cdnizerOptions)
      this.noCdnizer = true
  }

  apply(compiler) {
    var hasRequiredOptions = REQUIRED_S3_OPTS
      .every(type => this.clientConfig.s3Options[type])

    var hasRequiredUploadOpts = REQUIRED_S3_UP_OPTS
      .every(type => this.uploadOptions[type])

    // Set directory to output dir or custom
    this.options.directory = this.options.directory || compiler.options.output.path || compiler.options.output.context || '.'

    compiler.plugin('after-emit', (compilation, cb) => {
      if (!hasRequiredOptions) {
        compilation.errors.push(new Error('S3Plugin: Must provide ' + REQUIRED_S3_OPTS.join(', ')))
        cb()
      }

      if (!REQUIRED_S3_UP_OPTS) {
        compilation.errors.push(new Error('S3Plugin-RequiredS3UploadOpts: ' + REQUIRED_S3_UP_OPTS.join(', ')))
        cb()
      }

      fs.readdir(this.options.directory, (error, files) => {
        if (error) {
          compilation.errors.push(new Error('S3Plugin-ReadOutputDir: ' + error))
          cb()
        } else {
          this.uploadFiles(this.getAssetFiles(compilation))
            .then(this.changeHtmlUrls.bind(this))
            .then(() => {
              cb()
            })
            .catch(e => {
              compilation.errors.push(new Error('S3Plugin: ' + e))
              cb()
            })
        }
      })
    })
  }

  getFileName(file = '') {
    return file.search('/') === -1 ? file : file.match(/[^\/]+$/)[0]
  }

  getAssetFiles({chunks, options}) {
    var publicPath = options.output.publicPath || options.output.path

    var files = _(chunks)
      .pluck('files')
      .flatten()
      .map(file => path.resolve(publicPath, file))
      .value()

    return this.filterAllowedFiles(files)
  }

  cdnizeHtml(htmlPath) {
    return new Promise((resolve, reject) => {
      fs.readFile(htmlPath, (err, data) => {
        if (err)
          return reject(err)

        fs.writeFile(htmlPath, this.cdnizer(data.toString()), function(err) {
          if (err)
            return reject(err)

          resolve()
        })
      })
    })
  }

  changeHtmlUrls() {
    if (this.noCdnizer)
      return Promise.resolve()

    var {directory, htmlFiles} = this.options

    var allHtml = (htmlFiles || fs.readdirSync(directory).filter(file => /\.html$/.test(file)))
      .map(file => path.resolve(directory, file))

    this.cdnizer = cdnizer(this.cdnizerOptions)

    return Promise.all(allHtml.map(file => this.cdnizeHtml(file)))
  }

  filterAllowedFiles(files) {
    return files.reduce((res, file) => {
      if (this.isIncludeOrExclude(file))
        res.push({
          name: file,
          path: path.resolve(this.options.directory, file)
        })

      return res
    }, [])
  }

  isIncludeOrExclude(file) {
    var isExclude,
        isInclude,
        {include, exclude} = this.options

    if (!include)
      isInclude = true
    else
      isInclude = include.test(file)

    if (!exclude)
      isExclude = false
    else
      isExclude = exclude.test(file)

    return isInclude && !isExclude
  }

  connect() {
    if (this.isConnected)
      return

    this.client = s3.createClient(this.clientConfig)
    this.isConnected = true
  }

  uploadFiles(files = []) {
    var sum = (array) => array.reduce((res, val) => res += val, 0)
    var uploadFiles = files.map(file => this.uploadFile(file.name, file.path))
    var progressAmount = Array(files.length)
    var progressTotal = Array(files.length)
    var finishedUploads = []

    console.log('Uploading Files: \n' + files.map(file => this.getFileName(file.name)).join('\n'))

    var progressBar = new ProgressBar('Uploading [:bar] :percent :etas', {
      complete: '>',
      incomplete: '-',
      total: 100
    })

    uploadFiles.forEach(function({upload}, i) {
      upload.on('progress', function() {
        progressTotal[i] = this.progressTotal
        progressAmount[i] = this.progressAmount

        progressBar.update(sum(progressAmount) / sum(progressTotal).toFixed(2))
      })
    })

    return Promise.all(uploadFiles.map(({promise}) => promise))
  }

  uploadFile(fileName, file) {
    if (fs.lstatSync(file).isDirectory())
      return this.uploadFiles(this.filterAllowedFiles(fs.readdirSync(file)))

    var upload,
        s3Params = _.merge({Key: fileName}, this.uploadOptions, DEFAULT_UPLOAD_OPTIONS)

    // Remove Gzip from encoding if ico
    if (/\.ico/.test(fileName) && s3Params.ContentEncoding === 'gzip')
      delete s3Params.ContentEncoding

    this.connect()
    upload = this.client.uploadFile({
      localFile: file,
      s3Params
    })

    this.cdnizerOptions.files.push(fileName)
    this.cdnizerOptions.files.push(fileName + '*')

    var promise = new Promise((resolve, reject) => {
      upload.on('error', reject)
      upload.on('end', () => resolve(file))
    })

    return {upload, promise}
  }
}
<|endoftext|>


<commit_before>/**
 * Copyright (c) 2015-present, Facebook, Inc.
 * All rights reserved.
 *
 * This source code is licensed under the BSD-style license found in the
 * LICENSE file in the root directory of this source tree. An additional grant
 * of patent rights can be found in the PATENTS file in the same directory.
 *
 * @providesModule HeaderLinks
 */

var React = require('React');

var HeaderLinks = React.createClass({
  linksInternal: [
    {section: 'docs', href: '/react-native/docs/getting-started.html', text: 'Docs'},
    {section: 'support', href: '/react-native/support.html', text: 'Support'},
  ],
  linksExternal: [
    {section: 'github', href: 'https://github.com/facebook/react-native', text: 'GitHub'},
    {section: 'react', href: 'http://facebook.github.io/react', text: 'React'},
  ],

  makeLinks: function(links) {
    return links.map(function(link) {
      return (

            {link.text}

      );
    }, this)
  },

  render: function() {
    return (

          {this.makeLinks(this.linksInternal)}

          {this.makeLinks(this.linksExternal)}

    );
  }
});

module.exports = HeaderLinks;
<commit_msg>Add link for releases<commit_after>/**
 * Copyright (c) 2015-present, Facebook, Inc.
 * All rights reserved.
 *
 * This source code is licensed under the BSD-style license found in the
 * LICENSE file in the root directory of this source tree. An additional grant
 * of patent rights can be found in the PATENTS file in the same directory.
 *
 * @providesModule HeaderLinks
 */

var React = require('React');

var HeaderLinks = React.createClass({
  linksInternal: [
    {section: 'docs', href: '/react-native/docs/getting-started.html', text: 'Docs'},
    {section: 'support', href: '/react-native/support.html', text: 'Support'},
    {section: 'releases', href: 'https://github.com/facebook/react-native/releases', text: 'Releases'},
  ],
  linksExternal: [
    {section: 'github', href: 'https://github.com/facebook/react-native', text: 'GitHub'},
    {section: 'react', href: 'http://facebook.github.io/react', text: 'React'},
  ],

  makeLinks: function(links) {
    return links.map(function(link) {
      return (

            {link.text}

      );
    }, this)
  },

  render: function() {
    return (

          {this.makeLinks(this.linksInternal)}

          {this.makeLinks(this.linksExternal)}

    );
  }
});

module.exports = HeaderLinks;
<|endoftext|>


<commit_before>const jsonSchemaTest = require('json-schema-test');
const djv = require('../');

const refs = {
  'http://localhost:1234/integer.json': require('json-schema-test-suite/remotes/integer.json'),
  'http://localhost:1234/subSchemas.json': require('json-schema-test-suite/remotes/subSchemas.json'),
  'http://localhost:1234/folder/folderInteger.json': require('json-schema-test-suite/remotes/folder/folderInteger.json'),
  'http://json-schema.org/draft-04/schema': require('./resources/draft-04-schema.json'),
};

const factory = function djvTestSuiteAdapter() {
  return {
    validate(schema, instance) {
      try {
        const env = djv();
        Object.keys(refs).forEach((uri) => {
          env.addSchema(uri, refs[uri]);
        });
        env.addSchema('test', schema);
        const error = env.validate('test', instance);

        this.errors = error ? [error] : null;
        return !error;
      } catch (error) {
        this.errors = [error.message];
        return false;
      }
    },
  };
};

function runTest(draft) {
  jsonSchemaTest(factory(), {
    description: `Test suite draft-0${draft}`,
    suites: { tests: `../node_modules/json-schema-test-suite/tests/draft${draft}/{**/,}*.json` },
    cwd: __dirname,
    hideFolder: 'tests/'
  });
}

runTest(4);
runTest(6);
<commit_msg>tests(debug): Add console output if error is thrown<commit_after>const jsonSchemaTest = require('json-schema-test');
const djv = require('../');

const refs = {
  'http://localhost:1234/integer.json': require('json-schema-test-suite/remotes/integer.json'),
  'http://localhost:1234/subSchemas.json': require('json-schema-test-suite/remotes/subSchemas.json'),
  'http://localhost:1234/folder/folderInteger.json': require('json-schema-test-suite/remotes/folder/folderInteger.json'),
  'http://json-schema.org/draft-04/schema': require('./resources/draft-04-schema.json'),
};

const factory = function djvTestSuiteAdapter() {
  return {
    validate(schema, instance) {
      try {
        const env = djv();
        Object.keys(refs).forEach((uri) => {
          env.addSchema(uri, refs[uri]);
        });
        env.addSchema('test', schema);
        const error = env.validate('test', instance);

        this.errors = error ? [error] : null;
        return !error;
      } catch (error) {
        console.error(error.stack);
        this.errors = [error.message];
        return false;
      }
    },
  };
};

function runTest(draft) {
  jsonSchemaTest(factory(), {
    description: `Test suite draft-0${draft}`,
    suites: { tests: `../node_modules/json-schema-test-suite/tests/draft${draft}/{**/,}*.json` },
    cwd: __dirname,
    hideFolder: 'tests/'
  });
}

runTest(4);
runTest(6);
<|endoftext|>


<commit_before>(function() {
  "use strict";

  var root = this,
      $ = root.jQuery;
  if(typeof root.GOVUK === 'undefined') { root.GOVUK = {}; }

  var userSatisfaction = {
    TEMPLATE: '
' +
              '
' +
              '
Tell us what you think of GOV.UK
' +
              '
No thanks
' +
              '
Take the 3 minute survey This will open a short survey on another website
' +
              '
' +
              '
',

    TEST_TEMPLATE: '
' +
                   '
' +
                   '
Are you visiting GOV.UK for professional or personal reasons?
' +
                   '
No thanks
' +
                   '
Take the 1 question survey This will open a short survey on another website
' +
                   '
' +
                   '
',

    cookieNameTakenSurvey: "govuk_takenUserSatisfactionSurvey",
    trackEvent: function (action, label) {
      GOVUK.analytics.trackEvent('user_satisfaction_survey', action, {
        label: label,
        value: 1,
        nonInteraction: true
      });
    },
    setCookieTakenSurvey: function () {
      GOVUK.cookie(userSatisfaction.cookieNameTakenSurvey, true, { days: 30*4 });
      $("#user-satisfaction-survey").removeClass('visible').attr('aria-hidden', 'true');
    },
    setEventHandlers: function () {
      var $noThanks = $('#survey-no-thanks');
      var $takeSurvey = $('#take-survey');

      $noThanks.click(function (e) {
        userSatisfaction.setCookieTakenSurvey();
        userSatisfaction.trackEvent('banner_no_thanks', 'No thanks clicked');
        e.stopPropagation();
        return false;
      });
      $takeSurvey.click(function () {
        userSatisfaction.setCookieTakenSurvey();
        userSatisfaction.trackEvent('banner_taken', 'User taken survey');
      });
    },
    showSurveyBar: function () {
      if (GOVUK.cookie(userSatisfaction.cookieNameTakenSurvey) === "true" ||
          userSatisfaction.otherNotificationVisible()) {
        return;
      }

      var template = userSatisfaction.inTestPeriod() ? userSatisfaction.TEST_TEMPLATE : userSatisfaction.TEMPLATE;
      $("#user-satisfaction-survey-container").append(template);

      userSatisfaction.setEventHandlers();
      userSatisfaction.setSurveyUrl();
      userSatisfaction.trackEvent('banner_shown', 'Banner has been shown');
    },
    otherNotificationVisible: function() {
      return $('#banner-notification:visible, #global-cookie-message:visible, #global-browser-prompt:visible').length > 0;
    },
    randomlyShowSurveyBar: function () {
      if ($('#user-satisfaction-survey-container').length <= 0) {
        return;
      }
      if (Math.floor(Math.random() * 50) === 0) {
        userSatisfaction.showSurveyBar();
      }
    },
    setSurveyUrl: function(href) {
      var $surveyLink = $('#take-survey');
      var surveyUrl = $('#user-satisfaction-survey-container').data('survey-url');

      if (userSatisfaction.inTestPeriod()) {
        surveyUrl = 'https://www.surveymonkey.co.uk/r/D668G5Z';
      }

      if (surveyUrl.indexOf('?c=') === -1) {
        surveyUrl += "?c=" + root.location.pathname;
      }

      $surveyLink.attr('href', surveyUrl);
    },
    inTestPeriod: function() {
      var starts = new Date("March 2, 2016").getTime();
      var ends = new Date("March 3, 2016 23:59:59").getTime();

      return userSatisfaction.currentDate() >= starts &&
        userSatisfaction.currentDate() <= ends;
    },
    currentDate: function() { return new Date().getTime(); }
  };

  root.GOVUK.userSatisfaction = userSatisfaction;
}).call(this);
<commit_msg>Don't show global bar and survey bar<commit_after>(function() {
  "use strict";

  var root = this,
      $ = root.jQuery;
  if(typeof root.GOVUK === 'undefined') { root.GOVUK = {}; }

  var userSatisfaction = {
    TEMPLATE: '
' +
              '
' +
              '
Tell us what you think of GOV.UK
' +
              '
No thanks
' +
              '
Take the 3 minute survey This will open a short survey on another website
' +
              '
' +
              '
',

    TEST_TEMPLATE: '
' +
                   '
' +
                   '
Are you visiting GOV.UK for professional or personal reasons?
' +
                   '
No thanks
' +
                   '
Take the 1 question survey This will open a short survey on another website
' +
                   '
' +
                   '
',

    cookieNameTakenSurvey: "govuk_takenUserSatisfactionSurvey",
    trackEvent: function (action, label) {
      GOVUK.analytics.trackEvent('user_satisfaction_survey', action, {
        label: label,
        value: 1,
        nonInteraction: true
      });
    },
    setCookieTakenSurvey: function () {
      GOVUK.cookie(userSatisfaction.cookieNameTakenSurvey, true, { days: 30*4 });
      $("#user-satisfaction-survey").removeClass('visible').attr('aria-hidden', 'true');
    },
    setEventHandlers: function () {
      var $noThanks = $('#survey-no-thanks');
      var $takeSurvey = $('#take-survey');

      $noThanks.click(function (e) {
        userSatisfaction.setCookieTakenSurvey();
        userSatisfaction.trackEvent('banner_no_thanks', 'No thanks clicked');
        e.stopPropagation();
        return false;
      });
      $takeSurvey.click(function () {
        userSatisfaction.setCookieTakenSurvey();
        userSatisfaction.trackEvent('banner_taken', 'User taken survey');
      });
    },
    showSurveyBar: function () {
      if (GOVUK.cookie(userSatisfaction.cookieNameTakenSurvey) === "true" ||
          userSatisfaction.otherNotificationVisible()) {
        return;
      }

      // Hide global bar if one is showing
      $('#global-bar').hide();

      var template = userSatisfaction.inTestPeriod() ? userSatisfaction.TEST_TEMPLATE : userSatisfaction.TEMPLATE;
      $("#user-satisfaction-survey-container").append(template);

      userSatisfaction.setEventHandlers();
      userSatisfaction.setSurveyUrl();
      userSatisfaction.trackEvent('banner_shown', 'Banner has been shown');
    },
    otherNotificationVisible: function() {
      return $('#banner-notification:visible, #global-cookie-message:visible, #global-browser-prompt:visible').length > 0;
    },
    randomlyShowSurveyBar: function () {
      if ($('#user-satisfaction-survey-container').length <= 0) {
        return;
      }
      if (Math.floor(Math.random() * 50) === 0) {
        userSatisfaction.showSurveyBar();
      }
    },
    setSurveyUrl: function(href) {
      var $surveyLink = $('#take-survey');
      var surveyUrl = $('#user-satisfaction-survey-container').data('survey-url');

      if (userSatisfaction.inTestPeriod()) {
        surveyUrl = 'https://www.surveymonkey.co.uk/r/D668G5Z';
      }

      if (surveyUrl.indexOf('?c=') === -1) {
        surveyUrl += "?c=" + root.location.pathname;
      }

      $surveyLink.attr('href', surveyUrl);
    },
    inTestPeriod: function() {
      var starts = new Date("March 2, 2016").getTime();
      var ends = new Date("March 3, 2016 23:59:59").getTime();

      return userSatisfaction.currentDate() >= starts &&
        userSatisfaction.currentDate() <= ends;
    },
    currentDate: function() { return new Date().getTime(); }
  };

  root.GOVUK.userSatisfaction = userSatisfaction;
}).call(this);
<|endoftext|>


<commit_before>/**
 * @providesModule HeaderLinks
 * @jsx React.DOM
 */

var HeaderLinks = React.createClass({
  links: [
    {section: 'docs', href: '/flux/docs/overview.html#content', text: 'docs'},
    {section: 'support', href: '/flux/support.html', text: 'support'},
    {section: 'github', href: 'http://github.com/facebook/flux', text: 'github'},
  ],

  render: function() {
    return (

        {this.links.map(function(link) {
          return (

                {link.text}

          );
        }, this)}

    );
  }
});

module.exports = HeaderLinks;
<commit_msg>add github repo on header<commit_after>/**
 * @providesModule HeaderLinks
 * @jsx React.DOM
 */

var HeaderLinks = React.createClass({
  links: [
    {section: 'docs', href: '/flux/docs/overview.html#content', text: 'docs'},
    {section: 'support', href: '/flux/support.html', text: 'support'},
    {section: 'github', href: 'http://github.com/facebook/flux', text: 'github'},
    {section: 'github-ko', href: 'http://github.com/haruair/flux', text: '(ko)'},
  ],

  render: function() {
    return (

        {this.links.map(function(link) {
          return (

                {link.text}

          );
        }, this)}

    );
  }
});

module.exports = HeaderLinks;
<|endoftext|>


<commit_before>import React, { Component, PropTypes } from 'react';
import { findDOMNode } from 'react-dom';
import { Link } from 'react-router';
import _ from 'underscore';

import { capitalize } from '../utils/helpers';

export default class Breadcrumbs extends Component {

  handleChange(e) {
    const { onChange } = this.props;
    onChange(e.target.value);
  }

  renderBreadcrumbs() {
    const { breadcrumbs } = this.props;
    return _.map(breadcrumbs, (b, i) => {
      let text = capitalize(b.text);
      if (b.link) {
        return
<Link to={b.link}>{text}</Link>
;
      }else {
        return (

            <input onChange={(e) => this.handleChange(e)}
              ref="input"
              placeholder="Filename"
              value={b.text} />

        );
      }
    });
  }

  render() {
    return (

        {this.renderBreadcrumbs()}

    );
  }
}

Breadcrumbs.propTypes = {
  breadcrumbs: PropTypes.array.isRequired,
  onChange: PropTypes.func.isRequired
};
<commit_msg>fix filename input<commit_after>import React, { Component, PropTypes } from 'react';
import { findDOMNode } from 'react-dom';
import { Link } from 'react-router';
import _ from 'underscore';

import { capitalize } from '../utils/helpers';

export default class Breadcrumbs extends Component {

  handleChange(e) {
    const { onChange } = this.props;
    onChange(e.target.value);
  }

  renderBreadcrumbs() {
    const { breadcrumbs } = this.props;
    return _.map(breadcrumbs, (b, i) => {
      let text = capitalize(b.text);
      if (b.link) {
        return
<Link to={b.link}>{text}</Link>
;
      }else {
        return (

            <input onChange={(e) => this.handleChange(e)}
              ref="input"
              placeholder="Filename"
              defaultValue={b.text} />

        );
      }
    });
  }

  render() {
    return (

        {this.renderBreadcrumbs()}

    );
  }
}

Breadcrumbs.propTypes = {
  breadcrumbs: PropTypes.array.isRequired,
  onChange: PropTypes.func.isRequired
};
<|endoftext|>


<commit_before>'use strict';

describe('Service', function() {

    describe('dynamicTable', function () {

        var element, scope, dynamicTable = null,
            backendUrl = '/foobar.php', calledUrl = null, filterCalled = null;

        function compileDirective(tpl) {
            inject(function ($compile) {
                element = $compile(tpl)(scope);
                element.appendTo(document.body);
            });
            scope.$digest();
        }

        beforeEach(function (){
            angular.mock.module('dynamicTable', function (dynamicTableProvider, $provide) {
                dynamicTableProvider.setTranslationFilter('foobar');
                $provide.value('$filter', function (filterName) {
                    return function (key) {
                        filterCalled = filterName;
                    };
                });
            });
        });

        beforeEach(inject(function (_dynamicTable_) {
            dynamicTable = _dynamicTable_;
            jQuery.getJSON = function (url, params, success) {
                calledUrl = url;
                success({});
            };
        }));

        beforeEach(inject(function ($rootScope) {
            scope = $rootScope.$new();
        }));

        it('loads translations', function () {
            expect(filterCalled).toBe('foobar');
        });

        it('creates correct instance', function () {
            var inst = dynamicTable({ url: backendUrl });
            expect(typeof inst).toBe('object');
            expect(inst.options['url']).toBe(backendUrl);
        });

        it('underlying plugin works', function () {
            scope.ctrl = dynamicTable({ url: backendUrl });
            compileDirective('

');

            expect(calledUrl).toBe(backendUrl);

            scope.ctrl.element.trigger('dt.loading');
            expect(scope.ctrl.event).toBe('loading');

            scope.ctrl.element.trigger('dt.loaded');
            expect(scope.ctrl.event).toBe('loaded');

            scope.ctrl.element.trigger('dt.selected');
            expect(scope.ctrl.event).toBe('selected');

            scope.ctrl.element.trigger('dt.deselected');
            expect(scope.ctrl.event).toBe('deselected');
        });

    });

});
<commit_msg>Removed extra describe()<commit_after>'use strict';

describe('dynamicTable', function () {

    var element, scope, dynamicTable = null,
        backendUrl = '/foobar.php', calledUrl = null, filterCalled = null;

    function compileDirective(tpl) {
        inject(function ($compile) {
            element = $compile(tpl)(scope);
            element.appendTo(document.body);
        });
        scope.$digest();
    }

    beforeEach(function (){
        angular.mock.module('dynamicTable', function (dynamicTableProvider, $provide) {
            dynamicTableProvider.setTranslationFilter('foobar');
            $provide.value('$filter', function (filterName) {
                return function (key) {
                    filterCalled = filterName;
                };
            });
        });
    });

    beforeEach(inject(function (_dynamicTable_) {
        dynamicTable = _dynamicTable_;
        jQuery.getJSON = function (url, params, success) {
            calledUrl = url;
            success({});
        };
    }));

    beforeEach(inject(function ($rootScope) {
        scope = $rootScope.$new();
    }));

    it('loads translations', function () {
        expect(filterCalled).toBe('foobar');
    });

    it('creates correct instance', function () {
        var inst = dynamicTable({ url: backendUrl });
        expect(typeof inst).toBe('object');
        expect(inst.options['url']).toBe(backendUrl);
    });

    it('underlying plugin works', function () {
        scope.ctrl = dynamicTable({ url: backendUrl });
        compileDirective('

');

        expect(calledUrl).toBe(backendUrl);

        scope.ctrl.element.trigger('dt.loading');
        expect(scope.ctrl.event).toBe('loading');

        scope.ctrl.element.trigger('dt.loaded');
        expect(scope.ctrl.event).toBe('loaded');

        scope.ctrl.element.trigger('dt.selected');
        expect(scope.ctrl.event).toBe('selected');

        scope.ctrl.element.trigger('dt.deselected');
        expect(scope.ctrl.event).toBe('deselected');
    });

});
<|endoftext|>


<commit_before><commit_msg>Update replay.js<commit_after><|endoftext|>


<commit_before>(function() {
  "use strict";

  var root = this,
      $ = root.jQuery;
  if(typeof root.GOVUK === 'undefined') { root.GOVUK = {}; }

  var userSatisfaction = {
    cookieNameTakenSurvey: "govuk_takenUserSatisfactionSurvey",
    setCookieTakenSurvey: function () {
      GOVUK.cookie(userSatisfaction.cookieNameTakenSurvey, true, { days: 30*4 });
      $("#user-satisfaction-survey").removeClass('visible');
    },
    appendCurrentPathToSurveyUrl: function() {
      var takeSurvey = document.getElementById('take-survey');
      var href = takeSurvey.getAttribute('href');
      if (href.indexOf('?c=') === -1) {
        var surveyUrlWithPath = href + "?c=" + root.location.pathname;
        takeSurvey.setAttribute('href', surveyUrlWithPath);
      }
    },
    setEventHandlers: function () {
      var $noThanks = $('#survey-no-thanks');
      var $takeSurvey = $('#take-survey');

      $noThanks.click(function (e) {
        userSatisfaction.setCookieTakenSurvey();
        e.stopPropagation();
        return false;
      });
      $takeSurvey.click(userSatisfaction.setCookieTakenSurvey);
    },
    showSurveyBar: function () {
      if (GOVUK.cookie(userSatisfaction.cookieNameTakenSurvey) === "true" ||
          userSatisfaction.otherNotificationVisible()) {
        return;
      }

      userSatisfaction.setEventHandlers();
      userSatisfaction.appendCurrentPathToSurveyUrl();

      $("#user-satisfaction-survey").addClass('visible');
    },
    otherNotificationVisible: function() {
      return $('#banner-notification:visible, #global-cookie-message:visible, #global-browser-prompt:visible').length > 0;
    },
    randomlyShowSurveyBar: function () {
      if ($('#user-satisfaction-survey').length <= 0) {
        return;
      }
      if (Math.floor(Math.random() * 50) === 0) {
        userSatisfaction.showSurveyBar();
      }
    }
  };

  root.GOVUK.userSatisfaction = userSatisfaction;
}).call(this);
<commit_msg>Add method to set user satisfaction survey URL<commit_after>(function() {
  "use strict";

  var root = this,
      $ = root.jQuery;
  if(typeof root.GOVUK === 'undefined') { root.GOVUK = {}; }

  var userSatisfaction = {
    cookieNameTakenSurvey: "govuk_takenUserSatisfactionSurvey",
    setCookieTakenSurvey: function () {
      GOVUK.cookie(userSatisfaction.cookieNameTakenSurvey, true, { days: 30*4 });
      $("#user-satisfaction-survey").removeClass('visible');
    },
    appendCurrentPathToSurveyUrl: function() {
      var takeSurvey = document.getElementById('take-survey');
      var href = takeSurvey.getAttribute('href');
      if (href.indexOf('?c=') === -1) {
        var surveyUrlWithPath = href + "?c=" + root.location.pathname;
        takeSurvey.setAttribute('href', surveyUrlWithPath);
      }
    },
    setEventHandlers: function () {
      var $noThanks = $('#survey-no-thanks');
      var $takeSurvey = $('#take-survey');

      $noThanks.click(function (e) {
        userSatisfaction.setCookieTakenSurvey();
        e.stopPropagation();
        return false;
      });
      $takeSurvey.click(userSatisfaction.setCookieTakenSurvey);
    },
    showSurveyBar: function () {
      if (GOVUK.cookie(userSatisfaction.cookieNameTakenSurvey) === "true" ||
          userSatisfaction.otherNotificationVisible()) {
        return;
      }

      userSatisfaction.setEventHandlers();
      userSatisfaction.appendCurrentPathToSurveyUrl();

      $("#user-satisfaction-survey").addClass('visible');
    },
    otherNotificationVisible: function() {
      return $('#banner-notification:visible, #global-cookie-message:visible, #global-browser-prompt:visible').length > 0;
    },
    randomlyShowSurveyBar: function () {
      if ($('#user-satisfaction-survey').length <= 0) {
        return;
      }
      if (Math.floor(Math.random() * 50) === 0) {
        userSatisfaction.showSurveyBar();
      }
    },
    setSurveyUrl: function(href) {
      var takeSurvey = document.getElementById('take-survey');
      takeSurvey.setAttribute('href', href);
      userSatisfaction.appendCurrentPathToSurveyUrl();
    }
  };

  root.GOVUK.userSatisfaction = userSatisfaction;
}).call(this);
<|endoftext|>


<commit_before>var CONVERSATION_LIST_TEMPLATE = null;

Handlebars.registerHelper('unlessCond', function(v1, v2, options) {
  if(v1 !== v2) {
    return options.fn(this);
  }
  return options.inverse(this);
});

Handlebars.registerHelper('isConsult', function(participants, options) {
  if(participants.length > 2) {
    return options.fn(this);
  }
  return options.inverse(this);
});

var clientId = '6cf4b2d8-26ed-4f44-af81-932a8d8b9404';
var redirectUri = 'http://localhost:8080/index.html';

var pureCloudSession = new purecloud.platform.PureCloudSession({
    strategy: 'implicit',
    clientId: clientId,
    redirectUrl: redirectUri,
    environment: 'inindca.com'
});
pureCloudSession.login();

var conversationsApi = new purecloud.platform.ConversationsApi(pureCloudSession);
var interval;
var conversationList = [];

// jQuery calls this when the DOM is available
$(document).ready(function() {
    CONVERSATION_LIST_TEMPLATE =Handlebars.compile($("#entry-template").html());

    // Handle the toggle polling button click
    $('button#togglePolling').click(function() {
        if (interval) {
            clearInterval(interval);
            interval = null;
            $('button#togglePolling').text('Start Queue Polling');
        } else {
            interval = setInterval(checkQueue, 1000);
            $('button#togglePolling').text('Stop Queue Polling');
        }
    });

    // Handle dial button click
    $('button#dial').click(function() {
        // Create request body
        var body = {
            "phoneNumber":$('input#dialstring').val()
        };

        // Invoke API
        conversationsApi.postCalls(body).then(function(result) {
            // Clear dialstring from text box
            $('input#dialstring').val('');
        }).catch(function(err){
            console.error(err);
        });
    });
});

// Gets current queue contents and displays it
function checkQueue() {
    console.debug("Checking queue...");
    // Invoke API
    conversationsApi.getCalls().then(function(calls) {
        conversationList = calls.entities;
        var html = CONVERSATION_LIST_TEMPLATE(calls.entities);
        $('#call-table').html(html);
    });
}

// Mute participant
function mute(callId, participantId, currentMuteState) {
    // Create request body
    var body = {
        "muted": !currentMuteState
    };

    // Invoke API
    conversationsApi.patchCallsCallIdParticipantsParticipantId(callId, participantId, body).then(function(result) {
        // Result will be empty here
    }).catch(function(err){
        console.error(err);
    });
}

// Hold participant
function hold(callId, participantId, currentHoldState) {
    // Create request body
    var body = {
        "held": !currentHoldState
    };

    // Invoke API
    conversationsApi.patchCallsCallIdParticipantsParticipantId(callId, participantId, body).then(function(result) {
        // Result will be empty here
    }).catch(function(err){
        console.error(err);
    });
}

// Disconnect participant
function disconnect(callId, participantId) {
    // Create request body
    var body = {
        "state": "disconnected"
    };

    // Invoke API
    conversationsApi.patchCallsCallIdParticipantsParticipantId(callId, participantId, body).then(function(result) {
        // Result will be empty here
    }).catch(function(err){
        console.error(err);
    });
}

function startConsult() {
    var callId = conversationList[0].id;

    //grab the first participant which is going to be the party we dialed for an outbound call
    var participantId = conversationList[0].participants[1].id;

    // Create request body
    var body = {
        "speakTo": "destination",
        "destination":{
            "address" : $('input#newparticipant').val()
        }
    };

    // Invoke API
    conversationsApi.postCallsCallIdParticipantsParticipantIdConsult(callId, participantId, body).then(function(result) {
        $('input#newparticipant').val('');
        // We can ignore the response in this tutorial.
    }).catch(function(err){
        console.error(err);
    });
}

function consultSpeakTo(speakTo) {
    var callId = conversationList[0].id;

    //grab the first participant which is going to be the party we dialed for an outbound call
    var participantId = conversationList[0].participants[1].id;

    // Create request body
    var body = {
        "speakTo": speakTo
    };

    // Invoke API
    conversationsApi.patchCallsCallIdParticipantsParticipantIdConsult(callId, participantId, body).then(function(result) {
        // We can ignore the response in this tutorial.
    }).catch(function(err){
        console.error(err);
    });
}
<commit_msg>Update calls.js<commit_after>var CONVERSATION_LIST_TEMPLATE = null;

Handlebars.registerHelper('unlessCond', function(v1, v2, options) {
  if(v1 !== v2) {
    return options.fn(this);
  }
  return options.inverse(this);
});

Handlebars.registerHelper('isConsult', function(participants, options) {
  if(participants.length > 2) {
    return options.fn(this);
  }
  return options.inverse(this);
});

var clientId = '6cf4b2d8-26ed-4f44-af81-932a8d8b9404';
var redirectUri = 'http://localhost:8080/index.html';

var pureCloudSession = new purecloud.platform.PureCloudSession({
    strategy: 'implicit',
    clientId: clientId,
    redirectUrl: redirectUri,
    environment: 'mypurecloud.com'
});
pureCloudSession.login();

var conversationsApi = new purecloud.platform.ConversationsApi(pureCloudSession);
var interval;
var conversationList = [];

// jQuery calls this when the DOM is available
$(document).ready(function() {
    CONVERSATION_LIST_TEMPLATE =Handlebars.compile($("#entry-template").html());

    // Handle the toggle polling button click
    $('button#togglePolling').click(function() {
        if (interval) {
            clearInterval(interval);
            interval = null;
            $('button#togglePolling').text('Start Queue Polling');
        } else {
            interval = setInterval(checkQueue, 1000);
            $('button#togglePolling').text('Stop Queue Polling');
        }
    });

    // Handle dial button click
    $('button#dial').click(function() {
        // Create request body
        var body = {
            "phoneNumber":$('input#dialstring').val()
        };

        // Invoke API
        conversationsApi.postCalls(body).then(function(result) {
            // Clear dialstring from text box
            $('input#dialstring').val('');
        }).catch(function(err){
            console.error(err);
        });
    });
});

// Gets current queue contents and displays it
function checkQueue() {
    console.debug("Checking queue...");
    // Invoke API
    conversationsApi.getCalls().then(function(calls) {
        conversationList = calls.entities;
        var html = CONVERSATION_LIST_TEMPLATE(calls.entities);
        $('#call-table').html(html);
    });
}

// Mute participant
function mute(callId, participantId, currentMuteState) {
    // Create request body
    var body = {
        "muted": !currentMuteState
    };

    // Invoke API
    conversationsApi.patchCallsCallIdParticipantsParticipantId(callId, participantId, body).then(function(result) {
        // Result will be empty here
    }).catch(function(err){
        console.error(err);
    });
}

// Hold participant
function hold(callId, participantId, currentHoldState) {
    // Create request body
    var body = {
        "held": !currentHoldState
    };

    // Invoke API
    conversationsApi.patchCallsCallIdParticipantsParticipantId(callId, participantId, body).then(function(result) {
        // Result will be empty here
    }).catch(function(err){
        console.error(err);
    });
}

// Disconnect participant
function disconnect(callId, participantId) {
    // Create request body
    var body = {
        "state": "disconnected"
    };

    // Invoke API
    conversationsApi.patchCallsCallIdParticipantsParticipantId(callId, participantId, body).then(function(result) {
        // Result will be empty here
    }).catch(function(err){
        console.error(err);
    });
}

function startConsult() {
    var callId = conversationList[0].id;

    //grab the first participant which is going to be the party we dialed for an outbound call
    var participantId = conversationList[0].participants[1].id;

    // Create request body
    var body = {
        "speakTo": "destination",
        "destination":{
            "address" : $('input#newparticipant').val()
        }
    };

    // Invoke API
    conversationsApi.postCallsCallIdParticipantsParticipantIdConsult(callId, participantId, body).then(function(result) {
        $('input#newparticipant').val('');
        // We can ignore the response in this tutorial.
    }).catch(function(err){
        console.error(err);
    });
}

function consultSpeakTo(speakTo) {
    var callId = conversationList[0].id;

    //grab the first participant which is going to be the party we dialed for an outbound call
    var participantId = conversationList[0].participants[1].id;

    // Create request body
    var body = {
        "speakTo": speakTo
    };

    // Invoke API
    conversationsApi.patchCallsCallIdParticipantsParticipantIdConsult(callId, participantId, body).then(function(result) {
        // We can ignore the response in this tutorial.
    }).catch(function(err){
        console.error(err);
    });
}
<|endoftext|>


<commit_before>$('a.page-scroll').bind('click', function(event) {
    var $anchor = $(this);
    $('html, body').stop().animate({
        scrollTop: ($($anchor.attr('href')).offset().top - 50)
    }, 1250, 'easeInOutExpo');
    event.preventDefault();
});

// Highlight top nav bar when user starts scrolling
$('body').scrollspy({
    target: '.navbar-fixed-top',
    offset: 51
});

// Closes the Responsive Menu on Menu Item Click
$('.navbar-collapse ul li a').click(function(){
    $('.navbar-toggle:visible').click();
});

// Offset for Main Navigation
$('#mainNav').affix({
    offset: {
        top: 100
}});

var nuhacksmodule = angular.module('hacks', []);

nuhacksmodule.controller('hacksController',function ($http, $q, $scope) {
    $scope.teamdata = [];
    $scope.rules = [];
    $scope.sponsors = [];
    var faqjson = $http.get('js/faq.json');
    var teamjson = $http.get('js/team.json');
    var sponsorsjson = $http.get('js/sponsors.json');
    $q.all([faqjson,teamjson,sponsorsjson]).then(function(allpromises){
        $scope.rules = allpromises[0].data;
        $scope.teamdata = allpromises[1].data;
        $scope.sponsors = allpromises[2].data;
    });

});

$(window).load(function() {
    "use strict";
    $(".loader").delay(1000).fadeOut();
    $("#mask").delay(1000).fadeOut("slow");

    $(function() {
        var fr = new FilmRoll({
            container: '#film_roll',
            height: 210
        });
    });

    $(".item img").fadeIn(500);

    // clone image
    $('.item img').each(function(){
        var el = $(this);
        el.css({"position":"absolute"}).wrap("
").clone().addClass('img_grayscale').css({"position":"absolute","z-index":"998","opacity":"0"}).insertBefore(el).queue(function(){
            var el = $(this);
            el.parent().css({"width":this.width,"height":this.height});
            el.dequeue();
        });
        this.src = grayscale(this.src);
    });

    // Fade image
    $('.item img').mouseover(function(){
        $(this).parent().find('img:first').stop().animate({opacity:1}, 1000);
    })
    $('.img_grayscale').mouseout(function(){
        $(this).stop().animate({opacity:0}, 1000);
    });

});

// Grayscale w canvas method
function grayscale(src){
    var canvas = document.createElement('canvas');
    var ctx = canvas.getContext('2d');
    var imgObj = new Image();
    imgObj.src = src;
    canvas.width = imgObj.width;
    canvas.height = imgObj.height;
    ctx.drawImage(imgObj, 0, 0);
    var imgPixels = ctx.getImageData(0, 0, canvas.width, canvas.height);
    for(var y = 0; y < imgPixels.height; y++){
        for(var x = 0; x < imgPixels.width; x++){
            var i = (y * 4) * imgPixels.width + x * 4;
            var avg = (imgPixels.data[i] + imgPixels.data[i + 1] + imgPixels.data[i + 2]) / 3;
            imgPixels.data[i] = avg;
            imgPixels.data[i + 1] = avg;
            imgPixels.data[i + 2] = avg;
        }
    }
    ctx.putImageData(imgPixels, 0, 0, 0, 0, imgPixels.width, imgPixels.height);
    return canvas.toDataURL();
}
<commit_msg>Reduced preloader time<commit_after>$('a.page-scroll').bind('click', function(event) {
    var $anchor = $(this);
    $('html, body').stop().animate({
        scrollTop: ($($anchor.attr('href')).offset().top - 50)
    }, 1250, 'easeInOutExpo');
    event.preventDefault();
});

// Highlight top nav bar when user starts scrolling
$('body').scrollspy({
    target: '.navbar-fixed-top',
    offset: 51
});

// Closes the Responsive Menu on Menu Item Click
$('.navbar-collapse ul li a').click(function(){
    $('.navbar-toggle:visible').click();
});

// Offset for Main Navigation
$('#mainNav').affix({
    offset: {
        top: 100
}});

var nuhacksmodule = angular.module('hacks', []);

nuhacksmodule.controller('hacksController',function ($http, $q, $scope) {
    $scope.teamdata = [];
    $scope.rules = [];
    $scope.sponsors = [];
    var faqjson = $http.get('js/faq.json');
    var teamjson = $http.get('js/team.json');
    var sponsorsjson = $http.get('js/sponsors.json');
    $q.all([faqjson,teamjson,sponsorsjson]).then(function(allpromises){
        $scope.rules = allpromises[0].data;
        $scope.teamdata = allpromises[1].data;
        $scope.sponsors = allpromises[2].data;
    });

});

$(window).load(function() {
    "use strict";
    $(".loader").delay(500).fadeOut();
    $("#mask").delay(1000).fadeOut("slow");

    $(function() {
        var fr = new FilmRoll({
            container: '#film_roll',
            height: 210
        });
    });

    $(".item img").fadeIn(500);

    // clone image
    $('.item img').each(function(){
        var el = $(this);
        el.css({"position":"absolute"}).wrap("
").clone().addClass('img_grayscale').css({"position":"absolute","z-index":"998","opacity":"0"}).insertBefore(el).queue(function(){
            var el = $(this);
            el.parent().css({"width":this.width,"height":this.height});
            el.dequeue();
        });
        this.src = grayscale(this.src);
    });

    // Fade image
    $('.item img').mouseover(function(){
        $(this).parent().find('img:first').stop().animate({opacity:1}, 1000);
    })
    $('.img_grayscale').mouseout(function(){
        $(this).stop().animate({opacity:0}, 1000);
    });

});

// Grayscale w canvas method
function grayscale(src){
    var canvas = document.createElement('canvas');
    var ctx = canvas.getContext('2d');
    var imgObj = new Image();
    imgObj.src = src;
    canvas.width = imgObj.width;
    canvas.height = imgObj.height;
    ctx.drawImage(imgObj, 0, 0);
    var imgPixels = ctx.getImageData(0, 0, canvas.width, canvas.height);
    for(var y = 0; y < imgPixels.height; y++){
        for(var x = 0; x < imgPixels.width; x++){
            var i = (y * 4) * imgPixels.width + x * 4;
            var avg = (imgPixels.data[i] + imgPixels.data[i + 1] + imgPixels.data[i + 2]) / 3;
            imgPixels.data[i] = avg;
            imgPixels.data[i + 1] = avg;
            imgPixels.data[i + 2] = avg;
        }
    }
    ctx.putImageData(imgPixels, 0, 0, 0, 0, imgPixels.width, imgPixels.height);
    return canvas.toDataURL();
}
<|endoftext|>


<commit_before>import _ from 'underscore';
import Backbone from 'backbone';

function toModel(view) {
  return { view: view };
}

function toModels(views) {
  if (_.isArray(views)) {
    return _.map(views, toModel);
  } else {
    return toModel(views);
  }
}

const ListView = Backbone.View.extend({
  constructor() {
    this.views = new Backbone.Collection();
    this._views = [];

    this.listenTo(this.views, {
      add    : this._onViewCollectionAdd,
      remove : this._onViewCollectionRemove,
      reset  : this._onViewCollectionReset,
      sort   : this._onViewCollectionSort
    });

    Backbone.View.apply(this, arguments);
  },

  _onViewCollectionAdd(model, collection, options) {
    var view = model.get('view');
    var index = this.views.indexOf(model);
    this._addView(view, index, options);
  },

  _onViewCollectionRemove(model, collection, options) {
    var view = model.get('view');
    var index = _.indexOf(this._views, view);
    this._removeView(view, index, options);
  },

  _onViewCollectionReset(collection, options) {
    // to be implemented...
  },

  _onViewCollectionSort() {
    // to be implemented...
  },

  _addView(view, index, options) {
    if (index === this._views.length) {
      this._views.push(view);
      this._appendEl(this.$el);
    } else if (index === 0) {
      this._views.unshift(view);
      this._prependEl(this.$el);
    } else {
      this._views.splice(index, 0, view);
      this._insertElAfter(view.$el, this._views[index - 1].$el);
    }
    view.render();
    this.trigger('add', view, this, options);
    view.on('all', this._onViewEvent, this);
  },

  _removeView(view, index, options) {
    this._views.splice(index, 1);
    view.remove();
    this.trigger('remove', view, this, options);
    view.off('all', this._onViewEvent, this);
  },

  _onViewEvent() {
    this.trigger.apply(this, arguments);
  },

  _appendEl($el) {
    this.$el.append($el);
  },

  _prependEl($el) {
    this.$el.prepend($el);
  },

  _insertElAfter($el, $target) {
    $el.insertAfter($target);
  }
});

_.each([
  'set',
  'reset',
  'add',
  'push',
  'pop',
  'shift',
  'unshift'
], function(method) {
  ListView.prototype[method] = function(views, options) {
    this.views[method](toModels(views), options);
    return views;
  };
});

export default ListView;
<commit_msg>Fix lint<commit_after>import _ from 'underscore';
import Backbone from 'backbone';

function toModel(view) {
  return {view};
}

function toModels(views) {
  if (_.isArray(views)) {
    return _.map(views, toModel);
  } else {
    return toModel(views);
  }
}

const ListView = Backbone.View.extend({
  constructor() {
    this.views = new Backbone.Collection();
    this._views = [];

    this.listenTo(this.views, {
      add: this._onViewCollectionAdd,
      remove: this._onViewCollectionRemove,
      reset: this._onViewCollectionReset,
      sort: this._onViewCollectionSort
    });

    Backbone.View.apply(this, arguments);
  },

  _onViewCollectionAdd(model, collection, options) {
    var view = model.get('view');
    var index = this.views.indexOf(model);
    this._addView(view, index, options);
  },

  _onViewCollectionRemove(model, collection, options) {
    var view = model.get('view');
    var index = _.indexOf(this._views, view);
    this._removeView(view, index, options);
  },

  _onViewCollectionReset(collection, options) {
    // to be implemented...
  },

  _onViewCollectionSort() {
    // to be implemented...
  },

  _addView(view, index, options) {
    if (index === this._views.length) {
      this._views.push(view);
      this._appendEl(this.$el);
    } else if (index === 0) {
      this._views.unshift(view);
      this._prependEl(this.$el);
    } else {
      this._views.splice(index, 0, view);
      this._insertElAfter(view.$el, this._views[index - 1].$el);
    }
    view.render();
    this.trigger('add', view, this, options);
    view.on('all', this._onViewEvent, this);
  },

  _removeView(view, index, options) {
    this._views.splice(index, 1);
    view.remove();
    this.trigger('remove', view, this, options);
    view.off('all', this._onViewEvent, this);
  },

  _onViewEvent() {
    this.trigger.apply(this, arguments);
  },

  _appendEl($el) {
    this.$el.append($el);
  },

  _prependEl($el) {
    this.$el.prepend($el);
  },

  _insertElAfter($el, $target) {
    $el.insertAfter($target);
  }
});

_.each([
  'set',
  'reset',
  'add',
  'push',
  'pop',
  'shift',
  'unshift'
], function(method) {
  ListView.prototype[method] = function(views, options) {
    this.views[method](toModels(views), options);
    return views;
  };
});

export default ListView;
<|endoftext|>


<commit_before>const ee = require('ee')
const {map, switchMap, toArray} = require('rx/operators')
const {calculateIndex} = require('sepal/ee/optical/indexes')
const imageFactory = require('sepal/ee/imageFactory')
const {concat, defer, of, zip} = require('rx')
const _ = require('lodash')
const recipeRef = require('sepal/ee/recipeRef')

const NORMALIZE_MAX_PIXELS = 1e6

const classify =
    ({
        model: {
            inputImagery: {images: imageRecipes},
            legend,
            trainingData: {dataSets},
            auxiliaryImagery = [],
            classifier: classifierConfig,
            scale
        }
    },
    {selection: selectedBands} = {selection: []}
    ) => {
        const normalizeImage = image => {
            const minMax = image.reduceRegion({
                reducer: ee.Reducer.minMax(),
                scale: scale || 30,
                bestEffort: true,
                maxPixels: NORMALIZE_MAX_PIXELS
            })
            return ee.Image(ee.Image(
                image.bandNames().iterate((bandName, acc) => {
                    bandName = ee.String(bandName)
                    const min = minMax.getNumber(bandName.cat('_min'))
                    const max = minMax.getNumber(bandName.cat('_max'))
                    const band = image.select(bandName)
                    const scaled = ee.Image(ee.Algorithms.If(
                        min.eq(max),
                        band.divide(max),
                        unitScale(band, min, max)
                    ))
                    return ee.Image(acc).addBands(scaled)
                }, ee.Image([]))
            )
                .clip(image.geometry())
                .copyProperties(image, image.propertyNames()))
        }

        const recipeTrainingData$ = zip(...dataSets
            .filter(({type}) => type === 'RECIPE')
            .map(({recipe}) =>
                recipeRef({id: recipe}).getRecipe$().pipe(
                    switchMap(recipe => recipe.getTrainingData$())
                ))
        ).pipe(
            map(trainingDataSets => ee.FeatureCollection(trainingDataSets).flatten())
        )
        const referenceData = dataSets
            .filter(({type}) => type !== 'RECIPE')
            .map(({referenceData}) => referenceData)
            .flat()
        const toFeature = plot => {
            return ee.Feature(ee.Geometry.Point([plot['x'], plot['y']]), {'class': plot['class']})
        }
        const referenceDataCollection = ee.FeatureCollection(referenceData.map(toFeature))
        const imageToClassify$ = defer(() => zip(
            ...imageRecipes.map(recipe => toImage$(recipe, auxiliaryImagery))
        )).pipe(
            map(images => ee.Image(images)),
            map(imageToClassify => classifierConfig.type === 'SVM' ? normalizeImage(imageToClassify) : imageToClassify)
        )

        const getTrainingData$ = bands => {
            return imageToClassify$.pipe(
                switchMap(imageToClassify =>
                    concat(
                        recipeTrainingData$,
                        of(referenceDataCollection).pipe(
                            map(referenceDataCollection => {
                                return imageToClassify.sampleRegions({
                                    collection: referenceDataCollection.filterBounds(imageToClassify.geometry()),
                                    properties: ['class'],
                                    scale: scale || 1
                                })
                            })
                        )
                    ).pipe(
                        toArray(),
                        map(trainingDataSets =>
                            ee.FeatureCollection(trainingDataSets)
                                .flatten()
                                .set('band_order', bands || imageToClassify.bandNames())
                        )
                    )
                )
            )
        }

        const classify = (trainingData, imageToClassify) => {
            const classifier = classifierConfig.type === 'DECISION_TREE'
                ? createDecisionTreeClassifier(classifierConfig.decisionTree)
                : createClassifier(classifierConfig)
                    .train(trainingData, 'class')
                    .setOutputMode('CLASSIFICATION')
            return imageToClassify
                .classify(classifier)
                .uint8()
                .rename(['class'])
        }

        const createRegression = (trainingData, imageToClassify) => {
            const classifier = createClassifier(classifierConfig)
                .train(trainingData, 'class')
                .setOutputMode('REGRESSION')
            return imageToClassify
                .classify(classifier)
                .float()
                .rename(['regression'])
        }

        const calculateClassProbability = (trainingData, imageToClassify, classification) => {
            const probabilities = calculateProbabilities(trainingData, imageToClassify)
            const multipliers = ee.Image(legend.entries.map(({value}) =>
                ee.Image(1)
                    .where(classification.neq(value), 0)
                    .rename(`probability_${value}`)
            ))
            return probabilities
                .multiply(multipliers)
                .reduce(ee.Reducer.max())
                .rename('class_probability')
        }

        const calculateProbabilities = (trainingData, imageToClassify) => {
            const classifier = createClassifier(classifierConfig)
                .train(trainingData, 'class')
                .setOutputMode('MULTIPROBABILITY')
            const probabilityArrayImage = imageToClassify
                .classify(classifier)
                .multiply(100)
                .uint8()
            const classes = ee.List(classifier.explain().get('classes'))
            const indexes = ee.List.sequence(0, classes.size().subtract(1))
            const bandNames = legend.entries.map(({value}) => `probability_${value}`)
            return ee.Image(
                classes.zip(indexes).iterate((valueIndex, acc) => {
                    const value = ee.List(valueIndex).getNumber(0).int()
                    const index = ee.List(valueIndex).getNumber(1).int()
                    return ee.Image(acc).addBands(
                        probabilityArrayImage.arrayGet(index)
                            .rename(ee.String('probability_').cat(value.format()))
                    )
                }, ee.Image([]))
            ).addBands(ee.Image(bandNames.map(bandName => ee.Image(0).clip(imageToClassify.geometry()).rename(bandName)))).select(bandNames)
        }

        const classifyImage = (imageToClassify, selectedBands, trainingData) => {
            const bands = []
            if (selectedBands.includes('class') || selectedBands.includes('class_probability')) {
                const classification = classify(trainingData, imageToClassify)
                if (selectedBands.includes('class')) {
                    bands.push(classification)
                }
                if (selectedBands.includes('class_probability')) {
                    const classProbability = calculateClassProbability(trainingData, imageToClassify, classification)
                    bands.push(classProbability)
                }
            }
            if (selectedBands.includes('regression')) {
                const regression = createRegression(trainingData, imageToClassify)
                bands.push(regression)
            }
            const probabilityBands = selectedBands
                .filter(bandName => bandName.startsWith('probability_'))
            if (probabilityBands.length) {
                const probabilities = calculateProbabilities(trainingData, imageToClassify)
                    .select(probabilityBands)
                bands.push(probabilities)
            }
            return ee.Image(bands.flat())
        }

        return {
            getTrainingData$,
            getImage$() {
                return imageToClassify$.pipe(
                    switchMap(imageToClassify => {
                        return getTrainingData$(imageToClassify.bandNames()).pipe(
                            map(trainingData => classifyImage(imageToClassify, selectedBands, trainingData))
                        )
                    })
                )
            },
            getVisParams$() {
                const sortedLegend = _.sortBy(legend.entries, 'value')
                const min = sortedLegend[0].value
                const max = _.last(sortedLegend).value
                const legendPalette = sortedLegend.map(({color}) => color)
                const probabilityPalette = '#000000,#480000,#710101,#BA0000,#FF0000,#FFA500,#FFFF00,#79C900,#006400'
                if (selectedBands.includes('class')) {
                    return of({bands: 'class', min, max, palette: legendPalette})
                } else if (selectedBands.includes('regression')) {
                    return of({bands: 'regression', min, max, palette: legendPalette})
                } else if (selectedBands.includes('class_probability')) {
                    return of({bands: 'class_probability', min: 0, max: 100, palette: probabilityPalette})
                } else {
                    const probabilityBands = selectedBands
                        .filter(bandName => bandName.startsWith('probability_'))
                    if (probabilityBands.length) {
                        return of({bands: probabilityBands[0], min: 0, max: 100, palette: probabilityPalette})
                    } else {
                        throw new Error(`Expected selected bands to contain class, regression, class_probability, or probability_*: ${JSON.stringify(selectedBands)}`)
                    }
                }
            },
            getGeometry$() {
                return imageToClassify$().pipe(
                    map(imageToClassify => imageToClassify.geometry(scale))
                )
            },
            classifyImage(image, bands, trainingData) {
                if (imageRecipes.length > 1) {
                    throw new Error('This recipe contains more than one inputImage. Cannot classify it')
                }
                const covariates = addCovariates(image, imageRecipes[0].bandSetSpecs, auxiliaryImagery)
                const imageWithCovariates = image.addBands(covariates, null, true)
                return classifyImage(imageWithCovariates, bands, trainingData)
            }
        }
    }

const toImage$ = (recipe, auxiliaryImagery) =>
    imageFactory(recipe).getImage$().pipe(
        map(image => addCovariates(image, recipe.bandSetSpecs, auxiliaryImagery))
    )

const addCovariates = (image, bandSetSpecs, auxiliaryImagery) => {
    return ee.Image(
        bandSetSpecs.map(({type, included, operation}) => {
            switch (type) {
            case 'IMAGE_BANDS':
                return image.selectExisting(included).addBands(
                    calculateIndexes(
                        image.select(
                            image.bandNames().filter(ee.Filter(
                                ee.Filter.inList('item', included).not()
                            ))
                        ),
                        included
                    ), null, true
                )
            case 'PAIR_WISE_EXPRESSION':
                return combinePairwise(image, included, operation)
            case 'INDEXES':
                return calculateIndexes(image, included)
            }
        })
    ).addBands(
        auxiliaryImagery.length && getAuxiliaryImagery(auxiliaryImagery)
    ).updateMask(
        image.mask().reduce(ee.Reducer.max())
    )

}

const getAuxiliaryImagery = auxiliaryImagery =>
    ee.Image(
        auxiliaryImagery.map(type => {
            switch (type) {
            case 'LATITUDE':
                return createLatitudeImage()
            case 'TERRAIN':
                return createTerrainImage()
            case 'WATER':
                return createSurfaceWaterImage()
            }
        })
    )

const createLatitudeImage = () =>
    ee.Image.pixelLonLat().select('latitude').float()

const createTerrainImage = () => {
    const elevation = ee.Image('USGS/SRTMGL1_003')
    const topography = ee.Algorithms.Terrain(elevation)
    const aspectRad = topography.select(['aspect']).multiply(ee.Number(Math.PI).divide(180))
    const eastness = aspectRad.sin().rename(['eastness']).float()
    const northness = aspectRad.cos().rename(['northness']).float()
    return topography.select(['elevation', 'slope', 'aspect'])
        .addBands(eastness)
        .addBands(northness)
}

const createSurfaceWaterImage = () => {
    const water = ee.Image('JRC/GSW1_0/GlobalSurfaceWater').unmask()
    const transitions = [
        'no_change',
        'permanent',
        'new_permanent',
        'lost_permanent',
        'seasonal',
        'new_seasonal',
        'lost_seasonal',
        'seasonal_to_permanent',
        'permanent_to_seasonal',
        'ephemeral_permanent',
        'ephemeral_seasonal'
    ]
    const transitionMasks = ee.Image(
        transitions.map((transition, i) =>
            water.select('transition').eq(i).rename(`water_${transition}`))
    )

    return water
        .select(
            ['occurrence', 'change_abs', 'change_norm', 'seasonality', 'max_extent'],
            ['water_occurrence', 'water_change_abs', 'water_change_norm', 'water_seasonality', 'water_max_extent']
        )
        .addBands(transitionMasks)
}

const combinePairwise = (image, bands, operation) => {
    const operations = {
        RATIO: 'b1 / b2',
        NORMALIZED_DIFFERENCE: '(b1 - b2) / (b1 + b2)',
        DIFFERENCE: 'b1 - b2',
        DISTANCE: 'atan2(b1, b2) / PI',
        ANGLE: 'hypot(b1, b2)'
    }
    return image
        .selectExisting(bands)
        .combinePairwise(operations[operation], `_${operation.toLowerCase()}`)
}

const calculateIndexes = (image, indexNames) =>
    ee.Image(
        indexNames.map(indexName => calculateIndex(image, indexName)
        )
    )

const createDecisionTreeClassifier = decisionTree => {
    try {
        decisionTree = JSON.parse(decisionTree)
        // eslint-disable-next-line no-empty
    } catch (e) {
    } // Probably a single decision tree string
    if (_.isArray(decisionTree)) {
        return ee.Classifier.decisionTreeEnsemble(decisionTree)
    } else if (_.isString(decisionTree)) {
        return ee.Classifier.decisionTree(decisionTree)
    } else {
        throw new Error('Decision tree must either be a JSON array with decision trees or a single decision tree string')
    }
}

const createClassifier = ({type, ...config}) => {
    const randomForest = ({numberOfTrees, variablesPerSplit, minLeafPopulation, bagFraction, maxNodes, seed}) =>
        ee.Classifier.smileRandomForest({
            numberOfTrees: toInt(numberOfTrees),
            variablesPerSplit: toInt(variablesPerSplit),
            minLeafPopulation: toInt(minLeafPopulation),
            bagFraction: toFloat(bagFraction),
            maxNodes: toInt(maxNodes),
            seed: toInt(seed)
        })

    const cart = ({minLeafPopulation, maxNodes}) =>
        ee.Classifier.smileCart({
            minLeafPopulation: toInt(minLeafPopulation),
            maxNodes: toInt(maxNodes)
        })

    const naiveBayes = ({lambda}) =>
        ee.Classifier.smileNaiveBayes({lambda: toFloat(lambda)})

    const svm = ({decisionProcedure, svmType, kernelType, shrinking, degree, gamma, coef0, cost, nu, oneClass}) =>
        ee.Classifier.libsvm({
            decisionProcedure,
            svmType,
            kernelType,
            shrinking,
            degree: kernelType === 'POLY' ? toInt(degree) : null,
            gamma: ['POLY', 'RBF', 'SIGMOID'].includes(kernelType) ? toFloat(gamma) : null,
            coef0: ['POLY', 'SIGMOID'].includes(kernelType) ? toFloat(coef0) : null,
            cost: svmType === 'C_SVC' ? toFloat(cost) : null,
            nu: svmType === 'NU_SVC' ? toFloat(nu) : null,
            oneClass: svmType === 'ONE_CLASS' ? toInt(oneClass) : null
        })

    const minimumDistance = ({metric}) =>
        ee.Classifier.minimumDistance({metric})

    switch (type) {
    case 'RANDOM_FOREST':
        return randomForest(config)
    case 'CART':
        return cart(config)
    case 'NAIVE_BAYES':
        return naiveBayes(config)
    case 'SVM':
        return svm(config)
    case 'MINIMUM_DISTANCE':
        return minimumDistance(config)
    }
}

const toInt = input => {
    input = _.isString(input) ? input : _.toString(input)
    const parsed = parseInt(input)
    return _.isFinite(parsed) ? parsed : null
}

const toFloat = input => {
    input = _.isString(input) ? input : _.toString(input)
    const parsed = parseFloat(input)
    return _.isFinite(parsed) ? parsed : null
}

const unitScale = (image, low, high) =>
    image
        .subtract(low)
        .divide(ee.Number(high).subtract(low))
        .clamp(0, 1)

module.exports = classify
<commit_msg>Prevented normalized image from being clamped.<commit_after>const ee = require('ee')
const {map, switchMap, toArray} = require('rx/operators')
const {calculateIndex} = require('sepal/ee/optical/indexes')
const imageFactory = require('sepal/ee/imageFactory')
const {concat, defer, of, zip} = require('rx')
const _ = require('lodash')
const recipeRef = require('sepal/ee/recipeRef')

const NORMALIZE_MAX_PIXELS = 1e6

const classify =
    ({
        model: {
            inputImagery: {images: imageRecipes},
            legend,
            trainingData: {dataSets},
            auxiliaryImagery = [],
            classifier: classifierConfig,
            scale
        }
    },
    {selection: selectedBands} = {selection: []}
    ) => {
        const normalizeImage = image => {
            const minMax = image.reduceRegion({
                reducer: ee.Reducer.minMax(),
                scale: scale || 30,
                bestEffort: true,
                maxPixels: NORMALIZE_MAX_PIXELS
            })
            return ee.Image(ee.Image(
                image.bandNames().iterate((bandName, acc) => {
                    bandName = ee.String(bandName)
                    const min = minMax.getNumber(bandName.cat('_min'))
                    const max = minMax.getNumber(bandName.cat('_max'))
                    const band = image.select(bandName)
                    const scaled = ee.Image(ee.Algorithms.If(
                        min.eq(max),
                        band.divide(max),
                        unitScale(band, min, max)
                    ))
                    return ee.Image(acc).addBands(scaled)
                }, ee.Image([]))
            )
                .clip(image.geometry())
                .copyProperties(image, image.propertyNames()))
        }

        const recipeTrainingData$ = zip(...dataSets
            .filter(({type}) => type === 'RECIPE')
            .map(({recipe}) =>
                recipeRef({id: recipe}).getRecipe$().pipe(
                    switchMap(recipe => recipe.getTrainingData$())
                ))
        ).pipe(
            map(trainingDataSets => ee.FeatureCollection(trainingDataSets).flatten())
        )
        const referenceData = dataSets
            .filter(({type}) => type !== 'RECIPE')
            .map(({referenceData}) => referenceData)
            .flat()
        const toFeature = plot => {
            return ee.Feature(ee.Geometry.Point([plot['x'], plot['y']]), {'class': plot['class']})
        }
        const referenceDataCollection = ee.FeatureCollection(referenceData.map(toFeature))
        const imageToClassify$ = defer(() => zip(
            ...imageRecipes.map(recipe => toImage$(recipe, auxiliaryImagery))
        )).pipe(
            map(images => ee.Image(images)),
            map(imageToClassify => classifierConfig.type === 'SVM' ? normalizeImage(imageToClassify) : imageToClassify)
        )

        const getTrainingData$ = bands => {
            return imageToClassify$.pipe(
                switchMap(imageToClassify =>
                    concat(
                        recipeTrainingData$,
                        of(referenceDataCollection).pipe(
                            map(referenceDataCollection => {
                                return imageToClassify.sampleRegions({
                                    collection: referenceDataCollection.filterBounds(imageToClassify.geometry()),
                                    properties: ['class'],
                                    scale: scale || 1
                                })
                            })
                        )
                    ).pipe(
                        toArray(),
                        map(trainingDataSets =>
                            ee.FeatureCollection(trainingDataSets)
                                .flatten()
                                .set('band_order', bands || imageToClassify.bandNames())
                        )
                    )
                )
            )
        }

        const classify = (trainingData, imageToClassify) => {
            const classifier = classifierConfig.type === 'DECISION_TREE'
                ? createDecisionTreeClassifier(classifierConfig.decisionTree)
                : createClassifier(classifierConfig)
                    .train(trainingData, 'class')
                    .setOutputMode('CLASSIFICATION')
            return imageToClassify
                .classify(classifier)
                .uint8()
                .rename(['class'])
        }

        const createRegression = (trainingData, imageToClassify) => {
            const classifier = createClassifier(classifierConfig)
                .train(trainingData, 'class')
                .setOutputMode('REGRESSION')
            return imageToClassify
                .classify(classifier)
                .float()
                .rename(['regression'])
        }

        const calculateClassProbability = (trainingData, imageToClassify, classification) => {
            const probabilities = calculateProbabilities(trainingData, imageToClassify)
            const multipliers = ee.Image(legend.entries.map(({value}) =>
                ee.Image(1)
                    .where(classification.neq(value), 0)
                    .rename(`probability_${value}`)
            ))
            return probabilities
                .multiply(multipliers)
                .reduce(ee.Reducer.max())
                .rename('class_probability')
        }

        const calculateProbabilities = (trainingData, imageToClassify) => {
            const classifier = createClassifier(classifierConfig)
                .train(trainingData, 'class')
                .setOutputMode('MULTIPROBABILITY')
            const probabilityArrayImage = imageToClassify
                .classify(classifier)
                .multiply(100)
                .uint8()
            const classes = ee.List(classifier.explain().get('classes'))
            const indexes = ee.List.sequence(0, classes.size().subtract(1))
            const bandNames = legend.entries.map(({value}) => `probability_${value}`)
            return ee.Image(
                classes.zip(indexes).iterate((valueIndex, acc) => {
                    const value = ee.List(valueIndex).getNumber(0).int()
                    const index = ee.List(valueIndex).getNumber(1).int()
                    return ee.Image(acc).addBands(
                        probabilityArrayImage.arrayGet(index)
                            .rename(ee.String('probability_').cat(value.format()))
                    )
                }, ee.Image([]))
            ).addBands(ee.Image(bandNames.map(bandName => ee.Image(0).clip(imageToClassify.geometry()).rename(bandName)))).select(bandNames)
        }

        const classifyImage = (imageToClassify, selectedBands, trainingData) => {
            const bands = []
            if (selectedBands.includes('class') || selectedBands.includes('class_probability')) {
                const classification = classify(trainingData, imageToClassify)
                if (selectedBands.includes('class')) {
                    bands.push(classification)
                }
                if (selectedBands.includes('class_probability')) {
                    const classProbability = calculateClassProbability(trainingData, imageToClassify, classification)
                    bands.push(classProbability)
                }
            }
            if (selectedBands.includes('regression')) {
                const regression = createRegression(trainingData, imageToClassify)
                bands.push(regression)
            }
            const probabilityBands = selectedBands
                .filter(bandName => bandName.startsWith('probability_'))
            if (probabilityBands.length) {
                const probabilities = calculateProbabilities(trainingData, imageToClassify)
                    .select(probabilityBands)
                bands.push(probabilities)
            }
            return ee.Image(bands.flat())
        }

        return {
            getTrainingData$,
            getImage$() {
                return imageToClassify$.pipe(
                    switchMap(imageToClassify => {
                        return getTrainingData$(imageToClassify.bandNames()).pipe(
                            map(trainingData => classifyImage(imageToClassify, selectedBands, trainingData))
                        )
                    })
                )
            },
            getVisParams$() {
                const sortedLegend = _.sortBy(legend.entries, 'value')
                const min = sortedLegend[0].value
                const max = _.last(sortedLegend).value
                const legendPalette = sortedLegend.map(({color}) => color)
                const probabilityPalette = '#000000,#480000,#710101,#BA0000,#FF0000,#FFA500,#FFFF00,#79C900,#006400'
                if (selectedBands.includes('class')) {
                    return of({bands: 'class', min, max, palette: legendPalette})
                } else if (selectedBands.includes('regression')) {
                    return of({bands: 'regression', min, max, palette: legendPalette})
                } else if (selectedBands.includes('class_probability')) {
                    return of({bands: 'class_probability', min: 0, max: 100, palette: probabilityPalette})
                } else {
                    const probabilityBands = selectedBands
                        .filter(bandName => bandName.startsWith('probability_'))
                    if (probabilityBands.length) {
                        return of({bands: probabilityBands[0], min: 0, max: 100, palette: probabilityPalette})
                    } else {
                        throw new Error(`Expected selected bands to contain class, regression, class_probability, or probability_*: ${JSON.stringify(selectedBands)}`)
                    }
                }
            },
            getGeometry$() {
                return imageToClassify$().pipe(
                    map(imageToClassify => imageToClassify.geometry(scale))
                )
            },
            classifyImage(image, bands, trainingData) {
                if (imageRecipes.length > 1) {
                    throw new Error('This recipe contains more than one inputImage. Cannot classify it')
                }
                const covariates = addCovariates(image, imageRecipes[0].bandSetSpecs, auxiliaryImagery)
                const imageWithCovariates = image.addBands(covariates, null, true)
                return classifyImage(imageWithCovariates, bands, trainingData)
            }
        }
    }

const toImage$ = (recipe, auxiliaryImagery) =>
    imageFactory(recipe).getImage$().pipe(
        map(image => addCovariates(image, recipe.bandSetSpecs, auxiliaryImagery))
    )

const addCovariates = (image, bandSetSpecs, auxiliaryImagery) => {
    return ee.Image(
        bandSetSpecs.map(({type, included, operation}) => {
            switch (type) {
            case 'IMAGE_BANDS':
                return image.selectExisting(included).addBands(
                    calculateIndexes(
                        image.select(
                            image.bandNames().filter(ee.Filter(
                                ee.Filter.inList('item', included).not()
                            ))
                        ),
                        included
                    ), null, true
                )
            case 'PAIR_WISE_EXPRESSION':
                return combinePairwise(image, included, operation)
            case 'INDEXES':
                return calculateIndexes(image, included)
            }
        })
    ).addBands(
        auxiliaryImagery.length && getAuxiliaryImagery(auxiliaryImagery)
    ).updateMask(
        image.mask().reduce(ee.Reducer.max())
    )

}

const getAuxiliaryImagery = auxiliaryImagery =>
    ee.Image(
        auxiliaryImagery.map(type => {
            switch (type) {
            case 'LATITUDE':
                return createLatitudeImage()
            case 'TERRAIN':
                return createTerrainImage()
            case 'WATER':
                return createSurfaceWaterImage()
            }
        })
    )

const createLatitudeImage = () =>
    ee.Image.pixelLonLat().select('latitude').float()

const createTerrainImage = () => {
    const elevation = ee.Image('USGS/SRTMGL1_003')
    const topography = ee.Algorithms.Terrain(elevation)
    const aspectRad = topography.select(['aspect']).multiply(ee.Number(Math.PI).divide(180))
    const eastness = aspectRad.sin().rename(['eastness']).float()
    const northness = aspectRad.cos().rename(['northness']).float()
    return topography.select(['elevation', 'slope', 'aspect'])
        .addBands(eastness)
        .addBands(northness)
}

const createSurfaceWaterImage = () => {
    const water = ee.Image('JRC/GSW1_0/GlobalSurfaceWater').unmask()
    const transitions = [
        'no_change',
        'permanent',
        'new_permanent',
        'lost_permanent',
        'seasonal',
        'new_seasonal',
        'lost_seasonal',
        'seasonal_to_permanent',
        'permanent_to_seasonal',
        'ephemeral_permanent',
        'ephemeral_seasonal'
    ]
    const transitionMasks = ee.Image(
        transitions.map((transition, i) =>
            water.select('transition').eq(i).rename(`water_${transition}`))
    )

    return water
        .select(
            ['occurrence', 'change_abs', 'change_norm', 'seasonality', 'max_extent'],
            ['water_occurrence', 'water_change_abs', 'water_change_norm', 'water_seasonality', 'water_max_extent']
        )
        .addBands(transitionMasks)
}

const combinePairwise = (image, bands, operation) => {
    const operations = {
        RATIO: 'b1 / b2',
        NORMALIZED_DIFFERENCE: '(b1 - b2) / (b1 + b2)',
        DIFFERENCE: 'b1 - b2',
        DISTANCE: 'atan2(b1, b2) / PI',
        ANGLE: 'hypot(b1, b2)'
    }
    return image
        .selectExisting(bands)
        .combinePairwise(operations[operation], `_${operation.toLowerCase()}`)
}

const calculateIndexes = (image, indexNames) =>
    ee.Image(
        indexNames.map(indexName => calculateIndex(image, indexName)
        )
    )

const createDecisionTreeClassifier = decisionTree => {
    try {
        decisionTree = JSON.parse(decisionTree)
        // eslint-disable-next-line no-empty
    } catch (e) {
    } // Probably a single decision tree string
    if (_.isArray(decisionTree)) {
        return ee.Classifier.decisionTreeEnsemble(decisionTree)
    } else if (_.isString(decisionTree)) {
        return ee.Classifier.decisionTree(decisionTree)
    } else {
        throw new Error('Decision tree must either be a JSON array with decision trees or a single decision tree string')
    }
}

const createClassifier = ({type, ...config}) => {
    const randomForest = ({numberOfTrees, variablesPerSplit, minLeafPopulation, bagFraction, maxNodes, seed}) =>
        ee.Classifier.smileRandomForest({
            numberOfTrees: toInt(numberOfTrees),
            variablesPerSplit: toInt(variablesPerSplit),
            minLeafPopulation: toInt(minLeafPopulation),
            bagFraction: toFloat(bagFraction),
            maxNodes: toInt(maxNodes),
            seed: toInt(seed)
        })

    const cart = ({minLeafPopulation, maxNodes}) =>
        ee.Classifier.smileCart({
            minLeafPopulation: toInt(minLeafPopulation),
            maxNodes: toInt(maxNodes)
        })

    const naiveBayes = ({lambda}) =>
        ee.Classifier.smileNaiveBayes({lambda: toFloat(lambda)})

    const svm = ({decisionProcedure, svmType, kernelType, shrinking, degree, gamma, coef0, cost, nu, oneClass}) =>
        ee.Classifier.libsvm({
            decisionProcedure,
            svmType,
            kernelType,
            shrinking,
            degree: kernelType === 'POLY' ? toInt(degree) : null,
            gamma: ['POLY', 'RBF', 'SIGMOID'].includes(kernelType) ? toFloat(gamma) : null,
            coef0: ['POLY', 'SIGMOID'].includes(kernelType) ? toFloat(coef0) : null,
            cost: svmType === 'C_SVC' ? toFloat(cost) : null,
            nu: svmType === 'NU_SVC' ? toFloat(nu) : null,
            oneClass: svmType === 'ONE_CLASS' ? toInt(oneClass) : null
        })

    const minimumDistance = ({metric}) =>
        ee.Classifier.minimumDistance({metric})

    switch (type) {
    case 'RANDOM_FOREST':
        return randomForest(config)
    case 'CART':
        return cart(config)
    case 'NAIVE_BAYES':
        return naiveBayes(config)
    case 'SVM':
        return svm(config)
    case 'MINIMUM_DISTANCE':
        return minimumDistance(config)
    }
}

const toInt = input => {
    input = _.isString(input) ? input : _.toString(input)
    const parsed = parseInt(input)
    return _.isFinite(parsed) ? parsed : null
}

const toFloat = input => {
    input = _.isString(input) ? input : _.toString(input)
    const parsed = parseFloat(input)
    return _.isFinite(parsed) ? parsed : null
}

const unitScale = (image, low, high) =>
    image
        .subtract(low)
        .divide(ee.Number(high).subtract(low))

module.exports = classify
<|endoftext|>