<commit_before>/*
 * Copyright (C) 2018 - present Instructure, Inc.
 *
 * This file is part of Canvas.
 *
 * Canvas is free software: you can redistribute it and/or modify it under
 * the terms of the GNU Affero General Public License as published by the Free
 * Software Foundation, version 3 of the License.
 *
 * Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
 * A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
 * details.
 *
 * You should have received a copy of the GNU Affero General Public License along
 * with this program. If not, see <http://www.gnu.org/licenses/>.
 */
import gql from 'graphql-tag'
import {bool, number, oneOf, oneOfType, shape, string, arrayOf} from 'prop-types'

// This ENV shape is for the controller's current show action. We'll have
// something different when assignments are being created, which is a different
// controller action. Think about this later.
export const EnvShape = shape({
  // Legacy controller populates this id as a number instead of a string. I'm not sure what uses it,
  // so I didn't change it. It's probably always a local id and won't cause overflow problems.
  ASSIGNMENT_ID: number.isRequired,
  PERMISSIONS: shape({
    context: shape({
      read_as_admin: bool.isRequired,
      manage_assignments: bool.isRequired
    }).isRequired,
    assignment: shape({
      update: bool.isRequired,
      submit: bool.isRequired
    }).isRequired
  }).isRequired
})

const userFields = gql`
  fragment UserFields on User {
    __typename
    gid: id
    lid: _id
    name
    shortName
    sortableName
    avatarUrl
    email
  }
`
const assignmentOverridesNodes = gql`
  fragment AssignmentOverrides on AssignmentOverrideConnection {
    nodes {
      gid: id
      lid: _id
      title
      dueAt
      lockAt
      unlockAt
      set {
        __typename
        ... on Section {
          lid: _id
          sectionName: name
        }
        ... on Group {
          lid: _id
          groupName: name
        }
        ... on AdhocStudents {
          students {
            lid: _id
            studentName: name
          }
        }
      }
    }
  }
`
export const TEACHER_QUERY = gql`
  query GetAssignment($assignmentLid: ID!) {
    assignment(id: $assignmentLid) {
      __typename
      id
      lid: _id
      gid: id
      name
      description
      dueAt
      unlockAt
      lockAt
      pointsPossible
      state
      needsGradingCount
      onlyVisibleToOverrides
      lockInfo {
        isLocked
      }
      assignmentGroup {
        lid: _id
        name
      }
      modules {
        lid: _id
        name
      }
      submissionTypes
      allowedExtensions
      allowedAttempts
      course {
        lid: _id
        modulesConnection(first: 0) {
          pageInfo {
            hasNextPage
          }
        }
        assignmentGroupsConnection(first: 0) {
          pageInfo {
            hasNextPage
          }
        }
      }
      assignmentOverrides {
        pageInfo {
          startCursor
          endCursor
          hasNextPage
          hasPreviousPage
        }
        ...AssignmentOverrides
      }
      submissions: submissionsConnection(
        filter: {states: [submitted, unsubmitted, graded, ungraded, pending_review]}
      ) {
        pageInfo {
          startCursor
          endCursor
          hasNextPage
          hasPreviousPage
        }
        nodes {
          gid: id
          lid: _id
          attempt
          submissionStatus
          grade
          gradingStatus
          score
          state
          excused
          latePolicyStatus
          submittedAt
          user {
            ...UserFields
          }
        }
      }
    }
  }
  ${userFields}
  ${assignmentOverridesNodes}
`

const assignmentGroup = gql`
  fragment CourseAssignmentGroups on AssignmentGroupConnection {
    __typename
    nodes {
      lid: _id
      gid: id
      name
      __typename
    }
  }
`

export const COURSE_ASSIGNMENT_GROUPS_QUERY = gql`
  query GetCourseAssignmentGroups($courseId: ID!, $cursor: String) {
    course(id: $courseId) {
      lid: _id
      gid: id
      assignmentGroupsConnection(first: 200, after: $cursor) {
        pageInfo {
          endCursor
          hasNextPage
        }
        ...CourseAssignmentGroups
      }
    }
  }
  ${assignmentGroup}
`
export const COURSE_ASSIGNMENT_GROUPS_QUERY_LOCAL = gql`
  query GetCourseAssignmentGroupsLocal($courseId: ID!) {
    course(id: $courseId) @client {
      lid: _id
      gid: id
      assignmentGroupsConnection(first: 200) {
        ...CourseAssignmentGroups
      }
    }
  }
  ${assignmentGroup}
`

const assignmentModule = gql`
  fragment CourseModules on ModuleConnection {
    __typename
    nodes {
      lid: _id
      gid: id
      name
      position
      __typename
    }
  }
`

// FYI, modules are areturned sorted by position
export const COURSE_MODULES_QUERY = gql`
  query GetCourseModules($courseId: ID!, $cursor: String) {
    course(id: $courseId) {
      lid: _id
      gid: id
      modulesConnection(first: 200, after: $cursor) {
        pageInfo {
          endCursor
          hasNextPage
        }
        ...CourseModules
      }
    }
  }
  ${assignmentModule}
`

export const COURSE_MODULES_QUERY_LOCAL = gql`
  query GetCourseModules($courseId: ID!) {
    course(id: $courseId) @client {
      lid: _id
      gid: id
      modulesConnection(first: 200) {
        ...CourseModules
      }
    }
  }
  ${assignmentModule}
`

export const STUDENT_SEARCH_QUERY = gql`
  query SearchStudents(
    $assignmentId: ID!
    $userSearch: String
    $orderBy: [SubmissionSearchOrder!]
  ) {
    assignment(id: $assignmentId) {
      id
      submissions: submissionsConnection(
        filter: {
          userSearch: $userSearch
          enrollmentTypes: StudentEnrollment
          states: [submitted, unsubmitted, graded, ungraded, pending_review]
        }
        orderBy: $orderBy
      ) {
        nodes {
          lid: _id
          gid: id
          attempt
          excused
          state
          score
          submittedAt
          submissionDraft {
            submissionAttempt
          }
          submissionStatus
          user {
            ...UserFields
          }
          submissionHistories: submissionHistoriesConnection(
            filter: {states: [graded, pending_review, submitted, ungraded]}
          ) {
            nodes {
              attempt
              score
              submittedAt
            }
          }
        }
      }
    }
  }
  ${userFields}
`

export const SET_WORKFLOW = gql`
  mutation SetWorkflow($id: ID!, $workflow: AssignmentState!) {
    updateAssignment(input: {id: $id, state: $workflow}) {
      assignment {
        __typename
        id
        state
      }
    }
  }
`

export const SAVE_ASSIGNMENT = gql`
  mutation SaveAssignment(
    $id: ID!
    $name: String
    $description: String
    $dueAt: DateTime
    $unlockAt: DateTime
    $lockAt: DateTime
    $pointsPossible: Float
    $state: AssignmentState
    $assignmentOverrides: [AssignmentOverrideCreateOrUpdate!]
  ) {
    updateAssignment(
      input: {
        id: $id
        name: $name
        description: $description
        dueAt: $dueAt
        unlockAt: $unlockAt
        lockAt: $lockAt
        pointsPossible: $pointsPossible
        state: $state
        assignmentOverrides: $assignmentOverrides
      }
    ) {
      assignment {
        __typename
        id
        lid: _id
        gid: id
        dueAt
        unlockAt
        lockAt
        name
        description
        pointsPossible
        state
        assignmentOverrides {
          ...AssignmentOverrides
        }
      }
    }
  }
  ${assignmentOverridesNodes}
`

export const CourseShape = shape({
  lid: string.isRequired
})

export const ModuleShape = shape({
  lid: string.isRequired,
  name: string.isRequired
})

export const AssignmentGroupShape = shape({
  lid: string.isRequired,
  name: string.isRequired
})

export const LockInfoShape = shape({
  isLocked: bool.isRequired
})

// TODO: is this the final shape?
//       what is required?
export const OverrideShape = shape({
  id: string,
  lid: string,
  title: string,
  dueAt: string,
  lockAt: string,
  unlockAt: string,
  submissionTypes: arrayOf(string), // currently copied from the assignment
  allowedAttempts: number, // currently copied from the assignment
  allowedExtensions: arrayOf(string), // currently copied from the assignment
  set: shape({
    lid: string,
    name: string,
    __typename: oneOf(['Section', 'Group', 'AdhocStudents'])
  })
})

export const UserShape = shape({
  lid: string,
  gid: string,
  name: string,
  shortName: string,
  sortableName: string,
  avatarUrl: string,
  email: string
})

export const SubmissionHistoryShape = shape({
  attempt: number,
  score: number,
  submittedAt: string
})

export const SubmissionDraftShape = shape({
  submissionAttempt: string
})

export const SubmissionShape = shape({
  gid: string,
  lid: string,
  attempt: number,
  submissionStatus: oneOf(['resubmitted', 'missing', 'late', 'submitted', 'unsubmitted']),
  grade: string,
  gradingStatus: oneOf([null, 'excused', 'needs_review', 'needs_grading', 'graded']),
  score: number,
  state: oneOf(['submitted', 'unsubmitted', 'pending_review', 'graded', 'deleted']),
  excused: bool,
  latePolicyStatus: oneOf([null, 'missing']),
  submittedAt: string, // datetime
  user: UserShape,
  submissionHistoriesConnection: shape({
    nodes: arrayOf(SubmissionHistoryShape)
  }),
  submissionDraft: SubmissionDraftShape
})

export const TeacherAssignmentShape = shape({
  lid: string,
  name: string,
  pointsPossible: oneOfType([number, string]),
  dueAt: string,
  lockAt: string,
  unlockAt: string,
  description: string,
  state: oneOf(['published', 'unpublished', 'deleted']).isRequired,
  assignmentGroup: AssignmentGroupShape,
  modules: arrayOf(ModuleShape).isRequired,
  course: CourseShape.isRequired, // not edited by the teacher
  lockInfo: LockInfoShape.isRequired,
  submissionTypes: arrayOf(string).isRequired,
  allowedExtensions: arrayOf(string).isRequired,
  allowedAttempts: number,
  assignmentOverrides: shape({
    nodes: arrayOf(OverrideShape)
  }).isRequired,
  submissions: shape({
    nodes: arrayOf(SubmissionShape)
  }).isRequired
})

export const StudentSearchQueryShape = shape({
  assignmentId: string.isRequired,
  userSearch: string,
  orderBy: arrayOf(
    shape({
      field: string,
      direction: oneOf(['ascending', 'descending'])
    })
  )
})

