<commit_before>/**
 * Uses Mongoose Models for db operations.
 */
import Template from '../models/shipmentTemplate';
import sanitizeHtml from 'sanitize-html';

export default {
    getTemplates: function (req, res) {
        Template.find()
            .sort({
                company: 1,
                unit: -1,
            })
            .exec((err, docs) => {
                if (err) {
                    return res.status(500).send(err);
                }

                docs.sort((a, b) => {
                    // Ensure sorted after trimming whitespace
                    if (a.company.trim() < b.company.trim()) return -1;
                    if (a.company.trim() > b.company.trim()) return 1;
                    if (a.unit.trim() < b.unit.trim()) return 1;
                    if (a.unit.trim() > b.unit.trim()) return -1;
                    return +(a.dept.match(/\d+/)[0]) - +(b.dept.match(/\d+/)[0]);
                });

                res.json(docs);
            });
    },

    /**
     * Returns a list of companies and their respective depts sorted.
     */
    getDepartments: function (req, res) {
        Template.aggregate([
                { $group: {
                    _id: {
                        company: "$company",
                        dept: "$dept"
                    }
                } },
                { $group: {
                    _id: {
                        company: "$_id.company",
                    },
                    departments: {$push: "$_id.dept"}
                } },
                { $project: {
                    _id: 0,
                    company: "$_id.company",
                    departments: 1
                } },
                { $sort: {
                    company: 1,
                } },
            ])
            .exec((err, recs) => {
                if (err) {
                    return res.status(500).send(err);
                }

                for (let co of recs) {
                    co.departments.sort(
                        (a, b) => +a.match(/\d+/)[0] - +b.match(/\d+/)[0]
                    );
                }

                res.json(recs);
            });
    },

    addTemplate: function (req, res) {
        const newTemplate = {
            company: sanitizeHtml(req.body.company),
            dept: sanitizeHtml(req.body.dept),
            unit: sanitizeHtml(req.body.unit),
            product: sanitizeHtml(req.body.product),
            pn: sanitizeHtml(req.body.pn),
        };

        // Ensure all keys have values
        for (let key in newTemplate) {
            if (!newTemplate[key]) {
                return res.status(403).end();
            }
        }

        // Each option should be unique
        new Template(newTemplate).save((err, savedRec) => {
            if (err) {
                return res.status(500).send(err);
            }

            return res.json(savedRec);
        });
    },

    deleteTemplate: function (req, res) {
        const _id = req.body._id;
        Template.findById(_id).exec((err, template) => {
            if (err) {
                return res.status(500).send(err);
            }

            template.remove(() => {
                res.status(200).end();
            });
        });
    },

    /**
     * Returns an array of unique product-pn pairs.
     */
    getProducts: function (req, res) {
        Template.aggregate([
                { $group: {
                    _id: {
                        product: "$product",
                        pn: "$pn"
                    }
                } },
                { $project: {
                    _id: 0,
                    product: "$_id.product",
                    pn: "$_id.pn",
                } },
                { $sort: {
                    pn: 1
                } },
            ])
            .exec((err, recs) => {
                if (err) {
                    return res.status(500).send(err);
                }

                res.json(recs);
            });
    }
}
<commit_msg>Improved template sorting<commit_after>/**
 * Uses Mongoose Models for db operations.
 */
import Template from '../models/shipmentTemplate';
import sanitizeHtml from 'sanitize-html';

export default {
    getTemplates: function (req, res) {
        Template.find()
            .sort({
                company: 1,
                unit: -1,
            })
            .exec((err, docs) => {
                if (err) {
                    return res.status(500).send(err);
                }

                docs.sort((a, b) => {
                    // Ensure sorted after trimming whitespace
                    if (a.company.trim() < b.company.trim()) return -1;
                    if (a.company.trim() > b.company.trim()) return 1;
                    if (a.unit.trim() < b.unit.trim()) return 1;
                    if (a.unit.trim() > b.unit.trim()) return -1;
                    const aNo = Number(a.dept.match(/\d+/)[0]),
                          bNo = Number(b.dept.match(/\d+/)[0]);
                    if (aNo !== bNo) return aNo - bNo;
                    if (a.dept.trim() < b.dept.trim()) return -1;
                    if (a.dept.trim() > b.dept.trim()) return 1;
                    return 0;
                });

                res.json(docs);
            });
    },

    /**
     * Returns a list of companies and their respective depts sorted.
     */
    getDepartments: function (req, res) {
        Template.aggregate([
                { $group: {
                    _id: {
                        company: "$company",
                        dept: "$dept"
                    }
                } },
                { $group: {
                    _id: {
                        company: "$_id.company",
                    },
                    departments: {$push: "$_id.dept"}
                } },
                { $project: {
                    _id: 0,
                    company: "$_id.company",
                    departments: 1
                } },
                { $sort: {
                    company: 1,
                } },
            ])
            .exec((err, recs) => {
                if (err) {
                    return res.status(500).send(err);
                }

                for (let co of recs) {
                    co.departments.sort(
                        (a, b) => +a.match(/\d+/)[0] - +b.match(/\d+/)[0]
                    );
                }

                res.json(recs);
            });
    },

    addTemplate: function (req, res) {
        const newTemplate = {
            company: sanitizeHtml(req.body.company),
            dept: sanitizeHtml(req.body.dept),
            unit: sanitizeHtml(req.body.unit),
            product: sanitizeHtml(req.body.product),
            pn: sanitizeHtml(req.body.pn),
        };

        // Ensure all keys have values
        for (let key in newTemplate) {
            if (!newTemplate[key]) {
                return res.status(403).end();
            }
        }

        // Each option should be unique
        new Template(newTemplate).save((err, savedRec) => {
            if (err) {
                return res.status(500).send(err);
            }

            return res.json(savedRec);
        });
    },

    deleteTemplate: function (req, res) {
        const _id = req.body._id;
        Template.findById(_id).exec((err, template) => {
            if (err) {
                return res.status(500).send(err);
            }

            template.remove(() => {
                res.status(200).end();
            });
        });
    },

    /**
     * Returns an array of unique product-pn pairs.
     */
    getProducts: function (req, res) {
        Template.aggregate([
                { $group: {
                    _id: {
                        product: "$product",
                        pn: "$pn"
                    }
                } },
                { $project: {
                    _id: 0,
                    product: "$_id.product",
                    pn: "$_id.pn",
                } },
                { $sort: {
                    pn: 1
                } },
            ])
            .exec((err, recs) => {
                if (err) {
                    return res.status(500).send(err);
                }

                res.json(recs);
            });
    }
}
<|endoftext|>


<commit_before>// Returns group fragment which contains all data fragments for objects found in provided
// collection

'use strict';

var includes            = require('es5-ext/array/#/contains')
  , ensureString        = require('es5-ext/object/validate-stringifiable-value')
  , ensureIterable      = require('es5-ext/iterable/validate-object')
  , ensureObservable    = require('observable-value/valid-observable')
  , Set                 = require('es6-set')
  , DataFragmentGroup   = require('data-fragment/group')
  , defaultGetFragments = require('./get-direct-object-fragments')

  , isArray = Array.isArray;

module.exports = function (collection/*, getFragment*/) {
	var getFragment = arguments[1], fragment, current, isArrayMode = isArray(collection);

	ensureObservable(ensureIterable(collection));
	if (typeof getFragment !== 'function') {
		getFragment = defaultGetFragments(ensureString(getFragment));
	}

	fragment = new DataFragmentGroup();
	current = new Set();
	if (collection.promise) fragment.promise = collection.promise;

	collection.forEach(function (id) {
		fragment.addFragment(getFragment(id));
		current.add(id);
	});
	collection.on('change', function (event) {
		current.forEach(function (id) {
			if (isArrayMode) {
				if (includes.call(collection, id)) return;
			} else {
				if (collection.has(id)) return;
			}
			fragment.deleteFragment(getFragment(id));
			current.delete(id);
		});
		collection.forEach(function (id) {
			if (current.has(id)) return;
			fragment.addFragment(getFragment(id));
			current.add(id);
		});
	});
	return fragment;
};
<commit_msg>Ensure collection fragment comes flushed<commit_after>// Returns group fragment which contains all data fragments for objects found in provided
// collection

'use strict';

var includes            = require('es5-ext/array/#/contains')
  , ensureString        = require('es5-ext/object/validate-stringifiable-value')
  , ensureIterable      = require('es5-ext/iterable/validate-object')
  , ensureObservable    = require('observable-value/valid-observable')
  , Set                 = require('es6-set')
  , DataFragmentGroup   = require('data-fragment/group')
  , defaultGetFragments = require('./get-direct-object-fragments')

  , isArray = Array.isArray;

module.exports = function (collection/*, getFragment*/) {
	var getFragment = arguments[1], fragment, current, isArrayMode = isArray(collection);

	ensureObservable(ensureIterable(collection));
	if (typeof getFragment !== 'function') {
		getFragment = defaultGetFragments(ensureString(getFragment));
	}

	fragment = new DataFragmentGroup();
	current = new Set();
	if (collection.promise) fragment.promise = collection.promise;

	collection.forEach(function (id) {
		fragment.addFragment(getFragment(id));
		current.add(id);
	});
	collection.on('change', function (event) {
		current.forEach(function (id) {
			if (isArrayMode) {
				if (includes.call(collection, id)) return;
			} else {
				if (collection.has(id)) return;
			}
			fragment.deleteFragment(getFragment(id));
			current.delete(id);
		});
		collection.forEach(function (id) {
			if (current.has(id)) return;
			fragment.addFragment(getFragment(id));
			current.add(id);
		});
	});
	if (!fragment.promise || fragment.promise.resolved) fragment.flush();
	return fragment;
};
<|endoftext|>


<commit_before>import { Meteor } from 'meteor/meteor';
import { _ } from 'meteor/underscore';

import { dbUserOwnedProducts } from '/db/dbUserOwnedProducts';
import { dbLog } from '/db/dbLog';
import { getCurrentSeason } from '/db/dbSeason';

