<commit_before>/*
 * loquat / parser.js
 * copyright (c) 2016 Susisu
 */

/**
 * @module parser
 */

"use strict";

function end() {
    module.exports = Object.freeze({
        Config,
        State,
        Result,
        AbstractParser,
        Parser,
        LazyParser,
        lazy,
        parse
    });
}

const { SourcePos } = require("./pos.js");
const { ParseError } = require("./error.js");

/**
 * The `Config` class represents parser configuration.
 * @static
 */
class Config {
    /**
     * Creates a new `Config` instance.
     * @param {Object} [opts = {}] An object containing configuration options.
     * Available configurations are listed below:
     *

     *
Name	Type	Default	Description

     *
tabWidth	number	8	Positive integer specifying tab width.

     *
useCodePoint	boolean	false	Specifies character counting strategy.
     * If `false` (default), characters in input are counted by the traditional method.
     * If `true`, characters are counted based on the UTF-16 code point.
     * (For instance, `"\uD83C\uDF63"` is counted 2 characters by the traditional method,
     * while it is counted 1 character based on the code point.)
     * You should set this flag to `true` when the language you want to parse allows surrogate pairs (like emojis)
     * to appear in input.

     *

     */
    constructor(opts = {}) {
        this.tabWidth     = opts.tabWidth === undefined ? 8 : opts.tabWidth;
        this.useCodePoint = opts.useCodePoint === undefined ? false : opts.useCodePoint;
    }

    /** @member {number} module:parser.Config#tabWidth */
    /** @member {boolean} module:parser.Config#useCodePoint */

    /**
     * Checks if two configs are equal.
     * @param {module:parser.Config} configA
     * @param {module:parser.Config} configB
     * @returns {boolean}
     */
    static equal(configA, configB) {
        return configA.tabWidth === configB.tabWidth
            && configA.useCodePoint === configB.useCodePoint;
    }
}

/**
 * An instance of the `State` class describes state at some point in parsing.
 * @static
 */
class State {
    /**
     * Creates a new `State` instance.
     * @param {module:parser.Config} config Configuration of current parsing.
     * @param {(string|Array|module:stream.IStream)} input Current input.
     * @param {module:pos.SourcePos} pos Current position.
     * @param {*} [userState = undefined] Additional user-defined state for advanced use.
     */
    constructor(config, input, pos, userState = undefined) {
        this.config    = config;
        this.input     = input;
        this.pos       = pos;
        this.userState = userState;
    }

    /** @member {module:parser.Config} module:parser.State#config */
    /** @member {(string|Array|module:stream.IStream)} module:parser.State#input */
    /** @member {module:pos.SourcePos} module:parser.State#pos */
    /** @member {*} module:parser.State#userState */

    /**
     * Checks if two states are equal.
     * @param {module:parser.State} stateA
     * @param {module:parser.State} stateB
     * @param {(function|undefined)} [inputEqual = undefined]
     * @param {(function|undefined)} [userStateEqual = undefined]
     * @returns {boolean}
     */
    static equal(stateA, stateB, inputEqual = undefined, userStateEqual = undefined) {
        return Config.equal(stateA.config, stateB.config)
            && (inputEqual === undefined
                ? stateA.input === stateB.input
                : inputEqual(stateA.input, stateB.input))
            && SourcePos.equal(stateA.pos, stateB.pos)
            && (userStateEqual === undefined
                ? stateA.userState === stateB.userState
                : userStateEqual(stateA.userState, stateB.userState));
    }

    /**
     * @param {(string|Array|module:stream.IStream)} input
     * @returns {module:parser.State}
     */
    setInput(input) {
        return new State(this.config, input, this.pos, this.userState);
    }

    /**
     * @param {module:pos.SourcePos} pos
     * @returns {module:parser.State}
     */
    setPosition(pos) {
        return new State(this.config, this.input, pos, this.userState);
    }

    /**
     * @param {*} userState
     * @returns {module:parser.State}
     */
    setUserState(userState) {
        return new State(this.config, this.input, this.pos, userState);
    }
}

/**
 * The `Result` class represents a result of parsing.
 * @static
 */
class Result {
    /**
     * Creates a new `Result` instance.
     * @param {boolean} consumed Indicates the parser consumed input or not.
     * @param {boolean} succeeded Indicates the parser was succeeded or not.
     * If `true`, `val` and `state` must be specified.
     * @param {module:error.AbstractParseError} err Parse error object.
     * @param {*} [val = undefined] Obtained value.
     * @param {(module:parser.State|undefined)} [state = undefined] Next state.
     */
    constructor(consumed, succeeded, err, val = undefined, state = undefined) {
        this.consumed  = consumed;
        this.succeeded = succeeded;
        this.err       = err;
        this.val       = val;
        this.state     = state;
    }

    /**
     * Checks if two results are equal.
     * The properties `val` and `state` are compared only when both results are succeeded.
     * @param {module:parser.Result} resA
     * @param {module:parser.Result} resB
     * @param {(function|undefined)} valEqual
     * @param {(function|undefined)} inputEqual
     * @param {(function|undefined)} userStateEqual
     * @returns {boolean}
     */
    static equal(resA, resB, valEqual = undefined, inputEqual = undefined, userStateEqual = undefined) {
        if (resA.succeeded && resB.succeeded) {
            return resA.consumed === resB.consumed
                && (valEqual === undefined
                    ? resA.val === resB.val
                    : valEqual(resA.val, resB.val))
                && State.equal(resA.state, resB.state, inputEqual, userStateEqual)
                && ParseError.equal(resA.err, resB.err);
        }
        else {
            return resA.succeeded === resB.succeeded
                && resA.consumed === resB.consumed
                && ParseError.equal(resA.err, resB.err);
        }
    }

    /**
     * Returns consumed and succeeded result object.
     * @param {module:error.AbstractParseError} err
     * @param {*} val
     * @param {module:parser.State} state
     * @returns {module:parser.Result}
     */
    static csuc(err, val, state) {
        return new Result(true, true, err, val, state);
    }

    /**
     * Returns consumed but error result object.
     * @param {module:error.AbstractParseError} err
     * @returns {module:parser.Result}
     */
    static cerr(err) {
        return new Result(true, false, err);
    }

    /**
     * Returns not consumed and succeeded result object.
     * @param {module:error.AbstractParseError} err
     * @param {*} val
     * @param {module:parser.State} state
     * @returns {module:parser.Result}
     */
    static esuc(err, val, state) {
        return new Result(false, true, err, val, state);
    }

    /**
     * Returns not consumed but error result object.
     * @param {module:error.AbstractParseError} err
     * @returns {module:parser.Result}
     */
    static eerr(err) {
        return new Result(false, false, err);
    }
}

/**
 * The `AbstractParser` class is inherited by the concrete parser classes,
 * and is used for extending all the parser classes.
 * This class is abstract and you cannot create `AbstractParser` instance directly.
 * @static
 */
class AbstractParser {
    /**
     * You cannot create `AbstractParser` instance directly.
     * @throws {Error}
     */
    constructor() {
        if (this.constructor === AbstractParser) {
            throw new Error("cannot create AbstractParser object");
        }
    }

    /**
     * Not implemented.
     * @returns {module:parser.Result}
     * @throws {Error}
     */
    run() {
        throw new Error("not implemented");
    }
}

/**
 * The `Parser` class is just a wrapper of parser function.
 * @static
 * @extends {module:parser.AbstractParser}
 */
class Parser extends AbstractParser {
    /**
     * Creates a new `Parser` object.
     * @param {function} func Parser function, which must take a {@link module:parser.State} object as its argument
     * and return a {@link module:parser.Result} object.
     */
    constructor(func) {
        super();
        this._func = func;
    }

    /**
     * @param {module:parser.State} state
     * @returns {module:parser.Result}
     */
    run(state) {
        return this._func.call(undefined, state);
    }
}

/**
 * The `LazyParser` class is a lazy version of the {@link:module.Parser} class.
 * @static
 * @extends {module:parser.AbstractParser}
 */
class LazyParser extends AbstractParser {
    /**
     * Creates a new `LazyParser` object.
     * @param {function} thunk A function, which must return an {@link module:parser.AbstractParser} object.
     */
    constructor(thunk) {
        super();
        this._thunk = thunk;
        this._cache = undefined;
    }

    /**
     * Evaluates the thunk.
     * @returns {module:parser.Parser}
     * @throws {TypeError} Invalid thunk (not a function) found while evaluation.
     * @throws {TypeError} The final evaluation result is not a {@link module:error.ParseError} object.
     */
    eval() {
        if (this._cache) {
            return this._cache;
        }
        let lazyParsers = [];
        let parser = this;
        while (parser instanceof LazyParser) {
            if (parser._cache) {
                parser = parser._cache;
            }
            else {
                lazyParsers.push(parser);
                if (typeof parser._thunk !== "function") {
                    throw new TypeError("thunk is not a function");
                }
                parser = parser._thunk.call(undefined);
            }
        }
        if (!(parser instanceof Parser)) {
            throw new TypeError("evaluation result is not a Parser object");
        }
        for (let lazyParser of lazyParsers) {
            lazyParser._cache = parser;
        }
        return parser;
    }

