<commit_before>import "./application.styl"
import template from "./application.pug"

import autobind from "autobind-decorator"
import BaseComponent from "components/base-component"
import AttributeList from "components/attribute-list"
import ImageUploader from "components/image-uploader"
import {highlight} from "highlight.js"
import createElement from "lib/create-element"
import parseURL from "lib/parse-url"
import createEagerSchema from "lib/create-eager-schema"
import KM from "lib/key-map"
import {postJson} from "simple-fetch"
import autosize from "autosize"
import isURL from "is-url"
import formSerialize from "form-serialize"
import * as demos from "./demos"

const DEFAULT_PLUGIN_ICON = `${ASSET_BASE}/default-plugin-logo.png`
const ACTIVE_STEP = "data-active-step"
const ENTITY_ID = "data-entity-id"
const ENTITY_ORDER = "data-entity-order"
const ENTITY_QUERY = ".hljs-string, .hljs-number"
const previewURL = [
  EAGER_BASE,
  "/developer/app-tester?remoteInstall&embed&cmsName=appTester&initialUrl=example.com"
].join("")

const getType = element => {
  const [, type] = element.className.match(/hljs-([\S]*)/)

  return type
}
const getDelimiter = (type, text) => type === "string" ? text[0] : ""
const normalize = (type, text) => getDelimiter(type, text) ? text.substring(1, text.length - 1) : text

export default class Application extends BaseComponent {
  static template = template;

  constructor(mountPoint, options) {
    super(options)

    Object.assign(this, {
      awaitingPreview: false,
      entities: {},
      installJSON: null,
      steps: {},
      previewURL,
      previewReady: false
    })

    window.addEventListener("message", ({data}) => {
      if (data.type !== "eager:app-tester:upload-listener-ready") return

      this.previewReady = true

      if (this.awaitingPreview) {
        this.awaitingPreview = false
        this.sendPreviewPayload()
      }
    })

    const element = this.compileTemplate()
    const {
      attributeListMount,
      embedCodeInput,
      pluginDetailsForm,
      navigationButtons,
      imageUploadMount,
      steps
    } = this.refs

    autosize(this.element.querySelectorAll("textarea"))

    embedCodeInput.addEventListener("input", this.handleEntry)
    pluginDetailsForm.addEventListener("submit", event => {
      event.preventDefault()

      this.navigateToCreating()
    })

    steps.forEach(stepEl => this.steps[stepEl.getAttribute("data-step")] = stepEl)

    const stepHandlers = {
      demo: this.navigateToDemo,
      intro: this.navigateToIntro,
      embedCode: this.navigateToEmbedCode,
      attributes: this.navigateToAttributes,
      preview: this.navigateToPreview,
      details: this.navigateToDetails,
      creating: this.navigateToCreating,
      download: this.navigateToDownload,
      reset: () => window.location.reload() // TODO: Reset more elegantly.
    }

    navigationButtons.forEach(buttonEl => {
      const step = buttonEl.getAttribute("data-step")

      buttonEl.addEventListener("click", stepHandlers[step])
    })

    this.attributeList = new AttributeList({
      getEntities: () => this.entities,
      getTrackedEntityIDs: this.getTrackedEntityIDs,
      setEntityTitle: this.setEntityTitle
    })

    this.replaceElement(attributeListMount, this.attributeList.render())

    this.imageUploader = new ImageUploader({name: "app[icon]"})
    this.replaceElement(imageUploadMount, this.imageUploader.render())
    this.imageUploader.imageURL = DEFAULT_PLUGIN_ICON

    mountPoint.appendChild(element)

    this.navigateToIntro()
  }

  @autobind
  getTrackedEntityIDs() {
    const $ = this.entities

    return Object.keys($)
      .filter(key => $[key].tracked)
      .sort((keyA, keyB) => $[keyA].order - $[keyB].order)
  }

  get activeStep() {
    return this.element.getAttribute(ACTIVE_STEP)
  }

  set activeStep(value) {
    const {steps, stepsContainer} = this.refs
    const containerStyle = stepsContainer.style

    this.element.setAttribute(ACTIVE_STEP, value)

    if (this.activeStep !== "intro") {
      containerStyle.height = `${stepsContainer.clientHeight}px`
    }

    requestAnimationFrame(() => {
      steps.forEach(stepEl => {
        const active = stepEl.getAttribute("data-step") === value
        const method = active ? "add" : "remove"

        stepEl.classList[method]("active")

        if (active) {
          requestAnimationFrame(() => {
            containerStyle.height = `${stepEl.clientHeight + 16}px`

            // HACK: Chrome seems to be selective in calling transitionend if an
            // element is hidden or already in another transition.
            // The timeout is set slightly past the height transition to reset the property.
            setTimeout(() => containerStyle.height = "auto", 700)
          })

          this.autofocus(stepEl)
        }
      })

      this.syncButtonState()
      window.scrollTo(0, 0)
    })

    return value
  }

  @autobind
  createPreviewIframe() {
    const {previewContainer} = this.refs
    const previewIframe = createElement("iframe", {src: this.previewURL})

    this.refs.previewIframe = previewIframe

    previewContainer.innerHTML = ""

    previewContainer.appendChild(previewIframe)
  }

  @autobind
  navigateToIntro() {
    this.activeStep = "intro"
  }

  @autobind
  navigateToDemo({target}) {
    demos[target.getAttribute("data-demo")](this)
  }

  @autobind
  navigateToEmbedCode() {
    this.activeStep = "embedCode"

    // NOTE: Loading an iframe can freeze browser scrolling momentarily.
    // This has to occur at some point. This could use some tweaking.
    setTimeout(this.createPreviewIframe, 1500)
  }

  @autobind
  navigateToAttributes() {
    this.activeStep = "attributes"
    this.attributeList.render()
    this.syncButtonState()
  }

  @autobind
  navigateToPreview() {
    const IDs = this.getTrackedEntityIDs()
    const embedCodeDOM = this.refs.attributePicker.cloneNode(true)
    const properties = {}

    IDs.forEach((id, order) => {
      const {delimiter, normalized, title, type} = this.entities[id]
      const current = embedCodeDOM.querySelector(`[${ENTITY_ID}="${id}"]`)

      properties[id] = {
        order,
        placeholder: normalized,
        title: title || `Option ${order + 1}`,
        type
      }

      current.textContent = `${delimiter}TRACKED_ENTITY[${id}]${delimiter}`
    })

    this.installJSON = createEagerSchema({
      embedCode: embedCodeDOM.textContent,
      properties
    })

    if (this.previewReady) this.sendPreviewPayload()
    else this.awaitingPreview = true

    this.activeStep = "preview"
  }

  @autobind
  navigateToDetails() {
    this.activeStep = "details"
  }

  @autobind
  navigateToCreating() {
    this.activeStep = "creating"

    const onComplete = ({downloadURL}) => {
      this.downloadURL = downloadURL
      this.navigateToDownload()
    }

    const {pluginDetailsForm} = this.refs
    const pluginDetails = formSerialize(pluginDetailsForm, {hash: true})

    pluginDetails.app.icon = pluginDetails.app.icon || DEFAULT_PLUGIN_ICON

    const payload = {
      cmsName: "wordpress",
      installJSON: this.installJSON,
      ...pluginDetails
    }

    console.log(payload)

    postJson(`${API_BASE}/create/instant`, payload)
      .then(onComplete)
      .catch(error => console.error(error))
  }

  @autobind
  navigateToDownload() {
    this.activeStep = "download"

    const {downloadLink} = this.refs

    downloadLink.href = this.downloadURL

    const downloadIframe = createElement("iframe", {
      className: "download-iframe",
      src: this.downloadURL
    })

    document.body.appendChild(downloadIframe)
  }

  parseInput() {
    this.entities = {}

    const {attributePicker, embedCodeInput} = this.refs
    const serializer = createElement("div", {
      innerHTML: highlight("html", embedCodeInput.value).value
    })

    if (!serializer.querySelector(ENTITY_QUERY)) {
      attributePicker.classList.add("empty")
      attributePicker.innerHTML = `

          We couldn’t find any configurable strings or numbers in that embed code.

          Press “Back” to edit the embed code or “Preview Plugin” to continue.

      `

      this.syncButtonState()
      return
    }

    attributePicker.classList.remove("empty")
    attributePicker.innerHTML = serializer.innerHTML

    const getEntityElements = () => Array.from(attributePicker.querySelectorAll(ENTITY_QUERY))

    // Parse URL components into entities.
    getEntityElements()
      .map(element => {
        const type = getType(element)

        return {element, type, normalized: normalize(type, element.textContent)}
      })
      .filter(({type, normalized}) => type === "string" && isURL(encodeURI(normalized)))
      .forEach(({element}) => {
        const {paramDelimiter, params, urlBase} = parseURL(element.textContent)

        if (params.length === 0) return

        const groupFragment = document.createDocumentFragment()

        groupFragment.appendChild(createElement("span", {
          textContent: urlBase + paramDelimiter
        }))

        params.forEach(({key, value}) => {
          groupFragment.appendChild(createElement("span", {
            className: "url-param-key",
            textContent: `${key}=`
          }))

          groupFragment.appendChild(createElement("span", {
            className: "hljs-string url-param-value",
            textContent: value
          }))
        })

        this.replaceElement(element, groupFragment)

        element.classList.remove("hljs-string")
      })

    const entityElements = getEntityElements()
    const entityCount = entityElements.length

    // Offset the existing tabindexes given our entity count.
    Array
      .from(this.steps.attributes.querySelectorAll("[tabindex]"))
      .forEach((tabableEl, index) => tabableEl.tabIndex = entityCount + index + 1)

    entityElements.forEach((element, index) => {
      const text = element.textContent
      const id = `option_${index + 1}`
      const type = getType(element)
      const normalized = normalize(type, text)

      this.entities[id] = {
        delimiter: getDelimiter(type, text),
        element,
        normalized,
        order: index,
        original: text,
        tracked: false,
        type
      }

      if (index === 0) element.setAttribute("data-autofocus", "")

      element.tabIndex = index + 1
      element.setAttribute(ENTITY_ID, id)
      element.setAttribute(ENTITY_ORDER, index)
      element.addEventListener("click", this.toggleEntityTracking.bind(this, element))
    })

    attributePicker.addEventListener("keydown", this.handleAttributeKeyDown)

    this.attributeList.render()
    this.syncButtonState()
  }

