<commit_before>const path = require('path')
const webpack = require('webpack')

const {
  parseEntry,
  getMainEntry,
  normalizePath,
  getPlatformExts,
  getPlatformCssnano
} = require('@dcloudio/uni-cli-shared')

const WebpackUniAppPlugin = require('../../packages/webpack-uni-app-loader/plugin/index')

const modifyVueLoader = require('../vue-loader')

const {
  createTemplateCacheLoader
} = require('../cache-loader')

function createUniMPPlugin () {
  const WebpackUniMPPlugin = require('@dcloudio/webpack-uni-mp-loader/lib/plugin/index-new')
  return new WebpackUniMPPlugin()
}

function getProvides () {
  const uniPath = require('@dcloudio/uni-cli-shared/lib/platform').getMPRuntimePath()
  const uniCloudPath = path.resolve(__dirname, '../../packages/uni-cloud/dist/index.js')
  const provides = {
    uni: [uniPath, 'default'],
    uniCloud: [uniCloudPath, 'default']
  }

  if (process.env.UNI_USING_VUE3) {
    provides.uni = ['@dcloudio/uni-' + process.env.UNI_PLATFORM + '/dist/uni.api.esm.js', 'default']
    provides.createMiniProgramApp = [uniPath, 'createApp']
  }

  if (process.env.UNI_USING_COMPONENTS) {
    if (process.env.UNI_SUBPACKGE) {
      provides.createApp = [uniPath, 'createSubpackageApp']
    } else if (process.env.UNI_MP_PLUGIN) {
      provides.createApp = [uniPath, 'createPlugin']
    } else {
      provides.createApp = [uniPath, 'createApp']
    }
    provides.createPage = [uniPath, 'createPage']
    provides.createComponent = [uniPath, 'createComponent']
  }

  if (
    process.env.UNI_PLATFORM === 'app-plus' &&
    process.env.UNI_USING_V8
  ) {
    provides.__f__ = [path.resolve(__dirname, '../format-log.js'), 'default']

    const cryptoProvide = [path.resolve(__dirname, '../crypto.js'), 'default']
    provides.crypto = cryptoProvide
    provides['window.crypto'] = cryptoProvide
    provides['global.crypto'] = cryptoProvide
  }

  // TODO 目前依赖库 megalo 通过判断 wx 对象是否存在来识别平台做不同处理
  if (
    process.env.UNI_PLATFORM !== 'mp-qq' &&
    process.env.UNI_PLATFORM !== 'mp-weixin' &&
    process.env.UNI_PLATFORM !== 'app-plus'
  ) { // 非微信小程序，自动注入 wx 对象
    provides.wx = provides.uni
  }
  return provides
}

function processWxss (name, assets) {
  const dirname = path.dirname(name)
  const mainWxssCode = `@import "${normalizePath(path.relative(dirname, 'common/main.wxss'))}";`
  const code = `${mainWxssCode}` + assets[name].source().toString()
  assets[name] = {
    size () {
      return Buffer.byteLength(code, 'utf8')
    },
    source () {
      return code
    }
  }
}

const parseRequirePath = path => path.startsWith('common') ? `./${path}` : path

function procssJs (name, assets, hasVendor) {
  const dirname = path.dirname(name)
  const runtimeJsCode = `require('${normalizePath(parseRequirePath(path.relative(dirname, 'common/runtime.js')))}');`
  const vendorJsCode = hasVendor
    ? `require('${normalizePath(parseRequirePath(path.relative(dirname, 'common/vendor.js')))}');` : ''
  const mainJsCode = `require('${normalizePath(parseRequirePath(path.relative(dirname, 'common/main.js')))}');`
  const code = `${runtimeJsCode}${vendorJsCode}${mainJsCode}` + assets[name].source().toString()
  assets[name] = {
    size () {
      return Buffer.byteLength(code, 'utf8')
    },
    source () {
      return code
    }
  }
}

class PreprocessAssetsPlugin {
  apply (compiler) {
    compiler.hooks.emit.tap('PreprocessAssetsPlugin', compilation => {
      const assets = compilation.assets
      const hasMainWxss = assets['common/main.wxss']
      const hasVendor = assets['common/vendor.js']
      Object.keys(assets).forEach(name => {
        if (name.startsWith('common')) {
          return
        }
        const extname = path.extname(name)
        if (extname === '.wxss' && hasMainWxss && process.UNI_ENTRY[name.replace(extname, '')]) {
          processWxss(name, assets)
        } else if (extname === '.js') {
          procssJs(name, assets, hasVendor)
        }
      })
      // delete assets['common/main.js']
      delete assets['app.js']
      delete assets['app.json']
      delete assets['app.wxss']
      delete assets['project.config.json']
    })
  }
}

function initSubpackageConfig (webpackConfig, vueOptions) {
  if (process.env.UNI_OUTPUT_DEFAULT_DIR === process.env.UNI_OUTPUT_DIR) { // 未自定义output
    process.env.UNI_OUTPUT_DIR = path.resolve(process.env.UNI_OUTPUT_DIR, (process.env.UNI_SUBPACKGE || process.env
      .UNI_MP_PLUGIN))
  }
  vueOptions.outputDir = process.env.UNI_OUTPUT_DIR
  webpackConfig.output.path(process.env.UNI_OUTPUT_DIR)
  webpackConfig.output.jsonpFunction('webpackJsonp_' + (process.env.UNI_SUBPACKGE || process.env.UNI_MP_PLUGIN))
}

function addToUniEntry (fileName) {
  fileName && (process.UNI_ENTRY[fileName.split('.')[0]] = path.resolve(process.env.UNI_INPUT_DIR, fileName))
}

module.exports = {
  vueConfig: {
    parallel: false
  },
  webpackConfig (webpackConfig, vueOptions, api) {
    if (!webpackConfig.optimization) {
      webpackConfig.optimization = {}
    }
    // disable noEmitOnErrors
    webpackConfig.optimization.noEmitOnErrors = false

    webpackConfig.optimization.runtimeChunk = {
      name: 'common/runtime'
    }

    webpackConfig.optimization.splitChunks = require('../split-chunks')()

    parseEntry()

    const statCode = process.env.UNI_USING_STAT ? 'import \'@dcloudio/uni-stat\';' : ''

    let beforeCode = 'import \'uni-pages\';'

    const plugins = [
      new WebpackUniAppPlugin(),
      createUniMPPlugin(),
      new webpack.ProvidePlugin(getProvides())
    ]

    if ((process.env.UNI_SUBPACKGE || process.env.UNI_MP_PLUGIN) && process.env.UNI_SUBPACKGE !== 'main') {
      plugins.push(new PreprocessAssetsPlugin())
    }

    {
      const globalEnv = process.env.UNI_PLATFORM === 'mp-alipay' ? 'my' : 'wx';
      [].concat(process.env.UNI_MP_PLUGIN ? process.env.UNI_MP_PLUGIN_MAIN : JSON.parse(process.env
        .UNI_MP_PLUGIN_EXPORT))
        .forEach(fileName => addToUniEntry(fileName))
      beforeCode +=
        `${globalEnv}.__webpack_require_${(process.env.UNI_MP_PLUGIN || 'UNI_MP_PLUGIN').replace(/-/g, '_')}__ = __webpack_require__;`
    }

    const alias = { // 仅 mp-weixin
      'mpvue-page-factory': require.resolve(
        '@dcloudio/vue-cli-plugin-uni/packages/mpvue-page-factory')
    }

    if (process.env.UNI_USING_VUE3) {
      alias.vuex = require.resolve('@dcloudio/vue-cli-plugin-uni/packages/vuex')
      alias['@vue/devtools-api'] = require.resolve('@dcloudio/vue-cli-plugin-uni/packages/@vue/devtools-api')

      alias['vue-i18n'] = require.resolve('@dcloudio/vue-cli-plugin-uni/packages/vue3/node_modules/vue-i18n')
      alias['@dcloudio/uni-app'] = require.resolve('@dcloudio/vue-cli-plugin-uni/packages/uni-app')
    }

    // 使用外层依赖的版本
    alias['regenerator-runtime'] = require.resolve('regenerator-runtime')
    const optimization = {}
    if (process.env.UNI_MINIMIZE === 'true' && process.env.NODE_ENV === 'development') {
      optimization.namedChunks = true
    }
    return {
      mode: process.env.UNI_MINIMIZE === 'true' || process.env.NODE_ENV === 'production' ? 'production'
        : 'development',
      entry () {
        return process.UNI_ENTRY
      },
      output: {
        filename: '[name].js',
        chunkFilename: '[id].js',
        globalObject: process.env.UNI_PLATFORM === 'mp-alipay' ? 'my' : 'global'
        // sourceMapFilename: '../.sourcemap/' + process.env.UNI_PLATFORM + '/[name].js.map'
      },
      performance: {
        hints: false
      },
      resolve: {
        extensions: ['.nvue'],
        alias
      },
      module: {
        rules: [{
          test: path.resolve(process.env.UNI_INPUT_DIR, getMainEntry()),
          use: [{
            loader: path.resolve(__dirname, '../../packages/wrap-loader'),
            options: {
              before: [
                beforeCode + require('../util').getAutomatorCode() + statCode
              ]
            }
          }, {
            loader: '@dcloudio/webpack-uni-mp-loader/lib/main'
          }]
        }, {
          resourceQuery: /vue&type=script/,
          use: [{
            loader: '@dcloudio/webpack-uni-mp-loader/lib/script'
          }]
        }, {
          resourceQuery: /vue&type=template/,
          use: [{
            loader: '@dcloudio/webpack-uni-mp-loader/lib/template'
          }, {
            loader: '@dcloudio/vue-cli-plugin-uni/packages/webpack-uni-app-loader/page-meta'
          }]
        }, createTemplateCacheLoader(api), {
          resourceQuery: [
            /lang=wxs/,
            /lang=filter/,
            /lang=sjs/,
            /blockType=wxs/,
            /blockType=filter/,
            /blockType=sjs/
          ],
          use: [{
            loader: require.resolve(
              '@dcloudio/vue-cli-plugin-uni/packages/webpack-uni-filter-loader')
          }]
        }]
      },
      plugins,
      optimization
    }
  },
  chainWebpack (webpackConfig, vueOptions, api) {
    if (process.env.UNI_PLATFORM === 'mp-baidu') {
      webpackConfig.module
        .rule('js')
        .exclude
        .add(/\.filter\.js$/)
    }

    const compilerOptions = process.env.UNI_USING_COMPONENTS ? {} : require('../mp-compiler-options')

    modifyVueLoader(webpackConfig, {}, compilerOptions, api)

    const styleExt = getPlatformExts().style

    webpackConfig.plugin('extract-css')
      .init((Plugin, args) => new Plugin({
        filename: '[name]' + styleExt
      }))

    if (
      process.env.NODE_ENV === 'production' &&
      process.env.UNI_PLATFORM !== 'app-plus'
    ) {
      const OptimizeCssnanoPlugin = require('../../packages/@intervolga/optimize-cssnano-plugin/index.js')
      webpackConfig.plugin('optimize-css')
        .init((Plugin, args) => new OptimizeCssnanoPlugin({
          sourceMap: false,
          filter (assetName) {
            return path.extname(assetName) === styleExt
          },
          cssnanoOptions: {
            preset: [
              'default',
              Object.assign({}, getPlatformCssnano(), {
                discardComments: true
              })
            ]
          }

        }))
    }

    if (process.env.UNI_SUBPACKGE || process.env.UNI_MP_PLUGIN) {
      initSubpackageConfig(webpackConfig, vueOptions)
    }

    webpackConfig.plugins.delete('hmr')
    webpackConfig.plugins.delete('html')
    webpackConfig.plugins.delete('copy')
    webpackConfig.plugins.delete('preload')
    webpackConfig.plugins.delete('prefetch')
  }
}
<commit_msg>feat: improve performance<commit_after>const path = require('path')
const webpack = require('webpack')

