<commit_before>'use strict';
const path = require('path');
const os = require('os');
const Convert = require('./convert-source-map');
const fs = require('../file-system');
const SourceInclusion = require('./source-inclusion').SourceInclusion;
const DependencyInclusion = require('./dependency-inclusion').DependencyInclusion;
const Configuration = require('../configuration').Configuration;
const Utils = require('./utils');

exports.Bundle = class {
  constructor(bundler, config) {
    this.Minimatch = require('minimatch').Minimatch; //nested dep of vinyl-fs
    this.bundler = bundler;
    this.config = config;
    this.dependencies = [];
    this.prepend = (config.prepend || []).filter(x => bundler.itemIncludedInBuild(x)).map(x => typeof x === 'string' ? x : x.path);
    this.append = (config.append || []).filter(x => bundler.itemIncludedInBuild(x)).map(x => typeof x === 'string' ? x : x.path);
    this.moduleId = config.name.replace(path.extname(config.name), '');
    this.hash = '';
    this.includes = (config.source || []);
    this.excludes = [];
    if (this.includes instanceof Array) {
      this.includes = this.includes.map(x => new SourceInclusion(this, x));
    } else {
      this.excludes = (this.includes.exclude || []).map(x => this.createMatcher(x));
      this.includes = this.includes.include.map(x => new SourceInclusion(this, x));
    }
    this.buildOptions = new Configuration(config.options, bundler.buildOptions.getAllOptions());
    this.requiresBuild = true;
    this.fileCache = {};
  }

  static create(bundler, config) {
    let bundle = new exports.Bundle(bundler, config);
    let dependencies = config.dependencies || [];
    let dependenciesToBuild = dependencies
        .filter(x => bundler.itemIncludedInBuild(x));

    return Utils.runSequentially(
        dependenciesToBuild,
        dep => bundler.configureDependency(dep))
    .then(descriptions => {
      return Utils.runSequentially(
        descriptions,
        description => bundle.addDependency(description)
      );
    })
    .then(() => bundle);
  }

  createMatcher(pattern) {
    return new this.Minimatch(pattern, {
      dot: true
    });
  }

  addDependency(description) {
    this.dependencies.push(description);
    let inclusion = new DependencyInclusion(this, description);
    this.includes.push(inclusion);
    return inclusion.traceResources();
  }

  trySubsume(item) {
    let includes = this.includes;

    for (let i = 0, ii = includes.length; i < ii; ++i) {
      if (includes[i].trySubsume(item)) {
        this.requiresBuild = true;
        return true;
      }
    }

    return false;
  }

  transform() {
    if (this.requiresBuild) {
      let index = -1;
      let items = this.includes;

      function doTransform() {
        index++;

        if (index < items.length) {
          return items[index].transform().then(doTransform);
        }

        return Promise.resolve();
      }

      return doTransform();
    }

    return Promise.resolve();
  }

  getDependencyLocations() {
    return this.includes.filter(inclusion => inclusion.description && inclusion.description.location)
      .map(inclusion => {
        let normalizedLocation = path.posix.normalize(inclusion.description.location).replace(/\\/g, '\/');
        return {
          location: normalizedLocation,
          inclusion: inclusion
        };
      });
  }

  getBundledModuleIds() {
    return unique(this.includes.reduce((a, b) => a.concat(b.getAllModuleIds()), []));
  }

  getBundledFiles() {
    return uniqueBy(this.includes.reduce((a, b) => a.concat(b.getAllFiles()), []), (file) => file.path);
  }

  write(platform) {
    if (!this.requiresBuild) {
      return Promise.resolve();
    }

    let work = Promise.resolve();
    let loaderOptions = this.bundler.loaderOptions;
    let buildOptions = this.buildOptions;
    let files = [];

    if (this.prepend.length) {
      work = work.then(() => addFilesInOrder(this, this.prepend, files));
    }

    if (loaderOptions.configTarget === this.config.name) {
      work = work.then(() => {
        files.push({ contents: '_aureliaConfigureModuleLoader();'});
      });
    }

    work = work.then(() => files = files.concat(this.getBundledFiles()));

    if (this.append.length) {
      work = work.then(() => addFilesInOrder(this, this.append, files));
    }

    return work.then(() => {
      const Concat = require('./concat-with-sourcemaps');
      let concat = new Concat(true, this.config.name, os.EOL);
      const generateHashedPath = require('./utils').generateHashedPath;
      const generateHash = require('./utils').generateHash;
      let needsSourceMap = false;

      for (let i = 0; i < files.length; ++i) {
        let currentFile = files[i];
        let sourceMapEnabled = buildOptions.isApplicable('sourcemaps');
        let sourceMap = sourceMapEnabled ? currentFile.sourceMap : undefined;

        function fileIsDependency(file) {
          return file
            && file.path
            && file.path.indexOf('node_modules') >= 0;
        }

        function acquireSourceMapForDependency(file) {
          if (!file || !file.path) {
            return;
          }

          let parsedPath = path.parse(file.path);

          try {
            let base64SourceMap = Convert.fromSource(file.contents.toString());

            if (base64SourceMap) {
              return null;
            }
          } catch (error) {
            // we don't want the build to fail when a sourcemap file can't be parsed
            return null;
          }

          let converter;

          try {
            converter = Convert.fromMapFileSource(file.contents.toString(), parsedPath.dir);
          } catch (e) {
            console.log(e);
            return null;
          }

          sourceMap = converter
            ? converter.sourcemap
            : null;

          if (sourceMap !== null) {
            let sourceRoot = parsedPath.dir.substring(process.cwd().length);
            sourceMap.sourceRoot = sourceRoot.replace(/\\/g, '\/');
          }

          return sourceMap;
        }

        let content = currentFile.contents;

        if (sourceMapEnabled) {
          if (fileIsDependency(currentFile)) {
            sourceMap = acquireSourceMapForDependency(currentFile);
          }

          if (sourceMap) {
            needsSourceMap = true;
            content = Convert.removeMapFileComments(currentFile.contents);
          }
        }

        concat.add(currentFile.path, content, sourceMap ? JSON.stringify(sourceMap) : undefined);
      }

      let mapContents;
      let contents = concat.content;
      let bundleFileName = this.config.name;

      if (loaderOptions.configTarget === this.config.name) {
        //Add to the config bundle the loader config. Can't change index.html yet because we haven't generated hashes for all the files
        concat.add(undefined, this.writeLoaderCode(platform));
        contents = concat.content;

        if (buildOptions.isApplicable('rev')) {
          //Generate a unique hash based off of the bundle contents
          //Must generate hash after we write the loader config so that any other bundle changes (hash changes) can cause a new hash for the vendor file
          this.hash = generateHash(concat.content);
          bundleFileName = generateHashedPath(this.config.name, this.hash);
        }

        //Again, in the config setup, we're at the last bundle, and we can modify the index.html correctly now
        let outputDir = platform.baseUrl || platform.output; //If we have a baseUrl, then the files are served from there, else it's the output
        if (platform.index) {
          this.setIndexFileConfigTarget(platform, path.posix.join(outputDir, bundleFileName));
        }
      } else if (buildOptions.isApplicable('rev')) {
        //Generate a unique hash based off of the bundle contents
        //Must generate hash after we write the loader config so that any other bundle changes (hash changes) can cause a new hash for the vendor file
        this.hash = generateHash(concat.content);
        bundleFileName = generateHashedPath(this.config.name, this.hash);
      }

      let mapFileName = bundleFileName + '.map';
      let mapSourceRoot = path.posix.relative(
        path.posix.join(process.cwd(), platform.output),
        process.cwd()
      );

      console.log(`Writing ${bundleFileName}...`);

      if (buildOptions.isApplicable('minify')) {
        const UglifyJS = require('uglify-js');
        // fromString is not a supported option in v3
        const isV3 = !!(UglifyJS.minify('', {fromString: true}).error);

        let minificationOptions = {};
        if (!isV3) minificationOptions.fromString = true;

        let minifyOptions = buildOptions.getValue('minify');
        if (typeof minifyOptions === 'object') {
          Object.assign(minificationOptions, minifyOptions);
        }

        if (needsSourceMap) {
          if (isV3) {
            minificationOptions.sourceMap = {
              content: concat.sourceMap,
              filename: bundleFileName,
              url: mapFileName,
              root: mapSourceRoot
            };
          } else {
            minificationOptions.inSourceMap = Convert.fromJSON(concat.sourceMap).toObject();
            minificationOptions.outSourceMap = mapFileName;
            minificationOptions.sourceRoot = mapSourceRoot;
          }
        }

        let minificationResult = UglifyJS.minify(String(contents), minificationOptions);
        if (minificationResult.error) throw minificationResult.error;

        contents = minificationResult.code;
        mapContents = needsSourceMap ? Convert.fromJSON(minificationResult.map).toJSON() : undefined;
      } else if (needsSourceMap) {
        mapContents = Convert.fromJSON(concat.sourceMap)
          .setProperty('sourceRoot', mapSourceRoot)
          .toJSON();

        contents += os.EOL + '//# sourceMappingURL=' + mapFileName;
      }

      return fs.writeFile(path.posix.join(platform.output, bundleFileName), contents).then(() => {
        this.requiresBuild = false;

        if (mapContents) {
          return fs.writeFile(path.posix.join(platform.output, mapFileName), mapContents)
          .catch(e => {
            console.log(`Unable to write the sourcemap to ${path.posix.join(platform.output, mapFileName)}`);
          });
        }
      })
      .catch(e => {
        console.log(`Unable to write the bundle to ${path.posix.join(platform.output, bundleFileName)}`);
        console.log(e);
        throw e;
      });
    })
    .catch(e => {
      console.log('Failed to write the bundle');
      console.log(e);
      throw e;
    });
  }

  getFileFromCacheOrLoad(x) {
    let found = this.fileCache[x];
    if (found) {
      return Promise.resolve(found);
    }

    return fs.readFile(x).then(data => {
      found = { contents: data.toString() };
      this.fileCache[x] = found;
      return found;
    });
  }

  writeLoaderCode(platform) {
    const createLoaderCode = require('./loader').createLoaderCode;
    let config = createLoaderCode(platform, this.bundler);

    return 'function _aureliaConfigureModuleLoader(){' + config + '}';
  }

  setIndexFileConfigTarget(platform, location) {
    //Replace the reference to the vendor bundle in the "index.html" file to use the correct build revision file (or remove the build revision hash);
    const createSrcFileRegex = require('./utils').createSrcFileRegex;
    let indexLocation = platform.index;
    try {
      let indexFile = fs.readFileSync(indexLocation);
      let outputDir = platform.baseUrl || platform.output; //If we have a baseUrl, then the files are served from there, else it's the output
      let configMatcher = createSrcFileRegex(outputDir, this.moduleId);
      if (configMatcher.test(indexFile)) {
        indexFile = indexFile.replace(configMatcher, 'src="$2' + location + '"');//Replace the old reference to the file with whatever the new one is (preserving any unknown path parts before)
        fs.writeFile(indexLocation, indexFile);
      } else {
        console.log('Error: Unable to update ' + this.moduleId + ' path to ' + location + ', could not find existing reference to replace');
      }
    } catch (err) {
      if (err.code === 'ENOENT') {
        console.log('Error: No index file found at "' + indexLocation + '"');
      } else {
        console.log(err);
      }
    }
  }
};

