<commit_before>/**
 * @license
 * Copyright 2016 Google Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

foam.CLASS({
  package: 'foam.dao',
  name: 'SyncDAO',
  extends: 'foam.dao.ProxyDAO',

  documentation: `SyncDAO synchronizes data between multiple clients' offline
      caches and a server. When syncronizing, each client tracks the last-seen
      version of each object, or if the object was deleted. The most recent
      version is retained.

      Objects put to this DAO are convered to a versioned sub-class of "of". The
      "version_" property will be automatically be incremented as changes are
      put() into the SyncDAO. The SyncDAO will expect to find objects in
      remoteDAO that have been marked as deleted; this is interpreted as a
      signal to delete records (during initial sync or polling). Details on
      versioned class generation, and the "version_" and "deleted_" properties
      are in the foam.version package.

      Remote DAOs that interact with SyncDAO clients should be decorated with
      foam.dao.VersionNoDAO, or similar, to provision new version numbers for
      records being stored.`,

  requires: [
    'foam.dao.ArraySink',
    'foam.dao.sync.VersionedSyncRecord',
    'foam.version.VersionTrait',
    'foam.version.VersionedClassFactorySingleton'
  ],

  implements: [ 'foam.mlang.Expressions' ],

  imports: [ 'setInterval' ],

  classes: [
    {
      name: 'AdapterSink',
      extends: 'foam.dao.ProxySink',

      properties: [
        {
          class: 'Class',
          name: 'of'
        }
      ],

      methods: [
        function put(o, sub) {
          this.delegate.put(this.of.create(o, this.__subContext__), sub);
        }
      ]
    }
  ],

  properties: [
    {
      class: 'foam.dao.DAOProperty',
      name: 'remoteDAO',
      documentation: 'The shared server DAO to synchronize to.',
      transient: true,
      required: true
    },
    {
      name: 'delegate',
      documentation: 'The local cache to sync with the server DAO.'
    },
    {
      class: 'foam.dao.DAOProperty',
      name: 'syncRecordDAO',
      documentation: `The DAO in which to store VersionedSyncRecords for each
          object. Each client tracks their own sync state in a separate
          syncRecordDAO.`,
      transient: true,
      required: true
    },
    {
      name: 'of',
      documentation: 'The class of object this DAO will store.',
      required: true,
      transient: true
    },
    {
      class: 'Boolean',
      name: 'polling',
      documentation: `If using a remote DAO without push capabilities, such as
          an HTTP server, polling will periodically attempt to synchronize.`,
      postSet: function(old, nu) {
        this.sync_ = nu ? this.pollingSync_ : this.syncToRemote_;
      }
    },
    {
      class: 'Int',
      name: 'pollingFrequency',
      documentation: `If using polling, pollingFrequency will determine the
        number of milliseconds to wait between synchronization attempts.`,
      value: 1000
    },
    {
      name: 'synced',
      documentation: `A promise that resolves after any in-flight
          synchronization pass completes.`,
      factory: function() { return Promise.resolve(); }
    },
    {
      name: 'versionedOf_',
      expression: function(of) {
        return this.versionedClassFactory_.get(of);
      }
    },
    {
      name: 'versionedClassFactory_',
      factory: function() {
        return this.VersionedClassFactorySingleton.create();
      }
    },
    {
      name: 'syncRecordWriteSync_',
      factory: function() { return Promise.resolve(); }
    },
    {
      class: 'Function',
      name: 'sync_',
      documentation: `Selected sync strategy; either syncToRemote_() or
          pollingSync_().`,
      factory: function() {
        return this.polling ? this.pollingSync_ : this.syncToRemote_;
      }
    }
  ],

  methods: [
    function init() {
      this.SUPER();

      // Only listen to DAOs that support push (i.e., do not require polling).
      if ( ! this.polling )
        this.remoteDAO$proxy.sub('on', this.onRemoteUpdate);

      // Push initial data from delegate.
      var self = this;
      self.synced = self.delegate.select().
          then(function(sink) {
            var array = sink.array;
            for ( var i = 0; i < array.length; i++ ) {
              self.syncRecordDAO.put(self.VersionedSyncRecord.create({
                id: array[i].id,
                version_: -1
              }));
            }
          }).
          // Sync as if polling on initial sync.
          then(self.pollingSync_.bind(self));

      // Setup polling after initial sync.
      if ( ! self.polling ) return;
      self.synced.then(function() {
        self.setInterval(function() {
          self.sync();
        }, self.pollingFrequency);
      });
    },
    function sync() {
      // Sync after any sync(s) in progress complete.
      return this.synced = this.synced.then(function() {
        return this.sync_();
      }.bind(this));
    },

    //
    // DAO overrides.
    //

    function put_(x, inputObj) {
      var obj = this.versionedOf_.create(inputObj, x);
      var self = this;
      var ret;
      return self.withSyncRecordTx_(function() {
        return self.delegate.put_(x, obj).then(function(o) {
          ret = o;
          // Updates the object's last seen info.
          return self.syncRecordDAO.put_(x, self.VersionedSyncRecord.create({
            id: o.id,
            version_: -1
          }));
        });
      }).then(self.onLocalUpdate).then(function() { return ret; });
    },
    function remove_(x, inputObj) {
      var obj = this.versionedOf_.create(inputObj, x);
      var self = this;
      var ret;
      return self.withSyncRecordTx_(function() {
        return self.delegate.remove_(x, obj).then(function(o) {
          ret = o;
          // Marks the object as deleted.
          self.syncRecordDAO.put_(x, self.VersionedSyncRecord.create({
            id: obj.id,
            deleted_: true,
            version_: -1
          }));
        });
      }).then(self.onLocalUpdate).then(function() { return ret; });
    },
    function find_(x, objOrId) {
      if ( this.of.isInstance(objOrId) )
        objOrId = this.versionedOf_.create(objOrId, x);
      return this.delegate.find_(x, objOrId).then(function(o) {
        if ( o === null ) return o;

        return this.of.create(o, x);
      }.bind(this));
    },
    function select_(x, sink, skip, limit, order, predicate) {
      sink = sink || this.ArraySink.create();
      var adapterSink = this.AdapterSink.create({
        of: this.of,
        delegate: sink
      }, x);
      return this.delegate.
          select_(x, adapterSink, skip, limit, order, predicate).
          then(function() { return sink; });
    },
    function removeAll_(x, skip, limit, order, predicate) {
      // Marks all the removed objects' sync records as deleted via remove_().
      return this.delegate.select_(x, null, skip, limit, order, predicate).
          then(function(a) {
            a = a.array;
            var promises = [];

            for ( var i = 0 ; i < a.length ; i++ ) {
              promises.push(this.remove_(x, a[i]));
            }

            return Promise.all(promises);
          }.bind(this));
    },

    //
    // Private synchronization details.
    //

    {
      name: 'putFromRemote_',
      documentation: 'Process a put() to cache from remote.',
      code: function(obj) {
        var self = this;
        var ret;
        return self.withSyncRecordTx_(function() {
          return self.delegate.put(obj).then(function(o) {
            ret = o;
            return self.syncRecordDAO.put(self.VersionedSyncRecord.create({
              id: o.id,
              version_: self.VersionTrait.VERSION_.f(o)
            }));
          });
        }).then(function() { return ret; });
      }
    },
    {
      name: 'removeFromRemote_',
      documentation: 'Process a remove() on cache from remote.',
      code: function(obj) {
        var self = this;
        var ret;
        return self.withSyncRecordTx_(function() {
          return self.delegate.remove(obj).then(function(o) {
            ret = o;
            return self.syncRecordDAO.put(self.VersionedSyncRecord.create({
              id: obj.id,
              version_: self.VersionTrait.VERSION_.f(obj),
              deleted_: true
            }));
          });
        }).then(function() { return ret; });
      }
    },
    {
      name: 'resetFromRemote_',
      documentation: 'Process a reset signal on cache from remote.',
      code: function(obj) {
        // Clear sync records and data not associated with unsynced data, then
        // sync.
        var self = this;
        var ret;
        return self.withSyncRecordTx_(function() {
          return self.syncRecordDAO.
              where(self.GT(self.VersionedSyncRecord.VERSION_, -1)).
              removeAll().
              then(self.syncRecordDAO.select.bind(self.syncRecordDAO)).
              then(function(sink) {
                var idsToKeep = sink.array.map(function(syncRecord) {
                  return syncRecord.id;
                });
                return self.delegate.where(
                    self.NOT(self.IN(self.of.ID, idsToKeep))).
                    removeAll();
              });
        }).then(self.sync.bind(self));
      }
    },
    {
      name: 'pollingSync_',
      documentation: `Polling synchronization strategy. Determine current
          version, then push to remote, then pull update from remote.`,
      code: function() {
        var self = this;
        var VERSION_ = self.VersionedSyncRecord.VERSION_;
        return self.syncRecordDAO.
            // Like MAX(), but faster on DAOs that can optimize order+limit.
            orderBy(self.DESC(VERSION_)).limit(1).
            select().then(function(sink) {
              var minVersionNo = sink.array[0] && VERSION_.f(sink.array[0]) || 0;
              return self.syncToRemote_().then(function() {
                return self.remoteDAO.
                  where(self.GT(self.VersionTrait.VERSION_, minVersionNo)).
                  orderBy(self.VersionTrait.VERSION_).
                  select();
              }).then(function(sink) {
                var array = sink.array;
                var promises = [];

                for ( var i = 0 ; i < array.length ; i++ ) {
                  if ( self.VersionTrait.DELETED_.f(array[i]) ) {
                    promises.push(self.removeFromRemote_(array[i]));
                  } else {
                    promises.push(self.putFromRemote_(array[i]));
                  }
                }

                return Promise.all(promises);
              });
            });
      }
    },
    {
      name: 'syncToRemote_',
      documentation: `Push synchronization strategy: Push data from cach to
          remote; rely on pushed updates from server.`,
      code: function() {
        var self = this;

        return this.syncRecordDAO.
          where(self.EQ(this.VersionedSyncRecord.VERSION_, -1)).
          select().then(function(records) {
            records = records.array;
            var promises = [];

            for ( var i = 0 ; i < records.length ; i++ ) {
              var record = records[i];
              var id = record.id;
              var deleted = record.deleted;

              if ( deleted ) {
                var obj = self.versionedOf_.create(undefined, self);
                obj.id = id;
                var promise = self.remoteDAO.remove(obj);

                // When not polling, server result will processed when
                // onRemoteUpdate listener is fired.
                if ( self.polling ) {
                  promises.push(promise.then(function() {
                    var propName = self.VersionTrait.VERSION_.name;
                    // Ensure that obj SyncRecord does not remain queued (i.e.,
                    // does not have version_ = -1).
                    obj[propName] = Math.max(obj[propName], 0);
                    return self.removeFromRemote_(obj);
                  }));
                } else {
                  promises.push(promise);
                }
              } else {
                // TODO(markdittmer): Deal appropriately with failed updates.
                promises.push(self.delegate.find(id).then(function(obj) {
                  if ( ! obj ) return null;
                  var ret = self.remoteDAO.put(obj);

                  // When not polling, server result will processed when
                  // onRemoteUpdate listener is fired.
                  if ( self.polling ) {
                    ret = ret.then(function(o) {
                      return self.putFromRemote_(o);
                    });
                  }

                  return ret;
                }));
              }
            }

            return Promise.all(promises);
          });
      }
    },
    {
      name: 'withSyncRecordTx_',
      documentation: `Run a computation that writes to syncRecordDAO
          transactionally with respect to other syncRecordDAO writes.`,
      code: function(f) {
        return this.syncRecordWriteSync_ = this.syncRecordWriteSync_.then(f);
      }
    }
  ],

  listeners: [
    {
      name: 'onRemoteUpdate',
      documentation: 'Respond to push event from remote.',
      code: function(s, on, event, obj) {
        if ( event == 'put' ) {
          if ( this.VersionTrait.DELETED_.f(obj) ) this.removeFromRemote_(obj);
          else                                     this.putFromRemote_(obj);
        } else if ( event === 'remove' ) {
          throw new Error(`SyncDAO recieved remove() event;
                              expected put(deleted)-as-remove()`);
        } else if ( event === 'reset' ) {
          this.resetFromRemote_();
        }
      }
    },
    {
      name: 'onLocalUpdate',
      isMerged: true,
      mergeDelay: 100,
      code: function() {
        this.sync();
      }
    }
  ]
});
<commit_msg>Add FUTURE note for pulling versioned/unversioned type adaptation out of SyncDAO<commit_after>/**
 * @license
 * Copyright 2016 Google Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

foam.CLASS({
  package: 'foam.dao',
  name: 'SyncDAO',
  extends: 'foam.dao.ProxyDAO',

  documentation: `SyncDAO synchronizes data between multiple clients' offline
      caches and a server. When syncronizing, each client tracks the last-seen
      version of each object, or if the object was deleted. The most recent
      version is retained.

      Objects put to this DAO are convered to a versioned sub-class of "of". The
      "version_" property will be automatically be incremented as changes are
      put() into the SyncDAO. The SyncDAO will expect to find objects in
      remoteDAO that have been marked as deleted; this is interpreted as a
      signal to delete records (during initial sync or polling). Details on
      versioned class generation, and the "version_" and "deleted_" properties
      are in the foam.version package.

      Remote DAOs that interact with SyncDAO clients should be decorated with
      foam.dao.VersionNoDAO, or similar, to provision new version numbers for
      records being stored.`,

  requires: [
    'foam.dao.ArraySink',
    'foam.dao.sync.VersionedSyncRecord',
    'foam.version.VersionTrait',
    'foam.version.VersionedClassFactorySingleton'
  ],

  implements: [ 'foam.mlang.Expressions' ],

  imports: [ 'setInterval' ],

  classes: [
    {
      name: 'AdapterSink',
      extends: 'foam.dao.ProxySink',

      properties: [
        {
          class: 'Class',
          name: 'of'
        }
      ],

      methods: [
        function put(o, sub) {
          this.delegate.put(this.of.create(o, this.__subContext__), sub);
        }
      ]
    }
  ],

  properties: [
    {
      class: 'foam.dao.DAOProperty',
      name: 'remoteDAO',
      documentation: 'The shared server DAO to synchronize to.',
      transient: true,
      required: true
    },
    {
      name: 'delegate',
      documentation: 'The local cache to sync with the server DAO.'
    },
    {
      class: 'foam.dao.DAOProperty',
      name: 'syncRecordDAO',
      documentation: `The DAO in which to store VersionedSyncRecords for each
          object. Each client tracks their own sync state in a separate
          syncRecordDAO.`,
      transient: true,
      required: true
    },
    {
      name: 'of',
      documentation: 'The class of object this DAO will store.',
      required: true,
      transient: true
    },
    {
      class: 'Boolean',
      name: 'polling',
      documentation: `If using a remote DAO without push capabilities, such as
          an HTTP server, polling will periodically attempt to synchronize.`,
      postSet: function(old, nu) {
        this.sync_ = nu ? this.pollingSync_ : this.syncToRemote_;
      }
    },
    {
      class: 'Int',
      name: 'pollingFrequency',
      documentation: `If using polling, pollingFrequency will determine the
        number of milliseconds to wait between synchronization attempts.`,
      value: 1000
    },
    {
      name: 'synced',
      documentation: `A promise that resolves after any in-flight
          synchronization pass completes.`,
      factory: function() { return Promise.resolve(); }
    },
    // FUTURE: SyncDAO should not be responsible for adapting between
    // versioned/unversioned classes; should decorate with some kind of
    // TypeAdapterDAO and assert that SyncDAO is "of" a versioned class.
    {
      name: 'versionedOf_',
      expression: function(of) {
        return this.versionedClassFactory_.get(of);
      }
    },
    {
      name: 'versionedClassFactory_',
      factory: function() {
        return this.VersionedClassFactorySingleton.create();
      }
    },
    {
      name: 'syncRecordWriteSync_',
      factory: function() { return Promise.resolve(); }
    },
    {
      class: 'Function',
      name: 'sync_',
      documentation: `Selected sync strategy; either syncToRemote_() or
          pollingSync_().`,
      factory: function() {
        return this.polling ? this.pollingSync_ : this.syncToRemote_;
      }
    }
  ],

  methods: [
    function init() {
      this.SUPER();

      // Only listen to DAOs that support push (i.e., do not require polling).
      if ( ! this.polling )
        this.remoteDAO$proxy.sub('on', this.onRemoteUpdate);

      // Push initial data from delegate.
      var self = this;
      self.synced = self.delegate.select().
          then(function(sink) {
            var array = sink.array;
            for ( var i = 0; i < array.length; i++ ) {
              self.syncRecordDAO.put(self.VersionedSyncRecord.create({
                id: array[i].id,
                version_: -1
              }));
            }
          }).
          // Sync as if polling on initial sync.
          then(self.pollingSync_.bind(self));

      // Setup polling after initial sync.
      if ( ! self.polling ) return;
      self.synced.then(function() {
        self.setInterval(function() {
          self.sync();
        }, self.pollingFrequency);
      });
    },
    function sync() {
      // Sync after any sync(s) in progress complete.
      return this.synced = this.synced.then(function() {
        return this.sync_();
      }.bind(this));
    },

    //
    // DAO overrides.
    //

    function put_(x, inputObj) {
      var obj = this.versionedOf_.create(inputObj, x);
      var self = this;
      var ret;
      return self.withSyncRecordTx_(function() {
        return self.delegate.put_(x, obj).then(function(o) {
          ret = o;
          // Updates the object's last seen info.
          return self.syncRecordDAO.put_(x, self.VersionedSyncRecord.create({
            id: o.id,
            version_: -1
          }));
        });
      }).then(self.onLocalUpdate).then(function() { return ret; });
    },
    function remove_(x, inputObj) {
      var obj = this.versionedOf_.create(inputObj, x);
      var self = this;
      var ret;
      return self.withSyncRecordTx_(function() {
        return self.delegate.remove_(x, obj).then(function(o) {
          ret = o;
          // Marks the object as deleted.
          self.syncRecordDAO.put_(x, self.VersionedSyncRecord.create({
            id: obj.id,
            deleted_: true,
            version_: -1
          }));
        });
      }).then(self.onLocalUpdate).then(function() { return ret; });
    },
    function find_(x, objOrId) {
      if ( this.of.isInstance(objOrId) )
        objOrId = this.versionedOf_.create(objOrId, x);
      return this.delegate.find_(x, objOrId).then(function(o) {
        if ( o === null ) return o;

        return this.of.create(o, x);
      }.bind(this));
    },
    function select_(x, sink, skip, limit, order, predicate) {
      sink = sink || this.ArraySink.create();
      var adapterSink = this.AdapterSink.create({
        of: this.of,
        delegate: sink
      }, x);
      return this.delegate.
          select_(x, adapterSink, skip, limit, order, predicate).
          then(function() { return sink; });
    },
    function removeAll_(x, skip, limit, order, predicate) {
      // Marks all the removed objects' sync records as deleted via remove_().
      return this.delegate.select_(x, null, skip, limit, order, predicate).
          then(function(a) {
            a = a.array;
            var promises = [];

            for ( var i = 0 ; i < a.length ; i++ ) {
              promises.push(this.remove_(x, a[i]));
            }

            return Promise.all(promises);
          }.bind(this));
    },

    //
    // Private synchronization details.
    //

    {
      name: 'putFromRemote_',
      documentation: 'Process a put() to cache from remote.',
      code: function(obj) {
        var self = this;
        var ret;
        return self.withSyncRecordTx_(function() {
          return self.delegate.put(obj).then(function(o) {
            ret = o;
            return self.syncRecordDAO.put(self.VersionedSyncRecord.create({
              id: o.id,
              version_: self.VersionTrait.VERSION_.f(o)
            }));
          });
        }).then(function() { return ret; });
      }
    },
    {
      name: 'removeFromRemote_',
      documentation: 'Process a remove() on cache from remote.',
      code: function(obj) {
        var self = this;
        var ret;
        return self.withSyncRecordTx_(function() {
          return self.delegate.remove(obj).then(function(o) {
            ret = o;
            return self.syncRecordDAO.put(self.VersionedSyncRecord.create({
              id: obj.id,
              version_: self.VersionTrait.VERSION_.f(obj),
              deleted_: true
            }));
          });
        }).then(function() { return ret; });
      }
    },
    {
      name: 'resetFromRemote_',
      documentation: 'Process a reset signal on cache from remote.',
      code: function(obj) {
        // Clear sync records and data not associated with unsynced data, then
        // sync.
        var self = this;
        var ret;
        return self.withSyncRecordTx_(function() {
          return self.syncRecordDAO.
              where(self.GT(self.VersionedSyncRecord.VERSION_, -1)).
              removeAll().
              then(self.syncRecordDAO.select.bind(self.syncRecordDAO)).
              then(function(sink) {
                var idsToKeep = sink.array.map(function(syncRecord) {
                  return syncRecord.id;
                });
                return self.delegate.where(
                    self.NOT(self.IN(self.of.ID, idsToKeep))).
                    removeAll();
              });
        }).then(self.sync.bind(self));
      }
    },
    {
      name: 'pollingSync_',
      documentation: `Polling synchronization strategy. Determine current
          version, then push to remote, then pull update from remote.`,
      code: function() {
        var self = this;
        var VERSION_ = self.VersionedSyncRecord.VERSION_;
        return self.syncRecordDAO.
            // Like MAX(), but faster on DAOs that can optimize order+limit.
            orderBy(self.DESC(VERSION_)).limit(1).
            select().then(function(sink) {
              var minVersionNo = sink.array[0] && VERSION_.f(sink.array[0]) || 0;
              return self.syncToRemote_().then(function() {
                return self.remoteDAO.
                  where(self.GT(self.VersionTrait.VERSION_, minVersionNo)).
                  orderBy(self.VersionTrait.VERSION_).
                  select();
              }).then(function(sink) {
                var array = sink.array;
                var promises = [];

                for ( var i = 0 ; i < array.length ; i++ ) {
                  if ( self.VersionTrait.DELETED_.f(array[i]) ) {
                    promises.push(self.removeFromRemote_(array[i]));
                  } else {
                    promises.push(self.putFromRemote_(array[i]));
                  }
                }

                return Promise.all(promises);
              });
            });
      }
    },
    {
      name: 'syncToRemote_',
      documentation: `Push synchronization strategy: Push data from cach to
          remote; rely on pushed updates from server.`,
      code: function() {
        var self = this;

        return this.syncRecordDAO.
          where(self.EQ(this.VersionedSyncRecord.VERSION_, -1)).
          select().then(function(records) {
            records = records.array;
            var promises = [];

            for ( var i = 0 ; i < records.length ; i++ ) {
              var record = records[i];
              var id = record.id;
              var deleted = record.deleted;

              if ( deleted ) {
                var obj = self.versionedOf_.create(undefined, self);
                obj.id = id;
                var promise = self.remoteDAO.remove(obj);

                // When not polling, server result will processed when
                // onRemoteUpdate listener is fired.
                if ( self.polling ) {
                  promises.push(promise.then(function() {
                    var propName = self.VersionTrait.VERSION_.name;
                    // Ensure that obj SyncRecord does not remain queued (i.e.,
                    // does not have version_ = -1).
                    obj[propName] = Math.max(obj[propName], 0);
                    return self.removeFromRemote_(obj);
                  }));
                } else {
                  promises.push(promise);
                }
              } else {
                // TODO(markdittmer): Deal appropriately with failed updates.
                promises.push(self.delegate.find(id).then(function(obj) {
                  if ( ! obj ) return null;
                  var ret = self.remoteDAO.put(obj);

                  // When not polling, server result will processed when
                  // onRemoteUpdate listener is fired.
                  if ( self.polling ) {
                    ret = ret.then(function(o) {
                      return self.putFromRemote_(o);
                    });
                  }

                  return ret;
                }));
              }
            }

            return Promise.all(promises);
          });
      }
    },
    {
      name: 'withSyncRecordTx_',
      documentation: `Run a computation that writes to syncRecordDAO
          transactionally with respect to other syncRecordDAO writes.`,
      code: function(f) {
        return this.syncRecordWriteSync_ = this.syncRecordWriteSync_.then(f);
      }
    }
  ],

  listeners: [
    {
      name: 'onRemoteUpdate',
      documentation: 'Respond to push event from remote.',
      code: function(s, on, event, obj) {
        if ( event == 'put' ) {
          if ( this.VersionTrait.DELETED_.f(obj) ) this.removeFromRemote_(obj);
          else                                     this.putFromRemote_(obj);
        } else if ( event === 'remove' ) {
          throw new Error(`SyncDAO recieved remove() event;
                              expected put(deleted)-as-remove()`);
        } else if ( event === 'reset' ) {
          this.resetFromRemote_();
        }
      }
    },
    {
      name: 'onLocalUpdate',
      isMerged: true,
      mergeDelay: 100,
      code: function() {
        this.sync();
      }
    }
  ]
});
<|endoftext|>


<commit_before>/**
 * @license
 * Copyright 2019 The FOAM Authors. All Rights Reserved.
 * http://www.apache.org/licenses/LICENSE-2.0
 */

