<commit_before>'use strict';

angular.module('mgcrea.ngStrap.modal', ['mgcrea.ngStrap.helpers.dimensions'])

  .provider('$modal', function() {

    var defaults = this.defaults = {
      animation: 'am-fade',
      prefixClass: 'modal',
      placement: 'top',
      template: 'modal/modal.tpl.html',
      contentTemplate: false,
      container: false,
      element: null,
      backdrop: true,
      keyboard: true,
      html: false,
      show: true
    };

    this.$get = function($window, $rootScope, $compile, $q, $templateCache, $http, $animate, $timeout, dimensions) {

      var forEach = angular.forEach;
      var trim = String.prototype.trim;
      var requestAnimationFrame = $window.requestAnimationFrame || $window.setTimeout;
      var bodyElement = angular.element($window.document.body);
      var htmlReplaceRegExp = /ng-bind="/ig;

      // Helper functions

      var findElement = function(query, element) {
        return angular.element((element || document).querySelectorAll(query));
      };

      function ModalFactory(config) {

        var $modal = {};

        // Common vars
        var options = angular.extend({}, defaults, config);
        $modal.$promise = $q.when($templateCache.get(options.template) || $http.get(options.template));
        var scope = $modal.$scope = options.scope && options.scope.$new() || $rootScope.$new();
        if(!options.element && !options.container) {
          options.container = 'body';
        }

        // Support scope as string options
        if(!options.scope) {
          forEach(['title', 'content'], function(key) {
            if(options[key]) scope[key] = options[key];
          });
        }

        // Provide scope helpers
        scope.$hide = function() {
          scope.$$postDigest(function() {
            $modal.hide();
          });
        };
        scope.$show = function() {
          scope.$$postDigest(function() {
            $modal.show();
          });
        };
        scope.$toggle = function() {
          scope.$$postDigest(function() {
            $modal.toggle();
          });
        };

        // Support contentTemplate option
        if(options.contentTemplate) {
          $modal.$promise = $modal.$promise.then(function(template) {
            if(angular.isObject(template)) template = template.data;
            var templateEl = angular.element(template);
            return $q.when($templateCache.get(options.contentTemplate) || $http.get(options.contentTemplate))
            .then(function(contentTemplate) {
              if(angular.isObject(contentTemplate)) contentTemplate = contentTemplate.data;
              var contentEl = findElement('[ng-bind="content"]', templateEl[0]).removeAttr('ng-bind').html(contentTemplate);
              // Drop the default footer
              if(!config.template) contentEl.next().remove();
              return templateEl[0].outerHTML;
            });
          });
        }

        // Fetch, compile then initialize modal
        var modalLinker, modalElement;
        var backdropElement = angular.element('
');
        $modal.$promise.then(function(template) {
          if(angular.isObject(template)) template = template.data;
          if(options.html) template = template.replace(htmlReplaceRegExp, 'ng-bind-html="');
          template = trim.apply(template);
          modalLinker = $compile(template);
          $modal.init();
        });

        $modal.init = function() {

          // Options: show
          if(options.show) {
            scope.$$postDigest(function() {
              $modal.show();
            });
          }

        };

        $modal.destroy = function() {

          // Remove element
          if(modalElement) {
            modalElement.remove();
            modalElement = null;
          }
          if(backdropElement) {
            backdropElement.remove();
            backdropElement = null;
          }

          // Destroy scope
          scope.$destroy();

        };

        $modal.show = function() {

          var parent = options.container ? findElement(options.container) : null;
          var after = options.container ? null : options.element;

          // Fetch a cloned element linked from template
          modalElement = $modal.$element = modalLinker(scope, function(clonedElement, scope) {});

          // Set the initial positioning.
          modalElement.css({display: 'block'}).addClass(options.placement);

          // Options: animation
          if(options.animation) {
            if(options.backdrop) {
              backdropElement.addClass('am-fade');
            }
            modalElement.addClass(options.animation);
          }

          if(options.backdrop) {
            $animate.enter(backdropElement, bodyElement, null, function() {});
          }
          $animate.enter(modalElement, parent, after, function() {});
          scope.$isShown = true;
          scope.$$phase || scope.$digest();
          $modal.focus();

          bodyElement.addClass(options.prefixClass + '-open');
          // if(options.animation) {
          //   bodyElement.addClass(options.prefixClass + '-with-' + options.animation);
          // }

          // Bind events
          if(options.backdrop) {
            modalElement.on('click', hideOnBackdropClick);
            backdropElement.on('click', hideOnBackdropClick);
          }
          if(options.keyboard) {
            modalElement.on('keyup', $modal.$onKeyUp);
          }

        };

        $modal.hide = function() {

          $animate.leave(modalElement, function() {
            bodyElement.removeClass(options.prefixClass + '-open');
            // if(options.animation) {
            //   bodyElement.addClass(options.prefixClass + '-with-' + options.animation);
            // }
          });
          if(options.backdrop) {
            $animate.leave(backdropElement, function() {});
          }
          scope.$$phase || scope.$digest();
          scope.$isShown = false;

          // Unbind events
          if(options.backdrop) {
            modalElement.off('click', hideOnBackdropClick);
            backdropElement.off('click', hideOnBackdropClick);
          }
          if(options.keyboard) {
            modalElement.off('keyup', $modal.$onKeyUp);
          }

        };

        $modal.toggle = function() {

          scope.$isShown ? $modal.hide() : $modal.show();

        };

        $modal.focus = function() {
          // Focus once the enter-animation has started
          requestAnimationFrame(function() {
            modalElement[0].focus();
          });
        };

        // Protected methods

        $modal.$onKeyUp = function(evt) {

          evt.which === 27 && $modal.hide();

        };

        // Private methods

        function hideOnBackdropClick(evt) {
          if(evt.target !== evt.currentTarget) return;
          options.backdrop === 'static' ? $modal.focus() : $modal.hide();
        }

        return $modal;

      }

      return ModalFactory;

    };

  })

  .directive('bsModal', function($window, $location, $sce, $modal) {

    return {
      restrict: 'EAC',
      scope: true,
      link: function postLink(scope, element, attr, transclusion) {

        // Directive options
        var options = {scope: scope, element: element, show: false};
        angular.forEach(['template', 'contentTemplate', 'placement', 'backdrop', 'keyboard', 'html', 'container', 'animation'], function(key) {
          if(angular.isDefined(attr[key])) options[key] = attr[key];
        });

        // Support scope as data-attrs
        angular.forEach(['title', 'content'], function(key) {
          attr[key] && attr.$observe(key, function(newValue, oldValue) {
            scope[key] = newValue;
          });
        });

        // Support scope as an object
        attr.bsModal && scope.$watch(attr.bsModal, function(newValue, oldValue) {
          if(angular.isObject(newValue)) {
            angular.extend(scope, newValue);
          } else {
            scope.content = newValue;
          }
        }, true);

        // Initialize modal
        var modal = $modal(options);

        // Trigger
        element.on(attr.trigger || 'click', modal.toggle);

        // Garbage collection
        scope.$on('$destroy', function() {
          modal.destroy();
          options = null;
          modal = null;
        });

      }
    };

  });
<commit_msg>chore(modal): fix weird failing test<commit_after>'use strict';

angular.module('mgcrea.ngStrap.modal', ['mgcrea.ngStrap.helpers.dimensions'])

  .provider('$modal', function() {

    var defaults = this.defaults = {
      animation: 'am-fade',
      prefixClass: 'modal',
      placement: 'top',
      template: 'modal/modal.tpl.html',
      contentTemplate: false,
      container: false,
      element: null,
      backdrop: true,
      keyboard: true,
      html: false,
      show: true
    };

    this.$get = function($window, $rootScope, $compile, $q, $templateCache, $http, $animate, $timeout, dimensions) {

      var forEach = angular.forEach;
      var trim = String.prototype.trim;
      var requestAnimationFrame = $window.requestAnimationFrame || $window.setTimeout;
      var bodyElement = angular.element($window.document.body);
      var htmlReplaceRegExp = /ng-bind="/ig;

      // Helper functions

      var findElement = function(query, element) {
        return angular.element((element || document).querySelectorAll(query));
      };

      function ModalFactory(config) {

        var $modal = {};

        // Common vars
        var options = angular.extend({}, defaults, config);
        $modal.$promise = $q.when($templateCache.get(options.template) || $http.get(options.template));
        var scope = $modal.$scope = options.scope && options.scope.$new() || $rootScope.$new();
        if(!options.element && !options.container) {
          options.container = 'body';
        }

        // Support scope as string options
        if(!options.scope) {
          forEach(['title', 'content'], function(key) {
            if(options[key]) scope[key] = options[key];
          });
        }

        // Provide scope helpers
        scope.$hide = function() {
          scope.$$postDigest(function() {
            $modal.hide();
          });
        };
        scope.$show = function() {
          scope.$$postDigest(function() {
            $modal.show();
          });
        };
        scope.$toggle = function() {
          scope.$$postDigest(function() {
            $modal.toggle();
          });
        };

        // Support contentTemplate option
        if(options.contentTemplate) {
          $modal.$promise = $modal.$promise.then(function(template) {
            if(angular.isObject(template)) template = template.data;
            var templateEl = angular.element(template);
            return $q.when($templateCache.get(options.contentTemplate) || $http.get(options.contentTemplate))
            .then(function(contentTemplate) {
              if(angular.isObject(contentTemplate)) contentTemplate = contentTemplate.data;
              var contentEl = findElement('[ng-bind="content"]', templateEl[0]).removeAttr('ng-bind').html(contentTemplate);
              // Drop the default footer
              if(!config.template) contentEl.next().remove();
              return templateEl[0].outerHTML;
            });
          });
        }

        // Fetch, compile then initialize modal
        var modalLinker, modalElement;
        var backdropElement = angular.element('
');
        $modal.$promise.then(function(template) {
          if(angular.isObject(template)) template = template.data;
          if(options.html) template = template.replace(htmlReplaceRegExp, 'ng-bind-html="');
          template = trim.apply(template);
          modalLinker = $compile(template);
          $modal.init();
        });

        $modal.init = function() {

          // Options: show
          if(options.show) {
            scope.$$postDigest(function() {
              $modal.show();
            });
          }

        };

        $modal.destroy = function() {

          // Remove element
          if(modalElement) {
            modalElement.remove();
            modalElement = null;
          }
          if(backdropElement) {
            backdropElement.remove();
            backdropElement = null;
          }

          // Destroy scope
          scope.$destroy();

        };

        $modal.show = function() {

          var parent = options.container ? findElement(options.container) : null;
          var after = options.container ? null : options.element;

          // Fetch a cloned element linked from template
          modalElement = $modal.$element = modalLinker(scope, function(clonedElement, scope) {});

          // Set the initial positioning.
          modalElement.css({display: 'block'}).addClass(options.placement);

          // Options: animation
          if(options.animation) {
            if(options.backdrop) {
              backdropElement.addClass('am-fade');
            }
            modalElement.addClass(options.animation);
          }

          if(options.backdrop) {
            $animate.enter(backdropElement, bodyElement, null, function() {});
          }
          $animate.enter(modalElement, parent, after, function() {});
          scope.$isShown = true;
          scope.$$phase || scope.$digest();
          // Focus once the enter-animation has started
          // Weird PhantomJS bug hack
          var el = modalElement[0];
          requestAnimationFrame(function() {
            el.focus();
          });

          bodyElement.addClass(options.prefixClass + '-open');
          // if(options.animation) {
          //   bodyElement.addClass(options.prefixClass + '-with-' + options.animation);
          // }

          // Bind events
          if(options.backdrop) {
            modalElement.on('click', hideOnBackdropClick);
            backdropElement.on('click', hideOnBackdropClick);
          }
          if(options.keyboard) {
            modalElement.on('keyup', $modal.$onKeyUp);
          }

        };

        $modal.hide = function() {

          $animate.leave(modalElement, function() {
            bodyElement.removeClass(options.prefixClass + '-open');
            // if(options.animation) {
            //   bodyElement.addClass(options.prefixClass + '-with-' + options.animation);
            // }
          });
          if(options.backdrop) {
            $animate.leave(backdropElement, function() {});
          }
          scope.$$phase || scope.$digest();
          scope.$isShown = false;

          // Unbind events
          if(options.backdrop) {
            modalElement.off('click', hideOnBackdropClick);
            backdropElement.off('click', hideOnBackdropClick);
          }
          if(options.keyboard) {
            modalElement.off('keyup', $modal.$onKeyUp);
          }

        };

        $modal.toggle = function() {

          scope.$isShown ? $modal.hide() : $modal.show();

        };

        $modal.focus = function() {
          modalElement[0].focus();
        };

        // Protected methods

        $modal.$onKeyUp = function(evt) {

          evt.which === 27 && $modal.hide();

        };

        // Private methods

        function hideOnBackdropClick(evt) {
          if(evt.target !== evt.currentTarget) return;
          options.backdrop === 'static' ? $modal.focus() : $modal.hide();
        }

        return $modal;

      }

      return ModalFactory;

    };

  })

  .directive('bsModal', function($window, $location, $sce, $modal) {

    return {
      restrict: 'EAC',
      scope: true,
      link: function postLink(scope, element, attr, transclusion) {

        // Directive options
        var options = {scope: scope, element: element, show: false};
        angular.forEach(['template', 'contentTemplate', 'placement', 'backdrop', 'keyboard', 'html', 'container', 'animation'], function(key) {
          if(angular.isDefined(attr[key])) options[key] = attr[key];
        });

        // Support scope as data-attrs
        angular.forEach(['title', 'content'], function(key) {
          attr[key] && attr.$observe(key, function(newValue, oldValue) {
            scope[key] = newValue;
          });
        });

        // Support scope as an object
        attr.bsModal && scope.$watch(attr.bsModal, function(newValue, oldValue) {
          if(angular.isObject(newValue)) {
            angular.extend(scope, newValue);
          } else {
            scope.content = newValue;
          }
        }, true);

        // Initialize modal
        var modal = $modal(options);

        // Trigger
        element.on(attr.trigger || 'click', modal.toggle);

        // Garbage collection
        scope.$on('$destroy', function() {
          modal.destroy();
          options = null;
          modal = null;
        });

      }
    };

  });
<|endoftext|>


<commit_before>/**
 Copyright (c) 2016,2017,2018,2019,2020 Klaus Landsdorf (https://bianco-royal.com/)
 Copyright 2016 - Jason D. Harper, Argonne National Laboratory
 Copyright 2015,2016 - Mika Karaila, Valmet Automation Inc.
 Copyright 2013, 2016 IBM Corp. (node-red)
 All rights reserved.
 node-red-contrib-modbus

 @author Klaus Landsdorf (Bianco Royal)
 **/
/**
 * Modbus Read node.
 * @module NodeRedModbusRead
 *
 * @param RED
 */
module.exports = function (RED) {
  'use strict'
  // SOURCE-MAP-REQUIRED
  const mbBasics = require('./modbus-basics')
  const mbCore = require('./core/modbus-core')
  const mbIOCore = require('./core/modbus-io-core')
  const internalDebugLog = require('debug')('contribModbus:read')

  function ModbusRead (config) {
    RED.nodes.createNode(this, config)

    this.name = config.name
    this.topic = config.topic
    this.unitid = config.unitid

    this.dataType = config.dataType
    this.adr = config.adr
    this.quantity = config.quantity || 1

    this.rate = config.rate
    this.rateUnit = config.rateUnit

    this.delayOnStart = config.delayOnStart
    this.startDelayTime = parseInt(config.startDelayTime) || 10

    this.showStatusActivities = config.showStatusActivities
    this.showErrors = config.showErrors
    this.connection = null

    this.useIOFile = config.useIOFile
    this.ioFile = RED.nodes.getNode(config.ioFile)
    this.useIOForPayload = config.useIOForPayload
    this.logIOActivities = config.logIOActivities

    this.internalDebugLog = internalDebugLog
    this.verboseLogging = RED.settings.verbose

    const node = this
    let delayTimerID = null
    let timerID = null
    let timeoutOccurred = false
    node.INPUT_TIMEOUT_MILLISECONDS = 1000
    node.statusText = 'waiting'
    mbBasics.setNodeStatusTo(node.statusText, node)

    const modbusClient = RED.nodes.getNode(config.server)
    if (!modbusClient) {
      return
    }
    modbusClient.registerForModbus(node)
    mbBasics.initModbusClientEvents(node, modbusClient)

    node.onModbusInit = function () {
      mbBasics.setNodeStatusTo('initialize', node)
    }

    node.onModbusConnect = function () {
      if (node.delayOnStart) {
        if (!delayTimerID) {
          delayTimerID = setTimeout(node.startIntervalReading, node.INPUT_TIMEOUT_MILLISECONDS * node.startDelayTime)
        } else {
          clearTimeout(delayTimerID)
          delayTimerID = setTimeout(node.startIntervalReading, node.INPUT_TIMEOUT_MILLISECONDS * node.startDelayTime)
        }
      } else {
        if (delayTimerID) {
          clearTimeout(delayTimerID)
        }
        node.startIntervalReading()
      }

      setNodeStatusWithTimeTo('connected')
    }

    node.startIntervalReading = function () {
      if (!timerID) {
        timerID = setInterval(node.modbusPollingRead, mbBasics.calc_rateByUnit(node.rate, node.rateUnit))
      }
    }

    node.onModbusActive = function () {
      setNodeStatusWithTimeTo('active')
    }

    node.onModbusError = function (failureMsg) {
      mbBasics.setNodeStatusTo('failure', node)
      if (modbusClient.reconnectOnTimeout) {
        if (timerID) {
          clearInterval(timerID) // clear Timer from events
        }
        timerID = null
      }
      if (node.showErrors) {
        node.warn(failureMsg)
      }
    }

    node.onModbusClose = function () {
      mbBasics.setNodeStatusTo('closed', node)
      if (timerID) {
        clearInterval(timerID) // clear Timer from events
      }
      timerID = null
    }

    node.onModbusBroken = function () {
      if (modbusClient.reconnectOnTimeout) {
        mbBasics.setNodeStatusTo('reconnecting after ' + modbusClient.reconnectTimeout + ' msec.', node)
        if (timerID) {
          clearInterval(timerID) // clear Timer from events
        }
        timerID = null
      }
    }

    modbusClient.on('mbinit', node.onModbusInit)
    modbusClient.on('mbconnected', node.onModbusConnect)
    modbusClient.on('mbactive', node.onModbusActive)
    modbusClient.on('mberror', node.onModbusError)
    modbusClient.on('mbbroken', node.onModbusBroken)
    modbusClient.on('mbclosed', node.onModbusClose)

    node.modbusPollingRead = function () {
      if (!modbusClient.client) {
        setNodeStatusWithTimeTo('waiting')
        return
      }

      const msg = {
        topic: node.topic || 'polling',
        from: node.name,
        payload: {
          unitid: node.unitid,
          fc: mbCore.functionCodeModbusRead(node.dataType),
          address: node.adr,
          quantity: node.quantity,
          messageId: mbCore.getObjectId()
        }
      }

      if (node.showStatusActivities) {
        setNodeStatusWithTimeTo('polling')
      }

      modbusClient.emit('readModbus', msg, node.onModbusReadDone, node.onModbusReadError)
    }

    node.onModbusReadDone = function (resp, msg) {
      if (node.showStatusActivities) {
        setNodeStatusWithTimeTo('reading done')
      }

      sendMessage(resp.data, resp, msg)
    }

    node.onModbusReadError = function (err, msg) {
      internalDebugLog(err.message)
      if (node.showErrors) {
        node.error(err, msg)
      }
      mbBasics.setModbusError(node, modbusClient, err, msg)
    }

    node.on('close', function (done) {
      if (timerID) {
        clearInterval(timerID)
      }
      timerID = null
      mbBasics.setNodeStatusTo('closed', node)
      modbusClient.deregisterForModbus(node, done)
    })

    function sendMessage (values, response, msg) {
      if (node.useIOFile && node.ioFile.lastUpdatedAt) {
        if (node.logIOActivities) {
          mbIOCore.internalDebug('node.adr:' + node.adr + ' node.quantity:' + node.quantity)
        }

        const allValueNames = mbIOCore.nameValuesFromIOFile(node, msg, values, response, node.adr)
        const valueNames = mbIOCore.filterValueNames(node, allValueNames, mbCore.functionCodeModbusRead(node.dataType), node.adr, node.quantity)

        const origMsg = {
          topic: msg.topic,
          responseBuffer: response,
          input: msg
        }

        if (node.useIOForPayload) {
          origMsg.payload = valueNames
          origMsg.values = values
        } else {
          origMsg.payload = values
          origMsg.valueNames = valueNames
        }

        node.send([
          origMsg,
          {
            payload: response,
            values: values,
            input: msg,
            valueNames: valueNames
          }])
      } else {
        node.send([
          {
            payload: values,
            responseBuffer: response,
            input: msg
          },
          {
            payload: response,
            values: values,
            input: msg
          }
        ])
      }
    }

    function setNodeStatusWithTimeTo (statusValue) {
      if (statusValue === 'polling' && timeoutOccurred) {
        return
      }

      const statusOptions = mbBasics.setNodeStatusProperties(statusValue, node.showStatusActivities)
      const statusText = node.statusText

      if (statusValue.search('active') !== -1 || statusValue === 'polling') {
        const newStatusText = statusOptions.status + getTimeInfo()
        timeoutOccurred = false
        if (newStatusText !== statusText) {
          node.status({
            fill: statusOptions.fill,
            shape: statusOptions.shape,
            text: newStatusText
          })
        }
      } else {
        const newStatusText = statusOptions.status
        if (newStatusText !== statusText) {
          node.status({
            fill: statusOptions.fill,
            shape: statusOptions.shape,
            text: newStatusText
          })
        }
      }
    }

    function getTimeInfo () {
      return ' ( ' + node.rate + ' ' + mbBasics.get_timeUnit_name(node.rateUnit) + ' ) '
    }
  }

  RED.nodes.registerType('modbus-read', ModbusRead)

  RED.httpAdmin.post('/modbus/read/inject/:id', RED.auth.needsPermission('modbus.inject.write'), function (req, res) {
    const node = RED.nodes.getNode(req.params.id)

    if (node) {
      try {
        node.modbusPollingRead()
        res.sendStatus(200)
      } catch (err) {
        res.sendStatus(500)
        node.error(RED._('modbusinject.failed', { error: err.toString() }))
      }
    } else {
      res.sendStatus(404)
    }
  })
}
<commit_msg>fix(read): show active again with new state<commit_after>/**
 Copyright (c) 2016,2017,2018,2019,2020 Klaus Landsdorf (https://bianco-royal.com/)
 Copyright 2016 - Jason D. Harper, Argonne National Laboratory
 Copyright 2015,2016 - Mika Karaila, Valmet Automation Inc.
 Copyright 2013, 2016 IBM Corp. (node-red)
 All rights reserved.
 node-red-contrib-modbus

 @author Klaus Landsdorf (Bianco Royal)
 **/
/**
 * Modbus Read node.
 * @module NodeRedModbusRead
 *
 * @param RED
 */
module.exports = function (RED) {
  'use strict'
  // SOURCE-MAP-REQUIRED
  const mbBasics = require('./modbus-basics')
  const mbCore = require('./core/modbus-core')
  const mbIOCore = require('./core/modbus-io-core')
  const internalDebugLog = require('debug')('contribModbus:read')

  function ModbusRead (config) {
    RED.nodes.createNode(this, config)

    this.name = config.name
    this.topic = config.topic
    this.unitid = config.unitid

    this.dataType = config.dataType
    this.adr = config.adr
    this.quantity = config.quantity || 1

    this.rate = config.rate
    this.rateUnit = config.rateUnit

    this.delayOnStart = config.delayOnStart
    this.startDelayTime = parseInt(config.startDelayTime) || 10

    this.showStatusActivities = config.showStatusActivities
    this.showErrors = config.showErrors
    this.connection = null

    this.useIOFile = config.useIOFile
    this.ioFile = RED.nodes.getNode(config.ioFile)
    this.useIOForPayload = config.useIOForPayload
    this.logIOActivities = config.logIOActivities

    this.internalDebugLog = internalDebugLog
    this.verboseLogging = RED.settings.verbose

    const node = this
    let delayTimerID = null
    let timerID = null
    let timeoutOccurred = false
    node.INPUT_TIMEOUT_MILLISECONDS = 1000
    node.statusText = 'waiting'
    mbBasics.setNodeStatusTo(node.statusText, node)

    const modbusClient = RED.nodes.getNode(config.server)
    if (!modbusClient) {
      return
    }
    modbusClient.registerForModbus(node)
    mbBasics.initModbusClientEvents(node, modbusClient)

    node.onModbusInit = function () {
      mbBasics.setNodeStatusTo('initialize', node)
    }

    node.onModbusConnect = function () {
      if (node.delayOnStart) {
        if (!delayTimerID) {
          delayTimerID = setTimeout(node.startIntervalReading, node.INPUT_TIMEOUT_MILLISECONDS * node.startDelayTime)
        } else {
          clearTimeout(delayTimerID)
          delayTimerID = setTimeout(node.startIntervalReading, node.INPUT_TIMEOUT_MILLISECONDS * node.startDelayTime)
        }
      } else {
        if (delayTimerID) {
          clearTimeout(delayTimerID)
        }
        node.startIntervalReading()
      }

      setNodeStatusWithTimeTo('connected')
    }

    node.startIntervalReading = function () {
      if (!timerID) {
        timerID = setInterval(node.modbusPollingRead, mbBasics.calc_rateByUnit(node.rate, node.rateUnit))
      }
    }

    node.onModbusActive = function () {
      setNodeStatusWithTimeTo('active')
    }

    node.onModbusError = function (failureMsg) {
      mbBasics.setNodeStatusTo('failure', node)
      if (modbusClient.reconnectOnTimeout) {
        if (timerID) {
          clearInterval(timerID) // clear Timer from events
        }
        timerID = null
      }
      if (node.showErrors) {
        node.warn(failureMsg)
      }
    }

    node.onModbusClose = function () {
      mbBasics.setNodeStatusTo('closed', node)
      if (timerID) {
        clearInterval(timerID) // clear Timer from events
      }
      timerID = null
    }

    node.onModbusBroken = function () {
      if (modbusClient.reconnectOnTimeout) {
        mbBasics.setNodeStatusTo('reconnecting after ' + modbusClient.reconnectTimeout + ' msec.', node)
        if (timerID) {
          clearInterval(timerID) // clear Timer from events
        }
        timerID = null
      }
    }

    modbusClient.on('mbinit', node.onModbusInit)
    modbusClient.on('mbconnected', node.onModbusConnect)
    modbusClient.on('mbactive', node.onModbusActive)
    modbusClient.on('mbqueue', node.onModbusActive)
    modbusClient.on('mberror', node.onModbusError)
    modbusClient.on('mbclosed', node.onModbusClose)

    node.modbusPollingRead = function () {
      if (!modbusClient.client) {
        setNodeStatusWithTimeTo('waiting')
        return
      }

      const msg = {
        topic: node.topic || 'polling',
        from: node.name,
        payload: {
          unitid: node.unitid,
          fc: mbCore.functionCodeModbusRead(node.dataType),
          address: node.adr,
          quantity: node.quantity,
          messageId: mbCore.getObjectId()
        }
      }

      if (node.showStatusActivities) {
        setNodeStatusWithTimeTo('polling')
      }

      modbusClient.emit('readModbus', msg, node.onModbusReadDone, node.onModbusReadError)
    }

    node.onModbusReadDone = function (resp, msg) {
      if (node.showStatusActivities) {
        setNodeStatusWithTimeTo('reading done')
      }

      sendMessage(resp.data, resp, msg)
    }

    node.onModbusReadError = function (err, msg) {
      internalDebugLog(err.message)
      if (node.showErrors) {
        node.error(err, msg)
      }
      mbBasics.setModbusError(node, modbusClient, err, msg)
    }

    node.on('close', function (done) {
      if (timerID) {
        clearInterval(timerID)
      }
      timerID = null
      mbBasics.setNodeStatusTo('closed', node)
      modbusClient.deregisterForModbus(node, done)
    })

    function sendMessage (values, response, msg) {
      if (node.useIOFile && node.ioFile.lastUpdatedAt) {
        if (node.logIOActivities) {
          mbIOCore.internalDebug('node.adr:' + node.adr + ' node.quantity:' + node.quantity)
        }

        const allValueNames = mbIOCore.nameValuesFromIOFile(node, msg, values, response, node.adr)
        const valueNames = mbIOCore.filterValueNames(node, allValueNames, mbCore.functionCodeModbusRead(node.dataType), node.adr, node.quantity)

        const origMsg = {
          topic: msg.topic,
          responseBuffer: response,
          input: msg
        }

        if (node.useIOForPayload) {
          origMsg.payload = valueNames
          origMsg.values = values
        } else {
          origMsg.payload = values
          origMsg.valueNames = valueNames
        }

        node.send([
          origMsg,
          {
            payload: response,
            values: values,
            input: msg,
            valueNames: valueNames
          }])
      } else {
        node.send([
          {
            payload: values,
            responseBuffer: response,
            input: msg
          },
          {
            payload: response,
            values: values,
            input: msg
          }
        ])
      }
    }

    function setNodeStatusWithTimeTo (statusValue) {
      if (statusValue === 'polling' && timeoutOccurred) {
        return
      }

      const statusOptions = mbBasics.setNodeStatusProperties(statusValue, node.showStatusActivities)
      const statusText = node.statusText

      if (statusValue.search('active') !== -1 || statusValue === 'polling') {
        const newStatusText = statusOptions.status + getTimeInfo()
        timeoutOccurred = false
        if (newStatusText !== statusText) {
          node.status({
            fill: statusOptions.fill,
            shape: statusOptions.shape,
            text: newStatusText
          })
        }
      } else {
        const newStatusText = statusOptions.status
        if (newStatusText !== statusText) {
          node.status({
            fill: statusOptions.fill,
            shape: statusOptions.shape,
            text: newStatusText
          })
        }
      }
    }

    function getTimeInfo () {
      return ' ( ' + node.rate + ' ' + mbBasics.get_timeUnit_name(node.rateUnit) + ' ) '
    }
  }

  RED.nodes.registerType('modbus-read', ModbusRead)

  RED.httpAdmin.post('/modbus/read/inject/:id', RED.auth.needsPermission('modbus.inject.write'), function (req, res) {
    const node = RED.nodes.getNode(req.params.id)

    if (node) {
      try {
        node.modbusPollingRead()
        res.sendStatus(200)
      } catch (err) {
        res.sendStatus(500)
        node.error(RED._('modbusinject.failed', { error: err.toString() }))
      }
    } else {
      res.sendStatus(404)
    }
  })
}
<|endoftext|>


<commit_before>/**
 * The default testnet node
 *
 * @type {string}
 */
let defaultTestnet = 'http://bob.nem.ninja';

/**
 * The default mainnet node
 *
 * @type {string}
 */
let defaultMainnet = 'http://alice6.nem.ninja';

/**
 * The default mijin node
 *
 * @type {string}
 */
let defaultMijin = '';

/**
 * The default mainnet block explorer
 *
 * @type {string}
 */
let mainnetExplorer = 'http://chain.nem.ninja/#/transfer/';

/**
 * The default testnet block explorer
 *
 * @type {string}
 */
let testnetExplorer = 'http://bob.nem.ninja:8765/#/transfer/';

/**
 * The default mijin block explorer
 *
 * @type {string}
 */
let mijinExplorer = '';

/**
 * The nodes allowing search by transaction hash on testnet
 *
 * @type {array}
 */
let searchOnTestnet = [
	{
	    'uri': 'http://bigalice2.nem.ninja',
	    'location': 'America / New_York'
	},
	{
	    'uri': 'http://192.3.61.243',
	    'location': 'America / Los_Angeles'
	},
	{
	    'uri': 'http://23.228.67.85',
	    'location': 'America / Los_Angeles'
	}
];

/**
 * The nodes allowing search by transaction hash on mainnet
 *
 * @type {array}
 */
let searchOnMainnet = [
	{
	    'uri': 'http://62.75.171.41',
	    'location': 'Germany'
	}, {
	    'uri': 'http://104.251.212.131',
	    'location': 'USA'
	}, {
	    'uri': 'http://45.124.65.125',
	    'location': 'Hong Kong'
	}, {
	    'uri': 'http://185.53.131.101',
	    'location': 'Netherlands'
	}, {
	    'uri': 'http://sz.nemchina.com',
	    'location': 'China'
	}
];

/**
 * The nodes allowing search by transaction hash on mijin
 *
 * @type {array}
 */
let searchOnMijin = [
	{
	    'uri': '',
	    'location': ''
	}
];

/**
 * The testnet nodes
 *
 * @type {array}
 */
let testnet = [
	{
	    uri: 'http://bob.nem.ninja'
	}, {
	        uri: 'http://104.128.226.60'
	}, {
	        uri: 'http://23.228.67.85'
	}, {
	        uri: 'http://192.3.61.243'
	}, {
	        uri: 'http://50.3.87.123'
	}, {
	    uri: 'http://localhost'
	}
];

/**
 * The mainnet nodes
 *
 * @type {array}
 */
let mainnet = [
    {
        uri: 'http://62.75.171.41'
    }, {
        uri: 'http://san.nem.ninja'
    }, {
        uri: 'http://go.nem.ninja'
    }, {
        uri: 'http://hachi.nem.ninja'
    }, {
        uri: 'http://jusan.nem.ninja'
    }, {
        uri: 'http://nijuichi.nem.ninja'
    }, {
        uri: 'http://alice2.nem.ninja'
    }, {
        uri: 'http://alice3.nem.ninja'
    }, {
        uri: 'http://alice4.nem.ninja'
    }, {
        uri: 'http://alice5.nem.ninja'
    }, {
        uri: 'http://alice6.nem.ninja'
    }, {
        uri: 'http://alice7.nem.ninja'
    }, {
        uri: 'http://localhost'
    }
];

/**
 * The mijin nodes
 *
 * @type {array}
 */
let mijin = [
	{
	    uri: ''
	}
];

/**
 * The server verifying signed apostilles
 *
 * @type {string}
 */
let apostilleAuditServer = 'http://185.117.22.58:4567/verify';

/**
 * The API to get all supernodes
 *
 * @type {string}
 */
let supernodes = 'https://supernodes.nem.io/nodes';

/**
 * The API to get XEM/BTC market data
 *
 * @type {string}
 */
let marketInfo = 'https://poloniex.com/public';

/**
 * The API to get BTC/USD market data
 *
 * @type {string}
 */
let btcPrice = 'https://blockchain.info/ticker';

/**
 * The default endpoint port
 *
 * @type {number}
 */
let defaultPort = 7890;

/**
 * The Mijin endpoint port
 *
 * @type {number}
 */
let mijinPort = 7895;

/**
 * The websocket port
 *
 * @type {number}
 */
let websocketPort = 7778;

module.exports = {
    defaultTestnet,
    defaultMainnet,
    defaultMijin,
    mainnetExplorer,
    testnetExplorer,
    mijinExplorer,
    searchOnTestnet,
    searchOnMainnet,
    searchOnMijin,
    testnet,
    mainnet,
    mijin,
    apostilleAuditServer,
    supernodes,
    defaultPort,
    mijinPort,
    websocketPort,
    marketInfo,
    btcPrice
}<commit_msg>Change default testnet node<commit_after>/**
 * The default testnet node
 *
 * @type {string}
 */
let defaultTestnet = 'http://bigalice2.nem.ninja';

/**
 * The default mainnet node
 *
 * @type {string}
 */
let defaultMainnet = 'http://alice6.nem.ninja';

/**
 * The default mijin node
 *
 * @type {string}
 */
let defaultMijin = '';

/**
 * The default mainnet block explorer
 *
 * @type {string}
 */
let mainnetExplorer = 'http://chain.nem.ninja/#/transfer/';

/**
 * The default testnet block explorer
 *
 * @type {string}
 */
let testnetExplorer = 'http://bob.nem.ninja:8765/#/transfer/';

/**
 * The default mijin block explorer
 *
 * @type {string}
 */
let mijinExplorer = '';

/**
 * The nodes allowing search by transaction hash on testnet
 *
 * @type {array}
 */
let searchOnTestnet = [
	{
	    'uri': 'http://bigalice2.nem.ninja',
	    'location': 'America / New_York'
	},
	{
	    'uri': 'http://192.3.61.243',
	    'location': 'America / Los_Angeles'
	},
	{
	    'uri': 'http://23.228.67.85',
	    'location': 'America / Los_Angeles'
	}
];

/**
 * The nodes allowing search by transaction hash on mainnet
 *
 * @type {array}
 */
let searchOnMainnet = [
	{
	    'uri': 'http://62.75.171.41',
	    'location': 'Germany'
	}, {
	    'uri': 'http://104.251.212.131',
	    'location': 'USA'
	}, {
	    'uri': 'http://45.124.65.125',
	    'location': 'Hong Kong'
	}, {
	    'uri': 'http://185.53.131.101',
	    'location': 'Netherlands'
	}, {
	    'uri': 'http://sz.nemchina.com',
	    'location': 'China'
	}
];

/**
 * The nodes allowing search by transaction hash on mijin
 *
 * @type {array}
 */
let searchOnMijin = [
	{
	    'uri': '',
	    'location': ''
	}
];

/**
 * The testnet nodes
 *
 * @type {array}
 */
let testnet = [
	{
	    uri: 'http://bob.nem.ninja'
	}, {
	        uri: 'http://104.128.226.60'
	}, {
	        uri: 'http://23.228.67.85'
	}, {
	        uri: 'http://192.3.61.243'
	}, {
	        uri: 'http://50.3.87.123'
	}, {
	    uri: 'http://localhost'
	}
];

/**
 * The mainnet nodes
 *
 * @type {array}
 */
let mainnet = [
    {
        uri: 'http://62.75.171.41'
    }, {
        uri: 'http://san.nem.ninja'
    }, {
        uri: 'http://go.nem.ninja'
    }, {
        uri: 'http://hachi.nem.ninja'
    }, {
        uri: 'http://jusan.nem.ninja'
    }, {
        uri: 'http://nijuichi.nem.ninja'
    }, {
        uri: 'http://alice2.nem.ninja'
    }, {
        uri: 'http://alice3.nem.ninja'
    }, {
        uri: 'http://alice4.nem.ninja'
    }, {
        uri: 'http://alice5.nem.ninja'
    }, {
        uri: 'http://alice6.nem.ninja'
    }, {
        uri: 'http://alice7.nem.ninja'
    }, {
        uri: 'http://localhost'
    }
];

/**
 * The mijin nodes
 *
 * @type {array}
 */
let mijin = [
	{
	    uri: ''
	}
];

/**
 * The server verifying signed apostilles
 *
 * @type {string}
 */
let apostilleAuditServer = 'http://185.117.22.58:4567/verify';

/**
 * The API to get all supernodes
 *
 * @type {string}
 */
let supernodes = 'https://supernodes.nem.io/nodes';

/**
 * The API to get XEM/BTC market data
 *
 * @type {string}
 */
let marketInfo = 'https://poloniex.com/public';

/**
 * The API to get BTC/USD market data
 *
 * @type {string}
 */
let btcPrice = 'https://blockchain.info/ticker';

/**
 * The default endpoint port
 *
 * @type {number}
 */
let defaultPort = 7890;

/**
 * The Mijin endpoint port
 *
 * @type {number}
 */
let mijinPort = 7895;

/**
 * The websocket port
 *
 * @type {number}
 */
let websocketPort = 7778;

module.exports = {
    defaultTestnet,
    defaultMainnet,
    defaultMijin,
    mainnetExplorer,
    testnetExplorer,
    mijinExplorer,
    searchOnTestnet,
    searchOnMainnet,
    searchOnMijin,
    testnet,
    mainnet,
    mijin,
    apostilleAuditServer,
    supernodes,
    defaultPort,
    mijinPort,
    websocketPort,
    marketInfo,
    btcPrice
}<|endoftext|>


<commit_before>import { observable, action } from 'mobx'

import { downloadManager } from '~/src/network/http'
import { cache } from '~/src/network/cache'
import { segment } from '~/src/network/segment'
import * as _ from '~/src/utils/curry'
import { downloadManager } from '~/src/network/http'
import { cache } from '~/src/network/cache'
import { segment } from '~/src/network/segment'
import * as _ from '~/src/utils/curry'

import { analytics } from './analytics'
import { favStore } from './favstore'
import { tabStore } from './tabstore'
import { barStore } from './barstore'
import { barStatusStore } from './barstatusstore'
import { loginStore } from './loginstore'
import { tagStore } from './tagstore'
import { mapStore } from './mapstore'
import { orderStore } from './orders/orderstore'
import { paymentStore } from './orders/paymentstore'
import { historyStore } from './historystore'
import { timeStore } from './timestore'
import { drawerStore } from './drawerstore'

import { notificationStore } from './notificationstore'

const log = _.logger('./model/store')

export class Store {

    @observable initialized = false
    @observable mapVisible = true

    constructor() {
        // this.discoverScrollView = null
        this.previousState = null
        setTimeout(this.periodicallySaveToLocalStorage, 5000)
    }

    @action switchToDiscoverPage = (scrollToTop) => {
        tabStore.setCurrentTab(0)
        this.mapVisible = true
        // if (scrollToTop && this.discoverScrollView)
        //     this.discoverScrollView.scrollTo({x: 0, y: 0})
    }

    @action setMapVisible = (visible) => {
        this.mapVisible = visible
    }

    initialize = async () => {
        /* First call initialize() */
        await analytics.initialize()
        await favStore.initialize()
        await tabStore.initialize()
        await barStore.initialize()
        await barStatusStore.initialize()
        await loginStore.initialize()
        await tagStore.initialize()
        await mapStore.initialize()
        await orderStore.initialize()
        await paymentStore.initialize()
        await historyStore.initialize()
        await timeStore.initialize()
        await segment.initialize()

        try {
            await this.loadFromLocalStorage()
        } catch (e) {
            _.logError(e)
        }
        this.initialized = true

        /* Then call initialized() */
        await mapStore.initialized()
        await segment.initialized()
        await downloadManager.initialized()
        segment.track('Application Opened', {
            from_background: true, // TODO:
            // referring_application: 'GMail',
            // url: 'url://location'
        })
    }

    loadFromLocalStorage = async () => {
        const { fromCache, value } = await cache.get('qd:state', () => null)
        if (value) {
            // log("Restoring state...", savedState)
            await this.setState(value)
        }
    }

    setState = action(async (state) => {
        if (state.payState)
            paymentStore.setState(state.payState)
        if (state.barState)
            await barStore.setState(state.barState)
        if (state.barStatusState)
            await barStatusStore.setState(state.barStatusState)
        if (state.tabState)
            await tabStore.setState(state.tabState)
        if (state.loginState)
            loginStore.setState(state.loginState)
        if (state.orderState)
            orderStore.setState(state.orderState)
        if (state.mapState)
            mapStore.setState(state.mapState)
        if (state.tagState)
            tagStore.setState(state.tagState)
        if (state.segment)
            segment.setState(state.segment)
        if (state.downloadManagerState)
            downloadManager.setState(state.downloadManagerState)
    })

    getState = () => {
        return _.asData({
            payState:               paymentStore.getState(),
            barState:               barStore.getState(),
            barStatusState:         barStatusStore.getState(),
            tabState:               tabStore.getState(),
            loginState:             loginStore.getState(),
            orderState:             orderStore.getState(),
            mapState:               mapStore.getState(),
            tagState:               tagStore.getState(),
            segment:                segment.getState(),
            downloadManagerState:   downloadManager.getState(),
        })
    }

    emptyState = () => {
        return {
            payState:               paymentStore.getState(),
            loginState:             loginStore.getState(),

            barState:               barStore.emptyState(),
            barStatusState:         barStatusStore.emptyState(),
            tabState:               tabStore.emptyState(),
            orderState:             orderStore.emptyState(),
            mapState:               mapStore.emptyState(),
            tagState:               tagStore.emptyState(),
            segment:                segment.emptyState(),
            downloadManagerState:   downloadManager.emptyState(),
        }
    }

    saveToLocalStorage = async (state) => {
        if (!_.deepEqual(state, this.previousState)) {
            // log(state)
            // log(this.previousState)
            this.previousState = state
            await cache.set('qd:state', state)
        }
    }

    periodicallySaveToLocalStorage = _.logErrors(async () => {
        const state = this.getState()
        await this.saveToLocalStorage(state)
        setTimeout(this.periodicallySaveToLocalStorage, 1000)
    })

    @action clearData = () => {
        const emptyState = this.emptyState()
        this.setState(emptyState)
        // await this.saveToLocalStorage(emptyState)
    }
}

export const store = new Store()

export {
    analytics,
    favStore,
    tabStore,
    barStore,
    barStatusStore,
    loginStore,
    tagStore,
    mapStore,
    orderStore,
    paymentStore,
    historyStore,
    timeStore,
    segment,
    drawerStore,
    notificationStore,
}
<commit_msg>remove duplicate imports<commit_after>import { observable, action } from 'mobx'

import { downloadManager } from '~/src/network/http'
import { cache } from '~/src/network/cache'
import { segment } from '~/src/network/segment'
import * as _ from '~/src/utils/curry'

import { analytics } from './analytics'
import { favStore } from './favstore'
import { tabStore } from './tabstore'
import { barStore } from './barstore'
import { barStatusStore } from './barstatusstore'
import { loginStore } from './loginstore'
import { tagStore } from './tagstore'
import { mapStore } from './mapstore'
import { orderStore } from './orders/orderstore'
import { paymentStore } from './orders/paymentstore'
import { historyStore } from './historystore'
import { timeStore } from './timestore'
import { drawerStore } from './drawerstore'

import { notificationStore } from './notificationstore'

const log = _.logger('./model/store')

export class Store {

    @observable initialized = false
    @observable mapVisible = true

    constructor() {
        // this.discoverScrollView = null
        this.previousState = null
        setTimeout(this.periodicallySaveToLocalStorage, 5000)
    }

    @action switchToDiscoverPage = (scrollToTop) => {
        tabStore.setCurrentTab(0)
        this.mapVisible = true
        // if (scrollToTop && this.discoverScrollView)
        //     this.discoverScrollView.scrollTo({x: 0, y: 0})
    }

    @action setMapVisible = (visible) => {
        this.mapVisible = visible
    }

    initialize = async () => {
        /* First call initialize() */
        await analytics.initialize()
        await favStore.initialize()
        await tabStore.initialize()
        await barStore.initialize()
        await barStatusStore.initialize()
        await loginStore.initialize()
        await tagStore.initialize()
        await mapStore.initialize()
        await orderStore.initialize()
        await paymentStore.initialize()
        await historyStore.initialize()
        await timeStore.initialize()
        await segment.initialize()

        try {
            await this.loadFromLocalStorage()
        } catch (e) {
            _.logError(e)
        }
        this.initialized = true

        /* Then call initialized() */
        await mapStore.initialized()
        await segment.initialized()
        await downloadManager.initialized()
        segment.track('Application Opened', {
            from_background: true, // TODO:
            // referring_application: 'GMail',
            // url: 'url://location'
        })
    }

    loadFromLocalStorage = async () => {
        const { fromCache, value } = await cache.get('qd:state', () => null)
        if (value) {
            // log("Restoring state...", savedState)
            await this.setState(value)
        }
    }

    setState = action(async (state) => {
        if (state.payState)
            paymentStore.setState(state.payState)
        if (state.barState)
            await barStore.setState(state.barState)
        if (state.barStatusState)
            await barStatusStore.setState(state.barStatusState)
        if (state.tabState)
            await tabStore.setState(state.tabState)
        if (state.loginState)
            loginStore.setState(state.loginState)
        if (state.orderState)
            orderStore.setState(state.orderState)
        if (state.mapState)
            mapStore.setState(state.mapState)
        if (state.tagState)
            tagStore.setState(state.tagState)
        if (state.segment)
            segment.setState(state.segment)
        if (state.downloadManagerState)
            downloadManager.setState(state.downloadManagerState)
    })

    getState = () => {
        return _.asData({
            payState:               paymentStore.getState(),
            barState:               barStore.getState(),
            barStatusState:         barStatusStore.getState(),
            tabState:               tabStore.getState(),
            loginState:             loginStore.getState(),
            orderState:             orderStore.getState(),
            mapState:               mapStore.getState(),
            tagState:               tagStore.getState(),
            segment:                segment.getState(),
            downloadManagerState:   downloadManager.getState(),
        })
    }

    emptyState = () => {
        return {
            payState:               paymentStore.getState(),
            loginState:             loginStore.getState(),

            barState:               barStore.emptyState(),
            barStatusState:         barStatusStore.emptyState(),
            tabState:               tabStore.emptyState(),
            orderState:             orderStore.emptyState(),
            mapState:               mapStore.emptyState(),
            tagState:               tagStore.emptyState(),
            segment:                segment.emptyState(),
            downloadManagerState:   downloadManager.emptyState(),
        }
    }

    saveToLocalStorage = async (state) => {
        if (!_.deepEqual(state, this.previousState)) {
            // log(state)
            // log(this.previousState)
            this.previousState = state
            await cache.set('qd:state', state)
        }
    }

    periodicallySaveToLocalStorage = _.logErrors(async () => {
        const state = this.getState()
        await this.saveToLocalStorage(state)
        setTimeout(this.periodicallySaveToLocalStorage, 1000)
    })

    @action clearData = () => {
        const emptyState = this.emptyState()
        this.setState(emptyState)
        // await this.saveToLocalStorage(emptyState)
    }
}

export const store = new Store()

export {
    analytics,
    favStore,
    tabStore,
    barStore,
    barStatusStore,
    loginStore,
    tagStore,
    mapStore,
    orderStore,
    paymentStore,
    historyStore,
    timeStore,
    segment,
    drawerStore,
    notificationStore,
}
<|endoftext|>


<commit_before>/* jspsych plugin for categorization trials with feedback
 * Josh de Leeuw
 *
 * display an image and then give corrective feedback based on the subject's response
 *
 * updated March 2013
 */

 (function($) {
    jsPsych.categorize_feedback = (function() {

        var plugin = {};

        plugin.create = function(params) {
            var trials = [];
            for (var i = 0; i < params.stimuli.length; i++) {
                trials.push({});
                trials[i].type = "categorize_feedback";
                trials[i].a_path = params.stimuli[i];
                trials[i].key_answer = params.key_answer[i];
                trials[i].text_answer = params.text_answer[i];
                trials[i].choices = params.choices;
                trials[i].correct_text = params.correct_text;
                trials[i].incorrect_text = params.incorrect_text;
                // timing params
                trials[i].timing_image = params.timing_image || -1; // default is to show image until response
                trials[i].timing_feedback_duration = params.timing_feedback_duration || 2000;
                trials[i].timing_post_trial = params.timing_post_trial || 1000;
                // optional params
                trials[i].show_stim_with_feedback =  (typeof params.show_stim_with_feedback === undefined) ? true : params.show_stim_with_feedback;
                trials[i].is_html = (typeof params.is_html === undefined) ? false : params.is_html;
                trials[i].force_correct_button_press = (typeof params.force_correct_button_press === undefined) ? false : params.force_correct_button_press;
                trials[i].prompt = (typeof params.prompt === undefined) ? undefined : params.prompt;
                trials[i].data = (typeof params.data === undefined) ? undefined : params.data[i];
            }
            return trials;
        }

        var cf_trial_complete = false;

        plugin.trial = function(display_element, block, trial, part) {
            switch (part) {
            case 1:
                // set finish flag
                cf_trial_complete = false;

                if(!trial.is_html){
                    // add image to display
                    display_element.append($('<img>', {
                        "src": trial.a_path,
                        "class": 'cf',
                        "id": 'jspsych_cf_image'
                    }));
                } else {
                    display_element.append($('<div>', {
                        id: 'jspsych_cf_image',
                        "class": 'cf',
                        html: trial.a_path
                    }));
                }

                // hide image after time if the timing parameter is set
                if (trial.timing_image > 0) {
                    setTimeout(function() {
                        if (!cf_trial_complete) {
                            $('#jspsych_cf_image').css('visibility', 'hidden');
                        }
                    }, trial.timing_image);
                }

                // if prompt is set, show prompt
                if (trial.prompt) {
                    display_element.append(trial.prompt);
                }

                // start measuring RT
                var startTime = (new Date()).getTime();

                // create response function
                var resp_func = function(e) {
                    var flag = false;
                    var correct = false;
                    if (e.which == trial.key_answer) // correct category
                    {
                        flag = true;
                        correct = true;
                    }
                    else {
                        // check if the key is any of the options, or if it is an accidental keystroke
                        for (var i = 0; i < trial.choices.length; i++) {
                            if (e.which == trial.choices[i]) {
                                flag = true;
                                correct = false;
                            }
                        }
                    }
                    if (flag) {
                        cf_trial_complete = true;

                        // measure response time
                        var endTime = (new Date()).getTime();
                        var rt = (endTime - startTime);

                        // save data
                        var trial_data = {
                            "trial_type": "categorize_feedback",
                            "trial_index": block.trial_idx,
                            "rt": rt,
                            "correct": correct,
                            "a_path": trial.a_path,
                            "key_press": e.which
                        };

                        block.data[block.trial_idx] = $.extend({}, trial_data, trial.data);

                        // clear function
                        $(document).unbind('keyup', resp_func);
                        display_element.html('');
                        plugin.trial(display_element, block, trial, part + 1);
                    }
                };

                // add event listener
                $(document).keyup(resp_func);
                break;

            case 2:
                // show image during feedback if flag is set
                if (trial.show_stim_with_feedback) {
                    display_element.append($('<img>', {
                        "src": trial.a_path,
                        "class": 'cf'
                    }));
                }

                // substitute answer in feedback string.
                var atext = "";
                if (block.data[block.trial_idx].correct) {
                    atext = trial.correct_text.replace("&ANS&", trial.text_answer);
                }
                else {
                    atext = trial.incorrect_text.replace("&ANS&", trial.text_answer);
                }

                // show the feedback
                display_element.append(atext);

                // check if force correct button press is set
                if (trial.force_correct_button_press && block.data[block.trial_idx].correct === false) {
                    var resp_func_corr_key = function(e) {
                        if (e.which == trial.key_answer) // correct category
                        {
                            $(document).unbind('keyup', resp_func_corr_key);
                            plugin.trial(display_element, block, trial, part + 1);
                        }
                    };
                    $(document).keyup(resp_func_corr_key);
                }
                else {
                    setTimeout(function() {
                        plugin.trial(display_element, block, trial, part + 1);
                    }, trial.timing_feedback_duration);
                }
                break;
            case 3:
                display_element.html("");
                setTimeout(function() {
                    block.next();
                }, trial.timing_post_trial);
                break;
            }
        };

        return plugin;
    })();
})(jQuery);<commit_msg>bug fix categorize-feedback<commit_after>/* jspsych plugin for categorization trials with feedback
 * Josh de Leeuw
 *
 * display an image and then give corrective feedback based on the subject's response
 *
 * updated March 2013
 */

 (function($) {
    jsPsych.categorize_feedback = (function() {

        var plugin = {};

        plugin.create = function(params) {
            var trials = [];
            for (var i = 0; i < params.stimuli.length; i++) {
                trials.push({});
                trials[i].type = "categorize_feedback";
                trials[i].a_path = params.stimuli[i];
                trials[i].key_answer = params.key_answer[i];
                trials[i].text_answer = params.text_answer[i];
                trials[i].choices = params.choices;
                trials[i].correct_text = params.correct_text;
                trials[i].incorrect_text = params.incorrect_text;
                // timing params
                trials[i].timing_image = params.timing_image || -1; // default is to show image until response
                trials[i].timing_feedback_duration = params.timing_feedback_duration || 2000;
                trials[i].timing_post_trial = params.timing_post_trial || 1000;
                // optional params
                trials[i].show_stim_with_feedback =  (typeof params.show_stim_with_feedback === 'undefined') ? true : params.show_stim_with_feedback;
                trials[i].is_html = (typeof params.is_html === 'undefined') ? false : params.is_html;
                trials[i].force_correct_button_press = (typeof params.force_correct_button_press === 'undefined') ? false : params.force_correct_button_press;
                trials[i].prompt = (typeof params.prompt === 'undefined') ? 'undefined' : params.prompt;
                trials[i].data = (typeof params.data === 'undefined') ? {} : params.data[i];
            }
            return trials;
        };

        var cf_trial_complete = false;

        plugin.trial = function(display_element, block, trial, part) {
            switch (part) {
            case 1:
                // set finish flag
                cf_trial_complete = false;

                if(!trial.is_html){
                    // add image to display
                    display_element.append($('<img>', {
                        "src": trial.a_path,
                        "class": 'cf',
                        "id": 'jspsych_cf_image'
                    }));
                } else {
                    display_element.append($('<div>', {
                        id: 'jspsych_cf_image',
                        "class": 'cf',
                        html: trial.a_path
                    }));
                }

                // hide image after time if the timing parameter is set
                if (trial.timing_image > 0) {
                    setTimeout(function() {
                        if (!cf_trial_complete) {
                            $('#jspsych_cf_image').css('visibility', 'hidden');
                        }
                    }, trial.timing_image);
                }

                // if prompt is set, show prompt
                if (trial.prompt) {
                    display_element.append(trial.prompt);
                }

                // start measuring RT
                var startTime = (new Date()).getTime();

                // create response function
                var resp_func = function(e) {
                    var flag = false;
                    var correct = false;
                    if (e.which == trial.key_answer) // correct category
                    {
                        flag = true;
                        correct = true;
                    }
                    else {
                        // check if the key is any of the options, or if it is an accidental keystroke
                        for (var i = 0; i < trial.choices.length; i++) {
                            if (e.which == trial.choices[i]) {
                                flag = true;
                                correct = false;
                            }
                        }
                    }
                    if (flag) {
                        cf_trial_complete = true;

                        // measure response time
                        var endTime = (new Date()).getTime();
                        var rt = (endTime - startTime);

                        // save data
                        var trial_data = {
                            "trial_type": "categorize_feedback",
                            "trial_index": block.trial_idx,
                            "rt": rt,
                            "correct": correct,
                            "a_path": trial.a_path,
                            "key_press": e.which
                        };

                        block.data[block.trial_idx] = $.extend({}, trial_data, trial.data);

                        // clear function
                        $(document).unbind('keyup', resp_func);
                        display_element.html('');
                        plugin.trial(display_element, block, trial, part + 1);
                    }
                };

                // add event listener
                $(document).keyup(resp_func);
                break;

            case 2:
                // show image during feedback if flag is set
                if (trial.show_stim_with_feedback) {
                    display_element.append($('<img>', {
                        "src": trial.a_path,
                        "class": 'cf'
                    }));
                }

                // substitute answer in feedback string.
                var atext = "";
                if (block.data[block.trial_idx].correct) {
                    atext = trial.correct_text.replace("&ANS&", trial.text_answer);
                }
                else {
                    atext = trial.incorrect_text.replace("&ANS&", trial.text_answer);
                }

                // show the feedback
                display_element.append(atext);

                // check if force correct button press is set
                if (trial.force_correct_button_press && block.data[block.trial_idx].correct === false) {
                    var resp_func_corr_key = function(e) {
                        if (e.which == trial.key_answer) // correct category
                        {
                            $(document).unbind('keyup', resp_func_corr_key);
                            plugin.trial(display_element, block, trial, part + 1);
                        }
                    };
                    $(document).keyup(resp_func_corr_key);
                }
                else {
                    setTimeout(function() {
                        plugin.trial(display_element, block, trial, part + 1);
                    }, trial.timing_feedback_duration);
                }
                break;
            case 3:
                display_element.html("");
                setTimeout(function() {
                    block.next();
                }, trial.timing_post_trial);
                break;
            }
        };

        return plugin;
    })();
})(jQuery);<|endoftext|>


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

    angular
        .module('boxDrag', [])
        .directive('boxDrag', boxDrag);

    boxDrag.$inject = ['$document'];

    /* @ngInject */
    function boxDrag($document) {
        /**
         * Usage:
         * Make an element draggable like jQueryUI does,
         * but without using jQueryUI.
         *
         * Common intended use case: you want to show an box, similar to a gmail compose window, in the
         * bottom right corner of the page, and you want this box to be draggable.
         *
         * Inspired by angular documentation: https://docs.angularjs.org/guide/compiler
         */
        var directive = {
            link: link,
            restrict: 'E'
        };
        return directive;

        function link(scope, element, attrs) {
            var startX = 0,
                startY = 0,
                x = 0,
                y = 20;

            element.css({
                position: 'absolute',
                top: attrs.top || undefined,
                left: attrs.left || undefined,
                bottom: attrs.bottom || '20px',
                right: attrs.right || '20px',
                width: attrs.width || '400px'
            });

            element.find('div.box-drag-header').css({
                padding: attrs.paddingHeader || '7px 10px'
            });

            element.find('div.box-drag-body').css({
                height: attrs.bodyHeight || '310px'
            });

            element.find('div.box-drag-footer').css({
                padding: attrs.paddingFooter || '7px 10px'
            });

            var height_header = element.find('div.box-drag-header').prop('offsetHeight'),
                height_body = element.find('div.box-drag-body').prop('offsetHeight'),
                height_footer = element.find('div.box-drag-footer').prop('offsetHeight');

            var bd_head = element.find('div.box-drag-header').height(),
                bd_footer = element.find('div.box-drag-footer').height();

            /**
             * Compute the wrapper height based off the heights of the header, body, and footer divs.
             */
            element.css({ height: height_header + height_body + height_footer + 'px' });

            var initialMove = true;

            element.find('div.box-drag-handle').on('mousedown', function(event) {
                // Prevent default dragging of selected content
                event.preventDefault();

                /**
                 * This trick is necessary for the first time the modal is moved.
                 * Otherwise, it will move rapidly to the left (not intended behavior).
                 */

                if (initialMove === true) {
                    var elPos = element.position(),
                        elPosLeft = elPos.left,
                        elPosTop = elPos.top;

                    initialMove = false;
                    x = elPosLeft;
                    y = elPosTop;
                }

                startX = event.pageX - x;
                startY = event.pageY - y;

                $document.on('mousemove', mousemove);
                $document.on('mouseup', mouseup);
            });

            function mousemove(event) {
                y = event.pageY - startY;
                x = event.pageX - startX;

                element.css({
                    top: y + 'px',
                    left:  x + 'px'
                });
            }

            function mouseup() {
                $document.unbind('mousemove', mousemove);
                $document.unbind('mouseup', mouseup);
            }
        }
    }
})();
<commit_msg>Update box-drag.js<commit_after>(function () {
    'use strict';

    angular
        .module('boxDrag', [])
        .directive('boxDrag', boxDrag);

    boxDrag.$inject = ['$document'];

    /* @ngInject */
    function boxDrag($document) {
        /**
         * Usage:
         * Make an element draggable like jQueryUI does,
         * but without using jQueryUI.
         *
         * Common intended use case: you want to show an box, similar to a gmail compose window, in the
         * bottom right corner of the page, and you want this box to be draggable.
         *
         * Inspired by angular documentation: https://docs.angularjs.org/guide/compiler
         */
        var directive = {
            link: link,
            restrict: 'E'
        };
        return directive;

        function link(scope, element, attrs) {
            var startX = 0,
                startY = 0,
                x = 0,
                y = 20;

            element.css({
                position: 'fixed',
                top: attrs.top || undefined,
                left: attrs.left || undefined,
                bottom: attrs.bottom || '20px',
                right: attrs.right || '20px',
                width: attrs.width || '400px'
            });

            angular.element(element[0].querySelector('div.box-drag-header')).css({
                padding: attrs.paddingHeader || '7px 10px'
            });

            angular.element(element[0].querySelector('div.box-drag-body')).css({
                height: attrs.bodyHeight || '310px'
            });

            angular.element(element[0].querySelector('div.box-drag-footer')).css({
                padding: attrs.paddingFooter || '7px 10px'
            });

            var height_header = angular.element(element[0].querySelector('div.box-drag-header')).prop('offsetHeight'),
                height_body = angular.element(element[0].querySelector('div.box-drag-body')).prop('offsetHeight'),
                height_footer = angular.element(element[0].querySelector('div.box-drag-footer')).prop('offsetHeight');

            /**
             * Compute the wrapper height based off the heights of the header, body, and footer divs.
             */
            element.css({ height: height_header + height_body + height_footer + 'px' });

            var initialMove = true;

            /**
             * jqLite does not have jQuery's offset (top, left),
             * but this pull request implements this feature for jqLite.
             *
             * https://github.com/angular/angular.js/pull/3799/files
             */
            var jqLiteOffset = function(element) {
                var documentElem,
                    box = { top: 0, left: 0 },
                    doc = element && element.ownerDocument;

                documentElem = doc.documentElement;
                if ( typeof element.getBoundingClientRect !== undefined ) {
                    box = element.getBoundingClientRect();
                }

                return {
                    top: box.top + (window.pageYOffset || documentElem.scrollTop) - (documentElem.clientTop || 0),
                    left: box.left + (window.pageXOffset || documentElem.scrollLeft) - (documentElem.clientLeft || 0),
                    /**
                     * We add an extra return value to be used after mouseup function.
                     */
                    innerTop: box.top
                };
            };

            /**
             * Drag the box.
             */
            angular.element(element[0].querySelector('div.box-drag-handle')).on('mousedown', function(event) {
                event.preventDefault();

                x = jqLiteOffset(element[0]).left;
                y = jqLiteOffset(element[0]).top;

                startX = event.pageX - x;
                startY = event.pageY - y;

                $document.on('mousemove', mousemove);
                $document.on('mouseup', mouseup);
            });

            function mousemove(event) {
                element.css({position: 'absolute'});

                y = event.pageY - startY;
                x = event.pageX - startX;

                element.css({
                    top: y + 'px',
                    left: x + 'px'
                });
            }

            function mouseup() {
                $document.unbind('mousemove', mousemove);
                $document.unbind('mouseup', mouseup);

                /**
                 * By setting the element back to "fixed", and using the current position
                 * relative to the viewport top, the modal will not get lost while scrolling.
                 */
                element.css({
                    position: 'fixed',
                    top: jqLiteOffset(element[0]).innerTop + 'px'
                });
            }
        }
    }
})();
<|endoftext|>


