<commit_before>import React from 'react';
import {connectToStores} from 'fluxible-addons-react';
import FocusTrap from 'focus-trap-react';
import { FormattedMessage, defineMessages } from 'react-intl';
import { Button, Modal, Divider, TextArea, Dropdown, Segment} from 'semantic-ui-react';
import TranslationStore from '../../../../stores/TranslationStore';
import ISO6391 from 'iso-639-1';
import {navigateAction} from 'fluxible-router';

class DeckTranslationsModal extends React.Component {

    constructor(props) {
        super(props);

        this.state = {
            openModal: false,
            activeTrap: false,
            action: '',
            languageCode: ''
        };

        this.handleOpen = this.handleOpen.bind(this);
        this.handleClose = this.handleClose.bind(this);
        this.unmountTrap = this.unmountTrap.bind(this);
    }

    handleOpen(){
        $('#app').attr('aria-hidden','true');
        this.setState({
            modalOpen:true,
            activeTrap:true
        });
    }

    handleClose(){
        $('#app').attr('aria-hidden','false');
        this.setState({
            modalOpen:false,
            activeTrap: false,
            action: '',
            languageCode: ''
        });
    }

    unmountTrap(){
        if(this.state.activeTrap){
            this.setState({ activeTrap: false });
            $('#app').attr('aria-hidden','false');
        }
    }

    handleTranslationSelection(e, data) {
        this.setState({ action: 'languageselect', languageCode: data.value });
    }

    handleLanguageSelection(e, data) {
        this.setState({ action: 'translate', languageCode: data.value });
    }

    handleActionClick(e) {
        if (this.state.action === 'languageselect') {
            this.handleClose();

            let path = location.pathname;
            let pathElements = path.split('/');
            let element = pathElements[2];
            let index = element.indexOf('_');
            if (index === -1) {
                pathElements[2] = element + '_' + this.state.languageCode;
            }
            else {
                pathElements[2] = element.substring(0, index) + '_' + this.state.languageCode;
            }
            path = pathElements.join('/');
            this.context.executeAction(navigateAction, {
                url: path
            });
        }
    }

    render() {
        const messages = defineMessages({
            header: {
                id: 'DeckTranslationsModal.header',
                defaultMessage: 'Translation selection',
            },
            currentLanguage: {
                id: 'DeckTranslationsModal.currentLanguage',
                defaultMessage: 'This deck has the following language:',
            },
            currentTranslation: {
                id: 'DeckTranslationsModal.currentTranslation',
                defaultMessage: 'This is a translation of the deck in the following language:',
            },
            switch: {
                id: 'DeckTranslationsModal.switch',
                defaultMessage: 'Select a translation to switch to it:',
            },
            chooseTranslation: {
                id: 'DeckTranslationsModal.chooseTranslation',
                defaultMessage: 'Choose a translation...',
            },
            chooseLanguage: {
                id: 'DeckTranslationsModal.chooseLanguage',
                defaultMessage: 'Choose the target language...',
            },
            startTranslation: {
                id: 'DeckTranslationsModal.startTranslation',
                defaultMessage: 'Or you can start a translation - just select the target language:',
            },
            cancel: {
                id: 'DeckTranslationsModal.cancel',
                defaultMessage: 'Cancel',
            },
            translate: {
                id: 'DeckTranslationsModal.translate',
                defaultMessage: 'Create translation',
            },
            language: {
                id: 'DeckTranslationsModal.language',
                defaultMessage: 'Switch to language',
            },
        });

        let message = this.props.TranslationStore.translations.find((t) => t === this.props.TranslationStore.treeLanguage) ? messages.currentTranslation : messages.currentLanguage;

        let translationOptions = [];
        if (this.props.TranslationStore.translations && this.props.TranslationStore.translations.length > 0) {
            translationOptions = this.props.TranslationStore.translations.reduce((arr, current)  => {
                arr.push({key: current, value: current, text: ISO6391.getNativeName(current)});
                return arr;
            }, []);
        }

        let languagesOptions = [];
        if (this.props.TranslationStore.supportedLangs && this.props.TranslationStore.supportedLangs.length > 0) {
            languagesOptions = this.props.TranslationStore.supportedLangs.reduce((arr, current)  => {
                if (!this.props.TranslationStore.translations.find((t) => t === current))
                    arr.push({key: current, value: current, text: ISO6391.getNativeName(current)});
                return arr;
            }, []);
        }

        let btnMessage = ' ';
        if (this.state.action === 'languageselect')
            btnMessage = this.context.intl.formatMessage(messages.language)
        else if (this.state.action === 'translate')
            btnMessage = this.context.intl.formatMessage(messages.translate)

        return (
          <Modal trigger={
                  <Button tabIndex='-1' id="DeckTranslationsModalOpenButton" aria-hidden={this.state.modalOpen} basic onClick={this.handleOpen} style={{'display': 'none'}}/>
                 }
              open={this.state.modalOpen}
              onClose={this.handleClose}
              size="small"
              role="dialog"
              id="DeckTranslationsModal"
              aria-labelledby="DeckTranslationsModalHeader"
              aria-describedby="DeckTranslationsModalDescription"
              tabIndex="0">
              <FocusTrap
                  id='focus-trap-DeckTranslationsModal'
                  className = "header"
                  active={this.state.activeTrap}
                  focusTrapOptions={{
                      onDeactivate: this.unmountTrap,
                      clickOutsideDeactivates: false,
                      initialFocus: '#DeckTranslationsModalTranslationsDropdown',
                  }}>
                  <Modal.Header className="ui left aligned" as="h1" id="DeckTranslationsModalHeader">
                    {this.context.intl.formatMessage(messages.header)}
                  </Modal.Header>
                  <Modal.Content id="DeckTranslationsModalDescription">
                      <Divider />
                      {this.context.intl.formatMessage(message)} <Segment compact>{ISO6391.getNativeName(this.props.TranslationStore.treeLanguage)}</Segment>

                      {this.context.intl.formatMessage(messages.switch)}

                      <Dropdown
                          placeholder={this.context.intl.formatMessage(messages.chooseTranslation)}
                          fluid
                          scrolling
                          selection
                          search
                          options={translationOptions}
                          onChange={this.handleTranslationSelection.bind(this)}
                          id="DeckTranslationsModalTranslationsDropdown"
                          name='translationSelection'
                        />

                      {this.context.intl.formatMessage(messages.startTranslation)}

                      <Dropdown
                          placeholder={this.context.intl.formatMessage(messages.chooseLanguage)}
                          fluid
                          scrolling
                          selection
                          search
                          options={languagesOptions}
                          onChange={this.handleLanguageSelection.bind(this)}
                          name='languageSelection'
                        />
                      <Divider />
                      <Modal.Actions className="ui center aligned" as="div" style={{'textAlign': 'right'}}>
                        <Button id="DeckTranslationsModalActionButton" color="green" tabIndex="0" type="button" aria-label={btnMessage} onClick={this.handleActionClick.bind(this)} icon="save" labelPosition='left' content={btnMessage}/>
                        <Button color='red' tabIndex="0" type="button" aria-label={this.context.intl.formatMessage(messages.cancel)} onClick={this.handleClose} icon="minus circle" labelPosition='left' content={this.context.intl.formatMessage(messages.cancel)}/>
                      </Modal.Actions>
                  </Modal.Content>
              </FocusTrap>
          </Modal>
        );
    }
}

DeckTranslationsModal.contextTypes = {
    executeAction: React.PropTypes.func.isRequired,
    intl: React.PropTypes.object.isRequired
};
DeckTranslationsModal = connectToStores(DeckTranslationsModal, [TranslationStore], (context, props) => {
    return {
        TranslationStore: context.getStore(TranslationStore).getState()
    };
});

export default DeckTranslationsModal;
<commit_msg>[SWIK-1287_translation_ui_decks_and_slides] Added two missing semicolons<commit_after>import React from 'react';
import {connectToStores} from 'fluxible-addons-react';
import FocusTrap from 'focus-trap-react';
import { FormattedMessage, defineMessages } from 'react-intl';
import { Button, Modal, Divider, TextArea, Dropdown, Segment} from 'semantic-ui-react';
import TranslationStore from '../../../../stores/TranslationStore';
import ISO6391 from 'iso-639-1';
import {navigateAction} from 'fluxible-router';

class DeckTranslationsModal extends React.Component {

    constructor(props) {
        super(props);

        this.state = {
            openModal: false,
            activeTrap: false,
            action: '',
            languageCode: ''
        };

        this.handleOpen = this.handleOpen.bind(this);
        this.handleClose = this.handleClose.bind(this);
        this.unmountTrap = this.unmountTrap.bind(this);
    }

    handleOpen(){
        $('#app').attr('aria-hidden','true');
        this.setState({
            modalOpen:true,
            activeTrap:true
        });
    }