foam.CLASS({
  package: 'foam.glang',
  name: 'AbstractDateGlang',
  extends: 'foam.mlang.AbstractExpr',
  abstract: true,
  implements: [
    'foam.core.Serializable',
    'foam.mlang.order.Comparator',
  ],
  properties: [
    {
      class: 'foam.mlang.ExprProperty',
      name: 'delegate'
    }
  ],
  methods: [
    {
      name: 'createStatement',
      javaCode: 'return "";'
    },
    {
      name: 'prepareStatement',
      javaCode: '// noop'
    },
    {
      name: 'compare',
      code: function(o1, o2) {
        return foam.Date.compare(this.f(o1), this.f(o2));
      },
      javaCode: `
        java.util.Date date1 = (java.util.Date) f(o1);
        java.util.Date date2 = (java.util.Date) f(o2);
        return date1.compareTo(date2);
      `
    }
  ]
});

foam.CLASS({
  package: 'foam.glang',
  name: 'EndOfTimeSpan',
  extends: 'foam.glang.AbstractDateGlang',
  properties: [
    {
      class: 'Long',
      name: 'timeSpanMs'
    }
  ],
  methods: [
    {
      name: 'f',
      code: function(obj) {
        var ts = new Date(this.delegate.f(obj));
        var ms = this.timeSpanMs;
        return new Date(Math.floor(ts.getTime() / ms) * ms + ms - 1);
      },
      javaCode: `
        java.util.Date ts = (java.util.Date) getDelegate().f(obj);
        long ms = getTimeSpanMs();
        return new java.util.Date((ts.getTime() / ms) * ms + ms - 1);
      `
    }
  ]
});

