<commit_before>/*
 * == BSD2 LICENSE ==
 * Copyright (c) 2014, Tidepool Project
 *
 * This program is free software; you can redistribute it and/or modify it under
 * the terms of the associated License, which is identical to the BSD 2-Clause
 * License as published by the Open Source Initiative at opensource.org.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the License for more details.
 *
 * You should have received a copy of the License along with this program; if
 * not, you can obtain one from Tidepool Project at tidepool.org.
 * == BSD2 LICENSE ==
 */

const isBrowser = typeof window !== 'undefined';
const debug = isBrowser ? require('bows')('FreeStyleLibreDriver') : console.log;

import structJs from '../../struct.js';
const struct = structJs();

import _ from 'lodash';
import sundial from 'sundial';

import TZOUtil from '../../TimezoneOffsetUtil';

import {
  OP_CODE,
  ERROR_DESCRIPTION,
  DB_RECORD_TYPE,
  CFG_TABLE_ID,
  RESULT_VALUE_TYPE,
  COMPRESSION_TYPE
} from './freeStyleLibreConstants';

const FORMAT = {
  ERROR: 'bb',
  DATE_TIME: 'bbbbbsb',
  RECORD_HEADER: 'sbbin',
  HISTORICAL_DATA: 'ssss',
  TIME_CHANGE: 'iiss'
};

const FORMAT_LENGTH = _.mapValues(FORMAT, format => { return struct.structlen(format); });

const OP_CODE_PROCESSING_ORDER = [
  //OP_CODE.GET_CFG_SCHEMA, // not used for now
  //OP_CODE.GET_DB_SCHEMA, // not used for now
  OP_CODE.GET_DATE_TIME,
  OP_CODE.GET_CFG_DATA,
  OP_CODE.COMPRESSED_DATABASE,
  OP_CODE.GET_DATABASE
];

const KETONE_VALUE_FACTOR = 18.0;

export class FreeStyleLibreData {

  constructor(cfg) {
    this.cfg = cfg;

    this.opCodeHandlers = {};
    this.opCodeHandlers[OP_CODE.GET_DATE_TIME] = this.handleDateTime.bind(this);
    this.opCodeHandlers[OP_CODE.GET_DB_SCHEMA] = this.handleDatabaseSchema.bind(this);
    this.opCodeHandlers[OP_CODE.COMPRESSED_DATABASE] = this.handleCompressedDatabase.bind(this);
    this.opCodeHandlers[OP_CODE.GET_DATABASE] = this.handleDatabase.bind(this);
    this.opCodeHandlers[OP_CODE.GET_CFG_SCHEMA] = this.handleConfigSchema.bind(this);
    this.opCodeHandlers[OP_CODE.GET_CFG_DATA] = this.handleConfigData.bind(this);
    this.opCodeHandlers[OP_CODE.ERROR] = this.constructor.handleError;

    this.factoryConfig = {};
    this.deviceDateTime = null;
    this.records = [];
    this.postRecords = [];
  }

  processAapPackets(aapPackets) {

    // sort AAP packets by their OP code
    const aapPacketsByOpCode = {};
    for (let aapPacket of aapPackets) {
      const opCode = aapPacket['opCode'];
      if (!(opCode in aapPacketsByOpCode)) {
        aapPacketsByOpCode[opCode] = [];
      }
      aapPacketsByOpCode[opCode].push(aapPacket);
    }

    // process AAP packet in fixed order to make sure data is available when needed
    for (let opCode of OP_CODE_PROCESSING_ORDER) {
      if (opCode in aapPacketsByOpCode) {
        for (let aapPacket of aapPacketsByOpCode[opCode]) {
          const handler = this.opCodeHandlers[aapPacket['opCode']];
          if (handler) {
            handler(aapPacket);
          } else {
            debug('processAapPackets: no handler found for OP code:', aapPacket['opCode']);
          }
        }
      }
    }

    this.records.sort((a, b) => b.headerFields.recordNumber - a.headerFields.recordNumber);
    var timestamp = this.records[this.records.length-1].jsDate;
    var mostRecent = sundial.applyTimezone(timestamp, this.cfg.timezone).toISOString();

    this.buildTimeChangeRecords();
    debugger;
    this.cfg.tzoUtil = new TZOUtil(this.cfg.timezone, mostRecent, this.postRecords);

    this.buildCBGRecords();
    this.buildMeasurementRecords();

    return this.postRecords;
  }

  static handleError(aapPacket) {
    const fields = struct.unpack(aapPacket.data, 0, FORMAT.ERROR, ['opCode', 'errorCode']);
    debug('handleError:', ERROR_DESCRIPTION[fields.errorCode], 'for OP code', fields.opCode);
    if (aapPacket.data.length > FORMAT_LENGTH.ERROR) {
      debug('handleError: extra data:', aapPacket.data.slice(FORMAT_LENGTH.ERROR).toString('hex'));
    }
  }

  handleDateTime(aapPacket) {
    if (aapPacket.dataLength !== FORMAT_LENGTH.DATE_TIME) {
      debug('handleDateTime: wrong data length:', aapPacket.dataLength, 'instead of', FORMAT_LENGTH.DATE_TIME);
      return;
    }
    const fields = struct.unpack(aapPacket.data, 0, FORMAT.DATE_TIME,
      ['second', 'minute', 'hour', 'day', 'month', 'year', 'valid']);
    if (fields.valid !== 1) {
      debug('handleDateTime: date not marked as valid:', fields.valid, aapPacket.data.data[0]);
      return;
    }
    this.deviceDateTime = new Date(fields.year, fields.month - 1, fields.day,
      fields.hour, fields.minute, fields.second);
    debug('handleDateTime: datetime:', this.deviceDateTime);
  }

  handleDatabaseSchema(aapPacket) {
    /*
     * These are ignored for now, as the schemata are already known from the specs.
     * For now they are hardcoded based on the specs for the few record types that are actually needed.
     *
     * The schemata describe the fields in the database records, so that using this information to parse the records
     * instead of the hardcoded format strings, would make it possible to understand the data even after a potential
     * firmware upgrade that changes the database structure.
     * (As long as the field IDs stay the same, the fields parsed via these schemata can still be evaluated properly.)
     *
     * Schema description: (example: the record header prefixed to all records)
     *
        UINT8 RecordHeader_schema[] =
        {
          // schema descriptor
          48, 0, // [uint16_le] schema table length (including this descriptor)
          1, 0,  // [uint16_le] schema table version
          255,   // [uint8]     schema table/record ID
          6, 0,  // [uint16_le] number of data words (16bit) in the record
          5,     // [uint8]     number of fields in the record

          // field descriptors (8 byte each)
          // [uint16_le],  [uint16_le],  [uint8],                     [uint8],    [uint16_le]
          // field ID,     word offset,  bit offset inside the word,  data type,  data length in bits
          0,0,0,0,0,1,16,0,
          8,0,1,0,0,0,8,0,
          7,0,1,0,15,0,1,0,
          9,0,2,0,0,0,32,0,
          10,0,4,0,0,2,32,0
        };
     *
     */
  }

  getDateTime(readerTime, userTimeOffset) {
    const unixTimestamp = this.factoryConfig.timeConversion + readerTime + userTimeOffset;
    return new Date(unixTimestamp * 1000);
  }

  buildTimeChangeRecords() {
    for (let record of this.records.filter(elem => elem.headerFields.recordType === DB_RECORD_TYPE.TIME_CHANGE_RESULT )) {
      const oldDateTime = this.getDateTime(record.timeChangeFields.oldReaderTime, record.timeChangeFields.oldUserTimeOffset);

      if(oldDateTime > Date.now()) {
        // TODO: check if there's something else going on here
        debug('Skipping non-sensical time change from:', oldDateTime);
        continue;
      }

      const timeChange = this.cfg.builder.makeDeviceEventTimeChange()
        .with_change({
          from: sundial.formatDeviceTime(oldDateTime),
          to: sundial.formatDeviceTime(record.jsDate),
          agent: 'manual'
        })
        .with_deviceTime(sundial.formatDeviceTime(record.jsDate))
        .set('index', record.headerFields.recordNumber)
        .set('jsDate', record.jsDate);
      this.postRecords.push(timeChange);
    }
  }

  buildCBGRecords() {
    for (let record of this.records.filter(elem => elem.headerFields.recordType === DB_RECORD_TYPE.HISTORICAL_DATA )) {
      const cbg = this.cfg.builder.makeCBG()
        .with_value(record.historyFields.glucoseValue)
        .with_units('mg/dL') // values are always in 'mg/dL', independent of the unitOfMeasure setting
        .with_deviceTime(sundial.formatDeviceTime(record.jsDate))
        .set('index', record.headerFields.recordNumber)
        .set('jsDate', record.jsDate);

      this.cfg.tzoUtil.fillInUTCInfo(cbg, record.jsDate);
      this.postRecords.push(cbg.done());
    }
  }

  buildMeasurementRecords() {
    for (let record of this.records.filter(elem =>
      [DB_RECORD_TYPE.GLUCOSE_KETONE_SERVING,
        DB_RECORD_TYPE.GLUCOSE_KETONE_MEAL,
        DB_RECORD_TYPE.GLUCOSE_KETONE_CARBS
      ].includes(elem.headerFields.recordType))) {

      let recordBuilder;

      if (record.measurementFields.resultType === RESULT_VALUE_TYPE.GLUCOSE) {

        recordBuilder = this.cfg.builder.makeSMBG()
          .with_value(record.measurementFields.resultValue)
          .with_units('mg/dL') // values are always in 'mg/dL', independent of the unitOfMeasure setting
          .with_deviceTime(sundial.formatDeviceTime(record.jsDate))
          .set('index', record.headerFields.recordNumber)
          .set('jsDate', record.jsDate);

      } else if (record.measurementFields.resultType === RESULT_VALUE_TYPE.KETONE) {

        recordBuilder = this.cfg.builder.makeBloodKetone()
          .with_value(record.measurementFields.resultValue / KETONE_VALUE_FACTOR);

      }

      if (recordBuilder) {
        cfg.tzoUtil.fillInUTCInfo(recordBuilder, record.jsDate);
        this.postRecords.push(recordBuilder.done());
      }
    }
  }

  handleCompressedDatabase(aapPacket) {
    let decompressedBuffer = new Buffer(1);
    let compressedOffset = 0;

    // copy table ID
    decompressedBuffer[0] = aapPacket.data[compressedOffset];
    compressedOffset++;

    while (compressedOffset < aapPacket.dataLength) {
      const blockType = aapPacket.data[compressedOffset];
      compressedOffset++;

      // parse 24 bit little endian block length
      let blockLength = aapPacket.data[compressedOffset]
        | (aapPacket.data[compressedOffset + 1] << 8)
        | (aapPacket.data[compressedOffset + 2] << 16);
      compressedOffset += 3;

      blockLength *= 4; // convert number of uint32 values to number of uint8 values

      if (blockType === COMPRESSION_TYPE.UNCOMPRESSED) {

        decompressedBuffer = Buffer.concat(
          [decompressedBuffer, aapPacket.data.slice(compressedOffset, compressedOffset + blockLength)]);
        compressedOffset += blockLength;

      } else if (blockType === COMPRESSION_TYPE.ZERO_COMPRESSED) {

        decompressedBuffer = Buffer.concat([decompressedBuffer, Buffer.alloc(blockLength)]);
        compressedOffset += blockLength;

      } else {
        debug('handleCompressedDatabase: failed to decompress!');
        return;
      }
    }

    // build decompressed AAP packet to process
    const decompressedAapPacket = {
      packetLength: aapPacket.packetLength - aapPacket.dataLength + aapPacket.data.length,
      data: decompressedBuffer,
      dataLength: aapPacket.data.length,
      opCode: OP_CODE.GET_DATABASE
    };

    this.handleDatabase(decompressedAapPacket);
  }

