<commit_before>import fs from 'fs'
import path from 'path'
import info from '../../package.json'
import { ArgumentParser, RawTextHelpFormatter } from 'argparse'

import Options from '../models/options/Options'

import SwaggerParser from '../parsers/swagger/Parser'
import RAMLParser from '../parsers/raml/Parser'
import PostmanParser from '../parsers/postman/Parser'

import SwaggerSerializer from '../serializers/swagger/Serializer'
import RAMLSerializer from '../serializers/raml/Serializer'
import PostmanSerializer from '../serializers/postman/Serializer'

import ContextResolver from '../resolvers/ContextResolver'
import NodeEnvironment from '../models/environments/NodeEnvironment'

export default class FlowCLI {
    constructor() {}

    _createParser() {
        let parser = new ArgumentParser({
            version: info.version,
            addHelp: true,
            formatterClass: RawTextHelpFormatter,
            description: info.description
        })

        parser.addArgument([ 'source' ], {
            help: 'The source file'
        })

        parser.addArgument([ '-c', '--config' ], {
            metavar: 'path',
            help:
                'The path to a JSON file representing the base options of ' +
                'the command. These values are overriden by config values ' +
                'provided directly as arguments',
            nargs: 1
        })

        parser.addArgument([ '-f', '--from' ], {
            metavar: 'format',
            help:
                'The format of the source file',
            choices: [ 'swagger', 'raml', 'postman-1', 'postman-2' ],
            defaultValue: [ 'swagger' ],
            nargs: 1,
            action: 'store'
        })

        parser.addArgument([ '-t', '--to' ], {
            metavar: 'format',
            help:
                'The format of the destination file',
            choices: [ 'swagger', 'raml', 'postman' ],
            defaultValue: [ 'raml' ],
            nargs: 1,
            action: 'store'
        })

        parser.addArgument([ '-b', '--base' ], {
            metavar: 'location',
            help:
                'The location of the source file. If the location is set to ' +
                'raw, the input source will expect the content instead of a ' +
                'path or url',
            choices: [ 'local', 'remote', 'raw' ],
            defaultValue: [ 'local' ],
            nargs: 1
        })

        parser.addArgument([ '-r', '--ref' ], {
            metavar: [ 'uri', 'solve', 'val' ],
            help:
                'uri:   The uri of the reference\n' +
                'solve: A boolean describing whether to resolve ' +
                'the reference\n' +
                'val:   The value to use for the reference\n' +
                'All 3 parameters are required.',
            nargs: 3,
            action: 'append'
        })

        parser.addArgument([ '-p', '--param' ], {
            metavar: [ 'key', 'val' ],
            help:
                'key: The key of the parameter\n' +
                'val: The value to use for the parameter\n' +
                'Both parameters are required.',
            nargs: 2,
            action: 'append'
        })

        return parser
    }

    processArguments(parser) {
        let args = parser.parseArgs()

        let config = new Options()
        if (args.config) {
            try {
                let baseData = fs.readFileSync(args.config).toString()
                let baseJSON = JSON.parse(baseData)
                config = new Options(baseJSON)
            }
            catch (e) {
                config = new Options()
            }
        }

        if (args.from) {
            config = config.setIn([ 'parser', 'name' ], args.from[0])
        }

        if (args.to) {
            config = config.setIn([ 'serializer', 'name' ], args.to[0])
        }

        if (args.base) {
            config = config.setIn([ 'resolver', 'base' ], args.base[0])
        }

        if (args.ref || args.param) {
            let refs = (args.ref || []).map(ref => {
                return {
                    uri: ref[0],
                    resolve: ref[1],
                    value: ref[2]
                }
            })
            let params = (args.param || []).map(param => {
                return {
                    key: param[0],
                    value: param[1]
                }
            })

            let resolution = config.getIn([ 'resolver', 'resolve' ])
            resolution = resolution.addCustomResolutions(
                refs.concat(params)
            )

            config = config.setIn([ 'resolver', 'resolve' ], resolution)
        }

        this.options = config
        this.input = args.source
    }

    run(_input, _options, _callback) {
        let input = _input || this.input
        let options = _options || this.options

        if (!(options instanceof Options)) {
            options = new Options(options)
        }

        let parserMap = {
            swagger: SwaggerParser,
            raml: RAMLParser,
            'postman-1': PostmanParser,
            'postman-2': () => { return new PostmanParser('v2') }
        }

        let serializerMap = {
            swagger: SwaggerSerializer,
            raml: RAMLSerializer,
            postman: PostmanSerializer
        }

        let callback = _callback
        if (!callback) {
            callback = (data) => {
                /* eslint-disable no-console */
                console.log(data)
                /* eslint-enable no-console */
            }
        }

        let content
        let item
        if (options.getIn([ 'resolver', 'base' ]) === 'local') {
            let _path = path.resolve('./', input)
            content = fs.readFileSync(_path).toString()
            item = {
                file: {
                    name: path.basename(_path),
                    path: path.dirname(_path)
                },
                content: content
            }
        }
        else {
            content = input
            item = {
                file: {
                    name: '',
                    path: path.resolve('.')
                },
                content: content
            }
        }

        let source = options.getIn([ 'parser', 'name' ])
        let target = options.getIn([ 'serializer', 'name' ])

        if (!parserMap[source]) {
            throw new Error('unrecognized source format')
        }

        if (!serializerMap[target]) {
            throw new Error('unrecognized target format')
        }

        let parser = new parserMap[source]()
        let serializer = new serializerMap[target]()
        let environment = new NodeEnvironment()
        let resolver = new ContextResolver(environment)

        let promise = parser.parse(item, options.get('parser'))

        if (typeof promise.then !== 'function') {
            let value = promise
            promise = new Promise((resolve) => {
                resolve(value)
            })
        }

        /* eslint-disable no-console */
        return promise.then(context => {
            resolver.resolveAll(
                parser.item,
                context,
                options.get('resolver')
            ).then(_context => {
                try {
                    let final = serializer
                        .serialize(_context, options.get('serializer'))
                    callback(final)
                    return final
                }
                catch (e) {
                    console.error('got error', e.stack)
                }
            }).catch(error => {
                console.error('got error', error.stack)
            })
        }, error => {
            console.error('got promise error', error.stack)
        }).catch(err => {
            console.error('caught error', err, err.stack)
        })
        /* eslint-enable no-console */
    }
}
<commit_msg>node-flow.run now accepts a callback and returns a promise!<commit_after>import fs from 'fs'
import path from 'path'
import info from '../../package.json'
import { ArgumentParser, RawTextHelpFormatter } from 'argparse'

import Options from '../models/options/Options'

import SwaggerParser from '../parsers/swagger/Parser'
import RAMLParser from '../parsers/raml/Parser'
import PostmanParser from '../parsers/postman/Parser'

import SwaggerSerializer from '../serializers/swagger/Serializer'
import RAMLSerializer from '../serializers/raml/Serializer'
import PostmanSerializer from '../serializers/postman/Serializer'

import ContextResolver from '../resolvers/ContextResolver'
import NodeEnvironment from '../models/environments/NodeEnvironment'

export default class FlowCLI {
    constructor() {}

    _createParser() {
        let parser = new ArgumentParser({
            version: info.version,
            addHelp: true,
            formatterClass: RawTextHelpFormatter,
            description: info.description
        })

        parser.addArgument([ 'source' ], {
            help: 'The source file'
        })

        parser.addArgument([ '-c', '--config' ], {
            metavar: 'path',
            help:
                'The path to a JSON file representing the base options of ' +
                'the command. These values are overriden by config values ' +
                'provided directly as arguments',
            nargs: 1
        })

        parser.addArgument([ '-f', '--from' ], {
            metavar: 'format',
            help:
                'The format of the source file',
            choices: [ 'swagger', 'raml', 'postman-1', 'postman-2' ],
            defaultValue: [ 'swagger' ],
            nargs: 1,
            action: 'store'
        })

        parser.addArgument([ '-t', '--to' ], {
            metavar: 'format',
            help:
                'The format of the destination file',
            choices: [ 'swagger', 'raml', 'postman' ],
            defaultValue: [ 'raml' ],
            nargs: 1,
            action: 'store'
        })

        parser.addArgument([ '-b', '--base' ], {
            metavar: 'location',
            help:
                'The location of the source file. If the location is set to ' +
                'raw, the input source will expect the content instead of a ' +
                'path or url',
            choices: [ 'local', 'remote', 'raw' ],
            defaultValue: [ 'local' ],
            nargs: 1
        })

        parser.addArgument([ '-r', '--ref' ], {
            metavar: [ 'uri', 'solve', 'val' ],
            help:
                'uri:   The uri of the reference\n' +
                'solve: A boolean describing whether to resolve ' +
                'the reference\n' +
                'val:   The value to use for the reference\n' +
                'All 3 parameters are required.',
            nargs: 3,
            action: 'append'
        })

        parser.addArgument([ '-p', '--param' ], {
            metavar: [ 'key', 'val' ],
            help:
                'key: The key of the parameter\n' +
                'val: The value to use for the parameter\n' +
                'Both parameters are required.',
            nargs: 2,
            action: 'append'
        })

        return parser
    }

    processArguments(parser) {
        let args = parser.parseArgs()

        let config = new Options()
        if (args.config) {
            try {
                let baseData = fs.readFileSync(args.config).toString()
                let baseJSON = JSON.parse(baseData)
                config = new Options(baseJSON)
            }
            catch (e) {
                config = new Options()
            }
        }

        if (args.from) {
            config = config.setIn([ 'parser', 'name' ], args.from[0])
        }

        if (args.to) {
            config = config.setIn([ 'serializer', 'name' ], args.to[0])
        }

        if (args.base) {
            config = config.setIn([ 'resolver', 'base' ], args.base[0])
        }

        if (args.ref || args.param) {
            let refs = (args.ref || []).map(ref => {
                return {
                    uri: ref[0],
                    resolve: ref[1],
                    value: ref[2]
                }
            })
            let params = (args.param || []).map(param => {
                return {
                    key: param[0],
                    value: param[1]
                }
            })

            let resolution = config.getIn([ 'resolver', 'resolve' ])
            resolution = resolution.addCustomResolutions(
                refs.concat(params)
            )

            config = config.setIn([ 'resolver', 'resolve' ], resolution)
        }

        this.options = config
        this.input = args.source
    }