foam.CLASS({
  package: 'foam.glang',
  name: 'EndOfDay',
  extends: 'foam.glang.AbstractDateGlang',
  methods: [
    {
      name: 'f',
      code: function(obj) {
        var ts = new Date(this.delegate.f(obj));
        ts.setHours(23, 59, 59);
        ts.setMilliseconds(999);
        return ts;
      },
      javaCode: `
// Convert to LocalDate
java.util.Date date = (java.util.Date) getDelegate().f(obj);
java.time.LocalDate localDate = java.time.Instant.ofEpochMilli(date.getTime()).atZone(java.time.ZoneId.systemDefault()).toLocalDate();

// Convert to LocalDateTime set to End of Day
java.time.LocalDateTime localDateTime = localDate.atTime(java.time.LocalTime.MAX);

// Convert to Date using LocalDateTime
return java.util.Date.from(localDateTime.atZone(java.time.ZoneId.systemDefault()).toInstant());
      `
    }
  ]
});

foam.CLASS({
  package: 'foam.glang',
  name: 'EndOfWeek',
  extends: 'foam.glang.AbstractDateGlang',
  properties: [
    {
      class: 'Int',
      name: 'startOfWeek',
      documentation: 'Value between 0 - Sunday and 6 - Saturday inclusive.  Indicates which day is considered the first day of a new week.',
      min: 0,
      max: 6,
      value: 0
    }
  ],
  methods: [
    {
      name: 'f',
      code: function(obj) {
        var ts = new Date(this.delegate.f(obj));

        var date = ts.getDate();
        var endOfWeek = (this.startOfWeek + 6) % 7;
        var day = ts.getDay();
        var daysToEndOfWeek = (endOfWeek - day + 7) % 7;

        ts.setDate(date + daysToEndOfWeek);

        ts.setHours(23, 59, 59);
        ts.setMilliseconds(999);

        return ts;
      },
      javaCode: `
// Convert to LocalDate
java.util.Date date = (java.util.Date) getDelegate().f(obj);
java.time.LocalDate localDate = java.time.Instant.ofEpochMilli(date.getTime()).atZone(java.time.ZoneId.systemDefault()).toLocalDate();

// Set to end of week
localDate = localDate.plusDays(6 - localDate.getDayOfWeek().getValue());

// Convert to LocalDateTime set to End of Day
java.time.LocalDateTime localDateTime = localDate.atTime(java.time.LocalTime.MAX);

// Convert to Date using LocalDateTime
return java.util.Date.from(localDateTime.atZone(java.time.ZoneId.systemDefault()).toInstant());
      `
    }
  ]
});

foam.CLASS({
  package: 'foam.glang',
  name: 'EndOfMonth',
  extends: 'foam.glang.AbstractDateGlang',
  methods: [
    {
      name: 'f',
      code: function(obj) {
        var ts = new Date(this.delegate.f(obj));
        ts.setMonth(ts.getMonth() + 1);
        ts.setDate(0);
        ts.setHours(23, 59, 59);
        ts.setMilliseconds(999);
        return ts;
        return ts.getTime() > Date.now() ? new Date() : ts;
      },
      javaCode: `
// Convert to LocalDate
java.util.Date date = (java.util.Date) getDelegate().f(obj);
java.time.LocalDate localDate = java.time.Instant.ofEpochMilli(date.getTime()).atZone(java.time.ZoneId.systemDefault()).toLocalDate();

// Set to end of month
localDate = localDate.plusDays(localDate.lengthOfMonth() - localDate.getDayOfMonth());

// Convert to LocalDateTime set to End of Day
java.time.LocalDateTime localDateTime = localDate.atTime(java.time.LocalTime.MAX);

// Convert to Date using LocalDateTime
return java.util.Date.from(localDateTime.atZone(java.time.ZoneId.systemDefault()).toInstant());
      `
    }
  ]
});

foam.CLASS({
  package: 'foam.glang',
  name: 'EndOfQuarter',
  extends: 'foam.glang.AbstractDateGlang',
  methods: [
    {
      name: 'f',
      code: function(obj) {
        var ts = new Date(this.delegate.f(obj));
        // N % 3 is how many months into the quarter we are 0 1 or 2
        // 2 - ( N % 3 ) is how many months left till EOQ.
        // N + ( 2 - ( N % 3 ) ) is the last month of this quarter
        // N + ( 2 - ( N % 3 ) ) + 1 is the first month of next quarter
        // which reduces to N + 3 - ( N % 3 ).
        ts.setMonth(ts.getMonth() + 3 - ( ts.getMonth() % 3 ));
        ts.setDate(0);
        ts.setHours(23, 59, 59);
        ts.setMilliseconds(999);
        return ts;
        return ts.getTime() > Date.now() ? new Date() : ts;
      },
      javaCode: `
// Convert to LocalDate
java.util.Date date = (java.util.Date) getDelegate().f(obj);
java.time.LocalDate localDate = java.time.Instant.ofEpochMilli(date.getTime()).atZone(java.time.ZoneId.systemDefault()).toLocalDate();

// Set month to end of quarter
localDate = localDate.plusMonths(2 - (localDate.getMonthValue() - 1) % 3);

// Set to end of month
localDate = localDate.plusDays(localDate.lengthOfMonth() - localDate.getDayOfMonth());

// Convert to LocalDateTime set to End of Day
java.time.LocalDateTime localDateTime = localDate.atTime(java.time.LocalTime.MAX);

// Convert to Date using LocalDateTime
return java.util.Date.from(localDateTime.atZone(java.time.ZoneId.systemDefault()).toInstant());
      `
    }
  ]
});

