<commit_before>/*
Similarly as a point can be represented in multiple coordinate systems,
so can a transformation. To prevent users from thinking about representations,
we have InvariantTransform.

InvariantTransform is a planeless representation of a transformation.
Thus, most exact name would be a coordinate-plane-invariant transformation.

Alternative names:
  PlaneInvariantTransform
  PITransform
  InvariantTransform
  InvariantTransformation
  FreeTransform
  PlanelessTransform
*/
var Transform = require('./Transform')
var nudged = require('nudged')

// NOT YET USED ANYWHERE
// var getTrBetweenPlanes = function (sourcePlane, targetPlane) {
//   // Return a Transform that represents a mapping from sourcePlane
//   // to the targetPlane.
//   var source2space = sourcePlane.getGlobalTransform().toSpace()
//   var target2space = targetPlane.getGlobalTransform().toSpace()
//   return target2space.inverse().multiplyBy(source2space)
// }

var getTrOnPlane = function (tr, trToPlane) {
  // A same transformation can be represented on different coordinate systems.
  // This fn takes a transformation tr on plane A and another transformation
  // trToPlane from plane A to plane B. The result is a transformation
  // that is globally equivalent to tr but represented on plane B.
  //
  // Parameters:
  //   tr
  //     a Transform on plane A
  //   trToPlane
  //     a Transform from plane A to plane B.
  //     This is called a covariant transformation in math literature.
  //
  // Implementation note:
  //   So, the resulting transformation tr' maps a vector X' to vector Y'
  //   on plane B. The given transformation tr maps a vector X to vector Y
  //   on plane A. Therefore, the raw approach is to:
  //     1. map X' to X (= inverse of trToPlane)
  //     2. map X to Y  (= tr)
  //     3. map Y to Y' (= trToPlane)
  //   Fortunately we can combine the mappings.
  return trToPlane.multiplyBy(tr.multiplyBy(trToPlane.inverse()))
}

var InvariantTransform = function (transf, plane) {
  // Immutable i.e. new instances are returned.
  //
  // Example
  //   var t = new taaspace.InvariantTransform(tr, pixel)
  //
  // Parameter
  //   transf
  //     Optional. A taaspace.Transform. Default to identity transform.
  //   plane
  //     An optional SpacePlane. Defaults to space.
  //       an item in space, gives the reference plane of transf.
  //
  // Design note:
  //   fn (transf, plane) has this parameter order to make difference
  //   to SpaceNodes' (parent, prop1, prop2, ...)

  // DEBUG
  if (plane && !('_T' in plane)) {
    throw new Error('invalid reference')
  }
  if (transf && !transf.hasOwnProperty('tx')) {
    throw new Error('invalid transform')
  }

  // transf is the transformation on the plane.
  if (typeof transf === 'undefined') { transf = Transform.IDENTITY }

  if (plane) {
    // Convert transformation to space
    this._tr = getTrOnPlane(transf, plane.getGlobalTransform().toSpace())
  } else {
    // transf already in space
    this._tr = transf
  }
}

var proto = InvariantTransform.prototype

proto.equals = function (gt) {
  // Test if given InvariantTransform represents equivalent transformation
  // regardless of reference.
  //
  // Parameters:
  //   gt
  //     an InvariantTransform

  return this._tr.equals(gt._tr)
}

proto.inverse = function () {
  // Return inversed transformation.
  return new InvariantTransform(this._tr.inverse())
}

proto.to = function (plane) {
  // Represent the transform on the target coordinate plane.
  //
  // Parameters:
  //   plane: a SpacePlane
  //
  // Return:
  //   a Transform
  //
  if (plane === null || plane.isRoot()) {
    return this._tr
  }

  // Transformation from space to the target plane
  var covTr = plane.getGlobalTransform().toSpace().inverse()
  return getTrOnPlane(this._tr, covTr)
}

proto.toSpace = function () {
  // Represent the transform on the space coordinate plane.
  //
  // Return a Transform.
  //
  return this._tr
}

proto.multiplyRight =
proto.multiplyBy =
proto.transformBy = function (itr) {
  // Transform the image of this by given InvariantTransform.
  //
  // Parameters:
  //   itr
  //     an InvariantTransform
  //
  return new InvariantTransform(itr._tr.multiplyBy(this._tr))
}

proto.relativeTo = function (itr) {
  // Return an InvariantTransform T that when multiplied from right
  // i.e. applied to itr, produces self:
  //   self = T * itr
  //  <=> T = self * inv(itr)
  //
  // Parameters:
  //   itr
  //     InvariantTransform
  //
  return new InvariantTransform(this._tr.multiplyBy(itr._tr.inverse()))
}

proto.translate = function (domain, range) {
  // Move transform image horizontally and vertically with control points.
  //
  // Translate so that after the translation, the domain points
  // would be as close to given range points as possible.
  //
  // Parameters:
  //   domain: array of InvariantVector
  //   range: array of InvariantVector
  //
  // Return:
  //   an InvariantTransform
  //
  var itr = InvariantTransform.estimate('T', domain, range)
  return itr.multiplyRight(this)
}

proto.scale = function (pivot, multiplierOrDomain, range) {
  // Parameters:
  //   pivot: a InvariantVector
  //   multiplier: the scale factor, > 0
  //  OR
  //   pivot: a InvariantVector
  //   domain: array of InvariantVector
  //   range: array of InvariantVector
  var useMultiplier, normPivot, domain, multiplier, tr, itr

  useMultiplier = (typeof range === 'undefined')

  if (useMultiplier) {
    normPivot = pivot.toSpace().toArray()
    multiplier = multiplierOrDomain
    // Multiplier does not depend on plane.
    tr = this._tr.scaleBy(multiplier, normPivot)
    return new InvariantTransform(tr)
  } else {
    domain = multiplierOrDomain
    itr = InvariantTransform.estimate('S', domain, range, pivot)
    return itr.multiplyBy(this)
  }
}

proto.rotate = function (pivot, radiansOrDomain, range) {
  // Parameters:
  //   pivot: a InvariantVector
  //   radians: rotation angle
  //  OR
  //   pivot: a InvariantVector
  //   domain: array of InvariantVector
  //   range: array of InvariantVector
  var useRadians, normPivot, domain, radians, tr, itr

  useRadians = (typeof range === 'undefined')

  if (useRadians) {
    normPivot = pivot.toSpace().toArray()
    radians = radiansOrDomain
    // Radians do not depend on plane.
    tr = this._tr.rotateBy(radians, normPivot)
    return new InvariantTransform(tr)
  } else {
    domain = radiansOrDomain
    itr = InvariantTransform.estimate('R', domain, range, pivot)
    return itr.multiplyBy(this)
  }
}

proto.translateScale = function (domain, range) {
  // Parameters:
  //   domain: array of InvariantVector
  //   range: array of InvariantVector
  var itr = InvariantTransform.estimate('TS', domain, range)
  return itr.multiplyBy(this)
}

proto.translateRotate = function (domain, range) {
  // Parameters:
  //   domain: array of InvariantVector
  //   range: array of InvariantVector
  var itr = InvariantTransform.estimate('TR', domain, range)
  return itr.multiplyBy(this)
}

proto.scaleRotate = function (pivot, domain, range) {
  // Parameters:
  //   pivot: InvariantVector
  //   domain: array of InvariantVector
  //   range: array of InvariantVector
  var itr = InvariantTransform.estimate('SR', domain, range, pivot)
  return itr.multiplyBy(this)
}

proto.translateScaleRotate = function (domain, range) {
  // Parameters:
  //   domain: array of InvariantVector
  //   range: array of InvariantVector
  var itr = InvariantTransform.estimate('TSR', domain, range)
  return itr.multiplyBy(this)
}

// Class methods

InvariantTransform.IDENTITY = new InvariantTransform()

InvariantTransform.estimate = function (type, domain, range, pivot) {
  // Estimate InvariantTransform from control points.
  //
  // Parameters:
  //   type: transformation type.
  //     Available types: T,S,R,TS,TR,SR,TSR (see nudged for further details)
  //   domain
  //     InvariantPath or array of InvariantVector
  //   range
  //     InvariantPath or array of InvariantVector
  //   pivot
  //     InvariantVector, an optional pivot, used with types S,R,SR
  //
  var normPivot, normDomain, normRange, tr

  if (typeof pivot !== 'undefined') {
    normPivot = pivot.toSpace().toArray()
  }

  // Allow singles & InvariantPaths
  if (domain.hasOwnProperty('_vec')) {
    domain = [domain]
  } else if (domain.hasOwnProperty('_p')) {
    domain = domain.toArray()
  }

  if (range.hasOwnProperty('_vec')) {
    range = [range]
  } else if (range.hasOwnProperty('_p')) {
    range = range.toArray()
  }

  // Convert all InvariantVectors onto the plane and to arrays
  var piv2arr = function (iv) {
    return [iv._vec.x, iv._vec.y]
  }
  normDomain = domain.map(piv2arr)
  normRange = range.map(piv2arr)

  // Then compute optimal transformation on the plane
  tr = nudged.estimate(type, normDomain, normRange, normPivot)
  return new InvariantTransform(tr)
}

module.exports = InvariantTransform
<commit_msg>Implement almostEqual<commit_after>/*
Similarly as a point can be represented in multiple coordinate systems,
so can a transformation. To prevent users from thinking about representations,
we have InvariantTransform.

InvariantTransform is a planeless representation of a transformation.
Thus, most exact name would be a coordinate-plane-invariant transformation.

Alternative names:
  PlaneInvariantTransform
  PITransform
  InvariantTransform
  InvariantTransformation
  FreeTransform
  PlanelessTransform
*/
var Transform = require('./Transform')
var nudged = require('nudged')

// NOT YET USED ANYWHERE
// var getTrBetweenPlanes = function (sourcePlane, targetPlane) {
//   // Return a Transform that represents a mapping from sourcePlane
//   // to the targetPlane.
//   var source2space = sourcePlane.getGlobalTransform().toSpace()
//   var target2space = targetPlane.getGlobalTransform().toSpace()
//   return target2space.inverse().multiplyBy(source2space)
// }

var getTrOnPlane = function (tr, trToPlane) {
  // A same transformation can be represented on different coordinate systems.
  // This fn takes a transformation tr on plane A and another transformation
  // trToPlane from plane A to plane B. The result is a transformation
  // that is globally equivalent to tr but represented on plane B.
  //
  // Parameters:
  //   tr
  //     a Transform on plane A
  //   trToPlane
  //     a Transform from plane A to plane B.
  //     This is called a covariant transformation in math literature.
  //
  // Implementation note:
  //   So, the resulting transformation tr' maps a vector X' to vector Y'
  //   on plane B. The given transformation tr maps a vector X to vector Y
  //   on plane A. Therefore, the raw approach is to:
  //     1. map X' to X (= inverse of trToPlane)
  //     2. map X to Y  (= tr)
  //     3. map Y to Y' (= trToPlane)
  //   Fortunately we can combine the mappings.
  return trToPlane.multiplyBy(tr.multiplyBy(trToPlane.inverse()))
}

