<commit_before>#!/usr/bin/env node
;(function () {
  'use strict'

  var predicate = require('fun-predicate')
  var child = require('child_process')
  var async = require('fun-async')
  var delegate = require('fun-delegate')
  var array = require('fun-array')
  var os = require('os')
  var fn = require('fun-function')
  var funCase = require('fun-case')
  var string = require('fun-string')
  var object = require('fun-object')

  var semverStringToNumber = funCase([
    {
      p: predicate.match(/^\[PATCH\]/),
      f: fn.k(0)
    },
    {
      p: predicate.match(/^\[MINOR\]/),
      f: fn.k(1)
    },
    {
      p: predicate.match(/^\[MAJOR\]/),
      f: fn.k(2)
    }
  ])

  var argMax = fn.curry(function argMax (f, args) {
    return args[args.reduce(function (max, value, i) {
      return f(value) > f(max.value) ? { value: value, i: i } : max
    }, { value: args[0], i: 0 }).i]
  })

  var semverUpdateFromPartial = fn.composeAll([
    delegate('toLowerCase', []),
    array.get(1),
    delegate('match', [/^\[([^\]]+)]/]),
    argMax(semverStringToNumber)
  ])

  var NPM_PUBLISH = 'npm publish'
  var NPM_SET = 'npm set //registry.npmjs.org/:_authToken=$NPM_TOKEN'
  var GIT_PUSH = 'git push origin master'
  var GIT_SET_URL = 'git remote set-url --push origin ' +
    'https://bagrounds:$ACCESS_TOKEN@gitlab.com/bagrounds/fun-scalar.git'

  var semverUpdateFromGitLog = fn.composeAll([
    predicate.ifThenElse(
      fn.compose(predicate.truthy, object.get('length')),
      semverUpdateFromPartial,
      fn.k('none')
    ),
    array.filter(predicate.match(/^\[(MAJOR|MINOR|PATCH)\]/)),
    array.takeWhile(predicate.not(predicate.match(/^\d\.\d\.\d$/))),
    array.map(delegate('replace', [/^[^ ]+ /g, ''])),
    delegate('split', [os.EOL])
  ])

  var GIT_LOG = 'git log --oneline'

  var logNothing = fn.compose(
    async.of,
    fn.tee(fn.compose(console.log, fn.k('nothing to do')))
  )

  function runCommand (command) {
    return async.contramap(
      fn.tee(console.log),
      async.contramap(fn.k(command), fn.curry(child.exec, 2))
    )
  }

  var release = async.composeAll([
    runCommand(NPM_PUBLISH),
    runCommand(NPM_SET),
    runCommand(GIT_PUSH),
    runCommand(GIT_SET_URL),
    async.contramap(string.prepend('npm version '), fn.curry(child.exec, 2))
  ])

  var isSemver = array.map(predicate.equal, ['major', 'minor', 'patch'])
    .reduce(predicate.or, predicate.f)

  var maybeRelease = fn.curry(function maybeRelease (mmp, callback) {
    if (isSemver(mmp)) {
      release(mmp, callback)
      return
    }

    logNothing(mmp, callback)
  })

  main()

  function main () {
    fn.composeAll([
      async.compose(maybeRelease),
      async.map(semverUpdateFromGitLog),
      fn.curry(child.exec, 2)
    ])(GIT_LOG)(function CALLBACK (error, stdout, stderror) {
      if (error) {
        throw error
      }

      console.log('stdout: ' + stdout)
    })
  }
})()

<commit_msg>[PATCH][ADD] git add and git commit commands after npm version<commit_after>#!/usr/bin/env node
;(function () {
  'use strict'

  var predicate = require('fun-predicate')
  var child = require('child_process')
  var async = require('fun-async')
  var delegate = require('fun-delegate')
  var array = require('fun-array')
  var os = require('os')
  var fn = require('fun-function')
  var funCase = require('fun-case')
  var string = require('fun-string')
  var object = require('fun-object')

  var semverStringToNumber = funCase([
    {
      p: predicate.match(/^\[PATCH\]/),
      f: fn.k(0)
    },
    {
      p: predicate.match(/^\[MINOR\]/),
      f: fn.k(1)
    },
    {
      p: predicate.match(/^\[MAJOR\]/),
      f: fn.k(2)
    }
  ])

  var argMax = fn.curry(function argMax (f, args) {
    return args[args.reduce(function (max, value, i) {
      return f(value) > f(max.value) ? { value: value, i: i } : max
    }, { value: args[0], i: 0 }).i]
  })

  var semverUpdateFromPartial = fn.composeAll([
    delegate('toLowerCase', []),
    array.get(1),
    delegate('match', [/^\[([^\]]+)]/]),
    argMax(semverStringToNumber)
  ])

  var NPM_PUBLISH = 'npm publish'
  var NPM_SET = 'npm set //registry.npmjs.org/:_authToken=$NPM_TOKEN'
  var GIT_PUSH = 'git push origin master'
  var GIT_COMMIT = 'git commit -m "$(node -pe require(\"./package\").version)"'
  var GIT_ADD = 'git add .'
  var GIT_SET_URL = 'git remote set-url --push origin ' +
    'https://bagrounds:$ACCESS_TOKEN@gitlab.com/bagrounds/fun-scalar.git'

  var semverUpdateFromGitLog = fn.composeAll([
    predicate.ifThenElse(
      fn.compose(predicate.truthy, object.get('length')),
      semverUpdateFromPartial,
      fn.k('none')
    ),
    array.filter(predicate.match(/^\[(MAJOR|MINOR|PATCH)\]/)),
    array.takeWhile(predicate.not(predicate.match(/^\d\.\d\.\d$/))),
    array.map(delegate('replace', [/^[^ ]+ /g, ''])),
    delegate('split', [os.EOL])
  ])

  var GIT_LOG = 'git log --oneline'

  var logNothing = fn.compose(
    async.of,
    fn.tee(fn.compose(console.log, fn.k('nothing to do')))
  )

  function runCommand (command) {
    return async.contramap(
      fn.tee(console.log),
      async.contramap(fn.k(command), fn.curry(child.exec, 2))
    )
  }

  var release = async.composeAll([
    runCommand(NPM_PUBLISH),
    runCommand(NPM_SET),
    runCommand(GIT_PUSH),
    runCommand(GIT_SET_URL),
    runCommand(GIT_COMMIT),
    runCommand(GIT_ADD),
    async.contramap(string.prepend('npm version '), fn.curry(child.exec, 2))
  ])

  var isSemver = array.map(predicate.equal, ['major', 'minor', 'patch'])
    .reduce(predicate.or, predicate.f)

  var maybeRelease = fn.curry(function maybeRelease (mmp, callback) {
    if (isSemver(mmp)) {
      release(mmp, callback)
      return
    }

    logNothing(mmp, callback)
  })

  main()

  function main () {
    fn.composeAll([
      async.compose(maybeRelease),
      async.map(semverUpdateFromGitLog),
      fn.curry(child.exec, 2)
    ])(GIT_LOG)(function CALLBACK (error, stdout, stderror) {
      if (error) {
        throw error
      }

      console.log('stdout: ' + stdout)
    })
  }
})()

<|endoftext|>


<commit_before>import {Button} from 'widget/button'
import {EMPTY, Subject, animationFrameScheduler, interval} from 'rxjs'
import {Scrollable, ScrollableContainer} from 'widget/scrollable'
import {debounceTime, distinctUntilChanged, filter, map, scan, switchMap} from 'rxjs/operators'
import Keybinding from 'widget/keybinding'
import PropTypes from 'prop-types'
import React from 'react'
import ReactResizeDetector from 'react-resize-detector'
import _ from 'lodash'
import styles from './list.module.css'

const ANIMATION_SPEED = .2
const AUTO_CENTER_DELAY = 1000

const getContainer = element =>
    element && element.parentNode && element.parentNode.parentNode

const targetScrollOffset = element => {
    const container = getContainer(element)
    return container
        ? Math.round(element.offsetTop - (getContainer(element).clientHeight - element.clientHeight) / 2)
        : null
}

const currentScrollOffset = element => {
    const container = getContainer(element)
    return container
        ? container.scrollTop
        : null
}

const setScrollOffset = (element, value) => {
    const container = getContainer(element)
    if (container) {
        container.scrollTop = value
    }
}

const lerp = rate =>
    (value, targetValue) => value + (targetValue - value) * rate

export default class List extends React.Component {
    subscriptions = []
    list = React.createRef()
    highlighted = React.createRef()
    scrollHighlighted$ = new Subject()
    state = {
        highlightedOption: null,
        overrideHover: false
    }

    render() {
        const {onCancel, className, keyboard} = this.props
        const keymap = {
            Escape: onCancel ? onCancel : null,
            Enter: () => this.selectHighlighted(),
            ArrowUp: () => this.highlightPrevious(),
            ArrowDown: () => this.highlightNext(),
            Home: () => this.highlightFirst(),
            End: () => this.highlightLast()
        }
        return (
            <Keybinding keymap={keymap} disabled={!keyboard}>
                <ReactResizeDetector
                    handleHeight
                    onResize={() => this.scrollHighlighted$.next()}>
                    <ScrollableContainer className={className}>
                        <Scrollable className={styles.options}>
                            {scrollableContainerHeight => this.renderList(scrollableContainerHeight)}
                        </Scrollable>
                    </ScrollableContainer>
                </ReactResizeDetector>
            </Keybinding>
        )
    }

    renderList(scrollableContainerHeight = 0) {
        const {options, overScroll} = this.props
        return (

                {this.renderOptions(options)}

        )
    }

    updateState(state, callback) {
        const updatedState = (prevState, state) =>
            _.isEqual(_.pick(prevState, _.keys(state)), state) ? null : state
        this.setState(
            prevState =>
                updatedState(prevState, _.isFunction(state) ? state(prevState) : state),
            callback
        )
    }

    renderOptions(options) {
        return options.length
            ? options.map((option, index) => this.renderOption(option, index))
            : this.renderOption({label: 'No results'}) // [TODO] msg
    }

    renderOption(option, index) {
        return option.value !== undefined
            ? this.renderSelectableOption(option)
            : option.group
                ? this.renderGroup(option, index)
                : this.renderNonSelectableOption(option, index)
    }

    renderGroup(option, index) {
        return (

                {option.label}

        )
    }

    renderNonSelectableOption(option, index) {
        return (

                <Button
                    chromeless
                    look='transparent'
                    label={option.label}
                    width='fill'
                    alignment='left'
                    disabled
                />

        )
    }

    renderSelectableOption(option) {
        const {selectedOption} = this.props
        const {overrideHover} = this.state
        const selected = this.isSelected(option)
        const highlighted = this.isHighlighted(option)
        const ref = highlighted
            ? this.highlighted
            : null
        return (

                <Button
                    chromeless={!selected}
                    look={selected ? 'highlight' : 'transparent'}
                    label={option.label}
                    hover={overrideHover ? highlighted : null}
                    width='fill'
                    alignment='left'
                    disableTransitions
                    onMouseOver={() => this.highlightOption(option)}
                    onMouseOut={() => this.highlightOption(selectedOption)}
                    onClick={() => this.selectOption(option)}
                />

        )
    }

    isSelected(option) {
        const {selectedOption} = this.props
        return option === selectedOption
    }

    isSelectable(option) {
        return !option.group && option.value
    }

    isHighlighted(option) {
        const {highlightedOption} = this.state
        return highlightedOption && option && highlightedOption.value === option.value
    }

    selectHighlighted() {
        const {highlightedOption} = this.state
        if (highlightedOption) {
            this.selectOption(highlightedOption)
        }
    }

