<commit_before>'use strict';

const Promise = require('bluebird');

const fs = Promise.promisifyAll(require('fs'));
const path = require('path');
const url = require('url');
const express = require('express');
const spawn = require('child_process').spawn;
const mkdirp = Promise.promisifyAll(require('mkdirp'));
const semver = require('semver');
const SourceNode = require('source-map').SourceNode;
const SourceMapConsumer = require('source-map').SourceMapConsumer;
const webpack = require('webpack');
const WebpackDevServer = require('webpack-dev-server');
const getReactNativeExternals = require('./getReactNativeExternals');
const waitForSocket = require('socket-retry-connect').waitForSocket;
const fetch = require('./fetch');

const ENTRY_JS = 'global.React = require("react-native");';
const SOURCEMAP_REGEX = /\/\/[#@] sourceMappingURL=([^\s'"]*)/;

const makeExternals = require('../packager/lib/makeExternals');

function staticImageTransform(context, request, callback) {
  if (/^image!/.test(request)) {
    return callback(null, JSON.stringify({
      uri: request.replace('image!', ''),
      isStatic: true,
    }));
  }
  callback();
}
function md5(content){
  var crypto = require('crypto');
  var md5 = crypto.createHash('md5');
  md5.update(content);
  return md5.digest('hex');
}
function makeHotConfig(webpackConfig) {
  webpackConfig.plugins = webpackConfig.plugins || [];
  webpackConfig.plugins.unshift(
    new webpack.BannerPlugin(
      'if (typeof navigator.userAgent === \'undefined\') {\n' +
      '  throw new Error(\'Hot module replacement only works with RCTWebSocketExecutor; use Cmd + D, "Debug in Chrome"\')' +
      '}\n',
      {raw: true, entryOnly: true}
    )
  );
}

class Server {

  /**
   * Create a new server with the following options:
   * @param {String}  hostname
   * @param {Number}  port
   * @param {Number}  packagerPort
   * @param {Number}  webpackPort
   * @param {Boolean} android       Enable Android support
   * @param {Boolean} ios           Enable iOS support
   * @param {String}  androidEntry
   * @param {String}  iosEntry
   * @param {Object}  webpackConfig The webpack config to use for webpack-dev-server
   * @param {Boolean} hot           Enable hot module replacement
   *
   * @constructor
   * @param {Object} options
   */
  constructor(options) {
    this.hostname = options.hostname;
    this.port = options.port;
    this.packagerPort = options.packagerPort;
    this.webpackPort = options.webpackPort;
    this.entries = {
      android: options.androidEntry,
      ios: options.iosEntry,
    };
    this.platforms = options.platforms;
    this.projectRoots = options.projectRoots;
    this.assetRoots = options.assetRoots;
    this.resetCache = !!options.resetCache;
    this.hot = !!options.hot;
    this.webpackConfig = options.webpackConfig;

    this.dev = !!options.dev;

    this.makerExt = new makeExternals({
        projectRoots :options.projectRoots,
        assetRoots :options.assetRoots,
        dev: this.dev
    })
    // Check for local react-native.
    try {
      require.resolve('react-native');
    } catch (err) {
      throw new Error('Could not find react-native. Try `npm install react-native`.');
    }

    // Construct resource URLs up-front
    this.webpackBaseURL = url.format({
      protocol: 'http',
      hostname: this.hostname,
      port: this.webpackPort,
    });
    this.packagerBaseURL = url.format({
      protocol: 'http',
      hostname: this.hostname,
      port: this.packagerPort,
    });
  }

  start() {
    // Create a stub entry module for the RN packager.
    ///Users/baidu/projects/react-native-webpack-server/Examples/BabelES6/node_modules/react-native-webpack-server/_entry

    this.entryDir = path.resolve(__dirname, '../packager/entry');
    //return this._writeEntryFiles().then(() => {
      // Re-throw error if server fails to handle a promise rejection
      process.on('unhandledRejection', reason => {
        throw reason;
      });

      // Make sure to clean up when the process is terminated.
      process.on('exit', () => this.handleProcessExit());
      process.on('SIGINT', () => {
        this.handleProcessExit();
        process.exit(1);
      });

      // Construct a promise waiting for both servers to fully start...
      const readyPromise = this._startWebpackDevServer().then(() =>
        // We need to start this one second to prevent races between the bundlers.
        this._startPackageServer()
      );

      // Setup the express server
      this.server = express();
      this.server.use((req, res, next) => {
        // Wait until packager has started before serving requests
        readyPromise
          .then(() => next())
          .catch(err => next(err));
      });
      this.server.get('/*.bundle', this.handleBundleRequest.bind(this));
      this.server.get('/*.map', this.handleMapRequest.bind(this));
      this.server.use((err, req, res, next) => {
        console.error(err.stack);
        next(err);
      });

      const listenPromise = new Promise(resolve => {
        this.httpServer = this.server.listen(this.port, () => {
          console.log(`Server listening at http://${this.hostname}:${this.port}`);
          resolve();
        });
        // Disable any kind of automatic timeout behavior on incoming connections.
        this.httpServer.timeout = 0;
      });
      return Promise.all([listenPromise, readyPromise]);
    //});
  }

  stop() {
    this.handleProcessExit();
    this.httpServer && this.httpServer.close();
    this.webpackServer && this.webpackServer.close();
  }

  handleBundleRequest(req, res, next) {
    const parsedUrl = url.parse(req.url, /* parse query */ true);
    const urlSearch = parsedUrl.search;
    const platform = parsedUrl.query.platform;

    // Forward URL params to RN packager
    const reactCodeURL = this._getReactCodeURL(platform) + urlSearch;
    const appCodeURL = this._getAppCodeURL(platform);
    //console.log(reactCodeURL,' ' ,appCodeURL);
    //reactCodeURL:  http://localhost:8081/index.ios.bundle?platform=ios&dev=false&minify=false
    //appCodeURL: http://localhost:8082/index.ios.js
    Promise.props({
      reactCode: fetch(reactCodeURL),
      appCode: fetch(appCodeURL),
    }).then(r =>
      this._createBundleCode(r.reactCode, r.appCode, urlSearch, platform)
    ).then(bundleCode => {
      res.set('Content-Type', 'application/javascript');
      res.send(bundleCode);
    }).catch(err => next(err));
  }

  handleMapRequest(req, res, next) {
    const parsedUrl = url.parse(req.url, /* parse query */ true);
    const urlSearch = parsedUrl.search;
    const platform = parsedUrl.query.platform;

    // Forward URL params to RN packager
    const reactCodeURL = this._getReactCodeURL(platform) + urlSearch;
    const reactMapURL = this._getReactMapURL(platform) + urlSearch;
    const appCodeURL = this._getAppCodeURL(platform);
    const appMapURL = this._getAppMapURL(platform);

    Promise.props({
      reactCode: fetch(reactCodeURL),
      reactMap: fetch(reactMapURL),
      appCode: fetch(appCodeURL),
      appMap: fetch(appMapURL),
    }).then(r =>
      this._createBundleMap(r.reactCode, r.reactMap, r.appCode, r.appMap)
    ).then(bundleMap => {
      res.set('Content-Type', 'application/json');
      res.send(bundleMap);
    }).catch(err => next(err));
  }

  handleProcessExit() {
    // Clean up temp files
    const entryDir = this.entryDir;

    if (fs.existsSync(entryDir)) {
      fs.readdirSync(entryDir).forEach(file => {
        fs.unlinkSync(path.join(entryDir, file));
      });
      fs.rmdirSync(entryDir);
    }

    // Kill the package server
    if (this.packageServer) {
      this.packageServer.kill();
    }
  }

  _getReactCodeURL(platform) {
    return url.resolve(this.packagerBaseURL, `index.${platform}.bundle`);
  }

  _getReactMapURL(platform) {
    return url.resolve(this.packagerBaseURL, `index.${platform}.map`);
  }

  _getAppCodeURL(platform) {
    return url.resolve(this.webpackBaseURL, `${this.entries[platform]}.js`);
  }

  _getAppMapURL(platform) {
    return url.resolve(this.webpackBaseURL, `${this.entries[platform]}.js.map`);
  }

  _createBundleCode(reactCode, appCode, urlSearch, platform) {
    reactCode = reactCode.replace(SOURCEMAP_REGEX, '');
    console.log('reactCode md5:' + md5(reactCode));
    console.log('appCode md5:' + md5(appCode));
    appCode = appCode.replace(SOURCEMAP_REGEX, '');
    return reactCode + appCode + `//# sourceMappingURL=/${this.entries[platform]}.map${urlSearch}`;
  }

  _createBundleMap(reactCode, reactMap, appCode, appMap) {
    const node = new SourceNode();

    node.add(SourceNode.fromStringWithSourceMap(
      reactCode,
      new SourceMapConsumer(reactMap)
    ));
    node.add(SourceNode.fromStringWithSourceMap(
      appCode,
      new SourceMapConsumer(appMap)
    ));

    return node.join('').toStringWithSourceMap().map.toString();
  }

  _writeEntryFiles() {
    const source = ENTRY_JS + '\n';
    return mkdirp.mkdirpAsync(this.entryDir).then(() => Promise.all([
      fs.writeFileAsync(path.resolve(this.entryDir, 'index.android.js'), source, 'utf8'),
      fs.writeFileAsync(path.resolve(this.entryDir, 'index.ios.js'), source, 'utf8'),
    ]));
  }

  _startPackageServer() {
    /**
     * Starting the server is neither fast nor completely reliable we end up
     * hitting its public api over http periodically so we must wait for it to
     * be actually ready.
     */
    return new Promise((resolve, reject) => {
      // Easier to just shell out to the packager than use the JS API.
      // XXX: Uses the node only invocation so we don't have to deal with bash
      // as well... Fixes issues where server cannot be killed cleanly.
      const cmd = 'node';
      const reactNativeVersion = require('react-native/package.json').version;
      const script =
        semver.lt(reactNativeVersion, '0.14.0')
          ? ['./node_modules/react-native/packager/packager.js']
          : ['./node_modules/react-native/local-cli/cli.js', 'start'];
      const args = script.concat([
        '--root', this.entryDir,
        '--port', this.packagerPort,
      ]).concat(
        this.projectRoots ? ['--projectRoots', this.projectRoots.join(',')] : []
      ).concat(
        this.assetRoots ? ['--assetRoots', this.assetRoots.join(',')] : []
      ).concat(
        this.resetCache ? '--reset-cache' : []
      );
      const opts = {stdio: 'inherit'};
      this.packageServer = spawn(cmd, args, opts);

      function handleError(err) {
        reject(err);
      }

      this.packageServer.on('error', handleError);

      // waitForSocket retries the port every 250ms. Let's give the
      // React Native server up to 30 seconds to come online. watchman
      // can be slow to ramp up, especially on CI machines.
      waitForSocket({ port: this.packagerPort, tries: 120 }, err => {
        console.log('react-native packager ready...');
        this.packageServer.removeListener('error', handleError);
        if (err) {
          handleError(err);
          return;
        }
        resolve();
      });
    });
  }

  _startWebpackDevServer() {
    const webpackConfig = this.webpackConfig;
    const hot = this.hot;
    console.log(this.projectRoots,this.assetRoots,this.platforms,process.cwd())
    // return getReactNativeExternals({
    //   projectRoots: this.projectRoots,
    //   assetRoots: this.assetRoots,
    //   platforms: this.platforms,
    // }).then(reactNativeExternals => {

    return this.makerExt.start().then((reactNativeExternals)=>{

      // Coerce externals into an array, without clobbering it
      webpackConfig.externals = Array.isArray(webpackConfig.externals)
        ? webpackConfig.externals
        : [(webpackConfig.externals || {})];

      // Inject react native externals
      webpackConfig.externals.push(reactNativeExternals);

      // Transform static image references
      webpackConfig.externals.push(staticImageTransform);

      // By default webpack uses webpack://[resource-path]?[hash] in the source
      // map which is handled by its dev server. Use absolute path instead so
      // React Native's exception manager can load the source maps.
      webpackConfig.output = webpackConfig.output || {};
      if (!webpackConfig.output.devtoolModuleFilenameTemplate) {
        webpackConfig.output.devtoolModuleFilenameTemplate = '[absolute-resource-path]';
      }

      // Update webpack config for hot mode.
      if (hot) {
        makeHotConfig(webpackConfig);
      }

      // Plug into webpack compilation to extract webpack dependency tree.
      // Any React Native externals from the application source need to be
      // require()'d in the RN packager's entry file. This allows for RN
      // modules that aren't part of the main 'react-native' dependency tree
      // to be included in the generated bundle (e.g. AdSupportIOS).
      const compiler = webpack(webpackConfig);

      const compilerPromise = new Promise(resolve => {
        compiler.plugin('done', () => {
          // Write out the RN packager's entry file
          this._writeEntryFiles().then(resolve);
        });
      });

      this.webpackServer = new WebpackDevServer(compiler, {
        hot: hot,
        headers: {
          'Access-Control-Allow-Origin': '*',
        },
        stats: {colors: true, chunkModules: false},
      });

      const serverPromise = new Promise(resolve => {
        this.webpackServer.listen(this.webpackPort, this.hostname, () => {
          console.log('Webpack dev server listening at ', this.webpackBaseURL);
          resolve();
        });
        // Disable any kind of automatic timeout behavior on incoming connections.
        this.webpackServer.timeout = 0;
      });

      // Ensure that both the server is up and the compiler's entry
      // file has been written for the React Native packager.
      return Promise.all([compilerPromise, serverPromise]);
    });
  }

}

module.exports = Server;
<commit_msg> remove get externales<commit_after>'use strict';

const Promise = require('bluebird');

const fs = Promise.promisifyAll(require('fs'));
const path = require('path');
const url = require('url');
const express = require('express');
const spawn = require('child_process').spawn;
const mkdirp = Promise.promisifyAll(require('mkdirp'));
const semver = require('semver');
const SourceNode = require('source-map').SourceNode;
const SourceMapConsumer = require('source-map').SourceMapConsumer;
const webpack = require('webpack');
const WebpackDevServer = require('webpack-dev-server');
const waitForSocket = require('socket-retry-connect').waitForSocket;
const fetch = require('./fetch');

const ENTRY_JS = 'global.React = require("react-native");';
const SOURCEMAP_REGEX = /\/\/[#@] sourceMappingURL=([^\s'"]*)/;

const makeExternals = require('../packager/lib/makeExternals');

function staticImageTransform(context, request, callback) {
  if (/^image!/.test(request)) {
    return callback(null, JSON.stringify({
      uri: request.replace('image!', ''),
      isStatic: true,
    }));
  }
  callback();
}
function md5(content){
  var crypto = require('crypto');
  var md5 = crypto.createHash('md5');
  md5.update(content);
  return md5.digest('hex');
}
function makeHotConfig(webpackConfig) {
  webpackConfig.plugins = webpackConfig.plugins || [];
  webpackConfig.plugins.unshift(
    new webpack.BannerPlugin(
      'if (typeof navigator.userAgent === \'undefined\') {\n' +
      '  throw new Error(\'Hot module replacement only works with RCTWebSocketExecutor; use Cmd + D, "Debug in Chrome"\')' +
      '}\n',
      {raw: true, entryOnly: true}
    )
  );
}

class Server {

  /**
   * Create a new server with the following options:
   * @param {String}  hostname
   * @param {Number}  port
   * @param {Number}  packagerPort
   * @param {Number}  webpackPort
   * @param {Boolean} android       Enable Android support
   * @param {Boolean} ios           Enable iOS support
   * @param {String}  androidEntry
   * @param {String}  iosEntry
   * @param {Object}  webpackConfig The webpack config to use for webpack-dev-server
   * @param {Boolean} hot           Enable hot module replacement
   *
   * @constructor
   * @param {Object} options
   */
  constructor(options) {
    this.hostname = options.hostname;
    this.port = options.port;
    this.packagerPort = options.packagerPort;
    this.webpackPort = options.webpackPort;
    this.entries = {
      android: options.androidEntry,
      ios: options.iosEntry,
    };
    this.platforms = options.platforms;
    this.projectRoots = options.projectRoots;
    this.assetRoots = options.assetRoots;
    this.resetCache = !!options.resetCache;
    this.hot = !!options.hot;
    this.webpackConfig = options.webpackConfig;

    this.dev = !!options.dev;

    this.makerExt = new makeExternals({
        projectRoots :options.projectRoots,
        assetRoots :options.assetRoots,
        dev: this.dev
    })
    // Check for local react-native.
    try {
      require.resolve('react-native');
    } catch (err) {
      throw new Error('Could not find react-native. Try `npm install react-native`.');
    }

    // Construct resource URLs up-front
    this.webpackBaseURL = url.format({
      protocol: 'http',
      hostname: this.hostname,
      port: this.webpackPort,
    });
    this.packagerBaseURL = url.format({
      protocol: 'http',
      hostname: this.hostname,
      port: this.packagerPort,
    });
  }

  start() {
    // Create a stub entry module for the RN packager.
    ///Users/baidu/projects/react-native-webpack-server/Examples/BabelES6/node_modules/react-native-webpack-server/_entry

    this.entryDir = path.resolve(__dirname, '../packager/entry');
    //return this._writeEntryFiles().then(() => {
      // Re-throw error if server fails to handle a promise rejection
      process.on('unhandledRejection', reason => {
        throw reason;
      });

      // Make sure to clean up when the process is terminated.
      process.on('exit', () => this.handleProcessExit());
      process.on('SIGINT', () => {
        this.handleProcessExit();
        process.exit(1);
      });

      // Construct a promise waiting for both servers to fully start...
      const readyPromise = this._startWebpackDevServer().then(() =>
        // We need to start this one second to prevent races between the bundlers.
        this._startPackageServer()
      );

      // Setup the express server
      this.server = express();
      this.server.use((req, res, next) => {
        // Wait until packager has started before serving requests
        readyPromise
          .then(() => next())
          .catch(err => next(err));
      });
      this.server.get('/*.bundle', this.handleBundleRequest.bind(this));
      this.server.get('/*.map', this.handleMapRequest.bind(this));
      this.server.use((err, req, res, next) => {
        console.error(err.stack);
        next(err);
      });

      const listenPromise = new Promise(resolve => {
        this.httpServer = this.server.listen(this.port, () => {
          console.log(`Server listening at http://${this.hostname}:${this.port}`);
          resolve();
        });
        // Disable any kind of automatic timeout behavior on incoming connections.
        this.httpServer.timeout = 0;
      });
      return Promise.all([listenPromise, readyPromise]);
    //});
  }

  stop() {
    this.handleProcessExit();
    this.httpServer && this.httpServer.close();
    this.webpackServer && this.webpackServer.close();
  }

  handleBundleRequest(req, res, next) {
    const parsedUrl = url.parse(req.url, /* parse query */ true);
    const urlSearch = parsedUrl.search;
    const platform = parsedUrl.query.platform;

    // Forward URL params to RN packager
    const reactCodeURL = this._getReactCodeURL(platform) + urlSearch;
    const appCodeURL = this._getAppCodeURL(platform);
    //console.log(reactCodeURL,' ' ,appCodeURL);
    //reactCodeURL:  http://localhost:8081/index.ios.bundle?platform=ios&dev=false&minify=false
    //appCodeURL: http://localhost:8082/index.ios.js
    Promise.props({
      reactCode: fetch(reactCodeURL),
      appCode: fetch(appCodeURL),
    }).then(r =>
      this._createBundleCode(r.reactCode, r.appCode, urlSearch, platform)
    ).then(bundleCode => {
      res.set('Content-Type', 'application/javascript');
      res.send(bundleCode);
    }).catch(err => next(err));
  }

  handleMapRequest(req, res, next) {
    const parsedUrl = url.parse(req.url, /* parse query */ true);
    const urlSearch = parsedUrl.search;
    const platform = parsedUrl.query.platform;

    // Forward URL params to RN packager
    const reactCodeURL = this._getReactCodeURL(platform) + urlSearch;
    const reactMapURL = this._getReactMapURL(platform) + urlSearch;
    const appCodeURL = this._getAppCodeURL(platform);
    const appMapURL = this._getAppMapURL(platform);

    Promise.props({
      reactCode: fetch(reactCodeURL),
      reactMap: fetch(reactMapURL),
      appCode: fetch(appCodeURL),
      appMap: fetch(appMapURL),
    }).then(r =>
      this._createBundleMap(r.reactCode, r.reactMap, r.appCode, r.appMap)
    ).then(bundleMap => {
      res.set('Content-Type', 'application/json');
      res.send(bundleMap);
    }).catch(err => next(err));
  }

  handleProcessExit() {
    // Clean up temp files
    const entryDir = this.entryDir;

    if (fs.existsSync(entryDir)) {
      fs.readdirSync(entryDir).forEach(file => {
        fs.unlinkSync(path.join(entryDir, file));
      });
      fs.rmdirSync(entryDir);
    }

    // Kill the package server
    if (this.packageServer) {
      this.packageServer.kill();
    }
  }

  _getReactCodeURL(platform) {
    return url.resolve(this.packagerBaseURL, `index.${platform}.bundle`);
  }

  _getReactMapURL(platform) {
    return url.resolve(this.packagerBaseURL, `index.${platform}.map`);
  }

  _getAppCodeURL(platform) {
    return url.resolve(this.webpackBaseURL, `${this.entries[platform]}.js`);
  }

  _getAppMapURL(platform) {
    return url.resolve(this.webpackBaseURL, `${this.entries[platform]}.js.map`);
  }

  _createBundleCode(reactCode, appCode, urlSearch, platform) {
    reactCode = reactCode.replace(SOURCEMAP_REGEX, '');
    console.log('reactCode md5:' + md5(reactCode));
    console.log('appCode md5:' + md5(appCode));
    appCode = appCode.replace(SOURCEMAP_REGEX, '');
    return reactCode + appCode + `//# sourceMappingURL=/${this.entries[platform]}.map${urlSearch}`;
  }

  _createBundleMap(reactCode, reactMap, appCode, appMap) {
    const node = new SourceNode();

    node.add(SourceNode.fromStringWithSourceMap(
      reactCode,
      new SourceMapConsumer(reactMap)
    ));
    node.add(SourceNode.fromStringWithSourceMap(
      appCode,
      new SourceMapConsumer(appMap)
    ));

    return node.join('').toStringWithSourceMap().map.toString();
  }

  _writeEntryFiles() {
    const source = ENTRY_JS + '\n';
    return mkdirp.mkdirpAsync(this.entryDir).then(() => Promise.all([
      fs.writeFileAsync(path.resolve(this.entryDir, 'index.android.js'), source, 'utf8'),
      fs.writeFileAsync(path.resolve(this.entryDir, 'index.ios.js'), source, 'utf8'),
    ]));
  }

  _startPackageServer() {
    /**
     * Starting the server is neither fast nor completely reliable we end up
     * hitting its public api over http periodically so we must wait for it to
     * be actually ready.
     */
    return new Promise((resolve, reject) => {
      // Easier to just shell out to the packager than use the JS API.
      // XXX: Uses the node only invocation so we don't have to deal with bash
      // as well... Fixes issues where server cannot be killed cleanly.
      const cmd = 'node';
      const reactNativeVersion = require('react-native/package.json').version;
      const script =
        semver.lt(reactNativeVersion, '0.14.0')
          ? ['./node_modules/react-native/packager/packager.js']
          : ['./node_modules/react-native/local-cli/cli.js', 'start'];
      const args = script.concat([
        '--root', this.entryDir,
        '--port', this.packagerPort,
      ]).concat(
        this.projectRoots ? ['--projectRoots', this.projectRoots.join(',')] : []
      ).concat(
        this.assetRoots ? ['--assetRoots', this.assetRoots.join(',')] : []
      ).concat(
        this.resetCache ? '--reset-cache' : []
      );
      const opts = {stdio: 'inherit'};
      this.packageServer = spawn(cmd, args, opts);

      function handleError(err) {
        reject(err);
      }

      this.packageServer.on('error', handleError);

      // waitForSocket retries the port every 250ms. Let's give the
      // React Native server up to 30 seconds to come online. watchman
      // can be slow to ramp up, especially on CI machines.
      waitForSocket({ port: this.packagerPort, tries: 120 }, err => {
        console.log('react-native packager ready...');
        this.packageServer.removeListener('error', handleError);
        if (err) {
          handleError(err);
          return;
        }
        resolve();
      });
    });
  }

  _startWebpackDevServer() {
    const webpackConfig = this.webpackConfig;
    const hot = this.hot;
    console.log(this.projectRoots,this.assetRoots,this.platforms,process.cwd())
    // return getReactNativeExternals({
    //   projectRoots: this.projectRoots,
    //   assetRoots: this.assetRoots,
    //   platforms: this.platforms,
    // }).then(reactNativeExternals => {

    return this.makerExt.start().then((reactNativeExternals)=>{

      // Coerce externals into an array, without clobbering it
      webpackConfig.externals = Array.isArray(webpackConfig.externals)
        ? webpackConfig.externals
        : [(webpackConfig.externals || {})];

      // Inject react native externals
      webpackConfig.externals.push(reactNativeExternals);

      // Transform static image references
      webpackConfig.externals.push(staticImageTransform);

      // By default webpack uses webpack://[resource-path]?[hash] in the source
      // map which is handled by its dev server. Use absolute path instead so
      // React Native's exception manager can load the source maps.
      webpackConfig.output = webpackConfig.output || {};
      if (!webpackConfig.output.devtoolModuleFilenameTemplate) {
        webpackConfig.output.devtoolModuleFilenameTemplate = '[absolute-resource-path]';
      }

      // Update webpack config for hot mode.
      if (hot) {
        makeHotConfig(webpackConfig);
      }

      // Plug into webpack compilation to extract webpack dependency tree.
      // Any React Native externals from the application source need to be
      // require()'d in the RN packager's entry file. This allows for RN
      // modules that aren't part of the main 'react-native' dependency tree
      // to be included in the generated bundle (e.g. AdSupportIOS).
      const compiler = webpack(webpackConfig);

      const compilerPromise = new Promise(resolve => {
        compiler.plugin('done', () => {
          // Write out the RN packager's entry file
          this._writeEntryFiles().then(resolve);
        });
      });

      this.webpackServer = new WebpackDevServer(compiler, {
        hot: hot,
        headers: {
          'Access-Control-Allow-Origin': '*',
        },
        stats: {colors: true, chunkModules: false},
      });

      const serverPromise = new Promise(resolve => {
        this.webpackServer.listen(this.webpackPort, this.hostname, () => {
          console.log('Webpack dev server listening at ', this.webpackBaseURL);
          resolve();
        });
        // Disable any kind of automatic timeout behavior on incoming connections.
        this.webpackServer.timeout = 0;
      });

      // Ensure that both the server is up and the compiler's entry
      // file has been written for the React Native packager.
      return Promise.all([compilerPromise, serverPromise]);
    });
  }

}

module.exports = Server;
<|endoftext|>


<commit_before>import React, { Component } from 'react';
import { Text } from 'react-native';

import styles from '../../styles/styles';
import InPageImage from '../inPageImage';
import AnimalText from '../animalText';
import AnimalTemplate from '../animalTemplate';

const IMAGES = [
  require('../../images/animals/pandaCervena/01.jpg'),
  require('../../images/animals/pandaCervena/02.jpg'),
  require('../../images/animals/pandaCervena/03.jpg'),
];

const THUMBNAILS = [
  require('../../images/animals/pandaCervena/01-thumb.jpg'),
  require('../../images/animals/pandaCervena/02-thumb.jpg'),
  require('../../images/animals/pandaCervena/03-thumb.jpg'),
];

var AnimalDetail = React.createClass({
  render() {
    return (
      <AnimalTemplate firstIndex={[0]} thumbnails={THUMBNAILS} images={IMAGES} navigator={this.props.navigator}>
        <AnimalText>
          Když se řekne panda, každý si jistě představí velkého roztomilého medvěda černobílé barvy, jak sedí a spokojeně okusuje bambus.
        </AnimalText>
        <AnimalText>
          Když se řekne panda červená, obrázek se absolutně změní. Místo toho vám na mysli vytane malé zvířátko s rošťáckým výrazem a dlouhatánským ocasem, které líně pospává vysoko v koruně stromu.
        </AnimalText>
        <AnimalText>
          Stejné jméno – a přitom takové rozdíly! Obě pandy sice patří do čeledi psotvárných, ale zatímco panda velká je medvědovitá šelma, panda červená se řadí k malým pandám – obě k sobě tedy mají stejně daleko jako třeba k psovi nebo mroži. Stejné jméno si vysloužily jen podobným způsobem života.
        </AnimalText>
      <InPageImage indexes={[1]} thumbnails={THUMBNAILS} images={IMAGES} navigator={this.props.navigator} />
        <AnimalText>
          Panda červená žije v horských lesích Nepálu, Indie, Myanmaru, Bhútánu a Číny, a to ve výškách i nad 4000 m n. m. Nejraději šplhá v korunách vysokých stromů a tráví tam většinu dne. Udržovat rovnováhu ve větvích jí pomáhá její dlouhý ocas. Za svítání či za soumraku, kdy je nejaktivnější, se pak dolů vypravuje především kvůli potravě. Ze stromu slézá hlavou dolů, což je pro zvíře její velikosti docela neobvyklé. Pomáhají jí v tom zpola zatažitelné drápy. Panda červená si ráda pochutná na ovoci, vejcích nebo drobných obratlovcích. Nejraději však má bambus, denně sní až čtyři kilogramy bambusových výhonků nebo 1,5 kilogramu bambusových listů. Manipulaci s bambusovými stébly jí usnadňuje kostěný výrůstek na zápěstí, fungující jako nepravý šestý prst.
        </AnimalText>
      <InPageImage indexes={[2]} thumbnails={THUMBNAILS} images={IMAGES} navigator={this.props.navigator} />
        <AnimalText>
          Pandy červené jsou samotáři, setkávají se jen v období páření. Své úmysly si sdělí snadno – prostě na sebe zapískají. Samice jsou březí čtyři měsíce, a poté rodí jedno až čtyři mláďata, o která se celý následující rok starají. Dožívají se až 14 let. Bohužel vysoká úmrtnost mláďat a další faktory vedou k tomu, že se pandy červené řadí mezi zranitelné druhy.
        </AnimalText>
        <AnimalText>
          V naší zoo chováme dvě pandy červené. Pandora se narodila v srpnu roku 2012 v norském Kristiansadu (stejně jako náš rosomák Ivan) a do Brna přijela v říjnu 2013. Huan pochází z rakouské Zoo Linz, narodil se v červnu 2013 a v Brně je od poloviny roku 2014.
        </AnimalText>
      </AnimalTemplate>
    );
  }
});

module.exports = AnimalDetail;
<commit_msg>Update pandaCervena.adult.js<commit_after>import React, { Component } from 'react';
import { Text } from 'react-native';

import styles from '../../styles/styles';
import InPageImage from '../inPageImage';
import AnimalText from '../animalText';
import AnimalTemplate from '../animalTemplate';

const IMAGES = [
  require('../../images/animals/pandaCervena/01.jpg'),
  require('../../images/animals/pandaCervena/02.jpg'),
  require('../../images/animals/pandaCervena/03.jpg'),
];

const THUMBNAILS = [
  require('../../images/animals/pandaCervena/01-thumb.jpg'),
  require('../../images/animals/pandaCervena/02-thumb.jpg'),
  require('../../images/animals/pandaCervena/03-thumb.jpg'),
];

var AnimalDetail = React.createClass({
  render() {
    return (
      <AnimalTemplate firstIndex={[0]} thumbnails={THUMBNAILS} images={IMAGES} navigator={this.props.navigator}>
        <AnimalText>
          Když se řekne panda, každý si jistě představí velkého roztomilého medvěda černobílé barvy, jak sedí a spokojeně okusuje bambus.
        </AnimalText>
        <AnimalText>
          Když se řekne panda červená, obrázek se absolutně změní. Místo toho vám na mysli vytane malé zvířátko s rošťáckým výrazem a dlouhatánským ocasem, které líně pospává vysoko v koruně stromu.
        </AnimalText>
        <AnimalText>
          Stejné jméno – a přitom takové rozdíly! Obě pandy sice patří do stejného podřádu psotvárných, ale zatímco panda velká je medvědovitá šelma, panda červená se řadí k malým pandám – obě k sobě tedy mají stejně daleko jako třeba k psovi nebo mroži. Stejné jméno si vysloužily jen podobným způsobem života.
        </AnimalText>
      <InPageImage indexes={[1]} thumbnails={THUMBNAILS} images={IMAGES} navigator={this.props.navigator} />
        <AnimalText>
          Panda červená žije v horských lesích východního Himálaje (Nepálu, Indie, Myanmaru, Bhútánu a Číny), a to ve výškách i nad 4000 m n. m. Nejraději šplhá v korunách vysokých stromů a tráví tam většinu dne. Udržovat rovnováhu ve větvích jí pomáhá její dlouhý ocas. Za svítání či za soumraku, kdy je nejaktivnější, se pak dolů vypravuje především kvůli potravě. Ze stromu slézá hlavou dolů, což je pro zvíře její velikosti docela neobvyklé. Pomáhají jí v tom zpola zatažitelné drápy. Panda červená si ráda pochutná na ovoci, vejcích nebo drobných obratlovcích. Nejraději však má bambus, denně sní až čtyři kilogramy bambusových výhonků nebo 1,5 kilogramu bambusových listů. Manipulaci s bambusovými stébly jí usnadňuje kostěný výrůstek na zápěstí, fungující jako nepravý šestý prst.
        </AnimalText>
      <InPageImage indexes={[2]} thumbnails={THUMBNAILS} images={IMAGES} navigator={this.props.navigator} />
        <AnimalText>
          Pandy červené jsou samotáři, setkávají se jen v období páření. Své úmysly si sdělí snadno – prostě na sebe zapískají. Samice jsou březí čtyři měsíce, a poté rodí jedno až čtyři mláďata, o která se celý následující rok starají. Dožívají se až 14 let. Bohužel vysoká úmrtnost mláďat a další faktory, jako je například kácení lesů a lov, vedou k tomu, že se pandy červené řadí mezi ohrožené. Za poslední dvě desetiletí klesla jejich populace o 50 % a tento trend se i nadále zhoršuje.
        </AnimalText>
        <AnimalText>
          V naší zoo chováme dvě pandy červené. Pandora se narodila v srpnu roku 2012 v norském Kristiansandu (stejně jako náš rosomák Ivan) a do Brna přijela v říjnu 2013. Huan pochází z rakouské Zoo Linz, narodil se v červnu 2013 a v Brně je od poloviny roku 2014.
        </AnimalText>
      </AnimalTemplate>
    );
  }
});

module.exports = AnimalDetail;
<|endoftext|>


<commit_before>const particles = Particles.init({

  // normal options
  selector: '#particles',
  maxParticles: 100,
  connectParticles: true,
  color: '#FFFFFF'
});
<commit_msg>Fixing particles for mobile and tablet<commit_after>const particles = Particles.init({

  // normal options
  selector: '#particles',
  maxParticles: 100,
  connectParticles: true,
  color: '#FFFFFF',

  responsive: [
    {
      breakpoint: 768,
      options: {
        maxParticles: 55
      }
    },
    {
      breakpoint: 375,
      options: {
        maxParticles: 30
      }
    }
  ]
});
<|endoftext|>


<commit_before>import React, { Component } from 'react';
import { Text, View, Image, TextInput, ListView, TouchableHighlight } from 'react-native';
import helpers from '../helpers/helpers.js';
import Recipe from './Recipe.ios.js';
import styles from '../styles.ios.js';
import data from '../helpers/data/chickenData.js';

export default class NutrientsComparison extends Component {
  constructor(props) {
    super(props);
  }

  selectRecipe(id, title, image) {
    helpers.recipes.getRecipe(id)
      .then( resp => {
        this.props.navigator.push({
          component: Recipe,
          passProps: { recipe: resp.data, food: title, image: image  }
        })
      })
  }

  goBack() {
    this.props.navigator.pop();
  }

  render() {
    const renderNutrients = (index) => {
      return Object.keys(this.props.results[index]).map((nutrient, i) => {
          console.log(this.props.results[index][nutrient]);
        return (
          <Text style = {styles.ingredientListText} key={i} >{nutrient}: {this.props.results[index][nutrient][0]} {this.props.results[index][nutrient][1]}</Text>
        )
      })
    };

    const compareNutrients = Object.keys(this.props.results[0]).map((nutrient, i) => {
      if (this.props.results[0][nutrient][0] > this.props.results[1][nutrient][0]) {
        return (
          <Text style = {[styles.ingredientListText, {color: 'green'}]} key={i}>{nutrient}: {Math.floor(Math.abs(this.props.results[0][nutrient][0] - this.props.results[1][nutrient][0]))} {this.props.results[1][nutrient][1]}</Text>
        )
      } else {
        return (
          <Text style = {[styles.ingredientListText, {color: 'red'}]} key={i}>{nutrient}: {Math.floor(Math.abs(this.props.results[0][nutrient][0] - this.props.results[1][nutrient][0]))} {this.props.results[1][nutrient][1]}</Text>
        )
      }
    })
    return (
      <View style={styles.resultsList}>
          <View style={styles.resultsTitle}>
            <TouchableHighlight style={styles.backButton} onPress={this.goBack.bind(this)}>
              <Image style={styles.backButtonImage} source={{uri: 'https://cdn0.iconfinder.com/data/icons/vector-basic-tab-bar-icons/48/back_button-128.png'}} />
            </TouchableHighlight>
            <Text style={styles.resultsTitleText}>Nutritional Comparison:</Text>
          </View>
        <View style={styles.compareNutrientsContainer}>
          <TouchableHighlight style={styles.compareRecipe} onPress={this.selectRecipe.bind(this, this.props.ids[0], this.props.titles[0], this.props.images[0])} >
            <View style={styles.compareRecipe} >
              <View style={styles.compareHeader}>
                <Text style={styles.compareRecipeTitle} >{this.props.titles[0]} </Text>
                <Image style={styles.resultsPicture} source={{uri: this.props.images[0]}} />
              </View>
              <View style={styles.compareRecipe}>{renderNutrients(0)}</View>
            </View>
          </TouchableHighlight>
          <View style={styles.compareRecipe} >
            <Text style={styles.compareRecipeTitle} > Comparison: </Text>
            <View style={styles.compareRecipe}>{compareNutrients}</View>
          </View>
          <TouchableHighlight style={styles.compareRecipe} onPress={this.selectRecipe.bind(this, this.props.ids[0], this.props.titles[0], this.props.images[0])}>
            <View style={styles.compareRecipe} >
              <View style={styles.compareHeader}>
                <Text style={styles.compareRecipeTitle} >{this.props.titles[1]} </Text>
                <Image style={styles.resultsPicture} source={{uri: this.props.images[1]}} />
              </View>
              <View style={styles.compareRecipe}>{renderNutrients(1)}</View>
            </View>
          </TouchableHighlight>
        </View>
      </View>
    )
  }
}<commit_msg>updated styling sheet on the nutrients comparison and flex box styling<commit_after>import React, { Component } from 'react';
import { Text, View, Image, TextInput, ListView, TouchableHighlight, ScrollView } from 'react-native';
import helpers from '../helpers/helpers.js';
import Recipe from './Recipe.ios.js';
import styles from '../styles.ios.js';
import data from '../helpers/data/chickenData.js';

export default class NutrientsComparison extends Component {
  constructor(props) {
    super(props);
  }

  selectRecipe(id, title, image) {
    helpers.recipes.getRecipe(id)
      .then( resp => {
        this.props.navigator.push({
          component: Recipe,
          passProps: { recipe: resp.data, food: title, image: image  }
        })
      })
  }

  goBack() {
    this.props.navigator.pop();
  }

  render() {
    const renderNutrients = (index) => {
      return Object.keys(this.props.results[index]).map((nutrient, i) => {
          console.log(this.props.results[index][nutrient]);
        return (
          <Text style = {styles.ingredientListText} key={i} >{nutrient}: {this.props.results[index][nutrient][0]} {this.props.results[index][nutrient][1]}</Text>
        )
      })
    };

    const compareNutrients = Object.keys(this.props.results[0]).map((nutrient, i) => {
      if (this.props.results[0][nutrient][0] > this.props.results[1][nutrient][0]) {
        return (
          <Text style = {[styles.ingredientListText, {color: '#458B00'}]} key={i}>⇦  {nutrient}: {Math.floor(Math.abs(this.props.results[0][nutrient][0] - this.props.results[1][nutrient][0]))} {this.props.results[1][nutrient][1]}</Text>
        )
      } else {
        return (
          <Text style = {[styles.ingredientListText, {color: '#B22222'}]} key={i}>{nutrient}: {Math.floor(Math.abs(this.props.results[0][nutrient][0] - this.props.results[1][nutrient][0]))} {this.props.results[1][nutrient][1]}  ⇨</Text>
        )
      }
    })
    return (
      <View style={styles.resultsList}>
        <View style={styles.resultsTitle}>
          <TouchableHighlight style={styles.backButton} onPress={this.goBack.bind(this)}>
            <Image style={styles.backButtonImage} source={{uri: 'https://cdn0.iconfinder.com/data/icons/vector-basic-tab-bar-icons/48/back_button-128.png'}} />
          </TouchableHighlight>
          <Text style={styles.compareRecipeText}>Nutritional Comparison:</Text>
        </View>
        <View style={styles.compareNutrientsContainer}>
          <View style={styles.compareNutrientsHeader} >
            <TouchableHighlight style={styles.compareHeader} onPress={this.selectRecipe.bind(this, this.props.ids[0], this.props.titles[0], this.props.images[0])} >
              <View style={[styles.compareHeader, {backgroundColor: '#3B5323'}]}>
                <Text style={styles.compareRecipeTitle} >{this.props.titles[0]} </Text>
                <Image style={styles.resultsPicture} source={{uri: this.props.images[0]}} />
              </View>
            </TouchableHighlight>
            <TouchableHighlight style={styles.compareHeader} onPress={this.selectRecipe.bind(this, this.props.ids[1], this.props.titles[1], this.props.images[1])} >
              <View style={[styles.compareHeader, {backgroundColor: '#E9967A'}]}>
                <Text style={styles.compareRecipeTitle} >{this.props.titles[1]} </Text>
                <Image style={styles.resultsPicture} source={{uri: this.props.images[1]}} />
              </View>
            </TouchableHighlight>
          </View>
          <View style={styles.compareNutrientsHeader} >
            <TouchableHighlight style={styles.compareRecipe} onPress={this.selectRecipe.bind(this, this.props.ids[0], this.props.titles[0], this.props.images[0])} >
              <View style={[styles.compareRecipe, {backgroundColor: '#3B5323'}]} >
                <ScrollView style={styles.compareRecipe}>{renderNutrients(0)}</ScrollView>
              </View>
            </TouchableHighlight>
            <View style={styles.compareRecipe} >
              <View style={styles.compareNutrients}>{compareNutrients}</View>
            </View>
            <TouchableHighlight style={styles.compareRecipe} onPress={this.selectRecipe.bind(this, this.props.ids[1], this.props.titles[1], this.props.images[1])}>
              <View style={[styles.compareRecipe, {backgroundColor: '#E9967A'}]} >
                <ScrollView style={styles.compareRecipe}>{renderNutrients(1)}</ScrollView>
              </View>
            </TouchableHighlight>
          </View>
        </View>
      </View>
    )
  }
}<|endoftext|>


<commit_before>//登录状态
module.exports = (req, res, createWebAPIRequest, request) => {
  const cookie = req.get("Cookie") ? req.get("Cookie") : "";
  createWebAPIRequest(
    "music.163.com",
    "/",
    "GET",
    {},
    cookie,
    (music_req, cookie) => {
      var userInfo = (/var GUser=([^;]+);/g).exec(music_req)[1];
      var bindInfo = (/var GBinds=([^;]+);/g).exec(music_req)[1];
      userInfo = eval(`(${userInfo})`);
      userInfo.userBind = eval(`(${bindInfo})`);
      userInfo.userBind.forEach((item) => {item.tokenJsonStr = JSON.parse(item.tokenJsonStr)});
      res.send(userInfo);
    },
    err => res.status(502).send("fetch error")
  );
};<commit_msg>fix login status query bug<commit_after>//登录状态
module.exports = (req, res, createWebAPIRequest, request) => {
  const cookie = req.get("Cookie") ? req.get("Cookie") : "";
  createWebAPIRequest(
    "music.163.com",
    "/",
    "GET",
    {},
    cookie,
    (music_req, cookie) => {
      try {
        var userInfo = (/var GUser=([^;]+);/g).exec(music_req)[1];
        var bindInfo = (/var GBinds=([^;]+);/g).exec(music_req)[1];
        userInfo = eval(`(${userInfo})`);
        userInfo.userBind = eval(`(${bindInfo})`);
        userInfo.userBind.forEach((item) => {item.tokenJsonStr = JSON.parse(item.tokenJsonStr)});
        res.send(userInfo);
      } catch (error) {
        res.status(502).send("fetch error");
      }
    },
    err => res.status(502).send("fetch error")
  );
};<|endoftext|>


<commit_before>import {getScale} from './scale';
import {Gradient} from 'vega-scenegraph';
import {scaleFraction} from 'vega-scale';

export default function(scale, p0, p1, count, group) {
  scale = getScale(scale, (group || this).context);

  var gradient = Gradient(p0, p1),
      stops = scale.domain(),
      min = stops[0],
      max = stops[stops.length-1],
      fraction = scaleFraction(scale, min, max);

  if (scale.ticks) {
    stops = scale.ticks(+count || 15);
    if (min !== stops[0]) stops.unshift(min);
    if (max !== stops[stops.length-1]) stops.push(max);
  }

  for (var i=0, n=stops.length; i<n; ++i) {
    gradient.stop(fraction(stops[i]), scale(stops[i]));
  }

  return gradient;
}
<commit_msg>Minor clean up, use peek.<commit_after>import {getScale} from './scale';
import {Gradient} from 'vega-scenegraph';
import {scaleFraction} from 'vega-scale';
import {peek} from 'vega-util';

export default function(scale, p0, p1, count, group) {
  scale = getScale(scale, (group || this).context);

  var gradient = Gradient(p0, p1),
      stops = scale.domain(),
      min = stops[0],
      max = peek(stops),
      fraction = scaleFraction(scale, min, max);

  if (scale.ticks) {
    stops = scale.ticks(+count || 15);
    if (min !== stops[0]) stops.unshift(min);
    if (max !== peek(stops)) stops.push(max);
  }

  for (var i=0, n=stops.length; i<n; ++i) {
    gradient.stop(fraction(stops[i]), scale(stops[i]));
  }

  return gradient;
}
<|endoftext|>


<commit_before>angular
    .module(
        'application',
        [
            'ngAnimate',
            'ngCookies',
            'ui.router',
            'ui.bootstrap',
            'application.home',
        ]
    )
    .config( function($urlRouterProvider, $stateProvider) {
        // Routes
        $urlRouterProvider.otherwise('/');
    })
    .controller (
        'ApplicationController',
        function ApplicationController($rootScope, $scope) {
            var vm = this;
        }
    )
;
<commit_msg>Remove not needed dependencies in the bootstrap file<commit_after>angular
    .module(
        'application',
        [
            'application.home',
        ]
    )
    .config( function($urlRouterProvider) {
        // Routes
        $urlRouterProvider.otherwise('/');
    })
    .controller (
        'ApplicationController',
        function ApplicationController($rootScope, $scope) {
            var vm = this;
        }
    )
;
<|endoftext|>


<commit_before>/* ========================================================================== */
/* uducada - dialog.js - https://github.com/m5n/uducada                       */
/* ========================================================================== */
/* Support for dialogs.  No references to JS or UI frameworks here!           */
/* ========================================================================== */

// TODO LATER: allow override of global default on a per-dialog level, or set them all on a per-dialog level only?
// TODO LATER: dialogs should be centered around some parent element; <body> by default.
// TODO LATER: provide replacements for alert (default focus = OK button),
//             confirm (default focus on OK button), and
//             prompt (where Enter triggers OK button)

/*global */
var uducada = uducada || {};
uducada.dialog = (function () {
    'use strict';

    var defaultButtonTextLookup,   // Map button type -> text.
        defaultCloseOnEscape,
        defaultDraggable,
        defaultResizable,
        defaultShowCloseIcon;

    // Trigger a specific button event on the dialog element, making the button
    // event type easily accessible.
    function triggerEvent(dialogElement, buttonEventType) {
        uducada.jsfwk.trigger(dialogElement, 'button:' + buttonEventType, [ buttonEventType ]);
    }

    // 'k1:v1,k2:v2,...' --> [ {type: k1, text: v1}, {type: k2, text: v2}, ... ]
    function parseButtonConfigString(str, errorOnMissingText) {
        var items,
            pair,
            ii,
            result = [];

        // Be careful, ''.split(',') === [''], so an array of length 1!
        items = (str && str.length > 0) ? str.split(',') : [];   // 'k1:v1,k2:v2,...' --> ['k1:v1','k2:v2',...]
        for (ii = 0; ii < items.length; ii += 1) {
            pair = items[ii].split(':');   // 'k1:v1' --> ['k1', 'v1']
            if (errorOnMissingText && pair.length < 2) {
                throw new Error('Text missing for button event type "' + pair[0] + '"');
            }

            result.push({
                type: pair[0],
                text: pair.length > 1 ? pair[1] : undefined
            });
        }
        return result;
    }

    function setDefaults() {
        var defaults,
            items,
            ii;

        // Gather options from the DOM.
        defaults = uducada.jsfwk.getInterpretedDataValue('body', 'dialog-defaults') || {};
        // Expects object with keys:
        // - buttonText: string of format 'ok:Ok,cancel:Cancel,...'
        // - closeOnEscape: true | false
        // - draggable: true | false
        // - resizable: true | false
        // - showCloseIcon: true | false

        defaultButtonTextLookup = {};
        items = parseButtonConfigString(defaults.buttonText || '', true);
        for (ii = 0; ii < items.length; ii += 1) {
            defaultButtonTextLookup[items[ii].type] = items[ii].text;
        }

        // Keyboard navigation is good, so the Escape key should close a dialog.
        // Note: since value can be false, don't use value || default here.
        defaultCloseOnEscape = undefined === defaults.closeOnEscape ? true : defaults.closeOnEscape;

        // Dialogs obstruct parts of the page, so it's good to allow users to
        // move a dialog out of the way.
        // Note: since value can be false, don't use value || default here.
        defaultDraggable = undefined === defaults.draggable ? true : defaults.draggable;

        // Dialogs obstruct parts of the page, so it's good to allow users to
        // resize a dialog as they see fit.
        // Note: since value can be false, don't use value || default here.
        defaultResizable = undefined === defaults.resizable ? true : defaults.resizable;

        // Having all dialog actions (buttons) in close proximity is better, so
        // by default, do not provide a close icon (the 'x') to close a dialog.
        // Note: since value can be false, don't use value || default here.
        defaultShowCloseIcon = defaults.showCloseIcon || false;
    }

    // Initialize a single dialog instance.
    // element: JS framework reference (not a DOM elt ref or a css selector)
    function initializeDialog(element) {
        var options,
            bb;

        // Gather options from the DOM.
        options = uducada.jsfwk.getInterpretedDataValues(element, [
            'buttons',
            'close-on-escape',
            'draggable',
            'modal',
            'resizable',
            'show-close-icon',
            'title',
            'width'
        ]);

        // Since this is a global init, never open dialogs on init.
        // If you need to open a dialog on init / page load, use the methods
        // your UI framework of choice provides.
        options.autoOpen = false;

        // Convert button config string to an actual object.
        options.buttons = parseButtonConfigString(options.buttons, false);
        // Add missing text if a default is set.  Complain if there is not.
        for (bb = 0; bb < options.buttons.length; bb += 1) {
            if (!options.buttons[bb].text) {
                options.buttons[bb].text = defaultButtonTextLookup[options.buttons[bb].type];
                if (!options.buttons[bb].text) {
                    throw new Error('Missing or no default text for button event type "' + options.buttons[bb].type + '"');
                }
            }
        }

        // Events that affect a dialog's visibility but are not triggered via
        // buttons need to be captured too, i.e. the Escape key and close icon.
        // Make those trigger the cancel button event.
        options.cancelButtonEventType = 'cancel';

        // Convert 'close-on-escape' to camel-case key and use default if needed.
        // Note: since value can be false, don't use value || default here.
        options.closeOnEscape = undefined === options['close-on-escape'] ? defaultCloseOnEscape : options['close-on-escape'];
        delete options['close-on-escape'];

        // Use default draggable value if needed.
        // Note: since value can be false, don't use value || default here.
        options.draggable = undefined === options.draggable ? defaultDraggable : options.draggable;

        // Button clicks fire custom events, so capture them.
        options.onClick = triggerEvent;

        // Use default resizable value if needed.
        // Note: since value can be false, don't use value || default here.
        options.resizable = undefined === options.resizable ? defaultResizable : options.resizable;

        // Convert 'show-close-icon' to camel-case key and use default value if needed.
        // Note: since value can be false, don't use value || default here.
        options.showCloseIcon = undefined === options['show-close-icon'] ? defaultShowCloseIcon : options['show-close-icon'];
        delete options['show-close-icon'];
        // For UI frameworks that do not have an option to hide the close icon,
        // a custom CSS class is used.  Frameworks that do support such an
        // option can ignore this option and just use options.showCloseIcon.
        options.hideCloseIconCssClass = 'uducada-hide-close-icon';

        // Initialize this dialog.
        uducada.uifwk.initDialog(element, options);
    }

    // Initialize default options.
    setDefaults();

    // Initialize all dialogs present in the markup, except those that indicate
    // to skip initialization.
    uducada.jsfwk.callFunctionForElementsIfDataValueIsNot('.dialog', 'skip-init', true, initializeDialog);

    // Public functions.
    return {
        init: initializeDialog
    };
}());
<commit_msg>Add keyboard navigation TODO item<commit_after>/* ========================================================================== */
/* uducada - dialog.js - https://github.com/m5n/uducada                       */
/* ========================================================================== */
/* Support for dialogs.  No references to JS or UI frameworks here!           */
/* ========================================================================== */

// TODO LATER: allow override of global default on a per-dialog level, or set them all on a per-dialog level only?
// TODO LATER: dialogs should be centered around some parent element; <body> by default.
// TODO LATER: provide replacements for alert (default focus = OK button),
//             confirm (default focus on OK button), and
//             prompt (where Enter triggers OK button)
// TODO: keyboard navigation

/*global */
var uducada = uducada || {};
uducada.dialog = (function () {
    'use strict';

    var defaultButtonTextLookup,   // Map button type -> text.
        defaultCloseOnEscape,
        defaultDraggable,
        defaultResizable,
        defaultShowCloseIcon;

    // Trigger a specific button event on the dialog element, making the button
    // event type easily accessible.
    function triggerEvent(dialogElement, buttonEventType) {
        uducada.jsfwk.trigger(dialogElement, 'button:' + buttonEventType, [ buttonEventType ]);
    }

    // 'k1:v1,k2:v2,...' --> [ {type: k1, text: v1}, {type: k2, text: v2}, ... ]
    function parseButtonConfigString(str, errorOnMissingText) {
        var items,
            pair,
            ii,
            result = [];

        // Be careful, ''.split(',') === [''], so an array of length 1!
        items = (str && str.length > 0) ? str.split(',') : [];   // 'k1:v1,k2:v2,...' --> ['k1:v1','k2:v2',...]
        for (ii = 0; ii < items.length; ii += 1) {
            pair = items[ii].split(':');   // 'k1:v1' --> ['k1', 'v1']
            if (errorOnMissingText && pair.length < 2) {
                throw new Error('Text missing for button event type "' + pair[0] + '"');
            }

            result.push({
                type: pair[0],
                text: pair.length > 1 ? pair[1] : undefined
            });
        }
        return result;
    }

    function setDefaults() {
        var defaults,
            items,
            ii;

        // Gather options from the DOM.
        defaults = uducada.jsfwk.getInterpretedDataValue('body', 'dialog-defaults') || {};
        // Expects object with keys:
        // - buttonText: string of format 'ok:Ok,cancel:Cancel,...'
        // - closeOnEscape: true | false
        // - draggable: true | false
        // - resizable: true | false
        // - showCloseIcon: true | false

        defaultButtonTextLookup = {};
        items = parseButtonConfigString(defaults.buttonText || '', true);
        for (ii = 0; ii < items.length; ii += 1) {
            defaultButtonTextLookup[items[ii].type] = items[ii].text;
        }

        // Keyboard navigation is good, so the Escape key should close a dialog.
        // Note: since value can be false, don't use value || default here.
        defaultCloseOnEscape = undefined === defaults.closeOnEscape ? true : defaults.closeOnEscape;

        // Dialogs obstruct parts of the page, so it's good to allow users to
        // move a dialog out of the way.
        // Note: since value can be false, don't use value || default here.
        defaultDraggable = undefined === defaults.draggable ? true : defaults.draggable;

        // Dialogs obstruct parts of the page, so it's good to allow users to
        // resize a dialog as they see fit.
        // Note: since value can be false, don't use value || default here.
        defaultResizable = undefined === defaults.resizable ? true : defaults.resizable;

        // Having all dialog actions (buttons) in close proximity is better, so
        // by default, do not provide a close icon (the 'x') to close a dialog.
        // Note: since value can be false, don't use value || default here.
        defaultShowCloseIcon = defaults.showCloseIcon || false;
    }

    // Initialize a single dialog instance.
    // element: JS framework reference (not a DOM elt ref or a css selector)
    function initializeDialog(element) {
        var options,
            bb;

        // Gather options from the DOM.
        options = uducada.jsfwk.getInterpretedDataValues(element, [
            'buttons',
            'close-on-escape',
            'draggable',
            'modal',
            'resizable',
            'show-close-icon',
            'title',
            'width'
        ]);

        // Since this is a global init, never open dialogs on init.
        // If you need to open a dialog on init / page load, use the methods
        // your UI framework of choice provides.
        options.autoOpen = false;

        // Convert button config string to an actual object.
        options.buttons = parseButtonConfigString(options.buttons, false);
        // Add missing text if a default is set.  Complain if there is not.
        for (bb = 0; bb < options.buttons.length; bb += 1) {
            if (!options.buttons[bb].text) {
                options.buttons[bb].text = defaultButtonTextLookup[options.buttons[bb].type];
                if (!options.buttons[bb].text) {
                    throw new Error('Missing or no default text for button event type "' + options.buttons[bb].type + '"');
                }
            }
        }

        // Events that affect a dialog's visibility but are not triggered via
        // buttons need to be captured too, i.e. the Escape key and close icon.
        // Make those trigger the cancel button event.
        options.cancelButtonEventType = 'cancel';

        // Convert 'close-on-escape' to camel-case key and use default if needed.
        // Note: since value can be false, don't use value || default here.
        options.closeOnEscape = undefined === options['close-on-escape'] ? defaultCloseOnEscape : options['close-on-escape'];
        delete options['close-on-escape'];

        // Use default draggable value if needed.
        // Note: since value can be false, don't use value || default here.
        options.draggable = undefined === options.draggable ? defaultDraggable : options.draggable;

        // Button clicks fire custom events, so capture them.
        options.onClick = triggerEvent;

        // Use default resizable value if needed.
        // Note: since value can be false, don't use value || default here.
        options.resizable = undefined === options.resizable ? defaultResizable : options.resizable;

        // Convert 'show-close-icon' to camel-case key and use default value if needed.
        // Note: since value can be false, don't use value || default here.
        options.showCloseIcon = undefined === options['show-close-icon'] ? defaultShowCloseIcon : options['show-close-icon'];
        delete options['show-close-icon'];
        // For UI frameworks that do not have an option to hide the close icon,
        // a custom CSS class is used.  Frameworks that do support such an
        // option can ignore this option and just use options.showCloseIcon.
        options.hideCloseIconCssClass = 'uducada-hide-close-icon';

        // Initialize this dialog.
        uducada.uifwk.initDialog(element, options);
    }

    // Initialize default options.
    setDefaults();

    // Initialize all dialogs present in the markup, except those that indicate
    // to skip initialization.
    uducada.jsfwk.callFunctionForElementsIfDataValueIsNot('.dialog', 'skip-init', true, initializeDialog);

    // Public functions.
    return {
        init: initializeDialog
    };
}());
<|endoftext|>


<commit_before>/*===========================
Framework7 Swiper Additions
===========================*/
app.swiper = function (container, params) {
    return new Swiper(container, params);
};
app.initSwiper = function (pageContainer) {
    var page = $(pageContainer);
    var swipers = page.find('.swiper-init');
    if (swipers.length === 0) return;
    function destroySwiperOnRemove(slider) {
        function destroySwiper() {
            slider.destroy();
            page.off('pageBeforeRemove', destroySwiper);
        }
        page.on('pageBeforeRemove', destroySwiper);
    }
    for (var i = 0; i < swipers.length; i++) {
        var swiper = swipers.eq(i);
        var params;
        if (swiper.data('swiper')) {
            params = JSON.parse(swiper.data('swiper'));
        }
        else {
            params = {
                initialSlide: parseInt(swiper.data('initialSlide'), 10) || undefined,
                spaceBetween: parseInt(swiper.data('spaceBetween'), 10) || undefined,
                speed: parseInt(swiper.data('speed'), 10) || undefined,
                slidesPerView: swiper.data('slidesPerView') || undefined,
                slidesPerColumn: parseInt(swiper.data('slidesPerColumn'), 10) || undefined,
                centeredSlides: swiper.data('centeredSlides') && (swiper.data('centeredSlides') === 'true' ? true : false),
                direction: swiper.data('direction'),
                pagination: swiper.data('pagination'),
                paginationHide: swiper.data('paginationHide') && (swiper.data('paginationHide') === 'true' ? true : false),
                paginationClickable: swiper.data('paginationClickable') && (swiper.data('paginationClickable') === 'true' ? true : false),
                scrollbar: swiper.data('scrollbar'),
                scrollbarHide: swiper.data('scrollbarHide') && (swiper.data('scrollbarHide') === 'true' ? true : false),
                loop: swiper.data('loop') && (swiper.data('loop') === 'true' ? true : false),
                effect: swiper.data('effect') || 'slide',
                freeMode: swiper.data('freeMode') && (swiper.data('freeMode') === 'true' ? true : false),
                onlyExternal: swiper.data('onlyExternal') && (swiper.data('onlyExternal') === 'true' ? true : false),
                nextButton: swiper.data('nextButton'),
                prevButton: swiper.data('prevButton'),
                autoplay: swiper.data('autoplay')
            };
        }
        var _slider = app.swiper(swiper[0], params);
        destroySwiperOnRemove(_slider);
    }
};
app.reinitSwiper = function (pageContainer) {
    var page = $(pageContainer);
    var sliders = page.find('.swiper-init');
    if (sliders.length === 0) return;
    for (var i = 0; i < sliders.length; i++) {
        var sliderInstance = sliders[0].swiper;
        if (sliderInstance) {
            sliderInstance.onResize();
        }
    }
};
<commit_msg>Add parallax support<commit_after>/*===========================
Framework7 Swiper Additions
===========================*/
app.swiper = function (container, params) {
    return new Swiper(container, params);
};
app.initSwiper = function (pageContainer) {
    var page = $(pageContainer);
    var swipers = page.find('.swiper-init');
    if (swipers.length === 0) return;
    function destroySwiperOnRemove(slider) {
        function destroySwiper() {
            slider.destroy();
            page.off('pageBeforeRemove', destroySwiper);
        }
        page.on('pageBeforeRemove', destroySwiper);
    }
    for (var i = 0; i < swipers.length; i++) {
        var swiper = swipers.eq(i);
        var params;
        if (swiper.data('swiper')) {
            params = JSON.parse(swiper.data('swiper'));
        }
        else {
            params = {
                initialSlide: parseInt(swiper.data('initialSlide'), 10) || undefined,
                spaceBetween: parseInt(swiper.data('spaceBetween'), 10) || undefined,
                speed: parseInt(swiper.data('speed'), 10) || undefined,
                slidesPerView: swiper.data('slidesPerView') || undefined,
                slidesPerColumn: parseInt(swiper.data('slidesPerColumn'), 10) || undefined,
                centeredSlides: swiper.data('centeredSlides') && (swiper.data('centeredSlides') === 'true' ? true : false),
                direction: swiper.data('direction'),
                pagination: swiper.data('pagination'),
                paginationHide: swiper.data('paginationHide') && (swiper.data('paginationHide') === 'true' ? true : false),
                paginationClickable: swiper.data('paginationClickable') && (swiper.data('paginationClickable') === 'true' ? true : false),
                scrollbar: swiper.data('scrollbar'),
                scrollbarHide: swiper.data('scrollbarHide') && (swiper.data('scrollbarHide') === 'true' ? true : false),
                loop: swiper.data('loop') && (swiper.data('loop') === 'true' ? true : false),
                effect: swiper.data('effect') || 'slide',
                freeMode: swiper.data('freeMode') && (swiper.data('freeMode') === 'true' ? true : false),
                onlyExternal: swiper.data('onlyExternal') && (swiper.data('onlyExternal') === 'true' ? true : false),
                nextButton: swiper.data('nextButton'),
                prevButton: swiper.data('prevButton'),
                autoplay: swiper.data('autoplay'),
                parallax: swiper.data('parallax') && (swiper.data('parallax') === 'true' ? true : false)
            };
        }
        var _slider = app.swiper(swiper[0], params);
        destroySwiperOnRemove(_slider);
    }
};
app.reinitSwiper = function (pageContainer) {
    var page = $(pageContainer);
    var sliders = page.find('.swiper-init');
    if (sliders.length === 0) return;
    for (var i = 0; i < sliders.length; i++) {
        var sliderInstance = sliders[0].swiper;
        if (sliderInstance) {
            sliderInstance.onResize();
        }
    }
};
<|endoftext|>


<commit_before>const express = require('express');
const router = express.Router();
const cms = require('../cms.js');
const helperLib = require("../helper.js").helpers;
const helper = new helperLib();
module.exports = router;
router.get('/', function(req, res) {

	//checks if the user is logged in
	if (!req.session || !req.session.cas_user) {
		res.json({
			username: null,
			first_name: false
		});
		return;
	}
	var rcs_id = req.session.cas_user.toLowerCase();
	console.log(req.session.cas_user.toLowerCase());

	//parse user_data
	cms.getRCS(rcs_id).then(function(user_data) {
		user_data = JSON.parse(user_data);
		// username, student_id (is rin), last_name, middle_name, user_type
		console.log(user_data.first_name);
		res.json({
			username: rcs_id,
			first_name: user_data.preferred_name || user_data.first_name,
			isAdmin: helper.isAdmin(rcs_id)
		});
	});
});<commit_msg>Add status response to current_user.js<commit_after>const express = require('express');
const router = express.Router();
const cms = require('../cms.js');
const helperLib = require("../helper.js").helpers;
const helper = new helperLib();
module.exports = router;
router.get('/', function(req, res) {

	//checks if the user is logged in
	if (!req.session || !req.session.cas_user) {
		res.json({
			username: null,
			first_name: false
		});
		return;
	}
	var rcs_id = req.session.cas_user.toLowerCase();

	//parse user_data
	cms.getRCS(rcs_id).then(function(user_data) {
		user_data = JSON.parse(user_data);
		// username, student_id (is rin), last_name, middle_name, user_type
		console.log(helper.isAdmin(rcs_id));
		res.status(200).json({
			username: rcs_id,
			first_name: user_data.preferred_name || user_data.first_name,
			isAdmin: helper.isAdmin(rcs_id)
		});
	});
});<|endoftext|>


<commit_before>import moment from 'moment';
import { default as vegaEmbed } from 'vega-embed';
import { WKT } from 'ol/format';
import VectorLayer from 'ol/layer/Vector';
import { Vector as VectorSource } from 'ol/source';
import { Style, Icon, Stroke, Fill, Circle, Text } from 'ol/style';

var labelStyle = new Style({
    geometry: function (feature) {
        var geometry = feature.getGeometry();
        if (geometry.getType() == 'MultiPolygon') {
            // Only render label for the widest polygon of a multipolygon
            var polygons = geometry.getPolygons();
            var widest = 0;
            for (var i = 0, ii = polygons.length; i < ii; ++i) {
                var polygon = polygons[i];
                var width = getWidth(polygon.getExtent());
                if (width > widest) {
                    widest = width;
                    geometry = polygon;
                }
            }
        }
        return geometry;
    },
    text: new Text({
        font: '12px Calibri,sans-serif',
        overflow: true,
        fill: new Fill({
            color: '#000'
        }),
        stroke: new Stroke({
            color: '#fff',
            width: 3
        })
    })
});

var bookmarkStyle = [new Style({
    fill: new Fill({
        color: 'rgba(255, 255, 255, 0.2)'
    }),
    stroke: new Stroke({
        color: '#e49905',
        width: 2
    }),
    image: new Icon({
        src: require('../../components/styles/img/svg/wifi8.svg'),
        crossOrigin: 'anonymous',
        anchor: [0.5, 1]
    })
}), labelStyle];

export default ['hs.utils.service', '$http', 'config', 'hs.map.service', 'hs.layout.service', '$rootScope', '$compile', '$timeout', function (utils, $http, config, hsMap, layoutService, $rootScope, $compile, $timeout) {
    var me = this;
    var endpoint = config.senslog;

    $rootScope.$on('vectorQuery.featureSelected', utils.debounce((event, feature) => {
        layoutService.setMainPanel('sensors');
        me.units.forEach(unit => unit.expanded = false);
        me.selectUnit(me.units.filter(unit =>
            unit.unit_id == feature.get('unit_id')
        )[0]);
    }))

    return angular.extend(me, {
        units: [],
        sensorsSelected: [],
        sensorIdsSelected: [],
        layers: null,
        selectSensor(sensor) {
            me.sensorsSelected = [sensor];
            me.sensorIdsSelected = [sensor.sensor_id]
        },
        selectUnit(unit) {
            me.unit = unit;
            unit.expanded = !unit.expanded;
            me.selectSensor(unit.sensors[0]);
            if (document.querySelector('#sensor-unit-dialog') == null) {
                const dir = 'hs.sensors.unit-dialog';
                const html = `<${dir} unit="sensorsService.unit"></${dir}>`;
                const element = angular.element(html)[0];
                document.querySelector(".gui-overlay").appendChild(element);
                $compile(element)($rootScope.$new());
            } else {
                me.unitDialogVisible = true;
            }
            $timeout(_ => {
                if (angular.isUndefined(me.currentInterval))
                    me.currentInterval = { amount: 1, unit: 'days' };
                me.getObservationHistory(
                    me.unit,
                    me.currentInterval
                ).then(_ => me.createChart(me.unit))

            }, 0)
        },
        createLayer() {
            me.layer = new VectorLayer({
                title: 'Sensor units',
                synchronize: true,
                editor: {
                    editable: false
                },
                style: function (feature) {
                    labelStyle.getText().setText(feature.get('name'));
                    return bookmarkStyle;
                },
                source: new VectorSource({})
            });
            hsMap.map.addLayer(me.layer)
        },
        /**
         * @memberof hs.sensors.service
         * @function getUnits
         * @description Get list of units from Senslog backend
         */
        getUnits() {
            if (me.layer == null) me.createLayer();
            var url = utils.proxify(`${endpoint.url}/senslog-lite/rest/unit`);
            $http.get(url, {
                params: {
                    user_id: endpoint.user_id
                }
            }).then(response => {
                me.units = response.data;
                let features = me.units
                    .filter(unit => unit.unit_position && unit.unit_position.asWKT)
                    .map(unit => {
                        const format = new WKT();
                        const feature = format.readFeature(unit.unit_position.asWKT, {
                            dataProjection: 'EPSG:4326', featureProjection: 'EPSG:3857'
                        })
                        feature.set('name', unit.description);
                        feature.set('unit_id', unit.unit_id);
                        return feature
                    })
                me.layer.getSource().addFeatures(features);

                $http.get(utils.proxify(`${endpoint.url}/senslog1/SensorService`), {
                    params: {
                        Operation: 'GetLastObservations',
                        group: endpoint.group,
                        user: endpoint.user
                    }
                }).then(response => {
                    var sensorValues = {};
                    response.data.forEach(sv => {
                        sensorValues[sv.sensorId] = sv.observedValue;
                    });
                    me.units.forEach(unit => {
                        unit.sensors.forEach(sensor => {
                            sensor.lastObservationValue = sensorValues[sensor.sensor_id];
                        })
                    })
                })
            },
                function (err) {

                });
        },

        /**
         * @memberof hs.sensors.service
         * @function getObservationHistory
         * @param {Object} unit Object containing
         * {description, is_mobile, sensors, unit_id, unit_type}
         * @param {Object} interval Object {amount, unit}. Used to substract time
         * from current time, like 6 months before now
         * @description Gets list of observations in a given time frame for all
         * the sensors on a sensor unit (meteostation).
         */
        getObservationHistory(unit, interval) {
            return new Promise((resolve, reject) => {
                var url = utils.proxify(`${endpoint.url}/senslog-lite/rest/observation`);
                var from_time = moment().subtract(interval.amount, interval.unit);
                from_time = `${from_time.format('YYYY-MM-DD')} ${from_time.format('HH:mm:ssZ')}`;
                $http.get(url, {
                    params: {
                        user_id: endpoint.user_id,
                        unit_id: unit.unit_id,
                        from_time
                    }
                }).then(
                    response => {
                        me.observations = response.data;
                        resolve()
                    },
                    function (err) {
                        reject(err)
                    }).catch((e) => { reject(e) });
            }
            )
        },

        /**
         * @memberof hs.sensors.service
         * @function createChart
         * @description Create vega chart definition and use it in vegaEmbed
         * chart library. Observations for a specific unit from Senslog come
         * in a hierarchy, where 1st level contains object with timestamp and
         * for each timestamp there exist multiple sensor observations for
         * varying count of sensors. This nested list is flatened to simple
         * array of objects with {sensor_id, timestamp, value, sensor_name}
         */
        createChart(unit) {
            var sensorDesc = unit.sensors.filter(s =>
                me.sensorIdsSelected.indexOf(s.sensor_id) > -1
            );
            if (sensorDesc.length > 0) sensorDesc = sensorDesc[0];
            let observations = me.observations.reduce(
                (acc, val) => acc.concat(
                    val.sensors
                        .filter(s => me.sensorIdsSelected.indexOf(s.sensor_id) > -1)
                        .map(s => {
                            var time = moment(val.time_stamp);
                            s.sensor_name = sensorDesc.sensor_name;
                            s.time = time.format('DD.MM.YYYY HH:mm');
                            s.time_stamp = time.toDate();
                            return s
                        })
                ), []);
            observations.sort((a, b) => {
                if (a.time_stamp > b.time_stamp) return 1;
                if (b.time_stamp > a.time_stamp) return -1;
                return 0;
            });
            //See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/flat for flattening array
            var chartData = {
                "$schema": "https://vega.github.io/schema/vega-lite/v3.4.0.json",
                "config": {
                    "mark": {
                        "tooltip": null
                    },
                },
                "width": document.querySelector('#chartplace').offsetWidth - 40,
                "autosize": {
                    "type": "fit",
                    "contains": "padding"
                },
                "data": {
                    "name": "data-062c25e80e0ff23df3803082d5c6f7e7"
                },
                "datasets": {
                    "data-062c25e80e0ff23df3803082d5c6f7e7":
                        observations
                },
                "encoding": {
                    "color": {
                        "field": "sensor_name",
                        "legend": {
                            "title": `Sensor`
                        },
                        "type": "nominal"
                    },
                    "x": {
                        "axis": {
                            "title": "Timestamp",
                            "labelOverlap": true
                        },
                        "field": "time_stamp",
                        "sort": false,
                        "type": "temporal"
                    },
                    "y": {
                        "axis": {
                            "title": `${sensorDesc.phenomenon_name} ${sensorDesc.uom}`
                        },
                        "field": "value",
                        "type": "quantitative"
                    }
                },
                "mark": { "type": "line", "tooltip": { "content": "data" } },
                "selection": {
                    "selector016": {
                        "bind": "scales",
                        "encodings": [
                            "x",
                            "y"
                        ],
                        "type": "interval"
                    }
                }
            }
            try {
                vegaEmbed(document.querySelector('#chartplace'), chartData);
            } catch (ex) {
                console.warn('Could not create vega chart:', ex);
            }
        }

    })
}]<commit_msg>Switch to sensors panel only if sensor feature clicked<commit_after>import moment from 'moment';
import { default as vegaEmbed } from 'vega-embed';
import { WKT } from 'ol/format';
import VectorLayer from 'ol/layer/Vector';
import { Vector as VectorSource } from 'ol/source';
import { Style, Icon, Stroke, Fill, Circle, Text } from 'ol/style';

var labelStyle = new Style({
    geometry: function (feature) {
        var geometry = feature.getGeometry();
        if (geometry.getType() == 'MultiPolygon') {
            // Only render label for the widest polygon of a multipolygon
            var polygons = geometry.getPolygons();
            var widest = 0;
            for (var i = 0, ii = polygons.length; i < ii; ++i) {
                var polygon = polygons[i];
                var width = getWidth(polygon.getExtent());
                if (width > widest) {
                    widest = width;
                    geometry = polygon;
                }
            }
        }
        return geometry;
    },
    text: new Text({
        font: '12px Calibri,sans-serif',
        overflow: true,
        fill: new Fill({
            color: '#000'
        }),
        stroke: new Stroke({
            color: '#fff',
            width: 3
        })
    })
});

var bookmarkStyle = [new Style({
    fill: new Fill({
        color: 'rgba(255, 255, 255, 0.2)'
    }),
    stroke: new Stroke({
        color: '#e49905',
        width: 2
    }),
    image: new Icon({
        src: require('../../components/styles/img/svg/wifi8.svg'),
        crossOrigin: 'anonymous',
        anchor: [0.5, 1]
    })
}), labelStyle];

export default ['hs.utils.service', '$http', 'config', 'hs.map.service', 'hs.layout.service', '$rootScope', '$compile', '$timeout', function (utils, $http, config, hsMap, layoutService, $rootScope, $compile, $timeout) {
    var me = this;
    var endpoint = config.senslog;

    $rootScope.$on('vectorQuery.featureSelected', utils.debounce((event, feature) => {
        if (feature.getLayer(hsMap.map) == me.layer) {
            layoutService.setMainPanel('sensors');
            me.units.forEach(unit => unit.expanded = false);
            me.selectUnit(me.units.filter(unit =>
                unit.unit_id == feature.get('unit_id')
            )[0]);
        }
    }))

    return angular.extend(me, {
        units: [],
        sensorsSelected: [],
        sensorIdsSelected: [],
        layers: null,
        selectSensor(sensor) {
            me.sensorsSelected = [sensor];
            me.sensorIdsSelected = [sensor.sensor_id]
        },
        selectUnit(unit) {
            me.unit = unit;
            unit.expanded = !unit.expanded;
            me.selectSensor(unit.sensors[0]);
            if (document.querySelector('#sensor-unit-dialog') == null) {
                const dir = 'hs.sensors.unit-dialog';
                const html = `<${dir} unit="sensorsService.unit"></${dir}>`;
                const element = angular.element(html)[0];
                document.querySelector(".gui-overlay").appendChild(element);
                $compile(element)($rootScope.$new());
            } else {
                me.unitDialogVisible = true;
            }
            $timeout(_ => {
                if (angular.isUndefined(me.currentInterval))
                    me.currentInterval = { amount: 1, unit: 'days' };
                me.getObservationHistory(
                    me.unit,
                    me.currentInterval
                ).then(_ => me.createChart(me.unit))

            }, 0)
        },
        createLayer() {
            me.layer = new VectorLayer({
                title: 'Sensor units',
                synchronize: true,
                editor: {
                    editable: false
                },
                style: function (feature) {
                    labelStyle.getText().setText(feature.get('name'));
                    return bookmarkStyle;
                },
                source: new VectorSource({})
            });
            hsMap.map.addLayer(me.layer)
        },
        /**
         * @memberof hs.sensors.service
         * @function getUnits
         * @description Get list of units from Senslog backend
         */
        getUnits() {
            if (me.layer == null) me.createLayer();
            var url = utils.proxify(`${endpoint.url}/senslog-lite/rest/unit`);
            $http.get(url, {
                params: {
                    user_id: endpoint.user_id
                }
            }).then(response => {
                me.units = response.data;
                let features = me.units
                    .filter(unit => unit.unit_position && unit.unit_position.asWKT)
                    .map(unit => {
                        const format = new WKT();
                        const feature = format.readFeature(unit.unit_position.asWKT, {
                            dataProjection: 'EPSG:4326', featureProjection: 'EPSG:3857'
                        })
                        feature.set('name', unit.description);
                        feature.set('unit_id', unit.unit_id);
                        return feature
                    })
                me.layer.getSource().addFeatures(features);

                $http.get(utils.proxify(`${endpoint.url}/senslog1/SensorService`), {
                    params: {
                        Operation: 'GetLastObservations',
                        group: endpoint.group,
                        user: endpoint.user
                    }
                }).then(response => {
                    var sensorValues = {};
                    response.data.forEach(sv => {
                        sensorValues[sv.sensorId] = sv.observedValue;
                    });
                    me.units.forEach(unit => {
                        unit.sensors.forEach(sensor => {
                            sensor.lastObservationValue = sensorValues[sensor.sensor_id];
                        })
                    })
                })
            },
                function (err) {

                });
        },

        /**
         * @memberof hs.sensors.service
         * @function getObservationHistory
         * @param {Object} unit Object containing
         * {description, is_mobile, sensors, unit_id, unit_type}
         * @param {Object} interval Object {amount, unit}. Used to substract time
         * from current time, like 6 months before now
         * @description Gets list of observations in a given time frame for all
         * the sensors on a sensor unit (meteostation).
         */
        getObservationHistory(unit, interval) {
            return new Promise((resolve, reject) => {
                var url = utils.proxify(`${endpoint.url}/senslog-lite/rest/observation`);
                var from_time = moment().subtract(interval.amount, interval.unit);
                from_time = `${from_time.format('YYYY-MM-DD')} ${from_time.format('HH:mm:ssZ')}`;
                $http.get(url, {
                    params: {
                        user_id: endpoint.user_id,
                        unit_id: unit.unit_id,
                        from_time
                    }
                }).then(
                    response => {
                        me.observations = response.data;
                        resolve()
                    },
                    function (err) {
                        reject(err)
                    }).catch((e) => { reject(e) });
            }
            )
        },

        /**
         * @memberof hs.sensors.service
         * @function createChart
         * @description Create vega chart definition and use it in vegaEmbed
         * chart library. Observations for a specific unit from Senslog come
         * in a hierarchy, where 1st level contains object with timestamp and
         * for each timestamp there exist multiple sensor observations for
         * varying count of sensors. This nested list is flatened to simple
         * array of objects with {sensor_id, timestamp, value, sensor_name}
         */
        createChart(unit) {
            var sensorDesc = unit.sensors.filter(s =>
                me.sensorIdsSelected.indexOf(s.sensor_id) > -1
            );
            if (sensorDesc.length > 0) sensorDesc = sensorDesc[0];
            let observations = me.observations.reduce(
                (acc, val) => acc.concat(
                    val.sensors
                        .filter(s => me.sensorIdsSelected.indexOf(s.sensor_id) > -1)
                        .map(s => {
                            var time = moment(val.time_stamp);
                            s.sensor_name = sensorDesc.sensor_name;
                            s.time = time.format('DD.MM.YYYY HH:mm');
                            s.time_stamp = time.toDate();
                            return s
                        })
                ), []);
            observations.sort((a, b) => {
                if (a.time_stamp > b.time_stamp) return 1;
                if (b.time_stamp > a.time_stamp) return -1;
                return 0;
            });
            //See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/flat for flattening array
            var chartData = {
                "$schema": "https://vega.github.io/schema/vega-lite/v3.4.0.json",
                "config": {
                    "mark": {
                        "tooltip": null
                    },
                },
                "width": document.querySelector('#chartplace').offsetWidth - 40,
                "autosize": {
                    "type": "fit",
                    "contains": "padding"
                },
                "data": {
                    "name": "data-062c25e80e0ff23df3803082d5c6f7e7"
                },
                "datasets": {
                    "data-062c25e80e0ff23df3803082d5c6f7e7":
                        observations
                },
                "encoding": {
                    "color": {
                        "field": "sensor_name",
                        "legend": {
                            "title": `Sensor`
                        },
                        "type": "nominal"
                    },
                    "x": {
                        "axis": {
                            "title": "Timestamp",
                            "labelOverlap": true
                        },
                        "field": "time_stamp",
                        "sort": false,
                        "type": "temporal"
                    },
                    "y": {
                        "axis": {
                            "title": `${sensorDesc.phenomenon_name} ${sensorDesc.uom}`
                        },
                        "field": "value",
                        "type": "quantitative"
                    }
                },
                "mark": { "type": "line", "tooltip": { "content": "data" } },
                "selection": {
                    "selector016": {
                        "bind": "scales",
                        "encodings": [
                            "x",
                            "y"
                        ],
                        "type": "interval"
                    }
                }
            }
            try {
                vegaEmbed(document.querySelector('#chartplace'), chartData);
            } catch (ex) {
                console.warn('Could not create vega chart:', ex);
            }
        }

    })
}]<|endoftext|>