<commit_before>/* Copyright 2015 Bloomberg Finance L.P.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

var d3 = Object.assign({}, require("d3-scale"));
var _ = require("underscore");
var markmodel = require("./MarkModel");
var serialize = require('./serialize')

var PieModel = markmodel.MarkModel.extend({

    defaults: function() {
        return _.extend(markmodel.MarkModel.prototype.defaults(), {
            _model_name: "PieModel",
            _view_name: "Pie",

            sizes: [],
            color: null,
            x: 0.5,
            y: 0.5,
            scales_metadata: {
                color: { dimension: "color" }
            },
            sort: false,
            colors: d3.scaleOrdinal(d3.schemeCategory10).range(),
            stroke: null,
            opacities: [],
            radius: 180,
            inner_radius: 0.1,
            start_angle: 0.0,
            end_angle: 360.0
        });
    },

    initialize: function() {
        PieModel.__super__.initialize.apply(this, arguments);
        this.on("change:sizes", this.update_data, this);
        this.on("change:color", function() {
            this.update_color();
            this.trigger("colors_updated");
        }, this);
        this.on("change:labels", this.update_labels, this);

        this.on_some_change(["preserve_domain"], this.update_domains, this);
        this.update_data();
        this.update_color();
        this.update_labels();
        this.update_domains();
    },

    update_data: function() {
        var sizes = this.get("sizes");
        var color = this.get("color") || [];
        var labels = this.get("labels");
        this.mark_data = Array.prototype.map.call(sizes, function(d, i) {
            return {
                size: d,
                color: color[i],
                // jshint eqnull: true
                label: labels[i] == null ? "" : labels[i],
                index: i
            };
        });
        this.update_color();
        this.update_domains();
        this.trigger("data_updated");
    },

    update_labels: function() {
        if(!this.mark_data) {
            return;
        }
        var labels = this.get("labels");
        this.mark_data.forEach(function(data, index) {
            // jshint eqnull: true
            data.label = labels[index] == null ? "" : labels[index];
        });
        this.trigger("labels_updated");
    },

    update_color: function() {
        if(!this.mark_data) {
            return;
        }
        var color = this.get("color");
        var color_scale = this.get("scales").color;
        if(color_scale) {
            if(!this.get("preserve_domain").color) {
                color_scale.compute_and_set_domain(color, this.model_id + "_color");
            } else {
                color_scale.del_domain([], this.model_id + "_color");
            }
        }
    },

    update_domains: function() {
        if(!this.mark_data) {
            return;
        }
        var scales = this.get("scales");
        var x_scale = scales.x;
        var y_scale = scales.y;

        if(x_scale) {
            var x = (x_scale.type === "date") ?
                this.get_date_elem("x") : this.get("x");
            if(!this.get("preserve_domain").x) {
                x_scale.compute_and_set_domain([x], this.model_id + "_x");
            } else {
                x_scale.del_domain([], this.model_id + "_x");
            }
        }
        if(y_scale) {
            if(!this.get("preserve_domain").y) {
                y_scale.compute_and_set_domain([this.get("y")], this.model_id + "_y");
            } else {
                y_scale.del_domain([], this.model_id + "_y");
            }
        }
    },

    get_data_dict: function(data, index) {
        return data.data;
    }
}, {
    serializers: _.extend({
        sizes: serialize.array_or_json,
        color: serialize.array_or_json,
    }, markmodel.MarkModel.serializers)
});

module.exports = {
    PieModel: PieModel
};
<commit_msg>Use d3-scale-chromatic to fix error<commit_after>/* Copyright 2015 Bloomberg Finance L.P.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

var d3 = Object.assign({}, require("d3-scale"), require("d3-scale-chromatic"));
var _ = require("underscore");
var markmodel = require("./MarkModel");
var serialize = require('./serialize')

var PieModel = markmodel.MarkModel.extend({

    defaults: function() {
        return _.extend(markmodel.MarkModel.prototype.defaults(), {
            _model_name: "PieModel",
            _view_name: "Pie",

            sizes: [],
            color: null,
            x: 0.5,
            y: 0.5,
            scales_metadata: {
                color: { dimension: "color" }
            },
            sort: false,
            colors: d3.scaleOrdinal(d3.schemeCategory10).range(),
            stroke: null,
            opacities: [],
            radius: 180,
            inner_radius: 0.1,
            start_angle: 0.0,
            end_angle: 360.0
        });
    },

    initialize: function() {
        PieModel.__super__.initialize.apply(this, arguments);
        this.on("change:sizes", this.update_data, this);
        this.on("change:color", function() {
            this.update_color();
            this.trigger("colors_updated");
        }, this);
        this.on("change:labels", this.update_labels, this);

        this.on_some_change(["preserve_domain"], this.update_domains, this);
        this.update_data();
        this.update_color();
        this.update_labels();
        this.update_domains();
    },

    update_data: function() {
        var sizes = this.get("sizes");
        var color = this.get("color") || [];
        var labels = this.get("labels");
        this.mark_data = Array.prototype.map.call(sizes, function(d, i) {
            return {
                size: d,
                color: color[i],
                // jshint eqnull: true
                label: labels[i] == null ? "" : labels[i],
                index: i
            };
        });
        this.update_color();
        this.update_domains();
        this.trigger("data_updated");
    },

    update_labels: function() {
        if(!this.mark_data) {
            return;
        }
        var labels = this.get("labels");
        this.mark_data.forEach(function(data, index) {
            // jshint eqnull: true
            data.label = labels[index] == null ? "" : labels[index];
        });
        this.trigger("labels_updated");
    },

    update_color: function() {
        if(!this.mark_data) {
            return;
        }
        var color = this.get("color");
        var color_scale = this.get("scales").color;
        if(color_scale) {
            if(!this.get("preserve_domain").color) {
                color_scale.compute_and_set_domain(color, this.model_id + "_color");
            } else {
                color_scale.del_domain([], this.model_id + "_color");
            }
        }
    },

    update_domains: function() {
        if(!this.mark_data) {
            return;
        }
        var scales = this.get("scales");
        var x_scale = scales.x;
        var y_scale = scales.y;

        if(x_scale) {
            var x = (x_scale.type === "date") ?
                this.get_date_elem("x") : this.get("x");
            if(!this.get("preserve_domain").x) {
                x_scale.compute_and_set_domain([x], this.model_id + "_x");
            } else {
                x_scale.del_domain([], this.model_id + "_x");
            }
        }
        if(y_scale) {
            if(!this.get("preserve_domain").y) {
                y_scale.compute_and_set_domain([this.get("y")], this.model_id + "_y");
            } else {
                y_scale.del_domain([], this.model_id + "_y");
            }
        }
    },

    get_data_dict: function(data, index) {
        return data.data;
    }
}, {
    serializers: _.extend({
        sizes: serialize.array_or_json,
        color: serialize.array_or_json,
    }, markmodel.MarkModel.serializers)
});

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


<commit_before>/**
 * --------------------------------------------------------------------------
 * Bootstrap (v5.1.2): dropdown.js
 * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
 * --------------------------------------------------------------------------
 */

import * as Popper from '@popperjs/core'

import {
  defineJQueryPlugin,
  getElement,
  getElementFromSelector,
  getNextActiveElement,
  isDisabled,
  isElement,
  isRTL,
  isVisible,
  noop,
  typeCheckConfig
} from './util/index'
import EventHandler from './dom/event-handler'
import Manipulator from './dom/manipulator'
import SelectorEngine from './dom/selector-engine'
import BaseComponent from './base-component'

/**
 * ------------------------------------------------------------------------
 * Constants
 * ------------------------------------------------------------------------
 */

const NAME = 'dropdown'
const DATA_KEY = 'bs.dropdown'
const EVENT_KEY = `.${DATA_KEY}`
const DATA_API_KEY = '.data-api'

const ESCAPE_KEY = 'Escape'
const SPACE_KEY = 'Space'
const TAB_KEY = 'Tab'
const ARROW_UP_KEY = 'ArrowUp'
const ARROW_DOWN_KEY = 'ArrowDown'
const RIGHT_MOUSE_BUTTON = 2 // MouseEvent.button value for the secondary button, usually the right button

const REGEXP_KEYDOWN = new RegExp(`${ARROW_UP_KEY}|${ARROW_DOWN_KEY}|${ESCAPE_KEY}`)

