<commit_before>/* global Vue */

import './mnist-cnn.css'

import debounce from 'lodash/debounce'
import range from 'lodash/range'
import * as utils from './utils'

const MODEL_CONFIG = {
  filepaths: {
    model: '/demos/data/mnist_cnn/mnist_cnn.json',
    weights: '/demos/data/mnist_cnn/mnist_cnn_weights.buf',
    metadata: '/demos/data/mnist_cnn/mnist_cnn_metadata.json'
  },
  gpu: false
}

if (process.env.NODE_ENV === 'production') {
  Object.assign(MODEL_CONFIG, {
    filepaths: {
      model: 'demos/data/mnist_cnn/mnist_cnn.json',
      weights: 'demos/data/mnist_cnn/mnist_cnn_weights.buf',
      metadata: 'demos/data/mnist_cnn/mnist_cnn_metadata.json'
    }
  })
}

const LAYER_DISPLAY_CONFIG = {
  'convolution2d_1': {
    heading: '32 3x3 filters, border mode valid, 1x1 strides',
    scalingFactor: 2
  },
  'activation_1': {
    heading: 'ReLU',
    scalingFactor: 2
  },
  'convolution2d_2': {
    heading: '32 3x3 filters, border mode valid, 1x1 strides',
    scalingFactor: 2
  },
  'activation_2': {
    heading: 'ReLU',
    scalingFactor: 2
  },
  'maxpooling2d_1': {
    heading: '2x2 pools, 1x1 strides',
    scalingFactor: 2
  },
  'dropout_1': {
    heading: 'p=0.25 (only active during training phase)',
    scalingFactor: 2
  },
  'flatten_1': {
    heading: '',
    scalingFactor: 2
  },
  'dense_1': {
    heading: 'output dimensionality 128',
    scalingFactor: 4
  },
  'activation_3': {
    heading: 'ReLU',
    scalingFactor: 4
  },
  'dropout_2': {
    heading: 'p=0.5 (only active during training phase)',
    scalingFactor: 4
  },
  'dense_2': {
    heading: 'output dimensionality 10',
    scalingFactor: 8
  },
  'activation_4': {
    heading: 'Softmax',
    scalingFactor: 8
  }
}

/**
 *
 * VUE COMPONENT
 *
 */
export const MnistCnn = Vue.extend({
  template: `

Basic Convnet - MNIST

    <div class="loading-progress" v-if="modelLoading && loadingProgress < 100">
      Loading...{{ loadingProgress }}%

Draw any digit (0-9) here ⤸

            <canvas
              id="input-canvas" width="240" height="240"
              @mousedown="activateDraw"
              @mouseup="deactivateDrawAndPredict"
              @mouseleave="deactivateDrawAndPredict"
              @mousemove="draw"
              @touchstart="activateDraw"
              @touchend="deactivateDrawAndPredict"
              @touchmove="draw"
            ></canvas>
            <canvas id="input-canvas-scaled" width="28" height="28" style="display:none;"></canvas>
            <canvas id="input-canvas-centercrop" style="display:none;"></canvas>

            clearCLEAR

        <mdl-switch :checked.sync="useGpu" @click="toggleGpu">Use GPU</mdl-switch>

{{ i }}

          {{ layerResult.layerClass }}
           {{ layerDisplayConfig[layerResult.name].heading }}

          <canvas v-for="image in layerResult.images"
            id="intermediate-result-{{ $parent.$index }}-{{ $index }}"
            width="{{ image.width }}"
            height="{{ image.height }}"
            style="display:none;"
          ></canvas>
          <canvas v-for="image in layerResult.images"
            id="intermediate-result-{{ $parent.$index }}-{{ $index }}-scaled"
            width="{{ layerDisplayConfig[layerResult.name].scalingFactor * image.width }}"
            height="{{ layerDisplayConfig[layerResult.name].scalingFactor * image.height }}"
          ></canvas>

  `,

  data: function () {
    return {
      model: new KerasJS.Model(MODEL_CONFIG),
      modelLoading: true,
      input: new Float32Array(784),
      output: new Float32Array(10),
      outputClasses: range(10),
      layerResultImages: [],
      layerDisplayConfig: LAYER_DISPLAY_CONFIG,
      drawing: false,
      strokes: [],
      useGpu: MODEL_CONFIG.gpu
    }
  },

  computed: {
    loadingProgress: function () {
      return this.model.getLoadingProgress()
    },
    predictedClass: function () {
      if (this.output.reduce((a, b) => a + b, 0) === 0) {
        return -1
      }
      return this.output.reduce((argmax, n, i) => n > this.output[argmax] ? i : argmax, 0)
    }
  },

  created: function () {
    // initialize KerasJS model
    this.model.initialize()
    this.model.ready().then(() => {
      this.modelLoading = false
      this.getIntermediateResults()
    })
  },

  methods: {

    clear: function (e) {
      this.clearIntermediateResults()
      const ctx = document.getElementById('input-canvas').getContext('2d')
      ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height)
      const ctxCenterCrop = document.getElementById('input-canvas-centercrop').getContext('2d')
      ctxCenterCrop.clearRect(0, 0, ctxCenterCrop.canvas.width, ctxCenterCrop.canvas.height)
      const ctxScaled = document.getElementById('input-canvas-scaled').getContext('2d')
      ctxScaled.clearRect(0, 0, ctxScaled.canvas.width, ctxScaled.canvas.height)
      this.output = new Float32Array(10)
      this.drawing = false
      this.strokes = []
    },

    activateDraw: function (e) {
      this.drawing = true
      this.strokes.push([])
      let points = this.strokes[this.strokes.length - 1]
      points.push(utils.getCoordinates(e))
    },

    draw: function (e) {
      if (!this.drawing) return

      const ctx = document.getElementById('input-canvas').getContext('2d')

      ctx.lineWidth = 20
      ctx.lineJoin = ctx.lineCap = 'round'
      ctx.strokeStyle = '#393E46'

      ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height)

      let points = this.strokes[this.strokes.length - 1]
      points.push(utils.getCoordinates(e))

      // draw individual strokes
      for (let s = 0, slen = this.strokes.length; s < slen; s++) {
        points = this.strokes[s]

        let p1 = points[0]
        let p2 = points[1]
        ctx.beginPath()
        ctx.moveTo(...p1)

        // draw points in stroke
        // quadratic bezier curve
        for (let i = 1, len = points.length; i < len; i++) {
          ctx.quadraticCurveTo(...p1, ...utils.getMidpoint(p1, p2))
          p1 = points[i]
          p2 = points[i + 1]
        }
        ctx.lineTo(...p1)
        ctx.stroke()
      }
    },

    deactivateDrawAndPredict: debounce(function () {
      if (!this.drawing) return
      this.drawing = false

      const ctx = document.getElementById('input-canvas').getContext('2d')

      // center crop
      const imageDataCenterCrop = utils.centerCrop(ctx.getImageData(0, 0, ctx.canvas.width, ctx.canvas.height))
      const ctxCenterCrop = document.getElementById('input-canvas-centercrop').getContext('2d')
      ctxCenterCrop.canvas.width = imageDataCenterCrop.width
      ctxCenterCrop.canvas.height = imageDataCenterCrop.height
      ctxCenterCrop.putImageData(imageDataCenterCrop, 0, 0)

      // scaled to 28 x 28
      const ctxScaled = document.getElementById('input-canvas-scaled').getContext('2d')
      ctxScaled.save()
      ctxScaled.scale(28 / ctxCenterCrop.canvas.width, 28 / ctxCenterCrop.canvas.height)
      ctxScaled.clearRect(0, 0, ctxCenterCrop.canvas.width, ctxCenterCrop.canvas.height)
      ctxScaled.drawImage(document.getElementById('input-canvas-centercrop'), 0, 0)
      const imageDataScaled = ctxScaled.getImageData(0, 0, ctxScaled.canvas.width, ctxScaled.canvas.height)
      ctxScaled.restore()

      // process image data for model input
      const { data } = imageDataScaled
      this.input = new Float32Array(784)
      for (let i = 0, len = data.length; i < len; i += 4) {
        this.input[i / 4] = data[i + 3] / 255
      }

      this.output = this.model.predict({ input: this.input }).output
      this.getIntermediateResults()
    }, 200, { leading: true, trailing: true }),

    getIntermediateResults: function () {
      let results = []
      for (let [name, layer] of this.model.modelLayersMap.entries()) {
        if (name === 'input') continue

        const layerClass = layer.layerClass || ''

        let images = []
        if (layer.result && layer.result.tensor.shape.length === 3) {
          images = utils.unroll3Dtensor(layer.result.tensor)
        } else if (layer.result && layer.result.tensor.shape.length === 2) {
          images = [utils.image2Dtensor(layer.result.tensor)]
        } else if (layer.result && layer.result.tensor.shape.length === 1) {
          images = [utils.image1Dtensor(layer.result.tensor)]
        }
        results.push({
          name,
          layerClass,
          images
        })
      }
      this.layerResultImages = results
      setTimeout(() => {
        this.showIntermediateResults()
      }, 0)
    },

    showIntermediateResults: function () {
      this.layerResultImages.forEach((result, layerNum) => {
        const scalingFactor = this.layerDisplayConfig[result.name].scalingFactor
        result.images.forEach((image, imageNum) => {
          let ctx = document.getElementById(`intermediate-result-${layerNum}-${imageNum}`).getContext('2d')
          ctx.putImageData(image, 0, 0)
          let ctxScaled = document.getElementById(`intermediate-result-${layerNum}-${imageNum}-scaled`).getContext('2d')
          ctxScaled.save()
          ctxScaled.scale(scalingFactor, scalingFactor)
          ctxScaled.clearRect(0, 0, ctxScaled.canvas.width, ctxScaled.canvas.height)
          ctxScaled.drawImage(document.getElementById(`intermediate-result-${layerNum}-${imageNum}`), 0, 0)
          ctxScaled.restore()
        })
      })
    },

    clearIntermediateResults: function () {
      this.layerResultImages.forEach((result, layerNum) => {
        const scalingFactor = this.layerDisplayConfig[result.name].scalingFactor
        result.images.forEach((image, imageNum) => {
          let ctxScaled = document.getElementById(`intermediate-result-${layerNum}-${imageNum}-scaled`).getContext('2d')
          ctxScaled.save()
          ctxScaled.scale(scalingFactor, scalingFactor)
          ctxScaled.clearRect(0, 0, ctxScaled.canvas.width, ctxScaled.canvas.height)
          ctxScaled.restore()
        })
      })
    },

    toggleGpu: function () {
      this.model.gpu = !this.useGpu
    }
  }
})
<commit_msg>use release-attached binaries for demo data files<commit_after>/* global Vue */

import './mnist-cnn.css'

import debounce from 'lodash/debounce'
import range from 'lodash/range'
import * as utils from './utils'

const MODEL_CONFIG = {
  filepaths: {
    model: '/demos/data/mnist_cnn/mnist_cnn.json',
    weights: '/demos/data/mnist_cnn/mnist_cnn_weights.buf',
    metadata: '/demos/data/mnist_cnn/mnist_cnn_metadata.json'
  },
  gpu: false
}

