<commit_before>/**
 * Copyright 2015 Google Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
'use strict';

// Signs-in Friendly Chat.
function signIn() {
  // Sign in Firebase using popup auth and Google as the identity provider.
  var provider = new firebase.auth.GoogleAuthProvider();
  firebase.auth().signInWithPopup(provider);
}

// Signs-out of Friendly Chat.
function signOut() {
  // Sign out of Firebase.
  firebase.auth().signOut();
}

// Initiate firebase auth.
function initFirebaseAuth() {
  // Listen to auth state changes.
  firebase.auth().onAuthStateChanged(authStateObserver);
}

// Returns the signed-in user's profile Pic URL.
function getProfilePicUrl() {
  return firebase.auth().currentUser.photoURL || '/images/profile_placeholder.png';
}

// Returns the signed-in user's display name.
function getUserName() {
  return firebase.auth().currentUser.displayName;
}

// Returns true if a user is signed-in.
function isUserSignedIn() {
  return !!firebase.auth().currentUser;

// Saves a new message on the Cloud Firestore.
function saveMessage(messageText) {
  // Add a new message entry to the Firebase database.
  return firebase.firestore().collection('messages').add({
    name: getUserName(),
    text: messageText,
    profilePicUrl: getProfilePicUrl(),
    timestamp: firebase.firestore.FieldValue.serverTimestamp()
  }).catch(function(error) {
    console.error('Error writing new message to Firebase Database', error);
  });
}
}

// Loads chat messages history and listens for upcoming ones.
function loadMessages() {
  // Create the query to load the last 12 messages and listen for new ones.
  var query = firebase.firestore().collection('messages').orderBy('timestamp', 'desc').limit(12);

  // Start listening to the query.
  query.onSnapshot(function(snapshot) {
    snapshot.docChanges().forEach(function(change) {
      if (change.type === 'removed') {
        deleteMessage(change.doc.id);
      } else {
        var message = change.doc.data();
        displayMessage(change.doc.id, message.timestamp, message.name,
                      message.text, message.profilePicUrl, message.imageUrl);
      }
    });
  });
}

// Saves a new message containing an image in Firebase.
// This first saves the image in Firebase storage.
function saveImageMessage(file) {
  // 1 - We add a message with a loading icon that will get updated with the shared image.
  firebase.firestore().collection('messages').add({
    name: getUserName(),
    imageUrl: LOADING_IMAGE_URL,
    profilePicUrl: getProfilePicUrl(),
    timestamp: firebase.firestore.FieldValue.serverTimestamp()
  }).then(function(messageRef) {
    // 2 - Upload the image to Cloud Storage.
    var filePath = firebase.auth().currentUser.uid + '/' + messageRef.id + '/' + file.name;
    return firebase.storage().ref(filePath).put(file).then(function(fileSnapshot) {
      // 3 - Generate a public URL for the file.
      return fileSnapshot.ref.getDownloadURL().then((url) => {
        // 4 - Update the chat message placeholder with the image’s URL.
        return messageRef.update({
          imageUrl: url,
          storageUri: fileSnapshot.metadata.fullPath
        });
      });
    });
  }).catch(function(error) {
    console.error('There was an error uploading a file to Cloud Storage:', error);
  });
}

// Saves the messaging device token to the datastore.
function saveMessagingDeviceToken() {
  firebase.messaging().getToken().then(function(currentToken) {
    if (currentToken) {
      console.log('Got FCM device token:', currentToken);
      // Saving the Device Token to the datastore.
      firebase.firestore().collection('fcmTokens').doc(currentToken)
          .set({uid: firebase.auth().currentUser.uid});
    } else {
      // Need to request permissions to show notifications.
      requestNotificationsPermissions();
    }
  }).catch(function(error){
    console.error('Unable to get messaging token.', error);
  });
}

// Requests permissions to show notifications.
function requestNotificationsPermissions() {
  console.log('Requesting notifications permission...');
  firebase.messaging().requestPermission().then(function() {
    // Notification permission granted.
    saveMessagingDeviceToken();
  }).catch(function(error) {
    console.error('Unable to get permission to notify.', error);
  });
}

// Triggered when a file is selected via the media picker.
function onMediaFileSelected(event) {
  event.preventDefault();
  var file = event.target.files[0];

  // Clear the selection in the file picker input.
  imageFormElement.reset();

  // Check if the file is an image.
  if (!file.type.match('image.*')) {
    var data = {
      message: 'You can only share images',
      timeout: 2000
    };
    signInSnackbarElement.MaterialSnackbar.showSnackbar(data);
    return;
  }
  // Check if the user is signed-in
  if (checkSignedInWithMessage()) {
    saveImageMessage(file);
  }
}

// Triggered when the send new message form is submitted.
function onMessageFormSubmit(e) {
  e.preventDefault();
  // Check that the user entered a message and is signed in.
  if (messageInputElement.value && checkSignedInWithMessage()) {
    saveMessage(messageInputElement.value).then(function() {
      // Clear message text field and re-enable the SEND button.
      resetMaterialTextfield(messageInputElement);
      toggleButton();
    });
  }
}

// Triggers when the auth state change for instance when the user signs-in or signs-out.
function authStateObserver(user) {
  if (user) { // User is signed in!
    // Get the signed-in user's profile pic and name.
    var profilePicUrl = getProfilePicUrl();
    var userName = getUserName();

    // Set the user's profile pic and name.
    userPicElement.style.backgroundImage = 'url(' + addSizeToGoogleProfilePic(profilePicUrl) + ')';
    userNameElement.textContent = userName;

    // Show user's profile and sign-out button.
    userNameElement.removeAttribute('hidden');
    userPicElement.removeAttribute('hidden');
    signOutButtonElement.removeAttribute('hidden');

    // Hide sign-in button.
    signInButtonElement.setAttribute('hidden', 'true');

    // We save the Firebase Messaging Device token and enable notifications.
    saveMessagingDeviceToken();
  } else { // User is signed out!
    // Hide user's profile and sign-out button.
    userNameElement.setAttribute('hidden', 'true');
    userPicElement.setAttribute('hidden', 'true');
    signOutButtonElement.setAttribute('hidden', 'true');

    // Show sign-in button.
    signInButtonElement.removeAttribute('hidden');
  }
}

// Returns true if user is signed-in. Otherwise false and displays a message.
function checkSignedInWithMessage() {
  // Return true if the user is signed in Firebase
  if (isUserSignedIn()) {
    return true;
  }

  // Display a message to the user using a Toast.
  var data = {
    message: 'You must sign-in first',
    timeout: 2000
  };
  signInSnackbarElement.MaterialSnackbar.showSnackbar(data);
  return false;
}

// Resets the given MaterialTextField.
function resetMaterialTextfield(element) {
  element.value = '';
  element.parentNode.MaterialTextfield.boundUpdateClassesHandler();
}

// Template for messages.
var MESSAGE_TEMPLATE =
    '
' +
      '

' +
      '

' +
      '

' +
    '
';

// Adds a size to Google Profile pics URLs.
function addSizeToGoogleProfilePic(url) {
  if (url.indexOf('googleusercontent.com') !== -1 && url.indexOf('?') === -1) {
    return url + '?sz=150';
  }
  return url;
}

// A loading image URL.
var LOADING_IMAGE_URL = 'https://www.google.com/images/spin-32.gif?a';

// Delete a Message from the UI.
function deleteMessage(id) {
  var div = document.getElementById(id);
  // If an element for that message exists we delete it.
  if (div) {
    div.parentNode.removeChild(div);
  }
}

// Displays a Message in the UI.
function displayMessage(id, timestamp, name, text, picUrl, imageUrl) {
  var div = document.getElementById(id);
  // If an element for that message does not exists yet we create it.
  if (!div) {
    var container = document.createElement('div');
    container.innerHTML = MESSAGE_TEMPLATE;
    div = container.firstChild;
    div.setAttribute('id', id);
    div.setAttribute('timestamp', timestamp);
    for (var i = 0; i < messageListElement.children.length; i++) {
      var child = messageListElement.children[i];
      var time = child.getAttribute('timestamp');
      if (time && time > timestamp) {
        break;
      }
    }
    messageListElement.insertBefore(div, child);
  }
  if (picUrl) {
    div.querySelector('.pic').style.backgroundImage = 'url(' + addSizeToGoogleProfilePic(picUrl) + ')';
  }
  div.querySelector('.name').textContent = name;
  var messageElement = div.querySelector('.message');
  if (text) { // If the message is text.
    messageElement.textContent = text;
    // Replace all line breaks by
.
    messageElement.innerHTML = messageElement.innerHTML.replace(/\n/g, '
');
  } else if (imageUrl) { // If the message is an image.
    var image = document.createElement('img');
    image.addEventListener('load', function() {
      messageListElement.scrollTop = messageListElement.scrollHeight;
    });
    image.src = imageUrl + '&' + new Date().getTime();
    messageElement.innerHTML = '';
    messageElement.appendChild(image);
  }
  // Show the card fading-in and scroll to view the new message.
  setTimeout(function() {div.classList.add('visible')}, 1);
  messageListElement.scrollTop = messageListElement.scrollHeight;
  messageInputElement.focus();
}

// Enables or disables the submit button depending on the values of the input
// fields.
function toggleButton() {
  if (messageInputElement.value) {
    submitButtonElement.removeAttribute('disabled');
  } else {
    submitButtonElement.setAttribute('disabled', 'true');
  }
}

// Checks that the Firebase SDK has been correctly setup and configured.
function checkSetup() {
  if (!window.firebase || !(firebase.app instanceof Function) || !firebase.app().options) {
    window.alert('You have not configured and imported the Firebase SDK. ' +
        'Make sure you go through the codelab setup instructions and make ' +
        'sure you are running the codelab using `firebase serve`');
  }
}

// Checks that Firebase has been imported.
checkSetup();

// Shortcuts to DOM Elements.
var messageListElement = document.getElementById('messages');
var messageFormElement = document.getElementById('message-form');
var messageInputElement = document.getElementById('message');
var submitButtonElement = document.getElementById('submit');
var imageButtonElement = document.getElementById('submitImage');
var imageFormElement = document.getElementById('image-form');
var mediaCaptureElement = document.getElementById('mediaCapture');
var userPicElement = document.getElementById('user-pic');
var userNameElement = document.getElementById('user-name');
var signInButtonElement = document.getElementById('sign-in');
var signOutButtonElement = document.getElementById('sign-out');
var signInSnackbarElement = document.getElementById('must-signin-snackbar');

// Saves message on form submit.
messageFormElement.addEventListener('submit', onMessageFormSubmit);
signOutButtonElement.addEventListener('click', signOut);
signInButtonElement.addEventListener('click', signIn);

// Toggle for the button.
messageInputElement.addEventListener('keyup', toggleButton);
messageInputElement.addEventListener('change', toggleButton);

// Events for image upload.
imageButtonElement.addEventListener('click', function(e) {
  e.preventDefault();
  mediaCaptureElement.click();
});
mediaCaptureElement.addEventListener('change', onMediaFileSelected);

// initialize Firebase
initFirebaseAuth();

// Remove the warning about timstamps change.
var firestore = firebase.firestore();
var settings = {timestampsInSnapshots: true};
firestore.settings(settings);

// We load currently existing chat messages and listen to new ones.
loadMessages();
<commit_msg>Fixed wrong brackets<commit_after>/**
 * Copyright 2015 Google Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
'use strict';

// Signs-in Friendly Chat.
function signIn() {
  // Sign in Firebase using popup auth and Google as the identity provider.
  var provider = new firebase.auth.GoogleAuthProvider();
  firebase.auth().signInWithPopup(provider);
}

// Signs-out of Friendly Chat.
function signOut() {
  // Sign out of Firebase.
  firebase.auth().signOut();
}

// Initiate firebase auth.
function initFirebaseAuth() {
  // Listen to auth state changes.
  firebase.auth().onAuthStateChanged(authStateObserver);
}

// Returns the signed-in user's profile Pic URL.
function getProfilePicUrl() {
  return firebase.auth().currentUser.photoURL || '/images/profile_placeholder.png';
}

// Returns the signed-in user's display name.
function getUserName() {
  return firebase.auth().currentUser.displayName;
}

// Returns true if a user is signed-in.
function isUserSignedIn() {
  return !!firebase.auth().currentUser;
}

// Saves a new message on the Cloud Firestore.
function saveMessage(messageText) {
  // Add a new message entry to the Firebase database.
  return firebase.firestore().collection('messages').add({
    name: getUserName(),
    text: messageText,
    profilePicUrl: getProfilePicUrl(),
    timestamp: firebase.firestore.FieldValue.serverTimestamp()
  }).catch(function(error) {
    console.error('Error writing new message to Firebase Database', error);
  });
}

// Loads chat messages history and listens for upcoming ones.
function loadMessages() {
  // Create the query to load the last 12 messages and listen for new ones.
  var query = firebase.firestore().collection('messages').orderBy('timestamp', 'desc').limit(12);

  // Start listening to the query.
  query.onSnapshot(function(snapshot) {
    snapshot.docChanges().forEach(function(change) {
      if (change.type === 'removed') {
        deleteMessage(change.doc.id);
      } else {
        var message = change.doc.data();
        displayMessage(change.doc.id, message.timestamp, message.name,
                      message.text, message.profilePicUrl, message.imageUrl);
      }
    });
  });
}

// Saves a new message containing an image in Firebase.
// This first saves the image in Firebase storage.
function saveImageMessage(file) {
  // 1 - We add a message with a loading icon that will get updated with the shared image.
  firebase.firestore().collection('messages').add({
    name: getUserName(),
    imageUrl: LOADING_IMAGE_URL,
    profilePicUrl: getProfilePicUrl(),
    timestamp: firebase.firestore.FieldValue.serverTimestamp()
  }).then(function(messageRef) {
    // 2 - Upload the image to Cloud Storage.
    var filePath = firebase.auth().currentUser.uid + '/' + messageRef.id + '/' + file.name;
    return firebase.storage().ref(filePath).put(file).then(function(fileSnapshot) {
      // 3 - Generate a public URL for the file.
      return fileSnapshot.ref.getDownloadURL().then((url) => {
        // 4 - Update the chat message placeholder with the image’s URL.
        return messageRef.update({
          imageUrl: url,
          storageUri: fileSnapshot.metadata.fullPath
        });
      });
    });
  }).catch(function(error) {
    console.error('There was an error uploading a file to Cloud Storage:', error);
  });
}

// Saves the messaging device token to the datastore.
function saveMessagingDeviceToken() {
  firebase.messaging().getToken().then(function(currentToken) {
    if (currentToken) {
      console.log('Got FCM device token:', currentToken);
      // Saving the Device Token to the datastore.
      firebase.firestore().collection('fcmTokens').doc(currentToken)
          .set({uid: firebase.auth().currentUser.uid});
    } else {
      // Need to request permissions to show notifications.
      requestNotificationsPermissions();
    }
  }).catch(function(error){
    console.error('Unable to get messaging token.', error);
  });
}

// Requests permissions to show notifications.
function requestNotificationsPermissions() {
  console.log('Requesting notifications permission...');
  firebase.messaging().requestPermission().then(function() {
    // Notification permission granted.
    saveMessagingDeviceToken();
  }).catch(function(error) {
    console.error('Unable to get permission to notify.', error);
  });
}

// Triggered when a file is selected via the media picker.
function onMediaFileSelected(event) {
  event.preventDefault();
  var file = event.target.files[0];

  // Clear the selection in the file picker input.
  imageFormElement.reset();

  // Check if the file is an image.
  if (!file.type.match('image.*')) {
    var data = {
      message: 'You can only share images',
      timeout: 2000
    };
    signInSnackbarElement.MaterialSnackbar.showSnackbar(data);
    return;
  }
  // Check if the user is signed-in
  if (checkSignedInWithMessage()) {
    saveImageMessage(file);
  }
}

// Triggered when the send new message form is submitted.
function onMessageFormSubmit(e) {
  e.preventDefault();
  // Check that the user entered a message and is signed in.
  if (messageInputElement.value && checkSignedInWithMessage()) {
    saveMessage(messageInputElement.value).then(function() {
      // Clear message text field and re-enable the SEND button.
      resetMaterialTextfield(messageInputElement);
      toggleButton();
    });
  }
}

// Triggers when the auth state change for instance when the user signs-in or signs-out.
function authStateObserver(user) {
  if (user) { // User is signed in!
    // Get the signed-in user's profile pic and name.
    var profilePicUrl = getProfilePicUrl();
    var userName = getUserName();

    // Set the user's profile pic and name.
    userPicElement.style.backgroundImage = 'url(' + addSizeToGoogleProfilePic(profilePicUrl) + ')';
    userNameElement.textContent = userName;

    // Show user's profile and sign-out button.
    userNameElement.removeAttribute('hidden');
    userPicElement.removeAttribute('hidden');
    signOutButtonElement.removeAttribute('hidden');

    // Hide sign-in button.
    signInButtonElement.setAttribute('hidden', 'true');

    // We save the Firebase Messaging Device token and enable notifications.
    saveMessagingDeviceToken();
  } else { // User is signed out!
    // Hide user's profile and sign-out button.
    userNameElement.setAttribute('hidden', 'true');
    userPicElement.setAttribute('hidden', 'true');
    signOutButtonElement.setAttribute('hidden', 'true');

    // Show sign-in button.
    signInButtonElement.removeAttribute('hidden');
  }
}

// Returns true if user is signed-in. Otherwise false and displays a message.
function checkSignedInWithMessage() {
  // Return true if the user is signed in Firebase
  if (isUserSignedIn()) {
    return true;
  }

  // Display a message to the user using a Toast.
  var data = {
    message: 'You must sign-in first',
    timeout: 2000
  };
  signInSnackbarElement.MaterialSnackbar.showSnackbar(data);
  return false;
}

// Resets the given MaterialTextField.
function resetMaterialTextfield(element) {
  element.value = '';
  element.parentNode.MaterialTextfield.boundUpdateClassesHandler();
}

// Template for messages.
var MESSAGE_TEMPLATE =
    '
' +
      '

' +
      '

' +
      '

' +
    '
';

// Adds a size to Google Profile pics URLs.
function addSizeToGoogleProfilePic(url) {
  if (url.indexOf('googleusercontent.com') !== -1 && url.indexOf('?') === -1) {
    return url + '?sz=150';
  }
  return url;
}

// A loading image URL.
var LOADING_IMAGE_URL = 'https://www.google.com/images/spin-32.gif?a';

// Delete a Message from the UI.
function deleteMessage(id) {
  var div = document.getElementById(id);
  // If an element for that message exists we delete it.
  if (div) {
    div.parentNode.removeChild(div);
  }
}

// Displays a Message in the UI.
function displayMessage(id, timestamp, name, text, picUrl, imageUrl) {
  var div = document.getElementById(id);
  // If an element for that message does not exists yet we create it.
  if (!div) {
    var container = document.createElement('div');
    container.innerHTML = MESSAGE_TEMPLATE;
    div = container.firstChild;
    div.setAttribute('id', id);
    div.setAttribute('timestamp', timestamp);
    for (var i = 0; i < messageListElement.children.length; i++) {
      var child = messageListElement.children[i];
      var time = child.getAttribute('timestamp');
      if (time && time > timestamp) {
        break;
      }
    }
    messageListElement.insertBefore(div, child);
  }
  if (picUrl) {
    div.querySelector('.pic').style.backgroundImage = 'url(' + addSizeToGoogleProfilePic(picUrl) + ')';
  }
  div.querySelector('.name').textContent = name;
  var messageElement = div.querySelector('.message');
  if (text) { // If the message is text.
    messageElement.textContent = text;
    // Replace all line breaks by
.
    messageElement.innerHTML = messageElement.innerHTML.replace(/\n/g, '
');
  } else if (imageUrl) { // If the message is an image.
    var image = document.createElement('img');
    image.addEventListener('load', function() {
      messageListElement.scrollTop = messageListElement.scrollHeight;
    });
    image.src = imageUrl + '&' + new Date().getTime();
    messageElement.innerHTML = '';
    messageElement.appendChild(image);
  }
  // Show the card fading-in and scroll to view the new message.
  setTimeout(function() {div.classList.add('visible')}, 1);
  messageListElement.scrollTop = messageListElement.scrollHeight;
  messageInputElement.focus();
}

// Enables or disables the submit button depending on the values of the input
// fields.
function toggleButton() {
  if (messageInputElement.value) {
    submitButtonElement.removeAttribute('disabled');
  } else {
    submitButtonElement.setAttribute('disabled', 'true');
  }
}

// Checks that the Firebase SDK has been correctly setup and configured.
function checkSetup() {
  if (!window.firebase || !(firebase.app instanceof Function) || !firebase.app().options) {
    window.alert('You have not configured and imported the Firebase SDK. ' +
        'Make sure you go through the codelab setup instructions and make ' +
        'sure you are running the codelab using `firebase serve`');
  }
}

// Checks that Firebase has been imported.
checkSetup();

// Shortcuts to DOM Elements.
var messageListElement = document.getElementById('messages');
var messageFormElement = document.getElementById('message-form');
var messageInputElement = document.getElementById('message');
var submitButtonElement = document.getElementById('submit');
var imageButtonElement = document.getElementById('submitImage');
var imageFormElement = document.getElementById('image-form');
var mediaCaptureElement = document.getElementById('mediaCapture');
var userPicElement = document.getElementById('user-pic');
var userNameElement = document.getElementById('user-name');
var signInButtonElement = document.getElementById('sign-in');
var signOutButtonElement = document.getElementById('sign-out');
var signInSnackbarElement = document.getElementById('must-signin-snackbar');

// Saves message on form submit.
messageFormElement.addEventListener('submit', onMessageFormSubmit);
signOutButtonElement.addEventListener('click', signOut);
signInButtonElement.addEventListener('click', signIn);

// Toggle for the button.
messageInputElement.addEventListener('keyup', toggleButton);
messageInputElement.addEventListener('change', toggleButton);

// Events for image upload.
imageButtonElement.addEventListener('click', function(e) {
  e.preventDefault();
  mediaCaptureElement.click();
});
mediaCaptureElement.addEventListener('change', onMediaFileSelected);

// initialize Firebase
initFirebaseAuth();

// Remove the warning about timstamps change.
var firestore = firebase.firestore();
var settings = {timestampsInSnapshots: true};
firestore.settings(settings);

// We load currently existing chat messages and listen to new ones.
loadMessages();
<|endoftext|>


<commit_before>/**
 * Copyright 2020 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * Create a map using the Maps API, and insert it into the page.
 */