var InvariantTransform = function (transf, plane) {
  // Immutable i.e. new instances are returned.
  //
  // Example
  //   var t = new taaspace.InvariantTransform(tr, pixel)
  //
  // Parameter
  //   transf
  //     Optional. A taaspace.Transform. Default to identity transform.
  //   plane
  //     An optional SpacePlane. Defaults to space.
  //       an item in space, gives the reference plane of transf.
  //
  // Design note:
  //   fn (transf, plane) has this parameter order to make difference
  //   to SpaceNodes' (parent, prop1, prop2, ...)

  // DEBUG
  if (plane && !('_T' in plane)) {
    throw new Error('invalid reference')
  }
  if (transf && !transf.hasOwnProperty('tx')) {
    throw new Error('invalid transform')
  }

  // transf is the transformation on the plane.
  if (typeof transf === 'undefined') { transf = Transform.IDENTITY }

  if (plane) {
    // Convert transformation to space
    this._tr = getTrOnPlane(transf, plane.getGlobalTransform().toSpace())
  } else {
    // transf already in space
    this._tr = transf
  }
}

var proto = InvariantTransform.prototype

proto.almostEqual =
proto.almostEquals = function (gt) {
  // See Transform.almostEqual
  return this._tr.almostEqual(gt._tr)
}

proto.equal =
proto.equals = function (gt) {
  // Test if given InvariantTransform represents equivalent transformation
  // regardless of reference.
  //
  // Parameters:
  //   gt
  //     an InvariantTransform

  return this._tr.equals(gt._tr)
}

proto.inverse = function () {
  // Return inversed transformation.
  return new InvariantTransform(this._tr.inverse())
}

proto.to = function (plane) {
  // Represent the transform on the target coordinate plane.
  //
  // Parameters:
  //   plane: a SpacePlane
  //
  // Return:
  //   a Transform
  //
  if (plane === null || plane.isRoot()) {
    return this._tr
  }

  // Transformation from space to the target plane
  var covTr = plane.getGlobalTransform().toSpace().inverse()
  return getTrOnPlane(this._tr, covTr)
}

proto.toSpace = function () {
  // Represent the transform on the space coordinate plane.
  //
  // Return a Transform.
  //
  return this._tr
}

proto.multiplyRight =
proto.multiplyBy =
proto.transformBy = function (itr) {
  // Transform the image of this by given InvariantTransform.
  //
  // Parameters:
  //   itr
  //     an InvariantTransform
  //
  return new InvariantTransform(itr._tr.multiplyBy(this._tr))
}

proto.relativeTo = function (itr) {
  // Return an InvariantTransform T that when multiplied from right
  // i.e. applied to itr, produces self:
  //   self = T * itr
  //  <=> T = self * inv(itr)
  //
  // Parameters:
  //   itr
  //     InvariantTransform
  //
  return new InvariantTransform(this._tr.multiplyBy(itr._tr.inverse()))
}

proto.translate = function (domain, range) {
  // Move transform image horizontally and vertically with control points.
  //
  // Translate so that after the translation, the domain points
  // would be as close to given range points as possible.
  //
  // Parameters:
  //   domain: array of InvariantVector
  //   range: array of InvariantVector
  //
  // Return:
  //   an InvariantTransform
  //
  var itr = InvariantTransform.estimate('T', domain, range)
  return itr.multiplyRight(this)
}

proto.scale = function (pivot, multiplierOrDomain, range) {
  // Parameters:
  //   pivot: a InvariantVector
  //   multiplier: the scale factor, > 0
  //  OR
  //   pivot: a InvariantVector
  //   domain: array of InvariantVector
  //   range: array of InvariantVector
  var useMultiplier, normPivot, domain, multiplier, tr, itr

  useMultiplier = (typeof range === 'undefined')

  if (useMultiplier) {
    normPivot = pivot.toSpace().toArray()
    multiplier = multiplierOrDomain
    // Multiplier does not depend on plane.
    tr = this._tr.scaleBy(multiplier, normPivot)
    return new InvariantTransform(tr)
  } else {
    domain = multiplierOrDomain
    itr = InvariantTransform.estimate('S', domain, range, pivot)
    return itr.multiplyBy(this)
  }
}

proto.rotate = function (pivot, radiansOrDomain, range) {
  // Parameters:
  //   pivot: a InvariantVector
  //   radians: rotation angle
  //  OR
  //   pivot: a InvariantVector
  //   domain: array of InvariantVector
  //   range: array of InvariantVector
  var useRadians, normPivot, domain, radians, tr, itr

  useRadians = (typeof range === 'undefined')

  if (useRadians) {
    normPivot = pivot.toSpace().toArray()
    radians = radiansOrDomain
    // Radians do not depend on plane.
    tr = this._tr.rotateBy(radians, normPivot)
    return new InvariantTransform(tr)
  } else {
    domain = radiansOrDomain
    itr = InvariantTransform.estimate('R', domain, range, pivot)
    return itr.multiplyBy(this)
  }
}

proto.translateScale = function (domain, range) {
  // Parameters:
  //   domain: array of InvariantVector
  //   range: array of InvariantVector
  var itr = InvariantTransform.estimate('TS', domain, range)
  return itr.multiplyBy(this)
}

proto.translateRotate = function (domain, range) {
  // Parameters:
  //   domain: array of InvariantVector
  //   range: array of InvariantVector
  var itr = InvariantTransform.estimate('TR', domain, range)
  return itr.multiplyBy(this)
}

proto.scaleRotate = function (pivot, domain, range) {
  // Parameters:
  //   pivot: InvariantVector
  //   domain: array of InvariantVector
  //   range: array of InvariantVector
  var itr = InvariantTransform.estimate('SR', domain, range, pivot)
  return itr.multiplyBy(this)
}

proto.translateScaleRotate = function (domain, range) {
  // Parameters:
  //   domain: array of InvariantVector
  //   range: array of InvariantVector
  var itr = InvariantTransform.estimate('TSR', domain, range)
  return itr.multiplyBy(this)
}

// Class methods

InvariantTransform.IDENTITY = new InvariantTransform()

InvariantTransform.estimate = function (type, domain, range, pivot) {
  // Estimate InvariantTransform from control points.
  //
  // Parameters:
  //   type: transformation type.
  //     Available types: T,S,R,TS,TR,SR,TSR (see nudged for further details)
  //   domain
  //     InvariantPath or array of InvariantVector
  //   range
  //     InvariantPath or array of InvariantVector
  //   pivot
  //     InvariantVector, an optional pivot, used with types S,R,SR
  //
  var normPivot, normDomain, normRange, tr

  if (typeof pivot !== 'undefined') {
    normPivot = pivot.toSpace().toArray()
  }

  // Allow singles & InvariantPaths
  if (domain.hasOwnProperty('_vec')) {
    domain = [domain]
  } else if (domain.hasOwnProperty('_p')) {
    domain = domain.toArray()
  }

  if (range.hasOwnProperty('_vec')) {
    range = [range]
  } else if (range.hasOwnProperty('_p')) {
    range = range.toArray()
  }

  // Convert all InvariantVectors onto the plane and to arrays
  var piv2arr = function (iv) {
    return [iv._vec.x, iv._vec.y]
  }
  normDomain = domain.map(piv2arr)
  normRange = range.map(piv2arr)

  // Then compute optimal transformation on the plane
  tr = nudged.estimate(type, normDomain, normRange, normPivot)
  return new InvariantTransform(tr)
}

module.exports = InvariantTransform
<|endoftext|>