  handleDatabase(aapPacket) {
    if (aapPacket.dataLength === 0) {
      return;
    }

    let offset = 0;
    //const tableId = aapPacket.data[offset]; // not relevant
    offset += 1;

    const headerFields = struct.unpack(aapPacket.data, offset, FORMAT.RECORD_HEADER,
      ['recordNumber', 'recordType', 'isTimeValid', 'readerTime', 'userTimeOffset']);
    headerFields.isTimeValid = ((headerFields.isTimeValid & 0x80) > 0);
    offset += FORMAT_LENGTH.RECORD_HEADER;

    // TODO: handle time change events and adapt use of tzoUtil accordingly
    const dateTime = this.getDateTime(headerFields.readerTime, headerFields.userTimeOffset);

    if (headerFields.recordType === DB_RECORD_TYPE.TIME_CHANGE_RESULT) {
      const timeChangeFields = struct.unpack(aapPacket.data, offset, FORMAT.TIME_CHANGE,
        ['oldReaderTime', 'oldUserTimeOffset', 'valid', 'CRC16']);

      // TODO: validate CRC16
        if (timeChangeFields.valid) {
          this.records.push({headerFields, timeChangeFields, jsDate : dateTime});
        }

    } else if (headerFields.recordType === DB_RECORD_TYPE.HISTORICAL_DATA) {

      const historyFields = struct.unpack(aapPacket.data, offset, FORMAT.HISTORICAL_DATA,
        ['glucoseValue', 'lifeCounter', 'dataQualityErrorFlags', 'CRC16']);
      historyFields.firstFlag = ((historyFields.glucoseValue & 0x1000) > 0);
      historyFields.timeChangeFlag = ((historyFields.glucoseValue & 0x2000) > 0);
      historyFields.foodFlag = ((historyFields.glucoseValue & 0x4000) > 0);
      historyFields.rapidActingInsulinFlag = ((historyFields.glucoseValue & 0x8000) > 0);
      historyFields.glucoseValue = (historyFields.glucoseValue & 0x03ff);

      // TODO: validate CRC16
      if (historyFields.dataQualityErrorFlags === 0) {
        //debug('handleDatabase: historyFields:', historyFields, aapPacket.data.toString('hex'));
        this.records.push({headerFields, historyFields, jsDate : dateTime});
      }

    } else if ([DB_RECORD_TYPE.GLUCOSE_KETONE_SERVING,
        DB_RECORD_TYPE.GLUCOSE_KETONE_MEAL,
        DB_RECORD_TYPE.GLUCOSE_KETONE_CARBS
      ].includes(headerFields.recordType)) {

      const measurementFields = {};
      const RESULT_VALUE_OFFSET = 0;
      struct.unpack(aapPacket.data, offset + RESULT_VALUE_OFFSET, 's', ['resultValue'], measurementFields);
      measurementFields.resultType = (measurementFields.resultValue >> 14) & 0x3;
      measurementFields.resultValue = (measurementFields.resultValue & 0x03ff);

      const DATA_QUALITY_ERROR_FLAGS_OFFSET = 10;
      struct.unpack(aapPacket.data, offset + DATA_QUALITY_ERROR_FLAGS_OFFSET, 's', ['dataQualityErrorFlags'], measurementFields);

      // TODO: validate CRC16
      if (measurementFields.dataQualityErrorFlags === 0) {
        this.records.push({headerFields, measurementFields});
      }

    }
  }

  handleConfigSchema(aapPacket) {
    // ignored, since they are currently hardcoded based on the specs
  }

  handleConfigData(aapPacket) {
    let offset = 0;
    const tableId = aapPacket.data[offset];
    offset += 1;
    if (tableId === CFG_TABLE_ID.METER_FACTORY_CONFIGURATION) {
      const UNIT_OF_MEASURE_OFFSET = 133;
      struct.unpack(aapPacket.data, offset + UNIT_OF_MEASURE_OFFSET, 'b', ['unitOfMeasure'], this.factoryConfig);
      this.factoryConfig.unitOfMeasure = ['mmol/L', 'mg/dL'][this.factoryConfig.unitOfMeasure];

      const TIME_CONVERSION_OFFSET = 156;
      struct.unpack(aapPacket.data, offset + TIME_CONVERSION_OFFSET, 'i', ['timeConversion'], this.factoryConfig);
    }
  }

}
<commit_msg>add this to cfg<commit_after>/*
 * == BSD2 LICENSE ==
 * Copyright (c) 2014, Tidepool Project
 *
 * This program is free software; you can redistribute it and/or modify it under
 * the terms of the associated License, which is identical to the BSD 2-Clause
 * License as published by the Open Source Initiative at opensource.org.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the License for more details.
 *
 * You should have received a copy of the License along with this program; if
 * not, you can obtain one from Tidepool Project at tidepool.org.
 * == BSD2 LICENSE ==
 */

const isBrowser = typeof window !== 'undefined';
const debug = isBrowser ? require('bows')('FreeStyleLibreDriver') : console.log;

import structJs from '../../struct.js';
const struct = structJs();

import _ from 'lodash';
import sundial from 'sundial';

import TZOUtil from '../../TimezoneOffsetUtil';

import {
  OP_CODE,
  ERROR_DESCRIPTION,
  DB_RECORD_TYPE,
  CFG_TABLE_ID,
  RESULT_VALUE_TYPE,
  COMPRESSION_TYPE
} from './freeStyleLibreConstants';

const FORMAT = {
  ERROR: 'bb',
  DATE_TIME: 'bbbbbsb',
  RECORD_HEADER: 'sbbin',
  HISTORICAL_DATA: 'ssss',
  TIME_CHANGE: 'iiss'
};

const FORMAT_LENGTH = _.mapValues(FORMAT, format => { return struct.structlen(format); });

const OP_CODE_PROCESSING_ORDER = [
  //OP_CODE.GET_CFG_SCHEMA, // not used for now
  //OP_CODE.GET_DB_SCHEMA, // not used for now
  OP_CODE.GET_DATE_TIME,
  OP_CODE.GET_CFG_DATA,
  OP_CODE.COMPRESSED_DATABASE,
  OP_CODE.GET_DATABASE
];

const KETONE_VALUE_FACTOR = 18.0;

export class FreeStyleLibreData {

  constructor(cfg) {
    this.cfg = cfg;

    this.opCodeHandlers = {};
    this.opCodeHandlers[OP_CODE.GET_DATE_TIME] = this.handleDateTime.bind(this);
    this.opCodeHandlers[OP_CODE.GET_DB_SCHEMA] = this.handleDatabaseSchema.bind(this);
    this.opCodeHandlers[OP_CODE.COMPRESSED_DATABASE] = this.handleCompressedDatabase.bind(this);
    this.opCodeHandlers[OP_CODE.GET_DATABASE] = this.handleDatabase.bind(this);
    this.opCodeHandlers[OP_CODE.GET_CFG_SCHEMA] = this.handleConfigSchema.bind(this);
    this.opCodeHandlers[OP_CODE.GET_CFG_DATA] = this.handleConfigData.bind(this);
    this.opCodeHandlers[OP_CODE.ERROR] = this.constructor.handleError;

    this.factoryConfig = {};
    this.deviceDateTime = null;
    this.records = [];
    this.postRecords = [];
  }

  processAapPackets(aapPackets) {

    // sort AAP packets by their OP code
    const aapPacketsByOpCode = {};
    for (let aapPacket of aapPackets) {
      const opCode = aapPacket['opCode'];
      if (!(opCode in aapPacketsByOpCode)) {
        aapPacketsByOpCode[opCode] = [];
      }
      aapPacketsByOpCode[opCode].push(aapPacket);
    }

    // process AAP packet in fixed order to make sure data is available when needed
    for (let opCode of OP_CODE_PROCESSING_ORDER) {
      if (opCode in aapPacketsByOpCode) {
        for (let aapPacket of aapPacketsByOpCode[opCode]) {
          const handler = this.opCodeHandlers[aapPacket['opCode']];
          if (handler) {
            handler(aapPacket);
          } else {
            debug('processAapPackets: no handler found for OP code:', aapPacket['opCode']);
          }
        }
      }
    }

    this.records.sort((a, b) => b.headerFields.recordNumber - a.headerFields.recordNumber);
    var timestamp = this.records[this.records.length-1].jsDate;
    var mostRecent = sundial.applyTimezone(timestamp, this.cfg.timezone).toISOString();

    this.buildTimeChangeRecords();
    debugger;
    this.cfg.tzoUtil = new TZOUtil(this.cfg.timezone, mostRecent, this.postRecords);

    this.buildCBGRecords();
    this.buildMeasurementRecords();

    return this.postRecords;
  }

  static handleError(aapPacket) {
    const fields = struct.unpack(aapPacket.data, 0, FORMAT.ERROR, ['opCode', 'errorCode']);
    debug('handleError:', ERROR_DESCRIPTION[fields.errorCode], 'for OP code', fields.opCode);
    if (aapPacket.data.length > FORMAT_LENGTH.ERROR) {
      debug('handleError: extra data:', aapPacket.data.slice(FORMAT_LENGTH.ERROR).toString('hex'));
    }
  }

  handleDateTime(aapPacket) {
    if (aapPacket.dataLength !== FORMAT_LENGTH.DATE_TIME) {
      debug('handleDateTime: wrong data length:', aapPacket.dataLength, 'instead of', FORMAT_LENGTH.DATE_TIME);
      return;
    }
    const fields = struct.unpack(aapPacket.data, 0, FORMAT.DATE_TIME,
      ['second', 'minute', 'hour', 'day', 'month', 'year', 'valid']);
    if (fields.valid !== 1) {
      debug('handleDateTime: date not marked as valid:', fields.valid, aapPacket.data.data[0]);
      return;
    }
    this.deviceDateTime = new Date(fields.year, fields.month - 1, fields.day,
      fields.hour, fields.minute, fields.second);
    debug('handleDateTime: datetime:', this.deviceDateTime);
  }

  handleDatabaseSchema(aapPacket) {
    /*
     * These are ignored for now, as the schemata are already known from the specs.
     * For now they are hardcoded based on the specs for the few record types that are actually needed.
     *
     * The schemata describe the fields in the database records, so that using this information to parse the records
     * instead of the hardcoded format strings, would make it possible to understand the data even after a potential
     * firmware upgrade that changes the database structure.
     * (As long as the field IDs stay the same, the fields parsed via these schemata can still be evaluated properly.)
     *
     * Schema description: (example: the record header prefixed to all records)
     *
        UINT8 RecordHeader_schema[] =
        {
          // schema descriptor
          48, 0, // [uint16_le] schema table length (including this descriptor)
          1, 0,  // [uint16_le] schema table version
          255,   // [uint8]     schema table/record ID
          6, 0,  // [uint16_le] number of data words (16bit) in the record
          5,     // [uint8]     number of fields in the record

          // field descriptors (8 byte each)
          // [uint16_le],  [uint16_le],  [uint8],                     [uint8],    [uint16_le]
          // field ID,     word offset,  bit offset inside the word,  data type,  data length in bits
          0,0,0,0,0,1,16,0,
          8,0,1,0,0,0,8,0,
          7,0,1,0,15,0,1,0,
          9,0,2,0,0,0,32,0,
          10,0,4,0,0,2,32,0
        };
     *
     */
  }