    run(_input, _options, _callback) {
        let input = _input || this.input
        let options = _options || this.options

        if (!(options instanceof Options)) {
            options = new Options(options)
        }

        let parserMap = {
            swagger: SwaggerParser,
            raml: RAMLParser,
            'postman-1': PostmanParser,
            'postman-2': () => { return new PostmanParser('v2') }
        }

        let serializerMap = {
            swagger: SwaggerSerializer,
            raml: RAMLSerializer,
            postman: PostmanSerializer
        }

        let callback = _callback
        if (!callback) {
            callback = (data) => {
                /* eslint-disable no-console */
                console.log(data)
                /* eslint-enable no-console */
            }
        }

        let content
        let item
        if (options.getIn([ 'resolver', 'base' ]) === 'local') {
            let _path = path.resolve('./', input)
            content = fs.readFileSync(_path).toString()
            item = {
                file: {
                    name: path.basename(_path),
                    path: path.dirname(_path)
                },
                content: content
            }
        }
        else {
            content = input
            item = {
                file: {
                    name: '',
                    path: path.resolve('.')
                },
                content: content
            }
        }

        let source = options.getIn([ 'parser', 'name' ])
        let target = options.getIn([ 'serializer', 'name' ])

        if (!parserMap[source]) {
            throw new Error('unrecognized source format')
        }

        if (!serializerMap[target]) {
            throw new Error('unrecognized target format')
        }

        let parser = new parserMap[source]()
        let serializer = new serializerMap[target]()
        let environment = new NodeEnvironment()
        let resolver = new ContextResolver(environment)

        let promise = parser.parse(item, options.get('parser'))

        if (typeof promise.then !== 'function') {
            let value = promise
            promise = new Promise((resolve) => {
                resolve(value)
            })
        }

        /* eslint-disable no-console */
        return promise.then(context => {
            return resolver.resolveAll(
                parser.item,
                context,
                options.get('resolver')
            ).then(_context => {
                try {
                    let final = serializer
                        .serialize(_context, options.get('serializer'))
                    callback(final)
                    return final
                }
                catch (e) {
                    console.error('got error', e.stack)
                }
            }).catch(error => {
                console.error('got error', error.stack)
            })
        }, error => {
            console.error('got promise error', error.stack)
        }).catch(err => {
            console.error('caught error', err, err.stack)
        })
        /* eslint-enable no-console */
    }
}
<|endoftext|>


<commit_before>import React, { Component } from 'react';
import CSSModules from 'react-css-modules'
import { observer, inject } from 'mobx-react'
import { observable } from 'mobx'
import { Link, browserHistory } from 'react-router'
import 'react-s-alert/dist/s-alert-default.css'
import 'react-s-alert/dist/s-alert-css-effects/flip.css'
import ls from 'local-storage'
import { checkIfInstalled } from 'runtime'
import Loader from './shared/Loader'
import styles from './index.scss'

@inject('currentUser')
@observer
@CSSModules(styles)
export default class App extends Component {
  @observable isInstalled = false
  @observable isFetching = true

  constructor(props) {
    super(props)

    this.checkExtension()
  }

  @autobind
  fetchMeta(cb = n => n) {
    this.isFetching = true
    this.props.currentUser.fetchMeta().then((resp) => {
      if (resp.rating.super_likes.resets_at) {
        ls.set({ superLikeExpiration: resp.rating.super_likes.resets_at })
      }

      if (resp.rating.likes_remaining === 0) {
        ls.set({ likeExpiration: resp.rating.rate_limited_until })
      }

      cb()
      this.isFetching = false
    }).catch(() => {
      // browserHistory.push('/welcome')
      this.isFetching = false
    })
  }

  checkExtension() {
    checkIfInstalled((status) => {
      this.isInstalled = !!status

      if (status) {
        this.fetchMeta()
      } else {
        this.isFetching = false
        browserHistory.push('/welcome')
      }
    })
  }

  render() {
    return (

          {!this.isFetching && this.props.children && React.cloneElement(this.props.children, {
            currentUser: this.props.currentUser,
            isInstalled: this.isInstalled,
            fetchMeta: this.fetchMeta,
          })}
          {this.isFetching && <Loader isSimpleLoader />}

          Copyright © Rafal Lesniak | <Link to="/privacy-policy">Privacy Policy</Link>

    );
  }
}
<commit_msg>Add source link<commit_after>import React, { Component } from 'react';
import CSSModules from 'react-css-modules'
import { observer, inject } from 'mobx-react'
import { observable } from 'mobx'
import { Link, browserHistory } from 'react-router'
import 'react-s-alert/dist/s-alert-default.css'
import 'react-s-alert/dist/s-alert-css-effects/flip.css'
import ls from 'local-storage'
import { checkIfInstalled } from 'runtime'
import Loader from './shared/Loader'
import styles from './index.scss'

@inject('currentUser')
@observer
@CSSModules(styles)
export default class App extends Component {
  @observable isInstalled = false
  @observable isFetching = true

  constructor(props) {
    super(props)

    this.checkExtension()
  }

  @autobind
  fetchMeta(cb = n => n) {
    this.isFetching = true
    this.props.currentUser.fetchMeta().then((resp) => {
      if (resp.rating.super_likes.resets_at) {
        ls.set({ superLikeExpiration: resp.rating.super_likes.resets_at })
      }

      if (resp.rating.likes_remaining === 0) {
        ls.set({ likeExpiration: resp.rating.rate_limited_until })
      }

      cb()
      this.isFetching = false
    }).catch(() => {
      // browserHistory.push('/welcome')
      this.isFetching = false
    })
  }

  checkExtension() {
    checkIfInstalled((status) => {
      this.isInstalled = !!status

      if (status) {
        this.fetchMeta()
      } else {
        this.isFetching = false
        browserHistory.push('/welcome')
      }
    })
  }

  render() {
    return (

          {!this.isFetching && this.props.children && React.cloneElement(this.props.children, {
            currentUser: this.props.currentUser,
            isInstalled: this.isInstalled,
            fetchMeta: this.fetchMeta,
          })}
          {this.isFetching && <Loader isSimpleLoader />}

          Copyright © Rafal Lesniak | <Link to="/privacy-policy">Privacy Policy</Link> | Source

    );
  }
}
<|endoftext|>


<commit_before>'use strict'
import {LEGO_COLORS} from './common/legoColors.js';
import {BASE_LEGO_COLOR} from './common/const.js';
import {FireBaseLegoApp} from './firebase/firebase.js';
import {FireBaseAuth} from './firebase/firebaseAuth.js';
import {LegoGridCanvas} from './canvas/legoCanvas.js';

(function () {

    let gameInit = false,// true if we init the legoGrid
        fireBaseLego = null, // the reference of the fireBaseApp
        legoCanvas = null, // The legoGrid
        keys = null, // The keys of firenase submit draw
        snapshotFb = null, // The snapshot of submit draw
        index = 0;

    function initGame() {

        legoCanvas = new LegoGridCanvas('canvasDraw', true);

        $("#color-picker2").spectrum({
            showPaletteOnly: true,
            showPalette: true,
            color: BASE_LEGO_COLOR,
            palette: LEGO_COLORS,
            change: function (color) {
                legoCanvas.changeColor(color.toHexString());
            }
        });
    }

    function pageLoad() {

        fireBaseLego = new FireBaseLegoApp().app;
        // We init the authentication object
        let fireBaseAuth = new FireBaseAuth({
            idDivLogin: 'login-msg',
            idNextDiv: 'hello-msg',
            idLogout: 'signout',
            idImg: "img-user",
            idDisplayName: "name-user"
        });

        /**
         * Management of Cinematic Buttons
         */
        const startBtn = document.getElementById('startBtn');
        const helpBtn = document.getElementById('help')

        const streamStart = Rx.Observable
            .fromEvent(startBtn, 'click')
            .map(() => 'start');

        const streamHelp = Rx.Observable
            .fromEvent(helpBtn, 'click')
            .map(() => 'help');

        streamStart.merge(streamHelp)
            .subscribe((state) => {
                if (state === 'start') {
                    document.getElementById('hello-msg').setAttribute("hidden", "");
                    document.getElementById('game').removeAttribute('hidden');
                    document.getElementById('color-picker2').removeAttribute('hidden');
                    document.getElementById('help').removeAttribute('hidden');
                    if (!gameInit) {
                        document.getElementById('loading').removeAttribute('hidden');
                        // Timeout needed to start the rendering of loading animation (else will not be show)
                        setTimeout(function () {
                                gameInit = true;
                                initGame();
                            document.getElementById('loading').setAttribute('hidden', '')
                        }, 50);
                    }
                } else if (state === 'help') {
                    document.getElementById('hello-msg').removeAttribute("hidden");
                    document.getElementById('game').setAttribute('hidden', "");
                    document.getElementById('color-picker2').setAttribute('hidden', "");
                    document.getElementById('help').setAttribute('hidden', "");
                }
            })

        /**
         * Management of submission
         */

        document.getElementById('btnSubmission').addEventListener('click', () => {
            // When we submit a draw, we save it on firebase tree
            fireBaseLego.database().ref("/draw").push(legoCanvas.export(fireBaseAuth.displayName(), fireBaseAuth.userId()));
            legoCanvas.resetBoard();
        });

        /**
         * Management of menu items
         */

        const menuGame = document.getElementById('menu-game');
        const menuCreations = document.getElementById('menu-creations');

        const streamGame = Rx.Observable
            .fromEvent(menuGame, 'click')
            .map(() => 'game');

        const streamCreations = Rx.Observable
            .fromEvent(menuCreations, 'click')
            .map(() => 'creations');

        streamGame.merge(streamCreations)
            .subscribe((state) => {
                if (state === 'game'){
                    document.querySelector('.page-content').removeAttribute('hidden');
                    document.getElementById('submitted').setAttribute('hidden', '');
                    document.getElementById('menu-game').setAttribute('hidden', '');
                    document.getElementById('menu-creations').removeAttribute('hidden');
                    document.querySelector('.mdl-layout__drawer').classList.remove('is-visible');
                    document.querySelector('.mdl-layout__obfuscator').classList.remove('is-visible');

                }else if (state === 'creations'){
                    document.querySelector('.page-content').setAttribute('hidden', '');
                    document.getElementById('submitted').removeAttribute('hidden');
                    document.getElementById('menu-game').removeAttribute('hidden');
                    document.getElementById('menu-creations').setAttribute('hidden', '');
                    document.querySelector('.mdl-layout__drawer').classList.remove('is-visible');
                    document.querySelector('.mdl-layout__obfuscator').classList.remove('is-visible');

                    fireBaseLego.database().ref(`drawSaved/${fireBaseAuth.userId()}`).once('value', function (snapshot) {
                        if (snapshot && snapshot.val()) {
                            console.log(snapshot.val());
                            snapshotFb = snapshot.val();
                            keys = Object.keys(snapshotFb);
                            index = 0;
                            draw();
                        } else {
                            console.log('no draw !');
                        }

                    }, function (err) {
                        console.error(err);
                        // error callback triggered with PERMISSION_DENIED
                    });

                }
            });

        /**
         * Management of Buttons for changing of draw
         */

        const btnLeft = document.getElementById('btnLeft');
        const btnRight = document.getElementById('btnRight');

        const streamBtnLeft = Rx.Observable
            .fromEvent(btnLeft,'click',()=>index = Math.max(index - 1, 0));
        const streamBtnRight =  Rx.Observable
            .fromEvent(btnRight, 'click',()=>index = Math.min(index + 1, keys.length - 1));

       streamBtnLeft.merge(streamBtnRight).subscribe(draw);

    }

    /**
     * Show a draw and show it's state : Rejected or Accepted
     */
    function draw() {
        let draw = snapshotFb[keys[index]];
        let imgSubmission = document.getElementById('imgSubmission');
        let parentImg = imgSubmission.parentElement;
        imgSubmission.src = draw.dataUrl;
        if (draw.accepted && !parentImg.classList.contains('accepted')) {
            parentImg.classList.add('accepted');
        } else {
            parentImg.classList.remove('accepted');
        }

    }

    window.addEventListener('load', pageLoad);

    /* SERVICE_WORKER_REPLACE
    if ('serviceWorker' in navigator) {
        navigator.serviceWorker.register('./service-worker-phone.js', {scope : location.pathname}).then(function(reg) {
            console.log('Service Worker Register for scope : %s',reg.scope);
        });
    }
    SERVICE_WORKER_REPLACE */

})();
<commit_msg><commit_after>'use strict'
import {LEGO_COLORS} from './common/legoColors.js';
import {BASE_LEGO_COLOR} from './common/const.js';
import {FireBaseLegoApp} from './firebase/firebase.js';
import {FireBaseAuth} from './firebase/firebaseAuth.js';
import {LegoGridCanvas} from './canvas/legoCanvas.js';

