<commit_before>'use strict'

var uiUtils = require('../utils/ui')
var units = require('../utils/units')
var $ = uiUtils.jQuery

module.exports = Format

Format.prototype.applyToPlugin = applyToPlugin
Format.prototype.calculateBodyHeight = calculateBodyHeight
Format.prototype.hasHeader = hasHeader
Format.prototype.hasFooter = hasFooter
Format.prototype.hasHeaderBorder = hasHeaderBorder
Format.prototype.hasFooterBorder = hasFooterBorder

Format.defaults = {}
Format.defaults['A4'] = new Format('A4', {
  height: '297mm',
  width: '210mm',
  margins: { top: '10mm', right: '10mm', bottom: '10mm', left: '10mm' },
  header: {
    height: '0',
    // height: 'auto',
    margins: { right: '0', bottom: '10mm', left: '0' },
    border: { color: 'black', style: 'solid', width: '0' }
  },
  footer: {
    height: '0',
    // height: 'auto',
    margins: { right: '0', top: '10mm', left: '0' },
    border: { color: 'black', style: 'solid', width: '0' }
  },
  body: {
    border: { color: 'black', style: 'solid', width: '0' }
  }
})

if (window.env === 'development') {
  Format.defaults['dev-small'] = new Format('dev-small', {
    height: '150mm',
    width: '100mm',
    margins: { top: '10mm', right: '10mm', bottom: '10mm', left: '10mm' },
    header: {
      height: '10mm',
      // height: 'auto',
      margins: { right: '15mm', bottom: '10mm', left: '5mm' },
      border: { color: 'red', style: 'dashed', width: '1mm' }
    },
    footer: {
      height: '10mm',
      // height: 'auto',
      margins: { right: '0', top: '1cm', left: '0' },
      border: { color: 'red', style: 'dashed', width: '1mm' }
    },
    body: {
      border: { color: 'red', style: 'dashed', width: '1mm' }
    }
  })
}

/**
 * @param {string} name
 * @param {object} config
 */
function Format (name, config) {
  this.name = name
  this.orientation = (config.height > config.width) ? 'portrait' : 'paysage'
  this.width = config.width
  this.height = config.height
  this.margins = {
    top: config.margins ? config.margins.top : '0',
    right: config.margins ? config.margins.right : '0',
    bottom: config.margins ? config.margins.bottom : '0',
    left: config.margins ? config.margins.left : '0'
  }
  this.header = {
    height: config.header ? config.header.height : '0',
    margins: {
      right: config.header && config.header.margins ? config.header.margins.right : '0',
      bottom: config.header && config.header.margins ? config.header.margins.bottom : '0',
      left: config.header && config.header.margins ? config.header.margins.left : '0'
    },
    border: {
      color: config.header && config.header.border ? config.header.border.color : 'black',
      style: config.header && config.header.border ? config.header.border.style : 'none',
      width: config.header && config.header.border ? config.header.border.width : '0'
    }
  }
  this.footer = {
    height: config.footer ? config.footer.height : '0',
    margins: {
      top: config.footer && config.footer.margins ? config.footer.margins.top : '0',
      right: config.footer && config.footer.margins ? config.footer.margins.right : '0',
      left: config.footer && config.footer.margins ? config.footer.margins.left : '0'
    },
    border: {
      color: config.footer && config.footer.border ? config.footer.border.color : 'black',
      style: config.footer && config.footer.border ? config.footer.border.style : 'none',
      width: config.footer && config.footer.border ? config.footer.border.width : '0'
    }
  }
  this.body = {
    border: {
      color: config.body && config.body.border ? config.body.border.color : 'black',
      style: config.body && config.body.border ? config.body.border.style : 'none',
      width: config.body && config.body.border ? config.body.border.width : '0'
    }
  }
  this.showAlert = true
}

/**
 * Apply the current format to the DOM and fires the `AppliedToBody` event to
 * permit the main app to bind the format object definition to the document
 * object to be saved with.
 * @method
 * @param {HeadersFooters} plugin The current HeaderFooters plugin instance
 * @fires `HeadersFooters:Format:AppliedToBody`
 * @returns {undefined}
 */
function applyToPlugin (plugin) {
  var that = this
  var editor = plugin.editor

  if (plugin.documentBody) {
    var win = plugin.editor.getWin()
    var body = plugin.documentBody

    plugin = plugin.getMaster() || plugin
    that = plugin.currentFormat

    applyToStackedLayout()
    applyToBody(plugin)

    editor.fire('HeadersFooters:Format:AppliedToBody', {
      documentFormat: that
    })
  }

  function applyToStackedLayout () {
    // var bodyHeight = uiUtils.getElementHeight(body)
    var bodyHeight
    if (plugin.type === 'body') {
      bodyHeight = that.calculateBodyHeight(editor)
    } else {
      bodyHeight = that[plugin.type].height
    }
    var rules = {
      boxSizing: 'border-box',
      height: bodyHeight,
      minHeight: bodyHeight,
      maxHeight: bodyHeight
    }

    plugin.stackedLayout.editarea.css({border: 0})
    plugin.stackedLayout.iframe.css(rules)
  }

  function applyToBody (plugin) {
    // NOTE: set padding to zero to fix unknown bug
    // where all iframe's body paddings are set to '2cm'...
    // TODO: remove this statement if the 2cm padding source is found.
    $(body, win).css({
      margin: 0,
      padding: 0,
      overflow: 'hidden'
    })

    // Allow body panel overflow
    if (plugin.isMaster) {
      $(body, win).css({
        overflowY: 'auto'
      })
    }

    // var bodyHeight = uiUtils.getElementHeight(body, win)
    if (plugin.isMaster) {
      plugin.pageLayout.pageWrapper.css({
        overflow: 'auto', // TODO: update model spec
        background: '#464646',
        // height: 'auto', // TODO: update model spec
        position: 'absolute', // TODO: update model spec
        top: 0, // TODO: update model spec
        right: 0, // TODO: update model spec
        left: 0, // TODO: update model spec
        bottom: 0, // TODO: update model spec
        margin: 0
        // padding: '3cm 0 3cm 0',
        // width: '100%'
      })
      plugin.pageLayout.pagePanel.css({
        overflow: 'hidden', // TODO: update model spec
        background: 'white',
        border: 0,
        boxSizing: 'border-box',
        // minHeight: that.height, // @TODO update for pagination
        height: that.height, // @TODO update for pagination
        margin: '25mm auto 2cm auto',
        paddingTop: that.margins.top,
        paddingRight: that.margins.right,
        paddingBottom: that.margins.bottom,
        paddingLeft: that.margins.left,
        width: that.width
      })
      plugin.pageLayout.headerWrapper.css({
        overflow: 'hidden', // TODO: update model spec
        // border: 0,
        boxSizing: 'border-box',
        height: that.header.height + that.header.margins.bottom,
        margin: 0,
        padding: 0
        // width: '100%' // TODO: update model spec
      })

      /* TODO: split border to top/right/bottom/left */
      var hasHeader = that.hasHeader()
      var headerHasBorder = that.hasHeaderBorder()
      plugin.pageLayout.headerPanel.css({
        overflow: 'hidden', // TODO: update model spec
        borderColor: (hasHeader && !headerHasBorder) ? 'black' : that.header.border.color,
        borderStyle: (hasHeader && !headerHasBorder) ? 'solid' : that.header.border.style,
        borderWidth: (hasHeader && !headerHasBorder) ? '0.1mm' : that.header.border.width,
        boxSizing: 'border-box',
        height: that.header.height,
        marginTop: 0,
        marginRight: that.header.margins.right,
        marginBottom: that.header.margins.bottom,
        marginLeft: that.header.margins.left,
        padding: 0
        // width: '100%' // TODO: update model spec
      })
      if (plugin.pageLayout.headerIframe) {
        plugin.pageLayout.headerIframe.css({
          height: that.header.height,
          minHeight: that.header.height,
          maxHeight: that.header.height
        })
      }

      var bodyHeight = that.calculateBodyHeight(editor)
      // var bodyWrapperHeight = Number(units.getValueFromStyle(bodyHeight)) +
      //   Number(units.getValueFromStyle(that.body.border.width)) * 2
      // bodyWrapperHeight += 'mm'
      plugin.pageLayout.bodyWrapper.css({
        // overflow: 'hidden', // TODO: update model spec
        overflow: 'auto', // TODO: update for pagination
        border: 0,
        boxSizing: 'border-box',
        height: bodyHeight, // bodyWrapperHeight
        margin: 0,
        padding: 0,
        width: '100%'
      })
      plugin.pageLayout.bodyPanel.css({
        // overflow: 'hidden', // TODO: update model spec
        overflow: 'auto', // TODO: update for pagination
        borderColor: that.body.border.color,
        borderStyle: that.body.border.style,
        borderWidth: that.body.border.width,
        boxSizing: 'border-box',
        // minHeight: that.calculateBodyHeight(), // @TODO update for pagination
        height: bodyHeight, // @TODO update for pagination
        margin: 0,
        padding: 0,
        width: '100%'
      })
      plugin.pageLayout.footerWrapper.css({
        overflow: 'hidden', // TODO: update model spec
        border: 0,
        borderTop: 'dashed 1px gray', // TODO update model spec?
        boxSizing: 'border-box',
        height: that.footer.height + that.footer.margins.top,
        margin: 0,
        padding: 0
        // width: '100%' // TODO: update model spec
      })

      var hasFooter = that.hasFooter()
      var footerHasBorder = that.hasFooterBorder()
      /* TODO: split border to top/right/bottom/left */
      plugin.pageLayout.footerPanel.css({
        overflow: 'hidden', // TODO: update model spec
        borderColor: (hasFooter && !footerHasBorder) ? 'black' : that.footer.border.color,
        borderStyle: (hasFooter && !footerHasBorder) ? 'solid' : that.footer.border.style,
        borderWidth: (hasFooter && !footerHasBorder) ? '0.1mm' : that.footer.border.width,
        boxSizing: 'border-box',
        height: that.footer.height,
        marginTop: that.footer.margins.top,
        marginRight: that.footer.margins.right,
        marginBottom: 0,
        marginLeft: that.footer.margins.left,
        padding: 0
        // width: '100%' // TODO: update model spec
      })
      if (plugin.pageLayout.footerIframe) {
        plugin.pageLayout.footerIframe.css({
          height: that.footer.height,
          minHeight: that.footer.height,
          maxHeight: that.footer.height
        })
      }
    }
  }
}

/**
 * Caluculate the document Body height depending the Format propeties
 * @param {Editor}
 * @returns {String} the body height (in mm for now)
 * @fires HeadersFooters:Error:NegativeBodyHeight
 * @TODO support other size units (cm, pt)
 */
function calculateBodyHeight (editor) {
  var that = this
  var ret
  var height = units.getValueFromStyle(this.height)
  var marginTop = units.getValueFromStyle(this.margins.top)
  var marginBottom = units.getValueFromStyle(this.margins.bottom)
  var headerHeight = units.getValueFromStyle(this.header.height)
  var headerMarginBottom = units.getValueFromStyle(this.header.margins.bottom)
  var footerHeight = units.getValueFromStyle(this.footer.height)
  var footerMarginTop = units.getValueFromStyle(this.footer.margins.top)

  var value = height - marginTop - marginBottom -
    headerHeight - headerMarginBottom -
    footerHeight - footerMarginTop

  ret = value + 'mm'
  // console.log('calculateBodyHeight() => ', ret)
  if (value <= 0) {
    if (this.showAlert) {
      var message
      this.showAlert = false
      message = 'Inconsistant Custom Format: « Body height < 0 ». Do you want to fix it ?'

      editor.fire('HeadersFooters:Error:NegativeBodyHeight')
      editor.windowManager.confirm(message, function (conf) {
        if (conf) {
          editor.execCommand('editFormatCmd')
        }
        that.showAlert = true
      })
    }
  }
  return ret
}