const EVENT_HIDE = `hide${EVENT_KEY}`
const EVENT_HIDDEN = `hidden${EVENT_KEY}`
const EVENT_SHOW = `show${EVENT_KEY}`
const EVENT_SHOWN = `shown${EVENT_KEY}`
const EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`
const EVENT_KEYDOWN_DATA_API = `keydown${EVENT_KEY}${DATA_API_KEY}`
const EVENT_KEYUP_DATA_API = `keyup${EVENT_KEY}${DATA_API_KEY}`

const CLASS_NAME_SHOW = 'show'
const CLASS_NAME_DROPUP = 'dropup'
const CLASS_NAME_DROPEND = 'dropend'
const CLASS_NAME_DROPSTART = 'dropstart'
const CLASS_NAME_NAVBAR = 'navbar'

const SELECTOR_DATA_TOGGLE = '[data-bs-toggle="dropdown"]'
const SELECTOR_MENU = '.dropdown-menu'
const SELECTOR_NAVBAR_NAV = '.navbar-nav'
const SELECTOR_VISIBLE_ITEMS = '.dropdown-menu .dropdown-item:not(.disabled):not(:disabled)'

const PLACEMENT_TOP = isRTL() ? 'top-end' : 'top-start'
const PLACEMENT_TOPEND = isRTL() ? 'top-start' : 'top-end'
const PLACEMENT_BOTTOM = isRTL() ? 'bottom-end' : 'bottom-start'
const PLACEMENT_BOTTOMEND = isRTL() ? 'bottom-start' : 'bottom-end'
const PLACEMENT_RIGHT = isRTL() ? 'left-start' : 'right-start'
const PLACEMENT_LEFT = isRTL() ? 'right-start' : 'left-start'

const Default = {
  offset: [0, 2],
  boundary: 'clippingParents',
  reference: 'toggle',
  display: 'dynamic',
  popperConfig: null,
  autoClose: true
}

const DefaultType = {
  offset: '(array|string|function)',
  boundary: '(string|element)',
  reference: '(string|element|object)',
  display: 'string',
  popperConfig: '(null|object|function)',
  autoClose: '(boolean|string)'
}

/**
 * ------------------------------------------------------------------------
 * Class Definition
 * ------------------------------------------------------------------------
 */

class Dropdown extends BaseComponent {
  constructor(element, config) {
    super(element)

    this._popper = null
    this._config = this._getConfig(config)
    this._menu = this._getMenuElement()
    this._inNavbar = this._detectNavbar()
  }

  // Getters

  static get Default() {
    return Default
  }

  static get DefaultType() {
    return DefaultType
  }

  static get NAME() {
    return NAME
  }

  // Public

  toggle() {
    return this._isShown() ? this.hide() : this.show()
  }

  show() {
    if (isDisabled(this._element) || this._isShown(this._menu)) {
      return
    }

    const relatedTarget = {
      relatedTarget: this._element
    }

    const showEvent = EventHandler.trigger(this._element, EVENT_SHOW, relatedTarget)

    if (showEvent.defaultPrevented) {
      return
    }

    const parent = Dropdown.getParentFromElement(this._element)
    // Totally disable Popper for Dropdowns in Navbar
    if (this._inNavbar) {
      Manipulator.setDataAttribute(this._menu, 'popper', 'none')
    } else {
      this._createPopper(parent)
    }

    // If this is a touch-enabled device we add extra
    // empty mouseover listeners to the body's immediate children;
    // only needed because of broken event delegation on iOS
    // https://www.quirksmode.org/blog/archives/2014/02/mouse_event_bub.html
    if ('ontouchstart' in document.documentElement && !parent.closest(SELECTOR_NAVBAR_NAV)) {
      for (const elem of [].concat(...document.body.children)) {
        EventHandler.on(elem, 'mouseover', noop)
      }
    }

    this._element.focus()
    this._element.setAttribute('aria-expanded', true)

    this._menu.classList.add(CLASS_NAME_SHOW)
    this._element.classList.add(CLASS_NAME_SHOW)
    EventHandler.trigger(this._element, EVENT_SHOWN, relatedTarget)
  }

  hide() {
    if (isDisabled(this._element) || !this._isShown(this._menu)) {
      return
    }

    const relatedTarget = {
      relatedTarget: this._element
    }

    this._completeHide(relatedTarget)
  }

  dispose() {
    if (this._popper) {
      this._popper.destroy()
    }

    super.dispose()
  }

  update() {
    this._inNavbar = this._detectNavbar()
    if (this._popper) {
      this._popper.update()
    }
  }

  // Private

  _completeHide(relatedTarget) {
    const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE, relatedTarget)
    if (hideEvent.defaultPrevented) {
      return
    }

    // If this is a touch-enabled device we remove the extra
    // empty mouseover listeners we added for iOS support
    if ('ontouchstart' in document.documentElement) {
      for (const elem of [].concat(...document.body.children)) {
        EventHandler.off(elem, 'mouseover', noop)
      }
    }

    if (this._popper) {
      this._popper.destroy()
    }

    this._menu.classList.remove(CLASS_NAME_SHOW)
    this._element.classList.remove(CLASS_NAME_SHOW)
    this._element.setAttribute('aria-expanded', 'false')
    Manipulator.removeDataAttribute(this._menu, 'popper')
    EventHandler.trigger(this._element, EVENT_HIDDEN, relatedTarget)
  }

  _getConfig(config) {
    config = {
      ...this.constructor.Default,
      ...Manipulator.getDataAttributes(this._element),
      ...config
    }

    typeCheckConfig(NAME, config, this.constructor.DefaultType)

    if (typeof config.reference === 'object' && !isElement(config.reference) &&
      typeof config.reference.getBoundingClientRect !== 'function'
    ) {
      // Popper virtual elements require a getBoundingClientRect method
      throw new TypeError(`${NAME.toUpperCase()}: Option "reference" provided type "object" without a required "getBoundingClientRect" method.`)
    }

    return config
  }

  _createPopper(parent) {
    if (typeof Popper === 'undefined') {
      throw new TypeError('Bootstrap\'s dropdowns require Popper (https://popper.js.org)')
    }

    let referenceElement = this._element

    if (this._config.reference === 'parent') {
      referenceElement = parent
    } else if (isElement(this._config.reference)) {
      referenceElement = getElement(this._config.reference)
    } else if (typeof this._config.reference === 'object') {
      referenceElement = this._config.reference
    }

    const popperConfig = this._getPopperConfig()
    const isDisplayStatic = popperConfig.modifiers.find(modifier => modifier.name === 'applyStyles' && modifier.enabled === false)

    this._popper = Popper.createPopper(referenceElement, this._menu, popperConfig)

    if (isDisplayStatic) {
      Manipulator.setDataAttribute(this._menu, 'popper', 'static')
    }
  }

  _isShown(element = this._element) {
    return element.classList.contains(CLASS_NAME_SHOW)
  }

  _getMenuElement() {
    return SelectorEngine.next(this._element, SELECTOR_MENU)[0]
  }

  _getPlacement() {
    const parentDropdown = this._element.parentNode

    if (parentDropdown.classList.contains(CLASS_NAME_DROPEND)) {
      return PLACEMENT_RIGHT
    }

    if (parentDropdown.classList.contains(CLASS_NAME_DROPSTART)) {
      return PLACEMENT_LEFT
    }

    // We need to trim the value because custom properties can also include spaces
    const isEnd = getComputedStyle(this._menu).getPropertyValue('--bs-position').trim() === 'end'

    if (parentDropdown.classList.contains(CLASS_NAME_DROPUP)) {
      return isEnd ? PLACEMENT_TOPEND : PLACEMENT_TOP
    }

    return isEnd ? PLACEMENT_BOTTOMEND : PLACEMENT_BOTTOM
  }

  _detectNavbar() {
    return this._element.closest(`.${CLASS_NAME_NAVBAR}`) !== null
  }

  _getOffset() {
    const { offset } = this._config

    if (typeof offset === 'string') {
      return offset.split(',').map(val => Number.parseInt(val, 10))
    }

    if (typeof offset === 'function') {
      return popperData => offset(popperData, this._element)
    }

    return offset
  }

  _getPopperConfig() {
    const defaultBsPopperConfig = {
      placement: this._getPlacement(),
      modifiers: [{
        name: 'preventOverflow',
        options: {
          boundary: this._config.boundary
        }
      },
      {
        name: 'offset',
        options: {
          offset: this._getOffset()
        }
      }]
    }

    // Disable Popper if we have a static display
    if (this._config.display === 'static') {
      defaultBsPopperConfig.modifiers = [{
        name: 'applyStyles',
        enabled: false
      }]
    }

    return {
      ...defaultBsPopperConfig,
      ...(typeof this._config.popperConfig === 'function' ? this._config.popperConfig(defaultBsPopperConfig) : this._config.popperConfig)
    }
  }

  _selectMenuItem({ key, target }) {
    const items = SelectorEngine.find(SELECTOR_VISIBLE_ITEMS, this._menu).filter(isVisible)

    if (!items.length) {
      return
    }

    // if target isn't included in items (e.g. when expanding the dropdown)
    // allow cycling to get the last item in case key equals ARROW_UP_KEY
    getNextActiveElement(items, target, key === ARROW_DOWN_KEY, !items.includes(target)).focus()
  }

  // Static

  static jQueryInterface(config) {
    return this.each(function () {
      const data = Dropdown.getOrCreateInstance(this, config)

      if (typeof config !== 'string') {
        return
      }

      if (typeof data[config] === 'undefined') {
        throw new TypeError(`No method named "${config}"`)
      }

      data[config]()
    })
  }

  static clearMenus(event) {
    if (event && (event.button === RIGHT_MOUSE_BUTTON || (event.type === 'keyup' && event.key !== TAB_KEY))) {
      return
    }

    const toggles = SelectorEngine.find(SELECTOR_DATA_TOGGLE)

    for (const toggle of toggles) {
      const context = Dropdown.getInstance(toggle)
      if (!context || context._config.autoClose === false) {
        continue
      }

      if (!context._isShown()) {
        continue
      }

      const relatedTarget = {
        relatedTarget: context._element
      }

      if (event) {
        const composedPath = event.composedPath()
        const isMenuTarget = composedPath.includes(context._menu)
        if (
          composedPath.includes(context._element) ||
          (context._config.autoClose === 'inside' && !isMenuTarget) ||
          (context._config.autoClose === 'outside' && isMenuTarget)
        ) {
          continue
        }

        // Tab navigation through the dropdown menu or events from contained inputs shouldn't close the menu
        if (context._menu.contains(event.target) && ((event.type === 'keyup' && event.key === TAB_KEY) || /input|select|option|textarea|form/i.test(event.target.tagName))) {
          continue
        }

        if (event.type === 'click') {
          relatedTarget.clickEvent = event
        }
      }

      context._completeHide(relatedTarget)
    }
  }

  static getParentFromElement(element) {
    return getElementFromSelector(element) || element.parentNode
  }

  static dataApiKeydownHandler(event) {
    // If not input/textarea:
    //  - And not a key in REGEXP_KEYDOWN => not a dropdown command
    // If input/textarea:
    //  - If space key => not a dropdown command
    //  - If key is other than escape
    //    - If key is not up or down => not a dropdown command
    //    - If trigger inside the menu => not a dropdown command
    if (/input|textarea/i.test(event.target.tagName) ?
      event.key === SPACE_KEY || (event.key !== ESCAPE_KEY &&
      ((event.key !== ARROW_DOWN_KEY && event.key !== ARROW_UP_KEY) ||
        event.target.closest(SELECTOR_MENU))) :
      !REGEXP_KEYDOWN.test(event.key)) {
      return
    }

    const isActive = this.classList.contains(CLASS_NAME_SHOW)

    if (!isActive && event.key === ESCAPE_KEY) {
      return
    }

    event.preventDefault()
    event.stopPropagation()

    if (isDisabled(this)) {
      return
    }

    const getToggleButton = this.matches(SELECTOR_DATA_TOGGLE) ? this : SelectorEngine.prev(this, SELECTOR_DATA_TOGGLE)[0]
    const instance = Dropdown.getOrCreateInstance(getToggleButton)

    if (event.key === ESCAPE_KEY) {
      instance.hide()
      return
    }

    if (event.key === ARROW_UP_KEY || event.key === ARROW_DOWN_KEY) {
      if (!isActive) {
        instance.show()
      }

      instance._selectMenuItem(event)
      return
    }

    if (!isActive || event.key === SPACE_KEY) {
      Dropdown.clearMenus()
    }
  }
}

/**
 * ------------------------------------------------------------------------
 * Data Api implementation
 * ------------------------------------------------------------------------
 */

EventHandler.on(document, EVENT_KEYDOWN_DATA_API, SELECTOR_DATA_TOGGLE, Dropdown.dataApiKeydownHandler)
EventHandler.on(document, EVENT_KEYDOWN_DATA_API, SELECTOR_MENU, Dropdown.dataApiKeydownHandler)
EventHandler.on(document, EVENT_CLICK_DATA_API, Dropdown.clearMenus)
EventHandler.on(document, EVENT_KEYUP_DATA_API, Dropdown.clearMenus)
EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) {
  event.preventDefault()
  Dropdown.getOrCreateInstance(this).toggle()
})

/**
 * ------------------------------------------------------------------------
 * jQuery
 * ------------------------------------------------------------------------
 * add .Dropdown to jQuery only if jQuery is present
 */

defineJQueryPlugin(Dropdown)

export default Dropdown
<commit_msg>Fix a `unicorn/no-array-callback-reference` issue<commit_after>/**
 * --------------------------------------------------------------------------
 * Bootstrap (v5.1.2): dropdown.js
 * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
 * --------------------------------------------------------------------------
 */

import * as Popper from '@popperjs/core'

import {
  defineJQueryPlugin,
  getElement,
  getElementFromSelector,
  getNextActiveElement,
  isDisabled,
  isElement,
  isRTL,
  isVisible,
  noop,
  typeCheckConfig
} from './util/index'
import EventHandler from './dom/event-handler'
import Manipulator from './dom/manipulator'
import SelectorEngine from './dom/selector-engine'
import BaseComponent from './base-component'

/**
 * ------------------------------------------------------------------------
 * Constants
 * ------------------------------------------------------------------------
 */

const NAME = 'dropdown'
const DATA_KEY = 'bs.dropdown'
const EVENT_KEY = `.${DATA_KEY}`
const DATA_API_KEY = '.data-api'

const ESCAPE_KEY = 'Escape'
const SPACE_KEY = 'Space'
const TAB_KEY = 'Tab'
const ARROW_UP_KEY = 'ArrowUp'
const ARROW_DOWN_KEY = 'ArrowDown'
const RIGHT_MOUSE_BUTTON = 2 // MouseEvent.button value for the secondary button, usually the right button

const REGEXP_KEYDOWN = new RegExp(`${ARROW_UP_KEY}|${ARROW_DOWN_KEY}|${ESCAPE_KEY}`)

const EVENT_HIDE = `hide${EVENT_KEY}`
const EVENT_HIDDEN = `hidden${EVENT_KEY}`
const EVENT_SHOW = `show${EVENT_KEY}`
const EVENT_SHOWN = `shown${EVENT_KEY}`
const EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`
const EVENT_KEYDOWN_DATA_API = `keydown${EVENT_KEY}${DATA_API_KEY}`
const EVENT_KEYUP_DATA_API = `keyup${EVENT_KEY}${DATA_API_KEY}`