const {
  parseEntry,
  getMainEntry,
  normalizePath,
  getPlatformExts,
  getPlatformCssnano
} = require('@dcloudio/uni-cli-shared')

const WebpackUniAppPlugin = require('../../packages/webpack-uni-app-loader/plugin/index')

const modifyVueLoader = require('../vue-loader')

const {
  createTemplateCacheLoader
} = require('../cache-loader')

function createUniMPPlugin () {
  const WebpackUniMPPlugin = require('@dcloudio/webpack-uni-mp-loader/lib/plugin/index-new')
  return new WebpackUniMPPlugin()
}

function getProvides () {
  const uniPath = require('@dcloudio/uni-cli-shared/lib/platform').getMPRuntimePath()
  const uniCloudPath = path.resolve(__dirname, '../../packages/uni-cloud/dist/index.js')
  const provides = {
    uni: [uniPath, 'default'],
    uniCloud: [uniCloudPath, 'default']
  }

  if (process.env.UNI_USING_VUE3) {
    provides.uni = ['@dcloudio/uni-' + process.env.UNI_PLATFORM + '/dist/uni.api.esm.js', 'default']
    provides.createMiniProgramApp = [uniPath, 'createApp']
  }

  if (process.env.UNI_USING_COMPONENTS) {
    if (process.env.UNI_SUBPACKGE) {
      provides.createApp = [uniPath, 'createSubpackageApp']
    } else if (process.env.UNI_MP_PLUGIN) {
      provides.createApp = [uniPath, 'createPlugin']
    } else {
      provides.createApp = [uniPath, 'createApp']
    }
    provides.createPage = [uniPath, 'createPage']
    provides.createComponent = [uniPath, 'createComponent']
  }

  if (
    process.env.UNI_PLATFORM === 'app-plus' &&
    process.env.UNI_USING_V8
  ) {
    provides.__f__ = [path.resolve(__dirname, '../format-log.js'), 'default']

    const cryptoProvide = [path.resolve(__dirname, '../crypto.js'), 'default']
    provides.crypto = cryptoProvide
    provides['window.crypto'] = cryptoProvide
    provides['global.crypto'] = cryptoProvide
  }

  // TODO 目前依赖库 megalo 通过判断 wx 对象是否存在来识别平台做不同处理
  if (
    process.env.UNI_PLATFORM !== 'mp-qq' &&
    process.env.UNI_PLATFORM !== 'mp-weixin' &&
    process.env.UNI_PLATFORM !== 'app-plus'
  ) { // 非微信小程序，自动注入 wx 对象
    provides.wx = provides.uni
  }
  return provides
}

function processWxss (name, assets) {
  const dirname = path.dirname(name)
  const mainWxssCode = `@import "${normalizePath(path.relative(dirname, 'common/main.wxss'))}";`
  const code = `${mainWxssCode}` + assets[name].source().toString()
  assets[name] = {
    size () {
      return Buffer.byteLength(code, 'utf8')
    },
    source () {
      return code
    }
  }
}

const parseRequirePath = path => path.startsWith('common') ? `./${path}` : path

function procssJs (name, assets, hasVendor) {
  const dirname = path.dirname(name)
  const runtimeJsCode = `require('${normalizePath(parseRequirePath(path.relative(dirname, 'common/runtime.js')))}');`
  const vendorJsCode = hasVendor
    ? `require('${normalizePath(parseRequirePath(path.relative(dirname, 'common/vendor.js')))}');` : ''
  const mainJsCode = `require('${normalizePath(parseRequirePath(path.relative(dirname, 'common/main.js')))}');`
  const code = `${runtimeJsCode}${vendorJsCode}${mainJsCode}` + assets[name].source().toString()
  assets[name] = {
    size () {
      return Buffer.byteLength(code, 'utf8')
    },
    source () {
      return code
    }
  }
}

class PreprocessAssetsPlugin {
  apply (compiler) {
    compiler.hooks.emit.tap('PreprocessAssetsPlugin', compilation => {
      const assets = compilation.assets
      const hasMainWxss = assets['common/main.wxss']
      const hasVendor = assets['common/vendor.js']
      Object.keys(assets).forEach(name => {
        if (name.startsWith('common')) {
          return
        }
        const extname = path.extname(name)
        if (extname === '.wxss' && hasMainWxss && process.UNI_ENTRY[name.replace(extname, '')]) {
          processWxss(name, assets)
        } else if (extname === '.js') {
          procssJs(name, assets, hasVendor)
        }
      })
      // delete assets['common/main.js']
      delete assets['app.js']
      delete assets['app.json']
      delete assets['app.wxss']
      delete assets['project.config.json']
    })
  }
}

function initSubpackageConfig (webpackConfig, vueOptions) {
  if (process.env.UNI_OUTPUT_DEFAULT_DIR === process.env.UNI_OUTPUT_DIR) { // 未自定义output
    process.env.UNI_OUTPUT_DIR = path.resolve(process.env.UNI_OUTPUT_DIR, (process.env.UNI_SUBPACKGE || process.env
      .UNI_MP_PLUGIN))
  }
  vueOptions.outputDir = process.env.UNI_OUTPUT_DIR
  webpackConfig.output.path(process.env.UNI_OUTPUT_DIR)
  webpackConfig.output.jsonpFunction('webpackJsonp_' + (process.env.UNI_SUBPACKGE || process.env.UNI_MP_PLUGIN))
}

function addToUniEntry (fileName) {
  fileName && (process.UNI_ENTRY[fileName.split('.')[0]] = path.resolve(process.env.UNI_INPUT_DIR, fileName))
}

module.exports = {
  vueConfig: {
    parallel: false
  },
  webpackConfig (webpackConfig, vueOptions, api) {
    if (!webpackConfig.optimization) {
      webpackConfig.optimization = {}
    }
    // disable noEmitOnErrors
    webpackConfig.optimization.noEmitOnErrors = false

    webpackConfig.optimization.runtimeChunk = {
      name: 'common/runtime'
    }

    webpackConfig.optimization.splitChunks = require('../split-chunks')()

    parseEntry()

    const statCode = process.env.UNI_USING_STAT ? 'import \'@dcloudio/uni-stat\';' : ''

    let beforeCode = 'import \'uni-pages\';'

    const plugins = [
      new WebpackUniAppPlugin(),
      createUniMPPlugin(),
      new webpack.ProvidePlugin(getProvides())
    ]

    if ((process.env.UNI_SUBPACKGE || process.env.UNI_MP_PLUGIN) && process.env.UNI_SUBPACKGE !== 'main') {
      plugins.push(new PreprocessAssetsPlugin())
    }

    {
      const globalEnv = process.env.UNI_PLATFORM === 'mp-alipay' ? 'my' : 'wx';
      [].concat(process.env.UNI_MP_PLUGIN ? process.env.UNI_MP_PLUGIN_MAIN : JSON.parse(process.env
        .UNI_MP_PLUGIN_EXPORT))
        .forEach(fileName => addToUniEntry(fileName))
      beforeCode +=
        `${globalEnv}.__webpack_require_${(process.env.UNI_MP_PLUGIN || 'UNI_MP_PLUGIN').replace(/-/g, '_')}__ = __webpack_require__;`
    }

    const alias = { // 仅 mp-weixin
      'mpvue-page-factory': require.resolve(
        '@dcloudio/vue-cli-plugin-uni/packages/mpvue-page-factory')
    }

    if (process.env.UNI_USING_VUE3) {
      alias.vuex = require.resolve('@dcloudio/vue-cli-plugin-uni/packages/vuex')
      alias['@vue/devtools-api'] = require.resolve('@dcloudio/vue-cli-plugin-uni/packages/@vue/devtools-api')

      alias['vue-i18n'] = require.resolve('@dcloudio/vue-cli-plugin-uni/packages/vue3/node_modules/vue-i18n')
      alias['@dcloudio/uni-app'] = require.resolve('@dcloudio/vue-cli-plugin-uni/packages/uni-app')
    }

    // 使用外层依赖的版本
    alias['regenerator-runtime'] = require.resolve('regenerator-runtime')
    const output = {
      pathinfo: process.env.UNI_MINIMIZE !== 'true',
      filename: '[name].js',
      chunkFilename: '[id].js',
      globalObject: process.env.UNI_PLATFORM === 'mp-alipay' ? 'my' : 'global'
      // sourceMapFilename: '../.sourcemap/' + process.env.UNI_PLATFORM + '/[name].js.map'
    }
    if (process.env.UNI_MINIMIZE === 'true' && process.env.NODE_ENV === 'development') {
      output.pathinfo = false
    }
    return {
      mode: process.env.NODE_ENV === 'production' ? 'production'
        : 'development',
      entry () {
        return process.UNI_ENTRY
      },
      output,
      performance: {
        hints: false
      },
      resolve: {
        extensions: ['.nvue'],
        alias
      },
      module: {
        rules: [{
          test: path.resolve(process.env.UNI_INPUT_DIR, getMainEntry()),
          use: [{
            loader: path.resolve(__dirname, '../../packages/wrap-loader'),
            options: {
              before: [
                beforeCode + require('../util').getAutomatorCode() + statCode
              ]
            }
          }, {
            loader: '@dcloudio/webpack-uni-mp-loader/lib/main'
          }]
        }, {
          resourceQuery: /vue&type=script/,
          use: [{
            loader: '@dcloudio/webpack-uni-mp-loader/lib/script'
          }]
        }, {
          resourceQuery: /vue&type=template/,
          use: [{
            loader: '@dcloudio/webpack-uni-mp-loader/lib/template'
          }, {
            loader: '@dcloudio/vue-cli-plugin-uni/packages/webpack-uni-app-loader/page-meta'
          }]
        }, createTemplateCacheLoader(api), {
          resourceQuery: [
            /lang=wxs/,
            /lang=filter/,
            /lang=sjs/,
            /blockType=wxs/,
            /blockType=filter/,
            /blockType=sjs/
          ],
          use: [{
            loader: require.resolve(
              '@dcloudio/vue-cli-plugin-uni/packages/webpack-uni-filter-loader')
          }]
        }]
      },
      plugins
    }
  },
  chainWebpack (webpackConfig, vueOptions, api) {
    if (process.env.UNI_PLATFORM === 'mp-baidu') {
      webpackConfig.module
        .rule('js')
        .exclude
        .add(/\.filter\.js$/)
    }

    const compilerOptions = process.env.UNI_USING_COMPONENTS ? {} : require('../mp-compiler-options')

    modifyVueLoader(webpackConfig, {}, compilerOptions, api)

    const styleExt = getPlatformExts().style

    webpackConfig.plugin('extract-css')
      .init((Plugin, args) => new Plugin({
        filename: '[name]' + styleExt
      }))

    if (
      process.env.NODE_ENV === 'production' &&
      process.env.UNI_PLATFORM !== 'app-plus'
    ) {
      const OptimizeCssnanoPlugin = require('../../packages/@intervolga/optimize-cssnano-plugin/index.js')
      webpackConfig.plugin('optimize-css')
        .init((Plugin, args) => new OptimizeCssnanoPlugin({
          sourceMap: false,
          filter (assetName) {
            return path.extname(assetName) === styleExt
          },
          cssnanoOptions: {
            preset: [
              'default',
              Object.assign({}, getPlatformCssnano(), {
                discardComments: true
              })
            ]
          }

        }))
    }

    if (process.env.UNI_SUBPACKGE || process.env.UNI_MP_PLUGIN) {
      initSubpackageConfig(webpackConfig, vueOptions)
    }

    webpackConfig.plugins.delete('hmr')
    webpackConfig.plugins.delete('html')
    webpackConfig.plugins.delete('copy')
    webpackConfig.plugins.delete('preload')
    webpackConfig.plugins.delete('prefetch')
  }
}
<|endoftext|>