    handleClose(){
        $('#app').attr('aria-hidden','false');
        this.setState({
            modalOpen:false,
            activeTrap: false,
            action: '',
            languageCode: ''
        });
    }

    unmountTrap(){
        if(this.state.activeTrap){
            this.setState({ activeTrap: false });
            $('#app').attr('aria-hidden','false');
        }
    }

    handleTranslationSelection(e, data) {
        this.setState({ action: 'languageselect', languageCode: data.value });
    }

    handleLanguageSelection(e, data) {
        this.setState({ action: 'translate', languageCode: data.value });
    }

    handleActionClick(e) {
        if (this.state.action === 'languageselect') {
            this.handleClose();

            let path = location.pathname;
            let pathElements = path.split('/');
            let element = pathElements[2];
            let index = element.indexOf('_');
            if (index === -1) {
                pathElements[2] = element + '_' + this.state.languageCode;
            }
            else {
                pathElements[2] = element.substring(0, index) + '_' + this.state.languageCode;
            }
            path = pathElements.join('/');
            this.context.executeAction(navigateAction, {
                url: path
            });
        }
    }

    render() {
        const messages = defineMessages({
            header: {
                id: 'DeckTranslationsModal.header',
                defaultMessage: 'Translation selection',
            },
            currentLanguage: {
                id: 'DeckTranslationsModal.currentLanguage',
                defaultMessage: 'This deck has the following language:',
            },
            currentTranslation: {
                id: 'DeckTranslationsModal.currentTranslation',
                defaultMessage: 'This is a translation of the deck in the following language:',
            },
            switch: {
                id: 'DeckTranslationsModal.switch',
                defaultMessage: 'Select a translation to switch to it:',
            },
            chooseTranslation: {
                id: 'DeckTranslationsModal.chooseTranslation',
                defaultMessage: 'Choose a translation...',
            },
            chooseLanguage: {
                id: 'DeckTranslationsModal.chooseLanguage',
                defaultMessage: 'Choose the target language...',
            },
            startTranslation: {
                id: 'DeckTranslationsModal.startTranslation',
                defaultMessage: 'Or you can start a translation - just select the target language:',
            },
            cancel: {
                id: 'DeckTranslationsModal.cancel',
                defaultMessage: 'Cancel',
            },
            translate: {
                id: 'DeckTranslationsModal.translate',
                defaultMessage: 'Create translation',
            },
            language: {
                id: 'DeckTranslationsModal.language',
                defaultMessage: 'Switch to language',
            },
        });

        let message = this.props.TranslationStore.translations.find((t) => t === this.props.TranslationStore.treeLanguage) ? messages.currentTranslation : messages.currentLanguage;

        let translationOptions = [];
        if (this.props.TranslationStore.translations && this.props.TranslationStore.translations.length > 0) {
            translationOptions = this.props.TranslationStore.translations.reduce((arr, current)  => {
                arr.push({key: current, value: current, text: ISO6391.getNativeName(current)});
                return arr;
            }, []);
        }

        let languagesOptions = [];
        if (this.props.TranslationStore.supportedLangs && this.props.TranslationStore.supportedLangs.length > 0) {
            languagesOptions = this.props.TranslationStore.supportedLangs.reduce((arr, current)  => {
                if (!this.props.TranslationStore.translations.find((t) => t === current))
                    arr.push({key: current, value: current, text: ISO6391.getNativeName(current)});
                return arr;
            }, []);
        }

        let btnMessage = ' ';
        if (this.state.action === 'languageselect')
            btnMessage = this.context.intl.formatMessage(messages.language);
        else if (this.state.action === 'translate')
            btnMessage = this.context.intl.formatMessage(messages.translate);

        return (
          <Modal trigger={
                  <Button tabIndex='-1' id="DeckTranslationsModalOpenButton" aria-hidden={this.state.modalOpen} basic onClick={this.handleOpen} style={{'display': 'none'}}/>
                 }
              open={this.state.modalOpen}
              onClose={this.handleClose}
              size="small"
              role="dialog"
              id="DeckTranslationsModal"
              aria-labelledby="DeckTranslationsModalHeader"
              aria-describedby="DeckTranslationsModalDescription"
              tabIndex="0">
              <FocusTrap
                  id='focus-trap-DeckTranslationsModal'
                  className = "header"
                  active={this.state.activeTrap}
                  focusTrapOptions={{
                      onDeactivate: this.unmountTrap,
                      clickOutsideDeactivates: false,
                      initialFocus: '#DeckTranslationsModalTranslationsDropdown',
                  }}>
                  <Modal.Header className="ui left aligned" as="h1" id="DeckTranslationsModalHeader">
                    {this.context.intl.formatMessage(messages.header)}
                  </Modal.Header>
                  <Modal.Content id="DeckTranslationsModalDescription">
                      <Divider />
                      {this.context.intl.formatMessage(message)} <Segment compact>{ISO6391.getNativeName(this.props.TranslationStore.treeLanguage)}</Segment>

                      {this.context.intl.formatMessage(messages.switch)}

                      <Dropdown
                          placeholder={this.context.intl.formatMessage(messages.chooseTranslation)}
                          fluid
                          scrolling
                          selection
                          search
                          options={translationOptions}
                          onChange={this.handleTranslationSelection.bind(this)}
                          id="DeckTranslationsModalTranslationsDropdown"
                          name='translationSelection'
                        />

                      {this.context.intl.formatMessage(messages.startTranslation)}

                      <Dropdown
                          placeholder={this.context.intl.formatMessage(messages.chooseLanguage)}
                          fluid
                          scrolling
                          selection
                          search
                          options={languagesOptions}
                          onChange={this.handleLanguageSelection.bind(this)}
                          name='languageSelection'
                        />
                      <Divider />
                      <Modal.Actions className="ui center aligned" as="div" style={{'textAlign': 'right'}}>
                        <Button id="DeckTranslationsModalActionButton" color="green" tabIndex="0" type="button" aria-label={btnMessage} onClick={this.handleActionClick.bind(this)} icon="save" labelPosition='left' content={btnMessage}/>
                        <Button color='red' tabIndex="0" type="button" aria-label={this.context.intl.formatMessage(messages.cancel)} onClick={this.handleClose} icon="minus circle" labelPosition='left' content={this.context.intl.formatMessage(messages.cancel)}/>
                      </Modal.Actions>
                  </Modal.Content>
              </FocusTrap>
          </Modal>
        );
    }
}

DeckTranslationsModal.contextTypes = {
    executeAction: React.PropTypes.func.isRequired,
    intl: React.PropTypes.object.isRequired
};
DeckTranslationsModal = connectToStores(DeckTranslationsModal, [TranslationStore], (context, props) => {
    return {
        TranslationStore: context.getStore(TranslationStore).getState()
    };
});

export default DeckTranslationsModal;
<|endoftext|>


<commit_before>'use strict';

/**
 * Directive controller which is owned by the [gmMap]{@link module:gmMap}
 * directive and shared among all other angulargm directives.
 */