foam.CLASS({
  package: 'foam.glang',
  name: 'EndOfYear',
  extends: 'foam.glang.AbstractDateGlang',
  methods: [
    {
      name: 'f',
      code: function(obj) {
        var ts = new Date(this.delegate.f(obj));
        ts.setMonth(11);
        ts.setDate(31);
        ts.setHours(23, 59, 59);
        ts.setMilliseconds(999);
        return ts;
      },
      javaCode: `
java.util.Date date = (java.util.Date) getDelegate().f(obj);

java.time.Instant t = java.time.Instant.ofEpochMilli(date.getTime())
  .atZone(java.time.ZoneId.systemDefault())
  .toLocalDate()
  .with(java.time.temporal.TemporalAdjusters.lastDayOfYear())
  .atTime(23, 59, 59)
  .atZone(java.time.ZoneId.systemDefault())
  .toInstant();

return java.util.Date.from(t);
      `
    }
  ]
});<commit_msg>Removed unneccessary return<commit_after>/**
 * @license
 * Copyright 2019 The FOAM Authors. All Rights Reserved.
 * http://www.apache.org/licenses/LICENSE-2.0
 */

foam.CLASS({
  package: 'foam.glang',
  name: 'AbstractDateGlang',
  extends: 'foam.mlang.AbstractExpr',
  abstract: true,
  implements: [
    'foam.core.Serializable',
    'foam.mlang.order.Comparator',
  ],
  properties: [
    {
      class: 'foam.mlang.ExprProperty',
      name: 'delegate'
    }
  ],
  methods: [
    {
      name: 'createStatement',
      javaCode: 'return "";'
    },
    {
      name: 'prepareStatement',
      javaCode: '// noop'
    },
    {
      name: 'compare',
      code: function(o1, o2) {
        return foam.Date.compare(this.f(o1), this.f(o2));
      },
      javaCode: `
        java.util.Date date1 = (java.util.Date) f(o1);
        java.util.Date date2 = (java.util.Date) f(o2);
        return date1.compareTo(date2);
      `
    }
  ]
});

foam.CLASS({
  package: 'foam.glang',
  name: 'EndOfTimeSpan',
  extends: 'foam.glang.AbstractDateGlang',
  properties: [
    {
      class: 'Long',
      name: 'timeSpanMs'
    }
  ],
  methods: [
    {
      name: 'f',
      code: function(obj) {
        var ts = new Date(this.delegate.f(obj));
        var ms = this.timeSpanMs;
        return new Date(Math.floor(ts.getTime() / ms) * ms + ms - 1);
      },
      javaCode: `
        java.util.Date ts = (java.util.Date) getDelegate().f(obj);
        long ms = getTimeSpanMs();
        return new java.util.Date((ts.getTime() / ms) * ms + ms - 1);
      `
    }
  ]
});

foam.CLASS({
  package: 'foam.glang',
  name: 'EndOfDay',
  extends: 'foam.glang.AbstractDateGlang',
  methods: [
    {
      name: 'f',
      code: function(obj) {
        var ts = new Date(this.delegate.f(obj));
        ts.setHours(23, 59, 59);
        ts.setMilliseconds(999);
        return ts;
      },
      javaCode: `
// Convert to LocalDate
java.util.Date date = (java.util.Date) getDelegate().f(obj);
java.time.LocalDate localDate = java.time.Instant.ofEpochMilli(date.getTime()).atZone(java.time.ZoneId.systemDefault()).toLocalDate();

// Convert to LocalDateTime set to End of Day
java.time.LocalDateTime localDateTime = localDate.atTime(java.time.LocalTime.MAX);

// Convert to Date using LocalDateTime
return java.util.Date.from(localDateTime.atZone(java.time.ZoneId.systemDefault()).toInstant());
      `
    }
  ]
});

foam.CLASS({
  package: 'foam.glang',
  name: 'EndOfWeek',
  extends: 'foam.glang.AbstractDateGlang',
  properties: [
    {
      class: 'Int',
      name: 'startOfWeek',
      documentation: 'Value between 0 - Sunday and 6 - Saturday inclusive.  Indicates which day is considered the first day of a new week.',
      min: 0,
      max: 6,
      value: 0
    }
  ],
  methods: [
    {
      name: 'f',
      code: function(obj) {
        var ts = new Date(this.delegate.f(obj));

        var date = ts.getDate();
        var endOfWeek = (this.startOfWeek + 6) % 7;
        var day = ts.getDay();
        var daysToEndOfWeek = (endOfWeek - day + 7) % 7;

        ts.setDate(date + daysToEndOfWeek);

        ts.setHours(23, 59, 59);
        ts.setMilliseconds(999);

        return ts;
      },
      javaCode: `
// Convert to LocalDate
java.util.Date date = (java.util.Date) getDelegate().f(obj);
java.time.LocalDate localDate = java.time.Instant.ofEpochMilli(date.getTime()).atZone(java.time.ZoneId.systemDefault()).toLocalDate();

// Set to end of week
localDate = localDate.plusDays(6 - localDate.getDayOfWeek().getValue());

// Convert to LocalDateTime set to End of Day
java.time.LocalDateTime localDateTime = localDate.atTime(java.time.LocalTime.MAX);

// Convert to Date using LocalDateTime
return java.util.Date.from(localDateTime.atZone(java.time.ZoneId.systemDefault()).toInstant());
      `
    }
  ]
});

foam.CLASS({
  package: 'foam.glang',
  name: 'EndOfMonth',
  extends: 'foam.glang.AbstractDateGlang',
  methods: [
    {
      name: 'f',
      code: function(obj) {
        var ts = new Date(this.delegate.f(obj));
        ts.setMonth(ts.getMonth() + 1);
        ts.setDate(0);
        ts.setHours(23, 59, 59);
        ts.setMilliseconds(999);
        return ts;
      },
      javaCode: `
// Convert to LocalDate
java.util.Date date = (java.util.Date) getDelegate().f(obj);
java.time.LocalDate localDate = java.time.Instant.ofEpochMilli(date.getTime()).atZone(java.time.ZoneId.systemDefault()).toLocalDate();

// Set to end of month
localDate = localDate.plusDays(localDate.lengthOfMonth() - localDate.getDayOfMonth());

// Convert to LocalDateTime set to End of Day
java.time.LocalDateTime localDateTime = localDate.atTime(java.time.LocalTime.MAX);

// Convert to Date using LocalDateTime
return java.util.Date.from(localDateTime.atZone(java.time.ZoneId.systemDefault()).toInstant());
      `
    }
  ]
});

foam.CLASS({
  package: 'foam.glang',
  name: 'EndOfQuarter',
  extends: 'foam.glang.AbstractDateGlang',
  methods: [
    {
      name: 'f',
      code: function(obj) {
        var ts = new Date(this.delegate.f(obj));
        // N % 3 is how many months into the quarter we are 0 1 or 2
        // 2 - ( N % 3 ) is how many months left till EOQ.
        // N + ( 2 - ( N % 3 ) ) is the last month of this quarter
        // N + ( 2 - ( N % 3 ) ) + 1 is the first month of next quarter
        // which reduces to N + 3 - ( N % 3 ).
        ts.setMonth(ts.getMonth() + 3 - ( ts.getMonth() % 3 ));
        ts.setDate(0);
        ts.setHours(23, 59, 59);
        ts.setMilliseconds(999);
        return ts;
      },
      javaCode: `
// Convert to LocalDate
java.util.Date date = (java.util.Date) getDelegate().f(obj);
java.time.LocalDate localDate = java.time.Instant.ofEpochMilli(date.getTime()).atZone(java.time.ZoneId.systemDefault()).toLocalDate();

// Set month to end of quarter
localDate = localDate.plusMonths(2 - (localDate.getMonthValue() - 1) % 3);

// Set to end of month
localDate = localDate.plusDays(localDate.lengthOfMonth() - localDate.getDayOfMonth());

// Convert to LocalDateTime set to End of Day
java.time.LocalDateTime localDateTime = localDate.atTime(java.time.LocalTime.MAX);

// Convert to Date using LocalDateTime
return java.util.Date.from(localDateTime.atZone(java.time.ZoneId.systemDefault()).toInstant());
      `
    }
  ]
});

foam.CLASS({
  package: 'foam.glang',
  name: 'EndOfYear',
  extends: 'foam.glang.AbstractDateGlang',
  methods: [
    {
      name: 'f',
      code: function(obj) {
        var ts = new Date(this.delegate.f(obj));
        ts.setMonth(11);
        ts.setDate(31);
        ts.setHours(23, 59, 59);
        ts.setMilliseconds(999);
        return ts;
      },
      javaCode: `
java.util.Date date = (java.util.Date) getDelegate().f(obj);

java.time.Instant t = java.time.Instant.ofEpochMilli(date.getTime())
  .atZone(java.time.ZoneId.systemDefault())
  .toLocalDate()
  .with(java.time.temporal.TemporalAdjusters.lastDayOfYear())
  .atTime(23, 59, 59)
  .atZone(java.time.ZoneId.systemDefault())
  .toInstant();

return java.util.Date.from(t);
      `
    }
  ]
});<|endoftext|>