<commit_before>/**
 * The SpinnerTabsPresenter class for the SpinnerTabsPresenter view.
 *
 * @return SpinnerTabsPresenter class.
 */
define([
  'underscore',
  'mps',
  'map/presenters/PresenterClass',
], function(_, mps, PresenterClass) {

  'use strict';

  var SpinnerTabsPresenter = PresenterClass.extend({

    init: function(view) {
      this.view = view;
      this._super();
    },

    /**
     * Application subscriptions.
     */
    _subscriptions: [{
      'Spinner/start': function(allowCancel) {
        this.view.start();

        if (allowCancel === false) {
          this.view.$el.find('button').hide();
        } else {
          this.view.$el.show('button').hide();
        }
      }
    }, {
      'Spinner/stop': function() {
        this.view.stop();
      }
    }],

    cancel: function() {
      mps.publish('Spinner/cancel');
    }

  });

  return SpinnerTabsPresenter;
});
<commit_msg>Fix typo<commit_after>/**
 * The SpinnerTabsPresenter class for the SpinnerTabsPresenter view.
 *
 * @return SpinnerTabsPresenter class.
 */
define([
  'underscore',
  'mps',
  'map/presenters/PresenterClass',
], function(_, mps, PresenterClass) {

  'use strict';

  var SpinnerTabsPresenter = PresenterClass.extend({

    init: function(view) {
      this.view = view;
      this._super();
    },

    /**
     * Application subscriptions.
     */
    _subscriptions: [{
      'Spinner/start': function(allowCancel) {
        this.view.start();

        if (allowCancel === false) {
          this.view.$el.find('button').hide();
        } else {
          this.view.$el.find('button').show();
        }
      }
    }, {
      'Spinner/stop': function() {
        this.view.stop();
      }
    }],

    cancel: function() {
      mps.publish('Spinner/cancel');
    }

  });

  return SpinnerTabsPresenter;
});
<|endoftext|>


<commit_before>/* eslint-disable max-lines, max-statements, no-lonely-if */
/* global google */

// LocationMarkers is a marker manager.

var markerStore = georap.stores.markers;
var locationsStore = georap.stores.locations;
var account = georap.stores.account;
var filterStore = georap.stores.filter;
var emitter = require('component-emitter');
var rawEventToMarkerLocation = require('./lib/rawEventToMarkerLocation');
var getBoundsDiagonal = require('./lib/getBoundsDiagonal');
var FlagManager = require('./FlagManager');
var getGroupRadius = require('./getGroupRadius');
var chooseIcon = require('./chooseIcon');
var labels = require('./lib/labels');

module.exports = function (map) {

  // Init
  var self = this;
  emitter(self);

  // Location markers on the map. A mapping from id to google.maps.Marker
  var _markers = {};

  // Does the map load markers from the locations and display them on the map.
  // Will be set when client is ready to load markers (logged in)
  var _loaderListener = null;

  // True if map is ready for use. Will be switched to true at the first
  // 'idle' event. See addListenerOnce('idle', ...) below.
  // We need to track this for startLoadingMarkers: if map is ready
  // then load the markers immediately when called, otherwise wait for idle.
  var _mapReady = false;

  // Keep track of user's flags for markers.
  // Fetch them as soon as possible.
  var _flagsMan = new FlagManager();

  // Array of ids of locations currently selected ON MAP not in store.
  // The array is used to find the previously selected locations quickly.
  var _selectedId = null;
  // If selected location was hidden, a marker is created for it.
  // To avoid these markers from piling up, remove a deselected marker
  // if it was hidden before.
  var _selectedWasVisible = false;

  // Private methods

  var _chooseIcon = function (mloc) {
    var zoomLevel = map.getZoom();
    var isSelected = locationsStore.isSelected(mloc._id);
    var flags = _flagsMan.get(mloc._id);
    return chooseIcon(mloc, zoomLevel, isSelected, flags);
  };

  var _updateIcon = function (locId) {
    var m = _markers[locId];
    if (m) {
      m.setIcon(_chooseIcon(m.get('location')));
    }
  };

  var _ensureLabel = function (locId, forceUpdate) {
    // Ensure that label is visible.
    // Parameters:
    //   locId
    //   forceUpdate, optional bool
    //     re-render the label even if visible. Use to update the label text.
    var m = _markers[locId];
    if (m) {
      labels.ensureLabel(m, map.getMapTypeId(), forceUpdate);
    }
  };

  var _addMarker = function (mloc) {
    // Create marker and add it to the map.
    //
    // Parameters:
    //   mloc
    //     a MarkerLocation.
    // Return
    //   the created marker
    var lng, lat, m;

    lng = mloc.geom.coordinates[0];
    lat = mloc.geom.coordinates[1];

    m = new google.maps.Marker({
      position: new google.maps.LatLng(lat, lng),
      icon: _chooseIcon(mloc),
      // Order markers so that northern markers are always behind.
      zIndex: Math.floor((100 - lat) * 1048576),
    });

    // Store the MarkerLocation for _id, name, and layer info.
    m.set('location', mloc);

    // Label only the important, higher markers.
    if (mloc.layer < map.getZoom() - 1) {
      labels.ensureLabel(m, map.getMapTypeId(), true);
    }

    m.setMap(map);
    m.addListener('click', function () {

      if (labels.hasLabel(m)) {
        self.emit('marker_activated', mloc);
      } else {
        // First click shows the label
        labels.ensureLabel(m, map.getMapTypeId(), true);
      }

    });

    m.set('id', mloc._id);
    _markers[mloc._id] = m;

    return m;
  };

  var _removeMarker = function (m) {
    if (m) {
      // Remove from map
      m.setMap(null);

      // Remove click listener
      google.maps.event.clearInstanceListeners(m);

      delete _markers[m.get('id')];
    }
  };

  var _updateMarkers = function (locs) {
    // Add new markers and removes the excess.
    // To speed up things and avoid flicker,
    // only adds those markers on the screen that are not already there.

    // For each location candidate
    locs.forEach(function (l) {
      var m, mloc;
      if (l._id in _markers) {
        // Mark that it does not need to be removed.
        m = _markers[l._id];
        m.set('keep', true);
        // If the marker is selected, ensure it is not hidden after deselect.
        if (l._id === _selectedId) {
          _selectedWasVisible = true;
        }
        // HACK Update dynamic layer and childLayer properties to
        // display child mark properly between updates.
        // In filtered results the layer and childLayer changes constantly.
        mloc = m.get('location');
        if (Math.abs(mloc.layer - l.layer) +
            Math.abs(mloc.childLayer - l.childLayer) > 0) {
          // Change in layer data.
          mloc.layer = l.layer;
          mloc.childLayer = l.childLayer;
          m.setIcon(_chooseIcon(mloc)); // Update accordingly
        }
      } else {
        // otherwise, add it to the map.
        m = _addMarker(l);
        m.set('keep', true);
      }
    });

    // Keep the selected marker, because sometimes the selected is not
    // among the locations from the marker api but hierarchically hidden
    // below them.
    if (_markers[_selectedId]) {
      _markers[_selectedId].set('keep', true);
    }

    // Remove markers that were not marked to be kept.
    // Also, reset keep for next update.
    Object.keys(_markers).forEach(function (k) {
      var m = _markers[k];
      var keep = m.get('keep');
      if (keep) {
        // Reset for next update
        m.set('keep', false);
      } else {
        // Remove
        _removeMarker(m);
      }
    });
  };

  var _loadMarkersThen = function (err, locs) {
    if (err) {
      return console.error(err);
    } // else
    _updateMarkers(locs);
  };

  var _loadMarkers = function () {
    // Load markers for current viewport.
    // Take into consideration the current
    // - map center,
    // - map zoom level
    // - viewport size
    // - filter settings
    var bounds = map.getBounds();
    var zoom = map.getZoom();

    if (filterStore.isDefault()) {
      // No filtration
      var center = map.getCenter();
      var radius = Math.ceil(getBoundsDiagonal(bounds) / 2);
      markerStore.getWithin(center, radius, zoom, _loadMarkersThen);
    } else {
      // Query filtered set of locations.
      var filterState = filterStore.get();
      var boundsLiteral = bounds.toJSON();
      var groupRadius = getGroupRadius(boundsLiteral);
      markerStore.getFilteredWithin({
        east: boundsLiteral.east,
        north: boundsLiteral.north,
        south: boundsLiteral.south,
        west: boundsLiteral.west,
        status: filterState.status,
        type: filterState.type,
        layer: zoom,
        groupRadius: groupRadius,
      }, _loadMarkersThen);
    }
  };

  // Bind

  // Listen for changes in locations so that the markers and labels
  // are up to date.
  markerStore.on('location_name_changed', function (ev) {
    // Parameters
    //   ev
    //     ev.data.newName
    var m, mloc;

    // No need to update if no such marker on the map.
    if (!(ev.locationId in _markers)) {
      return;
    }

    // Update name of markerLocation
    m = _markers[ev.locationId];
    mloc = m.get('location');
    mloc.name = ev.data.newName;

    // Refresh label. Force update even if label already visible
    labels.ensureLabel(m, map.getMapTypeId(), true);
  });

  markerStore.on('location_geom_changed', function (ev) {
    // Parameters
    //   ev
    //     ev.data.newGeom
    var m, g, mloc;

    // No need to update if no such marker on the map.
    if (!(ev.locationId in _markers)) {
      return;
    }

    // Update geom of markerLocation
    g = ev.data.newGeom;
    m = _markers[ev.locationId];
    mloc = m.get('location');
    mloc.geom = g;

    // Ensure coordinates are up to date
    m.setPosition({
      lat: g.coordinates[1],
      lng: g.coordinates[0],
    });
  });

  markerStore.on('location_created', function (ev) {
    var mloc = rawEventToMarkerLocation(ev);
    _addMarker(mloc);
  });

  markerStore.on('location_removed', function (ev) {
    if (_markers[ev.locationId]) {
      var mToRemove = _markers[ev.locationId];
      _removeMarker(mToRemove);
    }
  });

  markerStore.on('location_status_changed', function (ev) {
    // Update icon according to new status.
    // Do this only if the marker has reached the map.
    if (_markers[ev.locationId]) {
      var m = _markers[ev.locationId];
      var mloc = m.get('location');
      // First update the stored MarkerLocation
      mloc.status = ev.data.newStatus;
      // Update icon accordingly
      m.setIcon(_chooseIcon(mloc));
    }
  });

  markerStore.on('location_type_changed', function (ev) {
    // Update icon according to new type.
    // Do this only if the marker has reached the map.
    if (_markers[ev.locationId]) {
      var m = _markers[ev.locationId];
      var mloc = m.get('location');
      // First update the stored MarkerLocation
      mloc.type = ev.data.newType;
      // Update icon accordingly
      m.setIcon(_chooseIcon(mloc));
    }
  });

  markerStore.on('location_entry_created', function (ev) {
    if (ev.data.entry.flags.length > 0 && account.isMe(ev.user)) {
      // Register possible new flags
      _flagsMan.add(ev.locationId, ev.data.entry.flags);

      // Update marker icon
      if (_markers[ev.locationId]) {
        var m = _markers[ev.locationId];
        var mloc = m.get('location');
        m.setIcon(_chooseIcon(mloc));
      }
    }
  });

  markerStore.on('location_entry_changed', function (ev) {
    // Add or remove flags according to how the entry has changed.
    // NOTE this is not perfect as it does not consider if there
    // were multiple entries with similar flags. Yet, it is good enough.
    if (account.isMe(ev.user)) {
      if (ev.data.delta.flags) {
        if (ev.data.original.flags) {
          _flagsMan.remove(ev.locationId, ev.data.original.flags);
        }
        _flagsMan.add(ev.locationId, ev.data.delta.flags);
        _updateIcon(ev.locationId);
      }
    }
  });

  // Listen possible location selections to add or update selected marker.
  locationsStore.on('updated', function (state) {
    if (state.selectedLocationId) {
      if (_selectedId === state.selectedLocationId) {
        // Already selected, do nothing.
      } else {
        // Unstyle the previous selected. Remove the previously selected
        // if it was hidden before.
        if (_selectedWasVisible) {
          _updateIcon(_selectedId);
        } else {
          _removeMarker(_markers[_selectedId]);
        }

        if (_markers[state.selectedLocationId]) {
          // New selection already on map. Restyle the new.
          _updateIcon(state.selectedLocationId);
          _selectedWasVisible = true;
        } else {
          // Marker not on the map. Use state's location if available.
          // If there is an attempt to add the selected marker on the map
          // afterwards, ensure elsewhere that _selectedWasVisible is flipped.
          if (state.selectedMarkerLocation) {
            _addMarker(state.selectedMarkerLocation);
            _selectedWasVisible = false;
          }
        }

        // Show location title
        _ensureLabel(state.selectedLocationId);
        _selectedId = state.selectedLocationId;
      }
    } else {
      // Null selected.
      // Clear select-styled markers.
      // Wait a moment before actually changing marker so that
      // quick reselection does not cause unnecessary flicker.
      var deselectedId = _selectedId;
      var deselectedWasVisible = _selectedWasVisible;
      _selectedId = null;
      var DELAY = 500;
      setTimeout(function () {
        // If not reselected
        if (_selectedId !== deselectedId) {
          if (deselectedWasVisible) {
            _updateIcon(deselectedId);
          } else {
            _removeMarker(_markers[deselectedId]);
          }
        }
      }, DELAY);
    }
  });

  // Listen map type change to invert label text colors.
  map.addListener('maptypeid_changed', function () {
    labels.updateMarkerLabels(_markers, map.getMapTypeId());
  });

  map.addListener('zoom_changed', function () {
    var z = map.getZoom();

    Object.keys(_markers).forEach(function (k) {
      var m = _markers[k];
      var mloc = m.get('location');

      // Listen zoom level change to only show labels of locations
      // with higher level than current zoom level.
      if (mloc.layer < z - 1) {
        // Ensure that label is visible.
        labels.ensureLabel(m, map.getMapTypeId());
      } else {
        labels.hideLabel(m);
      }

      // Listen zoom level change to update symbols of locations
      // when all their children become visible or hidden.
      // Ensure that the correct marker icon is used.
      // They are zoom-sensitive.
      _updateIcon(k);
    });
  });

  // Track when map becomes usable.
  // See 'var _mapReady = false' above for details.
  google.maps.event.addListenerOnce(map, 'idle', function () {
    _mapReady = true;
  });

  // Public methods

  self.startLoading = function () {
    // Each idle, fetch a new set of locations.
    // The idle is emitted automatically after the initial map load.

    // Prevent duplicate listeners.
    if (_loaderListener !== null) {
      return;
    }

    // Each time map stops, fetch.
    _loaderListener = map.addListener('idle', _loadMarkers);
    // Load the first manually but only if the map is ready.
    // Map emits 'idle' when ready but user might not have been logged in yet.
    if (_mapReady) {
      _loadMarkers();
    }

    // Each time filter changes, fetch.
    filterStore.on('updated', _loadMarkers);
  };

  self.stopLoading = function () {
    google.maps.event.removeListener(_loaderListener);
    filterStore.off('updated', _loadMarkers);
    _loaderListener = null;
  };

  self.removeAll = function () {
    // Clear the map.
    Object.keys(_markers).forEach(function (k) {
      _removeMarker(_markers[k]);
    });
  };

  self.startLoadingFlags = function () {
    // Fetch the list of location flags as soon as possible
    // to render the markers with correct templates.
    account.getFlags(function (err, flagsObj) {
      if (err) {
        console.error(err);
        return;
      }

      _flagsMan.setAll(flagsObj);

      // TODO update markers?
    });
  };

  self.stopLoadingFlags = function () {
    // noop
  };

  self.removeAllFlags = function () {
    _flagsMan.removeAll();
  };

};
<commit_msg>Update marker icons if flag visibility changes<commit_after>/* eslint-disable max-lines, max-statements, no-lonely-if */
/* global google */