function addFilesInOrder(bundle, paths, files) {
  let index = -1;

  function addFile() {
    index++;

    if (index < paths.length) {
      return bundle.getFileFromCacheOrLoad(paths[index])
        .then(file => files.push(file))
        .then(addFile);
    }

    return Promise.resolve();
  }

  return addFile();
}

function unique(collection) {
  let u = {};
  let a = [];

  for (let i = 0, l = collection.length; i < l; ++i) {
    if (u.hasOwnProperty(collection[i])) {
      continue;
    }

    a.push(collection[i]);
    u[collection[i]] = 1;
  }

  return a;
}

function uniqueBy(collection, key) {
  const seen = {};
  return collection.filter((item) => {
    const k = key(item);
    return seen.hasOwnProperty(k) ? false : (seen[k] = true);
  });
}
<commit_msg>chore(bundle): improved error logging<commit_after>'use strict';
const path = require('path');
const os = require('os');
const Convert = require('./convert-source-map');
const fs = require('../file-system');
const SourceInclusion = require('./source-inclusion').SourceInclusion;
const DependencyInclusion = require('./dependency-inclusion').DependencyInclusion;
const Configuration = require('../configuration').Configuration;
const Utils = require('./utils');
const logger = require('aurelia-logging').getLogger('Bundle');

exports.Bundle = class {
  constructor(bundler, config) {
    this.Minimatch = require('minimatch').Minimatch; //nested dep of vinyl-fs
    this.bundler = bundler;
    this.config = config;
    this.dependencies = [];
    this.prepend = (config.prepend || []).filter(x => bundler.itemIncludedInBuild(x)).map(x => typeof x === 'string' ? x : x.path);
    this.append = (config.append || []).filter(x => bundler.itemIncludedInBuild(x)).map(x => typeof x === 'string' ? x : x.path);
    this.moduleId = config.name.replace(path.extname(config.name), '');
    this.hash = '';
    this.includes = (config.source || []);
    this.excludes = [];
    if (this.includes instanceof Array) {
      this.includes = this.includes.map(x => new SourceInclusion(this, x));
    } else {
      this.excludes = (this.includes.exclude || []).map(x => this.createMatcher(x));
      this.includes = this.includes.include.map(x => new SourceInclusion(this, x));
    }
    this.buildOptions = new Configuration(config.options, bundler.buildOptions.getAllOptions());
    this.requiresBuild = true;
    this.fileCache = {};
  }

  static create(bundler, config) {
    let bundle = new exports.Bundle(bundler, config);
    let dependencies = config.dependencies || [];
    let dependenciesToBuild = dependencies
        .filter(x => bundler.itemIncludedInBuild(x));

    return Utils.runSequentially(
        dependenciesToBuild,
        dep => bundler.configureDependency(dep))
    .then(descriptions => {
      return Utils.runSequentially(
        descriptions,
        description => bundle.addDependency(description)
      );
    })
    .then(() => bundle);
  }

  createMatcher(pattern) {
    return new this.Minimatch(pattern, {
      dot: true
    });
  }

  addDependency(description) {
    this.dependencies.push(description);
    let inclusion = new DependencyInclusion(this, description);
    this.includes.push(inclusion);
    return inclusion.traceResources();
  }

  trySubsume(item) {
    let includes = this.includes;

    for (let i = 0, ii = includes.length; i < ii; ++i) {
      if (includes[i].trySubsume(item)) {
        this.requiresBuild = true;
        return true;
      }
    }

    return false;
  }

  transform() {
    if (this.requiresBuild) {
      let index = -1;
      let items = this.includes;

      function doTransform() {
        index++;

        if (index < items.length) {
          return items[index].transform().then(doTransform);
        }

        return Promise.resolve();
      }

      return doTransform();
    }

    return Promise.resolve();
  }

  getDependencyLocations() {
    return this.includes.filter(inclusion => inclusion.description && inclusion.description.location)
      .map(inclusion => {
        let normalizedLocation = path.posix.normalize(inclusion.description.location).replace(/\\/g, '\/');
        return {
          location: normalizedLocation,
          inclusion: inclusion
        };
      });
  }

  getBundledModuleIds() {
    return unique(this.includes.reduce((a, b) => a.concat(b.getAllModuleIds()), []));
  }

  getBundledFiles() {
    return uniqueBy(this.includes.reduce((a, b) => a.concat(b.getAllFiles()), []), (file) => file.path);
  }

  write(platform) {
    if (!this.requiresBuild) {
      return Promise.resolve();
    }

    let work = Promise.resolve();
    let loaderOptions = this.bundler.loaderOptions;
    let buildOptions = this.buildOptions;
    let files = [];

    if (this.prepend.length) {
      work = work.then(() => addFilesInOrder(this, this.prepend, files));
    }

    if (loaderOptions.configTarget === this.config.name) {
      work = work.then(() => {
        files.push({ contents: '_aureliaConfigureModuleLoader();'});
      });
    }

    work = work.then(() => files = files.concat(this.getBundledFiles()));

    if (this.append.length) {
      work = work.then(() => addFilesInOrder(this, this.append, files));
    }

    return work.then(() => {
      const Concat = require('./concat-with-sourcemaps');
      let concat = new Concat(true, this.config.name, os.EOL);
      const generateHashedPath = require('./utils').generateHashedPath;
      const generateHash = require('./utils').generateHash;
      let needsSourceMap = false;

      for (let i = 0; i < files.length; ++i) {
        let currentFile = files[i];
        let sourceMapEnabled = buildOptions.isApplicable('sourcemaps');
        let sourceMap = sourceMapEnabled ? currentFile.sourceMap : undefined;

        function fileIsDependency(file) {
          return file
            && file.path
            && file.path.indexOf('node_modules') >= 0;
        }

        function acquireSourceMapForDependency(file) {
          if (!file || !file.path) {
            return;
          }

          let parsedPath = path.parse(file.path);

          try {
            let base64SourceMap = Convert.fromSource(file.contents.toString());

            if (base64SourceMap) {
              return null;
            }
          } catch (error) {
            // we don't want the build to fail when a sourcemap file can't be parsed
            return null;
          }

          let converter;

          try {
            converter = Convert.fromMapFileSource(file.contents.toString(), parsedPath.dir);
          } catch (e) {
            console.log(e);
            return null;
          }

          sourceMap = converter
            ? converter.sourcemap
            : null;

          if (sourceMap !== null) {
            let sourceRoot = parsedPath.dir.substring(process.cwd().length);
            sourceMap.sourceRoot = sourceRoot.replace(/\\/g, '\/');
          }

          return sourceMap;
        }

        let content = currentFile.contents;

        if (sourceMapEnabled) {
          if (fileIsDependency(currentFile)) {
            sourceMap = acquireSourceMapForDependency(currentFile);
          }

          if (sourceMap) {
            needsSourceMap = true;
            content = Convert.removeMapFileComments(currentFile.contents);
          }
        }

        concat.add(currentFile.path, content, sourceMap ? JSON.stringify(sourceMap) : undefined);
      }

      let mapContents;
      let contents = concat.content;
      let bundleFileName = this.config.name;

      if (loaderOptions.configTarget === this.config.name) {
        //Add to the config bundle the loader config. Can't change index.html yet because we haven't generated hashes for all the files
        concat.add(undefined, this.writeLoaderCode(platform));
        contents = concat.content;

        if (buildOptions.isApplicable('rev')) {
          //Generate a unique hash based off of the bundle contents
          //Must generate hash after we write the loader config so that any other bundle changes (hash changes) can cause a new hash for the vendor file
          this.hash = generateHash(concat.content);
          bundleFileName = generateHashedPath(this.config.name, this.hash);
        }

        //Again, in the config setup, we're at the last bundle, and we can modify the index.html correctly now
        let outputDir = platform.baseUrl || platform.output; //If we have a baseUrl, then the files are served from there, else it's the output
        if (platform.index) {
          this.setIndexFileConfigTarget(platform, path.posix.join(outputDir, bundleFileName));
        }
      } else if (buildOptions.isApplicable('rev')) {
        //Generate a unique hash based off of the bundle contents
        //Must generate hash after we write the loader config so that any other bundle changes (hash changes) can cause a new hash for the vendor file
        this.hash = generateHash(concat.content);
        bundleFileName = generateHashedPath(this.config.name, this.hash);
      }

      let mapFileName = bundleFileName + '.map';
      let mapSourceRoot = path.posix.relative(
        path.posix.join(process.cwd(), platform.output),
        process.cwd()
      );

      console.log(`Writing ${bundleFileName}...`);

      if (buildOptions.isApplicable('minify')) {
        const UglifyJS = require('uglify-js');
        // fromString is not a supported option in v3
        const isV3 = !!(UglifyJS.minify('', {fromString: true}).error);

        let minificationOptions = {};
        if (!isV3) minificationOptions.fromString = true;

        let minifyOptions = buildOptions.getValue('minify');
        if (typeof minifyOptions === 'object') {
          Object.assign(minificationOptions, minifyOptions);
        }

        if (needsSourceMap) {
          if (isV3) {
            minificationOptions.sourceMap = {
              content: concat.sourceMap,
              filename: bundleFileName,
              url: mapFileName,
              root: mapSourceRoot
            };
          } else {
            minificationOptions.inSourceMap = Convert.fromJSON(concat.sourceMap).toObject();
            minificationOptions.outSourceMap = mapFileName;
            minificationOptions.sourceRoot = mapSourceRoot;
          }
        }

        let minificationResult = UglifyJS.minify(String(contents), minificationOptions);
        if (minificationResult.error) throw minificationResult.error;

        contents = minificationResult.code;
        mapContents = needsSourceMap ? Convert.fromJSON(minificationResult.map).toJSON() : undefined;
      } else if (needsSourceMap) {
        mapContents = Convert.fromJSON(concat.sourceMap)
          .setProperty('sourceRoot', mapSourceRoot)
          .toJSON();

        contents += os.EOL + '//# sourceMappingURL=' + mapFileName;
      }

      return fs.writeFile(path.posix.join(platform.output, bundleFileName), contents).then(() => {
        this.requiresBuild = false;

        if (mapContents) {
          return fs.writeFile(path.posix.join(platform.output, mapFileName), mapContents)
          .catch(e => {
            logger.error(`Unable to write the sourcemap to ${path.posix.join(platform.output, mapFileName)}`);
          });
        }
      })
      .catch(e => {
        logger.error(`Unable to write the bundle to ${path.posix.join(platform.output, bundleFileName)}`);
        logger.info(e);
        throw e;
      });
    })
    .catch(e => {
      logger.error('Failed to write the bundle');
      logger.info(e);
      throw e;
    });
  }

  getFileFromCacheOrLoad(x) {
    let found = this.fileCache[x];
    if (found) {
      return Promise.resolve(found);
    }

    return fs.readFile(x).then(data => {
      found = { contents: data.toString() };
      this.fileCache[x] = found;
      return found;
    }).catch(e => {
      logger.error(`Error while trying to read ${x}`);
      throw e;
    });
  }

  writeLoaderCode(platform) {
    const createLoaderCode = require('./loader').createLoaderCode;
    let config = createLoaderCode(platform, this.bundler);

    return 'function _aureliaConfigureModuleLoader(){' + config + '}';
  }

  setIndexFileConfigTarget(platform, location) {
    //Replace the reference to the vendor bundle in the "index.html" file to use the correct build revision file (or remove the build revision hash);
    const createSrcFileRegex = require('./utils').createSrcFileRegex;
    let indexLocation = platform.index;
    try {
      let indexFile = fs.readFileSync(indexLocation);
      let outputDir = platform.baseUrl || platform.output; //If we have a baseUrl, then the files are served from there, else it's the output
      let configMatcher = createSrcFileRegex(outputDir, this.moduleId);
      if (configMatcher.test(indexFile)) {
        indexFile = indexFile.replace(configMatcher, 'src="$2' + location + '"');//Replace the old reference to the file with whatever the new one is (preserving any unknown path parts before)
        fs.writeFile(indexLocation, indexFile);
      } else {
        logger.error('Error: Unable to update ' + this.moduleId + ' path to ' + location + ', could not find existing reference to replace');
      }
    } catch (err) {
      if (err.code === 'ENOENT') {
        logger.error('Error: No index file found at "' + indexLocation + '"');
      } else {
        logger.error(err);
      }
    }
  }
};

