<commit_before>import Service, { inject as service } from '@ember/service';
import RSVP from 'rsvp';
import { isEmpty, isPresent } from '@ember/utils';
import { singularize, pluralize } from 'ember-inflector';
import { capitalize, camelize, dasherize } from '@ember/string';
import striptags from 'striptags';

const { filter, resolve, map } = RSVP;

export default class ReportingService extends Service {
  @service store;
  @service currentUser;
  @service intl;
  @service iliosConfig;

  // canViewCourses: computed('currentUser.performsNonLearnerFunction', async function () {
  //   const currentUser = this.currentUser;
  //   return currentUser.get('performsNonLearnerFunction');
  // });
  //
  // canViewPrograms: computed('currentUser.performsNonLearnerFunction', async function () {
  //   const currentUser = this.currentUser;
  //   return currentUser.get('performsNonLearnerFunction');
  // });

  async findResults(report) {
    const store = this.store;
    const subject = report.subject;
    const object = report.prepositionalObject;
    const objectId = report.prepositionalObjectTableRowId;
    const school = await report.school;
    return store.query(this.getModel(subject), this.getQuery(subject, object, objectId, school));
  }

  async getResults(report, year) {
    const subject = report.subject;
    const results = await this.findResults(report);
    const mapper = pluralize(camelize(subject)) + 'Results';
    const mappedResults = await this[mapper](results, year);
    return mappedResults.sortBy('value');
  }

  async getArrayResults(report, year) {
    const subject = report.get('subject');

    const results = await this.findResults(report);
    const mapper = pluralize(camelize(subject)) + 'ArrayResults';
    return this[mapper](results, year);
  }

  getModel(subject) {
    let model = dasherize(subject);
    if (model === 'instructor') {
      model = 'user';
    }
    if (model === 'mesh-term') {
      model = 'mesh-descriptor';
    }

    return model;
  }

  getQuery(subject, object, objectId, school) {
    const query = {
      filters: {},
    };

    if (object && objectId) {
      let what = pluralize(camelize(object));
      if (object === 'mesh term') {
        what = 'meshDescriptors';
      }

      if (subject === 'session') {
        const sessionSingulars = ['sessionTypes', 'courses'];
        if (sessionSingulars.includes(what)) {
          what = singularize(what);
        }
      }
      if (subject === 'instructor') {
        const specialInstructed = ['learningMaterials', 'sessionTypes', 'courses', 'sessions'];
        if (specialInstructed.includes(what)) {
          what = 'instructed' + capitalize(what);
        }
      }
      if (subject === 'learning material' && object === 'course') {
        what = 'fullCourses';
      }
      query.filters[what] = objectId;
    } else {
      if (
        subject !== 'mesh term' &&
        subject !== 'instructor' &&
        subject !== 'learning material' &&
        school
      ) {
        query.filters['schools'] = [school.id];
      }
    }

    return query;
  }

  async coursesResults(results, year) {
    const canView = await this.canViewCourses;
    const academicYearCrossesCalendarYearBoundaries = await this.iliosConfig.itemFromConfig(
      'academicYearCrossesCalendarYearBoundaries'
    );
    return results
      .map((course) => {
        const rhett = {
          course,
          value: academicYearCrossesCalendarYearBoundaries
            ? `${course.year} - ${course.year + 1} ${course.title}`
            : `${course.year} ${course.title}`,
        };

        if (isPresent(course.externalId)) {
          rhett.value += ` (${course.externalId})`;
        }
        if (canView) {
          rhett.route = 'course';
          rhett.model = course;
        }

        return rhett;
      })
      .filter((obj) => isEmpty(year) || obj.course.year === parseInt(year, 10));
  }

  async coursesArrayResults(results, year) {
    const intl = this.intl;
    const academicYearCrossesCalendarYearBoundaries = await this.iliosConfig.itemFromConfig(
      'academicYearCrossesCalendarYearBoundaries'
    );
    const filteredResults = results.filter((course) => {
      return isEmpty(year) || course.year === parseInt(year, 10);
    });
    const sortedResults = filteredResults.sortBy('title');
    const mappedResults = sortedResults.map((course) => {
      return [
        course.get('title'),
        academicYearCrossesCalendarYearBoundaries
          ? `${course.year} - ${course.year + 1}`
          : course.year.toString(),
        course.get('externalId'),
      ];
    });

    return [
      [intl.t('general.courses'), intl.t('general.academicYear'), intl.t('general.externalId')],
    ].concat(mappedResults);
  }

  async sessionsResults(results, year) {
    const academicYearCrossesCalendarYearBoundaries = await this.iliosConfig.itemFromConfig(
      'academicYearCrossesCalendarYearBoundaries'
    );
    const canView = await this.canViewCourses;
    const mappedResults = await map(results.toArray(), async (item) => {
      const course = await item.get('course');
      const rhett = { course };
      rhett.value = academicYearCrossesCalendarYearBoundaries
        ? `${course.year} - ${course.year + 1} ${course.title} ${item.title}`
        : `${course.year} ${course.title} ${item.title}`;
      if (canView) {
        rhett.route = 'session';
        rhett.model = course;
        rhett.model2 = item;
      }

      return rhett;
    });

    return mappedResults.filter(
      (obj) => isEmpty(year) || parseInt(obj.course.year, 10) === parseInt(year, 10)
    );
  }

  async sessionsArrayResults(results, year) {
    const academicYearCrossesCalendarYearBoundaries = await this.iliosConfig.itemFromConfig(
      'academicYearCrossesCalendarYearBoundaries'
    );
    const intl = this.intl;
    const filteredResults = await filter(results.toArray(), async (session) => {
      const course = await session.course;
      return isEmpty(year) || course.year === parseInt(year, 10);
    });
    const sortedResults = filteredResults.sortBy('title');
    const mappedResults = await map(sortedResults, async (session) => {
      const course = await session.course;
      const sessionDescriptionText = session.textDescription;
      const objectives = await session.sessionObjectives;
      return [
        session.get('title'),
        course.get('title'),
        academicYearCrossesCalendarYearBoundaries
          ? `${course.year} - ${course.year + 1}`
          : course.year.toString(),
        sessionDescriptionText,
        striptags(objectives.mapBy('title').join()),
      ];
    });

    return [
      [
        intl.t('general.session'),
        intl.t('general.course'),
        intl.t('general.academicYear'),
        intl.t('general.description'),
        intl.t('general.objectives'),
      ],
    ].concat(mappedResults);
  }

  async programsResults(results) {
    const canView = await this.canViewPrograms;
    return await map(results.toArray(), async (item) => {
      const rhett = {};
      const school = await item.get('school');
      rhett.value = school.get('title') + ': ' + item.get('title');
      if (canView) {
        rhett.route = 'program';
        rhett.model = item;
      }
      return rhett;
    });
  }

  async programsArrayResults(results) {
    const intl = this.intl;
    const sortedResults = results.sortBy('title');
    const mappedResults = await map(sortedResults.toArray(), async (program) => {
      const school = await program.get('school');
      return [program.get('title'), school.get('title')];
    });

    return [[intl.t('general.program'), intl.t('general.school')]].concat(mappedResults);
  }

  async programYearsResults(results) {
    const canView = await this.canViewPrograms;
    return map(results.toArray(), async (programYear) => {
      const rhett = {};
      const program = await programYear.program;
      const school = await programYear.school;
      const classOfYear = await programYear.getClassOfYear();

      rhett.value = school.get('title') + ' ' + program.get('title') + ' ' + classOfYear;
      if (canView) {
        rhett.route = 'programYear';
        rhett.model = program;
        rhett.model2 = programYear;
      }
      return rhett;
    });
  }

  async programYearsArrayResults(results) {
    const intl = this.intl;
    const resultsWithClassOfYear = await map(results, async (programYear) => {
      const classOfYear = await programYear.getClassOfYear();
      return {
        programYear,
        classOfYear,
      };
    });
    const sortedResults = resultsWithClassOfYear.sortBy('classOfYear');
    const mappedResults = await map(
      sortedResults.toArray(),
      async ({ programYear, classOfYear }) => {
        const program = await programYear.get('program');
        const school = await program.get('school');
        return [classOfYear, program.get('title'), school.get('title')];
      }
    );

    return [[intl.t('general.year'), intl.t('general.program'), intl.t('general.school')]].concat(
      mappedResults
    );
  }

  instructorsResults(results) {
    const mappedResults = results.map((result) => {
      return {
        value: result.get('fullName'),
      };
    });
    return resolve(mappedResults);
  }

  async instructorsArrayResults(results) {
    const intl = this.intl;
    const arr = await this.instructorsResults(results);
    const sortedResults = arr.sortBy('value');
    const mappedResults = sortedResults.map((obj) => [obj.value]);
    return [[intl.t('general.instructors')]].concat(mappedResults);
  }

  titleResults(results) {
    const mappedResults = results.map((result) => {
      return {
        value: result.get('title'),
      };
    });
    return resolve(mappedResults);
  }

  async valueResults(results, translationKey) {
    const intl = this.intl;
    const arr = await this.titleResults(results);
    const sortedResults = arr.sortBy('value');
    const mappedResults = sortedResults.map((obj) => [obj.value]);
    return [[intl.t(translationKey)]].concat(mappedResults);
  }

  instructorGroupsResults(results) {
    return this.titleResults(results);
  }

  async instructorGroupsArrayResults(results) {
    return this.valueResults(results, 'general.instructorGroups');
  }

  learningMaterialsResults(results) {
    return this.titleResults(results);
  }

  async learningMaterialsArrayResults(results) {
    return this.valueResults(results, 'general.learningMaterials');
  }

  competenciesResults(results) {
    return this.titleResults(results);
  }

  async competenciesArrayResults(results) {
    return this.valueResults(results, 'general.competencies');
  }

  sessionTypesResults(results) {
    return this.titleResults(results);
  }

  async sessionTypesArrayResults(results) {
    return this.valueResults(results, 'general.sessionTypes');
  }

  meshTermsResults(results) {
    const mappedResults = results.map((result) => {
      return {
        value: result.get('name'),
      };
    });
    return resolve(mappedResults);
  }

  async meshTermsArrayResults(results) {
    const intl = this.intl;
    const arr = await this.meshTermsResults(results);
    const sortedResults = arr.sortBy('value');
    const mappedResults = sortedResults.map((obj) => [obj.value]);
    return [[intl.t('general.meshTerms')]].concat(mappedResults);
  }

  async termsResults(results) {
    return map(results.toArray(), async (term) => {
      const vocabulary = await term.get('vocabulary');
      const titleWithParentTitles = await term.get('titleWithParentTitles');
      const value = vocabulary.get('title') + ' > ' + titleWithParentTitles;
      return { value };
    });
  }

  async termsArrayResults(results) {
    const intl = this.intl;
    const arr = await this.termsResults(results);
    const sortedResults = arr.sortBy('value');
    const mappedResults = sortedResults.map((obj) => [obj.value]);
    return [[intl.t('general.vocabulary')]].concat(mappedResults);
  }
}
<commit_msg>replace permissions-related CPs with direct checks against current-user getter.<commit_after>import Service, { inject as service } from '@ember/service';
import RSVP from 'rsvp';
import { isEmpty, isPresent } from '@ember/utils';
import { singularize, pluralize } from 'ember-inflector';
import { capitalize, camelize, dasherize } from '@ember/string';
import striptags from 'striptags';

