<commit_before>
var _     	= require('lodash');
var React		= require('react');
var api 		= require('data/api.js')
var TitleMenu  	= require('component/TitleMenu.jsx');
var MenuItem    = require('component/MenuItem.jsx');
var FileInput = require('react-file-input');
var Dropzone = require('react-dropzone');

var DocForm = React.createClass({
// see here: https://fitacular.com/blog/react/2014/06/23/react-file-upload-base64/

  // since we are starting off without any data, there is no initial value
  getInitialState: function() {
    return {
      data_uri: null,
      config_options: [],
      uq_id_column: null,
      location_column: null,
      campaign_column: null,
      created_doc_id: null,
      doc_detail_meta: null,
      doc_is_refreshed: false,
      new_doc_title: null,
    };
  },

  onDrop: function (files) {
    this.handleFile(files[0])
  },

  // prevent form from submitting; we are going to capture the file contents
  handleSubmit: function(e) {
    e.preventDefault();
  },

  // when a file is passed to the input field, retrieve the contents as a
  // base64-encoded data URI and save it to the component's state
  handleFile: function(file) {
    var self = this;

    var reader = new FileReader();

    reader.onload = function(upload) {
      self.setState({
        data_uri: upload.target.result,
      });

      api.uploadPost({
          docfile:upload.target.result,
          doc_title:file.name,
        }).then(function (response) {

        var new_doc_obj = response.objects[0]
        self.setState({
          config_options: new_doc_obj.file_header.replace('"','').split(','),
          created_doc_id: new_doc_obj.id,
          new_doc_title: response.doc_title,
        });
      })

      api.docDetailType().then(function (response) {
        var doc_detail_types = _.indexBy(response.objects, 'name');
        self.setState({doc_detail_meta:doc_detail_types})
      })
    }
    reader.readAsDataURL(file);
  },

  setDocConfig :function (config_type, config_val) {
    var self = this;
    var doc_detail_type_lookup = {}

    var doc_detail_meta = this.state.doc_detail_meta
    var doc_detail_type = doc_detail_meta[config_type]

    api.docDetailPost({
          document_id: this.state.created_doc_id,
          doc_detail_type_id:  doc_detail_type.id,
          doc_detail_value: config_val
    }).then(function (response) {
        var stateObject = {}
        stateObject[config_type] = response.objects[0].doc_detail_value
        self.setState(stateObject)
      });
  },

  syncDocData : function (config_val) {
      var self = this;

      api.transformUpload({document_id: this.state.created_doc_id},null,{'cache-control':'no-cache'})
      .then(function (response) {
          self.setState({doc_is_refreshed: true})
      });
  },

  buildHeaderList : function (config_type){
    var state_header = this.state.config_options

    return MenuItem.fromArray(
      _.map(state_header, d => {
        return {
          title : d.replace('"',''),
          value : d.replace('"',''),
        };
      }),
      this.setDocConfig.bind('config_type',config_type));
  },

  // return the structure to display and bind the onChange, onSubmit handlers
  render: function() {

    var uqHeaderList = this.buildHeaderList('uq_id_column')
    var rgHeaderList = this.buildHeaderList('location_column')
    var cpHeaderList = this.buildHeaderList('campaign_column')

    if (this.state.created_doc_id) {
        var uq_col = this.state.uq_id_column
        var rg_col = this.state.location_column
        var cp_col = this.state.campaign_column

        var fileConfigForm =

            Unique ID Column:
            <TitleMenu text={uq_col}>
              {uqHeaderList}
            </TitleMenu>

            location Column:
            <TitleMenu text={rg_col}>
              {rgHeaderList}
            </TitleMenu>

            Campaign Column:
            <TitleMenu text={cp_col}>
              {cpHeaderList}
            </TitleMenu>

      }
      else {
        var fileConfigForm = ''
      }

      if (this.state.uq_id_column && this.state.location_column && this.state.campaign_column){
        var refreshBtn = <button onClick={this.syncDocData}> Sync Data</button>
      }
      else {
        var refreshBtn = ''
      }

    if (this.state.uq_id_column && this.state.location_column && this.state.campaign_column && this.state.doc_is_refreshed){
      var next_link = "/datapoints/source-data/Nigeria/2015/06/viewraw/" + this.state.created_doc_id;
      // FIXME ^^
      var reviewBtn =  Review Upload
    }
    else {
      var reviewBtn = ''
    }

    var divZoneStyle = {
      margin: 'auto',
      width: '50%',
      padding: '10px',
      border: '2px dashed #0087F7',
    };

    var dropZoneStyle = {
      minHeight: '100px',
      padding: '54px 54px',
      marginRight: '150px',
    };

    // since JSX is case sensitive, be sure to use 'encType'
    return (

      <Dropzone onDrop={this.onDrop} style={dropZoneStyle}>

Click here, or Drag a File to Upload New Data!

      </Dropzone>
      {fileConfigForm}
      {refreshBtn}
      {reviewBtn}

);
  },
});

module.exports = DocForm;
<commit_msg>when POSTing a new document, use response.objects not response.objects[0]<commit_after>
var _     	= require('lodash');
var React		= require('react');
var api 		= require('data/api.js')
var TitleMenu  	= require('component/TitleMenu.jsx');
var MenuItem    = require('component/MenuItem.jsx');
var FileInput = require('react-file-input');
var Dropzone = require('react-dropzone');

var DocForm = React.createClass({
// see here: https://fitacular.com/blog/react/2014/06/23/react-file-upload-base64/

  // since we are starting off without any data, there is no initial value
  getInitialState: function() {
    return {
      data_uri: null,
      config_options: [],
      uq_id_column: null,
      location_column: null,
      campaign_column: null,
      created_doc_id: null,
      doc_detail_meta: null,
      doc_is_refreshed: false,
      new_doc_title: null,
    };
  },

  onDrop: function (files) {
    this.handleFile(files[0])
  },

  // prevent form from submitting; we are going to capture the file contents
  handleSubmit: function(e) {
    e.preventDefault();
  },

  // when a file is passed to the input field, retrieve the contents as a
  // base64-encoded data URI and save it to the component's state
  handleFile: function(file) {
    var self = this;

    var reader = new FileReader();

    reader.onload = function(upload) {
      self.setState({
        data_uri: upload.target.result,
      });

      api.uploadPost({
          docfile:upload.target.result,
          doc_title:file.name,
        }).then(function (response) {

        var new_doc_obj = response.objects
        console.log('new_doc_obk',new_doc_obj)

        self.setState({
          config_options: new_doc_obj.file_header.replace('"','').split(','),
          created_doc_id: new_doc_obj.id,
          new_doc_title: response.doc_title,
        });
      })

      api.docDetailType().then(function (response) {
        var doc_detail_types = _.indexBy(response.objects, 'name');
        self.setState({doc_detail_meta:doc_detail_types})
      })
    }
    reader.readAsDataURL(file);
  },

  setDocConfig :function (config_type, config_val) {
    var self = this;
    var doc_detail_type_lookup = {}

    var doc_detail_meta = this.state.doc_detail_meta
    var doc_detail_type = doc_detail_meta[config_type]

    api.docDetailPost({
          document_id: this.state.created_doc_id,
          doc_detail_type_id:  doc_detail_type.id,
          doc_detail_value: config_val
    }).then(function (response) {
        var stateObject = {}
        stateObject[config_type] = response.objects[0].doc_detail_value
        self.setState(stateObject)
      });
  },

  syncDocData : function (config_val) {
      var self = this;

      api.transformUpload({document_id: this.state.created_doc_id},null,{'cache-control':'no-cache'})
      .then(function (response) {
          self.setState({doc_is_refreshed: true})
      });
  },

  buildHeaderList : function (config_type){
    var state_header = this.state.config_options

    return MenuItem.fromArray(
      _.map(state_header, d => {
        return {
          title : d.replace('"',''),
          value : d.replace('"',''),
        };
      }),
      this.setDocConfig.bind('config_type',config_type));
  },

  // return the structure to display and bind the onChange, onSubmit handlers
  render: function() {

    var uqHeaderList = this.buildHeaderList('uq_id_column')
    var rgHeaderList = this.buildHeaderList('location_column')
    var cpHeaderList = this.buildHeaderList('campaign_column')

    if (this.state.created_doc_id) {
        var uq_col = this.state.uq_id_column
        var rg_col = this.state.location_column
        var cp_col = this.state.campaign_column

        var fileConfigForm =

            Unique ID Column:
            <TitleMenu text={uq_col}>
              {uqHeaderList}
            </TitleMenu>

            location Column:
            <TitleMenu text={rg_col}>
              {rgHeaderList}
            </TitleMenu>

            Campaign Column:
            <TitleMenu text={cp_col}>
              {cpHeaderList}
            </TitleMenu>

      }
      else {
        var fileConfigForm = ''
      }

      if (this.state.uq_id_column && this.state.location_column && this.state.campaign_column){
        var refreshBtn = <button onClick={this.syncDocData}> Sync Data</button>
      }
      else {
        var refreshBtn = ''
      }

    if (this.state.uq_id_column && this.state.location_column && this.state.campaign_column && this.state.doc_is_refreshed){
      var next_link = "/datapoints/source-data/Nigeria/2015/06/viewraw/" + this.state.created_doc_id;
      // FIXME ^^
      var reviewBtn =  Review Upload
    }
    else {
      var reviewBtn = ''
    }

    var divZoneStyle = {
      margin: 'auto',
      width: '50%',
      padding: '10px',
      border: '2px dashed #0087F7',
    };

    var dropZoneStyle = {
      minHeight: '100px',
      padding: '54px 54px',
      marginRight: '150px',
    };

    // since JSX is case sensitive, be sure to use 'encType'
    return (

      <Dropzone onDrop={this.onDrop} style={dropZoneStyle}>

Click here, or Drag a File to Upload New Data!

      </Dropzone>
      {fileConfigForm}
      {refreshBtn}
      {reviewBtn}

);
  },
});

module.exports = DocForm;
<|endoftext|>


<commit_before>import _ from 'lodash'
import axios from 'axios'
import FMI from './fmi-constants.js'
import fs from 'fs'
import url from 'url'
import xml2js from 'xml2js'
import xml2jsProcessors from 'xml2js/lib/processors.js'

const {parseStringPromise} = xml2js
const {firstCharLowerCase, stripPrefix} = xml2jsProcessors

const FEATURE_URL = url.parse(FMI.WFS_FEATURE_URL)
FEATURE_URL.query = {
  request: 'getFeature',
  // eslint-disable-next-line camelcase
  storedquery_id: 'fmi::observations::lightning::multipointcoverage'
}
console.log(`Configured lightning URL stem: ${url.format(FEATURE_URL)}`)

async function fetchLightnings(frameDates, useLocalData = false) {
  if (frameDates.length < 2) {
    return []
  }

  const data = await loadData(frameDates, useLocalData)
  const wfsResponse = await xmlToObject(data)
  const lightnings = extractLocationsAndTimes(wfsResponse)
  return snapLightningsToFrames(lightnings, frameDates)
}