// 發放產品滿額回饋金給使用者
export function deliverProductRebates() {
  const { divisorAmount, deliverAmount } = Meteor.settings.public.productRebates;

  const { _id: seasonId } = getCurrentSeason();

  const rebateList = dbUserOwnedProducts
    .aggregate([ {
      $match: { seasonId }
    }, {
      $lookup: {
        from: 'companies',
        localField: 'companyId',
        foreignField: '_id',
        as: 'companyData'
      }
    }, {
      $unwind: '$companyData'
    }, {
      $match: { 'companyData.isSeal': false }
    }, {
      $group: {
        _id: { userId: '$userId', companyId: '$companyId' },
        totalCost: { $sum: { $multiply: ['$amount', '$price'] } }
      }
    }, {
      $project: {
        rebate: { $multiply: [deliverAmount, { $floor: { $divide: ['$totalCost', divisorAmount] } } ] }
      }
    }, {
      $match: { rebate: { $gt: 0 } }
    } ]);

  if (_.isEmpty(rebateList)) {
    return;
  }

  const userBulk = Meteor.users.rawCollection().initializeUnorderedBulkOp();
  const logBulk = dbLog.rawCollection().initializeUnorderedBulkOp();
  const logSchema = dbLog.simpleSchema();

  const nowDate = new Date();

  rebateList.forEach(({ _id: { userId, companyId }, rebate }) => {
    userBulk.find({ _id: userId }).updateOne({ $inc: { 'profile.money': rebate } });
    const logData = logSchema.clean({
      logType: '消費回饋',
      userId: [userId],
      companyId,
      data: { rebate },
      createdAt: nowDate
    });
    logSchema.validate(logData);
    logBulk.insert(logData);
  });

  Meteor.wrapAsync(userBulk.execute, userBulk)();
  Meteor.wrapAsync(logBulk.execute, logBulk)();
}
<commit_msg>重構 deliverProductRebates()<commit_after>import { Meteor } from 'meteor/meteor';
import { _ } from 'meteor/underscore';

import { dbUserOwnedProducts } from '/db/dbUserOwnedProducts';
import { dbLog } from '/db/dbLog';
import { getCurrentSeason } from '/db/dbSeason';

// 發放產品滿額回饋金給使用者
export function deliverProductRebates() {
  const rebateList = getRebateList();
  if (_.isEmpty(rebateList)) {
    return;
  }

  const userBulk = Meteor.users.rawCollection().initializeUnorderedBulkOp();
  const logBulk = dbLog.rawCollection().initializeUnorderedBulkOp();
  const logSchema = dbLog.simpleSchema();

  const nowDate = new Date();

  rebateList.forEach(({ _id: { userId, companyId }, rebate }) => {
    userBulk.find({ _id: userId }).updateOne({ $inc: { 'profile.money': rebate } });
    const logData = logSchema.clean({
      logType: '消費回饋',
      userId: [userId],
      companyId,
      data: { rebate },
      createdAt: nowDate
    });
    logSchema.validate(logData);
    logBulk.insert(logData);
  });

  Meteor.wrapAsync(userBulk.execute, userBulk)();
  Meteor.wrapAsync(logBulk.execute, logBulk)();
}

function getRebateList() {
  const costList = getCostList();
  const rebateList = [];
  costList.forEach(({ _id, totalCost }) => {
    const rebate = computeRebate(totalCost);
    if (rebate > 0) {
      rebateList.push({ _id, rebate });
    }
  });

  return rebateList;
}

function getCostList() {
  const { _id: seasonId } = getCurrentSeason();
  const costList = dbUserOwnedProducts
    .aggregate([ {
      $match: { seasonId }
    }, {
      $lookup: {
        from: 'companies',
        localField: 'companyId',
        foreignField: '_id',
        as: 'companyData'
      }
    }, {
      $unwind: '$companyData'
    }, {
      $match: { 'companyData.isSeal': false }
    }, {
      $group: {
        _id: { userId: '$userId', companyId: '$companyId' },
        totalCost: { $sum: { $multiply: ['$amount', '$price'] } }
      }
    } ]);

  return costList;
}

function computeRebate(totalCost) {
  const { divisorAmount, deliverAmount } = Meteor.settings.public.productRebates;

  return deliverAmount * Math.floor(totalCost / divisorAmount);
}
<|endoftext|>


<commit_before>/**
 * (c) 2015 Edouard Lafargue, ed@lafargue.name
 *
 * This file is part of Wizkers.
 *
 * Wizkers is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Wizkers is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Wizkers.  If not, see <http://www.gnu.org/licenses/>.
 */

/*
 * Log view for the Onyx.
 *
 * Our model is a collection of Logs
 *
 * @author Edouard Lafargue, ed@lafargue.name
 */

define(function (require) {
    "use strict";

    var $ = require('jquery'),
        _ = require('underscore'),
        Backbone = require('backbone'),
        simpleplot = require('app/lib/flotplot'),
        template = require('js/tpl/instruments/OnyxLogView.js');

    // Load the flot library & flot time plugin:
    require('flot');
    require('flot_time');
    require('flot_resize');
    require('flot_selection');

    return Backbone.View.extend({

        initialize: function () {
            var self = this;

            this.deviceLogs = this.collection;
            this.packedData = null;
            this.previousPoint = null;

            // Need this to make sure "render" will always be bound to our context.
            // -> required for the _.after below.
            _.bindAll(this, "render");

            // Now fetch all the contents, then render
            var renderGraph = _.after(this.deviceLogs.length, this.render);
            this.deviceLogs.each(function (log) {
                log.entries.fetch({
                    success: renderGraph,
                    xhr: function () {
                        var xhr = $.ajaxSettings.xhr();
                        xhr.onprogress = self.handleProgress;
                        return xhr;
                    }
                });
            });

            // We will pass this when we create plots, this is the global
            // config for the look and feel of the plot
            this.plotSettings = {
                selectable: true,
                vertical_stretch: true,
                points: 0,
                plot_options: {
                    xaxis: {
                        mode: "time",
                        show: true,
                        timeformat: "%Y.%m.%d
%H:%M",
                        timezone: settings.get("timezone"),
                    },
                    yaxis: {},
                    grid: {
                        hoverable: true,
                        clickable: true
                    },
                    legend: {
                        position: "ne"
                    },
                    colors: this.palette,
                }
            };

            this.plotOverviewSettings = {
                selectable: true,
                vertical_stretch: false,
                points: 0,
                plot_options: {
                    xaxis: {
                        mode: "time",
                        show: true,
                        timeformat: "%m.%d",
                        timezone: settings.get("timezone"),
                    },
                    yaxis: {},
                    colors: this.palette,
                }
            };

            // TODO: save color palette in settings ?
            // My own nice color palette:
            this.palette = ["#e27c48", "#5a3037", "#f1ca4f", "#acbe80", "#77b1a7", "#858485", "#d9c7ad"];
        },

        handleProgress: function (e) {
            $('#loadtext').html("Loaded: " + e.loaded + " bytes");
        },

        events: {
            "click .resetZoom": "resetZoom",
            "click #cpmscale": "cpmScaleToggle",
            "click .ctrl-edit": "editLog",
            "click #download-csv": "downloadCSV"
        },

        resetZoom: function () {
            delete this.ranges;
            this.plot.plotOptions = this.plotSettings.plot_options;
            this.plot.addPlot();
            this.plot.redraw();
            return false;
        },

        cpmScaleToggle: function (event) {
            return;
            var change = {};
            if (event.target.checked) {
                change["cpmscale"] = "log";
            } else {
                change["cpmscale"] = "linear";
            }
            settings.set(change);
            this.render();
            this.addPlot();

        },

        editLog: function () {
            console.log('Edit Log');
            var logIds = [];
            _.each(this.deviceLogs.models, function (log) {
                logIds.push(log.id);
            });
            router.navigate('editlogs/' + instrumentManager.getInstrument().id + '/' + logIds.join(','), true);
        },

        render: function () {
            var self = this;
            console.log('Main render of Log details view');

            $(this.el).html(template());

            this.addPlot();
            if (this.packedData == null || this.packedData.length == 0)
                this.packedData = this.packData();

            if (this.packedData.length == 0)
                return;
            return this;
        },

        onClose: function () {
            console.log("Log management view closing...");

            // Remove the window resize bindings on our plots:
            this.plot.onClose();
            this.overview.onClose();
            // Restore the settings since we don't want them to be saved when changed from
            // the home screen
            settings.fetch();
        },

        // Generate a "blob:"  URL to download (all) the data;
        downloadJSON: function () {
            var json = "";
            for (var i = 0; i < this.deviceLogs.length; i++) {
                json += "{timestamp:" + this.devicesLogs.at(i).get('timestamp') +
                    ",cpm:" + this.onyxlog.at(i).get('cpm') + "},";
            }

            var jsonBlob = new Blob([json], {
                type: 'application/json'
            });
            var url = window.URL.createObjectURL(jsonBlob);
            $('.ctrl-save', this.el).attr('href', url);
        },

        downloadCSV: function () {
            var header = 'data:text/csv; charset=utf-8,';
            var csv = header + "Timestamp (UTC), ";
            for (var i = 0; i < this.deviceLogs.length; i++) {
                var currentLog = this.deviceLogs.at(i);
                var entries = currentLog.entries;
                var type = currentLog.get('logtype');
                if (type == 'live') {
                    csv += "CPM, CPM30, USV, COUNT, Valid\n";
                } else if (entries.at(0) && entries.at(0).get('data').min != undefined) {
                    // We have new generation log type with min/max values
                    csv += "cpm, cpm_min, cpm_max, counts, duration (s), is_cpm_30, time on device (ISO String)\n";
                    type = "min_max";
                } else {
                    csv += "accel_x_start, accel_x_end, accel_y_start, accel_y_end, accel_z_start, accel_z_end, cpm, duration (s), time on device (ISO String)\n";
                }
                for (var j = 0; j < entries.length; j++) {
                    var entry = entries.at(j);
                    var data = entry.get('data');
                    // Sometimes, we get entries without a valid CPM reading, detect this
                    if (data.cpm) {
                        // No known spreadsheet software handles ISO8601 dates
                        // properly (like 2014-06-17T18:00:04.067Z ) so we
                        // convert the timestamp to a string that is recognized by
                        // Excel or Google Docs. The whole new Date + toISOString
                        // is here to guarantee that we do get a proper formatted
                        // time stamp whenever we are running as an embedded app or a server
                        // app.
                        var ts = new Date(entry.get('timestamp')).toISOString().replace(/[TZ]/g, ' ');
                        csv += ts;
                        // Our live data is packed a bit differently than onyxlog data
                        // Note: I tried a generic "for key in data" but the order
                        // the keys are returned can change randomly, leading to wrong outout, which is
                        // why I am using explicit key names:
                        if (type == 'live') {
                            csv += ',' + data.cpm.value +
                                ',' + data.cpm.cpm30 +
                                ',' + data.cpm.usv +
                                ',' + data.cpm.count +
                                ',' + data.cpm.valid;
                        } else if (type == 'min_max') {
                            csv += ',' + data.cpm +
                                ',' + data.min +
                                ',' + data.max +
                                ',' + data.counts +
                                ',' + data.duration +
                                ',' + (data.type == 1) +
                                ',' + data.time;
                        } else {
                            csv += ',' + data.accel_x_start +
                                ',' + data.accel_x_end +
                                ',' + data.accel_y_start +
                                ',' + data.accel_y_end +
                                ',' + data.accel_z_start +
                                ',' + data.accel_z_end +
                                ',' + data.cpm +
                                ',' + data.duration +
                                ',' + data.time;
                        }
                        csv += '\n';
                    }
                }
            }
            var uri = encodeURI(csv);
            window.open(uri);
        },

        addPlot: function () {
            var self = this;

            if (this.deviceLogs == null ||  this.deviceLogs.length == 0)
                return;

            $('#log_size', this.el).html(this.deviceLogs.getOverallLength());
            $('#log_start', this.el).html(new Date(this.deviceLogs.getLogsStart()).toString());
            $('#log_end', this.el).html(new Date(this.deviceLogs.getLogsEnd()).toString());

            this.plot = new simpleplot({
                model: this.model,
                settings: this.plotSettings
            });
            if (this.plot != null) {
                $('.geigerchart', this.el).empty().append(this.plot.el);
                this.plot.render();
            }

            this.overview = new simpleplot({
                model: this.model,
                settings: this.plotOverviewSettings
            });
            if (this.overview != null) {
                $('#overview', this.el).empty().append(this.overview.el);
                this.overview.render();
            }

            // Restore current zoom level if it exists:
            if (this.ranges) {
                this.plot.plotOptions = $.extend(true, {}, this.plotSettings.plot_options, {
                    xaxis: {
                        min: this.ranges.xaxis.from,
                        max: this.ranges.xaxis.to
                    },
                    yaxis: {
                        min: this.ranges.yaxis.from,
                        max: this.ranges.yaxis.to
                    }
                });
            }

        },

        // Depending on log type, we need to pack our data differently...
        packData: function () {
            var self = this;
            // Create a table of Y values with the x values from our collection
            var data = [];
            var logs = this.deviceLogs;
            // At this stage we know the logs are already fetched, btw
            for (var j = 0; j < logs.length; j++) {
                var ret = [];
                var value = logs.at(j).entries;
                var type = logs.at(j).get('logtype');
                for (var i = 0; i < value.length; i++) {
                    var entry = value.at(i);
                    // Be sure we only plot CPM entries (we might have anything in the
                    // log...
                    var stamp = new Date(entry.get('timestamp')).getTime();
                    if (entry.get('data').cpm != undefined) {
                        var cpm = parseFloat((type == 'onyxlog') ? entry.get('data').cpm : entry.get('data').cpm.value);
                        this.plot.fastAppendPoint({
                            name: 'CPM',
                            value: cpm,
                            timestamp: stamp
                        });
                        this.overview.fastAppendPoint({
                            name: 'CPM',
                            value: cpm,
                            timestamp: stamp
                        });
                    }
                    if (entry.get('data').cpm2 != undefined) {
                        var cpm2 = parseFloat(entry.get('data').cpm2.value);
                        this.plot.fastAppendPoint({
                            'name': "CPM2",
                            'value': cpm2,
                            timestamp: stamp
                        });
                        this.overview.fastAppendPoint({
                            'name': "CPM2",
                            'value': cpm2,
                            timestamp: stamp
                        });
                    }
                }
                if (ret.length)
                    data.push({
                        data: ret,
                        label: "CPM"
                    });
            }
            this.plot.redraw();
            this.overview.redraw();
            return data;
        },

        // Ugly at this stage, just to make it work (from flotcharts.org examples)
        showTooltip: function (x, y, contents) {
            $("
" + contents + "
").css({
                position: "absolute",
                display: "none",
                top: y + 5,
                left: x + 5,
                padding: "3px",
                opacity: 0.90
            }).appendTo("body").fadeIn(200);
        },

    });
});<commit_msg>Onyx log display: display Min/Max range on log display if it exists.<commit_after>/**
 * (c) 2015 Edouard Lafargue, ed@lafargue.name
 *
 * This file is part of Wizkers.
 *
 * Wizkers is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Wizkers is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Wizkers.  If not, see <http://www.gnu.org/licenses/>.
 */