<commit_before>/**
 * @license
 * Copyright 2015 Google Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

foam.CLASS({
  package: 'foam.u2',
  name: 'CheckBox',
  extends: 'foam.u2.tag.Input',

  documentation: 'Checkbox View.',

  properties: [
    {
      class: 'Boolean',
      name: 'data'
    },
    {
      class: 'Boolean',
      name: 'showLabel',
      expression: function(label) { return !!label },
    },
    {
      class: 'String',
      name: 'label'
    }
  ],

  methods: [
    function initE() {
      this.SUPER();
      this.setAttribute('type', 'checkbox');

      if ( this.showLabel ) {
        this.start('label')
          .addClass(this.myClass('label'))
          .addClass(this.myClass('noselect'))
          .add(this.label$)
          .on('click', function() {
            this.data = !this.data;
          }.bind(this))
        .end();
      }
    },
    function updateMode_(mode) {
      var disabled = mode === foam.u2.DisplayMode.RO ||
                     mode === foam.u2.DisplayMode.DISABLED;
      this.setAttribute('disabled', disabled);
    },
    function link() {
      this.data$.linkTo(this.attrSlot('checked'));
    }
  ],

  axioms: [
    foam.u2.CSS.create({
      code: function CSS() {/*
      ^ {
        margin: 8px;
        padding: 8px;
      }

      ^label {
        color: #444;
        flex-grow: 1;
        margin-left: 12px;
        overflow: hidden;
        white-space: nowrap;
        display: inline;
      }

      ^noselect {
        -webkit-touch-callout: none;
        -webkit-user-select: none;
        -khtml-user-select: none;
        -moz-user-select: none;
        -ms-user-select: none;
        user-select: none;
      }
    */}
    })
  ]
});
<commit_msg>Changing showLable to a factory instead of expression.<commit_after>/**
 * @license
 * Copyright 2015 Google Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

foam.CLASS({
  package: 'foam.u2',
  name: 'CheckBox',
  extends: 'foam.u2.tag.Input',

  documentation: 'Checkbox View.',

  properties: [
    {
      class: 'Boolean',
      name: 'data'
    },
    {
      class: 'Boolean',
      name: 'showLabel',
      factory: function() { return !!this.label },
    },
    {
      class: 'String',
      name: 'label'
    }
  ],

  methods: [
    function initE() {
      this.SUPER();
      this.setAttribute('type', 'checkbox');

      if ( this.showLabel ) {
        this.start('label')
          .addClass(this.myClass('label'))
          .addClass(this.myClass('noselect'))
          .add(this.label$)
          .on('click', function() {
            this.data = !this.data;
          }.bind(this))
        .end();
      }
    },
    function updateMode_(mode) {
      var disabled = mode === foam.u2.DisplayMode.RO ||
                     mode === foam.u2.DisplayMode.DISABLED;
      this.setAttribute('disabled', disabled);
    },
    function link() {
      this.data$.linkTo(this.attrSlot('checked'));
    }
  ],

  axioms: [
    foam.u2.CSS.create({
      code: function CSS() {/*
      ^ {
        margin: 8px;
        padding: 8px;
      }

      ^label {
        color: #444;
        flex-grow: 1;
        margin-left: 12px;
        overflow: hidden;
        white-space: nowrap;
        display: inline;
      }

      ^noselect {
        -webkit-touch-callout: none;
        -webkit-user-select: none;
        -khtml-user-select: none;
        -moz-user-select: none;
        -ms-user-select: none;
        user-select: none;
      }
    */}
    })
  ]
});
<|endoftext|>


<commit_before>/**
* @fileOverview Produces line chart
*/

/**  */
class DataVisualizer
{
  /**
   * @class DataVisualizer
   * @classdesc DataVisualizer is a wrapper class for line-chart in chart.js.
   *
   * @param {string} id - ID of canvas that chart should be rendered on.
   */
  constructor(id)
  {
      // binding functions to "this" context
      this.addDataPoint = this.addDataPoint.bind(this);
      this.initToggleButtons = this.initToggleButtons.bind(this);

      /**
       * Number of total packets added to chart.
       *
       * @private
       * @name DataVisualizer#numPackets
       * @type Integer
       * @default 0
       */
      this.numPackets = 0;

      /**
       * Number of data points to display on line chart.
       *
       * @private
       * @name DataVisualizer#numDisplayPoints
       * @type Integer
       * @default 25
       */
      this.numDisplayPoints = 25;

      /**
       * Chart object to be used by chartjs for chart manipulation>
       *
       * @private
       * @name DataVisualizer#chart
       * @type Object
       */
      this.chart;

      this.toggleData = function(button)
      {
          var dataSetIndex = button.getAttribute('data-data-set-index');
          var dataSet = this.chart.config.data.datasets[dataSetIndex];

          dataSet.hidden = !dataSet.hidden;

          this.chart.update();
      }.bind(this)

      // creating and configuring chart
      var ctx = document.getElementById(id).getContext("2d");
      this.chart = new Chart(ctx, config);
      Chart.defaults.global.defaultFontColor = "#ebebeb";
      Chart.defaults.global.defaultFontFamily = "'Lato','Helvetica Neue','Helvetica','Arial',sans-serif";

      // initializing toggle buttons
      this.initToggleButtons();
  }

  addDataPoint()
  {
      actualElevationData.push(Math.floor(Math.random() * 50000));
      actualTemperatureData.push(Math.floor(Math.random() * 100));
      actualPacketIndex.push(++this.numPackets);
      temperatureData.length = 0;
      elevationData.length = 0;
      packetIndex.length = 0;

      var startIndex = (this.numPackets > this.numDisplayPoints) ? (this.numPackets - this.numDisplayPoints) : 0;
      var endIndex = (this.numPackets > this.numDisplayPoints) ? (startIndex + this.numDisplayPoints) : this.numPackets;

      for (var i = startIndex; i < endIndex; i++) {
        temperatureData.push(actualTemperatureData[i]);
        elevationData.push(actualElevationData[i]);
        packetIndex.push(actualPacketIndex[i]);
      }

      this.chart.update();
  }

  // adding the dataset enum property to each toggle button for use in toggleData function below
  initToggleButtons()
  {
      var button;

      button = document.getElementById("toggleTemperature");
      button.setAttribute('data-data-set-index', DataSet.TEMPERATURE);

      button = document.getElementById("toggleAltitude");
      button.setAttribute('data-data-set-index', DataSet.ALTITUDE);
  }

}

// global accessor for html
var chart;
window.onload = function() {
    chart = new DataVisualizer("linechart");
};

<commit_msg>Made remaining methods privelleged<commit_after>/**
* @fileOverview Produces line chart
*/

/**  */
class DataVisualizer
{
  /**
   * @class DataVisualizer
   * @classdesc DataVisualizer is a wrapper class for line-chart in chart.js.
   *
   * @param {string} id - ID of canvas that chart should be rendered on.
   */
  constructor(id)
  {
      /**
       * Number of total packets added to chart.
       *
       * @private
       * @name DataVisualizer#numPackets
       * @type Integer
       * @default 0
       */
      this.numPackets = 0;

      /**
       * Number of data points to display on line chart.
       *
       * @private
       * @name DataVisualizer#numDisplayPoints
       * @type Integer
       * @default 25
       */
      this.numDisplayPoints = 25;

      /**
       * Chart object to be used by chartjs for chart manipulation>
       *
       * @private
       * @name DataVisualizer#chart
       * @type Object
       */
      this.chart;

      this.toggleData = function(button)
      {
          var dataSetIndex = button.getAttribute('data-data-set-index');
          var dataSet = this.chart.config.data.datasets[dataSetIndex];

          dataSet.hidden = !dataSet.hidden;

          this.chart.update();
      }.bind(this)

      // adding the dataset enum property to each toggle button for use in toggleData function below
      this.initToggleButtons = function()
      {
          var button;

          button = document.getElementById("toggleTemperature");
          button.setAttribute('data-data-set-index', DataSet.TEMPERATURE);

          button = document.getElementById("toggleAltitude");
          button.setAttribute('data-data-set-index', DataSet.ALTITUDE);
      }.bind(this)

      this.addDataPoint = function()
      {
          actualElevationData.push(Math.floor(Math.random() * 50000));
          actualTemperatureData.push(Math.floor(Math.random() * 100));
          actualPacketIndex.push(++this.numPackets);
          temperatureData.length = 0;
          elevationData.length = 0;
          packetIndex.length = 0;

          var startIndex = (this.numPackets > this.numDisplayPoints) ? (this.numPackets - this.numDisplayPoints) : 0;
          var endIndex = (this.numPackets > this.numDisplayPoints) ? (startIndex + this.numDisplayPoints) : this.numPackets;

          for (var i = startIndex; i < endIndex; i++) {
            temperatureData.push(actualTemperatureData[i]);
            elevationData.push(actualElevationData[i]);
            packetIndex.push(actualPacketIndex[i]);
          }

          this.chart.update();
      }.bind(this)

      // creating and configuring chart
      var ctx = document.getElementById(id).getContext("2d");
      this.chart = new Chart(ctx, config);
      Chart.defaults.global.defaultFontColor = "#ebebeb";
      Chart.defaults.global.defaultFontFamily = "'Lato','Helvetica Neue','Helvetica','Arial',sans-serif";

      // initializing toggle buttons
      this.initToggleButtons();
  }
}

// global accessor for html
var chart;
window.onload = function() {
    chart = new DataVisualizer("linechart");
};

<|endoftext|>


<commit_before><commit_msg>New cql4browsers.js with fix for replacing execute with exec in sort function (#27)<commit_after><|endoftext|>


<commit_before>/*
   Copyright 2010-2013 Portland Transport

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/

var transitBoardHorizontal = {}; // keep state

// constants

transitBoardHorizontal.APP_NAME 		= "Transit Board Horizontal";
transitBoardHorizontal.APP_VERSION 	= "1.03";
transitBoardHorizontal.APP_ID 			= "tbdhorizontal";

// 1.03 - 9/22/16 - shift to jQuery 1.11.0

// assess environment

transitBoardHorizontal.is_development = (document.domain == "dev.transitboard.com");
transitBoardHorizontal.isChumby = navigator.userAgent.match(/QtEmb/) != null;

//var orig_query_string = window.location.search;
//var app_query_string = orig_query_string.replace(/option\[(top|left|right|bottom)\]=[0-9]*(&|$)/g,"");
//var app_url = "/apps/transitBoardByLine/transitBoardByLine.html"+app_query_string;

/**
 * Loosely modeled on jquery.parsequery.js by Michael Manning (http://actingthemaggot.com)
 **/
trArrParseQuery = function(qs) {
	var q = (typeof qs === 'string'?qs:window.location.search);
	var params = {};
	jQuery.each(q.match(/^\??(.*)$/)[1].split('&'),function(i,p){
		p = p.replace(/\+/g,' ').replace(/\]/g,'');
		p = p.split('=');
		var keys = p[0].split('[');
		var value = p[1];
		var depth = keys.length;
		if (depth == 1) {
			// actually shouldn't happen, should always have at least two levels
			if (params[keys[0]] == undefined) {
				params[keys[0]] = {};
			}
			params[keys[0]][unescape(value)] = true;
		}
		if (depth == 2) {
			if (params[keys[0]] == undefined) {
				params[keys[0]] = {};
			}
			if (params[keys[0]][keys[1]] == undefined) {
				params[keys[0]][keys[1]] = {};
			}
			params[keys[0]][keys[1]][unescape(value)] = true;
		}
		if (depth == 3) {
			if (params[keys[0]] == undefined) {
				params[keys[0]] = {};
			}
			if (params[keys[0]][keys[1]] == undefined) {
				params[keys[0]][keys[1]] = {};
			}
			if (params[keys[0]][keys[1]][keys[2]] == undefined) {
				params[keys[0]][keys[1]][keys[2]] = {};
			}
			params[keys[0]][keys[1]][keys[2]][unescape(value)] = true;
		}
	});
	return params;
}

function trArrSupportsCors() {
  var xhr = new XMLHttpRequest();
  if ("withCredentials" in xhr) {
    // Supports CORS
    return true;
  } else if (typeof XDomainRequest != "undefined") {
    // IE
    return true;
  }
  return false;
}

var query_params = trArrParseQuery();

// turns options from objects into arrays
var options = {};
for (var option in query_params.option) {
	var opt_array = [];
	for (var value in this.query_params.option[option]) {
		opt_array.push(value);
	}
	options[option] = opt_array;
}

