<commit_before>/**
 * @author daniel.joppi
 * @since 2/12/14.
 */
module.exports = function(router, db) {
    var BSON = db.bson;
    //Link routes and functions

    //GET - Return all users in the DB
    router.get('/:entity', function (req, res) {
        var entity = req.params.entity;

        db.collection(entity, function(err, collection) {
            collection.find().toArray(function(err, items) {
                if (err) {
                    console.log('ERROR: ' + err);
                    res.send({entity: entity, status: "error", msg: ""+err});
                } else {
                    console.log('GET /'+entity);
                    res.send(items);
                }
            });
        });
    });
    //GET - Return a user with specified ID
    router.get('/:entity/:id', function (req, res) {
        var entity = req.params.entity;
        var id = req.params.id;

        console.log(db.collection(entity));
        db.collection(entity, function(err, collection) {
            if(err) {
                res.send({"_id": id, entity: entity, status: "error", msg: ""+ err});
                return;
            }

            collection.findOne({'_id':new BSON.ObjectID(id)}, function(err, item) {
                if (err) {
                    res.send({"_id": id, entity: entity, status: "error", msg: ""+err});
                } else {
                    console.log('GET /'+entity+'/' + id);
                    res.send(item);
                }
            });
        });
    });
    //POST - Insert a new user in the DB
    router.post('/:entity', function (req, res) {
        var entity = req.params.entity;

        var data = req.body;
        db.collection(entity, function(err, collection) {
            collection.insert(data, {safe:true}, function(err, result) {
                if (err) {
                    res.send({entity: entity, status: "error", msg: ""+err});
                } else {
                    console.log('POST /'+entity);
                    res.send({"_id": result[0].id, entity: entity, status: "inserted"});
                }
            });
        });
    });
    //PUT - Update a register already exists
    router.put('/:entity/:id', function (req, res) {
        var entity = req.params.entity;
        var id = req.params.id;
        var data = req.body;

        db.collection(entity, function(err, collection) {
            collection.update({'_id':new BSON.ObjectID(id)}, data, {safe:true}, function(err, result) {
                if (err) {
                    console.log('Error updating wine: ' + err);
                    res.send({"_id": id, entity: entity, status: "error", msg: ""+err});
                } else {
                    console.log('PUT /'+entity+'/' + id);
                    res.send({"_id": id, entity: entity, status: "updated"});
                }
            });
        });
    });
    //DELETE - Delete a TVShow with specified ID
    router.delete('/:entity/:id', function (req, res) {
        var entity = req.params.entity;
        var id = req.params.id;

        db.collection(entity, function(err, collection) {
            collection.remove({'_id':new BSON.ObjectID(id)}, {safe:true}, function(err, result) {
                if (err) {
                    res.send({"_id": id, entity: entity, status: "error", msg: ""+err});
                } else {
                    console.log('DELETE /'+entity+'/' + id);
                    res.send({"_id": id, entity: entity, status: "deleted"});
                }
            });
        });
    });

};<commit_msg>validations<commit_after>/**
 * @author daniel.joppi
 * @since 2/12/14.
 */
module.exports = function(router, db) {
    var BSON = db.bson;
    //Link routes and functions

    //GET - Return all users in the DB
    router.get('/:entity', function (req, res) {
        var entity = req.params.entity;

        var collection = db.collection(entity);
        collection.find().toArray(function (err, items) {
            if (err) {
                console.log('ERROR: ' + err);
                res.send({entity: entity, status: "error", msg: "" + err});
            } else {
                console.log('GET /' + entity);
                res.send(items);
            }
        });
    });
    //GET - Return a user with specified ID
    router.get('/:entity/:id', function (req, res) {
        var entity = req.params.entity;
        var id = req.params.id;

        var collection = db.collection(entity);
        collection.findOne({'_id': new BSON.ObjectID(id)}, function (err, item) {
            if (err) {
                res.send({"_id": id, entity: entity, status: "error", msg: "" + err});
            } else {
                console.log('GET /' + entity + '/' + id);
                res.send(item);
            }
        });
    });
    //POST - Insert a new user in the DB
    router.post('/:entity', function (req, res) {
        var entity = req.params.entity;

        var data = req.body;
        var collection = db.collection(entity);
        collection.insert(data, {safe: true}, function (err, result) {
            if (err) {
                res.send({entity: entity, status: "error", msg: "" + err});
            } else {
                console.log('POST /' + entity);
                res.send({"_id": result[0].id, entity: entity, status: "inserted"});
            }
        });
    });
    //PUT - Update a register already exists
    router.put('/:entity/:id', function (req, res) {
        var entity = req.params.entity;
        var id = req.params.id;
        var data = req.body;

        var collection = db.collection(entity);
        collection.update({'_id': new BSON.ObjectID(id)}, data, {safe: true}, function (err, result) {
            if (err) {
                console.log('Error updating wine: ' + err);
                res.send({"_id": id, entity: entity, status: "error", msg: "" + err});
            } else {
                console.log('PUT /' + entity + '/' + id);
                res.send({"_id": id, entity: entity, status: "updated"});
            }
        });
    });
    //DELETE - Delete a TVShow with specified ID
    router.delete('/:entity/:id', function (req, res) {
        var entity = req.params.entity;
        var id = req.params.id;

        var collection = db.collection(entity);
        collection.remove({'_id': new BSON.ObjectID(id)}, {safe: true}, function (err, result) {
            if (err) {
                res.send({"_id": id, entity: entity, status: "error", msg: "" + err});
            } else {
                console.log('DELETE /' + entity + '/' + id);
                res.send({"_id": id, entity: entity, status: "deleted"});
            }
        });
    });

};<|endoftext|>


