<commit_before>/*global $*/

// Global namespace for the app
var app = {};

// Expose it for introspection
window.app = app;

// Set up pointers to useful elements
app.hooks = {};

$('[data-hook]').each(function (i, el) {
  var dataHook = $(el).data('hook'),
      // Get _all_ elements for a data-hook value, not just the single element
      // in the current iteration. Supports multiple elements with the same
      // data-hook value.
      $el = $('[data-hook="' + dataHook + '"]');
  // Convert hyphen-names to camelCase in hooks
  var hook = dataHook.replace(/-([a-z])/g, function (m) {
    return m[1].toUpperCase();
  });
  app.hooks[hook] = $el;
});

// pushState on search submit
app.hooks.searchForm.on('submit', function (e) {
  if (e.ctrlKey || e.altKey || e.shiftKey) return;
  e.preventDefault();

  var params = app.util.serializeObject(this),
      queryStringParams = app.util.serializeQueryStringParams(params);

  if (params) {
    $(this).find('input').blur();
    history.pushState(null, params, '?' + queryStringParams);
    window.scroll(0, 0);
    app.hooks.results.addClass('hide');
    app.route();
  }
});

app.hooks.searchForm.find('input').on('input', function (e) {
  if (e.ctrlKey || e.altKey || e.shiftKey) return;
  e.preventDefault();

  if (!this.value) {
    app.hooks.results.addClass('hide');
  }
});

// global settings
app.settings = {
  ajaxType: $.support.cors ? 'json' : 'jsonp'
};

// global variables
app.globals = {};

// Routing
app.route = function () {
  var params = $.deparam(window.location.search.substr(1));

  if (params.a) {
    app.hooks.notices.text('Loading...');
    app.data.getStandardizedAddress(app.util.cleanPropertyQuery(params.a));
  } else {
    app.hooks.searchForm.get(0).reset();
    app.hooks.results.addClass('hide');
    app.hooks.notices.empty();
  }
};

// Route on page load and back button
$(app.route);
window.onpopstate = app.route;

// Shims to gracefully degrade pushState and replaceState for IE9
if (!history.pushState) {
  history.pushState = function (s, t, l) {window.location = l};
  history.replaceState = function (s, t, l) {
    if (l) window.location = l;
    else history.state = s;
  };
}

app.data = {};

app.data.getStandardizedAddress = function(address) {

  $.ajax('https://api.phila.gov/ulrs/v3/addresses/' + encodeURIComponent(address) + '?format=json',
    {dataType: app.settings.ajaxType})
    .done(function (data) {
      var standardizedAddress;
      if (data.addresses.length > 0) {
        standardizedAddress = data.addresses[0].standardizedAddress;
        app.data.getServiceAreas(standardizedAddress);
      } else {
        app.hooks.notices.text('No address was found.');
      }
    })
    .fail(function () {
      app.hooks.notices.text('No address was found.');
    });
};

app.data.getServiceAreas = function(standardizedAddress, success) {

  $.ajax('https://data.phila.gov/resource/bz79-67af.json?address_id=' + encodeURIComponent(standardizedAddress),
      {dataType: app.settings.ajaxType})
    .done(function (data) {
      var serviceAreas = data.length > 0 ? data[0] : null;
      var titleCaseAddress = app.util.toTitleCase(standardizedAddress);

      if (serviceAreas) {
        app.hooks.searchForm.find('input').val(titleCaseAddress);
        app.hooks.notices.empty();

        app.hooks.trashDay.text(app.util.abbrevToFullDay(serviceAreas.rubbish));
        app.hooks.address.text(titleCaseAddress);
        app.hooks.propertyLink.attr('href', 'https://alpha.phila.gov/property/?a=' +
          encodeURIComponent(standardizedAddress) + '&u=');

        app.hooks.results.removeClass('hide');
      } else {
        app.hooks.notices.text('The trash & recycling collection day was not found.');
      }
    })
    .fail(function () {
      app.hooks.notices.text('The trash & recycling collection day was not found.');
    });
};

// App utilties
app.util = {};

app.util.toTitleCase = function(str) {
  return str.replace(/\w\S*/g, function(txt) {
    return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
  });
};

