<commit_before>import React from 'react'
import TransitionGroup from 'react-transition-group/TransitionGroup'
import { Core, Data } from 'react-chunky'
import { Redirect } from 'react-router'
import { default as Component } from './Component'
import * as DefaultComponents from '../components'
import merge from 'deepmerge'
import { breakpoints } from '../utils/responsive'
import { default as Layout } from './Layout'
import { detect } from 'detect-browser'
import {
  Drawer,
  DrawerHeader,
  DrawerScrim,
  DrawerContent,
  DrawerTitle,
  DrawerSubtitle
} from '@rmwc/drawer'
import { Button } from '@rmwc/button'

import {
  List,
  ListItem,
  ListItemPrimaryText
} from '@rmwc/list'

export default class Screen extends Core.Screen {
  constructor (props) {
    super(props)
    this.state = {
      ...this.state,
      loading: true,
      height: window.innerHeight,
      width: window.innerWidth,
      scroll: 0,
      unCoveredHeader: false
    }

    this._updateScroll = this.updateScroll.bind(this)
    this._updateWindowDimensions = this.updateWindowDimensions.bind(this)
    this._onMenuItem = this.onMenuItem.bind(this)
    this._sidebarMenuSelected = this.sidebarMenuSelected.bind(this)
  }

  componentDidMount () {
    super.componentDidMount()
    this._updateWindowDimensions()
    this._sideMenu = [].concat(this.menu)

    window.addEventListener('resize', this._updateWindowDimensions)
    window.addEventListener('scroll', this._updateScroll)
    this.unsubscribeFromHistory = this.props.history.listen(
      this.handleLocationChange.bind(this)
    )
    this._onEvent = this.onEvent.bind(this)
    this._browser = detect()

    this.triggerAnalyticsView(this.props.location.pathname)
    const account = this.isLoggedIn ? 'member' : 'guest'

    this.triggerAnalyticsEvent({
      category: `${this.constructor.name}`,
      action: `${this.props.location.pathname}`,
      label: account
    })

    if (this.props.private && !this.isLoggedIn) {
      this.triggerRedirect(this.props.permissions.publicRedirect)
      return
    }

    if (!this.props.private && this.isLoggedIn && this.props.guestOnly) {
      this.triggerRedirect(this.props.privateRedirect || this.props.permissions.privateRedirect)
    }

    this._load(this.props)
  }

  get sidebarWidth () {
    return 200
  }

  get sections () {
    return this._sections
  }

  get restUrl () {
    if (!this.props.provisioning || !this.props.provisioning.rest || !this.props.provisioning.rest.url || !this.props.env) {
      return
    }

    return `${this.props.provisioning.rest.url}/${this.props.env === 'production' ? '' : this.props.env + '-'}`
  }

  componentWillReceiveProps (nextProps) {
    if (this.props.match.url !== nextProps.match.url) {
      this._load(nextProps)
      return
    }
    super.componentWillReceiveProps(nextProps)
  }

  handleLocationChange (location) {
  }

  get browser () {
    return this._browser
  }

  scrollToTop () {
    window.scrollTo(0, 0)
  }

  componentWillUnmount () {
    window.removeEventListener('resize', this._updateWindowDimensions)
    window.removeEventListener('scroll', this._updateScroll)
    this.unsubscribeFromHistory()
  }

  onMenuItem (item) {
    if (!item) {
      return
    }

    if (item.action && this[item.action]) {
      this[item.action](item)
      return
    }

    if (item.link) {
      this.triggerRawRedirect(item.link)
      return
    }

    if (item.path) {
      this.triggerRedirect(item.path)
    }
  }

  get menu () {
    return (this.props.menu || []).concat([])
  }

  get sideMenu () {
    return this._sideMenu
  }

  get isSmallScreen () {
    return this.width < breakpoints.main
  }

  get layout () {
    return Layout
  }

  get expectsVariants () {
    return this.props.variants !== undefined
  }

  updateWindowDimensions () {
    this.setState({ width: window.innerWidth, height: window.innerHeight })
  }

  updateScroll () {
    const scroll = window.scrollY
    if (scroll > 10 && !this.state.unCoveredHeader) {
      this.setState({ scroll, unCoveredHeader: true })
    } else if(scroll < 10) {
      this.setState({ scroll, unCoveredHeader: false })
    }
  }

  handleLocalEvent (fullPath) {
    this.triggerRedirect(fullPath)
  }

  handleExternalEvent (fullPath) {
    this.triggerRawRedirect(fullPath)
  }

  importData (name) {
    try {
      const parts = name.split('/')
      const chunkName = (parts.length > 1 ? parts[0] : this.props.chunkName)
      const filename = (parts.length > 1 ? parts[1] : name)

      if (this.props.desktop) {
        return require(`../../../../chunks/${chunkName}/data/${filename}.json`)
      }
      return require(`chunks/${chunkName}/data/${filename}.json`)
    } catch (e) {
    }
  }

  importRemoteData (url) {
    return fetch(url).then(response => response.json())
  }

  _loadVariants () {
    return new Promise((resolve, reject) => {
      if (!this.props.variants || !Array.isArray(this.props.variants) || this.props.variants.length === 0) {
        resolve([])
        return
      }

      if (
        this.props.variants.split('http://').length > 1 ||
        this.props.variants.split('https://').length > 1) {
        fetch(this.props.variants).then(response => resolve(response.json()))
        return
      }

      const data = this.importData(`${this.props.variants}${this.props.desktop ? '.desktop' : ''}`)

      if (!data || !Array.isArray(data) || data.length === 0) {
        resolve([])
        return
      }

      resolve(data)
    }).then(data => {
      this._variants = [].concat(data)
      return this.variants
    })
  }

  get variants () {
    return this._variants
  }

  get hasVariants () {
    return this._variants !== undefined
  }

  get isRootPath () {
    return this.isSamePath(this.path, this.props.path)
  }

  isSamePath (first = '', second = '') {
    const firstClean = first.replace(/^\/|\/$/g, '')
    const secondClean = second.replace(/^\/|\/$/g, '')
    return (firstClean === secondClean)
  }

  _updateVariants () {
    if (!this.hasVariants) {
      throw new Error('Missing expected variant')
    }

    if (this.variants.length === 0) {
      return
    }

    const variantPath = this.path.substring(this.props.path.length + 1)

    this.variants.forEach(variant => {
      if (!this.isSamePath(variant.path, variantPath)) {
        return
      }
      this._variant = Object.assign({}, variant)
    })

    if (!this.isVariantValid) {
      throw new Error('Invalid variant')
    }
  }

  _loadSections () {
    if (!this.props.sections || this.props.sections.length === 0) {
      return
    }

    this._sections = this.importData('sections')
    this._sideMenu = [].concat(this.menu)
  }

  _loadSection () {
    if (!this.sections || this.sections.length === 0) {
      return
    }

    var section = this.sections[0]

    if (this.isRootPath) {
      return section
    }

    this.sections.forEach(s => {
      if (!this.isSamePath(this.path, `${s.path}`)) {
        return
      }
      section = Object.assign({}, s)
    })

    return section
  }

  _load (props) {
    this.scrollToTop()
    this._path = props.location.pathname

    this._loadSections()
    const section = this._loadSection()

    if (this.props.skipRootVariant && this.expectsVariants && this.isRootPath) {
      this.setState({ loading: false, skip: true, section })
      return
    }

    if (!this.expectsVariants || this.isRootPath) {
      this.setState({ loading: false, section })
      return
    }

    try {
      if (!this.hasVariants) {
        this._loadVariants().then(() => {
          this._updateVariants()
          this.setState({ loading: false, section })
        })
        return
      }

      this._updateVariants()
      this.setState({ loading: false, section })
    } catch (e) {
      // Could not load variant path data
      this.stopWithError(e)
    }
  }

  stopWithError (e) {
    this.setState({ stopError: e, loading: false })
  }

  get isVariantValid () {
    return this.expectsVariants && this.variant
  }

  get _props () {
    return Object.assign(
      {},
      this.variant ? merge.all([this.props, this.variant]) : this.props,
      { menu: this.menu, sideMenu: this.sideMenu, sidebarWidth: this.sidebarWidth }
    )
  }

  get variant () {
    return this._variant
  }

  pushTransition (transition, data) {
    var pathname =
      transition.data.path.charAt(0) === ':'
        ? data[transition.data.path.substring(1)] || transition.data.path
        : transition.data.path

    this.setState({ redirect: { transition, data, push: true, pathname } })
  }

  replaceTransition (transition, data) {
    var pathname =
      transition.data.path.charAt(0) === ':'
        ? data[transition.data.path.substring(1)] || transition.data.path
        : transition.data.path

    this.setState({ redirect: { transition, data, push: false, pathname } })
  }

  get account () {
    return this.props.account
  }

  get isLoggedIn () {
    return this.account
  }

  get width () {
    return this.state.width || window.innerWidth
  }

  get height () {
    return this.state.height || window.innerHeight
  }

  get scroll () {
    return this.state.scroll
  }

  get path () {
    return this._path
  }

  components () {
    if (this.props.components) {
      return Object.keys(this.props.components)
    }
    return []
  }

  loadCustomComponent () {}

  loadSingleComponent (props) {
    const source = `${props.source
      .charAt(0)
      .toUpperCase()}${props.source.toLowerCase().slice(1)}`
    var Component = DefaultComponents[source]

    if (!Component) {
      Component = this.loadCustomComponent()
    }

    if (!Component) {
      return

    }

    return <Component {...this.defaultComponentProps} {...props} />
  }

  loadComponent (name, index) {
    if (
      !this.props.components ||
      !this.props.components[name] ||
      !(typeof this.props.components[name] === 'object')
    ) {
      return

    }

    if (!Array.isArray(this.props.components[name])) {
      return this.loadSingleComponent(
        Object.assign({}, this.props.components[name], { index })
      )
    }

    var subIndex = 0
    return (

        {this.props.components[name].map(props => {
          return this.loadSingleComponent(
            Object.assign({}, props, {
              key: `component.${subIndex++}`,
              index: `${index}.${subIndex}`
            })
          )
        })}

    )
  }