// LocationMarkers is a marker manager.

var markerStore = georap.stores.markers;
var locationsStore = georap.stores.locations;
var account = georap.stores.account;
var filterStore = georap.stores.filter;
var emitter = require('component-emitter');
var rawEventToMarkerLocation = require('./lib/rawEventToMarkerLocation');
var getBoundsDiagonal = require('./lib/getBoundsDiagonal');
var FlagManager = require('./FlagManager');
var getGroupRadius = require('./getGroupRadius');
var chooseIcon = require('./chooseIcon');
var labels = require('./lib/labels');

module.exports = function (map) {

  // Init
  var self = this;
  emitter(self);

  // Location markers on the map. A mapping from id to google.maps.Marker
  var _markers = {};

  // Does the map load markers from the locations and display them on the map.
  // Will be set when client is ready to load markers (logged in)
  var _loaderListener = null;

  // True if map is ready for use. Will be switched to true at the first
  // 'idle' event. See addListenerOnce('idle', ...) below.
  // We need to track this for startLoadingMarkers: if map is ready
  // then load the markers immediately when called, otherwise wait for idle.
  var _mapReady = false;

  // Keep track of user's flags for markers.
  // Fetch them as soon as possible.
  var _flagsMan = new FlagManager();

  // Array of ids of locations currently selected ON MAP not in store.
  // The array is used to find the previously selected locations quickly.
  var _selectedId = null;
  // If selected location was hidden, a marker is created for it.
  // To avoid these markers from piling up, remove a deselected marker
  // if it was hidden before.
  var _selectedWasVisible = false;

  // Private methods

  var _chooseIcon = function (mloc) {
    var zoomLevel = map.getZoom();
    var isSelected = locationsStore.isSelected(mloc._id);
    var flags = _flagsMan.get(mloc._id);
    return chooseIcon(mloc, zoomLevel, isSelected, flags);
  };

  var _updateIcon = function (locId) {
    var m = _markers[locId];
    if (m) {
      m.setIcon(_chooseIcon(m.get('location')));
    }
  };

  var _updateIcons = function () {
    Object.keys(_markers).forEach(_updateIcon);
  };

  var _ensureLabel = function (locId, forceUpdate) {
    // Ensure that label is visible.
    // Parameters:
    //   locId
    //   forceUpdate, optional bool
    //     re-render the label even if visible. Use to update the label text.
    var m = _markers[locId];
    if (m) {
      labels.ensureLabel(m, map.getMapTypeId(), forceUpdate);
    }
  };

  var _addMarker = function (mloc) {
    // Create marker and add it to the map.
    //
    // Parameters:
    //   mloc
    //     a MarkerLocation.
    // Return
    //   the created marker
    var lng, lat, m;

    lng = mloc.geom.coordinates[0];
    lat = mloc.geom.coordinates[1];

    m = new google.maps.Marker({
      position: new google.maps.LatLng(lat, lng),
      icon: _chooseIcon(mloc),
      // Order markers so that northern markers are always behind.
      zIndex: Math.floor((100 - lat) * 1048576),
    });

    // Store the MarkerLocation for _id, name, and layer info.
    m.set('location', mloc);

    // Label only the important, higher markers.
    if (mloc.layer < map.getZoom() - 1) {
      labels.ensureLabel(m, map.getMapTypeId(), true);
    }

    m.setMap(map);
    m.addListener('click', function () {

      if (labels.hasLabel(m)) {
        self.emit('marker_activated', mloc);
      } else {
        // First click shows the label
        labels.ensureLabel(m, map.getMapTypeId(), true);
      }

    });

    m.set('id', mloc._id);
    _markers[mloc._id] = m;

    return m;
  };

  var _removeMarker = function (m) {
    if (m) {
      // Remove from map
      m.setMap(null);

      // Remove click listener
      google.maps.event.clearInstanceListeners(m);

      delete _markers[m.get('id')];
    }
  };

  var _updateMarkers = function (locs) {
    // Add new markers and removes the excess.
    // To speed up things and avoid flicker,
    // only adds those markers on the screen that are not already there.

    // For each location candidate
    locs.forEach(function (l) {
      var m, mloc;
      if (l._id in _markers) {
        // Mark that it does not need to be removed.
        m = _markers[l._id];
        m.set('keep', true);
        // If the marker is selected, ensure it is not hidden after deselect.
        if (l._id === _selectedId) {
          _selectedWasVisible = true;
        }
        // HACK Update dynamic layer and childLayer properties to
        // display child mark properly between updates.
        // In filtered results the layer and childLayer changes constantly.
        mloc = m.get('location');
        if (Math.abs(mloc.layer - l.layer) +
            Math.abs(mloc.childLayer - l.childLayer) > 0) {
          // Change in layer data.
          mloc.layer = l.layer;
          mloc.childLayer = l.childLayer;
          m.setIcon(_chooseIcon(mloc)); // Update accordingly
        }
      } else {
        // otherwise, add it to the map.
        m = _addMarker(l);
        m.set('keep', true);
      }
    });

    // Keep the selected marker, because sometimes the selected is not
    // among the locations from the marker api but hierarchically hidden
    // below them.
    if (_markers[_selectedId]) {
      _markers[_selectedId].set('keep', true);
    }

    // Remove markers that were not marked to be kept.
    // Also, reset keep for next update.
    Object.keys(_markers).forEach(function (k) {
      var m = _markers[k];
      var keep = m.get('keep');
      if (keep) {
        // Reset for next update
        m.set('keep', false);
      } else {
        // Remove
        _removeMarker(m);
      }
    });
  };

  var _loadMarkersThen = function (err, locs) {
    if (err) {
      return console.error(err);
    } // else
    _updateMarkers(locs);
  };

  var _loadMarkers = function () {
    // Load markers for current viewport.
    // Take into consideration the current
    // - map center,
    // - map zoom level
    // - viewport size
    // - filter settings
    var bounds = map.getBounds();
    var zoom = map.getZoom();

    if (filterStore.isDefault()) {
      // No filtration
      var center = map.getCenter();
      var radius = Math.ceil(getBoundsDiagonal(bounds) / 2);
      markerStore.getWithin(center, radius, zoom, _loadMarkersThen);
    } else {
      // Query filtered set of locations.
      var filterState = filterStore.get();
      var boundsLiteral = bounds.toJSON();
      var groupRadius = getGroupRadius(boundsLiteral);
      markerStore.getFilteredWithin({
        east: boundsLiteral.east,
        north: boundsLiteral.north,
        south: boundsLiteral.south,
        west: boundsLiteral.west,
        status: filterState.status,
        type: filterState.type,
        layer: zoom,
        groupRadius: groupRadius,
      }, _loadMarkersThen);
    }
  };

  // Bind

  // Listen for changes in locations so that the markers and labels
  // are up to date.
  markerStore.on('location_name_changed', function (ev) {
    // Parameters
    //   ev
    //     ev.data.newName
    var m, mloc;

    // No need to update if no such marker on the map.
    if (!(ev.locationId in _markers)) {
      return;
    }

    // Update name of markerLocation
    m = _markers[ev.locationId];
    mloc = m.get('location');
    mloc.name = ev.data.newName;

    // Refresh label. Force update even if label already visible
    labels.ensureLabel(m, map.getMapTypeId(), true);
  });

  markerStore.on('location_geom_changed', function (ev) {
    // Parameters
    //   ev
    //     ev.data.newGeom
    var m, g, mloc;

    // No need to update if no such marker on the map.
    if (!(ev.locationId in _markers)) {
      return;
    }

    // Update geom of markerLocation
    g = ev.data.newGeom;
    m = _markers[ev.locationId];
    mloc = m.get('location');
    mloc.geom = g;

    // Ensure coordinates are up to date
    m.setPosition({
      lat: g.coordinates[1],
      lng: g.coordinates[0],
    });
  });

  markerStore.on('location_created', function (ev) {
    var mloc = rawEventToMarkerLocation(ev);
    _addMarker(mloc);
  });

  markerStore.on('location_removed', function (ev) {
    if (_markers[ev.locationId]) {
      var mToRemove = _markers[ev.locationId];
      _removeMarker(mToRemove);
    }
  });

  markerStore.on('location_status_changed', function (ev) {
    // Update icon according to new status.
    // Do this only if the marker has reached the map.
    if (_markers[ev.locationId]) {
      var m = _markers[ev.locationId];
      var mloc = m.get('location');
      // First update the stored MarkerLocation
      mloc.status = ev.data.newStatus;
      // Update icon accordingly
      m.setIcon(_chooseIcon(mloc));
    }
  });

  markerStore.on('location_type_changed', function (ev) {
    // Update icon according to new type.
    // Do this only if the marker has reached the map.
    if (_markers[ev.locationId]) {
      var m = _markers[ev.locationId];
      var mloc = m.get('location');
      // First update the stored MarkerLocation
      mloc.type = ev.data.newType;
      // Update icon accordingly
      m.setIcon(_chooseIcon(mloc));
    }
  });

  markerStore.on('location_entry_created', function (ev) {
    if (ev.data.entry.flags.length > 0 && account.isMe(ev.user)) {
      // Register possible new flags
      _flagsMan.add(ev.locationId, ev.data.entry.flags);

      // Update marker icon
      if (_markers[ev.locationId]) {
        var m = _markers[ev.locationId];
        var mloc = m.get('location');
        m.setIcon(_chooseIcon(mloc));
      }
    }
  });

  markerStore.on('location_entry_changed', function (ev) {
    // Add or remove flags according to how the entry has changed.
    // NOTE this is not perfect as it does not consider if there
    // were multiple entries with similar flags. Yet, it is good enough.
    if (account.isMe(ev.user)) {
      if (ev.data.delta.flags) {
        if (ev.data.original.flags) {
          _flagsMan.remove(ev.locationId, ev.data.original.flags);
        }
        _flagsMan.add(ev.locationId, ev.data.delta.flags);
        _updateIcon(ev.locationId);
      }
    }
  });

  // Listen possible location selections to add or update selected marker.
  locationsStore.on('updated', function (state) {
    if (state.selectedLocationId) {
      if (_selectedId === state.selectedLocationId) {
        // Already selected, do nothing.
      } else {
        // Unstyle the previous selected. Remove the previously selected
        // if it was hidden before.
        if (_selectedWasVisible) {
          _updateIcon(_selectedId);
        } else {
          _removeMarker(_markers[_selectedId]);
        }

        if (_markers[state.selectedLocationId]) {
          // New selection already on map. Restyle the new.
          _updateIcon(state.selectedLocationId);
          _selectedWasVisible = true;
        } else {
          // Marker not on the map. Use state's location if available.
          // If there is an attempt to add the selected marker on the map
          // afterwards, ensure elsewhere that _selectedWasVisible is flipped.
          if (state.selectedMarkerLocation) {
            _addMarker(state.selectedMarkerLocation);
            _selectedWasVisible = false;
          }
        }

        // Show location title
        _ensureLabel(state.selectedLocationId);
        _selectedId = state.selectedLocationId;
      }
    } else {
      // Null selected.
      // Clear select-styled markers.
      // Wait a moment before actually changing marker so that
      // quick reselection does not cause unnecessary flicker.
      var deselectedId = _selectedId;
      var deselectedWasVisible = _selectedWasVisible;
      _selectedId = null;
      var DELAY = 500;
      setTimeout(function () {
        // If not reselected
        if (_selectedId !== deselectedId) {
          if (deselectedWasVisible) {
            _updateIcon(deselectedId);
          } else {
            _removeMarker(_markers[deselectedId]);
          }
        }
      }, DELAY);
    }
  });

  // Listen map type change to invert label text colors.
  map.addListener('maptypeid_changed', function () {
    labels.updateMarkerLabels(_markers, map.getMapTypeId());
  });

  map.addListener('zoom_changed', function () {
    var z = map.getZoom();

    Object.keys(_markers).forEach(function (k) {
      var m = _markers[k];
      var mloc = m.get('location');

      // Listen zoom level change to only show labels of locations
      // with higher level than current zoom level.
      if (mloc.layer < z - 1) {
        // Ensure that label is visible.
        labels.ensureLabel(m, map.getMapTypeId());
      } else {
        labels.hideLabel(m);
      }

      // Listen zoom level change to update symbols of locations
      // when all their children become visible or hidden.
      // Ensure that the correct marker icon is used.
      // They are zoom-sensitive.
      _updateIcon(k);
    });
  });

  // Track when map becomes usable.
  // See 'var _mapReady = false' above for details.
  google.maps.event.addListenerOnce(map, 'idle', function () {
    _mapReady = true;
  });

  // Public methods

  self.startLoading = function () {
    // Each idle, fetch a new set of locations.
    // The idle is emitted automatically after the initial map load.

    // Prevent duplicate listeners.
    if (_loaderListener !== null) {
      return;
    }

    // Each time map stops, fetch.
    _loaderListener = map.addListener('idle', _loadMarkers);
    // Load the first manually but only if the map is ready.
    // Map emits 'idle' when ready but user might not have been logged in yet.
    if (_mapReady) {
      _loadMarkers();
    }

    // Each time filter changes, fetch.
    filterStore.on('updated', _loadMarkers);
  };

  self.stopLoading = function () {
    google.maps.event.removeListener(_loaderListener);
    filterStore.off('updated', _loadMarkers);
    _loaderListener = null;
  };

  self.removeAll = function () {
    // Clear the map.
    Object.keys(_markers).forEach(function (k) {
      _removeMarker(_markers[k]);
    });
  };

  self.startLoadingFlags = function () {
    // Fetch the list of location flags as soon as possible
    // to render the markers with correct templates.
    account.getFlags(function (err, flagsObj) {
      if (err) {
        console.error(err);
        return;
      }

      _flagsMan.setAll(flagsObj);
      _updateIcons();
    });
  };

  self.stopLoadingFlags = function () {
    // noop
  };

  self.removeAllFlags = function () {
    _flagsMan.removeAll();
    _updateIcons();
  };

};
<|endoftext|>