    /**
     * @param {module:parser.State} state
     * @returns {module:parser.Result}
     */
    run(state) {
        return this.eval().run(state);
    }
}

/**
 * Shorthand for `new LazyParser()`.
 * @param {function} thunk
 * @returns {module:parser.LazyParser}
 */
function lazy(thunk) {
    return new LazyParser(thunk);
}

/**
 * @param {module:parser.AbstractParser} parser
 * @param {string} name
 * @param {(string | Array | module:stream.IStream)} input
 * @param {*} [userState = undefined]
 * @param {Object} [opts = {}]
 * @returns {Object}
 */
function parse(parser, name, input, userState = undefined, opts = {}) {
    let state = new State(new Config(opts), input, SourcePos.init(name), userState);
    let res   = parser.run(state);
    return res.succeeded
        ? { succeeded: true, value: res.val }
        : { succeeded: false, error: res.err };
}

end();
<commit_msg>[core] Add `State#setConfig()`<commit_after>/*
 * loquat / parser.js
 * copyright (c) 2016 Susisu
 */

/**
 * @module parser
 */

"use strict";

function end() {
    module.exports = Object.freeze({
        Config,
        State,
        Result,
        AbstractParser,
        Parser,
        LazyParser,
        lazy,
        parse
    });
}

const { SourcePos } = require("./pos.js");
const { ParseError } = require("./error.js");

/**
 * The `Config` class represents parser configuration.
 * @static
 */
class Config {
    /**
     * Creates a new `Config` instance.
     * @param {Object} [opts = {}] An object containing configuration options.
     * Available configurations are listed below:
     *

     *
Name	Type	Default	Description

     *
tabWidth	number	8	Positive integer specifying tab width.

     *
useCodePoint	boolean	false	Specifies character counting strategy.
     * If `false` (default), characters in input are counted by the traditional method.
     * If `true`, characters are counted based on the UTF-16 code point.
     * (For instance, `"\uD83C\uDF63"` is counted 2 characters by the traditional method,
     * while it is counted 1 character based on the code point.)
     * You should set this flag to `true` when the language you want to parse allows surrogate pairs (like emojis)
     * to appear in input.

     *

     */
    constructor(opts = {}) {
        this.tabWidth     = opts.tabWidth === undefined ? 8 : opts.tabWidth;
        this.useCodePoint = opts.useCodePoint === undefined ? false : opts.useCodePoint;
    }

    /** @member {number} module:parser.Config#tabWidth */
    /** @member {boolean} module:parser.Config#useCodePoint */

    /**
     * Checks if two configs are equal.
     * @param {module:parser.Config} configA
     * @param {module:parser.Config} configB
     * @returns {boolean}
     */
    static equal(configA, configB) {
        return configA.tabWidth === configB.tabWidth
            && configA.useCodePoint === configB.useCodePoint;
    }
}

/**
 * An instance of the `State` class describes state at some point in parsing.
 * @static
 */
class State {
    /**
     * Creates a new `State` instance.
     * @param {module:parser.Config} config Configuration of current parsing.
     * @param {(string|Array|module:stream.IStream)} input Current input.
     * @param {module:pos.SourcePos} pos Current position.
     * @param {*} [userState = undefined] Additional user-defined state for advanced use.
     */
    constructor(config, input, pos, userState = undefined) {
        this.config    = config;
        this.input     = input;
        this.pos       = pos;
        this.userState = userState;
    }

    /** @member {module:parser.Config} module:parser.State#config */
    /** @member {(string|Array|module:stream.IStream)} module:parser.State#input */
    /** @member {module:pos.SourcePos} module:parser.State#pos */
    /** @member {*} module:parser.State#userState */

    /**
     * Checks if two states are equal.
     * @param {module:parser.State} stateA
     * @param {module:parser.State} stateB
     * @param {(function|undefined)} [inputEqual = undefined]
     * @param {(function|undefined)} [userStateEqual = undefined]
     * @returns {boolean}
     */
    static equal(stateA, stateB, inputEqual = undefined, userStateEqual = undefined) {
        return Config.equal(stateA.config, stateB.config)
            && (inputEqual === undefined
                ? stateA.input === stateB.input
                : inputEqual(stateA.input, stateB.input))
            && SourcePos.equal(stateA.pos, stateB.pos)
            && (userStateEqual === undefined
                ? stateA.userState === stateB.userState
                : userStateEqual(stateA.userState, stateB.userState));
    }

    /**
     * @param {module:parser.Config} config
     * @returns {module:parser.State}
     */
    setConfig(config) {
        return new State(config, this.input, this.pos, this.userState);
    }

    /**
     * @param {(string|Array|module:stream.IStream)} input
     * @returns {module:parser.State}
     */
    setInput(input) {
        return new State(this.config, input, this.pos, this.userState);
    }

    /**
     * @param {module:pos.SourcePos} pos
     * @returns {module:parser.State}
     */
    setPosition(pos) {
        return new State(this.config, this.input, pos, this.userState);
    }

    /**
     * @param {*} userState
     * @returns {module:parser.State}
     */
    setUserState(userState) {
        return new State(this.config, this.input, this.pos, userState);
    }
}

/**
 * The `Result` class represents a result of parsing.
 * @static
 */
class Result {
    /**
     * Creates a new `Result` instance.
     * @param {boolean} consumed Indicates the parser consumed input or not.
     * @param {boolean} succeeded Indicates the parser was succeeded or not.
     * If `true`, `val` and `state` must be specified.
     * @param {module:error.AbstractParseError} err Parse error object.
     * @param {*} [val = undefined] Obtained value.
     * @param {(module:parser.State|undefined)} [state = undefined] Next state.
     */
    constructor(consumed, succeeded, err, val = undefined, state = undefined) {
        this.consumed  = consumed;
        this.succeeded = succeeded;
        this.err       = err;
        this.val       = val;
        this.state     = state;
    }

    /**
     * Checks if two results are equal.
     * The properties `val` and `state` are compared only when both results are succeeded.
     * @param {module:parser.Result} resA
     * @param {module:parser.Result} resB
     * @param {(function|undefined)} valEqual
     * @param {(function|undefined)} inputEqual
     * @param {(function|undefined)} userStateEqual
     * @returns {boolean}
     */
    static equal(resA, resB, valEqual = undefined, inputEqual = undefined, userStateEqual = undefined) {
        if (resA.succeeded && resB.succeeded) {
            return resA.consumed === resB.consumed
                && (valEqual === undefined
                    ? resA.val === resB.val
                    : valEqual(resA.val, resB.val))
                && State.equal(resA.state, resB.state, inputEqual, userStateEqual)
                && ParseError.equal(resA.err, resB.err);
        }
        else {
            return resA.succeeded === resB.succeeded
                && resA.consumed === resB.consumed
                && ParseError.equal(resA.err, resB.err);
        }
    }

    /**
     * Returns consumed and succeeded result object.
     * @param {module:error.AbstractParseError} err
     * @param {*} val
     * @param {module:parser.State} state
     * @returns {module:parser.Result}
     */
    static csuc(err, val, state) {
        return new Result(true, true, err, val, state);
    }

    /**
     * Returns consumed but error result object.
     * @param {module:error.AbstractParseError} err
     * @returns {module:parser.Result}
     */
    static cerr(err) {
        return new Result(true, false, err);
    }

    /**
     * Returns not consumed and succeeded result object.
     * @param {module:error.AbstractParseError} err
     * @param {*} val
     * @param {module:parser.State} state
     * @returns {module:parser.Result}
     */
    static esuc(err, val, state) {
        return new Result(false, true, err, val, state);
    }

    /**
     * Returns not consumed but error result object.
     * @param {module:error.AbstractParseError} err
     * @returns {module:parser.Result}
     */
    static eerr(err) {
        return new Result(false, false, err);
    }
}

/**
 * The `AbstractParser` class is inherited by the concrete parser classes,
 * and is used for extending all the parser classes.
 * This class is abstract and you cannot create `AbstractParser` instance directly.
 * @static
 */
class AbstractParser {
    /**
     * You cannot create `AbstractParser` instance directly.
     * @throws {Error}
     */
    constructor() {
        if (this.constructor === AbstractParser) {
            throw new Error("cannot create AbstractParser object");
        }
    }

    /**
     * Not implemented.
     * @returns {module:parser.Result}
     * @throws {Error}
     */
    run() {
        throw new Error("not implemented");
    }
}

/**
 * The `Parser` class is just a wrapper of parser function.
 * @static
 * @extends {module:parser.AbstractParser}
 */
class Parser extends AbstractParser {
    /**
     * Creates a new `Parser` object.
     * @param {function} func Parser function, which must take a {@link module:parser.State} object as its argument
     * and return a {@link module:parser.Result} object.
     */
    constructor(func) {
        super();
        this._func = func;
    }

    /**
     * @param {module:parser.State} state
     * @returns {module:parser.Result}
     */
    run(state) {
        return this._func.call(undefined, state);
    }
}

/**
 * The `LazyParser` class is a lazy version of the {@link:module.Parser} class.
 * @static
 * @extends {module:parser.AbstractParser}
 */
