<commit_before>/* global Raven */
import EventEmitter from '../EventEmitter'
import shallowEquals from '../shallow-equals'
import PQueue from '../p-queue'
import { loadGetInitialProps, getURL } from '../utils'
import { _notifyBuildIdMismatch } from './'
import { formatPath, parsePath } from '../url'

export default class Router {
  constructor (pathname, query, as, { pageLoader, Component, ErrorComponent, err } = {}) {
    // represents the current component key
    this.route = toRoute(pathname)

    // set up the component cache (by route keys)
    this.components = {}
    // We should not keep the cache, if there's an error
    // Otherwise, this cause issues when when going back and
    // come again to the errored page.
    if (Component !== ErrorComponent) {
      this.components[this.route] = { Component, err }
    }

    // Handling Router Events
    this.events = new EventEmitter()

    this.pageLoader = pageLoader
    this.prefetchQueue = new PQueue({ concurrency: 2 })
    this.ErrorComponent = ErrorComponent
    this.pathname = pathname
    this.query = query
    this.asPath = as
    this.subscriptions = []
    this.componentLoadCancel = null

    if (typeof window !== 'undefined') {
      // in order for `e.state` to work on the `onpopstate` event
      // we have to register the initial route upon initialization
      this.changeState('replaceState', formatPath(this), getURL())

      window.addEventListener('popstate', (e) => {
        if (!e.state) {
          // We get state as undefined for two reasons.
          //  1. With older safari (< 8) and older chrome (< 34)
          //  2. When the URL changed with #
          //
          // In the both cases, we don't need to proceed and change the route.
          // (as it's already changed)
          // But we can simply replace the state with the new changes.
          // Actually, for (1) we don't need to nothing. But it's hard to detect that event.
          // So, doing the following for (1) does no harm.
          this.changeState('replaceState', formatPath(this), getURL())
          return
        }

        const { url, as, options } = e.state
        this.replace(url, as, options)
      })
    }
  }

  update (route, Component) {
    const data = this.components[route]
    if (!data) {
      throw new Error(`Cannot update unavailable route: ${route}`)
    }

    const newData = { ...data, Component }
    this.components[route] = newData

    if (route === this.route) {
      this.notify(newData)
    }
  }

  async reload (route) {
    delete this.components[route]
    this.pageLoader.clearCache(route)

    if (route !== this.route) return

    const { pathname, query } = this
    const url = window.location.href

    this.events.emit('routeChangeStart', url)
    const routeInfo = await this.getRouteInfo(route, pathname, query, url)
    const { error } = routeInfo

    if (error && error.cancelled) {
      return
    }

    this.notify(routeInfo)

    if (error) {
      this.events.emit('routeChangeError', error, url)
      throw error
    }

    this.events.emit('routeChangeComplete', url)
  }

  back () {
    window.history.back()
  }

  push (url, as = url, options = {}) {
    return this.change('pushState', url, as, options)
  }

  replace (url, as = url, options = {}) {
    return this.change('replaceState', url, as, options)
  }

  async change (method, _url, _as, options) {
    if (typeof Raven !== 'undefined') {
      Raven.captureBreadcrumb({
        message: 'Router.change',
        method,
        url: _url,
        as: _as
      })
    }
    // If url and as provided as an object representation,
    // we'll format them into the string version here.
    const url = typeof _url === 'object' ? formatPath(_url) : _url
    let as = typeof _as === 'object' ? formatPath(_as) : _as

    this.abortComponentLoad(as)
    const { pathname, query } = parsePath(url, true)

    // If the url change is only related to a hash change
    // We should not proceed. We should only change the state.
    if (this.onlyAHashChange(as)) {
      this.changeState(method, url, as)
      return
    }

    // If asked to change the current URL we should reload the current page
    // (not location.reload() but reload getInitalProps and other Next.js stuffs)
    // We also need to set the method = replaceState always
    // as this should not go into the history (That's how browsers work)
    if (!this.urlIsNew(pathname, query)) {
      method = 'replaceState'
    }

    const route = toRoute(pathname)
    const { shallow = false } = options
    let routeInfo = null

    this.events.emit('routeChangeStart', as)

    // If shallow === false and other conditions met, we reuse the
    // existing routeInfo for this route.
    // Because of this, getInitialProps would not run.
    if (shallow && this.isShallowRoutingPossible(route)) {
      routeInfo = this.components[route]
    } else {
      routeInfo = await this.getRouteInfo(route, pathname, query, as)
    }

    const { error } = routeInfo

    if (error && error.cancelled) {
      return false
    }

    this.events.emit('beforeHistoryChange', as)
    this.changeState(method, url, as, options)
    const hash = window.location.hash.substring(1)

    this.route = route
    this.pathname = pathname
    this.query = query
    this.asPath = as
    this.notify({ ...routeInfo, hash })

    if (error) {
      this.events.emit('routeChangeError', error, as)
      throw error
    }

    this.events.emit('routeChangeComplete', as)
    return true
  }

  changeState (method, url, as, options = {}) {
    if (method !== 'pushState' || getURL() !== as) {
      console.log('changeState', method, as)
      window.history[method]({ url, as, options }, null, as)
    }
  }

  async getRouteInfo (route, pathname, query, as) {
    let routeInfo = null

    try {
      routeInfo = this.components[route]
      if (!routeInfo) {
        routeInfo = { Component: await this.fetchComponent(route, as) }
      }

      const { Component } = routeInfo
      const ctx = { pathname, query, asPath: as }
      routeInfo.props = await this.getInitialProps(Component, ctx)

      this.components[route] = routeInfo
    } catch (err) {
      if (err.cancelled) {
        return { error: err }
      }

      if (err.buildIdMismatched) {
        // Now we need to reload the page or do the action asked by the user
        _notifyBuildIdMismatch(as)
        // We also need to cancel this current route change.
        // We do it like this.
        err.cancelled = true
        return { error: err }
      }

      if (err.statusCode === 404) {
        // Indicate main error display logic to
        // ignore rendering this error as a runtime error.
        err.ignore = true
      }

      const Component = this.ErrorComponent
      routeInfo = { Component, err }
      const ctx = { err, pathname, query }
      routeInfo.props = await this.getInitialProps(Component, ctx)

      routeInfo.error = err
    }

    return routeInfo
  }

  onlyAHashChange (as) {
    if (!this.asPath) return false
    const [ oldUrlNoHash ] = this.asPath.split('#')
    const [ newUrlNoHash ] = as.split('#')

    // If the urls are change, there's more than a hash change
    return oldUrlNoHash === newUrlNoHash
  }

  urlIsNew (pathname, query) {
    return this.pathname !== pathname || !shallowEquals(query, this.query)
  }

  isShallowRoutingPossible (route) {
    return (
      // If there's cached routeInfo for the route.
      Boolean(this.components[route]) &&
      // If the route is already rendered on the screen.
      this.route === route
    )
  }

  async prefetch (url) {
    // We don't add support for prefetch in the development mode.
    // If we do that, our on-demand-entries optimization won't performs better
    if (process.env.NODE_ENV === 'development') return

    const { pathname } = parsePath(url)
    const route = toRoute(pathname)
    return this.prefetchQueue.add(() => this.fetchRoute(route))
  }

  async fetchComponent (route, as) {
    let cancelled = false
    const cancel = this.componentLoadCancel = function () {
      cancelled = true
    }

    try {
      const Component = await this.fetchRoute(route)

      if (cancelled) {
        const error = new Error(`Abort fetching component for route: "${route}"`)
        error.cancelled = true
        throw error
      }

      if (cancel === this.componentLoadCancel) {
        this.componentLoadCancel = null
      }

      return Component
    } catch (err) {
      // There's an error in loading the route.
      // Usually this happens when there's a failure in the webpack build
      // So in that case, we need to load the page with full SSR
      // That'll clean the invalid exising client side information.
      // (Like cached routes)
      window.location.href = as
      throw err
    }
  }

  async getInitialProps (Component, ctx) {
    let cancelled = false
    const cancel = () => { cancelled = true }
    this.componentLoadCancel = cancel

    const props = await loadGetInitialProps(Component, ctx)

    if (cancel === this.componentLoadCancel) {
      this.componentLoadCancel = null
    }

    if (cancelled) {
      const err = new Error('Loading initial props cancelled')
      err.cancelled = true
      throw err
    }

    return props
  }

  async fetchRoute (route) {
    return this.pageLoader.loadPage(route)
  }

  abortComponentLoad (as) {
    if (this.componentLoadCancel) {
      this.events.emit('routeChangeError', new Error('Route Cancelled'), as)
      this.componentLoadCancel()
      this.componentLoadCancel = null
    }
  }

  notify (data) {
    this.subscriptions.forEach((fn) => fn(data))
  }

  subscribe (fn) {
    this.subscriptions.push(fn)
    return () => {
      let index = this.subscriptions.indexOf(fn)
      if (index >= 0) {
        this.subscriptions.splice(index, 1)
      }
    }
  }
}

function toRoute (path) {
  return path.replace(/\/$/, '') || '/'
}
<commit_msg>Avoid request terminating replace state on startup<commit_after>/* global Raven */
import EventEmitter from '../EventEmitter'
import shallowEquals from '../shallow-equals'
import PQueue from '../p-queue'
import { loadGetInitialProps, getURL } from '../utils'
import { _notifyBuildIdMismatch } from './'
import { formatPath, parsePath } from '../url'