// custom proptype validator
// requires the property if the component's prop.variant === 'detail'
// this is used in components that have summary and detail renderings
export function requiredIfDetail(props, propName, componentName) {
  if (!props[propName] && props.variant === 'detail') {
    return new Error(
      `The prop ${propName} is required on ${componentName} if the variant is 'detail'`
    )
  }
}
<commit_msg>A2: fix "everyone else" dates<commit_after>/*
 * Copyright (C) 2018 - present Instructure, Inc.
 *
 * This file is part of Canvas.
 *
 * Canvas is free software: you can redistribute it and/or modify it under
 * the terms of the GNU Affero General Public License as published by the Free
 * Software Foundation, version 3 of the License.
 *
 * Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
 * A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
 * details.
 *
 * You should have received a copy of the GNU Affero General Public License along
 * with this program. If not, see <http://www.gnu.org/licenses/>.
 */
import gql from 'graphql-tag'
import {bool, number, oneOf, oneOfType, shape, string, arrayOf} from 'prop-types'

// This ENV shape is for the controller's current show action. We'll have
// something different when assignments are being created, which is a different
// controller action. Think about this later.
export const EnvShape = shape({
  // Legacy controller populates this id as a number instead of a string. I'm not sure what uses it,
  // so I didn't change it. It's probably always a local id and won't cause overflow problems.
  ASSIGNMENT_ID: number.isRequired,
  PERMISSIONS: shape({
    context: shape({
      read_as_admin: bool.isRequired,
      manage_assignments: bool.isRequired
    }).isRequired,
    assignment: shape({
      update: bool.isRequired,
      submit: bool.isRequired
    }).isRequired
  }).isRequired
})

const userFields = gql`
  fragment UserFields on User {
    __typename
    gid: id
    lid: _id
    name
    shortName
    sortableName
    avatarUrl
    email
  }
`
const assignmentOverridesNodes = gql`
  fragment AssignmentOverrides on AssignmentOverrideConnection {
    nodes {
      gid: id
      lid: _id
      title
      dueAt
      lockAt
      unlockAt
      set {
        __typename
        ... on Section {
          lid: _id
          sectionName: name
        }
        ... on Group {
          lid: _id
          groupName: name
        }
        ... on AdhocStudents {
          students {
            lid: _id
            studentName: name
          }
        }
      }
    }
  }
`
export const TEACHER_QUERY = gql`
  query GetAssignment($assignmentLid: ID!) {
    assignment(id: $assignmentLid) {
      __typename
      id
      lid: _id
      gid: id
      name
      description
      dueAt(applyOverrides: false)
      unlockAt(applyOverrides: false)
      lockAt(applyOverrides: false)
      pointsPossible
      state
      needsGradingCount
      onlyVisibleToOverrides
      lockInfo {
        isLocked
      }
      assignmentGroup {
        lid: _id
        name
      }
      modules {
        lid: _id
        name
      }
      submissionTypes
      allowedExtensions
      allowedAttempts
      course {
        lid: _id
        modulesConnection(first: 0) {
          pageInfo {
            hasNextPage
          }
        }
        assignmentGroupsConnection(first: 0) {
          pageInfo {
            hasNextPage
          }
        }
      }
      assignmentOverrides {
        pageInfo {
          startCursor
          endCursor
          hasNextPage
          hasPreviousPage
        }
        ...AssignmentOverrides
      }
      submissions: submissionsConnection(
        filter: {states: [submitted, unsubmitted, graded, ungraded, pending_review]}
      ) {
        pageInfo {
          startCursor
          endCursor
          hasNextPage
          hasPreviousPage
        }
        nodes {
          gid: id
          lid: _id
          attempt
          submissionStatus
          grade
          gradingStatus
          score
          state
          excused
          latePolicyStatus
          submittedAt
          user {
            ...UserFields
          }
        }
      }
    }
  }
  ${userFields}
  ${assignmentOverridesNodes}
`

const assignmentGroup = gql`
  fragment CourseAssignmentGroups on AssignmentGroupConnection {
    __typename
    nodes {
      lid: _id
      gid: id
      name
      __typename
    }
  }
`

export const COURSE_ASSIGNMENT_GROUPS_QUERY = gql`
  query GetCourseAssignmentGroups($courseId: ID!, $cursor: String) {
    course(id: $courseId) {
      lid: _id
      gid: id
      assignmentGroupsConnection(first: 200, after: $cursor) {
        pageInfo {
          endCursor
          hasNextPage
        }
        ...CourseAssignmentGroups
      }
    }
  }
  ${assignmentGroup}
`
export const COURSE_ASSIGNMENT_GROUPS_QUERY_LOCAL = gql`
  query GetCourseAssignmentGroupsLocal($courseId: ID!) {
    course(id: $courseId) @client {
      lid: _id
      gid: id
      assignmentGroupsConnection(first: 200) {
        ...CourseAssignmentGroups
      }
    }
  }
  ${assignmentGroup}
`

const assignmentModule = gql`
  fragment CourseModules on ModuleConnection {
    __typename
    nodes {
      lid: _id
      gid: id
      name
      position
      __typename
    }
  }
`

// FYI, modules are areturned sorted by position
export const COURSE_MODULES_QUERY = gql`
  query GetCourseModules($courseId: ID!, $cursor: String) {
    course(id: $courseId) {
      lid: _id
      gid: id
      modulesConnection(first: 200, after: $cursor) {
        pageInfo {
          endCursor
          hasNextPage
        }
        ...CourseModules
      }
    }
  }
  ${assignmentModule}
`

export const COURSE_MODULES_QUERY_LOCAL = gql`
  query GetCourseModules($courseId: ID!) {
    course(id: $courseId) @client {
      lid: _id
      gid: id
      modulesConnection(first: 200) {
        ...CourseModules
      }
    }
  }
  ${assignmentModule}
`

export const STUDENT_SEARCH_QUERY = gql`
  query SearchStudents(
    $assignmentId: ID!
    $userSearch: String
    $orderBy: [SubmissionSearchOrder!]
  ) {
    assignment(id: $assignmentId) {
      id
      submissions: submissionsConnection(
        filter: {
          userSearch: $userSearch
          enrollmentTypes: StudentEnrollment
          states: [submitted, unsubmitted, graded, ungraded, pending_review]
        }
        orderBy: $orderBy
      ) {
        nodes {
          lid: _id
          gid: id
          attempt
          excused
          state
          score
          submittedAt
          submissionDraft {
            submissionAttempt
          }
          submissionStatus
          user {
            ...UserFields
          }
          submissionHistories: submissionHistoriesConnection(
            filter: {states: [graded, pending_review, submitted, ungraded]}
          ) {
            nodes {
              attempt
              score
              submittedAt
            }
          }
        }
      }
    }
  }
  ${userFields}
`

export const SET_WORKFLOW = gql`
  mutation SetWorkflow($id: ID!, $workflow: AssignmentState!) {
    updateAssignment(input: {id: $id, state: $workflow}) {
      assignment {
        __typename
        id
        state
      }
    }
  }
`

export const SAVE_ASSIGNMENT = gql`
  mutation SaveAssignment(
    $id: ID!
    $name: String
    $description: String
    $dueAt: DateTime
    $unlockAt: DateTime
    $lockAt: DateTime
    $pointsPossible: Float
    $state: AssignmentState
    $assignmentOverrides: [AssignmentOverrideCreateOrUpdate!]
  ) {
    updateAssignment(
      input: {
        id: $id
        name: $name
        description: $description
        dueAt: $dueAt
        unlockAt: $unlockAt
        lockAt: $lockAt
        pointsPossible: $pointsPossible
        state: $state
        assignmentOverrides: $assignmentOverrides
      }
    ) {
      assignment {
        __typename
        id
        lid: _id
        gid: id
        dueAt
        unlockAt
        lockAt
        name
        description
        pointsPossible
        state
        assignmentOverrides {
          ...AssignmentOverrides
        }
      }
    }
  }
  ${assignmentOverridesNodes}
`

export const CourseShape = shape({
  lid: string.isRequired
})

export const ModuleShape = shape({
  lid: string.isRequired,
  name: string.isRequired
})

export const AssignmentGroupShape = shape({
  lid: string.isRequired,
  name: string.isRequired
})

export const LockInfoShape = shape({
  isLocked: bool.isRequired
})

// TODO: is this the final shape?
//       what is required?
export const OverrideShape = shape({
  id: string,
  lid: string,
  title: string,
  dueAt: string,
  lockAt: string,
  unlockAt: string,
  submissionTypes: arrayOf(string), // currently copied from the assignment
  allowedAttempts: number, // currently copied from the assignment
  allowedExtensions: arrayOf(string), // currently copied from the assignment
  set: shape({
    lid: string,
    name: string,
    __typename: oneOf(['Section', 'Group', 'AdhocStudents'])
  })
})

export const UserShape = shape({
  lid: string,
  gid: string,
  name: string,
  shortName: string,
  sortableName: string,
  avatarUrl: string,
  email: string
})

export const SubmissionHistoryShape = shape({
  attempt: number,
  score: number,
  submittedAt: string
})

export const SubmissionDraftShape = shape({
  submissionAttempt: string
})

export const SubmissionShape = shape({
  gid: string,
  lid: string,
  attempt: number,
  submissionStatus: oneOf(['resubmitted', 'missing', 'late', 'submitted', 'unsubmitted']),
  grade: string,
  gradingStatus: oneOf([null, 'excused', 'needs_review', 'needs_grading', 'graded']),
  score: number,
  state: oneOf(['submitted', 'unsubmitted', 'pending_review', 'graded', 'deleted']),
  excused: bool,
  latePolicyStatus: oneOf([null, 'missing']),
  submittedAt: string, // datetime
  user: UserShape,
  submissionHistoriesConnection: shape({
    nodes: arrayOf(SubmissionHistoryShape)
  }),
  submissionDraft: SubmissionDraftShape
})

export const TeacherAssignmentShape = shape({
  lid: string,
  name: string,
  pointsPossible: oneOfType([number, string]),
  dueAt: string,
  lockAt: string,
  unlockAt: string,
  description: string,
  state: oneOf(['published', 'unpublished', 'deleted']).isRequired,
  assignmentGroup: AssignmentGroupShape,
  modules: arrayOf(ModuleShape).isRequired,
  course: CourseShape.isRequired, // not edited by the teacher
  lockInfo: LockInfoShape.isRequired,
  submissionTypes: arrayOf(string).isRequired,
  allowedExtensions: arrayOf(string).isRequired,
  allowedAttempts: number,
  assignmentOverrides: shape({
    nodes: arrayOf(OverrideShape)
  }).isRequired,
  submissions: shape({
    nodes: arrayOf(SubmissionShape)
  }).isRequired
})

export const StudentSearchQueryShape = shape({
  assignmentId: string.isRequired,
  userSearch: string,
  orderBy: arrayOf(
    shape({
      field: string,
      direction: oneOf(['ascending', 'descending'])
    })
  )
})

// custom proptype validator
// requires the property if the component's prop.variant === 'detail'
// this is used in components that have summary and detail renderings
export function requiredIfDetail(props, propName, componentName) {
  if (!props[propName] && props.variant === 'detail') {
    return new Error(
      `The prop ${propName} is required on ${componentName} if the variant is 'detail'`
    )
  }
}
<|endoftext|>