(function () {
  angular.module('AngularGM').

  controller('angulargmMapController',
    ['$scope', '$element', 'angulargmUtils', 'angulargmDefaults',
    'angulargmContainer',

    function ($scope, $element, angulargmUtils, angulargmDefaults,
      angulargmContainer) {

    /** aliases */
    var latLngEqual = angulargmUtils.latLngEqual;
    var boundsEqual = angulargmUtils.boundsEqual;
    var hasNaN = angulargmUtils.hasNaN;
    var gMDefaults = angulargmDefaults;
    var gMContainer = angulargmContainer;

    /*
     * Construct a new controller for the gmMap directive.
     * @param {angular.Scope} $scope
     * @param {angular.element} $element
     * @constructor
     */
    var constructor = function($scope, $element) {

      var mapId = $scope.gmMapId();
      if (!mapId) { throw 'angulargm must have non-empty gmMapId attribute'; }

      var mapDiv = angular.element($element[0].firstChild);
      mapDiv.attr('id', mapId);

      var config = this._getConfig($scope, gMDefaults);

      // 'private' properties
      this._map = this._createMap(mapId, mapDiv, config, gMContainer, $scope);
      this._markers = {};
      this._polylines = {};
      this._listeners = [];

      // 'public' properties
      this.dragging = false;

      Object.defineProperties(this, {
        'precision': {
          value: gMDefaults.precision,
          writeable: false
        },

        'center': {
          configurable: true, // for testing so we can mock
          get: function() {
             return this._map.getCenter();
           },
          set: function(center) {
            if (hasNaN(center))
              throw 'center contains null or NaN';
            var changed = !latLngEqual(this.center, center);
            if (changed) {
              this._map.panTo(center);
            }
          }
        },

        'zoom': {
          configurable: true, // for testing so we can mock
          get: function() {
            return this._map.getZoom();
          },
          set: function(zoom) {
            if (!(zoom != null && !isNaN(zoom)))
              throw 'zoom was null or NaN';
            var changed = this.zoom !== zoom;
            if (changed) {
              this._map.setZoom(zoom);
            }
          }
        },

        'bounds': {
          configurable: true, // for testing so we can mock
          get: function() {
            return this._map.getBounds();
          },
          set: function(bounds) {
            var numbers = !hasNaN(bounds.getSouthWest()) &&
                          !hasNaN(bounds.getNorthEast());
            if (!numbers)
              throw 'bounds contains null or NaN';

            var changed = !(boundsEqual(this.bounds, bounds));
            if (changed) {
              this._map.fitBounds(bounds);
            }
          }
        },

        'mapTypeId': {
          configurable: true, // for testing so we can mock
          get: function() {
            return this._map.getMapTypeId();
          },
          set: function(mapTypeId) {
            if (mapTypeId == null)
              throw 'mapTypeId was null or unknown';
            var changed = this.mapTypeId !== mapTypeId;
            if (changed) {
              this._map.setMapTypeId(mapTypeId);
            }
          }
        }
      });

      this._initDragListeners();
      $scope.$on('$destroy', angular.bind(this, this._destroy));
    };

    // Retrieve google.maps.MapOptions
    this._getConfig = function($scope, gMDefaults) {
      // Get config or defaults
      var defaults = gMDefaults.mapOptions;
      var config = {};
      angular.extend(config, defaults, $scope.gmMapOptions());
      return config;
    };

    // Create the map and add to angulargmContainer
    this._createMap = function(id, element, config, gMContainer) {
      var map = gMContainer.getMap(id);
      if (!map) {
        map = new google.maps.Map(element[0], config);
        gMContainer.addMap(id, map);
      } else {
        var div = map.getDiv();
        element.replaceWith(div);
        this._map = map;
        this.mapTrigger('resize');
        map.setOptions(config);
      }
      return map;
    };

    // Set up listeners to update this.dragging
    this._initDragListeners = function() {
      var self = this;
      this.addMapListener('dragstart', function () {
        self.dragging = true;
      });

      this.addMapListener('idle', function () {
        self.dragging = false;
      });

      this.addMapListener('drag', function() {
        self.dragging = true;
      });
    };

    this._destroy = function() {
      angular.forEach(this._listeners, function(listener) {
        google.maps.event.removeListener(listener);
      });

      var scopeIds = Object.keys(this._markers);
      var self = this;
      angular.forEach(scopeIds, function(scopeId) {
        self.forEachMarkerInScope(scopeId, function(marker, hash) {
          self.removeMarkerByHash(scopeId, hash);
        });
      });
    };

    /**
     * Alias for google.maps.event.addListener(map, event, handler)
     * @param {string} event an event defined on google.maps.Map
     * @param {Function} a handler for the event
     * @ignore
     */
    this.addMapListener = function(event, handler) {
      var listener = google.maps.event.addListener(this._map, event, handler);
      this._listeners.push(listener);
    };

    /**
     * Alias for google.maps.event.addListenerOnce(map, event, handler)
     * @param {string} event an event defined on google.maps.Map
     * @param {Function} a handler for the event
     * @ignore
     */
    this.addMapListenerOnce = function(event, handler) {
      google.maps.event.addListenerOnce(this._map,
          event, handler);
    };

    /**
     * Alias for google.maps.event.addListener(object, event, handler)
     * @ignore
     */
    this.addListener = function(object, event, handler) {
      google.maps.event.addListener(object, event, handler);
    };

    /**
     * Alias for google.maps.event.addListenerOnce(object, event, handler)
     * @ignore
     */
    this.addListenerOnce = function(object, event, handler) {
      google.maps.event.addListenerOnce(object, event, handler);
    };

    /**
     * Alias for google.maps.event.trigger(map, event)
     * @param {string} event an event defined on google.maps.Map
     * @ignore
     */
    this.mapTrigger = function(event) {
      google.maps.event.trigger(this._map, event);
    };

    /**
     * Alias for google.maps.event.trigger(object, event)
     * @ignore
     */
    this.trigger = function(object, event) {
      google.maps.event.trigger(object, event);
    };

    /**
     * Adds a new marker to the map.
     * @param {number} scope id
     * @param {google.maps.MarkerOptions} markerOptions
     * @return {boolean} true if a marker was added, false if there was already
     *   a marker at this position. 'at this position' means delta_lat and
     *   delta_lng are < 0.0005
     * @throw if markerOptions does not have all required options (i.e. position)
     * @ignore
     */
    this.addMarker = function(scopeId, markerOptions) {
      var opts = {};
      angular.extend(opts, markerOptions);

      if (!(opts.position instanceof google.maps.LatLng)) {
        throw 'markerOptions did not contain a position';
      }

      var marker = new angulargmDefaults.markerConstructor(opts);
      var position = marker.getPosition();
      if (this.hasMarker(scopeId, position.lat(), position.lng())) {
        return false;
      }

      var hash = position.toUrlValue(this.precision);
      if (this._markers[scopeId] == null) {
          this._markers[scopeId] = {};
      }
      this._markers[scopeId][hash] = marker;
      marker.setMap(this._map);
      return true;
    };

    /**
     * @param {number} scope id
     * @param {number} lat
     * @param {number} lng
     * @return {boolean} true if there is a marker with the given lat and lng
     * @ignore
     */
    this.hasMarker = function(scopeId, lat, lng) {
      return (this.getMarker(scopeId, lat, lng) instanceof google.maps.Marker);
    };

    /**
     * @param {number} scope id
     * @param {number} lat
     * @param {number} lng
     * @return {google.maps.Marker} the marker at given lat and lng, or null if
     *   no such marker exists
     * @ignore
     */
    this.getMarker = function (scopeId, lat, lng) {
      if (lat == null || lng == null)
        throw 'lat or lng was null';

      var latLng = new google.maps.LatLng(lat, lng);
      var hash = latLng.toUrlValue(this.precision);
      if (this._markers[scopeId] != null && hash in this._markers[scopeId]) {
        return this._markers[scopeId][hash];
      } else {
        return null;
      }
    };

    /**
     * @param {number} scope id
     * @param {number} lat
     * @param {number} lng
     * @return {boolean} true if a marker was removed, false if nothing
     *   happened
     * @ignore
     */
    this.removeMarker = function(scopeId, lat, lng) {
      if (lat == null || lng == null)
        throw 'lat or lng was null';

      var latLng = new google.maps.LatLng(lat, lng);

      var hash = latLng.toUrlValue(this.precision);
      return this.removeMarkerByHash(scopeId, hash);
    };

    /**
     *
     * @param {number} scope id
     * @param {string} hash
     * @returns {boolean} true if a marker was removed, false if nothing
     *   happened
     * @ignore
     */
    this.removeMarkerByHash = function(scopeId, hash) {
        var removed = false;
        var marker = this._markers[scopeId][hash];
        if (marker) {
            marker.setMap(null);
            removed = true;
        }
        this._markers[scopeId][hash] = null;
        delete this._markers[scopeId][hash];
        return removed;
    };

    /**
     * Applies a function to each marker.
     * @param {Function} fn will called with marker as first argument
     * @throw if fn is null or undefined
     * @ignore
     */
    this.forEachMarker = function(fn) {
      if (fn == null) { throw 'fn was null or undefined'; }
      var that = this;
      var allMarkers = Object.keys(this._markers).reduce(function(markers, key){
          angular.forEach(that._markers[key], function(marker){
              markers.push(marker);
          });
          return markers;
      }, []);
      angular.forEach(allMarkers, function(marker, hash) {
        if (marker != null) {
          fn(marker, hash);
        }
      });
    };

    /**
     * Applies a function to each marker.
     * @param {number} scope id
     * @param {Function} fn will called with marker as first argument
     * @throw if fn is null or undefined
     * @ignore
     */
    this.forEachMarkerInScope = function(scopeId, fn) {
        if (fn == null) { throw 'fn was null or undefined'; }
        angular.forEach(this._markers[scopeId], function(marker, hash) {
            if (marker != null) {
                fn(marker, hash);
            }
        });
    };

    this.addPolyline = function(scopeId, polylineOptions) {
      var opts = angular.extend({}, polylineOptions);

      if (!(opts.path) instanceof Array || opts.path.length < 2) {
        return;
      }

      angular.forEach(opts.path, function(point) {
        if (!(point instanceof google.maps.LatLng)) {
          throw 'An element in polylineOptions was found to not be a valid position';
        }
      });

      var hash = angulargmUtils.createHash(polylineOptions.path, this.precision);
      if (this.hasPolyline(scopeId, hash)) {
          return false;
      }

      var polyline = new angulargmDefaults.polylineConstructor(opts);
      if (null == this._polylines[scopeId]) {
        this._polylines[scopeId] = {};
      }
      this._polylines[scopeId][hash] = polyline;
      polyline.setMap(this._map);
      return true;
    };

    this.getPolyline = function (scopeId, hash) {
      if (null == hash || '' == hash) {
        throw 'no hash passed to lookup';
      }

      if (null != this._polylines[scopeId] && hash in this._polylines[scopeId]) {
        return this._polylines[scopeId][hash];
      } else {
        return null;
      }
    };

    this.hasPolyline = function (scopeId, hash) {
      return (this.getPolyline(scopeId, hash) instanceof Object);
    };

    this.forEachPolylineInScope = function(scopeId, fn) {
      if (null == fn) {
        throw 'fn was null or undefined';
      }

      angular.forEach(this._polylines[scopeId], function(polyline, hash) {
        if (null != polyline) {
          fn(polyline, hash);
        }
      });
    };

    this.forEachPolyline = function(fn) {
      if (null == fn) {
        throw 'fn was null or undefined';
      }

      angular.forEach(this._polylines, function(polylines, scopeId) {
        angular.forEach(polylines, function(polyline, hash) {
          if (null != polyline) {
            fn(polyline, hash);
          }
        });
      });
    };

    this.removePolylineByHash = function(scopeId, hash) {
      var removed = false;
      var polyline = this._polylines[scopeId][hash];
      if (polyline) {
        polyline.setMap(null);
        removed = true;
      }

      this._polylines[scopeId][hash] = null;
      delete this._polylines[scopeId][hash];
      return removed;
    };

    /**
     * Get current map.
     * @returns {object}
     * @ignore
     */
    this.getMap = function() {
        return this._map;
    };

    /** Instantiate controller */
    angular.bind(this, constructor)($scope, $element);

  }]);
})();