if (process.env.NODE_ENV === 'production') {
  Object.assign(MODEL_CONFIG, {
    filepaths: {
      model: 'demos/data/mnist_cnn/mnist_cnn.json',
      weights: 'https://github.com/transcranial/keras-js/releases/download/v0.0.1/mnist_cnn_weights.buf',
      metadata: 'demos/data/mnist_cnn/mnist_cnn_metadata.json'
    }
  })
}

const LAYER_DISPLAY_CONFIG = {
  'convolution2d_1': {
    heading: '32 3x3 filters, border mode valid, 1x1 strides',
    scalingFactor: 2
  },
  'activation_1': {
    heading: 'ReLU',
    scalingFactor: 2
  },
  'convolution2d_2': {
    heading: '32 3x3 filters, border mode valid, 1x1 strides',
    scalingFactor: 2
  },
  'activation_2': {
    heading: 'ReLU',
    scalingFactor: 2
  },
  'maxpooling2d_1': {
    heading: '2x2 pools, 1x1 strides',
    scalingFactor: 2
  },
  'dropout_1': {
    heading: 'p=0.25 (only active during training phase)',
    scalingFactor: 2
  },
  'flatten_1': {
    heading: '',
    scalingFactor: 2
  },
  'dense_1': {
    heading: 'output dimensionality 128',
    scalingFactor: 4
  },
  'activation_3': {
    heading: 'ReLU',
    scalingFactor: 4
  },
  'dropout_2': {
    heading: 'p=0.5 (only active during training phase)',
    scalingFactor: 4
  },
  'dense_2': {
    heading: 'output dimensionality 10',
    scalingFactor: 8
  },
  'activation_4': {
    heading: 'Softmax',
    scalingFactor: 8
  }
}

/**
 *
 * VUE COMPONENT
 *
 */
export const MnistCnn = Vue.extend({
  template: `

Basic Convnet - MNIST

    <div class="loading-progress" v-if="modelLoading && loadingProgress < 100">
      Loading...{{ loadingProgress }}%

Draw any digit (0-9) here ⤸

            <canvas
              id="input-canvas" width="240" height="240"
              @mousedown="activateDraw"
              @mouseup="deactivateDrawAndPredict"
              @mouseleave="deactivateDrawAndPredict"
              @mousemove="draw"
              @touchstart="activateDraw"
              @touchend="deactivateDrawAndPredict"
              @touchmove="draw"
            ></canvas>
            <canvas id="input-canvas-scaled" width="28" height="28" style="display:none;"></canvas>
            <canvas id="input-canvas-centercrop" style="display:none;"></canvas>

            clearCLEAR

        <mdl-switch :checked.sync="useGpu" @click="toggleGpu">Use GPU</mdl-switch>

{{ i }}

          {{ layerResult.layerClass }}
           {{ layerDisplayConfig[layerResult.name].heading }}

          <canvas v-for="image in layerResult.images"
            id="intermediate-result-{{ $parent.$index }}-{{ $index }}"
            width="{{ image.width }}"
            height="{{ image.height }}"
            style="display:none;"
          ></canvas>
          <canvas v-for="image in layerResult.images"
            id="intermediate-result-{{ $parent.$index }}-{{ $index }}-scaled"
            width="{{ layerDisplayConfig[layerResult.name].scalingFactor * image.width }}"
            height="{{ layerDisplayConfig[layerResult.name].scalingFactor * image.height }}"
          ></canvas>

  `,

  data: function () {
    return {
      model: new KerasJS.Model(MODEL_CONFIG),
      modelLoading: true,
      input: new Float32Array(784),
      output: new Float32Array(10),
      outputClasses: range(10),
      layerResultImages: [],
      layerDisplayConfig: LAYER_DISPLAY_CONFIG,
      drawing: false,
      strokes: [],
      useGpu: MODEL_CONFIG.gpu
    }
  },

  computed: {
    loadingProgress: function () {
      return this.model.getLoadingProgress()
    },
    predictedClass: function () {
      if (this.output.reduce((a, b) => a + b, 0) === 0) {
        return -1
      }
      return this.output.reduce((argmax, n, i) => n > this.output[argmax] ? i : argmax, 0)
    }
  },

  created: function () {
    // initialize KerasJS model
    this.model.initialize()
    this.model.ready().then(() => {
      this.modelLoading = false
      this.getIntermediateResults()
    })
  },

  methods: {

    clear: function (e) {
      this.clearIntermediateResults()
      const ctx = document.getElementById('input-canvas').getContext('2d')
      ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height)
      const ctxCenterCrop = document.getElementById('input-canvas-centercrop').getContext('2d')
      ctxCenterCrop.clearRect(0, 0, ctxCenterCrop.canvas.width, ctxCenterCrop.canvas.height)
      const ctxScaled = document.getElementById('input-canvas-scaled').getContext('2d')
      ctxScaled.clearRect(0, 0, ctxScaled.canvas.width, ctxScaled.canvas.height)
      this.output = new Float32Array(10)
      this.drawing = false
      this.strokes = []
    },

    activateDraw: function (e) {
      this.drawing = true
      this.strokes.push([])
      let points = this.strokes[this.strokes.length - 1]
      points.push(utils.getCoordinates(e))
    },

    draw: function (e) {
      if (!this.drawing) return

      const ctx = document.getElementById('input-canvas').getContext('2d')

      ctx.lineWidth = 20
      ctx.lineJoin = ctx.lineCap = 'round'
      ctx.strokeStyle = '#393E46'

      ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height)

      let points = this.strokes[this.strokes.length - 1]
      points.push(utils.getCoordinates(e))

      // draw individual strokes
      for (let s = 0, slen = this.strokes.length; s < slen; s++) {
        points = this.strokes[s]

        let p1 = points[0]
        let p2 = points[1]
        ctx.beginPath()
        ctx.moveTo(...p1)

        // draw points in stroke
        // quadratic bezier curve
        for (let i = 1, len = points.length; i < len; i++) {
          ctx.quadraticCurveTo(...p1, ...utils.getMidpoint(p1, p2))
          p1 = points[i]
          p2 = points[i + 1]
        }
        ctx.lineTo(...p1)
        ctx.stroke()
      }
    },

    deactivateDrawAndPredict: debounce(function () {
      if (!this.drawing) return
      this.drawing = false

      const ctx = document.getElementById('input-canvas').getContext('2d')

      // center crop
      const imageDataCenterCrop = utils.centerCrop(ctx.getImageData(0, 0, ctx.canvas.width, ctx.canvas.height))
      const ctxCenterCrop = document.getElementById('input-canvas-centercrop').getContext('2d')
      ctxCenterCrop.canvas.width = imageDataCenterCrop.width
      ctxCenterCrop.canvas.height = imageDataCenterCrop.height
      ctxCenterCrop.putImageData(imageDataCenterCrop, 0, 0)

      // scaled to 28 x 28
      const ctxScaled = document.getElementById('input-canvas-scaled').getContext('2d')
      ctxScaled.save()
      ctxScaled.scale(28 / ctxCenterCrop.canvas.width, 28 / ctxCenterCrop.canvas.height)
      ctxScaled.clearRect(0, 0, ctxCenterCrop.canvas.width, ctxCenterCrop.canvas.height)
      ctxScaled.drawImage(document.getElementById('input-canvas-centercrop'), 0, 0)
      const imageDataScaled = ctxScaled.getImageData(0, 0, ctxScaled.canvas.width, ctxScaled.canvas.height)
      ctxScaled.restore()

      // process image data for model input
      const { data } = imageDataScaled
      this.input = new Float32Array(784)
      for (let i = 0, len = data.length; i < len; i += 4) {
        this.input[i / 4] = data[i + 3] / 255
      }

      this.output = this.model.predict({ input: this.input }).output
      this.getIntermediateResults()
    }, 200, { leading: true, trailing: true }),

    getIntermediateResults: function () {
      let results = []
      for (let [name, layer] of this.model.modelLayersMap.entries()) {
        if (name === 'input') continue

        const layerClass = layer.layerClass || ''

        let images = []
        if (layer.result && layer.result.tensor.shape.length === 3) {
          images = utils.unroll3Dtensor(layer.result.tensor)
        } else if (layer.result && layer.result.tensor.shape.length === 2) {
          images = [utils.image2Dtensor(layer.result.tensor)]
        } else if (layer.result && layer.result.tensor.shape.length === 1) {
          images = [utils.image1Dtensor(layer.result.tensor)]
        }
        results.push({
          name,
          layerClass,
          images
        })
      }
      this.layerResultImages = results
      setTimeout(() => {
        this.showIntermediateResults()
      }, 0)
    },

    showIntermediateResults: function () {
      this.layerResultImages.forEach((result, layerNum) => {
        const scalingFactor = this.layerDisplayConfig[result.name].scalingFactor
        result.images.forEach((image, imageNum) => {
          let ctx = document.getElementById(`intermediate-result-${layerNum}-${imageNum}`).getContext('2d')
          ctx.putImageData(image, 0, 0)
          let ctxScaled = document.getElementById(`intermediate-result-${layerNum}-${imageNum}-scaled`).getContext('2d')
          ctxScaled.save()
          ctxScaled.scale(scalingFactor, scalingFactor)
          ctxScaled.clearRect(0, 0, ctxScaled.canvas.width, ctxScaled.canvas.height)
          ctxScaled.drawImage(document.getElementById(`intermediate-result-${layerNum}-${imageNum}`), 0, 0)
          ctxScaled.restore()
        })
      })
    },

    clearIntermediateResults: function () {
      this.layerResultImages.forEach((result, layerNum) => {
        const scalingFactor = this.layerDisplayConfig[result.name].scalingFactor
        result.images.forEach((image, imageNum) => {
          let ctxScaled = document.getElementById(`intermediate-result-${layerNum}-${imageNum}-scaled`).getContext('2d')
          ctxScaled.save()
          ctxScaled.scale(scalingFactor, scalingFactor)
          ctxScaled.clearRect(0, 0, ctxScaled.canvas.width, ctxScaled.canvas.height)
          ctxScaled.restore()
        })
      })
    },

    toggleGpu: function () {
      this.model.gpu = !this.useGpu
    }
  }
})
<|endoftext|>


<commit_before>/**
* @module helpers
*/
var fs = require('fs');
var yaml = require('../../node_modules/js-yaml');
var configFile = './.lifter/lifter.yml';

/**
* Function that takes in an array of tasks that will be individually
*   passed to the spawn function, then runs each task in series. At the
*   end of all the tasks, a final callback will be invoked.
* @function
* @memberof module:helpers
* EXAMPLE
*   spawnSeries([
*    ['ls', ['-lh', '/usr']],
*    ['ls', ['-la']]
*   ], function() { console.log('all done!'); });
*/
var spawnSeries = function(tasks, callback) {
  callback = callback || function() {};
  var completed = 0;
  var iterate = function() {
    if (++completed < tasks.length) {
      run();
    } else {
      callback();
    }
  }
  var run = function() {
    var task = tasks[completed];
    var proc = spawn.apply(null, task);
    proc.stdout.on('data', function(data) {
      console.log(data.toString());
    });
    proc.stderr.on('data', function(data) {
      console.log(data.toString());
    });
    proc.on('exit', function(code) {
      // console.log('EXIT CODE:', code);
      iterate();
    });
  }
  run();
}

/**
* Function that reads the lifter.yml file
* @function
* @memberof module:helpers
*/