const { filter, resolve, map } = RSVP;

export default class ReportingService extends Service {
  @service store;
  @service currentUser;
  @service intl;
  @service iliosConfig;

  async findResults(report) {
    const store = this.store;
    const subject = report.subject;
    const object = report.prepositionalObject;
    const objectId = report.prepositionalObjectTableRowId;
    const school = await report.school;
    return store.query(this.getModel(subject), this.getQuery(subject, object, objectId, school));
  }

  async getResults(report, year) {
    const subject = report.subject;
    const results = await this.findResults(report);
    const mapper = pluralize(camelize(subject)) + 'Results';
    const mappedResults = await this[mapper](results, year);
    return mappedResults.sortBy('value');
  }

  async getArrayResults(report, year) {
    const subject = report.get('subject');

    const results = await this.findResults(report);
    const mapper = pluralize(camelize(subject)) + 'ArrayResults';
    return this[mapper](results, year);
  }

  getModel(subject) {
    let model = dasherize(subject);
    if (model === 'instructor') {
      model = 'user';
    }
    if (model === 'mesh-term') {
      model = 'mesh-descriptor';
    }

    return model;
  }

  getQuery(subject, object, objectId, school) {
    const query = {
      filters: {},
    };

    if (object && objectId) {
      let what = pluralize(camelize(object));
      if (object === 'mesh term') {
        what = 'meshDescriptors';
      }

      if (subject === 'session') {
        const sessionSingulars = ['sessionTypes', 'courses'];
        if (sessionSingulars.includes(what)) {
          what = singularize(what);
        }
      }
      if (subject === 'instructor') {
        const specialInstructed = ['learningMaterials', 'sessionTypes', 'courses', 'sessions'];
        if (specialInstructed.includes(what)) {
          what = 'instructed' + capitalize(what);
        }
      }
      if (subject === 'learning material' && object === 'course') {
        what = 'fullCourses';
      }
      query.filters[what] = objectId;
    } else {
      if (
        subject !== 'mesh term' &&
        subject !== 'instructor' &&
        subject !== 'learning material' &&
        school
      ) {
        query.filters['schools'] = [school.id];
      }
    }

    return query;
  }

  async coursesResults(results, year) {
    const canView = this.currentUser.performsNonLearnerFunction;
    const academicYearCrossesCalendarYearBoundaries = await this.iliosConfig.itemFromConfig(
      'academicYearCrossesCalendarYearBoundaries'
    );
    return results
      .map((course) => {
        const rhett = {
          course,
          value: academicYearCrossesCalendarYearBoundaries
            ? `${course.year} - ${course.year + 1} ${course.title}`
            : `${course.year} ${course.title}`,
        };

        if (isPresent(course.externalId)) {
          rhett.value += ` (${course.externalId})`;
        }
        if (canView) {
          rhett.route = 'course';
          rhett.model = course;
        }

        return rhett;
      })
      .filter((obj) => isEmpty(year) || obj.course.year === parseInt(year, 10));
  }

  async coursesArrayResults(results, year) {
    const intl = this.intl;
    const academicYearCrossesCalendarYearBoundaries = await this.iliosConfig.itemFromConfig(
      'academicYearCrossesCalendarYearBoundaries'
    );
    const filteredResults = results.filter((course) => {
      return isEmpty(year) || course.year === parseInt(year, 10);
    });
    const sortedResults = filteredResults.sortBy('title');
    const mappedResults = sortedResults.map((course) => {
      return [
        course.get('title'),
        academicYearCrossesCalendarYearBoundaries
          ? `${course.year} - ${course.year + 1}`
          : course.year.toString(),
        course.get('externalId'),
      ];
    });

    return [
      [intl.t('general.courses'), intl.t('general.academicYear'), intl.t('general.externalId')],
    ].concat(mappedResults);
  }

  async sessionsResults(results, year) {
    const academicYearCrossesCalendarYearBoundaries = await this.iliosConfig.itemFromConfig(
      'academicYearCrossesCalendarYearBoundaries'
    );
    const canView = this.currentUser.performsNonLearnerFunction;
    const mappedResults = await map(results.toArray(), async (item) => {
      const course = await item.get('course');
      const rhett = { course };
      rhett.value = academicYearCrossesCalendarYearBoundaries
        ? `${course.year} - ${course.year + 1} ${course.title} ${item.title}`
        : `${course.year} ${course.title} ${item.title}`;
      if (canView) {
        rhett.route = 'session';
        rhett.model = course;
        rhett.model2 = item;
      }

      return rhett;
    });

    return mappedResults.filter(
      (obj) => isEmpty(year) || parseInt(obj.course.year, 10) === parseInt(year, 10)
    );
  }

  async sessionsArrayResults(results, year) {
    const academicYearCrossesCalendarYearBoundaries = await this.iliosConfig.itemFromConfig(
      'academicYearCrossesCalendarYearBoundaries'
    );
    const intl = this.intl;
    const filteredResults = await filter(results.toArray(), async (session) => {
      const course = await session.course;
      return isEmpty(year) || course.year === parseInt(year, 10);
    });
    const sortedResults = filteredResults.sortBy('title');
    const mappedResults = await map(sortedResults, async (session) => {
      const course = await session.course;
      const sessionDescriptionText = session.textDescription;
      const objectives = await session.sessionObjectives;
      return [
        session.get('title'),
        course.get('title'),
        academicYearCrossesCalendarYearBoundaries
          ? `${course.year} - ${course.year + 1}`
          : course.year.toString(),
        sessionDescriptionText,
        striptags(objectives.mapBy('title').join()),
      ];
    });

    return [
      [
        intl.t('general.session'),
        intl.t('general.course'),
        intl.t('general.academicYear'),
        intl.t('general.description'),
        intl.t('general.objectives'),
      ],
    ].concat(mappedResults);
  }

  async programsResults(results) {
    const canView = this.currentUser.performsNonLearnerFunction;
    return await map(results.toArray(), async (item) => {
      const rhett = {};
      const school = await item.get('school');
      rhett.value = school.get('title') + ': ' + item.get('title');
      if (canView) {
        rhett.route = 'program';
        rhett.model = item;
      }
      return rhett;
    });
  }

  async programsArrayResults(results) {
    const intl = this.intl;
    const sortedResults = results.sortBy('title');
    const mappedResults = await map(sortedResults.toArray(), async (program) => {
      const school = await program.get('school');
      return [program.get('title'), school.get('title')];
    });

    return [[intl.t('general.program'), intl.t('general.school')]].concat(mappedResults);
  }

  async programYearsResults(results) {
    const canView = this.currentUser.performsNonLearnerFunction;
    return map(results.toArray(), async (programYear) => {
      const rhett = {};
      const program = await programYear.program;
      const school = await programYear.school;
      const classOfYear = await programYear.getClassOfYear();

      rhett.value = school.get('title') + ' ' + program.get('title') + ' ' + classOfYear;
      if (canView) {
        rhett.route = 'programYear';
        rhett.model = program;
        rhett.model2 = programYear;
      }
      return rhett;
    });
  }

  async programYearsArrayResults(results) {
    const intl = this.intl;
    const resultsWithClassOfYear = await map(results, async (programYear) => {
      const classOfYear = await programYear.getClassOfYear();
      return {
        programYear,
        classOfYear,
      };
    });
    const sortedResults = resultsWithClassOfYear.sortBy('classOfYear');
    const mappedResults = await map(
      sortedResults.toArray(),
      async ({ programYear, classOfYear }) => {
        const program = await programYear.get('program');
        const school = await program.get('school');
        return [classOfYear, program.get('title'), school.get('title')];
      }
    );

    return [[intl.t('general.year'), intl.t('general.program'), intl.t('general.school')]].concat(
      mappedResults
    );
  }

  instructorsResults(results) {
    const mappedResults = results.map((result) => {
      return {
        value: result.get('fullName'),
      };
    });
    return resolve(mappedResults);
  }

  async instructorsArrayResults(results) {
    const intl = this.intl;
    const arr = await this.instructorsResults(results);
    const sortedResults = arr.sortBy('value');
    const mappedResults = sortedResults.map((obj) => [obj.value]);
    return [[intl.t('general.instructors')]].concat(mappedResults);
  }

  titleResults(results) {
    const mappedResults = results.map((result) => {
      return {
        value: result.get('title'),
      };
    });
    return resolve(mappedResults);
  }

  async valueResults(results, translationKey) {
    const intl = this.intl;
    const arr = await this.titleResults(results);
    const sortedResults = arr.sortBy('value');
    const mappedResults = sortedResults.map((obj) => [obj.value]);
    return [[intl.t(translationKey)]].concat(mappedResults);
  }

  instructorGroupsResults(results) {
    return this.titleResults(results);
  }

  async instructorGroupsArrayResults(results) {
    return this.valueResults(results, 'general.instructorGroups');
  }

  learningMaterialsResults(results) {
    return this.titleResults(results);
  }

  async learningMaterialsArrayResults(results) {
    return this.valueResults(results, 'general.learningMaterials');
  }

  competenciesResults(results) {
    return this.titleResults(results);
  }

  async competenciesArrayResults(results) {
    return this.valueResults(results, 'general.competencies');
  }

  sessionTypesResults(results) {
    return this.titleResults(results);
  }

  async sessionTypesArrayResults(results) {
    return this.valueResults(results, 'general.sessionTypes');
  }

  meshTermsResults(results) {
    const mappedResults = results.map((result) => {
      return {
        value: result.get('name'),
      };
    });
    return resolve(mappedResults);
  }

  async meshTermsArrayResults(results) {
    const intl = this.intl;
    const arr = await this.meshTermsResults(results);
    const sortedResults = arr.sortBy('value');
    const mappedResults = sortedResults.map((obj) => [obj.value]);
    return [[intl.t('general.meshTerms')]].concat(mappedResults);
  }

  async termsResults(results) {
    return map(results.toArray(), async (term) => {
      const vocabulary = await term.get('vocabulary');
      const titleWithParentTitles = await term.get('titleWithParentTitles');
      const value = vocabulary.get('title') + ' > ' + titleWithParentTitles;
      return { value };
    });
  }

  async termsArrayResults(results) {
    const intl = this.intl;
    const arr = await this.termsResults(results);
    const sortedResults = arr.sortBy('value');
    const mappedResults = sortedResults.map((obj) => [obj.value]);
    return [[intl.t('general.vocabulary')]].concat(mappedResults);
  }
}
<|endoftext|>


<commit_before>/**
 * Created by oleksiy.rudenko on 03/04/2016.
 */