function addFilesInOrder(bundle, paths, files) {
  let index = -1;

  function addFile() {
    index++;

    if (index < paths.length) {
      return bundle.getFileFromCacheOrLoad(paths[index])
        .then(file => files.push(file))
        .then(addFile);
    }

    return Promise.resolve();
  }

  return addFile();
}

function unique(collection) {
  let u = {};
  let a = [];

  for (let i = 0, l = collection.length; i < l; ++i) {
    if (u.hasOwnProperty(collection[i])) {
      continue;
    }

    a.push(collection[i]);
    u[collection[i]] = 1;
  }

  return a;
}

function uniqueBy(collection, key) {
  const seen = {};
  return collection.filter((item) => {
    const k = key(item);
    return seen.hasOwnProperty(k) ? false : (seen[k] = true);
  });
}
<|endoftext|>


<commit_before>var sequelize = require('../libs').sequelize;

function model(name) {
  return sequelize.import(__dirname + '/' + name + '.js');
}

module.exports = {
  registration: model('registration')
};
<commit_msg>working on user<commit_after>var sequelize = require('../libs').sequelize;

function model(name) {
  return sequelize.import(__dirname + '/' + name + '.js');
}

function modelObj(obj) {
  function _modelObj (obj, name) {
    obj[name] = model(name);
  }

  return _modelObj.bind(undefined, obj);
}

function models() {
  var obj = {};
  Array.prototype.forEach.call(arguments, modelObj(obj));
  return obj;
}

module.exports = models('registration', 'user');

<|endoftext|>


<commit_before>import * as Three from 'three';
import createGrid from './grid-creator';
import convert from 'convert-units';
import {disposeObject} from './three-memory-cleaner';

export function parseData(sceneData, editingActions, catalog) {

  let planData = {};

  planData.sceneGraph = {
    unit: sceneData.unit,
    layers: {},
    width: sceneData.width,
    height: sceneData.height
  };

  planData.plan = new Three.Object3D();

  // Add a grid to the plan
  planData.grid = createGrid(sceneData);
  planData.boundingBox = new Three.Box3().setFromObject(planData.grid);

  sceneData.layers.forEach(layer => {

    planData.sceneGraph.layers[layer.id] = {
      lines: {},
      holes: {},
      areas: {},
      items: {},
      visible: layer.visible,
      altitude: layer.altitude
    };

    // Import lines
    layer.lines.forEach(line => {
      addLine(sceneData, planData, layer, line.id, catalog, editingActions);
      line.holes.forEach(holeID => {
        addHole(sceneData, planData, layer, holeID, catalog, editingActions);
      })
    });

    // Import areas
    layer.areas.forEach(area => {
      addArea(sceneData, planData, layer, area.id, catalog, editingActions);
    });

    // Import items
    layer.items.forEach(item => {
      addItem(sceneData, planData, layer, item.id, catalog, editingActions);
    });
  });

  return planData;
}

export function updateScene(planData, sceneData, oldSceneData, diffArray, editingActions, catalog) {

  minimizeChangePropertiesDiffs(diffArray).forEach(diff => {

    /* First of all I need to find the object I need to update */
    let modifiedPath = diff.path.split("/");

    if (modifiedPath[1] === "layers") {

      let layer = sceneData[modifiedPath[1]].get(modifiedPath[2]);

      if (modifiedPath.length > 2) {

        switch (diff.op) {
          case 'replace':
            replaceObject(modifiedPath, layer, planData, editingActions, sceneData, oldSceneData, catalog);
            break;
          case 'add':
            addObject(modifiedPath, layer, planData, editingActions, sceneData, oldSceneData, catalog);
            break;
          case 'remove':
            removeObject(modifiedPath, layer, planData, editingActions, sceneData, oldSceneData, catalog);
            break;
        }
      }
    }
  });
  return planData;
}

function replaceObject(modifiedPath, layer, planData, editingActions, sceneData, oldSceneData, catalog) {

  switch (modifiedPath[3]) {
    case "layer":
      break;
    case "vertices":
      break;
    case "holes":
      let newHoleData = layer.holes.get(modifiedPath[4]);
      let lineID = newHoleData.line;
      if (modifiedPath[5] === 'selected') {
        // I remove only the hole without removing the wall
        removeHole(planData, layer, newHoleData.id);
        addHole(sceneData, planData, layer, newHoleData.id, catalog, editingActions);
      } else {
        layer.lines.get(lineID).holes.forEach(holeID => {
          removeHole(planData, layer, holeID);
        });
        removeLine(planData, layer, lineID);
        addLine(sceneData, planData, layer, lineID, catalog, editingActions);
        layer.lines.get(lineID).holes.forEach(holeID => {
          addHole(sceneData, planData, layer, holeID, catalog, editingActions);
        });
      }
      break;
    case "lines":
      removeLine(planData, layer, modifiedPath[4]);
      addLine(sceneData, planData, layer, modifiedPath[4], catalog, editingActions);
      break;
    case "areas":
      removeArea(planData, layer, modifiedPath[4]);
      addArea(sceneData, planData, layer, modifiedPath[4], catalog, editingActions);
      break;
    case "items":
      removeItem(planData, layer, modifiedPath[4]);
      addItem(sceneData, planData, layer, modifiedPath[4], catalog, editingActions);
      break;

    case "visible":
      let layerGraph = planData.sceneGraph.layers[layer.id];
      layerGraph.visible = layer.visible;
      for (let lineID in layerGraph.lines) {
        layerGraph.lines[lineID].visible = layer.visible;
      }

      for (let areaID in layerGraph.areas) {
        layerGraph.areas[areaID].visible = layer.visible;
      }

      for (let itemID in layerGraph.items) {
        layerGraph.items[itemID].visible = layer.visible;
      }

      for (let holeID in layerGraph.holes) {
        layerGraph.holes[holeID].visible = layer.visible;
      }
      break;
  }
}