async function loadData(frameDates, useLocalData) {
  if (useLocalData) {
    const lightningPath = 'resources/multipointcoverage.xml'
    console.log(`Loading lightnings locally from: ${lightningPath}`)
    return fs.promises.readFile(lightningPath)
  }
  const lightningsUrl = constructLightningsUrl(frameDates)
  return (await axios.get(lightningsUrl)).data
}

function constructLightningsUrl(frameDates) {
  const [firstDate, secondData] = frameDates
  const frameInterval = secondData.getTime() - firstDate.getTime()
  const starttime = new Date(firstDate.getTime() - frameInterval)
  const endtime = frameDates[frameDates.length - 1]
  const lightningsUrl = {...FEATURE_URL}
  lightningsUrl.query.starttime = starttime.toISOString()
  lightningsUrl.query.endtime = endtime.toISOString()
  return url.format(lightningsUrl)
}

function xmlToObject(xml) {
  return parseStringPromise(xml, {tagNameProcessors: [stripPrefix, firstCharLowerCase]})
}

function extractLocationsAndTimes({featureCollection}) {
  if (!featureCollection.member) {
    // No lightnings observed in this collection period
    return []
  }

  const positions = featureCollection.member[0].gridSeriesObservation[0].result[0].multiPointCoverage[0].domainSet[0].simpleMultiPoint[0].positions[0]
  return positions
    .split('\n')
    .map(pos => {
      const [lat, lon, seconds] = pos.trim().split(' ')
      return {
        location: [parseFloat(lon), parseFloat(lat)],
        time: new Date(parseInt(seconds, 10) * 1000)
      }
    })
}

function snapLightningsToFrames(lightnings, frameDates) {
  return frameDates.map(frame => ({
      timestamp: frame.toISOString(),
      locations: _.remove(lightnings, ({time}) => time < frame).map(({location}) => location)
    })
  )
}

export {
  fetchLightnings
}
<commit_msg>Simplify snapLightnigsToFrames, without lodash<commit_after>import axios from 'axios'
import FMI from './fmi-constants.js'
import fs from 'fs'
import url from 'url'
import xml2js from 'xml2js'
import xml2jsProcessors from 'xml2js/lib/processors.js'

const {parseStringPromise} = xml2js
const {firstCharLowerCase, stripPrefix} = xml2jsProcessors

const FEATURE_URL = url.parse(FMI.WFS_FEATURE_URL)
FEATURE_URL.query = {
  request: 'getFeature',
  // eslint-disable-next-line camelcase
  storedquery_id: 'fmi::observations::lightning::multipointcoverage'
}
console.log(`Configured lightning URL stem: ${url.format(FEATURE_URL)}`)

async function fetchLightnings(frameDates, useLocalData = false) {
  if (frameDates.length < 2) {
    return []
  }

  const data = await loadData(frameDates, useLocalData)
  const wfsResponse = await xmlToObject(data)
  const lightnings = extractLocationsAndTimes(wfsResponse)
  return snapLightningsToFrames(lightnings, frameDates)
}

async function loadData(frameDates, useLocalData) {
  if (useLocalData) {
    const lightningPath = 'resources/multipointcoverage.xml'
    console.log(`Loading lightnings locally from: ${lightningPath}`)
    return fs.promises.readFile(lightningPath)
  }
  const lightningsUrl = constructLightningsUrl(frameDates)
  return (await axios.get(lightningsUrl)).data
}

function constructLightningsUrl(frameDates) {
  const [firstDate, secondData] = frameDates
  const frameInterval = secondData.getTime() - firstDate.getTime()
  const starttime = new Date(firstDate.getTime() - frameInterval)
  const endtime = frameDates[frameDates.length - 1]
  const lightningsUrl = {...FEATURE_URL}
  lightningsUrl.query.starttime = starttime.toISOString()
  lightningsUrl.query.endtime = endtime.toISOString()
  return url.format(lightningsUrl)
}

function xmlToObject(xml) {
  return parseStringPromise(xml, {tagNameProcessors: [stripPrefix, firstCharLowerCase]})
}

function extractLocationsAndTimes({featureCollection}) {
  if (!featureCollection.member) {
    // No lightnings observed in this collection period
    return []
  }

  const positions = featureCollection.member[0].gridSeriesObservation[0].result[0].multiPointCoverage[0].domainSet[0].simpleMultiPoint[0].positions[0]
  return positions
    .split('\n')
    .map(pos => {
      const [lat, lon, seconds] = pos.trim().split(' ')
      return {
        location: [parseFloat(lon), parseFloat(lat)],
        time: new Date(parseInt(seconds, 10) * 1000)
      }
    })
}

function snapLightningsToFrames(lightnings, frameDates) {
  return frameDates.map((frameDate, index) => ({
      timestamp: frameDate.toISOString(),
      locations: locationsAfterPreviousFrame(lightnings, frameDate, frameDates[index - 1])
    })
  )
}

function locationsAfterPreviousFrame(lightnings, currentTime, previousTime = new Date(0)) {
  return lightnings
    .filter(l => l.time > previousTime && l.time <= currentTime)
    .map(f => f.location)
}

export {
  fetchLightnings
}
<|endoftext|>


<commit_before>/**
 * Builds the DLL for development electron renderer process
 */

import webpack from "webpack";
import path from "path";
import merge from "webpack-merge";
import baseConfig from "./webpack.config.base";
import { dependencies } from "./package.json";
import CheckNodeEnv from "./internals/scripts/CheckNodeEnv";

CheckNodeEnv("development");

const dist = path.resolve(process.cwd(), "dll");

export default merge.smart(baseConfig, {
  context: process.cwd(),

  devtool: "eval",

  target: "electron-renderer",

  externals: ["fsevents", "crypto-browserify"],

  /**
   * @HACK: Copy and pasted from renderer dev config. Consider merging these
   *        rules into the base config. May cause breaking changes.
   */
  module: {
    rules: [
      {
        test: /\.global\.css$/,
        use: [
          {
            loader: "style-loader"
          },
          {
            loader: "css-loader",
            options: {
              sourceMap: true
            }
          }
        ]
      },
      {
        test: /^((?!\.global).)*\.css$/,
        use: [
          {
            loader: "style-loader"
          },
          {
            loader: "css-loader",
            options: {
              modules: true,
              sourceMap: true,
              importLoaders: 1,
              localIdentName: "[name]__[local]__[hash:base64:5]"
            }
          }
        ]
      },
      // Add SASS support  - compile all .global.scss files and pipe it to style.css
      {
        test: /\.global\.scss$/,
        use: [
          {
            loader: "style-loader"
          },
          {
            loader: "css-loader",
            options: {
              sourceMap: true
            }
          },
          {
            loader: "sass-loader"
          }
        ]
      },
      // Add SASS support  - compile all other .scss files and pipe it to style.css
      {
        test: /^((?!\.global).)*\.scss$/,
        use: [
          {
            loader: "style-loader"
          },
          {
            loader: "css-loader",
            options: {
              modules: true,
              sourceMap: true,
              importLoaders: 1,
              localIdentName: "[name]__[local]__[hash:base64:5]"
            }
          },
          {
            loader: "sass-loader"
          }
        ]
      },
      // WOFF Font
      {
        test: /\.woff(\?v=\d+\.\d+\.\d+)?$/,
        use: {
          loader: "url-loader",
          options: {
            limit: 10000,
            mimetype: "application/font-woff"
          }
        }
      },
      // WOFF2 Font
      {
        test: /\.woff2(\?v=\d+\.\d+\.\d+)?$/,
        use: {
          loader: "url-loader",
          options: {
            limit: 10000,
            mimetype: "application/font-woff"
          }
        }
      },
      // TTF Font
      {
        test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/,
        use: {
          loader: "url-loader",
          options: {
            limit: 10000,
            mimetype: "application/octet-stream"
          }
        }
      },
      // EOT Font
      {
        test: /\.eot(\?v=\d+\.\d+\.\d+)?$/,
        use: "file-loader"
      },
      // SVG Font
      {
        test: /\.svg(\?v=\d+\.\d+\.\d+)?$/,
        use: {
          loader: "url-loader",
          options: {
            limit: 10000,
            mimetype: "image/svg+xml"
          }
        }
      },
      // Common Image Formats
      {
        test: /\.(?:ico|gif|png|jpg|jpeg|webp)$/,
        use: "url-loader"
      }
    ]
  },

  resolve: {
    modules: ["app"]
  },

  entry: {
    renderer: Object.keys(dependencies || {}).filter(
      dependency => dependency !== "font-awesome"
    )
  },

  output: {
    library: "renderer",
    path: dist,
    filename: "[name].dev.dll.js",
    libraryTarget: "var"
  },

  plugins: [
    new webpack.DllPlugin({
      path: path.join(dist, "[name].json"),
      name: "[name]"
    }),

    /**
     * Create global constants which can be configured at compile time.
     *
     * Useful for allowing different behaviour between development builds and
     * release builds
     *
     * NODE_ENV should be production so that modules do not perform certain
     * development checks
     */
    new webpack.DefinePlugin({
      "process.env.NODE_ENV": JSON.stringify(
        process.env.NODE_ENV || "development"
      )
    }),

    new webpack.LoaderOptionsPlugin({
      debug: true,
      options: {
        context: path.resolve(process.cwd(), "app"),
        output: {
          path: path.resolve(process.cwd(), "dll")
        }
      }
    })
  ]
});
<commit_msg>Added skeleton here, it was breaking<commit_after>/**
 * Builds the DLL for development electron renderer process
 */

import webpack from "webpack";
import path from "path";
import merge from "webpack-merge";
import baseConfig from "./webpack.config.base";
import { dependencies } from "./package.json";
import CheckNodeEnv from "./internals/scripts/CheckNodeEnv";

CheckNodeEnv("development");

const dist = path.resolve(process.cwd(), "dll");