  getDateTime(readerTime, userTimeOffset) {
    const unixTimestamp = this.factoryConfig.timeConversion + readerTime + userTimeOffset;
    return new Date(unixTimestamp * 1000);
  }

  buildTimeChangeRecords() {
    for (let record of this.records.filter(elem => elem.headerFields.recordType === DB_RECORD_TYPE.TIME_CHANGE_RESULT )) {
      const oldDateTime = this.getDateTime(record.timeChangeFields.oldReaderTime, record.timeChangeFields.oldUserTimeOffset);

      if(oldDateTime > Date.now()) {
        // TODO: check if there's something else going on here
        debug('Skipping non-sensical time change from:', oldDateTime);
        continue;
      }

      const timeChange = this.cfg.builder.makeDeviceEventTimeChange()
        .with_change({
          from: sundial.formatDeviceTime(oldDateTime),
          to: sundial.formatDeviceTime(record.jsDate),
          agent: 'manual'
        })
        .with_deviceTime(sundial.formatDeviceTime(record.jsDate))
        .set('index', record.headerFields.recordNumber)
        .set('jsDate', record.jsDate);
      this.postRecords.push(timeChange);
    }
  }

  buildCBGRecords() {
    for (let record of this.records.filter(elem => elem.headerFields.recordType === DB_RECORD_TYPE.HISTORICAL_DATA )) {
      const cbg = this.cfg.builder.makeCBG()
        .with_value(record.historyFields.glucoseValue)
        .with_units('mg/dL') // values are always in 'mg/dL', independent of the unitOfMeasure setting
        .with_deviceTime(sundial.formatDeviceTime(record.jsDate))
        .set('index', record.headerFields.recordNumber)
        .set('jsDate', record.jsDate);

      this.cfg.tzoUtil.fillInUTCInfo(cbg, record.jsDate);
      this.postRecords.push(cbg.done());
    }
  }

  buildMeasurementRecords() {
    for (let record of this.records.filter(elem =>
      [DB_RECORD_TYPE.GLUCOSE_KETONE_SERVING,
        DB_RECORD_TYPE.GLUCOSE_KETONE_MEAL,
        DB_RECORD_TYPE.GLUCOSE_KETONE_CARBS
      ].includes(elem.headerFields.recordType))) {

      let recordBuilder;

      if (record.measurementFields.resultType === RESULT_VALUE_TYPE.GLUCOSE) {

        recordBuilder = this.cfg.builder.makeSMBG()
          .with_value(record.measurementFields.resultValue)
          .with_units('mg/dL') // values are always in 'mg/dL', independent of the unitOfMeasure setting
          .with_deviceTime(sundial.formatDeviceTime(record.jsDate))
          .set('index', record.headerFields.recordNumber)
          .set('jsDate', record.jsDate);

      } else if (record.measurementFields.resultType === RESULT_VALUE_TYPE.KETONE) {

        recordBuilder = this.cfg.builder.makeBloodKetone()
          .with_value(record.measurementFields.resultValue / KETONE_VALUE_FACTOR);

      }

      if (recordBuilder) {
        this.cfg.tzoUtil.fillInUTCInfo(recordBuilder, record.jsDate);
        this.postRecords.push(recordBuilder.done());
      }
    }
  }

  handleCompressedDatabase(aapPacket) {
    let decompressedBuffer = new Buffer(1);
    let compressedOffset = 0;

    // copy table ID
    decompressedBuffer[0] = aapPacket.data[compressedOffset];
    compressedOffset++;

    while (compressedOffset < aapPacket.dataLength) {
      const blockType = aapPacket.data[compressedOffset];
      compressedOffset++;

      // parse 24 bit little endian block length
      let blockLength = aapPacket.data[compressedOffset]
        | (aapPacket.data[compressedOffset + 1] << 8)
        | (aapPacket.data[compressedOffset + 2] << 16);
      compressedOffset += 3;

      blockLength *= 4; // convert number of uint32 values to number of uint8 values

      if (blockType === COMPRESSION_TYPE.UNCOMPRESSED) {

        decompressedBuffer = Buffer.concat(
          [decompressedBuffer, aapPacket.data.slice(compressedOffset, compressedOffset + blockLength)]);
        compressedOffset += blockLength;

      } else if (blockType === COMPRESSION_TYPE.ZERO_COMPRESSED) {

        decompressedBuffer = Buffer.concat([decompressedBuffer, Buffer.alloc(blockLength)]);
        compressedOffset += blockLength;

      } else {
        debug('handleCompressedDatabase: failed to decompress!');
        return;
      }
    }

    // build decompressed AAP packet to process
    const decompressedAapPacket = {
      packetLength: aapPacket.packetLength - aapPacket.dataLength + aapPacket.data.length,
      data: decompressedBuffer,
      dataLength: aapPacket.data.length,
      opCode: OP_CODE.GET_DATABASE
    };

    this.handleDatabase(decompressedAapPacket);
  }

  handleDatabase(aapPacket) {
    if (aapPacket.dataLength === 0) {
      return;
    }

    let offset = 0;
    //const tableId = aapPacket.data[offset]; // not relevant
    offset += 1;

    const headerFields = struct.unpack(aapPacket.data, offset, FORMAT.RECORD_HEADER,
      ['recordNumber', 'recordType', 'isTimeValid', 'readerTime', 'userTimeOffset']);
    headerFields.isTimeValid = ((headerFields.isTimeValid & 0x80) > 0);
    offset += FORMAT_LENGTH.RECORD_HEADER;

    // TODO: handle time change events and adapt use of tzoUtil accordingly
    const dateTime = this.getDateTime(headerFields.readerTime, headerFields.userTimeOffset);

    if (headerFields.recordType === DB_RECORD_TYPE.TIME_CHANGE_RESULT) {
      const timeChangeFields = struct.unpack(aapPacket.data, offset, FORMAT.TIME_CHANGE,
        ['oldReaderTime', 'oldUserTimeOffset', 'valid', 'CRC16']);

      // TODO: validate CRC16
        if (timeChangeFields.valid) {
          this.records.push({headerFields, timeChangeFields, jsDate : dateTime});
        }

    } else if (headerFields.recordType === DB_RECORD_TYPE.HISTORICAL_DATA) {

      const historyFields = struct.unpack(aapPacket.data, offset, FORMAT.HISTORICAL_DATA,
        ['glucoseValue', 'lifeCounter', 'dataQualityErrorFlags', 'CRC16']);
      historyFields.firstFlag = ((historyFields.glucoseValue & 0x1000) > 0);
      historyFields.timeChangeFlag = ((historyFields.glucoseValue & 0x2000) > 0);
      historyFields.foodFlag = ((historyFields.glucoseValue & 0x4000) > 0);
      historyFields.rapidActingInsulinFlag = ((historyFields.glucoseValue & 0x8000) > 0);
      historyFields.glucoseValue = (historyFields.glucoseValue & 0x03ff);

      // TODO: validate CRC16
      if (historyFields.dataQualityErrorFlags === 0) {
        //debug('handleDatabase: historyFields:', historyFields, aapPacket.data.toString('hex'));
        this.records.push({headerFields, historyFields, jsDate : dateTime});
      }

    } else if ([DB_RECORD_TYPE.GLUCOSE_KETONE_SERVING,
        DB_RECORD_TYPE.GLUCOSE_KETONE_MEAL,
        DB_RECORD_TYPE.GLUCOSE_KETONE_CARBS
      ].includes(headerFields.recordType)) {

      const measurementFields = {};
      const RESULT_VALUE_OFFSET = 0;
      struct.unpack(aapPacket.data, offset + RESULT_VALUE_OFFSET, 's', ['resultValue'], measurementFields);
      measurementFields.resultType = (measurementFields.resultValue >> 14) & 0x3;
      measurementFields.resultValue = (measurementFields.resultValue & 0x03ff);

      const DATA_QUALITY_ERROR_FLAGS_OFFSET = 10;
      struct.unpack(aapPacket.data, offset + DATA_QUALITY_ERROR_FLAGS_OFFSET, 's', ['dataQualityErrorFlags'], measurementFields);

      // TODO: validate CRC16
      if (measurementFields.dataQualityErrorFlags === 0) {
        this.records.push({headerFields, measurementFields});
      }

    }
  }

  handleConfigSchema(aapPacket) {
    // ignored, since they are currently hardcoded based on the specs
  }

  handleConfigData(aapPacket) {
    let offset = 0;
    const tableId = aapPacket.data[offset];
    offset += 1;
    if (tableId === CFG_TABLE_ID.METER_FACTORY_CONFIGURATION) {
      const UNIT_OF_MEASURE_OFFSET = 133;
      struct.unpack(aapPacket.data, offset + UNIT_OF_MEASURE_OFFSET, 'b', ['unitOfMeasure'], this.factoryConfig);
      this.factoryConfig.unitOfMeasure = ['mmol/L', 'mg/dL'][this.factoryConfig.unitOfMeasure];

      const TIME_CONVERSION_OFFSET = 156;
      struct.unpack(aapPacket.data, offset + TIME_CONVERSION_OFFSET, 'i', ['timeConversion'], this.factoryConfig);
    }
  }

}
<|endoftext|>


<commit_before>// Prepare http request for server chain
// - find method (with router)
// - parse parameters

'use strict';

var http  = require('http');

var _     = require('lodash');

module.exports = function (N) {

  //
  // Request logger for http requests
  //
  function log(env) {
    var err = env.err
      , req = env.origin.req
      , message = http.STATUS_CODES[env.status]
      , logger = N.logger.getLogger('http@' + env.method)
      , level;

    if (N.io.APP_ERROR <= env.status) {
      message = err.stack || err.message || JSON.stringify(err);
      level   = 'fatal';
    } else if (N.io.BAD_REQUEST <= env.status && N.io.APP_ERROR > env.status) {
      level = 'error';
    } else {
      level = 'info';
    }

    logger[level]('%s - "%s %s HTTP/%s" %d "%s" - %s',
                  req.connection.remoteAddress,
                  req.method,
                  req.url,
                  req.httpVersion,
                  env.status,
                  req.headers['user-agent'] || '',
                  message);
  }

  //
  // Init envirement for http
  //

  N.wire.before('responder:http', function http_prepare(env, callback) {
    var req = env.origin.req
      , match = env.request.matched // N.runtime.router.match(req.fullUrl)
        // mix GET QUERY params (part after ? in URL) and params from router
        // params from router take precedence
      , params = _.extend({}, req.query, (match || {}).params);

    env.log_request = log;

    env.params = params;

    // Nothing matched -> error
    if (!match) {
      env.err = N.io.NOT_FOUND;
      callback();
      return;
    }

    env.method = match.meta;

    callback();
  });
};
<commit_msg>added post data catch to http responder<commit_after>// Prepare http request for server chain
// - find method (with router)
// - parse parameters

'use strict';

var http  = require('http');
var _     = require('lodash');

var MAX_POST_DATA = 10 * 1000 * 1024; // Max post data in bytes