<commit_before>/*
 * Say Cheese!
 * Lee Machin, 2012
 * http://leemach.in, http://new-bamboo.co.uk
 *
 * Minimal javascript library for integrating a webcam and snapshots into your app.
 *
 * Handles starting up the webcam and rendering the element, and also capturing shots
 * in a separate canvas element.
 *
 * Depends on video and canvas, and of course, getUserMedia. It's unlikely to work
 * on anything but the newest browsers.
 */

var SayCheese = (function() {

  var SayCheese;

  /* Check for the existence of the userMedia feature. */
  function userMediaFeatureExists() {
    return 'getUserMedia' in navigator ||  'webkitGetUserMedia' in navigator;
  };

  SayCheese = function SayCheese() {
    if (userMediaFeatureExists()) {
      // do something
    } else {
      // should make this more graceful in future
      throw new Error("getUserMedia() is not supported in this browser");
    }

    return this;
  };

  /* the getUserMedia function is different on Webkit browsers, so we have to
   * do a bit of faffing about to make sure we call the right one.
   */
  SayCheese.prototype.getUserMedia = function getUserMedia(success, error) {
    return (function() {
      // have to re-bind navigator because the context will be lost, and
      // we'll get illegal invocation errors.
      return (navigator.getUserMedia || navigator.webkitGetUserMedia).bind(navigator);
    })().call(this, { video: true }, success, error);
  };

  /* Webkit behaves differently as regards getting a proper video URL. So we have
   * to have another check for it.
   */
  SayCheese.prototype.getStreamUrl = function getStreamUrl(stream) {
    url = (function() {
      return (window.URL || window.webkitURL);
    })();

    return (url && url.createObjectURL) ? url.createObjectURL(stream) : stream;
  };

  /* The viewfinder is the element we use to preview the webcam stream */
  SayCheese.prototype.createVideo = function() {
    element = document.createElement('video');
    element.autoplay = true;
    return element;
  };

  /* The canvas is used for taking 'photos' */
  SayCheese.prototype.createCanvas = function() {
    element = document.createElement('canvas');
    return element;
  };

  /* Start up the stream, if possible */
  SayCheese.prototype.start = function start() {
    var success = function userMediaSuccess(stream) {
      console.log('woop woop', stream);
    };

    var error = function userMediaError() {
      console.log('nooooooooo');
    };

    this.getUserMedia(success, error);
  };

  /* Stop it */
  SayCheese.prototype.stop = function stop() {

  };

  /* Take a snapshot of the current state of the stream */
  SayCheese.prototype.takeSnapshot = function takeSnapshot() {

  };

  return SayCheese;

}).call(this);<commit_msg>made the success callback do something useful<commit_after>/*
 * Say Cheese!
 * Lee Machin, 2012
 * http://leemach.in, http://new-bamboo.co.uk
 *
 * Minimal javascript library for integrating a webcam and snapshots into your app.
 *
 * Handles starting up the webcam and rendering the element, and also capturing shots
 * in a separate canvas element.
 *
 * Depends on video and canvas, and of course, getUserMedia. It's unlikely to work
 * on anything but the newest browsers.
 */

var SayCheese = (function() {

  var SayCheese;

  /* Check for the existence of the userMedia feature. */
  function userMediaFeatureExists() {
    return 'getUserMedia' in navigator ||  'webkitGetUserMedia' in navigator;
  };

  SayCheese = function SayCheese() {
    if (userMediaFeatureExists()) {
      // do something
    } else {
      // should make this more graceful in future
      throw new Error("getUserMedia() is not supported in this browser");
    }

    return this;
  };

  /* the getUserMedia function is different on Webkit browsers, so we have to
   * do a bit of faffing about to make sure we call the right one.
   */
  SayCheese.prototype.getUserMedia = function getUserMedia(success, error) {
    return (function() {
      // have to re-bind navigator because the context will be lost, and
      // we'll get illegal invocation errors.
      return (navigator.getUserMedia || navigator.webkitGetUserMedia).bind(navigator);
    })().call(this, { video: true }, success, error);
  };

  /* Webkit behaves differently as regards getting a proper video URL. So we have
   * to have another check for it.
   */
  SayCheese.prototype.getStreamUrl = function getStreamUrl(stream) {
    url = (function() {
      return (window.URL || window.webkitURL);
    })();

    return (url && url.createObjectURL) ? url.createObjectURL(stream) : stream;
  };

  /* The viewfinder is the element we use to preview the webcam stream */
  SayCheese.prototype.createVideo = function() {
    element = document.createElement('video');
    element.autoplay = true;
    return element;
  };

  /* The canvas is used for taking 'photos' */
  SayCheese.prototype.createCanvas = function() {
    element = document.createElement('canvas');
    return element;
  };

  /* Start up the stream, if possible */
  SayCheese.prototype.start = function start() {
    var success = function userMediaSuccess(stream) {
     this.viewfinder = this.createVideo();
     this.viewfinder.src = this.getStreamUrl(stream);
     document.appendChild(this.viewfinder);
    }.bind(this);

    /* error is also called when someone denies access */
    var error = function userMediaError() {
      console.log('nooooooooo');
    }.bind(this);

    this.getUserMedia(success, error);
  };

  /* Stop it */
  SayCheese.prototype.stop = function stop() {

  };

  /* Take a snapshot of the current state of the stream */
  SayCheese.prototype.takeSnapshot = function takeSnapshot() {

  };

  return SayCheese;

}).call(this);<|endoftext|>


<commit_before>/*
 * Say Cheese!
 * Lee Machin, 2012
 * http://leemach.in, http://new-bamboo.co.uk
 *
 * Minimal javascript library for integrating a webcam and snapshots into your app.
 *
 * Handles starting up the webcam and rendering the element, and also capturing shots
 * in a separate canvas element.
 *
 * Depends on video and canvas, and of course, getUserMedia. It's unlikely to work
 * on anything but the newest browsers.
 */

var SayCheese = (function($) {

  var SayCheese;

  /* Check for the existence of the userMedia feature. */
  function userMediaFeatureExists() {
    return 'getUserMedia'       in navigator ||
           'webkitGetUserMedia' in navigator ||
           'mozGetUserMedia'    in navigator;
  };

  function eventCoords(evt) {
     return { x: evt.offsetX || evt.layerX, y: evt.offsetY || evt.layerY };
  };

  SayCheese = function SayCheese(element, options) {
    if (userMediaFeatureExists()) {
      this.viewfinder = {},
      this.snapshots = [],
      this.canvas = null,
      this.context = null,
      this.video = null,
      this.events = {},
      this.options = {
        dynamicViewfinder: false
      };

      this.element = document.querySelectorAll(element)[0];
      this.element.style.position = 'relative';
      this.setOptions(options);
    } else {
      // should make this more graceful in future
      throw new Error("getUserMedia() is not supported in this browser");
    }

    return this;
  };

  /**
   * Reimplementing events? No. This is intended to make it easier to
   * replace jQuery with a different solution in future, should that
   * become necessary, and also minimises the impact of the framework
   * on the rest of the code.
   */
  SayCheese.prototype.on = function on(evt, handler) {
    return $(this).on(evt, handler);
  };

  SayCheese.prototype.off = function off(evt, handler) {
    return $(this).off(evt, handler);
  };

  SayCheese.prototype.trigger = function trigger(evt, data) {
    // bubbling up the DOM makes things go a bit crazy. This assumes
    // preventDefault
    return $(this).triggerHandler(evt, data);
  };

  SayCheese.prototype.merge = function merge(target, object) {
    return $.extend(target, object);
  }

  SayCheese.prototype.setOptions = function setOptions(options) {
    this.options = this.merge(this.options, options);
  }

  SayCheese.prototype.getUserMedia = function getUserMedia(success, error) {
    return (function() {
      return (navigator.getUserMedia ||
              navigator.webkitGetUserMedia ||
              navigator.mozGetUserMedia).bind(navigator);
    })().call(this, { video: true }, success, error);
  };

  SayCheese.prototype.getStreamUrl = function getStreamUrl(stream) {
    url = (function() {
      return (window.URL || window.webkitURL);
    })();

    return (url && url.createObjectURL) ? url.createObjectURL(stream) : stream;
  };

  SayCheese.prototype.createVideo = function createVideo() {
    this.video = document.createElement('video');
    this.video.autoplay = true;
  };

  SayCheese.prototype.setupCanvas = function setupCanvas() {
    this.canvas = document.createElement('canvas');
    this.canvas.width = this.video.offsetWidth;
    this.canvas.height = this.video.offsetHeight;

    this.canvas.style.position = 'absolute';
    this.canvas.style.top = this.video.offsetTop;
    this.canvas.style.left = this.video.offsetLeft;

    this.context = this.canvas.getContext('2d');

    this.element.appendChild(this.canvas);

    this.initDefaultViewfinder();

    if (this.options.dynamicViewfinder == true) {
      this.initDynamicViewfinder();
    }

    // we're now all set up, so dispatch the ready event
    return this.trigger('start');
  };

  SayCheese.prototype.watermark = function watermark() {
    this.context.fillStyle = '#ee5f00';
    this.context.font = 'bold 32px Helvetica';

    x = this.video.offsetWidth - 100;
    y = this.video.offsetHeight - 15;

    this.context.fillText('demo', x, y);
  };

  /*
   * The default viewfinder is just the exact size of the video
   */
  SayCheese.prototype.initDefaultViewfinder = function initDefaultViewfinder() {
    return this.viewfinder = {
      startX: 0,
      startY: 0,
      endX: this.video.offsetWidth,
      endY: this.video.offsetHeight,
      width: this.video.offsetWidth,
      height: this.video.offsetHeight
    };
  };

  /*
   *  This viewfinder can be resized to capture select portions of the video stream
   */
  SayCheese.prototype.initDynamicViewfinder = function initDynamicViewfinder() {
    // track the dragging status when events are fired
    var isDragging = false,
        box = {};

    var start = function start(evt) {
      evt.preventDefault();
      var coords = eventCoords(evt);
      box.startX = coords.x;
      box.startY = coords.y;
      isDragging = true;
    }.bind(this);

    var stop = function stop(evt) {
      evt.preventDefault();
      isDragging = false;

      var coords = eventCoords(evt);
      box.endX = coords.x,
      box.endY = coords.y;

      this.viewfinder = box;
      this.trigger('change');
    }.bind(this);

    var draw = function draw(evt) {
      evt.preventDefault();
      var coords = eventCoords(evt);
      if (isDragging) {
        // used absolutely, can show dimensions. Use endX and endY for
        // mapping the visible canvas area to a new canvas.
        box.width = coords.x - box.startX,
        box.height = coords.y - box.startY;

        // draw the shade
        this.context.globalCompositeOperation = 'xor';
        this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
        this.context.fillStyle = 'rgba(0, 0, 0, .8)';
        this.context.fillRect(0, 0, this.canvas.width, this.canvas.height);

        // draw the window
        this.context.strokeStyle = 'rgba(255, 255, 255, .5)';
        this.context.lineWidth = 2;
        this.context.strokeRect(box.startX, box.startY, box.width, box.height);
        this.context.fillRect(box.startX, box.startY, box.width, box.height);
      }
    }.bind(this);

    this.canvas.addEventListener('mousedown', start, true);
    this.canvas.addEventListener('mouseup', stop, true);
    this.canvas.addEventListener('mousemove', draw, true);

    // add touch events if they're there
    if ('ontouchstart' in window) {
      this.canvas.addEventListener('touchstart', start, true);
      this.canvas.addEventListener('touchend', stop, true);
      this.canvas.addEventListener('touchmove', draw, true);
    }

    // make the cursor a crosshair to make it more clear what can be done
    this.canvas.style.cursor = 'crosshair';
  };

  SayCheese.prototype.resetViewfinder = function() {
    this.context.globalCompositeOperation = 'source-over';
    this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);

    this.viewfinder = {
      startX: 0,
      startY: 0,
      endX: this.video.offsetWidth,
      endY: this.video.offsetHeight,
      width: this.video.offsetWidth,
      height: this.video.offsetHeight
    }

    this.trigger('change');
  };

  /* Take a snapshot of the current state of the stream */
  SayCheese.prototype.takeSnapshot = function takeSnapshot(callback) {
    var snapshot = document.createElement('canvas'),
        ctx      = snapshot.getContext('2d');

    snapshot.width  = Math.abs(this.viewfinder.width),
    snapshot.height = Math.abs(this.viewfinder.height);

    ctx.drawImage(this.video,
                  Math.min(this.viewfinder.startX, this.viewfinder.endX),
                  Math.min(this.viewfinder.startY, this.viewfinder.endY),
                  snapshot.width,
                  snapshot.height,
                  0,
                  0,
                  snapshot.width,
                  snapshot.height);

    this.snapshots.push(snapshot);
    this.trigger('snapshot', snapshot);

    if (callback) {
      callback.call(this, snapshot);
    }

    ctx = null;
  };

  /* Start up the stream, if possible */
  SayCheese.prototype.start = function start(callback) {
    var success = function success(stream) {
      this.createVideo();

      // video width and height don't exist until metadata is loaded
      this.video.addEventListener('loadedmetadata', this.setupCanvas.bind(this), false);

      this.video.src = this.getStreamUrl(stream);
      this.element.appendChild(this.video);
    }.bind(this);

    /* error is also called when someone denies access */
    var error = function error() {
      this.trigger('error', arguments);
    }.bind(this);

    // add the callback to the start event if one is supplied.
    if (callback) {
      this.on('start', callback);
    }

    this.getUserMedia(success, error);
  };

  /* Stop it - TODO: figure out how to actually disable the stream */
  SayCheese.prototype.stop = function stop() {
    this.video = null;
    document.body.removeChild(video);
  };

  return SayCheese;

})($);
<commit_msg>clear up comments and whitespace<commit_after>/*
 * Say Cheese!
 * Lee Machin, 2012
 * http://leemach.in, http://new-bamboo.co.uk
 *
 * Minimal javascript library for integrating a webcam and snapshots into your app.
 *
 * Handles starting up the webcam and rendering the element, and also capturing shots
 * in a separate canvas element.
 *
 * Depends on video and canvas, and of course, getUserMedia. It's unlikely to work
 * on anything but the newest browsers.
 */