<commit_before>define(['container'], function(container) {
    describe("Container module", function () {
        var containerUnderTest;

        beforeEach(function(){
            containerUnderTest = container.create();
        });

        function simulateTransitionEnd() {
            var transitionEndEvent = document.createEvent('CustomEvent');
            transitionEndEvent.initCustomEvent('webkitTransitionEnd');
            containerUnderTest.dispatchEvent(transitionEndEvent);
        }

        describe("created container", function() {
            it("is an unordered list", function() {
                expect(containerUnderTest.nodeName).toBe("UL");
            });

            it("contains 9 list items", function() {
               expect(containerUnderTest.children.length).toBe(9);
               for(var i = 0; i != containerUnderTest.children.length; ++i) {
                   expect(containerUnderTest.children[i].nodeName).toBe("LI");
               }
            });

            it("has an id of albumList", function() {
                expect(containerUnderTest.id).toBe("albumList");
            });

            it("marks central item as current", function() {
               for(var i = 0; i != containerUnderTest.children.length; ++i) {
                   if(i==4) {
                       expect(containerUnderTest.children[i].className).toBe("current");
                   } else {
                       expect(containerUnderTest.children[i].className).toBe("");
                   }
               }
            });
        });

        describe("moveNext", function() {
           it("moves all items one to the left", function() {
               var itemContent = Array.prototype.map.call(containerUnderTest.children, function (item) {
                   return item.innerHTML;
               });

               containerUnderTest.moveNext();
               simulateTransitionEnd();

               for (var i = 0; i != containerUnderTest.children.length-1; ++i) {
                   expect(containerUnderTest.children[i].innerHTML).toBe(itemContent[i+1]);
               }
           });

           it("still marks central item as current", function() {
               containerUnderTest.moveNext();
               simulateTransitionEnd();

               for(var i = 0; i != containerUnderTest.children.length; ++i) {
                   if(i==4) {
                       expect(containerUnderTest.children[i].className).toBe("current");
                   } else {
                       expect(containerUnderTest.children[i].className).toBe("");
                   }
               }
           });
        });

        describe("movePrevious", function() {
           it("moves all items one to the right", function() {
               var itemContent = Array.prototype.map.call(containerUnderTest.children, function (item) {
                   return item.innerHTML;
               });

               containerUnderTest.movePrevious();
               simulateTransitionEnd();

               for (var i = 1; i != containerUnderTest.children.length; ++i) {
                   expect(containerUnderTest.children[i].innerHTML).toBe(itemContent[i-1]);
               }
           });

           it("still marks central item as current", function() {
               containerUnderTest.movePrevious();
               simulateTransitionEnd();

               for(var i = 0; i != containerUnderTest.children.length; ++i) {
                   if(i==4) {
                       expect(containerUnderTest.children[i].className).toBe("current");
                   } else {
                       expect(containerUnderTest.children[i].className).toBe("");
                   }
               }
           });
        });
    });
});<commit_msg>Test it ignores transition end from children The container under test has to be added to the DOM for the event bubbling to work properly from the tests.<commit_after>define(['container'], function(container) {
    describe("Container module", function () {
        var containerUnderTest;

        beforeEach(function(){
            containerUnderTest = container.create();
            document.body.appendChild(containerUnderTest);
        });

        function simulateTransitionEnd(elementToFireOn) {
            var transitionEndEvent = document.createEvent('CustomEvent');
            transitionEndEvent.initCustomEvent('webkitTransitionEnd', true, true);
            elementToFireOn.dispatchEvent(transitionEndEvent);
        }

        describe("created container", function() {
            it("is an unordered list", function() {
                expect(containerUnderTest.nodeName).toBe("UL");
            });

            it("contains 9 list items", function() {
               expect(containerUnderTest.children.length).toBe(9);
               for(var i = 0; i != containerUnderTest.children.length; ++i) {
                   expect(containerUnderTest.children[i].nodeName).toBe("LI");
               }
            });

            it("has an id of albumList", function() {
                expect(containerUnderTest.id).toBe("albumList");
            });

            it("marks central item as current", function() {
               for(var i = 0; i != containerUnderTest.children.length; ++i) {
                   if(i==4) {
                       expect(containerUnderTest.children[i].className).toBe("current");
                   } else {
                       expect(containerUnderTest.children[i].className).toBe("");
                   }
               }
            });
        });

        describe("moveNext", function() {
           it("moves all items one to the left", function() {
               var itemContent = Array.prototype.map.call(containerUnderTest.children, function (item) {
                   return item.innerHTML;
               });

               containerUnderTest.moveNext();
               simulateTransitionEnd(containerUnderTest);

               for (var i = 0; i != containerUnderTest.children.length-1; ++i) {
                   expect(containerUnderTest.children[i].innerHTML).toBe(itemContent[i+1]);
               }
           });

           it("ignores transition end from children", function() {
               var itemContent = Array.prototype.map.call(containerUnderTest.children, function (item) {
                   return item.innerHTML;
               });

               containerUnderTest.moveNext();
               for(var j = 0; j != containerUnderTest.children.length; ++j) {
                   simulateTransitionEnd(containerUnderTest.children[j]);

                   for (var i = 0; i != containerUnderTest.children.length; ++i) {
                       expect(containerUnderTest.children[i].innerHTML).toBe(itemContent[i]);
                   }
               }
           });

           it("still marks central item as current", function() {
               containerUnderTest.moveNext();
               simulateTransitionEnd(containerUnderTest);

               for(var i = 0; i != containerUnderTest.children.length; ++i) {
                   if(i==4) {
                       expect(containerUnderTest.children[i].className).toBe("current");
                   } else {
                       expect(containerUnderTest.children[i].className).toBe("");
                   }
               }
           });
        });

        describe("movePrevious", function() {
           it("moves all items one to the right", function() {
               var itemContent = Array.prototype.map.call(containerUnderTest.children, function (item) {
                   return item.innerHTML;
               });

               containerUnderTest.movePrevious();
               simulateTransitionEnd(containerUnderTest);

               for (var i = 1; i != containerUnderTest.children.length; ++i) {
                   expect(containerUnderTest.children[i].innerHTML).toBe(itemContent[i-1]);
               }
           });

           it("ignores transition end from children", function() {
               var itemContent = Array.prototype.map.call(containerUnderTest.children, function (item) {
                   return item.innerHTML;
               });

               containerUnderTest.movePrevious();
               for(var j = 0; j != containerUnderTest.children.length; ++j) {
                   simulateTransitionEnd(containerUnderTest.children[j]);

                   for (var i = 0; i != containerUnderTest.children.length; ++i) {
                       expect(containerUnderTest.children[i].innerHTML).toBe(itemContent[i]);
                   }
               }
           });

           it("still marks central item as current", function() {
               containerUnderTest.movePrevious();
               simulateTransitionEnd(containerUnderTest);

               for(var i = 0; i != containerUnderTest.children.length; ++i) {
                   if(i==4) {
                       expect(containerUnderTest.children[i].className).toBe("current");
                   } else {
                       expect(containerUnderTest.children[i].className).toBe("");
                   }
               }
           });
        });
    });
});<|endoftext|>


<commit_before>import { Meteor } from 'meteor/meteor';
import { Template } from 'meteor/templating';
import { moment } from 'meteor/momentjs:moment';
import { Session } from 'meteor/session';
import { BigNumber } from 'meteor/ethereum:web3';
import { _ } from 'meteor/underscore';
import { web3 } from 'meteor/makerotc:dapple';
import { Offers, Trades } from '/imports/api/offers';
import Chart from '/imports/utils/Chart.min';
import { formatNumber, removeOutliersFromArray } from '/imports/utils/functions';
import './chart.html';

const charts = [];
Session.set('depthChart', false);
Session.set('volumeChart', false);
Session.set('rendered', false);

let askPrices = []; // Array of ask prices
let bidPrices = []; // Array of bid prices
let askAmounts = { base: [], quote: [] }; // Array of ask amounts
let bidAmounts = { base: [], quote: [] }; // Array of bid amounts
const volumes = { base: {}, quote: {} };
let days = [];