(function(){
    'use strict';

    angular.module('app')
        .factory('appService', ['$q', AppService]);

    /**
     * Users DataService
     * Uses embedded, hard-coded data model; acts asynchronously to simulate
     * remote data service call(s).
     *
     * @returns {{loadAll: Function}}
     * @constructor
     */
    function AppService($q){
        var users = [
            {
                name: 'Lia Lugo',
                avatar: 'svg-1',
                content: 'I love cheese, especially airedale queso. Cheese and biscuits halloumi cauliflower cheese cottage cheese swiss boursin fondue caerphilly. Cow port-salut camembert de normandie macaroni cheese feta who moved my cheese babybel boursin. Red leicester roquefort boursin squirty cheese jarlsberg blue castello caerphilly chalk and cheese. Lancashire.'
            },
            {
                name: 'George Duke',
                avatar: 'svg-2',
                content: 'Zombie ipsum reversus ab viral inferno, nam rick grimes malum cerebro. De carne lumbering animata corpora quaeritis. Summus brains sit??, morbo vel maleficia? De apocalypsi gorger omero undead survivor dictum mauris.'
            },
            {
                name: 'Gener Delosreyes',
                avatar: 'svg-3',
                content: "Raw denim pour-over readymade Etsy Pitchfork. Four dollar toast pickled locavore bitters McSweeney's blog. Try-hard art party Shoreditch selfies. Odd Future butcher VHS, disrupt pop-up Thundercats chillwave vinyl jean shorts taxidermy master cleanse letterpress Wes Anderson mustache Helvetica. Schlitz bicycle rights chillwave irony lumberhungry Kickstarter next level sriracha typewriter Intelligentsia, migas kogi heirloom tousled. Disrupt 3 wolf moon lomo four loko. Pug mlkshk fanny pack literally hoodie bespoke, put a bird on it Marfa messenger bag kogi VHS."
            },
            {
                name: 'Lawrence Ray',
                avatar: 'svg-4',
                content: 'Scratch the furniture spit up on light gray carpet instead of adjacent linoleum so eat a plant, kill a hand pelt around the house and up and down stairs chasing phantoms run in circles, or claw drapes. Always hungry pelt around the house and up and down stairs chasing phantoms.'
            },
            {
                name: 'Ernesto Urbina',
                avatar: 'svg-5',
                content: 'Webtwo ipsum dolor sit amet, eskobo chumby doostang bebo. Bubbli greplin stypi prezi mzinga heroku wakoopa, shopify airbnb dogster dopplr gooru jumo, reddit plickers edmodo stypi zillow etsy.'
            },
            {
                name: 'Gani Ferrer',
                avatar: 'svg-10',
                content: "Lebowski ipsum yeah? What do you think happens when you get rad? You turn in your library card? Get a new driver's license? Stop being awesome? Dolor sit amet, consectetur adipiscing elit praesent ac magna justo pellentesque ac lectus. You don't go out and make a living dressed like that in the middle of a weekday. Quis elit blandit fringilla a ut turpis praesent felis ligula, malesuada suscipit malesuada."
            }
        ];

        // Promise-based API
        return {
            loadAllUsers : function() {
                // Simulate async nature of real remote calls
                return $q.when(users);
            }
        };
    }

})();<commit_msg>moved fn def outside return statement for the sake of readability<commit_after>/**
 * Created by oleksiy.rudenko on 03/04/2016.
 */
(function(){
    'use strict';

    angular.module('app')
        .factory('appService', ['$q', AppService]);

    /**
     * Users DataService
     * Uses embedded, hard-coded data model; acts asynchronously to simulate
     * remote data service call(s).
     *
     * @returns {{loadAll: Function}}
     * @constructor
     */
    function AppService($q) {
        var users = [
            {
                name: 'Lia Lugo',
                avatar: 'svg-1',
                content: 'I love cheese, especially airedale queso. Cheese and biscuits halloumi cauliflower cheese cottage cheese swiss boursin fondue caerphilly. Cow port-salut camembert de normandie macaroni cheese feta who moved my cheese babybel boursin. Red leicester roquefort boursin squirty cheese jarlsberg blue castello caerphilly chalk and cheese. Lancashire.'
            },
            {
                name: 'George Duke',
                avatar: 'svg-2',
                content: 'Zombie ipsum reversus ab viral inferno, nam rick grimes malum cerebro. De carne lumbering animata corpora quaeritis. Summus brains sit??, morbo vel maleficia? De apocalypsi gorger omero undead survivor dictum mauris.'
            },
            {
                name: 'Gener Delosreyes',
                avatar: 'svg-3',
                content: "Raw denim pour-over readymade Etsy Pitchfork. Four dollar toast pickled locavore bitters McSweeney's blog. Try-hard art party Shoreditch selfies. Odd Future butcher VHS, disrupt pop-up Thundercats chillwave vinyl jean shorts taxidermy master cleanse letterpress Wes Anderson mustache Helvetica. Schlitz bicycle rights chillwave irony lumberhungry Kickstarter next level sriracha typewriter Intelligentsia, migas kogi heirloom tousled. Disrupt 3 wolf moon lomo four loko. Pug mlkshk fanny pack literally hoodie bespoke, put a bird on it Marfa messenger bag kogi VHS."
            },
            {
                name: 'Lawrence Ray',
                avatar: 'svg-4',
                content: 'Scratch the furniture spit up on light gray carpet instead of adjacent linoleum so eat a plant, kill a hand pelt around the house and up and down stairs chasing phantoms run in circles, or claw drapes. Always hungry pelt around the house and up and down stairs chasing phantoms.'
            },
            {
                name: 'Ernesto Urbina',
                avatar: 'svg-5',
                content: 'Webtwo ipsum dolor sit amet, eskobo chumby doostang bebo. Bubbli greplin stypi prezi mzinga heroku wakoopa, shopify airbnb dogster dopplr gooru jumo, reddit plickers edmodo stypi zillow etsy.'
            },
            {
                name: 'Gani Ferrer',
                avatar: 'svg-10',
                content: "Lebowski ipsum yeah? What do you think happens when you get rad? You turn in your library card? Get a new driver's license? Stop being awesome? Dolor sit amet, consectetur adipiscing elit praesent ac magna justo pellentesque ac lectus. You don't go out and make a living dressed like that in the middle of a weekday. Quis elit blandit fringilla a ut turpis praesent felis ligula, malesuada suscipit malesuada."
            }
        ];

        // Promise-based API
        return {
            loadAllUsers: loadAllUsers
        };

        function loadAllUsers() {
            // Simulate async nature of real remote calls
            return $q.when(users);
        };
    }

})();<|endoftext|>


<commit_before>const logger = require("./logger");
const fs = require('fs');
const express = require('express');
const app = express();
const path = require('path');
const morgan = require("morgan");

var public = process.env.PUBLIC_PATH || path.join(__dirname, 'public');
var tilesUrl = process.env.TILES_URL || 'http://localhost:8080/all/{z}/{x}/{y}.pbf';

app.use(morgan(':remote-addr - :method :url :status :response-time ms - :res[content-length]'));

// allow CORS
app.all('*', (req, res, next) => {
  res.header('Access-Control-Allow-Origin', '*');
  res.header('Access-Control-Allow-Methods', 'GET,HEAD');
  res.header('Access-Control-Allow-Headers', 'X-Requested-With,Content-Type,Authorization');
  next();
});

// rewrite source path to local webservice
app.use('/styles/*\.json', function (req, res, next) {
  var filename = path.join(public, req.baseUrl);
  var style = JSON.parse(fs.readFileSync(filename, 'utf8'));
  if ('sources' in style) {
    for (var key in style.sources) {
      var source = style.sources[key];
      if (source.type != 'vector') {
        continue;
      }
      if (!('tiles' in source)) {
        return;
      }
      source.tiles = [tilesUrl];
    };
  }
  res.json(style).end();
});

// serve static files
app.use(express.static(public));
app.use('/', function (req, res) {
  res.sendFile(path.join(public, 'index.html'));
});

var server = app.listen(process.env.PORT || 8000, () => {
  logger.info(`listening on: ${server.address().port}`)
});

process.on('SIGINT', function () {
  server.close();
  process.exit();
});

<commit_msg>Add extra logging<commit_after>const logger = require("./logger");
const fs = require('fs');
const express = require('express');
const app = express();
const path = require('path');
const morgan = require("morgan");

var public = process.env.PUBLIC_PATH || path.join(__dirname, 'public');
var tilesUrl = process.env.TILES_URL || 'http://localhost:8080/all/{z}/{x}/{y}.pbf';

app.use(morgan(':remote-addr - :method :url :status :response-time ms - :res[content-length]'));

// allow CORS
app.all('*', (req, res, next) => {
  res.header('Access-Control-Allow-Origin', '*');
  res.header('Access-Control-Allow-Methods', 'GET,HEAD');
  res.header('Access-Control-Allow-Headers', 'X-Requested-With,Content-Type,Authorization');
  next();
});

// rewrite source path to local webservice
app.use('/styles/*\.json', function (req, res, next) {
  logger.info(`requesting file: ${req.baseUrl} => ${path.join(public, req.baseUrl)}`);
  var filename = path.join(public, req.baseUrl);
  var style = JSON.parse(fs.readFileSync(filename, 'utf8'));
  if ('sources' in style) {
    for (var key in style.sources) {
      var source = style.sources[key];
      if (source.type != 'vector') {
        continue;
      }
      if (!('tiles' in source)) {
        return;
      }
      source.tiles = [tilesUrl];
    };
  }
  res.json(style).end();
});

// serve static files
app.use(express.static(public));
app.use('/', function (req, res) {
  res.sendFile(path.join(public, 'index.html'));
});

var server = app.listen(process.env.PORT || 8000, () => {
  logger.info(`listening on: ${server.address().port}`)
});

process.on('SIGINT', function () {
  server.close();
  process.exit();
});

<|endoftext|>


<commit_before>function remoteMathService (cb) {
  var one;
  var two;

  callOneService( function (err, num) {
    one = num;
  });

  callTwoService( function (err, num) {
    two = num;
  });

  return cb( undefined , one + two);
}

function callOneService (cb) {
  setTimeout ( function () {
      return cb( undefined , 1 );
    }, 1000 );
}

function callTwoService (cb) {
  setTimeout ( function () {
    return cb( undefined , 2 );
  }, 1500 );
}

remoteMathService( function (err, answer) {
  if (err) console.log( "error " , err);
  if (answer == 3 ) {
    console.log ( "correct" );
  }
  else {
    console.log ("wrong answer" , answer);
  }
});
<commit_msg>Added solution for question 2<commit_after>const async = require('async');

function remoteMathService (cb) {
  var one = null;
  var two = null;

  async.parallel([
    function (callback) {
      callOneService( function (err, num) {
      one = num;
      callback();
    });
    },
    function (callback) {
      callTwoService( function (err, num) {
        two = num;
        callback();
      });
    },
  ], function(err) {
    if(err) return err;
    return cb( undefined , one + two);
  });
}

function callOneService (cb) {
  setTimeout ( function () {
      return cb( undefined , 1 );
    }, 1000 );
}

function callTwoService (cb) {
  setTimeout ( function () {
    return cb( undefined , 2 );
  }, 1500 );
}