(function () {

    let gameInit = false,// true if we init the legoGrid
        fireBaseLego = null, // the reference of the fireBaseApp
        legoCanvas = null, // The legoGrid
        keys = null, // The keys of firenase submit draw
        snapshotFb = null, // The snapshot of submit draw
        index = 0;

    function initGame() {

        legoCanvas = new LegoGridCanvas('canvasDraw', true);

        $("#color-picker2").spectrum({
            showPaletteOnly: true,
            showPalette: true,
            color: BASE_LEGO_COLOR,
            palette: LEGO_COLORS,
            change: function (color) {
                legoCanvas.changeColor(color.toHexString());
            }
        });
    }

    function pageLoad() {

        fireBaseLego = new FireBaseLegoApp().app;
        // We init the authentication object
        let fireBaseAuth = new FireBaseAuth({
            idDivLogin: 'login-msg',
            idNextDiv: 'hello-msg',
            idLogout: 'signout',
            idImg: "img-user",
            idDisplayName: "name-user"
        });

        /**
         * Management of Cinematic Buttons
         */
        const startBtn = document.getElementById('startBtn');
        const helpBtn = document.getElementById('help')

        const streamStart = Rx.Observable
            .fromEvent(startBtn, 'click')
            .map(() => 'start');

        const streamHelp = Rx.Observable
            .fromEvent(helpBtn, 'click')
            .map(() => 'help');

        streamStart.merge(streamHelp)
            .subscribe((state) => {
                if (state === 'start') {
                    document.getElementById('hello-msg').setAttribute("hidden", "");
                    document.getElementById('game').removeAttribute('hidden');
                    document.getElementById('color-picker2').removeAttribute('hidden');
                    document.getElementById('help').removeAttribute('hidden');
                    if (!gameInit) {
                        document.getElementById('loading').removeAttribute('hidden');
                        // Timeout needed to start the rendering of loading animation (else will not be show)
                        setTimeout(function () {
                                gameInit = true;
                                initGame();
                            document.getElementById('loading').setAttribute('hidden', '')
                        }, 50);
                    }
                } else if (state === 'help') {
                    document.getElementById('hello-msg').removeAttribute("hidden");
                    document.getElementById('game').setAttribute('hidden', "");
                    document.getElementById('color-picker2').setAttribute('hidden', "");
                    document.getElementById('help').setAttribute('hidden', "");
                }
            })

        /**
         * Management of submission
         */

        document.getElementById('btnSubmission').addEventListener('click', () => {
            // When we submit a draw, we save it on firebase tree
            fireBaseLego.database().ref("/draw").push(legoCanvas.export(fireBaseAuth.displayName(), fireBaseAuth.userId()));
            legoCanvas.resetBoard();
        });

        /**
         * Management of menu items
         */

        const menuGame = document.getElementById('menu-game');
        const menuCreations = document.getElementById('menu-creations');

        const streamGame = Rx.Observable
            .fromEvent(menuGame, 'click')
            .map(() => 'game');

        const streamCreations = Rx.Observable
            .fromEvent(menuCreations, 'click')
            .map(() => 'creations');

        streamGame.merge(streamCreations)
            .subscribe((state) => {
                if (state === 'game'){
                    document.querySelector('.page-content').removeAttribute('hidden');
                    document.getElementById('submitted').setAttribute('hidden', '');
                    document.getElementById('menu-game').setAttribute('hidden', '');
                    document.getElementById('menu-creations').removeAttribute('hidden');
                    document.querySelector('.mdl-layout__drawer').classList.remove('is-visible');
                    document.querySelector('.mdl-layout__obfuscator').classList.remove('is-visible');

                }else if (state === 'creations'){
                    document.querySelector('.page-content').setAttribute('hidden', '');
                    document.getElementById('submitted').removeAttribute('hidden');
                    document.getElementById('menu-game').removeAttribute('hidden');
                    document.getElementById('menu-creations').setAttribute('hidden', '');
                    document.querySelector('.mdl-layout__drawer').classList.remove('is-visible');
                    document.querySelector('.mdl-layout__obfuscator').classList.remove('is-visible');

                    fireBaseLego.database().ref(`drawSaved/${fireBaseAuth.userId()}`).once('value', function (snapshot) {
                        if (snapshot && snapshot.val()) {
                            console.log(snapshot.val());
                            snapshotFb = snapshot.val();
                            keys = Object.keys(snapshotFb);
                            index = 0;
                            draw();
                        } else {
                            console.log('no draw !');
                        }

                    }, function (err) {
                        console.error(err);
                        // error callback triggered with PERMISSION_DENIED
                    });

                }
            });

        /**
         * Management of Buttons for changing of draw
         */

        const btnLeft = document.getElementById('btnLeft');
        const btnRight = document.getElementById('btnRight');

        const streamBtnLeft = Rx.Observable
            .fromEvent(btnLeft,'click',()=>index = Math.max(index - 1, 0));
        const streamBtnRight =  Rx.Observable
            .fromEvent(btnRight, 'click',()=>index = Math.min(index + 1, keys.length - 1));

       streamBtnLeft.merge(streamBtnRight).subscribe(draw);

    }

    /**
     * Show a draw and show it's state : Rejected or Accepted
     */
    function draw() {
        let draw = snapshotFb[keys[index]];
        let imgSubmission = document.getElementById('imgSubmission');
        let parentImg = imgSubmission.parentElement;
        imgSubmission.src = draw.dataUrl;
        if (draw.accepted && !parentImg.classList.contains('accepted')) {
            parentImg.classList.add('accepted');
        } else if (!draw.accepted){
            parentImg.classList.remove('accepted');
        }

    }

    window.addEventListener('load', pageLoad);

    /* SERVICE_WORKER_REPLACE
    if ('serviceWorker' in navigator) {
        navigator.serviceWorker.register('./service-worker-phone.js', {scope : location.pathname}).then(function(reg) {
            console.log('Service Worker Register for scope : %s',reg.scope);
        });
    }
    SERVICE_WORKER_REPLACE */

})();
<|endoftext|>


<commit_before>import User   from '../models/user';
import Board  from '../models/board';
import Ticket from '../models/ticket';

import request from '../utils/request';
import page    from 'page';

/**
 * NOTE We use a lot of Model.fromJS.toJS conversion here, this is so that we
 *      can be sure the data we resolve to is formatted properly. This is not
 *      necessarily the most clean way, but it will do for now.
 */
export default {
	login:           login,
	loginGuest:      loginGuest,
	logout:          logout,
	register:        register,
	giveBoardAccess: giveBoardAccess,
	getUser:         getUser,
	getBoard:        getBoard,
	getBoards:       getBoards,
	getTicket:       getTicket,
	getTickets:      getTickets,

	createBoard:   createBoard,
	createTicket:  createTicket,
	createComment: createComment,
	updateBoard:   updateBoard,
	updateTicket:  updateTicket,
	deleteBoard:   deleteBoard,
	deleteTicket:  deleteTicket,

	revokeAccessCode:     revokeAccessCode,
	generateAccessCode:   generateAccessCode,
	setUserBoardActivity: setUserBoardActivity,

    queryApiVersion:    queryApiVersion,
    queryImgVersion:    queryImgVersion

}

const API_URL = process.env.API_URL || 'http://localhost:9002/api';