<commit_before>// Written by Joshua Paul A. Chan
(function() {
"use strict";

// `convoList`
// Controls the convo-list (conversation list pane), allowing for display of the
// conversations and bulk/singular book-keeping operations upon conversations.
//
// @pre     : the user must be logged-in
//
// @attr    : query             : String            : the search query [default: '']
// @attr    : isSearch          : Boolean           : whether a search is being
// performed or not [default: false]
// @attr    : filters           : Object            : the list of conversation
// filters
// @attr    : curFilter         : String            : the current filter being
// used [ default : the first defined filter in `filters` ]
// @attr    : curPage           : Number            : the current page being
// displayed up to in the conversation list [default: 0]
// @attr    : numItemsPerPage   : Number            : the number of
// conversations to display per page [default: 25]
// @attr    : convos            : Conversation[]    : the conversation objects [default: []]
// @attr    : selectedConvos    : String[]          : the ids of currently [default: []]
// selected conversation objects
//
// @method: loadConversations(pg)               : Loads the conversations from
// the server using the conersation service.
// @method: searchConvos()                      : Loads the conversations from
// the server using the conersation service.
// @method: loadNextPage()                      : Load the next page of
// conversation items (either during regular browsing or search).
// @method: byStatus(convo)                     : Works with `ng-repeat |
// filter` to filter the shown conversations in the list.
// @method: toggleSelect(convoId)               : Selects (or de-selects) an
// item depending on whether it is currently being selected.
// @method: selectAll()                         : Selects all the current
// conversations in scope.
// @method: deselectAll()                       : De-Selects all the
// conversations currently selected.
// @method: listButtonsDisabled()               : Checks whether the
// conversation list items shoudl be disabled or not.
// @method: openMarkModal()                     : Opens the 'Mark As' modal for
// marking a conversation/several conversations as a certain status (i.e. read,
// replied).
// @method: closeMarkModal()                    : Closes the 'Mark As' modal for
// marking a conversation/several conversations as a certain status (i.e. read,
// replied).
// @method: markConvos(convoIds, status)        : Uses the conversation list
// service to mark the given conversations (convoIds) with the given status
// (i.e. read/unread, unreplied/replied).
// @method: openMoveModal()                     : Opens the 'Move To' modal for
// selecting a folder (i.e inbox, trash, etc.) to move a conversation/several
// conversations to.
// @method: closeMoveModal()                    : Closes the 'Move To' modal for
// selecting a folder (i.e inbox, trash, etc.) to move a conversation/several
// conversations to.
// @method: moveConvos(convoIds, dest)          : Uses the conversation list
// service to move the given conversations to the specified destination folder.
// @method: viewConvo(convoId)                  : Use stateProvider to show the
// given conversation on screen
angular.module('wr.controllers')
.controller('convoListPane', function($scope, $state, $convoListService) {
    // .controller('convoListPane', function($scope, $stateProvider, $convoListService, $notificationService) {
    $scope.query = "";
    $scope.isSearch = false;
    $scope.filters = {
        "All": "@CONVERSATIONS__FILTER-ALL",
        "Unread": "@CONVERSATIONS__FILTER-UNREAD",
        "Unreplied": "@CONVERSATIONS__FILTER-UNREPLIED"
    };
    $scope.curFilter = $scope.filters[Object.keys($scope.filters)[0]];
    $scope.curPage = 0;
    $scope.numItemsPerPage = 25;
    $scope.convos = [];
    $scope.selectedConvos = [];

    // Load messages when initialized
    $scope.$on($convoListService.isReady, () => {
        $scope.$apply(() => {
            $scope.loadConversations($scope.curPage);
        });
    });

    ///////////////////////// DISPLAYING CONVERSATIONS /////////////////////////

    // FIXME: `loadConversations(pg)`
    // Loads the conversations from the server using the conersation service.
    //
    // @pre     : the conversation list service must be initialized
    // @pre     : `pg` must be a non-negative integer
    // @pre     : `numItemsPerPage` must be a positive integer
    // @post    : [success] the `numItemsPerPage` will saved to the service,
    // allowing `loadNextPage` to be used instead
    // @post    : [success] the conversations will replace the current convos
    // @post    : [error] if no more items (pg > LAST_PAGE), do nothing
    // @post    : [error] if other error, error will be sent to error service to
    // display on page and log for analytics
    //
    // @param   : pg    : Number    : the page number of the converations to get
    // @return  : null
    $scope.loadConversations = (pg) => {
        $convoListService
            .loadConvos(pg, $scope.numItemsPerPage)
            .then(
                (conversations) => {
                    $scope.convos = conversations;
                },
                (err) => {
                    // TODO: notification service + error notification directive
                    console.error(err);
                });
    };

    // TODO: `searchConvos()`
    // Loads the conversations from the server using the conersation service.
    //
    // @pre     : the conversation list service must be initialized
    // @pre     : `ev` must be a valid event object
    // @pre     : `query` must be a non-empty String
    // @pre     : `numItemsPerPage` must be a positive integer
    // @post    : [success] the search results (if any) will replace the current
    // items in the list
    // @post    : [success] the `query` and`numItemsPerPage` will saved to the
    // service, allowing `loadNextPage` to be used instead for the next few
    // results
    // @post    : [error] if other error, error will be sent to error service to
    // display on page and log for analytics
    //
    // @param   : query : String    : the term(s) to search for in the conversations
    // @param   : ev    : Event     : the event that triggered this function
    // @return  : null
    $scope.searchConvos = (query, ev) => {
        // CODE IN HERE
    };

    // TODO: `loadNextPage()`
    // Load the next page of conversation items (either during regular browsing
    // or search).
    //
    // @pre     : the conversation list service must be initialized
    // @pre     : [success] Either a search through the conversations or a
    // conversation page load must have been completed previously (to save the
    // default number of pages and current page in the conversation list
    // service)
    // @post    : [success] the next page of conversations will be appended to
    // the current list of conversations
    // @post    : [error] the current conversation list will not be mutated in
    // any way
    // @post    : [error] if other error, error will be sent to error service to
    // display on page and log for analytics
    //
    // @param   : null
    // @return  : null
    $scope.loadNextPage = () => {
        // CODE IN HERE
    };

    // FIXME: `byStatus(convo)`
    // Works with `ng-repeat | filter` to filter the shown conversations in the
    // list.
    //
    // @pre     : `convo` should be a valid conversation object to filter
    // @post    : true will be returned if the convo object meets the current
    // showing criteria (i.e. is unread, unreplied/all), false otherwise
    //
    // @param   : convo     : Conversation  : the conversation item to judge
    // @return  : Boolean   : whether or not `convo` should be shown
    $scope.byStatus = (convo) => {
        var flag = true;
        switch ($scope.show) {
            case "Unread":
                flag = convo.status.unread;
                break;
            case "unreplied":
                flag = !convo.status.repliedTo;
                break;
            case "All":
                break;
        }
        return flag;
    };

    ///////////////////////// SELECTING CONVERSATIONS /////////////////////////

    // TODO: `toggleSelect(convoId)`
    // Selects (or de-selects) an item depending on whether it is currently
    // being selected.
    //
    // @pre     : `convoId` must be a valid Conversation id
    // @pre     : `ev` must be a valid Event object
    // @post    : if convo is not selected, `convoId` will be selected (put into
    // the the array of selected convos)
    // @post    : if convo is selected, `convoId` will be de-selected and
    // removed from the array of selected convos
    //
    // @param   : convoId   : the id of the conversation to select
    // @param   : ev        : event object that triggered this
    // @return  : null
    $scope.toggleSelect = (convoId, ev) => {
        ev.stopPropagation();
        // CODE IN HERE
        // FIXME: how to save selected convos between conversation view?
    };

    // TODO: `selectAll()`
    // Selects all the current conversations in scope.
    //
    // @pre     : null
    // @post    : all the conversations shown in the list will be selected
    //
    // @param   : null
    // @return  : null
    $scope.selectAll = () => {
        // CODE IN HERE
    };

    // TODO: `deselectAll()`
    // De-Selects all the conversations currently selected.
    //
    // @pre     : null
    // @post    : all currently selected conversations will be de-selected.
    //
    // @param   : null
    // @return  : null
    $scope.deselectAll = () => {
        // CODE IN HERE
    };

    // TODO: `listButtonsDisabled()`
    // Checks whether the conversation list items shoudl be disabled or not.
    //
    // @pre     : null
    // @post    : to be true, at least one conversation should be view or
    // selected
    // @post    : to be false, not a single conversation must be "open" or
    // selected
    //
    // @param   : null
    // @return  : Boolean   : whether the list buttons should be active or not
    $scope.enableListButtons = () => {
        // CODE IN HERE
    };

    ////////////////////////// MARKING CONVERSATIONS //////////////////////////

    // TODO: `openMarkModal()`
    // Opens the 'Mark As' modal for marking a conversation/several
    // conversations as a certain status (i.e. read, replied).
    //
    // @pre     : at least one conversation must be selected
    // @pre     : no other modal (including the Mark modal) should be open
    // @post    : [success] the modal will be 'opened' and visible on screen
    //
    // @param   : null
    // @return  : null
    $scope.openMarkModal = () => {
        // CODE IN HERE
    };

    // TODO: `closeMarkModal()`
    // Closes the 'Mark As' modal for marking a conversation/several
    // conversations as a certain status (i.e. read, replied).
    //
    //
    // @post    : [success] the Mark modal will become invisible and inactive
    //
    // @param   : null
    // @return  : null
    $scope.closeMarkModal = () => {
        // CODE IN HERE
    };

    // TODO: `markConvos(convoIds, status)`
    // Uses the conversation list service to mark the given conversations
    // (convoIds) with the given status (i.e. read/unread, unreplied/replied).
    //
    // @pre     : `convoIds` must be an array of valid conversation ids
    // @pre     : `status` must be a valid string corresponding to a status a
    // conversation can be marked as
    // @post    : [success] the conversations will be set to the given status
    // @post    : [error] the conversations will all retain their current status
    // @post    : [error] a notification will be logged with the error
    //
    // @param   : convoIds  : String[]  : id(s) of conversations to change
    // the status of
    // @param   : status    : String    : the status to change the
    // conversation(s) to
    // @return  : null
    var markConvos = (convoIds, status) => {
        // CODE IN HERE
    };

    /////////////////////////// MOVING CONVERSATIONS ///////////////////////////

    // TODO: `openMoveModal()`
    // Opens the 'Move To' modal for selecting a folder (i.e inbox, trash, etc.)
    // to move a conversation/several conversations to.
    //
    // @pre     : at least one conversation must be selected
    // @pre     : no other modal (even the Move modal) should be open on screen
    // @post    : [success] the Mark modal will be opened and visible on screen
    //
    // @param   : null
    // @return  : null
    $scope.openMoveModal = () => {
        // CODE IN HERE
    };

    // TODO: `closeMoveModal()`
    // Closes the 'Move To' modal for selecting a folder (i.e inbox, trash,
    // etc.) to move a conversation/several conversations to.
    //
    // @post    : [success] the Move modal will be invisible and inactive
    //
    // @param   : null
    // @return  : null
    $scope.closeMoveModal = () => {
        // CODE IN HERE
    };

    // TODO: `moveConvos(convoIds, dest)`
    // Uses the conversation list service to move the given conversations to the
    // specified destination folder.
    //
    // @pre     : `convoIds` must be an array of valid conversation ids
    // @pre     : `dest` must be a valid string corresponding to a destination
    // folder to move the conversations to
    // @post    : [success] the conversations will be moved to the given folder
    // @post    : [error] the conversations will stay in their current
    // respective folders
    // @post    : [error] a notification will be logged with the error
    //
    // @param   : convoIds  : String[]  : id(s) of conversations to change
    // the status of
    // @param   : dest      : String    : the folder to move the
    // conversations(s) to
    // @return  : null
    var moveConvos = (convoIds, dest) => {
        // CODE IN HERE
    };

    ////////////////////////// VIEWING CONVERSATIONS ///////////////////////////

    // TODO: `viewConvo(convoId)`
    // Use stateProvider to show the given conversation on screen.
    //
    // @pre     : a valid conversation id must be given
    // @post    : [success] the conversation will be shown on screen via the
    // convoPane
    // @post    : [error] the currently viewed conversation will not change (if
    // any)
    // @post    : [error] an error notification will be logged and shown to the
    // user
    //
    // @param   : convoId   : String    : the id of the conversation to view
    // @return  : null
    $scope.viewConvo = (convoId) => {
    };
});
}());
<commit_msg>Implement 1/2 of the conversationList<commit_after>// Written by Joshua Paul A. Chan
(function() {
"use strict";

// `convoList`
// Controls the convo-list (conversation list pane), allowing for display of the
// conversations and bulk/singular book-keeping operations upon conversations.
//
// @pre     : the user must be logged-in
//
// @attr    : query             : String            : the search query [default: '']
// @attr    : isSearch          : Boolean           : whether a search is being
// performed or not [default: false]
// @attr    : filters           : Object            : the list of conversation
// filters
// @attr    : curFilter         : String            : the current filter being
// used [ default : the first defined filter in `filters` ]
// @attr    : curPage           : Number            : the current page being
// displayed up to in the conversation list [default: 0]
// @attr    : numItemsPerPage   : Number            : the number of
// conversations to display per page [default: 25]
// @attr    : convos            : Conversation[]    : the conversation objects [default: []]
// @attr    : selectedConvos    : String[]          : the ids of currently [default: []]
// selected conversation objects
//
// @method: loadConversations(pg)               : Loads the conversations from
// the server using the conersation service.
// @method: searchConvos()                      : Loads the conversations from
// the server using the conersation service.
// @method: loadNextPage()                      : Load the next page of
// conversation items (either during regular browsing or search).
// @method: byStatus(convo)                     : Works with `ng-repeat |
// filter` to filter the shown conversations in the list.
// @method: toggleSelect(convoId)               : Selects (or de-selects) an
// item depending on whether it is currently being selected.
// @method: selectAll()                         : Selects all the current
// conversations in scope.
// @method: deselectAll()                       : De-Selects all the
// conversations currently selected.
// @method: listButtonsDisabled()               : Checks whether the
// conversation list items shoudl be disabled or not.
// @method: openMarkModal()                     : Opens the 'Mark As' modal for
// marking a conversation/several conversations as a certain status (i.e. read,
// replied).
// @method: closeMarkModal()                    : Closes the 'Mark As' modal for
// marking a conversation/several conversations as a certain status (i.e. read,
// replied).
// @method: markConvos(convoIds, status)        : Uses the conversation list
// service to mark the given conversations (convoIds) with the given status
// (i.e. read/unread, unreplied/replied).
// @method: openMoveModal()                     : Opens the 'Move To' modal for
// selecting a folder (i.e inbox, trash, etc.) to move a conversation/several
// conversations to.
// @method: closeMoveModal()                    : Closes the 'Move To' modal for
// selecting a folder (i.e inbox, trash, etc.) to move a conversation/several
// conversations to.
// @method: moveConvos(convoIds, dest)          : Uses the conversation list
// service to move the given conversations to the specified destination folder.
// @method: viewConvo(convoId)                  : Use stateProvider to show the
// given conversation on screen
angular.module('wr.controllers')
.controller('convoListPane', function($scope, $state, $convoListService) {
    // .controller('convoListPane', function($scope, $stateProvider, $convoListService, $notificationService) {
    $scope.query = "";
    $scope.isSearch = false;
    $scope.filters = {
        "All": "@CONVERSATIONS__FILTER-ALL",
        "Unread": "@CONVERSATIONS__FILTER-UNREAD",
        "Unreplied": "@CONVERSATIONS__FILTER-UNREPLIED"
    };
    $scope.curFilter = $scope.filters[Object.keys($scope.filters)[0]];
    $scope.curPage = 0;
    $scope.numItemsPerPage = 25;
    $scope.convos = [];
    $scope.selectedConvos = [];

    // Load messages when initialized
    $scope.$on($convoListService.isReady, () => {
        $scope.$apply(() => {
            $scope.loadConversations($scope.curPage);
        });
    });

    ///////////////////////// DISPLAYING CONVERSATIONS /////////////////////////

    // `loadConversations(pg)`
    // Loads the conversations from the server using the conersation service.
    //
    // @pre     : the conversation list service must be initialized
    // @pre     : `pg` must be a non-negative integer
    // @pre     : `numItemsPerPage` must be a positive integer
    // @post    : [success] the `numItemsPerPage` will saved to the service,
    // allowing `loadNextPage` to be used instead
    // @post    : [success] the conversations will replace the current convos
    // @post    : [error] if no more items (pg > LAST_PAGE), do nothing
    // @post    : [error] if other error, error will be sent to error service to
    // display on page and log for analytics
    //
    // @param   : pg    : Number    : the page number of the converations to get
    // @return  : null
    $scope.loadConversations = (pg) => {
        $convoListService
            .loadConvos(pg, $scope.numItemsPerPage)
            .then(
                (conversations) => {
                    $scope.convos = conversations;
                },
                (err) => {
                    console.error(err);
                    // FIXME: uncomment when notification service is implemented
                    // $notificationService.notify({
                    //     type: "error",
                    //     text: "There was an error loading the conversations from the server."
                    // });
                });
    };

    // TODO: `searchConvos()`
    // Loads the conversations from the server using the conersation service.
    //
    // @pre     : the conversation list service must be initialized
    // @pre     : `ev` must be a valid event object
    // @pre     : `query` must be a non-empty String
    // @pre     : `numItemsPerPage` must be a positive integer
    // @post    : [success] the search results (if any) will replace the current
    // items in the list
    // @post    : [success] the `query` and`numItemsPerPage` will saved to the
    // service, allowing `loadNextPage` to be used instead for the next few
    // results
    // @post    : [error] if other error, error will be sent to error service to
    // display on page and log for analytics
    //
    // @param   : query : String    : the term(s) to search for in the conversations
    // @param   : ev    : Event     : the event that triggered this function
    // @return  : null
    $scope.searchConvos = (query, ev) => {
        // CODE IN HERE
    };

    // TODO: `loadNextPage()`
    // Load the next page of conversation items (either during regular browsing
    // or search).
    //
    // @pre     : the conversation list service must be initialized
    // @pre     : [success] Either a search through the conversations or a
    // conversation page load must have been completed previously (to save the
    // default number of pages and current page in the conversation list
    // service)
    // @post    : [success] the next page of conversations will be appended to
    // the current list of conversations
    // @post    : [error] the current conversation list will not be mutated in
    // any way
    // @post    : [error] if other error, error will be sent to error service to
    // display on page and log for analytics
    //
    // @param   : null
    // @return  : null
    $scope.loadNextPage = () => {
        // CODE IN HERE
    };

    // FIXME: `byStatus(convo)`
    // Works with `ng-repeat | filter` to filter the shown conversations in the
    // list.
    //
    // @pre     : `convo` should be a valid conversation object to filter
    // @post    : true will be returned if the convo object meets the current
    // showing criteria (i.e. is unread, unreplied/all), false otherwise
    //
    // @param   : convo     : Conversation  : the conversation item to judge
    // @return  : Boolean   : whether or not `convo` should be shown
    $scope.byStatus = (convo) => {
        var flag = true;
        switch ($scope.show) {
            case "Unread":
                flag = convo.status.unread;
                break;
            case "unreplied":
                flag = !convo.status.repliedTo;
                break;
            case "All":
                break;
        }
        return flag;
    };

    ///////////////////////// SELECTING CONVERSATIONS /////////////////////////

    // `toggleSelect(convoId)`
    // Selects (or de-selects) an item depending on whether it is currently
    // being selected.
    //
    // @pre     : `convoId` must be a valid Conversation id
    // @pre     : `ev` must be a valid Event object
    // @post    : if convo is not selected, `convoId` will be selected (put into
    // the the array of selected convos)
    // @post    : if convo is selected, `convoId` will be de-selected and
    // removed from the array of selected convos
    //
    // @param   : convoId   : the id of the conversation to select
    // @param   : ev        : event object that triggered this
    // @return  : null
    $scope.toggleSelect = (convoId, ev) => {
        // prevent other event handlers on item from activating
        ev.stopPropagation();
        // FIXME: how to save selected convos between conversation view?
        // A: possibly save them in the convoListService - that should persist
        var pos = -1;
        for (var i = 0; i < $scope.selectedConvos.length; i++) {
            if ($scope.selectedConvos[i] === convoId) { pos = i; break; }
        }
        if (pos === -1) {
            // add it if doesn't exist
            return $scope.selectedConvos.push(convoId);
        } else {
            // remove it if it exists
            $scope.selectedConvos.splice(pos, 1);
        }
    };

    // `selectAll()`
    // Selects all the current conversations in scope.
    //
    // @pre     : null
    // @post    : all the conversations shown in the list will be selected
    //
    // @param   : null
    // @return  : null
    $scope.selectAll = () => {
        $scope.selectedConvos = $scope.convos;
    };

    // `deselectAll()`
    // De-Selects all the conversations currently selected.
    //
    // @pre     : null
    // @post    : all currently selected conversations will be de-selected.
    //
    // @param   : null
    // @return  : null
    $scope.deselectAll = () => {
        $scope.selectedConvos = [];
    };

    // `listButtonsDisabled()`
    // Checks whether the conversation list items should be disabled or not.
    //
    // @pre     : null
    // @post    : to be true, at least one conversation should be in view or
    // selected
    // @post    : to be false, not a single conversation must be "open" or
    // selected
    //
    // @param   : null
    // @return  : Boolean   : whether the list buttons should be active or not
    $scope.enableListButtons = () => {
        return ($scope.selectedConvos.length > 0);
    };

    ////////////////////////// MARKING CONVERSATIONS //////////////////////////

    // TODO: `openMarkModal()`
    // Opens the 'Mark As' modal for marking a conversation/several
    // conversations as a certain status (i.e. read, replied).
    //
    // @pre     : at least one conversation must be selected
    // @pre     : no other modal (including the Mark modal) should be open
    // @post    : [success] the modal will be 'opened' and visible on screen
    //
    // @param   : null
    // @return  : null
    $scope.openMarkModal = () => {
        // CODE IN HERE
    };

    // TODO: `closeMarkModal()`
    // Closes the 'Mark As' modal for marking a conversation/several
    // conversations as a certain status (i.e. read, replied).
    //
    //
    // @post    : [success] the Mark modal will become invisible and inactive
    //
    // @param   : null
    // @return  : null
    $scope.closeMarkModal = () => {
        // CODE IN HERE
    };

    // `markConvos(convoIds, status)`
    // Uses the conversation list service to mark the given conversations
    // (convoIds) with the given status (i.e. read/unread, unreplied/replied).
    //
    // @pre     : `convoIds` must be an array of valid conversation ids
    // @pre     : `status` must be a valid string corresponding to a status a
    // conversation can be marked as
    // @post    : [success] the conversations will be set to the given status
    // @post    : [error] the conversations will all retain their current status
    // @post    : [error] a notification will be logged with the error
    //
    // @param   : convoIds  : String[]  : id(s) of conversations to change
    // the status of
    // @param   : status    : String    : the status to change the
    // conversation(s) to
    // @return  : null
    var markConvos = (convoIds, status) => {
        $convoListService
            .updateConversations(convoIds, { status : status })
            .then((data) => {
                // update view
                $scope.convos = $scope.convos.map((convo) => {
                    if ($scope.selectedConvos.indexOf(convo.id) !== -1) {
                        convo.status = status;
                    }
                });
            }, (err) => {
                // log error and
                console.error(err);
                // FIXME: uncomment this when notificaiton service is implemented
                // $notificationService.notify({
                //     type: "error",
                //     text: `There was an error marking the conversation(s) as ${status}`
                // });
            });
    };

    /////////////////////////// MOVING CONVERSATIONS ///////////////////////////

    // TODO: `openMoveModal()`
    // Opens the 'Move To' modal for selecting a folder (i.e inbox, trash, etc.)
    // to move a conversation/several conversations to.
    //
    // @pre     : at least one conversation must be selected
    // @pre     : no other modal (even the Move modal) should be open on screen
    // @post    : [success] the Mark modal will be opened and visible on screen
    //
    // @param   : null
    // @return  : null
    $scope.openMoveModal = () => {
        // CODE IN HERE
    };

    // TODO: `closeMoveModal()`
    // Closes the 'Move To' modal for selecting a folder (i.e inbox, trash,
    // etc.) to move a conversation/several conversations to.
    //
    // @post    : [success] the Move modal will be invisible and inactive
    //
    // @param   : null
    // @return  : null
    $scope.closeMoveModal = () => {
        // CODE IN HERE
    };

    // `moveConvos(convoIds, dest)`
    // Uses the conversation list service to move the given conversations to the
    // specified destination folder.
    //
    // @pre     : `convoIds` must be an array of valid conversation ids
    // @pre     : `dest` must be a valid string corresponding to a destination
    // folder to move the conversations to
    // @post    : [success] the conversations will be moved to the given folder
    // @post    : [error] the conversations will stay in their current
    // respective folders
    // @post    : [error] a notification will be logged with the error
    //
    // @param   : convoIds  : String[]  : id(s) of conversations to change
    // the status of
    // @param   : dest      : String    : the folder to move the
    // conversations(s) to
    // @return  : null
    var moveConvos = (convoIds, dest) => {
        $convoListService
            .updateConversations(convoIds, { location : dest })
            .then((data) => {
                // update view
                $scope.convos = $scope.convos.map((convo) => {
                    if ($scope.selectedConvos.indexOf(convo.id) !== -1) {
                        convo.location = dest;
                    }
                });
            }, (err) => {
                // log error and
                console.error(err);
                // FIXME: uncomment this when notificaiton service is implemented
                // $notificationService.notify({
                //     type: "error",
                //     text: `There was an error moving the conversation(s) to ${dest}`
                // });
            });
    };

    ////////////////////////// VIEWING CONVERSATIONS ///////////////////////////

    // `viewConvo(convoId)`
    // Use stateProvider to show the given conversation on screen.
    //
    // @pre     : a valid conversation id must be given
    // @post    : [success] the conversation will be shown on screen via the
    // convoPane
    // @post    : [success] the conversation will be 'selected'
    // @post    : [error] the currently viewed conversation will not change (if
    // any)
    // @post    : [error] an error notification will be logged and shown to the
    // user
    //
    // @param   : convoId   : String    : the id of the conversation to view
    // @return  : null
    $scope.viewConvo = (convoId) => {
        // add to selected convos
        $scope.selectedConvos.push(convoId);
        // navigate to view
        $state.go('app.conversation', { convoId : convoId });
    };
});
}());
<|endoftext|>