<commit_msg>Fixed jshint error<commit_after>'use strict';

/**
 * Directive controller which is owned by the [gmMap]{@link module:gmMap}
 * directive and shared among all other angulargm directives.
 */
(function () {
  angular.module('AngularGM').

  controller('angulargmMapController',
    ['$scope', '$element', 'angulargmUtils', 'angulargmDefaults',
    'angulargmContainer',

    function ($scope, $element, angulargmUtils, angulargmDefaults,
      angulargmContainer) {

    /** aliases */
    var latLngEqual = angulargmUtils.latLngEqual;
    var boundsEqual = angulargmUtils.boundsEqual;
    var hasNaN = angulargmUtils.hasNaN;
    var gMDefaults = angulargmDefaults;
    var gMContainer = angulargmContainer;

    /*
     * Construct a new controller for the gmMap directive.
     * @param {angular.Scope} $scope
     * @param {angular.element} $element
     * @constructor
     */
    var constructor = function($scope, $element) {

      var mapId = $scope.gmMapId();
      if (!mapId) { throw 'angulargm must have non-empty gmMapId attribute'; }

      var mapDiv = angular.element($element[0].firstChild);
      mapDiv.attr('id', mapId);

      var config = this._getConfig($scope, gMDefaults);

      // 'private' properties
      this._map = this._createMap(mapId, mapDiv, config, gMContainer, $scope);
      this._markers = {};
      this._polylines = {};
      this._listeners = [];

      // 'public' properties
      this.dragging = false;

      Object.defineProperties(this, {
        'precision': {
          value: gMDefaults.precision,
          writeable: false
        },

        'center': {
          configurable: true, // for testing so we can mock
          get: function() {
             return this._map.getCenter();
           },
          set: function(center) {
            if (hasNaN(center))
              throw 'center contains null or NaN';
            var changed = !latLngEqual(this.center, center);
            if (changed) {
              this._map.panTo(center);
            }
          }
        },

        'zoom': {
          configurable: true, // for testing so we can mock
          get: function() {
            return this._map.getZoom();
          },
          set: function(zoom) {
            if (!(zoom != null && !isNaN(zoom)))
              throw 'zoom was null or NaN';
            var changed = this.zoom !== zoom;
            if (changed) {
              this._map.setZoom(zoom);
            }
          }
        },

        'bounds': {
          configurable: true, // for testing so we can mock
          get: function() {
            return this._map.getBounds();
          },
          set: function(bounds) {
            var numbers = !hasNaN(bounds.getSouthWest()) &&
                          !hasNaN(bounds.getNorthEast());
            if (!numbers)
              throw 'bounds contains null or NaN';

            var changed = !(boundsEqual(this.bounds, bounds));
            if (changed) {
              this._map.fitBounds(bounds);
            }
          }
        },

        'mapTypeId': {
          configurable: true, // for testing so we can mock
          get: function() {
            return this._map.getMapTypeId();
          },
          set: function(mapTypeId) {
            if (mapTypeId == null)
              throw 'mapTypeId was null or unknown';
            var changed = this.mapTypeId !== mapTypeId;
            if (changed) {
              this._map.setMapTypeId(mapTypeId);
            }
          }
        }
      });

      this._initDragListeners();
      $scope.$on('$destroy', angular.bind(this, this._destroy));
    };

    // Retrieve google.maps.MapOptions
    this._getConfig = function($scope, gMDefaults) {
      // Get config or defaults
      var defaults = gMDefaults.mapOptions;
      var config = {};
      angular.extend(config, defaults, $scope.gmMapOptions());
      return config;
    };

    // Create the map and add to angulargmContainer
    this._createMap = function(id, element, config, gMContainer) {
      var map = gMContainer.getMap(id);
      if (!map) {
        map = new google.maps.Map(element[0], config);
        gMContainer.addMap(id, map);
      } else {
        var div = map.getDiv();
        element.replaceWith(div);
        this._map = map;
        this.mapTrigger('resize');
        map.setOptions(config);
      }
      return map;
    };

    // Set up listeners to update this.dragging
    this._initDragListeners = function() {
      var self = this;
      this.addMapListener('dragstart', function () {
        self.dragging = true;
      });

      this.addMapListener('idle', function () {
        self.dragging = false;
      });

      this.addMapListener('drag', function() {
        self.dragging = true;
      });
    };

    this._destroy = function() {
      angular.forEach(this._listeners, function(listener) {
        google.maps.event.removeListener(listener);
      });

      var scopeIds = Object.keys(this._markers);
      var self = this;
      angular.forEach(scopeIds, function(scopeId) {
        self.forEachMarkerInScope(scopeId, function(marker, hash) {
          self.removeMarkerByHash(scopeId, hash);
        });
      });
    };

    /**
     * Alias for google.maps.event.addListener(map, event, handler)
     * @param {string} event an event defined on google.maps.Map
     * @param {Function} a handler for the event
     * @ignore
     */
    this.addMapListener = function(event, handler) {
      var listener = google.maps.event.addListener(this._map, event, handler);
      this._listeners.push(listener);
    };

    /**
     * Alias for google.maps.event.addListenerOnce(map, event, handler)
     * @param {string} event an event defined on google.maps.Map
     * @param {Function} a handler for the event
     * @ignore
     */
    this.addMapListenerOnce = function(event, handler) {
      google.maps.event.addListenerOnce(this._map,
          event, handler);
    };

    /**
     * Alias for google.maps.event.addListener(object, event, handler)
     * @ignore
     */
    this.addListener = function(object, event, handler) {
      google.maps.event.addListener(object, event, handler);
    };

    /**
     * Alias for google.maps.event.addListenerOnce(object, event, handler)
     * @ignore
     */
    this.addListenerOnce = function(object, event, handler) {
      google.maps.event.addListenerOnce(object, event, handler);
    };

    /**
     * Alias for google.maps.event.trigger(map, event)
     * @param {string} event an event defined on google.maps.Map
     * @ignore
     */
    this.mapTrigger = function(event) {
      google.maps.event.trigger(this._map, event);
    };

    /**
     * Alias for google.maps.event.trigger(object, event)
     * @ignore
     */
    this.trigger = function(object, event) {
      google.maps.event.trigger(object, event);
    };

    /**
     * Adds a new marker to the map.
     * @param {number} scope id
     * @param {google.maps.MarkerOptions} markerOptions
     * @return {boolean} true if a marker was added, false if there was already
     *   a marker at this position. 'at this position' means delta_lat and
     *   delta_lng are < 0.0005
     * @throw if markerOptions does not have all required options (i.e. position)
     * @ignore
     */
    this.addMarker = function(scopeId, markerOptions) {
      var opts = {};
      angular.extend(opts, markerOptions);

      if (!(opts.position instanceof google.maps.LatLng)) {
        throw 'markerOptions did not contain a position';
      }

      var marker = new angulargmDefaults.markerConstructor(opts);
      var position = marker.getPosition();
      if (this.hasMarker(scopeId, position.lat(), position.lng())) {
        return false;
      }

      var hash = position.toUrlValue(this.precision);
      if (this._markers[scopeId] == null) {
          this._markers[scopeId] = {};
      }
      this._markers[scopeId][hash] = marker;
      marker.setMap(this._map);
      return true;
    };

    /**
     * @param {number} scope id
     * @param {number} lat
     * @param {number} lng
     * @return {boolean} true if there is a marker with the given lat and lng
     * @ignore
     */
    this.hasMarker = function(scopeId, lat, lng) {
      return (this.getMarker(scopeId, lat, lng) instanceof google.maps.Marker);
    };

    /**
     * @param {number} scope id
     * @param {number} lat
     * @param {number} lng
     * @return {google.maps.Marker} the marker at given lat and lng, or null if
     *   no such marker exists
     * @ignore
     */
    this.getMarker = function (scopeId, lat, lng) {
      if (lat == null || lng == null)
        throw 'lat or lng was null';

      var latLng = new google.maps.LatLng(lat, lng);
      var hash = latLng.toUrlValue(this.precision);
      if (this._markers[scopeId] != null && hash in this._markers[scopeId]) {
        return this._markers[scopeId][hash];
      } else {
        return null;
      }
    };

    /**
     * @param {number} scope id
     * @param {number} lat
     * @param {number} lng
     * @return {boolean} true if a marker was removed, false if nothing
     *   happened
     * @ignore
     */
    this.removeMarker = function(scopeId, lat, lng) {
      if (lat == null || lng == null)
        throw 'lat or lng was null';

      var latLng = new google.maps.LatLng(lat, lng);

      var hash = latLng.toUrlValue(this.precision);
      return this.removeMarkerByHash(scopeId, hash);
    };

    /**
     *
     * @param {number} scope id
     * @param {string} hash
     * @returns {boolean} true if a marker was removed, false if nothing
     *   happened
     * @ignore
     */
    this.removeMarkerByHash = function(scopeId, hash) {
        var removed = false;
        var marker = this._markers[scopeId][hash];
        if (marker) {
            marker.setMap(null);
            removed = true;
        }
        this._markers[scopeId][hash] = null;
        delete this._markers[scopeId][hash];
        return removed;
    };

    /**
     * Applies a function to each marker.
     * @param {Function} fn will called with marker as first argument
     * @throw if fn is null or undefined
     * @ignore
     */
    this.forEachMarker = function(fn) {
      if (fn == null) { throw 'fn was null or undefined'; }
      var that = this;
      var allMarkers = Object.keys(this._markers).reduce(function(markers, key){
          angular.forEach(that._markers[key], function(marker){
              markers.push(marker);
          });
          return markers;
      }, []);
      angular.forEach(allMarkers, function(marker, hash) {
        if (marker != null) {
          fn(marker, hash);
        }
      });
    };

    /**
     * Applies a function to each marker.
     * @param {number} scope id
     * @param {Function} fn will called with marker as first argument
     * @throw if fn is null or undefined
     * @ignore
     */
    this.forEachMarkerInScope = function(scopeId, fn) {
        if (fn == null) { throw 'fn was null or undefined'; }
        angular.forEach(this._markers[scopeId], function(marker, hash) {
            if (marker != null) {
                fn(marker, hash);
            }
        });
    };

    this.addPolyline = function(scopeId, polylineOptions) {
      var opts = angular.extend({}, polylineOptions);

      if (!(opts.path) instanceof Array || opts.path.length < 2) {
        return;
      }

      angular.forEach(opts.path, function(point) {
        if (!(point instanceof google.maps.LatLng)) {
          throw 'An element in polylineOptions was found to not be a valid position';
        }
      });

      var hash = angulargmUtils.createHash(polylineOptions.path, this.precision);
      if (this.hasPolyline(scopeId, hash)) {
          return false;
      }

      var polyline = new angulargmDefaults.polylineConstructor(opts);
      if (null == this._polylines[scopeId]) {
        this._polylines[scopeId] = {};
      }
      this._polylines[scopeId][hash] = polyline;
      polyline.setMap(this._map);
      return true;
    };

    this.getPolyline = function (scopeId, hash) {
      if (null == hash || '' === hash) {
        throw 'no hash passed to lookup';
      }

      if (null != this._polylines[scopeId] && hash in this._polylines[scopeId]) {
        return this._polylines[scopeId][hash];
      } else {
        return null;
      }
    };

    this.hasPolyline = function (scopeId, hash) {
      return (this.getPolyline(scopeId, hash) instanceof Object);
    };

    this.forEachPolylineInScope = function(scopeId, fn) {
      if (null == fn) {
        throw 'fn was null or undefined';
      }

      angular.forEach(this._polylines[scopeId], function(polyline, hash) {
        if (null != polyline) {
          fn(polyline, hash);
        }
      });
    };

    this.forEachPolyline = function(fn) {
      if (null == fn) {
        throw 'fn was null or undefined';
      }

      angular.forEach(this._polylines, function(polylines, scopeId) {
        angular.forEach(polylines, function(polyline, hash) {
          if (null != polyline) {
            fn(polyline, hash);
          }
        });
      });
    };

    this.removePolylineByHash = function(scopeId, hash) {
      var removed = false;
      var polyline = this._polylines[scopeId][hash];
      if (polyline) {
        polyline.setMap(null);
        removed = true;
      }

      this._polylines[scopeId][hash] = null;
      delete this._polylines[scopeId][hash];
      return removed;
    };

    /**
     * Get current map.
     * @returns {object}
     * @ignore
     */
    this.getMap = function() {
        return this._map;
    };

    /** Instantiate controller */
    angular.bind(this, constructor)($scope, $element);

  }]);
})();