class LazyParser extends AbstractParser {
    /**
     * Creates a new `LazyParser` object.
     * @param {function} thunk A function, which must return an {@link module:parser.AbstractParser} object.
     */
    constructor(thunk) {
        super();
        this._thunk = thunk;
        this._cache = undefined;
    }

    /**
     * Evaluates the thunk.
     * @returns {module:parser.Parser}
     * @throws {TypeError} Invalid thunk (not a function) found while evaluation.
     * @throws {TypeError} The final evaluation result is not a {@link module:error.ParseError} object.
     */
    eval() {
        if (this._cache) {
            return this._cache;
        }
        let lazyParsers = [];
        let parser = this;
        while (parser instanceof LazyParser) {
            if (parser._cache) {
                parser = parser._cache;
            }
            else {
                lazyParsers.push(parser);
                if (typeof parser._thunk !== "function") {
                    throw new TypeError("thunk is not a function");
                }
                parser = parser._thunk.call(undefined);
            }
        }
        if (!(parser instanceof Parser)) {
            throw new TypeError("evaluation result is not a Parser object");
        }
        for (let lazyParser of lazyParsers) {
            lazyParser._cache = parser;
        }
        return parser;
    }

    /**
     * @param {module:parser.State} state
     * @returns {module:parser.Result}
     */
    run(state) {
        return this.eval().run(state);
    }
}

/**
 * Shorthand for `new LazyParser()`.
 * @param {function} thunk
 * @returns {module:parser.LazyParser}
 */
function lazy(thunk) {
    return new LazyParser(thunk);
}

/**
 * @param {module:parser.AbstractParser} parser
 * @param {string} name
 * @param {(string | Array | module:stream.IStream)} input
 * @param {*} [userState = undefined]
 * @param {Object} [opts = {}]
 * @returns {Object}
 */
function parse(parser, name, input, userState = undefined, opts = {}) {
    let state = new State(new Config(opts), input, SourcePos.init(name), userState);
    let res   = parser.run(state);
    return res.succeeded
        ? { succeeded: true, value: res.val }
        : { succeeded: false, error: res.err };
}

end();
<|endoftext|>


<commit_before>import React from 'react'
import PropTypes from 'prop-types'

import { connect } from 'react-redux'

import { Notification, NotificationDrawer, MenuItem, Icon, Button } from 'patternfly-react'

import style from './style.css'

import { clearUserMessages, dismissUserMessage } from '../../actions'
import { hrefWithoutHistory, getFormatedDateTime } from '../../helpers'
import { msg } from '../../intl'

const UserMessage = ({ record, id, onDismissMessage }) => {
  const time = getFormatedDateTime(record.get('time'))
  return (<Notification seen>
    <NotificationDrawer.Dropdown id={id}>
      <MenuItem onClick={onDismissMessage}>
        { msg.clear() }
      </MenuItem>
    </NotificationDrawer.Dropdown>
    <Icon className='pull-left' type='pf' name='warning-triangle-o' />
    <Notification.Content>
      <Notification.Message>
        {record.get('message')}
      </Notification.Message>
      <Notification.Info leftText={time.date} rightText={time.time} />
    </Notification.Content>
  </Notification>)
}
UserMessage.propTypes = {
  record: PropTypes.object.isRequired,
  id: PropTypes.string,
  onDismissMessage: PropTypes.func.isRequired,
}

class VmUserMessages extends React.Component {
  constructor (props) {
    super(props)
    this.state = {
      show: false,
      expand: false,
    }
    this.handleToggle = this.handleToggle.bind(this)
    this.handleExpand = this.handleExpand.bind(this)
  }

  handleToggle () {
    this.setState((prevState) => ({ show: !prevState.show }))
  }

  handleExpand () {
    this.setState((prevState) => ({ expanded: !prevState.expanded }))
  }

  render () {
    const { userMessages, onClearMessages, onDismissMessage } = this.props

    const idPrefix = `usermsgs`

    const messagesCount = userMessages.get('records').size
    const messagesList = messagesCount
      ? userMessages.get('records').map(r => (
        <UserMessage
          key={`msg-${r.get('time')}`}
          record={r}
          id={`${idPrefix}-msg-${r.get('time')}-dropdown`}
          onDismissMessage={() => onDismissMessage(r.get('time'))}
        />
      ))
      : <NotificationDrawer.EmptyState title={msg.noMessages()} />

    const badgeElement = messagesCount === 0
      ? null
      : <span className='badge' id={`${idPrefix}-size`}>{messagesCount}
    return (

        <a className='dropdown-toggle nav-item-iconic' href='#' title={msg.messages()} onClick={hrefWithoutHistory(this.handleToggle)} id={`${idPrefix}-toggle`}>
          <i className={`fa fa-bell ${style['usermessages-icon']}`} />
          {badgeElement}
          <span className='caret' id={`${idPrefix}-caret`} />

        <NotificationDrawer hide={!this.state.show} expanded={this.state.expanded}>
          <NotificationDrawer.Title onCloseClick={this.handleToggle} onExpandClick={this.handleExpand} />
          <NotificationDrawer.PanelBody className={style['panel-body']}>
            {messagesList}
            <NotificationDrawer.PanelAction>
              <NotificationDrawer.PanelActionLink data-toggle='clear-all'>
                <Button bsStyle='link' onClick={onClearMessages} disabled={!messagesCount}>
                  <Icon type='pf' name='close' />
                  { msg.clearAll() }
                </Button>
              </NotificationDrawer.PanelActionLink>
            </NotificationDrawer.PanelAction>
          </NotificationDrawer.PanelBody>
        </NotificationDrawer>

    )
  }
}
VmUserMessages.propTypes = {
  userMessages: PropTypes.object.isRequired,
  onClearMessages: PropTypes.func.isRequired,
  onDismissMessage: PropTypes.func.isRequired,
}

export default connect(
  (state) => ({
    userMessages: state.userMessages,
  }),
  (dispatch) => ({
    onClearMessages: () => dispatch(clearUserMessages()),
    onDismissMessage: (time) => dispatch(dismissUserMessage({ time })),
  })
)(VmUserMessages)
<commit_msg>Hide 'Clear' button in notification drawer if there is no notifications. Fixes: https://github.com/oVirt/ovirt-web-ui/issues/710<commit_after>import React from 'react'
import PropTypes from 'prop-types'

import { connect } from 'react-redux'

import { Notification, NotificationDrawer, MenuItem, Icon, Button } from 'patternfly-react'

import style from './style.css'

import { clearUserMessages, dismissUserMessage } from '../../actions'
import { hrefWithoutHistory, getFormatedDateTime } from '../../helpers'
import { msg } from '../../intl'

const UserMessage = ({ record, id, onDismissMessage }) => {
  const time = getFormatedDateTime(record.get('time'))
  return (<Notification seen>
    <NotificationDrawer.Dropdown id={id}>
      <MenuItem onClick={onDismissMessage}>
        { msg.clear() }
      </MenuItem>
    </NotificationDrawer.Dropdown>
    <Icon className='pull-left' type='pf' name='warning-triangle-o' />
    <Notification.Content>
      <Notification.Message>
        {record.get('message')}
      </Notification.Message>
      <Notification.Info leftText={time.date} rightText={time.time} />
    </Notification.Content>
  </Notification>)
}
UserMessage.propTypes = {
  record: PropTypes.object.isRequired,
  id: PropTypes.string,
  onDismissMessage: PropTypes.func.isRequired,
}

class VmUserMessages extends React.Component {
  constructor (props) {
    super(props)
    this.state = {
      show: false,
      expand: false,
    }
    this.handleToggle = this.handleToggle.bind(this)
    this.handleExpand = this.handleExpand.bind(this)
  }

  handleToggle () {
    this.setState((prevState) => ({ show: !prevState.show }))
  }

  handleExpand () {
    this.setState((prevState) => ({ expanded: !prevState.expanded }))
  }

  render () {
    const { userMessages, onClearMessages, onDismissMessage } = this.props

    const idPrefix = `usermsgs`

    const messagesCount = userMessages.get('records').size
    const messagesList = messagesCount
      ? userMessages.get('records').map(r => (
        <UserMessage
          key={`msg-${r.get('time')}`}
          record={r}
          id={`${idPrefix}-msg-${r.get('time')}-dropdown`}
          onDismissMessage={() => onDismissMessage(r.get('time'))}
        />
      ))
      : <NotificationDrawer.EmptyState title={msg.noMessages()} />

    const badgeElement = messagesCount === 0
      ? null
      : <span className='badge' id={`${idPrefix}-size`}>{messagesCount}
    return (

        <a className='dropdown-toggle nav-item-iconic' href='#' title={msg.messages()} onClick={hrefWithoutHistory(this.handleToggle)} id={`${idPrefix}-toggle`}>
          <i className={`fa fa-bell ${style['usermessages-icon']}`} />
          {badgeElement}
          <span className='caret' id={`${idPrefix}-caret`} />

        <NotificationDrawer hide={!this.state.show} expanded={this.state.expanded}>
          <NotificationDrawer.Title onCloseClick={this.handleToggle} onExpandClick={this.handleExpand} />
          <NotificationDrawer.PanelBody className={style['panel-body']}>
            {messagesList}
            { messagesCount > 0 && <NotificationDrawer.PanelAction>
              <NotificationDrawer.PanelActionLink data-toggle='clear-all'>
                <Button bsStyle='link' onClick={onClearMessages}>
                  <Icon type='pf' name='close' />
                  { msg.clearAll() }
                </Button>
              </NotificationDrawer.PanelActionLink>
            </NotificationDrawer.PanelAction> }
          </NotificationDrawer.PanelBody>
        </NotificationDrawer>

    )
  }
}
VmUserMessages.propTypes = {
  userMessages: PropTypes.object.isRequired,
  onClearMessages: PropTypes.func.isRequired,
  onDismissMessage: PropTypes.func.isRequired,
}