app.util.cleanPropertyQuery = function(query) {
  if (!query) {
    return '';
  }

  // Trim, remove extra speces, and replace dots and hashes -- API can't handle them
  return query.replace(/\./g, ' ').replace(/ {2,}/g, ' ').replace(/#/g, '').trim().toUpperCase();
};

app.util.abbrevToFullDay = function(abbrev) {
  switch(abbrev) {
    case 'SUN': return 'Sundays';
    case 'MON': return 'Mondays';
    case 'TUE': return 'Tuesdays';
    case 'WED': return 'Wednesdays';
    case 'THU': return 'Thursdays';
    case 'FRI': return 'Fridays';
    case 'SAT': return 'Saturdays';
  }

  return abbrev;
};

// Serialize a form into an object, assuming only one level of depth
app.util.serializeObject = function (form) {
  var obj = {};
  $.each($(form).serializeArray(), function (i, element) {
      if (!obj[element.name]) {
        obj[element.name] = element.value;
      }
    });
  return obj;
};

// Serialize an object to query string params
app.util.serializeQueryStringParams = function(obj) {
  var str = [];
  for(var p in obj) {
    if (obj.hasOwnProperty(p)) {
      str.push(encodeURIComponent(p) + '=' + encodeURIComponent(obj[p]));
    }
  }
  return str.join('&');
};

<commit_msg>Update to work with AIS instead of ULRS and Socrata<commit_after>/*global $*/

// Global namespace for the app
var app = {};

// Expose it for introspection
window.app = app;

// Set up pointers to useful elements
app.hooks = {};

$('[data-hook]').each(function (i, el) {
  var dataHook = $(el).data('hook'),
      // Get _all_ elements for a data-hook value, not just the single element
      // in the current iteration. Supports multiple elements with the same
      // data-hook value.
      $el = $('[data-hook="' + dataHook + '"]');
  // Convert hyphen-names to camelCase in hooks
  var hook = dataHook.replace(/-([a-z])/g, function (m) {
    return m[1].toUpperCase();
  });
  app.hooks[hook] = $el;
});

// pushState on search submit
app.hooks.searchForm.on('submit', function (e) {
  if (e.ctrlKey || e.altKey || e.shiftKey) return;
  e.preventDefault();

  var params = app.util.serializeObject(this),
      queryStringParams = app.util.serializeQueryStringParams(params);

  if (params) {
    $(this).find('input').blur();
    history.pushState(null, params, '?' + queryStringParams);
    window.scroll(0, 0);
    app.hooks.results.addClass('hide');
    app.route();
  }
});

app.hooks.searchForm.find('input').on('input', function (e) {
  if (e.ctrlKey || e.altKey || e.shiftKey) return;
  e.preventDefault();

  if (!this.value) {
    app.hooks.results.addClass('hide');
  }
});

// global settings
app.settings = {
  ajaxType: $.support.cors ? 'json' : 'jsonp'
};

// global variables
app.globals = {};

// Routing
app.route = function () {
  var params = $.deparam(window.location.search.substr(1));

  if (params.a) {
    app.hooks.notices.text('Loading...');
    app.data.getStandardizedAddress(app.util.cleanPropertyQuery(params.a));
  } else {
    app.hooks.searchForm.get(0).reset();
    app.hooks.results.addClass('hide');
    app.hooks.notices.empty();
  }
};

// Route on page load and back button
$(app.route);
window.onpopstate = app.route;

// Shims to gracefully degrade pushState and replaceState for IE9
if (!history.pushState) {
  history.pushState = function (s, t, l) {window.location = l};
  history.replaceState = function (s, t, l) {
    if (l) window.location = l;
    else history.state = s;
  };
}

app.data = {};

app.data.getStandardizedAddress = function(address) {

  $.ajax('https://api.phila.gov/ais/v1/addresses/' + encodeURIComponent(address),
    {dataType: app.settings.ajaxType})
    .done(function (data) {
      var addressInfo;
      if (data.features && data.features.length > 0) {
        addressInfo = data.features[0].properties;
        app.data.renderAddressInfo(addressInfo);
      } else {
        app.hooks.notices.text('No address was found.');
      }
    })
    .fail(function () {
      app.hooks.notices.text('No address was found.');
    });
};

app.data.renderAddressInfo = function(addressInfo) {
  var titleCaseAddress = app.util.toTitleCase(addressInfo.street_address);
  var fullRubbishDay = app.util.abbrevToFullDay(addressInfo.rubbish_recycle_day);

  app.hooks.searchForm.find('input').val(titleCaseAddress);
  app.hooks.notices.empty();

  app.hooks.trashDay.text(fullRubbishDay);
  app.hooks.address.text(titleCaseAddress);
  app.hooks.propertyLink.attr('href', 'https://alpha.phila.gov/property/?a=' +
    encodeURIComponent(addressInfo.street_address) + '&u=');

  app.hooks.results.removeClass('hide');
};

// App utilties
app.util = {};

app.util.toTitleCase = function(str) {
  return str.replace(/\w\S*/g, function(txt) {
    return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
  });
};

app.util.cleanPropertyQuery = function(query) {
  if (!query) {
    return '';
  }

  // Trim, remove extra speces, and replace dots and hashes -- API can't handle them
  return query.replace(/\./g, ' ').replace(/ {2,}/g, ' ').replace(/#/g, '').trim().toUpperCase();
};

app.util.abbrevToFullDay = function(abbrev) {
  switch(abbrev) {
    case 'SUN': return 'Sundays';
    case 'MON': return 'Mondays';
    case 'TUE': return 'Tuesdays';
    case 'WED': return 'Wednesdays';
    case 'THU': return 'Thursdays';
    case 'FRI': return 'Fridays';
    case 'SAT': return 'Saturdays';
  }

  return abbrev;
};

// Serialize a form into an object, assuming only one level of depth
app.util.serializeObject = function (form) {
  var obj = {};
  $.each($(form).serializeArray(), function (i, element) {
      if (!obj[element.name]) {
        obj[element.name] = element.value;
      }
    });
  return obj;
};

// Serialize an object to query string params
app.util.serializeQueryStringParams = function(obj) {
  var str = [];
  for(var p in obj) {
    if (obj.hasOwnProperty(p)) {
      str.push(encodeURIComponent(p) + '=' + encodeURIComponent(obj[p]));
    }
  }
  return str.join('&');
};

<|endoftext|>


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

  var rose = document.getElementById("rose");
  var positionCurrent = {
    lat: null,
    lng: null,
    hng: null
  };

  var debug = false;

  var positionLat = document.getElementById("position-lat");
  var positionLng = document.getElementById("position-lng");
  var positionHng = document.getElementById("position-hng");
  var debugOrientation = document.getElementById("debug-orientation");
  var debugOrientationDefault = document.getElementById("debug-orientation-default");

  var popupButtons = document.querySelectorAll(".btn-popup");
  var popup = document.getElementById("popup");
  var popupContents = document.getElementById("popup-contents");
  var popupInners = document.querySelectorAll(".poppup__iner");

  var btnLockOrientation = document.getElementById("btn-lock-orientation");
  var btnNightmode = document.getElementById("btn-nightmode");
  var btnMap = document.getElementById("btn-map");
  var btnInfo = document.getElementById("btn-info");

  var orientationWarningShown = false;

  var headingPrevious = 0;
  var rotations = 0;
  var isOrientationLockable = false;
  var isOrientationLocked;
  var isNightMode;

  var defaultOrientation;

  function getBrowserOrientation() {
    var orientation;
    if (screen.orientation && screen.orientation.type) {
      orientation = screen.orientation.type;
    } else {
      orientation = screen.orientation ||
                    screen.mozOrientation ||
                    screen.msOrientation;
    }

    return orientation;
  }

  function browserUnlockOrientation() {
    if (screen.orientation && screen.orientation.unlock) {
      screen.orientation.unlock();
    } else if (screen.unlockOrientation) {
      screen.unlockOrientation();
    } else if (screen.mozUnlockOrientation) {
      screen.mozUnlockOrientation();
    } else if (screen.msUnlockOrientation) {
      screen.msUnlockOrientation();
    }
  }

  function getBrowserFullscreenElement() {
    if (typeof document.fullscreenElement !== "undefined") {
      return document.fullscreenElement;
    } else if (typeof document.webkitFullscreenElement !== "undefined") {
      return document.webkitFullscreenElement;
    } else if (typeof document.mozFullScreenElement !== "undefined") {
      return document.mozFullScreenElement;
    } else if (typeof document.msFullscreenElement !== "undefined") {
      return document.msFullscreenElement;
    }
  }

  function browserRequestFullscreen() {
    if (document.documentElement.requestFullscreen) {
      document.documentElement.requestFullscreen();
    } else if (document.documentElement.webkitRequestFullscreen) {
      document.documentElement.webkitRequestFullscreen();
    } else if (document.documentElement.mozRequestFullScreen) {
      document.documentElement.mozRequestFullScreen();
    } else if (document.documentElement.msRequestFullscreen) {
      document.documentElement.msRequestFullscreen();
    }
  }

  function browserExitFullscreen() {
    if (document.exitFullscreen) {
      document.exitFullscreen();
    } else if (document.webkitExitFullscreen) {
      document.webkitExitFullscreen();
    } else if (document.mozCancelFullScreen) {
      document.mozCancelFullScreen();
    } else if (document.msExitFullscreen) {
      document.msExitFullscreen();
    }
  }

  function onOrientationChange(event) {
    var heading = event.alpha;
    var orientation = getBrowserOrientation();

    if (typeof heading !== "undefined" && typeof orientation !== "undefined") {
      var diff = Math.abs(heading - headingPrevious);

      if(diff > 300) {
        if(heading - headingPrevious < 0) {
          rotations++;
        } else {
          rotations--;
        }
      }

      if (debug) {
        debugOrientation.textContent = orientation;
      }

      var adjustment = 0;
      var currentOrientation = orientation.split("-");

      if (defaultOrientation === "landscape") {
        adjustment -= 90;
      }

      if (defaultOrientation !== currentOrientation[0]) {
        if (defaultOrientation === "landscape") {
          adjustment -= 270;
        } else {
          adjustment -= 90;
        }
      }

      if (currentOrientation[1] === "secondary") {
        adjustment -= 180;
      }

      headingPrevious = heading;

      positionCurrent.hng = heading + adjustment;

      var phase = positionCurrent.hng < 0 ? 360 + positionCurrent.hng : positionCurrent.hng;
      positionHng.textContent = (360 - phase | 0) + "°";

      if (typeof rose.style.transform !== "undefined") {
        rose.style.transform = "rotateZ(" + (positionCurrent.hng + rotations*360) + "deg)";
      } else if (typeof rose.style.webkitTransform !== "undefined") {
        rose.style.webkitTransform = "rotateZ(" + (positionCurrent.hng + rotations*360) + "deg)";
      }
    } else {
      positionHng.textContent = "n/a";
      showOrientationWarning();
    }
  }

  function showOrientationWarning() {
    if (!orientationWarningShown) {
      popupOpen("noorientation");
      orientationWarningShown = true;
    }
  }

  function onFullscreenChange() {
    if (isOrientationLockable && getBrowserFullscreenElement()) {
      if (screen.orientation && screen.orientation.lock) {
        screen.orientation.lock(getBrowserOrientation()).then(function () {
        }).catch(function () {
        });
      }
    } else {
      lockOrientationRequest(false);
    }
  }

  function toggleOrientationLockable(lockable) {
    isOrientationLockable = lockable;

    if (isOrientationLockable) {
      btnLockOrientation.classList.remove("btn--hide");

      btnNightmode.classList.add("column-25");
      btnNightmode.classList.remove("column-33");
      btnMap.classList.add("column-25");
      btnMap.classList.remove("column-33");
      btnInfo.classList.add("column-25");
      btnInfo.classList.remove("column-33");
    } else {
      btnLockOrientation.classList.add("btn--hide");

      btnNightmode.classList.add("column-33");
      btnNightmode.classList.remove("column-25");
      btnMap.classList.add("column-33");
      btnMap.classList.remove("column-25");
      btnInfo.classList.add("column-33");
      btnInfo.classList.remove("column-25");
    }
  }

  function checkLockable() {
    if (screen.orientation && screen.orientation.lock) {
      screen.orientation.lock(getBrowserOrientation()).then(function () {
        toggleOrientationLockable(true);
        browserUnlockOrientation();
      }).catch(function (event) {
        if (event.code === 18) { // The page needs to be fullscreen in order to call lockOrientation(), but is lockable
          toggleOrientationLockable(true);
        } else {  // lockOrientation() is not available on this device (or other error)
          toggleOrientationLockable(false);
        }
      });
    } else {
      toggleOrientationLockable(false);
    }
  }

  function lockOrientationRequest(doLock) {
    if (isOrientationLockable) {
      if (doLock) {
        browserRequestFullscreen();
        lockOrientation(true);
      } else {
        browserUnlockOrientation();
        browserExitFullscreen();
        lockOrientation(false);
      }
    }
  }

  function lockOrientation(locked) {
    if (locked) {
      btnLockOrientation.classList.add("active");
    } else {
      btnLockOrientation.classList.remove("active");
    }

    isOrientationLocked = locked;
  }

  function toggleOrientationLock() {
    if (isOrientationLockable) {
      lockOrientationRequest(!isOrientationLocked);
    }
  }

  function locationUpdate(position) {
    positionCurrent.lat = position.coords.latitude;
    positionCurrent.lng = position.coords.longitude;

    positionLat.textContent = decimalToSexagesimal(positionCurrent.lat, "lat");
    positionLng.textContent = decimalToSexagesimal(positionCurrent.lng, "lng");
  }

  function locationUpdateFail(error) {
    positionLat.textContent = "n/a";
    positionLng.textContent = "n/a";
    console.log("location fail: ", error);
  }

  function setNightmode(on) {

    if (on) {
      btnNightmode.classList.add("active");
    } else {
      btnNightmode.classList.remove("active");
    }

    window.setTimeout(function() {
      if (on) {
        document.documentElement.classList.add("nightmode");
      } else {
        document.documentElement.classList.remove("nightmode");
      }
    }, 1);

    isNightMode = on;
  }

  function toggleNightmode() {
    setNightmode(!isNightMode);
  }

  function openMap() {
    window.open("https://www.google.com/maps/place/@" + positionCurrent.lat + "," + positionCurrent.lng + ",16z", "_blank");
  }

  function popupOpenFromClick(event) {
    popupOpen(event.currentTarget.dataset.name);
  }

  function popupOpen(name) {
    var i;
    for (i=0; i<popupInners.length; i++) {
      popupInners[i].classList.add("popup__inner--hide");
    }
    document.getElementById("popup-inner-" + name).classList.remove("popup__inner--hide");

    popup.classList.add("popup--show");
  }

  function popupClose() {
    popup.classList.remove("popup--show");
  }

  function popupContentsClick(event) {
    event.stopPropagation();
  }

  function decimalToSexagesimal(decimal, type) {
    var degrees = decimal | 0;
    var fraction = Math.abs(decimal - degrees);
    var minutes = (fraction * 60) | 0;
    var seconds = (fraction * 3600 - minutes * 60) | 0;

    var direction = "";
    var positive = degrees > 0;
    degrees = Math.abs(degrees);
    switch (type) {
      case "lat":
        direction = positive ? "N" : "S";
        break;
      case "lng":
        direction = positive ? "E" : "W";
        break;
    }

    return degrees + "° " + minutes + "' " + seconds + "\" " + direction;
  }

  if (screen.width > screen.height) {
    defaultOrientation = "landscape";
  } else {
    defaultOrientation = "portrait";
  }
  if (debug) {
    debugOrientationDefault.textContent = defaultOrientation;
  }

  window.addEventListener("deviceorientation", onOrientationChange);

  document.addEventListener("fullscreenchange", onFullscreenChange);
  document.addEventListener("webkitfullscreenchange", onFullscreenChange);
  document.addEventListener("mozfullscreenchange", onFullscreenChange);
  document.addEventListener("MSFullscreenChange", onFullscreenChange);

  btnLockOrientation.addEventListener("click", toggleOrientationLock);
  btnNightmode.addEventListener("click", toggleNightmode);
  btnMap.addEventListener("click", openMap);

  var i;
  for (i=0; i<popupButtons.length; i++) {
    popupButtons[i].addEventListener("click", popupOpenFromClick);
  }

  popup.addEventListener("click", popupClose);
  popupContents.addEventListener("click", popupContentsClick);

  navigator.geolocation.watchPosition(locationUpdate, locationUpdateFail, {
    enableHighAccuracy: false,
    maximumAge: 30000,
    timeout: 27000
  });

  setNightmode(false);
  checkLockable();

}());
<commit_msg>orientation check<commit_after>(function () {
  "use strict";

  var rose = document.getElementById("rose");
  var positionCurrent = {
    lat: null,
    lng: null,
    hng: null
  };

  var debug = false;

  var positionLat = document.getElementById("position-lat");
  var positionLng = document.getElementById("position-lng");
  var positionHng = document.getElementById("position-hng");
  var debugOrientation = document.getElementById("debug-orientation");
  var debugOrientationDefault = document.getElementById("debug-orientation-default");

  var popupButtons = document.querySelectorAll(".btn-popup");
  var popup = document.getElementById("popup");
  var popupContents = document.getElementById("popup-contents");
  var popupInners = document.querySelectorAll(".poppup__iner");

  var btnLockOrientation = document.getElementById("btn-lock-orientation");
  var btnNightmode = document.getElementById("btn-nightmode");
  var btnMap = document.getElementById("btn-map");
  var btnInfo = document.getElementById("btn-info");

  var orientationWarningShown = false;

  var headingPrevious = 0;
  var rotations = 0;
  var isOrientationLockable = false;
  var isOrientationLocked;
  var isNightMode;

  var defaultOrientation;

  function getBrowserOrientation() {
    var orientation;
    if (screen.orientation && screen.orientation.type) {
      orientation = screen.orientation.type;
    } else {
      orientation = screen.orientation ||
                    screen.mozOrientation ||
                    screen.msOrientation;
    }

    return orientation;
  }

  function browserUnlockOrientation() {
    if (screen.orientation && screen.orientation.unlock) {
      screen.orientation.unlock();
    } else if (screen.unlockOrientation) {
      screen.unlockOrientation();
    } else if (screen.mozUnlockOrientation) {
      screen.mozUnlockOrientation();
    } else if (screen.msUnlockOrientation) {
      screen.msUnlockOrientation();
    }
  }

  function getBrowserFullscreenElement() {
    if (typeof document.fullscreenElement !== "undefined") {
      return document.fullscreenElement;
    } else if (typeof document.webkitFullscreenElement !== "undefined") {
      return document.webkitFullscreenElement;
    } else if (typeof document.mozFullScreenElement !== "undefined") {
      return document.mozFullScreenElement;
    } else if (typeof document.msFullscreenElement !== "undefined") {
      return document.msFullscreenElement;
    }
  }

  function browserRequestFullscreen() {
    if (document.documentElement.requestFullscreen) {
      document.documentElement.requestFullscreen();
    } else if (document.documentElement.webkitRequestFullscreen) {
      document.documentElement.webkitRequestFullscreen();
    } else if (document.documentElement.mozRequestFullScreen) {
      document.documentElement.mozRequestFullScreen();
    } else if (document.documentElement.msRequestFullscreen) {
      document.documentElement.msRequestFullscreen();
    }
  }

  function browserExitFullscreen() {
    if (document.exitFullscreen) {
      document.exitFullscreen();
    } else if (document.webkitExitFullscreen) {
      document.webkitExitFullscreen();
    } else if (document.mozCancelFullScreen) {
      document.mozCancelFullScreen();
    } else if (document.msExitFullscreen) {
      document.msExitFullscreen();
    }
  }

  function onOrientationChange(event) {
    var heading = event.alpha;
    var orientation = getBrowserOrientation();

    if (typeof heading !== "undefined" && typeof orientation !== "undefined") {
      var diff = Math.abs(heading - headingPrevious);

      if(diff > 300) {
        if(heading - headingPrevious < 0) {
          rotations++;
        } else {
          rotations--;
        }
      }

      if (debug) {
        debugOrientation.textContent = orientation;
      }

      var adjustment = 0;
      var currentOrientation = orientation.split("-");

      if (defaultOrientation === "landscape") {
        adjustment -= 90;
      }

      if (defaultOrientation !== currentOrientation[0]) {
        if (defaultOrientation === "landscape") {
          adjustment -= 270;
        } else {
          adjustment -= 90;
        }
      }

      if (currentOrientation[1] === "secondary") {
        adjustment -= 180;
      }

      headingPrevious = heading;

      positionCurrent.hng = heading + adjustment;

      var phase = positionCurrent.hng < 0 ? 360 + positionCurrent.hng : positionCurrent.hng;
      positionHng.textContent = (360 - phase | 0) + "°";

      if (typeof rose.style.transform !== "undefined") {
        rose.style.transform = "rotateZ(" + (positionCurrent.hng + rotations*360) + "deg)";
      } else if (typeof rose.style.webkitTransform !== "undefined") {
        rose.style.webkitTransform = "rotateZ(" + (positionCurrent.hng + rotations*360) + "deg)";
      }
    } else {
      positionHng.textContent = "n/a";
      showOrientationWarning();
    }
  }

  function showOrientationWarning() {
    if (!orientationWarningShown) {
      popupOpen("noorientation");
      orientationWarningShown = true;
    }
  }

  function onFullscreenChange() {
    if (isOrientationLockable && getBrowserFullscreenElement()) {
      if (screen.orientation && screen.orientation.lock) {
        screen.orientation.lock(getBrowserOrientation()).then(function () {
        }).catch(function () {
        });
      }
    } else {
      lockOrientationRequest(false);
    }
  }

  function toggleOrientationLockable(lockable) {
    isOrientationLockable = lockable;

    if (isOrientationLockable) {
      btnLockOrientation.classList.remove("btn--hide");

      btnNightmode.classList.add("column-25");
      btnNightmode.classList.remove("column-33");
      btnMap.classList.add("column-25");
      btnMap.classList.remove("column-33");
      btnInfo.classList.add("column-25");
      btnInfo.classList.remove("column-33");
    } else {
      btnLockOrientation.classList.add("btn--hide");

      btnNightmode.classList.add("column-33");
      btnNightmode.classList.remove("column-25");
      btnMap.classList.add("column-33");
      btnMap.classList.remove("column-25");
      btnInfo.classList.add("column-33");
      btnInfo.classList.remove("column-25");
    }
  }

  function checkLockable() {
    if (screen.orientation && screen.orientation.lock) {
      screen.orientation.lock(getBrowserOrientation()).then(function () {
        toggleOrientationLockable(true);
        browserUnlockOrientation(); //needed as chrome was locking orientation (even if not in fullscreen, bug??)
      }).catch(function (event) {
        if (event.code === 18) { // The page needs to be fullscreen in order to call lockOrientation(), but is lockable
          toggleOrientationLockable(true);
        } else {  // lockOrientation() is not available on this device (or other error)
          toggleOrientationLockable(false);
        }
      });
    } else {
      toggleOrientationLockable(false);
    }
  }

  function lockOrientationRequest(doLock) {
    if (isOrientationLockable) {
      if (doLock) {
        browserRequestFullscreen();
        lockOrientation(true);
      } else {
        browserUnlockOrientation();
        browserExitFullscreen();
        lockOrientation(false);
      }
    }
  }

  function lockOrientation(locked) {
    if (locked) {
      btnLockOrientation.classList.add("active");
    } else {
      btnLockOrientation.classList.remove("active");
    }

    isOrientationLocked = locked;
  }

  function toggleOrientationLock() {
    if (isOrientationLockable) {
      lockOrientationRequest(!isOrientationLocked);
    }
  }

  function locationUpdate(position) {
    positionCurrent.lat = position.coords.latitude;
    positionCurrent.lng = position.coords.longitude;

    positionLat.textContent = decimalToSexagesimal(positionCurrent.lat, "lat");
    positionLng.textContent = decimalToSexagesimal(positionCurrent.lng, "lng");
  }

  function locationUpdateFail(error) {
    positionLat.textContent = "n/a";
    positionLng.textContent = "n/a";
    console.log("location fail: ", error);
  }

  function setNightmode(on) {

    if (on) {
      btnNightmode.classList.add("active");
    } else {
      btnNightmode.classList.remove("active");
    }

    window.setTimeout(function() {
      if (on) {
        document.documentElement.classList.add("nightmode");
      } else {
        document.documentElement.classList.remove("nightmode");
      }
    }, 1);

    isNightMode = on;
  }

  function toggleNightmode() {
    setNightmode(!isNightMode);
  }

  function openMap() {
    window.open("https://www.google.com/maps/place/@" + positionCurrent.lat + "," + positionCurrent.lng + ",16z", "_blank");
  }

  function popupOpenFromClick(event) {
    popupOpen(event.currentTarget.dataset.name);
  }

  function popupOpen(name) {
    var i;
    for (i=0; i<popupInners.length; i++) {
      popupInners[i].classList.add("popup__inner--hide");
    }
    document.getElementById("popup-inner-" + name).classList.remove("popup__inner--hide");

    popup.classList.add("popup--show");
  }

  function popupClose() {
    popup.classList.remove("popup--show");
  }

  function popupContentsClick(event) {
    event.stopPropagation();
  }

  function decimalToSexagesimal(decimal, type) {
    var degrees = decimal | 0;
    var fraction = Math.abs(decimal - degrees);
    var minutes = (fraction * 60) | 0;
    var seconds = (fraction * 3600 - minutes * 60) | 0;

    var direction = "";
    var positive = degrees > 0;
    degrees = Math.abs(degrees);
    switch (type) {
      case "lat":
        direction = positive ? "N" : "S";
        break;
      case "lng":
        direction = positive ? "E" : "W";
        break;
    }

    return degrees + "° " + minutes + "' " + seconds + "\" " + direction;
  }

  if (screen.width > screen.height) {
    defaultOrientation = "landscape";
  } else {
    defaultOrientation = "portrait";
  }
  if (debug) {
    debugOrientationDefault.textContent = defaultOrientation;
  }

  window.addEventListener("deviceorientation", onOrientationChange);

  document.addEventListener("fullscreenchange", onFullscreenChange);
  document.addEventListener("webkitfullscreenchange", onFullscreenChange);
  document.addEventListener("mozfullscreenchange", onFullscreenChange);
  document.addEventListener("MSFullscreenChange", onFullscreenChange);

  btnLockOrientation.addEventListener("click", toggleOrientationLock);
  btnNightmode.addEventListener("click", toggleNightmode);
  btnMap.addEventListener("click", openMap);

  var i;
  for (i=0; i<popupButtons.length; i++) {
    popupButtons[i].addEventListener("click", popupOpenFromClick);
  }

  popup.addEventListener("click", popupClose);
  popupContents.addEventListener("click", popupContentsClick);

  navigator.geolocation.watchPosition(locationUpdate, locationUpdateFail, {
    enableHighAccuracy: false,
    maximumAge: 30000,
    timeout: 27000
  });

  setNightmode(false);
  checkLockable();

}());
<|endoftext|>