    cancel() {
        const {onCancel} = this.props
        onCancel && onCancel()
    }

    getSelectedOption() {
        const {options, selectedOption} = this.props
        return _.find(options, option => option === selectedOption)
    }

    getPreviousSelectableOption(option) {
        const {options} = this.props
        const previousIndex = Math.max(_.indexOf(options, option) - 1, 0)
        return _.findLast(options, option => this.isSelectable(option), previousIndex) || option
    }

    getNextSelectableOption(option) {
        const {options} = this.props
        const nextIndex = Math.min(_.indexOf(options, option) + 1, options.length - 1)
        return _.find(options, option => this.isSelectable(option), nextIndex) || option
    }

    getFirstSelectableOption() {
        const {options} = this.props
        return _.find(options, option => this.isSelectable(option))
    }

    getLastSelectableOption() {
        const {options} = this.props
        return _.findLast(options, option => this.isSelectable(option))
    }

    highlightOption(highlightedOption) {
        this.setState({
            highlightedOption,
            overrideHover: false
        })
    }

    highlightPrevious() {
        this.setState(prevState => ({
            highlightedOption: this.getPreviousSelectableOption(prevState.highlightedOption),
            overrideHover: true
        }), this.scroll)
    }

    highlightNext() {
        this.setState(prevState => ({
            highlightedOption: this.getNextSelectableOption(prevState.highlightedOption),
            overrideHover: true
        }), this.scroll)
    }

    highlightFirst() {
        this.setState({
            highlightedOption: this.getFirstSelectableOption(),
            overrideHover: true
        }, this.scroll)
    }

    highlightLast() {
        this.setState({
            highlightedOption: this.getLastSelectableOption(),
            overrideHover: true
        }, this.scroll)
    }

    scroll() {
        this.highlighted.current && this.highlighted.current.scrollIntoView({
            behavior: 'auto',
            block: 'nearest'
        })
    }

    selectOption(option) {
        this.scrollHighlighted$.next(option)
    }

    autoCenterWhenIdle(scroll$) {
        return scroll$.pipe(
            debounceTime(AUTO_CENTER_DELAY)
        ).subscribe(() => {
            this.centerSelected()
        })
    }

    update() {
        const highlightedOption = this.getSelectedOption() || this.getFirstSelectableOption()
        this.setState({highlightedOption}, () => this.scrollHighlighted$.next())
    }

    initializeAutoScroll() {
        const animationFrame$ = interval(0, animationFrameScheduler)
        const {onSelect} = this.props

        const scroll$ = this.scrollHighlighted$.pipe(
            map(() => this.highlighted.current),
            filter(element => element),
            switchMap(element => {
                const target = targetScrollOffset(element)
                return Math.round(currentScrollOffset(element)) === target
                    ? EMPTY
                    : animationFrame$.pipe(
                        map(() => target),
                        scan(lerp(ANIMATION_SPEED), currentScrollOffset(element)),
                        map(value => Math.round(value)),
                        distinctUntilChanged(),
                        map(value => ({element, value}))
                    )
            })
        )

        const select$ = this.scrollHighlighted$.pipe(
            filter(option => option)
        )

        this.subscriptions.push(
            scroll$.subscribe(
                ({element, value}) => setScrollOffset(element, value)
            ),
            select$.subscribe(
                option => {
                    onSelect(option)
                }
            )
        )
    }

    componentDidMount() {
        this.initializeAutoScroll()
        this.update()
    }

    shouldComponentUpdate(nextProps, nextState) {
        if (!_.isEqual(nextProps, this.props)) {
            return true
        }
        if (nextState.overrideHover !== this.state.overrideHover) {
            return true
        }
        if (nextState.overrideHover && nextState.highlightedOption !== this.state.highlightedOption) {
            return true
        }
        return false
    }

    componentDidUpdate(prevProps) {
        if (!_.isEqual(prevProps, this.props)) {
            this.update()
        }
    }

    componentWillUnmount() {
        this.subscriptions.forEach(subscription => subscription.unsubscribe())
    }
}

List.propTypes = {
    options: PropTypes.arrayOf(
        PropTypes.shape({
            label: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
            value: PropTypes.any
        })
    ).isRequired,
    onSelect: PropTypes.func.isRequired,
    autoCenter: PropTypes.any,
    className: PropTypes.string,
    keyboard: PropTypes.any,
    overScroll: PropTypes.any,
    selectedOption: PropTypes.any,
    onCancel: PropTypes.func
}
<commit_msg>Added left/right (same as up/down) keybindings to List.<commit_after>import {Button} from 'widget/button'
import {EMPTY, Subject, animationFrameScheduler, interval} from 'rxjs'
import {Scrollable, ScrollableContainer} from 'widget/scrollable'
import {debounceTime, distinctUntilChanged, filter, map, scan, switchMap} from 'rxjs/operators'
import Keybinding from 'widget/keybinding'
import PropTypes from 'prop-types'
import React from 'react'
import ReactResizeDetector from 'react-resize-detector'
import _ from 'lodash'
import styles from './list.module.css'

const ANIMATION_SPEED = .2
const AUTO_CENTER_DELAY = 1000

const getContainer = element =>
    element && element.parentNode && element.parentNode.parentNode

const targetScrollOffset = element => {
    const container = getContainer(element)
    return container
        ? Math.round(element.offsetTop - (getContainer(element).clientHeight - element.clientHeight) / 2)
        : null
}

const currentScrollOffset = element => {
    const container = getContainer(element)
    return container
        ? container.scrollTop
        : null
}

const setScrollOffset = (element, value) => {
    const container = getContainer(element)
    if (container) {
        container.scrollTop = value
    }
}

const lerp = rate =>
    (value, targetValue) => value + (targetValue - value) * rate

export default class List extends React.Component {
    subscriptions = []
    list = React.createRef()
    highlighted = React.createRef()
    scrollHighlighted$ = new Subject()
    state = {
        highlightedOption: null,
        overrideHover: false
    }

    render() {
        const {onCancel, className, keyboard} = this.props
        const keymap = {
            Escape: onCancel ? onCancel : null,
            Enter: () => this.selectHighlighted(),
            ArrowLeft: () => this.highlightPrevious(),
            ArrowRight: () => this.highlightNext(),
            ArrowUp: () => this.highlightPrevious(),
            ArrowDown: () => this.highlightNext(),
            Home: () => this.highlightFirst(),
            End: () => this.highlightLast()
        }
        return (
            <Keybinding keymap={keymap} disabled={!keyboard}>
                <ReactResizeDetector
                    handleHeight
                    onResize={() => this.scrollHighlighted$.next()}>
                    <ScrollableContainer className={className}>
                        <Scrollable className={styles.options}>
                            {scrollableContainerHeight => this.renderList(scrollableContainerHeight)}
                        </Scrollable>
                    </ScrollableContainer>
                </ReactResizeDetector>
            </Keybinding>
        )
    }

    renderList(scrollableContainerHeight = 0) {
        const {options, overScroll} = this.props
        return (

                {this.renderOptions(options)}

        )
    }

    updateState(state, callback) {
        const updatedState = (prevState, state) =>
            _.isEqual(_.pick(prevState, _.keys(state)), state) ? null : state
        this.setState(
            prevState =>
                updatedState(prevState, _.isFunction(state) ? state(prevState) : state),
            callback
        )
    }

    renderOptions(options) {
        return options.length
            ? options.map((option, index) => this.renderOption(option, index))
            : this.renderOption({label: 'No results'}) // [TODO] msg
    }

    renderOption(option, index) {
        return option.value !== undefined
            ? this.renderSelectableOption(option)
            : option.group
                ? this.renderGroup(option, index)
                : this.renderNonSelectableOption(option, index)
    }

    renderGroup(option, index) {
        return (

                {option.label}

        )
    }

    renderNonSelectableOption(option, index) {
        return (

                <Button
                    chromeless
                    look='transparent'
                    label={option.label}
                    width='fill'
                    alignment='left'
                    disabled
                />

        )
    }

    renderSelectableOption(option) {
        const {selectedOption} = this.props
        const {overrideHover} = this.state
        const selected = this.isSelected(option)
        const highlighted = this.isHighlighted(option)
        const ref = highlighted
            ? this.highlighted
            : null
        return (

                <Button
                    chromeless={!selected}
                    look={selected ? 'highlight' : 'transparent'}
                    label={option.label}
                    hover={overrideHover ? highlighted : null}
                    width='fill'
                    alignment='left'
                    disableTransitions
                    onMouseOver={() => this.highlightOption(option)}
                    onMouseOut={() => this.highlightOption(selectedOption)}
                    onClick={() => this.selectOption(option)}
                />

        )
    }

    isSelected(option) {
        const {selectedOption} = this.props
        return option === selectedOption
    }

    isSelectable(option) {
        return !option.group && option.value
    }

    isHighlighted(option) {
        const {highlightedOption} = this.state
        return highlightedOption && option && highlightedOption.value === option.value
    }

    selectHighlighted() {
        const {highlightedOption} = this.state
        if (highlightedOption) {
            this.selectOption(highlightedOption)
        }
    }

    cancel() {
        const {onCancel} = this.props
        onCancel && onCancel()
    }

    getSelectedOption() {
        const {options, selectedOption} = this.props
        return _.find(options, option => option === selectedOption)
    }

    getPreviousSelectableOption(option) {
        const {options} = this.props
        const previousIndex = Math.max(_.indexOf(options, option) - 1, 0)
        return _.findLast(options, option => this.isSelectable(option), previousIndex) || option
    }

    getNextSelectableOption(option) {
        const {options} = this.props
        const nextIndex = Math.min(_.indexOf(options, option) + 1, options.length - 1)
        return _.find(options, option => this.isSelectable(option), nextIndex) || option
    }

    getFirstSelectableOption() {
        const {options} = this.props
        return _.find(options, option => this.isSelectable(option))
    }

    getLastSelectableOption() {
        const {options} = this.props
        return _.findLast(options, option => this.isSelectable(option))
    }

    highlightOption(highlightedOption) {
        this.setState({
            highlightedOption,
            overrideHover: false
        })
    }

    highlightPrevious() {
        this.setState(prevState => ({
            highlightedOption: this.getPreviousSelectableOption(prevState.highlightedOption),
            overrideHover: true
        }), this.scroll)
    }

    highlightNext() {
        this.setState(prevState => ({
            highlightedOption: this.getNextSelectableOption(prevState.highlightedOption),
            overrideHover: true
        }), this.scroll)
    }

    highlightFirst() {
        this.setState({
            highlightedOption: this.getFirstSelectableOption(),
            overrideHover: true
        }, this.scroll)
    }

    highlightLast() {
        this.setState({
            highlightedOption: this.getLastSelectableOption(),
            overrideHover: true
        }, this.scroll)
    }

    scroll() {
        this.highlighted.current && this.highlighted.current.scrollIntoView({
            behavior: 'auto',
            block: 'nearest'
        })
    }

    selectOption(option) {
        this.scrollHighlighted$.next(option)
    }

    autoCenterWhenIdle(scroll$) {
        return scroll$.pipe(
            debounceTime(AUTO_CENTER_DELAY)
        ).subscribe(() => {
            this.centerSelected()
        })
    }

    update() {
        const highlightedOption = this.getSelectedOption() || this.getFirstSelectableOption()
        this.setState({highlightedOption}, () => this.scrollHighlighted$.next())
    }

