2.0.1 Beta

This commit is contained in:
Colin McLeod
2016-02-13 22:48:48 -08:00
parent d783a38588
commit 9175fb60af
67 changed files with 1042 additions and 1112 deletions

View File

@@ -3,7 +3,7 @@ notifications:
email: false
sudo: false
node_js:
- "0.12"
- "4.2.6"
cache:
directories:
- node_modules
@@ -13,4 +13,3 @@ before_script:
script:
- npm run lint
- npm test
- npm run build

View File

@@ -1,5 +1,4 @@
[![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)
![Latest Release](https://img.shields.io/github/release/cmmcleod/coriolis.svg) [![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) [![Chat to us on HipChat](https://img.shields.io/badge/HipChat-Coriolis-white.svg?style=social)](https://www.hipchat.com/gfYQiZcmy)
## About
@@ -10,12 +9,12 @@ Coriolis was created using assets and imagery from Elite: Dangerous, with the pe
## 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.
### Feature Requests, Suggestions & Bugs
Chat to us on [HipChat](https://www.hipchat.com/gfYQiZcmy)!
All such requests are managed and tracked through [issues](https://github.com/cmmcleod/coriolis/issues). An overview of these can be found [here](https://waffle.io/cmmcleod/coriolis).
## Development
@@ -23,7 +22,7 @@ All such requests are managed and tracked through [issues](https://github.com/cm
See the [Developer's Guide](https://github.com/cmmcleod/coriolis/wiki/Developer's-Guide) in the wiki.
### Ship and Component Database
### Ship and Module Database
See the [Data wiki](https://github.com/cmmcleod/coriolis-data/wiki) for details on structure, etc.

View File

@@ -1,11 +1,11 @@
{
"$schema": "http://cdn.coriolis.io/schemas/ship-loadout/3.json#",
"name": "Test",
"name": "Test My Ship",
"ship": "Anaconda",
"references": [
{
"name": "Coriolis.io",
"url": "http://localhost:3300/outfit/anaconda/48A6A6A5A8A8A5C2c0o0o0o1m1m0q0q0404-0l0b0100034k5n052d04--0303326b.AwRj4zNKqA==.CwBhCYzBGW9qCTSqs5xA?bn=Test?bn=Test",
"url": "http://localhost:3300/outfit/anaconda/48A6A6A5A8A8A5C2c0o0o0o1m1m0q0q0404-0l0b0100034k5n052d04--0303326b.AwRj4zNKqA==.CwBhCYzBGW9qCTSqs5xA?bn=Test%20My%20Ship",
"code": "48A6A6A5A8A8A5C2c0o0o0o1m1m0q0q0404-0l0b0100034k5n052d04--0303326b.AwRj4zNKqA==.CwBhCYzBGW9qCTSqs5xA",
"shipId": "anaconda"
}
@@ -281,8 +281,8 @@
"unladenRange": 18.49,
"fullTankRange": 18.12,
"ladenRange": 16.39,
"unladenTotalRange": 73.21,
"ladenTotalRange": 66.15,
"unladenFastestRange": 73.21,
"ladenFastestRange": 66.15,
"maxJumpCount": 4,
"shieldStrength": 833
}

View File

@@ -117,8 +117,8 @@
"unladenRange": 32.48,
"fullTankRange": 30.27,
"ladenRange": 19.61,
"unladenTotalRange": 176.71,
"ladenTotalRange": 112.92,
"unladenFastestRange": 176.71,
"ladenFastestRange": 112.92,
"maxJumpCount": 6,
"shieldStrength": 86.49
}
@@ -251,8 +251,8 @@
"unladenRange": 30.24,
"fullTankRange": 28.32,
"ladenRange": 19.8,
"unladenTotalRange": 164.89,
"ladenTotalRange": 114.03,
"unladenFastestRange": 164.89,
"ladenFastestRange": 114.03,
"maxJumpCount": 6,
"shieldStrength": 149.2
}
@@ -381,8 +381,8 @@
"unladenRange": 31.71,
"fullTankRange": 29.61,
"ladenRange": 23.58,
"unladenTotalRange": 172.68,
"ladenTotalRange": 136.46,
"unladenFastestRange": 172.68,
"ladenFastestRange": 136.46,
"maxJumpCount": 6,
"shieldStrength": 86.49
}
@@ -513,8 +513,8 @@
"unladenRange": 26.41,
"fullTankRange": 24.97,
"ladenRange": 17.36,
"unladenTotalRange": 172.04,
"ladenTotalRange": 118.55,
"unladenFastestRange": 172.04,
"ladenFastestRange": 118.55,
"maxJumpCount": 7,
"shieldStrength": 89.07
}
@@ -655,8 +655,8 @@
"unladenRange": 25.77,
"fullTankRange": 24.39,
"ladenRange": 17.98,
"unladenTotalRange": 167.93,
"ladenTotalRange": 122.84,
"unladenFastestRange": 167.93,
"ladenFastestRange": 122.84,
"maxJumpCount": 7,
"shieldStrength": 125.07
}
@@ -793,8 +793,8 @@
"unladenRange": 19.27,
"fullTankRange": 18.95,
"ladenRange": 15.43,
"unladenTotalRange": 67.34,
"ladenTotalRange": 54.75,
"unladenFastestRange": 67.34,
"ladenFastestRange": 54.75,
"maxJumpCount": 4,
"shieldStrength": 111.07
}
@@ -956,8 +956,8 @@
"unladenRange": 27.1,
"fullTankRange": 25.58,
"ladenRange": 21.94,
"unladenTotalRange": 176.39,
"ladenTotalRange": 150.58,
"unladenFastestRange": 176.39,
"ladenFastestRange": 150.58,
"maxJumpCount": 7,
"shieldStrength": 253.58
}
@@ -1098,8 +1098,8 @@
"unladenRange": 26.01,
"fullTankRange": 25.42,
"ladenRange": 17.19,
"unladenTotalRange": 90.67,
"ladenTotalRange": 61.04,
"unladenFastestRange": 90.67,
"ladenFastestRange": 61.04,
"maxJumpCount": 4,
"shieldStrength": 191.82
}
@@ -1262,8 +1262,8 @@
"unladenRange": 15.19,
"fullTankRange": 14.99,
"ladenRange": 14.99,
"unladenTotalRange": 53.15,
"ladenTotalRange": 53.15,
"unladenFastestRange": 53.15,
"ladenFastestRange": 53.15,
"maxJumpCount": 4,
"shieldStrength": 489.6
}
@@ -1362,8 +1362,8 @@
"unladenRange": 24.25,
"fullTankRange": 23.73,
"ladenRange": 23.73,
"unladenTotalRange": 84.56,
"ladenTotalRange": 84.56,
"unladenFastestRange": 84.56,
"ladenFastestRange": 84.56,
"maxJumpCount": 4,
"shieldStrength": 0
}
@@ -1501,8 +1501,8 @@
"unladenRange": 19.51,
"fullTankRange": 18.58,
"ladenRange": 13.09,
"unladenTotalRange": 152.32,
"ladenTotalRange": 106.49,
"unladenFastestRange": 152.32,
"ladenFastestRange": 106.49,
"maxJumpCount": 8,
"shieldStrength": 170.57
}
@@ -1639,8 +1639,8 @@
"unladenRange": 28.25,
"fullTankRange": 26.6,
"ladenRange": 16.67,
"unladenTotalRange": 183.67,
"ladenTotalRange": 113.69,
"unladenFastestRange": 183.67,
"ladenFastestRange": 113.69,
"maxJumpCount": 7,
"shieldStrength": 214.26
}
@@ -1810,8 +1810,8 @@
"unladenRange": 20.32,
"fullTankRange": 19.46,
"ladenRange": 14.65,
"unladenTotalRange": 133.17,
"ladenTotalRange": 99.65,
"unladenFastestRange": 133.17,
"ladenFastestRange": 99.65,
"maxJumpCount": 7,
"shieldStrength": 486.35
}
@@ -1989,8 +1989,8 @@
"unladenRange": 14.32,
"fullTankRange": 13.89,
"ladenRange": 13.46,
"unladenTotalRange": 94.42,
"ladenTotalRange": 91.49,
"unladenFastestRange": 94.42,
"ladenFastestRange": 91.49,
"maxJumpCount": 7,
"shieldStrength": 645.57
}
@@ -2219,8 +2219,8 @@
"unladenRange": 16.99,
"fullTankRange": 16.68,
"ladenRange": 15.91,
"unladenTotalRange": 67.35,
"ladenTotalRange": 64.2,
"unladenFastestRange": 67.35,
"ladenFastestRange": 64.2,
"maxJumpCount": 4,
"shieldStrength": 952
}
@@ -2374,8 +2374,8 @@
"unladenRange": 39.26,
"fullTankRange": 37.65,
"ladenRange": 21.21,
"unladenTotalRange": 153.79,
"ladenTotalRange": 85.82,
"unladenFastestRange": 153.79,
"ladenFastestRange": 85.82,
"maxJumpCount": 4,
"shieldStrength": 372.98
}
@@ -2530,8 +2530,8 @@
"unladenRange": 38.44,
"fullTankRange": 36.89,
"ladenRange": 21.04,
"unladenTotalRange": 150.62,
"ladenTotalRange": 85.16,
"unladenFastestRange": 150.62,
"ladenFastestRange": 85.16,
"maxJumpCount": 4,
"shieldStrength": 372.98
}
@@ -2698,8 +2698,8 @@
"unladenRange": 38.04,
"fullTankRange": 30.11,
"ladenRange": 23.03,
"unladenTotalRange": 675.7,
"ladenTotalRange": 501.97,
"unladenFastestRange": 675.7,
"ladenFastestRange": 501.97,
"maxJumpCount": 20,
"shieldStrength": 372.98
}
@@ -2916,8 +2916,8 @@
"unladenRange": 18.49,
"fullTankRange": 18.12,
"ladenRange": 16.39,
"unladenTotalRange": 73.21,
"ladenTotalRange": 66.15,
"unladenFastestRange": 73.21,
"ladenFastestRange": 66.15,
"maxJumpCount": 4,
"shieldStrength": 833
}
@@ -3044,8 +3044,8 @@
"unladenRange": 35.99,
"fullTankRange": 33.36,
"ladenRange": 33.36,
"unladenTotalRange": 232.28,
"ladenTotalRange": 232.28,
"unladenFastestRange": 232.28,
"ladenFastestRange": 232.28,
"maxJumpCount": 7,
"shieldStrength": 92.25
}
@@ -3181,8 +3181,8 @@
"unladenRange": 15.06,
"fullTankRange": 14.86,
"ladenRange": 14.86,
"unladenTotalRange": 42.5,
"ladenTotalRange": 42.5,
"unladenFastestRange": 42.5,
"ladenFastestRange": 42.5,
"maxJumpCount": 3,
"shieldStrength": 548.74
}
@@ -3335,8 +3335,8 @@
"unladenRange": 12.51,
"fullTankRange": 12.38,
"ladenRange": 12.38,
"unladenTotalRange": 35.35,
"ladenTotalRange": 35.35,
"unladenFastestRange": 35.35,
"ladenFastestRange": 35.35,
"maxJumpCount": 3,
"shieldStrength": 760.16
}
@@ -3453,8 +3453,8 @@
"unladenRange": 17.12,
"fullTankRange": 16.71,
"ladenRange": 16.71,
"unladenTotalRange": 42.4,
"ladenTotalRange": 42.4,
"unladenFastestRange": 42.4,
"ladenFastestRange": 42.4,
"maxJumpCount": 3,
"shieldStrength": 102
}
@@ -3611,8 +3611,8 @@
"unladenRange": 8.43,
"fullTankRange": 8.09,
"ladenRange": 7.25,
"unladenTotalRange": 81.5,
"ladenTotalRange": 72.9,
"unladenFastestRange": 81.5,
"ladenFastestRange": 72.9,
"maxJumpCount": 10,
"shieldStrength": 299.48
}

View File