<|endoftext|>


<commit_before>define(function (require) {
    "use strict";

    var _                 = brackets.getModule("thirdparty/lodash"),
        FileSystem        = brackets.getModule("filesystem/FileSystem");

    var EventEmitter      = require("src/EventEmitter"),
        Events            = require("src/Events"),
        Git               = require("src/git/Git"),
        Preferences       = require("src/Preferences"),
        Promise           = require("bluebird"),
        Utils             = require("src/Utils");

    var ignoreEntries = [],
        newPaths      = [],
        modifiedPaths = [];

    function refreshIgnoreEntries() {
        function regexEscape(str) {
            // NOTE: We cannot use StringUtils.regexEscape() here because we don't wanna replace *
            return str.replace(/([.?+\^$\[\]\\(){}|\-])/g, "\\$1");
        }

        return new Promise(function (resolve) {

            if (!Preferences.get("markModifiedInTree")) {
                return resolve();
            }

            var projectRoot = Utils.getProjectRoot();

            FileSystem.getFileForPath(projectRoot + ".gitignore").read(function (err, content) {
                if (err) {
                    ignoreEntries = [];
                    return resolve();
                }

                ignoreEntries = _.compact(_.map(content.split("\n"), function (line) {
                    // Rules: http://git-scm.com/docs/gitignore
                    var type = "deny",
                        leadingSlash,
                        trailingSlash,
                        regex;

                    line = line.trim();
                    if (!line || line.indexOf("#") === 0) {
                        return;
                    }

                    // handle explicitly allowed files/folders with a leading !
                    if (line.indexOf("!") === 0) {
                        line = line.slice(1);
                        type = "accept";
                    }
                    // handle lines beginning with a backslash, which is used for escaping ! or #
                    if (line.indexOf("\\") === 0) {
                        line = line.slice(1);
                    }
                    // handle lines beginning with a slash, which only matches files/folders in the root dir
                    if (line.indexOf("/") === 0) {
                        line = line.slice(1);
                        leadingSlash = true;
                    }
                    // handle lines ending with a slash, which only exludes dirs
                    if (line.lastIndexOf("/") === line.length) {
                        // a line ending with a slash ends with **
                        line += "**";
                        trailingSlash = true;
                    }

                    // NOTE: /(.{0,})/ is basically the same as /(.*)/, but we can't use it because the asterisk
                    // would be replaced later on

                    // create the intial regexp here. We need the absolute path 'cause it could be that there
                    // are external files with the same name as a project file
                    regex = regexEscape(projectRoot) + (leadingSlash ? "" : "((.+)/)?") + regexEscape(line) + (leadingSlash ? "" : "(/.{0,})?");
                    // replace all the possible asterisks
                    regex = regex.replace(/\*\*$/g, "(.{0,})").replace(/(\*\*|\*$)/g, "(.+)").replace(/\*/g, "([^/]+)");
                    regex = "^" + regex + "$";

                    return {
                        regexp: new RegExp(regex),
                        type: type
                    };
                }));

                return resolve();
            });
        });
    }

    function isIgnored(path) {
        var ignored = false;
        _.forEach(ignoreEntries, function (entry) {
            if (entry.regexp.test(path)) {
                ignored = (entry.type === "deny");
            }
        });
        return ignored;
    }

    function isNew(fullPath) {
        return newPaths.indexOf(fullPath) !== -1;
    }

    function isModified(fullPath) {
        return modifiedPaths.indexOf(fullPath) !== -1;
    }

    function _refreshProjectFiles(selector, dataEntry) {
        $(selector).find("li").each(function () {
            var $li = $(this),
                data = $li.data(dataEntry);
            if (data) {
                var fullPath = data.fullPath;
                $li.toggleClass("git-ignored", isIgnored(fullPath))
                   .toggleClass("git-new", isNew(fullPath))
                   .toggleClass("git-modified", isModified(fullPath));
            }
        });
    }

    var refreshOpenFiles = _.debounce(function () {
        _refreshProjectFiles("#open-files-container", "file");
    }, 100);

    var refreshProjectFiles = _.debounce(function () {
        _refreshProjectFiles("#project-files-container", "entry");
    }, 100);

    function refreshBoth() {
        refreshOpenFiles();
        refreshProjectFiles();
    }

    function attachEvents() {
        if (Preferences.get("markModifiedInTree")) {
            $("#open-files-container").on("contentChanged", refreshOpenFiles).triggerHandler("contentChanged");
            $("#project-files-container").on("contentChanged after_open.jstree", refreshProjectFiles).triggerHandler("contentChanged");
        }
    }

    function detachEvents() {
        $("#open-files-container").off("contentChanged", refreshOpenFiles);
        $("#project-files-container").off("contentChanged after_open.jstree", refreshProjectFiles);
    }

    // this will refresh ignore entries when .gitignore is modified
    EventEmitter.on(Events.BRACKETS_FILE_CHANGED, function (evt, file) {
        if (file.fullPath === Utils.getProjectRoot() + ".gitignore") {
            refreshIgnoreEntries().finally(function () {
                refreshBoth();
            });
        }
    });
    // this will refresh new/modified paths on every status results
    EventEmitter.on(Events.GIT_STATUS_RESULTS, function (files) {
        var projectRoot = Utils.getProjectRoot();

        newPaths = [];
        modifiedPaths = [];

        files.forEach(function (entry) {
            var isNew = entry.status.indexOf(Git.FILE_STATUS.UNTRACKED) !== -1 ||
                        entry.status.indexOf(Git.FILE_STATUS.ADDED) !== -1;

            var fullPath = projectRoot + entry.file;
            if (isNew) {
                newPaths.push(fullPath);
            } else {
                modifiedPaths.push(fullPath);
            }
        });

        refreshBoth();
    });
    // this will refresh ignore entries when git project is opened
    EventEmitter.on(Events.GIT_ENABLED, function () {
        refreshIgnoreEntries();
        attachEvents();
    });
    // this will clear entries when non-git project is opened
    EventEmitter.on(Events.GIT_DISABLED, function () {
        ignoreEntries = [];
        newPaths      = [];
        modifiedPaths = [];
        detachEvents();
    });

});
<commit_msg>Improve .gitignore parsing<commit_after>define(function (require) {
    "use strict";

    var _                 = brackets.getModule("thirdparty/lodash"),
        FileSystem        = brackets.getModule("filesystem/FileSystem");

    var EventEmitter      = require("src/EventEmitter"),
        Events            = require("src/Events"),
        Git               = require("src/git/Git"),
        Preferences       = require("src/Preferences"),
        Promise           = require("bluebird"),
        Utils             = require("src/Utils");

    var ignoreEntries = [],
        newPaths      = [],
        modifiedPaths = [];

    function refreshIgnoreEntries() {
        function regexEscape(str) {
            // NOTE: We cannot use StringUtils.regexEscape() here because we don't wanna replace *
            return str.replace(/([.?+\^$\[\]\\(){}|\-])/g, "\\$1");
        }

        return new Promise(function (resolve) {

            if (!Preferences.get("markModifiedInTree")) {
                return resolve();
            }

            var projectRoot = Utils.getProjectRoot();

            FileSystem.getFileForPath(projectRoot + ".gitignore").read(function (err, content) {
                if (err) {
                    ignoreEntries = [];
                    return resolve();
                }

                ignoreEntries = _.compact(_.map(content.split("\n"), function (line) {
                    // Rules: http://git-scm.com/docs/gitignore
                    var type = "deny",
                        leadingSlash,
                        trailingSlash,
                        regex;

                    line = line.trim();
                    if (!line || line.indexOf("#") === 0) {
                        return;
                    }

                    // handle explicitly allowed files/folders with a leading !
                    if (line.indexOf("!") === 0) {
                        line = line.slice(1);
                        type = "accept";
                    }
                    // handle lines beginning with a backslash, which is used for escaping ! or #
                    if (line.indexOf("\\") === 0) {
                        line = line.slice(1);
                    }
                    // handle lines beginning with a slash, which only matches files/folders in the root dir
                    if (line.indexOf("/") === 0) {
                        line = line.slice(1);
                        leadingSlash = true;
                    }
                    // handle lines ending with a slash, which only exludes dirs
                    if (line.lastIndexOf("/") === line.length) {
                        // a line ending with a slash ends with **
                        line += "**";
                        trailingSlash = true;
                    }

                    // NOTE: /(.{0,})/ is basically the same as /(.*)/, but we can't use it because the asterisk
                    // would be replaced later on

                    // create the intial regexp here. We need the absolute path 'cause it could be that there
                    // are external files with the same name as a project file
                    regex = regexEscape(projectRoot) + (leadingSlash ? "" : "((.+)/)?") + regexEscape(line) + (trailingSlash ? "" : "(/.{0,})?");
                    // replace all the possible asterisks
                    regex = regex.replace(/\*\*$/g, "(.{0,})").replace(/(\*\*|\*$)/g, "(.+)").replace(/\*/g, "([^/]+)");
                    regex = "^" + regex + "$";

                    return {
                        regexp: new RegExp(regex),
                        type: type
                    };
                }));

                return resolve();
            });
        });
    }

    function isIgnored(path) {
        var ignored = false;
        _.forEach(ignoreEntries, function (entry) {
            if (entry.regexp.test(path)) {
                ignored = (entry.type === "deny");
            }
        });
        return ignored;
    }

    function isNew(fullPath) {
        return newPaths.indexOf(fullPath) !== -1;
    }

    function isModified(fullPath) {
        return modifiedPaths.indexOf(fullPath) !== -1;
    }

    function _refreshProjectFiles(selector, dataEntry) {
        $(selector).find("li").each(function () {
            var $li = $(this),
                data = $li.data(dataEntry);
            if (data) {
                var fullPath = data.fullPath;
                $li.toggleClass("git-ignored", isIgnored(fullPath))
                   .toggleClass("git-new", isNew(fullPath))
                   .toggleClass("git-modified", isModified(fullPath));
            }
        });
    }

    var refreshOpenFiles = _.debounce(function () {
        _refreshProjectFiles("#open-files-container", "file");
    }, 100);

    var refreshProjectFiles = _.debounce(function () {
        _refreshProjectFiles("#project-files-container", "entry");
    }, 100);

    function refreshBoth() {
        refreshOpenFiles();
        refreshProjectFiles();
    }

    function attachEvents() {
        if (Preferences.get("markModifiedInTree")) {
            $("#open-files-container").on("contentChanged", refreshOpenFiles).triggerHandler("contentChanged");
            $("#project-files-container").on("contentChanged after_open.jstree", refreshProjectFiles).triggerHandler("contentChanged");
        }
    }

    function detachEvents() {
        $("#open-files-container").off("contentChanged", refreshOpenFiles);
        $("#project-files-container").off("contentChanged after_open.jstree", refreshProjectFiles);
    }

    // this will refresh ignore entries when .gitignore is modified
    EventEmitter.on(Events.BRACKETS_FILE_CHANGED, function (evt, file) {
        if (file.fullPath === Utils.getProjectRoot() + ".gitignore") {
            refreshIgnoreEntries().finally(function () {
                refreshBoth();
            });
        }
    });
    // this will refresh new/modified paths on every status results
    EventEmitter.on(Events.GIT_STATUS_RESULTS, function (files) {
        var projectRoot = Utils.getProjectRoot();

        newPaths = [];
        modifiedPaths = [];

        files.forEach(function (entry) {
            var isNew = entry.status.indexOf(Git.FILE_STATUS.UNTRACKED) !== -1 ||
                        entry.status.indexOf(Git.FILE_STATUS.ADDED) !== -1;

            var fullPath = projectRoot + entry.file;
            if (isNew) {
                newPaths.push(fullPath);
            } else {
                modifiedPaths.push(fullPath);
            }
        });

        refreshBoth();
    });
    // this will refresh ignore entries when git project is opened
    EventEmitter.on(Events.GIT_ENABLED, function () {
        refreshIgnoreEntries();
        attachEvents();
    });
    // this will clear entries when non-git project is opened
    EventEmitter.on(Events.GIT_DISABLED, function () {
        ignoreEntries = [];
        newPaths      = [];
        modifiedPaths = [];
        detachEvents();
    });

});
<|endoftext|>