/*
 * Log view for the Onyx.
 *
 * Our model is a collection of Logs
 *
 * @author Edouard Lafargue, ed@lafargue.name
 */

define(function (require) {
    "use strict";

    var $ = require('jquery'),
        _ = require('underscore'),
        Backbone = require('backbone'),
        simpleplot = require('app/lib/flotplot'),
        template = require('js/tpl/instruments/OnyxLogView.js');

    // Load the flot library & flot time plugin:
    require('flot');
    require('flot_time');
    require('flot_resize');
    require('flot_selection');

    return Backbone.View.extend({

        initialize: function () {
            var self = this;

            this.deviceLogs = this.collection;
            this.packedData = null;
            this.previousPoint = null;

            // Need this to make sure "render" will always be bound to our context.
            // -> required for the _.after below.
            _.bindAll(this, "render");

            // Now fetch all the contents, then render
            var renderGraph = _.after(this.deviceLogs.length, this.render);
            this.deviceLogs.each(function (log) {
                log.entries.fetch({
                    success: renderGraph,
                    xhr: function () {
                        var xhr = $.ajaxSettings.xhr();
                        xhr.onprogress = self.handleProgress;
                        return xhr;
                    }
                });
            });

            // We will pass this when we create plots, this is the global
            // config for the look and feel of the plot
            this.plotSettings = {
                selectable: true,
                vertical_stretch: true,
                points: 0,
                plot_options: {
                    xaxis: {
                        mode: "time",
                        show: true,
                        timeformat: "%Y.%m.%d
%H:%M",
                        timezone: settings.get("timezone"),
                    },
                    yaxis: {},
                    grid: {
                        hoverable: true,
                        clickable: true
                    },
                    legend: {
                        position: "ne"
                    },
                    colors: this.palette,
                }
            };

            this.plotOverviewSettings = {
                selectable: true,
                vertical_stretch: false,
                points: 0,
                plot_options: {
                    xaxis: {
                        mode: "time",
                        show: true,
                        timeformat: "%m.%d",
                        timezone: settings.get("timezone"),
                    },
                    yaxis: {},
                    colors: this.palette,
                }
            };

            // TODO: save color palette in settings ?
            // My own nice color palette:
            this.palette = ["#e27c48", "#5a3037", "#f1ca4f", "#acbe80", "#77b1a7", "#858485", "#d9c7ad"];
        },

        handleProgress: function (e) {
            $('#loadtext').html("Loaded: " + e.loaded + " bytes");
        },

        events: {
            "click .resetZoom": "resetZoom",
            "click #cpmscale": "cpmScaleToggle",
            "click .ctrl-edit": "editLog",
            "click #download-csv": "downloadCSV"
        },

        resetZoom: function () {
            delete this.ranges;
            this.plot.plotOptions = this.plotSettings.plot_options;
            this.plot.addPlot();
            this.plot.redraw();
            return false;
        },

        cpmScaleToggle: function (event) {
            return;
            var change = {};
            if (event.target.checked) {
                change["cpmscale"] = "log";
            } else {
                change["cpmscale"] = "linear";
            }
            settings.set(change);
            this.render();
            this.addPlot();

        },

        editLog: function () {
            console.log('Edit Log');
            var logIds = [];
            _.each(this.deviceLogs.models, function (log) {
                logIds.push(log.id);
            });
            router.navigate('editlogs/' + instrumentManager.getInstrument().id + '/' + logIds.join(','), true);
        },

        render: function () {
            var self = this;
            console.log('Main render of Log details view');

            $(this.el).html(template());

            this.addPlot();
            if (this.packedData == null || this.packedData.length == 0)
                this.packedData = this.packData();

            if (this.packedData.length == 0)
                return;
            return this;
        },

        onClose: function () {
            console.log("Log management view closing...");

            // Remove the window resize bindings on our plots:
            this.plot.onClose();
            this.overview.onClose();
            // Restore the settings since we don't want them to be saved when changed from
            // the home screen
            settings.fetch();
        },

        // Generate a "blob:"  URL to download (all) the data;
        downloadJSON: function () {
            var json = "";
            for (var i = 0; i < this.deviceLogs.length; i++) {
                json += "{timestamp:" + this.devicesLogs.at(i).get('timestamp') +
                    ",cpm:" + this.onyxlog.at(i).get('cpm') + "},";
            }

            var jsonBlob = new Blob([json], {
                type: 'application/json'
            });
            var url = window.URL.createObjectURL(jsonBlob);
            $('.ctrl-save', this.el).attr('href', url);
        },

        downloadCSV: function () {
            var header = 'data:text/csv; charset=utf-8,';
            var csv = header + "Timestamp (UTC), ";
            for (var i = 0; i < this.deviceLogs.length; i++) {
                var currentLog = this.deviceLogs.at(i);
                var entries = currentLog.entries;
                var type = currentLog.get('logtype');
                if (type == 'live') {
                    csv += "CPM, CPM30, USV, COUNT, Valid\n";
                } else if (entries.at(0) && entries.at(0).get('data').min != undefined) {
                    // We have new generation log type with min/max values
                    csv += "cpm, cpm_min, cpm_max, counts, duration (s), is_cpm_30, time on device (ISO String)\n";
                    type = "min_max";
                } else {
                    csv += "accel_x_start, accel_x_end, accel_y_start, accel_y_end, accel_z_start, accel_z_end, cpm, duration (s), time on device (ISO String)\n";
                }
                for (var j = 0; j < entries.length; j++) {
                    var entry = entries.at(j);
                    var data = entry.get('data');
                    // Sometimes, we get entries without a valid CPM reading, detect this
                    if (data.cpm) {
                        // No known spreadsheet software handles ISO8601 dates
                        // properly (like 2014-06-17T18:00:04.067Z ) so we
                        // convert the timestamp to a string that is recognized by
                        // Excel or Google Docs. The whole new Date + toISOString
                        // is here to guarantee that we do get a proper formatted
                        // time stamp whenever we are running as an embedded app or a server
                        // app.
                        var ts = new Date(entry.get('timestamp')).toISOString().replace(/[TZ]/g, ' ');
                        csv += ts;
                        // Our live data is packed a bit differently than onyxlog data
                        // Note: I tried a generic "for key in data" but the order
                        // the keys are returned can change randomly, leading to wrong outout, which is
                        // why I am using explicit key names:
                        if (type == 'live') {
                            csv += ',' + data.cpm.value +
                                ',' + data.cpm.cpm30 +
                                ',' + data.cpm.usv +
                                ',' + data.cpm.count +
                                ',' + data.cpm.valid;
                        } else if (type == 'min_max') {
                            csv += ',' + data.cpm +
                                ',' + data.min +
                                ',' + data.max +
                                ',' + data.counts +
                                ',' + data.duration +
                                ',' + data.type +
                                ',' + data.time;
                        } else {
                            csv += ',' + data.accel_x_start +
                                ',' + data.accel_x_end +
                                ',' + data.accel_y_start +
                                ',' + data.accel_y_end +
                                ',' + data.accel_z_start +
                                ',' + data.accel_z_end +
                                ',' + data.cpm +
                                ',' + data.duration +
                                ',' + data.time;
                        }
                        csv += '\n';
                    }
                }
            }
            var uri = encodeURI(csv);
            window.open(uri);
        },

        addPlot: function () {
            var self = this;

            if (this.deviceLogs == null ||  this.deviceLogs.length == 0)
                return;

            $('#log_size', this.el).html(this.deviceLogs.getOverallLength());
            $('#log_start', this.el).html(new Date(this.deviceLogs.getLogsStart()).toString());
            $('#log_end', this.el).html(new Date(this.deviceLogs.getLogsEnd()).toString());

            this.plot = new simpleplot({
                model: this.model,
                settings: this.plotSettings
            });
            if (this.plot != null) {
                $('.geigerchart', this.el).empty().append(this.plot.el);
                this.plot.render();
            }

            this.overview = new simpleplot({
                model: this.model,
                settings: this.plotOverviewSettings
            });
            if (this.overview != null) {
                $('#overview', this.el).empty().append(this.overview.el);
                this.overview.render();
            }

            // Restore current zoom level if it exists:
            if (this.ranges) {
                this.plot.plotOptions = $.extend(true, {}, this.plotSettings.plot_options, {
                    xaxis: {
                        min: this.ranges.xaxis.from,
                        max: this.ranges.xaxis.to
                    },
                    yaxis: {
                        min: this.ranges.yaxis.from,
                        max: this.ranges.yaxis.to
                    }
                });
            }

        },

        // Depending on log type, we need to pack our data differently...
        packData: function () {
            var self = this;
            // Create a table of Y values with the x values from our collection
            var data = [];
            var logs = this.deviceLogs;
            // At this stage we know the logs are already fetched, btw
            for (var j = 0; j < logs.length; j++) {
                var ret = [];
                var value = logs.at(j).entries;
                var type = logs.at(j).get('logtype');
                for (var i = 0; i < value.length; i++) {
                    var entry = value.at(i);
                    // Be sure we only plot CPM entries (we might have anything in the
                    // log...
                    var stamp = new Date(entry.get('timestamp')).getTime();
                    if (entry.get('data').cpm != undefined) {
                        var cpm = parseFloat((type == 'onyxlog') ? entry.get('data').cpm : entry.get('data').cpm.value);
                        this.plot.fastAppendPoint({
                            name: 'CPM',
                            value: cpm,
                            timestamp: stamp
                        });
                        this.overview.fastAppendPoint({
                            name: 'CPM',
                            value: cpm,
                            timestamp: stamp
                        });

                        // If we have min/max values in the recording and we have a proper CPM reading
                        // (not CPM30) then record those as well:
                        if (entry.get('data').type == 'CPM') {
                            this.plot.fastAppendPoint({
                                value: entry.get('data').min,
                                name: 'Minimum',
                                timestamp: stamp,
                                options: {
                                    id: "min"
                                }
                            });
                            this.plot.fastAppendPoint({
                                value: entry.get('data').max,
                                name: "Maximum",
                                timestamp: stamp,
                                options: {lines: {
                                    show: true,
                                    fill: true
                                },
                                fillBetween: "min"},
                            });
                        }

                    }
                    if (entry.get('data').cpm2 != undefined) {
                        var cpm2 = parseFloat(entry.get('data').cpm2.value);
                        this.plot.fastAppendPoint({
                            'name': "CPM2",
                            'value': cpm2,
                            timestamp: stamp
                        });
                        this.overview.fastAppendPoint({
                            'name': "CPM2",
                            'value': cpm2,
                            timestamp: stamp
                        });
                    }
                }
                if (ret.length)
                    data.push({
                        data: ret,
                        label: "CPM"
                    });
            }
            this.plot.redraw();
            this.overview.redraw();
            return data;
        },

        // Ugly at this stage, just to make it work (from flotcharts.org examples)
        showTooltip: function (x, y, contents) {
            $("
" + contents + "
").css({
                position: "absolute",
                display: "none",
                top: y + 5,
                left: x + 5,
                padding: "3px",
                opacity: 0.90
            }).appendTo("body").fadeIn(200);
        },

    });
});<|endoftext|>


