<commit_before>var nest = require('depnest')
var {Value, Proxy, Array: MutantArray, h, computed, when, onceTrue, throttle} = require('mutant')
var pull = require('pull-stream')
var Abortable = require('pull-abortable')
var Scroller = require('../../../lib/scroller')
var nextStepper = require('../../../lib/next-stepper')
var extend = require('xtend')
var paramap = require('pull-paramap')

var bumpMessages = {
  'vote': 'liked this message',
  'post': 'replied to this message',
  'about': 'added changes',
  'mention': 'mentioned you',
  'channel-mention': 'mentioned this channel'
}

// bump even for first message
var rootBumpTypes = ['mention', 'channel-mention']

exports.needs = nest({
  'about.obs.name': 'first',
  'app.sync.externalHandler': 'first',
  'message.html.canRender': 'first',
  'message.html.render': 'first',
  'message.sync.isBlocked': 'first',
  'message.sync.unbox': 'first',
  'message.sync.timestamp': 'first',
  'profile.html.person': 'first',
  'channel.html.link': 'first',
  'message.html.link': 'first',
  'message.sync.root': 'first',
  'feed.pull.rollup': 'first',
  'feed.pull.withReplies': 'first',
  'feed.pull.unique': 'first',
  'sbot.async.get': 'first',
  'keys.sync.id': 'first',
  'intl.sync.i18n': 'first',
  'intl.sync.i18n_n': 'first',
  'message.html.missing': 'first'
})

exports.gives = nest({
  'feed.html.rollup': true
})

exports.create = function (api) {
  const i18n = api.intl.sync.i18n
  const i18nPlural = api.intl.sync.i18n_n
  return nest('feed.html.rollup', function (getStream, {
    prepend,
    rootFilter = returnTrue,
    bumpFilter = returnTrue,
    compactFilter = returnFalse,
    prefiltered = false,
    displayFilter = returnTrue,
    updateStream, // override the stream used for realtime updates
    waitFor = true
  }) {
    var updates = Value(0)
    var yourId = api.keys.sync.id()
    var throttledUpdates = throttle(updates, 200)
    var updateLoader = h('a Notifier -loader', { href: '#', 'ev-click': refresh }, [
      'Show ', h('strong', [throttledUpdates]), ' ', plural(throttledUpdates, i18n('update'), i18n('updates'))
    ])

    var abortLastFeed = null
    var content = Value()
    var loading = Proxy(true)
    var unreadIds = new Set()
    var newSinceRefresh = new Set()
    var highlightItems = new Set()

    var container = h('Scroller', {
      style: { overflow: 'auto' },
      hooks: [(element) => {
        // don't activate until added to DOM
        refresh()

        // deactivate when removed from DOM
        return () => {
          if (abortLastFeed) {
            abortLastFeed()
            abortLastFeed = null
          }
        }
      }]
    }, [
      h('div.wrapper', [
        h('section.prepend', prepend),
        content,
        when(loading, h('Loading -large'))
      ])
    ])

    onceTrue(waitFor, () => {
      // display pending updates
      pull(
        updateStream || pull(
          getStream({old: false}),
          LookupRoot()
        ),
        pull.filter((msg) => {
          // only render posts that have a root message
          var root = msg.root || msg
          return root && root.value && root.value.content && rootFilter(root) && bumpFilter(msg, root) && displayFilter(msg)
        }),
        pull.drain((msg) => {
          if (msg.value.content.type === 'vote') return
          if (api.app.sync.externalHandler(msg)) return

          // Only increment the 'new since' for items that we render on
          // the feed as otherwise the 'show <n> updates message' will be
          // shown on new messages that patchwork cannot render
          if (canRenderMessage(msg) && msg.value.author !== yourId && (!msg.root || canRenderMessage(msg.root))) {
            newSinceRefresh.add(msg.key)
            unreadIds.add(msg.key)
          }

          if (updates() === 0 && msg.value.author === yourId && container.scrollTop < 20) {
            refresh()
          } else {
            updates.set(newSinceRefresh.size)
          }
        })
      )
    })

    var result = MutantArray([
      when(updates, updateLoader),
      container
    ])

    result.pendingUpdates = throttledUpdates
    result.reload = refresh

    return result

    function canRenderMessage (msg) {
      return api.message.html.canRender(msg)
    }

    function refresh () {
      onceTrue(waitFor, () => {
        if (abortLastFeed) abortLastFeed()
        updates.set(0)
        content.set(h('section.content'))

        var abortable = Abortable()
        abortLastFeed = abortable.abort

        highlightItems = newSinceRefresh
        newSinceRefresh = new Set()

        var done = Value(false)
        var stream = nextStepper(getStream, {reverse: true, limit: 50})
        var scroller = Scroller(container, content(), renderItem, {
          onDone: () => done.set(true),
          onItemVisible: (item) => {
            if (Array.isArray(item.msgIds)) {
              item.msgIds.forEach(id => {
                unreadIds.delete(id)
              })
            }
          }
        })

        // track loading state
        loading.set(computed([done, scroller.queue], (done, queue) => {
          return !done && queue < 5
        }))

        pull(
          stream,
          abortable,
          pull.filter(msg => msg && msg.value && msg.value.content),
          prefiltered ? pull(
            pull.filter(msg => !api.message.sync.isBlocked(msg)),
            pull.filter(rootFilter),
            api.feed.pull.unique(),
            api.feed.pull.withReplies()
          ) : pull(
            pull.filter(bumpFilter),
            api.feed.pull.rollup(rootFilter)
          ),
          scroller
        )
      })
    }

    function renderItem (item, opts) {
      var partial = opts && opts.partial
      var meta = null
      var previousId = item.key

      var groupedBumps = {}
      var lastBumpType = null

      var rootBumpType = bumpFilter(item)
      if (rootBumpTypes.includes(rootBumpType)) {
        lastBumpType = rootBumpType
        groupedBumps[lastBumpType] = [item]
      }

      item.replies.forEach(msg => {
        var value = bumpFilter(msg)
        if (value) {
          var type = typeof value === 'string' ? value : getType(msg)
          ;(groupedBumps[type] = groupedBumps[type] || []).unshift(msg)
          lastBumpType = type
        }
      })

      var replies = item.replies.filter(isReply).sort(byAssertedTime)
      var highlightedReplies = replies.filter(getPriority)
      var replyElements = replies.filter(displayFilter).slice(-3).map((msg) => {
        var result = api.message.html.render(msg, {
          previousId,
          compact: compactFilter(msg, item),
          priority: getPriority(msg)
        })
        previousId = msg.key

        return [
          // insert missing message marker (if can't be found)
          api.message.html.missing(last(msg.value.content.branch), msg),
          result
        ]
      })

      var renderedMessage = api.message.html.render(item, {
        compact: compactFilter(item),
        includeForks: false, // this is a root message, so forks are already displayed as replies
        priority: getPriority(item)
      })

      unreadIds.delete(item.key)

      if (!renderedMessage) return h('div')

      if (rootBumpType === 'matches-channel') {
        var channels = new Set()
        if (item.filterResult) {
          if (item.filterResult.matchesChannel) channels.add(item.value.content.channel)
          if (Array.isArray(item.filterResult.matchingTags)) item.filterResult.matchingTags.forEach(x => channels.add(x))
        }
        meta = h('div.meta', [
          many(channels, api.channel.html.link, i18n), ' ', i18n('mentioned in your network')
        ])
      } else if (lastBumpType) {
        var bumps = lastBumpType === 'vote'
          ? getLikeAuthors(groupedBumps[lastBumpType])
          : getAuthors(groupedBumps[lastBumpType])

        if (lastBumpType === 'matches-channel' && item.value.content.channel) {
          var channel = api.channel.html.link(item.value.content.channel)
          meta = h('div.meta', [
            i18nPlural('%s people from your network replied to this message on ', groupedBumps[lastBumpType].length), channel
          ])
        } else {
          var description = i18n(bumpMessages[lastBumpType] || 'added changes')
          meta = h('div.meta', [
            many(bumps, api.profile.html.person, i18n), ' ', description
          ])
        }
      }

      // if there are new messages, view full thread goes to the top of those, otherwise to very first reply
      var anchorReply = highlightedReplies.length >= 3 ? highlightedReplies[0] : replies[0]

      var result = h('FeedEvent -post', {
        attributes: {
          'data-root-id': item.key
        }
      }, [
        meta,
        renderedMessage,
        when(replies.length > replyElements.length || partial,
          h('a.full', {href: item.key, anchor: anchorReply && anchorReply.key}, [i18n('View full thread') + ' (', replies.length, ')'])
        ),
        h('div.replies', replyElements)
      ])

      result.msgIds = [item.key].concat(item.replies.map(x => x.key))

      return result
    }

    function getPriority (msg) {
      if (highlightItems.has(msg.key)) {
        return 2
      } else if (unreadIds.has(msg.key)) {
        return 1
      } else {
        return 0
      }
    }
  })

  function LookupRoot () {
    return paramap((msg, cb) => {
      var rootId = api.message.sync.root(msg)
      if (rootId) {
        api.sbot.async.get(rootId, (_, value) => {
          if (value && typeof value.content === 'string') {
            // unbox private message
            value = api.message.sync.unbox(value)
          }
          cb(null, extend(msg, {
            root: {key: rootId, value}
          }))
        })
      } else {
        cb(null, msg)
      }
    })
  }

  function byAssertedTime (a, b) {
    return api.message.sync.timestamp(a) - api.message.sync.timestamp(b)
  }
}

function plural (value, single, many) {
  return computed(value, (value) => {
    if (value === 1) {
      return single
    } else {
      return many
    }
  })
}

function many (ids, fn, intl) {
  ids = Array.from(ids)
  var featuredIds = ids.slice(0, 4)

  if (ids.length) {
    if (ids.length > 4) {
      return [
        fn(featuredIds[0]), ', ',
        fn(featuredIds[1]), ', ',
        fn(featuredIds[2]), intl(' and '),
        ids.length - 3, intl(' others')
      ]
    } else if (ids.length === 4) {
      return [
        fn(featuredIds[0]), ', ',
        fn(featuredIds[1]), ', ',
        fn(featuredIds[2]), intl(' and '),
        fn(featuredIds[3])
      ]
    } else if (ids.length === 3) {
      return [
        fn(featuredIds[0]), ', ',
        fn(featuredIds[1]), intl(' and '),
        fn(featuredIds[2])
      ]
    } else if (ids.length === 2) {
      return [
        fn(featuredIds[0]), intl(' and '),
        fn(featuredIds[1])
      ]
    } else {
      return fn(featuredIds[0])
    }
  }
}

function getAuthors (items) {
  return items.reduce((result, msg) => {
    result.add(msg.value.author)
    return result
  }, new Set())
}

