<commit_before>/* @flow */
/*eslint-disable react/prop-types */ // Since we're using flow types for props

import React, {Component} from '../base-react'
import {clipboard, shell} from 'electron'
import resolveAssets from '../../../desktop/resolve-assets'

import {intersperse} from '../util/arrays'
import {parseFolderNameToUsers, canonicalizeUsernames, stripPublicTag} from '../util/kbfs'

import {globalStyles, globalColors} from '../styles/style-guide'
import {Text, Input, Terminal, Icon} from '../common-adapters/index'

import {CircularProgress} from 'material-ui'
import {cleanup, allowLoggedOut as allowLoggedOutKBFS} from '../util/kbfs'
import {NotifyPopup} from '../native/notifications'

// This is the only data that the renderer cares about for a folder
import type {FolderInfo, FolderEntry} from './index.render'

function iconPath (isPublic, isEmpty) {
  const pubPart = isPublic ? 'public' : 'private'
  const emptyPart = isEmpty ? 'empty' : 'full'
  return 'file:///' + resolveAssets(`../react-native/react/images/folders/kb-folder-${pubPart}-${emptyPart}.svg`)
}

const Header = props => {
  const openKBFS: () => void = props.openKBFS
  const showUser: () => void = props.showUser

  // $FlowIssue ignore
  const version = __VERSION__ // eslint-disable-line no-undef

  return (

      <Icon hint='Open KBFS folder' type='fa-folder' style={{marginRight: 10}} onClick={openKBFS}/>
      <Icon hint='Open keybase.io web' type='fa-globe' onClick={showUser}/>

      <Icon hint={`Report a bug for version: ${version}`} type='fa-bug' onClick={ () => {
        clipboard.writeText(`Keybase GUI Version: ${version}`)
        shell.openExternal('https://github.com/keybase/client/issues')
        NotifyPopup('Version copied to clipboard')
      }}/>

  )
}

const Footer = props => {
  const debug: boolean = props.debug
  const showHelp: () => void = props.showHelp
  const showMain: () => void = props.showMain
  const quit: () => void = props.quit

  return (

      <Text type='Body' small onClick={showHelp}>Help</Text>
      {debug && <Text type='Body' small onClick={showMain}>Debug</Text>}
      <Text type='Body' small onClick={quit}>Quit</Text>

  )
}

const LoggedoutMessage = props => {
  return (

      <Text type='Body' small style={{alignSelf: 'center', marginTop: 6}}>You're logged out!</Text>
      <Text type='Body' small style={{marginTop: 23, marginBottom: 5, marginLeft: 10}}>From the terminal:</Text>
      <Terminal>
        <Text type='TerminalCommand'>keybase login</Text>
        <Text type='TerminalEmpty'/>
        <Text type='TerminalComment'>or if you're new to Keybase:</Text>
        <Text type='TerminalCommand'>keybase signup</Text>
      </Terminal>
      {allowLoggedOutKBFS && <Text type='Body' small style={{marginTop: 22, marginBottom: 7, marginLeft: 10}}>Or access someone's public folder:</Text>}

  )
}

export default class Render extends Component {
  props: {
    username: ?string,
    openKBFS: () => void,
    openKBFSPublic: (username: ?string) => void,
    openKBFSPrivate: (username: ?string) => void,
    showMain: () => void,
    showHelp: () => void,
    showUser: (username: ?string) => void,
    quit: () => void,
    openingButtonInfo: {
      text: string,
      onClick: () => void
    },
    folders: Array<FolderInfo>,
    debug?: boolean,
    loading: boolean,
    loggedIn: boolean
  };

  render (): ReactElement {
    const {openKBFS, openKBFSPublic, openKBFSPrivate, showMain, showHelp, showUser, quit, username, loggedIn} = this.props

    return (

           showUser(username)}/>
          {!loggedIn && <LoggedoutMessage />}
          <FolderList loading={this.props.loading} username={this.props.username} openKBFSPublic={openKBFSPublic} openKBFSPrivate={openKBFSPrivate} folders={this.props.folders} loggedIn={loggedIn}/>

    )
  }
}

const Row = props => {
  return (

      <Text type='Body' link small key={props.key} style={{marginTop: 4, ...props.textStyle}}>{props.text}</Text>
      {props.children}

  )
}

const FolderRow = props => {
  const {username, folder: {isPublic, isEmpty, openFolder, folderName}} = props
  let line = intersperse(',', canonicalizeUsernames(username, parseFolderNameToUsers(folderName)))

  return <Row
    onClick={openFolder}
    text={line}
    iconStyle={SVGFolderIcon(iconPath(isPublic, isEmpty))}
    textStyle={{color: globalColors.blue}}
    key={isPublic + line}/>
}

const FolderEntryRow = props => {
  const {key, entry} = props
  let inputRef = null

  const openFolder = () => {
    if (inputRef) {
      entry.openFolder(cleanup(inputRef.getValue()))
      inputRef.clearValue()
      inputRef.blur()
    }
  }

  return (
    <Row
      style={{height: 25, position: 'relative'}}
      onClick={() => {}}
      text={entry.prefix}
      iconStyle={SVGFolderIcon(iconPath(entry.isPublic, false))}
      textStyle={{color: globalColors.blue}}
      key={key}>
    <Input
      ref={input => inputRef = input}
      small
      hintText='user1,user2,etc'
      onEnterKeyDown={() => openFolder()}
      style={{width: '100%', marginLeft: entry.prefix ? 2 : 0}} />
     openFolder()}>
  </Row>)
}

const ShowAll = props => {
  return <Row
    onClick={props.onClick}
    text='Show All'
    iconStyle={{...SVGFolderIcon('file:///' + resolveAssets(`../react-native/react/images/see-more.svg`)), marginTop: 2}}
    textStyle={{}}
    key={props.isPublic + 'showAll'}/>
}

class CollapsableFolderList extends Component {
  props: {
    username: ?string,
    folders: Array<FolderInfo|FolderEntry>,
    folderDisplayLimit: number,
    collapsed: boolean,
    onExpand: Function,
    isPublic: boolean
  };

  render () {
    const {collapsed, username, folderDisplayLimit, onExpand} = this.props

    let {folders} = this.props
    // Check if it's bigger by one because it's pointless to have a button
    // that says show all for just one more thing
    let truncated = false
    if (collapsed && folders.length > folderDisplayLimit + 1) {
      folders = folders.slice(0, folderDisplayLimit)
      truncated = true
    }

    return (

        {folders.map(f => {
          const key = f.type === 'entry' ? `entry${f.prefix}` : `${f.isPublic}:${f.folderName}`

          if (f.type === 'entry') {
            return <FolderEntryRow key={key} entry={f} />
          } else {
            return <FolderRow key={key} username={username} folder={f}/>
          }
        })}
        {truncated && <ShowAll onClick={onExpand} isPublic={this.props.isPublic} />}

    )
  }
}

class FolderList extends Component {
  props: {
    loading: boolean,
    username: ?string,
    folders: Array<FolderInfo>,
    openKBFSPublic: (username: ?string) => void,
    openKBFSPrivate: (username: ?string) => void,
    loggedIn: boolean
  };

  state: {
    privateCollapsed: boolean,
    publicCollapsed: boolean
  };

  constructor (props) {
    super(props)
    this.state = {
      privateCollapsed: true,
      publicCollapsed: true
    }
  }

  render () {
    const {username, loggedIn} = this.props

    if (!loggedIn && !allowLoggedOutKBFS) {
      return

    }

    // Remove folders that are just our personal ones, we'll add those in later
    // For consistency. Since we aren't gauranteed we have favorited our own folders.
    const folders = this.props.folders.filter(f => f.type === 'entry' || stripPublicTag(f.folderName) !== username)

    let privateFolders = []
    let publicFolders = []

    if (loggedIn && username) {
      const personalPrivateFolder: FolderInfo = {
        type: 'folder',
        folderName: username,
        isPublic: false,
        isEmpty: true,
        openFolder: () => this.props.openKBFSPrivate(username)
      }
      privateFolders.push(personalPrivateFolder)

      const privateFolderEntry: FolderEntry = {
        type: 'entry',
        isPublic: false,
        prefix: `${username},`,
        openFolder: folder => this.props.openKBFSPrivate(`${username},${folder}`)
      }
      privateFolders.push(privateFolderEntry)
    }

    if (loggedIn && username) {
      const personalPublicFolder: FolderInfo = {
        type: 'folder',
        folderName: username,
        isPublic: true,
        isEmpty: true,
        openFolder: () => this.props.openKBFSPublic(username)
      }

      publicFolders.push(personalPublicFolder)
    }

    const publicFolderEntry: FolderEntry = {
      type: 'entry',
      isPublic: true,
      prefix: '',
      openFolder: folder => this.props.openKBFSPublic(folder)
    }

    publicFolders.push(publicFolderEntry)

    privateFolders = privateFolders.concat(folders.filter(f => !f.isPublic))
    publicFolders = publicFolders.concat(
    folders.filter(f => f.isPublic))
    .map(f => f.type === 'entry' ? f : {...f, folderName: stripPublicTag(f.folderName)})

    const folderDisplayLimit = 5

    return (

        {this.props.loading && (

            <CircularProgress style={styles.loader} mode='indeterminate' size={0.5}/>

)}
        {!!privateFolders.length && (

            <Text type='Body' onClick={() => this.props.openKBFSPrivate('')}>/keybase/private/</Text>
            <CollapsableFolderList
              username={username}
              folders={privateFolders}
              folderDisplayLimit={folderDisplayLimit}
              onExpand={() => this.setState({privateCollapsed: false})}
              isPublic={false}
              collapsed={this.state.privateCollapsed} />

        )}
        <Text type='Body' onClick={() => this.props.openKBFSPublic('')}>/keybase/public/</Text>
        <CollapsableFolderList
          username={username}
          folders={publicFolders}
          folderDisplayLimit={folderDisplayLimit}
          onExpand={() => this.setState({publicCollapsed: false})}
          isPublic
          collapsed={this.state.publicCollapsed} />

    )
  }
}

const styles = {
  container: {
    ...globalStyles.flexBoxColumn,
    flex: 1
  },
  body: {
    ...globalStyles.flexBoxColumn,
    ...globalStyles.rounded,
    ...globalStyles.windowBorder,
    position: 'relative',
    overflow: 'hidden',
    height: 364,
    minHeight: 364,
    maxHeight: 364,
    marginTop: -1, // let arrow not have a border below it
    flex: 1
  },
  arrow: {
    ...globalStyles.topMost,
    width: 0,
    height: 10,
    minHeight: 10,
    borderLeft: '7px solid transparent',
    borderRight: '7px solid transparent',
    borderBottom: `5px solid ${globalColors.grey5}`,
    alignSelf: 'center'
  },
  header: {
    ...globalStyles.flexBoxRow,
    backgroundColor: globalColors.grey5,
    color: globalColors.grey2,
    minHeight: 31,
    maxHeight: 32,
    padding: 10
  },
  folderHeader: {
    ...globalStyles.flexBoxRow,
    justifyContent: 'space-between',
    paddingRight: 18
  },
  folderList: {
    ...globalStyles.flexBoxColumn,
    backgroundColor: globalColors.white,
    position: 'relative',
    flex: 1,
    padding: 10,
    overflowY: 'scroll',
    overflowX: 'hidden'
  },
  loader: {
    position: 'absolute',
    alignSelf: 'center',
    top: 0,
    right: 0,
    opacity: 0.8
  },
  footer: {
    ...globalStyles.flexBoxRow,
    backgroundColor: globalColors.grey5,
    justifyContent: 'space-between',
    padding: 10
  },
  personalTLDStyle: {
    fontSize: 15,
    lineHeight: '18px',
    color: globalColors.lightBlue
  },
  showAllBox: {
    color: globalColors.white,
    ...globalStyles.fontBold,
    fontSize: 8,
    backgroundColor: globalColors.grey3,
    minWidth: 16,
    minHeight: 11,
    borderRadius: 2,
    alignSelf: 'center',
    marginRight: 8
  },
  entryArrow: {
    ...globalStyles.clickable,
    color: globalColors.grey2,
    width: 25,
    fontSize: 13,
    height: 25,
    textAlign: 'center',
    position: 'absolute',
    right: 0,
    top: 5
  }
}