export default class Router {
  constructor (pathname, query, as, { pageLoader, Component, ErrorComponent, err } = {}) {
    // represents the current component key
    this.route = toRoute(pathname)

    // set up the component cache (by route keys)
    this.components = {}
    // We should not keep the cache, if there's an error
    // Otherwise, this cause issues when when going back and
    // come again to the errored page.
    if (Component !== ErrorComponent) {
      this.components[this.route] = { Component, err }
    }

    // Handling Router Events
    this.events = new EventEmitter()

    this.pageLoader = pageLoader
    this.prefetchQueue = new PQueue({ concurrency: 2 })
    this.ErrorComponent = ErrorComponent
    this.pathname = pathname
    this.query = query
    this.asPath = as
    this.subscriptions = []
    this.componentLoadCancel = null

    if (typeof window !== 'undefined') {
      // this.changeState('replaceState', formatPath(this), getURL())
      const originalState = { url: formatPath(this), as: getURL(), options: {} }

      window.addEventListener('popstate', (e) => {
        let state = e.state
        if (!state && originalState.as === getURL()) {
          // If the current url matches the original, then restore the state from that
          // since we likely do not have state on the initial entry. This could happen
          // on navigation back to the initial page or on hash change on the initial
          // url, but the replace call below should be non-impactful.
          state = originalState
        }
        if (!state) {
          // This is likely a hash change, NOP
          return
        }

        const { url, as, options } = state
        this.replace(url, as, options)
      })
    }
  }

  update (route, Component) {
    const data = this.components[route]
    if (!data) {
      throw new Error(`Cannot update unavailable route: ${route}`)
    }

    const newData = { ...data, Component }
    this.components[route] = newData

    if (route === this.route) {
      this.notify(newData)
    }
  }

  async reload (route) {
    delete this.components[route]
    this.pageLoader.clearCache(route)

    if (route !== this.route) return

    const { pathname, query } = this
    const url = window.location.href

    this.events.emit('routeChangeStart', url)
    const routeInfo = await this.getRouteInfo(route, pathname, query, url)
    const { error } = routeInfo

    if (error && error.cancelled) {
      return
    }

    this.notify(routeInfo)

    if (error) {
      this.events.emit('routeChangeError', error, url)
      throw error
    }

    this.events.emit('routeChangeComplete', url)
  }

  back () {
    window.history.back()
  }

  push (url, as = url, options = {}) {
    return this.change('pushState', url, as, options)
  }

  replace (url, as = url, options = {}) {
    return this.change('replaceState', url, as, options)
  }

  async change (method, _url, _as, options) {
    if (typeof Raven !== 'undefined') {
      Raven.captureBreadcrumb({
        message: 'Router.change',
        method,
        url: _url,
        as: _as
      })
    }
    // If url and as provided as an object representation,
    // we'll format them into the string version here.
    const url = typeof _url === 'object' ? formatPath(_url) : _url
    let as = typeof _as === 'object' ? formatPath(_as) : _as

    this.abortComponentLoad(as)
    const { pathname, query } = parsePath(url, true)

    // If the url change is only related to a hash change
    // We should not proceed. We should only change the state.
    if (this.onlyAHashChange(as)) {
      this.changeState(method, url, as)
      return
    }

    // If asked to change the current URL we should reload the current page
    // (not location.reload() but reload getInitalProps and other Next.js stuffs)
    // We also need to set the method = replaceState always
    // as this should not go into the history (That's how browsers work)
    if (!this.urlIsNew(pathname, query)) {
      method = 'replaceState'
    }

    const route = toRoute(pathname)
    const { shallow = false } = options
    let routeInfo = null

    this.events.emit('routeChangeStart', as)

    // If shallow === false and other conditions met, we reuse the
    // existing routeInfo for this route.
    // Because of this, getInitialProps would not run.
    if (shallow && this.isShallowRoutingPossible(route)) {
      routeInfo = this.components[route]
    } else {
      routeInfo = await this.getRouteInfo(route, pathname, query, as)
    }

    const { error } = routeInfo

    if (error && error.cancelled) {
      return false
    }

    this.events.emit('beforeHistoryChange', as)
    this.changeState(method, url, as, options)
    const hash = window.location.hash.substring(1)

    this.route = route
    this.pathname = pathname
    this.query = query
    this.asPath = as
    this.notify({ ...routeInfo, hash })

    if (error) {
      this.events.emit('routeChangeError', error, as)
      throw error
    }

    this.events.emit('routeChangeComplete', as)
    return true
  }

  changeState (method, url, as, options = {}) {
    if (method !== 'pushState' || getURL() !== as) {
      console.log('changeState', method, as)
      window.history[method]({ url, as, options }, null, as)
    }
  }

  async getRouteInfo (route, pathname, query, as) {
    let routeInfo = null

    try {
      routeInfo = this.components[route]
      if (!routeInfo) {
        routeInfo = { Component: await this.fetchComponent(route, as) }
      }

      const { Component } = routeInfo
      const ctx = { pathname, query, asPath: as }
      routeInfo.props = await this.getInitialProps(Component, ctx)

      this.components[route] = routeInfo
    } catch (err) {
      if (err.cancelled) {
        return { error: err }
      }

      if (err.buildIdMismatched) {
        // Now we need to reload the page or do the action asked by the user
        _notifyBuildIdMismatch(as)
        // We also need to cancel this current route change.
        // We do it like this.
        err.cancelled = true
        return { error: err }
      }

      if (err.statusCode === 404) {
        // Indicate main error display logic to
        // ignore rendering this error as a runtime error.
        err.ignore = true
      }

      const Component = this.ErrorComponent
      routeInfo = { Component, err }
      const ctx = { err, pathname, query }
      routeInfo.props = await this.getInitialProps(Component, ctx)

      routeInfo.error = err
    }

    return routeInfo
  }

  onlyAHashChange (as) {
    if (!this.asPath) return false
    const [ oldUrlNoHash ] = this.asPath.split('#')
    const [ newUrlNoHash ] = as.split('#')

    // If the urls are change, there's more than a hash change
    return oldUrlNoHash === newUrlNoHash
  }

  urlIsNew (pathname, query) {
    return this.pathname !== pathname || !shallowEquals(query, this.query)
  }

  isShallowRoutingPossible (route) {
    return (
      // If there's cached routeInfo for the route.
      Boolean(this.components[route]) &&
      // If the route is already rendered on the screen.
      this.route === route
    )
  }

  async prefetch (url) {
    // We don't add support for prefetch in the development mode.
    // If we do that, our on-demand-entries optimization won't performs better
    if (process.env.NODE_ENV === 'development') return

    const { pathname } = parsePath(url)
    const route = toRoute(pathname)
    return this.prefetchQueue.add(() => this.fetchRoute(route))
  }

  async fetchComponent (route, as) {
    let cancelled = false
    const cancel = this.componentLoadCancel = function () {
      cancelled = true
    }

    try {
      const Component = await this.fetchRoute(route)

      if (cancelled) {
        const error = new Error(`Abort fetching component for route: "${route}"`)
        error.cancelled = true
        throw error
      }

      if (cancel === this.componentLoadCancel) {
        this.componentLoadCancel = null
      }

      return Component
    } catch (err) {
      // There's an error in loading the route.
      // Usually this happens when there's a failure in the webpack build
      // So in that case, we need to load the page with full SSR
      // That'll clean the invalid exising client side information.
      // (Like cached routes)
      window.location.href = as
      throw err
    }
  }

  async getInitialProps (Component, ctx) {
    let cancelled = false
    const cancel = () => { cancelled = true }
    this.componentLoadCancel = cancel

    const props = await loadGetInitialProps(Component, ctx)

    if (cancel === this.componentLoadCancel) {
      this.componentLoadCancel = null
    }

    if (cancelled) {
      const err = new Error('Loading initial props cancelled')
      err.cancelled = true
      throw err
    }

    return props
  }

  async fetchRoute (route) {
    return this.pageLoader.loadPage(route)
  }

  abortComponentLoad (as) {
    if (this.componentLoadCancel) {
      this.events.emit('routeChangeError', new Error('Route Cancelled'), as)
      this.componentLoadCancel()
      this.componentLoadCancel = null
    }
  }

  notify (data) {
    this.subscriptions.forEach((fn) => fn(data))
  }

  subscribe (fn) {
    this.subscriptions.push(fn)
    return () => {
      let index = this.subscriptions.indexOf(fn)
      if (index >= 0) {
        this.subscriptions.splice(index, 1)
      }
    }
  }
}

function toRoute (path) {
  return path.replace(/\/$/, '') || '/'
}
<|endoftext|>


<commit_before>/*==================================================
 *
 *              Timeline API
 *              ------------
 *
 * This file will load all the Javascript files
 * necessary to make the standard timeline work.
 * It also detects the default locale.
 *
 * To run Timeline directly from the www.simile-widgets.org server
 * include this fragment in your HTML file as follows:
 *
 *   <script src="http://api.simile-widgets.org/timeline/2.3.1/timeline-api.js"
 *    type="text/javascript"></script>
 *
 *
 * To host the Timeline files on your own server:
 *
 * 1) Install the Timeline files onto your webserver using
 *    the minimal distribution "timeline_<version>_minimal.(zip|tar.gz)" found at
 *
 *        http://code.google.com/p/simile-widgets/downloads/list
 *
 * 2) Set the following global js variable used to send parameters to this script:
 *     var Timeline_ajax_url  -- URL for simile-ajax-api.js
 *     var Timeline_urlPrefix -- URL for the *directory* that contains timeline-api.js
 *                               on your web site (including the trailing slash!)
 *
 * eg your HTML page would include
 *
 *   <script>
 *     var Timeline_ajax_url="http://YOUR_SERVER/apis/timeline/ajax/simile-ajax-api.js";
 *     var Timeline_urlPrefix='http://YOUR_SERVER/apis/timeline/';
 *   </script>
 *   <script src="http://YOUR_SERVER/javascripts/timeline/timeline-api.js"
 *     type="text/javascript">
 *   </script>
 *
 * SCRIPT PARAMETERS
 *
 * This script auto-magically figures out locale and has defaults for other parameters
 * To set parameters explicity, set js global variable Timeline_parameters or include as
 * parameters on the url using GET style. Eg the two next lines pass the same parameters:
 *     Timeline_parameters='bundle=true';                    // pass parameter via js variable
 *     <script src="http://....timeline-api.js?bundle=true"  // pass parameter via url
 *
 * Parameters
 *   timeline-use-local-resources --
 *   bundle -- true: use the single js bundle file; false: load individual files (for debugging)
 *   locales --
 *   defaultLocale --
 *   forceLocale -- force locale to be a particular value--used for debugging. Normally locale is determined
 *                  by browser's and server's locale settings.
 *
 * DEBUGGING
 *
 * If you have a problem with Timeline, the first step is to use the unbundled Javascript files. To do so:
 * To use the unbundled Timeline and Ajax libraries
 * Change
 *   <script src="http://api.simile-widgets.org/timeline/2.3.1/api/timeline-api.js?bundle=true" type="text/javascript"></script>
 * To
 *   <script>var Timeline_ajax_url = "http://api.simile-widgets.org/ajax/2.2.1/simile-ajax-api.js?bundle=false"</script>
 *   <script src="http://api.simile-widgets.org/timeline/2.3.1/api/timeline-api.js?bundle=false" type="text/javascript"></script>
 *
 * Note that the Ajax version is usually NOT the same as the Timeline version.
 * See variable simile_ajax_ver below for the current version
 *
 *==================================================
 */