  toggleEntityTracking(element) {
    const entity = this.entities[element.getAttribute(ENTITY_ID)]

    if (entity.tracked) {
      entity.tracked = false
      element.classList.remove("tracked")
    }
    else {
      entity.tracked = true
      element.classList.add("tracked")
    }

    this.attributeList.render()
    this.syncButtonState()
  }

  @autobind
  setEntityTitle(id, {target: {value}}) {
    this.entities[id].title = value
  }

  syncButtonState() {
    const {embedCodeInput, stepsContainer} = this.refs
    const embedCodeStep = stepsContainer.querySelector(".step[data-step='embedCode']")
    const attributesStep = stepsContainer.querySelector(".step[data-step='attributes']")
    const navigateToAttributesButton = embedCodeStep.querySelector("button[data-step='attributes']")
    const navigateToPreviewButton = attributesStep.querySelector("button[data-step='preview']")
    const IDs = this.getTrackedEntityIDs()

    navigateToAttributesButton.disabled = embedCodeInput.value.length === 0
    navigateToPreviewButton.disabled = IDs.length === 0 && Object.keys(this.entities).length !== 0
  }

  @autobind
  handleAttributeKeyDown(event) {
    const entityEl = document.activeElement

    if (!entityEl || ![KM.enter, KM.spacebar].includes(event.keyCode)) return

    const {attributes} = this.steps
    const id = entityEl.getAttribute(ENTITY_ID)

    if (!attributes.contains(entityEl) || !id) return

    event.preventDefault()

    this.toggleEntityTracking(entityEl)
  }

  @autobind
  sendPreviewPayload() {
    const {previewIframe} = this.refs

    previewIframe.contentWindow.postMessage({
      installJSON: this.installJSON,
      type: "eager:app-tester:upload-app"
    }, "*")
  }

  @autobind
  handleEntry() {
    this.parseInput()
  }
}
<commit_msg>Add missing delimiters.<commit_after>import "./application.styl"
import template from "./application.pug"

import autobind from "autobind-decorator"
import BaseComponent from "components/base-component"
import AttributeList from "components/attribute-list"
import ImageUploader from "components/image-uploader"
import {highlight} from "highlight.js"
import createElement from "lib/create-element"
import parseURL from "lib/parse-url"
import createEagerSchema from "lib/create-eager-schema"
import KM from "lib/key-map"
import {postJson} from "simple-fetch"
import autosize from "autosize"
import isURL from "is-url"
import formSerialize from "form-serialize"
import * as demos from "./demos"

const DEFAULT_PLUGIN_ICON = `${ASSET_BASE}/default-plugin-logo.png`
const ACTIVE_STEP = "data-active-step"
const ENTITY_ID = "data-entity-id"
const ENTITY_ORDER = "data-entity-order"
const ENTITY_QUERY = ".hljs-string, .hljs-number"
const previewURL = [
  EAGER_BASE,
  "/developer/app-tester?remoteInstall&embed&cmsName=appTester&initialUrl=example.com"
].join("")

const getType = element => {
  const [, type] = element.className.match(/hljs-([\S]*)/)

  return type
}
const getDelimiter = (type, text) => type === "string" ? text[0] : ""
const normalize = (type, text) => getDelimiter(type, text) ? text.substring(1, text.length - 1) : text

export default class Application extends BaseComponent {
  static template = template;

  constructor(mountPoint, options) {
    super(options)

    Object.assign(this, {
      awaitingPreview: false,
      entities: {},
      installJSON: null,
      steps: {},
      previewURL,
      previewReady: false
    })

    window.addEventListener("message", ({data}) => {
      if (data.type !== "eager:app-tester:upload-listener-ready") return

      this.previewReady = true

      if (this.awaitingPreview) {
        this.awaitingPreview = false
        this.sendPreviewPayload()
      }
    })

    const element = this.compileTemplate()
    const {
      attributeListMount,
      embedCodeInput,
      pluginDetailsForm,
      navigationButtons,
      imageUploadMount,
      steps
    } = this.refs

    autosize(this.element.querySelectorAll("textarea"))

    embedCodeInput.addEventListener("input", this.handleEntry)
    pluginDetailsForm.addEventListener("submit", event => {
      event.preventDefault()

      this.navigateToCreating()
    })

    steps.forEach(stepEl => this.steps[stepEl.getAttribute("data-step")] = stepEl)

    const stepHandlers = {
      demo: this.navigateToDemo,
      intro: this.navigateToIntro,
      embedCode: this.navigateToEmbedCode,
      attributes: this.navigateToAttributes,
      preview: this.navigateToPreview,
      details: this.navigateToDetails,
      creating: this.navigateToCreating,
      download: this.navigateToDownload,
      reset: () => window.location.reload() // TODO: Reset more elegantly.
    }

    navigationButtons.forEach(buttonEl => {
      const step = buttonEl.getAttribute("data-step")

      buttonEl.addEventListener("click", stepHandlers[step])
    })

    this.attributeList = new AttributeList({
      getEntities: () => this.entities,
      getTrackedEntityIDs: this.getTrackedEntityIDs,
      setEntityTitle: this.setEntityTitle
    })

    this.replaceElement(attributeListMount, this.attributeList.render())

    this.imageUploader = new ImageUploader({name: "app[icon]"})
    this.replaceElement(imageUploadMount, this.imageUploader.render())
    this.imageUploader.imageURL = DEFAULT_PLUGIN_ICON

    mountPoint.appendChild(element)

    this.navigateToIntro()
  }

  @autobind
  getTrackedEntityIDs() {
    const $ = this.entities

    return Object.keys($)
      .filter(key => $[key].tracked)
      .sort((keyA, keyB) => $[keyA].order - $[keyB].order)
  }

  get activeStep() {
    return this.element.getAttribute(ACTIVE_STEP)
  }

  set activeStep(value) {
    const {steps, stepsContainer} = this.refs
    const containerStyle = stepsContainer.style

    this.element.setAttribute(ACTIVE_STEP, value)

    if (this.activeStep !== "intro") {
      containerStyle.height = `${stepsContainer.clientHeight}px`
    }

    requestAnimationFrame(() => {
      steps.forEach(stepEl => {
        const active = stepEl.getAttribute("data-step") === value
        const method = active ? "add" : "remove"

        stepEl.classList[method]("active")

        if (active) {
          requestAnimationFrame(() => {
            containerStyle.height = `${stepEl.clientHeight + 16}px`

            // HACK: Chrome seems to be selective in calling transitionend if an
            // element is hidden or already in another transition.
            // The timeout is set slightly past the height transition to reset the property.
            setTimeout(() => containerStyle.height = "auto", 700)
          })

          this.autofocus(stepEl)
        }
      })

      this.syncButtonState()
      window.scrollTo(0, 0)
    })

    return value
  }

  @autobind
  createPreviewIframe() {
    const {previewContainer} = this.refs
    const previewIframe = createElement("iframe", {src: this.previewURL})

    this.refs.previewIframe = previewIframe

    previewContainer.innerHTML = ""

    previewContainer.appendChild(previewIframe)
  }

  @autobind
  navigateToIntro() {
    this.activeStep = "intro"
  }

  @autobind
  navigateToDemo({target}) {
    demos[target.getAttribute("data-demo")](this)
  }

  @autobind
  navigateToEmbedCode() {
    this.activeStep = "embedCode"

    // NOTE: Loading an iframe can freeze browser scrolling momentarily.
    // This has to occur at some point. This could use some tweaking.
    setTimeout(this.createPreviewIframe, 1500)
  }

  @autobind
  navigateToAttributes() {
    this.activeStep = "attributes"
    this.attributeList.render()
    this.syncButtonState()
  }

  @autobind
  navigateToPreview() {
    const IDs = this.getTrackedEntityIDs()
    const embedCodeDOM = this.refs.attributePicker.cloneNode(true)
    const properties = {}

    IDs.forEach((id, order) => {
      const {delimiter, normalized, title, type} = this.entities[id]
      const current = embedCodeDOM.querySelector(`[${ENTITY_ID}="${id}"]`)

      properties[id] = {
        order,
        placeholder: normalized,
        title: title || `Option ${order + 1}`,
        type
      }

      current.textContent = `${delimiter}TRACKED_ENTITY[${id}]${delimiter}`
    })

    this.installJSON = createEagerSchema({
      embedCode: embedCodeDOM.textContent,
      properties
    })

    if (this.previewReady) this.sendPreviewPayload()
    else this.awaitingPreview = true

    this.activeStep = "preview"
  }

  @autobind
  navigateToDetails() {
    this.activeStep = "details"
  }

  @autobind
  navigateToCreating() {
    this.activeStep = "creating"

    const onComplete = ({downloadURL}) => {
      this.downloadURL = downloadURL
      this.navigateToDownload()
    }

    const {pluginDetailsForm} = this.refs
    const pluginDetails = formSerialize(pluginDetailsForm, {hash: true})

    pluginDetails.app.icon = pluginDetails.app.icon || DEFAULT_PLUGIN_ICON

    const payload = {
      cmsName: "wordpress",
      installJSON: this.installJSON,
      ...pluginDetails
    }

    console.log(payload)

    postJson(`${API_BASE}/create/instant`, payload)
      .then(onComplete)
      .catch(error => console.error(error))
  }

  @autobind
  navigateToDownload() {
    this.activeStep = "download"

    const {downloadLink} = this.refs

    downloadLink.href = this.downloadURL

    const downloadIframe = createElement("iframe", {
      className: "download-iframe",
      src: this.downloadURL
    })

    document.body.appendChild(downloadIframe)
  }