export default connect(
  (state) => ({
    userMessages: state.userMessages,
  }),
  (dispatch) => ({
    onClearMessages: () => dispatch(clearUserMessages()),
    onDismissMessage: (time) => dispatch(dismissUserMessage({ time })),
  })
)(VmUserMessages)
<|endoftext|>


<commit_before>'use strict';
angular.module('ngAudio', [])
.directive('ngAudio', ['$compile', '$q', 'ngAudio', function($compile, $q, ngAudio) {
    return {
        restrict: 'AEC',
        scope: {
            volume: '=',
            start: '=',
            currentTime: '=',
            loop: '=',
            clickPlay: '=',
            disablePreload:'='
            //ngAudio:'='
        },
        controller: function($scope, $attrs, $element, $timeout) {

            /* Loads the sound from destination */
            var audio;
            function initSound(){
                audio = ngAudio.load($attrs.ngAudio, $scope);
                /* Add audio to local scope for modification with nested inputs */
                $scope.$audio = audio;

                /* Remove watching features for improved performance */
                audio.unbind();
            }

            if (!$scope.disablePreload){
                initSound();
            }

            $element.on('click', function() {
                if ($scope.clickPlay === false) {
                    return;
                }

                if ($scope.disablePreload){
                    initSound();
                }

                /* iOS workaround: Call the play method directly in listener function */
                audio.audio.play();

                /* Set volume to $scope volume if it exists, or default to audio's current value */
                audio.volume = $scope.volume || audio.volume;
                audio.loop = $scope.loop;
                audio.currentTime = $scope.start || 0;

                /* Fixes a bug with Firefox (???) */
                $timeout(function() {
                    audio.play();
                }, 5);
            });

            $element.on('$destroy', function() {
                audio.destroy();
            });
        }
    };
}])

.directive('ngAudioHover', ['$compile', '$q', 'ngAudio', function($compile, $q, ngAudio) {
    return {
        restrict: 'AEC',
        controller: function($scope, $attrs, $element, $timeout) {

            var audio = ngAudio.load($attrs.ngAudioHover, $scope);

            $element.on('mouseover rollover hover', function() {

                /* iOS workaround: Call the play method directly in listener function */
                audio.audio.play();

                audio.volume = $attrs.volumeHover || audio.volume;
                audio.loop = $attrs.loop;
                audio.currentTime = $attrs.startHover || 0;

            });

            $element.on('$destroy', function() {
                audio.destroy();
            });
        }
    };
}])

.service('localAudioFindingService', ['$q', function($q) {

    this.find = function(id) {
        var deferred = $q.defer();
        var $sound = document.getElementById(id);
        if ($sound) {
            deferred.resolve($sound);
        } else {
            deferred.reject(id);
        }

        return deferred.promise;
    };
}])

.service('remoteAudioFindingService', ['$q', function($q) {

    this.find = function(url) {
        var deferred = $q.defer();
        var audio = new Audio();

        audio.addEventListener('error', function() {
            deferred.reject();
        });

        audio.addEventListener('loadstart', function() {
            deferred.resolve(audio);
        });

        // bugfix for chrome...
        setTimeout(function() {
            audio.src = url;
        }, 1);

        return deferred.promise;

    };
}])

.service('cleverAudioFindingService', ['$q', 'localAudioFindingService', 'remoteAudioFindingService', function($q, localAudioFindingService, remoteAudioFindingService) {
    this.find = function(id) {
        var deferred = $q.defer();

        id = id.replace('|', '/');

        localAudioFindingService.find(id)
            .then(deferred.resolve, function() {
                return remoteAudioFindingService.find(id);
            })
            .then(deferred.resolve, deferred.reject);

        return deferred.promise;
    };
}])

.value('ngAudioGlobals', {
    muting: false,
    songmuting: false,
    performance: 25,
    unlock: true
})

.factory('NgAudioObject', ['cleverAudioFindingService', '$rootScope', '$interval', '$timeout', 'ngAudioGlobals', function(cleverAudioFindingService, $rootScope, $interval, $timeout, ngAudioGlobals) {
    return function(id, scope) {

        var $audioWatch,
            $intervalWatch,
            $willPlay = false,
            $willPause = false,
            $willRestart = false,
            $willChangePlaybackRate = false,
            $newPlaybackRate = false,
            $volumeToSet,
            $looping,
            $isMuting = false,
            $observeProperties = true,
            $destroyed = false,
            $scope = scope || $rootScope,
            audio,
            audioObject = this;

        this.id = id;
        this.safeId = id.replace('/', '|');
        this.loop = 0;

        this.unbind = function() {
            $observeProperties = false;
        };

        this.play = function() {
            $willPlay = true;
            return this;
        };

        var completeListeners = [];
        this.complete = function(callback){
            completeListeners.push(callback);
        }

        this.pause = function() {
            $willPause = true;
        };

        this.restart = function() {
            $willRestart = true;
        };

        this.stop = function() {
            this.restart();
        };

        this.setVolume = function(volume) {
            $volumeToSet = volume;
        };

        this.setPlaybackRate = function(rate) {
            $newPlaybackRate = rate;
            $willChangePlaybackRate = true;
        };

        this.setMuting = function(muting) {
            $isMuting = muting;
        };

        this.setProgress = function(progress) {
            if (audio && audio.duration && isFinite(progress)) {
                audio.currentTime = audio.duration * progress;
            }
        };

        this.setCurrentTime = function(currentTime) {
            if (audio && audio.duration) {
                audio.currentTime = currentTime;
            }
        };

        this.destroy = $destroy;

        $scope.$on('$destroy', function() {
            $destroy();
        });

        function $destroy() {
            if (!$destroyed) {
                if (interval) {
                    $interval.cancel(interval);
                }
                if ($intervalWatch) {
                    $intervalWatch();
                }
                if ($audioWatch) {
                    $audioWatch();
                }
                $destroyed = true;
            }
        }

        function $setWatch() {
            if ($destroyed) {
                return;
            }
            $audioWatch = $scope.$watch(function() {
                return {
                    volume: audioObject.volume,
                    currentTime: audioObject.currentTime,
                    progress: audioObject.progress,
                    muting: audioObject.muting,
                    loop: audioObject.loop,
                    playbackRate: audioObject.playbackRate
                };
            }, function(newValue, oldValue) {
                //console.log("ngaudio watch callback for: " + audioObject.id);
                if (newValue.currentTime !== oldValue.currentTime) {
                    audioObject.setCurrentTime(newValue.currentTime);
                }

                if (newValue.progress !== oldValue.progress) {
                    audioObject.setProgress(newValue.progress);
                }
                if (newValue.volume !== oldValue.volume) {
                    audioObject.setVolume(newValue.volume);
                }

                if (newValue.playbackRate !== oldValue.playbackRate) {
                    audioObject.setPlaybackRate(newValue.playbackRate);
                }

                $looping = newValue.loop;

                if (newValue.muting !== oldValue.muting) {
                    audioObject.setMuting(newValue.muting);
                }
            }, true);
        }

        cleverAudioFindingService.find(id)
            .then(function(nativeAudio) {
                audio = nativeAudio;
                if (ngAudioGlobals.unlock) {

                    window.addEventListener("click",function twiddle(){
                        audio.play();
                        audio.pause();
                        window.removeEventListener("click",twiddle);
                    });

                }

                audio.addEventListener('canplay', function() {
                    audioObject.canPlay = true;
                });

            }, function(error) {
                audioObject.error = true;
                console.warn(error);
            });

        var interval = $interval(checkWatchers, ngAudioGlobals.performance);
        $intervalWatch = $scope.$watch(function(){
            return ngAudioGlobals.performance;
        },function(){
            $interval.cancel(interval);
            interval = $interval(checkWatchers, ngAudioGlobals.performance);
        })

        function checkWatchers() {
            if ($audioWatch) {
                $audioWatch();
            }
            if (audio) {

                if ($isMuting || ngAudioGlobals.isMuting) {
                    audio.volume = 0;
                } else {
                    audio.volume = audioObject.volume !== undefined ? audioObject.volume : 1;
                }

                if ($willPlay) {
                    audio.play();
                    $willPlay = false;
                }

                if ($willRestart) {
                    audio.pause();
                    audio.currentTime = 0;
                    $willRestart = false;
                }

                if ($willPause) {
                    audio.pause();
                    $willPause = false;
                }

                if ($willChangePlaybackRate) {
                    audio.playbackRate = $newPlaybackRate;
                    $willChangePlaybackRate = false;
                }

                if ($volumeToSet) {
                    audio.volume = $volumeToSet;
                    $volumeToSet = undefined;
                }

                if ($observeProperties) {
                    audioObject.currentTime = audio.currentTime;
                    audioObject.duration = audio.duration;
                    audioObject.remaining = audio.duration - audio.currentTime;
                    audioObject.progress = audio.currentTime / audio.duration;
                    audioObject.paused = audio.paused;
                    audioObject.src = audio.src;

                    if (audioObject.currentTime >= audioObject.duration) {
                        completeListeners.forEach(function(listener){
                            listener(audioObject);
                        })
                    }

                    if ($looping && audioObject.currentTime >= audioObject.duration) {
                        if ($looping !== true) {
                            $looping--;
                            audioObject.loop--;
                            // if (!$looping) return;
                        }
                        audioObject.setCurrentTime(0);
                        audioObject.play();

                    }
                }

                if (!$isMuting && !ngAudioGlobals.isMuting) {
                    audioObject.volume = audio.volume;
                }

                audioObject.audio = audio;
            }

            $setWatch();
        }
    };
}])
.service('ngAudio', ['NgAudioObject', 'ngAudioGlobals', function(NgAudioObject, ngAudioGlobals) {
    this.play = function(id, scope) {

        var audio = new NgAudioObject(id, scope);
        audio.play();
        return audio;
    };

    this.load = function(id, scope) {
        return new NgAudioObject(id, scope);
    };

    this.mute = function() {
        ngAudioGlobals.muting = true;
    };

    this.unmute = function() {
        ngAudioGlobals.muting = false;
    };

    this.toggleMute = function() {
        ngAudioGlobals.muting = !ngAudioGlobals.muting;
    };

    this.setUnlock = function(unlock) {
      ngAudioGlobals.unlock = unlock;
    };
}])
.filter("trackTime", function(){
    /* Conveniently takes a number and returns the track time */

    return function(input){

        var totalSec = Math.floor(input | 0);

        var output = "";
        var hours = 0;
        var minutes = 0;
        var seconds = 0;

        if (totalSec > 3599) {

            hours = Math.floor(totalSec / 3600);
            minutes = Math.floor((totalSec - (hours * 3600)) / 60);
            seconds = (totalSec - ((minutes * 60) + (hours * 3600)));

            if (hours.toString().length == 1) {
                hours = "0" + (Math.floor(totalSec / 3600)).toString();
            }

            if (minutes.toString().length == 1) {
                minutes = "0" + (Math.floor((totalSec - (hours * 3600)) / 60)).toString();
            }

            if (seconds.toString().length == 1) {
                seconds = "0" + (totalSec - ((minutes * 60) + (hours * 3600))).toString();
            }

            output = hours + ":" + minutes + ":" + seconds;

        } else if (totalSec > 59) {

            minutes = Math.floor(totalSec / 60);
            seconds = totalSec - (minutes * 60);

            if (minutes.toString().length == 1) {
                 minutes = "0" + (Math.floor(totalSec / 60)).toString();
            }

            if (seconds.toString().length == 1) {
                 seconds = "0" + (totalSec - (minutes * 60)).toString();
            }

            output = minutes + ":" + seconds;

        } else {

            seconds = totalSec;

            if (seconds.toString().length == 1) {
                seconds = "0" + (totalSec).toString();
            }

            output = totalSec + "s";

        }

        if (Number.isNaN(output)){
            debugger;
        }

        return output;
    }
});
<commit_msg>Better fix for pr91, fix twiddle being called too early.<commit_after>'use strict';
angular.module('ngAudio', [])
.directive('ngAudio', ['$compile', '$q', 'ngAudio', function($compile, $q, ngAudio) {
    return {
        restrict: 'AEC',
        scope: {
            volume: '=',
            start: '=',
            currentTime: '=',
            loop: '=',
            clickPlay: '=',
            disablePreload:'='
            //ngAudio:'='
        },
        controller: function($scope, $attrs, $element, $timeout) {

            /* Loads the sound from destination */
            var audio;
            function initSound(){
                audio = ngAudio.load($attrs.ngAudio, $scope);
                /* Add audio to local scope for modification with nested inputs */
                $scope.$audio = audio;

                /* Remove watching features for improved performance */
                audio.unbind();
            }

            if (!$scope.disablePreload){
                initSound();
            }

            $element.on('click', function() {
                if ($scope.clickPlay === false) {
                    return;
                }

                if ($scope.disablePreload){
                    initSound();
                }

                /* iOS workaround: Call the play method directly in listener function */
                audio.audio.play();

                /* Set volume to $scope volume if it exists, or default to audio's current value */
                audio.volume = $scope.volume || audio.volume;
                audio.loop = $scope.loop;
                audio.currentTime = $scope.start || 0;

                /* Fixes a bug with Firefox (???) */
                $timeout(function() {
                    audio.play();
                }, 5);
            });

            $element.on('$destroy', function() {
                audio.destroy();
            });
        }
    };
}])