const CLASS_NAME_SHOW = 'show'
const CLASS_NAME_DROPUP = 'dropup'
const CLASS_NAME_DROPEND = 'dropend'
const CLASS_NAME_DROPSTART = 'dropstart'
const CLASS_NAME_NAVBAR = 'navbar'

const SELECTOR_DATA_TOGGLE = '[data-bs-toggle="dropdown"]'
const SELECTOR_MENU = '.dropdown-menu'
const SELECTOR_NAVBAR_NAV = '.navbar-nav'
const SELECTOR_VISIBLE_ITEMS = '.dropdown-menu .dropdown-item:not(.disabled):not(:disabled)'

const PLACEMENT_TOP = isRTL() ? 'top-end' : 'top-start'
const PLACEMENT_TOPEND = isRTL() ? 'top-start' : 'top-end'
const PLACEMENT_BOTTOM = isRTL() ? 'bottom-end' : 'bottom-start'
const PLACEMENT_BOTTOMEND = isRTL() ? 'bottom-start' : 'bottom-end'
const PLACEMENT_RIGHT = isRTL() ? 'left-start' : 'right-start'
const PLACEMENT_LEFT = isRTL() ? 'right-start' : 'left-start'

const Default = {
  offset: [0, 2],
  boundary: 'clippingParents',
  reference: 'toggle',
  display: 'dynamic',
  popperConfig: null,
  autoClose: true
}

const DefaultType = {
  offset: '(array|string|function)',
  boundary: '(string|element)',
  reference: '(string|element|object)',
  display: 'string',
  popperConfig: '(null|object|function)',
  autoClose: '(boolean|string)'
}

/**
 * ------------------------------------------------------------------------
 * Class Definition
 * ------------------------------------------------------------------------
 */

class Dropdown extends BaseComponent {
  constructor(element, config) {
    super(element)

    this._popper = null
    this._config = this._getConfig(config)
    this._menu = this._getMenuElement()
    this._inNavbar = this._detectNavbar()
  }

  // Getters

  static get Default() {
    return Default
  }

  static get DefaultType() {
    return DefaultType
  }

  static get NAME() {
    return NAME
  }

  // Public

  toggle() {
    return this._isShown() ? this.hide() : this.show()
  }

  show() {
    if (isDisabled(this._element) || this._isShown(this._menu)) {
      return
    }

    const relatedTarget = {
      relatedTarget: this._element
    }

    const showEvent = EventHandler.trigger(this._element, EVENT_SHOW, relatedTarget)

    if (showEvent.defaultPrevented) {
      return
    }

    const parent = Dropdown.getParentFromElement(this._element)
    // Totally disable Popper for Dropdowns in Navbar
    if (this._inNavbar) {
      Manipulator.setDataAttribute(this._menu, 'popper', 'none')
    } else {
      this._createPopper(parent)
    }

    // If this is a touch-enabled device we add extra
    // empty mouseover listeners to the body's immediate children;
    // only needed because of broken event delegation on iOS
    // https://www.quirksmode.org/blog/archives/2014/02/mouse_event_bub.html
    if ('ontouchstart' in document.documentElement && !parent.closest(SELECTOR_NAVBAR_NAV)) {
      for (const elem of [].concat(...document.body.children)) {
        EventHandler.on(elem, 'mouseover', noop)
      }
    }

    this._element.focus()
    this._element.setAttribute('aria-expanded', true)

    this._menu.classList.add(CLASS_NAME_SHOW)
    this._element.classList.add(CLASS_NAME_SHOW)
    EventHandler.trigger(this._element, EVENT_SHOWN, relatedTarget)
  }

  hide() {
    if (isDisabled(this._element) || !this._isShown(this._menu)) {
      return
    }

    const relatedTarget = {
      relatedTarget: this._element
    }

    this._completeHide(relatedTarget)
  }

  dispose() {
    if (this._popper) {
      this._popper.destroy()
    }

    super.dispose()
  }

  update() {
    this._inNavbar = this._detectNavbar()
    if (this._popper) {
      this._popper.update()
    }
  }

  // Private

  _completeHide(relatedTarget) {
    const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE, relatedTarget)
    if (hideEvent.defaultPrevented) {
      return
    }

    // If this is a touch-enabled device we remove the extra
    // empty mouseover listeners we added for iOS support
    if ('ontouchstart' in document.documentElement) {
      for (const elem of [].concat(...document.body.children)) {
        EventHandler.off(elem, 'mouseover', noop)
      }
    }

    if (this._popper) {
      this._popper.destroy()
    }

    this._menu.classList.remove(CLASS_NAME_SHOW)
    this._element.classList.remove(CLASS_NAME_SHOW)
    this._element.setAttribute('aria-expanded', 'false')
    Manipulator.removeDataAttribute(this._menu, 'popper')
    EventHandler.trigger(this._element, EVENT_HIDDEN, relatedTarget)
  }

  _getConfig(config) {
    config = {
      ...this.constructor.Default,
      ...Manipulator.getDataAttributes(this._element),
      ...config
    }

    typeCheckConfig(NAME, config, this.constructor.DefaultType)

    if (typeof config.reference === 'object' && !isElement(config.reference) &&
      typeof config.reference.getBoundingClientRect !== 'function'
    ) {
      // Popper virtual elements require a getBoundingClientRect method
      throw new TypeError(`${NAME.toUpperCase()}: Option "reference" provided type "object" without a required "getBoundingClientRect" method.`)
    }

    return config
  }

  _createPopper(parent) {
    if (typeof Popper === 'undefined') {
      throw new TypeError('Bootstrap\'s dropdowns require Popper (https://popper.js.org)')
    }

    let referenceElement = this._element

    if (this._config.reference === 'parent') {
      referenceElement = parent
    } else if (isElement(this._config.reference)) {
      referenceElement = getElement(this._config.reference)
    } else if (typeof this._config.reference === 'object') {
      referenceElement = this._config.reference
    }

    const popperConfig = this._getPopperConfig()
    const isDisplayStatic = popperConfig.modifiers.find(modifier => modifier.name === 'applyStyles' && modifier.enabled === false)

    this._popper = Popper.createPopper(referenceElement, this._menu, popperConfig)

    if (isDisplayStatic) {
      Manipulator.setDataAttribute(this._menu, 'popper', 'static')
    }
  }

  _isShown(element = this._element) {
    return element.classList.contains(CLASS_NAME_SHOW)
  }

  _getMenuElement() {
    return SelectorEngine.next(this._element, SELECTOR_MENU)[0]
  }

  _getPlacement() {
    const parentDropdown = this._element.parentNode

    if (parentDropdown.classList.contains(CLASS_NAME_DROPEND)) {
      return PLACEMENT_RIGHT
    }

    if (parentDropdown.classList.contains(CLASS_NAME_DROPSTART)) {
      return PLACEMENT_LEFT
    }

    // We need to trim the value because custom properties can also include spaces
    const isEnd = getComputedStyle(this._menu).getPropertyValue('--bs-position').trim() === 'end'

    if (parentDropdown.classList.contains(CLASS_NAME_DROPUP)) {
      return isEnd ? PLACEMENT_TOPEND : PLACEMENT_TOP
    }

    return isEnd ? PLACEMENT_BOTTOMEND : PLACEMENT_BOTTOM
  }

  _detectNavbar() {
    return this._element.closest(`.${CLASS_NAME_NAVBAR}`) !== null
  }

  _getOffset() {
    const { offset } = this._config

    if (typeof offset === 'string') {
      return offset.split(',').map(val => Number.parseInt(val, 10))
    }

    if (typeof offset === 'function') {
      return popperData => offset(popperData, this._element)
    }

    return offset
  }

  _getPopperConfig() {
    const defaultBsPopperConfig = {
      placement: this._getPlacement(),
      modifiers: [{
        name: 'preventOverflow',
        options: {
          boundary: this._config.boundary
        }
      },
      {
        name: 'offset',
        options: {
          offset: this._getOffset()
        }
      }]
    }

    // Disable Popper if we have a static display
    if (this._config.display === 'static') {
      defaultBsPopperConfig.modifiers = [{
        name: 'applyStyles',
        enabled: false
      }]
    }

    return {
      ...defaultBsPopperConfig,
      ...(typeof this._config.popperConfig === 'function' ? this._config.popperConfig(defaultBsPopperConfig) : this._config.popperConfig)
    }
  }

  _selectMenuItem({ key, target }) {
    const items = SelectorEngine.find(SELECTOR_VISIBLE_ITEMS, this._menu).filter(el => isVisible(el))

    if (!items.length) {
      return
    }

    // if target isn't included in items (e.g. when expanding the dropdown)
    // allow cycling to get the last item in case key equals ARROW_UP_KEY
    getNextActiveElement(items, target, key === ARROW_DOWN_KEY, !items.includes(target)).focus()
  }

  // Static

  static jQueryInterface(config) {
    return this.each(function () {
      const data = Dropdown.getOrCreateInstance(this, config)

      if (typeof config !== 'string') {
        return
      }

      if (typeof data[config] === 'undefined') {
        throw new TypeError(`No method named "${config}"`)
      }

      data[config]()
    })
  }

  static clearMenus(event) {
    if (event && (event.button === RIGHT_MOUSE_BUTTON || (event.type === 'keyup' && event.key !== TAB_KEY))) {
      return
    }

    const toggles = SelectorEngine.find(SELECTOR_DATA_TOGGLE)

    for (const toggle of toggles) {
      const context = Dropdown.getInstance(toggle)
      if (!context || context._config.autoClose === false) {
        continue
      }

      if (!context._isShown()) {
        continue
      }

      const relatedTarget = {
        relatedTarget: context._element
      }

      if (event) {
        const composedPath = event.composedPath()
        const isMenuTarget = composedPath.includes(context._menu)
        if (
          composedPath.includes(context._element) ||
          (context._config.autoClose === 'inside' && !isMenuTarget) ||
          (context._config.autoClose === 'outside' && isMenuTarget)
        ) {
          continue
        }

        // Tab navigation through the dropdown menu or events from contained inputs shouldn't close the menu
        if (context._menu.contains(event.target) && ((event.type === 'keyup' && event.key === TAB_KEY) || /input|select|option|textarea|form/i.test(event.target.tagName))) {
          continue
        }

        if (event.type === 'click') {
          relatedTarget.clickEvent = event
        }
      }

      context._completeHide(relatedTarget)
    }
  }

  static getParentFromElement(element) {
    return getElementFromSelector(element) || element.parentNode
  }

  static dataApiKeydownHandler(event) {
    // If not input/textarea:
    //  - And not a key in REGEXP_KEYDOWN => not a dropdown command
    // If input/textarea:
    //  - If space key => not a dropdown command
    //  - If key is other than escape
    //    - If key is not up or down => not a dropdown command
    //    - If trigger inside the menu => not a dropdown command
    if (/input|textarea/i.test(event.target.tagName) ?
      event.key === SPACE_KEY || (event.key !== ESCAPE_KEY &&
      ((event.key !== ARROW_DOWN_KEY && event.key !== ARROW_UP_KEY) ||
        event.target.closest(SELECTOR_MENU))) :
      !REGEXP_KEYDOWN.test(event.key)) {
      return
    }

    const isActive = this.classList.contains(CLASS_NAME_SHOW)

    if (!isActive && event.key === ESCAPE_KEY) {
      return
    }

    event.preventDefault()
    event.stopPropagation()

    if (isDisabled(this)) {
      return
    }

    const getToggleButton = this.matches(SELECTOR_DATA_TOGGLE) ? this : SelectorEngine.prev(this, SELECTOR_DATA_TOGGLE)[0]
    const instance = Dropdown.getOrCreateInstance(getToggleButton)

    if (event.key === ESCAPE_KEY) {
      instance.hide()
      return
    }

    if (event.key === ARROW_UP_KEY || event.key === ARROW_DOWN_KEY) {
      if (!isActive) {
        instance.show()
      }

      instance._selectMenuItem(event)
      return
    }

    if (!isActive || event.key === SPACE_KEY) {
      Dropdown.clearMenus()
    }
  }
}