export default merge.smart(baseConfig, {
  context: process.cwd(),

  devtool: "eval",

  target: "electron-renderer",

  externals: ["fsevents", "crypto-browserify"],

  /**
   * @HACK: Copy and pasted from renderer dev config. Consider merging these
   *        rules into the base config. May cause breaking changes.
   */
  module: {
    rules: [
      {
        test: /\.global\.css$/,
        use: [
          {
            loader: "style-loader"
          },
          {
            loader: "css-loader",
            options: {
              sourceMap: true
            }
          }
        ]
      },
      {
        test: /^((?!\.global).)*\.css$/,
        use: [
          {
            loader: "style-loader"
          },
          {
            loader: "css-loader",
            options: {
              modules: true,
              sourceMap: true,
              importLoaders: 1,
              localIdentName: "[name]__[local]__[hash:base64:5]"
            }
          }
        ]
      },
      // Add SASS support  - compile all .global.scss files and pipe it to style.css
      {
        test: /\.global\.scss$/,
        use: [
          {
            loader: "style-loader"
          },
          {
            loader: "css-loader",
            options: {
              sourceMap: true
            }
          },
          {
            loader: "sass-loader"
          }
        ]
      },
      // Add SASS support  - compile all other .scss files and pipe it to style.css
      {
        test: /^((?!\.global).)*\.scss$/,
        use: [
          {
            loader: "style-loader"
          },
          {
            loader: "css-loader",
            options: {
              modules: true,
              sourceMap: true,
              importLoaders: 1,
              localIdentName: "[name]__[local]__[hash:base64:5]"
            }
          },
          {
            loader: "sass-loader"
          }
        ]
      },
      // WOFF Font
      {
        test: /\.woff(\?v=\d+\.\d+\.\d+)?$/,
        use: {
          loader: "url-loader",
          options: {
            limit: 10000,
            mimetype: "application/font-woff"
          }
        }
      },
      // WOFF2 Font
      {
        test: /\.woff2(\?v=\d+\.\d+\.\d+)?$/,
        use: {
          loader: "url-loader",
          options: {
            limit: 10000,
            mimetype: "application/font-woff"
          }
        }
      },
      // TTF Font
      {
        test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/,
        use: {
          loader: "url-loader",
          options: {
            limit: 10000,
            mimetype: "application/octet-stream"
          }
        }
      },
      // EOT Font
      {
        test: /\.eot(\?v=\d+\.\d+\.\d+)?$/,
        use: "file-loader"
      },
      // SVG Font
      {
        test: /\.svg(\?v=\d+\.\d+\.\d+)?$/,
        use: {
          loader: "url-loader",
          options: {
            limit: 10000,
            mimetype: "image/svg+xml"
          }
        }
      },
      // Common Image Formats
      {
        test: /\.(?:ico|gif|png|jpg|jpeg|webp)$/,
        use: "url-loader"
      }
    ]
  },

  resolve: {
    modules: ["app"]
  },

  entry: {
    renderer: Object.keys(dependencies || {}).filter(
      dependency =>
        dependency !== "font-awesome" && dependency !== "skeleton-css"
    )
  },

  output: {
    library: "renderer",
    path: dist,
    filename: "[name].dev.dll.js",
    libraryTarget: "var"
  },

  plugins: [
    new webpack.DllPlugin({
      path: path.join(dist, "[name].json"),
      name: "[name]"
    }),

    /**
     * Create global constants which can be configured at compile time.
     *
     * Useful for allowing different behaviour between development builds and
     * release builds
     *
     * NODE_ENV should be production so that modules do not perform certain
     * development checks
     */
    new webpack.DefinePlugin({
      "process.env.NODE_ENV": JSON.stringify(
        process.env.NODE_ENV || "development"
      )
    }),

    new webpack.LoaderOptionsPlugin({
      debug: true,
      options: {
        context: path.resolve(process.cwd(), "app"),
        output: {
          path: path.resolve(process.cwd(), "dll")
        }
      }
    })
  ]
});
<|endoftext|>


<commit_before>'use strict';

const { fork, spawn } = require('child_process');
const path = require('path');
const trimNewlines = require('trim-newlines');
const debugLog = require('./debugLog');
const { createUniqueId } = require('./utils');

const handlerCache = {};
const messageCallbacks = {};