Template.chart.viewmodel({
  currentChart: 'DEPTH',
  showDepth() {
    return this.currentChart() === 'DEPTH' ? '' : 'hidden';
  },
  showVolume() {
    return this.currentChart() === 'VOLUME' ? '' : 'hidden';
  },
  prepareTooltip(tooltip, canvasId) {
    let tooltipEl = document.getElementById('chartjs-tooltip');
    if (!tooltipEl) {
      tooltipEl = document.createElement('div');
      tooltipEl.id = 'chartjs-tooltip';
      document.body.appendChild(tooltipEl);
    }
    // Hide if no tooltip
    if (tooltip.opacity === 0) {
      tooltipEl.style.opacity = 0;
      return false;
    }
    // Set caret Position
    tooltipEl.classList.remove('above', 'below', 'no-transform');
    if (tooltip.yAlign) {
      tooltipEl.classList.add(tooltip.yAlign);
    } else {
      tooltipEl.classList.add('no-transform');
    }

    const position = document.getElementById(canvasId).getBoundingClientRect();
    tooltipEl.style.left = `${position.left + tooltip.caretX}px`;
    tooltipEl.style.top = `${position.top + document.body.scrollTop + tooltip.caretY}px`;
    tooltipEl.style.padding = `${tooltip.yPadding}px${tooltip.xPadding}px`;

    return tooltipEl;
  },
  fillDepthChart() {
    Meteor.defer(() => {
      if (Session.get('rendered') && typeof charts.depth === 'undefined') {
        const ctx = document.getElementById('market-chart-depth');
        charts.depth = new Chart(ctx, {
          type: 'line',
          data: {},
          options: {
            maintainAspectRatio: true,
            layout: {
              padding: 5,
            },
            tooltips: {
              enabled: false,
              mode: 'index',
              position: 'nearest',
              custom: (tooltip) => {
                const tooltipEl = this.prepareTooltip(tooltip, 'market-chart-depth');
                if (tooltipEl && tooltip.body) {
                  const price = tooltip.dataPoints[0].xLabel;
                  let type = null;
                  let quoteAmount = null;
                  let baseAmount = null;
                  [type, quoteAmount] = tooltip.body[0].lines[0].split(': ');

                  if (type === 'Sell') {
                    quoteAmount = askAmounts.quote[askPrices.indexOf(price)];
                    baseAmount = askAmounts.base[askPrices.indexOf(price)];
                  } else {
                    quoteAmount = bidAmounts.quote[bidPrices.indexOf(price)];
                    baseAmount = bidAmounts.base[bidPrices.indexOf(price)];
                  }
                  quoteAmount = formatNumber(web3.fromWei(quoteAmount), 5);
                  baseAmount = formatNumber(web3.fromWei(baseAmount), 5);

                  tooltipEl.innerHTML =
                    `

                      Price
                      ${formatNumber(price, 5)}

                      SUM(${Session.get('quoteCurrency')})
                      ${quoteAmount}

                      SUM(${Session.get('baseCurrency')})
                      ${baseAmount}

`;

                  tooltipEl.style.opacity = 1;
                }
              },
            },
            legend: {
              display: false,
            },
            scales: {
              yAxes: [{
                ticks: {
                  beginAtZero: true,
                },
              }],
              xAxes: [{
                display: false,
              }],
            },
          },
        });
        Session.set('depthChart', true);
      }
    });

    if (Session.get('depthChart')
        && Session.get('isConnected') && !Session.get('outOfSync')
        && !Session.get('loading') && Session.get('loadingProgress') === 100) {
      askPrices = [];
      bidPrices = [];
      askAmounts = { base: [], quote: [] };
      bidAmounts = { base: [], quote: [] };
      const quoteCurrency = Session.get('quoteCurrency');
      const baseCurrency = Session.get('baseCurrency');
      let bids = Offers.find({ buyWhichToken: baseCurrency, sellWhichToken: quoteCurrency },
                                { sort: { bid_price_sort: 1 } }).fetch();
      let asks = Offers.find({ buyWhichToken: quoteCurrency, sellWhichToken: baseCurrency },
                                { sort: { ask_price_sort: 1 } }).fetch();

      bids = removeOutliersFromArray(bids, 'bid_price_sort', 3);
      asks = removeOutliersFromArray(asks, 'ask_price_sort', 3);

      asks.forEach(ask => {
        const index = askPrices.indexOf(ask.ask_price_sort);
        if (index === -1) {
          // If it is the first order for this price

          // Keep track of new price index
          askPrices.push(ask.ask_price_sort);

          if (askAmounts.quote.length > 0) {
            // If there is a lower price we need to sum the amount of the previous price (to make a cumulative graph)
            askAmounts.quote.push(askAmounts.quote[askAmounts.quote.length - 1].add(new BigNumber(ask.buyHowMuch)));
            askAmounts.base.push(askAmounts.base[askAmounts.base.length - 1].add(new BigNumber(ask.sellHowMuch)));
          } else {
            askAmounts.quote.push(new BigNumber(ask.buyHowMuch));
            askAmounts.base.push(new BigNumber(ask.sellHowMuch));
          }
        } else {
          // If there was already another offer for the same price we add the new amount
          askAmounts.quote[index] = askAmounts.quote[index].add(new BigNumber(ask.buyHowMuch));
          askAmounts.base[index] = askAmounts.base[index].add(new BigNumber(ask.sellHowMuch));
        }
      });

      bids.forEach(bid => {
        const index = bidPrices.indexOf(bid.bid_price_sort);
        if (index === -1) {
          // If it is the first order for this price

          // Keep track of new price index and value
          bidPrices.push(bid.bid_price_sort);
          bidAmounts.quote.push(new BigNumber(bid.sellHowMuch));
          bidAmounts.base.push(new BigNumber(bid.buyHowMuch));
        } else {
          bidAmounts.quote[index] = bidAmounts.quote[index].add(new BigNumber(bid.sellHowMuch));
          bidAmounts.base[index] = bidAmounts.base[index].add(new BigNumber(bid.buyHowMuch));
        }

        // It is necessary to update all the previous prices adding the actual amount (to make a cumulative graph)
        bidAmounts.quote = bidAmounts.quote.map((b, i) =>
                            ((i < bidAmounts.quote.length - 1) ? b.add(bid.sellHowMuch) : b));
        bidAmounts.base = bidAmounts.base.map((b, i) =>
                            ((i < bidAmounts.base.length - 1) ? b.add(bid.buyHowMuch) : b));
      });

      // All price values (bids & asks)
      const vals = _.uniq(bidPrices.concat(askPrices).sort((a, b) => (a < b ? -1 : 1)));

      // Preparing arrays for graph
      const askAmountsGraph = [];
      const bidAmountsGraph = [];
      let index = null;
      let amount = null;

      for (let i = 0; i < vals.length; i++) {
        index = askPrices.indexOf(vals[i]);
        if (index !== -1) {
          // If there is a specific value for the price in asks, we add it
          amount = formatNumber(web3.fromWei(askAmounts.quote[index]), 3).replace(/,/g, '');
        } else if (askPrices.length === 0 || vals[i] < askPrices[0] || vals[i] > askPrices[askPrices.length - 1]) {
          // If the price is lower or higher than the asks range there is not value to print in the graph
          amount = null;
        } else {
          // If there is not an ask amount for this price, we need to add the previous amount
          amount = askAmountsGraph[askAmountsGraph.length - 1];
        }
        askAmountsGraph.push({ x: vals[i], y: amount });

        index = bidPrices.indexOf(vals[i]);
        if (index !== -1) {
          // If there is a specific value for the price in bids, we add it
          amount = formatNumber(web3.fromWei(bidAmounts.quote[index]), 3).replace(/,/g, '');
        } else if (bidPrices.length === 0 || vals[i] < bidPrices[0] || vals[i] > bidPrices[bidPrices.length - 1]) {
          // If the price is lower or higher than the bids range there is not value to print in the graph
          amount = null;
        } else {
          // If there is not a bid amount for this price, we need to add the next available amount
          for (let j = 0; j < askPrices.length; j++) {
            if (bidPrices[j] >= vals[i]) {
              amount = formatNumber(web3.fromWei(bidAmounts.quote[j]), 3).replace(/,/g, '');
              break;
            }
          }
        }
        bidAmountsGraph.push({ x: vals[i], y: amount });
      }

      charts.depth.data.labels = vals;
      charts.depth.data.datasets = [
        {
          label: 'Buy',
          data: bidAmountsGraph,
          backgroundColor: 'rgba(38, 166, 154, 0.2)',
          borderColor: 'rgba(38, 166, 154, 1)',
          borderWidth: 3,
          // fill: false,
          pointStyle: 'circle',
          pointRadius: 3,
          pointBorderWidth: 1,
          pointBorderColor: '#1ABC9C',
          pointBackgroundColor: '#1ABC9C',
          hoverBackgroundColor: '#1ABC9C',
          hoverBorderColor: '#1ABC9C',
          hoverBorderWidth: 5,
          steppedLine: true,
          invertedStep: true,
        },
        {
          label: 'Sell',
          data: askAmountsGraph,
          backgroundColor: 'rgba(239, 83, 80, 0.2)',
          borderColor: '#EF5350',
          borderWidth: 3,
          // fill: false,
          pointStyle: 'circle',
          pointRadius: 3,
          pointBorderWidth: 1,
          pointBorderColor: '#EF5350',
          pointBackgroundColor: '#EF5350',
          hoverBackgroundColor: '#EF5350',
          hoverBorderColor: '#EF5350',
          hoverBorderWidth: 5,
          steppedLine: true,
        }];
      charts.depth.update();
    }
  },
  fillVolumeChart() {
    Meteor.defer(() => {
      if (Session.get('rendered') && typeof charts.volume === 'undefined') {
        const ctx = document.getElementById('market-chart-volume');
        charts.volume = new Chart(ctx, {
          type: 'line',
          data: {},
          options: {
            maintainAspectRatio: true,
            layout: {
              padding: 5,
            },
            tooltips: {
              enabled: false,
              mode: 'index',
              position: 'nearest',
              custom: (tooltip) => {
                const tooltipEl = this.prepareTooltip(tooltip, 'market-chart-volume');
                if (tooltipEl && tooltip.body) {
                  const date = parseInt(tooltip.dataPoints[0].xLabel, 10);
                  let quoteAmount = null;
                  let baseAmount = null;
                  quoteAmount = formatNumber(web3.fromWei(volumes.quote[date]), 5);
                  baseAmount = formatNumber(web3.fromWei(volumes.base[date]), 5);

                  tooltipEl.innerHTML =
                    `

                      Date
                      ${moment(date).format('ll')}

                      SUM(${Session.get('quoteCurrency')})
                      ${quoteAmount}

                      SUM(${Session.get('baseCurrency')})
                      ${baseAmount}

`;

                  tooltipEl.style.opacity = 1;
                }
              },
            },
            legend: {
              display: false,
            },
            scales: {
              yAxes: [{
                ticks: {
                  beginAtZero: true,
                },
              }],
              xAxes: [{
                display: false,
              }],
            },
          },
        });
        Session.set('volumeChart', true);
      }
    });

    if (Session.get('volumeChart')
        && !Session.get('loadingTradeHistory')) {
      const quoteCurrency = Session.get('quoteCurrency');
      const baseCurrency = Session.get('baseCurrency');
      let day = null;
      days = [];
      volumes.base = {};
      volumes.quote = {};
      for (let i = 6; i >= 0; i--) {
        day = moment(Date.now()).startOf('day').subtract(i, 'days');
        days.push(day);
        volumes.base[day.unix() * 1000] = new BigNumber(0);
        volumes.quote[day.unix() * 1000] = new BigNumber(0);
      }

      const trades = Trades.find({ $or: [
        { buyWhichToken: baseCurrency, sellWhichToken: quoteCurrency },
        { buyWhichToken: quoteCurrency, sellWhichToken: baseCurrency },
      ],
        timestamp: { $gte: days[0].unix() },
      });

      trades.forEach((trade) => {
        day = moment.unix(trade.timestamp).startOf('day').unix() * 1000;
        if (trade.buyWhichToken === quoteCurrency) {
          volumes.quote[day] = volumes.quote[day].add(new BigNumber(trade.buyHowMuch));
          volumes.base[day] = volumes.base[day].add(new BigNumber(trade.sellHowMuch));
        } else {
          volumes.quote[day] = volumes.quote[day].add(new BigNumber(trade.sellHowMuch));
          volumes.base[day] = volumes.base[day].add(new BigNumber(trade.buyHowMuch));
        }
      });

      charts.volume.data.labels = days;

      charts.volume.data.datasets = [{
        label: 'Volume',
        data: Object.keys(volumes.quote).map((key) =>
                                              formatNumber(web3.fromWei(volumes.quote[key]), 5).replace(/,/g, '')),
        backgroundColor: 'rgba(140, 133, 200, 0.1)',
        borderColor: '#8D86C9',
        borderWidth: 3,
        // fill: false,
        pointBackgroundColor: '#8D86C9',
        pointRadius: 3,
      }];

      charts.volume.update();
    }
  },
});