@@ -10,6 +10,7 @@ import { getLanguage } from '../src/app/i18n/Language';
describe('Import Modal', function() {
let MockRouter = require('../src/app/Router');
const Persist = require('../src/app/stores/Persist').default;
const ModalImport = require('../src/app/components/ModalImport').default;
const mockContext = {
@@ -24,12 +25,15 @@ describe('Import Modal', function() {
onWindowResize: jest.genMockFunction()
};
MockRouter.go = jest.genMockFunction();
let modal, render, ContextProvider = Utils.createContextProvider(mockContext);
/**
* Clear saved builds, and reset React DOM
*/
function reset() {
MockRouter.go.mockClear();
Persist.deleteAll();
render = TU.renderIntoDocument(<ContextProvider><ModalImport /></ContextProvider>);
modal = TU.findRenderedComponentWithType(render, ModalImport);
@@ -135,16 +139,9 @@ describe('Import Modal', function() {
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' }
});
expect(MockRouter.go.mock.calls.length).toBe(1);
expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/anaconda/48A6A6A5A8A8A5C2c0o0o0o1m1m0q0q0404-0l0b0100034k5n052d04--0303326b.AwRj4zNKqA==.CwBhCYzBGW9qCTSqs5xA?bn=Test%20My%20Ship');
});
it('catches an invalid build', function() {
@@ -152,7 +149,7 @@ describe('Import Modal', function() {
pasteText(JSON.stringify(importData).replace('components', 'comps'));
expect(modal.state.importValid).toBeFalsy();
expect(modal.state.errorMsg).toEqual('Anaconda Build "Test": Invalid data');
expect(modal.state.errorMsg).toEqual('Anaconda Build "Test My Ship": Invalid data');
});
});
@@ -188,16 +185,13 @@ describe('Import Modal', function() {
for (let i = 0; i < imports.length; i++ ) {
reset();
pasteText(imports[i].buildText);
let fixture = imports[i];
pasteText(fixture.buildText);
expect(modal.state.importValid).toBeTruthy();
expect(modal.state.errorMsg).toEqual(null, 'Build #' + i + ': ' + imports[i].buildName);
expect(modal.state.errorMsg).toEqual(null);
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);
expect(MockRouter.go.mock.calls.length).toBe(1);
expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/' + fixture.shipId + '/' + fixture.buildCode + '?bn=' + encodeURIComponent(fixture.buildName));
}
});

View File

@@ -4,32 +4,103 @@ import React from 'react';
import ReactDOM from 'react-dom';
import TU from 'react-testutils-additions';
xdescribe('Persist', function() {
let origAddEventListener = window.addEventListener;
let storageListener;
let ls = {};
const Persist = require('../src/app/stores/Persist').default;
// Implment mock localStorage
let localStorage = {
getItem: function(key) {
return ls[key];
},
setItem: function(key, value) {
ls[key] = value;
},
removeItem: function(key) {
delete ls[key];
},
clear: function() {
ls = {};
}
}
window.addEventListener = function(eventName, listener) {
if(eventName == 'storage') {
storageListener = listener; // Keep track of latest storage listener
} else {
origAddEventListener.apply(arguments);
}
}
describe('Persist', function() {
const Persist = require('../src/app/stores/Persist').Persist;
describe('Builds', function() {
it("loads from localStorage correctly", function() {
});
it("can save a build", function() {
});
it("can delete a build", function() {
});
it("works without localStorage", function() {
});
});
describe('Comparisons', function() {
it("loads from localStorage correctly", function() {
});
describe('Settings', function() {
it("works without localStorage", function() {
it("has defaults", function() {
expect(false).toBeTruthy('Implement');
});
});
describe('Multi tab/window', function() {
it.only("syncs builds", function() {
window.localStorage = localStorage;
let p = new Persist();
let newBuilds = {};
storageListener({ key: 'builds', newValue: JSON.stringify(newBuilds) });
});
});
describe('General and Settings', function() {
it.only("has defaults", function() {
let p = new Persist();
expect(p.getLangCode()).toBe('en');
expect(p.showTooltips()).toBe(true);
expect(p.getInsurance()).toBe('standard');
expect(p.getShipDiscount()).toBe(1);
expect(p.getModuleDiscount()).toBe(1);
expect(p.getSizeRatio()).toBe(1);
});
it("loads from localStorage correctly", function() {
expect(false).toBeTruthy('Implement');
expect(false).toBeTruthy('TODO: Implement');
});
it("uses defaults from a corrupted localStorage", function() {
expect(false).toBeTruthy('TODO: Implement');
});
it("works without localStorage", function() {
});
it("generates the backup", function() {
expect(false).toBeTruthy('Implement');
expect(false).toBeTruthy('TODO: Implement');
});
});

View File

@@ -1,5 +1,5 @@
import Ship from '../src/app/shipyard/Ship';
import { Ships } from 'coriolis-data';
import { Ships } from 'coriolis-data/dist';
import * as Serializer from '../src/app/shipyard/Serializer';
import jsen from 'jsen';
@@ -11,7 +11,7 @@ describe("Serializer", function() {
describe("To Detailed Build", function() {
let testBuild = new Ship('anaconda', anaconda.properties, anaconda.slots).buildFrom(code);
let exportData = Serializer.toDetailedBuild('Test', testBuild);
let exportData = Serializer.toDetailedBuild('Test My Ship', testBuild);
it("conforms to the v3 ship-loadout schema", function() {
expect(validate(exportData)).toBe(true);

View File

@@ -1,5 +1,5 @@
import Ship from '../src/app/shipyard/Ship';
import { Ships } from 'coriolis-data';
import { Ships } from 'coriolis-data/dist';
import * as ModuleUtils from '../src/app/shipyard/ModuleUtils';
describe("Ship Factory", function() {
@@ -22,8 +22,8 @@ describe("Ship Factory", function() {
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.unladenFastestRange).toBeGreaterThan(0, s + ' unladenFastestRange');
expect(ship.ladenFastestRange).toBeGreaterThan(0, s + ' ladenFastestRange');
expect(ship.shieldStrength).toBeGreaterThan(0, s + ' shieldStrength');
expect(ship.armour).toBeGreaterThan(0, s + ' armour');
expect(ship.topSpeed).toBeGreaterThan(0, s + ' topSpeed');

View File

@@ -37,7 +37,7 @@ http {
gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript;
server {
listen 3300;
listen 3301;
server_name localhost;
root ./build/;
index index.html;

View File

@@ -1,11 +1,11 @@
{
"name": "coriolis_shipyard",
"version": "2.0.0-alpha",
"version": "2.0.1-Beta",
"repository": {
"type": "git",
"url": "https://github.com/cmmcleod/coriolis"
},
"homepage": "http://coriolis.io",
"homepage": "https://coriolis.io",
"bugs": "https://github.com/cmmcleod/coriolis/issues",
"private": true,
"engine": "node >= 4.0.0",
@@ -17,9 +17,8 @@
"test": "jest",
"prod-serve": "nginx -p $(pwd) -c nginx.conf",
"prod-stop": "kill -QUIT $(cat nginx.pid)",
"build:prod": "npm run clean && NODE_ENV=production CDN='//cdn.coriolis.io' webpack -d -p --config webpack.config.prod.js",
"build": "npm run clean && NODE_ENV=production webpack -d -p --config webpack.config.prod.js",
"rsync": "rsync -e 'ssh -i $CORIOLIS_PEM' -a --delete build/ $CORIOLIS_USER@$CORIOLIS_HOST:~/www",
"rsync": "rsync -ae \"ssh -i $CORIOLIS_PEM\" --delete build/ $CORIOLIS_USER@$CORIOLIS_HOST:~/wwws",
"deploy": "npm run lint && npm test && npm run build:prod && npm run rsync"
},
"jest": {
@@ -73,7 +72,7 @@
"json-loader": "^0.5.3",
"less": "^2.5.3",
"less-loader": "^2.2.1",
"react-addons-test-utils": "^0.14.6",
"react-addons-test-utils": "^0.14.7",
"react-testutils-additions": "^0.16.0",
"rimraf": "^2.4.3",
"style-loader": "^0.13.0",
@@ -84,11 +83,12 @@
"dependencies": {
"babel-polyfill": "^6.3.14",
"classnames": "^2.2.0",
"coriolis-data": "cmmcleod/coriolis-data",
"d3": "^3.5.9",
"fbemitter": "^2.0.0",
"lz-string": "^1.4.4",
"react": "^0.14.6",
"react-dom": "^0.14.6",
"react": "^0.14.7",
"react-dom": "^0.14.7",
"superagent": "^1.4.0"
}
}

View File

@@ -6,12 +6,14 @@ import Persist from './stores/Persist';
import Header from './components/Header';
import Tooltip from './components/Tooltip';
import ModalImport from './components/ModalImport';
import AboutPage from './pages/AboutPage';
import NotFoundPage from './pages/NotFoundPage';
import OutfittingPage from './pages/OutfittingPage';
import ComparisonPage from './pages/ComparisonPage';
import ShipyardPage from './pages/ShipyardPage';
import ErrorDetails from './pages/ErrorDetails';
/**
* Coriolis App
@@ -28,7 +30,8 @@ export default class Coriolis extends React.Component {
hideModal: React.PropTypes.func.isRequired,
tooltip: React.PropTypes.func.isRequired,
termtip: React.PropTypes.func.isRequired,
onWindowResize: React.PropTypes.func.isRequired
onWindowResize: React.PropTypes.func.isRequired,
onCommand: React.PropTypes.func.isRequired
};
/**
@@ -44,6 +47,7 @@ export default class Coriolis extends React.Component {
this._tooltip = this._tooltip.bind(this);
this._termtip = this._termtip.bind(this);
this._onWindowResize = this._onWindowResize.bind(this);
this._onCommand = this._onCommand.bind(this);
this._onLanguageChange = this._onLanguageChange.bind(this);
this._onSizeRatioChange = this._onSizeRatioChange.bind(this);
this._keyDown = this._keyDown.bind(this);
@@ -70,12 +74,15 @@ export default class Coriolis extends React.Component {
* @param {Object} route The current route
*/
_setPage(page, route) {
this.setState({ page, route, currentMenu: null });
this.setState({ page, route, currentMenu: null, modal: null, error: null });
}
/**
* Handle unexpected error
* TODO: Implement and fix to work with Webpack (dev + prod)
* Handle unexpected error. This is most likely an unhandled React Error which
* is also most likely unrecoverable. The best option is to catch as many details
* as possible so the user can report the error and provide a link to reload the page
* to reset the VM and clear any error state.
*
* @param {string} msg Message
* @param {string} scriptUrl URL
* @param {number} line Line number
@@ -83,8 +90,16 @@ export default class Coriolis extends React.Component {
* @param {Object} errObj Error Object
*/
_onError(msg, scriptUrl, line, col, errObj) {
console.log('WINDOW ERROR', arguments);
// this._setPage(<div>Some errors occured!!</div>);
console && console.error && console.error(arguments); // eslint-disable-line no-console
this.setState({
error: <ErrorDetails error={{ message: msg, details: { scriptUrl, line, col, error: JSON.stringify(errObj) } }}/>,
page: null,
currentMenu: null,
modal: null
});
// TODO: Improve in the event of React Errors
// Potentially ReactDOM.render into dom here instead
// ReactDOM.render(this, document.getElementById('coriolis'));
}
/**
@@ -108,11 +123,23 @@ export default class Coriolis extends React.Component {
* @param {Event} e Keyboard Event
*/
_keyDown(e) {
// .keyCode will eventually be replaced with .key
switch (e.keyCode) {
case 27:
case 27: // Escape Key
this._hideModal();
this._closeMenu();
break;
case 73: // 'i'
if (e.ctrlKey || e.metaKey) { // CTRL/CMD + i
e.preventDefault();
this._showModal(<ModalImport />);
}
break;
case 101010: // 's'
if (e.ctrlKey || e.metaKey) { // CTRL/CMD + i
e.preventDefault();
this.emitter.emit('command', 'save');
}
}
}
@@ -121,7 +148,7 @@ export default class Coriolis extends React.Component {
* @param {React.Component} content Modal Content
*/
_showModal(content) {
let modal = <div className='modal-bg' onTouchTap={(e) => this._hideModal() }>{content}</div>;
let modal = <div className='modal-bg' onClick={(e) => this._hideModal() }>{content}</div>;
this.setState({ modal });
}
@@ -169,16 +196,20 @@ export default class Coriolis extends React.Component {
/**
* Show the term tip
* @param {string} term Term
* @param {[type]} orientation Tooltip orientation (n,e,s,w)
* @param {string} term Term or Phrase
* @param {Object} opts Options - dontCap, orientation (n,e,s,w)
* @param {SyntheticEvent} event Event
*/
_termtip(term, orientation, event) {
if (typeof orientation != 'string') {
event = orientation;
orientation = null;
_termtip(term, opts, event) {
if (opts && opts.nativeEvent) { // Opts is a SyntheticEvent
event = opts;
opts = { cap: true };
}
this._tooltip(<div className='cap cen'>{this.state.language.translate(term)}</div>, event.currentTarget.getBoundingClientRect(), { orientation });
this._tooltip(
<div className={'cen' + (opts.cap ? ' cap' : '')}>{this.state.language.translate(term)}</div>,
event.currentTarget.getBoundingClientRect(),
opts
);
}
/**
@@ -190,6 +221,15 @@ export default class Coriolis extends React.Component {
return this.emitter.addListener('windowResize', listener);
}
/**
* Add a listener to global commands such as save,
* @param {Function} listener Listener callback
* @return {Object} Subscription token
*/
_onCommand(listener) {
return this.emitter.addListener('command', listener);
}
/**
* Creates the context to be passed down to pages / components containing
* language, sizeRatio and route references
@@ -206,7 +246,8 @@ export default class Coriolis extends React.Component {
hideModal: this._hideModal,
tooltip: this._tooltip,
termtip: this._termtip,
onWindowResize: this._onWindowResize
onWindowResize: this._onWindowResize,
onCommand: this._onCommand
};
}
@@ -238,9 +279,9 @@ export default class Coriolis extends React.Component {
* @return {React.Component} The main app
*/
render() {
return <div onTouchTap={this._closeMenu}>
return <div onClick={this._closeMenu}>
<Header appCacheUpdate={this.state.appCacheUpdate} currentMenu={this.state.currentMenu} />
{ this.state.page ? <this.state.page currentMenu={this.state.currentMenu} /> : <NotFoundPage/> }
{ this.state.error ? this.state.error : this.state.page ? <this.state.page currentMenu={this.state.currentMenu} /> : <NotFoundPage/> }
{ this.state.modal }
{ this.state.tooltip }
</div>;

View File

@@ -9,7 +9,7 @@ import cn from 'classnames';
* @return {boolean} If matches
*/
function isActive(href) {
return encodeURI(href) == (window.location.pathname + window.location.search);
return href == (window.location.pathname + window.location.search);
}
/**
@@ -22,13 +22,12 @@ export default class ActiveLink extends Link {
* @return {React.Component} The active link
*/
render() {
let action = this.handler.bind(this);
let className = this.props.className;
if (isActive(this.props.href)) {
className = cn(className, 'active');
}
return <a {...this.props} className={className} onTouchTap={action}>{this.props.children}</a>;
return <a {...this.props} className={className} onClick={this.handler}>{this.props.children}</a>;
}
}

View File

@@ -5,6 +5,8 @@ import { stopCtxPropagation } from '../utils/UtilityFunctions';
import cn from 'classnames';
import { MountFixed, MountGimballed, MountTurret } from './SvgIcons';
const PRESS_THRESHOLD = 5000; // mouse/touch down threshold
/**
* Available modules menu
*/
@@ -31,20 +33,19 @@ export default class AvailableModulesMenu extends TranslatedComponent {
constructor(props, context) {
super(props);
this._hideDiff = this._hideDiff.bind(this);
this._diffMove = this._diffMove.bind(this);
this.state = { list: this._initList(props, context) };
this.state = this._initState(props, context);
}
/**
* Initiate the list of available moduels
* @param {Object} props React Component properties
* @param {Object} context React Component context
* @return {Array} Array of React Components
* @return {Object} list: Array of React Components, currentGroup Component if any
*/
_initList(props, context) {
_initState(props, context) {
let translate = context.language.translate;
let { m, warning, shipMass, onSelect, modules } = props;
let list;
let list, currentGroup;
let buildGroup = this._buildGroup.bind(
this,
translate,
@@ -62,14 +63,19 @@ export default class AvailableModulesMenu extends TranslatedComponent {
} else {
list = [];
// At present time slots with grouped options (Hardpoints and Internal) can be empty
list.push(<div className={'empty-c upp'} key={'empty'} onClick={onSelect.bind(null, null)} >{translate('empty')}</div>);
list.push(<div className='empty-c upp' key='empty' onClick={onSelect.bind(null, null)} >{translate('empty')}</div>);
for (let g in modules) {
list.push(<div ref={g} key={g} className={'select-group cap'}>{translate(g)}</div>);
if (m && g == m.grp) {
list.push(<div ref={(elem) => this.groupElem = elem} key={g} className={'select-group cap'}>{translate(g)}</div>);
} else {
list.push(<div key={g} className={'select-group cap'}>{translate(g)}</div>);
}
list.push(buildGroup(g, modules[g]));
}
}
return list;
return { list, currentGroup };
}
/**
@@ -97,6 +103,22 @@ export default class AvailableModulesMenu extends TranslatedComponent {
active,
disabled
});
let eventHandlers;
if (disabled || active) {
eventHandlers = {};
} else {
let showDiff = this._showDiff.bind(this, mountedModule, m);
let select = onSelect.bind(null, m);
eventHandlers = {
onMouseEnter: this._over.bind(this, showDiff),
onTouchStart: this._touchStart.bind(this, showDiff),
onTouchEnd: this._touchEnd.bind(this, select),
onMouseLeave: this._hideDiff,
onClick: select
};
}
switch(m.mount) {
case 'F': mount = <MountFixed className={'lg'} />; break;
@@ -108,19 +130,10 @@ export default class AvailableModulesMenu extends TranslatedComponent {
elems.push(<br key={m.grp + i} />);
}
let showDiff = disabled || active ? null : this._showDiff.bind(this, mountedModule, m);
elems.push(
<li
key={m.id}
className={classes}
onMouseEnter={showDiff}
onTouchStart={showDiff}
onMouseLeave={this._hideDiff}
onClick={disabled ? null : onSelect.bind(null, m)}
>
<li key={m.id} className={classes} {...eventHandlers}>
{mount}
<span>{(mount ? ' ' : '') + m.class + m.rating + (m.missile ? '/' + m.missile : '') + (m.name ? ' ' + translate(m.name) : '')}</span>
{(mount ? ' ' : '') + m.class + m.rating + (m.missile ? '/' + m.missile : '') + (m.name ? ' ' + translate(m.name) : '')}
</li>
);
prevClass = m.class;
@@ -135,22 +148,46 @@ export default class AvailableModulesMenu extends TranslatedComponent {
* mounted module and the hovered modules
* @param {Object} mm The module mounet currently
* @param {Object} m The hovered module
* @param {DOMRect} rect DOMRect for target element
*/
_showDiff(mm, m, rect) {
if (this.props.diffDetails) {
this.touchTimeout = null;
this.context.tooltip(this.props.diffDetails(m, mm), rect);
}
}
/**
* Mouse over diff handler
* @param {Function} showDiff diff tooltip callback
* @param {SyntheticEvent} event Event
*/
_showDiff(mm, m, event) {
_over(showDiff, event) {
event.preventDefault();
if (this.props.diffDetails) {
this.context.tooltip(this.props.diffDetails(m, mm), event.currentTarget.getBoundingClientRect());
}
showDiff(event.currentTarget.getBoundingClientRect());
}
_touchStart(event) {
event.preventDefault();
console.log(Object.assign({}, event));
/**
* Toucch Start - Show diff after press, otherwise treat as tap
* @param {Function} showDiff diff tooltip callback
* @param {SyntheticEvent} event Event
*/
_touchStart(showDiff, event) {
let rect = event.currentTarget.getBoundingClientRect();
this.touchTimeout = setTimeout(showDiff.bind(this, rect), PRESS_THRESHOLD);
}
_diffMove(event) {
console.log(Object.assign({}, event));
/**
* Touch End - Select module on tap
* @param {Function} select Select module callback
* @param {SyntheticEvent} event Event
*/
_touchEnd(select, event) {
if (this.touchTimeout !== null) { // If timeout has not fired (been nulled out) yet
select();
}
event.preventDefault();
this._hideDiff();
}
/**
@@ -158,18 +195,17 @@ export default class AvailableModulesMenu extends TranslatedComponent {
* @param {SyntheticEvent} event Event
*/
_hideDiff(event) {
event.preventDefault();
clearTimeout(this.touchTimeout);
this.touchTimeout = null;
this.context.tooltip();
}
/**
* Scroll to mounted (if it exists) component on mount
* Scroll to mounted (if it exists) module group on mount
*/
componentDidMount() {
let m = this.props.m;
if (!(this.props.modules instanceof Array) && m && m.grp) {
findDOMNode(this).scrollTop = this.refs[m.grp].offsetTop; // Scroll to currently selected group
if (this.groupElem) { // Scroll to currently selected group
findDOMNode(this).scrollTop = this.groupElem.offsetTop;
}
}
@@ -179,7 +215,7 @@ export default class AvailableModulesMenu extends TranslatedComponent {
* @param {Object} nextContext Incoming/Next conext
*/
componentWillReceiveProps(nextProps, nextContext) {
this.setState({ list: this._initList(nextProps, nextContext) });
this.setState(this._initState(nextProps, nextContext));
}
/**
@@ -192,9 +228,6 @@ export default class AvailableModulesMenu extends TranslatedComponent {
className={cn('select', this.props.className)}
onScroll={this._hideDiff}
onClick={(e) => e.stopPropagation() }
onTouchStart={this._touchStart}
onTouchEnd={this._hideDiff}
onTouchCancel={this._hideDiff}
onContextMenu={stopCtxPropagation}
>
{this.state.list}

View File

@@ -40,18 +40,21 @@ export default class BarChart extends TranslatedComponent {
static defaultProps = {
colors: ['#7b6888', '#6b486b', '#3182bd', '#a05d56', '#d0743c'],
labels: null,
unit: ''
};
static PropTypes = {
data: React.PropTypes.array.isRequired,
width: React.PropTypes.number.isRequired,
format: React.PropTypes.string.isRequired,
label: React.PropTypes.string.isRequired,
unit: React.PropTypes.string.isRequired,
colors: React.PropTypes.array,
data: React.PropTypes.array.isRequired,
desc: React.PropTypes.bool,
format: React.PropTypes.string.isRequired,
labels: React.PropTypes.array,
predicate: React.PropTypes.string,
desc: React.PropTypes.bool
properties: React.PropTypes.array,
title: React.PropTypes.string.isRequired,
unit: React.PropTypes.string.isRequired,
width: React.PropTypes.number.isRequired
};
/**
@@ -78,23 +81,28 @@ export default class BarChart extends TranslatedComponent {
* Generate and Show tooltip
* @param {Object} build Ship build
* @param {string} property Property to display
* @param {number} propertyIndex Property Label index
*/
_showTip(build, property) {
let { unit, format } = this.props;
_showTip(build, property, propertyIndex) {
let { unit, format, labels } = this.props;
let { scale, y0, y1 } = this.state;
let { formats } = this.context.language;
let { translate, formats } = this.context.language;
let fontSize = parseFloat(window.getComputedStyle(document.getElementById('coriolis')).getPropertyValue('font-size') || 16);
let val = build[property];
let lblStr = labels ? translate(labels[propertyIndex]) + ': ' : '';
let valStr = formats[format](val) + ' ' + unit;
let width = (valStr.length / 1.7) * fontSize;
let midPoint = width / 2;
let valMidPoint = scale(val) / 2;
let y = y0(bName(build)) + y1(property) - fontSize - 5;
let width = ((lblStr.length + valStr.length) / 1.8) * fontSize;
let midPoint = width / 2;
let tooltip = <g>
<g transform={`translate(${Math.max(0, valMidPoint - midPoint)},${y})`}>
<rect className='primary-disabled' height={fontSize} width={width} />
<text x={midPoint} y={fontSize} dy='-0.4em' style={{ textAnchor: 'middle', fontSize: '0.7em' }}>{valStr}</text>
<text x={midPoint} y={fontSize} dy={fontSize / -4} style={{ textAnchor: 'middle', fontSize: '0.7em' }}>
<tspan style={{ textTransform: 'capitalize' }}>{lblStr}</tspan>
<tspan>{valStr}</tspan>
</text>
</g>
<path className='primary-disabled' d='M0,0L5,5L10,0Z' dy='1em' transform={`translate(${Math.max(0, valMidPoint - 5)},${y + fontSize})`} />
</g>;
@@ -173,12 +181,12 @@ export default class BarChart extends TranslatedComponent {
return null;
}
let { label, unit, width, data, properties } = this.props;
let { title, unit, width, data, properties } = this.props;
let { innerWidth, outerHeight, innerHeight, y0, y1, scale, color, tooltip } = this.state;
let bars = data.map((build, i) =>
<g key={i} transform={`translate(0,${y0(bName(build))})`}>
{ properties.map((p) =>
{ properties.map((p, propIndex) =>
<rect
key={p}
x={0}
@@ -186,7 +194,7 @@ export default class BarChart extends TranslatedComponent {
width={scale(build[p])}
height={y1.rangeBand()}
fill={color(p)}
onMouseOver={this._showTip.bind(this, build, p)}
onMouseOver={this._showTip.bind(this, build, p, propIndex)}
onMouseOut={this._hideTip}
/>
)}
@@ -199,7 +207,7 @@ export default class BarChart extends TranslatedComponent {
{tooltip}
<g className='x axis' ref={(elem) => d3.select(elem).call(this.xAxis)} transform={`translate(0,${innerHeight})`}>
<text className='cap' y='30' dy='.1em' x={innerWidth / 2} style={{ textAnchor: 'middle' }}>
<tspan>{label}</tspan>
<tspan>{title}</tspan>
{ unit ? <tspan className='metric'>{` (${unit})`}</tspan> : null }
</text>
</g>

View File

@@ -38,8 +38,8 @@ export default class ComparisonTable extends TranslatedComponent {
*/
_buildHeaders(facets, onSort, translate) {
let header = [
<th key='ship' rowSpan='2' className='sortable' onTouchTap={onSort.bind(null, 'name')}>{translate('ship')}</th>,
<th key='build' rowSpan='2' className='sortable' onTouchTap={onSort.bind(null, 'buildName')}>{translate('build')}</th>
<th key='ship' rowSpan='2' className='sortable' onClick={onSort.bind(null, 'name')}>{translate('ship')}</th>,
<th key='build' rowSpan='2' className='sortable' onClick={onSort.bind(null, 'buildName')}>{translate('build')}</th>
];
let subHeader = [];
@@ -47,13 +47,13 @@ export default class ComparisonTable extends TranslatedComponent {
if (f.active) {
let p = f.props;
let pl = p.length;
header.push(<th key={f.title} rowSpan={pl === 1 ? 2 : 1} colSpan={pl} className={cn({ sortable: pl === 1 })} onTouchTap={pl === 1 ? onSort.bind(null, p[0]) : null }>
header.push(<th key={f.title} rowSpan={pl === 1 ? 2 : 1} colSpan={pl} className={cn({ sortable: pl === 1 })} onClick={pl === 1 ? onSort.bind(null, p[0]) : null }>
{translate(f.title)}
</th>);
if (pl > 1) {
for (let i = 0; i < pl; i++) {
subHeader.push(<th key={p[i]} className={cn('sortable', { lft: i === 0 })} onTouchTap={onSort.bind(null, p[i])} >{translate(f.lbls[i])}</th>);
subHeader.push(<th key={p[i]} className={cn('sortable', { lft: i === 0 })} onClick={onSort.bind(null, p[i])} >{translate(f.lbls[i])}</th>);
}
}
}

View File

@@ -1,6 +1,6 @@
import React from 'react';
import cn from 'classnames';
import { Ships } from 'coriolis-data';
import { Ships } from 'coriolis-data/dist';
import Persist from '../stores/Persist';
import Ship from '../shipyard/Ship';
import { Insurance } from '../shipyard/Constants';
@@ -46,7 +46,6 @@ export default class CostSection extends TranslatedComponent {
retrofitName,
shipDiscount,
moduleDiscount,
total: props.ship.totalCost,
insurance: Insurance[Persist.getInsurance()],
tab: Persist.getCostTab(),
buildOptions: Persist.getBuildsNamesFor(props.ship.id),
@@ -108,6 +107,7 @@ export default class CostSection extends TranslatedComponent {
let moduleDiscount = Persist.getModuleDiscount();
this.props.ship.applyDiscounts(shipDiscount, moduleDiscount);
this.state.retrofitShip.applyDiscounts(shipDiscount, moduleDiscount);
this._updateRetrofit(this.props.ship, this.state.retrofitShip);
this.setState({ shipDiscount, moduleDiscount });
}
@@ -137,32 +137,28 @@ export default class CostSection extends TranslatedComponent {
}
/**
* On build save
* @param {string} shipId Ship Id
* @param {string} name Build name
* @param {string} code Serialized ship 'code'
* On builds changed check to see if the retrofit ship needs
* to be updated
*/
_onBuildSaved(shipId, name, code) {
if(this.state.retrofitName == name) {
this.state.retrofitShip.buildFrom(code); // Repopulate modules from saved build
this._updateRetrofit(this.props.ship, this.state.retrofitShip);
} else {
this.setState({ buildOptions: Persist.getBuildsNamesFor(this.props.shipId) });
}
_onBuildsChanged() {
let update = false;
let ship = this.props.ship;
let { retrofitName, retrofitShip } = this.state;
if(!Persist.hasBuild(ship.id, retrofitName)) {
retrofitShip.buildWith(Ships[ship.id].defaults); // Retrofit ship becomes stock build
this.setState({ retrofitName: null });
update = true;
} else if (Persist.getBuild(ship.id, retrofitName) != retrofitShip.toString()) {
retrofitShip.buildFrom(Persist.getBuild(ship.id, retrofitName)); // Repopulate modules from saved build
update = true;
}
/**
* On build deleted
* @param {string} shipId Ship Id
* @param {string} name Build name
* @param {string} code Serialized ship 'code'
*/
_onBuildDeleted(shipId, name, code) {
if(this.state.retrofitName == name) {
this.state.retrofitShip.buildWith(Ships[shipId].defaults); // Retrofit ship becomes stock build
this.setState({ retrofitName: null });
if (update) { // Update retrofit comparison
this._updateRetrofit(ship, retrofitShip);
}
this.setState({ buildOptions: Persist.getBuildsNamesFor(shipId) });
// Update list of retrofit base build options
this.setState({ buildOptions: Persist.getBuildsNamesFor(ship.id) });
}
/**
@@ -171,7 +167,7 @@ export default class CostSection extends TranslatedComponent {
*/
_toggleCost(item) {
this.props.ship.setCostIncluded(item, !item.incCost);
this.setState({ total: this.props.ship.totalCost });
this.forceUpdate();
}
/**
@@ -286,7 +282,7 @@ export default class CostSection extends TranslatedComponent {
*/
_costsTab() {
let { ship } = this.props;
let { total, shipDiscount, moduleDiscount, insurance } = this.state;
let { shipDiscount, moduleDiscount, insurance } = this.state;
let { translate, formats, units } = this.context.language;
let rows = [];
@@ -295,9 +291,9 @@ export default class CostSection extends TranslatedComponent {
if (item.m && item.m.cost) {
let toggle = this._toggleCost.bind(this, item);
rows.push(<tr key={i} className={cn('highlight', { disabled: !item.incCost })}>
<td className='ptr' style={{ width: '1em' }} onTouchTap={toggle}>{item.m.class + item.m.rating}</td>
<td className='le ptr shorten cap' onTouchTap={toggle}>{slotName(translate, item)}</td>
<td className='ri ptr' onTouchTap={toggle}>{formats.int(item.discountedCost)}{units.CR}</td>
<td className='ptr' style={{ width: '1em' }} onClick={toggle}>{item.m.class + item.m.rating}</td>
<td className='le ptr shorten cap' onClick={toggle}>{slotName(translate, item)}</td>
<td className='ri ptr' onClick={toggle}>{formats.int(item.discountedCost)}{units.CR}</td>
</tr>);
}
}
@@ -306,23 +302,23 @@ export default class CostSection extends TranslatedComponent {
<table style={{ width: '100%', borderCollapse: 'collapse' }}>
<thead>
<tr className='main'>
<th colSpan='2' className='sortable le' onTouchTap={this._sortCostBy.bind(this,'m')}>
<th colSpan='2' className='sortable le' onClick={this._sortCostBy.bind(this,'m')}>
{translate('component')}
{shipDiscount < 1 && <u className='cap optional-hide' style={{ marginLeft: '0.5em' }}>{`[${translate('ship')} -${formats.pct1(1 - shipDiscount)}]`}</u>}
{moduleDiscount < 1 && <u className='cap optional-hide' style={{ marginLeft: '0.5em' }}>{`[${translate('modules')} -${formats.pct1(1 - moduleDiscount)}]`}</u>}
</th>
<th className='sortable le' onTouchTap={this._sortCostBy.bind(this, 'cr')} >{translate('credits')}</th>
<th className='sortable le' onClick={this._sortCostBy.bind(this, 'cr')} >{translate('credits')}</th>
</tr>
</thead>
<tbody>
{rows}
<tr className='ri'>
<td colSpan='2' className='lbl' >{translate('total')}</td>
<td className='val'>{formats.int(total)}{units.CR}</td>
<td className='val'>{formats.int(ship.totalCost)}{units.CR}</td>
</tr>
<tr className='ri'>
<td colSpan='2' className='lbl'>{translate('insurance')}</td>
<td className='val'>{formats.int(total * insurance)}{units.CR}</td>
<td className='val'>{formats.int(ship.totalCost * insurance)}{units.CR}</td>
</tr>
</tbody>
</table>
@@ -346,12 +342,12 @@ export default class CostSection extends TranslatedComponent {
if (retrofitCosts.length) {
for (let i = 0, l = retrofitCosts.length; i < l; i++) {
let item = retrofitCosts[i];
rows.push(<tr key={i} className={cn('highlight', { disabled: !item.retroItem.incCost })} onTouchTap={this._toggleRetrofitCost.bind(this, item)}>
<td style={{ width: '1em' }}>{item.sellClassRating}</td>
<td className='le shorten cap'>{translate(item.sellName)}</td>
<td style={{ width: '1em' }}>{item.buyClassRating}</td>
<td className='le shorten cap'>{translate(item.buyName)}</td>
<td colSpan='2' className={cn('ri', item.retroItem.incCost ? item.netCost > 0 ? 'warning' : 'secondary-disabled' : 'disabled')}>{int(item.netCost)}{units.CR}</td>
rows.push(<tr key={i} className={cn('highlight', { disabled: !item.retroItem.incCost })} onClick={this._toggleRetrofitCost.bind(this, item)}>
<td className='ptr' style={{ width: '1em' }}>{item.sellClassRating}</td>
<td className='le ptr shorten cap'>{translate(item.sellName)}</td>
<td className='ptr' style={{ width: '1em' }}>{item.buyClassRating}</td>
<td className='le ptr shorten cap'>{translate(item.buyName)}</td>
<td colSpan='2' className={cn('ri ptr', item.retroItem.incCost ? item.netCost > 0 ? 'warning' : 'secondary-disabled' : 'disabled')}>{int(item.netCost)}{units.CR}</td>
</tr>);
}
} else {
@@ -363,11 +359,11 @@ export default class CostSection extends TranslatedComponent {
<table style={{ width: '100%' }}>
<thead>
<tr className='main'>
<th colSpan='2' className='sortable le' onTouchTap={this._sortRetrofitBy.bind(this, 'sellName')}>{translate('sell')}</th>
<th colSpan='2' className='sortable le' onTouchTap={this._sortRetrofitBy.bind(this, 'buyName')}>{translate('buy')}</th>
<th colSpan='2' className='sortable le' onTouchTap={this._sortRetrofitBy.bind(this, 'cr')}>
<th colSpan='2' className='sortable le' onClick={this._sortRetrofitBy.bind(this, 'sellName')}>{translate('sell')}</th>
<th colSpan='2' className='sortable le' onClick={this._sortRetrofitBy.bind(this, 'buyName')}>{translate('buy')}</th>
<th colSpan='2' className='sortable le' onClick={this._sortRetrofitBy.bind(this, 'cr')}>
{translate('net cost')}
{moduleDiscount < 1 && <u className='cap optional-hide' style={{ marginLeft: '0.5em' }}>{`[${translate('modules')} -${formats.rPct(1 - moduleDiscount)}]`}</u>}
{moduleDiscount < 1 && <u className='cap optional-hide' style={{ marginLeft: '0.5em' }}>{`[${translate('modules')} -${formats.pct1(1 - moduleDiscount)}]`}</u>}
</th>
</tr>
</thead>
@@ -473,10 +469,10 @@ export default class CostSection extends TranslatedComponent {
<table style={{ width: '100%' }}>
<thead>
<tr className='main'>
<th colSpan='2' className='sortable le' onTouchTap={this._sortAmmoBy.bind(this, 'm')} >{translate('module')}</th>
<th colSpan='1' className='sortable le' onTouchTap={this._sortAmmoBy.bind(this, 'max')} >{translate('qty')}</th>
<th colSpan='1' className='sortable le' onTouchTap={this._sortAmmoBy.bind(this, 'cost')} >{translate('unit cost')}</th>
<th className='sortable le' onTouchTap={this._sortAmmoBy.bind(this, 'total')}>{translate('total cost')}</th>
<th colSpan='2' className='sortable le' onClick={this._sortAmmoBy.bind(this, 'm')} >{translate('module')}</th>
<th colSpan='1' className='sortable le' onClick={this._sortAmmoBy.bind(this, 'max')} >{translate('qty')}</th>
<th colSpan='1' className='sortable le' onClick={this._sortAmmoBy.bind(this, 'cost')} >{translate('unit cost')}</th>
<th className='sortable le' onClick={this._sortAmmoBy.bind(this, 'total')}>{translate('total cost')}</th>
</tr>
</thead>
<tbody>
@@ -583,8 +579,7 @@ export default class CostSection extends TranslatedComponent {
this.listeners = [
Persist.addListener('discounts', this._onDiscountChanged.bind(this)),
Persist.addListener('insurance', this._onInsuranceChanged.bind(this)),
Persist.addListener('buildSaved', this._onBuildSaved.bind(this)),
Persist.addListener('buildDeleted', this._onBuildDeleted.bind(this))
Persist.addListener('builds', this._onBuildsChanged.bind(this)),
];
this._updateAmmoCosts(this.props.ship);
this._updateRetrofit(this.props.ship, this.state.retrofitShip);
@@ -613,7 +608,6 @@ export default class CostSection extends TranslatedComponent {
if (nextProps.ship != this.props.ship || nextProps.code != this.props.code) {
this._updateAmmoCosts(nextProps.ship);
this._updateRetrofit(nextProps.ship, retrofitShip);
this.setState({ total: nextProps.ship.totalCost });
this._sortCost(nextProps.ship);
}
}
@@ -673,9 +667,9 @@ export default class CostSection extends TranslatedComponent {
<table className='tabs'>
<thead>
<tr>
<th style={{ width:'33%' }} className={cn({ active: tab == 'costs' })} onTouchTap={this._showTab.bind(this, 'costs')} >{translate('costs')}</th>
<th style={{ width:'33%' }} className={cn({ active: tab == 'retrofit' })} onTouchTap={this._showTab.bind(this, 'retrofit')} >{translate('retrofit costs')}</th>
<th style={{ width:'34%' }} className={cn({ active: tab == 'ammo' })} onTouchTap={this._showTab.bind(this, 'ammo')} >{translate('reload costs')}</th>
<th style={{ width:'33%' }} className={cn({ active: tab == 'costs' })} onClick={this._showTab.bind(this, 'costs')} >{translate('costs')}</th>
<th style={{ width:'33%' }} className={cn({ active: tab == 'retrofit' })} onClick={this._showTab.bind(this, 'retrofit')} >{translate('retrofit costs')}</th>
<th style={{ width:'34%' }} className={cn({ active: tab == 'ammo' })} onClick={this._showTab.bind(this, 'ammo')} >{translate('reload costs')}</th>
</tr>
</thead>
</table>

View File

@@ -91,39 +91,39 @@ export default class HardpointsSlotSection extends SlotSection {
_getSectionMenu(translate) {
let _fill = this._fill;
return <div className='select hardpoint' onTouchTap={(e) => e.stopPropagation()} onContextMenu={stopCtxPropagation}>
return <div className='select hardpoint' onClick={(e) => e.stopPropagation()} onContextMenu={stopCtxPropagation}>
<ul>
<li className='lc' onTouchTap={this._empty}>{translate('empty all')}</li>
<li className='lc' onClick={this._empty}>{translate('empty all')}</li>
</ul>
<div className='select-group cap'>{translate('pl')}</div>
<ul>
<li className='c' onTouchTap={_fill.bind(this, 'pl', 'F')}><MountFixed className='lg'/></li>
<li className='c' onTouchTap={_fill.bind(this, 'pl', 'G')}><MountGimballed className='lg'/></li>
<li className='c' onTouchTap={_fill.bind(this, 'pl', 'T')}><MountTurret className='lg'/></li>
<li className='c' onClick={_fill.bind(this, 'pl', 'F')}><MountFixed className='lg'/></li>
<li className='c' onClick={_fill.bind(this, 'pl', 'G')}><MountGimballed className='lg'/></li>
<li className='c' onClick={_fill.bind(this, 'pl', 'T')}><MountTurret className='lg'/></li>
</ul>
<div className='select-group cap'>{translate('ul')}</div>
<ul>
<li className='c' onTouchTap={_fill.bind(this, 'ul', 'F')}><MountFixed className='lg'/></li>
<li className='c' onTouchTap={_fill.bind(this, 'ul', 'G')}><MountGimballed className='lg'/></li>
<li className='c' onTouchTap={_fill.bind(this, 'ul', 'T')}><MountTurret className='lg'/></li>
<li className='c' onClick={_fill.bind(this, 'ul', 'F')}><MountFixed className='lg'/></li>
<li className='c' onClick={_fill.bind(this, 'ul', 'G')}><MountGimballed className='lg'/></li>
<li className='c' onClick={_fill.bind(this, 'ul', 'T')}><MountTurret className='lg'/></li>
</ul>
<div className='select-group cap'>{translate('bl')}</div>
<ul>
<li className='c' onTouchTap={_fill.bind(this, 'bl', 'F')}><MountFixed className='lg'/></li>
<li className='c' onTouchTap={_fill.bind(this, 'bl', 'G')}><MountGimballed className='lg'/></li>
<li className='c' onTouchTap={_fill.bind(this, 'bl', 'T')}><MountTurret className='lg'/></li>
<li className='c' onClick={_fill.bind(this, 'bl', 'F')}><MountFixed className='lg'/></li>
<li className='c' onClick={_fill.bind(this, 'bl', 'G')}><MountGimballed className='lg'/></li>
<li className='c' onClick={_fill.bind(this, 'bl', 'T')}><MountTurret className='lg'/></li>
</ul>
<div className='select-group cap'>{translate('mc')}</div>
<ul>
<li className='c' onTouchTap={_fill.bind(this, 'mc', 'F')}><MountFixed className='lg'/></li>
<li className='c' onTouchTap={_fill.bind(this, 'mc', 'G')}><MountGimballed className='lg'/></li>
<li className='c' onTouchTap={_fill.bind(this, 'mc', 'T')}><MountTurret className='lg'/></li>
<li className='c' onClick={_fill.bind(this, 'mc', 'F')}><MountFixed className='lg'/></li>
<li className='c' onClick={_fill.bind(this, 'mc', 'G')}><MountGimballed className='lg'/></li>
<li className='c' onClick={_fill.bind(this, 'mc', 'T')}><MountTurret className='lg'/></li>
</ul>
<div className='select-group cap'>{translate('c')}</div>
<ul>
<li className='c' onTouchTap={_fill.bind(this, 'c', 'F')}><MountFixed className='lg'/></li>
<li className='c' onTouchTap={_fill.bind(this, 'c', 'G')}><MountGimballed className='lg'/></li>
<li className='c' onTouchTap={_fill.bind(this, 'c', 'T')}><MountTurret className='lg'/></li>
<li className='c' onClick={_fill.bind(this, 'c', 'F')}><MountFixed className='lg'/></li>
<li className='c' onClick={_fill.bind(this, 'c', 'G')}><MountGimballed className='lg'/></li>
<li className='c' onClick={_fill.bind(this, 'c', 'T')}><MountTurret className='lg'/></li>
</ul>
</div>;
}

View File

@@ -6,13 +6,14 @@ import Link from './Link';
import ActiveLink from './ActiveLink';
import cn from 'classnames';
import { Cogs, CoriolisLogo, Hammer, Rocket, StatsBars } from './SvgIcons';
import { Ships } from 'coriolis-data';
import { Ships } from 'coriolis-data/dist';
import Persist from '../stores/Persist';
import { toDetailedExport } from '../shipyard/Serializer';
import ModalDeleteAll from './ModalDeleteAll';
import ModalExport from './ModalExport';
import ModalImport from './ModalImport';
import Slider from './Slider';
import { outfitURL } from '../utils/UrlGenerators';
const SIZE_MIN = 0.65;
const SIZE_RANGE = 0.55;
@@ -181,11 +182,11 @@ export default class Header extends TranslatedComponent {
let shipList = [];
for (let s in Ships) {
shipList.push(<ActiveLink key={s} href={'/outfit/' + s} className='block'>{Ships[s].properties.name}</ActiveLink>);
shipList.push(<ActiveLink key={s} href={outfitURL(s)} className='block'>{Ships[s].properties.name}</ActiveLink>);
}
return (
<div className='menu-list dbl no-wrap' onTouchTap={ (e) => e.stopPropagation() }>
<div className='menu-list dbl no-wrap' onClick={ (e) => e.stopPropagation() }>
{shipList}
</div>
);
@@ -203,7 +204,7 @@ export default class Header extends TranslatedComponent {
let shipBuilds = [];
let buildNameOrder = Object.keys(builds[shipId]).sort();
for (let buildName of buildNameOrder) {
let href = ['/outfit/', shipId, '/', builds[shipId][buildName], '?bn=', buildName].join('');
let href = outfitURL(shipId, builds[shipId][buildName], buildName);
shipBuilds.push(<li key={shipId + '-' + buildName} ><ActiveLink href={href} className='block'>{buildName}</ActiveLink></li>);
}
buildList.push(<ul key={shipId}>{Ships[shipId].properties.name}{shipBuilds}</ul>);
@@ -211,7 +212,7 @@ export default class Header extends TranslatedComponent {
}
return (
<div className='menu-list' onTouchTap={ (e) => e.stopPropagation() }>
<div className='menu-list' onClick={ (e) => e.stopPropagation() }>
<div className='dbl'>{buildList}</div>
</div>
);
@@ -237,10 +238,10 @@ export default class Header extends TranslatedComponent {
}
return (
<div className='menu-list' onTouchTap={ (e) => e.stopPropagation() } style={{ whiteSpace: 'nowrap' }}>
<div className='menu-list' onClick={ (e) => e.stopPropagation() } style={{ whiteSpace: 'nowrap' }}>
{comparisons}
<hr />
<Link href='/compare/all' ui-sref="compare({name: 'all'})" className='block cap'>{translate('compare all')}</Link>
<Link href='/compare/all' className='block cap'>{translate('compare all')}</Link>
<Link href='/compare' className='block cap'>{translate('create new')}</Link>
</div>
);
@@ -255,14 +256,14 @@ export default class Header extends TranslatedComponent {
let tips = Persist.showTooltips();
return (
<div className='menu-list no-wrap cap' onTouchTap={ (e) => e.stopPropagation() }>
<div className='menu-list no-wrap cap' onClick={ (e) => e.stopPropagation() }>
<div style={{ lineHeight: '2em' }}>
{translate('language')}
<select className='cap' value={Persist.getLangCode()} onChange={this._setLanguage}>
{this.languageOptions}
</select>
<br/>
<span className='cap ptr' onTouchTap={this._toggleTooltips} >
<span className='cap ptr' onClick={this._toggleTooltips} >
{translate('tooltips')}
<div className={cn({ disabled: !tips, 'primary-disabled': tips })} style={{ marginLeft: '0.5em', display: 'inline-block' }}>{(tips ? '✓' : '✗')}</div>
</span>
@@ -285,10 +286,10 @@ export default class Header extends TranslatedComponent {
<hr />
<ul>
{translate('builds')} & {translate('comparisons')}
<li><a href="#" className='block' onTouchTap={this._showBackup.bind(this)}>{translate('backup')}</a></li>
<li><a href="#" className='block' onTouchTap={this._showDetailedExport.bind(this)}>{translate('detailed export')}</a></li>
<li><a href="#" className='block' onTouchTap={this._showImport.bind(this)}>{translate('import')}</a></li>
<li><a href="#" onTouchTap={this._showDeleteAll.bind(this)}>{translate('delete all')}</a></li>
<li><Link href="#" className='block' onClick={this._showBackup.bind(this)}>{translate('backup')}</Link></li>
<li><Link href="#" className='block' onClick={this._showDetailedExport.bind(this)}>{translate('detailed export')}</Link></li>
<li><Link href="#" className='block' onClick={this._showImport.bind(this)}>{translate('import')}</Link></li>
<li><Link href="#" onClick={this._showDeleteAll.bind(this)}>{translate('delete all')}</Link></li>
</ul>
<hr />
<table style={{ width: 300, backgroundColor: 'transparent' }}>
@@ -299,7 +300,7 @@ export default class Header extends TranslatedComponent {
<td style={{ width: 20 }}><span style={{ fontSize: 30 }}>A</span></td>
</tr>
<tr>
<td colSpan='3' style={{ textAlign: 'center', cursor: 'pointer' }} className='primary-disabled cap' onTouchTap={this._resetTextSize.bind(this)}>{translate('reset')}</td>
<td colSpan='3' style={{ textAlign: 'center', cursor: 'pointer' }} className='primary-disabled cap' onClick={this._resetTextSize.bind(this)}>{translate('reset')}</td>
</tr>
</tbody>
</table>
@@ -313,13 +314,13 @@ export default class Header extends TranslatedComponent {
* Add listeners on mount
*/
componentWillMount() {
Persist.addListener('language', () => this.forceUpdate());
Persist.addListener('insurance', () => this.forceUpdate());
Persist.addListener('discounts', () => this.forceUpdate());
Persist.addListener('deletedAll', () => this.forceUpdate());
Persist.addListener('buildSaved', () => this.forceUpdate());
Persist.addListener('buildDeleted', () => this.forceUpdate());
Persist.addListener('tooltips', () => this.forceUpdate());
let update = () => this.forceUpdate();
Persist.addListener('language', update);
Persist.addListener('insurance', update);
Persist.addListener('discounts', update);
Persist.addListener('deletedAll', update);
Persist.addListener('builds', update);
Persist.addListener('tooltips', update);
}
/**
@@ -347,36 +348,36 @@ export default class Header extends TranslatedComponent {
let hasBuilds = Persist.hasBuilds();
if (this.props.appCacheUpdate) {
return <div id="app-update" onTouchTap={() => window.location.reload() }>{translate('PHRASE_UPDATE_RDY')}</div>;
return <div id="app-update" onClick={() => window.location.reload() }>{translate('PHRASE_UPDATE_RDY')}</div>;
}
return (
<header>
<Link className='l' href="/" style={{ marginRight: '1em' }} title="Home"><CoriolisLogo className='icon xl' /></Link>
<Link className='l' href='/' style={{ marginRight: '1em' }} title='Home'><CoriolisLogo className='icon xl' /></Link>
<div className='l menu'>
<div className={cn('menu-header', { selected: openedMenu == 's' })} onTouchTap={this._openShips}>
<div className={cn('menu-header', { selected: openedMenu == 's' })} onClick={this._openShips}>
<Rocket className='warning' /><span className='menu-item-label'>{' ' + translate('ships')}</span>
</div>
{openedMenu == 's' ? this._getShipsMenu() : null}
</div>
<div className='l menu'>
<div className={cn('menu-header', { selected: openedMenu == 'b', disabled: !hasBuilds })} onTouchTap={hasBuilds && this._openBuilds}>
<div className={cn('menu-header', { selected: openedMenu == 'b', disabled: !hasBuilds })} onClick={hasBuilds && this._openBuilds}>
<Hammer className={cn('warning', { 'warning-disabled': !hasBuilds })} /><span className='menu-item-label'>{' ' + translate('builds')}</span>
</div>
{openedMenu == 'b' ? this._getBuildsMenu() : null}
</div>
<div className='l menu'>
<div className={cn('menu-header', { selected: openedMenu == 'comp', disabled: !hasBuilds })} onTouchTap={hasBuilds && this._openComp}>
<div className={cn('menu-header', { selected: openedMenu == 'comp', disabled: !hasBuilds })} onClick={hasBuilds && this._openComp}>
<StatsBars className={cn('warning', { 'warning-disabled': !hasBuilds })} /><span className='menu-item-label'>{' ' + translate('compare')}</span>
</div>
{openedMenu == 'comp' ? this._getComparisonsMenu() : null}
</div>
<div className='r menu'>
<div className={cn('menu-header', { selected: openedMenu == 'settings' })} onTouchTap={this._openSettings}>
<div className={cn('menu-header', { selected: openedMenu == 'settings' })} onClick={this._openSettings}>
<Cogs className='xl warning'/><span className='menu-item-label'>{translate('settings')}</span>
</div>
{openedMenu == 'settings' ? this._getSettingsMenu() : null}

View File

@@ -132,12 +132,12 @@ export default class InternalSlotSection extends SlotSection {
* @return {React.Component} Section menu
*/
_getSectionMenu(translate) {
return <div className='select' onTouchTap={e => e.stopPropagation()} onContextMenu={stopCtxPropagation}>
return <div className='select' onClick={e => e.stopPropagation()} onContextMenu={stopCtxPropagation}>
<ul>
<li className='lc' onTouchTap={this._empty}>{translate('empty all')}</li>
<li className='lc' onTouchTap={this._fillWithCargo}>{translate('cargo')}</li>
<li className='lc' onTouchTap={this._fillWithCells}>{translate('scb')}</li>
<li className='lc' onTouchTap={this._fillWithArmor}>{translate('hr')}</li>
<li className='lc' onClick={this._empty}>{translate('empty all')}</li>
<li className='lc' onClick={this._fillWithCargo}>{translate('cargo')}</li>
<li className='lc' onClick={this._fillWithCells}>{translate('scb')}</li>
<li className='lc' onClick={this._fillWithArmor}>{translate('hr')}</li>
</ul>
</div>;
}

View File

@@ -3,7 +3,7 @@ import d3 from 'd3';
import TranslatedComponent from './TranslatedComponent';
const RENDER_POINTS = 20; // Only render 20 points on the graph
const MARGIN = { top: 15, right: 15, bottom: 35, left: 60 };
const MARGIN = { top: 15, right: 20, bottom: 35, left: 60 };
/**
* Line Chart
@@ -47,7 +47,7 @@ export default class LineChart extends TranslatedComponent {
this._moveTip = this._moveTip.bind(this);
let markerElems = [];
let detailElems = [<text key={'lbl'} className='label x' y='1.25em'/>];
let detailElems = [<text key='lbl' className='text-tip x' y='1.25em'/>];
let xScale = d3.scale.linear();
let xAxisScale = d3.scale.linear();
let yScale = d3.scale.linear();
@@ -60,7 +60,7 @@ export default class LineChart extends TranslatedComponent {
for(let i = 0, l = series ? series.length : 1; i < l; i++) {
let yAccessor = series ? function(d) { return yScale(d[1][this]); }.bind(series[i]) : (d) => yScale(d[1]);
seriesLines.push(d3.svg.line().x((d) => xScale(d[0])).y(yAccessor));
detailElems.push(<text key={i} className='label y' y={1.25 * (i + 2) + 'em'}/>);
detailElems.push(<text key={i} className='text-tip y' y={1.25 * (i + 2) + 'em'}/>);
markerElems.push(<circle key={i} className='marker' r='4' />);
}
@@ -71,7 +71,7 @@ export default class LineChart extends TranslatedComponent {
seriesLines,
detailElems,
markerElems,
tipHeight: 2 + (1.25 * (series ? series.length : 0.75))
tipHeight: 2 + (1.2 * (series ? series.length : 0.8))
};
}
@@ -94,7 +94,7 @@ export default class LineChart extends TranslatedComponent {
xPos = xScale(x0); // Clamp xPos
tips.selectAll('text.label.y').text(function(d, i) {
tips.selectAll('text.text-tip.y').text(function(d, i) {
let yVal = series ? y0[series[i]] : y0;
yTotal += yVal;
return (series ? translate(series[i]) : '') + ' ' + formats.f2(yVal);
@@ -110,8 +110,8 @@ export default class LineChart extends TranslatedComponent {
tipWidth += 8;
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.x').text(formats.f2(x0)).append('tspan').attr('class', 'metric').text(' ' + xUnit);
tips.selectAll('text.text-tip').attr('x', flip ? -12 : 12).style('text-anchor', flip ? 'end' : 'start');
tips.selectAll('text.text-tip.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).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));
}

View File

@@ -7,6 +7,15 @@ import { shallowEqual } from '../utils/UtilityFunctions';
*/
export default class Link extends React.Component {
/**
* Constructor
* @param {Object} props React Component properties
*/
constructor(props) {
super(props);
this.handler = this.handler.bind(this);
}
/**
* Determine if a component should be rerendered
* @param {object} nextProps Next properties
@@ -21,18 +30,15 @@ export default class Link extends React.Component {
* @param {SyntheticEvent} event Event
*/
handler(event) {
if (event.getModifierState &&
(event.getModifierState('Shift') ||
event.getModifierState('Alt') ||
event.getModifierState('Control') ||
event.getModifierState('Meta') ||
event.button > 1)) {
if (event.shiftKey || event.altKey || event.ctrlKey || event.metaKey || event.button > 1) {
return;
}
event.nativeEvent && event.preventDefault && event.preventDefault();
event.preventDefault();
if (this.props.href) {
Router.go(encodeURI(this.props.href));
if (this.props.onClick) {
this.props.onClick(event);
} else if (this.props.href) {
Router.go(this.props.href);
}
}
@@ -41,8 +47,7 @@ export default class Link extends React.Component {
* @return {React.Component} A href element
*/
render() {
let action = this.handler.bind(this);
return <a {...this.props} onTouchTap={action}>{this.props.children}</a>;
return <a {...this.props} onClick={this.handler}>{this.props.children}</a>;
}
}

View File

@@ -1,6 +1,6 @@
import React from 'react';
import TranslatedComponent from './TranslatedComponent';
import { Ships } from 'coriolis-data';
import { Ships } from 'coriolis-data/dist';
import Persist from '../stores/Persist';
/**
@@ -96,39 +96,47 @@ export default class ModalCompare extends TranslatedComponent {
let translate = this.context.language.translate;
let availableBuilds = unusedBuilds.map((build, i) =>
<tr key={i} onTouchTap={this._addBuild.bind(this, i)}>
<tr key={i} onClick={this._addBuild.bind(this, i)}>
<td className='tl'>{build.name}</td>
<td className='tl'>{build.buildName}</td>
</tr>
);
let selectedBuilds = usedBuilds.map((build, i) =>
<tr key={i} onTouchTap={this._removeBuild.bind(this, i)}>
<tr key={i} onClick={this._removeBuild.bind(this, i)}>
<td className='tl'>{build.name}</td><
td className='tl'>{build.buildName}</td>
</tr>
);
return <div className='modal' onTouchTap={ (e) => e.stopPropagation() }>
return <div className='modal' onClick={ (e) => e.stopPropagation() }>
<h3>{translate('PHRASE_SELECT_BUILDS')}</h3>
<div id='build-select'>
<div className='build-section'>
<h1>{translate('available')}</h1>
<div>
<table>
<thead><tr><th colSpan='2'>{translate('available')}</th></tr></thead>
<tbody>
{availableBuilds}
</tbody>
</table>
</div>
</div>
<h1></h1>
<div className='build-section'>
<h1>{translate('added')}</h1>
<div>
<table>
<thead><tr><th colSpan='2'>{translate('added')}</th></tr></thead>
<tbody>
{selectedBuilds}
</tbody>
</table>
</div>
</div>
</div>
<br/>
<button className='cap' onTouchTap={this._selectBuilds.bind(this)}>{translate('Ok')}</button>
<button className='r cap' onTouchTap={() => this.context.hideModal()}>{translate('Cancel')}</button>
<button className='cap' onClick={this._selectBuilds.bind(this)}>{translate('Ok')}</button>
<button className='r cap' onClick={() => this.context.hideModal()}>{translate('Cancel')}</button>
</div>;
}
}

View File

@@ -22,11 +22,11 @@ export default class ModalDeleteAll extends TranslatedComponent {
render() {
let translate = this.context.language.translate;
return <div className='modal' onTouchTap={(e) => e.stopPropagation()}>
return <div className='modal' onClick={(e) => e.stopPropagation()}>
<h2>{translate('delete all')}</h2>
<p className='cen'>{translate('PHRASE_CONFIRMATION')}</p>
<button className='l cap' onTouchTap={this._deleteAll.bind(this)}>{translate('yes')}</button>
<button className='r cap' onTouchTap={this.context.hideModal}>{translate('no')}</button>
<button className='l cap' onClick={this._deleteAll.bind(this)}>{translate('yes')}</button>
<button className='r cap' onClick={this.context.hideModal}>{translate('no')}</button>
</div>;
}
}

View File

@@ -1,4 +1,5 @@
import React from 'react';
import { findDOMNode } from 'react-dom';
import TranslatedComponent from './TranslatedComponent';
/**
@@ -40,6 +41,17 @@ export default class ModalExport extends TranslatedComponent {
}
}
/**
* Focus on textarea and select all
*/
componentDidMount() {
let e = findDOMNode(this.refs.exportField);
if (e) {
e.focus();
e.select();
}
}
/**
* Render the modal
* @return {React.Component} Modal Content
@@ -52,13 +64,13 @@ export default class ModalExport extends TranslatedComponent {
description = <div>{translate(this.props.description)}</div>;
}
return <div className='modal' onTouchTap={ (e) => e.stopPropagation() }>
return <div className='modal' onClick={ (e) => e.stopPropagation() }>
<h2>{translate(this.props.title || 'Export')}</h2>
{description}
<div>
<textarea className='cb json' onFocus={ (e) => e.target.select() } readOnly value={this.state.exportJson} />
<textarea className='cb json' ref='exportField' readOnly value={this.state.exportJson} />
</div>
<button className='r dismiss cap' onTouchTap={this.context.hideModal}>{translate('close')}</button>
<button className='r dismiss cap' onClick={this.context.hideModal}>{translate('close')}</button>
</div>;
}
}

View File

@@ -1,13 +1,16 @@
import React from 'react';
import { findDOMNode } from 'react-dom';
import cn from 'classnames';
import TranslatedComponent from './TranslatedComponent';
import Router from '../Router';
import Persist from '../stores/Persist';
import { Ships } from 'coriolis-data';
import { Ships } from 'coriolis-data/dist';
import Ship from '../shipyard/Ship';
import { ModuleNameToGroup, Insurance } from '../shipyard/Constants';
import * as ModuleUtils from '../shipyard/ModuleUtils';
import { fromDetailedBuild } from '../shipyard/Serializer';
import { Download } from './SvgIcons';
import { outfitURL } from '../utils/UrlGenerators';
const textBuildRegex = new RegExp('^\\[([\\w \\-]+)\\]\n');
const lineRegex = new RegExp('^([\\dA-Z]{1,2}): (\\d)([A-I])[/]?([FGT])?([SD])? ([\\w\\- ]+)');
@@ -24,15 +27,6 @@ function isEmptySlot(slot) {
return slot.maxClass == this && slot.m === null;
}
/**
* Equal ignore case utility function. Must be bound to a string
* @param {string} str String
* @return {Boolean} True if equal
*/
function equalsIgnoreCase(str) {
return str.toLowerCase() == this.toLowerCase();
}
/**
* Determine if a build is valid
* @param {string} shipId Ship ID
@@ -273,7 +267,7 @@ export default class ModalImport extends TranslatedComponent {
let builds = {};
builds[shipId] = {};
builds[shipId]['Imported ' + buildName] = ship.toString();
this.setState({ builds });
this.setState({ builds, singleBuild: true });
}
/**
@@ -291,6 +285,7 @@ export default class ModalImport extends TranslatedComponent {
errorMsg: null,
importValid: false,
insurance: null,
singleBuild: false,
importString,
});
@@ -312,6 +307,7 @@ export default class ModalImport extends TranslatedComponent {
this._importDetailedArray(importData);
} else if (importData.ship && typeof importData.name !== undefined) { // Using JSON from a single ship build export
this._importDetailedArray([importData]); // Convert to array with singleobject
this.setState({ singleBuild: true });
} else { // Using Backup JSON
this._importBackup(importData);
}
@@ -330,6 +326,16 @@ export default class ModalImport extends TranslatedComponent {
_process() {
let builds = null, comparisons = null;
// If only importing a single build go straight to the outfitting page
if (this.state.singleBuild) {
builds = this.state.builds;
let shipId = Object.keys(builds)[0];
let name = Object.keys(builds[shipId])[0];
Router.go(outfitURL(shipId, builds[shipId][name], name));
return;
}
if (this.state.builds) {
builds = {}; // Create new builds object such that orginal name retained, but can be renamed
for (let shipId in this.state.builds) {
@@ -412,6 +418,14 @@ export default class ModalImport extends TranslatedComponent {
this._process();
}
}
/**
* If textarea is shown focus on mount
*/
componentDidMount() {
if (!this.props.builds && findDOMNode(this.refs.importField)) {
findDOMNode(this.refs.importField).focus();
}
}
/**
* Render the import modal
@@ -425,7 +439,7 @@ export default class ModalImport extends TranslatedComponent {
if (!state.processed) {
importStage = (
<div>
<textarea className='cb json' onChange={this._validateImport} defaultValue={this.state.importString} placeholder={translate('PHRASE_IMPORT')} />
<textarea className='cb json' ref='importField' onChange={this._validateImport} defaultValue={this.state.importString} placeholder={translate('PHRASE_IMPORT')} />
<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>
@@ -508,7 +522,7 @@ export default class ModalImport extends TranslatedComponent {
);
}
return <div className='modal' onTouchTap={ (e) => e.stopPropagation() } onClick={ (e) => e.stopPropagation() }>
return <div className='modal' onClick={ (e) => e.stopPropagation() } onClick={ (e) => e.stopPropagation() }>
<h2 >{translate('import')}</h2>
{importStage}
<button className={'r dismiss cap'} onClick={this.context.hideModal}>{translate('close')}</button>

View File

@@ -40,7 +40,7 @@ export default class ModalPermalink extends TranslatedComponent {
render() {
let translate = this.context.language.translate;
return <div className='modal' onTouchTap={ (e) => e.stopPropagation() }>
return <div className='modal' onClick={ (e) => e.stopPropagation() }>
<h2>{translate('permalink')}</h2>
<br/>
<h3>{translate('URL')}</h3>
@@ -49,7 +49,7 @@ export default class ModalPermalink extends TranslatedComponent {
<h3 >{translate('shortened')}</h3>
<input value={this.state.shortenedUrl} readOnly size={25} onFocus={ (e) => e.target.select() }/>
<br/><br/>
<button className={'r dismiss cap'} onTouchTap={this.context.hideModal}>{translate('close')}</button>
<button className={'r dismiss cap'} onClick={this.context.hideModal}>{translate('close')}</button>
</div>;
}
}

View File

@@ -211,7 +211,7 @@ export default class PowerBands extends TranslatedComponent {
height={state.barHeight}
x={Math.floor(Math.max(wattScale(b.retractedSum) - wattScale(b.retracted), 0))}
y={1}
onTouchTap={this._selectRet.bind(this, i)}
onClick={this._selectRet.bind(this, i)}
className={getClass(ret[i], b.retractedSum, available)}
/>);
@@ -223,7 +223,7 @@ export default class PowerBands extends TranslatedComponent {
height={state.barHeight}
x={wattScale(b.retractedSum) - (wattScale(b.retracted) / 2)}
y={state.retY}
onTouchTap={this._selectRet.bind(this, i)}
onClick={this._selectRet.bind(this, i)}
className='primary-bg'>{retLbl}</text>
);
}
@@ -238,7 +238,7 @@ export default class PowerBands extends TranslatedComponent {
height={state.barHeight}
x={Math.floor(Math.max(wattScale(b.deployedSum) - wattScale(b.retracted) - wattScale(b.deployed), 0))}
y={state.barHeight + 1}
onTouchTap={this._selectDep.bind(this, i)}
onClick={this._selectDep.bind(this, i)}
className={getClass(dep[i], b.deployedSum, available)}
/>);
@@ -250,7 +250,7 @@ export default class PowerBands extends TranslatedComponent {
height={state.barHeight}
x={wattScale(b.deployedSum) - ((wattScale(b.retracted) + wattScale(b.deployed)) / 2)}
y={state.depY}
onTouchTap={this._selectDep.bind(this, i)}
onClick={this._selectDep.bind(this, i)}
className='primary-bg'>{depLbl}</text>
);
}

View File

@@ -119,23 +119,23 @@ export default class PowerManagement extends TranslatedComponent {
let retractedElem = null, deployedElem = null;
if (slot.enabled) {
retractedElem = <td className='ptr upp' onTouchTap={toggleEnabled}>{POWER[ship.getSlotStatus(slot, false)]}</td>;
deployedElem = <td className='ptr upp' onTouchTap={toggleEnabled}>{POWER[ship.getSlotStatus(slot, true)]}</td>;
retractedElem = <td className='ptr upp' onClick={toggleEnabled}>{POWER[ship.getSlotStatus(slot, false)]}</td>;
deployedElem = <td className='ptr upp' onClick={toggleEnabled}>{POWER[ship.getSlotStatus(slot, true)]}</td>;
} else {
retractedElem = <td className='ptr disabled upp' colSpan='2' onTouchTap={toggleEnabled}>{translate('disabled')}</td>;
retractedElem = <td className='ptr disabled upp' colSpan='2' onClick={toggleEnabled}>{translate('disabled')}</td>;
}
powerRows.push(<tr key={i} className={cn('highlight', { disabled: !slot.enabled })}>
<td className='ptr' style={{ width: '1em' }} onTouchTap={toggleEnabled}>{m.class + m.rating}</td>
<td className='ptr le shorten cap' onTouchTap={toggleEnabled}>{slotName(translate, slot)}</td>
<td className='ptr' onTouchTap={toggleEnabled}><u>{translate(slot.type)}</u></td>
<td className='ptr' style={{ width: '1em' }} onClick={toggleEnabled}>{m.class + m.rating}</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>
<span className='flip ptr btn' onTouchTap={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) + ' '}
<span className='ptr btn' onTouchTap={this._priority.bind(this, slot, 1)}>&#9658;</span>
<span className='ptr btn' onClick={this._priority.bind(this, slot, 1)}>&#9658;</span>
</td>
<td className='ri ptr' style={{ width: '3.25em' }} onTouchTap={toggleEnabled}>{pwr(m.power)}</td>
<td className='ri ptr' style={{ width: '3em' }} onTouchTap={toggleEnabled}><u>{pct(m.power / ship.powerAvailable)}</u></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>
{retractedElem}
{deployedElem}
</tr>);
@@ -200,12 +200,12 @@ export default class PowerManagement extends TranslatedComponent {
<table style={{ width: '100%' }}>
<thead>
<tr className='main'>
<th colSpan='2' className='sortable le' onTouchTap={sortOrder.bind(this, 'n')} >{translate('module')}</th>
<th style={{ width: '3em' }} className='sortable' onTouchTap={sortOrder.bind(this, 't')} >{translate('type')}</th>
<th style={{ width: '4em' }} className='sortable' onTouchTap={sortOrder.bind(this, 'pri')} >{translate('pri')}</th>
<th colSpan='2' className='sortable' onTouchTap={sortOrder.bind(this, 'pwr')} >{translate('PWR')}</th>
<th style={{ width: '3em' }} className='sortable' onTouchTap={sortOrder.bind(this, 'r')} >{translate('ret')}</th>
<th style={{ width: '3em' }} className='sortable' onTouchTap={sortOrder.bind(this, 'd')} >{translate('dep')}</th>
<th colSpan='2' className='sortable le' onClick={sortOrder.bind(this, 'n')} >{translate('module')}</th>
<th style={{ width: '3em' }} className='sortable' onClick={sortOrder.bind(this, 't')} >{translate('type')}</th>
<th style={{ width: '4em' }} className='sortable' onClick={sortOrder.bind(this, 'pri')} >{translate('pri')}</th>
<th colSpan='2' className='sortable' onClick={sortOrder.bind(this, 'pwr')} >{translate('PWR')}</th>
<th style={{ width: '3em' }} className='sortable' onClick={sortOrder.bind(this, 'r')} >{translate('ret')}</th>
<th style={{ width: '3em' }} className='sortable' onClick={sortOrder.bind(this, 'd')} >{translate('dep')}</th>
</tr>
</thead>
<tbody>

View File

@@ -24,8 +24,11 @@ export default class ShipSummaryTable extends TranslatedComponent {
let u = language.units;
let formats = language.formats;
let round = formats.round;
let int = formats.int;
let { time, int } = formats;
let armourDetails = null;
let sgClassNames = cn({ warning: ship.sgSlot && !ship.shieldStrength, muted: !ship.sgSlot });
let sgRecover = '-';
let sgRecharge = '-';
let hide = tooltip.bind(null, null);
if (ship.armourMultiplier > 1 || ship.armourAdded) {
@@ -35,6 +38,11 @@ export default class ShipSummaryTable extends TranslatedComponent {
})</u>;
}
if (ship.shieldStrength) {
sgRecover = time(ship.calcShieldRecovery());
sgRecharge = time(ship.calcShieldRecharge());
}
return <div id='summary'>
<table id='summaryTable'>
<thead>
@@ -43,20 +51,23 @@ export default class ShipSummaryTable extends TranslatedComponent {
<th onMouseEnter={termtip.bind(null, 'maneuverability')} onMouseLeave={hide} rowSpan={2}>{translate('MNV')}</th>
<th rowSpan={2} className={ cn({ 'bg-warning-disabled': !ship.canThrust() }) }>{translate('speed')}</th>
<th rowSpan={2} className={ cn({ 'bg-warning-disabled': !ship.canBoost() }) }>{translate('boost')}</th>
<th onMouseOver={termtip.bind(null, 'damage per second')} onMouseOut={hide} rowSpan={2}>{translate('DPS')}</th>
<th onMouseEnter={termtip.bind(null, 'damage per second')} onMouseLeave={hide} rowSpan={2}>{translate('DPS')}</th>
<th rowSpan={2}>{translate('armour')}</th>
<th rowSpan={2}>{translate('shields')}</th>
<th colSpan={3}>{translate('shields')}</th>
<th colSpan={3}>{translate('mass')}</th>
<th rowSpan={2}>{translate('cargo')}</th>
<th rowSpan={2}>{translate('fuel')}</th>
<th colSpan={3}>{translate('jump range')}</th>
<th colSpan={3}>{translate('total range')}</th>
<th onMouseOver={termtip.bind(null, 'mass lock factor')} onMouseOut={hide} rowSpan={2}>{translate('MLF')}</th>
<th onMouseEnter={termtip.bind(null, 'PHRASE_FASTEST_RANGE')} onMouseLeave={hide} colSpan={3}>{translate('fastest range')}</th>
<th onMouseEnter={termtip.bind(null, 'mass lock factor')} onMouseLeave={hide} rowSpan={2}>{translate('MLF')}</th>
</tr>
<tr>
<th className='lft'>{translate('strength')}</th>
<th onMouseEnter={termtip.bind(null, 'PHRASE_SG_RECOVER', { cap: 0 })} onMouseLeave={hide}>{translate('recover')}</th>
<th onMouseEnter={termtip.bind(null, 'PHRASE_SG_RECHARGE', { cap: 0 })} onMouseLeave={hide}>{translate('recharge')}</th>
<th className='lft'>{translate('hull')}</th>
<th>{translate('unladen')}</th>
<th>{translate('laden')}</th>
<th onMouseEnter={termtip.bind(null, 'PHRASE_UNLADEN', { cap: 0 })} onMouseLeave={hide}>{translate('unladen')}</th>
<th onMouseEnter={termtip.bind(null, 'PHRASE_LADEN', { cap: 0 })} onMouseLeave={hide}>{translate('laden')}</th>
<th className='lft'>{translate('max')}</th>
<th>{translate('full tank')}</th>
<th>{translate('laden')}</th>
@@ -73,7 +84,9 @@ export default class ShipSummaryTable extends TranslatedComponent {
<td>{ ship.canBoost() ? <span>{int(ship.topBoost)} {u['m/s']}</span> : <span className='warning'>0 <Warning/></span> }</td>
<td>{round(ship.totalDps)}</td>
<td>{int(ship.armour)} {armourDetails}</td>
<td>{int(ship.shieldStrength)} {u.MJ} { ship.shieldMultiplier > 1 && ship.shieldStrength > 0 ? <u>({formats.rPct(ship.shieldMultiplier)})</u> : null }</td>
<td className={sgClassNames}>{int(ship.shieldStrength)} {u.MJ} { ship.shieldMultiplier > 1 && ship.shieldStrength > 0 ? <u>({formats.rPct(ship.shieldMultiplier)})</u> : null }</td>
<td className={sgClassNames}>{sgRecover}</td>
<td className={sgClassNames}>{sgRecharge}</td>
<td>{ship.hullMass} {u.T}</td>
<td>{round(ship.unladenMass)} {u.T}</td>
<td>{round(ship.ladenMass)} {u.T}</td>
@@ -83,8 +96,8 @@ export default class ShipSummaryTable extends TranslatedComponent {
<td>{round(ship.fullTankRange)} {u.LY}</td>
<td>{round(ship.ladenRange)} {u.LY}</td>
<td>{round(ship.maxJumpCount)}</td>
<td>{round(ship.unladenTotalRange)} {u.LY}</td>
<td>{round(ship.ladenTotalRange)} {u.LY}</td>
<td>{round(ship.unladenFastestRange)} {u.LY}</td>
<td>{round(ship.ladenFastestRange)} {u.LY}</td>
<td>{ship.masslock}</td>
</tr>
</tbody>

View File

@@ -98,7 +98,7 @@ export default class Slider extends React.Component {
*/
_updateDimensions() {
this.setState({
outerWidth: findDOMNode(this).offsetWidth
outerWidth: findDOMNode(this).getBoundingClientRect().width
});
}

View File

@@ -61,6 +61,8 @@ export default class Slot extends TranslatedComponent {
* @param {SyntheticEvent} event Event
*/
_contextMenu(event) {
event.stopPropagation();
event.preventDefault();
this.props.onSelect(null,null);
}
@@ -95,7 +97,7 @@ export default class Slot extends TranslatedComponent {
// TODO: implement touch dragging
return (
<div className={cn('slot', dropClass, { selected })} onTouchTap={onOpen} onContextMenu={this._contextMenu} onDragOver={dragOver}>
<div className={cn('slot', dropClass, { selected })} onClick={onOpen} onContextMenu={this._contextMenu} onDragOver={dragOver}>
<div className='details-container'>
<div className='sz'>{this._getMaxClassLabel(translate)}</div>
{slotDetails}

View File

@@ -177,7 +177,7 @@ export default class SlotSection extends TranslatedComponent {
return (
<div id={this.sectionId} className={'group'} onDragLeave={this._dragOverNone}>
<div className={cn('section-menu', { selected: sectionMenuOpened })} onTouchTap={open} onContextMenu={ctx}>
<div className={cn('section-menu', { selected: sectionMenuOpened })} onClick={open} onContextMenu={ctx}>
<h1>{translate(this.sectionName)} <Equalizer/></h1>
{sectionMenuOpened ? this._getSectionMenu(translate) : null }
</div>

View File

@@ -43,7 +43,7 @@ export default class StandardSlot extends TranslatedComponent {
}
return (
<div className={cn('slot', { selected: this.props.selected })} onTouchTap={this.props.onOpen}>
<div className={cn('slot', { selected: this.props.selected })} onClick={this.props.onOpen}>
<div className={cn('details-container', { warning: warning && warning(slot.m) })}>
<div className={'sz'}>{slot.maxClass}</div>
<div>

View File

@@ -172,7 +172,7 @@ export default class StandardSlotSection extends SlotSection {
let bh = ship.bulkheads;
slots[0] = (
<div key='bh' className={cn('slot', { selected: currentMenu === bh })} onTouchTap={open.bind(this, bh)}>
<div key='bh' className={cn('slot', { selected: currentMenu === bh })} onClick={open.bind(this, bh)}>
<div className={'details-container'}>
<div className={'details'}>
<div className={'sz'}>8</div>
@@ -184,21 +184,21 @@ export default class StandardSlotSection extends SlotSection {
</div>
</div>
{currentMenu === bh &&
<div className='select' onTouchTap={ e => e.stopPropagation() }>
<div className='select' onClick={ e => e.stopPropagation() }>
<ul>
<li onTouchTap={selBulkhead.bind(this, 0)} onMouseOver={this._bhDiff.bind(this, 0)} onMouseLeave={this._hideDiff} className={cn('lc', { active: bh.index == 0 })}>
<li onClick={selBulkhead.bind(this, 0)} onMouseOver={this._bhDiff.bind(this, 0)} onMouseLeave={this._hideDiff} className={cn('lc', { active: bh.index == 0 })}>
{translate('Lightweight Alloy')}
</li>
<li onTouchTap={selBulkhead.bind(this, 1)} onMouseOver={this._bhDiff.bind(this, 1)} onMouseLeave={this._hideDiff} className={cn('lc', { active: bh.index == 1 })}>
<li onClick={selBulkhead.bind(this, 1)} onMouseOver={this._bhDiff.bind(this, 1)} onMouseLeave={this._hideDiff} className={cn('lc', { active: bh.index == 1 })}>
{translate('Reinforced Alloy')}
</li>
<li onTouchTap={selBulkhead.bind(this, 2)} onMouseOver={this._bhDiff.bind(this, 2)} onMouseLeave={this._hideDiff} className={cn('lc', { active: bh.index == 2 })}>
<li onClick={selBulkhead.bind(this, 2)} onMouseOver={this._bhDiff.bind(this, 2)} onMouseLeave={this._hideDiff} className={cn('lc', { active: bh.index == 2 })}>
{translate('Military Grade Composite')}
</li>
<li onTouchTap={selBulkhead.bind(this, 3)} onMouseOver={this._bhDiff.bind(this, 3)} onMouseLeave={this._hideDiff} className={cn('lc', { active: bh.index == 3 })}>
<li onClick={selBulkhead.bind(this, 3)} onMouseOver={this._bhDiff.bind(this, 3)} onMouseLeave={this._hideDiff} className={cn('lc', { active: bh.index == 3 })}>
{translate('Mirrored Surface Composite')}
</li>
<li onTouchTap={selBulkhead.bind(this, 4)} onMouseOver={this._bhDiff.bind(this, 4)} onMouseLeave={this._hideDiff} className={cn('lc', { active: bh.index == 4 })}>
<li onClick={selBulkhead.bind(this, 4)} onMouseOver={this._bhDiff.bind(this, 4)} onMouseLeave={this._hideDiff} className={cn('lc', { active: bh.index == 4 })}>
{translate('Reactive Surface Composite')}
</li>
</ul>
@@ -294,19 +294,19 @@ export default class StandardSlotSection extends SlotSection {
_getSectionMenu(translate) {
let _fill = this._fill;
return <div className='select' onTouchTap={(e) => e.stopPropagation()} onContextMenu={stopCtxPropagation}>
return <div className='select' onClick={(e) => e.stopPropagation()} onContextMenu={stopCtxPropagation}>
<ul>
<li className='lc' onTouchTap={this._optimizeStandard}>{translate('Optimize')}</li>
<li className='c' onTouchTap={_fill.bind(this, 'E')}>E</li>
<li className='c' onTouchTap={_fill.bind(this, 'D')}>D</li>
<li className='c' onTouchTap={_fill.bind(this, 'C')}>C</li>
<li className='c' onTouchTap={_fill.bind(this, 'B')}>B</li>
<li className='c' onTouchTap={_fill.bind(this, 'A')}>A</li>
<li className='lc' onClick={this._optimizeStandard}>{translate('Optimize')}</li>
<li className='c' onClick={_fill.bind(this, 'E')}>E</li>
<li className='c' onClick={_fill.bind(this, 'D')}>D</li>
<li className='c' onClick={_fill.bind(this, 'C')}>C</li>
<li className='c' onClick={_fill.bind(this, 'B')}>B</li>
<li className='c' onClick={_fill.bind(this, 'A')}>A</li>
</ul>
<div className='select-group cap'>{translate('builds / roles')}</div>
<ul>
<li className='lc' onTouchTap={this._optimizeCargo}>{translate('Trader')}</li>
<li className='lc' onTouchTap={this._optimizeExplorer}>{translate('Explorer')}</li>
<li className='lc' onClick={this._optimizeCargo}>{translate('Trader')}</li>
<li className='lc' onClick={this._optimizeExplorer}>{translate('Explorer')}</li>
</ul>
</div>;
}

View File

@@ -91,21 +91,21 @@ export default class UtilitySlotSection extends SlotSection {
_getSectionMenu(translate) {
let _use = this._use;
return <div className='select' onTouchTap={(e) => e.stopPropagation()} onContextMenu={stopCtxPropagation}>
return <div className='select' onClick={(e) => e.stopPropagation()} onContextMenu={stopCtxPropagation}>
<ul>
<li className='lc' onTouchTap={this._empty}>{translate('empty all')}</li>
<li className='lc' onClick={this._empty}>{translate('empty all')}</li>
</ul>
<div className='select-group cap'>{translate('sb')}</div>
<ul>
<li className='c' onTouchTap={_use.bind(this, 'sb', 'E', null)}>E</li>
<li className='c' onTouchTap={_use.bind(this, 'sb', 'D', null)}>D</li>
<li className='c' onTouchTap={_use.bind(this, 'sb', 'C', null)}>C</li>
<li className='c' onTouchTap={_use.bind(this, 'sb', 'B', null)}>B</li>
<li className='c' onTouchTap={_use.bind(this, 'sb', 'A', null)}>A</li>
<li className='c' onClick={_use.bind(this, 'sb', 'E', null)}>E</li>
<li className='c' onClick={_use.bind(this, 'sb', 'D', null)}>D</li>
<li className='c' onClick={_use.bind(this, 'sb', 'C', null)}>C</li>
<li className='c' onClick={_use.bind(this, 'sb', 'B', null)}>B</li>
<li className='c' onClick={_use.bind(this, 'sb', 'A', null)}>A</li>
</ul>
<div className='select-group cap'>{translate('cm')}</div>
<ul>
<li className='lc' onTouchTap={_use.bind(this, 'cm', null, 'Heat Sink Launcher')}>{translate('Heat Sink Launcher')}</li>
<li className='lc' onClick={_use.bind(this, 'cm', null, 'Heat Sink Launcher')}>{translate('Heat Sink Launcher')}</li>
</ul>
</div>;
}

View File

@@ -22,6 +22,11 @@ export const terms = {
PHRASE_NO_RETROCH: 'No Retrofitting changes',
PHRASE_SELECT_BUILDS: 'Select Builds to Compare',
PHRASE_UPDATE_RDY: 'Update Available! Click to Refresh',
PHRASE_UNLADEN: 'Ship Mass excluding Fuel and Cargo',
PHRASE_LADEN: 'Ship Mass + Fuel + Cargo',
PHRASE_FASTEST_RANGE: 'Consecutive max range jumps',
PHRASE_SG_RECOVER: 'Recovery (to 50%) after collapse',
PHRASE_SG_RECHARGE: 'Time from 50% to 100% Charge',
am: 'Auto Field-Maintenance Unit',
'Basic Discovery Scanner': 'Basic Discovery Scanner',
bl: 'Beam Laser',

View File

@@ -3,9 +3,10 @@ import React from 'react';
import { render } from 'react-dom';
import '../less/app.less';
import Coriolis from './Coriolis';
import TapEventPlugin from 'react/lib/TapEventPlugin';
import EventPluginHub from 'react/lib/EventPluginHub';
// import TapEventPlugin from 'react/lib/TapEventPlugin';
// import EventPluginHub from 'react/lib/EventPluginHub';
EventPluginHub.injection.injectEventPluginsByName({ TapEventPlugin });
// onTouchTap not ready for primetime yet, too many issues with preventing default
// EventPluginHub.injection.injectEventPluginsByName({ TapEventPlugin });
render(<Coriolis />, document.getElementById('coriolis'));

View File

@@ -22,11 +22,10 @@ export default class AboutPage extends Page {
* Render the Page
* @return {React.Component} The page contents
*/
render() {
renderPage() {
return <div className={'page'} style={{ textAlign: 'left', maxWidth: 800, margin: '0 auto' }}>
<h1><CoriolisLogo style={{ marginRight: '0.4em' }} className='xl'/><span className='warning'>Coriolis</span></h1>
<p>The Coriolis project was inspired by <a href='http://www.edshipyard.com/' target='_blank'>E:D Shipyard</a> and, of course,
<a href='http://www.elitedangerous.com' target='_blank'>Elite Dangerous</a>. 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.</p>
<p>The Coriolis project was inspired by <a href='http://www.edshipyard.com/' target='_blank'>E:D Shipyard</a> and, of course, <a href='http://www.elitedangerous.com' target='_blank'>Elite Dangerous</a>. 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.</p>
<p>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.</p>
@@ -36,8 +35,7 @@ export default class AboutPage extends Page {
github.com/cmmcleod/coriolis
</a>
<p>Coriolis is an open source project. Checkout the list of upcoming features and to-do list on github.
Any and all contributions and feedback are welcome. If you encounter any bugs please report them and provide as much detail as possible.</p>
<p>Coriolis is an open source project. Checkout the list of upcoming features and to-do list on github. 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='_blank'>
<input type='hidden' name='cmd' value='_s-xclick' />
@@ -47,6 +45,9 @@ export default class AboutPage extends Page {
</form>
<p>Help keep the lights on! Donations will be used to cover costs of running and maintaining Coriolis. Thanks for helping!</p>
<h3>Chat</h3>
<p>You can chat to us on <a href='https://www.hipchat.com/gfYQiZcmy' target='_blank'>HipChat</a> and sign up to chat on all E:D HipChat groups <a href='https://elite-dangerous.hipchat.com/invite/74670/a3af7ea57008362d83d05fada09bdf84' target='_blank'>here</a>.</p>
</div>;
}
}

View File

@@ -3,7 +3,7 @@ import { findDOMNode } from 'react-dom';
import Page from './Page';
import Router from '../Router';
import cn from 'classnames';
import { Ships } from 'coriolis-data';
import { Ships } from 'coriolis-data/dist';
import Ship from '../shipyard/Ship';
import { fromComparison, toComparison } from '../shipyard/Serializer';
import Persist from '../stores/Persist';
@@ -418,7 +418,7 @@ export default class ComparisonPage extends Page {
* Render the Page
* @return {React.Component} The page contents
*/
render() {
renderPage() {
let translate = this.context.language.translate;
let compareHeader;
let { newName, name, saved, builds, facets, predicate, desc, chartWidth } = this.state;
@@ -428,17 +428,17 @@ export default class ComparisonPage extends Page {
<td className='head'>{translate('comparison')}</td>
<td>
<input value={newName} onChange={this._onNameChange} placeholder={translate('Enter Name')} maxLength='50' />
<button onTouchTap={this._save} disabled={!newName || newName == 'all' || saved}>
<button onClick={this._save} disabled={!newName || newName == 'all' || saved}>
<FloppyDisk className='lg'/><span className='button-lbl'>{translate('save')}</span>
</button>
<button onTouchTap={this._delete} disabled={name == 'all' || !saved}><Bin className='lg warning'/></button>
<button onTouchTap={this._selectBuilds}>
<button onClick={this._delete} disabled={name == 'all' || !saved}><Bin className='lg warning'/></button>
<button onClick={this._selectBuilds}>
<Rocket className='lg'/><span className='button-lbl'>{translate('builds')}</span>
</button>
<button className='r' onTouchTap={this._genPermalink} disabled={builds.length == 0}>
<button className='r' onClick={this._genPermalink} disabled={builds.length == 0}>
<LinkIcon className='lg'/><span className='button-lbl'>{translate('permalink')}</span>
</button>
<button className='r' onTouchTap={this._genBBcode} disabled={builds.length == 0}>
<button className='r' onClick={this._genBBcode} disabled={builds.length == 0}>
<Embed className='lg'/><span className='button-lbl'>{translate('forum')}</span>
</button>
</td>
@@ -448,7 +448,7 @@ export default class ComparisonPage extends Page {
<td className='head'>{translate('comparison')}</td>
<td>
<h3>{name}</h3>
<button className='r' onTouchTap={this._import}><Download className='lg'/>{translate('import')}</button>
<button className='r' onClick={this._import}><Download className='lg'/>{translate('import')}</button>
</td>
</tr>;
}
@@ -463,7 +463,7 @@ export default class ComparisonPage extends Page {
<td>
<ul id='facet-container' onDragOver={this._facetDragOver}>
{facets.map((f, i) =>
<li key={f.title} data-i={i} draggable='true' onDragStart={this._facetDrag} onDragEnd={this._facetDrop} className={cn('facet', { active: f.active })} onTouchTap={this._toggleFacet.bind(this, f)}>
<li key={f.title} data-i={i} draggable='true' onDragStart={this._facetDrag} onDragEnd={this._facetDrop} className={cn('facet', { active: f.active })} onClick={this._toggleFacet.bind(this, f)}>
{'↔ ' + translate(f.title)}
</li>
)}
@@ -479,14 +479,15 @@ export default class ComparisonPage extends Page {
<div className='chart' ref={'chartRef'}>{translate('PHRASE_NO_BUILDS')}</div> :
facets.filter((f) => f.active).map((f, i) =>
<div key={f.title} className='chart' ref={ i == 0 ? 'chartRef' : null}>
<h3 className='ptr' onTouchTap={this._sortShips.bind(this, f.props[0])}>{translate(f.title)}</h3>
<h3 className='ptr' onClick={this._sortShips.bind(this, f.props[0])}>{translate(f.title)}</h3>
<BarChart
width={chartWidth}
data={builds}
properties={f.props}
labels={f.lbls}
unit={translate(f.unit)}
format={f.fmt}
label={translate(f.title)}
title={translate(f.title)}
predicate={predicate}
desc={desc}
/>

View File

@@ -0,0 +1,48 @@
import React from 'react';
/**
* Unexpected Error page / block
*/
export default class ErrorDetails extends React.Component {
static contextTypes = {
route: React.PropTypes.object.isRequired,
language: React.PropTypes.object.isRequired
};
static propTypes = {
error: React.PropTypes.object.isRequired
};
/**
* Render the Page
* @return {React.Component} The page contents
*/
render() {
let content = null;
let error = this.props.error;
let ed = error.details;
if (ed) {
content = <div style={{ textAlign:'left', fontSize:'0.8em', width: '43em', margin: '0 auto' }}>
<div className='cen'>
<a href='https://github.com/cmmcleod/coriolis/issues' target='_blank' title='Coriolis Github Project'>Create an issue on Github</a>
{' if this keeps happening. Add these details:'}
</div>
<div style={{ marginTop: '2em' }}>
<div><span className='warning'>Browser:</span> {window.navigator.userAgent}</div>
<div><span className='warning'>Path:</span> {this.context.route.canonicalPath}</div>
<div><span className='warning'>Error:</span> {error.type || 'Unknown'}</div>
<div className='warning'>Details:</div>
<div><pre>{typeof ed == 'object' ? Object.keys(ed).map((e) => `${e}: ${ed[e]}\n`) : ed}</pre></div>
</div>
</div>;
}
return <div className='error'>
<h1>Jameson, we have a problem..</h1>
<h1><small>{error.message}</small></h1>
{content}
</div>;
}
}

View File

@@ -1,73 +0,0 @@
import React from 'react';
import Page from './Page';
/**
* Unexpected Error page
* TODO: Implement properly and test
*/
export default class ErrorPage extends Page {
/**
* Constructor
* @param {Object} props React Component properties
*/
constructor(props) {
super(props);
this.state = {
title: 'Error!'
};
}
/**
* Render the Page
* @return {React.Component} The page contents
*/
render() {
let msgPre, msgHighlight, msgPost, errorMessage, details, type;
switch (type) {
case 404:
msgPre = 'Page';
msgHighlight = this.context.route.path;
msgPost = 'Not Found';
break;
case 'no-ship':
msgPre = 'Ship';
msgHighlight = this.props.message;
msgPost = 'does not exist';
break;
case 'build':
msgPre = 'Build Failure!';
break;
default:
msgPre = 'Uh, Jameson, we have a problem..';
errorMessage = <div>Message:<pre>{this.props.message}</pre></div>;
}
if (this.props.details) {
details = <div>Details:<br/><pre>{this.props.details}</pre></div>;
}
return <div className='error'>
<h1>
<span>{msgPre}</span>
<small>{msgHighlight}</small>
<span>{msgPost}</span>
</h1>
<div style={{ textAlign:'left', fontSize:'0.8em', width: '43em', margin: '0 auto' }}>
<div className='cen'>
<a href='https://github.com/cmmcleod/coriolis/issues' target='_blank' title='Coriolis Github Project'>Create an issue on Github</a>
if this keeps happening. Add these details:
</div>
<div style={{ marginTop: '2em' }}>
<div>Browser: {window.navigator.userAgent}</div>
<div>Path: {this.context.route.canonicalPath}</div>
<div>Error:<br/>{this.props.type || 'Unknown'}</div>
{errorMessage}
{details}
</div>
</div>
</div>;
}
}

View File

@@ -21,7 +21,7 @@ export default class NotFoundPage extends Page {
* Render the Page
* @return {React.Component} The page contents
*/
render() {
return <div className={'page'}>Page {JSON.stringify(this.props.context)} Not Found</div>;
renderPage() {
return <div className='page' style={{ marginTop: 30 }}>Page <small>{this.context.route.path}</small> Not Found</div>;
}
}

View File

@@ -1,12 +1,14 @@
import React from 'react';
import { findDOMNode } from 'react-dom';
import Page from './Page';
import { Ships } from 'coriolis-data/dist';
import cn from 'classnames';
import Page from './Page';
import Router from '../Router';
import Persist from '../stores/Persist';
import { Ships } from 'coriolis-data';
import Ship from '../shipyard/Ship';
import { toDetailedBuild } from '../shipyard/Serializer';
import { outfitURL } from '../utils/UrlGenerators';
import { FloppyDisk, Bin, Switch, Download, Reload, Fuel } from '../components/SvgIcons';
import ShipSummaryTable from '../components/ShipSummaryTable';
import StandardSlotSection from '../components/StandardSlotSection';
@@ -51,9 +53,7 @@ export default class OutfittingPage extends Page {
let savedCode = Persist.getBuild(shipId, buildName);
if (!data) {
// TODO: throw Error for error page - Ship not found
// Router.errorPage(details) - something along these lines
throw { msg: 'Ship not found:' + shipId };
return { error: { message: 'Ship not found: ' + shipId } };
}
let ship = new Ship(shipId, data.properties, data.slots); // Create a new Ship instance
@@ -67,6 +67,7 @@ export default class OutfittingPage extends Page {
let fuelCapacity = ship.fuelCapacity;
return {
error: null,
title: 'Outfitting - ' + data.properties.name,
costTab: Persist.getCostTab() || 'costs',
buildName,
@@ -77,7 +78,7 @@ export default class OutfittingPage extends Page {
fuelCapacity,
fuelLevel: 1,
jumpRangeChartFunc: ship.calcJumpRangeWith.bind(ship, fuelCapacity),
totalRangeChartFunc: ship.calcFastestRangeWith.bind(ship, fuelCapacity),
fastestRangeChartFunc: ship.calcFastestRangeWith.bind(ship, fuelCapacity),
speedChartFunc: ship.calcSpeedsWith.bind(ship, fuelCapacity)
};
}
@@ -106,6 +107,7 @@ export default class OutfittingPage extends Page {
_saveBuild() {
let code = this.state.ship.toString();
Persist.saveBuild(this.state.shipId, this.state.buildName, code);
this._updateRoute(this.state.shipId, code, this.state.buildName);
this.setState({ code, savedCode: code });
}
@@ -130,7 +132,7 @@ export default class OutfittingPage extends Page {
*/
_deleteBuild() {
Persist.deleteBuild(this.state.shipId, this.state.buildName);
Router.go(`/outfit/${this.state.shipId}`);
Router.go(outfitURL(this.state.shipId));
}
/**
@@ -168,13 +170,7 @@ export default class OutfittingPage extends Page {
* @param {string} buildName Current build name
*/
_updateRoute(shipId, code, buildName) {
let qStr = '';
if (buildName) {
qStr = '?bn=' + encodeURIComponent(buildName);
}
Router.replace(`/outfit/${shipId}/${code}${qStr}`);
Router.replace(outfitURL(shipId, code, buildName));
}
/**
@@ -189,7 +185,7 @@ export default class OutfittingPage extends Page {
fuelLevel,
fuelCapacity,
jumpRangeChartFunc: ship.calcJumpRangeWith.bind(ship, fuel),
totalRangeChartFunc: ship.calcFastestRangeWith.bind(ship, fuel),
fastestRangeChartFunc: ship.calcFastestRangeWith.bind(ship, fuel),
speedChartFunc: ship.calcSpeedsWith.bind(ship, fuel)
});
}
@@ -198,10 +194,14 @@ export default class OutfittingPage extends Page {
* Update dimenions from rendered DOM
*/
_updateDimensions() {
let elem = findDOMNode(this.refs.chartThird);
if (elem) {
this.setState({
chartWidth: findDOMNode(this.refs.chartThird).offsetWidth
});
}
}
/**
* Update state based on context changes
@@ -239,17 +239,19 @@ export default class OutfittingPage extends Page {
* Render the Page
* @return {React.Component} The page contents
*/
render() {
let state = this.state;
let { language, termtip, tooltip, sizeRatio, onWindowResize } = this.context;
let { translate, units, formats } = language;
let { ship, code, savedCode, buildName, chartWidth, fuelCapacity, fuelLevel } = state;
let hide = tooltip.bind(null, null);
let menu = this.props.currentMenu;
let shipUpdated = this._shipUpdated;
let hStr = ship.getHardpointsString();
let sStr = ship.getStandardString();
let iStr = ship.getInternalString();
renderPage() {
let state = this.state,
{ language, termtip, tooltip, sizeRatio, onWindowResize } = this.context,
{ translate, units, formats } = language,
{ ship, code, savedCode, buildName, chartWidth, fuelCapacity, fuelLevel } = state,
hide = tooltip.bind(null, null),
menu = this.props.currentMenu,
shipUpdated = this._shipUpdated,
canSave = buildName && code !== savedCode,
canReload = savedCode && canSave,
hStr = ship.getHardpointsString(),
sStr = ship.getStandardString(),
iStr = ship.getInternalString();
return (
<div id='outfit' className={'page'} style={{ fontSize: (sizeRatio * 0.9) + 'em' }}>
@@ -257,19 +259,19 @@ export default class OutfittingPage extends Page {
<h1>{ship.name}</h1>
<div id='build'>
<input value={buildName} onChange={this._buildNameChange} placeholder={translate('Enter Name')} maxsize={50} />
<button onTouchTap={this._saveBuild} disabled={!buildName || savedCode && code == savedCode} onMouseOver={termtip.bind(null, 'save')} onMouseOut={hide}>
<button onClick={canSave && this._saveBuild} disabled={!canSave} onMouseOver={termtip.bind(null, 'save')} onMouseOut={hide}>
<FloppyDisk className='lg' />
</button>
<button onTouchTap={this._reloadBuild} disabled={!savedCode || code == savedCode} onMouseOver={termtip.bind(null, 'reload')} onMouseOut={hide}>
<button onClick={canReload && this._reloadBuild} disabled={!canReload} onMouseOver={termtip.bind(null, 'reload')} onMouseOut={hide}>
<Reload className='lg'/>
</button>
<button className={'danger'} onTouchTap={this._deleteBuild} disabled={!savedCode} onMouseOver={termtip.bind(null, 'delete')} onMouseOut={hide}>
<button className={'danger'} onClick={savedCode && this._deleteBuild} disabled={!savedCode} onMouseOver={termtip.bind(null, 'delete')} onMouseOut={hide}>
<Bin className='lg'/>
</button>
<button onTouchTap={this._resetBuild} disabled={!code} onMouseOver={termtip.bind(null, 'reset')} onMouseOut={hide}>
<button onClick={code && this._resetBuild} disabled={!code} onMouseOver={termtip.bind(null, 'reset')} onMouseOut={hide}>
<Switch className='lg'/>
</button>
<button onTouchTap={this._exportBuild} disabled={!buildName} onMouseOver={termtip.bind(null, 'export')} onMouseOut={hide}>
<button onClick={buildName && this._exportBuild} disabled={!buildName} onMouseOver={termtip.bind(null, 'export')} onMouseOut={hide}>
<Download className='lg'/>
</button>
</div>
@@ -302,12 +304,12 @@ export default class OutfittingPage extends Page {
<LineChart
width={chartWidth}
xMax={ship.cargoCapacity}
yMax={ship.unladenTotalRange}
yMax={ship.unladenFastestRange}
xUnit={translate('T')}
yUnit={translate('LY')}
yLabel={translate('total range')}
yLabel={translate('fastest range')}
xLabel={translate('cargo')}
func={state.totalRangeChartFunc}
func={state.fastestRangeChartFunc}
/>
</div>

View File

@@ -1,4 +1,5 @@
import React from 'react';
import ErrorDetails from './ErrorDetails';
import { shallowEqual } from '../utils/UtilityFunctions';
/**
@@ -16,7 +17,8 @@ export default class Page extends React.Component {
hideModal: React.PropTypes.func.isRequired,
tooltip: React.PropTypes.func.isRequired,
termtip: React.PropTypes.func.isRequired,
onWindowResize: React.PropTypes.func.isRequired
onWindowResize: React.PropTypes.func.isRequired,
onCommand: React.PropTypes.func.isRequired
};
static propTypes = {
@@ -67,4 +69,16 @@ export default class Page extends React.Component {
document.title = newState.title || 'Coriolis';
}
/**
* Checks error state before rendering the page contents.
* Pages should catch all errors where possible capture details to state.error.
* @return {React.Component} Page contents
*/
render() {
if (this.state.error) {
return <ErrorDetails error={this.state.error} />;
}
return this.renderPage();
}
}

View File

@@ -1,6 +1,6 @@
import React from 'react';
import Page from './Page';
import { Ships } from 'coriolis-data';
import { Ships } from 'coriolis-data/dist';
import cn from 'classnames';
import Ship from '../shipyard/Ship';
import * as ModuleUtils from '../shipyard/ModuleUtils';
@@ -155,7 +155,7 @@ export default class ShipyardPage extends Page {
* Render the Page
* @return {React.Component} The page contents
*/
render() {
renderPage() {
let { translate, formats, units } = this.context.language;
let fInt = formats.int;
let fRound = formats.round;
@@ -205,44 +205,44 @@ export default class ShipyardPage extends Page {
<table style={{ fontSize:'0.85em', whiteSpace:'nowrap', margin: '0 auto' }} align='center'>
<thead>
<tr className='main'>
<th rowSpan={2} className='sortable le' onTouchTap={sortShips('name')}>{translate('ship')}</th>
<th rowSpan={2} className='sortable' onTouchTap={sortShips('manufacturer')}>{translate('manufacturer')}</th>
<th rowSpan={2} className='sortable' onTouchTap={sortShips('class')}>{translate('size')}</th>
<th rowSpan={2} className='sortable' onMouseEnter={tip.bind(null, 'maneuverability')} onMouseLeave={hide} onTouchTap={sortShips('agility')}>{translate('mnv')}</th>
<th rowSpan={2} className='sortable le' onClick={sortShips('name')}>{translate('ship')}</th>
<th rowSpan={2} className='sortable' onClick={sortShips('manufacturer')}>{translate('manufacturer')}</th>
<th rowSpan={2} className='sortable' onClick={sortShips('class')}>{translate('size')}</th>
<th rowSpan={2} className='sortable' onMouseEnter={tip.bind(null, 'maneuverability')} onMouseLeave={hide} onClick={sortShips('agility')}>{translate('mnv')}</th>
<th colSpan={4}>{translate('base')}</th>
<th colSpan={4}>{translate('max')}</th>
<th colSpan={5} className='sortable' onTouchTap={sortShips('hpCount')}>{translate('hardpoints')}</th>
<th colSpan={8} className='sortable' onTouchTap={sortShips('intCount')}>{translate('internal compartments')}</th>
<th rowSpan={2} className='sortable' onTouchTap={sortShips('hullMass')}>{translate('hull')}</th>
<th rowSpan={2} className='sortable' onMouseEnter={tip.bind(null, 'mass lock factor')} onMouseLeave={hide} onTouchTap={sortShips('masslock')} >{translate('MLF')}</th>
<th rowSpan={2} className='sortable' onTouchTap={sortShips('retailCost')}>{translate('cost')}</th>
<th colSpan={5} className='sortable' onClick={sortShips('hpCount')}>{translate('hardpoints')}</th>
<th colSpan={8} className='sortable' onClick={sortShips('intCount')}>{translate('internal compartments')}</th>
<th rowSpan={2} className='sortable' onClick={sortShips('hullMass')}>{translate('hull')}</th>
<th rowSpan={2} className='sortable' onMouseEnter={tip.bind(null, 'mass lock factor')} onMouseLeave={hide} onClick={sortShips('masslock')} >{translate('MLF')}</th>
<th rowSpan={2} className='sortable' onClick={sortShips('retailCost')}>{translate('cost')}</th>
</tr>
<tr>
{/* Base */}
<th className='sortable lft' onTouchTap={sortShips('speed')}>{translate('speed')}</th>
<th className='sortable' onTouchTap={sortShips('boost')}>{translate('boost')}</th>
<th className='sortable' onTouchTap={sortShips('baseArmour')}>{translate('armour')}</th>
<th className='sortable' onTouchTap={sortShips('baseShieldStrength')}>{translate('shields')}</th>
<th className='sortable lft' onClick={sortShips('speed')}>{translate('speed')}</th>
<th className='sortable' onClick={sortShips('boost')}>{translate('boost')}</th>
<th className='sortable' onClick={sortShips('baseArmour')}>{translate('armour')}</th>
<th className='sortable' onClick={sortShips('baseShieldStrength')}>{translate('shields')}</th>
{/* Max */}
<th className='sortable lft' onTouchTap={sortShips('topSpeed')}>{translate('speed')}</th>
<th className='sortable' onTouchTap={sortShips('topBoost')}>{translate('boost')}</th>
<th className='sortable' onTouchTap={sortShips('maxJumpRange')}>{translate('jump')}</th>
<th className='sortable' onTouchTap={sortShips('maxCargo')}>{translate('cargo')}</th>
<th className='sortable lft' onClick={sortShips('topSpeed')}>{translate('speed')}</th>
<th className='sortable' onClick={sortShips('topBoost')}>{translate('boost')}</th>
<th className='sortable' onClick={sortShips('maxJumpRange')}>{translate('jump')}</th>
<th className='sortable' onClick={sortShips('maxCargo')}>{translate('cargo')}</th>
{/* Hardpoints */}
<th className='sortable lft' onTouchTap={sortShips('hp',1)}>{translate('S')}</th>
<th className='sortable' onTouchTap={sortShips('hp', 2)}>{translate('M')}</th>
<th className='sortable' onTouchTap={sortShips('hp', 3)}>{translate('L')}</th>
<th className='sortable' onTouchTap={sortShips('hp', 4)}>{translate('H')}</th>
<th className='sortable' onTouchTap={sortShips('hp', 0)}>{translate('U')}</th>
<th className='sortable lft' onClick={sortShips('hp',1)}>{translate('S')}</th>
<th className='sortable' onClick={sortShips('hp', 2)}>{translate('M')}</th>
<th className='sortable' onClick={sortShips('hp', 3)}>{translate('L')}</th>
<th className='sortable' onClick={sortShips('hp', 4)}>{translate('H')}</th>
<th className='sortable' onClick={sortShips('hp', 0)}>{translate('U')}</th>
{/* Internal */}
<th className='sortable lft' onTouchTap={sortShips('int', 0)} >1</th>
<th className='sortable' onTouchTap={sortShips('int', 1)} >2</th>
<th className='sortable' onTouchTap={sortShips('int', 2)} >3</th>
<th className='sortable' onTouchTap={sortShips('int', 3)} >4</th>
<th className='sortable' onTouchTap={sortShips('int', 4)} >5</th>
<th className='sortable' onTouchTap={sortShips('int', 5)} >6</th>
<th className='sortable' onTouchTap={sortShips('int', 6)} >7</th>
<th className='sortable' onTouchTap={sortShips('int', 7)} >8</th>
<th className='sortable lft' onClick={sortShips('int', 0)} >1</th>
<th className='sortable' onClick={sortShips('int', 1)} >2</th>
<th className='sortable' onClick={sortShips('int', 2)} >3</th>
<th className='sortable' onClick={sortShips('int', 3)} >4</th>
<th className='sortable' onClick={sortShips('int', 4)} >5</th>
<th className='sortable' onClick={sortShips('int', 5)} >6</th>
<th className='sortable' onClick={sortShips('int', 6)} >7</th>
<th className='sortable' onClick={sortShips('int', 7)} >8</th>
</tr>
</thead>
<tbody>

View File

@@ -12,25 +12,25 @@ export function jumpRange(mass, fsd, fuel) {
}
/**
* Calculate the total range based on mass and a specific FSD, and all fuel available
* Calculate the fastest (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
*/
export function totalRange(mass, fsd, fuel) {
export function fastestRange(mass, fsd, fuel) {
let fuelRemaining = fuel % fsd.maxfuel; // Fuel left after making N max jumps
let jumps = Math.floor(fuel / fsd.maxfuel);
mass += fuelRemaining;
// Going backwards, start with the last jump using the remaining fuel
let totalRange = fuelRemaining > 0 ? Math.pow(fuelRemaining / fsd.fuelmul, 1 / fsd.fuelpower) * fsd.optmass / mass : 0;
let fastestRange = 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 mass left in the tank
for (let j = 0; j < jumps; j++) {
mass += fsd.maxfuel;
totalRange += Math.pow(fsd.maxfuel / fsd.fuelmul, 1 / fsd.fuelpower) * fsd.optmass / mass;
fastestRange += Math.pow(fsd.maxfuel / fsd.fuelmul, 1 / fsd.fuelpower) * fsd.optmass / mass;
}
return totalRange;
return fastestRange;
};
/**

View File

@@ -164,8 +164,8 @@ export const ShipFacets = [
i: 9
},
{ // 10
title: 'total range',
props: ['unladenTotalRange', 'ladenTotalRange'],
title: 'fastest range',
props: ['unladenFastestRange', 'ladenFastestRange'],
lbls: ['unladen', 'laden'],
unit: 'LY',
fmt: 'round',

View File

@@ -1,6 +1,6 @@
import { ModuleNameToGroup, BulkheadNames } from './Constants';
import ModuleSet from './ModuleSet';
import { Ships, Modules } from 'coriolis-data';
import { Ships, Modules } from 'coriolis-data/dist';
/**
* Created a cargo hatch model

View File

@@ -1,5 +1,5 @@
import { ModuleGroupToName, MountMap, BulkheadNames } from './Constants';
import { Ships } from 'coriolis-data';
import { Ships } from 'coriolis-data/dist';
import Ship from './Ship';
import * as ModuleUtils from './ModuleUtils';
import LZString from 'lz-string';

View File

@@ -7,9 +7,9 @@ const UNIQUE_MODULES = ['psg', 'sg', 'bsg', 'rf', 'fs'];
/**
* Returns the power usage type of a slot and it's particular modul
* @param {object} slot The Slot
* @param {object} modul The modul in the slot
* @return {string} The key for the power usage type
* @param {Object} slot The Slot
* @param {Object} modul The modul in the slot
* @return {String} The key for the power usage type
*/
function powerUsageType(slot, modul) {
if (modul) {
@@ -23,10 +23,10 @@ function powerUsageType(slot, modul) {
/**
* Populates the category array with module IDs from
* the provided code
* @param {string} code Serialized ship code
* @param {String} code Serialized ship code
* @param {Array} arr Category array
* @param {number} codePos Current position/Index of code string
* @return {number} Next position/Index of code string
* @param {Number} codePos Current position/Index of code string
* @return {Number} Next position/Index of code string
*/
function decodeToArray(code, arr, codePos) {
for (let i = 0; i < arr.length; i++) {
@@ -44,7 +44,7 @@ function decodeToArray(code, arr, codePos) {
/**
* Reduce function used to get the IDs for a slot group (or array of slots)
* @param {array} idArray The current Array of IDs
* @param {object} slot Slot object
* @param {Object} slot Slot object
* @param {integer} slotIndex The index for the slot in its group
* @return {array} The mutated idArray
*/
@@ -59,9 +59,9 @@ function reduceToIDs(idArray, slot, slotIndex) {
export default class Ship {
/**
* @param {string} id Unique ship Id / Key
* @param {object} properties Basic ship properties such as name, manufacturer, mass, etc
* @param {object} slots Collection of slot groups (standard/standard, internal, hardpoints) with their max class size.
* @param {String} id Unique ship Id / Key
* @param {Object} properties Basic ship properties such as name, manufacturer, mass, etc
* @param {Object} slots Collection of slot groups (standard/standard, internal, hardpoints) with their max class size.
*/
constructor(id, properties, slots) {
this.id = id;
@@ -131,9 +131,9 @@ export default class Ship {
/**
* Calculate hypothetical jump range using the installed FSD and the
* specified mass which can be more or less than ships actual mass
* @param {number} fuel Fuel available in tons
* @param {number} cargo Cargo in tons
* @return {number} Jump range in Light Years
* @param {Number} fuel Fuel available in tons
* @param {Number} cargo Cargo in tons
* @return {Number} Jump range in Light Years
*/
calcJumpRangeWith(fuel, cargo) {
return Calc.jumpRange(this.unladenMass + fuel + cargo, this.standard[2].m, fuel);
@@ -141,10 +141,10 @@ export default class Ship {
/**
* Calculate the hypothetical laden jump range based on a potential change in mass, fuel, or FSD
* @param {number} massDelta Optional - Change in laden mass (mass + cargo + fuel)
* @param {number} fuel Optional - Available fuel (defaults to max fuel based on FSD)
* @param {Number} massDelta Optional - Change in laden mass (mass + cargo + fuel)
* @param {Number} fuel Optional - Available fuel (defaults to max fuel based on FSD)
* @param {Object} fsd Optional - Frame Shift Drive (or use mounted FSD)
* @return {number} Jump range in Light Years
* @return {Number} Jump range in Light Years
*/
calcLadenRange(massDelta, fuel, fsd) {
return Calc.jumpRange(this.ladenMass + (massDelta || 0), fsd || this.standard[2].m, fuel);
@@ -152,10 +152,10 @@ export default class Ship {
/**
* Calculate the hypothetical unladen jump range based on a potential change in mass, fuel, or FSD
* @param {number} massDelta Optional - Change in ship mass
* @param {number} fuel Optional - Available fuel (defaults to lesser of fuel capacity or max fuel based on FSD)
* @param {Number} massDelta Optional - Change in ship mass
* @param {Number} fuel Optional - Available fuel (defaults to lesser of fuel capacity or max fuel based on FSD)
* @param {Object} fsd Optional - Frame Shift Drive (or use mounted FSD)
* @return {number} Jump range in Light Years
* @return {Number} Jump range in Light Years
*/
calcUnladenRange(massDelta, fuel, fsd) {
fsd = fsd || this.standard[2].m;
@@ -165,44 +165,71 @@ export default class Ship {
/**
* Calculate cumulative (total) jump range when making longest jumps using the installed FSD and the
* specified mass which can be more or less than ships actual mass
* @param {number} fuel Fuel available in tons
* @param {number} cargo Cargo in tons
* @return {number} Total/Cumulative Jump range in Light Years
* @param {Number} fuel Fuel available in tons
* @param {Number} cargo Cargo in tons
* @return {Number} Total/Cumulative Jump range in Light Years
*/
calcFastestRangeWith(fuel, cargo) {
return Calc.totalRange(this.unladenMass + fuel + cargo, this.standard[2].m, fuel);
return Calc.fastestRange(this.unladenMass + fuel + cargo, this.standard[2].m, fuel);
}
/**
* Calculate the hypothetical top speeds at cargo and fuel tonnage
* @param {number} fuel Fuel available in tons
* @param {number} cargo Cargo in tons
* @param {Number} fuel Fuel available in tons
* @param {Number} cargo Cargo in tons
* @return {Object} Speed at pip settings and boost
*/
calcSpeedsWith(fuel, cargo) {
return Calc.speed(this.unladenMass + fuel + cargo, this.speed, this.boost, this.standard[1].m, this.pipSpeed);
}
/**
* Calculate the recovery time after losing or turning on shields
* Thanks to CMDRs Al Gray, GIF, and Nomad Enigma for providing Shield recharge data and formulas
*
* @return {Number} Recovery time in seconds
*/
calcShieldRecovery() {
if (this.shieldStrength && this.sgSlot) {
// 50% of shield strength / recovery recharge rate + 15 second delay before recharge starts
return ((this.shieldStrength / 2) / this.sgSlot.m.recover) + 15;
}
return 0;
}
/**
* Calculate the recharge time for a shield going from 50% to 100%
* Thanks to CMDRs Al Gray, GIF, and Nomad Enigma for providing Shield recharge data and formulas
*
* @return {Number} 50 - 100% Recharge time in seconds
*/
calcShieldRecharge() {
if (this.shieldStrength && this.sgSlot) {
// 50% -> 100% recharge time, Bi-Weave shields charge at 1.8 MJ/s
return (this.shieldStrength / 2) / (this.sgSlot.m.grp == 'bsg' ? 1.8 : 1);
}
return 0;
}
/**
* Calculate the hypothetical shield strength for the ship using the specified parameters
* @param {Object} sg [optional] Shield Generator to use
* @param {number} multiplierDelta [optional] Change to shield multiplier (+0.2, - 0.12, etc)
* @return {number} Shield strength in MH
* @param {Number} multiplierDelta [optional] Change to shield multiplier (+0.2, - 0.12, etc)
* @return {Number} Shield strength in MH
*/
calcShieldStrengthWith(sg, multiplierDelta) {
if (!sg) {
let sgSlot = this.findInternalByGroup('sg'); // Find Shield Generator slot Index if any
if (!sgSlot) {
if (!this.sgSlot) {
return 0;
}
sg = sgSlot.m;
sg = this.sgSlot.m;
}
return Calc.shieldStrength(this.hullMass, this.baseShieldStrength, sg, this.shieldMultiplier + (multiplierDelta || 0));
}
/**
* [getAvailableModules description]
* @return {[type]} [description]
* Get the set of available modules for this ship
* @return {ModuleSet} Available module set
*/
getAvailableModules() {
return this.availCS;
@@ -216,7 +243,7 @@ export default class Ship {
* 3 - Online
* @param {Object} slot Slot model
* @param {boolean} deployed True - power used when hardpoints are deployed
* @return {number} status index
* @return {Number} status index
*/
getSlotStatus(slot, deployed) {
if (!slot.m) { // Empty Slot
@@ -235,11 +262,10 @@ export default class Ship {
/**
* Find an internal slot that has an installed modul of the specific group.
*
* @param {string} group Module group/type
* @return {number} The index of the slot in ship.internal
* @param {String} group Module group/type
* @return {Number} The index of the slot in ship.internal
*/
findInternalByGroup(group) {
let index;
if (ModuleUtils.isShieldGenerator(group)) {
return this.internal.find(slot => slot.m && ModuleUtils.isShieldGenerator(slot.m.grp));
} else {
@@ -249,7 +275,7 @@ export default class Ship {
/**
* Serializes the ship to a string
* @return {string} Serialized ship 'code'
* @return {String} Serialized ship 'code'
*/
toString() {
return [
@@ -265,7 +291,7 @@ export default class Ship {
/**
* Serializes the standard modules to a string
* @return {string} Serialized standard modules 'code'
* @return {String} Serialized standard modules 'code'
*/
getStandardString() {
if(!this.serialized.standard) {
@@ -279,7 +305,7 @@ export default class Ship {
/**
* Serializes the internal modules to a string
* @return {string} Serialized internal modules 'code'
* @return {String} Serialized internal modules 'code'
*/
getInternalString() {
if(!this.serialized.internal) {
@@ -290,7 +316,7 @@ export default class Ship {
/**
* Serializes the hardpoints and utility modules to a string
* @return {string} Serialized hardpoints and utility modules 'code'
* @return {String} Serialized hardpoints and utility modules 'code'
*/
getHardpointsString() {
if(!this.serialized.hardpoints) {
@@ -301,7 +327,7 @@ export default class Ship {
/**
* Get the serialized module active/inactive settings
* @return {string} Serialized active/inactive settings
* @return {String} Serialized active/inactive settings
*/
getPowerEnabledString() {
return this.serialized.enabled;
@@ -309,7 +335,7 @@ export default class Ship {
/**
* Get the serialized module priority settings
* @return {string} Serialized priority settings
* @return {String} Serialized priority settings
*/
getPowerPrioritesString() {
return this.serialized.priorities;
@@ -319,8 +345,8 @@ export default class Ship {
/**
* Recalculate all item costs and total based on discounts.
* @param {number} shipCostMultiplier Ship cost multiplier discount (e.g. 0.9 === 10% discount)
* @param {number} moduleCostMultiplier Module cost multiplier discount (e.g. 0.75 === 25% discount)
* @param {Number} shipCostMultiplier Ship cost multiplier discount (e.g. 0.9 === 10% discount)
* @param {Number} moduleCostMultiplier Module cost multiplier discount (e.g. 0.75 === 25% discount)
* @return {this} The current ship instance for chaining
*/
applyDiscounts(shipCostMultiplier, moduleCostMultiplier) {
@@ -442,7 +468,7 @@ export default class Ship {
* Updates an existing ship instance's slots with modules determined by the
* code.
*
* @param {string} serializedString The string to deserialize
* @param {String} serializedString The string to deserialize
* @return {this} The current ship instance for chaining
*/
buildFrom(serializedString) {
@@ -527,7 +553,7 @@ export default class Ship {
/**
* Optimize for the lower mass build that can still boost and power the ship
* without power management.
* @param {object} m Standard Module overrides
* @param {Object} m Standard Module overrides
* @return {this} The current ship instance for chaining
*/
optimizeMass(m) {
@@ -537,7 +563,7 @@ export default class Ship {
/**
* Include/Exclude a item/slot in cost calculations
* @param {Object} item Slot or item
* @param {Boolean} included Cost included
* @param {boolean} included Cost included
* @return {this} The current ship instance for chaining
*/
setCostIncluded(item, included) {
@@ -551,7 +577,7 @@ export default class Ship {
/**
* Set slot active/inactive
* @param {Object} slot Slot model
* @param {Boolean} enabled True - active
* @param {boolean} enabled True - active
* @return {this} The current ship instance for chaining
*/
setSlotEnabled(slot, enabled) {
@@ -578,8 +604,8 @@ export default class Ship {
/**
* 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
* @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)
*/
setSlotPriority(slot, newPriority) {
@@ -601,9 +627,9 @@ export default class Ship {
/**
* Updates the ship's cumulative and aggregated stats based on the module change.
* @param {object} slot The slot being updated
* @param {object} n The new module (may be null)
* @param {object} old The old module (may be null)
* @param {Object} slot The slot being updated
* @param {Object} n The new module (may be null)
* @param {Object} old The old module (may be null)
* @param {boolean} preventUpdate If true the global ship state will not be updated
* @return {this} The ship instance (for chaining operations)
*/
@@ -722,8 +748,8 @@ export default class Ship {
* @return {this} The ship instance (for chaining operations)
*/
updateShieldStrength() {
let sgSlot = this.findInternalByGroup('sg'); // Find Shield Generator slot Index if any
this.shieldStrength = sgSlot && sgSlot.enabled ? Calc.shieldStrength(this.hullMass, this.baseShieldStrength, sgSlot.m, this.shieldMultiplier) : 0;
this.sgSlot = this.findInternalByGroup('sg'); // Find Shield Generator slot Index if any
this.shieldStrength = this.sgSlot && this.sgSlot.enabled ? Calc.shieldStrength(this.hullMass, this.baseShieldStrength, this.sgSlot.m, this.shieldMultiplier) : 0;
return this;
}
@@ -737,8 +763,8 @@ export default class Ship {
this.unladenRange = this.calcUnladenRange(); // Includes fuel weight for jump
this.fullTankRange = Calc.jumpRange(unladenMass + fuelCapacity, fsd); // Full Tank
this.ladenRange = this.calcLadenRange(); // Includes full tank and caro
this.unladenTotalRange = Calc.totalRange(unladenMass, fsd, fuelCapacity);
this.ladenTotalRange = Calc.totalRange(unladenMass + this.cargoCapacity, fsd, fuelCapacity);
this.unladenFastestRange = Calc.fastestRange(unladenMass, fsd, fuelCapacity);
this.ladenFastestRange = Calc.fastestRange(unladenMass + this.cargoCapacity, fsd, fuelCapacity);
this.maxJumpCount = Math.ceil(fuelCapacity / fsd.maxfuel);
return this;
}
@@ -791,7 +817,7 @@ export default class Ship {
*
* @param {Object} slot The modul slot
* @param {Object} m Properties for the selected module
* @param {Boolean} preventUpdate If true, do not update aggregated stats
* @param {boolean} preventUpdate If true, do not update aggregated stats
* @return {this} The ship instance (for chaining operations)
*/
use(slot, m, preventUpdate) {
@@ -823,8 +849,8 @@ export default class Ship {
/**
* Mount the specified bulkhead type (index)
* @param {number} index Bulkhead index [0-4]
* @param {Boolean} preventUpdate Prevent summary update
* @param {Number} index Bulkhead index [0-4]
* @param {boolean} preventUpdate Prevent summary update
* @return {this} The ship instance (for chaining operations)
*/
useBulkhead(index, preventUpdate) {
@@ -841,7 +867,7 @@ export default class Ship {
/**
* Set all standard slots to use the speficied rating and class based on
* the slot's max class
* @param {string} rating Module Rating (A-E)
* @param {String} rating Module Rating (A-E)
* @return {this} The ship instance (for chaining operations)
*/
useStandard(rating) {
@@ -854,7 +880,7 @@ export default class Ship {
/**
* Use the lightest standard ModuleUtils unless otherwise specified
* @param {object} m Module overrides
* @param {Object} m Module overrides
* @return {this} The ship instance (for chaining operations)
*/
useLightestStandard(m) {
@@ -898,9 +924,9 @@ export default class Ship {
/**
* Fill all utility slots with the specified module
* @param {string} group Group name
* @param {string} rating Rating [A-I]
* @param {string} name Module name
* @param {String} group Group name
* @param {String} rating Rating [A-I]
* @param {String} name Module name
* @param {boolean} clobber Overwrite non-empty slots
* @return {this} The ship instance (for chaining operations)
*/

View File

@@ -14,8 +14,8 @@ let LS;
// Safe check to determine if localStorage is enabled
try {
localStorage.setItem('test_string', 1);
localStorage.removeItem('test_string');
localStorage.setItem('test', 'test');
localStorage.removeItem('test');
LS = localStorage;
} catch(e) {
LS = null;
@@ -23,7 +23,7 @@ try {
/**
* Safe localstorage put
* @param {string} key key
* @param {String} key key
* @param {any} value data to store
*/
function _put(key, value) {
@@ -34,8 +34,8 @@ function _put(key, value) {
/**
* Safe localstorage get string
* @param {string} key key
* @return {string} The stored string
* @param {String} key key
* @return {String} The stored string
*/
function _getString(key) {
return LS ? LS.getItem(key) : null;
@@ -43,7 +43,7 @@ function _getString(key) {
/**
* Safe localstorage get
* @param {string} key key
* @param {String} key key
* @return {object | number} The stored data
*/
function _get(key) {
@@ -53,7 +53,7 @@ function _get(key) {
/**
* Safe localstorage delete
* @param {string} key key
* @param {String} key key
*/
function _delete(key) {
if (LS) {
@@ -64,8 +64,10 @@ function _delete(key) {
/**
* Persist store / service for all user settings. Currently uses localstorage only
* Should be treated as a singleton / single instance should be used hence why the default
* export is an instance (see end of this file).
*/
class Persist extends EventEmitter {
export class Persist extends EventEmitter {
/**
* Create an instance
@@ -75,22 +77,69 @@ class Persist extends EventEmitter {
let buildJson = _get(LS_KEY_BUILDS);
let comparisonJson = _get(LS_KEY_COMPARISONS);
let tips = _get(LS_KEY_TOOLTIPS);
let discounts = _get(LS_KEY_DISCOUNTS);
this.onStorageChange = this.onStorageChange.bind(this);
this.langCode = _getString(LS_KEY_LANG) || 'en';
this.insurance = _getString(LS_KEY_INSURANCE) || 'standard';
this.discounts = discounts && !isNaN(discounts[0]) && !isNaN(discounts[1]) ? discounts : [1, 1];
this.builds = buildJson ? buildJson : {};
this.comparisons = comparisonJson ? comparisonJson : {};
this.buildCount = Object.keys(this.builds).length;
this.langCode = _getString(LS_KEY_LANG) || 'en';
this.insurance = _getString(LS_KEY_INSURANCE) || 'standard';
this.discounts = _get(LS_KEY_DISCOUNTS) || [1, 1];
this.costTab = _getString(LS_KEY_COST_TAB);
this.state = _get(LS_KEY_STATE);
this.sizeRatio = _get(LS_KEY_SIZE_RATIO) || 1;
this.tooltipsEnabled = tips === null ? true : tips;
if (LS) {
window.addEventListener('storage', this.onStorageChange);
}
}
/**
* Listen to storage changes from other windows/tabs
* and update accordingly
* @param {StorageEvent} e Storage Event
*/
onStorageChange(e) {
let newValue = e.newValue;
try {
switch(e.key) {
case LS_KEY_BUILDS:
this.builds = newValue ? JSON.parse(newValue) : {};
this.emit('builds');
break;
case LS_KEY_COMPARISONS:
this.comparisons = newValue ? JSON.parse(newValue) : {};
this.emit('comparisons');
break;
case LS_KEY_LANG:
this.langCode = newValue;
this.emit('language', newValue);
break;
case LS_KEY_INSURANCE:
this.insurance = newValue;
this.emit('insurance', newValue);
break;
case LS_KEY_DISCOUNTS:
this.discounts = JSON.parse(newValue);
this.emit('discounts', this.discounts);
break;
case LS_KEY_TOOLTIPS:
this.tooltipsEnabled = !!newValue && newValue.toLowerCase() == 'true';
this.emit('tooltips', this.tooltipsEnabled);
break;
}
} catch (e) {
// On JSON.Parse Error - don't sync or do anything
console && console.error && console.error('Localstorage Sync Error', e); // eslint-disable-line no-console
}
}
/**
* Get the current language code
* @return {stirng} language code
* @return {String} language code
*/
getLangCode() {
return this.langCode;
@@ -98,7 +147,7 @@ class Persist extends EventEmitter {
/**
* Update and save the current language
* @param {string} langCode language code
* @param {String} langCode language code
*/
setLangCode(langCode) {
this.langCode = langCode;
@@ -124,9 +173,9 @@ class Persist extends EventEmitter {
/**
* Persist a ship build in local storage.
*
* @param {string} shipId The unique id for a model of ship
* @param {string} name The name of the build
* @param {string} code The serialized code
* @param {String} shipId The unique id for a model of ship
* @param {String} name The name of the build
* @param {String} code The serialized code
*/
saveBuild(shipId, name, code) {
if (!this.builds[shipId]) {
@@ -134,16 +183,16 @@ class Persist extends EventEmitter {
}
this.builds[shipId][name] = code;
_put(LS_KEY_BUILDS, this.builds);
this.emit('buildSaved', shipId, name, code);
this.emit('builds');
}
/**
* Get the serialized code/string for a build. Returns null if a
* build is not found.
*
* @param {string} shipId The unique id for a model of ship
* @param {string} name The name of the build
* @return {string} The serialized build string.
* @param {String} shipId The unique id for a model of ship
* @param {String} name The name of the build
* @return {String} The serialized build string.
*/
getBuild(shipId, name) {
if (this.builds[shipId] && this.builds[shipId][name]) {
@@ -154,7 +203,7 @@ class Persist extends EventEmitter {
/**
* Get all builds (object) or builds for a specific ship (array)
* @param {string} shipId Optional Ship Id
* @param {String} shipId Optional Ship Id
* @return {Object | Array} Object if Ship Id is not provided
*/
getBuilds(shipId) {
@@ -166,7 +215,7 @@ class Persist extends EventEmitter {
/**
* Get an array of all builds names for a ship
* @param {string} shipId Ship Id
* @param {String} shipId Ship Id
* @return {Array} Array of string or empty array
*/
getBuildsNamesFor(shipId) {
@@ -179,8 +228,8 @@ class Persist extends EventEmitter {
/**
* Check if a build has been saved
* @param {string} shipId Ship Id
* @param {string} name Build name
* @param {String} shipId Ship Id
* @param {String} name Build name
* @return {Boolean} True if the build exists
*/
hasBuild(shipId, name) {
@@ -199,8 +248,8 @@ class Persist extends EventEmitter {
* Delete a build from local storage. It will also delete the ship build collection if
* it becomes empty
*
* @param {string} shipId The unique id for a model of ship
* @param {string} name The name of the build
* @param {String} shipId The unique id for a model of ship
* @param {String} name The name of the build
*/
deleteBuild(shipId, name) {
if (this.builds[shipId][name]) {
@@ -220,14 +269,14 @@ class Persist extends EventEmitter {
}
}
_put(LS_KEY_COMPARISONS, this.comparisons);
this.emit('buildDeleted', shipId, name);
this.emit('builds');
}
}
/**
* Persist a comparison in localstorage.
*
* @param {string} name The name of the comparison
* @param {String} name The name of the comparison
* @param {array} builds Array of builds
* @param {array} facets Array of facet indices
*/
@@ -240,13 +289,13 @@ class Persist extends EventEmitter {
builds: builds.map(b => { return { shipId: b.id || b.shipId, buildName: b.buildName }; })
};
_put(LS_KEY_COMPARISONS, this.comparisons);
this.emit('comparisons', this.comparisons);
this.emit('comparisons');
}
/**
* [getComparison description]
* @param {string} name [description]
* @return {object} Object containing array of facets and ship id + build names
* Get a comparison
* @param {String} name Comparison name
* @return {Object} Object containing array of facets and ship id + build names
*/
getComparison(name) {
if (this.comparisons[name]) {
@@ -265,7 +314,7 @@ class Persist extends EventEmitter {
/**
* Check if a comparison has been saved
* @param {string} name Comparison name
* @param {String} name Comparison name
* @return {Boolean} True if a comparison has been saved
*/
hasComparison(name) {
@@ -282,13 +331,13 @@ class Persist extends EventEmitter {
/**
* Removes the comparison from localstorage.
* @param {string} name Comparison name
* @param {String} name Comparison name
*/
deleteComparison(name) {
if (this.comparisons[name]) {
delete this.comparisons[name];
_put(LS_KEY_COMPARISONS, this.comparisons);
this.emit('comparisons', this.comparisons);
this.emit('comparisons');
}
}
@@ -298,8 +347,8 @@ class Persist extends EventEmitter {
deleteAll() {
this.builds = {};
this.comparisons = {};
_delete(LS_KEY_BUILDS);
_delete(LS_KEY_COMPARISONS);
_put(LS_KEY_BUILDS, {});
_put(LS_KEY_COMPARISONS, {});
this.emit('deletedAll');
}
@@ -318,20 +367,20 @@ class Persist extends EventEmitter {
/**
* 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() {
return this.insurance.toLowerCase();
return this.insurance;
}
/**
* Persist selected insurance type
* @param {string} insurance Insurance type name
* @param {String} insurance Insurance type name
*/
setInsurance(insurance) {
this.insurance = insurance.toLowerCase();
_put(LS_KEY_INSURANCE, insurance);
this.emit('insurance', insurance);
_put(LS_KEY_INSURANCE, this.insurance);
this.emit('insurance', this.insurance);
}
/**
@@ -341,7 +390,7 @@ class Persist extends EventEmitter {
setShipDiscount(shipDiscount) {
this.discounts[0] = shipDiscount;
_put(LS_KEY_DISCOUNTS, this.discounts);
this.emit('discounts', this.discounts);
this.emit('discounts');
}
/**
@@ -359,7 +408,7 @@ class Persist extends EventEmitter {
setModuleDiscount(moduleDiscount) {
this.discounts[1] = moduleDiscount;
_put(LS_KEY_DISCOUNTS, this.discounts);
this.emit('discounts', this.discounts);
this.emit('discounts');
}
/**
@@ -389,7 +438,7 @@ class Persist extends EventEmitter {
/**
* Retrieve the last router state from local storage
* @return {object} state State object containing state name and params
* @return {Object} state State object containing state name and params
*/
getState() {
return this.state;
@@ -397,7 +446,7 @@ class Persist extends EventEmitter {
/**
* Save the current router state to localstorage
* @param {object} state State object containing state name and params
* @param {Object} state State object containing state name and params
*/
setState(state) {
this.state = state;
@@ -433,4 +482,5 @@ class Persist extends EventEmitter {
}
}
// Export an instance as the default to use as a singleton
export default new Persist();

View File

@@ -19,7 +19,7 @@ export function slotName(translate, slot) {
* @param {Function} translate Translate function
* @param {Object} a Slot object
* @param {Object} b Slot object
* @return {number} 1, 0, -1
* @return {Number} 1, 0, -1
*/
export function nameComparator(translate, a, b) {
return translate(a.name || a.grp).localeCompare(translate(b.name || b.grp));
@@ -97,7 +97,14 @@ const PROP_BLACKLIST = {
M: 1,
P: 1,
mass: 1,
cost: 1
cost: 1,
recover: 1,
weaponcapacity: 1,
weaponrecharge: 1,
enginecapacity: 1,
enginerecharge: 1,
systemcapacity: 1,
systemrecharge: 1
};
const TERM_LOOKUP = {
@@ -127,10 +134,10 @@ const UNIT_LOOKUP = {
/**
* Determine the appropriate class based on diff value
* @param {number} a Potential Module (cannot be null)
* @param {number} b Currently mounted module (optional - null)
* @param {boolean} negative A positive diff has a negative implication
* @return {string} CSS Class name
* @param {Number} a Potential Module (cannot be null)
* @param {Number} b Currently mounted module (optional - null)
* @param {Boolean} negative A positive diff has a negative implication
* @return {String} CSS Class name
*/
function diffClass(a, b, negative) {
if (b === undefined || a === b) {
@@ -144,8 +151,8 @@ function diffClass(a, b, negative) {
/**
* Determine the displayable diff of a module's proprty
* @param {Function} format Formatter
* @param {number} mVal Potential Module property value
* @param {number} mmVal Currently mounted module property value
* @param {Number} mVal Potential Module property value
* @param {Number} mmVal Currently mounted module property value
* @return {string | React.Component} Component to be rendered
*/
function diff(format, mVal, mmVal) {
@@ -208,7 +215,30 @@ export function diffDetails(language, m, mm) {
newShield = this.calcShieldStrengthWith(m);
}
}
propDiffs.push(<div key='shields'>{`${translate('shields')}: `}<span className={newShield > shield ? 'secondary' : 'warning'}>{diff(formats.int, newShield, shield)}{units.MJ}</span></div>);
let sgDiffClass = Math.round((newShield - shield) * 100) / 100 == 0 ? 'muted' : (newShield > shield ? 'secondary' : 'warning');
propDiffs.push(<div key='shields'>{`${translate('shields')}: `}<span className={sgDiffClass}>{diff(formats.int, newShield, shield)}{units.MJ}</span></div>);
}
if (m.grp == 'pd') {
propDiffs.push(<div key='wep'>
{`${translate('WEP')}: `}
<span className={diffClass(m.weaponcapacity, mm.weaponcapacity)}>{m.weaponcapacity}{units.MJ}</span>
{' / '}
<span className={diffClass(m.weaponrecharge, mm.weaponrecharge)}>{m.weaponrecharge}{units.MW}</span>
</div>);
propDiffs.push(<div key='sys'>
{`${translate('SYS')}: `}
<span className={diffClass(m.systemcapacity, mm.systemcapacity)}>{m.systemcapacity}{units.MJ}</span>
{' / '}
<span className={diffClass(m.systemrecharge, mm.systemrecharge)}>{m.systemrecharge}{units.MW}</span>
</div>);
propDiffs.push(<div key='eng'>
{`${translate('ENG')}: `}
<span className={diffClass(m.enginecapacity, mm.enginecapacity)}>{m.enginecapacity}{units.MJ}</span>
{' / '}
<span className={diffClass(m.enginerecharge, mm.enginerecharge)}>{m.enginerecharge}{units.MW}</span>
</div>);
}
if (m.grp == 'fd' || massDiff || capDiff) {

View File

@@ -0,0 +1,21 @@
/**
* Generates a URL for the outiffing page
* @param {String} shipId Ship Id
* @param {String} code [optional] Serliazed build code
* @param {String} buildName [optional] Build name
* @return {String} URL
*/
export function outfitURL(shipId, code, buildName) {
let parts = ['/outfit/', shipId];
if (code) {
parts.push('/', code);
}
if (buildName) {
parts.push('?bn=', encodeURIComponent(buildName));
}
return parts.join('');
}

View File

@@ -1,397 +0,0 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1">
<metadata></metadata>
<defs>
<font id="orbitronregular" horiz-adv-x="1695" >
<font-face units-per-em="2048" ascent="1536" descent="-512" />
<missing-glyph horiz-adv-x="557" />
<glyph horiz-adv-x="0" />
<glyph unicode="&#xd;" horiz-adv-x="681" />
<glyph unicode=" " horiz-adv-x="557" />
<glyph unicode="&#x09;" horiz-adv-x="557" />
<glyph unicode="&#xa0;" horiz-adv-x="557" />
<glyph unicode="!" horiz-adv-x="450" d="M119 0v168h168v-168h-168zM119 416v1059h168v-1059h-168z" />
<glyph unicode="&#x22;" horiz-adv-x="761" d="M121 1188v287h168v-287h-168zM438 1188v287h168v-287h-168z" />
<glyph unicode="#" horiz-adv-x="1632" d="M66 350v168h260l145 451h-340v168h393l115 336h166l-117 -336h535l114 336h166l-117 -336h197v-168h-248l-145 -451h326v-168h-379l-109 -350h-168l111 350h-533l-108 -350h-168l110 350h-206zM494 518h530l145 451h-532z" />
<glyph unicode="$" horiz-adv-x="1613" d="M70 248v59h168v-59q0 -33 23.5 -56.5t55.5 -23.5h414v487h-414q-102 0 -174.5 72t-72.5 176v324q0 104 72.5 177t174.5 73h414v233h168v-233h412q104 0 177 -73t73 -177v-58h-170v58q0 33 -24 56.5t-56 23.5h-412v-484h412q104 0 177 -72.5t73 -175.5v-327 q0 -102 -73 -175t-177 -73h-412v-233h-168v233h-414q-102 0 -174.5 72.5t-72.5 175.5zM238 903q0 -33 23.5 -56.5t55.5 -23.5h414v484h-414q-33 0 -56 -23.5t-23 -56.5v-324zM899 168h412q33 0 56.5 23.5t23.5 56.5v327q0 33 -23.5 56.5t-56.5 23.5h-412v-487z" />
<glyph unicode="%" horiz-adv-x="1978" d="M98 1053v172q0 104 73 177t177 73h193q104 0 177 -73t73 -177v-172q0 -102 -73 -175t-177 -73h-193q-104 0 -177 72.5t-73 175.5zM223 1010q0 -33 23.5 -56.5t56.5 -23.5h283q33 0 56.5 23.5t23.5 56.5v258q0 33 -24 56.5t-56 23.5h-283q-33 0 -56.5 -24t-23.5 -56v-258z M281 0v221l1491 1256v-220zM1219 246v174q0 102 71.5 175t175.5 73h195q102 0 175 -73t73 -175v-174q0 -102 -73 -175t-175 -73h-195q-104 0 -175.5 72.5t-71.5 175.5zM1343 203q0 -33 23.5 -56.5t56.5 -23.5h281q33 0 56.5 23.5t23.5 56.5v260q0 33 -23.5 56.5t-56.5 23.5 h-281q-33 0 -56.5 -23.5t-23.5 -56.5v-260z" />
<glyph unicode="&#x26;" horiz-adv-x="1921" d="M109 248v440q0 45 55 89t147 44q-117 88 -116 197v205q0 104 72.5 177t174.5 73h842q84 0 154.5 -62.5t95.5 -146.5v-113h-170v70q0 33 -23.5 56t-56.5 23h-842q-33 0 -56.5 -23.5t-23.5 -55.5v-285l1068 -539v285h167v-340l275 -162v-147l-281 157q-35 -86 -99.5 -138 t-141.5 -52h-994q-102 0 -174.5 72.5t-72.5 175.5zM276 248q0 -33 23.5 -56.5t56.5 -23.5h994q23 0 45 20.5t37 53.5l-1045 526h-31q-33 0 -56.5 -23.5t-23.5 -56.5v-440z" />
<glyph unicode="'" horiz-adv-x="458" d="M121 1188v287h168v-287h-168z" />
<glyph unicode="(" horiz-adv-x="567" d="M106 248v977q0 104 73 177t175 73h60v-170h-60q-33 0 -56.5 -24t-23.5 -56v-977q0 -33 23.5 -56.5t56.5 -23.5h60v-168h-60q-102 0 -175 72.5t-73 175.5z" />
<glyph unicode=")" horiz-adv-x="569" d="M115 0v168h57q33 0 56.5 23.5t23.5 56.5v977q0 33 -23.5 56.5t-56.5 23.5h-57v170h57q102 0 176 -73t74 -177v-977q0 -102 -74 -175t-176 -73h-57z" />
<glyph unicode="*" horiz-adv-x="1005" d="M51 1096l49 161l301 -100v316h168v-314l297 98l56 -161l-299 -97l184 -256q-25 -18 -67 -48.5t-68 -49.5l-187 252l-184 -252q-31 23 -74 52.5t-63 43.5l184 258z" />
<glyph unicode="+" horiz-adv-x="886" d="M35 522v168h311v314h168v-314h320v-168h-320v-317h-168v317h-311z" />
<glyph unicode="," horiz-adv-x="395" d="M111 168h168v-201q0 -82 -47.5 -146.5t-120.5 -88.5v436z" />
<glyph unicode="-" horiz-adv-x="1058" d="M121 522v168h799v-168h-799z" />
<glyph unicode="." horiz-adv-x="438" d="M111 0v168h168v-168h-168z" />
<glyph unicode="/" horiz-adv-x="1067" d="M12 0v217l1037 1258v-213z" />
<glyph unicode="0" horiz-adv-x="1708" d="M117 248v977q0 104 72 177t176 73h993q102 0 175 -73t73 -177v-977q0 -102 -73 -175t-175 -73h-993q-104 0 -176 72.5t-72 175.5zM285 371l1130 934h-1050q-33 0 -56.5 -24t-23.5 -56v-854zM305 168h1053q33 0 56.5 23.5t23.5 56.5v854z" />
<glyph unicode="1" horiz-adv-x="800" d="M2 1014l387 461h229v-1475h-169v1286q-29 -37 -114 -137t-114 -135h-219z" />
<glyph unicode="2" horiz-adv-x="1699" d="M117 0v553q0 104 71.5 176t176.5 72h993q33 0 56.5 23.5t23.5 56.5v346q0 33 -23.5 56.5t-56.5 23.5h-993q-33 0 -56.5 -23.5t-23.5 -56.5v-58h-168v58q0 104 72 177t176 73h993q102 0 175 -73t73 -177v-346q0 -102 -73 -175t-175 -73h-993q-33 0 -56.5 -23.5 t-23.5 -56.5v-305q0 -33 23.5 -56.5t56.5 -23.5h1241v-168h-1489z" />
<glyph unicode="3" horiz-adv-x="1691" d="M109 248v33h170v-33q0 -33 23.5 -56.5t55.5 -23.5h994q33 0 56.5 23.5t23.5 56.5v342q0 33 -24 56.5t-56 23.5h-961v168h895q33 0 56.5 23.5t23.5 56.5v309q0 33 -23.5 56.5t-56.5 23.5h-928q-33 0 -56 -23.5t-23 -56.5v-53h-170v53q0 104 72.5 177t176.5 73h928 q104 0 176 -73t72 -177v-309q0 -54 -25 -107l9 -37q82 -76 81 -184v-342q0 -102 -72.5 -175t-174.5 -73h-994q-104 0 -176.5 72.5t-72.5 175.5z" />
<glyph unicode="4" horiz-adv-x="1495" d="M12 381v188l1008 908h188v-928h230v-168h-230v-381h-168v381h-1028zM227 549h813v659z" />
<glyph unicode="5" horiz-adv-x="1699" d="M117 248v59h168v-59q0 -33 23.5 -56.5t56.5 -23.5h993q33 0 56.5 23.5t23.5 56.5v348q0 33 -23.5 56.5t-56.5 23.5h-1241v801h1489v-170h-1241q-33 0 -56.5 -23.5t-23.5 -56.5v-301q0 -33 23.5 -57.5t56.5 -24.5h993q102 0 175 -72t73 -176v-348q0 -102 -73 -175 t-175 -73h-993q-104 0 -176 72.5t-72 175.5z" />
<glyph unicode="6" horiz-adv-x="1679" d="M117 248v979q0 104 71.5 177t176.5 73h983v-170h-983q-33 0 -56.5 -23.5t-23.5 -56.5v-295q0 -33 23.5 -57.5t56.5 -24.5h993q102 0 175 -71.5t73 -176.5v-354q0 -102 -73 -175t-175 -73h-993q-104 0 -176 72.5t-72 175.5zM285 248q0 -33 23.5 -56.5t56.5 -23.5h993 q33 0 56.5 23.5t23.5 56.5v354q0 33 -23.5 56.5t-56.5 23.5h-1073v-434z" />
<glyph unicode="7" horiz-adv-x="1351" d="M6 1307v170h983q102 0 175 -73t73 -177v-1227h-168v1227q0 33 -23.5 56.5t-56.5 23.5h-983z" />
<glyph unicode="8" horiz-adv-x="1708" d="M117 248v348q0 84 61 164q-61 84 -61 166v301q0 104 71.5 177t176.5 73h993q82 0 152.5 -62.5t95.5 -146.5v-342q0 -82 -62 -166q61 -80 62 -164v-348q0 -102 -73 -175t-175 -73h-993q-104 0 -176 72.5t-72 175.5zM285 248q0 -33 23.5 -56.5t56.5 -23.5h993 q33 0 56.5 23.5t23.5 56.5v348q0 33 -23.5 56.5t-56.5 23.5h-993q-33 0 -56.5 -23.5t-23.5 -56.5v-348zM285 901q0 -33 23.5 -56.5t56.5 -23.5h993q33 0 56.5 23.5t23.5 56.5v324q0 33 -23.5 56.5t-56.5 23.5h-993q-33 0 -56.5 -24t-23.5 -56v-324z" />
<glyph unicode="9" d="M104 872v355q0 104 73 177t177 73h994q102 0 174.5 -73t72.5 -177v-979q0 -102 -72.5 -175t-174.5 -73h-994q-80 0 -145.5 47t-89.5 121h1229q33 0 56 23.5t23 56.5v297q0 33 -23 56.5t-56 23.5h-994q-104 0 -177 72.5t-73 174.5zM274 872q0 -33 24 -56t56 -23h1073v434 q0 33 -23.5 56.5t-55.5 23.5h-994q-33 0 -56.5 -23.5t-23.5 -56.5v-355z" />
<glyph unicode=":" horiz-adv-x="438" d="M111 0v168h168v-168h-168zM111 1036v168h168v-168h-168z" />
<glyph unicode=";" horiz-adv-x="395" d="M104 168h168v-201q0 -82 -47 -146.5t-121 -88.5v436zM104 1036v168h168v-168h-168z" />
<glyph unicode="&#x3c;" horiz-adv-x="968" d="M10 508v199l824 473v-195l-656 -379l656 -379v-194z" />
<glyph unicode="=" horiz-adv-x="1306" d="M121 309v168h1059v-168h-1059zM121 719v168h1059v-168h-1059z" />
<glyph unicode="&#x3e;" horiz-adv-x="972" d="M121 29v194l655 379l-655 379v195l823 -474v-198z" />
<glyph unicode="?" horiz-adv-x="1388" d="M63 1303v172l1059 -2q102 0 175 -73t73 -177v-398q0 -102 -72.5 -174.5t-175.5 -72.5h-561q-33 0 -56.5 -24t-23.5 -56v-86h-168v86q0 104 73 175.5t175 71.5h561q33 0 56.5 24t23.5 56v398q0 33 -23.5 56.5t-56.5 23.5h-1059zM313 0v168h168v-168h-168z" />
<glyph unicode="@" horiz-adv-x="1701" d="M117 248v977q0 104 71.5 177t176.5 73h993q102 0 175 -73t73 -177v-832h-842q-104 0 -176 73t-72 177v172q0 102 72 175t176 73h194q102 0 175 -72.5t73 -175.5v-295h232v705q0 33 -23.5 56.5t-56.5 23.5h-993q-33 0 -56.5 -24t-23.5 -56v-977q0 -33 23.5 -56.5 t56.5 -23.5h1241v-168h-1241q-104 0 -176 72.5t-72 175.5zM641 600q0 -33 23.5 -56.5t56.5 -23.5h360v338q0 33 -23.5 56.5t-56.5 23.5h-280q-33 0 -56.5 -23.5t-23.5 -56.5v-258z" />
<glyph unicode="A" horiz-adv-x="1712" d="M119 0v1229q0 102 72 174t174 72h983q102 0 173.5 -72t71.5 -174v-1229h-166v539h-1142v-539h-166zM285 705h1142v524q0 33 -23.5 56.5t-55.5 23.5h-983q-33 0 -56.5 -23.5t-23.5 -56.5v-524z" />
<glyph unicode="B" horiz-adv-x="1703" d="M121 0v1475h1165q102 0 174 -72t72 -174v-314q0 -55 -27 -108l8 -35q82 -76 82 -182v-344q0 -102 -71.5 -174t-173.5 -72h-1229zM287 246q0 -33 23.5 -56.5t56.5 -23.5h983q33 0 56.5 23.5t23.5 56.5v344q0 33 -24 56.5t-56 23.5h-983q-33 0 -56.5 -23.5t-23.5 -56.5 v-344zM287 915q0 -33 23.5 -56t56.5 -23h919q33 0 55.5 23.5t22.5 55.5v314q0 33 -22.5 56.5t-55.5 23.5h-919q-33 0 -56.5 -23.5t-23.5 -56.5v-314z" />
<glyph unicode="C" horiz-adv-x="1683" d="M115 246v983q0 102 71.5 174t173.5 72h1225v-166h-1225q-33 0 -56 -23.5t-23 -56.5v-983q0 -33 23.5 -56.5t55.5 -23.5h1225v-166h-1225q-102 0 -173.5 71.5t-71.5 174.5z" />
<glyph unicode="D" horiz-adv-x="1708" d="M119 0v1475h1229q102 0 173.5 -72t71.5 -174v-983q0 -102 -71.5 -174t-173.5 -72h-1229zM285 246q0 -33 23.5 -56.5t56.5 -23.5h983q33 0 56 23.5t23 56.5v983q0 33 -23.5 56.5t-55.5 23.5h-983q-33 0 -56.5 -23.5t-23.5 -56.5v-983z" />
<glyph unicode="E" horiz-adv-x="1568" d="M119 0v1475h1345v-166h-1179v-488h948v-168h-948v-487h1179v-166h-1345z" />
<glyph unicode="F" horiz-adv-x="1480" d="M119 0v1475h1345v-166h-1179v-488h948v-168h-948v-653h-166z" />
<glyph unicode="G" horiz-adv-x="1699" d="M115 246v983q0 102 71.5 174t173.5 72h983q102 0 174 -72t72 -174v-60h-166v60q0 33 -23.5 56.5t-56.5 23.5h-983q-33 0 -56 -23.5t-23 -56.5v-983q0 -33 23.5 -56.5t55.5 -23.5h983q33 0 56.5 23.5t23.5 56.5v364h-364v168h530v-532q0 -102 -71.5 -174t-174.5 -72h-983 q-102 0 -173.5 71.5t-71.5 174.5z" />
<glyph unicode="H" horiz-adv-x="1742" d="M117 0v1475h166v-654h1177v654h168v-1475h-168v653h-1177v-653h-166z" />
<glyph unicode="I" horiz-adv-x="450" d="M117 0v1475h166v-1475h-166z" />
<glyph unicode="J" horiz-adv-x="1597" d="M8 246v116h166v-116q0 -33 23.5 -56.5t56.5 -23.5h983q33 0 56.5 23.5t23.5 56.5v1229h166v-1229q0 -102 -72 -174t-174 -72h-983q-102 0 -174 71.5t-72 174.5z" />
<glyph unicode="K" horiz-adv-x="1632" d="M117 0v1475h168v-654h475l549 654h213l-617 -738l619 -737h-215l-549 653h-475v-653h-168z" />
<glyph unicode="L" horiz-adv-x="1595" d="M117 0v1477h166v-1311h1308v-166h-1474z" />
<glyph unicode="M" horiz-adv-x="1900" d="M115 0v1475h227l612 -730l613 730h227v-1475h-166v1286l-674 -801l-673 801v-1286h-166z" />
<glyph unicode="N" horiz-adv-x="1703" d="M115 0v1475h227l1081 -1287v1287h166v-1475h-227l-1081 1286v-1286h-166z" />
<glyph unicode="O" d="M111 246v983q0 102 71.5 174t173.5 72h983q102 0 174 -72t72 -174v-983q0 -102 -71.5 -174t-174.5 -72h-983q-102 0 -173.5 71.5t-71.5 174.5zM276 246q0 -33 23.5 -56.5t56.5 -23.5h983q33 0 56.5 23.5t23.5 56.5v983q0 33 -23.5 56.5t-56.5 23.5h-983 q-33 0 -56.5 -23.5t-23.5 -56.5v-983z" />
<glyph unicode="P" horiz-adv-x="1619" d="M115 0v1473h1228q102 0 174 -72t72 -174v-432q0 -102 -71.5 -174t-174.5 -72h-983q-18 0 -79 8v-557h-166zM281 795q0 -33 23.5 -55.5t55.5 -22.5h983q33 0 56.5 22.5t23.5 55.5v432q0 33 -23.5 56.5t-56.5 23.5h-983q-33 0 -56 -23.5t-23 -56.5v-432z" />
<glyph unicode="Q" horiz-adv-x="1810" d="M111 246v983q0 102 71.5 174t173.5 72h983q102 0 174 -72t72 -174v-983q0 -19 -8 -80h201v-166h-1422q-102 0 -173.5 71.5t-71.5 174.5zM276 246q0 -33 23.5 -56.5t56.5 -23.5h983q33 0 56.5 23.5t23.5 56.5v983q0 33 -23.5 56.5t-56.5 23.5h-983q-33 0 -56.5 -23.5 t-23.5 -56.5v-983z" />
<glyph unicode="R" horiz-adv-x="1689" d="M113 0v1473h1228q102 0 174 -72t72 -174v-432q0 -102 -71.5 -174t-174.5 -72h-225l463 -549h-219l-461 549h-541q-18 0 -79 8v-557h-166zM279 795q0 -33 23 -55.5t56 -22.5h983q33 0 55.5 22.5t22.5 55.5v432q0 33 -22.5 56.5t-55.5 23.5h-983q-33 0 -56 -23.5t-23 -56.5 v-432z" />
<glyph unicode="S" horiz-adv-x="1683" d="M104 246v57h166v-57q0 -33 23.5 -56.5t56.5 -23.5h983q33 0 56.5 23.5t23.5 56.5v329q0 33 -23.5 55.5t-56.5 22.5h-983q-102 0 -174 72t-72 174v330q0 102 72 174t174 72h983q102 0 174 -72t72 -174v-58h-166v58q0 33 -23.5 56.5t-56.5 23.5h-983q-33 0 -56.5 -23.5 t-23.5 -56.5v-330q0 -33 23.5 -55.5t56.5 -22.5h983q102 0 174 -71.5t72 -174.5v-329q0 -102 -71.5 -174t-174.5 -72h-983q-102 0 -174 71.5t-72 174.5z" />
<glyph unicode="T" horiz-adv-x="1554" d="M41 1309v166h1475v-166h-654v-1309h-166v1309h-655z" />
<glyph unicode="U" d="M111 246v1229h165v-1229q0 -33 24 -56.5t56 -23.5h983q33 0 56.5 23.5t23.5 56.5v1229h166v-1229q0 -102 -71.5 -174t-174.5 -72h-983q-102 0 -173.5 71.5t-71.5 174.5z" />
<glyph unicode="V" horiz-adv-x="2054" d="M72 1475h194l756 -1313l754 1313h194l-852 -1475h-192z" />
<glyph unicode="W" horiz-adv-x="2414" d="M72 1475h176l432 -1184l430 1184h201l432 -1184l428 1184h180l-536 -1475h-146l-459 1260l-458 -1260h-144z" />
<glyph unicode="X" horiz-adv-x="1662" d="M94 0l619 737l-619 738h213l514 -609l512 609h213l-618 -738l620 -737h-215l-512 608l-514 -608h-213z" />
<glyph unicode="Y" horiz-adv-x="1650" d="M35 1475h190l572 -721l565 721h194l-677 -920v-555h-166v555z" />
<glyph unicode="Z" horiz-adv-x="1681" d="M104 0v227l1287 1082h-1287v166h1475v-228l-1286 -1081h1286v-166h-1475z" />
<glyph unicode="[" horiz-adv-x="563" d="M111 0v1477h307v-170h-139v-1139h139v-168h-307z" />
<glyph unicode="\" horiz-adv-x="1064" d="M10 1272v213l1037 -1266v-217z" />
<glyph unicode="]" horiz-adv-x="565" d="M104 0v168h138v1139h-138v170h308v-1477h-308z" />
<glyph unicode="_" d="M111 0h1491v-168h-1491v168z" />
<glyph unicode="`" horiz-adv-x="436" d="M66 2071h165l70 -285h-164z" />
<glyph unicode="a" horiz-adv-x="1421" d="M106 248v430h1041v262q0 33 -23.5 56.5t-56.5 23.5h-961v168h961q104 0 177 -73t73 -175v-940h-963q-102 0 -175 72.5t-73 175.5zM274 248q0 -33 24 -56.5t56 -23.5h793v342h-873v-262z" />
<glyph unicode="b" horiz-adv-x="1366" d="M111 0v1577h168v-389h794q102 0 175 -73t73 -175v-692q0 -102 -72.5 -175t-175.5 -73h-962zM279 248q0 -33 24.5 -56.5t56.5 -23.5h713q33 0 56.5 23.5t23.5 56.5v692q0 33 -23.5 56.5t-56.5 23.5h-713q-33 0 -57 -23.5t-24 -56.5v-692z" />
<glyph unicode="c" horiz-adv-x="1423" d="M104 248v692q0 102 73 175t175 73h959v-168h-959q-33 0 -56.5 -23.5t-23.5 -56.5v-692q0 -33 23.5 -56.5t56.5 -23.5h963v-168h-963q-102 0 -175 72.5t-73 175.5z" />
<glyph unicode="d" horiz-adv-x="1366" d="M47 248v692q0 102 73 175t177 73h793v389h167v-1577h-960q-104 0 -177 72.5t-73 175.5zM217 248q0 -33 23.5 -56.5t56.5 -23.5h713q33 0 56.5 23.5t23.5 56.5v692q0 33 -24 56.5t-56 23.5h-713q-33 0 -56.5 -23.5t-23.5 -56.5v-692z" />
<glyph unicode="e" horiz-adv-x="1417" d="M104 248v692q0 102 73 175t175 73h713q104 0 177 -73t73 -175v-430h-1043v-262q0 -33 23.5 -56.5t56.5 -23.5h963v-168h-963q-102 0 -175 72.5t-73 175.5zM272 678h873v262q0 33 -23.5 56.5t-56.5 23.5h-713q-33 0 -56.5 -23.5t-23.5 -56.5v-262z" />
<glyph unicode="f" horiz-adv-x="833" d="M109 0v1329q0 104 71.5 176t175.5 72h435v-168h-435q-33 0 -56.5 -23.5t-23.5 -56.5v-141h515v-168h-515v-1020h-167z" />
<glyph unicode="g" horiz-adv-x="1398" d="M84 248v692q0 102 72.5 175t175.5 73h712q104 0 176 -73t72 -175v-1159q0 -104 -71.5 -177t-176.5 -73h-741v170h741q33 0 56.5 23.5t23.5 56.5v219h-792q-102 0 -175 72.5t-73 175.5zM252 248q0 -33 23.5 -56.5t56.5 -23.5h712q33 0 56.5 23.5t23.5 56.5v692 q0 33 -23.5 56.5t-56.5 23.5h-712q-33 0 -56.5 -23.5t-23.5 -56.5v-692z" />
<glyph unicode="h" horiz-adv-x="1368" d="M111 0v1577h168v-389h794q102 0 175 -73t73 -175v-940h-168v940q0 33 -23.5 56.5t-56.5 23.5h-713q-33 0 -57 -23.5t-24 -56.5v-940h-168z" />
<glyph unicode="i" horiz-adv-x="425" d="M106 0v1188h168v-1188h-168zM106 1409v168h168v-168h-168z" />
<glyph unicode="j" horiz-adv-x="489" d="M-383 -328h502q33 0 56.5 23.5t23.5 56.5v1436h170v-1436q0 -104 -73 -177t-177 -73h-502v170zM199 1409v168h170v-168h-170z" />
<glyph unicode="k" horiz-adv-x="1323" d="M111 0v1577h168v-899h327l467 510h221l-538 -594l536 -594h-219l-467 510h-327v-510h-168z" />
<glyph unicode="l" horiz-adv-x="618" d="M106 248v1329h168v-1329q0 -33 24 -56.5t56 -23.5h201v-168h-201q-102 0 -175 72.5t-73 175.5z" />
<glyph unicode="m" horiz-adv-x="2002" d="M111 0v1188h1568q104 0 176 -73t72 -175v-940h-166v940q0 33 -24.5 56.5t-57.5 23.5h-493q-33 0 -56.5 -23.5t-23.5 -56.5v-940h-170v940q0 33 -23.5 56.5t-56.5 23.5h-496q-33 0 -56 -23.5t-23 -56.5v-940h-170z" />
<glyph unicode="n" horiz-adv-x="1425" d="M111 0v1188h962q102 0 175 -73t73 -175v-940h-168v940q0 33 -23.5 56.5t-56.5 23.5h-713q-33 0 -57 -23.5t-24 -56.5v-940h-168z" />
<glyph unicode="o" horiz-adv-x="1417" d="M104 248v692q0 102 73 175t175 73h713q104 0 177 -73t73 -175v-692q0 -102 -73 -175t-177 -73h-713q-102 0 -175 72.5t-73 175.5zM272 248q0 -33 23.5 -56.5t56.5 -23.5h713q33 0 56.5 23.5t23.5 56.5v692q0 33 -23.5 56.5t-56.5 23.5h-713q-33 0 -56.5 -23.5 t-23.5 -56.5v-692z" />
<glyph unicode="p" horiz-adv-x="1359" d="M111 -471v1659h962q102 0 175 -73t73 -175v-692q0 -102 -72.5 -175t-175.5 -73h-794v-471h-168zM279 248q0 -33 24.5 -56.5t56.5 -23.5h713q33 0 56.5 23.5t23.5 56.5v692q0 33 -23.5 56.5t-56.5 23.5h-713q-33 0 -57 -23.5t-24 -56.5v-692z" />
<glyph unicode="q" horiz-adv-x="1359" d="M41 248v692q0 102 72.5 175t177.5 73h960v-1659h-168v471h-792q-104 0 -177 72.5t-73 175.5zM211 248q0 -33 23.5 -56.5t56.5 -23.5h713q33 0 56 23.5t23 56.5v692q0 33 -23.5 56.5t-55.5 23.5h-713q-33 0 -56.5 -23.5t-23.5 -56.5v-692z" />
<glyph unicode="r" horiz-adv-x="1048" d="M106 0v940q0 102 73 175t175 73h668v-168h-668q-33 0 -56.5 -23.5t-23.5 -56.5v-940h-168z" />
<glyph unicode="s" horiz-adv-x="1404" d="M98 248v16h168v-16q0 -33 23.5 -56.5t56.5 -23.5h713q33 0 56.5 23.5t23.5 56.5v182q0 33 -23.5 56.5t-56.5 23.5h-713q-102 0 -175 72.5t-73 175.5v182q0 102 73 175t175 73h713q104 0 177 -73t73 -175v-16h-170v16q0 33 -23.5 56.5t-56.5 23.5h-713q-33 0 -56.5 -23.5 t-23.5 -56.5v-182q0 -33 23.5 -56.5t56.5 -23.5h713q104 0 177 -73t73 -175v-182q0 -102 -73 -175t-177 -73h-713q-102 0 -175 72.5t-73 175.5z" />
<glyph unicode="t" horiz-adv-x="839" d="M109 248v1329h167v-389h515v-168h-515v-772q0 -33 24 -56.5t56 -23.5h435v-168h-435q-104 0 -175.5 72.5t-71.5 175.5z" />
<glyph unicode="u" horiz-adv-x="1423" d="M109 248v940h167v-940q0 -33 24 -56.5t56 -23.5h713q33 0 57.5 23.5t24.5 56.5v940h168v-940q0 -102 -72.5 -175t-177.5 -73h-713q-102 0 -174.5 72.5t-72.5 175.5z" />
<glyph unicode="v" horiz-adv-x="1617" d="M43 1188h197l557 -1024l557 1024h196l-657 -1188h-195z" />
<glyph unicode="w" horiz-adv-x="2193" d="M72 1188h180l360 -885l383 885h222l407 -883l336 883h182l-448 -1188h-146l-442 981l-420 -981h-147z" />
<glyph unicode="x" horiz-adv-x="1417" d="M94 0l496 604l-496 584h221l385 -451l387 451h220l-496 -584l498 -604h-222l-387 475l-385 -475h-221z" />
<glyph unicode="y" horiz-adv-x="1402" d="M86 248v936h168v-936q0 -33 23.5 -56.5t56.5 -23.5h713q33 0 56 23.5t23 56.5v936h168v-1405q0 -104 -71.5 -177t-175.5 -73h-742v170h742q33 0 56 23.5t23 56.5v221h-792q-102 0 -175 72.5t-73 175.5z" />
<glyph unicode="z" horiz-adv-x="1429" d="M111 0v229l1026 791h-1026v168h1210v-230l-1026 -790h1026v-168h-1210z" />
<glyph unicode="{" horiz-adv-x="591" d="M47 641v197l96 51v336q0 104 73 177t175 73h60v-170h-60q-33 0 -56.5 -24t-23.5 -56v-351l-172 -137l172 -141v-348q0 -33 23.5 -56.5t56.5 -23.5h60v-168h-60q-102 0 -175 72.5t-73 175.5v334q-12 8 -47 29.5t-49 29.5z" />
<glyph unicode="|" horiz-adv-x="438" d="M111 -236v1960h168v-1960h-168z" />
<glyph unicode="}" horiz-adv-x="591" d="M104 0v168h60q33 0 56.5 23.5t23.5 56.5v346l172 141l-172 135v355q0 33 -23.5 56.5t-56.5 23.5h-60v170h60q102 0 175 -73t73 -177v-336q16 -8 51 -29.5t43 -25.5v-195l-94 -59v-332q0 -102 -73 -175t-175 -73h-60z" />
<glyph unicode="~" horiz-adv-x="827" d="M49 631v76q55 18 127 18q82 0 219 -68.5t209 -76.5h4q66 0 135 32v-84q-72 -27 -135 -26q-72 0 -209 72.5t-223 72.5q-80 0 -127 -16z" />
<glyph unicode="&#xa1;" horiz-adv-x="430" d="M109 0v1059h167v-1059h-167zM109 1321v170h167v-170h-167z" />
<glyph unicode="&#xa2;" horiz-adv-x="1302" d="M68 248v713q0 104 71.5 176.5t175.5 72.5h273v277h168v-277h516v-170h-516v-872h516v-168h-516v-236h-168v236h-273q-104 0 -175.5 72.5t-71.5 175.5zM236 248q0 -33 23.5 -56.5t55.5 -23.5h273v872h-273q-33 0 -56 -23.5t-23 -55.5v-713z" />
<glyph unicode="&#xa3;" horiz-adv-x="1503" d="M80 0v168h215v494h-215v167h215v396q0 104 72.5 177t177.5 73h620q104 0 177 -73t73 -177v-31h-170v31q0 33 -23.5 56.5t-56.5 23.5h-620q-33 0 -56.5 -24t-23.5 -56v-396h721v-167h-721v-494h950v-168h-1335z" />
<glyph unicode="&#xa8;" horiz-adv-x="788" d="M111 1786v168h170v-168h-170zM479 1786v168h168v-168h-168z" />
<glyph unicode="&#xad;" horiz-adv-x="1058" d="M121 522v168h799v-168h-799z" />
<glyph unicode="&#xb0;" horiz-adv-x="899" d="M92 1069v172q0 104 73 177t177 73h193q104 0 176.5 -72.5t72.5 -177.5v-172q0 -102 -72.5 -175t-176.5 -73h-193q-104 0 -177 73t-73 175zM217 1026q0 -33 23.5 -56.5t56.5 -23.5h283q33 0 56 23.5t23 56.5v258q0 33 -23.5 56.5t-55.5 23.5h-283q-33 0 -56.5 -23.5 t-23.5 -56.5v-258z" />
<glyph unicode="&#xb4;" horiz-adv-x="436" d="M68 1786l71 285h162l-70 -285h-163z" />
<glyph unicode="&#xb6;" horiz-adv-x="1705" d="M115 803v436q0 104 71.5 177t175.5 73h1242v-1489h-168v555h-283v-555h-168v555h-623q-104 0 -175.5 72.5t-71.5 175.5zM283 803q0 -33 23.5 -56.5t55.5 -23.5h623v596h-623q-33 0 -56 -23.5t-23 -56.5v-436zM1153 723h283v596h-283v-596z" />
<glyph unicode="&#xb8;" horiz-adv-x="436" d="M68 -350l71 284h162l-70 -284h-163z" />
<glyph unicode="&#xbf;" horiz-adv-x="1382" d="M39 248v397q0 104 71.5 177t176.5 73h559q33 0 56.5 23.5t23.5 56.5v98h168v-98q0 -104 -72 -177t-176 -73h-559q-33 0 -56.5 -23.5t-23.5 -56.5v-397q0 -33 23.5 -56.5t56.5 -23.5h1040q0 -59 4 -105.5t8 -62.5l4 -16l-1056 16q-104 0 -176 72.5t-72 175.5zM926 1321 v170h168v-170h-168z" />
<glyph unicode="&#xc0;" horiz-adv-x="1712" d="M119 0v1229q0 102 72 174t174 72h983q102 0 173.5 -72t71.5 -174v-1229h-166v539h-1142v-539h-166zM285 705h1142v524q0 33 -23.5 56.5t-55.5 23.5h-983q-33 0 -56.5 -23.5t-23.5 -56.5v-524zM705 2056h165l70 -284h-164z" />
<glyph unicode="&#xc1;" horiz-adv-x="1712" d="M119 0v1229q0 102 72 174t174 72h983q102 0 173.5 -72t71.5 -174v-1229h-166v539h-1142v-539h-166zM285 705h1142v524q0 33 -23.5 56.5t-55.5 23.5h-983q-33 0 -56.5 -23.5t-23.5 -56.5v-524zM705 1769l71 285h162l-70 -285h-163z" />
<glyph unicode="&#xc2;" horiz-adv-x="1712" d="M119 0v1229q0 102 72 174t174 72h983q102 0 173.5 -72t71.5 -174v-1229h-166v539h-1142v-539h-166zM285 705h1142v524q0 33 -23.5 56.5t-55.5 23.5h-983q-33 0 -56.5 -23.5t-23.5 -56.5v-524zM606 1769l146 240h120l146 -240h-86l-119 175l-123 -175h-84z" />
<glyph unicode="&#xc3;" horiz-adv-x="1712" d="M119 0v1229q0 102 72 174t174 72h983q102 0 173.5 -72t71.5 -174v-1229h-166v539h-1142v-539h-166zM285 705h1142v524q0 33 -23.5 56.5t-55.5 23.5h-983q-33 0 -56.5 -23.5t-23.5 -56.5v-524zM496 1911v96q55 18 127 18q82 0 219 -68.5t209 -78.5h4q61 0 135 35v-105 q-78 -29 -135 -28q-72 0 -209 73.5t-223 73.5q-80 0 -127 -16z" />
<glyph unicode="&#xc4;" horiz-adv-x="1712" d="M119 0v1229q0 102 72 174t174 72h983q102 0 173.5 -72t71.5 -174v-1229h-166v539h-1142v-539h-166zM285 705h1142v524q0 33 -23.5 56.5t-55.5 23.5h-983q-33 0 -56.5 -23.5t-23.5 -56.5v-524zM571 1751v168h170v-168h-170zM940 1751v168h168v-168h-168z" />
<glyph unicode="&#xc5;" horiz-adv-x="1712" d="M119 0v1229q0 102 72 174t174 72h983q102 0 173.5 -72t71.5 -174v-1229h-166v539h-1142v-539h-166zM285 705h1142v524q0 33 -23.5 56.5t-55.5 23.5h-983q-33 0 -56.5 -23.5t-23.5 -56.5v-524zM688 1790v111q0 45 33 76.5t78 31.5h114q45 0 78 -31.5t33 -76.5v-111 q0 -45 -33 -77t-78 -32h-114q-45 0 -78 32t-33 77zM770 1784q0 -21 18 -21h136q18 0 18 21v123q0 20 -18 20h-136q-18 0 -18 -20v-123z" />
<glyph unicode="&#xc6;" horiz-adv-x="2816" d="M111 0v1229q0 102 71.5 174t173.5 72h2409v-166h-1180v-488h948v-168h-948v-487h1180v-166h-1346v539h-1143v-539h-165zM276 705h1143v524q0 33 -23.5 56.5t-56.5 23.5h-983q-33 0 -56.5 -23.5t-23.5 -56.5v-524z" />
<glyph unicode="&#xc7;" horiz-adv-x="1683" d="M115 246v983q0 102 71.5 174t173.5 72h1225v-166h-1225q-33 0 -56 -23.5t-23 -56.5v-983q0 -33 23.5 -56.5t55.5 -23.5h1225v-166h-661l-70 -285h-164l72 285h-402q-102 0 -173.5 71.5t-71.5 174.5z" />
<glyph unicode="&#xc8;" horiz-adv-x="1568" d="M119 0v1475h1345v-166h-1179v-488h948v-168h-948v-487h1179v-166h-1345zM633 2056h166l69 -284h-163z" />
<glyph unicode="&#xc9;" horiz-adv-x="1568" d="M119 0v1475h1345v-166h-1179v-488h948v-168h-948v-487h1179v-166h-1345zM633 1772l72 284h161l-69 -284h-164z" />
<glyph unicode="&#xca;" horiz-adv-x="1568" d="M119 0v1475h1345v-166h-1179v-488h948v-168h-948v-487h1179v-166h-1345zM535 1769l145 240h121l145 -240h-86l-119 175l-123 -175h-83z" />
<glyph unicode="&#xcb;" horiz-adv-x="1568" d="M119 0v1475h1345v-166h-1179v-488h948v-168h-948v-487h1179v-166h-1345zM500 1772v167h170v-167h-170zM868 1772v167h168v-167h-168z" />
<glyph unicode="&#xcc;" horiz-adv-x="450" d="M74 2056h166l69 -284h-164zM117 0v1475h166v-1475h-166z" />
<glyph unicode="&#xcd;" horiz-adv-x="450" d="M74 1772l71 284h162l-69 -284h-164zM117 0v1475h166v-1475h-166z" />
<glyph unicode="&#xce;" horiz-adv-x="450" d="M-25 1769l146 240h121l145 -240h-86l-119 175l-123 -175h-84zM117 0v1475h166v-1475h-166z" />
<glyph unicode="&#xcf;" horiz-adv-x="450" d="M-59 1772v167h170v-167h-170zM117 0v1475h166v-1475h-166zM309 1772v167h168v-167h-168z" />
<glyph unicode="&#xd1;" horiz-adv-x="1703" d="M115 0v1475h227l1081 -1287v1287h166v-1475h-227l-1081 1286v-1286h-166zM492 1911v96q55 18 126 18q82 0 219.5 -68.5t209.5 -78.5h4q61 0 135 35v-105q-78 -29 -135 -28q-72 0 -209 73.5t-224 73.5q-80 0 -126 -16z" />
<glyph unicode="&#xd2;" d="M111 246v983q0 102 71.5 174t173.5 72h983q102 0 174 -72t72 -174v-983q0 -102 -71.5 -174t-174.5 -72h-983q-102 0 -173.5 71.5t-71.5 174.5zM276 246q0 -33 23.5 -56.5t56.5 -23.5h983q33 0 56.5 23.5t23.5 56.5v983q0 33 -23.5 56.5t-56.5 23.5h-983 q-33 0 -56.5 -23.5t-23.5 -56.5v-983zM696 2056h166l70 -284h-164z" />
<glyph unicode="&#xd3;" d="M111 246v983q0 102 71.5 174t173.5 72h983q102 0 174 -72t72 -174v-983q0 -102 -71.5 -174t-174.5 -72h-983q-102 0 -173.5 71.5t-71.5 174.5zM276 246q0 -33 23.5 -56.5t56.5 -23.5h983q33 0 56.5 23.5t23.5 56.5v983q0 33 -23.5 56.5t-56.5 23.5h-983 q-33 0 -56.5 -23.5t-23.5 -56.5v-983zM696 1772l72 284h162l-70 -284h-164z" />
<glyph unicode="&#xd4;" d="M111 246v983q0 102 71.5 174t173.5 72h983q102 0 174 -72t72 -174v-983q0 -102 -71.5 -174t-174.5 -72h-983q-102 0 -173.5 71.5t-71.5 174.5zM276 246q0 -33 23.5 -56.5t56.5 -23.5h983q33 0 56.5 23.5t23.5 56.5v983q0 33 -23.5 56.5t-56.5 23.5h-983 q-33 0 -56.5 -23.5t-23.5 -56.5v-983zM598 1769l145 240h121l146 -240h-86l-119 175l-123 -175h-84z" />
<glyph unicode="&#xd5;" d="M111 246v983q0 102 71.5 174t173.5 72h983q102 0 174 -72t72 -174v-983q0 -102 -71.5 -174t-174.5 -72h-983q-102 0 -173.5 71.5t-71.5 174.5zM276 246q0 -33 23.5 -56.5t56.5 -23.5h983q33 0 56.5 23.5t23.5 56.5v983q0 33 -23.5 56.5t-56.5 23.5h-983 q-33 0 -56.5 -23.5t-23.5 -56.5v-983zM487 1911v96q55 18 127 18q82 0 219 -68.5t209 -78.5h5q61 0 135 35v-105q-78 -29 -135 -28q-72 0 -209.5 73.5t-223.5 73.5q-80 0 -127 -16z" />
<glyph unicode="&#xd6;" d="M111 246v983q0 102 71.5 174t173.5 72h983q102 0 174 -72t72 -174v-983q0 -102 -71.5 -174t-174.5 -72h-983q-102 0 -173.5 71.5t-71.5 174.5zM276 246q0 -33 23.5 -56.5t56.5 -23.5h983q33 0 56.5 23.5t23.5 56.5v983q0 33 -23.5 56.5t-56.5 23.5h-983 q-33 0 -56.5 -23.5t-23.5 -56.5v-983zM563 1772v167h170v-167h-170zM932 1772v167h168v-167h-168z" />
<glyph unicode="&#xd7;" horiz-adv-x="1118" d="M109 1036h221l215 -295l217 295h217l-324 -420l324 -421h-217l-217 294l-215 -294h-219l323 421z" />
<glyph unicode="&#xd9;" d="M111 246v1229h165v-1229q0 -33 24 -56.5t56 -23.5h983q33 0 56.5 23.5t23.5 56.5v1229h166v-1229q0 -102 -71.5 -174t-174.5 -72h-983q-102 0 -173.5 71.5t-71.5 174.5zM696 2056h166l70 -284h-164z" />
<glyph unicode="&#xda;" d="M111 246v1229h165v-1229q0 -33 24 -56.5t56 -23.5h983q33 0 56.5 23.5t23.5 56.5v1229h166v-1229q0 -102 -71.5 -174t-174.5 -72h-983q-102 0 -173.5 71.5t-71.5 174.5zM696 1772l72 284h162l-70 -284h-164z" />
<glyph unicode="&#xdb;" d="M111 246v1229h165v-1229q0 -33 24 -56.5t56 -23.5h983q33 0 56.5 23.5t23.5 56.5v1229h166v-1229q0 -102 -71.5 -174t-174.5 -72h-983q-102 0 -173.5 71.5t-71.5 174.5zM598 1769l145 240h121l146 -240h-86l-119 175l-123 -175h-84z" />
<glyph unicode="&#xdc;" d="M111 246v1229h165v-1229q0 -33 24 -56.5t56 -23.5h983q33 0 56.5 23.5t23.5 56.5v1229h166v-1229q0 -102 -71.5 -174t-174.5 -72h-983q-102 0 -173.5 71.5t-71.5 174.5zM563 1772v167h170v-167h-170zM932 1772v167h168v-167h-168z" />
<glyph unicode="&#xdd;" horiz-adv-x="1650" d="M35 1475h190l572 -721l565 721h194l-677 -920v-555h-166v555zM674 1772l71 284h162l-69 -284h-164z" />
<glyph unicode="&#xdf;" horiz-adv-x="1705" d="M117 0v1241q0 104 71.5 177t176.5 73h993q82 0 152.5 -62.5t95.5 -146.5v-350q0 -82 -62 -166q61 -80 62 -164v-354q0 -102 -73 -175t-175 -73h-776v168h776q33 0 56.5 23.5t23.5 56.5v354q0 33 -23.5 56.5t-56.5 23.5h-776v145h776q33 0 56.5 23.5t23.5 56.5v332 q0 33 -23.5 56.5t-56.5 23.5h-993q-33 0 -56.5 -23.5t-23.5 -56.5v-1239h-168z" />
<glyph unicode="&#xe0;" horiz-adv-x="1484" d="M106 248v430h1041v262q0 33 -23.5 56.5t-56.5 23.5h-961v168h961q104 0 177 -73t73 -175v-940h-963q-102 0 -175 72.5t-73 175.5zM274 248q0 -33 24 -56.5t56 -23.5h793v342h-873v-262zM590 1847h166l69 -284h-163z" />
<glyph unicode="&#xe1;" horiz-adv-x="1484" d="M106 248v430h1041v262q0 33 -23.5 56.5t-56.5 23.5h-961v168h961q104 0 177 -73t73 -175v-940h-963q-102 0 -175 72.5t-73 175.5zM274 248q0 -33 24 -56.5t56 -23.5h793v342h-873v-262zM590 1563l72 284h161l-69 -284h-164z" />
<glyph unicode="&#xe2;" horiz-adv-x="1484" d="M106 248v430h1041v262q0 33 -23.5 56.5t-56.5 23.5h-961v168h961q104 0 177 -73t73 -175v-940h-963q-102 0 -175 72.5t-73 175.5zM274 248q0 -33 24 -56.5t56 -23.5h793v342h-873v-262zM492 1563l145 239h121l145 -239h-86l-119 174l-123 -174h-83z" />
<glyph unicode="&#xe3;" horiz-adv-x="1484" d="M106 248v430h1041v262q0 33 -23.5 56.5t-56.5 23.5h-961v168h961q104 0 177 -73t73 -175v-940h-963q-102 0 -175 72.5t-73 175.5zM274 248q0 -33 24 -56.5t56 -23.5h793v342h-873v-262zM381 1700v96q55 18 127 19q82 0 219 -69t209 -79h4q61 0 135 35v-105 q-78 -29 -135 -28q-72 0 -209 73.5t-223 73.5q-80 0 -127 -16z" />
<glyph unicode="&#xe4;" horiz-adv-x="1484" d="M106 248v430h1041v262q0 33 -23.5 56.5t-56.5 23.5h-961v168h961q104 0 177 -73t73 -175v-940h-963q-102 0 -175 72.5t-73 175.5zM274 248q0 -33 24 -56.5t56 -23.5h793v342h-873v-262zM457 1563v168h170v-168h-170zM825 1563v168h168v-168h-168z" />
<glyph unicode="&#xe5;" horiz-adv-x="1484" d="M106 248v430h1041v262q0 33 -23.5 56.5t-56.5 23.5h-961v168h961q104 0 177 -73t73 -175v-940h-963q-102 0 -175 72.5t-73 175.5zM274 248q0 -33 24 -56.5t56 -23.5h793v342h-873v-262zM573 1579v111q0 45 33 76.5t78 31.5h115q45 0 77.5 -31.5t32.5 -76.5v-111 q0 -45 -32.5 -77t-77.5 -32h-115q-45 0 -78 32t-33 77zM655 1573q1 -21 19 -21h135q18 0 18 21v123q0 20 -18 20h-135q-18 0 -19 -20v-123z" />
<glyph unicode="&#xe6;" horiz-adv-x="2412" d="M109 248v430h1040v262q0 33 -23.5 56.5t-56.5 23.5h-960v168h2000q104 0 176 -73t72 -175v-430h-1040v-270q2 -31 25.5 -51.5t54.5 -20.5h960v-168h-2001q-104 0 -175.5 72.5t-71.5 175.5zM276 248q0 -33 24 -56.5t56 -23.5h793v342h-873v-262zM1317 678h872v262 q0 33 -23.5 56.5t-56.5 23.5h-712q-33 0 -56.5 -23.5t-23.5 -56.5v-262z" />
<glyph unicode="&#xe7;" horiz-adv-x="1419" d="M104 248v692q0 102 73 175t175 73h959v-168h-959q-33 0 -56.5 -23.5t-23.5 -56.5v-692q0 -33 23.5 -56.5t56.5 -23.5h963v-168h-524l-70 -285h-164l72 285h-277q-102 0 -175 72.5t-73 175.5z" />
<glyph unicode="&#xe8;" horiz-adv-x="1306" d="M104 248v692q0 102 73 175t175 73h713q104 0 177 -73t73 -175v-430h-1043v-262q0 -33 23.5 -56.5t56.5 -23.5h963v-168h-963q-102 0 -175 72.5t-73 175.5zM272 678h873v262q0 33 -23.5 56.5t-56.5 23.5h-713q-33 0 -56.5 -23.5t-23.5 -56.5v-262zM502 1847h166l69 -284 h-164z" />
<glyph unicode="&#xe9;" horiz-adv-x="1306" d="M104 248v692q0 102 73 175t175 73h713q104 0 177 -73t73 -175v-430h-1043v-262q0 -33 23.5 -56.5t56.5 -23.5h963v-168h-963q-102 0 -175 72.5t-73 175.5zM272 678h873v262q0 33 -23.5 56.5t-56.5 23.5h-713q-33 0 -56.5 -23.5t-23.5 -56.5v-262zM502 1565l71 284h162 l-69 -284h-164z" />
<glyph unicode="&#xea;" horiz-adv-x="1306" d="M104 248v692q0 102 73 175t175 73h713q104 0 177 -73t73 -175v-430h-1043v-262q0 -33 23.5 -56.5t56.5 -23.5h963v-168h-963q-102 0 -175 72.5t-73 175.5zM272 678h873v262q0 33 -23.5 56.5t-56.5 23.5h-713q-33 0 -56.5 -23.5t-23.5 -56.5v-262zM403 1563l146 239h121 l145 -239h-86l-119 174l-123 -174h-84z" />
<glyph unicode="&#xeb;" horiz-adv-x="1306" d="M104 248v692q0 102 73 175t175 73h713q104 0 177 -73t73 -175v-430h-1043v-262q0 -33 23.5 -56.5t56.5 -23.5h963v-168h-963q-102 0 -175 72.5t-73 175.5zM272 678h873v262q0 33 -23.5 56.5t-56.5 23.5h-713q-33 0 -56.5 -23.5t-23.5 -56.5v-262zM369 1563v168h170v-168 h-170zM737 1563v168h168v-168h-168z" />
<glyph unicode="&#xec;" horiz-adv-x="438" d="M33 1849h166l69 -284h-164zM115 0v1188h168v-1188h-168z" />
<glyph unicode="&#xed;" horiz-adv-x="438" d="M100 1563l72 284h162l-70 -284h-164zM104 0v1188h168v-1188h-168z" />
<glyph unicode="&#xee;" horiz-adv-x="438" d="M-31 1563l146 239h121l145 -239h-86l-119 174l-123 -174h-84zM104 0v1188h168v-1188h-168z" />
<glyph unicode="&#xef;" horiz-adv-x="438" d="M-66 1567v168h170v-168h-170zM113 0v1188h168v-1188h-168zM303 1567v168h168v-168h-168z" />
<glyph unicode="&#xf1;" horiz-adv-x="1449" d="M111 0v1188h962q102 0 175 -73t73 -175v-940h-168v940q0 33 -23.5 56.5t-56.5 23.5h-713q-33 0 -57 -23.5t-24 -56.5v-940h-168zM365 1698v96q55 18 127 18q82 0 219 -68.5t209 -78.5h4q61 0 135 35v-105q-78 -29 -135 -28q-72 0 -209 73.5t-223 73.5q-80 0 -127 -16z " />
<glyph unicode="&#xf2;" horiz-adv-x="1417" d="M104 248v692q0 102 73 175t175 73h713q104 0 177 -73t73 -175v-692q0 -102 -73 -175t-177 -73h-713q-102 0 -175 72.5t-73 175.5zM272 248q0 -33 23.5 -56.5t56.5 -23.5h713q33 0 56.5 23.5t23.5 56.5v692q0 33 -23.5 56.5t-56.5 23.5h-713q-33 0 -56.5 -23.5 t-23.5 -56.5v-692zM557 1849h166l70 -284h-164z" />
<glyph unicode="&#xf3;" horiz-adv-x="1417" d="M104 248v692q0 102 73 175t175 73h713q104 0 177 -73t73 -175v-692q0 -102 -73 -175t-177 -73h-713q-102 0 -175 72.5t-73 175.5zM272 248q0 -33 23.5 -56.5t56.5 -23.5h713q33 0 56.5 23.5t23.5 56.5v692q0 33 -23.5 56.5t-56.5 23.5h-713q-33 0 -56.5 -23.5 t-23.5 -56.5v-692zM557 1565l72 284h162l-70 -284h-164z" />
<glyph unicode="&#xf4;" horiz-adv-x="1417" d="M104 248v692q0 102 73 175t175 73h713q104 0 177 -73t73 -175v-692q0 -102 -73 -175t-177 -73h-713q-102 0 -175 72.5t-73 175.5zM272 248q0 -33 23.5 -56.5t56.5 -23.5h713q33 0 56.5 23.5t23.5 56.5v692q0 33 -23.5 56.5t-56.5 23.5h-713q-33 0 -56.5 -23.5 t-23.5 -56.5v-692zM459 1563l145 239h121l145 -239h-86l-118 174l-123 -174h-84z" />
<glyph unicode="&#xf5;" horiz-adv-x="1417" d="M104 248v692q0 102 73 175t175 73h713q104 0 177 -73t73 -175v-692q0 -102 -73 -175t-177 -73h-713q-102 0 -175 72.5t-73 175.5zM272 248q0 -33 23.5 -56.5t56.5 -23.5h713q33 0 56.5 23.5t23.5 56.5v692q0 33 -23.5 56.5t-56.5 23.5h-713q-33 0 -56.5 -23.5 t-23.5 -56.5v-692zM348 1698v96q55 18 127 18q82 0 219 -68.5t209 -78.5h4q61 0 135 35v-105q-78 -29 -135 -28q-72 0 -209 73.5t-223 73.5q-80 0 -127 -16z" />
<glyph unicode="&#xf6;" horiz-adv-x="1417" d="M104 248v692q0 102 73 175t175 73h713q104 0 177 -73t73 -175v-692q0 -102 -73 -175t-177 -73h-713q-102 0 -175 72.5t-73 175.5zM272 248q0 -33 23.5 -56.5t56.5 -23.5h713q33 0 56.5 23.5t23.5 56.5v692q0 33 -23.5 56.5t-56.5 23.5h-713q-33 0 -56.5 -23.5 t-23.5 -56.5v-692zM424 1567v168h170v-168h-170zM793 1567v168h168v-168h-168z" />
<glyph unicode="&#xf7;" horiz-adv-x="1040" d="M37 522v168h971v-168h-971zM440 2v168h168v-168h-168zM440 1018v168h168v-168h-168z" />
<glyph unicode="&#xf9;" horiz-adv-x="1423" d="M109 248v940h167v-940q0 -33 24 -56.5t56 -23.5h713q33 0 57.5 23.5t24.5 56.5v940h168v-940q0 -102 -72.5 -175t-177.5 -73h-713q-102 0 -174.5 72.5t-72.5 175.5zM559 1849h166l70 -284h-164z" />
<glyph unicode="&#xfa;" horiz-adv-x="1423" d="M109 248v940h167v-940q0 -33 24 -56.5t56 -23.5h713q33 0 57.5 23.5t24.5 56.5v940h168v-940q0 -102 -72.5 -175t-177.5 -73h-713q-102 0 -174.5 72.5t-72.5 175.5zM559 1565l72 284h162l-70 -284h-164z" />
<glyph unicode="&#xfb;" horiz-adv-x="1423" d="M109 248v940h167v-940q0 -33 24 -56.5t56 -23.5h713q33 0 57.5 23.5t24.5 56.5v940h168v-940q0 -102 -72.5 -175t-177.5 -73h-713q-102 0 -174.5 72.5t-72.5 175.5zM461 1565l145 239h121l145 -239h-86l-118 174l-123 -174h-84z" />
<glyph unicode="&#xfc;" horiz-adv-x="1423" d="M109 248v940h167v-940q0 -33 24 -56.5t56 -23.5h713q33 0 57.5 23.5t24.5 56.5v940h168v-940q0 -102 -72.5 -175t-177.5 -73h-713q-102 0 -174.5 72.5t-72.5 175.5zM426 1563v168h170v-168h-170zM795 1563v168h168v-168h-168z" />
<glyph unicode="&#xfd;" horiz-adv-x="1409" d="M86 248v936h168v-936q0 -33 23.5 -56.5t56.5 -23.5h713q33 0 56 23.5t23 56.5v936h168v-1405q0 -104 -71.5 -177t-175.5 -73h-742v170h742q33 0 56 23.5t23 56.5v221h-792q-102 0 -175 72.5t-73 175.5zM553 1565l72 284h161l-69 -284h-164z" />
<glyph unicode="&#xff;" horiz-adv-x="1409" d="M86 248v936h168v-936q0 -33 23.5 -56.5t56.5 -23.5h713q33 0 56 23.5t23 56.5v936h168v-1405q0 -104 -71.5 -177t-175.5 -73h-742v170h742q33 0 56 23.5t23 56.5v221h-792q-102 0 -175 72.5t-73 175.5zM420 1567v168h170v-168h-170zM788 1567v168h168v-168h-168z" />
<glyph unicode="&#x152;" horiz-adv-x="2813" d="M109 246v983q0 102 71.5 174t173.5 72h2409v-166h-1180v-488h948v-168h-948v-487h1180v-166h-2409q-102 0 -173.5 71.5t-71.5 174.5zM274 246q0 -33 24 -56.5t56 -23.5h983q33 0 56.5 23.5t23.5 56.5v983q0 33 -23.5 56.5t-56.5 23.5h-983q-33 0 -56.5 -23.5t-23.5 -56.5 v-983z" />
<glyph unicode="&#x153;" horiz-adv-x="2410" d="M106 248v692q0 102 72 175t176 73h1753q104 0 176 -73t72 -175v-430h-1040v-262q0 -33 23.5 -56.5t56.5 -23.5h960v-168h-2001q-104 0 -176 72.5t-72 175.5zM274 248q0 -33 24 -56.5t56 -23.5h713q33 0 56.5 23.5t23.5 56.5v692q0 33 -23.5 56.5t-56.5 23.5h-713 q-33 0 -56.5 -23.5t-23.5 -56.5v-692zM1315 678h872v262q0 33 -23.5 56.5t-56.5 23.5h-712q-33 0 -56.5 -23.5t-23.5 -56.5v-262z" />
<glyph unicode="&#x178;" horiz-adv-x="1650" d="M35 1475h190l572 -721l565 721h194l-677 -920v-555h-166v555zM541 1772v167h170v-167h-170zM909 1772v167h168v-167h-168z" />
<glyph unicode="&#x2c6;" horiz-adv-x="614" d="M57 1784l146 239h121l145 -239h-86l-119 174l-123 -174h-84z" />
<glyph unicode="&#x2dc;" horiz-adv-x="825" d="M47 1925v96q55 18 127 19q82 0 219 -69t209 -79h4q61 0 135 35v-104q-78 -29 -135 -29q-72 0 -209 74t-223 74q-80 0 -127 -17z" />
<glyph unicode="&#x2000;" horiz-adv-x="1035" />
<glyph unicode="&#x2001;" horiz-adv-x="2071" />
<glyph unicode="&#x2002;" horiz-adv-x="1035" />
<glyph unicode="&#x2003;" horiz-adv-x="2071" />
<glyph unicode="&#x2004;" horiz-adv-x="690" />
<glyph unicode="&#x2005;" horiz-adv-x="517" />
<glyph unicode="&#x2006;" horiz-adv-x="345" />
<glyph unicode="&#x2007;" horiz-adv-x="345" />
<glyph unicode="&#x2008;" horiz-adv-x="258" />
<glyph unicode="&#x2009;" horiz-adv-x="414" />
<glyph unicode="&#x200a;" horiz-adv-x="115" />
<glyph unicode="&#x2010;" horiz-adv-x="1058" d="M121 522v168h799v-168h-799z" />
<glyph unicode="&#x2011;" horiz-adv-x="1058" d="M121 522v168h799v-168h-799z" />
<glyph unicode="&#x2012;" horiz-adv-x="1058" d="M121 522v168h799v-168h-799z" />
<glyph unicode="&#x2013;" horiz-adv-x="1449" d="M111 520v168h1230v-168h-1230z" />
<glyph unicode="&#x2014;" horiz-adv-x="1683" d="M111 520v168h1476v-168h-1476z" />
<glyph unicode="&#x2018;" horiz-adv-x="344" d="M68 1030v201q0 82 47 147.5t123 87.5v-436h-170z" />
<glyph unicode="&#x2019;" horiz-adv-x="339" d="M111 1040v437h168v-201q0 -82 -47.5 -147.5t-120.5 -88.5z" />
<glyph unicode="&#x201c;" horiz-adv-x="743" d="M96 1028v201q0 82 47 147.5t123 87.5v-436h-170zM467 1028v201q0 82 46 147.5t122 87.5v-436h-168z" />
<glyph unicode="&#x201d;" horiz-adv-x="743" d="M111 1040v437h170v-201q0 -82 -47.5 -147.5t-122.5 -88.5zM481 1040v437h168v-201q0 -82 -47 -147.5t-121 -88.5z" />
<glyph unicode="&#x2022;" horiz-adv-x="759" d="M295 723v43q0 59 57 59h51q59 0 60 -59v-43q0 -57 -60 -57h-51q-57 0 -57 57z" />
<glyph unicode="&#x2026;" horiz-adv-x="1175" d="M111 0v168h168v-168h-168zM498 0v168h168v-168h-168zM883 0v168h170v-168h-170z" />
<glyph unicode="&#x202f;" horiz-adv-x="414" />
<glyph unicode="&#x205f;" horiz-adv-x="517" />
<glyph unicode="&#x20ac;" horiz-adv-x="1636" d="M72 471v168h215v211h-215v170h215v207q0 104 72.5 177t177.5 73h1022v-170h-1022q-33 0 -56.5 -23.5t-23.5 -56.5v-207h868v-170h-868v-211h868v-168h-868v-223q0 -33 23.5 -56.5t56.5 -23.5h1022v-168h-1022q-104 0 -177 72.5t-73 175.5v223h-215z" />
<glyph unicode="&#x25fc;" horiz-adv-x="1187" d="M0 0v1188h1188v-1188h-1188z" />
<hkern u1="&#x3f;" u2="v" k="2" />
<hkern u1="A" u2="Y" k="27" />
<hkern u1="A" u2="W" k="63" />
<hkern u1="B" u2="Y" k="63" />
<hkern u1="B" u2="V" k="100" />
<hkern u1="D" u2="Z" k="55" />
<hkern u1="D" u2="V" k="59" />
<hkern u1="E" u2="O" k="63" />
<hkern u1="E" u2="M" k="59" />
<hkern u1="F" u2="y" k="41" />
<hkern u1="F" u2="T" k="-20" />
<hkern u1="F" u2="R" k="-10" />
<hkern u1="F" u2="J" k="492" />
<hkern u1="G" u2="W" k="55" />
<hkern u1="K" u2="H" k="59" />
<hkern u1="K" u2="A" k="61" />
<hkern u1="L" u2="Y" k="367" />
<hkern u1="L" u2="W" k="264" />
<hkern u1="L" u2="V" k="473" />
<hkern u1="M" u2="c" k="20" />
<hkern u1="O" u2="X" k="80" />
<hkern u1="O" u2="W" k="55" />
<hkern u1="O" u2="V" k="55" />
<hkern u1="P" u2="v" k="-2" />
<hkern u1="P" u2="d" k="-20" />
<hkern u1="P" u2="J" k="383" />
<hkern u1="P" u2="A" k="-20" />
<hkern u1="R" u2="W" k="39" />
<hkern u1="R" u2="V" k="39" />
<hkern u1="S" u2="Y" k="20" />
<hkern u1="S" u2="W" k="51" />
<hkern u1="S" u2="N" k="39" />
<hkern u1="T" u2="z" k="211" />
<hkern u1="T" u2="y" k="186" />
<hkern u1="T" u2="w" k="170" />
<hkern u1="T" u2="u" k="207" />
<hkern u1="T" u2="s" k="248" />
<hkern u1="T" u2="o" k="252" />
<hkern u1="V" u2="s" k="82" />
<hkern u1="V" u2="o" k="61" />
<hkern u1="V" u2="S" k="41" />
<hkern u1="V" u2="O" k="68" />
<hkern u1="V" u2="A" k="76" />
<hkern u1="W" u2="u" k="41" />
<hkern u1="W" u2="o" k="51" />
<hkern u1="W" u2="i" k="20" />
<hkern u1="W" u2="e" k="72" />
<hkern u1="W" u2="a" k="31" />
<hkern u1="W" u2="O" k="47" />
<hkern u1="W" u2="A" k="55" />
<hkern u1="X" u2="B" k="72" />
<hkern u1="Y" u2="u" k="199" />
<hkern u1="Y" u2="s" k="240" />
<hkern u1="Y" u2="p" k="203" />
<hkern u1="Y" u2="o" k="244" />
<hkern u1="Y" u2="i" k="39" />
<hkern u1="Y" u2="e" k="244" />
<hkern u1="Y" u2="a" k="199" />
<hkern u1="Y" u2="S" k="72" />
<hkern u1="Z" u2="Y" k="20" />
<hkern u1="a" u2="z" k="51" />
<hkern u1="a" u2="x" k="31" />
<hkern u1="a" u2="w" k="61" />
<hkern u1="a" u2="v" k="41" />
<hkern u1="a" u2="s" k="41" />
<hkern u1="a" u2="r" k="10" />
<hkern u1="a" u2="n" k="31" />
<hkern u1="a" u2="m" k="41" />
<hkern u1="a" u2="l" k="20" />
<hkern u1="a" u2="c" k="20" />
<hkern u1="a" u2="b" k="51" />
<hkern u1="b" u2="d" k="-61" />
<hkern u1="c" u2="z" k="41" />
<hkern u1="c" u2="u" k="41" />
<hkern u1="c" u2="t" k="51" />
<hkern u1="c" u2="k" k="41" />
<hkern u1="c" u2="h" k="31" />
<hkern u1="c" u2="e" k="51" />
<hkern u1="d" u2="u" k="47" />
<hkern u1="d" u2="o" k="45" />
<hkern u1="d" u2="e" k="41" />
<hkern u1="e" u2="x" k="35" />
<hkern u1="e" u2="w" k="61" />
<hkern u1="e" u2="v" k="41" />
<hkern u1="e" u2="r" k="10" />
<hkern u1="e" u2="l" k="41" />
<hkern u1="e" u2="e" k="31" />
<hkern u1="e" u2="c" k="41" />
<hkern u1="f" u2="u" k="-20" />
<hkern u1="f" u2="t" k="-10" />
<hkern u1="f" u2="&#x2c;" k="371" />
<hkern u1="g" u2="w" k="41" />
<hkern u1="g" u2="u" k="51" />
<hkern u1="g" u2="s" k="43" />
<hkern u1="g" u2="r" k="31" />
<hkern u1="g" u2="o" k="41" />
<hkern u1="g" u2="e" k="31" />
<hkern u1="g" u2="c" k="31" />
<hkern u1="g" u2="a" k="31" />
<hkern u1="h" u2="i" k="-10" />
<hkern u1="h" u2="f" k="-20" />
<hkern u1="h" u2="a" k="-10" />
<hkern u1="i" u2="x" k="84" />
<hkern u1="i" u2="v" k="10" />
<hkern u1="i" u2="q" k="31" />
<hkern u1="i" u2="p" k="51" />
<hkern u1="i" u2="n" k="31" />
<hkern u1="i" u2="f" k="96" />
<hkern u1="i" u2="c" k="31" />
<hkern u1="i" u2="a" k="72" />
<hkern u1="j" u2="e" k="41" />
<hkern u1="k" u2="e" k="10" />
<hkern u1="l" u2="&#x2019;" k="178" />
<hkern u1="l" u2="w" k="133" />
<hkern u1="l" u2="v" k="121" />
<hkern u1="l" u2="o" k="20" />
<hkern u1="l" u2="m" k="20" />
<hkern u1="l" u2="e" k="31" />
<hkern u1="l" u2="d" k="-10" />
<hkern u1="l" u2="a" k="20" />
<hkern u1="m" u2="w" k="51" />
<hkern u1="m" u2="m" k="27" />
<hkern u1="m" u2="k" k="27" />
<hkern u1="n" u2="x" k="51" />
<hkern u1="n" u2="p" k="20" />
<hkern u1="n" u2="o" k="31" />
<hkern u1="n" u2="c" k="41" />
<hkern u1="n" u2="b" k="41" />
<hkern u1="o" u2="x" k="72" />
<hkern u1="o" u2="w" k="72" />
<hkern u1="o" u2="v" k="27" />
<hkern u1="o" u2="u" k="47" />
<hkern u1="o" u2="t" k="47" />
<hkern u1="o" u2="s" k="41" />
<hkern u1="o" u2="r" k="20" />
<hkern u1="o" u2="o" k="41" />
<hkern u1="o" u2="n" k="31" />
<hkern u1="o" u2="l" k="41" />
<hkern u1="o" u2="a" k="20" />
<hkern u1="p" u2="s" k="-20" />
<hkern u1="p" u2="i" k="-31" />
<hkern u1="p" u2="d" k="-51" />
<hkern u1="q" u2="u" k="51" />
<hkern u1="r" u2="o" k="10" />
<hkern u1="r" u2="&#x2e;" k="41" />
<hkern u1="r" u2="&#x2c;" k="498" />
<hkern u1="s" u2="z" k="47" />
<hkern u1="s" u2="x" k="76" />
<hkern u1="s" u2="v" k="41" />
<hkern u1="s" u2="u" k="41" />
<hkern u1="s" u2="n" k="47" />
<hkern u1="s" u2="k" k="31" />
<hkern u1="s" u2="e" k="31" />
<hkern u1="s" u2="c" k="39" />
<hkern u1="t" u2="z" k="-10" />
<hkern u1="t" u2="i" k="-10" />
<hkern u1="t" u2="h" k="-10" />
<hkern u1="t" u2="d" k="-41" />
<hkern u1="t" u2="b" k="-10" />
<hkern u1="u" u2="z" k="31" />
<hkern u1="u" u2="x" k="31" />
<hkern u1="u" u2="t" k="31" />
<hkern u1="u" u2="s" k="31" />
<hkern u1="u" u2="p" k="31" />
<hkern u1="u" u2="n" k="-10" />
<hkern u1="u" u2="m" k="41" />
<hkern u1="u" u2="f" k="20" />
<hkern u1="u" u2="e" k="31" />
<hkern u1="u" u2="d" k="-20" />
<hkern u1="u" u2="c" k="41" />
<hkern u1="u" u2="b" k="31" />
<hkern u1="v" u2="s" k="51" />
<hkern u1="v" u2="o" k="59" />
<hkern u1="v" u2="e" k="61" />
<hkern u1="w" u2="s" k="31" />
<hkern u1="w" u2="r" k="41" />
<hkern u1="w" u2="o" k="41" />
<hkern u1="w" u2="e" k="41" />
<hkern u1="x" u2="e" k="100" />
<hkern u1="y" u2="o" k="47" />
<hkern u1="y" u2="n" k="31" />
<hkern u1="y" u2="l" k="-20" />
<hkern u1="y" u2="e" k="41" />
<hkern u1="y" u2="c" k="41" />
<hkern u1="y" u2="a" k="47" />
<hkern u1="z" u2="z" k="41" />
<hkern u1="z" u2="t" k="41" />
<hkern u1="z" u2="l" k="41" />
<hkern u1="z" u2="e" k="51" />
<hkern u1="z" u2="a" k="51" />
</font>
</defs></svg>

Before

Width:  |  Height:  |  Size: 45 KiB

View File

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 31 KiB

View File

@@ -9,6 +9,7 @@
<meta name="mobile-web-app-capable" content="yes">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
<link rel="manifest" href="/manifest.json">
<link rel="shortcut icon" href=/favicon2.ico>
<link rel="icon" sizes="152x152 192x192" type="image/png" href="/192x192.png">
<!-- Apple/iOS headers -->

View File

@@ -108,6 +108,7 @@ h1 {
font-family: @fTitle;
color: @primary;
font-size: 1.6em;
letter-spacing: 0.1em;
}
h2 {

View File

@@ -41,8 +41,9 @@ svg {
}
.label {
.label, .text-tip {
text-transform: capitalize;
fill: @fg;
}
.metric {
@@ -55,13 +56,16 @@ svg {
.label {
font-size: 0.75em;
fill: @fg;
}
.text-tip {
font-size: 0.8em
}
.tooltip {
fill: @bgBlack;
stroke: @secondary;
stroke-width: 1px;
font-size: 0.75em
font-size: 0.8em
}
}

View File

@@ -105,7 +105,7 @@
vertical-align: top;
}
table {
.build-section {
box-sizing: border-box;
display: inline-block;
width: 45%;
@@ -114,11 +114,20 @@
width: 100%;
});
thead {
display: block;
h1 {
box-sizing: border-box;
margin: 0;
width: 100%;
font-size: 1em;
color: #000;
padding: 2px 0.4em 0;
background-color: #c06400;
text-transform: uppercase;
line-height: 1.3em;
font-weight: normal;
}
tbody {
div {
display: block;
font-size: 0.8em;
width: 100%;
@@ -129,6 +138,10 @@
max-height: 8em;
}
table {
width: 100%;
}
td {
cursor: pointer;
vertical-align: top;

View File

@@ -1,14 +1,3 @@
@font-face {
font-family: 'Orbitron-Regular';
src: url('../fonts/orbitron-regular-webfont.eot');
src: url('../fonts/orbitron-regular-webfont.eot?#iefix') format('embedded-opentype'),
url('../fonts/orbitron-regular-webfont.woff2') format('woff2'),
url('../fonts/orbitron-regular-webfont.woff') format('woff'),
url('../fonts/orbitron-regular-webfont.ttf') format('truetype'),
url('../fonts/orbitron-regular-webfont.svg#orbitronregular') format('svg');
font-weight: normal;
font-style: normal;
}
@font-face {
font-family: 'Eurostile';
@@ -23,4 +12,4 @@
}
@fStandard: 'Eurostile', Helvetica, sans-serif;
@fTitle: 'Orbitron-Regular', Arial, sans-serif;
@fTitle: 'Eurostile', Arial, sans-serif;

View File

@@ -47,7 +47,7 @@ header {
padding : 0 1em;
cursor: pointer;
color: @warning;
text-transform: capitalize;
text-transform: uppercase;
// Less than 600px screen width: hide text
&.disabled {

View File

@@ -25,8 +25,8 @@ CopyDirPlugin.prototype.apply = function(compiler) {
module.exports = {
entry: {
app: path.resolve(__dirname, 'src/app/index'),
lib: ['d3', 'react', 'react-dom', 'classnames', 'fbemitter', 'lz-string']
app: ['babel-polyfill', path.resolve(__dirname, 'src/app/index')],
lib: ['babel-polyfill', 'd3', 'react', 'react-dom', 'classnames', 'fbemitter', 'lz-string']
},
resolve: {
extensions: ['', '.js', '.jsx', '.json', '.less'],
@@ -39,9 +39,9 @@ module.exports = {
},
output: {
path: path.join(__dirname, 'build'),
filename: '[name].[hash:6].js',
chunkFilename: '[name].[hash:6]',
publicPath: process.env.CDN || '/'
filename: '[name].[chunkhash:6].js',
chunkFilename: '[name].[chunkhash:6]',
publicPath: '/'
},
plugins: [
new webpack.optimize.UglifyJsPlugin({
@@ -50,7 +50,7 @@ module.exports = {
},
'screw-ie8': true
}),
new webpack.optimize.CommonsChunkPlugin('lib', 'lib.[hash:6].js'),
new webpack.optimize.CommonsChunkPlugin('lib', 'lib.[chunkhash:6].js'),
new HtmlWebpackPlugin({
inject: false,
appCache: 'coriolis.appcache',