.directive('ngAudioHover', ['$compile', '$q', 'ngAudio', function($compile, $q, ngAudio) {
    return {
        restrict: 'AEC',
        controller: function($scope, $attrs, $element, $timeout) {

            var audio = ngAudio.load($attrs.ngAudioHover, $scope);

            $element.on('mouseover rollover hover', function() {

                /* iOS workaround: Call the play method directly in listener function */
                audio.audio.play();

                audio.volume = $attrs.volumeHover || audio.volume;
                audio.loop = $attrs.loop;
                audio.currentTime = $attrs.startHover || 0;

            });

            $element.on('$destroy', function() {
                audio.destroy();
            });
        }
    };
}])

.service('localAudioFindingService', ['$q', function($q) {

    this.find = function(id) {
        var deferred = $q.defer();
        var $sound = document.getElementById(id);
        if ($sound) {
            deferred.resolve($sound);
        } else {
            deferred.reject(id);
        }

        return deferred.promise;
    };
}])

.service('remoteAudioFindingService', ['$q', function($q) {

    this.find = function(url) {
        var deferred = $q.defer();
        var audio = new Audio();

        audio.addEventListener('error', function() {
            deferred.reject();
        });

        audio.addEventListener('loadstart', function() {
            deferred.resolve(audio);
        });

        // bugfix for chrome...
        setTimeout(function() {
            audio.src = url;
        }, 1);

        return deferred.promise;

    };
}])

.service('cleverAudioFindingService', ['$q', 'localAudioFindingService', 'remoteAudioFindingService', function($q, localAudioFindingService, remoteAudioFindingService) {
    this.find = function(id) {
        var deferred = $q.defer();

        id = id.replace('|', '/');

        localAudioFindingService.find(id)
            .then(deferred.resolve, function() {
                return remoteAudioFindingService.find(id);
            })
            .then(deferred.resolve, deferred.reject);

        return deferred.promise;
    };
}])

.value('ngAudioGlobals', {
    muting: false,
    songmuting: false,
    performance: 25,
    unlock: true
})