<commit_before>
if (!('Request' in this))
    Request = function() {};

function CacheList() {
    this.caches = {};
}

CacheList.prototype._getCache = function(cache_name) {
    if (!(cache_name in this.caches))
        this.caches[cache_name] = new Cache(cache_name);
    return this.caches[cache_name];
};

CacheList.prototype.forEach = function(callback) {
    for (var name in this.caches) {
        callback(name, this.caches[name], this);
    }
};

CacheList.prototype.delete = function(name) {
    if (name in this.caches)
        delete this.caches.name;
};

CacheList.prototype.match = function(cache_name, url) {
    return this._getCache(cache_name).match(url);
};

CacheList.prototype.get = function(cache_name) {
    return this._getCache(cache_name);
};

CacheList.prototype.set = function(cache_name, cache) {
    if (cache.name != cache_name)
        throw "Cache names must match [polyfill restriction]";
    if (cache_name in this.caches &&
        this.caches[cache_name] !== cache)
        throw "Already have a cache named '" + cache_name + "'";

    this.caches[cache_name] = cache;
    return cache;
};

CacheList.prototype.values = function() {
    var cacheList = this;
    return Object.keys(cacheList.caches).map(function(key) {
        return cacheList.caches[key];
    });
};

CacheList.prototype.keys = function() {
    return Object.keys(this.caches);
};

// translates a string or a request object into an object with a .url
// property
function _getRequest(urlOrRequest) {
    if (urlOrRequest.url) {
        urlOrRequest.url = absolutizeURI(location.toString(), urlOrRequest.url);
        return urlOrRequest;
    }
    var request = new SameOriginResponse();
    request.url = absolutizeURI(location.toString(), urlOrRequest);
    request.method = 'GET';
    return request;
}

// This differs slightly from the the explainer, in that the 'name' is
// passed as the first parameter - vastly simplified the implementation.
function Cache(name /*, url, url, ...*/) {
    var cache = this;
    var requestsOrUrls = Array.prototype.slice.call(arguments, 1);
    this.name = name;

    // These are promises we want to resolve before any future cache action.
    var pending = [];
    this.readyPromise = this._open().then(function(cache) {
        return cache._add(requestsOrUrls).then(function(addresults) {
            return addresults;
        });
    }).then(function(results) {
        // throw away the pending promises
        return cache;
    }).catch(function(e) {
        lasterr = e;
    });
}

