<commit_before>import Ember from "ember";
import layout from "../templates/components/resizable-table-cell";
import InjectStyle from "../mixins/inject-style";
import { formatStyle } from "../utils/format-style";

const { computed, get, Component } = Ember;

export default Component.extend(InjectStyle, {
  layout,
  classNames: ["resizable-table-cell"],
  attributeBindings: ["colSpan:colspan", "rowSpan:rowspan"],
  tagName: "td",
  colSpan: 1,
  rowSpan: 1,
  coordX: undefined /* set by resizable-table */,
  coordY: undefined /* set by resizable-table */,
  numColumns: computed.alias("row.table.numColumns"),
  numRows: computed.alias("row.table.numRows"),

  columnSizes: computed.alias("row.table.columnSizes"),
  rowSizes: computed.alias("row.table.rowSizes"),
  sashWidth: computed.alias("row.table.sashWidth"),
  sashDistance: 0,

  init() {
    this._super();
    this.get("myWidth");
    this.get("myHeight");
    this.get("style");
  },

  myWidth: computed(
    "coordX",
    "coordY",
    "columnSizes",
    "columnSizes.@each.size",
    function() {
      if (
        this.get("coordX") === undefined ||
        this.get("coordY") === undefined ||
        this.get("columnSizes") === undefined
      ) {
        return undefined;
      }

      const x = this.get("coordX");
      let ret = 0.0;
      for (let w = 0; w < this.get("colSpan"); w++) {
        const cur = this.get("columnSizes")[x + w];
        if (cur === undefined) {
          return undefined;
        }
        ret += get(cur, "size");
      }
      return ret;
    }
  ),

  myHeight: computed(
    "coordX",
    "coordY",
    "rowSizes",
    "rowSizes.@each.size",
    function() {
      if (
        this.get("coordY") === undefined ||
        this.get("coordY") === undefined ||
        this.get("rowSizes") === undefined
      ) {
        return undefined;
      }

      const y = this.get("coordY");
      let ret = 0.0;
      for (let h = 0; h < this.get("rowSpan"); h++) {
        const cur = this.get("rowSizes")[y + h];
        if (cur === undefined) {
          return undefined;
        }
        ret += get(cur, "size");
      }
      return ret;
    }
  ),

  style: computed("myWidth", "myHeight", function() {
    return {
      position: "relative",
      width: `${this.get("myWidth") * 100.0}%`,
      height: `${this.get("myHeight") * 100.0}%`
    };
  }),

  sashBaseStyle: computed(function() {
    return {
      position: "absolute",
      "user-select": "none",
      "z-index": "1000000"
    };
  }),

  sashTopStyle: computed(
    "sashWidth",
    "sashDistance",
    "baseSashStyle",
    function() {
      return formatStyle(
        Object.assign({}, this.get("sashBaseStyle"), {
          top: "-1px",
          left: `${this.get("sashDistance")}px`,
          right: `${this.get("sashDistance")}px`,
          height: `${this.get("sashWidth")}px`,
          cursor: "row-resize"
        })
      );
    }
  ),

  sashRightStyle: computed(
    "sashWidth",
    "sashDistance",
    "baseSashStyle",
    function() {
      return formatStyle(
        Object.assign({}, this.get("sashBaseStyle"), {
          right: "-1px",
          top: `${this.get("sashDistance")}px`,
          bottom: `${this.get("sashDistance")}px`,
          width: `${this.get("sashWidth")}px`,
          cursor: "col-resize"
        })
      );
    }
  ),

  sashBottomStyle: computed(
    "sashWidth",
    "sashDistance",
    "baseSashStyle",
    function() {
      return formatStyle(
        Object.assign({}, this.get("sashBaseStyle"), {
          bottom: "-1px",
          left: `${this.get("sashDistance")}px`,
          right: `${this.get("sashDistance")}px`,
          height: `${this.get("sashWidth")}px`,
          cursor: "row-resize"
        })
      );
    }
  ),

  sashLeftStyle: computed(
    "sashWidth",
    "sashDistance",
    "baseSashStyle",
    function() {
      return formatStyle(
        Object.assign({}, this.get("sashBaseStyle"), {
          left: "-1px",
          top: `${this.get("sashDistance")}px`,
          bottom: `${this.get("sashDistance")}px`,
          width: `${this.get("sashWidth")}px`,
          cursor: "col-resize"
        })
      );
    }
  ),

  /**
   * Helpers
   */

  reLayout() {
    this.get("row").reLayout();
  },

  /**
   * Life cycle
   */

  didInsertElement() {
    this._super(...arguments);
    this.get("row.cells").pushObject(this);
    this.reLayout();
  },

  willDestroyElement() {
    this._super(...arguments);
    this.get("row.cells").removeObject(this);
    this.reLayout();
  },

  /**
   * Actions
   */

  sashClicked(sashType) {
    let columnOrRow, index;
    switch (sashType) {
      case "top":
        columnOrRow = "row";
        index = this.get("coordY");
        break;
      case "right":
        columnOrRow = "column";
        index = this.get("coordX") + this.get("colSpan");
        break;
      case "bottom":
        columnOrRow = "row";
        index = this.get("coordY") + this.get("rowSpan");
        break;
      case "left":
        columnOrRow = "column";
        index = this.get("coordX");
        break;
    }

    this.get("row.table.startResize")(columnOrRow, index);
  }
});
<commit_msg><resizable-table-cell> Use attribute bindings for style instead of jQuery<commit_after>import Ember from "ember";
import layout from "../templates/components/resizable-table-cell";
import { formatStyle } from "../utils/format-style";

const { computed, get, Component } = Ember;
const { htmlSafe } = Ember.String;

export default Component.extend({
  layout,
  classNames: ["resizable-table-cell"],
  attributeBindings: ["colSpan:colspan", "rowSpan:rowspan", "htmlStyle:style"],
  tagName: "td",
  colSpan: 1,
  rowSpan: 1,
  coordX: undefined /* set by resizable-table */,
  coordY: undefined /* set by resizable-table */,
  numColumns: computed.alias("row.table.numColumns"),
  numRows: computed.alias("row.table.numRows"),

  columnSizes: computed.alias("row.table.columnSizes"),
  rowSizes: computed.alias("row.table.rowSizes"),
  sashWidth: computed.alias("row.table.sashWidth"),
  sashDistance: 0,

  init() {
    this._super();
    this.get("myWidth");
    this.get("myHeight");
    this.get("style");
  },

  myWidth: computed(
    "coordX",
    "coordY",
    "columnSizes",
    "columnSizes.@each.size",
    function() {
      if (
        this.get("coordX") === undefined ||
        this.get("coordY") === undefined ||
        this.get("columnSizes") === undefined
      ) {
        return undefined;
      }

      const x = this.get("coordX");
      let ret = 0.0;
      for (let w = 0; w < this.get("colSpan"); w++) {
        const cur = this.get("columnSizes")[x + w];
        if (cur === undefined) {
          return undefined;
        }
        ret += get(cur, "size");
      }
      return ret;
    }
  ),

  myHeight: computed(
    "coordX",
    "coordY",
    "rowSizes",
    "rowSizes.@each.size",
    function() {
      if (
        this.get("coordY") === undefined ||
        this.get("coordY") === undefined ||
        this.get("rowSizes") === undefined
      ) {
        return undefined;
      }

      const y = this.get("coordY");
      let ret = 0.0;
      for (let h = 0; h < this.get("rowSpan"); h++) {
        const cur = this.get("rowSizes")[y + h];
        if (cur === undefined) {
          return undefined;
        }
        ret += get(cur, "size");
      }
      return ret;
    }
  ),

  style: computed("myWidth", "myHeight", function() {
    return {
      position: "relative",
      width: `${this.get("myWidth") * 100.0}%`,
      height: `${this.get("myHeight") * 100.0}%`
    };
  }),

  htmlStyle: computed("style", function() {
    const styleObject = get(this, "style");
    const styleAttribute = Object.keys(styleObject)
      .map(key => `${key}: ${styleObject[key]};`)
      .reduce((x, y) => x + y, "");

    return htmlSafe(styleAttribute);
  }),

  sashBaseStyle: computed(function() {
    return {
      position: "absolute",
      "user-select": "none",
      "z-index": "1000000"
    };
  }),

  sashTopStyle: computed(
    "sashWidth",
    "sashDistance",
    "baseSashStyle",
    function() {
      return formatStyle(
        Object.assign({}, this.get("sashBaseStyle"), {
          top: "-1px",
          left: `${this.get("sashDistance")}px`,
          right: `${this.get("sashDistance")}px`,
          height: `${this.get("sashWidth")}px`,
          cursor: "row-resize"
        })
      );
    }
  ),

  sashRightStyle: computed(
    "sashWidth",
    "sashDistance",
    "baseSashStyle",
    function() {
      return formatStyle(
        Object.assign({}, this.get("sashBaseStyle"), {
          right: "-1px",
          top: `${this.get("sashDistance")}px`,
          bottom: `${this.get("sashDistance")}px`,
          width: `${this.get("sashWidth")}px`,
          cursor: "col-resize"
        })
      );
    }
  ),

  sashBottomStyle: computed(
    "sashWidth",
    "sashDistance",
    "baseSashStyle",
    function() {
      return formatStyle(
        Object.assign({}, this.get("sashBaseStyle"), {
          bottom: "-1px",
          left: `${this.get("sashDistance")}px`,
          right: `${this.get("sashDistance")}px`,
          height: `${this.get("sashWidth")}px`,
          cursor: "row-resize"
        })
      );
    }
  ),

  sashLeftStyle: computed(
    "sashWidth",
    "sashDistance",
    "baseSashStyle",
    function() {
      return formatStyle(
        Object.assign({}, this.get("sashBaseStyle"), {
          left: "-1px",
          top: `${this.get("sashDistance")}px`,
          bottom: `${this.get("sashDistance")}px`,
          width: `${this.get("sashWidth")}px`,
          cursor: "col-resize"
        })
      );
    }
  ),

  /**
   * Helpers
   */

  reLayout() {
    this.get("row").reLayout();
  },

  /**
   * Life cycle
   */

  didInsertElement() {
    this._super(...arguments);
    this.get("row.cells").pushObject(this);
    this.reLayout();
  },

  willDestroyElement() {
    this._super(...arguments);
    this.get("row.cells").removeObject(this);
    this.reLayout();
  },

  /**
   * Actions
   */

  sashClicked(sashType) {
    let columnOrRow, index;
    switch (sashType) {
      case "top":
        columnOrRow = "row";
        index = this.get("coordY");
        break;
      case "right":
        columnOrRow = "column";
        index = this.get("coordX") + this.get("colSpan");
        break;
      case "bottom":
        columnOrRow = "row";
        index = this.get("coordY") + this.get("rowSpan");
        break;
      case "left":
        columnOrRow = "column";
        index = this.get("coordX");
        break;
    }

    this.get("row.table.startResize")(columnOrRow, index);
  }
});
<|endoftext|>


