<commit_before>#!/usr/bin/env node

const program = require('commander')
const _ = require('lodash')
const fs = require('fs')
const process = require('process')

const linter = require('./lib/index')
const { loadConfig } = require('./lib/config/config-file')
const { validate } = require('./lib/config/config-validator')
const applyFixes = require('./lib/apply-fixes')
const ruleFixer = require('./lib/rule-fixer')
const packageJson = require('./package.json')

function init() {
  const version = packageJson.version
  program.version(version)

  program
    .usage('[options] <file> [...other_files]')
    .option('-f, --formatter [name]', 'report formatter name (stylish, table, tap, unix)')
    .option('-w, --max-warnings [maxWarningsNumber]', 'number of allowed warnings')
    .option('-c, --config [file_name]', 'file to use as your .solhint.json')
    .option('-q, --quiet', 'report errors only - default: false')
    .option('--ignore-path [file_name]', 'file to use as your .solhintignore')
    .option('--fix', 'automatically fix problems')
    .option('--init', 'create configuration file for solhint')
    .description('Linter for Solidity programming language')
    .action(execMainAction)

  program
    .command('stdin')
    .description('linting of source code data provided to STDIN')
    .option('--filename [file_name]', 'name of file received using STDIN')
    .action(processStdin)

  program
    .command('init-config', null, { noHelp:true })
    .description('create configuration file for solhint')
    .action(writeSampleConfigFile)

  program.parse(process.argv)

  if (program.init) {
    writeSampleConfigFile()
  }

  if (program.args.length < 1) {
    program.help()
  }
}

function execMainAction() {
  if (program.init) {
    writeSampleConfigFile()
  }

  let formatterFn

  try {
    // to check if is a valid formatter before execute linter
    formatterFn = getFormatter(program.formatter)
  } catch (ex) {
    console.error(ex.message)
    process.exit(1)
  }

  const reportLists = program.args.filter(_.isString).map(processPath)
  const reports = _.flatten(reportLists)
  const warningsCount = reports.reduce((acc, i) => acc + i.warningCount, 0)
  const warningsNumberExceeded = program.maxWarnings >= 0 && warningsCount > program.maxWarnings

  if (program.fix) {
    for (const report of reports) {
      const inputSrc = fs.readFileSync(report.filePath).toString()

      const fixes = _(report.reports)
        .filter(x => x.fix)
        .map(x => x.fix(ruleFixer))
        .sort((a, b) => a.range[0] - b.range[0])
        .value()

      const { fixed, output } = applyFixes(fixes, inputSrc)
      if (fixed) {
        report.reports = report.reports.filter(x => !x.fix)
        fs.writeFileSync(report.filePath, output)
      }
    }
  }

  if (program.quiet) {
    // filter the list of reports, to set errors only.
    reports[0].reports = reports[0].reports.filter(i => i.severity === 2)
  }

  if (printReports(reports, formatterFn)) {
    if (program.maxWarnings && !reports[0].errorCount && warningsNumberExceeded) {
      console.log(
        'Solhint found more warnings than the maximum specified (maximum: %s)',
        program.maxWarnings
      )
      process.exit(1)
    }
  }

  exitWithCode(reports)
}

function processStdin(options) {
  const STDIN_FILE = 0
  const stdinBuffer = fs.readFileSync(STDIN_FILE)

  const report = processStr(stdinBuffer.toString())
  report.file = options.filename || 'stdin'
  printReports([report])
}

function writeSampleConfigFile() {
  const configPath = '.solhint.json'
  const sampleConfig = `{
  "extends": "solhint:default"
}
`

  if (!fs.existsSync(configPath)) {
    fs.writeFileSync(configPath, sampleConfig)

    console.log('Configuration file created!')
  } else {
    console.log('Configuration file already exists')
  }

  process.exit(0)
}

const readIgnore = _.memoize(() => {
  let ignoreFile = '.solhintignore'
  try {
    if (program.ignorePath) {
      ignoreFile = program.ignorePath
    }

    return fs
      .readFileSync(ignoreFile)
      .toString()
      .split('\n')
      .map(i => i.trim())
  } catch (e) {
    if (program.ignorePath && e.code === 'ENOENT') {
      console.error(`\nERROR: ${ignoreFile} is not a valid path.`)
    }
    return []
  }
})

const readConfig = _.memoize(() => {
  let config = {}

  try {
    config = loadConfig(program.config)
  } catch (e) {
    console.log(e.message)
    process.exit(1)
  }

  const configExcludeFiles = _.flatten(config.excludedFiles)
  config.excludedFiles = _.concat(configExcludeFiles, readIgnore())

  // validate the configuration before continuing
  validate(config)

  return config
})

function processStr(input) {
  return linter.processStr(input, readConfig())
}

function processPath(path) {
  return linter.processPath(path, readConfig())
}

function printReports(reports, formatter) {
  console.log(formatter(reports))
  return reports
}

function getFormatter(formatter) {
  const formatterName = formatter || 'stylish'
  try {
    return require(`eslint/lib/formatters/${formatterName}`)
  } catch (ex) {
    ex.message = `\nThere was a problem loading formatter option: ${program.formatter} \nError: ${
      ex.message
    }`
    throw ex
  }
}

function exitWithCode(reports) {
  const errorsCount = reports.reduce((acc, i) => acc + i.errorCount, 0)

  process.exit(errorsCount > 0 ? 1 : 0)
}

init()
<commit_msg>fix prettier error<commit_after>#!/usr/bin/env node

const program = require('commander')
const _ = require('lodash')
const fs = require('fs')
const process = require('process')

const linter = require('./lib/index')
const { loadConfig } = require('./lib/config/config-file')
const { validate } = require('./lib/config/config-validator')
const applyFixes = require('./lib/apply-fixes')
const ruleFixer = require('./lib/rule-fixer')
const packageJson = require('./package.json')

function init() {
  const version = packageJson.version
  program.version(version)

  program
    .usage('[options] <file> [...other_files]')
    .option('-f, --formatter [name]', 'report formatter name (stylish, table, tap, unix)')
    .option('-w, --max-warnings [maxWarningsNumber]', 'number of allowed warnings')
    .option('-c, --config [file_name]', 'file to use as your .solhint.json')
    .option('-q, --quiet', 'report errors only - default: false')
    .option('--ignore-path [file_name]', 'file to use as your .solhintignore')
    .option('--fix', 'automatically fix problems')
    .option('--init', 'create configuration file for solhint')
    .description('Linter for Solidity programming language')
    .action(execMainAction)

  program
    .command('stdin')
    .description('linting of source code data provided to STDIN')
    .option('--filename [file_name]', 'name of file received using STDIN')
    .action(processStdin)

  program
    .command('init-config', null, { noHelp: true })
    .description('create configuration file for solhint')
    .action(writeSampleConfigFile)

  program.parse(process.argv)

  if (program.init) {
    writeSampleConfigFile()
  }

  if (program.args.length < 1) {
    program.help()
  }
}

function execMainAction() {
  if (program.init) {
    writeSampleConfigFile()
  }

  let formatterFn

  try {
    // to check if is a valid formatter before execute linter
    formatterFn = getFormatter(program.formatter)
  } catch (ex) {
    console.error(ex.message)
    process.exit(1)
  }

  const reportLists = program.args.filter(_.isString).map(processPath)
  const reports = _.flatten(reportLists)
  const warningsCount = reports.reduce((acc, i) => acc + i.warningCount, 0)
  const warningsNumberExceeded = program.maxWarnings >= 0 && warningsCount > program.maxWarnings

  if (program.fix) {
    for (const report of reports) {
      const inputSrc = fs.readFileSync(report.filePath).toString()

      const fixes = _(report.reports)
        .filter((x) => x.fix)
        .map((x) => x.fix(ruleFixer))
        .sort((a, b) => a.range[0] - b.range[0])
        .value()

      const { fixed, output } = applyFixes(fixes, inputSrc)
      if (fixed) {
        report.reports = report.reports.filter((x) => !x.fix)
        fs.writeFileSync(report.filePath, output)
      }
    }
  }

  if (program.quiet) {
    // filter the list of reports, to set errors only.
    reports[0].reports = reports[0].reports.filter((i) => i.severity === 2)
  }

  if (printReports(reports, formatterFn)) {
    if (program.maxWarnings && !reports[0].errorCount && warningsNumberExceeded) {
      console.log(
        'Solhint found more warnings than the maximum specified (maximum: %s)',
        program.maxWarnings
      )
      process.exit(1)
    }
  }

  exitWithCode(reports)
}

function processStdin(options) {
  const STDIN_FILE = 0
  const stdinBuffer = fs.readFileSync(STDIN_FILE)

  const report = processStr(stdinBuffer.toString())
  report.file = options.filename || 'stdin'
  printReports([report])
}

function writeSampleConfigFile() {
  const configPath = '.solhint.json'
  const sampleConfig = `{
  "extends": "solhint:default"
}
`

  if (!fs.existsSync(configPath)) {
    fs.writeFileSync(configPath, sampleConfig)

    console.log('Configuration file created!')
  } else {
    console.log('Configuration file already exists')
  }

  process.exit(0)
}

const readIgnore = _.memoize(() => {
  let ignoreFile = '.solhintignore'
  try {
    if (program.ignorePath) {
      ignoreFile = program.ignorePath
    }

    return fs
      .readFileSync(ignoreFile)
      .toString()
      .split('\n')
      .map((i) => i.trim())
  } catch (e) {
    if (program.ignorePath && e.code === 'ENOENT') {
      console.error(`\nERROR: ${ignoreFile} is not a valid path.`)
    }
    return []
  }
})

const readConfig = _.memoize(() => {
  let config = {}

  try {
    config = loadConfig(program.config)
  } catch (e) {
    console.log(e.message)
    process.exit(1)
  }

  const configExcludeFiles = _.flatten(config.excludedFiles)
  config.excludedFiles = _.concat(configExcludeFiles, readIgnore())

  // validate the configuration before continuing
  validate(config)

  return config
})

function processStr(input) {
  return linter.processStr(input, readConfig())
}

function processPath(path) {
  return linter.processPath(path, readConfig())
}

function printReports(reports, formatter) {
  console.log(formatter(reports))
  return reports
}