module.exports = function (N) {

  //
  // Request logger for http requests
  //
  function log(env) {
    var err = env.err
      , req = env.origin.req
      , message = http.STATUS_CODES[env.status]
      , logger = N.logger.getLogger('http@' + env.method)
      , level;

    if (N.io.APP_ERROR <= env.status) {
      message = err.stack || err.message || JSON.stringify(err);
      level   = 'fatal';
    } else if (N.io.BAD_REQUEST <= env.status && N.io.APP_ERROR > env.status) {
      level = 'error';
    } else {
      level = 'info';
    }

    logger[level]('%s - "%s %s HTTP/%s" %d "%s" - %s',
                  req.connection.remoteAddress,
                  req.method,
                  req.url,
                  req.httpVersion,
                  env.status,
                  req.headers['user-agent'] || '',
                  message);
  }

  //
  // Init envirement for http
  //

  N.wire.before('responder:http', function http_prepare(env, callback) {
    var req = env.origin.req
      , match = env.request.matched // N.runtime.router.match(req.fullUrl)
        // mix GET QUERY params (part after ? in URL) and params from router
        // params from router take precedence
      , params = _.extend({}, req.query, (match || {}).params);

    env.log_request = log;

    env.params = params;

    // Nothing matched -> error
    if (!match) {
      env.err = N.io.NOT_FOUND;
      callback();
      return;
    }

    env.method = match.meta;

    //
    // On post request - receive POST data & put to `env.post
    //
    if ('POST' === req.method) {

      //
      // Set encoding, to glue data chunks as strings.
      // In other case you need to work with buffer, to avoid
      // breaking unicode characters.
      //
      // We don't expect rpc to work with big uploads, so, strings are enougth
      //

      req.setEncoding('utf8');

      //
      // start harvesting POST data
      //
      var rawPostData ='';

      req.on('data', function (chunk) {
        rawPostData += chunk;

        if (MAX_POST_DATA < Buffer.byteLength(rawPostData)) {
          // max allowed post data reached, drop request.
          req.removeAllListeners('data');
          req.removeAllListeners('end');

          // Force destroy incoming connection
          req.connection.destroy();

          env.err = { code: N.io.BAD_REQUEST, message: 'Too big post data' };
          callback();
        }
      });

      //
      // when done (on success) process POST data and handle request
      //
      req.on('end', function () {
        env.post = rawPostData;
        callback();
      });

      return;
    }

    callback();
  });
};
<|endoftext|>


<commit_before>var fs = require('fs'),
    path = require('path');

function clientToString(client) {
    return client.handshake.address.address + ':' + client.handshake.address.port;
}

module.exports = function (app, options, sio) {
    var io = sio.listen(app, { log: !options.silentSocketIo }),
        clientsByRootRelativeUrl = {},
        rootRelativeUrlsByFileName = {}, // Map to translate file names reported by fs.watch into known root relative urls
        isWatchedByDirName = {},
        mtimeByFileName = {};

    function notifyWatchers(rootRelativeUrl) {
        if (rootRelativeUrl in clientsByRootRelativeUrl) {
            if (options.debug && clientsByRootRelativeUrl[rootRelativeUrl].length) {
                console.warn('  Notifying ' + clientsByRootRelativeUrl[rootRelativeUrl].length + ' watcher(s):\n  ' +
                             clientsByRootRelativeUrl[rootRelativeUrl].map(clientToString).join('\n  '));
            }
            clientsByRootRelativeUrl[rootRelativeUrl].forEach(function (client) {
                client.emit('change', rootRelativeUrl);
            });
        }
    }

    function notifyWatchersIfMtimeIncreased(rootRelativeUrl, fileName) {
        if (rootRelativeUrl in clientsByRootRelativeUrl) {
            if (!(fileName in mtimeByFileName)) {
                mtimeByFileName[fileName] = Infinity;
                notifyWatchers(rootRelativeUrl);
            } else {
                fs.stat(fileName, function (err, stats) {
                    if (err) {
                        return;
                    }
                    if (stats.mtime > mtimeByFileName[fileName]) {
                        notifyWatchers(rootRelativeUrl);
                    } else if (options.debug) {
                        console.warn('Suppressing notification for ' + fileName + ', the mtime of the file is still the same');
                    }
                    mtimeByFileName[fileName] = stats.mtime;
                });
            }
        }
    }

    if (!options.debug) {
        io.set('log level', 1);
    }
    io.sockets.on('connection', function (client) {
        if (options.debug) {
            console.warn('Client ' + clientToString(client) + ' connected');
        }
        client.on('watch', function (rootRelativeUrls) {
            if (options.debug) {
                console.warn('Client subscribed to ' + rootRelativeUrls.length + ' file(s):');
            }
            rootRelativeUrls.forEach(function (rootRelativeUrl) {
                if (options.debug) {
                    console.warn('  ' + rootRelativeUrl);
                }
                var mappedUrl = rootRelativeUrl.replace(/\?.*$/, '');
                if (options.mappings) {
                    Object.keys(options.mappings).every(function (url) {
                        if (mappedUrl.indexOf(url) === 0) {
                            mappedUrl = mappedUrl.replace(url, options.mappings[url]);
                            if (options.debug) {
                                console.warn('    => rewritten to ' + mappedUrl);
                            }
                            return false;
                        }
                        return true;
                    });
                }

                if (/\/$/.test(mappedUrl)) {
                    mappedUrl += 'index.html';
                }

                // Trim the leading slash if it exists
                mappedUrl = mappedUrl[0] === '/' ? mappedUrl.substr(1) : mappedUrl;

                var rootRelativePath = decodeURIComponent(mappedUrl),
                    fileName = path.resolve(options.root, rootRelativePath);

                rootRelativeUrlsByFileName[fileName] = rootRelativeUrlsByFileName[fileName] || [];
                if (rootRelativeUrlsByFileName[fileName].indexOf(rootRelativeUrl) === -1) {
                    rootRelativeUrlsByFileName[fileName].push(rootRelativeUrl);
                }
                if (rootRelativeUrl in clientsByRootRelativeUrl) {
                    clientsByRootRelativeUrl[rootRelativeUrl].push(client);
                } else {
                    clientsByRootRelativeUrl[rootRelativeUrl] = [client];
                    if (fs.watch && !options.watchfile) {
                        if (process.platform === 'linux' || process.platform === 'win32') {
                            var dirName = path.dirname(fileName);
                            if (!isWatchedByDirName[dirName]) {
                                isWatchedByDirName[dirName] = true;
                                if (options.debug) {
                                    console.warn('Starting to watch ' + dirName + ' using fs.watch');
                                }
                                try {
                                    fs.watch(dirName, function (eventName, fileName) {
                                        fileName = path.resolve(dirName, fileName); // Absolutify fileName
                                        if (options.debug) {
                                            console.warn('fs.watch: ' + eventName + ' ' + fileName);
                                        }
                                        (rootRelativeUrlsByFileName[fileName] || []).forEach(function (rootRelativeUrl) {
                                            if (options.mtime) {
                                                notifyWatchersIfMtimeIncreased(rootRelativeUrl, fileName);
                                            } else {
                                                notifyWatchers(rootRelativeUrl);
                                            }
                                        });
                                    });
                                } catch (err) {
                                    console.error("Error trying to watch " + dirName + " using fs.watch: " + err.code);
                                    if (options.debug) {
                                        console.error(err.stack);
                                    }
                                }
                            }
                        } else {
                            if (options.debug) {
                                console.warn('Starting to watch ' + fileName + ' using fs.watch');
                            }
                            try {
                                var fsWatcher = fs.watch(fileName, function handleWatchNotification(eventName) {
                                    // Resubscribe if the file is renamed (Mac OSX):
                                    if (eventName === 'rename') {
                                        fsWatcher.close();
                                        fsWatcher = fs.watch(fileName, handleWatchNotification);
                                    }
                                    if (options.mtime) {
                                        notifyWatchersIfMtimeIncreased(rootRelativeUrl, fileName);
                                    } else {
                                        notifyWatchers(rootRelativeUrl);
                                    }
                                });
                            } catch (err) {
                                console.error("Error trying to watch " + fileName + " using fs.watch: " + err.code);
                                if (options.debug) {
                                    console.error(err.stack);
                                }
                            }
                        }
                    } else {
                        // TODO: Poll the upstream server if the file isn't found on disc (or no root is specified)
                        if (options.debug) {
                            console.warn('Starting to watch ' + fileName + ' using fs.watchFile');
                        }
                        try {
                            fs.watchFile(fileName, function (currStat, prevStat) {
                                if (options.debug) {
                                    console.warn('fs.watchFile: ' + fileName + ' changed on disc');
                                }
                                if (currStat.mtime.getTime() !== prevStat.mtime.getTime()) {
                                    notifyWatchers(rootRelativeUrl);
                                }
                            });
                        } catch (err) {
                            console.error("Error trying to watch " + fileName + " using fs.watchFile: " + err.code);
                            if (options.debug) {
                                console.error(err.stack);
                            }
                        }
                    }
                }
            });
        }).on('disconnect', function () {
            if (options.debug) {
                console.warn('client disconnected');
            }
            Object.keys(clientsByRootRelativeUrl).forEach(function (rootRelativeUrl) {
                var clients = clientsByRootRelativeUrl[rootRelativeUrl],
                    clientIndex = clients.indexOf(client);
                if (clientIndex !== -1) {
                    clients.splice(clientIndex, 1);
                }
            });
        });
    });
};
<commit_msg>Normalize urls when registering watchers before resolving their location on disk<commit_after>var fs = require('fs'),
    path = require('path');

function clientToString(client) {
    return client.handshake.address.address + ':' + client.handshake.address.port;
}