const loadMap =
    () => {
      const onSuccess = (location) => {
        const lat = location.coords.latitude;
        const lng = location.coords.longitude;
        loadMapOnCoords(new google.maps.LatLng(lat, lng));
      };

      const onError = () => {
        loadMapOnCoords(new google.maps.LatLng(0, 0));
      };

      const options = {
        /* Try to get a high accuracy location result. */
        enableHighAccuracy: true,
        /* Wait no longer than 1.5s for a location result. */
        timeout: 1500  // ms
      };

      /* If the geolocation api is available, use it. Otherwise, center the map
       * on {0, 0}. */
      if ('geolocation' in navigator) {
        navigator.geolocation.getCurrentPosition(onSuccess, onError, options);
      } else {
        onError();
      }
    }

/**
 * Load a Map, centered on the provided location.
 * @param {google.maps.LatLng} center The center of the Map.
 */
const loadMapOnCoords = (center) => {
  /* Zoom level 17 corresponds to a 'local street' zoom, as per Maps API
   * documentation. */
  const zoom = 17;

  const mapContainer = document.getElementById('map');

  const options = {
    center: center,
    zoom: zoom,
    streetViewControl: false,
    mapTypeControl: false
  };

  /* Create a map, and insert it into the map container. */
  const map = new google.maps.Map(mapContainer, options);

  /* Once the map has been created, load a Marker in the center of the map. */
  loadMapMarker(map, center);
};