<commit_before>'use strict'

import includes from 'core-js/fn/array/includes'
import find from 'core-js/fn/array/find'
import repeat from 'core-js/fn/string/repeat'
import esprima from 'esprima'
import escodegen from 'escodegen'
import {clone, pick, merge, isString, isNumber} from 'lodash'

export {
  getOptions,
  replaceRefs,
  replaceFunctions,
  coerceVal,
  replaceChildSyntax,
  replaceFirebaseIdentifiers,
  parse
}

const SPECIAL_KEYS = ['.functions', '.refs', '.parent']
const SNAPSHOT_IDENTIFIERS = {
  next: 'newData',
  prev: 'data',
  root: 'root'
}
const IGNORE_IDENTIFIERS = ['auth']

/**
 * Return true if a node contains a CallExpression identifier
 *
 * @param {object} node
 * @param {string} name
 * @returns {boolean}
 */
function hasCallExpressionIdentifier (rootNode, name) {
  let found = false
  function walk (node) {
    if (node.type === 'CallExpression' && node.callee.property.name === name) {
      found = true
    } else {
      Object.values(node).filter(Object.isObject).forEach(walk)
    }
  }
  walk(rootNode)
  return found
}

/**
 * Return the root object name from a MemberExpresion
 *
 * @param {object} node
 * @returns {object}
 */
function getMemberExpressionRootName (memberNode) {
  let name = null
  function walk (node) {
    if (node.type === 'Identifier') {
      name = node.name
    } else {
      for (let [key, child] of Object.entries(node)) {
        if (Object.isObject(child) && !Array.isArray(child) && key !== 'property') {
          walk(child)
        }
      }
    }
  }
  walk(memberNode)
  return name
}

/**
 * Return an escodegen string from an esprima syntax object
 * and remove the trailing ";"
 *
 * @param {object} syntax
 * @returns {string}
 */
function cleanSyntax (syntax) {
  return escodegen.generate(syntax).replace(/;$/, '')
}

/**
 * Expand a ".refs" map of strings to objects
 *
 * @param {object} refs
 */
function expandRefs (refs) {
  for (let [key, ref] of Object.entries(refs)) {
    if (isString(ref)) {
      ref = refs[key] = {value: ref}
    }
    ref.depth = isNumber(ref.depth) ? ++ref.depth : 0
  }
}

/**
 * Expand a ".functions" map of strings to objects
 *
 * @param {object} functions
 */
function expandFunctions (functions) {
  for (let [fnName, fn] of Object.entries(functions)) {
    let syntax
    try {
      syntax = esprima.parse(fnName).body[0]
    } catch(e) {
      throw new Error(`Invalid .function declaration: ${fnName}`)
    }
    if (syntax.type !== 'ExpressionStatement' || syntax.expression.type !== 'CallExpression') {
      throw new Error(`Invalid .function declaration: ${fnName}`)
    }
    syntax.expression.arguments.forEach(arg => {
      if (arg.type !== 'Identifier') {
        throw new Error(`Invalid .function declaration: ${fnName}`)
      }
    })
    if (isString(fn)) {
      fn = functions[fnName] = {
        body: fn,
        name: syntax.expression.callee.name,
        args: syntax.expression.arguments.map(arg => arg.name)
      }
    }
  }
}

/**
 * Get the options for a ruleset — they are recursively merged down.
 *
 * @param {object} rules - will be mutated to remove SPECIAL_KEYS
 * @param {object} [options] - options from the parent ruleset
 * @returns {object}
 */
function getOptions (rules, options={}) {
  // Merge defaults, the previous options,
  // and the current options
  const prev = pick(options, SPECIAL_KEYS)
  const next = pick(rules, SPECIAL_KEYS)
  options = merge({}, {
    '.functions': {},
    '.refs': {}
  }, prev, next)
  const {
    '.functions': functions,
    '.refs': refs,
    '.parent': parent
  } = options
  // Remove the options from the rule set
  SPECIAL_KEYS.forEach(key => delete rules[key])
  // See if there is a parent ruleset
  if (parent) {
    // Check to see if any parent was a $wildcard
    Object.keys(parent).forEach((key) => {
      if (/^\$/.test(key)) {
        refs[key] = 'prev'
      }
    })
  }
  // Refs
  expandRefs(refs)
  // Functions
  expandFunctions(functions)
  // Misc
  options['.parent'] = rules
  return options
}