module.exports = function (app, options, sio) {
    var io = sio.listen(app, { log: !options.silentSocketIo }),
        clientsByRootRelativeUrl = {},
        rootRelativeUrlsByFileName = {}, // Map to translate file names reported by fs.watch into known root relative urls
        isWatchedByDirName = {},
        mtimeByFileName = {};

    function notifyWatchers(rootRelativeUrl) {
        if (rootRelativeUrl in clientsByRootRelativeUrl) {
            if (options.debug && clientsByRootRelativeUrl[rootRelativeUrl].length) {
                console.warn('  Notifying ' + clientsByRootRelativeUrl[rootRelativeUrl].length + ' watcher(s):\n  ' +
                             clientsByRootRelativeUrl[rootRelativeUrl].map(clientToString).join('\n  '));
            }
            clientsByRootRelativeUrl[rootRelativeUrl].forEach(function (client) {
                client.emit('change', rootRelativeUrl);
            });
        }
    }

    function notifyWatchersIfMtimeIncreased(rootRelativeUrl, fileName) {
        if (rootRelativeUrl in clientsByRootRelativeUrl) {
            if (!(fileName in mtimeByFileName)) {
                mtimeByFileName[fileName] = Infinity;
                notifyWatchers(rootRelativeUrl);
            } else {
                fs.stat(fileName, function (err, stats) {
                    if (err) {
                        return;
                    }
                    if (stats.mtime > mtimeByFileName[fileName]) {
                        notifyWatchers(rootRelativeUrl);
                    } else if (options.debug) {
                        console.warn('Suppressing notification for ' + fileName + ', the mtime of the file is still the same');
                    }
                    mtimeByFileName[fileName] = stats.mtime;
                });
            }
        }
    }

    if (!options.debug) {
        io.set('log level', 1);
    }
    io.sockets.on('connection', function (client) {
        if (options.debug) {
            console.warn('Client ' + clientToString(client) + ' connected');
        }
        client.on('watch', function (rootRelativeUrls) {
            if (options.debug) {
                console.warn('Client subscribed to ' + rootRelativeUrls.length + ' file(s):');
            }
            rootRelativeUrls.forEach(function (rootRelativeUrl) {
                rootRelativeUrl = path.normalize(rootRelativeUrl);

                if (options.debug) {
                    console.warn('  ' + rootRelativeUrl);
                }
                var mappedUrl = rootRelativeUrl.replace(/\?.*$/, '');
                if (options.mappings) {
                    Object.keys(options.mappings).every(function (url) {
                        if (mappedUrl.indexOf(url) === 0) {
                            mappedUrl = mappedUrl.replace(url, options.mappings[url]);
                            if (options.debug) {
                                console.warn('    => rewritten to ' + mappedUrl);
                            }
                            return false;
                        }
                        return true;
                    });
                }

                if (/\/$/.test(mappedUrl)) {
                    mappedUrl += 'index.html';
                }

                // Trim the leading slash if it exists
                mappedUrl = mappedUrl[0] === '/' ? mappedUrl.substr(1) : mappedUrl;

                var rootRelativePath = decodeURIComponent(mappedUrl),
                    fileName = path.resolve(options.root, rootRelativePath);

                rootRelativeUrlsByFileName[fileName] = rootRelativeUrlsByFileName[fileName] || [];
                if (rootRelativeUrlsByFileName[fileName].indexOf(rootRelativeUrl) === -1) {
                    rootRelativeUrlsByFileName[fileName].push(rootRelativeUrl);
                }
                if (rootRelativeUrl in clientsByRootRelativeUrl) {
                    clientsByRootRelativeUrl[rootRelativeUrl].push(client);
                } else {
                    clientsByRootRelativeUrl[rootRelativeUrl] = [client];
                    if (fs.watch && !options.watchfile) {
                        if (process.platform === 'linux' || process.platform === 'win32') {
                            var dirName = path.dirname(fileName);
                            if (!isWatchedByDirName[dirName]) {
                                isWatchedByDirName[dirName] = true;
                                if (options.debug) {
                                    console.warn('Starting to watch ' + dirName + ' using fs.watch');
                                }
                                try {
                                    fs.watch(dirName, function (eventName, fileName) {
                                        fileName = path.resolve(dirName, fileName); // Absolutify fileName
                                        if (options.debug) {
                                            console.warn('fs.watch: ' + eventName + ' ' + fileName);
                                        }
                                        (rootRelativeUrlsByFileName[fileName] || []).forEach(function (rootRelativeUrl) {
                                            if (options.mtime) {
                                                notifyWatchersIfMtimeIncreased(rootRelativeUrl, fileName);
                                            } else {
                                                notifyWatchers(rootRelativeUrl);
                                            }
                                        });
                                    });
                                } catch (err) {
                                    console.error("Error trying to watch " + dirName + " using fs.watch: " + err.code);
                                    if (options.debug) {
                                        console.error(err.stack);
                                    }
                                }
                            }
                        } else {
                            if (options.debug) {
                                console.warn('Starting to watch ' + fileName + ' using fs.watch');
                            }
                            try {
                                var fsWatcher = fs.watch(fileName, function handleWatchNotification(eventName) {
                                    // Resubscribe if the file is renamed (Mac OSX):
                                    if (eventName === 'rename') {
                                        fsWatcher.close();
                                        fsWatcher = fs.watch(fileName, handleWatchNotification);
                                    }
                                    if (options.mtime) {
                                        notifyWatchersIfMtimeIncreased(rootRelativeUrl, fileName);
                                    } else {
                                        notifyWatchers(rootRelativeUrl);
                                    }
                                });
                            } catch (err) {
                                console.error("Error trying to watch " + fileName + " using fs.watch: " + err.code);
                                if (options.debug) {
                                    console.error(err.stack);
                                }
                            }
                        }
                    } else {
                        // TODO: Poll the upstream server if the file isn't found on disc (or no root is specified)
                        if (options.debug) {
                            console.warn('Starting to watch ' + fileName + ' using fs.watchFile');
                        }
                        try {
                            fs.watchFile(fileName, function (currStat, prevStat) {
                                if (options.debug) {
                                    console.warn('fs.watchFile: ' + fileName + ' changed on disc');
                                }
                                if (currStat.mtime.getTime() !== prevStat.mtime.getTime()) {
                                    notifyWatchers(rootRelativeUrl);
                                }
                            });
                        } catch (err) {
                            console.error("Error trying to watch " + fileName + " using fs.watchFile: " + err.code);
                            if (options.debug) {
                                console.error(err.stack);
                            }
                        }
                    }
                }
            });
        }).on('disconnect', function () {
            if (options.debug) {
                console.warn('client disconnected');
            }
            Object.keys(clientsByRootRelativeUrl).forEach(function (rootRelativeUrl) {
                var clients = clientsByRootRelativeUrl[rootRelativeUrl],
                    clientIndex = clients.indexOf(client);
                if (clientIndex !== -1) {
                    clients.splice(clientIndex, 1);
                }
            });
        });
    });
};
<|endoftext|>


<commit_before>import createComponentCard from '../utils/create-component-card';

// map card names to component names
export const CARD_COMPONENT_MAP = {
    hr: 'koenig-card-hr',
    image: 'koenig-card-image',
    markdown: 'koenig-card-markdown',
    'card-markdown': 'koenig-card-markdown', // backwards-compat with markdown editor
    html: 'koenig-card-html',
    code: 'koenig-card-code',
    embed: 'koenig-card-embed',
    bookmark: 'koenig-card-bookmark',
    gallery: 'koenig-card-gallery',
    email: 'koenig-card-email'
};

// map card names to generic icons (used for ghost elements when dragging)
export const CARD_ICON_MAP = {
    hr: 'koenig/kg-card-type-divider',
    image: 'koenig/kg-card-type-image',
    markdown: 'koenig/kg-card-type-markdown',
    'card-markdown': 'koenig/kg-card-type-markdown',
    html: 'koenig/kg-card-type-html',
    code: 'koenig/kg-card-type-gen-embed',
    embed: 'koenig/kg-card-type-gen-embed',
    bookmark: 'koenig/kg-card-type-bookmark',
    gallery: 'koenig/kg-card-type-gallery',
    email: 'koenig/kg-card-type-gen-embed'
};

// TODO: move koenigOptions directly into cards now that card components register
// themselves so that they are available on card.component
export default [
    createComponentCard('card-markdown'), // backwards-compat with markdown editor
    createComponentCard('code', {deleteIfEmpty: 'payload.code'}),
    createComponentCard('embed', {hasEditMode: false, deleteIfEmpty: 'payload.html'}),
    createComponentCard('bookmark', {hasEditMode: false, deleteIfEmpty: 'payload.metadata'}),
    createComponentCard('hr', {hasEditMode: false, selectAfterInsert: false}),
    createComponentCard('html', {deleteIfEmpty: 'payload.html'}),
    createComponentCard('image', {hasEditMode: false, deleteIfEmpty(card) {
        return card.payload.imageSelector && !card.payload.src;
    }}),
    createComponentCard('markdown', {deleteIfEmpty: 'payload.markdown'}),
    createComponentCard('gallery', {hasEditMode: false}),
    createComponentCard('email', {deleteIfEmpty: 'payload.html'})
];

export const CARD_MENU = [
    {
        title: 'Primary',
        rowLength: 1,
        items: [{
            label: 'Image',
            icon: 'koenig/kg-card-type-image',
            iconClass: 'kg-card-type-native',
            matches: ['image', 'img'],
            type: 'card',
            replaceArg: 'image',
            params: ['src'],
            payload: {
                triggerBrowse: true
            }
        },
        {
            label: 'Markdown',
            icon: 'koenig/kg-card-type-markdown',
            iconClass: 'kg-card-type-native',
            matches: ['markdown', 'md'],
            type: 'card',
            replaceArg: 'markdown'
        },
        {
            label: 'HTML',
            icon: 'koenig/kg-card-type-html',
            iconClass: 'kg-card-type-native',
            matches: ['html'],
            type: 'card',
            replaceArg: 'html'
        },
        {
            label: 'Gallery',
            icon: 'koenig/kg-card-type-gallery',
            iconClass: 'kg-card-type-native',
            matches: ['gallery'],
            type: 'card',
            replaceArg: 'gallery'
        },
        {
            label: 'Divider',
            icon: 'koenig/kg-card-type-divider',
            iconClass: 'kg-card-type-native',
            matches: ['divider', 'horizontal-rule', 'hr'],
            type: 'card',
            replaceArg: 'hr'
        },
        {
            label: 'Bookmark',
            icon: 'koenig/kg-card-type-bookmark',
            matches: ['bookmark'],
            type: 'card',
            replaceArg: 'bookmark',
            params: ['url']
        },
        {
            label: 'Email',
            icon: 'koenig/kg-card-type-email',
            matches: ['email'],
            type: 'card',
            replaceArg: 'email'
        }]
    },
    {
        title: 'Embed',
        rowLength: 1,
        items: [{
            label: 'YouTube',
            icon: 'koenig/kg-card-type-youtube',
            matches: ['youtube'],
            type: 'card',
            replaceArg: 'embed',
            params: ['url']
        },
        {
            label: 'Twitter',
            icon: 'koenig/kg-card-type-twitter',
            matches: ['twitter'],
            type: 'card',
            replaceArg: 'embed',
            params: ['url']
        },
        {
            label: 'Instagram',
            icon: 'koenig/kg-card-type-instagram',
            matches: ['instagram'],
            type: 'card',
            replaceArg: 'embed',
            params: ['url']
        },
        {
            label: 'Unsplash',
            icon: 'koenig/kg-card-type-unsplash',
            iconClass: 'kg-card-type-unsplash',
            matches: ['unsplash'],
            type: 'card',
            replaceArg: 'image',
            params: ['searchTerm'],
            payload: {
                imageSelector: 'unsplash'
            }
        },
        {
            label: 'Vimeo',
            icon: 'koenig/kg-card-type-vimeo',
            matches: ['vimeo'],
            type: 'card',
            replaceArg: 'embed',
            params: ['url']
        },
        {
            label: 'CodePen',
            icon: 'koenig/kg-card-type-codepen',
            iconClass: 'kg-card-type-codepen',
            matches: ['codepen'],
            type: 'card',
            replaceArg: 'embed',
            params: ['url']
        },
        {
            label: 'Spotify',
            icon: 'koenig/kg-card-type-spotify',
            matches: ['spotify'],
            type: 'card',
            replaceArg: 'embed',
            params: ['url']
        },
        {
            label: 'SoundCloud',
            icon: 'koenig/kg-card-type-soundcloud',
            matches: ['soundcloud'],
            type: 'card',
            replaceArg: 'embed',
            params: ['url']
        },
        {
            label: 'Other...',
            icon: 'koenig/kg-card-type-other',
            iconClass: 'kg-card-type-native',
            matches: ['embed'],
            type: 'card',
            replaceArg: 'embed',
            params: ['url']
        }]
    }
];
<commit_msg>Removed Instagram embed entry from the card menu<commit_after>import createComponentCard from '../utils/create-component-card';

// map card names to component names
export const CARD_COMPONENT_MAP = {
    hr: 'koenig-card-hr',
    image: 'koenig-card-image',
    markdown: 'koenig-card-markdown',
    'card-markdown': 'koenig-card-markdown', // backwards-compat with markdown editor
    html: 'koenig-card-html',
    code: 'koenig-card-code',
    embed: 'koenig-card-embed',
    bookmark: 'koenig-card-bookmark',
    gallery: 'koenig-card-gallery',
    email: 'koenig-card-email'
};

// map card names to generic icons (used for ghost elements when dragging)
export const CARD_ICON_MAP = {
    hr: 'koenig/kg-card-type-divider',
    image: 'koenig/kg-card-type-image',
    markdown: 'koenig/kg-card-type-markdown',
    'card-markdown': 'koenig/kg-card-type-markdown',
    html: 'koenig/kg-card-type-html',
    code: 'koenig/kg-card-type-gen-embed',
    embed: 'koenig/kg-card-type-gen-embed',
    bookmark: 'koenig/kg-card-type-bookmark',
    gallery: 'koenig/kg-card-type-gallery',
    email: 'koenig/kg-card-type-gen-embed'
};