remoteMathService( function (err, answer) {
  if (err) console.log( "error " , err);
  if (answer !== 3 ) {
    console .log ( "wrong answer" , answer);
  }
  else {
    console .log ( "correct" );
  }
});
<|endoftext|>


<commit_before>import React from 'react'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import CurrentGrid from '../containers/CurrentGrid'

class App extends React.Component{

  render(){
    return (

        <CurrentGrid />

    )
  }
}

export default connect()(App)
<commit_msg>Keypresses now regenerate grid<commit_after>import React from 'react'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import CurrentGrid from '../containers/CurrentGrid'
import { newGrid } from '../actions'

class App extends React.Component{
 constructor() {
    super();
    this.handleKeyPress = this.handleKeyPress.bind(this);
  }

  handleKeyPress(event) {
    this.props.keyPressAction(event.keyCode);
  }

  componentDidMount() {
     document.addEventListener('keypress', this.handleKeyPress);
  }

  componentWillUnmount() {
     document.removeEventListener('keypress', this.handleKeyPress);
  }

  render(){
    return (

        <CurrentGrid />

    )
  }
}

const mapDispatchToProps = dispatch => {
  return {
    keyPressAction: keyCode => {
      dispatch(newGrid(10,20))
    }
  }
}

export default connect(null, mapDispatchToProps)(App)
<|endoftext|>


<commit_before>// hypestore - HYPErmedia STORage Engine
//
// rfc2616 server supporting transparent content
// storage and retrieval over HTTP
// TODO: Store MD5 of submitted request bodies?
// TODO: filter for hypestore administrative pages (_ prefixed?)
var express = require('express');
var fs = require('fs');
var app = express();
var path = require('path');
var server = require('http').createServer(app);
var util = require('util');
var getRawBody = require('raw-body');
var mkdirp = require('mkdirp');
var mime = require('mime-types');
var parseurl = require('parseurl');
var resolvePath = require('resolve-path');
var spawn = require('child_process').spawn;

var SUPPORT = 'GET, HEAD, PUT, DELETE, OPTIONS';
var MIMEDEF = 'application/octet-stream';
var config;

app.use(express.logger());
app.use(express.cookieParser());
app.use(express.bodyParser());
app.use(function(req, res, next) {
	res.setHeader('X-Powered-By', 'HypeStore/0');
	next();
});
app.use(express.methodOverride());

init(); // read config and start server

// HEAD
app.head("*", function(req, res) {

	let file = config.storage.contentLocation + req.url;
	let resource = file.split('/')[file.split('/').length - 1];

	if (file) {
		fs.stat(file, function(err, stats) {
			if (err) {
				res.json(500, err);
				return;
			} else {
				let type = mime.lookup(file);
				res.set('Content-Length', stats.size);
				res.set('Content-Type', type ? type : MIMEDEF);
				res.send(200);
				return;
			}
		});

	} else {
		res.send(404);
		return;
	}
});

// GET
app.get("*", function(req, res) {

	// TODO: Add support for Range header
	//       file reader should take no Range header as a request for byte range 0-<size of file>
	//       return a error 416 if upper range exceeds beyond length of file

	//let file = config.storage.contentLocation + req.url;
	//let resource = file.split('/')[file.split('/').length - 1];

	let file = decodeURIComponent(parseurl(req).pathname);

	    if (!file) {
		res.send(400, 'Path is required');
		return;
	    } else {
		file = config.storage.contentLocation + file
	    }

	fs.readFile(file, function(err, data) {

		if (err) {
			console.error("could not open file: " + file + ": " + util.inspect(err));
			if (err.errno == -2) { // ENOENT: No such file or directory
				res.json(404, err);
				return;
			} else {
				res.json(500, err);
				return;
			}

		} else {
			let type = mime.lookup(file);
			res.set('Content-Type', type ? type : MIMEDEF);
			res.send(200, data);
			return;
		}

	});

});

// PUT
app.put("*", function(req, res) {

	let file = config.storage.contentLocation + req.url;

	getRawBody(req, {
		length: req.headers['Content-Length']
	}, function(err, buffer) {
		if (err) {
			console.log("error getting raw body: " + util.inspect(err));
			res.json(500, err);
			return;
		} else {
			let parts = [];
			parts = req.url.split("/")
			console.log("path split into " + parts.length + " parts");
			let dirTree = config.storage.contentLocation + "/";
			let resourceFile;

			for (let i = 0; i < parts.length; i++) {
				// first splitee will always be empty
				if (i != 0) {
					// if we're still in the the dir structure
					if (i !== (parts.length - 1)) {
						dirTree += (parts[i] + "/");
					} else {
						// this is the "file" or "resource" component of the path
						resourceFile = parts[i];
					}
				}
			}

			let httpResponseCode;
			fs.exists(dirTree, function(exists) {
				if (!exists) {
					httpResponseCode = 201;
					mkdirp(dirTree, function(err) {
						if (err) {
							console.error("could not create directory: " + dirTree);
							res.json(500, err);
							return;
						} else {
							fs.exists(dirTree + "/" + resourceFile, function(exists) {
								httpResponseCode = exists ? 204 : 201;
								fs.writeFile(file, buffer, {
									flag: 'w'
								}, function(err) {
									if (err) {
										res.json(500, err);
										return;
									} else {
										res.send(httpResponseCode);
										return;
									}
								});
							});
						}
					});
				}
			});
		}
	});
});

// DELETE
app.delete("*", function(req, res) {
	var file = config.storage.contentLocation + req.url;
	fs.exists(file, function(exists) {
		if (exists) {
			fs.unlink(file, function(error) {
				if (error) {
					res.json(500, error);
					return;
				} else {
					res.send(204);
					return;
				}
			});
		} else {
			res.send(404);
			return;
		}
	});
});

// POST -> should this even exist?
app.post("*", function(req, res) {
	res.set('Allow', SUPPORT);
	res.json(405, {
		message: "Hypestore is militantly idempotent and only supports " + SUPPORT
	});
	return;
});

app.options("*", function(req, res) {
	res.set('Allow', SUPPORT);
	res.send(200);
	return;
});

function loadConfig() {
	if (fs.existsSync("./config.json")) {
		var config = fs.readFileSync("./config.json", 'utf-8');
		return JSON.parse(config);
	} else {
		console.error("loadConfig: no ./config.json found");
		return undefined;
	}
}

function init() {

	config = loadConfig();
	if (!config) {
		console.error("FATAL: No config.json in current directory, exiting.");
		process.exit(1);
	}
	fs.exists(config.storage.contentLocation, function(exists) {
		if (!exists) {
			console.warn("WARNING: location " + config.storage.contentLocation + " specified in config.json could not be found.  Trying to create...");
			fs.mkdir(config.storage.contentLocation, function(exception) {
				if (exception) {
					console.error("FATAL: Could not create directory " + config.storage.contentLocation);
					process.exit(exception);
				}
			});
		}
	});

	try {
		server.listen(config.port);
		console.log('hypestore listening on port ' + config.port);
	} catch (error) {
		console.error('Could not bind port ' + config.port);
		process.exit(error);
	}

}<commit_msg>beautification<commit_after>// hypestore - HYPErmedia STORage Engine
//
// rfc2616 server supporting transparent content
// storage and retrieval over HTTP
// TODO: Store MD5 of submitted request bodies?
// TODO: filter for hypestore administrative pages (_ prefixed?)
var express = require('express');
var fs = require('fs');
var app = express();
var path = require('path');
var server = require('http').createServer(app);
var util = require('util');
var getRawBody = require('raw-body');
var mkdirp = require('mkdirp');
var mime = require('mime-types');
var parseurl = require('parseurl');
var resolvePath = require('resolve-path');
var spawn = require('child_process').spawn;

var SUPPORT = 'GET, HEAD, PUT, DELETE, OPTIONS';
var MIMEDEF = 'application/octet-stream';
var config;

app.use(express.logger());
app.use(express.cookieParser());
app.use(express.bodyParser());
app.use(function(req, res, next) {
    res.setHeader('X-Powered-By', 'HypeStore/0');
    next();
});
app.use(express.methodOverride());

init(); // read config and start server

// HEAD
app.head("*", function(req, res) {

    let file = config.storage.contentLocation + req.url;
    let resource = file.split('/')[file.split('/').length - 1];

    if (file) {
        fs.stat(file, function(err, stats) {
            if (err) {
                res.json(500, err);
                return;
            } else {
                let type = mime.lookup(file);
                res.set('Content-Length', stats.size);
                res.set('Content-Type', type ? type : MIMEDEF);
                res.send(200);
                return;
            }
        });

    } else {
        res.send(404);
        return;
    }
});

// GET
app.get("*", function(req, res) {

    // TODO: Add support for Range header
    //       file reader should take no Range header as a request for byte range 0-<size of file>
    //       return a error 416 if upper range exceeds beyond length of file

    //let file = config.storage.contentLocation + req.url;
    //let resource = file.split('/')[file.split('/').length - 1];

    let file = decodeURIComponent(parseurl(req).pathname);

    if (!file) {
        res.send(400, 'Path is required');
        return;
    } else {
        file = config.storage.contentLocation + file
    }

    fs.readFile(file, function(err, data) {

        if (err) {
            console.error("could not open file: " + file + ": " + util.inspect(err));
            if (err.errno == -2) { // ENOENT: No such file or directory
                res.json(404, err);
                return;
            } else {
                res.json(500, err);
                return;
            }

        } else {
            let type = mime.lookup(file);
            res.set('Content-Type', type ? type : MIMEDEF);
            res.send(200, data);
            return;
        }

    });

});

// PUT
app.put("*", function(req, res) {

    let file = config.storage.contentLocation + req.url;

    getRawBody(req, {
        length: req.headers['Content-Length']
    }, function(err, buffer) {
        if (err) {
            console.log("error getting raw body: " + util.inspect(err));
            res.json(500, err);
            return;
        } else {
            let parts = [];
            parts = req.url.split("/")
            console.log("path split into " + parts.length + " parts");
            let dirTree = config.storage.contentLocation + "/";
            let resourceFile;

            for (let i = 0; i < parts.length; i++) {
                // first splitee will always be empty
                if (i != 0) {
                    // if we're still in the the dir structure
                    if (i !== (parts.length - 1)) {
                        dirTree += (parts[i] + "/");
                    } else {
                        // this is the "file" or "resource" component of the path
                        resourceFile = parts[i];
                    }
                }
            }

            let httpResponseCode;
            fs.exists(dirTree, function(exists) {
                if (!exists) {
                    httpResponseCode = 201;
                    mkdirp(dirTree, function(err) {
                        if (err) {
                            console.error("could not create directory: " + dirTree);
                            res.json(500, err);
                            return;
                        } else {
                            fs.exists(dirTree + "/" + resourceFile, function(exists) {
                                httpResponseCode = exists ? 204 : 201;
                                fs.writeFile(file, buffer, {
                                    flag: 'w'
                                }, function(err) {
                                    if (err) {
                                        res.json(500, err);
                                        return;
                                    } else {
                                        res.send(httpResponseCode);
                                        return;
                                    }
                                });
                            });
                        }
                    });
                }
            });
        }
    });
});