function runProxyHandler(funOptions, options) {
  return (event, context) => {
    const args = ['invoke', 'local', '-f', funOptions.funName];
    const stage = options.s || options.stage;

    if (stage) args.push('-s', stage);

    // Use path to binary if provided, otherwise assume globally-installed
    const binPath = options.b || options.binPath;
    const cmd = binPath || 'sls';

    const process = spawn(cmd, args, {
      cwd: funOptions.servicePath,
      shell: true,
      stdio: ['pipe', 'pipe', 'pipe'],
    });

    process.stdin.write(`${JSON.stringify(event)}\n`);
    process.stdin.end();

    let results = '';
    let hasDetectedJson = false;

    process.stdout.on('data', (data) => {
      let str = data.toString('utf8');

      if (hasDetectedJson) {
        // Assumes that all data after matching the start of the
        // JSON result is the rest of the context result.
        results += trimNewlines(str);
      } else {
        // Search for the start of the JSON result
        // https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html#api-gateway-simple-proxy-for-lambda-output-format
        const match = /{[\r\n]?\s*"isBase64Encoded"|{[\r\n]?\s*"statusCode"|{[\r\n]?\s*"headers"|{[\r\n]?\s*"body"|{[\r\n]?\s*"principalId"/.exec(
          str,
        );
        if (match && match.index > -1) {
          // The JSON result was in this chunk so slice it out
          hasDetectedJson = true;
          results += trimNewlines(str.slice(match.index));
          str = str.slice(0, match.index);
        }

        if (str.length > 0) {
          // The data does not look like JSON and we have not
          // detected the start of JSON, so write the
          // output to the console instead.
          console.log('Proxy Handler could not detect JSON:', str);
        }
      }
    });

    process.stderr.on('data', data => {
      context.fail(data);
    });

    process.on('close', code => {
      if (code.toString() === '0') {
        try {
          context.succeed(JSON.parse(results));
        } catch (ex) {
          context.fail(results);
        }
      } else {
        context.succeed(code, results);
      }
    });
  };
}

exports.getFunctionOptions = function getFunctionOptions(
  fun,
  funName,
  servicePath,
  serviceRuntime,
) {
  // Split handler into method name and path i.e. handler.run
  // Support nested paths i.e. ./src/somefolder/.handlers/handler.run
  const lastIndexOfDelimiter = fun.handler.lastIndexOf('.');
  const handlerPath = fun.handler.substr(0, lastIndexOfDelimiter);
  const handlerName = fun.handler.substr(lastIndexOfDelimiter + 1);

  return {
    funName,
    funTimeout: (fun.timeout || 30) * 1000,
    handlerName, // i.e. run
    handlerPath: path.join(servicePath, handlerPath),
    memorySize: fun.memorySize,
    runtime: fun.runtime || serviceRuntime,
  };
};

exports.createExternalHandler = function createExternalHandler(
  funOptions,
  options,
) {
  let handlerContext = handlerCache[funOptions.handlerPath];

  function handleFatal(error) {
    debugLog(`External handler received fatal error ${JSON.stringify(error)}`);
    handlerContext.inflight.forEach((id) => messageCallbacks[id](error));
    handlerContext.inflight.clear();
    delete handlerCache[funOptions.handlerPath];
  }

  if (!handlerContext) {
    debugLog(`Loading external handler... (${funOptions.handlerPath})`);

    const helperPath = path.resolve(__dirname, 'ipcHelper.js');
    const env = {};
    for (const key of Object.getOwnPropertyNames(process.env)) {
      if (process.env[key] !== undefined && process.env[key] !== 'undefined')
        env[key] = process.env[key];
    }

    const ipcProcess = fork(helperPath, [funOptions.handlerPath], {
      env,
      stdio: [0, 1, 2, 'ipc'],
    });

    handlerContext = { process: ipcProcess, inflight: new Set() };

    if (options.skipCacheInvalidation) {
      handlerCache[funOptions.handlerPath] = handlerContext;
    }

    ipcProcess.on('message', (message) => {
      debugLog(`External handler received message ${JSON.stringify(message)}`);
      if (message.id && messageCallbacks[message.id]) {
        messageCallbacks[message.id](message.error, message.ret);
        handlerContext.inflight.delete(message.id);
        delete messageCallbacks[message.id];
      } else if (message.error) {
        // Handler died!
        handleFatal(message.error);
      }

      if (!options.skipCacheInvalidation) {
        handlerContext.process.kill();
        delete handlerCache[funOptions.handlerPath];
      }
    });

    ipcProcess.on('error', (error) => handleFatal(error));
    ipcProcess.on('exit', (code) =>
      handleFatal(`Handler process exited with code ${code}`),
    );
  } else {
    debugLog(`Using existing external handler for ${funOptions.handlerPath}`);
  }

  return (event, context, done) => {
    const id = createUniqueId();
    messageCallbacks[id] = done;
    handlerContext.inflight.add(id);
    handlerContext.process.send(
      Object.assign({}, funOptions, { id, event, context }),
    );
  };
};

// Create a function handler
// The function handler is used to simulate Lambda functions
exports.createHandler = function createHandler(funOptions, options) {
  if (options.useSeparateProcesses) {
    return this.createExternalHandler(funOptions, options);
  }

  if (!options.skipCacheInvalidation) {
    debugLog('Invalidating cache...');

    for (const key in require.cache) {
      // Require cache invalidation, brutal and fragile.
      // Might cause errors, if so please submit an issue.
      if (!key.match(options.cacheInvalidationRegex || /node_modules/))
        delete require.cache[key];
    }
    const currentFilePath = __filename;
    if (
      require.cache[currentFilePath] &&
      require.cache[currentFilePath].children
    ) {
      const nextChildren = [];

      require.cache[currentFilePath].children.forEach((moduleCache) => {
        if (
          moduleCache.filename.match(
            options.cacheInvalidationRegex || /node_modules/,
          )
        ) {
          nextChildren.push(moduleCache);
        }
      });

      require.cache[currentFilePath].children = nextChildren;
    }
  }

  debugLog(`Loading handler... (${funOptions.handlerPath})`);

  let handler = null;

  if (funOptions.runtime.startsWith('nodejs')) {
    handler = require(funOptions.handlerPath)[funOptions.handlerName];
  } else {
    handler = runProxyHandler(funOptions, options);
  }

  if (typeof handler !== 'function') {
    throw new Error(
      `Serverless-offline: handler for '${funOptions.funName}' is not a function`,
    );
  }

  return handler;
};

exports.cleanup = function cleanup() {
  for (const key in handlerCache) {
    handlerCache[key].process.kill();
  }
};
<commit_msg>Combine comments<commit_after>'use strict';

const { fork, spawn } = require('child_process');
const path = require('path');
const trimNewlines = require('trim-newlines');
const debugLog = require('./debugLog');
const { createUniqueId } = require('./utils');

const handlerCache = {};
const messageCallbacks = {};

function runProxyHandler(funOptions, options) {
  return (event, context) => {
    const args = ['invoke', 'local', '-f', funOptions.funName];
    const stage = options.s || options.stage;

    if (stage) args.push('-s', stage);

    // Use path to binary if provided, otherwise assume globally-installed
    const binPath = options.b || options.binPath;
    const cmd = binPath || 'sls';

    const process = spawn(cmd, args, {
      cwd: funOptions.servicePath,
      shell: true,
      stdio: ['pipe', 'pipe', 'pipe'],
    });

    process.stdin.write(`${JSON.stringify(event)}\n`);
    process.stdin.end();

    let results = '';
    let hasDetectedJson = false;

    process.stdout.on('data', (data) => {
      let str = data.toString('utf8');

      if (hasDetectedJson) {
        // Assumes that all data after matching the start of the
        // JSON result is the rest of the context result.
        results += trimNewlines(str);
      } else {
        // Search for the start of the JSON result
        // https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html#api-gateway-simple-proxy-for-lambda-output-format
        const match = /{[\r\n]?\s*"isBase64Encoded"|{[\r\n]?\s*"statusCode"|{[\r\n]?\s*"headers"|{[\r\n]?\s*"body"|{[\r\n]?\s*"principalId"/.exec(
          str,
        );
        if (match && match.index > -1) {
          // The JSON result was in this chunk so slice it out
          hasDetectedJson = true;
          results += trimNewlines(str.slice(match.index));
          str = str.slice(0, match.index);
        }

        if (str.length > 0) {
          // The data does not look like JSON and we have not
          // detected the start of JSON, so write the
          // output to the console instead.
          console.log('Proxy Handler could not detect JSON:', str);
        }
      }
    });

    process.stderr.on('data', data => {
      context.fail(data);
    });

    process.on('close', code => {
      if (code.toString() === '0') {
        try {
          context.succeed(JSON.parse(results));
        } catch (ex) {
          context.fail(results);
        }
      } else {
        context.succeed(code, results);
      }
    });
  };
}

exports.getFunctionOptions = function getFunctionOptions(
  fun,
  funName,
  servicePath,
  serviceRuntime,
) {
  // Split handler into method name and path i.e. handler.run
  // Support nested paths i.e. ./src/somefolder/.handlers/handler.run
  const lastIndexOfDelimiter = fun.handler.lastIndexOf('.');
  const handlerPath = fun.handler.substr(0, lastIndexOfDelimiter);
  const handlerName = fun.handler.substr(lastIndexOfDelimiter + 1);

  return {
    funName,
    funTimeout: (fun.timeout || 30) * 1000,
    handlerName, // i.e. run
    handlerPath: path.join(servicePath, handlerPath),
    memorySize: fun.memorySize,
    runtime: fun.runtime || serviceRuntime,
  };
};

exports.createExternalHandler = function createExternalHandler(
  funOptions,
  options,
) {
  let handlerContext = handlerCache[funOptions.handlerPath];

  function handleFatal(error) {
    debugLog(`External handler received fatal error ${JSON.stringify(error)}`);
    handlerContext.inflight.forEach((id) => messageCallbacks[id](error));
    handlerContext.inflight.clear();
    delete handlerCache[funOptions.handlerPath];
  }

  if (!handlerContext) {
    debugLog(`Loading external handler... (${funOptions.handlerPath})`);

    const helperPath = path.resolve(__dirname, 'ipcHelper.js');
    const env = {};
    for (const key of Object.getOwnPropertyNames(process.env)) {
      if (process.env[key] !== undefined && process.env[key] !== 'undefined')
        env[key] = process.env[key];
    }

    const ipcProcess = fork(helperPath, [funOptions.handlerPath], {
      env,
      stdio: [0, 1, 2, 'ipc'],
    });

    handlerContext = { process: ipcProcess, inflight: new Set() };

    if (options.skipCacheInvalidation) {
      handlerCache[funOptions.handlerPath] = handlerContext;
    }

    ipcProcess.on('message', (message) => {
      debugLog(`External handler received message ${JSON.stringify(message)}`);
      if (message.id && messageCallbacks[message.id]) {
        messageCallbacks[message.id](message.error, message.ret);
        handlerContext.inflight.delete(message.id);
        delete messageCallbacks[message.id];
      } else if (message.error) {
        // Handler died!
        handleFatal(message.error);
      }

      if (!options.skipCacheInvalidation) {
        handlerContext.process.kill();
        delete handlerCache[funOptions.handlerPath];
      }
    });

    ipcProcess.on('error', (error) => handleFatal(error));
    ipcProcess.on('exit', (code) =>
      handleFatal(`Handler process exited with code ${code}`),
    );
  } else {
    debugLog(`Using existing external handler for ${funOptions.handlerPath}`);
  }

  return (event, context, done) => {
    const id = createUniqueId();
    messageCallbacks[id] = done;
    handlerContext.inflight.add(id);
    handlerContext.process.send(
      Object.assign({}, funOptions, { id, event, context }),
    );
  };
};

// function handler used to simulate Lambda functions
exports.createHandler = function createHandler(funOptions, options) {
  if (options.useSeparateProcesses) {
    return this.createExternalHandler(funOptions, options);
  }

  if (!options.skipCacheInvalidation) {
    debugLog('Invalidating cache...');

    for (const key in require.cache) {
      // Require cache invalidation, brutal and fragile.
      // Might cause errors, if so please submit an issue.
      if (!key.match(options.cacheInvalidationRegex || /node_modules/))
        delete require.cache[key];
    }
    const currentFilePath = __filename;
    if (
      require.cache[currentFilePath] &&
      require.cache[currentFilePath].children
    ) {
      const nextChildren = [];

      require.cache[currentFilePath].children.forEach((moduleCache) => {
        if (
          moduleCache.filename.match(
            options.cacheInvalidationRegex || /node_modules/,
          )
        ) {
          nextChildren.push(moduleCache);
        }
      });

      require.cache[currentFilePath].children = nextChildren;
    }
  }

  debugLog(`Loading handler... (${funOptions.handlerPath})`);

  let handler = null;

  if (funOptions.runtime.startsWith('nodejs')) {
    handler = require(funOptions.handlerPath)[funOptions.handlerName];
  } else {
    handler = runProxyHandler(funOptions, options);
  }

  if (typeof handler !== 'function') {
    throw new Error(
      `Serverless-offline: handler for '${funOptions.funName}' is not a function`,
    );
  }

  return handler;
};

exports.cleanup = function cleanup() {
  for (const key in handlerCache) {
    handlerCache[key].process.kill();
  }
};
<|endoftext|>


<commit_before>var Inventory = {
    items: [],

    clear: function () {
        this.items = [];

        if (Settings.spawnWithItems) {
            this.createAndAdd(ItemTypes.FIRE_WOOD, 25);
            this.createAndAdd(ItemTypes.MATCH_BOX, 1);
            this.createAndAdd(ItemTypes.AXE, 1);
        }

        this.syncUi();
    },

    add: function (item) {
        this.items.push(item);
        this.syncUi();
    },

    create: function (itemTypeId) {
        if (typeof window.Items[itemTypeId] === 'undefined') {
            return false;
        }

        var itemClass = window.Items[itemTypeId];
        var itemObj = new itemClass();
        return itemObj;
    },

    createAndAdd: function (itemTypeId, amount) {
        for (var i = 0; i < amount; i++) {
            var item = this.create(itemTypeId);
            this.items.push(item);
        }

        this.syncUi();
    },

    getItemQty: function (itemTypeId) {
        var amt = 0;

        for (var i = 0; i < this.items.length; i++) {
            var item = this.items[i];

            if (item.type === itemTypeId) {
                amt++;
            }
        }

        return amt;
    },

    removeItems: function (itemTypeId, amount) {
        var toDelete = [];

        for (var i = 0; i < this.items.length; i++) {
            var item = this.items[i];

            if (item.type === itemTypeId) {
                toDelete.push(item);
                amount--;

                if (amount <= 0) {
                    break;
                }
            }
        }

        for (var j = 0; j < toDelete.length; j++) {
            var deleteItem = toDelete[i];
            var idx = this.items.indexOf(deleteItem);

            if (idx >= 0) {
                this.items.splice(idx, 1);
            }
        }

        this.syncUi();
    },

    syncUi: function () {
        $ui = $('#inventory');
        $ui.html('');

        var itemsCreated = [];

        for (var i = 0; i < this.items.length; i++) {
            var item = this.items[i];

            var $elem = $ui.find('.item.' + item.type);

            if ($elem.length == 0) {
                $elem = $('<div />')
                    .addClass('item')
                    .addClass(item.type)
                    .appendTo($ui);

                var $img = $('<img />')
                    .attr('src', 'assets/images/items/' + item.getIcon())
                    .appendTo($elem);

                var $count = $('<div />')
                    .addClass('count')
                    .attr('data-count', 1)
                    .text('x1')
                    .appendTo($elem);
            } else {
                var $count = $elem.find('.count');
                var curAmount = parseInt($count.attr('data-count'));

                curAmount++;

                $count.attr('data-count', curAmount);
                $count.text('x' + curAmount)
            }
        }
    }

    ,
};<commit_msg>[Content] Inventory: Spawn the user with a match box.... because I have no better solution right now<commit_after>var Inventory = {
    items: [],

    clear: function () {
        this.items = [];

        if (Settings.spawnWithItems) {
            this.createAndAdd(ItemTypes.FIRE_WOOD, 25);
            this.createAndAdd(ItemTypes.MATCH_BOX, 1);
            this.createAndAdd(ItemTypes.AXE, 1);
        } else {
            // Give the user a match box for now...
            this.createAndAdd(ItemTypes.MATCH_BOX, 1);
        }

        this.syncUi();
    },

    add: function (item) {
        this.items.push(item);
        this.syncUi();
    },

    create: function (itemTypeId) {
        if (typeof window.Items[itemTypeId] === 'undefined') {
            return false;
        }

        var itemClass = window.Items[itemTypeId];
        var itemObj = new itemClass();
        return itemObj;
    },

    createAndAdd: function (itemTypeId, amount) {
        for (var i = 0; i < amount; i++) {
            var item = this.create(itemTypeId);
            this.items.push(item);
        }

        this.syncUi();
    },

    getItemQty: function (itemTypeId) {
        var amt = 0;

        for (var i = 0; i < this.items.length; i++) {
            var item = this.items[i];

            if (item.type === itemTypeId) {
                amt++;
            }
        }

        return amt;
    },

    removeItems: function (itemTypeId, amount) {
        var toDelete = [];

        for (var i = 0; i < this.items.length; i++) {
            var item = this.items[i];

            if (item.type === itemTypeId) {
                toDelete.push(item);
                amount--;

                if (amount <= 0) {
                    break;
                }
            }
        }

        for (var j = 0; j < toDelete.length; j++) {
            var deleteItem = toDelete[i];
            var idx = this.items.indexOf(deleteItem);

            if (idx >= 0) {
                this.items.splice(idx, 1);
            }
        }

        this.syncUi();
    },

    syncUi: function () {
        $ui = $('#inventory');
        $ui.html('');

        var itemsCreated = [];

        for (var i = 0; i < this.items.length; i++) {
            var item = this.items[i];

            var $elem = $ui.find('.item.' + item.type);

            if ($elem.length == 0) {
                $elem = $('<div />')
                    .addClass('item')
                    .addClass(item.type)
                    .appendTo($ui);

                var $img = $('<img />')
                    .attr('src', 'assets/images/items/' + item.getIcon())
                    .appendTo($elem);

                var $count = $('<div />')
                    .addClass('count')
                    .attr('data-count', 1)
                    .text('x1')
                    .appendTo($elem);
            } else {
                var $count = $elem.find('.count');
                var curAmount = parseInt($count.attr('data-count'));

                curAmount++;

                $count.attr('data-count', curAmount);
                $count.text('x' + curAmount)
            }
        }
    }

    ,
};<|endoftext|>


<commit_before>// Library imports
var util = require('util');
var http = require('http');
var raven = require('raven');
var sharejs = require('share');
var livedb = require('livedb');
var Duplex = require('stream').Duplex;
var WebSocketServer = require('ws').Server;
var express = require('express');
var morgan = require('morgan');
var async = require('async');

var settings = {
    debug: process.env.SHAREJS_DEBUG ? process.env.SHAREJS_DEBUG === 'true' : true,
    // Server Options
    host: process.env.SHAREJS_SERVER_HOST || 'localhost',
    port: process.env.SHAREJS_SERVER_PORT || 7007,
    corsAllowOrigin: process.env.SHAREJS_CORS_ALLOW_ORIGIN || 'http://localhost:5000',
    // Mongo options
    dbHost: process.env.SHAREJS_DB_HOST || 'localhost',
    dbPort: process.env.SHAREJS_DB_PORT || 27017,
    dbName: process.env.SHAREJS_DB_NAME || 'sharejs',
    // Raven client
    sentryDSN: process.env.SHAREJS_SENTRY_DSN
};

debugger;
var client = new raven.Client(settings.sentryDSN);

if (!settings.debug) {
    client.patchGlobal(function() {
        // It is highly discouraged to leave the process running after a
        // global uncaught exception has occurred.
        //
        // https://github.com/getsentry/raven-node#catching-global-errors
        // http://nodejs.org/api/process.html#process_event_uncaughtexception
        //
        console.log('Uncaught Exception process exiting');
        process.exit(1);
    });
}

// Server setup
var mongo = require('livedb-mongo')(
    util.format('mongodb://%s:%s/%s', settings.dbHost, settings.dbPort, settings.dbName),
    {safe:true}
);
var backend = livedb.client(mongo);
var share = sharejs.server.createClient({backend: backend});
var app = express();
var server = http.createServer(app);
var wss = new WebSocketServer({server: server});

// Local variables
var docs = {};  // TODO: Should this be stored in mongo?
var locked = {};

// Allow X-Forwarded-For headers
app.set('trust proxy');

// Raven Express Middleware
app.use(raven.middleware.express(settings.sentryDSN));
app.use(morgan('common'));

// Allow CORS
app.use(function(req, res, next) {
    res.header('Access-Control-Allow-Origin', settings.corsAllowOrigin);
    res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept');
    next();
});

// Serve static sharejs files
app.use(express.static(sharejs.scriptsDir));

// Broadcasts message to all clients connected to that doc
// TODO: Can we access the relevant list without iterating over every client?
wss.broadcast = function(docId, message) {
    async.each(this.clients, function (client, cb) {
        if (client.userMeta && client.userMeta.docId === docId) {
            try {
                client.send(message);
            } catch (e) {
                // ignore errors - connection should be handled by share.js library
            }
        }

        cb();
    });
};

wss.on('connection', function(client) {
    var stream = new Duplex({objectMode: true});

    stream._read = function() {};
    stream._write = function(chunk, encoding, callback) {
        if (client.state !== 'closed') {
            try {
                client.send(JSON.stringify(chunk));
            } catch (e) {
                // ignore errors - connection should be handled by share.js library
            }
        }
        callback();
    };

    stream.headers = client.upgradeReq.headers;
    stream.remoteAddress = client.upgradeReq.connection.remoteAddress;

    client.on('message', function(data) {
        if (client.userMeta && locked[client.userMeta.docId]) {
            wss.broadcast(client.userMeta.docId, JSON.stringify({type: 'lock'}));
            return;
        }

        try {
            data = JSON.parse(data);
        } catch (e) {
            client.captureMessage('Could not parse message data as json', {message: message});
            return;
        }

        // Handle our custom messages separately
        if (data.registration) {
            console.info('[User Registered] docId: %s, userId: %s', data.docId, data.userId);
            var docId = data.docId;
            var userId = data.userId;

            // Create a metadata entry for this document
            if (!docs[docId]) {
                docs[docId] = {};
            }

            // Add user to metadata
            if (!docs[docId][userId]) {
                docs[docId][userId] = {
                    name: data.userName,
                    url: data.userUrl,
                    count: 1,
                    gravatar: data.userGravatar
                };
            } else {
                docs[docId][userId].count++;
            }

            // Attach metadata to the client object
            client.userMeta = data;
            wss.broadcast(docId, JSON.stringify({type: 'meta', users: docs[docId]}));

            // Lock client if doc is locked
            if (locked[docId]) {
                try {
                    client.send(JSON.stringify({type: 'lock'}));
                } catch (e) {
                    // ignore errors - connection should be handled by share.js library
                }
            }
        } else {
            stream.push(data);
        }
    });

    client.on('close', function(reason) {
        if (client.userMeta) {
            console.info('[Connection Closed] docId: %s, userId: %s, reason: %s', client.userMeta.docId, client.userMeta.userId, reason);
        } else {
            console.info('[Connection Closed] reason: %s', reason);
        }

        if (client.userMeta) {
            var docId = client.userMeta.docId;
            var userId = client.userMeta.userId;

            if (docs[docId] && docs[docId][userId]) {
                docs[docId][userId].count--;
                if (docs[docId][userId].count === 0) {
                    delete docs[docId][userId];

                    if (!Object.keys(docs[docId]).length) {
                        delete docs[docId];
                    }
                }
            }

            wss.broadcast(docId, JSON.stringify({type: 'meta', users: docs[docId]}));
        }

        stream.push(null);
        stream.emit('close');
    });

    stream.on('error', function(msg) {
        client.captureMessage('Could not parse message data as json', {msg: msg});
        client.close(msg);
    });

    stream.on('end', function() {
        client.close();
    });

    // Give the stream to sharejs
    return share.listen(stream);
});

// Lock a document
app.post('/lock/:id', function (req, res, next) {
    locked[req.params.id] = true;
    wss.broadcast(req.params.id, JSON.stringify({type: 'lock'}));
    console.info('[Document Locked] docId: %s', req.params.id);
    res.send(util.format('%s was locked.', req.params.id));
});

// Unlock a document
app.post('/unlock/:id', function (req, res, next) {
    delete locked[req.params.id];
    wss.broadcast(req.params.id, JSON.stringify({type: 'unlock'}));
    console.info('[Document Unlocked] docId: %s', req.params.id);
    res.send(util.format('%s was unlocked.', req.params.id));
});

// Redirect from a document
app.post('/redirect/:id/:redirect', function (req, res, next) {
    wss.broadcast(req.params.id, JSON.stringify({
        type: 'redirect',
        redirect: req.params.redirect
    }));
    console.info('[Document Redirect] docId: %s, redirect: %s', req.params.id, req.params.redirect);
    res.send(util.format('%s was redirected to %s', req.params.id, req.params.redirect));
});

// Redirect from a deleted document
app.post('/delete/:id/:redirect', function (req, res, next) {
    wss.broadcast(req.params.id, JSON.stringify({
        type: 'delete',
        redirect: req.params.redirect
    }));
    console.info('[Document Delete] docId: %s, redirect: %s', req.params.id, req.params.redirect);
    res.send(util.format('%s was deleted and redirected to %s', req.params.id, req.params.redirect));
});

server.listen(settings.port, settings.host, function() {
    console.log('Server running at http://%s:%s', settings.host, settings.port);
});<commit_msg>no debug<commit_after>// Library imports
var util = require('util');
var http = require('http');
var raven = require('raven');
var sharejs = require('share');
var livedb = require('livedb');
var Duplex = require('stream').Duplex;
var WebSocketServer = require('ws').Server;
var express = require('express');
var morgan = require('morgan');
var async = require('async');

var settings = {
    debug: process.env.SHAREJS_DEBUG ? process.env.SHAREJS_DEBUG === 'true' : true,
    // Server Options
    host: process.env.SHAREJS_SERVER_HOST || 'localhost',
    port: process.env.SHAREJS_SERVER_PORT || 7007,
    corsAllowOrigin: process.env.SHAREJS_CORS_ALLOW_ORIGIN || 'http://localhost:5000',
    // Mongo options
    dbHost: process.env.SHAREJS_DB_HOST || 'localhost',
    dbPort: process.env.SHAREJS_DB_PORT || 27017,
    dbName: process.env.SHAREJS_DB_NAME || 'sharejs',
    // Raven client
    sentryDSN: process.env.SHAREJS_SENTRY_DSN
};

var client = new raven.Client(settings.sentryDSN);

if (!settings.debug) {
    client.patchGlobal(function() {
        // It is highly discouraged to leave the process running after a
        // global uncaught exception has occurred.
        //
        // https://github.com/getsentry/raven-node#catching-global-errors
        // http://nodejs.org/api/process.html#process_event_uncaughtexception
        //
        console.log('Uncaught Exception process exiting');
        process.exit(1);
    });
}

// Server setup
var mongo = require('livedb-mongo')(
    util.format('mongodb://%s:%s/%s', settings.dbHost, settings.dbPort, settings.dbName),
    {safe:true}
);
var backend = livedb.client(mongo);
var share = sharejs.server.createClient({backend: backend});
var app = express();
var server = http.createServer(app);
var wss = new WebSocketServer({server: server});

// Local variables
var docs = {};  // TODO: Should this be stored in mongo?
var locked = {};

// Allow X-Forwarded-For headers
app.set('trust proxy');

// Raven Express Middleware
app.use(raven.middleware.express(settings.sentryDSN));
app.use(morgan('common'));

// Allow CORS
app.use(function(req, res, next) {
    res.header('Access-Control-Allow-Origin', settings.corsAllowOrigin);
    res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept');
    next();
});

// Serve static sharejs files
app.use(express.static(sharejs.scriptsDir));

// Broadcasts message to all clients connected to that doc
// TODO: Can we access the relevant list without iterating over every client?
wss.broadcast = function(docId, message) {
    async.each(this.clients, function (client, cb) {
        if (client.userMeta && client.userMeta.docId === docId) {
            try {
                client.send(message);
            } catch (e) {
                // ignore errors - connection should be handled by share.js library
            }
        }

        cb();
    });
};

wss.on('connection', function(client) {
    var stream = new Duplex({objectMode: true});

    stream._read = function() {};
    stream._write = function(chunk, encoding, callback) {
        if (client.state !== 'closed') {
            try {
                client.send(JSON.stringify(chunk));
            } catch (e) {
                // ignore errors - connection should be handled by share.js library
            }
        }
        callback();
    };

    stream.headers = client.upgradeReq.headers;
    stream.remoteAddress = client.upgradeReq.connection.remoteAddress;

    client.on('message', function(data) {
        if (client.userMeta && locked[client.userMeta.docId]) {
            wss.broadcast(client.userMeta.docId, JSON.stringify({type: 'lock'}));
            return;
        }

        try {
            data = JSON.parse(data);
        } catch (e) {
            client.captureMessage('Could not parse message data as json', {message: message});
            return;
        }

        // Handle our custom messages separately
        if (data.registration) {
            console.info('[User Registered] docId: %s, userId: %s', data.docId, data.userId);
            var docId = data.docId;
            var userId = data.userId;

            // Create a metadata entry for this document
            if (!docs[docId]) {
                docs[docId] = {};
            }

            // Add user to metadata
            if (!docs[docId][userId]) {
                docs[docId][userId] = {
                    name: data.userName,
                    url: data.userUrl,
                    count: 1,
                    gravatar: data.userGravatar
                };
            } else {
                docs[docId][userId].count++;
            }

            // Attach metadata to the client object
            client.userMeta = data;
            wss.broadcast(docId, JSON.stringify({type: 'meta', users: docs[docId]}));

            // Lock client if doc is locked
            if (locked[docId]) {
                try {
                    client.send(JSON.stringify({type: 'lock'}));
                } catch (e) {
                    // ignore errors - connection should be handled by share.js library
                }
            }
        } else {
            stream.push(data);
        }
    });

    client.on('close', function(reason) {
        if (client.userMeta) {
            console.info('[Connection Closed] docId: %s, userId: %s, reason: %s', client.userMeta.docId, client.userMeta.userId, reason);
        } else {
            console.info('[Connection Closed] reason: %s', reason);
        }

        if (client.userMeta) {
            var docId = client.userMeta.docId;
            var userId = client.userMeta.userId;

            if (docs[docId] && docs[docId][userId]) {
                docs[docId][userId].count--;
                if (docs[docId][userId].count === 0) {
                    delete docs[docId][userId];

                    if (!Object.keys(docs[docId]).length) {
                        delete docs[docId];
                    }
                }
            }

            wss.broadcast(docId, JSON.stringify({type: 'meta', users: docs[docId]}));
        }

        stream.push(null);
        stream.emit('close');
    });

    stream.on('error', function(msg) {
        client.captureMessage('Could not parse message data as json', {msg: msg});
        client.close(msg);
    });

    stream.on('end', function() {
        client.close();
    });

    // Give the stream to sharejs
    return share.listen(stream);
});

// Lock a document
app.post('/lock/:id', function (req, res, next) {
    locked[req.params.id] = true;
    wss.broadcast(req.params.id, JSON.stringify({type: 'lock'}));
    console.info('[Document Locked] docId: %s', req.params.id);
    res.send(util.format('%s was locked.', req.params.id));
});

// Unlock a document
app.post('/unlock/:id', function (req, res, next) {
    delete locked[req.params.id];
    wss.broadcast(req.params.id, JSON.stringify({type: 'unlock'}));
    console.info('[Document Unlocked] docId: %s', req.params.id);
    res.send(util.format('%s was unlocked.', req.params.id));
});

// Redirect from a document
app.post('/redirect/:id/:redirect', function (req, res, next) {
    wss.broadcast(req.params.id, JSON.stringify({
        type: 'redirect',
        redirect: req.params.redirect
    }));
    console.info('[Document Redirect] docId: %s, redirect: %s', req.params.id, req.params.redirect);
    res.send(util.format('%s was redirected to %s', req.params.id, req.params.redirect));
});

// Redirect from a deleted document
app.post('/delete/:id/:redirect', function (req, res, next) {
    wss.broadcast(req.params.id, JSON.stringify({
        type: 'delete',
        redirect: req.params.redirect
    }));
    console.info('[Document Delete] docId: %s, redirect: %s', req.params.id, req.params.redirect);
    res.send(util.format('%s was deleted and redirected to %s', req.params.id, req.params.redirect));
});

server.listen(settings.port, settings.host, function() {
    console.log('Server running at http://%s:%s', settings.host, settings.port);
});<|endoftext|>


<commit_before>'use strict';

var ko = require('knockout');
require('knockout-punches');
var $ = require('jquery');
var $osf = require('osfHelpers');
var bootbox = require('bootbox');
var waterbutler = require('waterbutler');

ko.punches.enableAll();

var urlParams = $osf.urlParams();

var Revision = function(data, index, file, node) {
    var self = this;
    var options = {};

    $.extend(self, data);

    if (urlParams.branch !== undefined) {
        options.branch = urlParams.branch;
    }
    options[self.versionIdentifier] = self.version;
    // Note: Google Drive version identifiers often begin with the same sequence
    self.displayVersion = file.provider === 'googledrive' ?
        self.version.substring(self.version.length - 8) :
        self.version.substring(0, 8);

    self.date = new $osf.FormattableDate(data.modified);
    self.displayDate = self.date.local !== 'Invalid date' ?
        self.date.local :
        data.date;

    // Append modification time to file name if OSF Storage and not current version
    if (file.provider === 'osfstorage' && file.name && index !== 0) {
        var parts = file.name.split('.');
        if (parts.length === 1) {
            options.displayName = parts[0] + '-' + data.modified;
        } else {
            options.displayName = parts.slice(0, parts.length - 1).join('') + '-' + data.modified + '.' + parts[parts.length - 1];
        }
    }

    self.osfViewUrl = '?' + $.param(options);
    self.osfDownloadUrl = '?' + $.param($.extend({action: 'download'}, options));
    self.waterbutlerDownloadUrl = waterbutler.buildDownloadUrl(file.path, file.provider, node.id, options);

    self.download = function() {
        window.location = self.waterbutlerDownloadUrl;
        return false;
    };
};

var RevisionsViewModel = function(node, file, editable) {
    var self = this;
    var fileExtra = file.extra || {};
    var revisionsOptions = {};

    if (urlParams.branch !== undefined) {
        fileExtra.branch = urlParams.branch;
        revisionsOptions.sha = urlParams.branch;
    }

    self.node = node;
    self.file = file;
    self.path = file.provider !== 'googledrive' ?
        file.path.split('/') :
        file.path.split('/').map(decodeURIComponent);

    self.editable = ko.observable(editable);
    self.urls = {
        delete: waterbutler.buildDeleteUrl(file.path, file.provider, node.id, fileExtra),
        download: waterbutler.buildDownloadUrl(file.path, file.provider, node.id, fileExtra),
        metadata: waterbutler.buildMetadataUrl(file.path, file.provider, node.id, revisionsOptions),
        revisions: waterbutler.buildRevisionsUrl(file.path, file.provider, node.id, revisionsOptions)
    };

    // This is only because of for Google Drive
    if((self.file.path.split('/').length) > 2)
        self.path = '/' + self.file.path.split('/')[(self.file.path.split('/').length) -1]

    self.errorMessage = ko.observable('');
    self.currentVersion = ko.observable({});
    self.revisions = ko.observableArray([]);
    self.versioningSupported = ko.observable(true);

    self.userColumn = ko.computed(function() {
        return self.revisions()[0] &&
            self.revisions()[0].extra &&
            self.revisions()[0].extra.user;
    });
};

RevisionsViewModel.prototype.fetch = function() {
    var self = this;
    var request = $.getJSON(self.urls.revisions);

    request.done(function(response) {
        self.revisions(ko.utils.arrayMap(response.data, function(item, index) {
            if ($osf.urlParams()[item.versionIdentifier] === item.version) {
                self.currentVersion(new Revision(item, index, self.file, self.node));
                return self.currentVersion();
            }
            return new Revision(item, index, self.file, self.node);
        }));

        if (Object.keys(self.currentVersion()).length === 0) {
            self.currentVersion(self.revisions()[0]);
        }
    });

    request.fail(function(response) {
        self.versioningSupported(false);
        var err = response.responseJSON ?
            response.responseJSON.message || 'Unable to fetch versions' :
            'Unable to fetch versions';

        self.errorMessage(err);

        if (self.file.provider === 'figshare') {
            // Hack for Figshare
            // only figshare will error on a revisions request
            // so dont allow downloads and set a fake current version
            $.ajax({
                method: 'GET',
                url: self.urls.metadata,
            }).done(function(data) {
                if (data.data.extra.status === 'drafts') {
                    self.editable(true);
                } else {
                    self.editable(false);
                }
            }).fail(function(xhr) {
                self.editable(false);
            });
        }

        self.currentVersion({
            osfViewUrl: '',
            osfDownloadUrl: '?action=download',
            download: function() {
                window.location = self.urls.download + '&' + $.param({displayName: self.file.name});
                return false;
            }
        });
    });
};

RevisionsViewModel.prototype.delete = function() {
    var self = this;
    $.ajax({
        type: 'DELETE',
        url: self.urls.delete,
    }).done(function() {
        window.location = self.node.urls.files;
    }).fail(function() {
        $osf.growl('Error', 'Could not delete file.');
    });
};

RevisionsViewModel.prototype.askDelete = function() {
    var self = this;
    bootbox.confirm({
        title: 'Delete file?',
        message: '
' +
                'Are you sure you want to delete ' +
                self.file.safeName + '?' +
            '
',
        callback: function(confirm) {
            if (confirm) {
                self.delete();
            }
        }
    });
};

RevisionsViewModel.prototype.isActive = function(version) {
    var self = this;

    if (self.currentVersion() === version) {
        return 'info';
    }
    return;
};

var RevisionTable = function(selector, node, file, editable) {

    var self = this;

    self.viewModel = new RevisionsViewModel(node, file, editable);
    self.viewModel.fetch();
    $osf.applyBindings(self.viewModel, selector);

};

module.exports = RevisionTable;
<commit_msg>Fix f / i / l / e / n / a / m / e bug<commit_after>'use strict';

var ko = require('knockout');
require('knockout-punches');
var $ = require('jquery');
var $osf = require('osfHelpers');
var bootbox = require('bootbox');
var waterbutler = require('waterbutler');

ko.punches.enableAll();

var urlParams = $osf.urlParams();

var Revision = function(data, index, file, node) {
    var self = this;
    var options = {};

    $.extend(self, data);

    if (urlParams.branch !== undefined) {
        options.branch = urlParams.branch;
    }
    options[self.versionIdentifier] = self.version;
    // Note: Google Drive version identifiers often begin with the same sequence
    self.displayVersion = file.provider === 'googledrive' ?
        self.version.substring(self.version.length - 8) :
        self.version.substring(0, 8);

    self.date = new $osf.FormattableDate(data.modified);
    self.displayDate = self.date.local !== 'Invalid date' ?
        self.date.local :
        data.date;

    // Append modification time to file name if OSF Storage and not current version
    if (file.provider === 'osfstorage' && file.name && index !== 0) {
        var parts = file.name.split('.');
        if (parts.length === 1) {
            options.displayName = parts[0] + '-' + data.modified;
        } else {
            options.displayName = parts.slice(0, parts.length - 1).join('') + '-' + data.modified + '.' + parts[parts.length - 1];
        }
    }

    self.osfViewUrl = '?' + $.param(options);
    self.osfDownloadUrl = '?' + $.param($.extend({action: 'download'}, options));
    self.waterbutlerDownloadUrl = waterbutler.buildDownloadUrl(file.path, file.provider, node.id, options);

    self.download = function() {
        window.location = self.waterbutlerDownloadUrl;
        return false;
    };
};

var RevisionsViewModel = function(node, file, editable) {
    var self = this;
    var fileExtra = file.extra || {};
    var revisionsOptions = {};

    if (urlParams.branch !== undefined) {
        fileExtra.branch = urlParams.branch;
        revisionsOptions.sha = urlParams.branch;
    }

    self.node = node;
    self.file = file;
    self.path = file.provider !== 'googledrive' ?
        file.path.split('/') :
        file.path.split('/').map(decodeURIComponent);

    self.editable = ko.observable(editable);
    self.urls = {
        delete: waterbutler.buildDeleteUrl(file.path, file.provider, node.id, fileExtra),
        download: waterbutler.buildDownloadUrl(file.path, file.provider, node.id, fileExtra),
        metadata: waterbutler.buildMetadataUrl(file.path, file.provider, node.id, revisionsOptions),
        revisions: waterbutler.buildRevisionsUrl(file.path, file.provider, node.id, revisionsOptions)
    };

    self.errorMessage = ko.observable('');
    self.currentVersion = ko.observable({});
    self.revisions = ko.observableArray([]);
    self.versioningSupported = ko.observable(true);

    self.userColumn = ko.computed(function() {
        return self.revisions()[0] &&
            self.revisions()[0].extra &&
            self.revisions()[0].extra.user;
    });
};

RevisionsViewModel.prototype.fetch = function() {
    var self = this;
    var request = $.getJSON(self.urls.revisions);

    request.done(function(response) {
        self.revisions(ko.utils.arrayMap(response.data, function(item, index) {
            if ($osf.urlParams()[item.versionIdentifier] === item.version) {
                self.currentVersion(new Revision(item, index, self.file, self.node));
                return self.currentVersion();
            }
            return new Revision(item, index, self.file, self.node);
        }));

        if (Object.keys(self.currentVersion()).length === 0) {
            self.currentVersion(self.revisions()[0]);
        }
    });

    request.fail(function(response) {
        self.versioningSupported(false);
        var err = response.responseJSON ?
            response.responseJSON.message || 'Unable to fetch versions' :
            'Unable to fetch versions';

        self.errorMessage(err);

        if (self.file.provider === 'figshare') {
            // Hack for Figshare
            // only figshare will error on a revisions request
            // so dont allow downloads and set a fake current version
            $.ajax({
                method: 'GET',
                url: self.urls.metadata,
            }).done(function(data) {
                if (data.data.extra.status === 'drafts') {
                    self.editable(true);
                } else {
                    self.editable(false);
                }
            }).fail(function(xhr) {
                self.editable(false);
            });
        }

        self.currentVersion({
            osfViewUrl: '',
            osfDownloadUrl: '?action=download',
            download: function() {
                window.location = self.urls.download + '&' + $.param({displayName: self.file.name});
                return false;
            }
        });
    });
};

RevisionsViewModel.prototype.delete = function() {
    var self = this;
    $.ajax({
        type: 'DELETE',
        url: self.urls.delete,
    }).done(function() {
        window.location = self.node.urls.files;
    }).fail(function() {
        $osf.growl('Error', 'Could not delete file.');
    });
};

RevisionsViewModel.prototype.askDelete = function() {
    var self = this;
    bootbox.confirm({
        title: 'Delete file?',
        message: '
' +
                'Are you sure you want to delete ' +
                self.file.safeName + '?' +
            '
',
        callback: function(confirm) {
            if (confirm) {
                self.delete();
            }
        }
    });
};

RevisionsViewModel.prototype.isActive = function(version) {
    var self = this;

    if (self.currentVersion() === version) {
        return 'info';
    }
    return;
};

var RevisionTable = function(selector, node, file, editable) {

    var self = this;

    self.viewModel = new RevisionsViewModel(node, file, editable);
    self.viewModel.fetch();
    $osf.applyBindings(self.viewModel, selector);

};

module.exports = RevisionTable;
<|endoftext|>


<commit_before>var m = require('mithril');

function Panel(title, inner, args, selected) {
    panel = m.component(Panel, title, inner, args);
    panel.title = title;
    panel.selected = selected || false;
    return panel;
}

Panel.controller = function(title, inner, args) {
    var self = this;

    self.title = title;
    self.inner = m.component.apply(this, [inner].concat(args || []));
};

Panel.view = function(ctrl) {
    return m('.osf-panel', [
        m('.osf-panel-header', ctrl.title),
        m('.osf-panel-body', ctrl.inner)
    ]);
};

var PanelToggler = {
    controller: function(panels) {
        var self = this;
        self.panels = panels;

    },
    view: function(ctrl) {
        var shown = ctrl.panels.reduce(function(accu, panel) {
            return accu + (panel.selected ? 1 : 0);
        }, 0);

        return m('.panel-toggler', [
            m('.row', [
                m('.col-md-6'),
                m('.col-md-6', [
                    m('.pull-right', [
                        m('.btn-group.btn-group-sm', [m('.btn.btn-default.disabled', 'Toggle View: ')].concat(
                            ctrl.panels.map(function(panel) {
                                return m('.btn' + (panel.selected ? '.btn-primary' : '.btn-default'), {
                                    onclick: function(e) {
                                        e.preventDefault();
                                        panel.selected = !panel.selected;
                                    }
                                }, panel.title);
                            })
                        ))
                    ])
                ])
            ]),
            m('br'),
            m('.row', ctrl.panels.map(function(panel, index, iter) {
                if (!panel.selected) return m('[style="display:none"]', panel);
                return m('.col-md-' + Math.floor(12/shown), panel);
            }))
        ]);
    }
};

var Spinner = m('i.fa.fa-spinner.fa-pulse');

module.exports = {
    Panel: Panel,
    Spinner: Spinner,
    PanelToggler: PanelToggler
};
<commit_msg>Things break if Spinner is not a component...<commit_after>var m = require('mithril');

function Panel(title, inner, args, selected) {
    panel = m.component(Panel, title, inner, args);
    panel.title = title;
    panel.selected = selected || false;
    return panel;
}

Panel.controller = function(title, inner, args) {
    var self = this;

    self.title = title;
    self.inner = m.component.apply(this, [inner].concat(args || []));
};

Panel.view = function(ctrl) {
    return m('.osf-panel', [
        m('.osf-panel-header', ctrl.title),
        m('.osf-panel-body', ctrl.inner)
    ]);
};

var PanelToggler = {
    controller: function(panels) {
        var self = this;
        self.panels = panels;

    },
    view: function(ctrl) {
        var shown = ctrl.panels.reduce(function(accu, panel) {
            return accu + (panel.selected ? 1 : 0);
        }, 0);

        return m('.panel-toggler', [
            m('.row', [
                m('.col-md-6'),
                m('.col-md-6', [
                    m('.pull-right', [
                        m('.btn-group.btn-group-sm', [m('.btn.btn-default.disabled', 'Toggle View: ')].concat(
                            ctrl.panels.map(function(panel) {
                                return m('.btn' + (panel.selected ? '.btn-primary' : '.btn-default'), {
                                    onclick: function(e) {
                                        e.preventDefault();
                                        panel.selected = !panel.selected;
                                    }
                                }, panel.title);
                            })
                        ))
                    ])
                ])
            ]),
            m('br'),
            m('.row', ctrl.panels.map(function(panel, index, iter) {
                if (!panel.selected) return m('[style="display:none"]', panel);
                return m('.col-md-' + Math.floor(12/shown), panel);
            }))
        ]);
    }
};

var Spinner = m.component({
    controller: function(){},
    view: function() {
        return m('i.fa.fa-spinner.fa-pulse');
    }
});

module.exports = {
    Panel: Panel,
    Spinner: Spinner,
    PanelToggler: PanelToggler
};
<|endoftext|>


<commit_before>/*
  All Emoncms code is released under the GNU Affero General Public License.
  See COPYRIGHT.txt and LICENSE.txt.
    ---------------------------------------------------------------------
    Part of the OpenEnergyMonitor project:
    http://openenergymonitor.org

    Author: Trystan Lea: trystan.lea@googlemail.com
	Enhancements done by: Andreas Messerli firefox7518@gmail.com
    If you have any questions please get in touch, try the forums here:
    http://openenergymonitor.org/emon/forum
 */

 function addOption(widget, optionKey, optionType, optionName, optionHint, optionData)
{
  widget["options"    ].push(optionKey);
  widget["optionstype"].push(optionType);
  widget["optionsname"].push(optionName);
  widget["optionshint"].push(optionHint);
  widget["optionsdata"].push(optionData);
}

function feedtime_widgetlist()
{
  var widgets =
  {
    "feedtime":
    {
      "offsetx":-40,"offsety":-30,"width":120,"height":60,
      "menu":"Widgets",
      "options":    [],
      "optionstype":[],
      "optionsname":[],
      "optionshint":[],
      "optionsdata":[]
    }
  };

  var decimalsDropBoxOptions = [        // Options for the type combobox. Each item is [typeID, "description"]
        [-1,   _Tr("Automatic")],
        [0,    "0"],
        [1,    "1"],
        [2,    "2"],
        [3,    "3"],
        [4,    "4"],
        [5,    "5"],
        [6,    "6"]
    ];

	var fontoptions = [
					[9, "Arial Black"],
					[8, "Arial Narrow"],
					[7, "sans-serif"],
					[6, "Helvetica Neue"],
					[5, "Helvetica"],
					[4, "Comic Sans MS"],
					[3, "Courier New"],
					[2, "Arial"],
					[1, "Georgia"],
					[0, "Impact"]
				];

	var fstyleoptions = [
					[2, _Tr("Normal")],
					[1, _Tr("Italic")],
					[0, _Tr("Oblique")]
				];

	var fweightoptions = [
					[1, _Tr("Bold")],
					[0, _Tr("Normal")]
				];

	var sizeoptions = [
					[14, "18"], // set size 18 to the top position to be the default value for creating new feedtime widgets otherwise size 40 would be always the default
					[13, "40"],
					[12, "36"],
					[11, "32"],
					[10, "28"],
					[9, "24"],
					[8, "22"],
					[7, "20"],
					[6, "18"],
					[5, "16"],
					[4, "14"],
					[3, "12"],
					[2, "10"],
					[1, "8"],
					[0, "6"]
				];

	var unitEndOptions = [
					[0, _Tr("Back")],
					[1, _Tr("Front")]
				];

	addOption(widgets["feedtime"], "feedid",  "feedid",        _Tr("Feed"),          _Tr("Feed value"),                     []);
	addOption(widgets["feedtime"], "colour",  "colour_picker", _Tr("Colour"),        _Tr("Colour used for display"),        []);
	addOption(widgets["feedtime"], "font",    "dropbox",       _Tr("Font"),          _Tr("Font used for display"),          fontoptions);
	addOption(widgets["feedtime"], "fstyle",  "dropbox",       _Tr("Font style"),    _Tr("Font style used for display"),    fstyleoptions);
	addOption(widgets["feedtime"], "fweight", "dropbox",       _Tr("Font weight"),   _Tr("Font weight used for display"),   fweightoptions);
	addOption(widgets["feedtime"], "units",   "dropbox_other", _Tr("Units"),         _Tr("Units to show"),                  _SI);
	addOption(widgets["feedtime"], "size",    "dropbox",       _Tr("Size"),          _Tr("Text size in px to use"),         sizeoptions);
	addOption(widgets["feedtime"], "unitend", "dropbox",       _Tr("Unit position"), _Tr("Where should the unit be shown"), unitEndOptions);

	return widgets;
}

function draw_feedtime(context,
		x_pos,				// these x and y coords seem unused?
		y_pos,
		font,
		fstyle,
		fweight,
		width,
		height,
		val,
		units,
		colour,
		size,
		unitend)
		{
			if (!context){
			return;
			}

			context.save();
			context.clearRect(0,0,width,height); // Clear old drawing
			context.restore();
			colour = colour || "4444CC";
			unitend = unitend || "0";
			size = size || "8";
			font = font || "5";
			fstyle = fstyle || "2";
			fweight = fweight || "1";

			var fontsize;

			if (size === "0"){fontsize = 6;}
			if (size === "1"){fontsize = 8;}
			if (size === "2"){fontsize = 10;}
			if (size === "3"){fontsize = 12;}
			if (size === "4"){fontsize = 14;}
			if (size === "5"){fontsize = 16;}
			if (size === "6"){fontsize = 18;}
			if (size === "7"){fontsize = 20;}
			if (size === "8"){fontsize = 22;}
			if (size === "9"){fontsize = 24;}
			if (size === "10"){fontsize = 28;}
			if (size === "11"){fontsize = 32;}
			if (size === "12"){fontsize = 36;}
			if (size === "13"){fontsize = 40;}
			if (size === "14"){fontsize = 18;}  //default value so that not size 40 is always the default

			var fontname;

			if (font === "0"){fontname = "Impact";}
			if (font === "1"){fontname = "Georgia";}
			if (font === "2"){fontname = "Arial";}
			if (font === "3"){fontname = "Courier New";}
			if (font === "4"){fontname = "Comic Sans MS";}
			if (font === "5"){fontname = "Helvetica";}
			if (font === "6"){fontname = "Helvetica Neue";}
			if (font === "7"){fontname = "sans-serif";}
			if (font === "8"){fontname = "Arial Narrow";}
			if (font === "9"){fontname = "Arial Black";}

			var fontstyle;

			if (fstyle === "0"){fontstyle = "oblique";}
			if (fstyle === "1"){fontstyle = "italic";}
			if (fstyle === "2"){fontstyle = "normal";}

			var fontweight;

			if (fweight === "0"){fontweight = "normal";}
			if (fweight === "1"){fontweight = "bold";}

			val = val.toFixed(0);

			if (colour.indexOf("#") === -1){			// Fix missing "#" on colour if needed
				colour = "#" + colour;

				context.fillStyle = colour;
				context.textAlign    = "center";
				context.textBaseline = "middle";
				context.font = (fontstyle+ " "+ fontweight+ " "+ fontsize+"px "+ fontname);
				}

			if (unitend ==="0")
			{
			context.fillText(val+units, width/2 , height/2);
			}

			if (unitend ==="1")
			{
			context.fillText(units+val, width/2 , height/2);
			}

}

function feedtime_draw()
{
	$(".feedtime").each(function(index)
		{

			var font = $(this).attr("font");
			var feedid = $(this).attr("feedid");
		  if (assocfeed[feedid]!=undefined) feedid = assocfeed[feedid]; // convert tag:name to feedid
			if (associd[feedid] === undefined) {
                // console.log("Review config for feed id of " + $(this).attr("class"));
                return;
            }

			var val = ((new Date()).getTime() / 1000  - offsetofTime - (associd[feedid]["time"] * 1));

			if (val===undefined) {val = 0;}
			if (isNaN(val))  {val = 0;}

			var size = $(this).attr("size");
			var units = $(this).attr("units");
			var decimals = $(this).attr("decimals");

			if (decimals===undefined) {decimals = -1};

			var unitend = $(this).attr("unitend");

			{
				var id = "can-"+$(this).attr("id");

				draw_feedtime(widgetcanvas[id],
					0,
					0,
					$(this).attr("font"),
					$(this).attr("fstyle"),
					$(this).attr("fweight"),
					$(this).width(),
					$(this).height(),
					val,
					$(this).attr("units"),
					$(this).attr("colour"),
					$(this).attr("size"),
					$(this).attr("unitend")
					);
			}
		});
}

function feedtime_init()
{
	setup_widget_canvas("feedtime");
}
function feedtime_slowupdate()
	{
		feedtime_draw();
	}

function feedtime_fastupdate()
	{
		feedtime_draw();
	}
<commit_msg>feedtime aligment<commit_after>/*
  All Emoncms code is released under the GNU Affero General Public License.
  See COPYRIGHT.txt and LICENSE.txt.
    ---------------------------------------------------------------------
    Part of the OpenEnergyMonitor project:
    http://openenergymonitor.org

    Author: Trystan Lea: trystan.lea@googlemail.com
	Enhancements done by: Andreas Messerli firefox7518@gmail.com
    If you have any questions please get in touch, try the forums here:
    http://openenergymonitor.org/emon/forum
 */

 function addOption(widget, optionKey, optionType, optionName, optionHint, optionData)
{
  widget["options"    ].push(optionKey);
  widget["optionstype"].push(optionType);
  widget["optionsname"].push(optionName);
  widget["optionshint"].push(optionHint);
  widget["optionsdata"].push(optionData);
}

function feedtime_widgetlist()
{
  var widgets =
  {
    "feedtime":
    {
      "offsetx":-40,"offsety":-30,"width":120,"height":60,
      "menu":"Widgets",
      "options":    [],
      "optionstype":[],
      "optionsname":[],
      "optionshint":[],
      "optionsdata":[]
    }
  };

  var decimalsDropBoxOptions = [        // Options for the type combobox. Each item is [typeID, "description"]
        [-1,   _Tr("Automatic")],
        [0,    "0"],
        [1,    "1"],
        [2,    "2"],
        [3,    "3"],
        [4,    "4"],
        [5,    "5"],
        [6,    "6"]
    ];

	var fontoptions = [
					[9, "Arial Black"],
					[8, "Arial Narrow"],
					[7, "sans-serif"],
					[6, "Helvetica Neue"],
					[5, "Helvetica"],
					[4, "Comic Sans MS"],
					[3, "Courier New"],
					[2, "Arial"],
					[1, "Georgia"],
					[0, "Impact"]
				];

	var fstyleoptions = [
					[2, _Tr("Normal")],
					[1, _Tr("Italic")],
					[0, _Tr("Oblique")]
				];

	var fweightoptions = [
					[1, _Tr("Bold")],
					[0, _Tr("Normal")]
				];

	var sizeoptions = [
					[14, "18"], // set size 18 to the top position to be the default value for creating new feedtime widgets otherwise size 40 would be always the default
					[13, "40"],
					[12, "36"],
					[11, "32"],
					[10, "28"],
					[9, "24"],
					[8, "22"],
					[7, "20"],
					[6, "18"],
					[5, "16"],
					[4, "14"],
					[3, "12"],
					[2, "10"],
					[1, "8"],
					[0, "6"]
				];

	var unitEndOptions = [
					[0, _Tr("Back")],
					[1, _Tr("Front")]
				];
  var alignmentOptions = [
    ["center", _Tr("Center")],
    ["left", _Tr("Left")],
    ["right", _Tr("Right")]
  ];

	addOption(widgets["feedtime"], "feedid",  "feedid",        _Tr("Feed"),          _Tr("Feed value"),                     []);
	addOption(widgets["feedtime"], "colour",  "colour_picker", _Tr("Colour"),        _Tr("Colour used for display"),        []);
	addOption(widgets["feedtime"], "font",    "dropbox",       _Tr("Font"),          _Tr("Font used for display"),          fontoptions);
	addOption(widgets["feedtime"], "fstyle",  "dropbox",       _Tr("Font style"),    _Tr("Font style used for display"),    fstyleoptions);
	addOption(widgets["feedtime"], "fweight", "dropbox",       _Tr("Font weight"),   _Tr("Font weight used for display"),   fweightoptions);
	addOption(widgets["feedtime"], "units",   "dropbox_other", _Tr("Units"),         _Tr("Units to show"),                  _SI);
	addOption(widgets["feedtime"], "size",    "dropbox",       _Tr("Size"),          _Tr("Text size in px to use"),         sizeoptions);
	addOption(widgets["feedtime"], "align",    "dropbox", _Tr("Alignment"), _Tr("Alignment"), alignmentOptions);
	addOption(widgets["feedtime"], "unitend", "dropbox",       _Tr("Unit position"), _Tr("Where should the unit be shown"), unitEndOptions);

	return widgets;
}

function draw_feedtime(feedvalue,
		x_pos,				// these x and y coords seem unused?
		y_pos,
		font,
		fstyle,
		fweight,
		width,
		height,
		val,
		units,
		colour,
		size,
		align,
		unitend)
		{
			colour = colour || "4444CC";
			unitend = unitend || "0";
			size = size || "8";
			font = font || "5";
			fstyle = fstyle || "2";
			fweight = fweight || "1";
			align = align || "center";

			var fontsize;

			if (size === "0"){fontsize = 6;}
			if (size === "1"){fontsize = 8;}
			if (size === "2"){fontsize = 10;}
			if (size === "3"){fontsize = 12;}
			if (size === "4"){fontsize = 14;}
			if (size === "5"){fontsize = 16;}
			if (size === "6"){fontsize = 18;}
			if (size === "7"){fontsize = 20;}
			if (size === "8"){fontsize = 22;}
			if (size === "9"){fontsize = 24;}
			if (size === "10"){fontsize = 28;}
			if (size === "11"){fontsize = 32;}
			if (size === "12"){fontsize = 36;}
			if (size === "13"){fontsize = 40;}
			if (size === "14"){fontsize = 18;}  //default value so that not size 40 is always the default

			var fontname;

			if (font === "0"){fontname = "Impact";}
			if (font === "1"){fontname = "Georgia";}
			if (font === "2"){fontname = "Arial";}
			if (font === "3"){fontname = "Courier New";}
			if (font === "4"){fontname = "Comic Sans MS";}
			if (font === "5"){fontname = "Helvetica";}
			if (font === "6"){fontname = "Helvetica Neue";}
			if (font === "7"){fontname = "sans-serif";}
			if (font === "8"){fontname = "Arial Narrow";}
			if (font === "9"){fontname = "Arial Black";}

			var fontstyle;

			if (fstyle === "0"){fontstyle = "oblique";}
			if (fstyle === "1"){fontstyle = "italic";}
			if (fstyle === "2"){fontstyle = "normal";}

			var fontweight;

			if (fweight === "0"){fontweight = "normal";}
			if (fweight === "1"){fontweight = "bold";}

			val = val.toFixed(0);

			if (colour.indexOf("#") === -1){			// Fix missing "#" on colour if needed
				colour = "#" + colour;
			}

			feedvalue.css({
				"color":colour,
				"font":fontstyle+" "+ fontweight+" "+ fontsize+"px "+fontname,"text-align":align,
				"line-height":height+"px"
			});

			if (unitend ==="0")
			{
			feedvalue.html(val+units);
			}

			if (unitend ==="1")
			{
			feedvalue.html(units+val);
			}

}

function feedtime_draw()
{
	$(".feedtime").each(function(index)
		{
			var feedvalue = $(this);
			var font = $(this).attr("font");
			var feedid = $(this).attr("feedid");
			if (assocfeed[feedid]!=undefined) feedid = assocfeed[feedid]; // convert tag:name to feedid
			if (associd[feedid] === undefined) {
                // console.log("Review config for feed id of " + $(this).attr("class"));
                return;
            }

			var val = ((new Date()).getTime() / 1000  - offsetofTime - (associd[feedid]["time"] * 1));

			if (val===undefined) {val = 0;}
			if (isNaN(val))  {val = 0;}

			var size = $(this).attr("size");
			var units = $(this).attr("units");
			var decimals = $(this).attr("decimals");

			if (decimals===undefined) {decimals = -1};

			var unitend = $(this).attr("unitend");

			{
				var id = "can-"+$(this).attr("id");

				draw_feedtime(feedvalue,
					0,
					0,
					$(this).attr("font"),
					$(this).attr("fstyle"),
					$(this).attr("fweight"),
					$(this).width(),
					$(this).height(),
					val,
					$(this).attr("units"),
					$(this).attr("colour"),
					$(this).attr("size"),
					$(this).attr("align"),
					$(this).attr("unitend")
					);
			}
		});
}

function feedtime_init()
{
	$(".feedvalue").html("");
}
function feedtime_slowupdate()
	{
		feedtime_draw();
	}

function feedtime_fastupdate()
	{
		feedtime_draw();
	}
<|endoftext|>