<commit_before>/**
 * knockout binding for Froala Editor
 */

ko.bindingHandlers.froala = (function() { 'use strict';

  // locals
  var unwrap = ko.utils.unwrapObservable;

  /**
   * initiate froala editor, listen to its changes
   * and updates the underlying observable model
   *
   * @param {element} element
   * @param {object} value
   * @param {object} bindings
   * @api public
   */

  function init( element, value, bindings ) {
    var $el = $( element );
    var model = value();
    var allBindings = unwrap( bindings() );
    var options = unwrap( allBindings.froalaOptions );

    // initialize the editor
    $el.froalaEditor( options || {} );

    // update underlying model whenever editor content changed
    $el.on('froalaEditor.contentChanged', function (e, editor) {
      if( ko.isWriteableObservable( model ) ) {
        var editorValue = editor.html.get();
        var current = model();
        if(current !== editorValue) {
          model( editorValue );
        }
      }
    });

    // cleanup editor, when dom node is removed
    ko.utils.domNodeDisposal.addDisposeCallback( element, destroy( element ) );

    // do not handle child nodes
    return { controlsDescendantBindings: true };
  }

  /**
   * update froala editor whenever underlying observable model
   * is updated
   *
   * @param {element} element
   * @param {object} value
   * @api public
   */

  function update( element, value ) {
    var $el = $( element );
    var modelValue = unwrap( value() );
    var editorInstance = $el.data( 'froala.editor' );

    if( editorInstance == null ) {
      return;
    }

    var editorValue = editorInstance.html.get();

    // avoid any un-necessary updates
    if( editorValue !== modelValue ) {
      editorInstance.html.set( modelValue );
    }
  }

  /**
   * destroy froala editor instance
   *
   * @param {dom} element
   * @return {function} handler
   * @api private
   */

  function destroy( element ) {
    var $el = $( element );
    return function() {
      if( $el.data( 'froala.editor' ) ) {
        $el.froalaEditor( 'destroy' );
      }
    }
  }

  /**
   * expose `froala` binding handler methods
   */

  return {
    init: init,
    update: update
  }

})();
<commit_msg>all options can be observables too<commit_after>/**
 * knockout binding for Froala Editor
 */

ko.bindingHandlers.froala = (function() { 'use strict';

  // locals
  var unwrap = ko.utils.unwrapObservable;

  /**
   * initiate froala editor, listen to its changes
   * and updates the underlying observable model
   *
   * @param {element} element
   * @param {object} value
   * @param {object} bindings
   * @api public
   */

  function init( element, value, bindings ) {
    var $el = $( element );
    var model = value();
    var allBindings = unwrap( bindings() );
    var options = ko.toJS( allBindings.froalaOptions );

    // initialize the editor
    $el.froalaEditor( options || {} );

    // update underlying model whenever editor content changed
    $el.on('froalaEditor.contentChanged', function (e, editor) {
      if( ko.isWriteableObservable( model ) ) {
        var editorValue = editor.html.get();
        var current = model();
        if(current !== editorValue) {
          model( editorValue );
        }
      }
    });

    // cleanup editor, when dom node is removed
    ko.utils.domNodeDisposal.addDisposeCallback( element, destroy( element ) );

    // do not handle child nodes
    return { controlsDescendantBindings: true };
  }

  /**
   * update froala editor whenever underlying observable model
   * is updated
   *
   * @param {element} element
   * @param {object} value
   * @api public
   */

  function update( element, value ) {
    var $el = $( element );
    var modelValue = unwrap( value() );
    var editorInstance = $el.data( 'froala.editor' );

    if( editorInstance == null ) {
      return;
    }

    var editorValue = editorInstance.html.get();

    // avoid any un-necessary updates
    if( editorValue !== modelValue ) {
      editorInstance.html.set( modelValue );
    }
  }

  /**
   * destroy froala editor instance
   *
   * @param {dom} element
   * @return {function} handler
   * @api private
   */

  function destroy( element ) {
    var $el = $( element );
    return function() {
      if( $el.data( 'froala.editor' ) ) {
        $el.froalaEditor( 'destroy' );
      }
    }
  }

  /**
   * expose `froala` binding handler methods
   */

  return {
    init: init,
    update: update
  }

})();
<|endoftext|>


<commit_before>import QIcon from '../icon/QIcon.js'
import CanRenderMixin from '../../mixins/can-render.js'

export default {
  name: 'QField',
  mixins: [ CanRenderMixin ],
  props: {
    inset: {
      type: String,
      validator: v => ['icon', 'label', 'full'].includes(v)
    },
    label: String,
    count: {
      type: [Number, Boolean],
      default: false
    },
    error: Boolean,
    errorLabel: String,
    warning: Boolean,
    warningLabel: String,
    helper: String,
    icon: String,
    iconColor: String,
    dark: Boolean,
    orientation: {
      type: String,
      validator: v => ['vertical', 'horizontal'].includes(v)
    },
    labelWidth: {
      type: [Number, String],
      default: 5,
      validator (val) {
        const v = parseInt(val, 10)
        return v > 0 && v < 13
      }
    }
  },
  data () {
    return {
      input: {}
    }
  },
  computed: {
    hasError () {
      return this.input.error || this.error
    },
    hasWarning () {
      return !this.hasError && (this.input.warning || this.warning)
    },
    childHasLabel () {
      return this.input.floatLabel || this.input.stackLabel
    },
    isDark () {
      return this.input.dark || this.dark
    },
    insetIcon () {
      return ['icon', 'full'].includes(this.inset)
    },
    hasNoInput () {
      return this.canRender && (!this.input.$options || this.input.__needsBorder)
    },
    counter () {
      if (this.count) {
        const length = this.input.length || '0'
        return Number.isInteger(this.count)
          ? `${length} / ${this.count}`
          : length
      }
    },
    classes () {
      return {
        'q-field-responsive': !this.isVertical && !this.isHorizontal,
        'q-field-vertical': this.isVertical,
        'q-field-horizontal': this.isHorizontal,
        'q-field-floating': this.childHasLabel,
        'q-field-no-label': !this.label && !this.$slots.label,
        'q-field-with-error': this.hasError,
        'q-field-with-warning': this.hasWarning,
        'q-field-dark': this.isDark,
        'q-field-no-input': this.hasNoInput
      }
    },
    computedLabelWidth () {
      return parseInt(this.labelWidth, 10)
    },
    isVertical () {
      return this.orientation === 'vertical' || this.computedLabelWidth === 12
    },
    isHorizontal () {
      return this.orientation === 'horizontal'
    },
    labelClasses () {
      return this.isVertical
        ? `col-12`
        : (this.isHorizontal ? `col-${this.labelWidth}` : `col-xs-12 col-sm-${this.labelWidth}`)
    },
    inputClasses () {
      return this.isVertical
        ? `col-xs-12`
        : (this.isHorizontal ? 'col' : 'col-xs-12 col-sm')
    },
    iconProps () {
      const prop = { name: this.icon }
      if (this.iconColor && !this.hasError && !this.hasWarning) {
        prop.color = this.iconColor
      }
      return prop
    },
    insetHasLabel () {
      return ['label', 'full'].includes(this.inset)
    }
  },
  provide () {
    return {
      __field: this
    }
  },
  methods: {
    __registerInput (vm) {
      this.input = vm
    },
    __unregisterInput (vm) {
      if (!vm || vm === this.input) {
        this.input = {}
      }
    },
    __getBottomContent (h) {
      let label

      if (this.hasError && (label = this.$slots['error-label'] || this.errorLabel)) {
        return h('div', { staticClass: 'q-field-error col' }, label)
      }
      if (this.hasWarning && (label = this.$slots['warning-label'] || this.warningLabel)) {
        return h('div', { staticClass: 'q-field-warning col' }, label)
      }
      if ((label = this.$slots.helper || this.helper)) {
        return h('div', { staticClass: 'q-field-helper col' }, label)
      }
      return h('div', { staticClass: 'col' })
    },
    __hasBottom () {
      return (this.hasError && (this.$slots['error-label'] || this.errorLabel)) ||
        (this.hasWarning && (this.$slots['warning-label'] || this.warningLabel)) ||
        (this.$slots.helper || this.helper) ||
        this.count
    }
  },
  render (h) {
    const label = this.$slots.label || this.label

    return h('div', {
      staticClass: 'q-field row no-wrap items-start',
      'class': this.classes
    }, [
      this.icon
        ? h(QIcon, {
          props: this.iconProps,
          staticClass: 'q-field-icon q-field-margin'
        })
        : (this.insetIcon ? h('div', { staticClass: 'q-field-icon' }) : null),

      h('div', { staticClass: 'row col' }, [
        label || this.insetHasLabel
          ? h('div', {
            staticClass: 'q-field-label q-field-margin',
            'class': this.labelClasses
          }, [
            h('div', { staticClass: 'q-field-label-inner row items-center' }, [
              this.$slots.label || this.label
            ])
          ])
          : null,

        h('div', {
          staticClass: 'q-field-content ellipsis',
          'class': this.inputClasses
        }, [
          this.$slots.default,
          this.__hasBottom()
            ? h('div', {
              staticClass: 'q-field-bottom row no-wrap'
            }, [
              this.__getBottomContent(h),
              this.counter
                ? h('div', { staticClass: 'q-field-counter col-auto' }, [ this.counter ])
                : null
            ])
            : null
        ])
      ])
    ])
  }
}
<commit_msg>fix: QCard border is clipped when inside a QField #2446<commit_after>import QIcon from '../icon/QIcon.js'
import CanRenderMixin from '../../mixins/can-render.js'