const SVGFolderIcon = svgPath => ({
  height: 22,
  minWidth: 22,
  maxWidth: 22,
  backgroundImage: `url(${svgPath})`,
  backgroundRepeat: 'no-repeat',
  backgroundPosition: 'center center'
})
<commit_msg>reformat text<commit_after>/* @flow */
/*eslint-disable react/prop-types */ // Since we're using flow types for props

import React, {Component} from '../base-react'
import {clipboard, shell} from 'electron'
import resolveAssets from '../../../desktop/resolve-assets'

import {intersperse} from '../util/arrays'
import {parseFolderNameToUsers, canonicalizeUsernames, stripPublicTag} from '../util/kbfs'

import {globalStyles, globalColors} from '../styles/style-guide'
import {Text, Input, Terminal, Icon} from '../common-adapters/index'

import {CircularProgress} from 'material-ui'
import {cleanup, allowLoggedOut as allowLoggedOutKBFS} from '../util/kbfs'
import {NotifyPopup} from '../native/notifications'

// This is the only data that the renderer cares about for a folder
import type {FolderInfo, FolderEntry} from './index.render'

function iconPath (isPublic, isEmpty) {
  const pubPart = isPublic ? 'public' : 'private'
  const emptyPart = isEmpty ? 'empty' : 'full'
  return 'file:///' + resolveAssets(`../react-native/react/images/folders/kb-folder-${pubPart}-${emptyPart}.svg`)
}

const Header = props => {
  const openKBFS: () => void = props.openKBFS
  const showUser: () => void = props.showUser

  // $FlowIssue ignore
  const version = __VERSION__ // eslint-disable-line no-undef

  return (

      <Icon hint='Open KBFS folder' type='fa-folder' style={{marginRight: 10}} onClick={openKBFS}/>
      <Icon hint='Open keybase.io web' type='fa-globe' onClick={showUser}/>

      <Icon hint={`Report a bug for version: ${version}`} type='fa-bug' onClick={ () => {
        clipboard.writeText(`Keybase GUI Version: ${version}`)
        shell.openExternal('https://github.com/keybase/client/issues')
        NotifyPopup('Version copied to clipboard')
      }}/>

  )
}

const Footer = props => {
  const debug: boolean = props.debug
  const showHelp: () => void = props.showHelp
  const showMain: () => void = props.showMain
  const quit: () => void = props.quit

  return (

      <Text type='Body' small onClick={showHelp}>Help</Text>
      {debug && <Text type='Body' small onClick={showMain}>Debug</Text>}
      <Text type='Body' small onClick={quit}>Quit</Text>

  )
}

const LoggedoutMessage = props => {
  return (

      <Text type='Body' small style={{alignSelf: 'center', marginTop: 6}}>You're logged out!</Text>
      <Text type='Body' small style={{marginTop: 23, marginBottom: 5, marginLeft: 10}}>From the terminal:</Text>
      <Terminal>
        <Text type='TerminalCommand'>keybase login</Text>
        <Text type='TerminalEmpty'/>
        <Text type='TerminalComment'>or if you're new to Keybase:</Text>
        <Text type='TerminalCommand'>keybase signup</Text>
      </Terminal>
      {allowLoggedOutKBFS && <Text type='Body' small style={{marginTop: 22, marginBottom: 7, marginLeft: 10}}>Or access someone's public folder:</Text>}

  )
}

export default class Render extends Component {
  props: {
    username: ?string,
    openKBFS: () => void,
    openKBFSPublic: (username: ?string) => void,
    openKBFSPrivate: (username: ?string) => void,
    showMain: () => void,
    showHelp: () => void,
    showUser: (username: ?string) => void,
    quit: () => void,
    openingButtonInfo: {
      text: string,
      onClick: () => void
    },
    folders: Array<FolderInfo>,
    debug?: boolean,
    loading: boolean,
    loggedIn: boolean
  };

  render (): ReactElement {
    const {openKBFS, openKBFSPublic, openKBFSPrivate, showMain, showHelp, showUser, quit, username, loggedIn} = this.props

    return (

           showUser(username)}/>
          {!loggedIn && <LoggedoutMessage />}
          <FolderList loading={this.props.loading} username={this.props.username} openKBFSPublic={openKBFSPublic} openKBFSPrivate={openKBFSPrivate} folders={this.props.folders} loggedIn={loggedIn}/>

    )
  }
}

const Row = props => {
  return (

      <Text type='Body' link small key={props.key} style={{marginTop: 4, ...props.textStyle}}>{props.text}</Text>
      {props.children}

  )
}

const FolderRow = props => {
  const {username, folder: {isPublic, isEmpty, openFolder, folderName}} = props
  let line = intersperse(',', canonicalizeUsernames(username, parseFolderNameToUsers(folderName)))

  return <Row
    onClick={openFolder}
    text={line}
    iconStyle={SVGFolderIcon(iconPath(isPublic, isEmpty))}
    textStyle={{color: globalColors.blue}}
    key={isPublic + line}/>
}

const FolderEntryRow = props => {
  const {key, entry} = props
  let inputRef = null

  const openFolder = () => {
    if (inputRef) {
      entry.openFolder(cleanup(inputRef.getValue()))
      inputRef.clearValue()
      inputRef.blur()
    }
  }

  return (
    <Row
      style={{height: 25, position: 'relative'}}
      onClick={() => {}}
      text={entry.prefix}
      iconStyle={SVGFolderIcon(iconPath(entry.isPublic, false))}
      textStyle={{color: globalColors.blue}}
      key={key}>
    <Input
      ref={input => inputRef = input}
      small
      hintText='user1,user2,etc'
      onEnterKeyDown={() => openFolder()}
      style={{width: '100%', marginLeft: entry.prefix ? 2 : 0}} />
     openFolder()}>
  </Row>)
}

const ShowAll = props => {
  return <Row
    onClick={props.onClick}
    text='Show All'
    iconStyle={{...SVGFolderIcon('file:///' + resolveAssets(`../react-native/react/images/see-more.svg`)), marginTop: 2}}
    textStyle={{}}
    key={props.isPublic + 'showAll'}/>
}

class CollapsableFolderList extends Component {
  props: {
    username: ?string,
    folders: Array<FolderInfo|FolderEntry>,
    folderDisplayLimit: number,
    collapsed: boolean,
    onExpand: Function,
    isPublic: boolean
  };

  render () {
    const {collapsed, username, folderDisplayLimit, onExpand} = this.props

    let {folders} = this.props
    // Check if it's bigger by one because it's pointless to have a button
    // that says show all for just one more thing
    let truncated = false
    if (collapsed && folders.length > folderDisplayLimit + 1) {
      folders = folders.slice(0, folderDisplayLimit)
      truncated = true
    }

    return (

        {folders.map(f => {
          const key = f.type === 'entry' ? `entry${f.prefix}` : `${f.isPublic}:${f.folderName}`

          if (f.type === 'entry') {
            return <FolderEntryRow key={key} entry={f} />
          } else {
            return <FolderRow key={key} username={username} folder={f}/>
          }
        })}
        {truncated && <ShowAll onClick={onExpand} isPublic={this.props.isPublic} />}

    )
  }
}

class FolderList extends Component {
  props: {
    loading: boolean,
    username: ?string,
    folders: Array<FolderInfo>,
    openKBFSPublic: (username: ?string) => void,
    openKBFSPrivate: (username: ?string) => void,
    loggedIn: boolean
  };

  state: {
    privateCollapsed: boolean,
    publicCollapsed: boolean
  };

  constructor (props) {
    super(props)
    this.state = {
      privateCollapsed: true,
      publicCollapsed: true
    }
  }

  render () {
    const {username, loggedIn} = this.props

    if (!loggedIn && !allowLoggedOutKBFS) {
      return

    }

    // Remove folders that are just our personal ones, we'll add those in later
    // For consistency. Since we aren't gauranteed we have favorited our own folders.
    const folders = this.props.folders.filter(f => f.type === 'entry' || stripPublicTag(f.folderName) !== username)

    let privateFolders = []
    let publicFolders = []

    if (loggedIn && username) {
      const personalPrivateFolder: FolderInfo = {
        type: 'folder',
        folderName: username,
        isPublic: false,
        isEmpty: true,
        openFolder: () => this.props.openKBFSPrivate(username)
      }
      privateFolders.push(personalPrivateFolder)

      const privateFolderEntry: FolderEntry = {
        type: 'entry',
        isPublic: false,
        prefix: `${username},`,
        openFolder: folder => this.props.openKBFSPrivate(`${username},${folder}`)
      }
      privateFolders.push(privateFolderEntry)
    }

    if (loggedIn && username) {
      const personalPublicFolder: FolderInfo = {
        type: 'folder',
        folderName: username,
        isPublic: true,
        isEmpty: true,
        openFolder: () => this.props.openKBFSPublic(username)
      }

      publicFolders.push(personalPublicFolder)
    }

    const publicFolderEntry: FolderEntry = {
      type: 'entry',
      isPublic: true,
      prefix: '',
      openFolder: folder => this.props.openKBFSPublic(folder)
    }

    publicFolders.push(publicFolderEntry)

    privateFolders = privateFolders.concat(folders.filter(f => !f.isPublic))
    publicFolders = publicFolders.concat(folders.filter(f => f.isPublic)).map(f => f.type === 'entry'
      ? f : {...f, folderName: stripPublicTag(f.folderName)})

    const folderDisplayLimit = 5

    return (

        {this.props.loading && (

            <CircularProgress style={styles.loader} mode='indeterminate' size={0.5}/>

)}
        {!!privateFolders.length && (

            <Text type='Body' onClick={() => this.props.openKBFSPrivate('')}>/keybase/private/</Text>
            <CollapsableFolderList
              username={username}
              folders={privateFolders}
              folderDisplayLimit={folderDisplayLimit}
              onExpand={() => this.setState({privateCollapsed: false})}
              isPublic={false}
              collapsed={this.state.privateCollapsed} />

        )}
        <Text type='Body' onClick={() => this.props.openKBFSPublic('')}>/keybase/public/</Text>
        <CollapsableFolderList
          username={username}
          folders={publicFolders}
          folderDisplayLimit={folderDisplayLimit}
          onExpand={() => this.setState({publicCollapsed: false})}
          isPublic
          collapsed={this.state.publicCollapsed} />

    )
  }
}