function hasHeader () {
  return !this.header || (this.header.height && this.header.height !== '0')
}

function hasFooter () {
  return !this.footer || this.footer.height && this.footer.height !== '0'
}

function hasHeaderBorder () {
  return !this.hasHeader() || this.header.border.width && this.header.border.width !== '0'
}

function hasFooterBorder () {
  return !this.hasFooter() || this.footer.border.width && this.footer.border.width !== '0'
}
<commit_msg>Fix #46: Set a thin gray border to head, body or foot blocks<commit_after>'use strict'

var uiUtils = require('../utils/ui')
var units = require('../utils/units')
var $ = uiUtils.jQuery

module.exports = Format

Format.prototype.applyToPlugin = applyToPlugin
Format.prototype.calculateBodyHeight = calculateBodyHeight
Format.prototype.hasHeader = hasHeader
Format.prototype.hasFooter = hasFooter
Format.prototype.hasHeaderBorder = hasHeaderBorder
Format.prototype.hasFooterBorder = hasFooterBorder

Format.defaults = {}
Format.defaults['A4'] = new Format('A4', {
  height: '297mm',
  width: '210mm',
  margins: { top: '10mm', right: '10mm', bottom: '10mm', left: '10mm' },
  header: {
    height: '0',
    // height: 'auto',
    margins: { right: '0', bottom: '10mm', left: '0' },
    border: { color: 'black', style: 'solid', width: '0' }
  },
  footer: {
    height: '0',
    // height: 'auto',
    margins: { right: '0', top: '10mm', left: '0' },
    border: { color: 'black', style: 'solid', width: '0' }
  },
  body: {
    border: { color: 'black', style: 'solid', width: '0' }
  }
})

if (window.env === 'development') {
  Format.defaults['dev-small'] = new Format('dev-small', {
    height: '150mm',
    width: '100mm',
    margins: { top: '10mm', right: '10mm', bottom: '10mm', left: '10mm' },
    header: {
      height: '10mm',
      // height: 'auto',
      margins: { right: '15mm', bottom: '10mm', left: '5mm' },
      border: { color: 'red', style: 'dashed', width: '1mm' }
    },
    footer: {
      height: '10mm',
      // height: 'auto',
      margins: { right: '0', top: '1cm', left: '0' },
      border: { color: 'red', style: 'dashed', width: '1mm' }
    },
    body: {
      border: { color: 'red', style: 'dashed', width: '1mm' }
    }
  })
}

/**
 * @param {string} name
 * @param {object} config
 */
function Format (name, config) {
  this.name = name
  this.orientation = (config.height > config.width) ? 'portrait' : 'paysage'
  this.width = config.width
  this.height = config.height
  this.margins = {
    top: config.margins ? config.margins.top : '0',
    right: config.margins ? config.margins.right : '0',
    bottom: config.margins ? config.margins.bottom : '0',
    left: config.margins ? config.margins.left : '0'
  }
  this.header = {
    height: config.header ? config.header.height : '0',
    margins: {
      right: config.header && config.header.margins ? config.header.margins.right : '0',
      bottom: config.header && config.header.margins ? config.header.margins.bottom : '0',
      left: config.header && config.header.margins ? config.header.margins.left : '0'
    },
    border: {
      color: config.header && config.header.border ? config.header.border.color : 'black',
      style: config.header && config.header.border ? config.header.border.style : 'none',
      width: config.header && config.header.border ? config.header.border.width : '0'
    }
  }
  this.footer = {
    height: config.footer ? config.footer.height : '0',
    margins: {
      top: config.footer && config.footer.margins ? config.footer.margins.top : '0',
      right: config.footer && config.footer.margins ? config.footer.margins.right : '0',
      left: config.footer && config.footer.margins ? config.footer.margins.left : '0'
    },
    border: {
      color: config.footer && config.footer.border ? config.footer.border.color : 'black',
      style: config.footer && config.footer.border ? config.footer.border.style : 'none',
      width: config.footer && config.footer.border ? config.footer.border.width : '0'
    }
  }
  this.body = {
    border: {
      color: config.body && config.body.border ? config.body.border.color : 'black',
      style: config.body && config.body.border ? config.body.border.style : 'none',
      width: config.body && config.body.border ? config.body.border.width : '0'
    }
  }
  this.showAlert = true
}

/**
 * Apply the current format to the DOM and fires the `AppliedToBody` event to
 * permit the main app to bind the format object definition to the document
 * object to be saved with.
 * @method
 * @param {HeadersFooters} plugin The current HeaderFooters plugin instance
 * @fires `HeadersFooters:Format:AppliedToBody`
 * @returns {undefined}
 */
function applyToPlugin (plugin) {
  var that = this
  var editor = plugin.editor

  if (plugin.documentBody) {
    var win = plugin.editor.getWin()
    var body = plugin.documentBody

    apply()

    if (plugin.getMaster()) {
      plugin = plugin.getMaster()
      apply()
    }
  }

  function apply () {
    that = plugin.currentFormat

    applyToStackedLayout()
    applyToBody(plugin)

    editor.fire('HeadersFooters:Format:AppliedToBody', {
      documentFormat: that
    })
  }

  function applyToStackedLayout () {
    // var bodyHeight = uiUtils.getElementHeight(body)
    var bodyHeight
    if (plugin.type === 'body') {
      bodyHeight = that.calculateBodyHeight(editor)
    } else {
      bodyHeight = that[plugin.type].height
    }
    var rules = {
      boxSizing: 'border-box',
      height: bodyHeight,
      minHeight: bodyHeight,
      maxHeight: bodyHeight
    }

    plugin.stackedLayout.editarea.css({border: 0})
    plugin.stackedLayout.iframe.css(rules)

    setBodyCss(plugin)
  }

  function applyToBody (plugin) {
    // NOTE: set padding to zero to fix unknown bug
    // where all iframe's body paddings are set to '2cm'...
    // TODO: remove this statement if the 2cm padding source is found.
    $(body, win).css({
      margin: 0,
      padding: 0,
      overflow: 'hidden'
    })

    // Allow body panel overflow
    if (plugin.isMaster) {
      $(body, win).css({
        overflowY: 'auto'
      })
    }

    // var bodyHeight = uiUtils.getElementHeight(body, win)
    if (plugin.isMaster) {
      plugin.pageLayout.pageWrapper.css({
        overflow: 'auto', // TODO: update model spec
        background: '#464646',
        // height: 'auto', // TODO: update model spec
        position: 'absolute', // TODO: update model spec
        top: 0, // TODO: update model spec
        right: 0, // TODO: update model spec
        left: 0, // TODO: update model spec
        bottom: 0, // TODO: update model spec
        margin: 0
        // padding: '3cm 0 3cm 0',
        // width: '100%'
      })
      plugin.pageLayout.pagePanel.css({
        overflow: 'hidden', // TODO: update model spec
        background: 'white',
        border: 0,
        boxSizing: 'border-box',
        // minHeight: that.height, // @TODO update for pagination
        height: that.height, // @TODO update for pagination
        margin: '25mm auto 2cm auto',
        paddingTop: that.margins.top,
        paddingRight: that.margins.right,
        paddingBottom: that.margins.bottom,
        paddingLeft: that.margins.left,
        width: that.width
      })
      plugin.pageLayout.headerWrapper.css({
        overflow: 'hidden', // TODO: update model spec
        // border: 0,
        boxSizing: 'border-box',
        height: that.header.height + that.header.margins.bottom,
        margin: 0,
        padding: 0
        // width: '100%' // TODO: update model spec
      })

      /* TODO: split border to top/right/bottom/left */
      var hasHeader = that.hasHeader()
      var headerHasBorder = that.hasHeaderBorder()
      plugin.pageLayout.headerPanel.css({
        overflow: 'hidden', // TODO: update model spec
        borderColor: (hasHeader && !headerHasBorder) ? 'black' : that.header.border.color,
        borderStyle: (hasHeader && !headerHasBorder) ? 'solid' : that.header.border.style,
        borderWidth: (hasHeader && !headerHasBorder) ? '0.1mm' : that.header.border.width,
        boxSizing: 'border-box',
        height: that.header.height,
        marginTop: 0,
        marginRight: that.header.margins.right,
        marginBottom: that.header.margins.bottom,
        marginLeft: that.header.margins.left,
        padding: 0
        // width: '100%' // TODO: update model spec
      })
      if (plugin.pageLayout.headerIframe) {
        plugin.pageLayout.headerIframe.css({
          height: that.header.height,
          minHeight: that.header.height,
          maxHeight: that.header.height
        })
      }

      var bodyHeight = that.calculateBodyHeight(editor)
      // var bodyWrapperHeight = Number(units.getValueFromStyle(bodyHeight)) +
      //   Number(units.getValueFromStyle(that.body.border.width)) * 2
      // bodyWrapperHeight += 'mm'
      plugin.pageLayout.bodyWrapper.css({
        // overflow: 'hidden', // TODO: update model spec
        overflow: 'auto', // TODO: update for pagination
        border: 0,
        boxSizing: 'border-box',
        height: bodyHeight, // bodyWrapperHeight
        margin: 0,
        padding: 0,
        width: '100%'
      })
      plugin.pageLayout.bodyPanel.css({
        // overflow: 'hidden', // TODO: update model spec
        overflow: 'auto', // TODO: update for pagination
        borderColor: that.body.border.color,
        borderStyle: that.body.border.style,
        borderWidth: that.body.border.width,
        boxSizing: 'border-box',
        // minHeight: that.calculateBodyHeight(), // @TODO update for pagination
        height: bodyHeight, // @TODO update for pagination
        margin: 0,
        padding: 0,
        width: '100%'
      })
      plugin.pageLayout.footerWrapper.css({
        overflow: 'hidden', // TODO: update model spec
        border: 0,
        borderTop: 'dashed 1px gray', // TODO update model spec?
        boxSizing: 'border-box',
        height: that.footer.height + that.footer.margins.top,
        margin: 0,
        padding: 0
        // width: '100%' // TODO: update model spec
      })

      var hasFooter = that.hasFooter()
      var footerHasBorder = that.hasFooterBorder()
      /* TODO: split border to top/right/bottom/left */
      plugin.pageLayout.footerPanel.css({
        overflow: 'hidden', // TODO: update model spec
        borderColor: (hasFooter && !footerHasBorder) ? 'black' : that.footer.border.color,
        borderStyle: (hasFooter && !footerHasBorder) ? 'solid' : that.footer.border.style,
        borderWidth: (hasFooter && !footerHasBorder) ? '0.1mm' : that.footer.border.width,
        boxSizing: 'border-box',
        height: that.footer.height,
        marginTop: that.footer.margins.top,
        marginRight: that.footer.margins.right,
        marginBottom: 0,
        marginLeft: that.footer.margins.left,
        padding: 0
        // width: '100%' // TODO: update model spec
      })
      if (plugin.pageLayout.footerIframe) {
        plugin.pageLayout.footerIframe.css({
          height: that.footer.height,
          minHeight: that.footer.height,
          maxHeight: that.footer.height
        })
      }
    }
  }

  /**
   * Set iframes -> <body> CSS style:
   * - overflow-y,
   * - height
   * - border
   * @param {tinymce.Plugin} plugin
   */
  function setBodyCss (plugin) {
    var ctx = plugin.stackedLayout.iframe[0].contentDocument
    var borderWidth = plugin.stackedLayout.root.css('border-width')
    editor.$('body', ctx).css({
      'border': '1px dashed gray'
    })
  }
}