function removeObject(modifiedPath, layer, planData, editingActions, sceneData, oldSceneData, catalog) {

  switch (modifiedPath[3]) {
    case "lines":
      // Here I remove the line with all its holes
      let lineID = modifiedPath[4];
      let oldLayer = oldSceneData.layers.get(layer.id);
      oldLayer.lines.get(lineID).holes.forEach(holeID => {
        removeHole(planData, layer, holeID);
      });
      removeLine(planData, layer, lineID);
      if (modifiedPath.length > 5) {
        // I removed an hole, so I should add the new line
        addLine(sceneData, planData, layer, lineID, catalog, editingActions);
        layer.lines.get(lineID).holes.forEach(holeID => {
          addHole(sceneData, planData, layer, holeID, catalog, editingActions);
        });
      }
      break;
    case "areas":
      if (modifiedPath.length === 5) {
        // I am removing an entire area
        removeArea(planData, layer, modifiedPath[4]);
      }
      break;
    case "items":
      if (modifiedPath.length === 5) {
        // I am removing an item
        removeItem(planData, layer, modifiedPath[4]);
      }
      break;
  }
}

function removeHole(planData, layer, holeToRemoveID) {
  let holeToRemove = planData.sceneGraph.layers[layer.id].holes[holeToRemoveID];
  planData.plan.remove(holeToRemove);
  disposeObject(holeToRemove);
  delete planData.sceneGraph.layers[layer.id].holes[holeToRemoveID];
  holeToRemove = null;
  updateBoundingBox(planData);
}

function removeLine(planData, layer, lineID) {
  let line3D = planData.sceneGraph.layers[layer.id].lines[lineID];
  planData.plan.remove(line3D);
  disposeObject(line3D);
  delete planData.sceneGraph.layers[layer.id].lines[lineID];
  line3D = null;
  updateBoundingBox(planData);
}

function removeArea(planData, layer, areaID) {
  let area3D = planData.sceneGraph.layers[layer.id].areas[areaID];
  planData.plan.remove(area3D);
  disposeObject(area3D);
  delete planData.sceneGraph.layers[layer.id].areas[areaID];
  area3D = null;
  updateBoundingBox(planData);
}

function removeItem(planData, layer, itemID) {
  let item3D = planData.sceneGraph.layers[layer.id].items[itemID];
  planData.plan.remove(item3D);
  disposeObject(item3D);
  delete planData.sceneGraph.layers[layer.id].items[itemID];
  item3D = null;
  updateBoundingBox(planData);
}

function addObject(modifiedPath, layer, planData, editingActions, sceneData, oldSceneData, catalog) {

  switch (modifiedPath[3]) {
    case "lines":
      if (modifiedPath.length === 5) {
        // I have to add a line
        addLine(sceneData, planData, layer, modifiedPath[4], catalog, editingActions);
      }
      break;
    case "areas":
      if (modifiedPath.length === 5) {
        // I have to add an area
        addArea(sceneData, planData, layer, modifiedPath[4], catalog, editingActions);
      }
      break;
    case "items":
      if (modifiedPath.length === 5) {
        // I have to add an area
        addItem(sceneData, planData, layer, modifiedPath[4], catalog, editingActions);
      }
      break;
  }
}

function addHole(sceneData, planData, layer, holeID, catalog, editingActions) {
  let holeData = layer.holes.get(holeID);

  // Create the hole object
  catalog.getElement(holeData.type).render3D(holeData, layer, sceneData).then(object => {

    let line = layer.lines.get(holeData.line);

    // First of all I need to find the vertices of this line
    let vertex0 = layer.vertices.get(line.vertices.get(0));
    let vertex1 = layer.vertices.get(line.vertices.get(1));

    if (vertex0.x > vertex1.x) {
      let app = vertex0;
      vertex0 = vertex1;
      vertex1 = app;
    }

    let distance = Math.sqrt(Math.pow(vertex0.x - vertex1.x, 2) + Math.pow(vertex0.y - vertex1.y, 2));
    let alpha = Math.asin((vertex1.y - vertex0.y) / distance);

    let boundingBox = new Three.Box3().setFromObject(object);
    let center = [
      (boundingBox.max.x - boundingBox.min.x) / 2 + boundingBox.min.x,
      (boundingBox.max.y - boundingBox.min.y) / 2 + boundingBox.min.y,
      (boundingBox.max.z - boundingBox.min.z) / 2 + boundingBox.min.z];

    let holeAltitude = holeData.properties.get('altitude').get('length');

    let holeHeight = holeData.properties.get('height').get('length');

    object.rotation.y += alpha;
    object.position.x += vertex0.x + distance * holeData.offset * Math.cos(alpha) - center[2] * Math.sin(alpha);
    object.position.y += holeAltitude + holeHeight / 2 - center[1];
    object.position.z += -vertex0.y - distance * holeData.offset * Math.sin(alpha) - center[2] * Math.cos(alpha);

    planData.plan.add(object);
    planData.sceneGraph.layers[layer.id].holes[holeData.id] = object;

    applyInteract(object, () => {
      return editingActions.selectHole(layer.id, holeData.id)
    });

    updateBoundingBox(planData);
  });
}

function addLine(sceneData, planData, layer, lineID, catalog, editingActions) {
  let line = layer.lines.get(lineID);

  // First of all I need to find the vertices of this line
  let vertex0 = layer.vertices.get(line.vertices.get(0));
  let vertex1 = layer.vertices.get(line.vertices.get(1));

  if (vertex0.x > vertex1.x) {
    let app = vertex0;
    vertex0 = vertex1;
    vertex1 = app;
  }

  catalog.getElement(line.type).render3D(line, layer, sceneData).then(line3D => {

    line3D.position.x += vertex0.x;
    line3D.position.y += layer.altitude;
    line3D.position.z -= vertex0.y;

    line3D.visible = layer.visible;

    planData.plan.add(line3D);
    planData.sceneGraph.layers[layer.id].lines[lineID] = line3D;

    applyInteract(line3D, () => {
      return editingActions.selectLine(layer.id, line.id);
    });

    updateBoundingBox(planData);
  });
}

function addArea(sceneData, planData, layer, areaID, catalog, editingActions) {
  let area = layer.areas.get(areaID);
  let interactFunction = () => {
    editingActions.selectArea(layer.id, area.id);
  };

  catalog.getElement(area.type).render3D(area, layer, sceneData).then(area3D => {
    area3D.position.y += layer.altitude;
    planData.plan.add(area3D);
    planData.sceneGraph.layers[layer.id].areas[area.id] = area3D;
    area3D.visible = layer.visible;

    applyInteract(area3D, interactFunction);

    updateBoundingBox(planData);
  });
}

function addItem(sceneData, planData, layer, itemID, catalog, editingActions) {
  let item = layer.items.get(itemID);

  catalog.getElement(item.type).render3D(item, layer, sceneData).then(item3D => {

    let pivot = new Three.Object3D();
    pivot.add(item3D);

    pivot.rotation.y = item.rotation * Math.PI / 180;
    pivot.position.x = item.x;
    pivot.position.z -= item.y;

    applyInteract(item3D, () => {
        editingActions.selectItem(layer.id, item.id);
      }
    );

    planData.plan.add(pivot);
    planData.sceneGraph.layers[layer.id].items[item.id] = pivot;

    updateBoundingBox(planData);

  });

}

// Apply interact function to children of an Object3D
function applyInteract(object, interactFunction) {
  object.traverse(function (child) {
    if (child instanceof Three.Mesh) {
      child.interact = interactFunction;
    }
  });
}

function updateBoundingBox(planData) {
  let newBoundingBox = new Three.Box3().setFromObject(planData.plan);

  if (isFinite(newBoundingBox.max.x)
    || isFinite(newBoundingBox.min.x)
    || isFinite(newBoundingBox.max.y)
    || isFinite(newBoundingBox.min.y)
    || isFinite(newBoundingBox.max.z)
    || isFinite(newBoundingBox.min.z)) {

    let newCenter = [
      (newBoundingBox.max.x - newBoundingBox.min.x) / 2 + newBoundingBox.min.x,
      (newBoundingBox.max.y - newBoundingBox.min.y) / 2 + newBoundingBox.min.y,
      (newBoundingBox.max.z - newBoundingBox.min.z) / 2 + newBoundingBox.min.z];

    planData.plan.position.x -= newCenter[0];
    planData.plan.position.y -= newCenter[1];
    planData.plan.position.z -= newCenter[2];

    planData.grid.position.x -= newCenter[0];
    planData.grid.position.y -= newCenter[1];
    planData.grid.position.z -= newCenter[2];

    planData.boundingBox = newBoundingBox;
  }
}

/**
 * Reduces the number of change properties diffs
 * @param diffArray the array of the diffs
 * @returns {Array}
 */
function minimizeChangePropertiesDiffs(diffArray) {

  let minimizedDiffs = [];

  let propertiesDiffs = [];

  // Find all diffs for a changed property
  diffArray.forEach(currentDiff => {
    let splittedDiff = currentDiff.path.split("/");
    if (splittedDiff[5] === 'properties') {
      propertiesDiffs.push([currentDiff, splittedDiff[4]]);
    } else {
      minimizedDiffs.push(currentDiff);
    }

  });

  let sortedPropertiesDiffs = propertiesDiffs.sort((a, b) => {
    return a[1] < b[1];
  });

  for (let i = 0; i < sortedPropertiesDiffs.length; i++) {
    minimizedDiffs.push(sortedPropertiesDiffs[0][0]);
    let futureIndex = i + 1;
    while (futureIndex < sortedPropertiesDiffs.length && sortedPropertiesDiffs[i][1] === sortedPropertiesDiffs[futureIndex][1]) {
      futureIndex++;
    }
    i = futureIndex - 1;
  }

  return minimizedDiffs;
}
<commit_msg>Now all catalog plugins are packed inside an object3D in scene creator<commit_after>import * as Three from 'three';
import createGrid from './grid-creator';
import convert from 'convert-units';
import {disposeObject} from './three-memory-cleaner';