<commit_before>var catalogModule = (function(){

});

function installJavascript(success){
	$('#placeHolderPane').load('/catalog/fragments/javascript-template.html',function() {
		if(success !== undefined){
			success();
		}
	});
}

function showJSOverview() {
	console.log("show javascript overview");
	$('#headerJavascriptHolder').load('/catalog/fragments/overview.html',function() {
	});
}

function showJSGettingStarted() {
	console.log("show javascript getting started");
	$('#headerJavascriptHolder').load('/catalog/fragments/getting-started.html',function() {
		prettyPrint();
	});
}

var jsfeature;
var catalogJSBase;
function showJSFeature(feature) {
	console.log("show javascript feature : "+feature);
		if(jsfeature !== undefined){
			console.log('should maybe uninstall feature ? : '+jsfeature);
//			if(uninstallFeature){
//				uninstallFeature();
//			}

		}

		jsfeature = feature;
		var sbase = '/catalog/views/'+feature+'/catalog-'+feature+'.js';
		catalogJSBase = '/catalog/views/'+feature;
		//load catalog
		console.log("try load : "+sbase);
		$.getScript(sbase)
		.done(function( script, textStatus ) {
			console.log('catalog js loaded');
			//load views from catalog
			loadViews();
			//load feature page
			$('#headerJavascriptHolder').load('/catalog/features/feature-'+feature+'.html',function() {
				prettyPrint();
				$('#jscatalog').load('jscatalog.html',function() {
					var tbody='';
					var count=0;
					for (var property in views) {
						console.log('view : '+views[property].name);
					    if (views.hasOwnProperty(property)) {
					    	var idname = 'c'+count++;
					    	var link =''+views[property].name+'';
					    	if(views[property].dashboard)
					    		//tbody = tbody+'
'+link+'	'+views[property].desc+'
';
					    		tbody = tbody+'
'+link+'	'+views[property].desc+'
';
					    	else
					    		//tbody = tbody+'
'+link+'	'+views[property].desc+'
';
					    		tbody = tbody+'
'+link+'	'+views[property].desc+'
';

					    }
					}
					$('#jscatalog-tbody').html(tbody);
				});
			});

		})
		.fail(function( jqxhr, settings, exception ) {
			//alertify.error('JenScript Feature : '+feature+' not available.');
			console.log("fail to load :"+feature);
		});
}

if ( !String.prototype.contains ) {
    String.prototype.contains = function() {
        return String.prototype.indexOf.apply( this, arguments ) !== -1;
    };
}

if (!String.prototype.endsWith) {
	  Object.defineProperty(String.prototype, 'endsWith', {
	    value: function (searchString, position) {
	      var subjectString = this.toString();
	      if (position === undefined || position > subjectString.length) {
	        position = subjectString.length;
	      }
	      position -= searchString.length;
	      var lastIndex = subjectString.indexOf(searchString, position);
	      return lastIndex !== -1 && lastIndex === position;
	    }
	  });
	}

window.onerror = function(msg, url, line, col, error) {
	   // Note that col & error are new to the HTML 5 spec and may not be
	   // supported in every browser.  It worked for me in Chrome.
	   var extra = !col ? '' : '\ncolumn: ' + col;
	   extra += !error ? '' : '\nerror: ' + error;

	   if(url.contains('jenscript/jenscript') && url.endsWith('.js')){
		  console.log("Error: " + msg + "\nurl: " + url + "\nline: " + line + extra);

		   $('#errorHolder').load('/catalog/fragments/error.html',function() {
				var errorText ='\n'+'//'+ UA.toString()+'\n'+msg;
				errorText = errorText+'\n'+'url : '+url+'\n'+'line : '+line+'\n'+'col : '+col;
				errorText = errorText+'\n'+'msg : '+msg;
				if(error != 'undefined')
					errorText = errorText+'\n'+'stack : '+error.stack;

				$('#errorMessage').html(errorText);
				prettyPrint();
			});
	   }

	   var suppressErrorAlert = true;
	   // If you return true, then error alerts (like in older versions of
	   // Internet Explorer) will be suppressed.
	   return suppressErrorAlert;
	};

var currentView;
function showViewSource(viewName){
	if(currentView !== undefined){
		//console.log('current view : '+currentView.name);
		if(currentView.onQuit){
			//console.log('onQuit is defined, call it');
			try{
				currentView.onQuit();
			}catch(e){
				console.log('error on quit view '+currentView.name);
			}

		}
	}

	$("[id*='_desc']").removeAttr('class');
	$(".popover").remove();
	var view = getViewByName(viewName);
	//console.log("view found file : "+view.file);
	$.get(catalogJSBase+'/'+view.file, function(content) {
		$('#sourceHolder').load('/catalog/fragments/view-source.html',function() {
			$('#viewTitle').html(view.desc);
			try{
				create(viewName,"demoView",600,400);
				currentView = view;
			}
			catch(err){
				$('#errorHolder').load('/catalog/fragments/error.html',function() {
					var errorText ='\n'+'//'+ UA.toString()+'\n'+err.toString();
					errorText = errorText+'\n'+'file : '+err.fileName+'\n'+'line : '+err.lineNumber;
					errorText = errorText+'\n'+'stack : '+err.stack;
					$('#errorMessage').html(errorText);
					prettyPrint();
				});
			}finally{
				$('#viewSource').html(content);
				prettyPrint();
			}

		});
		return false;
	});
}

function showDashboardSource(viewName){
	$("[id*='_desc']").removeAttr('class');
	$(".popover").remove();
	var view = getViewByName(viewName);

	$.get(catalogJSBase+'/'+view.file, function(content) {
		$('#sourceHolder').load('/catalog/fragments/jsview-source.html',function() {
			$('#viewTitle').html(view.desc);
			try{
				create(viewName,"demoView",400,300);
			}
			catch(err){
				$('#errorHolder').load('/catalog/fragments/error.html',function() {
					var errorText ='\n'+'//'+ UA.toString()+'\n'+err.toString();
					errorText = errorText+'\n'+'file : '+err.fileName+'\n'+'line : '+err.lineNumber;
					errorText = errorText+'\n'+'stack : '+err.stack;
					$('#errorMessage').html(errorText);
					prettyPrint();
				});
			}finally{
				$('#viewSource').html(content);
				prettyPrint();
			}

		});
		return false;
	});

}