(function() {

    var simile_ajax_ver = "2.2.1"; // ===========>>>  current Simile-Ajax version

    var isCompiled = ("Timeline_isCompiled" in window) && window.Timeline_isCompiled;

    var useLocalResources = false;
    if (document.location.search.length > 0) {
        var params = document.location.search.substr(1).split("&");
        for (var i = 0; i < params.length; i++) {
            if (params[i] == "timeline-use-local-resources") {
                useLocalResources = true;
            }
        }
    };

    var loadMe = function() {
        if ("Timeline" in window) {
            return;
        }

        window.Timeline = new Object();
        window.Timeline.DateTime = window.SimileAjax.DateTime; // for backward compatibility

        var bundle = false;
        var javascriptFiles = [
            "timeline.js",
            "band.js",
            "themes.js",
            "ethers.js",
            "ether-painters.js",
            "event-utils.js",
            "labellers.js",
            "sources.js",
            "original-painter.js",
            "detailed-painter.js",
            "overview-painter.js",
            "compact-painter.js",
            "decorators.js",
            "units.js"
        ];
        var cssFiles = [
            "timeline.css",
            "ethers.css",
            "events.css"
        ];

        var localizedJavascriptFiles = [
            "timeline.js",
            "labellers.js"
        ];
        var localizedCssFiles = [
        ];

        // ISO-639 language codes, ISO-3166 country codes (2 characters)
        var supportedLocales = [
            "cs",       // Czech
            "de",       // German
            "en",       // English
            "es",       // Spanish
            "fr",       // French
            "it",       // Italian
            "nl",       // Dutch (The Netherlands)
            "ru",       // Russian
            "se",       // Swedish
            "tr",       // Turkish
            "vi",       // Vietnamese
            "zh"        // Chinese
        ];

        try {
            var desiredLocales = [ "en" ],
                defaultServerLocale = "en",
                forceLocale = null;

            var parseURLParameters = function(parameters) {
                var params = parameters.split("&");
                for (var p = 0; p < params.length; p++) {
                    var pair = params[p].split("=");
                    if (pair[0] == "locales") {
                        desiredLocales = desiredLocales.concat(pair[1].split(","));
                    } else if (pair[0] == "defaultLocale") {
                        defaultServerLocale = pair[1];
                    } else if (pair[0] == "forceLocale") {
                        forceLocale = pair[1];
                        desiredLocales = desiredLocales.concat(pair[1].split(","));
                    } else if (pair[0] == "bundle") {
                        bundle = pair[1] != "false";
                    }
                }
            };

            (function() {
                if (typeof Timeline_urlPrefix == "string") {
                    Timeline.urlPrefix = Timeline_urlPrefix;
                    if (typeof Timeline_parameters == "string") {
                        parseURLParameters(Timeline_parameters);
                    }
                } else {
                    var heads = document.documentElement.getElementsByTagName("head");
                    for (var h = 0; h < heads.length; h++) {
                        var scripts = heads[h].getElementsByTagName("script");
                        for (var s = 0; s < scripts.length; s++) {
                            var url = scripts[s].src;
                            var i = url.indexOf("timeline-api.js");
                            if (i >= 0) {
                                Timeline.urlPrefix = url.substr(0, i);
                                var q = url.indexOf("?");
                                if (q > 0) {
                                    parseURLParameters(url.substr(q + 1));
                                }
                                return;
                            }
                        }
                    }
                    throw new Error("Failed to derive URL prefix for Timeline API code files");
                }
            })();

            var includeJavascriptFiles = function(urlPrefix, filenames) {
                SimileAjax.includeJavascriptFiles(document, urlPrefix, filenames);
            }
            var includeCssFiles = function(urlPrefix, filenames) {
                SimileAjax.includeCssFiles(document, urlPrefix, filenames);
            }

            /*
             *  Include non-localized files
             */
            if (!isCompiled) {
                if (bundle) {
                    includeJavascriptFiles(Timeline.urlPrefix, [ "timeline-bundle.js" ]);
                    includeCssFiles(Timeline.urlPrefix, [ "timeline-bundle.css" ]);
                } else {
                    includeJavascriptFiles(Timeline.urlPrefix + "scripts/", javascriptFiles);
                    includeCssFiles(Timeline.urlPrefix + "styles/", cssFiles);
                }
            }

            /*
             *  Include localized files
             */
            var loadLocale = [];
            loadLocale[defaultServerLocale] = true;

            var tryExactLocale = function(locale) {
                for (var l = 0; l < supportedLocales.length; l++) {
                    if (locale == supportedLocales[l]) {
                        loadLocale[locale] = true;
                        return true;
                    }
                }
                return false;
            }
            var tryLocale = function(locale) {
                if (tryExactLocale(locale)) {
                    return locale;
                }

                var dash = locale.indexOf("-");
                if (dash > 0 && tryExactLocale(locale.substr(0, dash))) {
                    return locale.substr(0, dash);
                }

                return null;
            }

            for (var l = 0; l < desiredLocales.length; l++) {
                tryLocale(desiredLocales[l]);
            }

            var defaultClientLocale = defaultServerLocale;
            var defaultClientLocales = ("language" in navigator ? navigator.language : navigator.browserLanguage).split(";");
            for (var l = 0; l < defaultClientLocales.length; l++) {
                var locale = tryLocale(defaultClientLocales[l]);
                if (locale != null) {
                    defaultClientLocale = locale;
                    break;
                }
            }

            if (!isCompiled) {
                for (var l = 0; l < supportedLocales.length; l++) {
                    var locale = supportedLocales[l];
                    if (loadLocale[locale]) {
                        includeJavascriptFiles(Timeline.urlPrefix + "scripts/l10n/" + locale + "/", localizedJavascriptFiles);
                        includeCssFiles(Timeline.urlPrefix + "styles/l10n/" + locale + "/", localizedCssFiles);
                    }
                }
            }

            if (forceLocale == null) {
              Timeline.serverLocale = defaultServerLocale;
              Timeline.clientLocale = defaultClientLocale;
            } else {
              Timeline.serverLocale = forceLocale;
              Timeline.clientLocale = forceLocale;
            }
        } catch (e) {
            alert(e);
        }
    };

    /*
     *  Load SimileAjax if it's not already loaded
     */
    if (typeof SimileAjax == "undefined" && !isCompiled) {
        window.SimileAjax_onLoad = loadMe;

        var url = useLocalResources ?
            "http://127.0.0.1:9999/ajax/api/simile-ajax-api.js?bundle=false" :
            "http://api.simile-widgets.org/ajax/" + simile_ajax_ver + "/simile-ajax-api.js";
        if (typeof Timeline_ajax_url == "string") {
           url = Timeline_ajax_url;
        }
        var createScriptElement = function() {
            var script = document.createElement("script");
            script.type = "text/javascript";
            script.language = "JavaScript";
            script.src = url;
            document.getElementsByTagName("head")[0].appendChild(script);
        }
        if (document.body == null) {
            try {
                document.write("<script src='" + url + "' type='text/javascript'></script>");
            } catch (e) {
                createScriptElement();
            }
        } else {
            createScriptElement();
        }
    } else {
        loadMe();
    }
})();
<commit_msg>TIMELINE: no units.js file exists directly in scripts/ at this time<commit_after>/*==================================================
 *
 *              Timeline API
 *              ------------
 *
 * This file will load all the Javascript files
 * necessary to make the standard timeline work.
 * It also detects the default locale.
 *
 * To run Timeline directly from the www.simile-widgets.org server
 * include this fragment in your HTML file as follows:
 *
 *   <script src="http://api.simile-widgets.org/timeline/2.3.1/timeline-api.js"
 *    type="text/javascript"></script>
 *
 *
 * To host the Timeline files on your own server:
 *
 * 1) Install the Timeline files onto your webserver using
 *    the minimal distribution "timeline_<version>_minimal.(zip|tar.gz)" found at
 *
 *        http://code.google.com/p/simile-widgets/downloads/list
 *
 * 2) Set the following global js variable used to send parameters to this script:
 *     var Timeline_ajax_url  -- URL for simile-ajax-api.js
 *     var Timeline_urlPrefix -- URL for the *directory* that contains timeline-api.js
 *                               on your web site (including the trailing slash!)
 *
 * eg your HTML page would include
 *
 *   <script>
 *     var Timeline_ajax_url="http://YOUR_SERVER/apis/timeline/ajax/simile-ajax-api.js";
 *     var Timeline_urlPrefix='http://YOUR_SERVER/apis/timeline/';
 *   </script>
 *   <script src="http://YOUR_SERVER/javascripts/timeline/timeline-api.js"
 *     type="text/javascript">
 *   </script>
 *
 * SCRIPT PARAMETERS
 *
 * This script auto-magically figures out locale and has defaults for other parameters
 * To set parameters explicity, set js global variable Timeline_parameters or include as
 * parameters on the url using GET style. Eg the two next lines pass the same parameters:
 *     Timeline_parameters='bundle=true';                    // pass parameter via js variable
 *     <script src="http://....timeline-api.js?bundle=true"  // pass parameter via url
 *
 * Parameters
 *   timeline-use-local-resources --
 *   bundle -- true: use the single js bundle file; false: load individual files (for debugging)
 *   locales --
 *   defaultLocale --
 *   forceLocale -- force locale to be a particular value--used for debugging. Normally locale is determined
 *                  by browser's and server's locale settings.
 *
 * DEBUGGING
 *
 * If you have a problem with Timeline, the first step is to use the unbundled Javascript files. To do so:
 * To use the unbundled Timeline and Ajax libraries
 * Change
 *   <script src="http://api.simile-widgets.org/timeline/2.3.1/api/timeline-api.js?bundle=true" type="text/javascript"></script>
 * To
 *   <script>var Timeline_ajax_url = "http://api.simile-widgets.org/ajax/2.2.1/simile-ajax-api.js?bundle=false"</script>
 *   <script src="http://api.simile-widgets.org/timeline/2.3.1/api/timeline-api.js?bundle=false" type="text/javascript"></script>
 *
 * Note that the Ajax version is usually NOT the same as the Timeline version.
 * See variable simile_ajax_ver below for the current version
 *
 *==================================================
 */

