mirror of
https://github.com/EDCD/coriolis.git
synced 2025-12-10 15:15:34 +00:00
Custom comparisons, performance improvements
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
angular.module('app', ['ui.router', 'ct.ui.router.extras.sticky', 'shipyard', 'ngLodash', 'app.templates'])
|
||||
angular.module('app', ['ngTouch', 'ui.router', 'ct.ui.router.extras.sticky', 'ui.sortable', 'shipyard', 'ngLodash', 'app.templates'])
|
||||
.run(['$rootScope', '$location', '$window', '$document','$state','commonArray','shipPurpose','shipSize','hardPointClass','internalGroupMap','hardpointsGroupMap', function ($rootScope, $location, $window, $doc, $state, CArr, shipPurpose, sz, hpc, igMap, hgMap) {
|
||||
// Redirect any state transition errors to the error controller/state
|
||||
$rootScope.$on('$stateChangeError', function(e, toState, toParams, fromState, fromParams, error){
|
||||
@@ -9,8 +9,8 @@ angular.module('app', ['ui.router', 'ct.ui.router.extras.sticky', 'shipyard', 'n
|
||||
// Track on Google analytics if available
|
||||
$rootScope.$on('$stateChangeSuccess', function(e, to, toParams, from, fromParams) {
|
||||
$rootScope.prevState = { name: from.name, params: fromParams };
|
||||
if(to.url) { // Only track states that have a URL
|
||||
if ($window.ga) ga('send', 'pageview', {page: $location.path()});
|
||||
if(to.url && $window.ga) { // Only track states that have a URL
|
||||
ga('send', 'pageview', {page: $location.path()});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -26,8 +26,8 @@ angular.module('app', ['ui.router', 'ct.ui.router.extras.sticky', 'shipyard', 'n
|
||||
// Formatters
|
||||
$rootScope.fCrd = d3.format(',.0f');
|
||||
$rootScope.fPwr = d3.format(',.2f');
|
||||
$rootScope.fRound = function(d) { return d3.round(d, 2) };
|
||||
$rootScope.fRound4 = function(d) { return d3.round(d, 4) };
|
||||
$rootScope.fRound = function(d) { return d3.round(d, 2); };
|
||||
$rootScope.fRound4 = function(d) { return d3.round(d, 4); };
|
||||
$rootScope.fPct = d3.format('.2%');
|
||||
$rootScope.fRPct = d3.format('%');
|
||||
$rootScope.fTime = function(d) { return Math.floor(d/60) + ":" + ("00" + (d%60)).substr(-2,2); };
|
||||
@@ -44,6 +44,6 @@ angular.module('app', ['ui.router', 'ct.ui.router.extras.sticky', 'shipyard', 'n
|
||||
|
||||
$rootScope.bgClicked = function (e) {
|
||||
$rootScope.$broadcast('close', e);
|
||||
}
|
||||
};
|
||||
|
||||
}]);
|
||||
|
||||
@@ -11,9 +11,8 @@ angular.module('app').config(['$provide','$stateProvider', '$urlRouterProvider',
|
||||
.state('outfit', {
|
||||
url: '/outfit/:shipId/:code?bn',
|
||||
params: {
|
||||
// TODO: Squash:false not working due to UI-router issue
|
||||
shipId: { value: 'sidewinder', squash: false}, // Allow 'shipId' parameter to default to
|
||||
code: { value: null, squash: true } // Allow 'code' parameter to be empty/optional
|
||||
shipId: { value: 'sidewinder', squash: false}, // Allow 'shipId' parameter to default to sidewinder
|
||||
code: { value: null, squash: true } // Allow 'code' parameter to be empty/optional
|
||||
},
|
||||
templateUrl: 'views/page-outfit.html',
|
||||
controller: 'OutfitController',
|
||||
@@ -26,20 +25,35 @@ angular.module('app').config(['$provide','$stateProvider', '$urlRouterProvider',
|
||||
},
|
||||
sticky: true
|
||||
})
|
||||
.state('compare', {
|
||||
url: '/compare/:name',
|
||||
params: {
|
||||
name: {value: null, squash: true }
|
||||
},
|
||||
templateUrl: 'views/page-comparison.html',
|
||||
controller: 'ComparisonController',
|
||||
sticky: true
|
||||
})
|
||||
.state('comparison', {
|
||||
url: '/comparison/:code',
|
||||
templateUrl: 'views/page-comparison.html',
|
||||
controller: 'ComparisonController',
|
||||
sticky: true
|
||||
})
|
||||
.state('shipyard', { url: '/', templateUrl: 'views/page-shipyard.html', controller: 'ShipyardController', sticky: true })
|
||||
.state('comparison', { url: '/comparison', templateUrl: 'views/page-comparison.html', controller: 'ComparisonController', sticky: true })
|
||||
.state('error', { params: {type:null, message:null, details: null }, templateUrl: 'views/page-error.html', controller: 'ErrorController', sticky: true })
|
||||
|
||||
// Modal States and views
|
||||
.state('modal', { abstract: true, views:{ "modal": { templateUrl: "views/_modal.html", controller: 'ModalController' } } })
|
||||
.state('modal.about', { views: { "modal-content": { templateUrl: "views/modal-about.html" } } })
|
||||
.state('modal.export', { views: { "modal-content": { templateUrl: "views/modal-export.html", controller: 'ExportController' } } })
|
||||
.state('modal.import', { views: { "modal-content": { templateUrl: "views/modal-import.html", controller: 'ImportController' } } })
|
||||
.state('modal.export', { params: {title:null, data: null, promise: null}, views: { "modal-content": { templateUrl: "views/modal-export.html", controller: 'ExportController' } } })
|
||||
.state('modal.import', { params: {obj:null}, views: { "modal-content": { templateUrl: "views/modal-import.html", controller: 'ImportController' } } })
|
||||
.state('modal.link', { params: {url:null}, views: { "modal-content": { templateUrl: "views/modal-link.html", controller: 'LinkController' } } })
|
||||
.state('modal.delete', { views: { "modal-content": { templateUrl: "views/modal-delete.html", controller: 'DeleteController' } } });
|
||||
|
||||
|
||||
// Redirects
|
||||
$urlRouterProvider.when('/outfit','/outfit/sidewinder/');
|
||||
$urlRouterProvider.when('/outfit','/outfit/sidewinder');
|
||||
|
||||
/**
|
||||
* 404 Handler - Keep current URL/ do not redirect, change to error state.
|
||||
|
||||
@@ -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); });
|
||||
|
||||
|
||||
}]);
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
};
|
||||
|
||||
}]);
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
}]);
|
||||
16
app/js/controllers/controller-link.js
Normal file
16
app/js/controllers/controller-link.js
Normal 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;
|
||||
});
|
||||
|
||||
}]);
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
angular.module('app').controller('ShipyardController', ['$rootScope', 'ShipsDB', function ($rootScope, ships) {
|
||||
$rootScope.title = 'Coriolis';
|
||||
$rootScope.bodyClass = 'docking-bay';
|
||||
$rootScope.ships = ships;
|
||||
}]);
|
||||
123
app/js/directives/directive-area-chart.js
Normal file
123
app/js/directives/directive-area-chart.js
Normal file
@@ -0,0 +1,123 @@
|
||||
angular.module('app').directive('areaChart', function () {
|
||||
|
||||
|
||||
return {
|
||||
restrict: 'A',
|
||||
scope:{
|
||||
config: '=',
|
||||
series: '=',
|
||||
height: '=',
|
||||
width: '='
|
||||
},
|
||||
link: function(scope, element) {
|
||||
var width = scope.width,
|
||||
height = scope.height,
|
||||
series = scope.series,
|
||||
config = scope.config,
|
||||
labels = config.labels,
|
||||
margin = {top: 15, right: 15, bottom: 35, left: 50},
|
||||
w = width - margin.left - margin.right,
|
||||
h = height - margin.top - margin.bottom,
|
||||
fmt = d3.format('.3r'),
|
||||
fmtLong = d3.format('.2f');
|
||||
|
||||
// Create chart
|
||||
var svg = d3.select(element[0]).append("svg")
|
||||
.attr("width", width)
|
||||
.attr("height", height)
|
||||
.append("g")
|
||||
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
|
||||
|
||||
// Define Axes
|
||||
var x = d3.scale.linear().range([0, w]);
|
||||
var y = d3.scale.linear().range([h, 0]);
|
||||
var xAxis = d3.svg.axis().outerTickSize(0).orient("bottom").tickFormat(fmt);
|
||||
var yAxis = d3.svg.axis().outerTickSize(0).orient("left").tickFormat(fmt);
|
||||
// Define Area
|
||||
var area = d3.svg.area().x(function(d) { return x(d[0]); }).y0(h).y1(function(d) { return y(d[1]); });
|
||||
|
||||
var gradient = svg.append("defs")
|
||||
.append("linearGradient")
|
||||
.attr("id", "gradient")
|
||||
.attr("x1", "0%").attr("y1", "0%")
|
||||
.attr("x2", "100%").attr("y2", "100%")
|
||||
.attr("spreadMethod", "pad");
|
||||
gradient.append("stop")
|
||||
.attr("offset", "0%")
|
||||
.attr("stop-color", "#ff8c0d")
|
||||
.attr("stop-opacity", 1);
|
||||
gradient.append("stop")
|
||||
.attr("offset", "100%")
|
||||
.attr("stop-color", "#ff3b00")
|
||||
.attr("stop-opacity", 1);
|
||||
|
||||
// Create Y Axis SVG Elements
|
||||
svg.append("g").attr("class", "y axis")
|
||||
.append("text")
|
||||
.attr("transform", "rotate(-90)")
|
||||
.attr("y", -40)
|
||||
.attr("x", -h/2)
|
||||
.attr("dy", ".1em")
|
||||
.style("text-anchor", "middle")
|
||||
.text(labels.yAxis.title + ' (' + labels.yAxis.unit + ')');
|
||||
// Create X Axis SVG Elements
|
||||
svg.append("g").attr("class", "x axis").attr("transform", "translate(0," + h + ")")
|
||||
.append("text")
|
||||
.attr("y", 30)
|
||||
.attr("x", w/2)
|
||||
.attr("dy", ".1em")
|
||||
.style("text-anchor", "middle")
|
||||
.text(labels.xAxis.title + ' (' + labels.xAxis.unit + ')');
|
||||
|
||||
// Create and Add tooltip
|
||||
var tip = svg.append("g").style("display", "none");
|
||||
tip.append("circle")
|
||||
.attr("class", "marker")
|
||||
.attr("r", 4);
|
||||
tip.append("text").attr("class", 'label x').attr("y", -2);
|
||||
tip.append("text").attr("class", 'label y').attr("y", '0.7em');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Watch for changes in the series data (mass changes, etc)
|
||||
*/
|
||||
scope.$watchCollection('series', render);
|
||||
scope.$watchCollection('config.watch', render);
|
||||
|
||||
function render() {
|
||||
var data = [];
|
||||
var func = series.func;
|
||||
for (var d = series.xMin; d <= series.xMax; d++) {
|
||||
data.push([ d, func(d) ]);
|
||||
}
|
||||
// Update domain and scale for axes;
|
||||
x.domain([series.xMin, series.xMax]);
|
||||
xAxis.scale(x);
|
||||
y.domain([data[data.length - 1][1], data[0][1]]);
|
||||
yAxis.scale(y);
|
||||
svg.selectAll(".y.axis").call(yAxis);
|
||||
svg.selectAll(".x.axis").call(xAxis);
|
||||
|
||||
// Remove existing elements
|
||||
svg.selectAll('path.area').remove();
|
||||
|
||||
svg.insert("path",':first-child') // Area/Path to appear behind everything else
|
||||
.datum(data)
|
||||
.attr("class", "area")
|
||||
.attr('fill', 'url(#gradient)')
|
||||
.attr("d", area)
|
||||
.on("mouseover", function() { tip.style("display", null); })
|
||||
.on("mouseout", function() { tip.style("display", "none"); })
|
||||
.on('mousemove', function() {
|
||||
var xPos = d3.mouse(this)[0], x0 = x.invert(xPos), y0 = func(x0), flip = (xPos > w * 0.75);
|
||||
tip.attr("transform", "translate(" + x(x0) + "," + y(y0) + ")");
|
||||
tip.selectAll('text.label').attr("x", flip? -10 : 10).style("text-anchor", flip? 'end' : 'start');
|
||||
tip.select('text.label.x').text(fmtLong(x0) + ' ' + labels.xAxis.unit);
|
||||
tip.select('text.label.y').text(fmtLong(y0) + ' ' + labels.yAxis.unit);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
});
|
||||
@@ -25,76 +25,75 @@ angular.module('app').directive('barChart', ['$rootScope', function ($rootScope)
|
||||
width: '='
|
||||
},
|
||||
link: function(scope, element) {
|
||||
var color = d3.scale.ordinal().range([ "#7b6888", "#6b486b", "#3182bd", "#a05d56", "#d0743c"]),
|
||||
var color = d3.scale.ordinal().range([ '#7b6888', '#6b486b', '#3182bd', '#a05d56', '#d0743c']),
|
||||
width = scope.width,
|
||||
height = scope.height,
|
||||
labels = scope.facet.lbls,
|
||||
fmt = $rootScope[scope.facet.fmt],
|
||||
properties = scope.facet.prop? [scope.facet.prop] : scope.facet.props,
|
||||
fmt = scope.facet.fmt,
|
||||
properties = scope.facet.props,
|
||||
unit = scope.facet.unit,
|
||||
margin = {top: 10, right: 20, bottom: 35, left: 150},
|
||||
w = width - margin.left - margin.right,
|
||||
h = height - margin.top - margin.bottom;
|
||||
w = width - margin.left - margin.right;
|
||||
|
||||
// Create chart
|
||||
var svg = d3.select(element[0]).append("svg")
|
||||
.attr("width", width)
|
||||
.attr("height", height)
|
||||
.append("g")
|
||||
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
|
||||
var svg = d3.select(element[0]).append('svg').attr('width', width);
|
||||
var vis = svg.append('g').attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
|
||||
|
||||
// Create and Add tooltip
|
||||
var tip = d3.tip()
|
||||
.attr('class', 'd3-tip')
|
||||
.html(function(property, propertyIndex, buildIndex) {
|
||||
return (labels? (labels[propertyIndex] + ": ") : '') + fmt(property.value) + ' ' + unit;
|
||||
.html(function(property, propertyIndex) {
|
||||
return (labels? (labels[propertyIndex] + ': ') : '') + fmt(property.value) + ' ' + unit;
|
||||
});
|
||||
svg.call(tip);
|
||||
vis.call(tip);
|
||||
|
||||
// Create Y Axis SVG Elements
|
||||
svg.append("g").attr("class", "y axis");
|
||||
svg.selectAll('g.y.axis g text').each(insertLinebreaks);
|
||||
vis.append('g').attr('class', 'y axis');
|
||||
vis.selectAll('g.y.axis g text').each(insertLinebreaks);
|
||||
// Create X Axis SVG Elements
|
||||
svg.append("g")
|
||||
.attr("class", "x axis")
|
||||
.attr("transform", "translate(0," + h + ")")
|
||||
.append("text")
|
||||
.attr("y", 30)
|
||||
.attr("x", w/2)
|
||||
.attr("dy", ".1em")
|
||||
.style("text-anchor", "middle")
|
||||
vis.append('g')
|
||||
.attr('class', 'x axis')
|
||||
.append('text')
|
||||
.attr('y', 30)
|
||||
.attr('x', w/2)
|
||||
.attr('dy', '.1em')
|
||||
.style('text-anchor', 'middle')
|
||||
.text(scope.facet.title + (unit? (' (' + unit + ')') : ''));
|
||||
|
||||
|
||||
/**
|
||||
* Watch for changes in the comparison array (ships added/removed, sorting)
|
||||
*/
|
||||
scope.$watch('data', function() {
|
||||
var data = scope.data;
|
||||
var maxVal = d3.max(data, function(d) { return d3.max(properties, function(prop) {return d[prop]; }); });
|
||||
var y0 = d3.scale.ordinal().domain(data.map(bName)).rangeRoundBands([0, h],0.3);
|
||||
var y1 = d3.scale.ordinal().domain(properties).rangeRoundBands([0, y0.rangeBand()]);
|
||||
var x = d3.scale.linear().range([0, w]).domain([0, maxVal]);
|
||||
var yAxis = d3.svg.axis().scale(y0).outerTickSize(0).orient("left");
|
||||
var xAxis = d3.svg.axis().scale(x).outerTickSize(0).orient("bottom").tickFormat(d3.format('.2s'));
|
||||
scope.$watchCollection('data', function() {
|
||||
var data = scope.data,
|
||||
height = 45 + (25 * data.length),
|
||||
h = height - margin.top - margin.bottom,
|
||||
maxVal = d3.max(data, function(d) { return d3.max(properties, function(p) {return d[p]; }); }),
|
||||
y0 = d3.scale.ordinal().domain(data.map(bName)).rangeRoundBands([0, h],0.3),
|
||||
y1 = d3.scale.ordinal().domain(properties).rangeRoundBands([0, y0.rangeBand()]),
|
||||
x = d3.scale.linear().range([0, w]).domain([0, maxVal]),
|
||||
yAxis = d3.svg.axis().scale(y0).outerTickSize(0).orient('left'),
|
||||
xAxis = d3.svg.axis().scale(x).outerTickSize(0).orient('bottom').tickFormat(d3.format('.2s'));
|
||||
|
||||
// Update chart size
|
||||
svg.attr('height', height);
|
||||
|
||||
// Remove existing elements
|
||||
svg.selectAll('.ship').remove();
|
||||
svg.selectAll('rect').remove();
|
||||
vis.selectAll('.ship').remove();
|
||||
vis.selectAll('rect').remove();
|
||||
|
||||
// Update X & Y Axis
|
||||
svg.selectAll(".y.axis").call(yAxis);
|
||||
svg.selectAll(".x.axis").call(xAxis);
|
||||
vis.selectAll('.y.axis').call(yAxis);
|
||||
vis.selectAll('.x.axis').attr('transform', 'translate(0,' + h + ')').call(xAxis);
|
||||
// Update Y-Axis labels
|
||||
svg.selectAll('g.y.axis g text').each(insertLinebreaks);
|
||||
vis.selectAll('g.y.axis g text').each(insertLinebreaks);
|
||||
|
||||
var group = svg.selectAll(".ship")
|
||||
var group = vis.selectAll('.ship')
|
||||
.data(scope.data, bName)
|
||||
.enter().append("g")
|
||||
.attr("class", "g")
|
||||
.attr("transform", function(build) { return "translate(0," + y0(bName(build)) + ")"; });
|
||||
.enter().append('g')
|
||||
.attr('class', 'g')
|
||||
.attr('transform', function(build) { return 'translate(0,' + y0(bName(build)) + ')'; });
|
||||
|
||||
group.selectAll("rect")
|
||||
group.selectAll('rect')
|
||||
.data(function(build) {
|
||||
var o = [];
|
||||
for (var i = 0; i < properties.length; i++) {
|
||||
@@ -102,20 +101,20 @@ angular.module('app').directive('barChart', ['$rootScope', function ($rootScope)
|
||||
}
|
||||
return o;
|
||||
})
|
||||
.enter().append("rect")
|
||||
.attr("height", y1.rangeBand())
|
||||
.attr("x",0)
|
||||
.attr("y", function(d) {return y1(d.name); })
|
||||
.attr("width", function(d) { return x(d.value); })
|
||||
.enter().append('rect')
|
||||
.attr('height', y1.rangeBand())
|
||||
.attr('x',0)
|
||||
.attr('y', function(d) {return y1(d.name); })
|
||||
.attr('width', function(d) { return x(d.value); })
|
||||
.on('mouseover', tip.show)
|
||||
.on('mouseout', tip.hide)
|
||||
.style("fill", function(d) { return color(d.name); });
|
||||
.style('fill', function(d) { return color(d.name); });
|
||||
|
||||
});
|
||||
|
||||
scope.$on('$destroy', function() {
|
||||
tip.destroy(); // Remove the tooltip from the DOM
|
||||
})
|
||||
});
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
80
app/js/directives/directive-comparison-table.js
Normal file
80
app/js/directives/directive-comparison-table.js
Normal file
@@ -0,0 +1,80 @@
|
||||
angular.module('app').directive('comparisonTable', ['$state', function ($state) {
|
||||
|
||||
function tblHeader(facets) {
|
||||
var r1 = ['<tr class="main"><th rowspan="2" class="prop" prop="name">Ship</th><th rowspan="2" class="prop" prop="buildName">Build</th>'];
|
||||
var r2 = [];
|
||||
for (var i = 0, l = facets.length; i < l; i++) {
|
||||
if (facets[i].active) {
|
||||
var f = facets[i];
|
||||
var p = f.props;
|
||||
var pl = p.length;
|
||||
r1.push('<th rowspan="', f.props.length == 1 ? 2 : 1,'" colspan="',pl,'"');
|
||||
|
||||
if (pl == 1) {
|
||||
r1.push(' prop="',p[0],'" class="prop"');
|
||||
} else {
|
||||
for (var j = 0; j < pl; j++) {
|
||||
r2.push('<th prop="', p[j], '" class="prop ', j === 0? 'lft' : '', ' ">' , f.lbls[j], '</th>');
|
||||
}
|
||||
}
|
||||
|
||||
r1.push('>', f.title ,'</th>');
|
||||
}
|
||||
}
|
||||
r1.push('</tr><tr>');
|
||||
r1.push(r2.join(''));
|
||||
r1.push('</tr>');
|
||||
return r1.join('');
|
||||
}
|
||||
|
||||
function tblBody(facets, builds) {
|
||||
var body = [];
|
||||
|
||||
if(builds.length === 0) {
|
||||
return '<td colspan="100" class="cen">No builds added to comparison!</td';
|
||||
}
|
||||
|
||||
for (var i = 0, l = builds.length; i < l; i++) {
|
||||
var b = builds[i];
|
||||
body.push('<tr class="tr">');
|
||||
var href = $state.href('outfit',{shipId: b.id, code: b.code, bn: b.buildName});
|
||||
body.push('<td class="tl"><a href="', href,'">', b.name,'</a></td>');
|
||||
body.push('<td class="tl"><a href="', href,'">', b.buildName,'</a></td>');
|
||||
|
||||
for (var j = 0, fl = facets.length; j < fl; j++) {
|
||||
if (facets[j].active) {
|
||||
var f = facets[j];
|
||||
var p = f.props;
|
||||
for (var k = 0, pl = p.length; k < pl; k++) {
|
||||
body.push('<td>', f.fmt(b[p[k]]), '<u> ', f.unit, '</u></td>');
|
||||
}
|
||||
}
|
||||
}
|
||||
body.push('</tr>');
|
||||
}
|
||||
|
||||
return body.join('');
|
||||
}
|
||||
|
||||
return {
|
||||
restrict: 'A',
|
||||
|
||||
link: function (scope, element) {
|
||||
var header = angular.element('<thead></thead>');
|
||||
var body = angular.element('<tbody></tbody>');
|
||||
element.append(header);
|
||||
element.append(body);
|
||||
|
||||
var updateAll = function (){
|
||||
header.html(tblHeader(scope.facets));
|
||||
body.html(tblBody(scope.facets, scope.builds));
|
||||
};
|
||||
|
||||
scope.$watchCollection('facets', updateAll);
|
||||
scope.$watch('tblUpdate', updateAll);
|
||||
scope.$watchCollection('builds', function() {
|
||||
body.html(tblBody(scope.facets, scope.builds));
|
||||
});
|
||||
}
|
||||
};
|
||||
}]);
|
||||
@@ -1,4 +1,4 @@
|
||||
angular.module('app').directive('componentSelect', function() {
|
||||
angular.module('app').directive('componentSelect', function () {
|
||||
|
||||
// Generting the HTML in this manner is MUCH faster than using an angular template.
|
||||
|
||||
@@ -8,20 +8,34 @@ angular.module('app').directive('componentSelect', function() {
|
||||
var o = opts[i];
|
||||
var id = o.id || (o.class + o.rating); // Common components' ID is their class and rating
|
||||
list.push('<li class="', o.name? 'lc' : 'c');
|
||||
if(wrap && o.class != prevClass) list.push(' cl');
|
||||
if (cid == o.id) list.push(' active');
|
||||
|
||||
if(wrap && o.class != prevClass) {
|
||||
list.push(' cl');
|
||||
}
|
||||
|
||||
if (cid == o.id) {
|
||||
list.push(' active');
|
||||
}
|
||||
|
||||
list.push((o.maxmass && mass > o.maxmass)? ' disabled"' : '" cpid="', id, '">');
|
||||
|
||||
if(o.mode) {
|
||||
list.push('<svg cpid="', id, '" class="icon lg"><use xlink:href="#mount-', o.mode , '"></use></svg> ');
|
||||
}
|
||||
|
||||
list.push(o.class, o.rating);
|
||||
|
||||
if(o.mode) {
|
||||
list.push('/' + o.mode);
|
||||
if(o.missile) {
|
||||
list.push(o.missile);
|
||||
}
|
||||
}
|
||||
if(o.name) list.push(' ' + o.name);
|
||||
|
||||
if(o.name) {
|
||||
list.push(' ' + o.name);
|
||||
}
|
||||
|
||||
list.push('</li>');
|
||||
prevClass = o.class;
|
||||
prevRating= o.rating;
|
||||
@@ -49,7 +63,7 @@ angular.module('app').directive('componentSelect', function() {
|
||||
if(groups) {
|
||||
// At present time slots with grouped options (Hardpoints and Internal) can be empty
|
||||
list.push('<div class="empty-c" cpid="empty">EMPTY</div>');
|
||||
for (g in groups) {
|
||||
for (var g in groups) {
|
||||
var grp = groups[g];
|
||||
var grpCode = grp[Object.keys(grp)[0]].grp; // Nasty operation to get the grp property of the first/any single component
|
||||
list.push('<div id="', grpCode ,'" class="select-group">', g, '</div><ul>');
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
angular.module('app').directive('shipyardHeader', ['lodash','$rootScope', 'Persist', 'ShipsDB', function (_, $rootScope, Persist, ships) {
|
||||
angular.module('app').directive('shipyardHeader', ['lodash','$window','$rootScope', 'Persist', 'ShipsDB', function (_, $window, $rootScope, Persist, ships) {
|
||||
|
||||
return {
|
||||
restrict: 'E',
|
||||
@@ -8,6 +8,7 @@ angular.module('app').directive('shipyardHeader', ['lodash','$rootScope', 'Persi
|
||||
scope.openedMenu = null;
|
||||
scope.ships = ships;
|
||||
scope.allBuilds = Persist.builds;
|
||||
scope.allComparisons = Persist.comparisons;
|
||||
scope.bs = Persist.state;
|
||||
|
||||
// Insurance options and management here for now.
|
||||
@@ -17,9 +18,9 @@ angular.module('app').directive('shipyardHeader', ['lodash','$rootScope', 'Persi
|
||||
{ name:'Alpha', pct: 0.025 },
|
||||
{ name:'Beta', pct: 0.035 }
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
var insIndex = _.findIndex($rootScope.insurance.opts, 'name', localStorage.getItem('insurance'));
|
||||
var insIndex = _.findIndex($rootScope.insurance.opts, 'name', $window.localStorage.getItem('insurance'));
|
||||
$rootScope.insurance.current = $rootScope.insurance.opts[insIndex != -1? insIndex : 0];
|
||||
|
||||
// Close menus if a navigation change event occurs
|
||||
@@ -33,8 +34,8 @@ angular.module('app').directive('shipyardHeader', ['lodash','$rootScope', 'Persi
|
||||
});
|
||||
|
||||
scope.updateInsurance = function(){
|
||||
localStorage.setItem('insurance', $rootScope.insurance.current.name);
|
||||
}
|
||||
$window.localStorage.setItem('insurance', $rootScope.insurance.current.name);
|
||||
};
|
||||
|
||||
scope.openMenu = function (e, menu) {
|
||||
e.stopPropagation();
|
||||
@@ -59,8 +60,7 @@ angular.module('app').directive('shipyardHeader', ['lodash','$rootScope', 'Persi
|
||||
|
||||
$rootScope.hideAbout = function (){
|
||||
$rootScope.showAbout = false;
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
};
|
||||
}]);
|
||||
61
app/js/factory-utils.js
Normal file
61
app/js/factory-utils.js
Normal file
@@ -0,0 +1,61 @@
|
||||
/**
|
||||
* BBCode Generator functions for embedding in the Elite Dangerous Forums
|
||||
*/
|
||||
angular.module('app').factory('Utils', ['$state','$http', function ($state, $http) {
|
||||
|
||||
var shortenAPI = 'https://www.googleapis.com/urlshortener/v1/url?key=';
|
||||
|
||||
function shortenUrl(url) {
|
||||
return $http.post(shortenAPI + GAPI_KEY, {longUrl:url}).then(function(response) {
|
||||
return response.data.id;
|
||||
});
|
||||
}
|
||||
|
||||
function comparisonBBCode(facets, builds, link) {
|
||||
var colCount = 2, i,j,k, l = [];
|
||||
|
||||
for (i = 0; i < facets.length; i++) {
|
||||
if (facets[i].active) {
|
||||
var f = facets[i];
|
||||
var p = f.props;
|
||||
|
||||
if (p.length == 1) {
|
||||
l.push('[th][B][COLOR=#FF8C0D]', f.title, '[/COLOR][/B][/th]');
|
||||
colCount++;
|
||||
} else {
|
||||
for (j = 0; j < p.length; j++) {
|
||||
l.push('[th][B][COLOR=#FF8C0D]', f.title, '\n', f.lbls[j], '[/COLOR][/B][/th]');
|
||||
colCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
l.push('[/tr]\n');
|
||||
|
||||
for (i = 0; i < builds.length; i++) {
|
||||
var b = builds[i];
|
||||
//var href = $state.href('outfit',{shipId: b.id, code: b.code, bn: b.buildName}, {absolute: true});
|
||||
l.push('[tr][td]', b.name,'[/td][td]', b.buildName ,'[/td]');
|
||||
|
||||
for (j = 0, fl = facets.length; j < fl; j++) {
|
||||
if (facets[j].active) {
|
||||
var f = facets[j];
|
||||
var p = f.props;
|
||||
for (var k = 0, pl = p.length; k < pl; k++) {
|
||||
l.push('[td="align: right"]', f.fmt(b[p[k]]), ' [size=-2]', f.unit, '[/size][/td]');
|
||||
}
|
||||
}
|
||||
}
|
||||
l.push('[/tr]\n');
|
||||
}
|
||||
l.push('[tr][td="align: center, colspan:',colCount,'"][size=-3]\n[url=', link,']Interactive Comparison at Coriolis.io[/url][/td][/tr]\n[/size][/table]');
|
||||
l.unshift('[table="width:', colCount * 90,',align: center"]\n[tr][th][B][COLOR=#FF8C0D]Ship[/COLOR][/B][/th][th][B][COLOR="#FF8C0D"]Build[/COLOR][/B][/th]');
|
||||
return l.join('');
|
||||
}
|
||||
|
||||
return {
|
||||
comparisonBBCode: comparisonBBCode,
|
||||
shortenUrl: shortenUrl
|
||||
};
|
||||
|
||||
}]);
|
||||
@@ -1,24 +1,23 @@
|
||||
/**
|
||||
* [description]
|
||||
*/
|
||||
angular.module('app').service('Persist', ['lodash', function (_) {
|
||||
angular.module('app').service('Persist', ['$window','lodash', function ($window, _) {
|
||||
var LS_KEY_BUILDS = 'builds';
|
||||
var LS_KEY_COMPARISONS = 'comparisons';
|
||||
|
||||
var localStorage = $window.localStorage;
|
||||
var buildJson = localStorage.getItem(LS_KEY_BUILDS);
|
||||
var comparisonJson = localStorage.getItem(LS_KEY_COMPARISONS);
|
||||
|
||||
if (buildJson) {
|
||||
this.builds = angular.fromJson(localStorage.getItem(LS_KEY_BUILDS));
|
||||
} else {
|
||||
this.builds = {};
|
||||
}
|
||||
this.builds = buildJson? angular.fromJson(buildJson) : {};
|
||||
this.comparisons = comparisonJson? angular.fromJson(comparisonJson) : {};
|
||||
|
||||
var buildCount = Object.keys(this.builds).length;
|
||||
|
||||
this.state = {
|
||||
buildCount: buildCount,
|
||||
hasBuilds: buildCount > 0
|
||||
}
|
||||
hasBuilds: buildCount > 0,
|
||||
hasComparisons: Object.keys(this.comparisons).length > 0
|
||||
};
|
||||
/**
|
||||
* Persist a ship build in local storage.
|
||||
*
|
||||
@@ -39,7 +38,7 @@ angular.module('app').service('Persist', ['lodash', function (_) {
|
||||
this.builds[shipId][name] = code;
|
||||
// Persist updated build collection to localStorage
|
||||
localStorage.setItem(LS_KEY_BUILDS, angular.toJson(this.builds));
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the serialized code/string for a build. Returns null if a
|
||||
@@ -50,11 +49,11 @@ angular.module('app').service('Persist', ['lodash', function (_) {
|
||||
* @return {string} The serialized build string.
|
||||
*/
|
||||
this.getBuild = function (shipId, name) {
|
||||
if (this.builds[shipId]) {
|
||||
if (this.builds[shipId] && this.builds[shipId][name]) {
|
||||
return this.builds[shipId][name];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Delete a build from local storage. It will also delete the ship build collection if
|
||||
@@ -66,13 +65,57 @@ angular.module('app').service('Persist', ['lodash', function (_) {
|
||||
this.deleteBuild = function (shipId, name) {
|
||||
if(this.builds[shipId][name]) {
|
||||
delete this.builds[shipId][name];
|
||||
if (Object.keys(this.builds[shipId]).length == 0) {
|
||||
if (Object.keys(this.builds[shipId]).length === 0) {
|
||||
delete this.builds[shipId];
|
||||
this.state.buildCount--;
|
||||
this.state.hasBuilds = this.state.buildCount > 0;
|
||||
}
|
||||
// Persist updated build collection to localStorage
|
||||
localStorage.setItem(LS_KEY_BUILDS, angular.toJson(this.builds));
|
||||
// TODO: Check if build exists in comparisons
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Persist a comparison in localstorage.
|
||||
*
|
||||
* @param {string} name The name of the comparison
|
||||
* @param {array} builds Array of builds
|
||||
* @param {array} facets Array of facet indices
|
||||
*/
|
||||
this.saveComparison = function (name, builds, facets){
|
||||
if (!this.comparisons[name]) {
|
||||
this.comparisons[name] = {};
|
||||
}
|
||||
this.comparisons[name] = {
|
||||
facets: facets,
|
||||
builds: _.map(builds, function (b) { return {shipId: b.id, buildName: b.buildName }; })
|
||||
}
|
||||
localStorage.setItem(LS_KEY_COMPARISONS, angular.toJson(this.comparisons));
|
||||
this.state.hasComparisons = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* [getComparison description]
|
||||
* @param {string} name [description]
|
||||
* @return {object} Object containing array of facets and ship id + build names
|
||||
*/
|
||||
this.getComparison = function (name) {
|
||||
if (this.comparisons[name]) {
|
||||
return this.comparisons[name];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the comparison from localstorage.
|
||||
* @param {string} name Comparison name
|
||||
*/
|
||||
this.deleteComparison = function (name) {
|
||||
if (this.comparisons[name]) {
|
||||
delete this.comparisons[name];
|
||||
localStorage.setItem(LS_KEY_COMPARISONS, angular.toJson(this.comparisons));
|
||||
this.state.hasComparisons = Object.keys(this.comparisons).length > 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,8 +126,9 @@ angular.module('app').service('Persist', ['lodash', function (_) {
|
||||
angular.copy({}, this.builds); // Empty object but keep original instance
|
||||
angular.copy({}, this.comparisons);
|
||||
this.state.hasBuilds = false;
|
||||
this.state.buildCount = 0;
|
||||
localStorage.removeItem(LS_KEY_BUILDS);
|
||||
localStorage.removeItem(LS_KEY_COMPARISONS);
|
||||
}
|
||||
};
|
||||
|
||||
}]);
|
||||
|
||||
@@ -15,6 +15,7 @@ angular.module('app').service('Serializer', ['lodash', function (_) {
|
||||
_.map(ship.hardpoints, idToStr),
|
||||
_.map(ship.internal, idToStr),
|
||||
];
|
||||
|
||||
return _.flatten(data).join('');
|
||||
};
|
||||
|
||||
@@ -53,6 +54,26 @@ angular.module('app').service('Serializer', ['lodash', function (_) {
|
||||
ship.buildWith(comps);
|
||||
};
|
||||
|
||||
this.fromComparison = function (name, builds, facets, predicate, desc) {
|
||||
var shipBuilds = [];
|
||||
|
||||
builds.forEach(function (b) {
|
||||
shipBuilds.push({s: b.id, n: b.buildName, c: this.fromShip(b)});
|
||||
}.bind(this));
|
||||
|
||||
return LZString.compressToBase64(angular.toJson({
|
||||
n: name,
|
||||
b: shipBuilds,
|
||||
f: facets,
|
||||
p: predicate,
|
||||
d: desc? 1 : 0
|
||||
})).replace(/\//g,'-');
|
||||
}
|
||||
|
||||
this.toComparison = function (code) {
|
||||
return angular.fromJson(LZString.decompressFromBase64(code.replace(/-/g,'/')));
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility function to retrieve a safe string for selected component for a slot.
|
||||
* Used for serialization to code only.
|
||||
|
||||
@@ -58,8 +58,8 @@ angular.module('shipyard').factory('ComponentSet', ['lodash', function (_) {
|
||||
};
|
||||
|
||||
function filter (data, maxClass, minClass, mass) {
|
||||
return _.filter(data, function (c, index, collection) {
|
||||
return c.class <= maxClass && c.class >= minClass && (c.maxmass === undefined || mass <= c.maxmass)
|
||||
return _.filter(data, function (c) {
|
||||
return c.class <= maxClass && c.class >= minClass && (c.maxmass === undefined || mass <= c.maxmass);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -13,9 +13,9 @@ angular.module('shipyard').factory('Ship', ['Components', 'calcShieldStrength',
|
||||
this.cargoScoop = { enabled: true, c: Components.cargoScoop() };
|
||||
this.bulkheads = { incCost: true, maxClass: 8 };
|
||||
|
||||
for (p in properties) { this[p] = properties[p]; } // Copy all base properties from shipData
|
||||
for (var p in properties) { this[p] = properties[p]; } // Copy all base properties from shipData
|
||||
|
||||
for (slotType in slots) { // Initialize all slots
|
||||
for (var slotType in slots) { // Initialize all slots
|
||||
var slotGroup = slots[slotType];
|
||||
var group = this[slotType] = []; // Initialize Slot group (Common, Hardpoints, Internal)
|
||||
for(var i = 0; i < slotGroup.length; i++){
|
||||
@@ -101,7 +101,7 @@ angular.module('shipyard').factory('Ship', ['Components', 'calcShieldStrength',
|
||||
* @return {object} The mutated sum object
|
||||
*/
|
||||
function optsSum(sum, slot) {
|
||||
var c = slot.c
|
||||
var c = slot.c;
|
||||
if (c) { // The slot has a component installed
|
||||
sum.cost += (slot.incCost && c.cost)? c.cost : 0;
|
||||
sum.power += (slot.enabled && c.power)? c.power : 0;
|
||||
@@ -121,7 +121,7 @@ angular.module('shipyard').factory('Ship', ['Components', 'calcShieldStrength',
|
||||
* @return {object} The mutated sum object
|
||||
*/
|
||||
function hpSum(sum, slot) {
|
||||
var c = slot.c
|
||||
var c = slot.c;
|
||||
if (c) { // The slot has a component installed
|
||||
sum.cost += (slot.incCost && c.cost)? c.cost : 0;
|
||||
sum[c.passive? 'passive': 'active'] += slot.enabled? c.power : 0;
|
||||
@@ -135,7 +135,7 @@ angular.module('shipyard').factory('Ship', ['Components', 'calcShieldStrength',
|
||||
this.bulkheads.id = index;
|
||||
this.bulkheads.c = Components.bulkheads(this.id, index);
|
||||
this.updateTotals(); // Update mass, range, shield strength, armor
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Update a slot with a the component if the id is different from the current id for this slot.
|
||||
@@ -166,6 +166,15 @@ angular.module('shipyard').factory('Ship', ['Components', 'calcShieldStrength',
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* [jumpRange description]
|
||||
* @param {number} mass [description]
|
||||
* @return {number} Jump range in Light Years
|
||||
*/
|
||||
Ship.prototype.jumpRangeWithMass = function (mass) {
|
||||
return calcJumpRange(mass, this.common[2].c);
|
||||
};
|
||||
|
||||
/**
|
||||
* Find an internal slot that has an installed component of the specific group.
|
||||
*
|
||||
|
||||
@@ -79,67 +79,67 @@ angular.module('shipyard', ['ngLodash'])
|
||||
* @type {Array}
|
||||
*/
|
||||
.value('ShipFacets', [
|
||||
{
|
||||
{ // 0
|
||||
title: 'Agility',
|
||||
prop: 'agility',
|
||||
props: ['agility'],
|
||||
unit: '',
|
||||
fmt: 'fCrd'
|
||||
},
|
||||
{
|
||||
{ // 1
|
||||
title: 'Speed',
|
||||
props: ['speed', 'boost'],
|
||||
lbls: ['Thrusters', 'Boost'],
|
||||
unit: 'M/s',
|
||||
fmt: 'fRound'
|
||||
},
|
||||
{
|
||||
{ // 2
|
||||
title: 'Armour',
|
||||
prop: 'armour',
|
||||
props: ['armour'],
|
||||
unit: '',
|
||||
fmt: 'fCrd'
|
||||
},
|
||||
{
|
||||
{ // 3
|
||||
title: 'Shields',
|
||||
prop: 'shieldStrength',
|
||||
props: ['shieldStrength'],
|
||||
unit: 'Mj',
|
||||
fmt: 'fRound'
|
||||
},
|
||||
{
|
||||
{ // 4
|
||||
title: 'Jump Range',
|
||||
props: ['unladenJumpRange', 'ladenJumpRange'],
|
||||
lbls: ['Unladen', 'Laden'],
|
||||
unit: 'LY',
|
||||
fmt: 'fRound'
|
||||
},
|
||||
{
|
||||
{ // 5
|
||||
title: 'Mass',
|
||||
props: ['unladenMass', 'ladenMass'],
|
||||
lbls: ['Unladen', 'Laden'],
|
||||
unit: 'T',
|
||||
fmt: 'fRound'
|
||||
},
|
||||
{
|
||||
{ // 6
|
||||
title: 'Cargo',
|
||||
prop: 'cargoCapacity',
|
||||
props: ['cargoCapacity'],
|
||||
unit: 'T',
|
||||
fmt: 'fRound'
|
||||
},
|
||||
{
|
||||
{ // 7
|
||||
title: 'Fuel',
|
||||
prop: 'fuelCapacity',
|
||||
props: ['fuelCapacity'],
|
||||
unit: 'T',
|
||||
fmt: 'fRound'
|
||||
},
|
||||
{
|
||||
{ // 8
|
||||
title: 'Power',
|
||||
props: ['powerRetracted','powerDeployed','powerAvailable'],
|
||||
lbls: ['Retracted', 'Deployed', 'Available'],
|
||||
unit: 'MW',
|
||||
fmt: 'fPwr'
|
||||
},
|
||||
{
|
||||
{ // 9
|
||||
title: 'Cost',
|
||||
prop: 'totalCost',
|
||||
props: ['totalCost'],
|
||||
unit: 'CR',
|
||||
fmt: 'fCrd'
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ angular.module('shipyard').service('Components', ['lodash', 'ComponentsDB', 'Shi
|
||||
|
||||
this.cargoScoop = function() {
|
||||
return { name: 'Cargo Scoop', class: 1, rating: 'H', power: 0.6};
|
||||
}
|
||||
};
|
||||
|
||||
this.common = function (typeIndex, componentId) {
|
||||
return C.common[typeIndex][componentId];
|
||||
|
||||
Reference in New Issue
Block a user