<commit_before>const SUPPORTED_FORMATS = ['IMG']
const KEY_ESC = 27
const KEY_Q = 81
const CANCEL_KEYS = [KEY_ESC, KEY_Q]

const isSupported = img =>
  SUPPORTED_FORMATS.includes(img.tagName)

const isScaled = img =>
  img.naturalWidth !== img.width

const isListOrCollection = selector =>
  NodeList.prototype.isPrototypeOf(selector) ||
  HTMLCollection.prototype.isPrototypeOf(selector)

const isNode = selector =>
  (selector && selector.nodeType === 1)

/**
 * Attaches a zoom effect on a selection of images.
 *
 * @param {(string|Object[])} [selector] The images to attach the zoom to
 * @param {number} [options.margin=0] The space outside the zoomed image
 * @param {string} [options.background="#fff"] The color of the overlay
 * @param {number} [options.scrollOffset=48] The number of pixels to scroll to dismiss the zoom
 * @param {boolean} [options.metaClick=true] A boolean to enable the default action on meta click
 * @return The zoom object
 */
const mediumZoom = (selector, {
  margin = 0,
  background = '#fff',
  scrollOffset = 48,
  metaClick = true
} = {}) => {
  const selectImages = selector => {
    try {
      return Array.isArray(selector)
        ? selector.filter(isSupported)
        : isListOrCollection(selector)
          ? [...selector].filter(isSupported)
          : isNode(selector)
            ? [selector].filter(isSupported)
            : typeof selector === 'string'
              ? [...document.querySelectorAll(selector)].filter(isSupported)
              : [...document.querySelectorAll(
                  SUPPORTED_FORMATS.map(attr => attr.toLowerCase()).join(',')
                )].filter(isScaled)
    } catch (err) {
      throw new TypeError(
        'The provided selector is invalid.\n' +
        'Expects a CSS selector, a Node element, a NodeList, an HTMLCollection or an array.\n' +
        'See: https://github.com/francoischalifour/medium-zoom'
      )
    }
  }

  const createOverlay = background => {
    const overlay = document.createElement('div')
    overlay.classList.add('medium-zoom-overlay')
    overlay.style.backgroundColor = background

    return overlay
  }

  const cloneTarget = template => {
    const { top, left, width } = template.getBoundingClientRect()
    const clone = template.cloneNode()
    clone.removeAttribute('id')
    clone.style.position = 'absolute'
    clone.style.top = `${top + window.scrollY}px`
    clone.style.left = `${left + window.scrollX}px`
    clone.style.width = `${width}px`

    return clone
  }

  const zoom = () => {
    if (!target.template) return

    target.template.dispatchEvent(new Event('show'))

    scrollTop = document.documentElement.scrollTop || document.body.scrollTop
    isAnimating = true
    target.zoomed = cloneTarget(target.template)

    document.body.appendChild(overlay)
    document.body.appendChild(target.zoomed)

    requestAnimationFrame(() => {
      document.body.classList.add('medium-zoom--open')
    })

    target.template.style.visibility = 'hidden'
    target.zoomed.classList.add('medium-zoom-image--open')

    target.zoomed.addEventListener('click', zoomOut)
    target.zoomed.addEventListener('transitionend', onZoomEnd)

    animateTarget()
  }

  const zoomOut = (timeout = 0) => {
    const doZoomOut = () => {
      if (isAnimating || !target.template) return

      target.template.dispatchEvent(new Event('hide'))

      isAnimating = true
      document.body.classList.remove('medium-zoom--open')
      target.zoomed.style.transform = ''

      target.zoomed.removeEventListener('click', zoomOut)
      target.zoomed.addEventListener('transitionend', onZoomOutEnd)
    }

    (timeout > 0) ? setTimeout(doZoomOut, timeout) : doZoomOut()
  }

  const triggerZoom = event => {
    if (!target.template) {
      target.template = event ? event.target : images[0]
      zoom()
    } else {
      zoomOut()
    }
  }

  const update = (newOptions = {}) => {
    newOptions.background &&
      (overlay.style.backgroundColor = newOptions.background)

    return Object.assign(options, newOptions)
  }

  const addEventListeners = (type, listener) => {
    images.forEach(image => {
      image.addEventListener(type, listener)
    })
  }

  const detach = () => {
    const doDetach = () => {
      const event = new Event('detach')

      images.forEach(image => {
        image.classList.remove('medium-zoom-image')
        image.removeEventListener('click', onClick)
        image.dispatchEvent(event)
      })

      images.splice(0, images.length)

      target.zoomed &&
        target.zoomed.removeEventListener('transitionend', doDetach)
    }

    if (!target.zoomed) {
      doDetach()
    } else {
      zoomOut()
      target.zoomed.addEventListener('transitionend', doDetach)
    }
  }

  const onClick = event => {
    if (event.metaKey || event.ctrlKey) {
      if (options.metaClick) {
        return window.open(
          (event.target.getAttribute('data-original') ||
          event.target.parentNode.href ||
          event.target.src),
          '_blank')
      }
    }

    event.preventDefault()

    triggerZoom(event)
  }

  const onZoomEnd = () => {
    isAnimating = false
    target.zoomed.removeEventListener('transitionend', onZoomEnd)

    target.template.dispatchEvent(new Event('shown'))
  }

  const onZoomOutEnd = () => {
    if (!target.template) return

    target.template.style.visibility = ''
    document.body.removeChild(target.zoomed)
    document.body.removeChild(overlay)
    target.zoomed.classList.remove('medium-zoom-image--open')

    isAnimating = false
    target.zoomed.removeEventListener('transitionend', onZoomOutEnd)

    target.template.dispatchEvent(new Event('hidden'))

    target.template = null
    target.zoomed = null
  }

  const onScroll = () => {
    if (isAnimating || !target.template) return

    const currentScroll = document.documentElement.scrollTop || document.body.scrollTop

    if (Math.abs(scrollTop - currentScroll) > options.scrollOffset) {
      zoomOut(150)
    }
  }

  const onDismiss = event => {
    if (CANCEL_KEYS.includes(event.keyCode || event.which)) {
      zoomOut()
    }
  }

  const animateTarget = () => {
    if (!target.template) return

    const windowWidth = window.innerWidth
    const windowHeight = window.innerHeight

    const viewportWidth = windowWidth - (options.margin * 2)
    const viewportHeight = windowHeight - (options.margin * 2)

    const { naturalWidth = +Infinity, naturalHeight = +Infinity } = target.template
    const { top, left, width, height } = target.template.getBoundingClientRect()

    const scaleX = Math.min(naturalWidth, viewportWidth) / width
    const scaleY = Math.min(naturalHeight, viewportHeight) / height
    const scale = Math.min(scaleX, scaleY) || 1
    const translateX = (-left + ((viewportWidth - width) / 2) + options.margin) / scale
    const translateY = (-top + ((viewportHeight - height) / 2) + options.margin) / scale

    target.zoomed.style.transform = `scale(${scale}) translate3d(${translateX}px, ${translateY}px, 0)`
  }

  const options = {
    margin,
    background,
    scrollOffset,
    metaClick
  }

  // If the selector is omitted, it represents the options
  if (selector instanceof Object) {
    Object.assign(options, selector)
  }

  const images = selectImages(selector)
  const overlay = createOverlay(options.background)

  let target = {
    template: null,
    zoomed: null
  }
  let scrollTop = 0
  let isAnimating = false

  images.forEach(elem => {
    elem.classList.add('medium-zoom-image')
    elem.addEventListener('click', onClick)
  })
  overlay.addEventListener('click', zoomOut)
  document.addEventListener('scroll', onScroll)
  document.addEventListener('keyup', onDismiss)
  window.addEventListener('resize', zoomOut)

  return {
    show: triggerZoom,
    hide: zoomOut,
    toggle: triggerZoom,
    update,
    addEventListeners,
    detach,
    images,
    options
  }
}

module.exports = mediumZoom
<commit_msg>Add polyfills for scroll positions<commit_after>const SUPPORTED_FORMATS = ['IMG']
const KEY_ESC = 27
const KEY_Q = 81
const CANCEL_KEYS = [KEY_ESC, KEY_Q]

const isSupported = img =>
  SUPPORTED_FORMATS.includes(img.tagName)

const isScaled = img =>
  img.naturalWidth !== img.width

const isListOrCollection = selector =>
  NodeList.prototype.isPrototypeOf(selector) ||
  HTMLCollection.prototype.isPrototypeOf(selector)

const isNode = selector =>
  (selector && selector.nodeType === 1)

/**
 * Attaches a zoom effect on a selection of images.
 *
 * @param {(string|Object[])} [selector] The images to attach the zoom to
 * @param {number} [options.margin=0] The space outside the zoomed image
 * @param {string} [options.background="#fff"] The color of the overlay
 * @param {number} [options.scrollOffset=48] The number of pixels to scroll to dismiss the zoom
 * @param {boolean} [options.metaClick=true] A boolean to enable the default action on meta click
 * @return The zoom object
 */