/**
 * Replace "^REF_NAME" with the necessary snapshot method
 * chains to reach the specified child
 *
 * "REF_NAME" will be replaced with the value of "REF_NAME"
 * in the first matched ".refs" object plus the required
 * numbet of .parent() calls
 *
 * Alternatley, "^REF_NAME(next|prev)" syntax can be used and
 * will override the value of .refs[REF_NAME]
 *
 * @param {string} value
 * @param {object} options
 * @returns {string}
 */
function replaceRefs (value, options) {
  const {'.refs': refs} = options
  for (let [name, ref] of Object.entries(refs)) {
    name = RegExp.escape(name)
    const pattern = new RegExp(`\\^${name}(?:\\((next|prev)\\))?`, 'g')
    value = value.replace(pattern, (match, keyword) => {
      const snapshot = keyword || ref.value
      const parents = repeat('.parent()', ref.depth)
      return match.replace(match, `${snapshot}${parents}`)
    })
  }
  return value
}

/**
 * Replace function () calls inside a rule expression
 *
 * @param {string} value
 * @param {object} options
 * @returns {object}
 */
function replaceFunctions (value, options) {
  const {'.functions': fns} = options
  // Get a list of available fn names
  const fnNames = Object.values(fns).map(fn => fn.name)
  // Parse the rule expression
  const valueSyntax = esprima.parse(value)
  /**
   * Walk the function body and replace argument
   * identifiers with the specified value
   *
   * @param {object} node
   * @param {string} key
   * @param {object} parentNode
   * @param {object} fn - a function definition from the .functions map
   * @param {array} args - an array of esprima nodes that were passed
   *  passed into the function call
   */
  function walkFn (node, key, parentNode, fn, args) {
    // We found an identifier — see if it is one of the fn args
    if (node.type === 'Identifier' && includes(fn.args, node.name)) {
      // Get the argument
      const arg = args[fn.args.indexOf(node.name)]
      // Replace the identifier with the supplied argument node
      parentNode[key] = arg
    } else {
      // Keep walking
      for (let [childKey, child] of Object.entries(node)) {
        // Only objects that arent "property"
        if (Object.isObject(child)) {
          if (childKey === 'property' && node.computed === false) continue
          walkFn(child, childKey, node, fn, args)
        }
      }
    }
  }
  /**
   * Walk the rule expression for CallExpression nodes that are included
   * in "fnNames" and then replace them with the fn.body
   *
   * @param {object} node
   * @param {string} key
   * @param {object} parentNode
   */
  function walkValue (node, key, parentNode) {
    // A CallExpression that matches one of the fnNames to be replaced
    if (node.type === 'CallExpression' && includes(fnNames, node.callee.name)) {
      // Get the fn definition by name
      const fn = find(Object.values(fns), ({name}) => name === node.callee.name)
      // Parse the fn definition
      const fnSyntax = esprima.parse(replaceFunctions(fn.body, options).code)
      // Walk the definition and replace the argument identifiers with the
      // provided arguments
      walkFn(fnSyntax.body[0], key, parentNode, fn, node.arguments)
      // Replace the CallExpression with a node that represents the fn.body
      parentNode[key] = fnSyntax.body[0].expression
    } else {
      // Keep walking
      // Can't use pick because we need to keep references
      // to nodes inside valueSyntax
      for (let [childKey, childNode] of Object.entries(node)) {
        if (Object.isObject(childNode) && !Array.isArray(childNode)) {
          walkValue(childNode, childKey, node)
        }
      }
    }
  }
  walkValue(valueSyntax.body[0])
  return {
    syntax: valueSyntax,
    code: cleanSyntax(valueSyntax)
  }
}

/**
 * Append .val() if an expression is next to an operator
 * or in an array like child selector
 *
 * @param {string} value
 * @returns {object}
 */
function coerceVal (value) {
  const syntax = esprima.parse(value)
  /**
   * Walk the rule expression and append .val() to MemberExpressions
   *
   * @param {object} node
   * @param {string} key
   * @param {object} parentNode
   * @param {string} parentKey
   */
  function walk (node) {
    if (node.type === 'MemberExpression' || node.type === 'Identifier') {
      const name = getMemberExpressionRootName(node)
      // Only append .val() to MemberExpression that start
      // with a Firebase identifier
      if (!SNAPSHOT_IDENTIFIERS.hasOwnProperty(name)) {
        return
      }
      // Don't append .val() if it already exists
      if (hasCallExpressionIdentifier(node, 'val')) {
        return
      }
      const newNode = clone(node)
      node.type = 'CallExpression'
      node.callee = {
        type: 'MemberExpression',
        computed: false,
        object: newNode,
        property: {
          type: 'Identifier',
          name: 'val'
        }
      }
      node.arguments = []
      delete node.object
      delete node.property
    } else {
      for (let [childKey, child] of Object.entries(node)) {
        if (Object.isObject(child)) {
          if (node.type === 'CallExpression' && childKey === 'callee') continue
          walk(child, childKey, node)
        }
      }
    }
  }
  walk(syntax.body[0])
  return {
    syntax,
    code: cleanSyntax(syntax)
  }
}

/**
 * Replace dot/bracket syntax with the necessary
 * snapshot.child() calls
 *
 * @param {string} value
 * @returns {object}
 */
function replaceChildSyntax (value) {
  // Parse the rule expression
  const syntax = esprima.parse(value)
  /**
   * Check a MemberExpression to see if the root
   * is a Firebase identifier — if it is, return the node,
   * otherwise replaceChildSyntax()
   *
   * @param {object} node
   * @returns {object}
   */
  function filterMemeberExpression (node) {
    const name = getMemberExpressionRootName(node)
    if (includes(IGNORE_IDENTIFIERS, name)) {
      return node
    } else {
      return replaceChildSyntax(escodegen.generate(node)).syntax.body[0].expression
    }
  }
  /**
   * Walk the rule expression for Identifier nodes and convert parentNode
   * to a CallExpression: .child(Identifier)
   *
   * @param {object} node
   * @param {string} key
   * @param {object} parentNode
   * @param {string} parentKey
   */
  function walkValue (node, key, parentNode, parentKey) {
    const isCallIdentifier = node.type === 'Identifier' && parentKey === 'callee'
    const isParentComputed = parentNode && parentNode.computed === true
    // Only look at Identifiers that are properties
    // and not part of a CallExpression
    if (key === 'property' && (!isCallIdentifier || isParentComputed)) {
      const isIdentifier = parentNode.property.type === 'Identifier'
      parentNode.computed = false
      let newNode = clone(parentNode)
      let proposedArgument = newNode.property
      let argument
      // MemberExprssions
      if (proposedArgument.type === 'MemberExpression') {
        argument = filterMemeberExpression(proposedArgument)
      } else {
        // Identifier / Literal
        // If the node was computed, make sure the CallExpression
        // argument is an Identifier and not a Literal
        let argumentValue = isIdentifier
          ? proposedArgument.name
          : proposedArgument.value
        argument = isParentComputed && isIdentifier
          ? { type: 'Identifier', name: argumentValue }
          : { type: 'Literal', value: argumentValue }
      }
      // Convert the parent to a CallExpression
      parentNode.type = 'CallExpression'
      parentNode.callee = newNode
      parentNode.arguments = [argument]
      delete parentNode.object
      delete parentNode.property
      proposedArgument.name = 'child'
    } else {
      // Keep walking
      if (node.type === 'CallExpression') {
        node.arguments = node.arguments.map(argument => {
          if (argument.type === 'MemberExpression' || node.type === 'CallExpression') {
            return filterMemeberExpression(argument)
          }
          return argument
        })
      }
      for (let [childKey, child] of Object.entries(node)) {
        if (Object.isObject(child) && !Array.isArray(child)) {
          // Ignore some identifiers
          if (node.type === 'MemberExpression') {
            const name = getMemberExpressionRootName(node)
            if (includes(IGNORE_IDENTIFIERS, name)) continue
          }
          walkValue(child, childKey, node, key)
        }
      }
    }
  }
  walkValue(syntax.body[0])
  return {
    syntax,
    code: cleanSyntax(syntax)
  }
}

/**
 * Replace items in the SNAPSHOT_IDENTIFIERS map
 *
 * @param {string} value
 * @returns {object}
 */
function replaceFirebaseIdentifiers (value) {
  const syntax = esprima.parse(value)
  /**
   * Walk the rule expression and rename Identifier nodes that are the root of
   * an expresston and match identifers in SNAPSHOT_IDENTIFIERS
   *
   * @param {object} node
   * @param {string} key
   * @param {object} parentNode
   * @param {string} parentKey
   */
  function walk (node, key) {
    if (node.type === 'Identifier' && key !== 'property') {
      const name = getMemberExpressionRootName(node)
      if (SNAPSHOT_IDENTIFIERS.hasOwnProperty(name)) {
        node.name = SNAPSHOT_IDENTIFIERS[name]
      }
    } else {
      for (let [childKey, child] of Object.entries(node)) {
        if (Object.isObject(child)) {
          walk(child, childKey)
        }
      }
    }
  }
  walk(syntax.body[0])
  return {
    syntax,
    code: cleanSyntax(syntax)
  }
}

/**
 * Parse a collection of rulesets and replace all Butane syntax
 *
 * @param {object} rules
 * @param {object} [options]
 * @returns {object}
 */
function parse (rules, options={}) {
  options = getOptions(rules, options)
  for (let [key, rule] of Object.entries(rules)) {
    switch (typeof rule) {
      case 'object':
        parse(rule, options)
        break
      case 'string':
        rule = replaceRefs(rule, options)
        rule = replaceFunctions(rule, options).code
        rule = coerceVal(rule).code
        rule = replaceChildSyntax(rule).code
        rule = replaceFirebaseIdentifiers(rule).code
        rules[key] = rule
        break
    }
  }
  return rules
}
<commit_msg>Destructure Array/Object utils<commit_after>'use strict'

import includes from 'core-js/fn/array/includes'
import find from 'core-js/fn/array/find'
import repeat from 'core-js/fn/string/repeat'
import esprima from 'esprima'
import escodegen from 'escodegen'
import {clone, pick, merge, isString, isNumber} from 'lodash'

export {
  getOptions,
  replaceRefs,
  replaceFunctions,
  coerceVal,
  replaceChildSyntax,
  replaceFirebaseIdentifiers,
  parse
}

const {isArray} = Array
const {isObject} = Object

const SPECIAL_KEYS = ['.functions', '.refs', '.parent']
const SNAPSHOT_IDENTIFIERS = {
  next: 'newData',
  prev: 'data',
  root: 'root'
}
const IGNORE_IDENTIFIERS = ['auth']

/**
 * Return true if a node contains a CallExpression identifier
 *
 * @param {object} node
 * @param {string} name
 * @returns {boolean}
 */
function hasCallExpressionIdentifier (rootNode, name) {
  let found = false
  function walk (node) {
    if (node.type === 'CallExpression' && node.callee.property.name === name) {
      found = true
    } else {
      Object.values(node).filter(isObject).forEach(walk)
    }
  }
  walk(rootNode)
  return found
}

/**
 * Return the root object name from a MemberExpresion
 *
 * @param {object} node
 * @returns {object}
 */
function getMemberExpressionRootName (memberNode) {
  let name = null
  function walk (node) {
    if (node.type === 'Identifier') {
      name = node.name
    } else {
      for (let [key, child] of Object.entries(node)) {
        if (isObject(child) && !isArray(child) && key !== 'property') {
          walk(child)
        }
      }
    }
  }
  walk(memberNode)
  return name
}

/**
 * Return an escodegen string from an esprima syntax object
 * and remove the trailing ";"
 *
 * @param {object} syntax
 * @returns {string}
 */
function cleanSyntax (syntax) {
  return escodegen.generate(syntax).replace(/;$/, '')
}