// DELETE
app.delete("*", function(req, res) {
    var file = config.storage.contentLocation + req.url;
    fs.exists(file, function(exists) {
        if (exists) {
            fs.unlink(file, function(error) {
                if (error) {
                    res.json(500, error);
                    return;
                } else {
                    res.send(204);
                    return;
                }
            });
        } else {
            res.send(404);
            return;
        }
    });
});

// POST -> should this even exist?
app.post("*", function(req, res) {
    res.set('Allow', SUPPORT);
    res.json(405, {
        message: "Hypestore is militantly idempotent and only supports " + SUPPORT
    });
    return;
});

app.options("*", function(req, res) {
    res.set('Allow', SUPPORT);
    res.send(200);
    return;
});

function loadConfig() {
    if (fs.existsSync("./config.json")) {
        var config = fs.readFileSync("./config.json", 'utf-8');
        return JSON.parse(config);
    } else {
        console.error("loadConfig: no ./config.json found");
        return undefined;
    }
}

function init() {

    config = loadConfig();
    if (!config) {
        console.error("FATAL: No config.json in current directory, exiting.");
        process.exit(1);
    }
    fs.exists(config.storage.contentLocation, function(exists) {
        if (!exists) {
            console.warn("WARNING: location " + config.storage.contentLocation + " specified in config.json could not be found.  Trying to create...");
            fs.mkdir(config.storage.contentLocation, function(exception) {
                if (exception) {
                    console.error("FATAL: Could not create directory " + config.storage.contentLocation);
                    process.exit(exception);
                }
            });
        }
    });

    try {
        server.listen(config.port);
        console.log('hypestore listening on port ' + config.port);
    } catch (error) {
        console.error('Could not bind port ' + config.port);
        process.exit(error);
    }

}<|endoftext|>


<commit_before>/*
Flot plugin for selecting times in a flight

The plugin defines the following options:

  selection: {
    mode: null or "x",
    color: color
  }

Selection support is enabled by setting the mode to "x". The user will
only be able to specify the x range. "color" is color of the selection
(if you need to change the color later on, you can get to it with
plot.getOptions().selection.color).

When selection support is enabled, a "plotselected" event will be
emitted on the DOM element you passed into the plot function. The
event handler gets a parameter with the ranges selected on the axes,
like this:

  placeholder.bind("plotselected", function(event, ranges) {
    alert("You selected " + ranges.xaxis.from + " to " + ranges.xaxis.to)
    // with multiple axes, the extra ones are in x2axis, x3axis, ...
  });

The "plotselected" event is only fired when the user has finished
making the selection. A "plotselecting" event is fired during the
process with the same parameters as the "plotselected" event, in case
you want to know what's happening while it's happening,

A "plotunselected" event with no arguments is emitted when the user
clicks the mouse to remove the selection.

The plugin allso adds the following methods to the plot object:

- setSelection(ranges, preventEvent)

  Set the selection range. The passed in ranges is on the same
  form as returned in the "plotselected" event, like this:

    setSelection({ xaxis: { from: 0, to: 10 } });

  setSelection will trigger the "plotselected" event when called. If
  you don't want that to happen, e.g. if you're inside a
  "plotselected" handler, pass true as the second parameter. If you
  are using multiple axes, you can specify the ranges on any of those,
  e.g. as x2axis/x3axis/... instead of xaxis, the plugin picks the
  first one it sees.

- clearSelection(preventEvent)

  Clear the selection rectangle. Pass in true to avoid getting a
  "plotunselected" event.

- getSelection()

  Returns the current selection in the same format as the
  "plotselected" event. If there's currently no selection, the
  function returns null.

*/

(function($) {
  function init(plot) {
    var selection = {
      first: { x: -1}, second: { x: -1},
      show: false,
      active: false
    };

    // FIXME: The drag handling implemented here should be
    // abstracted out, there's some similar code from a library in
    // the navigation plugin, this should be massaged a bit to fit
    // the Flot cases here better and reused. Doing this would
    // make this plugin much slimmer.
    var savedhandlers = {};

    var mouseUpHandler = null;

    function onMouseMove(e) {
      if (selection.active) {
        updateSelection(e);

        plot.getPlaceholder().trigger('plotselecting', [getSelection()]);
      } else {
        var selected_marker = getMarker(e);

        if (selected_marker !== null) {
          plot.getPlaceholder().css('cursor', 'col-resize');
        } else
          plot.getPlaceholder().css('cursor', 'auto');
      }
    }

    function onMouseDown(e) {
      if (e.which != 1)  // only accept left-click
        return;

      // cancel out any text selections
      document.body.focus();

      // prevent text selection and drag in old-school browsers
      if (document.onselectstart !== undefined && savedhandlers.onselectstart == null) {
        savedhandlers.onselectstart = document.onselectstart;
        document.onselectstart = function() { return false; };
      }
      if (document.ondrag !== undefined && savedhandlers.ondrag == null) {
        savedhandlers.ondrag = document.ondrag;
        document.ondrag = function() { return false; };
      }

      var selected_marker = getMarker(e);

      if (selected_marker == null)
        return;
      else if (selected_marker == 'first')
        setSelectionPos(selection.first, e);
      else
        setSelectionPos(selection.second, e);

      selection.active = selected_marker;

      // this is a bit silly, but we have to use a closure to be
      // able to whack the same handler again
      mouseUpHandler = function(e) { onMouseUp(e); };

      $(document).one('mouseup', mouseUpHandler);
    }

    function onMouseUp(e) {
      mouseUpHandler = null;

      // revert drag stuff for old-school browsers
      if (document.onselectstart !== undefined)
        document.onselectstart = savedhandlers.onselectstart;
      if (document.ondrag !== undefined)
        document.ondrag = savedhandlers.ondrag;

      // no more dragging
      selection.active = null;
      updateSelection(e);

      triggerSelectedEvent();

      return false;
    }

    function getMarker(e) {
      var o = plot.getOptions();
      var offset = plot.getPlaceholder().offset();
      var plotOffset = plot.getPlotOffset();
      var pos = clamp(0, e.pageX - offset.left - plotOffset.left, plot.width());

      var dist_to_first = Math.abs(selection.first.x - pos),
          dist_to_second = Math.abs(selection.second.x - pos);

      if (dist_to_first <= dist_to_second && dist_to_first < 10)
        return 'first';
      else if (dist_to_first > dist_to_second && dist_to_second < 10)
        return 'second';
      else
        return null;
    }

    function getSelection() {
      var r = {}, c1 = selection.first, c2 = selection.second;
      $.each(plot.getAxes(), function(name, axis) {
        if (axis.used) {
          var p1 = axis.c2p(c1[axis.direction]), p2 = axis.c2p(c2[axis.direction]);
          r[name] = { from: Math.min(p1, p2), to: Math.max(p1, p2) };
        }
      });
      return r;
    }

    function triggerSelectedEvent() {
      var r = getSelection();

      plot.getPlaceholder().trigger('plotselected', [r]);

      // backwards-compat stuff, to be removed in future
      if (r.xaxis && r.yaxis)
        plot.getPlaceholder().trigger('selected', [{ x1: r.xaxis.from, x2: r.xaxis.to }]);
    }

    function clamp(min, value, max) {
      return value < min ? min : (value > max ? max : value);
    }

    function setSelectionPos(pos, e) {
      var o = plot.getOptions();
      var offset = plot.getPlaceholder().offset();
      var plotOffset = plot.getPlotOffset();
      pos.x = clamp(0, e.pageX - offset.left - plotOffset.left, plot.width());

    }

    function updateSelection(pos) {
      if (pos.pageX == null)
        return;

      if (selection.active == 'first')
        setSelectionPos(selection.first, pos);
      else if (selection.active == 'second')
        setSelectionPos(selection.second, pos);

      selection.show = true;
      plot.triggerRedrawOverlay();
    }

    function clearSelection(preventEvent) {
      if (selection.show) {
        selection.show = false;
        plot.triggerRedrawOverlay();
        if (!preventEvent)
          plot.getPlaceholder().trigger('plotunselected', []);
      }
    }

    // function taken from markings support in Flot
    function extractRange(ranges, coord) {
      var axis, from, to, key, axes = plot.getAxes();

      for (var k in axes) {
        axis = axes[k];
        if (axis.direction == coord) {
          key = coord + axis.n + 'axis';
          if (!ranges[key] && axis.n == 1)
            key = coord + 'axis'; // support x1axis as xaxis
          if (ranges[key]) {
            from = ranges[key].from;
            to = ranges[key].to;
            break;
          }
        }
      }

      // backwards-compat stuff - to be removed in future
      if (!ranges[key]) {
        axis = coord == 'x' ? plot.getXAxes()[0] : plot.getYAxes()[0];
        from = ranges[coord + '1'];
        to = ranges[coord + '2'];
      }

      // auto-reverse as an added bonus
      if (from != null && to != null && from > to) {
        var tmp = from;
        from = to;
        to = tmp;
      }

      return { from: from, to: to, axis: axis };
    }

    function setSelection(ranges, preventEvent) {
      var axis, range, o = plot.getOptions();

      range = extractRange(ranges, 'x');

      selection.first.x = range.axis.p2c(range.from);
      selection.second.x = range.axis.p2c(range.to);

      selection.show = true;
      plot.triggerRedrawOverlay();
      if (!preventEvent)
        triggerSelectedEvent();
    }

    plot.clearSelection = clearSelection;
    plot.setSelection = setSelection;
    plot.getSelection = getSelection;

    plot.hooks.bindEvents.push(function(plot, eventHolder) {
      var o = plot.getOptions();
      if (o.selection.mode != null) {
        eventHolder.mousemove(onMouseMove);
        eventHolder.mousedown(onMouseDown);
      }
    });

    plot.hooks.drawOverlay.push(function(plot, ctx) {
      // draw selection
      if (selection.show) {
        var plotOffset = plot.getPlotOffset();
        var o = plot.getOptions();

        ctx.save();
        ctx.translate(plotOffset.left, plotOffset.top);

        var c = $.color.parse(o.selection.color);

        ctx.strokeStyle = c.scale('a', 0.8).toString();
        ctx.lineWidth = 1;
        ctx.lineJoin = 'round';
        ctx.fillStyle = c.scale('a', 0.4).toString();

        var y = 0,
            h = plot.height();

        var w_left = Math.min(selection.first.x, selection.second.x);

        ctx.fillRect(0, y, w_left, h);
        ctx.strokeRect(0, y, w_left, h);

        var x_right = Math.max(selection.first.x, selection.second.x);

        ctx.fillRect(x_right, y, plot.width() - x_right, h);
        ctx.strokeRect(x_right, y, plot.width() - x_right, h);

        ctx.restore();
      }
    });

    plot.hooks.shutdown.push(function(plot, eventHolder) {
      eventHolder.unbind('mousemove', onMouseMove);
      eventHolder.unbind('mousedown', onMouseDown);

      if (mouseUpHandler)
        $(document).unbind('mouseup', mouseUpHandler);
    });

  }

  $.plot.plugins.push({
    init: init,
    options: {
      selection: {
        mode: null, // one of null, "x"
        color: '#777777'
      }
    },
    name: 'flight-upload',
    version: '0.9'
  });
})(jQuery);
<commit_msg>flight-upload: don't stroke around gray area, show single lines only<commit_after>/*
Flot plugin for selecting times in a flight