/**
 * Caluculate the document Body height depending the Format propeties
 * @param {Editor}
 * @returns {String} the body height (in mm for now)
 * @fires HeadersFooters:Error:NegativeBodyHeight
 * @TODO support other size units (cm, pt)
 */
function calculateBodyHeight (editor) {
  var that = this
  var ret
  var height = units.getValueFromStyle(this.height)
  var marginTop = units.getValueFromStyle(this.margins.top)
  var marginBottom = units.getValueFromStyle(this.margins.bottom)
  var headerHeight = units.getValueFromStyle(this.header.height)
  var headerMarginBottom = units.getValueFromStyle(this.header.margins.bottom)
  var footerHeight = units.getValueFromStyle(this.footer.height)
  var footerMarginTop = units.getValueFromStyle(this.footer.margins.top)

  var value = height - marginTop - marginBottom -
    headerHeight - headerMarginBottom -
    footerHeight - footerMarginTop

  ret = value + 'mm'
  // console.log('calculateBodyHeight() => ', ret)
  if (value <= 0) {
    if (this.showAlert) {
      var message
      this.showAlert = false
      message = 'Inconsistant Custom Format: « Body height < 0 ». Do you want to fix it ?'

      editor.fire('HeadersFooters:Error:NegativeBodyHeight')
      editor.windowManager.confirm(message, function (conf) {
        if (conf) {
          editor.execCommand('editFormatCmd')
        }
        that.showAlert = true
      })
    }
  }
  return ret
}

function hasHeader () {
  return !this.header || (this.header.height && this.header.height !== '0')
}

function hasFooter () {
  return !this.footer || this.footer.height && this.footer.height !== '0'
}

function hasHeaderBorder () {
  return !this.hasHeader() || this.header.border.width && this.header.border.width !== '0'
}

function hasFooterBorder () {
  return !this.hasFooter() || this.footer.border.width && this.footer.border.width !== '0'
}
<|endoftext|>


