Make outfitting page working

This commit is contained in:
Felix Linker
2020-04-10 13:19:53 +02:00
parent 00c525e6ab
commit 409be7374c
13 changed files with 511 additions and 838 deletions

View File

@@ -123,6 +123,7 @@
"sideEffects": false,
"dependencies": {
"@babel/polyfill": "^7.0.0",
"auto-bind": "^2.1.1",
"browserify-zlib-next": "^1.0.1",
"classnames": "^2.2.6",
"coriolis-data": "../coriolis-data",

View File

@@ -24,7 +24,6 @@ export default class AvailableModulesMenu extends TranslatedComponent {
onSelect: PropTypes.func.isRequired,
diffDetails: PropTypes.func,
m: PropTypes.object,
ship: PropTypes.object.isRequired,
warning: PropTypes.func,
slotDiv: PropTypes.object
};
@@ -49,7 +48,7 @@ export default class AvailableModulesMenu extends TranslatedComponent {
*/
_initState(props, context) {
const { translate } = context.language;
const { m, warning, onSelect, ship } = props;
const { m } = props;
const list = [], fuzzy = [];
let currentGroup;
@@ -63,13 +62,7 @@ export default class AvailableModulesMenu extends TranslatedComponent {
list.push(
<div key={'div-' + category} className="select-group cap">{catName}</div>,
this._buildGroup(
ship,
m,
warning,
(m, event) => {
this._hideDiff(event);
onSelect(m);
},
category,
infos,
),
@@ -91,15 +84,14 @@ export default class AvailableModulesMenu extends TranslatedComponent {
/**
* Generate React Components for Module Group
* @param {Ship} ship Ship the selection is for
* @param {Object} mountedModule Mounted Module
* @param {Function} warningFunc Warning function
* @param {function} onSelect Select/Mount callback
* @param {String} category Category key
* @param {Array} modules Available modules
* @return {React.Component} Available Module Group contents
*/
_buildGroup(ship, mountedModule, warningFunc, onSelect, category, modules) {
_buildGroup(mountedModule, category, modules) {
const { warning } = this.props;
const ship = mountedModule.getShip();
const classMapping = groupBy(modules, (info) => info.meta.class);
const itemsPerClass = Math.max(
@@ -133,7 +125,10 @@ export default class AvailableModulesMenu extends TranslatedComponent {
let eventHandlers = {};
if (!disabled) {
const showDiff = this._showDiff.bind(this, mountedModule, info);
const select = onSelect.bind(null, info);
const select = (event) => {
this._hideDiff(event);
this.props.onSelect(Item);
};
eventHandlers = {
onMouseEnter: this._over.bind(this, showDiff),
@@ -149,7 +144,7 @@ export default class AvailableModulesMenu extends TranslatedComponent {
<li key={Item} data-id={Item}
ref={Item === mountedModule.getItem() ? (ref) => { this.activeSlotRef = ref; } : undefined}
className={cn('c', {
warning: !disabled && warningFunc && warningFunc(info),
warning: !disabled && warning && warning(info),
active: mountedModule.getItem() === Item,
disabled,
hardpoint: mountSymbol,
@@ -187,10 +182,11 @@ export default class AvailableModulesMenu extends TranslatedComponent {
_showDiff(mountedModule, hoveringModule, rect) {
if (this.props.diffDetails) {
this.touchTimeout = null;
this.context.tooltip(
this.props.diffDetails(hoveringModule, mountedModule),
rect,
);
// TODO:
// this.context.tooltip(
// this.props.diffDetails(hoveringModule, mountedModule),
// rect,
// );
}
}

View File

@@ -3,40 +3,27 @@ import SlotSection from './SlotSection';
import Slot from './Slot';
import { MountFixed, MountGimballed, MountTurret } from '../components/SvgIcons';
import { stopCtxPropagation } from '../utils/UtilityFunctions';
import autoBind from 'auto-bind';
/**
* Hardpoint slot section
*/
export default class HardpointSlotSection extends SlotSection {
/**
* Constructor
* @param {Object} props React Component properties
* @param {Object} context React Component context
*/
constructor(props, context) {
super(props, context, 'hardpoints', 'hardpoints');
this._empty = this._empty.bind(this);
this.selectedRefId = null;
this.firstRefId = 'emptyall';
this.lastRefId = 'nl-F';
}
/**
* Handle focus when component updates
* @param {Object} prevProps React Component properties
*/
componentDidUpdate(prevProps) {
this._handleSectionFocus(prevProps,this.firstRefId, this.lastRefId);
constructor(props) {
super(props, 'hardpoints');
autoBind(this);
}
/**
* Empty all slots
*/
_empty() {
this.selectedRefId = 'emptyall';
this.props.ship.emptyWeapons();
this.props.onChange();
// TODO:
// this.props.ship.emptyWeapons();
this._close();
}
@@ -47,9 +34,8 @@ export default class HardpointSlotSection extends SlotSection {
* @param {SyntheticEvent} event Event
*/
_fill(group, mount, event) {
this.selectedRefId = group + '-' + mount;
this.props.ship.useWeapon(group, mount, null, event.getModifierState('Alt'));
this.props.onChange();
// TODO:
// this.props.ship.useWeapon(group, mount, null, event.getModifierState('Alt'));
this._close();
}
@@ -73,16 +59,12 @@ export default class HardpointSlotSection extends SlotSection {
slots.push(<Slot
key={h.object.Slot}
maxClass={h.getSize()}
onOpen={this._openMenu.bind(this, h)}
onSelect={this._selectModule.bind(this, h)}
onChange={this.props.onChange}
selected={currentMenu == h}
currentMenu={currentMenu}
drag={this._drag.bind(this, h)}
dragOver={this._dragOverSlot.bind(this, h)}
drop={this._drop}
dropClass={this._dropClass(h, originSlot, targetSlot)}
ship={ship}
slot={h}
m={h}
enabled={h.enabled ? true : false}
/>);
}
@@ -95,66 +77,67 @@ export default class HardpointSlotSection extends SlotSection {
* @param {Function} translate Translate function
* @return {React.Component} Section menu
*/
_getSectionMenu(translate) {
_getSectionMenu() {
const { translate } = this.context.language;
let _fill = this._fill;
return <div className='select hardpoint' onClick={(e) => e.stopPropagation()} onContextMenu={stopCtxPropagation}>
<ul>
<li className='lc' tabIndex='0' onClick={this._empty} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['emptyall'] = smRef}>{translate('empty all')}</li>
<li className='lc' tabIndex="0" onClick={this._empty} ref={smRef => this.sectionRefArr['emptyall'] = smRef}>{translate('empty all')}</li>
<li className='optional-hide' style={{ textAlign: 'center', marginTop: '1em' }}>{translate('PHRASE_ALT_ALL')}</li>
</ul>
<div className='select-group cap'>{translate('pl')}</div>
<ul>
<li className='c' tabIndex='0' onClick={_fill.bind(this, 'pl', 'F')} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['pl-F'] = smRef}><MountFixed className='lg'/></li>
<li className='c' tabIndex='0' onClick={_fill.bind(this, 'pl', 'G')} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['pl-G'] = smRef}><MountGimballed className='lg'/></li>
<li className='c' tabIndex='0' onClick={_fill.bind(this, 'pl', 'T')} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['pl-T'] = smRef}><MountTurret className='lg'/></li>
<li className="c" tabIndex="0" onClick={_fill.bind(this, 'pl', 'F')}><MountFixed className='lg'/></li>
<li className="c" tabIndex="0" onClick={_fill.bind(this, 'pl', 'G')}><MountGimballed className='lg'/></li>
<li className="c" tabIndex="0" onClick={_fill.bind(this, 'pl', 'T')}><MountTurret className='lg'/></li>
</ul>
<div className='select-group cap'>{translate('ul')}</div>
<ul>
<li className='c' tabIndex='0' onClick={_fill.bind(this, 'ul', 'F')} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['ul-F'] = smRef}><MountFixed className='lg'/></li>
<li className='c' tabIndex='0' onClick={_fill.bind(this, 'ul', 'G')} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['ul-G'] = smRef}><MountGimballed className='lg'/></li>
<li className='c' tabIndex='0' onClick={_fill.bind(this, 'ul', 'T')} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['ul-T'] = smRef}><MountTurret className='lg'/></li>
<li className="c" tabIndex="0" onClick={_fill.bind(this, 'ul', 'F')}><MountFixed className='lg'/></li>
<li className="c" tabIndex="0" onClick={_fill.bind(this, 'ul', 'G')}><MountGimballed className='lg'/></li>
<li className="c" tabIndex="0" onClick={_fill.bind(this, 'ul', 'T')}><MountTurret className='lg'/></li>
</ul>
<div className='select-group cap'>{translate('bl')}</div>
<ul>
<li className='c' tabIndex='0' onClick={_fill.bind(this, 'bl', 'F')} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['bl-F'] = smRef}><MountFixed className='lg'/></li>
<li className='c' tabIndex='0' onClick={_fill.bind(this, 'bl', 'G')} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['bl-G'] = smRef}><MountGimballed className='lg'/></li>
<li className='c' tabIndex='0' onClick={_fill.bind(this, 'bl', 'T')} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['bl-T'] = smRef}><MountTurret className='lg'/></li>
<li className="c" tabIndex="0" onClick={_fill.bind(this, 'bl', 'F')}><MountFixed className='lg'/></li>
<li className="c" tabIndex="0" onClick={_fill.bind(this, 'bl', 'G')}><MountGimballed className='lg'/></li>
<li className="c" tabIndex="0" onClick={_fill.bind(this, 'bl', 'T')}><MountTurret className='lg'/></li>
</ul>
<div className='select-group cap'>{translate('mc')}</div>
<ul>
<li className='c' tabIndex='0' onClick={_fill.bind(this, 'mc', 'F')} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['mc-F'] = smRef}><MountFixed className='lg'/></li>
<li className='c' tabIndex='0' onClick={_fill.bind(this, 'mc', 'G')} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['mc-G'] = smRef}><MountGimballed className='lg'/></li>
<li className='c' tabIndex='0' onClick={_fill.bind(this, 'mc', 'T')} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['mc-T'] = smRef}><MountTurret className='lg'/></li>
<li className="c" tabIndex="0" onClick={_fill.bind(this, 'mc', 'F')}><MountFixed className='lg'/></li>
<li className="c" tabIndex="0" onClick={_fill.bind(this, 'mc', 'G')}><MountGimballed className='lg'/></li>
<li className="c" tabIndex="0" onClick={_fill.bind(this, 'mc', 'T')}><MountTurret className='lg'/></li>
</ul>
<div className='select-group cap'>{translate('c')}</div>
<ul>
<li className='c' tabIndex='0' onClick={_fill.bind(this, 'c', 'F')} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['c-F'] = smRef}><MountFixed className='lg'/></li>
<li className='c' tabIndex='0' onClick={_fill.bind(this, 'c', 'G')} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['c-G'] = smRef}><MountGimballed className='lg'/></li>
<li className='c' tabIndex='0' onClick={_fill.bind(this, 'c', 'T')} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['c-T'] = smRef}><MountTurret className='lg'/></li>
<li className="c" tabIndex="0" onClick={_fill.bind(this, 'c', 'F')}><MountFixed className='lg'/></li>
<li className="c" tabIndex="0" onClick={_fill.bind(this, 'c', 'G')}><MountGimballed className='lg'/></li>
<li className="c" tabIndex="0" onClick={_fill.bind(this, 'c', 'T')}><MountTurret className='lg'/></li>
</ul>
<div className='select-group cap'>{translate('fc')}</div>
<ul>
<li className='c' tabIndex='0' onClick={_fill.bind(this, 'fc', 'F')} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['fc-F'] = smRef}><MountFixed className='lg'/></li>
<li className='c' tabIndex='0' onClick={_fill.bind(this, 'fc', 'G')} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['fc-G'] = smRef}><MountGimballed className='lg'/></li>
<li className='c' tabIndex='0' onClick={_fill.bind(this, 'fc', 'T')} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['fc-T'] = smRef}><MountTurret className='lg'/></li>
<li className="c" tabIndex="0" onClick={_fill.bind(this, 'fc', 'F')}><MountFixed className='lg'/></li>
<li className="c" tabIndex="0" onClick={_fill.bind(this, 'fc', 'G')}><MountGimballed className='lg'/></li>
<li className="c" tabIndex="0" onClick={_fill.bind(this, 'fc', 'T')}><MountTurret className='lg'/></li>
</ul>
<div className='select-group cap'>{translate('pa')}</div>
<ul>
<li className='lc' tabIndex='0' onClick={_fill.bind(this, 'pa', 'F')} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['pa-F'] = smRef}>{translate('pa')}</li>
<li className='lc' tabIndex="0" onClick={_fill.bind(this, 'pa', 'F')}>{translate('pa')}</li>
</ul>
<div className='select-group cap'>{translate('rg')}</div>
<ul>
<li className='lc' tabIndex='0' onClick={_fill.bind(this, 'rg', 'F')} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['rg-F'] = smRef}>{translate('rg')}</li>
<li className='lc' tabIndex="0" onClick={_fill.bind(this, 'rg', 'F')}>{translate('rg')}</li>
</ul>
<div className='select-group cap'>{translate('nl')}</div>
<ul>
<li className='lc' tabIndex='0' onClick={_fill.bind(this, 'nl', 'F')} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['nl-F'] = smRef}>{translate('nl')}</li>
<li className='lc' tabIndex="0" onClick={_fill.bind(this, 'nl', 'F')}>{translate('nl')}</li>
</ul>
<div className='select-group cap'>{translate('rfl')}</div>
<ul>
<li className='c' tabIndex='0' onClick={_fill.bind(this, 'rfl', 'F')} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['rfl-F'] = smRef}><MountFixed className='lg'/></li>
<li className='c' tabIndex='0' onClick={_fill.bind(this, 'rfl', 'T')} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['rfl-T'] = smRef}><MountTurret className='lg'/></li>
<li className="c" tabIndex="0" onClick={_fill.bind(this, 'rfl', 'F')}><MountFixed className='lg'/></li>
<li className="c" tabIndex="0" onClick={_fill.bind(this, 'rfl', 'T')}><MountTurret className='lg'/></li>
</ul>
</div>;
}