  get defaultComponentProps () {
    return Object.assign(
      {},
      {
        cache: this.cache,
        onEvent: this._onEvent,
        width: this.state.width,
        importRemoteData: this.importRemoteData,
        height: this.state.height,
        isSmallScreen: this.isSmallScreen,
        smallScreenBreakPoint: this.smallScreenBreakPoint
      },
      this.props
    )
  }

  renderComponent (OriginalComponent, index) {
    const props = Object.assign({}, this.defaultComponentProps, { index })
    var ComponentContainer = React.cloneElement(
      OriginalComponent,
      Object.assign({}, this.defaultComponentProps, { index })
    )

    if (typeof OriginalComponent.type === 'string') {
      return (
        <Component {...props} key={`${index}`} style={{ alignSelf: 'stretch' }}>
          {OriginalComponent}
        </Component>
      )
    }

    if (typeof OriginalComponent === 'string') {
      ComponentContainer = this.loadComponent(OriginalComponent, index)
    }

    return (
      <TransitionGroup key={`${index}`} style={{ alignSelf: 'stretch' }}>
        {ComponentContainer}
      </TransitionGroup>
    )
  }

  renderComponents () {
    if (!this.components() || this.components().length === 0) {
      return
    }

    var index = 1
    return this.components().map(component => {
      index = index + 1
      return this.renderComponent(component, index)
    })
  }

  redirect (pathname) {
    return (
      <Redirect
        exact
        push
        to={{
          pathname
        }}
      />
    )
  }

  triggerRedirect (link) {
    if (this.isSamePath(this.path, link)) {
      return
    }

    this.setState({ redirect: { push: true, pathname: link } })
  }

  triggerRawRedirect (link) {
    window.open(link, '_blank')
  }

  get cover () {
    return this._props.cover
  }

  renderScreenLayout () {
    const ScreenLayout = this.layout
    return (
      <ScreenLayout
        section={this.state.section}
        onMenuItem={this._onMenuItem}
        onEvent={this._onEvent}
        scroll={this.state.scroll}
        width={this.state.width}
        height={this.state.height}
        onSidebarMenuSelected={this._sidebarMenuSelected}
        isSmallScreen={this.isSmallScreen}
        {...this._props}
        cache={this.props.cache}
        sidebar={this.props.sidebar}
        sidebarIndex={this.props.sidebarIndex}
        private={this.props.private}
        cover={this.cover}>
        { this.renderComponents() }
      </ScreenLayout>
    )
  }

  sidebarMenuSelected (item) {
    if (!item) {
      return
    }

    if (item.action && this[item.action]) {
      this[item.action]()
      return
    }

    this.triggerRedirect(`${item.path}`)
  }

  saveAuth (account) {
    return Data.Cache.cacheAuth(account).then(() => {
      this.loggedIn(account)
    })
  }

  renderStopError (e) {
    return

  }

  renderLoading () {
    return

      <DefaultComponents.Loading message={this.state.loadingMessage || 'Loading, just a sec please ...'} />

  }

  render () {
    if (this.state.skip) {
      return

    }

    if (this.state.stopError) {
      return this.renderStopError(this.state.stopError)
    }

    if (this.state.height === 0) {
      return

    }

    if (this.state.redirect) {
      const { pathname, push } = this.state.redirect
      if (!this.isSamePath(this.path, pathname)) {
        return this.redirect(pathname, push)
      }
    }

    var height = `${this.height}px`

    return (

        {this.renderScreenLayout()}

    )
  }
}
<commit_msg>fix nav scroll<commit_after>import React from 'react'
import TransitionGroup from 'react-transition-group/TransitionGroup'
import { Core, Data } from 'react-chunky'
import { Redirect } from 'react-router'
import { default as Component } from './Component'
import * as DefaultComponents from '../components'
import merge from 'deepmerge'
import { breakpoints } from '../utils/responsive'
import { default as Layout } from './Layout'
import { detect } from 'detect-browser'
import {
  Drawer,
  DrawerHeader,
  DrawerScrim,
  DrawerContent,
  DrawerTitle,
  DrawerSubtitle
} from '@rmwc/drawer'
import { Button } from '@rmwc/button'

import {
  List,
  ListItem,
  ListItemPrimaryText
} from '@rmwc/list'

export default class Screen extends Core.Screen {
  constructor (props) {
    super(props)
    this.state = {
      ...this.state,
      loading: true,
      height: window.innerHeight,
      width: window.innerWidth,
      scroll: 0,
      unCoveredHeader: false
    }

    this._updateScroll = this.updateScroll.bind(this)
    this._updateWindowDimensions = this.updateWindowDimensions.bind(this)
    this._onMenuItem = this.onMenuItem.bind(this)
    this._sidebarMenuSelected = this.sidebarMenuSelected.bind(this)
  }

  componentDidMount () {
    super.componentDidMount()
    this._updateWindowDimensions()
    this._sideMenu = [].concat(this.menu)

    window.addEventListener('resize', this._updateWindowDimensions)
    window.addEventListener('scroll', this._updateScroll)
    this.unsubscribeFromHistory = this.props.history.listen(
      this.handleLocationChange.bind(this)
    )
    this._onEvent = this.onEvent.bind(this)
    this._browser = detect()

    this.triggerAnalyticsView(this.props.location.pathname)
    const account = this.isLoggedIn ? 'member' : 'guest'

    this.triggerAnalyticsEvent({
      category: `${this.constructor.name}`,
      action: `${this.props.location.pathname}`,
      label: account
    })

    if (this.props.private && !this.isLoggedIn) {
      this.triggerRedirect(this.props.permissions.publicRedirect)
      return
    }

    if (!this.props.private && this.isLoggedIn && this.props.guestOnly) {
      this.triggerRedirect(this.props.privateRedirect || this.props.permissions.privateRedirect)
    }

    this._load(this.props)
  }

  get sidebarWidth () {
    return 200
  }

  get sections () {
    return this._sections
  }

  get restUrl () {
    if (!this.props.provisioning || !this.props.provisioning.rest || !this.props.provisioning.rest.url || !this.props.env) {
      return
    }

    return `${this.props.provisioning.rest.url}/${this.props.env === 'production' ? '' : this.props.env + '-'}`
  }

  componentWillReceiveProps (nextProps) {
    if (this.props.match.url !== nextProps.match.url) {
      this._load(nextProps)
      return
    }
    super.componentWillReceiveProps(nextProps)
  }

  handleLocationChange (location) {
  }

  get browser () {
    return this._browser
  }

  scrollToTop () {
    window.scrollTo(0, 0)
  }

  componentWillUnmount () {
    window.removeEventListener('resize', this._updateWindowDimensions)
    window.removeEventListener('scroll', this._updateScroll)
    this.unsubscribeFromHistory()
  }

  onMenuItem (item) {
    if (!item) {
      return
    }

    if (item.action && this[item.action]) {
      this[item.action](item)
      return
    }

    if (item.link) {
      this.triggerRawRedirect(item.link)
      return
    }

    if (item.path) {
      this.triggerRedirect(item.path)
    }
  }

  get menu () {
    return (this.props.menu || []).concat([])
  }

  get sideMenu () {
    return this._sideMenu
  }

  get isSmallScreen () {
    return this.width < breakpoints.main
  }

  get layout () {
    return Layout
  }

  get expectsVariants () {
    return this.props.variants !== undefined
  }

  updateWindowDimensions () {
    this.setState({ width: window.innerWidth, height: window.innerHeight })
  }

  updateScroll () {
    const scroll = window.scrollY
    if ( this.props.theme.keepNavigatorSticky ) return
    if (scroll > 10 && !this.state.unCoveredHeader) {
      this.setState({ scroll, unCoveredHeader: true })
    } else if(scroll < 10) {
      this.setState({ scroll, unCoveredHeader: false })
    }
  }

  handleLocalEvent (fullPath) {
    this.triggerRedirect(fullPath)
  }

  handleExternalEvent (fullPath) {
    this.triggerRawRedirect(fullPath)
  }

  importData (name) {
    try {
      const parts = name.split('/')
      const chunkName = (parts.length > 1 ? parts[0] : this.props.chunkName)
      const filename = (parts.length > 1 ? parts[1] : name)

      if (this.props.desktop) {
        return require(`../../../../chunks/${chunkName}/data/${filename}.json`)
      }
      return require(`chunks/${chunkName}/data/${filename}.json`)
    } catch (e) {
    }
  }

  importRemoteData (url) {
    return fetch(url).then(response => response.json())
  }

  _loadVariants () {
    return new Promise((resolve, reject) => {
      if (!this.props.variants || !Array.isArray(this.props.variants) || this.props.variants.length === 0) {
        resolve([])
        return
      }

      if (
        this.props.variants.split('http://').length > 1 ||
        this.props.variants.split('https://').length > 1) {
        fetch(this.props.variants).then(response => resolve(response.json()))
        return
      }

      const data = this.importData(`${this.props.variants}${this.props.desktop ? '.desktop' : ''}`)

      if (!data || !Array.isArray(data) || data.length === 0) {
        resolve([])
        return
      }

      resolve(data)
    }).then(data => {
      this._variants = [].concat(data)
      return this.variants
    })
  }

  get variants () {
    return this._variants
  }

  get hasVariants () {
    return this._variants !== undefined
  }

  get isRootPath () {
    return this.isSamePath(this.path, this.props.path)
  }

  isSamePath (first = '', second = '') {
    const firstClean = first.replace(/^\/|\/$/g, '')
    const secondClean = second.replace(/^\/|\/$/g, '')
    return (firstClean === secondClean)
  }