<commit_before>/*
 * This is an internal configuration file.
 * If you want to change some configurable values, use the '/customize/application_config.js'
 * file (make a copy from /customize.dist/application_config.js)
 */
define(function() {
    var config = {};

    /* Select the buttons displayed on the main page to create new collaborative sessions
     * Existing types : pad, code, poll, slide
     */
    config.availablePadTypes = ['drive', 'pad', 'code', 'slide', 'poll', 'whiteboard', 'file', 'todo', 'contacts'];
    config.registeredOnlyTypes = ['file', 'contacts'];

    /*  Cryptpad apps use a common API to display notifications to users
     *  by default, notifications are hidden after 5 seconds
     *  You can change their duration here (measured in milliseconds)
     */
    config.notificationTimeout = 5000;
    config.disableUserlistNotifications = false;
    config.hideLoadingScreenTips = false;

    config.enablePinning = true;

    // Update the default colors available in the whiteboard application
    config.whiteboardPalette = [
        '#000000', // black
        '#FFFFFF', // white
        '#848484', // grey
        '#8B4513', // saddlebrown
        '#FF0000', // red
        '#FF8080', // peach?
        '#FF8000', // orange
        '#FFFF00', // yellow
        '#80FF80', // light green
        '#00FF00', // green
        '#00FFFF', // cyan
        '#008B8B', // dark cyan
        '#0000FF', // blue
        '#FF00FF', // fuschia
        '#FF00C0', // hot pink
        '#800080', // purple
    ];

    // Background color in the apps with centered content:
    // - file app in view mode
    // - rich text app when editor's width reduced in settings
    config.appBackgroundColor = '#666';

    // Set enableTemplates to false to remove the button allowing users to save a pad as a template
    // and remove the template category in CryptDrive
    config.enableTemplates = true;

    // Set enableHistory to false to remove the "History" button in all the apps.
    config.enableHistory = true;

    /*  user passwords are hashed with scrypt, and salted with their username.
        this value will be appended to the username, causing the resulting hash
        to differ from other CryptPad instances if customized. This makes it
        such that anyone who wants to bruteforce common credentials must do so
        again on each CryptPad instance that they wish to attack.

        WARNING: this should only be set when your CryptPad instance is first
        created. Changing it at a later time will break logins for all existing
        users.
    */
    config.loginSalt = '';
    config.minimumPasswordLength = 8;

    // Amount of time (ms) before aborting the session when the algorithm cannot synchronize the pad
    config.badStateTimeout = 30000;

    // Customize the icon used for each application.
    // You can update the colors by making a copy of /customize.dist/src/less2/include/colortheme.less
    config.applicationsIcon = {
        file: 'fa-file-text-o',
        pad: 'fa-file-word-o',
        code: 'fa-file-code-o',
        slide: 'fa-file-powerpoint-o',
        poll: 'fa-calendar',
        whiteboard: 'fa-paint-brush',
        todo: 'fa-tasks',
        contacts: 'fa-users',
    };

    // EXPERIMENTAL: Enabling "displayCreationScreen" may cause UI issues and possible loss of data
    config.displayCreationScreen = false;

    // Prevent anonymous users from storing pads in their drive
    config.disableAnonymousStore = false;

    // Hide the usage bar in settings and drive
    //config.hideUsageBar = true;

    // Disable feedback for all the users and hide the settings part about feedback
    //config.disableFeedback = true;

    // Add new options in the share modal (extend an existing tab or add a new tab).
    // More info about how to use it on the wiki:
    // https://github.com/xwiki-labs/cryptpad/wiki/Application-config#configcustomizeshareoptions
    //config.customizeShareOptions = function (hashes, tabs, config) {};

    // Add code to be executed on every page before loading the user object. `isLoggedIn` (bool) is
    // indicating if the user is registered or anonymous. Here you can change the way anonymous users
    // work in CryptPad, use an external SSO or even force registration
    // *NOTE*: You have to call the `callback` function to continue the loading process
    //config.beforeLogin = function(isLoggedIn, callback) {};

    // Add code to be executed on every page after the user object is loaded (also work for
    // unregistered users). This allows you to interact with your users' drive
    // *NOTE*: You have to call the `callback` function to continue the loading process
    //config.afterLogin = function(api, callback) {};

    // Disabling the profile app allows you to import the profile informations (display name, avatar)
    // from an external source and make sure the users can't change them from CryptPad.
    // You can use config.afterLogin to import these values in the users' drive.
    //config.disableProfile = true;

    return config;
});
<commit_msg>Enable pad creation screen<commit_after>/*
 * This is an internal configuration file.
 * If you want to change some configurable values, use the '/customize/application_config.js'
 * file (make a copy from /customize.dist/application_config.js)
 */