    initializeAutoScroll() {
        const animationFrame$ = interval(0, animationFrameScheduler)
        const {onSelect} = this.props

        const scroll$ = this.scrollHighlighted$.pipe(
            map(() => this.highlighted.current),
            filter(element => element),
            switchMap(element => {
                const target = targetScrollOffset(element)
                return Math.round(currentScrollOffset(element)) === target
                    ? EMPTY
                    : animationFrame$.pipe(
                        map(() => target),
                        scan(lerp(ANIMATION_SPEED), currentScrollOffset(element)),
                        map(value => Math.round(value)),
                        distinctUntilChanged(),
                        map(value => ({element, value}))
                    )
            })
        )

        const select$ = this.scrollHighlighted$.pipe(
            filter(option => option)
        )

        this.subscriptions.push(
            scroll$.subscribe(
                ({element, value}) => setScrollOffset(element, value)
            ),
            select$.subscribe(
                option => {
                    onSelect(option)
                }
            )
        )
    }

    componentDidMount() {
        this.initializeAutoScroll()
        this.update()
    }

    shouldComponentUpdate(nextProps, nextState) {
        if (!_.isEqual(nextProps, this.props)) {
            return true
        }
        if (nextState.overrideHover !== this.state.overrideHover) {
            return true
        }
        if (nextState.overrideHover && nextState.highlightedOption !== this.state.highlightedOption) {
            return true
        }
        return false
    }

    componentDidUpdate(prevProps) {
        if (!_.isEqual(prevProps, this.props)) {
            this.update()
        }
    }

    componentWillUnmount() {
        this.subscriptions.forEach(subscription => subscription.unsubscribe())
    }
}

List.propTypes = {
    options: PropTypes.arrayOf(
        PropTypes.shape({
            label: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
            value: PropTypes.any
        })
    ).isRequired,
    onSelect: PropTypes.func.isRequired,
    autoCenter: PropTypes.any,
    className: PropTypes.string,
    keyboard: PropTypes.any,
    overScroll: PropTypes.any,
    selectedOption: PropTypes.any,
    onCancel: PropTypes.func
}
<|endoftext|>


<commit_before>/* eslint-disable
    camelcase,
    no-return-assign,
    no-unused-vars,
*/
// TODO: This file was created by bulk-decaffeinate.
// Fix any style issues and re-enable lint.
/*
 * decaffeinate suggestions:
 * DS101: Remove unnecessary use of Array.from
 * DS102: Remove unnecessary code created because of implicit returns
 * DS207: Consider shorter variations of null checks
 * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
 */
let CommandRunner
const { spawn } = require('child_process')
const _ = require('lodash')
const logger = require('@overleaf/logger')

logger.info('using standard command runner')

module.exports = CommandRunner = {
  run(
    project_id,
    command,
    directory,
    image,
    timeout,
    environment,
    compileGroup,
    callback
  ) {
    let key, value
    callback = _.once(callback)
    command = Array.from(command).map(arg =>
      arg.toString().replace('$COMPILE_DIR', directory)
    )
    logger.log({ project_id, command, directory }, 'running command')
    logger.warn('timeouts and sandboxing are not enabled with CommandRunner')

    // merge environment settings
    const env = {}
    for (key in process.env) {
      value = process.env[key]
      env[key] = value
    }
    for (key in environment) {
      value = environment[key]
      env[key] = value
    }

    // run command as detached process so it has its own process group (which can be killed if needed)
    const proc = spawn(command[0], command.slice(1), { cwd: directory, env })

    let stdout = ''
    proc.stdout.setEncoding('utf8').on('data', data => (stdout += data))

    proc.on('error', function (err) {
      logger.err(
        { err, project_id, command, directory },
        'error running command'
      )
      return callback(err)
    })

    proc.on('close', function (code, signal) {
      let err
      logger.info({ code, signal, project_id }, 'command exited')
      if (signal === 'SIGTERM') {
        // signal from kill method below
        err = new Error('terminated')
        err.terminated = true
        return callback(err)
      } else if (code === 1) {
        // exit status from chktex
        err = new Error('exited')
        err.code = code
        return callback(err)
      } else {
        return callback(null, { stdout: stdout })
      }
    })

    return proc.pid
  }, // return process id to allow job to be killed if necessary

  kill(pid, callback) {
    if (callback == null) {
      callback = function () {}
    }
    try {
      process.kill(-pid) // kill all processes in group
    } catch (err) {
      return callback(err)
    }
    return callback()
  },
}
<commit_msg>Ignore stderr output when running commands Fixes #1006<commit_after>/* eslint-disable
    camelcase,
    no-return-assign,
    no-unused-vars,
*/
// TODO: This file was created by bulk-decaffeinate.
// Fix any style issues and re-enable lint.
/*
 * decaffeinate suggestions:
 * DS101: Remove unnecessary use of Array.from
 * DS102: Remove unnecessary code created because of implicit returns
 * DS207: Consider shorter variations of null checks
 * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
 */
let CommandRunner
const { spawn } = require('child_process')
const _ = require('lodash')
const logger = require('@overleaf/logger')

logger.info('using standard command runner')

module.exports = CommandRunner = {
  run(
    project_id,
    command,
    directory,
    image,
    timeout,
    environment,
    compileGroup,
    callback
  ) {
    let key, value
    callback = _.once(callback)
    command = Array.from(command).map(arg =>
      arg.toString().replace('$COMPILE_DIR', directory)
    )
    logger.log({ project_id, command, directory }, 'running command')
    logger.warn('timeouts and sandboxing are not enabled with CommandRunner')

    // merge environment settings
    const env = {}
    for (key in process.env) {
      value = process.env[key]
      env[key] = value
    }
    for (key in environment) {
      value = environment[key]
      env[key] = value
    }

    // run command as detached process so it has its own process group (which can be killed if needed)
    const proc = spawn(command[0], command.slice(1), { cwd: directory, env, stdio: ['pipe', 'pipe', 'ignore'] })

    let stdout = ''
    proc.stdout.setEncoding('utf8').on('data', data => (stdout += data))

    proc.on('error', function (err) {
      logger.err(
        { err, project_id, command, directory },
        'error running command'
      )
      return callback(err)
    })

    proc.on('close', function (code, signal) {
      let err
      logger.info({ code, signal, project_id }, 'command exited')
      if (signal === 'SIGTERM') {
        // signal from kill method below
        err = new Error('terminated')
        err.terminated = true
        return callback(err)
      } else if (code === 1) {
        // exit status from chktex
        err = new Error('exited')
        err.code = code
        return callback(err)
      } else {
        return callback(null, { stdout: stdout })
      }
    })

    return proc.pid
  }, // return process id to allow job to be killed if necessary

  kill(pid, callback) {
    if (callback == null) {
      callback = function () {}
    }
    try {
      process.kill(-pid) // kill all processes in group
    } catch (err) {
      return callback(err)
    }
    return callback()
  },
}
<|endoftext|>


