React port nearing completetion. Adding tests

This commit is contained in:
Colin McLeod
2016-01-27 00:52:57 -08:00
parent 8227a4e361
commit b42a812a45
23 changed files with 362 additions and 298 deletions

View File

@@ -10,6 +10,8 @@ Coriolis was created using assets and imagery from Elite: Dangerous, with the pe
## Contributing ## Contributing
Chat to us on [HipChat](https://www.hipchat.com/gfYQiZcmy)!
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. 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 ### Feature Requests, Suggestions & Bugs

View File

@@ -16,7 +16,7 @@
"Miner": "25A5A5A4D4A5A5C0s0s24242l2l---04054a1q02022o27" "Miner": "25A5A5A4D4A5A5C0s0s24242l2l---04054a1q02022o27"
}, },
"cobra_mk_iii": { "cobra_mk_iii": {
"Example": "24A4A4A3D3A3A4C0s0s2d2d0m0445032b2o2753.AwRj4yKA.CwBhEYyrKhmMQ===", "Example": "24A4A4A3D3A3A4C0s0s2d2d0m0445032b2o2753.AwRj4yKA.CwBhEYyrKhmMQ==="
}, },
"imperial_clipper": { "imperial_clipper": {
"Cargo": "03A5D5A5D4D5D4C--0s0s----0605450302020101", "Cargo": "03A5D5A5D4D5D4C--0s0s----0605450302020101",

View File

@@ -1,196 +0,0 @@
xdescribe('Import Controller', function() {
beforeEach(module('app'));
var importController, $rootScope, $stateParams, scope;
var eventStub = {
preventDefault: function(){ },
stopPropagation: function(){ }
};
beforeEach(inject(function(_$rootScope_, $controller) {
$rootScope = _$rootScope_;
$rootScope.discounts = {
ship: 1,
components: 1
};
$stateParams = { };
scope = $rootScope.$new();
scope.$parent.dismiss = function() {};
var store = {};
spyOn(localStorage, 'getItem').and.callFake(function (key) {
return store[key];
});
spyOn(localStorage, 'setItem').and.callFake(function (key, value) {
return store[key] = value + '';
});
spyOn(localStorage, 'clear').and.callFake(function () {
store = {};
});
importController = $controller('ImportController', { $rootScope: $rootScope, $scope: scope, $stateParams: $stateParams });
}));
describe('Import Backup', function() {
it('imports a valid backup', function() {
var importData = __json__['fixtures/valid-backup'];
scope.importString = angular.toJson(importData);
scope.validateImport();
expect(scope.importValid).toBeTruthy();
expect(scope.errorMsg).toEqual(null);
scope.process();
expect(scope.processed).toBeTruthy();
scope.import();
expect(angular.fromJson(localStorage.getItem('builds'))).toEqual(importData.builds);
expect(angular.fromJson(localStorage.getItem('comparisons'))).toEqual(importData.comparisons);
expect(localStorage.getItem('insurance')).toEqual(importData.insurance);
expect(angular.fromJson(localStorage.getItem('discounts'))).toEqual(importData.discounts);
});
it('imports an old valid backup', function() {
var importData = __json__['fixtures/old-valid-export'];
scope.importString = angular.toJson(importData);
scope.validateImport();
expect(scope.importValid).toBeTruthy();
expect(scope.errorMsg).toEqual(null);
scope.process();
expect(scope.processed).toBeTruthy();
scope.import();
expect(angular.fromJson(localStorage.getItem('builds'))).toEqual(importData.builds);
});
it('catches an invalid backup', function() {
var importData = __json__['fixtures/valid-backup'];
scope.importString = 'null';
scope.validateImport();
expect(scope.importValid).toBeFalsy();
expect(scope.errorMsg).toEqual('Must be an object or array!');
scope.importString = '{ "builds": "Should not be a string" }';
scope.validateImport();
expect(scope.importValid).toBeFalsy();
expect(scope.errorMsg).toEqual('builds must be an object!');
scope.importString = angular.toJson(importData).replace('anaconda', 'invalid_ship');
scope.validateImport();
expect(scope.importValid).toBeFalsy();
expect(scope.errorMsg).toEqual('"invalid_ship" is not a valid Ship Id!');
scope.importString = angular.toJson(importData).replace('Dream', '');
scope.validateImport();
expect(scope.importValid).toBeFalsy();
expect(scope.errorMsg).toEqual('Imperial Clipper build "" must be a string at least 1 character long!');
invalidImportData = angular.copy(importData);
invalidImportData.builds.asp = null; // Remove Asp Miner build used in comparison
scope.importString = angular.toJson(invalidImportData);
scope.validateImport();
expect(scope.importValid).toBeFalsy();
expect(scope.errorMsg).toEqual('asp build "Miner" data is missing!');
});
});
describe('Import Detailed Build', function() {
it('imports a valid v1 build', function() {
var importData = __json__['fixtures/anaconda-test-detailed-export-v1'];
scope.importString = angular.toJson(importData);
scope.validateImport();
expect(scope.importValid).toBeTruthy();
expect(scope.errorMsg).toEqual(null);
scope.process();
expect(scope.processed).toBeTruthy();
scope.import();
expect(angular.fromJson(localStorage.getItem('builds'))).toEqual({
anaconda: { 'Test': '48A6A6A5A8A8A5C2c0o0o0o1m1m0q0q0404-0l0b0100034k5n052d04--0303326b.Iw18ZlA=.Aw18ZlA=' }
});
});
it('imports a valid v2 build', function() {
var importData = __json__['fixtures/anaconda-test-detailed-export-v2'];
scope.importString = angular.toJson(importData);
scope.validateImport();
expect(scope.importValid).toBeTruthy();
expect(scope.errorMsg).toEqual(null);
scope.process();
expect(scope.processed).toBeTruthy();
scope.import();
expect(angular.fromJson(localStorage.getItem('builds'))).toEqual({
anaconda: { 'Test': '48A6A6A5A8A8A5C2c0o0o0o1m1m0q0q0404-0l0b0100034k5n052d04--0303326b.AwRj4zNKqA==.CwBhCYzBGW9qCTSqs5xA' }
});
});
it('catches an invalid build', function() {
var importData = __json__['fixtures/anaconda-test-detailed-export-v2'];
scope.importString = angular.toJson(importData).replace('components', 'comps');
scope.validateImport();
expect(scope.importValid).toBeFalsy();
expect(scope.errorMsg).toEqual('Anaconda Build "Test": Invalid data');
});
});
describe('Import Detaild Builds Array', function() {
it('imports all builds', function() {
var importData = __json__['fixtures/valid-detailed-export'];
var expectedBuilds = __json__['fixtures/expected-builds'];
scope.importString = angular.toJson(importData);
scope.validateImport();
expect(scope.importValid).toBeTruthy();
expect(scope.errorMsg).toEqual(null);
scope.process();
expect(scope.processed).toBeTruthy();
scope.import();
var builds = angular.fromJson(localStorage.getItem('builds'));
for (var s in builds) {
for (var b in builds[s]) {
expect(builds[s][b]).toEqual(expectedBuilds[s][b]);
}
}
});
});
describe('Import E:D Shipyard Builds', function() {
it('imports a valid builds', function() {
var imports = __json__['fixtures/ed-shipyard-import-valid'];
for (var i = 0; i < imports.length; i++ ) {
scope.importString = imports[i].buildText;
scope.validateImport();
expect(scope.importValid).toBeTruthy();
expect(scope.errorMsg).toEqual(null, 'Build #' + i + ': ' + imports[i].buildName);
if (scope.importValid) { // No point in carrying out other assertions
scope.process();
expect(scope.processed).toBeTruthy();
scope.import();
var allBuilds = angular.fromJson(localStorage.getItem('builds'));
var shipBuilds = allBuilds ? allBuilds[imports[i].shipId] : null;
var build = shipBuilds ? shipBuilds[imports[i].buildName] : null;
expect(build).toEqual(imports[i].buildCode, 'Build #' + i + ': ' + imports[i].buildName);
}
}
});
it('catches invalid builds', function() {
var imports = __json__['fixtures/ed-shipyard-import-invalid'];
for (var i = 0; i < imports.length; i++ ) {
scope.importString = imports[i].buildText;
scope.validateImport();
expect(scope.importValid).toBeFalsy();
expect(scope.errorMsg).toEqual(imports[i].errorMsg);
}
});
});
});

218
__tests__/test-import.js Normal file
View File

@@ -0,0 +1,218 @@
jest.dontMock('../src/app/stores/Persist');
jest.dontMock('../src/app/components/TranslatedComponent');
jest.dontMock('../src/app/components/ModalImport');
import React from 'react';
import ReactDOM from 'react-dom';
import TU from 'react-testutils-additions';
import Utils from './testUtils';
import Persist from '../src/app/stores/Persist';
import { getLanguage } from '../src/app/i18n/Language';
describe('Import Controller', function() {
const Persist = require('../src/app/stores/Persist').default;
const ModalImport = require('../src/app/components/ModalImport').default;
const mockContext = {
language: getLanguage('en'),
sizeRatio: 1,
openMenu: jest.genMockFunction(),
closeMenu: jest.genMockFunction(),
showModal: jest.genMockFunction(),
hideModal: jest.genMockFunction(),
tooltip: jest.genMockFunction(),
termtip: jest.genMockFunction(),
onWindowResize: jest.genMockFunction()
};
let modal, render, ContextProvider = Utils.createContextProvider(mockContext);
/**
* Clear saved builds, and reset React DOM
*/
function reset() {
Persist.deleteAll();
render = TU.renderIntoDocument(<ContextProvider><ModalImport /></ContextProvider>);
modal = TU.findRenderedComponentWithType(render, ModalImport);
}
/**
* Simulate user import text entry / paste
* @param {string} text Import text / raw data
*/
function pasteText(text) {
let textarea = TU.findRenderedDOMComponentWithTag(render, 'textarea');
TU.Simulate.change(textarea, { target: { value: text } });
}
/**
* Simulate click on Proceed button
*/
function clickProceed() {
let proceedButton = TU.findRenderedDOMComponentWithId(render, 'proceed');
TU.Simulate.click(proceedButton);
}
/**
* Simulate click on Import button
*/
function clickImport() {
let importButton = TU.findRenderedDOMComponentWithId(render, 'import');
TU.Simulate.click(importButton);
}
describe('Import Backup', function() {
beforeEach(reset);
it('imports a valid backup', function() {
let importData = require('./fixtures/valid-backup');
let importString = JSON.stringify(importData);
expect(modal.state.importValid).toEqual(false);
expect(modal.state.errorMsg).toEqual(null);
pasteText(importString);
expect(modal.state.importValid).toBe(true);
expect(modal.state.errorMsg).toEqual(null);
expect(modal.state.builds).toEqual(importData.builds);
expect(modal.state.comparisons).toEqual(importData.comparisons);
expect(modal.state.discounts).toEqual(importData.discounts);
expect(modal.state.insurance).toBe(importData.insurance.toLowerCase());
clickProceed();
expect(modal.state.processed).toBe(true);
expect(modal.state.errorMsg).toEqual(null);
clickImport();
expect(Persist.getBuilds()).toEqual(importData.builds);
expect(Persist.getComparisons()).toEqual(importData.comparisons);
expect(Persist.getInsurance()).toEqual(importData.insurance.toLowerCase());
expect(Persist.getShipDiscount()).toEqual(importData.discounts[0]);
expect(Persist.getModuleDiscount()).toEqual(importData.discounts[1]);
});
it('imports an old valid backup', function() {
let importData = require('./fixtures/old-valid-export');
let importStr = JSON.stringify(importData);
pasteText(importStr);
expect(modal.state.builds).toEqual(importData.builds);
expect(modal.state.importValid).toBe(true);
expect(modal.state.errorMsg).toEqual(null);
clickProceed();
expect(modal.state.processed).toBeTruthy();
clickImport();
expect(Persist.getBuilds()).toEqual(importData.builds);
});
it('catches an invalid backup', function() {
let importData = require('./fixtures/valid-backup');
let invalidImportData = Object.assign({}, importData);
invalidImportData.builds.asp = null; // Remove Asp Miner build used in comparison
pasteText('"this is not valid"');
expect(modal.state.importValid).toBeFalsy();
expect(modal.state.errorMsg).toEqual('Must be an object or array!');
pasteText('{ "builds": "Should not be a string" }');
expect(modal.state.importValid).toBeFalsy();
expect(modal.state.errorMsg).toEqual('builds must be an object!');
pasteText(JSON.stringify(importData).replace('anaconda', 'invalid_ship'));
expect(modal.state.importValid).toBeFalsy();
expect(modal.state.errorMsg).toEqual('"invalid_ship" is not a valid Ship Id!');
pasteText(JSON.stringify(importData).replace('Dream', ''));
expect(modal.state.importValid).toBeFalsy();
expect(modal.state.errorMsg).toEqual('Imperial Clipper build "" must be a string at least 1 character long!');
pasteText(JSON.stringify(invalidImportData));
expect(modal.state.importValid).toBeFalsy();
expect(modal.state.errorMsg).toEqual('asp build "Miner" data is missing!');
});
});
describe('Import Detailed Build', function() {
beforeEach(reset);
it('imports a valid v3 build', function() {
let importData = require('./fixtures/anaconda-test-detailed-export-v3');
pasteText(JSON.stringify(importData));
expect(modal.state.importValid).toBeTruthy();
expect(modal.state.errorMsg).toEqual(null);
clickProceed();
expect(modal.state.processed).toBeTruthy();
clickImport();
expect(Persist.getBuilds()).toEqual({
anaconda: { 'Test': '48A6A6A5A8A8A5C2c0o0o0o1m1m0q0q0404-0l0b0100034k5n052d04--0303326b.AwRj4zNKqA==.CwBhCYzBGW9qCTSqs5xA' }
});
});
it('catches an invalid build', function() {
let importData = require('./fixtures/anaconda-test-detailed-export-v3');
pasteText(JSON.stringify(importData).replace('components', 'comps'));
expect(modal.state.importValid).toBeFalsy();
expect(modal.state.errorMsg).toEqual('Anaconda Build "Test": Invalid data');
});
});
describe('Import Detaild Builds Array', function() {
beforeEach(reset);
it('imports all builds', function() {
let importData = require('./fixtures/valid-detailed-export');
let expectedBuilds = require('./fixtures/expected-builds');
pasteText(JSON.stringify(importData));
expect(modal.state.importValid).toBeTruthy();
expect(modal.state.errorMsg).toEqual(null);
clickProceed();
expect(modal.state.processed).toBeTruthy();
clickImport();
let builds = Persist.getBuilds();
for (let s in builds) {
for (let b in builds[s]) {
expect(builds[s][b]).toEqual(expectedBuilds[s][b]);
}
}
});
});
describe('Import E:D Shipyard Builds', function() {
it('imports a valid builds', function() {
let imports = require('./fixtures/ed-shipyard-import-valid');
for (let i = 0; i < imports.length; i++ ) {
reset();
pasteText(imports[i].buildText);
expect(modal.state.importValid).toBeTruthy();
expect(modal.state.errorMsg).toEqual(null, 'Build #' + i + ': ' + imports[i].buildName);
clickProceed();
expect(modal.state.processed).toBeTruthy();
clickImport();
let allBuilds = Persist.getBuilds();
let shipBuilds = allBuilds ? allBuilds[imports[i].shipId] : null;
let build = shipBuilds ? shipBuilds[imports[i].buildName] : null;
expect(build).toEqual(imports[i].buildCode, 'Build #' + i + ': ' + imports[i].buildName);
}
});
it('catches invalid builds', function() {
let imports = require('./fixtures/ed-shipyard-import-invalid');
for (let i = 0; i < imports.length; i++ ) {
reset();
pasteText(imports[i].buildText);
expect(modal.state.importValid).toBeFalsy();
expect(modal.state.errorMsg).toEqual(imports[i].errorMsg);
}
});
});
});

24
__tests__/testUtils.js Normal file
View File

@@ -0,0 +1,24 @@
import React from 'react';
const TestUtils = {
createContextProvider: function(context) {
var _contextTypes = {};
Object.keys(context).forEach(function(key) {
_contextTypes[key] = React.PropTypes.any;
});
return React.createClass({
displayName: 'ContextProvider',
childContextTypes: _contextTypes,
getChildContext() { return context; },
render() {
return React.Children.only(this.props.children);
}
});
}
};
export default TestUtils;

View File

@@ -5,6 +5,7 @@ var config = require('./webpack.config.dev');
new WebpackDevServer(webpack(config), { new WebpackDevServer(webpack(config), {
publicPath: config.output.publicPath, publicPath: config.output.publicPath,
hot: true, hot: true,
headers: { "Access-Control-Allow-Origin": "*" },
historyApiFallback: { historyApiFallback: {
rewrites: [ rewrites: [
// For some reason connect-history-api-fallback does not allow '.' in the URL for history fallback... // For some reason connect-history-api-fallback does not allow '.' in the URL for history fallback...

View File

@@ -36,6 +36,7 @@
"<rootDir>/node_modules/react", "<rootDir>/node_modules/react",
"<rootDir>/node_modules/react-dom", "<rootDir>/node_modules/react-dom",
"<rootDir>/node_modules/react-addons-test-utils", "<rootDir>/node_modules/react-addons-test-utils",
"<rootDir>/node_modules/react-testutils-additions",
"<rootDir>/node_modules/fbjs", "<rootDir>/node_modules/fbjs",
"<rootDir>/node_modules/fbemitter", "<rootDir>/node_modules/fbemitter",
"<rootDir>/node_modules/classnames", "<rootDir>/node_modules/classnames",
@@ -44,7 +45,8 @@
"<rootDir>/node_modules/coriolis-data", "<rootDir>/node_modules/coriolis-data",
"<rootDir>/src/app/shipyard", "<rootDir>/src/app/shipyard",
"<rootDir>/src/app/i18n", "<rootDir>/src/app/i18n",
"<rootDir>/src/app/utils" "<rootDir>/src/app/utils",
"<rootDir>/__tests__"
] ]
}, },
"devDependencies": { "devDependencies": {
@@ -56,7 +58,7 @@
"babel-preset-es2015": "^6.3.13", "babel-preset-es2015": "^6.3.13",
"babel-preset-react": "^6.3.13", "babel-preset-react": "^6.3.13",
"babel-preset-stage-0": "^6.3.13", "babel-preset-stage-0": "^6.3.13",
"css-loader": "^0.23.0", "css-loader": "^0.23.0",
"eslint": "2.0.0-beta.1", "eslint": "2.0.0-beta.1",
"eslint-plugin-react": "^3.15.0", "eslint-plugin-react": "^3.15.0",
"expose-loader": "^0.7.1", "expose-loader": "^0.7.1",
@@ -69,6 +71,7 @@
"less": "^2.5.3", "less": "^2.5.3",
"less-loader": "^2.2.1", "less-loader": "^2.2.1",
"react-addons-test-utils": "^0.14.6", "react-addons-test-utils": "^0.14.6",
"react-testutils-additions": "^0.16.0",
"rimraf": "^2.4.3", "rimraf": "^2.4.3",
"style-loader": "^0.13.0", "style-loader": "^0.13.0",
"url-loader": "^0.5.6", "url-loader": "^0.5.6",

View File

@@ -238,13 +238,11 @@ export default class Coriolis extends React.Component {
* @return {React.Component} The main app * @return {React.Component} The main app
*/ */
render() { render() {
return ( return <div onClick={this._closeMenu}>
<div onClick={this._closeMenu}> <Header appCacheUpdate={this.state.appCacheUpdate} currentMenu={this.state.currentMenu} />
<Header appCacheUpdate={this.state.appCacheUpdate} currentMenu={this.state.currentMenu} /> { this.state.page ? <this.state.page currentMenu={this.state.currentMenu} /> : <NotFoundPage/> }
{ this.state.page ? <this.state.page currentMenu={this.state.currentMenu} /> : <NotFoundPage/> } { this.state.modal }
{ this.state.modal } { this.state.tooltip }
{ this.state.tooltip } </div>;
</div>
);
} }
} }

View File

@@ -5,6 +5,7 @@ import cn from 'classnames';
/** /**
* Returns true if the current window location equals the link * Returns true if the current window location equals the link
* @param {string} href URL/Href
* @return {boolean} If matches * @return {boolean} If matches
*/ */
function isActive(href) { function isActive(href) {

View File

@@ -613,6 +613,7 @@ export default class CostSection extends TranslatedComponent {
if (nextProps.ship != this.props.ship || nextProps.code != this.props.code) { if (nextProps.ship != this.props.ship || nextProps.code != this.props.code) {
this._updateAmmoCosts(nextProps.ship); this._updateAmmoCosts(nextProps.ship);
this._updateRetrofit(nextProps.ship, retrofitShip); this._updateRetrofit(nextProps.ship, retrofitShip);
this.setState({ total: nextProps.ship.totalCost });
this._sortCost(nextProps.ship); this._sortCost(nextProps.ship);
} }
} }

View File

@@ -260,7 +260,7 @@ export default class Header extends TranslatedComponent {
<br/> <br/>
<span className='cap ptr' onClick={this._toggleTooltips} > <span className='cap ptr' onClick={this._toggleTooltips} >
{translate('tooltips')} {translate('tooltips')}
<div className={cn({ disabled: !tips, 'primary-disabled': tips })} style={{ marginLeft: '0.5em', display: 'inline-block' }}>{(tips ? '' : '')}</div> <div className={cn({ disabled: !tips, 'primary-disabled': tips })} style={{ marginLeft: '0.5em', display: 'inline-block' }}>{(tips ? '' : '')}</div>
</span> </span>
<br/> <br/>
{translate('insurance')} {translate('insurance')}

View File

@@ -88,10 +88,13 @@ export default class LineChart extends TranslatedComponent {
y0 = func(x0), y0 = func(x0),
tips = this.tipContainer, tips = this.tipContainer,
yTotal = 0, yTotal = 0,
flip = (xPos / innerWidth > 0.65), flip = (xPos / innerWidth > 0.60),
tipWidth = 0, tipWidth = 0,
tipHeightPx = tips.selectAll('rect').node().getBoundingClientRect().height; tipHeightPx = tips.selectAll('rect').node().getBoundingClientRect().height;
xPos = xScale(x0); // Clamp xPos
tips.selectAll('text.label.y').text(function(d, i) { tips.selectAll('text.label.y').text(function(d, i) {
let yVal = series ? y0[series[i]] : y0; let yVal = series ? y0[series[i]] : y0;
yTotal += yVal; yTotal += yVal;
@@ -110,7 +113,7 @@ export default class LineChart extends TranslatedComponent {
tips.attr('transform', 'translate(' + xPos + ',' + tipY + ')'); tips.attr('transform', 'translate(' + xPos + ',' + tipY + ')');
tips.selectAll('text.label').attr('x', flip ? -12 : 12).style('text-anchor', flip ? 'end' : 'start'); tips.selectAll('text.label').attr('x', flip ? -12 : 12).style('text-anchor', flip ? 'end' : 'start');
tips.selectAll('text.label.x').text(formats.f2(x0)).append('tspan').attr('class', 'metric').text(' ' + xUnit); tips.selectAll('text.label.x').text(formats.f2(x0)).append('tspan').attr('class', 'metric').text(' ' + xUnit);
tips.selectAll('rect').attr('width', tipWidth + 4).attr('x', flip ? -tipWidth - 12 : 8).style('text-anchor', flip ? 'end' : 'start'); tips.selectAll('rect').attr('width', tipWidth + 4).attr('x', flip ? -tipWidth - 12 : 8).attr('y', 0).style('text-anchor', flip ? 'end' : 'start');
this.markersContainer.selectAll('circle').attr('cx', xPos).attr('cy', (d, i) => yScale(series ? y0[series[i]] : y0)); this.markersContainer.selectAll('circle').attr('cx', xPos).attr('cy', (d, i) => yScale(series ? y0[series[i]] : y0));
} }
@@ -136,9 +139,10 @@ export default class LineChart extends TranslatedComponent {
* @param {SyntheticEvent} e Event * @param {SyntheticEvent} e Event
*/ */
_showTip(e) { _showTip(e) {
this._moveTip(e); e.preventDefault();
this.tipContainer.style('display', null); this.tipContainer.style('display', null);
this.markersContainer.style('display', null); this.markersContainer.style('display', null);
this._moveTip(e);
} }
/** /**
@@ -146,13 +150,16 @@ export default class LineChart extends TranslatedComponent {
* @param {SyntheticEvent} e Event * @param {SyntheticEvent} e Event
*/ */
_moveTip(e) { _moveTip(e) {
this._tooltip(Math.round(e.clientX - e.target.getBoundingClientRect().left)); let clientX = e.touches ? e.touches[0].clientX : e.clientX;
this._tooltip(Math.round(clientX - e.currentTarget.getBoundingClientRect().left));
} }
/** /**
* Hide tooltip * Hide tooltip
* @param {SyntheticEvent} e Event
*/ */
_hideTip() { _hideTip(e) {
e.preventDefault();
this.tipContainer.style('display', 'none'); this.tipContainer.style('display', 'none');
this.markersContainer.style('display', 'none'); this.markersContainer.style('display', 'none');
} }
@@ -238,8 +245,8 @@ export default class LineChart extends TranslatedComponent {
<tspan className='metric'>{` (${yUnit})`}</tspan> <tspan className='metric'>{` (${yUnit})`}</tspan>
</text> </text>
</g> </g>
<g ref={(g) => this.tipContainer = d3.select(g)} className='tooltip' style={{ display: 'none' }}> <g ref={(g) => this.tipContainer = d3.select(g)} style={{ display: 'none' }}>
<rect className='tip' style={{ height: tipHeight + 'em' }}></rect> <rect className='tooltip' height={tipHeight + 'em'}></rect>
{detailElems} {detailElems}
</g> </g>
<g ref={(g) => this.markersContainer = d3.select(g)} style={{ display: 'none' }}> <g ref={(g) => this.markersContainer = d3.select(g)} style={{ display: 'none' }}>

View File

@@ -4,7 +4,7 @@ import TranslatedComponent from './TranslatedComponent';
import Persist from '../stores/Persist'; import Persist from '../stores/Persist';
import { Ships } from 'coriolis-data'; import { Ships } from 'coriolis-data';
import Ship from '../shipyard/Ship'; import Ship from '../shipyard/Ship';
import { ModuleNameToGroup } from '../shipyard/Constants'; import { ModuleNameToGroup, Insurance } from '../shipyard/Constants';
import * as ModuleUtils from '../shipyard/ModuleUtils'; import * as ModuleUtils from '../shipyard/ModuleUtils';
import { fromDetailedBuild } from '../shipyard/Serializer'; import { fromDetailedBuild } from '../shipyard/Serializer';
import { Download } from './SvgIcons'; import { Download } from './SvgIcons';
@@ -146,8 +146,14 @@ export default class ModalImport extends TranslatedComponent {
if (importData.discounts instanceof Array && importData.discounts.length == 2) { if (importData.discounts instanceof Array && importData.discounts.length == 2) {
this.setState({ discounts: importData.discounts }); this.setState({ discounts: importData.discounts });
} }
if (typeof importData.insurance == 'string' && importData.insurance.length > 3) { if (typeof importData.insurance == 'string') {
this.setState({ insurance: importData.insurance }); let insurance = importData.insurance.toLowerCase();
if (Insurance[insurance] !== undefined) {
this.setState({ insurance });
} else {
throw 'Invalid insurance type: ' + insurance;
}
} }
} }
@@ -220,7 +226,7 @@ export default class ModalImport extends TranslatedComponent {
if (!slot) { throw 'No hardpoint slot available for: "' + line + '"'; } if (!slot) { throw 'No hardpoint slot available for: "' + line + '"'; }
group = ModuleNameToGroup[name.trim()]; group = ModuleNameToGroup[name.toLowerCase()];
let hp = ModuleUtils.findHardpoint(group, cl, rating, group ? null : name, mount, missile); let hp = ModuleUtils.findHardpoint(group, cl, rating, group ? null : name, mount, missile);
@@ -238,7 +244,7 @@ export default class ModalImport extends TranslatedComponent {
if (ship.standard[standardIndex].maxClass < cl) { throw name + ' exceeds max class for the ' + ship.name; } if (ship.standard[standardIndex].maxClass < cl) { throw name + ' exceeds max class for the ' + ship.name; }
ship.use(ship.standard[standardIndex], cl + rating, ModuleUtils.standard(standardIndex, cl + rating), true); ship.use(ship.standard[standardIndex], ModuleUtils.standard(standardIndex, cl + rating), true);
} else { } else {
throw 'Unknown component: "' + line + '"'; throw 'Unknown component: "' + line + '"';
} }
@@ -249,13 +255,13 @@ export default class ModalImport extends TranslatedComponent {
if (!slot) { throw 'No internal slot available for: "' + line + '"'; } if (!slot) { throw 'No internal slot available for: "' + line + '"'; }
group = ModuleNameToGroup[name.trim()]; group = ModuleNameToGroup[name.toLowerCase()];
let intComp = ModuleUtils.findInternal(group, cl, rating, group ? null : name); let intComp = ModuleUtils.findInternal(group, cl, rating, group ? null : name);
if (!intComp) { throw 'Unknown component: "' + line + '"'; } if (!intComp) { throw 'Unknown component: "' + line + '"'; }
ship.use(slot, intComp.id, intComp); ship.use(slot, intComp);
} }
} }
@@ -272,7 +278,7 @@ export default class ModalImport extends TranslatedComponent {
*/ */
_validateImport(event) { _validateImport(event) {
let importData = null; let importData = null;
let importString = event.target.value; let importString = event.target.value.trim();
this.setState({ this.setState({
builds: null, builds: null,
comparisons: null, comparisons: null,
@@ -334,7 +340,7 @@ export default class ModalImport extends TranslatedComponent {
} }
if (this.state.comparisons) { if (this.state.comparisons) {
let comparisons = this.state.comparisons; comparisons = this.state.comparisons;
for (let name in comparisons) { for (let name in comparisons) {
comparisons[name].useName = name; comparisons[name].useName = name;
} }
@@ -371,8 +377,9 @@ export default class ModalImport extends TranslatedComponent {
} }
} }
if (this.state.discounts) { if (this.state.discounts && this.state.discounts.length == 2) {
Persist.setDiscount(this.state.discounts); Persist.setShipDiscount(this.state.discounts[0]);
Persist.setModuleDiscount(this.state.discounts[1]);
} }
if (this.state.insurance) { if (this.state.insurance) {
@@ -384,11 +391,11 @@ export default class ModalImport extends TranslatedComponent {
/** /**
* Capture build name changes * Capture build name changes
* @param {Object} build Build import object * @param {Object} item Build/Comparison import object
* @param {SyntheticEvent} e Event * @param {SyntheticEvent} e Event
*/ */
_changeBuildName(build, e) { _changeName(item, e) {
build.useName = e.target.value; item.useName = e.target.value;
this.forceUpdate(); this.forceUpdate();
} }
@@ -415,7 +422,7 @@ export default class ModalImport extends TranslatedComponent {
importStage = ( importStage = (
<div> <div>
<textarea className='cb json' onChange={this._validateImport} defaultValue={this.state.importString} placeholder={translate('PHRASE_IMPORT')} /> <textarea className='cb json' onChange={this._validateImport} defaultValue={this.state.importString} placeholder={translate('PHRASE_IMPORT')} />
<button className='l cap' onClick={this._process} disabled={!state.importValid} >{translate('proceed')}</button> <button id='proceed' className='l cap' onClick={this._process} disabled={!state.importValid} >{translate('proceed')}</button>
<div className='l warning' style={{ marginLeft:'3em' }}>{state.errorMsg}</div> <div className='l warning' style={{ marginLeft:'3em' }}>{state.errorMsg}</div>
</div> </div>
); );
@@ -426,14 +433,14 @@ export default class ModalImport extends TranslatedComponent {
for (let name in state.comparisons) { for (let name in state.comparisons) {
let comparison = state.comparisons[name]; let comparison = state.comparisons[name];
let hasComparison = Persist.hasComparison(name); let hasComparison = Persist.hasComparison(comparison.useName);
comparisonRows.push( comparisonRows.push(
<tr key={name} className='cb'> <tr key={name} className='cb'>
<td> <td>
<input type='text' value={comparison.useName}/> <input type='text' onChange={this._changeName.bind(this, comparison)} value={comparison.useName}/>
</td> </td>
<td style={{ textAlign:'center' }} className={ cn('cap', { warning: hasComparison, disabled: comparison.useName == '' }) }> <td style={{ textAlign:'center' }} className={ cn('cap', { warning: hasComparison, disabled: comparison.useName == '' }) }>
{translate(comparison.useName == '' ? 'skip' : (hasComparison ? 'overwrite' : 'create'))}> {translate(comparison.useName == '' ? 'skip' : (hasComparison ? 'overwrite' : 'create'))}
</td> </td>
</tr> </tr>
); );
@@ -467,7 +474,7 @@ export default class ModalImport extends TranslatedComponent {
buildRows.push( buildRows.push(
<tr key={shipId + buildName} className='cb'> <tr key={shipId + buildName} className='cb'>
<td>{Ships[shipId].properties.name}</td> <td>{Ships[shipId].properties.name}</td>
<td><input type='text' onChange={this._changeBuildName.bind(this, b)} value={b.useName}/></td> <td><input type='text' onChange={this._changeName.bind(this, b)} value={b.useName}/></td>
<td style={{ textAlign: 'center' }} className={cn('cap', { warning: hasBuild, disabled: b.useName == '' })}> <td style={{ textAlign: 'center' }} className={cn('cap', { warning: hasBuild, disabled: b.useName == '' })}>
{translate(b.useName == '' ? 'skip' : (hasBuild ? 'overwrite' : 'create'))} {translate(b.useName == '' ? 'skip' : (hasBuild ? 'overwrite' : 'create'))}
</td> </td>
@@ -491,7 +498,7 @@ export default class ModalImport extends TranslatedComponent {
</tbody> </tbody>
</table> </table>
{comparisonTable} {comparisonTable}
<button className='cl l' onClick={this._import}><Download/> {translate('import')}</button> <button id='import' className='cl l' onClick={this._import}><Download/> {translate('import')}</button>
{edit} {edit}
</div> </div>
); );

View File

@@ -130,9 +130,9 @@ export default class PowerManagement extends TranslatedComponent {
<td className='ptr le shorten cap' onClick={toggleEnabled}>{slotName(translate, slot)}</td> <td className='ptr le shorten cap' onClick={toggleEnabled}>{slotName(translate, slot)}</td>
<td className='ptr' onClick={toggleEnabled}><u>{translate(slot.type)}</u></td> <td className='ptr' onClick={toggleEnabled}><u>{translate(slot.type)}</u></td>
<td> <td>
<span className='flip ptr' onClick={this._priority.bind(this, slot, -1)}>&#9658;</span> <span className='flip ptr btn' onClick={this._priority.bind(this, slot, -1)}>&#9658;</span>
{' ' + (slot.priority + 1) + ' '} {' ' + (slot.priority + 1) + ' '}
<span className='ptr' onClick={this._priority.bind(this, slot, 1)}>&#9658;</span> <span className='ptr btn' onClick={this._priority.bind(this, slot, 1)}>&#9658;</span>
</td> </td>
<td className='ri ptr' style={{ width: '3.25em' }} onClick={toggleEnabled}>{pwr(m.power)}</td> <td className='ri ptr' style={{ width: '3.25em' }} onClick={toggleEnabled}>{pwr(m.power)}</td>
<td className='ri ptr' style={{ width: '3em' }} onClick={toggleEnabled}><u>{pct(m.power / ship.powerAvailable)}</u></td> <td className='ri ptr' style={{ width: '3em' }} onClick={toggleEnabled}><u>{pct(m.power / ship.powerAvailable)}</u></td>

View File

@@ -32,29 +32,35 @@ export default class Slider extends React.Component {
/** /**
* On Mouse down handler * On Mouse down handler
* @param {SyntheticEvent} e Event * @param {SyntheticEvent} event Event
*/ */
down(e) { down(event) {
let rect = e.currentTarget.getBoundingClientRect(); if (this.move) {
this.move = this._updatePercent.bind(this, rect.left, rect.width); this.up(event);
this.move(e); } else {
document.addEventListener('mousemove', this.move); let rect = event.currentTarget.getBoundingClientRect();
document.addEventListener('mouseup', this.up); this.move = this._updatePercent.bind(this, rect.left, rect.width);
this.move(event);
document.addEventListener('mousemove', this.move, true);
document.addEventListener('mouseup', this.up, true);
}
} }
/** /**
* On Mouse up handler * On Mouse up handler
* @param {Event} event DOM Event
*/ */
up() { up(event) {
document.removeEventListener('mousemove', this.move); document.removeEventListener('mousemove', this.move, true);
document.removeEventListener('mouseup', this.up); document.removeEventListener('mouseup', this.up, true);
this.move = null;
} }
/** /**
* Update the slider percentage * Update the slider percentage
* @param {number} left Slider left position * @param {number} left Slider left position
* @param {number} width Slider width * @param {number} width Slider width
* @param {Event} event Event * @param {Event} event DOM Event
*/ */
_updatePercent(left, width, event) { _updatePercent(left, width, event) {
this.props.onChange(Math.min(Math.max((event.clientX - left) / width, 0), 1)); this.props.onChange(Math.min(Math.max((event.clientX - left) / width, 0), 1));
@@ -88,7 +94,7 @@ export default class Slider extends React.Component {
<rect className='primary' style={{ opacity: 0.3 }} y='0.25em' rx='0.3em' ry='0.3em' width='100%' height='0.7em' /> <rect className='primary' style={{ opacity: 0.3 }} y='0.25em' rx='0.3em' ry='0.3em' width='100%' height='0.7em' />
<rect className='primary-disabled'y='0.45em' rx='0.15em' ry='0.15em' width={pctStr} height='0.3em' /> <rect className='primary-disabled'y='0.45em' rx='0.15em' ry='0.15em' width={pctStr} height='0.3em' />
<circle className='primary' r='0.6em' cy='0.6em' cx={pctStr} /> <circle className='primary' r='0.6em' cy='0.6em' cx={pctStr} />
<rect width='100%' height='100%' fillOpacity='0' onMouseDown={this.down}/> <rect width='100%' height='100%' fillOpacity='0' onMouseDown={this.down} onClick={this.click} />
{axisGroup} {axisGroup}
</svg>; </svg>;
} }

View File

@@ -328,10 +328,10 @@ export default class OutfittingPage extends Page {
</div> </div>
<div className='group half'> <div className='group half'>
<table style={{ width: '100%', lineHeight: '1em' }}> <table style={{ width: '100%', lineHeight: '1em', backgroundColor: 'transparent' }}>
<tbody > <tbody >
<tr> <tr>
<td style={{ verticalAlign: 'top', padding:0 }}><Fuel className='xl primary-disabled' /></td> <td style={{ verticalAlign: 'top', padding: 0, width: '2.5em' }}><Fuel className='xl primary-disabled' /></td>
<td><Slider axis={true} onChange={this._fuelChange} axisUnit={translate('T')} percent={fuelLevel} max={fuelCapacity} /></td> <td><Slider axis={true} onChange={this._fuelChange} axisUnit={translate('T')} percent={fuelLevel} max={fuelCapacity} /></td>
<td className='primary' style={{ width: '10em', verticalAlign: 'top', fontSize: '0.9em' }}>{formats.f2(fuelLevel * fuelCapacity)}{units.T} {formats.pct1(fuelLevel)}</td> <td className='primary' style={{ width: '10em', verticalAlign: 'top', fontSize: '0.9em' }}>{formats.f2(fuelLevel * fuelCapacity)}{units.T} {formats.pct1(fuelLevel)}</td>
</tr> </tr>

View File

@@ -415,12 +415,10 @@ export default class Ship {
this.updatePower() this.updatePower()
.updateJumpStats() .updateJumpStats()
.updateShieldStrength() .updateShieldStrength()
.updateTopSpeed() .updateTopSpeed();
.updatePowerPrioritesString()
.updatePowerEnabledString();
} }
return this; return this.updatePowerPrioritesString().updatePowerEnabledString();
} }
/** /**
@@ -570,12 +568,12 @@ export default class Ship {
if (newPriority >= 0 && newPriority < this.priorityBands.length) { if (newPriority >= 0 && newPriority < this.priorityBands.length) {
let oldPriority = slot.priority; let oldPriority = slot.priority;
slot.priority = newPriority; slot.priority = newPriority;
this.updatePowerPrioritesString();
if (slot.enabled) { // Only update power if the slot is enabled if (slot.enabled) { // Only update power if the slot is enabled
let usage = powerUsageType(slot, slot.m); let usage = powerUsageType(slot, slot.m);
this.priorityBands[oldPriority][usage] -= slot.m.power; this.priorityBands[oldPriority][usage] -= slot.m.power;
this.priorityBands[newPriority][usage] += slot.m.power; this.priorityBands[newPriority][usage] += slot.m.power;
this.updatePowerPrioritesString();
this.updatePower(); this.updatePower();
} }
return true; return true;

View File

@@ -14,8 +14,8 @@ let LS;
// Safe check to determine if localStorage is enabled // Safe check to determine if localStorage is enabled
try { try {
localStorage.setItem('s', 1); localStorage.setItem('test_string', 1);
localStorage.removeItem('s'); localStorage.removeItem('test_string');
LS = localStorage; LS = localStorage;
} catch(e) { } catch(e) {
LS = null; LS = null;
@@ -38,7 +38,7 @@ function _put(key, value) {
* @return {string} The stored string * @return {string} The stored string
*/ */
function _getString(key) { function _getString(key) {
return LS.getItem(key); return LS ? LS.getItem(key) : null;
} }
/** /**
@@ -80,7 +80,7 @@ class Persist extends EventEmitter {
this.comparisons = comparisonJson ? comparisonJson : {}; this.comparisons = comparisonJson ? comparisonJson : {};
this.buildCount = Object.keys(this.builds).length; this.buildCount = Object.keys(this.builds).length;
this.langCode = _getString(LS_KEY_LANG) || 'en'; this.langCode = _getString(LS_KEY_LANG) || 'en';
this.insurance = _getString(LS_KEY_INSURANCE); this.insurance = _getString(LS_KEY_INSURANCE) || 'standard';
this.discounts = _get(LS_KEY_DISCOUNTS) || [1, 1]; this.discounts = _get(LS_KEY_DISCOUNTS) || [1, 1];
this.costTab = _getString(LS_KEY_COST_TAB); this.costTab = _getString(LS_KEY_COST_TAB);
this.state = _get(LS_KEY_STATE); this.state = _get(LS_KEY_STATE);
@@ -129,16 +129,13 @@ class Persist extends EventEmitter {
* @param {string} code The serialized code * @param {string} code The serialized code
*/ */
saveBuild(shipId, name, code) { saveBuild(shipId, name, code) {
if (LS) { if (!this.builds[shipId]) {
if (!this.builds[shipId]) { this.builds[shipId] = {};
this.builds[shipId] = {};
}
let newBuild = !this.builds[shipId][name];
this.builds[shipId][name] = code;
_put(LS_KEY_BUILDS, this.builds);
this.emit('buildSaved', shipId, name, code);
} }
}; this.builds[shipId][name] = code;
_put(LS_KEY_BUILDS, this.builds);
this.emit('buildSaved', shipId, name, code);
}
/** /**
* Get the serialized code/string for a build. Returns null if a * Get the serialized code/string for a build. Returns null if a
@@ -153,7 +150,7 @@ class Persist extends EventEmitter {
return this.builds[shipId][name]; return this.builds[shipId][name];
} }
return null; return null;
}; }
/** /**
* Get all builds (object) or builds for a specific ship (array) * Get all builds (object) or builds for a specific ship (array)
@@ -225,7 +222,7 @@ class Persist extends EventEmitter {
_put(LS_KEY_COMPARISONS, this.comparisons); _put(LS_KEY_COMPARISONS, this.comparisons);
this.emit('buildDeleted', shipId, name); this.emit('buildDeleted', shipId, name);
} }
}; }
/** /**
* Persist a comparison in localstorage. * Persist a comparison in localstorage.
@@ -244,7 +241,7 @@ class Persist extends EventEmitter {
}; };
_put(LS_KEY_COMPARISONS, this.comparisons); _put(LS_KEY_COMPARISONS, this.comparisons);
this.emit('comparisons', this.comparisons); this.emit('comparisons', this.comparisons);
}; }
/** /**
* [getComparison description] * [getComparison description]
@@ -256,7 +253,7 @@ class Persist extends EventEmitter {
return this.comparisons[name]; return this.comparisons[name];
} }
return null; return null;
}; }
/** /**
* Get all saved comparisons * Get all saved comparisons
@@ -293,7 +290,7 @@ class Persist extends EventEmitter {
_put(LS_KEY_COMPARISONS, this.comparisons); _put(LS_KEY_COMPARISONS, this.comparisons);
this.emit('comparisons', this.comparisons); this.emit('comparisons', this.comparisons);
} }
}; }
/** /**
* Delete all builds and comparisons from localStorage * Delete all builds and comparisons from localStorage
@@ -304,7 +301,7 @@ class Persist extends EventEmitter {
_delete(LS_KEY_BUILDS); _delete(LS_KEY_BUILDS);
_delete(LS_KEY_COMPARISONS); _delete(LS_KEY_COMPARISONS);
this.emit('deletedAll'); this.emit('deletedAll');
}; }
/** /**
* Get all saved data and settings * Get all saved data and settings
@@ -317,25 +314,25 @@ class Persist extends EventEmitter {
data[LS_KEY_INSURANCE] = this.getInsurance(); data[LS_KEY_INSURANCE] = this.getInsurance();
data[LS_KEY_DISCOUNTS] = this.discounts; data[LS_KEY_DISCOUNTS] = this.discounts;
return data; return data;
}; }
/** /**
* Get the saved insurance type * Get the saved insurance type
* @return {string} The name of the saved insurance type of null * @return {string} The name of the saved insurance type of null
*/ */
getInsurance() { getInsurance() {
return this.insurance; return this.insurance.toLowerCase();
}; }
/** /**
* Persist selected insurance type * Persist selected insurance type
* @param {string} insurance Insurance type name * @param {string} insurance Insurance type name
*/ */
setInsurance(insurance) { setInsurance(insurance) {
this.insurance = insurance; this.insurance = insurance.toLowerCase();
_put(LS_KEY_INSURANCE, insurance); _put(LS_KEY_INSURANCE, insurance);
this.emit('insurance', insurance); this.emit('insurance', insurance);
}; }
/** /**
* Persist selected ship discount * Persist selected ship discount
@@ -345,7 +342,7 @@ class Persist extends EventEmitter {
this.discounts[0] = shipDiscount; this.discounts[0] = shipDiscount;
_put(LS_KEY_DISCOUNTS, this.discounts); _put(LS_KEY_DISCOUNTS, this.discounts);
this.emit('discounts', this.discounts); this.emit('discounts', this.discounts);
}; }
/** /**
* Get the saved ship discount * Get the saved ship discount
@@ -363,7 +360,7 @@ class Persist extends EventEmitter {
this.discounts[1] = moduleDiscount; this.discounts[1] = moduleDiscount;
_put(LS_KEY_DISCOUNTS, this.discounts); _put(LS_KEY_DISCOUNTS, this.discounts);
this.emit('discounts', this.discounts); this.emit('discounts', this.discounts);
}; }
/** /**
* Get the saved ship discount * Get the saved ship discount
@@ -371,7 +368,7 @@ class Persist extends EventEmitter {
*/ */
getModuleDiscount() { getModuleDiscount() {
return this.discounts[1]; return this.discounts[1];
}; }
/** /**
* Persist selected cost tab * Persist selected cost tab
@@ -380,7 +377,7 @@ class Persist extends EventEmitter {
setCostTab(tabName) { setCostTab(tabName) {
this.costTab = tabName; this.costTab = tabName;
_put(LS_KEY_COST_TAB, tabName); _put(LS_KEY_COST_TAB, tabName);
}; }
/** /**
* Get the saved discount * Get the saved discount
@@ -388,7 +385,7 @@ class Persist extends EventEmitter {
*/ */
getCostTab() { getCostTab() {
return this.costTab; return this.costTab;
}; }
/** /**
* Retrieve the last router state from local storage * Retrieve the last router state from local storage
@@ -396,7 +393,7 @@ class Persist extends EventEmitter {
*/ */
getState() { getState() {
return this.state; return this.state;
}; }
/** /**
* Save the current router state to localstorage * Save the current router state to localstorage
@@ -405,7 +402,7 @@ class Persist extends EventEmitter {
setState(state) { setState(state) {
this.state = state; this.state = state;
_put(LS_KEY_STATE, state); _put(LS_KEY_STATE, state);
}; }
/** /**
* Retrieve the last router state from local storage * Retrieve the last router state from local storage
@@ -413,7 +410,7 @@ class Persist extends EventEmitter {
*/ */
getSizeRatio() { getSizeRatio() {
return this.sizeRatio; return this.sizeRatio;
}; }
/** /**
* Save the current size ratio to localstorage * Save the current size ratio to localstorage
@@ -425,7 +422,7 @@ class Persist extends EventEmitter {
_put(LS_KEY_SIZE_RATIO, sizeRatio); _put(LS_KEY_SIZE_RATIO, sizeRatio);
this.emit('sizeRatio', sizeRatio); this.emit('sizeRatio', sizeRatio);
} }
}; }
/** /**
* Check if localStorage is enabled/active * Check if localStorage is enabled/active

View File

@@ -2,6 +2,7 @@
<html {%= o.htmlWebpackPlugin.options.appCache ? 'manifest=/' + o.htmlWebpackPlugin.options.appCache : '' %} > <html {%= o.htmlWebpackPlugin.options.appCache ? 'manifest=/' + o.htmlWebpackPlugin.options.appCache : '' %} >
<head> <head>
<title>Coriolis</title> <title>Coriolis</title>
<meta charset="UTF-8">
<link rel="stylesheet" href="{%= o.htmlWebpackPlugin.files.css[0] %}"> <link rel="stylesheet" href="{%= o.htmlWebpackPlugin.files.css[0] %}">
<!-- Standard headers --> <!-- Standard headers -->
<meta name="description" content="A ship builder, outfitting and comparison tool for Elite Dangerous"> <meta name="description" content="A ship builder, outfitting and comparison tool for Elite Dangerous">

View File

@@ -58,7 +58,7 @@ svg {
fill: @fg; fill: @fg;
} }
.tip { .tooltip {
fill: @bgBlack; fill: @bgBlack;
stroke: @secondary; stroke: @secondary;
stroke-width: 1px; stroke-width: 1px;

View File

@@ -17,7 +17,7 @@
transform:translate(-50%,-50%); transform:translate(-50%,-50%);
-webkit-transform:translate(-50%,-50%); -webkit-transform:translate(-50%,-50%);
width: 800px; width: 800px;
max-height: 100%; max-height: 90%;
padding: 2em; padding: 2em;
background-color: @bgBlack; background-color: @bgBlack;
box-sizing: border-box; box-sizing: border-box;

View File

@@ -235,13 +235,9 @@
font-size: 0.8em; font-size: 0.8em;
} }
table tbody tr td { span.btn {
&:nth-child(4) { vertical-align: middle;
span { font-size: 1.6em;
vertical-align: middle;
font-size: 1.6em;
}
}
} }
}); });