var readYAML = function() {
  var out;
  try {
    var content = fs.readFileSync(configFile);
    out = yaml.safeLoad(content);
  } catch (e) {
    console.log(e.code, e.path);
    if (e.code === 'ENOENT') {
      console.log('... config file does not exist, please run lifter config');
    }
    process.exit();
  }
  return out;
}

var sendVMInfoToYAML = function(vmName, vmUser, callback){

  var newVMName = 'vmName: ' + vmName;
  var newVMUsername = 'vmUserName: ' + vmUser;

  console.log(newVMName, newVMUsername);
  fs.readFile(configFile, 'utf8', function(err, data){
    if(err){
      console.log('ERR: ', err);
    } else if(/vmName|vmUserName/g.test(data)) {
      console.log("Overwriting existing vm info in YAML file");
      var replace = data.replace(/vmName.*/g, newVMName).replace(/vmUserName.*/g, newVMUsername);
      fs.writeFile(configFile, replace, 'utf8', function(err){
        if(err){
          console.log('ERROR Could not update YAML with new vm info: ', err);
        }
        console.log("Writing Deploy script...");
        callback();
      });
    } else {
      console.log("Appending vm info into YAML file");
      var append = '\n'+ newVMName + '\n' + newVMUsername;
      fs.appendFile(configFile, append, function(err){
        if(err){
          console.log('ERROR Could not update YAML with new vm info: ', err);
        }
        console.log("Writing Deploy script...");
        callback();
      });
    }
  });
};

module.exports = {
  spawnSeries: spawnSeries,
  readYAML: readYAML,
  sendVMInfoToYAML: sendVMInfoToYAML,
  configFile: configFile
}<commit_msg>remove console log and document code<commit_after>/**
* @module helpers
*/
var fs = require('fs');
var yaml = require('../../node_modules/js-yaml');
var configFile = './.lifter/lifter.yml';

/**
* Function that takes in an array of tasks that will be individually
*   passed to the spawn function, then runs each task in series. At the
*   end of all the tasks, a final callback will be invoked.
* @function
* @memberof module:helpers
* EXAMPLE
*   spawnSeries([
*    ['ls', ['-lh', '/usr']],
*    ['ls', ['-la']]
*   ], function() { console.log('all done!'); });
*/
var spawnSeries = function(tasks, callback) {
  callback = callback || function() {};
  var completed = 0;
  var iterate = function() {
    if (++completed < tasks.length) {
      run();
    } else {
      callback();
    }
  }
  var run = function() {
    var task = tasks[completed];
    var proc = spawn.apply(null, task);
    proc.stdout.on('data', function(data) {
      console.log(data.toString());
    });
    proc.stderr.on('data', function(data) {
      console.log(data.toString());
    });
    proc.on('exit', function(code) {
      // console.log('EXIT CODE:', code);
      iterate();
    });
  }
  run();
}

/**
* Function that reads the lifter.yml file
* @function
* @memberof module:helpers
*/

var readYAML = function() {
  var out;
  try {
    var content = fs.readFileSync(configFile);
    out = yaml.safeLoad(content);
  } catch (e) {
    console.log(e.code, e.path);
    if (e.code === 'ENOENT') {
      console.log('... config file does not exist, please run lifter config');
    }
    process.exit();
  }
  return out;
}

/**
* Function that writes new vm info into lifter.yml
* @function
* @memberof module:helpers
*/

var sendVMInfoToYAML = function(vmName, vmUser, callback){

  var newVMName = 'vmName: ' + vmName;
  var newVMUsername = 'vmUserName: ' + vmUser;

  fs.readFile(configFile, 'utf8', function(err, data){
    if(err){
      console.log('ERR: ', err);
    // overwrites vm info in yaml file if it already exits
    } else if(/vmName|vmUserName/g.test(data)) {
      console.log("Overwriting existing vm info in YAML file");
      var replace = data.replace(/vmName.*/g, newVMName).replace(/vmUserName.*/g, newVMUsername);
      fs.writeFile(configFile, replace, 'utf8', function(err){
        if(err){
          console.log('ERROR Could not update YAML with new vm info: ', err);
        }
        console.log("Writing Deploy script...");
        callback();
      });
    // appends new vm info to yaml file if the info is not already there
    } else {
      console.log("Appending vm info into YAML file");
      var append = '\n'+ newVMName + '\n' + newVMUsername;
      fs.appendFile(configFile, append, function(err){
        if(err){
          console.log('ERROR Could not update YAML with new vm info: ', err);
        }
        console.log("Writing Deploy script...");
        callback();
      });
    }
  });
};

module.exports = {
  spawnSeries: spawnSeries,
  readYAML: readYAML,
  sendVMInfoToYAML: sendVMInfoToYAML,
  configFile: configFile
}<|endoftext|>


<commit_before>var SCROLL_STEP_SIZE = 60; // Pixels

document.addEventListener("keydown", onKeydown);
document.addEventListener("focus", onFocusCapturePhase, true);
document.addEventListener("blur", onBlurCapturePhase, true);

// Send the key to the key handler in the background page.
var keyPort = chrome.extension.connect({name: "keyDown"});
var keyCodes = { ESC: 27 };
var insertMode = false;

function scrollToBottom() { window.scrollTo(0, document.body.scrollHeight); }
function scrollToTop() { window.scrollTo(0, 0); }
function scrollUp() { window.scrollBy(0, -1 * SCROLL_STEP_SIZE); }
function scrollDown() { window.scrollBy(0, SCROLL_STEP_SIZE); }
function scrollPageUp() { window.scrollBy(0, -6 * SCROLL_STEP_SIZE); }
function scrollPageDown() { window.scrollBy(0, 6 * SCROLL_STEP_SIZE); }
function scrollLeft() { window.scrollBy(-1 * SCROLL_STEP_SIZE, 0); }
function scrollRight() { window.scrollBy(SCROLL_STEP_SIZE, 0); }

function reload() { window.location.reload(); }
function goBack() { history.back(); }
function goForward() { history.forward(); }

chrome.extension.onConnect.addListener(function (port, name) {
  if (port.name == "executePageCommand") {
    port.onMessage.addListener(function (args) {
      if (this[args.command])
      {
        for (var i = 0; i < args.count; i++) { this[args.command].call(); }
      }
    });
  }
  else if (port.name == "getScrollPosition") {
    port.onMessage.addListener(function (args) {
      var scrollPort = chrome.extension.connect({ name: "returnScrollPosition" });
      scrollPort.postMessage({
        scrollX: window.scrollX,
        scrollY: window.scrollY,
        currentTab: args.currentTab
      });
    });
  } else if (port.name == "setScrollPosition") {
    port.onMessage.addListener(function (args) {
      if (args.scrollX > 0 || args.scrollY > 0) { window.scrollBy(args.scrollX, args.scrollY); }
    });
  }
});

/**
 * Sends everything except i & ESC to the handler in background_page. i & ESC are special because they control
 * insert mode which is local state to the page. The key will be are either a single ascii letter or a
 * key-modifier pair, e.g. <c-a> for control a.
 *
 * Note that some keys will only register keydown events and not keystroke events, e.g. ESC.
 */
function onKeydown(event) {
  var keyChar = "";

  // Ignore modifier keys by themselves.
  if (event.keyCode > 31 && event.keyCode < 127) {
    keyChar = String.fromCharCode(event.keyCode).toLowerCase();
    if (event.shiftKey)
      keyChar = keyChar.toUpperCase();
    if (event.ctrlKey)
      keyChar = "<c-" + keyChar + ">";
  }

  if (insertMode && event.keyCode == keyCodes.ESC)
    exitInsertMode();
  else if (!insertMode && keyChar == "i")
    enterInsertMode();
  else if (!insertMode && keyChar)
    keyPort.postMessage(keyChar);
}

function onFocusCapturePhase(event) {
  if (event.target.tagName == "INPUT" || event.target.tagName == "TEXTAREA")
    enterInsertMode();
}

function onBlurCapturePhase(event) {
  if (event.target.tagName == "INPUT" || event.target.tagName == "TEXTAREA")
    exitInsertMode();
}

function enterInsertMode() {
  insertMode = true;
  HUD.show("Insert mode");
}

function exitInsertMode() {
  insertMode = false;
  HUD.hide();
}

HUD = {
  show:function(text) {
    HUD.displayElement().innerHTML = text;
    HUD.displayElement().style.display = "";
  },

  /*
   * Retrieves the HUD HTML element, creating it if necessary.
   */
  displayElement: function() {
    if (!HUD._displayElement) {
      var element = document.createElement("div");
      element.innerHTML = "howdy";
      element.style.position = "fixed";
      element.style.bottom = "0px";
      element.style.left = "10px";
      element.style.backgroundColor = " #e5e5e5";
      element.style.maxWidth = "400px";
      element.style.fontSize = "11px";
      element.style.padding = "3px";
      element.style.border = "1px solid #cccccc";
      element.style.borderBottomWidth = "0px";
      // element.style.fontFamily = "monospace";
      document.body.appendChild(element);
      HUD._displayElement = element
    }
    return HUD._displayElement
  },

  hide: function() {
    HUD.displayElement().style.display = "none";
  }
};
<commit_msg>Prevent our content script from being run on iframes -- only allow it to run on the top level DOM "window".<commit_after>var SCROLL_STEP_SIZE = 60; // Pixels

var keyCodes = { ESC: 27 };
var insertMode = false;
var keyPort;

function scrollToBottom() { window.scrollTo(0, document.body.scrollHeight); }
function scrollToTop() { window.scrollTo(0, 0); }
function scrollUp() { window.scrollBy(0, -1 * SCROLL_STEP_SIZE); }
function scrollDown() { window.scrollBy(0, SCROLL_STEP_SIZE); }
function scrollPageUp() { window.scrollBy(0, -6 * SCROLL_STEP_SIZE); }
function scrollPageDown() { window.scrollBy(0, 6 * SCROLL_STEP_SIZE); }
function scrollLeft() { window.scrollBy(-1 * SCROLL_STEP_SIZE, 0); }
function scrollRight() { window.scrollBy(SCROLL_STEP_SIZE, 0); }

function reload() { window.location.reload(); }
function goBack() { history.back(); }
function goForward() { history.forward(); }

function initializeFrontend() {
  document.addEventListener("keydown", onKeydown);
  document.addEventListener("focus", onFocusCapturePhase, true);
  document.addEventListener("blur", onBlurCapturePhase, true);

  // Send the key to the key handler in the background page.
  keyPort = chrome.extension.connect({name: "keyDown"});

  chrome.extension.onConnect.addListener(function (port, name) {
    if (port.name == "executePageCommand") {
      port.onMessage.addListener(function (args) {
        if (this[args.command])
        {
          for (var i = 0; i < args.count; i++) { this[args.command].call(); }
        }
      });
    }
    else if (port.name == "getScrollPosition") {
      port.onMessage.addListener(function (args) {
        var scrollPort = chrome.extension.connect({ name: "returnScrollPosition" });
        scrollPort.postMessage({
          scrollX: window.scrollX,
          scrollY: window.scrollY,
          currentTab: args.currentTab
        });
      });
    } else if (port.name == "setScrollPosition") {
      port.onMessage.addListener(function (args) {
        if (args.scrollX > 0 || args.scrollY > 0) { window.scrollBy(args.scrollX, args.scrollY); }
      });
    }
  });
};