define(function() {
    var config = {};

    /* Select the buttons displayed on the main page to create new collaborative sessions
     * Existing types : pad, code, poll, slide
     */
    config.availablePadTypes = ['drive', 'pad', 'code', 'slide', 'poll', 'whiteboard', 'file', 'todo', 'contacts'];
    config.registeredOnlyTypes = ['file', 'contacts'];

    /*  Cryptpad apps use a common API to display notifications to users
     *  by default, notifications are hidden after 5 seconds
     *  You can change their duration here (measured in milliseconds)
     */
    config.notificationTimeout = 5000;
    config.disableUserlistNotifications = false;
    config.hideLoadingScreenTips = false;

    config.enablePinning = true;

    // Update the default colors available in the whiteboard application
    config.whiteboardPalette = [
        '#000000', // black
        '#FFFFFF', // white
        '#848484', // grey
        '#8B4513', // saddlebrown
        '#FF0000', // red
        '#FF8080', // peach?
        '#FF8000', // orange
        '#FFFF00', // yellow
        '#80FF80', // light green
        '#00FF00', // green
        '#00FFFF', // cyan
        '#008B8B', // dark cyan
        '#0000FF', // blue
        '#FF00FF', // fuschia
        '#FF00C0', // hot pink
        '#800080', // purple
    ];

    // Background color in the apps with centered content:
    // - file app in view mode
    // - rich text app when editor's width reduced in settings
    config.appBackgroundColor = '#666';

    // Set enableTemplates to false to remove the button allowing users to save a pad as a template
    // and remove the template category in CryptDrive
    config.enableTemplates = true;

    // Set enableHistory to false to remove the "History" button in all the apps.
    config.enableHistory = true;

    /*  user passwords are hashed with scrypt, and salted with their username.
        this value will be appended to the username, causing the resulting hash
        to differ from other CryptPad instances if customized. This makes it
        such that anyone who wants to bruteforce common credentials must do so
        again on each CryptPad instance that they wish to attack.

        WARNING: this should only be set when your CryptPad instance is first
        created. Changing it at a later time will break logins for all existing
        users.
    */
    config.loginSalt = '';
    config.minimumPasswordLength = 8;

    // Amount of time (ms) before aborting the session when the algorithm cannot synchronize the pad
    config.badStateTimeout = 30000;

    // Customize the icon used for each application.
    // You can update the colors by making a copy of /customize.dist/src/less2/include/colortheme.less
    config.applicationsIcon = {
        file: 'fa-file-text-o',
        pad: 'fa-file-word-o',
        code: 'fa-file-code-o',
        slide: 'fa-file-powerpoint-o',
        poll: 'fa-calendar',
        whiteboard: 'fa-paint-brush',
        todo: 'fa-tasks',
        contacts: 'fa-users',
    };

    // Ability to create owned pads and expiring pads through a new pad creation screen.
    // The new screen can be disabled by the users in their settings page
    config.displayCreationScreen = true;

    // Prevent anonymous users from storing pads in their drive
    config.disableAnonymousStore = false;

    // Hide the usage bar in settings and drive
    //config.hideUsageBar = true;

    // Disable feedback for all the users and hide the settings part about feedback
    //config.disableFeedback = true;

    // Add new options in the share modal (extend an existing tab or add a new tab).
    // More info about how to use it on the wiki:
    // https://github.com/xwiki-labs/cryptpad/wiki/Application-config#configcustomizeshareoptions
    //config.customizeShareOptions = function (hashes, tabs, config) {};

    // Add code to be executed on every page before loading the user object. `isLoggedIn` (bool) is
    // indicating if the user is registered or anonymous. Here you can change the way anonymous users
    // work in CryptPad, use an external SSO or even force registration
    // *NOTE*: You have to call the `callback` function to continue the loading process
    //config.beforeLogin = function(isLoggedIn, callback) {};

    // Add code to be executed on every page after the user object is loaded (also work for
    // unregistered users). This allows you to interact with your users' drive
    // *NOTE*: You have to call the `callback` function to continue the loading process
    //config.afterLogin = function(api, callback) {};

    // Disabling the profile app allows you to import the profile informations (display name, avatar)
    // from an external source and make sure the users can't change them from CryptPad.
    // You can use config.afterLogin to import these values in the users' drive.
    //config.disableProfile = true;

    return config;
});
<|endoftext|>