The plugin defines the following options:

  selection: {
    mode: null or "x",
    color: color
  }

Selection support is enabled by setting the mode to "x". The user will
only be able to specify the x range. "color" is color of the selection
(if you need to change the color later on, you can get to it with
plot.getOptions().selection.color).

When selection support is enabled, a "plotselected" event will be
emitted on the DOM element you passed into the plot function. The
event handler gets a parameter with the ranges selected on the axes,
like this:

  placeholder.bind("plotselected", function(event, ranges) {
    alert("You selected " + ranges.xaxis.from + " to " + ranges.xaxis.to)
    // with multiple axes, the extra ones are in x2axis, x3axis, ...
  });

The "plotselected" event is only fired when the user has finished
making the selection. A "plotselecting" event is fired during the
process with the same parameters as the "plotselected" event, in case
you want to know what's happening while it's happening,

A "plotunselected" event with no arguments is emitted when the user
clicks the mouse to remove the selection.

The plugin allso adds the following methods to the plot object:

- setSelection(ranges, preventEvent)

  Set the selection range. The passed in ranges is on the same
  form as returned in the "plotselected" event, like this:

    setSelection({ xaxis: { from: 0, to: 10 } });

  setSelection will trigger the "plotselected" event when called. If
  you don't want that to happen, e.g. if you're inside a
  "plotselected" handler, pass true as the second parameter. If you
  are using multiple axes, you can specify the ranges on any of those,
  e.g. as x2axis/x3axis/... instead of xaxis, the plugin picks the
  first one it sees.

- clearSelection(preventEvent)

  Clear the selection rectangle. Pass in true to avoid getting a
  "plotunselected" event.

- getSelection()

  Returns the current selection in the same format as the
  "plotselected" event. If there's currently no selection, the
  function returns null.

*/

(function($) {
  function init(plot) {
    var selection = {
      first: { x: -1}, second: { x: -1},
      show: false,
      active: false
    };

    // FIXME: The drag handling implemented here should be
    // abstracted out, there's some similar code from a library in
    // the navigation plugin, this should be massaged a bit to fit
    // the Flot cases here better and reused. Doing this would
    // make this plugin much slimmer.
    var savedhandlers = {};

    var mouseUpHandler = null;

    function onMouseMove(e) {
      if (selection.active) {
        updateSelection(e);

        plot.getPlaceholder().trigger('plotselecting', [getSelection()]);
      } else {
        var selected_marker = getMarker(e);

        if (selected_marker !== null) {
          plot.getPlaceholder().css('cursor', 'col-resize');
        } else
          plot.getPlaceholder().css('cursor', 'auto');
      }
    }

    function onMouseDown(e) {
      if (e.which != 1)  // only accept left-click
        return;

      // cancel out any text selections
      document.body.focus();

      // prevent text selection and drag in old-school browsers
      if (document.onselectstart !== undefined && savedhandlers.onselectstart == null) {
        savedhandlers.onselectstart = document.onselectstart;
        document.onselectstart = function() { return false; };
      }
      if (document.ondrag !== undefined && savedhandlers.ondrag == null) {
        savedhandlers.ondrag = document.ondrag;
        document.ondrag = function() { return false; };
      }

      var selected_marker = getMarker(e);

      if (selected_marker == null)
        return;
      else if (selected_marker == 'first')
        setSelectionPos(selection.first, e);
      else
        setSelectionPos(selection.second, e);

      selection.active = selected_marker;

      // this is a bit silly, but we have to use a closure to be
      // able to whack the same handler again
      mouseUpHandler = function(e) { onMouseUp(e); };

      $(document).one('mouseup', mouseUpHandler);
    }

    function onMouseUp(e) {
      mouseUpHandler = null;

      // revert drag stuff for old-school browsers
      if (document.onselectstart !== undefined)
        document.onselectstart = savedhandlers.onselectstart;
      if (document.ondrag !== undefined)
        document.ondrag = savedhandlers.ondrag;

      // no more dragging
      selection.active = null;
      updateSelection(e);

      triggerSelectedEvent();

      return false;
    }

    function getMarker(e) {
      var o = plot.getOptions();
      var offset = plot.getPlaceholder().offset();
      var plotOffset = plot.getPlotOffset();
      var pos = clamp(0, e.pageX - offset.left - plotOffset.left, plot.width());

      var dist_to_first = Math.abs(selection.first.x - pos),
          dist_to_second = Math.abs(selection.second.x - pos);

      if (dist_to_first <= dist_to_second && dist_to_first < 10)
        return 'first';
      else if (dist_to_first > dist_to_second && dist_to_second < 10)
        return 'second';
      else
        return null;
    }

    function getSelection() {
      var r = {}, c1 = selection.first, c2 = selection.second;
      $.each(plot.getAxes(), function(name, axis) {
        if (axis.used) {
          var p1 = axis.c2p(c1[axis.direction]), p2 = axis.c2p(c2[axis.direction]);
          r[name] = { from: Math.min(p1, p2), to: Math.max(p1, p2) };
        }
      });
      return r;
    }

    function triggerSelectedEvent() {
      var r = getSelection();

      plot.getPlaceholder().trigger('plotselected', [r]);

      // backwards-compat stuff, to be removed in future
      if (r.xaxis && r.yaxis)
        plot.getPlaceholder().trigger('selected', [{ x1: r.xaxis.from, x2: r.xaxis.to }]);
    }

    function clamp(min, value, max) {
      return value < min ? min : (value > max ? max : value);
    }

    function setSelectionPos(pos, e) {
      var o = plot.getOptions();
      var offset = plot.getPlaceholder().offset();
      var plotOffset = plot.getPlotOffset();
      pos.x = clamp(0, e.pageX - offset.left - plotOffset.left, plot.width());

    }

    function updateSelection(pos) {
      if (pos.pageX == null)
        return;

      if (selection.active == 'first')
        setSelectionPos(selection.first, pos);
      else if (selection.active == 'second')
        setSelectionPos(selection.second, pos);

      selection.show = true;
      plot.triggerRedrawOverlay();
    }

    function clearSelection(preventEvent) {
      if (selection.show) {
        selection.show = false;
        plot.triggerRedrawOverlay();
        if (!preventEvent)
          plot.getPlaceholder().trigger('plotunselected', []);
      }
    }

    // function taken from markings support in Flot
    function extractRange(ranges, coord) {
      var axis, from, to, key, axes = plot.getAxes();

      for (var k in axes) {
        axis = axes[k];
        if (axis.direction == coord) {
          key = coord + axis.n + 'axis';
          if (!ranges[key] && axis.n == 1)
            key = coord + 'axis'; // support x1axis as xaxis
          if (ranges[key]) {
            from = ranges[key].from;
            to = ranges[key].to;
            break;
          }
        }
      }

      // backwards-compat stuff - to be removed in future
      if (!ranges[key]) {
        axis = coord == 'x' ? plot.getXAxes()[0] : plot.getYAxes()[0];
        from = ranges[coord + '1'];
        to = ranges[coord + '2'];
      }

      // auto-reverse as an added bonus
      if (from != null && to != null && from > to) {
        var tmp = from;
        from = to;
        to = tmp;
      }

      return { from: from, to: to, axis: axis };
    }

    function setSelection(ranges, preventEvent) {
      var axis, range, o = plot.getOptions();

      range = extractRange(ranges, 'x');

      selection.first.x = range.axis.p2c(range.from);
      selection.second.x = range.axis.p2c(range.to);

      selection.show = true;
      plot.triggerRedrawOverlay();
      if (!preventEvent)
        triggerSelectedEvent();
    }

    plot.clearSelection = clearSelection;
    plot.setSelection = setSelection;
    plot.getSelection = getSelection;

    plot.hooks.bindEvents.push(function(plot, eventHolder) {
      var o = plot.getOptions();
      if (o.selection.mode != null) {
        eventHolder.mousemove(onMouseMove);
        eventHolder.mousedown(onMouseDown);
      }
    });

    plot.hooks.drawOverlay.push(function(plot, ctx) {
      // draw selection
      if (selection.show) {
        var plotOffset = plot.getPlotOffset();
        var o = plot.getOptions();

        ctx.save();
        ctx.translate(plotOffset.left, plotOffset.top);

        var c = $.color.parse(o.selection.color);

        ctx.strokeStyle = c.scale('a', 0.8).toString();
        ctx.lineWidth = 1;
        ctx.lineJoin = 'round';
        ctx.fillStyle = c.scale('a', 0.4).toString();

        var y = 0,
            h = plot.height();

        var w_left = Math.min(selection.first.x, selection.second.x);

        ctx.fillRect(0, y, w_left, h);

        var x_right = Math.max(selection.first.x, selection.second.x);

        ctx.fillRect(x_right, y, plot.width() - x_right, h);

        ctx.beginPath();
        ctx.moveTo(w_left, 0);
        ctx.lineTo(w_left, plot.height());
        ctx.stroke();

        ctx.beginPath();
        ctx.moveTo(x_right, 0);
        ctx.lineTo(x_right, plot.height());
        ctx.stroke();

        ctx.restore();
      }
    });

    plot.hooks.shutdown.push(function(plot, eventHolder) {
      eventHolder.unbind('mousemove', onMouseMove);
      eventHolder.unbind('mousedown', onMouseDown);

      if (mouseUpHandler)
        $(document).unbind('mouseup', mouseUpHandler);
    });

  }

  $.plot.plugins.push({
    init: init,
    options: {
      selection: {
        mode: null, // one of null, "x"
        color: '#777777'
      }
    },
    name: 'flight-upload',
    version: '0.9'
  });
})(jQuery);
<|endoftext|>


<commit_before>/*! This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */

"use strict";

const {setTimeout, clearTimeout, setInterval, clearInterval} = require("sdk/timers");
const {PersistentObject} = require("./storage");
const sp = require("sdk/simple-prefs");
const prefs = sp.prefs;
const {Cu, Cc, Ci} = require("chrome");
const unload = require("sdk/system/unload").when;
const {dumpUpdateObject, handleCmd, isEnabled} = require("./debug");
const observerService = Cc["@mozilla.org/observer-service;1"]
                      .getService(Ci.nsIObserverService);

const timerDataAddress = "timer.data";
let timerData;

const tickHandlers = [];
const userActiveHandlers = [];
const userInactiveHandlers = [];

let activityObs;
let activity;
let tickInterval;

let startTimeMs;

const init = function(){
  return PersistentObject("simplePref", {address: timerDataAddress})
  .then((obj)=> {
    timerData = obj;
  }).then(_init);
}