/**
 * Sends everything except i & ESC to the handler in background_page. i & ESC are special because they control
 * insert mode which is local state to the page. The key will be are either a single ascii letter or a
 * key-modifier pair, e.g. <c-a> for control a.
 *
 * Note that some keys will only register keydown events and not keystroke events, e.g. ESC.
 */
function onKeydown(event) {
  var keyChar = "";

  // Ignore modifier keys by themselves.
  if (event.keyCode > 31 && event.keyCode < 127) {
    keyChar = String.fromCharCode(event.keyCode).toLowerCase();
    if (event.shiftKey)
      keyChar = keyChar.toUpperCase();
    if (event.ctrlKey)
      keyChar = "<c-" + keyChar + ">";
  }

  if (insertMode && event.keyCode == keyCodes.ESC)
    exitInsertMode();
  else if (!insertMode && keyChar == "i")
    enterInsertMode();
  else if (!insertMode && keyChar)
    keyPort.postMessage(keyChar);
}

function onFocusCapturePhase(event) {
  if (event.target.tagName == "INPUT" || event.target.tagName == "TEXTAREA")
    enterInsertMode();
}

function onBlurCapturePhase(event) {
  if (event.target.tagName == "INPUT" || event.target.tagName == "TEXTAREA")
    exitInsertMode();
}

function enterInsertMode() {
  insertMode = true;
  HUD.show("Insert mode");
}

function exitInsertMode() {
  insertMode = false;
  HUD.hide();
}

HUD = {
  show:function(text) {
    HUD.displayElement().innerHTML = text;
    HUD.displayElement().style.display = "";
  },

  /*
   * Retrieves the HUD HTML element, creating it if necessary.
   */
  displayElement: function() {
    if (!HUD._displayElement) {
      var element = document.createElement("div");
      element.innerHTML = "howdy";
      element.style.position = "fixed";
      element.style.bottom = "0px";
      element.style.left = "10px";
      element.style.backgroundColor = " #e5e5e5";
      element.style.maxWidth = "400px";
      element.style.fontSize = "11px";
      element.style.padding = "3px";
      element.style.border = "1px solid #cccccc";
      element.style.borderBottomWidth = "0px";
      // element.style.fontFamily = "monospace";
      document.body.appendChild(element);
      HUD._displayElement = element
    }
    return HUD._displayElement
  },

  hide: function() {
    HUD.displayElement().style.display = "none";
  }
};

// Prevent our content script from being run on iframes -- only allow it to run on the top level DOM "window".
// TODO(philc): We don't want to process multiple keyhandlers etc. when embedded on a page containing IFrames.
// This should be revisited, because sometimes we *do* want to listen inside of the currently focused iframe.
var isIframe = (window.self != window.parent);
if (!isIframe)
  initializeFrontend();<|endoftext|>


<commit_before>// ==UserScript==
// @name         Vola Admin/IP Tools
// @version      52
// @description  Does a bunch of stuff for mods.
// @namespace    https://volafile.org
// @icon         https://volafile.org/favicon.ico
// @author       topkuk productions
// @match        https://volafile.org/r/*
// @require      https://greasemonkey.github.io/gm4-polyfill/gm4-polyfill.js
// @require      https://cdn.rawgit.com/RealDolos/volascripts/51b76c05be26adca7b4a4897115f67f10d9df668/dry.js
// @run-at       document-start
// @grant        none
// ==/UserScript==

/* global dry, GM, _templates, RoomInstance */

dry.once("dom", () => {
  "use strict";

  const IS_BAN = /^\S+ (?:banned|muted|hellbanned|unbanned|un-muted|timed-in)\b/;

  function adjustBanPart(p, users, ips) {
    const {value} = p;
    let slash = value.indexOf(" / ");
    if (slash < 0) {
      slash = value.length;
    }
    const pre = value.slice(0, slash).replace("did nothing to", "did-nothing-to");
    const post = value.slice(slash);
    const pieces = pre.split(/ /g);
    let idx = 1;
    while (idx < pieces.length) {
      if (!pieces[idx++].endsWith(",")) {
        break;
      }
    }
    const pusers = pieces.slice(idx).map(u => {
      u = u.replace(/(?:[()]|,$)/g, "").trim();
      if (/^\d+\.\d+\.\d+\.\d+$/.test(u)) {
        ips.push(u);
        return null;
      }
      if (u) {
        users.push(u);
      }
      return u;
    }).filter(e => e).sort();

    if (!pusers.length) {
      pusers.push("some cuck");
    }

    p.value = `${pieces.slice(0, idx).join(" ").replace("did-nothing-to", "did nothing to")} ${pusers.join(", ")}${post}`;
  }

  function $e(tag, attrs, text) {
    let rv = document.createElement(tag);
    attrs = attrs || {};
    for (let a in attrs) {
        rv.setAttribute(a, attrs[a]);
    }
    if (text) {
        rv.textContent = text;
    }
    return rv;
  }

  const $ = (sel, el) => (el || document).querySelector(sel);

  console.log(
    "running", GM.info.script.name, GM.info.script.version, dry.version);

  const style = $e("style", {id: "iptools-style"}, `
body[noipspls] .chat_message_ip,
body[noipspls] .tag_key_ip {
  display: none;
}
.username.ban {
  display: inline-block;
}
@-moz-document url-prefix() {
.username.ban {
  display: inline;
}
}
.username.ban {
  vertical-align: top;
  color: white !important;
  font-size: 50%;
  padding: 0;
  opacity: 0.4;
}
.username.ban:hover {
  opacity: 1;
}
.username.ban icon-hammer {
  padding: 0;
}
.icon-untick {
  margin: 0 !important;
}
.untick-button, .tick-button {
  position: relative;
  z-index: 150;
  font-size: 18px;
  padding-bottom: 1px;
  margin-right: 1ex;
}
`);
  document.body.appendChild(style);
  let state = localStorage.getItem("noipspls") !== "false";
  if (state !== false) {
    state = true;
  }
  const update = () => {
    if (state) {
      document.body.setAttribute("noipspls", "true");
    }
    else {
      document.body.removeAttribute("noipspls");
    }
    localStorage.setItem("noipspls", state.toString());
    if (dry.exts) {
      dry.exts.chat.scrollState.bottom();
    }
  };
  const toggle = () => {
    state = !state;
    update();
  };
  update();

  const btn = document.createElement("a");
  btn.textContent = "IP";
  btn.style.padding = "0 1ex";
  const uc = document.querySelector("#user_count_icon");
  uc.parentElement.insertBefore(btn, uc.nextSibling);
  btn.addEventListener("click", toggle);

  const roomqueue = new Map();
  let resolving = false;
  const resolve_rooms = async () => {
    if (resolving) {
      return;
    }
    resolving = true;
    try {
      while (roomqueue.size) {
        const [room, elems] = roomqueue[Symbol.iterator]().next().value;
        try {
          let res = await dry.unsafeWindow.Volafile.makeAPIRequest("getRoomConfig", {
            room
          });
          for (const el of elems) {
            el.textContent = res.name;
          }
        }
        catch (ex) {
          console.error(ex);
        }
        roomqueue.delete(room);
      }
    }
    finally {
      resolving = false;
    }
  };

  dry.replaceEarly("chat", "showMessage",
    function(orig, nick, message, options, data, ...args) {
      try {
        if (nick === "Log" && options.staff && !options.profile && !(data && data.ip)) {
          const users = [];
          const ips = [];
          if (message[0].type === "url" && message[0].href === "/reports") {
            if (message[1].value.startsWith(" / BLACKLIST ")) {
              adjustBanPart(message[4], users, ips);
            }
            else {
              const [, p] = message;
              const m = p.value.match(/ \((\d+\.\d+\.\d+\.\d+)\)/);
              if (m) {
                p.value = p.value.replace(m[0], "");
                ips.push(m[1]);
              }
            }
          }
          else {
            for (const p of message) {
              if (p.type !== "text") {
                continue;
              }
              if (!IS_BAN.test(p.value)) {
                continue;
              }

              adjustBanPart(p, users, ips);
            }
          }
          if (users.length) {
            options.profile = users.sort().join(" user:");
            options.unprofile = true;
          }
          if (ips.length) {
            if (!data) {
              data = new dry.unsafeWindow.Object();
            }
            data.ip = ips.join(" ip:");
          }
        }
      }
      catch (ex) {
        console.error(ex);
      }
      const msg = orig(...[nick, message, options, data].concat(args));
      try {
        if (options.unprofile && msg.nick_elem) {
          const newnick = document.createElement("a");
          if (msg.ip_elem) {
            msg.ip_elem.parentElement.removeChild(msg.ip_elem);
          }
          newnick.textContent = msg.nick_elem.textContent;
          if (msg.ip_elem) {
            newnick.appendChild(msg.ip_elem);
          }
          newnick.className = msg.nick_elem.className;
          msg.nick_elem.parentElement.replaceChild(newnick, msg.nick_elem);
          msg.nick_elem = newnick;
          msg.elem.classList.remove("profile");
        }
        if (msg && (msg.ip_elem || msg.options.profile)) {
          msg.ban_elem = document.createElement("span");
          const hammer = document.createElement("i");
          hammer.setAttribute("class", "chat_message_icon icon-hammer");
          msg.ban_elem.appendChild(hammer);
          msg.ban_elem.setAttribute("class", "username clickable ban unselectable");
          msg.ban_elem.addEventListener("click", msg.showBanWindow.bind(msg));
          msg.nick_elem.appendChild(msg.ban_elem);
        }
        if (msg && nick === "Log" && msg.elem && msg.elem.textContent.includes("Reports /")) {
          for (const el of msg.elem.children) {
            let m = /https:\/\/volafile.org\/r\/(.+)$/.exec(el.href);
            if (m) {
              const l = roomqueue.get(m[1]);
              if (!l) {
                roomqueue.set(m[1], [el]);
              }
              else {
                l.push(el);
              }
            }
          }
        }
      }
      catch (ex) {
        console.error(ex);
      }
      finally {
        resolve_rooms().catch(console.error);
      }
      return msg;
    });

  new class extends dry.Commands {
    ip() {
      toggle();
      return true;
    }
  }();

  function nukeRoom() {
    dry.exts.ui.showQuestion({
      title: "Disable this room",
      text: "Are you sure you want to disable this room?",
      positive: "Disable",
      negative: "Abort"
    }, res => {
      if (!res) {
        return;
      }
      dry.unsafeWindow.Volafile.setRoomConfig({
        name: "closed",
        motd: "closed",
        disabled: true
      });
    });
  }

  function selectFreeform() {
    const form = RoomInstance.extensions.templates.renderForm("forms.freeformselect", {});
    form.on("submit", () => {
    const items = form.values.freeform;
      if (!items) {
        console.error("no items");
      }
      const ids = new Set();
      items.replace(/get\/(.+?)\//g, (m, id) => {
        ids.add(id);
      });
      items.replace(/gallery=([a-zA-z0-9_-]+)/g, (m, id) => {
        ids.add(id);
      });
      items.replace(/@([a-zA-z0-9_-]+)/g, (m, id) => {
        ids.add(id);
      });
      console.log(ids);
      RoomInstance.extensions.filelist.filelist.forEach(file => {
        if (ids.has(file.id)) {
          file.dom.controlElement.firstChild.click();
        }
      });
    });
  }

  dry.replaceEarly("ui", "showContextMenu", function(orig, el, options) {
    try {
      if (options && options.dedupe === "admin_contextmenu" && dry.exts.user.info.admin) {
        options.buttons.push({
          icon: "icon-rules",
          text: "Nuke Room",
          admin: true,
          click: nukeRoom
        });
        options.buttons.push({
          icon: "icon-broom",
          text: "Freeform Select",
          admin: true,
          click: selectFreeform
        });
      }
      if (options && options.dedupe === "room_contextmenu" && dry.exts.user.info.admin) {
        const idx = options.buttons.findIndex(e => e.text === "Copy URL");
        if (idx >= 0) {
          options.buttons.splice(idx, 1);
        }
      }
    }
    catch (ex) {
      console.error("ex", ex);
    }
    return orig(el, options);
  });

  // fixup ban templates
  for (const b of Object.values(window._templates.bans)) {
    b.lock = b.locked = false;
     if (b.upload && b.hours <= 24) {
       b.upload = false;
     }
  }

  function tickAll() {
    RoomInstance.extensions.filelist.filelist.forEach(file => {
      file.setData("checked", true);
    });
  }

  const doet = Symbol("doet");
  const cont = $("#upload_container");
  const untickButton = $e("label", {
    "for": "untick-button",
    "id": "untick-button",
    "class": "button untick-button",
    "title": "Untick all!"
  });
  untickButton.appendChild($e("span", {
    "class": "icon-minus"
  }));
  const tickButton = $e("label", {
    "for": "tick-button",
    "id": "tick-button",
    "class": "button tick-button",
    "title": "Tick all!"
  });
  tickButton.appendChild($e("span", {
    "class": "icon-plus"
  }));
  untickButton.addEventListener("click", () => dry.exts.adminButtons.untickAll(doet));
  tickButton.addEventListener("click", tickAll);
  cont.insertBefore(untickButton, cont.firstChild);
  cont.insertBefore(tickButton, cont.firstChild);

  dry.replaceEarly("adminButtons", "untickAll", function(orig, kek) {
    if (kek !== doet) {
      return;
    }
    return orig();
  });

  _templates.forms.freeformselect = {
    title: "Select files from freeform text",
    fields: [
      {type: "textarea", name: "freeform", placeholder: " ", label: "Text", rows: "8", cols: "30"},
    ],
    submit: "Select"
  };
});
<commit_msg>You have been purged<commit_after>// ==UserScript==
// @name         Vola Admin/IP Tools
// @version      53
// @description  Does a bunch of stuff for mods.
// @namespace    https://volafile.org
// @icon         https://volafile.org/favicon.ico
// @author       topkuk productions
// @match        https://volafile.org/r/*
// @require      https://greasemonkey.github.io/gm4-polyfill/gm4-polyfill.js
// @require      https://cdn.rawgit.com/RealDolos/volascripts/51b76c05be26adca7b4a4897115f67f10d9df668/dry.js
// @run-at       document-start
// @grant        none
// ==/UserScript==