(function() {

    var simile_ajax_ver = "2.2.1"; // ===========>>>  current Simile-Ajax version

    var isCompiled = ("Timeline_isCompiled" in window) && window.Timeline_isCompiled;

    var useLocalResources = false;
    if (document.location.search.length > 0) {
        var params = document.location.search.substr(1).split("&");
        for (var i = 0; i < params.length; i++) {
            if (params[i] == "timeline-use-local-resources") {
                useLocalResources = true;
            }
        }
    };

    var loadMe = function() {
        if ("Timeline" in window) {
            return;
        }

        window.Timeline = new Object();
        window.Timeline.DateTime = window.SimileAjax.DateTime; // for backward compatibility

        var bundle = false;
        var javascriptFiles = [
            "timeline.js",
            "band.js",
            "themes.js",
            "ethers.js",
            "ether-painters.js",
            "event-utils.js",
            "labellers.js",
            "sources.js",
            "original-painter.js",
            "detailed-painter.js",
            "overview-painter.js",
            "compact-painter.js",
            "decorators.js"
        ];
        var cssFiles = [
            "timeline.css",
            "ethers.css",
            "events.css"
        ];

        var localizedJavascriptFiles = [
            "timeline.js",
            "labellers.js"
        ];
        var localizedCssFiles = [
        ];

        // ISO-639 language codes, ISO-3166 country codes (2 characters)
        var supportedLocales = [
            "cs",       // Czech
            "de",       // German
            "en",       // English
            "es",       // Spanish
            "fr",       // French
            "it",       // Italian
            "nl",       // Dutch (The Netherlands)
            "ru",       // Russian
            "se",       // Swedish
            "tr",       // Turkish
            "vi",       // Vietnamese
            "zh"        // Chinese
        ];

        try {
            var desiredLocales = [ "en" ],
                defaultServerLocale = "en",
                forceLocale = null;

            var parseURLParameters = function(parameters) {
                var params = parameters.split("&");
                for (var p = 0; p < params.length; p++) {
                    var pair = params[p].split("=");
                    if (pair[0] == "locales") {
                        desiredLocales = desiredLocales.concat(pair[1].split(","));
                    } else if (pair[0] == "defaultLocale") {
                        defaultServerLocale = pair[1];
                    } else if (pair[0] == "forceLocale") {
                        forceLocale = pair[1];
                        desiredLocales = desiredLocales.concat(pair[1].split(","));
                    } else if (pair[0] == "bundle") {
                        bundle = pair[1] != "false";
                    }
                }
            };

            (function() {
                if (typeof Timeline_urlPrefix == "string") {
                    Timeline.urlPrefix = Timeline_urlPrefix;
                    if (typeof Timeline_parameters == "string") {
                        parseURLParameters(Timeline_parameters);
                    }
                } else {
                    var heads = document.documentElement.getElementsByTagName("head");
                    for (var h = 0; h < heads.length; h++) {
                        var scripts = heads[h].getElementsByTagName("script");
                        for (var s = 0; s < scripts.length; s++) {
                            var url = scripts[s].src;
                            var i = url.indexOf("timeline-api.js");
                            if (i >= 0) {
                                Timeline.urlPrefix = url.substr(0, i);
                                var q = url.indexOf("?");
                                if (q > 0) {
                                    parseURLParameters(url.substr(q + 1));
                                }
                                return;
                            }
                        }
                    }
                    throw new Error("Failed to derive URL prefix for Timeline API code files");
                }
            })();

            var includeJavascriptFiles = function(urlPrefix, filenames) {
                SimileAjax.includeJavascriptFiles(document, urlPrefix, filenames);
            }
            var includeCssFiles = function(urlPrefix, filenames) {
                SimileAjax.includeCssFiles(document, urlPrefix, filenames);
            }

            /*
             *  Include non-localized files
             */
            if (!isCompiled) {
                if (bundle) {
                    includeJavascriptFiles(Timeline.urlPrefix, [ "timeline-bundle.js" ]);
                    includeCssFiles(Timeline.urlPrefix, [ "timeline-bundle.css" ]);
                } else {
                    includeJavascriptFiles(Timeline.urlPrefix + "scripts/", javascriptFiles);
                    includeCssFiles(Timeline.urlPrefix + "styles/", cssFiles);
                }
            }

            /*
             *  Include localized files
             */
            var loadLocale = [];
            loadLocale[defaultServerLocale] = true;

            var tryExactLocale = function(locale) {
                for (var l = 0; l < supportedLocales.length; l++) {
                    if (locale == supportedLocales[l]) {
                        loadLocale[locale] = true;
                        return true;
                    }
                }
                return false;
            }
            var tryLocale = function(locale) {
                if (tryExactLocale(locale)) {
                    return locale;
                }

                var dash = locale.indexOf("-");
                if (dash > 0 && tryExactLocale(locale.substr(0, dash))) {
                    return locale.substr(0, dash);
                }

                return null;
            }

            for (var l = 0; l < desiredLocales.length; l++) {
                tryLocale(desiredLocales[l]);
            }

            var defaultClientLocale = defaultServerLocale;
            var defaultClientLocales = ("language" in navigator ? navigator.language : navigator.browserLanguage).split(";");
            for (var l = 0; l < defaultClientLocales.length; l++) {
                var locale = tryLocale(defaultClientLocales[l]);
                if (locale != null) {
                    defaultClientLocale = locale;
                    break;
                }
            }

            if (!isCompiled) {
                for (var l = 0; l < supportedLocales.length; l++) {
                    var locale = supportedLocales[l];
                    if (loadLocale[locale]) {
                        includeJavascriptFiles(Timeline.urlPrefix + "scripts/l10n/" + locale + "/", localizedJavascriptFiles);
                        includeCssFiles(Timeline.urlPrefix + "styles/l10n/" + locale + "/", localizedCssFiles);
                    }
                }
            }

            if (forceLocale == null) {
              Timeline.serverLocale = defaultServerLocale;
              Timeline.clientLocale = defaultClientLocale;
            } else {
              Timeline.serverLocale = forceLocale;
              Timeline.clientLocale = forceLocale;
            }
        } catch (e) {
            alert(e);
        }
    };

    /*
     *  Load SimileAjax if it's not already loaded
     */
    if (typeof SimileAjax == "undefined" && !isCompiled) {
        window.SimileAjax_onLoad = loadMe;

        var url = useLocalResources ?
            "http://127.0.0.1:9999/ajax/api/simile-ajax-api.js?bundle=false" :
            "http://api.simile-widgets.org/ajax/" + simile_ajax_ver + "/simile-ajax-api.js";
        if (typeof Timeline_ajax_url == "string") {
           url = Timeline_ajax_url;
        }
        var createScriptElement = function() {
            var script = document.createElement("script");
            script.type = "text/javascript";
            script.language = "JavaScript";
            script.src = url;
            document.getElementsByTagName("head")[0].appendChild(script);
        }
        if (document.body == null) {
            try {
                document.write("<script src='" + url + "' type='text/javascript'></script>");
            } catch (e) {
                createScriptElement();
            }
        } else {
            createScriptElement();
        }
    } else {
        loadMe();
    }
})();
<|endoftext|>


<commit_before>var SearchString = function (props) {
  this._field = null
  this._value = null
  this._inQuotes = false
  this._operator = 'and'

  if (props == null) {
  }

  else if (typeof props === 'string') {
    this.value = props
  }

  else if (props instanceof Object) {
    this.field = props.field
    this.value = props.value
    this.operator = props.operator
    this.inQuotes = props.inQuotes
  }
}

Object.defineProperty(SearchString.prototype, 'field', {
  set: function (value) { this._field = value || null },
  get: function () { return this._field },
  configurable: true,
  enumerable: true
})

Object.defineProperty(SearchString.prototype, 'value', {
  set: function (value) { this._value = value || null },
  get: function () { return this._value },
  configurable: true,
  enumerable: true
})

Object.defineProperty(SearchString.prototype, 'inQuotes', {
  set: function (value) { this._inQuotes = !!value },
  get: function () { return this._inQuotes },
  configurable: true,
  enumerable: true
})

Object.defineProperty(SearchString.prototype, 'operator', {
  set: function (value) { this._operator = /and|or|not/i.test(value) ? value.toLowerCase() : 'and' },
  get: function () { return this._operator },
  configurable: true,
  enumerable: true
})

Object.defineProperty(SearchString.prototype, 'and', {
  get: function () { return this._operator === 'and' }
})

Object.defineProperty(SearchString.prototype, 'or', {
  get: function () { return this._operator === 'or' }
})

Object.defineProperty(SearchString.prototype, 'not', {
  get: function () { return this._operator === 'not' }
})

SearchString.prototype.toString = function (noOperator) {
  var returns = []

  if (noOperator !== true && this.operator !== 'and') {
    returns.push(this.operator.toUpperCase() + ' ')
  }

  if (this.field) {
    returns.push(this.field + ':')
  }

  if (this.inQuotes) {
    returns.push(`"${ this.value }"`)
  }

  else {
    returns.push(this.value)
  }

  return returns.join('')
}

