diff --git a/src/app/components/BarChart.jsx b/src/app/components/BarChart.jsx index 8ab59851..801e9f88 100644 --- a/src/app/components/BarChart.jsx +++ b/src/app/components/BarChart.jsx @@ -44,7 +44,7 @@ export default class BarChart extends TranslatedComponent { unit: '' }; - static PropTypes = { + static propTypes = { colors: React.PropTypes.array, data: React.PropTypes.array.isRequired, desc: React.PropTypes.bool, diff --git a/src/app/components/BattleCentre.jsx b/src/app/components/BattleCentre.jsx index c5187ff5..88ce19eb 100644 --- a/src/app/components/BattleCentre.jsx +++ b/src/app/components/BattleCentre.jsx @@ -1,24 +1,23 @@ import React from 'react'; import TranslatedComponent from './TranslatedComponent'; import { Ships } from 'coriolis-data/dist'; -import Slider from '../components/Slider'; -import Pips from '../components/Pips'; -import Fuel from '../components/Fuel'; -import Cargo from '../components/Cargo'; -import Movement from '../components/Movement'; -import EngagementRange from '../components/EngagementRange'; +import Slider from './Slider'; +import Pips from './Pips'; +import Fuel from './Fuel'; +import Cargo from './Cargo'; +import Movement from './Movement'; +import EngagementRange from './EngagementRange'; +import ShipPicker from './ShipPicker'; /** * Battle centre allows you to pit your current build against another ship, * adjust pips and engagement range, and see a wide variety of information */ export default class BattleCentre extends TranslatedComponent { - static PropTypes = { + static propTypes = { ship: React.PropTypes.object.isRequired }; - static DEFAULT_OPPONENT = { ship: Ships['anaconda'] }; - /** * Constructor * @param {Object} props React Component properties @@ -34,18 +33,17 @@ export default class BattleCentre extends TranslatedComponent { this._fuelUpdated = this._fuelUpdated.bind(this); this._pipsUpdated = this._pipsUpdated.bind(this); this._engagementRangeUpdated = this._engagementRangeUpdated.bind(this); + this._targetShipUpdated = this._targetShipUpdated.bind(this); this.state = { // Pips sys: 2, eng: 2, wep: 2, - // Fuel fuel: ship.fuelCapacity, - // Cargo cargo: ship.cargoCapacity, - // Engagement range engagementRange: 1500, + targetShip: Ships['anaconda'] }; } @@ -83,6 +81,13 @@ export default class BattleCentre extends TranslatedComponent { this.setState({ engagementRange }); } + /** + * Triggered when target ship has been updated + */ + _targetShipUpdated(targetShip, targetBuild) { + this.setState({ targetShip, targetBuild: targetBuild }); + } + /** * Render * @return {React.Component} contents @@ -90,12 +95,13 @@ export default class BattleCentre extends TranslatedComponent { render() { const { language, onWindowResize, sizeRatio, tooltip, termtip } = this.context; const { formats, translate, units } = language; - const { sys, eng, wep, cargo, fuel, engagementRange, totals } = this.state; + const { sys, eng, wep, cargo, fuel, engagementRange } = this.state; const { ship } = this.props; return (

{translate('battle centre')}

+
diff --git a/src/app/components/Cargo.jsx b/src/app/components/Cargo.jsx index f03ae01e..25ec985a 100644 --- a/src/app/components/Cargo.jsx +++ b/src/app/components/Cargo.jsx @@ -8,7 +8,7 @@ import Slider from '../components/Slider'; * Requires an onChange() function of the form onChange(cargo), providing the cargo in tonnes, which is triggered on cargo level change */ export default class Cargo extends TranslatedComponent { - static PropTypes = { + static propTypes = { ship: React.PropTypes.object.isRequired, onChange: React.PropTypes.func.isRequired }; diff --git a/src/app/components/CostSection.jsx b/src/app/components/CostSection.jsx index 2df44855..f0dc5bfd 100644 --- a/src/app/components/CostSection.jsx +++ b/src/app/components/CostSection.jsx @@ -12,7 +12,7 @@ import TranslatedComponent from './TranslatedComponent'; */ export default class CostSection extends TranslatedComponent { - static PropTypes = { + static propTypes = { ship: React.PropTypes.object.isRequired, code: React.PropTypes.string.isRequired, buildName: React.PropTypes.string diff --git a/src/app/components/DamageDealt.jsx b/src/app/components/DamageDealt.jsx index bb4bd484..c9bd714e 100644 --- a/src/app/components/DamageDealt.jsx +++ b/src/app/components/DamageDealt.jsx @@ -50,7 +50,7 @@ export function weaponComparator(translate, propComparator, desc) { * Damage against a selected ship */ export default class DamageDealt extends TranslatedComponent { - static PropTypes = { + static propTypes = { ship: React.PropTypes.object.isRequired, chartWidth: React.PropTypes.number.isRequired, code: React.PropTypes.string.isRequired diff --git a/src/app/components/DamageReceived.jsx b/src/app/components/DamageReceived.jsx index 294e4fbc..ec1799ed 100644 --- a/src/app/components/DamageReceived.jsx +++ b/src/app/components/DamageReceived.jsx @@ -45,7 +45,7 @@ export function weaponComparator(translate, propComparator, desc) { * Damage received by a selected ship */ export default class DamageReceived extends TranslatedComponent { - static PropTypes = { + static propTypes = { ship: React.PropTypes.object.isRequired, code: React.PropTypes.string.isRequired }; diff --git a/src/app/components/DefenceSummary.jsx b/src/app/components/DefenceSummary.jsx index 21896b90..ba6ac4e5 100644 --- a/src/app/components/DefenceSummary.jsx +++ b/src/app/components/DefenceSummary.jsx @@ -7,7 +7,7 @@ import { DamageKinetic, DamageThermal, DamageExplosive } from './SvgIcons'; * Defence summary */ export default class DefenceSummary extends TranslatedComponent { - static PropTypes = { + static propTypes = { ship: React.PropTypes.object.isRequired }; diff --git a/src/app/components/EngagementRange.jsx b/src/app/components/EngagementRange.jsx index adfee2cd..c09b56ec 100644 --- a/src/app/components/EngagementRange.jsx +++ b/src/app/components/EngagementRange.jsx @@ -8,7 +8,7 @@ import Slider from '../components/Slider'; * Requires an onChange() function of the form onChange(range), providing the range in metres, which is triggered on range change */ export default class Range extends TranslatedComponent { - static PropTypes = { + static propTypes = { ship: React.PropTypes.object.isRequired, onChange: React.PropTypes.func.isRequired }; diff --git a/src/app/components/EngineProfile.jsx b/src/app/components/EngineProfile.jsx index b8f874c3..0ff10850 100644 --- a/src/app/components/EngineProfile.jsx +++ b/src/app/components/EngineProfile.jsx @@ -13,7 +13,7 @@ import * as Calc from '../shipyard/Calculations'; * Engine profile for a given ship */ export default class EngineProfile extends TranslatedComponent { - static PropTypes = { + static propTypes = { ship: React.PropTypes.object.isRequired, chartWidth: React.PropTypes.number.isRequired, code: React.PropTypes.string.isRequired diff --git a/src/app/components/FSDProfile.jsx b/src/app/components/FSDProfile.jsx index 1fa92289..b5138f9c 100644 --- a/src/app/components/FSDProfile.jsx +++ b/src/app/components/FSDProfile.jsx @@ -13,7 +13,7 @@ import * as Calc from '../shipyard/Calculations'; * FSD profile for a given ship */ export default class FSDProfile extends TranslatedComponent { - static PropTypes = { + static propTypes = { ship: React.PropTypes.object.isRequired, chartWidth: React.PropTypes.number.isRequired, code: React.PropTypes.string.isRequired diff --git a/src/app/components/Fuel.jsx b/src/app/components/Fuel.jsx index eb5d98c6..c3f9cc6b 100644 --- a/src/app/components/Fuel.jsx +++ b/src/app/components/Fuel.jsx @@ -8,7 +8,7 @@ import Slider from '../components/Slider'; * Requires an onChange() function of the form onChange(fuel), providing the fuel in tonnes, which is triggered on fuel level change */ export default class Fuel extends TranslatedComponent { - static PropTypes = { + static propTypes = { ship: React.PropTypes.object.isRequired, onChange: React.PropTypes.func.isRequired }; diff --git a/src/app/components/JumpRange.jsx b/src/app/components/JumpRange.jsx index d197ff04..e8929a58 100644 --- a/src/app/components/JumpRange.jsx +++ b/src/app/components/JumpRange.jsx @@ -13,7 +13,7 @@ import * as Calc from '../shipyard/Calculations'; * Jump range for a given ship */ export default class JumpRange extends TranslatedComponent { - static PropTypes = { + static propTypes = { ship: React.PropTypes.object.isRequired, chartWidth: React.PropTypes.number.isRequired, code: React.PropTypes.string.isRequired diff --git a/src/app/components/LineChart.jsx b/src/app/components/LineChart.jsx index 41021ff2..dbd18b16 100644 --- a/src/app/components/LineChart.jsx +++ b/src/app/components/LineChart.jsx @@ -17,7 +17,7 @@ export default class LineChart extends TranslatedComponent { colors: ['#ff8c0d'] }; - static PropTypes = { + static propTypes = { width: React.PropTypes.number.isRequired, func: React.PropTypes.func.isRequired, xLabel: React.PropTypes.string.isRequired, diff --git a/src/app/components/Movement.jsx b/src/app/components/Movement.jsx index 3cf1dc8e..f61843df 100644 --- a/src/app/components/Movement.jsx +++ b/src/app/components/Movement.jsx @@ -6,7 +6,7 @@ import TranslatedComponent from './TranslatedComponent'; * Movement */ export default class Movement extends TranslatedComponent { - static PropTypes = { + static propTypes = { ship: React.PropTypes.object.isRequired, eng: React.PropTypes.number.isRequired, fuel: React.PropTypes.number.isRequired, diff --git a/src/app/components/MovementSummary.jsx b/src/app/components/MovementSummary.jsx index adc3974e..f4c268f1 100644 --- a/src/app/components/MovementSummary.jsx +++ b/src/app/components/MovementSummary.jsx @@ -6,7 +6,7 @@ import TranslatedComponent from './TranslatedComponent'; * Movement summary */ export default class MovementSummary extends TranslatedComponent { - static PropTypes = { + static propTypes = { ship: React.PropTypes.object.isRequired }; diff --git a/src/app/components/OffenceSummary.jsx b/src/app/components/OffenceSummary.jsx index 1aac8b8c..d7778a70 100644 --- a/src/app/components/OffenceSummary.jsx +++ b/src/app/components/OffenceSummary.jsx @@ -7,7 +7,7 @@ import { DamageAbsolute, DamageKinetic, DamageThermal, DamageExplosive } from '. * Offence summary */ export default class OffenceSummary extends TranslatedComponent { - static PropTypes = { + static propTypes = { ship: React.PropTypes.object.isRequired }; diff --git a/src/app/components/Pips.jsx b/src/app/components/Pips.jsx index b6ecd59a..dfc7c7ce 100644 --- a/src/app/components/Pips.jsx +++ b/src/app/components/Pips.jsx @@ -14,7 +14,7 @@ import Module from '../shipyard/Module'; * Requires an onChange() function of the form onChange(sys, eng, wep) which is triggered whenever the pips change. */ export default class Pips extends TranslatedComponent { - static PropTypes = { + static propTypes = { ship: React.PropTypes.object.isRequired, onChange: React.PropTypes.func.isRequired }; diff --git a/src/app/components/PowerManagement.jsx b/src/app/components/PowerManagement.jsx index e8a81fd0..39e78a19 100644 --- a/src/app/components/PowerManagement.jsx +++ b/src/app/components/PowerManagement.jsx @@ -17,7 +17,7 @@ const POWER = [ * Power Management Section */ export default class PowerManagement extends TranslatedComponent { - static PropTypes = { + static propTypes = { ship: React.PropTypes.object.isRequired, code: React.PropTypes.string.isRequired, onChange: React.PropTypes.func.isRequired diff --git a/src/app/components/ShipPicker.jsx b/src/app/components/ShipPicker.jsx new file mode 100644 index 00000000..49b67a2e --- /dev/null +++ b/src/app/components/ShipPicker.jsx @@ -0,0 +1,143 @@ +import React from 'react'; +import TranslatedComponent from './TranslatedComponent'; +import Ship from '../shipyard/Ship'; +import { Ships } from 'coriolis-data/dist'; +import { Rocket } from './SvgIcons'; +import Persist from '../stores/Persist'; +import cn from 'classnames'; + +/** + * Ship picker + * Requires an onChange() function of the form onChange(ship), providing the ship, which is triggered on ship change + */ +export default class ShipPicker extends TranslatedComponent { + static propTypes = { + onChange: React.PropTypes.func.isRequired, + ship: React.PropTypes.object, + build: React.PropTypes.string + }; + + static defaultProps = { + ship: new Ship('anaconda', Ships['anaconda'].properties, Ships['anaconda'].slots) + } + + /** + * Constructor + * @param {Object} props React Component properties + * @param {Object} context React Component context + */ + constructor(props, context) { + super(props); + + this.shipOrder = Object.keys(Ships).sort(); + this._toggleMenu = this._toggleMenu.bind(this); + this._closeMenu = this._closeMenu.bind(this); + + this.state = { + ship: props.ship, + build: props.build + }; + } + + /** + * Update the state if our ship changes + * @param {Object} nextProps Incoming/Next properties + * @return {boolean} Returns true if the component should be rerendered + */ + componentWillReceiveProps(nextProps) { + const { ship, build } = this.state; + const { nextShip, nextBuild } = nextProps; + + if (nextShip != undefined && nextShip != ship && nextBuild != build) { + this.setState({ ship: nextShip, build: nextBuild }); + } + return true; + } + + /** + * Update ship + * @param {object} ship the ship + */ + _shipChange(shipId, build) { + const ship = new Ship(shipId, Ships[shipId].properties, Ships[shipId].slots); + if (build) { + // Ship is a particular build + ship.buildFrom(Persist.getBuild(shipId, build)); + } + this._closeMenu(); + this.setState({ ship, build }); + this.props.onChange(ship, build); + } + + /** + * Render the menu for the picker + */ + _renderPickerMenu() { + const { ship, build } = this.state; + const _shipChange = this._shipChange; + + const builds = Persist.getBuilds(); + const buildList = []; + for (let shipId of this.shipOrder) { + const shipBuilds = []; + // Add stock build + const stockSelected = (ship.id == shipId && !build); + shipBuilds.push(
  • Stock
  • ); + if (builds[shipId]) { + let buildNameOrder = Object.keys(builds[shipId]).sort(); + for (let buildName of buildNameOrder) { + const buildSelected = ship.id == shipId && build == buildName; + shipBuilds.push(
  • {buildName}
  • ); + } + } + buildList.push(); + } + + return buildList; + } + + /** + * Toggle the menu state + */ + _toggleMenu() { + const { menuOpen } = this.state; + this.setState({ menuOpen: !menuOpen }); + } + + /** + * Close the menu + */ + _closeMenu() { + const { menuOpen } = this.state; + if (menuOpen) { + this._toggleMenu(); + } + } + + /** + * Render picker + * @return {React.Component} contents + */ + render() { + const { language, onWindowResize, sizeRatio, tooltip, termtip } = this.context; + const { formats, translate, units } = language; + const { menuOpen, ship, build } = this.state; + + const shipString = ship.name + ': ' + (build ? build : 'stock'); + return ( +
    e.stopPropagation() }> +
    +
    + {shipString} +
    + { menuOpen ? +
    e.stopPropagation() }> +
    + {this._renderPickerMenu()} +
    +
    : null } +
    +
    + ); + } +} diff --git a/src/app/components/ShipSelector.jsx b/src/app/components/ShipSelector.jsx index 34f11786..a48ba4a8 100644 --- a/src/app/components/ShipSelector.jsx +++ b/src/app/components/ShipSelector.jsx @@ -8,7 +8,7 @@ import { Rocket } from './SvgIcons'; * Selector for ships */ export default class ShipSelector extends TranslatedComponent { - static PropTypes = { + static propTypes = { initial: React.PropTypes.object.isRequired, onChange: React.PropTypes.func.isRequired }; diff --git a/src/less/app.less b/src/less/app.less index 22417f53..2af1c2f1 100755 --- a/src/less/app.less +++ b/src/less/app.less @@ -21,6 +21,7 @@ @import 'loader'; @import 'pips'; @import 'movement'; +@import 'shippicker'; html, body { height: 100%; diff --git a/src/less/shippicker.less b/src/less/shippicker.less new file mode 100755 index 00000000..11726fff --- /dev/null +++ b/src/less/shippicker.less @@ -0,0 +1,180 @@ +.shippicker { + background-color: @bgBlack; + margin: 0; + padding: 0 0 0 1em; + height: 3em; + line-height: 3em; + font-family: @fTitle; + vertical-align: middle; + position: relative; + + .user-select-none(); + + .menu { + position: relative; + z-index: 1; + cursor: default; + + &.r { + .menu-list { + right: 0; + } + } + + .smallTablet({ + position: static; + position: initial; + }); + } + + .menu-header { + height: 100%; + z-index: 2; + padding : 0 1em; + cursor: pointer; + color: @warning; + text-transform: uppercase; + // Less than 600px screen width: hide text + + &.disabled { + color: @warning-disabled; + cursor: default; + } + + &.selected { + background-color: @bgBlack; + } + + .menu-item-label { + margin-left: 1em; + display: inline-block; + + .smallTablet({ + display: none; + }); + } + } + + .menu-list { + font-family: @fStandard; + position: absolute; + padding: 0.5em 1em; + box-sizing: border-box; + min-width: 100%; + overflow-x: hidden; + background-color: @bgBlack; + font-size: 0.9em; + overflow-y: auto; + z-index: 0; + -webkit-overflow-scrolling: touch; + max-height: 500px; + + &::-webkit-scrollbar { + width: 0.5em; + } + + &::-webkit-scrollbar-track { + background-color: transparent; + } + + &::-webkit-scrollbar-thumb { + background-color: @warning-disabled; + } + + input { + border: none; + background-color: transparent; + text-align: right; + font-size: 1em; + font-family: @fStandard; + } + + .smallTablet({ + max-height: 400px; + left: 0; + right: 0; + border-bottom: 1px solid @bg; + }); + + .tablet({ + li, a { + padding: 0.3em 0; + } + }); + } + + .quad { + -webkit-column-count: 4; /* Chrome, Safari, Opera */ + -moz-column-count: 4; /* Firefox */ + column-count: 4; + ul { + min-width: 10em; + } + + .smallTablet({ + -webkit-column-count: 3; /* Chrome, Safari, Opera */ + -moz-column-count: 3; /* Firefox */ + column-count: 3; + + ul { + min-width: 20em; + } + }); + + .largePhone({ + -webkit-column-count: 2; /* Chrome, Safari, Opera */ + -moz-column-count: 2; /* Firefox */ + column-count: 2; + }); + + .smallPhone({ + -webkit-column-count: 1; /* Chrome, Safari, Opera */ + -moz-column-count: 1; /* Firefox */ + column-count: 1; + }); + } + + ul { + display: inline-block; + white-space: nowrap; + margin: 0 0 0.5em; + padding: 0; + line-height: 1.3em; + color: @fg; + } + + li { + white-space: normal; + list-style: none; + margin-left: 1em; + line-height: 1.1em; + color: @warning; + cursor: pointer; + + &.selected { + color: @primary; + } + } + + hr { + border: none; + border-top: 1px solid @disabled; + } + + .no-wrap { + overflow-x: auto; + white-space: nowrap; + } + + .block { + display: block; + line-height: 1.5em; + } + + .title { + font-size: 1.3em; + display: inline-block; + margin:0px; + text-transform: uppercase; + } +}