const styles = {
  container: {
    ...globalStyles.flexBoxColumn,
    flex: 1
  },
  body: {
    ...globalStyles.flexBoxColumn,
    ...globalStyles.rounded,
    ...globalStyles.windowBorder,
    position: 'relative',
    overflow: 'hidden',
    height: 364,
    minHeight: 364,
    maxHeight: 364,
    marginTop: -1, // let arrow not have a border below it
    flex: 1
  },
  arrow: {
    ...globalStyles.topMost,
    width: 0,
    height: 10,
    minHeight: 10,
    borderLeft: '7px solid transparent',
    borderRight: '7px solid transparent',
    borderBottom: `5px solid ${globalColors.grey5}`,
    alignSelf: 'center'
  },
  header: {
    ...globalStyles.flexBoxRow,
    backgroundColor: globalColors.grey5,
    color: globalColors.grey2,
    minHeight: 31,
    maxHeight: 32,
    padding: 10
  },
  folderHeader: {
    ...globalStyles.flexBoxRow,
    justifyContent: 'space-between',
    paddingRight: 18
  },
  folderList: {
    ...globalStyles.flexBoxColumn,
    backgroundColor: globalColors.white,
    position: 'relative',
    flex: 1,
    padding: 10,
    overflowY: 'scroll',
    overflowX: 'hidden'
  },
  loader: {
    position: 'absolute',
    alignSelf: 'center',
    top: 0,
    right: 0,
    opacity: 0.8
  },
  footer: {
    ...globalStyles.flexBoxRow,
    backgroundColor: globalColors.grey5,
    justifyContent: 'space-between',
    padding: 10
  },
  personalTLDStyle: {
    fontSize: 15,
    lineHeight: '18px',
    color: globalColors.lightBlue
  },
  showAllBox: {
    color: globalColors.white,
    ...globalStyles.fontBold,
    fontSize: 8,
    backgroundColor: globalColors.grey3,
    minWidth: 16,
    minHeight: 11,
    borderRadius: 2,
    alignSelf: 'center',
    marginRight: 8
  },
  entryArrow: {
    ...globalStyles.clickable,
    color: globalColors.grey2,
    width: 25,
    fontSize: 13,
    height: 25,
    textAlign: 'center',
    position: 'absolute',
    right: 0,
    top: 5
  }
}

const SVGFolderIcon = svgPath => ({
  height: 22,
  minWidth: 22,
  maxWidth: 22,
  backgroundImage: `url(${svgPath})`,
  backgroundRepeat: 'no-repeat',
  backgroundPosition: 'center center'
})
<|endoftext|>


<commit_before>var async = require('../lib');
var expect = require('chai').expect;

describe('ensureAsync', function() {
  var passContext = function(cb) {
    cb(this);
  };

  it('should propely bind context to the wrapped function', function(done) {

    // call bind after wrapping with initialParams
    var context = {context: "post"};
    var postBind = async.ensureAsync(passContext);
    postBind = postBind.bind(context);
    postBind(function(ref) {
      expect(ref).to.equal(context);
      done();
    });
  });

  it('should not override the bound context of a function when wrapping', function(done) {

    // call bind before wrapping with initialParams
    var context = {context: "pre"};
    var preBind = passContext.bind(context);
    preBind = async.ensureAsync(preBind);
    preBind(function(ref) {
      expect(ref).to.equal(context);
      done();
    });
  });
});
<commit_msg>update comments to match change to ensureAsync<commit_after>var async = require('../lib');
var expect = require('chai').expect;

describe('ensureAsync', function() {
  var passContext = function(cb) {
    cb(this);
  };

  it('should propely bind context to the wrapped function', function(done) {

    // call bind after wrapping with ensureAsync
    var context = {context: "post"};
    var postBind = async.ensureAsync(passContext);
    postBind = postBind.bind(context);
    postBind(function(ref) {
      expect(ref).to.equal(context);
      done();
    });
  });

  it('should not override the bound context of a function when wrapping', function(done) {

    // call bind before wrapping with ensureAsync
    var context = {context: "pre"};
    var preBind = passContext.bind(context);
    preBind = async.ensureAsync(preBind);
    preBind(function(ref) {
      expect(ref).to.equal(context);
      done();
    });
  });
});
<|endoftext|>


<commit_before>
var EventDefinition = Class.extend({

	source: null,
	id: null,
	title: null,
	rendering: null,
	miscProps: null,

	constructor: function() {
		this.miscProps = {};
	},

	buildInstances: function(start, end) {
		// subclasses must implement
	},

	clone: function() {
		var copy = new this.constructor();

		copy.source = this.source;
		copy.id = this.id;
		copy.title = this.title;
		copy.rendering = this.rendering;
		copy.miscProps = $.extend({}, this.miscProps);

		return copy;
	}

});

EventDefinition.uuid = 0;
EventDefinition.reservedPropMap = {};

EventDefinition.addReservedProps = function(propArray) {
	var map = {};
	var i;

	for (i = 0; i < propArray.length; i++) {
		map[propArray[i]] = true;
	}

	// won't modify original object. don't want sideeffects on superclasses
	this.reservedPropMap = $.extend({}, this.reservedPropMap, map);
};

EventDefinition.isReservedProp = function(propName) {
	return this.reservedPropMap[propName] || false;
};

EventDefinition.parse = function(rawProps) {
	var def = new this();
	var propName;
	var miscProps = {};

	def.id = rawProps.id || ('_fc' + (++EventDefinition.uuid));
	def.title = rawProps.title || '';
	def.rendering = rawProps.rendering || null;

	for (propName in rawProps) {
		if (!this.isReservedProp(propName)) {
			miscProps[propName] = rawProps[propName];
		}
	}

	def.miscProps = miscProps;

	return def;
};

EventDefinition.addReservedProps([ 'id', 'title', 'rendering' ]);
<commit_msg>add contraint/overlap members to eventdef<commit_after>
var EventDefinition = Class.extend({

	source: null,
	id: null,
	title: null,
	rendering: null,
	constraint: null,
	overlap: null,
	miscProps: null,

	constructor: function() {
		this.miscProps = {};
	},

	buildInstances: function(start, end) {
		// subclasses must implement
	},

	clone: function() {
		var copy = new this.constructor();

		copy.source = this.source;
		copy.id = this.id;
		copy.title = this.title;
		copy.rendering = this.rendering;
		copy.constraint = this.constraint;
		copy.overlap = this.overlap;
		copy.miscProps = $.extend({}, this.miscProps);

		return copy;
	}

});

EventDefinition.uuid = 0;
EventDefinition.reservedPropMap = {};

EventDefinition.addReservedProps = function(propArray) {
	var map = {};
	var i;

	for (i = 0; i < propArray.length; i++) {
		map[propArray[i]] = true;
	}

	// won't modify original object. don't want sideeffects on superclasses
	this.reservedPropMap = $.extend({}, this.reservedPropMap, map);
};

EventDefinition.isReservedProp = function(propName) {
	return this.reservedPropMap[propName] || false;
};

EventDefinition.parse = function(rawProps) {
	var def = new this();
	var propName;
	var miscProps = {};

	def.id = rawProps.id || ('_fc' + (++EventDefinition.uuid));
	def.title = rawProps.title || '';
	def.rendering = rawProps.rendering || null;
	def.constraint = rawProps.constraint;
	def.overlap = rawProps.overlap;

	for (propName in rawProps) {
		if (!this.isReservedProp(propName)) {
			miscProps[propName] = rawProps[propName];
		}
	}

	def.miscProps = miscProps;

	return def;
};

EventDefinition.addReservedProps([ 'id', 'title', 'rendering', 'constraint', 'overlap' ]);
<|endoftext|>


<commit_before>//we probably have to have this only describing where the tests are
steal("jquery/model","jquery/dom/fixture")  //load your app
 .then('funcunit/qunit')  //load qunit
 .then("./model_test.js")//,"./associations_test.js")
 // .then(
	// "jquery/model/backup/qunit",
	// "jquery/model/list/list_test.js"
  // )
  // .then("jquery/model/validations/qunit/validations_test.js")
<commit_msg>accidentally commented out some tests<commit_after>//we probably have to have this only describing where the tests are
steal("jquery/model","jquery/dom/fixture")  //load your app
 .then('funcunit/qunit')  //load qunit
 .then("./model_test.js")//,"./associations_test.js")
 .then(
	"jquery/model/backup/qunit",
	"jquery/model/list/list_test.js"
  )
  .then("jquery/model/validations/qunit/validations_test.js")
<|endoftext|>


<commit_before>import { mongooseConnection } from './lib/connections'
import ProductsTask from './lib/worker/tasks/products'
import StoresTask from './lib/worker/tasks/stores'
import logger from 'winston'

const productsTask = new ProductsTask()
const storeTask = new StoresTask()

mongooseConnection.on('connected', () => {
  const products = productsTask.run()
  const stores = storeTask.run()

  Promise.all([products, stores])
    .then(() => {
      logger.info('Inserted products and stores!')
    })
    .catch(err => logger.error(err))
    .then(() => {
      process.exit()
    })
})
<commit_msg>Tidying up worker<commit_after>import { mongooseConnection } from './lib/connections'
import ProductsTask from './lib/worker/tasks/products'
import StoresTask from './lib/worker/tasks/stores'
import logger from 'winston'

const productsTask = new ProductsTask()
const storeTask = new StoresTask()

mongooseConnection.on('connected', run)

async function run () {
  try {
    await Promise.all([productsTask.run(), storeTask.run()])
    logger.info('Inserted products and stores!')
  } catch (e) {
    logger.error(e)
  }

  process.exit()
}
<|endoftext|>


<commit_before>/**
 Copyright (c) 2017,2018,2019,2020 Klaus Landsdorf (https://bianco-royal.com/)
 All rights reserved.
 node-red-contrib-modbus - The BSD 3-Clause License

 @author Klaus Landsdorf (Bianco Royal)
 */
/**
 * Modbus flexible Getter node.
 * @module NodeRedModbusFlexGetter
 *
 * @param RED
 */
module.exports = function (RED) {
  'use strict'
  // SOURCE-MAP-REQUIRED
  const mbBasics = require('./modbus-basics')
  const internalDebugLog = require('debug')('contribModbus:flex:connector')

  function ModbusFlexConnector (config) {
    RED.nodes.createNode(this, config)

    this.name = config.name
    this.maxReconnectsPerMinute = config.maxReconnectsPerMinute || 4
    this.emptyQueue = config.emptyQueue
    this.showStatusActivities = config.showStatusActivities
    this.showErrors = config.showErrors
    this.connection = null

    this.internalDebugLog = internalDebugLog
    this.verboseLogging = RED.settings.verbose

    const node = this
    mbBasics.setNodeStatusTo('waiting', node)

    const modbusClient = RED.nodes.getNode(config.server)
    if (!modbusClient) {
      return
    }
    modbusClient.registerForModbus(node)
    mbBasics.initModbusClientEvents(node, modbusClient)

    node.onConfigDone = function (msg) {
      if (node.showStatusActivities) {
        mbBasics.setNodeStatusTo('config done', node)
      }
      msg.config_change = 'emitted'
      node.send(msg)
    }

    node.onConfigError = function (err, msg) {
      internalDebugLog(err.message)
      if (node.showErrors) {
        node.error(err, msg)
      }
      if (err && err.message) {
        msg.error = err
      } else {
        msg.error = new Error(err)
      }
      msg.error.nodeStatus = node.statusText
      if (node.emptyMsgOnFail) {
        node.send({ payload: '', error: err, status: node.statusText, msg })
      } else {
        node.send(msg)
      }
    }

    node.on('input', function (msg) {
      if (mbBasics.invalidPayloadIn(msg)) {
        return
      }

      if (!modbusClient.client) {
        return
      }

      if (node.showStatusActivities) {
        mbBasics.setNodeStatusTo(modbusClient.actualServiceState, node)
      }

      if (msg.payload.connectorType) {
        internalDebugLog('dynamicReconnect: ' + JSON.stringify(msg.payload))
        msg.payload.emptyQueue = node.emptyQueue
        modbusClient.emit('dynamicReconnect', msg, node.onConfigDone, node.onConfigError)
      } else {
        const error = new Error('Payload Not Valid - Connector Type')
        node.error(error, msg)

        node.send(msg)
      }
    })
  }

  RED.nodes.registerType('modbus-flex-connector', ModbusFlexConnector)
}
<commit_msg>fix: similar empty msg structure<commit_after>/**
 Copyright (c) 2017,2018,2019,2020 Klaus Landsdorf (https://bianco-royal.com/)
 All rights reserved.
 node-red-contrib-modbus - The BSD 3-Clause License

 @author Klaus Landsdorf (Bianco Royal)
 */
/**
 * Modbus flexible Getter node.
 * @module NodeRedModbusFlexGetter
 *
 * @param RED
 */