// TODO: move koenigOptions directly into cards now that card components register
// themselves so that they are available on card.component
export default [
    createComponentCard('card-markdown'), // backwards-compat with markdown editor
    createComponentCard('code', {deleteIfEmpty: 'payload.code'}),
    createComponentCard('embed', {hasEditMode: false, deleteIfEmpty: 'payload.html'}),
    createComponentCard('bookmark', {hasEditMode: false, deleteIfEmpty: 'payload.metadata'}),
    createComponentCard('hr', {hasEditMode: false, selectAfterInsert: false}),
    createComponentCard('html', {deleteIfEmpty: 'payload.html'}),
    createComponentCard('image', {hasEditMode: false, deleteIfEmpty(card) {
        return card.payload.imageSelector && !card.payload.src;
    }}),
    createComponentCard('markdown', {deleteIfEmpty: 'payload.markdown'}),
    createComponentCard('gallery', {hasEditMode: false}),
    createComponentCard('email', {deleteIfEmpty: 'payload.html'})
];

export const CARD_MENU = [
    {
        title: 'Primary',
        rowLength: 1,
        items: [{
            label: 'Image',
            icon: 'koenig/kg-card-type-image',
            iconClass: 'kg-card-type-native',
            matches: ['image', 'img'],
            type: 'card',
            replaceArg: 'image',
            params: ['src'],
            payload: {
                triggerBrowse: true
            }
        },
        {
            label: 'Markdown',
            icon: 'koenig/kg-card-type-markdown',
            iconClass: 'kg-card-type-native',
            matches: ['markdown', 'md'],
            type: 'card',
            replaceArg: 'markdown'
        },
        {
            label: 'HTML',
            icon: 'koenig/kg-card-type-html',
            iconClass: 'kg-card-type-native',
            matches: ['html'],
            type: 'card',
            replaceArg: 'html'
        },
        {
            label: 'Gallery',
            icon: 'koenig/kg-card-type-gallery',
            iconClass: 'kg-card-type-native',
            matches: ['gallery'],
            type: 'card',
            replaceArg: 'gallery'
        },
        {
            label: 'Divider',
            icon: 'koenig/kg-card-type-divider',
            iconClass: 'kg-card-type-native',
            matches: ['divider', 'horizontal-rule', 'hr'],
            type: 'card',
            replaceArg: 'hr'
        },
        {
            label: 'Bookmark',
            icon: 'koenig/kg-card-type-bookmark',
            matches: ['bookmark'],
            type: 'card',
            replaceArg: 'bookmark',
            params: ['url']
        },
        {
            label: 'Email',
            icon: 'koenig/kg-card-type-email',
            matches: ['email'],
            type: 'card',
            replaceArg: 'email'
        }]
    },
    {
        title: 'Embed',
        rowLength: 1,
        items: [{
            label: 'YouTube',
            icon: 'koenig/kg-card-type-youtube',
            matches: ['youtube'],
            type: 'card',
            replaceArg: 'embed',
            params: ['url']
        },
        {
            label: 'Twitter',
            icon: 'koenig/kg-card-type-twitter',
            matches: ['twitter'],
            type: 'card',
            replaceArg: 'embed',
            params: ['url']
        },
        {
            label: 'Unsplash',
            icon: 'koenig/kg-card-type-unsplash',
            iconClass: 'kg-card-type-unsplash',
            matches: ['unsplash'],
            type: 'card',
            replaceArg: 'image',
            params: ['searchTerm'],
            payload: {
                imageSelector: 'unsplash'
            }
        },
        {
            label: 'Vimeo',
            icon: 'koenig/kg-card-type-vimeo',
            matches: ['vimeo'],
            type: 'card',
            replaceArg: 'embed',
            params: ['url']
        },
        {
            label: 'CodePen',
            icon: 'koenig/kg-card-type-codepen',
            iconClass: 'kg-card-type-codepen',
            matches: ['codepen'],
            type: 'card',
            replaceArg: 'embed',
            params: ['url']
        },
        {
            label: 'Spotify',
            icon: 'koenig/kg-card-type-spotify',
            matches: ['spotify'],
            type: 'card',
            replaceArg: 'embed',
            params: ['url']
        },
        {
            label: 'SoundCloud',
            icon: 'koenig/kg-card-type-soundcloud',
            matches: ['soundcloud'],
            type: 'card',
            replaceArg: 'embed',
            params: ['url']
        },
        {
            label: 'Other...',
            icon: 'koenig/kg-card-type-other',
            iconClass: 'kg-card-type-native',
            matches: ['embed'],
            type: 'card',
            replaceArg: 'embed',
            params: ['url']
        }]
    }
];
<|endoftext|>


<commit_before>import t from 't-component';
import o from 'component-dom';
import debug from 'debug';
import citizen from '../citizen/citizen.js';
import config from '../config/config.js';
import request from '../request/request.js';
import SideComments from 'side-comments';
import View from '../view/view.js';
import template from './template.jade';

let log = debug('democracyos:proposal-clause');

export default class ProposalClauses extends View {

  constructor (law) {
    super(template, { clauses: law.clauses });
    this.law = law;
    this.loadComments();
  }

  loadComments () {
    this
      .load()
      .ready(this.sideComments.bind(this));
  }

  load (clauseId) {
    let self = this;

    request
      .get('/api/law/:id/clause-comments'.replace(':id', this.law.id))
      .end((err, res) => {
        if (res.status !== 200) ; //log error

        self.comments = res.body;

        request
          .get('/api/law/:id/summary-comments'.replace(':id', self.law.id))
          .end((err, res) => {
            if (res.status !== 200) ; //log error

            self.comments = self.comments.concat(res.body);
            self.state('loaded');
          });
      });

    return this;
  }

  sideComments  () {
    let self = this;
    let userComment = citizen.id ? {
      id: citizen.id,
      avatarUrl: citizen.profilePictureUrl ? citizen.profilePictureUrl : citizen.gravatar,
      name: citizen.firstName + ' ' + citizen.lastName,
      isAdmin: citizen.staff
    }
    : null;

    let sections = [];
    let law = this.law;
    let summary = law.summary;
    let paragraphs = summary.split("\n");
    let i = 0;

    paragraphs.forEach((paragraph) => {
      if ('' != paragraph) {
        let section = {};
        section.sectionId = law.id + '-' + i;

        let filterBySection = comment => comment.reference == law.id + '-' + i;

        section.comments = self.comments.filter(filterBySection).map(self.formatComment);
        sections.push(section);
      }
      i++;
    });

    law.clauses.forEach((clause) => {
      let section = {};
      section.sectionId = clause.id;

      function filterBySection(comment) {
        return comment.reference == clause.id;
      }

      section.comments = self.comments.filter(filterBySection).map(self.formatComment);
      sections.push(section);
    });
    // FIXME
    let sideComments = self.sideComments = new SideComments('.commentable-container', userComment, sections, { trans: t, locale: config.locale });
    sideComments.on('commentPosted', self.onClauseCommentPosted.bind(self));
    sideComments.on('commentUpdated', self.onClauseCommentUpdated.bind(self));
    sideComments.on('commentDeleted', self.onClauseCommentDeleted.bind(self));
  }

  formatComment (comment) {
    comment.authorAvatarUrl = comment.author.profilePictureUrl ? comment.author.profilePictureUrl : comment.author.gravatar;
    comment.authorName = comment.author.fullName;
    comment.authorId = comment.author.id;
    comment.comment = comment.text;
    comment.sectionId = comment.reference;
    return comment;
  }

  onClauseCommentPosted (comment) {
    let self = this;
    let clauseComment = {};
    clauseComment.reference = comment.sectionId;
    if (~comment.sectionId.indexOf('-')) {
      clauseComment.context = 'summary';
    } else {
      clauseComment.context = 'clause';
    }
    clauseComment.text = comment.comment;
    request
    .post(
      '/api/:context/:reference/comment'
      .replace(':context', 'law')
      .replace(':reference', comment.sectionId))
    .send({ comment: clauseComment })
    .end(function(err, res) {
      if (err) return log('Error when posting clause comment %s', err);
      let comment = self.formatComment(res.body);
      self.sideComments.insertComment(comment);
    });
  }

  onClauseCommentUpdated (comment) {
    let self = this;

    request
    .put(
      '/api/comment/' + comment.id)
    .send({ text: comment.comment, reference: comment.sectionId })
    .end(function(err, res) {
      if (err) return log('Error when putting clause comment %s', err);
      let comment = self.formatComment(res.body);
      self.sideComments.replaceComment(comment);
    });
  };

  onClauseCommentDeleted (comment) {
    let self = this;
    request
      .del('/api/comment/:id'
        .replace(':id', comment.id))
      .end(function(err, res) {
        if (err) return log('Fetch error: %s', err);
        if (!res.ok) err = res.error, log('Fetch error: %s', err);
        if (res.body && res.body.error) err = res.body.error, log('Fetch response error: %s', err);

        self.sideComments.removeComment(comment.sectionId, comment.id);
      });
  }
}
<commit_msg>[proposal-clauses] Deleted forgotten comment<commit_after>import t from 't-component';
import o from 'component-dom';
import debug from 'debug';
import citizen from '../citizen/citizen.js';
import config from '../config/config.js';
import request from '../request/request.js';
import SideComments from 'side-comments';
import View from '../view/view.js';
import template from './template.jade';

let log = debug('democracyos:proposal-clause');

export default class ProposalClauses extends View {

  constructor (law) {
    super(template, { clauses: law.clauses });
    this.law = law;
    this.loadComments();
  }

  loadComments () {
    this
      .load()
      .ready(this.sideComments.bind(this));
  }

  load (clauseId) {
    let self = this;

    request
      .get('/api/law/:id/clause-comments'.replace(':id', this.law.id))
      .end((err, res) => {
        if (res.status !== 200) ; //log error

        self.comments = res.body;

        request
          .get('/api/law/:id/summary-comments'.replace(':id', self.law.id))
          .end((err, res) => {
            if (res.status !== 200) ; //log error

            self.comments = self.comments.concat(res.body);
            self.state('loaded');
          });
      });

    return this;
  }

  sideComments  () {
    let self = this;
    let userComment = citizen.id ? {
      id: citizen.id,
      avatarUrl: citizen.profilePictureUrl ? citizen.profilePictureUrl : citizen.gravatar,
      name: citizen.firstName + ' ' + citizen.lastName,
      isAdmin: citizen.staff
    }
    : null;

    let sections = [];
    let law = this.law;
    let summary = law.summary;
    let paragraphs = summary.split("\n");
    let i = 0;

    paragraphs.forEach((paragraph) => {
      if ('' != paragraph) {
        let section = {};
        section.sectionId = law.id + '-' + i;

        let filterBySection = comment => comment.reference == law.id + '-' + i;

        section.comments = self.comments.filter(filterBySection).map(self.formatComment);
        sections.push(section);
      }
      i++;
    });

    law.clauses.forEach((clause) => {
      let section = {};
      section.sectionId = clause.id;

      function filterBySection(comment) {
        return comment.reference == clause.id;
      }

      section.comments = self.comments.filter(filterBySection).map(self.formatComment);
      sections.push(section);
    });

    let sideComments = self.sideComments = new SideComments('.commentable-container', userComment, sections, { trans: t, locale: config.locale });
    sideComments.on('commentPosted', self.onClauseCommentPosted.bind(self));
    sideComments.on('commentUpdated', self.onClauseCommentUpdated.bind(self));
    sideComments.on('commentDeleted', self.onClauseCommentDeleted.bind(self));
  }

  formatComment (comment) {
    comment.authorAvatarUrl = comment.author.profilePictureUrl ? comment.author.profilePictureUrl : comment.author.gravatar;
    comment.authorName = comment.author.fullName;
    comment.authorId = comment.author.id;
    comment.comment = comment.text;
    comment.sectionId = comment.reference;
    return comment;
  }