var appliance = {};
for (var appl in query_params.appl) {
	var opt_array = [];
	for (var value in this.query_params.appl[appl]) {
		opt_array.push(value);
	}
	appliance[appl] = opt_array;
}

var second_page = false;
if (options['second_page'] == 1) {
	second_page = true;
}

var num_pages = options['num_pages'] || 1;
if (second_page && num_pages < 2) {
	num_pages = 2;
}
num_pages = num_pages * 1;

var page_delay = options['page_delay'] || 15;

// initialize screen margins

var body_width 		= options.width || jQuery(window).width();
var body_height 	= options.height || jQuery(window).height();

var left_border 	= options.left || 0;
var bottom_border = options.bottom || 0;
var top_border 		= options.top || 0;
var right_border 	= options.right || 0;

var split_pct 		= options.splitpct || 100;
var suppl_url 		= options.suppl_url;

if (suppl_url == "") {
	suppl_url = "http://transitappliance.com/size_info.html";
}

var effective_width = body_width - left_border - right_border;
var effective_height = body_height - bottom_border - top_border;

jQuery("body").css("width",effective_width).css("height",effective_height);
jQuery("body").css("margin","0");

jQuery("body").css('border-left-width',left_border);
jQuery("body").css('border-top-width',top_border);
jQuery("body").css('border-right-width',right_border);
jQuery("body").css('border-bottom-width',bottom_border);
jQuery("body").css('border-color','black');
jQuery("body").css('border-style','solid');
jQuery("body").css('position','relative'); // for reasons I haven't figured out, this has to be set late

var left_width = Math.floor(effective_width * split_pct/100);
var right_width = effective_width - left_width;

var primary_id = appliance['id']+":A";
var app_url = "/apps/loader.html?"+primary_id;

// populate html

var html = '
';
html += '
';
html += '';

if ( num_pages > 1 && appliance['id'] ) {
	for (var i=2;i<=num_pages;i++) {
		var letter = ("ABCDEFGHIJKLMNOPQRSTUVWXYZ").substr(i-1,1);
		//alert(letter);
		var id = appliance['id'];
		var alt_id = id+":"+letter;
		var app_url2 = "/apps/loader.html?"+alt_id;
		html += '';
	}
}
/*
if ( second_page && appliance['id'] ) {
	var id = appliance['id'];
	var alt_id = id+":B";
	var app_url2 = "/apps/loader.html?"+alt_id;
	html += '';
}
*/
html += '
';
if (right_width > 1) {
	html += '';
}
html += '
';

jQuery('body').html(html);

var current_frame = 0;
function rotate_frames () {
	current_frame = current_frame + 1;
	if (current_frame > num_pages) {
		current_frame = 1;
	}
	//alert(current_frame+" out of "+num_pages);
	for (var i=1;i<=num_pages;i++) {
		if (i == current_frame) {
			var frame = document.getElementById("app_frame"+i);
			if (frame !== null) {
				frame.style.zIndex = 1000;
			}
		} else {
			var frame = document.getElementById("app_frame"+i);
			if (frame !== null) {
				frame.style.zIndex = -1000;
			}
		}
	}
	setTimeout(rotate_frames,page_delay*1000);
}

if ( num_pages > 1 && appliance['id'] ) {
	setTimeout(rotate_frames,100000); // 100 second delay to let everything load
}

/*
if ( second_page && appliance['id'] ) {
	setTimeout(function(){
		jQuery("#app_frame2").css('display','none');
		setInterval(function(){
			jQuery("#app_frame1, #app_frame2").toggle(1000);
		},15000);
	},100000);
}
*/

// set up healthcheck/restart logic

var start_time = ((new Date)).getTime();

transitBoardHorizontal.access_method = "jsonp";
if (trArrSupportsCors()) {
	transitBoardHorizontal.access_method = "json";
}

var platform = "";
if (typeof options.platform === 'object') {
	platform = options.platform[0];
}

jQuery.ajax({
		dataType: transitBoardHorizontal.access_method,
		url: "http://ta-web-services.com/health_update.php",
		data: { timestamp: start_time, start_time: start_time, version: 'N/A', "id": appliance['id'], application_id: transitBoardHorizontal.APP_ID, application_name: transitBoardHorizontal.APP_NAME, application_version: transitBoardHorizontal.APP_VERSION, "height": jQuery(window).height(), "width": jQuery(window).width(), "platform": platform }
});

// logging of startup, beat every 30 min goes here
setInterval(function(){
	jQuery.ajax({
			url: "http://ta-web-services.com/health_update.php",
			dataType: transitBoardHorizontal.access_method,
			cache: false,
			data: { timestamp: ((new Date)).getTime(), start_time: start_time, version: 'N/A', "id": appliance['id'], application_id: transitBoardHorizontal.APP_ID, application_name: transitBoardHorizontal.APP_NAME, application_version: transitBoardHorizontal.APP_VERSION, "height": jQuery(window).height(), "width": jQuery(window).width(), "platform": platform },
			success: function(data) {
				if( typeof data != "undefined" && data.reset == true ) {
					reset_app();
				}
			}
	});
}, 30*60*1000);

var reset_app = function() {
	if (appliance['id']) {
		if(typeof trLoader == 'function') {
			trLoader(appliance['id']);
		} else {
			window.location = "http://transitappliance.com/cgi-bin/launch_by_id.pl?id="+appliance['id'];
		}
	} else {
		window.location.reload(true);
	}
}

<commit_msg>make neutral to http/https<commit_after>/*
   Copyright 2010-2013 Portland Transport

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/

var transitBoardHorizontal = {}; // keep state

// constants

transitBoardHorizontal.APP_NAME 		= "Transit Board Horizontal";
transitBoardHorizontal.APP_VERSION 	= "1.04";
transitBoardHorizontal.APP_ID 			= "tbdhorizontal";

// 1.03 - 9/22/16 - shift to jQuery 1.11.0
// 1.04 - 11/26/21 - make neutral with regard to http/https

// assess environment

transitBoardHorizontal.is_development = (document.domain == "dev.transitboard.com");
transitBoardHorizontal.isChumby = navigator.userAgent.match(/QtEmb/) != null;

//var orig_query_string = window.location.search;
//var app_query_string = orig_query_string.replace(/option\[(top|left|right|bottom)\]=[0-9]*(&|$)/g,"");
//var app_url = "/apps/transitBoardByLine/transitBoardByLine.html"+app_query_string;

/**
 * Loosely modeled on jquery.parsequery.js by Michael Manning (http://actingthemaggot.com)
 **/
trArrParseQuery = function(qs) {
	var q = (typeof qs === 'string'?qs:window.location.search);
	var params = {};
	jQuery.each(q.match(/^\??(.*)$/)[1].split('&'),function(i,p){
		p = p.replace(/\+/g,' ').replace(/\]/g,'');
		p = p.split('=');
		var keys = p[0].split('[');
		var value = p[1];
		var depth = keys.length;
		if (depth == 1) {
			// actually shouldn't happen, should always have at least two levels
			if (params[keys[0]] == undefined) {
				params[keys[0]] = {};
			}
			params[keys[0]][unescape(value)] = true;
		}
		if (depth == 2) {
			if (params[keys[0]] == undefined) {
				params[keys[0]] = {};
			}
			if (params[keys[0]][keys[1]] == undefined) {
				params[keys[0]][keys[1]] = {};
			}
			params[keys[0]][keys[1]][unescape(value)] = true;
		}
		if (depth == 3) {
			if (params[keys[0]] == undefined) {
				params[keys[0]] = {};
			}
			if (params[keys[0]][keys[1]] == undefined) {
				params[keys[0]][keys[1]] = {};
			}
			if (params[keys[0]][keys[1]][keys[2]] == undefined) {
				params[keys[0]][keys[1]][keys[2]] = {};
			}
			params[keys[0]][keys[1]][keys[2]][unescape(value)] = true;
		}
	});
	return params;
}

function trArrSupportsCors() {
  var xhr = new XMLHttpRequest();
  if ("withCredentials" in xhr) {
    // Supports CORS
    return true;
  } else if (typeof XDomainRequest != "undefined") {
    // IE
    return true;
  }
  return false;
}

var query_params = trArrParseQuery();

// turns options from objects into arrays
var options = {};
for (var option in query_params.option) {
	var opt_array = [];
	for (var value in this.query_params.option[option]) {
		opt_array.push(value);
	}
	options[option] = opt_array;
}

var appliance = {};
for (var appl in query_params.appl) {
	var opt_array = [];
	for (var value in this.query_params.appl[appl]) {
		opt_array.push(value);
	}
	appliance[appl] = opt_array;
}

var second_page = false;
if (options['second_page'] == 1) {
	second_page = true;
}

var num_pages = options['num_pages'] || 1;
if (second_page && num_pages < 2) {
	num_pages = 2;
}
num_pages = num_pages * 1;

var page_delay = options['page_delay'] || 15;

// initialize screen margins

var body_width 		= options.width || jQuery(window).width();
var body_height 	= options.height || jQuery(window).height();

var left_border 	= options.left || 0;
var bottom_border = options.bottom || 0;
var top_border 		= options.top || 0;
var right_border 	= options.right || 0;

var split_pct 		= options.splitpct || 100;
var suppl_url 		= options.suppl_url;

if (suppl_url == "") {
	suppl_url = "//transitappliance.com/size_info.html";
}

var effective_width = body_width - left_border - right_border;
var effective_height = body_height - bottom_border - top_border;

jQuery("body").css("width",effective_width).css("height",effective_height);
jQuery("body").css("margin","0");

jQuery("body").css('border-left-width',left_border);
jQuery("body").css('border-top-width',top_border);
jQuery("body").css('border-right-width',right_border);
jQuery("body").css('border-bottom-width',bottom_border);
jQuery("body").css('border-color','black');
jQuery("body").css('border-style','solid');
jQuery("body").css('position','relative'); // for reasons I haven't figured out, this has to be set late

var left_width = Math.floor(effective_width * split_pct/100);
var right_width = effective_width - left_width;

var primary_id = appliance['id']+":A";
var app_url = "/apps/loader.html?"+primary_id;

// populate html

var html = '
';
html += '
';
html += '';

if ( num_pages > 1 && appliance['id'] ) {
	for (var i=2;i<=num_pages;i++) {
		var letter = ("ABCDEFGHIJKLMNOPQRSTUVWXYZ").substr(i-1,1);
		//alert(letter);
		var id = appliance['id'];
		var alt_id = id+":"+letter;
		var app_url2 = "/apps/loader.html?"+alt_id;
		html += '';
	}
}
/*
if ( second_page && appliance['id'] ) {
	var id = appliance['id'];
	var alt_id = id+":B";
	var app_url2 = "/apps/loader.html?"+alt_id;
	html += '';
}
*/
html += '
';
if (right_width > 1) {
	html += '';
}
html += '
';