const _init = function(){
  console.log("initializing timer");

  console.time("timer init");

  startTimeMs = Number(require('./experiment').info.startTimeMs);

  if (!("elapsedTime" in timerData))
  timerData.elapsedTime = 0;

  timerData.elapsedTotalTime = elapsedTotalTime();

  if (!("silenceStart" in timerData))
    timerData.silenceStart = -1;

  silenceLeft(); //to update the silence status;

  watchActivity();

  tickInterval = setInterval(tick, prefs["timer.tick_length_s"]*1000);

  let f = function(pref){
    clearInterval(tickInterval);
    tickInterval = setInterval(tick, prefs["timer.tick_length_s"]*1000);
  };

  sp.on("timer.tick_length_s", f);
  unload(function(){sp.removeListener("timer.tick_length_s", f)});

  f = function(pref){
    timerData.elapsedTotalTime = elapsedTotalTime();
  };

  sp.on("experiment.startTimeMs", f);
  unload(function(){sp.removeListener("experiment.startTimeMs", f)});

  debug.init();
  debug.update({silenceStatus: true});

  console.timeEnd("timer init");
}

const elapsedTotalTime = function(stage){
  let exp = require("./experiment");

  if (!stage){
    let ett = (Date.now() - startTimeMs) / (1000 * prefs["timer.tick_length_s"]);

    return ett;
  }

  let stageTimes = exp.stageTimes;

  if (!stageTimes[stage])
    return 0;

  // in the stage
  if (!stageTimes[stage].duration){
    let ett = (Date.now() - Number(stageTimes[stage].start)) / (1000 * prefs["timer.tick_length_s"]);

    return ett;
  }

  // stage has ended
  let ett = stageTimes[stage].duration;

  return ett;
}

const watchActivity = function(){

  activity = {
    minor_inactive_s: 0,
    last_minor_inactive_s: 0,
    minor_active_s: 0,
    active_s: 0,
    active: false
  };

  let activeCounter, inactiveCounter;

  activityObs = {
    observe: function(subject, topic, data){
      switch(topic){
        case "user-interaction-active":

          // require('./stats').event("activeTick");
          // console.log("active " + elapsedTime());
          clearInterval(inactiveCounter);
          if (activity.minor_inactive_s) activity.last_minor_inactive_s = activity.minor_inactive_s;
          activity.minor_inactive_s = 0;
          if (!activeCounter){
            activeCounter = setInterval(function(){
              activity.minor_active_s += 1;
              activity.active_s += 1;
              debug.update();

            }, 1000);
          }
          activity.active = true;

          if (activity.minor_active_s == 0){ // to call the handlers only once
            userActiveHandlers.forEach((fn) => fn());
            require('./stats').event("activation");
          }

        break;
        case "user-interaction-inactive":
          require('./stats').event("inactiveTick");
          console.log("user inactive (minor)");
          clearInterval(activeCounter);
          activeCounter = null;
          activity.minor_active_s = 0;
          inactiveCounter = setInterval(function(){
            activity.minor_inactive_s += 1;

            if (activity.active)
              activity.active_s += 1;

            if (activity.minor_inactive_s > prefs["timer.inactive_threshold_s"] && activity.active){
              deactivate();
            }
            debug.update();
          }, 1000);
        break;
      }
    },
    register: function(){
      observerService.addObserver(this, "user-interaction-active", false);
      observerService.addObserver(this, "user-interaction-inactive", false);
    },
    unregister: function(){
      observerService.removeObserver(this, "user-interaction-inactive");
      observerService.removeObserver(this, "user-interaction-active");
    }
  };

  unload(function(){if (activityObs) activityObs.unregister();});

  activityObs.register();
}

const tick = function(){

  let ett = elapsedTotalTime();
  timerData.elapsedTotalTime = ett;

  if (!activity.active){
    console.log("tick missed due to inactivity");
    require('./stats').event("missedTick");
    debug.update({silenceStatus: true});
    return;
  }

  require('./stats').event("tick");

  let et = timerData.elapsedTime + 1;

  timerData.elapsedTime = et;
  console.log("elapsed time: " + et + " ticks = " + et*prefs["timer.tick_length_s"]/60 + " minutes");

  tickHandlers.forEach(function(callback){
    callback(et, ett);
  });

  debug.update({silenceStatus: true});
}

const onTick = function(callback){
  tickHandlers.push(callback);
}

const onUserActive = function(fn){
  userActiveHandlers.push(fn);
}

//TOTHINK: major or minor inactive?
const onInactive = function(fn){

}

const elapsedTime = function(stage){
  if (!stage)
    return timerData.elapsedTime;

  let exp = require('./experiment');
  let stageTimes = exp.stageTimes;

  if (!stageTimes[stage])
    return 0;
  else
    return stageTimes[stage].elapsedTime;
}

const silence = function(){
  let ett = elapsedTotalTime();
  timerData.silenceStart = ett;

  console.log("silence started at " + ett + " ticks");
  console.log("silence is expected to end at " + Number(ett + silence_length_tick()) + " ticks");

  silenceLeft(); //to update all variables

  debug.update({silenceStatus: true});

  let info = {startett: ett};
  require('./logger').logSilenceStart(info);
  require('./stats').event("silence");
}

const silenceElapsed = function(){
  return (isSilent()? elapsedTotalTime() - timerData.silenceStart : 0);
}

const silenceLeft = function(){

  if (timerData.silenceStart == -1)
    return 0;

  let ett = elapsedTotalTime();

  let left = silence_length_tick() - ett + timerData.silenceStart;

  //updating silence status
  if (left <= 0)
    endSilence();

  return left;
}

const endSilence = function(){
  let start = timerData.silenceStart;

  timerData.silenceStart = -1;
  let ett = elapsedTotalTime();

  console.log("silence ended at " + ett + " ticks");

  let info = {startett: start, endett: ett, effectiveLength: ett-start};

  require('./logger').logSilenceEnd(info);

  require('./stats').event("silenceend");

  debug.update({silenceStatus: true});

  return ett;
}

const isSilent = function(){
  return (silenceLeft() > 0);
}

const isActive = function(){
  return activity.active;
}

const deactivate = function(){
  activity.active = false;
  activity.active_s = 0;
  require('./stats').event("deactivation");
  console.log("user inactive");
}

const isRecentlyActive = function(activity_threshold_s, inactivity_threshold_s /* optional */){

  if (activity.minor_active_s > activity_threshold_s)
    return false;

  if (inactivity_threshold_s && (activity.last_minor_inactive_s < inactivity_threshold_s))
    return false;

  return true;
}

const isCertainlyActive = function(){
  return (activity.minor_inactive_s < 10);
}

const randomTime = function(start, end){
  return Math.floor(Math.random()*(end-start) + start);
}

const silence_length_tick = function(){
  return sToT(timerData.silence_length_s);
}

const tToS = function(t){
  return t*prefs["timer.tick_length_s"];
}

const sToT = function(s){
  return s/prefs["timer.tick_length_s"];
}

const debug = {
  init: function(){
    handleCmd(this.parseCmd);
  },
  update: function(options){

    if (!isEnabled()) return;

    dumpUpdateObject(activity, {list: "Activity Status"});

    if (options && options.silenceStatus){
      let silenceObj = {
        isSilent: isSilent(),
        silenceStart: timerData.silenceStart,
        silenceEnd: isSilent()? timerData.silenceStart + silence_length_tick() : 0,
        silenceElapsed: silenceElapsed(),
        silenceLeft: silenceLeft()
      }
      dumpUpdateObject(silenceObj, {list: "Silence Status"});
    }
  },

  parseCmd: function(cmd){
    const patt = /([^ ]*) *(.*)/;
    let args = patt.exec(cmd);

    let subArgs;

    if (!args)  //does not match the basic pattern
      return false;

    let name = args[1];
    let params = args[2];

    switch(name){
      case "tick":
        tick();
        break;
      case "silence":
        silence();
        break;
      case "issilent":
        return isSilent();
        break;
      case "endsilence":
        return "silence ended at " + endSilence() + " ticks";
        break;
      case "iscertainlyactive":
        return isCertainlyActive();
        break;
      case "inactive":
        deactivate();
        observerService.notifyObservers(null, "user-interaction-inactive", "");
        debug.update();
        return "user inactivity forced"
        break;

      case "time":
        subArgs = params.split(" ");

        let act = subArgs[0];

        if (!subArgs[0])
          return "error: invalid use of time command.";

        switch(act){
          case "set":
            if (!subArgs[1])
              return "error: invalid use of time set command.";

            switch(subArgs[1]){
              case "et":
                if (!subArgs[2])
                  return "error: invalid use of time set et command.";

                timerData.elapsedTime = Number(subArgs[2]);
                return "elapsedTime set to " + Number(subArgs[2]);
              break;
              case "tick_length_s":
                if (!subArgs[2])
                  return "error: invalid use of time set tick_length_s command.";

                prefs["timer.tick_length_s"] = Number(subArgs[2]);
                return "new tick length in seconds: " + prefs["timer.tick_length_s"];
                break;
              case "st-diff":
                if (!subArgs[2])
                  return "error: invalid use of time set st command.";

                let startTimeMs = Number(require('./storage').osFileObjects['experiment.data'].startTimeMs);
                console.log(startTimeMs);

                let dateNum = startTimeMs
                              + Number(subArgs[2])*prefs["timer.tick_length_s"]*1000;

                console.log(dateNum);
                require('./storage').osFileObjects['experiment.data'].startTimeMs = String(dateNum);

                return "new start time set to: " + require('./timer').dateTimeToString(new Date(dateNum));
                break;
              default:
                return "error: invalid use of time set command.";
            }
            break;

          case "get":

            if (!subArgs[1])
              return "error: invalid use of time set command.";

            switch(subArgs[1]){
              case "et":
                return elapsedTime(subArgs[2]);
                break;
              case "ett":
                return elapsedTotalTime(subArgs[2]);
                break;

              default:

            }
            break;

          default:
            return "error: invalid use of time command.";
        }
      default:
        return undefined;
    }

    return " ";
  }

}

exports.elapsedTime = elapsedTime;
exports.elapsedTotalTime = elapsedTotalTime;
exports.isActive = isActive;
exports.isRecentlyActive = isRecentlyActive;
exports.isCertainlyActive = isCertainlyActive;
exports.isSilent = isSilent;
exports.silence = silence;
exports.endSilence = endSilence;
exports.randomTime = randomTime;
exports.onTick = onTick;
exports.onUserActive = onUserActive;
exports.tToS = tToS;
exports.sToT = sToT;
exports.init = init;<commit_msg>now logs long insactivativity times<commit_after>/*! This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */

"use strict";

const {setTimeout, clearTimeout, setInterval, clearInterval} = require("sdk/timers");
const {PersistentObject} = require("./storage");
const sp = require("sdk/simple-prefs");
const prefs = sp.prefs;
const {Cu, Cc, Ci} = require("chrome");
const unload = require("sdk/system/unload").when;
const {dumpUpdateObject, handleCmd, isEnabled} = require("./debug");
const observerService = Cc["@mozilla.org/observer-service;1"]
                      .getService(Ci.nsIObserverService);

const timerDataAddress = "timer.data";
let timerData;

const tickHandlers = [];
const userActiveHandlers = [];
const userInactiveHandlers = [];

let activityObs;
let activity;
let tickInterval;

let startTimeMs;