<commit_before>elation.elements.define('janus.ui.buttons', class extends elation.elements.base {
  create() {
    this.innerHTML = elation.template.get('janus.ui.buttons');
  }
});
elation.elements.define('janus-buttons-display', class extends elation.elements.ui.dropdownbutton {
  init() {
    super.init();

    var detect = {
      fullscreen: () => this.supportsFullscreen(),
      webvr: () => this.supportsWebVR(),
      chromecast: () => this.supportsChromecast(),
      equirectangular: () => this.supportsEquirectangular(),
      sbs3d: () => this.supportsSBS3D(),
      ou3d: () => this.supportsOU3D(),
      peppersghost: () => this.supportsPeppersGhost(),
    };

  }
  supportsFullscreen() {
  }
  supportsWebVR() {
  }
  supportsChromecast() {
  }
  supportsEquirectangular() {
  }
  supportsSBS3D() {
  }
  supportsOU3D() {
  }
  supportsPeppersGhost() {
  }
});

elation.elements.define('janus-button-fullscreen', class extends elation.elements.ui.togglebutton {
  create() {
    super.create();
    elation.events.add(document, 'fullscreenchange', (ev) => {
      if (document.fullscreen) {
        this.active = true;
      } else {
        this.active = false;
      }
    });
  }
  onactivate(ev) {
    this.client.fullscreen(true);
  }
  ondeactivate(ev) {
    this.client.fullscreen(false);
  }
});
elation.elements.define('janus-button-webvr', class extends elation.elements.ui.togglebutton {
  create() {
    this.xr = false;
    this.activelabel = 'WebVR';
    this.inactivelabel = 'WebVR';

    this.label = this.activelabel;
    super.create();
    this.view = janus.engine.client.view;

    if ('xr' in navigator) {
      this.activelabel = 'WebXR';
      this.inactivelabel = 'WebXR';
      this.label = this.inactivelabel;
      this.xr = true;
    } else if ('getVRDevices' in navigator) {
      window.addEventListener( 'vrdisplayconnect', (ev) => {
        this.show();
      }, false );

      window.addEventListener( 'vrdisplaydisconnect', (ev) => {
        this.hide();
      }, false );

      window.addEventListener( 'vrdisplaypresentchange', (ev) => {
        if (this.view.vrdisplay.isPresenting) {
        } else {
        }
      }, false );
    }
  }
  onactivate(ev) {
    this.label = this.activelabel;
    if (this.xr) {
      janus.engine.client.startXR();
    } else {
      this.view.toggleVR(true);
    }
  }
  ondeactivate(ev) {
    this.label = this.inactivelabel;
    if (this.xr) {
      this.view.stopXR();
    } else {
      this.view.toggleVR(false);
    }
  }
});
elation.elements.define('janus-button-webar', class extends elation.elements.ui.togglebutton {
  create() {
    this.xr = false;
    this.activelabel = 'Exit AR';
    this.inactivelabel = 'Enter AR';
    this.enabled = false;

    this.label = this.activelabel;
    super.create();
    this.view = janus.engine.client.view;

    if ('xr' in navigator) {
      this.label = this.inactivelabel;
      this.xr = true;
      navigator.xr.isSessionSupported('immersive-ar').then(supported => {
        this.enabled = supported;
      });
    }
  }
  onactivate(ev) {
    this.label = this.activelabel;
    if (this.xr) {
      janus.engine.client.startXR('immersive-ar');
    }
  }
  ondeactivate(ev) {
    this.label = this.inactivelabel;
    if (this.xr) {
      this.view.stopXR();
    }
  }
});
elation.elements.define('janus-buttons-chromecast', class extends elation.elements.ui.togglebutton {
});
elation.elements.define('janus-buttons-equirectangular', class extends elation.elements.ui.togglebutton {
});
elation.elements.define('janus-buttons-sbs3d', class extends elation.elements.ui.togglebutton {
});
elation.elements.define('janus-buttons-ou3d', class extends elation.elements.ui.togglebutton {
});
elation.elements.define('janus-buttons-peppersghost', class extends elation.elements.ui.togglebutton {
});
<commit_msg>WebXR button fixes<commit_after>elation.elements.define('janus.ui.buttons', class extends elation.elements.base {
  create() {
    this.innerHTML = elation.template.get('janus.ui.buttons');
  }
});
elation.elements.define('janus-buttons-display', class extends elation.elements.ui.dropdownbutton {
  init() {
    super.init();

    var detect = {
      fullscreen: () => this.supportsFullscreen(),
      webvr: () => this.supportsWebVR(),
      chromecast: () => this.supportsChromecast(),
      equirectangular: () => this.supportsEquirectangular(),
      sbs3d: () => this.supportsSBS3D(),
      ou3d: () => this.supportsOU3D(),
      peppersghost: () => this.supportsPeppersGhost(),
    };

  }
  supportsFullscreen() {
  }
  supportsWebVR() {
  }
  supportsChromecast() {
  }
  supportsEquirectangular() {
  }
  supportsSBS3D() {
  }
  supportsOU3D() {
  }
  supportsPeppersGhost() {
  }
});

elation.elements.define('janus-button-fullscreen', class extends elation.elements.ui.togglebutton {
  create() {
    super.create();
    elation.events.add(document, 'fullscreenchange', (ev) => {
      if (document.fullscreen) {
        this.active = true;
      } else {
        this.active = false;
      }
    });
  }
  onactivate(ev) {
    this.client.fullscreen(true);
  }
  ondeactivate(ev) {
    this.client.fullscreen(false);
  }
});
elation.elements.define('janus-button-webvr', class extends elation.elements.ui.togglebutton {
  create() {
    this.xr = false;
    this.activelabel = 'WebVR';
    this.inactivelabel = 'WebVR';

    this.label = this.activelabel;
    super.create();
    this.view = janus.engine.client.view;

    if ('xr' in navigator) {
      this.activelabel = 'Exit VR';
      this.inactivelabel = 'Enter VR';
      this.label = this.inactivelabel;
      this.xr = true;
    } else if ('getVRDevices' in navigator) {
      window.addEventListener( 'vrdisplayconnect', (ev) => {
        this.show();
      }, false );

      window.addEventListener( 'vrdisplaydisconnect', (ev) => {
        this.hide();
      }, false );

      window.addEventListener( 'vrdisplaypresentchange', (ev) => {
        if (this.view.vrdisplay.isPresenting) {
        } else {
        }
      }, false );
    }
  }
  onactivate(ev) {
    this.label = this.activelabel;
    if (this.xr) {
      janus.engine.client.startXR();
    } else {
      this.view.toggleVR(true);
    }
  }
  ondeactivate(ev) {
    this.label = this.inactivelabel;
    if (this.xr) {
      //this.view.stopXR();
      janus.engine.client.stopXR();
    } else {
      this.view.toggleVR(false);
    }
  }
});
elation.elements.define('janus-button-webar', class extends elation.elements.ui.togglebutton {
  create() {
    this.xr = false;
    this.activelabel = 'Exit AR';
    this.inactivelabel = 'Enter AR';
    this.enabled = false;

    this.label = this.activelabel;
    super.create();
    this.view = janus.engine.client.view;

    if ('xr' in navigator) {
      this.label = this.inactivelabel;
      this.xr = true;
      navigator.xr.isSessionSupported('immersive-ar').then(supported => {
        this.enabled = supported;
      });
    }
  }
  onactivate(ev) {
    this.label = this.activelabel;
    if (this.xr) {
      janus.engine.client.startXR('immersive-ar');
    }
  }
  ondeactivate(ev) {
    this.label = this.inactivelabel;
    if (this.xr) {
      this.view.stopXR();
    }
  }
});
elation.elements.define('janus-buttons-chromecast', class extends elation.elements.ui.togglebutton {
});
elation.elements.define('janus-buttons-equirectangular', class extends elation.elements.ui.togglebutton {
});
elation.elements.define('janus-buttons-sbs3d', class extends elation.elements.ui.togglebutton {
});
elation.elements.define('janus-buttons-ou3d', class extends elation.elements.ui.togglebutton {
});
elation.elements.define('janus-buttons-peppersghost', class extends elation.elements.ui.togglebutton {
});
<|endoftext|>