function loadViewFile(viewName){
	var view = getViewByName(viewName);
	if(view !== undefined){
		$.getScript(catalogJSBase+'/'+view.file)
    	.done(function( script, textStatus ) {
    		console.log("success load "+viewName+" with file resource : "+view.file);
    	})
    	.fail(function( jqxhr, settings, exception ) {
    		//alertify.error('failed load  '+viewName+' with error '+exception);
    		console.log('failed load  '+viewName+' with error '+exception);
    	});
	}else{
		//alertify.error('no view found for name  '+viewName);
		console.log('no view found for name  '+viewName);
	}
};

function getViewByName(viewName){
	for (var property in views) {
	    if (views.hasOwnProperty(property)) {
	        //console.log("property : "+views[property].name);
	        if(views[property].name === viewName){
	        	return views[property];
	        }
	    }
	}
	return undefined;
};

function loadViews(){
	for (var property in views) {
	    if (views.hasOwnProperty(property)) {
	        console.log("load view  : "+views[property].name);
	        loadViewFile(views[property].name);
	    }
	}
};

function create(viewName, container, width, height){
	var view = getViewByName(viewName);
	if(view === undefined){
		console.log("Error call function with name :"+viewName+" of feature :"+jsfeature);
	}
	if(!window[view.fnName]){
		console.log("Error call function with name :"+viewName+" of feature :"+jsfeature +" with function name : "+view.fnName);
	}
	//window["My"]["Namespace"]["functionName"](arguments);
	window[view.fnName](container, width, height);
};

if (!String.prototype.startsWith) {
	  Object.defineProperty(String.prototype, 'startsWith', {
	    enumerable: false,
	    configurable: false,
	    writable: false,
	    value: function (searchString, position) {
	      position = position || 0;
	      return this.lastIndexOf(searchString, position) === position;
	    }
	  });
}

var StockLoader = function(proj,args,callback){
	 //console.log('new StockLoader');
	 var years = [];
	 var stocks = [];
	 var that = this;

	 var monitorPlugin = new JenScript.ProgressPlugin({
			x : 30,
			y : 20,
			width : 100,
			height : 6,
//			outlineColor : 'black',
//			backgroundColor : 'pink',
//			foregroundColor : 'purple'
	 });
	 proj.registerPlugin(monitorPlugin);

	 this.getY = function(fullYear){
		 for (var i = 0; i < years.length; i++) {
				var y = years[i];
				if(fullYear == y.year)
					return y;
		 }
		 return undefined;
	 },

	 this.loadYear = function(year){
		//console.log('load year : '+year.year);
		if(year === undefined || year.year === undefined)
			return;

		var m = new JenScript.ProgressMonitor({
			//x : 30,
			//y : 20,
			//width : 100,
			//height : 6,
			total : 250,//approximation of total which is (245/255 stock by year)
			onComplete : function() {
				proj.unregisterPlugin(this);
			},
			outlineColor : 'black',
			backgroundColor : 'white',
			foregroundColor : 'purple'
		});
		monitorPlugin.addMonitor(m);

		//Ref to monitor
		year.monitor = m;

		var stockCount = 0;
		var dataWorker = new Worker(JenSoft.Context.path+ '/module/javascript/catalog/stock/DataWorker2.js');
		dataWorker.addEventListener("message", function(event) {
			if (event.data.startsWith !== undefined && event.data.startsWith('finish')) {
				var yearFromWorker = event.data.split(':')[1];
				var yo = that.getY(yearFromWorker);
				yo.state='loaded';
				yo.monitor.complete();
				callback(yo.year,stocks);
				yo.worker.terminate();
			} else {
				stockCount++;
				var stock = new JenScript.Stock(event.data);
				if (event.data.fixing !== undefined){
					stocks[stocks.length] = stock;
					var yo = that.getY(stock.fixing.getFullYear());
					if(yo.monitor){
						setTimeout(function(){
							yo.monitor.setValue(stockCount, 'Load SLV '+yo.year+' stock '+ stockCount);
						},20);
					}else{
						console.log("no monitor found");
					}

				}

			}

		}, false);
		dataWorker.postMessage((year.year+''));
		year.worker=dataWorker;
	};

	proj.addProjectionListener('boundChanged',function(){
		var minYear = proj.getMinDate().getFullYear();
		var maxYear = proj.getMaxDate().getFullYear();
		var foundMinYear = false;
		var foundMaxYear = false;
		for (var i = 0; i < years.length; i++) {
			var y = years[i].year;
			if(y === minYear){
				foundMinYear = true;
			}
			if(y === maxYear){
				foundMaxYear = true;
			}
		}
		if(!foundMinYear){
			years[years.length]={year : minYear , state :'request'};
		}
		if(!foundMaxYear && minYear !== maxYear){
			years[years.length]={year : maxYear , state :'request'};
		}
		for (var i = 0; i < years.length; i++) {
			var y = years[i];
			if(y.state === 'request'){
				y.state = 'processing';
				setTimeout(that.loadYear(y),300);
			}
		}

	},'Stock projection listener');

	var init = function(i,y){
		console.log(i+','+y.year);
		setTimeout(function(){
			that.loadYear(y);
		},i*600);
	};

	for (var i = 0; i < args.length; i++) {
		console.log('launch defaut year : '+args[i]);
		years[i]={year:args[i],state:'processing'};
		init(i,years[i]);
	}

};

<commit_msg>refactor catalog module<commit_after>var catalogModule = (function(){
	//TODO make catalog module
});

function installJavascript(success){
	$('#placeHolderPane').load('/catalog/fragments/javascript-template.html',function() {
		if(success !== undefined){
			success();
		}
	});
}

function showJSOverview() {
	console.log("show javascript overview");
	$('#headerJavascriptHolder').load('/catalog/fragments/overview.html',function() {
	});
}

function showJSGettingStarted() {
	console.log("show javascript getting started");
	$('#headerJavascriptHolder').load('/catalog/fragments/getting-started.html',function() {
		prettyPrint();
	});
}

var jsfeature;
var catalogJSBase;
function showJSFeature(feature) {
	console.log("show javascript feature : "+feature);
		if(jsfeature !== undefined){
			//console.log('should maybe uninstall feature ? : '+jsfeature);
		}

		jsfeature = feature;
		var sbase = '/catalog/views/'+feature+'/catalog-'+feature+'.js';
		catalogJSBase = '/catalog/views/'+feature;
		//load catalog
		$.getScript(sbase)
		.done(function( script, textStatus ) {
			//load views from catalog
			loadViews();
			//load feature page
			$('#headerJavascriptHolder').load('/catalog/features/feature-'+feature+'.html',function() {
				prettyPrint();
				$('#jscatalog').load('/catalog/fragments/jscatalog.html',function() {
					var tbody='';
					var count=0;
					for (var property in views) {
						console.log('view : '+views[property].name);
					    if (views.hasOwnProperty(property)) {
					    	var idname = 'c'+count++;
					    	var link =''+views[property].name+'';
					    	if(views[property].dashboard)
					    		tbody = tbody+'
'+link+'	'+views[property].desc+'
';
					    	else
					    		tbody = tbody+'
'+link+'	'+views[property].desc+'
';

					    }
					}
					$('#jscatalog-tbody').html(tbody);
				});
			});

		})
		.fail(function( jqxhr, settings, exception ) {
			//console.log("fail to load :"+feature);
		});
}

if ( !String.prototype.contains ) {
    String.prototype.contains = function() {
        return String.prototype.indexOf.apply( this, arguments ) !== -1;
    };
}

if (!String.prototype.endsWith) {
	  Object.defineProperty(String.prototype, 'endsWith', {
	    value: function (searchString, position) {
	      var subjectString = this.toString();
	      if (position === undefined || position > subjectString.length) {
	        position = subjectString.length;
	      }
	      position -= searchString.length;
	      var lastIndex = subjectString.indexOf(searchString, position);
	      return lastIndex !== -1 && lastIndex === position;
	    }
	  });
	}

window.onerror = function(msg, url, line, col, error) {
	   // Note that col & error are new to the HTML 5 spec and may not be
	   // supported in every browser.  It worked for me in Chrome.
	   var extra = !col ? '' : '\ncolumn: ' + col;
	   extra += !error ? '' : '\nerror: ' + error;

	   if(url.contains('jenscript/jenscript') && url.endsWith('.js')){
		  console.log("Error: " + msg + "\nurl: " + url + "\nline: " + line + extra);

		   $('#errorHolder').load('/catalog/fragments/error.html',function() {
				var errorText ='\n'+'//'+ UA.toString()+'\n'+msg;
				errorText = errorText+'\n'+'url : '+url+'\n'+'line : '+line+'\n'+'col : '+col;
				errorText = errorText+'\n'+'msg : '+msg;
				if(error != 'undefined')
					errorText = errorText+'\n'+'stack : '+error.stack;

				$('#errorMessage').html(errorText);
				prettyPrint();
			});
	   }

	   var suppressErrorAlert = true;
	   // If you return true, then error alerts (like in older versions of
	   // Internet Explorer) will be suppressed.
	   return suppressErrorAlert;
	};

var currentView;
function showViewSource(viewName){
	if(currentView !== undefined){
		//console.log('current view : '+currentView.name);
		if(currentView.onQuit){
			//console.log('onQuit is defined, call it');
			try{
				currentView.onQuit();
			}catch(e){
				console.log('error on quit view '+currentView.name);
			}

		}
	}

	$("[id*='_desc']").removeAttr('class');
	$(".popover").remove();
	var view = getViewByName(viewName);
	$.get(catalogJSBase+'/'+view.file, function(content) {
		$('#sourceHolder').load('/catalog/fragments/view-source.html',function() {
			$('#viewTitle').html(view.desc);
			try{
				create(viewName,"demoView",600,400);
				currentView = view;
			}
			catch(err){
				$('#errorHolder').load('/catalog/fragments/error.html',function() {
					var errorText ='\n'+'//'+ UA.toString()+'\n'+err.toString();
					errorText = errorText+'\n'+'file : '+err.fileName+'\n'+'line : '+err.lineNumber;
					errorText = errorText+'\n'+'stack : '+err.stack;
					$('#errorMessage').html(errorText);
					prettyPrint();
				});
			}finally{
				$('#viewSource').html(content);
				prettyPrint();
			}

		});
		return false;
	});
}

function showDashboardSource(viewName){
	$("[id*='_desc']").removeAttr('class');
	$(".popover").remove();
	var view = getViewByName(viewName);

	$.get(catalogJSBase+'/'+view.file, function(content) {
		$('#sourceHolder').load('/catalog/fragments/jsview-source.html',function() {
			$('#viewTitle').html(view.desc);
			try{
				create(viewName,"demoView",400,300);
			}
			catch(err){
				$('#errorHolder').load('/catalog/fragments/error.html',function() {
					var errorText ='\n'+'//'+ UA.toString()+'\n'+err.toString();
					errorText = errorText+'\n'+'file : '+err.fileName+'\n'+'line : '+err.lineNumber;
					errorText = errorText+'\n'+'stack : '+err.stack;
					$('#errorMessage').html(errorText);
					prettyPrint();
				});
			}finally{
				$('#viewSource').html(content);
				prettyPrint();
			}

		});
		return false;
	});

}