const mediumZoom = (selector, {
  margin = 0,
  background = '#fff',
  scrollOffset = 48,
  metaClick = true
} = {}) => {
  const selectImages = selector => {
    try {
      return Array.isArray(selector)
        ? selector.filter(isSupported)
        : isListOrCollection(selector)
          ? [...selector].filter(isSupported)
          : isNode(selector)
            ? [selector].filter(isSupported)
            : typeof selector === 'string'
              ? [...document.querySelectorAll(selector)].filter(isSupported)
              : [...document.querySelectorAll(
                  SUPPORTED_FORMATS.map(attr => attr.toLowerCase()).join(',')
                )].filter(isScaled)
    } catch (err) {
      throw new TypeError(
        'The provided selector is invalid.\n' +
        'Expects a CSS selector, a Node element, a NodeList, an HTMLCollection or an array.\n' +
        'See: https://github.com/francoischalifour/medium-zoom'
      )
    }
  }

  const createOverlay = background => {
    const overlay = document.createElement('div')
    overlay.classList.add('medium-zoom-overlay')
    overlay.style.backgroundColor = background

    return overlay
  }

  const cloneTarget = template => {
    const { top, left, width } = template.getBoundingClientRect()
    const clone = template.cloneNode()
    const scrollTop = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0
    const scrollLeft = window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft || 0

    clone.removeAttribute('id')
    clone.style.position = 'absolute'
    clone.style.top = `${top + scrollTop}px`
    clone.style.left = `${left + scrollLeft}px`
    clone.style.width = `${width}px`

    return clone
  }

  const zoom = () => {
    if (!target.template) return

    target.template.dispatchEvent(new Event('show'))

    scrollTop = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0
    isAnimating = true
    target.zoomed = cloneTarget(target.template)

    document.body.appendChild(overlay)
    document.body.appendChild(target.zoomed)

    requestAnimationFrame(() => {
      document.body.classList.add('medium-zoom--open')
    })

    target.template.style.visibility = 'hidden'
    target.zoomed.classList.add('medium-zoom-image--open')

    target.zoomed.addEventListener('click', zoomOut)
    target.zoomed.addEventListener('transitionend', onZoomEnd)

    animateTarget()
  }

  const zoomOut = (timeout = 0) => {
    const doZoomOut = () => {
      if (isAnimating || !target.template) return

      target.template.dispatchEvent(new Event('hide'))

      isAnimating = true
      document.body.classList.remove('medium-zoom--open')
      target.zoomed.style.transform = ''

      target.zoomed.removeEventListener('click', zoomOut)
      target.zoomed.addEventListener('transitionend', onZoomOutEnd)
    }

    (timeout > 0) ? setTimeout(doZoomOut, timeout) : doZoomOut()
  }

  const triggerZoom = event => {
    if (!target.template) {
      target.template = event ? event.target : images[0]
      zoom()
    } else {
      zoomOut()
    }
  }

  const update = (newOptions = {}) => {
    newOptions.background &&
      (overlay.style.backgroundColor = newOptions.background)

    return Object.assign(options, newOptions)
  }

  const addEventListeners = (type, listener) => {
    images.forEach(image => {
      image.addEventListener(type, listener)
    })
  }

  const detach = () => {
    const doDetach = () => {
      const event = new Event('detach')

      images.forEach(image => {
        image.classList.remove('medium-zoom-image')
        image.removeEventListener('click', onClick)
        image.dispatchEvent(event)
      })

      images.splice(0, images.length)

      target.zoomed &&
        target.zoomed.removeEventListener('transitionend', doDetach)
    }

    if (!target.zoomed) {
      doDetach()
    } else {
      zoomOut()
      target.zoomed.addEventListener('transitionend', doDetach)
    }
  }

  const onClick = event => {
    if (event.metaKey || event.ctrlKey) {
      if (options.metaClick) {
        return window.open(
          (event.target.getAttribute('data-original') ||
          event.target.parentNode.href ||
          event.target.src),
          '_blank')
      }
    }

    event.preventDefault()

    triggerZoom(event)
  }

  const onZoomEnd = () => {
    isAnimating = false
    target.zoomed.removeEventListener('transitionend', onZoomEnd)

    target.template.dispatchEvent(new Event('shown'))
  }

  const onZoomOutEnd = () => {
    if (!target.template) return

    target.template.style.visibility = ''
    document.body.removeChild(target.zoomed)
    document.body.removeChild(overlay)
    target.zoomed.classList.remove('medium-zoom-image--open')

    isAnimating = false
    target.zoomed.removeEventListener('transitionend', onZoomOutEnd)

    target.template.dispatchEvent(new Event('hidden'))

    target.template = null
    target.zoomed = null
  }

  const onScroll = () => {
    if (isAnimating || !target.template) return

    const currentScroll = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0

    if (Math.abs(scrollTop - currentScroll) > options.scrollOffset) {
      zoomOut(150)
    }
  }

  const onDismiss = event => {
    if (CANCEL_KEYS.includes(event.keyCode || event.which)) {
      zoomOut()
    }
  }

  const animateTarget = () => {
    if (!target.template) return

    const windowWidth = window.innerWidth
    const windowHeight = window.innerHeight

    const viewportWidth = windowWidth - (options.margin * 2)
    const viewportHeight = windowHeight - (options.margin * 2)

    const { naturalWidth = +Infinity, naturalHeight = +Infinity } = target.template
    const { top, left, width, height } = target.template.getBoundingClientRect()

    const scaleX = Math.min(naturalWidth, viewportWidth) / width
    const scaleY = Math.min(naturalHeight, viewportHeight) / height
    const scale = Math.min(scaleX, scaleY) || 1
    const translateX = (-left + ((viewportWidth - width) / 2) + options.margin) / scale
    const translateY = (-top + ((viewportHeight - height) / 2) + options.margin) / scale

    target.zoomed.style.transform = `scale(${scale}) translate3d(${translateX}px, ${translateY}px, 0)`
  }

  const options = {
    margin,
    background,
    scrollOffset,
    metaClick
  }

  // If the selector is omitted, it represents the options
  if (selector instanceof Object) {
    Object.assign(options, selector)
  }

  const images = selectImages(selector)
  const overlay = createOverlay(options.background)

  let target = {
    template: null,
    zoomed: null
  }
  let scrollTop = 0
  let isAnimating = false

  images.forEach(elem => {
    elem.classList.add('medium-zoom-image')
    elem.addEventListener('click', onClick)
  })
  overlay.addEventListener('click', zoomOut)
  document.addEventListener('scroll', onScroll)
  document.addEventListener('keyup', onDismiss)
  window.addEventListener('resize', zoomOut)

  return {
    show: triggerZoom,
    hide: zoomOut,
    toggle: triggerZoom,
    update,
    addEventListeners,
    detach,
    images,
    options
  }
}

module.exports = mediumZoom
<|endoftext|>


<commit_before>// @flow

import uniqueId from 'lodash/uniqueId'
import CheckStudentWorker from './check-student.worker'
import {type ParsedHansonFile} from '@gob/hanson-format'
import {type EvaluationResult} from '@gob/examine-student'
import {Student} from '@gob/object-student'
import {getOnlyCourse} from '../helpers/get-courses'
import mem from 'mem'
import QuickLRU from 'quick-lru'

const worker = new CheckStudentWorker()

worker.addEventListener('error', function(event: Event) {
	console.warn('received error from check-student worker:', event)
})

// Checks a student object against an area of study.
async function checkStudentAgainstArea(
	student: Student,
	area: ParsedHansonFile,
): Promise<EvaluationResult> {
	return new Promise(async resolve => {
		const sourceId = uniqueId()

		// This is inside of the function so that it doesn't get unregistered too early
		function onMessage({data: messageData}) {
			const {id: resultId, type, data} = JSON.parse(messageData)

			if (resultId === sourceId) {
				worker.removeEventListener('message', onMessage)

				if (type === 'result') {
					resolve(data)
				} else if (type === 'error') {
					resolve({
						computed: false,
						details: null,
						error: data.message,
						progress: {at: 0, of: 1},
					})
				}
			}
		}

		worker.addEventListener('message', onMessage)

		/* why stringify? from https://code.google.com/p/chromium/issues/detail?id=536620#c11:
		 * > We know that serialization/deserialization is slow. It's actually faster to
		 * > JSON.stringify() then postMessage() a string than to postMessage() an object. :(
		 */
		let courses = await student.activeCourses(getOnlyCourse)
		let {fulfillments, overrides, name} = student
		let msg = JSON.stringify({
			id: sourceId,
			area,
			courses,
			fulfillments,
			overrides,
			name,
		})
		worker.postMessage(msg)
	})
}

const memoized = mem(checkStudentAgainstArea, {
	cache: new QuickLRU({maxSize: 8}),
	cacheKey: (student: Student, area: ParsedHansonFile) =>
		JSON.stringify([student.hashCode(), area]),
	maxAge: 60000,
})

export {memoized as checkStudentAgainstArea}
<commit_msg>remove call to hashCode from checkStudent<commit_after>// @flow

import uniqueId from 'lodash/uniqueId'
import CheckStudentWorker from './check-student.worker'
import {type ParsedHansonFile} from '@gob/hanson-format'
import {type EvaluationResult} from '@gob/examine-student'
import {Student} from '@gob/object-student'
import {getOnlyCourse} from '../helpers/get-courses'
import mem from 'mem'
import QuickLRU from 'quick-lru'

const worker = new CheckStudentWorker()

worker.addEventListener('error', function(event: Event) {
	console.warn('received error from check-student worker:', event)
})

// Checks a student object against an area of study.
async function checkStudentAgainstArea(
	student: Student,
	area: ParsedHansonFile,
): Promise<EvaluationResult> {
	return new Promise(async resolve => {
		const sourceId = uniqueId()

		// This is inside of the function so that it doesn't get unregistered too early
		function onMessage({data: messageData}) {
			const {id: resultId, type, data} = JSON.parse(messageData)

			if (resultId === sourceId) {
				worker.removeEventListener('message', onMessage)

				if (type === 'result') {
					resolve(data)
				} else if (type === 'error') {
					resolve({
						computed: false,
						details: null,
						error: data.message,
						progress: {at: 0, of: 1},
					})
				}
			}
		}

		worker.addEventListener('message', onMessage)

		/* why stringify? from https://code.google.com/p/chromium/issues/detail?id=536620#c11:
		 * > We know that serialization/deserialization is slow. It's actually faster to
		 * > JSON.stringify() then postMessage() a string than to postMessage() an object. :(
		 */
		let courses = await student.activeCourses(getOnlyCourse)
		let {fulfillments, overrides, name} = student
		let msg = JSON.stringify({
			id: sourceId,
			area,
			courses,
			fulfillments,
			overrides,
			name,
		})
		worker.postMessage(msg)
	})
}