/**
 * ------------------------------------------------------------------------
 * Data Api implementation
 * ------------------------------------------------------------------------
 */

EventHandler.on(document, EVENT_KEYDOWN_DATA_API, SELECTOR_DATA_TOGGLE, Dropdown.dataApiKeydownHandler)
EventHandler.on(document, EVENT_KEYDOWN_DATA_API, SELECTOR_MENU, Dropdown.dataApiKeydownHandler)
EventHandler.on(document, EVENT_CLICK_DATA_API, Dropdown.clearMenus)
EventHandler.on(document, EVENT_KEYUP_DATA_API, Dropdown.clearMenus)
EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) {
  event.preventDefault()
  Dropdown.getOrCreateInstance(this).toggle()
})

/**
 * ------------------------------------------------------------------------
 * jQuery
 * ------------------------------------------------------------------------
 * add .Dropdown to jQuery only if jQuery is present
 */

defineJQueryPlugin(Dropdown)

export default Dropdown
<|endoftext|>


<commit_before>KiddoPaint.Display.undoData = null;

KiddoPaint.Display.clearAll = function() {
  KiddoPaint.Display.saveUndo();
  KiddoPaint.Display.clearPreview();
  KiddoPaint.Display.clearTmp();
  KiddoPaint.Display.clearMain();
  KiddoPaint.Display.clearLocalStorage();
}

KiddoPaint.Display.clearMain = function() {
  KiddoPaint.Display.main_context.clearRect(0, 0, KiddoPaint.Display.main_canvas.width, KiddoPaint.Display.main_canvas.height);
}

KiddoPaint.Display.clearTmp = function() {
  KiddoPaint.Display.context.clearRect(0, 0, KiddoPaint.Display.canvas.width, KiddoPaint.Display.canvas.height);
}

KiddoPaint.Display.clearPreview = function() {
  KiddoPaint.Display.previewContext.clearRect(0, 0, KiddoPaint.Display.canvas.width, KiddoPaint.Display.canvas.height);
}

KiddoPaint.Display.saveMain = function() {
  KiddoPaint.Display.saveUndo();
  KiddoPaint.Display.main_context.drawImage(KiddoPaint.Display.canvas, 0, 0);
  KiddoPaint.Display.clearTmp();
  KiddoPaint.Display.saveToLocalStorage();
}

KiddoPaint.Display.saveUndo = function() {
  KiddoPaint.Display.undoData = KiddoPaint.Display.main_canvas.toDataURL();
}

KiddoPaint.Display.undo = function() {
  if(KiddoPaint.Display.undoData) {
    var nextUndoData = KiddoPaint.Display.main_canvas.toDataURL();
    var img = new Image();
    img.src = KiddoPaint.Display.undoData;
    img.onload = function() { KiddoPaint.Display.clearMain(); KiddoPaint.Display.main_context.drawImage(img, 0, 0); }
    KiddoPaint.Display.undoData = nextUndoData;
  }
}

KiddoPaint.Display.clearLocalStorage = function() {
  if (typeof(Storage) != "undefined") {
    localStorage.removeItem("kiddopaint");
  }
}

KiddoPaint.Display.saveToLocalStorage = function() {
  if (typeof(Storage) != "undefined") {
    localStorage.setItem("kiddopaint", KiddoPaint.Display.main_canvas.toDataURL());
  }
}

KiddoPaint.Display.loadFromLocalStorage = function() {
  if (typeof(Storage) != "undefined" && localStorage.getItem("kiddopaint")) {
      var img = new Image();
      img.src = localStorage.getItem("kiddopaint");
      img.onload = function() { KiddoPaint.Display.clearMain(); KiddoPaint.Display.main_context.drawImage(img, 0, 0); }
  }
}
<commit_msg>Add loading of splash page.<commit_after>KiddoPaint.Display.undoData = null;

KiddoPaint.Display.clearAll = function() {
  KiddoPaint.Display.saveUndo();
  KiddoPaint.Display.clearPreview();
  KiddoPaint.Display.clearTmp();
  KiddoPaint.Display.clearMain();
  KiddoPaint.Display.clearLocalStorage();
}

KiddoPaint.Display.clearMain = function() {
  KiddoPaint.Display.main_context.clearRect(0, 0, KiddoPaint.Display.main_canvas.width, KiddoPaint.Display.main_canvas.height);
}

KiddoPaint.Display.clearTmp = function() {
  KiddoPaint.Display.context.clearRect(0, 0, KiddoPaint.Display.canvas.width, KiddoPaint.Display.canvas.height);
}

KiddoPaint.Display.clearPreview = function() {
  KiddoPaint.Display.previewContext.clearRect(0, 0, KiddoPaint.Display.canvas.width, KiddoPaint.Display.canvas.height);
}

KiddoPaint.Display.saveMain = function() {
  KiddoPaint.Display.saveUndo();
  KiddoPaint.Display.main_context.drawImage(KiddoPaint.Display.canvas, 0, 0);
  KiddoPaint.Display.clearTmp();
  KiddoPaint.Display.saveToLocalStorage();
}

KiddoPaint.Display.saveUndo = function() {
  KiddoPaint.Display.undoData = KiddoPaint.Display.main_canvas.toDataURL();
}

KiddoPaint.Display.undo = function() {
  if(KiddoPaint.Display.undoData) {
    var nextUndoData = KiddoPaint.Display.main_canvas.toDataURL();
    var img = new Image();
    img.src = KiddoPaint.Display.undoData;
    img.onload = function() { KiddoPaint.Display.clearMain(); KiddoPaint.Display.main_context.drawImage(img, 0, 0); }
    KiddoPaint.Display.undoData = nextUndoData;
  }
}

KiddoPaint.Display.clearLocalStorage = function() {
  if (typeof(Storage) != "undefined") {
    localStorage.removeItem("kiddopaint");
  }
}

KiddoPaint.Display.saveToLocalStorage = function() {
  if (typeof(Storage) != "undefined") {
    localStorage.setItem("kiddopaint", KiddoPaint.Display.main_canvas.toDataURL());
  }
}

KiddoPaint.Display.loadFromLocalStorage = function() {
  var img = new Image();
  img.crossOrigin = "Anonymous";
  img.onload = function() { KiddoPaint.Display.clearMain(); KiddoPaint.Display.main_context.drawImage(img, 0, 0); }
  if (typeof(Storage) != "undefined" && localStorage.getItem("kiddopaint")) {
      img.src = localStorage.getItem("kiddopaint");
  }
  else {
      img.src = "https://kiddopaint.com/static/kiddopaint.jpg";
  }
}
<|endoftext|>


<commit_before>import bigNumber from 'bignumber.js';
import app from '../app';

/*
 * Will return a string representation of a number ensuring that standard
 * notation is used (as opposed to the default JS representation which uses
 * scientific notation for small numbers, e.g. 0.00000001 => 1E-8).
 */
console.log('unit test me.');
export function toStandardNotation(number, options) {
  const opts = {
    returnUnchangedOnError: true,
    ...options,
  };

  try {
    const bigNum = bigNumber(number);

    if (bigNum.isNaN()) {
      throw new Error(`${number} is not a valid number.`);
    }

    return (
      bigNum.toFormat({
        ...bigNumber.config().FORMAT,
        groupSeparator: '',
        fractionGroupSeparator: '',
      })
    );
  } catch (e) {
    if (opts.returnUnchangedOnError) return number;
    throw e;
  }
}

/*
 * Like Number.toFixed, but whereas that will force your number
 * to have the specified number of decimal places, this will not
 * allow you to have more than the specified number, but will allow
 * less, e.g:
 *
 * (18.986).toFixed(2) // 18.99
 * upToFixed(18.986,2) // 18.99
 * (18.986).toFixed(4) // 18.9860
 * upToFixed(18.986, 4) // 18.986
 */
export function upToFixed(number, decPlaces) {
  if (typeof number !== 'number') {
    throw new Error('Please provide a number.');
  }

  return toStandardNotation(
    parseFloat((number).toFixed(decPlaces))
  ).toString();
}

/*
 * Returns a random integer from min to (min + range).
 *
 * https://stackoverflow.com/questions/13997793/generate-random-number-between-2-numbers
 */
export function randomInt(min = 0, range = 0) {
  return Math.floor((Math.random() * (range + 1)) + min);
}

/*
 * Uses local settings' language or provided optional language to return a localized
 * string representing the provided number.
 *
 * ex: number='30000.05', lang='en-us' => 30,000.05
 * ex: number='30000.05', lang='ru' => 30 000,05
 * ex: number='30000.05', lang='es' => 30.000,05
*/
export function localizeNumber(number,
  lang = app && app.localSettings && app.localSettings.standardizedTranslatedLang() || 'en-US'
 ) {
  if (typeof number !== 'number') {
    throw new Error('Please provide a number.');
  }

  return new Intl.NumberFormat(lang).format(number);
}

// https://stackoverflow.com/a/10454560
console.log('doc me up - note this only works on string based numbers');
console.log('is this being called anywhere with the expectation it work on number numbrs?');
console.log('can this without too much pain work on numbers?');
export function decimalPlaces(num) {
  // trim trailing zeros
  const trimmed = String(num).replace(/0+$/, '');
  const match = trimmed.match(/(?:\.(\d+))?(?:[eE]([+-]?\d+))?$/);
  if (!match) { return 0; }
  return Math.max(
    0,
    // Number of digits right of decimal point.
    (match[1] ? match[1].length : 0)
    // Adjust for scientific notation.
    - (match[2] ? +match[2] : 0)
 );
}

// todo: doc and put in our style
// todo: doc and put in our style
// todo: doc and put in our style
console.log('todo: doc and put in our style');
console.log('update to expect 3 diff number types and match that on return');
// https://stackoverflow.com/a/7343013/632806
export function preciseRound(value, precision) {
  if (typeof value === 'string') {
    const bigNum = bigNumber(value);

    if (bigNum.isNaN()) {
      throw new Error('The provided string does not evaluate to a valid number.');
    }

    return bigNum
      .decimalPlaces(precision)
      .toString();
  }

  const multiplier = Math.pow(10, precision || 0);
  return Math.round(value * multiplier) / multiplier;
}