export default {
  name: 'QField',
  mixins: [ CanRenderMixin ],
  props: {
    inset: {
      type: String,
      validator: v => ['icon', 'label', 'full'].includes(v)
    },
    label: String,
    count: {
      type: [Number, Boolean],
      default: false
    },
    error: Boolean,
    errorLabel: String,
    warning: Boolean,
    warningLabel: String,
    helper: String,
    icon: String,
    iconColor: String,
    dark: Boolean,
    orientation: {
      type: String,
      validator: v => ['vertical', 'horizontal'].includes(v)
    },
    labelWidth: {
      type: [Number, String],
      default: 5,
      validator (val) {
        const v = parseInt(val, 10)
        return v > 0 && v < 13
      }
    }
  },
  data () {
    return {
      input: {}
    }
  },
  computed: {
    hasError () {
      return this.input.error || this.error
    },
    hasWarning () {
      return !this.hasError && (this.input.warning || this.warning)
    },
    childHasLabel () {
      return this.input.floatLabel || this.input.stackLabel
    },
    isDark () {
      return this.input.dark || this.dark
    },
    insetIcon () {
      return ['icon', 'full'].includes(this.inset)
    },
    hasNoInput () {
      return this.canRender && (!this.input.$options || this.input.__needsBorder)
    },
    counter () {
      if (this.count) {
        const length = this.input.length || '0'
        return Number.isInteger(this.count)
          ? `${length} / ${this.count}`
          : length
      }
    },
    classes () {
      return {
        'q-field-responsive': !this.isVertical && !this.isHorizontal,
        'q-field-vertical': this.isVertical,
        'q-field-horizontal': this.isHorizontal,
        'q-field-floating': this.childHasLabel,
        'q-field-no-label': !this.label && !this.$slots.label,
        'q-field-with-error': this.hasError,
        'q-field-with-warning': this.hasWarning,
        'q-field-dark': this.isDark,
        'q-field-no-input': this.hasNoInput
      }
    },
    computedLabelWidth () {
      return parseInt(this.labelWidth, 10)
    },
    isVertical () {
      return this.orientation === 'vertical' || this.computedLabelWidth === 12
    },
    isHorizontal () {
      return this.orientation === 'horizontal'
    },
    labelClasses () {
      return this.isVertical
        ? `col-12`
        : (this.isHorizontal ? `col-${this.labelWidth}` : `col-xs-12 col-sm-${this.labelWidth}`)
    },
    inputClasses () {
      return this.isVertical
        ? `col-xs-12`
        : (this.isHorizontal ? 'col' : 'col-xs-12 col-sm')
    },
    iconProps () {
      const prop = { name: this.icon }
      if (this.iconColor && !this.hasError && !this.hasWarning) {
        prop.color = this.iconColor
      }
      return prop
    },
    insetHasLabel () {
      return ['label', 'full'].includes(this.inset)
    }
  },
  provide () {
    return {
      __field: this
    }
  },
  methods: {
    __registerInput (vm) {
      this.input = vm
    },
    __unregisterInput (vm) {
      if (!vm || vm === this.input) {
        this.input = {}
      }
    },
    __getBottomContent (h) {
      let label

      if (this.hasError && (label = this.$slots['error-label'] || this.errorLabel)) {
        return h('div', { staticClass: 'q-field-error col' }, label)
      }
      if (this.hasWarning && (label = this.$slots['warning-label'] || this.warningLabel)) {
        return h('div', { staticClass: 'q-field-warning col' }, label)
      }
      if ((label = this.$slots.helper || this.helper)) {
        return h('div', { staticClass: 'q-field-helper col' }, label)
      }
      return h('div', { staticClass: 'col' })
    },
    __hasBottom () {
      return (this.hasError && (this.$slots['error-label'] || this.errorLabel)) ||
        (this.hasWarning && (this.$slots['warning-label'] || this.warningLabel)) ||
        (this.$slots.helper || this.helper) ||
        this.count
    }
  },
  render (h) {
    const label = this.$slots.label || this.label

    return h('div', {
      staticClass: 'q-field row no-wrap items-start',
      'class': this.classes
    }, [
      this.icon
        ? h(QIcon, {
          props: this.iconProps,
          staticClass: 'q-field-icon q-field-margin'
        })
        : (this.insetIcon ? h('div', { staticClass: 'q-field-icon' }) : null),

      h('div', { staticClass: 'row col' }, [
        label || this.insetHasLabel
          ? h('div', {
            staticClass: 'q-field-label q-field-margin',
            'class': this.labelClasses
          }, [
            h('div', { staticClass: 'q-field-label-inner row items-center' }, [
              this.$slots.label || this.label
            ])
          ])
          : null,

        h('div', {
          staticClass: 'q-field-content',
          'class': this.inputClasses
        }, [
          this.$slots.default,
          this.__hasBottom()
            ? h('div', {
              staticClass: 'q-field-bottom row no-wrap'
            }, [
              this.__getBottomContent(h),
              this.counter
                ? h('div', { staticClass: 'q-field-counter col-auto' }, [ this.counter ])
                : null
            ])
            : null
        ])
      ])
    ])
  }
}
<|endoftext|>


<commit_before>import { Component, $$, DefaultDOMElement, domHelpers } from '../dom'
import { isFunction, platform, uuid } from '../util'
import renderMenu from './renderMenu'

export default class Popover extends Component {
  getInitialState () {
    return { content: null, requester: null, desiredPos: null }
  }

  didMount () {
    if (platform.inBrowser) {
      DefaultDOMElement.getBrowserWindow().on('mousedown', this._onGlobalMousedown, this, { capture: true })
      DefaultDOMElement.getBrowserWindow().on('mouseup', this._onGlobalMouseup, this, { capture: true })
    }
  }

  dispose () {
    if (platform.inBrowser) {
      DefaultDOMElement.getBrowserWindow().off(this)
    }
  }

  render () {
    const { content, requester, update } = this.state
    const el = $$('div', { class: 'sc-popover' })
    if (!update) el.addClass('sm-hidden')
    if (requester) {
      // Note: content can either be given as menu spec or as a render function
      if (isFunction(content)) {
        const renderContent = content
        el.append(
          renderContent()
        )
      // default: content is given as menu spec
      } else {
        el.append(
          renderMenu(requester, content)
        )
      }
    }
    return el
  }

  /**
   * Request to render a popover, given either a menu specification or a render function,
   * at the given position.
   *
   * To receive actions, the requesting component has to be provided.
   *
   * @param {object} params
   * @param {Function|object} params.content - a render function or a menu specification (see renderMenu)
   * @param {object} params.desiredPos
   * @param {number} params.desiredPos.x - the desired x screen coordinate
   * @param {number} params.desiredPos.y - the desired y screen coordinate
   * @param {Component} [params.requester] - the component that is requesting the content; this is used to dispatch actions
   */
  acquire (params, scrollable) {
    // console.log('Popover.acquire()', params)

    // Note: allowing to update the current popover using the requestId of a previous run
    if (params.update) {
      return this._update(params)
    }

    this._checkParams(params)
    this.setState(Object.assign({}, params, { requestId: uuid(), scrollable }))

    // ATTENTION: we have to postpone showing the content
    // as otherwise, e.g. the DOM selection is not yet updated,
    // which is needed for positioning
    setTimeout(() => {
      this._showPopover()
    }, 0)
    return this.state.requestId
  }

  _update (params) {
    if (params.requestId === this.state.requestId) {
      this.extendState(params)
      this._showPopover()
    } else {
      console.error('Invalid request id')
    }
  }

  release (requester) {
    if (this.state.requester === requester) {
      // console.log('Popover.release()', requester)
      this._hide()
    }
  }

  close () {
    // console.log('Popover.close()')
    this._hide()
  }

  isOpen () {
    return Boolean(this.state.requester)
  }

  _checkParams (params) {
    if (!params.content) throw new Error("'params.content' is required")
    if (!params.desiredPos) throw new Error("'desiredPos' is required")
  }

  _showPopover () {
    const { desiredPos, scrollable } = this.state
    const el = this.getElement()
    const bounds = this._getBounds()
    // console.log('bounds', bounds, 'desiredPos', desiredPos)

    // TODO: we should do some positioning here to stay within the screen/container bounds
    const menuWidth = el.htmlProp('offsetWidth')
    // By default, context menu are aligned left bottom to the desired coordinate
    let leftPos = bounds.x + desiredPos.x - menuWidth / 2
    // Must not exceed left bound
    leftPos = Math.max(leftPos, 0)
    // Must not exceed right bound
    const maxLeftPos = bounds.right - menuWidth
    leftPos = Math.min(leftPos, maxLeftPos)
    const topPos = desiredPos.y - bounds.y
    const maxHeight = bounds.bottom - topPos

    // store topPos and leftPos so that we can do repositioning on scroll
    if (scrollable) {
      this._topPos = topPos
      this._leftPos = leftPos
      this._initialScrollTop = scrollable.getProperty('scrollTop')
      this._initialScrollLeft = scrollable.getProperty('scrollLeft')
    }

    el.css({
      top: topPos,
      left: leftPos,
      'max-height': maxHeight
    })
    el.removeClass('sm-hidden')
    this._visible = true
  }

  _getBounds () {
    if (platform.inBrowser) {
      let containerEl
      if (this.props.getContainer) {
        containerEl = this.props.getContainer().getNativeElement()
      } else {
        containerEl = window.document
      }
      return containerEl.getBoundingClientRect()
    } else {
      return { left: 0, top: 0, bottom: 0, right: 0 }
    }
  }