/* global dry, GM, _templates, RoomInstance */

dry.once("dom", () => {
  "use strict";

  const IS_BAN = /^\S+ (?:banned|muted|hellbanned|unbanned|un-muted|timed-in)\b/;

  function adjustBanPart(p, users, ips) {
    const {value} = p;
    let slash = value.indexOf(" / ");
    if (slash < 0) {
      slash = value.length;
    }
    const pre = value.slice(0, slash).replace("did nothing to", "did-nothing-to");
    const post = value.slice(slash);
    const pieces = pre.split(/ /g);
    let idx = 1;
    while (idx < pieces.length) {
      if (!pieces[idx++].endsWith(",")) {
        break;
      }
    }
    const pusers = pieces.slice(idx).map(u => {
      u = u.replace(/(?:[()]|,$)/g, "").trim();
      if (/^\d+\.\d+\.\d+\.\d+$/.test(u)) {
        ips.push(u);
        return null;
      }
      if (u) {
        users.push(u);
      }
      return u;
    }).filter(e => e).sort();

    if (!pusers.length) {
      pusers.push("some cuck");
    }

    p.value = `${pieces.slice(0, idx).join(" ").replace("did-nothing-to", "did nothing to")} ${pusers.join(", ")}${post}`;
  }

  function $e(tag, attrs, text) {
    let rv = document.createElement(tag);
    attrs = attrs || {};
    for (let a in attrs) {
      rv.setAttribute(a, attrs[a]);
    }
    if (text) {
      rv.textContent = text;
    }
    return rv;
  }

  const $ = (sel, el) => (el || document).querySelector(sel);

  console.log(
    "running", GM.info.script.name, GM.info.script.version, dry.version);

  const style = $e("style", {id: "iptools-style"}, `
body[noipspls] .chat_message_ip,
body[noipspls] .tag_key_ip {
display: none;
}
.username.ban {
display: inline-block;
}
@-moz-document url-prefix() {
.username.ban {
display: inline;
}
}
.username.ban {
vertical-align: top;
color: white !important;
font-size: 50%;
padding: 0;
opacity: 0.4;
}
.username.ban:hover {
opacity: 1;
}
.username.ban icon-hammer {
padding: 0;
}
.icon-untick {
margin: 0 !important;
}
.untick-button, .tick-button {
position: relative;
z-index: 150;
font-size: 18px;
padding-bottom: 1px;
margin-right: 1ex;
}
`);
  document.body.appendChild(style);
  let state = localStorage.getItem("noipspls") !== "false";
  if (state !== false) {
    state = true;
  }
  const update = () => {
    if (state) {
      document.body.setAttribute("noipspls", "true");
    }
    else {
      document.body.removeAttribute("noipspls");
    }
    localStorage.setItem("noipspls", state.toString());
    if (dry.exts) {
      dry.exts.chat.scrollState.bottom();
    }
  };
  const toggle = () => {
    state = !state;
    update();
  };
  update();

  const btn = document.createElement("a");
  btn.textContent = "IP";
  btn.style.padding = "0 1ex";
  const uc = document.querySelector("#user_count_icon");
  uc.parentElement.insertBefore(btn, uc.nextSibling);
  btn.addEventListener("click", toggle);

  const roomqueue = new Map();
  let resolving = false;
  const resolve_rooms = async () => {
    if (resolving) {
      return;
    }
    resolving = true;
    try {
      while (roomqueue.size) {
        const [room, elems] = roomqueue[Symbol.iterator]().next().value;
        try {
          let res = await dry.unsafeWindow.Volafile.makeAPIRequest("getRoomConfig", {
            room
          });
          for (const el of elems) {
            el.textContent = res.name;
          }
        }
        catch (ex) {
          console.error(ex);
        }
        roomqueue.delete(room);
      }
    }
    finally {
      resolving = false;
    }
  };

  dry.replaceEarly("chat", "showMessage",
                   function(orig, nick, message, options, data, ...args) {
    try {
      if (nick === "Log" && options.staff && !options.profile && !(data && data.ip)) {
        const users = [];
        const ips = [];
        if (message[0].type === "url" && message[0].href === "/reports") {
          if (message[1].value.startsWith(" / BLACKLIST ")) {
            adjustBanPart(message[4], users, ips);
          }
          else {
            const [, p] = message;
            const m = p.value.match(/ \((\d+\.\d+\.\d+\.\d+)\)/);
            if (m) {
              p.value = p.value.replace(m[0], "");
              ips.push(m[1]);
            }
          }
        }
        else {
          for (const p of message) {
            if (p.type !== "text") {
              continue;
            }
            if (!IS_BAN.test(p.value)) {
              continue;
            }

            adjustBanPart(p, users, ips);
          }
        }
        if (users.length) {
          options.profile = users.sort().join(" user:");
          options.unprofile = true;
        }
        if (ips.length) {
          if (!data) {
            data = new dry.unsafeWindow.Object();
          }
          data.ip = ips.join(" ip:");
        }
      }
    }
    catch (ex) {
      console.error(ex);
    }
    const msg = orig(...[nick, message, options, data].concat(args));
    try {
      if (options.unprofile && msg.nick_elem) {
        const newnick = document.createElement("a");
        if (msg.ip_elem) {
          msg.ip_elem.parentElement.removeChild(msg.ip_elem);
        }
        newnick.textContent = msg.nick_elem.textContent;
        if (msg.ip_elem) {
          newnick.appendChild(msg.ip_elem);
        }
        newnick.className = msg.nick_elem.className;
        msg.nick_elem.parentElement.replaceChild(newnick, msg.nick_elem);
        msg.nick_elem = newnick;
        msg.elem.classList.remove("profile");
      }
      if (msg && (msg.ip_elem || msg.options.profile)) {
        const addHammer = function() {
          msg.ban_elem = document.createElement("span");
          const hammer = document.createElement("i");
          hammer.setAttribute("class", "chat_message_icon icon-hammer");
          msg.ban_elem.appendChild(hammer);
          msg.ban_elem.setAttribute("class", "username clickable ban unselectable");
          const showBanWindow = msg.showBanWindow.bind(msg);
          msg.ban_elem.addEventListener("click", function(e) {
            if (e.altKey) {
              e.preventDefault();
              e.stopPropagation();
              purgeMessages(msg);
              return false;
            }
            else {
              return showBanWindow(e);
            }
          });
          msg.nick_elem.appendChild(msg.ban_elem);
        };
        addHammer();
        const update = msg.update.bind(msg);
        msg.update = function() {
          update();
          addHammer();
        }
      }
      if (msg && nick === "Log" && msg.elem && msg.elem.textContent.includes("Reports /")) {
        for (const el of msg.elem.children) {
          let m = /https:\/\/volafile.org\/r\/(.+)$/.exec(el.href);
          if (m) {
            const l = roomqueue.get(m[1]);
            if (!l) {
              roomqueue.set(m[1], [el]);
            }
            else {
              l.push(el);
            }
          }
        }
      }
    }
    catch (ex) {
      console.error(ex);
    }
    finally {
      resolve_rooms().catch(console.error);
    }
    return msg;
  });

  new class extends dry.Commands {
    ip() {
      toggle();
      return true;
    }
  }();

  function nukeRoom() {
    dry.exts.ui.showQuestion({
      title: "Disable this room",
      text: "Are you sure you want to disable this room?",
      positive: "Disable",
      negative: "Abort"
    }, res => {
      if (!res) {
        return;
      }
      dry.unsafeWindow.Volafile.setRoomConfig({
        name: "closed",
        motd: "closed",
        disabled: true
      });
    });
  }

  function purgeMessageIds(ids) {
    if (!Array.isArray(ids)) {
      ids = [ids];
    }
    if (!ids.length) {
      return;
    }
    dry.exts.connection.call("removeMessages", ids, function(e) {
      if (e) return dry.exts.chat.showError(e);
    });
  }

  function purgeMessages(msg) {
    dry.exts.templates.renderPrompt("prompts.purgemessages", {
      user: msg.nick,
      "one": function() {
        purgeMessageIds([msg.data.id]);
      },
      "ip": function() {
        const {ip} = msg.data;
        purgeMessageIds(dry.exts.chat.messages.filter(m => m.data.ip === ip && m.data.id).map(e => e.data.id));
      },
      "nick": function() {
        const nick = msg.nick.toUpperCase();
        if (!nick) {
          return;
        }
        if (msg.options.user) {
          purgeMessageIds(dry.exts.chat.messages.filter(m => m.nick.toUpperCase() === nick && m.data.id).map(e => e.data.id));
        }
        else {
          purgeMessageIds(dry.exts.chat.messages.filter(m => m.nick.toUpperCase() === nick && m.data.id && !m.options.user).map(e => e.data.id));
        }
      },
      "cancel": function() {}
    });
  }

  function selectFreeform() {
    const form = RoomInstance.extensions.templates.renderForm("forms.freeformselect", {});
    form.on("submit", () => {
      const items = form.values.freeform;
      if (!items) {
        console.error("no items");
      }
      const ids = new Set();
      items.replace(/get\/(.+?)\//g, (m, id) => {
        ids.add(id);
      });
      items.replace(/gallery=([a-zA-z0-9_-]+)/g, (m, id) => {
        ids.add(id);
      });
      items.replace(/@([a-zA-z0-9_-]+)/g, (m, id) => {
        ids.add(id);
      });
      console.log(ids);
      RoomInstance.extensions.filelist.filelist.forEach(file => {
        if (ids.has(file.id)) {
          file.dom.controlElement.firstChild.click();
        }
      });
    });
  }

  dry.replaceEarly("ui", "showContextMenu", function(orig, el, options) {
    try {
      if (options && options.dedupe === "admin_contextmenu" && dry.exts.user.info.admin) {
        options.buttons.push({
          icon: "icon-rules",
          text: "Nuke Room",
          admin: true,
          click: nukeRoom
        });
        options.buttons.push({
          icon: "icon-broom",
          text: "Freeform Select",
          admin: true,
          click: selectFreeform
        });
      }
      if (options && options.dedupe === "room_contextmenu" && dry.exts.user.info.admin) {
        const idx = options.buttons.findIndex(e => e.text === "Copy URL");
        if (idx >= 0) {
          options.buttons.splice(idx, 1);
        }
      }
    }
    catch (ex) {
      console.error("ex", ex);
    }
    return orig(el, options);
  });

  // fixup ban templates
  for (const b of Object.values(window._templates.bans)) {
    b.lock = b.locked = false;
    if (b.upload && b.hours <= 24) {
      b.upload = false;
    }
  }

  function tickAll() {
    RoomInstance.extensions.filelist.filelist.forEach(file => {
      file.setData("checked", true);
    });
  }

  const doet = Symbol("doet");
  const cont = $("#upload_container");
  const untickButton = $e("label", {
    "for": "untick-button",
    "id": "untick-button",
    "class": "button untick-button",
    "title": "Untick all!"
  });
  untickButton.appendChild($e("span", {
    "class": "icon-minus"
  }));
  const tickButton = $e("label", {
    "for": "tick-button",
    "id": "tick-button",
    "class": "button tick-button",
    "title": "Tick all!"
  });
  tickButton.appendChild($e("span", {
    "class": "icon-plus"
  }));
  untickButton.addEventListener("click", () => dry.exts.adminButtons.untickAll(doet));
  tickButton.addEventListener("click", tickAll);
  cont.insertBefore(untickButton, cont.firstChild);
  cont.insertBefore(tickButton, cont.firstChild);

  dry.replaceEarly("adminButtons", "untickAll", function(orig, kek) {
    if (kek !== doet) {
      return;
    }
    return orig();
  });

  _templates.forms.freeformselect = {
    title: "Select files from freeform text",
    fields: [
      {type: "textarea", name: "freeform", placeholder: " ", label: "Text", rows: "8", cols: "30"},
    ],
    submit: "Select"
  };

  _templates.prompts.purgemessages = {
    title: "Purge messages",
    content: "Are you sure you want to SHREKT <b>{{user}}</b>?",
    centered:true,
    dismissable:true,
    buttons: [
      {"name":"This Message","click":"one", default: true},
      {"name":"All by IP","add_class":"light","click":"ip"},
      {"name":"All by Nick","add_class":"light","click":"nick"},
      {"name":"It was a mistake", "add_class":"light", "click":"cancel"}
    ]
  };
});
<|endoftext|>