.factory('NgAudioObject', ['cleverAudioFindingService', '$rootScope', '$interval', '$timeout', 'ngAudioGlobals', function(cleverAudioFindingService, $rootScope, $interval, $timeout, ngAudioGlobals) {
    return function(id, scope) {

        function twiddle(){
            audio.play();
            audio.pause();
            window.removeEventListener("click",twiddle);
        }

        var $audioWatch,
            $intervalWatch,
            $willPlay = false,
            $willPause = false,
            $willRestart = false,
            $willChangePlaybackRate = false,
            $newPlaybackRate = false,
            $volumeToSet,
            $looping,
            $isMuting = false,
            $observeProperties = true,
            $destroyed = false,
            $scope = scope || $rootScope,
            audio,
            audioObject = this;

        this.id = id;
        this.safeId = id.replace('/', '|');
        this.loop = 0;

        this.unbind = function() {
            $observeProperties = false;
        };

        this.play = function() {
            $willPlay = true;
            return this;
        };

        var completeListeners = [];
        this.complete = function(callback){
            completeListeners.push(callback);
        }

        this.pause = function() {
            $willPause = true;
        };

        this.restart = function() {
            $willRestart = true;
        };

        this.stop = function() {
            this.restart();
        };

        this.setVolume = function(volume) {
            $volumeToSet = volume;
        };

        this.setPlaybackRate = function(rate) {
            $newPlaybackRate = rate;
            $willChangePlaybackRate = true;
        };

        this.setMuting = function(muting) {
            $isMuting = muting;
        };

        this.setProgress = function(progress) {
            if (audio && audio.duration && isFinite(progress)) {
                audio.currentTime = audio.duration * progress;
            }
        };

        this.setCurrentTime = function(currentTime) {
            if (audio && audio.duration) {
                audio.currentTime = currentTime;
            }
        };

        this.destroy = $destroy;

        $scope.$on('$destroy', function() {
            $destroy();
        });

        function $destroy() {
            if (!$destroyed) {
                if (interval) {
                    $interval.cancel(interval);
                }
                if ($intervalWatch) {
                    $intervalWatch();
                }
                if ($audioWatch) {
                    $audioWatch();
                }
                $destroyed = true;
            }
        }

        function $setWatch() {
            if ($destroyed) {
                return;
            }
            $audioWatch = $scope.$watch(function() {
                return {
                    volume: audioObject.volume,
                    currentTime: audioObject.currentTime,
                    progress: audioObject.progress,
                    muting: audioObject.muting,
                    loop: audioObject.loop,
                    playbackRate: audioObject.playbackRate
                };
            }, function(newValue, oldValue) {
                //console.log("ngaudio watch callback for: " + audioObject.id);
                if (newValue.currentTime !== oldValue.currentTime) {
                    audioObject.setCurrentTime(newValue.currentTime);
                }

                if (newValue.progress !== oldValue.progress) {
                    audioObject.setProgress(newValue.progress);
                }
                if (newValue.volume !== oldValue.volume) {
                    audioObject.setVolume(newValue.volume);
                }

                if (newValue.playbackRate !== oldValue.playbackRate) {
                    audioObject.setPlaybackRate(newValue.playbackRate);
                }

                $looping = newValue.loop;

                if (newValue.muting !== oldValue.muting) {
                    audioObject.setMuting(newValue.muting);
                }
            }, true);
        }

        cleverAudioFindingService.find(id)
            .then(function(nativeAudio) {
                audio = nativeAudio;
                if (ngAudioGlobals.unlock) {

                    window.addEventListener("click", twiddle);

                    audio.addEventListener('playing', function() {
                        window.removeEventListener("click",twiddle);
                    });

                }

                audio.addEventListener('canplay', function() {
                    audioObject.canPlay = true;
                });

            }, function(error) {
                audioObject.error = true;
                console.warn(error);
            });

        var interval = $interval(checkWatchers, ngAudioGlobals.performance);
        $intervalWatch = $scope.$watch(function(){
            return ngAudioGlobals.performance;
        },function(){
            $interval.cancel(interval);
            interval = $interval(checkWatchers, ngAudioGlobals.performance);
        })

        function checkWatchers() {
            if ($audioWatch) {
                $audioWatch();
            }
            if (audio) {

                if ($isMuting || ngAudioGlobals.isMuting) {
                    audio.volume = 0;
                } else {
                    audio.volume = audioObject.volume !== undefined ? audioObject.volume : 1;
                }

                if ($willPlay) {
                    audio.play();
                    $willPlay = false;
                }

                if ($willRestart) {
                    audio.pause();
                    audio.currentTime = 0;
                    $willRestart = false;
                }

                if ($willPause) {
                    audio.pause();
                    $willPause = false;
                }

                if ($willChangePlaybackRate) {
                    audio.playbackRate = $newPlaybackRate;
                    $willChangePlaybackRate = false;
                }

                if ($volumeToSet) {
                    audio.volume = $volumeToSet;
                    $volumeToSet = undefined;
                }

                if ($observeProperties) {
                    audioObject.currentTime = audio.currentTime;
                    audioObject.duration = audio.duration;
                    audioObject.remaining = audio.duration - audio.currentTime;
                    audioObject.progress = audio.currentTime / audio.duration;
                    audioObject.paused = audio.paused;
                    audioObject.src = audio.src;

                    if (audioObject.currentTime >= audioObject.duration) {
                        completeListeners.forEach(function(listener){
                            listener(audioObject);
                        })
                    }

                    if ($looping && audioObject.currentTime >= audioObject.duration) {
                        if ($looping !== true) {
                            $looping--;
                            audioObject.loop--;
                            // if (!$looping) return;
                        }
                        audioObject.setCurrentTime(0);
                        audioObject.play();

                    }
                }

                if (!$isMuting && !ngAudioGlobals.isMuting) {
                    audioObject.volume = audio.volume;
                }

                audioObject.audio = audio;
            }

            $setWatch();
        }
    };
}])
.service('ngAudio', ['NgAudioObject', 'ngAudioGlobals', function(NgAudioObject, ngAudioGlobals) {
    this.play = function(id, scope) {

        var audio = new NgAudioObject(id, scope);
        audio.play();
        return audio;
    };

    this.load = function(id, scope) {
        return new NgAudioObject(id, scope);
    };

    this.mute = function() {
        ngAudioGlobals.muting = true;
    };

    this.unmute = function() {
        ngAudioGlobals.muting = false;
    };

    this.toggleMute = function() {
        ngAudioGlobals.muting = !ngAudioGlobals.muting;
    };

    this.setUnlock = function(unlock) {
      ngAudioGlobals.unlock = unlock;
    };
}])
.filter("trackTime", function(){
    /* Conveniently takes a number and returns the track time */

    return function(input){

        var totalSec = Math.floor(input | 0);

        var output = "";
        var hours = 0;
        var minutes = 0;
        var seconds = 0;

        if (totalSec > 3599) {

            hours = Math.floor(totalSec / 3600);
            minutes = Math.floor((totalSec - (hours * 3600)) / 60);
            seconds = (totalSec - ((minutes * 60) + (hours * 3600)));

            if (hours.toString().length == 1) {
                hours = "0" + (Math.floor(totalSec / 3600)).toString();
            }

            if (minutes.toString().length == 1) {
                minutes = "0" + (Math.floor((totalSec - (hours * 3600)) / 60)).toString();
            }

            if (seconds.toString().length == 1) {
                seconds = "0" + (totalSec - ((minutes * 60) + (hours * 3600))).toString();
            }

            output = hours + ":" + minutes + ":" + seconds;

        } else if (totalSec > 59) {

            minutes = Math.floor(totalSec / 60);
            seconds = totalSec - (minutes * 60);

            if (minutes.toString().length == 1) {
                 minutes = "0" + (Math.floor(totalSec / 60)).toString();
            }

            if (seconds.toString().length == 1) {
                 seconds = "0" + (totalSec - (minutes * 60)).toString();
            }

            output = minutes + ":" + seconds;

        } else {

            seconds = totalSec;

            if (seconds.toString().length == 1) {
                seconds = "0" + (totalSec).toString();
            }

            output = totalSec + "s";

        }

        if (Number.isNaN(output)){
            debugger;
        }

        return output;
    }
});
<|endoftext|>


<commit_before>#! /usr/bin/env node
/* @flow */

import 'babel-polyfill'
import _ from 'lodash'
import chalk from 'chalk'
import path from 'path'
import program from 'commander'
import cliui from 'cliui'

import { Ouroboros, File } from 'ouroboros'

const ui = cliui({ width: 80 })

function parseStrings (value) {
  return value.split(/\s*,\s*/)
}

function parseNumber (value) {
  return parseInt(value, 10)
}

function parseNumbers (value) {
  return parseStrings(value).map(x => parseNumber(x))
}

function cloneOptions (options) {
  const newOptions = {}

  for (const property in options) {
    const value = options[property]
    if (value !== undefined) {
      newOptions[property] = value
    }
  }

  return newOptions
}

const command = async (inputs, env) => {
  const commands = env.name().split(',')
  const { saveEvents = [], verbose = false, ...options } = cloneOptions(env.opts())
  commands.unshift('load')
  commands.push('save')

  for (const filePath of inputs) {
    const events = []
    const ouroboros = await Ouroboros.create(path.resolve(filePath), options)
    ouroboros
      .on('log', event => {
        const nameText = event.name ? `[${event.name}] ` : ''
        const typeText = event.category ? `${event.category}: ` : ''
        const text = `${nameText}${typeText}${event.text.replace('\n', ' ')}`
        switch (event.severity) {
          case 'error':
            console.error(chalk.red(text))
            break
          case 'warning':
            console.warn(chalk.yellow(text))
            break
          case 'trace':
            console.warn(chalk.blue(text))
            break
          default:
            console.info(text)
            break
        }
      })
      .on('action', event => {
        if (verbose) {
          const triggerText = event.triggers.length !== 0 ? ` triggered by updates to ${event.triggers}` : ''
          console.log(`[${event.rule}] Evaluating ${event.action} action${triggerText}`)
        }
      })
      .on('command', event => {
        console.log(`[${event.rule}] Executing \`${event.command}\``)
      })
      .on('fileDeleted', event => {
        console.log(`Deleting \`${event.file}\``)
      })

    for (const type of saveEvents) {
      ouroboros.on(type, event => { events.push(event) })
    }

    await ouroboros.run(...commands)

    if (saveEvents.length !== 0) {
      const eventFilePath = ouroboros.resolvePath('$dir/$name-events.yaml')
      await File.safeDump(eventFilePath, { types: saveEvents, events })
    }
  }
}

program
  .version('0.0.0')
  .description('An experimental circular builder for LaTeX')