const memoized = mem(checkStudentAgainstArea, {
	cache: new QuickLRU({maxSize: 8}),
	cacheKey: (student: Student, area: ParsedHansonFile) =>
		JSON.stringify([student, area]),
	maxAge: 60000,
})

export {memoized as checkStudentAgainstArea}
<|endoftext|>


<commit_before>/**
* Adds captions support
*/
( function( mw, $ ) {

	mw.KTimedText = function( embedPlayer, kalturaConfig, callback ){
		return this.init( embedPlayer, kalturaConfig, callback );
	};
	mw.KTimedText.prototype = {
		init: function( embedPlayer, kalturaConfig, callback ){
			var _this = this;
			// Override embedPlayer hasTextTracks:
			embedPlayer.hasTextTracks = function(){
				return true
			};
			// Set the KTimedText plugin configuration
			_this.kVars = kalturaConfig;

			// Check for kaltura plugin representation of offset:
			if( _this.kVars.timeOffset ){
				this.timeOffset = _this.kVars.timeOffset;
			}

			// Inherit the timed text support via the base TimedText module:
			var baseTimedText = new mw.TimedText( embedPlayer, kalturaConfig );
			for( var i in _this ){
				if( baseTimedText[ i ] ){
					baseTimedText[ 'parent_' + i] = baseTimedText[i];
				}
				baseTimedText[i] = _this[i];
			}
			return baseTimedText;

			// Trigger changed caption
			$( embedPlayer ).bind( 'TimedText_ChangeSource', function() {
				$( embedPlayer ).trigger('changedClosedCaptions');
			});
		},

		getKalturaClient: function(){
			if( ! this.kClient ){
				this.kClient = mw.kApiGetPartnerClient( this.embedPlayer.kwidgetid );
			}
			return this.kClient;
		},
		/**
		 * Load the list of captions sources from the kaltura api, or from plugin config
		 */
		loadTextSources: function( callback ) {
			var _this = this;
			// Check if text sources are already loaded ( not null )
			if( this.textSources !== null ){
				callback();
				return ;
			}
			// init timedText sources:
			this.textSources = [];

			// Check for kaltura ccUrl style text tracks ( not eagle api )
			if( this.kVars.ccUrl ){
				// Set up a single source from the custom vars:
				$.each( _this.getTextSourceFromVars( _this.kVars ), function(inx, textSource ){
					_this.textSources.push( textSource);
				});
			}

			// Api sources require that a api query
			_this.getKalturaClient().getKS( function( ks ) {
				_this.ksCache = ks;
				_this.getTextSourcesFromApi( function( dbTextSources ){
					$.each( dbTextSources, function( inx, dbTextSource ){
						_this.textSources.push(
							_this.getTextSourceFromDB( dbTextSource )
						);
						$( _this.embedPlayer ).trigger('KalturaSupport_newClosedCaptionsData');
					});
					$( _this.embedPlayer ).trigger('KalturaSupport_ccDataLoaded');
					// Done adding source issue callback
					callback();
				});
			});
		},
		/**
		 * Get the text sources from the api:
		 */
		getTextSourcesFromApi: function( callback ){
			var _this = this;
			this.getKalturaClient().doRequest( {
				'service' : 'caption_captionasset',
				'action' : 'list',
				'filter:objectType' : 'KalturaAssetFilter',
				'filter:entryIdEqual' : _this.embedPlayer.kentryid,
				'filter:statusEqual' : 2
			}, function( data ) {
				mw.log( "KTimedText:: sources loaded: " + data.totalCount);
				// TODO is this needed does the api not return an empty set?
				if( data.totalCount > 0 ) {
					callback( data.objects );
				} else {
					callback( [] );
				}
			});
		},
		getTextContentType: function( type ){
			switch( type ){
				case 'srt':
					return 'text/x-srt';
					break;
				case 'tt':
					return 'text/xml';
					break;
			}
		},
		getTextSourceFromVars: function( kalturaTextVars ){
			var _this = this;
			var type = this.embedPlayer.evaluate( kalturaTextVars.type );
			var ccUrl = this.embedPlayer.evaluate( kalturaTextVars.ccUrl );
			if( !type || !ccUrl ){
				mw.log("Error: KTimedText error missing type or text source from custom vars");
				return [];
			}
			var embedSource = this.embedPlayer.mediaElement.tryAddSource(
				$( '<track />' ).attr({
					'kind'		: 'subtitles',
					'label'		: 'English',
					'srclang' 	: 'en',
					'fileExt'	: type,
					'type'		: this.getTextContentType( type ),
					'src'		: ccUrl
				}).get(0)
				);
			// Return a "textSource" object:
			return [ new mw.TextSource( embedSource ) ];
		},

		/**
		 * Gets a text source we can use the application from a database textSource
		 * @param {Object} textSource
		 */
		getTextSourceFromDB: function( dbTextSource ){
			var _this = this;
			// Try to insert the track source:
			var embedSource = this.embedPlayer.mediaElement.tryAddSource(
				$( '<track />' ).attr({
					'kind'		: 'subtitles',
					'language'	: dbTextSource.language,
					'srclang' 	: dbTextSource.languageCode,
					'label'		: dbTextSource.label,
					'id'		: dbTextSource.id,
					'fileExt'	: dbTextSource.fileExt,
					'src'		: _this.getCaptionUrl( dbTextSource.id, dbTextSource.fileExt ),
					'title'		: dbTextSource.label,
					'default'	: dbTextSource.isDefault
				}).get(0)
				);
			// Return a "textSource" object:
			return new mw.TextSource( embedSource );
		},

		/**
		* Returns the caption serve url
		* @param {String} captionId - caption asset id
		* @param {String} type - caption asset type
		*/
		getCaptionUrl: function( captionId, type ){
			// Sample Url for Caption serve
			// http://www.kaltura.com/api_v3/index.php?service=caption_captionasset&action=serve&captionAssetId=@ID@&ks=@KS@
			var params = {
				'action': 'serve',
				'captionAssetId': captionId,
				'ks': this.ksCache
			};
			var baseUrl = mw.getConfig('Kaltura.ServiceUrl') + mw.getConfig('Kaltura.ServiceBase').replace('index.php', '');
			return baseUrl + 'caption_captionasset&' + $.param( params ) + '&.' + type;
		}
	};

} )( window.mw, jQuery );<commit_msg>added kalsig to caption serve url!!! (very important, caused lots of issues)<commit_after>/**
* Adds captions support
*/
( function( mw, $ ) {

	mw.KTimedText = function( embedPlayer, kalturaConfig, callback ){
		return this.init( embedPlayer, kalturaConfig, callback );
	};
	mw.KTimedText.prototype = {
		init: function( embedPlayer, kalturaConfig, callback ){
			var _this = this;
			// Override embedPlayer hasTextTracks:
			embedPlayer.hasTextTracks = function(){
				return true
			};
			// Set the KTimedText plugin configuration
			_this.kVars = kalturaConfig;

			// Check for kaltura plugin representation of offset:
			if( _this.kVars.timeOffset ){
				_this.timeOffset = _this.kVars.timeOffset;
			}

			// Inherit the timed text support via the base TimedText module:
			var baseTimedText = new mw.TimedText( embedPlayer, kalturaConfig );
			for( var i in _this ){
				if( baseTimedText[ i ] ){
					baseTimedText[ 'parent_' + i] = baseTimedText[i];
				}
				baseTimedText[i] = _this[i];
			}
			return baseTimedText;

			// Trigger changed caption
			$( embedPlayer ).bind( 'TimedText_ChangeSource', function() {
				$( embedPlayer ).trigger('changedClosedCaptions');
			});
		},

		getKalturaClient: function(){
			if( ! this.kClient ){
				this.kClient = mw.kApiGetPartnerClient( this.embedPlayer.kwidgetid );
			}
			return this.kClient;
		},
		/**
		 * Load the list of captions sources from the kaltura api, or from plugin config
		 */
		loadTextSources: function( callback ) {
			var _this = this;
			// Check if text sources are already loaded ( not null )
			if( this.textSources !== null ){
				callback();
				return ;
			}
			// init timedText sources:
			this.textSources = [];

			// Check for kaltura ccUrl style text tracks ( not eagle api )
			if( this.kVars.ccUrl ){
				// Set up a single source from the custom vars:
				$.each( _this.getTextSourceFromVars( _this.kVars ), function(inx, textSource ){
					_this.textSources.push( textSource);
				});
			}

			// Api sources require that a api query
			_this.getKalturaClient().getKS( function( ks ) {
				_this.ksCache = ks;
				_this.getTextSourcesFromApi( function( dbTextSources ){
					$.each( dbTextSources, function( inx, dbTextSource ){
						_this.textSources.push(
							_this.getTextSourceFromDB( dbTextSource )
						);
						$( _this.embedPlayer ).trigger('KalturaSupport_newClosedCaptionsData');
					});
					$( _this.embedPlayer ).trigger('KalturaSupport_ccDataLoaded');
					// Done adding source issue callback
					callback();
				});
			});
		},
		/**
		 * Get the text sources from the api:
		 */
		getTextSourcesFromApi: function( callback ){
			var _this = this;
			this.getKalturaClient().doRequest( {
				'service' : 'caption_captionasset',
				'action' : 'list',
				'filter:objectType' : 'KalturaAssetFilter',
				'filter:entryIdEqual' : _this.embedPlayer.kentryid,
				'filter:statusEqual' : 2
			}, function( data ) {
				mw.log( "KTimedText:: sources loaded: " + data.totalCount);
				// TODO is this needed does the api not return an empty set?
				if( data.totalCount > 0 ) {
					callback( data.objects );
				} else {
					callback( [] );
				}
			});
		},
		getTextContentType: function( type ){
			switch( type ){
				case 'srt':
					return 'text/x-srt';
					break;
				case 'tt':
					return 'text/xml';
					break;
			}
		},
		getTextSourceFromVars: function( kalturaTextVars ){
			var _this = this;
			var type = this.embedPlayer.evaluate( kalturaTextVars.type );
			var ccUrl = this.embedPlayer.evaluate( kalturaTextVars.ccUrl );
			if( !type || !ccUrl ){
				mw.log("Error: KTimedText error missing type or text source from custom vars");
				return [];
			}
			var embedSource = this.embedPlayer.mediaElement.tryAddSource(
				$( '<track />' ).attr({
					'kind'		: 'subtitles',
					'label'		: 'English',
					'srclang' 	: 'en',
					'fileExt'	: type,
					'type'		: this.getTextContentType( type ),
					'src'		: ccUrl
				}).get(0)
				);
			// Return a "textSource" object:
			return [ new mw.TextSource( embedSource ) ];
		},

		/**
		 * Gets a text source we can use the application from a database textSource
		 * @param {Object} textSource
		 */
		getTextSourceFromDB: function( dbTextSource ){
			var _this = this;
			// Try to insert the track source:
			var embedSource = this.embedPlayer.mediaElement.tryAddSource(
				$( '<track />' ).attr({
					'kind'		: 'subtitles',
					'language'	: dbTextSource.language,
					'srclang' 	: dbTextSource.languageCode,
					'label'		: dbTextSource.label,
					'id'		: dbTextSource.id,
					'fileExt'	: dbTextSource.fileExt,
					'src'		: _this.getCaptionUrl( dbTextSource.id, dbTextSource.fileExt ),
					'title'		: dbTextSource.label,
					'default'	: dbTextSource.isDefault
				}).get(0)
				);
			// Return a "textSource" object:
			return new mw.TextSource( embedSource );
		},

		/**
		* Returns the caption serve url
		* @param {String} captionId - caption asset id
		* @param {String} type - caption asset type
		*/
		getCaptionUrl: function( captionId, type ){
			// Sample Url for Caption serve
			// http://www.kaltura.com/api_v3/index.php?service=caption_captionasset&action=serve&captionAssetId=@ID@&ks=@KS@
			var params = {
				'action': 'serve',
				'captionAssetId': captionId,
				'ks': this.ksCache
			};
			var kalsig = this.getKalturaClient().getSignature( params );
			var baseUrl = mw.getConfig('Kaltura.ServiceUrl') + mw.getConfig('Kaltura.ServiceBase').replace('index.php', '');
			return baseUrl + 'caption_captionasset&' + $.param( params ) + '&kalsig=' + kalsig + '&.' + type;
		}
	};

} )( window.mw, jQuery );<|endoftext|>


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

    var pageNames = {
            basic:     'Basic',
            overview:  'Overview',
            posts:     'Posts',
            pages:     'Pages',
            comments:  'Comments',
            settings:  'Settings',
            all:       'All',
            scheduled: 'Scheduled',
            draft:     'Draft',
            live:      'Published',
            users:     'Users',
            themes:    'Themes',
            redirects: 'Redirects',
            'new':     'New',
            ips:       'IP Restrictions',
            images:    'Images',
            plugins:   'Plugins'
        },
        status = {
            'D': 'Draft',
            'S': 'Scheduled',
            'A': 'Live'
        };

    angular.module('panda', ['ngRoute', 'ngResource', 'panda.controllers', 'panda.utils', 'angularFileUpload', 'ui.bootstrap']).
        config(['$routeProvider', function ($routeProvider) {
            $routeProvider
                .when('/', { templateUrl: 'overview', controller: 'OverviewCtrl' })
                .when('/posts', { redirectTo: '/posts/all' })
                .when('/posts/new', { templateUrl: 'editpost', controller: 'NewPostCtrl' })
                .when('/posts/:type?', { templateUrl: 'posts', controller: 'PostsCtrl' })
                .when('/pages', { redirectTo: '/pages/all' })
                .when('/pages/:type?', { templateUrl: 'posts', controller: 'PagesCtrl' })
                .when('/posts/:id/edit', { templateUrl: 'editpost', controller: 'PostEditCtrl' })
                .when('/comments', { templateUrl: 'comments', controller: 'CommentsCtrl' })
                .when('/settings', { redirectTo: '/settings/basic' })
                .when('/settings/basic', { templateUrl: 'settings', controller: 'SettingsCtrl' })
                .when('/settings/ips', { templateUrl: 'ips', controller: 'IpsCtrl' })
                .when('/settings/redirects', { templateUrl: 'redirects' })
                .when('/users', { templateUrl: 'users', controller: 'UsersCtrl' })
                .when('/plugins', { templateUrl: 'plugins', controller: 'PluginsCtrl' })
                .when('/themes', { templateUrl: 'themes', controller: 'ThemesCtrl' })
                .when('/images', { templateUrl: 'images', controller: 'ImgCtrl' })
                .otherwise({ redirectTo: '/' });

            NProgress.configure({ speed: 200, trickleRate: 0.05, trickleSpeed: 250  });
        }])
        .factory('Users', ['$resource', function($resource) {
            return $resource('/api/v1/user/:type', {type: '@type'},
                {
                    update:         { method: 'PUT', params: { type: 'basic' } },
                    updatePassword: { method: 'PUT', params: { type: 'password' } }
                }
            );
        }])
        .factory('Settings', ['$resource', function ($resource) {
            return $resource('/api/v1/settings/:id', { id:'@id' });
        }])
        .factory('Ips', ['$resource', function ($resource) {
            return $resource('/api/v1/ips/:type', { type:'@type' });
        }])
        .factory('Posts', ['$resource', function ($resource) {
            return $resource('/api/v1/posts/:id',
                { id: '@id' },
                { create: { method: 'POST' }, update: { method: 'PUT' } }
            );
        }])
        .factory('Redirects', ['$resource', function ($resource) {
            return $resource('/api/v1/redirects/:id',
                { id: '@id' },
                { create: { method: 'POST' }, update: { method: 'PUT' } }
            );
        }])
        .factory('PostsInfo', ['$resource', function ($resource) {
            return $resource('/api/v1/posts/infos/:id', {id: '@id'});
        }])
        .factory('Labels', ['$resource', function ($resource) {
            return $resource('/api/v1/labels');
        }])
        .factory('Images', ['$resource', function ($resource) {
            return $resource('/api/v1/images');
        }])
        .factory('Plugins', ['$resource', function ($resource) {
            return $resource('/api/v1/plugins/:id',
                {id: '@id'},
                {
                    start: { method: 'POST', params: { op: 'start' } },
                    stop: { method: 'POST', params: { op: 'stop' } }
                }
            );
        }])
        .factory('Themes', ['$resource', function ($resource) {
            return $resource('/api/v1/themes/:type/:id',
                { id: '@id' },
                { update: { method: 'PUT', params: { type: '@type'} } }
            );
        }])
        .factory('MarkdownConverter', [function () {
            marked.setOptions({
                renderer:    new marked.Renderer(),
                gfm:         true,
                tables:      true,
                breaks:      false,
                pedantic:    false,
                sanitize:    true,
                smartLists:  true,
                smartypants: false
            });
            return {
                makeHtml: function (value) {
                    return marked(value);
                }
            };
        }])
        .filter('moment', [function () {
            return function (date, format) {
                return moment(date).format(format || 'L');
            };
        }])
        .filter('startFrom', [function () {
            return function (input, start) {
                return input ? input.slice(+start) : input;
            };
        }])
        .filter('status', [function () {
            return function (post) {
                if (!post.published) return 'D';

                var date = post.publishedAt;
                if (!angular.isDate(date)) {
                    date = new Date(date);
                }
                if (date > _.now()) { // Convert string to date
                    return 'S';
                }
                return 'A';
            };
        }])
        .filter('statusText', ['statusFilter', function(statusFilter){
            return function(post) {
                return status[statusFilter(post)];
            };
        }])
        .filter('name', [function(){
            return function (name) {
                return pageNames[name];
            };
        }])
        .directive('ngEnter', function() {
            return function(scope, element, attrs) {
                element.bind("keydown keypress", function(event) {
                    if(event.which === 13) {
                        scope.$apply(function(){
                            scope.$eval(attrs.ngEnter, {'event': event});
                        });
                        event.preventDefault();
                    }
                });
            };
        })
        .directive('ngKeyAction', function() {
            return function(scope, element, attrs) {
                element.bind("keydown keypress", function(event) {
                    var actions = scope.$eval(attrs.ngKeyAction);
                    if (event.which in actions) {
                        scope.$apply(function(){
                            scope.$eval(attrs[actions[event.which]], {'event': event});
                        });
                        event.preventDefault();
                    }
                });
            };
        })
        .value('Constants', {
            status: status,
            pageNames: pageNames
        });
}());<commit_msg>Unsafe filter added<commit_after>(function () {
    'use strict';

    var pageNames = {
            basic:     'Basic',
            overview:  'Overview',
            posts:     'Posts',
            pages:     'Pages',
            comments:  'Comments',
            settings:  'Settings',
            all:       'All',
            scheduled: 'Scheduled',
            draft:     'Draft',
            live:      'Published',
            users:     'Users',
            themes:    'Themes',
            redirects: 'Redirects',
            'new':     'New',
            ips:       'IP Restrictions',
            images:    'Images',
            plugins:   'Plugins'
        },
        status = {
            'D': 'Draft',
            'S': 'Scheduled',
            'A': 'Live'
        };

    angular.module('panda', ['ngRoute', 'ngResource', 'panda.controllers', 'panda.utils', 'angularFileUpload', 'ui.bootstrap']).
        config(['$routeProvider', function ($routeProvider) {
            $routeProvider
                .when('/', { templateUrl: 'overview', controller: 'OverviewCtrl' })
                .when('/posts', { redirectTo: '/posts/all' })
                .when('/posts/new', { templateUrl: 'editpost', controller: 'NewPostCtrl' })
                .when('/posts/:type?', { templateUrl: 'posts', controller: 'PostsCtrl' })
                .when('/pages', { redirectTo: '/pages/all' })
                .when('/pages/:type?', { templateUrl: 'posts', controller: 'PagesCtrl' })
                .when('/posts/:id/edit', { templateUrl: 'editpost', controller: 'PostEditCtrl' })
                .when('/comments', { templateUrl: 'comments', controller: 'CommentsCtrl' })
                .when('/settings', { redirectTo: '/settings/basic' })
                .when('/settings/basic', { templateUrl: 'settings', controller: 'SettingsCtrl' })
                .when('/settings/ips', { templateUrl: 'ips', controller: 'IpsCtrl' })
                .when('/settings/redirects', { templateUrl: 'redirects' })
                .when('/users', { templateUrl: 'users', controller: 'UsersCtrl' })
                .when('/plugins', { templateUrl: 'plugins', controller: 'PluginsCtrl' })
                .when('/themes', { templateUrl: 'themes', controller: 'ThemesCtrl' })
                .when('/images', { templateUrl: 'images', controller: 'ImgCtrl' })
                .otherwise({ redirectTo: '/' });

            NProgress.configure({ speed: 200, trickleRate: 0.05, trickleSpeed: 250  });
        }])
        .factory('Users', ['$resource', function($resource) {
            return $resource('/api/v1/user/:type', {type: '@type'},
                {
                    update:         { method: 'PUT', params: { type: 'basic' } },
                    updatePassword: { method: 'PUT', params: { type: 'password' } }
                }
            );
        }])
        .factory('Settings', ['$resource', function ($resource) {
            return $resource('/api/v1/settings/:id', { id:'@id' });
        }])
        .factory('Ips', ['$resource', function ($resource) {
            return $resource('/api/v1/ips/:type', { type:'@type' });
        }])
        .factory('Posts', ['$resource', function ($resource) {
            return $resource('/api/v1/posts/:id',
                { id: '@id' },
                { create: { method: 'POST' }, update: { method: 'PUT' } }
            );
        }])
        .factory('Redirects', ['$resource', function ($resource) {
            return $resource('/api/v1/redirects/:id',
                { id: '@id' },
                { create: { method: 'POST' }, update: { method: 'PUT' } }
            );
        }])
        .factory('PostsInfo', ['$resource', function ($resource) {
            return $resource('/api/v1/posts/infos/:id', {id: '@id'});
        }])
        .factory('Labels', ['$resource', function ($resource) {
            return $resource('/api/v1/labels');
        }])
        .factory('Images', ['$resource', function ($resource) {
            return $resource('/api/v1/images');
        }])
        .factory('Plugins', ['$resource', function ($resource) {
            return $resource('/api/v1/plugins/:id',
                {id: '@id'},
                {
                    start: { method: 'POST', params: { op: 'start' } },
                    stop: { method: 'POST', params: { op: 'stop' } }
                }
            );
        }])
        .factory('Themes', ['$resource', function ($resource) {
            return $resource('/api/v1/themes/:type/:id',
                { id: '@id' },
                { update: { method: 'PUT', params: { type: '@type'} } }
            );
        }])
        .factory('MarkdownConverter', [function () {
            marked.setOptions({
                renderer:    new marked.Renderer(),
                gfm:         true,
                tables:      true,
                breaks:      false,
                pedantic:    false,
                sanitize:    true,
                smartLists:  true,
                smartypants: false
            });
            return {
                makeHtml: function (value) {
                    return marked(value);
                }
            };
        }])
        .filter('moment', [function () {
            return function (date, format) {
                return moment(date).format(format || 'L');
            };
        }])
        .filter('startFrom', [function () {
            return function (input, start) {
                return input ? input.slice(+start) : input;
            };
        }])
        .filter('status', [function () {
            return function (post) {
                if (!post.published) return 'D';

                var date = post.publishedAt;
                if (!angular.isDate(date)) {
                    date = new Date(date);
                }
                if (date > _.now()) { // Convert string to date
                    return 'S';
                }
                return 'A';
            };
        }])
        .filter('statusText', ['statusFilter', function(statusFilter){
            return function(post) {
                return status[statusFilter(post)];
            };
        }])
        .filter('name', [function(){
            return function (name) {
                return pageNames[name];
            };
        }])
        .filter('unsafe', ['$sce', function ($sce) {
            return function (val) {
                return $sce.trustAsHtml(val);
            };
        }])
        .directive('ngEnter', function() {
            return function(scope, element, attrs) {
                element.bind("keydown keypress", function(event) {
                    if(event.which === 13) {
                        scope.$apply(function(){
                            scope.$eval(attrs.ngEnter, {'event': event});
                        });
                        event.preventDefault();
                    }
                });
            };
        })
        .directive('ngKeyAction', function() {
            return function(scope, element, attrs) {
                element.bind("keydown keypress", function(event) {
                    var actions = scope.$eval(attrs.ngKeyAction);
                    if (event.which in actions) {
                        scope.$apply(function(){
                            scope.$eval(attrs[actions[event.which]], {'event': event});
                        });
                        event.preventDefault();
                    }
                });
            };
        })
        .value('Constants', {
            status: status,
            pageNames: pageNames
        });
}());<|endoftext|>