var SayCheese = (function($) {

  var SayCheese;

  /* Check for the existence of the userMedia feature. */
  function userMediaFeatureExists() {
    return 'getUserMedia'       in navigator ||
           'webkitGetUserMedia' in navigator ||
           'mozGetUserMedia'    in navigator;
  };

  function eventCoords(evt) {
     return { x: evt.offsetX || evt.layerX, y: evt.offsetY || evt.layerY };
  };

  SayCheese = function SayCheese(element, options) {
    if (userMediaFeatureExists()) {
      this.viewfinder = {},
      this.snapshots = [],
      this.canvas = null,
      this.context = null,
      this.video = null,
      this.events = {},
      this.options = {
        dynamicViewfinder: false
      };

      this.element = document.querySelectorAll(element)[0];
      this.element.style.position = 'relative';
      this.setOptions(options);
    } else {
      // should make this more graceful in future
      throw new Error("getUserMedia() is not supported in this browser");
    }

    return this;
  };

  /**
   * Wrap the jQuery stuff so as to minimise the impact of the framework on
   * the rest of the code.
   */
  SayCheese.prototype.on = function on(evt, handler) {
    return $(this).on(evt, handler);
  };

  SayCheese.prototype.off = function off(evt, handler) {
    return $(this).off(evt, handler);
  };

  SayCheese.prototype.trigger = function trigger(evt, data) {
    // bubbling up the DOM makes things go a bit crazy. This assumes
    // preventDefault
    return $(this).triggerHandler(evt, data);
  };

  SayCheese.prototype.merge = function merge(target, object) {
    return $.extend(target, object);
  }

  SayCheese.prototype.setOptions = function setOptions(options) {
    this.options = this.merge(this.options, options);
  }

  SayCheese.prototype.getUserMedia = function getUserMedia(success, error) {
    return (function() {
      return (navigator.getUserMedia ||
              navigator.webkitGetUserMedia ||
              navigator.mozGetUserMedia).bind(navigator);
    })().call(this, { video: true }, success, error);
  };

  SayCheese.prototype.getStreamUrl = function getStreamUrl(stream) {
    url = (function() {
      return (window.URL || window.webkitURL);
    })();

    return (url && url.createObjectURL) ? url.createObjectURL(stream) : stream;
  };

  SayCheese.prototype.createVideo = function createVideo() {
    this.video = document.createElement('video');
    this.video.autoplay = true;
  };

  SayCheese.prototype.setupCanvas = function setupCanvas() {
    this.canvas = document.createElement('canvas');
    this.canvas.width = this.video.offsetWidth;
    this.canvas.height = this.video.offsetHeight;

    this.canvas.style.position = 'absolute';
    this.canvas.style.top = this.video.offsetTop;
    this.canvas.style.left = this.video.offsetLeft;

    this.context = this.canvas.getContext('2d');

    this.element.appendChild(this.canvas);

    this.initDefaultViewfinder();

    if (this.options.dynamicViewfinder == true) {
      this.initDynamicViewfinder();
    }

    return this.trigger('start');
  };

  /* The default viewfinder is just the exact size of the video */
  SayCheese.prototype.initDefaultViewfinder = function initDefaultViewfinder() {
    return this.viewfinder = {
      startX: 0,
      startY: 0,
      endX: this.video.offsetWidth,
      endY: this.video.offsetHeight,
      width: this.video.offsetWidth,
      height: this.video.offsetHeight
    };
  };

  /* This viewfinder can be resized to capture specific parts of the video stream */
  SayCheese.prototype.initDynamicViewfinder = function initDynamicViewfinder() {
    var isDragging = false,
        box = {};

    var start = function start(evt) {
      evt.preventDefault();
      var coords = eventCoords(evt);
      box.startX = coords.x;
      box.startY = coords.y;
      isDragging = true;
    }.bind(this);

    var stop = function stop(evt) {
      evt.preventDefault();
      isDragging = false;

      var coords = eventCoords(evt);
      box.endX = coords.x,
      box.endY = coords.y;

      this.viewfinder = box;
      this.trigger('change');
    }.bind(this);

    var draw = function draw(evt) {
      evt.preventDefault();
      var coords = eventCoords(evt);
      if (isDragging) {
        // used absolutely, can show dimensions. Use endX and endY for
        // mapping the visible canvas area to a new canvas.
        box.width = coords.x - box.startX,
        box.height = coords.y - box.startY;

        // draw the shade
        this.context.globalCompositeOperation = 'xor';
        this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
        this.context.fillStyle = 'rgba(0, 0, 0, .8)';
        this.context.fillRect(0, 0, this.canvas.width, this.canvas.height);

        // draw the window
        this.context.strokeStyle = 'rgba(255, 255, 255, .5)';
        this.context.lineWidth = 2;
        this.context.strokeRect(box.startX, box.startY, box.width, box.height);
        this.context.fillRect(box.startX, box.startY, box.width, box.height);
      }
    }.bind(this);

    this.canvas.addEventListener('mousedown', start, true);
    this.canvas.addEventListener('mouseup', stop, true);
    this.canvas.addEventListener('mousemove', draw, true);

    // add touch events if they're there
    if ('ontouchstart' in window) {
      this.canvas.addEventListener('touchstart', start, true);
      this.canvas.addEventListener('touchend', stop, true);
      this.canvas.addEventListener('touchmove', draw, true);
    }

    // make the cursor a crosshair to make it more clear what can be done
    this.canvas.style.cursor = 'crosshair';
  };

  SayCheese.prototype.resetViewfinder = function() {
    this.context.globalCompositeOperation = 'source-over';
    this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);

    this.viewfinder = {
      startX: 0,
      startY: 0,
      endX: this.video.offsetWidth,
      endY: this.video.offsetHeight,
      width: this.video.offsetWidth,
      height: this.video.offsetHeight
    }

    this.trigger('change');
  };

  SayCheese.prototype.takeSnapshot = function takeSnapshot(callback) {
    var snapshot = document.createElement('canvas'),
        ctx      = snapshot.getContext('2d');

    snapshot.width  = Math.abs(this.viewfinder.width),
    snapshot.height = Math.abs(this.viewfinder.height);

    ctx.drawImage(this.video,
                  Math.min(this.viewfinder.startX, this.viewfinder.endX),
                  Math.min(this.viewfinder.startY, this.viewfinder.endY),
                  snapshot.width,
                  snapshot.height,
                  0,
                  0,
                  snapshot.width,
                  snapshot.height);

    this.snapshots.push(snapshot);
    this.trigger('snapshot', snapshot);

    if (callback) {
      callback.call(this, snapshot);
    }

    ctx = null;
  };

  /* Start up the stream, if possible */
  SayCheese.prototype.start = function start(callback) {
    var success = function success(stream) {
      this.createVideo();

      // video width and height don't exist until metadata is loaded
      this.video.addEventListener('loadedmetadata', this.setupCanvas.bind(this));

      this.video.src = this.getStreamUrl(stream);
      this.element.appendChild(this.video);
    }.bind(this);

    /* error is also called when someone denies access */
    var error = function error() {
      this.trigger('error', arguments);
    }.bind(this);

    // add the callback to the start event if one is supplied.
    if (callback) {
      this.on('start', callback);
    }

    return this.getUserMedia(success, error);
  };

  /* Stop it - TODO: figure out how to actually disable the stream */
  SayCheese.prototype.stop = function stop() {
    this.video = null;
    document.body.removeChild(video);
  };

  return SayCheese;

})($);
<|endoftext|>


<commit_before>import {ROW_NOT_EXIST} from "./constants";
import Column from "./Column";
import ColumnPairView from "./ColumnPairView";