Template.chart.onRendered(() => {
  Session.set('rendered', true);
});
<commit_msg>fix sums on depth chart<commit_after>import { Meteor } from 'meteor/meteor';
import { Template } from 'meteor/templating';
import { moment } from 'meteor/momentjs:moment';
import { Session } from 'meteor/session';
import { BigNumber } from 'meteor/ethereum:web3';
import { _ } from 'meteor/underscore';
import { web3 } from 'meteor/makerotc:dapple';
import { Offers, Trades } from '/imports/api/offers';
import Chart from '/imports/utils/Chart.min';
import { formatNumber, removeOutliersFromArray } from '/imports/utils/functions';
import './chart.html';

const charts = [];
Session.set('depthChart', false);
Session.set('volumeChart', false);
Session.set('rendered', false);

let askPrices = []; // Array of ask prices
let bidPrices = []; // Array of bid prices
let askAmounts = { base: [], quote: [] }; // Array of ask amounts
let bidAmounts = { base: [], quote: [] }; // Array of bid amounts
const volumes = { base: {}, quote: {} };
let days = [];

Template.chart.viewmodel({
  currentChart: 'DEPTH',
  showDepth() {
    return this.currentChart() === 'DEPTH' ? '' : 'hidden';
  },
  showVolume() {
    return this.currentChart() === 'VOLUME' ? '' : 'hidden';
  },
  prepareTooltip(tooltip, canvasId) {
    let tooltipEl = document.getElementById('chartjs-tooltip');
    if (!tooltipEl) {
      tooltipEl = document.createElement('div');
      tooltipEl.id = 'chartjs-tooltip';
      document.body.appendChild(tooltipEl);
    }
    // Hide if no tooltip
    if (tooltip.opacity === 0) {
      tooltipEl.style.opacity = 0;
      return false;
    }
    // Set caret Position
    tooltipEl.classList.remove('above', 'below', 'no-transform');
    if (tooltip.yAlign) {
      tooltipEl.classList.add(tooltip.yAlign);
    } else {
      tooltipEl.classList.add('no-transform');
    }

    const position = document.getElementById(canvasId).getBoundingClientRect();
    tooltipEl.style.left = `${position.left + tooltip.caretX}px`;
    tooltipEl.style.top = `${position.top + document.body.scrollTop + tooltip.caretY}px`;
    tooltipEl.style.padding = `${tooltip.yPadding}px${tooltip.xPadding}px`;

    return tooltipEl;
  },
  fillDepthChart() {
    Meteor.defer(() => {
      if (Session.get('rendered') && typeof charts.depth === 'undefined') {
        const ctx = document.getElementById('market-chart-depth');
        charts.depth = new Chart(ctx, {
          type: 'line',
          data: {},
          options: {
            maintainAspectRatio: true,
            layout: {
              padding: 5,
            },
            tooltips: {
              enabled: false,
              mode: 'index',
              position: 'nearest',
              custom: (tooltip) => {
                const tooltipEl = this.prepareTooltip(tooltip, 'market-chart-depth');
                if (tooltipEl && tooltip.body) {
                  const price = tooltip.dataPoints[0].xLabel;
                  let type = null;
                  let quoteAmount = null;
                  let baseAmount = null;
                  [type, quoteAmount] = tooltip.body[0].lines[0].split(': ');
                  if (type === 'Sell') {
                    quoteAmount = askAmounts.quote[askPrices.indexOf(price)];
                    baseAmount = askAmounts.base[askPrices.indexOf(price)];
                  } else {
                    quoteAmount = bidAmounts.quote[bidPrices.indexOf(price)];
                    baseAmount = bidAmounts.base[bidPrices.indexOf(price)];
                  }
                  quoteAmount = formatNumber(web3.fromWei(quoteAmount), 5);
                  baseAmount = formatNumber(web3.fromWei(baseAmount), 5);

                  tooltipEl.innerHTML =
                    `

                      Price
                      ${formatNumber(price, 5)}

                      SUM(${Session.get('quoteCurrency')})
                      ${quoteAmount}

                      SUM(${Session.get('baseCurrency')})
                      ${baseAmount}

`;

                  tooltipEl.style.opacity = 1;
                }
              },
            },
            legend: {
              display: false,
            },
            scales: {
              yAxes: [{
                ticks: {
                  beginAtZero: true,
                },
              }],
              xAxes: [{
                display: false,
              }],
            },
          },
        });
        Session.set('depthChart', true);
      }
    });

    if (Session.get('depthChart')
        && Session.get('isConnected') && !Session.get('outOfSync')
        && !Session.get('loading') && Session.get('loadingProgress') === 100) {
      askPrices = [];
      bidPrices = [];
      askAmounts = { base: [], quote: [] };
      bidAmounts = { base: [], quote: [] };
      const quoteCurrency = Session.get('quoteCurrency');
      const baseCurrency = Session.get('baseCurrency');
      let bids = Offers.find({ buyWhichToken: baseCurrency, sellWhichToken: quoteCurrency },
                                { sort: { bid_price_sort: 1 } }).fetch();
      let asks = Offers.find({ buyWhichToken: quoteCurrency, sellWhichToken: baseCurrency },
                                { sort: { ask_price_sort: 1 } }).fetch();

      bids = removeOutliersFromArray(bids, 'bid_price_sort', 3);
      asks = removeOutliersFromArray(asks, 'ask_price_sort', 3);

      asks.forEach(ask => {
        const index = askPrices.indexOf(ask.ask_price);
        if (index === -1) {
          // If it is the first order for this price

          // Keep track of new price index
          askPrices.push(ask.ask_price);

          if (askAmounts.quote.length > 0) {
            // If there is a lower price we need to sum the amount of the previous price (to make a cumulative graph)
            askAmounts.quote.push(askAmounts.quote[askAmounts.quote.length - 1].add(new BigNumber(ask.buyHowMuch)));
            askAmounts.base.push(askAmounts.base[askAmounts.base.length - 1].add(new BigNumber(ask.sellHowMuch)));
          } else {
            askAmounts.quote.push(new BigNumber(ask.buyHowMuch));
            askAmounts.base.push(new BigNumber(ask.sellHowMuch));
          }
        } else {
          // If there was already another offer for the same price we add the new amount
          askAmounts.quote[index] = askAmounts.quote[index].add(new BigNumber(ask.buyHowMuch));
          askAmounts.base[index] = askAmounts.base[index].add(new BigNumber(ask.sellHowMuch));
        }
      });

      bids.forEach(bid => {
        const index = bidPrices.indexOf(bid.bid_price);
        if (index === -1) {
          // If it is the first order for this price

          // Keep track of new price index and value
          bidPrices.push(bid.bid_price);
          bidAmounts.quote.push(new BigNumber(bid.sellHowMuch));
          bidAmounts.base.push(new BigNumber(bid.buyHowMuch));
        } else {
          bidAmounts.quote[index] = bidAmounts.quote[index].add(new BigNumber(bid.sellHowMuch));
          bidAmounts.base[index] = bidAmounts.base[index].add(new BigNumber(bid.buyHowMuch));
        }

        // It is necessary to update all the previous prices adding the actual amount (to make a cumulative graph)
        bidAmounts.quote = bidAmounts.quote.map((b, i) =>
                            ((i < bidAmounts.quote.length - 1) ? b.add(bid.sellHowMuch) : b));
        bidAmounts.base = bidAmounts.base.map((b, i) =>
                            ((i < bidAmounts.base.length - 1) ? b.add(bid.buyHowMuch) : b));
      });

      // All price values (bids & asks)
      const vals = _.uniq(bidPrices.concat(askPrices).sort((a, b) => (a < b ? -1 : 1)));

      // Preparing arrays for graph
      const askAmountsGraph = [];
      const bidAmountsGraph = [];
      let index = null;
      let amount = null;

      for (let i = 0; i < vals.length; i++) {
        index = askPrices.indexOf(vals[i]);
        if (index !== -1) {
          // If there is a specific value for the price in asks, we add it
          amount = formatNumber(web3.fromWei(askAmounts.quote[index]), 3).replace(/,/g, '');
        } else if (askPrices.length === 0 || vals[i] < askPrices[0] || vals[i] > askPrices[askPrices.length - 1]) {
          // If the price is lower or higher than the asks range there is not value to print in the graph
          amount = null;
        } else {
          // If there is not an ask amount for this price, we need to add the previous amount
          amount = askAmountsGraph[askAmountsGraph.length - 1];
        }
        askAmountsGraph.push({ x: vals[i], y: amount });

        index = bidPrices.indexOf(vals[i]);
        if (index !== -1) {
          // If there is a specific value for the price in bids, we add it
          amount = formatNumber(web3.fromWei(bidAmounts.quote[index]), 3).replace(/,/g, '');
        } else if (bidPrices.length === 0 || vals[i] < bidPrices[0] || vals[i] > bidPrices[bidPrices.length - 1]) {
          // If the price is lower or higher than the bids range there is not value to print in the graph
          amount = null;
        } else {
          // If there is not a bid amount for this price, we need to add the next available amount
          for (let j = 0; j < askPrices.length; j++) {
            if (bidPrices[j] >= vals[i]) {
              amount = formatNumber(web3.fromWei(bidAmounts.quote[j]), 3).replace(/,/g, '');
              break;
            }
          }
        }
        bidAmountsGraph.push({ x: vals[i], y: amount });
      }

      charts.depth.data.labels = vals;
      charts.depth.data.datasets = [
        {
          label: 'Buy',
          data: bidAmountsGraph,
          backgroundColor: 'rgba(38, 166, 154, 0.2)',
          borderColor: 'rgba(38, 166, 154, 1)',
          borderWidth: 3,
          // fill: false,
          pointStyle: 'circle',
          pointRadius: 3,
          pointBorderWidth: 1,
          pointBorderColor: '#1ABC9C',
          pointBackgroundColor: '#1ABC9C',
          hoverBackgroundColor: '#1ABC9C',
          hoverBorderColor: '#1ABC9C',
          hoverBorderWidth: 5,
          steppedLine: true,
          invertedStep: true,
        },
        {
          label: 'Sell',
          data: askAmountsGraph,
          backgroundColor: 'rgba(239, 83, 80, 0.2)',
          borderColor: '#EF5350',
          borderWidth: 3,
          // fill: false,
          pointStyle: 'circle',
          pointRadius: 3,
          pointBorderWidth: 1,
          pointBorderColor: '#EF5350',
          pointBackgroundColor: '#EF5350',
          hoverBackgroundColor: '#EF5350',
          hoverBorderColor: '#EF5350',
          hoverBorderWidth: 5,
          steppedLine: true,
        }];
      charts.depth.update();
    }
  },
  fillVolumeChart() {
    Meteor.defer(() => {
      if (Session.get('rendered') && typeof charts.volume === 'undefined') {
        const ctx = document.getElementById('market-chart-volume');
        charts.volume = new Chart(ctx, {
          type: 'line',
          data: {},
          options: {
            maintainAspectRatio: true,
            layout: {
              padding: 5,
            },
            tooltips: {
              enabled: false,
              mode: 'index',
              position: 'nearest',
              custom: (tooltip) => {
                const tooltipEl = this.prepareTooltip(tooltip, 'market-chart-volume');
                if (tooltipEl && tooltip.body) {
                  const date = parseInt(tooltip.dataPoints[0].xLabel, 10);
                  let quoteAmount = null;
                  let baseAmount = null;
                  quoteAmount = formatNumber(web3.fromWei(volumes.quote[date]), 5);
                  baseAmount = formatNumber(web3.fromWei(volumes.base[date]), 5);

                  tooltipEl.innerHTML =
                    `

                      Date
                      ${moment(date).format('ll')}

                      SUM(${Session.get('quoteCurrency')})
                      ${quoteAmount}

                      SUM(${Session.get('baseCurrency')})
                      ${baseAmount}

`;

                  tooltipEl.style.opacity = 1;
                }
              },
            },
            legend: {
              display: false,
            },
            scales: {
              yAxes: [{
                ticks: {
                  beginAtZero: true,
                },
              }],
              xAxes: [{
                display: false,
              }],
            },
          },
        });
        Session.set('volumeChart', true);
      }
    });

    if (Session.get('volumeChart')
        && !Session.get('loadingTradeHistory')) {
      const quoteCurrency = Session.get('quoteCurrency');
      const baseCurrency = Session.get('baseCurrency');
      let day = null;
      days = [];
      volumes.base = {};
      volumes.quote = {};
      for (let i = 6; i >= 0; i--) {
        day = moment(Date.now()).startOf('day').subtract(i, 'days');
        days.push(day);
        volumes.base[day.unix() * 1000] = new BigNumber(0);
        volumes.quote[day.unix() * 1000] = new BigNumber(0);
      }

      const trades = Trades.find({ $or: [
        { buyWhichToken: baseCurrency, sellWhichToken: quoteCurrency },
        { buyWhichToken: quoteCurrency, sellWhichToken: baseCurrency },
      ],
        timestamp: { $gte: days[0].unix() },
      });

      trades.forEach((trade) => {
        day = moment.unix(trade.timestamp).startOf('day').unix() * 1000;
        if (trade.buyWhichToken === quoteCurrency) {
          volumes.quote[day] = volumes.quote[day].add(new BigNumber(trade.buyHowMuch));
          volumes.base[day] = volumes.base[day].add(new BigNumber(trade.sellHowMuch));
        } else {
          volumes.quote[day] = volumes.quote[day].add(new BigNumber(trade.sellHowMuch));
          volumes.base[day] = volumes.base[day].add(new BigNumber(trade.buyHowMuch));
        }
      });

      charts.volume.data.labels = days;

      charts.volume.data.datasets = [{
        label: 'Volume',
        data: Object.keys(volumes.quote).map((key) =>
                                              formatNumber(web3.fromWei(volumes.quote[key]), 5).replace(/,/g, '')),
        backgroundColor: 'rgba(140, 133, 200, 0.1)',
        borderColor: '#8D86C9',
        borderWidth: 3,
        // fill: false,
        pointBackgroundColor: '#8D86C9',
        pointRadius: 3,
      }];

      charts.volume.update();
    }
  },
});