Ouroboros.getOptionDefinitions().then(definitions => {
  function loadOptions (pc) {
    for (const name in definitions) {
      const option = definitions[name]
      if (option.status === 'legacy') continue
      const commands = pc.name().split(',')

      if (!option.commands.some(command => commands.includes(command))) continue

      const prefix = (option.type === 'boolean' && option.defaultValue) ? 'no-' : ''
      const flagList = [].concat(option.aliases || [], name)
        .map(name => name.length === 1 ? `-${name}` : `--${prefix}${_.kebabCase(name)}`)
        .join(', ')
      const flags = (option.type === 'boolean') ? flagList : `${flagList} <${name}>`

      switch (option.type) {
        case 'string':
          if (option.values) {
            pc = pc.option(flags, option.description, new RegExp(`^(${option.values.join('|')})$`), option.defaultValue)
          } else {
            pc = pc.option(flags, option.description, option.defaultValue)
          }
          break
        case 'strings':
          pc = pc.option(flags, option.description, parseStrings, option.defaultValue)
          break
        case 'number':
          pc = pc.option(flags, option.description, parseNumber, option.defaultValue)
          break
        case 'numbers':
          pc = pc.option(flags, option.description, parseNumbers, option.defaultValue)
          break
        case 'boolean':
          pc = pc.option(flags, option.description)
          break
      }
    }

    pc = pc.option('--save-events <saveEvents>', 'List of event types to save for test usage.', parseStrings, [])
    pc = pc.option('-v, --verbose', 'Be verbose in command output.')

    return pc
  }

  loadOptions(program
    .command('build [inputs...]')
    .alias('b')
    .description('Build the inputs.'))
    .action(command)

  loadOptions(program
    .command('clean [inputs...]')
    .alias('c')
    .description('Clean up after a previous build.'))
    .action(command)

  loadOptions(program
    .command('log [inputs...]')
    .alias('l')
    .description('Report messages from any logs.'))
    .action(command)

  loadOptions(program
    .command('graph [inputs...]')
    .alias('g')
    .description('Create a dependency graph from a previous build.'))
    .action(command)

  loadOptions(program
    .command('build,clean [inputs...]')
    .alias('bc')
    .description('Build the inputs and then clean up.'))
    .action(command)

  loadOptions(program
    .command('build,log [inputs...]')
    .alias('bl')
    .description('Build the inputs and report messages from any logs.'))
    .action(command)

  loadOptions(program
    .command('build,log,clean [inputs...]')
    .alias('blc')
    .description('Build the inputs, report messages from any logs, and then clean up.'))
    .action(command)

  program
    .command('rules')
    .description('List available rules')
    .option('--command <command>', 'List only rules that apply to a specific command.', null)
    .action(async (env) => {
      const { command } = cloneOptions(env.opts())
      const ouroboros = await Ouroboros.create('foo.tex')
      ui.div(ouroboros.getAvailableRules(command).map(rule => `${rule.name}\t  ${rule.description}`).join('\n'))
      console.log(ui.toString())
    })

  program
    .parse(process.argv)
}, error => { console.log(error) })
<commit_msg>Update option parsing<commit_after>#! /usr/bin/env node
/* @flow */

import 'babel-polyfill'
import _ from 'lodash'
import chalk from 'chalk'
import path from 'path'
import program from 'commander'
import cliui from 'cliui'

import { Ouroboros, File } from 'ouroboros'

const ui = cliui({ width: 80 })

function parseStrings (value) {
  return value.split(/\s*,\s*/)
}

function parseNumber (value) {
  return parseInt(value, 10)
}

function parseNumbers (value) {
  return parseStrings(value).map(x => parseNumber(x))
}

function cloneOptions (options) {
  const newOptions = {}

  for (const property in options) {
    const value = options[property]
    if (value !== undefined) {
      newOptions[property] = value
    }
  }

  return newOptions
}

const command = async (inputs, env) => {
  const commands = env.name().split(',')
  const { saveEvents = [], verbose = false, ...options } = cloneOptions(env.opts())
  commands.unshift('load')
  commands.push('save')

  for (const filePath of inputs) {
    const events = []
    const ouroboros = await Ouroboros.create(path.resolve(filePath), options)
    ouroboros
      .on('log', event => {
        const nameText = event.name ? `[${event.name}] ` : ''
        const typeText = event.category ? `${event.category}: ` : ''
        const text = `${nameText}${typeText}${event.text.replace('\n', ' ')}`
        switch (event.severity) {
          case 'error':
            console.error(chalk.red(text))
            break
          case 'warning':
            console.warn(chalk.yellow(text))
            break
          case 'trace':
            console.warn(chalk.blue(text))
            break
          default:
            console.info(text)
            break
        }
      })
      .on('action', event => {
        if (verbose) {
          const triggerText = event.triggers.length !== 0 ? ` triggered by updates to ${event.triggers}` : ''
          console.log(`[${event.rule}] Evaluating ${event.action} action${triggerText}`)
        }
      })
      .on('command', event => {
        console.log(`[${event.rule}] Executing \`${event.command}\``)
      })
      .on('fileDeleted', event => {
        console.log(`Deleting \`${event.file}\``)
      })

    for (const type of saveEvents) {
      ouroboros.on(type, event => { events.push(event) })
    }

    await ouroboros.run(...commands)

    if (saveEvents.length !== 0) {
      const eventFilePath = ouroboros.resolvePath('$dir/$name-events.yaml')
      await File.safeDump(eventFilePath, { types: saveEvents, events })
    }
  }
}

program
  .version('0.0.0')
  .description('An experimental circular builder for LaTeX')

Ouroboros.getOptionDefinitions().then(definitions => {
  function loadOptions (pc) {
    for (const option of definitions) {
      const commands = pc.name().split(',')

      if (!option.commands.some(command => commands.includes(command))) continue

      const prefix = (option.type === 'boolean' && option.defaultValue) ? 'no-' : ''
      const flagList = [].concat((option.aliases || []).filter(alias => alias.length === 1), option.name)
        .map(name => name.length === 1 ? `-${name}` : `--${prefix}${_.kebabCase(name)}`)
        .join(', ')
      const flags = (option.type === 'boolean') ? flagList : `${flagList} <${option.name}>`

      switch (option.type) {
        case 'string':
          if (option.values) {
            pc = pc.option(flags, option.description, new RegExp(`^(${option.values.join('|')})$`), option.defaultValue)
          } else {
            pc = pc.option(flags, option.description, option.defaultValue)
          }
          break
        case 'strings':
          pc = pc.option(flags, option.description, parseStrings, option.defaultValue)
          break
        case 'number':
          pc = pc.option(flags, option.description, parseNumber, option.defaultValue)
          break
        case 'numbers':
          pc = pc.option(flags, option.description, parseNumbers, option.defaultValue)
          break
        case 'boolean':
          pc = pc.option(flags, option.description)
          break
      }
    }

    pc = pc.option('--save-events <saveEvents>', 'List of event types to save for test usage.', parseStrings, [])
    pc = pc.option('-v, --verbose', 'Be verbose in command output.')

    return pc
  }

  loadOptions(program
    .command('build [inputs...]')
    .alias('b')
    .description('Build the inputs.'))
    .action(command)

  loadOptions(program
    .command('clean [inputs...]')
    .alias('c')
    .description('Clean up after a previous build.'))
    .action(command)

  loadOptions(program
    .command('log [inputs...]')
    .alias('l')
    .description('Report messages from any logs.'))
    .action(command)

  loadOptions(program
    .command('graph [inputs...]')
    .alias('g')
    .description('Create a dependency graph from a previous build.'))
    .action(command)

  loadOptions(program
    .command('build,clean [inputs...]')
    .alias('bc')
    .description('Build the inputs and then clean up.'))
    .action(command)

  loadOptions(program
    .command('build,log [inputs...]')
    .alias('bl')
    .description('Build the inputs and report messages from any logs.'))
    .action(command)

  loadOptions(program
    .command('build,log,clean [inputs...]')
    .alias('blc')
    .description('Build the inputs, report messages from any logs, and then clean up.'))
    .action(command)

  program
    .command('rules')
    .description('List available rules')
    .option('--command <command>', 'List only rules that apply to a specific command.', null)
    .action(async (env) => {
      const { command } = cloneOptions(env.opts())
      const ouroboros = await Ouroboros.create('foo.tex')
      ui.div(ouroboros.getAvailableRules(command).map(rule => `${rule.name}\t  ${rule.description}`).join('\n'))
      console.log(ui.toString())
    })

  program
    .parse(process.argv)
}, error => { console.log(error) })
<|endoftext|>


<commit_before>/*
 * Create a Rekit project. Boilerplates are provided by Rekit plugins.
 *
 * To create a Rekit project:
 *  - If options.source is a local folder, then copy content from it (excepts node_modules and .git folders),
 *    if it's a git url, clone it.
 *  - Otherwise, looks for project type registry from https://github.com/supnate/rekit-registry/appTypes.json,
 *    clone the repo to the project folder.
 *  - Execute postCreate.js
 *
*/

const path = require('path');
const https = require('https');
const fs = require('fs-extra');
const download = require('download-git-repo');
const os = require('os');
const config = require('./config');

function create(options) {
  console.log('Creating app: ', options);

  if (!options.status)
    options.status = (code, msg) => {
      console.log(msg);
    };
  if (!options.type) options.type = 'rekit-react';

  const prjDir = path.join(options.location || process.cwd(), options.name);
  return new Promise(async (resolve, reject) => {
    try {
      if (fs.existsSync(prjDir)) {
        reject('FOLDER_EXISTS');
        return;
      }
      fs.mkdirSync(prjDir);
      let gitRepo;
      if (options.source) {
        if (/^https?:/.test(options.source)) {
          // It's a git repo
          gitRepo = options.source;
        } else {
          // It's a local folder
          const srcDir = path.isAbsolute(options.source) ? options.source : path.join(process.cwd(), options.source);
          options.status('CREATE_APP_COPY_FILES', `Copy files from ${srcDir}...`);
          await fs.copy(srcDir, prjDir, {
            filter: src => !/\/(\.git|node_modules\/|node_modules$)/.test(src),
          });
        }
      } else if (options.type) {
        // Get gitRepo
        options.status('QUERY_APP_TYPES_GIT_REPO', `Looking for the git repo for app type ${options.type}...`);
        const appTypes = await getAppTypes();
        if (!appTypes[options.type]) reject('APP_TYPE_NOT_SUPPORTED');
        gitRepo = appTypes[options.type].repo;
      } else {
        await fs.remove(prjDir);
        reject('NO_SOURCE_OR_APP_TYPE');
      }

      if (gitRepo) {
        options.status('CLONE_PROJECT', `Cloning project from ${gitRepo}...`);
        await cloneRepo(gitRepo, prjDir);
      }

      postCreate(prjDir, options);
      options.status('CREATION_SUCCESS', '😃App creation success.');
      resolve();
    } catch (err) {
      reject(err);
    }
  });
}