var BreadCrumbs = React.createClass({
    render() {
        return (

                {this.props.items.map((item) =>
 | {item}
)}

        )
    }
});

var ColumnView = React.createClass({

    getInitialState() {
        return {
            items: [],
            hovered: [],
            selected: []
        }
    },

    getDefaultProps() {
        return {
            onAppend: () => {}
        }
    },

    _sortResults({identifier, identifiers, links}) {
        identifiers = identifiers.sort((i1, i2) => i1.name.localeCompare(i2.name));
        links = links.sort((i1, i2) => {
            var name = identifier.name;
            // TODO: rewrite in elegant and robust way
            if (name === i1.target && name === i2.target) {
                return i1.source.localeCompare(i2.source);
            } else if (name === i1.source && name === i2.source) {
                return i1.target.localeCompare(i2.target);
            } else if (name === i1.source && name === i2.target) {
                return i1.target.localeCompare(i2.source);
            } else if (name === i1.target && name === i2.source) {
                return i1.source.localeCompare(i2.target);
            }

        });

        return {identifier, identifiers, links}
    },

    _removeIdentifierFromResult(identifier) {
        return ({identifiers, links}) => {
            var identifiersSet = new Set(identifiers);
            identifiersSet.delete(identifier);
            return {identifiers: Array.from(identifiersSet), links}
        }
    },

    _rootColumnSelect() {
        // TODO: rework component do we could use _handleIdentifierSelect(0,0);
        //this._handleIdentifierSelect(0, 0);
        this.search(this.props.identifier)
            .then((result) => {
                this.setState({
                    items: [result],
                    selected: [],
                    hovered: []
                });
            });
    },

    componentDidMount() {
        this._rootColumnSelect();
    },

    search(identifier) {
        return identifier.search({})
            .then(this._removeIdentifierFromResult(identifier))
            .then(({identifiers, links}) => {return {identifier, identifiers, links}})
            .then(this._sortResults);
    },

    _handleIdentifierSelect(itemIndex, identifierIndex) {
        var items = this.state.items,
            identifier = items[itemIndex].identifiers[identifierIndex],
            newItems = items.slice(0, itemIndex + 1),
            selected = this.state.selected.slice(0, itemIndex);

        selected[itemIndex] = identifierIndex;

        this.search(identifier)
            .then((result) => {
                newItems.push(result);
                this.setState({
                    items: newItems,
                    selected
                });
                this.props.onAppend();
            });

    },

    _handleIdentifierHover(itemIndex, identifierIndex) {
        var hovered = this.state.hovered;
        hovered[itemIndex] = identifierIndex;
        this.setState({hovered});
    },

    render() {
        return (

                    <Column
                        ref="rootIdentifier"
                        selected={0}
                        className="column-identifiers root-identifier"
                        items={[this.props.identifier]}
                        onSelect={() => this._rootColumnSelect()}
                        onHover={() => {}}
                        renderItem={(item) => item.name}
                    />
                    {this.state.items.map(({identifier, identifiers, links}, index, items) => {
                        return <ColumnPairView
                            className={index === items.length - 2 ? "last-column" : ""}
                            key={identifier.name + index}
                            identifiers={identifiers}
                            onHover={(identifierIndex) => this._handleIdentifierHover(index, identifierIndex)}
                            selected={this.state.selected[index]}
                            hovered={this.state.hovered[index]}
                            links={links}
                            onSelect={(identifierIndex) =>
                                this._handleIdentifierSelect(index, identifierIndex)}
                        />
                    })}

        )
    }
});

export default ColumnView;
<commit_msg>ColumnView refactoring prevent selection of selected identifier<commit_after>import {ROW_NOT_EXIST} from "./constants";
import Column from "./Column";
import ColumnPairView from "./ColumnPairView";

var BreadCrumbs = React.createClass({
    render() {
        return (

                {this.props.items.map((item) =>
 | {item}
)}

        )
    }
});

var ColumnView = React.createClass({

    getInitialState() {
        return {
            items: [],
            hovered: [],
            selected: []
        }
    },

    getDefaultProps() {
        return {
            onAppend: () => {}
        }
    },

    _sortResults({identifier, identifiers, links}) {
        identifiers = identifiers.sort((i1, i2) => i1.name.localeCompare(i2.name));
        links = links.sort((i1, i2) => {
            var name = identifier.name;
            // TODO: rewrite in elegant and robust way
            if (name === i1.target && name === i2.target) {
                return i1.source.localeCompare(i2.source);
            } else if (name === i1.source && name === i2.source) {
                return i1.target.localeCompare(i2.target);
            } else if (name === i1.source && name === i2.target) {
                return i1.target.localeCompare(i2.source);
            } else if (name === i1.target && name === i2.source) {
                return i1.source.localeCompare(i2.target);
            }

        });

        return {identifier, identifiers, links}
    },

    _removeIdentifierFromResult(identifier) {
        return ({identifiers, links}) => {
            var identifiersSet = new Set(identifiers);
            identifiersSet.delete(identifier);
            return {identifiers: Array.from(identifiersSet), links}
        }
    },

    _rootColumnSelect() {
        // TODO: rework component do we could use _handleIdentifierSelect(0,0);
        //this._handleIdentifierSelect(0, 0);
        this.search(this.props.identifier)
            .then((result) => {
                this.setState({
                    items: [result],
                    selected: [],
                    hovered: []
                });
            });
    },

    componentDidMount() {
        this._rootColumnSelect();
    },

    search(identifier) {
        return identifier.search({})
            .then(this._removeIdentifierFromResult(identifier))
            .then(({identifiers, links}) => {return {identifier, identifiers, links}})
            .then(this._sortResults);
    },

    _handleIdentifierSelect(itemIndex, identifierIndex) {
        var items = this.state.items,
            identifier = items[itemIndex].identifiers[identifierIndex],
            newItems = items.slice(0, itemIndex + 1),
            selected = this.state.selected.slice(0, itemIndex + 1);

        var selectedIdentifiers = new Set(newItems.map(({identifier}) => identifier));

        if (selectedIdentifiers.has(identifier)) {
            return;
        }

        selected[itemIndex] = identifierIndex;

        this.search(identifier)
            .then((result) => {
                newItems.push(result);
                this.setState({
                    items: newItems,
                    selected
                });
                this.props.onAppend();
            });

    },

    _handleIdentifierHover(itemIndex, identifierIndex) {
        var hovered = this.state.hovered;
        hovered[itemIndex] = identifierIndex;
        this.setState({hovered});
    },

    render() {
        return (

                    <Column
                        ref="rootIdentifier"
                        selected={0}
                        className="column-identifiers root-identifier"
                        items={[this.props.identifier]}
                        onSelect={() => this._rootColumnSelect()}
                        onHover={() => {}}
                        renderItem={(item) => item.name}
                    />
                    {this.state.items.map(({identifier, identifiers, links}, index, items) => {
                        return <ColumnPairView
                            className={index === items.length - 2 ? "last-column" : ""}
                            key={identifier.name + index}
                            identifiers={identifiers}
                            onHover={(identifierIndex) => this._handleIdentifierHover(index, identifierIndex)}
                            selected={this.state.selected[index]}
                            hovered={this.state.hovered[index]}
                            links={links}
                            onSelect={(identifierIndex) =>
                                this._handleIdentifierSelect(index, identifierIndex)}
                        />
                    })}

        )
    }
});

export default ColumnView;
<|endoftext|>


