mirror of
https://github.com/EDCD/coriolis.git
synced 2025-12-10 15:15:34 +00:00
Continued porting to React
This commit is contained in:
@@ -8,13 +8,15 @@ import Header from './components/Header';
|
|||||||
import AboutPage from './pages/AboutPage';
|
import AboutPage from './pages/AboutPage';
|
||||||
import NotFoundPage from './pages/NotFoundPage';
|
import NotFoundPage from './pages/NotFoundPage';
|
||||||
import OutfittingPage from './pages/OutfittingPage';
|
import OutfittingPage from './pages/OutfittingPage';
|
||||||
|
import ComparisonPage from './pages/ComparisonPage';
|
||||||
import ShipyardPage from './pages/ShipyardPage';
|
import ShipyardPage from './pages/ShipyardPage';
|
||||||
|
|
||||||
export default class Coriolis extends React.Component {
|
export default class Coriolis extends React.Component {
|
||||||
|
|
||||||
static childContextTypes = {
|
static childContextTypes = {
|
||||||
language: React.PropTypes.object.isRequired,
|
language: React.PropTypes.object.isRequired,
|
||||||
route: React.PropTypes.object
|
sizeRatio: React.PropTypes.number.isRequired,
|
||||||
|
route: React.PropTypes.object.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
@@ -24,19 +26,21 @@ export default class Coriolis extends React.Component {
|
|||||||
this._closeMenu = this._closeMenu.bind(this);
|
this._closeMenu = this._closeMenu.bind(this);
|
||||||
this._showModal = this._showModal.bind(this);
|
this._showModal = this._showModal.bind(this);
|
||||||
this._hideModal = this._hideModal.bind(this);
|
this._hideModal = this._hideModal.bind(this);
|
||||||
this._onLanguageChange = this._onLanguageChange.bind(this)
|
this._onLanguageChange = this._onLanguageChange.bind(this);
|
||||||
|
this._onSizeRatioChange = this._onSizeRatioChange.bind(this)
|
||||||
this._keyDown = this._keyDown.bind(this);
|
this._keyDown = this._keyDown.bind(this);
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
page: null,
|
page: null,
|
||||||
language: getLanguage(Persist.getLangCode()),
|
language: getLanguage(Persist.getLangCode()),
|
||||||
route: null
|
route: null,
|
||||||
|
sizeRatio: Persist.getSizeRatio()
|
||||||
};
|
};
|
||||||
|
|
||||||
Router('', (r) => this._setPage(ShipyardPage, r));
|
Router('', (r) => this._setPage(ShipyardPage, r));
|
||||||
Router('/outfit/:ship/:code?', (r) => this._setPage(OutfittingPage, r));
|
Router('/outfit/:ship/:code?', (r) => this._setPage(OutfittingPage, r));
|
||||||
// Router('/compare/:name', compare);
|
Router('/compare/:name?', (r) => this._setPage(ComparisonPage, r));
|
||||||
// Router('/comparison/:code', comparison);
|
Router('/comparison/:code', (r) => this._setPage(ComparisonPage, r));
|
||||||
Router('/about', (r) => this._setPage(AboutPage, r));
|
Router('/about', (r) => this._setPage(AboutPage, r));
|
||||||
Router('*', (r) => this._setPage(null, r));
|
Router('*', (r) => this._setPage(null, r));
|
||||||
}
|
}
|
||||||
@@ -54,6 +58,10 @@ export default class Coriolis extends React.Component {
|
|||||||
this.setState({ language: getLanguage(Persist.getLangCode()) });
|
this.setState({ language: getLanguage(Persist.getLangCode()) });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_onSizeRatioChange(sizeRatio) {
|
||||||
|
this.setState({ sizeRatio });
|
||||||
|
}
|
||||||
|
|
||||||
_keyDown(e) {
|
_keyDown(e) {
|
||||||
switch (e.keyCode) {
|
switch (e.keyCode) {
|
||||||
case 27:
|
case 27:
|
||||||
@@ -63,36 +71,59 @@ export default class Coriolis extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Opens the modal display with the specified content
|
||||||
|
* @param {React.Component} content Modal Content
|
||||||
|
*/
|
||||||
_showModal(content) {
|
_showModal(content) {
|
||||||
let modal = <div className='modal-bg' onClick={(e) => this._hideModal() }>{content}</div>;
|
let modal = <div className='modal-bg' onClick={(e) => this._hideModal() }>{content}</div>;
|
||||||
this.setState({ modal });
|
this.setState({ modal });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hides any open modal
|
||||||
|
*/
|
||||||
_hideModal() {
|
_hideModal() {
|
||||||
if (this.state.modal) {
|
if (this.state.modal) {
|
||||||
this.setState({ modal: null });
|
this.setState({ modal: null });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the open menu state
|
||||||
|
* @param {string|object} currentMenu The reference to the current menu
|
||||||
|
*/
|
||||||
_openMenu(currentMenu) {
|
_openMenu(currentMenu) {
|
||||||
if (this.state.currentMenu != currentMenu) {
|
if (this.state.currentMenu != currentMenu) {
|
||||||
this.setState({ currentMenu });
|
this.setState({ currentMenu });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Closes the open menu
|
||||||
|
*/
|
||||||
_closeMenu() {
|
_closeMenu() {
|
||||||
if (this.state.currentMenu) {
|
if (this.state.currentMenu) {
|
||||||
this.setState({ currentMenu: null });
|
this.setState({ currentMenu: null });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the context to be passed down to pages / components containing
|
||||||
|
* language, sizeRatio and route references
|
||||||
|
* @return {object} Context to be passed down
|
||||||
|
*/
|
||||||
getChildContext() {
|
getChildContext() {
|
||||||
return {
|
return {
|
||||||
language: this.state.language,
|
language: this.state.language,
|
||||||
route: this.state.route
|
route: this.state.route,
|
||||||
|
sizeRatio: this.state.sizeRatio
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds necessary listeners and starts Routing
|
||||||
|
*/
|
||||||
componentWillMount() {
|
componentWillMount() {
|
||||||
// Listen for appcache updated event, present refresh to update view
|
// Listen for appcache updated event, present refresh to update view
|
||||||
if (window.applicationCache) {
|
if (window.applicationCache) {
|
||||||
@@ -107,7 +138,7 @@ export default class Coriolis extends React.Component {
|
|||||||
window.addEventListener('resize', InterfaceEvents.windowResized);
|
window.addEventListener('resize', InterfaceEvents.windowResized);
|
||||||
document.addEventListener('keydown', this._keyDown);
|
document.addEventListener('keydown', this._keyDown);
|
||||||
Persist.addListener('language', this._onLanguageChange);
|
Persist.addListener('language', this._onLanguageChange);
|
||||||
Persist.addListener('language', this._onLanguageChange);
|
Persist.addListener('sizeRatio', this._onSizeRatioChange);
|
||||||
InterfaceEvents.addListener('openMenu', this._openMenu);
|
InterfaceEvents.addListener('openMenu', this._openMenu);
|
||||||
InterfaceEvents.addListener('closeMenu', this._closeMenu);
|
InterfaceEvents.addListener('closeMenu', this._closeMenu);
|
||||||
InterfaceEvents.addListener('showModal', this._showModal);
|
InterfaceEvents.addListener('showModal', this._showModal);
|
||||||
@@ -116,6 +147,10 @@ export default class Coriolis extends React.Component {
|
|||||||
Router.start();
|
Router.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renders the main app
|
||||||
|
* @return {React.Component} The main app
|
||||||
|
*/
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div onClick={this._closeMenu}>
|
<div onClick={this._closeMenu}>
|
||||||
|
|||||||
102
src/app/components/ComparisonTable.jsx
Normal file
102
src/app/components/ComparisonTable.jsx
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import TranslatedComponent from './TranslatedComponent';
|
||||||
|
import Link from './Link';
|
||||||
|
import cn from 'classnames';
|
||||||
|
import { SizeMap } from '../shipyard/Constants';
|
||||||
|
|
||||||
|
|
||||||
|
export default class ComparisonTable extends TranslatedComponent {
|
||||||
|
|
||||||
|
static propTypes = {
|
||||||
|
facets: React.PropTypes.array.isRequired,
|
||||||
|
builds: React.PropTypes.array.isRequired,
|
||||||
|
onSort: React.PropTypes.func.isRequired,
|
||||||
|
predicate: React.PropTypes.string.isRequired, // Used only to test again prop changes for shouldRender
|
||||||
|
desc: React.PropTypes.bool.isRequired, // Used only to test again prop changes for shouldRender
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(props, context) {
|
||||||
|
super(props, context);
|
||||||
|
this._buildHeaders = this._buildHeaders.bind(this);
|
||||||
|
|
||||||
|
this.state = this._buildHeaders(props.facets, props.onSort, context.language.translate);
|
||||||
|
}
|
||||||
|
|
||||||
|
_buildHeaders(facets, onSort, translate) {
|
||||||
|
let header = [
|
||||||
|
<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 = [];
|
||||||
|
|
||||||
|
for (let f of facets) {
|
||||||
|
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 })} 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 } )} onClick={onSort.bind(null, p[i])} >{translate(f.lbls[i])}</th>);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { header, subHeader };
|
||||||
|
}
|
||||||
|
|
||||||
|
_buildRow(build, facets, formats, units) {
|
||||||
|
let url = `/outfit/${build.id}/${build.toString()}?bn=${build.buildName}`;
|
||||||
|
let cells = [
|
||||||
|
<td key='s' className='tl'><Link href={url}>{build.name}</Link></td>,
|
||||||
|
<td key='bn' className='tl'><Link href={url}>{build.buildName}</Link></td>
|
||||||
|
];
|
||||||
|
|
||||||
|
for (let f of facets) {
|
||||||
|
if (f.active) {
|
||||||
|
for (let p of f.props) {
|
||||||
|
cells.push(<td key={p}>{formats[f.fmt](build[p])}{f.unit ? units[f.unit] : null}</td>);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return <tr key={build.id + build.buildName} className='tr'>{cells}</tr>;
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillReceiveProps(nextProps, nextContext) {
|
||||||
|
// If facets or language has changed re-render header
|
||||||
|
if (nextProps.facets != this.props.facets || nextContext.language != this.context.language) {
|
||||||
|
this.setState(this._buildHeaders(nextProps.facets, nextProps.onSort, nextContext.language.translate));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
let { builds, facets } = this.props;
|
||||||
|
let { header, subHeader } = this.state;
|
||||||
|
let { formats, units } = this.context.language;
|
||||||
|
|
||||||
|
let buildsRows = new Array(builds.length);
|
||||||
|
|
||||||
|
for (let i = 0, l = buildsRows.length; i < l; i++) {
|
||||||
|
buildsRows[i] = this._buildRow(builds[i], facets, formats, units);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='scroll-x'>
|
||||||
|
<table id='comp-tbl'>
|
||||||
|
<thead>
|
||||||
|
<tr className='main'>{header}</tr>
|
||||||
|
<tr>{subHeader}</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{buildsRows}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,28 +11,25 @@ export default class CostSection extends TranslatedComponent {
|
|||||||
|
|
||||||
static PropTypes = {
|
static PropTypes = {
|
||||||
ship: React.PropTypes.object.isRequired,
|
ship: React.PropTypes.object.isRequired,
|
||||||
shipId: React.PropTypes.string.isRequired,
|
|
||||||
code: React.PropTypes.string.isRequired,
|
code: React.PropTypes.string.isRequired,
|
||||||
buildName: React.PropTypes.string
|
buildName: React.PropTypes.string
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this._costsTab = this._costsTab.bind(this);
|
this._costsTab = this._costsTab.bind(this);
|
||||||
|
this._sortCost = this._sortCost.bind(this);
|
||||||
|
this._sortAmmo = this._sortAmmo.bind(this);
|
||||||
|
this._sortRetrofit = this._sortRetrofit.bind(this);
|
||||||
|
this._buildRetrofitShip = this._buildRetrofitShip.bind(this);
|
||||||
|
this._onBaseRetrofitChange = this._onBaseRetrofitChange.bind(this);
|
||||||
|
this._defaultRetrofitName = this._defaultRetrofitName.bind(this);
|
||||||
|
|
||||||
let data = Ships[props.shipId]; // Retrieve the basic ship properties, slots and defaults
|
let data = Ships[props.ship.id]; // Retrieve the basic ship properties, slots and defaults
|
||||||
let retrofitName = props.buildName;
|
let retrofitName = this._defaultRetrofitName(props.ship.id, props.buildName);
|
||||||
|
let retrofitShip = this._buildRetrofitShip(props.ship.id, retrofitName);
|
||||||
let shipDiscount = Persist.getShipDiscount();
|
let shipDiscount = Persist.getShipDiscount();
|
||||||
let moduleDiscount = Persist.getModuleDiscount();
|
let moduleDiscount = Persist.getModuleDiscount();
|
||||||
let existingBuild = Persist.getBuild(props.shipId, retrofitName);
|
|
||||||
let retrofitShip = new Ship(props.shipId, data.properties, data.slots); // Create a new Ship for retrofit comparison
|
|
||||||
|
|
||||||
if (existingBuild) {
|
|
||||||
retrofitShip.buildFrom(existingBuild); // Populate modules from existing build
|
|
||||||
} else {
|
|
||||||
retrofitShip.buildWith(data.defaults); // Populate with default components
|
|
||||||
}
|
|
||||||
|
|
||||||
this.props.ship.applyDiscounts(shipDiscount, moduleDiscount);
|
this.props.ship.applyDiscounts(shipDiscount, moduleDiscount);
|
||||||
retrofitShip.applyDiscounts(shipDiscount, moduleDiscount);
|
retrofitShip.applyDiscounts(shipDiscount, moduleDiscount);
|
||||||
@@ -45,16 +42,35 @@ export default class CostSection extends TranslatedComponent {
|
|||||||
total: props.ship.totalCost,
|
total: props.ship.totalCost,
|
||||||
insurance: Insurance[Persist.getInsurance()],
|
insurance: Insurance[Persist.getInsurance()],
|
||||||
tab: Persist.getCostTab(),
|
tab: Persist.getCostTab(),
|
||||||
buildOptions: Persist.getBuildsNamesFor(props.shipId),
|
buildOptions: Persist.getBuildsNamesFor(props.ship.id),
|
||||||
ammoPredicate: 'module',
|
ammoPredicate: 'cr',
|
||||||
ammoDesc: true,
|
ammoDesc: true,
|
||||||
costPredicate: 'cr',
|
costPredicate: 'cr',
|
||||||
costDesc: true,
|
costDesc: true,
|
||||||
retroPredicate: 'module',
|
retroPredicate: 'cr',
|
||||||
retroDesc: true
|
retroDesc: true
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_buildRetrofitShip(shipId, name, retrofitShip) {
|
||||||
|
let data = Ships[shipId]; // Retrieve the basic ship properties, slots and defaults
|
||||||
|
|
||||||
|
if (!retrofitShip) {
|
||||||
|
retrofitShip = new Ship(shipId, data.properties, data.slots); // Create a new Ship for retrofit comparison
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Persist.hasBuild(shipId, name)) {
|
||||||
|
retrofitShip.buildFrom(Persist.getBuild(shipId, name)); // Populate modules from existing build
|
||||||
|
} else {
|
||||||
|
retrofitShip.buildWith(data.defaults); // Populate with default components
|
||||||
|
}
|
||||||
|
return retrofitShip;
|
||||||
|
}
|
||||||
|
|
||||||
|
_defaultRetrofitName(shipId, name) {
|
||||||
|
return Persist.hasBuild(shipId, name) ? name : null;
|
||||||
|
}
|
||||||
|
|
||||||
_showTab(tab) {
|
_showTab(tab) {
|
||||||
Persist.setCostTab(tab);
|
Persist.setCostTab(tab);
|
||||||
this.setState({ tab });
|
this.setState({ tab });
|
||||||
@@ -72,72 +88,121 @@ export default class CostSection extends TranslatedComponent {
|
|||||||
this.setState({ insurance: Insurance[insuranceName] });
|
this.setState({ insurance: Insurance[insuranceName] });
|
||||||
}
|
}
|
||||||
|
|
||||||
_onBaseRetrofitChange(retrofitName) {
|
/**
|
||||||
let existingBuild = Persist.getBuild(this.props.shipId, retrofitName);
|
* Repopulate modules on retrofit ship from existing build
|
||||||
this.state.retrofitShip.buildFrom(existingBuild); // Repopulate modules from existing build
|
* @param {string} retrofitName Build name to base the retrofit ship on
|
||||||
|
*/
|
||||||
|
_onBaseRetrofitChange(event) {
|
||||||
|
let retrofitName = event.target.value;
|
||||||
|
let ship = this.props.ship;
|
||||||
|
|
||||||
|
if (retrofitName) {
|
||||||
|
this.state.retrofitShip.buildFrom(Persist.getBuild(ship.id, retrofitName));
|
||||||
|
} else {
|
||||||
|
this.state.retrofitShip.buildWith(Ships[ship.id].defaults); // Retrofit ship becomes stock build
|
||||||
|
}
|
||||||
|
this._updateRetrofit(ship, this.state.retrofitShip);
|
||||||
this.setState({ retrofitName });
|
this.setState({ retrofitName });
|
||||||
}
|
}
|
||||||
|
|
||||||
_onBuildSaved(shipId, name, code) {
|
_onBuildSaved(shipId, name, code) {
|
||||||
if(this.state.retrofitName == name) {
|
if(this.state.retrofitName == name) {
|
||||||
this.state.retrofitShip.buildFrom(code); // Repopulate modules from saved build
|
this.state.retrofitShip.buildFrom(code); // Repopulate modules from saved build
|
||||||
|
this._updateRetrofit(this.props.ship, this.state.retrofitShip);
|
||||||
} else {
|
} else {
|
||||||
this.setState({buildOptions: Persist.getBuildsNamesFor(this.props.shipId) });
|
this.setState({ buildOptions: Persist.getBuildsNamesFor(this.props.shipId) });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_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 });
|
||||||
|
}
|
||||||
|
this.setState({ buildOptions: Persist.getBuildsNamesFor(shipId) });
|
||||||
|
}
|
||||||
|
|
||||||
_toggleCost(item) {
|
_toggleCost(item) {
|
||||||
this.props.ship.setCostIncluded(item, !item.incCost);
|
this.props.ship.setCostIncluded(item, !item.incCost);
|
||||||
this.setState({ total: this.props.ship.totalCost });
|
this.setState({ total: this.props.ship.totalCost });
|
||||||
}
|
}
|
||||||
|
|
||||||
_sortCost(predicate) {
|
_toggleRetrofitCost(item) {
|
||||||
let costList = this.props.ship.costList;
|
let retrofitTotal = this.state.retrofitTotal;
|
||||||
|
item.retroItem.incCost = !item.retroItem.incCost;
|
||||||
|
retrofitTotal += item.netCost * (item.retroItem.incCost ? 1 : -1);
|
||||||
|
this.setState({ retrofitTotal });
|
||||||
|
}
|
||||||
|
|
||||||
|
_sortCostBy(predicate) {
|
||||||
let { costPredicate, costDesc } = this.state;
|
let { costPredicate, costDesc } = this.state;
|
||||||
|
|
||||||
if (predicate) {
|
if (costPredicate == predicate) {
|
||||||
if (costPredicate == predicate) {
|
costDesc = !costDesc;
|
||||||
costDesc = !costDesc;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
predicate = costPredicate;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (predicate == 'm') {
|
|
||||||
let translate = this.context.language.translate;
|
|
||||||
costList.sort(nameComparator(translate));
|
|
||||||
} else {
|
|
||||||
costList.sort((a, b) => (a.m && a.m.cost ? a.m.cost : 0) - (b.m && b.m.cost ? b.m.cost : 0));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!costDesc) {
|
|
||||||
costList.reverse();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setState({ costPredicate: predicate, costDesc });
|
this.setState({ costPredicate: predicate, costDesc });
|
||||||
}
|
}
|
||||||
|
|
||||||
_sortAmmo(predicate) {
|
_sortCost(ship, predicate, desc) {
|
||||||
let { ammoPredicate, ammoDesc, ammoCosts } = this.state;
|
let costList = ship.costList;
|
||||||
|
|
||||||
|
if (predicate == 'm') {
|
||||||
|
costList.sort(nameComparator(this.context.language.translate));
|
||||||
|
} else {
|
||||||
|
costList.sort((a, b) => (a.m && a.m.cost ? a.m.cost : 0) - (b.m && b.m.cost ? b.m.cost : 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!desc) {
|
||||||
|
costList.reverse();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_sortAmmoBy(predicate) {
|
||||||
|
let { ammoPredicate, ammoDesc } = this.state;
|
||||||
|
|
||||||
if (ammoPredicate == predicate) {
|
if (ammoPredicate == predicate) {
|
||||||
ammoDesc = !ammoDesc;
|
ammoDesc = !ammoDesc;
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (predicate) {
|
this.setState({ ammoPredicate: predicate, ammoDesc });
|
||||||
case 'm':
|
}
|
||||||
let translate = this.context.language.translate;
|
|
||||||
ammoCosts.sort(nameComparator(translate));
|
_sortAmmo(ammoCosts, predicate, desc) {
|
||||||
break;
|
|
||||||
default:
|
if (predicate == 'm') {
|
||||||
ammoCosts.sort((a, b) => a[predicate] - b[predicate]);
|
ammoCosts.sort(nameComparator(this.context.language.translate));
|
||||||
|
} else {
|
||||||
|
ammoCosts.sort((a, b) => a[predicate] - b[predicate]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!ammoDesc) {
|
if (!desc) {
|
||||||
ammoCosts.reverse();
|
ammoCosts.reverse();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
this.setState({ ammoPredicate: predicate, ammoDesc });
|
_sortRetrofitBy(predicate) {
|
||||||
|
let { retroPredicate, retroDesc } = this.state;
|
||||||
|
|
||||||
|
if (retroPredicate == predicate) {
|
||||||
|
retroDesc = !retroDesc;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState({ retroPredicate: predicate, retroDesc });
|
||||||
|
}
|
||||||
|
|
||||||
|
_sortRetrofit(retrofitCosts, predicate, desc) {
|
||||||
|
let translate = this.context.language.translate;
|
||||||
|
|
||||||
|
if (predicate == 'cr') {
|
||||||
|
retrofitCosts.sort((a, b) => a.netCost - b.netCost);
|
||||||
|
} else {
|
||||||
|
retrofitCosts.sort((a , b) => (a[predicate] ? translate(a[predicate]).toLowerCase() : '').localeCompare(b[predicate] ? translate(b[predicate]).toLowerCase() : ''));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!desc) {
|
||||||
|
retrofitCosts.reverse();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_costsTab() {
|
_costsTab() {
|
||||||
@@ -162,12 +227,12 @@ export default class CostSection extends TranslatedComponent {
|
|||||||
<table style={{ width: '100%', borderCollapse: 'collapse' }}>
|
<table style={{ width: '100%', borderCollapse: 'collapse' }}>
|
||||||
<thead>
|
<thead>
|
||||||
<tr className='main'>
|
<tr className='main'>
|
||||||
<th colSpan='2' className='sortable le' onClick={this._sortCost.bind(this,'m')}>
|
<th colSpan='2' className='sortable le' onClick={this._sortCostBy.bind(this,'m')}>
|
||||||
{translate('component')}
|
{translate('component')}
|
||||||
{shipDiscount < 1 && <u className='optional-hide'>{`[${translate('ship')} -${formats.rPct(1 - shipDiscount)}]`}</u>}
|
{shipDiscount < 1 && <u className='cap optional-hide' style={{ marginLeft: '0.5em' }}>{`[${translate('ship')} -${formats.pct1(1 - shipDiscount)}]`}</u>}
|
||||||
{moduleDiscount < 1 && <u className='optional-hide'>{`[${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>
|
</th>
|
||||||
<th className='sortable le' onClick={this._sortCost.bind(this, 'cr')} >{translate('credits')}</th>
|
<th className='sortable le' onClick={this._sortCostBy.bind(this, 'cr')} >{translate('credits')}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@@ -185,11 +250,72 @@ export default class CostSection extends TranslatedComponent {
|
|||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
updateRetrofitCosts() {
|
_retrofitTab() {
|
||||||
var costs = $scope.retrofitList = [];
|
let { retrofitTotal, retrofitCosts, moduleDiscount, retrofitName } = this.state;
|
||||||
var total = 0, i, l, item;
|
let { translate, formats, units } = this.context.language;
|
||||||
|
let int = formats.int;
|
||||||
|
let rows = [], options = [<option key='stock' value=''>{translate('Stock')}</option>];
|
||||||
|
|
||||||
if (ship.bulkheads.id != retrofitShip.bulkheads.id) {
|
for (let opt of this.state.buildOptions) {
|
||||||
|
options.push(<option key={opt} value={opt}>{opt}</option>);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 })} onClick={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>
|
||||||
|
</tr>);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
rows = <tr><td colSpan='7' style={{ padding: '3em 0' }}>{translate('PHRASE_NO_RETROCH')}</td></tr>
|
||||||
|
}
|
||||||
|
|
||||||
|
return <div>
|
||||||
|
<div className='scroll-x'>
|
||||||
|
<table style={{ width: '100%' }}>
|
||||||
|
<thead>
|
||||||
|
<tr className='main'>
|
||||||
|
<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='optional-hide'>{`[${translate('modules')} -${formats.rPct(1 - moduleDiscount)}]`}</u>}
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{rows}
|
||||||
|
<tr className='ri'>
|
||||||
|
<td colSpan='4' className='lbl' >{translate('cost')}</td>
|
||||||
|
<td colSpan='2' className={cn('val', retrofitTotal > 0 ? 'warning' : 'secondary-disabled')} style={{ borderBottom:'none' }}>
|
||||||
|
{int(retrofitTotal)}{units.CR}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr className='ri'>
|
||||||
|
<td colSpan='4' className='lbl cap' >{translate('retrofit from')}</td>
|
||||||
|
<td className='val cen' style={{ borderRight: 'none', width: '1em' }}><u className='primary-disabled'>▾</u></td>
|
||||||
|
<td className='val' style={{ borderLeft:'none', padding: 0 }}>
|
||||||
|
<select style={{ width: '100%', padding: 0 }} value={retrofitName} onChange={this._onBaseRetrofitChange}>
|
||||||
|
{options}
|
||||||
|
</select>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
_updateRetrofit(ship, retrofitShip) {
|
||||||
|
let retrofitCosts = [];
|
||||||
|
var retrofitTotal = 0, i, l, item;
|
||||||
|
|
||||||
|
if (ship.bulkheads.index != retrofitShip.bulkheads.index) {
|
||||||
item = {
|
item = {
|
||||||
buyClassRating: ship.bulkheads.m.class + ship.bulkheads.m.rating,
|
buyClassRating: ship.bulkheads.m.class + ship.bulkheads.m.rating,
|
||||||
buyName: ship.bulkheads.m.name,
|
buyName: ship.bulkheads.m.name,
|
||||||
@@ -198,9 +324,9 @@ export default class CostSection extends TranslatedComponent {
|
|||||||
netCost: ship.bulkheads.discountedCost - retrofitShip.bulkheads.discountedCost,
|
netCost: ship.bulkheads.discountedCost - retrofitShip.bulkheads.discountedCost,
|
||||||
retroItem: retrofitShip.bulkheads
|
retroItem: retrofitShip.bulkheads
|
||||||
};
|
};
|
||||||
costs.push(item);
|
retrofitCosts.push(item);
|
||||||
if (retrofitShip.bulkheads.incCost) {
|
if (retrofitShip.bulkheads.incCost) {
|
||||||
total += item.netCost;
|
retrofitTotal += item.netCost;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -208,71 +334,28 @@ export default class CostSection extends TranslatedComponent {
|
|||||||
var retroSlotGroup = retrofitShip[g];
|
var retroSlotGroup = retrofitShip[g];
|
||||||
var slotGroup = ship[g];
|
var slotGroup = ship[g];
|
||||||
for (i = 0, l = slotGroup.length; i < l; i++) {
|
for (i = 0, l = slotGroup.length; i < l; i++) {
|
||||||
if (slotGroup[i].id != retroSlotGroup[i].id) {
|
if (slotGroup[i].m != retroSlotGroup[i].m) {
|
||||||
item = { netCost: 0, retroItem: retroSlotGroup[i] };
|
item = { netCost: 0, retroItem: retroSlotGroup[i] };
|
||||||
if (slotGroup[i].id) {
|
if (slotGroup[i].m) {
|
||||||
item.buyName = slotGroup[i].m.name || slotGroup[i].m.grp;
|
item.buyName = slotGroup[i].m.name || slotGroup[i].m.grp;
|
||||||
item.buyClassRating = slotGroup[i].m.class + slotGroup[i].m.rating;
|
item.buyClassRating = slotGroup[i].m.class + slotGroup[i].m.rating;
|
||||||
item.netCost = slotGroup[i].discountedCost;
|
item.netCost = slotGroup[i].discountedCost;
|
||||||
}
|
}
|
||||||
if (retroSlotGroup[i].id) {
|
if (retroSlotGroup[i].m) {
|
||||||
item.sellName = retroSlotGroup[i].m.name || retroSlotGroup[i].m.grp;
|
item.sellName = retroSlotGroup[i].m.name || retroSlotGroup[i].m.grp;
|
||||||
item.sellClassRating = retroSlotGroup[i].m.class + retroSlotGroup[i].m.rating;
|
item.sellClassRating = retroSlotGroup[i].m.class + retroSlotGroup[i].m.rating;
|
||||||
item.netCost -= retroSlotGroup[i].discountedCost;
|
item.netCost -= retroSlotGroup[i].discountedCost;
|
||||||
}
|
}
|
||||||
costs.push(item);
|
retrofitCosts.push(item);
|
||||||
if (retroSlotGroup[i].incCost) {
|
if (retroSlotGroup[i].incCost) {
|
||||||
total += item.netCost;
|
retrofitTotal += item.netCost;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$scope.retrofitTotal = total;
|
|
||||||
}
|
|
||||||
|
|
||||||
_retrofitTab() {
|
this.setState({ retrofitCosts, retrofitTotal });
|
||||||
// return <div>
|
this._sortRetrofit(retrofitCosts, this.state.retroPredicate, this.state.retroDesc);
|
||||||
// <div className='scroll-x'>
|
|
||||||
// <table style='width:100%'>
|
|
||||||
// <thead>
|
|
||||||
// <tr className='main'>
|
|
||||||
// <th colspan='2' className='sortable le' ng-click='sortRetrofit('sellName | translate')' >{translate('sell')}</th>
|
|
||||||
// <th colspan='2' className='sortable le' ng-click='sortRetrofit('buyName | translate')' >{translate('buy')}</th>
|
|
||||||
// <th className='sortable le' ng-click='sortRetrofit('netCost')'>
|
|
||||||
// {{'net cost' | translate}} <u className='optional-hide' ng-if='discounts.components < 1'>[-{{fRPct(1 - discounts.components)}}]</u>
|
|
||||||
// </th>
|
|
||||||
// </tr>
|
|
||||||
// </thead>
|
|
||||||
// <tbody>
|
|
||||||
// <tr ng-if='!retrofitList || retrofitList.length == 0'>
|
|
||||||
// <td colspan='5' style='padding: 3em 0;' >{translate('PHRASE_NO_RETROCH')}</td>
|
|
||||||
// </tr>
|
|
||||||
// <tr className='highlight' ng-repeat='item in retrofitList | orderBy:retroPredicate:retroDesc' ng-click='toggleRetrofitCost(item.retroItem)' className={cn({disabled: !item.retroItem.incCost})}>
|
|
||||||
// <td style='width:1em;'>{{item.sellClassRating}}</td>
|
|
||||||
// <td className='le shorten cap'>{{item.sellName | translate}}</td>
|
|
||||||
// <td style='width:1em;'>{{item.buyClassRating}}</td>
|
|
||||||
// <td className='le shorten cap'>{{item.buyName | translate}}</td>
|
|
||||||
// <td className={cn('ri', item.retroItem.incCost ? item.netCost > 0 ? 'warning' : 'secondary-disabled' : 'disabled' )}>{{ fCrd(item.netCost)}} <u translate>CR</u></td>
|
|
||||||
// </tr>
|
|
||||||
// </tbody>
|
|
||||||
// </table>
|
|
||||||
// </div>
|
|
||||||
// <table className='total'>
|
|
||||||
// <tr className='ri'>
|
|
||||||
// <td className='lbl' >{translate('cost')}</td>
|
|
||||||
// <td colSpan={2} className={retrofitTotal > 0 ? 'warning' : 'secondary-disabled'}>{{fCrd(retrofitTotal)}} <u translate>CR</u></td>
|
|
||||||
// </tr>
|
|
||||||
// <tr className='ri'>
|
|
||||||
// <td className='lbl cap' >{translate('retrofit from')}</td>
|
|
||||||
// <td className='cen' style='border-right:none;width: 1em;'><u className='primary-disabled'>▾</u></td>
|
|
||||||
// <td style='border-left:none;padding:0;'>
|
|
||||||
// <select style='width: 100%;padding: 0' ng-model='$parent.retrofitBuild' ng-change='setRetrofitBase()' ng-options='name as name for (name, build) in allBuilds[ship.id]'>
|
|
||||||
// <option value=''>{{'Stock' | translate}}</option>
|
|
||||||
// </select>
|
|
||||||
// </td>
|
|
||||||
// </tr>
|
|
||||||
// </table>
|
|
||||||
// </div>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_ammoTab() {
|
_ammoTab() {
|
||||||
@@ -291,16 +374,16 @@ export default class CostSection extends TranslatedComponent {
|
|||||||
<td className='ri'>{int(item.total)}{units.CR}</td>
|
<td className='ri'>{int(item.total)}{units.CR}</td>
|
||||||
</tr>);
|
</tr>);
|
||||||
}
|
}
|
||||||
console.log(rows);
|
|
||||||
return <div>
|
return <div>
|
||||||
<div className='scroll-x' >
|
<div className='scroll-x' >
|
||||||
<table style={{ width: '100%', borderCollapse: 'collapse' }}>
|
<table style={{ width: '100%' }}>
|
||||||
<thead>
|
<thead>
|
||||||
<tr className='main'>
|
<tr className='main'>
|
||||||
<th colSpan='2' className='sortable le' onClick={this._sortAmmo.bind(this, 'm')} >{translate('module')}</th>
|
<th colSpan='2' className='sortable le' onClick={this._sortAmmoBy.bind(this, 'm')} >{translate('module')}</th>
|
||||||
<th colSpan='1' className='sortable le' onClick={this._sortAmmo.bind(this, 'max')} >{translate('qty')}</th>
|
<th colSpan='1' className='sortable le' onClick={this._sortAmmoBy.bind(this, 'max')} >{translate('qty')}</th>
|
||||||
<th colSpan='1' className='sortable le' onClick={this._sortAmmo.bind(this, 'cost')} >{translate('unit cost')}</th>
|
<th colSpan='1' className='sortable le' onClick={this._sortAmmoBy.bind(this, 'cost')} >{translate('unit cost')}</th>
|
||||||
<th className='sortable le' onClick={this._sortAmmo.bind(this, 'total')}>{translate('total cost')}</th>
|
<th className='sortable le' onClick={this._sortAmmoBy.bind(this, 'total')}>{translate('total cost')}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@@ -318,14 +401,13 @@ export default class CostSection extends TranslatedComponent {
|
|||||||
/**
|
/**
|
||||||
* Recalculate all ammo costs
|
* Recalculate all ammo costs
|
||||||
*/
|
*/
|
||||||
_updateAmmoCosts() {
|
_updateAmmoCosts(ship) {
|
||||||
let ship = this.props.ship;
|
let ammoCosts = [], ammoTotal = 0, item, q, limpets = 0, srvs = 0, scoop = false;
|
||||||
let ammoCosts = [], ammoTotal = 0, item, q, limpets = 0, scoop = false;
|
|
||||||
|
|
||||||
for (let g in { standard: 1, internal: 1, hardpoints: 1 }) {
|
for (let g in { standard: 1, internal: 1, hardpoints: 1 }) {
|
||||||
let slotGroup = ship[g];
|
let slotGroup = ship[g];
|
||||||
for (let i = 0, l = slotGroup.length; i < l; i++) {
|
for (let i = 0, l = slotGroup.length; i < l; i++) {
|
||||||
if (slotGroup[i].id) {
|
if (slotGroup[i].m) {
|
||||||
//special cases needed for SCB, AFMU, and limpet controllers since they don't use standard ammo/clip
|
//special cases needed for SCB, AFMU, and limpet controllers since they don't use standard ammo/clip
|
||||||
q = 0;
|
q = 0;
|
||||||
switch (slotGroup[i].m.grp) {
|
switch (slotGroup[i].m.grp) {
|
||||||
@@ -338,6 +420,9 @@ export default class CostSection extends TranslatedComponent {
|
|||||||
case 'am':
|
case 'am':
|
||||||
q = slotGroup[i].m.ammo;
|
q = slotGroup[i].m.ammo;
|
||||||
break;
|
break;
|
||||||
|
case 'pv':
|
||||||
|
srvs += slotGroup[i].m.vehicles;
|
||||||
|
break;
|
||||||
case 'fx': case 'hb': case 'cc': case 'pc':
|
case 'fx': case 'hb': case 'cc': case 'pc':
|
||||||
limpets = ship.cargoCapacity;
|
limpets = ship.cargoCapacity;
|
||||||
break;
|
break;
|
||||||
@@ -370,6 +455,17 @@ export default class CostSection extends TranslatedComponent {
|
|||||||
ammoCosts.push(item);
|
ammoCosts.push(item);
|
||||||
ammoTotal += item.total;
|
ammoTotal += item.total;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (srvs > 0) {
|
||||||
|
item = {
|
||||||
|
m: { name: 'SRVs', class: '', rating: '' },
|
||||||
|
max: srvs,
|
||||||
|
cost: 6005,
|
||||||
|
total: srvs * 6005
|
||||||
|
};
|
||||||
|
ammoCosts.push(item);
|
||||||
|
ammoTotal += item.total;
|
||||||
|
}
|
||||||
//calculate refuel costs if no scoop present
|
//calculate refuel costs if no scoop present
|
||||||
if (!scoop) {
|
if (!scoop) {
|
||||||
item = {
|
item = {
|
||||||
@@ -381,26 +477,66 @@ export default class CostSection extends TranslatedComponent {
|
|||||||
ammoCosts.push(item);
|
ammoCosts.push(item);
|
||||||
ammoTotal += item.total;
|
ammoTotal += item.total;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setState({ ammoTotal, ammoCosts });
|
this.setState({ ammoTotal, ammoCosts });
|
||||||
|
this._sortAmmo(ammoCosts, this.state.ammoPredicate, this.state.ammoDesc);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillMount(){
|
componentWillMount(){
|
||||||
this.listeners = [
|
this.listeners = [
|
||||||
Persist.addListener('discounts', this._onDiscountChanged.bind(this)),
|
Persist.addListener('discounts', this._onDiscountChanged.bind(this)),
|
||||||
Persist.addListener('insurance', this._onInsuranceChanged.bind(this)),
|
Persist.addListener('insurance', this._onInsuranceChanged.bind(this)),
|
||||||
|
Persist.addListener('buildSaved', this._onBuildSaved.bind(this)),
|
||||||
|
Persist.addListener('buildDeleted', this._onBuildDeleted.bind(this))
|
||||||
];
|
];
|
||||||
this._updateAmmoCosts(this.props.ship);
|
this._updateAmmoCosts(this.props.ship);
|
||||||
this._sortCost.call(this, this.state.costPredicate);
|
this._updateRetrofit(this.props.ship, this.state.retrofitShip);
|
||||||
|
this._sortCost(this.props.ship);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillReceiveProps(nextProps, nextContext) {
|
componentWillReceiveProps(nextProps, nextContext) {
|
||||||
this._updateAmmoCosts(nextProps.ship);
|
let retrofitShip = this.state.retrofitShip;
|
||||||
|
|
||||||
this._sortCost();
|
if (nextProps.ship != this.props.ship) { // Ship has changed
|
||||||
|
let nextId = nextProps.ship.id;
|
||||||
|
let retrofitName = this._defaultRetrofitName(nextId, nextProps.buildName);
|
||||||
|
retrofitShip = this._buildRetrofitShip(nextId, retrofitName, nextId == this.props.ship.id ? retrofitShip : null );
|
||||||
|
this.setState({
|
||||||
|
retrofitShip,
|
||||||
|
retrofitName,
|
||||||
|
buildOptions: Persist.getBuildsNamesFor(nextId)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nextProps.ship != this.props.ship || nextProps.code != this.props.code) {
|
||||||
|
this._updateAmmoCosts(nextProps.ship);
|
||||||
|
this._updateRetrofit(nextProps.ship, retrofitShip);
|
||||||
|
this._sortCost(nextProps.ship);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUpdate(nextProps, nextState) {
|
||||||
|
let state = this.state;
|
||||||
|
|
||||||
|
switch (nextState.tab) {
|
||||||
|
case 'ammo':
|
||||||
|
if (state.ammoPredicate != nextState.ammoPredicate || state.ammoDesc != nextState.ammoDesc) {
|
||||||
|
this._sortAmmo(nextState.ammoCosts, nextState.ammoPredicate, nextState.ammoDesc);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'retrofit':
|
||||||
|
if (state.retroPredicate != nextState.retroPredicate || state.retroDesc != nextState.retroDesc) {
|
||||||
|
this._sortRetrofit(nextState.retrofitCosts, nextState.retroPredicate, nextState.retroDesc);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
if (state.costPredicate != nextState.costPredicate || state.costDesc != nextState.costDesc) {
|
||||||
|
this._sortCost(nextProps.ship, nextState.costPredicate, nextState.costDesc);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount(){
|
componentWillUnmount(){
|
||||||
// remove window listener
|
|
||||||
this.listeners.forEach(l => l.remove());
|
this.listeners.forEach(l => l.remove());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import TranslatedComponent from './TranslatedComponent';
|
import TranslatedComponent from './TranslatedComponent';
|
||||||
|
import { Languages } from '../i18n/Language';
|
||||||
|
import { Insurance, Discounts } from '../shipyard/Constants';
|
||||||
import Link from './Link';
|
import Link from './Link';
|
||||||
import ActiveLink from './ActiveLink';
|
import ActiveLink from './ActiveLink';
|
||||||
import cn from 'classnames';
|
import cn from 'classnames';
|
||||||
@@ -7,28 +9,58 @@ import { Cogs, CoriolisLogo, Hammer, Rocket, StatsBars } from './SvgIcons';
|
|||||||
import { Ships } from 'coriolis-data';
|
import { Ships } from 'coriolis-data';
|
||||||
import InterfaceEvents from '../utils/InterfaceEvents';
|
import InterfaceEvents from '../utils/InterfaceEvents';
|
||||||
import Persist from '../stores/Persist';
|
import Persist from '../stores/Persist';
|
||||||
|
import { toDetailedExport } from '../shipyard/Serializer';
|
||||||
import ModalDeleteAll from './ModalDeleteAll';
|
import ModalDeleteAll from './ModalDeleteAll';
|
||||||
|
import ModalExport from './ModalExport';
|
||||||
|
import ModalImport from './ModalImport';
|
||||||
|
import Slider from './Slider';
|
||||||
|
|
||||||
|
const SIZE_MIN = 0.65;
|
||||||
|
const SIZE_RANGE = 0.55;
|
||||||
|
|
||||||
export default class Header extends TranslatedComponent {
|
export default class Header extends TranslatedComponent {
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props, context) {
|
||||||
super(props);
|
super(props);
|
||||||
this.shipOrder = Object.keys(Ships).sort();
|
this.shipOrder = Object.keys(Ships).sort();
|
||||||
|
|
||||||
|
this._setLanguage = this._setLanguage.bind(this);
|
||||||
|
this._setInsurance = this._setInsurance.bind(this);
|
||||||
|
|
||||||
|
this.languageOptions = [];
|
||||||
|
this.insuranceOptions = [];
|
||||||
|
this.discountOptions = [];
|
||||||
|
|
||||||
|
let translate = context.language.translate;
|
||||||
|
|
||||||
|
for (let langCode in Languages) {
|
||||||
|
this.languageOptions.push(<option key={langCode} value={langCode}>{Languages[langCode]}</option>);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let name in Insurance) {
|
||||||
|
this.insuranceOptions.push(<option key={name} value={name}>{translate(name)}</option>);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let name in Discounts) {
|
||||||
|
this.discountOptions.push(<option key={name} value={Discounts[name]}>{name}</option>);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_setInsurance(e) {
|
_setInsurance(e) {
|
||||||
e.stopPropagation();
|
Persist.setInsurance(e.target.value);
|
||||||
Persist.setInsurance('beta'); // TODO: get insurance name
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_setModuleDiscount(e) {
|
_setModuleDiscount(e) {
|
||||||
e.stopPropagation();
|
Persist.setModuleDiscount(e.target.value * 1);
|
||||||
Persist.setModuleDiscount(0); // TODO: get module discount
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_setShipDiscount(e) {
|
_setShipDiscount(e) {
|
||||||
e.stopPropagation();
|
Persist.setShipDiscount(e.target.value * 1);
|
||||||
Persist.setShipDiscount(0); // TODO: get ship discount
|
}
|
||||||
|
|
||||||
|
_setLanguage(e){
|
||||||
|
Persist.setLangCode(e.target.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
_showDeleteAll(e) {
|
_showDeleteAll(e) {
|
||||||
@@ -37,29 +69,33 @@ export default class Header extends TranslatedComponent {
|
|||||||
};
|
};
|
||||||
|
|
||||||
_showBackup(e) {
|
_showBackup(e) {
|
||||||
|
let translate = this.context.language.translate;
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
/*$state.go('modal.export', {
|
InterfaceEvents.showModal(<ModalExport
|
||||||
title: 'backup',
|
title={translate('backup')}
|
||||||
data: Persist.getAll(),
|
description={translate('PHRASE_BACKUP_DESC')}
|
||||||
description: 'PHRASE_BACKUP_DESC'
|
data={Persist.getAll()}
|
||||||
});*/
|
/>);
|
||||||
// TODO: implement modal
|
|
||||||
};
|
};
|
||||||
|
|
||||||
_showDetailedExport(e){
|
_showDetailedExport(e){
|
||||||
|
let translate = this.context.language.translate;
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
|
||||||
|
|
||||||
/*$state.go('modal.export', {
|
InterfaceEvents.showModal(<ModalExport
|
||||||
title: 'detailed export',
|
title={translate('detailed export')}
|
||||||
data: Serializer.toDetailedExport(Persist.getBuilds()),
|
description={translate('PHRASE_EXPORT_DESC')}
|
||||||
description: 'PHRASE_EXPORT_DESC'
|
data={toDetailedExport(Persist.getBuilds())}
|
||||||
});*/
|
/>);
|
||||||
// TODO: implement modal
|
}
|
||||||
|
|
||||||
|
_showImport(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
InterfaceEvents.showModal(<ModalImport/>);
|
||||||
}
|
}
|
||||||
|
|
||||||
_setTextSize(size) {
|
_setTextSize(size) {
|
||||||
Persist.setSizeRatio(size); // TODO: implement properly
|
Persist.setSizeRatio((size * SIZE_RANGE) + SIZE_MIN);
|
||||||
}
|
}
|
||||||
|
|
||||||
_resetTextSize() {
|
_resetTextSize() {
|
||||||
@@ -117,9 +153,8 @@ export default class Header extends TranslatedComponent {
|
|||||||
let translate = this.context.language.translate;
|
let translate = this.context.language.translate;
|
||||||
|
|
||||||
if (Persist.hasComparisons()) {
|
if (Persist.hasComparisons()) {
|
||||||
let comparisons = [];
|
comparisons = [];
|
||||||
let comps = Object.keys(Persist.getComparisons()).sort();
|
let comps = Object.keys(Persist.getComparisons()).sort();
|
||||||
console.log(comps);
|
|
||||||
|
|
||||||
for (let name of comps) {
|
for (let name of comps) {
|
||||||
comparisons.push(<ActiveLink key={name} href={'/compare/' + name} className={'block name'}>{name}</ActiveLink>);
|
comparisons.push(<ActiveLink key={name} href={'/compare/' + name} className={'block name'}>{name}</ActiveLink>);
|
||||||
@@ -145,38 +180,54 @@ export default class Header extends TranslatedComponent {
|
|||||||
<div className={'menu-list no-wrap cap'} onClick={ (e) => e.stopPropagation() }>
|
<div className={'menu-list no-wrap cap'} onClick={ (e) => e.stopPropagation() }>
|
||||||
<ul>
|
<ul>
|
||||||
{translate('language')}
|
{translate('language')}
|
||||||
<li><select className={'cap'} ng-model="language.current" ng-options="langCode as langName for (langCode,langName) in language.opts" ng-change="changeLanguage()"></select></li>
|
<li>
|
||||||
|
<select className={'cap'} value={Persist.getLangCode()} onChange={this._setLanguage}>
|
||||||
|
{this.languageOptions}
|
||||||
|
</select>
|
||||||
|
</li>
|
||||||
</ul><br/>
|
</ul><br/>
|
||||||
<ul>
|
<ul>
|
||||||
{translate('insurance')}
|
{translate('insurance')}
|
||||||
<li><select className={'cap'} ng-model="insurance.current" ng-options="ins.name | translate for (i,ins) in insurance.opts" ng-change="updateInsurance()"></select></li>
|
<li>
|
||||||
|
<select className={'cap'} value={Persist.getInsurance()} onChange={this._setInsurance}>
|
||||||
|
{this.insuranceOptions}
|
||||||
|
</select>
|
||||||
|
</li>
|
||||||
</ul><br/>
|
</ul><br/>
|
||||||
<ul>
|
<ul>
|
||||||
{translate('ship')} {translate('discount')}
|
{translate('ship')} {translate('discount')}
|
||||||
<li><select className={'cap'} ng-model="discounts.ship" ng-options="i for (i,d) in discounts.opts" ng-change="updateDiscount()"></select></li>
|
<li>
|
||||||
|
<select className={'cap'} value={Persist.getShipDiscount()} onChange={this._setShipDiscount}>
|
||||||
|
{this.discountOptions}
|
||||||
|
</select>
|
||||||
|
</li>
|
||||||
</ul><br/>
|
</ul><br/>
|
||||||
<ul>
|
<ul>
|
||||||
{translate('component')} {translate('discount')}
|
{translate('module')} {translate('discount')}
|
||||||
<li><select className={'cap'} ng-model="discounts.components" ng-options="i for (i,d) in discounts.opts" ng-change="updateDiscount()"></select></li>
|
<li>
|
||||||
|
<select className={'cap'} value={Persist.getModuleDiscount()} onChange={this._setModuleDiscount} >
|
||||||
|
{this.discountOptions}
|
||||||
|
</select>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<hr />
|
<hr />
|
||||||
<ul>
|
<ul>
|
||||||
{translate('builds')} & {translate('comparisons')}
|
{translate('builds')} & {translate('comparisons')}
|
||||||
<li><a href="#" className={'block'} ng-click="backup($event)">{translate('backup')}</a></li>
|
<li><a href="#" className={'block'} onClick={this._showBackup.bind(this)}>{translate('backup')}</a></li>
|
||||||
<li><a href="#" className={'block'} ng-click="detailedExport($event)">{translate('detailed export')}</a></li>
|
<li><a href="#" className={'block'} onClick={this._showDetailedExport.bind(this)}>{translate('detailed export')}</a></li>
|
||||||
<li><a href="#" className={'block'} ui-sref="modal.import">{translate('import')}</a></li>
|
<li><a href="#" className={'block'} onClick={this._showImport.bind(this)}>{translate('import')}</a></li>
|
||||||
<li><a href="#" onClick={this._showDeleteAll.bind(this)}>{translate('delete all')}</a></li>
|
<li><a href="#" onClick={this._showDeleteAll.bind(this)}>{translate('delete all')}</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
<hr />
|
<hr />
|
||||||
<table style={{width: 300, backgroundColor: 'transparent'}}>
|
<table style={{width: 300, backgroundColor: 'transparent'}}>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<td style={{width: 20}}><u>A</u></td>
|
<td style={{ width: '1em', verticalAlign: 'top' }}><u>A</u></td>
|
||||||
<td slider min="0.65" def="sizeRatio" max="1.2" on-change="textSizeChange(val)" ignore-resize="true"></td>
|
<td><Slider onChange={this._setTextSize} percent={(Persist.getSizeRatio() - SIZE_MIN) / SIZE_RANGE} /></td>
|
||||||
<td style={{width: 20}}><span style={{fontSize: 30}}>A</span></td>
|
<td style={{ width: 20 }}><span style={{ fontSize: 30 }}>A</span></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td></td><td style={{ textAlign: 'center' }} className={'primary-disabled cap'} ng-click="resetTextSize()" translate="reset"></td><td></td>
|
<td colSpan='3' style={{ textAlign: 'center', cursor: 'pointer' }} className={'primary-disabled cap'} onClick={this._resetTextSize.bind(this)}>{translate('reset')}</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
@@ -186,6 +237,22 @@ export default class Header extends TranslatedComponent {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
componentWillMount(){
|
||||||
|
Persist.addListener('language', () => this.forceUpdate());
|
||||||
|
Persist.addListener('insurance', () => this.forceUpdate());
|
||||||
|
Persist.addListener('discounts', () => this.forceUpdate());
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillReceiveProps(nextProps, nextContext) {
|
||||||
|
if(this.context.language != nextContext.language) {
|
||||||
|
let translate = nextContext.language.translate;
|
||||||
|
this.insuranceOptions = [];
|
||||||
|
for (let name in Insurance) {
|
||||||
|
this.insuranceOptions.push(<option key={name} value={name}>{translate(name)}</option>);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
let translate = this.context.language.translate;
|
let translate = this.context.language.translate;
|
||||||
let openedMenu = this.props.currentMenu;
|
let openedMenu = this.props.currentMenu;
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ export default class InternalSlot extends Slot {
|
|||||||
{ m.optmass ? <div className={'l'}>{translate('optimal mass') + ': '}{m.optmass}{u.T}</div> : null }
|
{ m.optmass ? <div className={'l'}>{translate('optimal mass') + ': '}{m.optmass}{u.T}</div> : null }
|
||||||
{ m.maxmass ? <div className={'l'}>{translate('max mass') + ': '}{m.maxmass}{u.T}</div> : null }
|
{ m.maxmass ? <div className={'l'}>{translate('max mass') + ': '}{m.maxmass}{u.T}</div> : null }
|
||||||
{ m.bins ? <div className={'l'}>{m.bins + ' '}<u>{translate('bins')}</u></div> : null }
|
{ m.bins ? <div className={'l'}>{m.bins + ' '}<u>{translate('bins')}</u></div> : null }
|
||||||
|
{ m.bays ? <div className={'l'}>{translate('bays') + ': ' + m.bays}</div> : null }
|
||||||
{ m.rate ? <div className={'l'}>{translate('rate')}: {m.rate}{u.kgs} {translate('refuel time')}: {formats.time(this.props.fuel * 1000 / m.rate)}</div> : null }
|
{ m.rate ? <div className={'l'}>{translate('rate')}: {m.rate}{u.kgs} {translate('refuel time')}: {formats.time(this.props.fuel * 1000 / m.rate)}</div> : null }
|
||||||
{ m.ammo ? <div className={'l'}>{translate('ammo')}: {formats.gen(m.ammo)}</div> : null }
|
{ m.ammo ? <div className={'l'}>{translate('ammo')}: {formats.gen(m.ammo)}</div> : null }
|
||||||
{ m.cells ? <div className={'l'}>{translate('cells')}: {m.cells}</div> : null }
|
{ m.cells ? <div className={'l'}>{translate('cells')}: {m.cells}</div> : null }
|
||||||
|
|||||||
@@ -83,7 +83,6 @@ export default class InternalSlotSection extends SlotSection {
|
|||||||
enabled={s.enabled}
|
enabled={s.enabled}
|
||||||
m={s.m}
|
m={s.m}
|
||||||
fuel={fuelCapacity}
|
fuel={fuelCapacity}
|
||||||
shipMass={ladenMass}
|
|
||||||
/>);
|
/>);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { findDOMNode } from 'react-dom';
|
||||||
|
import d3 from 'd3';
|
||||||
import TranslatedComponent from './TranslatedComponent';
|
import TranslatedComponent from './TranslatedComponent';
|
||||||
|
|
||||||
const RENDER_POINTS = 20; // Only render 20 points on the graph
|
const RENDER_POINTS = 20; // Only render 20 points on the graph
|
||||||
|
const MARGIN = { top: 15, right: 15, bottom: 35, left: 60 }
|
||||||
|
|
||||||
export default class LineChart extends TranslatedComponent {
|
export default class LineChart extends TranslatedComponent {
|
||||||
|
|
||||||
@@ -12,40 +15,201 @@ export default class LineChart extends TranslatedComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static PropTypes = {
|
static PropTypes = {
|
||||||
xMax: React.PropTypes.number.isRequired,
|
width: React.PropTypes.number.isRequired,
|
||||||
yMax: React.PropTypes.number.isRequired,
|
|
||||||
func: React.PropTypes.func.isRequired,
|
func: React.PropTypes.func.isRequired,
|
||||||
|
xLabel: React.PropTypes.string.isRequired,
|
||||||
|
xMin: React.PropTypes.number,
|
||||||
|
xMax: React.PropTypes.number.isRequired,
|
||||||
|
xUnit: React.PropTypes.string.isRequired,
|
||||||
|
yLabel: React.PropTypes.string.isRequired,
|
||||||
|
yMin: React.PropTypes.number,
|
||||||
|
yMax: React.PropTypes.number.isRequired,
|
||||||
|
yUnit: React.PropTypes.string.isRequired,
|
||||||
series: React.PropTypes.array,
|
series: React.PropTypes.array,
|
||||||
colors: React.PropTypes.array,
|
colors: React.PropTypes.array,
|
||||||
xMin: React.PropTypes.number,
|
|
||||||
yMin: React.PropTypes.number,
|
|
||||||
xUnit: React.PropTypes.string,
|
|
||||||
yUnit: React.PropTypes.string,
|
|
||||||
xLabel: React.PropTypes.string,
|
|
||||||
xLabel: React.PropTypes.string,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props, context) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
// init
|
this._updateDimensions = this._updateDimensions.bind(this);
|
||||||
|
this._updateSeriesData = this._updateSeriesData.bind(this);
|
||||||
|
this._tooltip = this._tooltip.bind(this);
|
||||||
|
this._showTip = this._showTip.bind(this);
|
||||||
|
this._hideTip = this._hideTip.bind(this);
|
||||||
|
this._moveTip = this._moveTip.bind(this);
|
||||||
|
|
||||||
|
let markerElems = [];
|
||||||
|
let detailElems = [<text key={'lbl'} className='label x' y='1.25em'/>];
|
||||||
|
let xScale = d3.scale.linear();
|
||||||
|
let yScale = d3.scale.linear();
|
||||||
|
let series = props.series;
|
||||||
|
let seriesLines = [];
|
||||||
|
|
||||||
|
this.xAxis = d3.svg.axis().scale(xScale).outerTickSize(0).orient('bottom');
|
||||||
|
this.yAxis = d3.svg.axis().scale(yScale).ticks(6).outerTickSize(0).orient('left');
|
||||||
|
|
||||||
|
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'}/>);
|
||||||
|
markerElems.push(<circle key={i} className='marker' r='4' />);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
xScale,
|
||||||
|
yScale,
|
||||||
|
seriesLines,
|
||||||
|
detailElems,
|
||||||
|
markerElems,
|
||||||
|
tipHeight: 2 + (1.25 * (series ? series.length : 0.75))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
_tooltip(xPos) {
|
||||||
|
let { xLabel, yLabel, xUnit, yUnit, func, series } = this.props;
|
||||||
|
let { xScale, yScale } = this.state;
|
||||||
|
let { formats, translate } = this.context.language;
|
||||||
|
let x0 = xScale.invert(xPos),
|
||||||
|
y0 = func(x0),
|
||||||
|
tips = this.tipContainer,
|
||||||
|
yTotal = 0,
|
||||||
|
flip = (x0 / xScale.domain()[1] > 0.65),
|
||||||
|
tipWidth = 0,
|
||||||
|
tipHeightPx = tips.selectAll('rect').node().getBoundingClientRect().height;
|
||||||
|
|
||||||
|
tips.selectAll('text.label.y').text(function(d, i) {
|
||||||
|
let yVal = series ? y0[series[i]] : y0;
|
||||||
|
yTotal += yVal;
|
||||||
|
return (series ? translate(series[i]) : '') + ' ' + formats.f2(yVal);
|
||||||
|
}).append('tspan').attr('class', 'metric').text(' ' + yUnit);
|
||||||
|
|
||||||
|
tips.selectAll('text').each(function() {
|
||||||
|
if (this.getBBox().width > tipWidth) {
|
||||||
|
tipWidth = Math.ceil(this.getBBox().width);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let tipY = Math.floor(yScale(yTotal / (series ? series.length : 1)) - (tipHeightPx / 2));
|
||||||
|
|
||||||
|
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('rect').attr('width', tipWidth + 4).attr('x', flip ? -tipWidth - 12 : 8).style('text-anchor', flip ? 'end' : 'start');
|
||||||
|
this.markersContainer.selectAll('circle').attr('cx', xPos).attr('cy', (d, i) => yScale(series ? y0[series[i]] : y0));
|
||||||
|
}
|
||||||
|
|
||||||
|
_updateDimensions(props, sizeRatio) {
|
||||||
|
let { width, xMax, xMin, yMin, yMax } = props;
|
||||||
|
let innerWidth = width - MARGIN.left - MARGIN.right;
|
||||||
|
let outerHeight = Math.round(width * 0.5 * sizeRatio);
|
||||||
|
let innerHeight = outerHeight - MARGIN.top - MARGIN.bottom;
|
||||||
|
|
||||||
|
this.state.xScale.range([0, innerWidth]).domain([xMin, xMax || 1]).clamp(true);
|
||||||
|
this.state.yScale.range([innerHeight, 0]).domain([yMin, yMax]);
|
||||||
|
this.setState({ innerWidth, outerHeight, innerHeight });
|
||||||
|
}
|
||||||
|
|
||||||
|
_showTip(e) {
|
||||||
|
this._moveTip(e);
|
||||||
|
this.tipContainer.style('display', null);
|
||||||
|
this.markersContainer.style('display', null);
|
||||||
|
}
|
||||||
|
|
||||||
|
_moveTip(e) {
|
||||||
|
this._tooltip(Math.round(e.clientX - e.target.getBoundingClientRect().left));
|
||||||
|
}
|
||||||
|
|
||||||
|
_hideTip() {
|
||||||
|
this.tipContainer.style('display', 'none');
|
||||||
|
this.markersContainer.style('display', 'none');
|
||||||
|
}
|
||||||
|
|
||||||
|
_updateSeriesData(props) {
|
||||||
|
let { func, xMin, xMax, series } = props;
|
||||||
|
let delta = (xMax - xMin) / RENDER_POINTS;
|
||||||
|
let seriesData = new Array(RENDER_POINTS);
|
||||||
|
|
||||||
|
if (delta) {
|
||||||
|
seriesData = new Array(RENDER_POINTS);
|
||||||
|
for (let i = 0, x = xMin; i < RENDER_POINTS; i++) {
|
||||||
|
seriesData[i] = [ x, func(x) ];
|
||||||
|
x += delta;
|
||||||
|
}
|
||||||
|
seriesData[RENDER_POINTS - 1] = [ xMax, func(xMax) ];
|
||||||
|
} else {
|
||||||
|
let yVal = func(xMin);
|
||||||
|
seriesData = [ [0, yVal], [1, yVal]];
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState({ seriesData });
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillMount(){
|
componentWillMount(){
|
||||||
// Listen to window resize
|
this._updateDimensions(this.props, this.context.sizeRatio);
|
||||||
}
|
this._updateSeriesData(this.props);
|
||||||
|
|
||||||
componentWillUnmount(){
|
|
||||||
// remove window listener
|
|
||||||
// remove mouse move listener / touch listner?
|
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillReceiveProps(nextProps, nextContext) {
|
componentWillReceiveProps(nextProps, nextContext) {
|
||||||
// on language change update formatting
|
let { func, xMin, xMax, yMin, yMax, width } = nextProps;
|
||||||
|
let props = this.props;
|
||||||
|
|
||||||
|
let domainChanged = xMax != props.xMax || xMin != props.xMin || yMax != props.yMax || yMin != props.yMin || func != props.func;
|
||||||
|
|
||||||
|
if (width != props.width || domainChanged || this.context.sizeRatio != nextContext.sizeRatio) {
|
||||||
|
this._updateDimensions(nextProps, nextContext.sizeRatio);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (domainChanged) {
|
||||||
|
this._updateSeriesData(nextProps);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return <div></div>;
|
if (!this.props.width) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
let { xLabel, yLabel, xUnit, yUnit, colors } = this.props;
|
||||||
|
let { innerWidth, outerHeight, innerHeight, tipHeight, detailElems, markerElems, seriesData, seriesLines } = this.state;
|
||||||
|
let line = this.line;
|
||||||
|
let lines = seriesLines.map((line, i) => <path key={i} className='line' stroke={colors[i]} strokeWidth='2' d={line(seriesData)} />);
|
||||||
|
|
||||||
|
return <svg style={{ width: '100%', height: outerHeight }}>
|
||||||
|
<g transform={`translate(${MARGIN.left},${MARGIN.top})`}>
|
||||||
|
<g>{lines}</g>
|
||||||
|
<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>{xLabel}</tspan>
|
||||||
|
<tspan className='metric'>{` (${xUnit})`}</tspan>
|
||||||
|
</text>
|
||||||
|
</g>
|
||||||
|
<g className='y axis' ref={(elem) => d3.select(elem).call(this.yAxis)}>
|
||||||
|
<text className='cap' transform='rotate(-90)' y='-50' dy='.1em' x={innerHeight / -2} style={{ textAnchor: 'middle' }}>
|
||||||
|
<tspan>{yLabel}</tspan>
|
||||||
|
<tspan className='metric'>{` (${yUnit})`}</tspan>
|
||||||
|
</text>
|
||||||
|
</g>
|
||||||
|
<g ref={(g) => this.tipContainer = d3.select(g)} className='tooltip' style={{ display: 'none' }}>
|
||||||
|
<rect className='tip' style={{height: tipHeight + 'em'}}></rect>
|
||||||
|
{detailElems}
|
||||||
|
</g>
|
||||||
|
<g ref={(g) => this.markersContainer = d3.select(g)} style={{ display: 'none' }}>
|
||||||
|
{markerElems}
|
||||||
|
</g>
|
||||||
|
<rect
|
||||||
|
fillOpacity='0'
|
||||||
|
height={innerHeight}
|
||||||
|
width={innerWidth + 1}
|
||||||
|
onMouseEnter={this._showTip}
|
||||||
|
onTouchStart={this._showTip}
|
||||||
|
onMouseLeave={this._hideTip}
|
||||||
|
onTouchEnd={this._hideTip}
|
||||||
|
onMouseMove={this._moveTip}
|
||||||
|
onTouchMove={this._moveTip}
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
</svg>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
108
src/app/components/ModalCompare.jsx
Normal file
108
src/app/components/ModalCompare.jsx
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import TranslatedComponent from './TranslatedComponent';
|
||||||
|
import InterfaceEvents from '../utils/InterfaceEvents';
|
||||||
|
import { Ships } from 'coriolis-data';
|
||||||
|
import Persist from '../stores/Persist';
|
||||||
|
|
||||||
|
function buildComparator(a, b) {
|
||||||
|
if (a.name == b.name) {
|
||||||
|
return a.buildName > b.buildName;
|
||||||
|
}
|
||||||
|
return a.name > b.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export default class ModalCompare extends TranslatedComponent {
|
||||||
|
|
||||||
|
static propTypes = {
|
||||||
|
onSelect: React.PropTypes.func.isRequired,
|
||||||
|
builds: React.PropTypes.array
|
||||||
|
};
|
||||||
|
|
||||||
|
static defaultProps = {
|
||||||
|
builds: []
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
let builds = props.builds;
|
||||||
|
let allBuilds = Persist.getBuilds();
|
||||||
|
let unusedBuilds = [];
|
||||||
|
|
||||||
|
for (let id in allBuilds) {
|
||||||
|
for (let buildName in allBuilds[id]) {
|
||||||
|
if (!builds.find((e) => e.buildName == buildName && e.id == id)) {
|
||||||
|
unusedBuilds.push({ id, buildName, name: Ships[id].properties.name })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
builds.sort(buildComparator);
|
||||||
|
unusedBuilds.sort(buildComparator);
|
||||||
|
|
||||||
|
this.state = { builds, unusedBuilds };
|
||||||
|
}
|
||||||
|
|
||||||
|
_addBuild(buildIndex) {
|
||||||
|
let { builds, unusedBuilds } = this.state;
|
||||||
|
builds.push(unusedBuilds[buildIndex]);
|
||||||
|
unusedBuilds = unusedBuilds.splice(buildIndex, 1);
|
||||||
|
builds.sort(buildComparator);
|
||||||
|
|
||||||
|
this.setState({ builds, unusedBuilds });
|
||||||
|
}
|
||||||
|
|
||||||
|
_removeBuild(buildIndex) {
|
||||||
|
let { builds, unusedBuilds } = this.state;
|
||||||
|
unusedBuilds.push(builds[buildIndex]);
|
||||||
|
builds = builds.splice(buildIndex, 1);
|
||||||
|
unusedBuilds.sort(buildComparator);
|
||||||
|
|
||||||
|
this.setState({ builds, unusedBuilds });
|
||||||
|
}
|
||||||
|
|
||||||
|
_selectBuilds() {
|
||||||
|
this.props.onSelect(this.state.builds);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
let { builds, unusedBuilds } = this.state;
|
||||||
|
let translate = this.context.language.translate;
|
||||||
|
|
||||||
|
let availableBuilds = unusedBuilds.map((build, 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 = builds.map((build, 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' onClick={ (e) => e.stopPropagation() }>
|
||||||
|
<h3>{translate('PHRASE_SELECT_BUILDS')}</h3>
|
||||||
|
<div id='build-select'>
|
||||||
|
<table>
|
||||||
|
<thead><tr><th colSpan='2'>{translate('available')}</th></tr></thead>
|
||||||
|
<tbody>
|
||||||
|
{availableBuilds}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<h1>⇆</h1>
|
||||||
|
<table>
|
||||||
|
<thead><tr><th colSpan='2'>{translate('added')}</th></tr></thead>
|
||||||
|
<tbody>
|
||||||
|
{selectedBuilds}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<br/>
|
||||||
|
<button className='cap' onClick={this._selectBuilds.bind(this)}>{translate('Ok')}</button>
|
||||||
|
<button className='r cap' onClick={() => InterfaceEvents.hideModal()}>{translate('Cancel')}</button>
|
||||||
|
</div>;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,7 +7,7 @@ export default class ModalExport extends TranslatedComponent {
|
|||||||
static propTypes = {
|
static propTypes = {
|
||||||
title: React.PropTypes.string,
|
title: React.PropTypes.string,
|
||||||
promise: React.PropTypes.func,
|
promise: React.PropTypes.func,
|
||||||
data: React.PropTypes.oneOfType([React.PropTypes.string, React.PropTypes.object])
|
data: React.PropTypes.oneOfType([React.PropTypes.string, React.PropTypes.object, React.PropTypes.array])
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import cn from 'classnames';
|
|||||||
import TranslatedComponent from './TranslatedComponent';
|
import TranslatedComponent from './TranslatedComponent';
|
||||||
import InterfaceEvents from '../utils/InterfaceEvents';
|
import InterfaceEvents from '../utils/InterfaceEvents';
|
||||||
import Persist from '../stores/Persist';
|
import Persist from '../stores/Persist';
|
||||||
import Ships from '../shipyard/Ships';
|
import { Ships } from 'coriolis-data';
|
||||||
import Ship from '../shipyard/Ship';
|
import Ship from '../shipyard/Ship';
|
||||||
import * as ModuleUtils from '../shipyard/ModuleUtils';
|
import * as ModuleUtils from '../shipyard/ModuleUtils';
|
||||||
import { Download } from './SvgIcons';
|
import { Download } from './SvgIcons';
|
||||||
@@ -36,7 +36,8 @@ function validateBuild(shipId, code, name) {
|
|||||||
throw shipData.properties.name + ' build "' + name + '" is not valid!';
|
throw shipData.properties.name + ' build "' + name + '" is not valid!';
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
Serializer.toShip(new Ship(shipId, shipData.properties, shipData.slots), code);
|
let ship = new Ship(shipId, shipData.properties, shipData.slots);
|
||||||
|
ship.buildFrom(code);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw shipData.properties.name + ' build "' + name + '" is not valid!';
|
throw shipData.properties.name + ' build "' + name + '" is not valid!';
|
||||||
}
|
}
|
||||||
@@ -58,17 +59,11 @@ function detailedJsonToBuild(detailedBuild) {
|
|||||||
throw detailedBuild.ship + ' Build "' + detailedBuild.name + '": Invalid data';
|
throw detailedBuild.ship + ' Build "' + detailedBuild.name + '": Invalid data';
|
||||||
}
|
}
|
||||||
|
|
||||||
return { shipId: ship.id, name: detailedBuild.name, code: Serializer.fromShip(ship) };
|
return { shipId: ship.id, name: detailedBuild.name, code: ship.toString() };
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class ModalImport extends TranslatedComponent {
|
export default class ModalImport extends TranslatedComponent {
|
||||||
|
|
||||||
static propTypes = {
|
|
||||||
title: React.propTypes.string,
|
|
||||||
promise: React.propTypes.func,
|
|
||||||
data: React.propTypes.oneOfType[React.propTypes.string, React.propTypes.object]
|
|
||||||
};
|
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
@@ -84,7 +79,11 @@ export default class ModalImport extends TranslatedComponent {
|
|||||||
};
|
};
|
||||||
|
|
||||||
this._process = this._process.bind(this);
|
this._process = this._process.bind(this);
|
||||||
|
this._import = this._import.bind(this);
|
||||||
this._importBackup = this._importBackup.bind(this);
|
this._importBackup = this._importBackup.bind(this);
|
||||||
|
this._importDetailedArray = this._importDetailedArray.bind(this);
|
||||||
|
this._importTextBuild = this._importTextBuild.bind(this);
|
||||||
|
this._validateImport = this._validateImport.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
_importBackup(importData) {
|
_importBackup(importData) {
|
||||||
@@ -121,7 +120,7 @@ export default class ModalImport extends TranslatedComponent {
|
|||||||
_importDetailedArray(importArr) {
|
_importDetailedArray(importArr) {
|
||||||
let builds = {};
|
let builds = {};
|
||||||
for (let i = 0, l = importArr.length; i < l; i++) {
|
for (let i = 0, l = importArr.length; i < l; i++) {
|
||||||
let build = this._detailedJsonToBuild(importArr[i]);
|
let build = detailedJsonToBuild(importArr[i]);
|
||||||
if (!builds[build.shipId]) {
|
if (!builds[build.shipId]) {
|
||||||
builds[build.shipId] = {};
|
builds[build.shipId] = {};
|
||||||
}
|
}
|
||||||
@@ -226,16 +225,17 @@ export default class ModalImport extends TranslatedComponent {
|
|||||||
this.setState({ builds });
|
this.setState({ builds });
|
||||||
}
|
}
|
||||||
|
|
||||||
_validateImport() {
|
_validateImport(e) {
|
||||||
let importData = null;
|
let importData = null;
|
||||||
let importString = $scope.importString.trim();
|
let importString = e.target.value;
|
||||||
this.setState({
|
this.setState({
|
||||||
builds: null,
|
builds: null,
|
||||||
comparisons: null,
|
comparisons: null,
|
||||||
discounts: null,
|
discounts: null,
|
||||||
errorMsg: null,
|
errorMsg: null,
|
||||||
importValid: false,
|
importValid: false,
|
||||||
insurance: null
|
insurance: null,
|
||||||
|
importString,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!importString) {
|
if (!importString) {
|
||||||
@@ -246,7 +246,7 @@ export default class ModalImport extends TranslatedComponent {
|
|||||||
if (textBuildRegex.test(importString)) { // E:D Shipyard build text
|
if (textBuildRegex.test(importString)) { // E:D Shipyard build text
|
||||||
importTextBuild(importString);
|
importTextBuild(importString);
|
||||||
} else { // JSON Build data
|
} else { // JSON Build data
|
||||||
importData = angular.fromJson($scope.importString);
|
importData = JSON.parse(importString);
|
||||||
|
|
||||||
if (!importData || typeof importData != 'object') {
|
if (!importData || typeof importData != 'object') {
|
||||||
throw 'Must be an object or array!';
|
throw 'Must be an object or array!';
|
||||||
@@ -272,7 +272,7 @@ export default class ModalImport extends TranslatedComponent {
|
|||||||
let builds = null, comparisons = null;
|
let builds = null, comparisons = null;
|
||||||
|
|
||||||
if (this.state.builds) {
|
if (this.state.builds) {
|
||||||
builds = $scope.builds;
|
builds = this.state.builds;
|
||||||
for (let shipId in builds) {
|
for (let shipId in builds) {
|
||||||
for (let buildName in builds[shipId]) {
|
for (let buildName in builds[shipId]) {
|
||||||
let code = builds[shipId][buildName];
|
let code = builds[shipId][buildName];
|
||||||
@@ -286,7 +286,7 @@ export default class ModalImport extends TranslatedComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (this.state.comparisons) {
|
if (this.state.comparisons) {
|
||||||
let comparisons = $scope.comparisons;
|
let comparisons = this.state.comparisons;
|
||||||
for (let name in comparisons) {
|
for (let name in comparisons) {
|
||||||
comparisons[name].useName = name;
|
comparisons[name].useName = name;
|
||||||
}
|
}
|
||||||
@@ -341,19 +341,20 @@ export default class ModalImport extends TranslatedComponent {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
let translate = this.context.language.translate;
|
let translate = this.context.language.translate;
|
||||||
|
let state = this.state;
|
||||||
let importStage;
|
let importStage;
|
||||||
|
|
||||||
if (this.state.processed) {
|
if (!state.processed) {
|
||||||
importStage = (
|
importStage = (
|
||||||
<div>
|
<div>
|
||||||
<textarea className='cb json' onCange={this.validateImport.bind(this)} placeholder={translate('PHRASE_IMPORT') | translate} />
|
<textarea className='cb json' onChange={this._validateImport} defaultValue={this.state.importString} placeholder={translate('PHRASE_IMPORT')} />
|
||||||
<button className='l cap' onClick={this.process.bind(this)} disabled={!this.state.importValid} >{translate('proceed')}</button>
|
<button className='l cap' onClick={this._process} disabled={!state.importValid} >{translate('proceed')}</button>
|
||||||
<div className='l warning' style={{ marginLeft:'3em' }}>{this.state.errorMsg}</div>
|
<div className='l warning' style={{ marginLeft:'3em' }}>{state.errorMsg}</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
let comparisonTable, edit, buildRows = [];
|
let comparisonTable, edit, buildRows = [];
|
||||||
if (this.state.comparisons) {
|
if (state.comparisons) {
|
||||||
let comparisonRows = [];
|
let comparisonRows = [];
|
||||||
|
|
||||||
for (let name in comparisons) {
|
for (let name in comparisons) {
|
||||||
@@ -387,7 +388,7 @@ export default class ModalImport extends TranslatedComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if(this.state.canEdit) {
|
if(this.state.canEdit) {
|
||||||
edit = <button className='l cap' style={{ marginLeft: '2em' }} ng-click={() => this.setState({processed: false})}>{translate('edit data')}</button>
|
edit = <button className='l cap' style={{ marginLeft: '2em' }} onClick={() => this.setState({processed: false})}>{translate('edit data')}</button>
|
||||||
}
|
}
|
||||||
|
|
||||||
let builds = this.state.builds;
|
let builds = this.state.builds;
|
||||||
@@ -410,11 +411,11 @@ export default class ModalImport extends TranslatedComponent {
|
|||||||
|
|
||||||
importStage = (
|
importStage = (
|
||||||
<div>
|
<div>
|
||||||
<table className='l' style='overflow:hidden;margin: 1em 0; width: 100%;'>
|
<table className='l' style={{ overflow:'hidden', margin: '1em 0', width: '100%'}}>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th style='text-align:left' >{translate('ship')}</th>
|
<th style={{ textAlign: 'left' }} >{translate('ship')}</th>
|
||||||
<th style='text-align:left' >{translate('build name')}</th>
|
<th style={{ textAlign: 'left' }} >{translate('build name')}</th>
|
||||||
<th >{translate('action')}</th>
|
<th >{translate('action')}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
@@ -422,9 +423,7 @@ export default class ModalImport extends TranslatedComponent {
|
|||||||
{buildRows}
|
{buildRows}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
{comparisonTable}
|
{comparisonTable}
|
||||||
|
|
||||||
<button className='cl l' onClick={this._import}><Download/> {translate('import')}</button>
|
<button className='cl l' onClick={this._import}><Download/> {translate('import')}</button>
|
||||||
{edit}
|
{edit}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,10 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { findDOMNode } from 'react-dom';
|
|
||||||
import d3 from 'd3';
|
import d3 from 'd3';
|
||||||
import cn from 'classnames';
|
import cn from 'classnames';
|
||||||
import Persist from '../stores/Persist';
|
|
||||||
import TranslatedComponent from './TranslatedComponent';
|
import TranslatedComponent from './TranslatedComponent';
|
||||||
import InterfaceEvents from '../utils/InterfaceEvents';
|
|
||||||
import { wrapCtxMenu } from '../utils/InterfaceEvents';
|
import { wrapCtxMenu } from '../utils/InterfaceEvents';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -27,7 +24,8 @@ export default class PowerBands extends TranslatedComponent {
|
|||||||
static propTypes = {
|
static propTypes = {
|
||||||
bands: React.PropTypes.array.isRequired,
|
bands: React.PropTypes.array.isRequired,
|
||||||
available: React.PropTypes.number.isRequired,
|
available: React.PropTypes.number.isRequired,
|
||||||
code: React.PropTypes.string
|
width: React.PropTypes.number.isRequired,
|
||||||
|
code: React.PropTypes.string,
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor(props, context) {
|
constructor(props, context) {
|
||||||
@@ -37,39 +35,46 @@ export default class PowerBands extends TranslatedComponent {
|
|||||||
this.wattAxis = d3.svg.axis().scale(this.wattScale).outerTickSize(0).orient('top').tickFormat(context.language.formats.r2);
|
this.wattAxis = d3.svg.axis().scale(this.wattScale).outerTickSize(0).orient('top').tickFormat(context.language.formats.r2);
|
||||||
this.pctAxis = d3.svg.axis().scale(this.pctScale).outerTickSize(0).orient('bottom').tickFormat(context.language.formats.rPct);
|
this.pctAxis = d3.svg.axis().scale(this.pctScale).outerTickSize(0).orient('bottom').tickFormat(context.language.formats.rPct);
|
||||||
|
|
||||||
this._updateWidth = this._updateWidth.bind(this);
|
this._updateDimensions = this._updateDimensions.bind(this);
|
||||||
this._updateAxes = this._updateAxes.bind(this);
|
this._updateScales = this._updateScales.bind(this);
|
||||||
this._selectNone = this._selectNone.bind(this);
|
this._selectNone = this._selectNone.bind(this);
|
||||||
|
|
||||||
let maxBand = props.bands[props.bands.length - 1];
|
let maxBand = props.bands[props.bands.length - 1];
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
outerWidth: 0,
|
|
||||||
innerWidth: 0,
|
|
||||||
sizes: this._initSizes(Persist.getSizeRatio()),
|
|
||||||
maxPwr: Math.max(props.available, maxBand.retractedSum, maxBand.deployedSum),
|
maxPwr: Math.max(props.available, maxBand.retractedSum, maxBand.deployedSum),
|
||||||
ret: {},
|
ret: {},
|
||||||
dep: {}
|
dep: {}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (props.width) {
|
||||||
|
this._updateDimensions(props, context.sizeRatio);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_initSizes(size) {
|
_updateDimensions(props, size) {
|
||||||
let barHeight = Math.round(20 * size);
|
let barHeight = Math.round(20 * size);
|
||||||
let innerHeight = (barHeight * 2) + 2;
|
let innerHeight = (barHeight * 2) + 2;
|
||||||
let mTop = Math.round(25 * size);
|
let mTop = Math.round(25 * size);
|
||||||
let mBottom = Math.round(25 * size);
|
let mBottom = Math.round(25 * size);
|
||||||
|
let mLeft = Math.round(45 * size);
|
||||||
|
let mRight = Math.round(140 * size);
|
||||||
|
let innerWidth = props.width - mLeft - mRight;
|
||||||
|
|
||||||
return {
|
this._updateScales(innerWidth, this.state.maxPwr, props.available);
|
||||||
|
|
||||||
|
this.setState({
|
||||||
barHeight,
|
barHeight,
|
||||||
innerHeight,
|
innerHeight,
|
||||||
mTop,
|
mTop,
|
||||||
mBottom,
|
mBottom,
|
||||||
mLeft: Math.round(45 * size),
|
mLeft,
|
||||||
mRight: Math.round(130 * size),
|
mRight,
|
||||||
|
innerWidth,
|
||||||
height: innerHeight + mTop + mBottom,
|
height: innerHeight + mTop + mBottom,
|
||||||
retY: (barHeight / 2),
|
retY: (barHeight / 2),
|
||||||
depY: (barHeight * 1.5) - 1
|
depY: (barHeight * 1.5) - 1
|
||||||
};
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
_selectNone() {
|
_selectNone() {
|
||||||
@@ -102,34 +107,18 @@ export default class PowerBands extends TranslatedComponent {
|
|||||||
this.setState({ dep: Object.assign({}, dep) });
|
this.setState({ dep: Object.assign({}, dep) });
|
||||||
}
|
}
|
||||||
|
|
||||||
_updateAxes(innerWidth, maxPwr, available) {
|
_updateScales(innerWidth, maxPwr, available) {
|
||||||
this.wattScale.range([0, innerWidth]).domain([0,maxPwr]).clamp(true);
|
this.wattScale.range([0, innerWidth]).domain([0,maxPwr]).clamp(true);
|
||||||
this.pctScale.range([0, innerWidth]).domain([0, maxPwr / available]).clamp(true);
|
this.pctScale.range([0, innerWidth]).domain([0, maxPwr / available]).clamp(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
_updateWidth() {
|
|
||||||
let outerWidth = findDOMNode(this).offsetWidth;
|
|
||||||
let innerWidth = outerWidth - this.state.sizes.mLeft - this.state.sizes.mRight;
|
|
||||||
this._updateAxes(innerWidth, this.state.maxPwr, this.props.available);
|
|
||||||
this.setState({ outerWidth, innerWidth });
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillMount(){
|
|
||||||
this.resizeListener = InterfaceEvents.addListener('windowResized', this._updateWidth);
|
|
||||||
this.sizeListener = Persist.addListener('sizeRatio', (sizeRatio) => this.setState({ sizes: this._initSizes(sizeRatio) }));
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
this._updateWidth();
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillReceiveProps(nextProps, nextContext) {
|
componentWillReceiveProps(nextProps, nextContext) {
|
||||||
let { innerWidth, maxPwr } = this.state;
|
let { innerWidth, maxPwr } = this.state;
|
||||||
let maxBand = nextProps.bands[nextProps.bands.length - 1];
|
let maxBand = nextProps.bands[nextProps.bands.length - 1];
|
||||||
let nextMaxPwr = Math.max(nextProps.available, maxBand.retractedSum, maxBand.deployedSum);
|
let nextMaxPwr = Math.max(nextProps.available, maxBand.retractedSum, maxBand.deployedSum);
|
||||||
|
|
||||||
if (maxPwr != nextMaxPwr) { // Update Axes if max power has changed
|
if (maxPwr != nextMaxPwr) { // Update Axes if max power has changed
|
||||||
this._updateAxes(innerWidth, nextMaxPwr, nextProps.available);
|
this._updateScales(innerWidth, nextMaxPwr, nextProps.available);
|
||||||
this.setState({ maxPwr: nextMaxPwr });
|
this.setState({ maxPwr: nextMaxPwr });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -137,19 +126,22 @@ export default class PowerBands extends TranslatedComponent {
|
|||||||
this.wattAxis.tickFormat(nextContext.language.formats.r2);
|
this.wattAxis.tickFormat(nextContext.language.formats.r2);
|
||||||
this.pctAxis.tickFormat(nextContext.language.formats.rPct);
|
this.pctAxis.tickFormat(nextContext.language.formats.rPct);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
componentWillUnmount(){
|
if (nextProps.width != this.props.width || this.context !== nextContext) {
|
||||||
this.resizeListener.remove();
|
this._updateDimensions(nextProps, nextContext.sizeRatio);
|
||||||
this.sizeListener.remove();
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
if (!this.props.width) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
let { wattScale, pctScale, context, props, state } = this;
|
let { wattScale, pctScale, context, props, state } = this;
|
||||||
let { translate, formats } = context.language;
|
let { translate, formats } = context.language;
|
||||||
let { f2, pct1, rPct, r2 } = formats; // wattFmt, pctFmt, pctAxis, wattAxis
|
let { f2, pct1, rPct, r2 } = formats; // wattFmt, pctFmt, pctAxis, wattAxis
|
||||||
let { available, bands } = props;
|
let { available, bands, width } = props;
|
||||||
let { sizes, outerWidth, innerWidth, maxPwr, ret, dep } = state;
|
let { innerWidth, maxPwr, ret, dep } = state;
|
||||||
let pwrWarningClass = cn('threshold', {exceeded: bands[0].retractedSum * 2 >= available });
|
let pwrWarningClass = cn('threshold', {exceeded: bands[0].retractedSum * 2 >= available });
|
||||||
let deployed = [];
|
let deployed = [];
|
||||||
let retracted = [];
|
let retracted = [];
|
||||||
@@ -158,71 +150,69 @@ export default class PowerBands extends TranslatedComponent {
|
|||||||
let retSum = 0;
|
let retSum = 0;
|
||||||
let depSum = 0;
|
let depSum = 0;
|
||||||
|
|
||||||
if (outerWidth > 0) {
|
for (var i = 0; i < bands.length; i++) {
|
||||||
for (var i = 0; i < bands.length; i++) {
|
let b = bands[i];
|
||||||
let b = bands[i];
|
retSum += (!retSelected || ret[i]) ? b.retracted : 0;
|
||||||
retSum += (!retSelected || ret[i]) ? b.retracted : 0;
|
depSum += (!depSelected || dep[i]) ? b.deployed + b.retracted : 0;
|
||||||
depSum += (!depSelected || dep[i]) ? b.deployed + b.retracted : 0;
|
|
||||||
|
|
||||||
if (outerWidth && b.retracted > 0) {
|
if (b.retracted > 0) {
|
||||||
let retLbl = bandText(b.retracted, i, wattScale);
|
let retLbl = bandText(b.retracted, i, wattScale);
|
||||||
|
|
||||||
retracted.push(<rect
|
retracted.push(<rect
|
||||||
key={'rB' + i}
|
key={'rB' + i}
|
||||||
width={Math.ceil(Math.max(wattScale(b.retracted), 0))}
|
width={Math.ceil(Math.max(wattScale(b.retracted), 0))}
|
||||||
height={sizes.barHeight}
|
height={state.barHeight}
|
||||||
x={Math.floor(Math.max(wattScale(b.retractedSum) - wattScale(b.retracted), 0))}
|
x={Math.floor(Math.max(wattScale(b.retractedSum) - wattScale(b.retracted), 0))}
|
||||||
y={1}
|
y={1}
|
||||||
|
onClick={this._selectRet.bind(this, i)}
|
||||||
|
className={getClass(ret[i], b.retractedSum, available)}
|
||||||
|
/>);
|
||||||
|
|
||||||
|
if (retLbl) {
|
||||||
|
retracted.push(<text
|
||||||
|
key={'rT' + i}
|
||||||
|
dy='0.5em'
|
||||||
|
textAnchor='middle'
|
||||||
|
height={state.barHeight}
|
||||||
|
x={wattScale(b.retractedSum) - (wattScale(b.retracted) / 2)}
|
||||||
|
y={state.retY}
|
||||||
onClick={this._selectRet.bind(this, i)}
|
onClick={this._selectRet.bind(this, i)}
|
||||||
className={getClass(ret[i], b.retractedSum, available)}
|
className='primary-bg'>{retLbl}</text>
|
||||||
/>);
|
);
|
||||||
|
|
||||||
if (retLbl) {
|
|
||||||
retracted.push(<text
|
|
||||||
key={'rT' + i}
|
|
||||||
dy='0.5em'
|
|
||||||
textAnchor='middle'
|
|
||||||
height={sizes.barHeight}
|
|
||||||
x={wattScale(b.retractedSum) - (wattScale(b.retracted) / 2)}
|
|
||||||
y={sizes.retY}
|
|
||||||
onClick={this._selectRet.bind(this, i)}
|
|
||||||
className='primary-bg'>{retLbl}</text>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (outerWidth && (b.retracted > 0 || b.deployed > 0)) {
|
if (b.retracted > 0 || b.deployed > 0) {
|
||||||
let depLbl = bandText(b.deployed + b.retracted, i, wattScale);
|
let depLbl = bandText(b.deployed + b.retracted, i, wattScale);
|
||||||
|
|
||||||
deployed.push(<rect
|
deployed.push(<rect
|
||||||
key={'dB' + i}
|
key={'dB' + i}
|
||||||
width={Math.ceil(Math.max(wattScale(b.deployed + b.retracted), 0))}
|
width={Math.ceil(Math.max(wattScale(b.deployed + b.retracted), 0))}
|
||||||
height={sizes.barHeight}
|
height={state.barHeight}
|
||||||
x={Math.floor(Math.max(wattScale(b.deployedSum) - wattScale(b.retracted) - wattScale(b.deployed), 0))}
|
x={Math.floor(Math.max(wattScale(b.deployedSum) - wattScale(b.retracted) - wattScale(b.deployed), 0))}
|
||||||
y={sizes.barHeight + 1}
|
y={state.barHeight + 1}
|
||||||
|
onClick={this._selectDep.bind(this, i)}
|
||||||
|
className={getClass(dep[i], b.deployedSum, available)}
|
||||||
|
/>);
|
||||||
|
|
||||||
|
if (depLbl) {
|
||||||
|
deployed.push(<text
|
||||||
|
key={'dT' + i}
|
||||||
|
dy='0.5em'
|
||||||
|
textAnchor='middle'
|
||||||
|
height={state.barHeight}
|
||||||
|
x={wattScale(b.deployedSum) - ((wattScale(b.retracted) + wattScale(b.deployed)) / 2)}
|
||||||
|
y={state.depY}
|
||||||
onClick={this._selectDep.bind(this, i)}
|
onClick={this._selectDep.bind(this, i)}
|
||||||
className={getClass(dep[i], b.deployedSum, available)}
|
className='primary-bg'>{depLbl}</text>
|
||||||
/>);
|
);
|
||||||
|
|
||||||
if (depLbl) {
|
|
||||||
deployed.push(<text
|
|
||||||
key={'dT' + i}
|
|
||||||
dy='0.5em'
|
|
||||||
textAnchor='middle'
|
|
||||||
height={sizes.barHeight}
|
|
||||||
x={wattScale(b.deployedSum) - ((wattScale(b.retracted) + wattScale(b.deployed)) / 2)}
|
|
||||||
y={sizes.depY}
|
|
||||||
onClick={this._selectDep.bind(this, i)}
|
|
||||||
className='primary-bg'>{depLbl}</text>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<svg style={{ marginTop: '1em', width: '100%', height: sizes.height }} onContextMenu={wrapCtxMenu(this._selectNone)}>
|
<svg style={{ marginTop: '1em', width: '100%', height: state.height }} onContextMenu={wrapCtxMenu(this._selectNone)}>
|
||||||
<g transform={`translate(${sizes.mLeft},${sizes.mTop})`}>
|
<g transform={`translate(${state.mLeft},${state.mTop})`}>
|
||||||
<g className='power-band'>{retracted}</g>
|
<g className='power-band'>{retracted}</g>
|
||||||
<g className='power-band'>{deployed}</g>
|
<g className='power-band'>{deployed}</g>
|
||||||
<g ref={ (elem) => d3.select(elem).call(this.wattAxis) } className='watt axis'></g>
|
<g ref={ (elem) => d3.select(elem).call(this.wattAxis) } className='watt axis'></g>
|
||||||
@@ -231,12 +221,12 @@ export default class PowerBands extends TranslatedComponent {
|
|||||||
axis.call(this.pctAxis);
|
axis.call(this.pctAxis);
|
||||||
axis.select('g:nth-child(6)').selectAll('line, text').attr('class', pwrWarningClass);
|
axis.select('g:nth-child(6)').selectAll('line, text').attr('class', pwrWarningClass);
|
||||||
}}
|
}}
|
||||||
className='pct axis' transform={`translate(0,${sizes.innerHeight})`}></g>
|
className='pct axis' transform={`translate(0,${state.innerHeight})`}></g>
|
||||||
<line x1={pctScale(0.5)} x2={pctScale(0.5)} y1='0' y2={sizes.innerHeight} className={pwrWarningClass} />
|
<line x1={pctScale(0.5)} x2={pctScale(0.5)} y1='0' y2={state.innerHeight} className={pwrWarningClass} />
|
||||||
<text dy='0.5em' x='-3' y={sizes.retY} className='primary upp' textAnchor='end'>{translate('ret')}</text>
|
<text dy='0.5em' x='-3' y={state.retY} className='primary upp' textAnchor='end'>{translate('ret')}</text>
|
||||||
<text dy='0.5em' x='-3' y={sizes.depY} className='primary upp' textAnchor='end'>{translate('dep')}</text>
|
<text dy='0.5em' x='-3' y={state.depY} className='primary upp' textAnchor='end'>{translate('dep')}</text>
|
||||||
<text dy='0.5em' x={innerWidth + 5} y={sizes.retY} className={getClass(retSelected, retSum, available)}>{f2(Math.max(0, retSum)) + ' (' + pct1(Math.max(0, retSum / available)) + ')'}</text>
|
<text dy='0.5em' x={innerWidth + 5} y={state.retY} className={getClass(retSelected, retSum, available)}>{f2(Math.max(0, retSum)) + ' (' + pct1(Math.max(0, retSum / available)) + ')'}</text>
|
||||||
<text dy='0.5em' x={innerWidth + 5} y={sizes.depY} className={getClass(depSelected, depSum, available)}>{f2(Math.max(0, depSum)) + ' (' + pct1(Math.max(0, depSum / available)) + ')'}</text>
|
<text dy='0.5em' x={innerWidth + 5} y={state.depY} className={getClass(depSelected, depSum, available)}>{f2(Math.max(0, depSum)) + ' (' + pct1(Math.max(0, depSum / available)) + ')'}</text>
|
||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { findDOMNode } from 'react-dom';
|
||||||
import cn from 'classnames';
|
import cn from 'classnames';
|
||||||
import TranslatedComponent from './TranslatedComponent';
|
import TranslatedComponent from './TranslatedComponent';
|
||||||
|
import InterfaceEvents from '../utils/InterfaceEvents';
|
||||||
import PowerBands from './PowerBands';
|
import PowerBands from './PowerBands';
|
||||||
import { slotName, nameComparator } from '../utils/SlotFunctions';
|
import { slotName, nameComparator } from '../utils/SlotFunctions';
|
||||||
import { Power, NoPower } from './SvgIcons';
|
import { Power, NoPower } from './SvgIcons';
|
||||||
@@ -22,61 +24,43 @@ export default class PowerManagement extends TranslatedComponent {
|
|||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this._renderPowerRows = this._renderPowerRows.bind(this);
|
this._renderPowerRows = this._renderPowerRows.bind(this);
|
||||||
this._sortName = this._sortName.bind(this);
|
this._updateWidth = this._updateWidth.bind(this);
|
||||||
this._sortType = this._sortType.bind(this);
|
this._sort = this._sort.bind(this);
|
||||||
this._sortPriority = this._sortPriority.bind(this);
|
|
||||||
this._sortPower = this._sortPower.bind(this);
|
|
||||||
this._sortRetracted = this._sortRetracted.bind(this);
|
|
||||||
this._sortDeployed = this._sortDeployed.bind(this);
|
|
||||||
|
|
||||||
this.state = {}; // State is initialized through componentWillMount
|
this.state = {
|
||||||
|
predicate: 'n',
|
||||||
|
desc: true,
|
||||||
|
width: 0
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
_sortOrder(predicate) {
|
_sortOrder(predicate) {
|
||||||
let desc = this.state.desc;
|
let desc = this.state.desc;
|
||||||
|
|
||||||
if (predicate == this.state.predicate) {
|
if (predicate == this.state.predicate) {
|
||||||
desc = !desc;
|
desc = !desc;
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
desc = true;
|
desc = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!desc) {
|
|
||||||
this.props.ship.powerList.reverse();
|
|
||||||
}
|
|
||||||
this.setState({ predicate, desc });
|
this.setState({ predicate, desc });
|
||||||
}
|
}
|
||||||
|
|
||||||
_sortName() {
|
_sort(ship, predicate, desc) {
|
||||||
let translate = this.context.language.translate;
|
let powerList = ship.powerList;
|
||||||
this.props.ship.powerList.sort(nameComparator(translate));
|
|
||||||
this._sortOrder('n');
|
|
||||||
}
|
|
||||||
|
|
||||||
_sortType() {
|
switch (predicate) {
|
||||||
this.props.ship.powerList.sort((a, b) => a.type.localeCompare(b.type));
|
case 'n': powerList.sort(nameComparator(this.context.language.translate)); break;
|
||||||
this._sortOrder('t');
|
case 't': powerList.sort((a, b) => a.type.localeCompare(b.type)); break;
|
||||||
}
|
case 'pri': powerList.sort((a, b) => a.priority - b.priority);break;
|
||||||
|
case 'pwr': powerList.sort((a, b) => (a.m ? a.m.power : 0) - (b.m ? b.m.power : 0)); break;
|
||||||
|
case 'r': powerList.sort((a, b) => ship.getSlotStatus(a) - ship.getSlotStatus(b)); break;
|
||||||
|
case 'd': powerList.sort((a, b) => ship.getSlotStatus(a, true) - ship.getSlotStatus(b, true)); break;
|
||||||
|
}
|
||||||
|
|
||||||
_sortPriority() {
|
if (!desc) {
|
||||||
this.props.ship.powerList.sort((a, b) => a.priority - b.priority);
|
powerList.reverse();
|
||||||
this._sortOrder('pri');
|
}
|
||||||
}
|
|
||||||
|
|
||||||
_sortPower() {
|
|
||||||
this.props.ship.powerList.sort((a, b) => (a.m ? a.m.power : 0) - (b.m ? b.m.power : 0));
|
|
||||||
this._sortOrder('pwr');
|
|
||||||
}
|
|
||||||
|
|
||||||
_sortRetracted() {
|
|
||||||
let ship = this.props.ship;
|
|
||||||
this.props.ship.powerList.sort((a, b) => ship.getSlotStatus(a) - ship.getSlotStatus(b));
|
|
||||||
this._sortOrder('ret');
|
|
||||||
}
|
|
||||||
|
|
||||||
_sortDeployed() {
|
|
||||||
let ship = this.props.ship;
|
|
||||||
this.props.ship.powerList.sort((a, b) => ship.getSlotStatus(a, true) - ship.getSlotStatus(b, true));
|
|
||||||
this._sortOrder('dep');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_priority(slot, inc) {
|
_priority(slot, inc) {
|
||||||
@@ -128,14 +112,30 @@ export default class PowerManagement extends TranslatedComponent {
|
|||||||
return powerRows;
|
return powerRows;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_updateWidth() {
|
||||||
|
this.setState({ width: findDOMNode(this).offsetWidth });
|
||||||
|
}
|
||||||
|
|
||||||
componentWillMount(){
|
componentWillMount(){
|
||||||
this._sortName();
|
this._sort(this.props.ship, this.state.predicate, this.state.desc);
|
||||||
// Listen to window resize
|
this.resizeListener = InterfaceEvents.addListener('windowResized', this._updateWidth);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this._updateWidth();
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUpdate(nextProps, nextState) {
|
||||||
|
// Can optimize this later: only sort when
|
||||||
|
// - predicate/desc changes
|
||||||
|
// - modules/language change AND sorting by type, name
|
||||||
|
// - power changes and sorting by pwr
|
||||||
|
// - enabled/disabled changes and sorting by priority
|
||||||
|
this._sort(nextProps.ship, nextState.predicate, nextState.desc);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount(){
|
componentWillUnmount(){
|
||||||
// remove window listener
|
this.resizeListener.remove();
|
||||||
// remove mouse move listener / touch listner?
|
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
@@ -143,18 +143,19 @@ export default class PowerManagement extends TranslatedComponent {
|
|||||||
let { translate, formats } = this.context.language;
|
let { translate, formats } = this.context.language;
|
||||||
let pwr = formats.f2;
|
let pwr = formats.f2;
|
||||||
let pp = ship.standard[0].m;
|
let pp = ship.standard[0].m;
|
||||||
|
let sortOrder = this._sortOrder;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='group half' id='componentPriority'>
|
<div className='group half' id='componentPriority'>
|
||||||
<table style={{ width: '100%' }}>
|
<table style={{ width: '100%' }}>
|
||||||
<thead>
|
<thead>
|
||||||
<tr className='main'>
|
<tr className='main'>
|
||||||
<th colSpan='2' className='sortable le' onClick={this._sortName} >{translate('module')}</th>
|
<th colSpan='2' className='sortable le' onClick={sortOrder.bind(this, 'n')} >{translate('module')}</th>
|
||||||
<th style={{ width: '3em' }} className='sortable' onClick={this._sortType} >{translate('type')}</th>
|
<th style={{ width: '3em' }} className='sortable' onClick={sortOrder.bind(this, 't')} >{translate('type')}</th>
|
||||||
<th style={{ width: '4em' }} className='sortable' onClick={this._sortPriority} >{translate('pri')}</th>
|
<th style={{ width: '4em' }} className='sortable' onClick={sortOrder.bind(this, 'pri')} >{translate('pri')}</th>
|
||||||
<th colSpan='2' className='sortable' onClick={this._sortPower} >{translate('PWR')}</th>
|
<th colSpan='2' className='sortable' onClick={sortOrder.bind(this, 'pwr')} >{translate('PWR')}</th>
|
||||||
<th style={{ width: '3em' }} className='sortable' onClick={this._sortRetracted} >{translate('ret')}</th>
|
<th style={{ width: '3em' }} className='sortable' onClick={sortOrder.bind(this, 'r')} >{translate('ret')}</th>
|
||||||
<th style={{ width: '3em' }} className='sortable' onClick={this._sortDeployed} >{translate('dep')}</th>
|
<th style={{ width: '3em' }} className='sortable' onClick={sortOrder.bind(this, 'd')} >{translate('dep')}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@@ -172,7 +173,7 @@ export default class PowerManagement extends TranslatedComponent {
|
|||||||
{this._renderPowerRows(ship, translate, pwr, formats.pct1)}
|
{this._renderPowerRows(ship, translate, pwr, formats.pct1)}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
<PowerBands code={code} available={ship.standard[0].m.pGen} bands={ship.priorityBands} />
|
<PowerBands width={this.state.width} code={code} available={ship.standard[0].m.pGen} bands={ship.priorityBands} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import TranslatedComponent from './TranslatedComponent';
|
|||||||
import cn from 'classnames';
|
import cn from 'classnames';
|
||||||
import { SizeMap } from '../shipyard/Constants';
|
import { SizeMap } from '../shipyard/Constants';
|
||||||
import { Warning } from './SvgIcons';
|
import { Warning } from './SvgIcons';
|
||||||
import shallowEqual from '../utils/shallowEqual';
|
|
||||||
|
|
||||||
export default class ShipSummaryTable extends TranslatedComponent {
|
export default class ShipSummaryTable extends TranslatedComponent {
|
||||||
|
|
||||||
@@ -63,8 +62,8 @@ export default class ShipSummaryTable extends TranslatedComponent {
|
|||||||
<tr>
|
<tr>
|
||||||
<td className='cap'>{translate(SizeMap[ship.class])}</td>
|
<td className='cap'>{translate(SizeMap[ship.class])}</td>
|
||||||
<td>{ship.agility}/10</td>
|
<td>{ship.agility}/10</td>
|
||||||
<td>{ ship.canThrust() ? <span>{int(ship.topSpeed)} {u.ms}</span> : <span className='warning'>0 <Warning/></span> }</td>
|
<td>{ ship.canThrust() ? <span>{int(ship.topSpeed)} {u['m/s']}</span> : <span className='warning'>0 <Warning/></span> }</td>
|
||||||
<td>{ ship.canBoost() ? <span>{int(ship.topBoost)} {u.ms}</span> : <span className='warning'>0 <Warning/></span> }</td>
|
<td>{ ship.canBoost() ? <span>{int(ship.topBoost)} {u['m/s']}</span> : <span className='warning'>0 <Warning/></span> }</td>
|
||||||
<td>{round(ship.totalDps)}</td>
|
<td>{round(ship.totalDps)}</td>
|
||||||
<td>{int(ship.armour)} {armourDetails}</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>{int(ship.shieldStrength)} {u.MJ} { ship.shieldMultiplier > 1 && ship.shieldStrength > 0 ? <u>({formats.rPct(ship.shieldMultiplier)})</u> : null }</td>
|
||||||
|
|||||||
68
src/app/components/Slider.jsx
Normal file
68
src/app/components/Slider.jsx
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
export default class Slider extends React.Component {
|
||||||
|
|
||||||
|
static defaultProps = {
|
||||||
|
axis: false,
|
||||||
|
min: 0,
|
||||||
|
max: 1
|
||||||
|
};
|
||||||
|
|
||||||
|
static PropTypes = {
|
||||||
|
axis: React.PropTypes.bool,
|
||||||
|
axisUnit: React.PropTypes.string,
|
||||||
|
min: React.PropTypes.number,
|
||||||
|
max: React.PropTypes.number,
|
||||||
|
onChange: React.PropTypes.func.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.down = this.down.bind(this);
|
||||||
|
this.up = this.up.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
down(e) {
|
||||||
|
let rect = e.currentTarget.getBoundingClientRect();
|
||||||
|
this.move = this.updatePercent.bind(this, rect.left, rect.width);
|
||||||
|
this.move(e);
|
||||||
|
document.addEventListener("mousemove", this.move);
|
||||||
|
document.addEventListener("mouseup", this.up);
|
||||||
|
}
|
||||||
|
|
||||||
|
up() {
|
||||||
|
document.removeEventListener("mousemove", this.move);
|
||||||
|
document.removeEventListener("mouseup", this.up);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount(){
|
||||||
|
this.up();
|
||||||
|
}
|
||||||
|
|
||||||
|
updatePercent(left, width, event) {
|
||||||
|
this.props.onChange(Math.min(Math.max((event.clientX - left) / width, 0), 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
let pctStr = (this.props.percent * 100) + '%';
|
||||||
|
let { axis, axisUnit, min, max } = this.props;
|
||||||
|
let axisGroup;
|
||||||
|
|
||||||
|
if (axis) {
|
||||||
|
axisGroup = <g style={{ fontSize: '.7em' }}>
|
||||||
|
<text className='primary-disabled' y="3em" x="0" style={{ textAnchor: 'middle' }}>{min + axisUnit}</text>
|
||||||
|
<text className='primary-disabled' y="3em" x="50%" style={{ textAnchor: 'middle' }}>{(min + max / 2) + axisUnit}</text>
|
||||||
|
<text className='primary-disabled' y="3em" x="99%" style={{ textAnchor: 'middle' }}>{max + axisUnit}</text>
|
||||||
|
</g>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <svg style={{ width: '100%', height: axis ? '2.5em' : '1.5em', padding: '0 0.6em', cursor: 'col-resize', boxSizing: 'border-box' }}>
|
||||||
|
<rect className='primary' style={{ opacity: 0.3 }} y='0.25em' rx='0.3em' ry='0.3em' width='100%' height='0.7em' />
|
||||||
|
<rect className='primary-disabled'y='0.45em' rx='0.15em' ry='0.15em' width={pctStr} height='0.3em' />
|
||||||
|
<circle className='primary' r='0.6em' cy='0.6em' cx={pctStr} />
|
||||||
|
<rect width='100%' height='100%' fillOpacity='0' onMouseDown={this.down}/>
|
||||||
|
{axisGroup}
|
||||||
|
</svg>;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -42,7 +42,7 @@ export default class StandardSlot extends TranslatedComponent {
|
|||||||
<div className={'cb'}>
|
<div className={'cb'}>
|
||||||
{ m.optmass ? <div className={'l'}>{translate('optimal mass') + ': '}{m.optmass}{units.T}</div> : null }
|
{ m.optmass ? <div className={'l'}>{translate('optimal mass') + ': '}{m.optmass}{units.T}</div> : null }
|
||||||
{ m.maxmass ? <div className={'l'}>{translate('max mass') + ': '}{m.maxmass}{units.T}</div> : null }
|
{ m.maxmass ? <div className={'l'}>{translate('max mass') + ': '}{m.maxmass}{units.T}</div> : null }
|
||||||
{ m.range ? <div className={'l'}>{translate('range')} {m.range}{units.km}</div> : null }
|
{ m.range ? <div className={'l'}>{translate('range')}: {m.range}{units.km}</div> : null }
|
||||||
{ m.time ? <div className={'l'}>{translate('time')}: {formats.time(m.time)}</div> : null }
|
{ m.time ? <div className={'l'}>{translate('time')}: {formats.time(m.time)}</div> : null }
|
||||||
{ m.eff ? <div className={'l'}>{translate('efficiency')}: {m.eff}</div> : null }
|
{ m.eff ? <div className={'l'}>{translate('efficiency')}: {m.eff}</div> : null }
|
||||||
{ m.pGen ? <div className={'l'}>{translate('power')}: {m.pGen}{units.MW}</div> : null }
|
{ m.pGen ? <div className={'l'}>{translate('power')}: {m.pGen}{units.MW}</div> : null }
|
||||||
|
|||||||
@@ -1,21 +1,44 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import shallowEqual from '../utils/shallowEqual';
|
import shallowEqual from '../utils/shallowEqual';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @class Abstract TranslatedComponent
|
||||||
|
*/
|
||||||
export default class TranslatedComponent extends React.Component {
|
export default class TranslatedComponent extends React.Component {
|
||||||
|
|
||||||
static contextTypes = {
|
static contextTypes = {
|
||||||
language: React.PropTypes.object.isRequired
|
language: React.PropTypes.object.isRequired,
|
||||||
|
sizeRatio: React.PropTypes.number.isRequired
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created an instance of a Translated Component. This is an abstract class.
|
||||||
|
* @param {object} props Properties
|
||||||
|
*/
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.didContextChange = this.didContextChange.bind(this);
|
this.didContextChange = this.didContextChange.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine if the context change incldues a language or size change
|
||||||
|
* @param {object} nextContext The incoming / next context
|
||||||
|
* @return {boolean} true if the language has changed
|
||||||
|
*/
|
||||||
didContextChange(nextContext){
|
didContextChange(nextContext){
|
||||||
return nextContext.language !== this.context.language;
|
return nextContext.language !== this.context.language || nextContext.sizeRatio != this.context.sizeRatio;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Translated components are 'pure' components that only render when
|
||||||
|
* props, state, or context changes. This method performs a shallow comparison to
|
||||||
|
* determine change.
|
||||||
|
*
|
||||||
|
* @param {object} nextProps
|
||||||
|
* @param {objec} nextState
|
||||||
|
* @param {objec} nextContext
|
||||||
|
* @return {boolean} True if props, state, or context has changed
|
||||||
|
*/
|
||||||
shouldComponentUpdate(nextProps, nextState, nextContext) {
|
shouldComponentUpdate(nextProps, nextState, nextContext) {
|
||||||
return !shallowEqual(this.props, nextProps)
|
return !shallowEqual(this.props, nextProps)
|
||||||
|| !shallowEqual(this.state, nextState)
|
|| !shallowEqual(this.state, nextState)
|
||||||
|
|||||||
@@ -1,80 +0,0 @@
|
|||||||
angular.module('app').directive('comparisonTable', ['$state', '$translate', '$rootScope', function($state, $translate, $rootScope) {
|
|
||||||
|
|
||||||
function tblHeader(facets) {
|
|
||||||
var r1 = ['<tr class="main"><th rowspan="2" class="prop" prop="name">', $translate.instant('SHIP'), '</th><th rowspan="2" class="prop" prop="buildName">', $translate.instant('BUILD'), '</th>'];
|
|
||||||
var r2 = [];
|
|
||||||
for (var i = 0, l = facets.length; i < l; i++) {
|
|
||||||
if (facets[i].active) {
|
|
||||||
var f = facets[i];
|
|
||||||
var p = f.props;
|
|
||||||
var pl = p.length;
|
|
||||||
r1.push('<th rowspan="', f.props.length == 1 ? 2 : 1, '" colspan="', pl, '"');
|
|
||||||
|
|
||||||
if (pl == 1) {
|
|
||||||
r1.push(' prop="', p[0], '" class="prop"');
|
|
||||||
} else {
|
|
||||||
for (var j = 0; j < pl; j++) {
|
|
||||||
r2.push('<th prop="', p[j], '" class="prop ', j === 0 ? 'lft' : '', '">', $translate.instant(f.lbls[j]), '</th>');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
r1.push('>', $translate.instant(f.title), '</th>');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
r1.push('</tr><tr>');
|
|
||||||
r1.push(r2.join(''));
|
|
||||||
r1.push('</tr>');
|
|
||||||
return r1.join('');
|
|
||||||
}
|
|
||||||
|
|
||||||
function tblBody(facets, builds) {
|
|
||||||
var body = [];
|
|
||||||
|
|
||||||
if (builds.length === 0) {
|
|
||||||
return '<td colspan="100" class="cen">No builds added to comparison!</td';
|
|
||||||
}
|
|
||||||
|
|
||||||
for (var i = 0, l = builds.length; i < l; i++) {
|
|
||||||
var b = builds[i];
|
|
||||||
body.push('<tr class="tr">');
|
|
||||||
var href = $state.href('outfit', { shipId: b.id, code: b.code, bn: b.buildName });
|
|
||||||
body.push('<td class="tl"><a href="', href, '">', b.name, '</a></td>');
|
|
||||||
body.push('<td class="tl"><a href="', href, '">', b.buildName, '</a></td>');
|
|
||||||
|
|
||||||
for (var j = 0, fl = facets.length; j < fl; j++) {
|
|
||||||
if (facets[j].active) {
|
|
||||||
var f = facets[j];
|
|
||||||
var p = f.props;
|
|
||||||
for (var k = 0, pl = p.length; k < pl; k++) {
|
|
||||||
body.push('<td>', $rootScope[f.fmt](b[p[k]]), '<u> ', $translate.instant(f.unit), '</u></td>');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
body.push('</tr>');
|
|
||||||
}
|
|
||||||
|
|
||||||
return body.join('');
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
restrict: 'A',
|
|
||||||
|
|
||||||
link: function(scope, element) {
|
|
||||||
var header = angular.element('<thead></thead>');
|
|
||||||
var body = angular.element('<tbody></tbody>');
|
|
||||||
element.append(header);
|
|
||||||
element.append(body);
|
|
||||||
|
|
||||||
var updateAll = function() {
|
|
||||||
header.html(tblHeader(scope.facets));
|
|
||||||
body.html(tblBody(scope.facets, scope.builds));
|
|
||||||
};
|
|
||||||
|
|
||||||
scope.$watchCollection('facets', updateAll);
|
|
||||||
scope.$watch('tblUpdate', updateAll);
|
|
||||||
scope.$watchCollection('builds', function() {
|
|
||||||
body.html(tblBody(scope.facets, scope.builds));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}]);
|
|
||||||
@@ -1,247 +0,0 @@
|
|||||||
angular.module('app').directive('lineChart', ['$window', '$translate', '$rootScope', function($window, $translate, $rootScope) {
|
|
||||||
|
|
||||||
var RENDER_POINTS = 20; // Only render 20 points on the graph
|
|
||||||
|
|
||||||
return {
|
|
||||||
restrict: 'A',
|
|
||||||
scope: {
|
|
||||||
config: '=',
|
|
||||||
series: '='
|
|
||||||
},
|
|
||||||
link: function(scope, element) {
|
|
||||||
var seriesConfig = scope.series,
|
|
||||||
series = seriesConfig.series,
|
|
||||||
color = d3.scale.ordinal().range(scope.series.colors ? scope.series.colors : ['#ff8c0d']),
|
|
||||||
config = scope.config,
|
|
||||||
labels = config.labels,
|
|
||||||
margin = { top: 15, right: 15, bottom: 35, left: 60 },
|
|
||||||
fmtLong = null,
|
|
||||||
func = seriesConfig.func,
|
|
||||||
drag = d3.behavior.drag(),
|
|
||||||
dragging = false,
|
|
||||||
// Define Scales
|
|
||||||
x = d3.scale.linear(),
|
|
||||||
y = d3.scale.linear(),
|
|
||||||
// Define Axes
|
|
||||||
xAxis = d3.svg.axis().scale(x).outerTickSize(0).orient('bottom'),
|
|
||||||
yAxis = d3.svg.axis().scale(y).ticks(6).outerTickSize(0).orient('left'),
|
|
||||||
data = [];
|
|
||||||
|
|
||||||
// Create chart
|
|
||||||
var svg = d3.select(element[0]).append('svg');
|
|
||||||
var vis = svg.append('g').attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
|
|
||||||
var lines = vis.append('g');
|
|
||||||
|
|
||||||
// Define Area
|
|
||||||
var line = d3.svg.line().y(function(d) { return y(d[1]); });
|
|
||||||
|
|
||||||
// Create Y Axis SVG Elements
|
|
||||||
var yTxt = vis.append('g').attr('class', 'y axis')
|
|
||||||
.append('text')
|
|
||||||
.attr('class', 'cap')
|
|
||||||
.attr('transform', 'rotate(-90)')
|
|
||||||
.attr('y', -50)
|
|
||||||
.attr('dy', '.1em')
|
|
||||||
.style('text-anchor', 'middle');
|
|
||||||
|
|
||||||
// Create X Axis SVG Elements
|
|
||||||
var xLbl = vis.append('g').attr('class', 'x axis');
|
|
||||||
var xTxt = xLbl.append('text')
|
|
||||||
.attr('class', 'cap')
|
|
||||||
.attr('y', 30)
|
|
||||||
.attr('dy', '.1em')
|
|
||||||
.style('text-anchor', 'middle');
|
|
||||||
|
|
||||||
// xTxt.append('tspan').attr('class', 'metric');
|
|
||||||
// yTxt.append('tspan').attr('class', 'metric');
|
|
||||||
|
|
||||||
// Create and Add tooltip
|
|
||||||
var tipHeight = 2 + (1.25 * (series ? series.length : 0.75));
|
|
||||||
var tips = vis.append('g').style('display', 'none').attr('class', 'tooltip');
|
|
||||||
var markers = vis.append('g').style('display', 'none');
|
|
||||||
|
|
||||||
tips.append('rect')
|
|
||||||
.attr('height', tipHeight + 'em')
|
|
||||||
.attr('y', (-tipHeight / 2) + 'em')
|
|
||||||
.attr('class', 'tip');
|
|
||||||
|
|
||||||
tips.append('text')
|
|
||||||
.attr('class', 'label x')
|
|
||||||
.attr('dy', (-tipHeight / 2) + 'em')
|
|
||||||
.attr('y', '1.25em');
|
|
||||||
|
|
||||||
var background = vis.append('rect') // Background to capture hover/drag
|
|
||||||
.attr('fill-opacity', 0)
|
|
||||||
.on('mouseover', showTip)
|
|
||||||
.on('mouseout', hideTip)
|
|
||||||
.on('mousemove', moveTip)
|
|
||||||
.call(drag);
|
|
||||||
|
|
||||||
drag
|
|
||||||
.on('dragstart', function() {
|
|
||||||
dragging = true;
|
|
||||||
moveTip.call(this);
|
|
||||||
showTip();
|
|
||||||
})
|
|
||||||
.on('dragend', function() {
|
|
||||||
dragging = false;
|
|
||||||
hideTip();
|
|
||||||
})
|
|
||||||
.on('drag', moveTip);
|
|
||||||
|
|
||||||
updateFormats();
|
|
||||||
|
|
||||||
function render() {
|
|
||||||
var width = element[0].parentElement.offsetWidth,
|
|
||||||
height = width * 0.5 * $rootScope.sizeRatio,
|
|
||||||
xMax = seriesConfig.xMax,
|
|
||||||
xMin = seriesConfig.xMin,
|
|
||||||
yMax = seriesConfig.yMax,
|
|
||||||
yMin = seriesConfig.yMin,
|
|
||||||
w = width - margin.left - margin.right,
|
|
||||||
h = height - margin.top - margin.bottom,
|
|
||||||
c, s, val, yVal, delta;
|
|
||||||
|
|
||||||
data.length = 0; // Reset Data array
|
|
||||||
|
|
||||||
if (seriesConfig.xMax == seriesConfig.xMin) {
|
|
||||||
line.x(function(d, i) { return i * w; });
|
|
||||||
} else {
|
|
||||||
line.x(function(d) { return x(d[0]); });
|
|
||||||
}
|
|
||||||
|
|
||||||
if (series) {
|
|
||||||
for (s = 0; s < series.length; s++) {
|
|
||||||
data.push([]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (xMax == xMin) {
|
|
||||||
yVal = func(xMin);
|
|
||||||
for (s = 0; s < series.length; s++) {
|
|
||||||
data[s].push( [ xMin, yVal[ series[s] ] ], [ 1, yVal[ series[s] ] ]);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
delta = (xMax - xMin) / RENDER_POINTS;
|
|
||||||
val = 0;
|
|
||||||
for (c = 0; c <= RENDER_POINTS; c++) {
|
|
||||||
yVal = func(val);
|
|
||||||
for (s = 0; s < series.length; s++) {
|
|
||||||
data[s].push([ val, yVal[ series[s] ] ]);
|
|
||||||
}
|
|
||||||
val += delta;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
var seriesData = [];
|
|
||||||
if (xMax == xMin) {
|
|
||||||
yVal = func(xMin);
|
|
||||||
seriesData.push([ xMin, yVal ], [ 1, yVal ]);
|
|
||||||
} else {
|
|
||||||
delta = (xMax - xMin) / RENDER_POINTS;
|
|
||||||
val = 0;
|
|
||||||
for (c = 0; c <= RENDER_POINTS; c++) {
|
|
||||||
seriesData.push([val, func(val) ]);
|
|
||||||
val += delta;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
data.push(seriesData);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update Chart Size
|
|
||||||
svg.attr('width', width).attr('height', height);
|
|
||||||
background.attr('height', h).attr('width', w);
|
|
||||||
|
|
||||||
// Update domain and scale for axes
|
|
||||||
x.range([0, w]).domain([xMin, xMax]).clamp(true);
|
|
||||||
xLbl.attr('transform', 'translate(0,' + h + ')');
|
|
||||||
xTxt.attr('x', w / 2);
|
|
||||||
y.range([h, 0]).domain([yMin, yMax]);
|
|
||||||
yTxt.attr('x', -h / 2);
|
|
||||||
vis.selectAll('.y.axis').call(yAxis);
|
|
||||||
vis.selectAll('.x.axis').call(xAxis);
|
|
||||||
|
|
||||||
lines.selectAll('path.line')
|
|
||||||
.data(data)
|
|
||||||
.attr('d', line) // Update existing series
|
|
||||||
.enter() // Add new series
|
|
||||||
.append('path')
|
|
||||||
.attr('class', 'line')
|
|
||||||
.attr('stroke', function(d, i) { return color(i); })
|
|
||||||
.attr('stroke-width', 2)
|
|
||||||
.attr('d', line);
|
|
||||||
|
|
||||||
tips.selectAll('text.label.y').data(data).enter()
|
|
||||||
.append('text')
|
|
||||||
.attr('class', 'label y')
|
|
||||||
.attr('dy', (-tipHeight / 2) + 'em')
|
|
||||||
.attr('y', function(d, i) { return 1.25 * (i + 2) + 'em'; });
|
|
||||||
|
|
||||||
markers.selectAll('circle.marker').data(data).enter().append('circle').attr('class', 'marker').attr('r', 4);
|
|
||||||
}
|
|
||||||
|
|
||||||
function showTip() {
|
|
||||||
tips.style('display', null);
|
|
||||||
markers.style('display', null);
|
|
||||||
}
|
|
||||||
|
|
||||||
function hideTip() {
|
|
||||||
if (!dragging) {
|
|
||||||
tips.style('display', 'none');
|
|
||||||
markers.style('display', 'none');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function moveTip() {
|
|
||||||
var xPos = d3.mouse(this)[0],
|
|
||||||
x0 = x.invert(xPos),
|
|
||||||
y0 = func(x0),
|
|
||||||
yTotal = 0,
|
|
||||||
flip = (x0 / x.domain()[1] > 0.65),
|
|
||||||
tipWidth = 0,
|
|
||||||
minTransY = (tips.selectAll('rect').node().getBoundingClientRect().height / 2) - margin.top;
|
|
||||||
|
|
||||||
tips.selectAll('text.label.y').text(function(d, i) {
|
|
||||||
var yVal = series ? y0[series[i]] : y0;
|
|
||||||
yTotal += yVal;
|
|
||||||
return (series ? $translate.instant(series[i]) : '') + ' ' + fmtLong(yVal);
|
|
||||||
}).append('tspan').attr('class', 'metric').text(' ' + $translate.instant(labels.yAxis.unit));
|
|
||||||
|
|
||||||
tips.selectAll('text').each(function() {
|
|
||||||
if (this.getBBox().width > tipWidth) {
|
|
||||||
tipWidth = Math.ceil(this.getBBox().width);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
tipWidth += 8;
|
|
||||||
markers.selectAll('circle.marker').attr('cx', x(x0)).attr('cy', function(d, i) { return y(series ? y0[series[i]] : y0); });
|
|
||||||
tips.selectAll('text.label').attr('x', flip ? -12 : 12).style('text-anchor', flip ? 'end' : 'start');
|
|
||||||
tips.selectAll('text.label.x').text(fmtLong(x0)).append('tspan').attr('class', 'metric').text(' ' + $translate.instant(labels.xAxis.unit));
|
|
||||||
tips.attr('transform', 'translate(' + x(x0) + ',' + Math.max(minTransY, y(yTotal / (series ? series.length : 1))) + ')');
|
|
||||||
tips.selectAll('rect')
|
|
||||||
.attr('width', tipWidth + 4)
|
|
||||||
.attr('x', flip ? -tipWidth - 12 : 8)
|
|
||||||
.style('text-anchor', flip ? 'end' : 'start');
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateFormats() {
|
|
||||||
xTxt.text($translate.instant(labels.xAxis.title)).append('tspan').attr('class', 'metric').text(' (' + $translate.instant(labels.xAxis.unit) + ')');
|
|
||||||
yTxt.text($translate.instant(labels.yAxis.title)).append('tspan').attr('class', 'metric').text(' (' + $translate.instant(labels.yAxis.unit) + ')');
|
|
||||||
fmtLong = $rootScope.localeFormat.numberFormat('.2f');
|
|
||||||
xAxis.tickFormat($rootScope.localeFormat.numberFormat('.2r'));
|
|
||||||
yAxis.tickFormat($rootScope.localeFormat.numberFormat('.3r'));
|
|
||||||
render();
|
|
||||||
}
|
|
||||||
|
|
||||||
angular.element($window).bind('orientationchange resize render', render);
|
|
||||||
scope.$watchCollection('series', render); // Watch for changes in the series data
|
|
||||||
scope.$on('languageChanged', updateFormats);
|
|
||||||
scope.$on('$destroy', function() {
|
|
||||||
angular.element($window).unbind('orientationchange resize render', render);
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}]);
|
|
||||||
@@ -1,90 +0,0 @@
|
|||||||
angular.module('app').directive('slider', ['$window', function($window) {
|
|
||||||
|
|
||||||
return {
|
|
||||||
restrict: 'A',
|
|
||||||
scope: {
|
|
||||||
min: '=',
|
|
||||||
def: '=',
|
|
||||||
max: '=',
|
|
||||||
unit: '=',
|
|
||||||
change: '&onChange',
|
|
||||||
ignoreResize: '='
|
|
||||||
},
|
|
||||||
link: function(scope, element) {
|
|
||||||
var unit = scope.unit,
|
|
||||||
margin = unit ? { top: -10, right: 145, left: 50 } : { top: 0, right: 10, left: 10 },
|
|
||||||
height = unit ? 40 : 20, // Height is fixed
|
|
||||||
h = height - margin.top,
|
|
||||||
fmt = d3.format('.2f'),
|
|
||||||
pct = d3.format('.1%'),
|
|
||||||
def = scope.def !== undefined ? scope.def : scope.max,
|
|
||||||
val = def,
|
|
||||||
svg = d3.select(element[0]).append('svg'),
|
|
||||||
vis = svg.append('g').attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'),
|
|
||||||
xAxisContainer = vis.append('g').attr('class', 'x slider-axis').attr('transform', 'translate(0,' + h / 2 + ')'),
|
|
||||||
x = d3.scale.linear(),
|
|
||||||
xAxis = d3.svg.axis().scale(x).orient('bottom').tickFormat(function(d) { return d + unit; }).tickSize(0).tickPadding(12),
|
|
||||||
slider = vis.append('g').attr('class', 'slider'),
|
|
||||||
filled = slider.append('path').attr('class', 'filled').attr('transform', 'translate(0,' + h / 2 + ')'),
|
|
||||||
brush = d3.svg.brush().x(x).extent([scope.max, scope.max]).on('brush', brushed),
|
|
||||||
handle = slider.append('circle').attr('class', 'handle').attr('r', '0.6em'),
|
|
||||||
lbl = unit ? slider.append('g').append('text').attr('y', h / 2) : null;
|
|
||||||
|
|
||||||
slider.call(brush);
|
|
||||||
slider.select('.background').attr('height', h);
|
|
||||||
handle.attr('transform', 'translate(0,' + h / 2 + ')');
|
|
||||||
|
|
||||||
function render() {
|
|
||||||
var width = element[0].offsetWidth, w = width - margin.left - margin.right;
|
|
||||||
svg.attr('width', width).attr('height', height);
|
|
||||||
x.domain([scope.min || 0, scope.max]).range([0, w]).clamp(true);
|
|
||||||
handle.attr('cx', x(val));
|
|
||||||
if (unit) {
|
|
||||||
xAxisContainer.call(xAxis.tickValues([0, scope.max / 4, scope.max / 2, (3 * scope.max) / 4, scope.max]));
|
|
||||||
lbl.attr('x', w + 20);
|
|
||||||
}
|
|
||||||
slider.call(brush.extent([val, val]));
|
|
||||||
drawBrush();
|
|
||||||
slider.selectAll('.extent,.resize').remove();
|
|
||||||
}
|
|
||||||
|
|
||||||
function brushed() {
|
|
||||||
val = x.invert(d3.mouse(this)[0]);
|
|
||||||
brush.extent([val, val]);
|
|
||||||
scope.change({ val: val });
|
|
||||||
drawBrush();
|
|
||||||
}
|
|
||||||
|
|
||||||
function drawBrush() {
|
|
||||||
if (unit) {
|
|
||||||
lbl.text(fmt(val) + ' ' + unit + ' ' + pct(val / scope.max));
|
|
||||||
}
|
|
||||||
handle.attr('cx', x(val));
|
|
||||||
filled.attr('d', 'M0,0V0H' + x(val) + 'V0');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Watch for changes in the max, window size
|
|
||||||
*/
|
|
||||||
scope.$watch('max', function(newMax, oldMax) {
|
|
||||||
val = newMax * (val / oldMax); // Retain percentage filled
|
|
||||||
scope.change({ val: val });
|
|
||||||
render();
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!scope.ignoreResize) {
|
|
||||||
angular.element($window).bind('orientationchange resize', render);
|
|
||||||
}
|
|
||||||
|
|
||||||
scope.$on('reset', function(e, resetVal) {
|
|
||||||
val = resetVal;
|
|
||||||
drawBrush();
|
|
||||||
});
|
|
||||||
|
|
||||||
scope.$on('$destroy', function() {
|
|
||||||
angular.element($window).unbind('orientationchange resize render', render);
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}]);
|
|
||||||
@@ -9,6 +9,11 @@ import d3 from 'd3';
|
|||||||
|
|
||||||
let fallbackTerms = EN.terms;
|
let fallbackTerms = EN.terms;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the units, translation and format functions for the specified language
|
||||||
|
* @param {string} langCode ISO Language code
|
||||||
|
* @return {object} Language units, translation and format functions
|
||||||
|
*/
|
||||||
export function getLanguage(langCode) {
|
export function getLanguage(langCode) {
|
||||||
let lang, translate;
|
let lang, translate;
|
||||||
|
|
||||||
@@ -53,15 +58,20 @@ export function getLanguage(langCode) {
|
|||||||
Ls: <u>{' ' + translate('Ls')}</u>, // Light Seconds
|
Ls: <u>{' ' + translate('Ls')}</u>, // Light Seconds
|
||||||
LY: <u>{' ' + translate('LY')}</u>, // Light Years
|
LY: <u>{' ' + translate('LY')}</u>, // Light Years
|
||||||
MJ: <u>{' ' + translate('MJ')}</u>, // Mega Joules
|
MJ: <u>{' ' + translate('MJ')}</u>, // Mega Joules
|
||||||
ms: <u>{' ' + translate('m/s')}</u>, // Meters per second
|
'm/s': <u>{' ' + translate('m/s')}</u>, // Meters per second
|
||||||
MW: <u>{' ' + translate('MW')}</u>, // Mega Watts (same as Mega Joules per second)
|
MW: <u>{' ' + translate('MW')}</u>, // Mega Watts (same as Mega Joules per second)
|
||||||
ps: <u>{translate('/s')}</u>, // per second
|
ps: <u>{translate('/s')}</u>, // per second
|
||||||
|
pm: <u>{translate('/min')}</u>, // per minute
|
||||||
T: <u>{' ' + translate('T')}</u>, // Metric Tons
|
T: <u>{' ' + translate('T')}</u>, // Metric Tons
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The list of available languages
|
||||||
|
* @type {Object}
|
||||||
|
*/
|
||||||
export const Languages = {
|
export const Languages = {
|
||||||
en: 'English',
|
en: 'English',
|
||||||
de: 'Deutsch',
|
de: 'Deutsch',
|
||||||
|
|||||||
@@ -1,215 +1,344 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import Page from './Page';
|
import Page from './Page';
|
||||||
import Ships from '../shipyard/Ships';
|
import Router from '../Router';
|
||||||
import cn from 'classnames';
|
import cn from 'classnames';
|
||||||
|
import { Ships } from 'coriolis-data';
|
||||||
import Ship from '../shipyard/Ship';
|
import Ship from '../shipyard/Ship';
|
||||||
import * as ModuleUtils from '../shipyard/ModuleUtils';
|
import Serializer from '../shipyard/Serializer';
|
||||||
import { SizeMap } from '../shipyard/Constants';
|
import InterfaceEvents from '../utils/InterfaceEvents';
|
||||||
import Link from '../components/Link';
|
import Persist from '../stores/Persist';
|
||||||
|
import { SizeMap, ShipFacets } from '../shipyard/Constants';
|
||||||
|
import ComparisonTable from '../components/ComparisonTable';
|
||||||
|
import ModalCompare from '../components/ModalCompare';
|
||||||
|
import { FloppyDisk, Bin, Download, Embed, Rocket, LinkIcon } from '../components/SvgIcons';
|
||||||
|
|
||||||
|
|
||||||
|
function sortBy(predicate) {
|
||||||
|
return (a, b) => {
|
||||||
|
if (a[predicate] === b[predicate]) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (typeof a[predicate] == 'string') {
|
||||||
|
return a[predicate].toLowerCase() > b[predicate].toLowerCase() ? 1 : -1;
|
||||||
|
}
|
||||||
|
return a[predicate] > b[predicate] ? 1 : -1;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export default class ComparisonPage extends Page {
|
export default class ComparisonPage extends Page {
|
||||||
|
|
||||||
constructor(props, context) {
|
constructor(props, context) {
|
||||||
super(props, context);
|
super(props, context);
|
||||||
this.state = {
|
this._sortShips = this._sortShips.bind(this);
|
||||||
title: 'Coriolis - Shipyard',
|
this._buildsSelected = this._buildsSelected.bind(this);
|
||||||
shipPredicate: 'name',
|
this.state = this._initState(props, context);
|
||||||
shipDesc: false
|
|
||||||
};
|
|
||||||
this.context = context;
|
|
||||||
this.shipSummaries = [];
|
|
||||||
|
|
||||||
for (let s in Ships) {
|
|
||||||
this.shipSummaries.push(this._shipSummary(s, Ships[s]));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_initState(props, context) {
|
||||||
|
let defaultFacets = [9, 6, 4, 1, 3, 2]; // Reverse order of Armour, Shields, Speed, Jump Range, Cargo Capacity, Cost
|
||||||
|
let params = context.route.params;
|
||||||
|
let code = params.code;
|
||||||
|
let name = params.name ? decodeURIComponent(params.name) : null;
|
||||||
|
let newName = '';
|
||||||
|
let compareMode = !code;
|
||||||
|
let facets = [];
|
||||||
|
let builds = [];
|
||||||
|
let saved = false;
|
||||||
|
let predicate = 'name';
|
||||||
|
let desc = false;
|
||||||
|
let importObj = {};
|
||||||
|
|
||||||
|
if (compareMode) {
|
||||||
|
if (name == 'all') {
|
||||||
|
let allBuilds = Persist.getBuilds();
|
||||||
|
newName = name;
|
||||||
|
for (let shipId in allBuilds) {
|
||||||
|
for (let buildName in allBuilds[shipId]) {
|
||||||
|
builds.push(this._createBuild(shipId, buildName, allBuilds[shipId][buildName]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
|
||||||
|
let comparisonData = Persist.getComparison(name);
|
||||||
|
if (comparisonData) {
|
||||||
|
defaultFacets = comparisonData.facets;
|
||||||
|
comparisonData.builds.forEach((b) => builds.push(this._createBuild(b.shipId, b.buildName)));
|
||||||
|
saved = true;
|
||||||
|
newName = name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
let comparisonData = Serializer.toComparison(code);
|
||||||
|
defaultFacets = comparisonData.f;
|
||||||
|
newName = name = comparisonData.n;
|
||||||
|
predicate = comparisonData.p;
|
||||||
|
desc = comparisonData.d;
|
||||||
|
comparisonData.b.forEach((build) => {
|
||||||
|
builds.push(this._createBuild(build.s, build.n, build.c));
|
||||||
|
if (!importObj[build.s]) {
|
||||||
|
importObj[build.s] = {};
|
||||||
|
}
|
||||||
|
importObj[build.s][build.n] = build.c;
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
throw { type: 'bad-comparison', message: e.message, details: e };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 0; i < ShipFacets.length; i++) {
|
||||||
|
facets.push(Object.assign({ index: i }, ShipFacets[i]));
|
||||||
|
}
|
||||||
|
|
||||||
|
let selectedFacets = [];
|
||||||
|
|
||||||
|
for (let fi of defaultFacets) {
|
||||||
|
let facet = facets.splice(fi, 1)[0];
|
||||||
|
facet.active = true;
|
||||||
|
selectedFacets.unshift(facet);
|
||||||
|
}
|
||||||
|
|
||||||
|
facets = selectedFacets.concat(facets);
|
||||||
|
console.log(selectedFacets);
|
||||||
|
builds.sort(sortBy(predicate));
|
||||||
|
|
||||||
|
return {
|
||||||
|
title: 'Coriolis - Compare',
|
||||||
|
predicate,
|
||||||
|
desc,
|
||||||
|
facets,
|
||||||
|
builds,
|
||||||
|
compareMode,
|
||||||
|
code,
|
||||||
|
name,
|
||||||
|
newName,
|
||||||
|
saved,
|
||||||
|
importObj
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
_createBuild(id, name, code) {
|
||||||
|
code = code ? code : Persist.getBuild(id, name); // Retrieve build code if not passed
|
||||||
|
|
||||||
|
if (!code) { // No build found
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let data = Ships[id]; // Get ship properties
|
||||||
|
let b = new Ship(id, data.properties, data.slots); // Create a new Ship instance
|
||||||
|
b.buildFrom(code); // Populate components from code
|
||||||
|
b.buildName = name;
|
||||||
|
return b;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sort ships
|
* Sort ships
|
||||||
* @param {object} key Sort predicate
|
* @param {object} key Sort predicate
|
||||||
*/
|
*/
|
||||||
_sortShips(shipPredicate, shipPredicateIndex) {
|
_sortShips(predicate) {
|
||||||
let shipDesc = this.state.shipDesc;
|
let { builds, desc } = this.state;
|
||||||
|
if (this.state.predicate == predicate) {
|
||||||
if (typeof shipPredicateIndex == 'object') {
|
desc = !desc;
|
||||||
shipPredicateIndex = undefined;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.state.shipPredicate == shipPredicate && this.state.shipPredicateIndex == shipPredicateIndex) {
|
builds.sort(sortBy(predicate));
|
||||||
shipDesc = !shipDesc;
|
|
||||||
|
if (desc) {
|
||||||
|
builds.reverse();
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setState({ shipPredicate, shipDesc, shipPredicateIndex });
|
this.setState({ predicate, desc });
|
||||||
};
|
};
|
||||||
|
|
||||||
_shipRowElement(s, translate, u, fInt, fRound) {
|
_selectBuilds() {
|
||||||
return <tr key={s.id} className={'highlight'}>
|
InterfaceEvents.showModal(React.cloneElement(
|
||||||
<td className={'le'}><Link href={'/outfitting/' + s.id}>{s.name}</Link></td>
|
<ModalCompare onSelect={this._buildsSelected}/>,
|
||||||
<td className={'le'}>{s.manufacturer}</td>
|
{ builds: this.state.builds }
|
||||||
<td className={'cap'}>{translate(SizeMap[s.class])}</td>
|
));
|
||||||
<td className={'ri'}>{fInt(s.speed)}{u.ms}</td>
|
|
||||||
<td className={'ri'}>{fInt(s.boost)}{u.ms}</td>
|
|
||||||
<td className={'ri'}>{s.baseArmour}</td>
|
|
||||||
<td className={'ri'}>{fInt(s.baseShieldStrength)}{u.MJ}</td>
|
|
||||||
<td className={'ri'}>{fInt(s.topSpeed)}{u.ms}</td>
|
|
||||||
<td className={'ri'}>{fInt(s.topBoost)}{u.ms}</td>
|
|
||||||
<td className={'ri'}>{fRound(s.maxJumpRange)}{u.LY}</td>
|
|
||||||
<td className={'ri'}>{fInt(s.maxCargo)}{u.T}</td>
|
|
||||||
<td className={cn({ disabled: !s.hp[1] })}>{s.hp[1]}</td>
|
|
||||||
<td className={cn({ disabled: !s.hp[2] })}>{s.hp[2]}</td>
|
|
||||||
<td className={cn({ disabled: !s.hp[3] })}>{s.hp[3]}</td>
|
|
||||||
<td className={cn({ disabled: !s.hp[4] })}>{s.hp[4]}</td>
|
|
||||||
<td className={cn({ disabled: !s.hp[0] })}>{s.hp[0]}</td>
|
|
||||||
<td className={cn({ disabled: !s.int[0] })}>{s.int[0]}</td>
|
|
||||||
<td className={cn({ disabled: !s.int[1] })}>{s.int[1]}</td>
|
|
||||||
<td className={cn({ disabled: !s.int[2] })}>{s.int[2]}</td>
|
|
||||||
<td className={cn({ disabled: !s.int[3] })}>{s.int[3]}</td>
|
|
||||||
<td className={cn({ disabled: !s.int[4] })}>{s.int[4]}</td>
|
|
||||||
<td className={cn({ disabled: !s.int[5] })}>{s.int[5]}</td>
|
|
||||||
<td className={cn({ disabled: !s.int[6] })}>{s.int[6]}</td>
|
|
||||||
<td className={cn({ disabled: !s.int[7] })}>{s.int[7]}</td>
|
|
||||||
<td className={'ri'}>{fInt(s.hullMass)}{u.T}</td>
|
|
||||||
<td className={'ri'}>{s.masslock}</td>
|
|
||||||
<td className={'ri'}>{fInt(s.retailCost)}{u.CR}</td>
|
|
||||||
</tr>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_renderSummaries(language) {
|
_buildsSelected(newBuilds) {
|
||||||
let fInt = language.formats.int;
|
InterfaceEvents.hideModal();
|
||||||
let fRound = language.formats.round;
|
let builds = [];
|
||||||
let translate = language.translate;
|
|
||||||
let u = language.units;
|
for (let b of newBuilds) {
|
||||||
// Regenerate ship rows on prop change
|
builds.push(this._createBuild(b.id, b.buildName));
|
||||||
for (let s of this.shipSummaries) {
|
}
|
||||||
s.rowElement = this._shipRowElement(s, translate, u, fInt, fRound);
|
|
||||||
|
this.setState({ builds });
|
||||||
|
}
|
||||||
|
|
||||||
|
_toggleFacet(facet) {
|
||||||
|
facet.active = !facet.active;
|
||||||
|
this.setState({ facets: [].concat(this.state.facets), saved: false });
|
||||||
|
}
|
||||||
|
|
||||||
|
_facetDrag(e) {
|
||||||
|
this.dragged = e.currentTarget;
|
||||||
|
let placeholder = this.placeholder = document.createElement("li");
|
||||||
|
placeholder.style.width = this.dragged.offsetWidth + 'px';
|
||||||
|
placeholder.className = "facet-placeholder";
|
||||||
|
e.dataTransfer.effectAllowed = 'move';
|
||||||
|
e.dataTransfer.setData("text/html", e.currentTarget);
|
||||||
|
}
|
||||||
|
|
||||||
|
_facetDrop(e) {
|
||||||
|
this.dragged.parentNode.removeChild(this.placeholder);
|
||||||
|
let facets = this.state.facets;
|
||||||
|
let frm = Number(this.dragged.dataset.i);
|
||||||
|
let to = Number(this.over.dataset.i);
|
||||||
|
|
||||||
|
if (frm < to) {
|
||||||
|
to--;
|
||||||
|
}
|
||||||
|
if (this.nodeAfter) {
|
||||||
|
to++;
|
||||||
|
}
|
||||||
|
|
||||||
|
facets.splice(to, 0, facets.splice(frm, 1)[0]);
|
||||||
|
this.dragged.style.display = null;
|
||||||
|
this.setState({ facets: [].concat(facets) });
|
||||||
|
}
|
||||||
|
|
||||||
|
_facetDragOver(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
if(e.target.className == "facet-placeholder") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.over = e.target;
|
||||||
|
this.dragged.style.display = "none";
|
||||||
|
let relX = e.clientX - this.over.getBoundingClientRect().left;
|
||||||
|
let width = this.over.offsetWidth / 2;
|
||||||
|
let parent = e.target.parentNode;
|
||||||
|
|
||||||
|
if (parent == e.currentTarget) {
|
||||||
|
if(relX > width) {
|
||||||
|
this.nodeAfter = true;
|
||||||
|
parent.insertBefore(this.placeholder, e.target.nextElementSibling);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.nodeAfter = false;
|
||||||
|
parent.insertBefore(this.placeholder, e.target);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUpdate(nextProps, nextState, nextContext) {
|
_onNameChange(e) {
|
||||||
if (this.context.language !== nextContext.language) {
|
this.setState({ newName: e.target.value, saved: false });
|
||||||
this._renderSummaries(language);
|
}
|
||||||
|
|
||||||
|
_delete() {
|
||||||
|
Persist.deleteComparison(this.state.name);
|
||||||
|
Router.go('/compare');
|
||||||
|
}
|
||||||
|
|
||||||
|
_import() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
_save() {
|
||||||
|
let { newName, builds, facets } = this.state;
|
||||||
|
|
||||||
|
let selectedFacets = [];
|
||||||
|
facets.forEach((f) => {
|
||||||
|
if (f.active) {
|
||||||
|
selectedFacets.unshift(f.index);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
console.log(selectedFacets);
|
||||||
|
//Persist.saveComparison(newName, builds, selectedFacets);
|
||||||
|
Router.replace(`/compare/${encodeURIComponent(this.state.newName)}`)
|
||||||
|
this.setState ({ name: newName, saved: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates the long permalink URL
|
||||||
|
* @return {string} The long permalink URL
|
||||||
|
*/
|
||||||
|
_genPermalink() {
|
||||||
|
let { facets, builds, name, predicate, desc } = this.state;
|
||||||
|
let selectedFacets = [];
|
||||||
|
|
||||||
|
for (let f of facets){
|
||||||
|
if (f.active) {
|
||||||
|
selectedFacets.unshift(f.index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let code = Serializer.fromComparison(name, builds, selectedFacets, predicate, desc);
|
||||||
|
// send code to permalink modal
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
componentWillReceiveProps(nextProps, nextContext) {
|
||||||
|
if (this.context.route !== nextContext.route) { // Only reinit state if the route has changed
|
||||||
|
this.setState(this._initState(nextProps, nextContext));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
let shipSummaries = this.shipSummaries;
|
|
||||||
let shipPredicate = this.state.shipPredicate;
|
|
||||||
let shipPredicateIndex = this.state.shipPredicateIndex;
|
|
||||||
let shipRows = [];
|
|
||||||
let sortShips = (predicate, index) => this._sortShips.bind(this, predicate, index);
|
|
||||||
|
|
||||||
// Sort shipsOverview
|
|
||||||
shipSummaries.sort((a, b) => {
|
|
||||||
let valA = a[shipPredicate], valB = b[shipPredicate];
|
|
||||||
|
|
||||||
if (shipPredicateIndex != undefined) {
|
|
||||||
valA = valA[shipPredicateIndex];
|
|
||||||
valB = valB[shipPredicateIndex];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this.state.shipDesc) {
|
|
||||||
let val = valA;
|
|
||||||
valA = valB;
|
|
||||||
valB = val;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(valA == valB) {
|
|
||||||
if (a.name > b.name) {
|
|
||||||
return 1;
|
|
||||||
} else {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
} else if (valA > valB) {
|
|
||||||
return 1;
|
|
||||||
} else {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
let formats = this.context.language.formats;
|
|
||||||
let fInt = formats.int;
|
|
||||||
let fRound = formats.round;
|
|
||||||
let translate = this.context.language.translate;
|
let translate = this.context.language.translate;
|
||||||
|
let compareHeader;
|
||||||
|
let {newName, name, saved, builds, facets, predicate, desc } = this.state;
|
||||||
|
|
||||||
for (let s of shipSummaries) {
|
if (this.state.compareMode) {
|
||||||
shipRows.push(s.rowElement);
|
compareHeader = <tr>
|
||||||
|
<td className='head'>{translate('comparison')}</td>
|
||||||
|
<td>
|
||||||
|
<input value={newName} onChange={this._onNameChange} placeholder={translate('Enter Name')} maxLength='50' />
|
||||||
|
<button onClick={this._save} disabled={!newName || newName == 'all' || saved}>
|
||||||
|
<FloppyDisk className='lg'/><span className='button-lbl'>{translate('save')}</span>
|
||||||
|
</button>
|
||||||
|
<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' ng-click='permalink($event)' ng-disabled='builds.length == 0'>
|
||||||
|
<LinkIcon className='lg'/><span className='button-lbl'>{translate('permalink')}</span>
|
||||||
|
</button>
|
||||||
|
<button className='r' ng-click='embed($event)' ng-disabled='builds.length == 0'>
|
||||||
|
<Embed className='lg'/><span className='button-lbl'>{translate('forum')}</span>
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
</tr>;
|
||||||
|
} else {
|
||||||
|
compareHeader = <tr>
|
||||||
|
<td className='head'>{translate('comparison')}</td>
|
||||||
|
<td>
|
||||||
|
<h3>{name}</h3>
|
||||||
|
<button className='r' onClick={this._import}><Download className='lg'/>{translate('import')}</button>
|
||||||
|
</td>
|
||||||
|
</tr>;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={'page'}>
|
<div className={'page'} style={{ fontSize: this.context.sizeRatio + 'em'}}>
|
||||||
{/*<table id='comparison'>
|
<table id='comparison'>
|
||||||
<tr ng-show='compareMode'>
|
<tbody>
|
||||||
<td class='head' translate='comparison'></td>
|
{compareHeader}
|
||||||
<td>
|
<tr key='facets'>
|
||||||
<input ng-model='name' ng-change='nameChange()' placeholder={translate('enter name')} maxlength={50} />
|
<td className='head'>{translate('compare')}</td>
|
||||||
<button ng-click='save()'disabled{!name || name == 'all' || saved}>
|
<td>
|
||||||
<svg class='icon lg '><use xlink:href='#floppy-disk'></use></svg><span class='button-lbl'> {{'save' | translate}}</span>
|
<ul id='facet-container' onDragOver={this._facetDragOver}>
|
||||||
</button>
|
{facets.map((f, i) =>
|
||||||
<button ng-click='delete()' ng-disabled='name == 'all' || !saved'><svg class='icon lg warning '><use xlink:href='#bin'></use></svg></button>
|
<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)}>
|
||||||
<button ng-click='selectBuilds(true, $event)'>
|
{'↔ ' + translate(f.title)}
|
||||||
<svg class='icon lg '><use xlink:href='#rocket'></use></svg><span class='button-lbl'> {{'builds' | translate}}</span>
|
</li>
|
||||||
</button>
|
)}
|
||||||
<button class='r' ng-click='permalink($event)' ng-disabled='builds.length == 0'>
|
</ul>
|
||||||
<svg class='icon lg '><use xlink:href='#link'></use></svg><span class='button-lbl'> {{'permalink' | translate}}</span>
|
</td>
|
||||||
</button>
|
|
||||||
<button class='r' ng-click='embed($event)' ng-disabled='builds.length == 0'>
|
|
||||||
<svg class='icon lg '><use xlink:href='#embed'></use></svg><span class='button-lbl'> {{'forum' | translate}}</span>
|
|
||||||
</button>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr ng-show='!compareMode'>
|
|
||||||
<td class='head' translate='comparison'></td>
|
|
||||||
<td>
|
|
||||||
<h3 ng-bind='name'></h3>
|
|
||||||
<button class='r' ui-sref='modal.import({obj:importObj})'><svg class='icon lg '><use xlink:href='#download'></use></svg> {{'import' | translate}}</button>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td class='head' translate='compare'></td>
|
|
||||||
<td>
|
|
||||||
<ul id='facet-container' as-sortable='facetSortOpts' ng-model='facets' class='sortable' update='tblUpdate'>
|
|
||||||
<li ng-repeat='(i,f) in facets' as-sortable-item class='facet' ng-class='{active: f.active}' ng-click='toggleFacet(i)'>
|
|
||||||
<div as-sortable-item-handle>↔ <span ng-bind='f.title | translate'></span></div>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</td>
|
|
||||||
</tr>
|
</tr>
|
||||||
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
<div class='scroll-x'>
|
<ComparisonTable builds={builds} facets={facets} onSort={this._sortShips} predicate={predicate} desc={desc} />
|
||||||
<table id='comp-tbl' comparison-table ng-click='handleClick($event)'></table>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div ng-repeat='f in facets | filter:{active:true}' ng-if='builds.length > 0' class='chart' bar-chart facet='f' data='builds'>
|
{/*<div ng-repeat='f in facets | filter:{active:true}' ng-if='builds.length > 0' className='chart' bar-chart facet='f' data='builds'>
|
||||||
<h3 ng-click='sort(f.props[0])' >{{f.title | translate}}</h3>
|
<h3 ng-click='sort(f.props[0])' >{{f.title | translate}}</h3>
|
||||||
</div>
|
</div>*/}
|
||||||
|
|
||||||
<div class='modal-bg' ng-show='showBuilds' ng-click='selectBuilds(false, $event)'>
|
|
||||||
<div class='modal' ui-view='modal-content' ng-click='$event.stopPropagation()'>
|
|
||||||
<h3 translate='PHRASE_SELECT_BUILDS'></h3>
|
|
||||||
<div id='build-select'>
|
|
||||||
<table>
|
|
||||||
<thead><tr><th colspan='2' translate='available'></th></tr></thead>
|
|
||||||
<tbody>
|
|
||||||
<tr ng-repeat='b in unusedBuilds | orderBy:'name'' ng-click='addBuild(b.id, b.buildName)'>
|
|
||||||
<td class='tl' ng-bind='b.name'></td><td class='tl' ng-bind='b.buildName'></td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
<h1>⇆</h1>
|
|
||||||
<table>
|
|
||||||
<thead><tr><th colspan='2' translate='added'></th></tr></thead>
|
|
||||||
<tbody>
|
|
||||||
<tr ng-repeat='b in builds | orderBy:'name'' ng-click='removeBuild(b.id, b.buildName)'>
|
|
||||||
<td class='tl' ng-bind='b.name'></td><td class='tl' ng-bind='b.buildName'></td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
<br/>
|
|
||||||
<button class='r dismiss cap' ng-click='selectBuilds(false, $event)' translate='done'></button>
|
|
||||||
</div>
|
|
||||||
</div> */}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { findDOMNode } from 'react-dom';
|
||||||
import Page from './Page';
|
import Page from './Page';
|
||||||
import cn from 'classnames';
|
import cn from 'classnames';
|
||||||
import Router from '../Router';
|
import Router from '../Router';
|
||||||
@@ -7,7 +8,7 @@ import InterfaceEvents from '../utils/InterfaceEvents';
|
|||||||
import { Ships } from 'coriolis-data';
|
import { Ships } from 'coriolis-data';
|
||||||
import Ship from '../shipyard/Ship';
|
import Ship from '../shipyard/Ship';
|
||||||
import { toDetailedBuild } from '../shipyard/Serializer';
|
import { toDetailedBuild } from '../shipyard/Serializer';
|
||||||
import { FloppyDisk, Bin, Switch, Download, Reload } from '../components/SvgIcons';
|
import { FloppyDisk, Bin, Switch, Download, Reload, Fuel } from '../components/SvgIcons';
|
||||||
import ShipSummaryTable from '../components/ShipSummaryTable';
|
import ShipSummaryTable from '../components/ShipSummaryTable';
|
||||||
import StandardSlotSection from '../components/StandardSlotSection';
|
import StandardSlotSection from '../components/StandardSlotSection';
|
||||||
import HardpointsSlotSection from '../components/HardpointsSlotSection';
|
import HardpointsSlotSection from '../components/HardpointsSlotSection';
|
||||||
@@ -17,6 +18,7 @@ import LineChart from '../components/LineChart';
|
|||||||
import PowerManagement from '../components/PowerManagement';
|
import PowerManagement from '../components/PowerManagement';
|
||||||
import CostSection from '../components/CostSection';
|
import CostSection from '../components/CostSection';
|
||||||
import ModalExport from '../components/ModalExport';
|
import ModalExport from '../components/ModalExport';
|
||||||
|
import Slider from '../components/Slider';
|
||||||
|
|
||||||
const SPEED_SERIES = ['boost', '4 Pips', '2 Pips', '0 Pips'];
|
const SPEED_SERIES = ['boost', '4 Pips', '2 Pips', '0 Pips'];
|
||||||
const SPEED_COLORS = ['#0088d2', '#ff8c0d', '#D26D00', '#c06400'];
|
const SPEED_COLORS = ['#0088d2', '#ff8c0d', '#D26D00', '#c06400'];
|
||||||
@@ -50,6 +52,8 @@ export default class OutfittingPage extends Page {
|
|||||||
ship.buildWith(data.defaults); // Populate with default components
|
ship.buildWith(data.defaults); // Populate with default components
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let fuelCapacity = ship.fuelCapacity;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
title: 'Outfitting - ' + data.properties.name,
|
title: 'Outfitting - ' + data.properties.name,
|
||||||
costTab: Persist.getCostTab() || 'costs',
|
costTab: Persist.getCostTab() || 'costs',
|
||||||
@@ -57,7 +61,12 @@ export default class OutfittingPage extends Page {
|
|||||||
shipId,
|
shipId,
|
||||||
ship,
|
ship,
|
||||||
code,
|
code,
|
||||||
savedCode
|
savedCode,
|
||||||
|
fuelCapacity,
|
||||||
|
fuelLevel: 1,
|
||||||
|
jumpRangeChartFunc: ship.getJumpRangeWith.bind(ship, fuelCapacity),
|
||||||
|
totalRangeChartFunc: ship.getFastestRangeWith.bind(ship, fuelCapacity),
|
||||||
|
speedChartFunc: ship.getSpeedsWith.bind(ship, fuelCapacity)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -107,10 +116,15 @@ export default class OutfittingPage extends Page {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_shipUpdated() {
|
_shipUpdated() {
|
||||||
let { shipId, buildName } = this.state;
|
let { shipId, buildName, ship, fuelCapacity } = this.state;
|
||||||
let newCode = this.state.ship.toString();
|
let code = ship.toString();
|
||||||
this._updateRoute(shipId, newCode, buildName);
|
|
||||||
this.setState({ code: newCode });
|
if (fuelCapacity != ship.fuelCapacity) {
|
||||||
|
this._fuelChange(this.state.fuelLevel);
|
||||||
|
}
|
||||||
|
|
||||||
|
this._updateRoute(shipId, code, buildName);
|
||||||
|
this.setState({ code });
|
||||||
}
|
}
|
||||||
|
|
||||||
_updateRoute(shipId, code, buildName) {
|
_updateRoute(shipId, code, buildName) {
|
||||||
@@ -123,16 +137,47 @@ export default class OutfittingPage extends Page {
|
|||||||
Router.replace(`/outfit/${shipId}/${code}${qStr}`);
|
Router.replace(`/outfit/${shipId}/${code}${qStr}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_fuelChange(fuelLevel) {
|
||||||
|
let ship = this.state.ship;
|
||||||
|
let fuelCapacity = ship.fuelCapacity;
|
||||||
|
let fuel = fuelCapacity * fuelLevel;
|
||||||
|
this.setState({
|
||||||
|
fuelLevel,
|
||||||
|
fuelCapacity,
|
||||||
|
jumpRangeChartFunc: ship.getJumpRangeWith.bind(ship, fuel),
|
||||||
|
totalRangeChartFunc: ship.getFastestRangeWith.bind(ship, fuel),
|
||||||
|
speedChartFunc: ship.getSpeedsWith.bind(ship, fuel)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
_updateDimensions() {
|
||||||
|
this.setState({
|
||||||
|
chartWidth: findDOMNode(this.refs.chartThird).offsetWidth
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
componentWillReceiveProps(nextProps, nextContext) {
|
componentWillReceiveProps(nextProps, nextContext) {
|
||||||
if (this.context.route !== nextContext.route) { // Only reinit state if the route has changed
|
if (this.context.route !== nextContext.route) { // Only reinit state if the route has changed
|
||||||
this.setState(this._initState(nextContext));
|
this.setState(this._initState(nextContext));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
componentWillMount(){
|
||||||
|
this.resizeListener = InterfaceEvents.addListener('windowResized', this._updateDimensions);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount(){
|
||||||
|
this._updateDimensions();
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount(){
|
||||||
|
this.resizeListener.remove();
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
let { translate, units } = this.context.language;
|
let { translate, units, formats } = this.context.language;
|
||||||
let state = this.state;
|
let state = this.state;
|
||||||
let { ship, code, savedCode, buildName } = state;
|
let { ship, code, savedCode, buildName, chartWidth } = state;
|
||||||
let menu = this.props.currentMenu;
|
let menu = this.props.currentMenu;
|
||||||
let shipUpdated = this._shipUpdated;
|
let shipUpdated = this._shipUpdated;
|
||||||
let hStr = ship.getHardpointsString();
|
let hStr = ship.getHardpointsString();
|
||||||
@@ -140,7 +185,7 @@ export default class OutfittingPage extends Page {
|
|||||||
let iStr = ship.getInternalString();
|
let iStr = ship.getInternalString();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div id='outfit' className={'page'}>
|
<div id='outfit' className={'page'} style={{ fontSize: (this.context.sizeRatio * 0.9) + 'em'}}>
|
||||||
<div id='overview'>
|
<div id='overview'>
|
||||||
<h1>{ship.name}</h1>
|
<h1>{ship.name}</h1>
|
||||||
<div id='build'>
|
<div id='build'>
|
||||||
@@ -164,22 +209,21 @@ export default class OutfittingPage extends Page {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ShipSummaryTable ship={ship} code={code} />
|
<ShipSummaryTable ship={ship} code={code} />
|
||||||
|
|
||||||
<StandardSlotSection ship={ship} code={sStr} onChange={shipUpdated} currentMenu={menu} />
|
<StandardSlotSection ship={ship} code={sStr} onChange={shipUpdated} currentMenu={menu} />
|
||||||
<InternalSlotSection ship={ship} code={iStr} onChange={shipUpdated} currentMenu={menu} />
|
<InternalSlotSection ship={ship} code={iStr} onChange={shipUpdated} currentMenu={menu} />
|
||||||
<HardpointsSlotSection ship={ship} code={hStr} onChange={shipUpdated} currentMenu={menu} />
|
<HardpointsSlotSection ship={ship} code={hStr} onChange={shipUpdated} currentMenu={menu} />
|
||||||
<UtilitySlotSection ship={ship} code={hStr} onChange={shipUpdated} currentMenu={menu} />
|
<UtilitySlotSection ship={ship} code={hStr} onChange={shipUpdated} currentMenu={menu} />
|
||||||
|
|
||||||
<PowerManagement ship={ship} code={code} onChange={shipUpdated} />
|
<PowerManagement ship={ship} code={code} onChange={shipUpdated} />
|
||||||
<CostSection ship={ship} shipId={state.shipId} buildName={buildName} code={sStr + hStr + iStr} />
|
<CostSection ship={ship} buildName={buildName} code={sStr + hStr + iStr} />
|
||||||
|
|
||||||
<div className='group third'>
|
<div ref='chartThird' className='group third'>
|
||||||
<h1>{translate('jump range')}</h1>
|
<h1>{translate('jump range')}</h1>
|
||||||
<LineChart
|
<LineChart
|
||||||
|
width={chartWidth}
|
||||||
xMax={ship.cargoCapacity}
|
xMax={ship.cargoCapacity}
|
||||||
yMax={ship.unladenRange}
|
yMax={ship.unladenRange}
|
||||||
xUnit={units.T}
|
xUnit={translate('T')}
|
||||||
yUnit={units.LY}
|
yUnit={translate('LY')}
|
||||||
yLabel={translate('jump range')}
|
yLabel={translate('jump range')}
|
||||||
xLabel={translate('cargo')}
|
xLabel={translate('cargo')}
|
||||||
func={state.jumpRangeChartFunc}
|
func={state.jumpRangeChartFunc}
|
||||||
@@ -189,10 +233,11 @@ export default class OutfittingPage extends Page {
|
|||||||
<div className='group third'>
|
<div className='group third'>
|
||||||
<h1>{translate('total range')}</h1>
|
<h1>{translate('total range')}</h1>
|
||||||
<LineChart
|
<LineChart
|
||||||
|
width={chartWidth}
|
||||||
xMax={ship.cargoCapacity}
|
xMax={ship.cargoCapacity}
|
||||||
yMax={ship.boostSpeed}
|
yMax={ship.unladenTotalRange}
|
||||||
xUnit={units.T}
|
xUnit={translate('T')}
|
||||||
yUnit={units.ms}
|
yUnit={translate('LY')}
|
||||||
yLabel={translate('total range')}
|
yLabel={translate('total range')}
|
||||||
xLabel={translate('cargo')}
|
xLabel={translate('cargo')}
|
||||||
func={state.totalRangeChartFunc}
|
func={state.totalRangeChartFunc}
|
||||||
@@ -202,25 +247,30 @@ export default class OutfittingPage extends Page {
|
|||||||
<div className='group third'>
|
<div className='group third'>
|
||||||
<h1>{translate('speed')}</h1>
|
<h1>{translate('speed')}</h1>
|
||||||
<LineChart
|
<LineChart
|
||||||
|
width={chartWidth}
|
||||||
xMax={ship.cargoCapacity}
|
xMax={ship.cargoCapacity}
|
||||||
yMax={ship.boostSpeed}
|
yMax={ship.topBoost + 10}
|
||||||
xUnit={units.T}
|
xUnit={translate('T')}
|
||||||
yUnit={units.ms}
|
yUnit={translate('m/s')}
|
||||||
yLabel={translate('speed')}
|
yLabel={translate('speed')}
|
||||||
series={SPEED_SERIES}
|
series={SPEED_SERIES}
|
||||||
color={SPEED_COLORS}
|
colors={SPEED_COLORS}
|
||||||
xLabel={translate('cargo')}
|
xLabel={translate('cargo')}
|
||||||
func={state.speedChartFunc}
|
func={state.speedChartFunc}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/*
|
<div className='group half'>
|
||||||
<div class='group half'>
|
<table style={{ width: '100%', lineHeight: '1em'}}>
|
||||||
<div slider max='ship.fuelCapacity' unit=''T'' on-change='::fuelChange(val)' style='position:relative; margin: 0 auto;'>
|
<tbody >
|
||||||
<svg class='icon xl primary-disabled' style='position:absolute;height: 100%;'><use xlink:href='#fuel'></use></svg>
|
<tr>
|
||||||
</div>
|
<td style={{ verticalAlign: 'top', padding:0 }}><Fuel className='xl primary-disabled' /></td>
|
||||||
|
<td><Slider axis={true} onChange={this._fuelChange} axisUnit={translate('T')} percent={state.fuelLevel} max={state.fuelCapacity} /></td>
|
||||||
|
<td className='primary' style={{ width: '10em', verticalAlign: 'top', fontSize: '0.9em' }}>{formats.f2(state.fuelLevel * ship.fuelCapacity)}{units.T} {formats.pct1(state.fuelLevel)}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
</div>
|
</div>
|
||||||
*/}
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,17 +1,25 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import shallowEqual from '../utils/shallowEqual';
|
import shallowEqual from '../utils/shallowEqual';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @class Abstract Page
|
||||||
|
*/
|
||||||
export default class Page extends React.Component {
|
export default class Page extends React.Component {
|
||||||
|
|
||||||
static contextTypes = {
|
static contextTypes = {
|
||||||
route: React.PropTypes.object.isRequired,
|
route: React.PropTypes.object.isRequired,
|
||||||
language: React.PropTypes.object.isRequired
|
language: React.PropTypes.object.isRequired,
|
||||||
|
sizeRatio: React.PropTypes.number.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
currentMenu: React.PropTypes.any
|
currentMenu: React.PropTypes.any
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created an instance of a Page. This is an abstract class.
|
||||||
|
* @param {object} props Properties
|
||||||
|
*/
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
@@ -23,16 +31,34 @@ export default class Page extends React.Component {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Translated components are 'pure' components that only render when
|
||||||
|
* props, state, or context changes. This method performs a shallow comparison to
|
||||||
|
* determine change.
|
||||||
|
*
|
||||||
|
* @param {object} nextProps
|
||||||
|
* @param {objec} nextState
|
||||||
|
* @param {objec} nextContext
|
||||||
|
* @return {boolean} True if props, state, or context has changed
|
||||||
|
*/
|
||||||
shouldComponentUpdate(nextProps, nextState, nextContext) {
|
shouldComponentUpdate(nextProps, nextState, nextContext) {
|
||||||
return !shallowEqual(this.props, nextProps)
|
return !shallowEqual(this.props, nextProps)
|
||||||
|| !shallowEqual(this.state, nextState)
|
|| !shallowEqual(this.state, nextState)
|
||||||
|| !shallowEqual(this.context, nextContext)
|
|| !shallowEqual(this.context, nextContext)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the window title upon mount
|
||||||
|
*/
|
||||||
componentWillMount() {
|
componentWillMount() {
|
||||||
document.title = this.state.title || 'Coriolis';
|
document.title = this.state.title || 'Coriolis';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the title upon change
|
||||||
|
* @param {Object} newProps Incoming properties
|
||||||
|
* @param {Object} newState Incoming state
|
||||||
|
*/
|
||||||
componentWillUpdate(newProps, newState) {
|
componentWillUpdate(newProps, newState) {
|
||||||
document.title = newState.title || 'Coriolis';
|
document.title = newState.title || 'Coriolis';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -91,12 +91,13 @@ export default class ShipyardPage extends Page {
|
|||||||
<td className='le'><Link href={'/outfit/' + s.id}>{s.name}</Link></td>
|
<td className='le'><Link href={'/outfit/' + s.id}>{s.name}</Link></td>
|
||||||
<td className='le'>{s.manufacturer}</td>
|
<td className='le'>{s.manufacturer}</td>
|
||||||
<td className='cap'>{translate(SizeMap[s.class])}</td>
|
<td className='cap'>{translate(SizeMap[s.class])}</td>
|
||||||
<td className='ri'>{fInt(s.speed)}{u.ms}</td>
|
<td>{s.agility}</td>
|
||||||
<td className='ri'>{fInt(s.boost)}{u.ms}</td>
|
<td className='ri'>{fInt(s.speed)}{u['m/s']}</td>
|
||||||
|
<td className='ri'>{fInt(s.boost)}{u['m/s']}</td>
|
||||||
<td className='ri'>{s.baseArmour}</td>
|
<td className='ri'>{s.baseArmour}</td>
|
||||||
<td className='ri'>{fInt(s.baseShieldStrength)}{u.MJ}</td>
|
<td className='ri'>{fInt(s.baseShieldStrength)}{u.MJ}</td>
|
||||||
<td className='ri'>{fInt(s.topSpeed)}{u.ms}</td>
|
<td className='ri'>{fInt(s.topSpeed)}{u['m/s']}</td>
|
||||||
<td className='ri'>{fInt(s.topBoost)}{u.ms}</td>
|
<td className='ri'>{fInt(s.topBoost)}{u['m/s']}</td>
|
||||||
<td className='ri'>{fRound(s.maxJumpRange)}{u.LY}</td>
|
<td className='ri'>{fRound(s.maxJumpRange)}{u.LY}</td>
|
||||||
<td className='ri'>{fInt(s.maxCargo)}{u.T}</td>
|
<td className='ri'>{fInt(s.maxCargo)}{u.T}</td>
|
||||||
<td className={cn({ disabled: !s.hp[1] })}>{s.hp[1]}</td>
|
<td className={cn({ disabled: !s.hp[1] })}>{s.hp[1]}</td>
|
||||||
@@ -169,6 +170,7 @@ export default class ShipyardPage extends Page {
|
|||||||
<th rowSpan={2} className='sortable le' onClick={sortShips('name')}>{translate('ship')}</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('manufacturer')}>{translate('manufacturer')}</th>
|
||||||
<th rowSpan={2} className='sortable' onClick={sortShips('class')}>{translate('size')}</th>
|
<th rowSpan={2} className='sortable' onClick={sortShips('class')}>{translate('size')}</th>
|
||||||
|
<th rowSpan={2} className='sortable' onClick={sortShips('agility')}>{translate('agility')}</th>
|
||||||
<th colSpan={4}>{translate('base')}</th>
|
<th colSpan={4}>{translate('base')}</th>
|
||||||
<th colSpan={4}>{translate('max')}</th>
|
<th colSpan={4}>{translate('max')}</th>
|
||||||
<th colSpan={5} className='sortable' onClick={sortShips('hpCount')}>{translate('hardpoints')}</th>
|
<th colSpan={5} className='sortable' onClick={sortShips('hpCount')}>{translate('hardpoints')}</th>
|
||||||
|
|||||||
@@ -1,243 +0,0 @@
|
|||||||
angular.module('app').controller('ComparisonController', ['lodash', '$rootScope', '$filter', '$scope', '$state', '$stateParams', '$translate', 'Utils', 'ShipFacets', 'ShipsDB', 'Ship', 'Persist', 'Serializer', function(_, $rootScope, $filter, $scope, $state, $stateParams, $translate, Utils, ShipFacets, Ships, Ship, Persist, Serializer) {
|
|
||||||
$rootScope.title = 'Coriolis - Compare';
|
|
||||||
$scope.predicate = 'name'; // Sort by ship name as default
|
|
||||||
$scope.desc = false;
|
|
||||||
$scope.facetSortOpts = { containment: '#facet-container', orderChanged: function() { $scope.saved = false; } };
|
|
||||||
$scope.builds = [];
|
|
||||||
$scope.unusedBuilds = [];
|
|
||||||
$scope.name = $stateParams.name;
|
|
||||||
$scope.compareMode = !$stateParams.code;
|
|
||||||
$scope.importObj = {}; // Used for importing comparison builds (from permalinked comparison)
|
|
||||||
var defaultFacets = [9, 6, 4, 1, 3, 2]; // Reverse order of Armour, Shields, Speed, Jump Range, Cargo Capacity, Cost
|
|
||||||
var facets = $scope.facets = angular.copy(ShipFacets);
|
|
||||||
var shipId, buildName, comparisonData;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add an existing build to the comparison. The build must be saved locally.
|
|
||||||
* @param {string} id The unique ship key/id
|
|
||||||
* @param {string} name The build name
|
|
||||||
*/
|
|
||||||
$scope.addBuild = function(id, name, code) {
|
|
||||||
var data = Ships[id]; // Get ship properties
|
|
||||||
code = code ? code : Persist.builds[id][name]; // Retrieve build code if not passed
|
|
||||||
|
|
||||||
if (!code) { // No build found
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var b = new Ship(id, data.properties, data.slots); // Create a new Ship instance
|
|
||||||
Serializer.toShip(b, code); // Populate components from code
|
|
||||||
// Extend ship instance and add properties below
|
|
||||||
b.buildName = name;
|
|
||||||
b.code = code;
|
|
||||||
b.pctRetracted = b.powerRetracted / b.powerAvailable;
|
|
||||||
b.pctDeployed = b.powerDeployed / b.powerAvailable;
|
|
||||||
$scope.builds.push(b); // Add ship build to comparison
|
|
||||||
$scope.builds = $filter('orderBy')($scope.builds, $scope.predicate, $scope.desc); // Resort
|
|
||||||
_.remove($scope.unusedBuilds, function(o) { // Remove from unused builds
|
|
||||||
return o.id == id && o.buildName == name;
|
|
||||||
});
|
|
||||||
$scope.saved = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes a build from the comparison
|
|
||||||
* @param {string} id The unique ship key/id
|
|
||||||
* @param {string} name The build name
|
|
||||||
*/
|
|
||||||
$scope.removeBuild = function(id, name) {
|
|
||||||
_.remove($scope.builds, function(s) {
|
|
||||||
if (s.id == id && s.buildName == name) {
|
|
||||||
$scope.unusedBuilds.push({ id: id, buildName: name, name: s.name }); // Add build back to unused builds
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
$scope.saved = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Toggles the selected the set of facets used in the comparison
|
|
||||||
* @param {number} i The index of the facet in facets
|
|
||||||
*/
|
|
||||||
$scope.toggleFacet = function(i) {
|
|
||||||
facets[i].active = !facets[i].active;
|
|
||||||
$scope.tblUpdate = !$scope.tblUpdate; // Simple switch to trigger the table to update
|
|
||||||
$scope.saved = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Click handler for sorting by facets in the table
|
|
||||||
* @param {object} e Event object
|
|
||||||
*/
|
|
||||||
$scope.handleClick = function(e) {
|
|
||||||
var elem = angular.element(e.target);
|
|
||||||
if (elem.attr('prop')) { // Get component ID
|
|
||||||
$scope.sort(elem.attr('prop'));
|
|
||||||
|
|
||||||
} else if (elem.attr('del')) { // Delete index
|
|
||||||
$scope.removeBuild(elem.attr('del'));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sort the comparison array based on the selected facet / ship property
|
|
||||||
* @param {string} key Ship property
|
|
||||||
*/
|
|
||||||
$scope.sort = function(key) {
|
|
||||||
$scope.desc = $scope.predicate == key ? !$scope.desc : $scope.desc;
|
|
||||||
$scope.predicate = key;
|
|
||||||
$scope.builds = $filter('orderBy')($scope.builds, $scope.predicate, $scope.desc);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Saves the current comparison's selected facets and builds
|
|
||||||
*/
|
|
||||||
$scope.save = function() {
|
|
||||||
$scope.name = $scope.name.trim();
|
|
||||||
if ($scope.name == 'all') {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var selectedFacets = [];
|
|
||||||
facets.forEach(function(f) {
|
|
||||||
if (f.active) {
|
|
||||||
selectedFacets.unshift(f.index);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
Persist.saveComparison($scope.name, $scope.builds, selectedFacets);
|
|
||||||
$state.go('compare', { name: $scope.name }, { location: 'replace', notify: false });
|
|
||||||
$scope.saved = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Permantently delete the current comparison
|
|
||||||
*/
|
|
||||||
$scope.delete = function() {
|
|
||||||
Persist.deleteComparison($scope.name);
|
|
||||||
$state.go('compare', { name: null }, { location: 'replace', reload: true });
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set saved to false when the name of the comparison is changed.
|
|
||||||
*/
|
|
||||||
$scope.nameChange = function() {
|
|
||||||
$scope.saved = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Hide/Show the select builds menu
|
|
||||||
* @param {boolean} s Show true/false
|
|
||||||
* @param {Event} e Event Object
|
|
||||||
*/
|
|
||||||
$scope.selectBuilds = function(s, e) {
|
|
||||||
e.stopPropagation();
|
|
||||||
$scope.showBuilds = s;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Show the permalink modal
|
|
||||||
* @param {Event} e Event object
|
|
||||||
*/
|
|
||||||
$scope.permalink = function(e) {
|
|
||||||
e.stopPropagation();
|
|
||||||
$state.go('modal.link', { url: genPermalink() });
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generate the forum embed code for the comparison
|
|
||||||
* and show the export modal.
|
|
||||||
*
|
|
||||||
* @param {Event} e Event object
|
|
||||||
*/
|
|
||||||
$scope.embed = function(e) {
|
|
||||||
e.stopPropagation();
|
|
||||||
// Make a request to goo.gl to shorten the URL, returns a promise
|
|
||||||
var promise = Utils.shortenUrl( genPermalink()).then(
|
|
||||||
function(shortUrl) {
|
|
||||||
return Utils.comparisonBBCode(facets, $scope.builds, shortUrl);
|
|
||||||
},
|
|
||||||
function(err) {
|
|
||||||
return 'Error - ' + err.statusText;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
$state.go('modal.export', { promise: promise, title: $translate.instant('FORUM') + ' BBCode' });
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generates the long permalink URL
|
|
||||||
* @return {string} The long permalink URL
|
|
||||||
*/
|
|
||||||
function genPermalink() {
|
|
||||||
var selectedFacets = [];
|
|
||||||
facets.forEach(function(f) {
|
|
||||||
if (f.active) {
|
|
||||||
selectedFacets.unshift(f.index);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
var code = Serializer.fromComparison(
|
|
||||||
$scope.name,
|
|
||||||
$scope.builds,
|
|
||||||
selectedFacets,
|
|
||||||
$scope.predicate,
|
|
||||||
$scope.desc
|
|
||||||
);
|
|
||||||
return $state.href('comparison', { code: code }, { absolute: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Event listeners */
|
|
||||||
$scope.$on('close', function() {
|
|
||||||
$scope.showBuilds = false;
|
|
||||||
});
|
|
||||||
|
|
||||||
$scope.$on('languageChanged', function() {
|
|
||||||
$scope.tblUpdate = !$scope.tblUpdate; // Simple switch to trigger the table to update
|
|
||||||
});
|
|
||||||
|
|
||||||
/* Initialization */
|
|
||||||
if ($scope.compareMode) {
|
|
||||||
if ($scope.name == 'all') {
|
|
||||||
for (shipId in Persist.builds) {
|
|
||||||
for (buildName in Persist.builds[shipId]) {
|
|
||||||
$scope.addBuild(shipId, buildName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for (shipId in Persist.builds) {
|
|
||||||
for (buildName in Persist.builds[shipId]) {
|
|
||||||
$scope.unusedBuilds.push({ id: shipId, buildName: buildName, name: Ships[shipId].properties.name });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
comparisonData = Persist.getComparison($scope.name);
|
|
||||||
if (comparisonData) {
|
|
||||||
defaultFacets = comparisonData.facets;
|
|
||||||
comparisonData.builds.forEach(function(b) {
|
|
||||||
$scope.addBuild(b.shipId, b.buildName);
|
|
||||||
});
|
|
||||||
$scope.saved = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
comparisonData = Serializer.toComparison($stateParams.code);
|
|
||||||
defaultFacets = comparisonData.f;
|
|
||||||
$scope.name = comparisonData.n;
|
|
||||||
$scope.predicate = comparisonData.p;
|
|
||||||
$scope.desc = comparisonData.d;
|
|
||||||
comparisonData.b.forEach(function(build) {
|
|
||||||
$scope.addBuild(build.s, build.n, build.c);
|
|
||||||
if (!$scope.importObj[build.s]) {
|
|
||||||
$scope.importObj[build.s] = {};
|
|
||||||
}
|
|
||||||
$scope.importObj[build.s][build.n] = build.c;
|
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
throw { type: 'bad-comparison', message: e.message, details: e };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Replace fmt with actual format function as defined in rootScope and retain original index
|
|
||||||
facets.forEach(function(f, i) { f.index = i; });
|
|
||||||
// Remove default facets, mark as active, and add them back in selected order
|
|
||||||
_.pullAt(facets, defaultFacets).forEach(function(f) { f.active = true; facets.unshift(f); });
|
|
||||||
$scope.builds = $filter('orderBy')($scope.builds, $scope.predicate, $scope.desc);
|
|
||||||
|
|
||||||
}]);
|
|
||||||
@@ -94,80 +94,78 @@ export const ShipFacets = [
|
|||||||
{ // 0
|
{ // 0
|
||||||
title: 'agility',
|
title: 'agility',
|
||||||
props: ['agility'],
|
props: ['agility'],
|
||||||
unit: '',
|
fmt: 'int'
|
||||||
fmt: 'fCrd'
|
|
||||||
},
|
},
|
||||||
{ // 1
|
{ // 1
|
||||||
title: 'speed',
|
title: 'speed',
|
||||||
props: ['topSpeed', 'topBoost'],
|
props: ['topSpeed', 'topBoost'],
|
||||||
lbls: ['thrusters', 'boost'],
|
lbls: ['thrusters', 'boost'],
|
||||||
unit: 'm/s',
|
unit: 'm/s',
|
||||||
fmt: 'fCrd'
|
fmt: 'int'
|
||||||
},
|
},
|
||||||
{ // 2
|
{ // 2
|
||||||
title: 'armour',
|
title: 'armour',
|
||||||
props: ['armour'],
|
props: ['armour'],
|
||||||
unit: '',
|
unit: '',
|
||||||
fmt: 'fCrd'
|
fmt: 'int'
|
||||||
},
|
},
|
||||||
{ // 3
|
{ // 3
|
||||||
title: 'shields',
|
title: 'shields',
|
||||||
props: ['shieldStrength'],
|
props: ['shieldStrength'],
|
||||||
unit: 'MJ',
|
unit: 'MJ',
|
||||||
fmt: 'fRound'
|
fmt: 'round'
|
||||||
},
|
},
|
||||||
{ // 4
|
{ // 4
|
||||||
title: 'jump range',
|
title: 'jump range',
|
||||||
props: ['unladenRange', 'fullTankRange', 'ladenRange'],
|
props: ['unladenRange', 'fullTankRange', 'ladenRange'],
|
||||||
lbls: ['max', 'full tank', 'laden'],
|
lbls: ['max', 'full tank', 'laden'],
|
||||||
unit: 'LY',
|
unit: 'LY',
|
||||||
fmt: 'fRound'
|
fmt: 'round'
|
||||||
},
|
},
|
||||||
{ // 5
|
{ // 5
|
||||||
title: 'mass',
|
title: 'mass',
|
||||||
props: ['unladenMass', 'ladenMass'],
|
props: ['unladenMass', 'ladenMass'],
|
||||||
lbls: ['unladen', 'laden'],
|
lbls: ['unladen', 'laden'],
|
||||||
unit: 'T',
|
unit: 'T',
|
||||||
fmt: 'fRound'
|
fmt: 'round'
|
||||||
},
|
},
|
||||||
{ // 6
|
{ // 6
|
||||||
title: 'cargo',
|
title: 'cargo',
|
||||||
props: ['cargoCapacity'],
|
props: ['cargoCapacity'],
|
||||||
unit: 'T',
|
unit: 'T',
|
||||||
fmt: 'fRound'
|
fmt: 'round'
|
||||||
},
|
},
|
||||||
{ // 7
|
{ // 7
|
||||||
title: 'fuel',
|
title: 'fuel',
|
||||||
props: ['fuelCapacity'],
|
props: ['fuelCapacity'],
|
||||||
unit: 'T',
|
unit: 'T',
|
||||||
fmt: 'fRound'
|
fmt: 'int'
|
||||||
},
|
},
|
||||||
{ // 8
|
{ // 8
|
||||||
title: 'power',
|
title: 'power',
|
||||||
props: ['powerRetracted', 'powerDeployed', 'powerAvailable'],
|
props: ['powerRetracted', 'powerDeployed', 'powerAvailable'],
|
||||||
lbls: ['retracted', 'deployed', 'available'],
|
lbls: ['retracted', 'deployed', 'available'],
|
||||||
unit: 'MW',
|
unit: 'MW',
|
||||||
fmt: 'fPwr'
|
fmt: 'f2'
|
||||||
},
|
},
|
||||||
{ // 9
|
{ // 9
|
||||||
title: 'cost',
|
title: 'cost',
|
||||||
props: ['totalCost'],
|
props: ['totalCost'],
|
||||||
unit: 'CR',
|
unit: 'CR',
|
||||||
fmt: 'fCrd'
|
fmt: 'int'
|
||||||
},
|
},
|
||||||
{ // 10
|
{ // 10
|
||||||
title: 'total range',
|
title: 'total range',
|
||||||
props: ['unladenTotalRange', 'ladenTotalRange'],
|
props: ['unladenTotalRange', 'ladenTotalRange'],
|
||||||
lbls: ['unladen', 'laden'],
|
lbls: ['unladen', 'laden'],
|
||||||
unit: 'LY',
|
unit: 'LY',
|
||||||
fmt: 'fRound'
|
fmt: 'round'
|
||||||
},
|
},
|
||||||
{ // 11
|
{ // 11
|
||||||
title: 'DPS',
|
title: 'DPS',
|
||||||
props: ['totalDps'],
|
props: ['totalDps'],
|
||||||
lbls: ['DPS'],
|
lbls: ['DPS'],
|
||||||
unit: '',
|
fmt: 'round'
|
||||||
fmt: 'fRound'
|
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -185,8 +183,10 @@ export const Insurance = {
|
|||||||
*/
|
*/
|
||||||
export const Discounts = {
|
export const Discounts = {
|
||||||
'0%': 1,
|
'0%': 1,
|
||||||
|
'2.5%': 0.975,
|
||||||
'5%': 0.95,
|
'5%': 0.95,
|
||||||
'10%': 0.90,
|
'10%': 0.90,
|
||||||
|
'12.5%': 0.875,
|
||||||
'15%': 0.85,
|
'15%': 0.85,
|
||||||
'20%': 0.80,
|
'20%': 0.80,
|
||||||
'25%': 0.75
|
'25%': 0.75
|
||||||
|
|||||||
@@ -165,7 +165,7 @@ export function findHardpointId(groupName, clss, rating, name, mount, missile) {
|
|||||||
*/
|
*/
|
||||||
export function bulkheads(shipId, index) {
|
export function bulkheads(shipId, index) {
|
||||||
let bulkhead = Ships[shipId].bulkheads[index];
|
let bulkhead = Ships[shipId].bulkheads[index];
|
||||||
bulkhead.class = 8;
|
bulkhead.class = 1;
|
||||||
bulkhead.rating = 'I';
|
bulkhead.rating = 'I';
|
||||||
bulkhead.name = BulkheadNames[index]
|
bulkhead.name = BulkheadNames[index]
|
||||||
|
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ export function toDetailedBuild(buildName, ship, code) {
|
|||||||
internal = ship.internal;
|
internal = ship.internal;
|
||||||
|
|
||||||
var data = {
|
var data = {
|
||||||
$schema: 'http://cdn.coriolis.io/schemas/ship-loadout/2.json#',
|
$schema: 'http://cdn.coriolis.io/schemas/ship-loadout/3.json#',
|
||||||
name: buildName,
|
name: buildName,
|
||||||
ship: ship.name,
|
ship: ship.name,
|
||||||
references: [{
|
references: [{
|
||||||
@@ -76,7 +76,7 @@ export function toDetailedBuild(buildName, ship, code) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export function fromDetailedBuild(detailedBuild) {
|
export function fromDetailedBuild(detailedBuild) {
|
||||||
var shipId = _.findKey(ShipsDB, { properties: { name: detailedBuild.ship } });
|
var shipId = Object.keys(Ships).find((shipId) => Ships[shipId].properties.name.toLowerCase() == detailedBuild.ship.toLowerCase());
|
||||||
|
|
||||||
if (!shipId) {
|
if (!shipId) {
|
||||||
throw 'No such ship: ' + detailedBuild.ship;
|
throw 'No such ship: ' + detailedBuild.ship;
|
||||||
@@ -133,9 +133,9 @@ export function toDetailedExport(builds) {
|
|||||||
for (var shipId in builds) {
|
for (var shipId in builds) {
|
||||||
for (var buildName in builds[shipId]) {
|
for (var buildName in builds[shipId]) {
|
||||||
var code = builds[shipId][buildName];
|
var code = builds[shipId][buildName];
|
||||||
var shipData = ShipsDB[shipId];
|
var shipData = Ships[shipId];
|
||||||
var ship = new Ship(shipId, shipData.properties, shipData.slots);
|
var ship = new Ship(shipId, shipData.properties, shipData.slots);
|
||||||
toShip(ship, code);
|
ship.buildFrom(code);
|
||||||
data.push(toDetailedBuild(buildName, ship, code));
|
data.push(toDetailedBuild(buildName, ship, code));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -149,8 +149,16 @@ export default class Ship {
|
|||||||
* @param {number} fuel Fuel available in tons
|
* @param {number} fuel Fuel available in tons
|
||||||
* @return {number} Jump range in Light Years
|
* @return {number} Jump range in Light Years
|
||||||
*/
|
*/
|
||||||
getJumpRangeForMass(mass, fuel) {
|
getJumpRangeWith(fuel, cargo) {
|
||||||
return Calc.jumpRange(mass, this.standard[2].m, fuel);
|
return Calc.jumpRange(this.unladenMass + fuel + cargo, this.standard[2].m, fuel);
|
||||||
|
}
|
||||||
|
|
||||||
|
getFastestRangeWith(fuel, cargo) {
|
||||||
|
return Calc.totalRange(this.unladenMass + fuel + cargo, this.standard[2].m, fuel);
|
||||||
|
}
|
||||||
|
|
||||||
|
getSpeedsWith(fuel, cargo) {
|
||||||
|
return Calc.speed(this.unladenMass + fuel + cargo, this.speed, this.boost, this.standard[1].m, this.pipSpeed);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -88,9 +88,7 @@ class Persist extends EventEmitter {
|
|||||||
let newBuild = !this.builds[shipId][name];
|
let newBuild = !this.builds[shipId][name];
|
||||||
this.builds[shipId][name] = code;
|
this.builds[shipId][name] = code;
|
||||||
_put(LS_KEY_BUILDS, this.builds);
|
_put(LS_KEY_BUILDS, this.builds);
|
||||||
if (newBuild) {
|
this.emit('buildSaved', shipId, name, code);
|
||||||
this.emit('buildSaved', shipId, name, code);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -232,7 +230,7 @@ class Persist extends EventEmitter {
|
|||||||
data[LS_KEY_BUILDS] = this.getBuilds();
|
data[LS_KEY_BUILDS] = this.getBuilds();
|
||||||
data[LS_KEY_COMPARISONS] = this.getComparisons();
|
data[LS_KEY_COMPARISONS] = this.getComparisons();
|
||||||
data[LS_KEY_INSURANCE] = this.getInsurance();
|
data[LS_KEY_INSURANCE] = this.getInsurance();
|
||||||
data[LS_KEY_DISCOUNTS] = this.getDiscount();
|
data[LS_KEY_DISCOUNTS] = this.discounts;
|
||||||
return data;
|
return data;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* [slotName description]
|
* Returns the translate name for the module mounted in the specified
|
||||||
* @param {[type]} translate [description]
|
* slot.
|
||||||
* @param {[type]} slot [description]
|
* @param {function} translate Translation function
|
||||||
* @return {[type]} [description]
|
* @param {object} slot Slot object
|
||||||
|
* @return {string} The translated name
|
||||||
*/
|
*/
|
||||||
export function slotName(translate, slot) {
|
export function slotName(translate, slot) {
|
||||||
return slot.m ? translate(slot.m.name || slot.m.grp) : '';
|
return slot.m ? translate(slot.m.name || slot.m.grp) : '';
|
||||||
@@ -15,5 +16,22 @@ export function slotName(translate, slot) {
|
|||||||
* @return {function} Comparator function for slot names
|
* @return {function} Comparator function for slot names
|
||||||
*/
|
*/
|
||||||
export function nameComparator(translate) {
|
export function nameComparator(translate) {
|
||||||
return (a, b) => slotName(translate, a).toLowerCase().localeCompare(slotName(translate, b).toLowerCase());
|
return (a, b) => {
|
||||||
|
a = a.m;
|
||||||
|
b = b.m;
|
||||||
|
|
||||||
|
if (a && !b) {
|
||||||
|
return 1;
|
||||||
|
} else if (!a && b) {
|
||||||
|
return -1;
|
||||||
|
} else if (!a && !b) {
|
||||||
|
return 0;
|
||||||
|
} else if (a.name === b.name && a.grp === b.grp) {
|
||||||
|
if(a.class == b.class) {
|
||||||
|
return a.rating > b.rating ? 1 : -1;
|
||||||
|
}
|
||||||
|
return a.class - b.class;
|
||||||
|
}
|
||||||
|
return translate(a.name || a.grp).localeCompare(translate(b.name || b.grp));
|
||||||
|
};
|
||||||
}
|
}
|
||||||
@@ -13,7 +13,6 @@
|
|||||||
@import 'select';
|
@import 'select';
|
||||||
@import 'modal';
|
@import 'modal';
|
||||||
@import 'charts';
|
@import 'charts';
|
||||||
@import 'slider';
|
|
||||||
@import 'chart-tooltip';
|
@import 'chart-tooltip';
|
||||||
@import 'buttons';
|
@import 'buttons';
|
||||||
@import 'error';
|
@import 'error';
|
||||||
@@ -43,7 +42,7 @@ div, a, li {
|
|||||||
|
|
||||||
#coriolis {
|
#coriolis {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
min-height: 100%;
|
min-height: 95%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.page {
|
.page {
|
||||||
|
|||||||
@@ -54,9 +54,10 @@
|
|||||||
display: block;
|
display: block;
|
||||||
.user-select-none();
|
.user-select-none();
|
||||||
cursor: default;
|
cursor: default;
|
||||||
|
white-space: nowrap;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0 1px;
|
||||||
|
|
||||||
.as-sortable-placeholder {
|
.as-sortable-placeholder {
|
||||||
background-color: @primary-bg;
|
background-color: @primary-bg;
|
||||||
@@ -66,23 +67,23 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.facet-placeholder {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0.5em 0.5em;
|
||||||
|
background-color: @primary-bg;
|
||||||
|
}
|
||||||
|
|
||||||
.facet {
|
.facet {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
display: inline-block;
|
||||||
background-color: @primary-bg;
|
background-color: @primary-bg;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0.5em 0.5em;
|
padding: 0 0.5em;
|
||||||
|
line-height: 2.5em;
|
||||||
list-style: none;
|
list-style: none;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
color: @disabled;
|
color: @disabled;
|
||||||
|
|
||||||
svg {
|
|
||||||
fill: @disabled;
|
|
||||||
}
|
|
||||||
|
|
||||||
.move {
|
|
||||||
cursor: ew-resize;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.active {
|
&.active {
|
||||||
color: @warning;
|
color: @warning;
|
||||||
svg {
|
svg {
|
||||||
|
|||||||
@@ -114,12 +114,6 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
.sortable {
|
|
||||||
&:hover {
|
|
||||||
color: @primary;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.shorten {
|
.shorten {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
max-width: 8em;
|
max-width: 8em;
|
||||||
@@ -225,15 +219,13 @@
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
svg g {
|
.threshold {
|
||||||
.threshold {
|
stroke: @secondary-disabled !important;
|
||||||
stroke: @secondary-disabled !important;
|
fill: @secondary-disabled !important;
|
||||||
fill: @secondary-disabled !important;
|
|
||||||
|
|
||||||
&.exceeded {
|
&.exceeded {
|
||||||
stroke: @warning !important;
|
stroke: @warning !important;
|
||||||
fill: @warning !important;
|
fill: @warning !important;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,98 +0,0 @@
|
|||||||
|
|
||||||
.slider-axis {
|
|
||||||
line, path {
|
|
||||||
fill: none;
|
|
||||||
stroke: @primary-disabled;
|
|
||||||
}
|
|
||||||
|
|
||||||
text {
|
|
||||||
font-size: 0.7em;
|
|
||||||
fill: @primary-disabled;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
.domain {
|
|
||||||
fill: none;
|
|
||||||
stroke: @primary;
|
|
||||||
stroke-opacity: .3;
|
|
||||||
stroke-width: 0.7em;
|
|
||||||
stroke-linecap: round;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
.slider {
|
|
||||||
|
|
||||||
text {
|
|
||||||
dominant-baseline: central;
|
|
||||||
fill: @primary;
|
|
||||||
font-size: 0.8em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.filled {
|
|
||||||
stroke-width: 0.3em;
|
|
||||||
stroke-linecap: round;
|
|
||||||
stroke: @primary-disabled;
|
|
||||||
}
|
|
||||||
|
|
||||||
.handle {
|
|
||||||
fill: @primary;
|
|
||||||
stroke-opacity: .5;
|
|
||||||
cursor: crosshair;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
input[type=range] {
|
|
||||||
-webkit-appearance: none;
|
|
||||||
border: 1px solid @bgBlack;
|
|
||||||
/*required for proper track sizing in FF*/
|
|
||||||
width: 300px;
|
|
||||||
|
|
||||||
&::-moz-range-track, &::-webkit-slider-runnable-track {
|
|
||||||
width: 300px;
|
|
||||||
height: 5px;
|
|
||||||
background: @primary;
|
|
||||||
border: none;
|
|
||||||
border-radius: 3px;
|
|
||||||
}
|
|
||||||
&::-moz-range-thumb, &::-webkit-slider-thumb {
|
|
||||||
-webkit-appearance: none;
|
|
||||||
border: none;
|
|
||||||
height: 1em;
|
|
||||||
width: 1em;
|
|
||||||
border-radius: 50%;
|
|
||||||
background: @primary;
|
|
||||||
}
|
|
||||||
&:focus {
|
|
||||||
outline: none;
|
|
||||||
}
|
|
||||||
/*hide the outline behind the border*/
|
|
||||||
&:-moz-focusring{
|
|
||||||
outline: 1px solid @bgBlack;
|
|
||||||
outline-offset: -1px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&::-ms-track {
|
|
||||||
width: 300px;
|
|
||||||
height: 5px;
|
|
||||||
background: transparent;
|
|
||||||
border-color: transparent;
|
|
||||||
border-width: 6px 0;
|
|
||||||
color: transparent;
|
|
||||||
}
|
|
||||||
&::-ms-fill-lower {
|
|
||||||
background: @primary;
|
|
||||||
border-radius: 10px;
|
|
||||||
}
|
|
||||||
&::-ms-fill-upper {
|
|
||||||
background: @primary;
|
|
||||||
border-radius: 10px;
|
|
||||||
}
|
|
||||||
&::-ms-thumb {
|
|
||||||
border: none;
|
|
||||||
height: 16px;
|
|
||||||
width: 16px;
|
|
||||||
border-radius: 50%;
|
|
||||||
background: goldenrod;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,31 +1,8 @@
|
|||||||
.sortable {
|
.sortable {
|
||||||
.user-select-none();
|
.user-select-none();
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
|
||||||
|
|
||||||
.as-sortable-item, .as-sortable-placeholder {
|
&:hover {
|
||||||
display: inline-block;
|
color: @primary;
|
||||||
float: left;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
.as-sortable-item {
|
|
||||||
-ms-touch-action: none;
|
|
||||||
touch-action: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.as-sortable-item-handle {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.as-sortable-drag {
|
|
||||||
margin: 0;
|
|
||||||
padding:0;
|
|
||||||
opacity: .8;
|
|
||||||
position: absolute;
|
|
||||||
pointer-events: none;
|
|
||||||
z-index: 9999;
|
|
||||||
}
|
|
||||||
|
|
||||||
.as-sortable-hidden {
|
|
||||||
display: none !important;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,13 +30,6 @@ thead {
|
|||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
padding: 2px 0.4em 0; // Padding top for font vertical alignment
|
padding: 2px 0.4em 0; // Padding top for font vertical alignment
|
||||||
|
|
||||||
&.prop {
|
|
||||||
cursor: pointer;
|
|
||||||
&:hover {
|
|
||||||
color: @primary;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.lft {
|
&.lft {
|
||||||
border-left: 1px solid @primary-bg;
|
border-left: 1px solid @primary-bg;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -302,7 +302,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"definitions": {
|
"definitions": {
|
||||||
"standardRatings": { "enum": ["A", "B", "C", "D", "E"] },
|
"standardRatings": { "enum": ["A", "B", "C", "D", "E", "F", "G", "H"] },
|
||||||
"allRatings": { "enum": ["A", "B", "C", "D", "E", "F", "I" ] }
|
"allRatings": { "enum": ["A", "B", "C", "D", "E", "F", "G", "H", "I" ] }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
308
src/schemas/ship-loadout/3.json
Normal file
308
src/schemas/ship-loadout/3.json
Normal file
@@ -0,0 +1,308 @@
|
|||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||||
|
"id": "http://cdn.coriolis.io/schemas/ship-loadout/3.json#",
|
||||||
|
"title": "Ship Loadout",
|
||||||
|
"type": "object",
|
||||||
|
"description": "The details for a specific ship build/loadout",
|
||||||
|
"required": ["name", "ship", "components"],
|
||||||
|
"properties": {
|
||||||
|
"name": {
|
||||||
|
"description": "The name of the build/loadout",
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 2
|
||||||
|
},
|
||||||
|
"ship": {
|
||||||
|
"description": "The full display name of the ship",
|
||||||
|
"type": "string",
|
||||||
|
"minimum": 3
|
||||||
|
},
|
||||||
|
"manufacturer": {
|
||||||
|
"description": "The ship manufacturer",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"references" : {
|
||||||
|
"description": "3rd Party references and/or links to this build/loadout",
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"required": ["name","url"],
|
||||||
|
"additionalProperties": true,
|
||||||
|
"properties": {
|
||||||
|
"name": {
|
||||||
|
"description": "The name of the 3rd party, .e.g 'Coriolis.io' or 'E:D Shipyard'",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"url": {
|
||||||
|
"description": "The link/url to the 3rd party referencing this build/loadout",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"components": {
|
||||||
|
"description": "The components used by this build",
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": ["standard", "internal", "hardpoints", "utility"],
|
||||||
|
"properties": {
|
||||||
|
"standard": {
|
||||||
|
"description": "The set of standard components across all ships",
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": ["bulkheads", "powerPlant", "thrusters", "frameShiftDrive", "lifeSupport", "powerDistributor", "sensors", "fuelTank", "cargoHatch"],
|
||||||
|
"properties": {
|
||||||
|
"bulkheads": {
|
||||||
|
"enum": ["Lightweight Alloy", "Reinforced Alloy", "Military Grade Composite", "Mirrored Surface Composite", "Reactive Surface Composite"]
|
||||||
|
},
|
||||||
|
"cargoHatch": {
|
||||||
|
"required": ["enabled", "priority"],
|
||||||
|
"properties": {
|
||||||
|
"enabled": { "type": "boolean" },
|
||||||
|
"priority": { "type": "integer", "minimum": 1, "maximum": 5 }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"powerPlant": {
|
||||||
|
"required": ["class", "rating", "enabled", "priority"],
|
||||||
|
"properties": {
|
||||||
|
"class": { "type": "integer", "minimum": 2, "maximum": 8 },
|
||||||
|
"rating": { "$ref": "#/definitions/standardRatings" },
|
||||||
|
"enabled": { "type": "boolean" },
|
||||||
|
"priority": { "type": "integer", "minimum": 1, "maximum": 5 }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"thrusters": {
|
||||||
|
"required": ["class", "rating", "enabled", "priority"],
|
||||||
|
"properties": {
|
||||||
|
"class": { "type": "integer", "minimum": 2, "maximum": 8 },
|
||||||
|
"rating": { "$ref": "#/definitions/standardRatings" },
|
||||||
|
"enabled": { "type": "boolean" },
|
||||||
|
"priority": { "type": "integer", "minimum": 1, "maximum": 5 }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"frameShiftDrive": {
|
||||||
|
"required": ["class", "rating", "enabled", "priority"],
|
||||||
|
"properties": {
|
||||||
|
"class": { "type": "integer", "minimum": 2, "maximum": 8 },
|
||||||
|
"rating": { "$ref": "#/definitions/standardRatings" },
|
||||||
|
"enabled": { "type": "boolean" },
|
||||||
|
"priority": { "type": "integer", "minimum": 1, "maximum": 5 }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"lifeSupport": {
|
||||||
|
"required": ["class", "rating", "enabled", "priority"],
|
||||||
|
"properties": {
|
||||||
|
"class": { "type": "integer", "minimum": 1, "maximum": 6 },
|
||||||
|
"rating": { "$ref": "#/definitions/standardRatings" },
|
||||||
|
"enabled": { "type": "boolean" },
|
||||||
|
"priority": { "type": "integer", "minimum": 1, "maximum": 5 }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"powerDistributor": {
|
||||||
|
"required": ["class", "rating", "enabled", "priority"],
|
||||||
|
"properties": {
|
||||||
|
"class": { "type": "integer", "minimum": 1, "maximum": 8 },
|
||||||
|
"rating": { "$ref": "#/definitions/standardRatings" },
|
||||||
|
"enabled": { "type": "boolean" },
|
||||||
|
"priority": { "type": "integer", "minimum": 1, "maximum": 5 }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"sensors": {
|
||||||
|
"required": ["class", "rating", "enabled", "priority"],
|
||||||
|
"properties": {
|
||||||
|
"class": { "type": "integer", "minimum": 1, "maximum": 8 },
|
||||||
|
"rating": { "$ref": "#/definitions/standardRatings" },
|
||||||
|
"enabled": { "type": "boolean" },
|
||||||
|
"priority": { "type": "integer", "minimum": 1, "maximum": 5 }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"fuelTank": {
|
||||||
|
"required": ["class", "rating", "enabled", "priority"],
|
||||||
|
"properties": {
|
||||||
|
"class": { "type": "integer", "minimum": 1, "maximum": 6 },
|
||||||
|
"rating": { "$ref": "#/definitions/standardRatings" },
|
||||||
|
"enabled": { "type": "boolean" },
|
||||||
|
"priority": { "type": "integer", "minimum": 1, "maximum": 5 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"internal": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": ["object", "null"],
|
||||||
|
"required": ["class", "rating", "enabled", "priority", "group"],
|
||||||
|
"properties" : {
|
||||||
|
"class": { "type": "integer", "minimum": 1, "maximum": 8 },
|
||||||
|
"rating": { "$ref": "#/definitions/standardRatings" },
|
||||||
|
"enabled": { "type": "boolean" },
|
||||||
|
"priority": { "type": "integer", "minimum": 1, "maximum": 5 },
|
||||||
|
"group": {
|
||||||
|
"description": "The group of the component, e.g. 'Shield Generator', or 'Cargo Rack'",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"description": "The name identifying the component (if applicable), e.g. 'Advance Discovery Scanner', or 'Detailed Surface Scanner'",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"minItems": 3
|
||||||
|
},
|
||||||
|
"hardpoints": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": ["object", "null"],
|
||||||
|
"required": ["class", "rating", "enabled", "priority", "group", "mount"],
|
||||||
|
"properties" : {
|
||||||
|
"class": { "type": "integer", "minimum": 1, "maximum": 4 },
|
||||||
|
"rating": { "$ref": "#/definitions/allRatings" },
|
||||||
|
"enabled": { "type": "boolean" },
|
||||||
|
"priority": { "type": "integer", "minimum": 1, "maximum": 5 },
|
||||||
|
"mount": { "type": "string", "enum": ["Fixed", "Gimballed", "Turret"] },
|
||||||
|
"group": {
|
||||||
|
"description": "The group of the component, e.g. 'Beam Laser', or 'Missile Rack'",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"description": "The name identifing the component (if applicable), e.g. 'Retributor', or 'Mining Lance'",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"minItems": 1
|
||||||
|
},
|
||||||
|
"utility": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": ["object", "null"],
|
||||||
|
"required": ["class", "rating", "enabled", "priority", "group"],
|
||||||
|
"properties" : {
|
||||||
|
"class": { "type": "integer", "minimum": 0, "maximum": 0 },
|
||||||
|
"rating": { "$ref": "#/definitions/allRatings" },
|
||||||
|
"enabled": { "type": "boolean" },
|
||||||
|
"priority": { "type": "integer", "minimum": 1, "maximum": 5 },
|
||||||
|
"group": {
|
||||||
|
"description": "The group of the component, e.g. 'Shield Booster', or 'Kill Warrant Scanner'",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"description": "The name identifing the component (if applicable), e.g. 'Point Defence', or 'Electronic Countermeasure'",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"minItems": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"stats": {
|
||||||
|
"description": "Optional statistics from the build",
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": true,
|
||||||
|
"properties": {
|
||||||
|
"agility": {
|
||||||
|
"type": "integer",
|
||||||
|
"minimum": 0
|
||||||
|
},
|
||||||
|
"armour": {
|
||||||
|
"description": "Sum of base armour + any hull reinforcements",
|
||||||
|
"type": "integer",
|
||||||
|
"minimum": 1
|
||||||
|
},
|
||||||
|
"armourAdded":{
|
||||||
|
"description": "Armour added through Hull reinforcement",
|
||||||
|
"type": "integer",
|
||||||
|
"minimum": 1
|
||||||
|
},
|
||||||
|
"baseShieldStrength": {
|
||||||
|
"type": "integer",
|
||||||
|
"minimum": 1
|
||||||
|
},
|
||||||
|
"baseArmour": {
|
||||||
|
"type": "integer",
|
||||||
|
"minimum": 1
|
||||||
|
},
|
||||||
|
"boost": {
|
||||||
|
"description": "Maximum boost speed of the ships (4 pips, straight-line)",
|
||||||
|
"type": "number",
|
||||||
|
"minimum": 1
|
||||||
|
},
|
||||||
|
"cargoCapacity": {
|
||||||
|
"type": "integer",
|
||||||
|
"minimum": 0
|
||||||
|
},
|
||||||
|
"class": {
|
||||||
|
"description": "Ship Class/Size [Small, Medium, Large]",
|
||||||
|
"enum": [1,2,3]
|
||||||
|
},
|
||||||
|
"dps": {
|
||||||
|
"description": "Cumulative DPS based on the in-game 1-10 statistic",
|
||||||
|
"type": "integer",
|
||||||
|
"minimum": 0
|
||||||
|
},
|
||||||
|
"hullCost": {
|
||||||
|
"description": "Cost of the ship's hull",
|
||||||
|
"type": "integer",
|
||||||
|
"minimum": 1
|
||||||
|
},
|
||||||
|
"hullMass": {
|
||||||
|
"description": "Mass of the Ship hull only",
|
||||||
|
"type": "number",
|
||||||
|
"minimum": 1
|
||||||
|
},
|
||||||
|
"fuelCapacity": {
|
||||||
|
"type": "integer",
|
||||||
|
"minimum": 1
|
||||||
|
},
|
||||||
|
"fullTankRange": {
|
||||||
|
"description": "Single Jump range with a full tank (unladenMass + fuel)",
|
||||||
|
"type": "number",
|
||||||
|
"minimum": 0
|
||||||
|
},
|
||||||
|
"ladenMass": {
|
||||||
|
"description": "Mass of the Ship + fuel + cargo (hull + all components + fuel tank + cargo capacity)",
|
||||||
|
"type": "number",
|
||||||
|
"minimum": 1
|
||||||
|
},
|
||||||
|
"ladenRange": {
|
||||||
|
"description": "Single Jump range with full cargo load, see ladenMass",
|
||||||
|
"type": "number",
|
||||||
|
"minimum": 0
|
||||||
|
},
|
||||||
|
"masslock": {
|
||||||
|
"description": "Mass Lock Factor of the Ship",
|
||||||
|
"type": "integer",
|
||||||
|
"minimum": 1
|
||||||
|
},
|
||||||
|
"shieldStrength": {
|
||||||
|
"description": "Shield strengh in Mega Joules (Mj)",
|
||||||
|
"type": "number",
|
||||||
|
"minimum": 0
|
||||||
|
},
|
||||||
|
"speed": {
|
||||||
|
"description": "Maximum speed of the ships (4 pips, straight-line)",
|
||||||
|
"type": "number",
|
||||||
|
"minimum": 1
|
||||||
|
},
|
||||||
|
"totalCost": {
|
||||||
|
"type": "integer",
|
||||||
|
"minimum": 1
|
||||||
|
},
|
||||||
|
"unladenRange": {
|
||||||
|
"description": "Single Jump range when unladen, see unladenMass",
|
||||||
|
"type": "number",
|
||||||
|
"minimum": 0
|
||||||
|
},
|
||||||
|
"unladenMass": {
|
||||||
|
"description": "Mass of the Ship (hull + all components)",
|
||||||
|
"type": "number",
|
||||||
|
"minimum": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"definitions": {
|
||||||
|
"standardRatings": { "enum": ["A", "B", "C", "D", "E", "F", "G", "H"] },
|
||||||
|
"allRatings": { "enum": ["A", "B", "C", "D", "E", "F", "G", "H", "I" ] }
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user