/*
function login(opts = {}) {
	let provider = 'basic'; // add dynamic provider
	let auth = "basic " + new Buffer(opts.payload.email + ":" + opts.payload.password).toString("base64");
	let options = {
		url:  `${API_URL}/auth/`+ provider + `/login`,
		auth: auth
	}

	return request.get(options).then((res) => {
		return {
			user:  User.fromJS(res.body).toJS(),
			token: res.headers['x-access-token']
		}
	});
*/
function login(opts = {}) {
    let options = {
        url:     `${API_URL}/auth/login`,
        payload: opts.payload
    }
    return request.post(options).then((res) => {
        return {
            user:  User.fromJS(res.body).toJS(),
            token: res.headers['x-access-token']
        }
    });
}

function logout(opts = {}) {
    let options = {
        url:   `${API_URL}/auth/logout`,
        token: opts.token
    }
    return request.post(options);
}

function register(opts = {}) {
    let options = {
        url:     `${API_URL}/auth/register`,
        payload: opts.payload
    }
    return request.post(options).then((res) => {
        return {
            user: User.fromJS(res.body).toJS()
        }
    });
}

function loginGuest(opts = {}) {
    let options = {
        url:     `${API_URL}/boards/${opts.id.board}/access/${opts.id.code}`,
        payload: opts.payload
    }
    return request.post(options).then((res) => {
        return {
            user:  User.fromJS(res.body).toJS(),
            token: res.headers['x-access-token']
        }
    });
}

function giveBoardAccess(opts = {}) {
	let options = {
		url:     `${API_URL}/boards/${opts.id.board}/access/${opts.id.code}/grantaccess`,
		token: opts.token
	}
	return request.put(options).then((res) => {
		return Board.fromJS(res.body).toJS();
	});
}

function getUser(opts = {}) {
    let options = {
        url:   `${API_URL}/auth`,
        token: opts.token
    }
    return request.get(options).then((res) => {
        return User.fromJS(res.body).toJS();
    });
}

function getBoard(opts = {}) {
    let options = {
        url:   `${API_URL}/boards/${opts.id.board}`,
        token: opts.token
    }
    return request.get(options).then((res) => {
        let board = Board.fromJS(res.body).toJS();
        // Remove the empty 'tickets' collection to prevent overwriting.
        delete board.tickets;
        return board;
    });
}

function getBoards(opts = {}) {
    let options = {
        url:   `${API_URL}/boards`,
        token: opts.token
    }
    return request.get(options).then((res) => {
        return res.body.map((board) => {
            board = Board.fromJS(board).toJS();
            // Remove the empty 'tickets' collection to prevent overwriting.
            delete board.tickets;
            return board;
        });
    });
}

function getTicket(opts = {}) {
    let options = {
        url:   `${API_URL}/boards/${opts.id.board}/tickets/${opts.id.ticket}`,
        token: opts.token
    }
    return request.get(options).then((res) => {
        return Ticket.fromJS(res.body).toJS();
    });
}

function getTickets(opts = {}) {
    let options = {
        url:   `${API_URL}/boards/${opts.id.board}/tickets`,
        token: opts.token
    }
    return request.get(options).then((res) => {
        return res.body.map((ticket) => {
            return Ticket.fromJS(ticket).toJS();
        });
    });
}

function createBoard(opts = {}) {
    let options = {
        url:     `${API_URL}/boards`,
        token:   opts.token,
        payload: opts.payload
    }
    return request.post(options).then((res) => {
        return Board.fromJS(res.body).toJS();
    });
}

function createTicket(opts = {}) {
    let options = {
        url:     `${API_URL}/boards/${opts.id.board}/tickets`,
        token:   opts.token,
        payload: opts.payload
    }
    return request.post(options).then((res) => {
        return Ticket.fromJS(res.body).toJS();
    });
}

function updateUserName(opts = {}) {
    let options = {
        url:     `${API_URL}/user/edit`,
        token:   opts.token,
        payload: opts.payload
    }
    return request.put(options).then((res) => {
        return User.fromJS(res.body).toJS();
    });
}

function updateUserPassword(opts = {}) {
    let options = {
        url:     `${API_URL}/user/changepw`,
        token:   opts.token,
        payload: opts.payload
    }
    return request.put(options).then((res) => {
        return User.fromJS(res.body).toJS();
    });
}

function createComment(opts = {}) {
	let options = {
		url:     `${API_URL}/boards/${opts.id.board}/tickets/${opts.id.ticket}/comments`,
		token:   opts.token,
		payload: opts.payload
	}

	return request.post(options).then((res) => {
		return Ticket.fromJS(res.body).toJS();
	});
}

function updateBoard(opts = {}) {
    let options = {
        url:     `${API_URL}/boards/${opts.id.board}`,
        token:   opts.token,
        payload: opts.payload
    }
    return request.put(options).then((res) => {
        let board = Board.fromJS(res.body).toJS();
        // Remove the empty 'tickets' collection to prevent overwriting.
        delete board.tickets;
        return board;
    });
}

function updateTicket(opts = {}) {
    let options = {
        url:     `${API_URL}/boards/${opts.id.board}/tickets/${opts.id.ticket}`,
        token:   opts.token,
        payload: opts.payload
    }
    return request.put(options).then((res) => {
        return Ticket.fromJS(res.body).toJS();
    });
}

function deleteBoard(opts = {}) {
    let options = {
        url:   `${API_URL}/boards/${opts.id.board}`,
        token: opts.token
    }
    return request.del(options).then((res) => {
        let board = Board.fromJS(res.body).toJS();
        // Remove the empty 'tickets' collection to prevent overwriting.
        delete board.tickets;
        return board;
    });
}

function deleteTicket(opts = {}) {
    let options = {
        url:   `${API_URL}/boards/${opts.id.board}/tickets/${opts.id.ticket}`,
        token: opts.token
    }
    return request.del(options).then((res) => {
        return Ticket.fromJS(res.body).toJS();
    });
}

function setUserBoardActivity(opts = {}) {
	let options = {
		url:   `${API_URL}/boards/${opts.id.board}/setactivity`,
		token: opts.token,
		payload: opts.payload
	}
	return request.post(options).then((res) => {
		let board = Board.fromJS(res.body).toJS();
		// Remove the empty 'tickets' collection to prevent overwriting.
		delete board.tickets;
		return board;
	});
}

function generateAccessCode(opts = {}) {
    let options = {
        url:   `${API_URL}/boards/${opts.id.board}/access`,
        token: opts.token
    }
    return request.post(options).then((res) => {
        let board = Board.fromJS(res.body).toJS();
        // Remove the empty 'tickets' collection to prevent overwriting.
        delete board.tickets;
        return board;
    });
}

function revokeAccessCode(opts = {}) {
    let options = {
        url:   `${API_URL}/boards/${opts.id.board}/access`,
        token: opts.token
    }
    return request.del(options);
}

function queryApiVersion(opts = {}) {
	let options = {
		url:   `${API_URL}/version/api`,
		token: opts.token
	}
	return request.get(options).then((res) => {
		return res.body;
	});
}

function queryImgVersion(opts = {}) {
	let options = {
		url:   `${API_URL}/version/img`,
		token: opts.token
	}
	return request.get(options).then((res) => {
		return res.body;
	});
}
<commit_msg>Add correct api login function<commit_after>import User   from '../models/user';
import Board  from '../models/board';
import Ticket from '../models/ticket';

import request from '../utils/request';
import page    from 'page';

/**
 * NOTE We use a lot of Model.fromJS.toJS conversion here, this is so that we
 *      can be sure the data we resolve to is formatted properly. This is not
 *      necessarily the most clean way, but it will do for now.
 */
export default {
	login:           login,
	loginGuest:      loginGuest,
	logout:          logout,
	register:        register,
	giveBoardAccess: giveBoardAccess,
	getUser:         getUser,
	getBoard:        getBoard,
	getBoards:       getBoards,
	getTicket:       getTicket,
	getTickets:      getTickets,

	createBoard:   createBoard,
	createTicket:  createTicket,
	createComment: createComment,
	updateBoard:   updateBoard,
	updateTicket:  updateTicket,
	deleteBoard:   deleteBoard,
	deleteTicket:  deleteTicket,

	revokeAccessCode:     revokeAccessCode,
	generateAccessCode:   generateAccessCode,
	setUserBoardActivity: setUserBoardActivity,

    queryApiVersion:    queryApiVersion,
    queryImgVersion:    queryImgVersion

}

const API_URL = process.env.API_URL || 'http://localhost:9002/api';

function login(opts = {}) {
    let provider = 'basic'; // add dynamic provider
    let auth = "basic " + new Buffer(opts.payload.email + ":" + opts.payload.password).toString("base64");
    let options = {
        url: `${API_URL}/auth/` + provider + `/login`,
        auth: auth
    }

    return request.get(options).then((res) => {
        return {
            user: User.fromJS(res.body).toJS(),
            token: res.headers['x-access-token']
        }
    });
}
/*
function login(opts = {}) {
    let options = {
        url:     `${API_URL}/auth/login`,
        payload: opts.payload
    }
    return request.post(options).then((res) => {
        return {
            user:  User.fromJS(res.body).toJS(),
            token: res.headers['x-access-token']
        }
    });
}*/

function logout(opts = {}) {
    let options = {
        url:   `${API_URL}/auth/logout`,
        token: opts.token
    }
    return request.post(options);
}

function register(opts = {}) {
    let options = {
        url:     `${API_URL}/auth/register`,
        payload: opts.payload
    }
    return request.post(options).then((res) => {
        return {
            user: User.fromJS(res.body).toJS()
        }
    });
}

function loginGuest(opts = {}) {
    let options = {
        url:     `${API_URL}/boards/${opts.id.board}/access/${opts.id.code}`,
        payload: opts.payload
    }
    return request.post(options).then((res) => {
        return {
            user:  User.fromJS(res.body).toJS(),
            token: res.headers['x-access-token']
        }
    });
}

function giveBoardAccess(opts = {}) {
	let options = {
		url:     `${API_URL}/boards/${opts.id.board}/access/${opts.id.code}/grantaccess`,
		token: opts.token
	}
	return request.put(options).then((res) => {
		return Board.fromJS(res.body).toJS();
	});
}