  _updateVariants () {
    if (!this.hasVariants) {
      throw new Error('Missing expected variant')
    }

    if (this.variants.length === 0) {
      return
    }

    const variantPath = this.path.substring(this.props.path.length + 1)

    this.variants.forEach(variant => {
      if (!this.isSamePath(variant.path, variantPath)) {
        return
      }
      this._variant = Object.assign({}, variant)
    })

    if (!this.isVariantValid) {
      throw new Error('Invalid variant')
    }
  }

  _loadSections () {
    if (!this.props.sections || this.props.sections.length === 0) {
      return
    }

    this._sections = this.importData('sections')
    this._sideMenu = [].concat(this.menu)
  }

  _loadSection () {
    if (!this.sections || this.sections.length === 0) {
      return
    }

    var section = this.sections[0]

    if (this.isRootPath) {
      return section
    }

    this.sections.forEach(s => {
      if (!this.isSamePath(this.path, `${s.path}`)) {
        return
      }
      section = Object.assign({}, s)
    })

    return section
  }

  _load (props) {
    this.scrollToTop()
    this._path = props.location.pathname

    this._loadSections()
    const section = this._loadSection()

    if (this.props.skipRootVariant && this.expectsVariants && this.isRootPath) {
      this.setState({ loading: false, skip: true, section })
      return
    }

    if (!this.expectsVariants || this.isRootPath) {
      this.setState({ loading: false, section })
      return
    }

    try {
      if (!this.hasVariants) {
        this._loadVariants().then(() => {
          this._updateVariants()
          this.setState({ loading: false, section })
        })
        return
      }

      this._updateVariants()
      this.setState({ loading: false, section })
    } catch (e) {
      // Could not load variant path data
      this.stopWithError(e)
    }
  }

  stopWithError (e) {
    this.setState({ stopError: e, loading: false })
  }

  get isVariantValid () {
    return this.expectsVariants && this.variant
  }

  get _props () {
    return Object.assign(
      {},
      this.variant ? merge.all([this.props, this.variant]) : this.props,
      { menu: this.menu, sideMenu: this.sideMenu, sidebarWidth: this.sidebarWidth }
    )
  }

  get variant () {
    return this._variant
  }

  pushTransition (transition, data) {
    var pathname =
      transition.data.path.charAt(0) === ':'
        ? data[transition.data.path.substring(1)] || transition.data.path
        : transition.data.path

    this.setState({ redirect: { transition, data, push: true, pathname } })
  }

  replaceTransition (transition, data) {
    var pathname =
      transition.data.path.charAt(0) === ':'
        ? data[transition.data.path.substring(1)] || transition.data.path
        : transition.data.path

    this.setState({ redirect: { transition, data, push: false, pathname } })
  }

  get account () {
    return this.props.account
  }

  get isLoggedIn () {
    return this.account
  }

  get width () {
    return this.state.width || window.innerWidth
  }

  get height () {
    return this.state.height || window.innerHeight
  }

  get scroll () {
    return this.state.scroll
  }

  get path () {
    return this._path
  }

  components () {
    if (this.props.components) {
      return Object.keys(this.props.components)
    }
    return []
  }

  loadCustomComponent () {}

  loadSingleComponent (props) {
    const source = `${props.source
      .charAt(0)
      .toUpperCase()}${props.source.toLowerCase().slice(1)}`
    var Component = DefaultComponents[source]

    if (!Component) {
      Component = this.loadCustomComponent()
    }

    if (!Component) {
      return

    }

    return <Component {...this.defaultComponentProps} {...props} />
  }

  loadComponent (name, index) {
    if (
      !this.props.components ||
      !this.props.components[name] ||
      !(typeof this.props.components[name] === 'object')
    ) {
      return

    }

    if (!Array.isArray(this.props.components[name])) {
      return this.loadSingleComponent(
        Object.assign({}, this.props.components[name], { index })
      )
    }

    var subIndex = 0
    return (

        {this.props.components[name].map(props => {
          return this.loadSingleComponent(
            Object.assign({}, props, {
              key: `component.${subIndex++}`,
              index: `${index}.${subIndex}`
            })
          )
        })}

    )
  }

  get defaultComponentProps () {
    return Object.assign(
      {},
      {
        cache: this.cache,
        onEvent: this._onEvent,
        width: this.state.width,
        importRemoteData: this.importRemoteData,
        height: this.state.height,
        isSmallScreen: this.isSmallScreen,
        smallScreenBreakPoint: this.smallScreenBreakPoint
      },
      this.props
    )
  }

  renderComponent (OriginalComponent, index) {
    const props = Object.assign({}, this.defaultComponentProps, { index })
    var ComponentContainer = React.cloneElement(
      OriginalComponent,
      Object.assign({}, this.defaultComponentProps, { index })
    )

    if (typeof OriginalComponent.type === 'string') {
      return (
        <Component {...props} key={`${index}`} style={{ alignSelf: 'stretch' }}>
          {OriginalComponent}
        </Component>
      )
    }

    if (typeof OriginalComponent === 'string') {
      ComponentContainer = this.loadComponent(OriginalComponent, index)
    }

    return (
      <TransitionGroup key={`${index}`} style={{ alignSelf: 'stretch' }}>
        {ComponentContainer}
      </TransitionGroup>
    )
  }

  renderComponents () {
    if (!this.components() || this.components().length === 0) {
      return
    }

    var index = 1
    return this.components().map(component => {
      index = index + 1
      return this.renderComponent(component, index)
    })
  }

  redirect (pathname) {
    return (
      <Redirect
        exact
        push
        to={{
          pathname
        }}
      />
    )
  }

  triggerRedirect (link) {
    if (this.isSamePath(this.path, link)) {
      return
    }

    this.setState({ redirect: { push: true, pathname: link } })
  }

  triggerRawRedirect (link) {
    window.open(link, '_blank')
  }

  get cover () {
    return this._props.cover
  }

  renderScreenLayout () {
    const ScreenLayout = this.layout
    return (
      <ScreenLayout
        section={this.state.section}
        onMenuItem={this._onMenuItem}
        onEvent={this._onEvent}
        scroll={this.state.scroll}
        width={this.state.width}
        height={this.state.height}
        onSidebarMenuSelected={this._sidebarMenuSelected}
        isSmallScreen={this.isSmallScreen}
        {...this._props}
        cache={this.props.cache}
        sidebar={this.props.sidebar}
        sidebarIndex={this.props.sidebarIndex}
        private={this.props.private}
        cover={this.cover}>
        { this.renderComponents() }
      </ScreenLayout>
    )
  }

  sidebarMenuSelected (item) {
    if (!item) {
      return
    }

    if (item.action && this[item.action]) {
      this[item.action]()
      return
    }

    this.triggerRedirect(`${item.path}`)
  }

  saveAuth (account) {
    return Data.Cache.cacheAuth(account).then(() => {
      this.loggedIn(account)
    })
  }

  renderStopError (e) {
    return

  }

  renderLoading () {
    return

      <DefaultComponents.Loading message={this.state.loadingMessage || 'Loading, just a sec please ...'} />

  }

  render () {
    if (this.state.skip) {
      return

    }

    if (this.state.stopError) {
      return this.renderStopError(this.state.stopError)
    }

    if (this.state.height === 0) {
      return

    }

    if (this.state.redirect) {
      const { pathname, push } = this.state.redirect
      if (!this.isSamePath(this.path, pathname)) {
        return this.redirect(pathname, push)
      }
    }

    var height = `${this.height}px`

    return (

        {this.renderScreenLayout()}

    )
  }
}
<|endoftext|>


<commit_before>import { parseResponse } from "./http.js"