  // overriding the default send() mechanism to be able to dispatch actions to the current requester
  _doesHandleAction () {
    return Boolean(this.state.requester)
  }

  _handleAction (action, args) {
    // console.log('FORWARDING action to requester', action, args)
    this.state.requester.send(action, ...args)
    // TODO: think if this is really what we want, i.e. hiding the menu whenever an action is emitted
    // console.log('Popover._handleAction(): hiding popover')
    this._hide()
  }

  _onGlobalMousedown () {
    // Note: we auto hide on every click
    // except those that occur inside the popover
    // and those that are 'white-listed' via params.allowClickInsideOf
    this._lastRequestId = this.state.requestId
  }

  _onGlobalMouseup (e) {
    if (!this._visible) return

    const targetEl = DefaultDOMElement.wrap(e.target)
    // do not auto-close if clicked inside the popover
    if (domHelpers.hasAncestor(targetEl, this.getElement())) {
      return
    }
    // Additionally, allow to skip auto-closing if clicked insied of a given element
    // e.g. QuerySelect has an input, that has a dropdown attached. Clicking into the input
    // should not auto-close the dropdown.
    if (this.state.allowClickInsideOf) {
      if (domHelpers.hasAncestor(targetEl, this.state.ignoreClicksInside)) {
        // console.log('Ignoring click because it is inside of a white-listed element')
        return
      }
    }
    const lastRequestId = this._lastRequestId
    const currentRequestId = this.state.requestId
    if (lastRequestId === currentRequestId) {
      this._hide()
    }
  }

  _hide () {
    // console.log('Hiding')
    const onClose = this.state.onClose
    if (onClose) onClose()
    this.setState(this.getInitialState())
    this._visible = false
  }

  /**
   * ATTENTION: this method has to be called by the owner whenever the scrollable
   * has been scrolled.
   */
  reposition (scrollable) {
    // Note: we use this for different kinds of popovers
    // typically, only context menus or alike require a 'relative' positioning
    if (this.state.position === 'relative') {
      const dtop = scrollable.getProperty('scrollTop') - this._initialScrollTop
      const dleft = scrollable.getProperty('scrollLeft') - this._initialScrollLeft
      this.el.css({
        top: this._topPos - dtop,
        left: this._leftPos + dleft
      })
    }
  }
}
<commit_msg>Bring back option to toggle popover.<commit_after>import { Component, $$, DefaultDOMElement, domHelpers } from '../dom'
import { isFunction, platform, uuid } from '../util'
import renderMenu from './renderMenu'

export default class Popover extends Component {
  getInitialState () {
    return { content: null, requester: null, desiredPos: null }
  }

  didMount () {
    if (platform.inBrowser) {
      DefaultDOMElement.getBrowserWindow().on('mousedown', this._onGlobalMousedown, this, { capture: true })
      DefaultDOMElement.getBrowserWindow().on('mouseup', this._onGlobalMouseup, this, { capture: true })
    }
  }

  dispose () {
    if (platform.inBrowser) {
      DefaultDOMElement.getBrowserWindow().off(this)
    }
  }

  render () {
    const { content, requester, update } = this.state
    const el = $$('div', { class: 'sc-popover' })
    if (!update) el.addClass('sm-hidden')
    if (requester) {
      // Note: content can either be given as menu spec or as a render function
      if (isFunction(content)) {
        const renderContent = content
        el.append(
          renderContent()
        )
      // default: content is given as menu spec
      } else {
        el.append(
          renderMenu(requester, content)
        )
      }
    }
    return el
  }

  /**
   * Request to render a popover, given either a menu specification or a render function,
   * at the given position.
   *
   * To receive actions, the requesting component has to be provided.
   *
   * @param {object} params
   * @param {Function|object} params.content - a render function or a menu specification (see renderMenu)
   * @param {object} params.desiredPos
   * @param {number} params.desiredPos.x - the desired x screen coordinate
   * @param {number} params.desiredPos.y - the desired y screen coordinate
   * @param {Component} [params.requester] - the component that is requesting the content; this is used to dispatch actions
   */
  acquire (params, scrollable) {
    // console.log('Popover.acquire()', params)

    // Note: allowing to update the current popover using the requestId of a previous run
    if (params.update) {
      return this._update(params)
    }

    // NOTE: this implements a toggle behavior. I.e. if the same requester
    // requests the popover for the same position, then we hide the popover
    const state = this.state
    if (params.toggle && state.requester === params.requester) {
      // console.log('Popover: toggling')
      this._hide()
      return false
    }

    this._checkParams(params)
    this.setState(Object.assign({}, params, { requestId: uuid(), scrollable }))

    // ATTENTION: we have to postpone showing the content
    // as otherwise, e.g. the DOM selection is not yet updated,
    // which is needed for positioning
    setTimeout(() => {
      this._showPopover()
    }, 0)
    return this.state.requestId
  }

  _update (params) {
    if (params.requestId === this.state.requestId) {
      this.extendState(params)
      this._showPopover()
    } else {
      console.error('Invalid request id')
    }
  }

  release (requester) {
    if (this.state.requester === requester) {
      // console.log('Popover.release()', requester)
      this._hide()
    }
  }

  close () {
    // console.log('Popover.close()')
    this._hide()
  }

  isOpen () {
    return Boolean(this.state.requester)
  }

  _checkParams (params) {
    if (!params.content) throw new Error("'params.content' is required")
    if (!params.desiredPos) throw new Error("'desiredPos' is required")
  }

  _showPopover () {
    const { desiredPos, scrollable } = this.state
    const el = this.getElement()
    const bounds = this._getBounds()
    // console.log('bounds', bounds, 'desiredPos', desiredPos)

    // TODO: we should do some positioning here to stay within the screen/container bounds
    const menuWidth = el.htmlProp('offsetWidth')
    // By default, context menu are aligned left bottom to the desired coordinate
    let leftPos = bounds.x + desiredPos.x - menuWidth / 2
    // Must not exceed left bound
    leftPos = Math.max(leftPos, 0)
    // Must not exceed right bound
    const maxLeftPos = bounds.right - menuWidth
    leftPos = Math.min(leftPos, maxLeftPos)
    const topPos = desiredPos.y - bounds.y
    const maxHeight = bounds.bottom - topPos

    // store topPos and leftPos so that we can do repositioning on scroll
    if (scrollable) {
      this._topPos = topPos
      this._leftPos = leftPos
      this._initialScrollTop = scrollable.getProperty('scrollTop')
      this._initialScrollLeft = scrollable.getProperty('scrollLeft')
    }

    el.css({
      top: topPos,
      left: leftPos,
      'max-height': maxHeight
    })
    el.removeClass('sm-hidden')
    this._visible = true
  }

  _getBounds () {
    if (platform.inBrowser) {
      let containerEl
      if (this.props.getContainer) {
        containerEl = this.props.getContainer().getNativeElement()
      } else {
        containerEl = window.document
      }
      return containerEl.getBoundingClientRect()
    } else {
      return { left: 0, top: 0, bottom: 0, right: 0 }
    }
  }

  // overriding the default send() mechanism to be able to dispatch actions to the current requester
  _doesHandleAction () {
    return Boolean(this.state.requester)
  }

  _handleAction (action, args) {
    // console.log('FORWARDING action to requester', action, args)
    this.state.requester.send(action, ...args)
    // TODO: think if this is really what we want, i.e. hiding the menu whenever an action is emitted
    // console.log('Popover._handleAction(): hiding popover')
    this._hide()
  }

  _onGlobalMousedown () {
    // Note: we auto hide on every click
    // except those that occur inside the popover
    // and those that are 'white-listed' via params.allowClickInsideOf
    this._lastRequestId = this.state.requestId
  }

  _onGlobalMouseup (e) {
    if (!this._visible) return

    const targetEl = DefaultDOMElement.wrap(e.target)
    // do not auto-close if clicked inside the popover
    if (domHelpers.hasAncestor(targetEl, this.getElement())) {
      return
    }
    // Additionally, allow to skip auto-closing if clicked insied of a given element
    // e.g. QuerySelect has an input, that has a dropdown attached. Clicking into the input
    // should not auto-close the dropdown.
    if (this.state.allowClickInsideOf) {
      if (domHelpers.hasAncestor(targetEl, this.state.ignoreClicksInside)) {
        // console.log('Ignoring click because it is inside of a white-listed element')
        return
      }
    }
    const lastRequestId = this._lastRequestId
    const currentRequestId = this.state.requestId
    if (lastRequestId === currentRequestId) {
      this._hide()
    }
  }

  _hide () {
    // console.log('Hiding')
    const onClose = this.state.onClose
    if (onClose) onClose()
    this.setState(this.getInitialState())
    this._visible = false
  }

  /**
   * ATTENTION: this method has to be called by the owner whenever the scrollable
   * has been scrolled.
   */
  reposition (scrollable) {
    // Note: we use this for different kinds of popovers
    // typically, only context menus or alike require a 'relative' positioning
    if (this.state.position === 'relative') {
      const dtop = scrollable.getProperty('scrollTop') - this._initialScrollTop
      const dleft = scrollable.getProperty('scrollLeft') - this._initialScrollLeft
      this.el.css({
        top: this._topPos - dtop,
        left: this._leftPos + dleft
      })
    }
  }
}
<|endoftext|>