/**
 * Create a Marker, and attach it to the provided Map at the provided position.
 * @param {google.maps.Map} map The map to attach the marker onto.
 * @param {google.maps.LatLng} position The position on the map to attach the Marker onto.
 */
const loadMapMarker = (map, position) => {
  const marker = new google.maps.Marker({
    position: position,
    map: map,
    title: 'Isolation Location',
  });

  /* Set the marker to be draggable to allow user interaction. */
  marker.setDraggable(true);

  /**
   * Geocode a location, and insert its human-readable address into the map's
   * search box.
   *
   * @param {google.maps.LatLng} latLng The location to geocode.
   */
  const geocode = (latLng) => {
    const textinput = document.getElementById('address-textinput');
    const geocoder = new google.maps.Geocoder();

    /* Attempt to geocode the current location, to obtain a human-readable
     * address. */
    geocoder.geocode({location: latLng}, (results, status) => {
      if (status === 'OK') {
        /* If an address is found, insert it into the search box. */
        textinput.value = results[0].formatted_address;
      } else {
        alert('Sorry, that location was not understood :( Please try again.');
      }
    });
  };

  /* If the map is clicked, move the marker to the location of the click, and
   * geocode the location. */
  const clickHandler = (event) => {
    marker.setPosition(event.latLng);
    geocode(event.latLng);
  };

  google.maps.event.addListener(map, 'click', clickHandler);

  /**
   * Event handler for the position_change event on the Marker.
   *
   * This will update the values of the hidden form elements for
   * latitude/longitude with the current location of the Marker.
   *
   * @param {google.maps.Marker} marker The marker whose position_change event
   *     will trigger the handler.
   */
  const positionChangeHandler = (marker) => {
    const latitudeFormInput = document.getElementById('latitude');
    const longitudeFormInput = document.getElementById('longitude');
    latitudeFormInput.value = marker.getPosition().lat();
    longitudeFormInput.value = marker.getPosition().lng();
    map.panTo(marker.getPosition());
  };

  google.maps.event.addListener(
      marker, 'position_changed', () => positionChangeHandler(marker));

  /* When the map first loads, the click trigger must be fired to
   * populate the hidden latitude and longitude input elements. */
  google.maps.event.trigger(marker, 'position_changed');

  /* Geocode the current marker position, and populate the search box with the
   * human-readable address of the current location. */
  geocode(marker.getPosition());

  loadSearchBox(map, marker);
};

/**
 * Load a search box onto the provided Map.
 * @param {google.maps.Map} map
 * @param {google.maps.Marker} marker
 */
const loadSearchBox = (map, marker) => {
  const textinput = document.getElementById('address-textinput');
  const searchBox = new google.maps.places.SearchBox(textinput);
  map.controls[google.maps.ControlPosition.TOP_CENTER].push(textinput);

  // Listen for the event fired when the user selects a prediction and retrieve
  // more details for that place.
  searchBox.addListener('places_changed', () => {
    /* Get the results of the user's location search query. */
    const places = searchBox.getPlaces();

    /* If the user's query has returned no results, ask them to try again. */
    if (places.length == 0) {
      alert('Sorry, that address cannot be located :( Please try again.')
    }

    /* The first element of 'places' is the address which the user selected from
     * the dropdown. */
    const place = places.pop();

    /* If the user has selected a place which does not have an associated
     * location (lat/lng), ask the user to try again. */
    if (!place.geometry) {
      alert('Sorry, that address cannot be located :( Please try again.')
    }

    marker.setPosition(place.geometry.location);

    const bounds = new google.maps.LatLngBounds();

    /* If there is a recommended viewport for this result, use that viewport. */
    if (place.geometry.viewport) {
      bounds.union(place.geometry.viewport);
    }
    /* If there is no recommended viewport for this result, simply center on the
       location of the result. */
    else {
      bounds.extend(place.geometry.location);
    }

    /* Recenter the map on the new location. */
    map.fitBounds(bounds);
  });
};

/* Export loadMap as a global variable on the browser's window object. No other
 * functions are exported. */
window.loadMap = loadMap;
<commit_msg>Improve documentation of maps.js<commit_after>/**
 * Copyright 2020 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * Create a map using the Maps API, and insert it into the page.
 */
const loadMap =
    () => {
      const onSuccess = (location) => {
        const lat = location.coords.latitude;
        const lng = location.coords.longitude;
        loadMapOnCoords(new google.maps.LatLng(lat, lng));
      };

      const onError = () => {
        loadMapOnCoords(new google.maps.LatLng(0, 0));
      };

      const options = {
        /* Try to get a high accuracy location result. */
        enableHighAccuracy: true,
        /* Wait no longer than 1.5s for a location result. */
        timeout: 1500  // ms
      };

      /* If the geolocation api is available, use it. Otherwise, center the map
       * on {0, 0}. */
      if ('geolocation' in navigator) {
        navigator.geolocation.getCurrentPosition(onSuccess, onError, options);
      } else {
        onError();
      }
    }

/**
 * Load a Map, centered on the provided location.
 * @param {google.maps.LatLng} center The center of the Map.
 */
const loadMapOnCoords = (center) => {
  /* Zoom level 17 corresponds to a 'local street' zoom, as per Maps API
   * documentation. */
  const zoom = 17;

  const mapContainer = document.getElementById('map');

  const options = {
    center: center,
    zoom: zoom,
    streetViewControl: false,
    mapTypeControl: false
  };

  /* Create a map, and insert it into the map container. */
  const map = new google.maps.Map(mapContainer, options);

  /* Once the map has been created, load a Marker in the center of the map. */
  loadMapMarker(map, center);
};

/**
 * Create a Marker, and attach it to the provided Map at the provided position.
 * @param {google.maps.Map} map The map to attach the marker onto.
 * @param {google.maps.LatLng} position The position on the map to attach the Marker onto.
 */
const loadMapMarker = (map, position) => {
  const marker = new google.maps.Marker({
    position: position,
    map: map,
    title: 'Isolation Location',
  });

  /* Set the marker to be draggable to allow user interaction. */
  marker.setDraggable(true);

  /**
   * Geocode a location, and insert its human-readable address into the map's
   * search box.
   *
   * @param {google.maps.LatLng} latLng The location to geocode.
   */
  const geocode = (latLng) => {
    const textinput = document.getElementById('address-textinput');
    const geocoder = new google.maps.Geocoder();

    geocoder.geocode({location: latLng}, (results, status) => {
      /* If an address is found, insert it into the search box. */
      if (status === 'OK') {
        textinput.value = results[0].formatted_address;
      } else {
        alert('Sorry, that location was not understood :( Please try again.');
      }
    });
  };

  /* If the map is clicked, move the marker to the location of the click, and
   * geocode the location. */
  const clickHandler = (event) => {
    marker.setPosition(event.latLng);
    geocode(event.latLng);
  };

  google.maps.event.addListener(map, 'click', clickHandler);

  /**
   * Event handler for the position_change event on the Marker.
   *
   * This will update the values of the hidden form elements for
   * latitude/longitude with the current location of the Marker.
   *
   * @param {google.maps.Marker} marker The marker whose position_change event
   *     will trigger the handler.
   */
  const positionChangeHandler = (marker) => {
    const latitudeFormInput = document.getElementById('latitude');
    const longitudeFormInput = document.getElementById('longitude');
    latitudeFormInput.value = marker.getPosition().lat();
    longitudeFormInput.value = marker.getPosition().lng();
    map.panTo(marker.getPosition());
  };

  google.maps.event.addListener(
      marker, 'position_changed', () => positionChangeHandler(marker));

  /* When the map first loads, the click trigger must be fired to
   * populate the hidden latitude and longitude input elements. */
  google.maps.event.trigger(marker, 'position_changed');

  /* Geocode the current marker position, and populate the search box with the
   * human-readable address of the current location. */
  geocode(marker.getPosition());

  loadSearchBox(map, marker);
};

/**
 * Load a search box onto the provided Map.
 * @param {google.maps.Map} map
 * @param {google.maps.Marker} marker
 */