const isValidNumberDefaultAllows = {
  allowNumber: true,
  allowBigNumber: true,
  allowString: true,
};

console.log('unit test me silly style');
console.log('doc me up more');
/*
 * Returns true if the provided string is a valid number as determined by
 * bigNumber.js.
 */
export function isValidNumber(num, options = {}) {
  const opts = {
    ...isValidNumberDefaultAllows,
    ...options,
  };

  if (
    !opts.allowNumber &&
    !opts.allowBigNumber &&
    !opts.allowString
  ) {
    throw new Error('At least one of allowNumber, allowBigNumber or allowString must ' +
      'be true.');
  }

  if (
    opts.allowNumber &&
    typeof num === 'number'
  ) {
    return true;
  }

  if (
    opts.allowBigNumber &&
    num instanceof bigNumber
  ) {
    return true;
  }

  if (opts.allowString) {
    const bigNum = bigNumber(num);
    return !bigNum.isNaN();
  }

  return false;
}

console.log('docs me uppers and write unit testy.');
export function validateNumberType(num, options = {}) {
  const opts = {
    fieldName: 'value',
    isValidNumberOpts: {},
    ...options,
  };

  const isValidNumberAllows = {
    ...isValidNumberDefaultAllows,
    ...opts.isValidNumberOpts,
  };

  if (!isValidNumber(num, opts.isValidNumberOpts)) {
    const errStr = `The ${opts.fieldName} must be provided as one of`;
    const allowedTypes = [];

    if (isValidNumberAllows.allowNumber) {
      allowedTypes.push('number');
    }

    if (isValidNumberAllows.allowBigNumber) {
      allowedTypes.push('bigNumber.js instance');
    }

    if (isValidNumberAllows.allowString) {
      allowedTypes.push('string based representation of a number');
    }

    throw new Error(`${errStr}: ${allowedTypes.join(', ')}`);
  }
}
<commit_msg>documentation for the number module<commit_after>import bigNumber from 'bignumber.js';
import app from '../app';

/*
 * Will return a string representation of a number ensuring that standard
 * notation is used (as opposed to the default JS representation which uses
 * scientific notation for small numbers, e.g. 0.00000001 => 1E-8).
 */
export function toStandardNotation(number, options) {
  const opts = {
    returnUnchangedOnError: true,
    ...options,
  };

  try {
    const bigNum = bigNumber(number);

    if (bigNum.isNaN()) {
      throw new Error(`${number} is not a valid number.`);
    }

    return (
      bigNum.toFormat({
        ...bigNumber.config().FORMAT,
        groupSeparator: '',
        fractionGroupSeparator: '',
      })
    );
  } catch (e) {
    if (opts.returnUnchangedOnError) return number;
    throw e;
  }
}

/*
 * Like Number.toFixed, but whereas that will force your number
 * to have the specified number of decimal places, this will not
 * allow you to have more than the specified number, but will allow
 * less, e.g:
 *
 * (18.986).toFixed(2) // 18.99
 * upToFixed(18.986,2) // 18.99
 * (18.986).toFixed(4) // 18.9860
 * upToFixed(18.986, 4) // 18.986
 */
export function upToFixed(number, decPlaces) {
  if (typeof number !== 'number') {
    throw new Error('Please provide a number.');
  }

  return toStandardNotation(
    parseFloat((number).toFixed(decPlaces))
  ).toString();
}

/*
 * Returns a random integer from min to (min + range).
 *
 * https://stackoverflow.com/questions/13997793/generate-random-number-between-2-numbers
 */
export function randomInt(min = 0, range = 0) {
  return Math.floor((Math.random() * (range + 1)) + min);
}

/*
 * Uses local setting's language or provided optional language to return a localized
 * string representing the provided number.
 *
 * ex: number='30000.05', lang='en-us' => 30,000.05
 * ex: number='30000.05', lang='ru' => 30 000,05
 * ex: number='30000.05', lang='es' => 30.000,05
*/
export function localizeNumber(number,
  lang = app && app.localSettings && app.localSettings.standardizedTranslatedLang() || 'en-US'
 ) {
  if (typeof number !== 'number') {
    throw new Error('Please provide a number.');
  }

  return new Intl.NumberFormat(lang).format(number);
}

/*
 * Returns the number of decimal places in a number (trailing 0's don't count).
 * @param {number|string|BigNumber} num
 * @returns {number} - The number of significant decimal places.
 */
// https://stackoverflow.com/a/10454560
export function decimalPlaces(num) {
  // trim trailing zeros
  const trimmed = String(num).replace(/0+$/, '');
  const match = trimmed.match(/(?:\.(\d+))?(?:[eE]([+-]?\d+))?$/);
  if (!match) { return 0; }
  return Math.max(
    0,
    // Number of digits right of decimal point.
    (match[1] ? match[1].length : 0)
    // Adjust for scientific notation.
    - (match[2] ? +match[2] : 0)
 );
}

/*
 * Rounds the value to the given number of decimal places.
 * @param {number|string|BigNumber} value
 * @param {number} precision - The desired number of decimal places.
 * @returns {number|string|BigNumber} - The rounded number with the return type
 *   matching the type of the provided value.
 */
// https://stackoverflow.com/a/7343013/632806
export function preciseRound(value, precision) {
  const bigNum = bigNumber(value);

  const rounded = bigNum
    .decimalPlaces(precision);

  if (typeof value === 'number') {
    return rounded.toNumber();
  } else if (typeof value === 'string') {
    return rounded.toString();
  }

  return rounded;
}

const isValidNumberDefaultAllows = {
  allowNumber: true,
  allowBigNumber: true,
  allowString: true,
};

/*
 * Returns true if the provided number is a valid number type.
 * @param {number|string|BigNumber} num
 * @param {object} options
 * @param {boolean} [options.allowNumber = true] - If the num is the JS "number"
 *   type, it will be considered valid. NaN is considered valid.
 * @param {boolean} [options.allowBigNumber = true] - If the num is a BigNumber
 *   instance, it will be considered valid (even if the instance evaluates to NaN).
 * @param {boolean} [options.allowString = true] - If the num is a valid string based
 *   number (as detrmined by BigNumber), it will be considered valid. If the BigNumber
 *   evaluates the string to NaN, it will be considered invalid.
 * @returns {boolean} - Boolean indicating whether num is a valid number.
 */
export function isValidNumber(num, options = {}) {
  const opts = {
    ...isValidNumberDefaultAllows,
    ...options,
  };

  if (
    !opts.allowNumber &&
    !opts.allowBigNumber &&
    !opts.allowString
  ) {
    throw new Error('At least one of allowNumber, allowBigNumber or allowString must ' +
      'be true.');
  }

  if (
    opts.allowNumber &&
    typeof num === 'number'
  ) {
    return true;
  }

  if (
    opts.allowBigNumber &&
    num instanceof bigNumber
  ) {
    return true;
  }

  if (opts.allowString) {
    const bigNum = bigNumber(num);
    return !bigNum.isNaN();
  }

  return false;
}

/*
 * Uses isValidNumber() to determine whether the provided num is a valid number. If it's
 * not, a descriptive exception will be thrown.
 * @param {number|string|BigNumber} num
 * @param {object} options
 * @param {string} [options.fieldName = 'value'] - The name of the variable you are trying
 *   to validate. The name will be used in the exception string.
 * @param {object} [options.isValidNumberOpts = {}] - The options to be passed to
 *   isValidNumber().
 */
export function validateNumberType(num, options = {}) {
  const opts = {
    fieldName: 'value',
    isValidNumberOpts: {},
    ...options,
  };

  const isValidNumberAllows = {
    ...isValidNumberDefaultAllows,
    ...opts.isValidNumberOpts,
  };

  if (!isValidNumber(num, opts.isValidNumberOpts)) {
    const errStr = `The ${opts.fieldName} must be provided as one of`;
    const allowedTypes = [];

    if (isValidNumberAllows.allowNumber) {
      allowedTypes.push('number');
    }

    if (isValidNumberAllows.allowBigNumber) {
      allowedTypes.push('bigNumber.js instance');
    }

    if (isValidNumberAllows.allowString) {
      allowedTypes.push('string based representation of a number');
    }

    throw new Error(`${errStr}: ${allowedTypes.join(', ')}`);
  }
}
<|endoftext|>


<commit_before>const ColorPickerView = require("./view/color-picker-view");
const LineWidthPickerView = require("./view/line-width-picker-view");
const MenuView = require("./view/menu-view");
const SequenceView = require("./view/sequence-view");
const eventPublisher = require("./publisher");

function ViewManager() {
  this.colorPicker = new ColorPickerView(document.getElementById("menu-colors"));
  eventPublisher.subscribe("defaultPalleteColors", (colors) => {
    this.colorPicker.clearPalette();
    colors.forEach(color => {
      this.colorPicker.addPalette(color);
    });
  });

  this.lineWidthPicker = new LineWidthPickerView(
      document.getElementById("menu-line-width"));

  this.sequence = new SequenceView(document.getElementById("thumbnails"));

  this.menu = new MenuView();
}

module.exports = ViewManager;
<commit_msg>this.sequence -> this.sequenceEditor<commit_after>const ColorPickerView = require("./view/color-picker-view");
const LineWidthPickerView = require("./view/line-width-picker-view");
const MenuView = require("./view/menu-view");
const SequenceView = require("./view/sequence-view");
const eventPublisher = require("./publisher");

function ViewManager() {
  this.colorPicker = new ColorPickerView(document.getElementById("menu-colors"));
  eventPublisher.subscribe("defaultPalleteColors", (colors) => {
    this.colorPicker.clearPalette();
    colors.forEach(color => {
      this.colorPicker.addPalette(color);
    });
  });

  this.lineWidthPicker = new LineWidthPickerView(
      document.getElementById("menu-line-width"));

  this.sequenceEditor = new SequenceView(document.getElementById("thumbnails"));

  this.menu = new MenuView();
}

module.exports = ViewManager;
<|endoftext|>