<commit_before>module.exports = {
  '/index.html': {
    page: 'home'
  },
  '/contact.html': {
    page: 'contact'
  },
  options: {
    outputDir: '/docs',
    files: ['./client/router-compiled.js']
  }
}
<commit_msg>relative docs<commit_after>module.exports = {
  '/index.html': {
    page: 'home'
  },
  '/contact.html': {
    page: 'contact'
  },
  options: {
    outputDir: './docs',
    files: ['./client/router-compiled.js']
  }
}
<|endoftext|>


<commit_before>'use strict';
        const electron = require('electron');
        const app = electron.app;
        const BrowserWindow = electron.BrowserWindow;
        var window = null;
var request = require('request');
var Q = require('q');

var ELECTRON_READY = false;

var config = {
    reactionDataDir: './reaction-data',
    bucketDataDir: './bucket-data',
    logDir: './logs'
};

function checkRunning() {
    var deferred = Q.defer();

    request({
        url: "http://127.0.0.1:8080/service",
        method: 'GET'
    }, function (err, res, body) {
        if (err) {
            if (err.code == "ECONNREFUSED") {
                deferred.resolve(false);
            } else {
                deferred.reject(err);
            }
        } else {
            if (body == "I AM ALIVE!!!") {
                deferred.resolve(true);
            } else {
                deferred.reject("UNKNOWN SERVICE RUNNING ON 8080");
            }
        }
    });

    return deferred.promise;
}

function start() {
    checkRunning().then(function (running) {
        if (!running) {
            require('./lib/config').setConfig(config);
            require('./lib/config').appLogger.info("Starting application");
            require('./lib/webinterface').start();
            require('./lib/bucketCollector').start();

        }
        startUI();
    }).fail(function (reason) {
        throw(reason);
    });
}

function startUI() {
    if (ELECTRON_READY) {
        window = new BrowserWindow({
            width: 800,
            height: 600
        });
        window.loadURL('http://127.0.0.1:8080/');

        window.on('closed', function () {
            window = null;
        });
        app.on('window-all-closed', function () {
            if (process.platform != 'darwin') {
                app.quit();
            }
        });
    } else {
        app.on('ready', function () {
            ELECTRON_READY = true;
            startUI();
        });
    }
}

app.on('ready', function () {
    ELECTRON_READY = true;
});

start();<commit_msg>added command line args for electron app<commit_after>'use strict';
const electron = require('electron');
const app = electron.app;
const BrowserWindow = electron.BrowserWindow;
var window = null;
var request = require('request');
var options = require('commander');
var Q = require('q');

var ELECTRON_READY = false;

options.version(require('./package.json').version)
    .option('-d, --development', 'Starts the application in development mode, which enables additional logging')
    .option('-r, --reactionDataDir [path]', 'Set the path where the raction-data is saved')
    .option('-b, --bucketDataDir [path]', 'Set the path where the runtime-data is saved')
    .option('-L, --logDir [path]', 'Set the path where the logfiles are saved')
    .option('-s, --service', 'Starts the application without the UI')
    .parse(process.argv);

options.reactionDataDir = options.reactionDataDir || './reaction-data';
options.bucketDataDir = options.bucketDataDir || './bucket-data';
options.logDir = options.logDir || './logs';
options.service = options.service || false;

function checkRunning() {
    var deferred = Q.defer();

    request({
        url: "http://127.0.0.1:8080/service",
        method: 'GET'
    }, function(err, res, body) {
        if (err) {
            if (err.code == "ECONNREFUSED") {
                deferred.resolve(false);
            } else {
                deferred.reject(err);
            }
        } else {
            if (body == "I AM ALIVE!!!") {
                deferred.resolve(true);
            } else {
                deferred.reject("UNKNOWN SERVICE RUNNING ON 8080");
            }
        }
    });

    return deferred.promise;
}

function start() {
    checkRunning().then(function(running) {
        if (!running) {
            require('./lib/config').setConfig(options);
            require('./lib/config').appLogger.info("Starting application");
            require('./lib/webinterface').start();
            require('./lib/bucketCollector').start();
        }
        if (!options.service) {
            startUI();
        }
    }).fail(function(reason) {
        throw (reason);
    });
}

function startUI() {
    if (ELECTRON_READY) {
        window = new BrowserWindow({
            width: 800,
            height: 600
        });
        window.loadURL('http://127.0.0.1:8080/');

        window.on('closed', function() {
            window = null;
        });
        app.on('window-all-closed', function() {
            if (process.platform != 'darwin') {
                app.quit();
            }
        });
    } else {
        app.on('ready', function() {
            ELECTRON_READY = true;
            startUI();
        });
    }
}

app.on('ready', function() {
    ELECTRON_READY = true;
});

start();<|endoftext|>


<commit_before>define([], function() {
        return [{
            label: 'Terrain',
            layers: [{
                url: window.location.protocol + '//mapserv.utah.gov/ArcGIS/rest/services/BaseMaps/Terrain/MapServer',
                opacity: 1
            }]
        }, {
            label: 'Hybrid',
            layers: [{
                url: window.location.protocol + '//mapserv.utah.gov/ArcGIS/rest/services/BaseMaps/Hybrid/MapServer',
                opacity: 1
            }]
        }, {
            label: 'Streets',
            layers: [{
                url: window.location.protocol + '//mapserv.utah.gov/ArcGIS/rest/services/BaseMaps/Vector/MapServer',
                opacity: 1
            }]
        }, {
            label: 'Imagery',
            layers: [{
                url: window.location.protocol + '//mapserv.utah.gov/ArcGIS/rest/services/BaseMaps/Imagery/MapServer',
                opacity: 1
            }]
        }, {
            label: 'Topo',
            layers: [{
                url: window.location.protocol + '//mapserv.utah.gov/ArcGIS/rest/services/BaseMaps/Topo/MapServer',
                opacity: 1
            }]
        }, {
            label: 'Lite',
            layers: [{
                url: window.location.protocol + '//mapserv.utah.gov/ArcGIS/rest/services/BaseMaps/Lite/MapServer',
                opacity: 1
            }]
        }, {
            label: 'Hillshade',
            layers: [{
                url: window.location.protocol + '//mapserv.utah.gov/ArcGIS/rest/services/BaseMaps/Hillshade/MapServer',
                opacity: 1
            }]
        }];
    });<commit_msg>change to basemaps.utah.gov<commit_after>define([], function() {
        return [{
            label: 'Terrain',
            layers: [{
                url: window.location.protocol + '//basemaps.utah.gov/ArcGIS/rest/services/BaseMaps/Terrain/MapServer',
                opacity: 1
            }]
        }, {
            label: 'Hybrid',
            layers: [{
                url: window.location.protocol + '//basemaps.utah.gov/ArcGIS/rest/services/BaseMaps/Hybrid/MapServer',
                opacity: 1
            }]
        }, {
            label: 'Streets',
            layers: [{
                url: window.location.protocol + '//basemaps.utah.gov/ArcGIS/rest/services/BaseMaps/Vector/MapServer',
                opacity: 1
            }]
        }, {
            label: 'Imagery',
            layers: [{
                url: window.location.protocol + '//basemaps.utah.gov/ArcGIS/rest/services/BaseMaps/Imagery/MapServer',
                opacity: 1
            }]
        }, {
            label: 'Topo',
            layers: [{
                url: window.location.protocol + '//basemaps.utah.gov/ArcGIS/rest/services/BaseMaps/Topo/MapServer',
                opacity: 1
            }]
        }, {
            label: 'Lite',
            layers: [{
                url: window.location.protocol + '//basemaps.utah.gov/ArcGIS/rest/services/BaseMaps/Lite/MapServer',
                opacity: 1
            }]
        }, {
            label: 'Hillshade',
            layers: [{
                url: window.location.protocol + '//basemaps.utah.gov/ArcGIS/rest/services/BaseMaps/Hillshade/MapServer',
                opacity: 1
            }]
        }];
    });<|endoftext|>


<commit_before>
var PythonShell = require('python-shell');
var express = require('express');
var app = express();
/*
PythonShell.run('beep.py', function (err) {
    if (err) throw err;
    console.log('finished');
});*/

app.get('/', function (req, res) {
    res.send('Hello World!');
});

//var pyshell = new PythonShell('beep.py');

app.get('/on', function (req, res) {
    pyshell.send('ON');
});

app.get('/off', function (req, res) {
    pyshell.send('OFF');
});

app.listen(3000, function () {
    console.log('Example app listening on port 3000!');
});

PythonShell.run('beep.py', function (err) {
    if (err) throw err;
    console.log('finished');
});

/*
var express = require('express');
var app = express();
var bodyParser = require('body-parser');
var server = require('http').Server(app);
var io = require('socket.io')(server);
var routes = require('./routes')(io);
var PythonShell = require('python-shell');

// initialize body-parser
// bodyparser parses the body from a HTTP POST to a JSON object
//app.use(bodyParser.urlencoded({extended: true}));
//app.use(bodyParser.json());

var port = process.env.PORT || 1337;

var router = express.Router();

// middleware for all requests
// if there is a new request, it has to pass this middleware
// POST to /api/message -> this function -> router.route(/message)
// GET to /api/message -> this function -> router.route(/message)
// just for i dont know ... logging?
router.use(function (req, res, next) {
    // ToDo: Logging.

    console.log('new request.');
    next(); // dont stop, go to the next route
})

// initialize root route (0.0.0.0:1337/api)
router.get('/', routes.index);

// initialize message route (0.0.0.0:1337/api/message)
router.route('/beepon')
    .post(routes.on);

router.route('/beepoff')
    .post(routes.off);

// define that /api is the root route
app.use('/api', router);

// start server
server.listen(port);

console.log('texta api started on port ' + port);
    */<commit_msg>beep?<commit_after>