export function parseData(sceneData, editingActions, catalog) {

  let planData = {};

  planData.sceneGraph = {
    unit: sceneData.unit,
    layers: {},
    width: sceneData.width,
    height: sceneData.height
  };

  planData.plan = new Three.Object3D();

  // Add a grid to the plan
  planData.grid = createGrid(sceneData);
  planData.boundingBox = new Three.Box3().setFromObject(planData.grid);

  sceneData.layers.forEach(layer => {

    planData.sceneGraph.layers[layer.id] = {
      lines: {},
      holes: {},
      areas: {},
      items: {},
      visible: layer.visible,
      altitude: layer.altitude
    };

    // Import lines
    layer.lines.forEach(line => {
      addLine(sceneData, planData, layer, line.id, catalog, editingActions);
      line.holes.forEach(holeID => {
        addHole(sceneData, planData, layer, holeID, catalog, editingActions);
      })
    });

    // Import areas
    layer.areas.forEach(area => {
      addArea(sceneData, planData, layer, area.id, catalog, editingActions);
    });

    // Import items
    layer.items.forEach(item => {
      addItem(sceneData, planData, layer, item.id, catalog, editingActions);
    });
  });

  return planData;
}

export function updateScene(planData, sceneData, oldSceneData, diffArray, editingActions, catalog) {

  minimizeChangePropertiesDiffs(diffArray).forEach(diff => {

    /* First of all I need to find the object I need to update */
    let modifiedPath = diff.path.split("/");

    if (modifiedPath[1] === "layers") {

      let layer = sceneData[modifiedPath[1]].get(modifiedPath[2]);

      if (modifiedPath.length > 2) {

        switch (diff.op) {
          case 'replace':
            replaceObject(modifiedPath, layer, planData, editingActions, sceneData, oldSceneData, catalog);
            break;
          case 'add':
            addObject(modifiedPath, layer, planData, editingActions, sceneData, oldSceneData, catalog);
            break;
          case 'remove':
            removeObject(modifiedPath, layer, planData, editingActions, sceneData, oldSceneData, catalog);
            break;
        }
      }
    }
  });
  return planData;
}

function replaceObject(modifiedPath, layer, planData, editingActions, sceneData, oldSceneData, catalog) {

  switch (modifiedPath[3]) {
    case "layer":
      break;
    case "vertices":
      break;
    case "holes":
      let newHoleData = layer.holes.get(modifiedPath[4]);
      let lineID = newHoleData.line;
      if (modifiedPath[5] === 'selected') {
        // I remove only the hole without removing the wall
        removeHole(planData, layer, newHoleData.id);
        addHole(sceneData, planData, layer, newHoleData.id, catalog, editingActions);
      } else {
        layer.lines.get(lineID).holes.forEach(holeID => {
          removeHole(planData, layer, holeID);
        });
        removeLine(planData, layer, lineID);
        addLine(sceneData, planData, layer, lineID, catalog, editingActions);
        layer.lines.get(lineID).holes.forEach(holeID => {
          addHole(sceneData, planData, layer, holeID, catalog, editingActions);
        });
      }
      break;
    case "lines":
      removeLine(planData, layer, modifiedPath[4]);
      addLine(sceneData, planData, layer, modifiedPath[4], catalog, editingActions);
      break;
    case "areas":
      removeArea(planData, layer, modifiedPath[4]);
      addArea(sceneData, planData, layer, modifiedPath[4], catalog, editingActions);
      break;
    case "items":
      removeItem(planData, layer, modifiedPath[4]);
      addItem(sceneData, planData, layer, modifiedPath[4], catalog, editingActions);
      break;

    case "visible":
      let layerGraph = planData.sceneGraph.layers[layer.id];
      layerGraph.visible = layer.visible;
      for (let lineID in layerGraph.lines) {
        layerGraph.lines[lineID].visible = layer.visible;
      }

      for (let areaID in layerGraph.areas) {
        layerGraph.areas[areaID].visible = layer.visible;
      }

      for (let itemID in layerGraph.items) {
        layerGraph.items[itemID].visible = layer.visible;
      }

      for (let holeID in layerGraph.holes) {
        layerGraph.holes[holeID].visible = layer.visible;
      }
      break;
  }
}

function removeObject(modifiedPath, layer, planData, editingActions, sceneData, oldSceneData, catalog) {

  switch (modifiedPath[3]) {
    case "lines":
      // Here I remove the line with all its holes
      let lineID = modifiedPath[4];
      let oldLayer = oldSceneData.layers.get(layer.id);
      oldLayer.lines.get(lineID).holes.forEach(holeID => {
        removeHole(planData, layer, holeID);
      });
      removeLine(planData, layer, lineID);
      if (modifiedPath.length > 5) {
        // I removed an hole, so I should add the new line
        addLine(sceneData, planData, layer, lineID, catalog, editingActions);
        layer.lines.get(lineID).holes.forEach(holeID => {
          addHole(sceneData, planData, layer, holeID, catalog, editingActions);
        });
      }
      break;
    case "areas":
      if (modifiedPath.length === 5) {
        // I am removing an entire area
        removeArea(planData, layer, modifiedPath[4]);
      }
      break;
    case "items":
      if (modifiedPath.length === 5) {
        // I am removing an item
        removeItem(planData, layer, modifiedPath[4]);
      }
      break;
  }
}

function removeHole(planData, layer, holeToRemoveID) {
  let holeToRemove = planData.sceneGraph.layers[layer.id].holes[holeToRemoveID];
  planData.plan.remove(holeToRemove);
  disposeObject(holeToRemove);
  delete planData.sceneGraph.layers[layer.id].holes[holeToRemoveID];
  holeToRemove = null;
  updateBoundingBox(planData);
}

function removeLine(planData, layer, lineID) {
  let line3D = planData.sceneGraph.layers[layer.id].lines[lineID];
  planData.plan.remove(line3D);
  disposeObject(line3D);
  delete planData.sceneGraph.layers[layer.id].lines[lineID];
  line3D = null;
  updateBoundingBox(planData);
}

function removeArea(planData, layer, areaID) {
  let area3D = planData.sceneGraph.layers[layer.id].areas[areaID];
  planData.plan.remove(area3D);
  disposeObject(area3D);
  delete planData.sceneGraph.layers[layer.id].areas[areaID];
  area3D = null;
  updateBoundingBox(planData);
}

function removeItem(planData, layer, itemID) {
  let item3D = planData.sceneGraph.layers[layer.id].items[itemID];
  planData.plan.remove(item3D);
  disposeObject(item3D);
  delete planData.sceneGraph.layers[layer.id].items[itemID];
  item3D = null;
  updateBoundingBox(planData);
}

function addObject(modifiedPath, layer, planData, editingActions, sceneData, oldSceneData, catalog) {

  switch (modifiedPath[3]) {
    case "lines":
      if (modifiedPath.length === 5) {
        // I have to add a line
        addLine(sceneData, planData, layer, modifiedPath[4], catalog, editingActions);
      }
      break;
    case "areas":
      if (modifiedPath.length === 5) {
        // I have to add an area
        addArea(sceneData, planData, layer, modifiedPath[4], catalog, editingActions);
      }
      break;
    case "items":
      if (modifiedPath.length === 5) {
        // I have to add an area
        addItem(sceneData, planData, layer, modifiedPath[4], catalog, editingActions);
      }
      break;
  }
}

function addHole(sceneData, planData, layer, holeID, catalog, editingActions) {
  let holeData = layer.holes.get(holeID);

  // Create the hole object
  catalog.getElement(holeData.type).render3D(holeData, layer, sceneData).then(object => {

    let pivot = new Three.Object3D();
    pivot.add(object);

    let line = layer.lines.get(holeData.line);

    // First of all I need to find the vertices of this line
    let vertex0 = layer.vertices.get(line.vertices.get(0));
    let vertex1 = layer.vertices.get(line.vertices.get(1));

    if (vertex0.x > vertex1.x) {
      let app = vertex0;
      vertex0 = vertex1;
      vertex1 = app;
    }

    let distance = Math.sqrt(Math.pow(vertex0.x - vertex1.x, 2) + Math.pow(vertex0.y - vertex1.y, 2));
    let alpha = Math.asin((vertex1.y - vertex0.y) / distance);

    let boundingBox = new Three.Box3().setFromObject(pivot);
    let center = [
      (boundingBox.max.x - boundingBox.min.x) / 2 + boundingBox.min.x,
      (boundingBox.max.y - boundingBox.min.y) / 2 + boundingBox.min.y,
      (boundingBox.max.z - boundingBox.min.z) / 2 + boundingBox.min.z];

    let holeAltitude = holeData.properties.get('altitude').get('length');

    let holeHeight = holeData.properties.get('height').get('length');

    pivot.rotation.y = alpha;
    pivot.position.x = vertex0.x + distance * holeData.offset * Math.cos(alpha) - center[2] * Math.sin(alpha);
    pivot.position.y = holeAltitude + holeHeight / 2 - center[1];
    pivot.position.z = -vertex0.y - distance * holeData.offset * Math.sin(alpha) - center[2] * Math.cos(alpha);

    planData.plan.add(pivot);
    planData.sceneGraph.layers[layer.id].holes[holeData.id] = pivot;

    applyInteract(pivot, () => {
      return editingActions.selectHole(layer.id, holeData.id)
    });

    updateBoundingBox(planData);
  });
}

function addLine(sceneData, planData, layer, lineID, catalog, editingActions) {
  let line = layer.lines.get(lineID);

  // First of all I need to find the vertices of this line
  let vertex0 = layer.vertices.get(line.vertices.get(0));
  let vertex1 = layer.vertices.get(line.vertices.get(1));

  if (vertex0.x > vertex1.x) {
    let app = vertex0;
    vertex0 = vertex1;
    vertex1 = app;
  }

  catalog.getElement(line.type).render3D(line, layer, sceneData).then(line3D => {

    let pivot = new Three.Object3D();
    pivot.add(line3D);

    pivot.position.x = vertex0.x;
    pivot.position.y = layer.altitude;
    pivot.position.z = -vertex0.y;

    pivot.visible = layer.visible;

    planData.plan.add(pivot);
    planData.sceneGraph.layers[layer.id].lines[lineID] = pivot;

    applyInteract(pivot, () => {
      return editingActions.selectLine(layer.id, line.id);
    });

    updateBoundingBox(planData);
  });
}