View File

@@ -4,6 +4,7 @@ import Slot from './Slot';
import * as ModuleUtils from '../shipyard/ModuleUtils';
import { stopCtxPropagation } from '../utils/UtilityFunctions';
import { canMount } from '../utils/SlotFunctions';
import autoBind from 'auto-bind';
/**
* Internal slot section
@@ -12,40 +13,18 @@ export default class InternalSlotSection extends SlotSection {
/**
* Constructor
* @param {Object} props React Component properties
* @param {Object} context React Component context
*/
constructor(props, context) {
super(props, context, 'internal', 'optional internal');
this._empty = this._empty.bind(this);
this._fillWithCargo = this._fillWithCargo.bind(this);
this._fillWithCells = this._fillWithCells.bind(this);
this._fillWithArmor = this._fillWithArmor.bind(this);
this._fillWithModuleReinforcementPackages = this._fillWithModuleReinforcementPackages.bind(this);
this._fillWithFuelTanks = this._fillWithFuelTanks.bind(this);
this._fillWithLuxuryCabins = this._fillWithLuxuryCabins.bind(this);
this._fillWithFirstClassCabins = this._fillWithFirstClassCabins.bind(this);
this._fillWithBusinessClassCabins = this._fillWithBusinessClassCabins.bind(this);
this._fillWithEconomyClassCabins = this._fillWithEconomyClassCabins.bind(this);
this.selectedRefId = null;
this.firstRefId = 'emptyall';
this.lastRefId = this.sectionRefArr['pcq'] ? 'pcq' : 'pcm';
}
/**
* Handle focus when component updates
* @param {Object} prevProps React Component properties
*/
componentDidUpdate(prevProps) {
this._handleSectionFocus(prevProps,this.firstRefId, this.lastRefId);
constructor(props) {
super(props, 'optional internal');
autoBind(this);
}
/**
* Empty all slots
*/
_empty() {
this.selectedRefId = 'emptyall';
this.props.ship.emptyInternal();
this.props.onChange();
// TODO:
// this.props.ship.emptyInternal();
this._close();
}
@@ -54,7 +33,6 @@ export default class InternalSlotSection extends SlotSection {
* @param {SyntheticEvent} event Event
*/
_fillWithCargo(event) {
this.selectedRefId = 'cargo';
let clobber = event.getModifierState('Alt');
let ship = this.props.ship;
ship.internal.forEach((slot) => {
@@ -62,7 +40,6 @@ export default class InternalSlotSection extends SlotSection {
ship.use(slot, ModuleUtils.findInternal('cr', slot.maxClass, 'E'));
}
});
this.props.onChange();
this._close();
}
@@ -71,7 +48,6 @@ export default class InternalSlotSection extends SlotSection {
* @param {SyntheticEvent} event Event
*/
_fillWithFuelTanks(event) {
this.selectedRefId = 'ft';
let clobber = event.getModifierState('Alt');
let ship = this.props.ship;
ship.internal.forEach((slot) => {
@@ -79,7 +55,6 @@ export default class InternalSlotSection extends SlotSection {
ship.use(slot, ModuleUtils.findInternal('ft', slot.maxClass, 'C'));
}
});
this.props.onChange();
this._close();
}
@@ -88,7 +63,6 @@ export default class InternalSlotSection extends SlotSection {
* @param {SyntheticEvent} event Event
*/
_fillWithLuxuryCabins(event) {
this.selectedRefId = 'pcq';
let clobber = event.getModifierState('Alt');
let ship = this.props.ship;
ship.internal.forEach((slot) => {
@@ -96,7 +70,6 @@ export default class InternalSlotSection extends SlotSection {
ship.use(slot, ModuleUtils.findInternal('pcq', Math.min(slot.maxClass, 6), 'B')); // Passenger cabins top out at 6
}
});
this.props.onChange();
this._close();
}
@@ -105,7 +78,6 @@ export default class InternalSlotSection extends SlotSection {
* @param {SyntheticEvent} event Event
*/
_fillWithFirstClassCabins(event) {
this.selectedRefId = 'pcm';
let clobber = event.getModifierState('Alt');
let ship = this.props.ship;
ship.internal.forEach((slot) => {
@@ -113,7 +85,6 @@ export default class InternalSlotSection extends SlotSection {
ship.use(slot, ModuleUtils.findInternal('pcm', Math.min(slot.maxClass, 6), 'C')); // Passenger cabins top out at 6
}
});
this.props.onChange();
this._close();
}
@@ -122,7 +93,6 @@ export default class InternalSlotSection extends SlotSection {
* @param {SyntheticEvent} event Event
*/
_fillWithBusinessClassCabins(event) {
this.selectedRefId = 'pci';
let clobber = event.getModifierState('Alt');
let ship = this.props.ship;
ship.internal.forEach((slot) => {
@@ -130,7 +100,6 @@ export default class InternalSlotSection extends SlotSection {
ship.use(slot, ModuleUtils.findInternal('pci', Math.min(slot.maxClass, 6), 'D')); // Passenger cabins top out at 6
}
});
this.props.onChange();
this._close();
}
@@ -139,7 +108,6 @@ export default class InternalSlotSection extends SlotSection {
* @param {SyntheticEvent} event Event
*/
_fillWithEconomyClassCabins(event) {
this.selectedRefId = 'pce';
let clobber = event.getModifierState('Alt');
let ship = this.props.ship;
ship.internal.forEach((slot) => {
@@ -147,7 +115,6 @@ export default class InternalSlotSection extends SlotSection {
ship.use(slot, ModuleUtils.findInternal('pce', Math.min(slot.maxClass, 6), 'E')); // Passenger cabins top out at 6
}
});
this.props.onChange();
this._close();
}
@@ -156,7 +123,6 @@ export default class InternalSlotSection extends SlotSection {
* @param {SyntheticEvent} event Event
*/
_fillWithCells(event) {
this.selectedRefId = 'scb';
let clobber = event.getModifierState('Alt');
let ship = this.props.ship;
let chargeCap = 0; // Capacity of single activation
@@ -167,7 +133,6 @@ export default class InternalSlotSection extends SlotSection {
chargeCap += slot.m.recharge;
}
});
this.props.onChange();
this._close();
}
@@ -176,7 +141,6 @@ export default class InternalSlotSection extends SlotSection {
* @param {SyntheticEvent} event Event
*/
_fillWithArmor(event) {
this.selectedRefId = 'hr';
let clobber = event.getModifierState('Alt');
let ship = this.props.ship;
ship.internal.forEach((slot) => {
@@ -184,7 +148,6 @@ export default class InternalSlotSection extends SlotSection {
ship.use(slot, ModuleUtils.findInternal('hr', Math.min(slot.maxClass, 5), 'D')); // Hull reinforcements top out at 5D
}
});
this.props.onChange();
this._close();
}
@@ -193,7 +156,6 @@ export default class InternalSlotSection extends SlotSection {
* @param {SyntheticEvent} event Event
*/
_fillWithModuleReinforcementPackages(event) {
this.selectedRefId = 'mrp';
let clobber = event.getModifierState('Alt');
let ship = this.props.ship;
ship.internal.forEach((slot) => {
@@ -201,7 +163,6 @@ export default class InternalSlotSection extends SlotSection {
ship.use(slot, ModuleUtils.findInternal('mrp', Math.min(slot.maxClass, 5), 'D')); // Module reinforcements top out at 5D
}
});
this.props.onChange();
this._close();
}
@@ -220,24 +181,16 @@ export default class InternalSlotSection extends SlotSection {
let slots = [];
let { currentMenu, ship } = this.props;
let { originSlot, targetSlot } = this.state;
let { fuelCapacity } = ship;
for (const slot of ship.getInternals(undefined, true)) {
for (const m of ship.getInternals(undefined, true)) {
slots.push(<Slot
key={slot.object.Slot}
maxClass={slot.getSize()}
onOpen={this._openMenu.bind(this, slot)}
onChange={this.props.onChange}
onSelect={this._selectModule.bind(this, slot)}
selected={currentMenu == slot}
slot={slot}
drag={this._drag.bind(this, slot)}
dragOver={this._dragOverSlot.bind(this, slot)}
key={m.object.Slot}
currentMenu={currentMenu}
m={m}
drag={this._drag.bind(this, m)}
dragOver={this._dragOverSlot.bind(this, m)}
drop={this._drop}
dropClass={this._dropClass(slot, originSlot, targetSlot)}
fuel={fuelCapacity}
ship={ship}
enabled={slot.isEnabled()}
dropClass={this._dropClass(m, originSlot, targetSlot)}
/>);
}
@@ -250,19 +203,21 @@ export default class InternalSlotSection extends SlotSection {
* @param {Function} ship The ship
* @return {React.Component} Section menu
*/
_getSectionMenu(translate, ship) {
_getSectionMenu() {
const { ship } = this.props;
const { translate } = this.context.language;
return <div className='select' onClick={e => e.stopPropagation()} onContextMenu={stopCtxPropagation}>
<ul>
<li className='lc' tabIndex='0' onClick={this._empty} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['emptyall'] = smRef}>{translate('empty all')}</li>
<li className='lc' tabIndex='0' onClick={this._fillWithCargo} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['cargo'] = smRef}>{translate('cargo')}</li>
<li className='lc' tabIndex='0' onClick={this._fillWithCells} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['scb'] = smRef}>{translate('scb')}</li>
<li className='lc' tabIndex='0' onClick={this._fillWithArmor} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['hr'] = smRef}>{translate('hr')}</li>
<li className='lc' tabIndex='0' onClick={this._fillWithModuleReinforcementPackages} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['mrp'] = smRef}>{translate('mrp')}</li>
<li className='lc' tabIndex='0' onClick={this._fillWithFuelTanks} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['ft'] = smRef}>{translate('ft')}</li>
<li className='lc' tabIndex='0' onClick={this._fillWithEconomyClassCabins} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['pce'] = smRef}>{translate('pce')}</li>
<li className='lc' tabIndex='0' onClick={this._fillWithBusinessClassCabins} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['pci'] = smRef}>{translate('pci')}</li>
<li className='lc' tabIndex='0' onClick={this._fillWithFirstClassCabins} onKeyDown={ship.luxuryCabins ? '' : this._keyDown} ref={smRef => this.sectionRefArr['pcm'] = smRef}>{translate('pcm')}</li>
{ ship.luxuryCabins ? <li className='lc' tabIndex='0' onClick={this._fillWithLuxuryCabins} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['pcq'] = smRef}>{translate('pcq')}</li> : ''}
<li className='lc' tabIndex='0' onClick={this._empty}>{translate('empty all')}</li>
<li className='lc' tabIndex='0' onClick={this._fillWithCargo}>{translate('cargo')}</li>
<li className='lc' tabIndex='0' onClick={this._fillWithCells}>{translate('scb')}</li>
<li className='lc' tabIndex='0' onClick={this._fillWithArmor}>{translate('hr')}</li>
<li className='lc' tabIndex='0' onClick={this._fillWithModuleReinforcementPackages}>{translate('mrp')}</li>
<li className='lc' tabIndex='0' onClick={this._fillWithFuelTanks}>{translate('ft')}</li>
<li className='lc' tabIndex='0' onClick={this._fillWithEconomyClassCabins}>{translate('pce')}</li>
<li className='lc' tabIndex='0' onClick={this._fillWithBusinessClassCabins}>{translate('pci')}</li>
<li className='lc' tabIndex='0' onClick={this._fillWithFirstClassCabins} onKeyDown={ship.luxuryCabins ? '' : this._keyDown}>{translate('pcm')}</li>
{ ship.luxuryCabins ? <li className='lc' tabIndex='0' onClick={this._fillWithLuxuryCabins}>{translate('pcq')}</li> : ''}
<li className='optional-hide' style={{ textAlign: 'center', marginTop: '1em' }}>{translate('PHRASE_ALT_ALL')}</li>
</ul>
</div>;

View File

@@ -12,33 +12,28 @@ export default class Modification extends TranslatedComponent {
static propTypes = {
m: PropTypes.instanceOf(Module).isRequired,
property: PropTypes.string.isRequired,
onChange: PropTypes.func.isRequired,
highlight: PropTypes.bool,
};
/**
* Constructor
* @param {Object} props React Component properties
* @param {Object} context React Component context
*/
constructor(props, context) {
constructor(props) {
super(props);
const { m, property } = props;
const originalValue = m.get(property);
this.state = { originalValue, value: String(originalValue) };
this.state = {};
}
/**
* Notify listeners that a new value has been entered and commited.
*/
_updateFinished() {
const { m, property } = this.props;
const { value, originalValue } = this.state;
const numValue = Number(value);
if (!isNaN(numValue) && originalValue !== numValue) {
const { m, property, value } = this.props;
const { inputValue } = this.state;
const numValue = Number(inputValue);
if (!isNaN(numValue) && value !== numValue) {
m.set(property, numValue);
this.props.onChange();
this.setState({ originalValue: numValue });
this.setState({ inputValue: undefined });
}
}
@@ -48,13 +43,13 @@ export default class Modification extends TranslatedComponent {
*/
render() {
const { translate, formats } = this.context.language;
const { m, property, highlight } = this.props;
const { originalValue, value } = this.state;
const { m, property, highlight, value } = this.props;
const { inputValue } = this.state;
// Some features only apply to specific modules; these features will be
// undefined on items that do not belong to the same class. Filter these
// features here
if (originalValue === undefined) {
if (value === undefined) {
return null;
}
@@ -69,7 +64,7 @@ export default class Modification extends TranslatedComponent {
<tr>
<td className="input-container">
<span>
<NumberEditor value={value} stepModifier={1}
<NumberEditor value={inputValue || value} stepModifier={1}
decimals={2} step={0.01} style={{ textAlign: 'right' }}
className={cn(
'cb',
@@ -81,9 +76,9 @@ export default class Modification extends TranslatedComponent {
event.stopPropagation();
}
}}
onValueChange={(value) => {
if (value.length <= 15) {
this.setState({ value });
onValueChange={(inputValue) => {
if (inputValue.length <= 15) {
this.setState({ inputValue });
}
}} />
{/* TODO: support unit */}

View File

@@ -18,10 +18,7 @@ import { getModuleInfo } from 'ed-forge/lib/data/items';
export default class ModificationsMenu extends TranslatedComponent {
static propTypes = {
className: PropTypes.string,
ship: PropTypes.object.isRequired,
m: PropTypes.object.isRequired,
onChange: PropTypes.func.isRequired,
modButton:PropTypes.object
};
/**
@@ -47,13 +44,11 @@ export default class ModificationsMenu extends TranslatedComponent {
/**
* Render the blueprints
* @param {Object} props React component properties
* @param {Object} context React component context
* @return {Object} list: Array of React Components
*/
_renderBlueprints(props, context) {
const { m } = props;
const { language, tooltip, termtip } = context;
_renderBlueprints() {
const { m } = this.props;
const { language, tooltip, termtip } = this.context;
const { translate } = language;
const blueprints = [];
@@ -71,13 +66,13 @@ export default class ModificationsMenu extends TranslatedComponent {
style={{ width: '2em' }}
// onMouseOver={termtip.bind(null, tooltipContent)}
// onMouseOut={tooltip.bind(null, null)}
onClick={this._change(() => {
onClick={() => {
m.setBlueprint(blueprint, grade);
this.setState({
blueprintMenuOpened: false,
specialMenuOpened: true,
});
})}
}}
ref={active ? (ref) => { this.selectedModRef = ref; } : undefined}
>{grade}</li>
);
@@ -102,9 +97,9 @@ export default class ModificationsMenu extends TranslatedComponent {
* @param {Object} context React component context
* @return {Object} list: Array of React Components
*/
_renderSpecials(props, context) {
const { m } = props;
const { language, tooltip, termtip } = context;
_renderSpecials() {
const { m } = this.props;
const { language, tooltip, termtip } = this.context;
const translate = language.translate;
const applied = m.getExperimental();
@@ -150,8 +145,7 @@ export default class ModificationsMenu extends TranslatedComponent {
_mkModification(property, highlight) {
const { m } = this.props;
return <Modification key={property} highlight={highlight} m={m}
property={property} onChange={this._change()}
/>;
property={property} value={m.get(property)} />;
}
/**
@@ -182,22 +176,6 @@ export default class ModificationsMenu extends TranslatedComponent {
this.setState({ blueprintMenuOpened: !this.state.blueprintMenuOpened });
}
/**
* Returns a callback that performs an action in form of a callback given as
* arguments and notifiers listeners.
* @param {function} cb Action to perform
* @returns {function} Change callback
*/
_change(cb) {
return (...args) => {
this.context.tooltip(null);
if (cb) {
cb(...args);
}
this.props.onChange();
};
}
/**
* Toggle the specials menu
*/
@@ -211,11 +189,11 @@ export default class ModificationsMenu extends TranslatedComponent {
* @returns {function} Callback
*/
_specialSelected(special) {
return this._change(() => {
return () => {
const { m } = this.props;
m.setExperimental(special);
this.setState({ specialMenuOpened: false });
});
};
}
/**
@@ -232,14 +210,6 @@ export default class ModificationsMenu extends TranslatedComponent {
return;
}
}
/**
* set focus to the modification menu icon after mod menu is unmounted.
*/
componentWillUnmount() {
if (this.props.modButton) {
this.props.modButton.focus();
}
}
/**
* Render the list
@@ -259,10 +229,10 @@ export default class ModificationsMenu extends TranslatedComponent {
let renderComponents = [];
switch (true) {
case !appliedBlueprint || blueprintMenuOpened:
renderComponents = this._renderBlueprints(this.props, this.context);
renderComponents = this._renderBlueprints();
break;
case specialMenuOpened:
renderComponents = this._renderSpecials(this.props, this.context);
renderComponents = this._renderSpecials();
break;
default:
// Since the first case didn't apply, there is a blueprint applied so
@@ -301,12 +271,12 @@ export default class ModificationsMenu extends TranslatedComponent {
<div
className="section-menu button-inline-menu warning"
style={{ cursor: 'pointer' }}
onClick={this._change(() => {
onClick={() => {
m.resetEngineering();
this.selectedModRef = null;
this.selectedSpecialRef = null;
this.setState({ blueprintProgress: undefined });
})}
}}
onMouseOver={termtip.bind(null, 'PHRASE_BLUEPRINT_RESET')}
onMouseOut={tooltip.bind(null, null)}
>{translate('reset')}</div>,
@@ -324,10 +294,10 @@ export default class ModificationsMenu extends TranslatedComponent {
'section-menu button-inline-menu',
{ active: blueprintProgress === 0 },
)} style={{ cursor: 'pointer' }}
onClick={this._change(() => {
onClick={() => {
m.setBlueprintProgress(0);
this.setState({ blueprintProgress: 0 });
})}
}}
onMouseOver={termtip.bind(null, 'PHRASE_BLUEPRINT_WORST')}
onMouseOut={tooltip.bind(null, null)}
>{translate('0%')}</td>
@@ -336,10 +306,10 @@ export default class ModificationsMenu extends TranslatedComponent {
'section-menu button-inline-menu',
{ active: blueprintProgress === 0.5 },
)} style={{ cursor: 'pointer' }}
onClick={this._change(() => {
onClick={() => {
m.setBlueprintProgress(0.5);
this.setState({ blueprintProgress: 0.5 });
})}
}}
onMouseOver={termtip.bind(null, 'PHRASE_BLUEPRINT_FIFTY')}
onMouseOut={tooltip.bind(null, null)}
>{translate('50%')}</td>
@@ -349,10 +319,10 @@ export default class ModificationsMenu extends TranslatedComponent {
{ active: blueprintProgress === 1 },
)}
style={{ cursor: 'pointer' }}
onClick={this._change(() => {
onClick={() => {
m.setBlueprintProgress(1);
this.setState({ blueprintProgress: 1 });
})}
}}
onMouseOver={termtip.bind(null, 'PHRASE_BLUEPRINT_BEST')}
onMouseOut={tooltip.bind(null, null)}
>{translate('100%')}</td>
@@ -362,11 +332,11 @@ export default class ModificationsMenu extends TranslatedComponent {
{ active: blueprintProgress % 0.5 !== 0 },
)}
style={{ cursor: 'pointer' }}
onClick={this._change(() => {
onClick={() => {
const blueprintProgress = Math.random();
m.setBlueprintProgress(blueprintProgress);
this.setState({ blueprintProgress });
})}
}}
onMouseOver={termtip.bind(null, 'PHRASE_BLUEPRINT_RANDOM')}
onMouseOut={tooltip.bind(null, null)}
>{translate('random')}</td>

View File

@@ -1,24 +1,24 @@
import autoBind from 'auto-bind';
import React from 'react';
import PropTypes from 'prop-types';
import TranslatedComponent from './TranslatedComponent';
import cn from 'classnames';
import { Warning } from './SvgIcons';
import * as Calc from '../shipyard/Calculations';
import { ShipProps } from 'ed-forge';
const {
SPEED, JUMP_RANGE, TOTAL_RANGE, SHIELD_METRICS, ARMOUR_METRICS, MODULE_ARMOUR,
MODULE_PROTECTION
MAX_SPEED, BOOST_SPEED, DAMAGE_METRICS, JUMP_METRICS, SHIELD_METRICS,
ARMOUR_METRICS, CARGO_CAPACITY, FUEL_CAPACITY, UNLADEN_MASS, MAXIMUM_MASS,
MODULE_PROTECTION_METRICS
} = ShipProps;
import { OBJECT_EVENT } from 'ed-forge/lib/Ship';
/**
* Ship Summary Table / Stats
*/
export default class ShipSummaryTable extends TranslatedComponent {
static propTypes = {
ship: PropTypes.object.isRequired,
marker: PropTypes.string.isRequired,
};
/**
@@ -27,12 +27,24 @@ export default class ShipSummaryTable extends TranslatedComponent {
*/
constructor(props) {
super(props);
this.didContextChange = this.didContextChange.bind(this);
autoBind(this);
this.state = {
shieldColour: 'blue'
};
}
_onChange() {
this.forceUpdate();
}
componentWillMount() {
this.props.ship.on(OBJECT_EVENT, this._onChange);
}
componentWillUnmount() {
this.props.ship.removeListener(OBJECT_EVENT, this._onChange);
}
/**
* Render the table
* @return {React.Component} Summary table
@@ -45,31 +57,39 @@ export default class ShipSummaryTable extends TranslatedComponent {
let formats = language.formats;
let { time, int, round, f1, f2 } = formats;
let hide = tooltip.bind(null, null);
const shieldGenerator = ship.getShieldGenerator();
const sgClassNames = cn({ warning: shieldGenerator && !ship.shield, muted: !shieldGenerator });
const sgTooltip = shieldGenerator ? 'TT_SUMMARY_SHIELDS' : 'TT_SUMMARY_SHIELDS_NONFUNCTIONAL';
const timeToDrain = Calc.timeToDrainWep(ship, 4);
const canThrust = ship.canThrust(cargo, ship.fuelCapacity);
const speed = ship.get(MAX_SPEED);
const shipBoost = ship.get(BOOST_SPEED);
const canThrust = 0 < speed;
const canBoost = canThrust && !isNaN(shipBoost);
const speedTooltip = canThrust ? 'TT_SUMMARY_SPEED' : 'TT_SUMMARY_SPEED_NONFUNCTIONAL';
const canBoost = ship.canBoost(cargo, ship.fuelCapacity);
const boostTooltip = canBoost ? 'TT_SUMMARY_BOOST' : canThrust ? 'TT_SUMMARY_BOOST_NONFUNCTIONAL' : 'TT_SUMMARY_SPEED_NONFUNCTIONAL';
const canJump = ship.getSlotStatus(ship.standard[2]) == 3;
const sgMetrics = Calc.shieldMetrics(ship, pips.sys);
const shipBoost = canBoost ? Calc.calcBoost(ship) : 'No Boost';
const restingHeat = Math.sqrt(((ship.standard[0].m.pgen * ship.standard[0].m.eff) / ship.heatCapacity) / 0.2);
const armourMetrics = Calc.armourMetrics(ship);
let shieldColour = 'blue';
if (shieldGenerator && shieldGenerator.m.grp === 'psg') {
shieldColour = 'green';
} else if (shieldGenerator && shieldGenerator.m.grp === 'bsg') {
shieldColour = 'purple';
const sgMetrics = ship.get(SHIELD_METRICS);
const armourMetrics = ship.get(ARMOUR_METRICS);
const damageMetrics = ship.get(DAMAGE_METRICS);
const moduleProtectionMetrics = ship.get(MODULE_PROTECTION_METRICS);
const timeToDrain = damageMetrics.timeToDrain[8];
const shieldGenerator = ship.getShieldGenerator();
const sgClassNames = cn({
warning: shieldGenerator && !shieldGenerator.isEnabled(),
muted: !shieldGenerator,
});
const sgTooltip = shieldGenerator ? 'TT_SUMMARY_SHIELDS' : 'TT_SUMMARY_SHIELDS_NONFUNCTIONAL';
let shieldColour;
switch (shieldGenerator.readMeta('type')) {
case 'biweaveshieldgen': shieldColour = 'purple'; break;
case 'prismaticshieldgen': shieldColour = 'green'; break;
default: shieldColour = 'blue';
}
this.state = {
shieldColour
};
let speed = ship.get(SPEED);
let jumpRange = ship.get(JUMP_RANGE);
const jumpRangeMetrics = ship.getMetrics(JUMP_METRICS);
// TODO:
const canJump = true;
return <div id='summary'>
<div style={{display: "table", width: "100%"}}>
@@ -79,7 +99,7 @@ export default class ShipSummaryTable extends TranslatedComponent {
<tr className='main'>
<th rowSpan={2} className={ cn({ 'bg-warning-disabled': speed == 0 }) }>{translate('speed')}</th>
<th rowSpan={2} className={ cn({ 'bg-warning-disabled': !canBoost }) }>{translate('boost')}</th>
<th colSpan={5} className={ cn({ 'bg-warning-disabled': jumpRange == 0 }) }>{translate('jump range')}</th>
<th colSpan={5} className={ cn({ 'bg-warning-disabled': jumpRangeMetrics.jumpRange == 0 }) }>{translate('jump range')}</th>
<th rowSpan={2}>{translate('shield')}</th>
<th rowSpan={2}>{translate('integrity')}</th>
<th rowSpan={2}>{translate('DPS')}</th>
@@ -97,11 +117,11 @@ export default class ShipSummaryTable extends TranslatedComponent {
<th rowSpan={2}>{translate('resting heat (Beta)')}</th>
</tr>
<tr>
<th className={ cn({ 'lft': true, 'bg-warning-disabled': !canJump }) }>{translate('max')}</th>
<th className={ cn({ 'bg-warning-disabled': !canJump }) }>{translate('unladen')}</th>
<th className={ cn({ 'bg-warning-disabled': !canJump }) }>{translate('laden')}</th>
<th className={ cn({ 'bg-warning-disabled': !canJump }) }>{translate('total unladen')}</th>
<th className={ cn({ 'bg-warning-disabled': !canJump }) }>{translate('total laden')}</th>
<th className="lft">{translate('max')}</th>
<th>{translate('unladen')}</th>
<th>{translate('laden')}</th>
<th>{translate('total unladen')}</th>
<th>{translate('total laden')}</th>
<th className='lft'>{translate('hull')}</th>
<th>{translate('unladen')}</th>
<th>{translate('laden')}</th>
@@ -109,30 +129,88 @@ export default class ShipSummaryTable extends TranslatedComponent {
</thead>
<tbody>
<tr>
<td onMouseEnter={termtip.bind(null, speedTooltip, { cap: 0 })} onMouseLeave={hide}>{ canThrust ? <span>{int(ship.calcSpeed(4, ship.fuelCapacity, 0, false))}{u['m/s']}</span> : <span className='warning'>0 <Warning/></span> }</td>
<td onMouseEnter={termtip.bind(null, boostTooltip, { cap: 0 })} onMouseLeave={hide}>{ canBoost ? <span>{int(ship.calcSpeed(4, ship.fuelCapacity, 0, true))}{u['m/s']}</span> : <span className='warning'>0 <Warning/></span> }</td>
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_MAX_SINGLE_JUMP', { cap: 0 })} onMouseLeave={hide}>{ canJump ? <span>{ f2(Calc.jumpRange(ship.unladenMass + ship.standard[2].m.getMaxFuelPerJump(), ship.standard[2].m, ship.standard[2].m.getMaxFuelPerJump(), ship))}{u.LY}</span> : <span className='warning'>0 <Warning/></span> }</td>
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_UNLADEN_SINGLE_JUMP', { cap: 0 })} onMouseLeave={hide}>{ canJump ? <span>{f2(Calc.jumpRange(ship.unladenMass + ship.fuelCapacity, ship.standard[2].m, ship.fuelCapacity, ship))}{u.LY}</span> : <span className='warning'>0 <Warning/></span> }</td>
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_LADEN_SINGLE_JUMP', { cap: 0 })} onMouseLeave={hide}>{ canJump ? <span>{f2(Calc.jumpRange(ship.unladenMass + ship.fuelCapacity + ship.cargoCapacity, ship.standard[2].m, ship.fuelCapacity, ship))}{u.LY}</span> : <span className='warning'>0 <Warning/></span> }</td>
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_UNLADEN_TOTAL_JUMP', { cap: 0 })} onMouseLeave={hide}>{ canJump ? <span>{f2(Calc.totalJumpRange(ship.unladenMass + ship.fuelCapacity, ship.standard[2].m, ship.fuelCapacity, ship))}{u.LY}</span> : <span className='warning'>0 <Warning/></span> }</td>
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_LADEN_TOTAL_JUMP', { cap: 0 })} onMouseLeave={hide}>{ canJump ? <span>{f2(Calc.totalJumpRange(ship.unladenMass + ship.fuelCapacity + ship.cargoCapacity, ship.standard[2].m, ship.fuelCapacity, ship))}{u.LY}</span> : <span className='warning'>0 <Warning/></span> }</td>
<td className={sgClassNames} onMouseEnter={termtip.bind(null, sgTooltip, { cap: 0 })} onMouseLeave={hide}>{int(ship.shield)}{u.MJ}</td>
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_INTEGRITY', { cap: 0 })} onMouseLeave={hide}>{int(ship.armour)}</td>
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_DPS', { cap: 0 })} onMouseLeave={hide}>{f1(ship.totalDps)}</td>
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_EPS', { cap: 0 })} onMouseLeave={hide}>{f1(ship.totalEps)}</td>
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_TTD', { cap: 0 })} onMouseLeave={hide}>{timeToDrain === Infinity ? '∞' : time(timeToDrain)}</td>
<td onMouseEnter={termtip.bind(null, speedTooltip, { cap: 0 })}
onMouseLeave={hide}
>{canThrust
? <span>{int(speed)}{u['m/s']}</span>
: <span className='warning'>0<Warning/></span>
}</td>
<td onMouseEnter={termtip.bind(null, boostTooltip, { cap: 0 })}
onMouseLeave={hide}
>{canBoost
? <span>{int(shipBoost)}{u['m/s']}</span>
: <span className='warning'>0<Warning/></span>
}</td>
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_MAX_SINGLE_JUMP', { cap: 0 })}
onMouseLeave={hide}
>{canJump
// TODO:
? <span>{NaN}{u.LY}</span>
: <span className='warning'>0<Warning/></span>
}</td>
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_UNLADEN_SINGLE_JUMP', { cap: 0 })}
onMouseLeave={hide}
>{canJump
// TODO:
? <span>{NaN}{u.LY}</span>
: <span className='warning'>0<Warning/></span>
}</td>
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_LADEN_SINGLE_JUMP', { cap: 0 })}
onMouseLeave={hide}
>{canJump
? <span>{f2(jumpRangeMetrics.jumpRange)}{u.LY}</span>
: <span className='warning'>0<Warning/></span>
}</td>
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_UNLADEN_TOTAL_JUMP', { cap: 0 })}
onMouseLeave={hide}
>{canJump
// TODO:
? <span>{NaN}{u.LY}</span>
: <span className='warning'>0 <Warning/></span>
}</td>
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_LADEN_TOTAL_JUMP', { cap: 0 })}
onMouseLeave={hide}
>{canJump
? <span>{f2(jumpRangeMetrics.totalRange)}{u.LY}</span>
: <span className='warning'>0<Warning/></span>
}</td>
<td className={sgClassNames}
onMouseEnter={termtip.bind(null, sgTooltip, { cap: 0 })}
onMouseLeave={hide}
>{int(sgMetrics.shieldStrength)}{u.MJ}</td>
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_INTEGRITY', { cap: 0 })}
onMouseLeave={hide}
>{int(armourMetrics.armour)}</td>
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_DPS', { cap: 0 })}
onMouseLeave={hide}
>{f1(damageMetrics.dps)}</td>
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_EPS', { cap: 0 })}
onMouseLeave={hide}
>{f1(damageMetrics.eps)}</td>
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_TTD', { cap: 0 })}
onMouseLeave={hide}
>{timeToDrain === Infinity ? '∞' : time(timeToDrain)}</td>
{/* <td>{f1(ship.totalHps)}</td> */}
<td>{round(ship.cargoCapacity)}{u.T}</td>
<td>{ship.passengerCapacity}</td>
<td>{round(ship.fuelCapacity)}{u.T}</td>
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_HULL_MASS', { cap: 0 })} onMouseLeave={hide}>{ship.hullMass}{u.T}</td>
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_UNLADEN_MASS', { cap: 0 })} onMouseLeave={hide}>{int(ship.unladenMass)}{u.T}</td>
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_LADEN_MASS', { cap: 0 })} onMouseLeave={hide}>{int(ship.ladenMass)}{u.T}</td>
<td>{int(ship.hardness)}</td>
<td>{ship.crew}</td>
<td>{ship.masslock}</td>
<td>{shipBoost !== 'No Boost' ? formats.time(shipBoost) : 'No Boost'}</td>
<td>{formats.pct(restingHeat)}</td>
<td>{round(ship.get(CARGO_CAPACITY))}{u.T}</td>
{/* TODO: PAX */}
<td>{NaN}</td>
<td>{round(ship.get(FUEL_CAPACITY))}{u.T}</td>
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_HULL_MASS', { cap: 0 })}
onMouseLeave={hide}
>{ship.getBaseProperty('hullmass')}{u.T}</td>
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_UNLADEN_MASS', { cap: 0 })}
onMouseLeave={hide}
>{int(ship.get(UNLADEN_MASS))}{u.T}</td>
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_LADEN_MASS', { cap: 0 })}
onMouseLeave={hide}
>{int(ship.get(MAXIMUM_MASS))}{u.T}</td>
<td>{int(ship.getBaseProperty('hardness'))}</td>
<td>{ship.readMeta('crew')}</td>
<td>{ship.getBaseProperty('masslock')}</td>
{/* TODO: boost intervall */}
<td>{NaN}</td>
{/* TODO: resting heat */}
<td>{NaN}</td>
</tr>
</tbody>
</table>
@@ -161,19 +239,19 @@ export default class ShipSummaryTable extends TranslatedComponent {
</thead>
<tbody>
<tr>
<td>{translate(shieldGenerator && shieldGenerator.m.grp || 'No Shield')}</td>
<td>{formats.pct1(ship.shieldExplRes)}</td>
<td>{formats.pct1(ship.shieldKinRes)}</td>
<td>{formats.pct1(ship.shieldThermRes)}</td>
<td>{translate(shieldGenerator.readMeta('type') || 'No Shield')}</td>
<td>{formats.pct1(1 - sgMetrics.explosive.damageMultiplier)}</td>
<td>{formats.pct1(1 - sgMetrics.kinetic.damageMultiplier)}</td>
<td>{formats.pct1(1 - sgMetrics.thermal.damageMultiplier)}</td>
<td></td>
<td>{int(ship && sgMetrics.summary > 0 ? sgMetrics.summary : 0)}{u.MJ}</td>
<td>{int(ship && sgMetrics.summary > 0 ? sgMetrics.summary / sgMetrics.explosive.base : 0)}{u.MJ}</td>
<td>{int(ship && sgMetrics.summary ? sgMetrics.summary / sgMetrics.kinetic.base : 0)}{u.MJ}</td>
<td>{int(ship && sgMetrics.summary ? sgMetrics.summary / sgMetrics.thermal.base : 0)}{u.MJ}</td>
<td>{int(sgMetrics.shieldStrength || 0)}{u.MJ}</td>
<td>{int(sgMetrics.shieldStrength / sgMetrics.explosive.damageMultiplier || 0)}{u.MJ}</td>
<td>{int(sgMetrics.shieldStrength / sgMetrics.kinetic.damageMultiplier || 0)}{u.MJ}</td>
<td>{int(sgMetrics.shieldStrength / sgMetrics.thermal.damageMultiplier || 0)}{u.MJ}</td>
<td></td>
<td>{sgMetrics && sgMetrics.recover === Math.Inf ? translate('Never') : formats.time(sgMetrics.recover)}</td>
<td>{sgMetrics && sgMetrics.recharge === Math.Inf ? translate('Never') : formats.time(sgMetrics.recharge)}</td>
<td>{formats.time(sgMetrics.recover) || translate('Never')}</td>
<td>{formats.time(sgMetrics.recharge) || translate('Never')}</td>
</tr>
</tbody>
<thead>
@@ -200,19 +278,18 @@ export default class ShipSummaryTable extends TranslatedComponent {
</thead>
<tbody>
<tr>
<td>{translate(ship && ship.bulkheads && ship.bulkheads.m && ship.bulkheads.m.name || 'No Armour')}</td>
<td>{formats.pct1(ship.hullExplRes)}</td>
<td>{formats.pct1(ship.hullKinRes)}</td>
<td>{formats.pct1(ship.hullThermRes)}</td>
<td>{formats.pct1(ship.hullCausRes)}</td>
<td>{int(armourMetrics.total)}</td>
<td>{int(armourMetrics.total / armourMetrics.explosive.total)}</td>
<td>{int(armourMetrics.total/ armourMetrics.kinetic.total)}</td>
<td>{int(armourMetrics.total / armourMetrics.thermal.total)}</td>
<td>{int(armourMetrics.total/ armourMetrics.caustic.total)}</td>
<td>{int(armourMetrics.modulearmour)}</td>
<td>{int(armourMetrics.moduleprotection * 100) + '%'}</td>
<td>{translate(ship.getAlloys().readMeta('type') || 'No Armour')}</td>
<td>{formats.pct1(1 - armourMetrics.explosive.damageMultiplier)}</td>
<td>{formats.pct1(1 - armourMetrics.kinetic.damageMultiplier)}</td>
<td>{formats.pct1(1 - armourMetrics.thermal.damageMultiplier)}</td>
<td>{formats.pct1(1 - armourMetrics.caustic.damageMultiplier)}</td>
<td>{int(armourMetrics.armour)}</td>
<td>{int(armourMetrics.armour / armourMetrics.explosive.damageMultiplier)}</td>
<td>{int(armourMetrics.armour / armourMetrics.kinetic.damageMultiplier)}</td>
<td>{int(armourMetrics.armour / armourMetrics.thermal.damageMultiplier)}</td>
<td>{int(armourMetrics.armour / armourMetrics.caustic.damageMultiplier)}</td>
<td>{int(moduleProtectionMetrics.moduleArmour)}</td>
<td>{formats.pct1(1 - moduleProtectionMetrics.moduleProtection)}</td>
</tr>
</tbody>
</table>

View File

@@ -1,5 +1,4 @@
import React from 'react';
import Persist from '../stores/Persist';
import PropTypes from 'prop-types';
import TranslatedComponent from './TranslatedComponent';
import cn from 'classnames';
@@ -9,8 +8,9 @@ import ModificationsMenu from './ModificationsMenu';
import { diffDetails } from '../utils/SlotFunctions';
import { stopCtxPropagation, wrapCtxMenu } from '../utils/UtilityFunctions';
import { blueprintTooltip } from '../utils/BlueprintFunctions';
import { Ship, Module } from 'ed-forge';
import { Module } from 'ed-forge';
import { REG_MILITARY_SLOT, REG_HARDPOINT_SLOT } from 'ed-forge/lib/data/slots';
import autoBind from 'auto-bind';
const HARDPOINT_SLOT_LABELS = {
1: 'S',
@@ -24,11 +24,8 @@ const HARDPOINT_SLOT_LABELS = {
*/
export default class Slot extends TranslatedComponent {
static propTypes = {
onSelect: PropTypes.func.isRequired,
onOpen: PropTypes.func.isRequired,
selected: PropTypes.bool,
slot: PropTypes.instanceOf(Module),
ship: PropTypes.instanceOf(Ship),
currentMenu: PropTypes.any,
m: PropTypes.instanceOf(Module),
warning: PropTypes.func,
drag: PropTypes.func,
drop: PropTypes.func,
@@ -41,31 +38,53 @@ export default class Slot extends TranslatedComponent {
*/
constructor(props) {
super(props);
autoBind(this);
this.state = { menuIndex: 0 };
}
this._modificationsSelected = false;
this._contextMenu = wrapCtxMenu(this._contextMenu.bind(this));
this._getMaxClassLabel = this._getMaxClassLabel.bind(this);
/**
* Opens a menu while setting state.
* @param {Object} newMenuIndex New menu index
* @param {Event} event Event object
*/
_openMenu(newMenuIndex, event) {
const slotName = this.props.m.getSlot();
if (
this.props.currentMenu === slotName &&
newMenuIndex === this.state.menuIndex
) {
this.context.closeMenu();
} {
this.setState({ menuIndex: newMenuIndex });
this.context.openMenu(slotName);
}
// If we don't stop event propagation, the underlying divs also might
// get clicked which would open up other menus
event.stopPropagation();
}
/**
* Generate the slot contents
* @param {Object} m Mounted Module
* @param {Function} translate Translate function
* @param {Object} formats Localized Formats map
* @param {Object} u Localized Units Map
* @return {React.Component} Slot contents
*/
_getSlotDetails(m, translate, formats, u) {
if (m) {
let classRating = String(m.getSize()) + m.getRating();
let { drag, drop, ship } = this.props;
let { termtip, tooltip } = this.context;
let showModuleResistances = Persist.showModuleResistances();
_getSlotDetails() {
const { m } = this.props;
let { termtip, tooltip, language } = this.context;
const { translate, units, formats } = language;
if (m.isEmpty()) {
return <div className="empty">
{translate(
m.getSlot().match(REG_MILITARY_SLOT) ? 'emptyrestricted' : 'empty'
)}
</div>;
} else {
let classRating = String(m.getClass()) + m.getRating();
let { drag, drop } = this.props;
// Modifications tooltip shows blueprint and grade, if available
// let modTT = translate('modified');
// const blueprint = m.getBlueprint();
let modTT = translate('modified');
const blueprint = m.getBlueprint();
// const experimental = m.getExperimental();
// const grade = m.getGrade();
// if (blueprint) {
@@ -82,12 +101,10 @@ export default class Slot extends TranslatedComponent {
// }
let mass = m.get('mass') || m.get('cargo') || m.get('fuel') || 0;
const enabled = m.isEnabled();
const className = cn('details', enabled ? '' : 'disabled');
const disabled = !m.isEnabled();
return (
<div
className={className}
className={cn('details', { disabled })}
draggable="true"
onDragStart={drag}
onDragEnd={drop}
@@ -95,65 +112,26 @@ export default class Slot extends TranslatedComponent {
<div className={'cb'}>
<div className={'l'}>
{classRating} {translate(m.readMeta('type'))}
{m.mods && Object.keys(m.mods).length > 0 ? (
{blueprint && (
<span
onMouseOver={termtip.bind(null, modTT)}
onMouseOut={tooltip.bind(null, null)}
>
<Modified />
</span>
) : (
''
)}
</div>
<div className={'r'}>
{formats.round(mass)}
{u.T}
{units.T}
</div>
</div>
<div className={'cb'}>
{/* { m.getOptMass() ? <div className={'l'}>{translate('optmass', 'sg')}: {formats.int(m.getOptMass())}{u.T}</div> : null }
{ m.getMaxMass() ? <div className={'l'}>{translate('maxmass', 'sg')}: {formats.int(m.getMaxMass())}{u.T}</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.rebuildsperbay ? <div className={'l'}>{translate('rebuildsperbay')}: {m.rebuildsperbay}</div> : null }
{ m.rate ? <div className={'l'}>{translate('rate')}: {m.rate}{u.kgs}&nbsp;&nbsp;&nbsp;{translate('refuel time')}: {formats.time(this.props.fuel * 1000 / m.rate)}</div> : null }
{ m.getAmmo() && m.grp !== 'scb' ? <div className={'l'}>{translate('ammunition')}: {formats.gen(m.getAmmo())}</div> : null }
{ m.getSpinup() ? <div className={'l'}>{translate('spinup')}: {formats.f1(m.getSpinup())}{u.s}</div> : null }
{ m.getDuration() ? <div className={'l'}>{translate('duration')}: {formats.f1(m.getDuration())}{u.s}</div> : null }
{ m.grp === 'scb' ? <div className={'l'}>{translate('cells')}: {formats.int(m.getAmmo() + 1)}</div> : null }
{ m.grp === 'gsrp' ? <div className={'l'}>{translate('shield addition')}: {formats.f1(m.getShieldAddition())}{u.MJ}</div> : null }
{ m.grp === 'gfsb' ? <div className={'l'}>{translate('jump addition')}: {formats.f1(m.getJumpBoost())}{u.LY}</div> : null }
{ m.grp === 'gs' ? <div className={'l'}>{translate('shield addition')}: {formats.f1(m.getShieldAddition())}{u.MJ}</div> : null }
{ m.getShieldReinforcement() ? <div className={'l'}>{translate('shieldreinforcement')}: {formats.f1(m.getDuration() * m.getShieldReinforcement())}{u.MJ}</div> : null }
{ m.getShieldReinforcement() ? <div className={'l'}>{translate('total')}: {formats.int((m.getAmmo() + 1) * (m.getDuration() * m.getShieldReinforcement()))}{u.MJ}</div> : null }
{ m.repair ? <div className={'l'}>{translate('repair')}: {m.repair}</div> : null }
{ m.getFacingLimit() ? <div className={'l'}>{translate('facinglimit')} {formats.f1(m.getFacingLimit())}°</div> : null }
{ m.getRange() ? <div className={'l'}>{translate('range')} {formats.f2(m.getRange())}{u.km}</div> : null }
{ m.getRangeT() ? <div className={'l'}>{translate('ranget')} {formats.f1(m.getRangeT())}{u.s}</div> : null }
{ m.getTime() ? <div className={'l'}>{translate('time')}: {formats.time(m.getTime())}</div> : null }
{ m.getHackTime() ? <div className={'l'}>{translate('hacktime')}: {formats.time(m.getHackTime())}</div> : null }
{ m.maximum ? <div className={'l'}>{translate('max')}: {(m.maximum)}</div> : null }
{ m.rangeLS ? <div className={'l'}>{translate('range')}: {m.rangeLS}{u.Ls}</div> : null }
{ m.rangeLS === null ? <div className={'l'}>∞{u.Ls}</div> : null }
{ m.rangeRating ? <div className={'l'}>{translate('range')}: {m.rangeRating}</div> : null }
{ m.passengers ? <div className={'l'}>{translate('passengers')}: {m.passengers}</div> : null }
{ m.getRegenerationRate() ? <div className='l'>{translate('regen')}: {formats.round1(m.getRegenerationRate())}{u.ps}</div> : null }
{ m.getBrokenRegenerationRate() ? <div className='l'>{translate('brokenregen')}: {formats.round1(m.getBrokenRegenerationRate())}{u.ps}</div> : null }
{ showModuleResistances && m.getExplosiveResistance() ? <div className='l'>{translate('explres')}: {formats.pct(m.getExplosiveResistance())}</div> : null }
{ showModuleResistances && m.getKineticResistance() ? <div className='l'>{translate('kinres')}: {formats.pct(m.getKineticResistance())}</div> : null }
{ showModuleResistances && m.getThermalResistance() ? <div className='l'>{translate('thermres')}: {formats.pct(m.getThermalResistance())}</div> : null }
{ showModuleResistances && m.getCausticResistance() ? <div className='l'>{translate('causres')}: {formats.pct(m.getCausticResistance())}</div> : null }
{ m.getHullReinforcement() ? <div className='l'>{translate('armour')}: {formats.int(m.getHullReinforcement() + ship.baseArmour * m.getModValue('hullboost') / 10000)}</div> : null }
{ m.getProtection() ? <div className='l'>{translate('protection')}: {formats.rPct(m.getProtection())}</div> : null }
{ m.getIntegrity() ? <div className='l'>{translate('integrity')}: {formats.int(m.getIntegrity())}</div> : null } */}
{(m.getApplicableBlueprints() || []).length > 0 ? (
<div className="r"
ref={(modButton) => (this.modButton = modButton)}
>
<button onClick={this._toggleModifications.bind(this)}
<div className="r">
<button onClick={this._openMenu.bind(this, 1)}
onContextMenu={stopCtxPropagation}
onMouseOver={termtip.bind(null, 'modifications')}
onMouseOver={termtip.bind(null, translate('modifications'))}
onMouseOut={tooltip.bind(null, null)}
>
<ListModifications />
@@ -163,35 +141,24 @@ export default class Slot extends TranslatedComponent {
</div>
</div>
);
} else {
return <div className={'empty'}>{translate('empty')}</div>;
}
}
/**
* Get the CSS class name for the slot. Can/should be overriden
* as necessary.
* @return {string} CSS Class name
*/
_getClassNames() {
return null;
}
/**
* Get the label for the slot size/class
* Should be overriden if necessary
* @return {string} label
*/
_getMaxClassLabel() {
const { slot } = this.props;
let size = slot.getSize();
const { m } = this.props;
let size = m.getSize();
switch (true) {
case slot.getSlot() === 'armour':
case m.getSlot() === 'armour':
return '';
case size === 0:
// This can also happen for armour but that case was handled above
return 'U';
case Boolean(slot.getSlot().match(REG_HARDPOINT_SLOT)):
case Boolean(m.getSlot().match(REG_HARDPOINT_SLOT)):
return HARDPOINT_SLOT_LABELS[size];
default:
return size;
@@ -205,7 +172,13 @@ export default class Slot extends TranslatedComponent {
_contextMenu(event) {
event.stopPropagation();
event.preventDefault();
this.props.onSelect(null,null);
const { m } = this.props;
m.reset();
if (this.props.currentMenu === m.getSlot()) {
this.context.closeMenu();
} else {
this.forceUpdate();
}
}
/**
@@ -215,65 +188,38 @@ export default class Slot extends TranslatedComponent {
render() {
let language = this.context.language;
let translate = language.translate;
let { ship, slot, dropClass, dragOver, onOpen, onChange, selected, onSelect, warning } = this.props;
let slotDetails, modificationsMarker, menu;
if (!selected) {
// If not selected then sure that modifications flag is unset
this._modificationsSelected = false;
}
if (!slot.isEmpty()) {
slotDetails = this._getSlotDetails(slot, translate, language.formats, language.units); // Must be implemented by sub classes
} else {
slotDetails = <div className={'empty'}>
{translate(
slot.getSlot().match(REG_MILITARY_SLOT) ? 'emptyrestricted' : 'empty'
)}
</div>;
}
if (selected) {
if (this._modificationsSelected) {
menu = <ModificationsMenu
className={this._getClassNames()}
onChange={onChange}
ship={ship}
m={slot}
modButton = {this.modButton}
/>;
} else {
menu = <AvailableModulesMenu
className={this._getClassNames()}
m={slot}
ship={ship}
onSelect={onSelect}
warning={warning}
diffDetails={diffDetails.bind(ship, this.context.language)}
/>;
}
}
let { currentMenu, m, dropClass, dragOver, warning } = this.props;
const { menuIndex } = this.state;
// TODO: implement touch dragging
const selected = currentMenu === m.getSlot();
return (
<div className={cn('slot', dropClass, { selected })} onClick={onOpen}
<div
className={cn('slot', dropClass, { selected })}
onContextMenu={this._contextMenu}
onDragOver={dragOver} tabIndex="0"
onClick={this._openMenu.bind(this, 0)}
>
<div className='details-container'>
<div className='sz'>{this._getMaxClassLabel(translate)}</div>
{slotDetails}
</div>
{menu}
<div className={cn(
'details-container',
{ warning: warning && warning(m) },
)}>
<div className="sz">{this._getMaxClassLabel(translate)}</div>
{this._getSlotDetails()}
</div>
{selected && menuIndex === 0 &&
<AvailableModulesMenu
m={m}
onSelect={(item) => {
m.setItem(item);
this.context.closeMenu();
}}
warning={warning}
// diffDetails={diffDetails.bind(ship, this.context.language)}
/>}
{selected && menuIndex === 1 &&
<ModificationsMenu m={m} />}
</div>
);
}
/**
* Toggle the modifications flag when selecting the modifications icon
*/
_toggleModifications() {
this._modificationsSelected = !this._modificationsSelected;
}
}

View File

@@ -6,6 +6,7 @@ import { canMount } from '../utils/SlotFunctions';
import { Equalizer } from '../components/SvgIcons';
import cn from 'classnames';
import { Ship } from 'ed-forge';
import autoBind from 'auto-bind';
const browser = require('detect-browser');
/**
@@ -14,36 +15,20 @@ const browser = require('detect-browser');
export default class SlotSection extends TranslatedComponent {
static propTypes = {
ship: PropTypes.instanceOf(Ship),
onChange: PropTypes.func.isRequired,
// code: PropTypes.string.isRequired,
togglePwr: PropTypes.func,
sectionMenuRefs: PropTypes.object
};
/**
* Constructor
* @param {Object} props React Component properties
* @param {Object} context React Component context
* @param {string} sectionId Section DOM Id
* @param {string} sectionName Section name
*/
constructor(props, context, sectionId, sectionName) {
constructor(props, sectionName) {
super(props);
this.sectionId = sectionId;
this.sectionName = sectionName;
this.ssHeadRef = null;
autoBind(this);
this.sectionName = sectionName;
this.sectionRefArr = this.props.sectionMenuRefs[this.sectionId] = [];
this.sectionRefArr['selectedRef'] = null;
this._getSlots = this._getSlots.bind(this);
this._selectModule = this._selectModule.bind(this);
this._getSectionMenu = this._getSectionMenu.bind(this);
this._contextMenu = this._contextMenu.bind(this);
this._drop = this._drop.bind(this);
this._dragOverNone = this._dragOverNone.bind(this);
this._close = this._close.bind(this);
this._keyDown = this._keyDown.bind(this);
this._handleSectionFocus = this._handleSectionFocus.bind(this);
this.state = {};
}
@@ -53,83 +38,6 @@ export default class SlotSection extends TranslatedComponent {
// _contextMenu()
// componentDidUpdate(prevProps)
/**
* TODO: May either need to send the function to be triggered when Enter key is pressed, or else
* may need a separate keyDown handler for each subclass (StandardSlotSection, HardpointSlotSection, etc.)
* ex: _keyDown(_keyDownfn, event)
*
* @param {SyntheticEvent} event KeyDown event
*/
_keyDown(event) {
if (event.key == 'Enter') {
event.stopPropagation();
if (event.currentTarget.nodeName === 'H1') {
this._openMenu(this.sectionName, event);
} else {
event.currentTarget.click();
}
return;
}
if (event.key == 'Tab') {
if (event.shiftKey) {
if ((event.currentTarget === this.sectionRefArr[this.firstRefId]) && this.sectionRefArr[this.lastRefId]) {
event.preventDefault();
this.sectionRefArr[this.lastRefId].focus();
}
} else {
if ((event.currentTarget === this.sectionRefArr[this.lastRefId]) && this.sectionRefArr[this.firstRefId]) {
event.preventDefault();
this.sectionRefArr[this.firstRefId].focus();
}
}
}
}
/**
* Set focus on appropriate Slot Section Menu element
* @param {Object} focusPrevProps prevProps for componentDidUpdate() from ...SlotSection.jsx
* @param {String} firstRef id of the first ref in ...SlotSection.jsx
* @param {String} lastRef id of the last ref in ...SlotSection.jsx
*
*/
_handleSectionFocus(focusPrevProps, firstRef, lastRef) {
if (this.selectedRefId !== null && this.sectionRefArr[this.selectedRefId]) {
// set focus on the previously selected option for the currently open section menu
this.sectionRefArr[this.selectedRefId].focus();
} else if (this.sectionRefArr[firstRef] && this.sectionRefArr[firstRef] != null) {
// set focus on the first option in the currently open section menu if none have been selected previously
this.sectionRefArr[firstRef].focus();
} else if (this.props.currentMenu == null && focusPrevProps.currentMenu == this.sectionName && this.sectionRefArr['ssHeadRef']) {
// set focus on the section menu header when section menu is closed
this.sectionRefArr['ssHeadRef'].focus();
}
}
/**
* Open a menu
* @param {string} menu Menu name
* @param {SyntheticEvent} event Event
*/
_openMenu(menu, event) {
event.preventDefault();
event.stopPropagation();
if (this.props.currentMenu === menu) {
menu = null;
}
this.context.openMenu(menu);
}
/**
* Mount/Use the specified module in the slot
* @param {Object} slot Slot
* @param {Object} m Selected module
*/
_selectModule(slot, m) {
slot.setItem(m);
this.props.onChange();
this._close();
}
/**
* Slot Drag Handler
* @param {object} originSlot Origin slot model
@@ -206,7 +114,6 @@ export default class SlotSection extends TranslatedComponent {
// Copy power info
targetSlot.enabled = originSlot.enabled;
targetSlot.priority = originSlot.priority;
this.props.onChange();
}
} else {
// Store power info
@@ -235,7 +142,6 @@ export default class SlotSection extends TranslatedComponent {
targetSlot.enabled = targetEnabled;
targetSlot.priority = targetPriority;
}
this.props.onChange();
this.props.ship
.updatePowerGenerated()
.updatePowerUsed()
@@ -281,6 +187,17 @@ export default class SlotSection extends TranslatedComponent {
return 'ineligible'; // Cannot be dropped / invalid drop slot
}
_open(newMenu, event) {
event.preventDefault();
event.stopPropagation();
const { currentMenu } = this.props;
if (currentMenu === newMenu) {
this.context.closeMenu();
} else {
this.context.openMenu(newMenu);
}
}
/**
* Close current menu
*/
@@ -297,14 +214,13 @@ export default class SlotSection extends TranslatedComponent {
render() {
let translate = this.context.language.translate;
let sectionMenuOpened = this.props.currentMenu === this.sectionName;
let open = this._openMenu.bind(this, this.sectionName);
let ctx = wrapCtxMenu(this._contextMenu);
return (
<div id={this.sectionId} className={'group'} onDragLeave={this._dragOverNone}>
<div className={cn('section-menu', { selected: sectionMenuOpened })} onClick={open} onContextMenu={ctx}>
<h1 tabIndex="0" onKeyDown={this._keyDown} ref={ssHead => this.sectionRefArr['ssHeadRef'] = ssHead}>{translate(this.sectionName)} <Equalizer/></h1>
{sectionMenuOpened ? this._getSectionMenu(translate, this.props.ship) : null }
<div className="group" onDragLeave={this._dragOverNone}>
<div className={cn('section-menu', { selected: sectionMenuOpened })}
onContextMenu={wrapCtxMenu(this._contextMenu)} onClick={this._open.bind(this, this.sectionName)}>
<h1 tabIndex="0">{translate(this.sectionName)}<Equalizer/></h1>
{sectionMenuOpened && this._getSectionMenu()}
</div>
{this._getSlots()}
</div>

View File

@@ -4,8 +4,10 @@ import SlotSection from './SlotSection';
import Slot from './Slot';
import Module from '../shipyard/Module';
import * as ShipRoles from '../shipyard/ShipRoles';
import { stopCtxPropagation } from '../utils/UtilityFunctions';
import autoBind from 'auto-bind';
import { stopCtxPropagation, moduleGet } from '../utils/UtilityFunctions';
import { ShipProps } from 'ed-forge';
const { CONSUMED_RETR, LADEN_MASS } = ShipProps;
/**
* Standard Slot section
@@ -16,29 +18,16 @@ export default class StandardSlotSection extends SlotSection {
* @param {Object} props React Component properties
* @param {Object} context React Component context
*/
constructor(props, context) {
super(props, context, 'standard', 'core internal');
this._optimizeStandard = this._optimizeStandard.bind(this);
this._selectBulkhead = this._selectBulkhead.bind(this);
this.selectedRefId = null;
this.firstRefId = 'maxjump';
this.lastRefId = 'racer';
}
/**
* Handle focus if the component updates
* @param {Object} prevProps React Component properties
*/
componentDidUpdate(prevProps) {
this._handleSectionFocus(prevProps,this.firstRefId, this.lastRefId);
constructor(props) {
super(props, 'core internal');
autoBind(this);
}
/**
* Use the lightest/optimal available standard modules
*/
_optimizeStandard() {
this.selectedRefId = 'maxjump';
this.props.ship.useLightestStandard();
this.props.onChange();
this._close();
}
@@ -48,10 +37,7 @@ export default class StandardSlotSection extends SlotSection {
* @param {integer} bulkheadIndex Bulkhead to use see Constants.BulkheadNames
*/
_multiPurpose(shielded, bulkheadIndex) {
this.selectedRefId = 'multipurpose';
if (bulkheadIndex === 2) this.selectedRefId = 'combat';
ShipRoles.multiPurpose(this.props.ship, shielded, bulkheadIndex);
this.props.onChange();
this._close();
}
@@ -60,9 +46,7 @@ export default class StandardSlotSection extends SlotSection {
* @param {Boolean} shielded True if shield generator should be included
*/
_optimizeCargo(shielded) {
this.selectedRefId = 'trader';
ShipRoles.trader(this.props.ship, shielded);
this.props.onChange();
this._close();
}
@@ -71,9 +55,7 @@ export default class StandardSlotSection extends SlotSection {
* @param {Boolean} shielded True if shield generator should be included
*/
_optimizeMiner(shielded) {
this.selectedRefId = 'miner';
ShipRoles.miner(this.props.ship, shielded);
this.props.onChange();
this._close();
}
@@ -82,10 +64,7 @@ export default class StandardSlotSection extends SlotSection {
* @param {Boolean} planetary True if Planetary Vehicle Hangar (PVH) should be included
*/
_optimizeExplorer(planetary) {
this.selectedRefId = 'explorer';
if (planetary) this.selectedRefId = 'planetary';
ShipRoles.explorer(this.props.ship, planetary);
this.props.onChange();
this._close();
}
@@ -93,20 +72,7 @@ export default class StandardSlotSection extends SlotSection {
* Racer role
*/
_optimizeRacer() {
this.selectedRefId = 'racer';
ShipRoles.racer(this.props.ship);
this.props.onChange();
this._close();
}
/**
* Use the specified bulkhead
* @param {Object} bulkhead Bulkhead module details
*/
_selectBulkhead(bulkhead) {
this.props.ship.useBulkhead(bulkhead.index);
this.context.tooltip();
this.props.onChange();
this._close();
}
@@ -117,122 +83,48 @@ export default class StandardSlotSection extends SlotSection {
this._optimizeStandard();
}
/**
* Creates a new slot for a given module.
* @param {Module} m Module to create the slot for
* @param {function} warning Warning callback
* @return {React.Component} Slot component
*/
_mkSlot(m, warning) {
const { currentMenu } = this.props;
return <Slot key={m.getSlot()} m={m} warning={warning}
currentMenu={currentMenu}
/>;
}
/**
* Generate the slot React Components
* @return {Array} Array of Slots
*/
_getSlots() {
let { ship, currentMenu } = this.props;
let slots = new Array(8);
let open = this._openMenu;
let select = this._selectModule;
// let st = ship.standard;
// let avail = ship.getAvailableModules().standard;
// let bh = ship.bulkheads;
let armour = ship.getAlloys();
slots[0] = <Slot
key='bh'
slot={armour}
modules={armour.getApplicableItems()}
onOpen={open.bind(this, armour)}
onSelect={this._selectBulkhead}
selected={currentMenu == armour}
onChange={this.props.onChange}
ship={ship}
/>;
const powerPlant = ship.getPowerPlant();
slots[1] = <Slot
key='pp'
slot={powerPlant}
modules={powerPlant.getApplicableItems()}
onOpen={open.bind(this, powerPlant)}
onSelect={select.bind(this, powerPlant)}
selected={currentMenu == powerPlant}
onChange={this.props.onChange}
ship={ship}
warning={m => ship.get(ShipProps.CONSUMED_RETR) < m.get('powercapacity')}
/>;
const thrusters = ship.getThrusters();
slots[2] = <Slot
key='th'
slot={thrusters}
modules={thrusters.getApplicableItems()}
onOpen={open.bind(this, thrusters)}
onSelect={select.bind(this, thrusters)}
selected={currentMenu == thrusters}
onChange={this.props.onChange}
ship={ship}
warning={m => m.get('enginemaximalmass') < ship.get(ShipProps.LADEN_MASS)}
/>;
const { ship } = this.props;
const fsd = ship.getFSD();
slots[3] = <Slot
key='fsd'
slot={fsd}
modules={fsd.getApplicableItems()}
onOpen={open.bind(this, fsd)}
onSelect={select.bind(this, fsd)}
onChange={this.props.onChange}
ship={ship}
selected={currentMenu == fsd}
/>;
const lifeSupport = ship.getLifeSupport();
slots[4] = <Slot
key='ls'
slot={lifeSupport}
modules={lifeSupport.getApplicableItems()}
onOpen={open.bind(this, lifeSupport)}
onSelect={select.bind(this, lifeSupport)}
onChange={this.props.onChange}
ship={ship}
selected={currentMenu == lifeSupport}
/>;
const powerDistributor = ship.getPowerDistributor();
slots[5] = <Slot
key='pd'
slot={powerDistributor}
modules={powerDistributor.getApplicableItems()}
onOpen={open.bind(this, powerDistributor)}
onSelect={select.bind(this, powerDistributor)}
selected={currentMenu == powerDistributor}
onChange={this.props.onChange}
ship={ship}
warning={m => m instanceof Module ? m.getEnginesCapacity() <= ship.boostEnergy : m.engcap <= ship.boostEnergy}
/>;
const sensors = ship.getSensors();
slots[6] = <Slot
key='ss'
slot={sensors}
modules={sensors.getApplicableItems()}
onOpen={open.bind(this, sensors)}
onSelect={select.bind(this, sensors)}
selected={currentMenu == sensors}
onChange={this.props.onChange}
ship={ship}
/>;
const fuelTank = ship.getCoreFuelTank();
slots[7] = <Slot
key='ft'
slot={fuelTank}
modules={fuelTank.getApplicableItems()}
onOpen={open.bind(this, fuelTank)}
onSelect={select.bind(this, fuelTank)}
selected={currentMenu == fuelTank}
onChange={this.props.onChange}
ship={ship}
// Show warning when fuel tank is smaller than FSD Max Fuel
warning= {m => m.get('fuel') < fsd.get('maxfuel')}
/>;
return slots;
return [
this._mkSlot(ship.getAlloys()),
this._mkSlot(
ship.getPowerPlant(),
(m) => moduleGet(m, 'powercapacity') < ship.get(CONSUMED_RETR),
),
this._mkSlot(
ship.getThrusters(),
(m) => moduleGet(m, 'enginemaximalmass') < ship.get(LADEN_MASS),
),
this._mkSlot(fsd),
this._mkSlot(
ship.getPowerDistributor(),
(m) => moduleGet(m, 'enginescapacity') <= ship.getBaseProperty('boostenergy'),
),
this._mkSlot(ship.getLifeSupport()),
this._mkSlot(ship.getSensors()),
this._mkSlot(
ship.getCoreFuelTank(),
(m) => moduleGet(m, 'fuel') < fsd.get('maxfuel')
),
];
}
/**
@@ -240,21 +132,21 @@ export default class StandardSlotSection extends SlotSection {
* @param {Function} translate Translate function
* @return {React.Component} Section menu
*/
_getSectionMenu(translate) {
let planetaryDisabled = this.props.ship.internal.length < 4;
_getSectionMenu() {
const { translate } = this.context.language;
return <div className='select' onClick={(e) => e.stopPropagation()} onContextMenu={stopCtxPropagation}>
<ul>
<li className='lc' tabIndex="0" onClick={this._optimizeStandard} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['maxjump'] = smRef}>{translate('Maximize Jump Range')}</li>
<li className='lc' tabIndex="0" onClick={this._optimizeStandard}>{translate('Maximize Jump Range')}</li>
</ul>
<div className='select-group cap'>{translate('roles')}</div>
<ul>
<li className='lc' tabIndex="0" onClick={this._multiPurpose.bind(this, false, 0)} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['multipurpose'] = smRef}>{translate('Multi-purpose')}</li>
<li className='lc' tabIndex="0" onClick={this._multiPurpose.bind(this, true, 2)} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['combat'] = smRef}>{translate('Combat')}</li>
<li className='lc' tabIndex="0" onClick={this._optimizeCargo.bind(this, true)} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['trader'] = smRef}>{translate('Trader')}</li>
<li className='lc' tabIndex="0" onClick={this._optimizeExplorer.bind(this, false)} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['explorer'] = smRef}>{translate('Explorer')}</li>
<li className={cn('lc', { disabled: planetaryDisabled })} tabIndex={planetaryDisabled ? '' : '0'} onClick={!planetaryDisabled && this._optimizeExplorer.bind(this, true)} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['planetary'] = smRef}>{translate('Planetary Explorer')}</li>
<li className='lc' tabIndex="0" onClick={this._optimizeMiner.bind(this, true)} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['miner'] = smRef}>{translate('Miner')}</li>
<li className='lc' tabIndex="0" onClick={this._optimizeRacer.bind(this)} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['racer'] = smRef}>{translate('Racer')}</li>
<li className='lc' tabIndex="0" onClick={this._multiPurpose.bind(this, false, 0)}>{translate('Multi-purpose')}</li>
<li className='lc' tabIndex="0" onClick={this._multiPurpose.bind(this, true, 2)}>{translate('Combat')}</li>
<li className='lc' tabIndex="0" onClick={this._optimizeCargo.bind(this, true)}>{translate('Trader')}</li>
<li className='lc' tabIndex="0" onClick={this._optimizeExplorer.bind(this, false)}>{translate('Explorer')}</li>
<li className='lc' tabIndex="0" onClick={this._optimizeExplorer.bind(this, true)}>{translate('Planetary Explorer')}</li>
<li className='lc' tabIndex="0" onClick={this._optimizeMiner.bind(this, true)}>{translate('Miner')}</li>
<li className='lc' tabIndex="0" onClick={this._optimizeRacer.bind(this)}>{translate('Racer')}</li>
</ul>
</div>;
}

View File

@@ -2,6 +2,7 @@ import React from 'react';
import SlotSection from './SlotSection';
import Slot from './Slot';
import { stopCtxPropagation } from '../utils/UtilityFunctions';
import autoBind from 'auto-bind';
/**
* Utility Slot Section
@@ -10,28 +11,16 @@ export default class UtilitySlotSection extends SlotSection {
/**
* Constructor
* @param {Object} props React Component properties
* @param {Object} context React Component context
*/
constructor(props, context) {
super(props, context, 'utility', 'utility mounts');
this._empty = this._empty.bind(this);
this.selectedRefId = null;
this.firstRefId = 'emptyall';
this.lastRefId = 'po';
}
/**
* Handle focus if the component updates
* @param {Object} prevProps React Component properties
*/
componentDidUpdate(prevProps) {
this._handleSectionFocus(prevProps,this.firstRefId, this.lastRefId);
constructor(props) {
super(props, 'utility mounts');
autoBind(this);
}
/**
* Empty all utility slots and close the menu
*/
_empty() {
this.selectedRefId = this.firstRefId;
this.props.ship.emptyUtility();
this.props.onChange();
this._close();
@@ -45,9 +34,6 @@ export default class UtilitySlotSection extends SlotSection {
* @param {Synthetic} event Event
*/
_use(group, rating, name, event) {
this.selectedRefId = group;
if (rating !== null) this.selectedRefId += '-' + rating;
this.props.ship.useUtility(group, rating, name, event.getModifierState('Alt'));
this.props.onChange();
this._close();
@@ -73,16 +59,13 @@ export default class UtilitySlotSection extends SlotSection {
slots.push(<Slot
key={h.object.Slot}
maxClass={h.getSize()}
onOpen={this._openMenu.bind(this,h)}
onSelect={this._selectModule.bind(this, h)}
onChange={this.props.onChange}
selected={currentMenu == h}
currentMenu={currentMenu}
drag={this._drag.bind(this, h)}
dragOver={this._dragOverSlot.bind(this, h)}
drop={this._drop}
dropClass={this._dropClass(h, originSlot, targetSlot)}
ship={ship}
slot={h}
m={h}
enabled={h.enabled ? true : false}
/>);
}
@@ -95,33 +78,34 @@ export default class UtilitySlotSection extends SlotSection {
* @param {Function} translate Translate function
* @return {React.Component} Section menu
*/
_getSectionMenu(translate) {
_getSectionMenu() {
const { translate } = this.context.language;
let _use = this._use;
return <div className='select' onClick={(e) => e.stopPropagation()} onContextMenu={stopCtxPropagation}>
<ul>
<li className='lc' tabIndex='0' onClick={this._empty} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['emptyall'] = smRef}>{translate('empty all')}</li>
<li className='lc' tabIndex='0' onClick={this._empty}>{translate('empty all')}</li>
<li className='optional-hide' style={{ textAlign: 'center', marginTop: '1em' }}>{translate('PHRASE_ALT_ALL')}</li>
</ul>
<div className='select-group cap'>{translate('sb')}</div>
<ul>
<li className='c' tabIndex='0' onClick={_use.bind(this, 'sb', 'A', null)} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['sb-A'] = smRef}>A</li>
<li className='c' tabIndex='0' onClick={_use.bind(this, 'sb', 'B', null)} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['sb-B'] = smRef}>B</li>
<li className='c' tabIndex='0' onClick={_use.bind(this, 'sb', 'C', null)} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['sb-C'] = smRef}>C</li>
<li className='c' tabIndex='0' onClick={_use.bind(this, 'sb', 'D', null)} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['sb-D'] = smRef}>D</li>
<li className='c' tabIndex='0' onClick={_use.bind(this, 'sb', 'E', null)} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['sb-E'] = smRef}>E</li>
<li className='c' tabIndex='0' onClick={_use.bind(this, 'sb', 'A', null)}>A</li>
<li className='c' tabIndex='0' onClick={_use.bind(this, 'sb', 'B', null)}>B</li>
<li className='c' tabIndex='0' onClick={_use.bind(this, 'sb', 'C', null)}>C</li>
<li className='c' tabIndex='0' onClick={_use.bind(this, 'sb', 'D', null)}>D</li>
<li className='c' tabIndex='0' onClick={_use.bind(this, 'sb', 'E', null)}>E</li>
</ul>
<div className='select-group cap'>{translate('hs')}</div>
<ul>
<li className='lc' tabIndex='0' onClick={_use.bind(this, 'hs', null, 'Heat Sink Launcher')} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['hs'] = smRef}>{translate('Heat Sink Launcher')}</li>
<li className='lc' tabIndex='0' onClick={_use.bind(this, 'hs', null, 'Heat Sink Launcher')}>{translate('Heat Sink Launcher')}</li>
</ul>
<div className='select-group cap'>{translate('ch')}</div>
<ul>
<li className='lc' tabIndex='0' onClick={_use.bind(this, 'ch', null, 'Chaff Launcher')} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['ch'] = smRef}>{translate('Chaff Launcher')}</li>
<li className='lc' tabIndex='0' onClick={_use.bind(this, 'ch', null, 'Chaff Launcher')}>{translate('Chaff Launcher')}</li>
</ul>
<div className='select-group cap'>{translate('po')}</div>
<ul>
<li className='lc' tabIndex='0' onClick={_use.bind(this, 'po', null, 'Point Defence')} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['po'] = smRef}>{translate('Point Defence')}</li>
<li className='lc' tabIndex='0' onClick={_use.bind(this, 'po', null, 'Point Defence')}>{translate('Point Defence')}</li>
</ul>
</div>;
}

View File

@@ -7,6 +7,7 @@ import Router from '../Router';
import Persist from '../stores/Persist';
import * as Utils from '../utils/UtilityFunctions';
import { Factory, Ship } from 'ed-forge';
import { STATE_EVENT, OBJECT_EVENT } from 'ed-forge/lib/Ship';
import * as _ from 'lodash';
import { toDetailedBuild } from '../shipyard/Serializer';
import { outfitURL } from '../utils/UrlGenerators';
@@ -38,16 +39,7 @@ import ModalExport from '../components/ModalExport';
import ModalPermalink from '../components/ModalPermalink';
import ModalShoppingList from '../components/ModalShoppingList';
import ModalOrbis from '../components/ModalOrbis';
/**
* Document Title Generator
* @param {String} shipName Ship Name
* @param {String} buildName Build Name
* @return {String} Document title
*/
function getTitle(shipName, buildName) {
return buildName ? buildName : shipName;
}
import autoBind from 'auto-bind';
/**
* The Outfitting Page
@@ -60,13 +52,8 @@ export default class OutfittingPage extends Page {
*/
constructor(props, context) {
super(props, context);
// window.Perf = Perf;
autoBind(this);
this.state = this._initState(props, context);
this._keyDown = this._keyDown.bind(this);
this._exportBuild = this._exportBuild.bind(this);
this._opponentUpdated = this._opponentUpdated.bind(this);
this._engagementRangeUpdated = this._engagementRangeUpdated.bind(this);
this._sectionMenuRefs = {};
}
/**
@@ -81,24 +68,32 @@ export default class OutfittingPage extends Page {
let buildName = params.bn;
let savedCode = Persist.getBuild(shipId, buildName);
let code = params.code || savedCode;
let ship = code ? new Ship(code) : Factory.newShip(shipId); // Create a new Ship instance
code = ship.compress();
this._getTitle = getTitle.bind(this, ship.getShipType());
// Create a new Ship instance
const ship = code ? new Ship(code) : Factory.newShip(shipId);
ship.on(STATE_EVENT, this._shipUpdated);
ship.on(OBJECT_EVENT, this._shipUpdated);
return {
error: null,
title: this._getTitle(buildName),
costTab: Persist.getCostTab() || 'costs',
buildName,
newBuildName: buildName,
shipId,
ship,
code,
code: ship.compress(),
savedCode,
};
}
/**
* Get this pages title for the browser.
* @returns {string} Page title
*/
_getTitle() {
const { buildName } = this.state;
const { translate } = this.context.language;
return buildName || translate(this.ship.getShipType());
}
/**
* Handle build name change and update state
* @param {SyntheticEvent} event React Event
@@ -108,10 +103,12 @@ export default class OutfittingPage extends Page {
newBuildName: event.target.value
};
if (Persist.hasBuild(this.state.shipId, stateChanges.newBuildName)) {
const { ship } = this.state;
const shipId = ship.getShipType();
if (Persist.hasBuild(shipId, stateChanges.newBuildName)) {
stateChanges.savedCode = Persist.getBuild(
this.state.shipId,
stateChanges.newBuildName
shipId,
stateChanges.newBuildName,
);
} else {
stateChanges.savedCode = null;
@@ -124,9 +121,9 @@ export default class OutfittingPage extends Page {
* Update the control part of the route
*/
_updateRoute() {
const { ship, shipId, buildName } = this.state;
const { ship } = this.state;
const code = ship.compress();
this._updateRoute(shipId, buildName, code);
this._setRoute();
this.setState({ code });
}
@@ -193,13 +190,14 @@ export default class OutfittingPage extends Page {
* Save the current build
*/
_saveBuild() {
const { ship, buildName, newBuildName, shipId } = this.state;
const { ship, buildName, newBuildName } = this.state;
const shipId = ship.getShipType();
// If this is a stock ship the code won't be set, so ensure that we have it
const code = this.state.code || ship.compress();
Persist.saveBuild(shipId, newBuildName, code);
this._updateRoute(shipId, newBuildName, code);
this._setRoute();
let opponent, opponentBuild, opponentSys, opponentEng, opponentWep;
if (
@@ -232,7 +230,6 @@ export default class OutfittingPage extends Page {
opponentSys,
opponentEng,
opponentWep,
title: this._getTitle(newBuildName)
});
}
@@ -240,11 +237,12 @@ export default class OutfittingPage extends Page {
* Rename the current build
*/
_renameBuild() {
const { code, buildName, newBuildName, shipId, ship } = this.state;
const { code, buildName, newBuildName, ship } = this.state;
const shipId = ship.getShipType();
if (buildName != newBuildName && newBuildName.length) {
Persist.deleteBuild(shipId, buildName);
Persist.saveBuild(shipId, newBuildName, code);
this._updateRoute(shipId, newBuildName, code);
this._setRoute();
this.setState({
buildName: newBuildName,
code,
@@ -265,20 +263,19 @@ export default class OutfittingPage extends Page {
* Reset build to Stock/Factory defaults
*/
_resetBuild() {
const { ship, shipId, buildName } = this.state;
let { ship } = this.state;
// Rebuild ship
ship.buildWith(Ships[shipId].defaults);
// Reset controls
const code = ship.compress();
ship = Factory.newShip(ship.getShipType());
// Update state, and refresh the ship
this._updateRoute(shipId, buildName, code);
this.setState({ ship, code: undefined }, () => this._setRoute());
}
/**
* Delete the build
*/
_deleteBuild() {
const { shipId, buildName } = this.state;
const { ship, buildName } = this.state;
const shipId = ship.getShipType();
Persist.deleteBuild(shipId, buildName);
let opponentBuild;
@@ -291,7 +288,7 @@ export default class OutfittingPage extends Page {
} else {
opponentBuild = this.state.opponentBuild;
}
Router.go(outfitURL(this.state.shipId));
Router.go(outfitURL(shipId));
this.setState({ opponentBuild });
}
@@ -315,24 +312,24 @@ export default class OutfittingPage extends Page {
* Called when the code for the ship has been updated, to synchronise the rest of the data
*/
_codeUpdated() {
const { code, shipId, buildName } = this.state;
const { ship, code, buildName } = this.state;
const shipId = ship.getShipType();
this.setState({
ship: new Ship(code),
}, () => this._updateRoute(shipId, buildName, code));
this.setState(
{ ship: new Ship(code), },
() => this._setRoute(),
);
}
/**
* Called when the ship has been updated, to set the code and then update accordingly
*/
_shipUpdated() {
let { ship, shipId, buildName } = this.state;
let { ship } = this.state;
const code = ship.compress();
// Only update the state if this really has been updated
if (this.state.code != code) {
this.setState({ code }, () =>
this._updateRoute(shipId, buildName, code)
);
this.setState({ code }, () => this._setRoute());
}
}
@@ -342,8 +339,9 @@ export default class OutfittingPage extends Page {
* @param {string} buildName Current build name
* @param {string} code Serialized ship 'code'
*/
_updateRoute(shipId, buildName, code) {
Router.replace(outfitURL(shipId, code, buildName));
_setRoute() {
const { ship, code, buildName } = this.state;
Router.replace(outfitURL(ship.getShipType(), code, buildName));
}
/**
@@ -463,31 +461,10 @@ export default class OutfittingPage extends Page {
} = state,
hide = tooltip.bind(null, null),
menu = this.props.currentMenu,
shipUpdated = this._shipUpdated,
canSave = (newBuildName || buildName) && code !== savedCode,
canRename = buildName && newBuildName && buildName != newBuildName,
canReload = savedCode && canSave;
// Code can be blank for a default loadout. Prefix it with the ship name to ensure that changes in default ships is picked up
code = ship.name + (code || '');
// Markers are used to propagate state changes without requiring a deep comparison of the ship, as that takes a long time
// const _sStr = ship.getStandardString();
// const _iStr = ship.getInternalString();
// const _hStr = ship.getHardpointsString();
// const _pStr = `${ship.getPowerEnabledString()}${ship.getPowerPrioritiesString()}`;
// const _mStr = ship.getModificationsString();
// const standardSlotMarker = `${ship.name}${_sStr}${_pStr}${_mStr}${
// ship.ladenMass
// }${cargo}${fuel}`;
// const internalSlotMarker = `${ship.name}${_iStr}${_pStr}${_mStr}`;
// const hardpointsSlotMarker = `${ship.name}${_hStr}${_pStr}${_mStr}`;
// const boostMarker = `${ship.canBoost(cargo, fuel)}`;
// const shipSummaryMarker = `${
// ship.name
// }${_sStr}${_iStr}${_hStr}${_pStr}${_mStr}${ship.ladenMass}${cargo}${fuel}`;
// const requirements = Ships[ship.id].requirements;
// let requirementElements = [];
// /**
@@ -654,46 +631,11 @@ export default class OutfittingPage extends Page {
</div>
{/* Main tables */}
{/* <ShipSummaryTable
ship={ship}
marker={shipSummaryMarker}
/> */}
<StandardSlotSection
ship={ship}
// code={standardSlotMarker}
onChange={shipUpdated}
onCargoChange={this._cargoUpdated}
onFuelChange={this._fuelUpdated}
currentMenu={menu}
sectionMenuRefs={this._sectionMenuRefs}
/>
<InternalSlotSection
ship={ship}
// code={internalSlotMarker}
onChange={shipUpdated}
onCargoChange={this._cargoUpdated}
onFuelChange={this._fuelUpdated}
currentMenu={menu}
sectionMenuRefs={this._sectionMenuRefs}
/>
<HardpointSlotSection
ship={ship}
// code={hardpointsSlotMarker}
onChange={shipUpdated}
onCargoChange={this._cargoUpdated}
onFuelChange={this._fuelUpdated}
currentMenu={menu}
sectionMenuRefs={this._sectionMenuRefs}
/>
<UtilitySlotSection
ship={ship}
// code={hardpointsSlotMarker}
onChange={shipUpdated}
onCargoChange={this._cargoUpdated}
onFuelChange={this._fuelUpdated}
currentMenu={menu}
sectionMenuRefs={this._sectionMenuRefs}
/>
<ShipSummaryTable ship={ship} />
<StandardSlotSection ship={ship} currentMenu={menu} />
<InternalSlotSection ship={ship} currentMenu={menu} />
<HardpointSlotSection ship={ship} currentMenu={menu} />
<UtilitySlotSection ship={ship} currentMenu={menu} />
{/* Control of ship and opponent */}
{/* <div className="group quarter">

View File

@@ -1,3 +1,4 @@
import { Module } from 'ed-forge';
/**
* Wraps the callback/context menu handler such that the default
@@ -83,3 +84,18 @@ export function isEmpty(obj) {
}
return true;
};
/**
* Fetches a property from either a Module or a moduleInfo object
* @param {Object} m Either a Module or a moduleInfo object
* @param {string} property Property name
* @returns {number} Property value
*/
export function moduleGet(m, property) {
if (m instanceof Module) {
return m.get(property);
} else {
// Assume its a moduleInfo object
return m.props[property];
}
}