function getUser(opts = {}) {
    let options = {
        url:   `${API_URL}/auth`,
        token: opts.token
    }
    return request.get(options).then((res) => {
        return User.fromJS(res.body).toJS();
    });
}

function getBoard(opts = {}) {
    let options = {
        url:   `${API_URL}/boards/${opts.id.board}`,
        token: opts.token
    }
    return request.get(options).then((res) => {
        let board = Board.fromJS(res.body).toJS();
        // Remove the empty 'tickets' collection to prevent overwriting.
        delete board.tickets;
        return board;
    });
}

function getBoards(opts = {}) {
    let options = {
        url:   `${API_URL}/boards`,
        token: opts.token
    }
    return request.get(options).then((res) => {
        return res.body.map((board) => {
            board = Board.fromJS(board).toJS();
            // Remove the empty 'tickets' collection to prevent overwriting.
            delete board.tickets;
            return board;
        });
    });
}

function getTicket(opts = {}) {
    let options = {
        url:   `${API_URL}/boards/${opts.id.board}/tickets/${opts.id.ticket}`,
        token: opts.token
    }
    return request.get(options).then((res) => {
        return Ticket.fromJS(res.body).toJS();
    });
}

function getTickets(opts = {}) {
    let options = {
        url:   `${API_URL}/boards/${opts.id.board}/tickets`,
        token: opts.token
    }
    return request.get(options).then((res) => {
        return res.body.map((ticket) => {
            return Ticket.fromJS(ticket).toJS();
        });
    });
}

function createBoard(opts = {}) {
    let options = {
        url:     `${API_URL}/boards`,
        token:   opts.token,
        payload: opts.payload
    }
    return request.post(options).then((res) => {
        return Board.fromJS(res.body).toJS();
    });
}

function createTicket(opts = {}) {
    let options = {
        url:     `${API_URL}/boards/${opts.id.board}/tickets`,
        token:   opts.token,
        payload: opts.payload
    }
    return request.post(options).then((res) => {
        return Ticket.fromJS(res.body).toJS();
    });
}

function updateUserName(opts = {}) {
    let options = {
        url:     `${API_URL}/user/edit`,
        token:   opts.token,
        payload: opts.payload
    }
    return request.put(options).then((res) => {
        return User.fromJS(res.body).toJS();
    });
}

function updateUserPassword(opts = {}) {
    let options = {
        url:     `${API_URL}/user/changepw`,
        token:   opts.token,
        payload: opts.payload
    }
    return request.put(options).then((res) => {
        return User.fromJS(res.body).toJS();
    });
}

function createComment(opts = {}) {
	let options = {
		url:     `${API_URL}/boards/${opts.id.board}/tickets/${opts.id.ticket}/comments`,
		token:   opts.token,
		payload: opts.payload
	}

	return request.post(options).then((res) => {
		return Ticket.fromJS(res.body).toJS();
	});
}

function updateBoard(opts = {}) {
    let options = {
        url:     `${API_URL}/boards/${opts.id.board}`,
        token:   opts.token,
        payload: opts.payload
    }
    return request.put(options).then((res) => {
        let board = Board.fromJS(res.body).toJS();
        // Remove the empty 'tickets' collection to prevent overwriting.
        delete board.tickets;
        return board;
    });
}

function updateTicket(opts = {}) {
    let options = {
        url:     `${API_URL}/boards/${opts.id.board}/tickets/${opts.id.ticket}`,
        token:   opts.token,
        payload: opts.payload
    }
    return request.put(options).then((res) => {
        return Ticket.fromJS(res.body).toJS();
    });
}

function deleteBoard(opts = {}) {
    let options = {
        url:   `${API_URL}/boards/${opts.id.board}`,
        token: opts.token
    }
    return request.del(options).then((res) => {
        let board = Board.fromJS(res.body).toJS();
        // Remove the empty 'tickets' collection to prevent overwriting.
        delete board.tickets;
        return board;
    });
}

function deleteTicket(opts = {}) {
    let options = {
        url:   `${API_URL}/boards/${opts.id.board}/tickets/${opts.id.ticket}`,
        token: opts.token
    }
    return request.del(options).then((res) => {
        return Ticket.fromJS(res.body).toJS();
    });
}

function setUserBoardActivity(opts = {}) {
	let options = {
		url:   `${API_URL}/boards/${opts.id.board}/setactivity`,
		token: opts.token,
		payload: opts.payload
	}
	return request.post(options).then((res) => {
		let board = Board.fromJS(res.body).toJS();
		// Remove the empty 'tickets' collection to prevent overwriting.
		delete board.tickets;
		return board;
	});
}

function generateAccessCode(opts = {}) {
    let options = {
        url:   `${API_URL}/boards/${opts.id.board}/access`,
        token: opts.token
    }
    return request.post(options).then((res) => {
        let board = Board.fromJS(res.body).toJS();
        // Remove the empty 'tickets' collection to prevent overwriting.
        delete board.tickets;
        return board;
    });
}

function revokeAccessCode(opts = {}) {
    let options = {
        url:   `${API_URL}/boards/${opts.id.board}/access`,
        token: opts.token
    }
    return request.del(options);
}

function queryApiVersion(opts = {}) {
	let options = {
		url:   `${API_URL}/version/api`,
		token: opts.token
	}
	return request.get(options).then((res) => {
		return res.body;
	});
}

function queryImgVersion(opts = {}) {
	let options = {
		url:   `${API_URL}/version/img`,
		token: opts.token
	}
	return request.get(options).then((res) => {
		return res.body;
	});
}
<|endoftext|>


<commit_before>angular.module('oi.multiselect')