<commit_before>class MapFactory {
    constructor(canvasName, mapCols, mapRows, tileWidth, tileHeight) {
        var _self = this;

        this.tileWidth = typeof tileWidth === "number" ? Math.abs(tileWidth) : 0;
        this.tileHeight = typeof tileHeight === "number" ? Math.abs(tileHeight) : 0;
        this.mapCols = typeof mapCols === "number" ? Math.abs(mapCols) : 0;
        this.mapRows = typeof mapRows === "number" ? Math.abs(mapRows) : 0;
        this.viewOffsetI = 0;
        this.viewOffsetJ = 0;

        this.minVisibleRows = Math.min(Math.trunc(document.body.clientHeight / this.tileHeight) /*- 1*/, this.mapRows);
        this.minVisibleCols = 0;
        this.maxVisibleRows = this.minVisibleRows; // Default behavior is fixed rows.
        this.maxVisibleCols = 0;

        var currOffsetI = 0;
        var currOffsetJ = 0;
        var crsrOffsetI = 0;
        var crsrOffsetJ = 0;

        var canvas = this["canvas"] = document.getElementById(canvasName);
        var context = this["context"] = canvas.getContext('2d');

        var assetBackground = this["assetBackground"] = new Image();
        var assetForeground = this["assetForeground"] = new Image();
        var assetCursor = this["assetCursor"] = new Image();

        //-------------------------------//
        canvas.addEventListener("mousemove", function (ev) { // Draw cursor
            var newI = Math.trunc(ev.offsetX / _self.tileWidth) + _self.viewOffsetI;
            var newJ = Math.trunc(ev.offsetY / _self.tileHeight) + _self.viewOffsetJ;
            var oldI = parseInt(canvas.dataset.offsetI);
            var oldJ = parseInt(canvas.dataset.offsetJ);

            if (newI !== oldI || newJ !== oldJ) {
                canvas.dataset.offsetI = newI;
                canvas.dataset.offsetJ = newJ;

                var cac = _self["cac"];

                if (assetCursor.src && cac && typeof cac === "function") {
                    var coor = cac(newI, newJ);
                    if (coor && typeof coor.x === 'number' && typeof coor.y === 'number') {
                        _self.drawCursorTile(coor.x, coor.y, newI, newJ);
                    } else {
                        canvas.style.cursor = "auto";
                        return true; // Cursor asset is not working. No needs to continue.
                    }
                } else {
                    canvas.style.cursor = "auto";
                    return true; // Same as above.
                }
                canvas.style.cursor = "none";
                _self.restoreTile(oldI, oldJ);
            }
            return true;
        });
        canvas.addEventListener("mouseleave", function (ev) { // Hide cursor when mouse leaves
            var oldI = parseInt(canvas.dataset.offsetI);
            var oldJ = parseInt(canvas.dataset.offsetJ);

            _self.restoreTile(oldI, oldJ);
        });
        //-------------------------------//
        canvas.addEventListener("mousedown", function (ev) {
            if (ev.button !== 0) {
                return true;
            }
            crsrOffsetI = Math.trunc(ev.offsetX / _self.tileWidth);
            crsrOffsetJ = Math.trunc(ev.offsetY / _self.tileHeight);
            currOffsetI = _self.viewOffsetI;
            currOffsetJ = _self.viewOffsetJ;

            canvas.style.cursor = "move";
        });
        canvas.addEventListener("mousemove", function (ev) { // Dragging map
            if (ev.which !== 1) {
                return true;
            }
            var tempOffsetI = Math.trunc(ev.offsetX / _self.tileWidth);
            var tempOffsetJ = Math.trunc(ev.offsetY / _self.tileHeight);

            var mapVisibleCols = _self["mapVisibleCols"];
            var mapVisibleRows = _self["mapVisibleRows"];

            var update = false;

            if (crsrOffsetI !== tempOffsetI) {
                var tempI = crsrOffsetI - tempOffsetI + currOffsetI;

                if (tempI < 0) {
                    tempI = 0;
                } else if (tempI + mapVisibleCols >= _self.mapCols) {
                    tempI = _self.mapCols - mapVisibleCols;
                }
                if (_self.viewOffsetI !== tempI) {
                    _self.viewOffsetI = tempI;
                    update = true;
                }
            }
            if (crsrOffsetJ !== tempOffsetJ) {
                var tempJ = crsrOffsetJ - tempOffsetJ + currOffsetJ;

                if (tempJ < 0) {
                    tempJ = 0;
                } else if (tempJ + mapVisibleRows >= _self.mapRows) {
                    tempJ = _self.mapRows - mapVisibleRows;
                }
                if (_self.viewOffsetJ !== tempJ) {
                    _self.viewOffsetJ = tempJ;
                    update = true;
                }
            }
            if (update) {
                _self.drawBackground();
                _self.drawForeground();
            }
            canvas.style.cursor = "move";
        });
        canvas.addEventListener("mouseup", function (ev) {
            canvas.style.cursor = "initial";

            var tempOffsetI = Math.trunc(ev.offsetX / _self.tileWidth);
            var tempOffsetJ = Math.trunc(ev.offsetY / _self.tileHeight);

            if (crsrOffsetI === tempOffsetI && crsrOffsetJ === tempOffsetJ) { // Not dragging the map.
                var callback = _self["mousedown"];
                if (callback) {
                    callback(ev, tempOffsetI + _self.viewOffsetI, tempOffsetJ + _self.viewOffsetJ);
                }
            }
        });
        //-------------------------------//
        window.addEventListener("resize", function (ev) {
            _self.update();
        });
        //-------------------------------//
        assetBackground.onload = function (ev) {
            _self.drawBackground();
        };
        assetForeground.onload = function (ev) {
            _self.drawForeground();
        };
        //-------------------------------//
        this.updateMapVisibleSize();
        //-------------------------------//
    }
}

MapFactory.prototype.foregroundAsset = function (url) {
    var assetForeground = this["assetForeground"];
    if (typeof url === 'string') {
        assetForeground.src = url;
        return this;
    }
    return assetForeground.src;
};

MapFactory.prototype.backgroundAsset = function (url) {
    var assetBackground = this["assetBackground"];
    if (typeof url === 'string') {
        assetBackground.src = url;
        return this;
    }
    return assetBackground.src;
};

MapFactory.prototype.cursorAsset = function (url) {
    var assetCursor = this["assetCursor"];
    if (typeof url === 'string') {
        assetCursor.src = url;
        return this;
    }
    return assetCursor.src;
};

MapFactory.prototype.backgroundAssetMapping = function (callback) {
    if (typeof callback === 'function') {
        this["bac"] = callback;
        return this;
    }
    return this["bac"];
};

MapFactory.prototype.foregroundAssetMapping = function (callback) {
    if (typeof callback === 'function') {
        this["fac"] = callback;
        return this;
    }
    return this["fac"];
};

MapFactory.prototype.cursorAssetMapping = function (callback) {
    if (typeof callback === 'function') {
        this["cac"] = callback;
        return this;
    }
    return this["cac"];
};

MapFactory.prototype.mousedown = function (callback) {
    if (typeof callback === 'function') {
        this["mousedown"] = callback;
        return this;
    }
    return this["mousedown"];
};