function getLikeAuthors (items) {
  return items.reduce((result, msg) => {
    if (msg.value.content.type === 'vote') {
      if (msg.value.content && msg.value.content.vote && msg.value.content.vote.value === 1) {
        result.add(msg.value.author)
      } else {
        result.delete(msg.value.author)
      }
    }
    return result
  }, new Set())
}

function isReply (msg) {
  if (msg.value && msg.value.content) {
    var type = msg.value.content.type
    return type === 'post' || (type === 'about' && msg.value.content.attendee)
  }
}

function getType (msg) {
  return msg && msg.value && msg.value.content && msg.value.content.type
}

function returnTrue () {
  return true
}

function returnFalse () {
  return false
}

function last (array) {
  if (Array.isArray(array)) {
    return array[array.length - 1]
  } else {
    return array
  }
}
<commit_msg>dynamically insert your own new posts into rollup<commit_after>var nest = require('depnest')
var {Value, Proxy, Array: MutantArray, h, computed, when, onceTrue, throttle} = require('mutant')
var pull = require('pull-stream')
var Abortable = require('pull-abortable')
var Scroller = require('../../../lib/scroller')
var nextStepper = require('../../../lib/next-stepper')
var extend = require('xtend')
var paramap = require('pull-paramap')

var bumpMessages = {
  'vote': 'liked this message',
  'post': 'replied to this message',
  'about': 'added changes',
  'mention': 'mentioned you',
  'channel-mention': 'mentioned this channel'
}

// bump even for first message
var rootBumpTypes = ['mention', 'channel-mention']

exports.needs = nest({
  'about.obs.name': 'first',
  'app.sync.externalHandler': 'first',
  'message.html.canRender': 'first',
  'message.html.render': 'first',
  'message.sync.isBlocked': 'first',
  'message.sync.unbox': 'first',
  'message.sync.timestamp': 'first',
  'profile.html.person': 'first',
  'channel.html.link': 'first',
  'message.html.link': 'first',
  'message.sync.root': 'first',
  'feed.pull.rollup': 'first',
  'feed.pull.withReplies': 'first',
  'feed.pull.unique': 'first',
  'sbot.async.get': 'first',
  'keys.sync.id': 'first',
  'intl.sync.i18n': 'first',
  'intl.sync.i18n_n': 'first',
  'message.html.missing': 'first'
})

exports.gives = nest({
  'feed.html.rollup': true
})

exports.create = function (api) {
  const i18n = api.intl.sync.i18n
  const i18nPlural = api.intl.sync.i18n_n
  return nest('feed.html.rollup', function (getStream, {
    prepend,
    rootFilter = returnTrue,
    bumpFilter = returnTrue,
    compactFilter = returnFalse,
    prefiltered = false,
    displayFilter = returnTrue,
    updateStream, // override the stream used for realtime updates
    waitFor = true
  }) {
    var updates = Value(0)
    var yourId = api.keys.sync.id()
    var throttledUpdates = throttle(updates, 200)
    var updateLoader = h('a Notifier -loader', { href: '#', 'ev-click': refresh }, [
      'Show ', h('strong', [throttledUpdates]), ' ', plural(throttledUpdates, i18n('update'), i18n('updates'))
    ])

    var abortLastFeed = null
    var content = Value()
    var loading = Proxy(true)
    var unreadIds = new Set()
    var newSinceRefresh = new Set()
    var highlightItems = new Set()

    var container = h('Scroller', {
      style: { overflow: 'auto' },
      hooks: [(element) => {
        // don't activate until added to DOM
        refresh()

        // deactivate when removed from DOM
        return () => {
          if (abortLastFeed) {
            abortLastFeed()
            abortLastFeed = null
          }
        }
      }]
    }, [
      h('div.wrapper', [
        h('section.prepend', prepend),
        content,
        when(loading, h('Loading -large'))
      ])
    ])

    onceTrue(waitFor, () => {
      // display pending updates
      pull(
        updateStream || pull(
          getStream({old: false}),
          LookupRoot()
        ),
        pull.filter((msg) => {
          // only render posts that have a root message
          var root = msg.root || msg
          return root && root.value && root.value.content && rootFilter(root) && bumpFilter(msg, root) && displayFilter(msg)
        }),
        pull.drain((msg) => {
          if (msg.value.content.type === 'vote') return
          if (api.app.sync.externalHandler(msg)) return

          // Only increment the 'new since' for items that we render on
          // the feed as otherwise the 'show <n> updates message' will be
          // shown on new messages that patchwork cannot render
          if (canRenderMessage(msg) && msg.value.author !== yourId && (!msg.root || canRenderMessage(msg.root))) {
            newSinceRefresh.add(msg.key)
            unreadIds.add(msg.key)
          }

          if (updates() === 0 && msg.value.author === yourId && container.scrollTop < 20) {
            refresh()
          } else if (msg.value.author === yourId && content()) {
            // dynamically insert this post into the feed! (manually so that it doesn't get slow with mutant)
            var existingContainer = content().querySelector(`[data-root-id="${msg.value.content.root}"]`)
            if (existingContainer) {
              var replies = existingContainer.querySelector('div.replies')
              var lastReply = existingContainer.querySelector('div.replies > .Message:last-child')
              var previousId = lastReply ? lastReply.getAttribute('data-id') : existingContainer.getAttribute('data-root-id')
              replies.appendChild(api.message.html.render(msg, {
                previousId,
                compact: false,
                priority: 2
              }))
            }
          }

          updates.set(newSinceRefresh.size)
        })
      )
    })

    var result = MutantArray([
      when(updates, updateLoader),
      container
    ])

    result.pendingUpdates = throttledUpdates
    result.reload = refresh

    return result

    function canRenderMessage (msg) {
      return api.message.html.canRender(msg)
    }

    function refresh () {
      onceTrue(waitFor, () => {
        if (abortLastFeed) abortLastFeed()
        updates.set(0)
        content.set(h('section.content'))

        var abortable = Abortable()
        abortLastFeed = abortable.abort

        highlightItems = newSinceRefresh
        newSinceRefresh = new Set()

        var done = Value(false)
        var stream = nextStepper(getStream, {reverse: true, limit: 50})
        var scroller = Scroller(container, content(), renderItem, {
          onDone: () => done.set(true),
          onItemVisible: (item) => {
            if (Array.isArray(item.msgIds)) {
              item.msgIds.forEach(id => {
                unreadIds.delete(id)
              })
            }
          }
        })

        // track loading state
        loading.set(computed([done, scroller.queue], (done, queue) => {
          return !done && queue < 5
        }))

        pull(
          stream,
          abortable,
          pull.filter(msg => msg && msg.value && msg.value.content),
          prefiltered ? pull(
            pull.filter(msg => !api.message.sync.isBlocked(msg)),
            pull.filter(rootFilter),
            api.feed.pull.unique(),
            api.feed.pull.withReplies()
          ) : pull(
            pull.filter(bumpFilter),
            api.feed.pull.rollup(rootFilter)
          ),
          scroller
        )
      })
    }

    function renderItem (item, opts) {
      var partial = opts && opts.partial
      var meta = null
      var previousId = item.key

      var groupedBumps = {}
      var lastBumpType = null

      var rootBumpType = bumpFilter(item)
      if (rootBumpTypes.includes(rootBumpType)) {
        lastBumpType = rootBumpType
        groupedBumps[lastBumpType] = [item]
      }

      item.replies.forEach(msg => {
        var value = bumpFilter(msg)
        if (value) {
          var type = typeof value === 'string' ? value : getType(msg)
          ;(groupedBumps[type] = groupedBumps[type] || []).unshift(msg)
          lastBumpType = type
        }
      })

      var replies = item.replies.filter(isReply).sort(byAssertedTime)
      var highlightedReplies = replies.filter(getPriority)
      var replyElements = replies.filter(displayFilter).slice(-3).map((msg) => {
        var result = api.message.html.render(msg, {
          previousId,
          compact: compactFilter(msg, item),
          priority: getPriority(msg)
        })
        previousId = msg.key

        return [
          // insert missing message marker (if can't be found)
          api.message.html.missing(last(msg.value.content.branch), msg),
          result
        ]
      })

      var renderedMessage = api.message.html.render(item, {
        compact: compactFilter(item),
        includeForks: false, // this is a root message, so forks are already displayed as replies
        priority: getPriority(item)
      })

      unreadIds.delete(item.key)

      if (!renderedMessage) return h('div')

      if (rootBumpType === 'matches-channel') {
        var channels = new Set()
        if (item.filterResult) {
          if (item.filterResult.matchesChannel) channels.add(item.value.content.channel)
          if (Array.isArray(item.filterResult.matchingTags)) item.filterResult.matchingTags.forEach(x => channels.add(x))
        }
        meta = h('div.meta', [
          many(channels, api.channel.html.link, i18n), ' ', i18n('mentioned in your network')
        ])
      } else if (lastBumpType) {
        var bumps = lastBumpType === 'vote'
          ? getLikeAuthors(groupedBumps[lastBumpType])
          : getAuthors(groupedBumps[lastBumpType])

        if (lastBumpType === 'matches-channel' && item.value.content.channel) {
          var channel = api.channel.html.link(item.value.content.channel)
          meta = h('div.meta', [
            i18nPlural('%s people from your network replied to this message on ', groupedBumps[lastBumpType].length), channel
          ])
        } else {
          var description = i18n(bumpMessages[lastBumpType] || 'added changes')
          meta = h('div.meta', [
            many(bumps, api.profile.html.person, i18n), ' ', description
          ])
        }
      }

      // if there are new messages, view full thread goes to the top of those, otherwise to very first reply
      var anchorReply = highlightedReplies.length >= 3 ? highlightedReplies[0] : replies[0]

      var result = h('FeedEvent -post', {
        attributes: {
          'data-root-id': item.key
        }
      }, [
        meta,
        renderedMessage,
        when(replies.length > replyElements.length || partial,
          h('a.full', {href: item.key, anchor: anchorReply && anchorReply.key}, [i18n('View full thread') + ' (', replies.length, ')'])
        ),
        h('div.replies', replyElements)
      ])

      result.msgIds = [item.key].concat(item.replies.map(x => x.key))

      return result
    }

    function getPriority (msg) {
      if (highlightItems.has(msg.key)) {
        return 2
      } else if (unreadIds.has(msg.key)) {
        return 1
      } else {
        return 0
      }
    }
  })

  function LookupRoot () {
    return paramap((msg, cb) => {
      var rootId = api.message.sync.root(msg)
      if (rootId) {
        api.sbot.async.get(rootId, (_, value) => {
          if (value && typeof value.content === 'string') {
            // unbox private message
            value = api.message.sync.unbox(value)
          }
          cb(null, extend(msg, {
            root: {key: rootId, value}
          }))
        })
      } else {
        cb(null, msg)
      }
    })
  }

  function byAssertedTime (a, b) {
    return api.message.sync.timestamp(a) - api.message.sync.timestamp(b)
  }
}