module.exports = function (RED) {
  'use strict'
  // SOURCE-MAP-REQUIRED
  const mbBasics = require('./modbus-basics')
  const internalDebugLog = require('debug')('contribModbus:flex:connector')

  function ModbusFlexConnector (config) {
    RED.nodes.createNode(this, config)

    this.name = config.name
    this.maxReconnectsPerMinute = config.maxReconnectsPerMinute || 4
    this.emptyQueue = config.emptyQueue
    this.showStatusActivities = config.showStatusActivities
    this.showErrors = config.showErrors
    this.connection = null

    this.internalDebugLog = internalDebugLog
    this.verboseLogging = RED.settings.verbose

    const node = this
    mbBasics.setNodeStatusTo('waiting', node)

    const modbusClient = RED.nodes.getNode(config.server)
    if (!modbusClient) {
      return
    }
    modbusClient.registerForModbus(node)
    mbBasics.initModbusClientEvents(node, modbusClient)

    node.onConfigDone = function (msg) {
      if (node.showStatusActivities) {
        mbBasics.setNodeStatusTo('config done', node)
      }
      msg.config_change = 'emitted'
      node.send(msg)
    }

    node.onConfigError = function (err, msg) {
      internalDebugLog(err.message)
      if (node.showErrors) {
        node.error(err, msg)
      }

      if (err && err.message) {
        msg.error = err
      } else {
        msg.error = new Error(err)
      }
      msg.error.nodeStatus = node.statusText

      if (node.emptyMsgOnFail) {
        msg.payload = ''
      }

      node.send(msg)
    }

    node.on('input', function (msg) {
      if (mbBasics.invalidPayloadIn(msg)) {
        return
      }

      if (!modbusClient.client) {
        return
      }

      if (node.showStatusActivities) {
        mbBasics.setNodeStatusTo(modbusClient.actualServiceState, node)
      }

      if (msg.payload.connectorType) {
        internalDebugLog('dynamicReconnect: ' + JSON.stringify(msg.payload))
        msg.payload.emptyQueue = node.emptyQueue
        modbusClient.emit('dynamicReconnect', msg, node.onConfigDone, node.onConfigError)
      } else {
        const error = new Error('Payload Not Valid - Connector Type')
        node.error(error, msg)

        node.send(msg)
      }
    })
  }

  RED.nodes.registerType('modbus-flex-connector', ModbusFlexConnector)
}
<|endoftext|>