<commit_before>define(['app', 'angular', 'text!./km-form-material-buttons.html','jquery'], function(app, angular, template) { 'use strict';

	app.directive('kmFormMaterialButtons', ["$q", function ($q)
	{
		return {
			restrict: 'EAC',
			template: template,
			replace: true,
			transclude: true,
			scope: {
				formStatus     	  : "=status",
				getDocumentFn     : "&document",
				onPreCloseFn      : "&onPreClose",
				onPostCloseFn     : "&onPostClose",
				onPreRevertFn     : "&onPreRevert",
				onPostRevertFn    : "&onPostRevert",
				onPreSaveDraftFn  : "&onPreSaveDraft",
				onPostSaveDraftFn : "&onPostSaveDraft",
				onPrePublishFn    : "&onPrePublish",
				onPostPublishFn   : "&onPostPublish",
				onPostWorkflowFn  : "&onPostWorkflow",
				onErrorFn: "&onError"
			},
			link: function ($scope, $element)
			{
				$scope.errors = null;
				$scope.status = "loading buttons";

			},
			controller: ["$scope", "$rootScope", "IStorage", "authentication", "editFormUtility", "$mdDialog", "$timeout", function ($scope, $rootScope, storage, authentication, editFormUtility, $mdDialog, $timeout)
			{

				//====================
				//
				//====================
				$scope.new_saveDraft = function()
				{
					$scope.status = "saving";

					$q.when($scope.onPreSaveDraftFn()).then(function(result) {
							return result;
					}).then(function(cancel) {
						if(cancel)
							return;

						var document = $scope.getDocumentFn();

						if(!document)
							throw "Invalid document";

						return editFormUtility.saveDraft(document).then(function(draftInfo) {
							$scope.onPostSaveDraftFn({ data: draftInfo });
							$scope.status = "";

						});
					}).catch(function(error){
						$scope.onErrorFn({ action: "saveDraft", error: error });
					});
				};

				//====================
				//
				//====================
				$scope.new_cancel = function(ev) {

					var confirm = $mdDialog.confirm()
						.title('Close the form?')
						.content('Click "CANCEL" to remain on this record or "CLOSE" to close the form. Changes will be lost if not previously saved.')
						.ariaLabel('close form')
						.ok('CLOSE RECORD')
						.cancel('CANCEL')
						.targetEvent(ev);
						$mdDialog.show(confirm).then(function() {
							$scope.close();
					});
				};

				$scope.validateForPublishing = function(ev) {

					// load the review page
					$q.when($scope.onPrePublishFn()).then(function(result) {
						return result;
					}).then(function(result) {

							if(result)
								return;

					}).then(null, function(error){
						$scope.onErrorFn({ action: "close", error: error });
					});

				}

				//====================
				//
				//====================
				$scope.new_publish = function()
				{

					$q.when($scope.onPrePublishFn()).then(function(result)
					{
						return result;

					}).then(function(canceled) {

						if(canceled)
							return;

						var document = $scope.getDocumentFn();

						$scope.status = "publishing";

						if(!document)
							throw "Invalid document";

						return editFormUtility.publish(document).then(function(documentInfo) {
							$scope.onPostPublishFn({ data: documentInfo });

							var identifier = document.header.identifier;
							var schema     = document.header.schema;

							$rootScope.$broadcast("ProcessingRecord", identifier, schema);

							return documentInfo;
						});

					}).catch(function(error){

						$scope.onErrorFn({ action: "publish", error: error });
					});
				};

				//====================
				//
				//====================
				$scope.new_publishRequest = function()
				{

					$q.when($scope.onPrePublishFn()).then(function(result) {
						return result;
					}).then(function(canceled) {

						if(canceled)
							return;

						var document = $scope.getDocumentFn();
						$scope.status = "publishing";

						if(!document)
							throw "Invalid document";

						return editFormUtility.publishRequest(document).then(function(workflowInfo) {

							$scope.onPostWorkflowFn({ data: workflowInfo });

							return workflowInfo;
						});

					}).catch(function(error){
						$scope.onErrorFn({ action: "publishRequest", error: error });

					});
				};

				//====================
				//
				//====================
				$scope.close = function()
				{
					$q.when($scope.onPreCloseFn()).then(function(result) {
						return result;
					}).then(function(result) {

							if(result)
								return;

							$scope.onPostCloseFn();

					}).then(null, function(error){
						$scope.onErrorFn({ action: "close", error: error });
					});
				};

				//++++++++++++++++++++++++++++++++++++++++++++++++++
				//++++++++++++++++++++++++++++++++++++++++++++++++++
				//++++++++++++++++++++++++++++++++++++++++++++++++++

				//====================
				//
				//====================
				$scope.safeApply = function(fn)
				{
					var phase = this.$root.$$phase;

					if (phase == '$apply' || phase == '$digest') {
						if (fn && (typeof (fn) === 'function')) {
							fn();
						}
					} else {
						this.$apply(fn);
					}
				};

				//==================================
				//
				//==================================
				$scope.$watch('formStatus', function(status) {

					if(!status) return;

					if(status =="ready"){
						$scope.updateSecurity();
						$scope.status = "ready";
					}
				});

				//====================
				//
				//====================
				$scope.updateSecurity = function()
				{
					$scope.security = {};

					$scope.formStatus = "loading";

					$q.when($scope.getDocumentFn()).then(function(document){

						if(!document || !document.header)
							return;

						var identifier = document.header.identifier;
						var schema     = document.header.schema;

						storage.documents.exists(identifier).then(function(exist){

							var q = exist ?
									storage.documents.security.canUpdate(document.header.identifier, schema) :
									storage.documents.security.canCreate(document.header.identifier, schema);

							q.then(function(allowed) {
								$scope.security.canSave = allowed;
							});
						});

						storage.drafts.exists(identifier).then(function(exist){

							var q = exist ?
									storage.drafts.security.canUpdate(document.header.identifier, schema) :
									storage.drafts.security.canCreate(document.header.identifier, schema);

							q.then(function(allowed) {
								$scope.security.canSaveDraft = allowed;
							});
						});
					}).finally(function(){
						$scope.formStatus = "ready";

					});;
				};

				//====================
				//
				//====================
				$scope.checkErrors = function()
				{
					$scope.errors = "";

					if($scope.errors.trim()=="")// jshint ignore:line
						$scope.errors = null;
				};

				//====================
				//
				//====================
				$scope.clone = function(data)
				{
					if(data)
						return angular.fromJson(angular.toJson(data));
				};

			}]
		};
	}]);
});
<commit_msg>implement confirm leaving<commit_after>define(['app', 'angular', 'text!./km-form-material-buttons.html','jquery'], function(app, angular, template) { 'use strict';

	app.directive('kmFormMaterialButtons', ["$q", function ($q)
	{
		return {
			restrict: 'EAC',
			template: template,
			replace: true,
			transclude: true,
			scope: {
				formStatus     	  : "=status",
				getDocumentFn     : "&document",
				onPreCloseFn      : "&onPreClose",
				onPostCloseFn     : "&onPostClose",
				onPreRevertFn     : "&onPreRevert",
				onPostRevertFn    : "&onPostRevert",
				onPreSaveDraftFn  : "&onPreSaveDraft",
				onPostSaveDraftFn : "&onPostSaveDraft",
				onPrePublishFn    : "&onPrePublish",
				onPostPublishFn   : "&onPostPublish",
				onPostWorkflowFn  : "&onPostWorkflow",
				onErrorFn: "&onError"
			},
			link: function ($scope, $element)
			{
				$scope.errors = null;
				$scope.status = "loading buttons";

			},
			controller: ["$scope", "$rootScope", "IStorage", "authentication", "editFormUtility", "$mdDialog", "$timeout", "$location",
			 function ($scope, $rootScope, storage, authentication, editFormUtility, $mdDialog, $timeout, $location)
			{
				var next_url;
				//====================
				//
				//====================
				$scope.new_saveDraft = function()
				{
					$scope.status = "saving";

					$q.when($scope.onPreSaveDraftFn()).then(function(result) {
							return result;
					}).then(function(cancel) {
						if(cancel)
							return;

						var document = $scope.getDocumentFn();

						if(!document)
							throw "Invalid document";

						return editFormUtility.saveDraft(document).then(function(draftInfo) {
							$scope.onPostSaveDraftFn({ data: draftInfo });
							$scope.status = "";

						});
					}).catch(function(error){
						$scope.onErrorFn({ action: "saveDraft", error: error });
					});
				};

				//====================
				//
				//====================
				$scope.new_cancel = function(ev) {

					var confirm = $mdDialog.confirm()
						.title('Close the form?')
						.content('Click "CANCEL" to remain on this record or "CLOSE" to close the form. Changes will be lost if not previously saved.')
						.ariaLabel('close form')
						.ok('CLOSE RECORD')
						.cancel('CANCEL')
						.targetEvent(ev);
						$mdDialog.show(confirm).then(function(a,b) {
							console.log('yes')
							$rootScope.originalDocument = undefined;
							$rootScope.isFormLeaving = false;
							if(next_url){
								var url = angular.copy(next_url);
								next_url = undefined;
								$timeout(function(){
									var absHosts = ['https://chm.cbddev.xyz/', 'https://chm.staging.cbd.int/',
										'http://localhost:8000/', 'https://chm.cbd.int/'
									]
									url = url.replace($location.$$protocol + '://' +
										$location.$$host + ($location.$$host != 'chm.cbd.int' ? ':' + $location.$$port : '') + '/', '');
									_.each(absHosts, function(host) {
										url = url.replace(host, '');
									});
									url = url.replace(/^(en|ar|fr|es|ru|zh)\//, '/');
									$location.path(url);
								},1);
							}
							else
								$scope.close();
						}, function() {
								console.log('no')
							$rootScope.isFormLeaving = false;
						});
				};

				$scope.validateForPublishing = function(ev) {

					// load the review page
					$q.when($scope.onPrePublishFn()).then(function(result) {
						return result;
					}).then(function(result) {

							if(result)
								return;

					}).then(null, function(error){
						$scope.onErrorFn({ action: "close", error: error });
					});

				}

				//====================
				//
				//====================
				$scope.new_publish = function()
				{

					$q.when($scope.onPrePublishFn()).then(function(result)
					{
						return result;

					}).then(function(canceled) {

						if(canceled)
							return;

						var document = $scope.getDocumentFn();

						$scope.status = "publishing";

						if(!document)
							throw "Invalid document";

						return editFormUtility.publish(document).then(function(documentInfo) {
							$scope.onPostPublishFn({ data: documentInfo });

							var identifier = document.header.identifier;
							var schema     = document.header.schema;

							$rootScope.$broadcast("ProcessingRecord", identifier, schema);

							return documentInfo;
						});

					}).catch(function(error){

						$scope.onErrorFn({ action: "publish", error: error });
					});
				};

				//====================
				//
				//====================
				$scope.new_publishRequest = function()
				{

					$q.when($scope.onPrePublishFn()).then(function(result) {
						return result;
					}).then(function(canceled) {

						if(canceled)
							return;

						var document = $scope.getDocumentFn();
						$scope.status = "publishing";

						if(!document)
							throw "Invalid document";

						return editFormUtility.publishRequest(document).then(function(workflowInfo) {

							$scope.onPostWorkflowFn({ data: workflowInfo });

							return workflowInfo;
						});

					}).catch(function(error){
						$scope.onErrorFn({ action: "publishRequest", error: error });

					});
				};

				//====================
				//
				//====================
				$scope.close = function()
				{
					$q.when($scope.onPreCloseFn()).then(function(result) {
						return result;
					}).then(function(result) {

							if(result)
								return;

							$scope.onPostCloseFn();

					}).then(null, function(error){
						$scope.onErrorFn({ action: "close", error: error });
					});
				};

				//++++++++++++++++++++++++++++++++++++++++++++++++++
				//++++++++++++++++++++++++++++++++++++++++++++++++++
				//++++++++++++++++++++++++++++++++++++++++++++++++++

				//====================
				//
				//====================
				$scope.safeApply = function(fn)
				{
					var phase = this.$root.$$phase;

					if (phase == '$apply' || phase == '$digest') {
						if (fn && (typeof (fn) === 'function')) {
							fn();
						}
					} else {
						this.$apply(fn);
					}
				};

				//==================================
				//
				//==================================
				$scope.$watch('formStatus', function(status) {

					if(!status) return;

					if(status =="ready"){
						$scope.updateSecurity();
						$scope.status = "ready";

						$rootScope.originalDocument = $scope.getDocumentFn();
					}
				});

				//====================
				//
				//====================
				$scope.updateSecurity = function()
				{
					$scope.security = {};

					$scope.formStatus = "loading";

					$q.when($scope.getDocumentFn()).then(function(document){

						if(!document || !document.header)
							return;

						var identifier = document.header.identifier;
						var schema     = document.header.schema;

						storage.documents.exists(identifier).then(function(exist){

							var q = exist ?
									storage.documents.security.canUpdate(document.header.identifier, schema) :
									storage.documents.security.canCreate(document.header.identifier, schema);

							q.then(function(allowed) {
								$scope.security.canSave = allowed;
							});
						});

						storage.drafts.exists(identifier).then(function(exist){

							var q = exist ?
									storage.drafts.security.canUpdate(document.header.identifier, schema) :
									storage.drafts.security.canCreate(document.header.identifier, schema);

							q.then(function(allowed) {
								$scope.security.canSaveDraft = allowed;
							});
						});
					}).finally(function(){
						$scope.formStatus = "ready";

					});;
				};

				//====================
				//
				//====================
				$scope.checkErrors = function()
				{
					$scope.errors = "";

					if($scope.errors.trim()=="")// jshint ignore:line
						$scope.errors = null;
				};

				//====================
				//
				//====================
				$scope.clone = function(data)
				{
					if(data)
						return angular.fromJson(angular.toJson(data));
				};

				$rootScope.isFormLeaving = false;
				function confirmLeaving(evt, next, current) {
					console.log(next, current, $rootScope.isFormLeaving);
					if($rootScope.isFormLeaving)
						return;
					var formChanged = !angular.equals($scope.getDocumentFn(), $rootScope.originalDocument);

					if(!$rootScope.originalDocument || !formChanged){
						return;
					}
						$rootScope.isFormLeaving = true;
				// },1);
					evt.preventDefault();
					next_url = next;
					$scope.new_cancel();
				}

				$scope.$on('$locationChangeStart', confirmLeaving);
				// $scope.$on('$locationChangeSuccess', function(evt, data){
				// 	next_url = undefined;
				// });
			}]
		};
	}]);
});
<|endoftext|>


<commit_before>import React, { Component } from 'react'
import Navbar from "../../components/Navbar.react"
import InputURL from "../../components/inputURL.react"

export default class Home extends Component {

  render() {
    return (

      <Navbar />

      <InputURL />

);
  }
}
<commit_msg>Message for front carosel..<commit_after>import React, { Component } from 'react'
import Navbar from "../../components/Navbar.react"
import InputURL from "../../components/inputURL.react"

export default class Home extends Component {

  constructor(props) {
     super(props);
     this.state = {
       messageIndex: 1,
       messages:[
         'a','b', 'c'
       ]
     };
   }
  render() {
    let messageIndex = this.state.messageIndex;
    return (

      <Navbar />

      <InputURL />

        {this.state.messages[messageIndex]}

          {this.state.messages.map((d, i)=>(

))}

);
  }
}
<|endoftext|>


<commit_before>(function () {

    angular.module('foodGenerator')
        .factory("reqErrInterceptor", reqErrInterceptor);

    function reqErrInterceptor(BASEURL, $injector, $q) {

        return {
            responseError: responseError
        };

        //////////////////////////////

        function responseError(rej) {
            if ([-1, 404].indexOf(rej.status) !== -1) {
                showAlert({title: 'Connection Error', msg: 'Could not reach the server. Try again later'});
            } else {
                showAlert({title: 'Unknown Error', msg: 'Unknown error. Try again later'});
            }

            return $q.reject(rej);
        }

        function showAlert(opt) {
            //inject manually to resolve circular dependency error
            var $mdDialog = $injector.get('$mdDialog');
            var alert = $mdDialog.alert({
                title: opt.title,
                textContent: opt.msg,
                ok: 'Close'
            });

            $mdDialog.show(alert)

        }

    }

})();<commit_msg>fixed request-error-interceptor using deprecated plugin<commit_after>(function () {

    angular.module('foodGenerator')
        .factory("reqErrInterceptor", reqErrInterceptor);

    function reqErrInterceptor(BASEURL, $injector, $q) {

        return {
            responseError: responseError
        };

        //////////////////////////////

        function responseError(rej) {
            if ([-1, 404].indexOf(rej.status) !== -1) {
                showAlert({title: 'Connection Error', msg: 'Could not reach the server. Try again later'});
            } else if ([-1, 401].indexOf(rej.status) !== -1) {
                //do nothing, login failed
            } else {
                showAlert({title: 'Unknown Error', msg: 'Unknown error. Try again later'});
            }

            return $q.reject(rej);
        }

        function showAlert(opt) {
            alert(opt.title + ": " + opt.msg);
            // //inject manually to resolve circular dependency error
            // var $mdDialog = $injector.get('$mdDialog');
            // var alert = $mdDialog.alert({
            //     title: opt.title,
            //     textContent: opt.msg,
            //     ok: 'Close'
            // });
            //
            // $mdDialog.show(alert)

        }

    }

})();<|endoftext|>


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

  angular
    .module('porttare.controllers')
    .controller('CategoriesController', CategoriesController);

  function CategoriesController($scope) {

    var categoryVm = this;
    var counter = 0;
    categoryVm.categories = function () { return $scope.$parent.menuVm.categories; };

    $scope.$on('$ionicView.enter', function() {
      if (counter !== 0) {
        $scope.$emit('update-categories');
      }
      counter++;
    });

    $scope.$watch('$parent.menuVm.categories', function(){
      categoryVm.categories();
    });
  }
})();
<commit_msg>Validate cache with time<commit_after>(function () {
  'use strict';

  angular
    .module('porttare.controllers')
    .controller('CategoriesController', CategoriesController);

  function CategoriesController($scope) {

    var categoryVm = this;
    var cacheInit = moment();
    categoryVm.categories = function () { return $scope.$parent.menuVm.categories; };

    $scope.$on('$ionicView.enter', function() {
      if (moment().diff(cacheInit, 'minutes') > 10) {
        $scope.$emit('update-categories');
      }
    });

    $scope.$watch('$parent.menuVm.categories', function(){
      categoryVm.categories();
    });
  }
})();
<|endoftext|>