function loadViewFile(viewName){
	var view = getViewByName(viewName);
	if(view !== undefined){
		$.getScript(catalogJSBase+'/'+view.file)
    	.done(function( script, textStatus ) {
    		console.log("success load "+viewName+" with file resource : "+view.file);
    	})
    	.fail(function( jqxhr, settings, exception ) {
    		console.log('failed load  '+viewName+' with error '+exception);
    	});
	}else{
		console.log('no view found for name  '+viewName);
	}
};

function getViewByName(viewName){
	for (var property in views) {
	    if (views.hasOwnProperty(property)) {
	        //console.log("property : "+views[property].name);
	        if(views[property].name === viewName){
	        	return views[property];
	        }
	    }
	}
	return undefined;
};

function loadViews(){
	for (var property in views) {
	    if (views.hasOwnProperty(property)) {
	        console.log("load view  : "+views[property].name);
	        loadViewFile(views[property].name);
	    }
	}
};

function create(viewName, container, width, height){
	var view = getViewByName(viewName);
	if(view === undefined){
		console.log("Error call function with name :"+viewName+" of feature :"+jsfeature);
	}
	if(!window[view.fnName]){
		console.log("Error call function with name :"+viewName+" of feature :"+jsfeature +" with function name : "+view.fnName);
	}
	//window["My"]["Namespace"]["functionName"](arguments);
	window[view.fnName](container, width, height);
};

if (!String.prototype.startsWith) {
	  Object.defineProperty(String.prototype, 'startsWith', {
	    enumerable: false,
	    configurable: false,
	    writable: false,
	    value: function (searchString, position) {
	      position = position || 0;
	      return this.lastIndexOf(searchString, position) === position;
	    }
	  });
}

var StockLoader = function(proj,args,callback){
	 //console.log('new StockLoader');
	 var years = [];
	 var stocks = [];
	 var that = this;

	 var monitorPlugin = new JenScript.ProgressPlugin({
			x : 30,
			y : 20,
			width : 100,
			height : 6,
//			outlineColor : 'black',
//			backgroundColor : 'pink',
//			foregroundColor : 'purple'
	 });
	 proj.registerPlugin(monitorPlugin);

	 this.getY = function(fullYear){
		 for (var i = 0; i < years.length; i++) {
				var y = years[i];
				if(fullYear == y.year)
					return y;
		 }
		 return undefined;
	 },

	 this.loadYear = function(year){
		//console.log('load year : '+year.year);
		if(year === undefined || year.year === undefined)
			return;

		var m = new JenScript.ProgressMonitor({
			//x : 30,
			//y : 20,
			//width : 100,
			//height : 6,
			total : 250,//approximation of total which is (245/255 stock by year)
			onComplete : function() {
				proj.unregisterPlugin(this);
			},
			outlineColor : 'black',
			backgroundColor : 'white',
			foregroundColor : 'purple'
		});
		monitorPlugin.addMonitor(m);

		//Ref to monitor
		year.monitor = m;

		var stockCount = 0;
		var dataWorker = new Worker('/catalog/views/stock/DataWorker2.js');
		dataWorker.addEventListener("message", function(event) {
			if (event.data.startsWith !== undefined && event.data.startsWith('finish')) {
				var yearFromWorker = event.data.split(':')[1];
				var yo = that.getY(yearFromWorker);
				yo.state='loaded';
				yo.monitor.complete();
				callback(yo.year,stocks);
				yo.worker.terminate();
			} else {
				stockCount++;
				var stock = new JenScript.Stock(event.data);
				if (event.data.fixing !== undefined){
					stocks[stocks.length] = stock;
					var yo = that.getY(stock.fixing.getFullYear());
					if(yo.monitor){
						setTimeout(function(){
							yo.monitor.setValue(stockCount, 'Load SLV '+yo.year+' stock '+ stockCount);
						},20);
					}else{
						//console.log("no monitor found");
					}

				}

			}

		}, false);
		dataWorker.postMessage((year.year+''));
		year.worker=dataWorker;
	};

	proj.addProjectionListener('boundChanged',function(){
		var minYear = proj.getMinDate().getFullYear();
		var maxYear = proj.getMaxDate().getFullYear();
		var foundMinYear = false;
		var foundMaxYear = false;
		for (var i = 0; i < years.length; i++) {
			var y = years[i].year;
			if(y === minYear){
				foundMinYear = true;
			}
			if(y === maxYear){
				foundMaxYear = true;
			}
		}
		if(!foundMinYear){
			years[years.length]={year : minYear , state :'request'};
		}
		if(!foundMaxYear && minYear !== maxYear){
			years[years.length]={year : maxYear , state :'request'};
		}
		for (var i = 0; i < years.length; i++) {
			var y = years[i];
			if(y.state === 'request'){
				y.state = 'processing';
				setTimeout(that.loadYear(y),300);
			}
		}

	},'Stock projection listener');

	var init = function(i,y){
		console.log(i+','+y.year);
		setTimeout(function(){
			that.loadYear(y);
		},i*600);
	};

	for (var i = 0; i < args.length; i++) {
		//console.log('launch defaut year : '+args[i]);
		years[i]={year:args[i],state:'processing'};
		init(i,years[i]);
	}

};

<|endoftext|>


<commit_before>/* Copyright 2009, 2010 Paperpile

   This file is part of Paperpile

   Paperpile is free software: you can redistribute it and/or modify it
   under the terms of the GNU Affero General Public License as
   published by the Free Software Foundation, either version 3 of the
   License, or (at your option) any later version.

   Paperpile is distributed in the hope that it will be useful, but
   WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   Affero General Public License for more details.  You should have
   received a copy of the GNU Affero General Public License along with
   Paperpile.  If not, see http://www.gnu.org/licenses. */

Paperpile.QueueList = function(config) {
  Ext.apply(this, config);
  Paperpile.QueueList.superclass.constructor.call(this, {});

  this.on('rowcontextmenu', this.onContextClick, this);

  this.on('rowclick', this.onRowClick, this);

};

