Merge pull request #6 from cmmcleod/master

Update my fork.
This commit is contained in:
Justin Murtagh
2015-06-23 22:04:34 +10:00
71 changed files with 1434 additions and 790 deletions

View File

@@ -1 +0,0 @@
app/js/db.js

19
.travis.yml Normal file
View File

@@ -0,0 +1,19 @@
language: node_js
notifications:
email: false
sudo: false
node_js:
- "0.12"
cache:
directories:
- node_modules
- bower_components
before_script:
- npm install -g gulp
- npm install -g bower
- bower install
script:
- gulp lint
- gulp build-prod
- gulp test

View File

@@ -1,4 +1,4 @@
[ ![Codeship Status for cmmcleod/coriolis](https://codeship.com/projects/637858c0-f2a5-0132-7af7-5ed004d44c71/status?branch=master)](https://codeship.com/projects/85232) [![Tasks in Ready](https://badge.waffle.io/cmmcleod/coriolis.png?label=ready&title=Ready)](https://waffle.io/cmmcleod/coriolis) [![Tasks in Progress](https://badge.waffle.io/cmmcleod/coriolis.svg?label=in%20progress&title=In%20Progress)](http://waffle.io/cmmcleod/coriolis)
[![Build Status](https://travis-ci.org/cmmcleod/coriolis.svg?branch=master)](https://travis-ci.org/cmmcleod/coriolis) [![Tasks in Ready](https://badge.waffle.io/cmmcleod/coriolis.png?label=ready&title=Ready)](https://waffle.io/cmmcleod/coriolis) [![Tasks in Progress](https://badge.waffle.io/cmmcleod/coriolis.svg?label=in%20progress&title=In%20Progress)](http://waffle.io/cmmcleod/coriolis)
@@ -6,11 +6,11 @@
The Coriolis project was inspired by [E:D Shipyard](http://www.edshipyard.com/) and, of course, [Elite Dangerous](http://www.elitedangerous.com). The ultimate goal of Coriolis is to provide rich features to support in-game play and planning while engaging the E:D community to support its development.
Coriolis was created for non-commercial purposes. It is not endorsed by nor reflects the views or opinions of Frontier Developments.
Coriolis was created for non-commercial purposes. Coriolis was created using assets and imagery from Elite: Dangerous, with the permission of Frontier Developments plc, for non-commercial purposes. It is not endorsed by nor reflects the views or opinions of Frontier Developments and no employee of Frontier Developments was involved in the making of it.
## Contributing
Please [submit issues](https://github.com/cmmcleod/coriolis/issues), or better yet [pull requests](http://www.elitedangerous.com) for any corrections or additions to the database or the code.
Please [submit issues](https://github.com/cmmcleod/coriolis/issues), or better yet [pull requests](https://github.com/cmmcleod/coriolis/pulls) for any corrections or additions to the database or the code.
### Feature Requests, Suggestions & Bugs
@@ -28,12 +28,15 @@ See [Data wiki](https://github.com/cmmcleod/coriolis/wiki/Database) for details
## License
The MIT License
All Data and [associated JSON](https://github.com/cmmcleod/coriolis/tree/master/data) files are intellectual property and copyright of Frontier Developments plc ('Frontier', 'Frontier Developments') and are subject to their
[terms and conditions](https://www.frontierstore.net/terms-and-conditions/).
The code (Javascript, CSS, HTML, and SVG files only) specificially for Coriolis.io is released under the MIT License.
Copyright (c) 2015 Coriolis.io, Colin McLeod
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
of this software (Javascript, CSS, HTML, and SVG files only), and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is

View File

@@ -1,7 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generated by IcoMoon.io -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="32" height="32" viewBox="0 0 32 32">
<path d="M16 0c-8.837 0-16 7.163-16 16s7.163 16 16 16 16-7.163 16-16-7.163-16-16-16zM16 29c-7.18 0-13-5.82-13-13s5.82-13 13-13 13 5.82 13 13-5.82 13-13 13z"></path>
<path d="M21 8l-5 5-5-5-3 3 5 5-5 5 3 3 5-5 5 5 3-3-5-5 5-5z"></path>
</svg>

Before

Width:  |  Height:  |  Size: 554 B

3
app/icons/feather.svg Normal file
View File

@@ -0,0 +1,3 @@
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="724" height="1024" viewBox="0 0 1024 1024">
<path d="M0 1024c128-384 463-1024 1024-1024-263 211-384 704-576 704s-192 0-192 0l-192 320h-64z"></path>
</svg>

After

Width:  |  Height:  |  Size: 218 B

View File

@@ -1,6 +1,3 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generated by IcoMoon.io -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="32" height="32" viewBox="0 0 32 32">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="33" height="33" viewBox="0 0 33 33">
<path d="M32 12h-12l4.485-4.485c-2.267-2.266-5.28-3.515-8.485-3.515s-6.219 1.248-8.485 3.515c-2.266 2.267-3.515 5.28-3.515 8.485s1.248 6.219 3.515 8.485c2.267 2.266 5.28 3.515 8.485 3.515s6.219-1.248 8.485-3.515c0.189-0.189 0.371-0.384 0.546-0.583l3.010 2.634c-2.933 3.349-7.239 5.464-12.041 5.464-8.837 0-16-7.163-16-16s7.163-16 16-16c4.418 0 8.418 1.791 11.313 4.687l4.687-4.687v12z"></path>
</svg>

Before

Width:  |  Height:  |  Size: 713 B

After

Width:  |  Height:  |  Size: 499 B

View File

@@ -1,6 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generated by IcoMoon.io -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="32" height="32" viewBox="0 0 32 32">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="33" height="33" viewBox="0 0 33 33">
<path d="M20 4.581v4.249c1.131 0.494 2.172 1.2 3.071 2.099 1.889 1.889 2.929 4.4 2.929 7.071s-1.040 5.182-2.929 7.071c-1.889 1.889-4.4 2.929-7.071 2.929s-5.182-1.040-7.071-2.929c-1.889-1.889-2.929-4.4-2.929-7.071s1.040-5.182 2.929-7.071c0.899-0.899 1.94-1.606 3.071-2.099v-4.249c-5.783 1.721-10 7.077-10 13.419 0 7.732 6.268 14 14 14s14-6.268 14-14c0-6.342-4.217-11.698-10-13.419zM14 0h4v16h-4z"></path>
</svg>

Before

Width:  |  Height:  |  Size: 723 B

After

Width:  |  Height:  |  Size: 510 B

View File

@@ -63,10 +63,10 @@
<footer>
<div class="right">
<a href="https://github.com/cmmcleod/coriolis" target="_blank" title="Coriolis Github Project">Version <%= version %> - <%= date %></a>
<a href="https://github.com/cmmcleod/coriolis/releases/" target="_blank" title="Coriolis Github Project">Version <%= version %> - <%= date %></a>
</div>
<div style="max-width:50%" class="l">
Coriolis Shipyard was created for non-commercial purposes. It is not endorsed by nor reflects the views or opinions of Frontier Developments.
Coriolis was created using assets and imagery from Elite: Dangerous, with the permission of Frontier Developments plc, for non-commercial purposes. It is not endorsed by nor reflects the views or opinions of Frontier Developments and no employee of Frontier Developments was involved in the making of it.
</div>
</footer>

View File

@@ -1,12 +1,13 @@
angular.module('app', ['ui.router', 'ct.ui.router.extras.sticky', 'ui.sortable', 'shipyard', 'ngLodash', 'app.templates'])
.run(['$rootScope', '$location', '$window', '$document','$state','commonArray','shipPurpose','shipSize','hardPointClass','GroupMap', 'Persist', function ($rootScope, $location, $window, $doc, $state, CArr, shipPurpose, sz, hpc, GroupMap, Persist) {
.run(['$rootScope', '$location', '$window', '$document', '$state', 'commonArray', 'shipPurpose', 'shipSize', 'hardPointClass', 'GroupMap', 'Persist',
function($rootScope, $location, $window, $doc, $state, CArr, shipPurpose, sz, hpc, GroupMap, Persist) {
// App is running as a standalone web app on tablet/mobile
var isStandAlone = $window.navigator.standalone || ($window.external && $window.external.msIsSiteMode && $window.external.msIsSiteMode());
// Redirect any state transition errors to the error controller/state
$rootScope.$on('$stateChangeError', function(e, toState, toParams, fromState, fromParams, error){
$rootScope.$on('$stateChangeError', function(e, toState, toParams, fromState, fromParams, error) {
e.preventDefault();
$state.go('error', error, {location:false, reload:true}); // Go to error state, reload the controller, keep the current URL
$state.go('error', error, { location: false, reload: true }); // Go to error state, reload the controller, keep the current URL
});
// Track on Google analytics if available
@@ -16,12 +17,12 @@ angular.module('app', ['ui.router', 'ct.ui.router.extras.sticky', 'ui.sortable',
if (to.url) { // Only track states that have a URL
if ($window.ga) {
ga('send', 'pageview', {page: $location.path()});
ga('send', 'pageview', { page: $location.path() });
}
if (isStandAlone) {
// Persist the current state
Persist.setState({name: to.name, params: toParams});
Persist.setState({ name: to.name, params: toParams });
}
}
});
@@ -32,12 +33,14 @@ angular.module('app', ['ui.router', 'ct.ui.router.extras.sticky', 'ui.sortable',
$rootScope.SZ = sz;
$rootScope.HPC = hpc;
$rootScope.GMAP = GroupMap;
$rootScope.STATUS = ['','DISABLED', 'OFF', 'ON'];
$rootScope.STATUS_CLASS = ['','disabled', 'warning', 'secondary-disabled'];
$rootScope.insurance = { opts: [{ name: 'Standard', pct: 0.05 }, { name: 'Alpha', pct: 0.025 }, { name: 'Beta', pct: 0.035 }] };
$rootScope.discounts = { opts: [{ name: 'None', pct: 1 }, { name: 'Founders World - 10%', pct: 0.90 }] };
$rootScope.STATUS = ['', 'DISABLED', 'OFF', 'ON'];
$rootScope.STATUS_CLASS = ['', 'disabled', 'warning', 'secondary-disabled'];
$rootScope.title = 'Coriolis';
$rootScope.cName = function (c) {
return c.c? c.c.name? c.c.name : GroupMap[c.c.grp] : null;
$rootScope.cName = function(c) {
return c.c ? c.c.name ? c.c.name : GroupMap[c.c.grp] : null;
};
// Formatters
@@ -48,21 +51,21 @@ angular.module('app', ['ui.router', 'ct.ui.router.extras.sticky', 'ui.sortable',
$rootScope.fPct = d3.format('.2%');
$rootScope.f1Pct = d3.format('.1%');
$rootScope.fRPct = d3.format('%');
$rootScope.fTime = function(d) { return Math.floor(d/60) + ":" + ("00" + Math.floor(d%60)).substr(-2,2); };
$rootScope.fTime = function(d) { return Math.floor(d / 60) + ':' + ('00' + Math.floor(d % 60)).substr(-2, 2); };
if (isStandAlone) {
var state = Persist.getState();
// If a previous state has been stored, load that state
if (state && state.name && state.params) {
$state.go(state.name, state.params, {location:'replace'});
$state.go(state.name, state.params, { location: 'replace' });
} else {
$state.go('shipyard', null, {location:'replace'}); // Default to home page
$state.go('shipyard', null, { location: 'replace' }); // Default to home page
}
}
// Global Event Listeners
$doc.bind('keyup', function (e) {
if(e.keyCode == 27) { // Escape Key
$doc.bind('keyup', function(e) {
if (e.keyCode == 27) { // Escape Key
$rootScope.$broadcast('close', e);
$rootScope.$apply();
} else {
@@ -70,7 +73,7 @@ angular.module('app', ['ui.router', 'ct.ui.router.extras.sticky', 'ui.sortable',
}
});
$rootScope.bgClicked = function (e) {
$rootScope.bgClicked = function(e) {
$rootScope.$broadcast('close', e);
};

View File

@@ -1,9 +1,9 @@
/**
* Sets up the routes and handlers before the Angular app is kicked off.
*/
angular.module('app').config(['$provide','$stateProvider', '$urlRouterProvider', '$locationProvider', 'ShipsDB', function ($provide, $stateProvider, $urlRouterProvider, $locationProvider, ships) {
angular.module('app').config(['$provide', '$stateProvider', '$urlRouterProvider', '$locationProvider', 'ShipsDB', function($provide, $stateProvider, $urlRouterProvider, $locationProvider, ships) {
// Use HTML5 push and replace state if possible
$locationProvider.html5Mode({enabled: true, requireBase: false});
$locationProvider.html5Mode({ enabled: true, requireBase: false });
/**
* Set up all states and their routes.
*/
@@ -11,13 +11,13 @@ angular.module('app').config(['$provide','$stateProvider', '$urlRouterProvider',
.state('outfit', {
url: '/outfit/:shipId/:code?bn',
params: {
shipId: { value: 'sidewinder', squash: false}, // Allow 'shipId' parameter to default to sidewinder
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',
resolve: {
shipId: ['$stateParams',function ($p) { // Ensure ship exists before loading controller
shipId: ['$stateParams', function($p) { // Ensure ship exists before loading controller
if (!ships[$p.shipId]) {
throw { type: 'no-ship', message: $p.shipId };
}
@@ -28,7 +28,7 @@ angular.module('app').config(['$provide','$stateProvider', '$urlRouterProvider',
.state('compare', {
url: '/compare/:name',
params: {
name: {value: null, squash: true }
name: { value: null, squash: true }
},
templateUrl: 'views/page-comparison.html',
controller: 'ComparisonController',
@@ -41,26 +41,26 @@ angular.module('app').config(['$provide','$stateProvider', '$urlRouterProvider',
sticky: true
})
.state('shipyard', { url: '/', templateUrl: 'views/page-shipyard.html', controller: 'ShipyardController', sticky: true })
.state('error', { params: {type:null, message:null, details: null }, templateUrl: 'views/page-error.html', controller: 'ErrorController', 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', { 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' } } });
.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', { 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.
*/
$urlRouterProvider.otherwise(function ($injector, $location) {
$urlRouterProvider.otherwise(function($injector, $location) {
// Go to error state, reload the controller, keep the current URL
$injector.get('$state').go('error', { type: 404, message: null, details: null }, {location:false, reload:true});
$injector.get('$state').go('error', { type: 404, message: null, details: null }, { location: false, reload: true });
return $location.path;
});
@@ -69,10 +69,10 @@ angular.module('app').config(['$provide','$stateProvider', '$urlRouterProvider',
* redirects uncaught errors to the error page.
*
*/
$provide.decorator('$exceptionHandler', ['$delegate', '$injector', function ($delegate, $injector) {
$provide.decorator('$exceptionHandler', ['$delegate', '$injector', function($delegate, $injector) {
return function(err, cause) {
// Go to error state, reload the controller, keep the current URL
$injector.get('$state').go('error', {type:null, message: err.message, details: err.stack }, {location:false, reload:true});
$injector.get('$state').go('error', { type: null, message: err.message, details: err.stack }, { location: false, reload: true });
$delegate(err, cause);
};
}]);

View File

@@ -1,48 +1,49 @@
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) {
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 = 'name'; // Sort by ship name as default
$scope.desc = false;
$scope.facetSortOpts = { containment: '#facet-container', orderChanged: function () { $scope.saved = false; } };
$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 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);
var shipId, buildName, comparisonData;
/**
* 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
* @param {string} id The unique ship key/id
* @param {string} name The build name
*/
$scope.addBuild = function (shipId, buildName, code) {
var data = Ships[shipId]; // Get ship properties
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
$scope.addBuild = function(id, name, code) {
var data = Ships[id]; // Get ship properties
code = code ? code : Persist.builds[id][name]; // Retrieve build code if not passed
var b = new Ship(id, 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.buildName = name;
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;
_.remove($scope.unusedBuilds, function(o) { // Remove from unused builds
return o.id == id && o.buildName == name;
});
$scope.saved = false;
};
/**
* Removes a build from the comparison
* @param {string} shipId The unique ship key/id
* @param {string} buildName The build name
* @param {string} id The unique ship key/id
* @param {string} name 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
$scope.removeBuild = function(id, name) {
_.remove($scope.builds, function(s) {
if (s.id == id && s.buildName == name) {
$scope.unusedBuilds.push({ id: id, buildName: name, name: s.name }); // Add build back to unused builds
return true;
}
return false;
@@ -54,7 +55,7 @@ angular.module('app').controller('ComparisonController', ['lodash', '$rootScope'
* 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) {
$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;
@@ -64,12 +65,12 @@ angular.module('app').controller('ComparisonController', ['lodash', '$rootScope'
* Click handler for sorting by facets in the table
* @param {object} e Event object
*/
$scope.handleClick = function (e) {
$scope.handleClick = function(e) {
var elem = angular.element(e.target);
if(elem.attr('prop')) { // Get component ID
if (elem.attr('prop')) { // Get component ID
$scope.sort(elem.attr('prop'));
}
else if (elem.attr('del')) { // Delete index
} else if (elem.attr('del')) { // Delete index
$scope.removeBuild(elem.attr('del'));
}
};
@@ -78,8 +79,8 @@ angular.module('app').controller('ComparisonController', ['lodash', '$rootScope'
* 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.sort = function(key) {
$scope.desc = $scope.predicate == key ? !$scope.desc : $scope.desc;
$scope.predicate = key;
$scope.builds = $filter('orderBy')($scope.builds, $scope.predicate, $scope.desc);
};
@@ -94,12 +95,12 @@ angular.module('app').controller('ComparisonController', ['lodash', '$rootScope'
}
var selectedFacets = [];
facets.forEach(function(f) {
if(f.active) {
if (f.active) {
selectedFacets.unshift(f.index);
}
});
Persist.saveComparison($scope.name, $scope.builds, selectedFacets);
$state.go('compare', {name: $scope.name}, {location:'replace', notify:false});
$state.go('compare', { name: $scope.name }, { location: 'replace', notify: false });
$scope.saved = true;
};
@@ -108,7 +109,7 @@ angular.module('app').controller('ComparisonController', ['lodash', '$rootScope'
*/
$scope.delete = function() {
Persist.deleteComparison($scope.name);
$state.go('compare', {name: null}, {location:'replace', reload:true});
$state.go('compare', { name: null }, { location: 'replace', reload: true });
};
/**
@@ -134,7 +135,7 @@ angular.module('app').controller('ComparisonController', ['lodash', '$rootScope'
*/
$scope.permalink = function(e) {
e.stopPropagation();
$state.go('modal.link', {url: genPermalink()});
$state.go('modal.link', { url: genPermalink() });
};
/**
@@ -147,14 +148,14 @@ angular.module('app').controller('ComparisonController', ['lodash', '$rootScope'
e.stopPropagation();
// Make a request to goo.gl to shorten the URL, returns a promise
var promise = Utils.shortenUrl( genPermalink()).then(
function (shortUrl) {
function(shortUrl) {
return Utils.comparisonBBCode(facets, $scope.builds, shortUrl);
},
function (e) {
return 'Error - ' + e.statusText;
function(err) {
return 'Error - ' + err.statusText;
}
);
$state.go('modal.export', {promise: promise, title:'Forum BBCode'});
$state.go('modal.export', { promise: promise, title: 'Forum BBCode' });
};
/**
@@ -164,7 +165,7 @@ angular.module('app').controller('ComparisonController', ['lodash', '$rootScope'
function genPermalink() {
var selectedFacets = [];
facets.forEach(function(f) {
if(f.active) {
if (f.active) {
selectedFacets.unshift(f.index);
}
});
@@ -175,7 +176,7 @@ angular.module('app').controller('ComparisonController', ['lodash', '$rootScope'
$scope.predicate,
$scope.desc
);
return $state.href('comparison', {code: code}, {absolute:true});
return $state.href('comparison', { code: code }, { absolute: true });
}
/* Event listeners */
@@ -184,7 +185,6 @@ angular.module('app').controller('ComparisonController', ['lodash', '$rootScope'
});
/* Initialization */
var shipId, buildName, comparisonData;
if ($scope.compareMode) {
if ($scope.name == 'all') {
for (shipId in Persist.builds) {
@@ -195,13 +195,13 @@ angular.module('app').controller('ComparisonController', ['lodash', '$rootScope'
} else {
for (shipId in Persist.builds) {
for (buildName in Persist.builds[shipId]) {
$scope.unusedBuilds.push({id: shipId, buildName: buildName, name: Ships[shipId].properties.name});
$scope.unusedBuilds.push({ id: shipId, buildName: buildName, name: Ships[shipId].properties.name });
}
}
comparisonData = Persist.getComparison($scope.name);
if (comparisonData) {
defaultFacets = comparisonData.facets;
comparisonData.builds.forEach(function (b) {
comparisonData.builds.forEach(function(b) {
$scope.addBuild(b.shipId, b.buildName);
});
$scope.saved = true;
@@ -214,9 +214,9 @@ angular.module('app').controller('ComparisonController', ['lodash', '$rootScope'
$scope.name = comparisonData.n;
$scope.predicate = comparisonData.p;
$scope.desc = comparisonData.d;
comparisonData.b.forEach(function (build) {
comparisonData.b.forEach(function(build) {
$scope.addBuild(build.s, build.n, build.c);
if(!$scope.importObj[build.s]) {
if (!$scope.importObj[build.s]) {
$scope.importObj[build.s] = {};
}
$scope.importObj[build.s][build.n] = build.c;
@@ -226,9 +226,9 @@ angular.module('app').controller('ComparisonController', ['lodash', '$rootScope'
}
}
// 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; });
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); });
_.pullAt(facets, defaultFacets).forEach(function(f) { f.active = true; facets.unshift(f); });
$scope.builds = $filter('orderBy')($scope.builds, $scope.predicate, $scope.desc);
}]);

View File

@@ -1,5 +1,5 @@
angular.module('app').controller('DeleteController', ['$scope', 'Persist', function ($scope, Persist) {
$scope.deleteAll = function () {
angular.module('app').controller('DeleteController', ['$scope', 'Persist', function($scope, Persist) {
$scope.deleteAll = function() {
Persist.deleteAll();
$scope.$parent.dismiss();
};

View File

@@ -1,5 +1,5 @@
angular.module('app')
.controller('ErrorController', ['$window','$rootScope','$scope','$stateParams', '$location', function ($window, $rootScope, $scope, $p, $location) {
.controller('ErrorController', ['$window', '$rootScope', '$scope', '$stateParams', '$location', function($window, $rootScope, $scope, $p, $location) {
$rootScope.title = 'Error';
$scope.path = $location.path();
$scope.type = $p.type || 'unknown';
@@ -21,7 +21,7 @@ angular.module('app')
$scope.details = $p.details;
break;
default:
$scope.msgPre = "Uh, Jameson, we have a problem..";
$scope.msgPre = 'Uh, Jameson, we have a problem..';
$scope.errorMessage = $p.message;
$scope.details = $p.details;
}

View File

@@ -1,17 +1,17 @@
angular.module('app').controller('ExportController', ['$scope', '$stateParams', function ($scope, $stateParams) {
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){
$stateParams.promise.then(function(data) {
$scope.export = data;
});
} else {
$scope.export = angular.toJson($stateParams.data, true);
}
$scope.onTextClick = function ($event) {
$scope.onTextClick = function($event) {
$event.target.select();
};

View File

@@ -1,4 +1,4 @@
angular.module('app').controller('ImportController', ['$scope', '$stateParams', 'ShipsDB', 'Ship', 'Persist', 'Serializer', function ($scope, $stateParams, 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;
@@ -21,7 +21,7 @@ angular.module('app').controller('ImportController', ['$scope', '$stateParams',
return;
}
if(typeof importObj != 'object') {
if (typeof importObj != 'object') {
$scope.errorMsg = 'Must be an object!';
return;
}
@@ -48,7 +48,7 @@ angular.module('app').controller('ImportController', ['$scope', '$stateParams',
}
}
} else {
$scope.errorMsg = '"' + shipId + '" is not a valid Ship Id!';
$scope.errorMsg = '"' + shipId + '"" is not a valid Ship Id!';
return;
}
$scope.builds = importObj.builds;
@@ -57,7 +57,7 @@ angular.module('app').controller('ImportController', ['$scope', '$stateParams',
$scope.jsonValid = true;
};
$scope.hasBuild = function (shipId, name) {
$scope.hasBuild = function(shipId, name) {
return Persist.getBuild(shipId, name) !== null;
};

View File

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

View File

@@ -1,9 +1,9 @@
angular.module('app').controller('ModalController', ['$rootScope','$scope', '$state', function ($rootScope, $scope, $state) {
angular.module('app').controller('ModalController', ['$rootScope', '$scope', '$state', function($rootScope, $scope, $state) {
$scope.dismiss = function() {
if ($rootScope.prevState) {
var state = $rootScope.prevState;
$state.go(state.name, state.params, {location: 'replace', reload: false});
$state.go(state.name, state.params, { location: 'replace', reload: false });
} else {
$state.go('shipyard');
}

View File

@@ -1,4 +1,4 @@
angular.module('app').controller('OutfitController', ['$window','$rootScope','$scope', '$state', '$stateParams', 'ShipsDB', 'Ship', 'Components', 'Serializer', 'Persist', function ($window, $rootScope, $scope, $state, $p, Ships, Ship, Components, Serializer, Persist) {
angular.module('app').controller('OutfitController', ['$window', '$rootScope', '$scope', '$state', '$stateParams', 'ShipsDB', 'Ship', 'Components', 'Serializer', 'Persist', 'calcTotalRange', 'calcSpeed', function($window, $rootScope, $scope, $state, $p, Ships, Ship, Components, Serializer, Persist, calcTotalRange, calcSpeed) {
var data = Ships[$p.shipId]; // Retrieve the basic ship properties, slots and defaults
var ship = new Ship($p.shipId, data.properties, data.slots); // Create a new Ship instance
var win = angular.element($window); // Angularized window object for event triggering
@@ -12,7 +12,7 @@ angular.module('app').controller('OutfitController', ['$window','$rootScope','$s
}
$scope.buildName = $p.bn;
$rootScope.title = ship.name + ($scope.buildName? ' - ' + $scope.buildName : '');
$rootScope.title = ship.name + ($scope.buildName ? ' - ' + $scope.buildName : '');
$scope.ship = ship;
$scope.pp = ship.common[0]; // Power Plant
$scope.th = ship.common[1]; // Thruster
@@ -39,8 +39,7 @@ angular.module('app').controller('OutfitController', ['$window','$rootScope','$s
$scope.jrSeries = {
xMin: 0,
xMax: ship.cargoCapacity,
// Slightly higher than actual based bacuse components are excluded
yMax: ship.jumpRangeWithMass(ship.unladenMass),
yMax: ship.unladenRange,
yMin: 0,
func: function(cargo) { // X Axis is Cargo
return ship.jumpRangeWithMass(ship.unladenMass + $scope.fuel + cargo, $scope.fuel);
@@ -49,15 +48,59 @@ angular.module('app').controller('OutfitController', ['$window','$rootScope','$s
$scope.jrChart = {
labels: {
xAxis: {
title:'Cargo',
title: 'Cargo',
unit: 'T'
},
yAxis: {
title:'Jump Range',
title: 'Jump Range',
unit: 'LY'
}
}
};
$scope.trSeries = {
xMin: 0,
xMax: ship.cargoCapacity,
yMax: ship.unladenTotalRange,
yMin: 0,
func: function(cargo) { // X Axis is Cargo
return calcTotalRange(ship.unladenMass + cargo, $scope.fsd.c, $scope.fuel);
}
};
$scope.trChart = {
labels: {
xAxis: {
title: 'Cargo',
unit: 'T'
},
watch: $scope.fsd
yAxis: {
title: 'Total Range',
unit: 'LY'
}
}
};
$scope.speedSeries = {
xMin: 0,
xMax: ship.cargoCapacity,
yMax: 500,
yMin: 0,
series: ['speed', 'boost'],
func: function(cargo) { // X Axis is Cargo
return calcSpeed(ship.unladenMass + $scope.fuel + cargo, ship.speed, ship.boost, $scope.th.c);
}
};
$scope.speedChart = {
labels: {
xAxis: {
title: 'Cargo',
unit: 'T'
},
yAxis: {
title: 'Speed',
unit: 'm/s'
}
}
};
/**
@@ -83,14 +126,14 @@ angular.module('app').controller('OutfitController', ['$window','$rootScope','$s
* @param {[type]} slot The slot object belonging to the ship instance
* @param {[type]} e The event object
*/
$scope.select = function(type, slot, e) {
$scope.select = function(type, slot, e, id) {
e.stopPropagation();
var id = angular.element(e.target).attr('cpid'); // Get component ID
id = id || angular.element(e.target).attr('cpid'); // Get component ID
if (id) {
if (id == 'empty') {
ship.use(slot, null, null);
} else if(type == 'h') {
} else if (type == 'h') {
ship.use(slot, id, Components.hardpoints(id));
} else if (type == 'c') {
ship.use(slot, id, Components.common(ship.common.indexOf(slot), id));
@@ -100,8 +143,7 @@ angular.module('app').controller('OutfitController', ['$window','$rootScope','$s
ship.useBulkhead(id);
}
$scope.selectedSlot = null;
$scope.code = Serializer.fromShip(ship);
updateState();
updateState(Serializer.fromShip(ship));
}
};
@@ -111,11 +153,24 @@ angular.module('app').controller('OutfitController', ['$window','$rootScope','$s
$scope.reloadBuild = function() {
if ($scope.buildName && $scope.savedCode) {
Serializer.toShip(ship, $scope.savedCode); // Repopulate with components from last save
$scope.code = $scope.savedCode;
updateState();
updateState($scope.savedCode);
}
};
/**
* Strip ship to D-class and no other components.
*/
$scope.stripBuild = function() {
for (var i = 0, l = ship.common.length - 1; i < l; i++) { // All except Fuel Tank
var id = ship.common[i].maxClass + 'D';
ship.use(ship.common[i], id, Components.common(i, id));
}
ship.hardpoints.forEach(function(slot) { ship.use(slot, null, null); });
ship.internal.forEach(function(slot) { ship.use(slot, null, null); });
ship.useBulkhead(0);
updateState(Serializer.fromShip(ship));
};
/**
* Save the current build. Will replace the saved build if there is one
* for this ship & with the exact name.
@@ -132,7 +187,7 @@ angular.module('app').controller('OutfitController', ['$window','$rootScope','$s
if ($scope.code != $scope.savedCode) {
Persist.saveBuild(ship.id, $scope.buildName, $scope.code);
$scope.savedCode = $scope.code;
updateState();
updateState($scope.code);
}
};
@@ -142,13 +197,13 @@ angular.module('app').controller('OutfitController', ['$window','$rootScope','$s
*/
$scope.deleteBuild = function() {
Persist.deleteBuild(ship.id, $scope.buildName);
$state.go('outfit', {shipId: ship.id, code: null, bn: null}, {location:'replace', reload:true});
$state.go('outfit', { shipId: ship.id, code: null, bn: null }, { location: 'replace', reload: true });
};
/**
* On build name change, retrieve the existing saved code if there is one
*/
$scope.bnChange = function(){
$scope.bnChange = function() {
$scope.savedCode = Persist.getBuild(ship.id, $scope.buildName);
};
@@ -165,13 +220,13 @@ angular.module('app').controller('OutfitController', ['$window','$rootScope','$s
* @param {[type]} key [description]
* @return {[type]} [description]
*/
$scope.sortCost = function (key) {
$scope.costDesc = ($scope.costPredicate == key)? !$scope.costDesc : $scope.costDesc;
$scope.sortCost = function(key) {
$scope.costDesc = $scope.costPredicate == key ? !$scope.costDesc : $scope.costDesc;
$scope.costPredicate = key;
};
$scope.sortPwr = function (key) {
$scope.pwrDesc = ($scope.pwrPredicate == key)? !$scope.pwrDesc : $scope.pwrDesc;
$scope.sortPwr = function(key) {
$scope.pwrDesc = $scope.pwrPredicate == key ? !$scope.pwrDesc : $scope.pwrDesc;
$scope.pwrPredicate = key;
};
@@ -181,49 +236,47 @@ angular.module('app').controller('OutfitController', ['$window','$rootScope','$s
*/
$scope.togglePwr = function(c) {
ship.setSlotEnabled(c, !c.enabled);
$scope.code = Serializer.fromShip(ship);
updateState();
updateState(Serializer.fromShip(ship));
};
$scope.incPriority = function (c) {
$scope.incPriority = function(c) {
if (ship.changePriority(c, c.priority + 1)) {
$scope.code = Serializer.fromShip(ship);
updateState();
updateState(Serializer.fromShip(ship));
}
};
$scope.decPriority = function (c) {
$scope.decPriority = function(c) {
if (ship.changePriority(c, c.priority - 1)) {
$scope.code = Serializer.fromShip(ship);
updateState();
updateState(Serializer.fromShip(ship));
}
};
$scope.fuelChange = function (fuel) {
$scope.fuelChange = function(fuel) {
$scope.fuel = fuel;
win.triggerHandler('render');
};
$scope.statusRetracted = function (slot) {
$scope.statusRetracted = function(slot) {
return ship.getSlotStatus(slot, false);
};
$scope.statusDeployed = function (slot) {
$scope.statusDeployed = function(slot) {
return ship.getSlotStatus(slot, true);
};
// Utilify functions
function updateState() {
$state.go('outfit', {shipId: ship.id, code: $scope.code, bn: $scope.buildName}, {location:'replace', notify:false});
$scope.jrSeries.xMax = ship.cargoCapacity;
$scope.jrSeries.yMax = ship.jumpRangeWithMass(ship.unladenMass);
$scope.jrSeries.mass = ship.unladenMass;
function updateState(code) {
$scope.code = code;
$state.go('outfit', { shipId: ship.id, code: $scope.code, bn: $scope.buildName }, { location: 'replace', notify: false });
$scope.speedSeries.xMax = $scope.trSeries.xMax = $scope.jrSeries.xMax = ship.cargoCapacity;
$scope.jrSeries.yMax = ship.unladenRange;
$scope.trSeries.yMax = ship.unladenTotalRange;
win.triggerHandler('pwrchange');
}
// Hide any open menu/slot/etc if the background is clicked
$scope.$on('close', function () {
$scope.$on('close', function() {
$scope.selectedSlot = null;
});

View File

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

View File

@@ -1,9 +1,7 @@
angular.module('app').directive('areaChart', ['$window', function ($window) {
angular.module('app').directive('areaChart', ['$window', function($window) {
return {
restrict: 'A',
scope:{
scope: {
config: '=',
series: '='
},
@@ -11,111 +9,71 @@ angular.module('app').directive('areaChart', ['$window', function ($window) {
var series = scope.series,
config = scope.config,
labels = config.labels,
margin = {top: 15, right: 15, bottom: 35, left: 60},
margin = { top: 15, right: 15, bottom: 35, left: 60 },
fmt = d3.format('.3r'),
fmtLong = d3.format('.2f'),
func = series.func,
drag = d3.behavior.drag(),
dragging = false,
// Define Axes
xAxis = d3.svg.axis().outerTickSize(0).orient("bottom").tickFormat(d3.format('.2r')),
yAxis = d3.svg.axis().ticks(6).outerTickSize(0).orient("left").tickFormat(fmt),
xAxis = d3.svg.axis().outerTickSize(0).orient('bottom').tickFormat(d3.format('.2r')),
yAxis = d3.svg.axis().ticks(6).outerTickSize(0).orient('left').tickFormat(fmt),
x = d3.scale.linear(),
y = d3.scale.linear();
y = d3.scale.linear(),
data = [];
// Create chart
var svg = d3.select(element[0]).append("svg");
var vis = svg.append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var svg = d3.select(element[0]).append('svg');
var vis = svg.append('g').attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
// Define Area
var area = d3.svg.area();
var gradient = vis.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);
var gradient = vis.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
var yTxt = vis.append("g").attr("class", "y axis")
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", -50)
.attr("dy", ".1em")
.style("text-anchor", "middle")
var yTxt = vis.append('g').attr('class', 'y axis')
.append('text')
.attr('transform', 'rotate(-90)')
.attr('y', -50)
.attr('dy', '.1em')
.style('text-anchor', 'middle')
.text(labels.yAxis.title + ' (' + labels.yAxis.unit + ')');
// Create X Axis SVG Elements
var xLbl = vis.append("g").attr("class", "x axis");
var xTxt = xLbl.append("text")
.attr("y", 30)
.attr("dy", ".1em")
.style("text-anchor", "middle")
var xLbl = vis.append('g').attr('class', 'x axis');
var xTxt = xLbl.append('text')
.attr('y', 30)
.attr('dy', '.1em')
.style('text-anchor', 'middle')
.text(labels.xAxis.title + ' (' + labels.xAxis.unit + ')');
// Create and Add tooltip
var tip = vis.append("g").style("display", "none");
tip.append("rect").attr("width","4em").attr("height", "2em").attr("x", "0.5em").attr("y","-1em").attr("class","tip");
tip.append("circle")
.attr("class", "marker")
.attr("r", 4);
tip.append("text").attr("class", 'label x').attr("y", "-0.25em");
tip.append("text").attr("class", 'label y').attr("y", '0.85em');
var tip = vis.append('g').style('display', 'none');
tip.append('rect').attr('width', '4.5em').attr('height', '2em').attr('x', '0.5em').attr('y', '-1em').attr('class', 'tip');
tip.append('circle')
.attr('class', 'marker')
.attr('r', 4);
tip.append('text').attr('class', 'label x').attr('y', '-0.25em');
tip.append('text').attr('class', 'label y').attr('y', '0.85em');
/**
* Watch for changes in the series data (mass changes, etc)
*/
scope.$watchCollection('series', render);
angular.element($window).bind('orientationchange resize render', render);
function render() {
var width = element[0].parentElement.offsetWidth,
height = width * 0.5,
w = width - margin.left - margin.right,
h = height - margin.top - margin.bottom,
data = [];
if (series.xMax == series.xMin) {
var yVal = func(series.xMin);
data.push([ series.xMin, yVal ]);
data.push([ series.xMin, yVal ]);
area.x(function(d,i) { return i * w; }).y0(h).y1(function(d) { return y(d[1]); });
} else {
for (var d = series.xMin; d <= series.xMax; d += 1) {
data.push([ d, func(d) ]);
}
area.x(function(d) { return x(d[0]); }).y0(h).y1(function(d) { return y(d[1]); });
}
// Update Chart Size
svg.attr("width", width).attr("height", height);
// Update domain and scale for axes;
x.range([0, w]).domain([series.xMin, series.xMax]).clamp(true);
xAxis.scale(x);
xLbl.attr("transform", "translate(0," + h + ")");
xTxt.attr("x", w/2);
y.range([h, 0]).domain([series.yMin, series.yMax]);
yAxis.scale(y);
yTxt.attr("x", -h/2);
vis.selectAll(".y.axis").call(yAxis);
vis.selectAll(".x.axis").call(xAxis);
// Remove existing elements
vis.selectAll('path.area').remove();
vis.insert("path",':first-child') // Area/Path to appear behind everything else
.datum(data)
.attr("class", "area")
vis.insert('path', ':first-child') // Area/Path to appear behind everything else
.data([data])
.attr('class', 'area')
.attr('fill', 'url(#gradient)')
.attr("d", area)
.attr('d', area)
.on('mouseover', showTip)
.on('mouseout', hideTip)
.on('mousemove', moveTip)
@@ -127,28 +85,71 @@ angular.module('app').directive('areaChart', ['$window', function ($window) {
moveTip.call(this);
showTip();
})
.on("dragend", function() {
.on('dragend', function() {
dragging = false;
hideTip();
})
.on('drag', moveTip);
/**
* Watch for changes in the series data (mass changes, etc)
*/
scope.$watchCollection('series', render);
angular.element($window).bind('orientationchange resize render', render);
function render() {
var width = element[0].parentElement.offsetWidth,
height = width * 0.5,
w = width - margin.left - margin.right,
h = height - margin.top - margin.bottom;
data.length = 0; // Reset Data array
if (series.xMax == series.xMin) {
var yVal = func(series.xMin);
data.push([ series.xMin, yVal ]);
data.push([ series.xMin, yVal ]);
area.x(function(d, i) { return i * w; }).y0(h).y1(function(d) { return y(d[1]); });
} else {
for (var val = series.xMin; val <= series.xMax; val += 1) {
data.push([ val, func(val) ]);
}
area.x(function(d) { return x(d[0]); }).y0(h).y1(function(d) { return y(d[1]); });
}
// Update Chart Size
svg.attr('width', width).attr('height', height);
// Update domain and scale for axes
x.range([0, w]).domain([series.xMin, series.xMax]).clamp(true);
xAxis.scale(x);
xLbl.attr('transform', 'translate(0,' + h + ')');
xTxt.attr('x', w / 2);
y.range([h, 0]).domain([series.yMin, series.yMax]);
yAxis.scale(y);
yTxt.attr('x', -h / 2);
vis.selectAll('.y.axis').call(yAxis);
vis.selectAll('.x.axis').call(xAxis);
vis.selectAll('path.area') // Area/Path to appear behind everything else
.data([data])
.attr('d', area);
}
function showTip() {
tip.style("display", null);
tip.style('display', null);
}
function hideTip() {
if (!dragging) {
tip.style("display", "none");
tip.style('display', 'none');
}
}
function moveTip() {
var xPos = d3.mouse(this)[0], x0 = x.invert(xPos), y0 = func(x0), flip = (x0 / x.domain()[1] > 0.75);
tip.attr("transform", "translate(" + x(x0) + "," + y(y0) + ")");
tip.selectAll('rect').attr("x", flip? '-4.5em' : "0.5em").style("text-anchor", flip? 'end' : 'start');
tip.selectAll('text.label').attr("x", flip? "-1em" : "1em").style("text-anchor", flip? 'end' : 'start');
var xPos = d3.mouse(this)[0], x0 = x.invert(xPos), y0 = func(x0), flip = (x0 / x.domain()[1] > 0.65);
tip.attr('transform', 'translate(' + x(x0) + ',' + y(y0) + ')');
tip.selectAll('rect').attr('x', flip ? '-5.75em' : '0.5em').style('text-anchor', flip ? 'end' : 'start');
tip.selectAll('text.label').attr('x', flip ? '-2em' : '1em').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);
}

View File

@@ -1,10 +1,10 @@
angular.module('app').directive('barChart', ['$window', function ($window) {
angular.module('app').directive('barChart', ['$window', function($window) {
function bName (build) {
function bName(build) {
return build.buildName + '\n' + build.name;
}
var insertLinebreaks = function (d) {
function insertLinebreaks(d) {
var el = d3.select(this);
var words = d.split('\n');
el.text('').attr('y', -6);
@@ -14,11 +14,11 @@ angular.module('app').directive('barChart', ['$window', function ($window) {
tspan.attr('x', -9).attr('dy', 12);
}
}
};
}
return {
restrict: 'A',
scope:{
scope: {
data: '=',
facet: '='
},
@@ -28,7 +28,7 @@ angular.module('app').directive('barChart', ['$window', function ($window) {
fmt = scope.facet.fmt,
properties = scope.facet.props,
unit = scope.facet.unit,
margin = {top: 10, right: 20, bottom: 35, left: 150},
margin = { top: 10, right: 20, bottom: 35, left: 150 },
y0 = d3.scale.ordinal(),
y1 = d3.scale.ordinal(),
x = d3.scale.linear(),
@@ -43,7 +43,7 @@ angular.module('app').directive('barChart', ['$window', function ($window) {
var tip = d3.tip()
.attr('class', 'd3-tip')
.html(function(property, propertyIndex) {
return (labels? (labels[propertyIndex] + ': ') : '') + fmt(property.value) + ' ' + unit;
return (labels ? (labels[propertyIndex] + ': ') : '') + fmt(property.value) + ' ' + unit;
});
vis.call(tip);
@@ -58,7 +58,7 @@ angular.module('app').directive('barChart', ['$window', function ($window) {
.attr('y', 30)
.attr('dy', '.1em')
.style('text-anchor', 'middle')
.text(scope.facet.title + (unit? (' (' + unit + ')') : ''));
.text(scope.facet.title + (unit ? (' (' + unit + ')') : ''));
/**
@@ -84,11 +84,11 @@ angular.module('app').directive('barChart', ['$window', function ($window) {
// Update X & Y Axis
x.range([0, w]).domain([0, maxVal]);
y0.domain(data.map(bName)).rangeRoundBands([0, h],0.3);
y0.domain(data.map(bName)).rangeRoundBands([0, h], 0.3);
y1.domain(properties).rangeRoundBands([0, y0.rangeBand()]);
vis.selectAll('.y.axis').call(yAxis);
vis.selectAll('.x.axis').attr('transform', 'translate(0,' + h + ')').call(xAxis);
xAxisLbl.attr('x', w/2);
xAxisLbl.attr('x', w / 2);
// Update Y-Axis labels
vis.selectAll('g.y.axis g text').each(insertLinebreaks);
@@ -102,13 +102,13 @@ angular.module('app').directive('barChart', ['$window', function ($window) {
.data(function(build) {
var o = [];
for (var i = 0; i < properties.length; i++) {
o.push({name: properties[i], value:build[properties[i]]});
o.push({ name: properties[i], value: build[properties[i]] });
}
return o;
})
.enter().append('rect')
.attr('height', y1.rangeBand())
.attr('x',0)
.attr('x', 0)
.attr('y', function(d) {return y1(d.name); })
.attr('width', function(d) { return x(d.value); })
.on('mouseover', tip.show)

View File

@@ -1,4 +1,4 @@
angular.module('app').directive('comparisonTable', ['$state', function ($state) {
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>'];
@@ -8,17 +8,17 @@ angular.module('app').directive('comparisonTable', ['$state', function ($state)
var f = facets[i];
var p = f.props;
var pl = p.length;
r1.push('<th rowspan="', f.props.length == 1 ? 2 : 1,'" colspan="',pl,'"');
r1.push('<th rowspan="', f.props.length == 1 ? 2 : 1, '" colspan="', pl, '"');
if (pl == 1) {
r1.push(' prop="',p[0],'" class="prop"');
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>');
r2.push('<th prop="', p[j], '" class="prop ', j === 0 ? 'lft' : '', '">', f.lbls[j], '</th>');
}
}
r1.push('>', f.title ,'</th>');
r1.push('>', f.title, '</th>');
}
}
r1.push('</tr><tr>');
@@ -30,16 +30,16 @@ angular.module('app').directive('comparisonTable', ['$state', function ($state)
function tblBody(facets, builds) {
var body = [];
if(builds.length === 0) {
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>');
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) {
@@ -59,13 +59,13 @@ angular.module('app').directive('comparisonTable', ['$state', function ($state)
return {
restrict: 'A',
link: function (scope, element) {
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 (){
var updateAll = function() {
header.html(tblHeader(scope.facets));
body.html(tblBody(scope.facets, scope.builds));
};

View File

@@ -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,42 +8,42 @@ 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
if(i > 0 && opts.length > 3 && o.class != prevClass && (!o.grp || o.rating != prevRating || o.mode)) {
if (i > 0 && opts.length > 3 && o.class != prevClass && (!o.grp || o.rating != prevRating || o.mode)) {
list.push('<br/>');
}
list.push('<li class="', o.name? 'lc' : 'c');
list.push('<li class="', o.name ? 'lc' : 'c');
if (cid == id) {
list.push(' active');
}
list.push((o.maxmass && mass > o.maxmass)? ' disabled"' : '" cpid="', id, '">');
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> ');
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.missile) {
if (o.missile) {
list.push('/' + o.missile);
}
if(o.name) {
if (o.name) {
list.push(' ' + o.name);
}
list.push('</li>');
prevClass = o.class;
prevRating= o.rating;
prevRating = o.rating;
}
}
return {
restrict: 'A',
scope:{
scope: {
opts: '=', // Component Options object
groups: '=', // Groups of Component Options
mass: '=', // Current ship unladen mass
@@ -57,13 +57,13 @@ angular.module('app').directive('componentSelect', function () {
var groups = scope.groups;
var mass = scope.mass || 0;
if(groups) {
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 (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>');
list.push('<div id="', grpCode, '" class="select-group">', g, '</div><ul>');
appendGroup(list, grp, cid, mass);
list.push('</ul>');
}

View File

@@ -0,0 +1,12 @@
angular.module('app').directive('contextMenu', ['$parse', function($parse) {
return function(scope, element, attrs) {
var fn = $parse(attrs.contextMenu);
element.bind('contextmenu', function(e) {
scope.$apply(function() {
e.preventDefault();
fn(scope, { $event: e });
});
});
};
}]);

View File

@@ -1,10 +1,10 @@
angular.module('app').directive('shipyardHeader', ['lodash', '$rootScope', 'Persist', 'ShipsDB', function (_, $rootScope, Persist, ships) {
angular.module('app').directive('shipyardHeader', ['lodash', '$rootScope', 'Persist', 'ShipsDB', function(_, $rootScope, Persist, ships) {
return {
restrict: 'E',
templateUrl: 'views/_header.html',
scope: true,
link: function (scope) {
link: function(scope) {
scope.openedMenu = null;
scope.ships = ships;
scope.allBuilds = Persist.builds;
@@ -12,25 +12,17 @@ angular.module('app').directive('shipyardHeader', ['lodash', '$rootScope', 'Pers
scope.allComparisons = Persist.comparisons;
scope.bs = Persist.state;
// Insurance options and management here for now.
$rootScope.insurance = {
opts: [
{ name:'Standard', pct: 0.05 },
{ name:'Alpha', pct: 0.025 },
{ name:'Beta', pct: 0.035 }
]
};
var insIndex = _.findIndex($rootScope.insurance.opts, 'name', Persist.getInsurance());
$rootScope.insurance.current = $rootScope.insurance.opts[insIndex != -1? insIndex : 0];
$rootScope.insurance.current = $rootScope.insurance.opts[insIndex != -1 ? insIndex : 0];
$rootScope.discounts.current = $rootScope.discounts.opts[Persist.getDiscount() || 0];
// Close menus if a navigation change event occurs
$rootScope.$on('$stateChangeStart',function(){
$rootScope.$on('$stateChangeStart', function() {
scope.openedMenu = null;
});
// Listen to close event to close opened menus or modals
$rootScope.$on('close', function () {
$rootScope.$on('close', function() {
scope.openedMenu = null;
$rootScope.showAbout = false;
});
@@ -38,13 +30,20 @@ angular.module('app').directive('shipyardHeader', ['lodash', '$rootScope', 'Pers
/**
* Save selected insurance option
*/
scope.updateInsurance = function(){
scope.updateInsurance = function() {
Persist.setInsurance($rootScope.insurance.current.name);
};
scope.openMenu = function (e, menu) {
/**
* Save selected discount option
*/
scope.updateDiscount = function() {
Persist.setDiscount($rootScope.discounts.opts.indexOf($rootScope.discounts.current));
};
scope.openMenu = function(e, menu) {
e.stopPropagation();
if(menu == scope.openedMenu) {
if (menu == scope.openedMenu) {
scope.openedMenu = null;
return;
}
@@ -63,7 +62,7 @@ angular.module('app').directive('shipyardHeader', ['lodash', '$rootScope', 'Pers
$rootScope.showAbout = true;
};
$rootScope.hideAbout = function (){
$rootScope.hideAbout = function() {
$rootScope.showAbout = false;
};

View File

@@ -0,0 +1,190 @@
angular.module('app').directive('lineChart', ['$window', function($window) {
return {
restrict: 'A',
scope: {
config: '=',
series: '='
},
link: function(scope, element) {
var seriesConfig = scope.series,
series = seriesConfig.series,
color = d3.scale.ordinal().range([ '#ff8c0d', '#1fb0ff', '#a05d56', '#d0743c']),
config = scope.config,
labels = config.labels,
margin = { top: 15, right: 15, bottom: 35, left: 60 },
fmt = d3.format('.3r'),
fmtLong = d3.format('.2f'),
func = seriesConfig.func,
drag = d3.behavior.drag(),
dragging = false,
// Define Scales
x = d3.scale.linear(),
y = d3.scale.linear(),
// Define Axes
xAxis = d3.svg.axis().scale(x).outerTickSize(0).orient('bottom').tickFormat(d3.format('.2r')),
yAxis = d3.svg.axis().scale(y).ticks(6).outerTickSize(0).orient('left').tickFormat(fmt),
data = [];
// Create chart
var svg = d3.select(element[0]).append('svg');
var vis = svg.append('g').attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
var lines = vis.append('g');
// Define Area
var line = d3.svg.line().y(function(d) { return y(d[1]); });
// Create Y Axis SVG Elements
var yTxt = vis.append('g').attr('class', 'y axis')
.append('text')
.attr('transform', 'rotate(-90)')
.attr('y', -50)
.attr('dy', '.1em')
.style('text-anchor', 'middle')
.text(labels.yAxis.title + ' (' + labels.yAxis.unit + ')');
// Create X Axis SVG Elements
var xLbl = vis.append('g').attr('class', 'x axis');
var xTxt = xLbl.append('text')
.attr('y', 30)
.attr('dy', '.1em')
.style('text-anchor', 'middle')
.text(labels.xAxis.title + ' (' + labels.xAxis.unit + ')');
// Create and Add tooltip
var tipWidth = (Math.max(labels.yAxis.unit.length, labels.xAxis.unit.length) * 1.25) + 2;
var tips = vis.append('g').style('display', 'none');
var background = vis.append('rect') // Background to capture hover/drag
.attr('fill-opacity', 0)
.on('mouseover', showTip)
.on('mouseout', hideTip)
.on('mousemove', moveTip)
.call(drag);
drag
.on('dragstart', function() {
dragging = true;
moveTip.call(this);
showTip();
})
.on('dragend', function() {
dragging = false;
hideTip();
})
.on('drag', moveTip);
/**
* Watch for changes in the series data (mass changes, etc)
*/
scope.$watchCollection('series', render);
angular.element($window).bind('orientationchange resize render', render);
function render() {
var width = element[0].parentElement.offsetWidth,
height = width * 0.5,
xMax = seriesConfig.xMax,
xMin = seriesConfig.xMin,
yMax = seriesConfig.yMax,
yMin = seriesConfig.yMin,
w = width - margin.left - margin.right,
h = height - margin.top - margin.bottom,
s, val, yVal, delta;
data.length = 0; // Reset Data array
if (seriesConfig.xMax == seriesConfig.xMin) {
line.x(function(d, i) { return i * w; });
} else {
line.x(function(d) { return x(d[0]); });
}
if (series) {
for (s = 0; s < series.length; s++) {
data.push([]);
}
if (xMax == xMin) {
yVal = func(xMin);
for (s = 0; s < series.length; s++) {
data[s].push( [ xMin, yVal[ series[s] ] ], [ 1, yVal[ series[s] ] ]);
}
} else {
delta = (xMax - xMin) / 30; // Only render 30 points on the graph
for (val = xMin; val <= xMax; val += delta) {
yVal = func(val);
for (s = 0; s < series.length; s++) {
data[s].push([ val, yVal[ series[s] ] ]);
}
}
}
} else {
var seriesData = [];
if (xMax == xMin) {
yVal = func(xMin);
seriesData.push([ xMin, yVal ], [ 1, yVal ]);
} else {
delta = (xMax - xMin) / 30; // Only render 30 points on the graph
for (val = xMin; val <= xMax; val += delta) {
seriesData.push([val, func(val) ]);
}
}
data.push(seriesData);
}
// Update Chart Size
svg.attr('width', width).attr('height', height);
background.attr('height', h).attr('width', w);
// Update domain and scale for axes
x.range([0, w]).domain([xMin, xMax]).clamp(true);
xLbl.attr('transform', 'translate(0,' + h + ')');
xTxt.attr('x', w / 2);
y.range([h, 0]).domain([yMin, yMax]);
yTxt.attr('x', -h / 2);
vis.selectAll('.y.axis').call(yAxis);
vis.selectAll('.x.axis').call(xAxis);
lines.selectAll('path.line')
.data(data)
.attr('d', line) // Update existing series
.enter() // Add new series
.append('path')
.attr('class', 'line')
.attr('stroke', function(d, i) { return color(i); })
.attr('stroke-width', 2)
.attr('d', line);
var tip = tips.selectAll('g.tooltip').data(data).enter().append('g').attr('class', 'tooltip');
tip.append('rect').attr('width', tipWidth + 'em').attr('height', '2em').attr('x', '0.5em').attr('y', '-1em').attr('class', 'tip');
tip.append('circle').attr('class', 'marker').attr('r', 4);
tip.append('text').attr('class', 'label x').attr('y', '-0.25em');
tip.append('text').attr('class', 'label y').attr('y', '0.85em');
}
function showTip() {
tips.style('display', null);
}
function hideTip() {
if (!dragging) {
tips.style('display', 'none');
}
}
function moveTip() {
var xPos = d3.mouse(this)[0], x0 = x.invert(xPos), y0 = func(x0), flip = (x0 / x.domain()[1] > 0.65);
var tip = tips.selectAll('g.tooltip').attr('transform', function(d, i) { return 'translate(' + x(x0) + ',' + y(series ? y0[series[i]] : y0) + ')'; });
tip.selectAll('rect').attr('x', flip ? (-tipWidth - 0.5) + 'em' : '0.5em').style('text-anchor', flip ? 'end' : 'start');
tip.selectAll('text.label').attr('x', flip ? '-1em' : '1em').style('text-anchor', flip ? 'end' : 'start');
tip.selectAll('text.label.x').text(fmtLong(x0) + ' ' + labels.xAxis.unit);
tips.selectAll('text.label.y').text(function(d, i) { return fmtLong(series ? y0[series[i]] : y0) + ' ' + labels.yAxis.unit; });
}
scope.$on('$destroy', function() {
angular.element($window).unbind('orientationchange resize render', render);
});
}
};
}]);

View File

@@ -1,13 +1,14 @@
angular.module('app').directive('powerBands', ['$window', function ($window) {
angular.module('app').directive('powerBands', ['$window', function($window) {
return {
restrict: 'A',
scope:{
scope: {
bands: '=',
available: '='
},
link: function(scope, element) {
var margin = {top: 20, right: 130, bottom: 20, left: 40},
var margin = { top: 20, right: 130, bottom: 20, left: 40 },
barHeight = 20,
bands = null,
innerHeight = (barHeight * 2) + 3,
height = innerHeight + margin.top + margin.bottom + 1,
wattScale = d3.scale.linear(),
@@ -19,29 +20,43 @@ angular.module('app').directive('powerBands', ['$window', function ($window) {
// Create chart
svg = d3.select(element[0]).append('svg'),
vis = svg.append('g').attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'),
deployed = vis.append('g'),
retracted = vis.append('g');
deployed = vis.append('g').attr('class', 'power-band'),
retracted = vis.append('g').attr('class', 'power-band');
svg.on('contextmenu', function() {
d3.event.preventDefault();
for (var i = 0, l = bands.length; i < l; i++) {
bands[i].retSelected = false;
bands[i].depSelected = false;
}
render();
});
// Create Y Axis SVG Elements
vis.append('g').attr('class', 'watt axis');
vis.append('g').attr('class', 'pct axis');
vis.append("text").attr('x', -35).attr('y', 16).attr('class','primary').text('RET');
vis.append("text").attr('x', -35).attr('y', barHeight + 18).attr('class','primary').text('DEP');
vis.append('text').attr('x', -35).attr('y', 16).attr('class', 'primary').text('RET');
vis.append('text').attr('x', -35).attr('y', barHeight + 18).attr('class', 'primary').text('DEP');
var retLbl = vis.append("text").attr('y', 16);
var depLbl = vis.append("text").attr('y', barHeight + 18);
var retLbl = vis.append('text').attr('y', 16);
var depLbl = vis.append('text').attr('y', barHeight + 18);
// Watch for changes to data and events
scope.$watchCollection('available', render);
angular.element($window).bind('orientationchange resize pwrchange', render);
function render() {
var bands = scope.bands,
available = scope.available,
bands = scope.bands;
var available = scope.available,
width = element[0].offsetWidth,
w = width - margin.left - margin.right,
maxBand = bands[bands.length - 1],
maxPwr = Math.max(available, maxBand.deployedSum);
deployedSum = 0,
retractedSum = 0,
retBandsSelected = false,
depBandsSelected = false,
maxPwr = Math.max(available, maxBand.retractedSum, maxBand.deployedSum);
// Update chart size
svg.attr('width', width).attr('height', height);
@@ -55,49 +70,80 @@ angular.module('app').directive('powerBands', ['$window', function ($window) {
// Update X & Y Axis
wattScale.range([0, w]).domain([0, maxPwr]).clamp(true);
pctScale.range([0, w]).domain([0, maxPwr / available]).clamp(true);
vis.selectAll('.watt.axis').call(wattAxis);
vis.selectAll('.pct.axis').attr('transform', 'translate(0,' + innerHeight + ')').call(pctAxis);
retLbl
.attr('x', w + 5 )
.attr('class',maxBand.retractedSum > available? 'warning': 'primary')
.text(wattFmt(Math.max(0,maxBand.retractedSum)) + ' (' + pctFmt(Math.max(0,maxBand.retractedSum / available)) + ')');
depLbl
.attr('x', w + 5 )
.attr('class',maxBand.deployedSum > available? 'warning': 'primary')
.text(wattFmt(Math.max(0,maxBand.deployedSum)) + ' (' + pctFmt(Math.max(0,maxBand.deployedSum / available)) + ')');
for (var b = 0, l = bands.length; b < l; b++) {
if (bands[b].retSelected) {
retractedSum += bands[b].retracted + bands[b].retOnly;
retBandsSelected = true;
}
if (bands[b].depSelected) {
deployedSum += bands[b].deployed + bands[b].retracted;
depBandsSelected = true;
}
}
retracted.selectAll("rect").data(bands).enter().append("rect")
.attr("height", barHeight)
.attr("width", function(d) { return Math.max(wattScale(d.retracted) - 1, 0); })
.attr("x", function(d) { return wattScale(d.retractedSum) - wattScale(d.retracted); })
updateLabel(retLbl, w, retBandsSelected, retBandsSelected ? retractedSum : maxBand.retractedSum, available);
updateLabel(depLbl, w, depBandsSelected, depBandsSelected ? deployedSum : maxBand.deployedSum, available);
retracted.selectAll('rect').data(bands).enter().append('rect')
.attr('height', barHeight)
.attr('width', function(d) { return Math.max(wattScale(d.retracted + d.retOnly) - 1, 0); })
.attr('x', function(d) { return wattScale(d.retractedSum) - wattScale(d.retracted + d.retOnly); })
.attr('y', 1)
.attr('class',function(d){ return (d.retractedSum > available)? 'warning' :'primary'; });
.on('click', function(d) {
d.retSelected = !d.retSelected;
render();
})
.attr('class', function(d) { return getClass(d.retSelected, d.retractedSum, available); });
retracted.selectAll("text").data(bands).enter().append("text")
.attr('x', function(d) { return wattScale(d.retractedSum) - (wattScale(d.retracted) / 2); })
retracted.selectAll('text').data(bands).enter().append('text')
.attr('x', function(d) { return wattScale(d.retractedSum) - (wattScale(d.retracted + d.retOnly) / 2); })
.attr('y', 15)
.style('text-anchor', 'middle')
.attr('class','primary-bg')
.text(function(d,i) { return bandText(d.retracted, i); });
.attr('class', 'primary-bg')
.on('click', function(d) {
d.retSelected = !d.retSelected;
render();
})
.text(function(d, i) { return bandText(d.retracted + d.retOnly, i); });
deployed.selectAll("rect").data(bands).enter().append("rect")
.attr("height", barHeight)
.attr("width", function(d) { return Math.max(wattScale(d.deployed + d.retracted) - 1, 0); })
.attr("x", function(d) { return wattScale(d.deployedSum) - wattScale(d.retracted) - wattScale(d.deployed); })
deployed.selectAll('rect').data(bands).enter().append('rect')
.attr('height', barHeight)
.attr('width', function(d) { return Math.max(wattScale(d.deployed + d.retracted) - 1, 0); })
.attr('x', function(d) { return wattScale(d.deployedSum) - wattScale(d.retracted) - wattScale(d.deployed); })
.attr('y', barHeight + 2)
.attr('class',function(d){ return (d.deployedSum > available)? 'warning' :'primary'; });
.on('click', function(d) {
d.depSelected = !d.depSelected;
render();
})
.attr('class', function(d) { return getClass(d.depSelected, d.deployedSum, available); });
deployed.selectAll("text").data(bands).enter().append("text")
deployed.selectAll('text').data(bands).enter().append('text')
.attr('x', function(d) { return wattScale(d.deployedSum) - ((wattScale(d.retracted) + wattScale(d.deployed)) / 2); })
.attr('y', barHeight + 17)
.style('text-anchor', 'middle')
.attr('class','primary-bg')
.text(function(d,i) { return bandText(d.deployed + d.retracted, i); });
.attr('class', 'primary-bg')
.on('click', function(d) {
d.depSelected = !d.depSelected;
render();
})
.text(function(d, i) { return bandText(d.deployed + d.retracted, i); });
}
function updateLabel(lbl, width, selected, sum, available) {
lbl
.attr('x', width + 5 )
.attr('class', getClass(selected, sum, available))
.text(wattFmt(Math.max(0, sum)) + ' (' + pctFmt(Math.max(0, sum / available)) + ')');
}
function getClass(selected, sum, available) {
return selected ? 'secondary' : (sum > available) ? 'warning' : 'primary';
}
function bandText(val, index) {
if (val > 0 && wattScale(val) > 13) {
return index + 1;

View File

@@ -1,33 +1,33 @@
angular.module('app').directive('slider', ['$window', function ($window) {
angular.module('app').directive('slider', ['$window', function($window) {
return {
restrict: 'A',
scope:{
scope: {
max: '=',
unit: '=',
change: '&onChange'
},
link: function(scope, element) {
var margin = {top: -10, right: 140, bottom: 0, left: 50},
var margin = { top: -10, right: 145, bottom: 0, left: 50 },
height = 40, // Height is fixed
h = height - margin.top - margin.bottom,
fmt = d3.format('.2f'),
pct = d3.format('.1%'),
unit = scope.unit,
val = scope.max,
svg = d3.select(element[0]).append("svg"),
vis = svg.append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")"),
xAxis = vis.append("g").attr("class", "x slider-axis").attr("transform", "translate(0," + h / 2 + ")"),
svg = d3.select(element[0]).append('svg'),
vis = svg.append('g').attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'),
xAxis = vis.append('g').attr('class', 'x slider-axis').attr('transform', 'translate(0,' + h / 2 + ')'),
x = d3.scale.linear(),
slider = vis.append("g").attr("class", "slider"),
filled = slider.append('path').attr('class', 'filled').attr("transform", "translate(0," + h/2 + ")"),
brush = d3.svg.brush().x(x).extent([scope.max, scope.max]).on("brush", brushed),
handle = slider.append("circle").attr("class", "handle").attr("r", '0.6em'),
lbl = slider.append("g").append("text").attr("y", h/2);
slider = vis.append('g').attr('class', 'slider'),
filled = slider.append('path').attr('class', 'filled').attr('transform', 'translate(0,' + h / 2 + ')'),
brush = d3.svg.brush().x(x).extent([scope.max, scope.max]).on('brush', brushed),
handle = slider.append('circle').attr('class', 'handle').attr('r', '0.6em'),
lbl = slider.append('g').append('text').attr('y', h / 2);
slider.call(brush);
slider.select(".background").attr("height", h);
handle.attr("transform", "translate(0," + h / 2 + ")");
slider.select('.background').attr('height', h);
handle.attr('transform', 'translate(0,' + h / 2 + ')');
/**
* Watch for changes in the max, window size
@@ -42,21 +42,21 @@ angular.module('app').directive('slider', ['$window', function ($window) {
function render() {
var width = element[0].offsetWidth, w = width - margin.left - margin.right;
svg.attr("width", width).attr("height", height);
svg.attr('width', width).attr('height', height);
x.domain([0, scope.max]).range([0, w]).clamp(true);
handle.attr("cx", x(val));
handle.attr('cx', x(val));
xAxis
.call(d3.svg.axis()
.scale(x)
.orient("bottom")
.orient('bottom')
.tickFormat(function(d) { return d + unit; })
.tickValues([0, scope.max / 4, scope.max / 2, (3 * scope.max) / 4, scope.max])
.tickSize(0)
.tickPadding(12))
.select(".domain");
.select('.domain');
lbl.attr('x', w + 20);
slider.call(brush.extent([val, val])).call(brush.event);
slider.selectAll(".extent,.resize").remove();
slider.selectAll('.extent,.resize').remove();
}
function brushed() {
@@ -66,9 +66,9 @@ angular.module('app').directive('slider', ['$window', function ($window) {
brush.extent([val, val]);
}
lbl.text(fmt(val) + ' ' + unit + ' ' + pct(val / scope.max));
scope.change({val: val});
handle.attr("cx", x(val));
filled.attr("d", "M0,0V0H" + x(val) + "V0");
scope.change({ val: val });
handle.attr('cx', x(val));
filled.attr('d', 'M0,0V0H' + x(val) + 'V0');
}
scope.$on('$destroy', function() {

View File

@@ -1,13 +1,13 @@
angular.module('app').directive('slotHardpoint', ['$rootScope', function ($r) {
angular.module('app').directive('slotHardpoint', ['$rootScope', function($r) {
return {
restrict: 'A',
scope:{
scope: {
hp: '=',
size: '=',
lbl: '=',
lbl: '='
},
templateUrl: 'views/_slot-hardpoint.html',
link: function (scope) {
link: function(scope) {
scope.$r = $r;
}
};

View File

@@ -1,7 +1,7 @@
angular.module('app').directive('slotInternal', ['$rootScope', function ($r) {
angular.module('app').directive('slotInternal', ['$rootScope', function($r) {
return {
restrict: 'A',
scope:{
scope: {
c: '=slot',
lbl: '=',
fuel: '='

View File

@@ -1,17 +1,17 @@
/**
* BBCode Generator functions for embedding in the Elite Dangerous Forums
*/
angular.module('app').factory('Utils', ['$window','$state','$http', '$q', function ($window, $state, $http, $q) {
angular.module('app').factory('Utils', ['$window', '$state', '$http', '$q', function($window, $state, $http, $q) {
var shortenAPI = 'https://www.googleapis.com/urlshortener/v1/url?key=';
function shortenUrl(url) {
if ($window.navigator.onLine) {
return $http.post(shortenAPI + GAPI_KEY, {longUrl:url}).then(function(response) {
return $http.post(shortenAPI + GAPI_KEY, { longUrl: url }).then(function(response) {
return response.data.id;
});
} else {
return $q.reject({statusText: 'Not Online'});
return $q.reject({ statusText: 'Not Online' });
}
}
@@ -39,7 +39,7 @@ angular.module('app').factory('Utils', ['$window','$state','$http', '$q', functi
for (i = 0; i < builds.length; i++) {
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]');
l.push('[tr][td]', b.name, '[/td][td]', b.buildName, '[/td]');
for (j = 0, fl = facets.length; j < fl; j++) {
if (facets[j].active) {
@@ -52,8 +52,8 @@ angular.module('app').factory('Utils', ['$window','$state','$http', '$q', functi
}
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]');
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('');
}

View File

@@ -1,7 +1,7 @@
/**
* [description]
*/
angular.module('app').service('Persist', ['$window','lodash', function ($window, _) {
angular.module('app').service('Persist', ['$window', 'lodash', function($window, _) {
var LS_KEY_BUILDS = 'builds';
var LS_KEY_COMPARISONS = 'comparisons';
var localStorage = $window.localStorage;
@@ -19,8 +19,8 @@ angular.module('app').service('Persist', ['$window','lodash', function ($window,
this.lsEnabled = false;
}
this.builds = buildJson? angular.fromJson(buildJson) : {};
this.comparisons = comparisonJson? angular.fromJson(comparisonJson) : {};
this.builds = buildJson ? angular.fromJson(buildJson) : {};
this.comparisons = comparisonJson ? angular.fromJson(comparisonJson) : {};
var buildCount = Object.keys(this.builds).length;
this.state = {
@@ -35,7 +35,7 @@ angular.module('app').service('Persist', ['$window','lodash', function ($window,
* @param {string} name The name of the build
* @param {string} code The serialized code
*/
this.saveBuild = function (shipId, name, code) {
this.saveBuild = function(shipId, name, code) {
if (!this.lsEnabled) {
return;
}
@@ -44,7 +44,7 @@ angular.module('app').service('Persist', ['$window','lodash', function ($window,
this.builds[shipId] = {};
}
if(!this.builds[shipId][name]) {
if (!this.builds[shipId][name]) {
this.state.buildCount++;
this.state.hasBuilds = true;
}
@@ -62,7 +62,7 @@ angular.module('app').service('Persist', ['$window','lodash', function ($window,
* @param {string} name The name of the build
* @return {string} The serialized build string.
*/
this.getBuild = function (shipId, name) {
this.getBuild = function(shipId, name) {
if (this.builds[shipId] && this.builds[shipId][name]) {
return this.builds[shipId][name];
}
@@ -76,8 +76,8 @@ angular.module('app').service('Persist', ['$window','lodash', function ($window,
* @param {string} shipId The unique id for a model of ship
* @param {string} name The name of the build
*/
this.deleteBuild = function (shipId, name) {
if(this.lsEnabled && this.builds[shipId][name]) {
this.deleteBuild = function(shipId, name) {
if (this.lsEnabled && this.builds[shipId][name]) {
delete this.builds[shipId][name];
if (Object.keys(this.builds[shipId]).length === 0) {
delete this.builds[shipId];
@@ -90,8 +90,8 @@ angular.module('app').service('Persist', ['$window','lodash', function ($window,
var comps = this.comparisons;
for (var c in comps) {
for (var i = 0; i < comps[c].builds.length; i++) { // For all builds in the current comparison
if(comps[c].builds[i].shipId == shipId && comps[c].builds[i].buildName == name) {
comps[c].builds.splice(i,1);
if (comps[c].builds[i].shipId == shipId && comps[c].builds[i].buildName == name) {
comps[c].builds.splice(i, 1);
break; // A build is unique ber comparison
}
}
@@ -107,7 +107,7 @@ angular.module('app').service('Persist', ['$window','lodash', function ($window,
* @param {array} builds Array of builds
* @param {array} facets Array of facet indices
*/
this.saveComparison = function (name, builds, facets){
this.saveComparison = function(name, builds, facets) {
if (!this.lsEnabled) {
return;
}
@@ -117,7 +117,7 @@ angular.module('app').service('Persist', ['$window','lodash', function ($window,
}
this.comparisons[name] = {
facets: facets,
builds: _.map(builds, function (b) { return {shipId: b.id, buildName: b.buildName }; })
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;
@@ -128,7 +128,7 @@ angular.module('app').service('Persist', ['$window','lodash', function ($window,
* @param {string} name [description]
* @return {object} Object containing array of facets and ship id + build names
*/
this.getComparison = function (name) {
this.getComparison = function(name) {
if (this.comparisons[name]) {
return this.comparisons[name];
}
@@ -139,7 +139,7 @@ angular.module('app').service('Persist', ['$window','lodash', function ($window,
* Removes the comparison from localstorage.
* @param {string} name Comparison name
*/
this.deleteComparison = function (name) {
this.deleteComparison = function(name) {
if (this.lsEnabled && this.comparisons[name]) {
delete this.comparisons[name];
localStorage.setItem(LS_KEY_COMPARISONS, angular.toJson(this.comparisons));
@@ -166,7 +166,7 @@ angular.module('app').service('Persist', ['$window','lodash', function ($window,
* Get the saved insurance type
* @return {string} The name of the saved insurance type of null
*/
this.getInsurance = function () {
this.getInsurance = function() {
if (this.lsEnabled) {
return localStorage.getItem('insurance');
}
@@ -177,17 +177,38 @@ angular.module('app').service('Persist', ['$window','lodash', function ($window,
* Persist selected insurance type
* @param {string} name Insurance type name
*/
this.setInsurance = function (name) {
this.setInsurance = function(name) {
if (this.lsEnabled) {
return localStorage.setItem('insurance', name);
}
};
/**
* Persist selected discount
* @param {number} val Discount value/amount
*/
this.setDiscount = function(val) {
if (this.lsEnabled) {
return localStorage.setItem('discount', val);
}
};
/**
* Get the saved discount
* @return {number} val Discount value/amount
*/
this.getDiscount = function() {
if (this.lsEnabled) {
return localStorage.getItem('discount');
}
return null;
};
/**
* Retrieve the last router state from local storage
* @param {object} state State object containing state name and params
*/
this.getState = function () {
this.getState = function() {
if (this.lsEnabled) {
var state = localStorage.getItem('state');
if (state) {
@@ -201,9 +222,9 @@ angular.module('app').service('Persist', ['$window','lodash', function ($window,
* Save the current router state to localstorage
* @param {object} state State object containing state name and params
*/
this.setState = function (state) {
this.setState = function(state) {
if (this.lsEnabled) {
localStorage.setItem('state',angular.toJson(state));
localStorage.setItem('state', angular.toJson(state));
}
};

View File

@@ -1,7 +1,7 @@
/**
* Service managing seralization and deserialization of models for use in URLs and persistene.
*/
angular.module('app').service('Serializer', ['lodash', function (_) {
angular.module('app').service('Serializer', ['lodash', function(_) {
/**
* Serializes the ships selected components for all slots to a URL friendly string.
@@ -10,7 +10,7 @@ angular.module('app').service('Serializer', ['lodash', function (_) {
*/
this.fromShip = function(ship) {
var power = {
enabled: [ship.cargoScoop.enabled? 1 : 0],
enabled: [ship.cargoScoop.enabled ? 1 : 0],
priorities: [ship.cargoScoop.priority]
};
@@ -20,9 +20,9 @@ angular.module('app').service('Serializer', ['lodash', function (_) {
_.map(ship.hardpoints, mapGroup, power),
_.map(ship.internal, mapGroup, power),
'.',
LZString.compressToBase64(power.enabled.join('')).replace(/\//g,'-'),
LZString.compressToBase64(power.enabled.join('')).replace(/\//g, '-'),
'.',
LZString.compressToBase64(power.priorities.join('')).replace(/\//g,'-')
LZString.compressToBase64(power.priorities.join('')).replace(/\//g, '-')
];
return _.flatten(data).join('');
@@ -35,11 +35,8 @@ angular.module('app').service('Serializer', ['lodash', function (_) {
* @param {Ship} ship The ship instance to be updated
* @param {string} code The string to deserialize
*/
this.toShip = function (ship, dataString) {
var commonCount = ship.common.length,
hpCount = commonCount + ship.hardpoints.length,
totalCount = hpCount + ship.internal.length,
common = new Array(ship.common.length),
this.toShip = function(ship, dataString) {
var common = new Array(ship.common.length),
hardpoints = new Array(ship.hardpoints.length),
internal = new Array(ship.internal.length),
parts = dataString.split('.'),
@@ -47,12 +44,12 @@ angular.module('app').service('Serializer', ['lodash', function (_) {
enabled = null,
code = parts[0];
if(parts[1]) {
enabled = LZString.decompressFromBase64(parts[1].replace(/-/g,'/')).split('');
if (parts[1]) {
enabled = LZString.decompressFromBase64(parts[1].replace(/-/g, '/')).split('');
}
if(parts[2]) {
priorities = LZString.decompressFromBase64(parts[2].replace(/-/g,'/')).split('');
if (parts[2]) {
priorities = LZString.decompressFromBase64(parts[2].replace(/-/g, '/')).split('');
}
decodeToArray(code, internal, decodeToArray(code, hardpoints, decodeToArray(code, common, 1)));
@@ -61,19 +58,23 @@ angular.module('app').service('Serializer', ['lodash', function (_) {
// - priorities
// - enabled/disabled
ship.buildWith({
ship.buildWith(
{
bulkheads: code.charAt(0) * 1,
common: common,
hardpoints: hardpoints,
internal: internal,
}, priorities, enabled);
internal: internal
},
priorities,
enabled
);
};
this.fromComparison = function (name, builds, facets, predicate, desc) {
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)});
builds.forEach(function(b) {
shipBuilds.push({ s: b.id, n: b.buildName, c: this.fromShip(b) });
}.bind(this));
return LZString.compressToBase64(angular.toJson({
@@ -81,12 +82,12 @@ angular.module('app').service('Serializer', ['lodash', function (_) {
b: shipBuilds,
f: facets,
p: predicate,
d: desc? 1 : 0
})).replace(/\//g,'-');
d: desc ? 1 : 0
})).replace(/\//g, '-');
};
this.toComparison = function (code) {
return angular.fromJson(LZString.decompressFromBase64(code.replace(/-/g,'/')));
this.toComparison = function(code) {
return angular.fromJson(LZString.decompressFromBase64(code.replace(/-/g, '/')));
};
/**
@@ -98,14 +99,14 @@ angular.module('app').service('Serializer', ['lodash', function (_) {
* @return {string} The id of the selected component or '-' if none selected
*/
function mapGroup(slot) {
this.enabled.push(slot.enabled? 1 : 0);
this.enabled.push(slot.enabled ? 1 : 0);
this.priorities.push(slot.priority);
return (slot.id === null)? '-' : slot.id;
return slot.id === null ? '-' : slot.id;
}
function decodeToArray(code, arr, codePos) {
for (i = 0; i < arr.length; i++) {
for (var i = 0; i < arr.length; i++) {
if (code.charAt(codePos) == '-') {
arr[i] = 0;
codePos++;

View File

@@ -1,4 +1,10 @@
angular.module('shipyard').factory('ComponentSet', ['lodash', function (_) {
angular.module('shipyard').factory('ComponentSet', ['lodash', function(_) {
function filter(data, maxClass, minClass, mass) {
return _.filter(data, function(c) {
return c.class <= maxClass && c.class >= minClass && (c.maxmass === undefined || mass <= c.maxmass);
});
}
function ComponentSet(components, mass, maxCommonArr, maxInternal, maxHardPoint) {
this.mass = mass;
@@ -8,7 +14,7 @@ angular.module('shipyard').factory('ComponentSet', ['lodash', function (_) {
this.hpClass = {};
this.intClass = {};
for (var i = 0; i < components.common.length; i ++) {
for (var i = 0; i < components.common.length; i++) {
var max = maxCommonArr[i];
switch (i) {
// Slots where component class must be equal to slot class
@@ -22,21 +28,21 @@ angular.module('shipyard').factory('ComponentSet', ['lodash', function (_) {
}
}
for(var h in components.hardpoints) {
for (var h in components.hardpoints) {
this.hardpoints[h] = filter(components.hardpoints[h], maxHardPoint, 0, this.mass);
}
for(var g in components.internal) {
for (var g in components.internal) {
this.internal[g] = filter(components.internal[g], maxInternal, 0, this.mass);
}
}
ComponentSet.prototype.getHps = function(c) {
if(!this.hpClass[c]) {
if (!this.hpClass[c]) {
var o = this.hpClass[c] = {};
for(var key in this.hardpoints) {
var data = filter(this.hardpoints[key], c, c? 1 : 0, this.mass);
if(data.length) { // If group is not empty
for (var key in this.hardpoints) {
var data = filter(this.hardpoints[key], c, c ? 1 : 0, this.mass);
if (data.length) { // If group is not empty
o[key] = data;
}
}
@@ -45,11 +51,11 @@ angular.module('shipyard').factory('ComponentSet', ['lodash', function (_) {
};
ComponentSet.prototype.getInts = function(c) {
if(!this.intClass[c]) {
if (!this.intClass[c]) {
var o = this.intClass[c] = {};
for(var key in this.internal) {
for (var key in this.internal) {
var data = filter(this.internal[key], c, 0, this.mass);
if(data.length) { // If group is not empty
if (data.length) { // If group is not empty
o[key] = data;
}
}
@@ -57,12 +63,6 @@ angular.module('shipyard').factory('ComponentSet', ['lodash', function (_) {
return this.intClass[c];
};
function filter (data, maxClass, minClass, mass) {
return _.filter(data, function (c) {
return c.class <= maxClass && c.class >= minClass && (c.maxmass === undefined || mass <= c.maxmass);
});
}
return ComponentSet;
}]);

View File

@@ -1,4 +1,22 @@
angular.module('shipyard').factory('Ship', ['Components', 'calcShieldStrength', 'calcJumpRange', 'lodash', function (Components, calcShieldStrength, calcJumpRange, _) {
angular.module('shipyard').factory('Ship', ['Components', 'calcShieldStrength', 'calcJumpRange', 'calcTotalRange', 'lodash', function(Components, calcShieldStrength, calcJumpRange, calcTotalRange, _) {
/**
* Returns the power usage type of a slot and it's particular component
* @param {object} slot The Slot
* @param {object} component The component in the slot
* @return {string} The key for the power usage type
*/
function powerUsageType(slot, component) {
if (component) {
if (component.retractedOnly) {
return 'retOnly';
}
if (component.passive) {
return 'retracted';
}
}
return slot.cat != 1 ? 'retracted' : 'deployed';
}
/**
* Ship model used to track all ship components and properties.
@@ -10,18 +28,18 @@ angular.module('shipyard').factory('Ship', ['Components', 'calcShieldStrength',
function Ship(id, properties, slots) {
this.id = id;
this.cargoScoop = { c: Components.cargoScoop(), type: 'SYS' };
this.bulkheads = { incCost: true, maxClass: 8 };
this.bulkheads = { incCost: true, maxClass: 8, discount: 1 };
for (var p in properties) { this[p] = properties[p]; } // Copy all base properties from shipData
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++){
group.push({id: null, c: null, incCost: true, maxClass: slotGroup[i]});
for (var i = 0; i < slotGroup.length; i++) {
group.push({ id: null, c: null, incCost: true, maxClass: slotGroup[i], discount: 1 });
}
}
this.c = { incCost: true, c: { name: this.name, cost: this.cost } }; // Make a 'Ship' component similar to other components
this.c = { incCost: true, discount: 1, c: { name: this.name, cost: this.cost } }; // Make a 'Ship' component similar to other components
this.costList = _.union(this.internal, this.common, this.hardpoints);
this.costList.push(this.bulkheads); // Add The bulkheads
@@ -37,22 +55,12 @@ angular.module('shipyard').factory('Ship', ['Components', 'calcShieldStrength',
this.powerList.unshift(this.common[0]); // Add Power Plant
this.priorityBands = [
{deployed: 0, retracted: 0},
{deployed: 0, retracted: 0},
{deployed: 0, retracted: 0},
{deployed: 0, retracted: 0},
{deployed: 0, retracted: 0}
{ deployed: 0, retracted: 0, retOnly: 0 },
{ deployed: 0, retracted: 0, retOnly: 0 },
{ deployed: 0, retracted: 0, retOnly: 0 },
{ deployed: 0, retracted: 0, retOnly: 0 },
{ deployed: 0, retracted: 0, retOnly: 0 }
];
// Cumulative and aggragate stats
this.fuelCapacity = 0;
this.cargoCapacity = 0;
this.ladenMass = 0;
this.armourAdded = 0;
this.shieldMultiplier = 1;
this.totalCost = this.cost;
this.unladenMass = this.mass;
this.armourTotal = this.armour;
}
/**
@@ -64,49 +72,70 @@ angular.module('shipyard').factory('Ship', ['Components', 'calcShieldStrength',
common = this.common,
hps = this.hardpoints,
bands = this.priorityBands,
cl = common.length, hl = hps.length, il = internal.length,
i,l;
cl = common.length,
i, l;
// Reset Cumulative stats
this.fuelCapacity = 0;
this.cargoCapacity = 0;
this.ladenMass = 0;
this.armourAdded = 0;
this.shieldMultiplier = 1;
this.totalCost = this.cost;
this.unladenMass = this.mass;
this.armourTotal = this.armour;
this.bulkheads.c = null;
this.useBulkhead(comps.bulkheads || 0, true);
this.cargoScoop.priority = priorities? priorities[0] * 1 : 0;
this.cargoScoop.enabled = enabled? enabled[0] * 1 : true;
this.cargoScoop.priority = priorities ? priorities[0] * 1 : 0;
this.cargoScoop.enabled = enabled ? enabled[0] * 1 : true;
for (i = 0, l = this.priorityBands.length; i < l; i++) {
this.priorityBands[i].deployed = 0;
this.priorityBands[i].retracted = 0;
this.priorityBands[i].retOnly = 0;
}
if (this.cargoScoop.enabled) {
bands[this.cargoScoop.priority].retracted += this.cargoScoop.c.power;
}
for(i = 0; i < cl; i++) {
common[i].enabled = enabled? enabled[i + 1] * 1 : true;
common[i].priority = priorities? priorities[i + 1] * 1 : 0;
for (i = 0; i < cl; i++) {
common[i].cat = 0;
common[i].enabled = enabled ? enabled[i + 1] * 1 : true;
common[i].priority = priorities ? priorities[i + 1] * 1 : 0;
common[i].type = 'SYS';
common[i].c = common[i].id = null; // Resetting 'old' component if there was one
this.use(common[i], comps.common[i], Components.common(i, comps.common[i]), true);
}
common[1].type = 'ENG'; // Thrusters
common[2].type = 'ENG'; // FSD
cl++; // Increase accounting for Cargo Scoop
cl++; // Increase accounts for Cargo Scoop
for(i = 0, l = comps.hardpoints.length; i < l; i++) {
hps[i].enabled = enabled? enabled[cl + i] * 1 : true;
hps[i].priority = priorities? priorities[cl + i] * 1 : 0;
hps[i].type = hps[i].maxClass? 'WEP' : 'SYS';
for (i = 0, l = hps.length; i < l; i++) {
hps[i].cat = 1;
hps[i].enabled = enabled ? enabled[cl + i] * 1 : true;
hps[i].priority = priorities ? priorities[cl + i] * 1 : 0;
hps[i].type = hps[i].maxClass ? 'WEP' : 'SYS';
hps[i].c = hps[i].id = null; // Resetting 'old' component if there was one
if (comps.hardpoints[i] !== 0) {
this.use(hps[i], comps.hardpoints[i], Components.hardpoints(comps.hardpoints[i]), true);
} else {
hps[i].c = hps[i].id = null;
}
}
for(i = 0, l = comps.internal.length; i < l; i++) {
internal[i].enabled = enabled? enabled[hl + cl + i] * 1 : true;
internal[i].priority = priorities? priorities[hl + cl + i] * 1 : 0;
cl += hps.length; // Increase accounts for hardpoints
for (i = 0, l = internal.length; i < l; i++) {
internal[i].cat = 2;
internal[i].enabled = enabled ? enabled[cl + i] * 1 : true;
internal[i].priority = priorities ? priorities[cl + i] * 1 : 0;
internal[i].type = 'SYS';
internal[i].id = internal[i].c = null; // Resetting 'old' component if there was one
if (comps.internal[i] !== 0) {
this.use(internal[i], comps.internal[i], Components.internal(comps.internal[i]), true);
} else {
internal[i].id = internal[i].c = null;
}
}
@@ -134,18 +163,14 @@ angular.module('shipyard').factory('Ship', ['Components', 'calcShieldStrength',
*/
Ship.prototype.use = function(slot, id, component, preventUpdate) {
if (slot.id != id) { // Selecting a different component
var slotIndex = this.internal.indexOf(slot);
// Slot is an internal slot, is not being emptied, and the selected component group/type must be of unique
if(slotIndex != -1 && component && _.includes(['sg','rf','fs'],component.grp)) {
if (slot.cat == 2 && component && _.includes(['sg', 'rf', 'fs'], component.grp)) {
// Find another internal slot that already has this type/group installed
var similarSlotIndex = this.findInternalByGroup(component.grp);
var similarSlot = this.findInternalByGroup(component.grp);
// If another slot has an installed component with of the same type
if (similarSlotIndex != -1 && similarSlotIndex != slotIndex) {
// Empty the slot
var similarSlot = this.internal[similarSlotIndex];
if (!preventUpdate && similarSlot && similarSlot !== slot) {
this.updateStats(similarSlot, null, similarSlot.c, true); // Update stats but don't trigger a global update
similarSlot.id = null;
similarSlot.c = null;
similarSlot.id = similarSlot.c = null; // Empty the slot
}
}
var oldComponent = slot.c;
@@ -163,7 +188,7 @@ angular.module('shipyard').factory('Ship', ['Components', 'calcShieldStrength',
* @param {number} fuel Fuel available in tons
* @return {number} Jump range in Light Years
*/
Ship.prototype.jumpRangeWithMass = function (mass, fuel) {
Ship.prototype.jumpRangeWithMass = function(mass, fuel) {
return calcJumpRange(mass, this.common[2].c, fuel);
};
@@ -173,65 +198,81 @@ angular.module('shipyard').factory('Ship', ['Components', 'calcShieldStrength',
* @param {string} group Component group/type
* @return {number} The index of the slot in ship.internal
*/
Ship.prototype.findInternalByGroup = function (group) {
return _.findIndex(this.internal, function (slot) {
Ship.prototype.findInternalByGroup = function(group) {
var index = _.findIndex(this.internal, function(slot) {
return slot.c && slot.c.grp == group;
});
if (index !== -1) {
return this.internal[index];
}
return null;
};
Ship.prototype.changePriority = function (slot, newPriority) {
if(newPriority >= 0 && newPriority < this.priorityBands.length) {
/**
* Will change the priority of the specified slot if the new priority is valid
* @param {object} slot The slot to be updated
* @param {number} newPriority The new priority to be set
* @return {boolean} Returns true if the priority was changed (within range)
*/
Ship.prototype.changePriority = function(slot, newPriority) {
if (newPriority >= 0 && newPriority < this.priorityBands.length) {
var oldPriority = slot.priority;
slot.priority = newPriority;
if (slot.enabled) {
var usage = (slot.c.passive || this.hardpoints.indexOf(slot) == -1)? 'retracted' : 'deployed';
if (slot.enabled) { // Only update power if the slot is enabled
var usage = powerUsageType(slot, slot.c);
this.priorityBands[oldPriority][usage] -= slot.c.power;
this.priorityBands[newPriority][usage] += slot.c.power;
this.updatePower();
return true;
}
return true;
}
return false;
};
Ship.prototype.setCostIncluded = function (item, included) {
Ship.prototype.setCostIncluded = function(item, included) {
if (item.incCost != included && item.c) {
this.totalCost += included? item.c.cost : -item.c.cost;
this.totalCost += included ? item.c.cost : -item.c.cost;
}
item.incCost = included;
};
Ship.prototype.setSlotEnabled = function (slot, enabled) {
if (slot.enabled != enabled && slot.c) { // Enabled state is changing
var usage = (slot.c.passive || this.hardpoints.indexOf(slot) == -1)? 'retracted' : 'deployed';
this.priorityBands[slot.priority][usage] += enabled? slot.c.power : -slot.c.power;
Ship.prototype.setSlotEnabled = function(slot, enabled) {
if (slot.enabled != enabled) { // Enabled state is changing
slot.enabled = enabled;
if (slot.c) {
this.priorityBands[slot.priority][powerUsageType(slot, slot.c)] += enabled ? slot.c.power : -slot.c.power;
if (slot.c.grp == 'sg') {
this.updateShieldStrength();
} else if (slot.c.grp == 'sb') {
this.shieldMultiplier += slot.c.shieldmul * (enabled ? 1 : -1);
this.updateShieldStrength();
}
this.updatePower();
}
slot.enabled = enabled;
}
};
Ship.prototype.getSlotStatus = function (slot, deployed) {
if(!slot.c) { // Empty Slot
Ship.prototype.getSlotStatus = function(slot, deployed) {
if (!slot.c) { // Empty Slot
return 0; // No Status (Not possible)
}
else if (!slot.enabled) {
} else if (!slot.enabled) {
return 1; // Disabled
}
else if (deployed) {
return this.priorityBands[slot.priority].deployedSum > this.powerAvailable? 2 : 3; // Offline : Online
}
else if (this.hardpoints.indexOf(slot) != -1 && !slot.c.passive) { // Active hardpoints have no retracted status
} else if (deployed && !slot.c.retractedOnly) { // Certain component (e.g. Detaild Surface scanner) are power only while retracted
return this.priorityBands[slot.priority].deployedSum > this.powerAvailable ? 2 : 3; // Offline : Online
// Active hardpoints have no retracted status
} else if ((deployed && slot.c.retractedOnly) || (slot.cat === 1 && !slot.c.passive)) {
return 0; // No Status (Not possible)
}
return this.priorityBands[slot.priority].retractedSum > this.powerAvailable? 2 : 3; // Offline : Online
return this.priorityBands[slot.priority].retractedSum > this.powerAvailable ? 2 : 3; // Offline : Online
};
/**
* Updates the ship's cumulative and aggregated stats based on the component change.
*/
Ship.prototype.updateStats = function(slot, n, old, preventUpdate) {
var isHardPoint = this.hardpoints.indexOf(slot) != -1;
var powerChange = slot == this.common[0];
if (old) { // Old component now being removed
@@ -246,7 +287,7 @@ angular.module('shipyard').factory('Ship', ['Components', 'calcShieldStrength',
this.armourAdded -= old.armouradd;
break;
case 'sb':
this.shieldMultiplier -= old.shieldmul;
this.shieldMultiplier -= slot.enabled ? old.shieldmul : 0;
break;
}
@@ -254,8 +295,8 @@ angular.module('shipyard').factory('Ship', ['Components', 'calcShieldStrength',
this.totalCost -= old.cost;
}
if(old.power) {
this.priorityBands[slot.priority][(isHardPoint && !old.passive)? 'deployed' : 'retracted'] -= old.power;
if (old.power && slot.enabled) {
this.priorityBands[slot.priority][powerUsageType(slot, old)] -= old.power;
powerChange = true;
}
this.unladenMass -= old.mass || 0;
@@ -276,7 +317,7 @@ angular.module('shipyard').factory('Ship', ['Components', 'calcShieldStrength',
this.armourAdded += n.armouradd;
break;
case 'sb':
this.shieldMultiplier += n.shieldmul;
this.shieldMultiplier += slot.enabled ? n.shieldmul : 0;
break;
}
@@ -284,8 +325,8 @@ angular.module('shipyard').factory('Ship', ['Components', 'calcShieldStrength',
this.totalCost += n.cost;
}
if (n.power) {
this.priorityBands[slot.priority][(isHardPoint && !n.passive)? 'deployed' : 'retracted'] += n.power;
if (n.power && slot.enabled) {
this.priorityBands[slot.priority][powerUsageType(slot, n)] += n.power;
powerChange = true;
}
this.unladenMass += n.mass || 0;
@@ -294,7 +335,7 @@ angular.module('shipyard').factory('Ship', ['Components', 'calcShieldStrength',
this.ladenMass = this.unladenMass + this.cargoCapacity + this.fuelCapacity;
this.armourTotal = this.armourAdded + this.armour;
if(!preventUpdate) {
if (!preventUpdate) {
if (powerChange) {
this.updatePower();
}
@@ -307,9 +348,9 @@ angular.module('shipyard').factory('Ship', ['Components', 'calcShieldStrength',
var bands = this.priorityBands;
var prevRetracted = 0, prevDeployed = 0;
for(var i = 0, l = bands.length; i < l; i++) {
for (var i = 0, l = bands.length; i < l; i++) {
var band = bands[i];
prevRetracted = band.retractedSum = prevRetracted + band.retracted;
prevRetracted = band.retractedSum = prevRetracted + band.retracted + band.retOnly;
prevDeployed = band.deployedSum = prevDeployed + band.deployed + band.retracted;
}
@@ -319,8 +360,8 @@ angular.module('shipyard').factory('Ship', ['Components', 'calcShieldStrength',
};
Ship.prototype.updateShieldStrength = function() {
var sgSI = this.findInternalByGroup('sg'); // Find Shield Generator slot Index if any
this.shieldStrength = sgSI != -1? calcShieldStrength(this.mass, this.shields, this.internal[sgSI].c, this.shieldMultiplier) : 0;
var sgSlot = this.findInternalByGroup('sg'); // Find Shield Generator slot Index if any
this.shieldStrength = sgSlot && sgSlot.enabled ? calcShieldStrength(this.mass, this.shields, sgSlot.c, this.shieldMultiplier) : 0;
};
/**
@@ -328,23 +369,12 @@ angular.module('shipyard').factory('Ship', ['Components', 'calcShieldStrength',
*/
Ship.prototype.updateJumpStats = function() {
var fsd = this.common[2].c; // Frame Shift Drive;
var fuelRemaining = this.fuelCapacity % fsd.maxfuel; // Fuel left after making N max jumps
var jumps = this.fuelCapacity / fsd.maxfuel;
this.unladenRange = calcJumpRange(this.unladenMass + fsd.maxfuel, fsd, this.fuelCapacity); // Include fuel weight for jump
this.fullTankRange = calcJumpRange(this.unladenMass + this.fuelCapacity, fsd, this.fuelCapacity); // Full Tanke
this.ladenRange = calcJumpRange(this.ladenMass, fsd, this.fuelCapacity);
this.maxJumpCount = Math.ceil(jumps); // Number of full fuel jumps + final jump to empty tank
// Going backwards, start with the last jump using the remaining fuel
this.unladenTotalRange = fuelRemaining > 0? calcJumpRange(this.unladenMass + fuelRemaining, fsd, fuelRemaining): 0;
this.ladenTotalRange = fuelRemaining > 0? calcJumpRange(this.unladenMass + this.cargoCapacity + fuelRemaining, fsd, fuelRemaining): 0;
// For each max fuel jump, calculate the max jump range based on fuel left in the tank
for (var j = 0, l = Math.floor(jumps); j < l; j++) {
fuelRemaining += fsd.maxfuel;
this.unladenTotalRange += calcJumpRange(this.unladenMass + fuelRemaining, fsd);
this.ladenTotalRange += calcJumpRange(this.unladenMass + this.cargoCapacity + fuelRemaining, fsd);
}
this.unladenTotalRange = calcTotalRange(this.unladenMass, fsd, this.fuelCapacity);
this.ladenTotalRange = calcTotalRange(this.unladenMass + this.cargoCapacity, fsd, this.fuelCapacity);
this.maxJumpCount = Math.ceil(this.fuelCapacity / fsd.maxfuel);
};
return Ship;

View File

@@ -22,48 +22,48 @@ angular.module('shipyard', ['ngLodash'])
// Map to lookup group labels/names for component grp
.value('GroupMap', {
// Common
pp:'Power Plant',
t:'Thrusters',
fsd:'Frame Shift Drive',
ls:'Life Support',
pd:'Power Distributor',
s:'Sensors',
ft:'Fuel Tank',
pp: 'Power Plant',
t: 'Thrusters',
fsd: 'Frame Shift Drive',
ls: 'Life Support',
pd: 'Power Distributor',
s: 'Sensors',
ft: 'Fuel Tank',
// Internal
fs:'Fuel Scoop',
sc:'Scanners',
am:'Auto Field-Maint. Unit',
cr:'Cargo Racks',
fi:'FSD Interdictor',
hb:'Hatch Breaker Limpet Ctrl',
hr:'Hull Reinforcement Package',
rf:'Refinery',
scb:'Shield Cell Bank',
sg:'Shield Generator',
dc:'Docking Computer',
fx:'Fuel Transfer Limpet Ctrl',
pc:'Prospector Limpet Ctrl',
cc:'Collector Limpet Ctrl',
fs: 'Fuel Scoop',
sc: 'Scanners',
am: 'Auto Field-Maint. Unit',
cr: 'Cargo Racks',
fi: 'FSD Interdictor',
hb: 'Hatch Breaker Limpet Ctrl',
hr: 'Hull Reinforcement Package',
rf: 'Refinery',
scb: 'Shield Cell Bank',
sg: 'Shield Generator',
dc: 'Docking Computer',
fx: 'Fuel Transfer Limpet Ctrl',
pc: 'Prospector Limpet Ctrl',
cc: 'Collector Limpet Ctrl',
// Hard Points
bl: "Beam Laser",
ul: "Burst Laser",
c: "Cannon",
cs: "Cargo Scanner",
cm: "Countermeasure",
fc: "Fragment Cannon",
ws: "Frame Shift Wake Scanner",
kw: "Kill Warrant Scanner",
nl: "Mine Launcher",
ml: "Mining Laser",
mr: "Missile Rack",
pa: "Plasma Accelerator",
mc: "Multi-cannon",
pl: "Pulse Laser",
rg: "Rail Gun",
sb: "Shield Booster",
tp: "Torpedo Pylon"
bl: 'Beam Laser',
ul: 'Burst Laser',
c: 'Cannon',
cs: 'Cargo Scanner',
cm: 'Countermeasure',
fc: 'Fragment Cannon',
ws: 'Frame Shift Wake Scanner',
kw: 'Kill Warrant Scanner',
nl: 'Mine Launcher',
ml: 'Mining Laser',
mr: 'Missile Rack',
pa: 'Plasma Accelerator',
mc: 'Multi-cannon',
pl: 'Pulse Laser',
rg: 'Rail Gun',
sb: 'Shield Booster',
tp: 'Torpedo Pylon'
})
.value('shipPurpose', {
mp: 'Multi Purpose',
@@ -77,7 +77,7 @@ angular.module('shipyard', ['ngLodash'])
'Small',
'Medium',
'Large',
'Capital',
'Capital'
])
.value('hardPointClass', [
'Utility',
@@ -146,7 +146,7 @@ angular.module('shipyard', ['ngLodash'])
},
{ // 8
title: 'Power',
props: ['powerRetracted','powerDeployed','powerAvailable'],
props: ['powerRetracted', 'powerDeployed', 'powerAvailable'],
lbls: ['Retracted', 'Deployed', 'Available'],
unit: 'MW',
fmt: 'fPwr'
@@ -163,7 +163,7 @@ angular.module('shipyard', ['ngLodash'])
lbls: ['Unladen', 'Laden'],
unit: 'LY',
fmt: 'fRound'
},
}
])
/**
* Calculate the maximum single jump range based on mass and a specific FSD
@@ -174,7 +174,28 @@ angular.module('shipyard', ['ngLodash'])
* @return {number} Distance in Light Years
*/
.value('calcJumpRange', function(mass, fsd, fuel) {
return Math.pow(Math.min(fuel === undefined? fsd.maxfuel : fuel, fsd.maxfuel) / fsd.fuelmul, 1 / fsd.fuelpower ) * fsd.optmass / mass;
return Math.pow(Math.min(fuel === undefined ? fsd.maxfuel : fuel, fsd.maxfuel) / fsd.fuelmul, 1 / fsd.fuelpower ) * fsd.optmass / mass;
})
/**
* Calculate the total range based on mass and a specific FSD, and all fuel available
*
* @param {number} mass Mass of a ship: laden, unlanden, partially laden, etc
* @param {object} fsd The FDS object/component with maxfuel, fuelmul, fuelpower, optmass
* @param {number} fuel The total fuel available
* @return {number} Distance in Light Years
*/
.value('calcTotalRange', function(mass, fsd, fuel) {
var fuelRemaining = fuel % fsd.maxfuel; // Fuel left after making N max jumps
var jumps = fuel / fsd.maxfuel;
mass += fuelRemaining;
// Going backwards, start with the last jump using the remaining fuel
var totalRange = fuelRemaining > 0 ? Math.pow(fuelRemaining / fsd.fuelmul, 1 / fsd.fuelpower ) * fsd.optmass / mass : 0;
// For each max fuel jump, calculate the max jump range based on fuel left in the tank
for (var j = 0, l = Math.floor(jumps); j < l; j++) {
fuelRemaining += fsd.maxfuel;
totalRange += Math.pow(fsd.maxfuel / fsd.fuelmul, 1 / fsd.fuelpower ) * fsd.optmass / mass;
}
return totalRange;
})
/**
* Calculate the a ships shield strength based on mass, shield generator and shield boosters used.
@@ -186,10 +207,7 @@ angular.module('shipyard', ['ngLodash'])
* @param {number} multiplier Shield multiplier for ship (1 + shield boosters if any)
* @return {number} Approximate shield strengh in MJ
*/
.value('calcShieldStrength', function (mass, shields, sg, multiplier) {
if (!sg) {
return 0;
}
.value('calcShieldStrength', function(mass, shields, sg, multiplier) {
if (mass <= sg.minmass) {
return shields * multiplier * sg.minmul;
}
@@ -200,4 +218,20 @@ angular.module('shipyard', ['ngLodash'])
return shields * multiplier * (sg.optmul + (mass - sg.optmass) / (sg.maxmass - sg.optmass) * (sg.maxmul - sg.optmul));
}
return shields * multiplier * sg.maxmul;
})
/**
* Calculate the a ships speed based on mass, and thrusters. Currently Innacurate / Incomplete :(
*
* @private
* @param {number} mass Current mass of the ship
* @param {number} baseSpeed Base speed m/s for ship
* @param {number} baseBoost Base boost m/s for ship
* @param {object} thrusters The shield generator used
* @return {object} Approximate speed and boost speed in m/s
*/
.value('calcSpeed', function(mass, baseSpeed, baseBoost) { //, thrusters) {
//var speed = baseSpeed * (1 + ((thrusters.optmass / mass) * 0.1 ) ); // TODO: find thruser coefficient(s)
//var boost = baseBoost * (1 + ((thrusters.optmass / mass) * 0.1 ) );
return { boost: baseSpeed, speed: baseBoost };
});

View File

@@ -1,10 +1,10 @@
angular.module('shipyard').service('Components', ['lodash', 'ComponentsDB', 'ShipsDB', 'ComponentSet', function (_, C, Ships, ComponentSet) {
angular.module('shipyard').service('Components', ['lodash', 'ComponentsDB', 'ShipsDB', 'ComponentSet', function(_, C, Ships, ComponentSet) {
this.cargoScoop = function() {
return { name: 'Cargo Hatch', class: 1, rating: 'H', power: 0.6};
return { name: 'Cargo Hatch', class: 1, rating: 'H', power: 0.6 };
};
this.common = function (typeIndex, componentId) {
this.common = function(typeIndex, componentId) {
return C.common[typeIndex][componentId];
};
@@ -49,7 +49,7 @@ angular.module('shipyard').service('Components', ['lodash', 'ComponentsDB', 'Shi
* @param {string} shipId Unique ship Id/Key
* @return {ComponentSet} The set of components the ship can install
*/
this.forShip = function (shipId) {
this.forShip = function(shipId) {
var ship = Ships[shipId];
return new ComponentSet(C, ship.properties.mass + 5, ship.slots.common, ship.slots.internal[0], ship.slots.hardpoints[0]);
};

View File

@@ -1,11 +1,9 @@
.chart {
.user-select-none();
display: inline-block;
margin: 0;
cursor: default;
overflow: hidden;
width: 33%;
box-sizing: border-box;
@@ -17,12 +15,23 @@
width: 100%;
});
.medPhone({
.axis {
font-size: 0.8em;
g.tick:nth-child(2n + 1) text {
display: none;
}
}
});
h3 {
text-align: center;
&[ng-click] {
cursor: pointer;
}
}
}
@@ -63,5 +72,3 @@ svg {
stroke-width: 1px;
}
}

View File

@@ -63,7 +63,7 @@ header {
.menu-item-label {
margin-left: 1em;
.medPhone({
.largePhone({
display: none;
});
}
@@ -92,21 +92,41 @@ header {
.tablet({
a {
li, a {
padding: 0.3em 0;
}
});
}
&.dbl {
.dbl {
-webkit-column-count: 2; /* Chrome, Safari, Opera */
-moz-column-count: 2; /* Firefox */
column-count: 2;
ul {
width: 10em;
min-width: 10em;
}
.smallTablet({
-webkit-column-count: 3; /* Chrome, Safari, Opera */
-moz-column-count: 3; /* Firefox */
column-count: 3;
ul {
min-width: 20em;
}
});
.largePhone({
-webkit-column-count: 2; /* Chrome, Safari, Opera */
-moz-column-count: 2; /* Firefox */
column-count: 2;
});
.smallPhone({
-webkit-column-count: 1; /* Chrome, Safari, Opera */
-moz-column-count: 1; /* Firefox */
column-count: 1;
});
}
ul {

View File

@@ -162,16 +162,38 @@ table.total {
});
.smallTablet({
overflow-x: auto;
-webkit-overflow-scrolling: touch;
width: 100% !important;
});
}
&.semi {
width: 33%;
.smallTablet({
.axis {
font-size: 0.8em;
g.tick:nth-child(2n + 1) text {
display: none;
}
}
});
.largePhone({
width: 100% !important;
});
}
}
.power-band {
text, rect {
cursor: pointer;
}
}
#componentPriority {
.tablet({
text.primary, text.warning, text.primary-bg {
text.primary, text.warning, text.primary-bg, text.secondary {
font-size: 0.8em;
}

View File

@@ -44,6 +44,7 @@
border-right: 1px solid @primary-disabled;
box-sizing: border-box;
padding-top: 0.2em;
padding-left: 0.1em;
}
.empty {

View File

@@ -18,13 +18,15 @@
<div class="menu-header" ng-class="{selected: openedMenu=='b', disabled: !bs.hasBuilds}" ng-click="openMenu($event,'b')">
<svg class="icon warning" ng-class="{'warning-disabled': !bs.hasBuilds}"><use xlink:href="#hammer"></use></svg><span class="menu-item-label"> Builds</span>
</div>
<div class="menu-list dbl" ng-if="openedMenu=='b'" ng-click="$event.stopPropagation();">
<ul ng-repeat="shipId in buildsList">
<div class="menu-list" ng-if="openedMenu=='b'" ng-click="$event.stopPropagation();">
<div class="dbl" >
<div><ul ng-repeat="shipId in buildsList">
{{ships[shipId].properties.name}}
<li ng-repeat="(name, build) in allBuilds[shipId]">
<a ui-sref-active="active" class="name" ui-sref="outfit({shipId:shipId, code:build, bn:name})" ng-bind="name"></a>
</li>
</ul>
</ul></div>
</div>
</div>
</div>
@@ -49,6 +51,10 @@
<ul>
Insurance
<li><select ng-model="insurance.current" ng-options="ins.name for (i,ins) in insurance.opts" ng-change="updateInsurance()"></select></li>
</ul><br>
<ul>
Discount
<li><select ng-model="discounts.current" ng-options="d.name for (i,d) in discounts.opts" ng-change="updateDiscount()"></select></li>
</ul>
<hr />
<ul>

View File

@@ -16,7 +16,7 @@
Any and all contributions and feedback are welcome. If you encounter any bugs please report them and provide as much detail as possible.
</p>
<form action="https://www.paypal.com/cgi-bin/webscr" method="post" target="_top">
<form action="https://www.paypal.com/cgi-bin/webscr" method="post" target="_blank">
<input type="hidden" name="cmd" value="_s-xclick">
<input type="hidden" name="encrypted" value="-----BEGIN PKCS7-----MIIHLwYJKoZIhvcNAQcEoIIHIDCCBxwCAQExggEwMIIBLAIBADCBlDCBjjELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtQYXlQYWwgSW5jLjETMBEGA1UECxQKbGl2ZV9jZXJ0czERMA8GA1UEAxQIbGl2ZV9hcGkxHDAaBgkqhkiG9w0BCQEWDXJlQHBheXBhbC5jb20CAQAwDQYJKoZIhvcNAQEBBQAEgYCjl3XoqQ3Q+x/qS7Va1lwvF0IgUs8gBbrwj1/uEv/xFyPSB2G0kgWqiB2c/8vvfcjjyMr4nlzLUlmQ0yl1zZaeTXFciN5a+JsvaBISThIlN9UP7PXP61TVHCECtt/hBNtlOmg8/gG8khJCj8+qi81XsNAz5bEDpdahKW3fwGHD4jELMAkGBSsOAwIaBQAwgawGCSqGSIb3DQEHATAUBggqhkiG9w0DBwQI4EVkn3RE+9qAgYhg2sTmY1Gul2yJyLYJZPRMO/PwgzEogb2NlIcshJSO+KvBea5NjjTXN2EJNqJa24h4lGA1mdrSgzTGDrVbdcnuti9+7ggn5R5s5IwEEQnN4JQx3IAqsp3UmJbti5t776Ns50nQbjA8NzxI+gwUmIvUQaVs6wC4HYXG6q8QtqUIWeVDhvbnt+H8oIIDhzCCA4MwggLsoAMCAQICAQAwDQYJKoZIhvcNAQEFBQAwgY4xCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEUMBIGA1UEChMLUGF5UGFsIEluYy4xEzARBgNVBAsUCmxpdmVfY2VydHMxETAPBgNVBAMUCGxpdmVfYXBpMRwwGgYJKoZIhvcNAQkBFg1yZUBwYXlwYWwuY29tMB4XDTA0MDIxMzEwMTMxNVoXDTM1MDIxMzEwMTMxNVowgY4xCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEUMBIGA1UEChMLUGF5UGFsIEluYy4xEzARBgNVBAsUCmxpdmVfY2VydHMxETAPBgNVBAMUCGxpdmVfYXBpMRwwGgYJKoZIhvcNAQkBFg1yZUBwYXlwYWwuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDBR07d/ETMS1ycjtkpkvjXZe9k+6CieLuLsPumsJ7QC1odNz3sJiCbs2wC0nLE0uLGaEtXynIgRqIddYCHx88pb5HTXv4SZeuv0Rqq4+axW9PLAAATU8w04qqjaSXgbGLP3NmohqM6bV9kZZwZLR/klDaQGo1u9uDb9lr4Yn+rBQIDAQABo4HuMIHrMB0GA1UdDgQWBBSWn3y7xm8XvVk/UtcKG+wQ1mSUazCBuwYDVR0jBIGzMIGwgBSWn3y7xm8XvVk/UtcKG+wQ1mSUa6GBlKSBkTCBjjELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtQYXlQYWwgSW5jLjETMBEGA1UECxQKbGl2ZV9jZXJ0czERMA8GA1UEAxQIbGl2ZV9hcGkxHDAaBgkqhkiG9w0BCQEWDXJlQHBheXBhbC5jb22CAQAwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQUFAAOBgQCBXzpWmoBa5e9fo6ujionW1hUhPkOBakTr3YCDjbYfvJEiv/2P+IobhOGJr85+XHhN0v4gUkEDI8r2/rNk1m0GA8HKddvTjyGw/XqXa+LSTlDYkqI8OwR8GEYj4efEtcRpRYBxV8KxAW93YDWzFGvruKnnLbDAF6VR5w/cCMn5hzGCAZowggGWAgEBMIGUMIGOMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExFjAUBgNVBAcTDU1vdW50YWluIFZpZXcxFDASBgNVBAoTC1BheVBhbCBJbmMuMRMwEQYDVQQLFApsaXZlX2NlcnRzMREwDwYDVQQDFAhsaXZlX2FwaTEcMBoGCSqGSIb3DQEJARYNcmVAcGF5cGFsLmNvbQIBADAJBgUrDgMCGgUAoF0wGAYJKoZIhvcNAQkDMQsGCSqGSIb3DQEHATAcBgkqhkiG9w0BCQUxDxcNMTUwNjA1MjEwNDQzWjAjBgkqhkiG9w0BCQQxFgQUx6Bs50H7tbYJln13pP5J7J1KiSUwDQYJKoZIhvcNAQEBBQAEgYBiOr1RX38uvwghuIZxKpjXX4LG/GoyYM6citfsBD5vjUGj0udmsamjlur+dwxJNs9dULnJO6huoTvxqxTui0Mh3n21YKoMqVE/erfNk2XygrJw9bEtW+HXjU3F+OGKR7dfD9STp2ZlvTEvZR9JRV5A/udC9/9U9eD5iLKRRwkIBg==-----END PKCS7-----
">

View File

@@ -16,6 +16,9 @@
<button ui-sref="outfit({shipId: ship.id,code:null, bn: buildName})" ng-disabled="!code">
<svg class="icon lg"><use xlink:href="#switch"></use></svg><span class="button-lbl">Reset</span>
</button>
<button ng-click="stripBuild()">
<svg class="icon lg"><use xlink:href="#feather"></use></svg><span class="button-lbl">Low-Weight</span>
</button>
</div>
</div>
@@ -53,7 +56,7 @@
<td>{{fRound(ship.speed)}} <u>m/s</u></td>
<td>{{fRound(ship.boost)}} <u>m/s</u></td>
<td>{{ship.armourTotal}} <span ng-if="ship.armourAdded">({{ship.armour}} + {{ship.armourAdded}})</span></td>
<td>{{fRound(ship.shieldStrength)}} <u>MJ</u> <span ng-if="ship.shieldMultiplier > 1">({{fRPct(ship.shieldMultiplier)}})</span></td>
<td>{{fRound(ship.shieldStrength)}} <u>MJ</u> <span ng-if="ship.shieldMultiplier > 1 && ship.shieldStrength > 0">({{fRPct(ship.shieldMultiplier)}})</span></td>
<td>{{fRound(ship.unladenMass)}} <u>T</u></td>
<td>{{fRound(ship.ladenMass)}} <u>T</u></td>
<td>{{fRound(ship.cargoCapacity)}} <u>T</u></td>
@@ -165,7 +168,7 @@
<div id="internal" class="group">
<h1>Internal Compartments</h1>
<div class="slot" ng-repeat="i in ship.internal" ng-click="selectSlot($event, i)" ng-class="{selected: selectedSlot==i}">
<div class="slot" ng-repeat="i in ship.internal" ng-click="selectSlot($event, i)" context-menu="select('i', i, $event, 'empty')" ng-class="{selected: selectedSlot==i}">
<div slot-internal class="details" slot="i" lbl="GMAP[i.c.grp]" fuel="ship.fuelCapacity"></div>
<div class="select" ng-if="selectedSlot==i" ng-click="select('i',i,$event)">
<div component-select s="i" groups="availCS.getInts(i.maxClass)"></div>
@@ -175,7 +178,7 @@
<div id="hardpoints" class="group">
<h1>HardPoints</h1>
<div class="slot" ng-repeat="h in ship.hardpoints | filter:{maxClass: '!0'}" ng-click="selectSlot($event, h)" ng-class="{selected: selectedSlot==h}">
<div class="slot" ng-repeat="h in ship.hardpoints | filter:{maxClass: '!0'}" ng-click="selectSlot($event, h)" context-menu="select('h', h, $event, 'empty')" ng-class="{selected: selectedSlot==h}">
<div slot-hardpoint class="details" hp="h" size="HPC[h.maxClass]" lbl="GMAP[h.c.grp]"></div>
<div class="select" ng-class="{hardpoint: h.maxClass > 0}" ng-if="selectedSlot==h" ng-click="select('h',h,$event)">
<div component-select s="h" groups="availCS.getHps(h.maxClass)"></div>
@@ -185,7 +188,7 @@
<div id="utility" class="group">
<h1>Utility Mounts</h1>
<div class="slot" ng-repeat="h in ship.hardpoints | filter:{maxClass: '0'}" ng-click="selectSlot($event, h)" ng-class="{selected: selectedSlot==h}">
<div class="slot" ng-repeat="h in ship.hardpoints | filter:{maxClass: '0'}" ng-click="selectSlot($event, h)" context-menu="select('h', h, $event, 'empty')" ng-class="{selected: selectedSlot==h}">
<div slot-hardpoint class="details" hp="h" size="HPC[h.maxClass]" lbl="GMAP[h.c.grp]"></div>
<div class="select" ng-class="{hardpoint: h.maxClass > 0}" ng-if="selectedSlot==h" ng-click="select('h',h,$event)">
<div component-select s="h" groups="availCS.getHps(h.maxClass)"></div>
@@ -212,7 +215,7 @@
<td><u>SYS</u></td>
<td>1</td>
<td class="ri">{{fPwr(pp.c.pGen)}}</td>
<td><u>100%</u></td>
<td class="ri"><u>100%</u></td>
<td></td>
<td></td>
</tr>
@@ -242,33 +245,44 @@
</tr>
</thead>
<tbody>
<tr class="toggleable" ng-repeat="c in costList | orderBy:costPredicate:costDesc" ng-if="c.c.cost > 0" ng-class="{disabled:!c.incCost}" ng-click="toggleCost(c)">
<td style="width:1em;">{{c.c.class}}{{c.c.rating}}</td>
<td class="le shorten" ng-bind="cName(c)"></td>
<td class="ri">{{fCrd(c.c.cost)}} <u>CR</u></td>
<tr ng-repeat="c in costList | orderBy:costPredicate:costDesc" ng-if="c.c.cost > 0" ng-class="{disabled:!c.incCost}">
<td class="toggleable" style="width:1em;" ng-click="toggleCost(c)">{{c.c.class}}{{c.c.rating}}</td>
<td class="le toggleable shorten" ng-bind="cName(c)" ng-click="toggleCost(c)"></td>
<td class="ri toggleable" ng-click="toggleCost(c)">{{fCrd(discounts.current.pct * c.c.cost)}} <u>CR</u></td>
</tr>
</tbody>
</table>
<table class="total">
<tr class="ri">
<td class="lbl">Total</td>
<td>{{fCrd(ship.totalCost)}} <u>CR</u></td>
<td>{{fCrd(ship.totalCost * discounts.current.pct)}} <u>CR</u></td>
</tr>
<tr class="ri">
<td class="lbl">Insurance</td>
<td>{{fCrd(ship.totalCost * insurance.current.pct)}} <u>CR</u></td>
<td>{{fCrd(ship.totalCost * discounts.current.pct * insurance.current.pct)}} <u>CR</u></td>
</tr>
</table>
</div>
<div class="group dbl">
<div class="group semi">
<h1>Jump Range</h1>
<div class="cen">
<div area-chart config="jrChart" series="jrSeries"></div>
<div line-chart config="jrChart" series="jrSeries"></div>
</div>
<div class="group semi">
<h1>Total Range</h1>
<div line-chart config="trChart" series="trSeries"></div>
</div>
<div class="group semi">
<h1>Thruster Speed</h1>
<div line-chart config="speedChart" series="speedSeries"></div>
</div>
<div class="group dbl">
<div slider max="ship.fuelCapacity" unit="'T'" on-change="::fuelChange(val)" style="position:relative; margin: 0 auto;">
<svg class="icon xl primary-disabled" style="position:absolute;height: 100%;"><use xlink:href="#fuel"></use></svg>
</div>
</div>
</div>
</div>

View File

@@ -38,6 +38,7 @@
"grp": "sc",
"name": "Detailed Surface Scanner",
"class": 1,
"retractedOnly": 1,
"rating": "C",
"cost": 250000,
"mass": 1.3,

View File

@@ -10,7 +10,7 @@
"boost": 320,
"agility": 8,
"shields": 60,
"armour": 90,
"armour": 162,
"fuelcost": 50,
"mass": 35
},

View File

@@ -10,7 +10,7 @@
"boost": 240,
"agility": 2,
"shields": 350,
"armour": 525,
"armour": 945,
"fuelcost": 50,
"mass": 400
},

View File

@@ -10,7 +10,7 @@
"boost": 340,
"agility": 6,
"shields": 140,
"armour": 210,
"armour": 378,
"fuelcost": 50,
"mass": 280
},

View File

@@ -10,7 +10,7 @@
"boost": 400,
"agility": 6,
"shields": 80,
"armour": 120,
"armour": 216,
"fuelcost": 50,
"mass": 180
},

View File

@@ -5,16 +5,16 @@
"name": "Diamondback Scout",
"manufacturer": "Lakon",
"class": 1,
"cost": 461312,
"cost": 461342,
"speed": 283,
"boost": 384,
"agility": 8,
"shields": 93,
"shields": 118,
"armour": 216,
"fuelcost": 50,
"mass": 170
},
"retailCost": 564300,
"retailCost": 564330,
"slots": {
"common": [
4,

View File

@@ -5,16 +5,16 @@
"name": "Diamondback Explorer",
"manufacturer": "Lakon",
"class": 1,
"cost": 1740931,
"cost": 1635691,
"speed": 242,
"boost": 316,
"agility": 5,
"shields": 115,
"shields": 146,
"armour": 270,
"fuelcost": 50,
"mass": 298
},
"retailCost": 2000000,
"retailCost": 1894760,
"slots": {
"common": [
4,

View File

@@ -10,7 +10,7 @@
"boost": 350,
"agility": 10,
"shields": 60,
"armour": 40,
"armour": 72,
"fuelcost": 50,
"mass": 50
},

View File

@@ -10,7 +10,7 @@
"boost": 300,
"agility": 0,
"shields": 200,
"armour": 300,
"armour": 540,
"fuelcost": 50,
"mass": 580
},

View File

@@ -10,7 +10,7 @@
"boost": 350,
"agility": 6,
"shields": 300,
"armour": 225,
"armour": 405,
"fuelcost": 50,
"mass": 250
},

View File

@@ -10,7 +10,7 @@
"boost": 300,
"agility": 6,
"shields": 50,
"armour": 50,
"armour": 90,
"fuelcost": 50,
"mass": 14
},

View File

@@ -10,7 +10,7 @@
"boost": 380,
"agility": 2,
"shields": 180,
"armour": 270,
"armour": 486,
"fuelcost": 50,
"mass": 400
},

View File

@@ -5,16 +5,16 @@
"name": "Imperial Courier",
"manufacturer": "Gutamaya",
"class": 1,
"cost": 2481521,
"cost": 2481552,
"speed": 277,
"boost": 380,
"agility": 6,
"shields": 230,
"shields": 197,
"armour": 144,
"fuelcost": 50,
"mass": 35
},
"retailCost": 2542900,
"retailCost": 2542931,
"slots": {
"common": [
4,

View File

@@ -10,7 +10,7 @@
"boost": 380,
"agility": 2,
"shields": 220,
"armour": 220,
"armour": 396,
"fuelcost": 50,
"mass": 580
},

View File

@@ -10,7 +10,7 @@
"boost": 280,
"agility": 6,
"shields": 260,
"armour": 260,
"armour": 468,
"fuelcost": 50,
"mass": 350
},

View File

@@ -10,7 +10,7 @@
"boost": 320,
"agility": 8,
"shields": 40,
"armour": 60,
"armour": 108,
"fuelcost": 50,
"mass": 25
},

View File

@@ -10,7 +10,7 @@
"boost": 350,
"agility": 3,
"shields": 90,
"armour": 90,
"armour": 162,
"fuelcost": 50,
"mass": 155
},

View File

@@ -10,7 +10,7 @@
"boost": 300,
"agility": 2,
"shields": 120,
"armour": 120,
"armour": 216,
"fuelcost": 50,
"mass": 420
},

View File

@@ -10,7 +10,7 @@
"boost": 200,
"agility": 0,
"shields": 240,
"armour": 240,
"armour": 432,
"fuelcost": 50,
"mass": 1000
},

View File

@@ -10,7 +10,7 @@
"boost": 400,
"agility": 6,
"shields": 105,
"armour": 70,
"armour": 126,
"fuelcost": 50,
"mass": 60
},

View File

@@ -10,7 +10,7 @@
"boost": 340,
"agility": 9,
"shields": 240,
"armour": 160,
"armour": 288,
"fuelcost": 50,
"mass": 230
},

View File

@@ -1,26 +1,29 @@
// Build / Built-in dependencies
var gulp = require('gulp'),
less = require('gulp-less'),
jshint = require('gulp-jshint'),
minifyCSS = require('gulp-minify-css'),
concat = require('gulp-concat'),
uglify = require('gulp-uglify'),
sourcemaps = require('gulp-sourcemaps'),
templateCache = require('gulp-angular-templatecache'),
htmlmin = require('gulp-htmlmin'),
template = require('gulp-template'),
mainBowerFiles = require('main-bower-files'),
del = require('del'),
runSequence = require('run-sequence'),
exec = require('child_process').exec,
RevAll = require('gulp-rev-all'),
gutil = require( 'gulp-util' ),
svgstore = require( 'gulp-svgstore' ),
svgmin = require( 'gulp-svgmin' ),
jsonlint = require("gulp-jsonlint"),
appCache = require("gulp-manifest"),
jasmine = require('gulp-jasmine'),
pkg = require('./package.json');
// Package.json / Gulp Dependencies
var appCache = require("gulp-manifest"),
concat = require('gulp-concat'),
del = require('del'),
eslint = require('gulp-eslint');
gutil = require('gulp-util'),
htmlmin = require('gulp-htmlmin'),
jsonlint = require("gulp-jsonlint"),
karma = require('karma').server,
less = require('gulp-less'),
mainBowerFiles = require('main-bower-files'),
minifyCSS = require('gulp-minify-css'),
revAll = require('gulp-rev-all'),
runSequence = require('run-sequence'),
sourcemaps = require('gulp-sourcemaps'),
svgstore = require('gulp-svgstore'),
svgmin = require('gulp-svgmin'),
template = require('gulp-template'),
templateCache = require('gulp-angular-templatecache'),
uglify = require('gulp-uglify');
var cdnHostStr = '';
gulp.task('less', function() {
@@ -36,20 +39,31 @@ gulp.task('less', function() {
});
gulp.task('js-lint', function() {
return gulp.src('app/js/**/*.js')
.pipe(jshint({
undef: true,
unused: true,
curly: true,
predef: [ 'angular','DB','d3', 'ga', 'GAPI_KEY', 'document' , 'LZString' ]
return gulp.src(['app/js/**/*.js', '!app/js/template_cache.js', '!app/js/db.js'])
.pipe(eslint({
globals: { angular:1, DB:1, d3:1, ga:1, GAPI_KEY:1, LZString: 1 },
rules: {
quotes: [2, 'single'],
strict: 'global',
eqeqeq: 'smart',
'space-after-keywords': [2, 'always'],
'no-use-before-define': 'no-func',
'space-before-function-paren': [2, 'never'],
'space-before-blocks': [2, 'always'],
'object-curly-spacing': [2, "always"],
'brace-style': [2, '1tbs', { allowSingleLine: true }]
},
envs: ['browser']
}))
.pipe(jshint.reporter('default'));
.pipe(eslint.format())
.pipe(eslint.failAfterError());
});
gulp.task('json-lint', function() {
return gulp.src('data/**/*.json')
.pipe(jsonlint())
.pipe(jsonlint.reporter());
.pipe(jsonlint.reporter())
.pipe(jsonlint.failAfterError());
});
gulp.task('bower', function(){
@@ -187,11 +201,11 @@ gulp.task('watch', function() {
});
gulp.task('cache-bust', function(done) {
var revAll = new RevAll({ prefix: cdnHostStr, dontRenameFile: ['.html','db.json'] });
var rev_all = new revAll({ prefix: cdnHostStr, dontRenameFile: ['.html','db.json'] });
var stream = gulp.src('build/**')
.pipe(revAll.revision())
.pipe(rev_all.revision())
.pipe(gulp.dest('build'))
.pipe(revAll.manifestFile())
.pipe(rev_all.manifestFile())
.pipe(gulp.dest('build'));
stream.on('end', function() {
@@ -238,9 +252,13 @@ gulp.task('upload', function(done) {
);
});
gulp.task('test', function () {
return gulp.src('tests/test-*.js')
.pipe(jasmine());
gulp.task('test', function (done) {
karma.start({
configFile: __dirname + '/test/karma.conf.js',
singleRun: true
}, function(exitStatus) {
done(exitStatus ? new gutil.PluginError('karma', { message: 'Unit tests failed!' }) : undefined);
});
});
gulp.task('lint', ['js-lint', 'json-lint']);

View File

@@ -1,6 +1,6 @@
{
"name": "coriolis_shipyard",
"version": "0.11.0",
"version": "0.13.3",
"repository": {
"type": "git",
"url": "https://github.com/cmmcleod/coriolis"
@@ -9,28 +9,35 @@
"engine": "node >= 0.12.2",
"dependencies": {},
"devDependencies": {
"async": "^0.9.0",
"del": "^1.1.1",
"gulp": "^3.8.11",
"gulp-angular-templatecache": "^1.6.0",
"gulp-concat": "^2.5.2",
"gulp-htmlmin": "^1.1.1",
"gulp-jasmine": "^2.0.1",
"gulp-jshint": "^1.10.0",
"gulp-jsonlint": "^1.0.2",
"gulp-less": "^3.0.2",
"angular-mocks": "1.3.x",
"async": "0.9.x",
"del": "1.2.x",
"gulp": "3.9.x",
"gulp-angular-templatecache": "1.6.x",
"gulp-concat": "2.5.x",
"gulp-eslint": "0.13.x",
"gulp-htmlmin": "1.1.x",
"gulp-jasmine": "2.0.x",
"gulp-jsonlint": "1.1.x",
"gulp-less": "3.0.x",
"gulp-manifest": "0.0.6",
"gulp-minify-css": "^1.0.0",
"gulp-minify-css": "1.1.x",
"gulp-rev-all": "0.8.18",
"gulp-sourcemaps": "^1.5.1",
"gulp-svgmin": "^1.1.2",
"gulp-svgstore": "^5.0.1",
"gulp-template": "^3.0.0",
"gulp-uglify": "^1.2.0",
"gulp-util": "^3.0.4",
"json-concat": "0.0.0",
"main-bower-files": "^2.6.2",
"run-sequence": "^1.0.2",
"uglify-js": "^2.4.19"
"gulp-sourcemaps": "1.5.x",
"gulp-svgmin": "1.1.x",
"gulp-svgstore": "5.0.x",
"gulp-template": "3.0.x",
"gulp-uglify": "1.2.x",
"gulp-util": "3.0.x",
"jasmine-core": "2.3.x",
"json-concat": "0.0.x",
"karma": "0.12.x",
"karma-jasmine": "0.3.x",
"karma-mocha-reporter": "1.0.x",
"karma-phantomjs-launcher": "0.2.x",
"main-bower-files": "2.8.x",
"phantomjs": "1.9.x",
"run-sequence": "1.1.x",
"uglify-js": "2.4.x"
}
}

26
test/karma.conf.js Normal file
View File

@@ -0,0 +1,26 @@
// Karma configuration
// Generated on Thu Jun 11 2015 19:39:40 GMT-0700 (PDT)
module.exports = function(config) {
config.set({
basePath: '',
// frameworks to use
// available frameworks: https://npmjs.org/browse/keyword/karma-adapter
frameworks: ['jasmine'],
// list of files / patterns to load in the browser
files: [
'../build/lib*.js',
'../node_modules/angular-mocks/angular-mocks.js',
'../build/app*.js',
'tests/**/*.js'
],
reporters: ['mocha'],
port: 9876,
colors: true,
// possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
logLevel: config.LOG_INFO,
autoWatch: false,
browsers: ['PhantomJS'],
singleRun: false
});
};

66
test/tests/test-data.js Normal file
View File

@@ -0,0 +1,66 @@
describe("Database", function() {
var shipProperties = ["grp", "name", "manufacturer", "class", "cost", "speed", "boost", "agility", "shields", "armour", "fuelcost", "mass"];
it("has ships and components", function() {
expect(DB.ships).toBeDefined()
expect(DB.components.common).toBeDefined();
expect(DB.components.hardpoints).toBeDefined();
expect(DB.components.internal).toBeDefined();
expect(DB.components.bulkheads).toBeDefined();
});
it("has unique IDs for every hardpoint", function() {
var ids = {};
var groups = DB.components.hardpoints;
for (var g in groups) {
var group = groups[g];
for (var i = 0; i < group.length; i++) {
var id = group[i].id;
expect(ids[id]).toBeFalsy('ID already exists: ' + id);
expect(group[i].grp).toBeDefined('Hardpoint has no group defined, ID:' + id);
ids[id] = true;
}
}
});
it("has valid internal components", function() {
var ids = {};
var groups = DB.components.internal;
for (var g in groups) {
var group = groups[g];
for (var i = 0; i < group.length; i++) {
var id = group[i].id;
expect(ids[id]).toBeFalsy('ID already exists: ' + id);
expect(group[i].grp).toBeDefined('Internal component has no group defined, ID:' + id);
ids[id] = true;
}
}
});
it("has data for every ship", function() {
for (var s in DB.ships) {
for (var p = 0; p < shipProperties.length; p++) {
expect(DB.ships[s].properties[shipProperties[p]]).toBeDefined(shipProperties[p] + ' is missing for ' + s);
}
expect(DB.ships[s].slots.common.length).toEqual(7, s + ' is missing common slots');
expect(DB.ships[s].defaults.common.length).toEqual(7, s + ' is missing common defaults');
expect(DB.ships[s].slots.hardpoints.length).toEqual(DB.ships[s].defaults.hardpoints.length, s + ' hardpoint slots and defaults dont match');
expect(DB.ships[s].slots.internal.length).toEqual(DB.ships[s].defaults.internal.length, s + ' hardpoint slots and defaults dont match');
expect(DB.ships[s].retailCost).toBeGreaterThan(DB.ships[s].properties.cost, s + ' has invalid retail cost');
expect(DB.components.bulkheads[s]).toBeDefined(s + ' is missing bulkheads');
}
});
it("has components with a group defined", function() {
for (var i = 0; i < DB.components.common.length; i++) {
var group = DB.components.common[i];
for (var c in group) {
expect(group[c].grp).toBeDefined('Common component has no group defined, Type: ' + i + ', ID: ' + c);
}
}
});
});

View File

@@ -0,0 +1,122 @@
describe("Ship Factory", function() {
var Ship;
var Components;
beforeEach(module('shipyard'));
beforeEach(inject(['Ship', 'Components', function (_Ship_, _Components_) {
Ship = _Ship_;
Components = _Components_;
}]));
it("can build all ships", function() {
for (var s in DB.ships) {
var shipData = DB.ships[s];
var ship = new Ship(s, shipData.properties, shipData.slots);
for (p in shipData.properties) {
expect(ship[p]).toEqual(shipData.properties[p], s + ' property [' + p + '] does not match when built');
}
ship.buildWith(shipData.defaults);
expect(ship.totalCost).toEqual(shipData.retailCost, s + ' retail cost does not match default build cost');
expect(ship.priorityBands[0].retracted).toBeGreaterThan(0, s + ' cargo');
expect(ship.powerAvailable).toBeGreaterThan(0, s + ' powerAvailable');
expect(ship.unladenRange).toBeGreaterThan(0, s + ' unladenRange');
expect(ship.ladenRange).toBeGreaterThan(0, s + ' ladenRange');
expect(ship.fuelCapacity).toBeGreaterThan(0, s + ' fuelCapacity');
expect(ship.unladenTotalRange).toBeGreaterThan(0, s + ' unladenTotalRange');
expect(ship.ladenTotalRange).toBeGreaterThan(0, s + ' ladenTotalRange');
expect(ship.shieldStrength).toBeGreaterThan(0, s + ' shieldStrength');
expect(ship.armourTotal).toBeGreaterThan(0, s + ' armourTotal');
}
});
it("resets and rebuilds properly", function() {
var id = 'cobra_mk_iii';
var cobra = DB.ships[id];
var shipA = new Ship(id, cobra.properties, cobra.slots);
var shipB = new Ship(id, cobra.properties, cobra.slots);
var testShip = new Ship(id, cobra.properties, cobra.slots);
var buildA = cobra.defaults;
var buildB = {
common:['4A', '4A', '4A', '3D', '3A', '3A', '4C'],
hardpoints: ['0s', '0s', '2d', '2d', 0, '04'],
internal: ['45', '03', '2b', '2o', '27', '53']
};
shipA.buildWith(buildA); // Build A
shipB.buildWith(buildB);// Build B
testShip.buildWith(buildA);
for(var p in testShip) {
expect(testShip[p]).toEqual(shipA[p], p + ' does not match');
}
testShip.buildWith(buildB);
for(var p in testShip) {
expect(testShip[p]).toEqual(shipB[p], p + ' does not match');
}
testShip.buildWith(buildA);
for(var p in testShip) {
expect(testShip[p]).toEqual(shipA[p], p + ' does not match');
}
});
it("enforces a single shield generator", function() {
var id = 'anaconda';
var anacondaData = DB.ships[id];
var anaconda = new Ship(id, anacondaData.properties, anacondaData.slots);
anaconda.buildWith(anacondaData.defaults);
expect(anaconda.internal[2].c.grp).toEqual('sg', 'Anaconda default shield generator slot');
anaconda.use(anaconda.internal[1], '4j', Components.internal('4j')); // 6E Shield Generator
expect(anaconda.internal[2].c).toEqual(null, 'Anaconda default shield generator slot is empty');
expect(anaconda.internal[2].id).toEqual(null, 'Anaconda default shield generator slot id is null');
expect(anaconda.internal[1].id).toEqual('4j', 'Slot 1 should have SG 4j in it');
expect(anaconda.internal[1].c.grp).toEqual('sg','Slot 1 should have SG 4j in it');
});
it("enforces a single shield fuel scoop", function() {
var id = 'anaconda';
var anacondaData = DB.ships[id];
var anaconda = new Ship(id, anacondaData.properties, anacondaData.slots);
anaconda.buildWith(anacondaData.defaults);
anaconda.use(anaconda.internal[4], '32', Components.internal('32')); // 4A Fuel Scoop
expect(anaconda.internal[4].c.grp).toEqual('fs', 'Anaconda fuel scoop slot');
anaconda.use(anaconda.internal[3], '32', Components.internal('32'));
expect(anaconda.internal[4].c).toEqual(null, 'Anaconda original fuel scoop slot is empty');
expect(anaconda.internal[4].id).toEqual(null, 'Anaconda original fuel scoop slot id is null');
expect(anaconda.internal[3].id).toEqual('32', 'Slot 1 should have FS 32 in it');
expect(anaconda.internal[3].c.grp).toEqual('fs','Slot 1 should have FS 32 in it');
});
it("enforces a single refinery", function() {
var id = 'anaconda';
var anacondaData = DB.ships[id];
var anaconda = new Ship(id, anacondaData.properties, anacondaData.slots);
anaconda.buildWith(anacondaData.defaults);
anaconda.use(anaconda.internal[4], '23', Components.internal('23')); // 4E Refinery
expect(anaconda.internal[4].c.grp).toEqual('rf', 'Anaconda refinery slot');
anaconda.use(anaconda.internal[3], '23', Components.internal('23'));
expect(anaconda.internal[4].c).toEqual(null, 'Anaconda original refinery slot is empty');
expect(anaconda.internal[4].id).toEqual(null, 'Anaconda original refinery slot id is null');
expect(anaconda.internal[3].id).toEqual('23', 'Slot 1 should have RF 23 in it');
expect(anaconda.internal[3].c.grp).toEqual('rf','Slot 1 should have RF 23 in it');
});
});

View File

@@ -1,68 +0,0 @@
var data = require('../app/db.json');
var shipProperties = ["grp", "name", "manufacturer", "class", "cost", "speed", "boost", "agility", "shields", "armour", "fuelcost", "mass"];
describe("Database", function() {
it("has ships and components", function() {
expect(data.ships).toBeDefined()
expect(data.components.common).toBeDefined();
expect(data.components.hardpoints).toBeDefined();
expect(data.components.internal).toBeDefined();
expect(data.components.bulkheads).toBeDefined();
});
it("has unique IDs for every hardpoint", function() {
var ids = {};
var groups = data.components.hardpoints;
for (var g in groups) {
var group = groups[g];
for (var i = 0; i < group.length; i++) {
var id = group[i].id;
expect(ids[id]).toBeFalsy('ID already exists: ' + id);
expect(group[i].grp).toBeDefined('Hardpoint has no group defined, ID:' + id);
ids[id] = true;
}
}
});
it("has valid internal components", function() {
var ids = {};
var groups = data.components.internal;
for (var g in groups) {
var group = groups[g];
for (var i = 0; i < group.length; i++) {
var id = group[i].id;
expect(ids[id]).toBeFalsy('ID already exists: ' + id);
expect(group[i].grp).toBeDefined('Internal component has no group defined, ID:' + id);
ids[id] = true;
}
}
});
it("has data for every ship", function() {
for (var s in data.ships) {
for (var p = 0; p < shipProperties.length; p++) {
expect(data.ships[s].properties[shipProperties[p]]).toBeDefined(shipProperties[p] + ' is missing for ' + s);
}
expect(data.ships[s].slots.common.length).toEqual(7, s + ' is missing common slots');
expect(data.ships[s].defaults.common.length).toEqual(7, s + ' is missing common defaults');
expect(data.ships[s].slots.hardpoints.length).toEqual(data.ships[s].defaults.hardpoints.length, s + ' hardpoint slots and defaults dont match');
expect(data.ships[s].slots.internal.length).toEqual(data.ships[s].defaults.internal.length, s + ' hardpoint slots and defaults dont match');
expect(data.ships[s].retailCost).toBeGreaterThan(data.ships[s].properties.cost, s + ' has invalid retail cost');
expect(data.components.bulkheads[s]).toBeDefined(s + ' is missing bulkheads');
}
});
it("has components with a group defined", function() {
for (var i = 0; i < data.components.common.length; i++) {
var group = data.components.common[i];
for (var c in group) {
expect(group[c].grp).toBeDefined('Common component has no group defined, Type: ' + i + ', ID: ' + c);
}
}
});
});