<commit_before>var Manual =  BAC.extend({
	el: '#load',

	events:{
		'click #manualBac': 'addBACLevel'
	},

	initialize: function(){
		this.user = new You();
		this.you = this.user.retrieveProfile();
		this.form = '
Total Alcohol Content Volume<input id="acv" name="acv" class="form-control" placeholder="0" required>

Total Ounzes<input id="oz" name="oz" class="form-control" placeholder="0" required>

Total Time<input id="time" min="0" max="24" class="form-control" required>

<button id="manualBac" class="btn btn-success">Calculate</button>
 Your BAC Level is
';

		//this.manualBac = new BAC();
		//this.render();
	},

	render: function(){
		this.$el.html(this.form);
	},

	/*calcBac: function(){
		var acv = $('#acv').val();
		var oz = $('#oz').val();
		var time = $('#time').val();
		var bac = this.calcBac(acv, oz, this.you.weight, this.you.genderBac, time);
		$('#BACLevel').empty();
		$('#BACLevel').append('
'+bac+'
');
		return bac;
	},*/

	getTime: function(){
		var date = new Date();
		var time = {
			hour: 0,
			min: 0
		};
		time.hour = date.getHours();
		time.min = date.getMinutes();

		var curTime = time.hour+':'+time.min;
		return curTime;
	},

	getDate: function(){
		var date2 = new Date();
		var date = {
			year: 0,
			month: 0,
			day:0
		};

		date.year = date2.getFullYear();
		date.month = date2.getMonth()+1;
		date.day = date2.getDate();

		var today = date.year+'-'+date.month+'-'+date.day;

		return today;
	},

	addBACLevel: function(){
		console.log(this.you);
		//var bac = this.calcBac();
		var acv = $('#acv').val();
		var oz = $('#oz').val();
		var time = $('#time').val();
		var bac = this.calcBac(acv, oz, this.you.weight, this.you.genderBac, time);
		var time = this.getTime();
		var date = this.getDate();
		area.getCurrentLocation();

		var newBac = new PersonBAC({
			"date": date,
			"bacLevelHigh": bac,
			"bacLevelCur": 0,
			"lat": window.lat,
			"lng": window.lng,
			"timeStart": time,
			"timeFinish": time,
			"manualEnter": true
		});

		this.you.BACLevels.push(newBac);
		people.localStorage.update(this.you);

		console.log(newBac);
	}
})<commit_msg>Re added numerical value<commit_after>var Manual =  BAC.extend({
	el: '#load',

	events:{
		'click #manualBac': 'addBACLevel'
	},

	initialize: function(){
		this.user = new You();
		this.you = this.user.retrieveProfile();
		this.form = '
Total Alcohol Content Volume<input type="number" step="any" id="acv" name="acv" class="form-control" placeholder="0" required>

Total Ounzes<input type="number" step="any" id="oz" name="oz" class="form-control" placeholder="0" required>

Total Time<input id="time" min="0" max="24" class="form-control" required>

<button id="manualBac" class="btn btn-success">Calculate</button>
 Your BAC Level is
';

		//this.manualBac = new BAC();
		//this.render();
	},

	render: function(){
		this.$el.html(this.form);
	},

	/*calcBac: function(){
		var acv = $('#acv').val();
		var oz = $('#oz').val();
		var time = $('#time').val();
		var bac = this.calcBac(acv, oz, this.you.weight, this.you.genderBac, time);
		$('#BACLevel').empty();
		$('#BACLevel').append('
'+bac+'
');
		return bac;
	},*/

	getTime: function(){
		var date = new Date();
		var time = {
			hour: 0,
			min: 0
		};
		time.hour = date.getHours();
		time.min = date.getMinutes();

		var curTime = time.hour+':'+time.min;
		return curTime;
	},

	getDate: function(){
		var date2 = new Date();
		var date = {
			year: 0,
			month: 0,
			day:0
		};

		date.year = date2.getFullYear();
		date.month = date2.getMonth()+1;
		date.day = date2.getDate();

		var today = date.year+'-'+date.month+'-'+date.day;

		return today;
	},

	addBACLevel: function(){
		console.log(this.you);
		//var bac = this.calcBac();
		var acv = $('#acv').val();
		var oz = $('#oz').val();
		var time = $('#time').val();
		var bac = this.calcBac(acv, oz, this.you.weight, this.you.genderBac, time);
		var time = this.getTime();
		var date = this.getDate();
		area.getCurrentLocation();

		var newBac = new PersonBAC({
			"date": date,
			"bacLevelHigh": bac,
			"bacLevelCur": 0,
			"lat": window.lat,
			"lng": window.lng,
			"timeStart": time,
			"timeFinish": time,
			"manualEnter": true
		});

		this.you.BACLevels.push(newBac);
		people.localStorage.update(this.you);

		console.log(newBac);
	}
})<|endoftext|>


<commit_before>import clonedeep from 'lodash.clonedeep';
import moment from 'moment';
import alertify from 'alertifyjs';
import $ from 'jquery';
import cookie from 'react-cookie';
import Promise from 'es6-promise';

export const LANGUAGE_COOKIE_NAME = 'django_language';

export var assign = require('object-assign');

alertify.defaults.notifier.delay = 10;
alertify.defaults.notifier.position = 'bottom-left';
alertify.defaults.notifier.closeButton = true;

export function notify(msg, atype='success') {
  alertify.notify(msg, atype);
}

export function formatTime(timeStr) {
  var _m = moment(timeStr);
  return _m.calendar(null, {sameElse: 'LL'});
}

export function formatTimeDate(timeStr) {
  var _m = moment(timeStr);
  return _m.format('LLL');
}

export function formatDate(timeStr) {
  var _m = moment(timeStr);
  return _m.format('ll');
}