Ext.extend(Paperpile.QueueList, Ext.grid.GridPanel, {

  onRowClick: function(grid, rowIndex, e) {
    var el = e.getTarget();
    var record = this.getStore().getAt(rowIndex);
    var data = record.data;
    switch (el.getAttribute('action')) {
    case 'pdf-match-error-report':
      data.reportString = Paperpile.utils.hashToString(data);
      data.file = record.data['_pdf_tmp'];
      Paperpile.main.reportPdfMatchError(data);
      break;
    case 'pdf-match-insert-manually':
      Paperpile.main.addPDFManually(data.id, data.gridID);
      break;
    case 'pdf-download-error-report':
      var string = Paperpile.utils.hashToString(data);
      var job = Paperpile.utils.hashToString(data._search_job);
      data.reportString = string + "\n\n" + job;
      Paperpile.main.reportPdfDownloadError(data);
      break;
    case 'pdf-download-open-url':
      Paperpile.utils.openURL(data.publisherLink);
      break;
    case 'pdf-view':
      var path;
      // Find the right field depending on the circumstances...
      if (data._pdf_tmp) {
        path = data._pdf_tmp;
      } else {
        if (data.pdf_name){
          path = Paperpile.utils.catPath(Paperpile.main.globalSettings.paper_root, data.pdf_name);
        } else {
          path = data.pdf;
        }
      }
      Paperpile.utils.openFile(path);
      break;
    case 'cancel-task':
      this.getQueuePanel().cancelJobs();
      break;
    case 'retry-task':
      this.getQueuePanel().retryJobs();
      break;
    }
  },

  getQueuePanel: function() {
    return this.findParentByType(Paperpile.QueuePanel);
  },

  renderData: function(value, meta, record) {

    var data = record.data;

    // Show that canceling is in progress
    if (data.status === 'RUNNING' && this.flaggedForCancel[data.id]){
      data.message = "Canceling Task...";
      data.flaggedForCancel = true;
    } else {
      data.flaggedForCancel = false;
    }

    if (data.size && data.downloaded && data.status === 'RUNNING') {
      data.message = 'Downloading (' + Math.round((data.downloaded / data.size) * 100) + '%)';
    }

    if (data.authors) {
      data.shortAuthors = this.shortAuthors(data.authors_display);
    } else {
      data.shortAuthors = null;
    }

    if (data.title) {
      data.shortTitle = Ext.util.Format.ellipsis(data.title, 65, true);
    } else {
      data.shortTitle = null;
    }

    data.publisherLink = null;

    if (data.doi) {
      data.publisherLink = 'http://dx.doi.org/' + data.doi;
    } else {
      if (data.linkout) {
        data.publisherLink = data.linkout;
      }
    }

    data.gridID = this.id;

    return this.dataTemplate.apply(data);
  },

  renderStatus: function(value, meta, record) {
    var data = record.data;
    return this.statusTemplate.apply(data);
  },

  renderType: function(value, meta, record) {
    var data = record.data;
    return this.typeTemplate.apply(data);
  },

  initComponent: function() {

    // Helper construct to show appropriate status messages when
    // cancel is in progress
    this.flaggedForCancel = {};

    var pager = new Ext.PagingToolbar({
      pageSize: 100,
      store: this.getStore(),
      displayInfo: true,
      displayMsg: 'Tasks {0} - {1} of {2}',
      emptyMsg: "No tasks"
    });

    this.dataTemplate = new Ext.XTemplate(

      '
',
      '    <tpl if="type==\'PDF_SEARCH\'||type==\'METADATA_UPDATE\'">',
      '
{shortTitle}
',
      '
{shortAuthors} <tpl if="year">({year}) </tpl> {journal}

',
      '    </tpl>',
      '    <tpl if="type==\'PDF_IMPORT\'">',
      '
{_pdf_tmp}
',
      '       <tpl if="status==\'DONE\'">',
      '
{shortTitle} {shortAuthors} <tpl if="year">({year}) </tpl> {journal}

',
      '       </tpl>',
      '    </tpl>',
      '
',
      '        {message}',
      '
',
      '
',
      '       <tpl if="status==\'DONE\'">',
      '         <tpl if="type==\'PDF_SEARCH\'||type==\'PDF_IMPORT\'">View PDF</tpl>',
      '       </tpl>',
      '       <tpl if="status==\'ERROR\'">',
      '         <tpl if="type==\'PDF_SEARCH\'">',
      '           <tpl if="publisherLink">',
      '               Go to publisher site | ',
      '            </tpl>',
      '            Send Error Report |',
      '         </tpl>',
      '         <tpl if="type==\'PDF_IMPORT\'">',
      '            Insert Data Manually |',
      '            Send Error Report ',
      '         </tpl>',
      '        <tpl if="type==\'PDF_SEARCH\'||type==\'METADATA_UPDATE\'"> Retry</tpl>',
      '       </tpl>',
      '       <tpl if="status==\'RUNNING\'||status==\'PENDING\'">',
      '          <tpl if="!flaggedForCancel">Cancel</tpl>',
      '          <tpl if="flaggedForCancel">Cancel</tpl>',
      '       </tpl>',
      '
 ',
      '
'

    ).compile();

    this.typeTemplate = new Ext.XTemplate(
      '
',
      '  <tpl if="type==\'PDF_SEARCH\'">',
      '    Download PDF',
      '  </tpl>',
      '  <tpl if="type==\'PDF_IMPORT\'">',
      '    Import PDF',
      '  </tpl>',
      '  <tpl if="type==\'METADATA_UPDATE\'">',
      '    Auto-complete',
      '  </tpl>',
      '
').compile();

    this.statusTemplate = new Ext.XTemplate(
      '
<tpl if="status==\'PENDING\'">Waiting</tpl>').compile();

    Ext.apply(this, {
      store: this.getStore(),
      bbar: pager,
      multiSelect: true,
      selModel: new Ext.ux.BetterRowSelectionModel(),
      cm: new Ext.grid.ColumnModel({
        defaults: {
          menuDisabled: true,
          sortable: false
        },
        columns: [{
          header: "Task",
          id: 'type',
          dataIndex: 'type',
          renderer: this.renderType.createDelegate(this),
          sortable: false,
          resizable: false
        },
        {
          header: "Data",
          id: 'title',
          dataIndex: 'title',
          renderer: this.renderData.createDelegate(this),
          sortable: false,
          resizable: false
        },
        {
          header: "Icon",
          id: 'status',
          dataIndex: 'status',
          renderer: this.renderStatus.createDelegate(this),
          sortable: false,
          resizable: false,
          width: 70,
        }]
      }),
      autoExpandColumn: 'title',
      hideHeaders: true
    });
    Paperpile.QueueList.superclass.initComponent.call(this);

    this.getStore().load();

    this.on('afterrender', function() {
      this.mon(this.getSelectionModel(), 'afterselectionchange', this.selChanged, this);
      this.selChanged();
    },
    this);

  },

  getSelectedRecords: function() {
    return this.getSelectionModel().getSelections();
  },

  getSelectedIds: function() {
    var sel = this.getSelectedRecords();
    var ids = [];
    for (var i = 0; i < sel.length; i++) {
      ids.push(sel[i].data.id);
    }
    return ids;
  },

  selChanged: function(selections) {

  },

  // onUpdate function for the Queue grid view.
  onUpdate: function(data) {
    var jobs = data.jobs;
    if (!jobs) {
      return;
    }

    var store = this.getStore();
    for (var id in jobs) {
      var record = store.getAt(this.store.findExact('id', id));
      if (!record) {
        continue;
      }

      var needsUpdating = false;
      var update = jobs[id];
      record.editing = true;
      for (var field in update) {
        record.set(field, update[field]);
      }
      record.set('size', update.info.size);
      record.set('downloaded', update.info.downloaded);
      record.editing = false;

      // Clear flaggedForCancel when job is done
      if (this.flaggedForCancel[record.get('id')] && record.get('status') != "RUNNING"){
        this.flaggedForCancel[record.get('id')] = false;
      }

      if (record.dirty) {
        needsUpdating = true;
      }
      if (needsUpdating) {
        store.fireEvent('update', store, record, Ext.data.Record.EDIT);
      }
    }
  },

  backgroundReload: function() {
    this.backgroundLoading = true;

    this.getStore().reload({
      callback: function() {
        this.backgroundLoading = false;
      },
      scope: this
    });
  },

  getStore: function() {
    if (this._store != null) {
      return this._store;
    }
    this._store = new Ext.data.Store({
      proxy: new Ext.data.HttpProxy({
        url: Paperpile.Url('/ajax/queue/grid'),
        method: 'GET'
      }),
      baseParams: {
        limit: 100
      },
      reader: new Ext.data.JsonReader()
    });
    return this._store;
  },

  shortAuthors: function(names) {
    var list = names.split(',');
    if (list.length > 1) {
      return list[0] + " et al.";
    } else {
      return names;
    }
  },

  destroy: function() {
    Paperpile.QueueList.superclass.destroy.call(this);

    if (this._store) {
      this._store.destroy();
    }

    if (this.context) {
      this.context.destroy();
    }
  }

});<commit_msg>Refactor: move queue/grid.js initComponent method to the top of the file.<commit_after>/* Copyright 2009, 2010 Paperpile

   This file is part of Paperpile

   Paperpile is free software: you can redistribute it and/or modify it
   under the terms of the GNU Affero General Public License as
   published by the Free Software Foundation, either version 3 of the
   License, or (at your option) any later version.

   Paperpile is distributed in the hope that it will be useful, but
   WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   Affero General Public License for more details.  You should have
   received a copy of the GNU Affero General Public License along with
   Paperpile.  If not, see http://www.gnu.org/licenses. */

Paperpile.QueueList = function(config) {
  Ext.apply(this, config);
  Paperpile.QueueList.superclass.constructor.call(this, {});

  this.on('rowcontextmenu', this.onContextClick, this);

  this.on('rowclick', this.onRowClick, this);

};

Ext.extend(Paperpile.QueueList, Ext.grid.GridPanel, {

  initComponent: function() {

    // Helper construct to show appropriate status messages when
    // cancel is in progress
    this.flaggedForCancel = {};

    var pager = new Ext.PagingToolbar({
      pageSize: 100,
      store: this.getStore(),
      displayInfo: true,
      displayMsg: 'Tasks {0} - {1} of {2}',
      emptyMsg: "No tasks"
    });

    this.dataTemplate = new Ext.XTemplate(

      '
',
      '    <tpl if="type==\'PDF_SEARCH\'||type==\'METADATA_UPDATE\'">',
      '
{shortTitle}
',
      '
{shortAuthors} <tpl if="year">({year}) </tpl> {journal}

',
      '    </tpl>',
      '    <tpl if="type==\'PDF_IMPORT\'">',
      '
{_pdf_tmp}
',
      '       <tpl if="status==\'DONE\'">',
      '
{shortTitle} {shortAuthors} <tpl if="year">({year}) </tpl> {journal}

',
      '       </tpl>',
      '    </tpl>',
      '
',
      '        {message}',
      '
',
      '
',
      '       <tpl if="status==\'DONE\'">',
      '         <tpl if="type==\'PDF_SEARCH\'||type==\'PDF_IMPORT\'">View PDF</tpl>',
      '       </tpl>',
      '       <tpl if="status==\'ERROR\'">',
      '         <tpl if="type==\'PDF_SEARCH\'">',
      '           <tpl if="publisherLink">',
      '               Go to publisher site | ',
      '            </tpl>',
      '            Send Error Report |',
      '         </tpl>',
      '         <tpl if="type==\'PDF_IMPORT\'">',
      '            Insert Data Manually |',
      '            Send Error Report ',
      '         </tpl>',
      '        <tpl if="type==\'PDF_SEARCH\'||type==\'METADATA_UPDATE\'"> Retry</tpl>',
      '       </tpl>',
      '       <tpl if="status==\'RUNNING\'||status==\'PENDING\'">',
      '          <tpl if="!flaggedForCancel">Cancel</tpl>',
      '          <tpl if="flaggedForCancel">Cancel</tpl>',
      '       </tpl>',
      '
 ',
      '
'

    ).compile();

    this.typeTemplate = new Ext.XTemplate(
      '
',
      '  <tpl if="type==\'PDF_SEARCH\'">',
      '    Download PDF',
      '  </tpl>',
      '  <tpl if="type==\'PDF_IMPORT\'">',
      '    Import PDF',
      '  </tpl>',
      '  <tpl if="type==\'METADATA_UPDATE\'">',
      '    Auto-complete',
      '  </tpl>',
      '
').compile();

    this.statusTemplate = new Ext.XTemplate(
      '
<tpl if="status==\'PENDING\'">Waiting</tpl>').compile();

    Ext.apply(this, {
      store: this.getStore(),
      bbar: pager,
      multiSelect: true,
      selModel: new Ext.ux.BetterRowSelectionModel(),
      cm: new Ext.grid.ColumnModel({
        defaults: {
          menuDisabled: true,
          sortable: false
        },
        columns: [{
          header: "Task",
          id: 'type',
          dataIndex: 'type',
          renderer: this.renderType.createDelegate(this),
          sortable: false,
          resizable: false
        },
        {
          header: "Data",
          id: 'title',
          dataIndex: 'title',
          renderer: this.renderData.createDelegate(this),
          sortable: false,
          resizable: false
        },
        {
          header: "Icon",
          id: 'status',
          dataIndex: 'status',
          renderer: this.renderStatus.createDelegate(this),
          sortable: false,
          resizable: false,
          width: 70,
        }]
      }),
      autoExpandColumn: 'title',
      hideHeaders: true
    });
    Paperpile.QueueList.superclass.initComponent.call(this);

    this.getStore().load();

    this.on('afterrender', function() {
      this.mon(this.getSelectionModel(), 'afterselectionchange', this.selChanged, this);
      this.selChanged();
    },
    this);

  },

  onRowClick: function(grid, rowIndex, e) {
    var el = e.getTarget();
    var record = this.getStore().getAt(rowIndex);
    var data = record.data;
    switch (el.getAttribute('action')) {
    case 'pdf-match-error-report':
      data.reportString = Paperpile.utils.hashToString(data);
      data.file = record.data['_pdf_tmp'];
      Paperpile.main.reportPdfMatchError(data);
      break;
    case 'pdf-match-insert-manually':
      Paperpile.main.addPDFManually(data.id, data.gridID);
      break;
    case 'pdf-download-error-report':
      var string = Paperpile.utils.hashToString(data);
      var job = Paperpile.utils.hashToString(data._search_job);
      data.reportString = string + "\n\n" + job;
      Paperpile.main.reportPdfDownloadError(data);
      break;
    case 'pdf-download-open-url':
      Paperpile.utils.openURL(data.publisherLink);
      break;
    case 'pdf-view':
      var path;
      // Find the right field depending on the circumstances...
      if (data._pdf_tmp) {
        path = data._pdf_tmp;
      } else {
        if (data.pdf_name){
          path = Paperpile.utils.catPath(Paperpile.main.globalSettings.paper_root, data.pdf_name);
        } else {
          path = data.pdf;
        }
      }
      Paperpile.utils.openFile(path);
      break;
    case 'cancel-task':
      this.getQueuePanel().cancelJobs();
      break;
    case 'retry-task':
      this.getQueuePanel().retryJobs();
      break;
    }
  },

  getQueuePanel: function() {
    return this.findParentByType(Paperpile.QueuePanel);
  },

  renderData: function(value, meta, record) {

    var data = record.data;

    // Show that canceling is in progress
    if (data.status === 'RUNNING' && this.flaggedForCancel[data.id]){
      data.message = "Canceling Task...";
      data.flaggedForCancel = true;
    } else {
      data.flaggedForCancel = false;
    }

    if (data.size && data.downloaded && data.status === 'RUNNING') {
      data.message = 'Downloading (' + Math.round((data.downloaded / data.size) * 100) + '%)';
    }

    if (data.authors) {
      data.shortAuthors = this.shortAuthors(data.authors_display);
    } else {
      data.shortAuthors = null;
    }

    if (data.title) {
      data.shortTitle = Ext.util.Format.ellipsis(data.title, 65, true);
    } else {
      data.shortTitle = null;
    }

    data.publisherLink = null;

    if (data.doi) {
      data.publisherLink = 'http://dx.doi.org/' + data.doi;
    } else {
      if (data.linkout) {
        data.publisherLink = data.linkout;
      }
    }

    data.gridID = this.id;

    return this.dataTemplate.apply(data);
  },

  renderStatus: function(value, meta, record) {
    var data = record.data;
    return this.statusTemplate.apply(data);
  },

  renderType: function(value, meta, record) {
    var data = record.data;
    return this.typeTemplate.apply(data);
  },

  getSelectedRecords: function() {
    return this.getSelectionModel().getSelections();
  },

  getSelectedIds: function() {
    var sel = this.getSelectedRecords();
    var ids = [];
    for (var i = 0; i < sel.length; i++) {
      ids.push(sel[i].data.id);
    }
    return ids;
  },

  selChanged: function(selections) {

  },

  // onUpdate function for the Queue grid view.
  onUpdate: function(data) {
    var jobs = data.jobs;
    if (!jobs) {
      return;
    }

    var store = this.getStore();
    for (var id in jobs) {
      var record = store.getAt(this.store.findExact('id', id));
      if (!record) {
        continue;
      }

      var needsUpdating = false;
      var update = jobs[id];
      record.editing = true;
      for (var field in update) {
        record.set(field, update[field]);
      }
      record.set('size', update.info.size);
      record.set('downloaded', update.info.downloaded);
      record.editing = false;

      // Clear flaggedForCancel when job is done
      if (this.flaggedForCancel[record.get('id')] && record.get('status') != "RUNNING"){
        this.flaggedForCancel[record.get('id')] = false;
      }

      if (record.dirty) {
        needsUpdating = true;
      }
      if (needsUpdating) {
        store.fireEvent('update', store, record, Ext.data.Record.EDIT);
      }
    }
  },

  backgroundReload: function() {
    this.backgroundLoading = true;

    this.getStore().reload({
      callback: function() {
        this.backgroundLoading = false;
      },
      scope: this
    });
  },

  getStore: function() {
    if (this._store != null) {
      return this._store;
    }
    this._store = new Ext.data.Store({
      proxy: new Ext.data.HttpProxy({
        url: Paperpile.Url('/ajax/queue/grid'),
        method: 'GET'
      }),
      baseParams: {
        limit: 100
      },
      reader: new Ext.data.JsonReader()
    });
    return this._store;
  },

  shortAuthors: function(names) {
    var list = names.split(',');
    if (list.length > 1) {
      return list[0] + " et al.";
    } else {
      return names;
    }
  },

  destroy: function() {
    Paperpile.QueueList.superclass.destroy.call(this);

    if (this._store) {
      this._store.destroy();
    }

    if (this.context) {
      this.context.destroy();
    }
  }

});<|endoftext|>


<commit_before>describe("About Arrays", function() {

  //We shall contemplate truth by testing reality, via spec expectations.
  it("should create arrays", function() {
    var emptyArray = [];
    expect(typeof(emptyArray)).toBe(FILL_ME_IN); //A mistake? - http:javascript.crockford.com/remedial.html
    expect(emptyArray.length).toBe(FILL_ME_IN);

    var multiTypeArray = [0, 1, "two", function () { return 3; }, {value1: 4, value2: 5}, [6, 7]];
    expect(multiTypeArray[0]).toBe(FILL_ME_IN);
    expect(multiTypeArray[2]).toBe(FILL_ME_IN);
    expect(multiTypeArray[3]()).toBe(FILL_ME_IN);
    expect(multiTypeArray[4].value1).toBe(FILL_ME_IN);
    expect(multiTypeArray[4]["value2"]).toBe(FILL_ME_IN);
    expect(multiTypeArray[5][0]).toBe(FILL_ME_IN);
  });

  it("should understand array literals", function () {
    var array = [];
    expect(array).toEqual([]);

    array[0] = 1;
    expect(array).toEqual([1]);

    array[1] = 2;
    expect(array).toEqual([1, FILL_ME_IN]);

    array.push(3);
    expect(array).toEqual(FILL_ME_IN);
  });

  it("should understand array length", function () {
    var fourNumberArray = [1, 2, 3, 4];

    expect(fourNumberArray.length).toBe(FILL_ME_IN);
    fourNumberArray.push(5, 6);
    expect(fourNumberArray.length).toBe(FILL_ME_IN);

    var tenEmptyElementArray = new Array(10);
    expect(tenEmptyElementArray.length).toBe(FILL_ME_IN);

    tenEmptyElementArray.length = 5;
    expect(tenEmptyElementArray.length).toBe(FILL_ME_IN);
  });

  it("should slice arrays", function () {
    var array = ["peanut", "butter", "and", "jelly"];

    expect(array.slice(0, 1)).toEqual(FILL_ME_IN);
    expect(array.slice(0, 2)).toEqual(FILL_ME_IN);
    expect(array.slice(2, 2)).toEqual(FILL_ME_IN);
    expect(array.slice(2, 20)).toEqual(FILL_ME_IN);
    expect(array.slice(3, 0)).toEqual(FILL_ME_IN);
    expect(array.slice(3, 100)).toEqual(FILL_ME_IN);
    expect(array.slice(5, 1)).toEqual(FILL_ME_IN);
  });

  it("should know array references", function () {
    var array = [ "zero", "one", "two", "three", "four", "five" ];

    function passedByReference(refArray) {
      refArray[1] = "changed in function";
    }
    passedByReference(array);
    expect(array[1]).toBe(FILL_ME_IN);

    var assignedArray = array;
    assignedArray[5] = "changed in assignedArray";
    expect(array[5]).toBe(FILL_ME_IN);

    var copyOfArray = array.slice();
    copyOfArray[3] = "changed in copyOfArray";
    expect(array[3]).toBe(FILL_ME_IN);
  });

  it("should push and pop", function () {
    var array = [1, 2];
    array.push(3);

    expect(array).toEqual(FILL_ME_IN);

    var poppedValue = array.pop();
    expect(poppedValue).toBe(FILL_ME_IN);
    expect(array).toEqual(FILL_ME_IN);
  });

  it("should know about shifting arrays", function () {
    var array = [1, 2];

    array.unshift(3);
    expect(array).toEqual(FILL_ME_IN);

    var shiftedValue = array.shift();
    expect(shiftedValue).toEqual(FILL_ME_IN);
    expect(array).toEqual(FILL_ME_IN);
  });
});
<commit_msg>completed AboutArrays<commit_after>describe("About Arrays", function() {

  //We shall contemplate truth by testing reality, via spec expectations.
  it("should create arrays", function() {
    var emptyArray = [];
    expect(typeof(emptyArray)).toBe('object'); //A mistake? - http:javascript.crockford.com/remedial.html
    expect(emptyArray.length).toBe(0);

    var multiTypeArray = [0, 1, "two", function () { return 3; }, {value1: 4, value2: 5}, [6, 7]];
    expect(multiTypeArray[0]).toBe(0);
    expect(multiTypeArray[2]).toBe("two");
    expect(multiTypeArray[3]()).toBe(3);
    expect(multiTypeArray[4].value1).toBe(4);
    expect(multiTypeArray[4]["value2"]).toBe(5);
    expect(multiTypeArray[5][0]).toBe(6);
  });

  it("should understand array literals", function () {
    var array = [];
    expect(array).toEqual([]);

    array[0] = 1;
    expect(array).toEqual([1]);

    array[1] = 2;
    expect(array).toEqual([1, 2]);

    array.push(3);
    expect(array).toEqual([1, 2, 3]);
  });

  it("should understand array length", function () {
    var fourNumberArray = [1, 2, 3, 4];

    expect(fourNumberArray.length).toBe(4);
    fourNumberArray.push(5, 6);
    expect(fourNumberArray.length).toBe(6);

    var tenEmptyElementArray = new Array(10);
    expect(tenEmptyElementArray.length).toBe(10);

    tenEmptyElementArray.length = 5;
    expect(tenEmptyElementArray.length).toBe(5);
  });

  it("should slice arrays", function () {
    var array = ["peanut", "butter", "and", "jelly"];

    expect(array.slice(0, 1)).toEqual(["peanut"]);
    expect(array.slice(0, 2)).toEqual(["peanut", "butter"]);
    expect(array.slice(2, 2)).toEqual([]);
    expect(array.slice(2, 20)).toEqual(["and", "jelly"]);
    expect(array.slice(3, 0)).toEqual([]);
    expect(array.slice(3, 100)).toEqual(["jelly"]);
    expect(array.slice(5, 1)).toEqual([]);
  });

  it("should know array references", function () {
    var array = [ "zero", "one", "two", "three", "four", "five" ];

    function passedByReference(refArray) {
      refArray[1] = "changed in function";
    }
    passedByReference(array);
    expect(array[1]).toBe("changed in function");

    var assignedArray = array;
    assignedArray[5] = "changed in assignedArray";
    expect(array[5]).toBe("changed in assignedArray");

    var copyOfArray = array.slice();
    copyOfArray[3] = "changed in copyOfArray";
    expect(array[3]).toBe("three");
  });

  it("should push and pop", function () {
    var array = [1, 2];
    array.push(3);

    expect(array).toEqual([1, 2, 3]);

    var poppedValue = array.pop();
    expect(poppedValue).toBe(3);
    expect(array).toEqual([1, 2]);
  });

  it("should know about shifting arrays", function () {
    var array = [1, 2];

    array.unshift(3);
    expect(array).toEqual([3, 1, 2]);

    var shiftedValue = array.shift();
    expect(shiftedValue).toEqual(3);
    expect(array).toEqual([1, 2]);
  });
});
<|endoftext|>