<commit_before>import * as utils from "base/utils";
import Model from "base/model";
import Reader from "base/reader";
import EventSource from "base/events";
import { DataStorage } from "base/datastorage";
/*
 * VIZABI Data Model (model.data)
 */

const DataModel = Model.extend({

  /**
   * Default values for this model
   */
  getClassDefaults() {
    const defaults = {
      reader: "csv"
    };
    return utils.deepExtend(this._super(), defaults);
  },

  trackInstances: true,

  /**
   * Initializes the data model.
   * @param {Object} values The initial values of this model
   * @param parent A reference to the parent model
   * @param {Object} bind Initial events to bind
   */
  init(name, values, parent, bind) {

    this._type = "data";

    this.queryQueue = {};
    this._collection = {};
    this._collectionPromises = {}; // stores promises, making sure we don't do one calulation twice

    //same constructor as parent, with same arguments
    this._super(name, values, parent, bind);

    this.readerObject = this.getReader();

  },

  /**
   * Loads concept properties when all other models are also starting to load data
   * @return {Promise} Promise which resolves when concepts are loaded
   */
  preloadData() {
    return this.loadDataAvailability()
      .then(this.loadConceptProps.bind(this));
  },

  /**
   * Loads resource from reader or cache
   * @param {Array} query Array with queries to be loaded
   * @param {Object} parsers An object with concepts as key and parsers as value
   * @param {*} evts ?
   */
  load(query, parsers = {}, sideLoad) {
    // deep clone to prevent one query sent to multiple data objects being manipulated cross-data model.
    // For example one query sent to two different waffle server datasets.
    query = utils.deepClone(query);
    // add waffle server specific query clauses if set
    if (this.dataset) query.dataset = this.dataset;
    if (this.version) query.version = this.version;
    const dataId = DataStorage.getDataId(query, this.readerObject, parsers);
    if (dataId) {
      if (!query.grouping) return Promise.resolve(dataId);
      return DataStorage.aggregateData(dataId, query, this.readerObject, this.getConceptprops());
    }
    utils.timeStamp("Vizabi Data: Loading Data");
    if (!sideLoad) {
      EventSource.freezeAll([
        "hook_change",
        "resize"
      ]);
    }

    return DataStorage.loadFromReader(query, parsers, this.readerObject)
      .then(dataId => {
        if (!query.grouping) return dataId;
        return DataStorage.aggregateData(dataId, query, this.readerObject, this.getConceptprops());
      })
      .then(dataId => {
        if (!sideLoad) EventSource.unfreezeAll();
        return dataId;
      })
      .catch(error => {
        if (!sideLoad) EventSource.unfreezeAll();
        this.handleLoadError(error);
      });
  },

  getAsset(assetName, callback) {
    return this.readerObject.getAsset(assetName)
      .then(response => callback(response))
      .catch(error => this.handleLoadError(error));
  },

  getReader() {
    // Create a new reader for this query
    const readerClass = Reader.get(this.reader);
    if (!readerClass) {
      throw new Error("Unknown reader: " + this.reader);
    }

    return new readerClass(this.getPlainObject());
  },

  /**
   * get data
   */
  getData(dataId, what, whatId, args) {
    // if not specified data from what query, return nothing
    if (!dataId) return utils.warn("Data.js 'get' method doesn't like the dataId you gave it: " + dataId);
    return DataStorage.getData(dataId, what, whatId, args);
  },

  loadDataAvailability() {
    const conceptsQuery = {
      select: {
        key: ["key", "value"],
        value: []
      },
      from: "concepts.schema"
    };
    const entitiesQuery = utils.extend({}, conceptsQuery, { from: "entities.schema" });
    const datapointsQuery = utils.extend({}, conceptsQuery, { from: "datapoints.schema" });

    return Promise.all([
      this.load(conceptsQuery),
      this.load(entitiesQuery),
      this.load(datapointsQuery)
    ])
      .then(this.handleDataAvailabilityResponse.bind(this))
      .catch(error => this.handleLoadError(error));
  },

  handleDataAvailabilityResponse(dataIds) {
    this.keyAvailability = new Map();
    this.dataAvailability = [];
    dataIds.forEach(dataId => {
      const collection = this.getData(dataId, "query").from.split(".")[0];
      this.dataAvailability[collection] = [];
      this.getData(dataId).forEach(kvPair => {
        const key = (typeof kvPair.key === "string" ? JSON.parse(kvPair.key) : kvPair.key).sort(); // sort to get canonical form (can be removed when reader gives back canonical)

        this.dataAvailability[collection].push({
          key: new Set(key),
          value: kvPair.value
        });

        this.keyAvailability.set(key.join(","), key);
      });
    });
  },

  loadConceptProps() {
    // only selecting concept properties which Vizabi needs and are available in dataset
    const vizabiConceptProps = [
      "concept_type",
      "domain",
      "totals_among_entities",
      "indicator_url",
      "color",
      "scales",
      "interpolation",
      "tags",
      "name",
      "name_short",
      "name_catalog",
      "description",
      "format"
    ];
    const availableConceptProps = this.dataAvailability.concepts.map(m => m.value);
    const availableVizabiConceptProps = vizabiConceptProps.filter(n => availableConceptProps.includes(n));

    const query = {
      select: {
        key: ["concept"],
        value: availableVizabiConceptProps
      },
      from: "concepts",
      where: {},
      language: this.getClosestModel("locale").id,
    };

    return this.load(query)
      .then(this.handleConceptPropsResponse.bind(this))
      .catch(error => this.handleLoadError(error));
  },

  handleConceptPropsResponse(dataId) {

    this.conceptDictionary = { _default: { concept: "_default", concept_type: "string", use: "constant", scales: ["ordinal"], tags: "_root" } };
    this.conceptArray = [];

    this.getData(dataId).forEach(d => {
      const concept = {};

      concept["concept"] = d.concept;
      concept["concept_type"] = d.concept_type;
      concept["sourceLink"] = d.indicator_url;
      try {
        concept["color"] = d.color && d.color !== "" ? (typeof d.color === "string" ? JSON.parse(d.color) : d.color) : null; //
      } catch (e) {
        concept["color"] = null;
      }
      try {
        concept["scales"] = d.scales && d.color !== "" ? (typeof d.scales === "string" ? JSON.parse(d.scales) : d.scales) : null;
      } catch (e) {
        concept["scales"] = null;
      }
      if (!concept.scales) {
        switch (d.concept_type) {
          case "measure": concept.scales = ["linear", "log"]; break;
          case "string": concept.scales = ["ordinal"]; break;
          case "entity_domain": concept.scales = ["ordinal"]; break;
          case "entity_set": concept.scales = ["ordinal"]; break;
          case "boolean": concept.scales = ["ordinal"]; break;
          case "time": concept.scales = ["time"]; break;
          default: concept.scales = ["linear", "log"];
        }
      }
      if (d.interpolation) {
        concept["interpolation"] = d.interpolation;
      } else if (d.concept_type == "measure") {
        concept["interpolation"] = concept.scales && concept.scales[0] == "log" ? "exp" : "linear";
      } else if (d.concept_type == "time") {
        concept["interpolation"] = "linear";
      } else {
        concept["interpolation"] = "stepMiddle";
      }
      concept["domain"] = d.domain;
      concept["totals_among_entities"] = d.totals_among_entities;
      concept["tags"] = d.tags;
      concept["format"] = d.format;
      concept["name"] = d.name || d.concept || "";
      concept["name_catalog"] = d.name_catalog || "";
      concept["name_short"] = d.name_short || d.name || d.concept || "";
      concept["description"] = d.description;
      this.conceptDictionary[d.concept] = concept;
      this.conceptArray.push(concept);
    });

  },

  getConceptprops(which) {
    if (typeof which !== "undefined") {
      if (!this.conceptDictionary[which]) {
        utils.warn("The concept " + which + " is not found in the dictionary");
        return null;
      }
      return this.conceptDictionary[which];
    }
    return this.conceptDictionary;
  },

  getConcept({ index: index = 0, type: type = null, includeOnlyIDs: includeOnlyIDs = [], excludeIDs: excludeIDs = [] } = { }) {
    if (!type && includeOnlyIDs.length == 0 && excludeIDs.length == 0) {
      return null;
    }

    const filtered = this.conceptArray.filter(f =>
      (!type || !f.concept_type || f.concept_type === type)
      && (includeOnlyIDs.length == 0 || includeOnlyIDs.indexOf(f.concept) !== -1)
      && (excludeIDs.length == 0 || excludeIDs.indexOf(f.concept) == -1)
    );
    return filtered[index] || filtered[filtered.length - 1];
  },

  getDatasetName() {
    if (this.readerObject.getDatasetInfo) {
      const meta = this.readerObject.getDatasetInfo();
      return meta.name + (meta.version ? " " + meta.version : "");
    }
    return this._name.replace("data_", "");
  },

  setGrouping(dataId, grouping) {
    DataStorage.setGrouping(dataId, grouping);
  },

  getFrames(dataId, framesArray, keys) {
    return DataStorage.getFrames(dataId, framesArray, keys, this.getConceptprops());
  },

  getFrame(dataId, framesArray, neededFrame, keys) {
    //can only be called after getFrames()
    return DataStorage.getFrame(dataId, framesArray, neededFrame, keys);
  },

  listenFrame(dataId, framesArray, keys,  cb) {
    DataStorage.listenFrame(dataId, framesArray, keys,  cb);
  },

  handleLoadError(error) {
    error.browserDetails = utils.getBrowserDetails();
    error.osName = utils.getOSname();
    error.homepoint = window.location.href;
    error.time = (new Date()).toString();
    this._super(error);
  },

});

