<commit_before>var stagedFiles = require('staged-files');
var map = require('map-stream');
var npmRun = require('npm-run');
var spawn = require("gulp-spawn")
var filter = require("gulp-filter")
var eslint = require('gulp-eslint')

var log = function(file, cb) {
    console.log(JSON.stringify(file));
    cb(null, file);
};

stagedFiles()
    // .pipe(filter(['*.js']))
    // .pipe(map(log))
    .pipe(eslint())
    .pipe(eslint.format())
    .pipe(eslint.failAfterError())
    // .pipe(log)
<commit_msg>Experimenting with VinylFS and gulp plugins<commit_after>var stagedFiles = require('staged-files');
var map = require('map-stream');
var npmRun = require('npm-run');
var spawn = require("gulp-spawn")
var filter = require("gulp-filter")
var eslint = require('gulp-eslint')
var fs = require('vinyl-fs');

var log = function(file, cb) {
    console.log(JSON.stringify(file));
    cb(null, file);
};

stagedFiles()
    // .pipe(filter(['*.js']))
    // .pipe(fs.src(['**/*.js'], { passthrough: true }))
    // .pipe(map(log))
    .pipe(eslint({
        useEslintrc: true
    }))
    .pipe(eslint.format())
    .pipe(eslint.failAfterError())
<|endoftext|>


<commit_before>/**
 * Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH
 * under one or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information regarding copyright
 * ownership.
 *
 * Camunda licenses this file to you under the MIT; you may not use this file
 * except in compliance with the MIT License.
 */

/* global sinon */

import React from 'react';

import TestRenderer from 'react-test-renderer';

import PluginContainer from '../PluginContainer';

describe('<PluginContainer>', function() {

  it('should render', function() {
    TestRenderer.create(<PluginContainer />);
  });

  it('should cancel subscriptions when unmounted', function() {

    // given
    const cancelSubscriptionsSpy = sinon.spy();
    const wrapper = TestRenderer.create(
      <PluginContainer cancelSubscriptions={ cancelSubscriptionsSpy } />
    );

    // when
    wrapper.unmount();

    // then
    expect(cancelSubscriptionsSpy).to.have.been.calledOnce;
  });

  describe('as error boundary', function() {

    function ErrorComponent() {
      throw new Error('error');
    }

    it('should work as an error boundary', function() {

      // when
      const { root } = TestRenderer.create(<PluginContainer><ErrorComponent /></PluginContainer>);

      // then
      expect(root).to.exist;
    });

    it('should correctly report the error', function() {

      // given
      const onErrorSpy = sinon.spy();

      // when
      const { root } = TestRenderer.create(
        <PluginContainer onError={ onErrorSpy }>
          <ErrorComponent />
        </PluginContainer>
      );

      // then
      expect(root).to.exist;
      expect(onErrorSpy).to.have.been.calledOnce;
    });

    it('should cancel subscriptions on error', function() {

      // given
      const cancelSubscriptionsSpy = sinon.spy();

      // when
      const { root } = TestRenderer.create(
        <PluginContainer cancelSubscriptions={ cancelSubscriptionsSpy }>
          <ErrorComponent />
        </PluginContainer>
      );

      // then
      expect(root).to.exist;
      expect(cancelSubscriptionsSpy).to.have.been.calledOnce;
    });

  });

});
<commit_msg>style(client): update test formating<commit_after>/**
 * Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH
 * under one or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information regarding copyright
 * ownership.
 *
 * Camunda licenses this file to you under the MIT; you may not use this file
 * except in compliance with the MIT License.
 */

/* global sinon */

import React from 'react';

import TestRenderer from 'react-test-renderer';

import PluginContainer from '../PluginContainer';

describe('<PluginContainer>', function() {

  it('should render', function() {
    TestRenderer.create(<PluginContainer />);
  });

  it('should cancel subscriptions when unmounted', function() {

    // given
    const cancelSubscriptionsSpy = sinon.spy();
    const wrapper = TestRenderer.create(
      <PluginContainer cancelSubscriptions={ cancelSubscriptionsSpy } />
    );

    // when
    wrapper.unmount();

    // then
    expect(cancelSubscriptionsSpy).to.have.been.calledOnce;
  });

  describe('as error boundary', function() {

    function ErrorComponent() {
      throw new Error('error');
    }

    it('should work as an error boundary', function() {

      // when
      const { root } = TestRenderer.create(
        <PluginContainer>
          <ErrorComponent />
        </PluginContainer>
      );

      // then
      expect(root).to.exist;
    });

    it('should correctly report the error', function() {

      // given
      const onErrorSpy = sinon.spy();

      // when
      const { root } = TestRenderer.create(
        <PluginContainer onError={ onErrorSpy }>
          <ErrorComponent />
        </PluginContainer>
      );

      // then
      expect(root).to.exist;
      expect(onErrorSpy).to.have.been.calledOnce;
    });

    it('should cancel subscriptions on error', function() {

      // given
      const cancelSubscriptionsSpy = sinon.spy();

      // when
      const { root } = TestRenderer.create(
        <PluginContainer cancelSubscriptions={ cancelSubscriptionsSpy }>
          <ErrorComponent />
        </PluginContainer>
      );

      // then
      expect(root).to.exist;
      expect(cancelSubscriptionsSpy).to.have.been.calledOnce;
    });

  });

});
<|endoftext|>


<commit_before>"use strict"

const asset = require('./asset')
const colorDepth = require('./converter/colorDepth')
const clamp = require('lodash/clamp')
const glob = require('glob')
const createMappoSession = require('./createMappoSession')
const createMappoMap = require('./createMappoMap')
const createMappoTileset = require('./createMappoTileset')
const detectFormat = require('./detectFormat')
const path = require('path')
const fs = require('fs')
const setupKeyboard = require('./setupKeyboard')
const renderTileHighlightInvertedOutline = require('./renderTileHighlightInvertedOutline')
const renderTileHighlightInvertedSolid = require('./renderTileHighlightInvertedSolid')
const renderTileHighlightColorOutline = require('./renderTileHighlightColorOutline')
const renderTile = require('./renderTile')
const renderLayer = require('./renderLayer')
const renderTileset = require('./renderTileset')
const createCheckerboardPattern = require('./createCheckerboardPattern')
const calcAutoScroll = require('./calcAutoScroll')

// DOM REFERENCES
const pageTitle = document.querySelector('title')
const mapList = document.querySelector('.map-list')
const layerList = document.querySelector('.layer-list')
const canvas = document.querySelector('.mappo-viewport')
const context = canvas.getContext('2d')
const tilesetCanvasContainer = document.querySelector('.tileset-canvas-container')
const tilesetCanvas = document.querySelector('.tileset-canvas')
const tilesetContext = tilesetCanvas.getContext('2d')
const tilesetSelectedTileCanvas = document.querySelector('.tileset-selected-tile-canvas')
const tilesetSelectedTileContext = tilesetSelectedTileCanvas.getContext('2d')
const tilesetSelectedTileIndex = document.querySelector('.tileset-selected-tile-index')
const tilesetHoveringTileCanvas = document.querySelector('.tileset-hovering-tile-canvas')
const tilesetHoveringTileContext = tilesetHoveringTileCanvas.getContext('2d')
const tilesetHoveringTileIndex = document.querySelector('.tileset-hovering-tile-index')
const middlePanel = document.querySelector('.middle-panel')

const checkerboardPattern = createCheckerboardPattern({document})

const launchFolder = 'data' // TODO(chuck): temp hack for windows. empty string dunna work
console.log('launchFolder', launchFolder)
const mapGlob = '**/*.map'
const mapFilenames = glob.sync(mapGlob, {nocase: true})
const mappoSession = createMappoSession({
  fileSystem: {
    files: mapFilenames
  },
  launchFolder
})