const mentionsRegExp = /\B@([a-zA-Z][a-zA-Z0-9_-]{0,17})/g
const urlsRegExp = /\b(https?:\/\/[\-A-Za-z0-9+&@#\/%?=~_|!:,\.;]*[\-A-Za-z0-9+&@#\/%=~_|])/gi
const imageExtRegExp = /(\.gif|\.jpg|\.jpeg|\.png|\.avif|\.apng|\.webp|\.bmp|\.ico|\.tif|\.tiff|\.svg)$/
const videoExtRegExp = /(\.mp4|\.webm|\.3gp|\.mov)$/
const audioExtRegExp = /(\.wav|\.mp3|\.aac|\.ogg|\.flac)$/

export function isLocalhost() {
    return ["localhost", "127.0.0.1"].includes(window.location.hostname)
}

export function isObject(x) {
    return typeof x === "object" && x !== null
}

export function isPlainObject(x) {
    return isObject(x) && !Array.isArray(x)
}

export function smartTrim(s) {
    return s
        .split("\n")
        .map(s => s.replace(/(\s)+/g, "$1").trim())
        .join("\n")
        .replace(/(\n){2,}/g, "$1$1")
        .trim()
}

/**
 * @param {string} s
 */
export function escapeHTML(s) {
    return s
        .replace(/&/g, "&")
        .replace(/</g, "<")
        .replace(/>/g, ">")
        .replace(/"/g, """)
        .replace(/"/g, "'")
}

/**
 * @param {string} s
 */
export function linkify(s) {
    return s
        .replace(mentionsRegExp, '@$1')
        .replace(urlsRegExp, '$1')
}

/**
 * @param {string|Date} date
 */
export function ago(date) {
    if (!(date instanceof Date)) {
        date = new Date(date)
    }
    const now = new Date()
    let diff = (now.getTime() - date.getTime()) / 1000
    if (diff <= 60) {
        return "Just now"
    } else if ((diff /= 60) < 60) {
        return (diff | 0) + "m"
    } else if ((diff /= 60) < 24) {
        return (diff | 0) + "h"
    } else if ((diff /= 24) < 7) {
        return (diff | 0) + "d"
    }
    let text = String(date).split(" ")[1] + " " + date.getDate()
    if (now.getFullYear() !== date.getFullYear()) {
        text += ", " + date.getFullYear()
    }
    return text
}

/**
 * @param {Node} oldNode
 * @param {Node} newNode
 */
export function replaceNode(oldNode, newNode) {
    oldNode.parentNode.insertBefore(newNode, oldNode)
    oldNode.parentNode.removeChild(oldNode)
    return newNode
}

/**
 * @param {string} html
 */
export function el(html) {
    const template = document.createElement("template")
    template.innerHTML = html
    return template.content.childElementCount === 1
        ? template.content.firstElementChild
        : template.content
}

/**
 * @param {ParentNode} el
 */
export async function collectMedia(el) {
    const media = /** @type {Node[]} */ ([])
    for (const link of Array.from(el.querySelectorAll("a"))) {
        if (link.hostname === window.location.hostname && link.pathname.startsWith("/users/")) {
            continue
        }

        const youtubeVideoID = findYouTubeVideoID(link.href)
        if (youtubeVideoID !== null) {
            const img = document.createElement("img")
            img.src = `https://img.youtube.com/vi/${youtubeVideoID}/0.jpg`
            // img.crossOrigin = ""
            img.width = 540
            img.height = 304
            const a = document.createElement("a")
            a.href = link.href
            a.target = "_blank"
            a.rel = "noopener"
            a.className = "media-item youtube-video-wrapper"
            a.appendChild(img)
            a.onclick = ev => {
                ev.preventDefault()
                ev.stopImmediatePropagation()

                const iframe = document.createElement("iframe")
                iframe.src = "https://www.youtube.com/embed/" + encodeURIComponent(youtubeVideoID) + "?autoplay=1"
                iframe.setAttribute("frameborder", "0")
                iframe.allow = "accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
                iframe.allowFullscreen = true
                iframe.className = "media-item youtube"
                a.insertAdjacentElement("afterend", iframe)
                a.remove()
            }
            media.push(a)
            continue
        }

        const coubVideoID = findCoubVideoID(link.href)
        if (coubVideoID !== null) {
            const iframe = document.createElement("iframe")
            iframe.src = "https://coub.com/embed/" + encodeURIComponent(coubVideoID) + "?muted=false&autostart=false&originalSize=true&startWithHD=true"
            iframe.setAttribute("frameborder", "0")
            iframe.allow = "accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
            iframe.allowFullscreen = true
            iframe.className = "media-item coub"
            media.push(iframe)
            continue
        }

        if (isSoundCloudURL(link.href)) {
            const iframe = await getSoundCloudIframe(link.href)
            iframe.className = "media-item soundcloud"
            media.push(iframe)
            continue
        }

        const mt = await mediaType(link.href)
        if (mt === "image") {
            const img = document.createElement("img")
            img.src = link.href
            const a = document.createElement("a")
            a.href = link.href
            a.target = "_blank"
            a.rel = "noopener"
            a.className = "media-item"
            a.appendChild(img)
            media.push(a)
            continue
        }

        if (mt === "audio") {
            const audio = document.createElement("audio")
            audio.src = link.href
            audio.controls = true
            audio.loop = true
            audio.volume = 0.5
            audio.autoplay = false
            audio.className = "media-item"
            media.push(audio)
            continue
        }

        if (mt === "video") {
            const video = document.createElement("video")
            video.src = link.href
            video.controls = true
            video.loop = true
            video.volume = 0.5
            video.autoplay = false
            video.muted = true
            video.className = "media-item"
            media.push(video)
            continue
        }
    }
    return media
}

/**
 * @param {string} url
 */
async function mediaType(url) {
    if (imageExtRegExp.test(url)) {
        return "image"
    }

    if (videoExtRegExp.test(url)) {
        return "video"
    }

    if (audioExtRegExp.test(url)) {
        return "audio"
    }

    const endpoint = "/api/proxy?target=" + encodeURIComponent(url)
    return fetch(endpoint, { method: "HEAD", redirect: "follow" }).then(res => {
        const ct = res.headers.get("Content-Type")
        if (ct.startsWith("image/")) {
            return "image"
        }
        if (ct.startsWith("video/")) {
            return "video"
        }
        if (ct.startsWith("audio/")) {
            return "audio"
        }

        return ""
    }).catch(_ => "")
}

/***
 * @param {string} href
 * @returns {string|null}
 */
function findYouTubeVideoID(href) {
    try {
        const url = new URL(href)
        if ((url.hostname === "www.youtube.com" || url.hostname === "m.youtube.com") && url.pathname === "/watch" && url.searchParams.has("v")) {
            return decodeURIComponent(url.searchParams.get("v"))
        }

        if (url.hostname === "youtu.be" && url.pathname.startsWith("/") && url.pathname !== "/") {
            return decodeURIComponent(url.pathname.substr(1))
        }

        if (url.hostname === "www.youtube.com" && url.pathname.startsWith("/embed/") && url.pathname !== "/embed/") {
            return decodeURIComponent(url.pathname.substr(7))
        }
    } catch (_) { }
    return null
}

function findCoubVideoID(href) {
    try {
        const url = new URL(href)
        if (url.hostname !== "coub.com") {
            return null
        }

        const parts = url.pathname.split("/")
        // /view/{id}
        // /embed/{id}
        if (parts.length === 3 && (parts[1] === "view" || parts[1] === "embed")) {
            return decodeURIComponent(parts[2])
        }
    } catch (_) { }
    return null
}

function isSoundCloudURL(href) {
    try {
        const url = new URL(href)
        if (url.hostname === "soundcloud.com") {
            return true
        }
    } catch (_) { }
    return false
}

function getSoundCloudIframe(url) {
    return fetch(`http://soundcloud.com/oembed?format=json&url=${encodeURIComponent(url)}`).then(parseResponse).then(body => {
        const tmpl = document.createElement("template")
        tmpl.innerHTML = body.html
        return tmpl.content.querySelector("iframe")
    })
}

/**
 * @param {ArrayBuffer} buff
 * @returns {string}
 */
export function arrayBufferToBase64(buff) {
    return btoa(
        new Uint8Array(buff)
            .reduce((data, byte) => data + String.fromCharCode(byte), "")
    )
        .replace(/\+/g, "-")
        .replace(/\//g, "_")
        .replace(/=/g, "")
}

/**
 * @param {string} s
 * @returns {ArrayBuffer}
 */
export function base64ToArrayBuffer(s) {
    return Uint8Array.from(atob(s), c => c.charCodeAt(0))
}
<commit_msg>web/static: use https on sound cloud<commit_after>import { parseResponse } from "./http.js"

const mentionsRegExp = /\B@([a-zA-Z][a-zA-Z0-9_-]{0,17})/g
const urlsRegExp = /\b(https?:\/\/[\-A-Za-z0-9+&@#\/%?=~_|!:,\.;]*[\-A-Za-z0-9+&@#\/%=~_|])/gi
const imageExtRegExp = /(\.gif|\.jpg|\.jpeg|\.png|\.avif|\.apng|\.webp|\.bmp|\.ico|\.tif|\.tiff|\.svg)$/
const videoExtRegExp = /(\.mp4|\.webm|\.3gp|\.mov)$/
const audioExtRegExp = /(\.wav|\.mp3|\.aac|\.ogg|\.flac)$/

export function isLocalhost() {
    return ["localhost", "127.0.0.1"].includes(window.location.hostname)
}

export function isObject(x) {
    return typeof x === "object" && x !== null
}

export function isPlainObject(x) {
    return isObject(x) && !Array.isArray(x)
}

export function smartTrim(s) {
    return s
        .split("\n")
        .map(s => s.replace(/(\s)+/g, "$1").trim())
        .join("\n")
        .replace(/(\n){2,}/g, "$1$1")
        .trim()
}

/**
 * @param {string} s
 */
export function escapeHTML(s) {
    return s
        .replace(/&/g, "&")
        .replace(/</g, "<")
        .replace(/>/g, ">")
        .replace(/"/g, """)
        .replace(/"/g, "'")
}

/**
 * @param {string} s
 */
export function linkify(s) {
    return s
        .replace(mentionsRegExp, '@$1')
        .replace(urlsRegExp, '$1')
}

/**
 * @param {string|Date} date
 */
export function ago(date) {
    if (!(date instanceof Date)) {
        date = new Date(date)
    }
    const now = new Date()
    let diff = (now.getTime() - date.getTime()) / 1000
    if (diff <= 60) {
        return "Just now"
    } else if ((diff /= 60) < 60) {
        return (diff | 0) + "m"
    } else if ((diff /= 60) < 24) {
        return (diff | 0) + "h"
    } else if ((diff /= 24) < 7) {
        return (diff | 0) + "d"
    }
    let text = String(date).split(" ")[1] + " " + date.getDate()
    if (now.getFullYear() !== date.getFullYear()) {
        text += ", " + date.getFullYear()
    }
    return text
}

/**
 * @param {Node} oldNode
 * @param {Node} newNode
 */
export function replaceNode(oldNode, newNode) {
    oldNode.parentNode.insertBefore(newNode, oldNode)
    oldNode.parentNode.removeChild(oldNode)
    return newNode
}

/**
 * @param {string} html
 */
export function el(html) {
    const template = document.createElement("template")
    template.innerHTML = html
    return template.content.childElementCount === 1
        ? template.content.firstElementChild
        : template.content
}

/**
 * @param {ParentNode} el
 */
export async function collectMedia(el) {
    const media = /** @type {Node[]} */ ([])
    for (const link of Array.from(el.querySelectorAll("a"))) {
        if (link.hostname === window.location.hostname && link.pathname.startsWith("/users/")) {
            continue
        }

        const youtubeVideoID = findYouTubeVideoID(link.href)
        if (youtubeVideoID !== null) {
            const img = document.createElement("img")
            img.src = `https://img.youtube.com/vi/${youtubeVideoID}/0.jpg`
            // img.crossOrigin = ""
            img.width = 540
            img.height = 304
            const a = document.createElement("a")
            a.href = link.href
            a.target = "_blank"
            a.rel = "noopener"
            a.className = "media-item youtube-video-wrapper"
            a.appendChild(img)
            a.onclick = ev => {
                ev.preventDefault()
                ev.stopImmediatePropagation()

                const iframe = document.createElement("iframe")
                iframe.src = "https://www.youtube.com/embed/" + encodeURIComponent(youtubeVideoID) + "?autoplay=1"
                iframe.setAttribute("frameborder", "0")
                iframe.allow = "accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
                iframe.allowFullscreen = true
                iframe.className = "media-item youtube"
                a.insertAdjacentElement("afterend", iframe)
                a.remove()
            }
            media.push(a)
            continue
        }

        const coubVideoID = findCoubVideoID(link.href)
        if (coubVideoID !== null) {
            const iframe = document.createElement("iframe")
            iframe.src = "https://coub.com/embed/" + encodeURIComponent(coubVideoID) + "?muted=false&autostart=false&originalSize=true&startWithHD=true"
            iframe.setAttribute("frameborder", "0")
            iframe.allow = "accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
            iframe.allowFullscreen = true
            iframe.className = "media-item coub"
            media.push(iframe)
            continue
        }

        if (isSoundCloudURL(link.href)) {
            const iframe = await getSoundCloudIframe(link.href)
            iframe.className = "media-item soundcloud"
            media.push(iframe)
            continue
        }

        const mt = await mediaType(link.href)
        if (mt === "image") {
            const img = document.createElement("img")
            img.src = link.href
            const a = document.createElement("a")
            a.href = link.href
            a.target = "_blank"
            a.rel = "noopener"
            a.className = "media-item"
            a.appendChild(img)
            media.push(a)
            continue
        }

        if (mt === "audio") {
            const audio = document.createElement("audio")
            audio.src = link.href
            audio.controls = true
            audio.loop = true
            audio.volume = 0.5
            audio.autoplay = false
            audio.className = "media-item"
            media.push(audio)
            continue
        }

        if (mt === "video") {
            const video = document.createElement("video")
            video.src = link.href
            video.controls = true
            video.loop = true
            video.volume = 0.5
            video.autoplay = false
            video.muted = true
            video.className = "media-item"
            media.push(video)
            continue
        }
    }
    return media
}

/**
 * @param {string} url
 */
async function mediaType(url) {
    if (imageExtRegExp.test(url)) {
        return "image"
    }

    if (videoExtRegExp.test(url)) {
        return "video"
    }

    if (audioExtRegExp.test(url)) {
        return "audio"
    }

    const endpoint = "/api/proxy?target=" + encodeURIComponent(url)
    return fetch(endpoint, { method: "HEAD", redirect: "follow" }).then(res => {
        const ct = res.headers.get("Content-Type")
        if (ct.startsWith("image/")) {
            return "image"
        }
        if (ct.startsWith("video/")) {
            return "video"
        }
        if (ct.startsWith("audio/")) {
            return "audio"
        }

        return ""
    }).catch(_ => "")
}

/***
 * @param {string} href
 * @returns {string|null}
 */
function findYouTubeVideoID(href) {
    try {
        const url = new URL(href)
        if ((url.hostname === "www.youtube.com" || url.hostname === "m.youtube.com") && url.pathname === "/watch" && url.searchParams.has("v")) {
            return decodeURIComponent(url.searchParams.get("v"))
        }

        if (url.hostname === "youtu.be" && url.pathname.startsWith("/") && url.pathname !== "/") {
            return decodeURIComponent(url.pathname.substr(1))
        }

        if (url.hostname === "www.youtube.com" && url.pathname.startsWith("/embed/") && url.pathname !== "/embed/") {
            return decodeURIComponent(url.pathname.substr(7))
        }
    } catch (_) { }
    return null
}

function findCoubVideoID(href) {
    try {
        const url = new URL(href)
        if (url.hostname !== "coub.com") {
            return null
        }

        const parts = url.pathname.split("/")
        // /view/{id}
        // /embed/{id}
        if (parts.length === 3 && (parts[1] === "view" || parts[1] === "embed")) {
            return decodeURIComponent(parts[2])
        }
    } catch (_) { }
    return null
}

function isSoundCloudURL(href) {
    try {
        const url = new URL(href)
        if (url.hostname === "soundcloud.com") {
            return true
        }

        // /{username}/{slug}
        const parts = url.pathname.split("/")
        return parts.length == 3
    } catch (_) { }
    return false
}

function getSoundCloudIframe(url) {
    return fetch(`https://soundcloud.com/oembed?format=json&url=${encodeURIComponent(url)}`).then(parseResponse).then(body => {
        const tmpl = document.createElement("template")
        tmpl.innerHTML = body.html
        return tmpl.content.querySelector("iframe")
    })
}

/**
 * @param {ArrayBuffer} buff
 * @returns {string}
 */
export function arrayBufferToBase64(buff) {
    return btoa(
        new Uint8Array(buff)
            .reduce((data, byte) => data + String.fromCharCode(byte), "")
    )
        .replace(/\+/g, "-")
        .replace(/\//g, "_")
        .replace(/=/g, "")
}

/**
 * @param {string} s
 * @returns {ArrayBuffer}
 */
export function base64ToArrayBuffer(s) {
    return Uint8Array.from(atob(s), c => c.charCodeAt(0))
}
<|endoftext|>


<commit_before>'use strict';

// --------------------------------------------------------------------
// Private stuff
// --------------------------------------------------------------------

function PosInfo(globalApplicationStack) {
  this.globalApplicationStack = globalApplicationStack;
  this.applicationStack = [];
  // Redundant (could be generated from applicationStack) but useful for performance reasons.
  this.activeApplications = {};
  this.memo = {};
}

PosInfo.prototype = {
  isActive: function(application) {
    return this.activeApplications[application.toMemoKey()];
  },

  enter: function(application) {
    this.globalApplicationStack.push(application);
    this.applicationStack.push(application);
    this.activeApplications[application.toMemoKey()] = true;
  },

  exit: function() {
    var application = this.globalApplicationStack.pop();
    this.applicationStack.pop();
    this.activeApplications[application.toMemoKey()] = false;
  },

  shouldUseMemoizedResult: function(memoRec) {
    var involvedApplications = memoRec.involvedApplications;
    for (var memoKey in involvedApplications) {
      if (involvedApplications[memoKey] && this.activeApplications[memoKey]) {
        return false;
      }
    }
    return true;
  },

  getCurrentLeftRecursion: function() {
    if (this.leftRecursionStack) {
      return this.leftRecursionStack[this.leftRecursionStack.length - 1];
    }
  },

  startLeftRecursion: function(application) {
    if (!this.leftRecursionStack) {
      this.leftRecursionStack = [];
    }
    this.leftRecursionStack.push({
        memoKey: application.toMemoKey(),
        value: false,
        pos: -1,
        involvedApplications: {}});
    this.updateInvolvedApplications();
  },

  endLeftRecursion: function(application) {
    this.leftRecursionStack.pop();
  },

  updateInvolvedApplications: function() {
    var currentLeftRecursion = this.getCurrentLeftRecursion();
    var involvedApplications = currentLeftRecursion.involvedApplications;
    var lrApplicationMemoKey = currentLeftRecursion.memoKey;
    var idx = this.applicationStack.length - 1;
    while (true) {
      var memoKey = this.applicationStack[idx--].toMemoKey();
      if (memoKey === lrApplicationMemoKey) {
        break;
      }
      involvedApplications[memoKey] = true;
    }
  }
};

// --------------------------------------------------------------------
// Exports
// --------------------------------------------------------------------

module.exports = PosInfo;
<commit_msg>Remove for-in loop in PosInfo -- has significant effect on perf.<commit_after>'use strict';

// --------------------------------------------------------------------
// Private stuff
// --------------------------------------------------------------------

function PosInfo(globalApplicationStack) {
  this.globalApplicationStack = globalApplicationStack;
  this.applicationStack = [];
  // Redundant (could be generated from applicationStack) but useful for performance reasons.
  this.activeApplications = {};
  this.memo = {};
}

PosInfo.prototype = {
  isActive: function(application) {
    return this.activeApplications[application.toMemoKey()];
  },

  enter: function(application) {
    this.globalApplicationStack.push(application);
    this.applicationStack.push(application);
    this.activeApplications[application.toMemoKey()] = true;
  },

  exit: function() {
    var application = this.globalApplicationStack.pop();
    this.applicationStack.pop();
    this.activeApplications[application.toMemoKey()] = false;
  },

  shouldUseMemoizedResult: function(memoRec) {
    var involvedApplications = memoRec.involvedApplications;
    if (involvedApplications != null) {
      var keys = Object.keys(involvedApplications);
      for (var i = 0; i < keys.length; ++i) {
        var memoKey = keys[i];
        if (involvedApplications[memoKey] && this.activeApplications[memoKey]) {
          return false;
        }
      }
    }
    return true;
  },

  getCurrentLeftRecursion: function() {
    if (this.leftRecursionStack) {
      return this.leftRecursionStack[this.leftRecursionStack.length - 1];
    }
  },

  startLeftRecursion: function(application) {
    if (!this.leftRecursionStack) {
      this.leftRecursionStack = [];
    }
    this.leftRecursionStack.push({
        memoKey: application.toMemoKey(),
        value: false,
        pos: -1,
        involvedApplications: {}});
    this.updateInvolvedApplications();
  },

  endLeftRecursion: function(application) {
    this.leftRecursionStack.pop();
  },

  updateInvolvedApplications: function() {
    var currentLeftRecursion = this.getCurrentLeftRecursion();
    var involvedApplications = currentLeftRecursion.involvedApplications;
    var lrApplicationMemoKey = currentLeftRecursion.memoKey;
    var idx = this.applicationStack.length - 1;
    while (true) {
      var memoKey = this.applicationStack[idx--].toMemoKey();
      if (memoKey === lrApplicationMemoKey) {
        break;
      }
      involvedApplications[memoKey] = true;
    }
  }
};

// --------------------------------------------------------------------
// Exports
// --------------------------------------------------------------------

module.exports = PosInfo;
<|endoftext|>


<commit_before>var canvas = document.getElementsByTagName('canvas')[0];
var ctx = null;
var body = document.getElementsByTagName('body')[0];
var keysDown = new Array();
var SCREENWIDTH  = 640;
var SCREENHEIGHT = 480;
var MODE_TITLE = 0;
var MODE_PLAY  = 1;
var MODE_WIN   = 2;

function getImage(name)
{
    image = new Image();
    image.src = 'graphics/'+name+'.png';
    return image;
}

function drawChar(context, c, x, y)
{
    c = c.charCodeAt(0);
    if(c > 0) {
        context.drawImage(bitfont, c*6, 0, 6,8, x, y, 12, 16);
    }
}

function drawString(context, string, x, y) {
    string = string.toUpperCase();
    for(i = 0; i < string.length; i++) {
	drawChar(context, string[i], x, y);
	x += 12;
    }
}

function paintTitleBitmaps()
{
    drawString(titlectx, 'This is a demo of the JavaScript/HTML5 game loop',32,32);
    drawString(winctx, 'Your game should always have an ending',32,32);
}

function makeTitleBitmaps()
{
    titleBitmap = document.createElement('canvas');
    titleBitmap.width = SCREENWIDTH;
    titleBitmap.height = SCREENHEIGHT;
    titlectx = titleBitmap.getContext('2d');
    winBitmap = document.createElement('canvas');
    winBitmap.width = SCREENWIDTH;
    winBitmap.height = SCREENHEIGHT;
    winctx = winBitmap.getContext('2d');
    bitfont = new Image();
    bitfont.src = "graphics/bitfont.png";
    bitfont.onload = paintTitleBitmaps;
}

function resetGame()
{
    x = 128;
    y = 128;
}

function init()
{
    mode = MODE_TITLE;
    playerImage = getImage("player");
    springSound = new Audio("audio/boing.wav");
    makeTitleBitmaps();
    return true;
}

function draw() {
    ctx.fillStyle = "#0000ff";
    ctx.fillRect(0, 0, SCREENWIDTH, SCREENHEIGHT);

    if(mode == MODE_TITLE) {
	ctx.drawImage(titleBitmap, 0, 0);
	return;
    }

    ctx.drawImage(playerImage, x, y);

    if(mode == MODE_WIN) {
	ctx.drawImage(winBitmap, 0, 0);
    }
}

function processKeys() {
    if(keysDown[40] || keysDown[83]) y += 4;
    if(keysDown[38] || keysDown[87]) y -= 4;
    if(keysDown[37] || keysDown[65]) x -= 4;
    if(keysDown[39] || keysDown[68]) x += 4;

    gamepads = navigator.getGamepads();
    for(var i=0;i<gamepads.length;i++) {
	if(gamepads[i] != null) {
	    console.log("checking gamepad "+i+" button 0: "+gamepads[i].buttons[0].pressed);
	    if(gamepads[i].buttons[0].pressed) x+=4;
	}
    }

    if(x < 0) x = 0;
    if(x > SCREENWIDTH - playerImage.width)  x = SCREENHEIGHT - playerImage.width;
    if(y < 0) y = 0;
    if(y > SCREENWIDTH - playerImage.height) y = SCREENHEIGHT - playerImage.height;
}

function degrees(r) {
    return r*(180/Math.PI);
}

function radians(r) {
    return r*(Math.PI/180);
}

function lineIntersection(ballx, bally, ballxv, ballyv,
			  lstartx, lstarty, lxv, lyv)
{
    j = (lstarty/ballyv - bally/ballyv + ballx/ballxv -lstartx/ballxv)/(lxv/ballxv -lyv/ballyv);
    i = (lstartx+j*lxv-ballx)/ballxv;
    return [i,j];
}

function lineLen(x,y)
{
    return Math.sqrt(x*x+y*y);
}

function intersectPoly(poly, collisions, ball, considerRadius, lastCollisionObjectID)
{
    if(!poly.alive) return;
    lines = getTaggedPolyLines(poly);
    hitline = null;
    lowi = 1.1;

    for(ln=0;ln<lines.length;ln++) {
	l = lines[ln]
        if(l.ident == lastCollisionObjectID) continue;
        lxv = l.x2-l.x1;
	lyv = l.y2-l.y1;
	lnormx = -lyv;
        lnormy = lxv;
        normScale = lineLen(lnormx,lnormy);
        lnormx = lnormx / normScale;
        lnormy = lnormy / normScale;
        dotproduct = lnormx*ball.dx+lnormy*ball.dy;
        if(dotproduct>0) {
            lnormx = -lnormx;
            lnormy = -lnormy;
	}
        if(considerRadius) {
            // Make a parallel line
            lstartx = l.x1+lnormx*ball.radius;
            lstarty = l.y1+lnormy*ball.radius;
	}
        else {
            lstartx = l.x1;
            lstarty = l.y1;
	}

        res = lineIntersection(ball.x,ball.y,ball.dx,ball.dy,
                               lstartx,lstarty,lxv,lyv);
        i = res[0];
	j = res[1];
        if(i>=0 && i<=1 && j>=0 && j<=1) {
            if(i<lowi) {
                lowi = i;
                hitline = l;
	    }
	}

        }
    if(hitline != null) {
	// Ok, now we can figure out the angle wrt the surface normal...
        lxv = hitline.x2 - hitline.x1;
        lyv = hitline.y2 - hitline.y1;

        lnormx = -lyv;
        lnormy = lxv;
        normScale = Math.sqrt(lnormx*lnormx+lnormy*lnormy);
        lnormx = lnormx / normScale;
        lnormy = lnormy / normScale;
        dotproduct = lnormx*ball.dx+lnormy*ball.dy;
	// This time, we make the normal in the same direction as the ball vector
        if(dotproduct<0) {
	    lnormx = -lnormx;
	    lnormy = -lnormy;
	}
        lenNormal = 1.0;
        ballToIntersectX = lowi*ball.dx;
        ballToIntersectY = lowi*ball.dy;
        distToIntersect = Math.sqrt(ballToIntersectX*ballToIntersectX
                                    +ballToIntersectY*ballToIntersectY);
        ballToIntersectX /= distToIntersect;
        ballToIntersectY /= distToIntersect;
        angle = Math.acos(ballToIntersectX*lnormx+ballToIntersectY*lnormy);
        console.log("Collision is at "+degrees(angle)+" degrees to surface normal");
        normAngle = Math.atan2(lnormy,lnormx)+Math.PI;
        incident = Math.atan2(ball.dy,ball.dx)+Math.PI;
        angle =  incident - normAngle;

        outangle = normAngle - angle;

        //if(poly.region.collide == False) {
        //    outangle = incident - Math.PI;
	//}
        collisions.push(new Collision(ball.x + lowi*ball.dx, ball.y+lowi*ball.dy, lowi, outangle,poly));
	console.log("Intersection with polygon "+poly.ident+" line "+hitline.ident);
    }
}

function checkClosestApproach(point, startx, starty, dx, dy)
{
    normx = -dy;
    normy = dx;
    res = lineIntersection(startx,starty,dx,dy,
                             point.x1,point.y1,normx,normy);
    i = res[0]; j = res[1];
    dist = Math.abs(j*lineLen(normx,normy));
    return [dist,i];
}
function intersectVertices(points, collisions, ballx,bally,ballxv,ballyv, ballRadius,lastCollisionObjectID)
{
    for(pointindex=0;pointindex<points.length;pointindex++) {
	p = points[pointindex];
        if(p.ident == lastCollisionObjectID) continue;
        res = checkClosestApproach(p,ballx,bally,ballxv,ballyv);

	d = res[0]; i = res[1];
        if(d<ballRadius) {
            diff = ballRadius - d;
            dist = Math.sqrt(ballRadius*ballRadius-d*d);
            iPoint = (lineLen(ballxv,ballyv)*i-dist)/lineLen(ballxv,ballyv); // Distance to intersect
            if(iPoint >=0 && iPoint <=1) {
                ix = ballx+ballxv*iPoint;
                iy = bally+ballyv*iPoint;
                radiusAng = Math.atan2(iy-p.y1,ix-p.x1);
                incident = Math.atan2(ballyv,ballxv)+Math.PI;
                angle =  incident - radiusAng;
                console.log("Collides with ident "+p.ident+" Incident angle = "+degrees(incident));
                console.log("Surface normal angle = "+degrees(radiusAng));
                outangle = radiusAng - angle;
		// Horrible way to get the polygon of this point by parsing the ident...
		split = p.ident.indexOf("-");
		polyNum = p.ident.slice(4,split);
                collisions.push(new Collision(ix,iy, iPoint, outangle, fragments[polyNum]))
	    }
	}
    }
}

function drawRepeat() {
    if(mode != MODE_TITLE) {
	processKeys();
    }
    draw();
    if(!stopRunloop) setTimeout('drawRepeat()',20);
}

window.addEventListener("gamepadconnected", function(e) {
    console.log("Gamepad connected at index %d: %s. %d buttons, %d axes.",
		e.gamepad.index, e.gamepad.id,
		e.gamepad.buttons.length, e.gamepad.axes.length);
});

window.addEventListener("gamepaddisconnected", function(e) {
    console.log("Gamepad disconnected from index %d: %s",
		e.gamepad.index, e.gamepad.id);
});

if (canvas.getContext('2d')) {
    stopRunloop = false;
    ctx = canvas.getContext('2d');
    body.onkeydown = function (event) {
	var c = event.keyCode;
        keysDown[c] = 1;
	if(c == 81) {
	    stopRunloop=true;
	}
	if(c == 32) {
	    if(mode == MODE_TITLE) {
		resetGame();
		mode = MODE_PLAY;
	    }
	}
	if(c == 82) {
	    if(mode == MODE_WIN) {
		mode = MODE_TITLE;
	    }
	}
    };

    body.onkeyup = function (event) {
	var c = event.keyCode;
        keysDown[c] = 0;
    };

    if(init()) {
      drawRepeat();
    }
}
<commit_msg>More code imported from LD34<commit_after>var canvas = document.getElementsByTagName('canvas')[0];
var ctx = null;
var body = document.getElementsByTagName('body')[0];
var keysDown = new Array();
var SCREENWIDTH  = 640;
var SCREENHEIGHT = 480;
var MODE_TITLE = 0;
var MODE_PLAY  = 1;
var MODE_WIN   = 2;

var x = 0, y = 0, dx = 1, dy=1;
var ballRadius = 32;
function getImage(name)
{
    image = new Image();
    image.src = 'graphics/'+name+'.png';
    return image;
}

function drawChar(context, c, x, y)
{
    c = c.charCodeAt(0);
    if(c > 0) {
        context.drawImage(bitfont, c*6, 0, 6,8, x, y, 12, 16);
    }
}

function drawString(context, string, x, y) {
    string = string.toUpperCase();
    for(i = 0; i < string.length; i++) {
	drawChar(context, string[i], x, y);
	x += 12;
    }
}

function paintTitleBitmaps()
{
    drawString(titlectx, 'This is a demo of the JavaScript/HTML5 game loop',32,32);
    drawString(winctx, 'Your game should always have an ending',32,32);
}

function makeTitleBitmaps()
{
    titleBitmap = document.createElement('canvas');
    titleBitmap.width = SCREENWIDTH;
    titleBitmap.height = SCREENHEIGHT;
    titlectx = titleBitmap.getContext('2d');
    winBitmap = document.createElement('canvas');
    winBitmap.width = SCREENWIDTH;
    winBitmap.height = SCREENHEIGHT;
    winctx = winBitmap.getContext('2d');
    bitfont = new Image();
    bitfont.src = "graphics/bitfont.png";
    bitfont.onload = paintTitleBitmaps;
}

function resetGame()
{
    x = 128;
    y = 128;
}

function loadPolygon(line)
{
    poly = new Array();
    scale = 0.5;
    pointArray = line.split(" ");
    for(var p=0;p < pointArray.length;p++) {
	point = pointArray[p];
	xy = point.split(",");
	// polygons are specified on a 1-1000 scale.
	poly.push([(xy[0]-500)*scale+320, xy[1]*scale]);
    }
    return poly;
}

function TaggedPoly(ident, pointarray, region) {
    this.poly = pointarray;
    this.region = region;
    this.ident = ident;
    this.alive = true;
}

function loadFragments()
{
    fragments = new Array();
    outline = new Array();
    colours = new Array();
    borderColours = new Array();

    totalFragments = 0;

    lineArray = [ "171.445210,58.112124 0.035655,229.521680 471.411930,700.897960 985.640600,229.521680 814.231040,58.112124" ];

    for(var l = 0;l< lineArray.length; l++) {
	line = lineArray[l];
	poly = loadPolygon(line);
	outline.push(new TaggedPoly("outline"+l, poly, null));
    }
}

function init()
{
    mode = MODE_TITLE;
    playerImage = getImage("player");
    springSound = new Audio("audio/boing.wav");
    makeTitleBitmaps();
    nextRoom();
    return true;
}

function nextRoom()
{
    loadFragments();
}

function draw() {
    ctx.fillStyle = "#0000ff";
    ctx.fillRect(0, 0, SCREENWIDTH, SCREENHEIGHT);

    if(mode == MODE_TITLE) {
	ctx.drawImage(titleBitmap, 0, 0);
	return;
    }

    ctx.drawImage(playerImage, x, y);

    if(mode == MODE_WIN) {
	ctx.drawImage(winBitmap, 0, 0);
    }
}

function processKeys() {
    if(keysDown[40] || keysDown[83]) y += 4;
    if(keysDown[38] || keysDown[87]) y -= 4;
    if(keysDown[37] || keysDown[65]) x -= 4;
    if(keysDown[39] || keysDown[68]) x += 4;

    gamepads = navigator.getGamepads();
    for(var i=0;i<gamepads.length;i++) {
	if(gamepads[i] != null) {
	    console.log("checking gamepad "+i+" button 0: "+gamepads[i].buttons[0].pressed);
	    if(gamepads[i].buttons[0].pressed) x+=4;
	}
    }

    if(x < 0) x = 0;
    if(x > SCREENWIDTH - playerImage.width)  x = SCREENHEIGHT - playerImage.width;
    if(y < 0) y = 0;
    if(y > SCREENWIDTH - playerImage.height) y = SCREENHEIGHT - playerImage.height;
}

function degrees(r) {
    return r*(180/Math.PI);
}

function radians(r) {
    return r*(Math.PI/180);
}

function lineIntersection(ballx, bally, ballxv, ballyv,
			  lstartx, lstarty, lxv, lyv)
{
    j = (lstarty/ballyv - bally/ballyv + ballx/ballxv -lstartx/ballxv)/(lxv/ballxv -lyv/ballyv);
    i = (lstartx+j*lxv-ballx)/ballxv;
    return [i,j];
}

function lineLen(x,y)
{
    return Math.sqrt(x*x+y*y);
}

function intersectPoly(poly, collisions, ball, considerRadius, lastCollisionObjectID)
{
    if(!poly.alive) return;
    lines = getTaggedPolyLines(poly);
    hitline = null;
    lowi = 1.1;

    for(ln=0;ln<lines.length;ln++) {
	l = lines[ln]
        if(l.ident == lastCollisionObjectID) continue;
        lxv = l.x2-l.x1;
	lyv = l.y2-l.y1;
	lnormx = -lyv;
        lnormy = lxv;
        normScale = lineLen(lnormx,lnormy);
        lnormx = lnormx / normScale;
        lnormy = lnormy / normScale;
        dotproduct = lnormx*ball.dx+lnormy*ball.dy;
        if(dotproduct>0) {
            lnormx = -lnormx;
            lnormy = -lnormy;
	}
        if(considerRadius) {
            // Make a parallel line
            lstartx = l.x1+lnormx*ball.radius;
            lstarty = l.y1+lnormy*ball.radius;
	}
        else {
            lstartx = l.x1;
            lstarty = l.y1;
	}

        res = lineIntersection(ball.x,ball.y,ball.dx,ball.dy,
                               lstartx,lstarty,lxv,lyv);
        i = res[0];
	j = res[1];
        if(i>=0 && i<=1 && j>=0 && j<=1) {
            if(i<lowi) {
                lowi = i;
                hitline = l;
	    }
	}

        }
    if(hitline != null) {
	// Ok, now we can figure out the angle wrt the surface normal...
        lxv = hitline.x2 - hitline.x1;
        lyv = hitline.y2 - hitline.y1;

        lnormx = -lyv;
        lnormy = lxv;
        normScale = Math.sqrt(lnormx*lnormx+lnormy*lnormy);
        lnormx = lnormx / normScale;
        lnormy = lnormy / normScale;
        dotproduct = lnormx*ball.dx+lnormy*ball.dy;
	// This time, we make the normal in the same direction as the ball vector
        if(dotproduct<0) {
	    lnormx = -lnormx;
	    lnormy = -lnormy;
	}
        lenNormal = 1.0;
        ballToIntersectX = lowi*ball.dx;
        ballToIntersectY = lowi*ball.dy;
        distToIntersect = Math.sqrt(ballToIntersectX*ballToIntersectX
                                    +ballToIntersectY*ballToIntersectY);
        ballToIntersectX /= distToIntersect;
        ballToIntersectY /= distToIntersect;
        angle = Math.acos(ballToIntersectX*lnormx+ballToIntersectY*lnormy);
        console.log("Collision is at "+degrees(angle)+" degrees to surface normal");
        normAngle = Math.atan2(lnormy,lnormx)+Math.PI;
        incident = Math.atan2(ball.dy,ball.dx)+Math.PI;
        angle =  incident - normAngle;

        outangle = normAngle - angle;

        //if(poly.region.collide == False) {
        //    outangle = incident - Math.PI;
	//}
        collisions.push(new Collision(ball.x + lowi*ball.dx, ball.y+lowi*ball.dy, lowi, outangle,poly));
	console.log("Intersection with polygon "+poly.ident+" line "+hitline.ident);
    }
}

function checkClosestApproach(point, startx, starty, dx, dy)
{
    normx = -dy;
    normy = dx;
    res = lineIntersection(startx,starty,dx,dy,
                             point.x1,point.y1,normx,normy);
    i = res[0]; j = res[1];
    dist = Math.abs(j*lineLen(normx,normy));
    return [dist,i];
}
function intersectVertices(points, collisions, ballx,bally,ballxv,ballyv, ballRadius,lastCollisionObjectID)
{
    for(pointindex=0;pointindex<points.length;pointindex++) {
	p = points[pointindex];
        if(p.ident == lastCollisionObjectID) continue;
        res = checkClosestApproach(p,ballx,bally,ballxv,ballyv);

	d = res[0]; i = res[1];
        if(d<ballRadius) {
            diff = ballRadius - d;
            dist = Math.sqrt(ballRadius*ballRadius-d*d);
            iPoint = (lineLen(ballxv,ballyv)*i-dist)/lineLen(ballxv,ballyv); // Distance to intersect
            if(iPoint >=0 && iPoint <=1) {
                ix = ballx+ballxv*iPoint;
                iy = bally+ballyv*iPoint;
                radiusAng = Math.atan2(iy-p.y1,ix-p.x1);
                incident = Math.atan2(ballyv,ballxv)+Math.PI;
                angle =  incident - radiusAng;
                console.log("Collides with ident "+p.ident+" Incident angle = "+degrees(incident));
                console.log("Surface normal angle = "+degrees(radiusAng));
                outangle = radiusAng - angle;
		// Horrible way to get the polygon of this point by parsing the ident...
		split = p.ident.indexOf("-");
		polyNum = p.ident.slice(4,split);
                collisions.push(new Collision(ix,iy, iPoint, outangle, fragments[polyNum]))
	    }
	}
    }
}

function animate()
{
    if(x > SCREENWIDTH || x<0) { dx = -dx; wallSound.play(); }
    if(y > SCREENHEIGHT || y<0) { dy = -dy; if(gameOverTimeout==0) wallSound.play(); }

    var ball = { 'x': x, 'y': y, 'dx': dx, 'dy': dy, 'radius': ballRadius };
    collisions = new Array();
    closest = null;
    lastCollisionObjectID = null;
    {
	for(f=0;f<fragments.length;f++) {
	    poly = fragments[f];
	    lastCollisionObjectID = "";
	    intersectPoly(poly, collisions, ball, 1, lastCollisionObjectID);
	}

	points = new Array();
	for(f=0;f<fragments.length;f++) {
	    poly = fragments[f];
	    if(poly.alive == false) continue;
	    for(p=0;p<poly.poly.length;p++) {
		points.push(new TaggedPoint(poly.poly[p], poly, p));
	    }
	}

	intersectVertices(points, collisions, ball.x,ball.y,ball.dx,ball.dy, ball.radius,lastCollisionObjectID)
	closestDist = 1.1;

	for(c=0;c<collisions.length;c++) {
	    col = collisions[c];
	    console.log(col.ix, col.iy, col.dist, col.outAngle);
	    if(col.dist < closestDist) {
		closest = col;
		closestDist = col.dist;
	    }
	}
    }

    if(closest != null) {
        console.log("Identified collision as "+closest.obj.ident+" at dist "+closestDist);
	ctx.beginPath();
	ctx.moveTo(x,y);
	x = closest.ix;
        y = closest.iy;
        dist = lineLen(dx,dy);
        dx = dist*Math.cos(closest.outAngle);
        dy = dist*Math.sin(closest.outAngle);
	console.log("Moving to "+x+","+y+" with vel "+dx+","+dy);
	// TODO: At the moment we only do one collision per check - we could get into trouble this way...
	ctx.lineTo(x,y);
	ctx.stroke();
	if (shattered) {
	    closest.obj.alive = false;

	    score += 2;
	    totalFragments -= 1;
	    if(totalFragments <= 1) {
		// For some reason we always have 1 fragment left when the game's finished...
		winTimeout = 100;
		winSound.play();
	    }
	    smashNoise[2+randint(3)].play();
	    console.log("Fragments remaining: "+totalFragments);
	} else {
	    shattered = true;
	    smashNoise[randint(2)].play();
	    }
    } else {
	x += dx;
	y += dy;
    }
}

function drawRepeat() {
    if(mode != MODE_TITLE) {
	processKeys();
	animate();
    }
    draw();
    if(!stopRunloop) setTimeout('drawRepeat()',20);
}

window.addEventListener("gamepadconnected", function(e) {
    console.log("Gamepad connected at index %d: %s. %d buttons, %d axes.",
		e.gamepad.index, e.gamepad.id,
		e.gamepad.buttons.length, e.gamepad.axes.length);
});

window.addEventListener("gamepaddisconnected", function(e) {
    console.log("Gamepad disconnected from index %d: %s",
		e.gamepad.index, e.gamepad.id);
});

if (canvas.getContext('2d')) {
    stopRunloop = false;
    ctx = canvas.getContext('2d');
    body.onkeydown = function (event) {
	var c = event.keyCode;
        keysDown[c] = 1;
	if(c == 81) {
	    stopRunloop=true;
	}
	if(c == 32) {
	    if(mode == MODE_TITLE) {
		resetGame();
		mode = MODE_PLAY;
	    }
	}
	if(c == 82) {
	    if(mode == MODE_WIN) {
		mode = MODE_TITLE;
	    }
	}
    };

    body.onkeyup = function (event) {
	var c = event.keyCode;
        keysDown[c] = 0;
    };

    if(init()) {
      drawRepeat();
    }
}
<|endoftext|>


<commit_before>(function () {
	function StaticMapProvider() {

		var _protocol;
		var _apiKey;

		this.useApiKey = function (key) {
			_apiKey = key;
		}

		this.forceProtocol = function (protocol) {
			if (angular.isString(protocol)) {

				_protocol = protocol ? (protocol.replace(/:$/, '') + ':') : protocol;
				return this;
			}

			return _protocol;
		}

		this.$get = [function () {
			return {
				getProtocol: function () {
					return _protocol || '';
				},
				getApiKey: function(){
					return _apiKey || '';
				}
			};
		}];
	}

	GoogleStaticMaps.$inject = ['staticMap'];
	function GoogleStaticMaps(staticMap) {
		var directive = {
			link: link,
			template: '',
			replace: true,
			restrict: 'E',
		};
		function link(scope, element, attrs) {
			var el = element[0];

			if (!attrs.mapWidth) {
				throw new Error('The `mapWidth` is required.');
			}

			if (!attrs.mapHeight) {
				throw new Error('The `mapHeight` is required.');
			}

			if (!attrs.address) {
				throw new Error('The `address` is required.');
			}

			var zoomParam = "&zoom=";
			if (!attrs.zoom) {
				zoomParam += "14"
			} else {
				zoomParam += attrs.zoom
			}

			var urlBase = staticMap.getProtocol() + '//maps.googleapis.com/maps/api/staticmap?center=';

			var sizeParam = "&size=" + attrs.mapWidth + "x" + attrs.mapHeight;
			var endEncoding = encodeURIComponent(attrs.address);
			var markerParam = "&markers=color:red|" + endEncoding;
			var keyParam = "&key=" + staticMap.getApiKey();

			el.alt = attrs.address;
			el.src = urlBase + endEncoding + sizeParam + zoomParam + markerParam + keyParam;
		}
		return directive;
	}

	angular.module('ngGoogleStaticMaps', []).provider('staticMap', StaticMapProvider).directive('staticMap', GoogleStaticMaps);

} ());<commit_msg>Add scale param<commit_after>(function () {
	function StaticMapProvider() {

		var _protocol;
		var _apiKey;

		this.useApiKey = function (key) {
			_apiKey = key;
		}

		this.forceProtocol = function (protocol) {
			if (angular.isString(protocol)) {

				_protocol = protocol ? (protocol.replace(/:$/, '') + ':') : protocol;
				return this;
			}

			return _protocol;
		}

		this.$get = [function () {
			return {
				getProtocol: function () {
					return _protocol || '';
				},
				getApiKey: function(){
					return _apiKey || '';
				}
			};
		}];
	}

	GoogleStaticMaps.$inject = ['staticMap'];
	function GoogleStaticMaps(staticMap) {
		var directive = {
			link: link,
			template: '',
			replace: true,
			restrict: 'E',
		};
		function link(scope, element, attrs) {
			var el = element[0];

			if (!attrs.mapWidth) {
				throw new Error('The `mapWidth` is required.');
			}

			if (!attrs.mapHeight) {
				throw new Error('The `mapHeight` is required.');
			}

			if (!attrs.address) {
				throw new Error('The `address` is required.');
			}

			var zoomParam = "&zoom=";
			if (!attrs.zoom) {
				zoomParam += "14"
			} else {
				zoomParam += attrs.zoom
			}

			var scaleParam = "&scale=";
			if (!attrs.mapScale) {
				scaleParam += "1"
			} else {
				scaleParam += attrs.mapScale
			}

			var urlBase = staticMap.getProtocol() + '//maps.googleapis.com/maps/api/staticmap?center=';

			var sizeParam = "&size=" + attrs.mapWidth + "x" + attrs.mapHeight;
			var endEncoding = encodeURIComponent(attrs.address);
			var markerParam = "&markers=color:red|" + endEncoding;
			var keyParam = "&key=" + staticMap.getApiKey();

			el.alt = attrs.address;
			el.src = urlBase + endEncoding + sizeParam + zoomParam + markerParam + scaleParam + keyParam;
		}
		return directive;
	}

	angular.module('ngGoogleStaticMaps', []).provider('staticMap', StaticMapProvider).directive('staticMap', GoogleStaticMaps);

} ());
<|endoftext|>


<commit_before>import FilterModule from "../FilterModule";
import { dependencies as Inject, singleton as Singleton } from "needlepoint";
import ChatApi from "../../util/ChatApi";
import EmojifyCommand from "../commands/EmojifyCommand";

const emojifiablePattern = /\b((?:[A-Z]\s){2,}[A-Z])\b/;

@Singleton
@Inject(ChatApi)
export default class AutoEmojifyFilter extends FilterModule {
    constructor(api) {
        super();

        this.api = api;
    }

    filter(msg) {
        if (!msg.body) {
            return msg;
        }

        if (AutoEmojifyFilter.shouldEmojify(msg.body)) {
            const response = msg.body.replace(emojifiablePattern, match => EmojifyCommand.emojify(match));

            this.api.sendMessage(response, msg.threadID);
        }

        return msg;
    }

    static shouldEmojify(text) {
        return text.match(emojifiablePattern) !== null;
    }
}<commit_msg>AutoEmojifyFilter: Fix not all emojifiables getting emojified<commit_after>import FilterModule from "../FilterModule";
import { dependencies as Inject, singleton as Singleton } from "needlepoint";
import ChatApi from "../../util/ChatApi";
import EmojifyCommand from "../commands/EmojifyCommand";

const emojifiablePattern = /\b((?:[A-Z]\s){2,}[A-Z])\b/g;

@Singleton
@Inject(ChatApi)
export default class AutoEmojifyFilter extends FilterModule {
    constructor(api) {
        super();

        this.api = api;
    }

    filter(msg) {
        if (!msg.body) {
            return msg;
        }

        if (AutoEmojifyFilter.shouldEmojify(msg.body)) {
            const response = msg.body.replace(emojifiablePattern, match => EmojifyCommand.emojify(match));

            this.api.sendMessage(response, msg.threadID);
        }

        return msg;
    }

    static shouldEmojify(text) {
        return text.match(emojifiablePattern) !== null;
    }
}<|endoftext|>