function plural (value, single, many) {
  return computed(value, (value) => {
    if (value === 1) {
      return single
    } else {
      return many
    }
  })
}

function many (ids, fn, intl) {
  ids = Array.from(ids)
  var featuredIds = ids.slice(0, 4)

  if (ids.length) {
    if (ids.length > 4) {
      return [
        fn(featuredIds[0]), ', ',
        fn(featuredIds[1]), ', ',
        fn(featuredIds[2]), intl(' and '),
        ids.length - 3, intl(' others')
      ]
    } else if (ids.length === 4) {
      return [
        fn(featuredIds[0]), ', ',
        fn(featuredIds[1]), ', ',
        fn(featuredIds[2]), intl(' and '),
        fn(featuredIds[3])
      ]
    } else if (ids.length === 3) {
      return [
        fn(featuredIds[0]), ', ',
        fn(featuredIds[1]), intl(' and '),
        fn(featuredIds[2])
      ]
    } else if (ids.length === 2) {
      return [
        fn(featuredIds[0]), intl(' and '),
        fn(featuredIds[1])
      ]
    } else {
      return fn(featuredIds[0])
    }
  }
}

function getAuthors (items) {
  return items.reduce((result, msg) => {
    result.add(msg.value.author)
    return result
  }, new Set())
}

function getLikeAuthors (items) {
  return items.reduce((result, msg) => {
    if (msg.value.content.type === 'vote') {
      if (msg.value.content && msg.value.content.vote && msg.value.content.vote.value === 1) {
        result.add(msg.value.author)
      } else {
        result.delete(msg.value.author)
      }
    }
    return result
  }, new Set())
}

function isReply (msg) {
  if (msg.value && msg.value.content) {
    var type = msg.value.content.type
    return type === 'post' || (type === 'about' && msg.value.content.attendee)
  }
}

function getType (msg) {
  return msg && msg.value && msg.value.content && msg.value.content.type
}

function returnTrue () {
  return true
}

function returnFalse () {
  return false
}

function last (array) {
  if (Array.isArray(array)) {
    return array[array.length - 1]
  } else {
    return array
  }
}
<|endoftext|>


<commit_before>/**
 * @file betFlip command
 * @author Sankarsan Kampa (a.k.a k3rn31p4nic)
 * @license MIT
 */

const sql = require('sqlite');
sql.open('./data/Bastion.sqlite');
let recentUsers = [];

exports.run = (Bastion, message, args) => {
  if (!recentUsers.includes(message.author.id)) {
    if (!(args[0] = parseInt(args[0])) || !/^(heads|tails)$/i.test(args[1]) || args[0] < 1) {
      return message.channel.send({
        embed: {
          color: Bastion.colors.yellow,
          title: 'Usage',
          description: `\`${Bastion.config.prefix}${this.help.usage}\``
        }
      }).catch(e => {
        Bastion.log.error(e.stack);
      });
    }

    if (args[0] < 5) {
      return message.channel.send({
        embed: {
          color: Bastion.colors.red,
          description: 'Minimum bet amount is 5 Bastion Currencies.'
        }
      }).catch(e => {
        Bastion.log.error(e.stack);
      });
    }

    let outcomes = [
      'Heads',
      'Tails'
    ];
    let outcome = outcomes[Math.floor(Math.random() * outcomes.length)];
    // let outcome = outcomes.random();

    sql.get(`SELECT bastionCurrencies FROM profiles WHERE userID=${message.author.id}`).then(profile => {
      if (args[0] > profile.bastionCurrencies) {
        return message.channel.send({
          embed: {
            color: Bastion.colors.red,
            description: `Unfortunately, you can't bet. You only have **${profile.bastionCurrencies}** Bastion Currencies.`
          }
        }).catch(e => {
          Bastion.log.error(e.stack);
        });
      }

      recentUsers.push(message.author.id);

      let result;
      if (outcome.toLowerCase() === args[1].toLowerCase()) {
        let prize = args[0] < 50 ? parseInt(args[0]) + outcomes.length : args[0] < 100 ? parseInt(args[0]) : parseInt(args[0]) * 1.5;
        result = `Congratulations! You won the bet.\nYou won **${prize}** Bastion Currencies.`;
        sql.run(`UPDATE profiles SET bastionCurrencies=${parseInt(profile.bastionCurrencies) + parseInt(prize)} WHERE userID=${message.author.id}`).catch(e => {
          Bastion.log.error(e.stack);
        });
      }
      else {
        result = 'Sorry, you lost the bet. Better luck next time.';
        sql.run(`UPDATE profiles SET bastionCurrencies=${parseInt(profile.bastionCurrencies) - parseInt(args[0])} WHERE userID=${message.author.id}`).catch(e => {
          Bastion.log.error(e.stack);
        });
      }
      message.channel.send({
        embed: {
          color: Bastion.colors.blue,
          title: `Flipped ${outcome}`,
          description: result
        }
      }).then(() => {
        setTimeout(function () {
          recentUsers.splice(recentUsers.indexOf(message.author.id), 1);
        }, 60 * 1000);
      }).catch(e => {
        Bastion.log.error(e.stack);
      });
    }).catch(e => {
      Bastion.log.error(e.stack);
    });
  }
  else {
    message.channel.send({
      embed: {
        color: Bastion.colors.red,
        description: `${message.author} you have gambled recently for this game, please wait at least 60 seconds before gambling again.`
      }
    }).catch(e => {
      Bastion.log.error(e.stack);
    });
  }
};

exports.config = {
  aliases: [ 'bf' ],
  enabled: true
};

exports.help = {
  name: 'betflip',
  description: 'Bets a specified amount of Bastion currency on prediction of the outcome of flipping a coin. If you win, you win more Bastion Currencies. If you lose, you lose the amount of currency you\'ve bet.',
  botPermission: '',
  userPermission: '',
  usage: 'betflip <amount> <heads|tails>',
  example: [ 'betflip 100 heads' ]
};
<commit_msg>Implemented userCredit & userDebit events in betFlip command<commit_after>/**
 * @file betFlip command
 * @author Sankarsan Kampa (a.k.a k3rn31p4nic)
 * @license MIT
 */

const sql = require('sqlite');
sql.open('./data/Bastion.sqlite');
let recentUsers = [];

exports.run = (Bastion, message, args) => {
  if (!recentUsers.includes(message.author.id)) {
    if (!(args[0] = parseInt(args[0])) || !/^(heads|tails)$/i.test(args[1]) || args[0] < 1) {
      return message.channel.send({
        embed: {
          color: Bastion.colors.yellow,
          title: 'Usage',
          description: `\`${Bastion.config.prefix}${this.help.usage}\``
        }
      }).catch(e => {
        Bastion.log.error(e.stack);
      });
    }

    if (args[0] < 5) {
      return message.channel.send({
        embed: {
          color: Bastion.colors.red,
          description: 'Minimum bet amount is 5 Bastion Currencies.'
        }
      }).catch(e => {
        Bastion.log.error(e.stack);
      });
    }

    let outcomes = [
      'Heads',
      'Tails'
    ];
    let outcome = outcomes[Math.floor(Math.random() * outcomes.length)];
    // let outcome = outcomes.random();

    sql.get(`SELECT bastionCurrencies FROM profiles WHERE userID=${message.author.id}`).then(profile => {
      if (args[0] > profile.bastionCurrencies) {
        return message.channel.send({
          embed: {
            color: Bastion.colors.red,
            description: `Unfortunately, you can't bet. You only have **${profile.bastionCurrencies}** Bastion Currencies.`
          }
        }).catch(e => {
          Bastion.log.error(e.stack);
        });
      }

      recentUsers.push(message.author.id);

      let result;
      if (outcome.toLowerCase() === args[1].toLowerCase()) {
        let prize = args[0] < 50 ? parseInt(args[0]) + outcomes.length : args[0] < 100 ? parseInt(args[0]) : parseInt(args[0]) * 1.5;
        result = `Congratulations! You won the bet.\nYou won **${prize}** Bastion Currencies.`;
        Bastion.emit('userDebit', message.author, prize);

      }
      else {
        result = 'Sorry, you lost the bet. Better luck next time.';
        Bastion.emit('userCredit', message.author, args[0]);
      }
      message.channel.send({
        embed: {
          color: Bastion.colors.blue,
          title: `Flipped ${outcome}`,
          description: result
        }
      }).then(() => {
        setTimeout(function () {
          recentUsers.splice(recentUsers.indexOf(message.author.id), 1);
        }, 60 * 1000);
      }).catch(e => {
        Bastion.log.error(e.stack);
      });
    }).catch(e => {
      Bastion.log.error(e.stack);
    });
  }
  else {
    message.channel.send({
      embed: {
        color: Bastion.colors.red,
        description: `${message.author} you have gambled recently for this game, please wait at least 60 seconds before gambling again.`
      }
    }).catch(e => {
      Bastion.log.error(e.stack);
    });
  }
};

exports.config = {
  aliases: [ 'bf' ],
  enabled: true
};

exports.help = {
  name: 'betflip',
  description: 'Bets a specified amount of Bastion currency on prediction of the outcome of flipping a coin. If you win, you win more Bastion Currencies. If you lose, you lose the amount of currency you\'ve bet.',
  botPermission: '',
  userPermission: '',
  usage: 'betflip <amount> <heads|tails>',
  example: [ 'betflip 100 heads' ]
};
<|endoftext|>


<commit_before>import View from "../view/View";

export default class MapView extends View
{
    metadata = {
		properties: {
			defaultCenterLocation: {
				type: "object",
                defaultValue: [ 32.04389, 118.77881 ]
			},
			defaultZoom: {
				type: "int",
				defaultValue: 15
			},
			minZoom: {
				type: "int",
				defaultValue: 11
			},
			maxZoom: {
				type: "int",
				defaultValue: 17
			},
			allowZoom: {
				type: "boolean",
				defaultValue: true
			},
			allowDrag: {
				type: "boolean",
				defaultValue: true
			}
		}
    };

    init()
    {
        super.init();
        this.addStyleClass("sap-a-map-view");
        this._initMap();

        this.attachAddedToParent(() => {
			setTimeout(() => {
				this.invalidateSize();
			});
		});
    }

    _initMap()
    {
        const options = {
            zoomControl: false,
			attributionControl: false,
			center: this.getDefaultCenterLocation(),
			zoom: this.getDefaultZoom(),
			minZoom: this.getMinZoom(),
			maxZoom: this.getMaxZoom(),
			dragging: this.getAllowDrag(),
			scrollWheelZoom: this.getAllowZoom(),
			doubleClickZoom: this.getAllowZoom()
        };
        this.map = L.map(this.$element[0], options);
        L.tileLayer("http://{s}.tile.osm.org/{z}/{x}/{y}.png").addTo(this.map);
    }