<commit_before>import {PanelButtonContext} from './toolbar'
import {PanelWizardContext} from './panelWizard'
import {connect, select} from 'store'
import Icon from 'widget/icon'
import PropTypes from 'prop-types'
import React from 'react'
import actionBuilder from 'action-builder'
import styles from './panel.module.css'

const mapStateToProps = (state, ownProps) => {
    const {statePath} = ownProps
    return {
        initialized: select([statePath, 'initialized']),
        selectedPanel: select([statePath, 'selectedPanel'])
    }
}

class Panel extends React.Component {
    componentDidMount() {
        const {initialized, form, modalOnDirty = true} = this.props
        if (modalOnDirty) {
            this.setModal(!initialized)
            if (initialized) {
                form.onDirty(() => this.setModal(true))
                form.onClean(() => this.setModal(false))
            }
        }
    }

    setModal(modal) {
        const {statePath} = this.props
        actionBuilder('SET_MODAL', {modal})
            .set([statePath, 'modal'], modal)
            .dispatch()
    }

    selectPanel(panel) {
        const {statePath} = this.props
        actionBuilder('SELECT_PANEL', {panel})
            .set([statePath, 'selectedPanel'], panel)
            .dispatch()
    }

    setInitialized() {
        const {statePath} = this.props
        actionBuilder('SET_INITIALIZED')
            .set([statePath, 'initialized'], true)
            .dispatch()
    }