const viewportScales = [0.25, 0.5, 0.75, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
const DEFAULT_SCALE_INDEX = 4
const defaultMappoState = {
  scaleIndex: DEFAULT_SCALE_INDEX,
  isLoading: true,
  map: null,
  mapLayerOrder: null,
  mapLayerSelected: null,
  tileset: null,
  tilesetTileHovering: null,
  tilesetTileSelected: null,
  cameraX: 0,
  cameraY: 0,
  cameraMoveX: 0,
  cameraMoveY: 0,
  cameraScrollAmount: 1,
  keyPressed: {},
  autoScroll: {},
}
let mappoState = Object.assign({}, defaultMappoState)

const getScale = () => viewportScales[mappoState.scaleIndex]

mappoSession.getMapFilenames().forEach(mapFilename => {
  const li = document.createElement('li')
  li.setAttribute('title', mapFilename)
  li.innerText = mapFilename
  // TODO(chuck): temp hack for windows. figure out better launchFolder shenanigans
  li.addEventListener('click', event => {
    loadMap('data/' + mapFilename)
    pageTitle.innerText = 'Mappo - ' + mapFilename
  })
  mapList.appendChild(li)
})

const refreshMapLayerList = () => {
  layerList.innerHTML = ''
  mappoState.map.tileLayers.forEach((layer, index) => {
    const li = document.createElement('li')
    li.setAttribute('title', layer.description)
    li.innerText = layer.description
    li.classList.add('layer-list-item')
    if (mappoState.mapLayerSelected === layer) {
      li.classList.add('selected')
    }
    li.addEventListener('click', event => {
      // TODO(chuck): use a proper element?
      if (event.offsetX < 35) {
        li.classList.toggle('is-layer-hidden')
        layer.isHidden = !layer.isHidden
      }
      mappoState.mapLayerSelected = layer
      const layerListItems = document.querySelectorAll('.layer-list .layer-list-item')
      layerListItems.forEach(layerListItem => {
        layerListItem.classList.remove('selected')
      })
      li.classList.add('selected')
    })
    layerList.appendChild(li)
  })
}

const loadMap = mapFilename => {
  console.group()
  try {
    mappoState = Object.assign({}, defaultMappoState)
    mappoState.isLoading = true

    mappoState.cameraX = 0
    mappoState.cameraY = 0

    const mapBuffer = fs.readFileSync(mapFilename)
    const mapFormat = detectFormat(mapBuffer)
    if (!mapFormat.includes('map')) {
      console.error('expected map but got', mapFormat)
      return
    }
    console.log('mapFormat', mapFormat)
    let mapData
    try {
      mapData = asset.fromBuffer(mapBuffer, asset[mapFormat])
    } catch (exception) {
      console.log(exception)
      return
    }
    mappoState.map = createMappoMap({map: mapData})
    console.log(mapFilename, mapData)
    mappoState.mapLayerSelected = mappoState.map.tileLayers[mappoState.map.mapLayerOrder[0]]
    refreshMapLayerList()

    const vspFilename = path.join(
      path.dirname(mapFilename),
      mappoState.map.tilesetFilename
    )
    const vspBuffer = fs.readFileSync(vspFilename)
    const vspFormat = detectFormat(vspBuffer)
    console.log('vspFormat', vspFormat)
    let vspData
    try {
      vspData = asset.fromBuffer(vspBuffer, asset[vspFormat])
    } catch (exception) {
      console.log(exception)
      return
    }
    mappoState.tileset = createMappoTileset({context, tileset: vspData})
    console.log(vspFilename, vspData)
    tilesetSelectedTileCanvas.width = mappoState.tileset.tileWidth
    tilesetSelectedTileCanvas.height = mappoState.tileset.tileHeight
    tilesetHoveringTileCanvas.width = mappoState.tileset.tileWidth
    tilesetHoveringTileCanvas.height = mappoState.tileset.tileHeight

    mappoState.tileset.imageBitmapPromise.then(() => {
      mappoState.isLoading = false
      resizeCanvas()
    })
  } catch (exception) {
    console.error('ack!', exception)
  } finally {
    console.groupEnd()
  }
}

const keyboard = setupKeyboard({
  addEventListener: document.addEventListener,
  keyPressed: mappoState.keyPressed,
})

let mousedown = false
let mousein = false
middlePanel.addEventListener('mousedown', event => {
  mousedown = true
})

let hoverCanvasCoord = null
middlePanel.addEventListener('mousemove', event => {
  if (mappoState.isLoading) {
    return
  }

  const scale = getScale()
  if (mousedown) {
    moveCamera(
      -event.movementX / scale,
      -event.movementY / scale
    )
  }

  mappoState.autoScroll = {}
  if (mousein) {
    mappoState.autoScroll = calcAutoScroll({
      scale,
      threshold: (mappoState.tileset.tileWidth + mappoState.tileset.tileHeight) / 2,
      cursorX: event.offsetX,
      cursorY: event.offsetY,
      viewportWidth: middlePanel.offsetWidth,
      viewportHeight: middlePanel.offsetHeight,
    })
  }

  hoverCanvasCoord = {
    x: ~~(event.offsetX / getScale()),
    y: ~~(event.offsetY / getScale()),
  }
})

const getTileCoordAndIndex = ({
  tileset,
  containerWidth,
  pixelX,
  pixelY,
}) => {
  const scale = getScale()
  const tilesetColumns = getTilesetColumns({tileset, containerWidth})
  const tileX = ~~(pixelX / (tileset.tileWidth * scale))
  const tileY = ~~(pixelY / (tileset.tileHeight * scale))
  const tileIndex = (tileY * tilesetColumns) + tileX

  return {
    tileX,
    tileY,
    tileIndex,
  }
}

tilesetCanvasContainer.addEventListener('mousemove', event => {
  if (mappoState.isLoading) {
    return
  }

  mappoState.tilesetTileHovering = getTileCoordAndIndex({
    tileset: mappoState.tileset,
    containerWidth: tilesetCanvasContainer.offsetWidth,
    pixelX: event.offsetX,
    pixelY: event.offsetY,
  })
})

tilesetCanvasContainer.addEventListener('click', event => {
  if (mappoState.isLoading) {
    return
  }

  mappoState.tilesetTileSelected = getTileCoordAndIndex({
    tileset: mappoState.tileset,
    containerWidth: tilesetCanvasContainer.offsetWidth,
    pixelX: event.offsetX,
    pixelY: event.offsetY,
  })
})

middlePanel.addEventListener('mouseup', event => {
  mousedown = false
})

middlePanel.addEventListener('mouseenter', event => {
  mousein = true
})

middlePanel.addEventListener('mouseout', event => {
  mousein = false
  mappoState.cameraMoveX = 0
  mappoState.cameraMoveY = 0
  mappoState.autoScroll = {}
  hoverCanvasCoord = null
})

const moveCamera = (moveX, moveY) => {
  mappoState.cameraX = clamp(mappoState.cameraX + moveX, 0, (mappoState.map.tileLayers[0].width * mappoState.tileset.tileWidth) - canvas.width)
  mappoState.cameraY = clamp(mappoState.cameraY + moveY, 0, (mappoState.map.tileLayers[0].height * mappoState.tileset.tileHeight) - canvas.height)
}

const clearCanvas = (canvas) => {
  const context = canvas.getContext('2d')
  context.globalCompositeOperation = 'source-over'
  context.fillStyle = checkerboardPattern
  context.fillRect(0, 0, canvas.width, canvas.height)
}

const getTilesetColumns = ({tileset, containerWidth}) => {
  const scaledTileWidth = tileset.tileWidth * getScale()
  return ~~(containerWidth / scaledTileWidth)
}

const getTilesetRows = ({tileset, containerWidth}) => {
  const tilesetColumns = getTilesetColumns({tileset, containerWidth})
  const roundedUpRows = ~~((tileset.numTiles + (tilesetColumns - 1)) / tilesetColumns)
  return roundedUpRows
}

const tick = () => {
  clearCanvas(canvas)
  clearCanvas(tilesetCanvas)
  clearCanvas(tilesetSelectedTileCanvas)
  clearCanvas(tilesetHoveringTileCanvas)

  if (!mappoState.isLoading) {
    const tileset = mappoState.tileset
    const tileWidth = tileset.tileWidth
    const tileHeight = tileset.tileHeight
    const containerWidth = tilesetCanvasContainer.offsetWidth

    mappoState.map.mapLayerOrder.forEach(layerIndex => {
      const tileLayer = mappoState.map.tileLayers[layerIndex]
      if (!tileLayer.isHidden) {
        renderLayer({
          tileset,
          layer: tileLayer,
          x: mappoState.cameraX * tileLayer.parallax.x,
          y: mappoState.cameraY * tileLayer.parallax.y,
          transparent: layerIndex > 0,
        })
      }
    })

    if (hoverCanvasCoord) {
      renderTileHighlightInvertedOutline({
        context,
        x: hoverCanvasCoord.x - ((~~mappoState.cameraX + hoverCanvasCoord.x) % tileWidth),
        y: hoverCanvasCoord.y - ((~~mappoState.cameraY + hoverCanvasCoord.y) % tileHeight),
        width: tileWidth,
        height: tileHeight,
      })
    }

    renderTileset({
      context: tilesetContext,
      tileset: mappoState.tileset,
      tilesetColumns: getTilesetColumns({tileset, containerWidth})
    })

    if (mappoState.tilesetTileHovering) {
      renderTileHighlightInvertedSolid({
        context: tilesetContext,
        x: mappoState.tilesetTileHovering.tileX * tileWidth,
        y: mappoState.tilesetTileHovering.tileY * tileHeight,
        width: tileWidth,
        height: tileHeight,
      })
      renderTile({
        context: tilesetHoveringTileContext,
        tileset: mappoState.tileset,
        tileIndex: mappoState.tilesetTileHovering.tileIndex,
        x: 0,
        y: 0,
      })

      tilesetHoveringTileIndex.innerText = mappoState.tilesetTileHovering.tileIndex
    }

    if (mappoState.tilesetTileSelected) {
      renderTileHighlightColorOutline({
        context: tilesetContext,
        x: mappoState.tilesetTileSelected.tileX * tileWidth,
        y: mappoState.tilesetTileSelected.tileY * tileHeight,
        color: 'white',
        width: tileWidth,
        height: tileHeight,
      })
      renderTile({
        context: tilesetSelectedTileContext,
        tileset: mappoState.tileset,
        tileIndex: mappoState.tilesetTileSelected.tileIndex,
        x: 0,
        y: 0,
      })

      tilesetSelectedTileIndex.innerText = mappoState.tilesetTileSelected.tileIndex
    }

    mappoState.cameraMoveX = 0
    mappoState.cameraMoveY = 0;
    const autoScroll = mappoState.autoScroll
    if (autoScroll) {
      (keyboard.isPressed(keyboard.KEYCODE_UP) || autoScroll.x < 0) && (mappoState.cameraMoveY = -mappoState.cameraScrollAmount);
      (keyboard.isPressed(keyboard.KEYCODE_DOWN) || autoScroll.y > 0) && (mappoState.cameraMoveY = +mappoState.cameraScrollAmount);
      (keyboard.isPressed(keyboard.KEYCODE_LEFT) || autoScroll.x < 0) && (mappoState.cameraMoveX = -mappoState.cameraScrollAmount);
      (keyboard.isPressed(keyboard.KEYCODE_RIGHT) || autoScroll.x > 0) && (mappoState.cameraMoveX = +mappoState.cameraScrollAmount);
      moveCamera(mappoState.cameraMoveX, mappoState.cameraMoveY)
    }

    // map zooming
    if (keyboard.isPressed('ctrlKey')) {
      const prevScaleIndex = mappoState.scaleIndex
      if (keyboard.isPressed(keyboard.KEYCODE_PLUS)) {
        keyboard.release(keyboard.KEYCODE_PLUS)
        mappoState.scaleIndex++
      }
      if (keyboard.isPressed(keyboard.KEYCODE_MINUS)) {
        keyboard.release(keyboard.KEYCODE_MINUS)
        mappoState.scaleIndex--
      }
      if (keyboard.isPressed(keyboard.KEYCODE_0)) {
        keyboard.release(keyboard.KEYCODE_0)
        mappoState.scaleIndex = DEFAULT_SCALE_INDEX
      }
      mappoState.scaleIndex = clamp(mappoState.scaleIndex, 0, viewportScales.length - 1)
      // TODO(chuck): redux-ify?
      if (prevScaleIndex !== mappoState.scaleIndex) {
        resizeCanvas()
      }
    }
  }

  window.requestAnimationFrame(tick)
}

const resizeCanvas = () => {
  canvas.width = ~~((middlePanel.offsetWidth + (getScale() - 1)) / getScale())
  canvas.height = ~~((middlePanel.offsetHeight + (getScale() - 1)) / getScale())
  canvas.style.width = middlePanel.offsetWidth + 'px'
  canvas.style.height = middlePanel.offsetHeight + 'px'

  if (!mappoState.isLoading) {
    const containerWidth = tilesetCanvasContainer.offsetWidth
    const tileset = mappoState.tileset
    tilesetCanvas.width = getTilesetColumns({tileset, containerWidth}) * tileset.tileWidth
    tilesetCanvas.height = getTilesetRows({tileset, containerWidth}) * tileset.tileHeight
    tilesetCanvas.style.width = (tilesetCanvas.width * getScale()) + 'px'
    tilesetCanvas.style.height = (tilesetCanvas.height * getScale()) + 'px'
  }
}

window.addEventListener('resize', resizeCanvas)
resizeCanvas()

tick()

<commit_msg>less reliance on global state<commit_after>"use strict"

const asset = require('./asset')
const colorDepth = require('./converter/colorDepth')
const clamp = require('lodash/clamp')
const glob = require('glob')
const createMappoSession = require('./createMappoSession')
const createMappoMap = require('./createMappoMap')
const createMappoTileset = require('./createMappoTileset')
const detectFormat = require('./detectFormat')
const path = require('path')
const fs = require('fs')
const setupKeyboard = require('./setupKeyboard')
const renderTileHighlightInvertedOutline = require('./renderTileHighlightInvertedOutline')
const renderTileHighlightInvertedSolid = require('./renderTileHighlightInvertedSolid')
const renderTileHighlightColorOutline = require('./renderTileHighlightColorOutline')
const renderTile = require('./renderTile')
const renderLayer = require('./renderLayer')
const renderTileset = require('./renderTileset')
const createCheckerboardPattern = require('./createCheckerboardPattern')
const calcAutoScroll = require('./calcAutoScroll')

// DOM REFERENCES
const pageTitle = document.querySelector('title')
const mapList = document.querySelector('.map-list')
const layerList = document.querySelector('.layer-list')
const canvas = document.querySelector('.mappo-viewport')
const context = canvas.getContext('2d')
const tilesetCanvasContainer = document.querySelector('.tileset-canvas-container')
const tilesetCanvas = document.querySelector('.tileset-canvas')
const tilesetContext = tilesetCanvas.getContext('2d')
const tilesetSelectedTileCanvas = document.querySelector('.tileset-selected-tile-canvas')
const tilesetSelectedTileContext = tilesetSelectedTileCanvas.getContext('2d')
const tilesetSelectedTileIndex = document.querySelector('.tileset-selected-tile-index')
const tilesetHoveringTileCanvas = document.querySelector('.tileset-hovering-tile-canvas')
const tilesetHoveringTileContext = tilesetHoveringTileCanvas.getContext('2d')
const tilesetHoveringTileIndex = document.querySelector('.tileset-hovering-tile-index')
const middlePanel = document.querySelector('.middle-panel')

const checkerboardPattern = createCheckerboardPattern({document})

const launchFolder = 'data' // TODO(chuck): temp hack for windows. empty string dunna work
console.log('launchFolder', launchFolder)
const mapGlob = '**/*.map'
const mapFilenames = glob.sync(mapGlob, {nocase: true})
const mappoSession = createMappoSession({
  fileSystem: {
    files: mapFilenames
  },
  launchFolder
})

const viewportScales = [0.25, 0.5, 0.75, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
const DEFAULT_SCALE_INDEX = 4
const defaultMappoState = {
  scaleIndex: DEFAULT_SCALE_INDEX,
  isLoading: true,
  map: null,
  mapLayerOrder: null,
  mapLayerSelected: null,
  tileset: null,
  tilesetTileHovering: null,
  tilesetTileSelected: null,
  cameraX: 0,
  cameraY: 0,
  cameraMoveX: 0,
  cameraMoveY: 0,
  cameraScrollAmount: 1,
  keyPressed: {},
  autoScroll: {},
}
let mappoState = Object.assign({}, defaultMappoState)

const getScale = () => viewportScales[mappoState.scaleIndex]

mappoSession.getMapFilenames().forEach(mapFilename => {
  const li = document.createElement('li')
  li.setAttribute('title', mapFilename)
  li.innerText = mapFilename
  // TODO(chuck): temp hack for windows. figure out better launchFolder shenanigans
  li.addEventListener('click', event => {
    loadMap('data/' + mapFilename)
    pageTitle.innerText = 'Mappo - ' + mapFilename
  })
  mapList.appendChild(li)
})

const refreshMapLayerList = () => {
  layerList.innerHTML = ''
  mappoState.map.tileLayers.forEach((layer, index) => {
    const li = document.createElement('li')
    li.setAttribute('title', layer.description)
    li.innerText = layer.description
    li.classList.add('layer-list-item')
    if (mappoState.mapLayerSelected === layer) {
      li.classList.add('selected')
    }
    li.addEventListener('click', event => {
      // TODO(chuck): use a proper element?
      if (event.offsetX < 35) {
        li.classList.toggle('is-layer-hidden')
        layer.isHidden = !layer.isHidden
      }
      mappoState.mapLayerSelected = layer
      const layerListItems = document.querySelectorAll('.layer-list .layer-list-item')
      layerListItems.forEach(layerListItem => {
        layerListItem.classList.remove('selected')
      })
      li.classList.add('selected')
    })
    layerList.appendChild(li)
  })
}

const loadMap = mapFilename => {
  console.group()
  try {
    mappoState = Object.assign({}, defaultMappoState)
    mappoState.isLoading = true

    mappoState.cameraX = 0
    mappoState.cameraY = 0

    const mapBuffer = fs.readFileSync(mapFilename)
    const mapFormat = detectFormat(mapBuffer)
    if (!mapFormat.includes('map')) {
      console.error('expected map but got', mapFormat)
      return
    }
    console.log('mapFormat', mapFormat)
    let mapData
    try {
      mapData = asset.fromBuffer(mapBuffer, asset[mapFormat])
    } catch (exception) {
      console.log(exception)
      return
    }
    mappoState.map = createMappoMap({map: mapData})
    console.log(mapFilename, mapData)
    mappoState.mapLayerSelected = mappoState.map.tileLayers[mappoState.map.mapLayerOrder[0]]
    refreshMapLayerList()

    const vspFilename = path.join(
      path.dirname(mapFilename),
      mappoState.map.tilesetFilename
    )
    const vspBuffer = fs.readFileSync(vspFilename)
    const vspFormat = detectFormat(vspBuffer)
    console.log('vspFormat', vspFormat)
    let vspData
    try {
      vspData = asset.fromBuffer(vspBuffer, asset[vspFormat])
    } catch (exception) {
      console.log(exception)
      return
    }
    mappoState.tileset = createMappoTileset({context, tileset: vspData})
    console.log(vspFilename, vspData)
    tilesetSelectedTileCanvas.width = mappoState.tileset.tileWidth
    tilesetSelectedTileCanvas.height = mappoState.tileset.tileHeight
    tilesetHoveringTileCanvas.width = mappoState.tileset.tileWidth
    tilesetHoveringTileCanvas.height = mappoState.tileset.tileHeight

    mappoState.tileset.imageBitmapPromise.then(() => {
      mappoState.isLoading = false
      resizeCanvas()
    })
  } catch (exception) {
    console.error('ack!', exception)
  } finally {
    console.groupEnd()
  }
}

const keyboard = setupKeyboard({
  addEventListener: document.addEventListener,
  keyPressed: mappoState.keyPressed,
})

let mousedown = false
let mousein = false
middlePanel.addEventListener('mousedown', event => {
  mousedown = true
})

let hoverCanvasCoord = null
middlePanel.addEventListener('mousemove', event => {
  if (mappoState.isLoading) {
    return
  }

  const scale = getScale()
  if (mousedown) {
    moveCamera(
      -event.movementX / scale,
      -event.movementY / scale
    )
  }

  mappoState.autoScroll = {}
  if (mousein) {
    mappoState.autoScroll = calcAutoScroll({
      scale,
      threshold: (mappoState.tileset.tileWidth + mappoState.tileset.tileHeight) / 2,
      cursorX: event.offsetX,
      cursorY: event.offsetY,
      viewportWidth: middlePanel.offsetWidth,
      viewportHeight: middlePanel.offsetHeight,
    })
  }

  hoverCanvasCoord = {
    x: ~~(event.offsetX / getScale()),
    y: ~~(event.offsetY / getScale()),
  }
})

const getTileCoordAndIndex = ({
  tileset,
  containerWidth,
  pixelX,
  pixelY,
}) => {
  const scale = getScale()
  const tilesetColumns = getTilesetColumns({tileset, containerWidth})
  const tileX = ~~(pixelX / (tileset.tileWidth * scale))
  const tileY = ~~(pixelY / (tileset.tileHeight * scale))
  const tileIndex = (tileY * tilesetColumns) + tileX

  return {
    tileX,
    tileY,
    tileIndex,
  }
}

tilesetCanvasContainer.addEventListener('mousemove', event => {
  if (mappoState.isLoading) {
    return
  }

  mappoState.tilesetTileHovering = getTileCoordAndIndex({
    tileset: mappoState.tileset,
    containerWidth: tilesetCanvasContainer.offsetWidth,
    pixelX: event.offsetX,
    pixelY: event.offsetY,
  })
})

tilesetCanvasContainer.addEventListener('click', event => {
  if (mappoState.isLoading) {
    return
  }

  mappoState.tilesetTileSelected = getTileCoordAndIndex({
    tileset: mappoState.tileset,
    containerWidth: tilesetCanvasContainer.offsetWidth,
    pixelX: event.offsetX,
    pixelY: event.offsetY,
  })
})

middlePanel.addEventListener('mouseup', event => {
  mousedown = false
})

middlePanel.addEventListener('mouseenter', event => {
  mousein = true
})

middlePanel.addEventListener('mouseout', event => {
  mousein = false
  mappoState.cameraMoveX = 0
  mappoState.cameraMoveY = 0
  mappoState.autoScroll = {}
  hoverCanvasCoord = null
})

const moveCamera = (moveX, moveY) => {
  mappoState.cameraX = clamp(mappoState.cameraX + moveX, 0, (mappoState.map.tileLayers[0].width * mappoState.tileset.tileWidth) - canvas.width)
  mappoState.cameraY = clamp(mappoState.cameraY + moveY, 0, (mappoState.map.tileLayers[0].height * mappoState.tileset.tileHeight) - canvas.height)
}

const clearCanvas = ({canvas, pattern}) => {
  const context = canvas.getContext('2d')
  context.globalCompositeOperation = 'source-over'
  context.fillStyle = pattern
  context.fillRect(0, 0, canvas.width, canvas.height)
}

const getTilesetColumns = ({tileset, containerWidth}) => {
  const scaledTileWidth = tileset.tileWidth * getScale()
  return ~~(containerWidth / scaledTileWidth)
}

const getTilesetRows = ({tileset, containerWidth}) => {
  const tilesetColumns = getTilesetColumns({tileset, containerWidth})
  const roundedUpRows = ~~((tileset.numTiles + (tilesetColumns - 1)) / tilesetColumns)
  return roundedUpRows
}

const tick = () => {
  clearCanvas({canvas, pattern: checkerboardPattern})
  clearCanvas({canvas: tilesetCanvas, pattern: checkerboardPattern})
  clearCanvas({canvas: tilesetSelectedTileCanvas, pattern: checkerboardPattern})
  clearCanvas({canvas: tilesetHoveringTileCanvas, pattern: checkerboardPattern})

  if (!mappoState.isLoading) {
    const tileset = mappoState.tileset
    const tileWidth = tileset.tileWidth
    const tileHeight = tileset.tileHeight
    const containerWidth = tilesetCanvasContainer.offsetWidth

    mappoState.map.mapLayerOrder.forEach(layerIndex => {
      const tileLayer = mappoState.map.tileLayers[layerIndex]
      if (!tileLayer.isHidden) {
        renderLayer({
          tileset,
          layer: tileLayer,
          x: mappoState.cameraX * tileLayer.parallax.x,
          y: mappoState.cameraY * tileLayer.parallax.y,
          transparent: layerIndex > 0,
        })
      }
    })

    if (hoverCanvasCoord) {
      renderTileHighlightInvertedOutline({
        context,
        x: hoverCanvasCoord.x - ((~~mappoState.cameraX + hoverCanvasCoord.x) % tileWidth),
        y: hoverCanvasCoord.y - ((~~mappoState.cameraY + hoverCanvasCoord.y) % tileHeight),
        width: tileWidth,
        height: tileHeight,
      })
    }

    renderTileset({
      context: tilesetContext,
      tileset: mappoState.tileset,
      tilesetColumns: getTilesetColumns({tileset, containerWidth})
    })

    if (mappoState.tilesetTileHovering) {
      renderTileHighlightInvertedSolid({
        context: tilesetContext,
        x: mappoState.tilesetTileHovering.tileX * tileWidth,
        y: mappoState.tilesetTileHovering.tileY * tileHeight,
        width: tileWidth,
        height: tileHeight,
      })
      renderTile({
        context: tilesetHoveringTileContext,
        tileset: mappoState.tileset,
        tileIndex: mappoState.tilesetTileHovering.tileIndex,
        x: 0,
        y: 0,
      })

      tilesetHoveringTileIndex.innerText = mappoState.tilesetTileHovering.tileIndex
    }

    if (mappoState.tilesetTileSelected) {
      renderTileHighlightColorOutline({
        context: tilesetContext,
        x: mappoState.tilesetTileSelected.tileX * tileWidth,
        y: mappoState.tilesetTileSelected.tileY * tileHeight,
        color: 'white',
        width: tileWidth,
        height: tileHeight,
      })
      renderTile({
        context: tilesetSelectedTileContext,
        tileset: mappoState.tileset,
        tileIndex: mappoState.tilesetTileSelected.tileIndex,
        x: 0,
        y: 0,
      })

      tilesetSelectedTileIndex.innerText = mappoState.tilesetTileSelected.tileIndex
    }

    mappoState.cameraMoveX = 0
    mappoState.cameraMoveY = 0;
    const autoScroll = mappoState.autoScroll
    if (autoScroll) {
      (keyboard.isPressed(keyboard.KEYCODE_UP) || autoScroll.x < 0) && (mappoState.cameraMoveY = -mappoState.cameraScrollAmount);
      (keyboard.isPressed(keyboard.KEYCODE_DOWN) || autoScroll.y > 0) && (mappoState.cameraMoveY = +mappoState.cameraScrollAmount);
      (keyboard.isPressed(keyboard.KEYCODE_LEFT) || autoScroll.x < 0) && (mappoState.cameraMoveX = -mappoState.cameraScrollAmount);
      (keyboard.isPressed(keyboard.KEYCODE_RIGHT) || autoScroll.x > 0) && (mappoState.cameraMoveX = +mappoState.cameraScrollAmount);
      moveCamera(mappoState.cameraMoveX, mappoState.cameraMoveY)
    }

    // map zooming
    if (keyboard.isPressed('ctrlKey')) {
      const prevScaleIndex = mappoState.scaleIndex
      if (keyboard.isPressed(keyboard.KEYCODE_PLUS)) {
        keyboard.release(keyboard.KEYCODE_PLUS)
        mappoState.scaleIndex++
      }
      if (keyboard.isPressed(keyboard.KEYCODE_MINUS)) {
        keyboard.release(keyboard.KEYCODE_MINUS)
        mappoState.scaleIndex--
      }
      if (keyboard.isPressed(keyboard.KEYCODE_0)) {
        keyboard.release(keyboard.KEYCODE_0)
        mappoState.scaleIndex = DEFAULT_SCALE_INDEX
      }
      mappoState.scaleIndex = clamp(mappoState.scaleIndex, 0, viewportScales.length - 1)
      // TODO(chuck): redux-ify?
      if (prevScaleIndex !== mappoState.scaleIndex) {
        resizeCanvas()
      }
    }
  }

  window.requestAnimationFrame(tick)
}

const resizeCanvas = () => {
  canvas.width = ~~((middlePanel.offsetWidth + (getScale() - 1)) / getScale())
  canvas.height = ~~((middlePanel.offsetHeight + (getScale() - 1)) / getScale())
  canvas.style.width = middlePanel.offsetWidth + 'px'
  canvas.style.height = middlePanel.offsetHeight + 'px'

  if (!mappoState.isLoading) {
    const containerWidth = tilesetCanvasContainer.offsetWidth
    const tileset = mappoState.tileset
    tilesetCanvas.width = getTilesetColumns({tileset, containerWidth}) * tileset.tileWidth
    tilesetCanvas.height = getTilesetRows({tileset, containerWidth}) * tileset.tileHeight
    tilesetCanvas.style.width = (tilesetCanvas.width * getScale()) + 'px'
    tilesetCanvas.style.height = (tilesetCanvas.height * getScale()) + 'px'
  }
}

window.addEventListener('resize', resizeCanvas)
resizeCanvas()

tick()

<|endoftext|>


<commit_before>var Etcd = require("node-etcd"),
    _ = require("underscore");

function Cache(applicationName, config, eventEmitter){
    this.applicationName = applicationName;
    this.eventEmitter = eventEmitter;

    config = _.defaults(config || {},
        {
            etcdHost: "127.0.0.1",
            etcdPort: 4001,
            cacheIntervalMs: 60000
        });

    if (config.cacheIntervalMs < 1000){
        throw new Error("Cache interval is too small, but be at least 1,000ms.");
    }

    this.etcd = new Etcd(config.etcdHost, config.etcdPort);
    this.intervalObject = setInterval(_.bind(this._updateCache, this), config.cacheIntervalMs);
    this._updateCache();
}

var getValueOrNullIfUndefined = function(cache, toggle){
    var value = cache[toggle];
    return value === undefined ? null : value;
};

var validateResponse = function(response, responseError){
    if (responseError){
        var error = new Error("Error updating cache, see inner error");
        error.inner = responseError;
        return error;
    }
    if (!(response && response.node)){
        return new Error("Error updating cache, response invalid");
    }
};

var parseValue = function(value){
    switch(value.toLowerCase())
    {
        case "true": return true;
        case "false": return false;
        default: return null;
    }
};

var parseResponse = function(response){
    return _.chain(response.node.nodes || [])
        .where(function (item) {
            return item.value !== undefined;
        })
        .map(function (item) {
            var key = _.last(item.key.split("/"));
            var value = parseValue(item.value);
            return [key, value];
        })
        .object()
        .value();
};

var setCache = function(self, value){
    var wasCacheNotInitialized = self.cache === undefined;
    self.cache = value;
    self.eventEmitter.emit("updated-cache");
    if (wasCacheNotInitialized){
        self.eventEmitter.emit("initialized");
    }
};

Cache.prototype.get = function(toggle, callback){
    var  self = this;
    if (self.cache){
        callback(null, getValueOrNullIfUndefined(self.cache, toggle));
    }
    else {
        var error = new Error("Cache not initialized");
        self.eventEmitter.emit("error", error);
        callback(error, null);
    }
};

Cache.prototype._updateCache = function(){
    var self = this;
    self.eventEmitter.emit("updating-cache");
    self.etcd.get("v1/toggles/" + this.applicationName, { recursive: true }, function(responseError, response){

        var noKeysFoundForApplication = responseError && responseError.errorCode === 100;
        if (noKeysFoundForApplication){
            setCache(self, {});
            return;
        }

        var validationError = validateResponse(response, responseError);
        if (validationError){
            self.eventEmitter.emit("error", validationError);
            return;
        }

        try {
            setCache(self, parseResponse(response));
        }
        catch(err){
            var error = new Error("Error updating cache");
            error.inner = err;
            self.eventEmitter.emit("error", error);
        }
    });
};

Cache.prototype.dispose = function(){
    clearInterval(this.intervalObject);
};

module.exports = Cache;<commit_msg>Removing error emitting, as it is pretty much syncronous code and the error is being returned<commit_after>var Etcd = require("node-etcd"),
    _ = require("underscore");

function Cache(applicationName, config, eventEmitter){
    this.applicationName = applicationName;
    this.eventEmitter = eventEmitter;

    config = _.defaults(config || {},
        {
            etcdHost: "127.0.0.1",
            etcdPort: 4001,
            cacheIntervalMs: 60000
        });

    if (config.cacheIntervalMs < 1000){
        throw new Error("Cache interval is too small, but be at least 1,000ms.");
    }

    this.etcd = new Etcd(config.etcdHost, config.etcdPort);
    this.intervalObject = setInterval(_.bind(this._updateCache, this), config.cacheIntervalMs);
    this._updateCache();
}

var getValueOrNullIfUndefined = function(cache, toggle){
    var value = cache[toggle];
    return value === undefined ? null : value;
};

var validateResponse = function(response, responseError){
    if (responseError){
        var error = new Error("Error updating cache, see inner error");
        error.inner = responseError;
        return error;
    }
    if (!(response && response.node)){
        return new Error("Error updating cache, response invalid");
    }
};

var parseValue = function(value){
    switch(value.toLowerCase())
    {
        case "true": return true;
        case "false": return false;
        default: return null;
    }
};

var parseResponse = function(response){
    return _.chain(response.node.nodes || [])
        .where(function (item) {
            return item.value !== undefined;
        })
        .map(function (item) {
            var key = _.last(item.key.split("/"));
            var value = parseValue(item.value);
            return [key, value];
        })
        .object()
        .value();
};

var setCache = function(self, value){
    var wasCacheNotInitialized = self.cache === undefined;
    self.cache = value;
    self.eventEmitter.emit("updated-cache");
    if (wasCacheNotInitialized){
        self.eventEmitter.emit("initialized");
    }
};

Cache.prototype.get = function(toggle, callback){
    var  self = this;
    if (self.cache){
        callback(null, getValueOrNullIfUndefined(self.cache, toggle));
    }
    else {
        var error = new Error("Cache not initialized");
        callback(error, null);
    }
};

Cache.prototype._updateCache = function(){
    var self = this;
    self.eventEmitter.emit("updating-cache");
    self.etcd.get("v1/toggles/" + this.applicationName, { recursive: true }, function(responseError, response){

        var noKeysFoundForApplication = responseError && responseError.errorCode === 100;
        if (noKeysFoundForApplication){
            setCache(self, {});
            return;
        }

        var validationError = validateResponse(response, responseError);
        if (validationError){
            self.eventEmitter.emit("error", validationError);
            return;
        }

        try {
            setCache(self, parseResponse(response));
        }
        catch(err){
            var error = new Error("Error updating cache");
            error.inner = err;
            self.eventEmitter.emit("error", error);
        }
    });
};

Cache.prototype.dispose = function(){
    clearInterval(this.intervalObject);
};

module.exports = Cache;<|endoftext|>


<commit_before>"use strict";

var _ = require('lodash'),
  through = require('through2'),
  gutil = require('gulp-util'),
  http = require('http'),
  https = require('https'),
  inject = require('connect-inject'),
  connect = require('connect'),
  proxy = require('proxy-middleware'),
  watch = require('node-watch'),
  fs = require('fs'),
  serveIndex = require('serve-index'),
  serveStatic = require('serve-static'),
  path = require('path'),
  open = require('open'),
  enableMiddlewareShorthand = require('./enableMiddlewareShorthand'),
  socket = require('socket.io'),
  url = require('url'),
  extend = require('node.extend');

var BROWSER_SCIPTS_DIR = path.join(__dirname, 'browser-scripts');

module.exports = function(options) {
  var defaults = {
    /**
     *
     * BASIC DEFAULTS
     *
     **/
    host: 'localhost',
    port: 8000,
    defaultFile: 'index.html',
    fallback: null,
    https: false,
    open: false,
    log: 'info',

    /**
     *
     * MIDDLEWARE DEFAULTS
     *
     * NOTE:
     *  All middleware should defaults should have the 'enable'
     *  property if you want to support shorthand syntax like:
     *
     *    webserver({
     *      livereload: true
     *    });
     *
     */

    // Middleware: Livereload
    livereload: {
      enable: false,
      markupHost: null,
      port: 35729,
      filter: function(filename, cb) {
        cb( !(/node_modules/.test(filename)) );
      },
      clientConsole: false,
    },

    // Middleware: Directory listing
    // For possible options, see:
    //  https://github.com/expressjs/serve-index
    directoryListing: {
      enable: false,
      path: './',
      options: undefined
    },

    // Middleware: Proxy
    // For possible options, see:
    //  https://github.com/andrewrk/connect-proxy
    proxies: []

  };

  // Deep extend user provided options over the all of the defaults
  // Allow shorthand syntax, using the enable property as a flag
  var config = enableMiddlewareShorthand(defaults, options, ['directoryListing', 'livereload']);

  var openInBrowser = function () {
    if (config.open === false) return;
    open('http' + (config.https ? 's' : '') + '://' + config.host + ':' + config.port);
    openInBrowser = undefined;
  };

  // connect app
  var app = connect();
  // Proxy requests
  for (var i = 0, len = config.proxies.length; i < len; i++) {
    var proxyoptions = url.parse(config.proxies[i].target);
    if (config.proxies[i].hasOwnProperty('options')) {
      extend(proxyoptions, config.proxies[i].options);
    }

    proxyoptions.route = config.proxies[i].source;
    app.use(proxy(proxyoptions));

    gutil.log(config.proxies[i].source + ' is proxied.');
  }
  //  directory listing
  if (config.directoryListing.enable) {
    app.use(serveIndex(path.resolve(config.directoryListing.path), config.directoryListing.options));
  }

  // socket.io
  if (config.livereload.enable) {
    var snippetParams = [];

    if (config.livereload.clientConsole) {
      snippetParams.push("extra=capture-console");
    }

    // If it wasn't provided, use the server host:
    var markupHost = !!_.get(config.livereload.markupHost, 'length')
      ? "'" + config.livereload.markupHost + "'"
      : null;

    var snippet =
      "<script type=\"text/javascript\">"
      + "var _lrscript = document.createElement('script');"
      + "_lrscript.type = 'text/javascript';"
      + "_lrscript.defer = _lrscript.async = true;"
      + "_lrscript.src = '//' + ((" + markupHost + "||location.host).split(':')[0]) + ':"+config.livereload.port+"/livereload.js?"+snippetParams.join('&')+"';"
      + "document.body.appendChild(_lrscript);"
      + "</script>";

    app.use(inject({
      snippet: snippet,
      rules: [{
        match: /<\/body>/,
        fn: function(w, s) {
          return s + w;
        }
      }]
    }));

    var io = config.livereload.io = socket();
    io.serveClient(true);
    io.path("");
    io.on('connection', function(socket){
      gutil.log('Livereload client connected');

      socket.on('console_log', function(data){
        var args = [
          gutil.colors.green('log')
        ];
        for (var i in data) {
          args.push(data[i]);
        }
        gutil.log.apply(null, args);
      });
      socket.on('console_warn', function(data){
        var args = [
          gutil.colors.yellow('warn')
        ];
        for (var i in data) {
          args.push(data[i]);
        }
        gutil.log.apply(null, args);
      });
      socket.on('console_info', function(data){
        var args = [
          gutil.colors.cyan('info')
        ];
        for (var i in data) {
          args.push(data[i]);
        }
        gutil.log.apply(null, args);
      });
      socket.on('console_error', function(data){
        var args = [
          gutil.colors.red('err')
        ];
        for (var i in data) {
          args.push(data[i]);
        }
        gutil.log.apply(null, args);
      });
    });

    var ioApp = connect();

    ioApp.use(serveStatic(BROWSER_SCIPTS_DIR, { index: false }));

    var ioServer = config.livereload.ioServer =
      http.createServer(ioApp).listen(config.livereload.port, config.host);

    io.attach(ioServer, {
      path: '/socket.io'
    });

    gutil.log('Livereload started at', gutil.colors.gray('http' + (config.https ? 's' : '') + '://' + config.host + ':' + config.livereload.port));
  }

  // http server
  var webserver = null;
  if (config.https) {
    var options = {
      key: fs.readFileSync(config.https.key || __dirname + '/../ssl/dev-key.pem'),
      cert: fs.readFileSync(config.https.cert || __dirname + '/../ssl/dev-cert.pem')
    };

    webserver = https.createServer(options, app);
  }
  else {
    webserver = http.createServer(app);
  }

  var files = [];

  // Create server
  var stream = through.obj(function(file, enc, callback) {
    if ('debug' === config.log) {
      app.use(function(req, res, next) {
        gutil.log(req.method + ' ' + req.url);

        next();
      });
    }

    app.use(serveStatic(file.path, {
      index: (config.directoryListing.enable ? false : config.defaultFile)
    }));

    if (config.livereload.enable) {
      watch(file.path, function(filename) {
        config.livereload.filter(filename, function(shouldReload) {
          if (shouldReload) {
            gutil.log('Livereload: file changed: ' + filename);

            config.livereload.io.sockets.emit('reload');
            // Treat changes to sourcemaps as changes to the original files.
            filename = filename.replace(/\.map$/, '');

            config.livereload.io.sockets.emit('file_changed', {
              path: filename,
              name: path.basename(filename),
              ext: path.extname(filename),
            });
          }
        });
      });
    }

    this.push(file);

    callback();
  })
  .on('data', function(f) {
    files.push(f);

    // start the web server
    webserver.listen(config.port, config.host, openInBrowser);

    gutil.log('Webserver started at', gutil.colors.cyan('http' + (config.https ? 's' : '') + '://' + config.host + ':' + config.port));
  })
  .on('end', function(){
    if (config.fallback) {
      files.forEach(function(file){
        var fallbackFile = file.path + '/' + config.fallback;
        if (fs.existsSync(fallbackFile)) {
          app.use(function(req, res) {
            res.setHeader('Content-Type', 'text/html; charset=UTF-8');
            fs.createReadStream(fallbackFile).pipe(res);
          });
        }
      });
    }
  });

  // once stream killed
  stream.on('kill', function() {
    webserver.close();

    if (config.livereload.enable) {
      config.livereload.ioServer.close();
    }
  });

  return stream;
};
<commit_msg>Add fallback match rules for html script injection<commit_after>"use strict";

var _ = require('lodash'),
  through = require('through2'),
  gutil = require('gulp-util'),
  http = require('http'),
  https = require('https'),
  inject = require('connect-inject'),
  connect = require('connect'),
  proxy = require('proxy-middleware'),
  watch = require('node-watch'),
  fs = require('fs'),
  serveIndex = require('serve-index'),
  serveStatic = require('serve-static'),
  path = require('path'),
  open = require('open'),
  enableMiddlewareShorthand = require('./enableMiddlewareShorthand'),
  socket = require('socket.io'),
  url = require('url'),
  extend = require('node.extend');

var BROWSER_SCIPTS_DIR = path.join(__dirname, 'browser-scripts');

module.exports = function(options) {
  var defaults = {
    /**
     *
     * BASIC DEFAULTS
     *
     **/
    host: 'localhost',
    port: 8000,
    defaultFile: 'index.html',
    fallback: null,
    https: false,
    open: false,
    log: 'info',

    /**
     *
     * MIDDLEWARE DEFAULTS
     *
     * NOTE:
     *  All middleware should defaults should have the 'enable'
     *  property if you want to support shorthand syntax like:
     *
     *    webserver({
     *      livereload: true
     *    });
     *
     */

    // Middleware: Livereload
    livereload: {
      enable: false,
      markupHost: null,
      port: 35729,
      filter: function(filename, cb) {
        cb( !(/node_modules/.test(filename)) );
      },
      clientConsole: false,
    },

    // Middleware: Directory listing
    // For possible options, see:
    //  https://github.com/expressjs/serve-index
    directoryListing: {
      enable: false,
      path: './',
      options: undefined
    },

    // Middleware: Proxy
    // For possible options, see:
    //  https://github.com/andrewrk/connect-proxy
    proxies: []

  };

  // Deep extend user provided options over the all of the defaults
  // Allow shorthand syntax, using the enable property as a flag
  var config = enableMiddlewareShorthand(defaults, options, ['directoryListing', 'livereload']);

  var openInBrowser = function () {
    if (config.open === false) return;
    open('http' + (config.https ? 's' : '') + '://' + config.host + ':' + config.port);
    openInBrowser = undefined;
  };

  // connect app
  var app = connect();
  // Proxy requests
  for (var i = 0, len = config.proxies.length; i < len; i++) {
    var proxyoptions = url.parse(config.proxies[i].target);
    if (config.proxies[i].hasOwnProperty('options')) {
      extend(proxyoptions, config.proxies[i].options);
    }

    proxyoptions.route = config.proxies[i].source;
    app.use(proxy(proxyoptions));

    gutil.log(config.proxies[i].source + ' is proxied.');
  }
  //  directory listing
  if (config.directoryListing.enable) {
    app.use(serveIndex(path.resolve(config.directoryListing.path), config.directoryListing.options));
  }

  // socket.io
  if (config.livereload.enable) {
    var snippetParams = [];

    if (config.livereload.clientConsole) {
      snippetParams.push("extra=capture-console");
    }

    // If it wasn't provided, use the server host:
    var markupHost = !!_.get(config.livereload.markupHost, 'length')
      ? "'" + config.livereload.markupHost + "'"
      : null;

    var snippet =
      "<script type=\"text/javascript\">"
      + "var _lrscript = document.createElement('script');"
      + "_lrscript.type = 'text/javascript';"
      + "_lrscript.defer = _lrscript.async = true;"
      + "_lrscript.src = '//' + ((" + markupHost + "||location.host).split(':')[0]) + ':"+config.livereload.port+"/livereload.js?"+snippetParams.join('&')+"';"
      + "document.body.appendChild(_lrscript);"
      + "</script>";

    var prepend = function(w, s) {
      return s + w;
    };

    var append = function(w, s) {
      return w + s;
    }

    app.use(inject({
      snippet: snippet,
      rules: [{
        match: /<\/body>/,
        fn: prepend
      }, {
        match: /<\/html>/,
        fn: prepend
      }, {
        match: /<\!DOCTYPE.+>/,
        fn: append
      }]
    }));

    var io = config.livereload.io = socket();
    io.serveClient(true);
    io.path("");
    io.on('connection', function(socket){
      gutil.log('Livereload client connected');

      socket.on('console_log', function(data){
        var args = [
          gutil.colors.green('log')
        ];
        for (var i in data) {
          args.push(data[i]);
        }
        gutil.log.apply(null, args);
      });
      socket.on('console_warn', function(data){
        var args = [
          gutil.colors.yellow('warn')
        ];
        for (var i in data) {
          args.push(data[i]);
        }
        gutil.log.apply(null, args);
      });
      socket.on('console_info', function(data){
        var args = [
          gutil.colors.cyan('info')
        ];
        for (var i in data) {
          args.push(data[i]);
        }
        gutil.log.apply(null, args);
      });
      socket.on('console_error', function(data){
        var args = [
          gutil.colors.red('err')
        ];
        for (var i in data) {
          args.push(data[i]);
        }
        gutil.log.apply(null, args);
      });
    });

    var ioApp = connect();

    ioApp.use(serveStatic(BROWSER_SCIPTS_DIR, { index: false }));

    var ioServer = config.livereload.ioServer =
      http.createServer(ioApp).listen(config.livereload.port, config.host);

    io.attach(ioServer, {
      path: '/socket.io'
    });

    gutil.log('Livereload started at', gutil.colors.gray('http' + (config.https ? 's' : '') + '://' + config.host + ':' + config.livereload.port));
  }

  // http server
  var webserver = null;
  if (config.https) {
    var options = {
      key: fs.readFileSync(config.https.key || __dirname + '/../ssl/dev-key.pem'),
      cert: fs.readFileSync(config.https.cert || __dirname + '/../ssl/dev-cert.pem')
    };

    webserver = https.createServer(options, app);
  }
  else {
    webserver = http.createServer(app);
  }

  var files = [];

  // Create server
  var stream = through.obj(function(file, enc, callback) {
    if ('debug' === config.log) {
      app.use(function(req, res, next) {
        gutil.log(req.method + ' ' + req.url);

        next();
      });
    }

    app.use(serveStatic(file.path, {
      index: (config.directoryListing.enable ? false : config.defaultFile)
    }));

    if (config.livereload.enable) {
      watch(file.path, function(filename) {
        config.livereload.filter(filename, function(shouldReload) {
          if (shouldReload) {
            gutil.log('Livereload: file changed: ' + filename);

            config.livereload.io.sockets.emit('reload');
            // Treat changes to sourcemaps as changes to the original files.
            filename = filename.replace(/\.map$/, '');

            config.livereload.io.sockets.emit('file_changed', {
              path: filename,
              name: path.basename(filename),
              ext: path.extname(filename),
            });
          }
        });
      });
    }

    this.push(file);

    callback();
  })
  .on('data', function(f) {
    files.push(f);

    // start the web server
    webserver.listen(config.port, config.host, openInBrowser);

    gutil.log('Webserver started at', gutil.colors.cyan('http' + (config.https ? 's' : '') + '://' + config.host + ':' + config.port));
  })
  .on('end', function(){
    if (config.fallback) {
      files.forEach(function(file){
        var fallbackFile = file.path + '/' + config.fallback;
        if (fs.existsSync(fallbackFile)) {
          app.use(function(req, res) {
            res.setHeader('Content-Type', 'text/html; charset=UTF-8');
            fs.createReadStream(fallbackFile).pipe(res);
          });
        }
      });
    }
  });

  // once stream killed
  stream.on('kill', function() {
    webserver.close();

    if (config.livereload.enable) {
      config.livereload.ioServer.close();
    }
  });

  return stream;
};
<|endoftext|>


<commit_before>"use strict"

const asset = require('./asset')
const colorDepth = require('./converter/colorDepth')
const clamp = require('lodash/clamp')

const mapFilename = 'data/v1/TEST.MAP';
const mapData = asset.fromDisk(mapFilename, asset.v1map)

console.log(mapFilename, mapData)
console.log('nummovescripts', mapData.nummovescripts)
console.log('msbufsize', mapData.msbufsize)
console.log('msofstbl', JSON.stringify(mapData.msofstbl))
console.log('msbuf', JSON.stringify(mapData.msbuf))
console.log('numscripts', mapData.numscripts)
console.log('scriptofstbl', JSON.stringify(mapData.scriptofstbl))
console.log('mapvcs', mapData.mapvc.length)

const palFilename = 'data/v1/VERGE.PAL'
const palData = asset.fromDisk(palFilename, asset.v1pal)

const vspFilename = 'data/v1/' + mapData.vsp0name
const vspData = asset.fromDisk(vspFilename, asset.v1vsp)
console.log('vsp', vspFilename, vspData)

const raw32bitData = colorDepth.convert8to32({
  palette: palData.pal.map(v => v * 4),
  raw8bitData: vspData.vsp0,
})

const canvas = document.querySelector('.mappo-viewport')
const tileColumns = 20
const tileRows = ~~((vspData.numtiles + 19) / 20)
const context = canvas.getContext('2d')
const imageData = context.createImageData(16, 16 * vspData.numtiles)
const image = document.createElement('canvas')
const viewportWidth = 320
const viewportHeight = 240

canvas.width = viewportWidth
canvas.height = viewportHeight

imageData.data.set(raw32bitData)

// huh. putImageData carries alpha but always overwrites the destination.
// http://weblogs.asp.net/bleroy/drawing-transparent-glyphs-on-the-html-canvas
image.width = 16
image.height = 16 * vspData.numtiles
image.getContext('2d').putImageData(imageData, 0, 0)

const renderTile = (tileIndex, x, y) => {
  context.drawImage(image, 0, tileIndex * 16, 16, 16, x, y, 16, 16)
}

const getTileIndex = (layer, tileX, tileY) => {
  return layer[(tileY * mapData.xsize) + tileX]
}

const renderLayer = (layer, x, y, transparent=false) => {
  const topLeftTileX = x >> 4
  const topLeftTileY = y >> 4
  const subTileX = x % 16
  const subTileY = y % 16

  for (let tileY = 0; tileY < (viewportHeight + 31) >> 4; tileY++) {
    for (let tileX = 0; tileX < (viewportWidth + 31) >> 4; tileX++) {
      const tileIndex = getTileIndex(
        layer,
        topLeftTileX + tileX,
        topLeftTileY + tileY
      )

      if (transparent && !tileIndex) {
        continue
      }

      renderTile(tileIndex, (tileX * 16) - subTileX, (tileY * 16) - subTileY)
    }
  }
}

let cameraX = 0
let cameraY = 0

const KEYCODE_UP = 38
const KEYCODE_DOWN = 40
const KEYCODE_LEFT = 37
const KEYCODE_RIGHT = 39
const keyPressed = {}

document.addEventListener('keydown', event => keyPressed[event.keyCode] = true)
document.addEventListener('keyup', event => keyPressed[event.keyCode] = false)

let mousedown = false
canvas.addEventListener('mousedown', event => {
  mousedown = true
})

canvas.addEventListener('mousemove', event => {
  if (mousedown) {
    moveCamera(
      ~~(-event.movementX / 3),
      ~~(-event.movementY / 3)
    )
  }
})

canvas.addEventListener('mouseup', event => {
  mousedown = false
})

const moveCamera = (moveX, moveY) => {
  cameraX = clamp(cameraX + moveX, 0, (mapData.xsize * 16) - viewportWidth)
  cameraY = clamp(cameraY + moveY, 0, (mapData.ysize * 16) - viewportHeight)
}

const tick = () => {
  context.fillStyle = 'black'
  context.fillRect(0, 0, viewportWidth, viewportHeight)
  renderLayer(mapData.map0, cameraX, cameraY)
  renderLayer(
    mapData.map1,
    ~~(cameraX * mapData.pmultx / mapData.pdivx),
    ~~(cameraY * mapData.pmultx / mapData.pdivx),
    true
  )

  let moveX = 0
  let moveY = 0
  keyPressed[KEYCODE_UP] && moveY--
  keyPressed[KEYCODE_DOWN] && moveY++
  keyPressed[KEYCODE_LEFT] && moveX--
  keyPressed[KEYCODE_RIGHT] && moveX++
  moveCamera(moveX, moveY)

  window.requestAnimationFrame(tick)
}

tick()<commit_msg>auto scroll when mouse near viewport edges<commit_after>"use strict"

const asset = require('./asset')
const colorDepth = require('./converter/colorDepth')
const clamp = require('lodash/clamp')

const mapFilename = 'data/v1/TEST.MAP';
const mapData = asset.fromDisk(mapFilename, asset.v1map)

console.log(mapFilename, mapData)
console.log('nummovescripts', mapData.nummovescripts)
console.log('msbufsize', mapData.msbufsize)
console.log('msofstbl', JSON.stringify(mapData.msofstbl))
console.log('msbuf', JSON.stringify(mapData.msbuf))
console.log('numscripts', mapData.numscripts)
console.log('scriptofstbl', JSON.stringify(mapData.scriptofstbl))
console.log('mapvcs', mapData.mapvc.length)

const palFilename = 'data/v1/VERGE.PAL'
const palData = asset.fromDisk(palFilename, asset.v1pal)

const vspFilename = 'data/v1/' + mapData.vsp0name
const vspData = asset.fromDisk(vspFilename, asset.v1vsp)
console.log('vsp', vspFilename, vspData)

const raw32bitData = colorDepth.convert8to32({
  palette: palData.pal.map(v => v * 4),
  raw8bitData: vspData.vsp0,
})

const canvas = document.querySelector('.mappo-viewport')
const tileColumns = 20
const tileRows = ~~((vspData.numtiles + 19) / 20)
const context = canvas.getContext('2d')
const imageData = context.createImageData(16, 16 * vspData.numtiles)
const image = document.createElement('canvas')
const viewportWidth = 320
const viewportHeight = 240
const scale = 3

canvas.width = viewportWidth
canvas.height = viewportHeight

imageData.data.set(raw32bitData)

// huh. putImageData carries alpha but always overwrites the destination.
// http://weblogs.asp.net/bleroy/drawing-transparent-glyphs-on-the-html-canvas
image.width = 16
image.height = 16 * vspData.numtiles
image.getContext('2d').putImageData(imageData, 0, 0)

const renderTile = (tileIndex, x, y) => {
  context.drawImage(image, 0, tileIndex * 16, 16, 16, x, y, 16, 16)
}

const getTileIndex = (layer, tileX, tileY) => {
  return layer[(tileY * mapData.xsize) + tileX]
}

const renderLayer = (layer, x, y, transparent=false) => {
  x = ~~x
  y = ~~y

  const topLeftTileX = x >> 4
  const topLeftTileY = y >> 4
  const subTileX = x % 16
  const subTileY = y % 16

  for (let tileY = 0; tileY < (viewportHeight + 31) >> 4; tileY++) {
    for (let tileX = 0; tileX < (viewportWidth + 31) >> 4; tileX++) {
      const tileIndex = getTileIndex(
        layer,
        topLeftTileX + tileX,
        topLeftTileY + tileY
      )

      if (transparent && !tileIndex) {
        continue
      }

      renderTile(tileIndex, (tileX * 16) - subTileX, (tileY * 16) - subTileY)
    }
  }
}

let cameraX = 0
let cameraY = 0
let cameraMoveX = 0
let cameraMoveY = 0

const KEYCODE_UP = 38
const KEYCODE_DOWN = 40
const KEYCODE_LEFT = 37
const KEYCODE_RIGHT = 39
const keyPressed = {}

document.addEventListener('keydown', event => keyPressed[event.keyCode] = true)
document.addEventListener('keyup', event => keyPressed[event.keyCode] = false)

let mousedown = false
canvas.addEventListener('mousedown', event => {
  mousedown = true
})

canvas.addEventListener('mousemove', event => {
  if (mousedown) {
    moveCamera(
      -event.movementX / scale,
      -event.movementY / scale
    )
  }

  cameraMoveX = 0
  cameraMoveY = 0;
  const autoScrollThreshold = 16;
  const autoScrollBy = 2;
  (event.clientX < autoScrollThreshold * scale) && (cameraMoveX = -autoScrollBy);
  (event.clientX >= (viewportWidth - autoScrollThreshold) * scale) && (cameraMoveX = +autoScrollBy);
  (event.clientY < autoScrollThreshold * scale) && (cameraMoveY = -autoScrollBy);
  (event.clientY >= (viewportHeight - autoScrollThreshold) * scale) && (cameraMoveY = +autoScrollBy);
})

canvas.addEventListener('mouseup', event => {
  mousedown = false
})

canvas.addEventListener('mouseout', event => {
  mousedown = false
  cameraMoveX = 0
  cameraMoveY = 0
})

const moveCamera = (moveX, moveY) => {
  cameraX = clamp(cameraX + moveX, 0, (mapData.xsize * 16) - viewportWidth)
  cameraY = clamp(cameraY + moveY, 0, (mapData.ysize * 16) - viewportHeight)
}

const tick = () => {
  context.fillStyle = 'black'
  context.fillRect(0, 0, viewportWidth, viewportHeight)
  renderLayer(mapData.map0, cameraX, cameraY)
  renderLayer(
    mapData.map1,
    cameraX * mapData.pmultx / mapData.pdivx,
    cameraY * mapData.pmultx / mapData.pdivx,
    true
  )

  keyPressed[KEYCODE_UP] && (cameraMoveY = -1)
  keyPressed[KEYCODE_DOWN] && (cameraMoveY = +1)
  keyPressed[KEYCODE_LEFT] && (cameraMoveX = -1)
  keyPressed[KEYCODE_RIGHT] && (cameraMoveX = +1)
  moveCamera(cameraMoveX, cameraMoveY)

  window.requestAnimationFrame(tick)
}

tick()<|endoftext|>


<commit_before>import { SmoothScrollbar } from './smooth_scrollbar';
import { selectors, sbList } from './shared';

import './apis/index';
import './events/index';
import './internals/index';

export default SmoothScrollbar;

SmoothScrollbar.version = '<%= version %>';

/**
 * init scrollbar on given element
 *
 * @param {Element} elem: target element
 * @param {Object} options: scrollbar options
 *
 * @return {Scrollbar} scrollbar instance
 */
SmoothScrollbar.init = (elem, options) => {
    if (!elem || elem.nodeType !== 1) {
        throw new TypeError(`expect element to be DOM Element, but got ${typeof elem}`);
    }

    if (sbList.has(elem)) return sbList.get(elem);

    elem.setAttribute('data-scrollbar', '');

    const children = [...elem.children];

    const div = document.createElement('div');

    div.innerHTML = `

    `;

    [...div.children].forEach((el) => elem.appendChild(el));

    const content = elem.querySelector('.scroll-content');

    children.forEach((el) => content.appendChild(el));

    return new SmoothScrollbar(elem, options);
};

/**
 * init scrollbars on pre-defined selectors
 *
 * @param {Object} options: scrollbar options
 *
 * @return {Array} a collection of scrollbar instances
 */
SmoothScrollbar.initAll = (options) => {
    return [...document.querySelectorAll(selectors)].map((el) => {
        return SmoothScrollbar.init(el, options);
    });
};

/**
 * check if scrollbar exists on given element
 *
 * @return {Boolean}
 */
SmoothScrollbar.has = (elem) => {
    return sbList.has(elem);
};

/**
 * get scrollbar instance through given element
 *
 * @param {Element} elem: target scrollbar container
 *
 * @return {Scrollbar}
 */
SmoothScrollbar.get = (elem) => {
    return sbList.get(elem);
};

/**
 * get all scrollbar instances
 *
 * @return {Array} a collection of scrollbars
 */
SmoothScrollbar.getAll = () => {
    return [...sbList.values()];
};

/**
 * destroy scrollbar on given element
 *
 * @param {Element} elem: target scrollbar container
 */
SmoothScrollbar.destroy = (elem) => {
    return SmoothScrollbar.has(elem) && SmoothScrollbar.get(elem).destroy();
};

/**
 * destroy all scrollbars in scrollbar instances
 */
SmoothScrollbar.destroyAll = () => {
    sbList.forEach((sb) => {
        sb.destroy();
    });
};<commit_msg>fix(init): fix content element<commit_after>import { SmoothScrollbar } from './smooth_scrollbar';
import { selectors, sbList } from './shared';

import './apis/index';
import './events/index';
import './internals/index';

export default SmoothScrollbar;

SmoothScrollbar.version = '<%= version %>';

/**
 * init scrollbar on given element
 *
 * @param {Element} elem: target element
 * @param {Object} options: scrollbar options
 *
 * @return {Scrollbar} scrollbar instance
 */
SmoothScrollbar.init = (elem, options) => {
    if (!elem || elem.nodeType !== 1) {
        throw new TypeError(`expect element to be DOM Element, but got ${typeof elem}`);
    }

    if (sbList.has(elem)) return sbList.get(elem);

    elem.setAttribute('data-scrollbar', '');

    const children = [...elem.children];

    const div = document.createElement('div');

    div.innerHTML = `

    `;

    const scrollContent = div.querySelector('.scroll-content');

    [...div.children].forEach((el) => elem.appendChild(el));

    children.forEach((el) => scrollContent.appendChild(el));

    return new SmoothScrollbar(elem, options);
};

/**
 * init scrollbars on pre-defined selectors
 *
 * @param {Object} options: scrollbar options
 *
 * @return {Array} a collection of scrollbar instances
 */
SmoothScrollbar.initAll = (options) => {
    return [...document.querySelectorAll(selectors)].map((el) => {
        return SmoothScrollbar.init(el, options);
    });
};

/**
 * check if scrollbar exists on given element
 *
 * @return {Boolean}
 */
SmoothScrollbar.has = (elem) => {
    return sbList.has(elem);
};

/**
 * get scrollbar instance through given element
 *
 * @param {Element} elem: target scrollbar container
 *
 * @return {Scrollbar}
 */
SmoothScrollbar.get = (elem) => {
    return sbList.get(elem);
};

/**
 * get all scrollbar instances
 *
 * @return {Array} a collection of scrollbars
 */
SmoothScrollbar.getAll = () => {
    return [...sbList.values()];
};

/**
 * destroy scrollbar on given element
 *
 * @param {Element} elem: target scrollbar container
 */
SmoothScrollbar.destroy = (elem) => {
    return SmoothScrollbar.has(elem) && SmoothScrollbar.get(elem).destroy();
};

/**
 * destroy all scrollbars in scrollbar instances
 */
SmoothScrollbar.destroyAll = () => {
    sbList.forEach((sb) => {
        sb.destroy();
    });
};<|endoftext|>


<commit_before>'use strict';

import markdown from 'markdown-it';
import React, { PropTypes, Component } from 'react';
import isPlainObject from 'lodash/lang/isPlainObject';
import assign from 'lodash/object/assign';
import reduce from 'lodash/collection/reduce';
import zipObject from 'lodash/array/zipObject';
import sortBy from 'lodash/collection/sortBy';
import compact from 'lodash/array/compact';
import camelCase from 'lodash/string/camelCase';
import isString from 'lodash/lang/isString';

const DEFAULT_TAGS = {
  'html': 'span'
};

const DEFAULT_RULES = {
  image(token, attrs, children) {
    if (children.length) {
      attrs = assign({}, attrs, { alt: children[0] });
    }
    return [[token.tag, attrs]];
  },

  codeInline(token, attrs) {
    return [compact([token.tag, attrs, token.content])];
  },

  codeBlock(token, attrs) {
    return [['pre', compact([token.tag, attrs, token.content])]];
  },

  fence(token, attrs) {
    if (token.info) {
      const langName = token.info.trim().split(/\s+/g)[0];
      attrs = assign({}, attrs, { 'data-language': langName });
    }

    return [['pre', compact([token.tag, attrs, token.content])]];
  },

  hardbreak() {
    return [['br']];
  },

  softbreak(token, attrs, children, options) {
    return options.breaks ? [['br']] : '\n';
  },

  text(token) {
    return token.content;
  },

  htmlBlock(token) {
    return token.content;
  },

  htmlInline(token) {
    return token.content;
  },

  inline(token, attrs, children) {
    return children;
  },

  default(token, attrs, children, options, getNext) {
    if (token.nesting === 1 && token.hidden) {
      return getNext();
    }
    /* plugin-related */
    if (!token.tag) {
      return token.content;
    }
    if (token.info) {
      attrs = assign({}, attrs, { 'data-info': token.info.trim() });
    }
    /* plugin-related */
    return [compact([token.tag, attrs].concat((token.nesting === 1) && getNext()))];
  }
};

function convertTree(tokens, convertRules, options) {
  function convertBranch(tkns, nested) {
    let branch = [];

    if (!nested) {
      branch.push('html');
    }

    function getNext() {
      return convertBranch(tkns, true);
    }

    let token = tkns.shift();
    while (token && token.nesting !== -1) {
      const attrs = token.attrs && zipObject(sortBy(token.attrs, 0));
      const children = token.children && convertBranch(token.children.slice(), true);
      const rule = convertRules[camelCase(token.type)] || convertRules.default;

      branch = branch.concat(
        rule(token, attrs, children, options, getNext)
      );
      token = tkns.shift();
    }
    return branch;
  }

  return convertBranch(tokens, false);
}

function mdReactFactory(options={}) {
  const { onIterate, tags=DEFAULT_TAGS,
    presetName, markdownOptions,
    enableRules=[], disableRules=[], plugins=[],
    onGenerateKey=(tag, index) => `mdrct-${tag}-${index}`,
    className } = options;

  let md = markdown(markdownOptions || presetName)
    .enable(enableRules)
    .disable(disableRules);

  const convertRules = assign({}, DEFAULT_RULES, options.convertRules);

  md = reduce(plugins, (m, plugin) =>
    plugin.plugin ?
    m.use(plugin.plugin, ...plugin.args) :
    m.use(plugin),
    md
  );

  function iterateTree(tree, level=0, index=0) {
    let tag = tree.shift();
    const key = onGenerateKey(tag, index);

    const props = (tree.length && isPlainObject(tree[0])) ?
      assign(tree.shift(), { key }) :
      { key };

    if (level === 0 && className) {
      props.className = className;
    }

    const children = tree.map(
      (branch, idx) => Array.isArray(branch) ?
        iterateTree(branch, level + 1, idx) :
        branch
    );

    tag = tags[tag] || tag;

    if (isString(props.style)) {
      props.style = zipObject(
        props.style.split(';')
          .map(prop => prop.split(':'))
          .map(keyVal => [camelCase(keyVal[0].trim()), keyVal[1].trim()])
      );
    }

    return (typeof onIterate === 'function') ?
      onIterate(tag, props, children, level) :
      React.createElement(tag, props, children);
  }

  return function(text) {
    const tree = convertTree(md.parse(text, {}), convertRules, md.options);
    return iterateTree(tree);
  };
}

class MDReactComponent extends Component {
  static propTypes = {
    text: PropTypes.string.isRequired,
    onIterate: PropTypes.func,
    onGenerateKey: PropTypes.func,
    tags: PropTypes.object,
    presetName: PropTypes.string,
    markdownOptions: PropTypes.object,
    enableRules: PropTypes.array,
    disableRules: PropTypes.array,
    convertRules: PropTypes.object,
    plugins: PropTypes.array,
    className: PropTypes.string
  }

  render() {
    const { text, ...props } = this.props;
    return mdReactFactory(props)(text);
  }
}

export default MDReactComponent;
export { mdReactFactory as mdReact };
<commit_msg>allows to use  preset name and options at the same time<commit_after>'use strict';

import markdown from 'markdown-it';
import React, { PropTypes, Component } from 'react';
import isPlainObject from 'lodash/lang/isPlainObject';
import assign from 'lodash/object/assign';
import reduce from 'lodash/collection/reduce';
import zipObject from 'lodash/array/zipObject';
import sortBy from 'lodash/collection/sortBy';
import compact from 'lodash/array/compact';
import camelCase from 'lodash/string/camelCase';
import isString from 'lodash/lang/isString';

const DEFAULT_TAGS = {
  'html': 'span'
};

const DEFAULT_RULES = {
  image(token, attrs, children) {
    if (children.length) {
      attrs = assign({}, attrs, { alt: children[0] });
    }
    return [[token.tag, attrs]];
  },

  codeInline(token, attrs) {
    return [compact([token.tag, attrs, token.content])];
  },

  codeBlock(token, attrs) {
    return [['pre', compact([token.tag, attrs, token.content])]];
  },

  fence(token, attrs) {
    if (token.info) {
      const langName = token.info.trim().split(/\s+/g)[0];
      attrs = assign({}, attrs, { 'data-language': langName });
    }

    return [['pre', compact([token.tag, attrs, token.content])]];
  },

  hardbreak() {
    return [['br']];
  },

  softbreak(token, attrs, children, options) {
    return options.breaks ? [['br']] : '\n';
  },

  text(token) {
    return token.content;
  },

  htmlBlock(token) {
    return token.content;
  },

  htmlInline(token) {
    return token.content;
  },

  inline(token, attrs, children) {
    return children;
  },

  default(token, attrs, children, options, getNext) {
    if (token.nesting === 1 && token.hidden) {
      return getNext();
    }
    /* plugin-related */
    if (!token.tag) {
      return token.content;
    }
    if (token.info) {
      attrs = assign({}, attrs, { 'data-info': token.info.trim() });
    }
    /* plugin-related */
    return [compact([token.tag, attrs].concat((token.nesting === 1) && getNext()))];
  }
};

function convertTree(tokens, convertRules, options) {
  function convertBranch(tkns, nested) {
    let branch = [];

    if (!nested) {
      branch.push('html');
    }

    function getNext() {
      return convertBranch(tkns, true);
    }

    let token = tkns.shift();
    while (token && token.nesting !== -1) {
      const attrs = token.attrs && zipObject(sortBy(token.attrs, 0));
      const children = token.children && convertBranch(token.children.slice(), true);
      const rule = convertRules[camelCase(token.type)] || convertRules.default;

      branch = branch.concat(
        rule(token, attrs, children, options, getNext)
      );
      token = tkns.shift();
    }
    return branch;
  }

  return convertBranch(tokens, false);
}

function mdReactFactory(options={}) {
  const { onIterate, tags=DEFAULT_TAGS,
    presetName, markdownOptions,
    enableRules=[], disableRules=[], plugins=[],
    onGenerateKey=(tag, index) => `mdrct-${tag}-${index}`,
    className } = options;

  let md = markdown(presetName || 'default', markdownOptions)
    .enable(enableRules)
    .disable(disableRules);

  const convertRules = assign({}, DEFAULT_RULES, options.convertRules);

  md = reduce(plugins, (m, plugin) =>
    plugin.plugin ?
    m.use(plugin.plugin, ...plugin.args) :
    m.use(plugin),
    md
  );

  function iterateTree(tree, level=0, index=0) {
    let tag = tree.shift();
    const key = onGenerateKey(tag, index);

    const props = (tree.length && isPlainObject(tree[0])) ?
      assign(tree.shift(), { key }) :
      { key };

    if (level === 0 && className) {
      props.className = className;
    }

    const children = tree.map(
      (branch, idx) => Array.isArray(branch) ?
        iterateTree(branch, level + 1, idx) :
        branch
    );

    tag = tags[tag] || tag;

    if (isString(props.style)) {
      props.style = zipObject(
        props.style.split(';')
          .map(prop => prop.split(':'))
          .map(keyVal => [camelCase(keyVal[0].trim()), keyVal[1].trim()])
      );
    }

    return (typeof onIterate === 'function') ?
      onIterate(tag, props, children, level) :
      React.createElement(tag, props, children);
  }

  return function(text) {
    const tree = convertTree(md.parse(text, {}), convertRules, md.options);
    return iterateTree(tree);
  };
}

class MDReactComponent extends Component {
  static propTypes = {
    text: PropTypes.string.isRequired,
    onIterate: PropTypes.func,
    onGenerateKey: PropTypes.func,
    tags: PropTypes.object,
    presetName: PropTypes.string,
    markdownOptions: PropTypes.object,
    enableRules: PropTypes.array,
    disableRules: PropTypes.array,
    convertRules: PropTypes.object,
    plugins: PropTypes.array,
    className: PropTypes.string
  }

  render() {
    const { text, ...props } = this.props;
    return mdReactFactory(props)(text);
  }
}

export default MDReactComponent;
export { mdReactFactory as mdReact };
<|endoftext|>


<commit_before>;(function(window, _, $, undefined){

  var Class = $.mvc.Class = function(){

    //Define a class-factory and an actual class for this "class".
    var ClassFactory = {}
      , noop = function(){}
      , Class = noop;

    //Extend the factory with function that can extend the class.
    _(ClassFactory).extend({

      //Set a parent class.
      extends: function(parent){

        //Add the statics of our parent to our statics.
        Class.__proto__ = parent;

        //Add the prototype of our parent to our own prototype.
        Class.prototype.__proto__ = parent.prototype;

        //If we haven't defined our own constructor, use a function that will call the parent.
        if(Class == noop){
          this.construct(function(){
            parent.apply(this, arguments);
          });
        }

        //Keep an easy reference to parent.
        Class.prototype.parent = parent;

        //Enable chaining.
        return this;

      },

      //Add static members to the class.
      statics: function(map){

        //Add the members in the map to the Class object.
        _(Class).extend(map);

        //Enable chaining.
        return this;

      },

      //Change the constructor (default is a noop).
      construct: function(constructor){

        //Remember the old class properties.
        var oldMembers = Class.prototype || {};
        var oldStatics = _({}).extend(Class);

        //Set the new constructor.
        Class = constructor;
        Class.prototype = oldMembers;
        _(Class).extend(oldStatics);

        //Add a reference to the classes static members from within the classes normal members.
        _(Class.prototype).extend({
          static: Class
        });

        //Enable chaining.
        return this;

      },

      //Add normal members to the class.
      members: function(map){

        //Add the members in the map to the Class's prototype.
        _(Class.prototype).extend(map);

        //Enable chaining.
        return this;

      },

      //Returns the class.
      finalize: function(){
        return Class;
      }

    });

    //Add a default noop constructor.
    ClassFactory.construct(noop);

    //Return the class-factory.
    return ClassFactory;

  }

})(window, _, (jQuery || Zepto || $ || ($={})));
<commit_msg>Separated the ClassFactory from the Class creation function and exposed it. So that it may be extended.<commit_after>;(function(window, _, $, undefined){

  var ClassFactory = $.mvc.ClassFactory = function(Class){
    this.Class = this.noop = Class;
  }

  //Extend the factory with functions that can extend the class.
  _(ClassFactory.prototype).extend({

    //Set a parent class.
    extends: function(parent){

      //Add the statics of our parent to our statics.
      this.Class.__proto__ = this.Class.parent = parent;

      //Add the prototype of our parent to our own prototype.
      this.Class.prototype.__proto__ = parent.prototype;

      //If we haven't defined our own constructor, use a function that will call the parent.
      if(this.Class == this.noop){
        this.construct(function(){
          parent.apply(this, arguments);
        });
      }

      //Keep an easy reference to parent.
      this.Class.prototype.parent = parent;

      //Enable chaining.
      return this;

    },

    //Add static members to the class.
    statics: function(map){

      //Add the members in the map to the Class object.
      _(this.Class).extend(map);

      //Enable chaining.
      return this;

    },

    //Change the constructor (default is a noop).
    construct: function(constructor){

      //Remember the old class properties.
      var oldMembers = this.Class.prototype || {};
      var oldStatics = _({}).extend(this.Class);

      //Set the new constructor.
      this.Class = constructor;
      this.Class.prototype = oldMembers;
      _(this.Class).extend(oldStatics);

      //Add a reference to the class' static members from within the class' normal members.
      _(this.Class.prototype).extend({
        static: this.Class
      });

      //Enable chaining.
      return this;

    },

    //Add normal members to the class.
    members: function(map){

      //Add the members in the map to the Class' prototype.
      _(this.Class.prototype).extend(map);

      //Enable chaining.
      return this;

    },

    //Returns the class.
    finalize: function(){
      return this.Class;
    }

  });

  var Class = $.mvc.Class = function(){

    //Define the class and its factory.
    var noop = function(){}
      , Class = noop
      , factory = new ClassFactory(Class);

    //Add a default noop constructor.
    factory.construct(noop);

    //Return the class-factory.
    return factory;

  }

})(window, _, (jQuery || Zepto || $ || ($={})));
<|endoftext|>


<commit_before>'use strict';
export Button from './button';
export Icon from './icon';
export Dropdown from './dropdown';
export Table from './table';
export Pagination from './pagination';
export Radio from './radio';
export Checkbox from './checkbox';
export Tabs from './tabs';
export Input, { InputNumber } from './input';
export message from './message';
export Tooltip from './tooltip';
export Modal from './modal';
export List  from  './list';
export Avatar from './avatar';
export Menu from './menu';
export Select from './select';
export Steps from './steps';
export Carousel from './carousel';
export Slider from './slider';
export Form from './form';
export ColorPicker from './color_picker';
export Datasheet from './datasheet';
export DatePicker from './date_picker';
export Checkbox2 from './checkbox2';
/* begin --- import antd component **/
export Row from 'antd/lib/row';
export Col from 'antd/lib/col';
// export Form from 'antd/lib/form';
export Layout from 'antd/lib/layout';
export Upload from 'antd/lib/upload';
export Progress from 'antd/lib/progress';
export Collapse from 'antd/lib/collapse';
export Popover from 'antd/lib/popover';
export Tree from 'antd/lib/tree';
// export DatePicker from 'antd/lib/date-picker';
export Spin from 'antd/lib/spin'
/* endt --- import antd component **/
<commit_msg>原来的datepickrer<commit_after>'use strict';
export Button from './button';
export Icon from './icon';
export Dropdown from './dropdown';
export Table from './table';
export Pagination from './pagination';
export Radio from './radio';
export Checkbox from './checkbox';
export Tabs from './tabs';
export Input, { InputNumber } from './input';
export message from './message';
export Tooltip from './tooltip';
export Modal from './modal';
export List  from  './list';
export Avatar from './avatar';
export Menu from './menu';
export Select from './select';
export Steps from './steps';
export Carousel from './carousel';
export Slider from './slider';
export Form from './form';
export ColorPicker from './color_picker';
export Datasheet from './datasheet';
export DatePicker from './date_picker';
export Checkbox2 from './checkbox2';
/* begin --- import antd component **/
export Row from 'antd/lib/row';
export Col from 'antd/lib/col';
// export Form from 'antd/lib/form';
export Layout from 'antd/lib/layout';
export Upload from 'antd/lib/upload';
export Progress from 'antd/lib/progress';
export Collapse from 'antd/lib/collapse';
export Popover from 'antd/lib/popover';
export Tree from 'antd/lib/tree';
export DatePicker2 from 'antd/lib/date-picker';
export Spin from 'antd/lib/spin'
/* endt --- import antd component **/
<|endoftext|>