  parseInput() {
    this.entities = {}

    const {attributePicker, embedCodeInput} = this.refs
    const serializer = createElement("div", {
      innerHTML: highlight("html", embedCodeInput.value).value
    })

    if (!serializer.querySelector(ENTITY_QUERY)) {
      attributePicker.classList.add("empty")
      attributePicker.innerHTML = `

          We couldn’t find any configurable strings or numbers in that embed code.

          Press “Back” to edit the embed code or “Preview Plugin” to continue.

      `

      this.syncButtonState()
      return
    }

    attributePicker.classList.remove("empty")
    attributePicker.innerHTML = serializer.innerHTML

    const getEntityElements = () => Array.from(attributePicker.querySelectorAll(ENTITY_QUERY))

    // Parse URL components into entities.
    getEntityElements()
      .map(element => {
        const type = getType(element)

        return {
          element,
          type,
          delimiter: getDelimiter(type, element.textContent),
          normalized: normalize(type, element.textContent)
        }
      })
      .filter(({type, normalized}) => type === "string" && isURL(encodeURI(normalized)))
      .forEach(({element, delimiter, normalized}) => {
        const {paramDelimiter, params, urlBase} = parseURL(normalized)

        if (params.length === 0) return

        const groupFragment = document.createDocumentFragment()

        groupFragment.appendChild(document.createTextNode(delimiter))
        groupFragment.appendChild(createElement("span", {
          textContent: urlBase + paramDelimiter
        }))

        params.forEach(({key, value}) => {
          groupFragment.appendChild(createElement("span", {
            className: "url-param-key",
            textContent: `${key}=`
          }))

          groupFragment.appendChild(createElement("span", {
            className: "hljs-string url-param-value",
            textContent: value
          }))
        })
        groupFragment.appendChild(document.createTextNode(delimiter))

        this.replaceElement(element, groupFragment)

        element.classList.remove("hljs-string")
      })

    const entityElements = getEntityElements()
    const entityCount = entityElements.length

    // Offset the existing tabindexes given our entity count.
    Array
      .from(this.steps.attributes.querySelectorAll("[tabindex]"))
      .forEach((tabableEl, index) => tabableEl.tabIndex = entityCount + index + 1)

    entityElements.forEach((element, index) => {
      const text = element.textContent
      const id = `option_${index + 1}`
      const type = getType(element)
      const normalized = normalize(type, text)

      this.entities[id] = {
        delimiter: getDelimiter(type, text),
        element,
        normalized,
        order: index,
        original: text,
        tracked: false,
        type
      }

      if (index === 0) element.setAttribute("data-autofocus", "")

      element.tabIndex = index + 1
      element.setAttribute(ENTITY_ID, id)
      element.setAttribute(ENTITY_ORDER, index)
      element.addEventListener("click", this.toggleEntityTracking.bind(this, element))
    })

    attributePicker.addEventListener("keydown", this.handleAttributeKeyDown)

    this.attributeList.render()
    this.syncButtonState()
  }

  toggleEntityTracking(element) {
    const entity = this.entities[element.getAttribute(ENTITY_ID)]

    if (entity.tracked) {
      entity.tracked = false
      element.classList.remove("tracked")
    }
    else {
      entity.tracked = true
      element.classList.add("tracked")
    }

    this.attributeList.render()
    this.syncButtonState()
  }

  @autobind
  setEntityTitle(id, {target: {value}}) {
    this.entities[id].title = value
  }

  syncButtonState() {
    const {embedCodeInput, stepsContainer} = this.refs
    const embedCodeStep = stepsContainer.querySelector(".step[data-step='embedCode']")
    const attributesStep = stepsContainer.querySelector(".step[data-step='attributes']")
    const navigateToAttributesButton = embedCodeStep.querySelector("button[data-step='attributes']")
    const navigateToPreviewButton = attributesStep.querySelector("button[data-step='preview']")
    const IDs = this.getTrackedEntityIDs()

    navigateToAttributesButton.disabled = embedCodeInput.value.length === 0
    navigateToPreviewButton.disabled = IDs.length === 0 && Object.keys(this.entities).length !== 0
  }

  @autobind
  handleAttributeKeyDown(event) {
    const entityEl = document.activeElement

    if (!entityEl || ![KM.enter, KM.spacebar].includes(event.keyCode)) return

    const {attributes} = this.steps
    const id = entityEl.getAttribute(ENTITY_ID)

    if (!attributes.contains(entityEl) || !id) return

    event.preventDefault()

    this.toggleEntityTracking(entityEl)
  }

  @autobind
  sendPreviewPayload() {
    const {previewIframe} = this.refs

    previewIframe.contentWindow.postMessage({
      installJSON: this.installJSON,
      type: "eager:app-tester:upload-app"
    }, "*")
  }

  @autobind
  handleEntry() {
    this.parseInput()
  }
}
<|endoftext|>


<commit_before>module.exports = function(req, res, next) {
	if (req.session.user) {
		return next();
	}
<<<<<<< HEAD
	req.session.flash = 'Please login to continue.';
=======
>>>>>>> dev
	return res.redirect('/login');
};
<commit_msg>resolve conflict<commit_after>module.exports = function(req, res, next) {
	if (req.session.user) {
		return next();
	}
	req.session.flash = 'Please login to continue.';
	return res.redirect('/login');
};
<|endoftext|>


<commit_before>import React from 'react';
export default class BarChart extends React.Component {
    render() {
        return
Bar Chart
;
    }
}
<commit_msg>linting<commit_after>import React from 'react';
export default class BarChart extends React.Component {
  render() {
    return
Bar Chart
;
  }
}
<|endoftext|>


<commit_before>(function () {
  'use strict';

  angular
    .module('openSenseMapApp')
    .directive('osemMap', osemMap);

  osemMap.$inject = ['$q', '$rootScope', 'osemMapData'];

  /* @ngInject */
  function osemMap ($q, $rootScope, osemMapData) {
    var directive = {
      templateUrl: 'components/leaflet.directive.html',
      link: link,
      restrict: 'EA',
      transclude: true,
      controller: function () {
        this._leafletMap = $q.defer();
        this.getMap = function () {
          return this._leafletMap.promise;
        };
      },
      scope: {
        markers: '=',
        center: '=',
        events: '='
      }
    };
    return directive;

    function link(scope, element, attrs, ctrl) {
      //TODO: destroy
      scope.mapId =  attrs.id;

      var map = new L.map(element[0], {
        minZoom: 2,
        maxZoom: 18,
        worldCopyJump: true,
        scrollWheelZoom: true
      }).setView([51.04139389812637, 10.2172851562], 6);
      ctrl._leafletMap.resolve(map);

      map.on('click', onMapClick);

      var markers = L.layerGroup();
      var oldMarkers = L.layerGroup();

      oldMarkers.on('add', function () {
        osemMapData.setLayer('oldMarkers', oldMarkers);
      });

      markers.on('add', function () {
        osemMapData.setLayer('markers', markers);
      });

      L.tileLayer('@@OPENSENSEMAP_MAPTILES_URL', {
        subdomains: 'abc',
        attribution: '© OpenStreetMap contributors | Tiles © Mapbox',
        detectRetina: true,
        reuseTiles: true,
        maxZoom: 18
      }).addTo(map);

      L.control.scale().addTo(map);

      map.addLayer(markers);
      map.addLayer(oldMarkers);

      if (angular.isDefined(scope.events) && !angular.equals({}, scope.events)) {
        if (scope.events.autolocation) {
          map.locate({setView: true, maxZoom: 16});
          map.on('locationfound', onLocationFound);
          map.on('locationerror', onLocationError);
        }
      }

      // Resolve the map object to the promises
      map.whenReady(function() {
        osemMapData.setMap(attrs.id, map);
        scope.$watch('markers', function (newVal, oldVal) {
          if (angular.isDefined(newVal) && !angular.equals({}, newVal)) {
            markers.clearLayers();
            oldMarkers.clearLayers();
            for (var marker in newVal) {
              var box = newVal[marker];
              var marker = L.marker([box.lat,box.lng], {
                icon: L.AwesomeMarkers.icon(box.icon),
                options: box,
                draggable: box.draggable,
                opacity: box.icon.opacity,
                zIndexOffset: box.icon.zIndexOffset});
              if (box.layer === 'oldMarker') {
                oldMarkers.addLayer(marker);
              } else {
                markers.addLayer(marker);
              }
              marker.on('click', onMarkerClick);
              marker.on('mouseover', onMouseOver);
              marker.on('mouseout', onMouseOut);

              if (box.draggable) {
                marker.on('dragend', onMarkerDragend);
              }
            }
          }
        });

        $rootScope.$broadcast('osemMapReady', {});
      });

      function onLocationFound (e) {
        var eventName = 'osemMapOnLocationFound.' + scope.mapId;
        $rootScope.$broadcast(eventName, e);
        $rootScope.$apply();
      }

      function onLocationError (e) {
        var eventName = 'osemMapOnLocationError.' + scope.mapId;
        $rootScope.$broadcast(eventName, e);
        $rootScope.$apply();
      }

      function onMapClick (e) {
        var eventName = 'osemMapClick.' + scope.mapId;
        $rootScope.$broadcast(eventName, e);
        $rootScope.$apply();
      }

      function onMarkerDragend (e) {
        var eventName = 'osemMarkerDragend.' + scope.mapId;
        $rootScope.$broadcast(eventName, e);
        $rootScope.$apply();
      }

      function onMouseOver (e) {
        var eventName = 'osemMarkerMouseOver.' + scope.mapId;
        $rootScope.$broadcast(eventName, e);
        $rootScope.$apply();
      }

      function onMouseOut (e) {
        var eventName = 'osemMarkerMouseOut.' + scope.mapId;
        $rootScope.$broadcast(eventName, e);
        $rootScope.$apply();
      }

      function onMarkerClick (e) {
        var eventName = 'osemMarkerClick.' + scope.mapId;
        $rootScope.$broadcast(eventName, e);
      }
    }
  }
})();
<commit_msg>refactor map layer access<commit_after>(function () {
  'use strict';

  angular
    .module('openSenseMapApp')
    .directive('osemMap', osemMap);

  osemMap.$inject = ['$q', '$rootScope', 'osemMapData'];

  /* @ngInject */
  function osemMap ($q, $rootScope, osemMapData) {
    var directive = {
      templateUrl: 'components/leaflet.directive.html',
      link: link,
      restrict: 'EA',
      transclude: true,
      controller: function () {
        this._leafletMap = $q.defer();
        this.getMap = function () {
          return this._leafletMap.promise;
        };
      },
      scope: {
        markers: '=',
        center: '=',
        events: '='
      }
    };
    return directive;

    function link(scope, element, attrs, ctrl) {
      //TODO: destroy
      scope.mapId =  attrs.id;

      var map = new L.map(element[0], {
        minZoom: 2,
        maxZoom: 18,
        worldCopyJump: true,
        scrollWheelZoom: true
      }).setView([51.04139389812637, 10.2172851562], 6);
      ctrl._leafletMap.resolve(map);

      map.on('click', onMapClick);

      var mapLayers = {
        'markers': L.layerGroup(),
        'oldMarkers': L.layerGroup(),
      };

      for (var layerName in mapLayers) {
        osemMapData.setLayer(layerName, mapLayers[layerName]);
        map.addLayer(mapLayers[layerName]);
      }

      L.tileLayer('@@OPENSENSEMAP_MAPTILES_URL', {
        subdomains: 'abc',
        attribution: '© OpenStreetMap contributors | Tiles © Mapbox',
        detectRetina: true,
        reuseTiles: true,
        maxZoom: 18
      }).addTo(map);

      L.control.scale().addTo(map);

      if (angular.isDefined(scope.events) && !angular.equals({}, scope.events)) {
        if (scope.events.autolocation) {
          map.locate({setView: true, maxZoom: 16});
          map.on('locationfound', onLocationFound);
          map.on('locationerror', onLocationError);
        }
      }

      // Resolve the map object to the promises
      map.whenReady(function() {
        osemMapData.setMap(attrs.id, map);
        scope.$watch('markers', onMarkersWatch);
        $rootScope.$broadcast('osemMapReady', {});
      });

      function onMarkersWatch (newVal, oldVal) {
        if (angular.isDefined(newVal) && !angular.equals({}, newVal)) {
          mapLayers['markers'].clearLayers();
          mapLayers['oldMarkers'].clearLayers();
          for (var marker in newVal) {
            var box = newVal[marker];
            var marker = L.marker([box.lat,box.lng], {
              icon: L.AwesomeMarkers.icon(box.icon),
              options: box,
              draggable: box.draggable,
              opacity: box.icon.opacity,
              zIndexOffset: box.icon.zIndexOffset});
            if (box.layer === 'oldMarker') {
              mapLayers['oldMarkers'].addLayer(marker);
            } else {
              mapLayers['markers'].addLayer(marker);
            }
            marker.on('click', onMarkerClick);
            marker.on('mouseover', onMouseOver);
            marker.on('mouseout', onMouseOut);

            if (box.draggable) {
              marker.on('dragend', onMarkerDragend);
            }
          }
        }
      }

      function onLocationFound (e) {
        var eventName = 'osemMapOnLocationFound.' + scope.mapId;
        $rootScope.$broadcast(eventName, e);
        $rootScope.$apply();
      }

      function onLocationError (e) {
        var eventName = 'osemMapOnLocationError.' + scope.mapId;
        $rootScope.$broadcast(eventName, e);
        $rootScope.$apply();
      }

      function onMapClick (e) {
        var eventName = 'osemMapClick.' + scope.mapId;
        $rootScope.$broadcast(eventName, e);
        $rootScope.$apply();
      }

      function onMarkerDragend (e) {
        var eventName = 'osemMarkerDragend.' + scope.mapId;
        $rootScope.$broadcast(eventName, e);
        $rootScope.$apply();
      }

      function onMouseOver (e) {
        var eventName = 'osemMarkerMouseOver.' + scope.mapId;
        $rootScope.$broadcast(eventName, e);
        $rootScope.$apply();
      }

      function onMouseOut (e) {
        var eventName = 'osemMarkerMouseOut.' + scope.mapId;
        $rootScope.$broadcast(eventName, e);
        $rootScope.$apply();
      }

      function onMarkerClick (e) {
        var eventName = 'osemMarkerClick.' + scope.mapId;
        $rootScope.$broadcast(eventName, e);
      }
    }
  }
})();
<|endoftext|>