// TODO - a basic cache consistency thing needs to be exposed here,
// since our promises may include network requests that may result in our gets/puts
// being executed slightly out of order from IDB itself.
//
// Consider:
// p1 = cache.add("http://google.com/");
// p2 = cache.match("http://google.com/");
//
// The add step may actually mean:
// fetch("http://google.com/").then(saveToIDB)
//
// But I think the effect we want is that p2 resolves after p1, which
// is how IDB works. Not sure.
//
// If this is how it should be implemented, then all outstanding
// requests need to be (functionally, if not actually) resolved before
// resolution of the next promise.
//
// Here is what needs to happen to make that work:
//
// 1) every time this api creats a new promise to write, it needs to
//    add it to a 'pending' list on Cache.
//
// 2) The first thing that such a promise needs to do when resolved is clear
//    itself from the pending promises list.
//
// 3) Every time ready() is called, we snapshot the outstanding
//    promises, and call Q.all() on them, probably returning that
//    here.
//
// [1] because promises are resolved with a separate resolver function, resolution doesn't technically even need to run until the first person calls then(), so
Cache.prototype.ready = function() {
    return this.readyPromise;
};

Cache.prototype._open = function () {
    var cache = this;
    if (!cache.db) {
        cache.db = new Promise(function(resolver) {
            var openReq = indexedDB.open("cache-" + cache.name, 1);
            openReq.onupgradeneeded = function(e) {
                var db = e.target.result;
                db.createObjectStore(cache.name);
            };
            openReq.onsuccess = function(e) {
                var db = cache.db = e.target.result;
                resolver.resolve(cache);
            };
            openReq.onerror = function(e) {
                resolver.reject("Error opening " + cache.name + ": " + e.name);
            };
        });
    }
    return cache.db;
};

// _get and _set are generic wrappers around idb, that assume the db is open and ready.
Cache.prototype._get = function(url) {
    var cache = this;
    url = _getRequest(url).url;
    return new Promise(function(resolver) {
        try {
            var req = cache.db.transaction(cache.name)
                    .objectStore(cache.name).get(url);
        } catch(ex) {
            resolver.reject("Couldn't load url '" + url + "': " + ex);
        }
        req.onsuccess = function(e) {
            if (!e.target.result) {
                resolver.reject("URL not found: " + url);
                return;
            }
            var result = {
                request: e.target.result.request,
                response: cache._makeResponse(e.target.result.response)
            };
            resolver.resolve(result);
        };
        req.onerror = function(e) {
            resolver.reject("Error in _get for " + url + ": " + e.name);
        };
    });
};

Cache.prototype._set = function(key, value) {
    var cache = this;
    return new Promise(function(resolver) {
        var req = cache.db.transaction(cache.name, 'readwrite')
                .objectStore(cache.name).put(value, key);
        req.onsuccess = function(e) {
            resolver.resolve(e.target.result);
        };
        req.onerror = function(e) {
            resolver.reject("Error in _get for " + key + ": " + e.name);
        };
    });
};

Cache.prototype._getKeys = function() {
    var cache = this;
    return new Promise(function(resolver) {
        var req = cache.db.transaction(cache.name)
                .objectStore(cache.name).openCursor();
        var resultkeys = [];
        req.onsuccess = function(e) {
            var cursor = e.target.result;
            if (cursor == null) {
                resolver.resolve(resultkeys);
                return;
            }
            resultkeys.push(cursor.key);
            cursor.continue();
        };
        req.onerror = function(e) {
            resolve.reject(e);
        };
    });
};

// Not sure what iteration looks like, so for now we just iterate here
Cache.prototype.getKeys = function() {
    return this.ready().then(function(cache) {
        return cache._getKeys();
    });
};

// Creates a SameOriginRequest from an object that came out of IDB
Cache.prototype._makeResponse = function (responseObj) {
    var response = new SameOriginResponse();
    for (var k in responseObj)
        response[k] = responseObj[k];
    var content_type;
    if ('setHeader' in response) {
        responseObj.headers.forEach(function(header) {
            if (header[0].toLowerCase() == 'content-type')
                content_type = header[1];
            response.setHeader(header[0], header[1]);
        });
    }
    if ('setBody' in response) {
        response.setBody(createBlob(response.body, content_type));
    }
    return response;
};

// Gives a response object suitable for passing to respondWith()
Cache.prototype.match = function(url) {
    var cache = this;
    return this.ready()
        .then(function(cache) {
            return cache._get(url);
        }).then(function(entry) {
            return entry.response;
        })
        // not sure we want a 'catch' at all here, but the
        // current implementation of respondWith() doesn't really
        // deal with failure.
        .catch(function(ex) {
            var response = new SameOriginResponse();
            response.statusCode = 404;
            response.statusText = "Not Found: " + ex;
            lastex = ex;
            response.method = '';
            response.setBody(createBlob("Not found in cache: " + ex, "text/plain"));
            return response;
        });
};

// Gets the full entry, including a request and response object
Cache.prototype.get = function(url) {
    var cache = this;
    return this.ready()
        .then(function(cache) {
            return cache._get(url);
        });
};

// This returns a callback which binds the request to the response
Cache.prototype._setResponse = function(request) {
    var cache = this;
    var requestObj = {};
    for (var k in request) {
        if (typeof request[k] != 'function')
            requestObj[k] = request[k];
    }
    return function(response) {
        // the main object isn't clonable, but its subparts are
        var responseObj = {};
        for (var k in response) {
            if (typeof response[k] != 'function')
                responseObj[k] = response[k];
        }
        return cache._set(request.url,
                          { request: requestObj, response: responseObj });
    };
};

// Given a set of requests, fetch them and store them in the cache.
Cache.prototype._add = function(requests) {
    var pending = [];
    var cache = this;
    for (var i = 0; i < requests.length; ++i) {
        var request = _getRequest(requests[i]);
        pending.push(fetch(request.url)
                     .then(cache._setResponse(request)));
    }
    return Promise.every.apply(Promise, pending);
};

// Add one or more urls to the cache.
Cache.prototype.add = function(/* request, request, request, ...*/) {
    var requests = arguments;
    return this.ready().then(function(cache) {
        var result = cache._add(requests);
        if (requests.length == 1)
            return result[0];
        return result;
    });
};

Cache.prototype.addResponse = function(urlOrRequest, response) {
    var request = _getRequest(urlOrRequest);
    return this.ready().then(function(cache) {
        request = JSON.parse(JSON.stringify(request));
        response = JSON.parse(JSON.stringify(response));
        cache._set(request.url, { request: request, response: response });
    });
};
<commit_msg>CacheList instance<commit_after>
if (!('Request' in this))
    Request = function() {};

function CacheList() {
    this.caches = {};
}

CacheList.prototype._getCache = function(cache_name) {
    if (!(cache_name in this.caches))
        this.caches[cache_name] = new Cache(cache_name);
    return this.caches[cache_name];
};

CacheList.prototype.forEach = function(callback) {
    for (var name in this.caches) {
        callback(name, this.caches[name], this);
    }
};

CacheList.prototype.delete = function(name) {
    if (name in this.caches)
        delete this.caches.name;
};

CacheList.prototype.match = function(cache_name, url) {
    return this._getCache(cache_name).match(url);
};

CacheList.prototype.get = function(cache_name) {
    return this._getCache(cache_name);
};

CacheList.prototype.set = function(cache_name, cache) {
    if (cache.name != cache_name)
        throw "Cache names must match [polyfill restriction]";
    if (cache_name in this.caches &&
        this.caches[cache_name] !== cache)
        throw "Already have a cache named '" + cache_name + "'";

    this.caches[cache_name] = cache;
    return cache;
};

CacheList.prototype.values = function() {
    var cacheList = this;
    return Object.keys(cacheList.caches).map(function(key) {
        return cacheList.caches[key];
    });
};

CacheList.prototype.keys = function() {
    return Object.keys(this.caches);
};

// translates a string or a request object into an object with a .url
// property
function _getRequest(urlOrRequest) {
    if (urlOrRequest.url) {
        urlOrRequest.url = absolutizeURI(location.toString(), urlOrRequest.url);
        return urlOrRequest;
    }
    var request = new SameOriginResponse();
    request.url = absolutizeURI(location.toString(), urlOrRequest);
    request.method = 'GET';
    return request;
}

// This differs slightly from the the explainer, in that the 'name' is
// passed as the first parameter - vastly simplified the implementation.
function Cache(name /*, url, url, ...*/) {
    var cache = this;
    var requestsOrUrls = Array.prototype.slice.call(arguments, 1);
    this.name = name;

    // These are promises we want to resolve before any future cache action.
    var pending = [];
    this.readyPromise = this._open().then(function(cache) {
        return cache._add(requestsOrUrls).then(function(addresults) {
            return addresults;
        });
    }).then(function(results) {
        // throw away the pending promises
        return cache;
    }).catch(function(e) {
        lasterr = e;
    });
}

// TODO - a basic cache consistency thing needs to be exposed here,
// since our promises may include network requests that may result in our gets/puts
// being executed slightly out of order from IDB itself.
//
// Consider:
// p1 = cache.add("http://google.com/");
// p2 = cache.match("http://google.com/");
//
// The add step may actually mean:
// fetch("http://google.com/").then(saveToIDB)
//
// But I think the effect we want is that p2 resolves after p1, which
// is how IDB works. Not sure.
//
// If this is how it should be implemented, then all outstanding
// requests need to be (functionally, if not actually) resolved before
// resolution of the next promise.
//
// Here is what needs to happen to make that work:
//
// 1) every time this api creats a new promise to write, it needs to
//    add it to a 'pending' list on Cache.
//
// 2) The first thing that such a promise needs to do when resolved is clear
//    itself from the pending promises list.
//
// 3) Every time ready() is called, we snapshot the outstanding
//    promises, and call Q.all() on them, probably returning that
//    here.
//
// [1] because promises are resolved with a separate resolver function, resolution doesn't technically even need to run until the first person calls then(), so
Cache.prototype.ready = function() {
    return this.readyPromise;
};

Cache.prototype._open = function () {
    var cache = this;
    if (!cache.db) {
        cache.db = new Promise(function(resolver) {
            var openReq = indexedDB.open("cache-" + cache.name, 1);
            openReq.onupgradeneeded = function(e) {
                var db = e.target.result;
                db.createObjectStore(cache.name);
            };
            openReq.onsuccess = function(e) {
                var db = cache.db = e.target.result;
                resolver.resolve(cache);
            };
            openReq.onerror = function(e) {
                resolver.reject("Error opening " + cache.name + ": " + e.name);
            };
        });
    }
    return cache.db;
};

// _get and _set are generic wrappers around idb, that assume the db is open and ready.
Cache.prototype._get = function(url) {
    var cache = this;
    url = _getRequest(url).url;
    return new Promise(function(resolver) {
        try {
            var req = cache.db.transaction(cache.name)
                    .objectStore(cache.name).get(url);
        } catch(ex) {
            resolver.reject("Couldn't load url '" + url + "': " + ex);
        }
        req.onsuccess = function(e) {
            if (!e.target.result) {
                resolver.reject("URL not found: " + url);
                return;
            }
            var result = {
                request: e.target.result.request,
                response: cache._makeResponse(e.target.result.response)
            };
            resolver.resolve(result);
        };
        req.onerror = function(e) {
            resolver.reject("Error in _get for " + url + ": " + e.name);
        };
    });
};