  onClauseCommentPosted (comment) {
    let self = this;
    let clauseComment = {};
    clauseComment.reference = comment.sectionId;
    if (~comment.sectionId.indexOf('-')) {
      clauseComment.context = 'summary';
    } else {
      clauseComment.context = 'clause';
    }
    clauseComment.text = comment.comment;
    request
    .post(
      '/api/:context/:reference/comment'
      .replace(':context', 'law')
      .replace(':reference', comment.sectionId))
    .send({ comment: clauseComment })
    .end(function(err, res) {
      if (err) return log('Error when posting clause comment %s', err);
      let comment = self.formatComment(res.body);
      self.sideComments.insertComment(comment);
    });
  }

  onClauseCommentUpdated (comment) {
    let self = this;

    request
    .put(
      '/api/comment/' + comment.id)
    .send({ text: comment.comment, reference: comment.sectionId })
    .end(function(err, res) {
      if (err) return log('Error when putting clause comment %s', err);
      let comment = self.formatComment(res.body);
      self.sideComments.replaceComment(comment);
    });
  };

  onClauseCommentDeleted (comment) {
    let self = this;
    request
      .del('/api/comment/:id'
        .replace(':id', comment.id))
      .end(function(err, res) {
        if (err) return log('Fetch error: %s', err);
        if (!res.ok) err = res.error, log('Fetch error: %s', err);
        if (res.body && res.body.error) err = res.body.error, log('Fetch response error: %s', err);

        self.sideComments.removeComment(comment.sectionId, comment.id);
      });
  }
}
<|endoftext|>


<commit_before>/**
 * Module dependencies.
 */

var page = require('page')
  , domify = require('domify')
  , delegates = require('delegates')
  , classes = require('classes')
  , request = require('superagent')
  , options = require('./options')
  , closest = require('closest')
  , autocomplete = require('autocomplete')
  , Enumerable = require('enumerable')
  , Chart = require('Chart.js')
  , log = require('debug')('proposal-options');

/**
 * Expose ProposalOptions.
 */

module.exports = ProposalOptions;

var buttonsBox = delegationBox = null;

/**
 * Proposal Options view
 *
 * @param {Array} proposals list of proposals
 * @param {Object} selected proposal object
 * @return {ProposalOptions} `ProposalOptions` instance.
 * @api public
 */

function ProposalOptions (proposal, citizen) {
  if (!(this instanceof ProposalOptions)) {
    return new ProposalOptions(proposals);
  }

  this.proposal = proposal;
  this.citizen = citizen;
  this.options = domify(options({ proposal: proposal, citizen: citizen }));
  this.events = delegates(this.options, this);
  // this.events.bind('click a.change-vote small', 'showvote');
  this.events.bind('click .vote-box .direct-vote .vote-option span', 'vote');
  this.events.bind('click .vote-box .proxy-vote .vote-option span', 'proxy');
  this.events.bind('click .vote-box .meta-data a.change-vote small', 'changevote');

  this.buttonsBox = this.options.querySelector('.vote-box .vote-options');
  this.delegationBox = this.options.querySelector('.vote-box .delegation-box');

  this.delegationInput = this.options.querySelector('.vote-box .delegation-box .delegation-input');
  this.initializeAutocomplete(this.delegationInput);
}

/**
 * Toggle voting buttons
 *
 * @param {Object} toggle element whose visibility is to be toggled
 * @return {ProposalOptions} instance of `ProposalOptions`
 * @api public
 */

ProposalOptions.prototype.toggleVisibility = function(el) {

  classes(el).toggle('hide');
}

/**
 * Vote for option
 *
 * @param {Object} ev event
 * @api private
 */

ProposalOptions.prototype.vote = function(ev) {
  ev.preventDefault();

  var target = closest(ev.target, '.vote-option')
    , id = target.getAttribute('data-proposal')
    , value;

  if (classes(target).has('vote-no')) {
    value = 'negative';
  } else if (classes(target).has('vote-yes')) {
    value = 'positive';
  } else if (classes(target).has('vote-abstain')) {
    value = 'neutral';
  }

  this.post(
    '/api/law/:id/vote'.replace(':id', id),
    {value:value},
    function(res) { page(window.location.pathname) }
  );
}

/**
 * Event handler for clicking the delegation button
 * @param  {Object} ev event
 * @return {ProposalOptions}    instance of `ProposalOptions`
 * @api private
 */
ProposalOptions.prototype.proxy = function(ev) {
  ev.preventDefault();

  this.toggleVisibility(this.buttonsBox);
  this.toggleVisibility(this.delegationBox);
}

ProposalOptions.prototype.delegate = function(ev) {
  ev.preventDefault();

  //CONTINUE HERE - gvilarino
  // var target = closest
  //get stuff from event

  this.post(
    '/api/delegation/create',
    { tag: 'tag', tid: 'trusteeId' },
    function(res) { page(windows.location.pathname) }
  );
}

/**
 * Change vote
 *
 * @param {Object} ev event
 * @api private
 */

ProposalOptions.prototype.changevote = function(ev) {
  ev.preventDefault();

  classes(this.buttonsBox).toggle('hide', false);
};

ProposalOptions.prototype.post = function(path, payload, onSucces, onBodyError, onError) {
  request
  .post(path)
  .set('Accept', 'application/json')
  .send(payload)
  .end(function (err, res) {
    if (err) {
      log(err);
      if (onError) {
        return onError(err);
      }
      else return;
    };

    if (res.body.error) {
      log(res.body.error);
      if (onBodyError) {
        return onBodyError();
      } else if (res.body.action && res.body.action.redirect) {
        return window.location.replace(res.body.action.redirect);
      };
    }

    if (onSucces) {
      return onSucces(res);
    }
    else return;
  });
};

/**
 * Render options
 *
 * @return {NodeElement} proposals options
 * @api public
 */

ProposalOptions.prototype.render = function() {
  // this.renderChart(); UNDONE: from previous layout; was breaking rendering
  return this.options;
}

ProposalOptions.prototype.initializeAutocomplete = function(input) {
  var me = this.citizen;

  autocomplete(input, '/api/citizen/search?q=:q')
  .label('fullName')
  .value('id')
  .parse(function(data){
    return Enumerable(data).reject('id == "' + me.id + '"').value();
  })
  .format(function(label, q) {
    //Format results displayed in select-box
    var r = new RegExp('(?:' + q + ')', 'i');
    return label.replace(r, '$&');
  })
  .on('select', function(id) {
    console.log(id);
  })
};

/**
 * Render chart into options block
 *
 * @return {ProposalOptions} `ProposalOptions` instance.
 * @api public
 */

ProposalOptions.prototype.renderChart = function() {
  var container = this.options.querySelector('#vote-chart');
  var vote = this.proposal.vote;
  var data = [];

  if (vote.census.length) {
    data.push({
      value: vote.positive.length,
      color: "#a4cb53",
      label: "YES",
      labelColor: "white",
      labelAlign: "center"
    });
    data.push({
      value: vote.negative.length,
      color: "#d95e59",
      label: "NO",
      labelColor: "white",
      labelAlign: "center"
    });

    new Chart(container.getContext('2d')).Pie(data);
  } else {
    var p = document.createElement('p');
    p.classList.add('alert');
    p.classList.add('alert-info');
    p.innerHTML = "No voting yet."
    container.parentNode.insertBefore(p, container);
  }
}<commit_msg>[proposal-options] - Correct use of .toggle() from component/classes<commit_after>/**
 * Module dependencies.
 */

var page = require('page')
  , domify = require('domify')
  , delegates = require('delegates')
  , classes = require('classes')
  , request = require('superagent')
  , options = require('./options')
  , closest = require('closest')
  , autocomplete = require('autocomplete')
  , Enumerable = require('enumerable')
  , Chart = require('Chart.js')
  , log = require('debug')('proposal-options');

/**
 * Expose ProposalOptions.
 */

module.exports = ProposalOptions;

var buttonsBox = delegationBox = null;

/**
 * Proposal Options view
 *
 * @param {Array} proposals list of proposals
 * @param {Object} selected proposal object
 * @return {ProposalOptions} `ProposalOptions` instance.
 * @api public
 */

function ProposalOptions (proposal, citizen) {
  if (!(this instanceof ProposalOptions)) {
    return new ProposalOptions(proposals);
  }

  this.proposal = proposal;
  this.citizen = citizen;
  this.options = domify(options({ proposal: proposal, citizen: citizen }));
  this.events = delegates(this.options, this);
  // this.events.bind('click a.change-vote small', 'showvote');
  this.events.bind('click .vote-box .direct-vote .vote-option span', 'vote');
  this.events.bind('click .vote-box .proxy-vote .vote-option span', 'proxy');
  this.events.bind('click .vote-box .meta-data a.change-vote small', 'changevote');

  this.buttonsBox = this.options.querySelector('.vote-box .vote-options');
  this.delegationBox = this.options.querySelector('.vote-box .delegation-box');

  this.delegationInput = this.options.querySelector('.vote-box .delegation-box .delegation-input');
  this.initializeAutocomplete(this.delegationInput);
}

/**
 * Toggle voting buttons
 *
 * @param {Object} toggle element whose visibility is to be toggled
 * @return {ProposalOptions} instance of `ProposalOptions`
 * @api public
 */

ProposalOptions.prototype.toggleVisibility = function(el) {

  classes(el).toggle('hide');
}

/**
 * Vote for option
 *
 * @param {Object} ev event
 * @api private
 */

ProposalOptions.prototype.vote = function(ev) {
  ev.preventDefault();

  var target = closest(ev.target, '.vote-option')
    , id = target.getAttribute('data-proposal')
    , value;

  if (classes(target).has('vote-no')) {
    value = 'negative';
  } else if (classes(target).has('vote-yes')) {
    value = 'positive';
  } else if (classes(target).has('vote-abstain')) {
    value = 'neutral';
  }

  this.post(
    '/api/law/:id/vote'.replace(':id', id),
    {value:value},
    function(res) { page(window.location.pathname) }
  );
}

/**
 * Event handler for clicking the delegation button
 * @param  {Object} ev event
 * @return {ProposalOptions}    instance of `ProposalOptions`
 * @api private
 */
ProposalOptions.prototype.proxy = function(ev) {
  ev.preventDefault();

  this.toggleVisibility(this.buttonsBox);
  this.toggleVisibility(this.delegationBox);
}

ProposalOptions.prototype.delegate = function(ev) {
  ev.preventDefault();

  //CONTINUE HERE - gvilarino
  // var target = closest
  //get stuff from event

  this.post(
    '/api/delegation/create',
    { tag: 'tag', tid: 'trusteeId' },
    function(res) { page(windows.location.pathname) }
  );
}

/**
 * Change vote
 *
 * @param {Object} ev event
 * @api private
 */

ProposalOptions.prototype.changevote = function(ev) {
  ev.preventDefault();

  classes(this.buttonsBox).toggle('hide');
};

ProposalOptions.prototype.post = function(path, payload, onSucces, onBodyError, onError) {
  request
  .post(path)
  .set('Accept', 'application/json')
  .send(payload)
  .end(function (err, res) {
    if (err) {
      log(err);
      if (onError) {
        return onError(err);
      }
      else return;
    };

    if (res.body.error) {
      log(res.body.error);
      if (onBodyError) {
        return onBodyError();
      } else if (res.body.action && res.body.action.redirect) {
        return window.location.replace(res.body.action.redirect);
      };
    }

    if (onSucces) {
      return onSucces(res);
    }
    else return;
  });
};