MapFactory.prototype.updateMapVisibleSize = function () {
    var bb = this["canvas"].parentNode.getBoundingClientRect();
    var rows = Math.min(Math.trunc(document.body.clientHeight / this.tileHeight) /*- 1*/, this.mapRows);
    var cols = Math.min(Math.trunc((bb.right - bb.left) / this.tileWidth) - 1, this.mapCols);

    if (this.minVisibleRows > 0 && rows < this.minVisibleRows) {
        rows = this.minVisibleRows;
    }
    else if (this.maxVisibleRows > 0 && this.maxVisibleRows >= this.minVisibleRows && rows > this.maxVisibleRows) {
        rows = this.maxVisibleRows;
    }
    if (this.minVisibleCols > 0 && cols < this.minVisibleCols) {
        cols = this.minVisibleCols;
    }
    else if (this.maxVisibleCols > 0 && this.maxVisibleCols >= this.minVisibleCols && cols > this.maxVisibleCols) {
        cols = this.maxVisibleCols;
    }

    this["mapVisibleCols"] = cols;
    this["mapVisibleRows"] = rows;

    this["canvas"].width = cols * this.tileWidth;
    this["canvas"].height = rows * this.tileHeight;
};

MapFactory.prototype.drawBackground = function () {
    var mapVisibleCols = this["mapVisibleCols"];
    var mapVisibleRows = this["mapVisibleRows"];
    var bac = this["bac"];

    if (this["assetBackground"].src && bac && typeof bac === "function") {
        for (var i = this.viewOffsetI; i < mapVisibleCols + this.viewOffsetI; i++) {
            for (var j = this.viewOffsetJ; j < mapVisibleRows + this.viewOffsetJ; j++) {
                var coor = bac(i, j);
                if (coor && typeof coor.x === 'number' && typeof coor.y === 'number') {
                    this.drawBackgroundTile(coor.x, coor.y, i, j);
                }
            }
        }
    }
};

MapFactory.prototype.drawForeground = function() {
    var mapVisibleCols = this["mapVisibleCols"];
    var mapVisibleRows = this["mapVisibleRows"];
    var fac = this["fac"];

    if (this["assetForeground"].src && fac && typeof fac === "function") {
        for (var i = this.viewOffsetI; i < mapVisibleCols + this.viewOffsetI; i++) {
            for (var j = this.viewOffsetJ; j < mapVisibleRows + this.viewOffsetJ; j++) {
                var coor = fac(i, j);
                if (coor && typeof coor.x === 'number' && typeof coor.y === 'number') {
                    this.drawForegroundTile(coor.x, coor.y, i, j);
                }
            }
        }
    }
};

MapFactory.prototype.drawCursor = function (i, j) {
    var cac = this["cac"];

    if (this["assetCursor"].src && cac && typeof cac === "function") {
        var coor = cac(i + this.viewOffsetI, j + this.viewOffsetJ);
        if (coor && typeof coor.x === 'number' && typeof coor.y === 'number') {
            this.drawCursorTile(coor.x, coor.y, i, j);
        }
    }
};

MapFactory.prototype.drawBackgroundTile = function (sourceX, sourceY, i, j) {
    var destX = (i - this.viewOffsetI) * this.tileWidth;
    var destY = (j - this.viewOffsetJ) * this.tileHeight;
    var asset = this["assetBackground"];

    this["context"].drawImage(asset,
        sourceX, sourceY, this.tileWidth, this.tileHeight, destX, destY, this.tileWidth, this.tileHeight);
};

MapFactory.prototype.drawForegroundTile = function (sourceX, sourceY, i, j) {
    var destX = (i - this.viewOffsetI) * this.tileWidth;
    var destY = (j - this.viewOffsetJ) * this.tileHeight;
    var asset = this["assetForeground"];
    this["context"].drawImage(asset,
        sourceX, sourceY, this.tileWidth, this.tileHeight, destX, destY, this.tileWidth, this.tileHeight);
};

MapFactory.prototype.drawCursorTile = function (sourceX, sourceY, i, j) {
    var destX = (i - this.viewOffsetI) * this.tileWidth;
    var destY = (j - this.viewOffsetJ) * this.tileHeight;
    var asset = this["assetCursor"];
    this["context"].drawImage(asset,
        sourceX, sourceY, this.tileWidth, this.tileHeight, destX, destY, this.tileWidth, this.tileHeight);
};

MapFactory.prototype.clearTile = function (i, j) {
    var destX = (i - this.viewOffsetI) * this.tileWidth;
    var destY = (j - this.viewOffsetJ) * this.tileHeight;
    this["context"].clearRect(destX, destY, this.tileWidth, this.tileHeight);
};

MapFactory.prototype.restoreTile = function (i, j) {
    var bac = this["bac"];
    var fac = this["fac"];

    var assetBackground = this["assetBackground"];
    var assetForeground = this["assetForeground"];

    this.clearTile(i, j);
    if (assetBackground.src && bac && typeof bac === "function") {
        var coor = bac(i, j);
        if (coor && typeof coor.x === 'number' && typeof coor.y === 'number') {
            this.drawBackgroundTile(coor.x, coor.y, i, j);
        }
    }
    if (assetForeground.src && fac && typeof fac === "function") {
        var coor = fac(i, j);
        if (coor && typeof coor.x === 'number' && typeof coor.y === 'number') {
            this.drawForegroundTile(coor.x, coor.y, i, j);
        }
    }
}

MapFactory.prototype.update = function () {
    this.updateMapVisibleSize();
    this.drawBackground();
    this.drawForeground();
}
<commit_msg>Several minor change & planning for new mode<commit_after>class MapFactory {
    constructor(canvasName, mapCols, mapRows, tileWidth, tileHeight) {
        var _self = this;

        this.tileWidth = typeof tileWidth === "number" ? Math.abs(tileWidth) : 0;
        this.tileHeight = typeof tileHeight === "number" ? Math.abs(tileHeight) : 0;
        this.mapCols = typeof mapCols === "number" ? Math.abs(mapCols) : 0;
        this.mapRows = typeof mapRows === "number" ? Math.abs(mapRows) : 0;
        this.viewOffsetI = 0;
        this.viewOffsetJ = 0;

        this.minVisibleRows = Math.min(Math.trunc(document.body.clientHeight / this.tileHeight) /*- 1*/, this.mapRows);
        this.minVisibleCols = 0;
        this.maxVisibleRows = this.minVisibleRows; // Default behavior is fixed rows.
        this.maxVisibleCols = 0;

        this.mode = "mapDragging";

        var currOffsetI = 0;
        var currOffsetJ = 0;
        var crsrOffsetI = 0;
        var crsrOffsetJ = 0;

        var canvas = this["canvas"] = document.getElementById(canvasName);
        var context = this["context"] = canvas.getContext('2d');

        var assetBackground = this["assetBackground"] = new Image();
        var assetForeground = this["assetForeground"] = new Image();
        var assetCursor = this["assetCursor"] = new Image();

        var showCursor = function (ev) {
            var newI = Math.trunc(ev.offsetX / _self.tileWidth) + _self.viewOffsetI;
            var newJ = Math.trunc(ev.offsetY / _self.tileHeight) + _self.viewOffsetJ;
            var oldI = parseInt(canvas.dataset.offsetI);
            var oldJ = parseInt(canvas.dataset.offsetJ);

            if (newI !== oldI || newJ !== oldJ) {
                canvas.dataset.offsetI = newI;
                canvas.dataset.offsetJ = newJ;

                var cac = _self["cac"];

                if (assetCursor.src && cac && typeof cac === "function") {
                    var coor = cac(newI, newJ);
                    if (coor && typeof coor.x === 'number' && typeof coor.y === 'number') {
                        _self.drawCursorTile(coor.x, coor.y, newI, newJ);
                    } else {
                        canvas.style.cursor = "auto";
                        return true; // Cursor asset is not working. No needs to continue.
                    }
                } else {
                    canvas.style.cursor = "auto";
                    return true; // Same as above.
                }
                canvas.style.cursor = "none";
                _self.restoreTile(oldI, oldJ);
            }
            return true;
        };
        var hideCursor = function (ev) { // Hide cursor when mouse leaves
            var oldI = parseInt(canvas.dataset.offsetI);
            var oldJ = parseInt(canvas.dataset.offsetJ);

            _self.restoreTile(oldI, oldJ);
        };
        var mapDragging = function (ev) { // Dragging map
            var tempOffsetI = Math.trunc(ev.offsetX / _self.tileWidth);
            var tempOffsetJ = Math.trunc(ev.offsetY / _self.tileHeight);

            var mapVisibleCols = _self["mapVisibleCols"];
            var mapVisibleRows = _self["mapVisibleRows"];

            var update = false;

            if (crsrOffsetI !== tempOffsetI) {
                var tempI = crsrOffsetI - tempOffsetI + currOffsetI;

                if (tempI < 0) {
                    tempI = 0;
                } else if (tempI + mapVisibleCols >= _self.mapCols) {
                    tempI = _self.mapCols - mapVisibleCols;
                }
                if (_self.viewOffsetI !== tempI) {
                    _self.viewOffsetI = tempI;
                    update = true;
                }
            }
            if (crsrOffsetJ !== tempOffsetJ) {
                var tempJ = crsrOffsetJ - tempOffsetJ + currOffsetJ;

                if (tempJ < 0) {
                    tempJ = 0;
                } else if (tempJ + mapVisibleRows >= _self.mapRows) {
                    tempJ = _self.mapRows - mapVisibleRows;
                }
                if (_self.viewOffsetJ !== tempJ) {
                    _self.viewOffsetJ = tempJ;
                    update = true;
                }
            }
            if (update) {
                _self.drawBackground();
                _self.drawForeground();
            }
            canvas.style.cursor = "move";
        };
        var rangeOperation = function (ev) {
            // TODO:
        }
        //-------------------------------//
        canvas.addEventListener("mousedown", function (ev) {
            if (ev.button !== 0) {
                return true;
            }
            crsrOffsetI = Math.trunc(ev.offsetX / _self.tileWidth);
            crsrOffsetJ = Math.trunc(ev.offsetY / _self.tileHeight);
            currOffsetI = _self.viewOffsetI;
            currOffsetJ = _self.viewOffsetJ;

            canvas.style.cursor = "move";
        });
        canvas.addEventListener("mousemove", function (ev) {
            if (ev.which !== 1) {
                return true;
            }
            switch (_self.mode) {
                case "mapDragging":
                    mapDragging(ev);
                    break;
                case "rangeOperation":
                    rangeOperation(ev);
                    break;
            }
        });
        canvas.addEventListener("mouseup", function (ev) {
            canvas.style.cursor = "initial";

            var tempOffsetI = Math.trunc(ev.offsetX / _self.tileWidth);
            var tempOffsetJ = Math.trunc(ev.offsetY / _self.tileHeight);

            if (crsrOffsetI === tempOffsetI && crsrOffsetJ === tempOffsetJ) { // Position doesn't change.
                var callback = _self["mousedown"];
                if (callback) {
                    callback(ev, tempOffsetI + _self.viewOffsetI, tempOffsetJ + _self.viewOffsetJ);
                }
            }
        });
        //-------------------------------//
        window.addEventListener("resize", function (ev) {
            _self.update();
        });
        //-------------------------------//
        assetBackground.onload = function (ev) {
            _self.drawBackground();
        };
        assetForeground.onload = function (ev) {
            _self.drawForeground();
        };
        assetCursor.onload = function (ev) {
            canvas.addEventListener("mousemove", showCursor);
            canvas.addEventListener("mouseleave", hideCursor);
        };
        //-------------------------------//
        this.updateMapVisibleSize();
        //-------------------------------//
    }
}