.directive('oiMultiselect', ['$document', '$q', '$timeout', '$parse', '$interpolate', '$injector', '$filter', 'oiUtils', 'oiMultiselect', function($document, $q, $timeout, $parse, $interpolate, $injector, $filter, oiUtils, oiMultiselect) {
    var NG_OPTIONS_REGEXP = /^\s*([\s\S]+?)(?:\s+as\s+([\s\S]+?))?(?:\s+group\s+by\s+([\s\S]+?))?\s+for\s+(?:([\$\w][\$\w]*)|(?:\(\s*([\$\w][\$\w]*)\s*,\s*([\$\w][\$\w]*)\s*\)))\s+in\s+([\s\S]+?)(?:\s+track\s+by\s+([\s\S]+?))?$/,
        VALUES_REGEXP     = /([^\(\)\s\|\s]*)\s*(\(.*\))?\s*(\|?\s*.+)?/;

    return {
        restrict: 'AE',
        templateUrl: 'template/multiselect/template.html',
        require: 'ngModel',
        scope: {},
        compile: function (element, attrs) {
            var optionsExp = attrs.oiOptions,
                match;

            if (!(match = optionsExp.match(NG_OPTIONS_REGEXP))) {
                throw new Error("Expected expression in form of '_select_ (as _label_)? for (_key_,)?_value_ in _collection_'");
            }

            var selectAsName         = / as /.test(match[0]) && match[1],    //item.modelValue
                displayName          = match[2] || match[1],                 //item.label
                valueName            = match[4] || match[6],                 //item
                groupByName          = match[3] || '',                       //item.groupName
                trackByName          = match[8] || displayName,              //item.id
                valueMatches         = match[7].match(VALUES_REGEXP);        //collection

            var valuesName           = valueMatches[1],                      //collection
                filteredValuesName   = valuesName + (valueMatches[3] || ''), //collection | filter
                valuesFnName         = valuesName + (valueMatches[2] || ''); //collection()

            var selectAsFn           = selectAsName && $parse(selectAsName),
                displayFn            = $parse(displayName),
                groupByFn            = $parse(groupByName),
                filteredValuesFn     = $parse(filteredValuesName),
                valuesFn             = $parse(valuesFnName),
                trackByFn            = $parse(trackByName);

            var multiple             = angular.isDefined(attrs.multiple),
                multipleLimit        = Number(attrs.multipleLimit),
                placeholderFn        = $interpolate(attrs.placeholder || ''),
                keyUpDownWerePressed = false,
                matchesWereReset     = false,
                optionsFn            = $parse(attrs.oiMultiselectOptions);

            var timeoutPromise,
                lastQuery;

            return function(scope, element, attrs, ctrl) {
                var inputElement = element.find('input'),
                    listElement  = angular.element(element[0].querySelector('.multiselect-dropdown')),
                    placeholder  = placeholderFn(scope),
                    options      = angular.extend({}, oiMultiselect.options, optionsFn(scope.$parent)),
                    lastQueryFn  = options.saveLastQuery ? $injector.get(options.saveLastQuery) : function() {return ''};

                options.newItemModelFn = function (query) {
                    return (optionsFn({$query: query}) || {}).newItemModel || query;
                };

                if (angular.isDefined(attrs.autofocus)) {
                    $timeout(function() {
                        inputElement[0].focus();
                    });
                }

                if (angular.isDefined(attrs.readonly)) {
                    inputElement.attr('readonly', true)
                }

                attrs.$observe('disabled', function(value) {
                    inputElement.prop('disabled', value);
                });

                scope.$parent.$watch(attrs.ngModel, function(value) {
                    adjustInput();

                    var output = value instanceof Array ? value : value ? [value]: [],
                        promise = $q.when(output);

                    if (selectAsFn && value) {
                        promise = getMatches(null, value)
                            .then(function(collection) {
                                return oiUtils.intersection(collection, output, oiUtils.isEqual, selectAs);
                            });
                        timeoutPromise = null; //`resetMatches` should not cancel the `promise`
                    }

                    promise.then(function(collection) {
                        scope.output = collection;

                        if (collection.length !== output.length) {
                            scope.removeItem(collection.length); //if newItem was not created
                        }
                    });
                });

                scope.$watch('query', function(inputValue, oldValue) {
                    adjustInput();

                    //We don't get matches if nothing added into matches list
                    if (inputValue !== oldValue && (!scope.oldQuery || inputValue) && !matchesWereReset) {
                        listElement[0].scrollTop = 0;

                        if (inputValue) {
                            getMatches(inputValue);
                            scope.oldQuery = null;
                        } else {
                            resetMatches();
                            matchesWereReset = true;
                        }
                    }
                    matchesWereReset = false;
                });

                scope.$watch('groups', function(groups) {
                    if (oiUtils.groupsIsEmpty(groups)) {
                        scope.isOpen = false;

                    } else if (!scope.isOpen && !attrs.disabled) {
                        scope.isOpen = true;
                        scope.isFocused = true;
                        oiUtils.copyWidth(element, listElement);
                    }
                });

                scope.$watch('isFocused', function(isFocused) {
                    if (isFocused) {
                        element.triggerHandler('focus');
                        $document.on('click', blurHandler);
                    }
                });

                scope.setFocus = function(event) {
                    if (attrs.disabled) return;

                    scope.backspaceFocus = false;

                    if (event.target.nodeName !== 'INPUT') {
                        inputElement[0].focus();
                    }

                    if (event.type === 'focus' && !scope.isOpen && !scope.isFocused) {
                        scope.isFocused = true;

                        return;
                    }

                    if (event.type === 'click' && angular.element(event.target).scope() === this) { //not click on add or remove buttons
                        if (scope.isOpen && !scope.query) {
                            resetMatches();
                        } else {
                            getMatches(scope.query);
                        }
                    }
                };

                scope.addItem = function addItem(option) {
                    lastQuery = scope.query;

                    //duplicate
                    if (oiUtils.intersection(scope.output, [option], null, trackBy, trackBy).length) return;

                    //limit is reached
                    if (!isNaN(multipleLimit) && scope.output.length >= multipleLimit) return;

                    var optionGroup = scope.groups[getGroupName(option)];
                    var modelOption = selectAsFn ? selectAs(option) : option;

                    optionGroup.splice(optionGroup.indexOf(option), 1);

                    if (multiple) {
                        ctrl.$setViewValue(angular.isArray(ctrl.$modelValue) ? ctrl.$modelValue.concat(modelOption) : [modelOption]);
                        updateGroupPos();
                    } else {
                        ctrl.$setViewValue(modelOption);
                        resetMatches();
                    }

                    if (oiUtils.groupsIsEmpty(scope.groups)) {
                        scope.groups = {}; //it is necessary for groups watcher
                    }

                    scope.oldQuery = scope.oldQuery || scope.query;
                    scope.query = '';
                    scope.backspaceFocus = false;

                    adjustInput();
                };

                scope.removeItem = function removeItem(position) {
                    var removedValue;

                    if (attrs.disabled) return;

                    if (multiple && position >= 0) {
                        removedValue = ctrl.$modelValue[position];
                        ctrl.$modelValue.splice(position, 1);
                        ctrl.$setViewValue([].concat(ctrl.$modelValue));

                    } else if (!angular.isDefined(attrs.notempty)) {
                        removedValue = ctrl.$modelValue;
                        ctrl.$setViewValue(undefined);
                    }

                    scope.query = lastQueryFn(removedValue, lastQuery);

                    if (scope.isOpen || scope.oldQuery || !multiple) {
                        getMatches(scope.oldQuery); //stay old list
                    }

                    adjustInput();
                };

                scope.setSelection = function(index) {
                    if (!keyUpDownWerePressed && scope.selectorPosition !== index) {
                        setOption(listElement, index);
                    } else {
                        keyUpDownWerePressed = false;
                    }
                };

                function saveOn(triggerName) {
                    var isTriggered    = (new RegExp(triggerName)).test(options.saveTrigger),
                        isNewItem      = options.newItem && scope.query,
                        isSelectedItem = angular.isNumber(scope.selectorPosition),
                        selectedOrder  = scope.order[scope.selectorPosition],
                        newItemFn      = options.newItemFn || options.newItemModelFn,
                        itemPromise    = $q.reject();

                    if (isTriggered && (isNewItem || isSelectedItem && selectedOrder)) {
                        scope.showLoader = true;
                        itemPromise = $q.when(triggerName !== 'blur' && selectedOrder || scope.query && newItemFn(scope.query));
                    }

                    itemPromise
                        .then(scope.addItem)
                        .finally(function() {
                            var bottom = scope.order.length - 1;

                            if (scope.selectorPosition === bottom) {
                                setOption(listElement, 0); //TODO optimise when list will be closed
                            }
                            options.newItemFn && !isSelectedItem || $timeout(angular.noop); //TODO $applyAsync work since Angular 1.3
                            resetMatches();
                        });
                }

                scope.keyParser = function keyParser(event) {
                    var top    = 0,
                        bottom = scope.order.length - 1;

                    switch (event.keyCode) {
                        case 38: /* up */
                            scope.selectorPosition = angular.isNumber(scope.selectorPosition) ? scope.selectorPosition : top;
                            setOption(listElement, scope.selectorPosition === top ? bottom : scope.selectorPosition - 1);
                            keyUpDownWerePressed = true;
                            break;

                        case 40: /* down */
                            scope.selectorPosition = angular.isNumber(scope.selectorPosition) ? scope.selectorPosition : top - 1;
                            setOption(listElement, scope.selectorPosition === bottom ? top : scope.selectorPosition + 1);
                            keyUpDownWerePressed = true;
                            if (!scope.query.length && !scope.isOpen) {
                                getMatches();
                            }
                            break;

                        case 37: /* left */
                        case 39: /* right */
                            break;

                        case 13: /* enter */
                            saveOn('enter');
                            break;
                        case 9: /* tab */
                            blurHandler();
                            break;
                        case 220: /* backslash */
                            saveOn('backslash');
                            event.preventDefault(); //backslash interpreted as a regexp
                            break;

                        case 27: /* esc */
                            resetMatches();
                            break;

                        case 8: /* backspace */
                            if (!scope.query.length) {
                                if (scope.backspaceFocus && scope.output) {
                                    scope.removeItem(scope.output.length - 1);
                                    if (!scope.output.length) {
                                        getMatches();
                                        break;
                                    }
                                }
                                scope.backspaceFocus = !scope.backspaceFocus;
                                break;
                            }
                        default: /* any key */
                            scope.backspaceFocus = false;
                            return false; //preventDefaults
                    }
                };

                scope.getSearchLabel = function(option) {
                    var label = getLabel(option);

                    if (options.searchFilter) {
                        label = $filter(options.searchFilter)(label, scope.oldQuery || scope.query, option)
                    }
                    return label;
                };

                scope.getDropdownLabel = function(option) {
                    var label = getLabel(option);

                    if (options.dropdownFilter) {
                        label = $filter(options.dropdownFilter)(label, scope.oldQuery || scope.query, option)
                    }
                    return label;
                };

                if (multiple) {
                    // Override the standard $isEmpty because an empty array means the input is empty.
                    ctrl.$isEmpty = function(value) {
                        return !value || !value.length;
                    };
                }

                resetMatches();

                function blurHandler(event) {
                    if (!event || event.target.ownerDocument.activeElement !== inputElement[0]) {
                        $timeout(function() {
                            element.triggerHandler('blur'); //conflict with current live cycle (case: multiple=none + tab)
                        });
                        saveOn('blur');
                        $document.off('click', blurHandler);
                        scope.isFocused = false;
                        scope.$evalAsync();
                    }
                }

                function adjustInput() {
                    var currentPlaceholder = ctrl.$modelValue && ctrl.$modelValue.length ? '' : placeholder;
                    inputElement.attr('placeholder', currentPlaceholder);
                    // expand input box width based on content
                    scope.inputWidth = oiUtils.measureString(scope.query || currentPlaceholder, inputElement) + 4;
                }

                function trackBy(item) {
                    return oiUtils.getValue(valueName, item, scope, trackByFn);
                }

                function selectAs(item) {
                    return oiUtils.getValue(valueName, item, scope, selectAsFn);
                }

                function getLabel(item) {
                    return oiUtils.getValue(valueName, item, scope, displayFn);
                }

                function getGroupName(option) {
                    return oiUtils.getValue(valueName, option, scope, groupByFn) || '';
                }

                function filter(list) {
                    return oiUtils.getValue(valuesName, list, scope.$parent, filteredValuesFn);
                }

                function getMatches(query, querySelectAs) {
                    var values = valuesFn(scope.$parent, {$query: query, $querySelectAs: querySelectAs}),
                        waitTime = 0;

                    scope.selectorPosition = options.newItem === 'prompt' ? false : 0;

                    if (!query && !querySelectAs) {
                        scope.oldQuery = null;
                    }

                    if (timeoutPromise && (angular.isFunction(values.then) || angular.isFunction(values.$promise))) {
                        $timeout.cancel(timeoutPromise); //cancel previous timeout
                        waitTime = options.debounce;
                    }

                    timeoutPromise = $timeout(function() {
                        scope.showLoader = true;

                        return $q.when(values.$promise || values)
                            .then(function(values) {
                                if (!querySelectAs) {
                                    var filteredList   = $filter(options.listFilter)(oiUtils.objToArr(values), query, getLabel);
                                    var withoutOverlap = oiUtils.intersection(filteredList, scope.output, oiUtils.isEqual, trackBy, trackBy, true);
                                    var filteredOutput = filter(withoutOverlap);

                                    scope.groups = group(filteredOutput);

                                    updateGroupPos();
                                }
                                return values;
                            })
                            .finally(function(){
                                scope.showLoader = false;
                            });
                    }, waitTime);

                    return timeoutPromise;
                }

                function updateGroupPos() {
                    var i, key, value, collectionKeys = [], groupCount = 0;

                    scope.order = [];
                    scope.groupPos = {};

                    for (key in scope.groups) {
                        if (scope.groups.hasOwnProperty(key) && key.charAt(0) != '$') {
                            collectionKeys.push(key);
                        }
                    }
                    collectionKeys.sort();

                    for (i = 0; i < collectionKeys.length; i++) {
                        key = collectionKeys[i];
                        value = scope.groups[key];

                        scope.order = scope.order.concat(value);
                        scope.groupPos[key] = groupCount;
                        groupCount += value.length
                    }
                }

                function resetMatches() {
                    scope.oldQuery = null;
                    scope.backspaceFocus = false; // clears focus on any chosen item for del
                    scope.query = '';
                    scope.groups = {};
                    scope.order = [];
                    scope.showLoader = false;
                    scope.isOpen   = false;

                    if (timeoutPromise) {
                        $timeout.cancel(timeoutPromise);//cancel previous timeout
                    }
                }

                function setOption(listElement, position) {
                    scope.selectorPosition = position;
                    oiUtils.scrollActiveOption(listElement[0], listElement.find('li')[position]);
                }

                function group(input) {
                    var optionGroups = {'':[]},
                        optionGroupName,
                        optionGroup;

                    for (var i = 0; i < input.length; i++) {
                        optionGroupName = getGroupName(input[i]);

                        if (!(optionGroup = optionGroups[optionGroupName])) {
                            optionGroup = optionGroups[optionGroupName] = [];
                        }
                        optionGroup.push(input[i]);
                    }

                    return optionGroups;
                }
            }
        }
    }
}]);<commit_msg>Fixed missing dropdown on touch devices<commit_after>angular.module('oi.multiselect')