    closePanel() {
        this.setModal(false)
        this.selectPanel()
    }

    apply() {
        const {form, onApply} = this.props
        onApply(form && form.values())
    }

    ok() {
        const {form, isActionForm} = this.props
        if (form && (isActionForm || form.isDirty())) {
            this.apply()
            this.closePanel()
        } else {
            this.cancel()
        }
    }

    cancel() {
        const {onCancel} = this.props
        onCancel && onCancel()
        this.closePanel()
    }

    back(panel) {
        this.apply()
        this.selectPanel(panel)
    }

    next(panel) {
        this.apply()
        this.selectPanel(panel)
    }

    done() {
        this.apply()
        this.setInitialized()
        this.closePanel()
    }

    renderForm({form, onApply, top, bottom, left, right, center, inline, panelButtonPosition, className}, content) {
        const classNames = [
            styles.panel,
            top || (panelButtonPosition && panelButtonPosition.top) ? styles.top : null,
            bottom || (panelButtonPosition && panelButtonPosition.bottom) ? styles.bottom : null,
            right || (panelButtonPosition && panelButtonPosition.right) ? styles.right : null,
            left ? styles.bottom : null,
            center ? styles.center : null,
            inline || (panelButtonPosition && panelButtonPosition.inline) ? styles.inline : null,
            className
        ].join(' ')
        return form
            ? (
                <form
                    className={classNames}
                    onSubmit={e => {
                        e.preventDefault()
                        onApply && onApply(form && form.values())
                    }}>
                    {content}
                </form>
            )
            : (

{content}

            )
    }

    renderModal({modal}, content) {
        return modal
            ?
{content}

            : content
    }

    render() {
        const {form = false, isActionForm, initialized, onApply, top, bottom, left, right, center, inline, modal, className, children} = this.props
        const {selectedPanel} = this.props
        return (
            <PanelWizardContext.Consumer>
                {(panels = []) => {
                    const wizard = panels && !initialized
                    const selectedPanelIndex = panels.indexOf(selectedPanel)
                    const first = selectedPanelIndex === 0
                    const last = selectedPanelIndex === panels.length - 1
                    return (
                        <PanelButtonContext.Consumer>
                            {panelButtonPosition => (
                                <PanelContext.Provider value={{
                                    wizard,
                                    first,
                                    last,
                                    isActionForm: form && isActionForm,
                                    dirty: form && form.isDirty(),
                                    invalid: form && form.isInvalid(),
                                    onOk: () => this.ok(),
                                    onCancel: () => this.cancel(),
                                    onBack: () => !first && this.back(panels[selectedPanelIndex - 1]),
                                    onNext: () => !last && this.next(panels[selectedPanelIndex + 1]),
                                    onDone: () => this.done()
                                }}>
                                    {this.renderModal({modal},
                                        this.renderForm({form, onApply, top, bottom, left, right, center, inline, panelButtonPosition, className}, children)
                                    )}
                                </PanelContext.Provider>
                            )}
                        </PanelButtonContext.Consumer>
                    )}}
            </PanelWizardContext.Consumer>
        )
    }
}

export default connect(mapStateToProps)(Panel)

Panel.propTypes = {
    children: PropTypes.any.isRequired,
    statePath: PropTypes.string.isRequired,
    bottom: PropTypes.any,
    center: PropTypes.any,
    className: PropTypes.string,
    form: PropTypes.object,
    initialized: PropTypes.any,
    inline: PropTypes.any,
    isActionForm: PropTypes.any,
    left: PropTypes.any,
    modal: PropTypes.any,
    modalOnDirty: PropTypes.any,
    right: PropTypes.any,
    top: PropTypes.any,
    onApply: PropTypes.func,
    onCancel: PropTypes.func,
}

export const PanelHeader = ({icon, title, children}) =>

        {children
            ? children
            : <React.Fragment>
                <Icon name={icon}/>
                {title}
            </React.Fragment>
        }

PanelHeader.propTypes = {
    children: PropTypes.any,
    icon: PropTypes.string,
    title: PropTypes.string,
}