var PythonShell = require('python-shell');
var express = require('express');
var app = express();
/*
PythonShell.run('beep.py', function (err) {
    if (err) throw err;
    console.log('finished');
});*/

var pyshell = new PythonShell('beep.py');

app.get('/', function (req, res) {
    res.send('Hello World!');
});

app.get('/on', function (req, res) {
    pyshell.send('ON');
});

app.get('/off', function (req, res) {
    pyshell.send('OFF');
});

app.listen(3000, function () {
    console.log('Example app listening on port 3000!');
});

PythonShell.run('beep.py', function (err) {
    if (err) throw err;
    console.log('finished');
});

/*
var express = require('express');
var app = express();
var bodyParser = require('body-parser');
var server = require('http').Server(app);
var io = require('socket.io')(server);
var routes = require('./routes')(io);
var PythonShell = require('python-shell');

// initialize body-parser
// bodyparser parses the body from a HTTP POST to a JSON object
//app.use(bodyParser.urlencoded({extended: true}));
//app.use(bodyParser.json());

var port = process.env.PORT || 1337;

var router = express.Router();

// middleware for all requests
// if there is a new request, it has to pass this middleware
// POST to /api/message -> this function -> router.route(/message)
// GET to /api/message -> this function -> router.route(/message)
// just for i dont know ... logging?
router.use(function (req, res, next) {
    // ToDo: Logging.

    console.log('new request.');
    next(); // dont stop, go to the next route
})

// initialize root route (0.0.0.0:1337/api)
router.get('/', routes.index);

// initialize message route (0.0.0.0:1337/api/message)
router.route('/beepon')
    .post(routes.on);

router.route('/beepoff')
    .post(routes.off);

// define that /api is the root route
app.use('/api', router);

// start server
server.listen(port);

console.log('texta api started on port ' + port);
    */<|endoftext|>


<commit_before>import React from 'react';
import styled from 'styled-components';
import enhanceWithClickOutside from 'react-click-outside';
import Icon from 'ui/components/common/Icon';

const OptionsContainer = styled.div`
  border: ${(props) => props.theme.lightBorder};
  position: absolute;
  ${(props) => `${props.position}: 0`};
  width: 100px;
  z-index: 2147483646;
  background: #fff;
  max-height: 600px;
  overflow-y: auto;
  ${(props) => `${props.anchorPoint}: 100%;`}
`;

const Option = styled.div`
  display: flex;
  padding: ${(props) => props.withPadding ? '5px 5px 5px 32px' : '5px'};
  user-select: none;
  justify-content: center;
  ${(props) => props.align === 'center' ? 'justify-content: center' : 'justify-content: flex-start'};
  ${(props) => props.align === 'center' ? 'align-items: center' : 'align-items: flex-start'};
  ${(props) => props.isPadded ? 'padding-left: 25px;' : ''}

  &:hover {
    cursor: pointer;
    background-color: #c9dcf4;
  }
`;

const Container = styled.span`
  position: relative;
  cursor: pointer;
  height: 100%;
  display: flex;
  align-items: center;
`;

const StyledIcon = styled(Icon)`
  ${(props) => props.active ? 'background-color: #427dd2;' : ''}
  padding: 3px;
`;

const SelectedIcon = styled(Icon)`
  padding: 0 3px;
`;

export class IconDropdown extends React.Component {

  state = {
    isOpen: false
  };

  toggle = () => {
    this.setState({ isOpen: !this.state.isOpen });
  };

  handleClickOutside = () => {
    this.setState({ isOpen: false });
  };

  selectValue = (option) => {
    const { onChange } = this.props;

    return () => {
      onChange && onChange(option.value);
    }
  };

  renderOptions = () => {
    if (!this.state.isOpen) {
      return false;
    }

    const options = this.props.options.map((option) => {
      return (
      <Option
        key={ option.value }
        onClick={ this.selectValue(option) }
        withPadding={ this.props.valueIcon }
        align={ this.props.align }
        isPadded={ this.props.value && this.props.value !== option.value}>
        { this.props.value === option.value && <SelectedIcon src="check"/> }
        { option.label }
      </Option>
    );
    });

    return (
      <OptionsContainer
        innerRef={ this.autoScrollToEnd }
        anchorPoint={ this.props.anchorPoint }
        position={ this.props.position }>
        { options }
      </OptionsContainer>
    )
  };

  render() {
    return (
      <Container isOpen={ this.state.isOpen } onClick={ this.toggle }>
        { this.renderOptions() }
        <StyledIcon src={ this.props.icon } active={ this.state.isOpen }/>
      </Container>
    );
  }

}

IconDropdown.defaultProps = {
  anchorPoint: 'top',
  position: 'left',
  align: 'center'
};

export default enhanceWithClickOutside(IconDropdown);
<commit_msg>Show icon as selected when the value is selected<commit_after>import React from 'react';
import styled from 'styled-components';
import enhanceWithClickOutside from 'react-click-outside';
import Icon from 'ui/components/common/Icon';

const OptionsContainer = styled.div`
  border: ${(props) => props.theme.lightBorder};
  position: absolute;
  ${(props) => `${props.position}: 0`};
  width: 100px;
  z-index: 2147483646;
  background: #fff;
  max-height: 600px;
  overflow-y: auto;
  ${(props) => `${props.anchorPoint}: 100%;`}
`;

const Option = styled.div`
  display: flex;
  padding: ${(props) => props.withPadding ? '5px 5px 5px 32px' : '5px'};
  user-select: none;
  justify-content: center;
  ${(props) => props.align === 'center' ? 'justify-content: center' : 'justify-content: flex-start'};
  ${(props) => props.align === 'center' ? 'align-items: center' : 'align-items: flex-start'};
  ${(props) => props.isPadded ? 'padding-left: 25px;' : ''}

  &:hover {
    cursor: pointer;
    background-color: #c9dcf4;
  }
`;

const Container = styled.span`
  position: relative;
  cursor: pointer;
  height: 100%;
  display: flex;
  align-items: center;
`;

const StyledIcon = styled(Icon)`
  ${(props) => props.active ? 'background-color: #427dd2;' : ''}
  padding: 3px;
`;

const SelectedIcon = styled(Icon)`
  padding: 0 3px;
`;

export class IconDropdown extends React.Component {

  state = {
    isOpen: false
  };

  toggle = () => {
    this.setState({ isOpen: !this.state.isOpen });
  };

  handleClickOutside = () => {
    this.setState({ isOpen: false });
  };

  selectValue = (option) => {
    const { onChange } = this.props;

    return () => {
      onChange && onChange(option.value);
    }
  };

  renderOptions = () => {
    if (!this.state.isOpen) {
      return false;
    }

    const options = this.props.options.map((option) => {
      return (
      <Option
        key={ option.value }
        onClick={ this.selectValue(option) }
        withPadding={ this.props.valueIcon }
        align={ this.props.align }
        isPadded={ this.props.value && this.props.value !== option.value}>
        { this.props.value === option.value && <SelectedIcon src="check"/> }
        { option.label }
      </Option>
    );
    });

    return (
      <OptionsContainer
        innerRef={ this.autoScrollToEnd }
        anchorPoint={ this.props.anchorPoint }
        position={ this.props.position }>
        { options }
      </OptionsContainer>
    )
  };

  render() {
    const optionSelected = this.props.options.slice(1).some((option) => option.value === this.props.value);

    return (
      <Container isOpen={ this.state.isOpen } onClick={ this.toggle }>
        { this.renderOptions() }
        <StyledIcon src={ this.props.icon } active={ this.state.isOpen || optionSelected }/>
      </Container>
    );
  }

}

IconDropdown.defaultProps = {
  anchorPoint: 'top',
  position: 'left',
  align: 'center'
};

export default enhanceWithClickOutside(IconDropdown);
<|endoftext|>


<commit_before>import React from 'react';
import PropTypes from 'prop-types';
import debounce from 'debounce';
import { canUseDOM } from 'exenv';
import Portal from './Portal';

const listeners = {};

function fireListeners() {
  Object.keys(listeners).forEach(key => listeners[key]());
}

function getPageOffset() {
  return {
    x: (window.pageXOffset !== undefined)
      ? window.pageXOffset
      : (document.documentElement || document.body.parentNode || document.body).scrollLeft,
    y: (window.pageYOffset !== undefined)
      ? window.pageYOffset
      : (document.documentElement || document.body.parentNode || document.body).scrollTop,
  }
}

if (canUseDOM) {
  document.body.addEventListener('mousewheel', debounce(fireListeners, 100, true));
  window.addEventListener('resize', debounce(fireListeners, 50, true));
}

let listenerIdCounter = 0;
function subscribe(fn) {
  listenerIdCounter += 1;
  const id = listenerIdCounter;
  listeners[id] = fn;
  return () => delete listeners[id];
}

export default class RelativePortal extends React.Component {
  static propTypes = {
    right: PropTypes.number,
    left: PropTypes.number,
    fullWidth: PropTypes.bool,
    top: PropTypes.number,
    children: PropTypes.any,
    onOutClick: PropTypes.func,
    component: PropTypes.string.isRequired,
  };

  static defaultProps = {
    left: 0,
    top: 0,
    component: 'span',
  };

  state = {
    right: 0,
    left: 0,
    top: 0,
  };

  componentDidMount() {
    this.handleScroll = () => {
      if (this.element) {
        const rect = this.element.getBoundingClientRect();
        const pageOffset = getPageOffset();
        const top = pageOffset.y + rect.top;
        const right = window.innerWidth - rect.right - pageOffset.x;
        const left = pageOffset.x + rect.left;

        if (top !== this.state.top || left !== this.state.left || right !== this.state.right) {
          this.setState({ left, top, right });
        }
      }
    };
    this.unsubscribe = subscribe(this.handleScroll);
    this.handleScroll();
  }