/**
 * Expand a ".refs" map of strings to objects
 *
 * @param {object} refs
 */
function expandRefs (refs) {
  for (let [key, ref] of Object.entries(refs)) {
    if (isString(ref)) {
      ref = refs[key] = {value: ref}
    }
    ref.depth = isNumber(ref.depth) ? ++ref.depth : 0
  }
}

/**
 * Expand a ".functions" map of strings to objects
 *
 * @param {object} functions
 */
function expandFunctions (functions) {
  for (let [fnName, fn] of Object.entries(functions)) {
    let syntax
    try {
      syntax = esprima.parse(fnName).body[0]
    } catch(e) {
      throw new Error(`Invalid .function declaration: ${fnName}`)
    }
    if (syntax.type !== 'ExpressionStatement' || syntax.expression.type !== 'CallExpression') {
      throw new Error(`Invalid .function declaration: ${fnName}`)
    }
    syntax.expression.arguments.forEach(arg => {
      if (arg.type !== 'Identifier') {
        throw new Error(`Invalid .function declaration: ${fnName}`)
      }
    })
    if (isString(fn)) {
      fn = functions[fnName] = {
        body: fn,
        name: syntax.expression.callee.name,
        args: syntax.expression.arguments.map(arg => arg.name)
      }
    }
  }
}

/**
 * Get the options for a ruleset — they are recursively merged down.
 *
 * @param {object} rules - will be mutated to remove SPECIAL_KEYS
 * @param {object} [options] - options from the parent ruleset
 * @returns {object}
 */
function getOptions (rules, options={}) {
  // Merge defaults, the previous options,
  // and the current options
  const prev = pick(options, SPECIAL_KEYS)
  const next = pick(rules, SPECIAL_KEYS)
  options = merge({}, {
    '.functions': {},
    '.refs': {}
  }, prev, next)
  const {
    '.functions': functions,
    '.refs': refs,
    '.parent': parent
  } = options
  // Remove the options from the rule set
  SPECIAL_KEYS.forEach(key => delete rules[key])
  // See if there is a parent ruleset
  if (parent) {
    // Check to see if any parent was a $wildcard
    Object.keys(parent).forEach((key) => {
      if (/^\$/.test(key)) {
        refs[key] = 'prev'
      }
    })
  }
  // Refs
  expandRefs(refs)
  // Functions
  expandFunctions(functions)
  // Misc
  options['.parent'] = rules
  return options
}

/**
 * Replace "^REF_NAME" with the necessary snapshot method
 * chains to reach the specified child
 *
 * "REF_NAME" will be replaced with the value of "REF_NAME"
 * in the first matched ".refs" object plus the required
 * numbet of .parent() calls
 *
 * Alternatley, "^REF_NAME(next|prev)" syntax can be used and
 * will override the value of .refs[REF_NAME]
 *
 * @param {string} value
 * @param {object} options
 * @returns {string}
 */
function replaceRefs (value, options) {
  const {'.refs': refs} = options
  for (let [name, ref] of Object.entries(refs)) {
    name = RegExp.escape(name)
    const pattern = new RegExp(`\\^${name}(?:\\((next|prev)\\))?`, 'g')
    value = value.replace(pattern, (match, keyword) => {
      const snapshot = keyword || ref.value
      const parents = repeat('.parent()', ref.depth)
      return match.replace(match, `${snapshot}${parents}`)
    })
  }
  return value
}

/**
 * Replace function () calls inside a rule expression
 *
 * @param {string} value
 * @param {object} options
 * @returns {object}
 */
function replaceFunctions (value, options) {
  const {'.functions': fns} = options
  // Get a list of available fn names
  const fnNames = Object.values(fns).map(fn => fn.name)
  // Parse the rule expression
  const valueSyntax = esprima.parse(value)
  /**
   * Walk the function body and replace argument
   * identifiers with the specified value
   *
   * @param {object} node
   * @param {string} key
   * @param {object} parentNode
   * @param {object} fn - a function definition from the .functions map
   * @param {array} args - an array of esprima nodes that were passed
   *  passed into the function call
   */
  function walkFn (node, key, parentNode, fn, args) {
    // We found an identifier — see if it is one of the fn args
    if (node.type === 'Identifier' && includes(fn.args, node.name)) {
      // Get the argument
      const arg = args[fn.args.indexOf(node.name)]
      // Replace the identifier with the supplied argument node
      parentNode[key] = arg
    } else {
      // Keep walking
      for (let [childKey, child] of Object.entries(node)) {
        // Only objects that arent "property"
        if (Object.isObject(child)) {
          if (childKey === 'property' && node.computed === false) continue
          walkFn(child, childKey, node, fn, args)
        }
      }
    }
  }
  /**
   * Walk the rule expression for CallExpression nodes that are included
   * in "fnNames" and then replace them with the fn.body
   *
   * @param {object} node
   * @param {string} key
   * @param {object} parentNode
   */
  function walkValue (node, key, parentNode) {
    // A CallExpression that matches one of the fnNames to be replaced
    if (node.type === 'CallExpression' && includes(fnNames, node.callee.name)) {
      // Get the fn definition by name
      const fn = find(Object.values(fns), ({name}) => name === node.callee.name)
      // Parse the fn definition
      const fnSyntax = esprima.parse(replaceFunctions(fn.body, options).code)
      // Walk the definition and replace the argument identifiers with the
      // provided arguments
      walkFn(fnSyntax.body[0], key, parentNode, fn, node.arguments)
      // Replace the CallExpression with a node that represents the fn.body
      parentNode[key] = fnSyntax.body[0].expression
    } else {
      // Keep walking
      // Can't use pick because we need to keep references
      // to nodes inside valueSyntax
      for (let [childKey, childNode] of Object.entries(node)) {
        if (isObject(childNode) && !isArray(childNode)) {
          walkValue(childNode, childKey, node)
        }
      }
    }
  }
  walkValue(valueSyntax.body[0])
  return {
    syntax: valueSyntax,
    code: cleanSyntax(valueSyntax)
  }
}

/**
 * Append .val() if an expression is next to an operator
 * or in an array like child selector
 *
 * @param {string} value
 * @returns {object}
 */
function coerceVal (value) {
  const syntax = esprima.parse(value)
  /**
   * Walk the rule expression and append .val() to MemberExpressions
   *
   * @param {object} node
   * @param {string} key
   * @param {object} parentNode
   * @param {string} parentKey
   */
  function walk (node) {
    if (node.type === 'MemberExpression' || node.type === 'Identifier') {
      const name = getMemberExpressionRootName(node)
      // Only append .val() to MemberExpression that start
      // with a Firebase identifier
      if (!SNAPSHOT_IDENTIFIERS.hasOwnProperty(name)) {
        return
      }
      // Don't append .val() if it already exists
      if (hasCallExpressionIdentifier(node, 'val')) {
        return
      }
      const newNode = clone(node)
      node.type = 'CallExpression'
      node.callee = {
        type: 'MemberExpression',
        computed: false,
        object: newNode,
        property: {
          type: 'Identifier',
          name: 'val'
        }
      }
      node.arguments = []
      delete node.object
      delete node.property
    } else {
      for (let [childKey, child] of Object.entries(node)) {
        if (isObject(child)) {
          if (node.type === 'CallExpression' && childKey === 'callee') continue
          walk(child, childKey, node)
        }
      }
    }
  }
  walk(syntax.body[0])
  return {
    syntax,
    code: cleanSyntax(syntax)
  }
}

/**
 * Replace dot/bracket syntax with the necessary
 * snapshot.child() calls
 *
 * @param {string} value
 * @returns {object}
 */
function replaceChildSyntax (value) {
  // Parse the rule expression
  const syntax = esprima.parse(value)
  /**
   * Check a MemberExpression to see if the root
   * is a Firebase identifier — if it is, return the node,
   * otherwise replaceChildSyntax()
   *
   * @param {object} node
   * @returns {object}
   */
  function filterMemeberExpression (node) {
    const name = getMemberExpressionRootName(node)
    if (includes(IGNORE_IDENTIFIERS, name)) {
      return node
    } else {
      return replaceChildSyntax(escodegen.generate(node)).syntax.body[0].expression
    }
  }
  /**
   * Walk the rule expression for Identifier nodes and convert parentNode
   * to a CallExpression: .child(Identifier)
   *
   * @param {object} node
   * @param {string} key
   * @param {object} parentNode
   * @param {string} parentKey
   */
  function walkValue (node, key, parentNode, parentKey) {
    const isCallIdentifier = node.type === 'Identifier' && parentKey === 'callee'
    const isParentComputed = parentNode && parentNode.computed === true
    // Only look at Identifiers that are properties
    // and not part of a CallExpression
    if (key === 'property' && (!isCallIdentifier || isParentComputed)) {
      const isIdentifier = parentNode.property.type === 'Identifier'
      parentNode.computed = false
      let newNode = clone(parentNode)
      let proposedArgument = newNode.property
      let argument
      // MemberExprssions
      if (proposedArgument.type === 'MemberExpression') {
        argument = filterMemeberExpression(proposedArgument)
      } else {
        // Identifier / Literal
        // If the node was computed, make sure the CallExpression
        // argument is an Identifier and not a Literal
        let argumentValue = isIdentifier
          ? proposedArgument.name
          : proposedArgument.value
        argument = isParentComputed && isIdentifier
          ? { type: 'Identifier', name: argumentValue }
          : { type: 'Literal', value: argumentValue }
      }
      // Convert the parent to a CallExpression
      parentNode.type = 'CallExpression'
      parentNode.callee = newNode
      parentNode.arguments = [argument]
      delete parentNode.object
      delete parentNode.property
      proposedArgument.name = 'child'
    } else {
      // Keep walking
      if (node.type === 'CallExpression') {
        node.arguments = node.arguments.map(argument => {
          if (argument.type === 'MemberExpression' || node.type === 'CallExpression') {
            return filterMemeberExpression(argument)
          }
          return argument
        })
      }
      for (let [childKey, child] of Object.entries(node)) {
        if (isObject(child) && !isArray(child)) {
          // Ignore some identifiers
          if (node.type === 'MemberExpression') {
            const name = getMemberExpressionRootName(node)
            if (includes(IGNORE_IDENTIFIERS, name)) continue
          }
          walkValue(child, childKey, node, key)
        }
      }
    }
  }
  walkValue(syntax.body[0])
  return {
    syntax,
    code: cleanSyntax(syntax)
  }
}

/**
 * Replace items in the SNAPSHOT_IDENTIFIERS map
 *
 * @param {string} value
 * @returns {object}
 */
function replaceFirebaseIdentifiers (value) {
  const syntax = esprima.parse(value)
  /**
   * Walk the rule expression and rename Identifier nodes that are the root of
   * an expresston and match identifers in SNAPSHOT_IDENTIFIERS
   *
   * @param {object} node
   * @param {string} key
   * @param {object} parentNode
   * @param {string} parentKey
   */
  function walk (node, key) {
    if (node.type === 'Identifier' && key !== 'property') {
      const name = getMemberExpressionRootName(node)
      if (SNAPSHOT_IDENTIFIERS.hasOwnProperty(name)) {
        node.name = SNAPSHOT_IDENTIFIERS[name]
      }
    } else {
      for (let [childKey, child] of Object.entries(node)) {
        if (isObject(child)) {
          walk(child, childKey)
        }
      }
    }
  }
  walk(syntax.body[0])
  return {
    syntax,
    code: cleanSyntax(syntax)
  }
}

/**
 * Parse a collection of rulesets and replace all Butane syntax
 *
 * @param {object} rules
 * @param {object} [options]
 * @returns {object}
 */
function parse (rules, options={}) {
  options = getOptions(rules, options)
  for (let [key, rule] of Object.entries(rules)) {
    switch (typeof rule) {
      case 'object':
        parse(rule, options)
        break
      case 'string':
        rule = replaceRefs(rule, options)
        rule = replaceFunctions(rule, options).code
        rule = coerceVal(rule).code
        rule = replaceChildSyntax(rule).code
        rule = replaceFirebaseIdentifiers(rule).code
        rules[key] = rule
        break
    }
  }
  return rules
}
<|endoftext|>