<commit_before>(function () {
  'use strict';

  angular.module('moi.controllers')
  .controller('TreeController', function ($scope,
                                          $auth,
                                          $timeout,
                                          data,
                                          storage,
                                          PreloadAssets,
                                          ModalService,
                                          TreeService,
                                          NeuronAnimateService,
                                          StorageService,
                                          SocialService,
                                          TestService,
                                          AdvicesPage,
                                          AdvicesPageEn,
                                          TreeAnimateService) {

    var treeModel = this;
    treeModel.neurons = data.tree;
    treeModel.meta = data.meta;
    var progressTree = TreeService.progressTree(treeModel.meta);
    treeModel.percentageLevel = progressTree.userLevel.percentage;
    treeModel.userLevel = progressTree.userLevel.level;
    treeModel.percentage = progressTree.percentage;
    treeModel.isBasicLevel = data.meta.depth < 5;
    treeModel.sharedTree = sharedTree;
    treeModel.randomPositionsCss = getRandomPositionCss();
    var language = $auth.user.language;
    treeModel.percentageTooltip = language === 'es' ? 'Has descubierto el ' +treeModel.percentage+ '% de tu árbol Moi': 'You have discovered the ' +treeModel.percentage+ '% of your tree Moi';
    treeModel.learnTooltip = language === 'es' ? 'Tu nivel de aprendizaje es ' +treeModel.userLevel: 'Your level of learning is ' + treeModel.userLevel;
    var $backgroundSound = angular.element(document.querySelector('#backgroundSound'));
    var currentUser = $auth.user;
    var successAnswers = localStorage.getItem('successAnswers');
    var isShowingPassiveModal = false;
    var finishedAnimations = false;

    treeModel.frameOptions = {
      type: 'marco_arbol',
      showBackButton: true
    };

    if(successAnswers > 0){
      treeModel.cssPoint = {
        '-webkit-animation': 'rotate 1s linear',
        'animation':'rotate 1s linear',
        'animation-iteration-count': successAnswers
      };
      treeModel.cssBar = {
        'transition': 'height '+successAnswers+'s ease-in-out'
      };
      localStorage.setItem('successAnswers', 0);
    }

    initVineta();

    treeModel.finishedAnimation = function() {
      $scope.$apply(function(){treeModel.showTree = true;});
      $backgroundSound[0].play();
      $backgroundSound[0].autoplay = true;
      if(storage.tree){
        if(storage.tree.vinetas_animadas){ //jshint ignore:line
          storage.tree.vinetas_animadas.depth = data.meta.depth; //jshint ignore:line
        }else {
          storage.tree.vinetas_animadas = {'depth': data.meta.depth}; //jshint ignore:line
        }
        StorageService.update(storage);
      }else if(currentUser.username){
        storage.tree = {'vinetas_animadas': {'depth': data.meta.depth}};
        StorageService.update(storage);
      }

      initAnimations();

      //show only when a user is new
      if(data.meta.depth === 1){
        showWelcomeModal();
      }
      //show only when a user reach level 9
      if(data.meta.depth === 9){
        TestService.goFinalTest(null, currentUser.name);
      }
    };

    function initVineta() {
      treeModel.urlVineta = PreloadAssets.shouldPreloadVideo(data, storage);
      if(treeModel.urlVineta && currentUser.username) {
        $backgroundSound[0].autoplay = false;
        treeModel.showTree = false;
      }else{
        treeModel.showTree = true;
        initAnimations();
      }
    }

    function initAnimations() {
      $timeout(animateWidgets, 2000);
    }

    function showWelcomeModal(){
      var language = $auth.user.language;
      var messageWelcome = language === 'es' ?'Bienvenido '+currentUser.username+'. Este es tu árbol Moi. '+
      'Contiene grandes conocimientos y solo de ti depende su crecimiento. '+
      'Sigue tu curiosidad y descubre como hacer que se desarrolle hasta su '+
      'máxima expresión.': 'Welcome '+currentUser.username+ '. This is your Moi tree. '+
      'It contains great knowledge and its growth depends on you. '+
      'Follow your curiousity and discover how to make it develop to its '+
      'full expression.';
      var dialogContentModel = {
        message: messageWelcome,
        callbacks: {
          btnCenter: function(){
            dialogContentModel.closeModal();
          }
        },
        labels: {
          btnCenter: 'Ok'
        }
      };

      var dialogOptions = {
        templateUrl: 'templates/partials/modal-alert-content.html',
        model: dialogContentModel
      };
      ModalService.showModel(dialogOptions);
    }

    function sharedTree(){
      var learntContents = treeModel.meta.current_learnt_contents; //jshint ignore:line
      var language = $auth.user.language;
      var data = language ==='es'? {
        title: 'Así se ve mi árbol Moi',
        description: 'Hasta aquí descubrí '+learntContents+' contenidos. Tu también puedes hacer crecer tus conocimientos con Moi Aprendizaje Social'
      }:{
        title: 'This is how my Moi tree looks',
        description: 'So far I discovered '+learntContents+' contents. You can also grow your knowledge with Moi Social Learning'
      };
      SocialService.showModal(data);
    }

    $scope.$on('IdleStart', showPassiveModal);

    function showPassiveModal() {
      var isActiveMessages = (localStorage.getItem('advicesOn') === 'true');
      var message = language === 'es' ? AdvicesPage.tree.messages[0] : AdvicesPageEn.tree.messages[0];
      if(!isShowingPassiveModal && treeModel.showTree && isActiveMessages && finishedAnimations){
        var dialogOptions = {
          templateUrl: 'templates/partials/modal-pasive-info.html',
          animation: 'animated flipInX',
          backdropClickToClose: true,
          model: {
            message: message,
            type: 'passive',
            cssClass: 'modal-bottomRight'
          },
          onHide: function() {
            isShowingPassiveModal = false;
          }
        };
        ModalService.showModel(dialogOptions);
        isShowingPassiveModal = true;
      }
    }

    function animateWidgets() {
      var oldPercentage = TreeAnimateService.getTempData('percentageTree');
      if(oldPercentage !== treeModel.percentage){
        TreeAnimateService.setTempData('percentageTree', treeModel.percentage);
        if(!!oldPercentage){
          var percentageTreeWidget = document.querySelector('.tree-percentage');
          var levelUserWidget = document.querySelector('.level-user');
          var barAnimation = 'pulse';
          animationLevelBadge();
          TreeAnimateService.animateWidget(levelUserWidget, barAnimation).then(function(){
            TreeAnimateService.animateWidget(percentageTreeWidget, barAnimation).then(function(){
              animateNeurons();
            });
          });
        }else{
          animateNeurons();
        }
      }else {
        animateNeurons();
      }
    }

    function animationLevelBadge(){
      var oldLevel = TreeAnimateService.getTempData('levelUser');
      if(oldLevel !== treeModel.userLevel){
        TreeAnimateService.setTempData('levelUser', treeModel.userLevel);
        if(!!oldLevel){
          var levelUserCountWidget = document.querySelector('.counter-container');
          var numberAnimation = 'zoomIn';
          TreeAnimateService.animateWidget(levelUserCountWidget, numberAnimation);
        }
      }
    }

    function animateNeurons(){
      if(treeModel.neurons.root.in_desired_neuron_path){ //jshint ignore:line
        $timeout(NeuronAnimateService.specialCallToAction, 1000);
      }
      $timeout(NeuronAnimateService.callToAction, 1000);
      finishedAnimations = true;
    }

    function getRandomPositionCss() {
      var minRange = 5;
      var maxRange = 30;
      var totalClouds = 3;
      var randomPositionsCss = [];
      for (var i = 0; i < totalClouds; i ++) {
        var randomPercentage = Math.floor(Math.random() * (maxRange - minRange + 1)) + minRange;
        var cssPosition = { 'top': randomPercentage + '%' };
        randomPositionsCss.push(cssPosition);
      }
      return randomPositionsCss;
    }

  });

})();
<commit_msg>fix skip video into tree page<commit_after>(function () {
  'use strict';

  angular.module('moi.controllers')
  .controller('TreeController', function ($scope,
                                          $auth,
                                          $timeout,
                                          data,
                                          storage,
                                          PreloadAssets,
                                          ModalService,
                                          TreeService,
                                          NeuronAnimateService,
                                          StorageService,
                                          SocialService,
                                          TestService,
                                          AdvicesPage,
                                          AdvicesPageEn,
                                          TreeAnimateService) {

    var treeModel = this;
    treeModel.neurons = data.tree;
    treeModel.meta = data.meta;
    var progressTree = TreeService.progressTree(treeModel.meta);
    treeModel.percentageLevel = progressTree.userLevel.percentage;
    treeModel.userLevel = progressTree.userLevel.level;
    treeModel.percentage = progressTree.percentage;
    treeModel.isBasicLevel = data.meta.depth < 5;
    treeModel.sharedTree = sharedTree;
    treeModel.randomPositionsCss = getRandomPositionCss();
    var language = $auth.user.language;
    treeModel.percentageTooltip = language === 'es' ? 'Has descubierto el ' +treeModel.percentage+ '% de tu árbol Moi': 'You have discovered the ' +treeModel.percentage+ '% of your tree Moi';
    treeModel.learnTooltip = language === 'es' ? 'Tu nivel de aprendizaje es ' +treeModel.userLevel: 'Your level of learning is ' + treeModel.userLevel;
    var $backgroundSound = angular.element(document.querySelector('#backgroundSound'));
    var currentUser = $auth.user;
    var successAnswers = localStorage.getItem('successAnswers');
    var isShowingPassiveModal = false;
    var finishedAnimations = false;

    treeModel.frameOptions = {
      type: 'marco_arbol',
      showBackButton: true
    };

    if(successAnswers > 0){
      treeModel.cssPoint = {
        '-webkit-animation': 'rotate 1s linear',
        'animation':'rotate 1s linear',
        'animation-iteration-count': successAnswers
      };
      treeModel.cssBar = {
        'transition': 'height '+successAnswers+'s ease-in-out'
      };
      localStorage.setItem('successAnswers', 0);
    }

    initVineta();

    treeModel.finishedAnimation = function() {
      $timeout(function(){treeModel.showTree = true;});
      $backgroundSound[0].play();
      $backgroundSound[0].autoplay = true;
      if(storage.tree){
        if(storage.tree.vinetas_animadas){ //jshint ignore:line
          storage.tree.vinetas_animadas.depth = data.meta.depth; //jshint ignore:line
        }else {
          storage.tree.vinetas_animadas = {'depth': data.meta.depth}; //jshint ignore:line
        }
        StorageService.update(storage);
      }else if(currentUser.username){
        storage.tree = {'vinetas_animadas': {'depth': data.meta.depth}};
        StorageService.update(storage);
      }

      initAnimations();

      //show only when a user is new
      if(data.meta.depth === 1){
        showWelcomeModal();
      }
      //show only when a user reach level 9
      if(data.meta.depth === 9){
        TestService.goFinalTest(null, currentUser.name);
      }
    };

    function initVineta() {
      treeModel.urlVineta = PreloadAssets.shouldPreloadVideo(data, storage);
      if(treeModel.urlVineta && currentUser.username) {
        $backgroundSound[0].autoplay = false;
        treeModel.showTree = false;
      }else{
        treeModel.showTree = true;
        initAnimations();
      }
    }

    function initAnimations() {
      $timeout(animateWidgets, 2000);
    }

    function showWelcomeModal(){
      var language = $auth.user.language;
      var messageWelcome = language === 'es' ?'Bienvenido '+currentUser.username+'. Este es tu árbol Moi. '+
      'Contiene grandes conocimientos y solo de ti depende su crecimiento. '+
      'Sigue tu curiosidad y descubre como hacer que se desarrolle hasta su '+
      'máxima expresión.': 'Welcome '+currentUser.username+ '. This is your Moi tree. '+
      'It contains great knowledge and its growth depends on you. '+
      'Follow your curiousity and discover how to make it develop to its '+
      'full expression.';
      var dialogContentModel = {
        message: messageWelcome,
        callbacks: {
          btnCenter: function(){
            dialogContentModel.closeModal();
          }
        },
        labels: {
          btnCenter: 'Ok'
        }
      };

      var dialogOptions = {
        templateUrl: 'templates/partials/modal-alert-content.html',
        model: dialogContentModel
      };
      ModalService.showModel(dialogOptions);
    }

    function sharedTree(){
      var learntContents = treeModel.meta.current_learnt_contents; //jshint ignore:line
      var language = $auth.user.language;
      var data = language ==='es'? {
        title: 'Así se ve mi árbol Moi',
        description: 'Hasta aquí descubrí '+learntContents+' contenidos. Tu también puedes hacer crecer tus conocimientos con Moi Aprendizaje Social'
      }:{
        title: 'This is how my Moi tree looks',
        description: 'So far I discovered '+learntContents+' contents. You can also grow your knowledge with Moi Social Learning'
      };
      SocialService.showModal(data);
    }

    $scope.$on('IdleStart', showPassiveModal);

    function showPassiveModal() {
      var isActiveMessages = (localStorage.getItem('advicesOn') === 'true');
      var message = language === 'es' ? AdvicesPage.tree.messages[0] : AdvicesPageEn.tree.messages[0];
      if(!isShowingPassiveModal && treeModel.showTree && isActiveMessages && finishedAnimations){
        var dialogOptions = {
          templateUrl: 'templates/partials/modal-pasive-info.html',
          animation: 'animated flipInX',
          backdropClickToClose: true,
          model: {
            message: message,
            type: 'passive',
            cssClass: 'modal-bottomRight'
          },
          onHide: function() {
            isShowingPassiveModal = false;
          }
        };
        ModalService.showModel(dialogOptions);
        isShowingPassiveModal = true;
      }
    }

    function animateWidgets() {
      var oldPercentage = TreeAnimateService.getTempData('percentageTree');
      if(oldPercentage !== treeModel.percentage){
        TreeAnimateService.setTempData('percentageTree', treeModel.percentage);
        if(!!oldPercentage){
          var percentageTreeWidget = document.querySelector('.tree-percentage');
          var levelUserWidget = document.querySelector('.level-user');
          var barAnimation = 'pulse';
          animationLevelBadge();
          TreeAnimateService.animateWidget(levelUserWidget, barAnimation).then(function(){
            TreeAnimateService.animateWidget(percentageTreeWidget, barAnimation).then(function(){
              animateNeurons();
            });
          });
        }else{
          animateNeurons();
        }
      }else {
        animateNeurons();
      }
    }

    function animationLevelBadge(){
      var oldLevel = TreeAnimateService.getTempData('levelUser');
      if(oldLevel !== treeModel.userLevel){
        TreeAnimateService.setTempData('levelUser', treeModel.userLevel);
        if(!!oldLevel){
          var levelUserCountWidget = document.querySelector('.counter-container');
          var numberAnimation = 'zoomIn';
          TreeAnimateService.animateWidget(levelUserCountWidget, numberAnimation);
        }
      }
    }

    function animateNeurons(){
      if(treeModel.neurons.root.in_desired_neuron_path){ //jshint ignore:line
        $timeout(NeuronAnimateService.specialCallToAction, 1000);
      }
      $timeout(NeuronAnimateService.callToAction, 1000);
      finishedAnimations = true;
    }

    function getRandomPositionCss() {
      var minRange = 5;
      var maxRange = 30;
      var totalClouds = 3;
      var randomPositionsCss = [];
      for (var i = 0; i < totalClouds; i ++) {
        var randomPercentage = Math.floor(Math.random() * (maxRange - minRange + 1)) + minRange;
        var cssPosition = { 'top': randomPercentage + '%' };
        randomPositionsCss.push(cssPosition);
      }
      return randomPositionsCss;
    }

  });

})();
<|endoftext|>