    invalidateSize(...args)
    {
        this.map.invalidateSize(...args);
    }
}
<commit_msg>add init function<commit_after>import View from "../view/View";

export default class MapView extends View
{
    metadata = {
		properties: {
			defaultCenterLocation: {
				type: "object",
                defaultValue: [ 32.04389, 118.77881 ]
			},
			defaultZoom: {
				type: "int",
				defaultValue: 15
			},
			minZoom: {
				type: "int",
				defaultValue: 11
			},
			maxZoom: {
				type: "int",
				defaultValue: 17
			},
			allowZoom: {
				type: "boolean",
				defaultValue: true
			},
			allowDrag: {
				type: "boolean",
				defaultValue: true
			}
		}
    };

    init()
    {
        super.init();
        this.addStyleClass("sap-a-map-view");
        this._initMap();

        this.attachAddedToParent(() => {
			setTimeout(() => {
				this.invalidateSize();
			});
		});
    }

    _initMap()
    {
        const options = {
            zoomControl: true,
			attributionControl: false,
			center: this.getDefaultCenterLocation(),
			zoom: this.getDefaultZoom(),
			minZoom: this.getMinZoom(),
			maxZoom: this.getMaxZoom(),
			dragging: this.getAllowDrag(),
			scrollWheelZoom: this.getAllowZoom(),
			doubleClickZoom: this.getAllowZoom()
        };
        this.map = L.map(this.$element[0], options);
        L.tileLayer("http://{s}.tile.osm.org/{z}/{x}/{y}.png").addTo(this.map);
    }

    getCenterLocation()
    {
        return this.getCenterLocation();
    }

    setCenterLocation(centerLocation, zoom, options)
    {
        this.map.setView(centerLocation, zoom, options);
    }

    getBounds()
    {
        return this.map.getBounds();
    }

    setBounds(bounds)
    {
        this.map.fitBounds(bounds);
    }

    getZoom()
    {
        return this.map.getZoom();
    }

    setZoom(zoom)
    {
        this.map.setZoom();
    }

    invalidateSize(...args)
    {
        this.map.invalidateSize(...args);
    }
}
<|endoftext|>


<commit_before>Entry.Comment = class Comment {
    dragMode = Entry.DRAG_MODE_NONE;
    offsetX = 50;
    offsetY = 10;

    constructor(block, board) {
        Entry.Model(this, false);

        if (!board.svgCommentGroup) {
            return;
        }
        const { comment, view } = block;
        const { svgGroup } = view || {};
        this._block = block;
        this._board = board;
        this._blockView = view;
        if (svgGroup && !(board instanceof Entry.BlockMenu)) {
            console.time('a');
            this.createComment();
            this.startRender();
            this.addControl();
            console.timeEnd('a');
        }

        this.observe(this, 'updateOpacity', ['visible'], false);
    }

    get block() {
        return this._block;
    }

    get board() {
        return this._board;
    }

    get blockView() {
        return this._blockView;
    }

    createComment() {
        // const { comment, view } = this.block;
        let thread = this.block.getThread();
        while (!(thread.parent instanceof Entry.Code)) {
            if (thread instanceof Entry.FieldBlock) {
                thread = thread.getParentThread();
            } else {
                thread = thread.parent.getThread();
            }
        }
        const { svgGroup, pathGroup } = this.blockView || {};
        this.pathGroup = pathGroup;
        this.parentGroup = svgGroup;
        this.svgGroup = this._blockView.commentShapeGroup;
        this.mouseDown = this.mouseDown.bind(this);
        this.mouseMove = this.mouseMove.bind(this);
        this.mouseUp = this.mouseUp.bind(this);
    }

    startRender() {
        if (this.svgGroup) {
            this._line = this.svgGroup.elem('line');
            this._comment = this.svgGroup.elem('rect');

            const { width } = this.pathGroup.getBBox();
            const { topFieldHeight, height } = this._blockView;
            const startX = width;
            const startY = 0;
            const defaultLineLength = this.defaultLineLength;
            const lineHeight = (topFieldHeight || height) / 2;
            this._comment.attr({
                width: this.commentWidth,
                height: this.commentTitleHeight,
                x: startX + defaultLineLength,
                y: startY + lineHeight - this.commentTitleHeight / 2,
                stroke: '#EDA913',
                fill: '#FBB315',
                rx: '4',
            });

            this._line.attr({
                x1: startX,
                y1: startY + lineHeight,
                x2: startX + defaultLineLength,
                y2: startY + lineHeight,
                style: 'stroke:#eda913;stroke-width:2',
            });

            this.set({
                startX,
                startY: startY + lineHeight,
            });
            this.canRender = true;
        }
    }

    updatePos() {
        if (this.pathGroup) {
            const { width } = this.pathGroup.getBBox();
            const matrix = this.parentGroup.getCTM();
            const { x: pathX, y: pathY } = Entry.GlobalSvg.getRelativePoint(matrix);
            const startX = pathX + width + this.defaultLineLength;
            const startY = pathY;

            this._line.attr({
                x2: startX,
                y2: startY + this.commentTitleHeight / 2,
            });

            this.set({
                startX,
                startY,
            });
        }
    }

    moveTo(x, y) {
        const thisX = this.x;
        const thisY = this.y;
        if (!this.display) {
            x = -99999;
            y = -99999;
        }
        if (thisX !== x || thisY !== y) {
            this.set({ x, y });
        }
        if (this.visible && this.display) {
            this.setPosition();
        }
    }

    moveBy(x, y) {
        return this.moveTo(this.x + x, this.y + y);
    }

    setPosition() {
        this._comment.attr({
            x: this.x + this.startX + this.defaultLineLength,
            y: this.y + this.startY - this.commentTitleHeight / 2,
        });

        this._line.attr({
            x2: this.x + this.startX + this.commentWidth / 2 + this.defaultLineLength,
            y2: this.y + this.startY,
        });
    }

    mouseDown(e) {
        e.preventDefault();
        e.stopPropagation();

        const { scale = 1 } = this.board || {};
        if (
            (e.button === 0 || (e.originalEvent && e.originalEvent.touches)) &&
            !this._board.readOnly
        ) {
            const mouseEvent = Entry.Utils.convertMouseEvent(e);
            const matrix = this.svgGroup.getCTM();
            const { x, y } = Entry.GlobalSvg.getRelativePoint(matrix);
            this.mouseDownCoordinate = {
                x: mouseEvent.pageX,
                y: mouseEvent.pageY,
                parentX: x,
                parentY: y,
            };
            document.onmousemove = this.mouseMove;
            document.ontouchmove = this.mouseMove;
            document.onmouseup = this.mouseUp;
            document.ontouchend = this.mouseUp;
            this.dragInstance = new Entry.DragInstance({
                startX: (x + this.startX) / scale,
                startY: (y + this.startY) / scale,
                offsetX: mouseEvent.pageX,
                offsetY: mouseEvent.pageY,
                mode: true,
            });
            this.dragMode = Entry.DRAG_MODE_MOUSEDOWN;
        }
    }

    mouseMove(e) {
        e.preventDefault();
        e.stopPropagation();
        const mouseEvent = Entry.Utils.convertMouseEvent(e);
        const diff = Math.sqrt(
            Math.pow(mouseEvent.pageX - this.mouseDownCoordinate.x, 2) +
                Math.pow(mouseEvent.pageY - this.mouseDownCoordinate.y, 2)
        );
        if (this.dragMode == Entry.DRAG_MODE_DRAG || diff > Entry.BlockView.DRAG_RADIUS) {
            this.set({ visible: false });
            const workspaceMode = this.board.workspace.getMode();

            const dragInstance = this.dragInstance;
            const { scale = 1 } = this.board || {};
            if (this.dragMode != Entry.DRAG_MODE_DRAG) {
                this.dragMode = Entry.DRAG_MODE_DRAG;
                Entry.GlobalSvg.setComment(this, workspaceMode);
                const offset = this.board.offset();
                Entry.GlobalSvg._applyDomPos(offset.left / scale, offset.top / scale);
            }
            this.moveBy(
                (mouseEvent.pageX - dragInstance.offsetX) / scale,
                (mouseEvent.pageY - dragInstance.offsetY) / scale,
                false,
                true
            );

            dragInstance.set({
                offsetX: mouseEvent.pageX,
                offsetY: mouseEvent.pageY,
            });

            Entry.GlobalSvg.commentPosition(dragInstance);
        }
    }

    mouseUp(e) {
        e.preventDefault();
        e.stopPropagation();
        this.dragMode = Entry.DRAG_MODE_NONE;
        document.onmousemove = undefined;
        document.ontouchmove = undefined;
        document.onmouseup = undefined;
        document.ontouchend = undefined;
        this.board.set({ dragBlock: null });
        Entry.GlobalSvg.remove();
        this.set({ visible: true });
        this.setPosition();
        delete this.mouseDownCoordinate;
        delete this.dragInstance;
    }

    addControl() {
        const dom = this.svgGroup;
        dom.onmousedown = this.mouseDown;
        dom.ontouchstart = this.mouseDown;
    }

    updateOpacity = function() {
        if (this.visible === false) {
            this.svgGroup.attr({ opacity: 0 });
        } else {
            this.svgGroup.removeAttr('opacity');
            this.setPosition();
        }
    };

    isReadOnly() {
        return this.readOnly;
    }

    getBoard() {
        return undefined;
    }

    getAbsoluteCoordinate(dragMode = this.dragMode) {
        const { scale = 1 } = this.board || {};
        let pos = null;
        const { parentX, parentY } = this.mouseDownCoordinate;
        const posX = this.x + this.startX + parentX + this.defaultLineLength;
        const posY = this.y + this.startY + parentY;
        if (dragMode === Entry.DRAG_MODE_DRAG) {
            pos = {
                x: posX,
                y: posY,
                scaleX: posX / scale,
                scaleY: posY / scale,
            };
        } else {
            pos = this.block.getThread().view.requestAbsoluteCoordinate(this);
            pos.x += posX;
            pos.y += posY;
            pos.scaleX = pos.x / scale;
            pos.scaleY = pos.y / scale;
        }
        return pos;
    }
};