export var anonUsername = 'AnonymousUser';
export function getAnonymousUserPermission(permissions) {
  return permissions.filter(function(perm){
    if (perm.user__username === undefined) {
      perm.user__username = perm.user.match(/\/users\/(.*)\//)[1];
    }
    return perm.user__username === anonUsername;
  })[0];
}

export function surveyToValidJson(survey) {
  // skip logic references only preserved after initial call
  // to "survey.toFlatJSON()"
  survey.toFlatJSON();
  // returning the result of the second call to "toFlatJSON()"
  return JSON.stringify(survey.toFlatJSON());
}

// TRANSLATIONS HACK (Part 2/2):
// this function reverses nullifying default language - use it just before saving
export function unnullifyTranslations(surveyDataJSON, assetContent) {
  let surveyData = JSON.parse(surveyDataJSON);

  let translatedProps = [];
  if (assetContent.translated) {
     translatedProps = assetContent.translated;
  }

  // set default_language
  let defaultLang = assetContent.translations_0;
  if (!defaultLang) {
    defaultLang = null;
  }
  if (!surveyData.settings[0].default_language && typeof defaultLang !== 'undefined') {
    surveyData.settings[0].default_language = defaultLang;
  }

  // replace every "translatedProp" with "translatedProp::defaultLang"
  if (surveyData.choices) {
    surveyData.choices.forEach((choice) => {
      translatedProps.forEach((translatedProp) => {
        if (typeof choice[translatedProp] !== 'undefined') {
          choice[`${translatedProp}::${defaultLang}`] = choice[translatedProp]
          delete choice[translatedProp];
        }
      });
    });
  }
  if (surveyData.survey) {
    surveyData.survey.forEach((surveyRow) => {
      translatedProps.forEach((translatedProp) => {
        if (typeof surveyRow[translatedProp] !== 'undefined') {
          surveyRow[`${translatedProp}::${defaultLang}`] = surveyRow[translatedProp]
          delete surveyRow[translatedProp];
        }
      });
    });
  }

  return JSON.stringify(surveyData);
}

export function nullifyTranslations(translations, translatedProps, survey, baseSurvey) {
  const data = {
    survey: clonedeep(survey),
    translations: clonedeep(translations)
  };

  if (typeof translations === 'undefined') {
    data.translations = [null];
    return data;
  }

  if (data.translations.length > 1 && data.translations.indexOf(null) !== -1) {
    throw new Error('There is an unnamed translation in your form definition.\nPlease give a name to all translations in your form.\nUse "Manage Translations" option from form landing page.');
  }

  /*
  TRANSLATIONS HACK (Part 1/2):
  all the coffee code assumes first language to be null, and we don't want
  to introduce potential code-breaking refactor in old code, so we store
  first language, then replace with null and reverse this just before saving
  NOTE: when importing assets from Library into form, we need to make sure
  the default language is the same (or force baseSurvey default language)
  */
  if (baseSurvey) {
    const formDefaultLang = baseSurvey._initialParams.translations_0 || null;
    if (data.translations[0] === formDefaultLang) {
      // case 1: nothing to do - same default language in both
    } else if (data.translations.includes(formDefaultLang)) {
      // case 2: imported asset has form default language but not as first, so
      // we need to reorder things
      const defaultLangIndex = data.translations.indexOf(formDefaultLang);
      data.translations.unshift(data.translations.pop(defaultLangIndex));
      data.survey.forEach((row) => {
        translatedProps.forEach((translatedProp) => {
          const transletedPropArr = row[translatedProp];
          if (transletedPropArr) {
            transletedPropArr.unshift(transletedPropArr.pop(defaultLangIndex));
          }
        });
      });
    }

    if (!data.translations.includes(formDefaultLang)) {
      // case 3: imported asset doesn't have form default language, so we
      // force it onto the asset as the first language and try setting some
      // meaningful property value
      data.translations.unshift(formDefaultLang);
      data.survey.forEach((row) => {
        translatedProps.forEach((translatedProp) => {
          if (row[translatedProp]) {
            let propVal = null
            if (row.name) {
              propVal = row.name;
            } else if (row.$autoname) {
              propVal = row.$autoname;
            }
            row[translatedProp].unshift(propVal);
          }
        });
      });
    }
  }

  // no need to nullify null
  if (data.translations[0] !== null) {
    data.translations_0 = data.translations[0]
    data.translations[0] = null
  }

  return data;
}

export function redirectTo(href) {
  window.location.href = href;
}

export function parsePermissions(owner, permissions) {
  var users = [];
  var perms = {};
  if (!permissions) {
    return [];
  }
  permissions.map((perm) => {
    perm.user__username = perm.user.match(/\/users\/(.*)\//)[1];
    return perm;
  }).filter((perm)=> {
    return ( perm.user__username !== owner && perm.user__username !== anonUsername);
  }).forEach((perm)=> {
    if(users.indexOf(perm.user__username) === -1) {
      users.push(perm.user__username);
      perms[perm.user__username] = [];
    }
    perms[perm.user__username].push(perm);
  });
  return users.map((username)=>{
    return {
      username: username,
      can: perms[username].reduce((cans, perm)=> {
        var permCode = perm.permission.includes('_submissions') ? perm.permission : perm.permission.split('_')[0];
        cans[permCode] = perm;
        return cans;
      }, {})
    };
  });
}

export var log = (function(){
  var _log = function(...args) {
    console.log.apply(console, args);
    return args[0];
  };
  _log.profileSeconds = function(n=1) {
    console.profile();
    window.setTimeout(function(){
      console.profileEnd();
    }, n * 1000);
  };
  return _log;
})();
window.log = log;

var __strings = [];

/*global gettext*/
if (window.gettext) {
  var _gettext = window.gettext;
} else {
  var _gettext = function(s){
    return s;
  };
}
export function t(str) {
  return _gettext(str);
};

const originalSupportEmail = 'help@kobotoolbox.org';

// use this utility function to replace hardcoded email in transifex translations
export function replaceSupportEmail(str) {
  if (typeof supportDetails !== 'undefined') {
    return str.replace(originalSupportEmail, supportDetails.email);
  } else {
    return str;
  }
}

export function currentLang() {
  return cookie.load(LANGUAGE_COOKIE_NAME) || 'en';
}

// langString contains name and code e.g. "English (en)"
export function getLangAsObject(langString) {
  const openingIndex = langString.indexOf('(');
  const closingIndex = langString.indexOf(')');

  const langCode = langString.substring(openingIndex + 1, closingIndex);

  const langName = langString.substring(0, openingIndex).trim();

  if (
    langCode &&
    langName &&
    // make sure langString contains just name and bracket-wrapped code
    langName.length + langCode.length + 3 === langString.length
  ) {
    return {
      code: langCode,
      name: langName
    };
  } else {
    return undefined;
  }
}

export function getLangString(obj) {
  if (typeof obj === 'object' && obj.name && obj.code) {
    return `${obj.name} (${obj.code})`;
  } else {
    return undefined;
  }
}

log.t = function () {
  let _t = {};
  __strings.forEach(function(str){ _t[str] = str; })
  console.log(JSON.stringify(_t, null, 4));
};

// unique id for forms with inputs and labels
let lastId = 0;
export var newId = function(prefix='id') {
  lastId++;
  return `${prefix}${lastId}`;
};

export var randString = function () {
  return Math.random().toString(36).match(/\.(\S{6}).*/)[1];
};

export function stringToColor(str, prc) {
  // Higher prc = lighter color, lower = darker
  var prc = typeof prc === 'number' ? prc : -15;
  var hash = function(word) {
      var h = 0;
      for (var i = 0; i < word.length; i++) {
          h = word.charCodeAt(i) + ((h << 5) - h);
      }
      return h;
  };
  var shade = function(color, prc) {
      var num = parseInt(color, 16),
          amt = Math.round(2.55 * prc),
          R = (num >> 16) + amt,
          G = (num >> 8 & 0x00FF) + amt,
          B = (num & 0x0000FF) + amt;
      return (0x1000000 + (R < 255 ? R < 1 ? 0 : R : 255) * 0x10000 +
          (G < 255 ? G < 1 ? 0 : G : 255) * 0x100 +
          (B < 255 ? B < 1 ? 0 : B : 255))
          .toString(16)
          .slice(1);
  };
  var int_to_rgba = function(i) {
      var color = ((i >> 24) & 0xFF).toString(16) +
          ((i >> 16) & 0xFF).toString(16) +
          ((i >> 8) & 0xFF).toString(16) +
          (i & 0xFF).toString(16);
      return color;
  };
  return shade(int_to_rgba(hash(str)), prc);
}

export function isAValidUrl(url) {
  try {
    new URL(url);
    return true;
  } catch(e) {
    return false;
  }
}

export function checkLatLng(geolocation) {
  if (geolocation && geolocation[0] && geolocation[1]) return true;
  else return false;
}

export function validFileTypes() {
  const VALID_ASSET_UPLOAD_FILE_TYPES = [
    '.xls',
    '.xlsx',
    'application/xls',
    'application/vnd.ms-excel',
    'application/octet-stream',
    'application/vnd.openxmlformats',
    'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
    '' // Keep this to fix issue with IE Edge sending an empty MIME type
  ];
  return VALID_ASSET_UPLOAD_FILE_TYPES.join(',');
}

export function koboMatrixParser(params) {
  if (params.content)
    var content = JSON.parse(params.content);
  if (params.source)
    var content = JSON.parse(params.source);

  if (!content.survey)
    return params;

  var hasMatrix = false;
  var surveyLength = content.survey.length;

  // add open/close tags for kobomatrix groups
  for (var i = 0; i < surveyLength; i++) {
    if (content.survey[i].type === 'kobomatrix') {
      content.survey[i].type = 'begin_kobomatrix';
      content.survey[i].appearance = 'field-list';
      surveyLength++;
      content.survey.splice(i + 1, 0, {type: 'end_kobomatrix', '$kuid': `/${content.survey[i].$kuid}`});
    }
  }

  // add columns as items in the group
  for (i = 0; i < surveyLength; i++) {
    if (content.survey[i].type === 'begin_kobomatrix') {
      var j = i;
      hasMatrix = true;
      var matrix = localStorage.getItem(`koboMatrix.${content.survey[i].$kuid}`);

      if (matrix != null) {
        matrix = JSON.parse(matrix);
        for (var kuid of matrix.cols) {
          i++;
          surveyLength++;
          content.survey.splice(i, 0, matrix[kuid]);
        }

        for (var k of Object.keys(matrix.choices)) {
          content.choices.push(matrix.choices[k]);
        }
      }
      // TODO: handle corrupt matrix data
    }
  }

  if (hasMatrix) {
    if (params.content)
      params.content = JSON.stringify(content);
    if (params.source)
      params.source = JSON.stringify(content);
  }
  return params;
}
<commit_msg>fix code<commit_after>import clonedeep from 'lodash.clonedeep';
import moment from 'moment';
import alertify from 'alertifyjs';
import $ from 'jquery';
import cookie from 'react-cookie';
import Promise from 'es6-promise';

export const LANGUAGE_COOKIE_NAME = 'django_language';

export var assign = require('object-assign');

alertify.defaults.notifier.delay = 10;
alertify.defaults.notifier.position = 'bottom-left';
alertify.defaults.notifier.closeButton = true;

export function notify(msg, atype='success') {
  alertify.notify(msg, atype);
}

export function formatTime(timeStr) {
  var _m = moment(timeStr);
  return _m.calendar(null, {sameElse: 'LL'});
}

export function formatTimeDate(timeStr) {
  var _m = moment(timeStr);
  return _m.format('LLL');
}

export function formatDate(timeStr) {
  var _m = moment(timeStr);
  return _m.format('ll');
}

export var anonUsername = 'AnonymousUser';
export function getAnonymousUserPermission(permissions) {
  return permissions.filter(function(perm){
    if (perm.user__username === undefined) {
      perm.user__username = perm.user.match(/\/users\/(.*)\//)[1];
    }
    return perm.user__username === anonUsername;
  })[0];
}

export function surveyToValidJson(survey) {
  // skip logic references only preserved after initial call
  // to "survey.toFlatJSON()"
  survey.toFlatJSON();
  // returning the result of the second call to "toFlatJSON()"
  return JSON.stringify(survey.toFlatJSON());
}

// TRANSLATIONS HACK (Part 2/2):
// this function reverses nullifying default language - use it just before saving
export function unnullifyTranslations(surveyDataJSON, assetContent) {
  let surveyData = JSON.parse(surveyDataJSON);

  let translatedProps = [];
  if (assetContent.translated) {
     translatedProps = assetContent.translated;
  }

  // set default_language
  let defaultLang = assetContent.translations_0;
  if (!defaultLang) {
    defaultLang = null;
  }
  if (!surveyData.settings[0].default_language && defaultLang !== null) {
    surveyData.settings[0].default_language = defaultLang;
  }

  if (defaultLang !== null) {
    // replace every "translatedProp" with "translatedProp::defaultLang"
    if (surveyData.choices) {
      surveyData.choices.forEach((choice) => {
        translatedProps.forEach((translatedProp) => {
          if (typeof choice[translatedProp] !== 'undefined') {
            choice[`${translatedProp}::${defaultLang}`] = choice[translatedProp]
            delete choice[translatedProp];
          }
        });
      });
    }
    if (surveyData.survey) {
      surveyData.survey.forEach((surveyRow) => {
        translatedProps.forEach((translatedProp) => {
          if (typeof surveyRow[translatedProp] !== 'undefined') {
            surveyRow[`${translatedProp}::${defaultLang}`] = surveyRow[translatedProp]
            delete surveyRow[translatedProp];
          }
        });
      });
    }
  }

  return JSON.stringify(surveyData);
}

export function nullifyTranslations(translations, translatedProps, survey, baseSurvey) {
  const data = {
    survey: clonedeep(survey),
    translations: clonedeep(translations)
  };

  if (typeof translations === 'undefined') {
    data.translations = [null];
    return data;
  }

  if (data.translations.length > 1 && data.translations.indexOf(null) !== -1) {
    throw new Error('There is an unnamed translation in your form definition.\nPlease give a name to all translations in your form.\nUse "Manage Translations" option from form landing page.');
  }

  /*
  TRANSLATIONS HACK (Part 1/2):
  all the coffee code assumes first language to be null, and we don't want
  to introduce potential code-breaking refactor in old code, so we store
  first language, then replace with null and reverse this just before saving
  NOTE: when importing assets from Library into form, we need to make sure
  the default language is the same (or force baseSurvey default language)
  */
  if (baseSurvey) {
    const formDefaultLang = baseSurvey._initialParams.translations_0 || null;
    if (data.translations[0] === formDefaultLang) {
      // case 1: nothing to do - same default language in both
    } else if (data.translations.includes(formDefaultLang)) {
      // case 2: imported asset has form default language but not as first, so
      // we need to reorder things
      const defaultLangIndex = data.translations.indexOf(formDefaultLang);
      data.translations.unshift(data.translations.pop(defaultLangIndex));
      data.survey.forEach((row) => {
        translatedProps.forEach((translatedProp) => {
          const transletedPropArr = row[translatedProp];
          if (transletedPropArr) {
            transletedPropArr.unshift(transletedPropArr.pop(defaultLangIndex));
          }
        });
      });
    }

    if (!data.translations.includes(formDefaultLang)) {
      // case 3: imported asset doesn't have form default language, so we
      // force it onto the asset as the first language and try setting some
      // meaningful property value
      data.translations.unshift(formDefaultLang);
      data.survey.forEach((row) => {
        translatedProps.forEach((translatedProp) => {
          if (row[translatedProp]) {
            let propVal = null
            if (row.name) {
              propVal = row.name;
            } else if (row.$autoname) {
              propVal = row.$autoname;
            }
            row[translatedProp].unshift(propVal);
          }
        });
      });
    }
  }

  // no need to nullify null
  if (data.translations[0] !== null) {
    data.translations_0 = data.translations[0]
    data.translations[0] = null
  }

  return data;
}

export function redirectTo(href) {
  window.location.href = href;
}

export function parsePermissions(owner, permissions) {
  var users = [];
  var perms = {};
  if (!permissions) {
    return [];
  }
  permissions.map((perm) => {
    perm.user__username = perm.user.match(/\/users\/(.*)\//)[1];
    return perm;
  }).filter((perm)=> {
    return ( perm.user__username !== owner && perm.user__username !== anonUsername);
  }).forEach((perm)=> {
    if(users.indexOf(perm.user__username) === -1) {
      users.push(perm.user__username);
      perms[perm.user__username] = [];
    }
    perms[perm.user__username].push(perm);
  });
  return users.map((username)=>{
    return {
      username: username,
      can: perms[username].reduce((cans, perm)=> {
        var permCode = perm.permission.includes('_submissions') ? perm.permission : perm.permission.split('_')[0];
        cans[permCode] = perm;
        return cans;
      }, {})
    };
  });
}

export var log = (function(){
  var _log = function(...args) {
    console.log.apply(console, args);
    return args[0];
  };
  _log.profileSeconds = function(n=1) {
    console.profile();
    window.setTimeout(function(){
      console.profileEnd();
    }, n * 1000);
  };
  return _log;
})();
window.log = log;

var __strings = [];

/*global gettext*/
if (window.gettext) {
  var _gettext = window.gettext;
} else {
  var _gettext = function(s){
    return s;
  };
}
export function t(str) {
  return _gettext(str);
};

const originalSupportEmail = 'help@kobotoolbox.org';

// use this utility function to replace hardcoded email in transifex translations
export function replaceSupportEmail(str) {
  if (typeof supportDetails !== 'undefined') {
    return str.replace(originalSupportEmail, supportDetails.email);
  } else {
    return str;
  }
}

export function currentLang() {
  return cookie.load(LANGUAGE_COOKIE_NAME) || 'en';
}

// langString contains name and code e.g. "English (en)"
export function getLangAsObject(langString) {
  const openingIndex = langString.indexOf('(');
  const closingIndex = langString.indexOf(')');

  const langCode = langString.substring(openingIndex + 1, closingIndex);

  const langName = langString.substring(0, openingIndex).trim();

  if (
    langCode &&
    langName &&
    // make sure langString contains just name and bracket-wrapped code
    langName.length + langCode.length + 3 === langString.length
  ) {
    return {
      code: langCode,
      name: langName
    };
  } else {
    return undefined;
  }
}

export function getLangString(obj) {
  if (typeof obj === 'object' && obj.name && obj.code) {
    return `${obj.name} (${obj.code})`;
  } else {
    return undefined;
  }
}

log.t = function () {
  let _t = {};
  __strings.forEach(function(str){ _t[str] = str; })
  console.log(JSON.stringify(_t, null, 4));
};

// unique id for forms with inputs and labels
let lastId = 0;
export var newId = function(prefix='id') {
  lastId++;
  return `${prefix}${lastId}`;
};

export var randString = function () {
  return Math.random().toString(36).match(/\.(\S{6}).*/)[1];
};

export function stringToColor(str, prc) {
  // Higher prc = lighter color, lower = darker
  var prc = typeof prc === 'number' ? prc : -15;
  var hash = function(word) {
      var h = 0;
      for (var i = 0; i < word.length; i++) {
          h = word.charCodeAt(i) + ((h << 5) - h);
      }
      return h;
  };
  var shade = function(color, prc) {
      var num = parseInt(color, 16),
          amt = Math.round(2.55 * prc),
          R = (num >> 16) + amt,
          G = (num >> 8 & 0x00FF) + amt,
          B = (num & 0x0000FF) + amt;
      return (0x1000000 + (R < 255 ? R < 1 ? 0 : R : 255) * 0x10000 +
          (G < 255 ? G < 1 ? 0 : G : 255) * 0x100 +
          (B < 255 ? B < 1 ? 0 : B : 255))
          .toString(16)
          .slice(1);
  };
  var int_to_rgba = function(i) {
      var color = ((i >> 24) & 0xFF).toString(16) +
          ((i >> 16) & 0xFF).toString(16) +
          ((i >> 8) & 0xFF).toString(16) +
          (i & 0xFF).toString(16);
      return color;
  };
  return shade(int_to_rgba(hash(str)), prc);
}

export function isAValidUrl(url) {
  try {
    new URL(url);
    return true;
  } catch(e) {
    return false;
  }
}

export function checkLatLng(geolocation) {
  if (geolocation && geolocation[0] && geolocation[1]) return true;
  else return false;
}

export function validFileTypes() {
  const VALID_ASSET_UPLOAD_FILE_TYPES = [
    '.xls',
    '.xlsx',
    'application/xls',
    'application/vnd.ms-excel',
    'application/octet-stream',
    'application/vnd.openxmlformats',
    'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
    '' // Keep this to fix issue with IE Edge sending an empty MIME type
  ];
  return VALID_ASSET_UPLOAD_FILE_TYPES.join(',');
}

export function koboMatrixParser(params) {
  if (params.content)
    var content = JSON.parse(params.content);
  if (params.source)
    var content = JSON.parse(params.source);

  if (!content.survey)
    return params;

  var hasMatrix = false;
  var surveyLength = content.survey.length;

  // add open/close tags for kobomatrix groups
  for (var i = 0; i < surveyLength; i++) {
    if (content.survey[i].type === 'kobomatrix') {
      content.survey[i].type = 'begin_kobomatrix';
      content.survey[i].appearance = 'field-list';
      surveyLength++;
      content.survey.splice(i + 1, 0, {type: 'end_kobomatrix', '$kuid': `/${content.survey[i].$kuid}`});
    }
  }

  // add columns as items in the group
  for (i = 0; i < surveyLength; i++) {
    if (content.survey[i].type === 'begin_kobomatrix') {
      var j = i;
      hasMatrix = true;
      var matrix = localStorage.getItem(`koboMatrix.${content.survey[i].$kuid}`);

      if (matrix != null) {
        matrix = JSON.parse(matrix);
        for (var kuid of matrix.cols) {
          i++;
          surveyLength++;
          content.survey.splice(i, 0, matrix[kuid]);
        }

        for (var k of Object.keys(matrix.choices)) {
          content.choices.push(matrix.choices[k]);
        }
      }
      // TODO: handle corrupt matrix data
    }
  }

  if (hasMatrix) {
    if (params.content)
      params.content = JSON.stringify(content);
    if (params.source)
      params.source = JSON.stringify(content);
  }
  return params;
}
<|endoftext|>


<commit_before>var http = require('http')
   ,FlakeId = require('flake-idgen')
   ,restify = require('restify')
   ,levelup = require('levelup')
   ,bunyan = require('bunyan')
   ,format  = require('biguint-format')
   ,Container = require('./Container')
;

var docker = null;

/**
 * The container manager object that contains all routes.
 */
var Server = function() {
  this.configure = this.configure.bind(this);
  this.run = this.run.bind(this);
  this.flakeIdGen = new FlakeId();
  var verb = null;
  for (verb in this.routes) {
    var route = null;
    for (route in this.routes[verb]) {
      this.routes[verb][route] = this.routes[verb][route].bind(this);
    }
  }
}

/**
 * Starts the server.
 */
Server.prototype.run = function(amour, done) {
  var self = this;
  this.server.listen(amour.config.port, function(error) {
    self.log.info('server started and listening on ' + amour.config.port);
    done(error);
  });
}

/**
 * Configures the server.
 */
Server.prototype.configure = function(config, done) {
  var server = restify.createServer({
    name: config.name,
    version: require('../package').version,
  });
  server.use(restify.acceptParser(server.acceptable));
  server.use(restify.queryParser());
  server.use(restify.bodyParser());
  for (var verb in this.routes) {
    for (var route in this.routes[verb]) {
      server[verb](config.prefix + '/' + route, this.routes[verb][route]);
    }
  }
  this.server = server;
  this.log = bunyan.createLogger({name: 'container-manager'});
  // Allow the db to be handed in with the config.
  this.db = config.levelupDB || levelup(config.dataDir, { valueEncoding: 'json' });
  done();
}

/**
 * Define empty route structures so that it is easy to layer them on below.
 */
Server.prototype.routes = {
  get: {},
  post: {},
  put: {},
  del: {},
};

/**
 * Create a new build in a new envrionment.
 */
Server.prototype.routes.post['builds'] = function(req, res) {
  var data = req.body;
  var buildConfig = {
  };
  this.storeBuildData(data, function(error, data) {
    if (error) {
      res.writeHead(400);
      res.write(error.message);
      return;
    }
    var container = new Container(data);
    container.runBuild(function(error, data) {
      if (error) {
        res.writeHead(500);
        // TODO: Send some saner error message?
        res.write(JSON.stringify(error));
      }
      res.send(data);
    });
  });
};

/**
 * List builds on this server.
 */
Server.prototype.routes.get['builds'] = function(req, res) {
  res.writeHead(200);
  res.write("[\n");
  var readStream = this.db.createValueStream({
    gte: 'builds!!',
    lte: 'builds!~',
    valueEncoding: 'json',
  });
  var item = 0;
  readStream.on('data', function(data) {
    if (item > 0) res.write(",\n");
    res.write(JSON.stringify(data) + "\n");
    item++;
  });
  readStream.on('end', function() {
    res.end(']');
  });
};

/**
 * List builds on this server.
 */
Server.prototype.routes.get['builds/:id'] = function(req, res) {
  this.db.get('builds!' + req.params.id, { valueEncoding: 'json' }, function(error, data) {
    if (error) return res.send(404, error);
    res.send(data);
  });
};

/**
 * Delete an individual build.
 */
Server.prototype.routes.del['builds/:id'] = function(req, res) {
  this.log.info('deleting build ' + req.params.id);
  this.db.del('builds!' + req.params.id, function(error) {
    if (error) return res.send(500, error);
    res.send('success');
  });
};

/**
 * Store build data.
 */
Server.prototype.storeBuildData = function(data, done) {
  if (!data.id) {
    data.id = format(this.flakeIdGen.next(), 'hex');
  }
  this.db.put('builds!' + data.id, data, function(error) {
    done(error, data);
  });
};

module.exports = Server;

<commit_msg>updated startbuild endpoint with stubbed code for handling build/project<commit_after>var http = require('http')
   ,FlakeId = require('flake-idgen')
   ,restify = require('restify')
   ,levelup = require('levelup')
   ,bunyan = require('bunyan')
   ,format  = require('biguint-format')
   ,Container = require('./Container')
;

var docker = null;

/**
 * The container manager object that contains all routes.
 */
var Server = function() {
  this.configure = this.configure.bind(this);
  this.run = this.run.bind(this);
  this.flakeIdGen = new FlakeId();
  var verb = null;
  for (verb in this.routes) {
    var route = null;
    for (route in this.routes[verb]) {
      this.routes[verb][route] = this.routes[verb][route].bind(this);
    }
  }
}

/**
 * Starts the server.
 */
Server.prototype.run = function(amour, done) {
  var self = this;
  this.server.listen(amour.config.port, function(error) {
    self.log.info('server started and listening on ' + self.server.url);
    done(error);
  });
}

/**
 * Configures the server.
 */
Server.prototype.configure = function(config, done) {
  var server = restify.createServer({
    name: config.name,
    version: require('../package').version,
  });
  server.use(restify.acceptParser(server.acceptable));
  server.use(restify.queryParser());
  server.use(restify.bodyParser());
  for (var verb in this.routes) {
    for (var route in this.routes[verb]) {
      server[verb](config.prefix + '/' + route, this.routes[verb][route]);
    }
  }
  this.server = server;
  this.log = bunyan.createLogger({name: 'container-manager'});
  // Allow the db to be handed in with the config.
  this.db = config.levelupDB || levelup(config.dataDir, { valueEncoding: 'json' });
  done();
}

/**
 * Define empty route structures so that it is easy to layer them on below.
 */
Server.prototype.routes = {
  get: {},
  post: {},
  put: {},
  del: {},
};

/**
 * Create a new build in a new envrionment.
 */
Server.prototype.routes.post['startbuild'] = function(req, res) {
  var data = req.body, // {build, project}
      build = data.build,
      project = data.project;

  build.project = project;

  this.storeBuildData(build, function(error, data) {
    if (error) {
      res.writeHead(400);
      res.write(error.message);
      return;
    }

    var container_config = {
      // docker, jobConfig, log, containerName, containerId, image, imageConfig, binds, attachLogs
    };

    var container = new Container(container_config);
    container.runBuild(function(error, data) {
      if (error) {
        res.writeHead(500);
        // TODO: Send some saner error message?
        res.write(JSON.stringify(error));
      }
      res.send(data);
    });
  });
};

/**
 * List builds on this server.
 */
Server.prototype.routes.get['builds'] = function(req, res) {
  res.writeHead(200);
  res.write("[\n");
  var readStream = this.db.createValueStream({
    gte: 'builds!!',
    lte: 'builds!~',
    valueEncoding: 'json',
  });
  var item = 0;
  readStream.on('data', function(data) {
    if (item > 0) res.write(",\n");
    res.write(JSON.stringify(data) + "\n");
    item++;
  });
  readStream.on('end', function() {
    res.end(']');
  });
};

/**
 * List builds on this server.
 */
Server.prototype.routes.get['builds/:id'] = function(req, res) {
  this.db.get('builds!' + req.params.id, { valueEncoding: 'json' }, function(error, data) {
    if (error) return res.send(404, error);
    res.send(data);
  });
};

/**
 * Delete an individual build.
 */
Server.prototype.routes.del['builds/:id'] = function(req, res) {
  this.log.info('deleting build ' + req.params.id);
  this.db.del('builds!' + req.params.id, function(error) {
    if (error) return res.send(500, error);
    res.send('success');
  });
};

/**
 * Store build data.
 */
Server.prototype.storeBuildData = function(data, done) {
  if (!data.id) {
    data.id = format(this.flakeIdGen.next(), 'hex');
  }
  this.db.put('builds!' + data.id, data, function(error) {
    done(error, data);
  });
};

module.exports = Server;
<|endoftext|>