Cache.prototype._set = function(key, value) {
    var cache = this;
    return new Promise(function(resolver) {
        var req = cache.db.transaction(cache.name, 'readwrite')
                .objectStore(cache.name).put(value, key);
        req.onsuccess = function(e) {
            resolver.resolve(e.target.result);
        };
        req.onerror = function(e) {
            resolver.reject("Error in _get for " + key + ": " + e.name);
        };
    });
};

Cache.prototype._getKeys = function() {
    var cache = this;
    return new Promise(function(resolver) {
        var req = cache.db.transaction(cache.name)
                .objectStore(cache.name).openCursor();
        var resultkeys = [];
        req.onsuccess = function(e) {
            var cursor = e.target.result;
            if (cursor == null) {
                resolver.resolve(resultkeys);
                return;
            }
            resultkeys.push(cursor.key);
            cursor.continue();
        };
        req.onerror = function(e) {
            resolve.reject(e);
        };
    });
};

// Not sure what iteration looks like, so for now we just iterate here
Cache.prototype.getKeys = function() {
    return this.ready().then(function(cache) {
        return cache._getKeys();
    });
};

// Creates a SameOriginRequest from an object that came out of IDB
Cache.prototype._makeResponse = function (responseObj) {
    var response = new SameOriginResponse();
    for (var k in responseObj)
        response[k] = responseObj[k];
    var content_type;
    if ('setHeader' in response) {
        responseObj.headers.forEach(function(header) {
            if (header[0].toLowerCase() == 'content-type')
                content_type = header[1];
            response.setHeader(header[0], header[1]);
        });
    }
    if ('setBody' in response) {
        response.setBody(createBlob(response.body, content_type));
    }
    return response;
};

// Gives a response object suitable for passing to respondWith()
Cache.prototype.match = function(url) {
    var cache = this;
    return this.ready()
        .then(function(cache) {
            return cache._get(url);
        }).then(function(entry) {
            return entry.response;
        })
        // not sure we want a 'catch' at all here, but the
        // current implementation of respondWith() doesn't really
        // deal with failure.
        .catch(function(ex) {
            var response = new SameOriginResponse();
            response.statusCode = 404;
            response.statusText = "Not Found: " + ex;
            lastex = ex;
            response.method = '';
            response.setBody(createBlob("Not found in cache: " + ex, "text/plain"));
            return response;
        });
};

// Gets the full entry, including a request and response object
Cache.prototype.get = function(url) {
    var cache = this;
    return this.ready()
        .then(function(cache) {
            return cache._get(url);
        });
};

// This returns a callback which binds the request to the response
Cache.prototype._setResponse = function(request) {
    var cache = this;
    var requestObj = {};
    for (var k in request) {
        if (typeof request[k] != 'function')
            requestObj[k] = request[k];
    }
    return function(response) {
        // the main object isn't clonable, but its subparts are
        var responseObj = {};
        for (var k in response) {
            if (typeof response[k] != 'function')
                responseObj[k] = response[k];
        }
        return cache._set(request.url,
                          { request: requestObj, response: responseObj });
    };
};

// Given a set of requests, fetch them and store them in the cache.
Cache.prototype._add = function(requests) {
    var pending = [];
    var cache = this;
    for (var i = 0; i < requests.length; ++i) {
        var request = _getRequest(requests[i]);
        pending.push(fetch(request.url)
                     .then(cache._setResponse(request)));
    }
    return Promise.every.apply(Promise, pending);
};

// Add one or more urls to the cache.
Cache.prototype.add = function(/* request, request, request, ...*/) {
    var requests = arguments;
    return this.ready().then(function(cache) {
        var result = cache._add(requests);
        if (requests.length == 1)
            return result[0];
        return result;
    });
};

Cache.prototype.addResponse = function(urlOrRequest, response) {
    var request = _getRequest(urlOrRequest);
    return this.ready().then(function(cache) {
        request = JSON.parse(JSON.stringify(request));
        response = JSON.parse(JSON.stringify(response));
        cache._set(request.url, { request: request, response: response });
    });
};

this.caches = new CacheList();<|endoftext|>


<commit_before>define(['backbone'], function(Backbone) {

    return Backbone.Model.extend({
        idAttribute: 'platetypeid',
        urlRoot: '/plates/types',

        dropTotal: function() {
            return this.get('drop_per_well_x') * this.get('drop_per_well_y') - (this.get('well_drop') > -1 ? 1 : 0)
        },

        wellTotal: function() {
            return this.get('capacity') / this.dropTotal()
        },

        getWell: function(pos) {
            return Math.floor( (parseInt(pos)-1) / this.dropTotal() )
        },

        getName: function(pos) {
            var p = this.getRowColDrop(pos)
            return String.fromCharCode(p.row+65)+(p.col+1)
        },

        getDrop: function(pos) {
            return ((pos-1) % this.dropTotal())+1
        },

        getRowColDrop: function(pos) {
            var wellpos = Math.floor((parseInt(pos)-1) / this.dropTotal())
            var drop = ((pos-1) % this.dropTotal())+1

            var col = wellpos % this.get('well_per_row')
            var row = Math.floor(wellpos / this.get('well_per_row'))

            return { row: row, col: col, drop: drop, pos: pos }
        },

        calcParams: function() {
            this.set('well_pad', this.width / 130)
            this.set('drop_pad', this.width / 130)
            //console.log('well_pad', this.get('well_pad'), 'drop_pad', this.get('drop_pad'))

            this.set('well_width', ((this.width-this.get('offset_x')) / this.get('well_per_row')) - this.get('well_pad'))
            this.set('well_height', ((this.width*(1/1.5))-this.get('offset_y')) / (this.get('capacity')/(this.get('well_per_row')*this.dropTotal())) - this.get('well_pad'))

            this.set('drop_widthpx', (this.get('well_width')-this.get('drop_pad')*2) / (this.get('drop_per_well_x') / this.get('drop_width')))
            this.set('drop_heightpx', (this.get('well_height')-this.get('drop_pad')*2) / (this.get('drop_per_well_y') / this.get('drop_height')))

            this.set('drop_offset_x', this.get('drop_offset_x')*(this.get('well_width')-this.get('drop_pad')*2))
            this.set('drop_offset_y', this.get('drop_offset_y')*(this.get('well_height')-this.get('drop_pad')*2))
        },

        setGeometry: function(width, height, ofx, ofy) {
            this.width = width
            this.height = height
            this.set('offset_x', ofx || 25)
            this.set('offset_y', ofy || 25)

            this.calcParams()
        },

    })

})
<commit_msg>add pcr tube type<commit_after>define(['backbone'], function(Backbone) {

    return Backbone.Model.extend({
        idAttribute: 'platetypeid',
        urlRoot: '/plates/types',

        dropTotal: function() {
            return this.get('drop_per_well_x') * this.get('drop_per_well_y') - (this.get('well_drop') > -1 ? 1 : 0)
        },

        wellTotal: function() {
            return this.get('capacity') / this.dropTotal()
        },

        getWell: function(pos) {
            return Math.floor( (parseInt(pos)-1) / this.dropTotal() )
        },

        getName: function(pos) {
            var p = this.getRowColDrop(pos)
            return String.fromCharCode(p.row+65)+(p.col+1)
        },

        getDrop: function(pos) {
            return ((pos-1) % this.dropTotal())+1
        },

        getRowColDrop: function(pos) {
            var wellpos = Math.floor((parseInt(pos)-1) / this.dropTotal())
            var drop = ((pos-1) % this.dropTotal())+1

            var col = wellpos % this.get('well_per_row')
            var row = Math.floor(wellpos / this.get('well_per_row'))

            return { row: row, col: col, drop: drop, pos: pos }
        },

        calcParams: function() {
            this.set('well_pad', this.width / 130)
            this.set('drop_pad', this.width / 130)
            //console.log('well_pad', this.get('well_pad'), 'drop_pad', this.get('drop_pad'))

            this.set('well_width', ((this.width-this.get('offset_x')) / this.get('well_per_row')) - this.get('well_pad'))
            // this.set('well_height', ((this.width*(1/1.5))-this.get('offset_y')) / (this.get('capacity')/(this.get('well_per_row')*this.dropTotal())) - this.get('well_pad'))
            this.set('well_height', (this.height-this.get('offset_y')) / (this.get('capacity')/(this.get('well_per_row')*this.dropTotal())) - this.get('well_pad'))

            this.set('drop_widthpx', (this.get('well_width')-this.get('drop_pad')*2) / (this.get('drop_per_well_x') / this.get('drop_width')))
            this.set('drop_heightpx', (this.get('well_height')-this.get('drop_pad')*2) / (this.get('drop_per_well_y') / this.get('drop_height')))

            this.set('drop_offset_x', this.get('drop_offset_x')*(this.get('well_width')-this.get('drop_pad')*2))
            this.set('drop_offset_y', this.get('drop_offset_y')*(this.get('well_height')-this.get('drop_pad')*2))
        },

        setGeometry: function(width, height, ofx, ofy) {
            this.width = width
            this.height = height
            this.set('offset_x', ofx || 25)
            this.set('offset_y', ofy || 25)

            this.calcParams()
        },

    })

})
<|endoftext|>


<commit_before>function Cache(defaults) {
	this.defaults = {}
	this.init(defaults || {})
  this.map = function() {
		var r = this.mget.apply(this, arguments);
		return function() { return r };
  }
}

Cache.prototype = {
	mget: function() {
		// Lazy mget w/array
		if (arguments.length == 1 && arguments[0] instanceof Array)
			return this.mget.apply(this, arguments[0]);

		var reqs = [];
		var keys = [];

		next: for (var i = 0; i < arguments.length; i++) {
			v = arguments[i];
			if (typeof(v) == "string") {
				reqs.push(this.get(v));
				keys.push(v);
				continue next;
			}

			if (typeof(v) == "object") {
				for (var k in v) {
					reqs.push(this.get(k, v[k]));
					keys.push(k);
					continue next;
				}
			}

			keys.push(v);
			reqs.push(null);
		}
		return Promise.all(reqs)
		    .then(function(r) {
				var retval = {};
				r.forEach(function(v, k) {
					retval[keys[k]] = v;
				})
				return retval;
		    });
	},
	get: function(key, fallback) {
		return new Promise(function (resolve, reject) {
			return chrome.storage.local.get(key, function(result) {
				if (!key)
					resolve(result);
				else if (result.hasOwnProperty(key))
					resolve(result[key])
				else if (typeof fallback != 'undefined')
					resolve(fallback)
				else if (typeof this.defaults[key] != 'undefined')
					resolve(this.defaults[key])
				else
					resolve(null);
			}.bind(this));
		}.bind(this));
	},

	mset: function(map) {
		return new Promise(function (resolve, reject) {
			return chrome.storage.local.set(map, resolve);
		});
	},

	set: function(key, value) {
		return this.mset({key: value});
	},

	init: function(kvpairs) {
		this.defaults = kvpairs
	}
}
<commit_msg>do not try to use input param name as object literal key<commit_after>function Cache(defaults) {
	this.defaults = {}
	this.init(defaults || {})
  this.map = function() {
		var r = this.mget.apply(this, arguments);
		return function() { return r };
  }
}