<commit_before>angular.module('super-micro-paint', ['touch-directives'])
    .controller('smpController', ['$scope', '$window', function ($scope, $window) {
        $scope.numFrames = 4;
        $scope.height = 16;
        $scope.width = 32;
        $scope._ = _;
        $scope.activeTool = 'pencil';
        $scope.undoBuffers = _.range(0, $scope.numFrames)
            .map(function () {
                return [];
            });
        $scope.redoBuffers = _.range(0, $scope.numFrames)
            .map(function () {
                return [];
            });
        $scope.frames = _.range(0, $scope.numFrames)
            .map(function () {
                return new SuperPixelGrid($scope.width, $scope.height)
                    .fill(false);
            });
        $scope.currentFrameNum = 0;
        $scope.currentFrame = new SuperPixelGrid($scope.width, $scope.height)
            .fill(false);
        $scope.overlay = new SuperPixelGrid($scope.width, $scope.height)
            .fill(false);
        $scope.pen = {};
        $scope.pen.drawing = false;
        $scope.pen.mode = false;
        $scope.pen.start = undefined;
        $scope.pen.last = undefined;
        $scope.pen.finish = false;
        $scope.events = {};
        $scope.events.start = undefined;
        $scope.events.current = undefined;
        $scope.events.finish = undefined;
        $scope.mode = 'normal';
        $scope.range = function (n) {
            return new Array(n);
        };
        var switchToFrame = function (n) {
            copyFrame($scope.currentFrame, $scope.frames[$scope.currentFrameNum]);
            $scope.currentFrameNum = n;
            copyFrame($scope.frames[n], $scope.currentFrame);
        };
        var clearSelection = function () {
            var selection = ('getSelection' in window) ? window.getSelection() : ('selection' in document) ? document.selection : null;
            if ('removeAllRanges' in selection) {
                selection.removeAllRanges();
            } else if ('empty' in selection) {
                selection.empty();
            }
        };
        var setPixel = function (pixel, mode) {
            $scope.currentFrame.set(pixel.x, pixel.y, mode);
        };
        var setUndo = function (frame) {
            var f, frameToSave;
            if (frame === undefined) {
                f = $scope.currentFrameNum;
                frameToSave = $scope.currentFrame;
            } else {
                f = frame;
                frameToSave = $scope.frames[f];
            }
            $scope.undoBuffers[f].push(frameToSave.toString());
        };
        var setRedo = function () {
            var f = $scope.currentFrameNum;
            $scope.redoBuffers[f].push($scope.currentFrame.toString());
        };
        $scope.undo = function () {
            var f = $scope.currentFrameNum;
            if ($scope.undoBuffers[f].length > 0) {
                setRedo();
                $scope.currentFrame = $scope.currentFrame.fromString($scope.undoBuffers[f].pop());
                $scope.save();
            }
        };
        $scope.redo = function () {
            var f = $scope.currentFrameNum;
            if ($scope.redoBuffers[f].length > 0) {
                setUndo();
                $scope.currentFrame = $scope.currentFrame.fromString($scope.redoBuffers[f].pop());
                $scope.save();
            }
        };
        $scope.canUndo = function () {
            return $scope.undoBuffers[$scope.currentFrameNum].length > 0;
        };
        $scope.canRedo = function () {
            return $scope.redoBuffers[$scope.currentFrameNum].length > 0;
        };
        var getPixel = function (pixel) {
            return $scope.currentFrame.get(pixel.x, pixel.y);
        };
        $scope.getExportURL = function() {
            return "export/?smp=" + location.hash.slice(1);
        };
        var tools = {};
        var buildPointToPointDrawingTool = function (drawingFunction) {
            return {
                'start': function (pen) {
                    $scope.overlay.fill(false);
                },
                'finish': function (pen) {
                    setUndo();
                    $scope.currentFrame[drawingFunction](pen.start.x, pen.start.y, pen.finish.x, pen.finish.y, pen.mode);
                    $scope.overlay.fill(false);
                    $scope.save();
                    clearSelection();
                },
                'update': function (pen) {
                    $scope.overlay.fill(false)[drawingFunction](pen.start.x, pen.start.y, pen.current.x, pen.current.y, true);
                }
            };
        };
        tools.pencil = {
            'start': function (pen) {
                setUndo();
                setPixel(pen.start, pen.mode);
            },
            'finish': function (pen) {
                $scope.save();
                clearSelection();
            },
            'update': function (pen) {
                $scope.currentFrame.drawLine(pen.last.x, pen.last.y, pen.current.x, pen.current.y, pen.mode);
            }
        };
        tools.fill = {
            'start': function (pen) {
                setUndo();
                $scope.currentFrame.floodFill(pen.start.x, pen.start.y, pen.mode);
                $scope.save();
            }
        };
        tools.line = buildPointToPointDrawingTool('drawLine');
        tools.rectangle = buildPointToPointDrawingTool('drawRectangle');
        tools.ellipse = buildPointToPointDrawingTool('drawEllipse');
        var drawUpdate = function() {
            var pen = $scope.pen;
            var events = $scope.events;
            if (events.start && !events.finish) {
                pen.drawing = true;
                pen.start = pen.last = pointFromEvent(events.start);
                pen.mode = !getPixel(pen.start);
                events.start = undefined;
                if (tools[$scope.activeTool].start) {
                    tools[$scope.activeTool].start(pen);
                }
            } else if (pen.drawing === true && events.current && !events.finish) {
                pen.current = pointFromEvent(events.current);
                if (tools[$scope.activeTool].update && !(pen.current.x === pen.last.x && pen.current.y === pen.last.y)) {
                    tools[$scope.activeTool].update(pen);
                }
                pen.last = pen.current;
                events.current = undefined;
            } else if (pen.drawing === true && events.finish) {
                pen.drawing = false;
                if (events.finish === 'outside') {
                    pen.finish = pen.last;
                } else {
                    pen.finish = pointFromEvent(events.finish);
                }
                if (tools[$scope.activeTool].finish) {
                    tools[$scope.activeTool].finish(pen);
                }
                events.finish = events.current = events.start = undefined;
            }
            window.requestAnimationFrame(drawUpdate);
        };
        var timeStep = (1 / 60) * 1000;
        var currentTime = Date.now();
        var drawingLoop = function () {
            var newTime = Date.now();
            var frameTime = newTime - currentTime;
            var delta;
            currentTime = newTime;
            while (frameTime > 0) {
                delta = Math.min(frameTime, timeStep);
                frameTime -= delta;
            }
            drawUpdate();
            requestAnimationFrame(drawingLoop);
        };
        drawingLoop();
        var updateHash = function() {
            if (window.history.replaceState) {
                return function(hash) {
                    window.history.replaceState(undefined, undefined, '#' + hash);
                };
            } else {
                return function(hash) {
                    location.replace('#' + hash);
                };
            }
        }();
        $scope.save = function () {
            copyFrame($scope.currentFrame, $scope.frames[$scope.currentFrameNum]);
            var frameHash = $scope.frames.map(function (frame) {
                    return frame.toUrlSafeBase64();
                })
                .reduce(function (a, b) {
                    return a + "." + b;
                });
            updateHash(frameHash);
        };
        $scope.doLifeStep = function () {
            setUndo();
            $scope.currentFrame = $scope.currentFrame.lifeStep();
            $scope.save();
        };
        $scope.clear = function () {
            setUndo();
            $scope.currentFrame.fill(false);
            $scope.save();
        };
        $scope.nudge = function (xOffset, yOffset) {
            setUndo();
            $scope.currentFrame.nudge(xOffset, yOffset);
            $scope.save();
        };
        $scope.invert = function (xOffset, yOffset) {
            setUndo();
            $scope.currentFrame.invert();
            $scope.save();
        };
        var pointFromEvent = function ($event) {
            var event = $event.originalEvent;
            var point = {};
            if (event instanceof MouseEvent) {
                point = $scope.getPointFromCoords(event.pageX, event.pageY);
            }
            if (('ontouchstart' in window || navigator.msMaxTouchPoints) && event instanceof TouchEvent) {
                var touch = event.changedTouches[0];
                point = $scope.getPointFromCoords(touch.pageX, touch.pageY);
            }
            return point;
        };
        $scope.penDown = function ($event) {
            $event.preventDefault();
            if ($scope.mode != 'copy') {
                $scope.events.start = $event;
            }
        };
        $scope.penOver = function ($event) {
            $event.preventDefault();
            if ($scope.mode != 'copy') {
                $scope.events.current = $event;
            }
        };
        $scope.penUp = function ($event) {
            if ($event === 'outside') {
                if ($scope.pen.drawing) {
                    $scope.events.finish = $event;
                }
            } else {
                $event.stopPropagation();
                $event.preventDefault();
                if ($scope.mode != 'copy' && $scope.pen.drawing) {
                    $scope.events.finish = $event;
                }
            }
        };
        angular.element($window).bind('blur', function (){
          $scope.penUp('outside');
        });
        angular.element($window).bind('mouseup', function (){
          $scope.penUp('outside');
        });
        var updatePreviews = function () {
            $scope.frames.map(function (frame, index) {
                var previewCanvas = document.getElementById('preview' + index);
                if (index === $scope.currentFrameNum) {
                    drawPreview($scope.currentFrame, previewCanvas);
                } else {
                    drawPreview(frame, previewCanvas);
                }
            });
        };

        var aniTimer = function () {
            var aniState = 0;
            var aniCanvas = document.getElementById('previewani');
            var animate = function () {
                if (aniState === $scope.currentFrameNum) {
                    drawPreview($scope.currentFrame, aniCanvas);
                } else {
                    drawPreview($scope.frames[aniState], aniCanvas);
                }
                if (aniState >= $scope.numFrames - 1) {
                    aniState = aniState - 3;
                } else {
                    aniState++;
                }
                updatePreviews();
            };
            window.setInterval(animate, 250);
        }();
        $scope.copyStart = function () {
            if ($scope.mode !== 'copy') {
                $scope.mode = 'copy';
            } else {
                $scope.mode = 'normal';
            }
        };
        $scope.previewClick = function (n) {
            if ($scope.mode === 'normal') {
                switchToFrame(n);
            } else if ($scope.mode === 'copy') {
                if ($scope.currentFrameNum != n) {
                    setUndo(n);
                    copyFrame($scope.currentFrame, $scope.frames[n]);
                    switchToFrame(n);
                    $scope.save();
                }
                $scope.mode = 'normal';
            }
        };
        var copyFrame = function (from, to) {
            to.rawArray = from.rawArray.slice();
        };
        var drawPreview = function (frame, canvas) {
            var drawCommands = {
                bg: function (w, h, ctx) {
                    ctx.fillStyle = 'rgba(40, 40, 40, .05)';
                    ctx.fillRect(0, 0, w, h);
                },
                on: function (x, y, pixelW, pixelH, ctx) {
                    ctx.strokeStyle = 'rgba(40, 40, 40, 0.85)';
                    ctx.fillStyle = 'rgba(40, 40, 40, 0.85)';
                    ctx.fillRect(x, y, pixelW, pixelH);
                },
                off: function () {}
            };
            var pixelScale = 2;
            frame.drawToCanvas(canvas.width, canvas.height, pixelScale, pixelScale, canvas, drawCommands);
        };
        var init = function () {
            if (location.hash.length > 0) {
                $scope.frames.forEach(function (frame, i) {
                    frame.fromUrlSafeBase64(location.hash.substring(1).split('.')[i]);
                });
                copyFrame($scope.frames[0], $scope.currentFrame);
            } else {
                $scope.save();
            }
            var frame = document.querySelector(".frame");
        };
        init();
    }])
    .directive('pixelCanvas', function() {
        return {
            restrict: 'E',
            scope: {width: '@', height: '@', currentFrame: '='},
            template: '<canvas width="{{width}}" height="{{height}}"></canvas>',
            link: function (scope, element, attrs) {
                var pointGetter = function (element, pitchX, pitchY) {
                    return function(x, y) {
                        var origin = $(element).offset();
                        var left = origin.left;
                        var top = origin.top;
                        x = Math.floor((x - left) / pitchX);
                        y = Math.floor((y - top) / pitchY);
                        return {
                            x: x,
                            y: y
                        };
                    };
                };
                scope.$parent.getPointFromCoords = pointGetter(document.querySelector('pixel-canvas canvas'), 25, 25);
                var updateCanvas = function() {
                    requestAnimationFrame(updateCanvas);
                    var canvas = element.children()[0];
                    var drawCommands = {
                        bg: function (w, h, ctx) {
                            ctx.save();
                            ctx.fillStyle = '#DCF0E6';
                            ctx.fillRect(0, 0, w, h);
                            ctx.restore();
                        },
                        on: function (x, y, pixelW, pixelH, ctx) {
                            ctx.save();
                            ctx.strokeStyle = 'rgba(40, 40, 40, 0.85)';
                            ctx.fillStyle = 'rgba(40, 40, 40, 0.85)';
                            ctx.shadowOffsetX = 1;
                            ctx.shadowOffsetY = 1;
                            ctx.shadowBlur = 2;
                            ctx.shadowColor = '#888';
                            ctx.fillRect(x + 1, y + 1, pixelW - 2, pixelH - 2);
                            ctx.restore();
                        },
                        off: function (x, y, pixelW, pixelH, ctx) {
                            ctx.save();
                            ctx.strokeStyle = 'rgba(40, 40, 40, 0.05)';
                            ctx.fillStyle = 'rgba(40, 40, 40, 0.05)';
                            ctx.fillRect(x + 1, y + 1, pixelW - 2, pixelH - 2);
                            ctx.restore();
                        }
                    };
                    var pixelScale = 25;
                    scope.$parent.currentFrame.drawToCanvas(canvas.width, canvas.height, pixelScale, pixelScale, canvas, drawCommands, scope.$parent.overlay);
                };
                updateCanvas();
            }
        };
    });<commit_msg>Fix bug with pen getting stuck in drawing mode<commit_after>angular.module('super-micro-paint', ['touch-directives'])
    .controller('smpController', ['$scope', '$window', function ($scope, $window) {
        $scope.numFrames = 4;
        $scope.height = 16;
        $scope.width = 32;
        $scope._ = _;
        $scope.activeTool = 'pencil';
        $scope.undoBuffers = _.range(0, $scope.numFrames)
            .map(function () {
                return [];
            });
        $scope.redoBuffers = _.range(0, $scope.numFrames)
            .map(function () {
                return [];
            });
        $scope.frames = _.range(0, $scope.numFrames)
            .map(function () {
                return new SuperPixelGrid($scope.width, $scope.height)
                    .fill(false);
            });
        $scope.currentFrameNum = 0;
        $scope.currentFrame = new SuperPixelGrid($scope.width, $scope.height)
            .fill(false);
        $scope.overlay = new SuperPixelGrid($scope.width, $scope.height)
            .fill(false);
        $scope.pen = {};
        $scope.pen.drawing = false;
        $scope.pen.mode = false;
        $scope.pen.start = undefined;
        $scope.pen.last = undefined;
        $scope.pen.finish = false;
        $scope.events = {};
        $scope.events.start = undefined;
        $scope.events.current = undefined;
        $scope.events.finish = undefined;
        $scope.mode = 'normal';
        $scope.range = function (n) {
            return new Array(n);
        };
        var switchToFrame = function (n) {
            copyFrame($scope.currentFrame, $scope.frames[$scope.currentFrameNum]);
            $scope.currentFrameNum = n;
            copyFrame($scope.frames[n], $scope.currentFrame);
        };
        var clearSelection = function () {
            var selection = ('getSelection' in window) ? window.getSelection() : ('selection' in document) ? document.selection : null;
            if ('removeAllRanges' in selection) {
                selection.removeAllRanges();
            } else if ('empty' in selection) {
                selection.empty();
            }
        };
        var setPixel = function (pixel, mode) {
            $scope.currentFrame.set(pixel.x, pixel.y, mode);
        };
        var setUndo = function (frame) {
            var f, frameToSave;
            if (frame === undefined) {
                f = $scope.currentFrameNum;
                frameToSave = $scope.currentFrame;
            } else {
                f = frame;
                frameToSave = $scope.frames[f];
            }
            $scope.undoBuffers[f].push(frameToSave.toString());
        };
        var setRedo = function () {
            var f = $scope.currentFrameNum;
            $scope.redoBuffers[f].push($scope.currentFrame.toString());
        };
        $scope.undo = function () {
            var f = $scope.currentFrameNum;
            if ($scope.undoBuffers[f].length > 0) {
                setRedo();
                $scope.currentFrame = $scope.currentFrame.fromString($scope.undoBuffers[f].pop());
                $scope.save();
            }
        };
        $scope.redo = function () {
            var f = $scope.currentFrameNum;
            if ($scope.redoBuffers[f].length > 0) {
                setUndo();
                $scope.currentFrame = $scope.currentFrame.fromString($scope.redoBuffers[f].pop());
                $scope.save();
            }
        };
        $scope.canUndo = function () {
            return $scope.undoBuffers[$scope.currentFrameNum].length > 0;
        };
        $scope.canRedo = function () {
            return $scope.redoBuffers[$scope.currentFrameNum].length > 0;
        };
        var getPixel = function (pixel) {
            return $scope.currentFrame.get(pixel.x, pixel.y);
        };
        $scope.getExportURL = function() {
            return "export/?smp=" + location.hash.slice(1);
        };
        var tools = {};
        var buildPointToPointDrawingTool = function (drawingFunction) {
            return {
                'start': function (pen) {
                    $scope.overlay.fill(false);
                },
                'finish': function (pen) {
                    setUndo();
                    $scope.currentFrame[drawingFunction](pen.start.x, pen.start.y, pen.finish.x, pen.finish.y, pen.mode);
                    $scope.overlay.fill(false);
                    $scope.save();
                    clearSelection();
                },
                'update': function (pen) {
                    $scope.overlay.fill(false)[drawingFunction](pen.start.x, pen.start.y, pen.current.x, pen.current.y, true);
                }
            };
        };
        tools.pencil = {
            'start': function (pen) {
                setUndo();
                setPixel(pen.start, pen.mode);
            },
            'finish': function (pen) {
                $scope.save();
                clearSelection();
            },
            'update': function (pen) {
                $scope.currentFrame.drawLine(pen.last.x, pen.last.y, pen.current.x, pen.current.y, pen.mode);
            }
        };
        tools.fill = {
            'start': function (pen) {
                setUndo();
                $scope.currentFrame.floodFill(pen.start.x, pen.start.y, pen.mode);
                $scope.save();
            }
        };
        tools.line = buildPointToPointDrawingTool('drawLine');
        tools.rectangle = buildPointToPointDrawingTool('drawRectangle');
        tools.ellipse = buildPointToPointDrawingTool('drawEllipse');
        var drawUpdate = function() {
            var pen = $scope.pen;
            var events = $scope.events;
            if (events.start && !events.finish) {
                pen.drawing = true;
                pen.start = pen.last = pointFromEvent(events.start);
                pen.mode = !getPixel(pen.start);
                events.start = undefined;
                if (tools[$scope.activeTool].start) {
                    tools[$scope.activeTool].start(pen);
                }
            } else if (pen.drawing === true && events.current && !events.finish) {
                pen.current = pointFromEvent(events.current);
                if (tools[$scope.activeTool].update && !(pen.current.x === pen.last.x && pen.current.y === pen.last.y)) {
                    tools[$scope.activeTool].update(pen);
                }
                pen.last = pen.current;
                events.current = undefined;
            } else if (events.finish) {
                pen.drawing = false;
                if (events.finish === 'outside') {
                    pen.finish = pen.last;
                } else {
                    pen.finish = pointFromEvent(events.finish);
                }
                if (tools[$scope.activeTool].finish) {
                    tools[$scope.activeTool].finish(pen);
                }
                events.finish = events.current = events.start = undefined;
            }
            window.requestAnimationFrame(drawUpdate);
        };
        var timeStep = (1 / 60) * 1000;
        var currentTime = Date.now();
        var drawingLoop = function () {
            var newTime = Date.now();
            var frameTime = newTime - currentTime;
            var delta;
            currentTime = newTime;
            while (frameTime > 0) {
                delta = Math.min(frameTime, timeStep);
                frameTime -= delta;
            }
            drawUpdate();
            requestAnimationFrame(drawingLoop);
        };
        drawingLoop();
        var updateHash = function() {
            if (window.history.replaceState) {
                return function(hash) {
                    window.history.replaceState(undefined, undefined, '#' + hash);
                };
            } else {
                return function(hash) {
                    location.replace('#' + hash);
                };
            }
        }();
        $scope.save = function () {
            copyFrame($scope.currentFrame, $scope.frames[$scope.currentFrameNum]);
            var frameHash = $scope.frames.map(function (frame) {
                    return frame.toUrlSafeBase64();
                })
                .reduce(function (a, b) {
                    return a + "." + b;
                });
            updateHash(frameHash);
        };
        $scope.doLifeStep = function () {
            setUndo();
            $scope.currentFrame = $scope.currentFrame.lifeStep();
            $scope.save();
        };
        $scope.clear = function () {
            setUndo();
            $scope.currentFrame.fill(false);
            $scope.save();
        };
        $scope.nudge = function (xOffset, yOffset) {
            setUndo();
            $scope.currentFrame.nudge(xOffset, yOffset);
            $scope.save();
        };
        $scope.invert = function (xOffset, yOffset) {
            setUndo();
            $scope.currentFrame.invert();
            $scope.save();
        };
        var pointFromEvent = function ($event) {
            var event = $event.originalEvent;
            var point = {};
            if (event instanceof MouseEvent) {
                point = $scope.getPointFromCoords(event.pageX, event.pageY);
            }
            if (('ontouchstart' in window || navigator.msMaxTouchPoints) && event instanceof TouchEvent) {
                var touch = event.changedTouches[0];
                point = $scope.getPointFromCoords(touch.pageX, touch.pageY);
            }
            return point;
        };
        $scope.penDown = function ($event) {
            $event.preventDefault();
            if ($scope.mode != 'copy') {
                $scope.events.start = $event;
            }
        };
        $scope.penOver = function ($event) {
            $event.preventDefault();
            if ($scope.mode != 'copy') {
                $scope.events.current = $event;
            }
        };
        $scope.penUp = function ($event) {
            if ($event === 'outside') {
                    $scope.events.finish = $event;
            } else {
                $event.stopPropagation();
                $event.preventDefault();
                if ($scope.mode != 'copy') {
                    $scope.events.finish = $event;
                }
            }
        };
        angular.element($window).bind('blur', function (){
          $scope.penUp('outside');
        });
        angular.element($window).bind('mouseup', function (){
          $scope.penUp('outside');
        });
        var updatePreviews = function () {
            $scope.frames.map(function (frame, index) {
                var previewCanvas = document.getElementById('preview' + index);
                if (index === $scope.currentFrameNum) {
                    drawPreview($scope.currentFrame, previewCanvas);
                } else {
                    drawPreview(frame, previewCanvas);
                }
            });
        };

        var aniTimer = function () {
            var aniState = 0;
            var aniCanvas = document.getElementById('previewani');
            var animate = function () {
                if (aniState === $scope.currentFrameNum) {
                    drawPreview($scope.currentFrame, aniCanvas);
                } else {
                    drawPreview($scope.frames[aniState], aniCanvas);
                }
                if (aniState >= $scope.numFrames - 1) {
                    aniState = aniState - 3;
                } else {
                    aniState++;
                }
                updatePreviews();
            };
            window.setInterval(animate, 250);
        }();
        $scope.copyStart = function () {
            if ($scope.mode !== 'copy') {
                $scope.mode = 'copy';
            } else {
                $scope.mode = 'normal';
            }
        };
        $scope.previewClick = function (n) {
            if ($scope.mode === 'normal') {
                switchToFrame(n);
            } else if ($scope.mode === 'copy') {
                if ($scope.currentFrameNum != n) {
                    setUndo(n);
                    copyFrame($scope.currentFrame, $scope.frames[n]);
                    switchToFrame(n);
                    $scope.save();
                }
                $scope.mode = 'normal';
            }
        };
        var copyFrame = function (from, to) {
            to.rawArray = from.rawArray.slice();
        };
        var drawPreview = function (frame, canvas) {
            var drawCommands = {
                bg: function (w, h, ctx) {
                    ctx.fillStyle = 'rgba(40, 40, 40, .05)';
                    ctx.fillRect(0, 0, w, h);
                },
                on: function (x, y, pixelW, pixelH, ctx) {
                    ctx.strokeStyle = 'rgba(40, 40, 40, 0.85)';
                    ctx.fillStyle = 'rgba(40, 40, 40, 0.85)';
                    ctx.fillRect(x, y, pixelW, pixelH);
                },
                off: function () {}
            };
            var pixelScale = 2;
            frame.drawToCanvas(canvas.width, canvas.height, pixelScale, pixelScale, canvas, drawCommands);
        };
        var init = function () {
            if (location.hash.length > 0) {
                $scope.frames.forEach(function (frame, i) {
                    frame.fromUrlSafeBase64(location.hash.substring(1).split('.')[i]);
                });
                copyFrame($scope.frames[0], $scope.currentFrame);
            } else {
                $scope.save();
            }
            var frame = document.querySelector(".frame");
        };
        init();
    }])
    .directive('pixelCanvas', function() {
        return {
            restrict: 'E',
            scope: {width: '@', height: '@', currentFrame: '='},
            template: '<canvas width="{{width}}" height="{{height}}"></canvas>',
            link: function (scope, element, attrs) {
                var pointGetter = function (element, pitchX, pitchY) {
                    return function(x, y) {
                        var origin = $(element).offset();
                        var left = origin.left;
                        var top = origin.top;
                        x = Math.floor((x - left) / pitchX);
                        y = Math.floor((y - top) / pitchY);
                        return {
                            x: x,
                            y: y
                        };
                    };
                };
                scope.$parent.getPointFromCoords = pointGetter(document.querySelector('pixel-canvas canvas'), 25, 25);
                var updateCanvas = function() {
                    requestAnimationFrame(updateCanvas);
                    var canvas = element.children()[0];
                    var drawCommands = {
                        bg: function (w, h, ctx) {
                            ctx.save();
                            ctx.fillStyle = '#DCF0E6';
                            ctx.fillRect(0, 0, w, h);
                            ctx.restore();
                        },
                        on: function (x, y, pixelW, pixelH, ctx) {
                            ctx.save();
                            ctx.strokeStyle = 'rgba(40, 40, 40, 0.85)';
                            ctx.fillStyle = 'rgba(40, 40, 40, 0.85)';
                            ctx.shadowOffsetX = 1;
                            ctx.shadowOffsetY = 1;
                            ctx.shadowBlur = 2;
                            ctx.shadowColor = '#888';
                            ctx.fillRect(x + 1, y + 1, pixelW - 2, pixelH - 2);
                            ctx.restore();
                        },
                        off: function (x, y, pixelW, pixelH, ctx) {
                            ctx.save();
                            ctx.strokeStyle = 'rgba(40, 40, 40, 0.05)';
                            ctx.fillStyle = 'rgba(40, 40, 40, 0.05)';
                            ctx.fillRect(x + 1, y + 1, pixelW - 2, pixelH - 2);
                            ctx.restore();
                        }
                    };
                    var pixelScale = 25;
                    scope.$parent.currentFrame.drawToCanvas(canvas.width, canvas.height, pixelScale, pixelScale, canvas, drawCommands, scope.$parent.overlay);
                };
                updateCanvas();
            }
        };
    });<|endoftext|>