const loadSearchBox = (map, marker) => {
  const textinput = document.getElementById('address-textinput');
  const searchBox = new google.maps.places.SearchBox(textinput);
  map.controls[google.maps.ControlPosition.TOP_CENTER].push(textinput);

  // Listen for the event fired when the user selects a prediction and retrieve
  // more details for that place.
  searchBox.addListener('places_changed', () => {
    /* Get the results of the user's location search query. */
    const places = searchBox.getPlaces();

    /* If the user's query has returned no results, ask them to try again. */
    if (places.length == 0) {
      alert('Sorry, that address cannot be located :( Please try again.')
    }

    /* The first element of 'places' is the address which the user selected from
     * the dropdown. */
    const place = places.pop();

    /* If the user has selected a place which does not have an associated
     * location (lat/lng), ask the user to try again. */
    if (!place.geometry) {
      alert('Sorry, that address cannot be located :( Please try again.')
    }

    marker.setPosition(place.geometry.location);

    const bounds = new google.maps.LatLngBounds();

    /* If there is a recommended viewport for this result, use that viewport. */
    if (place.geometry.viewport) {
      bounds.union(place.geometry.viewport);
    }
    /* If there is no recommended viewport for this result, simply center on the
       location of the result. */
    else {
      bounds.extend(place.geometry.location);
    }

    /* Recenter the map on the new location. */
    map.fitBounds(bounds);
  });
};

/* Export loadMap as a global variable on the browser's window object. No other
 * functions are exported. */
window.loadMap = loadMap;
<|endoftext|>


<commit_before>'use strict';

/* global _, $, moment, PNotify */
var CartUtility = function() {
    this.spinTasksCount = 0;
    this.pnotifyStack = {'dir1': 'up', 'dir2': 'right', 'push': 'bottom', 'spacing1': 0, 'spacing2': 0};
};

CartUtility.prototype.post = function($http, $q, url, data, onSuccess, onError) {
    var self = this;
    var deferred = $q.defer();

    var options = {
        method: "POST",
        url: url,
        headers: {"Content-type": "application/x-www-form-urlencoded"}
    };
    if (_.isObject(data) && !_.isEmpty(data)) {
        options.data = $.param(data);
    }

    $http(options)
    .success(function(data) {
        if (self.handleResponse(data)) {
            deferred.resolve(onSuccess(data));
        } else {
            deferred.reject();
        }
    })
    .error(function(data) {
        if (_.isFunction(onError)) {
            onError(data);
        }
        deferred.reject();
    });

    return self.spinShow(deferred.promise);
};

CartUtility.prototype.spinShow = function(promise) {
    var self = this;
    self.spinTasksCount++;
    $('.spinner-mask').show();
    if (_.isObject(promise) && promise.hasOwnProperty('finally')) {
        // this is a promise
        promise['finally'](function() {
            self.spinHide();
        });
    } else {
        setTimeout(function() {
            self.spinHide();
        }, 300);
    }
    return promise;
};

CartUtility.prototype.spinHide = function() {
    this.spinTasksCount--;
    if (this.spinTasksCount > 0) {
        return; // still spin tasks left
    }
    $('.spinner-mask').hide();
};

CartUtility.prototype.notify = function(title, text, type, duration) {
    // type: "notice", "info", "success", or "error"
    // duration: milliseconds
    if (_.isUndefined(title)) {
        title = 'title';
    }
    if (_.isUndefined(text)) {
        text = '';
    }
    if (_.isUndefined(type) || _.isEmpty(type) || _.isNull(type)) {
        type = 'info';
    }
    if (_.isUndefined(duration) || _.isEmpty(duration) || _.isNull(duration)) {
        duration = 3000; // 3s
    }

    var options = {
        title: title,
        text: text,
        type: type,
        delay: duration,
        addclass: 'stack-bar-bottom',
        width: '70%',
        stack: this.pnotifyStack
    };
    if (type === 'error') {
        options.hide = false;
    }

    new PNotify(options);
};

CartUtility.prototype.handleResponse = function(result, notifySuccess, notifyError) {
    if (!_.isBoolean(notifySuccess)) {
        notifySuccess = false; // default not display the succeed message
    }
    if (!_.isBoolean(notifyError)) {
        notifyError = true; // default display the failure message
    }

    if (_.isObject(result) && result.hasOwnProperty('valid') && result.valid === false) {
        // request failed with normal framework exception
        if (notifyError) { // show failure msg box
            this.notify('REQ Failed!', result.message.error, 'error');
        }
        return false;
    } else if (!_.isObject(result) || (_.isObject(result) && !result.hasOwnProperty('valid'))) {
        // request failed without normal framework exception
        if (notifyError) { // show failure msg box
            this.notify('REQ Failed!');
        }
        return false;
    } else {
        // request done
        if (notifySuccess) { // show success msg box, and hide automatically
            this.notify('REQ Succeed!', JSON.stringify(result.message));
        }
        return true;
    }
};