<commit_before>'use strict';

angular.module('openSenseMapApp')
.controller('InterpolationCtrl',
	["$scope", "$stateParams", "$http", "OpenSenseBoxAPI", "leafletData", "$timeout", "moment", function($scope, $stateParams, $http, OpenSenseBoxAPI, leafletData, $timeout, moment){

    $scope.calculating = false;
    $scope.interpolationPickerStart = {
      date: moment().subtract(5, 'm').toDate(),
      open: false,
      buttonBar: {
        show: false
      },
      timepickerOptions: {
        readonlyInput: false,
        showMeridian: false,
        max: null,
        min: null
      },
      datepickerOptions: {
        minDate: null
      }
    };
    $scope.interpolationPickerEnd = {
      date: moment().toDate(),
      open: false,
      buttonBar: {
        show: false
      },
      timepickerOptions: {
        readonlyInput: false,
        showMeridian: false,
        max: null,
        min: null
      },
      datepickerOptions: {
        maxDate: null
      }
    };

    $scope.visible = false;
    var dates = [];
    $scope.selectedTimeStep = Date.now();
    $scope.slider = {
      options: {
        stepsArray: dates,
        showTicks: true,
        hidePointerLabels: true,
        hideLimitLabels: true,
        onChange: function(id, value) {
          changeSlider(value);
        }
      }
    }

    var changeSlider = function (value) {
      $scope.selectedTimeStep = value;
      $scope.map.removeLayer($scope.layer);
      for (var i = 0; i < dates.length; i++) {
        if (dates[i].toISOString() === value.toISOString()) {
          $scope.layer = createGeoJsonLayer(featureCollections[i], breaks);
          $scope.map.addLayer($scope.layer);
          break;
        }
      }
    }

    $scope.refreshSlider = function () {
      $timeout(function () {
        $scope.$broadcast('rzSliderForceRender');
      });
    };

    $scope.legendTitle = '';
    $scope.legendEntries = [];

    $scope.alerts = [];
    $scope.closeAlert = function(index) {
      $scope.alerts.splice(index, 1);
    };

    $scope.idwPower = 3;
    $scope.cellWidth = 1;
    $scope.numTimeSteps = 1;
    $scope.exposure = "outdoor";
    $scope.layerGroup;
    $scope.selectedPhenomenon = "";
    $scope.map;

    $scope.minIDWPower = 1;
    $scope.maxIDWPower = 9;

    $scope.changeIDWPower = function(number) {
      var newValue = $scope.idwPower + number;
      if (newValue >= $scope.minIDWPower && newValue <= $scope.maxIDWPower) {
        $scope.idwPower += number;
      }
    }

    $scope.minNumTimeSteps = 1;
    $scope.maxNumTimeSteps = 9;

    $scope.changeNumTimeSteps = function(number) {
      var newValue = $scope.numTimeSteps + number;
      if (newValue >= $scope.minNumTimeSteps && newValue <= $scope.maxNumTimeSteps) {
        $scope.numTimeSteps += number;
      }
    }

    $scope.layer;
    var colors = "#A2F689,#B1E36F,#BCD05B,#C4BD4C,#C8AA44,#C99840".split(",");
    var breaks = [];
    var idwLayers = [];
    var featureCollections = [];
    $scope.calculateInterpolation = function () {
      clearInterpolation();
      $scope.calculating = true;

      leafletData.getMap('map_main').then(function (map) {
        if (!angular.isUndefined($scope.layer)) {
          $scope.map.removeLayer($scope.layer);
        }
        $scope.map = map;
        return map.getBounds().toBBoxString();
      }).then(function (bbox) {
        $http.get(OpenSenseBoxAPI.url+'/statistics/idw', {
          params: {
            'phenomenon': $scope.selectedPhenomenon,
            'from-date': moment($scope.interpolationPickerStart.date).toISOString(),
            'to-date': moment($scope.interpolationPickerEnd.date).toISOString(),
            'exposure': $scope.exposure,
            'cellWidth': $scope.cellWidth,
            'power': $scope.idwPower,
            'numClasses': 6,
            'bbox': bbox,
            'numTimeSteps': $scope.numTimeSteps
          }
        })
        .then(function (response) {
          if (response.data.code === 'NotFoundError') {
            return response;
          }

          dates = [];
          response.data.data.timesteps.forEach(function (element, index) {
            dates.push(new Date(element));
            featureCollections.push({type: "FeatureCollection", features: []});
          });

          $scope.slider.value = dates[0];
          $scope.slider.options.stepsArray = dates;
          $scope.selectedTimeStep = dates[0];

          breaks = response.data.data.breaks;

          // Set legend title
          switch ($scope.selectedPhenomenon) {
            case 'Temperatur':
              $scope.legendTitle = $scope.selectedPhenomenon + " in °C"
              break;
            case 'rel. Luftfeuchte':
              $scope.legendTitle = $scope.selectedPhenomenon + " in %"
              break;
            case 'Luftdruck':
              $scope.legendTitle = $scope.selectedPhenomenon + " in hPa"
              break;
            case 'Beleuchtungsstärke':
              $scope.legendTitle = $scope.selectedPhenomenon + " in lx"
              break;
            case 'UV-Intensität':
              $scope.legendTitle = $scope.selectedPhenomenon + " in μW/cm²"
              break;
          }

          // Generate legend
          for (var j = 0; j < breaks.length; j++) {
            var caption = "";
            if (j === breaks.length-1) {
              caption = "> " + breaks[j].toFixed(2);
            } else {
              caption = breaks[j].toFixed(2) +' - '+ breaks[j+1].toFixed(2);
            }
            $scope.legendEntries.push({
              caption: caption,
              style:{
                'background': colors[j]
              }
            });
          }

          response.data.data.featureCollection.features.map(function (feature) {
            for (var i = 0; i < feature.properties.idwValues.length; i++) {
              var newFeature = {};
              angular.copy(feature, newFeature);
              newFeature.properties.idwValues = [];
              var value = feature.properties.idwValues[i];
              newFeature.properties.idwValues.push(value);
              featureCollections[i].features.push(newFeature);
            }
          });

          $scope.layer = createGeoJsonLayer(featureCollections[0], breaks);
          $scope.map.addLayer($scope.layer);

          if (featureCollections.length > 1) {
            $scope.visible = true;
          }
          $scope.refreshSlider();

          return;
        }, function(error) {
          return error;
        })
        .then(function (error) {
          if (angular.isUndefined(error)) {
            return;
          }

          switch (error.data.message) {
            case "planned computation too expensive ((area in square kilometers / cellWidth) > 2500)":
              $scope.alerts.push({msg: 'Der gewählte Kartenausschnitt ist zu groß!'});
              break;
            case "Invalid time frame specified: to-date is in the future":
              $scope.alerts.push({msg: 'Das gewählte Datum darf nicht in der Zukunft liegen!'});
              break;
            case "no senseBoxes found":
              $scope.alerts.push({msg: 'Es konnten keine senseBoxen für die Filtereinstellungen gefunden werden!'});
              break;
            case "no measurements found":
              $scope.alerts.push({msg: 'Es wurden keine Messungen für den angegebenen Zeitpunkt gefunden!'});
              break;
            default:
              $scope.alerts.push({msg: 'Bei der Interpolation ist ein unbekannter Fehler aufgetreten! Bitte überprüfe die Filtereinstellungen.'});
              break;
          }
        })
        .finally(function () {
          $scope.calculating = false;
        });
      });
    };

    $scope.prom;
    var delay = 5000;
    $scope.isPlaying = false;
    $scope.playInterpolation = function () {
      $scope.isPlaying = true;
      $scope.prom = $timeout($scope.playInterpolation, delay);
      var value = 0;
      var nextIndex = dates.indexOf($scope.selectedTimeStep)+1;
      if (angular.isUndefined(dates[nextIndex])) {
        value = dates[0];
      } else {
        value = dates[nextIndex];
      }
      $scope.selectedTimeStep = value;
      $scope.slider.value = value;
      changeSlider(value);
    }

    $scope.stopInterpolation = function () {
      $scope.isPlaying = false;
      $timeout.cancel($scope.prom);
    }

    function createGeoJsonLayer (featureCollection, breaks) {
      var idwLayer = L.geoJson(featureCollection, {
        style: function (feature) {
          var props = feature.properties;
          for (var key in props) {
            var z = props[key];
            if (!Number.isNaN(z)) {
              var fillColor = colors[0];
              for (var i = 0; i < breaks.length; i++) {
                if (z >= breaks[i]) {
                  fillColor = colors[i];
                } else {
                  break;
                }
              }
              return {
                weight: 0.1,
                fillOpacity: 0.6,
                fillColor: fillColor
              }
            }
            return {
              weight: 0,
              fillColor: "red",
              fillOpacity: 1
            };
          }
        }
      });
      return idwLayer;
    }

    function clearInterpolation () {
      $scope.isPlaying = false;
      $scope.alerts.length = 0;
      $scope.legendEntries.length = 0;
      $scope.legendTitle = '';
      $timeout.cancel($scope.prom);
      idwLayers = [];
      featureCollections = [];
      breaks = [];
      if (!angular.isUndefined($scope.map) && !angular.isUndefined($scope.layer)) {
        $scope.map.removeLayer($scope.layer);
        delete $scope.layer;
        $scope.visible = false;
      }
    }

    $scope.removeInterpolation = function () {
      clearInterpolation();
    }

    $scope.showRemoveInterpolation = function () {
      if (!angular.isUndefined($scope.map) && !angular.isUndefined($scope.layer)) {
        return true;
      }
      return false;
    }

    $scope.openCalendar = function(e, picker) {
      e.preventDefault();
      e.stopPropagation();
      switch(picker) {
        case 'interpolationPickerStart':
          $scope.interpolationPickerStart.open = true;
          // $scope.interpolationPickerStart.timepickerOptions.max = moment($scope.interpolationPickerEnd.date).toISOString();
          break;
        case 'interpolationPickerEnd':
          $scope.interpolationPickerEnd.open = true;
          $scope.interpolationPickerEnd.datepickerOptions.maxDate = moment().toDate();
          $scope.interpolationPickerEnd.datepickerOptions.minDate = $scope.interpolationPickerStart.date;
          $scope.interpolationPickerEnd.timepickerOptions.max = moment().toDate();
          $scope.interpolationPickerEnd.timepickerOptions.min = $scope.interpolationPickerStart.timepickerOptions.max;
          $timeout(function () {
            angular.element('#interpolationPickerEnd').parent()[0].children[1].style.right = "0px";
            angular.element('#interpolationPickerEnd').parent()[0].children[1].style.left = "auto";
          });
          break;
      }
    }

    // watch min and max dates to calculate difference
    var unwatchMinMaxValues = $scope.$watch(function() {
      return [$scope.interpolationPickerStart, $scope.interpolationPickerEnd];
    }, function() {
      // min max dates
      $scope.interpolationPickerStart.datepickerOptions.maxDate = $scope.interpolationPickerEnd.date;
      $scope.interpolationPickerEnd.datepickerOptions.minDate = $scope.interpolationPickerStart.date;

      // min max times
      $scope.interpolationPickerStart.timepickerOptions.max = $scope.interpolationPickerEnd.date;
      $scope.interpolationPickerEnd.timepickerOptions.min = $scope.interpolationPickerStart.date;
    }, true);

    // destroy watcher
    $scope.$on('$destroy', function() {
      unwatchMinMaxValues();
    });

    $scope.selectExposure = function (exposure) {
      $scope.exposure = exposure;
    };

    $scope.closeSidebar = function () {
      if (!angular.isUndefined($scope.map) && !angular.isUndefined($scope.layer)) {
        clearInterpolation()
      }
    }
}]);
<commit_msg>remove interpolation when controller is destroyed #161<commit_after>'use strict';