.directive('oiMultiselect', ['$document', '$q', '$timeout', '$parse', '$interpolate', '$injector', '$filter', 'oiUtils', 'oiMultiselect', function($document, $q, $timeout, $parse, $interpolate, $injector, $filter, oiUtils, oiMultiselect) {
    var NG_OPTIONS_REGEXP = /^\s*([\s\S]+?)(?:\s+as\s+([\s\S]+?))?(?:\s+group\s+by\s+([\s\S]+?))?\s+for\s+(?:([\$\w][\$\w]*)|(?:\(\s*([\$\w][\$\w]*)\s*,\s*([\$\w][\$\w]*)\s*\)))\s+in\s+([\s\S]+?)(?:\s+track\s+by\s+([\s\S]+?))?$/,
        VALUES_REGEXP     = /([^\(\)\s\|\s]*)\s*(\(.*\))?\s*(\|?\s*.+)?/;

    return {
        restrict: 'AE',
        templateUrl: 'template/multiselect/template.html',
        require: 'ngModel',
        scope: {},
        compile: function (element, attrs) {
            var optionsExp = attrs.oiOptions,
                match;

            if (!(match = optionsExp.match(NG_OPTIONS_REGEXP))) {
                throw new Error("Expected expression in form of '_select_ (as _label_)? for (_key_,)?_value_ in _collection_'");
            }

            var selectAsName         = / as /.test(match[0]) && match[1],    //item.modelValue
                displayName          = match[2] || match[1],                 //item.label
                valueName            = match[4] || match[6],                 //item
                groupByName          = match[3] || '',                       //item.groupName
                trackByName          = match[8] || displayName,              //item.id
                valueMatches         = match[7].match(VALUES_REGEXP);        //collection

            var valuesName           = valueMatches[1],                      //collection
                filteredValuesName   = valuesName + (valueMatches[3] || ''), //collection | filter
                valuesFnName         = valuesName + (valueMatches[2] || ''); //collection()

            var selectAsFn           = selectAsName && $parse(selectAsName),
                displayFn            = $parse(displayName),
                groupByFn            = $parse(groupByName),
                filteredValuesFn     = $parse(filteredValuesName),
                valuesFn             = $parse(valuesFnName),
                trackByFn            = $parse(trackByName);

            var multiple             = angular.isDefined(attrs.multiple),
                multipleLimit        = Number(attrs.multipleLimit),
                placeholderFn        = $interpolate(attrs.placeholder || ''),
                keyUpDownWerePressed = false,
                matchesWereReset     = false,
                optionsFn            = $parse(attrs.oiMultiselectOptions);

            var timeoutPromise,
                lastQuery;

            return function(scope, element, attrs, ctrl) {
                var inputElement = element.find('input'),
                    listElement  = angular.element(element[0].querySelector('.multiselect-dropdown')),
                    placeholder  = placeholderFn(scope),
                    options      = angular.extend({}, oiMultiselect.options, optionsFn(scope.$parent)),
                    lastQueryFn  = options.saveLastQuery ? $injector.get(options.saveLastQuery) : function() {return ''};

                options.newItemModelFn = function (query) {
                    return (optionsFn({$query: query}) || {}).newItemModel || query;
                };

                if (angular.isDefined(attrs.autofocus)) {
                    $timeout(function() {
                        inputElement[0].focus();
                    });
                }

                if (angular.isDefined(attrs.readonly)) {
                    inputElement.attr('readonly', true)
                }

                attrs.$observe('disabled', function(value) {
                    inputElement.prop('disabled', value);
                });

                scope.$parent.$watch(attrs.ngModel, function(value) {
                    adjustInput();

                    var output = value instanceof Array ? value : value ? [value]: [],
                        promise = $q.when(output);

                    if (selectAsFn && value) {
                        promise = getMatches(null, value)
                            .then(function(collection) {
                                return oiUtils.intersection(collection, output, oiUtils.isEqual, selectAs);
                            });
                        timeoutPromise = null; //`resetMatches` should not cancel the `promise`
                    }

                    promise.then(function(collection) {
                        scope.output = collection;

                        if (collection.length !== output.length) {
                            scope.removeItem(collection.length); //if newItem was not created
                        }
                    });
                });

                scope.$watch('query', function(inputValue, oldValue) {
                    adjustInput();

                    //We don't get matches if nothing added into matches list
                    if (inputValue !== oldValue && (!scope.oldQuery || inputValue) && !matchesWereReset) {
                        listElement[0].scrollTop = 0;

                        if (inputValue) {
                            getMatches(inputValue);
                            scope.oldQuery = null;
                        } else {
                            resetMatches();
                            matchesWereReset = true;
                        }
                    }
                    matchesWereReset = false;
                });

                scope.$watch('groups', function(groups) {
                    if (oiUtils.groupsIsEmpty(groups)) {
                        scope.isOpen = false;

                    } else if (!scope.isOpen && !attrs.disabled) {
                        scope.isOpen = true;
                        scope.isFocused = true;
                        oiUtils.copyWidth(element, listElement);
                    }
                });

                scope.$watch('isFocused', function(isFocused) {
                    if (isFocused) {
                        element.triggerHandler('focus');
                        $document.on('click', blurHandler);
                    }
                });

                scope.setFocus = function(event) {
                    if (attrs.disabled) return;

                    scope.backspaceFocus = false;

                    if (event.target.nodeName !== 'INPUT') {
                        inputElement[0].focus();
                    }

                    if (event.type === 'focus' && !scope.isOpen && !scope.isFocused) {
                        scope.isFocused = true;

                        return;
                    }

                   if ((event.type === 'click' || event.type === 'touchend') && angular.element(event.target).scope() === this) { //not click on add or remove buttons

                        if (scope.isOpen && !scope.query) {
                            resetMatches();
                        } else {
                            getMatches(scope.query);
                        }
                    }
                };

                scope.addItem = function addItem(option) {
                    lastQuery = scope.query;

                    //duplicate
                    if (oiUtils.intersection(scope.output, [option], null, trackBy, trackBy).length) return;

                    //limit is reached
                    if (!isNaN(multipleLimit) && scope.output.length >= multipleLimit) return;

                    var optionGroup = scope.groups[getGroupName(option)];
                    var modelOption = selectAsFn ? selectAs(option) : option;

                    optionGroup.splice(optionGroup.indexOf(option), 1);

                    if (multiple) {
                        ctrl.$setViewValue(angular.isArray(ctrl.$modelValue) ? ctrl.$modelValue.concat(modelOption) : [modelOption]);
                        updateGroupPos();
                    } else {
                        ctrl.$setViewValue(modelOption);
                        resetMatches();
                    }

                    if (oiUtils.groupsIsEmpty(scope.groups)) {
                        scope.groups = {}; //it is necessary for groups watcher
                    }

                    scope.oldQuery = scope.oldQuery || scope.query;
                    scope.query = '';
                    scope.backspaceFocus = false;

                    adjustInput();
                };

                scope.removeItem = function removeItem(position) {
                    var removedValue;

                    if (attrs.disabled) return;

                    if (multiple && position >= 0) {
                        removedValue = ctrl.$modelValue[position];
                        ctrl.$modelValue.splice(position, 1);
                        ctrl.$setViewValue([].concat(ctrl.$modelValue));

                    } else if (!angular.isDefined(attrs.notempty)) {
                        removedValue = ctrl.$modelValue;
                        ctrl.$setViewValue(undefined);
                    }

                    scope.query = lastQueryFn(removedValue, lastQuery);

                    if (scope.isOpen || scope.oldQuery || !multiple) {
                        getMatches(scope.oldQuery); //stay old list
                    }

                    adjustInput();
                };

                scope.setSelection = function(index) {
                    if (!keyUpDownWerePressed && scope.selectorPosition !== index) {
                        setOption(listElement, index);
                    } else {
                        keyUpDownWerePressed = false;
                    }
                };

                function saveOn(triggerName) {
                    var isTriggered    = (new RegExp(triggerName)).test(options.saveTrigger),
                        isNewItem      = options.newItem && scope.query,
                        isSelectedItem = angular.isNumber(scope.selectorPosition),
                        selectedOrder  = scope.order[scope.selectorPosition],
                        newItemFn      = options.newItemFn || options.newItemModelFn,
                        itemPromise    = $q.reject();

                    if (isTriggered && (isNewItem || isSelectedItem && selectedOrder)) {
                        scope.showLoader = true;
                        itemPromise = $q.when(triggerName !== 'blur' && selectedOrder || scope.query && newItemFn(scope.query));
                    }

                    itemPromise
                        .then(scope.addItem)
                        .finally(function() {
                            var bottom = scope.order.length - 1;

                            if (scope.selectorPosition === bottom) {
                                setOption(listElement, 0); //TODO optimise when list will be closed
                            }
                            options.newItemFn && !isSelectedItem || $timeout(angular.noop); //TODO $applyAsync work since Angular 1.3
                            resetMatches();
                        });
                }

                scope.keyParser = function keyParser(event) {
                    var top    = 0,
                        bottom = scope.order.length - 1;

                    switch (event.keyCode) {
                        case 38: /* up */
                            scope.selectorPosition = angular.isNumber(scope.selectorPosition) ? scope.selectorPosition : top;
                            setOption(listElement, scope.selectorPosition === top ? bottom : scope.selectorPosition - 1);
                            keyUpDownWerePressed = true;
                            break;

                        case 40: /* down */
                            scope.selectorPosition = angular.isNumber(scope.selectorPosition) ? scope.selectorPosition : top - 1;
                            setOption(listElement, scope.selectorPosition === bottom ? top : scope.selectorPosition + 1);
                            keyUpDownWerePressed = true;
                            if (!scope.query.length && !scope.isOpen) {
                                getMatches();
                            }
                            break;

                        case 37: /* left */
                        case 39: /* right */
                            break;

                        case 13: /* enter */
                            saveOn('enter');
                            break;
                        case 9: /* tab */
                            blurHandler();
                            break;
                        case 220: /* backslash */
                            saveOn('backslash');
                            event.preventDefault(); //backslash interpreted as a regexp
                            break;

                        case 27: /* esc */
                            resetMatches();
                            break;

                        case 8: /* backspace */
                            if (!scope.query.length) {
                                if (scope.backspaceFocus && scope.output) {
                                    scope.removeItem(scope.output.length - 1);
                                    if (!scope.output.length) {
                                        getMatches();
                                        break;
                                    }
                                }
                                scope.backspaceFocus = !scope.backspaceFocus;
                                break;
                            }
                        default: /* any key */
                            scope.backspaceFocus = false;
                            return false; //preventDefaults
                    }
                };

                scope.getSearchLabel = function(option) {
                    var label = getLabel(option);

                    if (options.searchFilter) {
                        label = $filter(options.searchFilter)(label, scope.oldQuery || scope.query, option)
                    }
                    return label;
                };

                scope.getDropdownLabel = function(option) {
                    var label = getLabel(option);

                    if (options.dropdownFilter) {
                        label = $filter(options.dropdownFilter)(label, scope.oldQuery || scope.query, option)
                    }
                    return label;
                };

                if (multiple) {
                    // Override the standard $isEmpty because an empty array means the input is empty.
                    ctrl.$isEmpty = function(value) {
                        return !value || !value.length;
                    };
                }

                resetMatches();

                function blurHandler(event) {
                    if (!event || event.target.ownerDocument.activeElement !== inputElement[0]) {
                        $timeout(function() {
                            element.triggerHandler('blur'); //conflict with current live cycle (case: multiple=none + tab)
                        });
                        saveOn('blur');
                        $document.off('click', blurHandler);
                        scope.isFocused = false;
                        scope.$evalAsync();
                    }
                }

                function adjustInput() {
                    var currentPlaceholder = ctrl.$modelValue && ctrl.$modelValue.length ? '' : placeholder;
                    inputElement.attr('placeholder', currentPlaceholder);
                    // expand input box width based on content
                    scope.inputWidth = oiUtils.measureString(scope.query || currentPlaceholder, inputElement) + 4;
                }

                function trackBy(item) {
                    return oiUtils.getValue(valueName, item, scope, trackByFn);
                }

                function selectAs(item) {
                    return oiUtils.getValue(valueName, item, scope, selectAsFn);
                }

                function getLabel(item) {
                    return oiUtils.getValue(valueName, item, scope, displayFn);
                }

                function getGroupName(option) {
                    return oiUtils.getValue(valueName, option, scope, groupByFn) || '';
                }

                function filter(list) {
                    return oiUtils.getValue(valuesName, list, scope.$parent, filteredValuesFn);
                }

                function getMatches(query, querySelectAs) {
                    var values = valuesFn(scope.$parent, {$query: query, $querySelectAs: querySelectAs}),
                        waitTime = 0;

                    scope.selectorPosition = options.newItem === 'prompt' ? false : 0;

                    if (!query && !querySelectAs) {
                        scope.oldQuery = null;
                    }

                    if (timeoutPromise && (angular.isFunction(values.then) || angular.isFunction(values.$promise))) {
                        $timeout.cancel(timeoutPromise); //cancel previous timeout
                        waitTime = options.debounce;
                    }

                    timeoutPromise = $timeout(function() {
                        scope.showLoader = true;

                        return $q.when(values.$promise || values)
                            .then(function(values) {
                                if (!querySelectAs) {
                                    var filteredList   = $filter(options.listFilter)(oiUtils.objToArr(values), query, getLabel);
                                    var withoutOverlap = oiUtils.intersection(filteredList, scope.output, oiUtils.isEqual, trackBy, trackBy, true);
                                    var filteredOutput = filter(withoutOverlap);

                                    scope.groups = group(filteredOutput);

                                    updateGroupPos();
                                }
                                return values;
                            })
                            .finally(function(){
                                scope.showLoader = false;
                            });
                    }, waitTime);

                    return timeoutPromise;
                }

                function updateGroupPos() {
                    var i, key, value, collectionKeys = [], groupCount = 0;

                    scope.order = [];
                    scope.groupPos = {};

                    for (key in scope.groups) {
                        if (scope.groups.hasOwnProperty(key) && key.charAt(0) != '$') {
                            collectionKeys.push(key);
                        }
                    }
                    collectionKeys.sort();

                    for (i = 0; i < collectionKeys.length; i++) {
                        key = collectionKeys[i];
                        value = scope.groups[key];

                        scope.order = scope.order.concat(value);
                        scope.groupPos[key] = groupCount;
                        groupCount += value.length
                    }
                }

                function resetMatches() {
                    scope.oldQuery = null;
                    scope.backspaceFocus = false; // clears focus on any chosen item for del
                    scope.query = '';
                    scope.groups = {};
                    scope.order = [];
                    scope.showLoader = false;
                    scope.isOpen   = false;

                    if (timeoutPromise) {
                        $timeout.cancel(timeoutPromise);//cancel previous timeout
                    }
                }

                function setOption(listElement, position) {
                    scope.selectorPosition = position;
                    oiUtils.scrollActiveOption(listElement[0], listElement.find('li')[position]);
                }

                function group(input) {
                    var optionGroups = {'':[]},
                        optionGroupName,
                        optionGroup;

                    for (var i = 0; i < input.length; i++) {
                        optionGroupName = getGroupName(input[i]);

                        if (!(optionGroup = optionGroups[optionGroupName])) {
                            optionGroup = optionGroups[optionGroupName] = [];
                        }
                        optionGroup.push(input[i]);
                    }

                    return optionGroups;
                }
            }
        }
    }
}]);<|endoftext|>


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