jQuery('body').html(html);

var current_frame = 0;
function rotate_frames () {
	current_frame = current_frame + 1;
	if (current_frame > num_pages) {
		current_frame = 1;
	}
	//alert(current_frame+" out of "+num_pages);
	for (var i=1;i<=num_pages;i++) {
		if (i == current_frame) {
			var frame = document.getElementById("app_frame"+i);
			if (frame !== null) {
				frame.style.zIndex = 1000;
			}
		} else {
			var frame = document.getElementById("app_frame"+i);
			if (frame !== null) {
				frame.style.zIndex = -1000;
			}
		}
	}
	setTimeout(rotate_frames,page_delay*1000);
}

if ( num_pages > 1 && appliance['id'] ) {
	setTimeout(rotate_frames,100000); // 100 second delay to let everything load
}

/*
if ( second_page && appliance['id'] ) {
	setTimeout(function(){
		jQuery("#app_frame2").css('display','none');
		setInterval(function(){
			jQuery("#app_frame1, #app_frame2").toggle(1000);
		},15000);
	},100000);
}
*/

// set up healthcheck/restart logic

var start_time = ((new Date)).getTime();

transitBoardHorizontal.access_method = "jsonp";
if (trArrSupportsCors()) {
	transitBoardHorizontal.access_method = "json";
}

var platform = "";
if (typeof options.platform === 'object') {
	platform = options.platform[0];
}

jQuery.ajax({
		dataType: transitBoardHorizontal.access_method,
		url: "//ta-web-services.com/health_update.php",
		data: { timestamp: start_time, start_time: start_time, version: 'N/A', "id": appliance['id'], application_id: transitBoardHorizontal.APP_ID, application_name: transitBoardHorizontal.APP_NAME, application_version: transitBoardHorizontal.APP_VERSION, "height": jQuery(window).height(), "width": jQuery(window).width(), "platform": platform }
});

// logging of startup, beat every 30 min goes here
setInterval(function(){
	jQuery.ajax({
			url: "//ta-web-services.com/health_update.php",
			dataType: transitBoardHorizontal.access_method,
			cache: false,
			data: { timestamp: ((new Date)).getTime(), start_time: start_time, version: 'N/A', "id": appliance['id'], application_id: transitBoardHorizontal.APP_ID, application_name: transitBoardHorizontal.APP_NAME, application_version: transitBoardHorizontal.APP_VERSION, "height": jQuery(window).height(), "width": jQuery(window).width(), "platform": platform },
			success: function(data) {
				if( typeof data != "undefined" && data.reset == true ) {
					reset_app();
				}
			}
	});
}, 30*60*1000);

var reset_app = function() {
	if (appliance['id']) {
		if(typeof trLoader == 'function') {
			trLoader(appliance['id']);
		} else {
			window.location = "//transitappliance.com/cgi-bin/launch_by_id.pl?id="+appliance['id'];
		}
	} else {
		window.location.reload(true);
	}
}

<|endoftext|>


<commit_before>/**
 * mSupply Mobile
 * Sustainable Solutions (NZ) Ltd. 2020
 */
import moment from 'moment';
import { createSelector } from 'reselect';
import { UIDatabase } from '../database';
import chunk from '../utilities';
import { CHART_CONSTANTS } from '../utilities/modules/vaccines/constants';

export const selectSelectedFridgeID = ({ fridge }) => {
  const { selectedFridge = {} } = fridge;
  const { id } = selectedFridge;

  return id;
};

export const selectTemperatureLogsFromDate = ({ fridge }) => {
  const { fromDate } = fridge;

  return fromDate;
};

export const selectTemperatureLogsToDate = ({ fridge }) => {
  const { toDate } = fridge;

  return toDate;
};

export const selectSelectedFridge = createSelector([selectSelectedFridgeID], selectedFridgeID => {
  const selectedFridge = UIDatabase.get('Location', selectedFridgeID);

  return selectedFridge;
});

export const selectFridgeTemperatureLogs = createSelector([selectSelectedFridge], fridge => {
  const { temperatureLogs = {} } = fridge ?? {};

  return temperatureLogs.sorted?.('timestamp') ?? [];
});

export const selectFridgeTemperatureLogsFromDate = createSelector(
  [selectFridgeTemperatureLogs, selectTemperatureLogsFromDate, selectTemperatureLogsToDate],
  (logs, fromDate, toDate) => {
    const temperatureLogs =
      logs?.filtered?.('timestamp >= $0 && timestamp =< $1', fromDate, toDate) ?? [];

    return temperatureLogs;
  }
);

export const selectChunkedTemperatureLogs = createSelector(
  [selectFridgeTemperatureLogsFromDate],
  logs => {
    const { length: numberOfLogs } = logs;

    // If the number of temperature logs is less than the maximum number of data points,
    // then the array does not need to be chunked together - however still need to create
    // a 2D array, so chunk with a chunk size of 1.
    return chunk(logs, Math.ceil(numberOfLogs / CHART_CONSTANTS.MAX_DATA_POINTS));
  }
);

export const selectMinAndMaxLogs = createSelector([selectChunkedTemperatureLogs], logs =>
  logs.reduce(
    (acc, logGroup) => {
      const temperatures = logGroup.map(({ temperature }) => temperature);
      const timestamps = logGroup.map(({ timestamp }) => timestamp);

      const maxTemperature = Math.max(...temperatures);
      const minTemperature = Math.min(...temperatures);
      const timestamp = Math.min(...timestamps);

      const { minLine, maxLine } = acc;

      return {
        minLine: [...minLine, { temperature: minTemperature, timestamp }],
        maxLine: [...maxLine, { temperature: maxTemperature, timestamp }],
      };
    },
    { minLine: [], maxLine: [] }
  )
);

export const selectMinAndMaxDomains = createSelector([selectMinAndMaxLogs], minAndMaxLogs => {
  const { minLine, maxLine } = minAndMaxLogs;

  return {
    minDomain: Math.min(...minLine.map(({ temperature }) => temperature)),
    maxDomain: Math.max(...maxLine.map(({ temperature }) => temperature)),
  };
});

export const selectBreaches = createSelector(
  [selectTemperatureLogsFromDate, selectTemperatureLogsToDate, selectSelectedFridge],
  (fromDate, toDate, fridge) => {
    const { breaches } = fridge ?? {};
    const breachesInDateRange = breaches?.filtered(
      '(startTimestamp <= $0 && (endTimestamp >= $0 || endTimestamp == null)) || ' +
        '(startTimestamp >= $0 && startTimestamp <= $1)',
      fromDate,
      toDate
    );

    const adjustedBreaches = breachesInDateRange?.map(({ timestamp, id, temperature }) => ({
      id,
      temperature,
      timestamp: moment(timestamp).isBefore(moment(fromDate)) ? fromDate : timestamp,
    }));

    return adjustedBreaches ?? [];
  }
);

export const selectTimestampFormatter = createSelector(
  [selectTemperatureLogsFromDate, selectTemperatureLogsToDate],
  (fromDate, toDate) => {
    const durationInDays = moment(toDate).diff(moment(fromDate), 'days', true);

    const time = 'HH:MM';
    const date = 'DD/MM';
    const dateAndTime = `${time} - ${date}`;

    const getTickFormat = format => tick => moment(tick).format(format);

    if (durationInDays <= 1) return getTickFormat(time);
    if (durationInDays <= 3) return getTickFormat(dateAndTime);

    return getTickFormat(date);
  }
);
<commit_msg>Add corrected import<commit_after>/**
 * mSupply Mobile
 * Sustainable Solutions (NZ) Ltd. 2020
 */
import moment from 'moment';
import { createSelector } from 'reselect';
import { UIDatabase } from '../database';
import { chunk } from '../utilities';
import { CHART_CONSTANTS } from '../utilities/modules/vaccines/constants';

export const selectSelectedFridgeID = ({ fridge }) => {
  const { selectedFridge = {} } = fridge;
  const { id } = selectedFridge;

  return id;
};

export const selectTemperatureLogsFromDate = ({ fridge }) => {
  const { fromDate } = fridge;

  return fromDate;
};

export const selectTemperatureLogsToDate = ({ fridge }) => {
  const { toDate } = fridge;

  return toDate;
};

export const selectSelectedFridge = createSelector([selectSelectedFridgeID], selectedFridgeID => {
  const selectedFridge = UIDatabase.get('Location', selectedFridgeID);

  return selectedFridge;
});

export const selectFridgeTemperatureLogs = createSelector([selectSelectedFridge], fridge => {
  const { temperatureLogs = {} } = fridge ?? {};

  return temperatureLogs.sorted?.('timestamp') ?? [];
});

export const selectFridgeTemperatureLogsFromDate = createSelector(
  [selectFridgeTemperatureLogs, selectTemperatureLogsFromDate, selectTemperatureLogsToDate],
  (logs, fromDate, toDate) => {
    const temperatureLogs =
      logs?.filtered?.('timestamp >= $0 && timestamp =< $1', fromDate, toDate) ?? [];

    return temperatureLogs;
  }
);

export const selectChunkedTemperatureLogs = createSelector(
  [selectFridgeTemperatureLogsFromDate],
  logs => {
    const { length: numberOfLogs } = logs;

    // If the number of temperature logs is less than the maximum number of data points,
    // then the array does not need to be chunked together - however still need to create
    // a 2D array, so chunk with a chunk size of 1.
    return chunk(logs, Math.ceil(numberOfLogs / CHART_CONSTANTS.MAX_DATA_POINTS));
  }
);

export const selectMinAndMaxLogs = createSelector([selectChunkedTemperatureLogs], logs =>
  logs.reduce(
    (acc, logGroup) => {
      const temperatures = logGroup.map(({ temperature }) => temperature);
      const timestamps = logGroup.map(({ timestamp }) => timestamp);

      const maxTemperature = Math.max(...temperatures);
      const minTemperature = Math.min(...temperatures);
      const timestamp = Math.min(...timestamps);

      const { minLine, maxLine } = acc;

      return {
        minLine: [...minLine, { temperature: minTemperature, timestamp }],
        maxLine: [...maxLine, { temperature: maxTemperature, timestamp }],
      };
    },
    { minLine: [], maxLine: [] }
  )
);

export const selectMinAndMaxDomains = createSelector([selectMinAndMaxLogs], minAndMaxLogs => {
  const { minLine, maxLine } = minAndMaxLogs;

  return {
    minDomain: Math.min(...minLine.map(({ temperature }) => temperature)),
    maxDomain: Math.max(...maxLine.map(({ temperature }) => temperature)),
  };
});