<commit_before>import React from 'react/addons'
import classnames from 'classnames'
import { Block } from './../../components'
import * as EditMobilizationImport from './../../pages/EditMobilization.jsx'

const EditMobilization = EditMobilizationImport.WrappedComponent
const { TestUtils } = React.addons

let block1
let block2
let blocks
let mobilization
let dispatch

describe('EditMobilization', () => {
  before(() => {
    block1 = { position: 0, id: 1 }
    block2 = { position: 1, id: 2 }
    blocks = {data: [block1, block2]}
    mobilization = { color_scheme: 'meurio-scheme' }
    dispatch = () => {}
  })

  describe('#render', () => {
    let component

    before(() => {
      component = TestUtils.renderIntoDocument(
        <EditMobilization mobilization={mobilization} blocks={blocks} widgets={{data: []}} dispatch={dispatch} />
      )
    })

    it('should render blocks', () => {
      const blocksComponents = TestUtils.scryRenderedComponentsWithType(component, Block)
      expect(blocksComponents).to.have.length(blocks.data.length)
    })

    it('should apply mobilization classes', () => {
      const { color_scheme } = mobilization
      const div = TestUtils.scryRenderedDOMComponentsWithClass(component, classnames(color_scheme))
      expect(div).to.have.length(1)
    })
  })
})
<commit_msg>[#102387980] fix tests<commit_after>import React from 'react/addons'
import classnames from 'classnames'
import { Block } from './../../components'
import * as EditMobilizationImport from './../../pages/EditMobilization.jsx'

const EditMobilization = EditMobilizationImport.WrappedComponent
const { TestUtils } = React.addons

let block1
let block2
let blocks
let mobilization
let dispatch

describe('EditMobilization', () => {
  before(() => {
    block1 = { position: 0, id: 1 }
    block2 = { position: 1, id: 2 }
    blocks = {data: [block1, block2], loaded: true}
    mobilization = { color_scheme: 'meurio-scheme' }
    dispatch = () => {}
  })

  describe('#render', () => {
    let component

    before(() => {
      component = TestUtils.renderIntoDocument(
        <EditMobilization mobilization={mobilization} blocks={blocks} widgets={{data: []}} dispatch={dispatch} />
      )
    })

    it('should render blocks', () => {
      const blocksComponents = TestUtils.scryRenderedComponentsWithType(component, Block)
      expect(blocksComponents).to.have.length(blocks.data.length)
    })

    it('should apply mobilization classes', () => {
      const { color_scheme } = mobilization
      const div = TestUtils.scryRenderedDOMComponentsWithClass(component, classnames(color_scheme))
      expect(div).to.have.length(1)
    })
  })
})
<|endoftext|>


<commit_before>var express = require('express');
var authorization = require('../middleware/authorization');
var request = require('request');
var Activity = require('../models/activity');
var Subscriber = require('../models/subscriber');
var Update = require('../models/update');

var activities = express.Router();
var route = '/products/:product/activities';

var COUNT = 10;
var ONE_DAY = 24 * 60 * 60 * 1000;

activities.use(route, authorization);

activities.route(route)
.get(function activitiesGET(req, res, next) {
  res.status(200).json({
    location: route,
    product: req.params.product
  });
})
.post(function activitiesPOST(req, res, next) {
  var product = req.params.product;
  var body = req.body;

  var activity = new Activity({
    product: product,
    webhook_id: body.data.id,
    type: body.data.email_type,
    list_id: body.data.list_id,
    email: body.data.email
  })
  .save()
  .then(function(a) {
    checkUpdates(product);
    res.status(201).json(a);
  })
  .otherwise(function(err) {
    console.error('Activity failed to save.');
    res.status(400).json(err);
  });
});

function alertProduct(count, product, timestamp) {
  Subscriber.where({ product: product })
  .fetchAll()
  .then(function(subscribers) {
    subscribers.forEach(function(subscriber) {
      var endpoint = subscriber.get('endpoint');
      var countDays = timestamp > 0 ? Math.floor((Date.now() - timestamp) / ONE_DAY) : 1;
      var days = countDays === 1 ? 'the past day' : 'the past' + countDays + ' days';
      var message = 'There were ' + count + ' new signups in ' + days + '.';

      request({
        method: 'POST',
        uri: endpoint,
        body: {
          news_feed_item: {
            message: message
          },
          user_token: process.env.ASSEMBLY_AUTHENTICATION_TOKEN
        },
        json: true
      }, function(err, response, body) {
        if (err) {
          console.log('Error sending message to ' + endpoint);
        }

        console.log(body);
      });
    });
  })
  .otherwise(function(error) {
    console.log('Error fetching subscribers.');
    console.error(error);
  });
}

function checkUpdates(product) {
  Update.where({ product: product })
  .fetch()
  .then(function(update) {
    var now = Date.now();

    if (!update) {
      return countActivitiesSince(product);
    }

    if (now - update.get('sent_at') > ONE_DAY &&
        // don't check more than once per day
        now - update.get('checked_at') > ONE_DAY) {
      countActivitiesSince(product, update);
    }
  })
  .otherwise(function(error) {
    console.log('Error fetching update for ' + product);
    console.error(error);
  });
}

function countActivitiesSince(product, update) {
  if (!update) {
    update = new Update({
      product: product,
      sent_at: new Date(0),
      current_count: 0
    });
  }

  var timestamp = update.get('sent_at');

  Activity.where({ product: product })
  .query('where', 'created_at', '>=', timestamp)
  .fetchAll()
  .then(function(activities) {
    var count = activities.length + update.get('current_count');
    var now = new Date();

    if (count >= COUNT) {
      alertProduct(count, product, timestamp);
      return markUpdate(update, {
        sent_at: now,
        current_count: 0
      });
    }

    markUpdate(update, {
      checked_at: now,
      current_count: count
    });
  })
  .otherwise(function(error) {
    console.log('Error counting activities');
    console.error(error);
  });
}

function markUpdate(update, data) {
  update.set(data)
  .save()
  .then(function(u) {
    console.log('Saved:', u);
  })
  .otherwise(function(error) {
    console.log('Error marking update', data);
    console.error(error);
  })
}

module.exports = activities;
<commit_msg>Automatically link updates to Assembly products<commit_after>var express = require('express');
var authorization = require('../middleware/authorization');
var request = require('request');
var Activity = require('../models/activity');
var Subscriber = require('../models/subscriber');
var Update = require('../models/update');

var activities = express.Router();
var route = '/products/:product/activities';

var COUNT = 10;
var ONE_DAY = 24 * 60 * 60 * 1000;

activities.use(route, authorization);

activities.route(route)
.get(function activitiesGET(req, res, next) {
  res.status(200).json({
    location: route,
    product: req.params.product
  });
})
.post(function activitiesPOST(req, res, next) {
  var product = req.params.product;
  var token = req.query.token;
  var body = req.body;

  var activity = new Activity({
    product: product,
    webhook_id: body.data.id,
    type: body.data.email_type,
    list_id: body.data.list_id,
    email: body.data.email
  })
  .save()
  .then(function(a) {
    checkUpdates(product, token);
    res.status(201).json(a);
  })
  .otherwise(function(err) {
    console.error('Activity failed to save.');
    res.status(400).json(err);
  });
});

function alertProduct(count, product, token, timestamp) {
  // Assembly-only
  var endpoint = process.env.ASSEMBLY_API + '/' + product + '/activities?token=' + token;
  var countDays = timestamp > 0 ? Math.floor((Date.now() - timestamp) / ONE_DAY) : 1;
  var days = countDays === 1 ? 'the past day' : 'the past' + countDays + ' days';
  var message = 'There were ' + count + ' new signups in ' + days + '.';

  request({
    method: 'POST',
    uri: endpoint,
    body: {
      news_feed_item: {
        message: message
      },
      user_token: process.env.ASSEMBLY_AUTHENTICATION_TOKEN
    },
    json: true
  }, function(err, response, body) {
    if (err) {
      console.log('Error sending message to ' + endpoint);
    }

    console.log(body);
  });

  alertSubscribers(count, product, timestamp);
}

function alertSubscribers(count, product, timestamp) {
  Subscriber.where({ product: product })
  .fetchAll()
  .then(function(subscribers) {
    subscribers.forEach(function(subscriber) {
      var endpoint = subscriber.get('endpoint');
      var countDays = timestamp > 0 ? Math.floor((Date.now() - timestamp) / ONE_DAY) : 1;
      var days = countDays === 1 ? 'the past day' : 'the past' + countDays + ' days';
      var message = 'There were ' + count + ' new signups in ' + days + '.';

      request({
        method: 'POST',
        uri: endpoint,
        body: {
          news_feed_item: {
            message: message
          },
          user_token: process.env.ASSEMBLY_AUTHENTICATION_TOKEN
        },
        json: true
      }, function(err, response, body) {
        if (err) {
          console.log('Error sending message to ' + endpoint);
        }

        console.log(body);
      });
    });
  })
  .otherwise(function(error) {
    console.log('Error fetching subscribers.');
    console.error(error);
  });
}

function checkUpdates(product, token) {
  Update.where({ product: product })
  .fetch()
  .then(function(update) {
    var now = Date.now();

    if (!update) {
      return countActivitiesSince(product, token);
    }

    if (now - update.get('sent_at') > ONE_DAY &&
        // don't check more than once per day
        now - update.get('checked_at') > ONE_DAY) {
      countActivitiesSince(product, token, update);
    }
  })
  .otherwise(function(error) {
    console.log('Error fetching update for ' + product);
    console.error(error);
  });
}

function countActivitiesSince(product, token, update) {
  if (!update) {
    update = new Update({
      product: product,
      sent_at: new Date(0),
      current_count: 0
    });
  }

  var timestamp = update.get('sent_at');

  Activity.where({ product: product })
  .query('where', 'created_at', '>=', timestamp)
  .fetchAll()
  .then(function(activities) {
    var count = activities.length + update.get('current_count');
    var now = new Date();

    if (count >= COUNT) {
      alertProduct(count, product, token, timestamp);
      return markUpdate(update, {
        sent_at: now,
        current_count: 0
      });
    }

    markUpdate(update, {
      checked_at: now,
      current_count: count
    });
  })
  .otherwise(function(error) {
    console.log('Error counting activities');
    console.error(error);
  });
}

function markUpdate(update, data) {
  update.set(data)
  .save()
  .then(function(u) {
    console.log('Saved:', u);
  })
  .otherwise(function(error) {
    console.log('Error marking update', data);
    console.error(error);
  })
}

module.exports = activities;
<|endoftext|>


<commit_before>import React, { Component } from 'react';
import PropTypes from 'prop-types';
import rantscript from '../../consts/rantscript';
import Loading from '../utilities/loading';
import Column from '../columns/column';
import { ITEM, STATE } from '../../consts/types';

const { shell } = require('electron');

const USER_PROFILE_FILTERS = {
  SORT: {
    RANTS: 'rants',
    COMMENTS: 'comments',
    FAVOURITES: 'favorites',
    UPVOTED: 'upvoted',
  },
  PRIMARY: 'SORT',
};

const DEFAULT_COLUMN = {
  itemType: ITEM.RANT.NAME,
  items: [],
  page: 0,
  id: 'id',
  type: 'user_profile',
  state: STATE.SUCCESS,
  filters: USER_PROFILE_FILTERS,
  sort: USER_PROFILE_FILTERS.RANTS,
};

class UserProfile extends Component {
  constructor(props) {
    super(props);
    this.state = {
      user: null,
      column: DEFAULT_COLUMN,
      loading: false,
    };
  }
  componentDidMount() {
    this.fetch();
  }
  shouldComponentUpdate(nextProps, nextState) {
    const nextLength = nextState.column.items.length;
    const currentLength = this.state.column.items.length;
    if (nextLength === currentLength) {
      return false;
    }
    return true;
  }
  componentDidUpdate(prevProps) {
    if (this.props.item.id !== prevProps.item.id) {
      /*
       * This works fine. Although not a good practice, as long as it is in a conditional loop
       * it can used without side effects
       */
      // eslint-disable-next-line
      this.setState({ loading: true });
      this.fetch();
    }
  }
  fetch(sort = USER_PROFILE_FILTERS.SORT.RANTS, range = null, id = 0, refresh = false) {
    const { item, auth } = this.props;
    const prevColumn = Object.assign({}, this.state.column);
    prevColumn.state = STATE.LOADING;
    if (sort !== this.state.column.sort) {
      prevColumn.page = 0;
      prevColumn.items = [];
    }
    this.setState({ column: prevColumn });

    const { column } = this.state;
    const page = refresh || sort !== this.state.column.sort ? 0 : column.page;
    let token = null;
    if (auth.user) {
      token = auth.user.authToken;
    }
    rantscript.profile(item.id, token, sort, page * 30)
      .then((res) => {
        /**
         * If you clone the object, JS keeps directly modifies the the items of DEFAULT_COLUMN.
         * Which stays even after the component unmounts. So next time the component is mounting
         * it is showing the previous items.
         *
         * Bloody hell, JS. You're beautiful
         */
        const nextColumn = Object.assign({}, DEFAULT_COLUMN);
        nextColumn.page = this.state.column.page + 1;
        const nextItems = sort !== column.sort ? [] : [...column.items];
        nextColumn.items = [...nextItems, ...res.content.content[sort]];
        nextColumn.state = STATE.SUCCESS;
        nextColumn.sort = sort;
        if (sort === USER_PROFILE_FILTERS.SORT.COMMENTS) {
          nextColumn.itemType = ITEM.COMMENT.NAME;
        } else {
          nextColumn.itemType = ITEM.RANT.NAME;
        }
        this.setState({
          user: res,
          column: nextColumn,
          loading: false,
        });
      })
      .catch((err) => {
        console.log(err);
      });
  }
  static openLink(url) {
    let fURL = url;
    if (
      url.indexOf('http://') === -1
      || url.indexOf('https://') === -1
    ) {
      fURL = `http://${url}`;
    }
    shell.openExternal(fURL);
  }
  render() {
    if (!this.state.user || this.state.loading) {
      return (

          <Loading />

      );
    }
    const { user } = this.state;
    const { theme } = this.props;
    let imageSource = 'res/images/invis.png';
    if (user.avatar.i) {
      imageSource = `https://avatars.devrant.io/${user.avatar.i.replace('c-1', 'c-2').replace('png', 'jpg')}`;
    }
    return (

        <div
          className="profile"
          style={{
            background: 'url(./res/images/profile_banner.png)',
            width: `${theme.column.width}px`,
          }}
        >

            <img alt="" src={imageSource} style={{ backgroundColor: `#${user.avatar.b}` }} />

              {user.username}
              +{user.score}

                { user.about !== '' &&

{user.about}

}
                { user.location !== '' &&

{user.location}

}
                { user.github !== '' &&

 shell.openExternal(`https://www.github.com/${user.github}`)}>
                    {user.github}

}
                { user.website !== '' &&

 UserProfile.openLink(user.website)}>
                    {user.website}

}

          <Column
            {...this.props}
            column={this.state.column}
            filters={this.state.column.filters}
            itemType={this.state.column.itemType}
            fetch={(sort, range, id, refresh) => this.fetch(sort, range, id, refresh)}
          />

    );
  }
}