angular.module('openSenseMapApp')
.controller('InterpolationCtrl',
	["$scope", "$stateParams", "$http", "OpenSenseBoxAPI", "leafletData", "$timeout", "moment", function($scope, $stateParams, $http, OpenSenseBoxAPI, leafletData, $timeout, moment){

    $scope.calculating = false;
    $scope.interpolationPickerStart = {
      date: moment().subtract(5, 'm').toDate(),
      open: false,
      buttonBar: {
        show: false
      },
      timepickerOptions: {
        readonlyInput: false,
        showMeridian: false,
        max: null,
        min: null
      },
      datepickerOptions: {
        minDate: null
      }
    };
    $scope.interpolationPickerEnd = {
      date: moment().toDate(),
      open: false,
      buttonBar: {
        show: false
      },
      timepickerOptions: {
        readonlyInput: false,
        showMeridian: false,
        max: null,
        min: null
      },
      datepickerOptions: {
        maxDate: null
      }
    };

    $scope.visible = false;
    var dates = [];
    $scope.selectedTimeStep = Date.now();
    $scope.slider = {
      options: {
        stepsArray: dates,
        showTicks: true,
        hidePointerLabels: true,
        hideLimitLabels: true,
        onChange: function(id, value) {
          changeSlider(value);
        }
      }
    }

    var changeSlider = function (value) {
      $scope.selectedTimeStep = value;
      $scope.map.removeLayer($scope.layer);
      for (var i = 0; i < dates.length; i++) {
        if (dates[i].toISOString() === value.toISOString()) {
          $scope.layer = createGeoJsonLayer(featureCollections[i], breaks);
          $scope.map.addLayer($scope.layer);
          break;
        }
      }
    }

    $scope.refreshSlider = function () {
      $timeout(function () {
        $scope.$broadcast('rzSliderForceRender');
      });
    };

    $scope.legendTitle = '';
    $scope.legendEntries = [];

    $scope.alerts = [];
    $scope.closeAlert = function(index) {
      $scope.alerts.splice(index, 1);
    };

    $scope.idwPower = 3;
    $scope.cellWidth = 1;
    $scope.numTimeSteps = 1;
    $scope.exposure = "outdoor";
    $scope.layerGroup;
    $scope.selectedPhenomenon = "";
    $scope.map;

    $scope.minIDWPower = 1;
    $scope.maxIDWPower = 9;

    $scope.changeIDWPower = function(number) {
      var newValue = $scope.idwPower + number;
      if (newValue >= $scope.minIDWPower && newValue <= $scope.maxIDWPower) {
        $scope.idwPower += number;
      }
    }

    $scope.minNumTimeSteps = 1;
    $scope.maxNumTimeSteps = 9;

    $scope.changeNumTimeSteps = function(number) {
      var newValue = $scope.numTimeSteps + number;
      if (newValue >= $scope.minNumTimeSteps && newValue <= $scope.maxNumTimeSteps) {
        $scope.numTimeSteps += number;
      }
    }

    $scope.layer;
    var colors = "#A2F689,#B1E36F,#BCD05B,#C4BD4C,#C8AA44,#C99840".split(",");
    var breaks = [];
    var idwLayers = [];
    var featureCollections = [];
    $scope.calculateInterpolation = function () {
      clearInterpolation();
      $scope.calculating = true;

      leafletData.getMap('map_main').then(function (map) {
        if (!angular.isUndefined($scope.layer)) {
          $scope.map.removeLayer($scope.layer);
        }
        $scope.map = map;
        return map.getBounds().toBBoxString();
      }).then(function (bbox) {
        $http.get(OpenSenseBoxAPI.url+'/statistics/idw', {
          params: {
            'phenomenon': $scope.selectedPhenomenon,
            'from-date': moment($scope.interpolationPickerStart.date).toISOString(),
            'to-date': moment($scope.interpolationPickerEnd.date).toISOString(),
            'exposure': $scope.exposure,
            'cellWidth': $scope.cellWidth,
            'power': $scope.idwPower,
            'numClasses': 6,
            'bbox': bbox,
            'numTimeSteps': $scope.numTimeSteps
          }
        })
        .then(function (response) {
          if (response.data.code === 'NotFoundError') {
            return response;
          }

          dates = [];
          response.data.data.timesteps.forEach(function (element, index) {
            dates.push(new Date(element));
            featureCollections.push({type: "FeatureCollection", features: []});
          });

          $scope.slider.value = dates[0];
          $scope.slider.options.stepsArray = dates;
          $scope.selectedTimeStep = dates[0];

          breaks = response.data.data.breaks;

          // Set legend title
          switch ($scope.selectedPhenomenon) {
            case 'Temperatur':
              $scope.legendTitle = $scope.selectedPhenomenon + " in °C"
              break;
            case 'rel. Luftfeuchte':
              $scope.legendTitle = $scope.selectedPhenomenon + " in %"
              break;
            case 'Luftdruck':
              $scope.legendTitle = $scope.selectedPhenomenon + " in hPa"
              break;
            case 'Beleuchtungsstärke':
              $scope.legendTitle = $scope.selectedPhenomenon + " in lx"
              break;
            case 'UV-Intensität':
              $scope.legendTitle = $scope.selectedPhenomenon + " in μW/cm²"
              break;
          }

          // Generate legend
          for (var j = 0; j < breaks.length; j++) {
            var caption = "";
            if (j === breaks.length-1) {
              caption = "> " + breaks[j].toFixed(2);
            } else {
              caption = breaks[j].toFixed(2) +' - '+ breaks[j+1].toFixed(2);
            }
            $scope.legendEntries.push({
              caption: caption,
              style:{
                'background': colors[j]
              }
            });
          }

          response.data.data.featureCollection.features.map(function (feature) {
            for (var i = 0; i < feature.properties.idwValues.length; i++) {
              var newFeature = {};
              angular.copy(feature, newFeature);
              newFeature.properties.idwValues = [];
              var value = feature.properties.idwValues[i];
              newFeature.properties.idwValues.push(value);
              featureCollections[i].features.push(newFeature);
            }
          });

          $scope.layer = createGeoJsonLayer(featureCollections[0], breaks);
          $scope.map.addLayer($scope.layer);

          if (featureCollections.length > 1) {
            $scope.visible = true;
          }
          $scope.refreshSlider();

          return;
        }, function(error) {
          return error;
        })
        .then(function (error) {
          if (angular.isUndefined(error)) {
            return;
          }

          switch (error.data.message) {
            case "planned computation too expensive ((area in square kilometers / cellWidth) > 2500)":
              $scope.alerts.push({msg: 'Der gewählte Kartenausschnitt ist zu groß!'});
              break;
            case "Invalid time frame specified: to-date is in the future":
              $scope.alerts.push({msg: 'Das gewählte Datum darf nicht in der Zukunft liegen!'});
              break;
            case "no senseBoxes found":
              $scope.alerts.push({msg: 'Es konnten keine senseBoxen für die Filtereinstellungen gefunden werden!'});
              break;
            case "no measurements found":
              $scope.alerts.push({msg: 'Es wurden keine Messungen für den angegebenen Zeitpunkt gefunden!'});
              break;
            default:
              $scope.alerts.push({msg: 'Bei der Interpolation ist ein unbekannter Fehler aufgetreten! Bitte überprüfe die Filtereinstellungen.'});
              break;
          }
        })
        .finally(function () {
          $scope.calculating = false;
        });
      });
    };

    $scope.prom;
    var delay = 5000;
    $scope.isPlaying = false;
    $scope.playInterpolation = function () {
      $scope.isPlaying = true;
      $scope.prom = $timeout($scope.playInterpolation, delay);
      var value = 0;
      var nextIndex = dates.indexOf($scope.selectedTimeStep)+1;
      if (angular.isUndefined(dates[nextIndex])) {
        value = dates[0];
      } else {
        value = dates[nextIndex];
      }
      $scope.selectedTimeStep = value;
      $scope.slider.value = value;
      changeSlider(value);
    }

    $scope.stopInterpolation = function () {
      $scope.isPlaying = false;
      $timeout.cancel($scope.prom);
    }

    function createGeoJsonLayer (featureCollection, breaks) {
      var idwLayer = L.geoJson(featureCollection, {
        style: function (feature) {
          var props = feature.properties;
          for (var key in props) {
            var z = props[key];
            if (!Number.isNaN(z)) {
              var fillColor = colors[0];
              for (var i = 0; i < breaks.length; i++) {
                if (z >= breaks[i]) {
                  fillColor = colors[i];
                } else {
                  break;
                }
              }
              return {
                weight: 0.1,
                fillOpacity: 0.6,
                fillColor: fillColor
              }
            }
            return {
              weight: 0,
              fillColor: "red",
              fillOpacity: 1
            };
          }
        }
      });
      return idwLayer;
    }

    function clearInterpolation () {
      $scope.isPlaying = false;
      $scope.alerts.length = 0;
      $scope.legendEntries.length = 0;
      $scope.legendTitle = '';
      $timeout.cancel($scope.prom);
      idwLayers = [];
      featureCollections = [];
      breaks = [];
      if (!angular.isUndefined($scope.map) && !angular.isUndefined($scope.layer)) {
        $scope.map.removeLayer($scope.layer);
        delete $scope.layer;
        $scope.visible = false;
      }
    }

    $scope.removeInterpolation = function () {
      clearInterpolation();
    }

    $scope.showRemoveInterpolation = function () {
      if (!angular.isUndefined($scope.map) && !angular.isUndefined($scope.layer)) {
        return true;
      }
      return false;
    }

    $scope.openCalendar = function(e, picker) {
      e.preventDefault();
      e.stopPropagation();
      switch(picker) {
        case 'interpolationPickerStart':
          $scope.interpolationPickerStart.open = true;
          // $scope.interpolationPickerStart.timepickerOptions.max = moment($scope.interpolationPickerEnd.date).toISOString();
          break;
        case 'interpolationPickerEnd':
          $scope.interpolationPickerEnd.open = true;
          $scope.interpolationPickerEnd.datepickerOptions.maxDate = moment().toDate();
          $scope.interpolationPickerEnd.datepickerOptions.minDate = $scope.interpolationPickerStart.date;
          $scope.interpolationPickerEnd.timepickerOptions.max = moment().toDate();
          $scope.interpolationPickerEnd.timepickerOptions.min = $scope.interpolationPickerStart.timepickerOptions.max;
          $timeout(function () {
            angular.element('#interpolationPickerEnd').parent()[0].children[1].style.right = "0px";
            angular.element('#interpolationPickerEnd').parent()[0].children[1].style.left = "auto";
          });
          break;
      }
    }

    // watch min and max dates to calculate difference
    var unwatchMinMaxValues = $scope.$watch(function() {
      return [$scope.interpolationPickerStart, $scope.interpolationPickerEnd];
    }, function() {
      // min max dates
      $scope.interpolationPickerStart.datepickerOptions.maxDate = $scope.interpolationPickerEnd.date;
      $scope.interpolationPickerEnd.datepickerOptions.minDate = $scope.interpolationPickerStart.date;

      // min max times
      $scope.interpolationPickerStart.timepickerOptions.max = $scope.interpolationPickerEnd.date;
      $scope.interpolationPickerEnd.timepickerOptions.min = $scope.interpolationPickerStart.date;
    }, true);

    // destroy watcher
    $scope.$on('$destroy', function() {
      unwatchMinMaxValues();
      clearInterpolation();
    });

    $scope.selectExposure = function (exposure) {
      $scope.exposure = exposure;
    };

    $scope.closeSidebar = function () {
      if (!angular.isUndefined($scope.map) && !angular.isUndefined($scope.layer)) {
        clearInterpolation()
      }
    }
}]);
<|endoftext|>