function addArea(sceneData, planData, layer, areaID, catalog, editingActions) {
  let area = layer.areas.get(areaID);
  let interactFunction = () => {
    editingActions.selectArea(layer.id, area.id);
  };

  catalog.getElement(area.type).render3D(area, layer, sceneData).then(area3D => {
    let pivot = new Three.Object3D();
    pivot.add(area3D);
    pivot.position.y = layer.altitude;
    planData.plan.add(pivot);
    planData.sceneGraph.layers[layer.id].areas[area.id] = pivot;
    pivot.visible = layer.visible;

    applyInteract(pivot, interactFunction);

    updateBoundingBox(planData);
  });
}

function addItem(sceneData, planData, layer, itemID, catalog, editingActions) {
  let item = layer.items.get(itemID);

  catalog.getElement(item.type).render3D(item, layer, sceneData).then(item3D => {

    let pivot = new Three.Object3D();
    pivot.add(item3D);

    pivot.rotation.y = item.rotation * Math.PI / 180;
    pivot.position.x = item.x;
    pivot.position.z = -item.y;

    applyInteract(item3D, () => {
        editingActions.selectItem(layer.id, item.id);
      }
    );

    planData.plan.add(pivot);
    planData.sceneGraph.layers[layer.id].items[item.id] = pivot;

    updateBoundingBox(planData);

  });

}

// Apply interact function to children of an Object3D
function applyInteract(object, interactFunction) {
  object.traverse(function (child) {
    if (child instanceof Three.Mesh) {
      child.interact = interactFunction;
    }
  });
}

function updateBoundingBox(planData) {
  let newBoundingBox = new Three.Box3().setFromObject(planData.plan);

  if (isFinite(newBoundingBox.max.x)
    || isFinite(newBoundingBox.min.x)
    || isFinite(newBoundingBox.max.y)
    || isFinite(newBoundingBox.min.y)
    || isFinite(newBoundingBox.max.z)
    || isFinite(newBoundingBox.min.z)) {

    let newCenter = [
      (newBoundingBox.max.x - newBoundingBox.min.x) / 2 + newBoundingBox.min.x,
      (newBoundingBox.max.y - newBoundingBox.min.y) / 2 + newBoundingBox.min.y,
      (newBoundingBox.max.z - newBoundingBox.min.z) / 2 + newBoundingBox.min.z];

    planData.plan.position.x -= newCenter[0];
    planData.plan.position.y -= newCenter[1];
    planData.plan.position.z -= newCenter[2];

    planData.grid.position.x -= newCenter[0];
    planData.grid.position.y -= newCenter[1];
    planData.grid.position.z -= newCenter[2];

    planData.boundingBox = newBoundingBox;
  }
}

/**
 * Reduces the number of change properties diffs
 * @param diffArray the array of the diffs
 * @returns {Array}
 */
function minimizeChangePropertiesDiffs(diffArray) {

  let minimizedDiffs = [];

  let propertiesDiffs = [];

  // Find all diffs for a changed property
  diffArray.forEach(currentDiff => {
    let splittedDiff = currentDiff.path.split("/");
    if (splittedDiff[5] === 'properties') {
      propertiesDiffs.push([currentDiff, splittedDiff[4]]);
    } else {
      minimizedDiffs.push(currentDiff);
    }

  });

  let sortedPropertiesDiffs = propertiesDiffs.sort((a, b) => {
    return a[1] < b[1];
  });

  for (let i = 0; i < sortedPropertiesDiffs.length; i++) {
    minimizedDiffs.push(sortedPropertiesDiffs[0][0]);
    let futureIndex = i + 1;
    while (futureIndex < sortedPropertiesDiffs.length && sortedPropertiesDiffs[i][1] === sortedPropertiesDiffs[futureIndex][1]) {
      futureIndex++;
    }
    i = futureIndex - 1;
  }

  return minimizedDiffs;
}
<|endoftext|>


<commit_before>var pump = require("pump");
var assign = require("lodash/assign");
var minify = require("../stream/minify");
var transpile = require("../stream/transpile");
var concat = require("../bundle/concat_stream");
var makeBundle = require("../stream/make_bundle");
var filterBundleGraph = require("../stream/filter_bundle_graph");

var assignDefaultOptions = require("../assign_default_options");
var createWriteStream = require("../bundle/write_bundles").createWriteStream;
var createBundleGraphStream = require("../graph/make_graph_with_bundles")
	.createBundleGraphStream;

module.exports = function(cfg, options) {
	// Use the build-development environment.
	if (!options) options = {};

	var isDestProvided = !!options.dest;

	// minification is disabled by default
	options.minify = options.minify == null ? false : options.minify;

	// tree shaking is disabled.
	var config  = { treeShaking: false, ...cfg };

	try {
		options = assignDefaultOptions(config, options);
	} catch(err) {
		return Promise.reject(err);
	}

	// if `dest` was not provided in the options object, override the default
	// value so the development bundle is written out in the root folder
	assign(options, {
		defaultBundlesPathName: "",
		dest: isDestProvided ? options.dest : "",
		buildStealConfig: {
			env: "bundle-build"
		},
		// Never really want to remove development strings when using
		// deps/dev bundles
		removeDevelopmentCode: false
	});

	return new Promise(function(resolve, reject) {
		var writeStream = pump(
			createBundleGraphStream(config, options),
			filterBundleGraph(),
			transpile(),
			minify(),
			makeBundle(),
			concat(),
			createWriteStream(),
			function(err) {
				if (err) reject(err);
			}
		);

		writeStream.on("data", function(data){
			this.end();
			resolve(data);
		});
	});
};
<commit_msg>Use Object.assign()<commit_after>var pump = require("pump");
var assign = require("lodash/assign");
var minify = require("../stream/minify");
var transpile = require("../stream/transpile");
var concat = require("../bundle/concat_stream");
var makeBundle = require("../stream/make_bundle");
var filterBundleGraph = require("../stream/filter_bundle_graph");

var assignDefaultOptions = require("../assign_default_options");
var createWriteStream = require("../bundle/write_bundles").createWriteStream;
var createBundleGraphStream = require("../graph/make_graph_with_bundles")
	.createBundleGraphStream;

module.exports = function(cfg, options) {
	// Use the build-development environment.
	if (!options) options = {};

	var isDestProvided = !!options.dest;

	// minification is disabled by default
	options.minify = options.minify == null ? false : options.minify;

	// tree shaking is disabled.
	var config = Object.assign({ treeShaking: false }, cfg);

	try {
		options = assignDefaultOptions(config, options);
	} catch(err) {
		return Promise.reject(err);
	}

	// if `dest` was not provided in the options object, override the default
	// value so the development bundle is written out in the root folder
	assign(options, {
		defaultBundlesPathName: "",
		dest: isDestProvided ? options.dest : "",
		buildStealConfig: {
			env: "bundle-build"
		},
		// Never really want to remove development strings when using
		// deps/dev bundles
		removeDevelopmentCode: false
	});

	return new Promise(function(resolve, reject) {
		var writeStream = pump(
			createBundleGraphStream(config, options),
			filterBundleGraph(),
			transpile(),
			minify(),
			makeBundle(),
			concat(),
			createWriteStream(),
			function(err) {
				if (err) reject(err);
			}
		);

		writeStream.on("data", function(data){
			this.end();
			resolve(data);
		});
	});
};
<|endoftext|>


<commit_before>"use strict";
const path = require('path');
const os = require('os');
const Convert = require('./convert-source-map');
const fs = require('../file-system');
const SourceInclusion = require('./source-inclusion').SourceInclusion;
const DependencyInclusion = require('./dependency-inclusion').DependencyInclusion;