<commit_before>'use strict';
define(
    [
        'three',
        'SpaceShip',
        'Debug'
    ],
    function(THREE, SpaceShip, Debug) {
        return class Player extends SpaceShip {

            // ##############################################
            // # Constructor ################################
            // ##############################################

            constructor(scene, name, options) {
                if(!scene || !name || !options || typeof(options) !== 'object') {
                    throw new Error('Player has not been initialized properly.');
                }

                options.model = 'assets/models/ship.json';

                super(scene, name, options);
                this.positionIndicatorScale = 1;

                this.accelerationChange = new THREE.Vector3(0, 0, 0);
            }

            // ##############################################
            // # Private functions ##########################
            // ##############################################

            _createPositionIndicator() {
                let arrowGeometry = new THREE.Geometry();

                let xOffset = 3;
                let zOffset = 3;

                // Push vertices for the arrow
                arrowGeometry.vertices.push(new THREE.Vector3(0 - xOffset, 0, 6 - zOffset));
                arrowGeometry.vertices.push(new THREE.Vector3(3 - xOffset, 0, 0 - zOffset));
                arrowGeometry.vertices.push(new THREE.Vector3(6 - xOffset, 0, 6 - zOffset));
                arrowGeometry.vertices.push(new THREE.Vector3(3 - xOffset, 0, 4 - zOffset));
                arrowGeometry.vertices.push(new THREE.Vector3(0 - xOffset, 0, 6 - zOffset));

                let arrowMaterial      = new THREE.LineBasicMaterial({ color: 0x7fffd4 });
                this.positionIndicator = new THREE.Line(arrowGeometry, arrowMaterial);
                this.positionIndicator.position.set(this.position.x, this.position.y, this.position.z);
                this.scene.add(this.positionIndicator);
            }

            // ##############################################
            // # Public functions ###########################
            // ##############################################

            accelerate() {
                this.accelerationChange.set(
                    Math.cos(this.mesh.rotation.y),
                    0,
                    -Math.sin(this.mesh.rotation.y)
                );

                this.accelerationChange.multiplyScalar(2E-24);
            }

            // ##############################################

            decelerate() {
                this.accelerationChange.set(
                    -Math.cos(this.mesh.rotation.y),
                    0,
                    Math.sin(this.mesh.rotation.y)
                );

                this.accelerationChange.multiplyScalar(2E-24);
            }

            // ##############################################

            turnLeft() {
                this.mesh.rotation.y += Math.PI / 60;
            }

            // ##############################################

            turnRight() {
                this.mesh.rotation.y -= Math.PI / 60;
            }

            // ##############################################

            update(deltaT, smoothDeltaT, spaceObjects) {
                super.update(deltaT, smoothDeltaT, spaceObjects);

                Debug.log(this.mesh ? this.mesh.rotation : '');

                if(this.positionIndicator) {

                    this.positionIndicator.rotation.set(
                        this.mesh.rotation.x,
                        this.mesh.rotation.y - Math.PI / 2,
                        this.mesh.rotation.z
                    );
                }
            }

            // ##############################################

            force(position, mass, id, gameObjects) {
                let vec = super.force(position, mass, id, gameObjects);

                vec.add(this.accelerationChange);

                this.accelerationChange.set(0, 0, 0);

                return vec;
            }
        }
    }
);<commit_msg>Implemented player movement by thrust (fixes #12)<commit_after>'use strict';
define(
    [
        'three',
        'SpaceShip',
        'Debug'
    ],
    function(THREE, SpaceShip, Debug) {
        return class Player extends SpaceShip {

            // ##############################################
            // # Constructor ################################
            // ##############################################

            constructor(scene, name, options) {
                if(!scene || !name || !options || typeof(options) !== 'object') {
                    throw new Error('Player has not been initialized properly.');
                }

                options.model = 'assets/models/ship.json';

                super(scene, name, options);
                this.positionIndicatorScale = 1;

                this.thrust = 0.0;
            }

            // ##############################################
            // # Private functions ##########################
            // ##############################################

            _createPositionIndicator() {
                let arrowGeometry = new THREE.Geometry();

                let xOffset = 3;
                let zOffset = 3;

                // Push vertices for the arrow
                arrowGeometry.vertices.push(new THREE.Vector3(0 - xOffset, 0, 6 - zOffset));
                arrowGeometry.vertices.push(new THREE.Vector3(3 - xOffset, 0, 0 - zOffset));
                arrowGeometry.vertices.push(new THREE.Vector3(6 - xOffset, 0, 6 - zOffset));
                arrowGeometry.vertices.push(new THREE.Vector3(3 - xOffset, 0, 4 - zOffset));
                arrowGeometry.vertices.push(new THREE.Vector3(0 - xOffset, 0, 6 - zOffset));

                let arrowMaterial      = new THREE.LineBasicMaterial({ color: 0x7fffd4 });
                this.positionIndicator = new THREE.Line(arrowGeometry, arrowMaterial);
                this.positionIndicator.position.set(this.position.x, this.position.y, this.position.z);
                this.scene.add(this.positionIndicator);
            }

            // ##############################################
            // # Public functions ###########################
            // ##############################################

            accelerate() {
                this.thrust += 0.01;
                if(this.thrust > 1.0)
                    this.thrust = 1.0;
            }

            // ##############################################

            decelerate() {
                this.thrust -= 0.01;
                if(this.thrust < 0.0)
                    this.thrust = 0.0;
            }

            // ##############################################

            turnLeft() {
                this.mesh.rotation.y += Math.PI / 60;
            }

            // ##############################################

            turnRight() {
                this.mesh.rotation.y -= Math.PI / 60;
            }

            // ##############################################

            update(deltaT, smoothDeltaT, spaceObjects) {
                super.update(deltaT, smoothDeltaT, spaceObjects);

                if(this.positionIndicator) {

                    this.positionIndicator.rotation.set(
                        this.mesh.rotation.x,
                        this.mesh.rotation.y - Math.PI / 2,
                        this.mesh.rotation.z
                    );
                }
            }

            // ##############################################

            force(position, mass, id, gameObjects) {
                let vec = super.force(position, mass, id, gameObjects);

                if(!this.mesh) return vec;

                let accelerationChange = new THREE.Vector3(0, 0, 0);
                accelerationChange.set(
                    Math.cos(this.mesh.rotation.y),
                    0,
                    -Math.sin(this.mesh.rotation.y)
                );

                accelerationChange.multiplyScalar(this.thrust * 2E-24);
                vec.add(accelerationChange);

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


<commit_before>/*globals geolocator,alert,_,confirm,define*/
// Main entry point for client side code
'use strict';

$.material.init();

// Set up require
require.config({
  paths: {
    shake: './lib/shake',
    dot: './lib/doT.min',
    lodash: './lib/lodash.min',
    async: './lib/async'
  },
  map: {
    '*': {
      'text': './lib/require-text/text'
    }
  }
});

define([ 'Client'
       , 'Utils'
       , 'shake'
       , './controllers/Controller'
       , 'lodash'
       , 'text!../html/no_more.html'
       , 'text!../html/result.html'
       ],
function(Client, Utils, shake, Controller, _, noMoreTemplate, resultTemplate) {

  // Initialize shake listening
  // TODO

  // This really should be refactored and put in Utils
  var currentId = null;
  var getCategoryMap = function() {
      var buttons = $('.genre'),
          categories = {};

      for (var i = buttons.length; i--;) {
          categories[buttons[i].getAttribute('id')] = buttons[i].getAttribute('data-categories')
              // Get array of the categories
              .split(' ')
              // Remove '' from categories
              .filter(Utils.isNonEmptyString);
      }
      return categories;
  };

  var categories = getCategoryMap(),
      client = new Client(categories);

    console.log('categories:');
    console.log(categories);

  // Create the controllers
  var Contr = Controller.bind(this, client);
  $('.genre').each(function(i, btn) {
      new Controller(client, btn);
  });

  // get location by ip (faster than browser)
  geolocator.locateByIP(function(location) {
    updateLocation('coords by IP', location.coords);
    var html5Options = { enableHighAccuracy: true, timeout: 6000, maximumAge: 0 };
    // get more accurate location from the browser
    geolocator.locate(function(location) {
      updateLocation('coords', location.coords);
    }, function(error) {
      console.log(error);
    }, false, html5Options);
  }, function(error) {
    console.log(error);
  }, 2);

  function updateLocation(src, coords) {
    console.info(src, coords.latitude, coords.longitude);
    client.setLocation(coords.latitude, coords.longitude);
  }

  // Manipulating the UI
  $(function() {

    $('.container').on('click', '.result__restart, .logo', function(){
      location.reload();  // FIXME Change this not to reload the whole page...
    });

    $('.content').on('click', '.result__accept', function(){
      //confirm('Awesome! Tweet now?');
      location.reload();  // FIXME Change this not to reload the whole page...
    });

  });

});
<commit_msg>Fixed text! paths in requirejs<commit_after>/*globals geolocator,alert,_,confirm,define*/
// Main entry point for client side code
'use strict';

$.material.init();

// Set up require
require.config({
  baseDir: '..',
  paths: {
    shake: './lib/shake',
    dot: './lib/doT.min',
    lodash: './lib/lodash.min',
    async: './lib/async',

    html: '../html/'
  },
  map: {
    '*': {
      'text': 'lib/require-text/text'
    }
  }
});

define([ 'Client'
       , 'Utils'
       , 'shake'
       , './controllers/Controller'
       , 'lodash'
       , 'text!../html/no_more.html'
       , 'text!../html/result.html'
       ],
function(Client, Utils, shake, Controller, _, noMoreTemplate, resultTemplate) {

  // Initialize shake listening
  // TODO

  // This really should be refactored and put in Utils
  var currentId = null;
  var getCategoryMap = function() {
      var buttons = $('.genre'),
          categories = {};

      for (var i = buttons.length; i--;) {
          categories[buttons[i].getAttribute('id')] = buttons[i].getAttribute('data-categories')
              // Get array of the categories
              .split(' ')
              // Remove '' from categories
              .filter(Utils.isNonEmptyString);
      }
      return categories;
  };

  var categories = getCategoryMap(),
      client = new Client(categories);

    console.log('categories:');
    console.log(categories);

  // Create the controllers
  var Contr = Controller.bind(this, client);
  $('.genre').each(function(i, btn) {
      new Controller(client, btn);
  });

  // get location by ip (faster than browser)
  geolocator.locateByIP(function(location) {
    updateLocation('coords by IP', location.coords);
    var html5Options = { enableHighAccuracy: true, timeout: 6000, maximumAge: 0 };
    // get more accurate location from the browser
    geolocator.locate(function(location) {
      updateLocation('coords', location.coords);
    }, function(error) {
      console.log(error);
    }, false, html5Options);
  }, function(error) {
    console.log(error);
  }, 2);

  function updateLocation(src, coords) {
    console.info(src, coords.latitude, coords.longitude);
    client.setLocation(coords.latitude, coords.longitude);
  }

  // Manipulating the UI
  $(function() {

    $('.container').on('click', '.result__restart, .logo', function(){
      location.reload();  // FIXME Change this not to reload the whole page...
    });

    $('.content').on('click', '.result__accept', function(){
      //confirm('Awesome! Tweet now?');
      location.reload();  // FIXME Change this not to reload the whole page...
    });

  });

});
<|endoftext|>


<commit_before>"use strict"

define(['jquery', 'd3', 'consts', 'rz_bus', 'util', 'model/graph', 'model/core', 'view/helpers', 'view/view', 'rz_observer', 'view/selection', 'rz_mesh', 'model/diff', "view/graph_view", 'view/svg_input'],
function($,        d3,   consts,   rz_bus,   util,   model_graph,   model_core,   view_helpers,   view,        rz_observer,   selection,        rz_mesh,   model_diff,   graph_view,       svg_input) {

var addednodes = [],
    vis,
    graphinterval = 0,
    timeline_timer = 0,
    deliverables = [],
    circle, // <-- should not be module globals.
    scrollValue = 0,
    edit_graph,
    main_graph,
    main_graph_view,
    edit_graph_view,
    root_element_id_to_graph_view;

// "CSS" for SVG elements. Reused for editing elements.
var node_text_dx = 15,
    node_text_dy = '.30em';

var zoomProgress = false,
    zoomBus = new Bacon.Bus(),
    zoom_property = zoomBus.toProperty(zoomProgress),
    svgInput;

function updateZoomProgress(val)
{
    zoomProgress = val;
    zoomBus.push(val);
}

function svg_click_handler(e) {
    if (zoomProgress) {
        updateZoomProgress(false);
        return;
    }
    if (e.originalEvent.target.nodeName != 'svg') {
        return;
    }
    svgInput.hide();
    selection.clear();
    view.hide();
    update_view__graph(false);
}

function recenterZoom() {
    vis.attr("transform", "translate(0,0)scale(1)");
}

var initDrawingArea = function () {

    function zoom() {
        zoom_g.attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")");
        d3.event.sourceEvent != null && d3.event.sourceEvent.stopPropagation();
        updateZoomProgress(true);
    }

    main_graph = new model_graph.Graph({temporary: false, base: null});
    edit_graph = new model_graph.Graph({temporary: true, base: main_graph});

    // TODO: we are listening both on graph.diffBus and selection.selectionChangedBus,
    //  but the relation is actually:
    //
    //  diffBus -> SelectedNodesBus -> us
    //  diffBus         ->             us
    //
    //  we need to deduplicate this event
    // but there is no coordination, resulting in double updates.
    selection.selectionChangedBus.onValue(
        function() {
            main_graph.setRegularState();
            update_view__graph(false);
        }
    );

    var user_id = $('#user_id'),
        user = user_id.text();

    if (user_id.length > 0) {
        console.log('found user ID: \'' + user + '\'');
        main_graph.set_user(user);
        edit_graph.set_user(user);
    }

    var el = document.body;
    vis = d3.select('#graph-view__canvas').append("svg:svg")
        .attr('id', 'canvas_d3')
        .attr("width", '100%')
        .attr("height", '100%')
        .attr("pointer-events", "all");

    vis.append('defs').append('filter').attr('id', 'f_blur__creation-circle')
                                       .attr('y', '-10%')
                                       .attr('x', '-10%').append('feGaussianBlur').attr('in', 'SourceGraphic')
                                                                                .attr('stdDeviation', '15');

    var zoom_g = vis
        .append("g")
        .attr("class", "zoom");

    var nozoom_g = vis.append("g");

    svgInput = svg_input(vis, main_graph);

    d3.select(el).select("svg").append("svg:defs")
        .data(["end"]) // Different link/path types can be defined here
        .append("svg:marker") // This section adds in the arrows
        .attr("id", String)
        .attr("viewBox", "0 -5 10 10")
        .attr("refX", 22)
        .attr("refY", -1.5)
        .attr("markerWidth", 4)
        .attr("markerHeight", 4)
        .attr("orient", "auto")
        .append("svg:path")
        .attr("d", "M0,-5L10,0L0,5");

    /*
     * init zoom behavior
     */
    var zoom_obj = d3.behavior.zoom().scaleExtent([0.1, 3]).on("zoom", zoom);
    zoom_obj(vis);
    d3.select("svg").on("dblclick.zoom", null); // disable zoom on double click

    $('svg').click(svg_click_handler);
    var bubble_property =
        edit_graph.diffBus.map(function () {
            return edit_graph.nodes().length == 0 ? 0 : 180;
        }).skipDuplicates();

    main_graph_view = graph_view.GraphView({
            parent_element: zoom_g,
            graph_name: "main",
            graph: main_graph,
            zoom_obj: zoom_obj,
            zoom_obj_element: vis,
            parent_graph_zoom_obj: null,
            zoom_property: zoom_property,
            temporary: false,
            node_text_dx: node_text_dx,
            node_text_dy: node_text_dy,
            svgInput: svgInput,
            // FIXME: good place to animate bubble radius
            bubble_property: bubble_property,
    });
    edit_graph_view = graph_view.GraphView({
            parent_element: nozoom_g,
            graph_name: "edit",
            graph: edit_graph,
            zoom_obj: null,
            zoom_obj_element: null,
            parent_graph_zoom_obj: zoom_obj,
            zoom_property: zoom_property,
            temporary: true,
            node_text_dx: node_text_dx,
            node_text_dy: node_text_dy,
            bubble_property: bubble_property,
    });

    // $('#canvas_d3').dblclick(canvas_handler_dblclick); - see #138
    if (rz_config.backend_enabled) {
        main_graph.load_from_backend();
    }

    root_element_id_to_graph_view = {};
    root_element_id_to_graph_view[main_graph_view.root_element.id] =  main_graph_view;
    root_element_id_to_graph_view[edit_graph_view.root_element.id] = edit_graph_view;

    // publish vars
    published_var_dict.root_element_id_to_graph_view = root_element_id_to_graph_view;
    published_var_dict.main_graph = main_graph;
    published_var_dict.main_graph_view = main_graph_view;
    published_var_dict.edit_graph = edit_graph;
    published_var_dict.edit_graph_view = edit_graph_view;
}

/**
 * Initialize backend websocket connection - this will have no effect if
 * rz_config.backend__maintain_ws_connection is set to 'false'
 */
function init_ws_connection(){
    if (true == rz_config.backend__maintain_ws_connection){
        rz_mesh.init({graph: main_graph});
    }
}

function init() {
    initDrawingArea();
    init_ws_connection();
}

/**
 * find the visual element counterpart of a given model object. This relies on
 * the visual element having an id attribute equal to the object's id.
 *
 * @return null if visual element is not found
 */
function locate_visual_element(model_obj){
    var id_sel = $('#' + model_obj.id);
    if (0 === id_sel.length) {
        console.warn('unable to find visual element for model object: object id: ' + model_obj.id.toString())
        return null;
    }
    return id_sel[0];
}

/**
 * add node on canvas double click
 */
function canvas_handler_dblclick(){
    var n = model_core.create_node__set_random_id();
    n.name = ''; // will be set by user

    main_graph.commit_and_tx_diff__topo(model_diff.new_topo({
            node_set_add : [n].map(model_util.adapt_format_write_node),
        }));

    var n_ve = locate_visual_element(n);

    var on_slowdown_cb =  function(){
        svgInput.enable($(n_ve).find('.nodetext'), n);
        observer.disconnect();
    }
    var mutation_handler = rz_observer.new_Mutation_Handler__on_dxy_slowdown(on_slowdown_cb);
    var observer = rz_observer.new_MutationObserver(mutation_handler);
    mutation_handler.on_slowdown_threshold_reached;

    observer.observe(n_ve, {
        subtree: false,
        childList : false,
        attributes: true,
        attributeOldValue : true,
    });
}

function update_view__graph(relayout) {
    main_graph_view.update_view(relayout);
    edit_graph_view.update_view(relayout);
}

function load_from_json(result) {
    main_graph.load_from_json(result);
    recenterZoom();
    update_view__graph(true);
}

/**
 * open rzdoc:
 *    - set rz_config.rzdoc_cur__name
 *    - load rzdoc frombackend
 *    - do nothing if name of current rzdoc equals requested rzdoc
 */
function rzdoc__open(rzdoc_name) {
    rz_config.rzdoc_cur__name = rzdoc_name;
    main_graph.clear();
    edit_graph.clear();
    main_graph.load_from_backend();

    var rzdoc_bar = $('#rzdoc-bar_doc-label');
    var rzdoc_bar__doc_lable = $('#rzdoc-bar_doc-label');
    rzdoc_bar.fadeToggle(1000, 'swing', function() {
        rzdoc_bar__doc_lable.text(rzdoc_name);
        rzdoc_bar.fadeToggle(1000);
    });
    console.log('rzdoc: opened rzdoc : \'' + rzdoc_name + '\'');
}

var published_var_dict = {
    // functions
    init: init,
    load_from_json: load_from_json,
    rzdoc__open: rzdoc__open,
    update_view__graph : update_view__graph,

    // vars, set by init
    main_graph: undefined,
    main_graph_view: undefined,
    edit_graph: undefined,
    edit_graph_view: undefined,
    root_element_id_to_graph_view: undefined,
}

return published_var_dict;
}); /* close define call */
<commit_msg>rz_core.js: rzdoc__current__get_name() - TODO: use RZDoc object<commit_after>"use strict"

define(['jquery', 'd3', 'consts', 'rz_bus', 'util', 'model/graph', 'model/core', 'view/helpers', 'view/view', 'rz_observer', 'view/selection', 'rz_mesh', 'model/diff', "view/graph_view", 'view/svg_input'],
function($,        d3,   consts,   rz_bus,   util,   model_graph,   model_core,   view_helpers,   view,        rz_observer,   selection,        rz_mesh,   model_diff,   graph_view,       svg_input) {

var addednodes = [],
    vis,
    graphinterval = 0,
    timeline_timer = 0,
    deliverables = [],
    circle, // <-- should not be module globals.
    scrollValue = 0,
    edit_graph,
    main_graph,
    main_graph_view,
    edit_graph_view,
    root_element_id_to_graph_view;

// "CSS" for SVG elements. Reused for editing elements.
var node_text_dx = 15,
    node_text_dy = '.30em';

var zoomProgress = false,
    zoomBus = new Bacon.Bus(),
    zoom_property = zoomBus.toProperty(zoomProgress),
    svgInput;

function updateZoomProgress(val)
{
    zoomProgress = val;
    zoomBus.push(val);
}

function svg_click_handler(e) {
    if (zoomProgress) {
        updateZoomProgress(false);
        return;
    }
    if (e.originalEvent.target.nodeName != 'svg') {
        return;
    }
    svgInput.hide();
    selection.clear();
    view.hide();
    update_view__graph(false);
}

function recenterZoom() {
    vis.attr("transform", "translate(0,0)scale(1)");
}

var initDrawingArea = function () {

    function zoom() {
        zoom_g.attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")");
        d3.event.sourceEvent != null && d3.event.sourceEvent.stopPropagation();
        updateZoomProgress(true);
    }

    main_graph = new model_graph.Graph({temporary: false, base: null});
    edit_graph = new model_graph.Graph({temporary: true, base: main_graph});

    // TODO: we are listening both on graph.diffBus and selection.selectionChangedBus,
    //  but the relation is actually:
    //
    //  diffBus -> SelectedNodesBus -> us
    //  diffBus         ->             us
    //
    //  we need to deduplicate this event
    // but there is no coordination, resulting in double updates.
    selection.selectionChangedBus.onValue(
        function() {
            main_graph.setRegularState();
            update_view__graph(false);
        }
    );

    var user_id = $('#user_id'),
        user = user_id.text();

    if (user_id.length > 0) {
        console.log('found user ID: \'' + user + '\'');
        main_graph.set_user(user);
        edit_graph.set_user(user);
    }

    var el = document.body;
    vis = d3.select('#graph-view__canvas').append("svg:svg")
        .attr('id', 'canvas_d3')
        .attr("width", '100%')
        .attr("height", '100%')
        .attr("pointer-events", "all");

    vis.append('defs').append('filter').attr('id', 'f_blur__creation-circle')
                                       .attr('y', '-10%')
                                       .attr('x', '-10%').append('feGaussianBlur').attr('in', 'SourceGraphic')
                                                                                .attr('stdDeviation', '15');

    var zoom_g = vis
        .append("g")
        .attr("class", "zoom");

    var nozoom_g = vis.append("g");

    svgInput = svg_input(vis, main_graph);

    d3.select(el).select("svg").append("svg:defs")
        .data(["end"]) // Different link/path types can be defined here
        .append("svg:marker") // This section adds in the arrows
        .attr("id", String)
        .attr("viewBox", "0 -5 10 10")
        .attr("refX", 22)
        .attr("refY", -1.5)
        .attr("markerWidth", 4)
        .attr("markerHeight", 4)
        .attr("orient", "auto")
        .append("svg:path")
        .attr("d", "M0,-5L10,0L0,5");

    /*
     * init zoom behavior
     */
    var zoom_obj = d3.behavior.zoom().scaleExtent([0.1, 3]).on("zoom", zoom);
    zoom_obj(vis);
    d3.select("svg").on("dblclick.zoom", null); // disable zoom on double click

    $('svg').click(svg_click_handler);
    var bubble_property =
        edit_graph.diffBus.map(function () {
            return edit_graph.nodes().length == 0 ? 0 : 180;
        }).skipDuplicates();

    main_graph_view = graph_view.GraphView({
            parent_element: zoom_g,
            graph_name: "main",
            graph: main_graph,
            zoom_obj: zoom_obj,
            zoom_obj_element: vis,
            parent_graph_zoom_obj: null,
            zoom_property: zoom_property,
            temporary: false,
            node_text_dx: node_text_dx,
            node_text_dy: node_text_dy,
            svgInput: svgInput,
            // FIXME: good place to animate bubble radius
            bubble_property: bubble_property,
    });
    edit_graph_view = graph_view.GraphView({
            parent_element: nozoom_g,
            graph_name: "edit",
            graph: edit_graph,
            zoom_obj: null,
            zoom_obj_element: null,
            parent_graph_zoom_obj: zoom_obj,
            zoom_property: zoom_property,
            temporary: true,
            node_text_dx: node_text_dx,
            node_text_dy: node_text_dy,
            bubble_property: bubble_property,
    });

    // $('#canvas_d3').dblclick(canvas_handler_dblclick); - see #138
    if (rz_config.backend_enabled) {
        main_graph.load_from_backend();
    }

    root_element_id_to_graph_view = {};
    root_element_id_to_graph_view[main_graph_view.root_element.id] =  main_graph_view;
    root_element_id_to_graph_view[edit_graph_view.root_element.id] = edit_graph_view;

    // publish vars
    published_var_dict.root_element_id_to_graph_view = root_element_id_to_graph_view;
    published_var_dict.main_graph = main_graph;
    published_var_dict.main_graph_view = main_graph_view;
    published_var_dict.edit_graph = edit_graph;
    published_var_dict.edit_graph_view = edit_graph_view;
}

/**
 * Initialize backend websocket connection - this will have no effect if
 * rz_config.backend__maintain_ws_connection is set to 'false'
 */
function init_ws_connection(){
    if (true == rz_config.backend__maintain_ws_connection){
        rz_mesh.init({graph: main_graph});
    }
}

function init() {
    initDrawingArea();
    init_ws_connection();
}

/**
 * find the visual element counterpart of a given model object. This relies on
 * the visual element having an id attribute equal to the object's id.
 *
 * @return null if visual element is not found
 */
function locate_visual_element(model_obj){
    var id_sel = $('#' + model_obj.id);
    if (0 === id_sel.length) {
        console.warn('unable to find visual element for model object: object id: ' + model_obj.id.toString())
        return null;
    }
    return id_sel[0];
}

/**
 * add node on canvas double click
 */
function canvas_handler_dblclick(){
    var n = model_core.create_node__set_random_id();
    n.name = ''; // will be set by user

    main_graph.commit_and_tx_diff__topo(model_diff.new_topo({
            node_set_add : [n].map(model_util.adapt_format_write_node),
        }));

    var n_ve = locate_visual_element(n);

    var on_slowdown_cb =  function(){
        svgInput.enable($(n_ve).find('.nodetext'), n);
        observer.disconnect();
    }
    var mutation_handler = rz_observer.new_Mutation_Handler__on_dxy_slowdown(on_slowdown_cb);
    var observer = rz_observer.new_MutationObserver(mutation_handler);
    mutation_handler.on_slowdown_threshold_reached;

    observer.observe(n_ve, {
        subtree: false,
        childList : false,
        attributes: true,
        attributeOldValue : true,
    });
}

function update_view__graph(relayout) {
    main_graph_view.update_view(relayout);
    edit_graph_view.update_view(relayout);
}

function load_from_json(result) {
    main_graph.load_from_json(result);
    recenterZoom();
    update_view__graph(true);
}

/**
 * open rzdoc:
 *    - set rz_config.rzdoc_cur__name
 *    - load rzdoc frombackend
 *    - do nothing if name of current rzdoc equals requested rzdoc
 */
function rzdoc__open(rzdoc_name) {
    rz_config.rzdoc_cur__name = rzdoc_name;
    main_graph.clear();
    edit_graph.clear();
    main_graph.load_from_backend();

    var rzdoc_bar = $('#rzdoc-bar_doc-label');
    var rzdoc_bar__doc_lable = $('#rzdoc-bar_doc-label');
    rzdoc_bar.fadeToggle(1000, 'swing', function() {
        rzdoc_bar__doc_lable.text(rzdoc_name);
        rzdoc_bar.fadeToggle(1000);
    });
    console.log('rzdoc: opened rzdoc : \'' + rzdoc_name + '\'');
}

function rzdoc__current__get_name() {
    return rz_config.rzdoc_cur__name;
}

var published_var_dict = {
    // functions
    init: init,
    load_from_json: load_from_json,
    rzdoc__open: rzdoc__open,
    rzdoc__current__get_name: rzdoc__current__get_name,
    update_view__graph : update_view__graph,

    // vars, set by init
    main_graph: undefined,
    main_graph_view: undefined,
    edit_graph: undefined,
    edit_graph_view: undefined,
    root_element_id_to_graph_view: undefined,
}

return published_var_dict;
}); /* close define call */
<|endoftext|>


<commit_before>/*
 * Copyright (c) 2012 Adobe Systems Incorporated. All rights reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 *
 */

/*jslint vars: true, plusplus: true, devel: true, nomen: true, indent: 4, maxerr: 50, regexp:true */
/*global define, brackets */

define(function (require, exports, module) {
    'use strict';

    /**
     * @private
     * builds the keyDescriptor string from the given parts
     * @param {boolean} hasCtrl Is Ctrl key enabled
     * @param {boolean} hasAlt Is Alt key enabled
     * @param {boolean} hasShift Is Shift key enabled
     * @param {string} key The key that's pressed
     * @param {string} origDescriptor The optional original descriptor
     * @return {string} The normalized key descriptor
     */
    function _buildKeyDescriptor(hasCtrl, hasAlt, hasShift, key, origDescriptor) {
        if (!key) {

            if (origDescriptor && origDescriptor.search(/^.+--$/) !== -1) { // Check if the binding is for "-"
                key = "-";
            } else {
                console.log("KeyMap _buildKeyDescriptor() - No key provided!");
                return "";
            }
        }

        var keyDescriptor = [];

        if (hasAlt) {
            keyDescriptor.push("Alt");
        }
        if (hasShift) {
            keyDescriptor.push("Shift");
        }

        if (hasCtrl) {
            // Windows display Ctrl first, Mac displays Command symbol last
            if (brackets.platform === "win") {
                keyDescriptor.unshift("Ctrl");
            } else {
                keyDescriptor.push("Ctrl");
            }
        }

        keyDescriptor.push(key);

        return keyDescriptor.join("-");
    }

    function _isModifier(left, right, previouslyFound, origDescriptor) {
        if (!left || !right) {
            return false;
        }
        left = left.trim().toLowerCase();
        right = right.trim().toLowerCase();
        var matched = (left.length > 0 && left === right);
        if (matched && previouslyFound) {
            console.log("KeyMap _normalizeKeyDescriptorString() - Modifier defined twice: " + origDescriptor);
        }
        return matched;
    }

    /**
     * @private
     * normalizes the incoming key descriptor so the modifier keys are always specified in the correct order
     * @param {string} The string for a key descriptor, can be in any order, the result will be Ctrl-Alt-Shift-<Key>
     * @return {string} The normalized key descriptor
     */
    function _normalizeKeyDescriptorString(origDescriptor) {
        var hasCtrl = false,
            hasAlt = false,
            hasShift = false,
            key = "";

        origDescriptor.split("-").forEach(function parseDescriptor(ele, i, arr) {
            if (_isModifier("ctrl", ele, hasCtrl)) {
                hasCtrl = true;
            } else if (_isModifier("cmd", ele, hasCtrl, origDescriptor)) {
                console.log("KeyMap _normalizeKeyDescriptorString() - Cmd getting mapped to Ctrl from: " + origDescriptor);
                hasCtrl = true;
            } else if (_isModifier("alt", ele, hasAlt, origDescriptor)) {
                hasAlt = true;
            } else if (_isModifier("opt", ele, hasAlt, origDescriptor)) {
                console.log("KeyMap _normalizeKeyDescriptorString() - Opt getting mapped to Alt from: " + origDescriptor);
                hasAlt = true;
            } else if (_isModifier("shift", ele, hasShift, origDescriptor)) {
                hasShift = true;
            } else if (key.length > 0) {
                console.log("KeyMap _normalizeKeyDescriptorString() - Multiple keys defined. Using key: " + key + " from: " + origDescriptor);
            } else {
                key = ele;
            }
        });

        return _buildKeyDescriptor(hasCtrl, hasAlt, hasShift, key, origDescriptor);
    }

    /**
     * @private
     * normalizes the incoming map so all the key descriptors are specified the correct way
     * @param {map} map The string for a key descriptor, can be in any order, the result will be Ctrl-Alt-Shift-<Key>
     * @return {map} The normalized map
     */
    function _normalizeMap(map) {
        var finalMap = {};
        Object.keys(map).forEach(function normalizeKey(ele, i, arr) {
            var val = map[ele];
            var normalizedKey = _normalizeKeyDescriptorString(ele);
            if (normalizedKey.length === 0) {
                console.log("KeyMap _normalizeMap() - Rejecting malformed key: " + ele + " (value: " + val + ")");
            } else if (!val) {
                console.log("KeyMap _normalizeMap() - Rejecting key for falsy value: " + ele + " (value: " + val + ")");
            } else if (finalMap[normalizedKey]) {
                console.log("KeyMap _normalizeMap() - Rejecting key because it was defined twice: " + ele + " (value: " + val + ")");
            } else {
                finalMap[normalizedKey] = val;
            }
        });
        return finalMap;
    }

    /**
     * @private
     * given a list of bindings, goes through and turns them into a map. The list is filter based
     * on platform, so if I binding has no specific platform or needs to match the given platform
     * @param [{bindings}] bindings A list of binding objects
     * @param {string} platform The platform to filter on
     */
    function _bindingsToMap(bindings, platform) {
        var map = {};
        bindings.forEach(function transformToMap(ele, i, arr) {
            var keys = Object.keys(ele);
            var platformIndex = keys.indexOf("platform");
            if (platformIndex > -1) {
                if (ele.platform !== platform) {
                    return; //don't add this to the map
                }
                keys.splice(platformIndex, 1);
            }
            if (keys.length !== 1) {
                console.log("KeyMap _bindingsToMap() - bindings list has unknown keys: " + bindings);
                return;
            }
            var key = keys[0];
            map[key] = ele[key];
        });
        return map;
    }

    /** class Keymap
     *
     * A keymap specifies how keys are mapped to commands. This currently just holds the map, but in future
     * it will likely be extended to include other metadata about the keymap.
     *
     * Keys are described by strings of the form "[modifier-modifier-...-]key", where modifier is one of
     * Ctrl, Alt, or Shift. If multiple modifiers are specified, they will get normalized to the form
     * "Ctrl-Alt-Shift-<Key>" so modifiers are always stored and lookedup in that order.
     *    -- Ctrl maps to Cmd on Mac. (This means that you can't specifically bind to the Ctrl key on Mac.)
     *    -- Alt maps to the Option key on Mac.
     *    -- Letters must be uppercase, but do not require Shift by default. To indicate that Shift must be held
     *       down, you must specifically include Shift.
     *
     * @constructor
     * @param {args} An object with a list of objects mapping key-description strings to command IDs and a platform
     */
    var KeyMap = function (args) {
        if (args === undefined) {
            throw new Error("All parameters to the KeyMap constructor must be specified");
        }
        this.map = _normalizeMap(_bindingsToMap(args.bindings, args.platform));
    };

    /**
     * simple creator
     * @param {map} map An object mapping key-description strings to command IDs.
     */
    function create(map) {
        return new KeyMap(map);
    }

    /**
     * Takes a keyboard event and translates it into a key in a key map
     */
    function translateKeyboardEvent(event) {
        var hasCtrl = (event.metaKey || event.ctrlKey),
            hasAlt = (event.altKey),
            hasShift = (event.shiftKey),
            key = String.fromCharCode(event.keyCode);

        //From the W3C, if we can get the KeyboardEvent.keyIdentifier then look here
        //As that will let us use keys like then function keys "F5" for commands. The
        //full set of values we can use is here
        //http://www.w3.org/TR/2007/WD-DOM-Level-3-Events-20071221/keyset.html#KeySet-Set
        var ident = event.keyIdentifier;
        if (ident) {
            if (ident.charAt(0) === "U" && ident.charAt(1) === "+") {
                //This is a unicode code point like "U+002A", get the 002A and use that
                key = String.fromCharCode(parseInt(ident.substring(2), 16));
            } else {
                //This is some non-character key, just use the raw identifier
                key = ident;
            }
        }

        // Translate some keys to their common names
        if (key === "\t") { key = "Tab"; }
        if (event.keyCode === 191) { key = "/"; }

        return _buildKeyDescriptor(hasCtrl, hasAlt, hasShift, key);
    }

    // Define public API
    exports.create = create;
    exports.translateKeyboardEvent = translateKeyboardEvent;
});
<commit_msg>Removed an extra newline.<commit_after>/*
 * Copyright (c) 2012 Adobe Systems Incorporated. All rights reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 *
 */

/*jslint vars: true, plusplus: true, devel: true, nomen: true, indent: 4, maxerr: 50, regexp:true */
/*global define, brackets */

define(function (require, exports, module) {
    'use strict';

    /**
     * @private
     * builds the keyDescriptor string from the given parts
     * @param {boolean} hasCtrl Is Ctrl key enabled
     * @param {boolean} hasAlt Is Alt key enabled
     * @param {boolean} hasShift Is Shift key enabled
     * @param {string} key The key that's pressed
     * @param {string} origDescriptor The optional original descriptor
     * @return {string} The normalized key descriptor
     */
    function _buildKeyDescriptor(hasCtrl, hasAlt, hasShift, key, origDescriptor) {
        if (!key) {
            if (origDescriptor && origDescriptor.search(/^.+--$/) !== -1) { // Check if the binding is for "-"
                key = "-";
            } else {
                console.log("KeyMap _buildKeyDescriptor() - No key provided!");
                return "";
            }
        }

        var keyDescriptor = [];

        if (hasAlt) {
            keyDescriptor.push("Alt");
        }
        if (hasShift) {
            keyDescriptor.push("Shift");
        }

        if (hasCtrl) {
            // Windows display Ctrl first, Mac displays Command symbol last
            if (brackets.platform === "win") {
                keyDescriptor.unshift("Ctrl");
            } else {
                keyDescriptor.push("Ctrl");
            }
        }

        keyDescriptor.push(key);

        return keyDescriptor.join("-");
    }

    function _isModifier(left, right, previouslyFound, origDescriptor) {
        if (!left || !right) {
            return false;
        }
        left = left.trim().toLowerCase();
        right = right.trim().toLowerCase();
        var matched = (left.length > 0 && left === right);
        if (matched && previouslyFound) {
            console.log("KeyMap _normalizeKeyDescriptorString() - Modifier defined twice: " + origDescriptor);
        }
        return matched;
    }

    /**
     * @private
     * normalizes the incoming key descriptor so the modifier keys are always specified in the correct order
     * @param {string} The string for a key descriptor, can be in any order, the result will be Ctrl-Alt-Shift-<Key>
     * @return {string} The normalized key descriptor
     */
    function _normalizeKeyDescriptorString(origDescriptor) {
        var hasCtrl = false,
            hasAlt = false,
            hasShift = false,
            key = "";

        origDescriptor.split("-").forEach(function parseDescriptor(ele, i, arr) {
            if (_isModifier("ctrl", ele, hasCtrl)) {
                hasCtrl = true;
            } else if (_isModifier("cmd", ele, hasCtrl, origDescriptor)) {
                console.log("KeyMap _normalizeKeyDescriptorString() - Cmd getting mapped to Ctrl from: " + origDescriptor);
                hasCtrl = true;
            } else if (_isModifier("alt", ele, hasAlt, origDescriptor)) {
                hasAlt = true;
            } else if (_isModifier("opt", ele, hasAlt, origDescriptor)) {
                console.log("KeyMap _normalizeKeyDescriptorString() - Opt getting mapped to Alt from: " + origDescriptor);
                hasAlt = true;
            } else if (_isModifier("shift", ele, hasShift, origDescriptor)) {
                hasShift = true;
            } else if (key.length > 0) {
                console.log("KeyMap _normalizeKeyDescriptorString() - Multiple keys defined. Using key: " + key + " from: " + origDescriptor);
            } else {
                key = ele;
            }
        });

        return _buildKeyDescriptor(hasCtrl, hasAlt, hasShift, key, origDescriptor);
    }

    /**
     * @private
     * normalizes the incoming map so all the key descriptors are specified the correct way
     * @param {map} map The string for a key descriptor, can be in any order, the result will be Ctrl-Alt-Shift-<Key>
     * @return {map} The normalized map
     */
    function _normalizeMap(map) {
        var finalMap = {};
        Object.keys(map).forEach(function normalizeKey(ele, i, arr) {
            var val = map[ele];
            var normalizedKey = _normalizeKeyDescriptorString(ele);
            if (normalizedKey.length === 0) {
                console.log("KeyMap _normalizeMap() - Rejecting malformed key: " + ele + " (value: " + val + ")");
            } else if (!val) {
                console.log("KeyMap _normalizeMap() - Rejecting key for falsy value: " + ele + " (value: " + val + ")");
            } else if (finalMap[normalizedKey]) {
                console.log("KeyMap _normalizeMap() - Rejecting key because it was defined twice: " + ele + " (value: " + val + ")");
            } else {
                finalMap[normalizedKey] = val;
            }
        });
        return finalMap;
    }

    /**
     * @private
     * given a list of bindings, goes through and turns them into a map. The list is filter based
     * on platform, so if I binding has no specific platform or needs to match the given platform
     * @param [{bindings}] bindings A list of binding objects
     * @param {string} platform The platform to filter on
     */
    function _bindingsToMap(bindings, platform) {
        var map = {};
        bindings.forEach(function transformToMap(ele, i, arr) {
            var keys = Object.keys(ele);
            var platformIndex = keys.indexOf("platform");
            if (platformIndex > -1) {
                if (ele.platform !== platform) {
                    return; //don't add this to the map
                }
                keys.splice(platformIndex, 1);
            }
            if (keys.length !== 1) {
                console.log("KeyMap _bindingsToMap() - bindings list has unknown keys: " + bindings);
                return;
            }
            var key = keys[0];
            map[key] = ele[key];
        });
        return map;
    }

    /** class Keymap
     *
     * A keymap specifies how keys are mapped to commands. This currently just holds the map, but in future
     * it will likely be extended to include other metadata about the keymap.
     *
     * Keys are described by strings of the form "[modifier-modifier-...-]key", where modifier is one of
     * Ctrl, Alt, or Shift. If multiple modifiers are specified, they will get normalized to the form
     * "Ctrl-Alt-Shift-<Key>" so modifiers are always stored and lookedup in that order.
     *    -- Ctrl maps to Cmd on Mac. (This means that you can't specifically bind to the Ctrl key on Mac.)
     *    -- Alt maps to the Option key on Mac.
     *    -- Letters must be uppercase, but do not require Shift by default. To indicate that Shift must be held
     *       down, you must specifically include Shift.
     *
     * @constructor
     * @param {args} An object with a list of objects mapping key-description strings to command IDs and a platform
     */
    var KeyMap = function (args) {
        if (args === undefined) {
            throw new Error("All parameters to the KeyMap constructor must be specified");
        }
        this.map = _normalizeMap(_bindingsToMap(args.bindings, args.platform));
    };

    /**
     * simple creator
     * @param {map} map An object mapping key-description strings to command IDs.
     */
    function create(map) {
        return new KeyMap(map);
    }

    /**
     * Takes a keyboard event and translates it into a key in a key map
     */
    function translateKeyboardEvent(event) {
        var hasCtrl = (event.metaKey || event.ctrlKey),
            hasAlt = (event.altKey),
            hasShift = (event.shiftKey),
            key = String.fromCharCode(event.keyCode);

        //From the W3C, if we can get the KeyboardEvent.keyIdentifier then look here
        //As that will let us use keys like then function keys "F5" for commands. The
        //full set of values we can use is here
        //http://www.w3.org/TR/2007/WD-DOM-Level-3-Events-20071221/keyset.html#KeySet-Set
        var ident = event.keyIdentifier;
        if (ident) {
            if (ident.charAt(0) === "U" && ident.charAt(1) === "+") {
                //This is a unicode code point like "U+002A", get the 002A and use that
                key = String.fromCharCode(parseInt(ident.substring(2), 16));
            } else {
                //This is some non-character key, just use the raw identifier
                key = ident;
            }
        }

        // Translate some keys to their common names
        if (key === "\t") { key = "Tab"; }
        if (event.keyCode === 191) { key = "/"; }

        return _buildKeyDescriptor(hasCtrl, hasAlt, hasShift, key);
    }

    // Define public API
    exports.create = create;
    exports.translateKeyboardEvent = translateKeyboardEvent;
});
<|endoftext|>


<commit_before>let G = 500; //Gravitational constant
let EPSILON = 15;
let TAIL_LENGTH = 64;
let TAIL_VISIBILITY = 180;
let SIDE_MARGIN = 0.2; // For particles, as fraction of div width/height
let VERTICAL_MARGIN = 0.2;
let SHOT_RAD = 400;
let BACKGROUND = 30;
let PARTICLE_DRAW_SCALE = 6;

let ball = null;
let nuclei = []; //Static particles
let walls = [];
let target = null;
let shootx = 0;
let shooty = 0;
let div_height = 0;
let div_width = 0;
let level = 0;
var cnv;
//
// document.getElementById("reset").onclick = function() {ready_game()};

window.onkeydown = function(event){
    if(event.keyCode === 32) {
        event.preventDefault();
        document.getElementById("retry").click();
    }
    else if(event.keyCode === 13) {
        event.preventDefault();
        document.getElementById("new").click();
    }

};

window.onload = function() {
    div_width = document.getElementById("game").offsetWidth;
    div_height = document.getElementById("game").offsetHeight;
    console.log(div_height, div_width);
    new_game();
};

function setup() {
    cnv = createCanvas(div_width, div_height);
    cnv.style('display', 'block');
    cnv.parent("game");

    pos_img = loadImage("./assets/BLUE.png");
    neg_img = loadImage("./assets/YELLOW.png");
    el_img = loadImage("./assets/WHITE.png");
    target_img = loadImage("./assets/TARGET.png");

    // console.log(p.x +', '+p.y + ' v: ', p.vx);
    // console.log(i.x +', '+i.y + ', ' + i.intensity);
    // frameRate(1);
}
function new_game() {
    nuclei = [];
    walls = [];
    resizeCanvas(div_width, div_height);

    if(level >= levels.length) {
        end_game()
    }
    else {
        document.getElementById("level").textContent = "LEVEL " + (level+1).toString();

        create_field_from_data(levels[level]);
        ball = new particle(50, div_height / 2, 1, 0, 0, 0, 0, true);
    }
}
function ready_game() {
    if (level >= levels.length) {
        level = 0;
        new_game();
    }
    else
        ball = new particle(50, div_height / 2, 1, 0, 0, 0, 0, true);
}

function win() {
    level++;
    console.log(level);
    new_game();
}

function end_game() {
    document.getElementById("level").textContent = "YOU WIN!";
}

function getRandomInt(min, max) {
    return Math.floor(Math.random() * Math.floor(max - min)) + min;
}

function reletive_to_absolute_position(x, y, intensity=1) {
    let left_extreme = div_width * SIDE_MARGIN;
    let right_extreme = div_width * (1 - SIDE_MARGIN);
    let up_extreme = div_height * VERTICAL_MARGIN;
    let bottom_extreme = div_height * (1 - VERTICAL_MARGIN);

    let x_abs = x * (right_extreme - left_extreme) + left_extreme;
    let y_abs = y * (bottom_extreme - up_extreme) + up_extreme;
    return [x_abs, y_abs, intensity];
}

function reletive_to_absolute_size(x, y) {
    let left_extreme = div_width * SIDE_MARGIN;
    let right_extreme = div_width * (1 - SIDE_MARGIN);
    let up_extreme = div_height * VERTICAL_MARGIN;
    let bottom_extreme = div_height * (1 - VERTICAL_MARGIN);

    let x_abs = x * (right_extreme - left_extreme);
    let y_abs = y * (bottom_extreme - up_extreme);
    return [x_abs, y_abs];
}

function create_field_from_data(data) {
    let x, y, intensity, width, height;

    for(let i = 0; i < data.nuclei.length; i++) {
        let curr = data.nuclei[i];
        [x, y, intensity] = reletive_to_absolute_position(curr.x, curr.y, curr.intensity);
        nuclei.push(new particle(x, y, intensity))
    }

    for(let i = 0; i < data.walls.length; i++) {
        let curr = data.walls[i];
        [x, y] = reletive_to_absolute_position(curr.x, curr.y);
        [width, height] = reletive_to_absolute_size(curr.width, curr.height);

        walls.push(new wall(x, y, width, height));
    }

    [x,y] = reletive_to_absolute_position(data.target.x, data.target.y);
    target = new particle(x, y);
}

function create_random_field(nuclei_num, target_num, min_intensity, max_intensity) {

    let this_field = {"nuclei":[], "target":null};

    let pos_num = getRandomInt(nuclei_num / 2, nuclei_num); //number of positive nuclei

    let left_extreme = div_width * SIDE_MARGIN;
    let right_extreme = div_width * (1 - SIDE_MARGIN);
    let up_extreme = div_height * VERTICAL_MARGIN;
    let bottom_extreme = div_height * (1 - VERTICAL_MARGIN);

    for(let i = 0; i < pos_num; i++) {
        let intensity = getRandomInt(min_intensity, max_intensity);
        let x = getRandomInt(left_extreme, right_extreme);
        let y = getRandomInt(up_extreme, bottom_extreme);
        let p = new particle(x, y, intensity);
        nuclei.push(p);

        this_field.nuclei.push([x, y, intensity]);
    }
    for(let i = 0; i < nuclei_num - pos_num - 1; i++) {
        let intensity = getRandomInt(min_intensity, max_intensity);
        let x = getRandomInt(left_extreme, right_extreme);
        let y = getRandomInt(up_extreme, bottom_extreme);
        let p = new particle(x, y, -intensity);
        nuclei.push(p);

        this_field.nuclei.push([x, y, intensity]);
    }

    for(let i = 0; i < target_num; i++) {
        let x = getRandomInt(left_extreme, right_extreme);
        let y = getRandomInt(up_extreme, bottom_extreme);
        target = new particle(x, y);

        this_field.target = [x, y];
    }

    console.log(JSON.stringify(this_field, null, 2));
}

function draw_nuclei() {
    for(let i = 0; i < nuclei.length; i++) {
        let size = PARTICLE_DRAW_SCALE * nuclei[i].intensity;
        // ellipse(nuclei[i].x, nuclei[i].y, size, size);
        if(nuclei[i].intensity > 0)
            image(pos_img, nuclei[i].x, nuclei[i].y, size, size);
        else
            image(neg_img, nuclei[i].x, nuclei[i].y, size, size)
    }
}

function collided_with_wall(x, y, i) {
    return x > walls[i].x && x < (walls[i].x + walls[i].width) &&
           y > walls[i].y && y < (walls[i].y + walls[i].height);

}
function update_ball() {
    if (ball.locked === false) {
        for (let j = 0; j < nuclei.length; j++) {
            ball.influenced_by(nuclei[j]);
        }
        ball.update();

        //Hit wall?
        for(let i = 0; i < walls.length; i++) {
            if(collided_with_wall(ball.x, ball.y, i))
                ball.die = true;
        }

        //Hit target?
        let distsq, dirx, diry;
        [distsq, [dirx, diry]] = ball.dist_from_particle(target);
        if(sqrt(distsq) < EPSILON) {
            ball.die = true;
            if(ball.die === true)
                win();
        }
    }
}

function draw_ball() {
    image(el_img, ball.x, ball.y, 10, 10);
}

function draw_target() {
    let size = PARTICLE_DRAW_SCALE * 6;
    image(target_img, target.x, target.y, size, size);
}

function draw_tails() {
    for(let j = 0; j < ball.history.length - 1; j++) {
        fill(Math.max(BACKGROUND, BACKGROUND + (j + 1) * (TAIL_VISIBILITY - BACKGROUND) / ball.history.length));
        ellipse(ball.history[j][0], ball.history[j][1], 3, 3);
    }
}

function draw_walls() {
    fill(193, 255, 193);
    stroke(200);
    rectMode(CORNER);
    for(let i = 0; i < walls.length; i++) {
        rect(walls[i].x, walls[i].y, walls[i].width, walls[i].height);
    }
}

function draw() {
    if(ball === null)
        return;
    console.log(ball.die);

    imageMode(CENTER);
    rectMode(CENTER);
    background(BACKGROUND);
    stroke(BACKGROUND);
    fill(255);
    smooth();

    if(ball.die === false)
        update_ball();
    draw_nuclei();
    draw_target();
    draw_ball();
    draw_tails();
    push();
    draw_walls();
    pop();

    if(ball.die === true && level < levels.length)
        ready_game();
    push();
    if(ball.locked === true)
        ready_shoot(ball);
    pop();

}

function dottedLine(x1, y1, x2, y2, dot_num) {
    for (let i = 0; i <= dot_num; i++) {
        let x = lerp(x1, x2, i/dot_num);
        let y = lerp(y1, y2, i/dot_num);
        point(x, y);
    }
}

function ready_shoot(p){
    stroke(250);
    let sx = mouseX;
    let sy = mouseY;
    let len = sqrt(pow(mouseX - p.x, 2) + pow(mouseY - p.y, 2));
    if(len > SHOT_RAD) {
        sx = (mouseX - p.x) * SHOT_RAD / len + p.x;
        sy = (mouseY - p.y) * SHOT_RAD / len + p.y;
    }
    dottedLine(p.x, p.y, sx, sy, 20);
    strokeWeight(.3);
    text(min(Math.round(len), SHOT_RAD).toString(), sx, sy + 20);
    // line(p.x, p.y, sx, sy);
    shootx = (sx - p.x) / 100;
    shooty = (sy - p.y) / 100;
}

function mousePressed() {
    if(ball.locked === true && mouseX < div_width) {
        ball.locked = false;
        ball.vx = shootx;
        ball.vy = shooty;
    }
}
let particle = function (x, y, intensity = 1, vx = 0, vy = 0, ax = 0, ay = 0, locked = false) {
    this.x = x;
    this.y = y;
    this.vx = vx;
    this.vy = vy;
    this.ax = ax;
    this.ay = ay;
    this.intensity = intensity;
    this.locked = locked;
    this.die = false;
    this.history = [];
    this.tick = 0;

    this.dist_from_particle = function(other) {
        // returns square of distance aith normalized direction
        let dx = other.x - this.x;
        let dy = other.y - this.y;
        let distsq = pow(dx, 2) + pow(dy, 2);
        let dirx = dx / sqrt(distsq);
        let diry = dy / sqrt(distsq);

        return [distsq, [dirx, diry]];
    };

    this.influenced_by = function(other) {
        let distsq, dirx, diry;
        [distsq, [dirx, diry]] = this.dist_from_particle(other);

        this.ax += dirx * G * this.intensity * other.intensity / distsq;
        this.ay += diry * G * this.intensity * other.intensity / distsq;

        if(sqrt(distsq) < EPSILON)
            this.die = true;
    };

    this.update = function() {
        this.tick++;
        this.vx += this.ax;
        this.vy += this.ay;

        if(this.tick % 3 === 0) {
            this.history.push([this.x, this.y]);
            if(this.history.length > TAIL_LENGTH)
                this.history.splice(0, 1);
        }
        this.x += this.vx;
        this.y += this.vy;

        this.ax = this.ay = 0;
    };
};

let wall = function (x, y, width, height) {
    this.x = x;
    this.y = y;
    this.width = width;
    this.height = height;
};<commit_msg>Bolder game completion indication<commit_after>let G = 500; //Gravitational constant
let EPSILON = 15;
let TAIL_LENGTH = 64;
let TAIL_VISIBILITY = 180;
let SIDE_MARGIN = 0.2; // For particles, as fraction of div width/height
let VERTICAL_MARGIN = 0.2;
let SHOT_RAD = 400;
let BACKGROUND = 30;
let PARTICLE_DRAW_SCALE = 6;

let ball = null;
let nuclei = []; //Static particles
let walls = [];
let target = null;
let shootx = 0;
let shooty = 0;
let div_height = 0;
let div_width = 0;
let level = 0;
var cnv;
//
// document.getElementById("reset").onclick = function() {ready_game()};

window.onkeydown = function(event){
    if(event.keyCode === 32) {
        event.preventDefault();
        document.getElementById("retry").click();
    }
    else if(event.keyCode === 13) {
        event.preventDefault();
        document.getElementById("new").click();
    }

};

window.onload = function() {
    div_width = document.getElementById("game").offsetWidth;
    div_height = document.getElementById("game").offsetHeight;
    console.log(div_height, div_width);
    new_game();
};

function setup() {
    cnv = createCanvas(div_width, div_height);
    cnv.style('display', 'block');
    cnv.parent("game");

    pos_img = loadImage("./assets/BLUE.png");
    neg_img = loadImage("./assets/YELLOW.png");
    el_img = loadImage("./assets/WHITE.png");
    target_img = loadImage("./assets/TARGET.png");

    // console.log(p.x +', '+p.y + ' v: ', p.vx);
    // console.log(i.x +', '+i.y + ', ' + i.intensity);
    // frameRate(1);
}
function new_game() {
    nuclei = [];
    walls = [];
    resizeCanvas(div_width, div_height);

    if(level >= levels.length) {
        end_game()
    }
    else {
        let level_indicator = document.getElementById("level");
        level_indicator.style.color = "#656565";
        level_indicator.style.fontSize = "40px";
        level_indicator.textContent = "LEVEL " + (level+1).toString();

        create_field_from_data(levels[level]);
        ball = new particle(50, div_height / 2, 1, 0, 0, 0, 0, true);
    }
}
function ready_game() {
    if (level >= levels.length) {
        level = 0;
        new_game();
    }
    else
        ball = new particle(50, div_height / 2, 1, 0, 0, 0, 0, true);
}

function end_game() {
    level++;
    let level_indicator = document.getElementById("level");
    level_indicator.textContent = "YOU WIN!";
    level_indicator.style.color = "red";
    level_indicator.style.fontSize = "60px";
}

function getRandomInt(min, max) {
    return Math.floor(Math.random() * Math.floor(max - min)) + min;
}

function reletive_to_absolute_position(x, y, intensity=1) {
    let left_extreme = div_width * SIDE_MARGIN;
    let right_extreme = div_width * (1 - SIDE_MARGIN);
    let up_extreme = div_height * VERTICAL_MARGIN;
    let bottom_extreme = div_height * (1 - VERTICAL_MARGIN);

    let x_abs = x * (right_extreme - left_extreme) + left_extreme;
    let y_abs = y * (bottom_extreme - up_extreme) + up_extreme;
    return [x_abs, y_abs, intensity];
}

function reletive_to_absolute_size(x, y) {
    let left_extreme = div_width * SIDE_MARGIN;
    let right_extreme = div_width * (1 - SIDE_MARGIN);
    let up_extreme = div_height * VERTICAL_MARGIN;
    let bottom_extreme = div_height * (1 - VERTICAL_MARGIN);

    let x_abs = x * (right_extreme - left_extreme);
    let y_abs = y * (bottom_extreme - up_extreme);
    return [x_abs, y_abs];
}

function create_field_from_data(data) {
    let x, y, intensity, width, height;

    for(let i = 0; i < data.nuclei.length; i++) {
        let curr = data.nuclei[i];
        [x, y, intensity] = reletive_to_absolute_position(curr.x, curr.y, curr.intensity);
        nuclei.push(new particle(x, y, intensity))
    }

    for(let i = 0; i < data.walls.length; i++) {
        let curr = data.walls[i];
        [x, y] = reletive_to_absolute_position(curr.x, curr.y);
        [width, height] = reletive_to_absolute_size(curr.width, curr.height);

        walls.push(new wall(x, y, width, height));
    }

    [x,y] = reletive_to_absolute_position(data.target.x, data.target.y);
    target = new particle(x, y);
}

function create_random_field(nuclei_num, target_num, min_intensity, max_intensity) {

    let this_field = {"nuclei":[], "target":null};

    let pos_num = getRandomInt(nuclei_num / 2, nuclei_num); //number of positive nuclei

    let left_extreme = div_width * SIDE_MARGIN;
    let right_extreme = div_width * (1 - SIDE_MARGIN);
    let up_extreme = div_height * VERTICAL_MARGIN;
    let bottom_extreme = div_height * (1 - VERTICAL_MARGIN);

    for(let i = 0; i < pos_num; i++) {
        let intensity = getRandomInt(min_intensity, max_intensity);
        let x = getRandomInt(left_extreme, right_extreme);
        let y = getRandomInt(up_extreme, bottom_extreme);
        let p = new particle(x, y, intensity);
        nuclei.push(p);

        this_field.nuclei.push([x, y, intensity]);
    }
    for(let i = 0; i < nuclei_num - pos_num - 1; i++) {
        let intensity = getRandomInt(min_intensity, max_intensity);
        let x = getRandomInt(left_extreme, right_extreme);
        let y = getRandomInt(up_extreme, bottom_extreme);
        let p = new particle(x, y, -intensity);
        nuclei.push(p);

        this_field.nuclei.push([x, y, intensity]);
    }

    for(let i = 0; i < target_num; i++) {
        let x = getRandomInt(left_extreme, right_extreme);
        let y = getRandomInt(up_extreme, bottom_extreme);
        target = new particle(x, y);

        this_field.target = [x, y];
    }

    console.log(JSON.stringify(this_field, null, 2));
}

function draw_nuclei() {
    for(let i = 0; i < nuclei.length; i++) {
        let size = PARTICLE_DRAW_SCALE * nuclei[i].intensity;
        // ellipse(nuclei[i].x, nuclei[i].y, size, size);
        if(nuclei[i].intensity > 0)
            image(pos_img, nuclei[i].x, nuclei[i].y, size, size);
        else
            image(neg_img, nuclei[i].x, nuclei[i].y, size, size)
    }
}

function collided_with_wall(x, y, i) {
    return x > walls[i].x && x < (walls[i].x + walls[i].width) &&
           y > walls[i].y && y < (walls[i].y + walls[i].height);

}
function update_ball() {
    if (ball.locked === false) {
        for (let j = 0; j < nuclei.length; j++) {
            ball.influenced_by(nuclei[j]);
        }
        ball.update();

        //Hit wall?
        for(let i = 0; i < walls.length; i++) {
            if(collided_with_wall(ball.x, ball.y, i))
                ball.die = true;
        }

        //Hit target?
        let distsq, dirx, diry;
        [distsq, [dirx, diry]] = ball.dist_from_particle(target);
        if(sqrt(distsq) < EPSILON) {
            ball.die = true;
            if(ball.die === true)
                end_game();
        }
    }
}

function draw_ball() {
    image(el_img, ball.x, ball.y, 10, 10);
}

function draw_target() {
    let size = PARTICLE_DRAW_SCALE * 6;
    image(target_img, target.x, target.y, size, size);
}

function draw_tails() {
    for(let j = 0; j < ball.history.length - 1; j++) {
        fill(Math.max(BACKGROUND, BACKGROUND + (j + 1) * (TAIL_VISIBILITY - BACKGROUND) / ball.history.length));
        ellipse(ball.history[j][0], ball.history[j][1], 3, 3);
    }
}

function draw_walls() {
    fill(193, 255, 193);
    stroke(200);
    rectMode(CORNER);
    for(let i = 0; i < walls.length; i++) {
        rect(walls[i].x, walls[i].y, walls[i].width, walls[i].height);
    }
}

function draw() {
    if(ball === null)
        return;
    console.log(ball.die);

    imageMode(CENTER);
    rectMode(CENTER);
    background(BACKGROUND);
    stroke(BACKGROUND);
    fill(255);
    smooth();

    if(ball.die === false)
        update_ball();
    draw_nuclei();
    draw_target();
    draw_ball();
    draw_tails();
    push();
    draw_walls();
    pop();

    if(ball.die === true && level < levels.length)
        ready_game();
    push();
    if(ball.locked === true)
        ready_shoot(ball);
    pop();

}

function dottedLine(x1, y1, x2, y2, dot_num) {
    for (let i = 0; i <= dot_num; i++) {
        let x = lerp(x1, x2, i/dot_num);
        let y = lerp(y1, y2, i/dot_num);
        point(x, y);
    }
}

function ready_shoot(p){
    stroke(250);
    let sx = mouseX;
    let sy = mouseY;
    let len = sqrt(pow(mouseX - p.x, 2) + pow(mouseY - p.y, 2));
    if(len > SHOT_RAD) {
        sx = (mouseX - p.x) * SHOT_RAD / len + p.x;
        sy = (mouseY - p.y) * SHOT_RAD / len + p.y;
    }
    dottedLine(p.x, p.y, sx, sy, 20);
    strokeWeight(.3);
    text(min(Math.round(len), SHOT_RAD).toString(), sx, sy + 20);
    // line(p.x, p.y, sx, sy);
    shootx = (sx - p.x) / 100;
    shooty = (sy - p.y) / 100;
}

function mousePressed() {
    if(ball.locked === true && mouseX < div_width) {
        ball.locked = false;
        ball.vx = shootx;
        ball.vy = shooty;
    }
}
let particle = function (x, y, intensity = 1, vx = 0, vy = 0, ax = 0, ay = 0, locked = false) {
    this.x = x;
    this.y = y;
    this.vx = vx;
    this.vy = vy;
    this.ax = ax;
    this.ay = ay;
    this.intensity = intensity;
    this.locked = locked;
    this.die = false;
    this.history = [];
    this.tick = 0;

    this.dist_from_particle = function(other) {
        // returns square of distance aith normalized direction
        let dx = other.x - this.x;
        let dy = other.y - this.y;
        let distsq = pow(dx, 2) + pow(dy, 2);
        let dirx = dx / sqrt(distsq);
        let diry = dy / sqrt(distsq);

        return [distsq, [dirx, diry]];
    };

    this.influenced_by = function(other) {
        let distsq, dirx, diry;
        [distsq, [dirx, diry]] = this.dist_from_particle(other);

        this.ax += dirx * G * this.intensity * other.intensity / distsq;
        this.ay += diry * G * this.intensity * other.intensity / distsq;

        if(sqrt(distsq) < EPSILON)
            this.die = true;
    };

    this.update = function() {
        this.tick++;
        this.vx += this.ax;
        this.vy += this.ay;

        if(this.tick % 3 === 0) {
            this.history.push([this.x, this.y]);
            if(this.history.length > TAIL_LENGTH)
                this.history.splice(0, 1);
        }
        this.x += this.vx;
        this.y += this.vy;

        this.ax = this.ay = 0;
    };
};

let wall = function (x, y, width, height) {
    this.x = x;
    this.y = y;
    this.width = width;
    this.height = height;
};<|endoftext|>