Template.chart.onRendered(() => {
  Session.set('rendered', true);
});
<|endoftext|>


<commit_before>var express = require('express');
var router = express.Router();
var mdb = require('moviedb')('1046d0e8bf3b7860228747333688b85d');
var http = require('http');
var moviesService = require('../services/movies-service');
var locale = require('../locale/en_gb');
var downloader = require('downloader');
var fs = require('fs-extra');
var flash = require('connect-flash');
var session = require('express-session');

var movieData,
	imgDir = 'public/images/w342/',
	thumbDir = 'public/images/w92/';

router.use(flash());

router.route('/:id')
.get(function (aRequest, aResponse) {
	moviesService.findMovie(aRequest.params.id, function (aError, aResults) {
		var cDetails = aResults, flashSuccess = aRequest.flash('success'),
			flashError = aRequest.flash('error');

		movieData = aResults.movie;

		if (movieData) {
			if(!aResults.movie.local_img) {
				fs.mkdirs(imgDir, function (err) {
				  if (err) return console.error(err);

				  	downloader.download('http://image.tmdb.org/t/p/w342' + movieData.poster_path, imgDir);
				  	downloader.on('done', function (aResponse) {
				  		moviesService.updateImg(aRequest.params.id, function (aError, aResults) {});
				  	});
				})
			}

			if (!aResults.movie.local_thumb) {
				fs.mkdirs(thumbDir, function (err) {
				  if (err) return console.error(err);

				  	downloader.download('http://image.tmdb.org/t/p/w92' + movieData.poster_path, thumbDir);
				  	downloader.on('done', function (aResponse) {
				  		moviesService.updateThumb(aRequest.params.id, function (aError, aResults) {});
				  	});
				})

			}
		}

		if (!aResults.movie) {
			mdb.movieInfo({id: aRequest.params.id  }, function(aError, aResults) {
				var vm = {
					detailsView	: true,
					user 				: aRequest.user || null,
					lang				: locale,
					movie 			: movieData,
					cDetails 		: cDetails,
					success 		: flashSuccess,
					error 			: flashError
				};

				movieData = aResults;

				return aResponse.render('index', vm);
			});
		} else {
			var vm = {
				detailsView		: true,
				user 					: aRequest.user || null,
				inCollection	: true,
				lang					: locale,
				movie 				: movieData,
				success 			: flashSuccess,
				error 				: flashError
			};

			return aResponse.render('index', vm);
		}

	});
})
.post(function (aRequest, aResponse) {
	movieData.collection_wanted = false;
	moviesService.addMovie(movieData, aRequest.body, function (aError, aMovie) {
		if (aError) {
			console.error(aError);

			aRequest.flash('error', 'Entry not added to collection');

			return aResponse.redirect('/movie-details/' + aRequest.params.id);
		}

		aRequest.flash('success', 'Entry added to collection');

		return aResponse.redirect('/movie-details/' + aRequest.params.id);
	});
});