  componentDidUpdate() {
    this.handleScroll();
  }

  componentWillUnmount() {
    this.unsubscribe();
  }

  render() {
    const { component: Comp, top, left, right, fullWidth, ...props } = this.props;

    const fromLeftOrRight = right !== undefined ?
      { right: this.state.right + right } :
      { left: this.state.left + left };

    const horizontalPosition = fullWidth ?
      { right: this.state.right + right, left: this.state.left + left } : fromLeftOrRight;

    return (
      <Comp
        ref={element => {
          this.element = element;
        }}
      >
        <Portal {...props}>
          <div
            style={{
              position: 'absolute',
              top: this.state.top + top,
              ...horizontalPosition,
            }}
          >
            {this.props.children}
          </div>
        </Portal>
      </Comp>
    );
  }
}
<commit_msg>defer initialization of dom event listener after document.body is loaded (#11)<commit_after>import React from 'react';
import PropTypes from 'prop-types';
import debounce from 'debounce';
import { canUseDOM } from 'exenv';
import Portal from './Portal';

const listeners = {};

function fireListeners() {
  Object.keys(listeners).forEach(key => listeners[key]());
}

function getPageOffset() {
  return {
    x: (window.pageXOffset !== undefined)
      ? window.pageXOffset
      : (document.documentElement || document.body.parentNode || document.body).scrollLeft,
    y: (window.pageYOffset !== undefined)
      ? window.pageYOffset
      : (document.documentElement || document.body.parentNode || document.body).scrollTop,
  }
}

function initDOMListener() {
  document.body.addEventListener('mousewheel', debounce(fireListeners, 100, true));
  window.addEventListener('resize', debounce(fireListeners, 50, true));
}

if (canUseDOM) {
  if (document.body) {
    initDOMListener();
  } else {
    document.addEventListener('DOMContentLoaded', initDOMListener);
  }
}

let listenerIdCounter = 0;
function subscribe(fn) {
  listenerIdCounter += 1;
  const id = listenerIdCounter;
  listeners[id] = fn;
  return () => delete listeners[id];
}

export default class RelativePortal extends React.Component {
  static propTypes = {
    right: PropTypes.number,
    left: PropTypes.number,
    fullWidth: PropTypes.bool,
    top: PropTypes.number,
    children: PropTypes.any,
    onOutClick: PropTypes.func,
    component: PropTypes.string.isRequired,
  };

  static defaultProps = {
    left: 0,
    top: 0,
    component: 'span',
  };

  state = {
    right: 0,
    left: 0,
    top: 0,
  };

  componentDidMount() {
    this.handleScroll = () => {
      if (this.element) {
        const rect = this.element.getBoundingClientRect();
        const pageOffset = getPageOffset();
        const top = pageOffset.y + rect.top;
        const right = window.innerWidth - rect.right - pageOffset.x;
        const left = pageOffset.x + rect.left;

        if (top !== this.state.top || left !== this.state.left || right !== this.state.right) {
          this.setState({ left, top, right });
        }
      }
    };
    this.unsubscribe = subscribe(this.handleScroll);
    this.handleScroll();
  }

  componentDidUpdate() {
    this.handleScroll();
  }

  componentWillUnmount() {
    this.unsubscribe();
  }

  render() {
    const { component: Comp, top, left, right, fullWidth, ...props } = this.props;

    const fromLeftOrRight = right !== undefined ?
      { right: this.state.right + right } :
      { left: this.state.left + left };

    const horizontalPosition = fullWidth ?
      { right: this.state.right + right, left: this.state.left + left } : fromLeftOrRight;

    return (
      <Comp
        ref={element => {
          this.element = element;
        }}
      >
        <Portal {...props}>
          <div
            style={{
              position: 'absolute',
              top: this.state.top + top,
              ...horizontalPosition,
            }}
          >
            {this.props.children}
          </div>
        </Portal>
      </Comp>
    );
  }
}
<|endoftext|>


<commit_before>// function completer.
//
// completer should be a class that take an cell instance
var IPython = (function (IPython) {
    // that will prevent us from misspelling
    "use strict";

    // easyier key mapping
    var key = IPython.utils.keycodes;

    // what is the common start of all completions

    function shared_start(B) {
        if (B.length == 1) {
            return B[0];
        }
        var A = new Array();
        for (var i = 0; i < B.length; i++) {
            A.push(B[i].str);
        }
        if (A.length > 1) {
            var tem1, tem2, s;
            A = A.slice(0).sort();
            tem1 = A[0];
            s = tem1.length;
            tem2 = A.pop();
            while (s && tem2.indexOf(tem1) == -1) {
                tem1 = tem1.substring(0, --s);
            }
            if (tem1 == "" || tem2.indexOf(tem1) != 0) {
                return null;
            }
            return {
                str: tem1,
                type: "computed",
                from: B[0].from,
                to: B[0].to
            };
        }
        return null;
    }

    var Completer = function (cell) {
            this.editor = cell.code_mirror;
            var that = this;
            $([IPython.events]).on('status_busy.Kernel', function () {
                that.skip_kernel_completion = true;
            });
            $([IPython.events]).on('status_idle.Kernel', function () {
                that.skip_kernel_completion = false;
            });

        };

    Completer.prototype.startCompletion = function () {
        // call for a 'first' completion, that will set the editor and do some
        // special behaviour like autopicking if only one completion availlable
        //
        if (this.editor.somethingSelected()) return;
        this.done = false;
        // use to get focus back on opera
        this.carry_on_completion(true);
    };

    Completer.prototype.carry_on_completion = function (ff) {
        // Pass true as parameter if you want the commpleter to autopick when
        // only one completion. This function is automatically reinvoked at
        // each keystroke with ff = false
        var cur = this.editor.getCursor();
        var line = this.editor.getLine(cur.line);
        var pre_cursor = this.editor.getRange({
            line: cur.line,
            ch: cur.ch - 1
        }, cur);

        // we need to check that we are still on a word boundary
        // because while typing the completer is still reinvoking itself
        if (!/[0-9a-z._]/i.test(pre_cursor)) {
            this.close();
            return;
        }

        this.autopick = false;
        if (ff != 'undefined' && ff == true) {
            this.autopick = true;
        }

        // We want a single cursor position.
        if (this.editor.somethingSelected()) return;

        // one kernel completion came back, finish_completing will be called with the results
        // we fork here and directly call finish completing if kernel is busy
        if (this.skip_kernel_completion == true) {
            this.finish_completing({
                'matches': [],
                matched_text: ""
            })
        } else {
            var callbacks = {
                'complete_reply': $.proxy(this.finish_completing, this)
            };
            IPython.notebook.kernel.complete(line, cur.ch, callbacks);
        }
    };

    Completer.prototype.finish_completing = function (content) {
        // let's build a function that wrap all that stuff into what is needed
        // for the new completer:
        var matched_text = content.matched_text;
        var matches = content.matches;

        var cur = this.editor.getCursor();
        var results = CodeMirror.contextHint(this.editor);
        var filterd_results = Array();
        console.log('results',results)
        //remove results from context completion
        //that are already in kernel completion
        for(var elm in results)
        {
            if(matches.indexOf(results[elm]['str']) == -1)
            {
                //filterd_results.push(elm);
                console.log('adding',results[elm])
            }
            else
            {
                console.log('skipping ',results[elm]);
            }
        }

        // append the introspection result, in order, at at the beginning of
        // the table and compute the replacement range from current cursor
        // positon and matched_text length.
        for (var i = matches.length - 1; i >= 0; --i) {
            filterd_results.unshift({
                str: matches[i],
                type: "introspection",
                from: {
                    line: cur.line,
                    ch: cur.ch - matched_text.length
                },
                to: {
                    line: cur.line,
                    ch: cur.ch
                }
            });
        }

        // one the 2 sources results have been merge, deal with it
        this.raw_result = filterd_results;

        // if empty result return
        if (!this.raw_result || !this.raw_result.length) return;

        // When there is only one completion, use it directly.
        if (this.autopick == true && this.raw_result.length == 1) {
            this.insert(this.raw_result[0]);
            return;
        }

        if (this.raw_result.length == 1) {
            // test if first and only completion totally matches
            // what is typed, in this case dismiss
            var str = this.raw_result[0].str;
            var pre_cursor = this.editor.getRange({
                line: cur.line,
                ch: cur.ch - str.length
            }, cur);
            if (pre_cursor == str) {
                this.close();
                return;
            }
        }

        this.complete = $('<div/>').addClass('completions');
        this.complete.attr('id', 'complete');

        this.sel = $('<select/>').attr('multiple', 'true').attr('size', Math.min(10, this.raw_result.length));
        var pos = this.editor.cursorCoords();

        // TODO: I propose to remove enough horizontal pixel
        // to align the text later
        this.complete.css('left', pos.x + 'px');
        this.complete.css('top', pos.yBot + 'px');
        this.complete.append(this.sel);

        $('body').append(this.complete);
        //build the container
        var that = this;
        this.sel.dblclick(function () {
            that.pick();
        });
        this.sel.blur(this.close);
        this.sel.keydown(function (event) {
            that.keydown(event);
        });

        this.build_gui_list(this.raw_result);

        this.sel.focus();
        // Opera sometimes ignores focusing a freshly created node
        if (window.opera) setTimeout(function () {
            if (!this.done) this.sel.focus();
        }, 100);
        return true;
    }

    Completer.prototype.insert = function (completion) {
        this.editor.replaceRange(completion.str, completion.from, completion.to);
    }

    Completer.prototype.build_gui_list = function (completions) {
        // Need to clear the all list
        for (var i = 0; i < completions.length; ++i) {
            var opt = $('<option/>').text(completions[i].str).addClass(completions[i].type);
            this.sel.append(opt);
        }
        this.sel.children().first().attr('selected', 'true');
    }

    Completer.prototype.close = function () {
        if (this.done) return;
        this.done = true;
        $('.completions').remove();
    }

    Completer.prototype.pick = function () {
        this.insert(this.raw_result[this.sel[0].selectedIndex]);
        this.close();
        var that = this;
        setTimeout(function () {
            that.editor.focus();
        }, 50);
    }

    Completer.prototype.keydown = function (event) {
        var code = event.keyCode;
        var that = this;
        // Enter
        if (code == key.ENTER) {
            CodeMirror.e_stop(event);
            this.pick();
        }
        // Escape or backspace
        else if (code == key.ESC) {
            CodeMirror.e_stop(event);
            this.close();
            this.editor.focus();
        } else if (code == key.SPACE || code == key.BACKSPACE) {
            this.close();
            this.editor.focus();
        } else if (code == key.TAB) {
            //all the fastforwarding operation,
            //Check that shared start is not null which can append with prefixed completion
            // like %pylab , pylab have no shred start, and ff will result in py<tab><tab>
            // to erase py
            var sh = shared_start(this.raw_result);
            if (sh) {
                this.insert(sh);
            }
            this.close();
            CodeMirror.e_stop(event);
            this.editor.focus();
            //reinvoke self
            setTimeout(function () {
                that.carry_on_completion();
            }, 50);
        } else if (code == key.UPARROW || code == key.DOWNARROW) {
            // need to do that to be able to move the arrow
            // when on the first or last line ofo a code cell
            event.stopPropagation();
        } else if (code != key.UPARROW && code != key.DOWNARROW) {
            this.close();
            this.editor.focus();
            //we give focus to the editor immediately and call sell in 50 ms
            setTimeout(function () {
                that.carry_on_completion();
            }, 50);
        }
    }

    IPython.Completer = Completer;

    return IPython;
}(IPython));
<commit_msg>take care of token starting by '.' (dot)<commit_after>// function completer.
//
// completer should be a class that take an cell instance
var IPython = (function (IPython) {
    // that will prevent us from misspelling
    "use strict";

    // easyier key mapping
    var key = IPython.utils.keycodes;

    // what is the common start of all completions
    function _existing_completion(item, completion_array)
    {
        if(item.substr(0,1) == '.')
        {
            for( var c in completion_array )
            {
                if(completion_array[c].substr(-item.length) == item)
                { return true; }
            }
            return false;
        }
        else
        {return completion_array.indexOf(item) != -1}
    }

    function shared_start(B) {
        if (B.length == 1) {
            return B[0];
        }
        var A = new Array();
        for (var i = 0; i < B.length; i++) {
            A.push(B[i].str);
        }
        if (A.length > 1) {
            var tem1, tem2, s;
            A = A.slice(0).sort();
            tem1 = A[0];
            s = tem1.length;
            tem2 = A.pop();
            while (s && tem2.indexOf(tem1) == -1) {
                tem1 = tem1.substring(0, --s);
            }
            if (tem1 == "" || tem2.indexOf(tem1) != 0) {
                return null;
            }
            return {
                str: tem1,
                type: "computed",
                from: B[0].from,
                to: B[0].to
            };
        }
        return null;
    }

    var Completer = function (cell) {
            this.editor = cell.code_mirror;
            var that = this;
            $([IPython.events]).on('status_busy.Kernel', function () {
                that.skip_kernel_completion = true;
            });
            $([IPython.events]).on('status_idle.Kernel', function () {
                that.skip_kernel_completion = false;
            });

        };

    Completer.prototype.startCompletion = function () {
        // call for a 'first' completion, that will set the editor and do some
        // special behaviour like autopicking if only one completion availlable
        //
        if (this.editor.somethingSelected()) return;
        this.done = false;
        // use to get focus back on opera
        this.carry_on_completion(true);
    };

    Completer.prototype.carry_on_completion = function (ff) {
        // Pass true as parameter if you want the commpleter to autopick when
        // only one completion. This function is automatically reinvoked at
        // each keystroke with ff = false
        var cur = this.editor.getCursor();
        var line = this.editor.getLine(cur.line);
        var pre_cursor = this.editor.getRange({
            line: cur.line,
            ch: cur.ch - 1
        }, cur);

        // we need to check that we are still on a word boundary
        // because while typing the completer is still reinvoking itself
        if (!/[0-9a-z._]/i.test(pre_cursor)) {
            this.close();
            return;
        }

        this.autopick = false;
        if (ff != 'undefined' && ff == true) {
            this.autopick = true;
        }

        // We want a single cursor position.
        if (this.editor.somethingSelected()) return;

        // one kernel completion came back, finish_completing will be called with the results
        // we fork here and directly call finish completing if kernel is busy
        if (this.skip_kernel_completion == true) {
            this.finish_completing({
                'matches': [],
                matched_text: ""
            })
        } else {
            var callbacks = {
                'complete_reply': $.proxy(this.finish_completing, this)
            };
            IPython.notebook.kernel.complete(line, cur.ch, callbacks);
        }
    };

    Completer.prototype.finish_completing = function (content) {
        // let's build a function that wrap all that stuff into what is needed
        // for the new completer:
        var matched_text = content.matched_text;
        var matches = content.matches;

        var cur = this.editor.getCursor();
        var results = CodeMirror.contextHint(this.editor);
        var filterd_results = Array();
        //remove results from context completion
        //that are already in kernel completion
        for(var elm in results)
        {
            if(_existing_completion(results[elm]['str'],matches) == false)
            {
                filterd_results.push(results[elm]);
            }
        }

        // append the introspection result, in order, at at the beginning of
        // the table and compute the replacement range from current cursor
        // positon and matched_text length.
        for (var i = matches.length - 1; i >= 0; --i) {
            filterd_results.unshift({
                str: matches[i],
                type: "introspection",
                from: {
                    line: cur.line,
                    ch: cur.ch - matched_text.length
                },
                to: {
                    line: cur.line,
                    ch: cur.ch
                }
            });
        }

        // one the 2 sources results have been merge, deal with it
        this.raw_result = filterd_results;

        // if empty result return
        if (!this.raw_result || !this.raw_result.length) return;

        // When there is only one completion, use it directly.
        if (this.autopick == true && this.raw_result.length == 1) {
            this.insert(this.raw_result[0]);
            return;
        }

        if (this.raw_result.length == 1) {
            // test if first and only completion totally matches
            // what is typed, in this case dismiss
            var str = this.raw_result[0].str;
            var pre_cursor = this.editor.getRange({
                line: cur.line,
                ch: cur.ch - str.length
            }, cur);
            if (pre_cursor == str) {
                this.close();
                return;
            }
        }

        this.complete = $('<div/>').addClass('completions');
        this.complete.attr('id', 'complete');

        this.sel = $('<select/>').attr('multiple', 'true').attr('size', Math.min(10, this.raw_result.length));
        var pos = this.editor.cursorCoords();

        // TODO: I propose to remove enough horizontal pixel
        // to align the text later
        this.complete.css('left', pos.x + 'px');
        this.complete.css('top', pos.yBot + 'px');
        this.complete.append(this.sel);

        $('body').append(this.complete);
        //build the container
        var that = this;
        this.sel.dblclick(function () {
            that.pick();
        });
        this.sel.blur(this.close);
        this.sel.keydown(function (event) {
            that.keydown(event);
        });

        this.build_gui_list(this.raw_result);

        this.sel.focus();
        // Opera sometimes ignores focusing a freshly created node
        if (window.opera) setTimeout(function () {
            if (!this.done) this.sel.focus();
        }, 100);
        return true;
    }

    Completer.prototype.insert = function (completion) {
        this.editor.replaceRange(completion.str, completion.from, completion.to);
    }

    Completer.prototype.build_gui_list = function (completions) {
        // Need to clear the all list
        for (var i = 0; i < completions.length; ++i) {
            var opt = $('<option/>').text(completions[i].str).addClass(completions[i].type);
            this.sel.append(opt);
        }
        this.sel.children().first().attr('selected', 'true');
    }

    Completer.prototype.close = function () {
        if (this.done) return;
        this.done = true;
        $('.completions').remove();
    }

    Completer.prototype.pick = function () {
        this.insert(this.raw_result[this.sel[0].selectedIndex]);
        this.close();
        var that = this;
        setTimeout(function () {
            that.editor.focus();
        }, 50);
    }

    Completer.prototype.keydown = function (event) {
        var code = event.keyCode;
        var that = this;
        // Enter
        if (code == key.ENTER) {
            CodeMirror.e_stop(event);
            this.pick();
        }
        // Escape or backspace
        else if (code == key.ESC) {
            CodeMirror.e_stop(event);
            this.close();
            this.editor.focus();
        } else if (code == key.SPACE || code == key.BACKSPACE) {
            this.close();
            this.editor.focus();
        } else if (code == key.TAB) {
            //all the fastforwarding operation,
            //Check that shared start is not null which can append with prefixed completion
            // like %pylab , pylab have no shred start, and ff will result in py<tab><tab>
            // to erase py
            var sh = shared_start(this.raw_result);
            if (sh) {
                this.insert(sh);
            }
            this.close();
            CodeMirror.e_stop(event);
            this.editor.focus();
            //reinvoke self
            setTimeout(function () {
                that.carry_on_completion();
            }, 50);
        } else if (code == key.UPARROW || code == key.DOWNARROW) {
            // need to do that to be able to move the arrow
            // when on the first or last line ofo a code cell
            event.stopPropagation();
        } else if (code != key.UPARROW && code != key.DOWNARROW) {
            this.close();
            this.editor.focus();
            //we give focus to the editor immediately and call sell in 50 ms
            setTimeout(function () {
                that.carry_on_completion();
            }, 50);
        }
    }

    IPython.Completer = Completer;

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


<commit_before>/**
 * Jay Extend 1.0.1
 * https://github.com/parasyte/jay-extend
 * A super fast prototypal inheritance microlib for the modern web.
 * Copyright (c) 2014-2015 Jay Oster
 *  MIT License
 */

/**
 * Extend a class prototype with the provided mixin descriptors.
 * Designed as a faster replacement for John Resig's Simple Inheritance.
 * @name extend
 * @memberOf Jay
 * @function
 * @param {Object[]} mixins... Each mixin is a dictionary of functions, or a
 * previously extended class whose methods will be applied to the target class
 * prototype.
 * @return {Object}
 * @example
 * var Person = Jay.extend({
 *     "init" : function (isDancing) {
 *         this.dancing = isDancing;
 *     },
 *     "dance" : function () {
 *         return this.dancing;
 *     }
 * });
 *
 * var Ninja = Person.extend({
 *     "init" : function () {
 *         // Call the super constructor, passing a single argument
 *         this._super(Person, "init", [false]);
 *     },
 *     "dance" : function () {
 *         // Call the overridden dance() method
 *         return this._super(Person, "dance");
 *     },
 *     "swingSword" : function () {
 *         return true;
 *     }
 * });
 *
 * var Pirate = Person.extend(Ninja, {
 *     "init" : function () {
 *         // Call the super constructor, passing a single argument
 *         this._super(Person, "init", [true]);
 *     }
 * });
 *
 * var p = new Person(true);
 * console.log(p.dance()); // => true
 *
 * var n = new Ninja();
 * console.log(n.dance()); // => false
 * console.log(n.swingSword()); // => true
 *
 * var r = new Pirate();
 * console.log(r.dance()); // => true
 * console.log(r.swingSword()); // => true
 *
 * console.log(
 *     p instanceof Person &&
 *     n instanceof Ninja &&
 *     n instanceof Person &&
 *     r instanceof Pirate &&
 *     r instanceof Person
 * ); // => true
 *
 * console.log(r instanceof Ninja); // => false
 */
(function () {
    function extend() {
        var methods = {};
        var mixins = new Array(arguments.length);
        for (var i = 0; i < arguments.length; i++) {
            mixins.push(arguments[i]);
        }

        /**
         * The class constructor which calls the user `init` constructor.
         * @ignore
         */
        function Class() {
            // Call the user constructor
            this.init.apply(this, arguments);
            return this;
        }

        // Apply superClass
        Class.prototype = Object.create(this.prototype);

        // Apply all mixin methods to the class prototype
        mixins.forEach(function (mixin) {
            apply_methods(Class, methods, mixin.__methods__ || mixin);
        });

        // Verify constructor exists
        if (!("init" in Class.prototype)) {
            throw new TypeError(
                "extend: Class is missing a constructor named `init`"
            );
        }

        // Apply syntactic sugar for accessing methods on super classes
        Object.defineProperty(Class.prototype, "_super", {
            "value" : _super
        });

        // Create a hidden property on the class itself
        // List of methods, used for applying classes as mixins
        Object.defineProperty(Class, "__methods__", {
            "value" : methods
        });

        // Make this class extendable
        Class.extend = extend;

        return Class;
    }

    /**
     * Apply methods to the class prototype.
     * @ignore
     */
    function apply_methods(Class, methods, descriptor) {
        Object.keys(descriptor).forEach(function (method) {
            methods[method] = descriptor[method];

            if (typeof(descriptor[method]) !== "function") {
                throw new TypeError(
                    "extend: Method `" + method + "` is not a function"
                );
            }

            Object.defineProperty(Class.prototype, method, {
                "configurable" : true,
                "value" : descriptor[method]
            });
        });
    }

    /**
     * Special method that acts as a proxy to the super class.
     * @name _super
     * @ignore
     */
    function _super(superClass, method, args) {
        return superClass.prototype[method].apply(this, args);
    }

    /**
     * The base class from which all jay-extend classes inherit.
     * @ignore
     */
    var Jay = function () {
        Object.apply(this, arguments);
    };
    Jay.prototype = Object.create(Object.prototype);
    Jay.prototype.constructor = Jay;

    Object.defineProperty(Jay, "extend", {
        "value" : extend
    });

    /**
     * Export the extend method.
     * @ignore
     */
    if (typeof(window) !== "undefined") {
        window.Jay = Jay;
    }
    /*
     TODO : module support in melonJS
    else {
        module.exports = Jay;
    }
    */
})();
<commit_msg>vanilla Jay-extend source code<commit_after>/**
 * Jay Extend 1.0.1
 * https://github.com/parasyte/jay-extend
 * A super fast prototypal inheritance microlib for the modern web.
 * Copyright (c) 2014-2015 Jay Oster
 *  MIT License
 */

/* eslint-disable no-undef */
/**
 * Extend a class prototype with the provided mixin descriptors.
 * Designed as a faster replacement for John Resig's Simple Inheritance.
 * @name extend
 * @memberOf Jay
 * @function
 * @param {Object[]} mixins... Each mixin is a dictionary of functions, or a
 * previously extended class whose methods will be applied to the target class
 * prototype.
 * @return {Object}
 * @example
 * var Person = Jay.extend({
 *     "init" : function (isDancing) {
 *         this.dancing = isDancing;
 *     },
 *     "dance" : function () {
 *         return this.dancing;
 *     }
 * });
 *
 * var Ninja = Person.extend({
 *     "init" : function () {
 *         // Call the super constructor, passing a single argument
 *         this._super(Person, "init", [false]);
 *     },
 *     "dance" : function () {
 *         // Call the overridden dance() method
 *         return this._super(Person, "dance");
 *     },
 *     "swingSword" : function () {
 *         return true;
 *     }
 * });
 *
 * var Pirate = Person.extend(Ninja, {
 *     "init" : function () {
 *         // Call the super constructor, passing a single argument
 *         this._super(Person, "init", [true]);
 *     }
 * });
 *
 * var p = new Person(true);
 * console.log(p.dance()); // => true
 *
 * var n = new Ninja();
 * console.log(n.dance()); // => false
 * console.log(n.swingSword()); // => true
 *
 * var r = new Pirate();
 * console.log(r.dance()); // => true
 * console.log(r.swingSword()); // => true
 *
 * console.log(
 *     p instanceof Person &&
 *     n instanceof Ninja &&
 *     n instanceof Person &&
 *     r instanceof Pirate &&
 *     r instanceof Person
 * ); // => true
 *
 * console.log(r instanceof Ninja); // => false
 */
(function () {
    function extend() {
        var methods = {};
        var mixins = new Array(arguments.length);
        for (var i = 0; i < arguments.length; i++) {
            mixins.push(arguments[i]);
        }

        /**
         * The class constructor which calls the user `init` constructor.
         * @ignore
         */
        function Class() {
            // Call the user constructor
            this.init.apply(this, arguments);
            return this;
        }

        // Apply superClass
        Class.prototype = Object.create(this.prototype);

        // Apply all mixin methods to the class prototype
        mixins.forEach(function (mixin) {
            apply_methods(Class, methods, mixin.__methods__ || mixin);
        });

        // Verify constructor exists
        if (!("init" in Class.prototype)) {
            throw new TypeError(
                "extend: Class is missing a constructor named `init`"
            );
        }

        // Apply syntactic sugar for accessing methods on super classes
        Object.defineProperty(Class.prototype, "_super", {
            "value" : _super
        });

        // Create a hidden property on the class itself
        // List of methods, used for applying classes as mixins
        Object.defineProperty(Class, "__methods__", {
            "value" : methods
        });

        // Make this class extendable
        Class.extend = extend;

        return Class;
    }

    /**
     * Apply methods to the class prototype.
     * @ignore
     */
    function apply_methods(Class, methods, descriptor) {
        Object.keys(descriptor).forEach(function (method) {
            methods[method] = descriptor[method];

            if (typeof(descriptor[method]) !== "function") {
                throw new TypeError(
                    "extend: Method `" + method + "` is not a function"
                );
            }

            Object.defineProperty(Class.prototype, method, {
                "configurable" : true,
                "value" : descriptor[method]
            });
        });
    }

    /**
     * Special method that acts as a proxy to the super class.
     * @name _super
     * @ignore
     */
    function _super(superClass, method, args) {
        return superClass.prototype[method].apply(this, args);
    }

    /**
     * The base class from which all jay-extend classes inherit.
     * @ignore
     */
    var Jay = function () {
        Object.apply(this, arguments);
    };
    Jay.prototype = Object.create(Object.prototype);
    Jay.prototype.constructor = Jay;

    Object.defineProperty(Jay, "extend", {
        "value" : extend
    });

    /**
     * Export the extend method.
     * @ignore
     */
    if (typeof(window) !== "undefined") {
        window.Jay = Jay;
    }
    else {
        module.exports = Jay;
    }
})();
/* eslint-enable no-undef */
<|endoftext|>