CartUtility.prototype.buildToc = function(html, baseUrl) {
    if (_.isUndefined(baseUrl) || _.isNull(baseUrl)) {
        baseUrl = ''; // shall be 'http://...'
    }
    var rootNodeName = 'root';
    /**
     * [
     *     { // index of this node is 5
     *         "level": 3,
     *         "text": "headerTextForLv3",
     *         "anchor": "CartUtility.escapeAnchorName(headerTextForLv3)"
     *         "parent": 4, // index of node in this array, only root parent is string 'root'
     *         "html": $('text')
     *     },
     *     ...
     * ]
     */
    var headers = [];

    if (_.isUndefined(html) || _.isEmpty(html) || _.isNull(html)) {
        return '';
    }

    // find all headers, and collect basic info
    var headerInfos = $(html).filter(':header');
    if (headerInfos.length <= 0) {
        return ''; // no headers found
    }
    _.forEach(headerInfos, function(header) {
        // build basic headers info
        var hElement = $(header);
        var headerLevel = parseInt(hElement.prop("tagName").substring(1));
        var headerText = hElement.text();
        var anchorName = hElement.find('a').attr('name');
        headers.push({
            "level": headerLevel,
            "text": headerText,
            "anchor": anchorName,
            "parent": null,
            "html": null
        });
    });
    var headersCloned = _.clone(headers);
    // loop to build html code & find parent levels
    _.forEach(headers, function(header, index) {
        header.html = $('
' + header.text + '
');
        if (index === 0) {
            header.parent = rootNodeName;
        } else {
            var reversedBeforeHeaders = headersCloned.slice(0, index).reverse();
            _.forEach(reversedBeforeHeaders, function(bfHeader, bfIndex) {
                if (header.parent !== null) {
                    // means parent already determined, skip this loop
                    return;
                }
                if (bfHeader.level === header.level) {
                    // same level, shall share the same parent node
                    header.parent = bfHeader.parent;
                } else if (bfHeader.level  header.level) {
                    // current level is lower than target, dismiss it
                }
            });
            if (header.parent === null) {
                // parent not found, root node
                header.parent = rootNodeName;
            }
        }
    });

    // build final html codes
    var root = $('

');
    // build non root nodes first, and have to reverse headers first
    // since we need to handle sub headers from the bottom to top
    _.forEach(headers, function(header) {
        if (header.parent === rootNodeName) {
            return; // dismiss root nodes, since the content in it has not been built yet
        }
        var parentNode = headers[header.parent].html;
        if (parentNode.find('ul').length <= 0) {
            // parent li element does not contain the ul element, add it
            parentNode.append('

');
        }
        $(parentNode.find('ul')[0]).append(header.html);
    });
    // build root nodes
    _.forEach(headers, function(header) {
        if (header.parent === rootNodeName) {
            root.append(header.html);
        }
    });

    return '
' + root.html() + '
'; // since html() only returned the innerHtml of the element
};

CartUtility.prototype.toggleToc = function(icon, toc) { /* all params are jquery elements: $('#id') */
    if (toc.html() !== '') {
        toc.slideToggle('fast', function() {
            icon.toggleClass('glyphicon-plus').toggleClass('glyphicon-minus');
        });
    }
};

CartUtility.prototype.escapeAnchorName = function(anchorName) {
    return encodeURIComponent(anchorName);
};

CartUtility.prototype.getPureRootUrlFromLocation = function($location) {
    return $location.protocol() + '://' + $location.host() + '/';
};

CartUtility.prototype.getPureAbsUrlFromLocation = function($location) {
    return $location.protocol() + '://' + $location.host() + '/' + $location.path();
};

CartUtility.prototype.log = function(msg, facility) {
    if (_.isUndefined(msg) || _.isEmpty(msg) || _.isNull(msg)) {
        return;
    }
    if (_.isObject(console)) {
        if (_.isObject(msg)) {
            msg = JSON.stringify(msg);
        }
        msg = '[' + moment().format('YYYY-MM-DD HH:mm:ss') + ']' + ((_.isString(facility)) ? (' ' + facility) : '') + ' ' + msg;
        console.log(msg);
    }
};

CartUtility.prototype.isDndSupported = function() {
    var div = document.createElement('div');
    return ('draggable' in div) || ('ondragstart' in div && 'ondrop' in div);
};

CartUtility.prototype.generateMdHTMLHeader = function(uuid, title, category, tags, attachments) {
    return '

' + JSON.stringify({
        "uuid": uuid,
        "title": title,
        "category": category,
        "tags": tags,
        "attachments": attachments
    }) + '

';
};

CartUtility.prototype.parseMdHTMLHeader = function(markdown) {
    return JSON.parse($(markdown).find('.mdHeader').text());
};

CartUtility.prototype.parseMdPureContent = function(markdown) {
    return markdown.substr(markdown.indexOf('

') + '

'.length);
};

CartUtility.prototype.focusEditor = function(editor) {
    editor.focus(); // focus the ace editor
    editor.resize(true); // resize the window

    var editorValues = editor.getSession().getValue();
    if (_.isEmpty(editorValues)) {
        return; // no content, no need to redirect to end of page
    }

    editorValues = editorValues.split("\n");
    var lineNo = editorValues.length; // count total number of lines
    var columnNo = editorValues[lineNo - 1].split('').length;
    editor.gotoLine(lineNo, columnNo); // go to end of document
};

module.exports = new CartUtility();<commit_msg>Fix header generation function param input issue.<commit_after>'use strict';

/* global _, $, moment, PNotify */
var CartUtility = function() {
    this.spinTasksCount = 0;
    this.pnotifyStack = {'dir1': 'up', 'dir2': 'right', 'push': 'bottom', 'spacing1': 0, 'spacing2': 0};
};

CartUtility.prototype.post = function($http, $q, url, data, onSuccess, onError) {
    var self = this;
    var deferred = $q.defer();

    var options = {
        method: "POST",
        url: url,
        headers: {"Content-type": "application/x-www-form-urlencoded"}
    };
    if (_.isObject(data) && !_.isEmpty(data)) {
        options.data = $.param(data);
    }

    $http(options)
    .success(function(data) {
        if (self.handleResponse(data)) {
            deferred.resolve(onSuccess(data));
        } else {
            deferred.reject();
        }
    })
    .error(function(data) {
        if (_.isFunction(onError)) {
            onError(data);
        }
        deferred.reject();
    });

    return self.spinShow(deferred.promise);
};

CartUtility.prototype.spinShow = function(promise) {
    var self = this;
    self.spinTasksCount++;
    $('.spinner-mask').show();
    if (_.isObject(promise) && promise.hasOwnProperty('finally')) {
        // this is a promise
        promise['finally'](function() {
            self.spinHide();
        });
    } else {
        setTimeout(function() {
            self.spinHide();
        }, 300);
    }
    return promise;
};

CartUtility.prototype.spinHide = function() {
    this.spinTasksCount--;
    if (this.spinTasksCount > 0) {
        return; // still spin tasks left
    }
    $('.spinner-mask').hide();
};

CartUtility.prototype.notify = function(title, text, type, duration) {
    // type: "notice", "info", "success", or "error"
    // duration: milliseconds
    if (_.isUndefined(title)) {
        title = 'title';
    }
    if (_.isUndefined(text)) {
        text = '';
    }
    if (_.isUndefined(type) || _.isEmpty(type) || _.isNull(type)) {
        type = 'info';
    }
    if (_.isUndefined(duration) || _.isEmpty(duration) || _.isNull(duration)) {
        duration = 3000; // 3s
    }

    var options = {
        title: title,
        text: text,
        type: type,
        delay: duration,
        addclass: 'stack-bar-bottom',
        width: '70%',
        stack: this.pnotifyStack
    };
    if (type === 'error') {
        options.hide = false;
    }

    new PNotify(options);
};

CartUtility.prototype.handleResponse = function(result, notifySuccess, notifyError) {
    if (!_.isBoolean(notifySuccess)) {
        notifySuccess = false; // default not display the succeed message
    }
    if (!_.isBoolean(notifyError)) {
        notifyError = true; // default display the failure message
    }

    if (_.isObject(result) && result.hasOwnProperty('valid') && result.valid === false) {
        // request failed with normal framework exception
        if (notifyError) { // show failure msg box
            this.notify('REQ Failed!', result.message.error, 'error');
        }
        return false;
    } else if (!_.isObject(result) || (_.isObject(result) && !result.hasOwnProperty('valid'))) {
        // request failed without normal framework exception
        if (notifyError) { // show failure msg box
            this.notify('REQ Failed!');
        }
        return false;
    } else {
        // request done
        if (notifySuccess) { // show success msg box, and hide automatically
            this.notify('REQ Succeed!', JSON.stringify(result.message));
        }
        return true;
    }
};

CartUtility.prototype.buildToc = function(html, baseUrl) {
    if (_.isUndefined(baseUrl) || _.isNull(baseUrl)) {
        baseUrl = ''; // shall be 'http://...'
    }
    var rootNodeName = 'root';
    /**
     * [
     *     { // index of this node is 5
     *         "level": 3,
     *         "text": "headerTextForLv3",
     *         "anchor": "CartUtility.escapeAnchorName(headerTextForLv3)"
     *         "parent": 4, // index of node in this array, only root parent is string 'root'
     *         "html": $('text')
     *     },
     *     ...
     * ]
     */
    var headers = [];

    if (_.isUndefined(html) || _.isEmpty(html) || _.isNull(html)) {
        return '';
    }

    // find all headers, and collect basic info
    var headerInfos = $(html).filter(':header');
    if (headerInfos.length <= 0) {
        return ''; // no headers found
    }
    _.forEach(headerInfos, function(header) {
        // build basic headers info
        var hElement = $(header);
        var headerLevel = parseInt(hElement.prop("tagName").substring(1));
        var headerText = hElement.text();
        var anchorName = hElement.find('a').attr('name');
        headers.push({
            "level": headerLevel,
            "text": headerText,
            "anchor": anchorName,
            "parent": null,
            "html": null
        });
    });
    var headersCloned = _.clone(headers);
    // loop to build html code & find parent levels
    _.forEach(headers, function(header, index) {
        header.html = $('
' + header.text + '
');
        if (index === 0) {
            header.parent = rootNodeName;
        } else {
            var reversedBeforeHeaders = headersCloned.slice(0, index).reverse();
            _.forEach(reversedBeforeHeaders, function(bfHeader, bfIndex) {
                if (header.parent !== null) {
                    // means parent already determined, skip this loop
                    return;
                }
                if (bfHeader.level === header.level) {
                    // same level, shall share the same parent node
                    header.parent = bfHeader.parent;
                } else if (bfHeader.level  header.level) {
                    // current level is lower than target, dismiss it
                }
            });
            if (header.parent === null) {
                // parent not found, root node
                header.parent = rootNodeName;
            }
        }
    });

    // build final html codes
    var root = $('

');
    // build non root nodes first, and have to reverse headers first
    // since we need to handle sub headers from the bottom to top
    _.forEach(headers, function(header) {
        if (header.parent === rootNodeName) {
            return; // dismiss root nodes, since the content in it has not been built yet
        }
        var parentNode = headers[header.parent].html;
        if (parentNode.find('ul').length <= 0) {
            // parent li element does not contain the ul element, add it
            parentNode.append('

');
        }
        $(parentNode.find('ul')[0]).append(header.html);
    });
    // build root nodes
    _.forEach(headers, function(header) {
        if (header.parent === rootNodeName) {
            root.append(header.html);
        }
    });

    return '
' + root.html() + '
'; // since html() only returned the innerHtml of the element
};

CartUtility.prototype.toggleToc = function(icon, toc) { /* all params are jquery elements: $('#id') */
    if (toc.html() !== '') {
        toc.slideToggle('fast', function() {
            icon.toggleClass('glyphicon-plus').toggleClass('glyphicon-minus');
        });
    }
};

CartUtility.prototype.escapeAnchorName = function(anchorName) {
    return encodeURIComponent(anchorName);
};

CartUtility.prototype.getPureRootUrlFromLocation = function($location) {
    return $location.protocol() + '://' + $location.host() + '/';
};

CartUtility.prototype.getPureAbsUrlFromLocation = function($location) {
    return $location.protocol() + '://' + $location.host() + '/' + $location.path();
};

CartUtility.prototype.log = function(msg, facility) {
    if (_.isUndefined(msg) || _.isEmpty(msg) || _.isNull(msg)) {
        return;
    }
    if (_.isObject(console)) {
        if (_.isObject(msg)) {
            msg = JSON.stringify(msg);
        }
        msg = '[' + moment().format('YYYY-MM-DD HH:mm:ss') + ']' + ((_.isString(facility)) ? (' ' + facility) : '') + ' ' + msg;
        console.log(msg);
    }
};

CartUtility.prototype.isDndSupported = function() {
    var div = document.createElement('div');
    return ('draggable' in div) || ('ondragstart' in div && 'ondrop' in div);
};

CartUtility.prototype.generateMdHTMLHeader = function(post) {
    return '

' + JSON.stringify({
        "uuid": post.uuid,
        "title": post.title,
        "category": post.category,
        "tags": post.tags,
        "attachments": post.attachments
    }) + '

';
};

CartUtility.prototype.parseMdHTMLHeader = function(markdown) {
    return JSON.parse($(markdown).find('.mdHeader').text());
};

CartUtility.prototype.parseMdPureContent = function(markdown) {
    return markdown.substr(markdown.indexOf('

') + '

'.length);
};

CartUtility.prototype.focusEditor = function(editor) {
    editor.focus(); // focus the ace editor
    editor.resize(true); // resize the window

    var editorValues = editor.getSession().getValue();
    if (_.isEmpty(editorValues)) {
        return; // no content, no need to redirect to end of page
    }

    editorValues = editorValues.split("\n");
    var lineNo = editorValues.length; // count total number of lines
    var columnNo = editorValues[lineNo - 1].split('').length;
    editor.gotoLine(lineNo, columnNo); // go to end of document
};

module.exports = new CartUtility();<|endoftext|>


<commit_before>import Corridor from './corridor';
import {isInside, modGrid} from "./utils";
import {WallSize, Horizontal, Vertical } from './constants';

const CorridorHeight = 4 * WallSize;
const CorridorWidth = 4 * WallSize;
const MaxRoom = 3;
//each room shoud have the same size
const MinRoomSize = 5 * WallSize;
const MaxRoomSize = 5 * WallSize;

class Dungeon extends Phaser.Group {

  constructor(game, parent, worldWitdth, worldHeight, arrayOfRoom) {
    super(game, parent, "dungeon", false, true, Phaser.Physics.ARCADE);
    this.worldWitdth = worldWitdth;
    this.worldHeight = worldHeight;
    this.arrayOfRoom = arrayOfRoom;
    this.generateLevel(game, this.randomGeneration.bind(this));
  }

  generateLevel(game, generationFunction) {
    generationFunction(game);
    this.exportDebug();
    this.removeUselessWalls(game);
    this.sortDepth();
    this.addAdditionalSprite(game);
  }

  randomGeneration(game) {
    for(let i = 0; i < MaxRoom; i++) {
      //FIX ME  the computation does not work well
      // TODO OFFSET WALLSIZE
      const width = modGrid(WallSize, MinRoomSize + Math.random() * (MaxRoomSize - MinRoomSize));
      const height = modGrid(WallSize, MinRoomSize + Math.random() * (MaxRoomSize - MinRoomSize));
      //console.log(width)
      //console.log(height)
      const x = modGrid(WallSize, Math.random() * (this.worldWitdth - width - 1) + 1);
      const y = modGrid(WallSize, Math.random() * (this.worldHeight - height - 1) + 1);

      const indexChosen = Math.trunc(Math.random() * this.arrayOfRoom.length);
      let newRoom = Reflect.construct(this.arrayOfRoom[indexChosen],[game, game.world, x, y, width, height]);
      let failed = false;
      this.children.some(child => {
         failed = newRoom.overlapRoom(child);
         if(failed) {
          //exit le loop
          return true;
         }
      });
      if (!failed) {
        // local function to carve out new room
        this.createRoom(newRoom);
        if(this.rooms().length > 1) {
          const prevRoom = this.findLastRoom(newRoom);
          const rng = Math.random() * 2;
          if(rng >= 1) {
            this.horizontalCorridor(game, prevRoom, newRoom);
            this.verticalCorridor(game, prevRoom, newRoom, false);
          } else {
            this.verticalCorridor(game, prevRoom, newRoom);
            this.horizontalCorridor(game, prevRoom, newRoom, false);
          }
        }
      }
    }
  }

   importFromJson(game) {
     this.import( game,
                  [{"x":1376,"y":384,"w":128,"h":128},{"x":1376,"y":816,"w":128,"h":128}],
                  [{"x":1424,"y":400,"w":272,"h":96,"direction":"horizontal"},{"x":1392,"y":432,"w":96,"h":464,"direction":"vertical"}]
                );
   }

  import(game, rooms, corridors) {
    rooms.forEach(room => {
      const newRoom = Reflect.construct(this.arrayOfRoom[0],[game, game.world, room.x, room.y, room.w, room.h]);
      this.createRoom(newRoom);
    });
    corridors.forEach(corridor => {
      const newCorridor = new Corridor(game, game.world, corridor.x, corridor.y, corridor.w, corridor.h, corridor.direction);
      this.add(newCorridor);
    });
  }

  rooms(newRoom = null) {
    return this.children.filter(child => {
      return child.name === "room" && child != newRoom;
    });
  }

  findLastRoom(newRoom) {
    const rooms = this.rooms(newRoom);
    return rooms[rooms.length - 1];
  }

  createRoom(room) {
    this.add(room);
  }

  horizontalCorridor(game, prevRoom, newRoom, first = true) {
    const x1 = Math.min(prevRoom.borders().center.x, newRoom.borders().center.x);
    const x2 = Math.max(prevRoom.borders().center.x, newRoom.borders().center.x);
    const y = first ? prevRoom.borders().center.y : newRoom.borders().center.y;
    const width = x2 - x1;
    const corridor = new Corridor(game, game.world, x1, y - CorridorHeight/2, width, CorridorHeight, Horizontal);
    this.add(corridor);
  }

  verticalCorridor(game, prevRoom, newRoom, first = true) {
    const y1 = Math.min(prevRoom.borders().center.y, newRoom.borders().center.y);
    const y2 = Math.max(prevRoom.borders().center.y, newRoom.borders().center.y);
    const x = first ? prevRoom.borders().center.x : newRoom.borders().center.x;
    const height = y2 - y1;
    const corridor = new Corridor(game, game.world, x - CorridorWidth/2, y1, CorridorWidth, height, Vertical);
    this.add(corridor);
  }

  getInitialRoom() {
    return this.rooms()[0];
  }

  collide(character) {
    let collide = false;
    this.children.forEach(child => {
      if(child.name === "corridor") {
        if(isInside(character, child.corridorSprite())) {
          collide = true;
        }
      } else {
        if(isInside(character, child)) {
          collide = true;
        }
      }
    });
    return collide;
  }

  walls() {
    const arrayMultipleDim = this.children.map(child => child.walls());
    return [].concat.apply([], arrayMultipleDim);
  }

  corridors() {
    return this.children.filter(child => child.name === "corridor");
  }

  corridorSprites() {
    return this.corridors().map(child => child.corridorSprite());
  }

  roomsSprites() {
    return this.rooms().map(child => child.roomSprite());
  }

  removeUselessWalls(game) {
    const destroyFunction = (wall, other) => {
      wall.kill();
    }

    this.rooms().forEach(room => {
      game.physics.arcade.collide(room.walls(), this.corridorSprites(), destroyFunction);
    });

    this.corridors().forEach(corridor => {
      game.physics.arcade.collide(corridor.walls(), this.roomsSprites(), destroyFunction);
    });

     this.corridors().forEach(corridor => {
        this.corridors().forEach(corridor2 => {
          if(corridor !== corridor2) {
            game.physics.arcade.collide(corridor.walls(), corridor2.corridorSprite(), destroyFunction);
          }
        });
     });
  }

  addAdditionalSprite(game) {
    this.rooms().forEach(room => {
      room.addAdditionalSprite(game);
    });
  }

  sortDepth() {
    const compare = (a,b) => {
      if(a.name === "room" && b.name === "room" || a.name === "corridor" &&  b.name === "corridor") {
        return 0;
      }
      else if (a.name === "room" && b.name == "corridor") {
        return 1;
      }
      else if (a.name === "corridor" && b.name == "room") {
        return -1;
      }
    };
    this.children.sort(compare);
  }

  exportDebug() {
    const roomArray = this.rooms().map(m => {return {x: m.roomSprite().x, y: m.roomSprite().y, w: m.width, h: m.height};});
    const corridorArray = this.corridors().map(m => {return {x: m.corridorSprite().x, y: m.corridorSprite().y, w: m.width, h: m.height, direction: m.direction};});
    console.log(JSON.stringify(roomArray));
    console.log(JSON.stringify(corridorArray));
  }

}

export default Dungeon;<commit_msg>change how the level is processed<commit_after>import Corridor from './corridor';
import {isInside, modGrid} from "./utils";
import {WallSize, Horizontal, Vertical } from './constants';

const CorridorHeight = 4 * WallSize;
const CorridorWidth = 4 * WallSize;
const MaxRoom = 3;
//each room shoud have the same size
const MinRoomSize = 5 * WallSize;
const MaxRoomSize = 5 * WallSize;

class Dungeon extends Phaser.Group {

  constructor(game, parent, worldWitdth, worldHeight, arrayOfRoom) {
    super(game, parent, "dungeon", false, true, Phaser.Physics.ARCADE);
    this.worldWitdth = worldWitdth;
    this.worldHeight = worldHeight;
    this.arrayOfRoom = arrayOfRoom;
  }

  generate(game, JSONData = null) {
    const strategyFn = JSONData === null ? this.randomGeneration.bind(this) : this.importFromJson(JSONData).bind(this);
    this.generateLevel(game, strategyFn);
  }

  generateLevel(game, generationFunction) {
    generationFunction(game);
    this.exportDebug();
    this.removeUselessWalls(game);
    this.sortDepth();
    this.addAdditionalSprite(game);
  }

  randomGeneration(game) {
    for(let i = 0; i < MaxRoom; i++) {
      //FIX ME  the computation does not work well
      // TODO OFFSET WALLSIZE
      const width = modGrid(WallSize, MinRoomSize + Math.random() * (MaxRoomSize - MinRoomSize));
      const height = modGrid(WallSize, MinRoomSize + Math.random() * (MaxRoomSize - MinRoomSize));
      //console.log(width)
      //console.log(height)
      const x = modGrid(WallSize, Math.random() * (this.worldWitdth - width - 1) + 1);
      const y = modGrid(WallSize, Math.random() * (this.worldHeight - height - 1) + 1);

      const indexChosen = Math.trunc(Math.random() * this.arrayOfRoom.length);
      let newRoom = Reflect.construct(this.arrayOfRoom[indexChosen],[game, game.world, x, y, width, height]);
      let failed = false;
      this.children.some(child => {
         failed = newRoom.overlapRoom(child);
         if(failed) {
          //exit le loop
          return true;
         }
      });
      if (!failed) {
        // local function to carve out new room
        this.createRoom(newRoom);
        if(this.rooms().length > 1) {
          const prevRoom = this.findLastRoom(newRoom);
          const rng = Math.random() * 2;
          if(rng >= 1) {
            this.horizontalCorridor(game, prevRoom, newRoom);
            this.verticalCorridor(game, prevRoom, newRoom, false);
          } else {
            this.verticalCorridor(game, prevRoom, newRoom);
            this.horizontalCorridor(game, prevRoom, newRoom, false);
          }
        }
      }
    }
  }

  importFromJson(JSONData) {
    return (game) => {
      var data = JSON.parse(JSONData);
      this.import( game,
                   data.rooms,
                   data.corridors
                );
    }
  }

  import(game, rooms, corridors) {
    rooms.forEach(room => {
      const newRoom = Reflect.construct(this.arrayOfRoom[0],[game, game.world, room.x, room.y, room.w, room.h]);
      this.createRoom(newRoom);
    });
    corridors.forEach(corridor => {
      const newCorridor = new Corridor(game, game.world, corridor.x, corridor.y, corridor.w, corridor.h, corridor.direction);
      this.add(newCorridor);
    });
  }

  rooms(newRoom = null) {
    return this.children.filter(child => {
      return child.name === "room" && child != newRoom;
    });
  }

  findLastRoom(newRoom) {
    const rooms = this.rooms(newRoom);
    return rooms[rooms.length - 1];
  }

  createRoom(room) {
    this.add(room);
  }

  horizontalCorridor(game, prevRoom, newRoom, first = true) {
    const x1 = Math.min(prevRoom.borders().center.x, newRoom.borders().center.x);
    const x2 = Math.max(prevRoom.borders().center.x, newRoom.borders().center.x);
    const y = first ? prevRoom.borders().center.y : newRoom.borders().center.y;
    const width = x2 - x1;
    const corridor = new Corridor(game, game.world, x1, y - CorridorHeight/2, width, CorridorHeight, Horizontal);
    this.add(corridor);
  }

  verticalCorridor(game, prevRoom, newRoom, first = true) {
    const y1 = Math.min(prevRoom.borders().center.y, newRoom.borders().center.y);
    const y2 = Math.max(prevRoom.borders().center.y, newRoom.borders().center.y);
    const x = first ? prevRoom.borders().center.x : newRoom.borders().center.x;
    const height = y2 - y1;
    const corridor = new Corridor(game, game.world, x - CorridorWidth/2, y1, CorridorWidth, height, Vertical);
    this.add(corridor);
  }

  getInitialRoom() {
    return this.rooms()[0];
  }

  collide(character) {
    let collide = false;
    this.children.forEach(child => {
      if(child.name === "corridor") {
        if(isInside(character, child.corridorSprite())) {
          collide = true;
        }
      } else {
        if(isInside(character, child)) {
          collide = true;
        }
      }
    });
    return collide;
  }

  walls() {
    const arrayMultipleDim = this.children.map(child => child.walls());
    return [].concat.apply([], arrayMultipleDim);
  }

  corridors() {
    return this.children.filter(child => child.name === "corridor");
  }

  corridorSprites() {
    return this.corridors().map(child => child.corridorSprite());
  }

  roomsSprites() {
    return this.rooms().map(child => child.roomSprite());
  }

  removeUselessWalls(game) {
    const destroyFunction = (wall, other) => {
      wall.kill();
    }

    this.rooms().forEach(room => {
      game.physics.arcade.collide(room.walls(), this.corridorSprites(), destroyFunction);
    });

    this.corridors().forEach(corridor => {
      game.physics.arcade.collide(corridor.walls(), this.roomsSprites(), destroyFunction);
    });

     this.corridors().forEach(corridor => {
        this.corridors().forEach(corridor2 => {
          if(corridor !== corridor2) {
            game.physics.arcade.collide(corridor.walls(), corridor2.corridorSprite(), destroyFunction);
          }
        });
     });
  }

  addAdditionalSprite(game) {
    this.rooms().forEach(room => {
      room.addAdditionalSprite(game);
    });
  }

  sortDepth() {
    const compare = (a,b) => {
      if(a.name === "room" && b.name === "room" || a.name === "corridor" &&  b.name === "corridor") {
        return 0;
      }
      else if (a.name === "room" && b.name == "corridor") {
        return 1;
      }
      else if (a.name === "corridor" && b.name == "room") {
        return -1;
      }
    };
    this.children.sort(compare);
  }

  exportDebug() {
    const roomArray = this.rooms().map(m => {return {x: m.roomSprite().x, y: m.roomSprite().y, w: m.width, h: m.height};});
    const corridorArray = this.corridors().map(m => {return {x: m.corridorSprite().x, y: m.corridorSprite().y, w: m.width, h: m.height, direction: m.direction};});
    console.log(JSON.stringify({rooms: roomArray, corridors: corridorArray}));
  }

}

export default Dungeon;<|endoftext|>


<commit_before>// Generated on 2014-06-01 using generator-angular 0.8.0
'use strict';

// # Globbing
// for performance reasons we're only matching one level down:
// 'test/spec/{,*/}*.js'
// use this if you want to recursively match all subfolders:
// 'test/spec/**/*.js'

module.exports = function (grunt) {

  // Load grunt tasks automatically
  require('load-grunt-tasks')(grunt);

  // Time how long tasks take. Can help when optimizing build times
  require('time-grunt')(grunt);

  // Define the configuration for all the tasks
  grunt.initConfig({

    // Project settings
    yeoman: {
      // configurable paths
      app: require('./bower.json').appPath || 'app',
      dist: 'dist'
    },

    // Watches files for changes and runs tasks based on the changed files
    watch: {
      bower: {
        files: ['bower.json'],
        tasks: ['wiredep']
      },
      js: {
        files: ['<%= yeoman.app %>/scripts/{,*/}*.js'],
        tasks: ['newer:jshint:all'],
        options: {
          livereload: '<%= connect.options.livereload %>'
        }
      },
      jsTest: {
        files: ['test/spec/{,*/}*.js'],
        tasks: ['newer:jshint:test', 'karma']
      },
      compass: {
        files: ['<%= yeoman.app %>/styles/{,*/}*.{scss,sass}'],
        tasks: ['compass:server', 'autoprefixer']
      },
      gruntfile: {
        files: ['Gruntfile.js']
      },
      livereload: {
        options: {
          livereload: '<%= connect.options.livereload %>'
        },
        files: [
          '<%= yeoman.app %>/{,*/}*.html',
          '.tmp/styles/{,*/}*.css',
          '<%= yeoman.app %>/images/{,*/}*.{png,jpg,jpeg,gif,webp,svg}'
        ]
      }
    },

    // The actual grunt server settings
    connect: {
      options: {
        port: 9000,
        // Change this to '0.0.0.0' to access the server from outside.
        hostname: 'localhost',
        livereload: 35729
      },
      livereload: {
        options: {
          open: true,
          base: [
            '.tmp',
            '<%= yeoman.app %>'
          ]
        }
      },
      test: {
        options: {
          port: 9001,
          base: [
            '.tmp',
            'test',
            '<%= yeoman.app %>'
          ]
        }
      },
      dist: {
        options: {
          open: true,
          base: '<%= yeoman.dist %>'
        }
      }
    },

    // Make sure code styles are up to par and there are no obvious mistakes
    jshint: {
      options: {
        jshintrc: '.jshintrc',
        reporter: require('jshint-stylish')
      },
      all: {
        src: [
          'Gruntfile.js',
          '<%= yeoman.app %>/scripts/{,*/}*.js'
        ]
      },
      test: {
        options: {
          jshintrc: 'test/.jshintrc'
        },
        src: ['test/spec/{,*/}*.js']
      }
    },

    // Empties folders to start fresh
    clean: {
      dist: {
        files: [{
          dot: true,
          src: [
            '.tmp',
            '.sass-cache',
            '<%= yeoman.dist %>/{,*/}*',
            '!<%= yeoman.dist %>/.git*'
          ]
        }]
      },
      server: '.tmp'
    },

    // Add vendor prefixed styles
    autoprefixer: {
      options: {
        browsers: ['last 1 version']
      },
      dist: {
        files: [{
          expand: true,
          cwd: '.tmp/styles/',
          src: '{,*/}*.css',
          dest: '.tmp/styles/'
        }]
      }
    },

    // Automatically inject Bower components into the app
    wiredep: {
      app: {
        src: ['<%= yeoman.app %>/index.html'],
        ignorePath: new RegExp('^<%= yeoman.app %>/')
      },
      sass: {
        src: ['<%= yeoman.app %>/styles/{,*/}*.{scss,sass}'],
        ignorePath: '<%= yeoman.app %>/bower_components/'
      }
    },

    // Compiles Sass to CSS and generates necessary files if requested
    compass: {
      options: {
        sassDir: '<%= yeoman.app %>/styles',
        cssDir: '.tmp/styles',
        generatedImagesDir: '.tmp/images/generated',
        imagesDir: '<%= yeoman.app %>/images',
        javascriptsDir: '<%= yeoman.app %>/scripts',
        fontsDir: '<%= yeoman.app %>/styles/fonts',
        importPath: '<%= yeoman.app %>/bower_components',
        httpImagesPath: '/images',
        httpGeneratedImagesPath: '/images/generated',
        httpFontsPath: '/styles/fonts',
        relativeAssets: false,
        assetCacheBuster: false,
        raw: 'Sass::Script::Number.precision = 10\n'
      },
      dist: {
        options: {
          generatedImagesDir: '<%= yeoman.dist %>/images/generated'
        }
      },
      server: {
        options: {
          debugInfo: true
        }
      }
    },

    // Renames files for browser caching purposes
    filerev: {
      dist: {
        src: [
          '<%= yeoman.dist %>/scripts/{,*/}*.js',
          '<%= yeoman.dist %>/styles/{,*/}*.css',
          '<%= yeoman.dist %>/images/{,*/}*.{png,jpg,jpeg,gif,webp,svg}',
          '<%= yeoman.dist %>/styles/fonts/*'
        ]
      }
    },

    // Reads HTML for usemin blocks to enable smart builds that automatically
    // concat, minify and revision files. Creates configurations in memory so
    // additional tasks can operate on them
    useminPrepare: {
      html: '<%= yeoman.app %>/index.html',
      options: {
        dest: '<%= yeoman.dist %>',
        flow: {
          html: {
            steps: {
              js: ['concat', 'uglifyjs'],
              css: ['cssmin']
            },
            post: {}
          }
        }
      }
    },

    // Performs rewrites based on filerev and the useminPrepare configuration
    usemin: {
      html: ['<%= yeoman.dist %>/{,*/}*.html'],
      css: ['<%= yeoman.dist %>/styles/{,*/}*.css'],
      options: {
        assetsDirs: ['<%= yeoman.dist %>','<%= yeoman.dist %>/images']
      }
    },

    // The following *-min tasks will produce minified files in the dist folder
    // By default, your `index.html`'s <!-- Usemin block --> will take care of
    // minification. These next options are pre-configured if you do not wish
    // to use the Usemin blocks.
    // cssmin: {
    //   dist: {
    //     files: {
    //       '<%= yeoman.dist %>/styles/main.css': [
    //         '.tmp/styles/{,*/}*.css'
    //       ]
    //     }
    //   }
    // },
    // uglify: {
    //   dist: {
    //     files: {
    //       '<%= yeoman.dist %>/scripts/scripts.js': [
    //         '<%= yeoman.dist %>/scripts/scripts.js'
    //       ]
    //     }
    //   }
    // },
    // concat: {
    //   dist: {}
    // },

    imagemin: {
      dist: {
        files: [{
          expand: true,
          cwd: '<%= yeoman.app %>/images',
          src: '{,*/}*.{png,jpg,jpeg,gif}',
          dest: '<%= yeoman.dist %>/images'
        }]
      }
    },

    svgmin: {
      dist: {
        files: [{
          expand: true,
          cwd: '<%= yeoman.app %>/images',
          src: '{,*/}*.svg',
          dest: '<%= yeoman.dist %>/images'
        }]
      }
    },

    htmlmin: {
      dist: {
        options: {
          collapseWhitespace: true,
          conservativeCollapse: true,
          collapseBooleanAttributes: true,
          removeCommentsFromCDATA: true,
          removeOptionalTags: true
        },
        files: [{
          expand: true,
          cwd: '<%= yeoman.dist %>',
          src: ['*.html', 'views/{,*/}*.html'],
          dest: '<%= yeoman.dist %>'
        }]
      }
    },

    // ngmin tries to make the code safe for minification automatically by
    // using the Angular long form for dependency injection. It doesn't work on
    // things like resolve or inject so those have to be done manually.
    ngmin: {
      dist: {
        files: [{
          expand: true,
          cwd: '.tmp/concat/scripts',
          src: '*.js',
          dest: '.tmp/concat/scripts'
        }]
      }
    },

    // Replace Google CDN references
    cdnify: {
      dist: {
        html: ['<%= yeoman.dist %>/*.html']
      }
    },

    // Copies remaining files to places other tasks can use
    copy: {
      dist: {
        files: [{
          expand: true,
          dot: true,
          cwd: '<%= yeoman.app %>',
          dest: '<%= yeoman.dist %>',
          src: [
            '*.{ico,png,txt}',
            '.htaccess',
            'config.json',
            'heatmap.json',
            '*.html',
            'views/{,*/}*.html',
            'images/{,*/}*.{webp}',
            'fonts/*'
          ]
        }, {
          expand: true,
          cwd: '.tmp/images',
          dest: '<%= yeoman.dist %>/images',
          src: ['generated/*']
        }]
      },
      styles: {
        expand: true,
        cwd: '<%= yeoman.app %>/styles',
        dest: '.tmp/styles/',
        src: '{,*/}*.css'
      }
    },

    // Run some tasks in parallel to speed up the build process
    concurrent: {
      server: [
        'compass:server'
      ],
      test: [
        'compass'
      ],
      dist: [
        'compass:dist',
        'imagemin',
        'svgmin'
      ]
    },

    // Test settings
    karma: {
      unit: {
        configFile: 'karma.conf.js',
        singleRun: true
      }
    }
  });

  grunt.registerTask('serve', 'Compile then start a connect web server', function (target) {
    if (target === 'dist') {
      return grunt.task.run(['build', 'connect:dist:keepalive']);
    }

    grunt.task.run([
      'clean:server',
      'wiredep',
      'concurrent:server',
      'autoprefixer',
      'connect:livereload',
      'watch'
    ]);
  });

  grunt.registerTask('server', 'DEPRECATED TASK. Use the "serve" task instead', function (target) {
    grunt.log.warn('The `server` task has been deprecated. Use `grunt serve` to start a server.');
    grunt.task.run(['serve:' + target]);
  });

  grunt.registerTask('test', [
    'clean:server',
    'concurrent:test',
    'autoprefixer',
    'connect:test',
    'karma'
  ]);

  grunt.registerTask('build', [
    'clean:dist',
    'wiredep',
    'useminPrepare',
    'concurrent:dist',
    'autoprefixer',
    'concat',
    'ngmin',
    'copy:dist',
    'cdnify',
    'cssmin',
    'uglify',
    'filerev',
    'usemin',
    'htmlmin'
  ]);

  grunt.registerTask('default', [
    'newer:jshint',
    'test',
    'build'
  ]);
};
<commit_msg>grunt watch watches new all js files not only in  dir<commit_after>// Generated on 2014-06-01 using generator-angular 0.8.0
'use strict';

// # Globbing
// for performance reasons we're only matching one level down:
// 'test/spec/{,*/}*.js'
// use this if you want to recursively match all subfolders:
// 'test/spec/**/*.js'

module.exports = function (grunt) {

  // Load grunt tasks automatically
  require('load-grunt-tasks')(grunt);

  // Time how long tasks take. Can help when optimizing build times
  require('time-grunt')(grunt);

  // Define the configuration for all the tasks
  grunt.initConfig({

    // Project settings
    yeoman: {
      // configurable paths
      app: require('./bower.json').appPath || 'app',
      dist: 'dist'
    },

    // Watches files for changes and runs tasks based on the changed files
    watch: {
      bower: {
        files: ['bower.json'],
        tasks: ['wiredep']
      },
      js: {
        files: ['<%= yeoman.app %>/{,*/}*.js'],
        tasks: ['newer:jshint:all'],
        options: {
          livereload: '<%= connect.options.livereload %>'
        }
      },
      jsTest: {
        files: ['test/spec/{,*/}*.js'],
        tasks: ['newer:jshint:test', 'karma']
      },
      compass: {
        files: ['<%= yeoman.app %>/styles/{,*/}*.{scss,sass}'],
        tasks: ['compass:server', 'autoprefixer']
      },
      gruntfile: {
        files: ['Gruntfile.js']
      },
      livereload: {
        options: {
          livereload: '<%= connect.options.livereload %>'
        },
        files: [
          '<%= yeoman.app %>/{,*/}*.html',
          '.tmp/styles/{,*/}*.css',
          '<%= yeoman.app %>/images/{,*/}*.{png,jpg,jpeg,gif,webp,svg}'
        ]
      }
    },

    // The actual grunt server settings
    connect: {
      options: {
        port: 9000,
        // Change this to '0.0.0.0' to access the server from outside.
        hostname: 'localhost',
        livereload: 35729
      },
      livereload: {
        options: {
          open: true,
          base: [
            '.tmp',
            '<%= yeoman.app %>'
          ]
        }
      },
      test: {
        options: {
          port: 9001,
          base: [
            '.tmp',
            'test',
            '<%= yeoman.app %>'
          ]
        }
      },
      dist: {
        options: {
          open: true,
          base: '<%= yeoman.dist %>'
        }
      }
    },

    // Make sure code styles are up to par and there are no obvious mistakes
    jshint: {
      options: {
        jshintrc: '.jshintrc',
        reporter: require('jshint-stylish')
      },
      all: {
        src: [
          'Gruntfile.js',
          '<%= yeoman.app %>/scripts/{,*/}*.js'
        ]
      },
      test: {
        options: {
          jshintrc: 'test/.jshintrc'
        },
        src: ['test/spec/{,*/}*.js']
      }
    },

    // Empties folders to start fresh
    clean: {
      dist: {
        files: [{
          dot: true,
          src: [
            '.tmp',
            '.sass-cache',
            '<%= yeoman.dist %>/{,*/}*',
            '!<%= yeoman.dist %>/.git*'
          ]
        }]
      },
      server: '.tmp'
    },

    // Add vendor prefixed styles
    autoprefixer: {
      options: {
        browsers: ['last 1 version']
      },
      dist: {
        files: [{
          expand: true,
          cwd: '.tmp/styles/',
          src: '{,*/}*.css',
          dest: '.tmp/styles/'
        }]
      }
    },

    // Automatically inject Bower components into the app
    wiredep: {
      app: {
        src: ['<%= yeoman.app %>/index.html'],
        ignorePath: new RegExp('^<%= yeoman.app %>/')
      },
      sass: {
        src: ['<%= yeoman.app %>/styles/{,*/}*.{scss,sass}'],
        ignorePath: '<%= yeoman.app %>/bower_components/'
      }
    },

    // Compiles Sass to CSS and generates necessary files if requested
    compass: {
      options: {
        sassDir: '<%= yeoman.app %>/styles',
        cssDir: '.tmp/styles',
        generatedImagesDir: '.tmp/images/generated',
        imagesDir: '<%= yeoman.app %>/images',
        javascriptsDir: '<%= yeoman.app %>/scripts',
        fontsDir: '<%= yeoman.app %>/styles/fonts',
        importPath: '<%= yeoman.app %>/bower_components',
        httpImagesPath: '/images',
        httpGeneratedImagesPath: '/images/generated',
        httpFontsPath: '/styles/fonts',
        relativeAssets: false,
        assetCacheBuster: false,
        raw: 'Sass::Script::Number.precision = 10\n'
      },
      dist: {
        options: {
          generatedImagesDir: '<%= yeoman.dist %>/images/generated'
        }
      },
      server: {
        options: {
          debugInfo: true
        }
      }
    },

    // Renames files for browser caching purposes
    filerev: {
      dist: {
        src: [
          '<%= yeoman.dist %>/scripts/{,*/}*.js',
          '<%= yeoman.dist %>/styles/{,*/}*.css',
          '<%= yeoman.dist %>/images/{,*/}*.{png,jpg,jpeg,gif,webp,svg}',
          '<%= yeoman.dist %>/styles/fonts/*'
        ]
      }
    },

    // Reads HTML for usemin blocks to enable smart builds that automatically
    // concat, minify and revision files. Creates configurations in memory so
    // additional tasks can operate on them
    useminPrepare: {
      html: '<%= yeoman.app %>/index.html',
      options: {
        dest: '<%= yeoman.dist %>',
        flow: {
          html: {
            steps: {
              js: ['concat', 'uglifyjs'],
              css: ['cssmin']
            },
            post: {}
          }
        }
      }
    },

    // Performs rewrites based on filerev and the useminPrepare configuration
    usemin: {
      html: ['<%= yeoman.dist %>/{,*/}*.html'],
      css: ['<%= yeoman.dist %>/styles/{,*/}*.css'],
      options: {
        assetsDirs: ['<%= yeoman.dist %>','<%= yeoman.dist %>/images']
      }
    },

    // The following *-min tasks will produce minified files in the dist folder
    // By default, your `index.html`'s <!-- Usemin block --> will take care of
    // minification. These next options are pre-configured if you do not wish
    // to use the Usemin blocks.
    // cssmin: {
    //   dist: {
    //     files: {
    //       '<%= yeoman.dist %>/styles/main.css': [
    //         '.tmp/styles/{,*/}*.css'
    //       ]
    //     }
    //   }
    // },
    // uglify: {
    //   dist: {
    //     files: {
    //       '<%= yeoman.dist %>/scripts/scripts.js': [
    //         '<%= yeoman.dist %>/scripts/scripts.js'
    //       ]
    //     }
    //   }
    // },
    // concat: {
    //   dist: {}
    // },

    imagemin: {
      dist: {
        files: [{
          expand: true,
          cwd: '<%= yeoman.app %>/images',
          src: '{,*/}*.{png,jpg,jpeg,gif}',
          dest: '<%= yeoman.dist %>/images'
        }]
      }
    },

    svgmin: {
      dist: {
        files: [{
          expand: true,
          cwd: '<%= yeoman.app %>/images',
          src: '{,*/}*.svg',
          dest: '<%= yeoman.dist %>/images'
        }]
      }
    },

    htmlmin: {
      dist: {
        options: {
          collapseWhitespace: true,
          conservativeCollapse: true,
          collapseBooleanAttributes: true,
          removeCommentsFromCDATA: true,
          removeOptionalTags: true
        },
        files: [{
          expand: true,
          cwd: '<%= yeoman.dist %>',
          src: ['*.html', 'views/{,*/}*.html'],
          dest: '<%= yeoman.dist %>'
        }]
      }
    },

    // ngmin tries to make the code safe for minification automatically by
    // using the Angular long form for dependency injection. It doesn't work on
    // things like resolve or inject so those have to be done manually.
    ngmin: {
      dist: {
        files: [{
          expand: true,
          cwd: '.tmp/concat/scripts',
          src: '*.js',
          dest: '.tmp/concat/scripts'
        }]
      }
    },

    // Replace Google CDN references
    cdnify: {
      dist: {
        html: ['<%= yeoman.dist %>/*.html']
      }
    },

    // Copies remaining files to places other tasks can use
    copy: {
      dist: {
        files: [{
          expand: true,
          dot: true,
          cwd: '<%= yeoman.app %>',
          dest: '<%= yeoman.dist %>',
          src: [
            '*.{ico,png,txt}',
            '.htaccess',
            'config.json',
            'heatmap.json',
            '*.html',
            'views/{,*/}*.html',
            'images/{,*/}*.{webp}',
            'fonts/*'
          ]
        }, {
          expand: true,
          cwd: '.tmp/images',
          dest: '<%= yeoman.dist %>/images',
          src: ['generated/*']
        }]
      },
      styles: {
        expand: true,
        cwd: '<%= yeoman.app %>/styles',
        dest: '.tmp/styles/',
        src: '{,*/}*.css'
      }
    },

    // Run some tasks in parallel to speed up the build process
    concurrent: {
      server: [
        'compass:server'
      ],
      test: [
        'compass'
      ],
      dist: [
        'compass:dist',
        'imagemin',
        'svgmin'
      ]
    },

    // Test settings
    karma: {
      unit: {
        configFile: 'karma.conf.js',
        singleRun: true
      }
    }
  });

  grunt.registerTask('serve', 'Compile then start a connect web server', function (target) {
    if (target === 'dist') {
      return grunt.task.run(['build', 'connect:dist:keepalive']);
    }

    grunt.task.run([
      'clean:server',
      'wiredep',
      'concurrent:server',
      'autoprefixer',
      'connect:livereload',
      'watch'
    ]);
  });

  grunt.registerTask('server', 'DEPRECATED TASK. Use the "serve" task instead', function (target) {
    grunt.log.warn('The `server` task has been deprecated. Use `grunt serve` to start a server.');
    grunt.task.run(['serve:' + target]);
  });

  grunt.registerTask('test', [
    'clean:server',
    'concurrent:test',
    'autoprefixer',
    'connect:test',
    'karma'
  ]);

  grunt.registerTask('build', [
    'clean:dist',
    'wiredep',
    'useminPrepare',
    'concurrent:dist',
    'autoprefixer',
    'concat',
    'ngmin',
    'copy:dist',
    'cdnify',
    'cssmin',
    'uglify',
    'filerev',
    'usemin',
    'htmlmin'
  ]);

  grunt.registerTask('default', [
    'newer:jshint',
    'test',
    'build'
  ]);
};
<|endoftext|>