router.route('/:id/edit')
.get(function (aRequest, aResponse) {
	moviesService.findMovie(aRequest.params.id, function (aError, aResults) {
		var flashSuccess = aRequest.flash('success'),
			flashError = aRequest.flash('error'),
			vm = {
			detailsView 	: true,
			inCollection	: true,
			editable			: true,
			user 					: aRequest.user || null,
			lang					: locale,
			movie 				: aResults.movie,
			cDetails 			: aResults,
			success 			: flashSuccess,
			error 				: flashError
		};

		return aResponse.render('index', vm);
	});
})
.post(function (aRequest, aResponse) {
	moviesService.updateMovie(aRequest.params.id, aRequest.body, function (aError, aResults) {
		if (aError) {
			console.error(aError);
			aRequest.flash('success', 'Entry not updated');

			return aResponse.redirect('/movie-details/' + aRequest.params.id);
		}

		aRequest.flash('success', 'Entry successfully updated');

		return aResponse.redirect('/movie-details/' + aRequest.params.id);
	})
});

module.exports = router;<commit_msg>v0.9.1.1: Bug fix: Detail view for non-collection items<commit_after>var express = require('express');
var router = express.Router();
var mdb = require('moviedb')('1046d0e8bf3b7860228747333688b85d');
var http = require('http');
var moviesService = require('../services/movies-service');
var locale = require('../locale/en_gb');
var downloader = require('downloader');
var fs = require('fs-extra');
var flash = require('connect-flash');
var session = require('express-session');

var movieData,
	imgDir = 'public/images/w342/',
	thumbDir = 'public/images/w92/';

router.use(flash());

router.route('/:id')
.get(function (aRequest, aResponse) {
	moviesService.findMovie(aRequest.params.id, function (aError, aResults) {
		var cDetails = aResults, flashSuccess = aRequest.flash('success'),
			flashError = aRequest.flash('error');

		movieData = aResults.movie;

		if (movieData) {
			if(!aResults.movie.local_img) {
				fs.mkdirs(imgDir, function (err) {
				  if (err) return console.error(err);

				  	downloader.download('http://image.tmdb.org/t/p/w342' + movieData.poster_path, imgDir);
				  	downloader.on('done', function (aResponse) {
				  		moviesService.updateImg(aRequest.params.id, function (aError, aResults) {});
				  	});
				})
			}

			if (!aResults.movie.local_thumb) {
				fs.mkdirs(thumbDir, function (err) {
				  if (err) return console.error(err);

				  	downloader.download('http://image.tmdb.org/t/p/w92' + movieData.poster_path, thumbDir);
				  	downloader.on('done', function (aResponse) {
				  		moviesService.updateThumb(aRequest.params.id, function (aError, aResults) {});
				  	});
				})

			}
		}

		if (!aResults.movie) {
			mdb.movieInfo({id: aRequest.params.id  }, function(aError, aResults) {
				var vm;
				movieData = aResults;
				vm = {
					detailsView	: true,
					user 				: aRequest.user || null,
					lang				: locale,
					movie 			: movieData,
					cDetails 		: cDetails,
					success 		: flashSuccess,
					error 			: flashError
				};

				return aResponse.render('index', vm);
			});
		} else {
			var vm = {
				detailsView		: true,
				user 					: aRequest.user || null,
				inCollection	: true,
				lang					: locale,
				movie 				: movieData,
				success 			: flashSuccess,
				error 				: flashError
			};

			return aResponse.render('index', vm);
		}

	});
})
.post(function (aRequest, aResponse) {
	movieData.collection_wanted = false;
	moviesService.addMovie(movieData, aRequest.body, function (aError, aMovie) {
		if (aError) {
			console.error(aError);

			aRequest.flash('error', 'Entry not added to collection');

			return aResponse.redirect('/movie-details/' + aRequest.params.id);
		}

		aRequest.flash('success', 'Entry added to collection');

		return aResponse.redirect('/movie-details/' + aRequest.params.id);
	});
});

router.route('/:id/edit')
.get(function (aRequest, aResponse) {
	moviesService.findMovie(aRequest.params.id, function (aError, aResults) {
		var flashSuccess = aRequest.flash('success'),
			flashError = aRequest.flash('error'),
			vm = {
			detailsView 	: true,
			inCollection	: true,
			editable			: true,
			user 					: aRequest.user || null,
			lang					: locale,
			movie 				: aResults.movie,
			cDetails 			: aResults,
			success 			: flashSuccess,
			error 				: flashError
		};

		return aResponse.render('index', vm);
	});
})
.post(function (aRequest, aResponse) {
	moviesService.updateMovie(aRequest.params.id, aRequest.body, function (aError, aResults) {
		if (aError) {
			console.error(aError);
			aRequest.flash('success', 'Entry not updated');

			return aResponse.redirect('/movie-details/' + aRequest.params.id);
		}

		aRequest.flash('success', 'Entry successfully updated');

		return aResponse.redirect('/movie-details/' + aRequest.params.id);
	})
});

module.exports = router;<|endoftext|>


<commit_before>/**
 * Copyright 2015-2019 OpenCB
 *
 * 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.
 */

import {LitElement, html} from "lit";
import {classMap} from "lit/directives/class-map.js";
import UtilsNew from "./../../core/utilsNew.js";
import LitUtils from "./utils/lit-utils.js";
import NotificationUtils from "./utils/notification-utils.js";
import BioinfoUtils from "../../core/bioinfo/bioinfo-utils.js";
import "./forms/select-token-filter";
import "../commons/filters/ontology-autocomplete-filter.js";

export default class VariantModalOntology extends LitElement {

    constructor() {
        super();
        this.#init();
    }

    createRenderRoot() {
        return this;
    }

    static get properties() {
        return {
            config: {
                type: Object
            },
            selectedTerms: {
                type: String
            },
        };
    }