exports.Bundle = class {
  constructor(bundler, config) {
    this.bundler = bundler;
    this.config = config;
    this.dependencies = [];
    this.moduleId = config.name.replace(path.extname(config.name), '');
    this.includes = (config.source || []).map(x => new SourceInclusion(this, x));
    this.buildOptions = bundler.interpretBuildOptions(config.options, bundler.buildOptions);
    this.requiresBuild = true;
    this.Minimatch = require('minimatch').Minimatch; //nested dep of vinyl-fs
  }

  static create(bundler, config) {
    let bundle = new exports.Bundle(bundler, config);
    let dependencies = config.dependencies || [];

    return Promise.all(
      dependencies.map(dependency => bundler.configureDependency(dependency)
        .then(description => bundle.addDependency(description)))
    ).then(() => bundle);
  }

  createMatcher(pattern) {
    return new this.Minimatch(pattern);
  }

  addDependency(description) {
    this.dependencies.push(description);
    this.includes.push(new DependencyInclusion(this, description));
  }

  trySubsume(item) {
    let includes = this.includes;

    for (let i = 0, ii = includes.length; i < ii; ++i) {
      if (includes[i].trySubsume(item)) {
        this.requiresBuild = true;
        return true;
      }
    }

    return false;
  }

  transform() {
    if (this.requiresBuild) {
      return Promise.all(this.includes.map(x => x.transform()));
    }

    return Promise.resolve();
  }

  getBundledModuleIds() {
    return unique(this.includes.reduce((a, b) => a.concat(b.getAllModuleIds()), []));
  }

  getBundledFiles() {
    return this.includes.reduce((a, b) => a.concat(b.getAllFiles()), []);
  }

  write(dest) {
    if (!this.requiresBuild) {
      return Promise.resolve();
    }

    let work = Promise.resolve();
    let loaderOptions = this.bundler.loaderOptions;
    let buildOptions = this.buildOptions;
    let files = [];

    if (loaderOptions.inject === this.config.name) {
      work = fs.readFile(loaderOptions.source)
        .then(data => files.push({ contents: data.toString() }))
        .then(() => files.push({ contents: this.writeLoaderConfig(loaderOptions) }));
    }

    return work.then(() => {
      files = files.concat(this.getBundledFiles());

      if (loaderOptions.inject === this.config.name) {
        files.push({ contents: this.writeBootstrapper(loaderOptions) });
      }

      const Concat = require('./concat-with-sourcemaps');
      let concat = new Concat(true, this.config.name, os.EOL);
      let needsSourceMap = false;

      for (let i = 0; i < files.length; ++i) {
        let currentFile = files[i];
        let sourceMap = buildOptions.sourcemaps ? currentFile.sourceMap : undefined;

        if (sourceMap) {
          needsSourceMap = true;
        }

        concat.add(currentFile.path, currentFile.contents, sourceMap ? JSON.stringify(sourceMap) : undefined);
      }

      let mapContents;
      let contents = concat.content;
      let mapFileName = this.config.name + '.map';
      let mapSourceRoot = path.relative(
        path.join(process.cwd(), this.bundler.project.paths.output),
        path.join(process.cwd(), this.bundler.project.paths.root)
      );

      if (buildOptions.minify) {
        let minificationOptions = { fromString: true };

        if (needsSourceMap) {
          minificationOptions.inSourceMap = Convert.fromJSON(concat.sourceMap).toObject();
          minificationOptions.outSourceMap = mapFileName;
          minificationOptions.sourceRoot = mapSourceRoot;
        }

        const UglifyJS = require('uglify-js'); //added to user project devDependencies
        let minificationResult = UglifyJS.minify(String(contents), minificationOptions);

        contents = minificationResult.code;
        mapContents = needsSourceMap ? Convert.fromJSON(minificationResult.map).toJSON() : undefined;
      } else if (needsSourceMap) {
        mapContents = Convert.fromJSON(concat.sourceMap)
          .setProperty('sourceRoot', mapSourceRoot)
          .toJSON();

        contents += os.EOL + '//# sourceMappingURL=' + mapFileName;
      }

      return fs.writeFile(path.join(dest, this.config.name), contents).then(() => {
        this.requiresBuild = false;

        if (mapContents) {
          return fs.writeFile(path.join(dest, mapFileName), mapContents);
        }
      });
    });
  }

  writeLoaderConfig(loaderOptions) {
    switch(loaderOptions.type) {
      case 'require':
        return this.createRequireJSConfig();
      default:
        throw new Error(`Loader configuration style ${loaderOptions.type} is not supported.`);
    }
  }

  writeBootstrapper(loaderOptions) {
    switch(loaderOptions.type) {
      case 'require':
        return '';
        //return 'require(["aurelia-bootstrapper"]);';
      default:
        throw new Error(`Bootstrapper configuration style ${loaderOptions.type} is not supported.`);
    }
  }

  createRequireJSConfig() {
    let bundler = this.bundler;
    let loaderOptions = bundler.loaderOptions;
    let loaderConfig = bundler.loaderConfig;
    let bundles = bundler.bundles;
    let name = this.config.name;

    let config = Object.assign({
      bundles: {}
    }, loaderConfig);

    for (let i = 0; i < bundles.length; ++i) {
      let currentBundle = bundles[i];
      let currentName = currentBundle.config.name;

      if (currentName === name) {
        continue;
      }

      config.bundles[currentBundle.moduleId] = currentBundle.getBundledModuleIds();
      config.paths[currentBundle.moduleId] = '../' + bundler.project.paths.output + '/' + currentBundle.moduleId;
    }

    return 'requirejs.config(' + JSON.stringify(config) + ');'
  }
}

function unique(collection){
  var u = {}, a = [];

  for(var i = 0, l = collection.length; i < l; ++i){
    if(u.hasOwnProperty(collection[i])) {
      continue;
    }

    a.push(collection[i]);
    u[collection[i]] = 1;
  }

  return a;
}
<commit_msg>fix(build): minimatch reference too late<commit_after>"use strict";
const path = require('path');
const os = require('os');
const Convert = require('./convert-source-map');
const fs = require('../file-system');
const SourceInclusion = require('./source-inclusion').SourceInclusion;
const DependencyInclusion = require('./dependency-inclusion').DependencyInclusion;

exports.Bundle = class {
  constructor(bundler, config) {
    this.Minimatch = require('minimatch').Minimatch; //nested dep of vinyl-fs
    this.bundler = bundler;
    this.config = config;
    this.dependencies = [];
    this.moduleId = config.name.replace(path.extname(config.name), '');
    this.includes = (config.source || []).map(x => new SourceInclusion(this, x));
    this.buildOptions = bundler.interpretBuildOptions(config.options, bundler.buildOptions);
    this.requiresBuild = true;
  }

  static create(bundler, config) {
    let bundle = new exports.Bundle(bundler, config);
    let dependencies = config.dependencies || [];

    return Promise.all(
      dependencies.map(dependency => bundler.configureDependency(dependency)
        .then(description => bundle.addDependency(description)))
    ).then(() => bundle);
  }

  createMatcher(pattern) {
    return new this.Minimatch(pattern);
  }

  addDependency(description) {
    this.dependencies.push(description);
    this.includes.push(new DependencyInclusion(this, description));
  }

  trySubsume(item) {
    let includes = this.includes;

    for (let i = 0, ii = includes.length; i < ii; ++i) {
      if (includes[i].trySubsume(item)) {
        this.requiresBuild = true;
        return true;
      }
    }

    return false;
  }

  transform() {
    if (this.requiresBuild) {
      return Promise.all(this.includes.map(x => x.transform()));
    }

    return Promise.resolve();
  }

  getBundledModuleIds() {
    return unique(this.includes.reduce((a, b) => a.concat(b.getAllModuleIds()), []));
  }

  getBundledFiles() {
    return this.includes.reduce((a, b) => a.concat(b.getAllFiles()), []);
  }

  write(dest) {
    if (!this.requiresBuild) {
      return Promise.resolve();
    }

    let work = Promise.resolve();
    let loaderOptions = this.bundler.loaderOptions;
    let buildOptions = this.buildOptions;
    let files = [];

    if (loaderOptions.inject === this.config.name) {
      work = fs.readFile(loaderOptions.source)
        .then(data => files.push({ contents: data.toString() }))
        .then(() => files.push({ contents: this.writeLoaderConfig(loaderOptions) }));
    }

    return work.then(() => {
      files = files.concat(this.getBundledFiles());

      if (loaderOptions.inject === this.config.name) {
        files.push({ contents: this.writeBootstrapper(loaderOptions) });
      }

      const Concat = require('./concat-with-sourcemaps');
      let concat = new Concat(true, this.config.name, os.EOL);
      let needsSourceMap = false;

      for (let i = 0; i < files.length; ++i) {
        let currentFile = files[i];
        let sourceMap = buildOptions.sourcemaps ? currentFile.sourceMap : undefined;

        if (sourceMap) {
          needsSourceMap = true;
        }

        concat.add(currentFile.path, currentFile.contents, sourceMap ? JSON.stringify(sourceMap) : undefined);
      }

      let mapContents;
      let contents = concat.content;
      let mapFileName = this.config.name + '.map';
      let mapSourceRoot = path.relative(
        path.join(process.cwd(), this.bundler.project.paths.output),
        path.join(process.cwd(), this.bundler.project.paths.root)
      );

      if (buildOptions.minify) {
        let minificationOptions = { fromString: true };

        if (needsSourceMap) {
          minificationOptions.inSourceMap = Convert.fromJSON(concat.sourceMap).toObject();
          minificationOptions.outSourceMap = mapFileName;
          minificationOptions.sourceRoot = mapSourceRoot;
        }

        const UglifyJS = require('uglify-js'); //added to user project devDependencies
        let minificationResult = UglifyJS.minify(String(contents), minificationOptions);

        contents = minificationResult.code;
        mapContents = needsSourceMap ? Convert.fromJSON(minificationResult.map).toJSON() : undefined;
      } else if (needsSourceMap) {
        mapContents = Convert.fromJSON(concat.sourceMap)
          .setProperty('sourceRoot', mapSourceRoot)
          .toJSON();

        contents += os.EOL + '//# sourceMappingURL=' + mapFileName;
      }

      return fs.writeFile(path.join(dest, this.config.name), contents).then(() => {
        this.requiresBuild = false;

        if (mapContents) {
          return fs.writeFile(path.join(dest, mapFileName), mapContents);
        }
      });
    });
  }

  writeLoaderConfig(loaderOptions) {
    switch(loaderOptions.type) {
      case 'require':
        return this.createRequireJSConfig();
      default:
        throw new Error(`Loader configuration style ${loaderOptions.type} is not supported.`);
    }
  }

  writeBootstrapper(loaderOptions) {
    switch(loaderOptions.type) {
      case 'require':
        return '';
        //return 'require(["aurelia-bootstrapper"]);';
      default:
        throw new Error(`Bootstrapper configuration style ${loaderOptions.type} is not supported.`);
    }
  }

  createRequireJSConfig() {
    let bundler = this.bundler;
    let loaderOptions = bundler.loaderOptions;
    let loaderConfig = bundler.loaderConfig;
    let bundles = bundler.bundles;
    let name = this.config.name;

    let config = Object.assign({
      bundles: {}
    }, loaderConfig);

    for (let i = 0; i < bundles.length; ++i) {
      let currentBundle = bundles[i];
      let currentName = currentBundle.config.name;

      if (currentName === name) {
        continue;
      }

      config.bundles[currentBundle.moduleId] = currentBundle.getBundledModuleIds();
      config.paths[currentBundle.moduleId] = '../' + bundler.project.paths.output + '/' + currentBundle.moduleId;
    }

    return 'requirejs.config(' + JSON.stringify(config) + ');'
  }
}

function unique(collection){
  var u = {}, a = [];

  for(var i = 0, l = collection.length; i < l; ++i){
    if(u.hasOwnProperty(collection[i])) {
      continue;
    }

    a.push(collection[i]);
    u[collection[i]] = 1;
  }

  return a;
}
<|endoftext|>