var __extends__ = {
  Or: function (props) {
    SearchString.call(this, props)
    this.operator = 'or'
  },
  And: function (props) {
    SearchString.call(this, props)
    this.operator = 'and'
  },
  Not: function (props) {
    SearchString.call(this, props)
    this._operator = 'not'
  },
  Word: function (props) {
    SearchString.call(this, props)
  },
  Pipe: function (props) {
    SearchString.call(this, props)
    this._operator = 'or'
  },
  Minus: function (props) {
    SearchString.call(this, props)
    this._operator = 'not'
  },
  Field: function (props) {
    SearchString.call(this, props)
  },
  Brackets: function (props) {
    SearchString.call(this, props)
  },
  SingleQuotes: function (props) {
    SearchString.call(this, props)
    this._inQuotes = true
  },
  DoubleQuotes: function (props) {
    SearchString.call(this, props)
    this._inQuotes = true
  }
}

module.exports = SearchString

for (var className of Object.keys(__extends__)) {
  var childClass = __extends__[className]
  Object.setPrototypeOf(childClass.prototype, SearchString.prototype)
  module.exports[className] = childClass
}
<commit_msg>issue #7 バッククォート廃止<commit_after>var SearchString = function (props) {
  this._field = null
  this._value = null
  this._inQuotes = false
  this._operator = 'and'

  if (props == null) {
  }

  else if (typeof props === 'string') {
    this.value = props
  }

  else if (props instanceof Object) {
    this.field = props.field
    this.value = props.value
    this.operator = props.operator
    this.inQuotes = props.inQuotes
  }
}

Object.defineProperty(SearchString.prototype, 'field', {
  set: function (value) { this._field = value || null },
  get: function () { return this._field },
  configurable: true,
  enumerable: true
})

Object.defineProperty(SearchString.prototype, 'value', {
  set: function (value) { this._value = value || null },
  get: function () { return this._value },
  configurable: true,
  enumerable: true
})

Object.defineProperty(SearchString.prototype, 'inQuotes', {
  set: function (value) { this._inQuotes = !!value },
  get: function () { return this._inQuotes },
  configurable: true,
  enumerable: true
})

Object.defineProperty(SearchString.prototype, 'operator', {
  set: function (value) { this._operator = /and|or|not/i.test(value) ? value.toLowerCase() : 'and' },
  get: function () { return this._operator },
  configurable: true,
  enumerable: true
})

Object.defineProperty(SearchString.prototype, 'and', {
  get: function () { return this._operator === 'and' }
})

Object.defineProperty(SearchString.prototype, 'or', {
  get: function () { return this._operator === 'or' }
})

Object.defineProperty(SearchString.prototype, 'not', {
  get: function () { return this._operator === 'not' }
})

SearchString.prototype.toString = function (noOperator) {
  var returns = []

  if (noOperator !== true && this.operator !== 'and') {
    returns.push(this.operator.toUpperCase() + ' ')
  }

  if (this.field) {
    returns.push(this.field + ':')
  }

  if (this.inQuotes) {
    returns.push('"' + this.value + '"')
  }

  else {
    returns.push(this.value)
  }

  return returns.join('')
}

var __extends__ = {
  Or: function (props) {
    SearchString.call(this, props)
    this.operator = 'or'
  },
  And: function (props) {
    SearchString.call(this, props)
    this.operator = 'and'
  },
  Not: function (props) {
    SearchString.call(this, props)
    this._operator = 'not'
  },
  Word: function (props) {
    SearchString.call(this, props)
  },
  Pipe: function (props) {
    SearchString.call(this, props)
    this._operator = 'or'
  },
  Minus: function (props) {
    SearchString.call(this, props)
    this._operator = 'not'
  },
  Field: function (props) {
    SearchString.call(this, props)
  },
  Brackets: function (props) {
    SearchString.call(this, props)
  },
  SingleQuotes: function (props) {
    SearchString.call(this, props)
    this._inQuotes = true
  },
  DoubleQuotes: function (props) {
    SearchString.call(this, props)
    this._inQuotes = true
  }
}

module.exports = SearchString

for (var className of Object.keys(__extends__)) {
  var childClass = __extends__[className]
  Object.setPrototypeOf(childClass.prototype, SearchString.prototype)
  module.exports[className] = childClass
}
<|endoftext|>


<commit_before>/**
 * mSupply Mobile
 * Sustainable Solutions (NZ) Ltd. 2016
 */

import React from 'react';
import PropTypes from 'prop-types';
import autobind from 'react-autobind';
import { TextInput, StyleSheet } from 'react-native';
import { formatExpiryDate, parseExpiryDate } from '../utilities';

/* Component that allows masking of TextInput for date in format MM/YY[YY] */
export class ExpiryTextInput extends React.Component {
  constructor(props) {
    super(props);
    this.previousText = '';
    autobind(this);
    this.state = { text: this.getFormattedDate(this.props.text) };
  }

  // Remove all text on focus
  onFocus() {
    this.setState({ text: '' });
  }

  onChangeText(text) {
    let newTextValue = text;

    if (text.length === 1) {
      const monthValue = parseInt(text, 10);
      if (isNaN(monthValue) || monthValue < 0) return;
    } else if (this.previousText.length === 3 && text.length === 2) {
      // In case backspace is pressed after '/' is inserted, we delete the last digit
      // of the month
      newTextValue = newTextValue.slice(0, 1);
    } else if (text.length === 2) {
      if (text.endsWith('/')) {
        // If backspace is pressed with only one month digit prepend 0
        newTextValue = `0${text}`;
      } else {
        // If two digits are entered append '/''
        const monthValue = parseInt(text, 10);
        if (isNaN(monthValue) || monthValue > 12) return;
        newTextValue = `${text}/`;
      }
    } else if (text.length > 3 && text.length < 8) {
      // Disallow chaning cursor position changing of month
      if (text.slice(2, 3) !== '/') return;
      // Make sure everything after '/' is a number
      const yearValue = parseInt(text.slice(3), 10);
      if (isNaN(yearValue)) return;
    } else if (text.length !== 0 && text.length !== 3) return; // Allow MM/ value and empty value

    this.previousText = newTextValue;
    this.setState({ text: newTextValue });
  }

  onEndEditing(event) {
    const expiryDate = parseExpiryDate(event.nativeEvent.text);
    if (expiryDate && this.props.onEndEditing) {
      this.props.onEndEditing(expiryDate);
    }
    this.setState({ text: this.getFormattedDate(expiryDate) });
  }

  getFormattedDate(date) {
    // Remember previous good date
    this.previousFormatedDate = formatExpiryDate(date) || this.previousFormatedDate;
    return this.previousFormatedDate || 'mm/yy';
  }

  render() {
    const { style, ...extraProps } = this.props;
    return (
      <TextInput
        {...extraProps}
        keyboardType="numeric"
        onEndEditing={this.onEndEditing}
        editable={this.props.isEditable}
        onChangeText={this.onChangeText}
        onFocus={this.onFocus}
        value={this.state.text}
        style={[style, localStyles.middleAlignText]}
      />
    );
  }
}

ExpiryTextInput.propTypes = {
  text: PropTypes.string,
  style: PropTypes.object,
  onEndEditing: PropTypes.func,
  isEditable: PropTypes.bool,
};

const localStyles = StyleSheet.create({
  middleAlignText: {
    textAlign: 'center',
  },
});
<commit_msg>Change prop type to object (date)<commit_after>/**
 * mSupply Mobile
 * Sustainable Solutions (NZ) Ltd. 2016
 */

import React from 'react';
import PropTypes from 'prop-types';
import autobind from 'react-autobind';
import { TextInput, StyleSheet } from 'react-native';
import { formatExpiryDate, parseExpiryDate } from '../utilities';

/* Component that allows masking of TextInput for date in format MM/YY[YY] */
export class ExpiryTextInput extends React.Component {
  constructor(props) {
    super(props);
    this.previousText = '';
    autobind(this);
    this.state = { text: this.getFormattedDate(this.props.text) };
  }

  // Remove all text on focus
  onFocus() {
    this.setState({ text: '' });
  }

  onChangeText(text) {
    let newTextValue = text;

    if (text.length === 1) {
      const monthValue = parseInt(text, 10);
      if (isNaN(monthValue) || monthValue < 0) return;
    } else if (this.previousText.length === 3 && text.length === 2) {
      // In case backspace is pressed after '/' is inserted, we delete the last digit
      // of the month
      newTextValue = newTextValue.slice(0, 1);
    } else if (text.length === 2) {
      if (text.endsWith('/')) {
        // If backspace is pressed with only one month digit prepend 0
        newTextValue = `0${text}`;
      } else {
        // If two digits are entered append '/''
        const monthValue = parseInt(text, 10);
        if (isNaN(monthValue) || monthValue > 12) return;
        newTextValue = `${text}/`;
      }
    } else if (text.length > 3 && text.length < 8) {
      // Disallow chaning cursor position changing of month
      if (text.slice(2, 3) !== '/') return;
      // Make sure everything after '/' is a number
      const yearValue = parseInt(text.slice(3), 10);
      if (isNaN(yearValue)) return;
    } else if (text.length !== 0 && text.length !== 3) return; // Allow MM/ value and empty value

    this.previousText = newTextValue;
    this.setState({ text: newTextValue });
  }

  onEndEditing(event) {
    const expiryDate = parseExpiryDate(event.nativeEvent.text);
    if (expiryDate && this.props.onEndEditing) {
      this.props.onEndEditing(expiryDate);
    }
    this.setState({ text: this.getFormattedDate(expiryDate) });
  }

  getFormattedDate(date) {
    // Remember previous good date
    this.previousFormatedDate = formatExpiryDate(date) || this.previousFormatedDate;
    return this.previousFormatedDate || 'mm/yy';
  }

  render() {
    const { style, ...extraProps } = this.props;
    return (
      <TextInput
        {...extraProps}
        keyboardType="numeric"
        onEndEditing={this.onEndEditing}
        editable={this.props.isEditable}
        onChangeText={this.onChangeText}
        onFocus={this.onFocus}
        value={this.state.text}
        style={[style, localStyles.middleAlignText]}
      />
    );
  }
}

ExpiryTextInput.propTypes = {
  text: PropTypes.object,
  style: PropTypes.object,
  onEndEditing: PropTypes.func,
  isEditable: PropTypes.bool,
};