Entry.Comment.prototype.schema = {
    x: 0,
    y: 0,
    moveX: 0,
    moveY: 0,
    startX: 0,
    startY: 0,
    readOnly: false,
    visible: true,
    display: true,
    movable: true,
    commentWidth: 160,
    commentTitleHeight: 22,
    defaultLineLength: 120,
};
<commit_msg>edit comment line start position<commit_after>Entry.Comment = class Comment {
    dragMode = Entry.DRAG_MODE_NONE;
    offsetX = 50;
    offsetY = 10;

    constructor(block, board) {
        Entry.Model(this, false);

        if (!board.svgCommentGroup) {
            return;
        }
        const { comment, view } = block;
        const { svgGroup } = view || {};
        this._block = block;
        this._board = board;
        this._blockView = view;
        if (svgGroup && !(board instanceof Entry.BlockMenu)) {
            console.time('a');
            this.createComment();
            this.startRender();
            this.addControl();
            console.timeEnd('a');
        }

        this.observe(this, 'updateOpacity', ['visible'], false);
    }

    get block() {
        return this._block;
    }

    get board() {
        return this._board;
    }

    get blockView() {
        return this._blockView;
    }

    createComment() {
        // const { comment, view } = this.block;
        let thread = this.block.getThread();
        while (!(thread.parent instanceof Entry.Code)) {
            if (thread instanceof Entry.FieldBlock) {
                thread = thread.getParentThread();
            } else {
                thread = thread.parent.getThread();
            }
        }
        const { svgGroup, pathGroup } = this.blockView || {};
        this.pathGroup = pathGroup;
        this.parentGroup = svgGroup;
        this.svgGroup = this._blockView.commentShapeGroup;
        this.mouseDown = this.mouseDown.bind(this);
        this.mouseMove = this.mouseMove.bind(this);
        this.mouseUp = this.mouseUp.bind(this);
    }

    startRender() {
        if (this.svgGroup) {
            this._line = this.svgGroup.elem('line');
            this._comment = this.svgGroup.elem('rect');

            const { width } = this.pathGroup.getBBox();
            const { topFieldHeight, height } = this._blockView;
            const startX = width;
            const startY = 0;
            const defaultLineLength = this.defaultLineLength;
            const lineHeight = (topFieldHeight || height) / 2;
            this._comment.attr({
                width: this.commentWidth,
                height: this.commentTitleHeight,
                x: startX + defaultLineLength,
                y: startY + lineHeight - this.commentTitleHeight / 2,
                stroke: '#EDA913',
                fill: '#FBB315',
                rx: '4',
            });

            this._line.attr({
                x1: startX,
                y1: startY + lineHeight,
                x2: startX + defaultLineLength,
                y2: startY + lineHeight,
                style: 'stroke:#eda913;stroke-width:2',
            });

            this.set({
                startX,
                startY: startY + lineHeight,
            });
            this.canRender = true;
        }
    }

    updatePos() {
        if (this.pathGroup) {
            const { width } = this.pathGroup.getBBox();
            const matrix = this.parentGroup.getCTM();
            const { x: pathX, y: pathY } = Entry.GlobalSvg.getRelativePoint(matrix);
            const startX = pathX + width + this.defaultLineLength;
            const startY = pathY;

            this._line.attr({
                x2: startX,
                y2: startY + this.commentTitleHeight / 2,
            });

            this.set({
                startX,
                startY,
            });
        }
    }

    moveTo(x, y) {
        const thisX = this.x;
        const thisY = this.y;
        if (!this.display) {
            x = -99999;
            y = -99999;
        }
        if (thisX !== x || thisY !== y) {
            this.set({ x, y });
        }
        if (this.visible && this.display) {
            this.setPosition();
        }
    }

    moveBy(x, y) {
        return this.moveTo(this.x + x, this.y + y);
    }

    setPosition() {
        this._comment.attr({
            x: this.x + this.startX + this.defaultLineLength,
            y: this.y + this.startY - this.commentTitleHeight / 2,
        });

        this._line.attr({
            x2: this.x + this.startX + this.commentWidth / 2 + this.defaultLineLength,
            y2: this.y + this.startY,
        });
    }

    mouseDown(e) {
        e.preventDefault();
        e.stopPropagation();

        const { scale = 1 } = this.board || {};
        if (
            (e.button === 0 || (e.originalEvent && e.originalEvent.touches)) &&
            !this._board.readOnly
        ) {
            const mouseEvent = Entry.Utils.convertMouseEvent(e);
            const matrix = this.svgGroup.getCTM();
            const { x, y } = Entry.GlobalSvg.getRelativePoint(matrix);
            this.mouseDownCoordinate = {
                x: mouseEvent.pageX,
                y: mouseEvent.pageY,
                parentX: x,
                parentY: y,
            };
            document.onmousemove = this.mouseMove;
            document.ontouchmove = this.mouseMove;
            document.onmouseup = this.mouseUp;
            document.ontouchend = this.mouseUp;
            console.log(x, this.startX);
            this.dragInstance = new Entry.DragInstance({
                startX: x / scale + this.startX,
                startY: y / scale + this.startY,
                offsetX: mouseEvent.pageX,
                offsetY: mouseEvent.pageY,
                mode: true,
            });
            this.dragMode = Entry.DRAG_MODE_MOUSEDOWN;
        }
    }

    mouseMove(e) {
        e.preventDefault();
        e.stopPropagation();
        const mouseEvent = Entry.Utils.convertMouseEvent(e);
        const diff = Math.sqrt(
            Math.pow(mouseEvent.pageX - this.mouseDownCoordinate.x, 2) +
                Math.pow(mouseEvent.pageY - this.mouseDownCoordinate.y, 2)
        );
        if (this.dragMode == Entry.DRAG_MODE_DRAG || diff > Entry.BlockView.DRAG_RADIUS) {
            this.set({ visible: false });
            const workspaceMode = this.board.workspace.getMode();

            const dragInstance = this.dragInstance;
            const { scale = 1 } = this.board || {};
            if (this.dragMode != Entry.DRAG_MODE_DRAG) {
                this.dragMode = Entry.DRAG_MODE_DRAG;
                Entry.GlobalSvg.setComment(this, workspaceMode);
                const offset = this.board.offset();
                Entry.GlobalSvg._applyDomPos(offset.left / scale, offset.top / scale);
            }
            this.moveBy(
                (mouseEvent.pageX - dragInstance.offsetX) / scale,
                (mouseEvent.pageY - dragInstance.offsetY) / scale,
                false,
                true
            );

            dragInstance.set({
                offsetX: mouseEvent.pageX,
                offsetY: mouseEvent.pageY,
            });

            Entry.GlobalSvg.commentPosition(dragInstance);
        }
    }

    mouseUp(e) {
        e.preventDefault();
        e.stopPropagation();
        this.dragMode = Entry.DRAG_MODE_NONE;
        document.onmousemove = undefined;
        document.ontouchmove = undefined;
        document.onmouseup = undefined;
        document.ontouchend = undefined;
        this.board.set({ dragBlock: null });
        Entry.GlobalSvg.remove();
        this.set({ visible: true });
        this.setPosition();
        delete this.mouseDownCoordinate;
        delete this.dragInstance;
    }

    addControl() {
        const dom = this.svgGroup;
        dom.onmousedown = this.mouseDown;
        dom.ontouchstart = this.mouseDown;
    }

    updateOpacity = function() {
        if (this.visible === false) {
            this.svgGroup.attr({ opacity: 0 });
        } else {
            this.svgGroup.removeAttr('opacity');
            this.setPosition();
        }
    };

    isReadOnly() {
        return this.readOnly;
    }

    getBoard() {
        return undefined;
    }

    getAbsoluteCoordinate(dragMode = this.dragMode) {
        const { scale = 1 } = this.board || {};
        let pos = null;
        const { parentX, parentY } = this.mouseDownCoordinate;
        const posX = this.x + this.startX + parentX + this.defaultLineLength;
        const posY = this.y + this.startY + parentY;
        if (dragMode === Entry.DRAG_MODE_DRAG) {
            pos = {
                x: posX,
                y: posY,
                scaleX: this.x + this.startX + parentX / scale + this.defaultLineLength,
                scaleY: this.y + this.startY + parentY / scale,
            };
        } else {
            pos = this.block.getThread().view.requestAbsoluteCoordinate(this);
            pos.x += posX;
            pos.y += posY;
            pos.scaleX = pos.x / scale;
            pos.scaleY = pos.y / scale;
        }
        return pos;
    }
};

Entry.Comment.prototype.schema = {
    x: 0,
    y: 0,
    moveX: 0,
    moveY: 0,
    startX: 0,
    startY: 0,
    readOnly: false,
    visible: true,
    display: true,
    movable: true,
    commentWidth: 160,
    commentTitleHeight: 22,
    defaultLineLength: 120,
};
<|endoftext|>


<commit_before>'use strict';

var schemautil = module.exports = {},
  util = require('../util');

var isEmpty = function(obj) {
  return Object.keys(obj).length === 0;
};

schemautil.extend = function(instance, schema) {
  return schemautil.merge(schemautil.instantiate(schema), instance);
};

// instantiate a schema
schemautil.instantiate = function(schema) {
  var val;
  if ('default' in schema) {
    val = schema.default;
    return util.isObject(val) ? util.duplicate(val) : val;
  } else if (schema.type === 'object') {
    var instance = {};
    for (var name in schema.properties) {
      val = schemautil.instantiate(schema.properties[name]);
      if (val !== undefined) {
        instance[name] = val;
      }
    }
    return instance;
  } else if (schema.type === 'array') {
    return [];
  }
  return undefined;
};

// remove all defaults from an instance
schemautil.subtract = function(instance, defaults) {
  var changes = {};
  for (var prop in instance) {
    var def = defaults[prop];
    var ins = instance[prop];
    // Note: does not properly subtract arrays
    if (!defaults || def !== ins) {
      if (typeof ins === 'object' && !util.isArray(ins) && def) {
        var c = schemautil.subtract(ins, def);
        if (!isEmpty(c))
          changes[prop] = c;
      } else if (!util.isArray(ins) || ins.length > 0) {
        changes[prop] = ins;
      }
    }
  }
  return changes;
};

schemautil.merge = function(/*dest*, src0, src1, ...*/){
  var dest = arguments[0];
  for (var i=1 ; i<arguments.length; i++) {
    dest = merge(dest, arguments[i]);
  }
  return dest;
};

// recursively merges src into dest
function merge(dest, src) {
  if (typeof src !== 'object' || src === null) {
    return dest;
  }

  for (var p in src) {
    if (!src.hasOwnProperty(p)) {
      continue;
    }
    if (src[p] === undefined) {
      continue;
    }
    if (typeof src[p] !== 'object' || src[p] === null) {
      dest[p] = src[p];
    } else if (typeof dest[p] !== 'object' || dest[p] === null) {
      dest[p] = merge(src[p].constructor === Array ? [] : {}, src[p]);
    } else {
      merge(dest[p], src[p]);
    }
  }
  return dest;
}<commit_msg>take care of undefined case<commit_after>'use strict';