<commit_before>var TRAVIS_BRANCH_CONFIGS = {
  develop: {
    AWS_BUCKET: 'teach.mofostaging.net',
    NODE_ENV: 'production',
    ORIGIN: 'https://teach.mofostaging.net',
    SHOW_DEV_RIBBON: 'on',
    PLEDGE_MAILINGLIST_URL: 'https://sendto.mozilla.org/page/s/mentor-signup-for-teach-site-staging-testing',
    PLEDGE_MAILINGLIST_PRIVACY_NAME: 'custom-3557',
    OPTIMIZELY_ID: '206878104',
    OPTIMIZELY_ACTIVE: 'yes',
    MAKE_METADATA_URL: 'https://{username}.mywebmaker.org/makes.json'
  },
  master: {
    AWS_BUCKET: 'teach-mozilla-org-s3bucket-1oinic8tfxxim',
    NODE_ENV: 'production',
    TEACH_API_URL: 'https://teach-api-production.herokuapp.com',
    ORIGIN: 'https://teach.mozilla.org',
    PLEDGE_MAILINGLIST_URL: 'https://sendto.mozilla.org/page/s/mentor-signup',
    PLEDGE_MAILINGLIST_PRIVACY_NAME: 'custom-1788',
    OPTIMIZELY_ID: '206878104',
    OPTIMIZELY_ACTIVE: 'yes',
    MAKE_METADATA_URL: 'https://{username}.makes.org/makes.json'
  }
};

exports.getS3Env = function(env) {
  env = env || process.env;

  var config = TRAVIS_BRANCH_CONFIGS[env.TRAVIS_BRANCH];

  if (env.TRAVIS !== 'true' || env.TRAVIS_PULL_REQUEST !== 'false' ||
      !config) {
    return null;
  }

  return config;
};
<commit_msg>travis env var updates<commit_after>var TRAVIS_BRANCH_CONFIGS = {
  develop: {
    AWS_BUCKET: 'teach.mofostaging.net',
    NODE_ENV: 'production',
    ORIGIN: 'https://teach.mofostaging.net',
    SHOW_DEV_RIBBON: 'on',
    NEWSLETTER_MAILINGLIST_URL: 'https://sendto.mozilla.org/page/s/mentor-signup-for-teach-site-staging-testing',
    NEWSLETTER_MAILINGLIST_PRIVACY_NAME: 'custom-3557',
    OPTIMIZELY_ID: '206878104',
    OPTIMIZELY_ACTIVE: 'yes',
    MAKE_METADATA_URL: 'https://{username}.mywebmaker.org/makes.json'
  },
  master: {
    AWS_BUCKET: 'teach-mozilla-org-s3bucket-1oinic8tfxxim',
    NODE_ENV: 'production',
    TEACH_API_URL: 'https://teach-api-production.herokuapp.com',
    ORIGIN: 'https://teach.mozilla.org',
    NEWSLETTER_MAILINGLIST_URL: 'https://sendto.mozilla.org/page/s/mentor-signup',
    NEWSLETTER_MAILINGLIST_PRIVACY_NAME: 'custom-1788',
    OPTIMIZELY_ID: '206878104',
    OPTIMIZELY_ACTIVE: 'yes',
    MAKE_METADATA_URL: 'https://{username}.makes.org/makes.json'
  }
};

exports.getS3Env = function(env) {
  env = env || process.env;

  var config = TRAVIS_BRANCH_CONFIGS[env.TRAVIS_BRANCH];

  if (env.TRAVIS !== 'true' || env.TRAVIS_PULL_REQUEST !== 'false' ||
      !config) {
    return null;
  }

  return config;
};
<|endoftext|>


<commit_before>import React, { Component, Platform } from 'react';
import { Actions } from 'react-native-router-flux';
import ListItem from 'react-native-elements/src/list/ListItem';
import List from 'react-native-elements/src/list/List';
import { ScrollView } from 'react-native';
import DeviceInfo from 'react-native-device-info';
import Share from 'react-native-share';
import * as StoreReview from 'react-native-store-review';

import Helper from '../../utils/helper';

class UserCenter extends Component {
  static componentName = 'UserCenter';

  static shareToFriend() {
    const option = {
      title: 'Growth：带你成为顶尖开发者',
      message: '我现在使用Growth，这是一款专注于Web开发者成长的应用，涵盖Web开发的流程及技术栈，Web开发的学习路线、成长衡量等各方面。快来下载吧!',
      url: 'http://growth.ren/',
    };
    Share.open(option);
  }

  static ratingApp() {
    if (StoreReview.isAvailable) {
      return StoreReview.requestReview();
    }
    let link = 'market://details?id=ren.growth';

    if (Platform.OS === 'iOS') {
      link = 'itms://itunes.apple.com/us/app/apple-store/myiosappid?mt=8';
    }

    return Helper.openLink(link);
  }

  constructor(render) {
    super(render);
    this.state = {
      version: DeviceInfo.getVersion(),
    };
    UserCenter.shareToFriend = UserCenter.shareToFriend.bind(this);
  }

  render = () => (
    <ScrollView>
      <List>
        <ListItem
          title={'我的书签'}
          leftIcon={{ name: 'bookmark' }}
          onPress={() => Actions.comingSoon()}
        />
        <ListItem
          title={'我的技能分布'}
          leftIcon={{ name: 'trending-up' }}
          onPress={() => Actions.comingSoon()}
        />
      </List>
      <List>
        <ListItem
          title={'关于'}
          leftIcon={{ name: 'info' }}
          onPress={Actions.aboutUs}
        />
        <ListItem
          title={'分享给好友'}
          leftIcon={{ name: 'group-work' }}
          onPress={() => UserCenter.shareToFriend()}
        />
        <ListItem
          title={'觉得不错，给个好评'}
          onPress={() => UserCenter.ratingApp()}
          leftIcon={{ name: 'thumb-up' }}
        />
        <ListItem
          title={'发现Bug'}
          onPress={() => Helper.openLink('https://github.com/phodal/growth/issues')}
          leftIcon={{ name: 'bug-report' }}
        />
        <ListItem
          title={'功能反馈'}
          onPress={() => Helper.openLink('https://jinshuju.net/f/aZ4CAS')}
          leftIcon={{ name: 'feedback' }}
        />
      </List>
      <List>
        <ListItem
          hideChevron
          title={'版权声明'}
          leftIcon={{ name: 'copyright' }}
        />
        <ListItem
          hideChevron
          title={'微信公众号'}
          leftIcon={{ name: 'code' }}
          subtitle={'phodal-weixin'}
        />
        <ListItem
          hideChevron
          title={'当前构建版本'}
          leftIcon={{ name: 'perm-device-information' }}
          subtitle={this.state.version}
        />
      </List>
    </ScrollView>
  );
}

export default UserCenter;
<commit_msg>[UserCenter] fix rating app issue<commit_after>import React, { Component, Platform } from 'react';
import { Actions } from 'react-native-router-flux';
import ListItem from 'react-native-elements/src/list/ListItem';
import List from 'react-native-elements/src/list/List';
import { ScrollView } from 'react-native';
import DeviceInfo from 'react-native-device-info';
import Share from 'react-native-share';
import * as StoreReview from 'react-native-store-review';

import Helper from '../../utils/helper';

class UserCenter extends Component {
  static componentName = 'UserCenter';

  static shareToFriend() {
    const option = {
      title: 'Growth：带你成为顶尖开发者',
      message: '我现在使用Growth，这是一款专注于Web开发者成长的应用，涵盖Web开发的流程及技术栈，Web开发的学习路线、成长衡量等各方面。快来下载吧!',
      url: 'http://growth.ren/',
    };
    Share.open(option);
  }

  static ratingApp() {
    let link = 'market://details?id=ren.growth';

    if (Platform.OS === 'iOS') {
      if (StoreReview.isAvailable) {
        return StoreReview.requestReview();
      }

      link = 'itms://itunes.apple.com/us/app/apple-store/myiosappid?mt=8';
    }

    return Helper.openLink(link);
  }

  constructor(render) {
    super(render);
    this.state = {
      version: DeviceInfo.getVersion(),
    };
    UserCenter.shareToFriend = UserCenter.shareToFriend.bind(this);
  }

  render = () => (
    <ScrollView>
      <List>
        <ListItem
          title={'我的书签'}
          leftIcon={{ name: 'bookmark' }}
          onPress={() => Actions.comingSoon()}
        />
        <ListItem
          title={'我的技能分布'}
          leftIcon={{ name: 'trending-up' }}
          onPress={() => Actions.comingSoon()}
        />
      </List>
      <List>
        <ListItem
          title={'关于'}
          leftIcon={{ name: 'info' }}
          onPress={Actions.aboutUs}
        />
        <ListItem
          title={'分享给好友'}
          leftIcon={{ name: 'group-work' }}
          onPress={() => UserCenter.shareToFriend()}
        />
        <ListItem
          title={'觉得不错，给个好评'}
          onPress={() => UserCenter.ratingApp()}
          leftIcon={{ name: 'thumb-up' }}
        />
        <ListItem
          title={'发现Bug'}
          onPress={() => Helper.openLink('https://github.com/phodal/growth/issues')}
          leftIcon={{ name: 'bug-report' }}
        />
        <ListItem
          title={'功能反馈'}
          onPress={() => Helper.openLink('https://jinshuju.net/f/aZ4CAS')}
          leftIcon={{ name: 'feedback' }}
        />
      </List>
      <List>
        <ListItem
          hideChevron
          title={'版权声明'}
          leftIcon={{ name: 'copyright' }}
        />
        <ListItem
          hideChevron
          title={'微信公众号'}
          leftIcon={{ name: 'code' }}
          subtitle={'phodal-weixin'}
        />
        <ListItem
          hideChevron
          title={'当前构建版本'}
          leftIcon={{ name: 'perm-device-information' }}
          subtitle={this.state.version}
        />
      </List>
    </ScrollView>
  );
}

export default UserCenter;
<|endoftext|>