function getFormatter(formatter) {
  const formatterName = formatter || 'stylish'
  try {
    return require(`eslint/lib/formatters/${formatterName}`)
  } catch (ex) {
    ex.message = `\nThere was a problem loading formatter option: ${program.formatter} \nError: ${ex.message}`
    throw ex
  }
}

function exitWithCode(reports) {
  const errorsCount = reports.reduce((acc, i) => acc + i.errorCount, 0)

  process.exit(errorsCount > 0 ? 1 : 0)
}

init()
<|endoftext|>


<commit_before>/**
 *  UI for data corrections for cases and forms, which is a modal that lists out properties and an editable value
 *  for each. The modal includes a search box (which does client-side filtering only), pagination, and the option
 *  to toggle between different display properties (useful with forms, for toggling between question id and label).
 *  The modal will be sized depending on the number of properties: small, large, or full-screen.
 *
 *  Used with reports/partials/data_corrections_trigger.html and reports/partials/data_corrections_modal.html
 *  Usage: hqImport("reports/js/data_corrections").init($triggerElement, $modalElement, options);
 *  Options:
 *      Required:
 *          saveUrl
 *          properties: An object, where keys are the property name and values may be either strings or objects.
 *              If strings, they are assumed to be the property values. If objects, they should have a 'value' key
 *              with the property value and may then have arbitrary other properties to be used for display (see
 *              displayProperties below).
 *      Optional:
 *          propertyNames: All property names, in the order that properties should be displayed.
 *          propertyNamesUrl: Ignore propertyNames and instead fetch that list from this URL.
 *          propertyPrefix: HTML string to display before each property. Rendered as a knockout template that has
 *              access to the property.
 *          propertySuffix: Same idea as proeprtyPrefix.
 *          displayProperties: A list of objects, each with the keys 'property' and 'name'.
 *              Property is the data, plucked from the properties array. Name is displayed in the menu that lets
 *              user toggle between display properties.
 */