import currency from '../localization/currency';
import { UIDatabase } from '../database';
import { sortDataBy } from '../utilities';

export const selectPatientHistory = ({ patient }) => {
  const { currentPatient } = patient;
  const { transactions } = currentPatient;

  // Create a query string `transaction.id == "{id} OR transaction.id == "{id}" ...`
  // finding all transaction batches for the patient.
  const inQuery = transactions.map(({ id }) => `transaction.id == "${id}"`).join(' OR ');
  const baseQueryString = 'type != "cash_in" AND type != "cash_out"';
  const fullQuery = `(${inQuery}) AND ${baseQueryString}`;
  return inQuery ? UIDatabase.objects('TransactionBatch').filtered(fullQuery) : [];
};

export const selectSortedPatientHistory = ({ patient }) => {
  const { sortKey, isAscending } = patient;
  const patientHistory = selectPatientHistory({ patient });

  return patientHistory ? sortDataBy(patientHistory.slice(), sortKey, isAscending) : patientHistory;
};

export const selectCurrentPatient = ({ patient }) => {
  const { currentPatient } = patient;
  return currentPatient;
};

export const selectAvailableCredit = ({ patient }) =>
  currency(patient?.currentPatient?.availableCredit);

export const selectPatientInsurancePolicies = ({ patient }) => {
  const { currentPatient } = patient;
  const { policies } = currentPatient;
  return policies;
};

export const selectPatientModalOpen = ({ patient }) => {
  const { isCreating, isEditing } = patient;
  const { viewingHistory } = patient;
  return [isCreating || isEditing, viewingHistory];
};

export const selectCanEditPatient = ({ patient }) => {
  const { currentPatient } = patient;
  const { thisStoresPatient } = currentPatient ?? {};

  return thisStoresPatient ?? true;
};
<commit_msg>Update selectPatientInsurancePolicies to display inactive<commit_after>/**
 * mSupply Mobile
 * Sustainable Solutions (NZ) Ltd. 2019
 */

import currency from '../localization/currency';
import { UIDatabase } from '../database';
import { sortDataBy } from '../utilities';

export const selectPatientHistory = ({ patient }) => {
  const { currentPatient } = patient;
  const { transactions } = currentPatient;

  // Create a query string `transaction.id == "{id} OR transaction.id == "{id}" ...`
  // finding all transaction batches for the patient.
  const inQuery = transactions.map(({ id }) => `transaction.id == "${id}"`).join(' OR ');
  const baseQueryString = 'type != "cash_in" AND type != "cash_out"';
  const fullQuery = `(${inQuery}) AND ${baseQueryString}`;
  return inQuery ? UIDatabase.objects('TransactionBatch').filtered(fullQuery) : [];
};

export const selectSortedPatientHistory = ({ patient }) => {
  const { sortKey, isAscending } = patient;
  const patientHistory = selectPatientHistory({ patient });

  return patientHistory ? sortDataBy(patientHistory.slice(), sortKey, isAscending) : patientHistory;
};

export const selectCurrentPatient = ({ patient }) => {
  const { currentPatient } = patient;
  return currentPatient;
};

export const selectAvailableCredit = ({ patient }) =>
  currency(patient?.currentPatient?.availableCredit);

export const selectPatientInsurancePolicies = ({ patient }) => {
  const { currentPatient } = patient;
  const { policies } = currentPatient;
  return policies.map(policy => {
    const { isActive, policyNumber } = policy;
    return isActive ? policy : { ...policy, policyNumber: `${policyNumber} (inactive)` };
  });
};
export const selectPatientModalOpen = ({ patient }) => {
  const { isCreating, isEditing } = patient;
  const { viewingHistory } = patient;
  return [isCreating || isEditing, viewingHistory];
};

export const selectCanEditPatient = ({ patient }) => {
  const { currentPatient } = patient;
  const { thisStoresPatient } = currentPatient ?? {};

  return thisStoresPatient ?? true;
};
<|endoftext|>