var schemautil = module.exports = {},
  util = require('../util');

var isEmpty = function(obj) {
  return Object.keys(obj).length === 0;
};

schemautil.extend = function(instance, schema) {
  return schemautil.merge(schemautil.instantiate(schema), instance);
};

// instantiate a schema
schemautil.instantiate = function(schema) {
  var val;
  if (schema === undefined) {
    return undefined;
  } else if ('default' in schema) {
    val = schema.default;
    return util.isObject(val) ? util.duplicate(val) : val;
  } else if (schema.type === 'object') {
    var instance = {};
    for (var name in schema.properties) {
      val = schemautil.instantiate(schema.properties[name]);
      if (val !== undefined) {
        instance[name] = val;
      }
    }
    return instance;
  } else if (schema.type === 'array') {
    return [];
  }
  return undefined;
};

// remove all defaults from an instance
schemautil.subtract = function(instance, defaults) {
  var changes = {};
  for (var prop in instance) {
    var def = defaults[prop];
    var ins = instance[prop];
    // Note: does not properly subtract arrays
    if (!defaults || def !== ins) {
      if (typeof ins === 'object' && !util.isArray(ins) && def) {
        var c = schemautil.subtract(ins, def);
        if (!isEmpty(c))
          changes[prop] = c;
      } else if (!util.isArray(ins) || ins.length > 0) {
        changes[prop] = ins;
      }
    }
  }
  return changes;
};

schemautil.merge = function(/*dest*, src0, src1, ...*/){
  var dest = arguments[0];
  for (var i=1 ; i<arguments.length; i++) {
    dest = merge(dest, arguments[i]);
  }
  return dest;
};

// recursively merges src into dest
function merge(dest, src) {
  if (typeof src !== 'object' || src === null) {
    return dest;
  }

  for (var p in src) {
    if (!src.hasOwnProperty(p)) {
      continue;
    }
    if (src[p] === undefined) {
      continue;
    }
    if (typeof src[p] !== 'object' || src[p] === null) {
      dest[p] = src[p];
    } else if (typeof dest[p] !== 'object' || dest[p] === null) {
      dest[p] = merge(src[p].constructor === Array ? [] : {}, src[p]);
    } else {
      merge(dest[p], src[p]);
    }
  }
  return dest;
}<|endoftext|>


<commit_before>/**
 * @license ng-notify v0.6.2
 * http://matowens.github.io/ng-notify
 * (c) 2014 MIT License, MatOwens.com
 */
(function() {
    'use strict';

    /**
     * @description
     *
     * This module provides any AngularJS application with a simple, lightweight
     * system for displaying notifications of varying degree to it's users.
     *
     */
    var module = angular.module('ngNotify', []);

    /**
     * Check to see if the ngSanitize script has been included by the user.
     * If so, pull it in and allow for it to be used when user has specified.
     */
    var hasSanitize = false;

    try {

        /* istanbul ignore else  */
        if (angular.module('ngSanitize')) {

            // Note on the requires array from module() source code:
            // Holds the list of modules which the injector will load before the current module is loaded.

            // A sort of lazy load for our dependency on ngSanitize, only if the module exists.
            angular.module('ngNotify').requires.push('ngSanitize');

            hasSanitize = true;
        }

    } catch (err) {
        // Ignore error, we'll disable any sanitize related functionality...
    }

    // Generate ngNotify template and add it to cache...

    var html =
        '
' +
            '×' +
            '' + // Display HTML notifications.
            '' + // Display escaped notifications.
        '
';

    module.run(['$templateCache',

        function($templateCache) {

            $templateCache.put('templates/ng-notify/ng-notify.html', html);
        }
    ]);

    module.provider('ngNotify', function() {

        this.$get = ['$document', '$compile', '$log', '$rootScope', '$timeout', '$interval', '$templateCache',

            function($document, $compile, $log, $rootScope, $timeout, $interval, $templateCache) {

                // Constants...

                var EMPTY = '';
                var SPACER = ' ';
                var DEFAULT_DURATION = 3000;
                var STICKY_CLASS = 'ngn-sticky';

                var FADE_IN_MODE = 1;
                var FADE_OUT_MODE = -1;
                var FADE_IN_DURATION = 200;
                var FADE_OUT_DURATION = 500;
                var FADE_INTERVAL = 25;

                var OPACITY_MIN = 0;
                var OPACITY_MAX = 1;

                // Defaults...

                var defaultOptions = {
                    theme: 'pure',
                    position: 'bottom',
                    duration: DEFAULT_DURATION,
                    type: 'info',
                    sticky: false,
                    button: true,
                    html: false
                };

                var defaultScope = {
                    notifyClass: '',
                    notifyMessage: '',
                    notifyStyle: {
                        display: 'none',
                        opacity: OPACITY_MIN
                    }
                };

                // Options...

                var themes = {
                    pure: EMPTY,
                    prime: 'ngn-prime',
                    pastel: 'ngn-pastel',
                    pitchy: 'ngn-pitchy'
                };

                var types = {
                    info: 'ngn-info',
                    error: 'ngn-error',
                    success: 'ngn-success',
                    warn: 'ngn-warn',
                    grimace: 'ngn-grimace'
                };

                var positions = {
                    bottom: 'ngn-bottom',
                    top: 'ngn-top'
                };

                // Fade params...

                var notifyTimeout;
                var notifyInterval;

                // Template and scope...

                var notifyScope = $rootScope.$new();
                var tpl = $compile($templateCache.get('templates/ng-notify/ng-notify.html'))(notifyScope);

                // Init our scope params...

                notifyScope.ngNotify = angular.extend({}, defaultScope);

                // Add the template to the page...

                $timeout(function() {
                    $document.find('body').append(tpl);
                });

                // Private methods...

                /**
                 * Gets what type of notification do display, eg, error, warning, etc.
                 *
                 * @param {Object} userOpts - object containing user defined options.
                 *
                 * @return {String} - the type that will be assigned to this notification.
                 */
                var getType = function(userOpts) {
                    var type = userOpts.type || defaultOptions.type;
                    return (types[type] || types.info) + SPACER;
                };

                /**
                 * Gets the theme for a notification, eg, pure, pastel, etc.
                 *
                 * @param {Object} userOpts - object containing user defined options.
                 *
                 * @return {String} - the theme that will be assigned to this notification.
                 */
                var getTheme = function(userOpts) {
                    var theme = userOpts.theme || defaultOptions.theme;
                    return (themes[theme] || themes.pure) + SPACER;
                };

                /**
                 * Gets the position of the notification, eg, top or bottom.
                 *
                 * @param {Object} userOpts - object containing user defined options.
                 *
                 * @return {String} - the position that will be assigned to this notification.
                 */
                var getPosition = function(userOpts) {
                    var position = userOpts.position || defaultOptions.position;
                    return (positions[position] || positions.bottom) + SPACER;
                };

                /**
                 * Gets how long (in ms) to display the notification for.
                 *
                 * @param {Object} userOpts - object containing user defined options.
                 *
                 * @return {Number} - the number of ms a fade on this notification will last.
                 */
                var getDuration = function(userOpts) {
                    var duration = userOpts.duration || defaultOptions.duration;
                    return angular.isNumber(duration) ? duration : DEFAULT_DURATION;
                };

                /**
                 * Gets our notification's sticky state, forcing manual dismissal when true.
                 *
                 * @param {Object} userOpts - object containing user defined options.
                 *
                 * @return {Boolean} - whether we'll be showing a sticky notification or not.
                 */
                var getSticky = function(userOpts) {
                    var sticky = userOpts.sticky !== undefined ? userOpts.sticky : defaultOptions.sticky;
                    return sticky ? true : false;
                };

                /**
                 * Gets whether or not we'd like to show the close button on our sticky notification.
                 * Notification is required to be sticky.
                 *
                 * @param {Object} userOpts - object containing user defined options.
                 * @param {Boolean} isSticky - bool whether we'll be showing a sticky notification or not.
                 *
                 * @returns {Boolean} - whether or now we should display the close button.
                 */
                var showButton = function(userOpts, isSticky) {
                    var showButton = userOpts.button !== undefined ? userOpts.button : defaultOptions.button;
                    return showButton && isSticky;
                };

                /**
                 * Gets whether or not to allow HTML binding via ngSanitize.
                 * Check to make sure ngSanitize is included in the project and warn the user if it's not.
                 *
                 * @param {Object} userOpts - object containing user defined options.
                 *
                 * @return {Boolean} - whether we'll be using ng-bind-html or not.
                 */
                var getHtml = function(userOpts) {

                    /* istanbul ignore if  */
                    if ((userOpts.html || defaultOptions.html) && !hasSanitize) {

                        $log.debug(
                            "ngNotify warning: \ngSanitize couldn't be located.  In order to use the " +
                            "'html' option, be sure the ngSanitize source is included in your project."
                        );

                        return false;
                    }

                    var html = userOpts.html !== undefined ? userOpts.html : defaultOptions.html;
                    return html ? true : false;
                };

                /**
                 * Grabs all of the classes that our notification will need in order to display properly.
                 *
                 * @param {Object}  userOpts - object containing user defined options.
                 * @param {Boolean} isSticky - optional bool indicating if the message is sticky or not.
                 *
                 * @returns {string}
                 */
                var getClasses = function(userOpts, isSticky) {

                    var classes = getType(userOpts) +
                                  getTheme(userOpts) +
                                  getPosition(userOpts);

                    classes += isSticky ? STICKY_CLASS : EMPTY;

                    return classes;
                };

                /**
                 * Resets our notification classes and message.
                 */
                var notifyReset = function() {
                    notifyScope.ngNotify = angular.extend({}, defaultScope);
                };

                /**
                 * Handles the fading functionality and the duration for each fade.
                 *
                 * @param {Number}   mode     - used to trigger fade in or out, adds or subtracts opacity until visible or hidden.
                 * @param {Number}   opacity  - initial opacity for our element.
                 * @param {Number}   duration - how long the fade should take to complete, in ms.
                 * @param {Function} callback - function to invoke once our fade is complete.
                 */
                var doFade = function(mode, opacity, duration, callback) {

                    var gap = FADE_INTERVAL / duration;

                    notifyScope.ngNotify.notifyStyle = {
                        display: 'block',
                        opacity: opacity
                    };

                    var func = function() {

                        opacity += mode * gap;

                        notifyScope.ngNotify.notifyStyle.opacity = opacity;

                        if (opacity <= OPACITY_MIN || OPACITY_MAX <= opacity) {

                            $interval.cancel(notifyInterval);
                            notifyInterval = false;

                            callback();
                        }
                    };

                    if (!notifyInterval) {
                        notifyInterval = $interval(func, FADE_INTERVAL);
                    }
                };

                /**
                 * Triggers a fade out, opacity from 1 to 0.
                 *
                 * @param {Number}   duration - how long the fade should take to complete, in ms.
                 * @param {Function} callback - function to invoke once fade has completed.
                 */
                var fadeOut = function(duration, callback) {
                    doFade(FADE_OUT_MODE, OPACITY_MAX, duration, callback);
                };

                /**
                 * Triggers a fade in, opacity from 0 to 1.
                 *
                 * @param  {Number}   duration - how long the fade should take to complete, in ms.
                 * @param  {Function} callback - function to invoke once fade has completed.
                 */
                var fadeIn = function(duration, callback) {
                    doFade(FADE_IN_MODE, OPACITY_MIN, duration, callback);
                };

                /**
                 * Dismisses our notification when called, attached to scope for ngCLick event to trigger.
                 */
                notifyScope.dismiss = function() {
                    fadeOut(FADE_OUT_DURATION, function() {
                        notifyReset();
                    });
                };

                /**
                 * Our primary object containing all public API methods and allows for all our functionality to be invoked.
                 *
                 * @type {Object}
                 */
                return {

                    /**
                     * Merges our user specified options with our default set of options.
                     *
                     * @param {Object} params - object of user provided options to configure notifications.
                     */
                    config: function(params) {
                        params = params || {};
                        angular.extend(defaultOptions, params);
                    },

                    /**
                     * Sets, configures and displays each notification.
                     *
                     * @param {String}                   message - the message our notification will display to the user.
                     * @param {String|Object|undefined}  userOpt - optional parameter that contains the type or an object of options used to configure this notification.
                     * @param {String|undefined}         userOpt.type
                     * @param {String|undefined}         userOpt.theme
                     * @param {String|undefined}         userOpt.position
                     * @param {Number|undefined}         userOpt.duration
                     * @param {Boolean|undefined}        userOpt.sticky
                     * @param {Boolean|undefined}        userOpt.button
                     * @param {Boolean|undefined}        userOpt.html
                     */
                    set: function(message, userOpt) {

                        if (!message) {
                            return;
                        }

                        $interval.cancel(notifyInterval);
                        notifyInterval = false;

                        $timeout.cancel(notifyTimeout);

                        var userOpts = {};

                        // User either provides an object of options
                        // or a string specifying the type.
                        if (typeof userOpt === 'object') {
                            userOpts = userOpt;
                        } else {
                            userOpts.type = userOpt;
                        }

                        var isSticky = getSticky(userOpts);
                        var duration = getDuration(userOpts);

                        angular.extend(notifyScope.ngNotify, {
                            notifyHtml: getHtml(userOpts),
                            notifyClass: getClasses(userOpts, isSticky),
                            notifyButton: showButton(userOpts, isSticky),
                            notifyMessage: message
                        });

                        fadeIn(FADE_IN_DURATION, function() {

                            if (isSticky) {
                                return;
                            }

                            notifyTimeout = $timeout(function() {
                                notifyScope.dismiss();
                            }, duration);
                        });
                    },

                    /**
                     * Allows a developer to manually dismiss a notification that may be
                     * set to sticky, when the message is no longer warranted.
                     */
                    dismiss: function() {
                        notifyScope.dismiss();
                    },

                    // User customizations...

                    /**
                     * Adds a new, user specified theme to our notification system
                     * that they can then use throughout their application.
                     *
                     * @param {String} themeName  - the name for this new theme that will be used when applying it via configuration.
                     * @param {String} themeClass - the class that this theme will use when applying it's styles.
                     */
                    addTheme: function(themeName, themeClass) {

                        if (!themeName || !themeClass) {
                            return;
                        }

                        themes[themeName] = themeClass;
                    },

                    /**
                     * Adds a new, user specified notification type that they
                     * can then use throughout their application.
                     *
                     * @param {String} typeName  - the name for this new type that will be used when applying it via configuration.
                     * @param {String} typeClass - the class that this type will use when applying it's styles.
                     */
                    addType: function(typeName, typeClass) {

                        if (!typeName || !typeClass) {
                            return;
                        }

                        types[typeName] = typeClass;
                    }
                };
            }
        ];
    });
})();
<commit_msg>Fix warning message when ngSanitize is not found.<commit_after>/**
 * @license ng-notify v0.6.2
 * http://matowens.github.io/ng-notify
 * (c) 2014 MIT License, MatOwens.com
 */