<commit_before>define(
['ext/jquery', 'ext/underscore', 'ext/underscore.string', 'ext/bootstrap',
'ext/backbone', 'jquery.slide', 'base_views', 'ratings', 'util'],
function($, _, _s, bootstrap, Backbone, jqSlide, baseViews, ratings, util) {

  var Prof = Backbone.Model.extend({
    defaults: {
      name: 'Charles L.A. Clarke',
      email: 'charles.clarke@uwaterloo.ca',
      phone: '519-888-4567 x35241',
      office: 'DC 2506',
      department: 'School of Computer Science',
      pictureUrl: '',
      course_ratings: [],
      reviews: null
    },

    initialize: function(attributes) {
      // TODO(david): Use some other instructional placeholder
      if (!attributes || !attributes.pictureUrl) {
        var attrs = attributes ? attributes : this.defaults;
        var size = [
            util.getHashCode(attrs.name) % 20 + 140,
            util.getHashCode(attrs.email) % 20 + 140
        ];
        this.set('pictureUrl',
            'http://placekitten.com/' + size[0] + '/' + size[1]);
      }

      if (attributes && attributes.course_ratings) {
        var coll = new ratings.RatingCollection(attributes.course_ratings);
        var overall = coll.where({ name: 'overall' })[0];
        this.set('overall', overall);
        coll.remove(overall);
        this.set('ratings', coll);
      } else {
        this.set('ratings', new ratings.RatingCollection());
      }

      if (!attributes || !attributes.reviews) {
        this.set('reviews', new ProfReviewCollection());
      }
    }
  });

  // TODO(david): Convert other backbone views to pre-compile templates
  var ProfCardView = Backbone.View.extend({
    className: 'prof-card',
    template: _.template($('#prof-card-tpl').html()),

    render: function() {
      this.$el.html(this.template(this.model.toJSON()));
      return this;
    }
  });

  var ProfCollection = Backbone.Collection.extend({
    model: Prof
  });

  // TODO(david): This might just be a user-course model
  var ProfReview = Backbone.Model.extend({
    defaults: {
      name: 'Larry Smith',
      passion: 5,
      clarity: 3,
      overall: 6,
      comment: 'Was great!!!!!!!!!!!!!!!!'
    }
  });

  var ProfReviewView = Backbone.View.extend({
    className: 'prof-review',
    template: _.template($('#prof-review-tpl').html()),

    render: function() {
      this.$el.html(this.template(this.model.toJSON()));
      return this;
    }
  });

  var ProfReviewCollection = Backbone.Collection.extend({
    model: ProfReview
  });

  var ProfReviewCollectionView = baseViews.CollectionView.extend({
    className: 'prof-review-collection',

    createItemView: function(model) {
      return new ProfReviewView({ model: model });
    }
  });

  // TODO(david): Need base expandable collection view
  var ExpandableProfView = Backbone.View.extend({
    template: _.template($('#prof-expandable-reviews-tpl').html()),
    expanded: false,
    numShown: 1,

    events: {
      'click .toggle-reviews': 'toggleExpand'
    },

    initialize: function(options) {
      this.prof = options.prof;
      this.profView = new ProfCardView({
        model: this.prof
      });

      // Reviews
      this.reviews = options.reviews;
      this.profReviewCollectionView = new ProfReviewCollectionView({
        collection: this.reviews
      });

      // Rating info
      this.ratingBoxView = new ratings.RatingBoxView({
        model: this.prof.get('overall')
      });
      this.ratingsView = new ratings.RatingsView({
        ratings: this.prof.get('ratings'),
        readOnly: true
      });
    },

    render: function() {
      this.$el.html(this.template({
        numHidden: this.numHidden()
      }));

      // Professor business card
      this.$('.prof-card-placeholder').replaceWith(this.profView.render().el);

      // Aggregate rating
      this.$('.rating-box-placeholder').replaceWith(
          this.ratingBoxView.render().el);
      this.$('.aggregate-ratings-placeholder').replaceWith(
        this.ratingsView.render().el);

      // Professor reviews
      this.$('.reviews-collection-placeholder').replaceWith(
        this.profReviewCollectionView.render().el);

      this.$('.prof-review').slice(this.numShown)
        .wrapAll('
');

      return this;
    },

    numReviews: function() {
      return this.reviews.length;
    },

    numHidden: function() {
      return this.numReviews() - this.numShown;
    },

    toggleExpand: function() {
      if (this.expanded) {
        this.$('.expanded-reviews').fancySlide('up');
        this.$('.toggle-reviews')
          .html('See ' + this.numHidden() + ' more reviews »');
      } else {
        this.$('.expanded-reviews').fancySlide('down');
        this.$('.toggle-reviews').html('« Hide reviews');
      }
      this.expanded = !this.expanded;
    }
  });

  var ProfCollectionView = baseViews.CollectionView.extend({
    className: 'prof-collection',

    createItemView: function(model) {
      return new ExpandableProfView({
        prof: model,
        reviews: model.get('reviews')
      });
    }
  });

  return {
    Prof: Prof,
    ProfCardView: ProfCardView,
    ProfCollection: ProfCollection,
    ProfReview: ProfReview,
    ProfReviewCollection: ProfReviewCollection,
    ProfReviewCollectionView: ProfReviewCollectionView,
    ExpandableProfView: ExpandableProfView,
    ProfCollectionView: ProfCollectionView
  };

});
<commit_msg>sort profs on single course page by popularity<commit_after>define(
['ext/jquery', 'ext/underscore', 'ext/underscore.string', 'ext/bootstrap',
'ext/backbone', 'jquery.slide', 'base_views', 'ratings', 'util'],
function($, _, _s, bootstrap, Backbone, jqSlide, baseViews, ratings, util) {

  var Prof = Backbone.Model.extend({
    defaults: {
      name: 'Charles L.A. Clarke',
      email: 'charles.clarke@uwaterloo.ca',
      phone: '519-888-4567 x35241',
      office: 'DC 2506',
      department: 'School of Computer Science',
      pictureUrl: '',
      course_ratings: [],
      reviews: null
    },

    initialize: function(attributes) {
      // TODO(david): Use some other instructional placeholder
      if (!attributes || !attributes.pictureUrl) {
        var attrs = attributes ? attributes : this.defaults;
        var size = [
            util.getHashCode(attrs.name) % 20 + 140,
            util.getHashCode(attrs.email) % 20 + 140
        ];
        this.set('pictureUrl',
            'http://placekitten.com/' + size[0] + '/' + size[1]);
      }

      if (attributes && attributes.course_ratings) {
        var coll = new ratings.RatingCollection(attributes.course_ratings);
        var overall = coll.where({ name: 'overall' })[0];
        this.set('overall', overall);
        coll.remove(overall);
        this.set('ratings', coll);
      } else {
        this.set('ratings', new ratings.RatingCollection());
      }

      if (!attributes || !attributes.reviews) {
        this.set('reviews', new ProfReviewCollection());
      }
    }
  });

  // TODO(david): Convert other backbone views to pre-compile templates
  var ProfCardView = Backbone.View.extend({
    className: 'prof-card',
    template: _.template($('#prof-card-tpl').html()),

    render: function() {
      this.$el.html(this.template(this.model.toJSON()));
      return this;
    }
  });

  var ProfCollection = Backbone.Collection.extend({
    model: Prof,

    // TODO(david): Allow changing sort/filter options
    comparator: function(prof) {
      return -prof.get('overall').get('count');
    }
  });

  // TODO(david): This might just be a user-course model
  var ProfReview = Backbone.Model.extend({
    defaults: {
      name: 'Larry Smith',
      passion: 5,
      clarity: 3,
      overall: 6,
      comment: 'Was great!!!!!!!!!!!!!!!!'
    }
  });

  var ProfReviewView = Backbone.View.extend({
    className: 'prof-review',
    template: _.template($('#prof-review-tpl').html()),

    render: function() {
      this.$el.html(this.template(this.model.toJSON()));
      return this;
    }
  });

  var ProfReviewCollection = Backbone.Collection.extend({
    model: ProfReview
  });

  var ProfReviewCollectionView = baseViews.CollectionView.extend({
    className: 'prof-review-collection',

    createItemView: function(model) {
      return new ProfReviewView({ model: model });
    }
  });

  // TODO(david): Need base expandable collection view
  var ExpandableProfView = Backbone.View.extend({
    template: _.template($('#prof-expandable-reviews-tpl').html()),
    expanded: false,
    numShown: 1,

    events: {
      'click .toggle-reviews': 'toggleExpand'
    },

    initialize: function(options) {
      this.prof = options.prof;
      this.profView = new ProfCardView({
        model: this.prof
      });

      // Reviews
      this.reviews = options.reviews;
      this.profReviewCollectionView = new ProfReviewCollectionView({
        collection: this.reviews
      });

      // Rating info
      this.ratingBoxView = new ratings.RatingBoxView({
        model: this.prof.get('overall')
      });
      this.ratingsView = new ratings.RatingsView({
        ratings: this.prof.get('ratings'),
        readOnly: true
      });
    },

    render: function() {
      this.$el.html(this.template({
        numHidden: this.numHidden()
      }));

      // Professor business card
      this.$('.prof-card-placeholder').replaceWith(this.profView.render().el);

      // Aggregate rating
      this.$('.rating-box-placeholder').replaceWith(
          this.ratingBoxView.render().el);
      this.$('.aggregate-ratings-placeholder').replaceWith(
        this.ratingsView.render().el);

      // Professor reviews
      this.$('.reviews-collection-placeholder').replaceWith(
        this.profReviewCollectionView.render().el);

      this.$('.prof-review').slice(this.numShown)
        .wrapAll('
');

      return this;
    },

    numReviews: function() {
      return this.reviews.length;
    },

    numHidden: function() {
      return this.numReviews() - this.numShown;
    },

    toggleExpand: function() {
      if (this.expanded) {
        this.$('.expanded-reviews').fancySlide('up');
        this.$('.toggle-reviews')
          .html('See ' + this.numHidden() + ' more reviews »');
      } else {
        this.$('.expanded-reviews').fancySlide('down');
        this.$('.toggle-reviews').html('« Hide reviews');
      }
      this.expanded = !this.expanded;
    }
  });

  var ProfCollectionView = baseViews.CollectionView.extend({
    className: 'prof-collection',

    createItemView: function(model) {
      return new ExpandableProfView({
        prof: model,
        reviews: model.get('reviews')
      });
    }
  });

  return {
    Prof: Prof,
    ProfCardView: ProfCardView,
    ProfCollection: ProfCollection,
    ProfReview: ProfReview,
    ProfReviewCollection: ProfReviewCollection,
    ProfReviewCollectionView: ProfReviewCollectionView,
    ExpandableProfView: ExpandableProfView,
    ProfCollectionView: ProfCollectionView
  };

});
<|endoftext|>


<commit_before>var constants = require("../../../shared/scripts/constants");

var Path = Bramble.Filer.Path;
var Buffer = Bramble.Filer.Buffer;
var fs = Bramble.getFileSystem();

// If there are pending operations to be run in the SyncQueue for a
// given path in the project (e.g., user deleted a file locally, but closed
// before the change could be synced to the server), we can safely ignore
// this path, since we'll just be modifying the file anyway.
function shouldSkipPath(pending, path) {
  return pending[path];
}

function installFile(path, data, callback) {
  var sh = new fs.Shell();
  var basedir = Path.dirname(path);

  sh.mkdirp(basedir, function(err) {
    if (err) {
      return callback(err);
    }

    fs.writeFile(path, new Buffer(data), { encoding: null }, callback);
  });
}

// Installs a tarball (arraybuffer) containing the project's files/folders.
function installTarball(config, tarball, callback) {
  var untarWorker;
  var pending = null;
  var root = config.root;
  var pendingOperations = config.syncQueue.pending;

  function maybeExtract(path, data, callback) {
    path = Path.join(root, path);

    if (shouldSkipPath(pendingOperations, path)) {
      callback();
      return;
    }

    installFile(path, data, callback);
  }

  function finish(err) {
    untarWorker.terminate();
    untarWorker = null;

    callback(err);
  }

  function writeCallback(err) {
    if (err) {
      console.error("[Thimble error] couldn't extract file for tar", err);
    }

    pending--;
    if (pending === 0) {
      finish(err);
    }
  }

  untarWorker = new Worker("/resources/scripts/bitjs-untar-worker.min.js");
  untarWorker.addEventListener("message", function(e) {
    var data = e.data;

    if (data.type === "progress" && pending === null) {
      // Set the total number of files we need to deal with so we know when we're done
      pending = data.totalFilesInArchive;
    } else if (data.type === "extract") {
      maybeExtract(
        data.unarchivedFile.filename,
        data.unarchivedFile.fileData,
        writeCallback
      );
    } else if (data.type === "error") {
      finish(new Error("[Thimble error]: " + data.msg));
    }
  });

  untarWorker.postMessage({ file: tarball });
}

function loadTarball(config, callback) {
  // jQuery doesn't seem to support getting the arraybuffer type
  var url = config.host + "/projects";
  if (config.remixId || config.id) {
    url += "/" + (config.remixId || config.id);
  }
  url += "/files/data?cacheBust=" + new Date().toISOString();

  var xhr = new XMLHttpRequest();
  xhr.open("GET", url, true);
  xhr.responseType = "arraybuffer";
  xhr.onload = function() {
    if (this.status !== 200) {
      return callback(
        new Error(
          "[Thimble error] unable to get tarball, status was:",
          this.status
        )
      );
    }

    installTarball(config, this.response, callback);
  };
  xhr.send();
}

// Load all files as separate requests.  File data is of the form:
// [{ id: 1, path: "/index.html", project_id: 3 }, ... ]
function loadFiles(config, callback) {
  if (!Array.isArray(config.data) && config.data.length > 0) {
    return callback(new Error("file metadata was missing or in wrong form"));
  }

  var root = config.root;
  var url = config.host + "/files/";

  $.when
    .apply(
      $,
      config.data.map(function(fileInfo) {
        var deferred = $.Deferred();
        var path = Path.join(root, fileInfo.path);
        var pendingOperations = config.syncQueue.pending;

        if (shouldSkipPath(pendingOperations, path)) {
          return deferred.resolve().promise();
        }

        // jQuery doesn't seem to support getting the arraybuffer type
        var xhr = new XMLHttpRequest();
        xhr.open("GET", url + fileInfo.id, true);
        xhr.responseType = "arraybuffer";
        xhr.onload = function() {
          if (this.status !== 200) {
            return deferred.reject();
          }

          installFile(path, this.response, function(err) {
            if (err) {
              return deferred.reject();
            }
            deferred.resolve();
          });
        };
        xhr.send();

        return deferred.promise();
      })
    )
    .then(callback, function() {
      callback(new Error("[Thimble error] unable to load project files."));
    });
}

function upgradeAnonymousProject(config, callback) {
  var shell = new fs.Shell();
  var oldRoot = Path.join(
    constants.ANONYMOUS_USER_FOLDER,
    config.anonymousId.toString()
  );
  var newRoot = config.root;
  var pathUpdatesCache = [];

  function upgradeFile(path, next) {
    if (path.match(/\/$/)) {
      return next();
    }

    fs.readFile(path, function(err, data) {
      if (err) {
        return next(err);
      }

      var relativePath = Path.relative(oldRoot, path);
      var newPath = Path.join(newRoot, relativePath);
      var parent = Path.dirname(newPath);

      shell.mkdirp(parent, function(err) {
        if (err) {
          return next(err);
        }

        fs.writeFile(newPath, data, { encoding: null }, function(err) {
          if (err) {
            return next(err);
          }

          // Track this as a file to be persisted to publish
          pathUpdatesCache.push(newPath);
          next();
        });
      });
    });
  }

  shell.find(oldRoot, { exec: upgradeFile }, function(err) {
    if (err) {
      console.error(
        "[Thimble Error] unable to process path while upgrading anonymous project",
        err
      );
      return callback(err);
    }

    shell.rm(oldRoot, { recursive: true }, function(err) {
      if (err) {
        console.error(
          "[Thimble Error] unable to remove path while upgrading anonymous project",
          err
        );
        return callback(err);
      }

      // Send back the list of paths to be updated
      callback(null, pathUpdatesCache);
    });
  });
}

function load(config, callback) {
  // Support loading projects as a single tarball or as a set of individual files.
  // Change the value via .env on the server and the PROJECT_LOAD_STRATEGY variable.
  // Default to loading a tarball.
  if (config.projectLoadStrategy === "files") {
    loadFiles(config, callback);
  } else {
    loadTarball(config, callback);
  }
}

function loadProject(config, callback) {
  if (config.user) {
    if (config.anonymousId) {
      return upgradeAnonymousProject(config, callback);
    }

    return load(config, callback);
  }

  // First, check if this anonymous project already exists by checking the
  // root. If it exists, we've done this before and no loading is required
  fs.stat(config.root, function(err) {
    if (err) {
      if (err.code !== "ENOENT") {
        return callback(err);
      }

      // Anonymous project does not exist
      new fs.Shell().mkdirp(config.root, function(err) {
        if (err) {
          return callback(err);
        }

        // Prefer loading as tarball if this is the default project, even if loadfiles=1.
        loadTarball(config, callback);
      });
    } else {
      callback();
    }
  });
}

module.exports = {
  loadProject: loadProject
};
<commit_msg>Fix missing require for jquery<commit_after>var $ = require("jquery");

var constants = require("../../../shared/scripts/constants");

var Path = Bramble.Filer.Path;
var Buffer = Bramble.Filer.Buffer;
var fs = Bramble.getFileSystem();

// If there are pending operations to be run in the SyncQueue for a
// given path in the project (e.g., user deleted a file locally, but closed
// before the change could be synced to the server), we can safely ignore
// this path, since we'll just be modifying the file anyway.
function shouldSkipPath(pending, path) {
  return pending[path];
}

function installFile(path, data, callback) {
  var sh = new fs.Shell();
  var basedir = Path.dirname(path);

  sh.mkdirp(basedir, function(err) {
    if (err) {
      return callback(err);
    }

    fs.writeFile(path, new Buffer(data), { encoding: null }, callback);
  });
}

// Installs a tarball (arraybuffer) containing the project's files/folders.
function installTarball(config, tarball, callback) {
  var untarWorker;
  var pending = null;
  var root = config.root;
  var pendingOperations = config.syncQueue.pending;

  function maybeExtract(path, data, callback) {
    path = Path.join(root, path);

    if (shouldSkipPath(pendingOperations, path)) {
      callback();
      return;
    }

    installFile(path, data, callback);
  }

  function finish(err) {
    untarWorker.terminate();
    untarWorker = null;

    callback(err);
  }

  function writeCallback(err) {
    if (err) {
      console.error("[Thimble error] couldn't extract file for tar", err);
    }

    pending--;
    if (pending === 0) {
      finish(err);
    }
  }

  untarWorker = new Worker("/resources/scripts/bitjs-untar-worker.min.js");
  untarWorker.addEventListener("message", function(e) {
    var data = e.data;

    if (data.type === "progress" && pending === null) {
      // Set the total number of files we need to deal with so we know when we're done
      pending = data.totalFilesInArchive;
    } else if (data.type === "extract") {
      maybeExtract(
        data.unarchivedFile.filename,
        data.unarchivedFile.fileData,
        writeCallback
      );
    } else if (data.type === "error") {
      finish(new Error("[Thimble error]: " + data.msg));
    }
  });

  untarWorker.postMessage({ file: tarball });
}

function loadTarball(config, callback) {
  // jQuery doesn't seem to support getting the arraybuffer type
  var url = config.host + "/projects";
  if (config.remixId || config.id) {
    url += "/" + (config.remixId || config.id);
  }
  url += "/files/data?cacheBust=" + new Date().toISOString();

  var xhr = new XMLHttpRequest();
  xhr.open("GET", url, true);
  xhr.responseType = "arraybuffer";
  xhr.onload = function() {
    if (this.status !== 200) {
      return callback(
        new Error(
          "[Thimble error] unable to get tarball, status was:",
          this.status
        )
      );
    }

    installTarball(config, this.response, callback);
  };
  xhr.send();
}

// Load all files as separate requests.  File data is of the form:
// [{ id: 1, path: "/index.html", project_id: 3 }, ... ]
function loadFiles(config, callback) {
  if (!Array.isArray(config.data) && config.data.length > 0) {
    return callback(new Error("file metadata was missing or in wrong form"));
  }

  var root = config.root;
  var url = config.host + "/files/";

  $.when
    .apply(
      $,
      config.data.map(function(fileInfo) {
        var deferred = $.Deferred();
        var path = Path.join(root, fileInfo.path);
        var pendingOperations = config.syncQueue.pending;

        if (shouldSkipPath(pendingOperations, path)) {
          return deferred.resolve().promise();
        }

        // jQuery doesn't seem to support getting the arraybuffer type
        var xhr = new XMLHttpRequest();
        xhr.open("GET", url + fileInfo.id, true);
        xhr.responseType = "arraybuffer";
        xhr.onload = function() {
          if (this.status !== 200) {
            return deferred.reject();
          }

          installFile(path, this.response, function(err) {
            if (err) {
              return deferred.reject();
            }
            deferred.resolve();
          });
        };
        xhr.send();

        return deferred.promise();
      })
    )
    .then(callback, function() {
      callback(new Error("[Thimble error] unable to load project files."));
    });
}

function upgradeAnonymousProject(config, callback) {
  var shell = new fs.Shell();
  var oldRoot = Path.join(
    constants.ANONYMOUS_USER_FOLDER,
    config.anonymousId.toString()
  );
  var newRoot = config.root;
  var pathUpdatesCache = [];

  function upgradeFile(path, next) {
    if (path.match(/\/$/)) {
      return next();
    }

    fs.readFile(path, function(err, data) {
      if (err) {
        return next(err);
      }

      var relativePath = Path.relative(oldRoot, path);
      var newPath = Path.join(newRoot, relativePath);
      var parent = Path.dirname(newPath);

      shell.mkdirp(parent, function(err) {
        if (err) {
          return next(err);
        }

        fs.writeFile(newPath, data, { encoding: null }, function(err) {
          if (err) {
            return next(err);
          }

          // Track this as a file to be persisted to publish
          pathUpdatesCache.push(newPath);
          next();
        });
      });
    });
  }

  shell.find(oldRoot, { exec: upgradeFile }, function(err) {
    if (err) {
      console.error(
        "[Thimble Error] unable to process path while upgrading anonymous project",
        err
      );
      return callback(err);
    }

    shell.rm(oldRoot, { recursive: true }, function(err) {
      if (err) {
        console.error(
          "[Thimble Error] unable to remove path while upgrading anonymous project",
          err
        );
        return callback(err);
      }

      // Send back the list of paths to be updated
      callback(null, pathUpdatesCache);
    });
  });
}

function load(config, callback) {
  // Support loading projects as a single tarball or as a set of individual files.
  // Change the value via .env on the server and the PROJECT_LOAD_STRATEGY variable.
  // Default to loading a tarball.
  if (config.projectLoadStrategy === "files") {
    loadFiles(config, callback);
  } else {
    loadTarball(config, callback);
  }
}

function loadProject(config, callback) {
  if (config.user) {
    if (config.anonymousId) {
      return upgradeAnonymousProject(config, callback);
    }

    return load(config, callback);
  }

  // First, check if this anonymous project already exists by checking the
  // root. If it exists, we've done this before and no loading is required
  fs.stat(config.root, function(err) {
    if (err) {
      if (err.code !== "ENOENT") {
        return callback(err);
      }

      // Anonymous project does not exist
      new fs.Shell().mkdirp(config.root, function(err) {
        if (err) {
          return callback(err);
        }

        // Prefer loading as tarball if this is the default project, even if loadfiles=1.
        loadTarball(config, callback);
      });
    } else {
      callback();
    }
  });
}

module.exports = {
  loadProject: loadProject
};
<|endoftext|>


<commit_before>function path(fp) {
    if (fp[fp.length -1] == '/')
        return fp;
    var last = fp.lastIndexOf('/');
    if (last < 0)
        return '.';
    return fp.slice(0, last);
}

function fileName(fph) {
    var fp = fph.toString();
    if (fp[fp.length -1] == '/')
        return fp;
    var last = fp.lastIndexOf('/');
    if (last < 0)
        return '.';
    return fp.slice(last + 1);
}

function baseName(fph) {
    var fn = fileName(fph);
    return fn.split('.')[0];
}

function relativePath(base, rel)
{
    var basel = base.split('/');
    var rell  = rel.split('/');
    var i = 0;

    while (i < basel.length && i < rell.length && basel[i] == rell[i])
        i++;

    var j = i;
    var r = [];

    for (; i < basel.length; i++)
        r.push('..');

    for (; j < rell.length; j++)
        r.push(rell[j]);

    return r.join('/');
}

function isAbsolutePath(path)
{
    if (!path)
        return false;
    return (path.charAt(0) == '/');
}

function toWindowsSeparators(str)
{
    return str.toString().replace(/\//g, '\\');
}

function fromWindowsSeparators(str)
{
    return str.toString().replace(/\\/g, '/');
}

var removeDoubleSlashesPattern = new RegExp("/{2,}", "g")
removeDoubleSlashesPattern.compile()

function joinPaths()
{
    function pathFilter(path) {
        return path && typeof path === 'string';
    }

    var paths = Array.prototype.slice.call(arguments, 0).filter(pathFilter);
    var joinedPaths = paths.join('/');

    return joinedPaths.replace(removeDoubleSlashesPattern, "/")
}
<commit_msg>work around weird JavaScript bug^Wfeature<commit_after>function path(fp) {
    if (fp[fp.length -1] == '/')
        return fp;
    var last = fp.lastIndexOf('/');
    if (last < 0)
        return '.';
    return fp.slice(0, last);
}

function fileName(fph) {
    var fp = fph.toString();
    if (fp[fp.length -1] == '/')
        return fp;
    var last = fp.lastIndexOf('/');
    if (last < 0)
        return '.';
    return fp.slice(last + 1);
}

function baseName(fph) {
    var fn = fileName(fph);
    return fn.split('.')[0];
}

function relativePath(base, rel)
{
    var basel = base.split('/');
    var rell  = rel.split('/');
    var i = 0;

    while (i < basel.length && i < rell.length && basel[i] == rell[i])
        i++;

    var j = i;
    var r = [];

    for (; i < basel.length; i++)
        r.push('..');

    for (; j < rell.length; j++)
        r.push(rell[j]);

    return r.join('/');
}

function isAbsolutePath(path)
{
    if (!path)
        return false;
    return (path.charAt(0) == '/');
}

function toWindowsSeparators(str)
{
    return str.toString().replace(/\//g, '\\');
}

function fromWindowsSeparators(str)
{
    return str.toString().replace(/\\/g, '/');
}

var removeDoubleSlashesPattern = new RegExp("/{2,}", "g")

function joinPaths()
{
    function pathFilter(path) {
        return path && typeof path === 'string';
    }

    var paths = Array.prototype.slice.call(arguments, 0).filter(pathFilter);
    var joinedPaths = paths.join('/');

    return joinedPaths.replace(removeDoubleSlashesPattern, "/")
}
<|endoftext|>


<commit_before>// @flow
import React, {Component} from 'react'
import {TextInput, Animated} from 'react-native'
import {Box, Text, Icon, ClickableBox} from '../../common-adapters'
import {globalColors, globalMargins, globalStyles} from '../../styles'
import IconOrAvatar from '../icon-or-avatar'
import {followingStateToStyle} from '../shared'
import {getStyle as getTextStyle} from '../../common-adapters/text'

import type {UserDetails, Props} from './'

type UserItemProps = UserDetails & {onRemoveUser: (id: string) => void}
type UserItemState = {isSelected: boolean, selectAnim: Animated.Value}

class UserItem extends Component<void, UserItemProps, UserItemState> {
  state = {
    isSelected: false,
    selectAnim: new Animated.Value(0),
  }

  _onRemoveUser = () => {
    this.props.onRemoveUser(this.props.id)
  }

  _onSelect = () => {
    Animated.timing(this.state.selectAnim, {toValue: 1, duration: 100}).start()
    this.setState({isSelected: true})
  }

  _onDeselect = () => {
    Animated.timing(this.state.selectAnim, {toValue: 0, duration: 100}).start()
    this.setState({isSelected: false})
  }

  _onChangeText = (text: string) => {
    if (!text.length) {
      this._onDeselect()
      this._onRemoveUser()
    }
  }

  render() {
    const {followingState, icon, service, username} = this.props
    const {isSelected, selectAnim} = this.state

    const usernameStyle = followingStateToStyle(followingState)
    return (
      <Box style={{...globalStyles.flexBoxRow, marginRight: globalMargins.xtiny}}>
        <ClickableBox feedback={false} onClick={this._onSelect}>
          <Animated.View
            style={{
              ..._pillStyle,
              backgroundColor: selectAnim.interpolate({
                inputRange: [0, 1],
                outputRange: [globalColors.white_0, globalColors.blue],
              }),
            }}
          >
            <IconOrAvatar icon={icon} service={service} username={username} avatarSize={16} />
            <Animated.Text
              style={{
                ..._pillTextStyle,
                ...usernameStyle,
                ...globalStyles.fontSemibold,
                color: selectAnim.interpolate({
                  inputRange: [0, 1],
                  outputRange: [usernameStyle.color, globalColors.white],
                }),
                marginLeft: 3,
              }}
            >
              {username}
            </Animated.Text>
          </Animated.View>
        </ClickableBox>
        <Text
          type="BodySemibold"
          style={{
            ..._pillTextStyle,
            color: globalColors.black_20,
          }}
        >
          ,
        </Text>
        {/* We can't listen to keyboard events in RN Android, so instead we
            create an offscreen text input with a single space character and
            observe it being removed when the value changes. */}
        {isSelected &&
          <TextInput
            autoFocus={true}
            onBlur={this._onDeselect}
            onChangeText={this._onChangeText}
            value=" "
            style={{position: 'absolute', left: -9999}}
          />}
      </Box>
    )
  }
}

type State = {isFocused: boolean}

class UserInput extends Component<void, Props, State> {
  _textInput: TextInput

  state = {
    isFocused: false,
  }

  focus = () => {
    this._textInput && this._textInput.focus()
  }

  _onFocus = () => {
    this.setState({isFocused: true})
  }

  _onBlur = () => {
    this.setState({isFocused: false})
  }

  _onRemoveUser = (username: string) => {
    this.props.onRemoveUser(username)
    this.focus()
  }

  render() {
    const {
      autoFocus,
      placeholder,
      userItems,
      usernameText,
      onChangeText,
      onClickAddButton,
      onAddSelectedUser,
      onClearSearch,
    } = this.props

    const showAddButton = !!userItems.length && !usernameText.length && onClickAddButton
    return (
      <ClickableBox feedback={false} onClick={this.focus}>
        <Box style={{...globalStyles.flexBoxRow, alignItems: 'center', flexWrap: 'wrap'}}>
          {userItems.map(item => <UserItem {...item} onRemoveUser={this._onRemoveUser} key={item.id} />)}
          <Box
            style={{
              ...globalStyles.flexBoxRow,
              height: 24,
              alignItems: 'center',
              flexGrow: 1,
              minWidth: 50,
            }}
          >
            <TextInput
              autoFocus={autoFocus}
              autoCorrect={false}
              autoCapitalize={'none'}
              ref={el => {
                this._textInput = el
              }}
              onFocus={this._onFocus}
              onBlur={this._onBlur}
              style={{
                ..._inputStyle,
                ...(showAddButton ? {width: this.state.isFocused ? 10 : 0} : {flexGrow: 1}),
              }}
              placeholder={userItems.length ? '' : placeholder}
              value={usernameText}
              onChangeText={onChangeText}
              onSubmitEditing={onAddSelectedUser}
              returnKeyType="next"
            />
            {showAddButton &&
              onClickAddButton &&
              <Icon
                onClick={onClickAddButton}
                type="iconfont-add"
                style={{
                  fontSize: 16,
                  height: 16,
                  color: globalColors.blue,
                }}
              />}
          </Box>
          {onClearSearch &&
            <Icon
              type="iconfont-remove"
              style={{height: 16, width: 16, marginRight: 16}}
              onClick={onClearSearch}
            />}
        </Box>
      </ClickableBox>
    )
  }
}

const _pillStyle = {
  ...globalStyles.flexBoxRow,
  alignItems: 'center',
  height: 24,
  paddingTop: 2,
  paddingBottom: 2,
  paddingLeft: 1,
  paddingRight: 1,
  borderRadius: 2,
}

const _pillTextStyle = {
  fontSize: 14,
  lineHeight: 20,
  height: 22,
}

const _inputStyle = {
  ...getTextStyle('Body'),
  fontSize: 14,
  color: globalColors.black_75,
  lineHeight: 20,
  height: 22,
  paddingTop: 2,
  paddingBottom: 2,
}

export default UserInput
<commit_msg>remove green underline on android (#7809)<commit_after>// @flow
import React, {Component} from 'react'
import {TextInput, Animated} from 'react-native'
import {Box, Text, Icon, ClickableBox} from '../../common-adapters'
import {globalColors, globalMargins, globalStyles} from '../../styles'
import IconOrAvatar from '../icon-or-avatar'
import {followingStateToStyle} from '../shared'
import {getStyle as getTextStyle} from '../../common-adapters/text'

import type {UserDetails, Props} from './'

type UserItemProps = UserDetails & {onRemoveUser: (id: string) => void}
type UserItemState = {isSelected: boolean, selectAnim: Animated.Value}

class UserItem extends Component<void, UserItemProps, UserItemState> {
  state = {
    isSelected: false,
    selectAnim: new Animated.Value(0),
  }

  _onRemoveUser = () => {
    this.props.onRemoveUser(this.props.id)
  }

  _onSelect = () => {
    Animated.timing(this.state.selectAnim, {toValue: 1, duration: 100}).start()
    this.setState({isSelected: true})
  }

  _onDeselect = () => {
    Animated.timing(this.state.selectAnim, {toValue: 0, duration: 100}).start()
    this.setState({isSelected: false})
  }

  _onChangeText = (text: string) => {
    if (!text.length) {
      this._onDeselect()
      this._onRemoveUser()
    }
  }

  render() {
    const {followingState, icon, service, username} = this.props
    const {isSelected, selectAnim} = this.state

    const usernameStyle = followingStateToStyle(followingState)
    return (
      <Box style={{...globalStyles.flexBoxRow, marginRight: globalMargins.xtiny}}>
        <ClickableBox feedback={false} onClick={this._onSelect}>
          <Animated.View
            style={{
              ..._pillStyle,
              backgroundColor: selectAnim.interpolate({
                inputRange: [0, 1],
                outputRange: [globalColors.white_0, globalColors.blue],
              }),
            }}
          >
            <IconOrAvatar icon={icon} service={service} username={username} avatarSize={16} />
            <Animated.Text
              style={{
                ..._pillTextStyle,
                ...usernameStyle,
                ...globalStyles.fontSemibold,
                color: selectAnim.interpolate({
                  inputRange: [0, 1],
                  outputRange: [usernameStyle.color, globalColors.white],
                }),
                marginLeft: 3,
              }}
            >
              {username}
            </Animated.Text>
          </Animated.View>
        </ClickableBox>
        <Text
          type="BodySemibold"
          style={{
            ..._pillTextStyle,
            color: globalColors.black_20,
          }}
        >
          ,
        </Text>
        {/* We can't listen to keyboard events in RN Android, so instead we
            create an offscreen text input with a single space character and
            observe it being removed when the value changes. */}
        {isSelected &&
          <TextInput
            autoFocus={true}
            onBlur={this._onDeselect}
            onChangeText={this._onChangeText}
            value=" "
            underlineColorAndroid="transparent"
            style={{position: 'absolute', left: -9999}}
          />}
      </Box>
    )
  }
}

type State = {isFocused: boolean}

class UserInput extends Component<void, Props, State> {
  _textInput: TextInput

  state = {
    isFocused: false,
  }

  focus = () => {
    this._textInput && this._textInput.focus()
  }

  _onFocus = () => {
    this.setState({isFocused: true})
  }

  _onBlur = () => {
    this.setState({isFocused: false})
  }

  _onRemoveUser = (username: string) => {
    this.props.onRemoveUser(username)
    this.focus()
  }

  render() {
    const {
      autoFocus,
      placeholder,
      userItems,
      usernameText,
      onChangeText,
      onClickAddButton,
      onAddSelectedUser,
      onClearSearch,
    } = this.props

    const showAddButton = !!userItems.length && !usernameText.length && onClickAddButton
    return (
      <ClickableBox feedback={false} onClick={this.focus}>
        <Box style={{...globalStyles.flexBoxRow, alignItems: 'center', flexWrap: 'wrap'}}>
          {userItems.map(item => <UserItem {...item} onRemoveUser={this._onRemoveUser} key={item.id} />)}
          <Box
            style={{
              ...globalStyles.flexBoxRow,
              height: 24,
              alignItems: 'center',
              flexGrow: 1,
              minWidth: 50,
            }}
          >
            <TextInput
              autoFocus={autoFocus}
              autoCorrect={false}
              autoCapitalize={'none'}
              ref={el => {
                this._textInput = el
              }}
              onFocus={this._onFocus}
              onBlur={this._onBlur}
              style={{
                ..._inputStyle,
                ...(showAddButton ? {width: this.state.isFocused ? 10 : 0} : {flexGrow: 1}),
              }}
              placeholder={userItems.length ? '' : placeholder}
              value={usernameText}
              onChangeText={onChangeText}
              onSubmitEditing={onAddSelectedUser}
              underlineColorAndroid="transparent"
              returnKeyType="next"
            />
            {showAddButton &&
              onClickAddButton &&
              <Icon
                onClick={onClickAddButton}
                type="iconfont-add"
                style={{
                  fontSize: 16,
                  height: 16,
                  color: globalColors.blue,
                }}
              />}
          </Box>
          {onClearSearch &&
            <Icon
              type="iconfont-remove"
              style={{height: 16, width: 16, marginRight: 16}}
              onClick={onClearSearch}
            />}
        </Box>
      </ClickableBox>
    )
  }
}

const _pillStyle = {
  ...globalStyles.flexBoxRow,
  alignItems: 'center',
  height: 24,
  paddingTop: 2,
  paddingBottom: 2,
  paddingLeft: 1,
  paddingRight: 1,
  borderRadius: 2,
}

const _pillTextStyle = {
  fontSize: 14,
  lineHeight: 20,
  height: 22,
}

const _inputStyle = {
  ...getTextStyle('Body'),
  fontSize: 14,
  color: globalColors.black_75,
  lineHeight: 20,
  height: 22,
  paddingTop: 2,
  paddingBottom: 2,
}

export default UserInput
<|endoftext|>


<commit_before>/**
 * JS implementation of PHP's http_build_query()
 * @see https://github.com/vladzadvorny/http-build-query
 * @type {httpBuildQuery}
 */
const httpBuildQuery = require('http-build-query');

/**
 * Helpers functions
 */
module.exports = class Utils {
  /**
   * Encode data object to urlencoded string
   * {name: 'taly', id: 2} -> "name=taly&id=2"
   *
   * @param {object} data
   * @return {string}
   */
  static urlEncode(data) {
    return httpBuildQuery(data);
  }

  /**
   * Encode data to JSON string
   *
   * @param {*} data
   * @return {string}
   */
  static jsonEncode(data) {
    return JSON.stringify(data);
  }

  /**
   * Encode data to FormData object
   *
   * @param {object|FormData|HTMLElement} data
   * @return {FormData}
   */
  static formEncode(data) {
    /**
     * If data is a FormData object
     */
    if (this.isFormData(data)) {
      return data;
    }

    /**
     * If data is a FORM element
     */
    if (this.isFormElement(data)) {
      return new FormData(data);
    }

    /**
     * If data is just an object
     */
    if (this.isObject(data)) {
      let requestData = new FormData();

      Object.keys(data).forEach(key => {
        const value = data[key];

        requestData.append(key, value);
      });

      return requestData;
    }

    /**
     * Otherwise throw an error
     */
    throw new Error('`data` must be an instance of Object, FormData or <FORM> HTMLElement');
  }

  /**
   * Check if variable is an Object
   *
   * @param {*} obj
   * @return {boolean}
   */
  static isObject(obj) {
    return Object.prototype.toString.call(obj) === '[object Object]';
  };

  /**
   * Check if variable is an instance of FormData
   *
   * @param {*} obj
   * @return {boolean}
   */
  static isFormData(obj) {
    return obj instanceof FormData;
  };

  /**
   * Check if variable is a HTMLFormElement
   *
   * @param {*} obj
   * @return {boolean}
   */
  static isFormElement(obj) {
    return obj instanceof HTMLFormElement;
  }

  /**
   * @typedef {object} transportParams
   * @property {string} accept
   * @property {boolean} multiple
   * @property {string} fieldName
   * @property {function} beforeSend
   */

  /**
   * Create an ephemeral input file field and return FormData object with files
   *
   * @param {transportParams} config
   * @return {Promise<FormData>}
   */
  static transport(config) {
    return new Promise((resolve, reject) => {
      /**
       * Create a new INPUT element
       * @type {HTMLElement}
       */
      let inputElement = document.createElement('INPUT');

      /**
       * Set a 'FILE' type for this input element
       * @type {string}
       */
      inputElement.type = 'file';

      if (config.multiple) {
        inputElement.setAttribute('multiple', 'multiple');
      }

      if (config.accept) {
        inputElement.setAttribute('accept', config.accept);
      }

      /**
       * Do not show element
       */
      inputElement.style.display = "none";

      /**
       * Append element to the body
       * Fix using module on mobile devices
       */
      document.body.appendChild(inputElement);

      /**
       * Add onchange listener for «choose file» pop-up
       */
      inputElement.addEventListener('change', event => {

        /**
         * Get files from input field
         */
        const files = event.target.files;

        /**
         * Create a FormData object
         * @type {FormData}
         */
        let formData = new FormData();

        /**
         * Append files to FormData
         */
        for (let i = 0; i < files.length; i++) {
          formData.append(config.fieldName, files[i], files[i].name);
        }

        /**
         * Fire beforeSend callback on changed files field
         */
        config.beforeSend(files);

        /**
         * Return ready to be uploaded FormData object
         */
        resolve(formData);
      }, false);

      /**
       * Fire click event on «input file» field
       */
      inputElement.click();
    });
  };

  /**
   * Parse string of headers
   *
   * From
   *   "cache-control: public, max-age=14400
   *   content-type: application/json; charset=utf-8
   *   ..."
   *
   * To
   *   {
   *     cache-control: "public, max-age=14400",
   *     content-type: "application/json; charset=utf-8",
   *     ...
   *   }
   *
   * @param {string} headersString
   * @return {object}
   */
  static parseHeaders(headersString) {
    /**
     * Convert the header string into an array of individual headers
     * Split by line breaks
     */
    let headersArray = headersString.trim().split(/[\r\n]+/);

    /**
     * Create a map of header names to values
     */
    let headerMap = {};

    headersArray.forEach(function (line) {
      /**
       * Split header string by ": "
       * @type {string[]}
       */
      const parts = line.split(': ');

      /**
       * Get the first chunk of splitted string from array
       * @type {string | undefined}
       */
      const header = parts.shift();

      /**
       * Join all chunks left in an array by separator
       * @type {string}
       */
      const value = parts.join(': ');

      if (header) {
        headerMap[header] = value;
      }
    });

    return headerMap;
  }
};
<commit_msg>Remove blank line<commit_after>/**
 * JS implementation of PHP's http_build_query()
 * @see https://github.com/vladzadvorny/http-build-query
 * @type {httpBuildQuery}
 */
const httpBuildQuery = require('http-build-query');

/**
 * Helpers functions
 */
module.exports = class Utils {
  /**
   * Encode data object to urlencoded string
   * {name: 'taly', id: 2} -> "name=taly&id=2"
   *
   * @param {object} data
   * @return {string}
   */
  static urlEncode(data) {
    return httpBuildQuery(data);
  }

  /**
   * Encode data to JSON string
   *
   * @param {*} data
   * @return {string}
   */
  static jsonEncode(data) {
    return JSON.stringify(data);
  }

  /**
   * Encode data to FormData object
   *
   * @param {object|FormData|HTMLElement} data
   * @return {FormData}
   */
  static formEncode(data) {
    /**
     * If data is a FormData object
     */
    if (this.isFormData(data)) {
      return data;
    }

    /**
     * If data is a FORM element
     */
    if (this.isFormElement(data)) {
      return new FormData(data);
    }

    /**
     * If data is just an object
     */
    if (this.isObject(data)) {
      let requestData = new FormData();

      Object.keys(data).forEach(key => {
        const value = data[key];

        requestData.append(key, value);
      });

      return requestData;
    }

    /**
     * Otherwise throw an error
     */
    throw new Error('`data` must be an instance of Object, FormData or <FORM> HTMLElement');
  }

  /**
   * Check if variable is an Object
   *
   * @param {*} obj
   * @return {boolean}
   */
  static isObject(obj) {
    return Object.prototype.toString.call(obj) === '[object Object]';
  };

  /**
   * Check if variable is an instance of FormData
   *
   * @param {*} obj
   * @return {boolean}
   */
  static isFormData(obj) {
    return obj instanceof FormData;
  };

  /**
   * Check if variable is a HTMLFormElement
   *
   * @param {*} obj
   * @return {boolean}
   */
  static isFormElement(obj) {
    return obj instanceof HTMLFormElement;
  }

  /**
   * @typedef {object} transportParams
   * @property {string} accept
   * @property {boolean} multiple
   * @property {string} fieldName
   * @property {function} beforeSend
   */

  /**
   * Create an ephemeral input file field and return FormData object with files
   *
   * @param {transportParams} config
   * @return {Promise<FormData>}
   */
  static transport(config) {
    return new Promise((resolve, reject) => {
      /**
       * Create a new INPUT element
       * @type {HTMLElement}
       */
      let inputElement = document.createElement('INPUT');

      /**
       * Set a 'FILE' type for this input element
       * @type {string}
       */
      inputElement.type = 'file';

      if (config.multiple) {
        inputElement.setAttribute('multiple', 'multiple');
      }

      if (config.accept) {
        inputElement.setAttribute('accept', config.accept);
      }

      /**
       * Do not show element
       */
      inputElement.style.display = "none";

      /**
       * Append element to the body
       * Fix using module on mobile devices
       */
      document.body.appendChild(inputElement);

      /**
       * Add onchange listener for «choose file» pop-up
       */
      inputElement.addEventListener('change', event => {
        /**
         * Get files from input field
         */
        const files = event.target.files;

        /**
         * Create a FormData object
         * @type {FormData}
         */
        let formData = new FormData();

        /**
         * Append files to FormData
         */
        for (let i = 0; i < files.length; i++) {
          formData.append(config.fieldName, files[i], files[i].name);
        }

        /**
         * Fire beforeSend callback on changed files field
         */
        config.beforeSend(files);

        /**
         * Return ready to be uploaded FormData object
         */
        resolve(formData);
      }, false);

      /**
       * Fire click event on «input file» field
       */
      inputElement.click();
    });
  };

  /**
   * Parse string of headers
   *
   * From
   *   "cache-control: public, max-age=14400
   *   content-type: application/json; charset=utf-8
   *   ..."
   *
   * To
   *   {
   *     cache-control: "public, max-age=14400",
   *     content-type: "application/json; charset=utf-8",
   *     ...
   *   }
   *
   * @param {string} headersString
   * @return {object}
   */
  static parseHeaders(headersString) {
    /**
     * Convert the header string into an array of individual headers
     * Split by line breaks
     */
    let headersArray = headersString.trim().split(/[\r\n]+/);

    /**
     * Create a map of header names to values
     */
    let headerMap = {};

    headersArray.forEach(function (line) {
      /**
       * Split header string by ": "
       * @type {string[]}
       */
      const parts = line.split(': ');

      /**
       * Get the first chunk of splitted string from array
       * @type {string | undefined}
       */
      const header = parts.shift();

      /**
       * Join all chunks left in an array by separator
       * @type {string}
       */
      const value = parts.join(': ');

      if (header) {
        headerMap[header] = value;
      }
    });

    return headerMap;
  }
};
<|endoftext|>


<commit_before>const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch({ args: ['--no-sandbox', '--disable-setuid-sandbox'] })
  const page = await browser.newPage()
  page.setViewport({ width: 1920, height: 1080 })
  await page.goto('http://localhost:8080')

  await page.click('#theme_select')
  await clickToNavigateAndScreenshot(page, '#navbar_home', 'screenshot_0_connect_white.png')
  await connectWithServer(page)
  await removeSnackbar(page)
  await clickToNavigateAndScreenshot(page, '#navbar_home', 'screenshot_1_home_white.png')
  await page.click('#theme_select')
  await clickToNavigateAndScreenshot(page, '#navbar_nodes', 'screenshot_2_nodes.png')
  await clickToNavigateAndScreenshot(page, '#navbar_indices', 'screenshot_3_indices.png')
  await clickToNavigateAndScreenshot(page, ['button[title="Options"]', 'a[href$="/stats"]'], 'screenshot_4_index.png')

  await page.reload()
  await clickToNavigateAndScreenshot(page, '#navbar_search', 'screenshot_5_search_dark.png', async page => {
    await page.click('#indices')
    await page.waitFor(50)
    await page.click('input[type="checkbox"]')
    await page.click('#search_submit')
    await page.waitFor(250)
    await page.click('th[aria-label="author_name: Not sorted. Activate to sort ascending."')
    await page.waitFor(250)
  })
  await clickToNavigateAndScreenshot(page, ['#navbar_query', '#navbar_query_rest'], 'screenshot_6_query_dark.png', async page => {
    await page.click('#execute_query')
    await page.waitFor(500)
  })
  await clickToNavigateAndScreenshot(page, '#navbar_utilities', 'screenshot_7_utilities_dark.png')

  await browser.close()
})()

async function connectWithServer (page) {
  await page.waitFor('#test_connection')
  await page.click('#test_connection')
  await page.waitFor('#connect:not(.v-btn--disabled)')
  await page.click('#connect')
  await page.waitFor(50)
}

async function removeSnackbar (page) {
  await page.waitFor('.v-snack')
  await page.evaluate(() => {
    let div = document.querySelector('.v-snack')
    div.parentNode.removeChild(div)
  })
}

async function clickToNavigateAndScreenshot (page, selectors, screenshot, callback) {
  if (Array.isArray(selectors)) {
    for (let selector of selectors) {
      await page.waitFor(200)
      await page.click(selector)
    }
  } else {
    await page.waitFor(200)
    await page.click(selectors)
  }

  await page.waitFor(500)
  if (typeof callback === 'function') await callback(page)
  await page.screenshot({ path: 'scripts/website_screenshots/' + screenshot })
}
<commit_msg>use jpg for website screenshots<commit_after>const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch({ args: ['--no-sandbox', '--disable-setuid-sandbox'] })
  const page = await browser.newPage()
  page.setViewport({ width: 1920, height: 1080 })
  await page.goto('http://localhost:8080')

  await page.click('#theme_select')
  await clickToNavigateAndScreenshot(page, '#navbar_home', 'screenshot_0_connect_white.jpg')
  await connectWithServer(page)
  await removeSnackbar(page)
  await clickToNavigateAndScreenshot(page, '#navbar_home', 'screenshot_1_home_white.jpg')
  await page.click('#theme_select')
  await clickToNavigateAndScreenshot(page, '#navbar_nodes', 'screenshot_2_nodes.jpg')
  await clickToNavigateAndScreenshot(page, '#navbar_indices', 'screenshot_3_indices.jpg')
  await clickToNavigateAndScreenshot(page, ['button[title="Options"]', 'a[href$="/stats"]'], 'screenshot_4_index.jpg')

  await page.reload()
  await clickToNavigateAndScreenshot(page, '#navbar_search', 'screenshot_5_search_dark.jpg', async page => {
    await page.click('#indices')
    await page.waitFor(50)
    await page.click('input[type="checkbox"]')
    await page.click('#search_submit')
    await page.waitFor(250)
    await page.click('th[aria-label="author_name: Not sorted. Activate to sort ascending."')
    await page.waitFor(250)
  })
  await clickToNavigateAndScreenshot(page, ['#navbar_query', '#navbar_query_rest'], 'screenshot_6_query_dark.jpg', async page => {
    await page.click('#execute_query')
    await page.waitFor(500)
  })
  await clickToNavigateAndScreenshot(page, '#navbar_utilities', 'screenshot_7_utilities_dark.jpg')

  await browser.close()
})()

async function connectWithServer (page) {
  await page.waitFor('#test_connection')
  await page.click('#test_connection')
  await page.waitFor('#connect:not(.v-btn--disabled)')
  await page.click('#connect')
  await page.waitFor(50)
}

async function removeSnackbar (page) {
  await page.waitFor('.v-snack')
  await page.evaluate(() => {
    let div = document.querySelector('.v-snack')
    div.parentNode.removeChild(div)
  })
}

async function clickToNavigateAndScreenshot (page, selectors, screenshot, callback) {
  if (Array.isArray(selectors)) {
    for (let selector of selectors) {
      await page.waitFor(200)
      await page.click(selector)
    }
  } else {
    await page.waitFor(200)
    await page.click(selectors)
  }

  await page.waitFor(500)
  if (typeof callback === 'function') await callback(page)
  await page.screenshot({ path: 'scripts/website_screenshots/' + screenshot })
}
<|endoftext|>


<commit_before>import Phaser from 'phaser'

export const centerGameObjects = objects => {
  objects.forEach(
    object => object.anchor.setTo(0.5)
  )
}

export const enableMusicForState = (state) => {
  let Idx = 0

  const synthwave = state.game.add.audio('synthwave', 0.5, true, true)
  synthwave.allowMultiple = true
  synthwave.stop()
  const bigbeat = state.game.add.audio('bigbeat', 0.5, true, true)
  bigbeat.allowMultiple = true
  bigbeat.stop()
  const techno = state.game.add.audio('techno', 0.5, true, true)
  techno.allowMultiple = true
  techno.stop()
  const ambient = state.game.add.audio('ambient', 0.5, true, true)
  ambient.allowMultiple = true
  ambient.stop()

  const tracks = [
    synthwave,
    bigbeat,
    techno,
    ambient
  ]

  tracks[Idx].play()

  const muteButton = state.game.input.keyboard.addKey(Phaser.Keyboard.M)
  muteButton.onDown.add(() => {
    if (tracks[Idx].volume > 0) {
      tracks[Idx].volume = 0
    } else {
      tracks[Idx].volume = 0.35
    }
  }, state)

  const nextSongButton = state.game.input.keyboard.addKey(Phaser.Keyboard.N)
  nextSongButton.onDown.add(() => {
    tracks.map(t => {
      t.volume = 0.35
      t.stop()
    })

    if (Idx === tracks.length - 1) {
      Idx = 0
    } else {
      Idx += 1
    }
    tracks[Idx].play()
  })

  return () => {
    tracks[Idx].stop()
  }
}
<commit_msg>Allow choosing music by index<commit_after>import Phaser from 'phaser'

export const centerGameObjects = objects => {
  objects.forEach(
    object => object.anchor.setTo(0.5)
  )
}

export const enableMusicForState = (state, initialIdx = 0) => {
  let Idx = initialIdx !== 0 ? initialIdx : 0

  const synthwave = state.game.add.audio('synthwave', 0.5, true, true)
  synthwave.allowMultiple = true
  synthwave.stop()
  const bigbeat = state.game.add.audio('bigbeat', 0.5, true, true)
  bigbeat.allowMultiple = true
  bigbeat.stop()
  const techno = state.game.add.audio('techno', 0.5, true, true)
  techno.allowMultiple = true
  techno.stop()
  const ambient = state.game.add.audio('ambient', 0.5, true, true)
  ambient.allowMultiple = true
  ambient.stop()

  const tracks = [
    synthwave,
    bigbeat,
    techno,
    ambient
  ]

  tracks[Idx].play()

  const muteButton = state.game.input.keyboard.addKey(Phaser.Keyboard.M)
  muteButton.onDown.add(() => {
    if (tracks[Idx].volume > 0) {
      tracks[Idx].volume = 0
    } else {
      tracks[Idx].volume = 0.35
    }
  }, state)

  const nextSongButton = state.game.input.keyboard.addKey(Phaser.Keyboard.N)
  nextSongButton.onDown.add(() => {
    tracks.map(t => {
      t.volume = 0.35
      t.stop()
    })

    if (Idx === tracks.length - 1) {
      Idx = 0
    } else {
      Idx += 1
    }
    tracks[Idx].play()
  })

  return () => {
    tracks[Idx].stop()
  }
}
<|endoftext|>


<commit_before>$(function () {
    $('.selectpicker').selectpicker({
        'selectedText': 'cat'
    });

    renderVoicebanks(json);
});

var renderVoicebanks = function(values){
	var pagesCount = Math.ceil(values.voicebanksCount / $pageSize.val());
	var pagesCountArray = new Array();
	for(var i=0; i<pagesCount; i++){
		pagesCountArray[i] = {page:i};
		if(values.pageNow == i){
			pagesCountArray[i].active = true;
		}
	}
	values.pagesCount = pagesCountArray;
	if(values.pageNow-1>=0){
		values.prev = { exist: true, page: values.pageNow-1 };
	}
	if(values.pageNow+1<pagesCount){
		values.next ={ exist: true, page: values.pageNow+1 };
	}

	template = Handlebars.compile($('#all-voicebanks-tmpl').html());
    $('#voicebanks').html(template(values));
}

var showVoicebanks = function(pageNum){
	$.ajax({
	    url: "/json/getAllVoicebanks",
	    data: {
	    	page: pageNum,
	    	pageSize: $pageSize.val(),
	    	sortCode: $sortOrder.find(':selected').data('sort'),
	    	orderCode: $sortOrder.find(':selected').data('order')
	    	},
	    beforeSend: function(){
			            $('#voicebanks').html($("img").attr("src","../images/ajax-loader.gif"));
		            }
	}).done(function(data){
		var values = data;
		renderVoicebanks(values);

	    if(page != values.pageNow){
		    history.pushState("","","/voicebanks?page="+values.pageNow);
			page = values.pageNow; // Global
	    }
	}).fail(function(e){
	    console.log('error!!!');
	    console.log(e);
	}).always(function(){
	    $('.page-button').on('click', function(){
	    	showVoicebanks($(this).data('page'));
	    });

	    $pageSize.on('change', function(){
	    	showVoicebanks(0);
	    });

	    $sortOrder.on('change', function(){
	    	showVoicebanks(0);
	    })
	});
}<commit_msg>fix push state<commit_after>$(function () {
    $('.selectpicker').selectpicker({
        'selectedText': 'cat'
    });

    renderVoicebanks(json);
    history.pushState("","","/voicebanks");
});

var renderVoicebanks = function(values){
	var pagesCount = Math.ceil(values.voicebanksCount / $pageSize.val());
	var pagesCountArray = new Array();
	for(var i=0; i<pagesCount; i++){
		pagesCountArray[i] = {page:i};
		if(values.pageNow == i){
			pagesCountArray[i].active = true;
		}
	}
	values.pagesCount = pagesCountArray;
	if(values.pageNow-1>=0){
		values.prev = { exist: true, page: values.pageNow-1 };
	}
	if(values.pageNow+1<pagesCount){
		values.next ={ exist: true, page: values.pageNow+1 };
	}

	template = Handlebars.compile($('#all-voicebanks-tmpl').html());
    $('#voicebanks').html(template(values));
}

var showVoicebanks = function(pageNum){
	$.ajax({
	    url: "/json/getAllVoicebanks",
	    data: {
	    	page: pageNum,
	    	pageSize: $pageSize.val(),
	    	sortCode: $sortOrder.find(':selected').data('sort'),
	    	orderCode: $sortOrder.find(':selected').data('order')
	    	},
	    beforeSend: function(){
			            $('#voicebanks').html($("img").attr("src","../images/ajax-loader.gif"));
		            }
	}).done(function(data){
		var values = data;
		renderVoicebanks(values);

	    if(page != values.pageNow){
		    history.pushState("","","/voicebanks?page="+values.pageNow);
			page = values.pageNow; // Global
	    }
	}).fail(function(e){
	    console.log('error!!!');
	    console.log(e);
	}).always(function(){
	    $('.page-button').on('click', function(){
	    	showVoicebanks($(this).data('page'));
	    });

	    $pageSize.on('change', function(){
	    	showVoicebanks(0);
	    });

	    $sortOrder.on('change', function(){
	    	showVoicebanks(0);
	    })
	});
}<|endoftext|>


<commit_before>// Copyright (c) 2017 The Absolute Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import cluster from 'cluster';
import os from 'os';

// Policy of dispatching task to another worker available
// cluster.SCHED_NONE to leave it to the operating system
// cluster.SCHED_RR for round-robin
cluster.schedulingPolicy = cluster.SCHED_RR;

if (cluster.isMaster) {
  // master worker
  os.cpus().forEach((cpu) => {
    cluster.fork();
  });
  // recieve message from worker
  cluster.on('exit', (worker, code, siganl) => {
    if (code == 200) {
      // cluster.fork();
    }
  });
} else if (cluster.isWoker) {
  // child worker
  // i.e) server.js
  // import server from server;
  console.log(`worker id: ${cluster.worker.id}`);
}
<commit_msg>Error on worker managers (#328)<commit_after>// Copyright (c) 2017 The Absolute Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import cluster from 'cluster';
import os from 'os';

// Policy of dispatching task to another worker available
// cluster.SCHED_NONE to leave it to the operating system
// cluster.SCHED_RR for round-robin
cluster.schedulingPolicy = cluster.SCHED_RR;

if (cluster.isMaster) {
  // master worker
  os.cpus().forEach((cpu) => {
    cluster.fork();
  });
  // recieve message from worker
  cluster.on('exit', (worker, code, siganl) => {
    if (code == 200) {
      // cluster.fork();
    }
  });
} else if (cluster.isWorker) {
  // child worker
  // i.e) server.js
  // import server from server;
  console.log(`worker id: ${cluster.worker.id}`);
}
<|endoftext|>