hqDefine("reports/js/data_corrections", function() {
    // Represents a single property/value pair, e.g., a form question and its response
    var PropertyModel = function(options) {
        // Don't assert properties of options because PropertyModel allows for
        // arbitrary keys to be used as display properties. Do error if any of
        // these arbitrary keys conflict with existing PropertyModel members.
        var reservedKeys = _.intersection(['dirty'], _.keys(options));
        if (reservedKeys.length) {
            throw new Error("Keys disallowed in PropertyModel: " + reservedKeys.join(", "));
        }

        var self = _.extend({}, options);

        self.name = options.name;
        self.value = ko.observable(options.value || '');
        self.dirty = ko.observable(false);

        return self;
    };

    // Controls the full modal UI
    var DataCorrectionsModel = function(options) {
        hqImport("hqwebapp/js/assert_properties").assert(options, ['saveUrl', 'properties'],
            ['propertyNames', 'propertyNamesUrl', 'displayProperties', 'propertyPrefix', 'propertySuffix']);
        var self = {};

        // Core data, and the order in which it should be displayed
        self.properties = {};                       // map of name => PropertyModel, populated in init
        self.propertyNames = ko.observableArray();  // populated in init, whether names were provided in options or via ajax

        // Handle modal size: small, large or full-screen, with one, two, or three columns, respectively.
        self.itemsPerColumn = 12;
        self.columnsPerPage = ko.observable(1);
        self.itemsPerPage = ko.computed(function() {
            return self.itemsPerColumn * self.columnsPerPage();
        });
        self.columnClass = ko.observable('');
        self.isFullScreenModal = ko.observable(false);
        self.isLargeModal = ko.observable(false);
        self.propertyNames.subscribe(function(newValue) {
            self.columnsPerPage(Math.min(3, Math.ceil(newValue.length / self.itemsPerColumn)));
            self.columnClass("col-sm-" + (12 / self.columnsPerPage()));
            self.isLargeModal(self.columnsPerPage() === 2);
            self.isFullScreenModal(self.columnsPerPage() === 3);
        });

        // Support for displaying different property attributes (e.g., name and id)
        self.displayProperties = _.isEmpty(options.displayProperties) ? [{ property: 'name' }] : options.displayProperties;
        self.displayProperty = ko.observable(_.first(self.displayProperties).property);
        self.updateDisplayProperty = function(newValue) {
            self.displayProperty(newValue);
            self.initQuery();
        };
        self.breakWord = function(str) {
            return str.replace(/([\/_])/g, "$1\u200B");     // eslint-disable-line no-useless-escape
        };
        var innerTemplate = _.map(self.displayProperties, function(p) {
            return _.template("<span data-bind='text: $root.breakWord(<%= property %>), visible: $root.displayProperty() === \"<%= property %>\"'>")(p);
        }).join("");
        self.propertyTemplate = {
            nodes: $("
" + (options.propertyPrefix || "") + innerTemplate + (options.propertySuffix || "") + "
"),
        };

        // Draw items on the current page, taking into account pagination and searching.
        // visibleItems is a list of whichever properties should be displayed on the current page.
        // visibleColumns is a list of lists: the same properties, but organized into columns
        // to simplify the knockout template.
        self.visibleItems = ko.observableArray([]);
        self.visibleColumns = ko.observableArray([]);
        self.render = function() {
            var added = 0,
                index = 0;

            // Remove all items
            self.visibleItems.splice(0);

            // Cycle over all items on previous pages
            while (added < self.itemsPerPage() * (self.currentPage() - 1) && index < self.propertyNames().length) {
                if (self.matchesQuery(self.propertyNames()[index])) {
                    added++;
                }
                index++;
            }

            // Add as many items as fit on a page
            added = 0;
            while (added < self.itemsPerPage() && index < self.propertyNames().length) {
                if (self.matchesQuery(self.propertyNames()[index])) {
                    var name = self.propertyNames()[index];
                    if (!self.properties[name]) {
                        self.properties[name] = new PropertyModel({ name: name });
                    }
                    self.visibleItems.push(self.properties[name]);
                    added++;
                }
                index++;
            }

            // Break visibleItems into separate columns for rendering
            self.visibleColumns.splice(0);
            var itemsPerColumn = self.itemsPerPage() / self.columnsPerPage();
            for (var i = 0; i < self.itemsPerPage(); i += itemsPerColumn) {
                self.visibleColumns.push(self.visibleItems.slice(i, i + itemsPerColumn));
            }
        };

        // Pagination
        self.currentPage = ko.observable();
        self.totalPages = ko.observable();  // observable because it will change if there's a search query
        self.incrementPage = function(increment) {
            var newCurrentPage = self.currentPage() + increment;
            if (newCurrentPage <= 0 || newCurrentPage > self.totalPages()) {
                return;
            }
            self.currentPage(newCurrentPage);
        };

        // Track an array of page numbers, e.g., [1, 2, 3], used by the pagination UI.
        // Having it as an array makes knockout rendering simpler.
        self.visiblePages = ko.observableArray([]);
        self.totalPages.subscribe(function(newValue) {
            self.visiblePages(_.map(_.range(newValue), function(p) { return p + 1; }));
        });
        self.currentPage.subscribe(self.render);

        // Search
        self.query = ko.observable();
        self.matchesQuery = function(propertyName) {
            return !self.query() || propertyName.toLowerCase().indexOf(self.query().toLowerCase()) !== -1;
        };
        self.initQuery = function() {
            self.query("");
        };
        self.query.subscribe(function() {
            self.currentPage(1);
            self.totalPages(Math.ceil(_.filter(self.propertyNames, self.matchesQuery).length / self.itemsPerPage()) || 1);
            self.render();
        });

        // Saving
        self.submitForm = function(model, e) {
            var $button = $(e.currentTarget);
            $button.disableButton();
            $.post({
                url: options.saveUrl,
                data: _.mapObject(self.properties, function(model) {
                    return model.value();
                }),
                success: function() {
                    window.location.reload();
                },
                error: function() {
                    $button.enableButton();
                    self.showRetry(true);
                },
            });
            return true;
        };

        // Control visibility around loading (spinner is shown if names are fetched via ajax) and error handling.
        self.showSpinner = ko.observable(true);
        self.showPagination = ko.computed(function() {
            return !self.showSpinner() && self.propertyNames().length > self.itemsPerPage();
        });
        self.showError = ko.observable(false);
        self.showRetry = ko.observable(false);
        self.disallowSave = ko.computed(function() {
            return self.showSpinner() || self.showError();
        });
        self.showNoData = ko.computed(function() {
            return !self.showError() && self.visibleItems().length === 0;
        });

        // Setup to do once property names exist
        self.init = function() {
            self.properties = _.extend({}, _.mapObject(options.properties, function(data, name) {
                if (typeof(data) === "string") {
                    data = { value: data };
                }
                return new PropertyModel(_.extend({}, data, {
                    name: name,
                    value: data.value,
                    display: _.without(data, 'name', 'value'),
                }));
            }));
            self.initQuery();
            self.currentPage(1);
            self.showError(false);
            self.showRetry(false);
            self.render();
        };

        // Initialization: fetch property names if needed
        var _loadPropertyNames = function(names) {
            _.each(names, function(name) {
                self.propertyNames.push(name);
            });
            self.showSpinner(false);
            self.init();
        };
        if (options.propertyNamesUrl) {
            $.get({
                url: options.propertyNamesUrl,
                success: _loadPropertyNames,
                error: function() {
                    self.showSpinner(false);
                    self.showError(true);
                },
            });
        } else {
            _loadPropertyNames(options.propertyNames || _.keys(options.properties));
        }

        return self;
    };

    var init = function($trigger, $modal, options) {
        var model = undefined;
        if ($trigger.length && $modal.length) {
            $trigger.click(function() {
                $modal.modal();
            });
            model = new DataCorrectionsModel(options);
            $modal.koApplyBindings(model);
        }
        return model;
    };

    return {
        init: init,
    };
});
<commit_msg>Fix paging bug introduced in PR#20423<commit_after>/**
 *  UI for data corrections for cases and forms, which is a modal that lists out properties and an editable value
 *  for each. The modal includes a search box (which does client-side filtering only), pagination, and the option
 *  to toggle between different display properties (useful with forms, for toggling between question id and label).
 *  The modal will be sized depending on the number of properties: small, large, or full-screen.
 *
 *  Used with reports/partials/data_corrections_trigger.html and reports/partials/data_corrections_modal.html
 *  Usage: hqImport("reports/js/data_corrections").init($triggerElement, $modalElement, options);
 *  Options:
 *      Required:
 *          saveUrl
 *          properties: An object, where keys are the property name and values may be either strings or objects.
 *              If strings, they are assumed to be the property values. If objects, they should have a 'value' key
 *              with the property value and may then have arbitrary other properties to be used for display (see
 *              displayProperties below).
 *      Optional:
 *          propertyNames: All property names, in the order that properties should be displayed.
 *          propertyNamesUrl: Ignore propertyNames and instead fetch that list from this URL.
 *          propertyPrefix: HTML string to display before each property. Rendered as a knockout template that has
 *              access to the property.
 *          propertySuffix: Same idea as proeprtyPrefix.
 *          displayProperties: A list of objects, each with the keys 'property' and 'name'.
 *              Property is the data, plucked from the properties array. Name is displayed in the menu that lets
 *              user toggle between display properties.
 */
hqDefine("reports/js/data_corrections", function() {
    // Represents a single property/value pair, e.g., a form question and its response
    var PropertyModel = function(options) {
        // Don't assert properties of options because PropertyModel allows for
        // arbitrary keys to be used as display properties. Do error if any of
        // these arbitrary keys conflict with existing PropertyModel members.
        var reservedKeys = _.intersection(['dirty'], _.keys(options));
        if (reservedKeys.length) {
            throw new Error("Keys disallowed in PropertyModel: " + reservedKeys.join(", "));
        }

        var self = _.extend({}, options);

        self.name = options.name;
        self.value = ko.observable(options.value || '');
        self.dirty = ko.observable(false);

        return self;
    };

    // Controls the full modal UI
    var DataCorrectionsModel = function(options) {
        hqImport("hqwebapp/js/assert_properties").assert(options, ['saveUrl', 'properties'],
            ['propertyNames', 'propertyNamesUrl', 'displayProperties', 'propertyPrefix', 'propertySuffix']);
        var self = {};

        // Core data, and the order in which it should be displayed
        self.properties = {};                       // map of name => PropertyModel, populated in init
        self.propertyNames = ko.observableArray();  // populated in init, whether names were provided in options or via ajax

        // Handle modal size: small, large or full-screen, with one, two, or three columns, respectively.
        self.itemsPerColumn = 12;
        self.columnsPerPage = ko.observable(1);
        self.itemsPerPage = ko.computed(function() {
            return self.itemsPerColumn * self.columnsPerPage();
        });
        self.columnClass = ko.observable('');
        self.isFullScreenModal = ko.observable(false);
        self.isLargeModal = ko.observable(false);
        self.propertyNames.subscribe(function(newValue) {
            self.columnsPerPage(Math.min(3, Math.ceil(newValue.length / self.itemsPerColumn)));
            self.columnClass("col-sm-" + (12 / self.columnsPerPage()));
            self.isLargeModal(self.columnsPerPage() === 2);
            self.isFullScreenModal(self.columnsPerPage() === 3);
        });

        // Support for displaying different property attributes (e.g., name and id)
        self.displayProperties = _.isEmpty(options.displayProperties) ? [{ property: 'name' }] : options.displayProperties;
        self.displayProperty = ko.observable(_.first(self.displayProperties).property);
        self.updateDisplayProperty = function(newValue) {
            self.displayProperty(newValue);
            self.initQuery();
        };
        self.breakWord = function(str) {
            return str.replace(/([\/_])/g, "$1\u200B");     // eslint-disable-line no-useless-escape
        };
        var innerTemplate = _.map(self.displayProperties, function(p) {
            return _.template("<span data-bind='text: $root.breakWord(<%= property %>), visible: $root.displayProperty() === \"<%= property %>\"'>")(p);
        }).join("");
        self.propertyTemplate = {
            nodes: $("
" + (options.propertyPrefix || "") + innerTemplate + (options.propertySuffix || "") + "
"),
        };

        // Draw items on the current page, taking into account pagination and searching.
        // visibleItems is a list of whichever properties should be displayed on the current page.
        // visibleColumns is a list of lists: the same properties, but organized into columns
        // to simplify the knockout template.
        self.visibleItems = ko.observableArray([]);
        self.visibleColumns = ko.observableArray([]);
        self.render = function() {
            var added = 0,
                index = 0;

            // Remove all items
            self.visibleItems.splice(0);

            // Cycle over all items on previous pages
            while (added < self.itemsPerPage() * (self.currentPage() - 1) && index < self.propertyNames().length) {
                if (self.matchesQuery(self.propertyNames()[index])) {
                    added++;
                }
                index++;
            }

            // Add as many items as fit on a page
            added = 0;
            while (added < self.itemsPerPage() && index < self.propertyNames().length) {
                if (self.matchesQuery(self.propertyNames()[index])) {
                    var name = self.propertyNames()[index];
                    if (!self.properties[name]) {
                        self.properties[name] = new PropertyModel({ name: name });
                    }
                    self.visibleItems.push(self.properties[name]);
                    added++;
                }
                index++;
            }

            // Break visibleItems into separate columns for rendering
            self.visibleColumns.splice(0);
            var itemsPerColumn = self.itemsPerPage() / self.columnsPerPage();
            for (var i = 0; i < self.itemsPerPage(); i += itemsPerColumn) {
                self.visibleColumns.push(self.visibleItems.slice(i, i + itemsPerColumn));
            }
        };

        // Pagination
        self.currentPage = ko.observable();
        self.totalPages = ko.observable();  // observable because it will change if there's a search query
        self.incrementPage = function(increment) {
            var newCurrentPage = self.currentPage() + increment;
            if (newCurrentPage <= 0 || newCurrentPage > self.totalPages()) {
                return;
            }
            self.currentPage(newCurrentPage);
        };

        // Track an array of page numbers, e.g., [1, 2, 3], used by the pagination UI.
        // Having it as an array makes knockout rendering simpler.
        self.visiblePages = ko.observableArray([]);
        self.totalPages.subscribe(function(newValue) {
            self.visiblePages(_.map(_.range(newValue), function(p) { return p + 1; }));
        });
        self.currentPage.subscribe(self.render);

        // Search
        self.query = ko.observable();
        self.matchesQuery = function(propertyName) {
            return !self.query() || propertyName.toLowerCase().indexOf(self.query().toLowerCase()) !== -1;
        };
        self.initQuery = function() {
            self.query("");
        };
        self.query.subscribe(function() {
            self.currentPage(1);
            self.totalPages(Math.ceil(_.filter(self.propertyNames(), self.matchesQuery).length / self.itemsPerPage()) || 1);
            self.render();
        });

        // Saving
        self.submitForm = function(model, e) {
            var $button = $(e.currentTarget);
            $button.disableButton();
            $.post({
                url: options.saveUrl,
                data: _.mapObject(self.properties, function(model) {
                    return model.value();
                }),
                success: function() {
                    window.location.reload();
                },
                error: function() {
                    $button.enableButton();
                    self.showRetry(true);
                },
            });
            return true;
        };

        // Control visibility around loading (spinner is shown if names are fetched via ajax) and error handling.
        self.showSpinner = ko.observable(true);
        self.showPagination = ko.computed(function() {
            return !self.showSpinner() && self.propertyNames().length > self.itemsPerPage();
        });
        self.showError = ko.observable(false);
        self.showRetry = ko.observable(false);
        self.disallowSave = ko.computed(function() {
            return self.showSpinner() || self.showError();
        });
        self.showNoData = ko.computed(function() {
            return !self.showError() && self.visibleItems().length === 0;
        });

        // Setup to do once property names exist
        self.init = function() {
            self.properties = _.extend({}, _.mapObject(options.properties, function(data, name) {
                if (typeof(data) === "string") {
                    data = { value: data };
                }
                return new PropertyModel(_.extend({}, data, {
                    name: name,
                    value: data.value,
                    display: _.without(data, 'name', 'value'),
                }));
            }));
            self.initQuery();
            self.currentPage(1);
            self.showError(false);
            self.showRetry(false);
            self.render();
        };

        // Initialization: fetch property names if needed
        var _loadPropertyNames = function(names) {
            _.each(names, function(name) {
                self.propertyNames.push(name);
            });
            self.showSpinner(false);
            self.init();
        };
        if (options.propertyNamesUrl) {
            $.get({
                url: options.propertyNamesUrl,
                success: _loadPropertyNames,
                error: function() {
                    self.showSpinner(false);
                    self.showError(true);
                },
            });
        } else {
            _loadPropertyNames(options.propertyNames || _.keys(options.properties));
        }

        return self;
    };

    var init = function($trigger, $modal, options) {
        var model = undefined;
        if ($trigger.length && $modal.length) {
            $trigger.click(function() {
                $modal.modal();
            });
            model = new DataCorrectionsModel(options);
            $modal.koApplyBindings(model);
        }
        return model;
    };

    return {
        init: init,
    };
});
<|endoftext|>


<commit_before>Phase1.Rock = {
	itemRock9075 : "assets/phase1/images/rock_90-75.png",
	itemRock92230 : "assets/phase1/images/rock_92-230.png",
	itemRock10035 : "assets/phase1/images/rock_100-35.png",
	itemRock12575 : "assets/phase1/images/rock_125-75.png",
	itemRock22085 : "assets/phase1/images/rock_220-85.png",
	itemRock37085 : "assets/phase1/images/rock_370-85.png",
	itemRock371108 : "assets/phase1/images/rock_371-108.png",
	itemRock412190 : "assets/phase1/images/rock_412-190.png",
	itemRock500100 : "assets/phase1/images/rock_500-100.png",
	itemRock515130 : "assets/phase1/images/rock_515-130.png",
	itemRock540190 : "assets/phase1/images/rock_540-190.png",
	itemRock550165 : "assets/phase1/images/rock_550-165.png",
	itemRock655230 : "assets/phase1/images/rock_655-230.png",
	itemRock825100 : "assets/phase1/images/rock_825-100.png",
	itemRock925220 : "assets/phase1/images/rock_925-220.png",
	init : function(){
		this.game.load.image('itemRock9075', this.itemRock9075);
		this.game.load.image('itemRock92230', this.itemRock92230);
		this.game.load.image('itemRock10035', this.itemRock10035);
		this.game.load.image('itemRock12575', this.itemRock12575);
		this.game.load.image('itemRock22085', this.itemRock22085);
		this.game.load.image('itemRock37085', this.itemRock37085);
		this.game.load.image('itemRock371108', this.itemRock371108);
		this.game.load.image('itemRock412190', this.itemRock412190);
		this.game.load.image('itemRock500100', this.itemRock500100);
		this.game.load.image('itemRock515130', this.itemRock515130);
		this.game.load.image('itemRock540190', this.itemRock540190);
		this.game.load.image('itemRock550165', this.itemRock550165);
		this.game.load.image('itemRock655230', this.itemRock655230);
		this.game.load.image('itemRock825100', this.itemRock825100);
		this.game.load.image('itemRock925220', this.itemRock925220);
	},
	create : function(){
		this.rocks = [];
		this.rocks[0] = this.game.add.sprite(1764, 1772, 'itemRock9075');
		this.rocks[1] = this.game.add.sprite(0, 1620, 'itemRock92230');
		this.rocks[2] = this.game.add.sprite(212, 406, 'itemRock10035');
		this.rocks[3] = this.game.add.sprite(348, 504, 'itemRock10035');
		this.rocks[4] = this.game.add.sprite(245, 852, 'itemRock10035');
		this.rocks[5] = this.game.add.sprite(2510, 1413, 'itemRock10035');
		this.rocks[6] = this.game.add.sprite(2638, 1505, 'itemRock10035');
		this.rocks[7] = this.game.add.sprite(2792, 1582, 'itemRock10035');
		this.rocks[8] = this.game.add.sprite(1336, 1772, 'itemRock12575');
		this.rocks[9] = this.game.add.sprite(1538, 1772, 'itemRock12575');
		this.rocks[10] = this.game.add.sprite(0, 240, 'itemRock22085');
		this.rocks[11] = this.game.add.sprite(1383, 1275, 'itemRock37085');
		this.rocks[12] = this.game.add.sprite(2634, 1737, 'itemRock371108');
		this.rocks[13] = this.game.add.sprite(99, 1659, 'itemRock412190');
		this.rocks[14] = this.game.add.sprite(1996, 1256, 'itemRock500100');
		this.rocks[15] = this.game.add.sprite(1986, 1716, 'itemRock515130');
		this.rocks[16] = this.game.add.sprite(738, 1170, 'itemRock540190');
		this.rocks[17] = this.game.add.sprite(681, 1686, 'itemRock550165');
		this.rocks[18] = this.game.add.sprite(0, 1021, 'itemRock655230');
		this.rocks[19] = this.foorTheDoors = this.game.add.sprite(3000, 354, 'itemRock655230');
		this.foorTheDoors.scale.x *= -1;
		this.rocks[20] = this.game.add.sprite(1552, 704, 'itemRock825100');
		this.rocks[21] = this.game.add.sprite(478, 587, 'itemRock925220');

		this.enableTile();

		return this.rocks;
	},
	enableTile : function(){
		for(var x = 0; x < 21; x++ ){
			this.game.physics.ninja.enableTile(this.rocks[x], this.rocks[x].frame);
		}
	},
	collide : function(player){
		for(var x = 0; x < 21; x++){
			this.game.physics.ninja.collide(player, this.rocks[x]);
		}
	}
};<commit_msg>faltou colidir com uma pedra<commit_after>Phase1.Rock = {
	itemRock9075 : "assets/phase1/images/rock_90-75.png",
	itemRock92230 : "assets/phase1/images/rock_92-230.png",
	itemRock10035 : "assets/phase1/images/rock_100-35.png",
	itemRock12575 : "assets/phase1/images/rock_125-75.png",
	itemRock22085 : "assets/phase1/images/rock_220-85.png",
	itemRock37085 : "assets/phase1/images/rock_370-85.png",
	itemRock371108 : "assets/phase1/images/rock_371-108.png",
	itemRock412190 : "assets/phase1/images/rock_412-190.png",
	itemRock500100 : "assets/phase1/images/rock_500-100.png",
	itemRock515130 : "assets/phase1/images/rock_515-130.png",
	itemRock540190 : "assets/phase1/images/rock_540-190.png",
	itemRock550165 : "assets/phase1/images/rock_550-165.png",
	itemRock655230 : "assets/phase1/images/rock_655-230.png",
	itemRock825100 : "assets/phase1/images/rock_825-100.png",
	itemRock925220 : "assets/phase1/images/rock_925-220.png",
	init : function(){
		this.game.load.image('itemRock9075', this.itemRock9075);
		this.game.load.image('itemRock92230', this.itemRock92230);
		this.game.load.image('itemRock10035', this.itemRock10035);
		this.game.load.image('itemRock12575', this.itemRock12575);
		this.game.load.image('itemRock22085', this.itemRock22085);
		this.game.load.image('itemRock37085', this.itemRock37085);
		this.game.load.image('itemRock371108', this.itemRock371108);
		this.game.load.image('itemRock412190', this.itemRock412190);
		this.game.load.image('itemRock500100', this.itemRock500100);
		this.game.load.image('itemRock515130', this.itemRock515130);
		this.game.load.image('itemRock540190', this.itemRock540190);
		this.game.load.image('itemRock550165', this.itemRock550165);
		this.game.load.image('itemRock655230', this.itemRock655230);
		this.game.load.image('itemRock825100', this.itemRock825100);
		this.game.load.image('itemRock925220', this.itemRock925220);
	},
	create : function(){
		this.rocks = [];
		this.rocks[0] = this.game.add.sprite(1764, 1772, 'itemRock9075');
		this.rocks[1] = this.game.add.sprite(0, 1620, 'itemRock92230');
		this.rocks[2] = this.game.add.sprite(212, 406, 'itemRock10035');
		this.rocks[3] = this.game.add.sprite(348, 504, 'itemRock10035');
		this.rocks[4] = this.game.add.sprite(245, 852, 'itemRock10035');
		this.rocks[5] = this.game.add.sprite(2510, 1413, 'itemRock10035');
		this.rocks[6] = this.game.add.sprite(2638, 1505, 'itemRock10035');
		this.rocks[7] = this.game.add.sprite(2792, 1582, 'itemRock10035');
		this.rocks[8] = this.game.add.sprite(1336, 1772, 'itemRock12575');
		this.rocks[9] = this.game.add.sprite(1538, 1772, 'itemRock12575');
		this.rocks[10] = this.game.add.sprite(0, 240, 'itemRock22085');
		this.rocks[11] = this.game.add.sprite(1383, 1275, 'itemRock37085');
		this.rocks[12] = this.game.add.sprite(2634, 1737, 'itemRock371108');
		this.rocks[13] = this.game.add.sprite(99, 1659, 'itemRock412190');
		this.rocks[14] = this.game.add.sprite(1996, 1256, 'itemRock500100');
		this.rocks[15] = this.game.add.sprite(1986, 1716, 'itemRock515130');
		this.rocks[16] = this.game.add.sprite(738, 1170, 'itemRock540190');
		this.rocks[17] = this.game.add.sprite(681, 1686, 'itemRock550165');
		this.rocks[18] = this.game.add.sprite(0, 1021, 'itemRock655230');
		this.rocks[19] = this.foorTheDoors = this.game.add.sprite(3000, 354, 'itemRock655230');
		this.foorTheDoors.scale.x *= -1;
		this.rocks[20] = this.game.add.sprite(1552, 704, 'itemRock825100');
		this.rocks[21] = this.game.add.sprite(478, 587, 'itemRock925220');

		this.enableTile();

		return this.rocks;
	},
	enableTile : function(){
		for(var x = 0; x < 22; x++ ){
			this.game.physics.ninja.enableTile(this.rocks[x], this.rocks[x].frame);
		}
	},
	collide : function(player){
		for(var x = 0; x < 22; x++){
			this.game.physics.ninja.collide(player, this.rocks[x]);
		}
	}
};<|endoftext|>


<commit_before>/**
 * @private
 */

import * as util from 'silk-sysutils';
import events from 'events';
import net from 'net';
import createLog from 'silk-log/device';

const log = createLog('sensors');

const SENSORS_SOCKET_NAME = '/dev/socket/sensors';

/**
 * Type of sensors to interact with. For a full list of
 * sensor types please refer to the source code.
 *
 * @property {(ACCELEROMETER|LIGHT|PROXIMITY)} SENSOR_TYPE
 * @memberof silk-sensors
 */
let SENSOR_TYPE = {
  ACCELEROMETER: 1,
  MAGNETIC_FIELD: 2,
  ORIENTATION: 3,
  GYROSCOPE: 4,
  LIGHT: 5,
  PRESSURE: 6,
  TEMPERATURE: 7,
  PROXIMITY: 8,
  GRAVITY: 9,
  LINEAR_ACCELERATION: 10,
  ROTATION_VECTOR: 11,
  RELATIVE_HUMIDITY: 12,
  AMBIENT_TEMPERATURE: 13,
  MAGNETIC_FIELD_UNCALIBRATED: 14,
  GAME_ROTATION_VECTOR: 15,
  GYROSCOPE_UNCALIBRATED: 16,
  SIGNIFICANT_MOTION: 17,
  STEP_DETECTOR: 18,
  STEP_COUNTER: 19,
  GEOMAGNETIC_ROTATION_VECTOR: 20,
  HEART_RATE: 21,
  TILT_DETECTOR: 22,
  WAKE_GESTURE: 23,
  GLANCE_GESTURE: 24,
  PICK_UP_GESTURE: 25
};

/**
 * This module exposes functionality to activate/deactivate and read sensors
 * data from various sensors available on the device.
 * @module silk-sensors
 * @example
 * const sensors = require('silk-sensors');
 *
 * sensors.init();
 * sensors.on('initialize', () => {
 *  sensors.activate(sensors.SENSOR_TYPE.LIGHT, 250);
 * });
 * sesnors.on('data', (sensorEvent) => {
 *  log.info('Sensor type: ', + sensorEvent.sensorType);
 *  log.info('Sensor values: + sensorEvent.values');
 * });
 */
class Sensors extends events.EventEmitter {
  constructor() {
    super();
    this._ready = false;
    this._active = false;
  }

  /**
   * Initialize the Sensors module
   * @memberof silk-sensors
   */
  init() {
    log.verbose(`connecting to ${SENSORS_SOCKET_NAME} socket`);
    this._socket = net.createConnection(SENSORS_SOCKET_NAME, () => {
      log.verbose(`connected to ${SENSORS_SOCKET_NAME} socket`);
      this._buffer = '';
      this._ready = true;
      this._command({cmdName: 'ready'});
    });
    this._socket.on('data', data => this._onData(data));
    this._socket.on('error', err => {
      this._restart(`sensors error, reason=${err}`);
    });
    this._socket.on('close', hadError => {
      if (!hadError) {
        this._restart(`sensors close`);
      }
    });
  }

  /**
   * Activate a given sensor
   *
   * @param {number} sensorType SENSOR_TYPE.* constant
   * @param {number} rate Sensor polling rate in milliseconds
   * @memberof silk-sensors
   */
  activate(sensorType, rate) {
    this._active = true;
    this._command({cmdName: 'activate', sensorType, rate});
  }

  /**
   * Dectivate a given sensor
   *
   * @param {number} sensorType SENSOR_TYPE.* constant
   * @memberof silk-sensors
   */
  deactivate(sensorType) {
    this._active = false;
    this._command({cmdName: 'deactivate', sensorType});
  }

  /**
   * @private
   */
  _restart(why) {
    if (this._socket) {
      this._ready = false;
      this._socket = null;

      log.info(`sensors restart: ${why}`);
      util.timeout(1000)
      .then(() => this.init())
      .catch(util.processthrow);
      return;
    }
    log.info(`sensors restart pending (ignored "${why}")`);
  }

  /**
   * @private
   */
  _onData(data) {
    log.debug(`received data: ${data.toString()}`);
    this._buffer += data.toString();

    let nullByte;
    while ((nullByte = this._buffer.indexOf('\0')) !== -1) {
      let line = this._buffer.substring(0, nullByte);
      this._buffer = this._buffer.substring(nullByte + 1);

      let found;
      if ((found = line.match(/^([\d]*) (.*)/))) {
        line = found[2];
      }

      let sensorEvent = JSON.parse(line);
      if (sensorEvent.eventName === 'initialized') {
        /**
         * `initialize` event. This event is emitted when sensor class is initialized
         * and is ready to activate/decativate a sensor.
         *
         * @event initialize
         * @memberof silk-sensors
         */
        this.emit('initialize');
      } else if (sensorEvent.eventName === 'activated') {
        this.emit('activate');
        this._command({cmdName: 'poll'}); // Start polling
      } else if (sensorEvent.eventName === 'deactivated') {
        this.emit('deactivate');
      } else if (sensorEvent.eventName === 'error') {
        log.warn('Received "error" from sensor service');
      } else if (sensorEvent.eventName === 'data') {
        /**
         * `data` event. This event is emitted when sensor data is available.
         *
         * @event data
         * @property {number} sensorType One of the SENSOR_TYPE.* constants
         * @property {*[]} values: Sensor data as returned by the hardware sensor. The length and
         *           contents of the values array depends on which sensor type is being
         *           monitored. Please refer to `hardware/sensors.h` for details.
         * @memberof silk-sensors
         */
        this.emit('data', sensorEvent);
      } else {
        log.warn(`Error: Unknown sensors command ${line}`);
      }
    }
  }

  /**
   * @private
   */
  _command(cmd) {
    if (!this._ready) {
      log.warn(`sensors service not ready, ignoring the command`);
      return;
    }

    // sensors socket expects the command data in the following format
    const event = JSON.stringify(cmd) + '\0';
    this._socket.write(event);
    log.verbose(`sensors << ${JSON.stringify(cmd)}`);
  }
}

Sensors.prototype.SENSOR_TYPE = SENSOR_TYPE;

let sensors = new Sensors();
module.exports = sensors;
<commit_msg>Fix comment<commit_after>/**
 * @private
 */

import * as util from 'silk-sysutils';
import events from 'events';
import net from 'net';
import createLog from 'silk-log/device';

const log = createLog('sensors');

const SENSORS_SOCKET_NAME = '/dev/socket/sensors';

/**
 * Type of sensors to interact with. For a full list of
 * sensor types please refer to the source code.
 *
 * @property {(ACCELEROMETER|LIGHT|PROXIMITY)} SENSOR_TYPE
 * @memberof silk-sensors
 */
let SENSOR_TYPE = {
  ACCELEROMETER: 1,
  MAGNETIC_FIELD: 2,
  ORIENTATION: 3,
  GYROSCOPE: 4,
  LIGHT: 5,
  PRESSURE: 6,
  TEMPERATURE: 7,
  PROXIMITY: 8,
  GRAVITY: 9,
  LINEAR_ACCELERATION: 10,
  ROTATION_VECTOR: 11,
  RELATIVE_HUMIDITY: 12,
  AMBIENT_TEMPERATURE: 13,
  MAGNETIC_FIELD_UNCALIBRATED: 14,
  GAME_ROTATION_VECTOR: 15,
  GYROSCOPE_UNCALIBRATED: 16,
  SIGNIFICANT_MOTION: 17,
  STEP_DETECTOR: 18,
  STEP_COUNTER: 19,
  GEOMAGNETIC_ROTATION_VECTOR: 20,
  HEART_RATE: 21,
  TILT_DETECTOR: 22,
  WAKE_GESTURE: 23,
  GLANCE_GESTURE: 24,
  PICK_UP_GESTURE: 25
};

/**
 * This module exposes functionality to activate/deactivate and read sensors
 * data from various sensors available on the device.
 * @module silk-sensors
 * @example
 * const sensors = require('silk-sensors');
 *
 * sensors.init();
 * sensors.on('initialize', () => {
 *  sensors.activate(sensors.SENSOR_TYPE.LIGHT, 250);
 * });
 * sensors.on('data', (sensorEvent) => {
 *  log.info('Sensor type: ' + sensorEvent.sensorType);
 *  log.info('Sensor values: + sensorEvent.values');
 * });
 */
class Sensors extends events.EventEmitter {
  constructor() {
    super();
    this._ready = false;
    this._active = false;
  }

  /**
   * Initialize the Sensors module
   * @memberof silk-sensors
   */
  init() {
    log.verbose(`connecting to ${SENSORS_SOCKET_NAME} socket`);
    this._socket = net.createConnection(SENSORS_SOCKET_NAME, () => {
      log.verbose(`connected to ${SENSORS_SOCKET_NAME} socket`);
      this._buffer = '';
      this._ready = true;
      this._command({cmdName: 'ready'});
    });
    this._socket.on('data', data => this._onData(data));
    this._socket.on('error', err => {
      this._restart(`sensors error, reason=${err}`);
    });
    this._socket.on('close', hadError => {
      if (!hadError) {
        this._restart(`sensors close`);
      }
    });
  }

  /**
   * Activate a given sensor
   *
   * @param {number} sensorType SENSOR_TYPE.* constant
   * @param {number} rate Sensor polling rate in milliseconds
   * @memberof silk-sensors
   */
  activate(sensorType, rate) {
    this._active = true;
    this._command({cmdName: 'activate', sensorType, rate});
  }

  /**
   * Dectivate a given sensor
   *
   * @param {number} sensorType SENSOR_TYPE.* constant
   * @memberof silk-sensors
   */
  deactivate(sensorType) {
    this._active = false;
    this._command({cmdName: 'deactivate', sensorType});
  }

  /**
   * @private
   */
  _restart(why) {
    if (this._socket) {
      this._ready = false;
      this._socket = null;

      log.info(`sensors restart: ${why}`);
      util.timeout(1000)
      .then(() => this.init())
      .catch(util.processthrow);
      return;
    }
    log.info(`sensors restart pending (ignored "${why}")`);
  }

  /**
   * @private
   */
  _onData(data) {
    log.debug(`received data: ${data.toString()}`);
    this._buffer += data.toString();

    let nullByte;
    while ((nullByte = this._buffer.indexOf('\0')) !== -1) {
      let line = this._buffer.substring(0, nullByte);
      this._buffer = this._buffer.substring(nullByte + 1);

      let found;
      if ((found = line.match(/^([\d]*) (.*)/))) {
        line = found[2];
      }

      let sensorEvent = JSON.parse(line);
      if (sensorEvent.eventName === 'initialized') {
        /**
         * `initialize` event. This event is emitted when sensor class is initialized
         * and is ready to activate/decativate a sensor.
         *
         * @event initialize
         * @memberof silk-sensors
         */
        this.emit('initialize');
      } else if (sensorEvent.eventName === 'activated') {
        this.emit('activate');
        this._command({cmdName: 'poll'}); // Start polling
      } else if (sensorEvent.eventName === 'deactivated') {
        this.emit('deactivate');
      } else if (sensorEvent.eventName === 'error') {
        log.warn('Received "error" from sensor service');
      } else if (sensorEvent.eventName === 'data') {
        /**
         * `data` event. This event is emitted when sensor data is available.
         *
         * @event data
         * @property {number} sensorType One of the SENSOR_TYPE.* constants
         * @property {*[]} values: Sensor data as returned by the hardware sensor. The length and
         *           contents of the values array depends on which sensor type is being
         *           monitored. Please refer to `hardware/sensors.h` for details.
         * @memberof silk-sensors
         */
        this.emit('data', sensorEvent);
      } else {
        log.warn(`Error: Unknown sensors command ${line}`);
      }
    }
  }

  /**
   * @private
   */
  _command(cmd) {
    if (!this._ready) {
      log.warn(`sensors service not ready, ignoring the command`);
      return;
    }

    // sensors socket expects the command data in the following format
    const event = JSON.stringify(cmd) + '\0';
    this._socket.write(event);
    log.verbose(`sensors << ${JSON.stringify(cmd)}`);
  }
}

Sensors.prototype.SENSOR_TYPE = SENSOR_TYPE;

let sensors = new Sensors();
module.exports = sensors;
<|endoftext|>


<commit_before>// Constants
var IMG_URL_KEY = "imgurl=";
var SEARCH_BOX_ID = "gsfi";

// Initial Vars
var links = document.getElementsByTagName('a')
var processedGifs = {};

function getGifUrl(link) {
  var href = link.href + "";
  var start = href.indexOf(IMG_URL_KEY) + IMG_URL_KEY.length
  href = href.substr(start)
  var end = href.indexOf("&")
  href = href.substr(0,end)
  return decodeURIComponent(href);
}

// extract the relevant image and replace the source of the child image
function replaceGif(link) {
  var gifUrl = getGifUrl(link);
  try {
    var currentSearch = document.getElementsByClassName(SEARCH_BOX_ID)[0].value
  } catch(e) {
    var currentSearch = "";
  }
  var key = gifUrl + currentSearch
  if (processedGifs[key] == undefined) {
    link.children[0].src = gifUrl;
    processedGifs[key] = true;
  }
}

// changes all the gifs on the page
function main() {
  for(var i=0; i<links.length;i++) {
    var link = links[i];
    var href = link.href
    if (href.indexOf(IMG_URL_KEY) !== -1) {
      replaceGif(link);
    }
  }
}

main();

setInterval(main,500);
setTimeout(main,100);
<commit_msg>call decodeURIComponent twice because javascript is ???<commit_after>// Constants
var IMG_URL_KEY = "imgurl=";
var SEARCH_BOX_ID = "gsfi";

// Initial Vars
var links = document.getElementsByTagName('a')
var processedGifs = {};

function getGifUrl(link) {
  var href = link.href + "";
  var start = href.indexOf(IMG_URL_KEY) + IMG_URL_KEY.length
  href = href.substr(start)
  var end = href.indexOf("&")
  href = href.substr(0,end)
  // yes, decodeURIComponent should be called twice, I don't know why :(
  return decodeURIComponent(decodeURIComponent(href));
}

// extract the relevant image and replace the source of the child image
function replaceGif(link) {
  var gifUrl = getGifUrl(link);
  try {
    var currentSearch = document.getElementsByClassName(SEARCH_BOX_ID)[0].value
  } catch(e) {
    var currentSearch = "";
  }
  var key = gifUrl + currentSearch
  if (processedGifs[key] == undefined) {
    link.children[0].src = gifUrl;
    processedGifs[key] = true;
  }
}

// changes all the gifs on the page
function main() {
  for(var i=0; i<links.length;i++) {
    var link = links[i];
    var href = link.href
    if (href.indexOf(IMG_URL_KEY) !== -1) {
      replaceGif(link);
    }
  }
}

main();

setInterval(main,500);
setTimeout(main,100);
<|endoftext|>


<commit_before>module.exports = require('./dist/inspect')
<commit_msg>Fix import inspect -> inject<commit_after>module.exports = require('./dist/inject')
<|endoftext|>


<commit_before>'use strict'
function isEmpty (o) {
  for(var k in o) return false
  return true
}

function isInteger (i) {
  return Number.isInteger(i)
}

var Notify = require('pull-notify')
var pull = require('pull-stream')
var isBlobId = require('ssb-ref').isBlob

var MB = 1024*1024
var MAX_SIZE = 5*MB

function noop () {}

function clone (obj) {
  var o = {}
  for(var k in obj)
    o[k] = obj[k]
  return o
}

module.exports = function inject (blobs, name) {

  var notify = Notify()
  var pushed = Notify()

  var peers = {}
  var want = {}, push = {}, waiting = {}, getting = {}
  var available = {}, streams = {}
  var send = {}, timer

  function queue (hash, hops) {
    if(hops < 0)
      want[hash] = hops
    else
      delete want[hash]

    send[hash] = hops
    //setImmediate(function () {
      var _send = send
      send = {}
      notify(_send)
    //})
  }

  function isAvailable(id) {
    for(var peer in peers)
      if(available[peer] && available[peer][id] && peers[peer])
        return peer
  }

  function get (peer, id, name) {
    if(getting[id] || !peers[peer]) return

    getting[id] = peer
    //XXX REINSTATE MAXIMUM SIZE BLOBS!
//    var source = peers[peer].blobs.get({id: id, max: 5*1024*1024})
    var source = peers[peer].blobs.get(id)
    pull(source, blobs.add(id, function (err, _id) {
      delete getting[id]
      if(err) {
        if(available[peer]) delete available[peer][id]
        //check if another peer has this.
        //if so get it from them.
        if(peer = isAvailable(id)) get(peer, id, name)
      }
    }))
  }

  function wants (peer, id, hops) {
    if(!want[id] || want[id] < hops) {
      want[id] = hops
      queue(id, hops)
      if(peer = isAvailable(id)) {
        get(peer, id)
      }
    }
  }

  pull(
    blobs.ls({old: false, meta: true}),
    pull.drain(function (data) {
      queue(data.id, data.size)
      delete want[data.id]
      if(waiting[data.id])
        while(waiting[data.id].length)
          waiting[data.id].shift()(null, true)
    })
  )

  function has(peer_id, id, size) {
    if('string' !== typeof peer_id) throw new Error('peer must be string id')
    available[peer_id] = available[peer_id] || {}
    available[peer_id][id] = size
    //XXX if we are broadcasting this blob,
    //mark this peer has it.
    //if N peers have it, we can stop broadcasting.
    if(push[id]) {
      push[id][peer_id] = size
      if(Object.keys(push[id]).length >= MIN_PUSH_PEERS) {
        var data = {key: id, peers: push[id]}
        //XXX: CLEAR THIS JOB FROM THE DURABLE QUEUE!
        delete push[id]; pushed(data)
      }
    }
    if(want[id] && !getting[id] && size < MAX_SIZE) get(peer_id, id)
  }

  function process (data, peer, cb) {
    var n = 0, res = {}
    for(var id in data) {
      if(isBlobId(id) && isInteger(data[id])) {
        if(data[id] <= 0) { //interpret as "WANT"
          n++
          //check whether we already *HAVE* this file.
          //respond with it's size, if we do.
          blobs.size(id, function (err, size) { //XXX
            if(size) res[id] = size
            else wants(peer, id, data[id] - 1)
            next()
          })
        }
        else if(data[id] > 0) { //interpret as "HAS"
          has(peer, id, data[id])
        }
      }
    }

    function next () {
      if(--n) return
      cb(null, res)
    }
  }

  function dead (peer_id) {
    delete peers[peer_id]
    delete available[peer_id]
    delete streams[peer_id]
  }

  function legacySync (peer) {
    var drain //we need to keep a reference to drain
              //so we can abort it when we get an error.
    function hasLegacy (hashes) {
      var ary = Object.keys(hashes).filter(function (k) {
        return hashes[k] < 0
      })
      if(ary.length)
        peer.blobs.has(ary, function (err, haves) {
          if(err) drain.abort(err) //ERROR: abort this stream.
          else haves.forEach(function (have, i) {
            if(have) has(peer.id, ary[i], have)
          })
        })
    }

    function notPeer (err) {
      if(err) dead(peer.id)
    }

    drain = pull.drain(function (hash) {
      has(peer.id, hash, true)
    }, notPeer)

    pull(peer.blobs.changes(), drain)

    hasLegacy(want)

    //a stream of hashes
    pull(notify.listen(), pull.drain(hasLegacy, notPeer))
  }

  function createWantStream (id) {
    if(!streams[id]) {
      console.log("WANT", want)
      streams[id] = notify.listen()
      streams[id].push(clone(want))
      return streams[id]
    }
    return streams[id]
  }

  function wantSink (peer) {
    createWantStream(peer.id) //set streams[peer.id]
//    if(!streams[peer.id]) {
//      console.log('CREATE STERAM')
//      streams[peer.id] = notify.listen()
//    }

    var modern = false
    return pull.drain(function (data) {
        modern = true
        //respond with list of blobs you already have,
        process(data, peer.id, function (err, has_data) {
          //(if you have any)
          if(!isEmpty(has_data)) streams[peer.id].push(has_data)
        })
      }, function (err) {
        if(err && !modern) {
          streams[peer.id] = false
          legacySync(peer)
        }
        //handle error and fallback to legacy mode.
        else if(peers[peer.id] == peer) {
          delete peers[peer.id]
          delete available[peer.id]
          delete streams[peer.id]
        }
      })
  }

  var self
  return self = {
    has: function (hash, cb) {
      var a = Array.isArray(hash) ? hash : [hash]
      var o = {}
      a.forEach(function (h) { o[h] = -1 })
      //since this is always "has" process will never use the second arg.
      process(o, null, function () {})
      return blobs.has.call(this, hash, cb)
    },
    size: blobs.size,
    get: blobs.get,
    add: blobs.add,
    changes: function () {
      return blobs.ls({old: false, meta: false})
    },
    want: function (hash, cb) {
      //always broadcast wants immediately, because of race condition
      //between has and adding a blob (needed to pass test/async.js)
      var id = isAvailable(hash)
      if(!id) queue(hash, -1)

      if(waiting[hash])
        waiting[hash].push(cb)
      else {
        waiting[hash] = [cb]
        blobs.size(hash, function (err, has) {
          if(has) {
            while(waiting[hash].length)
              waiting[hash].shift()(null, true)
            delete waiting[hash]
          }
        })
      }
      if(id) return get(id, hash)
    },
    push: function (id) {
      push[id] = push[id] || {}
    },
    pushed: function () {
      return pushed.listen()
    },
    createWants: function () {
      return createWantStream(this.id)
      var s = streams[this.id] || (streams[this.id] = notify.listen())
//      s.push(want)
      return s
    },
    //private api. used for testing. not exposed over rpc.
    _wantSink: wantSink,
    _onConnect: function (other, name) {
      peers[other.id] = other
      //sending of your wants starts when you we know
      //that they are not legacy style.
      //process is called when wantSync
      //doesn't immediately get an error.
      pull(other.blobs.createWants(), wantSink(other))
    }
  }
}

<commit_msg>handle legacy behaviour in a way that does not create races<commit_after>'use strict'
function isEmpty (o) {
  for(var k in o) return false
  return true
}

function isInteger (i) {
  return Number.isInteger(i)
}

var Notify = require('pull-notify')
var pull = require('pull-stream')
var isBlobId = require('ssb-ref').isBlob

var MB = 1024*1024
var MAX_SIZE = 5*MB

function noop () {}

function clone (obj) {
  var o = {}
  for(var k in obj)
    o[k] = obj[k]
  return o
}

module.exports = function inject (blobs, name) {

  var notify = Notify()
  var pushed = Notify()

  var peers = {}
  var want = {}, push = {}, waiting = {}, getting = {}
  var available = {}, streams = {}
  var send = {}, timer

  function queue (hash, hops) {
    if(hops < 0)
      want[hash] = hops
    else
      delete want[hash]

    send[hash] = hops
    //setImmediate(function () {
      var _send = send
      send = {}
      notify(_send)
    //})
  }

  function isAvailable(id) {
    for(var peer in peers)
      if(available[peer] && available[peer][id] && peers[peer])
        return peer
  }

  function get (peer, id, name) {
    if(getting[id] || !peers[peer]) return

    getting[id] = peer
    //XXX REINSTATE MAXIMUM SIZE BLOBS!
//    var source = peers[peer].blobs.get({id: id, max: 5*1024*1024})
    var source = peers[peer].blobs.get(id)
    pull(source, blobs.add(id, function (err, _id) {
      delete getting[id]
      if(err) {
        if(available[peer]) delete available[peer][id]
        //check if another peer has this.
        //if so get it from them.
        if(peer = isAvailable(id)) get(peer, id, name)
      }
    }))
  }

  function wants (peer, id, hops) {
    if(!want[id] || want[id] < hops) {
      want[id] = hops
      queue(id, hops)
      if(peer = isAvailable(id)) {
        get(peer, id)
      }
    }
  }

  pull(
    blobs.ls({old: false, meta: true}),
    pull.drain(function (data) {
      queue(data.id, data.size)
      delete want[data.id]
      if(waiting[data.id])
        while(waiting[data.id].length)
          waiting[data.id].shift()(null, true)
    })
  )

  function has(peer_id, id, size) {
    if('string' !== typeof peer_id) throw new Error('peer must be string id')
    available[peer_id] = available[peer_id] || {}
    available[peer_id][id] = size
    //XXX if we are broadcasting this blob,
    //mark this peer has it.
    //if N peers have it, we can stop broadcasting.
    if(push[id]) {
      push[id][peer_id] = size
      if(Object.keys(push[id]).length >= MIN_PUSH_PEERS) {
        var data = {key: id, peers: push[id]}
        //XXX: CLEAR THIS JOB FROM THE DURABLE QUEUE!
        delete push[id]; pushed(data)
      }
    }
    if(want[id] && !getting[id] && size < MAX_SIZE) get(peer_id, id)
  }

  function process (data, peer, cb) {
    var n = 0, res = {}
    for(var id in data) {
      if(isBlobId(id) && isInteger(data[id])) {
        if(data[id] <= 0) { //interpret as "WANT"
          n++
          //check whether we already *HAVE* this file.
          //respond with it's size, if we do.
          blobs.size(id, function (err, size) { //XXX
            if(size) res[id] = size
            else wants(peer, id, data[id] - 1)
            next()
          })
        }
        else if(data[id] > 0) { //interpret as "HAS"
          has(peer, id, data[id])
        }
      }
    }

    function next () {
      if(--n) return
      cb(null, res)
    }
  }

  function dead (peer_id) {
    delete peers[peer_id]
    delete available[peer_id]
    delete streams[peer_id]
  }

  function legacySync (peer) {
    var drain //we need to keep a reference to drain
              //so we can abort it when we get an error.
    function hasLegacy (hashes) {
      var ary = Object.keys(hashes).filter(function (k) {
        return hashes[k] < 0
      })
      if(ary.length)
        peer.blobs.has(ary, function (err, haves) {
          if(err) drain.abort(err) //ERROR: abort this stream.
          else haves.forEach(function (have, i) {
            if(have) has(peer.id, ary[i], have)
          })
        })
    }

    function notPeer (err) {
      if(err) dead(peer.id)
    }

    drain = pull.drain(function (hash) {
      has(peer.id, hash, true)
    }, notPeer)

    pull(peer.blobs.changes(), drain)

    hasLegacy(want)

    //a stream of hashes
    pull(notify.listen(), pull.drain(hasLegacy, notPeer))
  }

  function createWantStream (id) {
    if(!streams[id]) {
      streams[id] = notify.listen()
      streams[id].push(clone(want))
      return streams[id]
    }
    return streams[id]
  }

  function wantSink (peer) {
    createWantStream(peer.id) //set streams[peer.id]

    var modern = false
    return pull.drain(function (data) {
        modern = true
        //respond with list of blobs you already have,
        process(data, peer.id, function (err, has_data) {
          //(if you have any)
          if(!isEmpty(has_data)) streams[peer.id].push(has_data)
        })
      }, function (err) {
        if(err && !modern) {
          streams[peer.id] = false
          legacySync(peer)
        }
        //handle error and fallback to legacy mode.
        else if(peers[peer.id] == peer) {
          delete peers[peer.id]
          delete available[peer.id]
          delete streams[peer.id]
        }
      })
  }

  var self
  return self = {
    has: function (hash, cb) {
      if(this.id === name) // a local call
        return blobs.has.call(this, hash, cb)

      //ELSE, interpret remote calls to has as a WANT request.
      //handling this by calling process (which calls size())
      //avoids a race condition in the tests.
      //(and avoids doubling the number of calls)
      var a = Array.isArray(hash) ? hash : [hash]
      var o = {}
      a.forEach(function (h) { o[h] = -1 })
      //since this is always "has" process will never use the second arg.
      process(o, null, function (err, res) {
        var a = []; for(var k in o) a.push(res[k] > 0)
        cb(null, Array.isArray(hash) ? a : a[0])
      })
    },
    size: blobs.size,
    get: blobs.get,
    add: blobs.add,
    changes: function () {
      return blobs.ls({old: false, meta: false})
    },
    want: function (hash, cb) {
      //always broadcast wants immediately, because of race condition
      //between has and adding a blob (needed to pass test/async.js)
      var id = isAvailable(hash)
      if(!id) queue(hash, -1)

      if(waiting[hash])
        waiting[hash].push(cb)
      else {
        waiting[hash] = [cb]
        blobs.size(hash, function (err, has) {
          if(has) {
            while(waiting[hash].length)
              waiting[hash].shift()(null, true)
            delete waiting[hash]
          }
        })
      }
      if(id) return get(id, hash)
    },
    push: function (id) {
      push[id] = push[id] || {}
    },
    pushed: function () {
      return pushed.listen()
    },
    createWants: function () {
      return createWantStream(this.id)
      var s = streams[this.id] || (streams[this.id] = notify.listen())
//      s.push(want)
      return s
    },
    //private api. used for testing. not exposed over rpc.
    _wantSink: wantSink,
    _onConnect: function (other, name) {
      peers[other.id] = other
      //sending of your wants starts when you we know
      //that they are not legacy style.
      //process is called when wantSync
      //doesn't immediately get an error.
      pull(other.blobs.createWants(), wantSink(other))
    }
  }
}

<|endoftext|>


<commit_before>(function (global, document, $, undefined) {
    "use strict";

    // datos del usuario
    var user_name = null;
    var fb_id = null;
    var hora = null;
    // conección a Firebase
    var Fire = new Firebase("https://saiberz.firebaseio.com/chat");
    var auth = new FirebaseSimpleLogin(Fire, function(error, user) {
        if (error) {
            console.error("An error occurred while attempting login.");
        } else if (user) {
            $("input").prop("disabled", false);
            $("input:text").prop("placeholder", "Escribe");
            user_name = user.displayName;
            fb_id = user.id;
            $("#fbl").hide(1000);
        } else {
            user_name = "Anonymous!";
        }
    });

    Fire.on("child_added", function(ss) {
        ss = ss.val();
        addmessage(ss.text, ss.name, ss.id, ss.hour);
    });

    function addmessage(m, un, user_id, hour) {
        var newdivbox = document.createElement("div");
        var newdiv1 = document.createElement("div");
        var newdiv2 = document.createElement("div");
        var timeago = moment(hour).fromNow();
        newdivbox.setAttribute("id", "container");
        var newimg = document.createElement("img");
        newimg.src = "http://graph.facebook.com/" + user_id + "/picture";
        newimg.setAttribute("id", "user-image");
        newdiv1.appendChild(newimg);
        newdiv2.setAttribute("id", "user-message");
        var text = document.createElement("p");
        text.appendChild(document.createTextNode(m));
        text.setAttribute("id", "user-comment");
        var text2 = document.createElement("span");
        text2.appendChild(document.createTextNode(" ("+timeago+")"));
        text2.setAttribute("id", "user-comment");
        var userlink = document.createElement("a");
        userlink.setAttribute("href", "http://fb.com/" + user_id);
        userlink.setAttribute("id", "user-name");
        userlink.appendChild(document.createTextNode(un));
        newdiv2.appendChild(userlink);
        newdiv2.appendChild(text2);
        newdiv2.appendChild(text);
        newdivbox.appendChild(newdiv1);
        newdivbox.appendChild(newdiv2);
        $("#chatbox").prepend(newdivbox);
    }

    // event handlers

    $("#fblo").click(function() {
        if(confirm("Are you sure")) {
            auth.logout();
        }
    });

    $("#fbl").click(function() {
        auth.login("facebook");
    });

    $("#message-input").keypress(function (event) {
        var ENTER_KEY = 13;
        if (event.keyCode === ENTER_KEY) {
            if($("#message-input").val() !== "") {
                var text = $("#message-input").val();
                var hour = moment().format();
                Fire.push({name:user_name, text:text, id:fb_id,hour: hour});
                $("#message-input").val("");
            }
        }
    });

})(window, window.document, window.jQuery);
<commit_msg>No write if not login<commit_after>(function (global, document, $, undefined) {
    "use strict";

    // datos del usuario
    var user_name = null;
    var fb_id = null;
    var hora = null;
    // conección a Firebase
    var Fire = new Firebase("https://saiberz.firebaseio.com/chat");
    var auth = new FirebaseSimpleLogin(Fire, function(error, user) {
        if (error) {
            console.error("An error occurred while attempting login.");
        } else if (user) {
            $("input").prop("disabled", false);
            $("input:text").prop("placeholder", "Escribe");
            user_name = user.displayName;
            fb_id = user.id;
            $("#fbl").hide(1000);
        } else {
            user_name = "Anonymous!";
        }
    });

    Fire.on("child_added", function(ss) {
        ss = ss.val();
        addmessage(ss.text, ss.name, ss.id, ss.hour);
    });

    function addmessage(m, un, user_id, hour) {
        var newdivbox = document.createElement("div");
        var newdiv1 = document.createElement("div");
        var newdiv2 = document.createElement("div");
        var timeago = moment(hour).fromNow();
        newdivbox.setAttribute("id", "container");
        var newimg = document.createElement("img");
        newimg.src = "http://graph.facebook.com/" + user_id + "/picture";
        newimg.setAttribute("id", "user-image");
        newdiv1.appendChild(newimg);
        newdiv2.setAttribute("id", "user-message");
        var text = document.createElement("p");
        text.appendChild(document.createTextNode(m));
        text.setAttribute("id", "user-comment");
        var text2 = document.createElement("span");
        text2.appendChild(document.createTextNode(" ("+timeago+")"));
        text2.setAttribute("id", "user-comment");
        var userlink = document.createElement("a");
        userlink.setAttribute("href", "http://fb.com/" + user_id);
        userlink.setAttribute("id", "user-name");
        userlink.appendChild(document.createTextNode(un));
        newdiv2.appendChild(userlink);
        newdiv2.appendChild(text2);
        newdiv2.appendChild(text);
        newdivbox.appendChild(newdiv1);
        newdivbox.appendChild(newdiv2);
        $("#chatbox").prepend(newdivbox);
    }

    // event handlers

    $("#fblo").click(function() {
        if(confirm("Are you sure")) {
            auth.logout();
            $("input").prop("disabled", true);
        }
    });

    $("#fbl").click(function() {
        auth.login("facebook");
    });

    $("#message-input").keypress(function (event) {
        var ENTER_KEY = 13;
        if (event.keyCode === ENTER_KEY) {
            if($("#message-input").val() !== "") {
                var text = $("#message-input").val();
                var hour = moment().format();
                Fire.push({name:user_name, text:text, id:fb_id,hour: hour});
                $("#message-input").val("");
            }
        }
    });

})(window, window.document, window.jQuery);
<|endoftext|>


<commit_before>"use strict";
Relive.useSingleton = false;

window.addEventListener("load", function ()
{
    var load, latest = [];

    Element.prototype.text = function (text)
    {
        return text && (this.textContent = text) && this || this.textContent;
    };

    Object.toArray = function (obj)
    {
        return Object.keys(obj)
            .reduce(function (list, key)
            {
                list.push(obj[key]);
                return list;
            }, []);
    };

    // Display timestamp, title, host + info text, and how long the show is.
    // timestamp  | Title
    // showlength | host - info text
    function entry (stream)
    {
        var date, time, seconds, minutes, hours, length, info, title, entry;

        date = new Date(stream.timestamp * 1000).toISOString();
        time = document.createElement("time");
        time.setAttribute("datetime", date);
        time.textContent = date.replace("T", " ").replace(/\..+$/, "");

        hours = Math.floor(stream.length / 3600);
        minutes = Math.floor((stream.length - hours * 3600) / 60);
        seconds = Math.floor((stream.length - hours * 3600 - minutes * 60));

        length = document.createElement("span");
        length.textContent = ((hours ? hours + "h " : "") + (minutes ? minutes + "m " : "") +
                (seconds ? seconds + "s" : ""));

        info = document.createElement("span");
        info.textContent = stream.host + " - " + stream.infoText;

        title = document.createElement("h3");
        title.textContent = stream.name + " ";
        title.appendChild(info);

        entry = document.createElement("li");
        entry.appendChild(time);
        entry.appendChild(length);
        entry.appendChild(title);

        entry.addEventListener("click", function (event)
        {
            // TODO call player.
        });

        return entry;
    }

    function latestStreams ()
    {
        var date, list, lists;

        if (latest)
        {
            lists = document.getElementById("lists");

            date = new Date();
            date = date.setMonth(date.getMonth() - 3);

            list = lists.children[1];
            list.setAttribute("reversed", "reversed");

            latest.sort(function (a, b)
            {
                return b.timestamp - a.timestamp;
            })
            .every(function (stream, i)
            {
                list.appendChild(entry(stream));
console.log(stream);
                return stream.timestamp >= date || list.children.length < 25;
            });

            list.className = "loaded";
            latest = null;
        }
    };

    function stationInfo (station, parent)
    {
        Relive.loadStationInfo(station.id, function (info)
        {
            var list = document.createElement("ol");
            list.setAttribute("reversed", "reversed");

            // Again? :(
            info.streams = Object.toArray(info.streams)
                .sort(function (a, b)
                {
                    return b.timestamp - a.timestamp;
                });

            info.streams.forEach(function (stream)
            {
                list.appendChild(entry(stream));

                if (latest)
                {
                    latest.push(stream);
                }
            });

            if (latest)
            {
                clearTimeout(load);
                load = setTimeout(latestStreams, 2000);
            }

            parent.appendChild(list);
        });
    };

    Relive.loadStations(function (stations)
    {
        var list = document.createElement("ul");

        // Why is this an object? When will there ever be meaningful holes in the list?
        stations = Object.toArray(stations)
            .sort(function (a, b)
            {
                return a.name.localeCompare(b.name);
            });

        stations.forEach(function (station, i)
        {
            var name, entry;

            name = document.createElement("div");
            name.addEventListener("click", function (event)
            {
                entry.className = (entry.className ? "" : "open");
                entry.style.height = (entry.className ? entry.scrollHeight + "px" : "");
            });
            name.textContent = station.name;

            entry = document.createElement("li");
            entry.appendChild(name);
            list.appendChild(entry);

            stationInfo(station, entry);
        });

        document.getElementById("lists")
            .appendChild(document.createElement("ol"))
            .parentNode.appendChild(list);
    });

    document.getElementById("latest").addEventListener("click", function (event)
    {
        event.target.className = "active";
        event.target.nextElementSibling.className = "";
        document.querySelector("#lists > ol").style.marginLeft = "";
    });

    document.getElementById("stations").addEventListener("click", function (event)
    {
        event.target.className = "active";
        event.target.previousElementSibling.className = "";
        document.querySelector("#lists > ol").style.marginLeft = "-100%";
    });
});
<commit_msg>Removed log of streams.<commit_after>"use strict";
Relive.useSingleton = false;

window.addEventListener("load", function ()
{
    var load, latest = [];

    Element.prototype.text = function (text)
    {
        return text && (this.textContent = text) && this || this.textContent;
    };

    Object.toArray = function (obj)
    {
        return Object.keys(obj)
            .reduce(function (list, key)
            {
                list.push(obj[key]);
                return list;
            }, []);
    };

    // Display timestamp, title, host + info text, and how long the show is.
    // timestamp  | Title
    // showlength | host - info text
    function entry (stream)
    {
        var date, time, seconds, minutes, hours, length, info, title, entry;

        date = new Date(stream.timestamp * 1000).toISOString();
        time = document.createElement("time");
        time.setAttribute("datetime", date);
        time.textContent = date.replace("T", " ").replace(/\..+$/, "");

        hours = Math.floor(stream.length / 3600);
        minutes = Math.floor((stream.length - hours * 3600) / 60);
        seconds = Math.floor((stream.length - hours * 3600 - minutes * 60));

        length = document.createElement("span");
        length.textContent = ((hours ? hours + "h " : "") + (minutes ? minutes + "m " : "") +
                (seconds ? seconds + "s" : ""));

        info = document.createElement("span");
        info.textContent = stream.host + " - " + stream.infoText;

        title = document.createElement("h3");
        title.textContent = stream.name + " ";
        title.appendChild(info);

        entry = document.createElement("li");
        entry.appendChild(time);
        entry.appendChild(length);
        entry.appendChild(title);

        entry.addEventListener("click", function (event)
        {
            // TODO call player.
        });

        return entry;
    }

    function latestStreams ()
    {
        var date, list, lists;

        if (latest)
        {
            lists = document.getElementById("lists");

            date = new Date();
            date = date.setMonth(date.getMonth() - 3);

            list = lists.children[1];
            list.setAttribute("reversed", "reversed");

            latest.sort(function (a, b)
            {
                return b.timestamp - a.timestamp;
            })
            .every(function (stream, i)
            {
                list.appendChild(entry(stream));
                return stream.timestamp >= date || list.children.length < 25;
            });

            list.className = "loaded";
            latest = null;
        }
    };

    function stationInfo (station, parent)
    {
        Relive.loadStationInfo(station.id, function (info)
        {
            var list = document.createElement("ol");
            list.setAttribute("reversed", "reversed");

            // Again? :(
            info.streams = Object.toArray(info.streams)
                .sort(function (a, b)
                {
                    return b.timestamp - a.timestamp;
                });

            info.streams.forEach(function (stream)
            {
                list.appendChild(entry(stream));

                if (latest)
                {
                    latest.push(stream);
                }
            });

            if (latest)
            {
                clearTimeout(load);
                load = setTimeout(latestStreams, 2000);
            }

            parent.appendChild(list);
        });
    };

    Relive.loadStations(function (stations)
    {
        var list = document.createElement("ul");

        // Why is this an object? When will there ever be meaningful holes in the list?
        stations = Object.toArray(stations)
            .sort(function (a, b)
            {
                return a.name.localeCompare(b.name);
            });

        stations.forEach(function (station, i)
        {
            var name, entry;

            name = document.createElement("div");
            name.addEventListener("click", function (event)
            {
                entry.className = (entry.className ? "" : "open");
                entry.style.height = (entry.className ? entry.scrollHeight + "px" : "");
            });
            name.textContent = station.name;

            entry = document.createElement("li");
            entry.appendChild(name);
            list.appendChild(entry);

            stationInfo(station, entry);
        });

        document.getElementById("lists")
            .appendChild(document.createElement("ol"))
            .parentNode.appendChild(list);
    });

    document.getElementById("latest").addEventListener("click", function (event)
    {
        event.target.className = "active";
        event.target.nextElementSibling.className = "";
        document.querySelector("#lists > ol").style.marginLeft = "";
    });

    document.getElementById("stations").addEventListener("click", function (event)
    {
        event.target.className = "active";
        event.target.previousElementSibling.className = "";
        document.querySelector("#lists > ol").style.marginLeft = "-100%";
    });
});
<|endoftext|>