UserProfile.propTypes = {
  item: PropTypes.object.isRequired,
  auth: PropTypes.object.isRequired,
  theme: PropTypes.object.isRequired,
};

export default UserProfile;
<commit_msg>cursor bug fixed<commit_after>import React, { Component } from 'react';
import PropTypes from 'prop-types';
import rantscript from '../../consts/rantscript';
import Loading from '../utilities/loading';
import Column from '../columns/column';
import { ITEM, STATE } from '../../consts/types';

const { shell } = require('electron');

const USER_PROFILE_FILTERS = {
  SORT: {
    RANTS: 'rants',
    COMMENTS: 'comments',
    FAVOURITES: 'favorites',
    UPVOTED: 'upvoted',
  },
  PRIMARY: 'SORT',
};

const DEFAULT_COLUMN = {
  itemType: ITEM.RANT.NAME,
  items: [],
  page: 0,
  id: 'id',
  type: 'user_profile',
  state: STATE.SUCCESS,
  filters: USER_PROFILE_FILTERS,
  sort: USER_PROFILE_FILTERS.RANTS,
};

class UserProfile extends Component {
  constructor(props) {
    super(props);
    this.state = {
      user: null,
      column: DEFAULT_COLUMN,
      loading: false,
    };
  }
  componentDidMount() {
    this.fetch();
  }
  shouldComponentUpdate(nextProps, nextState) {
    const nextLength = nextState.column.items.length;
    const currentLength = this.state.column.items.length;
    if (nextLength === currentLength) {
      return false;
    }
    return true;
  }
  componentDidUpdate(prevProps) {
    if (this.props.item.id !== prevProps.item.id) {
      /*
       * This works fine. Although not a good practice, as long as it is in a conditional loop
       * it can used without side effects
       */
      // eslint-disable-next-line
      this.setState({ loading: true });
      this.fetch();
    }
  }
  fetch(sort = USER_PROFILE_FILTERS.SORT.RANTS, range = null, id = 0, refresh = false) {
    const { item, auth } = this.props;
    const prevColumn = Object.assign({}, this.state.column);
    prevColumn.state = STATE.LOADING;
    if (sort !== this.state.column.sort) {
      prevColumn.page = 0;
      prevColumn.items = [];
    }
    this.setState({ column: prevColumn });

    const { column } = this.state;
    const page = refresh || sort !== this.state.column.sort ? 0 : column.page;
    let token = null;
    if (auth.user) {
      token = auth.user.authToken;
    }
    rantscript.profile(item.id, token, sort, page * 30)
      .then((res) => {
        /**
         * If you clone the object, JS keeps directly modifies the the items of DEFAULT_COLUMN.
         * Which stays even after the component unmounts. So next time the component is mounting
         * it is showing the previous items.
         *
         * Bloody hell, JS. You're beautiful
         */
        const nextColumn = Object.assign({}, DEFAULT_COLUMN);
        nextColumn.page = this.state.column.page + 1;
        const nextItems = sort !== column.sort ? [] : [...column.items];
        nextColumn.items = [...nextItems, ...res.content.content[sort]];
        nextColumn.state = STATE.SUCCESS;
        nextColumn.sort = sort;
        if (sort === USER_PROFILE_FILTERS.SORT.COMMENTS) {
          nextColumn.itemType = ITEM.COMMENT.NAME;
        } else {
          nextColumn.itemType = ITEM.RANT.NAME;
        }
        this.setState({
          user: res,
          column: nextColumn,
          loading: false,
        });
      })
      .catch((err) => {
        console.log(err);
      });
  }
  static openLink(url) {
    let fURL = url;
    if (
      url.indexOf('http://') === -1
      || url.indexOf('https://') === -1
    ) {
      fURL = `http://${url}`;
    }
    shell.openExternal(fURL);
  }
  render() {
    if (!this.state.user || this.state.loading) {
      return (

          <Loading />

      );
    }
    const { user } = this.state;
    const { theme } = this.props;
    let imageSource = 'res/images/invis.png';
    if (user.avatar.i) {
      imageSource = `https://avatars.devrant.io/${user.avatar.i.replace('c-1', 'c-2').replace('png', 'jpg')}`;
    }
    return (

        <div
          className="profile"
          style={{
            background: 'url(./res/images/profile_banner.png)',
            width: `${theme.column.width}px`,
          }}
        >

            <img alt="" src={imageSource} style={{ backgroundColor: `#${user.avatar.b}` }} />

              {user.username}
              +{user.score}

                { user.about !== '' &&

{user.about}

}
                { user.location !== '' &&

{user.location}

}
                { user.github !== '' &&

 shell.openExternal(`https://www.github.com/${user.github}`)}>
                    {user.github}

}
                { user.website !== '' &&

 UserProfile.openLink(user.website)}>
                    {user.website}

}

          <Column
            {...this.props}
            column={this.state.column}
            filters={this.state.column.filters}
            itemType={this.state.column.itemType}
            fetch={(sort, range, id, refresh) => this.fetch(sort, range, id, refresh)}
          />

    );
  }
}

UserProfile.propTypes = {
  item: PropTypes.object.isRequired,
  auth: PropTypes.object.isRequired,
  theme: PropTypes.object.isRequired,
};

export default UserProfile;
<|endoftext|>