const localStyles = StyleSheet.create({
  middleAlignText: {
    textAlign: 'center',
  },
});
<|endoftext|>


<commit_before>/**
 * mSupply Mobile
 * Sustainable Solutions (NZ) Ltd. 2016
 */

import React, { useCallback, useState } from 'react';
import PropTypes from 'prop-types';

import { Dimensions, FlatList, StyleSheet, View, Text } from 'react-native';
import globalStyles, { APP_FONT_FAMILY, SUSSOL_ORANGE } from '../globalStyles';
import { generalStrings, buttonStrings } from '../localization';
import { OnePressButton, SearchBar } from '.';
import ResultRow from './ResultRow';
import { WHITE } from '../globalStyles/colors';

const MultiSelectList = ({
  options,
  sortByString,
  queryString,
  queryStringSecondary,
  primaryFilterProperty,
  secondaryFilterProperty,
  renderLeftText,
  renderRightText,
  showCheckIcon,
  placeholderText,
  onConfirmSelections,
  emptyMessage,
}) => {
  const [queryText, setQueryText] = useState('');
  const [selected, setSelected] = useState([]);

  const filterResultData = () => {
    const data = options.filtered(queryString, queryText);
    const secondaryFiltered = queryStringSecondary
      ? data.filtered(queryStringSecondary, queryText)
      : data;
    const sortedData = secondaryFiltered.sorted(sortByString);
    return sortedData;
  };

  const filterArrayData = () => {
    const regexFilter = RegExp(queryText, 'i');

    return options.filter(
      optionItem =>
        regexFilter.test(optionItem[primaryFilterProperty]) ||
        regexFilter.test(optionItem[secondaryFilterProperty])
    );
  };

  const getData = () => {
    if (options && options.filtered && queryString) return filterResultData(options);
    if (Array.isArray(options) && primaryFilterProperty) return filterArrayData(options);
    return [];
  };

  const onDoneSelected = useCallback(() => onConfirmSelections(selected), [selected]);

  const data = getData();

  const onSelect = useCallback(
    ({ item }) =>
      setSelected(prevState =>
        prevState.indexOf(item.id) === -1
          ? [...prevState, item.id]
          : prevState.filter(selectedItem => selectedItem !== item.id)
      ),
    []
  );

  const renderItem = useCallback(
    row => (
      <ResultRow
        data={row}
        onPress={onSelect}
        isSelected={selected.indexOf(row.item.id) >= 0}
        renderLeftText={renderLeftText}
        renderRightText={renderRightText}
        showCheckIcon={showCheckIcon}
      />
    ),
    [data, onSelect, renderLeftText, renderRightText, showCheckIcon]
  );

  const EmptyComponent = useCallback(
    ({ title }) => (
      <View style={localStyles.emptyContainer}>
        <Text style={localStyles.emptyText}>{title}</Text>
      </View>
    ),
    []
  );

  return (
    <View style={localStyles.container}>
      <SearchBar
        onChangeText={setQueryText}
        value={queryText}
        color={WHITE}
        placeholder={placeholderText}
      />
      <FlatList
        data={data}
        keyExtractor={item => item.id || item.name}
        renderItem={renderItem}
        keyboardShouldPersistTaps="always"
        style={localStyles.resultList}
        extraData={selected}
        ListEmptyComponent={<EmptyComponent title={emptyMessage} />}
      />
      <View style={localStyles.contentContainer}>
        <View style={localStyles.buttonContainer}>
          <OnePressButton
            style={[globalStyles.button, localStyles.confirmButton]}
            textStyle={[globalStyles.buttonText, localStyles.confirmButtonText]}
            text={buttonStrings.done}
            onPress={onDoneSelected}
          />
        </View>
      </View>
    </View>
  );
};

export default MultiSelectList;
export { MultiSelectList };

/* eslint-disable react/forbid-prop-types, react/require-default-props */
MultiSelectList.propTypes = {
  options: PropTypes.oneOfType([PropTypes.object, PropTypes.array]),
  queryString: PropTypes.string.isRequired,
  queryStringSecondary: PropTypes.string,
  sortByString: PropTypes.string.isRequired,
  placeholderText: PropTypes.string,
  renderLeftText: PropTypes.func,
  renderRightText: PropTypes.func,
  primaryFilterProperty: PropTypes.string,
  secondaryFilterProperty: PropTypes.string,
  emptyMessage: PropTypes.string,
  onConfirmSelections: PropTypes.func,
  showCheckIcon: PropTypes.bool,
};

MultiSelectList.defaultProps = {
  placeholderText: generalStrings.start_typing_to_search,
  showCheckIcon: true,
};

const localStyles = StyleSheet.create({
  container: {
    flex: 1,
  },
  emptyContainer: {
    flex: 1,
    alignItems: 'flex-start',
  },
  emptyText: {
    fontSize: 20,
    fontFamily: APP_FONT_FAMILY,
    padding: 15,
  },
  buttonContainer: {
    position: 'absolute',
    top: 15,
    right: 0,
  },
  contentContainer: {
    paddingTop: Dimensions.get('window').height / 10, // Start the content 1/10 down the page
  },
  resultList: {
    flex: 1,
    marginHorizontal: 10,
    backgroundColor: 'white',
    borderColor: '#b9b9b9',
    borderRadius: 1,
    borderWidth: 1,
  },
  confirmButton: {
    backgroundColor: SUSSOL_ORANGE,
  },
  confirmButtonText: {
    color: 'white',
    fontSize: 20,
    fontFamily: APP_FONT_FAMILY,
  },
});
<commit_msg>Add tidy styles<commit_after>/* eslint-disable react/require-default-props */
/**
 * mSupply Mobile
 * Sustainable Solutions (NZ) Ltd. 2016
 */

import React, { useCallback, useState } from 'react';
import PropTypes from 'prop-types';

import { FlatList, StyleSheet, View, Text } from 'react-native';
import globalStyles, { APP_FONT_FAMILY, SUSSOL_ORANGE } from '../globalStyles';
import { generalStrings, buttonStrings } from '../localization';
import { OnePressButton, SearchBar } from '.';
import ResultRow from './ResultRow';
import { WHITE } from '../globalStyles/colors';

const keyExtractor = item => item.id || item.name;

const MultiSelectList = ({
  options,
  sortByString,
  queryString,
  queryStringSecondary,
  primaryFilterProperty,
  secondaryFilterProperty,
  renderLeftText,
  renderRightText,
  showCheckIcon,
  placeholderText,
  onConfirmSelections,
  emptyMessage,
}) => {
  const [queryText, setQueryText] = useState('');
  const [selected, setSelected] = useState([]);

  const onDoneSelected = useCallback(() => onConfirmSelections(selected), [selected]);
  const onSelect = useCallback(
    ({ item }) =>
      setSelected(prevState =>
        prevState.indexOf(item.id) === -1
          ? [...prevState, item.id]
          : prevState.filter(selectedItem => selectedItem !== item.id)
      ),
    []
  );

  const filterResultData = () => {
    const data = options.filtered(queryString, queryText);
    const secondaryFiltered = queryStringSecondary
      ? data.filtered(queryStringSecondary, queryText)
      : data;
    const sortedData = secondaryFiltered.sorted(sortByString);
    return sortedData;
  };

  const filterArrayData = () => {
    const regexFilter = RegExp(queryText, 'i');

    return options.filter(
      optionItem =>
        regexFilter.test(optionItem[primaryFilterProperty]) ||
        regexFilter.test(optionItem[secondaryFilterProperty])
    );
  };

  const getData = () => {
    if (options && options.filtered && queryString) return filterResultData(options);
    if (Array.isArray(options) && primaryFilterProperty) return filterArrayData(options);
    return [];
  };

  const data = getData();

  const renderItem = useCallback(
    row => (
      <ResultRow
        data={row}
        onPress={onSelect}
        isSelected={selected.indexOf(row.item.id) >= 0}
        renderLeftText={renderLeftText}
        renderRightText={renderRightText}
        showCheckIcon={showCheckIcon}
      />
    ),
    [data, onSelect, renderLeftText, renderRightText, showCheckIcon]
  );

  const EmptyComponent = useCallback(
    ({ title }) => (
      <View style={localStyles.emptyContainer}>
        <Text style={localStyles.emptyText}>{title}</Text>
      </View>
    ),
    []
  );

  return (
    <View style={localStyles.container}>
      <SearchBar
        onChangeText={setQueryText}
        value={queryText}
        color={WHITE}
        placeholder={placeholderText}
      />
      <FlatList
        data={data}
        keyExtractor={keyExtractor}
        renderItem={renderItem}
        keyboardShouldPersistTaps="always"
        style={localStyles.resultList}
        extraData={selected}
        ListEmptyComponent={<EmptyComponent title={emptyMessage} />}
      />

      <View style={localStyles.buttonContainer}>
        <OnePressButton
          style={localStyles.confirmButton}
          textStyle={[globalStyles.buttonText, localStyles.confirmButtonText]}
          text={buttonStrings.done}
          onPress={onDoneSelected}
        />
      </View>
    </View>
  );
};

export default MultiSelectList;
export { MultiSelectList };

MultiSelectList.propTypes = {
  options: PropTypes.oneOfType([PropTypes.object, PropTypes.array]),
  queryString: PropTypes.string.isRequired,
  queryStringSecondary: PropTypes.string,
  sortByString: PropTypes.string.isRequired,
  placeholderText: PropTypes.string,
  renderLeftText: PropTypes.func,
  renderRightText: PropTypes.func,
  primaryFilterProperty: PropTypes.string,
  secondaryFilterProperty: PropTypes.string,
  emptyMessage: PropTypes.string,
  onConfirmSelections: PropTypes.func,
  showCheckIcon: PropTypes.bool,
};

MultiSelectList.defaultProps = {
  placeholderText: generalStrings.start_typing_to_search,
  showCheckIcon: true,
};

