Custom comparisons, performance improvements

This commit is contained in:
Colin McLeod
2015-05-20 00:29:24 -07:00
parent 02fe76f43b
commit 6c1e3a7410
146 changed files with 1096 additions and 1062 deletions

View File

@@ -1,66 +1,211 @@
angular.module('app').controller('ComparisonController', ['$rootScope', '$filter', '$scope', 'ShipFacets', 'ShipsDB', 'Ship', 'Persist', 'Serializer', function ($rootScope, $filter, $scope, ShipFacets, Ships, Ship, Persist, Serializer) {
$rootScope.title = 'Coriolis - Comparison';
$rootScope.bodyClass = null;
$scope.facets = ShipFacets;
$scope.subFacets = [];
for (var i = 0, l = $scope.facets.length; i < l; i++) {
var facet = $scope.facets[i];
if(facet.prop) {
$scope.subFacets.push({
prop: facet.prop,
fmt: $rootScope[facet.fmt],
unit: facet.unit
});
} else {
for (var j = 0, pl = facet.props.length; j < pl; j++) {
$scope.subFacets.push({
sub: true,
start: j == 0,
prop: facet.props[j],
label: facet.lbls[j],
fmt: $rootScope[facet.fmt],
unit: facet.unit
});
}
}
}
var comparison = $scope.comparison = [];
var orderBy = $filter('orderBy');
var buildCount = 0;
for (var shipId in Persist.builds) {
var data = Ships[shipId];
for (var buildName in Persist.builds[shipId]) {
var code = Persist.builds[shipId][buildName];
var b = new Ship(shipId, data.properties, data.slots); // Create a new Ship instance
Serializer.toShip(b, code); // Populate components from 'code' URL param
// Extend ship instance and add properties below
b.buildName = buildName;
b.code = code;
b.pctRetracted = b.powerRetracted / b.powerAvailable;
b.pctDeployed = b.powerDeployed / b.powerAvailable;
comparison.push(b); // Add ship build to comparison
}
}
$scope.chartHeight = 45 + (25 * comparison.length);
$scope.predicate = 'ship.name';
angular.module('app').controller('ComparisonController', ['lodash', '$rootScope', '$filter', '$scope', '$state', '$stateParams', 'Utils', 'ShipFacets', 'ShipsDB', 'Ship', 'Persist', 'Serializer', function (_, $rootScope, $filter, $scope, $state, $stateParams, Utils, ShipFacets, Ships, Ship, Persist, Serializer) {
$rootScope.title = 'Coriolis - Compare';
$scope.predicate = 'ship.name'; // Sort by ship name as default
$scope.desc = true;
$scope.facetSortOpts = { containment: '#facet-container', orderChanged: function () { $scope.saved = false; } };
$scope.builds = [];
$scope.unusedBuilds = [];
$scope.name = $stateParams.name;
$scope.compareMode = !$stateParams.code;
$scope.importObj = {}; // Used for importing comparison builds (from permalinked comparison)
var defaultFacets = [9,6,4,1,3,2]; // Reverse order of Armour, Shields, Speed, Jump Range, Cargo Capacity, Cost
var facets = $scope.facets = angular.copy(ShipFacets);
$scope.sortProperty = function (e) {
var prop = angular.element(e.target).attr('prop'); // Get component ID
if(prop) {
$scope.sort(prop);
/**
* Add an existing build to the comparison. The build must be saved locally.
* @param {string} shipId The unique ship key/id
* @param {string} buildName The build name
*/
$scope.addBuild = function (shipId, buildName, code) {
var data = Ships[shipId]; // Get ship properties
var code = code? code : Persist.builds[shipId][buildName]; // Retrieve build code if not passed
var b = new Ship(shipId, data.properties, data.slots); // Create a new Ship instance
Serializer.toShip(b, code); // Populate components from code
// Extend ship instance and add properties below
b.buildName = buildName;
b.code = code;
b.pctRetracted = b.powerRetracted / b.powerAvailable;
b.pctDeployed = b.powerDeployed / b.powerAvailable;
$scope.builds.push(b); // Add ship build to comparison
$scope.builds = $filter('orderBy')($scope.builds, $scope.predicate, $scope.desc); // Resort
_.remove($scope.unusedBuilds, function (b) { // Remove from unused builds
return b.id == shipId && b.buildName == buildName;
});
$scope.saved = false;
};
/**
* Removes a build from the comparison
* @param {string} shipId The unique ship key/id
* @param {string} buildName The build name
*/
$scope.removeBuild = function (shipId, buildName) {
_.remove($scope.builds, function (b) {
if (b.id == shipId && b.buildName == buildName) {
$scope.unusedBuilds.push({id: shipId, buildName: buildName, name: b.name}); // Add build back to unused builds
return true;
}
return false;
});
$scope.saved = false;
};
/**
* Toggles the selected the set of facets used in the comparison
* @param {number} i The index of the facet in facets
*/
$scope.toggleFacet = function (i) {
facets[i].active = !facets[i].active;
$scope.tblUpdate = !$scope.tblUpdate; // Simple switch to trigger the table to update
$scope.saved = false;
};
/**
* Click handler for sorting by facets in the table
* @param {object} e Event object
*/
$scope.handleClick = function (e) {
var elem = angular.element(e.target);
if(elem.attr('prop')) { // Get component ID
$scope.sort(elem.attr('prop'));
}
else if (elem.attr('del')) { // Delete index
$scope.removeBuild(elem.attr('del'));
}
};
/**
* Sort the comparison array based on the selected facet / ship property
* @param {string} key Ship property
*/
$scope.sort = function (key) {
$scope.desc = ($scope.predicate == key)? !$scope.desc : $scope.desc;
$scope.predicate = key;
$scope.comparison = orderBy($scope.comparison, $scope.predicate, $scope.desc);
$scope.builds = $filter('orderBy')($scope.builds, $scope.predicate, $scope.desc);
};
$scope.sort('name');
/**
* Saves the current comparison's selected facets and builds
*/
$scope.save = function() {
$scope.name = $scope.name.trim();
if ($scope.name == 'all') {
return;
}
var selectedFacets = [];
facets.forEach(function(f) {
if(f.active) {
selectedFacets.unshift(f.index);
}
});
Persist.saveComparison($scope.name, $scope.builds, selectedFacets);
$state.go('compare', {name: $scope.name}, {location:'replace', reload:false});
$scope.saved = true;
};
/**
* Permantently delete the current comparison
*/
$scope.delete = function() {
Persist.deleteComparison($scope.name);
$state.go('compare', {name: null}, {location:'replace', reload:true});
};
$scope.nameChange = function() {
$scope.saved = false;
};
$scope.selectBuilds = function(s, e) {
e.stopPropagation();
$scope.showBuilds = s;
};
$scope.permalink = function(e) {
e.stopPropagation();
$state.go('modal.link', {url: genPermalink()});
};
$scope.embed = function(e) {
e.stopPropagation();
var promise = Utils.shortenUrl( genPermalink()).then(
function (shortUrl) {
return Utils.comparisonBBCode(facets, $scope.builds, shortUrl);
},
function (e) {
return 'Error - ' + e.statusText;
}
);
$state.go('modal.export', {promise: promise, title:'Forum BBCode'});
};
function genPermalink() {
var selectedFacets = [];
facets.forEach(function(f) {
if(f.active) {
selectedFacets.unshift(f.index);
}
});
var code = Serializer.fromComparison(
$scope.name,
$scope.builds,
selectedFacets,
$scope.predicate,
$scope.desc
);
return $state.href('comparison', {code: code}, {absolute:true});
};
/* Event listeners */
$scope.$on('close', function() {
$scope.showBuilds = false;
});
/* Initialization */
if ($scope.compareMode) {
if ($scope.name == 'all') {
for (var shipId in Persist.builds) {
for (var buildName in Persist.builds[shipId]) {
$scope.addBuild(shipId, buildName);
}
}
} else {
for (var shipId in Persist.builds) {
for (var buildName in Persist.builds[shipId]) {
$scope.unusedBuilds.push({id: shipId, buildName: buildName, name: Ships[shipId].properties.name});
}
}
var comparisonData = Persist.getComparison($scope.name);
if (comparisonData) {
defaultFacets = comparisonData.facets;
comparisonData.builds.forEach(function (b) {
$scope.addBuild(b.shipId, b.buildName);
});
$scope.saved = true;
}
}
} else {
try {
var comparisonData = Serializer.toComparison($stateParams.code);
defaultFacets = comparisonData.f;
$scope.name = comparisonData.n
$scope.predicate = comparisonData.p;
$scope.desc = comparisonData.d;
comparisonData.b.forEach(function (build) {
$scope.addBuild(build.s, build.n, build.c);
if(!$scope.importObj[build.s]) {
$scope.importObj[build.s] = {};
}
$scope.importObj[build.s][build.n] = build.c;
});
} catch (e) {
throw { type: 'bad-comparison', message: e.message, details: e };
}
}
// Replace fmt with actual format function as defined in rootScope and retain original index
facets.forEach(function(f,i) { f.fmt = $rootScope[f.fmt]; f.index = i; });
// Remove default facets, mark as active, and add them back in selected order
_.pullAt(facets, defaultFacets).forEach(function (f) { f.active = true; facets.unshift(f); });
}]);

View File

@@ -7,24 +7,23 @@ angular.module('app')
switch ($scope.type) {
case 404:
$rootScope.bodyClass = 'deep-space';
$scope.msgPre = 'Page';
$scope.msgHighlight = $scope.path;
$scope.msgPost = 'Not Found';
break;
case 'no-ship':
$rootScope.bodyClass = 'docking-bay';
//$rootScope.bodyClass = 'docking-bay';
$scope.msgPre = 'Ship';
$scope.msgHighlight = $p.message;
$scope.msgPost = 'does not exist';
break;
case 'build-fail':
$rootScope.bodyClass = 'ship-explode'; // TODO: create background imag for this
//$rootScope.bodyClass = 'ship-explode'; // TODO: create background imag for this
$scope.msgPre = 'Build Failure!';
$scope.details = $p.details;
break;
default:
$rootScope.bodyClass = 'thargoid'; // TODO: create background imag for this
//$rootScope.bodyClass = 'thargoid'; // TODO: create background imag for this
$scope.msgPre = "Uh, Jameson, we have a problem..";
$scope.errorMessage = $p.message;
$scope.details = $p.details;

View File

@@ -1,7 +1,18 @@
angular.module('app').controller('ExportController', ['$scope', 'Persist', function ($scope, Persist) {
$scope.builds = {
builds: Persist.builds
// TODO: add comparisons
angular.module('app').controller('ExportController', ['$scope', '$stateParams', function ($scope, $stateParams) {
$scope.title = $stateParams.title || 'Export';
if ($stateParams.promise) {
$scope.export = 'Generating...';
$stateParams.promise.then(function(data){
$scope.export = data;
});
} else {
$scope.export = angular.toJson($stateParams.data, true);
}
$scope.onTextClick = function ($event) {
$event.target.select();
};
}]);

View File

@@ -1,7 +1,9 @@
angular.module('app').controller('ImportController', ['$scope', 'ShipsDB', 'Ship', 'Persist', 'Serializer', function ($scope, Ships, Ship, Persist, Serializer) {
angular.module('app').controller('ImportController', ['$scope', '$stateParams', 'ShipsDB', 'Ship', 'Persist', 'Serializer', function ($scope, $stateParams, Ships, Ship, Persist, Serializer) {
$scope.jsonValid = false;
$scope.importData = null;
$scope.errorMsg = null;
$scope.canEdit = true;
$scope.builds = $stateParams.obj || null;
$scope.validateJson = function() {
var importObj = null;
@@ -10,7 +12,7 @@ angular.module('app').controller('ImportController', ['$scope', 'ShipsDB', 'Ship
$scope.builds = null;
$scope.ships = Ships;
if(!$scope.importData) return;
if (!$scope.importData) { return; }
try {
importObj = angular.fromJson($scope.importData);
@@ -24,15 +26,15 @@ angular.module('app').controller('ImportController', ['$scope', 'ShipsDB', 'Ship
return;
}
if ((!importObj.builds || !Object.keys(importObj.builds).length) && (!importObj.comparisons || !Object.keys(importObj.comparisons).length)) {
$scope.errorMsg = 'No builds or comparisons in data';
if ((!importObj.builds || !Object.keys(importObj.builds).length)) {
$scope.errorMsg = 'No builds in data';
return;
}
for (var shipId in importObj.builds) {
var shipData = Ships[shipId];
if (shipData) {
for (buildName in importObj.builds[shipId]) {
for (var buildName in importObj.builds[shipId]) {
if (typeof importObj.builds[shipId][buildName] != 'string') {
$scope.errorMsg = shipData.properties.name + ' build "' + buildName + '" must be a string!';
return;
@@ -41,7 +43,6 @@ angular.module('app').controller('ImportController', ['$scope', 'ShipsDB', 'Ship
// Actually build the ship with the code to ensure it's valid
Serializer.toShip(new Ship(shipId, shipData.properties, shipData.slots), importObj.builds[shipId][buildName]);
} catch (e) {
console.log(e);
$scope.errorMsg = shipData.properties.name + ' build "' + buildName + '" is not valid!';
return;
}
@@ -53,15 +54,12 @@ angular.module('app').controller('ImportController', ['$scope', 'ShipsDB', 'Ship
$scope.builds = importObj.builds;
}
// Check for comparison object
// if (importObj.comparisons)
$scope.jsonValid = true;
};
$scope.hasBuild = function (shipId, name) {
return Persist.getBuild(shipId, name) != null;
}
return Persist.getBuild(shipId, name) !== null;
};
$scope.process = function() {
var builds = $scope.builds;
@@ -75,7 +73,6 @@ angular.module('app').controller('ImportController', ['$scope', 'ShipsDB', 'Ship
};
}
}
$scope.processed = true;
};
@@ -93,4 +90,12 @@ angular.module('app').controller('ImportController', ['$scope', 'ShipsDB', 'Ship
$scope.$parent.dismiss();
};
/* Initialization */
if ($scope.builds) { // If import is passed an build object
$scope.canEdit = false;
$scope.process();
}
}]);

View File

@@ -0,0 +1,16 @@
angular.module('app').controller('LinkController', ['$scope', 'Utils', '$stateParams', function ($scope, Utils, $stateParams) {
$scope.url = $stateParams.url;
$scope.shortenedUrl = 'Shortening...';
$scope.onTextClick = function ($event) {
$event.target.select();
};
Utils.shortenUrl($scope.url)
.then(function(url) {
$scope.shortenedUrl = url;
},function(e) {
$scope.shortenedUrl = 'Error - ' + e.statusText;
});
}]);

View File

@@ -1,5 +1,5 @@
angular.module('app').controller('ModalController', ['$rootScope','$scope', '$state', function ($rootScope, $scope, $state) {
var dismissListener;
$scope.dismiss = function() {
if ($rootScope.prevState) {
var state = $rootScope.prevState;
@@ -7,7 +7,7 @@ angular.module('app').controller('ModalController', ['$rootScope','$scope', '$st
} else {
$state.go('shipyard');
}
}
};
$scope.$on('close', $scope.dismiss);

View File

@@ -11,6 +11,7 @@ angular.module('app').controller('OutfitController', ['$rootScope','$scope', '$s
}
$scope.buildName = $p.bn;
$rootScope.title = ship.name + ($scope.buildName? ' - ' + $scope.buildName : '');
$scope.ship = ship;
$scope.pp = ship.common[0]; // Power Plant
$scope.th = ship.common[1]; // Thruster
@@ -24,11 +25,25 @@ angular.module('app').controller('OutfitController', ['$rootScope','$scope', '$s
$scope.availCS = Components.forShip(ship.id);
$scope.selectedSlot = null;
$scope.savedCode = Persist.getBuild(ship.id, $scope.buildName);
$rootScope.title = ship.name + ($scope.buildName? ' - ' + $scope.buildName: '');
$rootScope.bodyClass = 'docking-bay';
// for debugging
window.myScope = $scope;
$scope.jrSeries = {
xMin: ship.unladenMass,
xMax: ship.ladenMass,
func: ship.jumpRangeWithMass.bind(ship)
};
$scope.jrChart = {
labels: {
xAxis: {
title:'Ship Mass',
unit: 'T'
},
yAxis: {
title:'Jump Range',
unit: 'LY'
}
},
watch: $scope.fsd
};
/**
* 'Opens' a select for component selection.
@@ -72,7 +87,7 @@ angular.module('app').controller('OutfitController', ['$rootScope','$scope', '$s
$scope.code = Serializer.fromShip(ship);
updateState();
}
}
};
/**
* Reload the build from the last save.
@@ -103,7 +118,7 @@ angular.module('app').controller('OutfitController', ['$rootScope','$scope', '$s
$scope.savedCode = $scope.code;
updateState();
}
}
};
/**
* Permanently delete the current build and redirect/reload this controller
@@ -111,13 +126,12 @@ angular.module('app').controller('OutfitController', ['$rootScope','$scope', '$s
*/
$scope.deleteBuild = function() {
Persist.deleteBuild(ship.id, $scope.buildName);
$rootScope.$broadcast('buildDeleted', $scope.saveName, ship.id);
$state.go('outfit', {shipId: ship.id, code: null, bn: null}, {location:'replace', reload:true});
}
};
$scope.bnChange = function(){
$scope.savedCode = Persist.getBuild(ship.id, $scope.buildName);
}
};
$scope.toggleCost = function(item) {
item.incCost = !item.incCost;
@@ -132,24 +146,17 @@ angular.module('app').controller('OutfitController', ['$rootScope','$scope', '$s
// Utilify functions
function updateState() {
$state.go('outfit', {shipId: ship.id, code: $scope.code, bn: $scope.buildName}, {location:'replace', notify:false});
$scope.jrSeries.xMin = ship.unladenMass;
$scope.jrSeries.xMax = ship.ladenMass;
}
// Event listeners
$scope.$on('keyup', function (e, keyEvent) {
// CTRL + S or CMD + S will override the default and save the build is possible
if (keyEvent.keycode == 83 && keyEvent.ctrlKey) {
e.preventDefault();
$scope.saveBuild();
}
});
// Hide any open menu/slot/etc if escape key is pressed
$scope.$on('escape', function (e, keyEvent) {
$scope.$on('escape', function () {
$scope.selectedSlot = null;
$scope.$apply();
});
// Hide any open menu/slot/etc if the background is clicked
$scope.$on('close', function (e, keyEvent) {
$scope.$on('close', function () {
$scope.selectedSlot = null;
});

View File

@@ -1,5 +1,4 @@
angular.module('app').controller('ShipyardController', ['$rootScope', 'ShipsDB', function ($rootScope, ships) {
$rootScope.title = 'Coriolis';
$rootScope.bodyClass = 'docking-bay';
$rootScope.ships = ships;
}]);