export const selectBreaches = createSelector(
  [selectTemperatureLogsFromDate, selectTemperatureLogsToDate, selectSelectedFridge],
  (fromDate, toDate, fridge) => {
    const { breaches } = fridge ?? {};
    const breachesInDateRange = breaches?.filtered(
      '(startTimestamp <= $0 && (endTimestamp >= $0 || endTimestamp == null)) || ' +
        '(startTimestamp >= $0 && startTimestamp <= $1)',
      fromDate,
      toDate
    );

    const adjustedBreaches = breachesInDateRange?.map(({ timestamp, id, temperature }) => ({
      id,
      temperature,
      timestamp: moment(timestamp).isBefore(moment(fromDate)) ? fromDate : timestamp,
    }));

    return adjustedBreaches ?? [];
  }
);

export const selectTimestampFormatter = createSelector(
  [selectTemperatureLogsFromDate, selectTemperatureLogsToDate],
  (fromDate, toDate) => {
    const durationInDays = moment(toDate).diff(moment(fromDate), 'days', true);

    const time = 'HH:MM';
    const date = 'DD/MM';
    const dateAndTime = `${time} - ${date}`;

    const getTickFormat = format => tick => moment(tick).format(format);

    if (durationInDays <= 1) return getTickFormat(time);
    if (durationInDays <= 3) return getTickFormat(dateAndTime);

    return getTickFormat(date);
  }
);
<|endoftext|>


<commit_before>const mongoose = require('mongoose');
const AutoIncrement = require('mongoose-sequence')(mongoose);
const OrderState = require('../../common/orderState');

const models = {};

models.ProductPicturesCollection = 'productpictures';

const address = {
  streetAddress: String,
  zip: Number,
  city: String,
  country: String,
};

const AddressSchema = mongoose.Schema(address);

const ProductCategoryTranslationsSchema = {
  name: String,
};

models.ProductCategory = mongoose.model('ProductCategory', {
  parentCategory: mongoose.Schema.Types.Object,
  de: ProductCategoryTranslationsSchema,
  en: ProductCategoryTranslationsSchema,
  fr: ProductCategoryTranslationsSchema,
  it: ProductCategoryTranslationsSchema,
});

models.Tag = mongoose.model('Tag', {
  name: {
    type: String,
    index: true,
    unique: true,
  },
});

const DownloadLinkSchema = {
  text: String,
  href: String,
};

const ProductTranslationsSchema = {
  name: String,
  shortDescription: String,
  description: String,
  specifications: [String],
  delivery: [String],
  downloads: [DownloadLinkSchema],
};

const ProductSchema = mongoose.Schema({
  category: {
    type: mongoose.Schema.Types.ObjectId,
    index: true,
  },
  de: ProductTranslationsSchema,
  en: ProductTranslationsSchema,
  fr: ProductTranslationsSchema,
  it: ProductTranslationsSchema,
  price: Number,
  purchasePrice: Number,
  purchasePackageSize: Number,
  stock: Number,
  minStock: Number,
  supplier: mongoose.Schema.Types.ObjectId,
  supplierProductCode: String,
  producer: mongoose.Schema.Types.ObjectId,
  remarks: String,
  files: [String],
  tags: [String],
});

models.Product = mongoose.model('Product', ProductSchema);

models.Producer = mongoose.model('Producer', {
  name: String,
  vatNumber: String,
  address: AddressSchema,
  contactName: String,
  contactEmail: String,
  contactPhone: String,
  remarks: String,
});

models.Supplier = mongoose.model('Supplier', {
  name: String,
  vatNumber: String,
  customerNumber: String,
  address: AddressSchema,
  bank: String,
  iban: String,
  bic: String,
  bankRemarks: String,
  contactName: String,
  contactEmail: String,
  contactPhone: String,
  remarks: String,
});

const UserAddressSchema = mongoose.Schema(Object.assign(
  {},
  {
    user: {
      type: String,
      index: true,
    },
    title: String,
    firstName: String,
    lastName: String,
  },
  address
));

models.UserAddress = mongoose.model('UserAddress', UserAddressSchema);

const OrderItemSchema = mongoose.Schema({
  amount: Number,
  product: ProductSchema,
});

models.Order = mongoose.model('Order', {
  _id: {
    type: String,
    index: true,
  },
  state: {
    type: String,
    enum: Object.keys(OrderState),
    default: OrderState.STARTED,
  },
  user: {
    type: String,
    index: true,
  },
  address: UserAddressSchema,
  date: {
    type: Date,
    default: Date.now(),
    index: true,
  },
  items: [OrderItemSchema],
  itemTotal: {
    type: Number,
  },
  total: {
    type: Number,
  },
});

const InvoiceSchema = mongoose.Schema({
  user: {
    type: String,
    index: true,
  },
  invoiceNumber: {
    type: Number,
    index: true,
  },
  value: Number,
  address: UserAddressSchema,
});
InvoiceSchema.plugin(AutoIncrement, { inc_field: 'invoiceNumber' });

models.Invoice = mongoose.model('Invoice', InvoiceSchema);

const HomeTileName = mongoose.Schema({
  name: String,
});

models.HomeTile = mongoose.model('HomeTile', {
  _id: {
    type: String,
    index: true,
  },
  de: HomeTileName,
  fr: HomeTileName,
  it: HomeTileName,
  en: HomeTileName,
  category: mongoose.Schema.Types.ObjectId,
  picture: String,
});

module.exports = models;
<commit_msg>add order field<commit_after>const mongoose = require('mongoose');
const AutoIncrement = require('mongoose-sequence')(mongoose);
const OrderState = require('../../common/orderState');

const models = {};

models.ProductPicturesCollection = 'productpictures';

const address = {
  streetAddress: String,
  zip: Number,
  city: String,
  country: String,
};

const AddressSchema = mongoose.Schema(address);

const ProductCategoryTranslationsSchema = {
  name: String,
};

models.ProductCategory = mongoose.model('ProductCategory', {
  parentCategory: mongoose.Schema.Types.Object,
  de: ProductCategoryTranslationsSchema,
  en: ProductCategoryTranslationsSchema,
  fr: ProductCategoryTranslationsSchema,
  it: ProductCategoryTranslationsSchema,
});

models.Tag = mongoose.model('Tag', {
  name: {
    type: String,
    index: true,
    unique: true,
  },
});

const DownloadLinkSchema = {
  text: String,
  href: String,
};

const ProductTranslationsSchema = {
  name: String,
  shortDescription: String,
  description: String,
  specifications: [String],
  delivery: [String],
  downloads: [DownloadLinkSchema],
};

const ProductSchema = mongoose.Schema({
  category: {
    type: mongoose.Schema.Types.ObjectId,
    index: true,
  },
  de: ProductTranslationsSchema,
  en: ProductTranslationsSchema,
  fr: ProductTranslationsSchema,
  it: ProductTranslationsSchema,
  price: Number,
  purchasePrice: Number,
  purchasePackageSize: Number,
  stock: Number,
  minStock: Number,
  supplier: mongoose.Schema.Types.ObjectId,
  supplierProductCode: String,
  producer: mongoose.Schema.Types.ObjectId,
  remarks: String,
  files: [String],
  tags: [String],
});

models.Product = mongoose.model('Product', ProductSchema);

models.Producer = mongoose.model('Producer', {
  name: String,
  vatNumber: String,
  address: AddressSchema,
  contactName: String,
  contactEmail: String,
  contactPhone: String,
  remarks: String,
});

models.Supplier = mongoose.model('Supplier', {
  name: String,
  vatNumber: String,
  customerNumber: String,
  address: AddressSchema,
  bank: String,
  iban: String,
  bic: String,
  bankRemarks: String,
  contactName: String,
  contactEmail: String,
  contactPhone: String,
  remarks: String,
});

const UserAddressSchema = mongoose.Schema(Object.assign(
  {},
  {
    user: {
      type: String,
      index: true,
    },
    title: String,
    firstName: String,
    lastName: String,
  },
  address
));

models.UserAddress = mongoose.model('UserAddress', UserAddressSchema);

const OrderItemSchema = mongoose.Schema({
  amount: Number,
  product: ProductSchema,
});

models.Order = mongoose.model('Order', {
  _id: {
    type: String,
    index: true,
  },
  state: {
    type: String,
    enum: Object.keys(OrderState),
    default: OrderState.STARTED,
  },
  user: {
    type: String,
    index: true,
  },
  address: UserAddressSchema,
  date: {
    type: Date,
    default: Date.now(),
    index: true,
  },
  items: [OrderItemSchema],
  itemTotal: {
    type: Number,
  },
  total: {
    type: Number,
  },
});

const InvoiceSchema = mongoose.Schema({
  user: {
    type: String,
    index: true,
  },
  invoiceNumber: {
    type: Number,
    index: true,
  },
  value: Number,
  address: UserAddressSchema,
});
InvoiceSchema.plugin(AutoIncrement, { inc_field: 'invoiceNumber' });

models.Invoice = mongoose.model('Invoice', InvoiceSchema);

const HomeTileName = mongoose.Schema({
  name: String,
});

models.HomeTile = mongoose.model('HomeTile', {
  _id: {
    type: String,
    index: true,
  },
  de: HomeTileName,
  fr: HomeTileName,
  it: HomeTileName,
  en: HomeTileName,
  category: mongoose.Schema.Types.ObjectId,
  picture: String,
  order: Number,
});

module.exports = models;
<|endoftext|>


<commit_before>/**
 * seneschal.module.js
 * Created by anonymous on 08/12/15 17:00.
 */

(function() {
    'use strict';

    angular.module('seneschal', [
            /* Shared */
            'appFoundation',
            'widgets'
        ]);

})();

<commit_msg>Modified seneschal.module<commit_after>/**
 * seneschal.module.js
 * Created by anonymous on 08/12/15 17:00.
 */

(function() {
    'use strict';

    angular
        .module('seneschal', [

            /* Shared */
            'appFoundation',
            'widgets',

            /* components */
            'dashboard'

        ]);

})();

<|endoftext|>


<commit_before>var port = process.env.MONGO_PORT || 27017,
    $db = require('monk')('localhost:' + port + '/leaderboard'),
    games = require('./games');

games.supported.forEach(function(game) {
    $db.get(game +'_dumps').index('idno', {unique: true});
    $db.get(game +'_players').index('name', {unique: true});
    $db.get(game +'_games').index('idno', {unique: true});
});

$db.get('auth').index('name', {unique: true});

module.exports = $db;<commit_msg>adding clans table<commit_after>var port = process.env.MONGO_PORT || 27017,
    $db = require('monk')('localhost:' + port + '/leaderboard'),
    games = require('./games');

games.supported.forEach(function(game) {
    $db.get(game +'_dumps').index('idno', {unique: true});
    $db.get(game +'_players').index('name', {unique: true});
    $db.get(game +'_games').index('idno', {unique: true});
    $db.get(game +'_clans').index('name', {unique: true});
});

$db.get('auth').index('name', {unique: true});

module.exports = $db;<|endoftext|>