const localStyles = StyleSheet.create({
  container: { flex: 1 },
  emptyContainer: { flex: 1, alignItems: 'flex-start' },
  emptyText: { fontSize: 20, fontFamily: APP_FONT_FAMILY, padding: 15 },
  buttonContainer: { alignItems: 'flex-end' },
  resultList: { marginHorizontal: 5, backgroundColor: 'white' },
  confirmButton: { ...globalStyles.button, backgroundColor: SUSSOL_ORANGE },
  confirmButtonText: { color: 'white', fontSize: 20, fontFamily: APP_FONT_FAMILY },
});
<|endoftext|>


<commit_before>var os = require('os');
var fs = require('fs');
var path = require('path');
var argv = require('optimist');

// create paths
var paths = {MONO_ROOT: path.normalize(__dirname + '/../../')};
paths.LIB_ROOT = paths.MONO_ROOT + 'lib/';
paths.PROXY_SERVER = paths.LIB_ROOT + 'server/proxy.js';
paths.APPLICATION_SERVER = paths.LIB_ROOT + 'application/server.js';
paths.USERS_ROOT = paths.MONO_ROOT + 'users/';

// extend js functionality
require(paths.LIB_ROOT + 'extend');

// read mono package
var mono = JSON.parse(fs.readFileSync(paths.MONO_ROOT + 'package.json'));

// define possible options with default value, short name and description
var options = {
    "version": {
        "short": 'v',
        "description": 'print mono\'s version'
    },
    "host": {
        "value": '',
        "short": 'h',
        "description": 'define a host (useful with a proxy)'
    },
    "log": {
        "value": paths.MONO_ROOT + 'tmp/log.txt',
        "short": 'l',
        "description": 'specify a path for the log file'
    },
    "port": {
        "value": 8000,
        "description": 'port for http and websockets communication'
    },
    "appPortStart": {
        "value": 10001,
        "description": 'application port range start'
    },
    "appPortEnd": {
        "value": 19999,
        "description": 'application port range end'
    },
    "attempts": {
        "value": 3,
        "description": 'number of attempts to restart a script'
    },
    "minUptime": {
        "value": 2000,
        "description": 'minimum uptime (millis) for a script to not be considered "spinning"'
    },
    "spinSleepTime": {
        "value": 1000,
        "description": 'time to wait (millis) between launches of a spinning script'
    },
    "help": {
        "description": 'you\'re staring at it'
    }
};

// define possible actions with description
var actions = {
    "start": 'start mono server (it\'s the same as without "start")',
    "stop": 'stop mono server and applications'
};

// create help info text
function help (usage, info, actions, options, more, spaces) {

    if (arguments.length !== 6) {
        return 'printHelp: Invalid arguments';
    }

    spaces = parseInt(spaces, 10) || 20;

    usage = usage.split('\n');

    var text = 'usage: ' + usage[0] + '\n';
    for (var i = 1, l = usage.length; i < l; ++i) {
        text += '       ' + usage[i] + '\n';
    }

    text += '\n';
    text += info + '\n';

    var row;

    if (actions) {
        text += '\n';
        text += 'actions:\n';

        // create actions
        for (var action in actions) {
            row = '  ' + action;

            if (actions[action]) {
                for (i = 0, l = (spaces - row.length); i < l; ++i) {
                    row += ' ';
                }
                row += actions[action];
            }

            text += row + '\n';
        }
    }

    text += '\n';
    text += 'options:\n';

    // create options
    for (var option in options) {
        row = '  ';

        if (options[option].short) {
            row += '-' + options[option].short + ', ';
        }

        row += '--' + option;

        if (options[option].description) {
            for (i = 0, l = (spaces - row.length); i < l; ++i) {
                row += ' ';
            }
            row += options[option].description;

            if (typeof options[option].value !== 'undefined') {
                row += ' (' + options[option].value + ')';
            }
        }

        text += row + '\n';
    }

    text += '\n';
    text += more;
    text += '\n';

    return text;
}

// get host
function ip (version, internal) {

    var netIf = os.networkInterfaces();

    version = version || 4;
    internal = internal ? true : false;

    for (var netIfName in netIf) {

        for (var i = 0, l = netIf[netIfName].length; i < l; ++i) {

            if (
                netIf[netIfName][i] &&
                netIf[netIfName][i].internal == internal &&
                netIf[netIfName][i].family.toLowerCase() === "ipv" + version &&
                netIf[netIfName][i].address
            ) {

               return netIf[netIfName][i].address;
            }
        }
    }

    return null;
}

// create config
function getConfig () {

    var config = {};
    var helpText = help(
        'mono [actions] [options]',
        'The mono proxy server ' + mono.version,
        actions, options,
        'Documentation can be found at https://github.com/jillix/mono/',
        18
    );

    // create default config
    for (var defOption in options) {
        if (options[defOption].value) {
            config[defOption] = options[defOption].value;
        }
    }

    // set default cli options
    argv = argv.default(config).argv;

    // handle start and stop
    if (argv._[0] === 'stop') {
        return 'stop';
    } else if (argv._[0] && argv._[0] !== 'start') {
        return helpText;
    }

    // show version
    if (argv.v || argv.version) {
        return mono.version;
    }

    // merge cli options
    for (var option in argv) {

        // ignore keys
        if (option === '_' || option === '$0') {
            continue;
        }

        // try find long name
        if (typeof options[option] === 'undefined') {
            for (var long in options) {
                if (options[long].short === option) {
                    argv[long] = argv[option];
                    option = long;
                    break;
                }
            }
        }

        // show help
        if (option === 'help' || typeof options[option] === 'undefined') {
            return helpText;
        }

        // check argv option value
        if (typeof options[option].value === typeof argv[option]) {
            config[option] = argv[option];
        }
    }

    // paths
    config.paths = paths;

    // get the right host address, if no host is set
    if (!config.host) {

        config.host = ip();

        if (!config.host) {
            return 'Missing host';
        }
    }

    // save package infos in config
    config.mono = mono;

    return config;
}

module.exports = getConfig();
<commit_msg>added store config<commit_after>var os = require('os');
var fs = require('fs');
var path = require('path');
var argv = require('optimist');

// create paths
var paths = {MONO_ROOT: path.normalize(__dirname + '/../../')};
paths.LIB_ROOT = paths.MONO_ROOT + 'lib/';
paths.PROXY_SERVER = paths.LIB_ROOT + 'server/proxy.js';
paths.APPLICATION_SERVER = paths.LIB_ROOT + 'application/server.js';
paths.USERS_ROOT = paths.MONO_ROOT + 'users/';

// TOOD get this config from a file or the environment
var projectModel = 'm_projects';
var storeConfig = {
    storesCollection: 'm_stores',
    // TODO change to project_ when admin gets installed
    //systemDbPrefix: 'project_',
    systemDbPrefix: 'app_',
    systemStoreName: 'projects',
    systemAdapter: 'mongodb',
    systemConfig: {
        host: 'localhost',
        port: '27017',
        name: 'mono',
        user: 'server',
        pass: '1234'
    }
};

// extend js functionality
require(paths.LIB_ROOT + 'extend');

// read mono package
var mono = JSON.parse(fs.readFileSync(paths.MONO_ROOT + 'package.json'));

// define possible options with default value, short name and description
var options = {
    "version": {
        "short": 'v',
        "description": 'print mono\'s version'
    },
    "host": {
        "value": '',
        "short": 'h',
        "description": 'define a host (useful with a proxy)'
    },
    "log": {
        "value": paths.MONO_ROOT + 'tmp/log.txt',
        "short": 'l',
        "description": 'specify a path for the log file'
    },
    "port": {
        "value": 8000,
        "description": 'port for http and websockets communication'
    },
    "appPortStart": {
        "value": 10001,
        "description": 'application port range start'
    },
    "appPortEnd": {
        "value": 19999,
        "description": 'application port range end'
    },
    "attempts": {
        "value": 3,
        "description": 'number of attempts to restart a script'
    },
    "minUptime": {
        "value": 2000,
        "description": 'minimum uptime (millis) for a script to not be considered "spinning"'
    },
    "spinSleepTime": {
        "value": 1000,
        "description": 'time to wait (millis) between launches of a spinning script'
    },
    "help": {
        "description": 'you\'re staring at it'
    }
};

// define possible actions with description
var actions = {
    "start": 'start mono server (it\'s the same as without "start")',
    "stop": 'stop mono server and applications'
};

// create help info text
function help (usage, info, actions, options, more, spaces) {

    if (arguments.length !== 6) {
        return 'printHelp: Invalid arguments';
    }

    spaces = parseInt(spaces, 10) || 20;

    usage = usage.split('\n');

    var text = 'usage: ' + usage[0] + '\n';
    for (var i = 1, l = usage.length; i < l; ++i) {
        text += '       ' + usage[i] + '\n';
    }

    text += '\n';
    text += info + '\n';

    var row;

    if (actions) {
        text += '\n';
        text += 'actions:\n';

        // create actions
        for (var action in actions) {
            row = '  ' + action;

            if (actions[action]) {
                for (i = 0, l = (spaces - row.length); i < l; ++i) {
                    row += ' ';
                }
                row += actions[action];
            }

            text += row + '\n';
        }
    }

    text += '\n';
    text += 'options:\n';

    // create options
    for (var option in options) {
        row = '  ';

        if (options[option].short) {
            row += '-' + options[option].short + ', ';
        }

        row += '--' + option;

        if (options[option].description) {
            for (i = 0, l = (spaces - row.length); i < l; ++i) {
                row += ' ';
            }
            row += options[option].description;

            if (typeof options[option].value !== 'undefined') {
                row += ' (' + options[option].value + ')';
            }
        }

        text += row + '\n';
    }

    text += '\n';
    text += more;
    text += '\n';

    return text;
}

// get host
function ip (version, internal) {

    var netIf = os.networkInterfaces();

    version = version || 4;
    internal = internal ? true : false;

    for (var netIfName in netIf) {

        for (var i = 0, l = netIf[netIfName].length; i < l; ++i) {

            if (
                netIf[netIfName][i] &&
                netIf[netIfName][i].internal == internal &&
                netIf[netIfName][i].family.toLowerCase() === "ipv" + version &&
                netIf[netIfName][i].address
            ) {

               return netIf[netIfName][i].address;
            }
        }
    }

    return null;
}