<commit_before>/* global $ Snap */
(function() {
  "use strict";
  var CircuitExercise = function (options) {
    this.options = $.extend({components: ["and", "not", "or"],
      template: '
' +
        '
',
      addSubmit: true}, options);
    this.element = this.options.element;
    this.element.html(this.options.template);
    this.editor = new CircuitEditor($.extend({}, this.options, {element: this.element.find(".circuit"),
      buttonPanelElement: this.element.find(".circuit-buttonpanel")}));
    if (this.options.addSubmit) {
      this.addSubmitToToolbar();
    }

    this.initInputs();
    this.initOutputs();
  };
  var exerproto = CircuitExercise.prototype;
  exerproto.addSubmitToToolbar = function () {
    var $buttonPanel = this.options.buttonPanelElement || this.element.find(".circuit-buttonpanel");
    $buttonPanel.prepend('<button class="submit">Submit</button>');
    this.element.find(".submit").click(function () {
      var fb = this.grade();
      new CircuitExerciseFeedback(this.options, fb);
    }.bind(this));
  };
  exerproto.initInputs = function () {
    var input = this.options.input,
      w = this.editor.circuit.element.outerWidth(),
      h = this.editor.circuit.element.outerHeight();
    var inputSpacing = 0.8 * h / (input.length + 1);
    for (var i = 0; i < input.length; i++) {
      var inp = this.editor.circuit.inputComponent(input[i]);
      this.editor.setInteractive(inp);
      inp.element.css({top: Math.round(0.2 * h + inputSpacing * i),
        left: Math.round(0.1 * w) });
    }
  };
  exerproto.initOutputs = function () {
    var output = this.options.output,
      w = this.editor.circuit.element.outerWidth(),
      h = this.editor.circuit.element.outerHeight();
    var outputSpacing = 0.8 * h / (output.length + 1);
    for (var i = 0; i < output.length; i++) {
      var out = this.editor.circuit.outputComponent(output[i]);
      this.editor.setInteractive(out);
      out.element.css({top: Math.round(0.2 * h + outputSpacing * i),
        left: Math.round(0.9 * w) });
    }
  };
  exerproto.grade = function () {
    var checks = this.options.grading,
      feedback = {checks: [], success: true},
      correct;
    for (var i = 0; i < checks.length; i++) {
      var c = checks[i];
      this.editor.circuit.clearFeedback();
      var res = this.editor.circuit.simulateOutput(c.input);
      if (this.options.output.length === 1) {
        correct = (c.output === res[this.options.output[0]]);
      }
      feedback.success = feedback.success && correct;
      var checkFb = { input: $.extend({}, c.input), output: $.extend({}, res),
        expected: c.output, correct: correct};
      feedback.checks.push(checkFb);
    }
    feedback.circuit = this.editor.circuit.state();
    this.editor.circuit.clearFeedback();
    return feedback;
  };

  var CircuitExerciseFeedback = function (exeropts, feedback, options) {
    this.options = $.extend({}, options);
    this.exeropts = exeropts;
    this.feedback = feedback;
    if (!this.options.element) {
      this.element = $("

");
      this.element.appendTo(document.body);
    } else {
      this.element = $(this.options.element);
    }
    this.element.addClass("circuit-feedback");
    this.element.html("
Submission Feedback

" +
      "

");
    this.initFeedback();
    this.initCircuit();

    this.element.find("h2").click(function () {
      this.element.remove();
    }.bind(this));
  };
  CircuitExerciseFeedback.prototype.initFeedback = function () {
    var outputKey = this.exeropts.output;
    var fbHTML = "

";
    fbHTML += "" + this.exeropts.input.join('	');
    fbHTML += "	" + outputKey + ' from your circuit	';
    fbHTML += "	" + outputKey + " expected

";

    for (var i = 0; i < this.feedback.checks.length; i++) {
      var c = this.feedback.checks[i];
      fbHTML += "
";
      for (var j = 0; j < this.exeropts.input.length; j++) {
        fbHTML += "" + (c.input[this.exeropts.input[j]]?"1":"0") + "	";
      }
      var outVal = c.output[outputKey];
      if (outVal === null) {
        outVal = "";
      } else {
        outVal = (outVal?"1":"0");
      }
      fbHTML += "" + outVal + "	";
      fbHTML += "	";
      fbHTML += "" + (JSON.stringify(c.expected)?"1":"0") + "	";
      fbHTML += "
";
    }
    fbHTML += "

";

    var self = this;
    this.element.find('.circuit-input-output').html(fbHTML)
      .find("tbody tr").click(function () {
        self.circuit.clearFeedback();
        self.circuit.simulateOutput(self.feedback.checks[$(this).data("check")].input);
        $(this).parent().find(".circuit-active").removeClass("circuit-active");
        $(this).addClass("circuit-active");
      });

  };
  CircuitExerciseFeedback.prototype.initCircuit = function () {
    this.circuit = new LogicCircuit({element: this.element.find(".circuit")});
    this.circuit.state(this.feedback.circuit);
  };

  var CircuitSimulationExercise = function (circuit, options) {
    this.circuit = circuit;
    this.options = $.extend({}, options);
    this.initInputs();
    this.initToggles();
  };
  CircuitSimulationExercise.prototype.initInputs = function () {
    var inputValues = this.options.input;
    var inputComponents = this.circuit._inputs;
    for (var key in inputComponents) {
      if (inputComponents.hasOwnProperty(key)) {
        inputComponents[key].element.find(".circuit-output").addClass(CIRCUIT_CONSTANTS.VALCLASS[inputValues[key]]);
      }
    }
  };
  CircuitSimulationExercise.prototype.initToggles = function () {
    var toggles = this.circuit.element.find(".circuit-output, .circuit-input")
      .not(".circuit-value-true, .circuit-value-false")
      .addClass(CIRCUIT_CONSTANTS.VALCLASS.UNKNOWN)
      .addClass("circuit-value-interactive");
    var circuit = this.circuit;
    toggles.click(function (evt) {
      evt.stopPropagation();
      evt.preventDefault();
      var $this = $(this);
      if ($this.hasClass(CIRCUIT_CONSTANTS.VALCLASS.UNKNOWN)) {
        $this.removeClass(CIRCUIT_CONSTANTS.VALCLASS.UNKNOWN)
          .addClass(CIRCUIT_CONSTANTS.VALCLASS[false]);
      } else {
        $this.toggleClass(CIRCUIT_CONSTANTS.VALCLASS[false])
          .toggleClass(CIRCUIT_CONSTANTS.VALCLASS[true]);
      }
      circuit.element.trigger("circuit-changed");
    });
  };
  CircuitSimulationExercise.prototype.grade = function () {
    var outputValue = function (comp) {
        if (comp.element.find(".circuit-output." + CIRCUIT_CONSTANTS.VALCLASS[true]).size() > 0) {
          return true;
        } else if (comp.element.find(".circuit-output." + CIRCUIT_CONSTANTS.VALCLASS[false]).size() > 0) {
          return false;
        } else {
          return null;
        }
      },
      inputValue = function (comp, pos) {
        if (comp.element.find(".circuit-input." + CIRCUIT_CONSTANTS.VALCLASS[true] +
          "[data-pos=" + pos + "]").size() > 0) {
          return true;
        } else if (comp.element.find(".circuit-input." + CIRCUIT_CONSTANTS.VALCLASS[false] +
          "[data-pos=" + pos + "]").size() > 0) {
          return false;
        } else {
          return null;
        }
      };
    var modelcircuit = new LogicCircuit({});
    modelcircuit.state(this.circuit.state());
    modelcircuit.simulateOutput(this.options.input);
    var modelComps = modelcircuit._components,
      stdComps = this.circuit._components,
      mc, sc, fb, corr, state, val, success = true,
      feedback = [],
      states = [];
    for (var i = 0, l = stdComps.length; i < l; i++) {
      mc = modelComps[i];
      sc = stdComps[i];
      if (!(sc instanceof LogicCircuit.COMPONENT_TYPES.CircuitInputComponent)) {
        fb = {input: []};
        feedback.push(fb);
        state = {input: []};
        states.push(state);
        if (!(sc instanceof LogicCircuit.COMPONENT_TYPES.CircuitOutputComponent)) {
          val = outputValue(sc);
          corr = val === outputValue(mc);
          success = success && corr;
          fb.output = corr;
          state.output = val;
        }
        for (var j = 0; j < sc._inputCount; j++) {
          val = inputValue(sc, j);
          corr = val === inputValue(mc, j);
          success = success && corr;
          fb.input.push(corr);
          state.input.push(val);
        }
      } else {
        feedback.push(null);
        states.push(null);
      }
    }
    modelcircuit.element.remove();
    return {feedback: feedback, states: states, success: success, circuit: this.circuit.state()};
  };

  var CircuitSimulationFeedback = function (exeropts, feedback, options) {
    this.options = $.extend({}, options);
    this.exeropts = exeropts;
    this.feedback = feedback;
    if (!this.options.element) {
      this.element = $("

");
      this.element.appendTo(document.body);
    } else {
      this.element = $(this.options.element);
    }
    this.element.addClass("circuit-feedback");
    this.element.html("

");
    this.initCircuit();
    this.initFeedback();

    this.element.find("h2").click(function () {
      this.element.remove();
    }.bind(this));
  };
  CircuitSimulationFeedback.prototype.initCircuit = CircuitExerciseFeedback.prototype.initCircuit;
  CircuitSimulationFeedback.prototype.initFeedback = function () {
    var comps = this.circuit._components,
      feedback = this.feedback.feedback,
      states = this.feedback.states,
      c, state, fb;
    var outputFeedback = function (comp, compFb, compState) {
      var e = comp.element.find(".circuit-output");
      e.addClass(CIRCUIT_CONSTANTS.VALCLASS[compState.output])
        .addClass(CIRCUIT_CONSTANTS.FEEDBACKCLASS[compFb.output])
        .addClass("circuit-value-interactive");
    };
    var inputFeedback = function (comp, pos, compFb, compState) {
      var e = comp._inputElements[pos];
      e.addClass(CIRCUIT_CONSTANTS.VALCLASS[compState.input[pos]])
        .addClass(CIRCUIT_CONSTANTS.FEEDBACKCLASS[compFb.input[pos]])
        .addClass("circuit-value-interactive");
    };
    for (var i = 0, l = comps.length; i < l; i++) {
      c = comps[i];
      if (!(c instanceof LogicCircuit.COMPONENT_TYPES.CircuitInputComponent)) {
        fb = feedback[i];
        state = states[i];
        if (!(c instanceof LogicCircuit.COMPONENT_TYPES.CircuitOutputComponent)) {
          outputFeedback(c, fb, state);
        }
        for (var j = c._inputCount; j--;) {
          inputFeedback(c, j, fb, state);
        }
      }
    }
  };

  window.CircuitExercise = CircuitExercise;
  window.CircuitExerciseFeedback = CircuitExerciseFeedback;
  window.CircuitSimulationExercise = CircuitSimulationExercise;
  window.CircuitSimulationFeedback = CircuitSimulationFeedback;
}());<commit_msg>Rudimentary localization support<commit_after>/* global $ Snap */
(function() {
  "use strict";

  var TRANSLATIONS = {
    "en": {
      SUBMIT: "Submit",
      YOUR_CIRCUIT: "from your circuit",
      EXPECTED: "expected"
    },
    "fi": {
      SUBMIT: "Lähetä",
      YOUR_CIRCUIT: "piiristäsi",
      EXPECTED: "odotettu"
    }
  };
  var getLocalizedString = function(lang, strkey) {
    if (!TRANSLATIONS[lang] ||!TRANSLATIONS[lang][strkey]) {
      return strkey;
    }
    return TRANSLATIONS[lang][strkey];
  };
  var CircuitExercise = function (options) {
    this.options = $.extend({components: ["and", "not", "or"],
      template: '
' +
        '
',
      addSubmit: true}, options);
    this.lang = this.options.language || "en";
    this.element = this.options.element;
    this.element.html(this.options.template);
    this.editor = new CircuitEditor($.extend({}, this.options, {element: this.element.find(".circuit"),
      buttonPanelElement: this.element.find(".circuit-buttonpanel")}));
    if (this.options.addSubmit) {
      this.addSubmitToToolbar();
    }

    this.initInputs();
    this.initOutputs();
  };
  var exerproto = CircuitExercise.prototype;
  exerproto.addSubmitToToolbar = function () {
    var $buttonPanel = this.options.buttonPanelElement || this.element.find(".circuit-buttonpanel");
    $buttonPanel.prepend('<button class="submit">' + getLocalizedString(this.lang, "SUBMIT") + '</button>');
    this.element.find(".submit").click(function () {
      var fb = this.grade();
      new CircuitExerciseFeedback(this.options, fb);
    }.bind(this));
  };
  exerproto.initInputs = function () {
    var input = this.options.input,
      w = this.editor.circuit.element.outerWidth(),
      h = this.editor.circuit.element.outerHeight();
    var inputSpacing = 0.8 * h / (input.length + 1);
    for (var i = 0; i < input.length; i++) {
      var inp = this.editor.circuit.inputComponent(input[i]);
      this.editor.setInteractive(inp);
      inp.element.css({top: Math.round(0.2 * h + inputSpacing * i),
        left: Math.round(0.1 * w) });
    }
  };
  exerproto.initOutputs = function () {
    var output = this.options.output,
      w = this.editor.circuit.element.outerWidth(),
      h = this.editor.circuit.element.outerHeight();
    var outputSpacing = 0.8 * h / (output.length + 1);
    for (var i = 0; i < output.length; i++) {
      var out = this.editor.circuit.outputComponent(output[i]);
      this.editor.setInteractive(out);
      out.element.css({top: Math.round(0.2 * h + outputSpacing * i),
        left: Math.round(0.9 * w) });
    }
  };
  exerproto.grade = function () {
    var checks = this.options.grading,
      feedback = {checks: [], success: true},
      correct;
    for (var i = 0; i < checks.length; i++) {
      var c = checks[i];
      this.editor.circuit.clearFeedback();
      var res = this.editor.circuit.simulateOutput(c.input);
      if (this.options.output.length === 1) {
        correct = (c.output === res[this.options.output[0]]);
      }
      feedback.success = feedback.success && correct;
      var checkFb = { input: $.extend({}, c.input), output: $.extend({}, res),
        expected: c.output, correct: correct};
      feedback.checks.push(checkFb);
    }
    feedback.circuit = this.editor.circuit.state();
    this.editor.circuit.clearFeedback();
    return feedback;
  };

  var CircuitExerciseFeedback = function (exeropts, feedback, options) {
    this.options = $.extend({}, options);
    this.exeropts = exeropts;
    this.feedback = feedback;
    if (!this.options.element) {
      this.element = $("

");
      this.element.appendTo(document.body);
    } else {
      this.element = $(this.options.element);
    }
    this.element.addClass("circuit-feedback");
    this.element.html("

" +
                    "

");
    this.initFeedback();
    this.initCircuit();

    this.element.find("h2").click(function () {
      this.element.remove();
    }.bind(this));
  };
  CircuitExerciseFeedback.prototype.initFeedback = function () {
    var outputKey = this.exeropts.output;
    var fbHTML = "

";
    fbHTML += "" + this.exeropts.input.join('	');
    fbHTML += "	" + outputKey + " " + getLocalizedString(this.lang, "YOUR_CIRCUIT") + '	';
    fbHTML += "	" + outputKey + " " +
            getLocalizedString(this.lang, "EXPECTED") + "

";

    for (var i = 0; i < this.feedback.checks.length; i++) {
      var c = this.feedback.checks[i];
      fbHTML += "
";
      for (var j = 0; j < this.exeropts.input.length; j++) {
        fbHTML += "" + (c.input[this.exeropts.input[j]]?"1":"0") + "	";
      }
      var outVal = c.output[outputKey];
      if (outVal === null) {
        outVal = "";
      } else {
        outVal = (outVal?"1":"0");
      }
      fbHTML += "" + outVal + "	";
      fbHTML += "	";
      fbHTML += "" + (JSON.stringify(c.expected)?"1":"0") + "	";
      fbHTML += "
";
    }
    fbHTML += "

";

    var self = this;
    this.element.find('.circuit-input-output').html(fbHTML)
      .find("tbody tr").click(function () {
        self.circuit.clearFeedback();
        self.circuit.simulateOutput(self.feedback.checks[$(this).data("check")].input);
        $(this).parent().find(".circuit-active").removeClass("circuit-active");
        $(this).addClass("circuit-active");
      });

  };
  CircuitExerciseFeedback.prototype.initCircuit = function () {
    this.circuit = new LogicCircuit({element: this.element.find(".circuit")});
    this.circuit.state(this.feedback.circuit);
  };

  var CircuitSimulationExercise = function (circuit, options) {
    this.circuit = circuit;
    this.options = $.extend({}, options);
    this.initInputs();
    this.initToggles();
  };
  CircuitSimulationExercise.prototype.initInputs = function () {
    var inputValues = this.options.input;
    var inputComponents = this.circuit._inputs;
    for (var key in inputComponents) {
      if (inputComponents.hasOwnProperty(key)) {
        inputComponents[key].element.find(".circuit-output").addClass(CIRCUIT_CONSTANTS.VALCLASS[inputValues[key]]);
      }
    }
  };
  CircuitSimulationExercise.prototype.initToggles = function () {
    var toggles = this.circuit.element.find(".circuit-output, .circuit-input")
      .not(".circuit-value-true, .circuit-value-false")
      .addClass(CIRCUIT_CONSTANTS.VALCLASS.UNKNOWN)
      .addClass("circuit-value-interactive");
    var circuit = this.circuit;
    toggles.click(function (evt) {
      evt.stopPropagation();
      evt.preventDefault();
      var $this = $(this);
      if ($this.hasClass(CIRCUIT_CONSTANTS.VALCLASS.UNKNOWN)) {
        $this.removeClass(CIRCUIT_CONSTANTS.VALCLASS.UNKNOWN)
          .addClass(CIRCUIT_CONSTANTS.VALCLASS[false]);
      } else {
        $this.toggleClass(CIRCUIT_CONSTANTS.VALCLASS[false])
          .toggleClass(CIRCUIT_CONSTANTS.VALCLASS[true]);
      }
      circuit.element.trigger("circuit-changed");
    });
  };
  CircuitSimulationExercise.prototype.grade = function () {
    var outputValue = function (comp) {
        if (comp.element.find(".circuit-output." + CIRCUIT_CONSTANTS.VALCLASS[true]).size() > 0) {
          return true;
        } else if (comp.element.find(".circuit-output." + CIRCUIT_CONSTANTS.VALCLASS[false]).size() > 0) {
          return false;
        } else {
          return null;
        }
      },
      inputValue = function (comp, pos) {
        if (comp.element.find(".circuit-input." + CIRCUIT_CONSTANTS.VALCLASS[true] +
          "[data-pos=" + pos + "]").size() > 0) {
          return true;
        } else if (comp.element.find(".circuit-input." + CIRCUIT_CONSTANTS.VALCLASS[false] +
          "[data-pos=" + pos + "]").size() > 0) {
          return false;
        } else {
          return null;
        }
      };
    var modelcircuit = new LogicCircuit({});
    modelcircuit.state(this.circuit.state());
    modelcircuit.simulateOutput(this.options.input);
    var modelComps = modelcircuit._components,
      stdComps = this.circuit._components,
      mc, sc, fb, corr, state, val, success = true,
      feedback = [],
      states = [];
    for (var i = 0, l = stdComps.length; i < l; i++) {
      mc = modelComps[i];
      sc = stdComps[i];
      if (!(sc instanceof LogicCircuit.COMPONENT_TYPES.CircuitInputComponent)) {
        fb = {input: []};
        feedback.push(fb);
        state = {input: []};
        states.push(state);
        if (!(sc instanceof LogicCircuit.COMPONENT_TYPES.CircuitOutputComponent)) {
          val = outputValue(sc);
          corr = val === outputValue(mc);
          success = success && corr;
          fb.output = corr;
          state.output = val;
        }
        for (var j = 0; j < sc._inputCount; j++) {
          val = inputValue(sc, j);
          corr = val === inputValue(mc, j);
          success = success && corr;
          fb.input.push(corr);
          state.input.push(val);
        }
      } else {
        feedback.push(null);
        states.push(null);
      }
    }
    modelcircuit.element.remove();
    return {feedback: feedback, states: states, success: success, circuit: this.circuit.state()};
  };

  var CircuitSimulationFeedback = function (exeropts, feedback, options) {
    this.options = $.extend({}, options);
    this.exeropts = exeropts;
    this.feedback = feedback;
    if (!this.options.element) {
      this.element = $("

");
      this.element.appendTo(document.body);
    } else {
      this.element = $(this.options.element);
    }
    this.element.addClass("circuit-feedback");
    this.element.html("

");
    this.initCircuit();
    this.initFeedback();

    this.element.find("h2").click(function () {
      this.element.remove();
    }.bind(this));
  };
  CircuitSimulationFeedback.prototype.initCircuit = CircuitExerciseFeedback.prototype.initCircuit;
  CircuitSimulationFeedback.prototype.initFeedback = function () {
    var comps = this.circuit._components,
      feedback = this.feedback.feedback,
      states = this.feedback.states,
      c, state, fb;
    var outputFeedback = function (comp, compFb, compState) {
      var e = comp.element.find(".circuit-output");
      e.addClass(CIRCUIT_CONSTANTS.VALCLASS[compState.output])
        .addClass(CIRCUIT_CONSTANTS.FEEDBACKCLASS[compFb.output])
        .addClass("circuit-value-interactive");
    };
    var inputFeedback = function (comp, pos, compFb, compState) {
      var e = comp._inputElements[pos];
      e.addClass(CIRCUIT_CONSTANTS.VALCLASS[compState.input[pos]])
        .addClass(CIRCUIT_CONSTANTS.FEEDBACKCLASS[compFb.input[pos]])
        .addClass("circuit-value-interactive");
    };
    for (var i = 0, l = comps.length; i < l; i++) {
      c = comps[i];
      if (!(c instanceof LogicCircuit.COMPONENT_TYPES.CircuitInputComponent)) {
        fb = feedback[i];
        state = states[i];
        if (!(c instanceof LogicCircuit.COMPONENT_TYPES.CircuitOutputComponent)) {
          outputFeedback(c, fb, state);
        }
        for (var j = c._inputCount; j--;) {
          inputFeedback(c, j, fb, state);
        }
      }
    }
  };

  window.CircuitExercise = CircuitExercise;
  window.CircuitExerciseFeedback = CircuitExerciseFeedback;
  window.CircuitSimulationExercise = CircuitSimulationExercise;
  window.CircuitSimulationFeedback = CircuitSimulationFeedback;
}());<|endoftext|>


<commit_before>import React, { Component, PureComponent } from 'react'
import { view, store } from 'react-easy-state'
import { mount } from 'enzyme'

describe('errors', () => {
  test('view() should respect shouldComponentUpdate', () => {
    const person = store({ name: 'Bob' })
    const MyComp = view(
      class extends Component {
        shouldComponentUpdate () {
          return false
        }
        render () {
          return
{person.name}

        }
      }
    )

    const comp = mount(<MyComp />)
    expect(comp.text()).toBe('Bob')
    person.name = 'Ann'
    expect(comp.text()).toBe('Bob')
  })

  test('store() should throw on non object or undefined argument', () => {
    expect(() => store(null)).toThrow()
    expect(() => store({})).not.toThrow()
    expect(() => store()).not.toThrow()
  })

  test('should not change vanilla setState behavior', () => {
    const MyComp = view(
      class extends Component {
        state = { counter: 0 }
        increment = () => this.setState({ counter: this.state.counter + 1 })

        render () {
          return
{this.state.counter}

        }
      }
    )

    const comp = mount(<MyComp />)
    expect(comp.text()).toBe('0')
    comp.find('div').simulate('click')
    expect(comp.text()).toBe('1')
  })

  test('should not change vanilla setState behavior', () => {
    const MyComp = view(
      class extends Component {
        state = { counter: 0 }
        increment = () => this.setState({ counter: this.state.counter + 1 })

        render () {
          return
{this.state.counter}

        }
      }
    )

    const comp = mount(<MyComp />)
    expect(comp.text()).toBe('0')
    comp.find('div').simulate('click')
    expect(comp.text()).toBe('1')
  })

  test("should not render when state or props don't change", () => {
    const MyComp = view(
      class extends Component {
        state = { counter: 0 }
        increment = () => this.setState({ counter: this.state.counter + 1 })

        render () {
          return (

              <Child />
              <button onClick={this.increment}>Increment</button>

          )
        }
      }
    )

    const RawChild = jest.fn().mockReturnValue(
Test
)
    const Child = view(RawChild)

    const comp = mount(<MyComp />)
    expect(RawChild.mock.calls.length).toBe(1)
    comp.find('button').simulate('click')
    expect(RawChild.mock.calls.length).toBe(1)
  })
})
<commit_msg>fix: linter issues<commit_after>import React, { Component } from 'react'
import { view, store } from 'react-easy-state'
import { mount } from 'enzyme'

describe('errors', () => {
  test('view() should respect shouldComponentUpdate', () => {
    const person = store({ name: 'Bob' })
    const MyComp = view(
      class extends Component {
        shouldComponentUpdate () {
          return false
        }
        render () {
          return
{person.name}

        }
      }
    )

    const comp = mount(<MyComp />)
    expect(comp.text()).toBe('Bob')
    person.name = 'Ann'
    expect(comp.text()).toBe('Bob')
  })

  test('store() should throw on non object or undefined argument', () => {
    expect(() => store(null)).toThrow()
    expect(() => store({})).not.toThrow()
    expect(() => store()).not.toThrow()
  })

  test('should not change vanilla setState behavior', () => {
    const MyComp = view(
      class extends Component {
        state = { counter: 0 }
        increment = () => this.setState({ counter: this.state.counter + 1 })

        render () {
          return
{this.state.counter}

        }
      }
    )

    const comp = mount(<MyComp />)
    expect(comp.text()).toBe('0')
    comp.find('div').simulate('click')
    expect(comp.text()).toBe('1')
  })

  test('should not change vanilla setState behavior', () => {
    const MyComp = view(
      class extends Component {
        state = { counter: 0 }
        increment = () => this.setState({ counter: this.state.counter + 1 })

        render () {
          return
{this.state.counter}

        }
      }
    )

    const comp = mount(<MyComp />)
    expect(comp.text()).toBe('0')
    comp.find('div').simulate('click')
    expect(comp.text()).toBe('1')
  })

  test("should not render when state or props don't change", () => {
    const MyComp = view(
      class extends Component {
        state = { counter: 0 }
        increment = () => this.setState({ counter: this.state.counter + 1 })

        render () {
          return (

              <Child />
              <button onClick={this.increment}>Increment</button>

          )
        }
      }
    )

    const RawChild = jest.fn().mockReturnValue(
Test
)
    const Child = view(RawChild)

    const comp = mount(<MyComp />)
    expect(RawChild.mock.calls.length).toBe(1)
    comp.find('button').simulate('click')
    expect(RawChild.mock.calls.length).toBe(1)
  })
})
<|endoftext|>


<commit_before>// @flow
import hashStr from 'glamor/lib/hash'

import type { RuleSet, NameGenerator } from '../types'
import flatten from '../utils/flatten'
import parse from '../vendor/postcss-safe-parser/parse'
import postcssNested from '../vendor/postcss-nested'
import autoprefix from '../utils/autoprefix'
import styleSheet from './StyleSheet'

/*
 ComponentStyle is all the CSS-specific stuff, not
 the React-specific stuff.
 */
export default (nameGenerator: NameGenerator) => {
  const inserted = {}

  class ComponentStyle {
    rules: RuleSet
    insertedRule: Object

    constructor(rules: RuleSet) {
      this.rules = rules
      if (!styleSheet.injected) styleSheet.inject()
    }

    generateName(str: string) {
      return nameGenerator(hashStr(str))
    }

    /*
     * Flattens a rule set into valid CSS
     * Hashes it, wraps the whole chunk in a ._hashName {}
     * Parses that with PostCSS then runs PostCSS-Nested on it
     * Returns the hash to be injected on render()
     * */
    generateAndInjectStyles(executionContext: Object, identifier: string) {
      if (!this.insertedRule) this.insertedRule = styleSheet.insert(`.${identifier} {}`)
      const flatCSS = flatten(this.rules, executionContext).join('')
        .replace(/^\s*\/\/.*$/gm, '') // replace JS comments
      const hash = hashStr(identifier + flatCSS)
      if (!inserted[hash]) {
        const selector = nameGenerator(hash)
        inserted[hash] = selector
        const root = parse(`.${selector} { ${flatCSS} }`)
        postcssNested(root)
        autoprefix(root)
        this.insertedRule.appendRule(`\n${root.toResult().css}`)
      }
      return inserted[hash]
    }
  }

  return ComponentStyle
}
<commit_msg>roll back the empty classname generation<commit_after>// @flow
import hashStr from 'glamor/lib/hash'

import type { RuleSet, NameGenerator } from '../types'
import flatten from '../utils/flatten'
import parse from '../vendor/postcss-safe-parser/parse'
import postcssNested from '../vendor/postcss-nested'
import autoprefix from '../utils/autoprefix'
import styleSheet from './StyleSheet'

/*
 ComponentStyle is all the CSS-specific stuff, not
 the React-specific stuff.
 */
export default (nameGenerator: NameGenerator) => {
  const inserted = {}

  class ComponentStyle {
    rules: RuleSet
    insertedRule: Object

    constructor(rules: RuleSet) {
      this.rules = rules
      if (!styleSheet.injected) styleSheet.inject()
    }

    generateName(str: string) {
      return nameGenerator(hashStr(str))
    }

    /*
     * Flattens a rule set into valid CSS
     * Hashes it, wraps the whole chunk in a ._hashName {}
     * Parses that with PostCSS then runs PostCSS-Nested on it
     * Returns the hash to be injected on render()
     * */
    generateAndInjectStyles(executionContext: Object, identifier: string) {
      // TODO: this is good. Restore it. --------------------------------------v
      if (!this.insertedRule) this.insertedRule = styleSheet.insert(''/* `.${identifier} {}`*/)
      const flatCSS = flatten(this.rules, executionContext).join('')
        .replace(/^\s*\/\/.*$/gm, '') // replace JS comments
      const hash = hashStr(identifier + flatCSS)
      if (!inserted[hash]) {
        const selector = nameGenerator(hash)
        inserted[hash] = selector
        const root = parse(`.${selector} { ${flatCSS} }`)
        postcssNested(root)
        autoprefix(root)
        this.insertedRule.appendRule(/* \n*/`${root.toResult().css}`)
      }
      return inserted[hash]
    }
  }

  return ComponentStyle
}
<|endoftext|>


<commit_before>import React from 'react'
import { sizeClassNames } from '../util.js'
import cx from 'classnames'

export default class Label extends React.Component {
  static displayName = 'FriggingBootstrap.Label'

  static propTypes = {
    labelWidth: React.PropTypes.object.isRequired,
    layout: React.PropTypes.string.isRequired,
    block: React.PropTypes.bool,
    label: React.PropTypes.oneOfType([
      React.PropTypes.string,
      React.PropTypes.element,
    ]),
  }

  static defaultProps = {
    block: false,
    label: '',
  }

  isEmpty() { return !this.props.label }

  render() {
    const horizontalClasses = sizeClassNames(
      this.props.labelWidth, { offsets: false }
    )

    if (this.props.block) return null
    if (this.props.layout === 'horizontal' && this.isEmpty()) {
      return

    }

    const labelHtml = Object.assign({}, this.props)
    labelHtml.className = cx(labelHtml.className, {
      [horizontalClasses]: this.props.layout === 'horizontal',
    })

    return (

          {this.props.label}

    )
  }
}
<commit_msg>Fix proptype warning when setting label={false} on an input<commit_after>import React from 'react'
import { sizeClassNames } from '../util.js'
import cx from 'classnames'

export default class Label extends React.Component {
  static displayName = 'FriggingBootstrap.Label'

  static propTypes = {
    labelWidth: React.PropTypes.object.isRequired,
    layout: React.PropTypes.string.isRequired,
    block: React.PropTypes.bool,
    label: React.PropTypes.oneOfType([
      React.PropTypes.string,
      React.PropTypes.element,
      React.PropTypes.bool,    // can set label={false} to not show a label
    ]),
  }

  static defaultProps = {
    block: false,
    label: '',
  }

  isEmpty() { return !this.props.label }

  render() {
    const horizontalClasses = sizeClassNames(
      this.props.labelWidth, { offsets: false }
    )

    if (this.props.block) return null
    if (this.props.layout === 'horizontal' && this.isEmpty()) {
      return

    }

    const labelHtml = Object.assign({}, this.props)
    labelHtml.className = cx(labelHtml.className, {
      [horizontalClasses]: this.props.layout === 'horizontal',
    })

    return (

          {this.props.label}

    )
  }
}
<|endoftext|>


<commit_before>/**
 * Main javascript engine
 */
const host = 'predb.ovh';
const apiEndpoint = '/api/v1';
const apiUrl = 'https://' + host + apiEndpoint;
const wsUrl = 'wss://' + host + apiEndpoint + '/ws';

var $input = document.getElementsByClassName('input-query')[0];
var $btnGo = document.getElementsByClassName('input-go')[0];
var $btnLive = document.getElementsByClassName('input-live')[0];
var $status = document.getElementsByClassName('status')[0];
var $t = document.getElementsByClassName('table')[0];
var $pgn = document.getElementsByClassName('pagination')[0];
var $tpl = document.getElementById('template');
var $tplRow = $tpl.getElementsByClassName('row')[0];

var ws = null;
const empty = '\u00A0';
const validQueryParams = [
    'page',
    'offset',
    'count',
    'q',
    'live',
];

/**
 * Query input handlers
 */
function rereadInput() {
    $btnGo.href = $input.value ? '/?q=' + $input.value : '/';
}

$input.onkeyup = function (e) {
    rereadInput();
    if (e.keyCode != 13) {
        return;
    }

    $btnGo.onclick();
}

/**
 * Buttons handlers
 */
function clickHandler(e) {
    if (e.target instanceof HTMLAnchorElement) {
        setUrl(e.target.href);
        runUrl();
        return false;
    }
}

$btnGo.onclick = function () {
    setUrl($btnGo.href);
    runUrl();
    $input.focus();
    return false;
}

$btnLive.onclick = clickHandler;
$pgn.onclick = clickHandler;
$t.onclick = clickHandler;

/**
 * Fetch handlers
 */
function checkHTTP(res) {
    if (res.status == 200) {
        return res;
    } else {
        var err = new Error(res.statusText);
        err.response = res;
        throw err;
    }
}

function parseJSON(res) {
    return res.json();
}

function checkStatus(j) {
    if (j.status == "success") {
        return j.data;
    } else {
        var err = new Error(j.message);
        err.response = j;
        throw err;
    }
}

/**
 * Main query function
 */
function query(str, page) {
    if (ws) {
        ws.close();
        ws = null;
    }

    var req = '';
    var title = '';
    if (str) {
        req = '/?q=' + encodeURIComponent(str)
        title = 'Search ' + str;
    }
    if (page) {
        req += (str ? '&' : '/?') + 'page=' + page;
    }

    setTitle(title);
    getAndFill(req);
}

function getAndFill(req) {
    var perfStart = window.performance.now();
    $t.innerHTML = '';
    status('Loading');
    hidePageBar();
    fetch(apiUrl + (req || '/'))
        .then(checkHTTP)
        .then(parseJSON)
        .then(checkStatus)
        .then(function (j) {
            $t.innerHTML = '';
            if (j.rowCount === 0) {
                status('No results');
                return;
            }
            if (req !== false) {
                var nTime = Math.round(window.performance.now() - perfStart) / 1000;
                var bTime = Math.round(j.time * 1000) / 1000;
                paginate(j);
                status('Results ' + (j.offset + 1) + '-' +
                    (j.offset + j.rowCount) + ' of ' + j.total +
                    ' matches in ' + nTime + ' seconds');
            } else {
                status();
            }

            var fragment = document.createDocumentFragment();
            j.rows.forEach(function (e) {
                fragment.appendChild(newRow(e));
            }, this);

            $t.appendChild(fragment);
        }).catch(function (err) {
            status(err.message || 'Internal error');
        });
}

/**
 * Live websocket engine
 */
function websocket() {
    getAndFill('/live');
    if (ws) {
        ws.close()
    }

    $input.value = '';
    rereadInput();
    setTitle('Live');
    hidePageBar();

    ws = new WebSocket(wsUrl);
    ws.onopen = function (e) {
        status('Websocket started');
    };
    ws.onmessage = function (e) {
        var j = JSON.parse(e.data);
        if (!j.action) {
            return;
        }

        status('Last update : ' + new Date().toLocaleString());
        switch (j.action) {
            case 'insert':
                $r = newRow(j.row);
                $t.insertBefore($r, $t.firstChild);
                break;
            case 'update':
                var $r = document.getElementById(j.row.id);
                if ($r) {
                    setRow($r, j.row);
                }
                break;
            case 'delete':
                var $r = document.getElementById(j.row.id);
                if ($r) {
                    $r.remove();
                }
                break;
            case 'nuke':
            case 'modnuke':
            case 'unnuke':
            case 'delpre':
            case 'undelpre':
                if (j.row.nuke) {
                    var $r = document.getElementById(j.row.id);
                    if ($r) {
                        setNuke($r, j.row.nuke);
                    }
                }
                break;
        }

    };
    ws.onerror = function (e) {
        status('Websocket error, trying again');
        setTimeout(websocket, 1000);
    };
    ws.onclose = function (e) {
        status('Websocket closed');
    }
}

/**
 * DOM modifiers
 */
function status(str) {
    replaceChildren($status, nodeTxt(str || empty));
}

function newRow(e) {
    var $r = $tplRow.cloneNode(true);
    return setRow($r, e);
}

function setRow($r, e) {
    $r.setAttribute('id', e.id);
    replaceChildren($r.getElementsByClassName('cell-cat')[0], nodeLink('/?q=@cat ' + e.cat, e.cat));
    replaceChildren($r.getElementsByClassName('cell-genre')[0], nodeTxt(e.genre || empty, 30));
    e.dTeam = '-' + e.team;
    e.dName = e.name.replace(e.dTeam, '');
    //replaceChildren($r.getElementsByClassName('rls-name')[0], nodeLink('/?q=@name ' + e.name, e.dName));
    replaceChildren($r.getElementsByClassName('rls-name')[0], nodeTxt(e.dName));
    replaceChildren($r.getElementsByClassName('rls-grp')[0], nodeLink('/?q=@team ' + e.team, e.dTeam));
    if (e.nuke) {
        setNuke($r, e.nuke);
    }
    replaceChildren($r.getElementsByClassName('cell-pretime')[0], nodeTime(e.preAt));
    replaceChildren($r.getElementsByClassName('cell-files')[0], nodeNumber(e.files));
    replaceChildren($r.getElementsByClassName('cell-size')[0], nodeNumber(e.size));
    return $r;
}

function setNuke($r, e) {
    var classList = $r.getElementsByClassName('cell-nuke')[0].classList;
    classList.remove("hidden");
    switch (e.type) {
        case "nuke":
        case "modnuke":
        case "delpre":
            classList.add("nuketype-nuke");
            break;
        case "unnuke":
        case "undelpre":
            classList.add("nuketype-fine");
    }
    replaceChildren($r.getElementsByClassName('nuke-reason')[0], nodeTxt(e.reason));
    //replaceChildren($r.getElementsByClassName('nuke-time')[0], nodeTime(e.nukeAt));
    replaceChildren($r.getElementsByClassName('nuke-net')[0], nodeTxt(e.net));
}

function nodeLink(u, t) {
    var $n = document.createElement('a');
    $n.setAttribute('href', u);
    $n.text = t;
    return $n;
}

function nodeNumber(n) {
    return n > 0 ? nodeTxt(n) : null;
}

function nodeTxt(t, maxLen) {
    if (maxLen && t.length > maxLen) {
        pos = t.indexOf('_', maxLen);
        if (pos != -1) {
            t = t.substring(0, pos);
        }
    }
    return document.createTextNode(t);
}

function nodeTime(timestamp) {
    var $n = document.createElement('time');
    var d = new Date(timestamp * 1000);
    $n.setAttribute('datetime', d.toISOString());
    $n.textContent = d.toLocaleString();
    return $n;
}

function replaceChildren($parent, $e) {
    while ($parent.lastChild) {
        $parent.removeChild($parent.lastChild);
    }
    if ($e) {
        $parent.appendChild($e);
    }
}

/**
 * Pagination handlers
 */
function paginate(j) {
    if (j.rowCount == j.total) {
        hidePageBar();
        return;
    }

    setPageBar(j.offset / j.reqCount + 1, Math.ceil(j.total / j.reqCount));
}

function hidePageBar() {
    $pgn.style.display = 'none';
}

function setPageBar(curr, max) {
    if (max === 0) {
        hidePageBar();
        return;
    }

    var qParams = parseQueryParams(window.location.search);

    var fragment = document.createDocumentFragment();
    var first = curr - 4;
    var last = curr + 5;
    for (var i = first; i <= last; i++) {
        if (i <= 0) {
            first++;
            last++;
            continue;
        }

        if (i === first && i !== curr) {
            fragment.appendChild(newPageButton(curr - 1, qParams, '<<', true));
        }
        if (i > max || i >= last) {
            if (curr !== max) {
                fragment.appendChild(newPageButton(curr + 1, qParams, '>>', true));
            }
            break;
        }
        if (i === curr) {
            fragment.appendChild(newPageButton(i, qParams, i, false));
        } else {
            fragment.appendChild(newPageButton(i, qParams, i, true));
        }
    }

    replaceChildren($pgn, fragment);
    $pgn.style.display = 'block';
}

function newPageButton(i, qParams, text, active) {
    var $btn;
    if (active) {
        $btn = document.createElement('a');
        $btn.text = text;
        $btn.classList.add('page-btn');
        qParams['page'] = i;
        $btn.href = buildQueryUrl(qParams);
    } else {
        $btn = document.createElement('span');
        $btn.textContent = text;
        $btn.classList.add('page-btn', 'page-btn-inactive');
    }
    return $btn;
}

/**
 * URL functions
 */
function setUrl(u) {
    if (!u) {
        u = window.location.href.replace(window.location.search, '');
    }
    window.history.replaceState(null, '', u);
}

function setTitle(title) {
    if (title) {
        document.title = title + ' - PREdb';
    } else {
        document.title = 'PREdb';
    }
}

function parseQueryParams(s) {
    if (s == '') {
        return {};
    }

    var split = s.split('?');
    if (split.length < 2) {
        return;
    }

    qParams = {};
    params = split[1].split('&');
    params.forEach(function (p) {
        var [k, v] = p.split('=');
        qParams[k] = decodeURIComponent(v || '');
    });

    return qParams;
}

function buildQueryUrl(qParams) {
    var urlQuery = [];
    validQueryParams.forEach(function (p) {
        if (p == 'live' && urlQuery.length) {
            return;
        }
        if (qParams[p] !== undefined) {
            urlQuery.push(p + (qParams[p] == '' ? '' : '=' + qParams[p]));
        }
    });

    return urlQuery.length ? ('/?' + urlQuery.join('&')) : '';
}

function runUrl() {
    var qParams = parseQueryParams(window.location.search);

    if (qParams.hasOwnProperty('live')) {
        websocket();
        return;
    }

    $input.value = qParams.q || '';
    rereadInput();
    query(qParams.q, qParams.page);
}

runUrl();
<commit_msg>Nice use of fetch promises<commit_after>/**
 * Main javascript engine
 */
const host = 'predb.ovh';
const apiEndpoint = '/api/v1';
const apiUrl = 'https://' + host + apiEndpoint;
const wsUrl = 'wss://' + host + apiEndpoint + '/ws';

var $input = document.getElementsByClassName('input-query')[0];
var $btnGo = document.getElementsByClassName('input-go')[0];
var $btnLive = document.getElementsByClassName('input-live')[0];
var $status = document.getElementsByClassName('status')[0];
var $t = document.getElementsByClassName('table')[0];
var $pgn = document.getElementsByClassName('pagination')[0];
var $tpl = document.getElementById('template');
var $tplRow = $tpl.getElementsByClassName('row')[0];

var ws = null;
const empty = '\u00A0';
const validQueryParams = [
    'page',
    'offset',
    'count',
    'q',
    'live',
];

/**
 * Query input handlers
 */
function rereadInput() {
    $btnGo.href = $input.value ? '/?q=' + $input.value : '/';
}

$input.onkeyup = function (e) {
    rereadInput();
    if (e.keyCode != 13) {
        return;
    }

    $btnGo.onclick();
}

/**
 * Buttons handlers
 */
function clickHandler(e) {
    if (e.target instanceof HTMLAnchorElement) {
        setUrl(e.target.href);
        runUrl();
        return false;
    }
}

$btnGo.onclick = function () {
    setUrl($btnGo.href);
    runUrl();
    $input.focus();
    return false;
}

$btnLive.onclick = clickHandler;
$pgn.onclick = clickHandler;
$t.onclick = clickHandler;

/**
 * Fetch handlers
 */
function checkHTTP(res) {
    if (res.status == 200) {
        return res;
    } else {
        var err = new Error(res.statusText);
        err.response = res;
        throw err;
    }
}

function parseJSON(res) {
    return res.json();
}

function checkStatus(j) {
    if (j.status == "success") {
        return j.data;
    } else {
        var err = new Error(j.message);
        err.response = j;
        throw err;
    }
}

/**
 * Main query function
 */
function query(str, page) {
    if (ws) {
        ws.close();
        ws = null;
    }

    var req = '';
    var title = '';
    if (str) {
        req = '/?q=' + encodeURIComponent(str)
        title = 'Search ' + str;
    }
    if (page) {
        req += (str ? '&' : '/?') + 'page=' + page;
    }

    setTitle(title);
    hidePageBar();
    status('Loading');
    getAndFill(req).then(paginate);
}

function getAndFill(req) {
    var perfStart = window.performance.now();
    $t.innerHTML = '';
    return fetch(apiUrl + (req || '/'))
        .then(checkHTTP)
        .then(parseJSON)
        .then(checkStatus)
        .then(function (j) {
            $t.innerHTML = '';
            if (j.rowCount === 0) {
                status('No results');
                return;
            }
                var nTime = Math.round(window.performance.now() - perfStart) / 1000;
                status('Results ' + (j.offset + 1) + '-' +
                    (j.offset + j.rowCount) + ' of ' + j.total +
                    ' matches in ' + nTime + ' seconds');

            var fragment = document.createDocumentFragment();
            j.rows.forEach(function (e) {
                fragment.appendChild(newRow(e));
            }, this);

            $t.appendChild(fragment);

            return j;
        }).catch(function (err) {
            status(err.message || 'Internal error');
        });
}

/**
 * Live websocket engine
 */
function websocket() {
    setTitle('Live');
    hidePageBar();
    status('Live');
    getAndFill('/live');
    if (ws) {
        ws.close()
    }

    $input.value = '';
    rereadInput();

    ws = new WebSocket(wsUrl);
    ws.onopen = function (e) {
        status('Websocket started');
    };
    ws.onmessage = function (e) {
        var j = JSON.parse(e.data);
        if (!j.action) {
            return;
        }

        status('Last update : ' + new Date().toLocaleString());
        switch (j.action) {
            case 'insert':
                $r = newRow(j.row);
                $t.insertBefore($r, $t.firstChild);
                break;
            case 'update':
                var $r = document.getElementById(j.row.id);
                if ($r) {
                    setRow($r, j.row);
                }
                break;
            case 'delete':
                var $r = document.getElementById(j.row.id);
                if ($r) {
                    $r.remove();
                }
                break;
            case 'nuke':
            case 'modnuke':
            case 'unnuke':
            case 'delpre':
            case 'undelpre':
                if (j.row.nuke) {
                    var $r = document.getElementById(j.row.id);
                    if ($r) {
                        setNuke($r, j.row.nuke);
                    }
                }
                break;
        }

    };
    ws.onerror = function (e) {
        status('Websocket error, trying again');
        setTimeout(websocket, 1000);
    };
    ws.onclose = function (e) {
        status('Websocket closed');
    }
}

/**
 * DOM modifiers
 */
function status(str) {
    replaceChildren($status, nodeTxt(str || empty));
}

function newRow(e) {
    var $r = $tplRow.cloneNode(true);
    return setRow($r, e);
}

function setRow($r, e) {
    $r.setAttribute('id', e.id);
    replaceChildren($r.getElementsByClassName('cell-cat')[0], nodeLink('/?q=@cat ' + e.cat, e.cat));
    replaceChildren($r.getElementsByClassName('cell-genre')[0], nodeTxt(e.genre || empty, 30));
    e.dTeam = '-' + e.team;
    e.dName = e.name.replace(e.dTeam, '');
    //replaceChildren($r.getElementsByClassName('rls-name')[0], nodeLink('/?q=@name ' + e.name, e.dName));
    replaceChildren($r.getElementsByClassName('rls-name')[0], nodeTxt(e.dName));
    replaceChildren($r.getElementsByClassName('rls-grp')[0], nodeLink('/?q=@team ' + e.team, e.dTeam));
    if (e.nuke) {
        setNuke($r, e.nuke);
    }
    replaceChildren($r.getElementsByClassName('cell-pretime')[0], nodeTime(e.preAt));
    replaceChildren($r.getElementsByClassName('cell-files')[0], nodeNumber(e.files));
    replaceChildren($r.getElementsByClassName('cell-size')[0], nodeNumber(e.size));
    return $r;
}

function setNuke($r, e) {
    var classList = $r.getElementsByClassName('cell-nuke')[0].classList;
    classList.remove("hidden");
    switch (e.type) {
        case "nuke":
        case "modnuke":
        case "delpre":
            classList.add("nuketype-nuke");
            break;
        case "unnuke":
        case "undelpre":
            classList.add("nuketype-fine");
    }
    replaceChildren($r.getElementsByClassName('nuke-reason')[0], nodeTxt(e.reason));
    //replaceChildren($r.getElementsByClassName('nuke-time')[0], nodeTime(e.nukeAt));
    replaceChildren($r.getElementsByClassName('nuke-net')[0], nodeTxt(e.net));
}

function nodeLink(u, t) {
    var $n = document.createElement('a');
    $n.setAttribute('href', u);
    $n.text = t;
    return $n;
}

function nodeNumber(n) {
    return n > 0 ? nodeTxt(n) : null;
}

function nodeTxt(t, maxLen) {
    if (maxLen && t.length > maxLen) {
        pos = t.indexOf('_', maxLen);
        if (pos != -1) {
            t = t.substring(0, pos);
        }
    }
    return document.createTextNode(t);
}

function nodeTime(timestamp) {
    var $n = document.createElement('time');
    var d = new Date(timestamp * 1000);
    $n.setAttribute('datetime', d.toISOString());
    $n.textContent = d.toLocaleString();
    return $n;
}

function replaceChildren($parent, $e) {
    while ($parent.lastChild) {
        $parent.removeChild($parent.lastChild);
    }
    if ($e) {
        $parent.appendChild($e);
    }
}

/**
 * Pagination handlers
 */
function paginate(j) {
    if (j.rowCount == j.total) {
        hidePageBar();
        return;
    }

    setPageBar(j.offset / j.reqCount + 1, Math.ceil(j.total / j.reqCount));
}

function hidePageBar() {
    $pgn.style.display = 'none';
}

function setPageBar(curr, max) {
    if (max === 0) {
        hidePageBar();
        return;
    }

    var qParams = parseQueryParams(window.location.search);

    var fragment = document.createDocumentFragment();
    var first = curr - 4;
    var last = curr + 5;
    for (var i = first; i <= last; i++) {
        if (i <= 0) {
            first++;
            last++;
            continue;
        }

        if (i === first && i !== curr) {
            fragment.appendChild(newPageButton(curr - 1, qParams, '<<', true));
        }
        if (i > max || i >= last) {
            if (curr !== max) {
                fragment.appendChild(newPageButton(curr + 1, qParams, '>>', true));
            }
            break;
        }
        if (i === curr) {
            fragment.appendChild(newPageButton(i, qParams, i, false));
        } else {
            fragment.appendChild(newPageButton(i, qParams, i, true));
        }
    }

    replaceChildren($pgn, fragment);
    $pgn.style.display = 'block';
}

function newPageButton(i, qParams, text, active) {
    var $btn;
    if (active) {
        $btn = document.createElement('a');
        $btn.text = text;
        $btn.classList.add('page-btn');
        qParams['page'] = i;
        $btn.href = buildQueryUrl(qParams);
    } else {
        $btn = document.createElement('span');
        $btn.textContent = text;
        $btn.classList.add('page-btn', 'page-btn-inactive');
    }
    return $btn;
}

/**
 * URL functions
 */
function setUrl(u) {
    if (!u) {
        u = window.location.href.replace(window.location.search, '');
    }
    window.history.replaceState(null, '', u);
}

function setTitle(title) {
    if (title) {
        document.title = title + ' - PREdb';
    } else {
        document.title = 'PREdb';
    }
}

function parseQueryParams(s) {
    if (s == '') {
        return {};
    }

    var split = s.split('?');
    if (split.length < 2) {
        return;
    }

    qParams = {};
    params = split[1].split('&');
    params.forEach(function (p) {
        var [k, v] = p.split('=');
        qParams[k] = decodeURIComponent(v || '');
    });

    return qParams;
}

function buildQueryUrl(qParams) {
    var urlQuery = [];
    validQueryParams.forEach(function (p) {
        if (p == 'live' && urlQuery.length) {
            return;
        }
        if (qParams[p] !== undefined) {
            urlQuery.push(p + (qParams[p] == '' ? '' : '=' + qParams[p]));
        }
    });

    return urlQuery.length ? ('/?' + urlQuery.join('&')) : '';
}

function runUrl() {
    var qParams = parseQueryParams(window.location.search);

    if (qParams.hasOwnProperty('live')) {
        websocket();
        return;
    }

    $input.value = qParams.q || '';
    rereadInput();
    query(qParams.q, qParams.page);
}

runUrl();
<|endoftext|>


<commit_before>"use strict";

const controller = function controller ($scope, $element, $sce, adsFactory, productsService) {

  $scope.ads = adsFactory;
  $scope.currentAd = $scope.ads[0] || null;

  $scope.products = [];
  $scope.currentProduct = null;
  productsService.get((error, products) => {
    if (error) {
      return alert(`Error loading products (${error}); returning.`);
    }
    $scope.products = products;
    $scope.currentProduct = $scope.products[0] || null;
  });

  // $scope.$watch("currentAd", () => {
  //   $scope.updateHtml();
  // });

  $scope.updateHtml = function updateHtml () {
    $scope.currentAd.updateHtml(
      $sce.trustAsHtml(
        $scope.currentAd.getHtml($scope.currentProduct)
      )
    );
  };
  $scope.updateHtml();

};

module.exports = controller;<commit_msg>Run updateHtml() when async product get completes.<commit_after>"use strict";

const controller = function controller ($scope, $element, $sce, adsFactory, productsService) {

  $scope.ads = adsFactory;
  $scope.currentAd = $scope.ads[0] || null;

  $scope.updateHtml = function updateHtml () {
    $scope.currentAd.updateHtml(
      $sce.trustAsHtml(
        $scope.currentAd.getHtml($scope.currentProduct)
      )
    );
  };

  $scope.products = [];
  $scope.currentProduct = null;
  productsService.get((error, products) => {
    if (error) {
      return alert(`Error loading products (${error}); returning.`);
    }
    $scope.products = products;
    $scope.currentProduct = $scope.products[0] || null;
    $scope.updateHtml();
  });

  // $scope.$watch("currentAd", () => {
  //   $scope.updateHtml();
  // });

};

module.exports = controller;<|endoftext|>