(function() {
    'use strict';

    /**
     * @description
     *
     * This module provides any AngularJS application with a simple, lightweight
     * system for displaying notifications of varying degree to it's users.
     *
     */
    var module = angular.module('ngNotify', []);

    /**
     * Check to see if the ngSanitize script has been included by the user.
     * If so, pull it in and allow for it to be used when user has specified.
     */
    var hasSanitize = false;

    try {

        /* istanbul ignore else  */
        if (angular.module('ngSanitize')) {

            // Note on the requires array from module() source code:
            // Holds the list of modules which the injector will load before the current module is loaded.

            // A sort of lazy load for our dependency on ngSanitize, only if the module exists.
            angular.module('ngNotify').requires.push('ngSanitize');

            hasSanitize = true;
        }

    } catch (err) {
        // Ignore error, we'll disable any sanitize related functionality...
    }

    // Generate ngNotify template and add it to cache...

    var html =
        '
' +
            '×' +
            '' + // Display HTML notifications.
            '' + // Display escaped notifications.
        '
';

    module.run(['$templateCache',

        function($templateCache) {

            $templateCache.put('templates/ng-notify/ng-notify.html', html);
        }
    ]);

    module.provider('ngNotify', function() {

        this.$get = ['$document', '$compile', '$log', '$rootScope', '$timeout', '$interval', '$templateCache',

            function($document, $compile, $log, $rootScope, $timeout, $interval, $templateCache) {

                // Constants...

                var EMPTY = '';
                var SPACER = ' ';
                var DEFAULT_DURATION = 3000;
                var STICKY_CLASS = 'ngn-sticky';

                var FADE_IN_MODE = 1;
                var FADE_OUT_MODE = -1;
                var FADE_IN_DURATION = 200;
                var FADE_OUT_DURATION = 500;
                var FADE_INTERVAL = 25;

                var OPACITY_MIN = 0;
                var OPACITY_MAX = 1;

                // Defaults...

                var defaultOptions = {
                    theme: 'pure',
                    position: 'bottom',
                    duration: DEFAULT_DURATION,
                    type: 'info',
                    sticky: false,
                    button: true,
                    html: false
                };

                var defaultScope = {
                    notifyClass: '',
                    notifyMessage: '',
                    notifyStyle: {
                        display: 'none',
                        opacity: OPACITY_MIN
                    }
                };

                // Options...

                var themes = {
                    pure: EMPTY,
                    prime: 'ngn-prime',
                    pastel: 'ngn-pastel',
                    pitchy: 'ngn-pitchy'
                };

                var types = {
                    info: 'ngn-info',
                    error: 'ngn-error',
                    success: 'ngn-success',
                    warn: 'ngn-warn',
                    grimace: 'ngn-grimace'
                };

                var positions = {
                    bottom: 'ngn-bottom',
                    top: 'ngn-top'
                };

                // Fade params...

                var notifyTimeout;
                var notifyInterval;

                // Template and scope...

                var notifyScope = $rootScope.$new();
                var tpl = $compile($templateCache.get('templates/ng-notify/ng-notify.html'))(notifyScope);

                // Init our scope params...

                notifyScope.ngNotify = angular.extend({}, defaultScope);

                // Add the template to the page...

                $timeout(function() {
                    $document.find('body').append(tpl);
                });

                // Private methods...

                /**
                 * Gets what type of notification do display, eg, error, warning, etc.
                 *
                 * @param {Object} userOpts - object containing user defined options.
                 *
                 * @return {String} - the type that will be assigned to this notification.
                 */
                var getType = function(userOpts) {
                    var type = userOpts.type || defaultOptions.type;
                    return (types[type] || types.info) + SPACER;
                };

                /**
                 * Gets the theme for a notification, eg, pure, pastel, etc.
                 *
                 * @param {Object} userOpts - object containing user defined options.
                 *
                 * @return {String} - the theme that will be assigned to this notification.
                 */
                var getTheme = function(userOpts) {
                    var theme = userOpts.theme || defaultOptions.theme;
                    return (themes[theme] || themes.pure) + SPACER;
                };

                /**
                 * Gets the position of the notification, eg, top or bottom.
                 *
                 * @param {Object} userOpts - object containing user defined options.
                 *
                 * @return {String} - the position that will be assigned to this notification.
                 */
                var getPosition = function(userOpts) {
                    var position = userOpts.position || defaultOptions.position;
                    return (positions[position] || positions.bottom) + SPACER;
                };

                /**
                 * Gets how long (in ms) to display the notification for.
                 *
                 * @param {Object} userOpts - object containing user defined options.
                 *
                 * @return {Number} - the number of ms a fade on this notification will last.
                 */
                var getDuration = function(userOpts) {
                    var duration = userOpts.duration || defaultOptions.duration;
                    return angular.isNumber(duration) ? duration : DEFAULT_DURATION;
                };

                /**
                 * Gets our notification's sticky state, forcing manual dismissal when true.
                 *
                 * @param {Object} userOpts - object containing user defined options.
                 *
                 * @return {Boolean} - whether we'll be showing a sticky notification or not.
                 */
                var getSticky = function(userOpts) {
                    var sticky = userOpts.sticky !== undefined ? userOpts.sticky : defaultOptions.sticky;
                    return sticky ? true : false;
                };

                /**
                 * Gets whether or not we'd like to show the close button on our sticky notification.
                 * Notification is required to be sticky.
                 *
                 * @param {Object} userOpts - object containing user defined options.
                 * @param {Boolean} isSticky - bool whether we'll be showing a sticky notification or not.
                 *
                 * @returns {Boolean} - whether or now we should display the close button.
                 */
                var showButton = function(userOpts, isSticky) {
                    var showButton = userOpts.button !== undefined ? userOpts.button : defaultOptions.button;
                    return showButton && isSticky;
                };

                /**
                 * Gets whether or not to allow HTML binding via ngSanitize.
                 * Check to make sure ngSanitize is included in the project and warn the user if it's not.
                 *
                 * @param {Object} userOpts - object containing user defined options.
                 *
                 * @return {Boolean} - whether we'll be using ng-bind-html or not.
                 */
                var getHtml = function(userOpts) {

                    /* istanbul ignore if  */
                    if ((userOpts.html || defaultOptions.html) && !hasSanitize) {

                        $log.debug(
                            "ngNotify warning:\nngSanitize couldn't be located.  In order to use the " +
                            "'html' option, be sure the ngSanitize source is included in your project."
                        );

                        return false;
                    }

                    var html = userOpts.html !== undefined ? userOpts.html : defaultOptions.html;
                    return html ? true : false;
                };

                /**
                 * Grabs all of the classes that our notification will need in order to display properly.
                 *
                 * @param {Object}  userOpts - object containing user defined options.
                 * @param {Boolean} isSticky - optional bool indicating if the message is sticky or not.
                 *
                 * @returns {string}
                 */
                var getClasses = function(userOpts, isSticky) {

                    var classes = getType(userOpts) +
                                  getTheme(userOpts) +
                                  getPosition(userOpts);

                    classes += isSticky ? STICKY_CLASS : EMPTY;

                    return classes;
                };

                /**
                 * Resets our notification classes and message.
                 */
                var notifyReset = function() {
                    notifyScope.ngNotify = angular.extend({}, defaultScope);
                };

                /**
                 * Handles the fading functionality and the duration for each fade.
                 *
                 * @param {Number}   mode     - used to trigger fade in or out, adds or subtracts opacity until visible or hidden.
                 * @param {Number}   opacity  - initial opacity for our element.
                 * @param {Number}   duration - how long the fade should take to complete, in ms.
                 * @param {Function} callback - function to invoke once our fade is complete.
                 */
                var doFade = function(mode, opacity, duration, callback) {

                    var gap = FADE_INTERVAL / duration;

                    notifyScope.ngNotify.notifyStyle = {
                        display: 'block',
                        opacity: opacity
                    };

                    var func = function() {

                        opacity += mode * gap;

                        notifyScope.ngNotify.notifyStyle.opacity = opacity;

                        if (opacity <= OPACITY_MIN || OPACITY_MAX <= opacity) {

                            $interval.cancel(notifyInterval);
                            notifyInterval = false;

                            callback();
                        }
                    };

                    if (!notifyInterval) {
                        notifyInterval = $interval(func, FADE_INTERVAL);
                    }
                };

                /**
                 * Triggers a fade out, opacity from 1 to 0.
                 *
                 * @param {Number}   duration - how long the fade should take to complete, in ms.
                 * @param {Function} callback - function to invoke once fade has completed.
                 */
                var fadeOut = function(duration, callback) {
                    doFade(FADE_OUT_MODE, OPACITY_MAX, duration, callback);
                };

                /**
                 * Triggers a fade in, opacity from 0 to 1.
                 *
                 * @param  {Number}   duration - how long the fade should take to complete, in ms.
                 * @param  {Function} callback - function to invoke once fade has completed.
                 */
                var fadeIn = function(duration, callback) {
                    doFade(FADE_IN_MODE, OPACITY_MIN, duration, callback);
                };

                /**
                 * Dismisses our notification when called, attached to scope for ngCLick event to trigger.
                 */
                notifyScope.dismiss = function() {
                    fadeOut(FADE_OUT_DURATION, function() {
                        notifyReset();
                    });
                };

                /**
                 * Our primary object containing all public API methods and allows for all our functionality to be invoked.
                 *
                 * @type {Object}
                 */
                return {

                    /**
                     * Merges our user specified options with our default set of options.
                     *
                     * @param {Object} params - object of user provided options to configure notifications.
                     */
                    config: function(params) {
                        params = params || {};
                        angular.extend(defaultOptions, params);
                    },

                    /**
                     * Sets, configures and displays each notification.
                     *
                     * @param {String}                   message - the message our notification will display to the user.
                     * @param {String|Object|undefined}  userOpt - optional parameter that contains the type or an object of options used to configure this notification.
                     * @param {String|undefined}         userOpt.type
                     * @param {String|undefined}         userOpt.theme
                     * @param {String|undefined}         userOpt.position
                     * @param {Number|undefined}         userOpt.duration
                     * @param {Boolean|undefined}        userOpt.sticky
                     * @param {Boolean|undefined}        userOpt.button
                     * @param {Boolean|undefined}        userOpt.html
                     */
                    set: function(message, userOpt) {

                        if (!message) {
                            return;
                        }

                        $interval.cancel(notifyInterval);
                        notifyInterval = false;

                        $timeout.cancel(notifyTimeout);

                        var userOpts = {};

                        // User either provides an object of options
                        // or a string specifying the type.
                        if (typeof userOpt === 'object') {
                            userOpts = userOpt;
                        } else {
                            userOpts.type = userOpt;
                        }

                        var isSticky = getSticky(userOpts);
                        var duration = getDuration(userOpts);

                        angular.extend(notifyScope.ngNotify, {
                            notifyHtml: getHtml(userOpts),
                            notifyClass: getClasses(userOpts, isSticky),
                            notifyButton: showButton(userOpts, isSticky),
                            notifyMessage: message
                        });

                        fadeIn(FADE_IN_DURATION, function() {

                            if (isSticky) {
                                return;
                            }

                            notifyTimeout = $timeout(function() {
                                notifyScope.dismiss();
                            }, duration);
                        });
                    },

                    /**
                     * Allows a developer to manually dismiss a notification that may be
                     * set to sticky, when the message is no longer warranted.
                     */
                    dismiss: function() {
                        notifyScope.dismiss();
                    },

                    // User customizations...

                    /**
                     * Adds a new, user specified theme to our notification system
                     * that they can then use throughout their application.
                     *
                     * @param {String} themeName  - the name for this new theme that will be used when applying it via configuration.
                     * @param {String} themeClass - the class that this theme will use when applying it's styles.
                     */
                    addTheme: function(themeName, themeClass) {

                        if (!themeName || !themeClass) {
                            return;
                        }

                        themes[themeName] = themeClass;
                    },

                    /**
                     * Adds a new, user specified notification type that they
                     * can then use throughout their application.
                     *
                     * @param {String} typeName  - the name for this new type that will be used when applying it via configuration.
                     * @param {String} typeClass - the class that this type will use when applying it's styles.
                     */
                    addType: function(typeName, typeClass) {

                        if (!typeName || !typeClass) {
                            return;
                        }

                        types[typeName] = typeClass;
                    }
                };
            }
        ];
    });
})();
<|endoftext|>


