diff --git a/404.html b/404.html
index b801417f..f7a17719 100644
--- a/404.html
+++ b/404.html
@@ -125,7 +125,7 @@
Your browser is out of date!
-
+
diff --git a/assets/images/iaac.png b/assets/images/iaac.png
index e527cb96..3c0c9c02 100644
Binary files a/assets/images/iaac.png and b/assets/images/iaac.png differ
diff --git a/index.html b/index.html
index b801417f..f7a17719 100644
--- a/index.html
+++ b/index.html
@@ -125,7 +125,7 @@ Your browser is out of date!
-
+
diff --git a/scripts/app-1ff5977f0f.js b/scripts/app-4cd2f18a1c.js
similarity index 100%
rename from scripts/app-1ff5977f0f.js
rename to scripts/app-4cd2f18a1c.js
index eae0796d..04dd890c 100644
--- a/scripts/app-1ff5977f0f.js
+++ b/scripts/app-4cd2f18a1c.js
@@ -9,1824 +9,1824 @@
'use strict';
angular.module('app.components')
- .factory('User', ['COUNTRY_CODES', function(COUNTRY_CODES) {
+ .controller('KitController', KitController);
- /**
- * User constructor
- * @param {Object} userData - User data sent from API
- * @property {number} id - User ID
- * @property {string} username - Username
- * @property {string} profile_picture - Avatar URL of user
- * @property {Array} devices - Kits that belongs to this user
- * @property {string} url - URL
- * @property {string} city - User city
- * @property {string} country - User country
- */
+ KitController.$inject = ['$state','$scope', '$stateParams',
+ 'sensor', 'FullDevice', '$mdDialog', 'belongsToUser',
+ 'timeUtils', 'animation', 'auth',
+ '$timeout', 'alert', '$q', 'device',
+ 'HasSensorDevice', 'geolocation', 'PreviewDevice'];
+ function KitController($state, $scope, $stateParams,
+ sensor, FullDevice, $mdDialog, belongsToUser,
+ timeUtils, animation, auth,
+ $timeout, alert, $q, device,
+ HasSensorDevice, geolocation, PreviewDevice) {
- function User(userData) {
- this.id = userData.id;
- this.username = userData.username;
- this.profile_picture = userData.profile_picture;
- this.devices = userData.devices;
- this.url = userData.url;
- this.city = userData.location.city;
- /*jshint camelcase: false */
- this.country = COUNTRY_CODES[userData.location.country_code];
- }
- return User;
- }]);
+ var vm = this;
+ var sensorsData = [];
-})();
+ var mainSensorID, compareSensorID;
+ var picker;
+ vm.deviceID = $stateParams.id;
+ vm.battery = {};
+ vm.downloadData = downloadData;
+ vm.geolocate = geolocate;
+ vm.device = undefined;
+ vm.deviceBelongsToUser = belongsToUser;
+ vm.deviceWithoutData = false;
+ vm.legacyApiKey = belongsToUser ?
+ auth.getCurrentUser().data.key :
+ undefined;
+ vm.loadingChart = true;
+ vm.moveChart = moveChart;
+ vm.allowUpdateChart = true;
+ vm.ownerDevices = [];
+ vm.removeDevice = removeDevice;
+ vm.resetTimeOpts = resetTimeOpts;
+ vm.sampleDevices = [];
+ vm.selectedSensor = undefined;
+ vm.selectedSensorData = {};
+ vm.selectedSensorToCompare = undefined;
+ vm.selectedSensorToCompareData = {};
+ vm.sensors = [];
+ vm.chartSensors = [];
+ vm.sensorsToCompare = [];
+ vm.setFromLast = setFromLast;
+ vm.showSensorOnChart = showSensorOnChart;
+ vm.showStore = showStore;
+ vm.slide = slide;
+ vm.showRaw = false;
+ vm.timeOpt = ['60 minutes', 'day' , 'month'];
+ vm.timeOptSelected = timeOptSelected;
+ vm.updateInterval = 15000;
+ vm.hasRaw;
+ vm.sensorNames = {};
-(function() {
- 'use strict';
+ var focused = true;
- angular.module('app.components')
- .factory('NonAuthUser', ['User', function(User) {
+ // event listener on change of value of main sensor selector
+ $scope.$watch('vm.selectedSensor', function(newVal) {
- function NonAuthUser(userData) {
- User.call(this, userData);
+ // Prevents undisered calls if selected sensor is not yet defined
+ if (!newVal) {
+ return;
}
- NonAuthUser.prototype = Object.create(User.prototype);
- NonAuthUser.prototype.constructor = User;
-
- return NonAuthUser;
- }]);
-})();
-
-(function() {
- 'use strict';
-
- angular.module('app.components')
- .factory('AuthUser', ['User', function(User) {
- /**
- * AuthUser constructor. Used for authenticated users
- * @extends User
- * @param {Object} userData - Contains user data sent from API
- * @property {string} email - User email
- * @property {string} role - User role. Ex: admin
- * @property {string} key - Personal API Key
- */
+ vm.selectedSensorToCompare = undefined;
+ vm.selectedSensorToCompareData = {};
+ vm.chartDataCompare = [];
+ compareSensorID = undefined;
- function AuthUser(userData) {
- User.call(this, userData);
+ setSensorSideChart();
- this.email = userData.email;
- this.role = userData.role;
- /*jshint camelcase: false */
- this.key = userData.legacy_api_key;
- }
- AuthUser.prototype = Object.create(User.prototype);
- AuthUser.prototype.constructor = User;
+ vm.sensorsToCompare = getSensorsToCompare();
- return AuthUser;
- }]);
-})();
+ $timeout(function() {
+ // TODO: Improvement, change how we set the colors
+ colorSensorCompareName();
+ setSensor({type: 'main', value: newVal});
-(function() {
- 'use strict';
+ if (picker){
+ changeChart([mainSensorID]);
+ }
+ }, 100);
- angular.module('app.components')
- .factory('Sensor', ['sensorUtils', 'timeUtils', function(sensorUtils, timeUtils) {
+ });
- /**
- * Sensor constructor
- * @param {Object} sensorData - Contains the data of a sensor sent from the API
- * @property {string} name - Name of sensor
- * @property {number} id - ID of sensor
- * @property {string} unit - Unit of sensor. Ex: %
- * @property {string} value - Last value sent. Ex: 95
- * @property {string} prevValue - Previous value before last value
- * @property {string} lastReadingAt - last_reading_at for the sensor reading
- * @property {string} icon - Icon URL for sensor
- * @property {string} arrow - Icon URL for sensor trend(up, down or equal)
- * @property {string} color - Color that belongs to sensor
- * @property {object} measurement - Measurement
- * @property {string} fullDescription - Full Description for popup
- * @property {string} previewDescription - Short Description for dashboard. Max 140 chars
- * @property {string} tags - Contains sensor tags for filtering the view
- */
- function Sensor(sensorData) {
+ // event listener on change of value of compare sensor selector
+ $scope.$watch('vm.selectedSensorToCompare', function(newVal, oldVal) {
+ vm.sensorsToCompare.forEach(function(sensor) {
+ if(sensor.id === newVal) {
+ _.extend(vm.selectedSensorToCompareData, sensor);
+ }
+ });
- this.id = sensorData.id;
- this.name = sensorData.name;
- this.unit = sensorData.unit;
- this.value = sensorUtils.getSensorValue(sensorData);
- this.prevValue = sensorUtils.getSensorPrevValue(sensorData);
- this.lastReadingAt = timeUtils.parseDate(sensorData.last_reading_at);
- this.icon = sensorUtils.getSensorIcon(this.name);
- this.arrow = sensorUtils.getSensorArrow(this.value, this.prevValue);
- this.color = sensorUtils.getSensorColor(this.name);
- this.measurement = sensorData.measurement;
+ $timeout(function() {
+ colorSensorCompareName();
+ setSensor({type: 'compare', value: newVal});
- // Some sensors don't have measurements because they are ancestors
- if (sensorData.measurement) {
- var description = sensorData.measurement.description;
- this.fullDescription = description;
- this.previewDescription = description.length > 140 ? description.slice(
- 0, 140).concat(' ... ') : description;
- this.is_ancestor = false;
- } else {
- this.is_ancestor = true;
+ if(oldVal === undefined && newVal === undefined) {
+ return;
}
+ changeChart([compareSensorID]);
+ }, 100);
- // Get sensor tags
- this.tags = sensorData.tags;
- }
+ });
- return Sensor;
- }]);
-})();
-(function() {
- 'use strict';
+ $scope.$on('hideChartSpinner', function() {
+ vm.loadingChart = false;
+ });
- angular.module('app.components')
- .factory('SearchResultLocation', ['SearchResult', function(SearchResult) {
+ $scope.$on('$destroy', function() {
+ focused = false;
+ $timeout.cancel(vm.updateTimeout);
+ });
- /**
- * Search Result Location constructor
- * @extends SearchResult
- * @param {Object} object - Object that contains the search result data from API
- * @property {number} lat - Latitude
- * @property {number} lng - Longitude
- */
- function SearchResultLocation(object) {
- SearchResult.call(this, object);
+ $scope.$on('$viewContentLoaded', function(event){
+ initialize();
+ });
- this.lat = object.latitude;
- this.lng = object.longitude;
- this.layer = object.layer;
- }
- return SearchResultLocation;
- }]);
+ function initialize() {
+ animation.viewLoaded();
+ updatePeriodically();
+ }
-})();
+ function pollAndUpdate(){
+ vm.updateTimeout = $timeout(function() {
+ updatePeriodically();
+ }, vm.updateInterval);
+ }
-(function() {
- 'use strict';
+ function updatePeriodically(){
+ getAndUpdateDevice().then(function(){
+ pollAndUpdate();
+ });
+ }
- angular.module('app.components')
- .factory('SearchResult', ['searchUtils', function(searchUtils) {
+ function getAndUpdateDevice(){
+ // TODO: Improvement UX Change below to && to avoid constant unhandled error
+ // Through reject is not possible
+ if (vm.deviceID || !isNaN(vm.deviceID)){
+ return device.getDevice(vm.deviceID)
+ .then(function(deviceData) {
+ if (deviceData.is_private) {
+ deviceIsPrivate();
+ }
+ var newDevice = new FullDevice(deviceData);
+ vm.prevDevice = vm.device;
- /**
- * Search Result constructor
- * @param {Object} object - Object that belongs to a search result from API
- * @property {string} type - Type of search result. Ex: Country, City, User, Device
- * @property {number} id - ID of search result, only for user & device
- * @property {string} name - Name of search result, only for user & device
- * @property {string} location - Location of search result. Ex: 'Paris, France'
- * @property {string} icon - URL for the icon that belongs to this search result
- * @property {string} iconType - Type of icon. Can be either img or div
- */
-
- function SearchResult(object) {
- this.type = object.type;
- this.id = object.id;
- this.name = searchUtils.parseName(object);
- this.location = searchUtils.parseLocation(object);
- this.icon = searchUtils.parseIcon(object, this.type);
- this.iconType = searchUtils.parseIconType(this.type);
- }
- return SearchResult;
- }]);
-})();
-
-(function () {
- 'use strict';
+ if (vm.prevDevice) {
+ /* Kit already loaded. We are waiting for updates */
+ if (vm.prevDevice.state.name !== 'has published' && newDevice.state.name === 'has published'){
+ /* The kit has just published data for the first time. Fully reload the view */
+ return $q.reject({justPublished: true});
+ } else if(new Date(vm.prevDevice.lastReadingAt.raw) >= new Date(newDevice.lastReadingAt.raw)) {
+ /* Break if there's no new data*/
+ return $q.reject();
+ }
+ }
- angular.module('app.components')
- .factory('PreviewDevice', ['Device', function (Device) {
+ vm.device = newDevice;
+ setOwnerSampleDevices();
- /**
- * Preview Device constructor.
- * Used for devices stacked in a list, like in User Profile or Device states
- * @extends Device
- * @constructor
- * @param {Object} object - Object with all the data about the device from the API
- */
- function PreviewDevice(object) {
- Device.call(this, object);
+ if (vm.device.state.name === 'has published') {
+ /* Device has data */
+ setDeviceOnMap();
+ setChartTimeRange();
+ deviceAnnouncements();
- this.dropdownOptions = [];
- this.dropdownOptions.push({ text: 'EDIT', value: '1', href: 'kits/' + this.id + '/edit', icon: 'fa fa-edit' });
- this.dropdownOptions.push({ text: 'SD CARD UPLOAD', value: '2', href: 'kits/' + this.id + '/upload', icon: 'fa fa-sd-card' });
+ /*Load sensor if it has already published*/
+ return $q.all([getMainSensors(vm.device), getCompareSensors(vm.device)]);
+ } else {
+ /* Device just loaded and has no data yet */
+ return $q.reject({noSensorData: true});
+ }
+ })
+ .then(setSensors, killSensorsLoading);
}
- PreviewDevice.prototype = Object.create(Device.prototype);
- PreviewDevice.prototype.constructor = Device;
- return PreviewDevice;
- }]);
-})();
-
-(function() {
- 'use strict';
+ }
- angular.module('app.components')
- .factory('HasSensorDevice', ['Device', function(Device) {
+ function killSensorsLoading(error){
+ if(error) {
+ if(error.status === 404) {
+ $state.go('layout.404');
+ }
+ else if (error.justPublished) {
+ $state.transitionTo($state.current, {reloadMap: true, id: vm.deviceID}, {
+ reload: true, inherit: false, notify: true
+ });
+ }
+ else if (error.noSensorData) {
+ deviceHasNoData();
+ }
+ else if (error.status === 403){
+ deviceIsPrivate();
+ }
+ }
+ }
- function HasSensorDevice(object) {
- Device.call(this, object);
+ function deviceAnnouncements(){
+ if(!timeUtils.isWithin(1, 'months', vm.device.lastReadingAt.raw)) {
+ //TODO: Cosmetic Update the message
+ alert.info.longTime();
+ }
+ /* The device has just published data after not publishing for 15min */
+ else if(vm.prevDevice && timeUtils.isDiffMoreThan15min(vm.prevDevice.lastReadingAt.raw, vm.device.lastReadingAt.raw)) {
+ alert.success('Your Kit just published again!');
+ }
+ }
- this.sensors = object.data.sensors;
- this.longitude = object.data.location.longitude;
- this.latitude = object.data.location.latitude;
+ function deviceHasNoData() {
+ vm.deviceWithoutData = true;
+ animation.deviceWithoutData({device: vm.device, belongsToUser:vm.deviceBelongsToUser});
+ if(vm.deviceBelongsToUser) {
+ alert.info.noData.owner($stateParams.id);
+ } else {
+ alert.info.noData.visitor();
}
+ }
- HasSensorDevice.prototype = Object.create(Device.prototype);
- HasSensorDevice.prototype.constructor = Device;
+ function deviceIsPrivate() {
+ alert.info.noData.private();
+ }
- HasSensorDevice.prototype.sensorsHasData = function() {
- var parsedSensors = this.sensors.map(function(sensor) {
- return sensor.value;
+ function setOwnerSampleDevices() {
+ // TODO: Refactor - this information is in the user, no need to go to devices
+ getOwnerDevices(vm.device, -6)
+ .then(function(ownerDevices){
+ vm.sampleDevices = ownerDevices;
});
+ }
- return _.some(parsedSensors, function(sensorValue) {
- return !!sensorValue;
- });
- };
+ function setChartTimeRange() {
+ if(vm.allowUpdateChart) {
+ /* Init the chart range to default if doesn't exist of the user hasn't interacted */
+ picker = initializePicker();
+ }
+ }
- return HasSensorDevice;
- }]);
-})();
+ function setDeviceOnMap() {
+ animation.deviceLoaded({lat: vm.device.latitude, lng: vm.device.longitude,
+ id: vm.device.id});
+ }
-(function() {
- 'use strict';
+ function setSensors(sensorsRes){
- angular.module('app.components')
- .factory('FullDevice', ['Device', 'Sensor', 'deviceUtils', function(Device, Sensor, deviceUtils) {
+ var mainSensors = sensorsRes[0];
+ var compareSensors = sensorsRes[1];
- /**
- * Full Device constructor.
- * @constructor
- * @extends Device
- * @param {Object} object - Object with all the data about the device from the API
- * @property {Object} owner - Device owner data
- * @property {Array} data - Device sensor's data
- * @property {Array} sensors - Device sensors data
- * @property {Array} postProcessing - Device postprocessing
- */
- function FullDevice(object) {
- Device.call(this, object);
+ vm.battery = _.find(mainSensors, {name: 'battery'});
+ vm.sensors = mainSensors.reverse();
+ vm.sensors.forEach(checkRaw);
+ vm.sensors.forEach(getHardwareName);
- this.owner = deviceUtils.parseOwner(object);
- this.postProcessing = object.postprocessing;
- this.data = object.data;
- this.sensors = object.data.sensors;
- }
+ setSensorSideChart();
- FullDevice.prototype = Object.create(Device.prototype);
- FullDevice.prototype.constructor = FullDevice;
+ if (!vm.selectedSensor) {
+ vm.chartSensors = vm.sensors;
+ vm.sensorsToCompare = compareSensors;
+ vm.selectedSensor = (vm.sensors && vm.sensors[0]) ? vm.sensors[0].id : undefined;
+ }
- FullDevice.prototype.getSensors = function(options) {
- var sensors = _(this.data.sensors)
- .chain()
- .map(function(sensor) {
- return new Sensor(sensor);
- }).sort(function(a, b) {
- /* This is a temporary hack to set always PV panel at the end*/
- if (a.id === 18){ return -1;}
- if (b.id === 18){ return 1;}
- /* This is a temporary hack to set always the Battery at the end*/
- if (a.id === 17){ return -1;}
- if (b.id === 17){ return 1;}
- /* This is a temporary hack to set always the Battery at the end*/
- if (a.id === 10){ return -1;}
- if (b.id === 10){ return 1;}
- /* After the hacks, sort the sensors by id */
- return b.id - a.id;
- })
- .tap(function(sensors) {
- if(options.type === 'compare') {
- sensors.unshift({
- name: 'NONE',
- color: 'white',
- id: -1
- });
- }
- })
- .value();
- return sensors;
- };
+ animation.mapStateLoaded();
+ }
- return FullDevice;
- }]);
-})();
+ function checkRaw(value){
+ vm.hasRaw |= (value.tags.indexOf('raw') !== -1);
+ }
-(function() {
- 'use strict';
+ function getHardwareName(value) {
+ vm.sensorNames[value.id] = vm.device.sensors.find(element => element.id === value.id).name;
+ }
+ function setSensorSideChart() {
+ if(vm.sensors){
+ vm.sensors.forEach(function(sensor) {
+ if(sensor.id === vm.selectedSensor) {
+ _.extend(vm.selectedSensorData, sensor);
+ }
+ });
+ }
+ }
- angular.module('app.components')
- .factory('Device', ['deviceUtils', 'timeUtils', function(deviceUtils, timeUtils) {
+ function removeDevice() {
+ var confirm = $mdDialog.confirm()
+ .title('Delete this kit?')
+ .textContent('Are you sure you want to delete this kit?')
+ .ariaLabel('')
+ .ok('DELETE')
+ .cancel('Cancel')
+ .theme('primary')
+ .clickOutsideToClose(true);
- /**
- * Device constructor.
- * @constructor
- * @param {Object} object - Object with all the data about the device from the API
- * @property {number} id - ID of the device
- * @property {string} name - Name of the device
- * @property {string} state - State of the device. Ex: Never published
- * @property {string} description - Device description
- * @property {Array} systemTags - System tags
- * @property {Array} userTags - User tags. Ex: ''
- * @property {bool} isPrivate - True if private device
- * @property {Array} notifications - Notifications for low battery and stopped publishing
- * @property {Object} lastReadingAt - last_reading_at: raw, ago, and parsed
- * @property {Object} createdAt - created_at: raw, ago, and parsed
- * @property {Object} updatedAt - updated_at: raw, ago, and parsed
- * @property {Object} location - Location of device. Object with lat, long, elevation, city, country, country_code
- * @property {string} locationString - Location of device. Ex: Madrid, Spain; Germany; Paris, France
- * @property {Object} hardware - Device hardware field. Contains type, version, info, slug and name
- * @property {string} hardwareName - Device hardware name
- * @property {bool} isLegacy - True if legacy device
- * @property {bool} isSCK - True if SC device
- * @property {string} avatar - URL that contains the user avatar
- */
- function Device(object) {
- // Basic information
- this.id = object.id;
- this.name = object.name;
- this.state = deviceUtils.parseState(object);
- this.description = object.description;
- this.token = object.device_token;
- this.macAddress = object.mac_address;
+ $mdDialog
+ .show(confirm)
+ .then(function(){
+ device
+ .removeDevice(vm.device.id)
+ .then(function(){
+ alert.success('Your kit was deleted successfully');
+ device.updateContext().then(function(){
+ $state.transitionTo('layout.myProfile.kits', $stateParams,
+ { reload: false,
+ inherit: false,
+ notify: true
+ });
+ });
+ })
+ .catch(function(){
+ alert.error('Error trying to delete your kit.');
+ });
+ });
+ }
- // Tags and dates
- this.systemTags = deviceUtils.parseSystemTags(object);
- this.userTags = deviceUtils.parseUserTags(object);
- this.isPrivate = deviceUtils.isPrivate(object);
- this.preciseLocation = deviceUtils.preciseLocation(object);
- this.enableForwarding = deviceUtils.enableForwarding(object);
- this.notifications = deviceUtils.parseNotifications(object);
- this.lastReadingAt = timeUtils.parseDate(object.last_reading_at);
- this.createdAt = timeUtils.parseDate(object.created_at);
- this.updatedAt = timeUtils.parseDate(object.updated_at);
+ function showSensorOnChart(sensorID) {
+ vm.selectedSensor = sensorID;
+ }
- // Location
- this.location = object.location;
- this.locationString = deviceUtils.parseLocation(object);
+ function slide(direction) {
+ var slideContainer = angular.element('.sensors_container');
+ var scrollPosition = slideContainer.scrollLeft();
+ var width = slideContainer.width();
+ var slideStep = width/2;
- // Hardware
- this.hardware = deviceUtils.parseHardware(object);
- this.hardwareName = deviceUtils.parseHardwareName(this);
- this.isLegacy = deviceUtils.isLegacyVersion(this);
- this.isSCK = deviceUtils.isSCKHardware(this);
- // this.class = deviceUtils.classify(object); // TODO - Do we need this?
+ if(direction === 'left') {
+ slideContainer.animate({'scrollLeft': scrollPosition + slideStep},
+ {duration: 250, queue:false});
+ } else if(direction === 'right') {
+ slideContainer.animate({'scrollLeft': scrollPosition - slideStep},
+ {duration: 250, queue:false});
+ }
+ }
- this.avatar = deviceUtils.parseAvatar();
- /*jshint camelcase: false */
+ function getSensorsToCompare() {
+ return vm.sensors ? vm.sensors.filter(function(sensor) {
+ return sensor.id !== vm.selectedSensor;
+ }) : [];
+ }
+
+ function changeChart(sensorsID, options) {
+ if(!sensorsID[0]) {
+ return;
}
- return Device;
- }]);
-})();
+ if(!options) {
+ options = {};
+ }
+ options.from = options && options.from || picker.getValuePickerFrom();
+ options.to = options && options.to || picker.getValuePickerTo();
-(function() {
- 'use strict';
+ //show spinner
+ vm.loadingChart = true;
+ //grab chart data and save it
- angular.module('app.components')
- .factory('Marker', ['deviceUtils', 'markerUtils', 'timeUtils', '$state', function(deviceUtils, markerUtils, timeUtils, $state) {
- /**
- * Marker constructor
- * @constructor
- * @param {Object} deviceData - Object with data about marker from API
- * @property {number} lat - Latitude
- * @property {number} lng - Longitude
- * @property {string} message - Message inside marker popup
- * @property {Object} icon - Object with classname, size and type of marker icon
- * @property {string} layer - Map layer that icons belongs to
- * @property {boolean} focus - Whether marker popup is opened
- * @property {Object} myData - Marker id and labels
- */
- function Marker(deviceData) {
- let linkStart = '', linkEnd = '';
- const id = deviceData.id;
- if ($state.$current.name === 'embbed') {
- linkStart = '';
- linkEnd = '';
- }
- this.lat = deviceUtils.parseCoordinates(deviceData).lat;
- this.lng = deviceUtils.parseCoordinates(deviceData).lng;
- // TODO: Bug, pop-up lastreading at doesn't get updated by publication
- this.message = '';
+ // it can be either 2 sensors or 1 sensor, so we use $q.all to wait for all
+ $q.all(
+ _.map(sensorsID, function(sensorID) {
+ return getChartData($stateParams.id, sensorID, options.from, options.to)
+ .then(function(data) {
+ return data;
+ });
+ })
+ ).then(function() {
+ // after all sensors resolve, prepare data and attach it to scope
+ // the variable on the scope will pass the data to the chart directive
+ vm.chartDataMain = prepareChartData([mainSensorID, compareSensorID]);
+ });
+ }
+ // calls api to get sensor data and saves it to sensorsData array
+ function getChartData(deviceID, sensorID, dateFrom, dateTo, options) {
+ return sensor.getSensorsData(deviceID, sensorID, dateFrom, dateTo)
+ .then(function(data) {
+ //save sensor data of this kit so that it can be reused
+ sensorsData[sensorID] = data.readings;
+ return data;
+ });
+ }
- this.icon = markerUtils.getIcon(deviceData);
- this.layer = 'devices';
- this.focus = false;
- this.myData = {
- id: id,
- labels: deviceUtils.parseSystemTags(deviceData),
- tags: deviceUtils.parseUserTags(deviceData)
+ function prepareChartData(sensorsID) {
+ var compareSensor;
+ var parsedDataMain = parseSensorData(sensorsData, sensorsID[0]);
+ var mainSensor = {
+ data: parsedDataMain,
+ color: vm.selectedSensorData.color,
+ unit: vm.selectedSensorData.unit
+ };
+ if(sensorsID[1] && sensorsID[1] !== -1) {
+ var parsedDataCompare = parseSensorData(sensorsData, sensorsID[1]);
+
+ compareSensor = {
+ data: parsedDataCompare,
+ color: vm.selectedSensorToCompareData.color,
+ unit: vm.selectedSensorToCompareData.unit
};
}
- return Marker;
-
- function createTagsTemplate(tagsArr, tagType, clickable) {
- if(typeof(clickable) === 'undefined'){
- clickable = false;
- }
- var clickablTag = '';
- if(clickable){
- clickablTag = 'clickable';
- }
+ var newChartData = [mainSensor, compareSensor];
+ return newChartData;
+ }
- if(!tagType){
- tagType = 'tag';
- }
+ function parseSensorData(data, sensorID) {
+ if(data.length === 0) {
+ return [];
+ }
+ return data[sensorID].map(function(dataPoint) {
+ var time = timeUtils.formatDate(dataPoint[0]);
+ var value = dataPoint[1];
+ var count = value === null ? 0 : value;
+ return {
+ time: time,
+ count: count,
+ value: value
+ };
+ });
+ }
- return _.reduce(tagsArr, function(acc, label) {
- var element ='';
- if(tagType === 'tag'){
- element = '';
- }else{
- element = ''+label+'';
- }
- return acc.concat(element);
- }, '');
+ function setSensor(options) {
+ var sensorID = options.value;
+ if(sensorID === undefined) {
+ return;
+ }
+ if(options.type === 'main') {
+ mainSensorID = sensorID;
+ } else if(options.type === 'compare') {
+ compareSensorID = sensorID;
}
+ }
- }]);
-})();
+ function colorSensorCompareName() {
+ var name = angular.element('.sensor_compare').find('md-select-label').find('span');
+ name.css('color', vm.selectedSensorToCompareData.color || 'white');
+ var icon = angular.element('.sensor_compare').find('md-select-label').find('.md-select-icon');
+ icon.css('color', 'white');
+ }
-(function() {
- 'use strict';
+ function getCurrentRange() {
+ var to = moment(picker.getValuePickerTo());
+ var from = moment(picker.getValuePickerFrom());
+ return to.diff(from)/1000;
+ }
- angular.module('app.components')
- .directive('noDataBackdrop', noDataBackdrop);
+ function moveChart(direction) {
- /**
- * Backdrop for chart section when kit has no data
- *
- */
- noDataBackdrop.$inject = [];
-
- function noDataBackdrop() {
- return {
- restrict: 'A',
- scope: {},
- templateUrl: 'app/core/animation/backdrop/noDataBackdrop.html',
- controller: function($scope, $timeout) {
- var vm = this;
-
- vm.deviceWithoutData = false;
- vm.scrollToComments = scrollToComments;
-
- $scope.$on('deviceWithoutData', function(ev, data) {
+ var valueTo, valueFrom;
+ //grab current date range
+ var currentRange = getCurrentRange();
- $timeout(function() {
- vm.device = data.device;
- vm.deviceWithoutData = true;
+ /*jshint camelcase: false*/
+ var from_picker = angular.element('#picker_from').pickadate('picker');
+ var to_picker = angular.element('#picker_to').pickadate('picker');
- if (data.belongsToUser) {
- vm.user = 'owner';
- } else {
- vm.user = 'visitor';
- }
- }, 0);
+ if(direction === 'left') {
+ //set both from and to pickers to prev range
+ valueTo = moment(picker.getValuePickerFrom());
+ valueFrom = moment(picker.getValuePickerFrom()).subtract(currentRange, 'seconds');
- });
+ picker.setValuePickers([valueFrom.toDate(), valueTo.toDate()]);
- function scrollToComments(){
- location.hash = '';
- location.hash = '#disqus_thread';
+ } else if(direction === 'right') {
+ var today = timeUtils.getToday();
+ var currentValueTo = picker.getValuePickerTo();
+ if( timeUtils.isSameDay(today, timeUtils.getMillisFromDate(currentValueTo)) ) {
+ return;
}
- },
- controllerAs: 'vm'
- };
- }
-})();
-(function() {
- 'use strict';
-
- angular.module('app.components')
- .directive('loadingBackdrop', loadingBackdrop);
-
- /**
- * Backdrop for app initialization and between states
- *
- */
- loadingBackdrop.$inject = [];
- function loadingBackdrop() {
- return {
- templateUrl: 'app/core/animation/backdrop/loadingBackdrop.html',
- controller: function($scope) {
- var vm = this;
- vm.isViewLoading = true;
- vm.mapStateLoading = false;
+ valueFrom = moment(picker.getValuePickerTo());
+ valueTo = moment(picker.getValuePickerTo()).add(currentRange, 'seconds');
- // listen for app loading event
- $scope.$on('viewLoading', function() {
- vm.isViewLoading = true;
- });
+ picker.setValuePickers([valueFrom.toDate(), valueTo.toDate()]);
- $scope.$on('viewLoaded', function() {
- vm.isViewLoading = false;
- });
+ }
+ resetTimeOpts();
+ }
- // listen for map state loading event
- $scope.$on('mapStateLoading', function() {
- if(vm.isViewLoading) {
- return;
- }
- vm.mapStateLoading = true;
- });
+ //hide everything but the functions to interact with the pickers
+ function initializePicker() {
+ var range = {};
+ /*jshint camelcase: false*/
+ var from_$input = angular.element('#picker_from').pickadate({
+ onOpen: function(){
+ vm.resetTimeOpts();
+ },
+ onClose: function(){
+ angular.element(document.activeElement).blur();
+ },
+ container: 'body',
+ klass: {
+ holder: 'picker__holder picker_container'
+ }
+ });
+ var from_picker = from_$input.pickadate('picker');
- $scope.$on('mapStateLoaded', function() {
- vm.mapStateLoading = false;
- });
+ var to_$input = angular.element('#picker_to').pickadate({
+ onOpen: function(){
+ vm.resetTimeOpts();
},
- controllerAs: 'vm'
- };
- }
-})();
+ onClose: function(){
+ angular.element(document.activeElement).blur();
+ },
+ container: 'body',
+ klass: {
+ holder: 'picker__holder picker_container'
+ }
+ });
-(function() {
- 'use strict';
+ var to_picker = to_$input.pickadate('picker');
- angular.module('app.components')
- .controller('KitController', KitController);
+ if( from_picker.get('value') ) {
+ to_picker.set('min', from_picker.get('select') );
+ }
+ if( to_picker.get('value') ) {
+ from_picker.set('max', to_picker.get('select') );
+ }
- KitController.$inject = ['$state','$scope', '$stateParams',
- 'sensor', 'FullDevice', '$mdDialog', 'belongsToUser',
- 'timeUtils', 'animation', 'auth',
- '$timeout', 'alert', '$q', 'device',
- 'HasSensorDevice', 'geolocation', 'PreviewDevice'];
- function KitController($state, $scope, $stateParams,
- sensor, FullDevice, $mdDialog, belongsToUser,
- timeUtils, animation, auth,
- $timeout, alert, $q, device,
- HasSensorDevice, geolocation, PreviewDevice) {
+ from_picker.on('close', function(event) {
+ setFromRange(getCalculatedFrom(from_picker.get('value')));
+ });
- var vm = this;
- var sensorsData = [];
+ to_picker.on('close', function(event) {
+ setToRange(getCalculatedTo(to_picker.get('value')));
+ });
- var mainSensorID, compareSensorID;
- var picker;
- vm.deviceID = $stateParams.id;
- vm.battery = {};
- vm.downloadData = downloadData;
- vm.geolocate = geolocate;
- vm.device = undefined;
- vm.deviceBelongsToUser = belongsToUser;
- vm.deviceWithoutData = false;
- vm.legacyApiKey = belongsToUser ?
- auth.getCurrentUser().data.key :
- undefined;
- vm.loadingChart = true;
- vm.moveChart = moveChart;
- vm.allowUpdateChart = true;
- vm.ownerDevices = [];
- vm.removeDevice = removeDevice;
- vm.resetTimeOpts = resetTimeOpts;
- vm.sampleDevices = [];
- vm.selectedSensor = undefined;
- vm.selectedSensorData = {};
- vm.selectedSensorToCompare = undefined;
- vm.selectedSensorToCompareData = {};
- vm.sensors = [];
- vm.chartSensors = [];
- vm.sensorsToCompare = [];
- vm.setFromLast = setFromLast;
- vm.showSensorOnChart = showSensorOnChart;
- vm.showStore = showStore;
- vm.slide = slide;
- vm.showRaw = false;
- vm.timeOpt = ['60 minutes', 'day' , 'month'];
- vm.timeOptSelected = timeOptSelected;
- vm.updateInterval = 15000;
- vm.hasRaw;
- vm.sensorNames = {};
+ from_picker.on('set', function(event) {
+ if(event.select) {
+ to_picker.set('min', getFromRange());
+ } else if( 'clear' in event) {
+ to_picker.set('min', false);
+ }
+ });
- var focused = true;
+ to_picker.on('set', function(event) {
+ if(event.select) {
+ from_picker.set('max', getToRange());
+ } else if( 'clear' in event) {
+ from_picker.set('max', false);
+ }
+ });
- // event listener on change of value of main sensor selector
- $scope.$watch('vm.selectedSensor', function(newVal) {
+ //set to-picker max to today
+ to_picker.set('max', getLatestUpdated());
- // Prevents undisered calls if selected sensor is not yet defined
- if (!newVal) {
- return;
+ function getSevenDaysAgoFromLatestUpdate() {
+ var lastTime = moment(vm.device.lastReadingAt.raw);
+ return lastTime.subtract(7, 'days').valueOf();
}
- vm.selectedSensorToCompare = undefined;
- vm.selectedSensorToCompareData = {};
- vm.chartDataCompare = [];
- compareSensorID = undefined;
+ function getLatestUpdated() {
+ return moment(vm.device.lastReadingAt.raw).toDate();
+ }
- setSensorSideChart();
+ function getCalculatedFrom(pickerTimeFrom) {
+ var from,
+ pickerTime;
- vm.sensorsToCompare = getSensorsToCompare();
+ pickerTime = moment(pickerTimeFrom, 'D MMMM, YYYY');
+ from = pickerTime.startOf('day');
- $timeout(function() {
- // TODO: Improvement, change how we set the colors
- colorSensorCompareName();
- setSensor({type: 'main', value: newVal});
+ return from;
+ }
- if (picker){
- changeChart([mainSensorID]);
- }
- }, 100);
+ function getCalculatedTo(pickerTimeTo) {
+ var to,
+ pickerTime;
- });
+ pickerTime = moment(pickerTimeTo, 'D MMMM, YYYY');
- // event listener on change of value of compare sensor selector
- $scope.$watch('vm.selectedSensorToCompare', function(newVal, oldVal) {
- vm.sensorsToCompare.forEach(function(sensor) {
- if(sensor.id === newVal) {
- _.extend(vm.selectedSensorToCompareData, sensor);
- }
- });
+ to = pickerTime.endOf('day');
+ if (moment().diff(to) < 0) {
+ var now = moment();
+ to = pickerTime.set({
+ 'hour' : now.get('hour'),
+ 'minute' : now.get('minute'),
+ 'second' : now.get('second')
+ });
+ }
- $timeout(function() {
- colorSensorCompareName();
- setSensor({type: 'compare', value: newVal});
+ return to;
+ }
- if(oldVal === undefined && newVal === undefined) {
- return;
- }
- changeChart([compareSensorID]);
- }, 100);
+ function updateChart() {
+ var sensors = [mainSensorID, compareSensorID];
+ sensors = sensors.filter(function(sensor) {
+ return sensor;
+ });
+ changeChart(sensors, {
+ from: range.from,
+ to: range.to
+ });
+ }
- });
+ function setFromRange(from) {
+ range.from = from;
+ from_picker.set('select', getFromRange());
+ updateChart();
+ }
- $scope.$on('hideChartSpinner', function() {
- vm.loadingChart = false;
- });
+ function setToRange(to) {
+ range.to = to;
+ to_picker.set('select', getToRange());
+ updateChart();
+ }
- $scope.$on('$destroy', function() {
- focused = false;
- $timeout.cancel(vm.updateTimeout);
- });
+ function getFromRange() {
+ return moment(range.from).toDate();
+ }
- $scope.$on('$viewContentLoaded', function(event){
- initialize();
- });
+ function getToRange() {
+ return moment(range.to).toDate();
+ }
- function initialize() {
- animation.viewLoaded();
- updatePeriodically();
- }
+ function setRange(from, to) {
+ range.from = from;
+ range.to = to;
+ from_picker.set('select', getFromRange());
+ to_picker.set('select', getToRange());
+ updateChart();
+ }
- function pollAndUpdate(){
- vm.updateTimeout = $timeout(function() {
- updatePeriodically();
- }, vm.updateInterval);
- }
+ if(vm.device){
+ if(vm.device.systemTags.includes('new')){
+ var lastUpdate = getLatestUpdated();
+ setRange(timeUtils.getHourBefore(lastUpdate), lastUpdate);
+ } else if (timeUtils.isWithin(7, 'days', vm.device.lastReadingAt.raw) || !vm.device.lastReadingAt.raw) {
+ //set from-picker to seven days ago and set to-picker to today
+ setRange(timeUtils.getSevenDaysAgo(), timeUtils.getToday());
+ } else {
+ // set from-picker to and set to-picker to today
+ setRange(getSevenDaysAgoFromLatestUpdate(), getLatestUpdated());
+ }
+ }
- function updatePeriodically(){
- getAndUpdateDevice().then(function(){
- pollAndUpdate();
- });
+ // api to interact with the picker from outside
+ return {
+ getValuePickerFrom: function() {
+ return getFromRange();
+ },
+ setValuePickerFrom: function(newValue) {
+ setFromRange(newValue);
+ },
+ getValuePickerTo: function() {
+ return getToRange();
+ },
+ setValuePickerTo: function(newValue) {
+ setToRange(newValue);
+ },
+ setValuePickers: function(newValues) {
+ var from = newValues[0];
+ var to = newValues[1];
+ setRange(from, to);
+ }
+ };
}
- function getAndUpdateDevice(){
- // TODO: Improvement UX Change below to && to avoid constant unhandled error
- // Through reject is not possible
- if (vm.deviceID || !isNaN(vm.deviceID)){
- return device.getDevice(vm.deviceID)
- .then(function(deviceData) {
- if (deviceData.is_private) {
- deviceIsPrivate();
- }
- var newDevice = new FullDevice(deviceData);
- vm.prevDevice = vm.device;
-
- if (vm.prevDevice) {
- /* Kit already loaded. We are waiting for updates */
- if (vm.prevDevice.state.name !== 'has published' && newDevice.state.name === 'has published'){
- /* The kit has just published data for the first time. Fully reload the view */
- return $q.reject({justPublished: true});
- } else if(new Date(vm.prevDevice.lastReadingAt.raw) >= new Date(newDevice.lastReadingAt.raw)) {
- /* Break if there's no new data*/
- return $q.reject();
- }
- }
+ function geolocate() {
+ if (navigator.geolocation) {
+ navigator.geolocation.getCurrentPosition(function(position){
+ if(!position){
+ alert.error('Please, allow smartcitizen to geolocate your' +
+ 'position so we can find a kit near you.');
+ return;
+ }
- vm.device = newDevice;
- setOwnerSampleDevices();
+ geolocation.grantHTML5Geolocation();
- if (vm.device.state.name === 'has published') {
- /* Device has data */
- setDeviceOnMap();
- setChartTimeRange();
- deviceAnnouncements();
+ var location = {
+ lat:position.coords.latitude,
+ lng:position.coords.longitude
+ };
+ device.getDevices(location)
+ .then(function(data){
+ data = data.plain();
- /*Load sensor if it has already published*/
- return $q.all([getMainSensors(vm.device), getCompareSensors(vm.device)]);
- } else {
- /* Device just loaded and has no data yet */
- return $q.reject({noSensorData: true});
- }
- })
- .then(setSensors, killSensorsLoading);
+ _(data)
+ .chain()
+ .map(function(device) {
+ return new HasSensorDevice(device);
+ })
+ .filter(function(device) {
+ return !!device.longitude && !!device.latitude;
+ })
+ .find(function(device) {
+ return _.includes(device.labels, 'online');
+ })
+ .tap(function(closestDevice) {
+ if(focused){
+ if(closestDevice) {
+ $state.go('layout.home.kit', {id: closestDevice.id});
+ } else {
+ $state.go('layout.home.kit', {id: data[0].id});
+ }
+ }
+ })
+ .value();
+ });
+ });
}
}
- function killSensorsLoading(error){
- if(error) {
- if(error.status === 404) {
- $state.go('layout.404');
- }
- else if (error.justPublished) {
- $state.transitionTo($state.current, {reloadMap: true, id: vm.deviceID}, {
- reload: true, inherit: false, notify: true
- });
- }
- else if (error.noSensorData) {
- deviceHasNoData();
- }
- else if (error.status === 403){
- deviceIsPrivate();
+ function downloadData(device){
+ $mdDialog.show({
+ hasBackdrop: true,
+ controller: 'DownloadModalController',
+ controllerAs: 'vm',
+ templateUrl: 'app/components/download/downloadModal.html',
+ clickOutsideToClose: true,
+ locals: {thisDevice: device}
+ }).then(function(){
+ var alert = $mdDialog.alert()
+ .title('SUCCESS')
+ .textContent('We are processing your data. Soon you will be notified in your inbox')
+ .ariaLabel('')
+ .ok('OK!')
+ .theme('primary')
+ .clickOutsideToClose(true);
+
+ $mdDialog.show(alert);
+ }).catch(function(err){
+ if (!err){
+ return;
}
- }
- }
+ var errorAlert = $mdDialog.alert()
+ .title('ERROR')
+ .textContent('Uh-oh, something went wrong')
+ .ariaLabel('')
+ .ok('D\'oh')
+ .theme('primary')
+ .clickOutsideToClose(false);
- function deviceAnnouncements(){
- if(!timeUtils.isWithin(1, 'months', vm.device.lastReadingAt.raw)) {
- //TODO: Cosmetic Update the message
- alert.info.longTime();
- }
- /* The device has just published data after not publishing for 15min */
- else if(vm.prevDevice && timeUtils.isDiffMoreThan15min(vm.prevDevice.lastReadingAt.raw, vm.device.lastReadingAt.raw)) {
- alert.success('Your Kit just published again!');
- }
+ $mdDialog.show(errorAlert);
+ });
}
- function deviceHasNoData() {
- vm.deviceWithoutData = true;
- animation.deviceWithoutData({device: vm.device, belongsToUser:vm.deviceBelongsToUser});
- if(vm.deviceBelongsToUser) {
- alert.info.noData.owner($stateParams.id);
- } else {
- alert.info.noData.visitor();
+ function getMainSensors(deviceData) {
+ if(!deviceData) {
+ return undefined;
}
+ return deviceData.getSensors({type: 'main'});
}
-
- function deviceIsPrivate() {
- alert.info.noData.private();
+ function getCompareSensors(deviceData) {
+ if(!vm.device) {
+ return undefined;
+ }
+ deviceData.getSensors({type: 'compare'});
}
-
- function setOwnerSampleDevices() {
- // TODO: Refactor - this information is in the user, no need to go to devices
- getOwnerDevices(vm.device, -6)
- .then(function(ownerDevices){
- vm.sampleDevices = ownerDevices;
- });
+ function getOwnerDevices(deviceData, sampling) {
+ if(!deviceData) {
+ return undefined;
+ }
+ var deviceIDs = deviceData.owner.devices.slice(sampling);
+ // var ownerID = deviceData.owner.id;
+ // TODO: Refactor This is in the user endpoint, no need to query devices
+ return $q.all(
+ deviceIDs.map(function(id) {
+ return device.getDevice(id)
+ .then(function(data) {
+ return new PreviewDevice(data);
+ });
+ })
+ );
}
- function setChartTimeRange() {
- if(vm.allowUpdateChart) {
- /* Init the chart range to default if doesn't exist of the user hasn't interacted */
- picker = initializePicker();
+ function setFromLast(what){
+ /* This will not show the last 60 minutes or 24 hours,
+ instead it will show the last hour or day*/
+ var to, from;
+ if (what === '60 minutes') {
+ to = moment(vm.device.lastReadingAt.raw);
+ from = moment(vm.device.lastReadingAt.raw).subtract(60, 'minutes');
+ } else {
+ to = moment(vm.device.lastReadingAt.raw).endOf(what);
+ from = moment(vm.device.lastReadingAt.raw).startOf(what);
+ }
+ // Check if we are in the future
+ if (moment().diff(to) < 0){
+ to = moment(vm.device.lastReadingAt.raw);
}
+ picker.setValuePickers([from.toDate(), to.toDate()]);
}
- function setDeviceOnMap() {
- animation.deviceLoaded({lat: vm.device.latitude, lng: vm.device.longitude,
- id: vm.device.id});
+ function timeOptSelected(){
+ vm.allowUpdateChart = false;
+ if (vm.dropDownSelection){
+ setFromLast(vm.dropDownSelection);
+ }
+ }
+ function resetTimeOpts(){
+ vm.allowUpdateChart = false;
+ vm.dropDownSelection = undefined;
}
- function setSensors(sensorsRes){
+ function showStore() {
+ $mdDialog.show({
+ hasBackdrop: true,
+ controller: 'StoreModalController',
+ templateUrl: 'app/components/store/storeModal.html',
+ clickOutsideToClose: true
+ });
+ }
+ }
+})();
- var mainSensors = sensorsRes[0];
- var compareSensors = sensorsRes[1];
+(function() {
+ 'use strict';
- vm.battery = _.find(mainSensors, {name: 'battery'});
- vm.sensors = mainSensors.reverse();
- vm.sensors.forEach(checkRaw);
- vm.sensors.forEach(getHardwareName);
+ angular.module('app.components')
+ .controller('NewKitController', NewKitController);
- setSensorSideChart();
+ NewKitController.$inject = ['$scope', '$state', 'animation', 'device', 'tag', 'alert', 'auth', '$stateParams', '$timeout'];
+ function NewKitController($scope, $state, animation, device, tag, alert, auth, $stateParams, $timeout) {
+ var vm = this;
- if (!vm.selectedSensor) {
- vm.chartSensors = vm.sensors;
- vm.sensorsToCompare = compareSensors;
- vm.selectedSensor = (vm.sensors && vm.sensors[0]) ? vm.sensors[0].id : undefined;
- }
+ vm.step = 1;
+ vm.submitStepOne = submitStepOne;
+ vm.backToProfile = backToProfile;
- animation.mapStateLoaded();
- }
+ // FORM INFO
+ vm.deviceForm = {
+ name: undefined,
+ exposure: undefined,
+ location: {
+ lat: undefined,
+ lng: undefined,
+ zoom: 16
+ },
+ is_private: false,
+ legacyVersion: '1.1',
+ tags: []
+ };
- function checkRaw(value){
- vm.hasRaw |= (value.tags.indexOf('raw') !== -1);
- }
+ // EXPOSURE SELECT
+ vm.exposure = [
+ {name: 'indoor', value: 1},
+ {name: 'outdoor', value: 2}
+ ];
- function getHardwareName(value) {
- vm.sensorNames[value.id] = vm.device.sensors.find(element => element.id === value.id).name;
- }
- function setSensorSideChart() {
- if(vm.sensors){
- vm.sensors.forEach(function(sensor) {
- if(sensor.id === vm.selectedSensor) {
- _.extend(vm.selectedSensorData, sensor);
- }
- });
- }
- }
+ // VERSION SELECT
+ vm.version = [
+ {name: 'Smart Citizen Kit 1.0', value: '1.0'},
+ {name: 'Smart Citizen Kit 1.1', value: '1.1'}
+ ];
- function removeDevice() {
- var confirm = $mdDialog.confirm()
- .title('Delete this kit?')
- .textContent('Are you sure you want to delete this kit?')
- .ariaLabel('')
- .ok('DELETE')
- .cancel('Cancel')
- .theme('primary')
- .clickOutsideToClose(true);
+ $scope.$on('leafletDirectiveMarker.dragend', function(event, args){
+ vm.deviceForm.location.lat = args.model.lat;
+ vm.deviceForm.location.lng = args.model.lng;
+ });
- $mdDialog
- .show(confirm)
- .then(function(){
- device
- .removeDevice(vm.device.id)
- .then(function(){
- alert.success('Your kit was deleted successfully');
- device.updateContext().then(function(){
- $state.transitionTo('layout.myProfile.kits', $stateParams,
- { reload: false,
- inherit: false,
- notify: true
- });
- });
- })
- .catch(function(){
- alert.error('Error trying to delete your kit.');
- });
+ // TAGS SELECT
+ vm.tags = [];
+ $scope.$watch('vm.tag', function(newVal, oldVal) {
+ if(!newVal) {
+ return;
+ }
+ // remove selected tag from select element
+ vm.tag = undefined;
+
+ var alreadyPushed = _.some(vm.deviceForm.tags, function(tag) {
+ return tag.id === newVal;
});
- }
+ if(alreadyPushed) {
+ return;
+ }
- function showSensorOnChart(sensorID) {
- vm.selectedSensor = sensorID;
- }
+ var tag = _.find(vm.tags, function(tag) {
+ return tag.id === newVal;
+ });
+ vm.deviceForm.tags.push(tag);
+ });
+ vm.removeTag = removeTag;
- function slide(direction) {
- var slideContainer = angular.element('.sensors_container');
- var scrollPosition = slideContainer.scrollLeft();
- var width = slideContainer.width();
- var slideStep = width/2;
+ // MAP CONFIGURATION
+ var mapBoxToken = 'pk.eyJ1IjoidG9tYXNkaWV6IiwiYSI6ImRTd01HSGsifQ.loQdtLNQ8GJkJl2LUzzxVg';
- if(direction === 'left') {
- slideContainer.animate({'scrollLeft': scrollPosition + slideStep},
- {duration: 250, queue:false});
- } else if(direction === 'right') {
- slideContainer.animate({'scrollLeft': scrollPosition - slideStep},
- {duration: 250, queue:false});
- }
- }
+ vm.getLocation = getLocation;
+ vm.markers = {
+ main: {
+ lat: undefined,
+ lng: undefined,
+ draggable: true
+ }
+ };
+ vm.tiles = {
+ url: 'https://api.mapbox.com/styles/v1/mapbox/streets-v10/tiles/{z}/{x}/{y}?access_token=' + mapBoxToken
+ };
+ vm.defaults = {
+ scrollWheelZoom: false
+ };
- function getSensorsToCompare() {
- return vm.sensors ? vm.sensors.filter(function(sensor) {
- return sensor.id !== vm.selectedSensor;
- }) : [];
- }
+ vm.macAddress = undefined;
- function changeChart(sensorsID, options) {
- if(!sensorsID[0]) {
- return;
+ initialize();
+
+ //////////////
+
+ function initialize() {
+ animation.viewLoaded();
+ getTags();
+ vm.userRole = auth.getCurrentUser().data.role;
}
- if(!options) {
- options = {};
+ function getLocation() {
+ window.navigator.geolocation.getCurrentPosition(function(position) {
+ $scope.$apply(function() {
+ var lat = position.coords.latitude;
+ var lng = position.coords.longitude;
+ vm.deviceForm.location.lat = lat;
+ vm.deviceForm.location.lng = lng;
+ vm.markers.main.lat = lat;
+ vm.markers.main.lng = lng;
+ });
+ });
}
- options.from = options && options.from || picker.getValuePickerFrom();
- options.to = options && options.to || picker.getValuePickerTo();
- //show spinner
- vm.loadingChart = true;
- //grab chart data and save it
+ function removeTag(tagID) {
+ vm.deviceForm.tags = _.filter(vm.deviceForm.tags, function(tag) {
+ return tag.id !== tagID;
+ });
+ }
- // it can be either 2 sensors or 1 sensor, so we use $q.all to wait for all
- $q.all(
- _.map(sensorsID, function(sensorID) {
- return getChartData($stateParams.id, sensorID, options.from, options.to)
- .then(function(data) {
- return data;
- });
- })
- ).then(function() {
- // after all sensors resolve, prepare data and attach it to scope
- // the variable on the scope will pass the data to the chart directive
- vm.chartDataMain = prepareChartData([mainSensorID, compareSensorID]);
- });
- }
- // calls api to get sensor data and saves it to sensorsData array
- function getChartData(deviceID, sensorID, dateFrom, dateTo, options) {
- return sensor.getSensorsData(deviceID, sensorID, dateFrom, dateTo)
- .then(function(data) {
- //save sensor data of this kit so that it can be reused
- sensorsData[sensorID] = data.readings;
- return data;
- });
- }
-
- function prepareChartData(sensorsID) {
- var compareSensor;
- var parsedDataMain = parseSensorData(sensorsData, sensorsID[0]);
- var mainSensor = {
- data: parsedDataMain,
- color: vm.selectedSensorData.color,
- unit: vm.selectedSensorData.unit
- };
- if(sensorsID[1] && sensorsID[1] !== -1) {
- var parsedDataCompare = parseSensorData(sensorsData, sensorsID[1]);
-
- compareSensor = {
- data: parsedDataCompare,
- color: vm.selectedSensorToCompareData.color,
- unit: vm.selectedSensorToCompareData.unit
+ function submitStepOne() {
+ var data = {
+ name: vm.deviceForm.name,
+ description: vm.deviceForm.description,
+ exposure: findExposure(vm.deviceForm.exposure),
+ latitude: vm.deviceForm.location.lat,
+ longitude: vm.deviceForm.location.lng,
+ is_private: vm.deviceForm.is_private,
+ hardware_version_override: vm.deviceForm.legacyVersion,
+ /*jshint camelcase: false */
+ user_tags: _.map(vm.deviceForm.tags, 'name').join(',')
};
+
+ device.createDevice(data)
+ .then(
+ function(response) {
+ device.updateContext().then(function(){
+ auth.setCurrentUser('appLoad').then(function(){
+ $timeout($state.go('layout.kitEdit', {id:response.id, step:2}), 2000);
+ });
+ });
+ },
+ function(err) {
+ vm.errors = err.data.errors;
+ alert.error('There has been an error during kit set up');
+ });
}
- var newChartData = [mainSensor, compareSensor];
- return newChartData;
- }
- function parseSensorData(data, sensorID) {
- if(data.length === 0) {
- return [];
+ function getTags() {
+ tag.getTags()
+ .then(function(tagsData) {
+ vm.tags = tagsData;
+ });
}
- return data[sensorID].map(function(dataPoint) {
- var time = timeUtils.formatDate(dataPoint[0]);
- var value = dataPoint[1];
- var count = value === null ? 0 : value;
- return {
- time: time,
- count: count,
- value: value
- };
- });
- }
- function setSensor(options) {
- var sensorID = options.value;
- if(sensorID === undefined) {
- return;
+ function toProfile(){
+ $state.transitionTo('layout.myProfile.kits', $stateParams,
+ { reload: false,
+ inherit: false,
+ notify: true
+ });
}
- if(options.type === 'main') {
- mainSensorID = sensorID;
- } else if(options.type === 'compare') {
- compareSensorID = sensorID;
+
+ function backToProfile(){
+ // TODO: Refactor Check
+ toProfile();
}
- }
- function colorSensorCompareName() {
- var name = angular.element('.sensor_compare').find('md-select-label').find('span');
- name.css('color', vm.selectedSensorToCompareData.color || 'white');
- var icon = angular.element('.sensor_compare').find('md-select-label').find('.md-select-icon');
- icon.css('color', 'white');
- }
+ //TODO: move to utils
+ function findExposure(nameOrValue) {
+ var findProp, resultProp;
+ //if it's a string
+ if(isNaN(parseInt(nameOrValue))) {
+ findProp = 'name';
+ resultProp = 'value';
+ } else {
+ findProp = 'value';
+ resultProp = 'name';
+ }
- function getCurrentRange() {
- var to = moment(picker.getValuePickerTo());
- var from = moment(picker.getValuePickerFrom());
- return to.diff(from)/1000;
+ var option = _.find(vm.exposure, function(exposureFromList) {
+ return exposureFromList[findProp] === nameOrValue;
+ });
+ if(option) {
+ return option[resultProp];
+ }
+ }
}
+})();
- function moveChart(direction) {
+(function() {
+ 'use strict';
- var valueTo, valueFrom;
- //grab current date range
- var currentRange = getCurrentRange();
+ // Taken from this answer on SO:
+ // https://stackoverflow.com/questions/17893708/angularjs-textarea-bind-to-json-object-shows-object-object
+ angular.module('app.components').directive('jsonText', function() {
+ return {
+ restrict: 'A',
+ require: 'ngModel',
+ link: function(scope, element, attr, ngModel){
+ function into(input) {
+ return JSON.parse(input);
+ }
+ function out(data) {
+ return JSON.stringify(data);
+ }
+ ngModel.$parsers.push(into);
+ ngModel.$formatters.push(out);
+ }
+ };
+ });
- /*jshint camelcase: false*/
- var from_picker = angular.element('#picker_from').pickadate('picker');
- var to_picker = angular.element('#picker_to').pickadate('picker');
+ angular.module('app.components')
+ .controller('EditKitController', EditKitController);
- if(direction === 'left') {
- //set both from and to pickers to prev range
- valueTo = moment(picker.getValuePickerFrom());
- valueFrom = moment(picker.getValuePickerFrom()).subtract(currentRange, 'seconds');
+ EditKitController.$inject = ['$scope', '$element', '$location', '$timeout', '$state',
+ 'animation','auth','device', 'tag', 'alert', 'step', '$stateParams', 'FullDevice'];
+ function EditKitController($scope, $element, $location, $timeout, $state, animation,
+ auth, device, tag, alert, step, $stateParams, FullDevice) {
- picker.setValuePickers([valueFrom.toDate(), valueTo.toDate()]);
+ var vm = this;
- } else if(direction === 'right') {
- var today = timeUtils.getToday();
- var currentValueTo = picker.getValuePickerTo();
- if( timeUtils.isSameDay(today, timeUtils.getMillisFromDate(currentValueTo)) ) {
- return;
- }
+ // WAIT INTERVAL FOR USER FEEDBACK and TRANSITIONS (This will need to change)
+ var timewait = {
+ long: 5000,
+ normal: 2000,
+ short: 1000
+ };
- valueFrom = moment(picker.getValuePickerTo());
- valueTo = moment(picker.getValuePickerTo()).add(currentRange, 'seconds');
+ vm.step = step;
- picker.setValuePickers([valueFrom.toDate(), valueTo.toDate()]);
+ // KEY USER ACTIONS
+ vm.submitFormAndKit = submitFormAndKit;
+ vm.backToProfile = backToProfile;
+ vm.backToDevice = backToDevice;
+ vm.submitForm = submitForm;
+ vm.goToStep = goToStep;
+ vm.nextAction = 'save';
- }
- resetTimeOpts();
- }
+ // EXPOSURE SELECT
+ vm.exposure = [
+ {name: 'indoor', value: 1},
+ {name: 'outdoor', value: 2}
+ ];
- //hide everything but the functions to interact with the pickers
- function initializePicker() {
- var range = {};
- /*jshint camelcase: false*/
- var from_$input = angular.element('#picker_from').pickadate({
- onOpen: function(){
- vm.resetTimeOpts();
- },
- onClose: function(){
- angular.element(document.activeElement).blur();
- },
- container: 'body',
- klass: {
- holder: 'picker__holder picker_container'
- }
+ // FORM INFO
+ vm.deviceForm = {};
+ vm.device = undefined;
+
+ $scope.clearSearchTerm = function() {
+ $scope.searchTerm = '';
+ };
+ // The md-select directive eats keydown events for some quick select
+ // logic. Since we have a search input here, we don't need that logic.
+ $element.find('input').on('keydown', function(ev) {
+ ev.stopPropagation();
});
- var from_picker = from_$input.pickadate('picker');
- var to_$input = angular.element('#picker_to').pickadate({
- onOpen: function(){
- vm.resetTimeOpts();
- },
- onClose: function(){
- angular.element(document.activeElement).blur();
- },
- container: 'body',
- klass: {
- holder: 'picker__holder picker_container'
- }
+ $scope.$on('leafletDirectiveMarker.dragend', function(event, args){
+ vm.deviceForm.location.lat = args.model.lat;
+ vm.deviceForm.location.lng = args.model.lng;
});
- var to_picker = to_$input.pickadate('picker');
+ // MAP CONFIGURATION
+ var mapBoxToken = 'pk.eyJ1IjoidG9tYXNkaWV6IiwiYSI6ImRTd01HSGsifQ.loQdtLNQ8GJkJl2LUzzxVg';
- if( from_picker.get('value') ) {
- to_picker.set('min', from_picker.get('select') );
- }
- if( to_picker.get('value') ) {
- from_picker.set('max', to_picker.get('select') );
- }
+ vm.getLocation = getLocation;
+ vm.markers = {};
+ vm.tiles = {
+ url: 'https://api.mapbox.com/styles/v1/mapbox/streets-v10/tiles/{z}/{x}/{y}?access_token=' + mapBoxToken
+ };
+ vm.defaults = {
+ scrollWheelZoom: false
+ };
- from_picker.on('close', function(event) {
- setFromRange(getCalculatedFrom(from_picker.get('value')));
- });
+ initialize();
- to_picker.on('close', function(event) {
- setToRange(getCalculatedTo(to_picker.get('value')));
- });
+ /////////////////
- from_picker.on('set', function(event) {
- if(event.select) {
- to_picker.set('min', getFromRange());
- } else if( 'clear' in event) {
- to_picker.set('min', false);
- }
- });
+ function initialize() {
+ var deviceID = $stateParams.id;
- to_picker.on('set', function(event) {
- if(event.select) {
- from_picker.set('max', getToRange());
- } else if( 'clear' in event) {
- from_picker.set('max', false);
+ animation.viewLoaded();
+ getTags();
+
+ if (!deviceID || deviceID === ''){
+ return;
}
- });
+ device.getDevice(deviceID)
+ .then(function(deviceData) {
+ vm.device = new FullDevice(deviceData);
+ vm.userRole = auth.getCurrentUser().data.role;
+ vm.deviceForm = {
+ name: vm.device.name,
+ exposure: findExposureFromLabels(vm.device.systemTags),
+ location: {
+ lat: vm.device.location.latitude,
+ lng: vm.device.location.longitude,
+ zoom: 16
+ },
+ is_private: vm.device.isPrivate,
+ precise_location: vm.device.preciseLocation,
+ enable_forwarding: vm.device.enableForwarding,
+ notify_low_battery: vm.device.notifications.lowBattery,
+ notify_stopped_publishing: vm.device.notifications.stopPublishing,
+ tags: vm.device.userTags,
+ postprocessing: vm.device.postProcessing,
+ description: vm.device.description,
+ hardwareName: vm.device.hardware.name
+ };
+ vm.markers = {
+ main: {
+ lat: vm.device.location.latitude,
+ lng: vm.device.location.longitude,
+ draggable: true
+ }
+ };
- //set to-picker max to today
- to_picker.set('max', getLatestUpdated());
+ if (vm.device.isLegacy) {
+ vm.deviceForm.macAddress = vm.device.macAddress;
+ }
+ });
+ }
- function getSevenDaysAgoFromLatestUpdate() {
- var lastTime = moment(vm.device.lastReadingAt.raw);
- return lastTime.subtract(7, 'days').valueOf();
+ // Return tags in a comma separated list
+ function joinSelectedTags(){
+ let tmp = []
+ $scope.selectedTags.forEach(function(e){
+ tmp.push(e.name)
+ })
+ return tmp.join(', ');
}
- function getLatestUpdated() {
- return moment(vm.device.lastReadingAt.raw).toDate();
+ function getLocation() {
+ window.navigator.geolocation.getCurrentPosition(function(position) {
+ $scope.$apply(function() {
+ var lat = position.coords.latitude;
+ var lng = position.coords.longitude;
+ vm.deviceForm.location.lat = lat;
+ vm.deviceForm.location.lng = lng;
+ vm.markers.main.lat = lat;
+ vm.markers.main.lng = lng;
+ });
+ });
}
- function getCalculatedFrom(pickerTimeFrom) {
- var from,
- pickerTime;
+ function submitFormAndKit(){
+ submitForm(backToProfile, timewait.normal);
+ }
- pickerTime = moment(pickerTimeFrom, 'D MMMM, YYYY');
- from = pickerTime.startOf('day');
+ function submitForm(next, delayTransition) {
+ var data = {
+ name: vm.deviceForm.name,
+ description: vm.deviceForm.description,
+ postprocessing_attributes: vm.deviceForm.postprocessing,
+ exposure: findExposure(vm.deviceForm.exposure),
+ latitude: vm.deviceForm.location.lat,
+ longitude: vm.deviceForm.location.lng,
+ is_private: vm.deviceForm.is_private,
+ enable_forwarding: vm.deviceForm.enable_forwarding,
+ precise_location: vm.deviceForm.precise_location,
+ notify_low_battery: vm.deviceForm.notify_low_battery,
+ notify_stopped_publishing: vm.deviceForm.notify_stopped_publishing,
+ mac_address: "",
+ /*jshint camelcase: false */
+ user_tags: joinSelectedTags(),
+ };
- return from;
- }
+ vm.errors={};
- function getCalculatedTo(pickerTimeTo) {
- var to,
- pickerTime;
+ if(!vm.device.isSCK) {
+ data.hardware_name_override = vm.deviceForm.hardwareName;
+ }
- pickerTime = moment(pickerTimeTo, 'D MMMM, YYYY');
+ // Workaround for the mac_address bypass
+ // If mac_address is "", we get an error on the request -> we use it for the newKit
+ // If mac_address is null, no problem -> we use it for the
+ if ($stateParams.step === "2") {
+ data.mac_address = vm.deviceForm.macAddress ? vm.deviceForm.macAddress : "";
+ } else {
+ data.mac_address = vm.deviceForm.macAddress ? vm.deviceForm.macAddress : null;
+ }
- to = pickerTime.endOf('day');
- if (moment().diff(to) < 0) {
- var now = moment();
- to = pickerTime.set({
- 'hour' : now.get('hour'),
- 'minute' : now.get('minute'),
- 'second' : now.get('second')
+ device.updateDevice(vm.device.id, data)
+ .then(
+ function() {
+
+ if (next){
+ alert.success('Your kit was updated!');
+ }
+
+ device.updateContext().then(function(){
+ if (next){
+ $timeout(next, delayTransition);
+ }
+ });
+ })
+ .catch(function(err) {
+ if(err.data.errors) {
+ vm.errors = err.data.errors;
+ var message = Object.keys(vm.errors).map(function (key, _) {
+ return [key, vm.errors[key][0]].join(' '); }).join('');
+ alert.error('Oups! Check the input. Something went wrong!');
+ throw new Error('[Client:error] ' + message);
+ }
+ $timeout(function(){ }, timewait.long);
});
- }
+ }
- return to;
+ function findExposureFromLabels(labels){
+ var label = vm.exposure.filter(function(n) {
+ return labels.indexOf(n.name) !== -1;
+ })[0];
+ if(label) {
+ return findExposure(label.name);
+ } else {
+ return findExposure(vm.exposure[0].name);
+ }
}
- function updateChart() {
- var sensors = [mainSensorID, compareSensorID];
- sensors = sensors.filter(function(sensor) {
- return sensor;
- });
- changeChart(sensors, {
- from: range.from,
- to: range.to
+ function findExposure(nameOrValue) {
+ var findProp, resultProp;
+
+ //if it's a string
+ if(isNaN(parseInt(nameOrValue))) {
+ findProp = 'name';
+ resultProp = 'value';
+ } else {
+ findProp = 'value';
+ resultProp = 'name';
+ }
+
+ var option = _.find(vm.exposure, function(exposureFromList) {
+ return exposureFromList[findProp] === nameOrValue;
});
+ if(option) {
+ return option[resultProp];
+ } else {
+ return vm.exposure[0][resultProp];
+ }
}
- function setFromRange(from) {
- range.from = from;
- from_picker.set('select', getFromRange());
- updateChart();
+ function getTags() {
+ tag.getTags()
+ .then(function(tagsData) {
+ vm.tags = tagsData;
+ });
}
- function setToRange(to) {
- range.to = to;
- to_picker.set('select', getToRange());
- updateChart();
+ function backToProfile(){
+ $state.transitionTo('layout.myProfile.kits', $stateParams,
+ { reload: false,
+ inherit: false,
+ notify: true
+ });
}
- function getFromRange() {
- return moment(range.from).toDate();
+ function backToDevice(){
+ $state.transitionTo('layout.home.kit', $stateParams,
+ { reload: false,
+ inherit: false,
+ notify: true
+ });
}
- function getToRange() {
- return moment(range.to).toDate();
+ function goToStep(step) {
+ vm.step = step;
+ $state.transitionTo('layout.kitEdit', { id:$stateParams.id, step: step} ,
+ {
+ reload: false,
+ inherit: false,
+ notify: false
+ });
}
+ }
+})();
- function setRange(from, to) {
- range.from = from;
- range.to = to;
- from_picker.set('select', getFromRange());
- to_picker.set('select', getToRange());
- updateChart();
- }
+(function() {
+ 'use strict';
- if(vm.device){
- if(vm.device.systemTags.includes('new')){
- var lastUpdate = getLatestUpdated();
- setRange(timeUtils.getHourBefore(lastUpdate), lastUpdate);
- } else if (timeUtils.isWithin(7, 'days', vm.device.lastReadingAt.raw) || !vm.device.lastReadingAt.raw) {
- //set from-picker to seven days ago and set to-picker to today
- setRange(timeUtils.getSevenDaysAgo(), timeUtils.getToday());
- } else {
- // set from-picker to and set to-picker to today
- setRange(getSevenDaysAgoFromLatestUpdate(), getLatestUpdated());
- }
- }
+ angular.module('app.components')
+ .factory('User', ['COUNTRY_CODES', function(COUNTRY_CODES) {
- // api to interact with the picker from outside
- return {
- getValuePickerFrom: function() {
- return getFromRange();
- },
- setValuePickerFrom: function(newValue) {
- setFromRange(newValue);
- },
- getValuePickerTo: function() {
- return getToRange();
- },
- setValuePickerTo: function(newValue) {
- setToRange(newValue);
- },
- setValuePickers: function(newValues) {
- var from = newValues[0];
- var to = newValues[1];
- setRange(from, to);
- }
- };
- }
+ /**
+ * User constructor
+ * @param {Object} userData - User data sent from API
+ * @property {number} id - User ID
+ * @property {string} username - Username
+ * @property {string} profile_picture - Avatar URL of user
+ * @property {Array} devices - Kits that belongs to this user
+ * @property {string} url - URL
+ * @property {string} city - User city
+ * @property {string} country - User country
+ */
- function geolocate() {
- if (navigator.geolocation) {
- navigator.geolocation.getCurrentPosition(function(position){
- if(!position){
- alert.error('Please, allow smartcitizen to geolocate your' +
- 'position so we can find a kit near you.');
- return;
- }
+ function User(userData) {
+ this.id = userData.id;
+ this.username = userData.username;
+ this.profile_picture = userData.profile_picture;
+ this.devices = userData.devices;
+ this.url = userData.url;
+ this.city = userData.location.city;
+ /*jshint camelcase: false */
+ this.country = COUNTRY_CODES[userData.location.country_code];
+ }
+ return User;
+ }]);
- geolocation.grantHTML5Geolocation();
+})();
- var location = {
- lat:position.coords.latitude,
- lng:position.coords.longitude
- };
- device.getDevices(location)
- .then(function(data){
- data = data.plain();
+(function() {
+ 'use strict';
- _(data)
- .chain()
- .map(function(device) {
- return new HasSensorDevice(device);
- })
- .filter(function(device) {
- return !!device.longitude && !!device.latitude;
- })
- .find(function(device) {
- return _.includes(device.labels, 'online');
- })
- .tap(function(closestDevice) {
- if(focused){
- if(closestDevice) {
- $state.go('layout.home.kit', {id: closestDevice.id});
- } else {
- $state.go('layout.home.kit', {id: data[0].id});
- }
- }
- })
- .value();
- });
- });
+ angular.module('app.components')
+ .factory('NonAuthUser', ['User', function(User) {
+
+ function NonAuthUser(userData) {
+ User.call(this, userData);
}
- }
+ NonAuthUser.prototype = Object.create(User.prototype);
+ NonAuthUser.prototype.constructor = User;
- function downloadData(device){
- $mdDialog.show({
- hasBackdrop: true,
- controller: 'DownloadModalController',
- controllerAs: 'vm',
- templateUrl: 'app/components/download/downloadModal.html',
- clickOutsideToClose: true,
- locals: {thisDevice: device}
- }).then(function(){
- var alert = $mdDialog.alert()
- .title('SUCCESS')
- .textContent('We are processing your data. Soon you will be notified in your inbox')
- .ariaLabel('')
- .ok('OK!')
- .theme('primary')
- .clickOutsideToClose(true);
+ return NonAuthUser;
+ }]);
+})();
- $mdDialog.show(alert);
- }).catch(function(err){
- if (!err){
- return;
- }
- var errorAlert = $mdDialog.alert()
- .title('ERROR')
- .textContent('Uh-oh, something went wrong')
- .ariaLabel('')
- .ok('D\'oh')
- .theme('primary')
- .clickOutsideToClose(false);
+(function() {
+ 'use strict';
- $mdDialog.show(errorAlert);
- });
- }
+ angular.module('app.components')
+ .factory('AuthUser', ['User', function(User) {
- function getMainSensors(deviceData) {
- if(!deviceData) {
- return undefined;
- }
- return deviceData.getSensors({type: 'main'});
- }
- function getCompareSensors(deviceData) {
- if(!vm.device) {
- return undefined;
- }
- deviceData.getSensors({type: 'compare'});
- }
- function getOwnerDevices(deviceData, sampling) {
- if(!deviceData) {
- return undefined;
- }
- var deviceIDs = deviceData.owner.devices.slice(sampling);
- // var ownerID = deviceData.owner.id;
- // TODO: Refactor This is in the user endpoint, no need to query devices
- return $q.all(
- deviceIDs.map(function(id) {
- return device.getDevice(id)
- .then(function(data) {
- return new PreviewDevice(data);
- });
- })
- );
- }
+ /**
+ * AuthUser constructor. Used for authenticated users
+ * @extends User
+ * @param {Object} userData - Contains user data sent from API
+ * @property {string} email - User email
+ * @property {string} role - User role. Ex: admin
+ * @property {string} key - Personal API Key
+ */
- function setFromLast(what){
- /* This will not show the last 60 minutes or 24 hours,
- instead it will show the last hour or day*/
- var to, from;
- if (what === '60 minutes') {
- to = moment(vm.device.lastReadingAt.raw);
- from = moment(vm.device.lastReadingAt.raw).subtract(60, 'minutes');
- } else {
- to = moment(vm.device.lastReadingAt.raw).endOf(what);
- from = moment(vm.device.lastReadingAt.raw).startOf(what);
- }
- // Check if we are in the future
- if (moment().diff(to) < 0){
- to = moment(vm.device.lastReadingAt.raw);
- }
- picker.setValuePickers([from.toDate(), to.toDate()]);
- }
+ function AuthUser(userData) {
+ User.call(this, userData);
- function timeOptSelected(){
- vm.allowUpdateChart = false;
- if (vm.dropDownSelection){
- setFromLast(vm.dropDownSelection);
+ this.email = userData.email;
+ this.role = userData.role;
+ /*jshint camelcase: false */
+ this.key = userData.legacy_api_key;
}
- }
- function resetTimeOpts(){
- vm.allowUpdateChart = false;
- vm.dropDownSelection = undefined;
- }
+ AuthUser.prototype = Object.create(User.prototype);
+ AuthUser.prototype.constructor = User;
- function showStore() {
- $mdDialog.show({
- hasBackdrop: true,
- controller: 'StoreModalController',
- templateUrl: 'app/components/store/storeModal.html',
- clickOutsideToClose: true
- });
- }
- }
+ return AuthUser;
+ }]);
})();
(function() {
'use strict';
angular.module('app.components')
- .controller('NewKitController', NewKitController);
+ .factory('Sensor', ['sensorUtils', 'timeUtils', function(sensorUtils, timeUtils) {
- NewKitController.$inject = ['$scope', '$state', 'animation', 'device', 'tag', 'alert', 'auth', '$stateParams', '$timeout'];
- function NewKitController($scope, $state, animation, device, tag, alert, auth, $stateParams, $timeout) {
- var vm = this;
+ /**
+ * Sensor constructor
+ * @param {Object} sensorData - Contains the data of a sensor sent from the API
+ * @property {string} name - Name of sensor
+ * @property {number} id - ID of sensor
+ * @property {string} unit - Unit of sensor. Ex: %
+ * @property {string} value - Last value sent. Ex: 95
+ * @property {string} prevValue - Previous value before last value
+ * @property {string} lastReadingAt - last_reading_at for the sensor reading
+ * @property {string} icon - Icon URL for sensor
+ * @property {string} arrow - Icon URL for sensor trend(up, down or equal)
+ * @property {string} color - Color that belongs to sensor
+ * @property {object} measurement - Measurement
+ * @property {string} fullDescription - Full Description for popup
+ * @property {string} previewDescription - Short Description for dashboard. Max 140 chars
+ * @property {string} tags - Contains sensor tags for filtering the view
+ */
+ function Sensor(sensorData) {
- vm.step = 1;
- vm.submitStepOne = submitStepOne;
- vm.backToProfile = backToProfile;
+ this.id = sensorData.id;
+ this.name = sensorData.name;
+ this.unit = sensorData.unit;
+ this.value = sensorUtils.getSensorValue(sensorData);
+ this.prevValue = sensorUtils.getSensorPrevValue(sensorData);
+ this.lastReadingAt = timeUtils.parseDate(sensorData.last_reading_at);
+ this.icon = sensorUtils.getSensorIcon(this.name);
+ this.arrow = sensorUtils.getSensorArrow(this.value, this.prevValue);
+ this.color = sensorUtils.getSensorColor(this.name);
+ this.measurement = sensorData.measurement;
- // FORM INFO
- vm.deviceForm = {
- name: undefined,
- exposure: undefined,
- location: {
- lat: undefined,
- lng: undefined,
- zoom: 16
- },
- is_private: false,
- legacyVersion: '1.1',
- tags: []
- };
-
- // EXPOSURE SELECT
- vm.exposure = [
- {name: 'indoor', value: 1},
- {name: 'outdoor', value: 2}
- ];
-
- // VERSION SELECT
- vm.version = [
- {name: 'Smart Citizen Kit 1.0', value: '1.0'},
- {name: 'Smart Citizen Kit 1.1', value: '1.1'}
- ];
-
- $scope.$on('leafletDirectiveMarker.dragend', function(event, args){
- vm.deviceForm.location.lat = args.model.lat;
- vm.deviceForm.location.lng = args.model.lng;
- });
-
- // TAGS SELECT
- vm.tags = [];
- $scope.$watch('vm.tag', function(newVal, oldVal) {
- if(!newVal) {
- return;
+ // Some sensors don't have measurements because they are ancestors
+ if (sensorData.measurement) {
+ var description = sensorData.measurement.description;
+ this.fullDescription = description;
+ this.previewDescription = description.length > 140 ? description.slice(
+ 0, 140).concat(' ... ') : description;
+ this.is_ancestor = false;
+ } else {
+ this.is_ancestor = true;
}
- // remove selected tag from select element
- vm.tag = undefined;
- var alreadyPushed = _.some(vm.deviceForm.tags, function(tag) {
- return tag.id === newVal;
- });
- if(alreadyPushed) {
- return;
- }
+ // Get sensor tags
+ this.tags = sensorData.tags;
+ }
- var tag = _.find(vm.tags, function(tag) {
- return tag.id === newVal;
- });
- vm.deviceForm.tags.push(tag);
- });
- vm.removeTag = removeTag;
+ return Sensor;
+ }]);
+})();
+(function() {
+ 'use strict';
- // MAP CONFIGURATION
- var mapBoxToken = 'pk.eyJ1IjoidG9tYXNkaWV6IiwiYSI6ImRTd01HSGsifQ.loQdtLNQ8GJkJl2LUzzxVg';
+ angular.module('app.components')
+ .factory('SearchResultLocation', ['SearchResult', function(SearchResult) {
- vm.getLocation = getLocation;
- vm.markers = {
- main: {
- lat: undefined,
- lng: undefined,
- draggable: true
- }
- };
- vm.tiles = {
- url: 'https://api.mapbox.com/styles/v1/mapbox/streets-v10/tiles/{z}/{x}/{y}?access_token=' + mapBoxToken
- };
- vm.defaults = {
- scrollWheelZoom: false
- };
+ /**
+ * Search Result Location constructor
+ * @extends SearchResult
+ * @param {Object} object - Object that contains the search result data from API
+ * @property {number} lat - Latitude
+ * @property {number} lng - Longitude
+ */
+ function SearchResultLocation(object) {
+ SearchResult.call(this, object);
- vm.macAddress = undefined;
+ this.lat = object.latitude;
+ this.lng = object.longitude;
+ this.layer = object.layer;
+ }
+ return SearchResultLocation;
+ }]);
- initialize();
+})();
- //////////////
+(function() {
+ 'use strict';
- function initialize() {
- animation.viewLoaded();
- getTags();
- vm.userRole = auth.getCurrentUser().data.role;
- }
+ angular.module('app.components')
+ .factory('SearchResult', ['searchUtils', function(searchUtils) {
- function getLocation() {
- window.navigator.geolocation.getCurrentPosition(function(position) {
- $scope.$apply(function() {
- var lat = position.coords.latitude;
- var lng = position.coords.longitude;
- vm.deviceForm.location.lat = lat;
- vm.deviceForm.location.lng = lng;
- vm.markers.main.lat = lat;
- vm.markers.main.lng = lng;
- });
- });
+ /**
+ * Search Result constructor
+ * @param {Object} object - Object that belongs to a search result from API
+ * @property {string} type - Type of search result. Ex: Country, City, User, Device
+ * @property {number} id - ID of search result, only for user & device
+ * @property {string} name - Name of search result, only for user & device
+ * @property {string} location - Location of search result. Ex: 'Paris, France'
+ * @property {string} icon - URL for the icon that belongs to this search result
+ * @property {string} iconType - Type of icon. Can be either img or div
+ */
+
+ function SearchResult(object) {
+ this.type = object.type;
+ this.id = object.id;
+ this.name = searchUtils.parseName(object);
+ this.location = searchUtils.parseLocation(object);
+ this.icon = searchUtils.parseIcon(object, this.type);
+ this.iconType = searchUtils.parseIconType(this.type);
}
+ return SearchResult;
+ }]);
+})();
- function removeTag(tagID) {
- vm.deviceForm.tags = _.filter(vm.deviceForm.tags, function(tag) {
- return tag.id !== tagID;
- });
- }
+(function() {
+ 'use strict';
- function submitStepOne() {
- var data = {
- name: vm.deviceForm.name,
- description: vm.deviceForm.description,
- exposure: findExposure(vm.deviceForm.exposure),
- latitude: vm.deviceForm.location.lat,
- longitude: vm.deviceForm.location.lng,
- is_private: vm.deviceForm.is_private,
- hardware_version_override: vm.deviceForm.legacyVersion,
- /*jshint camelcase: false */
- user_tags: _.map(vm.deviceForm.tags, 'name').join(',')
- };
+ angular.module('app.components')
+ .factory('Marker', ['deviceUtils', 'markerUtils', 'timeUtils', '$state', function(deviceUtils, markerUtils, timeUtils, $state) {
+ /**
+ * Marker constructor
+ * @constructor
+ * @param {Object} deviceData - Object with data about marker from API
+ * @property {number} lat - Latitude
+ * @property {number} lng - Longitude
+ * @property {string} message - Message inside marker popup
+ * @property {Object} icon - Object with classname, size and type of marker icon
+ * @property {string} layer - Map layer that icons belongs to
+ * @property {boolean} focus - Whether marker popup is opened
+ * @property {Object} myData - Marker id and labels
+ */
+ function Marker(deviceData) {
+ let linkStart = '', linkEnd = '';
+ const id = deviceData.id;
+ if ($state.$current.name === 'embbed') {
+ linkStart = '';
+ linkEnd = '';
+ }
+ this.lat = deviceUtils.parseCoordinates(deviceData).lat;
+ this.lng = deviceUtils.parseCoordinates(deviceData).lng;
+ // TODO: Bug, pop-up lastreading at doesn't get updated by publication
+ this.message = '';
- device.createDevice(data)
- .then(
- function(response) {
- device.updateContext().then(function(){
- auth.setCurrentUser('appLoad').then(function(){
- $timeout($state.go('layout.kitEdit', {id:response.id, step:2}), 2000);
- });
- });
- },
- function(err) {
- vm.errors = err.data.errors;
- alert.error('There has been an error during kit set up');
- });
+ this.icon = markerUtils.getIcon(deviceData);
+ this.layer = 'devices';
+ this.focus = false;
+ this.myData = {
+ id: id,
+ labels: deviceUtils.parseSystemTags(deviceData),
+ tags: deviceUtils.parseUserTags(deviceData)
+ };
}
+ return Marker;
- function getTags() {
- tag.getTags()
- .then(function(tagsData) {
- vm.tags = tagsData;
- });
- }
+ function createTagsTemplate(tagsArr, tagType, clickable) {
+ if(typeof(clickable) === 'undefined'){
+ clickable = false;
+ }
+ var clickablTag = '';
+ if(clickable){
+ clickablTag = 'clickable';
+ }
- function toProfile(){
- $state.transitionTo('layout.myProfile.kits', $stateParams,
- { reload: false,
- inherit: false,
- notify: true
- });
- }
+ if(!tagType){
+ tagType = 'tag';
+ }
- function backToProfile(){
- // TODO: Refactor Check
- toProfile();
+ return _.reduce(tagsArr, function(acc, label) {
+ var element ='';
+ if(tagType === 'tag'){
+ element = '';
+ }else{
+ element = ''+label+'';
+ }
+ return acc.concat(element);
+ }, '');
}
- //TODO: move to utils
- function findExposure(nameOrValue) {
- var findProp, resultProp;
- //if it's a string
- if(isNaN(parseInt(nameOrValue))) {
- findProp = 'name';
- resultProp = 'value';
- } else {
- findProp = 'value';
- resultProp = 'name';
- }
+ }]);
+})();
- var option = _.find(vm.exposure, function(exposureFromList) {
- return exposureFromList[findProp] === nameOrValue;
- });
- if(option) {
- return option[resultProp];
- }
+(function () {
+ 'use strict';
+
+ angular.module('app.components')
+ .factory('PreviewDevice', ['Device', function (Device) {
+
+ /**
+ * Preview Device constructor.
+ * Used for devices stacked in a list, like in User Profile or Device states
+ * @extends Device
+ * @constructor
+ * @param {Object} object - Object with all the data about the device from the API
+ */
+ function PreviewDevice(object) {
+ Device.call(this, object);
+
+ this.dropdownOptions = [];
+ this.dropdownOptions.push({ text: 'EDIT', value: '1', href: 'kits/' + this.id + '/edit', icon: 'fa fa-edit' });
+ this.dropdownOptions.push({ text: 'SD CARD UPLOAD', value: '2', href: 'kits/' + this.id + '/upload', icon: 'fa fa-sd-card' });
}
- }
+ PreviewDevice.prototype = Object.create(Device.prototype);
+ PreviewDevice.prototype.constructor = Device;
+ return PreviewDevice;
+ }]);
})();
(function() {
'use strict';
- // Taken from this answer on SO:
- // https://stackoverflow.com/questions/17893708/angularjs-textarea-bind-to-json-object-shows-object-object
- angular.module('app.components').directive('jsonText', function() {
- return {
- restrict: 'A',
- require: 'ngModel',
- link: function(scope, element, attr, ngModel){
- function into(input) {
- return JSON.parse(input);
- }
- function out(data) {
- return JSON.stringify(data);
- }
- ngModel.$parsers.push(into);
- ngModel.$formatters.push(out);
- }
- };
- });
-
angular.module('app.components')
- .controller('EditKitController', EditKitController);
+ .factory('HasSensorDevice', ['Device', function(Device) {
- EditKitController.$inject = ['$scope', '$element', '$location', '$timeout', '$state',
- 'animation','auth','device', 'tag', 'alert', 'step', '$stateParams', 'FullDevice'];
- function EditKitController($scope, $element, $location, $timeout, $state, animation,
- auth, device, tag, alert, step, $stateParams, FullDevice) {
+ function HasSensorDevice(object) {
+ Device.call(this, object);
- var vm = this;
+ this.sensors = object.data.sensors;
+ this.longitude = object.data.location.longitude;
+ this.latitude = object.data.location.latitude;
+ }
- // WAIT INTERVAL FOR USER FEEDBACK and TRANSITIONS (This will need to change)
- var timewait = {
- long: 5000,
- normal: 2000,
- short: 1000
+ HasSensorDevice.prototype = Object.create(Device.prototype);
+ HasSensorDevice.prototype.constructor = Device;
+
+ HasSensorDevice.prototype.sensorsHasData = function() {
+ var parsedSensors = this.sensors.map(function(sensor) {
+ return sensor.value;
+ });
+
+ return _.some(parsedSensors, function(sensorValue) {
+ return !!sensorValue;
+ });
};
- vm.step = step;
+ return HasSensorDevice;
+ }]);
+})();
- // KEY USER ACTIONS
- vm.submitFormAndKit = submitFormAndKit;
- vm.backToProfile = backToProfile;
- vm.backToDevice = backToDevice;
- vm.submitForm = submitForm;
- vm.goToStep = goToStep;
- vm.nextAction = 'save';
+(function() {
+ 'use strict';
- // EXPOSURE SELECT
- vm.exposure = [
- {name: 'indoor', value: 1},
- {name: 'outdoor', value: 2}
- ];
+ angular.module('app.components')
+ .factory('FullDevice', ['Device', 'Sensor', 'deviceUtils', function(Device, Sensor, deviceUtils) {
- // FORM INFO
- vm.deviceForm = {};
- vm.device = undefined;
+ /**
+ * Full Device constructor.
+ * @constructor
+ * @extends Device
+ * @param {Object} object - Object with all the data about the device from the API
+ * @property {Object} owner - Device owner data
+ * @property {Array} data - Device sensor's data
+ * @property {Array} sensors - Device sensors data
+ * @property {Array} postProcessing - Device postprocessing
+ */
+ function FullDevice(object) {
+ Device.call(this, object);
- $scope.clearSearchTerm = function() {
- $scope.searchTerm = '';
+ this.owner = deviceUtils.parseOwner(object);
+ this.postProcessing = object.postprocessing;
+ this.data = object.data;
+ this.sensors = object.data.sensors;
+ }
+
+ FullDevice.prototype = Object.create(Device.prototype);
+ FullDevice.prototype.constructor = FullDevice;
+
+ FullDevice.prototype.getSensors = function(options) {
+ var sensors = _(this.data.sensors)
+ .chain()
+ .map(function(sensor) {
+ return new Sensor(sensor);
+ }).sort(function(a, b) {
+ /* This is a temporary hack to set always PV panel at the end*/
+ if (a.id === 18){ return -1;}
+ if (b.id === 18){ return 1;}
+ /* This is a temporary hack to set always the Battery at the end*/
+ if (a.id === 17){ return -1;}
+ if (b.id === 17){ return 1;}
+ /* This is a temporary hack to set always the Battery at the end*/
+ if (a.id === 10){ return -1;}
+ if (b.id === 10){ return 1;}
+ /* After the hacks, sort the sensors by id */
+ return b.id - a.id;
+ })
+ .tap(function(sensors) {
+ if(options.type === 'compare') {
+ sensors.unshift({
+ name: 'NONE',
+ color: 'white',
+ id: -1
+ });
+ }
+ })
+ .value();
+ return sensors;
};
- // The md-select directive eats keydown events for some quick select
- // logic. Since we have a search input here, we don't need that logic.
- $element.find('input').on('keydown', function(ev) {
- ev.stopPropagation();
- });
- $scope.$on('leafletDirectiveMarker.dragend', function(event, args){
- vm.deviceForm.location.lat = args.model.lat;
- vm.deviceForm.location.lng = args.model.lng;
- });
+ return FullDevice;
+ }]);
+})();
- // MAP CONFIGURATION
- var mapBoxToken = 'pk.eyJ1IjoidG9tYXNkaWV6IiwiYSI6ImRTd01HSGsifQ.loQdtLNQ8GJkJl2LUzzxVg';
+(function() {
+ 'use strict';
- vm.getLocation = getLocation;
- vm.markers = {};
- vm.tiles = {
- url: 'https://api.mapbox.com/styles/v1/mapbox/streets-v10/tiles/{z}/{x}/{y}?access_token=' + mapBoxToken
- };
- vm.defaults = {
- scrollWheelZoom: false
- };
+ angular.module('app.components')
+ .factory('Device', ['deviceUtils', 'timeUtils', function(deviceUtils, timeUtils) {
- initialize();
+ /**
+ * Device constructor.
+ * @constructor
+ * @param {Object} object - Object with all the data about the device from the API
+ * @property {number} id - ID of the device
+ * @property {string} name - Name of the device
+ * @property {string} state - State of the device. Ex: Never published
+ * @property {string} description - Device description
+ * @property {Array} systemTags - System tags
+ * @property {Array} userTags - User tags. Ex: ''
+ * @property {bool} isPrivate - True if private device
+ * @property {Array} notifications - Notifications for low battery and stopped publishing
+ * @property {Object} lastReadingAt - last_reading_at: raw, ago, and parsed
+ * @property {Object} createdAt - created_at: raw, ago, and parsed
+ * @property {Object} updatedAt - updated_at: raw, ago, and parsed
+ * @property {Object} location - Location of device. Object with lat, long, elevation, city, country, country_code
+ * @property {string} locationString - Location of device. Ex: Madrid, Spain; Germany; Paris, France
+ * @property {Object} hardware - Device hardware field. Contains type, version, info, slug and name
+ * @property {string} hardwareName - Device hardware name
+ * @property {bool} isLegacy - True if legacy device
+ * @property {bool} isSCK - True if SC device
+ * @property {string} avatar - URL that contains the user avatar
+ */
+ function Device(object) {
+ // Basic information
+ this.id = object.id;
+ this.name = object.name;
+ this.state = deviceUtils.parseState(object);
+ this.description = object.description;
+ this.token = object.device_token;
+ this.macAddress = object.mac_address;
- /////////////////
+ // Tags and dates
+ this.systemTags = deviceUtils.parseSystemTags(object);
+ this.userTags = deviceUtils.parseUserTags(object);
+ this.isPrivate = deviceUtils.isPrivate(object);
+ this.preciseLocation = deviceUtils.preciseLocation(object);
+ this.enableForwarding = deviceUtils.enableForwarding(object);
+ this.notifications = deviceUtils.parseNotifications(object);
+ this.lastReadingAt = timeUtils.parseDate(object.last_reading_at);
+ this.createdAt = timeUtils.parseDate(object.created_at);
+ this.updatedAt = timeUtils.parseDate(object.updated_at);
- function initialize() {
- var deviceID = $stateParams.id;
+ // Location
+ this.location = object.location;
+ this.locationString = deviceUtils.parseLocation(object);
- animation.viewLoaded();
- getTags();
+ // Hardware
+ this.hardware = deviceUtils.parseHardware(object);
+ this.hardwareName = deviceUtils.parseHardwareName(this);
+ this.isLegacy = deviceUtils.isLegacyVersion(this);
+ this.isSCK = deviceUtils.isSCKHardware(this);
+ // this.class = deviceUtils.classify(object); // TODO - Do we need this?
- if (!deviceID || deviceID === ''){
- return;
- }
- device.getDevice(deviceID)
- .then(function(deviceData) {
- vm.device = new FullDevice(deviceData);
- vm.userRole = auth.getCurrentUser().data.role;
- vm.deviceForm = {
- name: vm.device.name,
- exposure: findExposureFromLabels(vm.device.systemTags),
- location: {
- lat: vm.device.location.latitude,
- lng: vm.device.location.longitude,
- zoom: 16
- },
- is_private: vm.device.isPrivate,
- precise_location: vm.device.preciseLocation,
- enable_forwarding: vm.device.enableForwarding,
- notify_low_battery: vm.device.notifications.lowBattery,
- notify_stopped_publishing: vm.device.notifications.stopPublishing,
- tags: vm.device.userTags,
- postprocessing: vm.device.postProcessing,
- description: vm.device.description,
- hardwareName: vm.device.hardware.name
- };
- vm.markers = {
- main: {
- lat: vm.device.location.latitude,
- lng: vm.device.location.longitude,
- draggable: true
- }
- };
-
- if (vm.device.isLegacy) {
- vm.deviceForm.macAddress = vm.device.macAddress;
- }
- });
+ this.avatar = deviceUtils.parseAvatar();
+ /*jshint camelcase: false */
}
- // Return tags in a comma separated list
- function joinSelectedTags(){
- let tmp = []
- $scope.selectedTags.forEach(function(e){
- tmp.push(e.name)
- })
- return tmp.join(', ');
- }
+ return Device;
+ }]);
+})();
- function getLocation() {
- window.navigator.geolocation.getCurrentPosition(function(position) {
- $scope.$apply(function() {
- var lat = position.coords.latitude;
- var lng = position.coords.longitude;
- vm.deviceForm.location.lat = lat;
- vm.deviceForm.location.lng = lng;
- vm.markers.main.lat = lat;
- vm.markers.main.lng = lng;
- });
- });
- }
+(function() {
+ 'use strict';
- function submitFormAndKit(){
- submitForm(backToProfile, timewait.normal);
- }
+ angular.module('app.components')
+ .directive('noDataBackdrop', noDataBackdrop);
- function submitForm(next, delayTransition) {
- var data = {
- name: vm.deviceForm.name,
- description: vm.deviceForm.description,
- postprocessing_attributes: vm.deviceForm.postprocessing,
- exposure: findExposure(vm.deviceForm.exposure),
- latitude: vm.deviceForm.location.lat,
- longitude: vm.deviceForm.location.lng,
- is_private: vm.deviceForm.is_private,
- enable_forwarding: vm.deviceForm.enable_forwarding,
- precise_location: vm.deviceForm.precise_location,
- notify_low_battery: vm.deviceForm.notify_low_battery,
- notify_stopped_publishing: vm.deviceForm.notify_stopped_publishing,
- mac_address: "",
- /*jshint camelcase: false */
- user_tags: joinSelectedTags(),
- };
+ /**
+ * Backdrop for chart section when kit has no data
+ *
+ */
+ noDataBackdrop.$inject = [];
- vm.errors={};
+ function noDataBackdrop() {
+ return {
+ restrict: 'A',
+ scope: {},
+ templateUrl: 'app/core/animation/backdrop/noDataBackdrop.html',
+ controller: function($scope, $timeout) {
+ var vm = this;
- if(!vm.device.isSCK) {
- data.hardware_name_override = vm.deviceForm.hardwareName;
- }
+ vm.deviceWithoutData = false;
+ vm.scrollToComments = scrollToComments;
- // Workaround for the mac_address bypass
- // If mac_address is "", we get an error on the request -> we use it for the newKit
- // If mac_address is null, no problem -> we use it for the
- if ($stateParams.step === "2") {
- data.mac_address = vm.deviceForm.macAddress ? vm.deviceForm.macAddress : "";
- } else {
- data.mac_address = vm.deviceForm.macAddress ? vm.deviceForm.macAddress : null;
- }
+ $scope.$on('deviceWithoutData', function(ev, data) {
- device.updateDevice(vm.device.id, data)
- .then(
- function() {
+ $timeout(function() {
+ vm.device = data.device;
+ vm.deviceWithoutData = true;
- if (next){
- alert.success('Your kit was updated!');
- }
+ if (data.belongsToUser) {
+ vm.user = 'owner';
+ } else {
+ vm.user = 'visitor';
+ }
+ }, 0);
- device.updateContext().then(function(){
- if (next){
- $timeout(next, delayTransition);
- }
- });
- })
- .catch(function(err) {
- if(err.data.errors) {
- vm.errors = err.data.errors;
- var message = Object.keys(vm.errors).map(function (key, _) {
- return [key, vm.errors[key][0]].join(' '); }).join('');
- alert.error('Oups! Check the input. Something went wrong!');
- throw new Error('[Client:error] ' + message);
- }
- $timeout(function(){ }, timewait.long);
- });
- }
+ });
- function findExposureFromLabels(labels){
- var label = vm.exposure.filter(function(n) {
- return labels.indexOf(n.name) !== -1;
- })[0];
- if(label) {
- return findExposure(label.name);
- } else {
- return findExposure(vm.exposure[0].name);
+ function scrollToComments(){
+ location.hash = '';
+ location.hash = '#disqus_thread';
}
- }
+ },
+ controllerAs: 'vm'
+ };
+ }
+})();
- function findExposure(nameOrValue) {
- var findProp, resultProp;
+(function() {
+ 'use strict';
- //if it's a string
- if(isNaN(parseInt(nameOrValue))) {
- findProp = 'name';
- resultProp = 'value';
- } else {
- findProp = 'value';
- resultProp = 'name';
- }
+ angular.module('app.components')
+ .directive('loadingBackdrop', loadingBackdrop);
- var option = _.find(vm.exposure, function(exposureFromList) {
- return exposureFromList[findProp] === nameOrValue;
- });
- if(option) {
- return option[resultProp];
- } else {
- return vm.exposure[0][resultProp];
- }
- }
+ /**
+ * Backdrop for app initialization and between states
+ *
+ */
+ loadingBackdrop.$inject = [];
+ function loadingBackdrop() {
+ return {
+ templateUrl: 'app/core/animation/backdrop/loadingBackdrop.html',
+ controller: function($scope) {
+ var vm = this;
+ vm.isViewLoading = true;
+ vm.mapStateLoading = false;
- function getTags() {
- tag.getTags()
- .then(function(tagsData) {
- vm.tags = tagsData;
+ // listen for app loading event
+ $scope.$on('viewLoading', function() {
+ vm.isViewLoading = true;
});
- }
- function backToProfile(){
- $state.transitionTo('layout.myProfile.kits', $stateParams,
- { reload: false,
- inherit: false,
- notify: true
- });
- }
+ $scope.$on('viewLoaded', function() {
+ vm.isViewLoading = false;
+ });
- function backToDevice(){
- $state.transitionTo('layout.home.kit', $stateParams,
- { reload: false,
- inherit: false,
- notify: true
- });
- }
+ // listen for map state loading event
+ $scope.$on('mapStateLoading', function() {
+ if(vm.isViewLoading) {
+ return;
+ }
+ vm.mapStateLoading = true;
+ });
- function goToStep(step) {
- vm.step = step;
- $state.transitionTo('layout.kitEdit', { id:$stateParams.id, step: step} ,
- {
- reload: false,
- inherit: false,
- notify: false
- });
- }
+ $scope.$on('mapStateLoaded', function() {
+ vm.mapStateLoading = false;
+ });
+ },
+ controllerAs: 'vm'
+ };
}
})();
@@ -1834,787 +1834,769 @@
'use strict';
angular.module('app.components')
- .factory('userUtils', userUtils);
+ .controller('UserProfileController', UserProfileController);
- function userUtils() {
- var service = {
- isAdmin: isAdmin,
- isAuthUser: isAuthUser
- };
- return service;
+ UserProfileController.$inject = ['$scope', '$stateParams', '$location',
+ 'user', 'auth', 'userUtils', '$timeout', 'animation',
+ 'NonAuthUser', '$q', 'PreviewDevice'];
+ function UserProfileController($scope, $stateParams, $location,
+ user, auth, userUtils, $timeout, animation,
+ NonAuthUser, $q, PreviewDevice) {
- ///////////
+ var vm = this;
+ var userID = parseInt($stateParams.id);
- function isAdmin(userData) {
- return userData.role === 'admin';
- }
- function isAuthUser(userID, authUserData) {
- return userID === authUserData.id;
- }
- }
-})();
-
-(function() {
- 'use strict';
-
- angular.module('app.components')
- .factory('timeUtils', timeUtils);
-
- function timeUtils() {
- var service = {
- getSecondsFromDate: getSecondsFromDate,
- getMillisFromDate: getMillisFromDate,
- getCurrentRange: getCurrentRange,
- getToday: getToday,
- getHourBefore: getHourBefore,
- getSevenDaysAgo: getSevenDaysAgo,
- getDateIn: getDateIn,
- convertTime: convertTime,
- formatDate: formatDate,
- isSameDay: isSameDay,
- isWithin15min: isWithin15min,
- isWithin1Month: isWithin1Month,
- isWithin: isWithin,
- isDiffMoreThan15min: isDiffMoreThan15min,
- parseDate: parseDate
- };
- return service;
-
- ////////////
-
- function getDateIn(timeMS, format) {
- if(!format) {
- return timeMS;
- }
+ vm.status = undefined;
+ vm.user = {};
+ vm.devices = [];
+ vm.filteredDevices = [];
+ vm.filterDevices = filterDevices;
- var result;
- if(format === 'ms') {
- result = timeMS;
- } else if(format === 's') {
- result = timeMS / 1000;
- } else if(format === 'm') {
- result = timeMS / 1000 / 60;
- } else if(format === 'h') {
- result = timeMS / 1000 / 60 / 60;
- } else if(format === 'd') {
- result = timeMS / 1000 / 60 / 60 / 24;
- }
- return result;
- }
+ $scope.$on('loggedIn', function() {
+ var authUser = auth.getCurrentUser().data;
+ if( userUtils.isAuthUser(userID, authUser) ) {
+ $location.path('/profile');
+ }
+ });
- function convertTime(time) {
- return moment(time).toISOString();
- }
+ initialize();
- function formatDate(time) {
- return moment(time).format('YYYY-MM-DDTHH:mm:ss');
- }
+ //////////////////
- function getSecondsFromDate(date) {
- return (new Date(date)).getTime();
- }
+ function initialize() {
- function getMillisFromDate(date) {
- return (new Date(date)).getTime();
- }
+ user.getUser(userID)
+ .then(function(user) {
+ vm.user = new NonAuthUser(user);
- function getCurrentRange(fromDate, toDate) {
- return moment(toDate).diff(moment(fromDate), 'days');
- }
+ if(!vm.user.devices.length) {
+ return [];
+ }
- function getToday() {
- return (new Date()).getTime();
- }
+ $q.all(vm.devices = vm.user.devices.map(function(data){
+ return new PreviewDevice(data);
+ }))
- function getSevenDaysAgo() {
- return getSecondsFromDate( getToday() - (7 * 24 * 60 * 60 * 1000) );
- }
+ }).then(function(error) {
+ if(error && error.status === 404) {
+ $location.url('/404');
+ }
+ });
- function getHourBefore(date) {
- var now = moment(date);
- return now.subtract(1, 'hour').valueOf();
- }
+ $timeout(function() {
+ setSidebarMinHeight();
+ animation.viewLoaded();
+ }, 500);
+ }
- function isSameDay(day1, day2) {
- day1 = moment(day1);
- day2 = moment(day2);
+ function filterDevices(status) {
+ if(status === 'all') {
+ status = undefined;
+ }
+ vm.status = status;
+ }
- if(day1.startOf('day').isSame(day2.startOf('day'))) {
- return true;
+ function setSidebarMinHeight() {
+ var height = document.body.clientHeight / 4 * 3;
+ angular.element('.profile_content').css('min-height', height + 'px');
}
- return false;
}
+})();
- function isDiffMoreThan15min(dateToCheckFrom, dateToCheckTo) {
- var duration = moment.duration(moment(dateToCheckTo).diff(moment(dateToCheckFrom)));
- return duration.as('minutes') > 15;
- }
+(function() {
+ 'use strict';
- function isWithin15min(dateToCheck) {
- var fifteenMinAgo = moment().subtract(15, 'minutes').valueOf();
- dateToCheck = moment(dateToCheck).valueOf();
+ angular.module('app.components')
+ .controller('UploadController', UploadController);
- return dateToCheck > fifteenMinAgo;
- }
+ UploadController.$inject = ['kit', '$state', '$stateParams', 'animation'];
+ function UploadController(kit, $state, $stateParams, animation) {
+ var vm = this;
- function isWithin1Month(dateToCheck) {
- var oneMonthAgo = moment().subtract(1, 'months').valueOf();
- dateToCheck = moment(dateToCheck).valueOf();
+ vm.kit = kit;
- return dateToCheck > oneMonthAgo;
- }
+ vm.backToProfile = backToProfile;
- function isWithin(number, type, dateToCheck) {
- var ago = moment().subtract(number, type).valueOf();
- dateToCheck = moment(dateToCheck).valueOf();
+ initialize();
- return dateToCheck > ago;
+ /////////////////
+
+ function initialize() {
+ animation.viewLoaded();
}
- function parseDate(object){
- var time = object;
- return {
- raw: time,
- parsed: !time ? 'No time' : moment(time).format('MMMM DD, YYYY - HH:mm'),
- ago: !time ? 'No time' : moment(time).fromNow()
- }
+ function backToProfile() {
+ $state.transitionTo('layout.myProfile.kits', $stateParams,
+ { reload: false,
+ inherit: false,
+ notify: true
+ });
}
}
})();
-(function() {
- 'use strict';
-
- angular.module('app.components')
- .factory('sensorUtils', sensorUtils);
-
- sensorUtils.$inject = ['timeUtils'];
- function sensorUtils(timeUtils) {
- var service = {
- getRollup: getRollup,
- getSensorName: getSensorName,
- getSensorValue: getSensorValue,
- getSensorPrevValue: getSensorPrevValue,
- getSensorIcon: getSensorIcon,
- getSensorArrow: getSensorArrow,
- getSensorColor: getSensorColor,
- getSensorDescription: getSensorDescription
- };
- return service;
-
- ///////////////
+(function(){
+'use strict';
- function getRollup(dateFrom, dateTo) {
- // Calculate how many data points we can fit on a users screen
- // Smaller screens request less data from the API
- var durationInSec = moment(dateTo).diff(moment(dateFrom)) / 1000;
- var chartWidth = window.innerWidth / 2;
- var rollup = parseInt(durationInSec / chartWidth) + 's';
+function parseDataForPost(csvArray) {
+ /*
+ EXPECTED PAYLOAD
+ {
+ "data": [{
+ "recorded_at": "2016-06-08 10:30:00",
+ "sensors": [{
+ "id": 22,
+ "value": 21
+ }]
+ }]
+ }
+ */
+ const ids = csvArray[3]; // save ids from the 4th header
+ csvArray.splice(0,4); // remove useless headers
+ return {
+ data: csvArray.map((data) => {
+ return {
+ recorded_at: data.shift(), // get the timestamp from the first column
+ sensors: data.map((value, index) => {
+ return {
+ id: ids[index+1], // get ID of sensor from headers
+ value: value
+ };
+ })
+ .filter((sensor) => sensor.value && sensor.id) // remove empty value or id
+ };
+ })
+ };
+}
- /*
- //var rangeDays = timeUtils.getCurrentRange(dateFrom, dateTo, {format: 'd'});
- var rollup;
- if(rangeDays <= 1) {
- rollup = '15s';
- } else if(rangeDays <= 7) {
- rollup = '1h';//rollup = '15m';
- } else if(rangeDays > 7) {
- rollup = '1d';
- }
- */
- return rollup;
- }
- function getSensorName(name) {
- var sensorName;
- // TODO: Improvement check how we set new names
- if( new RegExp('custom circuit', 'i').test(name) ) {
- sensorName = name;
- } else {
- if(new RegExp('noise', 'i').test(name) ) {
- sensorName = 'SOUND';
- } else if(new RegExp('light', 'i').test(name) ) {
- sensorName = 'LIGHT';
- } else if((new RegExp('nets', 'i').test(name) ) ||
- (new RegExp('wifi', 'i').test(name))) {
- sensorName = 'NETWORKS';
- } else if(new RegExp('co', 'i').test(name) ) {
- sensorName = 'CO';
- } else if(new RegExp('no2', 'i').test(name) ) {
- sensorName = 'NO2';
- } else if(new RegExp('humidity', 'i').test(name) ) {
- sensorName = 'HUMIDITY';
- } else if(new RegExp('temperature', 'i').test(name) ) {
- sensorName = 'TEMPERATURE';
- } else if(new RegExp('panel', 'i').test(name) ) {
- sensorName = 'SOLAR PANEL';
- } else if(new RegExp('battery', 'i').test(name) ) {
- sensorName = 'BATTERY';
- } else if(new RegExp('barometric pressure', 'i').test(name) ) {
- sensorName = 'BAROMETRIC PRESSURE';
- } else if(new RegExp('PM 1', 'i').test(name) ) {
- sensorName = 'PM 1';
- } else if(new RegExp('PM 2.5', 'i').test(name) ) {
- sensorName = 'PM 2.5';
- } else if(new RegExp('PM 10', 'i').test(name) ) {
- sensorName = 'PM 10';
- } else {
- sensorName = name;
+controller.$inject = ['device', 'Papa', '$mdDialog', '$q'];
+function controller(device, Papa, $mdDialog, $q) {
+ var vm = this;
+ vm.loadingStatus = false;
+ vm.loadingProgress = 0;
+ vm.loadingType = 'indeterminate';
+ vm.csvFiles = [];
+ vm.$onInit = function() {
+ vm.kitLastUpdate = Math.floor(new Date(vm.kit.time).getTime() / 1000);
+ }
+ vm.onSelect = function() {
+ vm.loadingStatus = true;
+ vm.loadingType = 'indeterminate';
+ }
+ vm.change = function(files, invalidFiles) {
+ let count = 0;
+ vm.invalidFiles = invalidFiles;
+ if (!files) { return; }
+ vm.loadingStatus = true;
+ vm.loadingType = 'determinate';
+ vm.loadingProgress = 0;
+ $q.all(
+ files
+ .filter((file) => vm._checkDuplicate(file))
+ .map((file, index, filteredFiles) => {
+ vm.csvFiles.push(file);
+ return vm._analyzeData(file)
+ .then((result) => {
+ if (result.errors && result.errors.length > 0) {
+ file.parseErrors = result.errors;
}
- }
- return sensorName.toUpperCase();
- }
+ const lastTimestamp = Math.floor((new Date(result.data[result.data.length - 1][0])).getTime() / 1000);
+ const isNew = vm.kitLastUpdate < lastTimestamp;
+ file.checked = isNew;
+ file.progress = null;
+ file.isNew = isNew;
+ })
+ .then(() => {
+ count += 1;
+ vm.loadingProgress = (count)/filteredFiles.length * 100;
- function getSensorValue(sensor) {
- var value = sensor.value;
+ });
+ })
+ ).then(() => {
+ vm.loadingStatus = false;
+ }).catch(() => {
+ vm.loadingStatus = false;
+ });
+ }
- if(isNaN(parseInt(value))) {
- value = 'NA';
- } else {
- value = round(value, 1).toString();
- }
+ vm.haveSelectedFiles = function() {
+ return vm.csvFiles && vm.csvFiles.some((file) => file.checked);
+ };
- return value;
- }
+ vm.haveSelectedNoFiles = function() {
+ return vm.csvFiles && !vm.csvFiles.some((file) => file.checked);
+ };
- function round(value, precision) {
- var multiplier = Math.pow(10, precision || 0);
- return Math.round(value * multiplier) / multiplier;
- }
+ vm.haveSelectedAllFiles = function() {
+ return vm.csvFiles && vm.csvFiles.every((file) => file.checked);
+ };
- function getSensorPrevValue(sensor) {
- /*jshint camelcase: false */
- var prevValue = sensor.prev_value;
- return (prevValue && prevValue.toString() ) || 0;
- }
+ vm.doAction = function() {
+ switch (vm.action) {
+ case 'selectAll':
+ vm.selectAll(true);
+ break;
+ case 'deselectAll':
+ vm.selectAll(false);
+ break;
+ case 'upload':
+ vm.uploadData();
+ break;
+ case 'remove':
+ vm.csvFiles = vm.csvFiles.filter((file) => !file.checked);
+ break;
+ }
+ vm.action = null;
+ };
- function getSensorIcon(sensorName) {
+ vm.selectAll = function(value) {
+ vm.csvFiles.forEach((file) => { file.checked = value });
+ };
- var thisName = getSensorName(sensorName);
+ vm.removeFile = function(index) {
+ vm.csvFiles.splice(index, 1);
+ };
+ vm._analyzeData = function(file) {
+ file.progress = true;
+ return Papa.parse(file, {
+ delimiter: ',',
+ dynamicTyping: true,
+ worker: false,
+ skipEmptyLines: true
+ }).catch((err) => {
+ file.progress = null;
+ console('catch',err)
+ });
+ };
- switch(thisName) {
- case 'TEMPERATURE':
- return './assets/images/temperature_icon_new.svg';
+ vm._checkDuplicate = function(file) {
+ if (vm.csvFiles.some((csvFile) => file.name === csvFile.name)) {
+ file.$errorMessages = {};
+ file.$errorMessages.duplicate = true;
+ vm.invalidFiles.push(file);
+ return false;
+ } else {
+ return true;
+ }
+ };
- case 'HUMIDITY':
- return './assets/images/humidity_icon_new.svg';
+ vm.showErrorModal = function(csvFile) {
+ $mdDialog.show({
+ hasBackdrop: true,
+ controller: ['$mdDialog',function($mdDialog) {
+ this.parseErrors = csvFile.parseErrors;
+ this.backEndErrors = csvFile.backEndErrors;
+ this.cancel = function() { $mdDialog.hide(); };
+ }],
+ controllerAs: 'csvFile',
+ templateUrl: 'app/components/upload/errorModal.html',
+ clickOutsideToClose: true
+ });
+ }
- case 'LIGHT':
- return './assets/images/light_icon_new.svg';
- case 'SOUND':
- return './assets/images/sound_icon_new.svg';
+ vm.uploadData = function() {
+ vm.loadingStatus = true;
+ vm.loadingType = 'indeterminate';
+ vm.loadingProgress = 0;
+ let count = 0;
- case 'CO':
- return './assets/images/co_icon_new.svg';
+ $q.all(
+ vm.csvFiles
+ .filter((file) => file.checked && !file.success)
+ .map((file, index, filteredFiles) => {
+ file.progress = true;
+ return vm._analyzeData(file)
+ .then((result) => parseDataForPost(result.data)) // TODO: Improvement remove
+ // TODO: Improvement with workers
+ .then((payload) => device.postReadings(vm.kit, payload))
+ .then(() => {
+ if (vm.loadingType === 'indeterminate') { vm.loadingType = 'determinate'; };
+ file.success = true;
+ file.progress = null;
+ count += 1;
+ vm.loadingProgress = (count)/filteredFiles.length * 100;
+ })
+ .catch((errors) => {
+ console.log(errors);
+ file.detailShowed = true;
+ file.backEndErrors = errors;
+ file.progress = null;
+ });
+ })
+ ).then(() => {
+ vm.loadingStatus = false;
+ })
+ .catch(() => {
+ vm.loadingStatus = false;
+ });
+ }
+};
- case 'NO2':
- return './assets/images/no2_icon_new.svg';
- case 'NETWORKS':
- return './assets/images/networks_icon.svg';
+angular.module('app.components')
+ .component('scCsvUpload', {
+ templateUrl: 'app/components/upload/csvUpload.html',
+ controller: controller,
+ bindings: {
+ kit: '<'
+ },
+ controllerAs: 'vm'
+ });
+})();
- case 'BATTERY':
- return './assets/images/battery_icon.svg';
+(function() {
+ 'use strict';
- case 'SOLAR PANEL':
- return './assets/images/solar_panel_icon.svg';
+ angular.module('app.components')
+ .controller('tagsController', tagsController);
- case 'BAROMETRIC PRESSURE':
- return './assets/images/pressure_icon_new.svg';
+ tagsController.$inject = ['tag', '$scope', 'device', '$state', '$q',
+ 'PreviewDevice', 'animation'
+ ];
- case 'PM 1':
- case 'PM 2.5':
- case 'PM 10':
- return './assets/images/particle_icon_new.svg';
+ function tagsController(tag, $scope, device, $state, $q, PreviewDevice,
+ animation) {
- default:
- return './assets/images/unknownsensor_icon.svg';
- }
- }
+ var vm = this;
- function getSensorArrow(currentValue, prevValue) {
- currentValue = parseInt(currentValue) || 0;
- prevValue = parseInt(prevValue) || 0;
+ vm.selectedTags = tag.getSelectedTags();
+ vm.markers = [];
+ vm.kits = [];
+ vm.percActive = 0;
- if(currentValue > prevValue) {
- return 'arrow_up';
- } else if(currentValue < prevValue) {
- return 'arrow_down';
- } else {
- return 'equal';
- }
- }
+ initialize();
- function getSensorColor(sensorName) {
- switch(getSensorName(sensorName)) {
- case 'TEMPERATURE':
- return '#FF3D4C';
+ /////////////////////////////////////////////////////////
- case 'HUMIDITY':
- return '#55C4F5';
+ function initialize() {
+ if(vm.selectedTags.length === 0){
+ $state.transitionTo('layout.home.kit');
+ }
- case 'LIGHT':
- return '#ffc107';
+ if (device.getWorldMarkers()) {
+ // If the user has already loaded a prev page and has markers in mem or localstorage
+ updateSelectedTags();
+ } else {
+ // If the user is new we wait the map to load the markers
+ $scope.$on('mapStateLoaded', function(event, data) {
+ updateSelectedTags();
+ });
+ }
- case 'SOUND':
- return '#0019FF';
+ }
- case 'CO':
- return '#00A103';
+ function updateSelectedTags(){
- case 'NO2':
- return '#8cc252';
+ vm.markers = tag.filterMarkersByTag(device.getWorldMarkers());
- case 'NETWORKS':
- return '#681EBD';
+ var onlineMarkers = _.filter(vm.markers, isOnline);
+ if (vm.markers.length === 0) {
+ vm.percActive = 0;
+ } else {
+ vm.percActive = Math.floor(onlineMarkers.length / vm.markers.length *
+ 100);
+ }
- case 'SOLAR PANEL':
- return '#d555ce';
+ animation.viewLoaded();
- case 'BATTERY':
- return '#ff8601';
+ getTaggedDevices()
+ .then(function(res){
+ vm.kits = res;
+ });
+ }
- default:
- return '#0019FF';
- }
- }
- function getSensorDescription(sensorID, sensorTypes) {
- return _(sensorTypes)
- .chain()
- .find(function(sensorType) {
- return sensorType.id === sensorID;
- })
- .value()
- .measurement.description;
- }
+ function isOnline(marker) {
+ return _.includes(marker.myData.labels, 'online');
}
-})();
-(function() {
- 'use strict';
+ function descLastUpdate(o) {
+ return -new Date(o.last_reading_at).getTime();
+ }
- angular.module('app.components')
- .factory('searchUtils', searchUtils);
+ function getTaggedDevices() {
+ var deviceProm = _.map(vm.markers, getMarkerDevice);
- searchUtils.$inject = [];
- function searchUtils() {
- var service = {
- parseLocation: parseLocation,
- parseName: parseName,
- parseIcon: parseIcon,
- parseIconType: parseIconType
- };
- return service;
+ return $q.all(deviceProm)
+ .then(function(devices) {
+ return _.map(_.sortBy(devices, descLastUpdate), toPreviewDevice); // This sort is temp
+ });
+ }
- /////////////////
+ function toPreviewDevice(dev) {
+ return new PreviewDevice(dev);
+ }
- function parseLocation(object) {
- var location = '';
+ function getMarkerDevice(marker) {
+ return device.getDevice(marker.myData.id);
+ }
+ }
- if(!!object.city) {
- location += object.city;
- }
- if(!!object.city && !!object.country) {
- location += ', ';
- }
- if(!!object.country) {
- location += object.country;
- }
+})();
- return location;
- }
+(function(){
+ 'use strict';
+ angular.module('app.components')
+ .directive('tag',tag);
- function parseName(object) {
- var name = object.type === 'User' ? object.username : object.name;
- return name;
- }
+ function tag(){
+ return{
+ restrict: 'E',
+ scope:{
+ tagName: '=',
+ openTag: '&'
+ },
+ controller:function($scope, $state){
+ $scope.openTag = function(){
+ $state.go('layout.home.tags', {tags:[$scope.tagName]});
+ };
+ },
+ template:'{{tagName}}',
+ link: function(scope, element, attrs){
+ element.addClass('tag');
- function parseIcon(object, type) {
- switch(type) {
- case 'User':
- return object.profile_picture;
- case 'Device':
- return 'assets/images/kit.svg';
- case 'Country':
- case 'City':
- return 'assets/images/location_icon_normal.svg';
+ if(typeof(attrs.clickable) !== 'undefined'){
+ element.bind('click', scope.openTag);
}
}
+ };
+ }
+})();
- function parseIconType(type) {
- switch(type) {
- case 'Device':
- return 'div';
- default:
- return 'img';
- }
- }
+(function() {
+ 'use strict';
+
+ angular.module('app.components')
+ .controller('StoreModalController', StoreModalController);
+
+ StoreModalController.$inject = ['$scope', '$mdDialog'];
+ function StoreModalController($scope, $mdDialog) {
+
+ $scope.cancel = function() {
+ $mdDialog.hide();
+ };
}
})();
(function() {
'use strict';
- angular.module('app.components')
- .factory('markerUtils', markerUtils);
+ angular.module('app.components')
+ .directive('store', store);
- markerUtils.$inject = ['deviceUtils', 'MARKER_ICONS'];
- function markerUtils(deviceUtils, MARKER_ICONS) {
- var service = {
- getIcon: getIcon,
- getMarkerIcon: getMarkerIcon,
+ function store() {
+ return {
+ scope: {
+ isLoggedin: '=logged'
+ },
+ restrict: 'A',
+ controller: 'StoreController',
+ controllerAs: 'vm',
+ templateUrl: 'app/components/store/store.html'
};
- _.defaults(service, deviceUtils);
- return service;
+ }
+})();
- ///////////////
+(function() {
+ 'use strict';
- function getIcon(object) {
- var icon;
- var labels = deviceUtils.parseSystemTags(object);
- var isSCKHardware = deviceUtils.isSCKHardware(object);
+ angular.module('app.components')
+ .controller('StoreController', StoreController);
- if(hasLabel(labels, 'offline')) {
- icon = MARKER_ICONS.markerSmartCitizenOffline;
- } else if (isSCKHardware) {
- icon = MARKER_ICONS.markerSmartCitizenOnline;
- } else {
- icon = MARKER_ICONS.markerExperimentalNormal;
- }
- return icon;
- }
+ StoreController.$inject = ['$scope', '$mdDialog'];
+ function StoreController($scope, $mdDialog) {
- function hasLabel(labels, targetLabel) {
- return _.some(labels, function(label) {
- return label === targetLabel;
- });
- }
+ $scope.showStore = showStore;
- function getMarkerIcon(marker, state) {
- var markerType = marker.icon.className;
+ $scope.$on('showStore', function() {
+ showStore();
+ });
+
+ ////////////////
- if(state === 'active') {
- marker.icon = MARKER_ICONS[markerType + 'Active'];
- marker.focus = true;
- } else if(state === 'inactive') {
- var targetClass = markerType.split(' ')[0];
- marker.icon = MARKER_ICONS[targetClass];
- }
- return marker;
- }
+ function showStore() {
+ $mdDialog.show({
+ hasBackdrop: true,
+ controller: 'StoreModalController',
+ templateUrl: 'app/components/store/storeModal.html',
+ clickOutsideToClose: true
+ });
}
+
+ }
})();
(function() {
'use strict';
angular.module('app.components')
- .factory('mapUtils', mapUtils);
+ .controller('StaticController', StaticController);
- mapUtils.$inject = [];
- function mapUtils() {
- var service = {
- getDefaultFilters: getDefaultFilters,
- setDefaultFilters: setDefaultFilters,
- canFilterBeRemoved: canFilterBeRemoved
- };
- return service;
+ StaticController.$inject = ['$timeout', 'animation', '$mdDialog', '$location', '$anchorScroll'];
- //////////////
+ function StaticController($timeout, animation, $mdDialog, $location, $anchorScroll) {
+ var vm = this;
- function getDefaultFilters(filterData, defaultFilters) {
- var obj = {};
- if(!filterData.indoor && !filterData.outdoor) {
- obj[defaultFilters.exposure] = true;
- }
- if(!filterData.online && !filterData.offline) {
- obj[defaultFilters.status] = true;
- }
- return obj;
- }
+ vm.showStore = showStore;
- function setDefaultFilters(filterData) {
- var obj = {};
- if(!filterData.indoor || !filterData.outdoor) {
- obj.exposure = filterData.indoor ? 'indoor' : 'outdoor';
- }
- if(!filterData.online || !filterData.offline) {
- obj.status = filterData.online ? 'online' : 'offline';
- }
- return obj;
- }
+ $anchorScroll.yOffset = 80;
- function canFilterBeRemoved(filterData, filterName) {
- if(filterName === 'indoor' || filterName === 'outdoor') {
- return filterData.indoor && filterData.outdoor;
- } else if(filterName === 'online' || filterName === 'offline') {
- return filterData.online && filterData.offline;
+ ///////////////////////
+
+ initialize();
+
+ //////////////////
+
+ function initialize() {
+ $timeout(function() {
+ animation.viewLoaded();
+ if($location.hash()){
+ $anchorScroll();
}
- }
+ }, 500);
+ }
+
+ function showStore() {
+ $mdDialog.show({
+ hasBackdrop: true,
+ controller: 'StoreModalController',
+ templateUrl: 'app/components/store/storeModal.html',
+ clickOutsideToClose: true
+ });
}
+ }
})();
(function() {
'use strict';
+
angular.module('app.components')
- .config(function ($provide) {
- $provide.decorator('$exceptionHandler', ['$delegate', function($delegate) {
- return function (exception, cause) {
- /*jshint camelcase: false */
- $delegate(exception, cause);
- };
- }]);
+ .controller('SignupModalController', SignupModalController);
- });
+ SignupModalController.$inject = ['$scope', '$mdDialog', 'user',
+ 'alert', 'animation'];
+ function SignupModalController($scope, $mdDialog, user,
+ alert, animation ) {
+ var vm = this;
+ vm.answer = function(signupForm) {
+
+ if (!signupForm.$valid){
+ return;
+ }
+
+ $scope.waitingFromServer = true;
+ user.createUser(vm.user)
+ .then(function() {
+ alert.success('Signup was successful');
+ $mdDialog.hide();
+ }).catch(function(err) {
+ alert.error('Signup failed');
+ $scope.errors = err.data.errors;
+ })
+ .finally(function() {
+ $scope.waitingFromServer = false;
+ });
+ };
+ $scope.hide = function() {
+ $mdDialog.hide();
+ };
+ $scope.cancel = function() {
+ $mdDialog.cancel();
+ };
+
+ $scope.openLogin = function() {
+ animation.showLogin();
+ $mdDialog.hide();
+ };
+ }
})();
(function() {
'use strict';
- angular.module('app.components')
- .factory('deviceUtils', deviceUtils);
+ angular.module('app.components')
+ .directive('signup', signup);
- deviceUtils.$inject = ['COUNTRY_CODES', 'device'];
- function deviceUtils(COUNTRY_CODES, device) {
- var service = {
- parseLocation: parseLocation,
- parseCoordinates: parseCoordinates,
- parseSystemTags: parseSystemTags,
- parseUserTags: parseUserTags,
- classify: classify,
- parseNotifications: parseNotifications,
- parseOwner: parseOwner,
- parseName: parseName,
- parseString: parseString,
- parseHardware: parseHardware,
- parseHardwareInfo: parseHardwareInfo,
- parseHardwareName: parseHardwareName,
- isPrivate: isPrivate,
- preciseLocation: preciseLocation,
- enableForwarding: enableForwarding,
- isLegacyVersion: isLegacyVersion,
- isSCKHardware: isSCKHardware,
- parseState: parseState,
- parseAvatar: parseAvatar,
- belongsToUser: belongsToUser,
- parseSensorTime: parseSensorTime
+ function signup() {
+ return {
+ scope: {
+ show: '=',
+ },
+ restrict: 'A',
+ controller: 'SignupController',
+ controllerAs: 'vm',
+ templateUrl: 'app/components/signup/signup.html'
};
+ }
+})();
- return service;
+(function() {
+ 'use strict';
- ///////////////
+ angular.module('app.components')
+ .controller('SignupController', SignupController);
- function parseLocation(object) {
- var location = '';
- var city = '';
- var country = '';
+ SignupController.$inject = ['$scope', '$mdDialog'];
+ function SignupController($scope, $mdDialog) {
+ var vm = this;
- if (object.location) {
- city = object.location.city;
- country = object.location.country;
- if(!!city) {
- location += city;
- }
- if(!!city && !!location) {
- location += ', '
- }
- if(!!country) {
- location += country;
- }
- }
- return location;
- }
+ vm.showSignup = showSignup;
- function parseCoordinates(object) {
- if (object.location) {
- return {
- lat: object.location.latitude,
- lng: object.location.longitude
- };
- }
- // TODO: Bug - what happens if no location?
- }
+ $scope.$on('showSignup', function() {
+ showSignup();
+ });
+ ////////////////////////
- function parseSystemTags(object) {
- /*jshint camelcase: false */
- return object.system_tags;
- }
- function parseUserTags(object) {
- return object.user_tags;
+ function showSignup() {
+ $mdDialog.show({
+ fullscreen: true,
+ hasBackdrop: true,
+ controller: 'SignupModalController',
+ controllerAs: 'vm',
+ templateUrl: 'app/components/signup/signupModal.html',
+ clickOutsideToClose: true
+ });
}
+ }
+})();
- function parseNotifications(object){
- return {
- lowBattery: object.notify.low_battery,
- stopPublishing: object.notify.stopped_publishing
- }
- }
+(function() {
+'use strict';
- function classify(kitType) {
- if(!kitType) {
- return '';
- }
- return kitType.toLowerCase().split(' ').join('_');
- }
- function parseName(object, trim=false) {
- if(!object.name) {
- return;
- }
- if (trim) {
- return object.name.length <= 41 ? object.name : object.name.slice(0, 35).concat(' ... ');
- }
- return object.name;
- }
+ angular.module('app.components')
+ .directive('search', search);
- function parseHardware(object) {
- if (!object.hardware) {
- return;
- }
+ function search() {
+ return {
+ scope: true,
+ restrict: 'E',
+ templateUrl: 'app/components/search/search.html',
+ controller: 'SearchController',
+ controllerAs: 'vm'
+ };
+ }
+})();
- return {
- name: parseString(object.hardware.name),
- type: parseString(object.hardware.type),
- description: parseString(object.hardware.description),
- version: parseVersionString(object.hardware.version),
- slug: object.hardware.slug,
- info: parseHardwareInfo(object.hardware.info)
- }
- }
+(function() {
+ 'use strict';
- function parseString(str) {
- if (typeof(str) !== 'string') { return null; }
- return str;
- }
+ angular.module('app.components')
+ .controller('SearchController', SearchController);
- function parseVersionString (str) {
- if (typeof(str) !== 'string') { return null; }
- var x = str.split('.');
- // parse from string or default to 0 if can't parse
- var maj = parseInt(x[0]) || 0;
- var min = parseInt(x[1]) || 0;
- var pat = parseInt(x[2]) || 0;
- return {
- major: maj,
- minor: min,
- patch: pat
- };
- }
+ SearchController.$inject = ['$scope', 'search', 'SearchResult', '$location', 'animation', 'SearchResultLocation'];
+ function SearchController($scope, search, SearchResult, $location, animation, SearchResultLocation) {
+ var vm = this;
- function parseHardwareInfo (object) {
- if (!object) { return null; } // null
- if (typeof(object) == 'string') { return null; } // FILTERED
+ vm.searchTextChange = searchTextChange;
+ vm.selectedItemChange = selectedItemChange;
+ vm.querySearch = querySearch;
- var id = parseString(object.id);
- var mac = parseString(object.mac);
- var time = Date(object.time);
- var esp_bd = parseString(object.esp_bd);
- var hw_ver = parseString(object.hw_ver);
- var sam_bd = parseString(object.sam_bd);
- var esp_ver = parseString(object.esp_ver);
- var sam_ver = parseString(object.sam_ver);
+ ///////////////////
- return {
- id: id,
- mac: mac,
- time: time,
- esp_bd: esp_bd,
- hw_ver: hw_ver,
- sam_bd: sam_bd,
- esp_ver: esp_ver,
- sam_ver: sam_ver
- };
+ function searchTextChange() {
}
- function parseHardwareName(object) {
- if (object.hasOwnProperty('hardware')) {
- if (!object.hardware.name) {
- return 'Unknown hardware'
- }
- return object.hardware.name;
- } else {
- return 'Unknown hardware'
+ function selectedItemChange(result) {
+ if (!result) { return; }
+ if(result.type === 'User') {
+ $location.path('/users/' + result.id);
+ } else if(result.type === 'Device') {
+ $location.path('/kits/' + result.id);
+ } else if (result.type === 'City'){
+ animation.goToLocation({lat: result.lat, lng: result.lng, type: result.type, layer: result.layer});
}
}
- function isPrivate(object) {
- return object.data_policy.is_private;
- }
+ function querySearch(query) {
+ if(query.length < 3) {
+ return [];
+ }
- function preciseLocation(object) {
- return object.data_policy.precise_location;
- }
+ return search.globalSearch(query)
+ .then(function(data) {
- function enableForwarding(object) {
- return object.data_policy.enable_forwarding ;
- }
+ return data.map(function(object) {
- function isLegacyVersion (object) {
- if (!object.hardware || !object.hardware.version || object.hardware.version.major > 1) {
- return false;
- } else {
- if (object.hardware.version.major == 1 && object.hardware.version.minor <5 ){
- return true;
- }
- return false;
- }
+ if(object.type === 'City' || object.type === 'Country') {
+ return new SearchResultLocation(object);
+ } else {
+ return new SearchResult(object);
+ }
+ });
+ });
}
+ }
+})();
- function isSCKHardware (object){
- if (!object.hardware || !object.hardware.type || object.hardware.type != 'SCK') {
- return false;
- } else {
- return true;
- }
- }
+(function() {
+ 'use strict';
- function parseOwner(object) {
- return {
- id: object.owner.id,
- username: object.owner.username,
- /*jshint camelcase: false */
- devices: object.owner.device_ids,
- city: object.owner.location.city,
- country: COUNTRY_CODES[object.owner.location.country_code],
- url: object.owner.url,
- profile_picture: object.owner.profile_picture
- };
- }
+ angular.module('app.components')
+ .controller('PasswordResetController', PasswordResetController);
- function parseState(status) {
- var name = parseStateName(status);
- var className = classify(name);
+ PasswordResetController.$inject = ['$mdDialog', '$stateParams', '$timeout',
+ 'animation', '$location', 'alert', 'auth'];
+ function PasswordResetController($mdDialog, $stateParams, $timeout,
+ animation, $location, alert, auth) {
+
+ var vm = this;
+ vm.showForm = false;
+ vm.form = {};
+ vm.isDifferent = false;
+ vm.answer = answer;
- return {
- name: name,
- className: className
- };
- }
+ initialize();
+ ///////////
- function parseStateName(object) {
- return object.state.replace('_', ' ');
+ function initialize() {
+ $timeout(function() {
+ animation.viewLoaded();
+ }, 500);
+ getUserData();
}
- function parseAvatar() {
- return './assets/images/sckit_avatar.jpg';
+ function getUserData() {
+ auth.getResetPassword($stateParams.code)
+ .then(function() {
+ vm.showForm = true;
+ })
+ .catch(function() {
+ alert.error('Wrong url');
+ $location.path('/');
+ });
}
- function parseSensorTime(sensor) {
- /*jshint camelcase: false */
- return moment(sensor.recorded_at).format('');
- }
+ function answer(data) {
+ vm.waitingFromServer = true;
+ vm.errors = undefined;
- function belongsToUser(devicesArray, deviceID) {
- return _.some(devicesArray, function(device) {
- return device.id === deviceID;
- });
+ if(data.newPassword === data.confirmPassword) {
+ vm.isDifferent = false;
+ } else {
+ vm.isDifferent = true;
+ return;
+ }
+
+ auth.patchResetPassword($stateParams.code, {password: data.newPassword})
+ .then(function() {
+ alert.success('Your data was updated successfully');
+ $location.path('/profile');
+ })
+ .catch(function(err) {
+ alert.error('Your data wasn\'t updated');
+ vm.errors = err.data.errors;
+ })
+ .finally(function() {
+ vm.waitingFromServer = false;
+ });
}
}
})();
@@ -2623,25 +2605,42 @@
'use strict';
angular.module('app.components')
- .filter('filterLabel', filterLabel);
+ .controller('PasswordRecoveryModalController', PasswordRecoveryModalController);
+ PasswordRecoveryModalController.$inject = ['$scope', 'animation', '$mdDialog', 'auth', 'alert'];
+ function PasswordRecoveryModalController($scope, animation, $mdDialog, auth, alert) {
- function filterLabel() {
- return function(devices, targetLabel) {
- if(targetLabel === undefined) {
- return devices;
- }
- if(devices) {
- return _.filter(devices, function(device) {
- var containsLabel = device.systemTags.indexOf(targetLabel) !== -1;
- if(containsLabel) {
- return containsLabel;
- }
- // This should be fixed or polished in the future
- // var containsNewIfTargetIsOnline = targetLabel === 'online' && _.some(kit.labels, function(label) {return label.indexOf('new') !== -1;});
- // return containsNewIfTargetIsOnline;
- });
- }
+ $scope.hide = function() {
+ $mdDialog.hide();
+ };
+ $scope.cancel = function() {
+ $mdDialog.cancel();
+ };
+
+ $scope.recoverPassword = function() {
+ $scope.waitingFromServer = true;
+ var data = {
+ /*jshint camelcase: false */
+ email_or_username: $scope.input
+ };
+
+ auth.recoverPassword(data)
+ .then(function() {
+ alert.success('You were sent an email to recover your password');
+ $mdDialog.hide();
+ })
+ .catch(function(err) {
+ alert.error('That username doesn\'t exist');
+ $scope.errors = err.data;
+ })
+ .finally(function() {
+ $scope.waitingFromServer = false;
+ });
+ };
+
+ $scope.openSignup = function() {
+ animation.showSignup();
+ $mdDialog.hide();
};
}
})();
@@ -2649,1634 +2648,1199 @@
(function() {
'use strict';
- /**
- * Tools links for user profile
- * @constant
- * @type {Array}
- */
-
angular.module('app.components')
- .constant('PROFILE_TOOLS', [{
- type: 'documentation',
- title: 'How to connect your Smart Citizen Kit tutorial',
- description: 'Adding a Smart Citizen Kit tutorial',
- avatar: '',
- href: 'http://docs.smartcitizen.me/#/start/adding-a-smart-citizen-kit'
- }, {
- type: 'documentation',
- title: 'Download the latest Smart Citizen Kit Firmware',
- description: 'The latest Arduino firmware for your kit',
- avatar: '',
- href: 'https://github.com/fablabbcn/Smart-Citizen-Kit/releases/latest'
- }, {
- type: 'documentation',
- title: 'API Documentation',
- description: 'Documentation for the new API',
- avatar: '',
- href: 'http://developer.smartcitizen.me/'
- }, {
- type: 'community',
- title: 'Smart Citizen Forum',
- description: 'Join the community discussion. Your feedback is important for us.',
- avatar: '',
- href:'http://forum.smartcitizen.me/'
- }, {
- type: 'documentation',
- title: 'Smart Citizen Kit hardware details',
- description: 'Visit the docs',
- avatar: 'https://docs.smartcitizen.me/#/start/hardware'
- }, {
- type: 'documentation',
- title: 'Style Guide',
- description: 'Guidelines of the Smart Citizen UI',
- avatar: '',
- href: '/styleguide'
- }, {
- type: 'social',
- title: 'Like us on Facebook',
- description: 'Join the community on Facebook',
- avatar: '',
- href: 'https://www.facebook.com/smartcitizenBCN'
- }, {
- type: 'social',
- title: 'Follow us on Twitter',
- description: 'Follow our news on Twitter',
- avatar: '',
- href: 'https://twitter.com/SmartCitizenKit'
- }]);
-})();
+ .controller('PasswordRecoveryController', PasswordRecoveryController);
-(function() {
- 'use strict';
+ PasswordRecoveryController.$inject = ['auth', 'alert', '$mdDialog'];
+ function PasswordRecoveryController(auth, alert, $mdDialog) {
+ var vm = this;
- /**
- * Marker icons
- * @constant
- * @type {Object}
- */
+ vm.waitingFromServer = false;
+ vm.errors = undefined;
+ vm.recoverPassword = recoverPassword;
- angular.module('app.components')
- .constant('MARKER_ICONS', {
- defaultIcon: {},
- markerSmartCitizenNormal: {
- type: 'div',
- className: 'markerSmartCitizenNormal',
- iconSize: [24, 24]
- },
- markerExperimentalNormal: {
- type: 'div',
- className: 'markerExperimentalNormal',
- iconSize: [24, 24]
- },
- markerSmartCitizenOnline: {
- type: 'div',
- className: 'markerSmartCitizenOnline',
- iconSize: [24, 24]
- },
- markerSmartCitizenOnlineActive: {
- type: 'div',
- className: 'markerSmartCitizenOnline marker_blink',
- iconSize: [24, 24]
- },
- markerSmartCitizenOffline: {
- type: 'div',
- className: 'markerSmartCitizenOffline',
- iconSize: [24, 24]
- },
- markerSmartCitizenOfflineActive: {
- type: 'div',
- className: 'markerSmartCitizenOffline marker_blink',
- iconSize: [24, 24]
+ ///////////////
+
+ function recoverPassword() {
+ vm.waitingFromServer = true;
+ vm.errors = undefined;
+
+ var data = {
+ username: vm.username
+ };
+
+ auth.recoverPassword(data)
+ .then(function() {
+ alert.success('You were sent an email to recover your password');
+ $mdDialog.hide();
+ })
+ .catch(function(err) {
+ vm.errors = err.data.errors;
+ if(vm.errors) {
+ alert.error('That email/username doesn\'t exist');
+ }
+ })
+ .finally(function() {
+ vm.waitingFromServer = false;
+ });
}
- });
+ }
})();
(function() {
'use strict';
- /**
- * Dropdown options for user
- * @constant
- * @type {Array}
- */
angular.module('app.components')
- .constant('DROPDOWN_OPTIONS_USER', [
- {divider: true, text: 'Hi,', href: './profile'},
- {text: 'My profile', href: './profile'},
- {text: 'Log out', href: './logout'}
- ]);
-})();
+ .controller('MyProfileController', MyProfileController);
-(function() {
- 'use strict';
+ MyProfileController.$inject = ['$scope', '$location', '$q', '$interval',
+ 'userData', 'AuthUser', 'user', 'auth', 'alert',
+ 'COUNTRY_CODES', '$timeout', 'file', 'animation',
+ '$mdDialog', 'PreviewDevice', 'device', 'deviceUtils',
+ 'userUtils', '$filter', '$state', 'Restangular', '$window'];
+ function MyProfileController($scope, $location, $q, $interval,
+ userData, AuthUser, user, auth, alert,
+ COUNTRY_CODES, $timeout, file, animation,
+ $mdDialog, PreviewDevice, device, deviceUtils,
+ userUtils, $filter, $state, Restangular, $window) {
- /**
- * Dropdown options for community button
- * @constant
- * @type {Array}
- */
+ var vm = this;
- angular.module('app.components')
- .constant('DROPDOWN_OPTIONS_COMMUNITY', [
- {text: 'About', href: '/about'},
- {text: 'Forum', href: 'https://forum.smartcitizen.me/'},
- {text: 'Documentation', href: 'http://docs.smartcitizen.me/'},
- {text: 'API Reference', href: 'http://developer.smartcitizen.me/'},
- {text: 'Github', href: 'https://github.com/fablabbcn/Smart-Citizen-Kit'},
- {text: 'Legal', href: '/policy'}
- ]);
-})();
+ vm.unhighlightIcon = unhighlightIcon;
-(function() {
- 'use strict';
+ //PROFILE TAB
+ vm.formUser = {};
+ vm.getCountries = getCountries;
- /**
- * Country codes.
- * @constant
- * @type {Object}
- */
-
- angular.module('app.components')
- .constant('COUNTRY_CODES', {
- 'AF': 'Afghanistan',
- 'AX': 'Aland Islands',
- 'AL': 'Albania',
- 'DZ': 'Algeria',
- 'AS': 'American Samoa',
- 'AD': 'Andorra',
- 'AO': 'Angola',
- 'AI': 'Anguilla',
- 'AQ': 'Antarctica',
- 'AG': 'Antigua And Barbuda',
- 'AR': 'Argentina',
- 'AM': 'Armenia',
- 'AW': 'Aruba',
- 'AU': 'Australia',
- 'AT': 'Austria',
- 'AZ': 'Azerbaijan',
- 'BS': 'Bahamas',
- 'BH': 'Bahrain',
- 'BD': 'Bangladesh',
- 'BB': 'Barbados',
- 'BY': 'Belarus',
- 'BE': 'Belgium',
- 'BZ': 'Belize',
- 'BJ': 'Benin',
- 'BM': 'Bermuda',
- 'BT': 'Bhutan',
- 'BO': 'Bolivia',
- 'BA': 'Bosnia And Herzegovina',
- 'BW': 'Botswana',
- 'BV': 'Bouvet Island',
- 'BR': 'Brazil',
- 'IO': 'British Indian Ocean Territory',
- 'BN': 'Brunei Darussalam',
- 'BG': 'Bulgaria',
- 'BF': 'Burkina Faso',
- 'BI': 'Burundi',
- 'KH': 'Cambodia',
- 'CM': 'Cameroon',
- 'CA': 'Canada',
- 'CV': 'Cape Verde',
- 'KY': 'Cayman Islands',
- 'CF': 'Central African Republic',
- 'TD': 'Chad',
- 'CL': 'Chile',
- 'CN': 'China',
- 'CX': 'Christmas Island',
- 'CC': 'Cocos (Keeling) Islands',
- 'CO': 'Colombia',
- 'KM': 'Comoros',
- 'CG': 'Congo',
- 'CD': 'Congo, Democratic Republic',
- 'CK': 'Cook Islands',
- 'CR': 'Costa Rica',
- 'CI': 'Cote D\'Ivoire',
- 'HR': 'Croatia',
- 'CU': 'Cuba',
- 'CY': 'Cyprus',
- 'CZ': 'Czech Republic',
- 'DK': 'Denmark',
- 'DJ': 'Djibouti',
- 'DM': 'Dominica',
- 'DO': 'Dominican Republic',
- 'EC': 'Ecuador',
- 'EG': 'Egypt',
- 'SV': 'El Salvador',
- 'GQ': 'Equatorial Guinea',
- 'ER': 'Eritrea',
- 'EE': 'Estonia',
- 'ET': 'Ethiopia',
- 'FK': 'Falkland Islands (Malvinas)',
- 'FO': 'Faroe Islands',
- 'FJ': 'Fiji',
- 'FI': 'Finland',
- 'FR': 'France',
- 'GF': 'French Guiana',
- 'PF': 'French Polynesia',
- 'TF': 'French Southern Territories',
- 'GA': 'Gabon',
- 'GM': 'Gambia',
- 'GE': 'Georgia',
- 'DE': 'Germany',
- 'GH': 'Ghana',
- 'GI': 'Gibraltar',
- 'GR': 'Greece',
- 'GL': 'Greenland',
- 'GD': 'Grenada',
- 'GP': 'Guadeloupe',
- 'GU': 'Guam',
- 'GT': 'Guatemala',
- 'GG': 'Guernsey',
- 'GN': 'Guinea',
- 'GW': 'Guinea-Bissau',
- 'GY': 'Guyana',
- 'HT': 'Haiti',
- 'HM': 'Heard Island & Mcdonald Islands',
- 'VA': 'Holy See (Vatican City State)',
- 'HN': 'Honduras',
- 'HK': 'Hong Kong',
- 'HU': 'Hungary',
- 'IS': 'Iceland',
- 'IN': 'India',
- 'ID': 'Indonesia',
- 'IR': 'Iran, Islamic Republic Of',
- 'IQ': 'Iraq',
- 'IE': 'Ireland',
- 'IM': 'Isle Of Man',
- 'IL': 'Israel',
- 'IT': 'Italy',
- 'JM': 'Jamaica',
- 'JP': 'Japan',
- 'JE': 'Jersey',
- 'JO': 'Jordan',
- 'KZ': 'Kazakhstan',
- 'KE': 'Kenya',
- 'KI': 'Kiribati',
- 'KR': 'Korea',
- 'KW': 'Kuwait',
- 'KG': 'Kyrgyzstan',
- 'LA': 'Lao People\'s Democratic Republic',
- 'LV': 'Latvia',
- 'LB': 'Lebanon',
- 'LS': 'Lesotho',
- 'LR': 'Liberia',
- 'LY': 'Libyan Arab Jamahiriya',
- 'LI': 'Liechtenstein',
- 'LT': 'Lithuania',
- 'LU': 'Luxembourg',
- 'MO': 'Macao',
- 'MK': 'Macedonia',
- 'MG': 'Madagascar',
- 'MW': 'Malawi',
- 'MY': 'Malaysia',
- 'MV': 'Maldives',
- 'ML': 'Mali',
- 'MT': 'Malta',
- 'MH': 'Marshall Islands',
- 'MQ': 'Martinique',
- 'MR': 'Mauritania',
- 'MU': 'Mauritius',
- 'YT': 'Mayotte',
- 'MX': 'Mexico',
- 'FM': 'Micronesia, Federated States Of',
- 'MD': 'Moldova',
- 'MC': 'Monaco',
- 'MN': 'Mongolia',
- 'ME': 'Montenegro',
- 'MS': 'Montserrat',
- 'MA': 'Morocco',
- 'MZ': 'Mozambique',
- 'MM': 'Myanmar',
- 'NA': 'Namibia',
- 'NR': 'Nauru',
- 'NP': 'Nepal',
- 'NL': 'Netherlands',
- 'AN': 'Netherlands Antilles',
- 'NC': 'New Caledonia',
- 'NZ': 'New Zealand',
- 'NI': 'Nicaragua',
- 'NE': 'Niger',
- 'NG': 'Nigeria',
- 'NU': 'Niue',
- 'NF': 'Norfolk Island',
- 'MP': 'Northern Mariana Islands',
- 'NO': 'Norway',
- 'OM': 'Oman',
- 'PK': 'Pakistan',
- 'PW': 'Palau',
- 'PS': 'Palestinian Territory, Occupied',
- 'PA': 'Panama',
- 'PG': 'Papua New Guinea',
- 'PY': 'Paraguay',
- 'PE': 'Peru',
- 'PH': 'Philippines',
- 'PN': 'Pitcairn',
- 'PL': 'Poland',
- 'PT': 'Portugal',
- 'PR': 'Puerto Rico',
- 'QA': 'Qatar',
- 'RE': 'Reunion',
- 'RO': 'Romania',
- 'RU': 'Russian Federation',
- 'RW': 'Rwanda',
- 'BL': 'Saint Barthelemy',
- 'SH': 'Saint Helena',
- 'KN': 'Saint Kitts And Nevis',
- 'LC': 'Saint Lucia',
- 'MF': 'Saint Martin',
- 'PM': 'Saint Pierre And Miquelon',
- 'VC': 'Saint Vincent And Grenadines',
- 'WS': 'Samoa',
- 'SM': 'San Marino',
- 'ST': 'Sao Tome And Principe',
- 'SA': 'Saudi Arabia',
- 'SN': 'Senegal',
- 'RS': 'Serbia',
- 'SC': 'Seychelles',
- 'SL': 'Sierra Leone',
- 'SG': 'Singapore',
- 'SK': 'Slovakia',
- 'SI': 'Slovenia',
- 'SB': 'Solomon Islands',
- 'SO': 'Somalia',
- 'ZA': 'South Africa',
- 'GS': 'South Georgia And Sandwich Isl.',
- 'ES': 'Spain',
- 'LK': 'Sri Lanka',
- 'SD': 'Sudan',
- 'SR': 'Suriname',
- 'SJ': 'Svalbard And Jan Mayen',
- 'SZ': 'Swaziland',
- 'SE': 'Sweden',
- 'CH': 'Switzerland',
- 'SY': 'Syrian Arab Republic',
- 'TW': 'Taiwan',
- 'TJ': 'Tajikistan',
- 'TZ': 'Tanzania',
- 'TH': 'Thailand',
- 'TL': 'Timor-Leste',
- 'TG': 'Togo',
- 'TK': 'Tokelau',
- 'TO': 'Tonga',
- 'TT': 'Trinidad And Tobago',
- 'TN': 'Tunisia',
- 'TR': 'Turkey',
- 'TM': 'Turkmenistan',
- 'TC': 'Turks And Caicos Islands',
- 'TV': 'Tuvalu',
- 'UG': 'Uganda',
- 'UA': 'Ukraine',
- 'AE': 'United Arab Emirates',
- 'GB': 'United Kingdom',
- 'US': 'United States',
- 'UM': 'United States Outlying Islands',
- 'UY': 'Uruguay',
- 'UZ': 'Uzbekistan',
- 'VU': 'Vanuatu',
- 'VE': 'Venezuela',
- 'VN': 'Viet Nam',
- 'VG': 'Virgin Islands, British',
- 'VI': 'Virgin Islands, U.S.',
- 'WF': 'Wallis And Futuna',
- 'EH': 'Western Sahara',
- 'YE': 'Yemen',
- 'ZM': 'Zambia',
- 'ZW': 'Zimbabwe'
- });
-})();
+ vm.user = userData;
+ copyUserToForm(vm.formUser, vm.user);
+ vm.searchText = vm.formUser.country;
-(function() {
- 'use strict';
+ vm.updateUser = updateUser;
+ vm.removeUser = removeUser;
+ vm.uploadAvatar = uploadAvatar;
- angular.module('app.components')
- .factory('user', user);
+ //THIS IS TEMPORARY.
+ // Will grow on to a dynamic API KEY management
+ // with the new /accounts oAuth mgmt methods
- user.$inject = ['Restangular'];
- function user(Restangular) {
- var service = {
- createUser: createUser,
- getUser: getUser,
- updateUser: updateUser
- };
- return service;
+ // The auth controller has not populated the `user` at this point,
+ // so user.token is undefined
+ // This controller depends on auth has already been run.
+ vm.user.token = auth.getToken();
+ vm.addNewDevice = addNewDevice;
- ////////////////////
+ //KITS TAB
+ vm.devices = [];
+ vm.deviceStatus = undefined;
+ vm.removeDevice = removeDevice;
+ vm.downloadData = downloadData;
- function createUser(signupData) {
- return Restangular.all('users').post(signupData);
- }
+ vm.filteredDevices = [];
+ vm.dropdownSelected = undefined;
- function getUser(id) {
- return Restangular.one('users', id).get();
- }
+ //SIDEBAR
+ vm.filterDevices = filterDevices;
+ vm.filterTools = filterTools;
- function updateUser(updateData) {
- return Restangular.all('me').customPUT(updateData);
- }
- }
-})();
+ vm.selectThisTab = selectThisTab;
-(function() {
- 'use strict';
+ $scope.$on('loggedOut', function() {
+ $location.path('/');
+ });
- angular.module('app.components')
- .factory('tag', tag);
+ $scope.$on('devicesContextUpdated', function(){
+ var userData = auth.getCurrentUser().data;
+ if(userData){
+ vm.user = userData;
+ }
+ initialize();
+ });
- tag.$inject = ['Restangular'];
- function tag(Restangular) {
- var tags = [];
- var selectedTags = [];
+ initialize();
- var service = {
- getTags: getTags,
- getSelectedTags: getSelectedTags,
- setSelectedTags: setSelectedTags,
- tagWithName: tagWithName,
- filterMarkersByTag: filterMarkersByTag
- };
+ //////////////////
- return service;
+ function initialize() {
- /////////////////
+ startingTab();
+ if(!vm.user.devices.length) {
+ vm.devices = [];
+ animation.viewLoaded();
+ } else {
- function getTags() {
- return Restangular.all('tags')
- .getList({'per_page': 200})
- .then(function(fetchedTags){
- tags = fetchedTags.plain();
- return tags;
+ vm.devices = vm.user.devices.map(function(data) {
+ return new PreviewDevice(data);
+ })
+
+ $timeout(function() {
+ mapWithBelongstoUser(vm.devices);
+ filterDevices(vm.status);
+ setSidebarMinHeight();
+ animation.viewLoaded();
});
- }
- function getSelectedTags(){
- return selectedTags;
- }
-
- function setSelectedTags(tags){
- selectedTags = tags;
+ }
}
- function tagWithName(name){
- var result = _.where(tags, {name: name});
- if (result && result.length > 0){
- return result[0];
- }else{
- return;
+ function filterDevices(status) {
+ if(status === 'all') {
+ status = undefined;
}
+ vm.deviceStatus = status;
+ vm.filteredDevices = $filter('filterLabel')(vm.devices, vm.deviceStatus);
}
- function filterMarkersByTag(tmpMarkers) {
- var markers = filterMarkers(tmpMarkers);
- return markers;
+ function filterTools(type) {
+ if(type === 'all') {
+ type = undefined;
+ }
+ vm.toolType = type;
}
- function filterMarkers(tmpMarkers) {
- if (service.getSelectedTags().length === 0){
- return tmpMarkers;
+ function updateUser(userData) {
+ if(userData.country) {
+ _.each(COUNTRY_CODES, function(value, key) {
+ if(value === userData.country) {
+ /*jshint camelcase: false */
+ userData.country_code = key;
+ return;
+ }
+ });
+ } else {
+ userData.country_code = null;
}
- return tmpMarkers.filter(function(marker) {
- var tags = marker.myData.tags;
- if (tags.length === 0){
- return false;
- }
- return _.some(tags, function(tag) {
- return _.includes(service.getSelectedTags(), tag);
+
+ user.updateUser(userData)
+ .then(function(data) {
+ var user = new AuthUser(data);
+ _.extend(vm.user, user);
+ auth.updateUser();
+ vm.errors = {};
+ alert.success('User updated');
+ })
+ .catch(function(err) {
+ alert.error('User could not be updated ');
+ vm.errors = err.data.errors;
});
- });
}
- }
-})();
-(function() {
- 'use strict';
+ function removeUser() {
+ var confirm = $mdDialog.confirm()
+ .title('Delete your account?')
+ .textContent('Are you sure you want to delete your account?')
+ .ariaLabel('')
+ .ok('delete')
+ .cancel('cancel')
+ .theme('primary')
+ .clickOutsideToClose(true);
- angular.module('app.components')
- .factory('sensor', sensor);
+ $mdDialog.show(confirm)
+ .then(function(){
+ return Restangular.all('').customDELETE('me')
+ .then(function(){
+ alert.success('Account removed successfully. Redirecting you…');
+ $timeout(function(){
+ auth.logout();
+ $state.transitionTo('landing');
+ }, 2000);
+ })
+ .catch(function(){
+ alert.error('Error occurred trying to delete your account.');
+ });
+ });
+ }
- sensor.$inject = ['Restangular', 'timeUtils', 'sensorUtils'];
- function sensor(Restangular, timeUtils, sensorUtils) {
- var sensorTypes;
- callAPI().then(function(data) {
- setTypes(data);
- });
+ function selectThisTab(iconIndex, uistate){
+ /* This looks more like a hack but we need to workout how to properly use md-tab with ui-router */
- var service = {
- callAPI: callAPI,
- setTypes: setTypes,
- getTypes: getTypes,
- getSensorsData: getSensorsData
- };
- return service;
+ highlightIcon(iconIndex);
- ////////////////
+ if ($state.current.name.includes('myProfileAdmin')){
+ var transitionState = 'layout.myProfileAdmin.' + uistate;
+ $state.transitionTo(transitionState, {id: userData.id});
+ } else {
+ var transitionState = 'layout.myProfile.' + uistate;
+ $state.transitionTo(transitionState);
+ }
- function callAPI() {
- return Restangular.all('sensors').getList({'per_page': 1000});
}
- function setTypes(sensorTypes) {
- sensorTypes = sensorTypes;
- }
+ function startingTab() {
+ /* This looks more like a hack but we need to workout how to properly use md-tab with ui-router */
- function getTypes() {
- return sensorTypes;
- }
+ var childState = $state.current.name.split('.').pop();
- function getSensorsData(deviceID, sensorID, dateFrom, dateTo) {
- var rollup = sensorUtils.getRollup(dateFrom, dateTo);
- dateFrom = timeUtils.convertTime(dateFrom);
- dateTo = timeUtils.convertTime(dateTo);
+ switch(childState) {
+ case 'user':
+ vm.startingTab = 1;
+ break;
+ default:
+ vm.startingTab = 0;
+ break;
+ }
- return Restangular.one('devices', deviceID).customGET('readings', {'from': dateFrom, 'to': dateTo, 'rollup': rollup, 'sensor_id': sensorID, 'all_intervals': true});
}
- }
-})();
-(function() {
- 'use strict';
+ function highlightIcon(iconIndex) {
- angular.module('app.components')
- .factory('search', search);
-
- search.$inject = ['$http', 'Restangular'];
- function search($http, Restangular) {
- var service = {
- globalSearch: globalSearch
- };
+ var icons = angular.element('.myProfile_tab_icon');
- return service;
+ _.each(icons, function(icon) {
+ unhighlightIcon(icon);
+ });
- /////////////////////////
+ var icon = icons[iconIndex];
- function globalSearch(query) {
- return Restangular.all('search').getList({q: query});
+ angular.element(icon).find('.stroke_container').css({'stroke': 'white', 'stroke-width': '0.01px'});
+ angular.element(icon).find('.fill_container').css('fill', 'white');
}
- }
-})();
-
-(function() {
- 'use strict';
-
- angular.module('app.components')
- .factory('measurement', measurement);
- measurement.$inject = ['Restangular'];
+ function unhighlightIcon(icon) {
+ icon = angular.element(icon);
- function measurement(Restangular) {
+ icon.find('.stroke_container').css({'stroke': 'none'});
+ icon.find('.fill_container').css('fill', '#FF8600');
+ }
- var service = {
- getTypes: getTypes,
- getMeasurement: getMeasurement
+ function setSidebarMinHeight() {
+ var height = document.body.clientHeight / 4 * 3;
+ angular.element('.profile_content').css('min-height', height + 'px');
+ }
- };
- return service;
+ function getCountries(searchText) {
+ return _.filter(COUNTRY_CODES, createFilter(searchText));
+ }
- ////////////////
+ function createFilter(searchText) {
+ searchText = searchText.toLowerCase();
+ return function(country) {
+ country = country.toLowerCase();
+ return country.indexOf(searchText) !== -1;
+ };
+ }
+ function uploadAvatar(fileData) {
+ if(fileData && fileData.length) {
- function getTypes() {
- return Restangular.all('measurements').getList({'per_page': 1000});
- }
+ // TODO: Improvement Is there a simpler way to patch the image to the API and use the response?
+ // Something like:
+ //Restangular.all('/me').patch(data);
+ // Instead of doing it manually like here:
+ var fd = new FormData();
+ fd.append('profile_picture', fileData[0]);
+ Restangular.one('/me')
+ .withHttpConfig({transformRequest: angular.identity})
+ .customPATCH(fd, '', undefined, {'Content-Type': undefined})
+ .then(function(resp){
+ vm.user.profile_picture = resp.profile_picture;
+ })
+ }
+ }
- function getMeasurement(mesID) {
+ function copyUserToForm(formData, userData) {
+ var props = {username: true, email: true, city: true, country: true, country_code: true, url: true, constructor: false};
- return Restangular.one('measurements', mesID).get();
- }
- }
-})();
-(function() {
- 'use strict';
+ for(var key in userData) {
+ if(props[key]) {
+ formData[key] = userData[key];
+ }
+ }
+ }
- angular.module('app.components')
- .factory('geolocation', geolocation);
+ function mapWithBelongstoUser(devices){
+ _.map(devices, addBelongProperty);
+ }
- geolocation.$inject = ['$http', '$window'];
- function geolocation($http, $window) {
+ function addBelongProperty(device){
+ device.belongProperty = deviceBelongsToUser(device);
+ return device;
+ }
- var service = {
- grantHTML5Geolocation: grantHTML5Geolocation,
- isHTML5GeolocationGranted: isHTML5GeolocationGranted
- };
- return service;
- ///////////////////////////
+ function deviceBelongsToUser(device){
+ if(!auth.isAuth() || !device || !device.id) {
+ return false;
+ }
+ var deviceID = parseInt(device.id);
+ var userData = ( auth.getCurrentUser().data ) ||
+ ($window.localStorage.getItem('smartcitizen.data') &&
+ new AuthUser( JSON.parse(
+ $window.localStorage.getItem('smartcitizen.data') )));
+ var belongsToUser = deviceUtils.belongsToUser(userData.devices, deviceID);
+ var isAdmin = userUtils.isAdmin(userData);
- function grantHTML5Geolocation(){
- $window.localStorage.setItem('smartcitizen.geolocation_granted', true);
- }
+ return isAdmin || belongsToUser;
+ }
- function isHTML5GeolocationGranted(){
- return $window.localStorage
- .getItem('smartcitizen.geolocation_granted');
- }
- }
-})();
-
-(function() {
- 'use strict';
+ function downloadData(device){
+ $mdDialog.show({
+ hasBackdrop: true,
+ controller: 'DownloadModalController',
+ controllerAs: 'vm',
+ templateUrl: 'app/components/download/downloadModal.html',
+ clickOutsideToClose: true,
+ locals: {thisDevice:device}
+ }).then(function(){
+ var alert = $mdDialog.alert()
+ .title('SUCCESS')
+ .textContent('We are processing your data. Soon you will be notified in your inbox')
+ .ariaLabel('')
+ .ok('OK!')
+ .theme('primary')
+ .clickOutsideToClose(true);
- angular.module('app.components')
- .factory('file', file);
+ $mdDialog.show(alert);
+ }).catch(function(err){
+ if (!err){
+ return;
+ }
+ var errorAlert = $mdDialog.alert()
+ .title('ERROR')
+ .textContent('Uh-oh, something went wrong')
+ .ariaLabel('')
+ .ok('D\'oh')
+ .theme('primary')
+ .clickOutsideToClose(false);
- file.$inject = ['Restangular', 'Upload'];
- function file(Restangular, Upload) {
- var service = {
- getCredentials: getCredentials,
- uploadFile: uploadFile,
- getImageURL: getImageURL
- };
- return service;
+ $mdDialog.show(errorAlert);
+ });
+ }
- ///////////////
+ function removeDevice(deviceID) {
+ var confirm = $mdDialog.confirm()
+ .title('Delete this kit?')
+ .textContent('Are you sure you want to delete this kit?')
+ .ariaLabel('')
+ .ok('DELETE')
+ .cancel('Cancel')
+ .theme('primary')
+ .clickOutsideToClose(true);
- function getCredentials(filename) {
- var data = {
- filename: filename
- };
- return Restangular.all('me/avatar').post(data);
+ $mdDialog
+ .show(confirm)
+ .then(function(){
+ device
+ .removeDevice(deviceID)
+ .then(function(){
+ alert.success('Your kit was deleted successfully');
+ device.updateContext();
+ })
+ .catch(function(){
+ alert.error('Error trying to delete your kit.');
+ });
+ });
}
- function uploadFile(fileData, key, policy, signature) {
- return Upload.upload({
- url: 'https://smartcitizen.s3-eu-west-1.amazonaws.com',
- method: 'POST',
- data: {
- key: key,
- policy: policy,
- signature: signature,
- AWSAccessKeyId: 'AKIAJ753OQI6JPSDCPHA',
- acl: 'public-read',
- "Content-Type": fileData.type || 'application/octet-stream',
- /*jshint camelcase: false */
- success_action_status: 200,
- file: fileData
- }
+ $scope.addDeviceSelector = addDeviceSelector;
+ function addDeviceSelector(){
+ $mdDialog.show({
+ templateUrl: 'app/components/myProfile/addDeviceSelectorModal.html',
+ clickOutsideToClose: true,
+ multiple: true,
+ controller: DialogController,
});
}
- function getImageURL(filename, size) {
- size = size === undefined ? 's101' : size;
+ function DialogController($scope, $mdDialog){
+ $scope.cancel = function(){
+ $mdDialog.cancel();
+ };
+ }
- return 'https://images.smartcitizen.me/' + size + '/' + filename;
+ function addNewDevice() {
+ var confirm = $mdDialog.confirm()
+ .title('Hey! Do you want to add a new kit?')
+ .textContent('Please, notice this currently supports just the SCK 1.0 and SCK 1.1')
+ .ariaLabel('')
+ .ok('Ok')
+ .cancel('Cancel')
+ .theme('primary')
+ .clickOutsideToClose(true);
+
+ $mdDialog
+ .show(confirm)
+ .then(function(){
+ $state.go('layout.kitAdd');
+ });
}
+
+
}
})();
(function() {
- 'use strict';
+ 'use strict';
- angular.module('app.components')
- .factory('device', device);
+ angular.module('app.components')
+ .controller('MapTagModalController', MapTagModalController);
- device.$inject = ['Restangular', '$window', 'timeUtils','$http', 'auth', '$rootScope'];
- function device(Restangular, $window, timeUtils, $http, auth, $rootScope) {
- var worldMarkers;
+ MapTagModalController.$inject = ['$mdDialog', 'tag', 'selectedTags'];
- initialize();
+ function MapTagModalController($mdDialog, tag, selectedTags) {
- var service = {
- getDevices: getDevices,
- getAllDevices: getAllDevices,
- getDevice: getDevice,
- createDevice: createDevice,
- updateDevice: updateDevice,
- getWorldMarkers: getWorldMarkers,
- setWorldMarkers: setWorldMarkers,
- mailReadings: mailReadings,
- postReadings: postReadings,
- removeDevice: removeDevice,
- updateContext: updateContext
- };
+ var vm = this;
- return service;
+ vm.checks = {};
- //////////////////////////
+ vm.answer = answer;
+ vm.hide = hide;
+ vm.clear = clear;
+ vm.cancel = cancel;
+ vm.tags = [];
- function initialize() {
- if(areMarkersOld()) {
- removeMarkers();
- }
- }
+ init();
- function getDevices(location) {
- var parameter = '';
- parameter += location.lat + ',' + location.lng;
- return Restangular.all('devices').getList({near: parameter, 'per_page': '100'});
- }
+ ////////////////////////////////////////////////////////
- function getAllDevices(forceReload) {
- if (forceReload || auth.isAuth()) {
- return getAllDevicesNoCached();
- } else {
- return getAllDevicesCached();
- }
- }
+ function init() {
+ tag.getTags()
+ .then(function(tags) {
+ vm.tags = tags;
- function getAllDevicesCached() {
- return Restangular.all('devices/world_map')
- .getList()
- .then(function(fetchedDevices){
- return fetchedDevices.plain();
- });
- }
+ _.forEach(selectedTags, select);
- function getAllDevicesNoCached() {
- return Restangular.all('devices/fresh_world_map')
- .getList()
- .then(function(fetchedDevices){
- return fetchedDevices.plain();
});
- }
+ }
- function getDevice(id) {
- return Restangular.one('devices', id).get();
- }
+ function answer() {
- function createDevice(data) {
- return Restangular.all('devices').post(data);
- }
+ var selectedTags = _(vm.tags)
+ .filter(isTagSelected)
+ .value();
+ $mdDialog.hide(selectedTags);
+ }
- function updateDevice(id, data) {
- return Restangular.one('devices', id).patch(data);
- }
+ function hide() {
+ answer();
+ }
- function getWorldMarkers() {
- return worldMarkers || ($window.localStorage.getItem('smartcitizen.markers') && JSON.parse($window.localStorage.getItem('smartcitizen.markers') ).data);
- }
+ function clear() {
+ $mdDialog.hide(null);
+ }
- function setWorldMarkers(data) {
- var obj = {
- timestamp: new Date(),
- data: data
- };
- try {
- $window.localStorage.setItem('smartcitizen.markers', JSON.stringify(obj) );
- } catch (e) {
- console.log("Could not store markers in localstorage. skipping...");
- }
- worldMarkers = obj.data;
- }
+ function cancel() {
+ answer();
+ }
- function getTimeStamp() {
- return ($window.localStorage.getItem('smartcitizen.markers') &&
- JSON.parse($window.localStorage
- .getItem('smartcitizen.markers') ).timestamp);
- }
+ function isTagSelected(tag) {
+ return vm.checks[tag.name];
+ }
- function areMarkersOld() {
- var markersDate = getTimeStamp();
- return !timeUtils.isWithin(1, 'minutes', markersDate);
- }
-
- function removeMarkers() {
- worldMarkers = null;
- $window.localStorage.removeItem('smartcitizen.markers');
- }
-
- function mailReadings(kit) {
- return Restangular
- .one('devices', kit.id)
- .customGET('readings/csv_archive');
- }
-
- function postReadings(kit, readings) {
- return Restangular
- .one('devices', kit.id)
- .post('readings', readings);
- }
-
- function removeDevice(deviceID){
- return Restangular
- .one('devices', deviceID)
- .remove().then(function () {
- $rootScope.$broadcast('devicesContextUpdated');
- })
- ;
- }
-
- function updateContext (){
- return auth.updateUser().then(function(){
- removeMarkers();
- $rootScope.$broadcast('devicesContextUpdated');
- });
- }
-
- }
-})();
+ function select(tag){
+ vm.checks[tag] = true;
+ }
+ }
+})();
(function() {
'use strict';
angular.module('app.components')
- .factory('auth', auth);
-
- auth.$inject = ['$location', '$window', '$state', 'Restangular',
- '$rootScope', 'AuthUser', '$timeout', 'alert', '$cookies'];
- function auth($location, $window, $state, Restangular, $rootScope, AuthUser,
- $timeout, alert, $cookies) {
-
- var user = {};
+ .controller('MapFilterModalController', MapFilterModalController);
- //wait until http interceptor is added to Restangular
- $timeout(function() {
- initialize();
- }, 100);
+ MapFilterModalController.$inject = ['$mdDialog','selectedFilters', '$timeout'];
- var service = {
- isAuth: isAuth,
- setCurrentUser: setCurrentUser,
- getCurrentUser: getCurrentUser,
- updateUser: updateUser,
- saveToken: saveToken,
- getToken: getToken,
- login: login,
- logout: logout,
- recoverPassword: recoverPassword,
- getResetPassword: getResetPassword,
- patchResetPassword: patchResetPassword,
- isAdmin: isAdmin
- };
- return service;
+ function MapFilterModalController($mdDialog, selectedFilters, $timeout) {
- //////////////////////////
+ var vm = this;
- function initialize() {
- //console.log('---- AUTH INIT -----');
- setCurrentUser('appLoad');
- }
+ vm.checks = {};
- //run on app initialization so that we can keep auth across different sessions
- // 1. Check if token in cookie exists. Return if it doesn't, user needs to login (and save a token to the cookie)
- // 2. Populate user.data with the response from the API.
- // 3. Broadcast logged in
- function setCurrentUser(time) {
- // TODO later: Should we check if token is expired here?
- if (getToken()) {
- user.token = getToken();
- }else{
- //console.log('token not found in cookie, returning');
- return;
- }
+ vm.answer = answer;
+ vm.hide = hide;
+ vm.clear = clear;
+ vm.cancel = cancel;
+ vm.toggle = toggle;
- return getCurrentUserFromAPI()
- .then(function(data) {
- // Save user.data also in localStorage. It is beeing used across the app.
- // Should it instead just be saved in the user object? Or is it OK to also have it in localStorage?
- $window.localStorage.setItem('smartcitizen.data', JSON.stringify(data.plain()) );
+ vm.location = ['indoor', 'outdoor'];
+ vm.status = ['online', 'offline'];
+ vm.new = ['new'];
- var newUser = new AuthUser(data);
- //check sensitive information
- if(user.data && user.data.role !== newUser.role) {
- user.data = newUser;
- $location.path('/');
- }
- user.data = newUser;
+ vm.filters = [];
- //console.log('-- User populated with data: ', user)
- // Broadcast happens 2x, so the user wont think he is not logged in.
- // The 2nd broadcast waits 3sec, because f.x. on the /kits/ page, the layout has not loaded when the broadcast is sent
- $rootScope.$broadcast('loggedIn');
+ init();
- // used for app initialization
- if(time && time === 'appLoad') {
- //wait until navbar is loaded to emit event
- $timeout(function() {
- $rootScope.$broadcast('loggedIn', {time: 'appLoad'});
- }, 3000);
- } else {
- // used for login
- //$state.reload();
- $timeout(function() {
- alert.success('Login was successful');
- $rootScope.$broadcast('loggedIn', {});
- }, 2000);
- }
- });
- }
+ ////////////////////////////////////////////////////////
- // Called from device.service.js updateContext(), which is called from multiple /kit/ pages
- function updateUser() {
- return getCurrentUserFromAPI()
- .then(function(data) {
- // TODO: Should this update the token or user.data? Then it could instead call setCurrentUser?
- $window.localStorage.setItem('smartcitizen.data', JSON.stringify(data.plain()) );
- return getCurrentUser();
- });
- }
+ function init() {
+ _.forEach(selectedFilters, select);
+ }
- function getCurrentUser() {
- user.token = getToken();
- user.data = $window.localStorage.getItem('smartcitizen.data') && new AuthUser(JSON.parse( $window.localStorage.getItem('smartcitizen.data') ));
- return user;
- }
+ function answer() {
+ vm.filters = vm.filters.concat(vm.location, vm.status, vm.new);
+ var selectedFilters = _(vm.filters)
+ .filter(isFilterSelected)
+ .value();
+ $mdDialog.hide(selectedFilters);
+ }
- // Should check if user.token exists - but now checks if the cookies.token exists.
- function isAuth() {
- // TODO: isAuth() is called from many different services BEFORE auth.init has run.
- // That means that the user.token is EMPTY, meaning isAuth will be false
- // We can cheat and just check the cookie, but we should NOT. Because auth.init should also check if the cookie is valid / expired
- // Ideally it should return !!user.token
- //return !!user.token;
- return !!getToken();
- }
+ function hide() {
+ answer();
+ }
- // LoginModal calls this after it receives the token from the API, and wants to save it in a cookie.
- function saveToken(token) {
- //console.log('saving Token to cookie:', token);
- $cookies.put('smartcitizen.token', token);
- setCurrentUser();
- }
+ function clear() {
+ vm.filters = vm.filters.concat(vm.location, vm.status, vm.new);
+ $mdDialog.hide(vm.filters);
+ }
- function getToken(){
- return $cookies.get('smartcitizen.token');
- }
+ function cancel() {
+ answer();
+ }
- function login(loginData) {
- return Restangular.all('sessions').post(loginData);
- }
+ function isFilterSelected(filter) {
+ return vm.checks[filter];
+ }
- function logout() {
- $cookies.remove('smartcitizen.token');
- }
+ function toggle(filters) {
+ $timeout(function() {
- function getCurrentUserFromAPI() {
- return Restangular.all('').customGET('me');
- }
+ for (var i = 0; i < filters.length - 1; i++) {
+ if (vm.checks[filters[i]] === false && vm.checks[filters[i]] === vm.checks[filters[i+1]]) {
+ for (var n = 0; n < filters.length; n++) {
+ vm.checks[filters[n]] = true;
+ }
+ }
+ }
- function recoverPassword(data) {
- return Restangular.all('password_resets').post(data);
- }
+ });
+ }
- function getResetPassword(code) {
- return Restangular.one('password_resets', code).get();
- }
- function patchResetPassword(code, data) {
- return Restangular.one('password_resets', code).patch(data);
- }
- function isAdmin(userData) {
- return userData.role === 'admin';
- }
+ function select(filter){
+ vm.checks[filter] = true;
}
+ }
})();
(function() {
'use strict';
- /**
- * Unused directive. Double-check before removing.
- *
- */
angular.module('app.components')
- .directive('slide', slide)
- .directive('slideMenu', slideMenu);
+ .controller('MapController', MapController);
- function slideMenu() {
- return {
- controller: controller,
- link: link
- };
+ MapController.$inject = ['$scope', '$state', '$stateParams', '$timeout', 'device',
+ '$mdDialog', 'leafletData', 'alert',
+ 'Marker', 'tag', 'animation', '$q'];
+ function MapController($scope, $state, $stateParams, $timeout, device,
+ $mdDialog, leafletData, alert, Marker, tag, animation, $q) {
+ var vm = this;
+ var updateType;
+ var focusedMarkerID;
- function link(scope, element) {
- scope.element = element;
- }
+ vm.markers = [];
- function controller($scope) {
- $scope.slidePosition = 0;
- $scope.slideSize = 20;
+ var retinaSuffix = isRetina() ? '512' : '256';
+ var retinaLegacySuffix = isRetina() ? '@2x' : '';
- this.getTimesSlided = function() {
- return $scope.slideSize;
- };
- this.getPosition = function() {
- return $scope.slidePosition * $scope.slideSize;
- };
- this.decrementPosition = function() {
- $scope.slidePosition -= 1;
- };
- this.incrementPosition = function() {
- $scope.slidePosition += 1;
- };
- this.scrollIsValid = function(direction) {
- var scrollPosition = $scope.element.scrollLeft();
- console.log('scrollpos', scrollPosition);
- if(direction === 'left') {
- return scrollPosition > 0 && $scope.slidePosition >= 0;
- } else if(direction === 'right') {
- return scrollPosition < 300;
- }
- };
- }
- }
+ var mapBoxToken = 'pk.eyJ1IjoidG9tYXNkaWV6IiwiYSI6ImRTd01HSGsifQ.loQdtLNQ8GJkJl2LUzzxVg';
- slide.$inject = [];
- function slide() {
- return {
- link: link,
- require: '^slide-menu',
- restrict: 'A',
- scope: {
- direction: '@'
+ vm.layers = {
+ baselayers: {
+ osm: {
+ name: 'OpenStreetMap',
+ type: 'xyz',
+ url: 'https://api.mapbox.com/styles/v1/mapbox/streets-v10/tiles/' + retinaSuffix + '/{z}/{x}/{y}?access_token=' + mapBoxToken
+ },
+ legacy: {
+ name: 'Legacy',
+ type: 'xyz',
+ url: 'https://api.tiles.mapbox.com/v4/mapbox.streets-basic/{z}/{x}/{y}'+ retinaLegacySuffix +'.png' + '?access_token=' + mapBoxToken
+ },
+ sat: {
+ name: 'Satellite',
+ type: 'xyz',
+ url: 'https://api.mapbox.com/styles/v1/mapbox/satellite-streets-v10/tiles/' + retinaSuffix + '/{z}/{x}/{y}?access_token=' + mapBoxToken
+ }
+ },
+ overlays: {
+ devices: {
+ name: 'Devices',
+ type: 'markercluster',
+ visible: true,
+ layerOptions: {
+ showCoverageOnHover: false
+ }
+ }
}
};
- function link(scope, element, attr, slideMenuCtrl) {
- //select first sensor container
- var sensorsContainer = angular.element('.sensors_container');
-
- element.on('click', function() {
-
- if(slideMenuCtrl.scrollIsValid('left') && attr.direction === 'left') {
- slideMenuCtrl.decrementPosition();
- sensorsContainer.scrollLeft(slideMenuCtrl.getPosition());
- console.log(slideMenuCtrl.getPosition());
- } else if(slideMenuCtrl.scrollIsValid('right') && attr.direction === 'right') {
- slideMenuCtrl.incrementPosition();
- sensorsContainer.scrollLeft(slideMenuCtrl.getPosition());
- console.log(slideMenuCtrl.getPosition());
- }
- });
- }
- }
-})();
-
-(function() {
- 'use strict';
+ vm.center = {
+ lat: $stateParams.lat ? parseInt($stateParams.lat, 10) : 13.14950321154457,
+ lng: $stateParams.lng ? parseInt($stateParams.lng, 10) : -1.58203125,
+ zoom: $stateParams.zoom ? parseInt($stateParams.zoom, 10) : 2
+ };
- angular.module('app.components')
- .directive('showPopupInfo', showPopupInfo);
+ vm.defaults = {
+ dragging: true,
+ touchZoom: true,
+ scrollWheelZoom: true,
+ doubleClickZoom: true,
+ minZoom:2,
+ worldCopyJump: true
+ };
- /**
- * Used to show/hide explanation of sensor value at kit dashboard
- *
- */
- showPopupInfo.$inject = [];
- function showPopupInfo() {
- return {
- link: link
+ vm.events = {
+ map: {
+ enable: ['dragend', 'zoomend', 'moveend', 'popupopen', 'popupclose',
+ 'mousedown', 'dblclick', 'click', 'touchstart', 'mouseup'],
+ logic: 'broadcast'
+ }
};
- //////
+ $scope.$on('leafletDirectiveMarker.click', function(event, data) {
+ var id = undefined;
+ var currentMarker = vm.markers[data.modelName];
+ if(currentMarker) {
+ id = currentMarker.myData.id;
+ }
- function link(scope, elem) {
- elem.on('mouseenter', function() {
- angular.element('.sensor_data_description').css('display', 'inline-block');
- });
- elem.on('mouseleave', function() {
- angular.element('.sensor_data_description').css('display', 'none');
- });
- }
- }
-})();
+ vm.deviceLoading = true;
+ vm.center.lat = data.leafletEvent.latlng.lat;
+ vm.center.lng = data.leafletEvent.latlng.lng;
-(function() {
- 'use strict';
+ if(id === parseInt($state.params.id)) {
+ $timeout(function() {
+ vm.deviceLoading = false;
+ });
+ return;
+ }
- angular.module('app.components')
- .directive('showPopup', showPopup);
+ updateType = 'map';
- /**
- * Used on kit dashboard to open full sensor description
- */
+ if ($state.$current.name === 'embbed') { return; }
+ $state.go('layout.home.kit', {id: id});
- showPopup.$inject = [];
- function showPopup() {
- return {
- link: link
- };
+ // angular.element('section.map').scope().$broadcast('resizeMapHeight');
+ });
- /////
- function link(scope, element) {
- element.on('click', function() {
- var text = angular.element('.sensor_description_preview').text();
- if(text.length < 140) {
- return;
+ $scope.$on('leafletDirectiveMarker.popupclose', function() {
+ if(focusedMarkerID) {
+ var marker = vm.markers[focusedMarkerID];
+ if(marker) {
+ vm.markers[focusedMarkerID].focus = false;
}
- angular.element('.sensor_description_preview').hide();
- angular.element('.sensor_description_full').show();
- });
- }
- }
-})();
+ }
+ });
-(function() {
- 'use strict';
+ vm.readyForDevice = {
+ device: false,
+ map: false
+ };
- angular.module('app.components')
- .directive('moveFilters', moveFilters);
+ $scope.$on('deviceLoaded', function(event, data) {
+ vm.readyForDevice.device = data;
+ });
- /**
- * Moves map filters when scrolling
- *
- */
- moveFilters.$inject = ['$window', '$timeout'];
- function moveFilters($window, $timeout) {
- return {
- link: link
- };
+ $scope.$watch('vm.readyForDevice', function() {
+ if (vm.readyForDevice.device && vm.readyForDevice.map) {
+ zoomDeviceAndPopUp(vm.readyForDevice.device);
+ }
+ }, true);
- function link() {
- var chartHeight;
- $timeout(function() {
- chartHeight = angular.element('.kit_chart').height();
- }, 1000);
+ $scope.$on('goToLocation', function(event, data) {
+ goToLocation(data);
+ });
- /*
- angular.element($window).on('scroll', function() {
- var windowPosition = document.body.scrollTop;
- if(chartHeight > windowPosition) {
- elem.css('bottom', 12 + windowPosition + 'px');
- }
- });
- */
- }
- }
-})();
+ vm.filters = ['indoor', 'outdoor', 'online', 'offline'];
-(function() {
- 'use strict';
+ vm.openFilterPopup = openFilterPopup;
+ vm.openTagPopup = openTagPopup;
+ vm.removeFilter = removeFilter;
+ vm.removeTag = removeTag;
+ vm.selectedTags = tag.getSelectedTags();
+ vm.selectedFilters = ['indoor', 'outdoor', 'online', 'offline', 'new'];
- angular.module('app.components')
- .factory('layout', layout);
+ vm.checkAllFiltersSelected = checkAllFiltersSelected;
+ initialize();
- function layout() {
+ /////////////////////
- var kitHeight;
+ function initialize() {
- var service = {
- setKit: setKit,
- getKit: getKit
- };
- return service;
+ vm.readyForDevice.map = false;
- function setKit(height) {
- kitHeight = height;
- }
+ $q.all([device.getAllDevices($stateParams.reloadMap)])
+ .then(function(data){
- function getKit() {
- return kitHeight;
- }
- }
-})();
+ data = data[0];
-(function() {
- 'use strict';
+ vm.markers = _.chain(data)
+ .map(function(device) {
+ return new Marker(device);
+ })
+ .filter(function(marker) {
+ return !!marker.lng && !!marker.lat;
+ })
+ .tap(function(data) {
+ device.setWorldMarkers(data);
+ })
+ .value();
- angular.module('app.components')
- .directive('horizontalScroll', horizontalScroll);
-
- /**
- * Used to highlight and unhighlight buttons on the kit dashboard when scrolling horizontally
- *
- */
- horizontalScroll.$inject = ['$window', '$timeout'];
- function horizontalScroll($window, $timeout) {
- return {
- link: link,
- restrict: 'A'
- };
-
- ///////////////////
+ var markersByIndex = _.keyBy(vm.markers, function(marker) {
+ return marker.myData.id;
+ });
+ if($state.params.id && markersByIndex[parseInt($state.params.id)]){
+ focusedMarkerID = markersByIndex[parseInt($state.params.id)]
+ .myData.id;
+ vm.readyForDevice.map = true;
+ } else {
+ updateMarkers();
+ vm.readyForDevice.map = true;
+ }
- function link(scope, element) {
+ });
+ }
- element.on('scroll', function() {
- // horizontal scroll position
- var position = angular.element(this).scrollLeft();
- // real width of element
- var scrollWidth = this.scrollWidth;
- // visible width of element
- var width = angular.element(this).width();
+ function zoomDeviceAndPopUp(data){
- // if you cannot scroll, unhighlight both
- if(scrollWidth === width) {
- angular.element('.button_scroll_left').css('opacity', '0.5');
- angular.element('.button_scroll_right').css('opacity', '0.5');
- }
- // if scroll is in the middle, highlight both
- if(scrollWidth - width > 2) {
- angular.element('.button_scroll_left').css('opacity', '1');
- angular.element('.button_scroll_right').css('opacity', '1');
- }
- // if scroll is at the far right, unhighligh right button
- if(scrollWidth - width - position <= 2) {
- angular.element('.button_scroll_right').css('opacity', '0.5');
+ if(updateType === 'map') {
+ vm.deviceLoading = false;
+ updateType = undefined;
return;
+ } else {
+ vm.deviceLoading = true;
}
- // if scroll is at the far left, unhighligh left button
- if(position === 0) {
- angular.element('.button_scroll_left').css('opacity', '0.5');
- return;
- }
- //set opacity back to normal otherwise
- angular.element('.button_scroll_left').css('opacity', '1');
- angular.element('.button_scroll_right').css('opacity', '1');
- });
+ leafletData.getMarkers()
+ .then(function(markers) {
+ var currentMarker = _.find(markers, function(marker) {
+ return data.id === marker.options.myData.id;
+ });
- $timeout(function() {
- element.trigger('scroll');
- });
+ var id = data.id;
- angular.element($window).on('resize', function() {
- $timeout(function() {
- element.trigger('scroll');
- }, 1000);
- });
- }
- }
-})();
+ leafletData.getLayers()
+ .then(function(layers) {
+ if(currentMarker){
+ layers.overlays.devices.zoomToShowLayer(currentMarker,
+ function() {
+ var selectedMarker = currentMarker;
+ if(selectedMarker) {
+ // Ensures the marker is not just zoomed but the marker is centered to improve UX
+ // The $timeout can be replaced by an event but tests didn't show good results
+ $timeout(function() {
+ vm.center.lat = selectedMarker.options.lat;
+ vm.center.lng = selectedMarker.options.lng;
+ selectedMarker.openPopup();
+ vm.deviceLoading = false;
+ }, 1000);
+ }
+ });
+ } else {
+ leafletData.getMap().then(function(map){
+ map.closePopup();
+ });
+ }
+ });
+ });
-(function() {
- 'use strict';
+ }
- angular.module('app.components')
- .directive('hidePopup', hidePopup);
+ function checkAllFiltersSelected() {
+ var allFiltersSelected = _.every(vm.filters, function(filterValue) {
+ return _.includes(vm.selectedFilters, filterValue);
+ });
+ return allFiltersSelected;
+ }
- /**
- * Used on kit dashboard to hide popup with full sensor description
- *
- */
-
- hidePopup.$inject = [];
- function hidePopup() {
- return {
- link: link
- };
+ function openFilterPopup() {
+ $mdDialog.show({
+ hasBackdrop: true,
+ controller: 'MapFilterModalController',
+ controllerAs: 'vm',
+ templateUrl: 'app/components/map/mapFilterModal.html',
+ clickOutsideToClose: true,
+ locals: {
+ selectedFilters: vm.selectedFilters
+ }
+ })
+ .then(function(selectedFilters) {
+ updateType = 'map';
+ vm.selectedFilters = selectedFilters;
+ updateMapFilters();
+ });
+ }
- /////////////
+ function openTagPopup() {
+ $mdDialog.show({
+ hasBackdrop: true,
+ controller: 'MapTagModalController',
+ controllerAs: 'vm',
+ templateUrl: 'app/components/map/mapTagModal.html',
+ //targetEvent: ev,
+ clickOutsideToClose: true,
+ locals: {
+ selectedTags: vm.selectedTags
+ }
+ })
+ .then(function(selectedTags) {
+ if (selectedTags && selectedTags.length > 0) {
+ updateType = 'map';
+ tag.setSelectedTags(_.map(selectedTags, 'name'));
+ vm.selectedTags = tag.getSelectedTags();
+ reloadWithTags();
+ } else if (selectedTags === null) {
+ reloadNoTags();
+ }
+ });
+ }
- function link(scope, elem) {
- elem.on('mouseleave', function() {
- angular.element('.sensor_description_preview').show();
- angular.element('.sensor_description_full').hide();
+ function updateMapFilters(){
+ vm.selectedTags = tag.getSelectedTags();
+ checkAllFiltersSelected();
+ updateMarkers();
+ }
+
+ function removeFilter(filterName) {
+ vm.selectedFilters = _.filter(vm.selectedFilters, function(el){
+ return el !== filterName;
});
+ if(vm.selectedFilters.length === 0){
+ vm.selectedFilters = vm.filters;
+ }
+ updateMarkers();
}
- }
-})();
-(function() {
- 'use strict';
+ function filterMarkersByLabel(tmpMarkers) {
+ return tmpMarkers.filter(function(marker) {
+ var labels = marker.myData.labels;
+ if (labels.length === 0 && vm.selectedFilters.length !== 0){
+ return false;
+ }
+ return _.every(labels, function(label) {
+ return _.includes(vm.selectedFilters, label);
+ });
+ });
+ }
- angular.module('app.components')
- .directive('disableScroll', disableScroll);
+ function updateMarkers() {
+ $timeout(function() {
+ $scope.$apply(function() {
+ var allMarkers = device.getWorldMarkers();
- disableScroll.$inject = ['$timeout'];
- function disableScroll($timeout) {
- return {
- // link: {
- // pre: link
- // },
- compile: link,
- restrict: 'A',
- priority: 100000
- };
+ var updatedMarkers = allMarkers;
+ updatedMarkers = tag.filterMarkersByTag(updatedMarkers);
+ updatedMarkers = filterMarkersByLabel(updatedMarkers);
+ vm.markers = updatedMarkers;
- //////////////////////
+ animation.mapStateLoaded();
- function link(elem) {
- console.log('i', elem);
- // var select = elem.find('md-select');
- // angular.element(select).on('click', function() {
- elem.on('click', function() {
- console.log('e');
- angular.element(document.body).css('overflow', 'hidden');
- $timeout(function() {
- angular.element(document.body).css('overflow', 'initial');
+ vm.deviceLoading = false;
+
+ zoomOnMarkers();
});
});
}
- }
-})();
-
-(function() {
- 'use strict';
-
- angular.module('app.components')
- .factory('animation', animation);
-
- /**
- * Used to emit events from rootscope.
- *
- * This events are then listened by $scope on controllers and directives that care about that particular event
- */
- animation.$inject = ['$rootScope'];
- function animation($rootScope) {
+ function getZoomLevel(data) {
+ // data.layer is an array of strings like ["establishment", "point_of_interest"]
+ var zoom = 18;
- var service = {
- blur: blur,
- unblur: unblur,
- removeNav: removeNav,
- addNav: addNav,
- showChartSpinner: showChartSpinner,
- hideChartSpinner: hideChartSpinner,
- deviceLoaded: deviceLoaded,
- showPasswordRecovery: showPasswordRecovery,
- showLogin: showLogin,
- showSignup: showSignup,
- showPasswordReset: showPasswordReset,
- hideAlert: hideAlert,
- viewLoading: viewLoading,
- viewLoaded: viewLoaded,
- deviceWithoutData: deviceWithoutData,
- deviceIsPrivate: deviceIsPrivate,
- goToLocation: goToLocation,
- mapStateLoading: mapStateLoading,
- mapStateLoaded: mapStateLoaded
- };
- return service;
-
- //////////////
+ if(data.layer && data.layer[0]) {
+ switch(data.layer[0]) {
+ case 'point_of_interest':
+ zoom = 18;
+ break;
+ case 'address':
+ zoom = 18;
+ break;
+ case "establishment":
+ zoom = 15;
+ break;
+ case 'neighbourhood':
+ zoom = 13;
+ break;
+ case 'locality':
+ zoom = 13;
+ break;
+ case 'localadmin':
+ zoom = 9;
+ break;
+ case 'county':
+ zoom = 9;
+ break;
+ case 'region':
+ zoom = 8;
+ break;
+ case 'country':
+ zoom = 7;
+ break;
+ case 'coarse':
+ zoom = 7;
+ break;
+ }
+ }
- function blur() {
- $rootScope.$broadcast('blur');
- }
- function unblur() {
- $rootScope.$broadcast('unblur');
- }
- function removeNav() {
- $rootScope.$broadcast('removeNav');
- }
- function addNav() {
- $rootScope.$broadcast('addNav');
- }
- function showChartSpinner() {
- $rootScope.$broadcast('showChartSpinner');
- }
- function hideChartSpinner() {
- $rootScope.$broadcast('hideChartSpinner');
- }
- function deviceLoaded(data) {
- $rootScope.$broadcast('deviceLoaded', data);
- }
- function showPasswordRecovery() {
- $rootScope.$broadcast('showPasswordRecovery');
- }
- function showLogin() {
- $rootScope.$broadcast('showLogin');
- }
- function showSignup() {
- $rootScope.$broadcast('showSignup');
- }
- function showPasswordReset() {
- $rootScope.$broadcast('showPasswordReset');
- }
- function hideAlert() {
- $rootScope.$broadcast('hideAlert');
- }
- function viewLoading() {
- $rootScope.$broadcast('viewLoading');
+ return zoom;
}
- function viewLoaded() {
- $rootScope.$broadcast('viewLoaded');
+
+ function isRetina(){
+ return ((window.matchMedia &&
+ (window.matchMedia('only screen and (min-resolution: 192dpi), ' +
+ 'only screen and (min-resolution: 2dppx), only screen and ' +
+ '(min-resolution: 75.6dpcm)').matches ||
+ window.matchMedia('only screen and (-webkit-min-device-pixel-ra' +
+ 'tio: 2), only screen and (-o-min-device-pixel-ratio: 2/1), only' +
+ ' screen and (min--moz-device-pixel-ratio: 2), only screen and ' +
+ '(min-device-pixel-ratio: 2)').matches)) ||
+ (window.devicePixelRatio && window.devicePixelRatio >= 2)) &&
+ /(iPad|iPhone|iPod|Apple)/g.test(navigator.userAgent);
}
- function deviceWithoutData(data) {
- $rootScope.$broadcast('deviceWithoutData', data);
+
+ function goToLocation(data){
+ // This ensures the action runs after the event is registered
+ $timeout(function() {
+ vm.center.lat = data.lat;
+ vm.center.lng = data.lng;
+ vm.center.zoom = getZoomLevel(data);
+ });
}
- function deviceIsPrivate(data) {
- $rootScope.$broadcast('deviceIsPrivate', data);
+
+ function removeTag(tagName){
+ tag.setSelectedTags(_.filter(vm.selectedTags, function(el){
+ return el !== tagName;
+ }));
+
+ vm.selectedTags = tag.getSelectedTags();
+
+ if(vm.selectedTags.length === 0){
+ reloadNoTags();
+ } else {
+ reloadWithTags();
+ }
+
}
- function goToLocation(data) {
- $rootScope.$broadcast('goToLocation', data);
+
+ function zoomOnMarkers(){
+ $timeout(function() {
+ if(vm.markers && vm.markers.length > 0) {
+ leafletData.getMap().then(function(map){
+ var bounds = L.latLngBounds(vm.markers);
+ map.fitBounds(bounds);
+ });
+ } else {
+ alert.error('No markers found with those filters', 5000);
+ }
+ });
}
- function mapStateLoading() {
- $rootScope.$broadcast('mapStateLoading');
+
+ function reloadWithTags(){
+ $state.transitionTo('layout.home.tags', {tags: vm.selectedTags}, {reload: true});
}
- function mapStateLoaded() {
- $rootScope.$broadcast('mapStateLoaded');
+
+ function reloadNoTags(){
+ $state.transitionTo('layout.home.kit');
}
+
}
+
})();
(function() {
'use strict';
- /**
- * TODO: Improvement These directives can be split up each one in a different file
- */
+ angular.module('app.components')
+ .controller('LoginModalController', LoginModalController);
- angular.module('app.components')
- .directive('moveDown', moveDown)
- .directive('stick', stick)
- .directive('blur', blur)
- .directive('focus', focus)
- .directive('changeMapHeight', changeMapHeight)
- .directive('changeContentMargin', changeContentMargin)
- .directive('focusInput', focusInput);
+ LoginModalController.$inject = ['$scope', '$mdDialog', 'auth', 'animation'];
+ function LoginModalController($scope, $mdDialog, auth, animation) {
+ const vm = this;
+ $scope.answer = function(answer) {
+ $scope.waitingFromServer = true;
+ auth.login(answer)
+ .then(function(data) {
+ /*jshint camelcase: false */
+ var token = data.access_token;
+ auth.saveToken(token);
+ $mdDialog.hide();
+ })
+ .catch(function(err) {
+ vm.errors = err.data;
+ })
+ .finally(function() {
+ $scope.waitingFromServer = false;
+ });
+ };
+ $scope.hide = function() {
+ $mdDialog.hide();
+ };
+ $scope.cancel = function() {
+ $mdDialog.hide();
+ };
- /**
- * It moves down kit section to ease the transition after the kit menu is sticked to the top
- *
- */
- moveDown.$inject = [];
- function moveDown() {
+ $scope.openSignup = function() {
+ animation.showSignup();
+ $mdDialog.hide();
+ };
- function link(scope, element) {
- scope.$watch('moveDown', function(isTrue) {
- if(isTrue) {
- element.addClass('move_down');
- } else {
- element.removeClass('move_down');
- }
+ $scope.openPasswordRecovery = function() {
+ $mdDialog.show({
+ hasBackdrop: true,
+ controller: 'PasswordRecoveryModalController',
+ templateUrl: 'app/components/passwordRecovery/passwordRecoveryModal.html',
+ clickOutsideToClose: true
});
- }
- return {
- link: link,
- scope: false,
- restrict: 'A'
+ $mdDialog.hide();
};
}
+})();
- /**
- * It sticks kit menu when kit menu touchs navbar on scrolling
- *
- */
- stick.$inject = ['$window', '$timeout'];
- function stick($window, $timeout) {
- function link(scope, element) {
- var elementPosition = element[0].offsetTop;
- //var elementHeight = element[0].offsetHeight;
- var navbarHeight = angular.element('.stickNav').height();
+(function() {
+ 'use strict';
- $timeout(function() {
- elementPosition = element[0].offsetTop;
- //var elementHeight = element[0].offsetHeight;
- navbarHeight = angular.element('.stickNav').height();
- }, 1000);
+ angular.module('app.components')
+ .directive('login', login);
+ function login() {
+ return {
+ scope: {
+ show: '='
+ },
+ restrict: 'A',
+ controller: 'LoginController',
+ controllerAs: 'vm',
+ templateUrl: 'app/components/login/login.html'
+ };
+ }
+})();
- angular.element($window).on('scroll', function() {
- var windowPosition = document.body.scrollTop;
+(function() {
+ 'use strict';
- //sticking menu and moving up/down
- if(windowPosition + navbarHeight >= elementPosition) {
- element.addClass('stickMenu');
- scope.$apply(function() {
- scope.moveDown = true;
- });
- } else {
- element.removeClass('stickMenu');
- scope.$apply(function() {
- scope.moveDown = false;
- });
- }
- });
- }
-
- return {
- link: link,
- scope: false,
- restrict: 'A'
- };
- }
-
- /**
- * Unused directive. Double-check is not being used before removing it
- *
- */
+ angular.module('app.components')
+ .controller('LoginController', LoginController);
- function blur() {
+ LoginController.$inject = ['$scope', '$mdDialog'];
+ function LoginController($scope, $mdDialog) {
- function link(scope, element) {
+ $scope.showLogin = showLogin;
- scope.$on('blur', function() {
- element.addClass('blur');
- });
+ $scope.$on('showLogin', function() {
+ showLogin();
+ });
- scope.$on('unblur', function() {
- element.removeClass('blur');
- });
- }
+ ////////////////
- return {
- link: link,
- scope: false,
- restrict: 'A'
- };
+ function showLogin() {
+ $mdDialog.show({
+ hasBackdrop: true,
+ fullscreen: true,
+ controller: 'LoginModalController',
+ controllerAs: 'vm',
+ templateUrl: 'app/components/login/loginModal.html',
+ clickOutsideToClose: true
+ });
}
- /**
- * Used to remove nav and unable scrolling when searching
- *
- */
- focus.$inject = ['animation'];
- function focus(animation) {
- function link(scope, element) {
- element.on('focusin', function() {
- animation.removeNav();
- });
-
- element.on('focusout', function() {
- animation.addNav();
- });
-
- var searchInput = element.find('input');
- searchInput.on('blur', function() {
- //enable scrolling on body when search input is not active
- angular.element(document.body).css('overflow', 'auto');
- });
-
- searchInput.on('focus', function() {
- angular.element(document.body).css('overflow', 'hidden');
- });
- }
+ }
+})();
- return {
- link: link
- };
- }
+(function() {
+ 'use strict';
- /**
- * Changes map section based on screen size
- *
- */
- changeMapHeight.$inject = ['$document', 'layout', '$timeout'];
- function changeMapHeight($document, layout, $timeout) {
- function link(scope, element) {
+ angular.module('app.components')
+ .controller('LayoutController', LayoutController);
- var screenHeight = $document[0].body.clientHeight;
- var navbarHeight = angular.element('.stickNav').height();
+ LayoutController.$inject = ['$mdSidenav','$mdDialog', '$location', '$state', '$scope', '$transitions', 'auth', 'animation', '$timeout', 'DROPDOWN_OPTIONS_COMMUNITY', 'DROPDOWN_OPTIONS_USER'];
+ function LayoutController($mdSidenav, $mdDialog, $location, $state, $scope, $transitions, auth, animation, $timeout, DROPDOWN_OPTIONS_COMMUNITY, DROPDOWN_OPTIONS_USER) {
+ var vm = this;
- // var overviewHeight = angular.element('.kit_overview').height();
- // var menuHeight = angular.element('.kit_menu').height();
- // var chartHeight = angular.element('.kit_chart').height();
+ vm.navRightLayout = 'space-around center';
- function resizeMap(){
- $timeout(function() {
- var overviewHeight = angular.element('.over_map').height();
+ $scope.toggleRight = buildToggler('right');
- var objectsHeight = navbarHeight + overviewHeight;
- var objectsHeightPercentage = parseInt((objectsHeight * 100) / screenHeight);
- var mapHeightPercentage = 100 - objectsHeightPercentage;
+ function buildToggler(componentId) {
+ return function() {
+ $mdSidenav(componentId).toggle();
+ };
+ }
- element.css('height', mapHeightPercentage + '%');
+ // listen for any login event so that the navbar can be updated
+ $scope.$on('loggedIn', function(ev, options) {
+ // if(options && options.time === 'appLoad') {
+ // $scope.$apply(function() {
+ // vm.isLoggedin = true;
+ // vm.isShown = true;
+ // angular.element('.nav_right .wrap-dd-menu').css('display', 'initial');
+ // vm.currentUser = auth.getCurrentUser().data;
+ // vm.dropdownOptions[0].text = 'Hello, ' + vm.currentUser.username;
+ // vm.navRightLayout = 'end center';
+ // });
+ // } else {
+ // vm.isLoggedin = true;
+ // vm.isShown = true;
+ // angular.element('.nav_right .wrap-dd-menu').css('display', 'initial');
+ // vm.currentUser = auth.getCurrentUser().data;
+ // vm.dropdownOptions[0].text = 'Hello, ' + vm.currentUser.username;
+ // vm.navRightLayout = 'end center';
+ // }
- var aboveTheFoldHeight = screenHeight - overviewHeight;
- angular
- .element('section[change-content-margin]')
- .css('margin-top', aboveTheFoldHeight + 'px');
- });
+ vm.isLoggedin = true;
+ vm.isShown = true;
+ angular.element('.nav_right .wrap-dd-menu').css('display', 'initial');
+ vm.currentUser = auth.getCurrentUser().data;
+ vm.dropdownOptions[0].text = 'Hi, ' + vm.currentUser.username + '!';
+ vm.navRightLayout = 'end center';
+ if(!$scope.$$phase) {
+ $scope.$digest();
}
+ });
- resizeMap();
-
- scope.element = element;
+ // listen for logout events so that the navbar can be updated
+ $scope.$on('loggedOut', function() {
+ vm.isLoggedIn = false;
+ vm.isShown = true;
+ angular.element('navbar .wrap-dd-menu').css('display', 'none');
+ vm.navRightLayout = 'space-around center';
+ });
- scope.$on('resizeMapHeight',function(){
- resizeMap();
- });
- }
+ vm.isShown = true;
+ vm.isLoggedin = false;
+ vm.logout = logout;
- return {
- link: link,
- scope: true,
- restrict: 'A'
- };
- }
+ vm.dropdownOptions = DROPDOWN_OPTIONS_USER;
+ vm.dropdownSelected = undefined;
- /**
- * Changes margin on kit section based on above-the-fold space left after map section is resize
- */
+ vm.dropdownOptionsCommunity = DROPDOWN_OPTIONS_COMMUNITY;
+ vm.dropdownSelectedCommunity = undefined;
- changeContentMargin.$inject = ['layout', '$timeout', '$document'];
- function changeContentMargin(layout, $timeout, $document) {
- function link(scope, element) {
- var screenHeight = $document[0].body.clientHeight;
+ $scope.$on('removeNav', function() {
+ vm.isShown = false;
+ });
- var overviewHeight = angular.element('.over_map').height();
+ $scope.$on('addNav', function() {
+ vm.isShown = true;
+ });
- var aboveTheFoldHeight = screenHeight - overviewHeight;
- element.css('margin-top', aboveTheFoldHeight + 'px');
- }
+ initialize();
- return {
- link: link
- };
- }
+ //////////////////
- /**
- * Fixes autofocus for inputs that are inside modals
- *
- */
- focusInput.$inject = ['$timeout'];
- function focusInput($timeout) {
- function link(scope, elem) {
+ function initialize() {
$timeout(function() {
- elem.focus();
- });
+ var hash = $location.search();
+ if(hash.signup) {
+ animation.showSignup();
+ } else if(hash.login) {
+ animation.showLogin();
+ } else if(hash.passwordRecovery) {
+ animation.showPasswordRecovery();
+ }
+ }, 1000);
+ }
+
+ function logout() {
+ auth.logout();
+ vm.isLoggedin = false;
}
- return {
- link: link
- };
}
})();
@@ -4284,931 +3848,951 @@
'use strict';
angular.module('app.components')
- .directive('activeButton', activeButton);
+ .controller('LandingController', LandingController);
- /**
- * Used to highlight and unhighlight buttons on kit menu
- *
- * It attaches click handlers dynamically
- */
+ LandingController.$inject = ['$timeout', 'animation', '$mdDialog', '$location', '$anchorScroll'];
- activeButton.$inject = ['$timeout', '$window'];
- function activeButton($timeout, $window) {
- return {
- link: link,
- restrict: 'A'
+ function LandingController($timeout, animation, $mdDialog, $location, $anchorScroll) {
+ var vm = this;
- };
+ vm.showStore = showStore;
+ vm.goToHash = goToHash;
- ////////////////////////////
+ ///////////////////////
- function link(scope, element) {
- var childrens = element.children();
- var container;
+ initialize();
- $timeout(function() {
- var navbar = angular.element('.stickNav');
- var kitMenu = angular.element('.kit_menu');
- var kitOverview = angular.element('.kit_overview');
- var kitDashboard = angular.element('.kit_chart');
- var kitDetails = angular.element('.kit_details');
- var kitOwner = angular.element('.kit_owner');
- var kitComments = angular.element('.kit_comments');
-
- container = {
- navbar: {
- height: navbar.height()
- },
- kitMenu: {
- height: kitMenu.height()
- },
- kitOverview: {
- height: kitOverview.height(),
- offset: kitOverview.offset().top,
- buttonOrder: 0
- },
- kitDashboard: {
- height: kitDashboard.height(),
- offset: kitDashboard.offset().top,
- buttonOrder: 40
- },
- kitDetails: {
- height: kitDetails.height(),
- offset: kitDetails.offset() ? kitDetails.offset().top : 0,
- buttonOrder: 1
- },
- kitOwner: {
- height: kitOwner.height(),
- offset: kitOwner.offset() ? kitOwner.offset().top : 0,
- buttonOrder: 2
- },
- kitComments: {
- height: kitComments.height(),
- offset: kitComments.offset() ? kitComments.offset().top : 0,
- buttonOrder: 3
- }
- };
- }, 1000);
-
- function scrollTo(offset) {
- if(!container) {
- return;
- }
- angular.element($window).scrollTop(offset - container.navbar.height - container.kitMenu.height);
- }
-
- function getButton(buttonOrder) {
- return childrens[buttonOrder];
- }
-
- function unHighlightButtons() {
- //remove border, fill and stroke of every icon
- var activeButton = angular.element('.md-button.button_active');
- if(activeButton.length) {
- activeButton.removeClass('button_active');
-
- var strokeContainer = activeButton.find('.stroke_container');
- strokeContainer.css('stroke', 'none');
- strokeContainer.css('stroke-width', '1');
+ //////////////////
- var fillContainer = strokeContainer.find('.fill_container');
- fillContainer.css('fill', '#FF8600');
- }
+ function initialize() {
+ $timeout(function() {
+ animation.viewLoaded();
+ if($location.hash()) {
+ $anchorScroll();
}
+ }, 500);
+ }
- function highlightButton(button) {
- var clickedButton = angular.element(button);
- //add border, fill and stroke to every icon
- clickedButton.addClass('button_active');
+ function goToHash(hash){
+ $location.hash(hash);
+ $anchorScroll();
+ }
- var strokeContainer = clickedButton.find('.stroke_container');
- strokeContainer.css('stroke', 'white');
- strokeContainer.css('stroke-width', '0.01px');
+ function showStore() {
+ $mdDialog.show({
+ hasBackdrop: true,
+ controller: 'StoreModalController',
+ templateUrl: 'app/components/store/storeModal.html',
+ clickOutsideToClose: true
+ });
+ }
+ }
+})();
- var fillContainer = strokeContainer.find('.fill_container');
- fillContainer.css('fill', 'white');
- }
+(function(){
+ 'use strict';
+ angular.module('app.components')
+ .directive('kitList',kitList);
- //attach event handlers for clicks for every button and scroll to a section when clicked
- _.each(childrens, function(button) {
- angular.element(button).on('click', function() {
- var buttonOrder = angular.element(this).index();
- for(var elem in container) {
- if(container[elem].buttonOrder === buttonOrder) {
- var offset = container[elem].offset;
- scrollTo(offset);
- angular.element($window).trigger('scroll');
- }
- }
- });
- });
+ function kitList(){
+ return{
+ restrict:'E',
+ scope:{
+ devices:'=devices',
+ actions: '=actions'
+ },
+ controllerAs:'vm',
+ templateUrl:'app/components/kitList/kitList.html'
+ };
+ }
+})();
- var currentSection;
+(function() {
+ 'use strict';
- //on scroll, check if window is on a section
- angular.element($window).on('scroll', function() {
- if(!container){ return; }
+ angular.module('app.components')
+ .controller('HomeController', HomeController);
- var windowPosition = document.body.scrollTop;
- var appPosition = windowPosition + container.navbar.height + container.kitMenu.height;
- var button;
- if(currentSection !== 'none' && appPosition <= container.kitOverview.offset) {
- button = getButton(container.kitOverview.buttonOrder);
- unHighlightButtons();
- currentSection = 'none';
- } else if(currentSection !== 'overview' && appPosition >= container.kitOverview.offset && appPosition <= container.kitOverview.offset + container.kitOverview.height) {
- button = getButton(container.kitOverview.buttonOrder);
- unHighlightButtons();
- highlightButton(button);
- currentSection = 'overview';
- } else if(currentSection !== 'details' && appPosition >= container.kitDetails.offset && appPosition <= container.kitDetails.offset + container.kitDetails.height) {
- button = getButton(container.kitDetails.buttonOrder);
- unHighlightButtons();
- highlightButton(button);
- currentSection = 'details';
- } else if(currentSection !== 'owner' && appPosition >= container.kitOwner.offset && appPosition <= container.kitOwner.offset + container.kitOwner.height) {
- button = getButton(container.kitOwner.buttonOrder);
- unHighlightButtons();
- highlightButton(button);
- currentSection = 'owner';
- } else if(currentSection !== 'comments' && appPosition >= container.kitComments.offset && appPosition <= container.kitComments.offset + container.kitOwner.height) {
- button = getButton(container.kitComments.buttonOrder);
- unHighlightButtons();
- highlightButton(button);
- currentSection = 'comments';
- }
- });
- }
- }
+ function HomeController() {
+ }
})();
+(function (){
+ 'use strict';
-(function() {
- 'use strict';
+ angular.module('app.components')
+ .controller('DownloadModalController', DownloadModalController);
- angular.module('app.components')
- .controller('UserProfileController', UserProfileController);
+ DownloadModalController.$inject = ['thisDevice', 'device', '$mdDialog'];
- UserProfileController.$inject = ['$scope', '$stateParams', '$location',
- 'user', 'auth', 'userUtils', '$timeout', 'animation',
- 'NonAuthUser', '$q', 'PreviewDevice'];
- function UserProfileController($scope, $stateParams, $location,
- user, auth, userUtils, $timeout, animation,
- NonAuthUser, $q, PreviewDevice) {
+ function DownloadModalController(thisDevice, device, $mdDialog) {
+ var vm = this;
- var vm = this;
- var userID = parseInt($stateParams.id);
+ vm.device = thisDevice;
+ vm.download = download;
+ vm.cancel = cancel;
- vm.status = undefined;
- vm.user = {};
- vm.devices = [];
- vm.filteredDevices = [];
- vm.filterDevices = filterDevices;
+ ////////////////////////////
- $scope.$on('loggedIn', function() {
- var authUser = auth.getCurrentUser().data;
- if( userUtils.isAuthUser(userID, authUser) ) {
- $location.path('/profile');
- }
- });
+ function download(){
+ device.mailReadings(vm.device)
+ .then(function (){
+ $mdDialog.hide();
+ }).catch(function(err){
+ $mdDialog.cancel(err);
+ });
+ }
- initialize();
+ function cancel(){
+ $mdDialog.cancel();
+ }
+ }
- //////////////////
+})();
- function initialize() {
+(function(){
+'use strict';
- user.getUser(userID)
- .then(function(user) {
- vm.user = new NonAuthUser(user);
+angular.module('app.components')
+ .directive('cookiesLaw', cookiesLaw);
- if(!vm.user.devices.length) {
- return [];
- }
- $q.all(vm.devices = vm.user.devices.map(function(data){
- return new PreviewDevice(data);
- }))
+cookiesLaw.$inject = ['$cookies'];
- }).then(function(error) {
- if(error && error.status === 404) {
- $location.url('/404');
- }
- });
+function cookiesLaw($cookies) {
+ return {
+ template:
+ '' +
+ 'This site uses cookies to offer you a better experience. ' +
+ '
Accept or' +
+ '
Learn More. ' +
+ '
',
+ controller: function($scope) {
- $timeout(function() {
- setSidebarMinHeight();
- animation.viewLoaded();
- }, 500);
+ var init = function(){
+ $scope.isCookieValid();
}
- function filterDevices(status) {
- if(status === 'all') {
- status = undefined;
- }
- vm.status = status;
- }
+ // Helpers to debug
+ // You can also use `document.cookie` in the browser dev console.
+ //console.log($cookies.getAll());
- function setSidebarMinHeight() {
- var height = document.body.clientHeight / 4 * 3;
- angular.element('.profile_content').css('min-height', height + 'px');
+ $scope.isCookieValid = function() {
+ // Use a boolean for the ng-hide, because using a function with ng-hide
+ // is considered bad practice. The digest cycle will call it multiple
+ // times, in our case around 240 times.
+ $scope.isCookieValidBool = ($cookies.get('consent') === 'true')
}
+
+ $scope.acceptCookie = function() {
+ //console.log('Accepting cookie...');
+ var today = new Date();
+ var expireDate = new Date(today);
+ expireDate.setMonth(today.getMonth() + 6);
+
+ $cookies.put('consent', true, {'expires' : expireDate.toUTCString()} );
+
+ // Trigger the check again, after we click
+ $scope.isCookieValid();
+ };
+
+ init();
+
}
+ };
+}
+
+
})();
(function() {
'use strict';
angular.module('app.components')
- .controller('UploadController', UploadController);
+ .directive('chart', chart);
- UploadController.$inject = ['kit', '$state', '$stateParams', 'animation'];
- function UploadController(kit, $state, $stateParams, animation) {
- var vm = this;
+ chart.$inject = ['sensor', 'animation', '$timeout', '$window'];
+ function chart(sensor, animation, $timeout, $window) {
+ var margin, width, height, svg, xScale, yScale0, yScale1, xAxis, yAxisLeft, yAxisRight, dateFormat, areaMain, valueLineMain, areaCompare, valueLineCompare, focusCompare, focusMain, popup, dataMain, colorMain, yAxisScale, unitMain, popupContainer;
- vm.kit = kit;
+ return {
+ link: link,
+ restrict: 'A',
+ scope: {
+ chartData: '='
+ }
+ };
- vm.backToProfile = backToProfile;
+ function link(scope, elem) {
- initialize();
+ $timeout(function() {
+ createChart(elem[0]);
+ }, 0);
- /////////////////
+ var lastData = {};
- function initialize() {
- animation.viewLoaded();
- }
+ // on window resize, it re-renders the chart to fit into the new window size
+ angular.element($window).on('resize', function() {
+ createChart(elem[0]);
+ updateChartData(lastData.data, {type: lastData.type, container: elem[0], color: lastData.color, unit: lastData.unit});
+ });
- function backToProfile() {
- $state.transitionTo('layout.myProfile.kits', $stateParams,
- { reload: false,
- inherit: false,
- notify: true
- });
- }
- }
-})();
+ scope.$watch('chartData', function(newData) {
+ if(!newData) {
+ return;
+ }
-(function(){
-'use strict';
+ if(newData !== undefined) {
+ // if there's data for 2 sensors
+ if(newData[0] && newData[1]) {
+ var sensorDataMain = newData[0].data;
+ // we could get some performance from saving the map in the showKit controller on line 218 and putting that logic in here
+ var dataMain = sensorDataMain.map(function(dataPoint) {
+ return {
+ date: dateFormat(dataPoint.time),
+ count: dataPoint && dataPoint.count,
+ value: dataPoint && dataPoint.value
+ };
+ });
+ // sort data points by date
+ dataMain.sort(function(a, b) {
+ return a.date - b.date;
+ });
+ var sensorDataCompare = newData[1].data;
+ var dataCompare = sensorDataCompare.map(function(dataPoint) {
+ return {
+ date: dateFormat(dataPoint.time),
+ count: dataPoint && dataPoint.count,
+ value: dataPoint && dataPoint.value
+ };
+ });
+ dataCompare.sort(function(a, b) {
+ return a.date - b.date;
+ });
-function parseDataForPost(csvArray) {
- /*
- EXPECTED PAYLOAD
- {
- "data": [{
- "recorded_at": "2016-06-08 10:30:00",
- "sensors": [{
- "id": 22,
- "value": 21
- }]
- }]
- }
- */
- const ids = csvArray[3]; // save ids from the 4th header
- csvArray.splice(0,4); // remove useless headers
- return {
- data: csvArray.map((data) => {
- return {
- recorded_at: data.shift(), // get the timestamp from the first column
- sensors: data.map((value, index) => {
- return {
- id: ids[index+1], // get ID of sensor from headers
- value: value
- };
- })
- .filter((sensor) => sensor.value && sensor.id) // remove empty value or id
- };
- })
- };
-}
+ var data = [dataMain, dataCompare];
+ var colors = [newData[0].color, newData[1].color];
+ var units = [newData[0].unit, newData[1].unit];
+ // saves everything in case we need to re-render
+ lastData = {
+ data: data,
+ type: 'both',
+ color: colors,
+ unit: units
+ };
+ // call function to update the chart with the new data
+ updateChartData(data, {type: 'both', container: elem[0], color: colors, unit: units });
+ // if only data for the main sensor
+ } else if(newData[0]) {
+ var sensorData = newData[0].data;
+ /*jshint -W004 */
+ var data = sensorData.map(function(dataPoint) {
+ return {
+ date: dateFormat(dataPoint.time),
+ count: dataPoint && dataPoint.count,
+ value: dataPoint && dataPoint.value
+ };
+ });
+ data.sort(function(a, b) {
+ return a.date - b.date;
+ });
-controller.$inject = ['device', 'Papa', '$mdDialog', '$q'];
-function controller(device, Papa, $mdDialog, $q) {
- var vm = this;
- vm.loadingStatus = false;
- vm.loadingProgress = 0;
- vm.loadingType = 'indeterminate';
- vm.csvFiles = [];
- vm.$onInit = function() {
- vm.kitLastUpdate = Math.floor(new Date(vm.kit.time).getTime() / 1000);
- }
- vm.onSelect = function() {
- vm.loadingStatus = true;
- vm.loadingType = 'indeterminate';
- }
- vm.change = function(files, invalidFiles) {
- let count = 0;
- vm.invalidFiles = invalidFiles;
- if (!files) { return; }
- vm.loadingStatus = true;
- vm.loadingType = 'determinate';
- vm.loadingProgress = 0;
- $q.all(
- files
- .filter((file) => vm._checkDuplicate(file))
- .map((file, index, filteredFiles) => {
- vm.csvFiles.push(file);
- return vm._analyzeData(file)
- .then((result) => {
- if (result.errors && result.errors.length > 0) {
- file.parseErrors = result.errors;
- }
- const lastTimestamp = Math.floor((new Date(result.data[result.data.length - 1][0])).getTime() / 1000);
- const isNew = vm.kitLastUpdate < lastTimestamp;
- file.checked = isNew;
- file.progress = null;
- file.isNew = isNew;
- })
- .then(() => {
- count += 1;
- vm.loadingProgress = (count)/filteredFiles.length * 100;
+ var color = newData[0].color;
+ var unit = newData[0].unit;
+
+ lastData = {
+ data: data,
+ type: 'main',
+ color: color,
+ unit: unit
+ };
+ updateChartData(data, {type: 'main', container: elem[0], color: color, unit: unit });
+ }
+ animation.hideChartSpinner();
+ }
});
- })
- ).then(() => {
- vm.loadingStatus = false;
- }).catch(() => {
- vm.loadingStatus = false;
- });
- }
+ }
- vm.haveSelectedFiles = function() {
- return vm.csvFiles && vm.csvFiles.some((file) => file.checked);
- };
+ // creates the container that is re-used across different sensor charts
+ function createChart(elem) {
+ d3.select(elem).selectAll('*').remove();
- vm.haveSelectedNoFiles = function() {
- return vm.csvFiles && !vm.csvFiles.some((file) => file.checked);
- };
+ margin = {top: 20, right: 12, bottom: 20, left: 42};
+ width = elem.clientWidth - margin.left - margin.right;
+ height = elem.clientHeight - margin.top - margin.bottom;
- vm.haveSelectedAllFiles = function() {
- return vm.csvFiles && vm.csvFiles.every((file) => file.checked);
- };
+ xScale = d3.time.scale().range([0, width]);
+ xScale.tickFormat("%Y-%m-%d %I:%M:%S");
+ yScale0 = d3.scale.linear().range([height, 0]);
+ yScale1 = d3.scale.linear().range([height, 0]);
+ yAxisScale = d3.scale.linear().range([height, 0]);
- vm.doAction = function() {
- switch (vm.action) {
- case 'selectAll':
- vm.selectAll(true);
- break;
- case 'deselectAll':
- vm.selectAll(false);
- break;
- case 'upload':
- vm.uploadData();
- break;
- case 'remove':
- vm.csvFiles = vm.csvFiles.filter((file) => !file.checked);
- break;
- }
- vm.action = null;
- };
+ dateFormat = d3.time.format('%Y-%m-%dT%H:%M:%S').parse;//d3.time.format('%Y-%m-%dT%X.%LZ').parse; //'YYYY-MM-DDTHH:mm:ssZ'
- vm.selectAll = function(value) {
- vm.csvFiles.forEach((file) => { file.checked = value });
- };
+ xAxis = d3.svg.axis()
+ .scale(xScale)
+ .orient('bottom')
+ .ticks(5);
- vm.removeFile = function(index) {
- vm.csvFiles.splice(index, 1);
- };
- vm._analyzeData = function(file) {
- file.progress = true;
- return Papa.parse(file, {
- delimiter: ',',
- dynamicTyping: true,
- worker: false,
- skipEmptyLines: true
- }).catch((err) => {
- file.progress = null;
- console('catch',err)
- });
- };
+ yAxisLeft = d3.svg.axis()
+ .scale(yScale0)
+ .orient('left')
+ .ticks(5);
- vm._checkDuplicate = function(file) {
- if (vm.csvFiles.some((csvFile) => file.name === csvFile.name)) {
- file.$errorMessages = {};
- file.$errorMessages.duplicate = true;
- vm.invalidFiles.push(file);
- return false;
- } else {
- return true;
- }
- };
+ yAxisRight = d3.svg.axis()
+ .scale(yScale1)
+ .orient('right')
+ .ticks(5);
- vm.showErrorModal = function(csvFile) {
- $mdDialog.show({
- hasBackdrop: true,
- controller: ['$mdDialog',function($mdDialog) {
- this.parseErrors = csvFile.parseErrors;
- this.backEndErrors = csvFile.backEndErrors;
- this.cancel = function() { $mdDialog.hide(); };
- }],
- controllerAs: 'csvFile',
- templateUrl: 'app/components/upload/errorModal.html',
- clickOutsideToClose: true
- });
- }
+ areaMain = d3.svg.area()
+ .defined(function(d) {return d.value != null })
+ .interpolate('linear')
+ .x(function(d) { return xScale(d.date); })
+ .y0(height)
+ .y1(function(d) { return yScale0(d.count); });
+ valueLineMain = d3.svg.line()
+ .defined(function(d) {return d.value != null })
+ .interpolate('linear')
+ .x(function(d) { return xScale(d.date); })
+ .y(function(d) { return yScale0(d.count); });
- vm.uploadData = function() {
- vm.loadingStatus = true;
- vm.loadingType = 'indeterminate';
- vm.loadingProgress = 0;
- let count = 0;
+ areaCompare = d3.svg.area()
+ .defined(function(d) {return d.value != null })
+ .interpolate('linear')
+ .x(function(d) { return xScale(d.date); })
+ .y0(height)
+ .y1(function(d) { return yScale1(d.count); });
- $q.all(
- vm.csvFiles
- .filter((file) => file.checked && !file.success)
- .map((file, index, filteredFiles) => {
- file.progress = true;
- return vm._analyzeData(file)
- .then((result) => parseDataForPost(result.data)) // TODO: Improvement remove
- // TODO: Improvement with workers
- .then((payload) => device.postReadings(vm.kit, payload))
- .then(() => {
- if (vm.loadingType === 'indeterminate') { vm.loadingType = 'determinate'; };
- file.success = true;
- file.progress = null;
- count += 1;
- vm.loadingProgress = (count)/filteredFiles.length * 100;
- })
- .catch((errors) => {
- console.log(errors);
- file.detailShowed = true;
- file.backEndErrors = errors;
- file.progress = null;
- });
- })
- ).then(() => {
- vm.loadingStatus = false;
- })
- .catch(() => {
- vm.loadingStatus = false;
- });
- }
-};
+ valueLineCompare = d3.svg.line()
+ .defined(function(d) {return d.value != null })
+ .interpolate('linear')
+ .x(function(d) { return xScale(d.date); })
+ .y(function(d) { return yScale1(d.count); });
+ svg = d3
+ .select(elem)
+ .append('svg')
+ .attr('width', width + margin.left + margin.right)
+ .attr('height', height + margin.top + margin.bottom)
+ .append('g')
+ .attr('transform', 'translate(' + (margin.left - margin.right) + ',' + margin.top + ')');
+ }
+ // calls functions depending on type of chart
+ function updateChartData(newData, options) {
+ if(options.type === 'main') {
+ updateChartMain(newData, options);
+ } else if(options.type === 'both') {
+ updateChartCompare(newData, options);
+ }
+ }
+ // function in charge of rendering when there's data for 1 sensor
+ function updateChartMain(data, options) {
+ xScale.domain(d3.extent(data, function(d) { return d.date; }));
+ yScale0.domain([(d3.min(data, function(d) { return d.count; })) * 0.8, (d3.max(data, function(d) { return d.count; })) * 1.2]);
-angular.module('app.components')
- .component('scCsvUpload', {
- templateUrl: 'app/components/upload/csvUpload.html',
- controller: controller,
- bindings: {
- kit: '<'
- },
- controllerAs: 'vm'
- });
-})();
+ svg.selectAll('*').remove();
-(function() {
- 'use strict';
+ //Add the area path
+ svg.append('path')
+ .datum(data)
+ .attr('class', 'chart_area')
+ .attr('fill', options.color)
+ .attr('d', areaMain);
- angular.module('app.components')
- .controller('tagsController', tagsController);
+ // Add the valueline path.
+ svg.append('path')
+ .attr('class', 'chart_line')
+ .attr('stroke', options.color)
+ .attr('d', valueLineMain(data));
- tagsController.$inject = ['tag', '$scope', 'device', '$state', '$q',
- 'PreviewDevice', 'animation'
- ];
+ // Add the X Axis
+ svg.append('g')
+ .attr('class', 'axis x')
+ .attr('transform', 'translate(0,' + height + ')')
+ .call(xAxis);
- function tagsController(tag, $scope, device, $state, $q, PreviewDevice,
- animation) {
+ // Add the Y Axis
+ svg.append('g')
+ .attr('class', 'axis y_left')
+ .call(yAxisLeft);
- var vm = this;
+ // Draw the x Grid lines
+ svg.append('g')
+ .attr('class', 'grid')
+ .attr('transform', 'translate(0,' + height + ')')
+ .call(xGrid()
+ .tickSize(-height, 0, 0)
+ .tickFormat('')
+ );
- vm.selectedTags = tag.getSelectedTags();
- vm.markers = [];
- vm.kits = [];
- vm.percActive = 0;
+ // Draw the y Grid lines
+ svg.append('g')
+ .attr('class', 'grid')
+ .call(yGrid()
+ .tickSize(-width, 0, 0)
+ .tickFormat('')
+ );
- initialize();
+ focusMain = svg.append('g')
+ .attr('class', 'focus')
+ .style('display', 'none');
- /////////////////////////////////////////////////////////
+ focusMain.append('circle')
+ .style('stroke', options.color)
+ .attr('r', 4.5);
- function initialize() {
- if(vm.selectedTags.length === 0){
- $state.transitionTo('layout.home.kit');
- }
+ var popupWidth = 84;
+ var popupHeight = 46;
- if (device.getWorldMarkers()) {
- // If the user has already loaded a prev page and has markers in mem or localstorage
- updateSelectedTags();
- } else {
- // If the user is new we wait the map to load the markers
- $scope.$on('mapStateLoaded', function(event, data) {
- updateSelectedTags();
- });
- }
+ popup = svg.append('g')
+ .attr('class', 'focus')
+ .style('display', 'none');
- }
+ popupContainer = popup.append('rect')
+ .attr('width', popupWidth)
+ .attr('height', popupHeight)
+ .attr('transform', function() {
+ var result = 'translate(-42, 5)';
- function updateSelectedTags(){
+ return result;
+ })
+ .style('stroke', 'grey')
+ .style('stroke-width', '0.5')
+ .style('fill', 'white');
- vm.markers = tag.filterMarkersByTag(device.getWorldMarkers());
+ var text = popup.append('text')
+ .attr('class', '');
- var onlineMarkers = _.filter(vm.markers, isOnline);
- if (vm.markers.length === 0) {
- vm.percActive = 0;
- } else {
- vm.percActive = Math.floor(onlineMarkers.length / vm.markers.length *
- 100);
- }
+ var textMain = text.append('tspan')
+ .attr('class', 'popup_main')
+ .attr('text-anchor', 'start')
+ .attr('x', -popupWidth / 2)
+ .attr('dx', 8)
+ .attr('y', popupHeight / 2)
+ .attr('dy', 3);
- animation.viewLoaded();
+ textMain.append('tspan')
+ .attr('class', 'popup_value');
- getTaggedDevices()
- .then(function(res){
- vm.kits = res;
- });
- }
-
-
- function isOnline(marker) {
- return _.includes(marker.myData.labels, 'online');
- }
+ textMain.append('tspan')
+ .attr('class', 'popup_unit')
+ .attr('dx', 5);
- function descLastUpdate(o) {
- return -new Date(o.last_reading_at).getTime();
- }
+ text.append('tspan')
+ .attr('class', 'popup_date')
+ .attr('x', -popupWidth / 2)
+ .attr('dx', 8)
+ .attr('y', popupHeight - 2)
+ .attr('dy', 0)
+ .attr( 'text-anchor', 'start' );
- function getTaggedDevices() {
+ svg.append('rect')
+ .attr('class', 'overlay')
+ .attr('width', width)
+ .attr('height', height)
+ .on('mouseover', function() {
+ popup.style('display', null);
+ focusMain.style('display', null);
+ })
+ .on('mouseout', function() {
+ popup.style('display', 'none');
+ focusMain.style('display', 'none');
+ })
+ .on('mousemove', mousemove);
- var deviceProm = _.map(vm.markers, getMarkerDevice);
- return $q.all(deviceProm)
- .then(function(devices) {
- return _.map(_.sortBy(devices, descLastUpdate), toPreviewDevice); // This sort is temp
- });
- }
- function toPreviewDevice(dev) {
- return new PreviewDevice(dev);
- }
+ function mousemove() {
+ var bisectDate = d3.bisector(function(d) { return d.date; }).left;
- function getMarkerDevice(marker) {
- return device.getDevice(marker.myData.id);
- }
- }
+ var x0 = xScale.invert(d3.mouse(this)[0]);
+ var i = bisectDate(data, x0, 1);
+ var d0 = data[i - 1];
+ var d1 = data[i];
+ var d = d1 && (x0 - d0.date > d1.date - x0) ? d1 : d0;
-})();
+ focusMain.attr('transform', 'translate(' + xScale(d.date) + ', ' + yScale0(d.count) + ')');
+ var popupText = popup.select('text');
+ var textMain = popupText.select('.popup_main');
+ var valueMain = textMain.select('.popup_value').text(parseValue(d.value));
+ var unitMain = textMain.select('.popup_unit').text(options.unit);
+ var date = popupText.select('.popup_date').text(parseTime(d.date));
-(function(){
- 'use strict';
- angular.module('app.components')
- .directive('tag',tag);
+ var textContainers = [
+ textMain,
+ date
+ ];
- function tag(){
- return{
- restrict: 'E',
- scope:{
- tagName: '=',
- openTag: '&'
- },
- controller:function($scope, $state){
- $scope.openTag = function(){
- $state.go('layout.home.tags', {tags:[$scope.tagName]});
- };
- },
- template:'{{tagName}}',
- link: function(scope, element, attrs){
- element.addClass('tag');
+ var popupWidth = resizePopup(popupContainer, textContainers);
- if(typeof(attrs.clickable) !== 'undefined'){
- element.bind('click', scope.openTag);
+ if(xScale(d.date) + 80 + popupWidth > options.container.clientWidth) {
+ popup.attr('transform', 'translate(' + (xScale(d.date) - 120) + ', ' + (d3.mouse(this)[1] - 20) + ')');
+ } else {
+ popup.attr('transform', 'translate(' + (xScale(d.date) + 80) + ', ' + (d3.mouse(this)[1] - 20) + ')');
+ }
}
}
- };
- }
-})();
-(function() {
- 'use strict';
+ // function in charge of rendering when there's data for 2 sensors
+ function updateChartCompare(data, options) {
+ xScale.domain(d3.extent(data[0], function(d) { return d.date; }));
+ yScale0.domain([(d3.min(data[0], function(d) { return d.count; })) * 0.8, (d3.max(data[0], function(d) { return d.count; })) * 1.2]);
+ yScale1.domain([(d3.min(data[1], function(d) { return d.count; })) * 0.8, (d3.max(data[1], function(d) { return d.count; })) * 1.2]);
- angular.module('app.components')
- .controller('StoreModalController', StoreModalController);
+ svg.selectAll('*').remove();
- StoreModalController.$inject = ['$scope', '$mdDialog'];
- function StoreModalController($scope, $mdDialog) {
+ //Add both area paths
+ svg.append('path')
+ .datum(data[0])
+ .attr('class', 'chart_area')
+ .attr('fill', options.color[0])
+ .attr('d', areaMain);
- $scope.cancel = function() {
- $mdDialog.hide();
- };
- }
-})();
+ svg.append('path')
+ .datum(data[1])
+ .attr('class', 'chart_area')
+ .attr('fill', options.color[1])
+ .attr('d', areaCompare);
-(function() {
- 'use strict';
+ // Add both valueline paths.
+ svg.append('path')
+ .attr('class', 'chart_line')
+ .attr('stroke', options.color[0])
+ .attr('d', valueLineMain(data[0]));
- angular.module('app.components')
- .directive('store', store);
+ svg.append('path')
+ .attr('class', 'chart_line')
+ .attr('stroke', options.color[1])
+ .attr('d', valueLineCompare(data[1]));
- function store() {
- return {
- scope: {
- isLoggedin: '=logged'
- },
- restrict: 'A',
- controller: 'StoreController',
- controllerAs: 'vm',
- templateUrl: 'app/components/store/store.html'
- };
- }
-})();
+ // Add the X Axis
+ svg.append('g')
+ .attr('class', 'axis x')
+ .attr('transform', 'translate(0,' + height + ')')
+ .call(xAxis);
-(function() {
- 'use strict';
+ // Add both Y Axis
+ svg.append('g')
+ .attr('class', 'axis y_left')
+ .call(yAxisLeft);
- angular.module('app.components')
- .controller('StoreController', StoreController);
+ svg.append('g')
+ .attr('class', 'axis y_right')
+ .attr('transform', 'translate(' + width + ' ,0)')
+ .call(yAxisRight);
- StoreController.$inject = ['$scope', '$mdDialog'];
- function StoreController($scope, $mdDialog) {
+ // Draw the x Grid lines
+ svg.append('g')
+ .attr('class', 'grid')
+ .attr('transform', 'translate(0,' + height + ')')
+ .call(xGrid()
+ .tickSize(-height, 0, 0)
+ .tickFormat('')
+ );
- $scope.showStore = showStore;
+ // Draw the y Grid lines
+ svg.append('g')
+ .attr('class', 'grid')
+ .call(yGrid()
+ .tickSize(-width, 0, 0)
+ .tickFormat('')
+ );
- $scope.$on('showStore', function() {
- showStore();
- });
-
- ////////////////
+ focusCompare = svg.append('g')
+ .attr('class', 'focus')
+ .style('display', 'none');
- function showStore() {
- $mdDialog.show({
- hasBackdrop: true,
- controller: 'StoreModalController',
- templateUrl: 'app/components/store/storeModal.html',
- clickOutsideToClose: true
- });
- }
+ focusMain = svg.append('g')
+ .attr('class', 'focus')
+ .style('display', 'none');
- }
-})();
+ focusCompare.append('circle')
+ .style('stroke', options.color[1])
+ .attr('r', 4.5);
-(function() {
- 'use strict';
+ focusMain.append('circle')
+ .style('stroke', options.color[0])
+ .attr('r', 4.5);
- angular.module('app.components')
- .controller('StaticController', StaticController);
+ var popupWidth = 84;
+ var popupHeight = 75;
- StaticController.$inject = ['$timeout', 'animation', '$mdDialog', '$location', '$anchorScroll'];
+ popup = svg.append('g')
+ .attr('class', 'focus')
+ .style('display', 'none');
- function StaticController($timeout, animation, $mdDialog, $location, $anchorScroll) {
- var vm = this;
+ popupContainer = popup.append('rect')
+ .attr('width', popupWidth)
+ .attr('height', popupHeight)
+ .style('min-width', '40px')
+ .attr('transform', function() {
+ var result = 'translate(-42, 5)';
- vm.showStore = showStore;
+ return result;
+ })
+ .style('stroke', 'grey')
+ .style('stroke-width', '0.5')
+ .style('fill', 'white');
- $anchorScroll.yOffset = 80;
+ popup.append('rect')
+ .attr('width', 8)
+ .attr('height', 2)
+ .attr('transform', function() {
+ return 'translate(' + (-popupWidth / 2 + 4).toString() + ', 20)';
+ })
+ .style('fill', options.color[0]);
- ///////////////////////
+ popup.append('rect')
+ .attr('width', 8)
+ .attr('height', 2)
+ .attr('transform', function() {
+ return 'translate(' + (-popupWidth / 2 + 4).toString() + ', 45)';
+ })
+ .style('fill', options.color[1]);
- initialize();
+ var text = popup.append('text')
+ .attr('class', '');
- //////////////////
+ var textMain = text.append('tspan')
+ .attr('class', 'popup_main')
+ .attr('x', -popupHeight / 2 + 7) //position of text
+ .attr('dx', 8) //margin given to the element, will be applied to both sides thanks to resizePopup function
+ .attr('y', popupHeight / 3)
+ .attr('dy', 3);
- function initialize() {
- $timeout(function() {
- animation.viewLoaded();
- if($location.hash()){
- $anchorScroll();
- }
- }, 500);
- }
+ textMain.append('tspan')
+ .attr('class', 'popup_value')
+ .attr( 'text-anchor', 'start' );
- function showStore() {
- $mdDialog.show({
- hasBackdrop: true,
- controller: 'StoreModalController',
- templateUrl: 'app/components/store/storeModal.html',
- clickOutsideToClose: true
- });
- }
- }
-})();
+ textMain.append('tspan')
+ .attr('class', 'popup_unit')
+ .attr('dx', 5);
-(function() {
- 'use strict';
+ var textCompare = text.append('tspan')
+ .attr('class', 'popup_compare')
+ .attr('x', -popupHeight / 2 + 7) //position of text
+ .attr('dx', 8) //margin given to the element, will be applied to both sides thanks to resizePopup function
+ .attr('y', popupHeight / 1.5)
+ .attr('dy', 3);
- angular.module('app.components')
- .controller('SignupModalController', SignupModalController);
+ textCompare.append('tspan')
+ .attr('class', 'popup_value')
+ .attr( 'text-anchor', 'start' );
- SignupModalController.$inject = ['$scope', '$mdDialog', 'user',
- 'alert', 'animation'];
- function SignupModalController($scope, $mdDialog, user,
- alert, animation ) {
- var vm = this;
- vm.answer = function(signupForm) {
+ textCompare.append('tspan')
+ .attr('class', 'popup_unit')
+ .attr('dx', 5);
- if (!signupForm.$valid){
- return;
- }
+ text.append('tspan')
+ .attr('class', 'popup_date')
+ .attr('x', (- popupWidth / 2))
+ .attr('dx', 8)
+ .attr('y', popupHeight - 2)
+ .attr('dy', 0)
+ .attr( 'text-anchor', 'start' );
- $scope.waitingFromServer = true;
- user.createUser(vm.user)
- .then(function() {
- alert.success('Signup was successful');
- $mdDialog.hide();
- }).catch(function(err) {
- alert.error('Signup failed');
- $scope.errors = err.data.errors;
+ svg.append('rect')
+ .attr('class', 'overlay')
+ .attr('width', width)
+ .attr('height', height)
+ .on('mouseover', function() {
+ focusCompare.style('display', null);
+ focusMain.style('display', null);
+ popup.style('display', null);
})
- .finally(function() {
- $scope.waitingFromServer = false;
- });
- };
- $scope.hide = function() {
- $mdDialog.hide();
- };
- $scope.cancel = function() {
- $mdDialog.cancel();
- };
+ .on('mouseout', function() {
+ focusCompare.style('display', 'none');
+ focusMain.style('display', 'none');
+ popup.style('display', 'none');
+ })
+ .on('mousemove', mousemove);
- $scope.openLogin = function() {
- animation.showLogin();
- $mdDialog.hide();
- };
- }
-})();
+ function mousemove() {
+ var bisectDate = d3.bisector(function(d) { return d.date; }).left;
-(function() {
- 'use strict';
+ var x0 = xScale.invert(d3.mouse(this)[0]);
+ var i = bisectDate(data[1], x0, 1);
+ var d0 = data[1][i - 1];
+ var d1 = data[1][i];
+ var d = x0 - d0.date > d1.date - x0 ? d1 : d0;
+ focusCompare.attr('transform', 'translate(' + xScale(d.date) + ', ' + yScale1(d.count) + ')');
- angular.module('app.components')
- .directive('signup', signup);
- function signup() {
- return {
- scope: {
- show: '=',
- },
- restrict: 'A',
- controller: 'SignupController',
- controllerAs: 'vm',
- templateUrl: 'app/components/signup/signup.html'
- };
- }
-})();
+ var dMain0 = data[0][i - 1];
+ var dMain1 = data[0][i];
+ var dMain = x0 - dMain0.date > dMain1.date - x0 ? dMain1 : dMain0;
+ focusMain.attr('transform', 'translate(' + xScale(dMain.date) + ', ' + yScale0(dMain.count) + ')');
-(function() {
- 'use strict';
+ var popupText = popup.select('text');
+ var textMain = popupText.select('.popup_main');
+ textMain.select('.popup_value').text(parseValue(dMain.value));
+ textMain.select('.popup_unit').text(options.unit[0]);
+ var textCompare = popupText.select('.popup_compare');
+ textCompare.select('.popup_value').text(parseValue(d.value));
+ textCompare.select('.popup_unit').text(options.unit[1]);
+ var date = popupText.select('.popup_date').text(parseTime(d.date));
- angular.module('app.components')
- .controller('SignupController', SignupController);
+ var textContainers = [
+ textMain,
+ textCompare,
+ date
+ ];
- SignupController.$inject = ['$scope', '$mdDialog'];
- function SignupController($scope, $mdDialog) {
- var vm = this;
+ var popupWidth = resizePopup(popupContainer, textContainers);
- vm.showSignup = showSignup;
+ if(xScale(d.date) + 80 + popupWidth > options.container.clientWidth) {
+ popup.attr('transform', 'translate(' + (xScale(d.date) - 120) + ', ' + (d3.mouse(this)[1] - 20) + ')');
+ } else {
+ popup.attr('transform', 'translate(' + (xScale(d.date) + 80) + ', ' + (d3.mouse(this)[1] - 20) + ')');
+ }
+ }
+ }
- $scope.$on('showSignup', function() {
- showSignup();
- });
- ////////////////////////
+ function xGrid() {
+ return d3.svg.axis()
+ .scale(xScale)
+ .orient('bottom')
+ .ticks(5);
+ }
+ function yGrid() {
+ return d3.svg.axis()
+ .scale(yScale0)
+ .orient('left')
+ .ticks(5);
+ }
- function showSignup() {
- $mdDialog.show({
- fullscreen: true,
- hasBackdrop: true,
- controller: 'SignupModalController',
- controllerAs: 'vm',
- templateUrl: 'app/components/signup/signupModal.html',
- clickOutsideToClose: true
- });
+ function parseValue(value) {
+ if(value === null) {
+ return 'No data on the current timespan';
+ } else if(value.toString().indexOf('.') !== -1) {
+ var result = value.toString().split('.');
+ return result[0] + '.' + result[1].slice(0, 2);
+ } else if(value > 99.99) {
+ return value.toString();
+ } else {
+ return value.toString().slice(0, 2);
+ }
+ }
+
+ function parseTime(time) {
+ return moment(time).format('h:mm a ddd Do MMM YYYY');
+ }
+
+ function resizePopup(popupContainer, textContainers) {
+ if(!textContainers.length) {
+ return;
+ }
+
+ var widestElem = textContainers.reduce(function(widestElemSoFar, textContainer) {
+ var currentTextContainerWidth = getContainerWidth(textContainer);
+ var prevTextContainerWidth = getContainerWidth(widestElemSoFar);
+ return prevTextContainerWidth >= currentTextContainerWidth ? widestElemSoFar : textContainer;
+ }, textContainers[0]);
+
+ var margins = widestElem.attr('dx') * 2;
+
+ popupContainer
+ .attr('width', getContainerWidth(widestElem) + margins);
+
+ function getContainerWidth(container) {
+ var node = container.node();
+ var width;
+ if(node.getComputedTextLength) {
+ width = node.getComputedTextLength();
+ } else if(node.getBoundingClientRect) {
+ width = node.getBoundingClientRect().width;
+ } else {
+ width = node.getBBox().width;
+ }
+ return width;
+ }
+ return getContainerWidth(widestElem) + margins;
}
}
-})();
-(function() {
-'use strict';
+})();
+(function(){
+ 'use strict';
angular.module('app.components')
- .directive('search', search);
+ .directive('apiKey', apiKey);
- function search() {
+ function apiKey(){
return {
- scope: true,
- restrict: 'E',
- templateUrl: 'app/components/search/search.html',
- controller: 'SearchController',
- controllerAs: 'vm'
+ scope: {
+ apiKey: '=apiKey'
+ },
+ restrict: 'A',
+ controller: 'ApiKeyController',
+ controllerAs: 'vm',
+ templateUrl: 'app/components/apiKey/apiKey.html'
};
}
})();
-(function() {
+(function(){
'use strict';
angular.module('app.components')
- .controller('SearchController', SearchController);
+ .controller('ApiKeyController', ApiKeyController);
- SearchController.$inject = ['$scope', 'search', 'SearchResult', '$location', 'animation', 'SearchResultLocation'];
- function SearchController($scope, search, SearchResult, $location, animation, SearchResultLocation) {
- var vm = this;
+ ApiKeyController.$inject = ['alert'];
+ function ApiKeyController(alert){
+ var vm = this;
- vm.searchTextChange = searchTextChange;
- vm.selectedItemChange = selectedItemChange;
- vm.querySearch = querySearch;
+ vm.copied = copied;
+ vm.copyFail = copyFail;
- ///////////////////
+ ///////////////
- function searchTextChange() {
- }
+ function copied(){
+ alert.success('API key copied to your clipboard.');
+ }
- function selectedItemChange(result) {
- if (!result) { return; }
- if(result.type === 'User') {
- $location.path('/users/' + result.id);
- } else if(result.type === 'Device') {
- $location.path('/kits/' + result.id);
- } else if (result.type === 'City'){
- animation.goToLocation({lat: result.lat, lng: result.lng, type: result.type, layer: result.layer});
- }
+ function copyFail(err){
+ console.log('Copy error: ', err);
+ alert.error('Oops! An error occurred copying the api key.');
+ }
+
+ }
+})();
+
+(function() {
+ 'use strict';
+
+ angular.module('app.components')
+ .factory('alert', alert);
+
+ alert.$inject = ['$mdToast'];
+ function alert($mdToast) {
+ var service = {
+ success: success,
+ error: error,
+ info: {
+ noData: {
+ visitor: infoNoDataVisitor,
+ owner: infoNoDataOwner,
+ private: infoDataPrivate,
+ },
+ longTime: infoLongTime,
+ // TODO: Refactor, check why this was removed
+ // inValid: infoDataInvalid,
+ generic: info
}
+ };
- function querySearch(query) {
- if(query.length < 3) {
- return [];
- }
+ return service;
- return search.globalSearch(query)
- .then(function(data) {
+ ///////////////////
- return data.map(function(object) {
+ function success(message) {
+ toast('success', message);
+ }
- if(object.type === 'City' || object.type === 'Country') {
- return new SearchResultLocation(object);
- } else {
- return new SearchResult(object);
- }
- });
- });
+ function error(message) {
+ toast('error', message);
+ }
+
+ function infoNoDataVisitor() {
+ info('Woah! We couldn\'t locate this kit on the map because it hasn\'t published any data. Leave a ' +
+ 'comment to let its owner know.',
+ 10000,
+ {
+ button: 'Leave comment',
+ href: 'https://forum.smartcitizen.me/'
+ });
+ }
+
+ function infoNoDataOwner() {
+ info('Woah! We couldn\'t locate this kit on the map because it hasn\'t published any data.',
+ 10000);
+ }
+
+ function infoDataPrivate() {
+ info('Device not found, or it has been set to private. Leave a ' +
+ 'comment to let its owner know you\'re interested.',
+ 10000,
+ {
+ button: 'Leave comment',
+ href: 'https://forum.smartcitizen.me/'
+ });
+ }
+
+ // TODO: Refactor, check why this was removed
+ // function infoDataInvalid() {
+ // info('Device not found, or it has been set to private.',
+ // 10000);
+ // }
+
+ function infoLongTime() {
+ info('😅 It looks like this kit hasn\'t posted any data in a long ' +
+ 'time. Why not leave a comment to let its owner know?',
+ 10000,
+ {
+ button: 'Leave comment',
+ href: 'https://forum.smartcitizen.me/'
+ });
+ }
+
+ function info(message, delay, options) {
+ if(options && options.button) {
+ toast('infoButton', message, options, undefined, delay);
+ } else {
+ toast('info', message, options, undefined, delay);
}
}
+
+ function toast(type, message, options, position, delay) {
+ position = position === undefined ? 'top': position;
+ delay = delay === undefined ? 5000 : delay;
+
+ $mdToast.show({
+ controller: 'AlertController',
+ controllerAs: 'vm',
+ templateUrl: 'app/components/alert/alert' + type + '.html',
+ hideDelay: delay,
+ position: position,
+ locals: {
+ message: message,
+ button: options && options.button,
+ href: options && options.href
+ }
+ });
+ }
+ }
})();
(function() {
'use strict';
angular.module('app.components')
- .controller('PasswordResetController', PasswordResetController);
+ .controller('AlertController', AlertController);
- PasswordResetController.$inject = ['$mdDialog', '$stateParams', '$timeout',
- 'animation', '$location', 'alert', 'auth'];
- function PasswordResetController($mdDialog, $stateParams, $timeout,
- animation, $location, alert, auth) {
-
+ AlertController.$inject = ['$scope', '$mdToast', 'message', 'button', 'href'];
+ function AlertController($scope, $mdToast, message, button, href) {
var vm = this;
- vm.showForm = false;
- vm.form = {};
- vm.isDifferent = false;
- vm.answer = answer;
-
- initialize();
- ///////////
-
- function initialize() {
- $timeout(function() {
- animation.viewLoaded();
- }, 500);
- getUserData();
- }
- function getUserData() {
- auth.getResetPassword($stateParams.code)
- .then(function() {
- vm.showForm = true;
- })
- .catch(function() {
- alert.error('Wrong url');
- $location.path('/');
- });
- }
+ vm.close = close;
+ vm.message = message;
+ vm.button = button;
+ vm.href = href;
- function answer(data) {
- vm.waitingFromServer = true;
- vm.errors = undefined;
+ // hideAlert will be triggered on state change
+ $scope.$on('hideAlert', function() {
+ close();
+ });
- if(data.newPassword === data.confirmPassword) {
- vm.isDifferent = false;
- } else {
- vm.isDifferent = true;
- return;
- }
+ ///////////////////
- auth.patchResetPassword($stateParams.code, {password: data.newPassword})
- .then(function() {
- alert.success('Your data was updated successfully');
- $location.path('/profile');
- })
- .catch(function(err) {
- alert.error('Your data wasn\'t updated');
- vm.errors = err.data.errors;
- })
- .finally(function() {
- vm.waitingFromServer = false;
- });
+ function close() {
+ $mdToast.hide();
}
}
})();
@@ -5217,43 +4801,23 @@ angular.module('app.components')
'use strict';
angular.module('app.components')
- .controller('PasswordRecoveryModalController', PasswordRecoveryModalController);
-
- PasswordRecoveryModalController.$inject = ['$scope', 'animation', '$mdDialog', 'auth', 'alert'];
- function PasswordRecoveryModalController($scope, animation, $mdDialog, auth, alert) {
+ .factory('userUtils', userUtils);
- $scope.hide = function() {
- $mdDialog.hide();
- };
- $scope.cancel = function() {
- $mdDialog.cancel();
+ function userUtils() {
+ var service = {
+ isAdmin: isAdmin,
+ isAuthUser: isAuthUser
};
+ return service;
- $scope.recoverPassword = function() {
- $scope.waitingFromServer = true;
- var data = {
- /*jshint camelcase: false */
- email_or_username: $scope.input
- };
-
- auth.recoverPassword(data)
- .then(function() {
- alert.success('You were sent an email to recover your password');
- $mdDialog.hide();
- })
- .catch(function(err) {
- alert.error('That username doesn\'t exist');
- $scope.errors = err.data;
- })
- .finally(function() {
- $scope.waitingFromServer = false;
- });
- };
+ ///////////
- $scope.openSignup = function() {
- animation.showSignup();
- $mdDialog.hide();
- };
+ function isAdmin(userData) {
+ return userData.role === 'admin';
+ }
+ function isAuthUser(userID, authUserData) {
+ return userID === authUserData.id;
+ }
}
})();
@@ -5261,424 +4825,340 @@ angular.module('app.components')
'use strict';
angular.module('app.components')
- .controller('PasswordRecoveryController', PasswordRecoveryController);
+ .factory('timeUtils', timeUtils);
- PasswordRecoveryController.$inject = ['auth', 'alert', '$mdDialog'];
- function PasswordRecoveryController(auth, alert, $mdDialog) {
- var vm = this;
-
- vm.waitingFromServer = false;
- vm.errors = undefined;
- vm.recoverPassword = recoverPassword;
+ function timeUtils() {
+ var service = {
+ getSecondsFromDate: getSecondsFromDate,
+ getMillisFromDate: getMillisFromDate,
+ getCurrentRange: getCurrentRange,
+ getToday: getToday,
+ getHourBefore: getHourBefore,
+ getSevenDaysAgo: getSevenDaysAgo,
+ getDateIn: getDateIn,
+ convertTime: convertTime,
+ formatDate: formatDate,
+ isSameDay: isSameDay,
+ isWithin15min: isWithin15min,
+ isWithin1Month: isWithin1Month,
+ isWithin: isWithin,
+ isDiffMoreThan15min: isDiffMoreThan15min,
+ parseDate: parseDate
+ };
+ return service;
- ///////////////
+ ////////////
- function recoverPassword() {
- vm.waitingFromServer = true;
- vm.errors = undefined;
-
- var data = {
- username: vm.username
- };
+ function getDateIn(timeMS, format) {
+ if(!format) {
+ return timeMS;
+ }
- auth.recoverPassword(data)
- .then(function() {
- alert.success('You were sent an email to recover your password');
- $mdDialog.hide();
- })
- .catch(function(err) {
- vm.errors = err.data.errors;
- if(vm.errors) {
- alert.error('That email/username doesn\'t exist');
- }
- })
- .finally(function() {
- vm.waitingFromServer = false;
- });
+ var result;
+ if(format === 'ms') {
+ result = timeMS;
+ } else if(format === 's') {
+ result = timeMS / 1000;
+ } else if(format === 'm') {
+ result = timeMS / 1000 / 60;
+ } else if(format === 'h') {
+ result = timeMS / 1000 / 60 / 60;
+ } else if(format === 'd') {
+ result = timeMS / 1000 / 60 / 60 / 24;
}
- }
-})();
+ return result;
+ }
-(function() {
- 'use strict';
+ function convertTime(time) {
+ return moment(time).toISOString();
+ }
- angular.module('app.components')
- .controller('MyProfileController', MyProfileController);
+ function formatDate(time) {
+ return moment(time).format('YYYY-MM-DDTHH:mm:ss');
+ }
- MyProfileController.$inject = ['$scope', '$location', '$q', '$interval',
- 'userData', 'AuthUser', 'user', 'auth', 'alert',
- 'COUNTRY_CODES', '$timeout', 'file', 'animation',
- '$mdDialog', 'PreviewDevice', 'device', 'deviceUtils',
- 'userUtils', '$filter', '$state', 'Restangular', '$window'];
- function MyProfileController($scope, $location, $q, $interval,
- userData, AuthUser, user, auth, alert,
- COUNTRY_CODES, $timeout, file, animation,
- $mdDialog, PreviewDevice, device, deviceUtils,
- userUtils, $filter, $state, Restangular, $window) {
+ function getSecondsFromDate(date) {
+ return (new Date(date)).getTime();
+ }
- var vm = this;
+ function getMillisFromDate(date) {
+ return (new Date(date)).getTime();
+ }
- vm.unhighlightIcon = unhighlightIcon;
+ function getCurrentRange(fromDate, toDate) {
+ return moment(toDate).diff(moment(fromDate), 'days');
+ }
- //PROFILE TAB
- vm.formUser = {};
- vm.getCountries = getCountries;
+ function getToday() {
+ return (new Date()).getTime();
+ }
- vm.user = userData;
- copyUserToForm(vm.formUser, vm.user);
- vm.searchText = vm.formUser.country;
+ function getSevenDaysAgo() {
+ return getSecondsFromDate( getToday() - (7 * 24 * 60 * 60 * 1000) );
+ }
- vm.updateUser = updateUser;
- vm.removeUser = removeUser;
- vm.uploadAvatar = uploadAvatar;
+ function getHourBefore(date) {
+ var now = moment(date);
+ return now.subtract(1, 'hour').valueOf();
+ }
- //THIS IS TEMPORARY.
- // Will grow on to a dynamic API KEY management
- // with the new /accounts oAuth mgmt methods
+ function isSameDay(day1, day2) {
+ day1 = moment(day1);
+ day2 = moment(day2);
- // The auth controller has not populated the `user` at this point,
- // so user.token is undefined
- // This controller depends on auth has already been run.
- vm.user.token = auth.getToken();
- vm.addNewDevice = addNewDevice;
+ if(day1.startOf('day').isSame(day2.startOf('day'))) {
+ return true;
+ }
+ return false;
+ }
- //KITS TAB
- vm.devices = [];
- vm.deviceStatus = undefined;
- vm.removeDevice = removeDevice;
- vm.downloadData = downloadData;
+ function isDiffMoreThan15min(dateToCheckFrom, dateToCheckTo) {
+ var duration = moment.duration(moment(dateToCheckTo).diff(moment(dateToCheckFrom)));
+ return duration.as('minutes') > 15;
+ }
- vm.filteredDevices = [];
- vm.dropdownSelected = undefined;
+ function isWithin15min(dateToCheck) {
+ var fifteenMinAgo = moment().subtract(15, 'minutes').valueOf();
+ dateToCheck = moment(dateToCheck).valueOf();
- //SIDEBAR
- vm.filterDevices = filterDevices;
- vm.filterTools = filterTools;
+ return dateToCheck > fifteenMinAgo;
+ }
- vm.selectThisTab = selectThisTab;
+ function isWithin1Month(dateToCheck) {
+ var oneMonthAgo = moment().subtract(1, 'months').valueOf();
+ dateToCheck = moment(dateToCheck).valueOf();
- $scope.$on('loggedOut', function() {
- $location.path('/');
- });
+ return dateToCheck > oneMonthAgo;
+ }
- $scope.$on('devicesContextUpdated', function(){
- var userData = auth.getCurrentUser().data;
- if(userData){
- vm.user = userData;
- }
- initialize();
- });
+ function isWithin(number, type, dateToCheck) {
+ var ago = moment().subtract(number, type).valueOf();
+ dateToCheck = moment(dateToCheck).valueOf();
- initialize();
+ return dateToCheck > ago;
+ }
- //////////////////
+ function parseDate(object){
+ var time = object;
+ return {
+ raw: time,
+ parsed: !time ? 'No time' : moment(time).format('MMMM DD, YYYY - HH:mm'),
+ ago: !time ? 'No time' : moment(time).fromNow()
+ }
+ }
+ }
+})();
- function initialize() {
+(function() {
+ 'use strict';
- startingTab();
- if(!vm.user.devices.length) {
- vm.devices = [];
- animation.viewLoaded();
- } else {
+ angular.module('app.components')
+ .factory('sensorUtils', sensorUtils);
- vm.devices = vm.user.devices.map(function(data) {
- return new PreviewDevice(data);
- })
+ sensorUtils.$inject = ['timeUtils'];
+ function sensorUtils(timeUtils) {
+ var service = {
+ getRollup: getRollup,
+ getSensorName: getSensorName,
+ getSensorValue: getSensorValue,
+ getSensorPrevValue: getSensorPrevValue,
+ getSensorIcon: getSensorIcon,
+ getSensorArrow: getSensorArrow,
+ getSensorColor: getSensorColor,
+ getSensorDescription: getSensorDescription
+ };
+ return service;
- $timeout(function() {
- mapWithBelongstoUser(vm.devices);
- filterDevices(vm.status);
- setSidebarMinHeight();
- animation.viewLoaded();
- });
+ ///////////////
- }
- }
+ function getRollup(dateFrom, dateTo) {
- function filterDevices(status) {
- if(status === 'all') {
- status = undefined;
- }
- vm.deviceStatus = status;
- vm.filteredDevices = $filter('filterLabel')(vm.devices, vm.deviceStatus);
- }
+ // Calculate how many data points we can fit on a users screen
+ // Smaller screens request less data from the API
+ var durationInSec = moment(dateTo).diff(moment(dateFrom)) / 1000;
+ var chartWidth = window.innerWidth / 2;
- function filterTools(type) {
- if(type === 'all') {
- type = undefined;
+ var rollup = parseInt(durationInSec / chartWidth) + 's';
+
+ /*
+ //var rangeDays = timeUtils.getCurrentRange(dateFrom, dateTo, {format: 'd'});
+ var rollup;
+ if(rangeDays <= 1) {
+ rollup = '15s';
+ } else if(rangeDays <= 7) {
+ rollup = '1h';//rollup = '15m';
+ } else if(rangeDays > 7) {
+ rollup = '1d';
}
- vm.toolType = type;
+ */
+ return rollup;
}
- function updateUser(userData) {
- if(userData.country) {
- _.each(COUNTRY_CODES, function(value, key) {
- if(value === userData.country) {
- /*jshint camelcase: false */
- userData.country_code = key;
- return;
- }
- });
+ function getSensorName(name) {
+
+ var sensorName;
+ // TODO: Improvement check how we set new names
+ if( new RegExp('custom circuit', 'i').test(name) ) {
+ sensorName = name;
} else {
- userData.country_code = null;
+ if(new RegExp('noise', 'i').test(name) ) {
+ sensorName = 'SOUND';
+ } else if(new RegExp('light', 'i').test(name) ) {
+ sensorName = 'LIGHT';
+ } else if((new RegExp('nets', 'i').test(name) ) ||
+ (new RegExp('wifi', 'i').test(name))) {
+ sensorName = 'NETWORKS';
+ } else if(new RegExp('co', 'i').test(name) ) {
+ sensorName = 'CO';
+ } else if(new RegExp('no2', 'i').test(name) ) {
+ sensorName = 'NO2';
+ } else if(new RegExp('humidity', 'i').test(name) ) {
+ sensorName = 'HUMIDITY';
+ } else if(new RegExp('temperature', 'i').test(name) ) {
+ sensorName = 'TEMPERATURE';
+ } else if(new RegExp('panel', 'i').test(name) ) {
+ sensorName = 'SOLAR PANEL';
+ } else if(new RegExp('battery', 'i').test(name) ) {
+ sensorName = 'BATTERY';
+ } else if(new RegExp('barometric pressure', 'i').test(name) ) {
+ sensorName = 'BAROMETRIC PRESSURE';
+ } else if(new RegExp('PM 1', 'i').test(name) ) {
+ sensorName = 'PM 1';
+ } else if(new RegExp('PM 2.5', 'i').test(name) ) {
+ sensorName = 'PM 2.5';
+ } else if(new RegExp('PM 10', 'i').test(name) ) {
+ sensorName = 'PM 10';
+ } else {
+ sensorName = name;
+ }
}
-
- user.updateUser(userData)
- .then(function(data) {
- var user = new AuthUser(data);
- _.extend(vm.user, user);
- auth.updateUser();
- vm.errors = {};
- alert.success('User updated');
- })
- .catch(function(err) {
- alert.error('User could not be updated ');
- vm.errors = err.data.errors;
- });
- }
-
- function removeUser() {
- var confirm = $mdDialog.confirm()
- .title('Delete your account?')
- .textContent('Are you sure you want to delete your account?')
- .ariaLabel('')
- .ok('delete')
- .cancel('cancel')
- .theme('primary')
- .clickOutsideToClose(true);
-
- $mdDialog.show(confirm)
- .then(function(){
- return Restangular.all('').customDELETE('me')
- .then(function(){
- alert.success('Account removed successfully. Redirecting you…');
- $timeout(function(){
- auth.logout();
- $state.transitionTo('landing');
- }, 2000);
- })
- .catch(function(){
- alert.error('Error occurred trying to delete your account.');
- });
- });
+ return sensorName.toUpperCase();
}
- function selectThisTab(iconIndex, uistate){
- /* This looks more like a hack but we need to workout how to properly use md-tab with ui-router */
-
- highlightIcon(iconIndex);
+ function getSensorValue(sensor) {
+ var value = sensor.value;
- if ($state.current.name.includes('myProfileAdmin')){
- var transitionState = 'layout.myProfileAdmin.' + uistate;
- $state.transitionTo(transitionState, {id: userData.id});
+ if(isNaN(parseInt(value))) {
+ value = 'NA';
} else {
- var transitionState = 'layout.myProfile.' + uistate;
- $state.transitionTo(transitionState);
+ value = round(value, 1).toString();
}
+ return value;
}
- function startingTab() {
- /* This looks more like a hack but we need to workout how to properly use md-tab with ui-router */
+ function round(value, precision) {
+ var multiplier = Math.pow(10, precision || 0);
+ return Math.round(value * multiplier) / multiplier;
+ }
- var childState = $state.current.name.split('.').pop();
+ function getSensorPrevValue(sensor) {
+ /*jshint camelcase: false */
+ var prevValue = sensor.prev_value;
+ return (prevValue && prevValue.toString() ) || 0;
+ }
- switch(childState) {
- case 'user':
- vm.startingTab = 1;
- break;
- default:
- vm.startingTab = 0;
- break;
- }
+ function getSensorIcon(sensorName) {
- }
+ var thisName = getSensorName(sensorName);
- function highlightIcon(iconIndex) {
+ switch(thisName) {
+ case 'TEMPERATURE':
+ return './assets/images/temperature_icon_new.svg';
- var icons = angular.element('.myProfile_tab_icon');
+ case 'HUMIDITY':
+ return './assets/images/humidity_icon_new.svg';
- _.each(icons, function(icon) {
- unhighlightIcon(icon);
- });
+ case 'LIGHT':
+ return './assets/images/light_icon_new.svg';
- var icon = icons[iconIndex];
+ case 'SOUND':
+ return './assets/images/sound_icon_new.svg';
- angular.element(icon).find('.stroke_container').css({'stroke': 'white', 'stroke-width': '0.01px'});
- angular.element(icon).find('.fill_container').css('fill', 'white');
- }
+ case 'CO':
+ return './assets/images/co_icon_new.svg';
- function unhighlightIcon(icon) {
- icon = angular.element(icon);
+ case 'NO2':
+ return './assets/images/no2_icon_new.svg';
- icon.find('.stroke_container').css({'stroke': 'none'});
- icon.find('.fill_container').css('fill', '#FF8600');
- }
+ case 'NETWORKS':
+ return './assets/images/networks_icon.svg';
- function setSidebarMinHeight() {
- var height = document.body.clientHeight / 4 * 3;
- angular.element('.profile_content').css('min-height', height + 'px');
- }
+ case 'BATTERY':
+ return './assets/images/battery_icon.svg';
- function getCountries(searchText) {
- return _.filter(COUNTRY_CODES, createFilter(searchText));
- }
+ case 'SOLAR PANEL':
+ return './assets/images/solar_panel_icon.svg';
- function createFilter(searchText) {
- searchText = searchText.toLowerCase();
- return function(country) {
- country = country.toLowerCase();
- return country.indexOf(searchText) !== -1;
- };
- }
+ case 'BAROMETRIC PRESSURE':
+ return './assets/images/pressure_icon_new.svg';
- function uploadAvatar(fileData) {
- if(fileData && fileData.length) {
+ case 'PM 1':
+ case 'PM 2.5':
+ case 'PM 10':
+ return './assets/images/particle_icon_new.svg';
- // TODO: Improvement Is there a simpler way to patch the image to the API and use the response?
- // Something like:
- //Restangular.all('/me').patch(data);
- // Instead of doing it manually like here:
- var fd = new FormData();
- fd.append('profile_picture', fileData[0]);
- Restangular.one('/me')
- .withHttpConfig({transformRequest: angular.identity})
- .customPATCH(fd, '', undefined, {'Content-Type': undefined})
- .then(function(resp){
- vm.user.profile_picture = resp.profile_picture;
- })
+ default:
+ return './assets/images/unknownsensor_icon.svg';
}
}
- function copyUserToForm(formData, userData) {
- var props = {username: true, email: true, city: true, country: true, country_code: true, url: true, constructor: false};
+ function getSensorArrow(currentValue, prevValue) {
+ currentValue = parseInt(currentValue) || 0;
+ prevValue = parseInt(prevValue) || 0;
- for(var key in userData) {
- if(props[key]) {
- formData[key] = userData[key];
- }
+ if(currentValue > prevValue) {
+ return 'arrow_up';
+ } else if(currentValue < prevValue) {
+ return 'arrow_down';
+ } else {
+ return 'equal';
}
}
- function mapWithBelongstoUser(devices){
- _.map(devices, addBelongProperty);
- }
+ function getSensorColor(sensorName) {
+ switch(getSensorName(sensorName)) {
+ case 'TEMPERATURE':
+ return '#FF3D4C';
- function addBelongProperty(device){
- device.belongProperty = deviceBelongsToUser(device);
- return device;
- }
+ case 'HUMIDITY':
+ return '#55C4F5';
+ case 'LIGHT':
+ return '#ffc107';
- function deviceBelongsToUser(device){
- if(!auth.isAuth() || !device || !device.id) {
- return false;
- }
- var deviceID = parseInt(device.id);
- var userData = ( auth.getCurrentUser().data ) ||
- ($window.localStorage.getItem('smartcitizen.data') &&
- new AuthUser( JSON.parse(
- $window.localStorage.getItem('smartcitizen.data') )));
+ case 'SOUND':
+ return '#0019FF';
- var belongsToUser = deviceUtils.belongsToUser(userData.devices, deviceID);
- var isAdmin = userUtils.isAdmin(userData);
+ case 'CO':
+ return '#00A103';
- return isAdmin || belongsToUser;
- }
+ case 'NO2':
+ return '#8cc252';
- function downloadData(device){
- $mdDialog.show({
- hasBackdrop: true,
- controller: 'DownloadModalController',
- controllerAs: 'vm',
- templateUrl: 'app/components/download/downloadModal.html',
- clickOutsideToClose: true,
- locals: {thisDevice:device}
- }).then(function(){
- var alert = $mdDialog.alert()
- .title('SUCCESS')
- .textContent('We are processing your data. Soon you will be notified in your inbox')
- .ariaLabel('')
- .ok('OK!')
- .theme('primary')
- .clickOutsideToClose(true);
+ case 'NETWORKS':
+ return '#681EBD';
- $mdDialog.show(alert);
- }).catch(function(err){
- if (!err){
- return;
- }
- var errorAlert = $mdDialog.alert()
- .title('ERROR')
- .textContent('Uh-oh, something went wrong')
- .ariaLabel('')
- .ok('D\'oh')
- .theme('primary')
- .clickOutsideToClose(false);
-
- $mdDialog.show(errorAlert);
- });
- }
-
- function removeDevice(deviceID) {
- var confirm = $mdDialog.confirm()
- .title('Delete this kit?')
- .textContent('Are you sure you want to delete this kit?')
- .ariaLabel('')
- .ok('DELETE')
- .cancel('Cancel')
- .theme('primary')
- .clickOutsideToClose(true);
-
- $mdDialog
- .show(confirm)
- .then(function(){
- device
- .removeDevice(deviceID)
- .then(function(){
- alert.success('Your kit was deleted successfully');
- device.updateContext();
- })
- .catch(function(){
- alert.error('Error trying to delete your kit.');
- });
- });
- }
+ case 'SOLAR PANEL':
+ return '#d555ce';
- $scope.addDeviceSelector = addDeviceSelector;
- function addDeviceSelector(){
- $mdDialog.show({
- templateUrl: 'app/components/myProfile/addDeviceSelectorModal.html',
- clickOutsideToClose: true,
- multiple: true,
- controller: DialogController,
- });
- }
+ case 'BATTERY':
+ return '#ff8601';
- function DialogController($scope, $mdDialog){
- $scope.cancel = function(){
- $mdDialog.cancel();
- };
+ default:
+ return '#0019FF';
+ }
}
- function addNewDevice() {
- var confirm = $mdDialog.confirm()
- .title('Hey! Do you want to add a new kit?')
- .textContent('Please, notice this currently supports just the SCK 1.0 and SCK 1.1')
- .ariaLabel('')
- .ok('Ok')
- .cancel('Cancel')
- .theme('primary')
- .clickOutsideToClose(true);
-
- $mdDialog
- .show(confirm)
- .then(function(){
- $state.go('layout.kitAdd');
- });
+ function getSensorDescription(sensorID, sensorTypes) {
+ return _(sensorTypes)
+ .chain()
+ .find(function(sensorType) {
+ return sensorType.id === sensorID;
+ })
+ .value()
+ .measurement.description;
}
-
-
}
})();
@@ -5686,772 +5166,1018 @@ angular.module('app.components')
'use strict';
angular.module('app.components')
- .controller('MapTagModalController', MapTagModalController);
-
- MapTagModalController.$inject = ['$mdDialog', 'tag', 'selectedTags'];
+ .factory('searchUtils', searchUtils);
- function MapTagModalController($mdDialog, tag, selectedTags) {
- var vm = this;
+ searchUtils.$inject = [];
+ function searchUtils() {
+ var service = {
+ parseLocation: parseLocation,
+ parseName: parseName,
+ parseIcon: parseIcon,
+ parseIconType: parseIconType
+ };
+ return service;
- vm.checks = {};
+ /////////////////
- vm.answer = answer;
- vm.hide = hide;
- vm.clear = clear;
- vm.cancel = cancel;
- vm.tags = [];
+ function parseLocation(object) {
+ var location = '';
- init();
+ if(!!object.city) {
+ location += object.city;
+ }
+ if(!!object.city && !!object.country) {
+ location += ', ';
+ }
+ if(!!object.country) {
+ location += object.country;
+ }
- ////////////////////////////////////////////////////////
+ return location;
+ }
- function init() {
- tag.getTags()
- .then(function(tags) {
- vm.tags = tags;
+ function parseName(object) {
+ var name = object.type === 'User' ? object.username : object.name;
+ return name;
+ }
- _.forEach(selectedTags, select);
+ function parseIcon(object, type) {
+ switch(type) {
+ case 'User':
+ return object.profile_picture;
+ case 'Device':
+ return 'assets/images/kit.svg';
+ case 'Country':
+ case 'City':
+ return 'assets/images/location_icon_normal.svg';
+ }
+ }
- });
+ function parseIconType(type) {
+ switch(type) {
+ case 'Device':
+ return 'div';
+ default:
+ return 'img';
+ }
+ }
}
+})();
- function answer() {
+(function() {
+ 'use strict';
- var selectedTags = _(vm.tags)
- .filter(isTagSelected)
- .value();
- $mdDialog.hide(selectedTags);
- }
+ angular.module('app.components')
+ .factory('markerUtils', markerUtils);
- function hide() {
- answer();
- }
+ markerUtils.$inject = ['deviceUtils', 'MARKER_ICONS'];
+ function markerUtils(deviceUtils, MARKER_ICONS) {
+ var service = {
+ getIcon: getIcon,
+ getMarkerIcon: getMarkerIcon,
+ };
+ _.defaults(service, deviceUtils);
+ return service;
- function clear() {
- $mdDialog.hide(null);
- }
+ ///////////////
- function cancel() {
- answer();
- }
+ function getIcon(object) {
+ var icon;
+ var labels = deviceUtils.parseSystemTags(object);
+ var isSCKHardware = deviceUtils.isSCKHardware(object);
- function isTagSelected(tag) {
- return vm.checks[tag.name];
- }
+ if(hasLabel(labels, 'offline')) {
+ icon = MARKER_ICONS.markerSmartCitizenOffline;
+ } else if (isSCKHardware) {
+ icon = MARKER_ICONS.markerSmartCitizenOnline;
+ } else {
+ icon = MARKER_ICONS.markerExperimentalNormal;
+ }
+ return icon;
+ }
- function select(tag){
- vm.checks[tag] = true;
+ function hasLabel(labels, targetLabel) {
+ return _.some(labels, function(label) {
+ return label === targetLabel;
+ });
+ }
+
+ function getMarkerIcon(marker, state) {
+ var markerType = marker.icon.className;
+
+ if(state === 'active') {
+ marker.icon = MARKER_ICONS[markerType + 'Active'];
+ marker.focus = true;
+ } else if(state === 'inactive') {
+ var targetClass = markerType.split(' ')[0];
+ marker.icon = MARKER_ICONS[targetClass];
+ }
+ return marker;
+ }
}
- }
})();
(function() {
'use strict';
angular.module('app.components')
- .controller('MapFilterModalController', MapFilterModalController);
+ .factory('mapUtils', mapUtils);
- MapFilterModalController.$inject = ['$mdDialog','selectedFilters', '$timeout'];
+ mapUtils.$inject = [];
+ function mapUtils() {
+ var service = {
+ getDefaultFilters: getDefaultFilters,
+ setDefaultFilters: setDefaultFilters,
+ canFilterBeRemoved: canFilterBeRemoved
+ };
+ return service;
- function MapFilterModalController($mdDialog, selectedFilters, $timeout) {
+ //////////////
- var vm = this;
+ function getDefaultFilters(filterData, defaultFilters) {
+ var obj = {};
+ if(!filterData.indoor && !filterData.outdoor) {
+ obj[defaultFilters.exposure] = true;
+ }
+ if(!filterData.online && !filterData.offline) {
+ obj[defaultFilters.status] = true;
+ }
+ return obj;
+ }
- vm.checks = {};
+ function setDefaultFilters(filterData) {
+ var obj = {};
+ if(!filterData.indoor || !filterData.outdoor) {
+ obj.exposure = filterData.indoor ? 'indoor' : 'outdoor';
+ }
+ if(!filterData.online || !filterData.offline) {
+ obj.status = filterData.online ? 'online' : 'offline';
+ }
+ return obj;
+ }
- vm.answer = answer;
- vm.hide = hide;
- vm.clear = clear;
- vm.cancel = cancel;
- vm.toggle = toggle;
-
- vm.location = ['indoor', 'outdoor'];
- vm.status = ['online', 'offline'];
- vm.new = ['new'];
-
- vm.filters = [];
-
- init();
-
- ////////////////////////////////////////////////////////
-
- function init() {
- _.forEach(selectedFilters, select);
- }
-
- function answer() {
- vm.filters = vm.filters.concat(vm.location, vm.status, vm.new);
- var selectedFilters = _(vm.filters)
- .filter(isFilterSelected)
- .value();
- $mdDialog.hide(selectedFilters);
- }
-
- function hide() {
- answer();
- }
-
- function clear() {
- vm.filters = vm.filters.concat(vm.location, vm.status, vm.new);
- $mdDialog.hide(vm.filters);
- }
-
- function cancel() {
- answer();
- }
-
- function isFilterSelected(filter) {
- return vm.checks[filter];
- }
-
- function toggle(filters) {
- $timeout(function() {
-
- for (var i = 0; i < filters.length - 1; i++) {
- if (vm.checks[filters[i]] === false && vm.checks[filters[i]] === vm.checks[filters[i+1]]) {
- for (var n = 0; n < filters.length; n++) {
- vm.checks[filters[n]] = true;
- }
- }
+ function canFilterBeRemoved(filterData, filterName) {
+ if(filterName === 'indoor' || filterName === 'outdoor') {
+ return filterData.indoor && filterData.outdoor;
+ } else if(filterName === 'online' || filterName === 'offline') {
+ return filterData.online && filterData.offline;
}
-
- });
+ }
}
+})();
- function select(filter){
- vm.checks[filter] = true;
- }
- }
+(function() {
+ 'use strict';
+ angular.module('app.components')
+ .config(function ($provide) {
+ $provide.decorator('$exceptionHandler', ['$delegate', function($delegate) {
+ return function (exception, cause) {
+ /*jshint camelcase: false */
+ $delegate(exception, cause);
+ };
+ }]);
+
+ });
})();
(function() {
'use strict';
angular.module('app.components')
- .controller('MapController', MapController);
+ .factory('deviceUtils', deviceUtils);
- MapController.$inject = ['$scope', '$state', '$stateParams', '$timeout', 'device',
- '$mdDialog', 'leafletData', 'alert',
- 'Marker', 'tag', 'animation', '$q'];
- function MapController($scope, $state, $stateParams, $timeout, device,
- $mdDialog, leafletData, alert, Marker, tag, animation, $q) {
- var vm = this;
- var updateType;
- var focusedMarkerID;
+ deviceUtils.$inject = ['COUNTRY_CODES', 'device'];
+ function deviceUtils(COUNTRY_CODES, device) {
+ var service = {
+ parseLocation: parseLocation,
+ parseCoordinates: parseCoordinates,
+ parseSystemTags: parseSystemTags,
+ parseUserTags: parseUserTags,
+ classify: classify,
+ parseNotifications: parseNotifications,
+ parseOwner: parseOwner,
+ parseName: parseName,
+ parseString: parseString,
+ parseHardware: parseHardware,
+ parseHardwareInfo: parseHardwareInfo,
+ parseHardwareName: parseHardwareName,
+ isPrivate: isPrivate,
+ preciseLocation: preciseLocation,
+ enableForwarding: enableForwarding,
+ isLegacyVersion: isLegacyVersion,
+ isSCKHardware: isSCKHardware,
+ parseState: parseState,
+ parseAvatar: parseAvatar,
+ belongsToUser: belongsToUser,
+ parseSensorTime: parseSensorTime
+ };
- vm.markers = [];
+ return service;
- var retinaSuffix = isRetina() ? '512' : '256';
- var retinaLegacySuffix = isRetina() ? '@2x' : '';
+ ///////////////
- var mapBoxToken = 'pk.eyJ1IjoidG9tYXNkaWV6IiwiYSI6ImRTd01HSGsifQ.loQdtLNQ8GJkJl2LUzzxVg';
+ function parseLocation(object) {
+ var location = '';
+ var city = '';
+ var country = '';
- vm.layers = {
- baselayers: {
- osm: {
- name: 'OpenStreetMap',
- type: 'xyz',
- url: 'https://api.mapbox.com/styles/v1/mapbox/streets-v10/tiles/' + retinaSuffix + '/{z}/{x}/{y}?access_token=' + mapBoxToken
- },
- legacy: {
- name: 'Legacy',
- type: 'xyz',
- url: 'https://api.tiles.mapbox.com/v4/mapbox.streets-basic/{z}/{x}/{y}'+ retinaLegacySuffix +'.png' + '?access_token=' + mapBoxToken
- },
- sat: {
- name: 'Satellite',
- type: 'xyz',
- url: 'https://api.mapbox.com/styles/v1/mapbox/satellite-streets-v10/tiles/' + retinaSuffix + '/{z}/{x}/{y}?access_token=' + mapBoxToken
+ if (object.location) {
+ city = object.location.city;
+ country = object.location.country;
+ if(!!city) {
+ location += city;
}
- },
- overlays: {
- devices: {
- name: 'Devices',
- type: 'markercluster',
- visible: true,
- layerOptions: {
- showCoverageOnHover: false
- }
+ if(!!city && !!location) {
+ location += ', '
+ }
+ if(!!country) {
+ location += country;
}
}
- };
-
- vm.center = {
- lat: $stateParams.lat ? parseInt($stateParams.lat, 10) : 13.14950321154457,
- lng: $stateParams.lng ? parseInt($stateParams.lng, 10) : -1.58203125,
- zoom: $stateParams.zoom ? parseInt($stateParams.zoom, 10) : 2
- };
-
- vm.defaults = {
- dragging: true,
- touchZoom: true,
- scrollWheelZoom: true,
- doubleClickZoom: true,
- minZoom:2,
- worldCopyJump: true
- };
+ return location;
+ }
- vm.events = {
- map: {
- enable: ['dragend', 'zoomend', 'moveend', 'popupopen', 'popupclose',
- 'mousedown', 'dblclick', 'click', 'touchstart', 'mouseup'],
- logic: 'broadcast'
+ function parseCoordinates(object) {
+ if (object.location) {
+ return {
+ lat: object.location.latitude,
+ lng: object.location.longitude
+ };
}
- };
+ // TODO: Bug - what happens if no location?
+ }
- $scope.$on('leafletDirectiveMarker.click', function(event, data) {
- var id = undefined;
- var currentMarker = vm.markers[data.modelName];
+ function parseSystemTags(object) {
+ /*jshint camelcase: false */
+ return object.system_tags;
+ }
- if(currentMarker) {
- id = currentMarker.myData.id;
+ function parseUserTags(object) {
+ return object.user_tags;
+ }
+
+ function parseNotifications(object){
+ return {
+ lowBattery: object.notify.low_battery,
+ stopPublishing: object.notify.stopped_publishing
}
+ }
- vm.deviceLoading = true;
- vm.center.lat = data.leafletEvent.latlng.lat;
- vm.center.lng = data.leafletEvent.latlng.lng;
+ function classify(kitType) {
+ if(!kitType) {
+ return '';
+ }
+ return kitType.toLowerCase().split(' ').join('_');
+ }
- if(id === parseInt($state.params.id)) {
- $timeout(function() {
- vm.deviceLoading = false;
- });
+ function parseName(object, trim=false) {
+ if(!object.name) {
return;
}
-
- updateType = 'map';
-
- if ($state.$current.name === 'embbed') { return; }
- $state.go('layout.home.kit', {id: id});
-
- // angular.element('section.map').scope().$broadcast('resizeMapHeight');
- });
-
-
- $scope.$on('leafletDirectiveMarker.popupclose', function() {
- if(focusedMarkerID) {
- var marker = vm.markers[focusedMarkerID];
- if(marker) {
- vm.markers[focusedMarkerID].focus = false;
- }
+ if (trim) {
+ return object.name.length <= 41 ? object.name : object.name.slice(0, 35).concat(' ... ');
}
- });
-
- vm.readyForDevice = {
- device: false,
- map: false
- };
-
- $scope.$on('deviceLoaded', function(event, data) {
- vm.readyForDevice.device = data;
- });
+ return object.name;
+ }
- $scope.$watch('vm.readyForDevice', function() {
- if (vm.readyForDevice.device && vm.readyForDevice.map) {
- zoomDeviceAndPopUp(vm.readyForDevice.device);
+ function parseHardware(object) {
+ if (!object.hardware) {
+ return;
}
- }, true);
- $scope.$on('goToLocation', function(event, data) {
- goToLocation(data);
- });
-
- vm.filters = ['indoor', 'outdoor', 'online', 'offline'];
-
- vm.openFilterPopup = openFilterPopup;
- vm.openTagPopup = openTagPopup;
- vm.removeFilter = removeFilter;
- vm.removeTag = removeTag;
- vm.selectedTags = tag.getSelectedTags();
- vm.selectedFilters = ['indoor', 'outdoor', 'online', 'offline', 'new'];
-
- vm.checkAllFiltersSelected = checkAllFiltersSelected;
-
- initialize();
+ return {
+ name: parseString(object.hardware.name),
+ type: parseString(object.hardware.type),
+ description: parseString(object.hardware.description),
+ version: parseVersionString(object.hardware.version),
+ slug: object.hardware.slug,
+ info: parseHardwareInfo(object.hardware.info)
+ }
+ }
- /////////////////////
+ function parseString(str) {
+ if (typeof(str) !== 'string') { return null; }
+ return str;
+ }
- function initialize() {
+ function parseVersionString (str) {
+ if (typeof(str) !== 'string') { return null; }
+ var x = str.split('.');
+ // parse from string or default to 0 if can't parse
+ var maj = parseInt(x[0]) || 0;
+ var min = parseInt(x[1]) || 0;
+ var pat = parseInt(x[2]) || 0;
+ return {
+ major: maj,
+ minor: min,
+ patch: pat
+ };
+ }
- vm.readyForDevice.map = false;
+ function parseHardwareInfo (object) {
+ if (!object) { return null; } // null
+ if (typeof(object) == 'string') { return null; } // FILTERED
- $q.all([device.getAllDevices($stateParams.reloadMap)])
- .then(function(data){
+ var id = parseString(object.id);
+ var mac = parseString(object.mac);
+ var time = Date(object.time);
+ var esp_bd = parseString(object.esp_bd);
+ var hw_ver = parseString(object.hw_ver);
+ var sam_bd = parseString(object.sam_bd);
+ var esp_ver = parseString(object.esp_ver);
+ var sam_ver = parseString(object.sam_ver);
- data = data[0];
+ return {
+ id: id,
+ mac: mac,
+ time: time,
+ esp_bd: esp_bd,
+ hw_ver: hw_ver,
+ sam_bd: sam_bd,
+ esp_ver: esp_ver,
+ sam_ver: sam_ver
+ };
+ }
- vm.markers = _.chain(data)
- .map(function(device) {
- return new Marker(device);
- })
- .filter(function(marker) {
- return !!marker.lng && !!marker.lat;
- })
- .tap(function(data) {
- device.setWorldMarkers(data);
- })
- .value();
+ function parseHardwareName(object) {
+ if (object.hasOwnProperty('hardware')) {
+ if (!object.hardware.name) {
+ return 'Unknown hardware'
+ }
+ return object.hardware.name;
+ } else {
+ return 'Unknown hardware'
+ }
+ }
- var markersByIndex = _.keyBy(vm.markers, function(marker) {
- return marker.myData.id;
- });
+ function isPrivate(object) {
+ return object.data_policy.is_private;
+ }
- if($state.params.id && markersByIndex[parseInt($state.params.id)]){
- focusedMarkerID = markersByIndex[parseInt($state.params.id)]
- .myData.id;
- vm.readyForDevice.map = true;
- } else {
- updateMarkers();
- vm.readyForDevice.map = true;
- }
+ function preciseLocation(object) {
+ return object.data_policy.precise_location;
+ }
- });
+ function enableForwarding(object) {
+ return object.data_policy.enable_forwarding ;
}
- function zoomDeviceAndPopUp(data){
+ function isLegacyVersion (object) {
+ if (!object.hardware || !object.hardware.version || object.hardware.version.major > 1) {
+ return false;
+ } else {
+ if (object.hardware.version.major == 1 && object.hardware.version.minor <5 ){
+ return true;
+ }
+ return false;
+ }
+ }
- if(updateType === 'map') {
- vm.deviceLoading = false;
- updateType = undefined;
- return;
+ function isSCKHardware (object){
+ if (!object.hardware || !object.hardware.type || object.hardware.type != 'SCK') {
+ return false;
} else {
- vm.deviceLoading = true;
+ return true;
}
+ }
- leafletData.getMarkers()
- .then(function(markers) {
- var currentMarker = _.find(markers, function(marker) {
- return data.id === marker.options.myData.id;
- });
+ function parseOwner(object) {
+ return {
+ id: object.owner.id,
+ username: object.owner.username,
+ /*jshint camelcase: false */
+ devices: object.owner.device_ids,
+ city: object.owner.location.city,
+ country: COUNTRY_CODES[object.owner.location.country_code],
+ url: object.owner.url,
+ profile_picture: object.owner.profile_picture
+ };
+ }
- var id = data.id;
+ function parseState(status) {
+ var name = parseStateName(status);
+ var className = classify(name);
- leafletData.getLayers()
- .then(function(layers) {
- if(currentMarker){
- layers.overlays.devices.zoomToShowLayer(currentMarker,
- function() {
- var selectedMarker = currentMarker;
- if(selectedMarker) {
- // Ensures the marker is not just zoomed but the marker is centered to improve UX
- // The $timeout can be replaced by an event but tests didn't show good results
- $timeout(function() {
- vm.center.lat = selectedMarker.options.lat;
- vm.center.lng = selectedMarker.options.lng;
- selectedMarker.openPopup();
- vm.deviceLoading = false;
- }, 1000);
- }
- });
- } else {
- leafletData.getMap().then(function(map){
- map.closePopup();
- });
- }
- });
- });
+ return {
+ name: name,
+ className: className
+ };
+ }
+ function parseStateName(object) {
+ return object.state.replace('_', ' ');
}
- function checkAllFiltersSelected() {
- var allFiltersSelected = _.every(vm.filters, function(filterValue) {
- return _.includes(vm.selectedFilters, filterValue);
- });
- return allFiltersSelected;
+ function parseAvatar() {
+ return './assets/images/sckit_avatar.jpg';
}
- function openFilterPopup() {
- $mdDialog.show({
- hasBackdrop: true,
- controller: 'MapFilterModalController',
- controllerAs: 'vm',
- templateUrl: 'app/components/map/mapFilterModal.html',
- clickOutsideToClose: true,
- locals: {
- selectedFilters: vm.selectedFilters
- }
- })
- .then(function(selectedFilters) {
- updateType = 'map';
- vm.selectedFilters = selectedFilters;
- updateMapFilters();
- });
+ function parseSensorTime(sensor) {
+ /*jshint camelcase: false */
+ return moment(sensor.recorded_at).format('');
}
- function openTagPopup() {
- $mdDialog.show({
- hasBackdrop: true,
- controller: 'MapTagModalController',
- controllerAs: 'vm',
- templateUrl: 'app/components/map/mapTagModal.html',
- //targetEvent: ev,
- clickOutsideToClose: true,
- locals: {
- selectedTags: vm.selectedTags
- }
- })
- .then(function(selectedTags) {
- if (selectedTags && selectedTags.length > 0) {
- updateType = 'map';
- tag.setSelectedTags(_.map(selectedTags, 'name'));
- vm.selectedTags = tag.getSelectedTags();
- reloadWithTags();
- } else if (selectedTags === null) {
- reloadNoTags();
- }
+ function belongsToUser(devicesArray, deviceID) {
+ return _.some(devicesArray, function(device) {
+ return device.id === deviceID;
});
}
+ }
+})();
- function updateMapFilters(){
- vm.selectedTags = tag.getSelectedTags();
- checkAllFiltersSelected();
- updateMarkers();
- }
+(function() {
+ 'use strict';
- function removeFilter(filterName) {
- vm.selectedFilters = _.filter(vm.selectedFilters, function(el){
- return el !== filterName;
- });
- if(vm.selectedFilters.length === 0){
- vm.selectedFilters = vm.filters;
- }
- updateMarkers();
- }
+ angular.module('app.components')
+ .filter('filterLabel', filterLabel);
- function filterMarkersByLabel(tmpMarkers) {
- return tmpMarkers.filter(function(marker) {
- var labels = marker.myData.labels;
- if (labels.length === 0 && vm.selectedFilters.length !== 0){
- return false;
- }
- return _.every(labels, function(label) {
- return _.includes(vm.selectedFilters, label);
- });
- });
- }
-
- function updateMarkers() {
- $timeout(function() {
- $scope.$apply(function() {
- var allMarkers = device.getWorldMarkers();
-
- var updatedMarkers = allMarkers;
- updatedMarkers = tag.filterMarkersByTag(updatedMarkers);
- updatedMarkers = filterMarkersByLabel(updatedMarkers);
- vm.markers = updatedMarkers;
-
- animation.mapStateLoaded();
-
- vm.deviceLoading = false;
-
- zoomOnMarkers();
+ function filterLabel() {
+ return function(devices, targetLabel) {
+ if(targetLabel === undefined) {
+ return devices;
+ }
+ if(devices) {
+ return _.filter(devices, function(device) {
+ var containsLabel = device.systemTags.indexOf(targetLabel) !== -1;
+ if(containsLabel) {
+ return containsLabel;
+ }
+ // This should be fixed or polished in the future
+ // var containsNewIfTargetIsOnline = targetLabel === 'online' && _.some(kit.labels, function(label) {return label.indexOf('new') !== -1;});
+ // return containsNewIfTargetIsOnline;
});
- });
- }
-
- function getZoomLevel(data) {
- // data.layer is an array of strings like ["establishment", "point_of_interest"]
- var zoom = 18;
-
- if(data.layer && data.layer[0]) {
- switch(data.layer[0]) {
- case 'point_of_interest':
- zoom = 18;
- break;
- case 'address':
- zoom = 18;
- break;
- case "establishment":
- zoom = 15;
- break;
- case 'neighbourhood':
- zoom = 13;
- break;
- case 'locality':
- zoom = 13;
- break;
- case 'localadmin':
- zoom = 9;
- break;
- case 'county':
- zoom = 9;
- break;
- case 'region':
- zoom = 8;
- break;
- case 'country':
- zoom = 7;
- break;
- case 'coarse':
- zoom = 7;
- break;
- }
}
+ };
+ }
+})();
- return zoom;
- }
-
- function isRetina(){
- return ((window.matchMedia &&
- (window.matchMedia('only screen and (min-resolution: 192dpi), ' +
- 'only screen and (min-resolution: 2dppx), only screen and ' +
- '(min-resolution: 75.6dpcm)').matches ||
- window.matchMedia('only screen and (-webkit-min-device-pixel-ra' +
- 'tio: 2), only screen and (-o-min-device-pixel-ratio: 2/1), only' +
- ' screen and (min--moz-device-pixel-ratio: 2), only screen and ' +
- '(min-device-pixel-ratio: 2)').matches)) ||
- (window.devicePixelRatio && window.devicePixelRatio >= 2)) &&
- /(iPad|iPhone|iPod|Apple)/g.test(navigator.userAgent);
- }
+(function() {
+ 'use strict';
- function goToLocation(data){
- // This ensures the action runs after the event is registered
- $timeout(function() {
- vm.center.lat = data.lat;
- vm.center.lng = data.lng;
- vm.center.zoom = getZoomLevel(data);
- });
- }
+ /**
+ * Tools links for user profile
+ * @constant
+ * @type {Array}
+ */
- function removeTag(tagName){
- tag.setSelectedTags(_.filter(vm.selectedTags, function(el){
- return el !== tagName;
- }));
+ angular.module('app.components')
+ .constant('PROFILE_TOOLS', [{
+ type: 'documentation',
+ title: 'How to connect your Smart Citizen Kit tutorial',
+ description: 'Adding a Smart Citizen Kit tutorial',
+ avatar: '',
+ href: 'http://docs.smartcitizen.me/#/start/adding-a-smart-citizen-kit'
+ }, {
+ type: 'documentation',
+ title: 'Download the latest Smart Citizen Kit Firmware',
+ description: 'The latest Arduino firmware for your kit',
+ avatar: '',
+ href: 'https://github.com/fablabbcn/Smart-Citizen-Kit/releases/latest'
+ }, {
+ type: 'documentation',
+ title: 'API Documentation',
+ description: 'Documentation for the new API',
+ avatar: '',
+ href: 'http://developer.smartcitizen.me/'
+ }, {
+ type: 'community',
+ title: 'Smart Citizen Forum',
+ description: 'Join the community discussion. Your feedback is important for us.',
+ avatar: '',
+ href:'http://forum.smartcitizen.me/'
+ }, {
+ type: 'documentation',
+ title: 'Smart Citizen Kit hardware details',
+ description: 'Visit the docs',
+ avatar: 'https://docs.smartcitizen.me/#/start/hardware'
+ }, {
+ type: 'documentation',
+ title: 'Style Guide',
+ description: 'Guidelines of the Smart Citizen UI',
+ avatar: '',
+ href: '/styleguide'
+ }, {
+ type: 'social',
+ title: 'Like us on Facebook',
+ description: 'Join the community on Facebook',
+ avatar: '',
+ href: 'https://www.facebook.com/smartcitizenBCN'
+ }, {
+ type: 'social',
+ title: 'Follow us on Twitter',
+ description: 'Follow our news on Twitter',
+ avatar: '',
+ href: 'https://twitter.com/SmartCitizenKit'
+ }]);
+})();
- vm.selectedTags = tag.getSelectedTags();
+(function() {
+ 'use strict';
- if(vm.selectedTags.length === 0){
- reloadNoTags();
- } else {
- reloadWithTags();
- }
+ /**
+ * Marker icons
+ * @constant
+ * @type {Object}
+ */
+ angular.module('app.components')
+ .constant('MARKER_ICONS', {
+ defaultIcon: {},
+ markerSmartCitizenNormal: {
+ type: 'div',
+ className: 'markerSmartCitizenNormal',
+ iconSize: [24, 24]
+ },
+ markerExperimentalNormal: {
+ type: 'div',
+ className: 'markerExperimentalNormal',
+ iconSize: [24, 24]
+ },
+ markerSmartCitizenOnline: {
+ type: 'div',
+ className: 'markerSmartCitizenOnline',
+ iconSize: [24, 24]
+ },
+ markerSmartCitizenOnlineActive: {
+ type: 'div',
+ className: 'markerSmartCitizenOnline marker_blink',
+ iconSize: [24, 24]
+ },
+ markerSmartCitizenOffline: {
+ type: 'div',
+ className: 'markerSmartCitizenOffline',
+ iconSize: [24, 24]
+ },
+ markerSmartCitizenOfflineActive: {
+ type: 'div',
+ className: 'markerSmartCitizenOffline marker_blink',
+ iconSize: [24, 24]
}
+ });
+})();
- function zoomOnMarkers(){
- $timeout(function() {
- if(vm.markers && vm.markers.length > 0) {
- leafletData.getMap().then(function(map){
- var bounds = L.latLngBounds(vm.markers);
- map.fitBounds(bounds);
- });
- } else {
- alert.error('No markers found with those filters', 5000);
- }
- });
- }
+(function() {
+ 'use strict';
- function reloadWithTags(){
- $state.transitionTo('layout.home.tags', {tags: vm.selectedTags}, {reload: true});
- }
+ /**
+ * Dropdown options for user
+ * @constant
+ * @type {Array}
+ */
+ angular.module('app.components')
+ .constant('DROPDOWN_OPTIONS_USER', [
+ {divider: true, text: 'Hi,', href: './profile'},
+ {text: 'My profile', href: './profile'},
+ {text: 'Log out', href: './logout'}
+ ]);
+})();
- function reloadNoTags(){
- $state.transitionTo('layout.home.kit');
- }
+(function() {
+ 'use strict';
- }
+ /**
+ * Dropdown options for community button
+ * @constant
+ * @type {Array}
+ */
+
+ angular.module('app.components')
+ .constant('DROPDOWN_OPTIONS_COMMUNITY', [
+ {text: 'About', href: '/about'},
+ {text: 'Forum', href: 'https://forum.smartcitizen.me/'},
+ {text: 'Documentation', href: 'http://docs.smartcitizen.me/'},
+ {text: 'API Reference', href: 'http://developer.smartcitizen.me/'},
+ {text: 'Github', href: 'https://github.com/fablabbcn/Smart-Citizen-Kit'},
+ {text: 'Legal', href: '/policy'}
+ ]);
+})();
+
+(function() {
+ 'use strict';
+ /**
+ * Country codes.
+ * @constant
+ * @type {Object}
+ */
+
+ angular.module('app.components')
+ .constant('COUNTRY_CODES', {
+ 'AF': 'Afghanistan',
+ 'AX': 'Aland Islands',
+ 'AL': 'Albania',
+ 'DZ': 'Algeria',
+ 'AS': 'American Samoa',
+ 'AD': 'Andorra',
+ 'AO': 'Angola',
+ 'AI': 'Anguilla',
+ 'AQ': 'Antarctica',
+ 'AG': 'Antigua And Barbuda',
+ 'AR': 'Argentina',
+ 'AM': 'Armenia',
+ 'AW': 'Aruba',
+ 'AU': 'Australia',
+ 'AT': 'Austria',
+ 'AZ': 'Azerbaijan',
+ 'BS': 'Bahamas',
+ 'BH': 'Bahrain',
+ 'BD': 'Bangladesh',
+ 'BB': 'Barbados',
+ 'BY': 'Belarus',
+ 'BE': 'Belgium',
+ 'BZ': 'Belize',
+ 'BJ': 'Benin',
+ 'BM': 'Bermuda',
+ 'BT': 'Bhutan',
+ 'BO': 'Bolivia',
+ 'BA': 'Bosnia And Herzegovina',
+ 'BW': 'Botswana',
+ 'BV': 'Bouvet Island',
+ 'BR': 'Brazil',
+ 'IO': 'British Indian Ocean Territory',
+ 'BN': 'Brunei Darussalam',
+ 'BG': 'Bulgaria',
+ 'BF': 'Burkina Faso',
+ 'BI': 'Burundi',
+ 'KH': 'Cambodia',
+ 'CM': 'Cameroon',
+ 'CA': 'Canada',
+ 'CV': 'Cape Verde',
+ 'KY': 'Cayman Islands',
+ 'CF': 'Central African Republic',
+ 'TD': 'Chad',
+ 'CL': 'Chile',
+ 'CN': 'China',
+ 'CX': 'Christmas Island',
+ 'CC': 'Cocos (Keeling) Islands',
+ 'CO': 'Colombia',
+ 'KM': 'Comoros',
+ 'CG': 'Congo',
+ 'CD': 'Congo, Democratic Republic',
+ 'CK': 'Cook Islands',
+ 'CR': 'Costa Rica',
+ 'CI': 'Cote D\'Ivoire',
+ 'HR': 'Croatia',
+ 'CU': 'Cuba',
+ 'CY': 'Cyprus',
+ 'CZ': 'Czech Republic',
+ 'DK': 'Denmark',
+ 'DJ': 'Djibouti',
+ 'DM': 'Dominica',
+ 'DO': 'Dominican Republic',
+ 'EC': 'Ecuador',
+ 'EG': 'Egypt',
+ 'SV': 'El Salvador',
+ 'GQ': 'Equatorial Guinea',
+ 'ER': 'Eritrea',
+ 'EE': 'Estonia',
+ 'ET': 'Ethiopia',
+ 'FK': 'Falkland Islands (Malvinas)',
+ 'FO': 'Faroe Islands',
+ 'FJ': 'Fiji',
+ 'FI': 'Finland',
+ 'FR': 'France',
+ 'GF': 'French Guiana',
+ 'PF': 'French Polynesia',
+ 'TF': 'French Southern Territories',
+ 'GA': 'Gabon',
+ 'GM': 'Gambia',
+ 'GE': 'Georgia',
+ 'DE': 'Germany',
+ 'GH': 'Ghana',
+ 'GI': 'Gibraltar',
+ 'GR': 'Greece',
+ 'GL': 'Greenland',
+ 'GD': 'Grenada',
+ 'GP': 'Guadeloupe',
+ 'GU': 'Guam',
+ 'GT': 'Guatemala',
+ 'GG': 'Guernsey',
+ 'GN': 'Guinea',
+ 'GW': 'Guinea-Bissau',
+ 'GY': 'Guyana',
+ 'HT': 'Haiti',
+ 'HM': 'Heard Island & Mcdonald Islands',
+ 'VA': 'Holy See (Vatican City State)',
+ 'HN': 'Honduras',
+ 'HK': 'Hong Kong',
+ 'HU': 'Hungary',
+ 'IS': 'Iceland',
+ 'IN': 'India',
+ 'ID': 'Indonesia',
+ 'IR': 'Iran, Islamic Republic Of',
+ 'IQ': 'Iraq',
+ 'IE': 'Ireland',
+ 'IM': 'Isle Of Man',
+ 'IL': 'Israel',
+ 'IT': 'Italy',
+ 'JM': 'Jamaica',
+ 'JP': 'Japan',
+ 'JE': 'Jersey',
+ 'JO': 'Jordan',
+ 'KZ': 'Kazakhstan',
+ 'KE': 'Kenya',
+ 'KI': 'Kiribati',
+ 'KR': 'Korea',
+ 'KW': 'Kuwait',
+ 'KG': 'Kyrgyzstan',
+ 'LA': 'Lao People\'s Democratic Republic',
+ 'LV': 'Latvia',
+ 'LB': 'Lebanon',
+ 'LS': 'Lesotho',
+ 'LR': 'Liberia',
+ 'LY': 'Libyan Arab Jamahiriya',
+ 'LI': 'Liechtenstein',
+ 'LT': 'Lithuania',
+ 'LU': 'Luxembourg',
+ 'MO': 'Macao',
+ 'MK': 'Macedonia',
+ 'MG': 'Madagascar',
+ 'MW': 'Malawi',
+ 'MY': 'Malaysia',
+ 'MV': 'Maldives',
+ 'ML': 'Mali',
+ 'MT': 'Malta',
+ 'MH': 'Marshall Islands',
+ 'MQ': 'Martinique',
+ 'MR': 'Mauritania',
+ 'MU': 'Mauritius',
+ 'YT': 'Mayotte',
+ 'MX': 'Mexico',
+ 'FM': 'Micronesia, Federated States Of',
+ 'MD': 'Moldova',
+ 'MC': 'Monaco',
+ 'MN': 'Mongolia',
+ 'ME': 'Montenegro',
+ 'MS': 'Montserrat',
+ 'MA': 'Morocco',
+ 'MZ': 'Mozambique',
+ 'MM': 'Myanmar',
+ 'NA': 'Namibia',
+ 'NR': 'Nauru',
+ 'NP': 'Nepal',
+ 'NL': 'Netherlands',
+ 'AN': 'Netherlands Antilles',
+ 'NC': 'New Caledonia',
+ 'NZ': 'New Zealand',
+ 'NI': 'Nicaragua',
+ 'NE': 'Niger',
+ 'NG': 'Nigeria',
+ 'NU': 'Niue',
+ 'NF': 'Norfolk Island',
+ 'MP': 'Northern Mariana Islands',
+ 'NO': 'Norway',
+ 'OM': 'Oman',
+ 'PK': 'Pakistan',
+ 'PW': 'Palau',
+ 'PS': 'Palestinian Territory, Occupied',
+ 'PA': 'Panama',
+ 'PG': 'Papua New Guinea',
+ 'PY': 'Paraguay',
+ 'PE': 'Peru',
+ 'PH': 'Philippines',
+ 'PN': 'Pitcairn',
+ 'PL': 'Poland',
+ 'PT': 'Portugal',
+ 'PR': 'Puerto Rico',
+ 'QA': 'Qatar',
+ 'RE': 'Reunion',
+ 'RO': 'Romania',
+ 'RU': 'Russian Federation',
+ 'RW': 'Rwanda',
+ 'BL': 'Saint Barthelemy',
+ 'SH': 'Saint Helena',
+ 'KN': 'Saint Kitts And Nevis',
+ 'LC': 'Saint Lucia',
+ 'MF': 'Saint Martin',
+ 'PM': 'Saint Pierre And Miquelon',
+ 'VC': 'Saint Vincent And Grenadines',
+ 'WS': 'Samoa',
+ 'SM': 'San Marino',
+ 'ST': 'Sao Tome And Principe',
+ 'SA': 'Saudi Arabia',
+ 'SN': 'Senegal',
+ 'RS': 'Serbia',
+ 'SC': 'Seychelles',
+ 'SL': 'Sierra Leone',
+ 'SG': 'Singapore',
+ 'SK': 'Slovakia',
+ 'SI': 'Slovenia',
+ 'SB': 'Solomon Islands',
+ 'SO': 'Somalia',
+ 'ZA': 'South Africa',
+ 'GS': 'South Georgia And Sandwich Isl.',
+ 'ES': 'Spain',
+ 'LK': 'Sri Lanka',
+ 'SD': 'Sudan',
+ 'SR': 'Suriname',
+ 'SJ': 'Svalbard And Jan Mayen',
+ 'SZ': 'Swaziland',
+ 'SE': 'Sweden',
+ 'CH': 'Switzerland',
+ 'SY': 'Syrian Arab Republic',
+ 'TW': 'Taiwan',
+ 'TJ': 'Tajikistan',
+ 'TZ': 'Tanzania',
+ 'TH': 'Thailand',
+ 'TL': 'Timor-Leste',
+ 'TG': 'Togo',
+ 'TK': 'Tokelau',
+ 'TO': 'Tonga',
+ 'TT': 'Trinidad And Tobago',
+ 'TN': 'Tunisia',
+ 'TR': 'Turkey',
+ 'TM': 'Turkmenistan',
+ 'TC': 'Turks And Caicos Islands',
+ 'TV': 'Tuvalu',
+ 'UG': 'Uganda',
+ 'UA': 'Ukraine',
+ 'AE': 'United Arab Emirates',
+ 'GB': 'United Kingdom',
+ 'US': 'United States',
+ 'UM': 'United States Outlying Islands',
+ 'UY': 'Uruguay',
+ 'UZ': 'Uzbekistan',
+ 'VU': 'Vanuatu',
+ 'VE': 'Venezuela',
+ 'VN': 'Viet Nam',
+ 'VG': 'Virgin Islands, British',
+ 'VI': 'Virgin Islands, U.S.',
+ 'WF': 'Wallis And Futuna',
+ 'EH': 'Western Sahara',
+ 'YE': 'Yemen',
+ 'ZM': 'Zambia',
+ 'ZW': 'Zimbabwe'
+ });
})();
(function() {
- 'use strict';
+ 'use strict';
- angular.module('app.components')
- .controller('LoginModalController', LoginModalController);
+ angular.module('app.components')
+ .factory('user', user);
- LoginModalController.$inject = ['$scope', '$mdDialog', 'auth', 'animation'];
- function LoginModalController($scope, $mdDialog, auth, animation) {
- const vm = this;
- $scope.answer = function(answer) {
- $scope.waitingFromServer = true;
- auth.login(answer)
- .then(function(data) {
- /*jshint camelcase: false */
- var token = data.access_token;
- auth.saveToken(token);
- $mdDialog.hide();
- })
- .catch(function(err) {
- vm.errors = err.data;
- })
- .finally(function() {
- $scope.waitingFromServer = false;
- });
- };
- $scope.hide = function() {
- $mdDialog.hide();
- };
- $scope.cancel = function() {
- $mdDialog.hide();
+ user.$inject = ['Restangular'];
+ function user(Restangular) {
+ var service = {
+ createUser: createUser,
+ getUser: getUser,
+ updateUser: updateUser
};
+ return service;
- $scope.openSignup = function() {
- animation.showSignup();
- $mdDialog.hide();
- };
+ ////////////////////
- $scope.openPasswordRecovery = function() {
- $mdDialog.show({
- hasBackdrop: true,
- controller: 'PasswordRecoveryModalController',
- templateUrl: 'app/components/passwordRecovery/passwordRecoveryModal.html',
- clickOutsideToClose: true
- });
+ function createUser(signupData) {
+ return Restangular.all('users').post(signupData);
+ }
- $mdDialog.hide();
- };
- }
+ function getUser(id) {
+ return Restangular.one('users', id).get();
+ }
+
+ function updateUser(updateData) {
+ return Restangular.all('me').customPUT(updateData);
+ }
+ }
})();
(function() {
'use strict';
- angular.module('app.components')
- .directive('login', login);
+ angular.module('app.components')
+ .factory('tag', tag);
- function login() {
- return {
- scope: {
- show: '='
- },
- restrict: 'A',
- controller: 'LoginController',
- controllerAs: 'vm',
- templateUrl: 'app/components/login/login.html'
+ tag.$inject = ['Restangular'];
+ function tag(Restangular) {
+ var tags = [];
+ var selectedTags = [];
+
+ var service = {
+ getTags: getTags,
+ getSelectedTags: getSelectedTags,
+ setSelectedTags: setSelectedTags,
+ tagWithName: tagWithName,
+ filterMarkersByTag: filterMarkersByTag
};
- }
-})();
-(function() {
- 'use strict';
+ return service;
- angular.module('app.components')
- .controller('LoginController', LoginController);
+ /////////////////
- LoginController.$inject = ['$scope', '$mdDialog'];
- function LoginController($scope, $mdDialog) {
+ function getTags() {
+ return Restangular.all('tags')
+ .getList({'per_page': 200})
+ .then(function(fetchedTags){
+ tags = fetchedTags.plain();
+ return tags;
+ });
+ }
- $scope.showLogin = showLogin;
+ function getSelectedTags(){
+ return selectedTags;
+ }
+
+ function setSelectedTags(tags){
+ selectedTags = tags;
+ }
- $scope.$on('showLogin', function() {
- showLogin();
- });
+ function tagWithName(name){
+ var result = _.where(tags, {name: name});
+ if (result && result.length > 0){
+ return result[0];
+ }else{
+ return;
+ }
+ }
- ////////////////
+ function filterMarkersByTag(tmpMarkers) {
+ var markers = filterMarkers(tmpMarkers);
+ return markers;
+ }
- function showLogin() {
- $mdDialog.show({
- hasBackdrop: true,
- fullscreen: true,
- controller: 'LoginModalController',
- controllerAs: 'vm',
- templateUrl: 'app/components/login/loginModal.html',
- clickOutsideToClose: true
- });
+ function filterMarkers(tmpMarkers) {
+ if (service.getSelectedTags().length === 0){
+ return tmpMarkers;
+ }
+ return tmpMarkers.filter(function(marker) {
+ var tags = marker.myData.tags;
+ if (tags.length === 0){
+ return false;
+ }
+ return _.some(tags, function(tag) {
+ return _.includes(service.getSelectedTags(), tag);
+ });
+ });
+ }
}
-
- }
})();
(function() {
'use strict';
angular.module('app.components')
- .controller('LayoutController', LayoutController);
+ .factory('sensor', sensor);
- LayoutController.$inject = ['$mdSidenav','$mdDialog', '$location', '$state', '$scope', '$transitions', 'auth', 'animation', '$timeout', 'DROPDOWN_OPTIONS_COMMUNITY', 'DROPDOWN_OPTIONS_USER'];
- function LayoutController($mdSidenav, $mdDialog, $location, $state, $scope, $transitions, auth, animation, $timeout, DROPDOWN_OPTIONS_COMMUNITY, DROPDOWN_OPTIONS_USER) {
- var vm = this;
+ sensor.$inject = ['Restangular', 'timeUtils', 'sensorUtils'];
+ function sensor(Restangular, timeUtils, sensorUtils) {
+ var sensorTypes;
+ callAPI().then(function(data) {
+ setTypes(data);
+ });
- vm.navRightLayout = 'space-around center';
+ var service = {
+ callAPI: callAPI,
+ setTypes: setTypes,
+ getTypes: getTypes,
+ getSensorsData: getSensorsData
+ };
+ return service;
- $scope.toggleRight = buildToggler('right');
+ ////////////////
- function buildToggler(componentId) {
- return function() {
- $mdSidenav(componentId).toggle();
- };
+ function callAPI() {
+ return Restangular.all('sensors').getList({'per_page': 1000});
}
- // listen for any login event so that the navbar can be updated
- $scope.$on('loggedIn', function(ev, options) {
- // if(options && options.time === 'appLoad') {
- // $scope.$apply(function() {
- // vm.isLoggedin = true;
- // vm.isShown = true;
- // angular.element('.nav_right .wrap-dd-menu').css('display', 'initial');
- // vm.currentUser = auth.getCurrentUser().data;
- // vm.dropdownOptions[0].text = 'Hello, ' + vm.currentUser.username;
- // vm.navRightLayout = 'end center';
- // });
- // } else {
- // vm.isLoggedin = true;
- // vm.isShown = true;
- // angular.element('.nav_right .wrap-dd-menu').css('display', 'initial');
- // vm.currentUser = auth.getCurrentUser().data;
- // vm.dropdownOptions[0].text = 'Hello, ' + vm.currentUser.username;
- // vm.navRightLayout = 'end center';
- // }
-
- vm.isLoggedin = true;
- vm.isShown = true;
- angular.element('.nav_right .wrap-dd-menu').css('display', 'initial');
- vm.currentUser = auth.getCurrentUser().data;
- vm.dropdownOptions[0].text = 'Hi, ' + vm.currentUser.username + '!';
- vm.navRightLayout = 'end center';
- if(!$scope.$$phase) {
- $scope.$digest();
- }
- });
-
- // listen for logout events so that the navbar can be updated
- $scope.$on('loggedOut', function() {
- vm.isLoggedIn = false;
- vm.isShown = true;
- angular.element('navbar .wrap-dd-menu').css('display', 'none');
- vm.navRightLayout = 'space-around center';
- });
-
-
- vm.isShown = true;
- vm.isLoggedin = false;
- vm.logout = logout;
+ function setTypes(sensorTypes) {
+ sensorTypes = sensorTypes;
+ }
- vm.dropdownOptions = DROPDOWN_OPTIONS_USER;
- vm.dropdownSelected = undefined;
+ function getTypes() {
+ return sensorTypes;
+ }
- vm.dropdownOptionsCommunity = DROPDOWN_OPTIONS_COMMUNITY;
- vm.dropdownSelectedCommunity = undefined;
+ function getSensorsData(deviceID, sensorID, dateFrom, dateTo) {
+ var rollup = sensorUtils.getRollup(dateFrom, dateTo);
+ dateFrom = timeUtils.convertTime(dateFrom);
+ dateTo = timeUtils.convertTime(dateTo);
- $scope.$on('removeNav', function() {
- vm.isShown = false;
- });
+ return Restangular.one('devices', deviceID).customGET('readings', {'from': dateFrom, 'to': dateTo, 'rollup': rollup, 'sensor_id': sensorID, 'all_intervals': true});
+ }
+ }
+})();
- $scope.$on('addNav', function() {
- vm.isShown = true;
- });
+(function() {
+ 'use strict';
- initialize();
+ angular.module('app.components')
+ .factory('search', search);
+
+ search.$inject = ['$http', 'Restangular'];
+ function search($http, Restangular) {
+ var service = {
+ globalSearch: globalSearch
+ };
- //////////////////
+ return service;
- function initialize() {
- $timeout(function() {
- var hash = $location.search();
- if(hash.signup) {
- animation.showSignup();
- } else if(hash.login) {
- animation.showLogin();
- } else if(hash.passwordRecovery) {
- animation.showPasswordRecovery();
- }
- }, 1000);
- }
+ /////////////////////////
- function logout() {
- auth.logout();
- vm.isLoggedin = false;
+ function globalSearch(query) {
+ return Restangular.all('search').getList({q: query});
}
}
})();
@@ -6460,951 +6186,1225 @@ angular.module('app.components')
'use strict';
angular.module('app.components')
- .controller('LandingController', LandingController);
+ .factory('measurement', measurement);
- LandingController.$inject = ['$timeout', 'animation', '$mdDialog', '$location', '$anchorScroll'];
+ measurement.$inject = ['Restangular'];
- function LandingController($timeout, animation, $mdDialog, $location, $anchorScroll) {
- var vm = this;
+ function measurement(Restangular) {
- vm.showStore = showStore;
- vm.goToHash = goToHash;
+ var service = {
+ getTypes: getTypes,
+ getMeasurement: getMeasurement
- ///////////////////////
+ };
+ return service;
- initialize();
+ ////////////////
- //////////////////
- function initialize() {
- $timeout(function() {
- animation.viewLoaded();
- if($location.hash()) {
- $anchorScroll();
- }
- }, 500);
+ function getTypes() {
+ return Restangular.all('measurements').getList({'per_page': 1000});
}
- function goToHash(hash){
- $location.hash(hash);
- $anchorScroll();
- }
+ function getMeasurement(mesID) {
- function showStore() {
- $mdDialog.show({
- hasBackdrop: true,
- controller: 'StoreModalController',
- templateUrl: 'app/components/store/storeModal.html',
- clickOutsideToClose: true
- });
+ return Restangular.one('measurements', mesID).get();
}
}
})();
+(function() {
+ 'use strict';
-(function(){
- 'use strict';
- angular.module('app.components')
- .directive('kitList',kitList);
+ angular.module('app.components')
+ .factory('geolocation', geolocation);
- function kitList(){
- return{
- restrict:'E',
- scope:{
- devices:'=devices',
- actions: '=actions'
- },
- controllerAs:'vm',
- templateUrl:'app/components/kitList/kitList.html'
- };
- }
+ geolocation.$inject = ['$http', '$window'];
+ function geolocation($http, $window) {
+
+ var service = {
+ grantHTML5Geolocation: grantHTML5Geolocation,
+ isHTML5GeolocationGranted: isHTML5GeolocationGranted
+ };
+ return service;
+
+ ///////////////////////////
+
+
+ function grantHTML5Geolocation(){
+ $window.localStorage.setItem('smartcitizen.geolocation_granted', true);
+ }
+
+ function isHTML5GeolocationGranted(){
+ return $window.localStorage
+ .getItem('smartcitizen.geolocation_granted');
+ }
+ }
})();
(function() {
- 'use strict';
+ 'use strict';
- angular.module('app.components')
- .controller('HomeController', HomeController);
+ angular.module('app.components')
+ .factory('file', file);
- function HomeController() {
- }
+ file.$inject = ['Restangular', 'Upload'];
+ function file(Restangular, Upload) {
+ var service = {
+ getCredentials: getCredentials,
+ uploadFile: uploadFile,
+ getImageURL: getImageURL
+ };
+ return service;
+
+ ///////////////
+
+ function getCredentials(filename) {
+ var data = {
+ filename: filename
+ };
+ return Restangular.all('me/avatar').post(data);
+ }
+
+ function uploadFile(fileData, key, policy, signature) {
+ return Upload.upload({
+ url: 'https://smartcitizen.s3-eu-west-1.amazonaws.com',
+ method: 'POST',
+ data: {
+ key: key,
+ policy: policy,
+ signature: signature,
+ AWSAccessKeyId: 'AKIAJ753OQI6JPSDCPHA',
+ acl: 'public-read',
+ "Content-Type": fileData.type || 'application/octet-stream',
+ /*jshint camelcase: false */
+ success_action_status: 200,
+ file: fileData
+ }
+ });
+ }
+
+ function getImageURL(filename, size) {
+ size = size === undefined ? 's101' : size;
+
+ return 'https://images.smartcitizen.me/' + size + '/' + filename;
+ }
+ }
})();
-(function (){
+
+(function() {
'use strict';
angular.module('app.components')
- .controller('DownloadModalController', DownloadModalController);
-
- DownloadModalController.$inject = ['thisDevice', 'device', '$mdDialog'];
+ .factory('device', device);
- function DownloadModalController(thisDevice, device, $mdDialog) {
- var vm = this;
+ device.$inject = ['Restangular', '$window', 'timeUtils','$http', 'auth', '$rootScope'];
+ function device(Restangular, $window, timeUtils, $http, auth, $rootScope) {
+ var worldMarkers;
- vm.device = thisDevice;
- vm.download = download;
- vm.cancel = cancel;
+ initialize();
- ////////////////////////////
+ var service = {
+ getDevices: getDevices,
+ getAllDevices: getAllDevices,
+ getDevice: getDevice,
+ createDevice: createDevice,
+ updateDevice: updateDevice,
+ getWorldMarkers: getWorldMarkers,
+ setWorldMarkers: setWorldMarkers,
+ mailReadings: mailReadings,
+ postReadings: postReadings,
+ removeDevice: removeDevice,
+ updateContext: updateContext
+ };
- function download(){
- device.mailReadings(vm.device)
- .then(function (){
- $mdDialog.hide();
- }).catch(function(err){
- $mdDialog.cancel(err);
- });
- }
+ return service;
- function cancel(){
- $mdDialog.cancel();
- }
- }
+ //////////////////////////
-})();
+ function initialize() {
+ if(areMarkersOld()) {
+ removeMarkers();
+ }
+ }
-(function(){
-'use strict';
+ function getDevices(location) {
+ var parameter = '';
+ parameter += location.lat + ',' + location.lng;
+ return Restangular.all('devices').getList({near: parameter, 'per_page': '100'});
+ }
-angular.module('app.components')
- .directive('cookiesLaw', cookiesLaw);
+ function getAllDevices(forceReload) {
+ if (forceReload || auth.isAuth()) {
+ return getAllDevicesNoCached();
+ } else {
+ return getAllDevicesCached();
+ }
+ }
+ function getAllDevicesCached() {
+ return Restangular.all('devices/world_map')
+ .getList()
+ .then(function(fetchedDevices){
+ return fetchedDevices.plain();
+ });
+ }
-cookiesLaw.$inject = ['$cookies'];
+ function getAllDevicesNoCached() {
+ return Restangular.all('devices/fresh_world_map')
+ .getList()
+ .then(function(fetchedDevices){
+ return fetchedDevices.plain();
+ });
+ }
-function cookiesLaw($cookies) {
- return {
- template:
- '' +
- 'This site uses cookies to offer you a better experience. ' +
- '
Accept or' +
- '
Learn More. ' +
- '
',
- controller: function($scope) {
+ function getDevice(id) {
+ return Restangular.one('devices', id).get();
+ }
- var init = function(){
- $scope.isCookieValid();
+ function createDevice(data) {
+ return Restangular.all('devices').post(data);
}
- // Helpers to debug
- // You can also use `document.cookie` in the browser dev console.
- //console.log($cookies.getAll());
+ function updateDevice(id, data) {
+ return Restangular.one('devices', id).patch(data);
+ }
- $scope.isCookieValid = function() {
- // Use a boolean for the ng-hide, because using a function with ng-hide
- // is considered bad practice. The digest cycle will call it multiple
- // times, in our case around 240 times.
- $scope.isCookieValidBool = ($cookies.get('consent') === 'true')
+ function getWorldMarkers() {
+ return worldMarkers || ($window.localStorage.getItem('smartcitizen.markers') && JSON.parse($window.localStorage.getItem('smartcitizen.markers') ).data);
}
- $scope.acceptCookie = function() {
- //console.log('Accepting cookie...');
- var today = new Date();
- var expireDate = new Date(today);
- expireDate.setMonth(today.getMonth() + 6);
+ function setWorldMarkers(data) {
+ var obj = {
+ timestamp: new Date(),
+ data: data
+ };
+ try {
+ $window.localStorage.setItem('smartcitizen.markers', JSON.stringify(obj) );
+ } catch (e) {
+ console.log("Could not store markers in localstorage. skipping...");
+ }
+ worldMarkers = obj.data;
+ }
- $cookies.put('consent', true, {'expires' : expireDate.toUTCString()} );
+ function getTimeStamp() {
+ return ($window.localStorage.getItem('smartcitizen.markers') &&
+ JSON.parse($window.localStorage
+ .getItem('smartcitizen.markers') ).timestamp);
+ }
- // Trigger the check again, after we click
- $scope.isCookieValid();
- };
+ function areMarkersOld() {
+ var markersDate = getTimeStamp();
+ return !timeUtils.isWithin(1, 'minutes', markersDate);
+ }
- init();
+ function removeMarkers() {
+ worldMarkers = null;
+ $window.localStorage.removeItem('smartcitizen.markers');
+ }
- }
- };
-}
+ function mailReadings(kit) {
+ return Restangular
+ .one('devices', kit.id)
+ .customGET('readings/csv_archive');
+ }
+
+ function postReadings(kit, readings) {
+ return Restangular
+ .one('devices', kit.id)
+ .post('readings', readings);
+ }
+
+ function removeDevice(deviceID){
+ return Restangular
+ .one('devices', deviceID)
+ .remove().then(function () {
+ $rootScope.$broadcast('devicesContextUpdated');
+ })
+ ;
+ }
+ function updateContext (){
+ return auth.updateUser().then(function(){
+ removeMarkers();
+ $rootScope.$broadcast('devicesContextUpdated');
+ });
+ }
+ }
})();
(function() {
'use strict';
angular.module('app.components')
- .directive('chart', chart);
-
- chart.$inject = ['sensor', 'animation', '$timeout', '$window'];
- function chart(sensor, animation, $timeout, $window) {
- var margin, width, height, svg, xScale, yScale0, yScale1, xAxis, yAxisLeft, yAxisRight, dateFormat, areaMain, valueLineMain, areaCompare, valueLineCompare, focusCompare, focusMain, popup, dataMain, colorMain, yAxisScale, unitMain, popupContainer;
-
- return {
- link: link,
- restrict: 'A',
- scope: {
- chartData: '='
- }
- };
+ .factory('auth', auth);
- function link(scope, elem) {
+ auth.$inject = ['$location', '$window', '$state', 'Restangular',
+ '$rootScope', 'AuthUser', '$timeout', 'alert', '$cookies'];
+ function auth($location, $window, $state, Restangular, $rootScope, AuthUser,
+ $timeout, alert, $cookies) {
- $timeout(function() {
- createChart(elem[0]);
- }, 0);
+ var user = {};
- var lastData = {};
+ //wait until http interceptor is added to Restangular
+ $timeout(function() {
+ initialize();
+ }, 100);
- // on window resize, it re-renders the chart to fit into the new window size
- angular.element($window).on('resize', function() {
- createChart(elem[0]);
- updateChartData(lastData.data, {type: lastData.type, container: elem[0], color: lastData.color, unit: lastData.unit});
- });
+ var service = {
+ isAuth: isAuth,
+ setCurrentUser: setCurrentUser,
+ getCurrentUser: getCurrentUser,
+ updateUser: updateUser,
+ saveToken: saveToken,
+ getToken: getToken,
+ login: login,
+ logout: logout,
+ recoverPassword: recoverPassword,
+ getResetPassword: getResetPassword,
+ patchResetPassword: patchResetPassword,
+ isAdmin: isAdmin
+ };
+ return service;
- scope.$watch('chartData', function(newData) {
- if(!newData) {
- return;
- }
+ //////////////////////////
- if(newData !== undefined) {
- // if there's data for 2 sensors
- if(newData[0] && newData[1]) {
- var sensorDataMain = newData[0].data;
- // we could get some performance from saving the map in the showKit controller on line 218 and putting that logic in here
- var dataMain = sensorDataMain.map(function(dataPoint) {
- return {
- date: dateFormat(dataPoint.time),
- count: dataPoint && dataPoint.count,
- value: dataPoint && dataPoint.value
- };
- });
- // sort data points by date
- dataMain.sort(function(a, b) {
- return a.date - b.date;
- });
+ function initialize() {
+ //console.log('---- AUTH INIT -----');
+ setCurrentUser('appLoad');
+ }
- var sensorDataCompare = newData[1].data;
- var dataCompare = sensorDataCompare.map(function(dataPoint) {
- return {
- date: dateFormat(dataPoint.time),
- count: dataPoint && dataPoint.count,
- value: dataPoint && dataPoint.value
- };
- });
+ //run on app initialization so that we can keep auth across different sessions
+ // 1. Check if token in cookie exists. Return if it doesn't, user needs to login (and save a token to the cookie)
+ // 2. Populate user.data with the response from the API.
+ // 3. Broadcast logged in
+ function setCurrentUser(time) {
+ // TODO later: Should we check if token is expired here?
+ if (getToken()) {
+ user.token = getToken();
+ }else{
+ //console.log('token not found in cookie, returning');
+ return;
+ }
- dataCompare.sort(function(a, b) {
- return a.date - b.date;
- });
+ return getCurrentUserFromAPI()
+ .then(function(data) {
+ // Save user.data also in localStorage. It is beeing used across the app.
+ // Should it instead just be saved in the user object? Or is it OK to also have it in localStorage?
+ $window.localStorage.setItem('smartcitizen.data', JSON.stringify(data.plain()) );
- var data = [dataMain, dataCompare];
- var colors = [newData[0].color, newData[1].color];
- var units = [newData[0].unit, newData[1].unit];
- // saves everything in case we need to re-render
- lastData = {
- data: data,
- type: 'both',
- color: colors,
- unit: units
- };
- // call function to update the chart with the new data
- updateChartData(data, {type: 'both', container: elem[0], color: colors, unit: units });
- // if only data for the main sensor
- } else if(newData[0]) {
+ var newUser = new AuthUser(data);
+ //check sensitive information
+ if(user.data && user.data.role !== newUser.role) {
+ user.data = newUser;
+ $location.path('/');
+ }
+ user.data = newUser;
- var sensorData = newData[0].data;
- /*jshint -W004 */
- var data = sensorData.map(function(dataPoint) {
- return {
- date: dateFormat(dataPoint.time),
- count: dataPoint && dataPoint.count,
- value: dataPoint && dataPoint.value
- };
- });
+ //console.log('-- User populated with data: ', user)
+ // Broadcast happens 2x, so the user wont think he is not logged in.
+ // The 2nd broadcast waits 3sec, because f.x. on the /kits/ page, the layout has not loaded when the broadcast is sent
+ $rootScope.$broadcast('loggedIn');
- data.sort(function(a, b) {
- return a.date - b.date;
- });
+ // used for app initialization
+ if(time && time === 'appLoad') {
+ //wait until navbar is loaded to emit event
+ $timeout(function() {
+ $rootScope.$broadcast('loggedIn', {time: 'appLoad'});
+ }, 3000);
+ } else {
+ // used for login
+ //$state.reload();
+ $timeout(function() {
+ alert.success('Login was successful');
+ $rootScope.$broadcast('loggedIn', {});
+ }, 2000);
+ }
+ });
+ }
- var color = newData[0].color;
- var unit = newData[0].unit;
+ // Called from device.service.js updateContext(), which is called from multiple /kit/ pages
+ function updateUser() {
+ return getCurrentUserFromAPI()
+ .then(function(data) {
+ // TODO: Should this update the token or user.data? Then it could instead call setCurrentUser?
+ $window.localStorage.setItem('smartcitizen.data', JSON.stringify(data.plain()) );
+ return getCurrentUser();
+ });
+ }
- lastData = {
- data: data,
- type: 'main',
- color: color,
- unit: unit
- };
+ function getCurrentUser() {
+ user.token = getToken();
+ user.data = $window.localStorage.getItem('smartcitizen.data') && new AuthUser(JSON.parse( $window.localStorage.getItem('smartcitizen.data') ));
+ return user;
+ }
- updateChartData(data, {type: 'main', container: elem[0], color: color, unit: unit });
- }
- animation.hideChartSpinner();
- }
- });
+ // Should check if user.token exists - but now checks if the cookies.token exists.
+ function isAuth() {
+ // TODO: isAuth() is called from many different services BEFORE auth.init has run.
+ // That means that the user.token is EMPTY, meaning isAuth will be false
+ // We can cheat and just check the cookie, but we should NOT. Because auth.init should also check if the cookie is valid / expired
+ // Ideally it should return !!user.token
+ //return !!user.token;
+ return !!getToken();
}
- // creates the container that is re-used across different sensor charts
- function createChart(elem) {
- d3.select(elem).selectAll('*').remove();
+ // LoginModal calls this after it receives the token from the API, and wants to save it in a cookie.
+ function saveToken(token) {
+ //console.log('saving Token to cookie:', token);
+ $cookies.put('smartcitizen.token', token);
+ setCurrentUser();
+ }
- margin = {top: 20, right: 12, bottom: 20, left: 42};
- width = elem.clientWidth - margin.left - margin.right;
- height = elem.clientHeight - margin.top - margin.bottom;
+ function getToken(){
+ return $cookies.get('smartcitizen.token');
+ }
- xScale = d3.time.scale().range([0, width]);
- xScale.tickFormat("%Y-%m-%d %I:%M:%S");
- yScale0 = d3.scale.linear().range([height, 0]);
- yScale1 = d3.scale.linear().range([height, 0]);
- yAxisScale = d3.scale.linear().range([height, 0]);
+ function login(loginData) {
+ return Restangular.all('sessions').post(loginData);
+ }
- dateFormat = d3.time.format('%Y-%m-%dT%H:%M:%S').parse;//d3.time.format('%Y-%m-%dT%X.%LZ').parse; //'YYYY-MM-DDTHH:mm:ssZ'
+ function logout() {
+ $cookies.remove('smartcitizen.token');
+ }
- xAxis = d3.svg.axis()
- .scale(xScale)
- .orient('bottom')
- .ticks(5);
+ function getCurrentUserFromAPI() {
+ return Restangular.all('').customGET('me');
+ }
- yAxisLeft = d3.svg.axis()
- .scale(yScale0)
- .orient('left')
- .ticks(5);
+ function recoverPassword(data) {
+ return Restangular.all('password_resets').post(data);
+ }
- yAxisRight = d3.svg.axis()
- .scale(yScale1)
- .orient('right')
- .ticks(5);
+ function getResetPassword(code) {
+ return Restangular.one('password_resets', code).get();
+ }
+ function patchResetPassword(code, data) {
+ return Restangular.one('password_resets', code).patch(data);
+ }
+ function isAdmin(userData) {
+ return userData.role === 'admin';
+ }
+ }
+})();
- areaMain = d3.svg.area()
- .defined(function(d) {return d.value != null })
- .interpolate('linear')
- .x(function(d) { return xScale(d.date); })
- .y0(height)
- .y1(function(d) { return yScale0(d.count); });
+(function() {
+ 'use strict';
- valueLineMain = d3.svg.line()
- .defined(function(d) {return d.value != null })
- .interpolate('linear')
- .x(function(d) { return xScale(d.date); })
- .y(function(d) { return yScale0(d.count); });
+ /**
+ * Unused directive. Double-check before removing.
+ *
+ */
+ angular.module('app.components')
+ .directive('slide', slide)
+ .directive('slideMenu', slideMenu);
- areaCompare = d3.svg.area()
- .defined(function(d) {return d.value != null })
- .interpolate('linear')
- .x(function(d) { return xScale(d.date); })
- .y0(height)
- .y1(function(d) { return yScale1(d.count); });
+ function slideMenu() {
+ return {
+ controller: controller,
+ link: link
+ };
- valueLineCompare = d3.svg.line()
- .defined(function(d) {return d.value != null })
- .interpolate('linear')
- .x(function(d) { return xScale(d.date); })
- .y(function(d) { return yScale1(d.count); });
+ function link(scope, element) {
+ scope.element = element;
+ }
- svg = d3
- .select(elem)
- .append('svg')
- .attr('width', width + margin.left + margin.right)
- .attr('height', height + margin.top + margin.bottom)
- .append('g')
- .attr('transform', 'translate(' + (margin.left - margin.right) + ',' + margin.top + ')');
+ function controller($scope) {
+ $scope.slidePosition = 0;
+ $scope.slideSize = 20;
+
+ this.getTimesSlided = function() {
+ return $scope.slideSize;
+ };
+ this.getPosition = function() {
+ return $scope.slidePosition * $scope.slideSize;
+ };
+ this.decrementPosition = function() {
+ $scope.slidePosition -= 1;
+ };
+ this.incrementPosition = function() {
+ $scope.slidePosition += 1;
+ };
+ this.scrollIsValid = function(direction) {
+ var scrollPosition = $scope.element.scrollLeft();
+ console.log('scrollpos', scrollPosition);
+ if(direction === 'left') {
+ return scrollPosition > 0 && $scope.slidePosition >= 0;
+ } else if(direction === 'right') {
+ return scrollPosition < 300;
+ }
+ };
}
- // calls functions depending on type of chart
- function updateChartData(newData, options) {
- if(options.type === 'main') {
- updateChartMain(newData, options);
- } else if(options.type === 'both') {
- updateChartCompare(newData, options);
+ }
+
+ slide.$inject = [];
+ function slide() {
+ return {
+ link: link,
+ require: '^slide-menu',
+ restrict: 'A',
+ scope: {
+ direction: '@'
}
- }
- // function in charge of rendering when there's data for 1 sensor
- function updateChartMain(data, options) {
- xScale.domain(d3.extent(data, function(d) { return d.date; }));
- yScale0.domain([(d3.min(data, function(d) { return d.count; })) * 0.8, (d3.max(data, function(d) { return d.count; })) * 1.2]);
+ };
- svg.selectAll('*').remove();
+ function link(scope, element, attr, slideMenuCtrl) {
+ //select first sensor container
+ var sensorsContainer = angular.element('.sensors_container');
- //Add the area path
- svg.append('path')
- .datum(data)
- .attr('class', 'chart_area')
- .attr('fill', options.color)
- .attr('d', areaMain);
+ element.on('click', function() {
- // Add the valueline path.
- svg.append('path')
- .attr('class', 'chart_line')
- .attr('stroke', options.color)
- .attr('d', valueLineMain(data));
+ if(slideMenuCtrl.scrollIsValid('left') && attr.direction === 'left') {
+ slideMenuCtrl.decrementPosition();
+ sensorsContainer.scrollLeft(slideMenuCtrl.getPosition());
+ console.log(slideMenuCtrl.getPosition());
+ } else if(slideMenuCtrl.scrollIsValid('right') && attr.direction === 'right') {
+ slideMenuCtrl.incrementPosition();
+ sensorsContainer.scrollLeft(slideMenuCtrl.getPosition());
+ console.log(slideMenuCtrl.getPosition());
+ }
+ });
+ }
+ }
+})();
- // Add the X Axis
- svg.append('g')
- .attr('class', 'axis x')
- .attr('transform', 'translate(0,' + height + ')')
- .call(xAxis);
+(function() {
+ 'use strict';
- // Add the Y Axis
- svg.append('g')
- .attr('class', 'axis y_left')
- .call(yAxisLeft);
+ angular.module('app.components')
+ .directive('showPopupInfo', showPopupInfo);
- // Draw the x Grid lines
- svg.append('g')
- .attr('class', 'grid')
- .attr('transform', 'translate(0,' + height + ')')
- .call(xGrid()
- .tickSize(-height, 0, 0)
- .tickFormat('')
- );
+ /**
+ * Used to show/hide explanation of sensor value at kit dashboard
+ *
+ */
+ showPopupInfo.$inject = [];
+ function showPopupInfo() {
+ return {
+ link: link
+ };
- // Draw the y Grid lines
- svg.append('g')
- .attr('class', 'grid')
- .call(yGrid()
- .tickSize(-width, 0, 0)
- .tickFormat('')
- );
+ //////
- focusMain = svg.append('g')
- .attr('class', 'focus')
- .style('display', 'none');
- focusMain.append('circle')
- .style('stroke', options.color)
- .attr('r', 4.5);
+ function link(scope, elem) {
+ elem.on('mouseenter', function() {
+ angular.element('.sensor_data_description').css('display', 'inline-block');
+ });
+ elem.on('mouseleave', function() {
+ angular.element('.sensor_data_description').css('display', 'none');
+ });
+ }
+ }
+})();
- var popupWidth = 84;
- var popupHeight = 46;
+(function() {
+ 'use strict';
- popup = svg.append('g')
- .attr('class', 'focus')
- .style('display', 'none');
+ angular.module('app.components')
+ .directive('showPopup', showPopup);
- popupContainer = popup.append('rect')
- .attr('width', popupWidth)
- .attr('height', popupHeight)
- .attr('transform', function() {
- var result = 'translate(-42, 5)';
+ /**
+ * Used on kit dashboard to open full sensor description
+ */
- return result;
- })
- .style('stroke', 'grey')
- .style('stroke-width', '0.5')
- .style('fill', 'white');
+ showPopup.$inject = [];
+ function showPopup() {
+ return {
+ link: link
+ };
- var text = popup.append('text')
- .attr('class', '');
+ /////
- var textMain = text.append('tspan')
- .attr('class', 'popup_main')
- .attr('text-anchor', 'start')
- .attr('x', -popupWidth / 2)
- .attr('dx', 8)
- .attr('y', popupHeight / 2)
- .attr('dy', 3);
+ function link(scope, element) {
+ element.on('click', function() {
+ var text = angular.element('.sensor_description_preview').text();
+ if(text.length < 140) {
+ return;
+ }
+ angular.element('.sensor_description_preview').hide();
+ angular.element('.sensor_description_full').show();
+ });
+ }
+ }
+})();
- textMain.append('tspan')
- .attr('class', 'popup_value');
+(function() {
+ 'use strict';
- textMain.append('tspan')
- .attr('class', 'popup_unit')
- .attr('dx', 5);
+ angular.module('app.components')
+ .directive('moveFilters', moveFilters);
- text.append('tspan')
- .attr('class', 'popup_date')
- .attr('x', -popupWidth / 2)
- .attr('dx', 8)
- .attr('y', popupHeight - 2)
- .attr('dy', 0)
- .attr( 'text-anchor', 'start' );
+ /**
+ * Moves map filters when scrolling
+ *
+ */
+ moveFilters.$inject = ['$window', '$timeout'];
+ function moveFilters($window, $timeout) {
+ return {
+ link: link
+ };
- svg.append('rect')
- .attr('class', 'overlay')
- .attr('width', width)
- .attr('height', height)
- .on('mouseover', function() {
- popup.style('display', null);
- focusMain.style('display', null);
- })
- .on('mouseout', function() {
- popup.style('display', 'none');
- focusMain.style('display', 'none');
- })
- .on('mousemove', mousemove);
+ function link() {
+ var chartHeight;
+ $timeout(function() {
+ chartHeight = angular.element('.kit_chart').height();
+ }, 1000);
+ /*
+ angular.element($window).on('scroll', function() {
+ var windowPosition = document.body.scrollTop;
+ if(chartHeight > windowPosition) {
+ elem.css('bottom', 12 + windowPosition + 'px');
+ }
+ });
+ */
+ }
+ }
+})();
+(function() {
+ 'use strict';
- function mousemove() {
- var bisectDate = d3.bisector(function(d) { return d.date; }).left;
+ angular.module('app.components')
+ .factory('layout', layout);
- var x0 = xScale.invert(d3.mouse(this)[0]);
- var i = bisectDate(data, x0, 1);
- var d0 = data[i - 1];
- var d1 = data[i];
- var d = d1 && (x0 - d0.date > d1.date - x0) ? d1 : d0;
- focusMain.attr('transform', 'translate(' + xScale(d.date) + ', ' + yScale0(d.count) + ')');
- var popupText = popup.select('text');
- var textMain = popupText.select('.popup_main');
- var valueMain = textMain.select('.popup_value').text(parseValue(d.value));
- var unitMain = textMain.select('.popup_unit').text(options.unit);
- var date = popupText.select('.popup_date').text(parseTime(d.date));
+ function layout() {
- var textContainers = [
- textMain,
- date
- ];
+ var kitHeight;
- var popupWidth = resizePopup(popupContainer, textContainers);
+ var service = {
+ setKit: setKit,
+ getKit: getKit
+ };
+ return service;
- if(xScale(d.date) + 80 + popupWidth > options.container.clientWidth) {
- popup.attr('transform', 'translate(' + (xScale(d.date) - 120) + ', ' + (d3.mouse(this)[1] - 20) + ')');
- } else {
- popup.attr('transform', 'translate(' + (xScale(d.date) + 80) + ', ' + (d3.mouse(this)[1] - 20) + ')');
- }
- }
+ function setKit(height) {
+ kitHeight = height;
}
- // function in charge of rendering when there's data for 2 sensors
- function updateChartCompare(data, options) {
- xScale.domain(d3.extent(data[0], function(d) { return d.date; }));
- yScale0.domain([(d3.min(data[0], function(d) { return d.count; })) * 0.8, (d3.max(data[0], function(d) { return d.count; })) * 1.2]);
- yScale1.domain([(d3.min(data[1], function(d) { return d.count; })) * 0.8, (d3.max(data[1], function(d) { return d.count; })) * 1.2]);
+ function getKit() {
+ return kitHeight;
+ }
+ }
+})();
- svg.selectAll('*').remove();
+(function() {
+ 'use strict';
- //Add both area paths
- svg.append('path')
- .datum(data[0])
- .attr('class', 'chart_area')
- .attr('fill', options.color[0])
- .attr('d', areaMain);
+ angular.module('app.components')
+ .directive('horizontalScroll', horizontalScroll);
- svg.append('path')
- .datum(data[1])
- .attr('class', 'chart_area')
- .attr('fill', options.color[1])
- .attr('d', areaCompare);
+ /**
+ * Used to highlight and unhighlight buttons on the kit dashboard when scrolling horizontally
+ *
+ */
+ horizontalScroll.$inject = ['$window', '$timeout'];
+ function horizontalScroll($window, $timeout) {
+ return {
+ link: link,
+ restrict: 'A'
+ };
- // Add both valueline paths.
- svg.append('path')
- .attr('class', 'chart_line')
- .attr('stroke', options.color[0])
- .attr('d', valueLineMain(data[0]));
+ ///////////////////
- svg.append('path')
- .attr('class', 'chart_line')
- .attr('stroke', options.color[1])
- .attr('d', valueLineCompare(data[1]));
- // Add the X Axis
- svg.append('g')
- .attr('class', 'axis x')
- .attr('transform', 'translate(0,' + height + ')')
- .call(xAxis);
+ function link(scope, element) {
- // Add both Y Axis
- svg.append('g')
- .attr('class', 'axis y_left')
- .call(yAxisLeft);
+ element.on('scroll', function() {
+ // horizontal scroll position
+ var position = angular.element(this).scrollLeft();
+ // real width of element
+ var scrollWidth = this.scrollWidth;
+ // visible width of element
+ var width = angular.element(this).width();
- svg.append('g')
- .attr('class', 'axis y_right')
- .attr('transform', 'translate(' + width + ' ,0)')
- .call(yAxisRight);
+ // if you cannot scroll, unhighlight both
+ if(scrollWidth === width) {
+ angular.element('.button_scroll_left').css('opacity', '0.5');
+ angular.element('.button_scroll_right').css('opacity', '0.5');
+ }
+ // if scroll is in the middle, highlight both
+ if(scrollWidth - width > 2) {
+ angular.element('.button_scroll_left').css('opacity', '1');
+ angular.element('.button_scroll_right').css('opacity', '1');
+ }
+ // if scroll is at the far right, unhighligh right button
+ if(scrollWidth - width - position <= 2) {
+ angular.element('.button_scroll_right').css('opacity', '0.5');
+ return;
+ }
+ // if scroll is at the far left, unhighligh left button
+ if(position === 0) {
+ angular.element('.button_scroll_left').css('opacity', '0.5');
+ return;
+ }
- // Draw the x Grid lines
- svg.append('g')
- .attr('class', 'grid')
- .attr('transform', 'translate(0,' + height + ')')
- .call(xGrid()
- .tickSize(-height, 0, 0)
- .tickFormat('')
- );
+ //set opacity back to normal otherwise
+ angular.element('.button_scroll_left').css('opacity', '1');
+ angular.element('.button_scroll_right').css('opacity', '1');
+ });
- // Draw the y Grid lines
- svg.append('g')
- .attr('class', 'grid')
- .call(yGrid()
- .tickSize(-width, 0, 0)
- .tickFormat('')
- );
+ $timeout(function() {
+ element.trigger('scroll');
+ });
- focusCompare = svg.append('g')
- .attr('class', 'focus')
- .style('display', 'none');
+ angular.element($window).on('resize', function() {
+ $timeout(function() {
+ element.trigger('scroll');
+ }, 1000);
+ });
+ }
+ }
+})();
+
+(function() {
+ 'use strict';
+
+ angular.module('app.components')
+ .directive('hidePopup', hidePopup);
- focusMain = svg.append('g')
- .attr('class', 'focus')
- .style('display', 'none');
+ /**
+ * Used on kit dashboard to hide popup with full sensor description
+ *
+ */
+
+ hidePopup.$inject = [];
+ function hidePopup() {
+ return {
+ link: link
+ };
- focusCompare.append('circle')
- .style('stroke', options.color[1])
- .attr('r', 4.5);
+ /////////////
- focusMain.append('circle')
- .style('stroke', options.color[0])
- .attr('r', 4.5);
+ function link(scope, elem) {
+ elem.on('mouseleave', function() {
+ angular.element('.sensor_description_preview').show();
+ angular.element('.sensor_description_full').hide();
+ });
+ }
+ }
+})();
- var popupWidth = 84;
- var popupHeight = 75;
+(function() {
+ 'use strict';
- popup = svg.append('g')
- .attr('class', 'focus')
- .style('display', 'none');
+ angular.module('app.components')
+ .directive('disableScroll', disableScroll);
- popupContainer = popup.append('rect')
- .attr('width', popupWidth)
- .attr('height', popupHeight)
- .style('min-width', '40px')
- .attr('transform', function() {
- var result = 'translate(-42, 5)';
+ disableScroll.$inject = ['$timeout'];
+ function disableScroll($timeout) {
+ return {
+ // link: {
+ // pre: link
+ // },
+ compile: link,
+ restrict: 'A',
+ priority: 100000
+ };
- return result;
- })
- .style('stroke', 'grey')
- .style('stroke-width', '0.5')
- .style('fill', 'white');
- popup.append('rect')
- .attr('width', 8)
- .attr('height', 2)
- .attr('transform', function() {
- return 'translate(' + (-popupWidth / 2 + 4).toString() + ', 20)';
- })
- .style('fill', options.color[0]);
+ //////////////////////
- popup.append('rect')
- .attr('width', 8)
- .attr('height', 2)
- .attr('transform', function() {
- return 'translate(' + (-popupWidth / 2 + 4).toString() + ', 45)';
- })
- .style('fill', options.color[1]);
+ function link(elem) {
+ console.log('i', elem);
+ // var select = elem.find('md-select');
+ // angular.element(select).on('click', function() {
+ elem.on('click', function() {
+ console.log('e');
+ angular.element(document.body).css('overflow', 'hidden');
+ $timeout(function() {
+ angular.element(document.body).css('overflow', 'initial');
+ });
+ });
+ }
+ }
+})();
- var text = popup.append('text')
- .attr('class', '');
+(function() {
+ 'use strict';
- var textMain = text.append('tspan')
- .attr('class', 'popup_main')
- .attr('x', -popupHeight / 2 + 7) //position of text
- .attr('dx', 8) //margin given to the element, will be applied to both sides thanks to resizePopup function
- .attr('y', popupHeight / 3)
- .attr('dy', 3);
+ angular.module('app.components')
+ .factory('animation', animation);
- textMain.append('tspan')
- .attr('class', 'popup_value')
- .attr( 'text-anchor', 'start' );
+ /**
+ * Used to emit events from rootscope.
+ *
+ * This events are then listened by $scope on controllers and directives that care about that particular event
+ */
- textMain.append('tspan')
- .attr('class', 'popup_unit')
- .attr('dx', 5);
+ animation.$inject = ['$rootScope'];
+ function animation($rootScope) {
- var textCompare = text.append('tspan')
- .attr('class', 'popup_compare')
- .attr('x', -popupHeight / 2 + 7) //position of text
- .attr('dx', 8) //margin given to the element, will be applied to both sides thanks to resizePopup function
- .attr('y', popupHeight / 1.5)
- .attr('dy', 3);
+ var service = {
+ blur: blur,
+ unblur: unblur,
+ removeNav: removeNav,
+ addNav: addNav,
+ showChartSpinner: showChartSpinner,
+ hideChartSpinner: hideChartSpinner,
+ deviceLoaded: deviceLoaded,
+ showPasswordRecovery: showPasswordRecovery,
+ showLogin: showLogin,
+ showSignup: showSignup,
+ showPasswordReset: showPasswordReset,
+ hideAlert: hideAlert,
+ viewLoading: viewLoading,
+ viewLoaded: viewLoaded,
+ deviceWithoutData: deviceWithoutData,
+ deviceIsPrivate: deviceIsPrivate,
+ goToLocation: goToLocation,
+ mapStateLoading: mapStateLoading,
+ mapStateLoaded: mapStateLoaded
+ };
+ return service;
- textCompare.append('tspan')
- .attr('class', 'popup_value')
- .attr( 'text-anchor', 'start' );
+ //////////////
- textCompare.append('tspan')
- .attr('class', 'popup_unit')
- .attr('dx', 5);
+ function blur() {
+ $rootScope.$broadcast('blur');
+ }
+ function unblur() {
+ $rootScope.$broadcast('unblur');
+ }
+ function removeNav() {
+ $rootScope.$broadcast('removeNav');
+ }
+ function addNav() {
+ $rootScope.$broadcast('addNav');
+ }
+ function showChartSpinner() {
+ $rootScope.$broadcast('showChartSpinner');
+ }
+ function hideChartSpinner() {
+ $rootScope.$broadcast('hideChartSpinner');
+ }
+ function deviceLoaded(data) {
+ $rootScope.$broadcast('deviceLoaded', data);
+ }
+ function showPasswordRecovery() {
+ $rootScope.$broadcast('showPasswordRecovery');
+ }
+ function showLogin() {
+ $rootScope.$broadcast('showLogin');
+ }
+ function showSignup() {
+ $rootScope.$broadcast('showSignup');
+ }
+ function showPasswordReset() {
+ $rootScope.$broadcast('showPasswordReset');
+ }
+ function hideAlert() {
+ $rootScope.$broadcast('hideAlert');
+ }
+ function viewLoading() {
+ $rootScope.$broadcast('viewLoading');
+ }
+ function viewLoaded() {
+ $rootScope.$broadcast('viewLoaded');
+ }
+ function deviceWithoutData(data) {
+ $rootScope.$broadcast('deviceWithoutData', data);
+ }
+ function deviceIsPrivate(data) {
+ $rootScope.$broadcast('deviceIsPrivate', data);
+ }
+ function goToLocation(data) {
+ $rootScope.$broadcast('goToLocation', data);
+ }
+ function mapStateLoading() {
+ $rootScope.$broadcast('mapStateLoading');
+ }
+ function mapStateLoaded() {
+ $rootScope.$broadcast('mapStateLoaded');
+ }
+ }
+})();
- text.append('tspan')
- .attr('class', 'popup_date')
- .attr('x', (- popupWidth / 2))
- .attr('dx', 8)
- .attr('y', popupHeight - 2)
- .attr('dy', 0)
- .attr( 'text-anchor', 'start' );
+(function() {
+ 'use strict';
- svg.append('rect')
- .attr('class', 'overlay')
- .attr('width', width)
- .attr('height', height)
- .on('mouseover', function() {
- focusCompare.style('display', null);
- focusMain.style('display', null);
- popup.style('display', null);
- })
- .on('mouseout', function() {
- focusCompare.style('display', 'none');
- focusMain.style('display', 'none');
- popup.style('display', 'none');
- })
- .on('mousemove', mousemove);
+ /**
+ * TODO: Improvement These directives can be split up each one in a different file
+ */
- function mousemove() {
- var bisectDate = d3.bisector(function(d) { return d.date; }).left;
+ angular.module('app.components')
+ .directive('moveDown', moveDown)
+ .directive('stick', stick)
+ .directive('blur', blur)
+ .directive('focus', focus)
+ .directive('changeMapHeight', changeMapHeight)
+ .directive('changeContentMargin', changeContentMargin)
+ .directive('focusInput', focusInput);
- var x0 = xScale.invert(d3.mouse(this)[0]);
- var i = bisectDate(data[1], x0, 1);
- var d0 = data[1][i - 1];
- var d1 = data[1][i];
- var d = x0 - d0.date > d1.date - x0 ? d1 : d0;
- focusCompare.attr('transform', 'translate(' + xScale(d.date) + ', ' + yScale1(d.count) + ')');
+ /**
+ * It moves down kit section to ease the transition after the kit menu is sticked to the top
+ *
+ */
+ moveDown.$inject = [];
+ function moveDown() {
+ function link(scope, element) {
+ scope.$watch('moveDown', function(isTrue) {
+ if(isTrue) {
+ element.addClass('move_down');
+ } else {
+ element.removeClass('move_down');
+ }
+ });
+ }
- var dMain0 = data[0][i - 1];
- var dMain1 = data[0][i];
- var dMain = x0 - dMain0.date > dMain1.date - x0 ? dMain1 : dMain0;
- focusMain.attr('transform', 'translate(' + xScale(dMain.date) + ', ' + yScale0(dMain.count) + ')');
+ return {
+ link: link,
+ scope: false,
+ restrict: 'A'
+ };
+ }
- var popupText = popup.select('text');
- var textMain = popupText.select('.popup_main');
- textMain.select('.popup_value').text(parseValue(dMain.value));
- textMain.select('.popup_unit').text(options.unit[0]);
- var textCompare = popupText.select('.popup_compare');
- textCompare.select('.popup_value').text(parseValue(d.value));
- textCompare.select('.popup_unit').text(options.unit[1]);
- var date = popupText.select('.popup_date').text(parseTime(d.date));
+ /**
+ * It sticks kit menu when kit menu touchs navbar on scrolling
+ *
+ */
+ stick.$inject = ['$window', '$timeout'];
+ function stick($window, $timeout) {
+ function link(scope, element) {
+ var elementPosition = element[0].offsetTop;
+ //var elementHeight = element[0].offsetHeight;
+ var navbarHeight = angular.element('.stickNav').height();
- var textContainers = [
- textMain,
- textCompare,
- date
- ];
+ $timeout(function() {
+ elementPosition = element[0].offsetTop;
+ //var elementHeight = element[0].offsetHeight;
+ navbarHeight = angular.element('.stickNav').height();
+ }, 1000);
- var popupWidth = resizePopup(popupContainer, textContainers);
- if(xScale(d.date) + 80 + popupWidth > options.container.clientWidth) {
- popup.attr('transform', 'translate(' + (xScale(d.date) - 120) + ', ' + (d3.mouse(this)[1] - 20) + ')');
+ angular.element($window).on('scroll', function() {
+ var windowPosition = document.body.scrollTop;
+
+ //sticking menu and moving up/down
+ if(windowPosition + navbarHeight >= elementPosition) {
+ element.addClass('stickMenu');
+ scope.$apply(function() {
+ scope.moveDown = true;
+ });
} else {
- popup.attr('transform', 'translate(' + (xScale(d.date) + 80) + ', ' + (d3.mouse(this)[1] - 20) + ')');
+ element.removeClass('stickMenu');
+ scope.$apply(function() {
+ scope.moveDown = false;
+ });
}
- }
+ });
}
- function xGrid() {
- return d3.svg.axis()
- .scale(xScale)
- .orient('bottom')
- .ticks(5);
- }
+ return {
+ link: link,
+ scope: false,
+ restrict: 'A'
+ };
+ }
- function yGrid() {
- return d3.svg.axis()
- .scale(yScale0)
- .orient('left')
- .ticks(5);
- }
+ /**
+ * Unused directive. Double-check is not being used before removing it
+ *
+ */
- function parseValue(value) {
- if(value === null) {
- return 'No data on the current timespan';
- } else if(value.toString().indexOf('.') !== -1) {
- var result = value.toString().split('.');
- return result[0] + '.' + result[1].slice(0, 2);
- } else if(value > 99.99) {
- return value.toString();
- } else {
- return value.toString().slice(0, 2);
- }
- }
+ function blur() {
- function parseTime(time) {
- return moment(time).format('h:mm a ddd Do MMM YYYY');
+ function link(scope, element) {
+
+ scope.$on('blur', function() {
+ element.addClass('blur');
+ });
+
+ scope.$on('unblur', function() {
+ element.removeClass('blur');
+ });
}
- function resizePopup(popupContainer, textContainers) {
- if(!textContainers.length) {
- return;
- }
+ return {
+ link: link,
+ scope: false,
+ restrict: 'A'
+ };
+ }
- var widestElem = textContainers.reduce(function(widestElemSoFar, textContainer) {
- var currentTextContainerWidth = getContainerWidth(textContainer);
- var prevTextContainerWidth = getContainerWidth(widestElemSoFar);
- return prevTextContainerWidth >= currentTextContainerWidth ? widestElemSoFar : textContainer;
- }, textContainers[0]);
+ /**
+ * Used to remove nav and unable scrolling when searching
+ *
+ */
+ focus.$inject = ['animation'];
+ function focus(animation) {
+ function link(scope, element) {
+ element.on('focusin', function() {
+ animation.removeNav();
+ });
- var margins = widestElem.attr('dx') * 2;
+ element.on('focusout', function() {
+ animation.addNav();
+ });
- popupContainer
- .attr('width', getContainerWidth(widestElem) + margins);
+ var searchInput = element.find('input');
+ searchInput.on('blur', function() {
+ //enable scrolling on body when search input is not active
+ angular.element(document.body).css('overflow', 'auto');
+ });
- function getContainerWidth(container) {
- var node = container.node();
- var width;
- if(node.getComputedTextLength) {
- width = node.getComputedTextLength();
- } else if(node.getBoundingClientRect) {
- width = node.getBoundingClientRect().width;
- } else {
- width = node.getBBox().width;
- }
- return width;
- }
- return getContainerWidth(widestElem) + margins;
+ searchInput.on('focus', function() {
+ angular.element(document.body).css('overflow', 'hidden');
+ });
}
+
+ return {
+ link: link
+ };
}
-})();
+ /**
+ * Changes map section based on screen size
+ *
+ */
+ changeMapHeight.$inject = ['$document', 'layout', '$timeout'];
+ function changeMapHeight($document, layout, $timeout) {
+ function link(scope, element) {
-(function(){
- 'use strict';
+ var screenHeight = $document[0].body.clientHeight;
+ var navbarHeight = angular.element('.stickNav').height();
- angular.module('app.components')
- .directive('apiKey', apiKey);
+ // var overviewHeight = angular.element('.kit_overview').height();
+ // var menuHeight = angular.element('.kit_menu').height();
+ // var chartHeight = angular.element('.kit_chart').height();
- function apiKey(){
- return {
- scope: {
- apiKey: '=apiKey'
- },
- restrict: 'A',
- controller: 'ApiKeyController',
- controllerAs: 'vm',
- templateUrl: 'app/components/apiKey/apiKey.html'
- };
- }
-})();
+ function resizeMap(){
+ $timeout(function() {
+ var overviewHeight = angular.element('.over_map').height();
-(function(){
- 'use strict';
+ var objectsHeight = navbarHeight + overviewHeight;
+ var objectsHeightPercentage = parseInt((objectsHeight * 100) / screenHeight);
+ var mapHeightPercentage = 100 - objectsHeightPercentage;
- angular.module('app.components')
- .controller('ApiKeyController', ApiKeyController);
+ element.css('height', mapHeightPercentage + '%');
- ApiKeyController.$inject = ['alert'];
- function ApiKeyController(alert){
- var vm = this;
+ var aboveTheFoldHeight = screenHeight - overviewHeight;
+ angular
+ .element('section[change-content-margin]')
+ .css('margin-top', aboveTheFoldHeight + 'px');
+ });
+ }
- vm.copied = copied;
- vm.copyFail = copyFail;
+ resizeMap();
- ///////////////
+ scope.element = element;
- function copied(){
- alert.success('API key copied to your clipboard.');
+ scope.$on('resizeMapHeight',function(){
+ resizeMap();
+ });
+
+ }
+
+ return {
+ link: link,
+ scope: true,
+ restrict: 'A'
+ };
}
- function copyFail(err){
- console.log('Copy error: ', err);
- alert.error('Oops! An error occurred copying the api key.');
+ /**
+ * Changes margin on kit section based on above-the-fold space left after map section is resize
+ */
+
+ changeContentMargin.$inject = ['layout', '$timeout', '$document'];
+ function changeContentMargin(layout, $timeout, $document) {
+ function link(scope, element) {
+ var screenHeight = $document[0].body.clientHeight;
+
+ var overviewHeight = angular.element('.over_map').height();
+
+ var aboveTheFoldHeight = screenHeight - overviewHeight;
+ element.css('margin-top', aboveTheFoldHeight + 'px');
+ }
+
+ return {
+ link: link
+ };
}
- }
+ /**
+ * Fixes autofocus for inputs that are inside modals
+ *
+ */
+ focusInput.$inject = ['$timeout'];
+ function focusInput($timeout) {
+ function link(scope, elem) {
+ $timeout(function() {
+ elem.focus();
+ });
+ }
+ return {
+ link: link
+ };
+ }
})();
(function() {
'use strict';
angular.module('app.components')
- .factory('alert', alert);
-
- alert.$inject = ['$mdToast'];
- function alert($mdToast) {
- var service = {
- success: success,
- error: error,
- info: {
- noData: {
- visitor: infoNoDataVisitor,
- owner: infoNoDataOwner,
- private: infoDataPrivate,
- },
- longTime: infoLongTime,
- // TODO: Refactor, check why this was removed
- // inValid: infoDataInvalid,
- generic: info
- }
- };
+ .directive('activeButton', activeButton);
- return service;
+ /**
+ * Used to highlight and unhighlight buttons on kit menu
+ *
+ * It attaches click handlers dynamically
+ */
- ///////////////////
+ activeButton.$inject = ['$timeout', '$window'];
+ function activeButton($timeout, $window) {
+ return {
+ link: link,
+ restrict: 'A'
- function success(message) {
- toast('success', message);
- }
+ };
- function error(message) {
- toast('error', message);
- }
+ ////////////////////////////
- function infoNoDataVisitor() {
- info('Woah! We couldn\'t locate this kit on the map because it hasn\'t published any data. Leave a ' +
- 'comment to let its owner know.',
- 10000,
- {
- button: 'Leave comment',
- href: 'https://forum.smartcitizen.me/'
- });
- }
+ function link(scope, element) {
+ var childrens = element.children();
+ var container;
- function infoNoDataOwner() {
- info('Woah! We couldn\'t locate this kit on the map because it hasn\'t published any data.',
- 10000);
- }
+ $timeout(function() {
+ var navbar = angular.element('.stickNav');
+ var kitMenu = angular.element('.kit_menu');
+ var kitOverview = angular.element('.kit_overview');
+ var kitDashboard = angular.element('.kit_chart');
+ var kitDetails = angular.element('.kit_details');
+ var kitOwner = angular.element('.kit_owner');
+ var kitComments = angular.element('.kit_comments');
- function infoDataPrivate() {
- info('Device not found, or it has been set to private. Leave a ' +
- 'comment to let its owner know you\'re interested.',
- 10000,
- {
- button: 'Leave comment',
- href: 'https://forum.smartcitizen.me/'
- });
- }
+ container = {
+ navbar: {
+ height: navbar.height()
+ },
+ kitMenu: {
+ height: kitMenu.height()
+ },
+ kitOverview: {
+ height: kitOverview.height(),
+ offset: kitOverview.offset().top,
+ buttonOrder: 0
+ },
+ kitDashboard: {
+ height: kitDashboard.height(),
+ offset: kitDashboard.offset().top,
+ buttonOrder: 40
+ },
+ kitDetails: {
+ height: kitDetails.height(),
+ offset: kitDetails.offset() ? kitDetails.offset().top : 0,
+ buttonOrder: 1
+ },
+ kitOwner: {
+ height: kitOwner.height(),
+ offset: kitOwner.offset() ? kitOwner.offset().top : 0,
+ buttonOrder: 2
+ },
+ kitComments: {
+ height: kitComments.height(),
+ offset: kitComments.offset() ? kitComments.offset().top : 0,
+ buttonOrder: 3
+ }
+ };
+ }, 1000);
- // TODO: Refactor, check why this was removed
- // function infoDataInvalid() {
- // info('Device not found, or it has been set to private.',
- // 10000);
- // }
+ function scrollTo(offset) {
+ if(!container) {
+ return;
+ }
+ angular.element($window).scrollTop(offset - container.navbar.height - container.kitMenu.height);
+ }
- function infoLongTime() {
- info('😅 It looks like this kit hasn\'t posted any data in a long ' +
- 'time. Why not leave a comment to let its owner know?',
- 10000,
- {
- button: 'Leave comment',
- href: 'https://forum.smartcitizen.me/'
- });
- }
+ function getButton(buttonOrder) {
+ return childrens[buttonOrder];
+ }
- function info(message, delay, options) {
- if(options && options.button) {
- toast('infoButton', message, options, undefined, delay);
- } else {
- toast('info', message, options, undefined, delay);
- }
- }
+ function unHighlightButtons() {
+ //remove border, fill and stroke of every icon
+ var activeButton = angular.element('.md-button.button_active');
+ if(activeButton.length) {
+ activeButton.removeClass('button_active');
- function toast(type, message, options, position, delay) {
- position = position === undefined ? 'top': position;
- delay = delay === undefined ? 5000 : delay;
+ var strokeContainer = activeButton.find('.stroke_container');
+ strokeContainer.css('stroke', 'none');
+ strokeContainer.css('stroke-width', '1');
- $mdToast.show({
- controller: 'AlertController',
- controllerAs: 'vm',
- templateUrl: 'app/components/alert/alert' + type + '.html',
- hideDelay: delay,
- position: position,
- locals: {
- message: message,
- button: options && options.button,
- href: options && options.href
+ var fillContainer = strokeContainer.find('.fill_container');
+ fillContainer.css('fill', '#FF8600');
+ }
}
- });
- }
- }
-})();
-(function() {
- 'use strict';
+ function highlightButton(button) {
+ var clickedButton = angular.element(button);
+ //add border, fill and stroke to every icon
+ clickedButton.addClass('button_active');
- angular.module('app.components')
- .controller('AlertController', AlertController);
+ var strokeContainer = clickedButton.find('.stroke_container');
+ strokeContainer.css('stroke', 'white');
+ strokeContainer.css('stroke-width', '0.01px');
- AlertController.$inject = ['$scope', '$mdToast', 'message', 'button', 'href'];
- function AlertController($scope, $mdToast, message, button, href) {
- var vm = this;
+ var fillContainer = strokeContainer.find('.fill_container');
+ fillContainer.css('fill', 'white');
+ }
- vm.close = close;
- vm.message = message;
- vm.button = button;
- vm.href = href;
+ //attach event handlers for clicks for every button and scroll to a section when clicked
+ _.each(childrens, function(button) {
+ angular.element(button).on('click', function() {
+ var buttonOrder = angular.element(this).index();
+ for(var elem in container) {
+ if(container[elem].buttonOrder === buttonOrder) {
+ var offset = container[elem].offset;
+ scrollTo(offset);
+ angular.element($window).trigger('scroll');
+ }
+ }
+ });
+ });
- // hideAlert will be triggered on state change
- $scope.$on('hideAlert', function() {
- close();
- });
+ var currentSection;
- ///////////////////
+ //on scroll, check if window is on a section
+ angular.element($window).on('scroll', function() {
+ if(!container){ return; }
- function close() {
- $mdToast.hide();
+ var windowPosition = document.body.scrollTop;
+ var appPosition = windowPosition + container.navbar.height + container.kitMenu.height;
+ var button;
+ if(currentSection !== 'none' && appPosition <= container.kitOverview.offset) {
+ button = getButton(container.kitOverview.buttonOrder);
+ unHighlightButtons();
+ currentSection = 'none';
+ } else if(currentSection !== 'overview' && appPosition >= container.kitOverview.offset && appPosition <= container.kitOverview.offset + container.kitOverview.height) {
+ button = getButton(container.kitOverview.buttonOrder);
+ unHighlightButtons();
+ highlightButton(button);
+ currentSection = 'overview';
+ } else if(currentSection !== 'details' && appPosition >= container.kitDetails.offset && appPosition <= container.kitDetails.offset + container.kitDetails.height) {
+ button = getButton(container.kitDetails.buttonOrder);
+ unHighlightButtons();
+ highlightButton(button);
+ currentSection = 'details';
+ } else if(currentSection !== 'owner' && appPosition >= container.kitOwner.offset && appPosition <= container.kitOwner.offset + container.kitOwner.height) {
+ button = getButton(container.kitOwner.buttonOrder);
+ unHighlightButtons();
+ highlightButton(button);
+ currentSection = 'owner';
+ } else if(currentSection !== 'comments' && appPosition >= container.kitComments.offset && appPosition <= container.kitComments.offset + container.kitOwner.height) {
+ button = getButton(container.kitComments.buttonOrder);
+ unHighlightButtons();
+ highlightButton(button);
+ currentSection = 'comments';
+ }
+ });
}
}
})();