export default DataModel;
<commit_msg>Replace indicator_url with source_url<commit_after>import * as utils from "base/utils";
import Model from "base/model";
import Reader from "base/reader";
import EventSource from "base/events";
import { DataStorage } from "base/datastorage";
/*
 * VIZABI Data Model (model.data)
 */

const DataModel = Model.extend({

  /**
   * Default values for this model
   */
  getClassDefaults() {
    const defaults = {
      reader: "csv"
    };
    return utils.deepExtend(this._super(), defaults);
  },

  trackInstances: true,

  /**
   * Initializes the data model.
   * @param {Object} values The initial values of this model
   * @param parent A reference to the parent model
   * @param {Object} bind Initial events to bind
   */
  init(name, values, parent, bind) {

    this._type = "data";

    this.queryQueue = {};
    this._collection = {};
    this._collectionPromises = {}; // stores promises, making sure we don't do one calulation twice

    //same constructor as parent, with same arguments
    this._super(name, values, parent, bind);

    this.readerObject = this.getReader();

  },

  /**
   * Loads concept properties when all other models are also starting to load data
   * @return {Promise} Promise which resolves when concepts are loaded
   */
  preloadData() {
    return this.loadDataAvailability()
      .then(this.loadConceptProps.bind(this));
  },

  /**
   * Loads resource from reader or cache
   * @param {Array} query Array with queries to be loaded
   * @param {Object} parsers An object with concepts as key and parsers as value
   * @param {*} evts ?
   */
  load(query, parsers = {}, sideLoad) {
    // deep clone to prevent one query sent to multiple data objects being manipulated cross-data model.
    // For example one query sent to two different waffle server datasets.
    query = utils.deepClone(query);
    // add waffle server specific query clauses if set
    if (this.dataset) query.dataset = this.dataset;
    if (this.version) query.version = this.version;
    const dataId = DataStorage.getDataId(query, this.readerObject, parsers);
    if (dataId) {
      if (!query.grouping) return Promise.resolve(dataId);
      return DataStorage.aggregateData(dataId, query, this.readerObject, this.getConceptprops());
    }
    utils.timeStamp("Vizabi Data: Loading Data");
    if (!sideLoad) {
      EventSource.freezeAll([
        "hook_change",
        "resize"
      ]);
    }

    return DataStorage.loadFromReader(query, parsers, this.readerObject)
      .then(dataId => {
        if (!query.grouping) return dataId;
        return DataStorage.aggregateData(dataId, query, this.readerObject, this.getConceptprops());
      })
      .then(dataId => {
        if (!sideLoad) EventSource.unfreezeAll();
        return dataId;
      })
      .catch(error => {
        if (!sideLoad) EventSource.unfreezeAll();
        this.handleLoadError(error);
      });
  },

  getAsset(assetName, callback) {
    return this.readerObject.getAsset(assetName)
      .then(response => callback(response))
      .catch(error => this.handleLoadError(error));
  },

  getReader() {
    // Create a new reader for this query
    const readerClass = Reader.get(this.reader);
    if (!readerClass) {
      throw new Error("Unknown reader: " + this.reader);
    }

    return new readerClass(this.getPlainObject());
  },

  /**
   * get data
   */
  getData(dataId, what, whatId, args) {
    // if not specified data from what query, return nothing
    if (!dataId) return utils.warn("Data.js 'get' method doesn't like the dataId you gave it: " + dataId);
    return DataStorage.getData(dataId, what, whatId, args);
  },

  loadDataAvailability() {
    const conceptsQuery = {
      select: {
        key: ["key", "value"],
        value: []
      },
      from: "concepts.schema"
    };
    const entitiesQuery = utils.extend({}, conceptsQuery, { from: "entities.schema" });
    const datapointsQuery = utils.extend({}, conceptsQuery, { from: "datapoints.schema" });

    return Promise.all([
      this.load(conceptsQuery),
      this.load(entitiesQuery),
      this.load(datapointsQuery)
    ])
      .then(this.handleDataAvailabilityResponse.bind(this))
      .catch(error => this.handleLoadError(error));
  },

  handleDataAvailabilityResponse(dataIds) {
    this.keyAvailability = new Map();
    this.dataAvailability = [];
    dataIds.forEach(dataId => {
      const collection = this.getData(dataId, "query").from.split(".")[0];
      this.dataAvailability[collection] = [];
      this.getData(dataId).forEach(kvPair => {
        const key = (typeof kvPair.key === "string" ? JSON.parse(kvPair.key) : kvPair.key).sort(); // sort to get canonical form (can be removed when reader gives back canonical)

        this.dataAvailability[collection].push({
          key: new Set(key),
          value: kvPair.value
        });

        this.keyAvailability.set(key.join(","), key);
      });
    });
  },

  loadConceptProps() {
    // only selecting concept properties which Vizabi needs and are available in dataset
    const vizabiConceptProps = [
      "concept_type",
      "domain",
      "totals_among_entities",
      "source_url",
      "color",
      "scales",
      "interpolation",
      "tags",
      "name",
      "name_short",
      "name_catalog",
      "description",
      "format"
    ];
    const availableConceptProps = this.dataAvailability.concepts.map(m => m.value);
    const availableVizabiConceptProps = vizabiConceptProps.filter(n => availableConceptProps.includes(n));

    const query = {
      select: {
        key: ["concept"],
        value: availableVizabiConceptProps
      },
      from: "concepts",
      where: {},
      language: this.getClosestModel("locale").id,
    };

    return this.load(query)
      .then(this.handleConceptPropsResponse.bind(this))
      .catch(error => this.handleLoadError(error));
  },

  handleConceptPropsResponse(dataId) {

    this.conceptDictionary = { _default: { concept: "_default", concept_type: "string", use: "constant", scales: ["ordinal"], tags: "_root" } };
    this.conceptArray = [];

    this.getData(dataId).forEach(d => {
      const concept = {};

      concept["concept"] = d.concept;
      concept["concept_type"] = d.concept_type;
      concept["sourceLink"] = d.source_url;
      try {
        concept["color"] = d.color && d.color !== "" ? (typeof d.color === "string" ? JSON.parse(d.color) : d.color) : null; //
      } catch (e) {
        concept["color"] = null;
      }
      try {
        concept["scales"] = d.scales && d.color !== "" ? (typeof d.scales === "string" ? JSON.parse(d.scales) : d.scales) : null;
      } catch (e) {
        concept["scales"] = null;
      }
      if (!concept.scales) {
        switch (d.concept_type) {
          case "measure": concept.scales = ["linear", "log"]; break;
          case "string": concept.scales = ["ordinal"]; break;
          case "entity_domain": concept.scales = ["ordinal"]; break;
          case "entity_set": concept.scales = ["ordinal"]; break;
          case "boolean": concept.scales = ["ordinal"]; break;
          case "time": concept.scales = ["time"]; break;
          default: concept.scales = ["linear", "log"];
        }
      }
      if (d.interpolation) {
        concept["interpolation"] = d.interpolation;
      } else if (d.concept_type == "measure") {
        concept["interpolation"] = concept.scales && concept.scales[0] == "log" ? "exp" : "linear";
      } else if (d.concept_type == "time") {
        concept["interpolation"] = "linear";
      } else {
        concept["interpolation"] = "stepMiddle";
      }
      concept["domain"] = d.domain;
      concept["totals_among_entities"] = d.totals_among_entities;
      concept["tags"] = d.tags;
      concept["format"] = d.format;
      concept["name"] = d.name || d.concept || "";
      concept["name_catalog"] = d.name_catalog || "";
      concept["name_short"] = d.name_short || d.name || d.concept || "";
      concept["description"] = d.description;
      this.conceptDictionary[d.concept] = concept;
      this.conceptArray.push(concept);
    });

  },

  getConceptprops(which) {
    if (typeof which !== "undefined") {
      if (!this.conceptDictionary[which]) {
        utils.warn("The concept " + which + " is not found in the dictionary");
        return null;
      }
      return this.conceptDictionary[which];
    }
    return this.conceptDictionary;
  },

  getConcept({ index: index = 0, type: type = null, includeOnlyIDs: includeOnlyIDs = [], excludeIDs: excludeIDs = [] } = { }) {
    if (!type && includeOnlyIDs.length == 0 && excludeIDs.length == 0) {
      return null;
    }

    const filtered = this.conceptArray.filter(f =>
      (!type || !f.concept_type || f.concept_type === type)
      && (includeOnlyIDs.length == 0 || includeOnlyIDs.indexOf(f.concept) !== -1)
      && (excludeIDs.length == 0 || excludeIDs.indexOf(f.concept) == -1)
    );
    return filtered[index] || filtered[filtered.length - 1];
  },

  getDatasetName() {
    if (this.readerObject.getDatasetInfo) {
      const meta = this.readerObject.getDatasetInfo();
      return meta.name + (meta.version ? " " + meta.version : "");
    }
    return this._name.replace("data_", "");
  },

  setGrouping(dataId, grouping) {
    DataStorage.setGrouping(dataId, grouping);
  },

  getFrames(dataId, framesArray, keys) {
    return DataStorage.getFrames(dataId, framesArray, keys, this.getConceptprops());
  },

  getFrame(dataId, framesArray, neededFrame, keys) {
    //can only be called after getFrames()
    return DataStorage.getFrame(dataId, framesArray, neededFrame, keys);
  },

  listenFrame(dataId, framesArray, keys,  cb) {
    DataStorage.listenFrame(dataId, framesArray, keys,  cb);
  },

  handleLoadError(error) {
    error.browserDetails = utils.getBrowserDetails();
    error.osName = utils.getOSname();
    error.homepoint = window.location.href;
    error.time = (new Date()).toString();
    this._super(error);
  },

});

export default DataModel;
<|endoftext|>