<commit_before>import { range } from 'utils'
import { PivotTable } from 'pivot-table'
import { div } from 'selection'
import chai from 'chai'

export default () ->
  should = chai.should()

  describe 'Pivot Table', ->
    it 'should have the correct default options', ->
      topHead = range(2)
      leftHead = range(2)
      body = range(2).map (x) ->
        range(2).map (y) -> x * y

      data = {
        body: body,
        leftHead: leftHead,
        topHead: topHead
      }

      pt = new PivotTable(div())

      pt.options.stickyHeaders.should.equal(true)
      should.not.exist(pt.options.topLeftCellRender)
      pt.options.cellRender.should.be.a('function')
      pt.options.useResponsive.should.equal(true)
      should.not.exist(pt.options.data)
      should.not.exist(pt.options.fullWidth)

    it 'should not mutate the data', ->
      topHead = range(8)
      leftHead = range(8)
      body = range(8).map (x) ->
        range(8).map (y) -> x * y

      data = {
        body: body,
        leftHead: leftHead,
        topHead: topHead
      }

      pt = new PivotTable(div(), {
        data: data
      })
      topHead.should.eql([0,1,2,3,4,5,6,7])
      leftHead.should.eql([0,1,2,3,4,5,6,7])

      pt.data().should.not.equal(data)
      pt.data().should.eql(data)
      pt.data(pt.data()).should.equal(pt)

    it 'should pass the fullWidth option through to the sticky table', ->
      topHead = range(2)
      leftHead = range(2)
      body = range(2).map (x) ->
        range(2).map (y) -> x * y

      data = {
        body: body,
        leftHead: leftHead,
        topHead: topHead
      }

      pt = new PivotTable(div(), {
        data: data
        fullWidth: true
      })

      pt.options.fullWidth.should.equal(true)
      pt.stickyTableHeaders._.options.fullWidth.should.equal(true)

    it 'should use the fullWidth option when sticky tables are disabled', ->
      topHead = range(2)
      leftHead = range(2)
      body = range(2).map (x) ->
        range(2).map (y) -> x * y

      data = {
        body: body,
        leftHead: leftHead,
        topHead: topHead
      }

      sel = div()

      pt = new PivotTable(sel.node(), {
        data: data
        fullWidth: true
        stickyHeaders: false
      })

      pt.options.fullWidth.should.equal(true)
      should.not.exist(pt.stickyTableHeaders)
      sel.selectAll('.hx-table-full').size().should.equal(1)
<commit_msg>Port the pivot table tests<commit_after>import { range } from 'utils'
import { PivotTable } from 'pivot-table'
import { div } from 'selection'
import chai from 'chai'

export default () => {
  const should = chai.should()

  describe('Pivot Table', () => {
    it('should have the correct default options', () => {
      const topHead = range(2)
      const leftHead = range(2)
      const body = range(2).map(x => range(2).map(y => x * y))

      const pt = new PivotTable(div())

      pt.options.stickyHeaders.should.equal(true)
      should.not.exist(pt.options.topLeftCellRender)
      pt.options.cellRender.should.be.a('function')
      pt.options.useResponsive.should.equal(true)
      should.not.exist(pt.options.data)
      should.not.exist(pt.options.fullWidth)
    })

    it('should not mutate the data', () => {
      const topHead = range(8)
      const leftHead = range(8)
      const body = range(8).map(x => range(8).map(y => x * y))

      const data = {
        body,
        leftHead,
        topHead
      }

      const pt = new PivotTable(div(), {
        data
      })
      topHead.should.eql([0, 1, 2, 3, 4, 5, 6, 7])
      leftHead.should.eql([0, 1, 2, 3, 4, 5, 6, 7])

      pt.data().should.not.equal(data)
      pt.data().should.eql(data)
      pt.data(pt.data()).should.equal(pt)
    })

    it('should pass the fullWidth option through to the sticky table', () => {
      const topHead = range(2)
      const leftHead = range(2)
      const body = range(2).map(x => range(2).map(y => x * y))

      const data = {
        body,
        leftHead,
        topHead
      }

      const pt = new PivotTable(div(), {
        data,
        fullWidth: true
      })

      pt.options.fullWidth.should.equal(true)
      pt.stickyTableHeaders._.options.fullWidth.should.equal(true)
    })

    it('should use the fullWidth option when sticky tables are disabled', () => {
      const topHead = range(2)
      const leftHead = range(2)
      const body = range(2).map(x => range(2).map(y => x * y))

      const data = {
        body,
        leftHead,
        topHead
      }

      const sel = div()

      const pt = new PivotTable(sel.node(), {
        data,
        fullWidth: true,
        stickyHeaders: false
      })

      pt.options.fullWidth.should.equal(true)
      should.not.exist(pt.stickyTableHeaders)
      sel.selectAll('.hx-table-full').size().should.equal(1)
    })
  })
}
<|endoftext|>