<commit_before>// Copyright (C) 2014 Erik Ringsmuth - MIT license
define(function(require) {
  'use strict';
  var config            = require('config'),
      Ractive           = require('ractive'),
      xhrPanelTemplate  = require('rv!./xhrPanelTemplate'),
      sequence          = require('components/apiSequence/sequence'),
      utilities         = require('components/util/utilities'),
      prettify          = require('prettify'),
      URI               = require('bower_components/URIjs/src/URI'),
      vkbeautify        = require('vkbeautify'),
      $                 = require('jquery');
  require('ractive-transitions-slide');

  var XhrPanel = Ractive.extend({
    template: xhrPanelTemplate,

    el: '#api-sequence-placeholder',

    append: true,

    // prototype
    data: {
      name: 'XHR',
      method: 'GET',
      url: 'http://www.suri.io/',
      body: '',
      corsEnabled: false,
      isPublic: true,
      callCount: 0,
      starCount: 0,
      forkCount: 0,
      owner: config.session.userId,
      starred: false,
      signedIn: config.session.signedIn,
      responseBody: '',
      showOptions: false,
      saveButtonClass: 'default',
      sendButtonClass: 'default',
      sendButtonDisabled: false,
      showMoreButton: false,
      fullScreen: false,
      formatNumber: function(number) {
        return number.toLocaleString();
      },
      formatDate: function(date) {
        var d = new Date(date);
        return d.getFullYear() + '-' + d.getMonth() + '-' + d.getDay() + ' ' + d.getHours() + ':' + d.getMinutes();
      }
    },

    init: function() {
      // set non-prototype defaults
      this.set('panelId', utilities.guid());
      if (!this.data.hasOwnProperty('headers')) this.set('headers', []);
      if (!this.data.hasOwnProperty('queryParameters')) this.set('queryParameters', []);
      if (!this.data.hasOwnProperty('tags')) this.set('tags', []);
      if (!this.data.hasOwnProperty('stars')) this.set('stars', []);
      if (!this.data.hasOwnProperty('forks')) this.set('forks', []);
      this.set('isOwner', !this.get('id') || config.session.userId === this.get('owner'));
      if (this.get('stars').indexOf(config.session.userId) !== -1) {
        this.set('starred', true);
      }

      // add this XHR to the api sequence
      sequence.add(this);

      this.on({
        teardown: function teardown(event) {
          this.detach();
          sequence.remove(this);
          if (event && event.original && event.original.stopPropagation) {
            event.original.stopPropagation();
          }
        },

        scrollToPanel: function scrollToPanel() {
          $('html,body').animate({
            scrollTop: document.getElementById(this.get('panelId')).offsetTop + $('#api-sequence').offset().top - 15
          }, 200, 'easeOutQuint');
        },

        setupTooltips: function() {
          $('.bs-tooltip').tooltip();
        },

        toggleOptions: function() {
          this.set('showOptions', !this.get('showOptions'));
          this.fire('setupTooltips');
        },

        toggleFullscreen: function() {
          this.set('fullScreen', !this.get('fullScreen'));
        },

        star: function() {
          if (this.get('starred')) {
            // Unstar
            $.ajax('/xhr/' + this.get('id') + '/stars/' + config.session.userId, {
              method: 'DELETE'
            })
              .done(function() {
                this.data.stars.splice(this.data.stars.indexOf(config.session.userId), 1);
                this.set('starCount', this.get('starCount') - 1);
                this.set('starred', false);
              }.bind(this));
          } else {
            // Star
            $.ajax('/xhr/' + this.get('id') + '/stars', {
              method: 'POST',
              data: config.session.userId
            })
              .done(function() {
                this.data.stars.push(config.session.userId);
                this.set('starCount', this.get('starCount') + 1);
                this.set('starred', true);
              }.bind(this));
          }
        },

        save: function() {
          this.set('saveButtonClass', 'default');

          if (!this.get('signedIn')) return;

          if (this.get('id')) {
            // update
            $.ajax('/xhr/' + this.get('id'), {
              type: 'PUT',
              contentType: 'application/json',
              data: this.toJSON()
            })
              .done(function() {
                this.set('saveButtonClass', 'success');
              }.bind(this))
              .fail(function() {
                this.set('saveButtonClass', 'danger');
              }.bind(this));
          }
          else {
            // save
            $.ajax('/xhr', {
              type: 'POST',
              contentType: 'application/json',
              data: this.toJSON()
            })
              .done(function(data) {
                this.set('saveButtonClass', 'success');
                this.set('id', data._id);
              }.bind(this))
              .fail(function() {
                this.set('saveButtonClass', 'danger');
              }.bind(this));
          }
        },

        delete: function() {
          if (!this.get('id')) {
            // This was never saved, remove the panel
            this.teardown();
          } else {
            $.ajax('/xhr/' + this.get('id'), {
              type: 'DELETE'
            })
              .done(function() {
                this.teardown();
              }.bind(this));
          }
        },

        fork: function() {
          var fork = new XhrPanel({data: JSON.parse(this.toJSON())});
          fork.set({
            id: null,
            isOwner: true,
            owner: config.session.userId,
            callCount: 0,
            starCount: 0,
            forkCount: 0,
            forks: [],
            forkedFrom: this.get('id')
          });
          fork.fire('save');
          this.data.forks.push(''); // increment the forks count, this is faking out what's happening server side
        },

        addTagOnEnter: function(event) {
          if (event.original.keyCode === 13) {
            var tag = event.node.value.replace(/\s/g, '').toLowerCase();
            if (this.get('tags').indexOf(tag) === -1) {
              this.get('tags').push(tag);
              event.node.value = '';
            }
          }
        },

        deleteTag: function(event, tag) {
          this.get('tags').splice(this.get('tags').indexOf(tag), 1);
          event.original.preventDefault();
        },

        addBlankHeader: function() {
          this.get('headers').push({
            header: '',
            options: [],
            selected: '',
            required: false
          });
        },

        removeHeader: function(event, header) {
          this.get('headers').splice(this.get('headers').indexOf(header), 1);
        },

        sendOnEnter: function sendOnEnter(event) {
          if (event.original.keyCode === 13) {
            this.fire('send');
          }
        },

        send: function send() {
          // Clear and disable send button
          this.set('sendButtonClass', 'default');
          this.set('sendButtonDisabled', true);

          // Increment the call count locally, this gets set server-side by the api-id header
          this.set('callCount', this.get('callCount') + 1);

          // Get the selected headers
          var headers = this.get('headers');
          var requestHeaders = {};
          headers.forEach(function(header) {
            requestHeaders[header.header] = header.selected;
          });

          // Make sure URL the protocol is included otherwise default to HTTP
          var url = this.get('url');
          if (url.indexOf('://') === -1) {
            url = 'http://' + url;
          }
          var apiUri = new URI(url);
          var suriHost = new URI(window.location.href).host();
          if (!this.get('corsEnabled') && apiUri.host() !== suriHost) {
            // Proxy through the suri.io server
            requestHeaders['api-host'] = apiUri.protocol() + '://' + apiUri.authority();
            apiUri.authority(suriHost);
            apiUri.protocol(window.location.protocol); // Suri doesn't support HTTPS yet
          }

          // Set the api-id to log the call counts
          requestHeaders['api-id'] = this.get('id');

          // Send the request
          $.ajax({
            type: this.get('method'),
            url: apiUri,
            headers: requestHeaders,
            data: this.get('body')
          })
          .done(function(data, textStatus, jqXHR) {
            this.fire('displayResponse', jqXHR);
          }.bind(this))
          .fail(function(jqXHR) {
            this.fire('displayResponse', jqXHR);
          }.bind(this));
        },

        displayResponse: function displayResponse(jqXHR) {
          // update send button
          this.set('sendButtonDisabled', false);
          if (jqXHR.status >= 200 && jqXHR.status < 300) {
            this.set('sendButtonClass', 'success');
          } else {
            this.set('sendButtonClass', 'danger');
          }

          // headers
          this.set('responseHeaders', 'HTTP/1.1 ' + jqXHR.status + ' ' + jqXHR.statusText + '\n' + jqXHR.getAllResponseHeaders());

          // body
          this.set('responseBody', '');

          if (typeof(jqXHR.responseJSON) !== 'undefined') {
            // JSON
            this.set('responseBody', utilities.escape(JSON.stringify(jqXHR.responseJSON, null, 2)));
          }
          else if (typeof(jqXHR.responseXML) !== 'undefined') {
            // XML
            this.set('responseBody', utilities.escape(vkbeautify.xml(new XMLSerializer().serializeToString(jqXHR.responseXML), 2)));
          }
          else {
            var contentType = jqXHR.getResponseHeader('content-type');
            if (contentType && contentType.indexOf('javascript') !== -1) {
              // This could still be JSON, Google does this :P
              var parsedResponse;
              try {
                parsedResponse = JSON.stringify(JSON.parse(jqXHR.responseText), null, 2);
              } catch (e) {
                parsedResponse = jqXHR.responseText;
              }
              this.set('responseBody', utilities.escape(parsedResponse));
            }
            else {
              // Plain text, html, or something
              this.set('responseBody', utilities.escape(jqXHR.responseText));
            }
          }

          if (this.get('responseBody').length > 3000) {
            this.nodes.responseBody.innerHTML = prettify.prettyPrintOne(this.get('responseBody').substring(0, 3000));
            this.set('showMoreButton', true);
          } else {
            this.fire('displayEntireResponse');
          }
        },

        displayEntireResponse: function displayEntireResponse() {
          this.nodes.responseBody.innerHTML = prettify.prettyPrintOne(this.get('responseBody'));
          this.set('showMoreButton', false);
        }
      });

      // Scroll to the panel when it's created
      this.fire('scrollToPanel');
      this.fire('setupTooltips');
    },

    // Return the Ractive object data as JSON including the default data properties which are on the prototype
    toJSON: function() {
      var json = {};
      for (var property in this.data) {
        if (typeof(property) !== 'function' && property !== 'responseBody' && property !== 'responseHeaders') {
          json[property] = this.data[property];
        }
      }
      return JSON.stringify(json);
    }
  });

  return XhrPanel;
});
<commit_msg>fix issue 1 - strip api-id header on CORS request<commit_after>// Copyright (C) 2014 Erik Ringsmuth - MIT license
define(function(require) {
  'use strict';
  var config            = require('config'),
      Ractive           = require('ractive'),
      xhrPanelTemplate  = require('rv!./xhrPanelTemplate'),
      sequence          = require('components/apiSequence/sequence'),
      utilities         = require('components/util/utilities'),
      prettify          = require('prettify'),
      URI               = require('bower_components/URIjs/src/URI'),
      vkbeautify        = require('vkbeautify'),
      $                 = require('jquery');
  require('ractive-transitions-slide');

  var XhrPanel = Ractive.extend({
    template: xhrPanelTemplate,

    el: '#api-sequence-placeholder',

    append: true,

    // prototype
    data: {
      name: 'XHR',
      method: 'GET',
      url: 'http://www.suri.io/',
      body: '',
      corsEnabled: false,
      isPublic: true,
      callCount: 0,
      starCount: 0,
      forkCount: 0,
      owner: config.session.userId,
      starred: false,
      signedIn: config.session.signedIn,
      responseBody: '',
      showOptions: false,
      saveButtonClass: 'default',
      sendButtonClass: 'default',
      sendButtonDisabled: false,
      showMoreButton: false,
      fullScreen: false,
      formatNumber: function(number) {
        return number.toLocaleString();
      },
      formatDate: function(date) {
        var d = new Date(date);
        return d.getFullYear() + '-' + d.getMonth() + '-' + d.getDay() + ' ' + d.getHours() + ':' + d.getMinutes();
      }
    },

    init: function() {
      // set non-prototype defaults
      this.set('panelId', utilities.guid());
      if (!this.data.hasOwnProperty('headers')) this.set('headers', []);
      if (!this.data.hasOwnProperty('queryParameters')) this.set('queryParameters', []);
      if (!this.data.hasOwnProperty('tags')) this.set('tags', []);
      if (!this.data.hasOwnProperty('stars')) this.set('stars', []);
      if (!this.data.hasOwnProperty('forks')) this.set('forks', []);
      this.set('isOwner', !this.get('id') || config.session.userId === this.get('owner'));
      if (this.get('stars').indexOf(config.session.userId) !== -1) {
        this.set('starred', true);
      }

      // add this XHR to the api sequence
      sequence.add(this);

      this.on({
        teardown: function teardown(event) {
          this.detach();
          sequence.remove(this);
          if (event && event.original && event.original.stopPropagation) {
            event.original.stopPropagation();
          }
        },

        scrollToPanel: function scrollToPanel() {
          $('html,body').animate({
            scrollTop: document.getElementById(this.get('panelId')).offsetTop + $('#api-sequence').offset().top - 15
          }, 200, 'easeOutQuint');
        },

        setupTooltips: function() {
          $('.bs-tooltip').tooltip();
        },

        toggleOptions: function() {
          this.set('showOptions', !this.get('showOptions'));
          this.fire('setupTooltips');
        },

        toggleFullscreen: function() {
          this.set('fullScreen', !this.get('fullScreen'));
        },

        star: function() {
          if (this.get('starred')) {
            // Unstar
            $.ajax('/xhr/' + this.get('id') + '/stars/' + config.session.userId, {
              method: 'DELETE'
            })
              .done(function() {
                this.data.stars.splice(this.data.stars.indexOf(config.session.userId), 1);
                this.set('starCount', this.get('starCount') - 1);
                this.set('starred', false);
              }.bind(this));
          } else {
            // Star
            $.ajax('/xhr/' + this.get('id') + '/stars', {
              method: 'POST',
              data: config.session.userId
            })
              .done(function() {
                this.data.stars.push(config.session.userId);
                this.set('starCount', this.get('starCount') + 1);
                this.set('starred', true);
              }.bind(this));
          }
        },

        save: function() {
          this.set('saveButtonClass', 'default');

          if (!this.get('signedIn')) return;

          if (this.get('id')) {
            // update
            $.ajax('/xhr/' + this.get('id'), {
              type: 'PUT',
              contentType: 'application/json',
              data: this.toJSON()
            })
              .done(function() {
                this.set('saveButtonClass', 'success');
              }.bind(this))
              .fail(function() {
                this.set('saveButtonClass', 'danger');
              }.bind(this));
          }
          else {
            // save
            $.ajax('/xhr', {
              type: 'POST',
              contentType: 'application/json',
              data: this.toJSON()
            })
              .done(function(data) {
                this.set('saveButtonClass', 'success');
                this.set('id', data._id);
              }.bind(this))
              .fail(function() {
                this.set('saveButtonClass', 'danger');
              }.bind(this));
          }
        },

        delete: function() {
          if (!this.get('id')) {
            // This was never saved, remove the panel
            this.teardown();
          } else {
            $.ajax('/xhr/' + this.get('id'), {
              type: 'DELETE'
            })
              .done(function() {
                this.teardown();
              }.bind(this));
          }
        },

        fork: function() {
          var fork = new XhrPanel({data: JSON.parse(this.toJSON())});
          fork.set({
            id: null,
            isOwner: true,
            owner: config.session.userId,
            callCount: 0,
            starCount: 0,
            forkCount: 0,
            forks: [],
            forkedFrom: this.get('id')
          });
          fork.fire('save');
          this.data.forks.push(''); // increment the forks count, this is faking out what's happening server side
        },

        addTagOnEnter: function(event) {
          if (event.original.keyCode === 13) {
            var tag = event.node.value.replace(/\s/g, '').toLowerCase();
            if (this.get('tags').indexOf(tag) === -1) {
              this.get('tags').push(tag);
              event.node.value = '';
            }
          }
        },

        deleteTag: function(event, tag) {
          this.get('tags').splice(this.get('tags').indexOf(tag), 1);
          event.original.preventDefault();
        },

        addBlankHeader: function() {
          this.get('headers').push({
            header: '',
            options: [],
            selected: '',
            required: false
          });
        },

        removeHeader: function(event, header) {
          this.get('headers').splice(this.get('headers').indexOf(header), 1);
        },

        sendOnEnter: function sendOnEnter(event) {
          if (event.original.keyCode === 13) {
            this.fire('send');
          }
        },

        send: function send() {
          // Clear and disable send button
          this.set('sendButtonClass', 'default');
          this.set('sendButtonDisabled', true);

          // Increment the call count locally, this gets set server-side by the api-id header
          this.set('callCount', this.get('callCount') + 1);

          // Get the selected headers
          var headers = this.get('headers');
          var requestHeaders = {};
          headers.forEach(function(header) {
            requestHeaders[header.header] = header.selected;
          });

          // Make sure URL the protocol is included otherwise default to HTTP
          var url = this.get('url');
          if (url.indexOf('://') === -1) {
            url = 'http://' + url;
          }
          var apiUri = new URI(url);
          var suriHost = new URI(window.location.href).host();
          if (!this.get('corsEnabled') && apiUri.host() !== suriHost) {
            // Proxy through the suri.io server
            requestHeaders['api-host'] = apiUri.protocol() + '://' + apiUri.authority();
            apiUri.authority(suriHost);
            apiUri.protocol(window.location.protocol); // Suri doesn't support HTTPS yet

            // Set the api-id to log the call counts
            requestHeaders['api-id'] = this.get('id');
          }

          // Send the request
          $.ajax({
            type: this.get('method'),
            url: apiUri,
            headers: requestHeaders,
            data: this.get('body')
          })
          .done(function(data, textStatus, jqXHR) {
            this.fire('displayResponse', jqXHR);
          }.bind(this))
          .fail(function(jqXHR) {
            this.fire('displayResponse', jqXHR);
          }.bind(this));
        },

        displayResponse: function displayResponse(jqXHR) {
          // update send button
          this.set('sendButtonDisabled', false);
          if (jqXHR.status >= 200 && jqXHR.status < 300) {
            this.set('sendButtonClass', 'success');
          } else {
            this.set('sendButtonClass', 'danger');
          }

          // headers
          this.set('responseHeaders', 'HTTP/1.1 ' + jqXHR.status + ' ' + jqXHR.statusText + '\n' + jqXHR.getAllResponseHeaders());

          // body
          this.set('responseBody', '');

          if (typeof(jqXHR.responseJSON) !== 'undefined') {
            // JSON
            this.set('responseBody', utilities.escape(JSON.stringify(jqXHR.responseJSON, null, 2)));
          }
          else if (typeof(jqXHR.responseXML) !== 'undefined') {
            // XML
            this.set('responseBody', utilities.escape(vkbeautify.xml(new XMLSerializer().serializeToString(jqXHR.responseXML), 2)));
          }
          else {
            var contentType = jqXHR.getResponseHeader('content-type');
            if (contentType && contentType.indexOf('javascript') !== -1) {
              // This could still be JSON, Google does this :P
              var parsedResponse;
              try {
                parsedResponse = JSON.stringify(JSON.parse(jqXHR.responseText), null, 2);
              } catch (e) {
                parsedResponse = jqXHR.responseText;
              }
              this.set('responseBody', utilities.escape(parsedResponse));
            }
            else {
              // Plain text, html, or something
              this.set('responseBody', utilities.escape(jqXHR.responseText));
            }
          }

          if (this.get('responseBody').length > 3000) {
            this.nodes.responseBody.innerHTML = prettify.prettyPrintOne(this.get('responseBody').substring(0, 3000));
            this.set('showMoreButton', true);
          } else {
            this.fire('displayEntireResponse');
          }
        },

        displayEntireResponse: function displayEntireResponse() {
          this.nodes.responseBody.innerHTML = prettify.prettyPrintOne(this.get('responseBody'));
          this.set('showMoreButton', false);
        }
      });

      // Scroll to the panel when it's created
      this.fire('scrollToPanel');
      this.fire('setupTooltips');
    },

    // Return the Ractive object data as JSON including the default data properties which are on the prototype
    toJSON: function() {
      var json = {};
      for (var property in this.data) {
        if (typeof(property) !== 'function' && property !== 'responseBody' && property !== 'responseHeaders') {
          json[property] = this.data[property];
        }
      }
      return JSON.stringify(json);
    }
  });

  return XhrPanel;
});
<|endoftext|>