const init = function(){
  return PersistentObject("simplePref", {address: timerDataAddress})
  .then((obj)=> {
    timerData = obj;
  }).then(_init);
}

const _init = function(){
  console.log("initializing timer");

  console.time("timer init");

  startTimeMs = Number(require('./experiment').info.startTimeMs);

  if (!("elapsedTime" in timerData))
  timerData.elapsedTime = 0;

  timerData.elapsedTotalTime = elapsedTotalTime();

  if (!("silenceStart" in timerData))
    timerData.silenceStart = -1;

  silenceLeft(); //to update the silence status;

  watchActivity();

  tickInterval = setInterval(tick, prefs["timer.tick_length_s"]*1000);

  let f = function(pref){
    clearInterval(tickInterval);
    tickInterval = setInterval(tick, prefs["timer.tick_length_s"]*1000);
  };

  sp.on("timer.tick_length_s", f);
  unload(function(){sp.removeListener("timer.tick_length_s", f)});

  f = function(pref){
    timerData.elapsedTotalTime = elapsedTotalTime();
  };

  sp.on("experiment.startTimeMs", f);
  unload(function(){sp.removeListener("experiment.startTimeMs", f)});

  debug.init();
  debug.update({silenceStatus: true});

  console.timeEnd("timer init");
}

const elapsedTotalTime = function(stage){
  let exp = require("./experiment");

  if (!stage){
    let ett = (Date.now() - startTimeMs) / (1000 * prefs["timer.tick_length_s"]);

    return ett;
  }

  let stageTimes = exp.stageTimes;

  if (!stageTimes[stage])
    return 0;

  // in the stage
  if (!stageTimes[stage].duration){
    let ett = (Date.now() - Number(stageTimes[stage].start)) / (1000 * prefs["timer.tick_length_s"]);

    return ett;
  }

  // stage has ended
  let ett = stageTimes[stage].duration;

  return ett;
}

const watchActivity = function(){

  activity = {
    minor_inactive_s: 0,
    last_minor_inactive_s: 0,
    minor_active_s: 0,
    active_s: 0,
    active: false
  };

  let activeCounter, inactiveCounter;

  activityObs = {
    observe: function(subject, topic, data){
      switch(topic){
        case "user-interaction-active":

          // require('./stats').event("activeTick");
          // console.log("active " + elapsedTime());
          clearInterval(inactiveCounter);
          if (activity.minor_inactive_s) activity.last_minor_inactive_s = activity.minor_inactive_s;
          activity.minor_inactive_s = 0;
          if (!activeCounter){
            activeCounter = setInterval(function(){
              activity.minor_active_s += 1;
              activity.active_s += 1;
              debug.update();

            }, 1000);
          }
          activity.active = true;

          if (activity.minor_active_s == 0){ // to call the handlers only once
            userActiveHandlers.forEach((fn) => fn());
            require('./stats').event("activation", {}, {inactivity: activity.last_minor_inactive_s}, {inactivity: 'average'});

            if (isRecentlyActive(10, 10 * 60)){
              require('./logger').logLongInactivityBack({length: activity.last_minor_inactive_s});

            }
          }

        break;
        case "user-interaction-inactive":
          require('./stats').event("inactiveTick");
          console.log("user inactive (minor)");
          clearInterval(activeCounter);
          activeCounter = null;
          activity.minor_active_s = 0;
          inactiveCounter = setInterval(function(){
            activity.minor_inactive_s += 1;

            if (activity.active)
              activity.active_s += 1;

            if (activity.minor_inactive_s > prefs["timer.inactive_threshold_s"] && activity.active){
              deactivate();
            }

            if (activity.minor_inactive_s == 10 * 60){
              require('./logger').logLongInactivity();
            }

            debug.update();
          }, 1000);
        break;
      }
    },
    register: function(){
      observerService.addObserver(this, "user-interaction-active", false);
      observerService.addObserver(this, "user-interaction-inactive", false);
    },
    unregister: function(){
      observerService.removeObserver(this, "user-interaction-inactive");
      observerService.removeObserver(this, "user-interaction-active");
    }
  };

  unload(function(){if (activityObs) activityObs.unregister();});

  activityObs.register();
}

const tick = function(){

  let ett = elapsedTotalTime();
  timerData.elapsedTotalTime = ett;

  if (!activity.active){
    console.log("tick missed due to inactivity");
    require('./stats').event("missedTick");
    debug.update({silenceStatus: true});
    return;
  }

  require('./stats').event("tick");

  let et = timerData.elapsedTime + 1;

  timerData.elapsedTime = et;
  console.log("elapsed time: " + et + " ticks = " + et*prefs["timer.tick_length_s"]/60 + " minutes");

  tickHandlers.forEach(function(callback){
    callback(et, ett);
  });

  debug.update({silenceStatus: true});
}

const onTick = function(callback){
  tickHandlers.push(callback);
}

const onUserActive = function(fn){
  userActiveHandlers.push(fn);
}

//TOTHINK: major or minor inactive?
const onInactive = function(fn){

}

const elapsedTime = function(stage){
  if (!stage)
    return timerData.elapsedTime;

  let exp = require('./experiment');
  let stageTimes = exp.stageTimes;

  if (!stageTimes[stage])
    return 0;
  else
    return stageTimes[stage].elapsedTime;
}

const silence = function(){
  let ett = elapsedTotalTime();
  timerData.silenceStart = ett;

  console.log("silence started at " + ett + " ticks");
  console.log("silence is expected to end at " + Number(ett + silence_length_tick()) + " ticks");

  silenceLeft(); //to update all variables

  debug.update({silenceStatus: true});

  let info = {startett: ett};
  require('./logger').logSilenceStart(info);
  require('./stats').event("silence");
}

const silenceElapsed = function(){
  return (isSilent()? elapsedTotalTime() - timerData.silenceStart : 0);
}

const silenceLeft = function(){

  if (timerData.silenceStart == -1)
    return 0;

  let ett = elapsedTotalTime();

  let left = silence_length_tick() - ett + timerData.silenceStart;

  //updating silence status
  if (left <= 0)
    endSilence();

  return left;
}

const endSilence = function(){
  let start = timerData.silenceStart;

  timerData.silenceStart = -1;
  let ett = elapsedTotalTime();

  console.log("silence ended at " + ett + " ticks");

  let info = {startett: start, endett: ett, effectiveLength: ett-start};

  require('./logger').logSilenceEnd(info);

  require('./stats').event("silenceend");

  debug.update({silenceStatus: true});

  return ett;
}

const isSilent = function(){
  return (silenceLeft() > 0);
}

const isActive = function(){
  return activity.active;
}

const deactivate = function(){
  activity.active = false;
  activity.active_s = 0;
  require('./stats').event("deactivation");
  console.log("user inactive");
}

const isRecentlyActive = function(activity_threshold_s, inactivity_threshold_s /* optional */){

  if (activity.minor_active_s > activity_threshold_s)
    return false;

  if (inactivity_threshold_s && (activity.last_minor_inactive_s < inactivity_threshold_s))
    return false;

  return true;
}

const isCertainlyActive = function(){
  return (activity.minor_inactive_s < 10);
}

const randomTime = function(start, end){
  return Math.floor(Math.random()*(end-start) + start);
}

const silence_length_tick = function(){
  return sToT(timerData.silence_length_s);
}

const tToS = function(t){
  return t*prefs["timer.tick_length_s"];
}

const sToT = function(s){
  return s/prefs["timer.tick_length_s"];
}

const debug = {
  init: function(){
    handleCmd(this.parseCmd);
  },
  update: function(options){

    if (!isEnabled()) return;

    dumpUpdateObject(activity, {list: "Activity Status"});

    if (options && options.silenceStatus){
      let silenceObj = {
        isSilent: isSilent(),
        silenceStart: timerData.silenceStart,
        silenceEnd: isSilent()? timerData.silenceStart + silence_length_tick() : 0,
        silenceElapsed: silenceElapsed(),
        silenceLeft: silenceLeft()
      }
      dumpUpdateObject(silenceObj, {list: "Silence Status"});
    }
  },

  parseCmd: function(cmd){
    const patt = /([^ ]*) *(.*)/;
    let args = patt.exec(cmd);

    let subArgs;

    if (!args)  //does not match the basic pattern
      return false;

    let name = args[1];
    let params = args[2];

    switch(name){
      case "tick":
        tick();
        break;
      case "silence":
        silence();
        break;
      case "issilent":
        return isSilent();
        break;
      case "endsilence":
        return "silence ended at " + endSilence() + " ticks";
        break;
      case "iscertainlyactive":
        return isCertainlyActive();
        break;
      case "inactive":
        deactivate();
        observerService.notifyObservers(null, "user-interaction-inactive", "");
        debug.update();
        return "user inactivity forced"
        break;

      case "time":
        subArgs = params.split(" ");

        let act = subArgs[0];

        if (!subArgs[0])
          return "error: invalid use of time command.";

        switch(act){
          case "set":
            if (!subArgs[1])
              return "error: invalid use of time set command.";

            switch(subArgs[1]){
              case "et":
                if (!subArgs[2])
                  return "error: invalid use of time set et command.";

                timerData.elapsedTime = Number(subArgs[2]);
                return "elapsedTime set to " + Number(subArgs[2]);
              break;
              case "tick_length_s":
                if (!subArgs[2])
                  return "error: invalid use of time set tick_length_s command.";

                prefs["timer.tick_length_s"] = Number(subArgs[2]);
                return "new tick length in seconds: " + prefs["timer.tick_length_s"];
                break;
              case "st-diff":
                if (!subArgs[2])
                  return "error: invalid use of time set st command.";

                let startTimeMs = Number(require('./storage').osFileObjects['experiment.data'].startTimeMs);
                console.log(startTimeMs);

                let dateNum = startTimeMs
                              + Number(subArgs[2])*prefs["timer.tick_length_s"]*1000;

                console.log(dateNum);
                require('./storage').osFileObjects['experiment.data'].startTimeMs = String(dateNum);

                return "new start time set to: " + require('./timer').dateTimeToString(new Date(dateNum));
                break;
              default:
                return "error: invalid use of time set command.";
            }
            break;

          case "get":

            if (!subArgs[1])
              return "error: invalid use of time set command.";

            switch(subArgs[1]){
              case "et":
                return elapsedTime(subArgs[2]);
                break;
              case "ett":
                return elapsedTotalTime(subArgs[2]);
                break;

              default:

            }
            break;

          default:
            return "error: invalid use of time command.";
        }
      default:
        return undefined;
    }

    return " ";
  }

}

exports.elapsedTime = elapsedTime;
exports.elapsedTotalTime = elapsedTotalTime;
exports.isActive = isActive;
exports.isRecentlyActive = isRecentlyActive;
exports.isCertainlyActive = isCertainlyActive;
exports.isSilent = isSilent;
exports.silence = silence;
exports.endSilence = endSilence;
exports.randomTime = randomTime;
exports.onTick = onTick;
exports.onUserActive = onUserActive;
exports.tToS = tToS;
exports.sToT = sToT;
exports.init = init;<|endoftext|>