export const PanelContent = ({className, children}) =>

        {children}

PanelContent.propTypes = {
    children: PropTypes.any.isRequired,
    className: PropTypes.string,
}

export const PanelContext = React.createContext()
<commit_msg>Fixed erroneous activation of wizard mode.<commit_after>import {PanelButtonContext} from './toolbar'
import {PanelWizardContext} from './panelWizard'
import {connect, select} from 'store'
import Icon from 'widget/icon'
import PropTypes from 'prop-types'
import React from 'react'
import actionBuilder from 'action-builder'
import styles from './panel.module.css'

const mapStateToProps = (state, ownProps) => {
    const {statePath} = ownProps
    return {
        initialized: select([statePath, 'initialized']),
        selectedPanel: select([statePath, 'selectedPanel'])
    }
}

class Panel extends React.Component {
    componentDidMount() {
        const {initialized, form, modalOnDirty = true} = this.props
        if (modalOnDirty) {
            this.setModal(!initialized)
            if (initialized) {
                form.onDirty(() => this.setModal(true))
                form.onClean(() => this.setModal(false))
            }
        }
    }

    setModal(modal) {
        const {statePath} = this.props
        actionBuilder('SET_MODAL', {modal})
            .set([statePath, 'modal'], modal)
            .dispatch()
    }

    selectPanel(panel) {
        const {statePath} = this.props
        actionBuilder('SELECT_PANEL', {panel})
            .set([statePath, 'selectedPanel'], panel)
            .dispatch()
    }

    setInitialized() {
        const {statePath} = this.props
        actionBuilder('SET_INITIALIZED')
            .set([statePath, 'initialized'], true)
            .dispatch()
    }

    closePanel() {
        this.setModal(false)
        this.selectPanel()
    }

    apply() {
        const {form, onApply} = this.props
        onApply(form && form.values())
    }

    ok() {
        const {form, isActionForm} = this.props
        if (form && (isActionForm || form.isDirty())) {
            this.apply()
            this.closePanel()
        } else {
            this.cancel()
        }
    }

    cancel() {
        const {onCancel} = this.props
        onCancel && onCancel()
        this.closePanel()
    }

    back(panel) {
        this.apply()
        this.selectPanel(panel)
    }

    next(panel) {
        this.apply()
        this.selectPanel(panel)
    }

    done() {
        this.apply()
        this.setInitialized()
        this.closePanel()
    }

    renderForm({form, onApply, top, bottom, left, right, center, inline, panelButtonPosition, className}, content) {
        const classNames = [
            styles.panel,
            top || (panelButtonPosition && panelButtonPosition.top) ? styles.top : null,
            bottom || (panelButtonPosition && panelButtonPosition.bottom) ? styles.bottom : null,
            right || (panelButtonPosition && panelButtonPosition.right) ? styles.right : null,
            left ? styles.bottom : null,
            center ? styles.center : null,
            inline || (panelButtonPosition && panelButtonPosition.inline) ? styles.inline : null,
            className
        ].join(' ')
        return form
            ? (
                <form
                    className={classNames}
                    onSubmit={e => {
                        e.preventDefault()
                        onApply && onApply(form && form.values())
                    }}>
                    {content}
                </form>
            )
            : (

{content}

            )
    }

    renderModal({modal}, content) {
        return modal
            ?
{content}

            : content
    }

    render() {
        const {form = false, isActionForm, initialized, onApply, top, bottom, left, right, center, inline, modal, className, children} = this.props
        const {selectedPanel} = this.props
        return (
            <PanelWizardContext.Consumer>
                {(panels = []) => {
                    console.log({panels, initialized})
                    const wizard = panels.length && !initialized
                    const selectedPanelIndex = panels.indexOf(selectedPanel)
                    const first = selectedPanelIndex === 0
                    const last = selectedPanelIndex === panels.length - 1
                    return (
                        <PanelButtonContext.Consumer>
                            {panelButtonPosition => (
                                <PanelContext.Provider value={{
                                    wizard,
                                    first,
                                    last,
                                    isActionForm: form && isActionForm,
                                    dirty: form && form.isDirty(),
                                    invalid: form && form.isInvalid(),
                                    onOk: () => this.ok(),
                                    onCancel: () => this.cancel(),
                                    onBack: () => !first && this.back(panels[selectedPanelIndex - 1]),
                                    onNext: () => !last && this.next(panels[selectedPanelIndex + 1]),
                                    onDone: () => this.done()
                                }}>
                                    {this.renderModal({modal},
                                        this.renderForm({form, onApply, top, bottom, left, right, center, inline, panelButtonPosition, className}, children)
                                    )}
                                </PanelContext.Provider>
                            )}
                        </PanelButtonContext.Consumer>
                    )}}
            </PanelWizardContext.Consumer>
        )
    }
}

export default connect(mapStateToProps)(Panel)

Panel.propTypes = {
    children: PropTypes.any.isRequired,
    statePath: PropTypes.string.isRequired,
    bottom: PropTypes.any,
    center: PropTypes.any,
    className: PropTypes.string,
    form: PropTypes.object,
    initialized: PropTypes.any,
    inline: PropTypes.any,
    isActionForm: PropTypes.any,
    left: PropTypes.any,
    modal: PropTypes.any,
    modalOnDirty: PropTypes.any,
    right: PropTypes.any,
    top: PropTypes.any,
    onApply: PropTypes.func,
    onCancel: PropTypes.func,
}

export const PanelHeader = ({icon, title, children}) =>

        {children
            ? children
            : <React.Fragment>
                <Icon name={icon}/>
                {title}
            </React.Fragment>
        }

PanelHeader.propTypes = {
    children: PropTypes.any,
    icon: PropTypes.string,
    title: PropTypes.string,
}

export const PanelContent = ({className, children}) =>

        {children}

PanelContent.propTypes = {
    children: PropTypes.any.isRequired,
    className: PropTypes.string,
}

export const PanelContext = React.createContext()
<|endoftext|>