<commit_before>// Define the `person` module
var personApp = angular.module('personApp', []);

personApp.factory('personService', function() {
    var state = 'Resume';

    var person = {
        firstName: "Kris",
        lastName: "Wall",
        image: 'https://pbs.twimg.com/profile_images/713461396835414016/Jxjm2uQ5_400x400.jpg',
        city: 'Oklahoma City',
        tagLine: 'At the space bar...',
        github: 'https://github.com/AmbitiousOkie',
        facebook: false,
        twitter: 'https://twitter.com/ambitiousokie',
        email: 'kris@urbanscouter.com'
    };

    var tabs = [{
        name: 'Resume',
        text: 'This is the resume',
				imageUrl: 'https://s3.amazonaws.com/uifaces/faces/twitter/idiot/73.jpg'
    }, {
        name: 'Bio',
        text: 'This is the bio'
    }, {
        name: 'Projects',
        text: 'This is the projects'
    }, {
        name: 'Teams',
        text: 'This is the teams'
    }, {
        name: 'Events',
        text: 'This is the events'
    }];

    var groups = [
      {
        name: 'OKC Digital Creators'
      },
      {
        name: 'The Irregulars'
      }
    ];

    //  ---- Getters--------------------
    function getState() {
        return state;
    }

    function getPerson() {
        return person;
    }

		function getTabs() {
			return tabs;
		}

    // Setters ---------------------------
    function setState(newState) {
        state = newState;
    }

    // Factory returns -----------------------
    return {
        getState: getState,
        setState: setState,
        getPerson: getPerson,
				getTabs:getTabs
    }
});

// Define the `PersonController` controller on the `person` module
personApp.controller('smallNavController', function smallNavController($scope, personService) {
    $scope.$watch(function() {
        return personService.getState();
    }, function(value) {
        $scope.state = value;
        console.log('Local 1 ' + $scope.state);
    });

    $scope.setState = personService.setState;
		$scope.getTabs = personService.getTabs;
		$scope.tabs = $scope.getTabs();
		console.log('tabs:' + $scope.tabs);

    $scope.person = personService.getPerson();

});

personApp.controller('largeNavController', function largeNavController($scope, personService) {
    $scope.$watch(function() {
        return personService.getState();
    }, function(value) {
        $scope.state = value;
        console.log('Local 2 ' + $scope.state);
    });

		$scope.setState = personService.setState;
		$scope.getTabs = personService.getTabs;
		$scope.tabs = $scope.getTabs();
		console.log('tabs:' + $scope.tabs);
});

personApp.controller('dataViewController', function dataViewController($scope, personService) {
    $scope.$watch(function() {
        return personService.getState();
    }, function(value) {
        $scope.state = value;
        console.log('Local 3 ' + $scope.state);
    });

		$scope.getTabs = personService.getTabs;
		$scope.tabs = $scope.getTabs();
    $scope.person = personService.getPerson();

});
<commit_msg>removed debug code<commit_after>// Define the `person` module
var personApp = angular.module('personApp', []);

personApp.factory('personService', function() {
    var state = 'Resume';

    var person = {
        firstName: "Kris",
        lastName: "Wall",
        image: 'https://pbs.twimg.com/profile_images/713461396835414016/Jxjm2uQ5_400x400.jpg',
        city: 'Oklahoma City',
        tagLine: 'At the space bar...',
        github: 'https://github.com/AmbitiousOkie',
        facebook: false,
        twitter: 'https://twitter.com/ambitiousokie',
        email: 'kris@urbanscouter.com',
        resume: {
          objective: 'This is my objective',
          currentEmp: {
            name: 'Freelance',
            startMonth: 'April',
            startYear: '2008',
            roles: [
              'Role 1',
              'Role 2'
            ]
          }

        }
    };

    var tabs = [{
        name: 'Resume',
        text: 'This is the resume',
        imageUrl: 'https://s3.amazonaws.com/uifaces/faces/twitter/idiot/73.jpg'
    }, {
        name: 'Bio',
        text: 'This is the bio'
    }, {
        name: 'Projects',
        text: 'This is the projects'
    }, {
        name: 'Teams',
        text: 'This is the teams'
    }, {
        name: 'Events',
        text: 'This is the events'
    }];

    var groups = [{
        name: 'OKC Digital Creators'
    }, {
        name: 'The Irregulars'
    }];

    //  ---- Getters--------------------
    function getState() {
        return state;
    }

    function getPerson() {
        return person;
    }

    function getTabs() {
        return tabs;
    }

    // Setters ---------------------------
    function setState(newState) {
        state = newState;
    }

    // Factory returns -----------------------
    return {
        getState: getState,
        setState: setState,
        getPerson: getPerson,
        getTabs: getTabs
    }
});

// Define the `PersonController` controller on the `person` module
personApp.controller('smallNavController', function smallNavController($scope, personService) {
    $scope.$watch(function() {
        return personService.getState();
    }, function(value) {
        $scope.state = value;
    });

    $scope.setState = personService.setState;
    $scope.getTabs = personService.getTabs;
    $scope.tabs = $scope.getTabs();

    $scope.person = personService.getPerson();

});

personApp.controller('largeNavController', function largeNavController($scope, personService) {
    $scope.$watch(function() {
        return personService.getState();
    }, function(value) {
        $scope.state = value;

    });

    $scope.setState = personService.setState;
    $scope.getTabs = personService.getTabs;
    $scope.tabs = $scope.getTabs();

});