// create config
function getConfig () {

    var config = {};
    var helpText = help(
        'mono [actions] [options]',
        'The mono proxy server ' + mono.version,
        actions, options,
        'Documentation can be found at https://github.com/jillix/mono/',
        18
    );

    // create default config
    for (var defOption in options) {
        if (options[defOption].value) {
            config[defOption] = options[defOption].value;
        }
    }

    // set default cli options
    argv = argv.default(config).argv;

    // handle start and stop
    if (argv._[0] === 'stop') {
        return 'stop';
    } else if (argv._[0] && argv._[0] !== 'start') {
        return helpText;
    }

    // show version
    if (argv.v || argv.version) {
        return mono.version;
    }

    // merge cli options
    for (var option in argv) {

        // ignore keys
        if (option === '_' || option === '$0') {
            continue;
        }

        // try find long name
        if (typeof options[option] === 'undefined') {
            for (var long in options) {
                if (options[long].short === option) {
                    argv[long] = argv[option];
                    option = long;
                    break;
                }
            }
        }

        // show help
        if (option === 'help' || typeof options[option] === 'undefined') {
            return helpText;
        }

        // check argv option value
        if (typeof options[option].value === typeof argv[option]) {
            config[option] = argv[option];
        }
    }

    // paths
    config.paths = paths;

    // get the right host address, if no host is set
    if (!config.host) {

        config.host = ip();

        if (!config.host) {
            return 'Missing host';
        }
    }

    // save store config
    config.store = storeConfig;

    // save projects model name
    config.projectModel = projectModel;

    // save package infos in config
    config.mono = mono;

    return config;
}

module.exports = getConfig();
<|endoftext|>


<commit_before>/* eslint-disable react/forbid-prop-types */
/**
 * mSupply Mobile
 * Sustainable Solutions (NZ) Ltd. 2019
 */

import React from 'react';
import PropTypes from 'prop-types';

import { connect } from 'react-redux';

import { PageButton } from '../PageButton';
import { PrescriptionInfo } from '../PrescriptionInfo';
import { SimpleTable } from '../SimpleTable';

import { UIDatabase } from '../../database';
import { getColumns } from '../../pages/dataTableUtilities';
import {
  selectPrescriber,
  selectItem,
  switchTab,
  editQuantity,
} from '../../reducers/PrescriptionReducer';

import { PrescriptionCart } from '../PrescriptionCart';
import { FlexRow } from '../FlexRow';
import { FlexColumn } from '../FlexColumn';
import { FlexView } from '../FlexView';

const ItemSelectComponent = ({ transaction, chooseItem, nextTab, updateQuantity }) => {
  const columns = getColumns('itemSelect');

  const disabledRows = UIDatabase.objects('Item').reduce(
    (acc, value) => ({ ...acc, [value.id]: value.totalQuantity <= 0 }),
    {}
  );

  const selectedRows = transaction.items.reduce(
    (acc, { item }) => ({ ...acc, [item.id]: true }),
    {}
  );
  return (
    <>
      <PrescriptionInfo />
      <FlexRow flex={1}>
        <FlexView flex={10}>
          <SimpleTable
            data={UIDatabase.objects('Item')}
            columns={columns}
            disabledRows={disabledRows}
            selectedRows={selectedRows}
            selectRow={chooseItem}
          />
        </FlexView>
        <FlexColumn flex={15}>
          <PrescriptionCart items={transaction.items.slice()} onChangeQuantity={updateQuantity} />
          <PageButton text="Next" onPress={() => nextTab(1)} style={{ alignSelf: 'flex-end' }} />
        </FlexColumn>
      </FlexRow>
    </>
  );
};

const mapDispatchToProps = dispatch => {
  const choosePrescriber = prescriberID => dispatch(selectPrescriber(prescriberID));
  const chooseItem = itemID => dispatch(selectItem(itemID));
  const nextTab = currentTab => dispatch(switchTab(currentTab + 1));
  const updateQuantity = (id, quantity) => dispatch(editQuantity(id, quantity));
  return { nextTab, choosePrescriber, chooseItem, updateQuantity };
};

const mapStateToProps = state => {
  const { prescription } = state;
  return { ...prescription };
};

ItemSelectComponent.propTypes = {
  transaction: PropTypes.object.isRequired,
  chooseItem: PropTypes.func.isRequired,
  nextTab: PropTypes.func.isRequired,
  updateQuantity: PropTypes.func.isRequired,
};

export const ItemSelect = connect(mapStateToProps, mapDispatchToProps)(ItemSelectComponent);
<commit_msg>Add ItemSelect doc string<commit_after>/* eslint-disable react/forbid-prop-types */
/**
 * mSupply Mobile
 * Sustainable Solutions (NZ) Ltd. 2019
 */

import React from 'react';
import PropTypes from 'prop-types';

import { connect } from 'react-redux';

import { PageButton } from '../PageButton';
import { PrescriptionInfo } from '../PrescriptionInfo';
import { SimpleTable } from '../SimpleTable';

import { UIDatabase } from '../../database';
import { getColumns } from '../../pages/dataTableUtilities';
import {
  selectPrescriber,
  selectItem,
  switchTab,
  editQuantity,
} from '../../reducers/PrescriptionReducer';

import { PrescriptionCart } from '../PrescriptionCart';
import { FlexRow } from '../FlexRow';
import { FlexColumn } from '../FlexColumn';
import { FlexView } from '../FlexView';

/**
 * Layout component used for a tab within the prescription wizard.
 *
 * @prop {Func} transaction    Underlying realm Transaction for this script.
 * @prop {Func} chooseItem     Callback for selecting an item.
 * @prop {Func} nextTab        Callback for transitioning to the next step.
 * @prop {Func} updateQuantity Callback for updating an items quantity.
 */
const ItemSelectComponent = ({ transaction, chooseItem, nextTab, updateQuantity }) => {
  const columns = getColumns('itemSelect');
  const disabledRows = UIDatabase.objects('Item').reduce(
    (acc, value) => ({ ...acc, [value.id]: value.totalQuantity <= 0 }),
    {}
  );

  const selectedRows = transaction.items.reduce(
    (acc, { item }) => ({ ...acc, [item.id]: true }),
    {}
  );
  return (
    <>
      <PrescriptionInfo />
      <FlexRow flex={1}>
        <FlexView flex={10}>
          <SimpleTable
            data={UIDatabase.objects('Item')}
            columns={columns}
            disabledRows={disabledRows}
            selectedRows={selectedRows}
            selectRow={chooseItem}
          />
        </FlexView>
        <FlexColumn flex={15}>
          <PrescriptionCart items={transaction.items.slice()} onChangeQuantity={updateQuantity} />
          <PageButton text="Next" onPress={() => nextTab(1)} style={{ alignSelf: 'flex-end' }} />
        </FlexColumn>
      </FlexRow>
    </>
  );
};

const mapDispatchToProps = dispatch => {
  const choosePrescriber = prescriberID => dispatch(selectPrescriber(prescriberID));
  const chooseItem = itemID => dispatch(selectItem(itemID));
  const nextTab = currentTab => dispatch(switchTab(currentTab + 1));
  const updateQuantity = (id, quantity) => dispatch(editQuantity(id, quantity));
  return { nextTab, choosePrescriber, chooseItem, updateQuantity };
};

const mapStateToProps = state => {
  const { prescription } = state;
  return { ...prescription };
};

ItemSelectComponent.propTypes = {
  transaction: PropTypes.object.isRequired,
  chooseItem: PropTypes.func.isRequired,
  nextTab: PropTypes.func.isRequired,
  updateQuantity: PropTypes.func.isRequired,
};

export const ItemSelect = connect(mapStateToProps, mapDispatchToProps)(ItemSelectComponent);
<|endoftext|>


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

    var DIR         = '../',
        DIR_SERVER  = DIR + 'server/',

        isWin       = process.platform === 'win32',
        Util        = require(DIR           + 'util'),
        commander   = require(DIR_SERVER    + 'commander');

    exports.read            = read;
    exports.convertPath     = convertPath;

    function read(path, callback) {
        if (isWin && path === '/')
            getRoot(callback);
        else
            commander.getDirContent(path, callback);
    }

    function getRoot(callback) {
        var win = require(DIR_SERVER + 'win');

        win.getVolumes(function(error, volumes) {
            var data = {
                path    : '/',
                files   : []
            };

            if (!error)
                data.files = volumes.map(function(volume) {
                    return {
                        name: volume,
                        size: 'dir',
                        mode: 0,
                        owner: 0
                    };
                });

            callback(error, data);
        });
    }

    function convertPath(path) {
        var volume;

        Util.checkArgs(arguments, ['path']);

        if (isWin && path !== '/') {
            volume  = path[1];
            path    = path.split('')
                          .slice(2)
                          .join('');

            path    = volume + ':' + path;
        }

        return path;
    }
})();
<commit_msg>feature(mellow) getRoot: set default mode<commit_after>(function() {
    'use strict';

    var DIR         = '../',
        DIR_SERVER  = DIR + 'server/',

        isWin       = process.platform === 'win32',
        Util        = require(DIR           + 'util'),
        commander   = require(DIR_SERVER    + 'commander');

    exports.read            = read;
    exports.convertPath     = convertPath;

    function read(path, callback) {
        if (isWin && path === '/')
            getRoot(callback);
        else
            commander.getDirContent(path, callback);
    }

    function getRoot(callback) {
        var win = require(DIR_SERVER + 'win');

        win.getVolumes(function(error, volumes) {
            var data = {
                path    : '/',
                files   : []
            };

            if (!error)
                data.files = volumes.map(function(volume) {
                    return {
                        name: volume,
                        size: 'dir',
                        mode: '--- --- ---',
                        owner: 0
                    };
                });

            callback(error, data);
        });
    }

    function convertPath(path) {
        var volume;

        Util.checkArgs(arguments, ['path']);

        if (isWin && path !== '/') {
            volume  = path[1];
            path    = path.split('')
                          .slice(2)
                          .join('');

            path    = volume + ':' + path;
        }

        return path;
    }
})();
<|endoftext|>