    #init() {
        this._prefix = UtilsNew.randomString(8);
        this.ontologyConfig = {
            root: "https://ws.zettagenomics.com/cellbase/webservices/rest/v5/hsapiens/feature/ontology",
            tree: {
                "HP": [
                    "HP:0012823",
                    "HP:0040279",
                    "HP:0000005",
                    "HP:0040006",
                    "HP:0000118",
                ],
                "GO": [
                    "GO:0008150",
                    "GO:0005575",
                    "GO:0003674"
                ],
            },
            search: "/search",
        };
        this.rootTree = {text: "All", nodes: [], selectable: false};
        this.selectedTerms = null;
    }

    update(changedProperties) {
        if (changedProperties.has("selectedTerms")) {
            // selectedTerm observer to handle subsequent reopening of the modal after a first selection
        }

        if (changedProperties.has("config")) {
            this._config = {...this.getDefaultConfig(), ...this.config};
            this.ontologyFilterObserver();
        }
        super.update(changedProperties);
    }

    ontologyFilterObserver() {
        this.loadTermsTree();
    }

    updateTerms(e) {
        this.selectedTerms = e.detail.value;
        this.onFilterChange();
    }

    addTerm(ontologyId) {
        if (ontologyId) {
            const elms = this.selectedTerms ? this.selectedTerms.split(",") : [];
            const arr = [...new Set([...elms, ontologyId])];
            this.selectedTerms = arr.join(",");
        }
        this.onFilterChange();
    }

    onFilterChange() {
        LitUtils.dispatchCustomEvent(this, "filterChange", this.selectedTerms);
    }

    #getGoTerm(goTerm) {
        return this.ontologyConfig.root + this.ontologyConfig.search + "?id=" + goTerm + "&sort=name&order=ASCENDING&skip=0&limit=10";
    }

    #getOntologies(results, child) {
        return results.map((result, index) => {
            const hasChildren = UtilsNew.isNotEmptyArray(result?.children);
            return {
                name: result.name,
                description: result.description,
                comment: result.comment,
                synonyms: result?.synonyms,
                has_children: hasChildren,
                children_count: hasChildren ? result.children.length : 0,
                children: hasChildren ? this.#getGoTerm(result.children): "",
                nodes: hasChildren ? [] : null,
                obo_id: result.id,
                depth: !child? 0: child?.depth + 1,
                path: !child ? index : "000",
                selectable: true,
                state: {expanded: false},
            };
        });
    }

    async loadTermsTree() {
        const defaultsNodes = this.ontologyConfig.tree[this._config.ontologyFilter];
        if (UtilsNew.isNotEmptyArray(defaultsNodes)) {
            try {
                const fetchOntology = await fetch(this.#getGoTerm(defaultsNodes.join(",")));
                const ontologyTerms = await fetchOntology.json();
                const results = ontologyTerms.responses[0].results;
                this.rootTree.nodes = [...this.#getOntologies(results, false)];
                this.requestUpdate();
            } catch (e) {
                NotificationUtils.dispatch(this, NotificationUtils.NOTIFY_RESPONSE, e);
            }
        }
    }

    async toggleNode(node) {
        node.state.expanded = !node.state.expanded;
        this.rootTree = {...this.rootTree};
        // this.requestUpdate(); Not necessary
        if (UtilsNew.isEmpty(node.nodes)) {
            node.state.loading = true;
            this.rootTree = {...this.rootTree};
            try {
                const fetchOntologyChildren = await fetch(node.children);
                const ontologyChildren = await fetchOntologyChildren.json();
                const results = ontologyChildren.responses[0].results;
                node.nodes = [...this.#getOntologies(results, node)];
                node.state.loading = false;
                this.rootTree = {...this.rootTree};
                this.requestUpdate();
            } catch (e) {
                NotificationUtils.dispatch(this, NotificationUtils.NOTIFY_RESPONSE, e);
            }
        }
    }

    selectItem(node) {
        this.selectedItem = node;
        this.requestUpdate();
    }

    // * This config is not necessary because this component uses ontology-autocomplete-filter
    // * to provide the data depends on the source GO or HP.
    //  ! Deprecated
    getDefaultConfig() {
        return {
            limit: 10,
            fields: item => {
                return {
                    name: item.text,
                    id: item.id,
                    IRI: item.iri,
                };
            },
            select2Config: {
                ajax: {
                    transport: async (params, success, failure) => {
                        const _params = params;
                        _params.data.page = params.data.page || 1;
                        const q = _params?.data?.term ? _params.data.term : "";
                        try {
                            const request = await fetch(this.ontologyConfig.root + this.ontologyConfig.search + "?id=^"+
                                this.ontologyFilter + ":" + q + "&limit=" + this._config.limit + "&count=false");
                            const json = await request.json();
                            const results = json.responses[0].results;
                            success(results);
                        } catch (e) {
                            NotificationUtils.dispatch(this, NotificationUtils.NOTIFY_RESPONSE, e);
                            failure(e);
                        }
                    },
                    processResults: (response, params) => {
                        const _params = params;
                        _params.page = _params.page || 1;
                        return {
                            results: response,
                        };
                    }
                },
            },
        };
    }

    drawNode(node) {

        const isLoading = flag => flag ? html`<i class="fa fa-spinner fa-spin" aria-hidden="true"></i>`:"";
        const isExpanded = flag => flag ? html`<i class="fas fa-plus"></i>` : html`<i class="fas fa-minus"></i>`;
        const isCollapsed = flag => flag ? "in" : "";
        const isChildrenExpanded = flag => flag ?html`${node.nodes.map(n => this.drawNode(n))}` : "";
        const childrenSize = node.has_children ?
            html`<span class="label label-primary"> Children: ${node.children_count}</span>`:"";

        return html`

 this.selectItem(node)}" data-obo-id="${node.obo_id}">
                    ${node.has_children ? html`

                             this.toggleNode(node)}" class="" role="button" data-toggle="collapse" aria-expanded="true">
                                ${isExpanded(!node.state.expanded)}

                            ${node.name} ${childrenSize}
                            ${isLoading(node.state.loading)}
                        `:
                        html` <span class="leaf" style="margin-left: ${node.depth}em;">${node.name}</span>`}

                ${node.has_children ? html`

                        ${isChildrenExpanded(node.state.expanded)}

                ` : ""}

`;
    }

    render() {

        return html`

                            <button type="button" class="close" data-dismiss="modal" aria-label="Close">
                                ×
                            </button>

${this.term} terms selector

                                        <ontology-autocomplete-filter
                                            source="${this._config.ontologyFilter}"
                                            .value="${this.selectedTerms}"
                                            @filterChange="${this.updateTerms}">
                                        </ontology-autocomplete-filter>

                                        ${this.rootTree?.nodes.map(node => this.drawNode(node))}

                                        ${this.selectedItem ? html`

Name: ${this.selectedItem.name}

Id: ${this.selectedItem.obo_id}

IRI:

                                                        ${BioinfoUtils.getOboLink(this.selectedItem.obo_id)}

Synonyms: ${this.selectedItem.synonyms?.join(", ")}

Description: ${this.selectedItem.description}

Comment: ${this.selectedItem?.comment}

                                            <button type="button" class="btn btn-default btn-small" @click="${() => this.addTerm(this.selectedItem.obo_id)}">Add Term</button>
                                        ` : ""}

                            <button type="button" class="btn btn-primary ripple" data-dismiss="modal">OK</button>

        `;
    }

}

customElements.define("variant-modal-ontology", VariantModalOntology);
<commit_msg>wc: support show more terms children #TASK-517<commit_after>/**
 * Copyright 2015-2019 OpenCB
 *
 * 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.
 */

import {LitElement, html} from "lit";
import {classMap} from "lit/directives/class-map.js";
import UtilsNew from "./../../core/utilsNew.js";
import LitUtils from "./utils/lit-utils.js";
import NotificationUtils from "./utils/notification-utils.js";
import BioinfoUtils from "../../core/bioinfo/bioinfo-utils.js";
import "./forms/select-token-filter";
import "../commons/filters/ontology-autocomplete-filter.js";

export default class VariantModalOntology extends LitElement {

    constructor() {
        super();
        this.#init();
    }

    createRenderRoot() {
        return this;
    }

    static get properties() {
        return {
            config: {
                type: Object
            },
            selectedTerms: {
                type: String
            },
        };
    }

    #init() {
        this._prefix = UtilsNew.randomString(8);
        this.ontologyConfig = {
            root: "https://ws.zettagenomics.com/cellbase/webservices/rest/v5/hsapiens/feature/ontology",
            tree: {
                "HP": [
                    "HP:0012823",
                    "HP:0040279",
                    "HP:0000005",
                    "HP:0040006",
                    "HP:0000118",
                ],
                "GO": [
                    "GO:0008150",
                    "GO:0005575",
                    "GO:0003674"
                ],
            },
            search: "/search",
        };
        this.rootTree = {text: "All", nodes: [], selectable: false};
        this.selectedTerms = null;
    }

    update(changedProperties) {
        if (changedProperties.has("selectedTerms")) {
            // selectedTerm observer to handle subsequent reopening of the modal after a first selection
        }

        if (changedProperties.has("config")) {
            this._config = {...this.getDefaultConfig(), ...this.config};
            this.ontologyFilterObserver();
        }
        super.update(changedProperties);
    }

    ontologyFilterObserver() {
        this.loadTermsTree();
    }

    updateTerms(e) {
        this.selectedTerms = e.detail.value;
        this.onFilterChange();
    }

    addTerm(ontologyId) {
        if (ontologyId) {
            const elms = this.selectedTerms ? this.selectedTerms.split(",") : [];
            const arr = [...new Set([...elms, ontologyId])];
            this.selectedTerms = arr.join(",");
        }
        this.onFilterChange();
    }

    onFilterChange() {
        LitUtils.dispatchCustomEvent(this, "filterChange", this.selectedTerms);
    }

    #getGoTerm(goTerm, isParent = false, skip = 0) {
        if (!isParent) {
            return this.ontologyConfig.root + this.ontologyConfig.search + "?id=" + goTerm + "&sort=name&order=ASCENDING&skip=0&limit=10";
        }

        if (isParent) {
            return this.ontologyConfig.root + this.ontologyConfig.search + "?parents=" + goTerm + "&sort=name&order=ASCENDING&limit=10&skip=" + skip;
        }

    }

    #getOntologies(results, child) {
        return results.map((result, index) => {
            const hasChildren = UtilsNew.isNotEmptyArray(result?.children);
            return {
                name: result.name,
                description: result.description,
                comment: result.comment,
                synonyms: result?.synonyms,
                has_children: hasChildren,
                children_count: hasChildren ? result.children.length : 0,
                children: hasChildren ? this.#getGoTerm(result.children): "",
                nodes: hasChildren ? [] : null,
                obo_id: result.id,
                depth: !child? 0: child?.depth + 1,
                path: !child ? index : "000",
                selectable: true,
                state: {expanded: false},
            };
        });
    }

    async loadTermsTree() {
        const defaultsNodes = this.ontologyConfig.tree[this._config.ontologyFilter];
        if (UtilsNew.isNotEmptyArray(defaultsNodes)) {
            try {
                const fetchOntology = await fetch(this.#getGoTerm(defaultsNodes.join(",")));
                const ontologyTerms = await fetchOntology.json();
                const results = ontologyTerms.responses[0].results;
                this.rootTree.nodes = [...this.#getOntologies(results, false)];
                this.requestUpdate();
            } catch (e) {
                NotificationUtils.dispatch(this, NotificationUtils.NOTIFY_RESPONSE, e);
            }
        }
    }

    async toggleNode(node) {
        node.state.expanded = !node.state.expanded;
        this.rootTree = {...this.rootTree};
        // this.requestUpdate(); Not necessary
        if (UtilsNew.isEmpty(node.nodes)) {
            node.state.loading = true;
            this.rootTree = {...this.rootTree};
            try {
                const fetchOntologyChildren = await fetch(node.children);
                const ontologyChildren = await fetchOntologyChildren.json();
                const results = ontologyChildren.responses[0].results;
                node.nodes = [...this.#getOntologies(results, node)];
                node.state.loading = false;
                this.rootTree = {...this.rootTree};
                this.requestUpdate();
            } catch (e) {
                NotificationUtils.dispatch(this, NotificationUtils.NOTIFY_RESPONSE, e);
            }
        }
    }

    async showMoreItems(node, lastIndex) {
        // console.log("Node show more", node);
        // Parent Id, Skip;
        try {
            const fetchOntologyChildren = await fetch(this.#getGoTerm(node.obo_id, true, lastIndex));
            const ontologyChildren = await fetchOntologyChildren.json();
            const results = ontologyChildren.responses[0].results;
            node.nodes = [...node.nodes, ...this.#getOntologies(results, node)];
            this.requestUpdate();
        } catch (e) {
            NotificationUtils.dispatch(this, NotificationUtils.NOTIFY_RESPONSE, e);
        }
    }

    selectItem(node) {
        this.selectedItem = node;
        this.requestUpdate();
    }

    // * This config is not necessary because this component uses ontology-autocomplete-filter
    // * to provide the data depends on the source GO or HP.
    //  ! Deprecated
    getDefaultConfig() {
        return {
            limit: 10,
            fields: item => {
                return {
                    name: item.text,
                    id: item.id,
                    IRI: item.iri,
                };
            },
            select2Config: {
                ajax: {
                    transport: async (params, success, failure) => {
                        const _params = params;
                        _params.data.page = params.data.page || 1;
                        const q = _params?.data?.term ? _params.data.term : "";
                        try {
                            const request = await fetch(this.ontologyConfig.root + this.ontologyConfig.search + "?id=^"+
                                this.ontologyFilter + ":" + q + "&limit=" + this._config.limit + "&count=false");
                            const json = await request.json();
                            const results = json.responses[0].results;
                            success(results);
                        } catch (e) {
                            NotificationUtils.dispatch(this, NotificationUtils.NOTIFY_RESPONSE, e);
                            failure(e);
                        }
                    },
                    processResults: (response, params) => {
                        const _params = params;
                        _params.page = _params.page || 1;
                        return {
                            results: response,
                        };
                    }
                },
            },
        };
    }

    renderShowMore(showMore, index, lastIndex, node, parent) {
        if (showMore && ((index+1) >= lastIndex) && ((index+1) <= parent.children_count)) {
            return html`

                     this.showMoreItems(parent, lastIndex)}">
                        Show More...

`;
        }
    }

    drawNode(node, index, parent, showMore) {
        const isLoading = flag => flag ? html`<i class="fa fa-spinner fa-spin" aria-hidden="true"></i>`:"";
        const isExpanded = flag => flag ? html`<i class="fas fa-plus"></i>` : html`<i class="fas fa-minus"></i>`;
        const isCollapsed = flag => flag ? "in" : "";

        const isChildrenExpanded = flag => flag ?
            html`${node.nodes.map((nodeChild, i) => this.drawNode(nodeChild, i, node, node?.children_count > node?.nodes?.length))}` :
            "";

        const childrenSize = node.has_children ?
            html`<span class="label label-primary"> Children: ${node.children_count}</span>`:"";

        const lastIndex = parent?.nodes?.length;
        return html`

 this.selectItem(node)}" data-obo-id="${node.obo_id}">
                    ${node.has_children ? html`

                             this.toggleNode(node)}" class="" role="button" data-toggle="collapse" aria-expanded="true">
                                ${isExpanded(!node.state.expanded)}

                            ${node.name} ${childrenSize}
                            ${isLoading(node.state.loading)}
                        `:
                        html`<span class="leaf" style="margin-left: ${node.depth}em;">${node.name}</span>`}

                ${node.has_children ? html`

                        ${isChildrenExpanded(node.state.expanded)}

                ` : ""}
                ${this.renderShowMore(showMore, index, lastIndex, node, parent)}

`;
    }

    render() {

        return html`

                            <button type="button" class="close" data-dismiss="modal" aria-label="Close">
                                ×
                            </button>

${this.term} terms selector

                                        <ontology-autocomplete-filter
                                            source="${this._config.ontologyFilter}"
                                            .value="${this.selectedTerms}"
                                            @filterChange="${this.updateTerms}">
                                        </ontology-autocomplete-filter>

                                        ${this.rootTree?.nodes.map(node => this.drawNode(node, false))}

                                        ${this.selectedItem ? html`

Name: ${this.selectedItem.name}

Id: ${this.selectedItem.obo_id}

IRI:

                                                        ${BioinfoUtils.getOboLink(this.selectedItem.obo_id)}

Synonyms: ${this.selectedItem.synonyms?.join(", ")}

Description: ${this.selectedItem.description}

Comment: ${this.selectedItem?.comment}

                                            <button type="button" class="btn btn-default btn-small" @click="${() => this.addTerm(this.selectedItem.obo_id)}">Add Term</button>
                                        ` : ""}

                            <button type="button" class="btn btn-primary ripple" data-dismiss="modal">OK</button>

        `;
    }

}

customElements.define("variant-modal-ontology", VariantModalOntology);
<|endoftext|>