personApp.controller('dataViewController', function dataViewController($scope, personService) {
    $scope.$watch(function() {
        return personService.getState();
    }, function(value) {
        $scope.state = value;

    });

    $scope.getTabs = personService.getTabs;
    $scope.tabs = $scope.getTabs();
    $scope.person = personService.getPerson();

});
<|endoftext|>


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

  //set to true for debugging output
  var debug = true;

  // our current position
  var positionCurrent = {
    lat: null,
    lng: null,
    hng: null
  };

  // the outer part of the compass that rotates
  var rose = document.getElementById("rose");

  // elements that ouput our position
  var positionLat = document.getElementById("position-lat");
  var positionLng = document.getElementById("position-lng");
  var positionHng = document.getElementById("position-hng");

  // debug outputs
  var debugOrientation = document.getElementById("debug-orientation");
  var debugOrientationDefault = document.getElementById("debug-orientation-default");

  // info popup elements, pus buttons that open popups
  var popup = document.getElementById("popup");
  var popupContents = document.getElementById("popup-contents");
  var popupInners = document.querySelectorAll(".popup__inner");
  var btnsPopup = document.querySelectorAll(".btn-popup");

  // buttons at the bottom of the screen
  var btnLockOrientation = document.getElementById("btn-lock-orientation");
  var btnNightmode = document.getElementById("btn-nightmode");
  var btnMap = document.getElementById("btn-map");
  var btnInfo = document.getElementById("btn-info");

  // if we have shown the heading unavailable warning yet
  var warningHeadingShown = false;

  // switches keeping track of our current app state
  var isOrientationLockable = false;
  var isOrientationLocked = false;
  var isNightMode = false;

  // the orientation of the device on app load
  var defaultOrientation;

  // browser agnostic orientation
  function getBrowserOrientation() {
    var orientation;
    if (screen.orientation && screen.orientation.type) {
      orientation = screen.orientation.type;
    } else {
      orientation = screen.orientation ||
                    screen.mozOrientation ||
                    screen.msOrientation;
    }

    /*
      'portait-primary':      for (screen width < screen height, e.g. phone, phablet, small tablet)
                                device is in 'normal' orientation
                              for (screen width > screen height, e.g. large tablet, laptop)
                                device has been turned 90deg clockwise from normal

      'portait-secondary':    for (screen width < screen height)
                                device has been turned 180deg from normal
                              for (screen width > screen height)
                                device has been turned 90deg anti-clockwise (or 270deg clockwise) from normal

      'landscape-primary':    for (screen width < screen height)
                                device has been turned 90deg clockwise from normal
                              for (screen width > screen height)
                                device is in 'normal' orientation

      'landscape-secondary':  for (screen width < screen height)
                                device has been turned 90deg anti-clockwise (or 270deg clockwise) from normal
                              for (screen width > screen height)
                                device has been turned 180deg from normal
    */

    return orientation;
  }

  // browser agnostic orientation unlock
  function browserUnlockOrientation() {
    if (screen.orientation && screen.orientation.unlock) {
      screen.orientation.unlock();
    } else if (screen.unlockOrientation) {
      screen.unlockOrientation();
    } else if (screen.mozUnlockOrientation) {
      screen.mozUnlockOrientation();
    } else if (screen.msUnlockOrientation) {
      screen.msUnlockOrientation();
    }
  }

  // browser agnostic document.fullscreenElement
  function getBrowserFullscreenElement() {
    if (typeof document.fullscreenElement !== "undefined") {
      return document.fullscreenElement;
    } else if (typeof document.webkitFullscreenElement !== "undefined") {
      return document.webkitFullscreenElement;
    } else if (typeof document.mozFullScreenElement !== "undefined") {
      return document.mozFullScreenElement;
    } else if (typeof document.msFullscreenElement !== "undefined") {
      return document.msFullscreenElement;
    }
  }

  // browser agnostic document.documentElement.requestFullscreen
  function browserRequestFullscreen() {
    if (document.documentElement.requestFullscreen) {
      document.documentElement.requestFullscreen();
    } else if (document.documentElement.webkitRequestFullscreen) {
      document.documentElement.webkitRequestFullscreen();
    } else if (document.documentElement.mozRequestFullScreen) {
      document.documentElement.mozRequestFullScreen();
    } else if (document.documentElement.msRequestFullscreen) {
      document.documentElement.msRequestFullscreen();
    }
  }

  // browser agnostic document.documentElement.exitFullscreen
  function browserExitFullscreen() {
    if (document.exitFullscreen) {
      document.exitFullscreen();
    } else if (document.webkitExitFullscreen) {
      document.webkitExitFullscreen();
    } else if (document.mozCancelFullScreen) {
      document.mozCancelFullScreen();
    } else if (document.msExitFullscreen) {
      document.msExitFullscreen();
    }
  }

  // called on device orientation change
  function onHeadingChange(event) {
    var heading = event.alpha;
    var orientation = getBrowserOrientation();

    if (typeof heading !== "undefined" && heading !== null && typeof orientation !== "undefined") {
      // we have a browser that reports device heading and orientation

      if (debug) {
        debugOrientation.textContent = orientation;
      }

      // what adjustment we have to add to rotation to allow for current device orientation
      var adjustment = 0;

      var currentOrientation = orientation.split("-");

      if (defaultOrientation === "landscape") {
        adjustment -= 90;
      }

      if (defaultOrientation !== currentOrientation[0]) {
        if (defaultOrientation === "landscape") {
          adjustment -= 270;
        } else {
          adjustment -= 90;
        }
      }

      if (currentOrientation[1] === "secondary") {
        adjustment -= 180;
      }

      positionCurrent.hng = heading + adjustment;

      var phase = positionCurrent.hng < 0 ? 360 + positionCurrent.hng : positionCurrent.hng;
      positionHng.textContent = (360 - phase | 0) + "°";

      // apply rotation to compass rose
      if (typeof rose.style.transform !== "undefined") {
        rose.style.transform = "rotateZ(" + positionCurrent.hng + "deg)";
      } else if (typeof rose.style.webkitTransform !== "undefined") {
        rose.style.webkitTransform = "rotateZ(" + positionCurrent.hng + "deg)";
      }
    } else {
      // device can't show heading

      positionHng.textContent = "n/a";
      showHeadingWarning();
    }
  }

  function showHeadingWarning() {
    if (!warningHeadingShown) {
      popupOpen("noorientation");
      warningHeadingShown = true;
    }
  }

  function onFullscreenChange() {
    if (isOrientationLockable && getBrowserFullscreenElement()) {
      if (screen.orientation && screen.orientation.lock) {
        screen.orientation.lock(getBrowserOrientation()).then(function () {
        }).catch(function () {
        });
      }
    } else {
      lockOrientationRequest(false);
    }
  }

  function toggleOrientationLockable(lockable) {
    isOrientationLockable = lockable;

    if (isOrientationLockable) {
      btnLockOrientation.classList.remove("btn--hide");

      btnNightmode.classList.add("column-25");
      btnNightmode.classList.remove("column-33");
      btnMap.classList.add("column-25");
      btnMap.classList.remove("column-33");
      btnInfo.classList.add("column-25");
      btnInfo.classList.remove("column-33");
    } else {
      btnLockOrientation.classList.add("btn--hide");

      btnNightmode.classList.add("column-33");
      btnNightmode.classList.remove("column-25");
      btnMap.classList.add("column-33");
      btnMap.classList.remove("column-25");
      btnInfo.classList.add("column-33");
      btnInfo.classList.remove("column-25");
    }
  }

  function checkLockable() {
    if (screen.orientation && screen.orientation.lock) {
      screen.orientation.lock(getBrowserOrientation()).then(function () {
        toggleOrientationLockable(true);
        browserUnlockOrientation();
      }).catch(function (event) {
        if (event.code === 18) { // The page needs to be fullscreen in order to call lockOrientation(), but is lockable
          toggleOrientationLockable(true);
          browserUnlockOrientation(); //needed as chrome was locking orientation (even if not in fullscreen, bug??)
        } else {  // lockOrientation() is not available on this device (or other error)
          toggleOrientationLockable(false);
        }
      });
    } else {
      toggleOrientationLockable(false);
    }
  }

  function lockOrientationRequest(doLock) {
    if (isOrientationLockable) {
      if (doLock) {
        browserRequestFullscreen();
        lockOrientation(true);
      } else {
        browserUnlockOrientation();
        browserExitFullscreen();
        lockOrientation(false);
      }
    }
  }

  function lockOrientation(locked) {
    if (locked) {
      btnLockOrientation.classList.add("active");
    } else {
      btnLockOrientation.classList.remove("active");
    }

    isOrientationLocked = locked;
  }

  function toggleOrientationLock() {
    if (isOrientationLockable) {
      lockOrientationRequest(!isOrientationLocked);
    }
  }

  function locationUpdate(position) {
    positionCurrent.lat = position.coords.latitude;
    positionCurrent.lng = position.coords.longitude;

    positionLat.textContent = decimalToSexagesimal(positionCurrent.lat, "lat");
    positionLng.textContent = decimalToSexagesimal(positionCurrent.lng, "lng");
  }

  function locationUpdateFail(error) {
    positionLat.textContent = "n/a";
    positionLng.textContent = "n/a";
    console.log("location fail: ", error);
  }

  function setNightmode(on) {

    if (on) {
      btnNightmode.classList.add("active");
    } else {
      btnNightmode.classList.remove("active");
    }

    window.setTimeout(function() {
      if (on) {
        document.documentElement.classList.add("nightmode");
      } else {
        document.documentElement.classList.remove("nightmode");
      }
    }, 1);

    isNightMode = on;
  }

  function toggleNightmode() {
    setNightmode(!isNightMode);
  }

  function openMap() {
    window.open("https://www.google.com/maps/place/@" + positionCurrent.lat + "," + positionCurrent.lng + ",16z", "_blank");
  }

  function popupOpenFromClick(event) {
    popupOpen(event.currentTarget.dataset.name);
  }

  function popupOpen(name) {
    var i;
    for (i=0; i<popupInners.length; i++) {
      popupInners[i].classList.add("popup__inner--hide");
    }
    document.getElementById("popup-inner-" + name).classList.remove("popup__inner--hide");

    popup.classList.add("popup--show");
  }

  function popupClose() {
    popup.classList.remove("popup--show");
  }

  function popupContentsClick(event) {
    event.stopPropagation();
  }

  function decimalToSexagesimal(decimal, type) {
    var degrees = decimal | 0;
    var fraction = Math.abs(decimal - degrees);
    var minutes = (fraction * 60) | 0;
    var seconds = (fraction * 3600 - minutes * 60) | 0;

    var direction = "";
    var positive = degrees > 0;
    degrees = Math.abs(degrees);
    switch (type) {
      case "lat":
        direction = positive ? "N" : "S";
        break;
      case "lng":
        direction = positive ? "E" : "W";
        break;
    }

    return degrees + "° " + minutes + "' " + seconds + "\" " + direction;
  }

  if (screen.width > screen.height) {
    defaultOrientation = "landscape";
  } else {
    defaultOrientation = "portrait";
  }
  if (debug) {
    debugOrientationDefault.textContent = defaultOrientation;
  }

  window.addEventListener("deviceorientation", onHeadingChange);

  document.addEventListener("fullscreenchange", onFullscreenChange);
  document.addEventListener("webkitfullscreenchange", onFullscreenChange);
  document.addEventListener("mozfullscreenchange", onFullscreenChange);
  document.addEventListener("MSFullscreenChange", onFullscreenChange);

  btnLockOrientation.addEventListener("click", toggleOrientationLock);
  btnNightmode.addEventListener("click", toggleNightmode);
  btnMap.addEventListener("click", openMap);

  var i;
  for (i=0; i<btnsPopup.length; i++) {
    btnsPopup[i].addEventListener("click", popupOpenFromClick);
  }

  popup.addEventListener("click", popupClose);
  popupContents.addEventListener("click", popupContentsClick);

  navigator.geolocation.watchPosition(locationUpdate, locationUpdateFail, {
    enableHighAccuracy: false,
    maximumAge: 30000,
    timeout: 27000
  });

  setNightmode(false);
  checkLockable();

}());
<commit_msg>debug off<commit_after>(function () {
  "use strict";

  //set to true for debugging output
  var debug = false;

  // our current position
  var positionCurrent = {
    lat: null,
    lng: null,
    hng: null
  };

  // the outer part of the compass that rotates
  var rose = document.getElementById("rose");

  // elements that ouput our position
  var positionLat = document.getElementById("position-lat");
  var positionLng = document.getElementById("position-lng");
  var positionHng = document.getElementById("position-hng");

  // debug outputs
  var debugOrientation = document.getElementById("debug-orientation");
  var debugOrientationDefault = document.getElementById("debug-orientation-default");

  // info popup elements, pus buttons that open popups
  var popup = document.getElementById("popup");
  var popupContents = document.getElementById("popup-contents");
  var popupInners = document.querySelectorAll(".popup__inner");
  var btnsPopup = document.querySelectorAll(".btn-popup");

  // buttons at the bottom of the screen
  var btnLockOrientation = document.getElementById("btn-lock-orientation");
  var btnNightmode = document.getElementById("btn-nightmode");
  var btnMap = document.getElementById("btn-map");
  var btnInfo = document.getElementById("btn-info");

  // if we have shown the heading unavailable warning yet
  var warningHeadingShown = false;

  // switches keeping track of our current app state
  var isOrientationLockable = false;
  var isOrientationLocked = false;
  var isNightMode = false;

  // the orientation of the device on app load
  var defaultOrientation;

  // browser agnostic orientation
  function getBrowserOrientation() {
    var orientation;
    if (screen.orientation && screen.orientation.type) {
      orientation = screen.orientation.type;
    } else {
      orientation = screen.orientation ||
                    screen.mozOrientation ||
                    screen.msOrientation;
    }

    /*
      'portait-primary':      for (screen width < screen height, e.g. phone, phablet, small tablet)
                                device is in 'normal' orientation
                              for (screen width > screen height, e.g. large tablet, laptop)
                                device has been turned 90deg clockwise from normal

      'portait-secondary':    for (screen width < screen height)
                                device has been turned 180deg from normal
                              for (screen width > screen height)
                                device has been turned 90deg anti-clockwise (or 270deg clockwise) from normal

      'landscape-primary':    for (screen width < screen height)
                                device has been turned 90deg clockwise from normal
                              for (screen width > screen height)
                                device is in 'normal' orientation

      'landscape-secondary':  for (screen width < screen height)
                                device has been turned 90deg anti-clockwise (or 270deg clockwise) from normal
                              for (screen width > screen height)
                                device has been turned 180deg from normal
    */

    return orientation;
  }

  // browser agnostic orientation unlock
  function browserUnlockOrientation() {
    if (screen.orientation && screen.orientation.unlock) {
      screen.orientation.unlock();
    } else if (screen.unlockOrientation) {
      screen.unlockOrientation();
    } else if (screen.mozUnlockOrientation) {
      screen.mozUnlockOrientation();
    } else if (screen.msUnlockOrientation) {
      screen.msUnlockOrientation();
    }
  }

  // browser agnostic document.fullscreenElement
  function getBrowserFullscreenElement() {
    if (typeof document.fullscreenElement !== "undefined") {
      return document.fullscreenElement;
    } else if (typeof document.webkitFullscreenElement !== "undefined") {
      return document.webkitFullscreenElement;
    } else if (typeof document.mozFullScreenElement !== "undefined") {
      return document.mozFullScreenElement;
    } else if (typeof document.msFullscreenElement !== "undefined") {
      return document.msFullscreenElement;
    }
  }

  // browser agnostic document.documentElement.requestFullscreen
  function browserRequestFullscreen() {
    if (document.documentElement.requestFullscreen) {
      document.documentElement.requestFullscreen();
    } else if (document.documentElement.webkitRequestFullscreen) {
      document.documentElement.webkitRequestFullscreen();
    } else if (document.documentElement.mozRequestFullScreen) {
      document.documentElement.mozRequestFullScreen();
    } else if (document.documentElement.msRequestFullscreen) {
      document.documentElement.msRequestFullscreen();
    }
  }

  // browser agnostic document.documentElement.exitFullscreen
  function browserExitFullscreen() {
    if (document.exitFullscreen) {
      document.exitFullscreen();
    } else if (document.webkitExitFullscreen) {
      document.webkitExitFullscreen();
    } else if (document.mozCancelFullScreen) {
      document.mozCancelFullScreen();
    } else if (document.msExitFullscreen) {
      document.msExitFullscreen();
    }
  }

  // called on device orientation change
  function onHeadingChange(event) {
    var heading = event.alpha;
    var orientation = getBrowserOrientation();

    if (typeof heading !== "undefined" && heading !== null && typeof orientation !== "undefined") {
      // we have a browser that reports device heading and orientation

      if (debug) {
        debugOrientation.textContent = orientation;
      }

      // what adjustment we have to add to rotation to allow for current device orientation
      var adjustment = 0;

      var currentOrientation = orientation.split("-");

      if (defaultOrientation === "landscape") {
        adjustment -= 90;
      }

      if (defaultOrientation !== currentOrientation[0]) {
        if (defaultOrientation === "landscape") {
          adjustment -= 270;
        } else {
          adjustment -= 90;
        }
      }

      if (currentOrientation[1] === "secondary") {
        adjustment -= 180;
      }

      positionCurrent.hng = heading + adjustment;

      var phase = positionCurrent.hng < 0 ? 360 + positionCurrent.hng : positionCurrent.hng;
      positionHng.textContent = (360 - phase | 0) + "°";

      // apply rotation to compass rose
      if (typeof rose.style.transform !== "undefined") {
        rose.style.transform = "rotateZ(" + positionCurrent.hng + "deg)";
      } else if (typeof rose.style.webkitTransform !== "undefined") {
        rose.style.webkitTransform = "rotateZ(" + positionCurrent.hng + "deg)";
      }
    } else {
      // device can't show heading

      positionHng.textContent = "n/a";
      showHeadingWarning();
    }
  }

  function showHeadingWarning() {
    if (!warningHeadingShown) {
      popupOpen("noorientation");
      warningHeadingShown = true;
    }
  }

  function onFullscreenChange() {
    if (isOrientationLockable && getBrowserFullscreenElement()) {
      if (screen.orientation && screen.orientation.lock) {
        screen.orientation.lock(getBrowserOrientation()).then(function () {
        }).catch(function () {
        });
      }
    } else {
      lockOrientationRequest(false);
    }
  }

  function toggleOrientationLockable(lockable) {
    isOrientationLockable = lockable;

    if (isOrientationLockable) {
      btnLockOrientation.classList.remove("btn--hide");

      btnNightmode.classList.add("column-25");
      btnNightmode.classList.remove("column-33");
      btnMap.classList.add("column-25");
      btnMap.classList.remove("column-33");
      btnInfo.classList.add("column-25");
      btnInfo.classList.remove("column-33");
    } else {
      btnLockOrientation.classList.add("btn--hide");

      btnNightmode.classList.add("column-33");
      btnNightmode.classList.remove("column-25");
      btnMap.classList.add("column-33");
      btnMap.classList.remove("column-25");
      btnInfo.classList.add("column-33");
      btnInfo.classList.remove("column-25");
    }
  }

  function checkLockable() {
    if (screen.orientation && screen.orientation.lock) {
      screen.orientation.lock(getBrowserOrientation()).then(function () {
        toggleOrientationLockable(true);
        browserUnlockOrientation();
      }).catch(function (event) {
        if (event.code === 18) { // The page needs to be fullscreen in order to call lockOrientation(), but is lockable
          toggleOrientationLockable(true);
          browserUnlockOrientation(); //needed as chrome was locking orientation (even if not in fullscreen, bug??)
        } else {  // lockOrientation() is not available on this device (or other error)
          toggleOrientationLockable(false);
        }
      });
    } else {
      toggleOrientationLockable(false);
    }
  }

  function lockOrientationRequest(doLock) {
    if (isOrientationLockable) {
      if (doLock) {
        browserRequestFullscreen();
        lockOrientation(true);
      } else {
        browserUnlockOrientation();
        browserExitFullscreen();
        lockOrientation(false);
      }
    }
  }

  function lockOrientation(locked) {
    if (locked) {
      btnLockOrientation.classList.add("active");
    } else {
      btnLockOrientation.classList.remove("active");
    }

    isOrientationLocked = locked;
  }

  function toggleOrientationLock() {
    if (isOrientationLockable) {
      lockOrientationRequest(!isOrientationLocked);
    }
  }

  function locationUpdate(position) {
    positionCurrent.lat = position.coords.latitude;
    positionCurrent.lng = position.coords.longitude;

    positionLat.textContent = decimalToSexagesimal(positionCurrent.lat, "lat");
    positionLng.textContent = decimalToSexagesimal(positionCurrent.lng, "lng");
  }

  function locationUpdateFail(error) {
    positionLat.textContent = "n/a";
    positionLng.textContent = "n/a";
    console.log("location fail: ", error);
  }

  function setNightmode(on) {

    if (on) {
      btnNightmode.classList.add("active");
    } else {
      btnNightmode.classList.remove("active");
    }

    window.setTimeout(function() {
      if (on) {
        document.documentElement.classList.add("nightmode");
      } else {
        document.documentElement.classList.remove("nightmode");
      }
    }, 1);

    isNightMode = on;
  }

  function toggleNightmode() {
    setNightmode(!isNightMode);
  }

  function openMap() {
    window.open("https://www.google.com/maps/place/@" + positionCurrent.lat + "," + positionCurrent.lng + ",16z", "_blank");
  }

  function popupOpenFromClick(event) {
    popupOpen(event.currentTarget.dataset.name);
  }

  function popupOpen(name) {
    var i;
    for (i=0; i<popupInners.length; i++) {
      popupInners[i].classList.add("popup__inner--hide");
    }
    document.getElementById("popup-inner-" + name).classList.remove("popup__inner--hide");

    popup.classList.add("popup--show");
  }

  function popupClose() {
    popup.classList.remove("popup--show");
  }

  function popupContentsClick(event) {
    event.stopPropagation();
  }

  function decimalToSexagesimal(decimal, type) {
    var degrees = decimal | 0;
    var fraction = Math.abs(decimal - degrees);
    var minutes = (fraction * 60) | 0;
    var seconds = (fraction * 3600 - minutes * 60) | 0;

    var direction = "";
    var positive = degrees > 0;
    degrees = Math.abs(degrees);
    switch (type) {
      case "lat":
        direction = positive ? "N" : "S";
        break;
      case "lng":
        direction = positive ? "E" : "W";
        break;
    }

    return degrees + "° " + minutes + "' " + seconds + "\" " + direction;
  }

  if (screen.width > screen.height) {
    defaultOrientation = "landscape";
  } else {
    defaultOrientation = "portrait";
  }
  if (debug) {
    debugOrientationDefault.textContent = defaultOrientation;
  }

  window.addEventListener("deviceorientation", onHeadingChange);

  document.addEventListener("fullscreenchange", onFullscreenChange);
  document.addEventListener("webkitfullscreenchange", onFullscreenChange);
  document.addEventListener("mozfullscreenchange", onFullscreenChange);
  document.addEventListener("MSFullscreenChange", onFullscreenChange);

  btnLockOrientation.addEventListener("click", toggleOrientationLock);
  btnNightmode.addEventListener("click", toggleNightmode);
  btnMap.addEventListener("click", openMap);

  var i;
  for (i=0; i<btnsPopup.length; i++) {
    btnsPopup[i].addEventListener("click", popupOpenFromClick);
  }

  popup.addEventListener("click", popupClose);
  popupContents.addEventListener("click", popupContentsClick);

  navigator.geolocation.watchPosition(locationUpdate, locationUpdateFail, {
    enableHighAccuracy: false,
    maximumAge: 30000,
    timeout: 27000
  });

  setNightmode(false);
  checkLockable();

}());
<|endoftext|>