MapFactory.prototype.foregroundAsset = function (url) {
    var assetForeground = this["assetForeground"];
    if (typeof url === 'string') {
        assetForeground.src = url;
        return this;
    }
    return assetForeground.src;
};

MapFactory.prototype.backgroundAsset = function (url) {
    var assetBackground = this["assetBackground"];
    if (typeof url === 'string') {
        assetBackground.src = url;
        return this;
    }
    return assetBackground.src;
};

MapFactory.prototype.cursorAsset = function (url) {
    var assetCursor = this["assetCursor"];
    if (typeof url === 'string') {
        assetCursor.src = url;
        return this;
    }
    return assetCursor.src;
};

MapFactory.prototype.backgroundAssetMapping = function (callback) {
    if (typeof callback === 'function') {
        this["bac"] = callback;
        return this;
    }
    return this["bac"];
};

MapFactory.prototype.foregroundAssetMapping = function (callback) {
    if (typeof callback === 'function') {
        this["fac"] = callback;
        return this;
    }
    return this["fac"];
};

MapFactory.prototype.cursorAssetMapping = function (callback) {
    if (typeof callback === 'function') {
        this["cac"] = callback;
        return this;
    }
    return this["cac"];
};

MapFactory.prototype.mousedown = function (callback) {
    if (typeof callback === 'function') {
        this["mousedown"] = callback;
        return this;
    }
    return this["mousedown"];
};

MapFactory.prototype.updateMapVisibleSize = function () {
    var bb = this["canvas"].parentNode.getBoundingClientRect();
    var rows = Math.min(Math.trunc(document.body.clientHeight / this.tileHeight) /*- 1*/, this.mapRows);
    var cols = Math.min(Math.trunc((bb.right - bb.left) / this.tileWidth) - 1, this.mapCols);

    if (this.minVisibleRows > 0 && rows < this.minVisibleRows) {
        rows = this.minVisibleRows;
    }
    else if (this.maxVisibleRows > 0 && this.maxVisibleRows >= this.minVisibleRows && rows > this.maxVisibleRows) {
        rows = this.maxVisibleRows;
    }
    if (this.minVisibleCols > 0 && cols < this.minVisibleCols) {
        cols = this.minVisibleCols;
    }
    else if (this.maxVisibleCols > 0 && this.maxVisibleCols >= this.minVisibleCols && cols > this.maxVisibleCols) {
        cols = this.maxVisibleCols;
    }

    this["mapVisibleCols"] = cols;
    this["mapVisibleRows"] = rows;

    this["canvas"].width = cols * this.tileWidth;
    this["canvas"].height = rows * this.tileHeight;
};

MapFactory.prototype.drawBackground = function () {
    var mapVisibleCols = this["mapVisibleCols"];
    var mapVisibleRows = this["mapVisibleRows"];
    var bac = this["bac"];

    if (this["assetBackground"].src && bac && typeof bac === "function") {
        for (var i = this.viewOffsetI; i < mapVisibleCols + this.viewOffsetI; i++) {
            for (var j = this.viewOffsetJ; j < mapVisibleRows + this.viewOffsetJ; j++) {
                var coor = bac(i, j);
                if (coor && typeof coor.x === 'number' && typeof coor.y === 'number') {
                    this.drawBackgroundTile(coor.x, coor.y, i, j);
                }
            }
        }
    }
};

MapFactory.prototype.drawForeground = function() {
    var mapVisibleCols = this["mapVisibleCols"];
    var mapVisibleRows = this["mapVisibleRows"];
    var fac = this["fac"];

    if (this["assetForeground"].src && fac && typeof fac === "function") {
        for (var i = this.viewOffsetI; i < mapVisibleCols + this.viewOffsetI; i++) {
            for (var j = this.viewOffsetJ; j < mapVisibleRows + this.viewOffsetJ; j++) {
                var coor = fac(i, j);
                if (coor && typeof coor.x === 'number' && typeof coor.y === 'number') {
                    this.drawForegroundTile(coor.x, coor.y, i, j);
                }
            }
        }
    }
};

MapFactory.prototype.drawCursor = function (viewI, viewJ, cols, rows) {
    var cac = this["cac"];

    cols = isNaN(cols) ? 1 : parseInt(cols);
    rows = isNaN(rows) ? 1 : parseInt(rows);

    if (this["assetCursor"].src && cac && typeof cac === "function") {
        for (var i = viewI; i < viewI + cols; i++) {
            for (var j = viewI; j < viewI + rows; j++) {
                var coor = cac(i + this.viewOffsetI, j + this.viewOffsetJ);
                if (coor && typeof coor.x === 'number' && typeof coor.y === 'number') {
                    this.drawCursorTile(coor.x, coor.y, i, j);
                }
            }
        }
    }
};

MapFactory.prototype.drawBackgroundTile = function (sourceX, sourceY, i, j) {
    var destX = (i - this.viewOffsetI) * this.tileWidth;
    var destY = (j - this.viewOffsetJ) * this.tileHeight;
    var asset = this["assetBackground"];

    this["context"].drawImage(asset,
        sourceX, sourceY, this.tileWidth, this.tileHeight, destX, destY, this.tileWidth, this.tileHeight);
};

MapFactory.prototype.drawForegroundTile = function (sourceX, sourceY, i, j) {
    var destX = (i - this.viewOffsetI) * this.tileWidth;
    var destY = (j - this.viewOffsetJ) * this.tileHeight;
    var asset = this["assetForeground"];
    this["context"].drawImage(asset,
        sourceX, sourceY, this.tileWidth, this.tileHeight, destX, destY, this.tileWidth, this.tileHeight);
};

MapFactory.prototype.drawCursorTile = function (sourceX, sourceY, i, j) {
    var destX = (i - this.viewOffsetI) * this.tileWidth;
    var destY = (j - this.viewOffsetJ) * this.tileHeight;
    var asset = this["assetCursor"];
    this["context"].drawImage(asset,
        sourceX, sourceY, this.tileWidth, this.tileHeight, destX, destY, this.tileWidth, this.tileHeight);
};

MapFactory.prototype.clearTile = function (i, j) {
    var destX = (i - this.viewOffsetI) * this.tileWidth;
    var destY = (j - this.viewOffsetJ) * this.tileHeight;
    this["context"].clearRect(destX, destY, this.tileWidth, this.tileHeight);
};

MapFactory.prototype.restoreTile = function (i, j) {
    var bac = this["bac"];
    var fac = this["fac"];

    var assetBackground = this["assetBackground"];
    var assetForeground = this["assetForeground"];

    this.clearTile(i, j);
    if (assetBackground.src && bac && typeof bac === "function") {
        var coor = bac(i, j);
        if (coor && typeof coor.x === 'number' && typeof coor.y === 'number') {
            this.drawBackgroundTile(coor.x, coor.y, i, j);
        }
    }
    if (assetForeground.src && fac && typeof fac === "function") {
        var coor = fac(i, j);
        if (coor && typeof coor.x === 'number' && typeof coor.y === 'number') {
            this.drawForegroundTile(coor.x, coor.y, i, j);
        }
    }
}

MapFactory.prototype.update = function () {
    this.updateMapVisibleSize();
    this.drawBackground();
    this.drawForeground();
}
<|endoftext|>


<commit_before>function MarcRecord(marc) {
    this.marc = marc;

    this.leader = function() {
        return this.marc.leader;
    }

    this.fields = function(tag) {
        var out = [];
        var tag_re = new RegExp(tag);
        for (var i = 0; i < this.marc.fields.length-1; i+=2) {
            if ( tag_re.test(this.marc.fields[i]) ) {
                out.push([this.marc.fields[i], this.marc.fields[i+1]]);
            }
        }
        return out;
    };

    this.field = function(tag) {
        var fields = this.fields(tag);
        return (fields.length == 0) ? undefined : fields[0][1];
    };

    this.has = function (tag) {
        return this.fields(tag).length > 0;
    };

    this.subfields = function(tagspec) {
        var tag = tagspec.substr(0, 3);
        var needles = tagspec.substr(3).split('');
        var haystack = this.field(tag).subfields;
        var out = [];
        for (var i = 0; i < haystack.length-1; i+=2) {
            for (var j = 0; j < needles.length; j++) {
                if (haystack[i] === needles[j]) {
                    out.push([ tag + haystack[i], haystack[i+1]]);
                    break;
                }
            }
        }
        return out;
    };

    this.subfield = function(tagspec) {
        var subfields = this.subfields(tagspec.substr(0, 4));
        return (subfields.length == 0) ? undefined : subfields[0][1];
    };

    this.ctrl = function(ctrlspec) {
        var tag = ctrlspec.substr(0, 3);
        var pos = ctrlspec.substr(4).replace(/\[|\]/g, '').split(/-/);
        if (pos[0] === '') {
            return this.field(tag);
        }
        else if (pos.length === 1) {
            return this.field(tag).substr(pos[0], 1);
        }
        else if (pos.length === 2) {
            return this.field(tag).substr(pos[0], pos[1] - pos[0] + 1);
        }
    };
}
<commit_msg>Don't throw exception if field dne.<commit_after>function MarcRecord(marc) {
    this.marc = marc;

    this.leader = function() {
        return this.marc.leader;
    }

    this.fields = function(tag) {
        var out = [];
        var tag_re = new RegExp(tag);
        for (var i = 0; i < this.marc.fields.length-1; i+=2) {
            if ( tag_re.test(this.marc.fields[i]) ) {
                out.push([this.marc.fields[i], this.marc.fields[i+1]]);
            }
        }
        return out;
    };

    this.field = function(tag) {
        var fields = this.fields(tag);
        return (fields.length == 0) ? undefined : fields[0][1];
    };

    this.has = function (tag) {
        return this.fields(tag).length > 0;
    };

    this.subfields = function(tagspec) {
        var tag = tagspec.substr(0, 3);
        var needles = tagspec.substr(3).split('');
        if(!this.field(tag)){
            return [];
        }
        var haystack = this.field(tag).subfields;
        var out = [];
        for (var i = 0; i < haystack.length-1; i+=2) {
            for (var j = 0; j < needles.length; j++) {
                if (haystack[i] === needles[j]) {
                    out.push([ tag + haystack[i], haystack[i+1]]);
                    break;
                }
            }
        }
        return out;
    };

    this.subfield = function(tagspec) {
        var subfields = this.subfields(tagspec.substr(0, 4));
        return (subfields.length == 0) ? undefined : subfields[0][1];
    };

    this.ctrl = function(ctrlspec) {
        var tag = ctrlspec.substr(0, 3);
        var pos = ctrlspec.substr(4).replace(/\[|\]/g, '').split(/-/);
        if (pos[0] === '') {
            return this.field(tag);
        }
        else if (pos.length === 1) {
            return this.field(tag).substr(pos[0], 1);
        }
        else if (pos.length === 2) {
            return this.field(tag).substr(pos[0], pos[1] - pos[0] + 1);
        }
    };
}
<|endoftext|>