Cache.prototype = {
	mget: function() {
		// Lazy mget w/array
		if (arguments.length == 1 && arguments[0] instanceof Array)
			return this.mget.apply(this, arguments[0]);

		var reqs = [];
		var keys = [];

		next: for (var i = 0; i < arguments.length; i++) {
			v = arguments[i];
			if (typeof(v) == "string") {
				reqs.push(this.get(v));
				keys.push(v);
				continue next;
			}

			if (typeof(v) == "object") {
				for (var k in v) {
					reqs.push(this.get(k, v[k]));
					keys.push(k);
					continue next;
				}
			}

			keys.push(v);
			reqs.push(null);
		}
		return Promise.all(reqs)
		    .then(function(r) {
				var retval = {};
				r.forEach(function(v, k) {
					retval[keys[k]] = v;
				})
				return retval;
		    });
	},
	get: function(key, fallback) {
		return new Promise(function (resolve, reject) {
			return chrome.storage.local.get(key, function(result) {
				if (!key)
					resolve(result);
				else if (result.hasOwnProperty(key))
					resolve(result[key])
				else if (typeof fallback != 'undefined')
					resolve(fallback)
				else if (typeof this.defaults[key] != 'undefined')
					resolve(this.defaults[key])
				else
					resolve(null);
			}.bind(this));
		}.bind(this));
	},

	mset: function(map) {
		return new Promise(function (resolve, reject) {
			return chrome.storage.local.set(map, resolve);
		});
	},

	set: function(key, value) {
		var obj = new Object();
		obj[key] = value;
		return this.mset(obj);
	},

	init: function(kvpairs) {
		this.defaults = kvpairs
	}
}
<|endoftext|>


<commit_before>import React, { Component } from 'react';
import { Meteor } from 'meteor/meteor';
import { Session } from 'meteor/session';
import { createContainer } from 'meteor/react-meteor-data';
import FeedbackMessage  from '../misc/FeedbackMessage';

class CreationForm extends Component {

  constructor(props) {
    super(props);
    this.state = {
      name: '',
      start: '',
      end: '',
      visibility: 'public',
      description: ''
    };
    this.handleChange = this.handleChange.bind(this);
    this.handleSubmit = this.handleSubmit.bind(this);
  }

  handleChange(key) {
    return function (e) {
      let state = {};
      state[key] = e.target.value;
      this.setState(state);
    }.bind(this);
  }

  handleSubmit(){
    Meteor.call('project.create', this.state, function(err, res) {
      if (err) {
        Session.set('error', err.message);
        Session.set('success', null);
      } else {
        Session.set('success', res);
        Session.set('error', null);
      }
    });
  }

  render() {
    return (

          Name:
          <input type="text" onChange={this.handleChange('name')} value={this.state.name} className="form-control" pattern="^[a-zA-Z ]{2,30}$" required />

The project name must contains between 2 and 30 letters, lowercase or uppercase.

        {/* Date range */}

          Start:

            <input type="date" onChange={this.handleChange('start')} value={this.state.start} id="start" className="form-control pull-right datepicker" required />

          {/* /.input group */}

          End:

            <input type="date" onChange={this.handleChange('end')} value={this.state.end} id="end" className="form-control pull-right datepicker" required />

          {/* /.input group */}

        {/* Public Private */}

          Visibility level:

              <input type="radio" checked={this.state.visibility === 'public'} onChange={this.handleChange('visibility')} value="public"/>
              Public

              <input type="radio" checked={this.state.visibility === 'private'} onChange={this.handleChange('visibility')} value="private" />
              Private

          Description:
          <textarea className="form-control" rows="5" onChange={this.handleChange('description')} value={this.state.description} required />

          Administrator:
          <input type="text" className="form-control" value={this.props.user.username} disabled/>

This project will be created with you as the Administrator.
            Once the project exists, you may choose an Administrator from among the project members.

        <FeedbackMessage
          error={this.props.error}
          success={this.props.success}
          />
        <button className="btn btn-flat center-block" onClick={this.handleSubmit}>
          Submit
        </button>

    );
  }
}

export default createContainer(() => {
  return {
    error: Session.get('error'),
    success: Session.get('success'),
    user: Meteor.user() || {}
  };
}, CreationForm);
<commit_msg>fix(new project): client side form verification now working<commit_after>import React, { Component } from 'react';
import { Meteor } from 'meteor/meteor';
import { Session } from 'meteor/session';
import { createContainer } from 'meteor/react-meteor-data';
import FeedbackMessage  from '../misc/FeedbackMessage';

class CreationForm extends Component {

  constructor(props) {
    super(props);
    this.state = {
      name: '',
      start: '',
      end: '',
      visibility: 'public',
      description: ''
    };
    this.handleChange = this.handleChange.bind(this);
    this.handleSubmit = this.handleSubmit.bind(this);
  }

  handleChange(key) {
    return function (e) {
      let state = {};
      state[key] = e.target.value;
      this.setState(state);
    }.bind(this);
  }

  handleSubmit(e){
    e.preventDefault();
    Meteor.call('project.create', this.state, function(err, res) {
      if (err) {
        Session.set('error', err.message);
        Session.set('success', null);
      } else {
        Session.set('success', res);
        Session.set('error', null);
      }
    });
  }

  render() {
    return (
      <form onSubmit={this.handleSubmit}>

          Name:
          <input type="text" onChange={this.handleChange('name')} value={this.state.name} className="form-control" pattern="^[a-zA-Z ]{2,30}$" required />

The project name must contains between 2 and 30 letters, lowercase or uppercase.

        {/* Date range */}

          Start:

            <input type="date" onChange={this.handleChange('start')} value={this.state.start} id="start" className="form-control pull-right datepicker" required />

          {/* /.input group */}

          End:

            <input type="date" onChange={this.handleChange('end')} value={this.state.end} id="end" className="form-control pull-right datepicker" required />

          {/* /.input group */}

        {/* Public Private */}

          Visibility level:

              <input type="radio" checked={this.state.visibility === 'public'} onChange={this.handleChange('visibility')} value="public"/>
              Public

              <input type="radio" checked={this.state.visibility === 'private'} onChange={this.handleChange('visibility')} value="private" />
              Private

          Description:
          <textarea className="form-control" rows="5" onChange={this.handleChange('description')} value={this.state.description} required />

          Administrator:
          <input type="text" className="form-control" value={this.props.user.username} disabled/>

This project will be created with you as the Administrator.
            Once the project exists, you may choose an Administrator from among the project members.

        <FeedbackMessage
          error={this.props.error}
          success={this.props.success}
          />
        <button type="submit" className="btn btn-flat center-block">
          Submit
        </button>

      </form>
    );
  }
}

export default createContainer(() => {
  return {
    error: Session.get('error'),
    success: Session.get('success'),
    user: Meteor.user() || {}
  };
}, CreationForm);
<|endoftext|>


<commit_before><commit_msg>updates logiclayer parents query to use new local api endpoint<commit_after><|endoftext|>


<commit_before>'use strict';

exports.__esModule = true;
exports.default = {
  el: {
    datepicker: {
      now: 'Şimdi',
      today: 'Bugün',
      cancel: 'İptal',
      clear: 'Temizle',
      confirm: 'OK',
      selectDate: 'Tarih seç',
      selectTime: 'Saat seç',
      startDate: 'Başlangıç Tarihi',
      startTime: 'Başlangıç Saati',
      endDate: 'Bitiş Tarihi',
      endTime: 'Bitiş Saati',
      year: '',
      month1: 'Ocak',
      month2: 'Şubat',
      month3: 'Mart',
      month4: 'Nisan',
      month5: 'Mayıs',
      month6: 'Haziran',
      month7: 'Temmuz',
      month8: 'Ağustos',
      month9: 'Eylül',
      month10: 'Ekim',
      month11: 'Kasım',
      month12: 'Aralık',
      // week: 'week',
      weeks: {
        sun: 'Paz',
        mon: 'Pzt',
        tue: 'Sal',
        wed: 'Çar',
        thu: 'Per',
        fri: 'Cum',
        sat: 'Cmt'
      },
      months: {
        jan: 'Oca',
        feb: 'Şub',
        mar: 'Mar',
        apr: 'Nis',
        may: 'May',
        jun: 'Haz',
        jul: 'Tem',
        aug: 'Ağu',
        sep: 'Eyl',
        oct: 'Eki',
        nov: 'Kas',
        dec: 'Ara'
      }
    },
    select: {
      loading: 'Yükleniyor',
      noMatch: 'Eşleşen veri bulunamadı',
      noData: 'Veri yok',
      placeholder: 'Seç'
    },
    pagination: {
      goto: 'Git',
      pagesize: '/page',
      total: 'Toplam {total}',
      pageClassifier: ''
    },
    messagebox: {
      title: 'Mesaj',
      confirm: 'OK',
      cancel: 'İptal',
      error: 'İllegal giriş'
    },
    upload: {
      delete: 'Sil',
      preview: 'Görüntüle',
      continue: 'Devam'
    },
    table: {
      emptyText: 'Veri yok',
      confirmFilter: 'Onayla',
      resetFilter: 'Reset',
      clearFilter: 'Hepsi'
    },
    tree: {
      emptyText: 'Veri yok'
    }
  }
};
<commit_msg>locale: fix tr-TR<commit_after>export default {
  el: {
    datepicker: {
      now: 'Şimdi',
      today: 'Bugün',
      cancel: 'İptal',
      clear: 'Temizle',
      confirm: 'OK',
      selectDate: 'Tarih seç',
      selectTime: 'Saat seç',
      startDate: 'Başlangıç Tarihi',
      startTime: 'Başlangıç Saati',
      endDate: 'Bitiş Tarihi',
      endTime: 'Bitiş Saati',
      year: '',
      month1: 'Ocak',
      month2: 'Şubat',
      month3: 'Mart',
      month4: 'Nisan',
      month5: 'Mayıs',
      month6: 'Haziran',
      month7: 'Temmuz',
      month8: 'Ağustos',
      month9: 'Eylül',
      month10: 'Ekim',
      month11: 'Kasım',
      month12: 'Aralık',
      // week: 'week',
      weeks: {
        sun: 'Paz',
        mon: 'Pzt',
        tue: 'Sal',
        wed: 'Çar',
        thu: 'Per',
        fri: 'Cum',
        sat: 'Cmt'
      },
      months: {
        jan: 'Oca',
        feb: 'Şub',
        mar: 'Mar',
        apr: 'Nis',
        may: 'May',
        jun: 'Haz',
        jul: 'Tem',
        aug: 'Ağu',
        sep: 'Eyl',
        oct: 'Eki',
        nov: 'Kas',
        dec: 'Ara'
      }
    },
    select: {
      loading: 'Yükleniyor',
      noMatch: 'Eşleşen veri bulunamadı',
      noData: 'Veri yok',
      placeholder: 'Seç'
    },
    pagination: {
      goto: 'Git',
      pagesize: '/page',
      total: 'Toplam {total}',
      pageClassifier: ''
    },
    messagebox: {
      title: 'Mesaj',
      confirm: 'OK',
      cancel: 'İptal',
      error: 'İllegal giriş'
    },
    upload: {
      delete: 'Sil',
      preview: 'Görüntüle',
      continue: 'Devam'
    },
    table: {
      emptyText: 'Veri yok',
      confirmFilter: 'Onayla',
      resetFilter: 'Reset',
      clearFilter: 'Hepsi'
    },
    tree: {
      emptyText: 'Veri yok'
    }
  }
};
<|endoftext|>