/**
 * Render options
 *
 * @return {NodeElement} proposals options
 * @api public
 */

ProposalOptions.prototype.render = function() {
  // this.renderChart(); UNDONE: from previous layout; was breaking rendering
  return this.options;
}

ProposalOptions.prototype.initializeAutocomplete = function(input) {
  var me = this.citizen;

  autocomplete(input, '/api/citizen/search?q=:q')
  .label('fullName')
  .value('id')
  .parse(function(data){
    return Enumerable(data).reject('id == "' + me.id + '"').value();
  })
  .format(function(label, q) {
    //Format results displayed in select-box
    var r = new RegExp('(?:' + q + ')', 'i');
    return label.replace(r, '$&');
  })
  .on('select', function(id) {
    console.log(id);
  })
};

/**
 * Render chart into options block
 *
 * @return {ProposalOptions} `ProposalOptions` instance.
 * @api public
 */

ProposalOptions.prototype.renderChart = function() {
  var container = this.options.querySelector('#vote-chart');
  var vote = this.proposal.vote;
  var data = [];

  if (vote.census.length) {
    data.push({
      value: vote.positive.length,
      color: "#a4cb53",
      label: "YES",
      labelColor: "white",
      labelAlign: "center"
    });
    data.push({
      value: vote.negative.length,
      color: "#d95e59",
      label: "NO",
      labelColor: "white",
      labelAlign: "center"
    });

    new Chart(container.getContext('2d')).Pie(data);
  } else {
    var p = document.createElement('p');
    p.classList.add('alert');
    p.classList.add('alert-info');
    p.innerHTML = "No voting yet."
    container.parentNode.insertBefore(p, container);
  }
}<|endoftext|>


<commit_before>import React, { Component } from "react";
import PropTypes from "prop-types";
import cx from "classnames";
import Icon from "@hig/icon";
import { AVAILABLE_NAMES as AVAILABLE_ICON_NAMES } from "@hig/icons";
import { types, AVAILABLE_TYPES } from "./types";
import "./icon-button.scss";

export default class IconButton extends Component {
  static propTypes = {
    /**
     * Prevents user actions on the button
     */
    disabled: PropTypes.bool,
    /**
     * Deprecated; use `name` or `svg` instead
     */
    icon: PropTypes.string,
    /**
     * Url button will navigate to when clicked
     */
    link: PropTypes.string,
    /**
     * Name of the icon to be used
     */
    name: PropTypes.oneOf(AVAILABLE_ICON_NAMES),
    /**
     * Called when user moves focus away from the button
     */
    onBlur: PropTypes.func,
    /**
     * Called when user clicks the button
     */
    onClick: PropTypes.func,
    /**
     * Called when user moves focus onto the button
     */
    onFocus: PropTypes.func,
    /**
     * Called when mouse begins to move over the button
     */
    onMouseEnter: PropTypes.func,
    /**
     * Called when mouse stops moving over the button
     */
    onMouseLeave: PropTypes.func,
    /**
     * SVG markup used for the icon
     */
    svg: PropTypes.string,
    /**
     * Title of the button for accessibility purposes
     */
    title: PropTypes.string.isRequired,
    /**
     * 'primary' or 'flat'; the style of the button
     */
    type: PropTypes.oneOf(AVAILABLE_TYPES)
  };

  static defaultProps = {
    type: types.PRIMARY
  };

  render() {
    const {
      disabled,
      link,
      onClick,
      onBlur,
      onFocus,
      onMouseEnter,
      onMouseLeave,
      title
    } = this.props;

    const iconButtonClasses = cx(
      "hig__icon-button",
      `hig__icon-button--${this.props.type}`,
      { "hig__icon-button--disabled": this.props.disabled }
    );

    const linkProps = link
      ? {
          tabIndex: disabled ? "-1" : "0",
          href: link
        }
      : {};

    const props = {
      className: iconButtonClasses,
      ...linkProps,
      onClick,
      onBlur,
      onFocus,
      onMouseEnter,
      onMouseLeave,
      title
    };

    const Element = this.props.link ? "a" : "button";

    return (
      <Element {...props}>

          <Icon
            svg={this.props.svg}
            name={this.props.name}
            nameOrSVG={this.props.icon}
          />

      </Element>
    );
  }
}
<commit_msg>fix: add icon css to icon-button<commit_after>import React, { Component } from "react";
import PropTypes from "prop-types";
import cx from "classnames";

import Icon from "@hig/icon";
import "@hig/icon/build/index.css";
import { AVAILABLE_NAMES as AVAILABLE_ICON_NAMES } from "@hig/icons";

import { types, AVAILABLE_TYPES } from "./types";
import "./icon-button.scss";

export default class IconButton extends Component {
  static propTypes = {
    /**
     * Prevents user actions on the button
     */
    disabled: PropTypes.bool,
    /**
     * Deprecated; use `name` or `svg` instead
     */
    icon: PropTypes.string,
    /**
     * Url button will navigate to when clicked
     */
    link: PropTypes.string,
    /**
     * Name of the icon to be used
     */
    name: PropTypes.oneOf(AVAILABLE_ICON_NAMES),
    /**
     * Called when user moves focus away from the button
     */
    onBlur: PropTypes.func,
    /**
     * Called when user clicks the button
     */
    onClick: PropTypes.func,
    /**
     * Called when user moves focus onto the button
     */
    onFocus: PropTypes.func,
    /**
     * Called when mouse begins to move over the button
     */
    onMouseEnter: PropTypes.func,
    /**
     * Called when mouse stops moving over the button
     */
    onMouseLeave: PropTypes.func,
    /**
     * SVG markup used for the icon
     */
    svg: PropTypes.string,
    /**
     * Title of the button for accessibility purposes
     */
    title: PropTypes.string.isRequired,
    /**
     * 'primary' or 'flat'; the style of the button
     */
    type: PropTypes.oneOf(AVAILABLE_TYPES)
  };

  static defaultProps = {
    type: types.PRIMARY
  };

  render() {
    const {
      disabled,
      link,
      onClick,
      onBlur,
      onFocus,
      onMouseEnter,
      onMouseLeave,
      title
    } = this.props;

    const iconButtonClasses = cx(
      "hig__icon-button",
      `hig__icon-button--${this.props.type}`,
      { "hig__icon-button--disabled": this.props.disabled }
    );

    const linkProps = link
      ? {
          tabIndex: disabled ? "-1" : "0",
          href: link
        }
      : {};

    const props = {
      className: iconButtonClasses,
      ...linkProps,
      onClick,
      onBlur,
      onFocus,
      onMouseEnter,
      onMouseLeave,
      title
    };

    const Element = this.props.link ? "a" : "button";

    return (
      <Element {...props}>

          <Icon
            svg={this.props.svg}
            name={this.props.name}
            nameOrSVG={this.props.icon}
          />

      </Element>
    );
  }
}
<|endoftext|>


<commit_before>import paramCase from 'param-case';
import pascalCase from 'pascal-case';
import commonjs from 'rollup-plugin-commonjs';
import nodeBuiltins from 'rollup-plugin-node-builtins';
import nodeGlobals from 'rollup-plugin-node-globals';
import nodeResolve from 'rollup-plugin-node-resolve';
import sourcemaps from 'rollup-plugin-sourcemaps';

const pkg = require('./package');

const moduleName = pascalCase(pkg.name)

export default {
  dest: `dist/${paramCase(pkg.name)}.es2015.js`,
  entry: 'dist/es2015/index.js',
  format: 'iife',
  moduleId: pkg.name,
  moduleName,
  // This may comes handy
  // onwarn(warning) {
  //   // Suppress this error message: "The 'this' keyword is equivalent to 'undefined' at the top level of an ES module, and has been rewritten"
  //   // https://github.com/rollup/rollup/issues/794
  //   if (warning.code === 'THIS_IS_UNDEFINED') {
  //     return
  //   }

  //   console.error(warning.message)
  // },
  plugins: [
    sourcemaps(),
    nodeResolve({
      jsnext: true
    }),
    nodeGlobals(),
    nodeBuiltins(),
    commonjs()
  ],
  sourceMap: true
};
<commit_msg>chore: update rollup config<commit_after>import paramCase from 'param-case';
import pascalCase from 'pascal-case';
import commonjs from 'rollup-plugin-commonjs';
import nodeBuiltins from 'rollup-plugin-node-builtins';
import nodeGlobals from 'rollup-plugin-node-globals';
import nodeResolve from 'rollup-plugin-node-resolve';
import sourcemaps from 'rollup-plugin-sourcemaps';

const pkg = require('./package');

const name = pascalCase(pkg.name)

export default {
  input: 'dist/es2015/index.js',
  output: {
    file: `dist/${paramCase(pkg.name)}.es2015.js`,
    name,
    sourcemap: true,
    format: 'iife'
  },
  moduleId: pkg.name,
  // This may comes handy
  // onwarn(warning) {
  //   // Suppress this error message: "The 'this' keyword is equivalent to 'undefined' at the top level of an ES module, and has been rewritten"
  //   // https://github.com/rollup/rollup/issues/794
  //   if (warning.code === 'THIS_IS_UNDEFINED') {
  //     return
  //   }

  //   console.error(warning.message)
  // },
  plugins: [
    sourcemaps(),
    nodeResolve({
      jsnext: true
    }),
    nodeGlobals(),
    nodeBuiltins(),
    commonjs()
  ]
};
<|endoftext|>


<commit_before>Ext.namespace("dbInfo");

dbInfo.application = {
    init: function ()
    {

        var sumaryInfPanel = PMExt.createInfoPanel('../main/getSystemInfo');

        var panelInfo = new Ext.Panel({
            id:'panelInfo',
            title: _("ID_SYSTEM_INFO"),
            frame:true,
            autoWidth:true,
            autoHeight:true,
            collapsible:false,
            items:[
                sumaryInfPanel
            ]
        });

        //Load all panels
        var viewport = new Ext.Viewport({
            layout: "fit",
            items: [panelInfo]
        });
    }
}

Ext.onReady(dbInfo.application.init, dbInfo.application);

<commit_msg>BUG 10811 Nothing is displayed on Admin > Settings > System Information.<commit_after>Ext.namespace("dbInfo");

dbInfo.application = {
    init: function ()
    {

        var sumaryInfPanel = PMExt.createInfoPanel('../../uxmodern/main/getSystemInfo');

        var panelInfo = new Ext.Panel({
            id:'panelInfo',
            title: _("ID_SYSTEM_INFO"),
            frame:true,
            autoWidth:true,
            autoHeight:true,
            collapsible:false,
            items:[
                sumaryInfPanel
            ]
        });

        //Load all panels
        var viewport = new Ext.Viewport({
            layout: "fit",
            items: [panelInfo]
        });
    }
}

Ext.onReady(dbInfo.application.init, dbInfo.application);

<|endoftext|>


<commit_before>module.exports = (sheetDataMiner) => {
  const router = require('express').Router();

  router.get('/', (req, res) => {
    sheetDataMiner.calculateSum()
      .then(sum => res.send('Сумма: ' + sum))
      .catch(e => res.error(e));
  });

  return router;
};
<commit_msg>Simple view of handed over list<commit_after>module.exports = (sheetDataMiner) => {
  const router = require('express').Router();

  router.get('/', (req, res) => {
    Promise.all(
      [sheetDataMiner.calculateSum(), sheetDataMiner.handedOverList()]
    ).then(([sum, handedOver]) =>
      res.send(
        'Сдали: ' + handedOver.join(', ') + '<br>' +
        'Сумма: ' + sum
      )
    ).catch(e => res.error(e));
  });

  return router;
};
<|endoftext|>