function getAppTypes() {
  return new Promise((resolve, reject) => {
    https
      .get('https://raw.githubusercontent.com/supnate/rekit-registry/master/appTypes.json', resp => {
        let data = '';

        // A chunk of data has been recieved.
        resp.on('data', chunk => {
          data += chunk;
        });

        // The whole response has been received. Print out the result.
        resp.on('end', () => {
          const appTypes = JSON.parse(data);
          resolve(appTypes);
        });
      })
      .on('error', err => {
        console.log('Failed to get app types: ', err);
        reject('GET_APP_TYPES_FAILED');
      });
  });
}

function cloneRepo(gitRepo, prjDir) {
  console.log('Cloning repp: ', gitRepo);
  return new Promise((resolve, reject) => {
    const isDirect = /^https?:/.test(gitRepo);
    download(isDirect ? `direct:${gitRepo}` : gitRepo, prjDir, { clone: isDirect }, err => {
      if (err) {
        console.log('Failed to download the boilerplate. The project was not created. Please check and retry.');
        console.log(err);
        reject('CLONE_REPO_FAILED');
        return;
      }
      resolve();
    });
  });
}

function postCreate(prjDir, options) {
  const postCreateScript = path.join(prjDir, 'postCreate.js');
  if (fs.existsSync(postCreateScript)) {
    options.status('POST_CREATE', 'Executing post create script...');
    require(postCreateScript)(options);
    fs.removeSync(postCreateScript);
  }
}

function syncAppRegistryRepo() {
  const registryDir = path.join(os.homedir(), '.rekit/app-registry');
  return new Promise((resolve, reject) => {
    const appRegistry = config.getAppRegistry();
    const arr = appRegistry.split('/');
    const owner = arr[0];
    const arr2 = arr[1].split('#');
    const repo = arr2[0];
    const branch = arr2[1] || 'master';
    console.log(owner, repo, branch);
    https
      .get(
        `https://api.github.com/repos/${owner}/${repo}/git/refs/heads/${branch}`,
        { headers: { 'User-Agent': 'rekit-core' } },
        resp => {
          let data = '';

          // A chunk of data has been recieved.
          resp.on('data', chunk => {
            data += chunk;
          });

          // The whole response has been received. Print out the result.
          resp.on('end', () => {
            try {
              const ref = JSON.parse(data);
              const lastCommit = ref.object.sha;
              if (!fs.existsSync(path.join(registryDir, lastCommit))) {
                fs.removeSync(registryDir);
                cloneRepo(appRegistry, registryDir)
                  .then(() => {
                    fs.writeFileSync(path.join(registryDir, lastCommit), '');
                    resolve(lastCommit);
                  })
                  .catch(reject);
              } else {
                resolve();
              }
            } catch (err) {
              reject(err);
            }
          });
        }
      )
      .on('error', err => {
        console.log('Failed to get last commit of app registry: ', err);
        reject('FAILED_CHECK_APP_REGISTRY_LATEST_COMMIT');
      });
  });
}

module.exports = create;
module.exports.syncAppRegistryRepo = syncAppRegistryRepo;
syncAppRegistryRepo();<commit_msg>Get app types from local config.<commit_after>/*
 * Create a Rekit project. Boilerplates are provided by Rekit plugins.
 *
 * To create a Rekit project:
 *  - If options.source is a local folder, then copy content from it (excepts node_modules and .git folders),
 *    if it's a git url, clone it.
 *  - Otherwise, looks for project type registry from https://github.com/supnate/rekit-registry/appTypes.json,
 *    clone the repo to the project folder.
 *  - Execute postCreate.js
 *
*/

const path = require('path');
const https = require('https');
const fs = require('fs-extra');
const download = require('download-git-repo');
const config = require('./config');
const paths = require('./paths');

function create(options) {
  console.log('Creating app: ', options);

  if (!options.status)
    options.status = (code, msg) => {
      console.log(msg);
    };
  if (!options.type) options.type = 'rekit-react';

  const prjDir = path.join(options.location || process.cwd(), options.name);
  return new Promise(async (resolve, reject) => {
    try {
      if (fs.existsSync(prjDir)) {
        reject('FOLDER_EXISTS');
        return;
      }
      fs.mkdirSync(prjDir);
      let gitRepo;
      if (options.source) {
        if (/^https?:/.test(options.source)) {
          // It's a git repo
          gitRepo = options.source;
        } else {
          // It's a local folder
          const srcDir = path.isAbsolute(options.source) ? options.source : path.join(process.cwd(), options.source);
          options.status('CREATE_APP_COPY_FILES', `Copy files from ${srcDir}...`);
          await fs.copy(srcDir, prjDir, {
            filter: src => !/\/(\.git|node_modules\/|node_modules$)/.test(src),
          });
        }
      } else if (options.type) {
        // Get gitRepo
        options.status('QUERY_APP_TYPES_GIT_REPO', `Looking for the git repo for app type ${options.type}...`);
        const appTypes = await getAppTypes();
        if (!appTypes[options.type]) reject('APP_TYPE_NOT_SUPPORTED');
        gitRepo = appTypes[options.type].repo;
      } else {
        await fs.remove(prjDir);
        reject('NO_SOURCE_OR_APP_TYPE');
      }

      if (gitRepo) {
        options.status('CLONE_PROJECT', `Cloning project from ${gitRepo}...`);
        await cloneRepo(gitRepo, prjDir);
      }

      postCreate(prjDir, options);
      options.status('CREATION_SUCCESS', '😃App creation success.');
      resolve();
    } catch (err) {
      reject(err);
    }
  });
}

function getAppTypes() {
  return new Promise((resolve, reject) => {
    syncAppRegistryRepo()
      .then(() => {
        resolve(fs.readJsonSync(paths.configFile('app-registry/appTypes.json')));
      })
      .catch(err => {
        console.log('Failed to get app types: ', err);
        reject('GET_APP_TYPES_FAILED');
      });
  });
}

function cloneRepo(gitRepo, prjDir) {
  console.log('Cloning repp: ', gitRepo);
  return new Promise((resolve, reject) => {
    const isDirect = /^https?:/.test(gitRepo);
    download(isDirect ? `direct:${gitRepo}` : gitRepo, prjDir, { clone: isDirect }, err => {
      if (err) {
        console.log('Failed to download the boilerplate. The project was not created. Please check and retry.');
        console.log(err);
        reject('CLONE_REPO_FAILED');
        return;
      }
      resolve();
    });
  });
}

function postCreate(prjDir, options) {
  const postCreateScript = path.join(prjDir, 'postCreate.js');
  if (fs.existsSync(postCreateScript)) {
    options.status('POST_CREATE', 'Executing post create script...');
    require(postCreateScript)(options);
    fs.removeSync(postCreateScript);
  }
}

function syncAppRegistryRepo() {
  const registryDir = paths.configFile('app-registry');
  return new Promise((resolve, reject) => {
    const appRegistry = config.getAppRegistry();
    const arr = appRegistry.split('/');
    const owner = arr[0];
    const arr2 = arr[1].split('#');
    const repo = arr2[0];
    const branch = arr2[1] || 'master';
    console.log(owner, repo, branch);
    https
      .get(
        `https://api.github.com/repos/${owner}/${repo}/git/refs/heads/${branch}`,
        { headers: { 'User-Agent': 'rekit-core' } },
        resp => {
          let data = '';

          // A chunk of data has been recieved.
          resp.on('data', chunk => {
            data += chunk;
          });

          // The whole response has been received. Print out the result.
          resp.on('end', () => {
            try {
              const ref = JSON.parse(data);
              const lastCommit = ref.object.sha;
              if (!fs.existsSync(path.join(registryDir, lastCommit))) {
                fs.removeSync(registryDir);
                cloneRepo(appRegistry, registryDir)
                  .then(() => {
                    fs.writeFileSync(path.join(registryDir, lastCommit), '');
                    resolve(lastCommit);
                  })
                  .catch(reject);
              } else {
                resolve();
              }
            } catch (err) {
              reject(err);
            }
          });
        }
      )
      .on('error', err => {
        console.log('Failed to get last commit of app registry: ', err);
        reject('FAILED_CHECK_APP_REGISTRY_LATEST_COMMIT');
      });
  });
}

module.exports = create;
module.exports.syncAppRegistryRepo = syncAppRegistryRepo;
<|endoftext|>