<commit_before>/*
 Firebase and AngularJS based Finance Manager
*/
(function () {
  'use strict';
  /*global angular*/
  var app = angular.module("app", []);
}());
<commit_msg>Quotes.<commit_after>/*
 Firebase and AngularJS based Finance Manager
*/
(function () {
  "use strict";
  /*global angular*/
  var app = angular.module("app", []);
}());
<|endoftext|>


<commit_before>var map;

function main() {

  var options = {
    center: [-42.27730877423707, 172.63916015625],
    zoom: 6,
    zoomControl: false,  // dont add the zoom overlay (it is added by default)
    loaderControl: false //dont show tiles loader
  };

  cartodb.createVis('map', 'http://sarahkehresman.cartodb.com/api/v2/viz/af372514-b3d7-11e4-81ba-0e0c41326911/viz.json', options)
    .done(function(vis, layers) {
      // there are two layers, base layer and points layer
      var sublayer = layers[1].getSubLayer(0);
      sublayer.set({ 'interactivity': ['cartodb_id', 'name_to_display', 'description'] });

      // Set the custom infowindow template defined on the html
      sublayer.infowindow.set('template', $('#infowindow_template').html());

      // add the tooltip show when hover on the point
      vis.addOverlay({
        type: 'tooltip',
        position: 'top|center',
        template: '
{{name_to_display}}
'
      });

      vis.addOverlay({
        type: 'infobox',
        template: '
{{name_to_display}}

{{description}}
',
        width: 200,
        position: 'bottom|right'
      });

    });

}

window.onload = main;
<commit_msg>Update app.js<commit_after>var map;

function main() {

  var options = {
    center: [-42.27730877423707, 172.63916015625],
    zoom: 6,
    zoomControl: false,  // dont add the zoom overlay (it is added by default)
    loaderControl: false //dont show tiles loader
  };

  cartodb.createVis('map', 'http://cityresearch.cartodb.com/api/v2/viz/5c39067a-b3aa-11e4-a01f-0e9d821ea90d/viz.json', options)
    .done(function(vis, layers) {
      // there are two layers, base layer and points layer
      var sublayer = layers[1].getSubLayer(0);
      sublayer.set({ 'interactivity': ['cartodb_id', 'name_to_display', 'description'] });

      // Set the custom infowindow template defined on the html
      sublayer.infowindow.set('template', $('#infowindow_template').html());

      // add the tooltip show when hover on the point
      vis.addOverlay({
        type: 'tooltip',
        position: 'top|center',
        template: '
{{name_to_display}}
'
      });

      vis.addOverlay({
        type: 'infobox',
        template: '
{{name_to_display}}

{{description}}
',
        width: 200,
        position: 'bottom|right'
      });

    });

}

window.onload = main;
<|endoftext|>


<commit_before>// Tun on full stack traces in errors to help debugging
Error.stackTraceLimit=Infinity;

jasmine.DEFAULT_TIMEOUT_INTERVAL = 100;

// Cancel Karma's synchronous start,
// we will call `__karma__.start()` later, once all the specs are loaded.
__karma__.loaded = function() {};

System.config({
  baseURL: '/base/',
  defaultJSExtensions: true,
  paths: {
    'benchpress/*': 'dist/js/dev/es5/benchpress/*.js',
    'angular2/*': 'dist/js/dev/es5/angular2/*.js',
    'rtts_assert/*': 'dist/js/dev/es5/rtts_assert/*.js',
    'rx': 'node_modules/rx/dist/rx.js'
  }
});

// Import all the specs, execute their `main()` method and kick off Karma (Jasmine).
System.import('angular2/src/core/dom/browser_adapter').then(function(browser_adapter) {
  browser_adapter.BrowserDomAdapter.makeCurrent();
}).then(function() {
  return Promise.all(
    Object.keys(window.__karma__.files) // All files served by Karma.
    .filter(onlySpecFiles)
    .map(window.file2moduleName)        // Normalize paths to module names.
    .map(function(path) {
      return System.import(path).then(function(module) {
        if (module.hasOwnProperty('main')) {
          module.main();
        } else {
          throw new Error('Module ' + path + ' does not implement main() method.');
        }
      });
    }));
})
.then(function() {
  __karma__.start();
}, function(error) {
  console.error(error.stack || error);
  __karma__.start();
});

function onlySpecFiles(path) {
  return /_spec\.js$/.test(path);
}
<commit_msg>fix(test): error in karma when systemjs imports fail<commit_after>// Tun on full stack traces in errors to help debugging
Error.stackTraceLimit=Infinity;

jasmine.DEFAULT_TIMEOUT_INTERVAL = 100;

// Cancel Karma's synchronous start,
// we will call `__karma__.start()` later, once all the specs are loaded.
__karma__.loaded = function() {};

System.config({
  baseURL: '/base/',
  defaultJSExtensions: true,
  paths: {
    'benchpress/*': 'dist/js/dev/es5/benchpress/*.js',
    'angular2/*': 'dist/js/dev/es5/angular2/*.js',
    'rtts_assert/*': 'dist/js/dev/es5/rtts_assert/*.js',
    'rx': 'node_modules/rx/dist/rx.js'
  }
});

// Import all the specs, execute their `main()` method and kick off Karma (Jasmine).
System.import('angular2/src/core/dom/browser_adapter').then(function(browser_adapter) {
  browser_adapter.BrowserDomAdapter.makeCurrent();
}).then(function() {
  return Promise.all(
    Object.keys(window.__karma__.files) // All files served by Karma.
    .filter(onlySpecFiles)
    .map(window.file2moduleName)        // Normalize paths to module names.
    .map(function(path) {
      return System.import(path).then(function(module) {
        if (module.hasOwnProperty('main')) {
          module.main();
        } else {
          throw new Error('Module ' + path + ' does not implement main() method.');
        }
      });
    }));
})
.then(function() {
  __karma__.start();
}, function(error) {
  __karma__.error(error.stack || error);
});

function onlySpecFiles(path) {
  return /_spec\.js$/.test(path);
}
<|endoftext|>