<commit_before>define([
    'dom-tool'
  , 'socket.io'
  , 'Controller'
  , 'Report'
  , 'CollectionController'
  , 'StatusController'
  , 'CollectionReport'
  , 'DashboardController'
], function(
    dom
  , socketio
  , Controller
  , Report
  , CollectionController
  , StatusController
  , CollectionReport
  , DashboardController
) {
    "use strict";
    /*global document, window, FileReader*/

    function makeFileInput(fileOnLoad, element) {
        var hiddenFileInput = dom.createChildElement(element, 'input'
                                , {type: 'file', 'multiple': 'multiple'});
        hiddenFileInput.style.display = 'none'; // can be hidden!

        function handleFiles(files) {
            var i, l, reader, file;
            for(i=0,l=files.length;i<l;i++) {
                file = files[i];
                reader = new FileReader();
                reader.onload = fileOnLoad.bind(null, file);
                reader.readAsArrayBuffer(file);
            }
        }

        // for the file dialogue
        function fileInputChange(e) {
            /*jshint validthis:true, unused:vars*/
            handleFiles(this.files);
        }
        function forwardClick(e) {
            /*jshint unused:vars*/
            // forward the click => opens the file dialogue
            hiddenFileInput.click();
        }

        // for drag and drop
        function noAction(e) {
            e.stopPropagation();
            e.preventDefault();
        }
        function drop(e) {
            e.stopPropagation();
            e.preventDefault();
            handleFiles(e.dataTransfer.files);
        }

        hiddenFileInput.addEventListener('change', fileInputChange);
        element.addEventListener('click', forwardClick);
        element.addEventListener("dragenter", noAction);
        element.addEventListener("dragover", noAction);
        element.addEventListener("drop", drop);
    }

    function initFileInputs(fileOnLoad, container, klass) {
        var _makefileInput =  makeFileInput.bind(null, fileOnLoad);
        Array.from(container.getElementsByClassName(klass)).forEach(_makefileInput);
    }

    function getTemplatesContainer(klass) {
        return document.querySelector('body > .' + klass);
    }

    function activateTemplate(klass) {
        var templatesContainer = getTemplatesContainer('templates')
          , template = templatesContainer.getElementsByClassName(klass)[0]
          , target = document.getElementsByClassName('active-interface')[0]
          , activatedElement
          ;

        for(var i=0,l=target.children.length;i<l;i++)
            // children can listen for the event and cleanup if needed
            // activatedElement.addEventListener('destroy', function (e) { //... }, false);
            target.children[i].dispatchEvent(new Event('destroy'));
        dom.clear(target);
        activatedElement = template.cloneNode(true);
        target.appendChild(activatedElement);
        return activatedElement;
    }

    function initDNDSendingInterface() {
        var container = activateTemplate('sending-interface')
          , ctrl = new Controller(
                  container.getElementsByClassName('files-controls')[0]
                , container.getElementsByClassName('general-controls')[0]
            )
          ;
        initFileInputs(ctrl.fileOnLoad, container, 'drop-fonts');
        ctrl.onResponse(initReportingInterface);
    }

    function initReportingInterface(data) {
        var container = activateTemplate('reporting-interface')
          , templatesContainer = getTemplatesContainer('report-templates')
          , socket = socketio('/')
          , report = new Report(container, templatesContainer, data)
          ;

        socket.on('changes-report', report.onChange.bind(report));
        socket.emit('subscribe-report', { id: data.id });
    }

    function _initCollectionsInterface() {
        var container = activateTemplate('collection-landing-page')
          , ctrl = new CollectionController(container)
          ;
          return ctrl;
    }

    function _initStatusInterface() {
        var container = activateTemplate('status-landing-page')
          , ctrl = new StatusController(container)
          ;
        return ctrl;
    }

    function initCollectionReportInterface(data) {
        var container = activateTemplate('collection-report-interface')
          , templatesContainer = getTemplatesContainer('collection-report-templates')
          , socket = socketio('/')
          , report = new CollectionReport(container, templatesContainer, data)
          ;

        socket.on('changes-collection', report.onChange.bind(report));
        socket.emit('subscribe-collection', { id: data.id });
    }

    function initDashboard(data) {
        var container = activateTemplate('dashboard-interface')
         , templatesContainer = getTemplatesContainer('dashboard-templates')
         , socket = socketio('/')
         , dashboard = new DashboardController(container, templatesContainer, data)
         ;
        socket.on('changes-dashboard', dashboard.onChange.bind(dashboard));
        console.log('subscribe-dashboard');
        socket.emit('subscribe-dashboard', {});
    }

    function getInterfaceMode() {
        var data = null
          , defaultMode = 'drag-and-drop'
          , defaultData = null
          , mode, init
          , pathparts = window.location.pathname.split('/')
          , i, l
          , modes = {
                // path-marker: "mode"
                // We use a mapping here for historical reasons.
                // Could maybe get rid of it.
                report:  initReportingInterface
              , collections: _initCollectionsInterface
              , status: _initStatusInterface
              , 'status-report': function(){} // No action, is server side rendered.
              , 'collection-report': initCollectionReportInterface
              , 'drag-and-drop': initDNDSendingInterface
              , 'dashboard': initDashboard
            }
          ;
        mode = defaultMode;
        // change mode?
        for(i=0,l=pathparts.length;i<l;i++) {
            if(pathparts[i] in modes) {
                mode = pathparts[i];
                break;
            }
        }
        init = modes[mode];
        // extra data for mode?
        switch(mode) {
            case('collection-report'):
            // falls through
            // behaves exactly like report, but `id` is for collection_id
            case('report'):
                // if 'reporting/' in url
                // familytests_id is at reporting/{id}
                data = {
                    id: decodeURIComponent(pathparts[i+1])
                  , url:  window.location.pathname
                };
                break;
            //case('collection'):
            //case('drag-and-drop'):
            //case('dashboard'):
            default:
                data = defaultData;
            break;
        }
        return [data, init];
    }

    return function main() {
        // here's a early difference:
        // either we want to bootstrap the sending interface OR the
        // reporting interface.
        // The sending interface will also transform into the reporting interface.

        var interfaceMode = getInterfaceMode()
          , data = interfaceMode[0]
          , init = interfaceMode[1]
          ;
        init(data);
        // Using pushstate changes the behavior of the browser back-button.
        // This is intended to make it behave as if pushstate was not used.
        window.onpopstate = function(e) {
            // jshint unused:vars
            window.location.reload();
        };
    };
});
<commit_msg>[dashboard-query] use location.hash to filter/change dashboard client subscription<commit_after>define([
    'dom-tool'
  , 'socket.io'
  , 'Controller'
  , 'Report'
  , 'CollectionController'
  , 'StatusController'
  , 'CollectionReport'
  , 'DashboardController'
], function(
    dom
  , socketio
  , Controller
  , Report
  , CollectionController
  , StatusController
  , CollectionReport
  , DashboardController
) {
    "use strict";
    /*global document, window, FileReader*/
    // jshint browser:true

    function makeFileInput(fileOnLoad, element) {
        var hiddenFileInput = dom.createChildElement(element, 'input'
                                , {type: 'file', 'multiple': 'multiple'});
        hiddenFileInput.style.display = 'none'; // can be hidden!

        function handleFiles(files) {
            var i, l, reader, file;
            for(i=0,l=files.length;i<l;i++) {
                file = files[i];
                reader = new FileReader();
                reader.onload = fileOnLoad.bind(null, file);
                reader.readAsArrayBuffer(file);
            }
        }

        // for the file dialogue
        function fileInputChange(e) {
            /*jshint validthis:true, unused:vars*/
            handleFiles(this.files);
        }
        function forwardClick(e) {
            /*jshint unused:vars*/
            // forward the click => opens the file dialogue
            hiddenFileInput.click();
        }

        // for drag and drop
        function noAction(e) {
            e.stopPropagation();
            e.preventDefault();
        }
        function drop(e) {
            e.stopPropagation();
            e.preventDefault();
            handleFiles(e.dataTransfer.files);
        }

        hiddenFileInput.addEventListener('change', fileInputChange);
        element.addEventListener('click', forwardClick);
        element.addEventListener("dragenter", noAction);
        element.addEventListener("dragover", noAction);
        element.addEventListener("drop", drop);
    }

    function initFileInputs(fileOnLoad, container, klass) {
        var _makefileInput =  makeFileInput.bind(null, fileOnLoad);
        Array.from(container.getElementsByClassName(klass)).forEach(_makefileInput);
    }

    function getTemplatesContainer(klass) {
        return document.querySelector('body > .' + klass);
    }

    function activateTemplate(klass) {
        var templatesContainer = getTemplatesContainer('templates')
          , template = templatesContainer.getElementsByClassName(klass)[0]
          , target = document.getElementsByClassName('active-interface')[0]
          , activatedElement
          ;

        for(var i=0,l=target.children.length;i<l;i++)
            // children can listen for the event and cleanup if needed
            // activatedElement.addEventListener('destroy', function (e) { //... }, false);
            target.children[i].dispatchEvent(new Event('destroy'));
        dom.clear(target);
        activatedElement = template.cloneNode(true);
        target.appendChild(activatedElement);
        return activatedElement;
    }

    function initDNDSendingInterface() {
        var container = activateTemplate('sending-interface')
          , ctrl = new Controller(
                  container.getElementsByClassName('files-controls')[0]
                , container.getElementsByClassName('general-controls')[0]
            )
          ;
        initFileInputs(ctrl.fileOnLoad, container, 'drop-fonts');
        ctrl.onResponse(initReportingInterface);
    }

    function initReportingInterface(data) {
        var container = activateTemplate('reporting-interface')
          , templatesContainer = getTemplatesContainer('report-templates')
          , socket = socketio('/')
          , report = new Report(container, templatesContainer, data)
          ;

        socket.on('changes-report', report.onChange.bind(report));
        socket.emit('subscribe-report', { id: data.id });
    }

    function _initCollectionsInterface() {
        var container = activateTemplate('collection-landing-page')
          , ctrl = new CollectionController(container)
          ;
          return ctrl;
    }

    function _initStatusInterface() {
        var container = activateTemplate('status-landing-page')
          , ctrl = new StatusController(container)
          ;
        return ctrl;
    }

    function initCollectionReportInterface(data) {
        var container = activateTemplate('collection-report-interface')
          , templatesContainer = getTemplatesContainer('collection-report-templates')
          , socket = socketio('/')
          , report = new CollectionReport(container, templatesContainer, data)
          ;

        socket.on('changes-collection', report.onChange.bind(report));
        socket.emit('subscribe-collection', { id: data.id });
    }

    // also used on the server, but useful in the client as well
    function _dashboarNormalizeFilter(userInputfilter) {
        return userInputfilter
            ? userInputfilter.split(',')
                  .map(function(s){return s.trim();})
                  .filter(function(s){return !!s;})
                  .sort()
                  .join(',')
            : ''
            ;
    }

    function initDashboard(data) {
        var socket = socketio('/')
         , lastQuery = null
         , socketChangeHandler = null
         ;
        function onQueryFilterChange() {
            var hash = window.location.hash()
              , marker = '#filter:'
              , userInputFilter = ''
              , queryFilter
              ;
            if(hash.indexOf( marker) === 0)
                userInputFilter = hash.slice(marker.length);
            // did it really change?
            queryFilter = _dashboarNormalizeFilter(userInputFilter);
            if(lastQuery === queryFilter)
                return;
            lastQuery = queryFilter;

            // we don't want to act on this event.
            // thought, we should catch it with the check above;
            window.location.hash = marker + queryFilter;

            if(socketChangeHandler) {
                socket.off('changes-dashboard', socketChangeHandler);
                socketChangeHandler = null;
            }
            // replace the whole thing!
            var container = activateTemplate('dashboard-interface')
              , templatesContainer = getTemplatesContainer('dashboard-templates')
              , dashboard = new DashboardController(container, templatesContainer, data)
              ;

            window.addEventListener('hashchange', onQueryFilterChange, false);
            container.addEventListener('destroy', function(){
                window.removeEventListener('hashchange', onQueryFilterChange, false);
            }, false);

            console.log('subscribe-dashboard, filter:' + queryFilter);
            socketChangeHandler = dashboard.onChange.bind(dashboard);
            socket.on('changes-dashboard', socketChangeHandler);
            socket.emit('subscribe-dashboard', {filter: queryFilter});
        }

        // init
        onQueryFilterChange();
    }

    function getInterfaceMode() {
        var data = null
          , defaultMode = 'drag-and-drop'
          , defaultData = null
          , mode, init
          , pathparts = window.location.pathname.split('/')
          , i, l
          , modes = {
                // path-marker: "mode"
                // We use a mapping here for historical reasons.
                // Could maybe get rid of it.
                report:  initReportingInterface
              , collections: _initCollectionsInterface
              , status: _initStatusInterface
              , 'status-report': function(){} // No action, is server side rendered.
              , 'collection-report': initCollectionReportInterface
              , 'drag-and-drop': initDNDSendingInterface
              , 'dashboard': initDashboard
            }
          ;
        mode = defaultMode;
        // change mode?
        for(i=0,l=pathparts.length;i<l;i++) {
            if(pathparts[i] in modes) {
                mode = pathparts[i];
                break;
            }
        }
        init = modes[mode];
        // extra data for mode?
        switch(mode) {
            case('collection-report'):
            // falls through
            // behaves exactly like report, but `id` is for collection_id
            case('report'):
                // if 'reporting/' in url
                // familytests_id is at reporting/{id}
                data = {
                    id: decodeURIComponent(pathparts[i+1])
                  , url:  window.location.pathname
                };
                break;
            //case('collection'):
            //case('drag-and-drop'):
            //case('dashboard'):
            default:
                data = defaultData;
            break;
        }
        return [data, init];
    }

    return function main() {
        // here's a early difference:
        // either we want to bootstrap the sending interface OR the
        // reporting interface.
        // The sending interface will also transform into the reporting interface.

        var interfaceMode = getInterfaceMode()
          , data = interfaceMode[0]
          , init = interfaceMode[1]
          ;
        init(data);
        // Using pushstate changes the behavior of the browser back-button.
        // This is intended to make it behave as if pushstate was not used.
        window.onpopstate = function(e) {
            // jshint unused:vars
            window.location.reload();
        };
    };
});
<|endoftext|>