Feature/#293 header keynav (#303)

Added keydown and focus handlers for Slot Section Menus ("Core Internal", "Optional Internal", etc.)

When focus is on the header, Enter key will open the menu and set focus to either the first option, or else the currently selected option, such as "Planetary Explorer" in Core Internal menu (if one has been previously selected).

While menu is open, Tab and Shift-Tab will move the focus up and down as expected. Shift-tab on first option will move focus to last option in the menu, and Tab on the last option will move focus to the top. Focus will stay inside the menu until menu is closed.

When focus is on a menu options, hitting the Enter key will trigger the onClick function for that option, and will set the option as the currently selected option for that menu.

Esc key will close the menu and set focus to the menu header H1 element.
This commit is contained in:
Pat Nellesen
2018-06-09 18:00:43 -05:00
committed by William
parent 3e77e23d71
commit 9b81f6efd2
11 changed files with 1540 additions and 1450 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -17,14 +17,25 @@ export default class HardpointSlotSection extends SlotSection {
*/ */
constructor(props, context) { constructor(props, context) {
super(props, context, 'hardpoints', 'hardpoints'); super(props, context, 'hardpoints', 'hardpoints');
this._empty = this._empty.bind(this); 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);
} }
/** /**
* Empty all slots * Empty all slots
*/ */
_empty() { _empty() {
this.selectedRefId = 'emptyall';
this.props.ship.emptyWeapons(); this.props.ship.emptyWeapons();
this.props.onChange(); this.props.onChange();
this._close(); this._close();
@@ -37,6 +48,7 @@ export default class HardpointSlotSection extends SlotSection {
* @param {SyntheticEvent} event Event * @param {SyntheticEvent} event Event
*/ */
_fill(group, mount, event) { _fill(group, mount, event) {
this.selectedRefId = group + '-' + mount;
this.props.ship.useWeapon(group, mount, null, event.getModifierState('Alt')); this.props.ship.useWeapon(group, mount, null, event.getModifierState('Alt'));
this.props.onChange(); this.props.onChange();
this._close(); this._close();
@@ -95,52 +107,52 @@ export default class HardpointSlotSection extends SlotSection {
return <div className='select hardpoint' onClick={(e) => e.stopPropagation()} onContextMenu={stopCtxPropagation}> return <div className='select hardpoint' onClick={(e) => e.stopPropagation()} onContextMenu={stopCtxPropagation}>
<ul> <ul>
<li className='lc' onClick={this._empty}>{translate('empty all')}</li> <li className='lc' tabIndex='0' onClick={this._empty} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['emptyall'] = smRef}>{translate('empty all')}</li>
<li className='optional-hide' style={{ textAlign: 'center', marginTop: '1em' }}>{translate('PHRASE_ALT_ALL')}</li> <li className='optional-hide' style={{ textAlign: 'center', marginTop: '1em' }}>{translate('PHRASE_ALT_ALL')}</li>
</ul> </ul>
<div className='select-group cap'>{translate('pl')}</div> <div className='select-group cap'>{translate('pl')}</div>
<ul> <ul>
<li className='c' onClick={_fill.bind(this, 'pl', 'F')}><MountFixed className='lg'/></li> <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' onClick={_fill.bind(this, 'pl', 'G')}><MountGimballed 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' onClick={_fill.bind(this, 'pl', 'T')}><MountTurret 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>
</ul> </ul>
<div className='select-group cap'>{translate('ul')}</div> <div className='select-group cap'>{translate('ul')}</div>
<ul> <ul>
<li className='c' onClick={_fill.bind(this, 'ul', 'F')}><MountFixed className='lg'/></li> <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' onClick={_fill.bind(this, 'ul', 'G')}><MountGimballed 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' onClick={_fill.bind(this, 'ul', 'T')}><MountTurret 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>
</ul> </ul>
<div className='select-group cap'>{translate('bl')}</div> <div className='select-group cap'>{translate('bl')}</div>
<ul> <ul>
<li className='c' onClick={_fill.bind(this, 'bl', 'F')}><MountFixed className='lg'/></li> <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' onClick={_fill.bind(this, 'bl', 'G')}><MountGimballed 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' onClick={_fill.bind(this, 'bl', 'T')}><MountTurret 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>
</ul> </ul>
<div className='select-group cap'>{translate('mc')}</div> <div className='select-group cap'>{translate('mc')}</div>
<ul> <ul>
<li className='c' onClick={_fill.bind(this, 'mc', 'F')}><MountFixed className='lg'/></li> <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' onClick={_fill.bind(this, 'mc', 'G')}><MountGimballed 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' onClick={_fill.bind(this, 'mc', 'T')}><MountTurret 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>
</ul> </ul>
<div className='select-group cap'>{translate('c')}</div> <div className='select-group cap'>{translate('c')}</div>
<ul> <ul>
<li className='c' onClick={_fill.bind(this, 'c', 'F')}><MountFixed className='lg'/></li> <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' onClick={_fill.bind(this, 'c', 'G')}><MountGimballed 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' onClick={_fill.bind(this, 'c', 'T')}><MountTurret 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>
</ul> </ul>
<div className='select-group cap'>{translate('fc')}</div> <div className='select-group cap'>{translate('fc')}</div>
<ul> <ul>
<li className='c' onClick={_fill.bind(this, 'fc', 'F')}><MountFixed className='lg'/></li> <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' onClick={_fill.bind(this, 'fc', 'G')}><MountGimballed 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' onClick={_fill.bind(this, 'fc', 'T')}><MountTurret 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>
</ul> </ul>
<div className='select-group cap'>{translate('pa')}</div> <div className='select-group cap'>{translate('pa')}</div>
<ul> <ul>
<li className='lc' onClick={_fill.bind(this, 'pa', 'F')}>{translate('pa')}</li> <li className='lc' tabIndex='0' onClick={_fill.bind(this, 'pa', 'F')} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['pa-F'] = smRef}>{translate('pa')}</li>
</ul> </ul>
<div className='select-group cap'>{translate('nl')}</div> <div className='select-group cap'>{translate('nl')}</div>
<ul> <ul>
<li className='lc' onClick={_fill.bind(this, 'nl', 'F')}>{translate('nl')}</li> <li className='lc' tabIndex='0' onClick={_fill.bind(this, 'nl', 'F')} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['nl-F'] = smRef}>{translate('nl')}</li>
</ul> </ul>
</div>; </div>;
} }

View File

@@ -18,7 +18,6 @@ export default class InternalSlotSection extends SlotSection {
*/ */
constructor(props, context) { constructor(props, context) {
super(props, context, 'internal', 'optional internal'); super(props, context, 'internal', 'optional internal');
this._empty = this._empty.bind(this); this._empty = this._empty.bind(this);
this._fillWithCargo = this._fillWithCargo.bind(this); this._fillWithCargo = this._fillWithCargo.bind(this);
this._fillWithCells = this._fillWithCells.bind(this); this._fillWithCells = this._fillWithCells.bind(this);
@@ -29,12 +28,24 @@ export default class InternalSlotSection extends SlotSection {
this._fillWithFirstClassCabins = this._fillWithFirstClassCabins.bind(this); this._fillWithFirstClassCabins = this._fillWithFirstClassCabins.bind(this);
this._fillWithBusinessClassCabins = this._fillWithBusinessClassCabins.bind(this); this._fillWithBusinessClassCabins = this._fillWithBusinessClassCabins.bind(this);
this._fillWithEconomyClassCabins = this._fillWithEconomyClassCabins.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);
} }
/** /**
* Empty all slots * Empty all slots
*/ */
_empty() { _empty() {
this.selectedRefId = 'emptyall';
this.props.ship.emptyInternal(); this.props.ship.emptyInternal();
this.props.onChange(); this.props.onChange();
this._close(); this._close();
@@ -45,6 +56,7 @@ export default class InternalSlotSection extends SlotSection {
* @param {SyntheticEvent} event Event * @param {SyntheticEvent} event Event
*/ */
_fillWithCargo(event) { _fillWithCargo(event) {
this.selectedRefId = 'cargo';
let clobber = event.getModifierState('Alt'); let clobber = event.getModifierState('Alt');
let ship = this.props.ship; let ship = this.props.ship;
ship.internal.forEach((slot) => { ship.internal.forEach((slot) => {
@@ -61,6 +73,7 @@ export default class InternalSlotSection extends SlotSection {
* @param {SyntheticEvent} event Event * @param {SyntheticEvent} event Event
*/ */
_fillWithFuelTanks(event) { _fillWithFuelTanks(event) {
this.selectedRefId = 'ft';
let clobber = event.getModifierState('Alt'); let clobber = event.getModifierState('Alt');
let ship = this.props.ship; let ship = this.props.ship;
ship.internal.forEach((slot) => { ship.internal.forEach((slot) => {
@@ -77,6 +90,7 @@ export default class InternalSlotSection extends SlotSection {
* @param {SyntheticEvent} event Event * @param {SyntheticEvent} event Event
*/ */
_fillWithLuxuryCabins(event) { _fillWithLuxuryCabins(event) {
this.selectedRefId = 'pcq';
let clobber = event.getModifierState('Alt'); let clobber = event.getModifierState('Alt');
let ship = this.props.ship; let ship = this.props.ship;
ship.internal.forEach((slot) => { ship.internal.forEach((slot) => {
@@ -93,6 +107,7 @@ export default class InternalSlotSection extends SlotSection {
* @param {SyntheticEvent} event Event * @param {SyntheticEvent} event Event
*/ */
_fillWithFirstClassCabins(event) { _fillWithFirstClassCabins(event) {
this.selectedRefId = 'pcm';
let clobber = event.getModifierState('Alt'); let clobber = event.getModifierState('Alt');
let ship = this.props.ship; let ship = this.props.ship;
ship.internal.forEach((slot) => { ship.internal.forEach((slot) => {
@@ -109,6 +124,7 @@ export default class InternalSlotSection extends SlotSection {
* @param {SyntheticEvent} event Event * @param {SyntheticEvent} event Event
*/ */
_fillWithBusinessClassCabins(event) { _fillWithBusinessClassCabins(event) {
this.selectedRefId = 'pci';
let clobber = event.getModifierState('Alt'); let clobber = event.getModifierState('Alt');
let ship = this.props.ship; let ship = this.props.ship;
ship.internal.forEach((slot) => { ship.internal.forEach((slot) => {
@@ -125,6 +141,7 @@ export default class InternalSlotSection extends SlotSection {
* @param {SyntheticEvent} event Event * @param {SyntheticEvent} event Event
*/ */
_fillWithEconomyClassCabins(event) { _fillWithEconomyClassCabins(event) {
this.selectedRefId = 'pce';
let clobber = event.getModifierState('Alt'); let clobber = event.getModifierState('Alt');
let ship = this.props.ship; let ship = this.props.ship;
ship.internal.forEach((slot) => { ship.internal.forEach((slot) => {
@@ -141,6 +158,7 @@ export default class InternalSlotSection extends SlotSection {
* @param {SyntheticEvent} event Event * @param {SyntheticEvent} event Event
*/ */
_fillWithCells(event) { _fillWithCells(event) {
this.selectedRefId = 'scb';
let clobber = event.getModifierState('Alt'); let clobber = event.getModifierState('Alt');
let ship = this.props.ship; let ship = this.props.ship;
let chargeCap = 0; // Capacity of single activation let chargeCap = 0; // Capacity of single activation
@@ -160,6 +178,7 @@ export default class InternalSlotSection extends SlotSection {
* @param {SyntheticEvent} event Event * @param {SyntheticEvent} event Event
*/ */
_fillWithArmor(event) { _fillWithArmor(event) {
this.selectedRefId = 'hr';
let clobber = event.getModifierState('Alt'); let clobber = event.getModifierState('Alt');
let ship = this.props.ship; let ship = this.props.ship;
ship.internal.forEach((slot) => { ship.internal.forEach((slot) => {
@@ -176,6 +195,7 @@ export default class InternalSlotSection extends SlotSection {
* @param {SyntheticEvent} event Event * @param {SyntheticEvent} event Event
*/ */
_fillWithModuleReinforcementPackages(event) { _fillWithModuleReinforcementPackages(event) {
this.selectedRefId = 'mrp';
let clobber = event.getModifierState('Alt'); let clobber = event.getModifierState('Alt');
let ship = this.props.ship; let ship = this.props.ship;
ship.internal.forEach((slot) => { ship.internal.forEach((slot) => {
@@ -240,16 +260,16 @@ export default class InternalSlotSection extends SlotSection {
_getSectionMenu(translate, ship) { _getSectionMenu(translate, ship) {
return <div className='select' onClick={e => e.stopPropagation()} onContextMenu={stopCtxPropagation}> return <div className='select' onClick={e => e.stopPropagation()} onContextMenu={stopCtxPropagation}>
<ul> <ul>
<li className='lc' onClick={this._empty}>{translate('empty all')}</li> <li className='lc' tabIndex='0' onClick={this._empty} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['emptyall'] = smRef}>{translate('empty all')}</li>
<li className='lc' onClick={this._fillWithCargo}>{translate('cargo')}</li> <li className='lc' tabIndex='0' onClick={this._fillWithCargo} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['cargo'] = smRef}>{translate('cargo')}</li>
<li className='lc' onClick={this._fillWithCells}>{translate('scb')}</li> <li className='lc' tabIndex='0' onClick={this._fillWithCells} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['scb'] = smRef}>{translate('scb')}</li>
<li className='lc' onClick={this._fillWithArmor}>{translate('hr')}</li> <li className='lc' tabIndex='0' onClick={this._fillWithArmor} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['hr'] = smRef}>{translate('hr')}</li>
<li className='lc' onClick={this._fillWithModuleReinforcementPackages}>{translate('mrp')}</li> <li className='lc' tabIndex='0' onClick={this._fillWithModuleReinforcementPackages} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['mrp'] = smRef}>{translate('mrp')}</li>
<li className='lc' onClick={this._fillWithFuelTanks}>{translate('ft')}</li> <li className='lc' tabIndex='0' onClick={this._fillWithFuelTanks} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['ft'] = smRef}>{translate('ft')}</li>
<li className='lc' onClick={this._fillWithEconomyClassCabins}>{translate('pce')}</li> <li className='lc' tabIndex='0' onClick={this._fillWithEconomyClassCabins} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['pce'] = smRef}>{translate('pce')}</li>
<li className='lc' onClick={this._fillWithBusinessClassCabins}>{translate('pci')}</li> <li className='lc' tabIndex='0' onClick={this._fillWithBusinessClassCabins} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['pci'] = smRef}>{translate('pci')}</li>
<li className='lc' onClick={this._fillWithFirstClassCabins}>{translate('pcm')}</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' onClick={this._fillWithLuxuryCabins}>{translate('pcq')}</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='optional-hide' style={{ textAlign: 'center', marginTop: '1em' }}>{translate('PHRASE_ALT_ALL')}</li> <li className='optional-hide' style={{ textAlign: 'center', marginTop: '1em' }}>{translate('PHRASE_ALT_ALL')}</li>
</ul> </ul>
</div>; </div>;

View File

@@ -1,476 +1,474 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import * as _ from 'lodash'; import * as _ from 'lodash';
import TranslatedComponent from './TranslatedComponent'; import TranslatedComponent from './TranslatedComponent';
import { isEmpty, stopCtxPropagation } from '../utils/UtilityFunctions'; import { isEmpty, stopCtxPropagation } from '../utils/UtilityFunctions';
import cn from 'classnames'; import cn from 'classnames';
import { Modifications } from 'coriolis-data/dist'; import { Modifications } from 'coriolis-data/dist';
import Modification from './Modification'; import Modification from './Modification';
import { import {
getBlueprint, getBlueprint,
blueprintTooltip, blueprintTooltip,
setPercent, setPercent,
getPercent, getPercent,
setRandom, setRandom,
specialToolTip specialToolTip
} from '../utils/BlueprintFunctions'; } from '../utils/BlueprintFunctions';
/** /**
* Modifications menu * Modifications menu
*/ */
export default class ModificationsMenu extends TranslatedComponent { export default class ModificationsMenu extends TranslatedComponent {
static propTypes = { static propTypes = {
ship: PropTypes.object.isRequired, ship: PropTypes.object.isRequired,
m: PropTypes.object.isRequired, m: PropTypes.object.isRequired,
marker: PropTypes.string.isRequired, marker: PropTypes.string.isRequired,
onChange: PropTypes.func.isRequired, onChange: PropTypes.func.isRequired,
modButton:PropTypes.object modButton:PropTypes.object
}; };
/** /**
* Constructor * Constructor
* @param {Object} props React Component properties * @param {Object} props React Component properties
* @param {Object} context React Component context * @param {Object} context React Component context
*/ */
constructor(props, context) { constructor(props, context) {
super(props); super(props);
this._toggleBlueprintsMenu = this._toggleBlueprintsMenu.bind(this); this._toggleBlueprintsMenu = this._toggleBlueprintsMenu.bind(this);
this._toggleSpecialsMenu = this._toggleSpecialsMenu.bind(this); this._toggleSpecialsMenu = this._toggleSpecialsMenu.bind(this);
this._rollFifty = this._rollFifty.bind(this); this._rollFifty = this._rollFifty.bind(this);
this._rollRandom = this._rollRandom.bind(this); this._rollRandom = this._rollRandom.bind(this);
this._rollBest = this._rollBest.bind(this); this._rollBest = this._rollBest.bind(this);
this._rollWorst = this._rollWorst.bind(this); this._rollWorst = this._rollWorst.bind(this);
this._reset = this._reset.bind(this); this._reset = this._reset.bind(this);
this._keyDown = this._keyDown.bind(this); this._keyDown = this._keyDown.bind(this);
this.modItems = [];// Array to hold various element refs (<li>, <div>, <ul>, etc.) this.modItems = [];// Array to hold various element refs (<li>, <div>, <ul>, etc.)
this.firstModId = null; this.firstModId = null;
this.firstBPLabel = null;// First item in mod menu this.firstBPLabel = null;// First item in mod menu
this.lastModId = null; this.lastModId = null;
this.lastNeId = null;// Last number editor id. Used to set focus to last number editor when shift-tab pressed on first element in mod menu. this.lastNeId = null;// Last number editor id. Used to set focus to last number editor when shift-tab pressed on first element in mod menu.
this.modValDidChange = false; // used to determine if component update was caused by change in modification value. this.modValDidChange = false; // used to determine if component update was caused by change in modification value.
this._handleModChange = this._handleModChange.bind(this); this._handleModChange = this._handleModChange.bind(this);
this.state = { this.state = {
blueprintMenuOpened: !(props.m.blueprint && props.m.blueprint.name), blueprintMenuOpened: !(props.m.blueprint && props.m.blueprint.name),
specialMenuOpened: false specialMenuOpened: false
}; };
} }
/** /**
* Render the blueprints * Render the blueprints
* @param {Object} props React component properties * @param {Object} props React component properties
* @param {Object} context React component context * @param {Object} context React component context
* @return {Object} list: Array of React Components * @return {Object} list: Array of React Components
*/ */
_renderBlueprints(props, context) { _renderBlueprints(props, context) {
const { m } = props; const { m } = props;
const { language, tooltip, termtip } = context; const { language, tooltip, termtip } = context;
const translate = language.translate; const translate = language.translate;
const blueprints = []; const blueprints = [];
for (const blueprintName in Modifications.modules[m.grp].blueprints) { for (const blueprintName in Modifications.modules[m.grp].blueprints) {
const blueprint = getBlueprint(blueprintName, m); const blueprint = getBlueprint(blueprintName, m);
let blueprintGrades = []; let blueprintGrades = [];
for (let grade in Modifications.modules[m.grp].blueprints[blueprintName].grades) { for (let grade in Modifications.modules[m.grp].blueprints[blueprintName].grades) {
// Grade is a string in the JSON so make it a number // Grade is a string in the JSON so make it a number
grade = Number(grade); grade = Number(grade);
const classes = cn('c', { const classes = cn('c', {
active: m.blueprint && blueprint.id === m.blueprint.id && grade === m.blueprint.grade active: m.blueprint && blueprint.id === m.blueprint.id && grade === m.blueprint.grade
}); });
const close = this._blueprintSelected.bind(this, blueprintName, grade); const close = this._blueprintSelected.bind(this, blueprintName, grade);
const key = blueprintName + ':' + grade; const key = blueprintName + ':' + grade;
const tooltipContent = blueprintTooltip(translate, blueprint.grades[grade], Modifications.modules[m.grp].blueprints[blueprintName].grades[grade].engineers, m.grp); const tooltipContent = blueprintTooltip(translate, blueprint.grades[grade], Modifications.modules[m.grp].blueprints[blueprintName].grades[grade].engineers, m.grp);
blueprintGrades.unshift(<li key={key} tabIndex="0" data-id={key} className={classes} style={{ width: '2em' }} onMouseOver={termtip.bind(null, tooltipContent)} onMouseOut={tooltip.bind(null, null)} onClick={close} onKeyDown={this._keyDown} ref={modItem => this.modItems[key] = modItem}>{grade}</li>); blueprintGrades.unshift(<li key={key} tabIndex="0" data-id={key} className={classes} style={{ width: '2em' }} onMouseOver={termtip.bind(null, tooltipContent)} onMouseOut={tooltip.bind(null, null)} onClick={close} onKeyDown={this._keyDown} ref={modItem => this.modItems[key] = modItem}>{grade}</li>);
} }
if (blueprintGrades) { if (blueprintGrades) {
const thisLen = blueprintGrades.length; const thisLen = blueprintGrades.length;
if (this.firstModId == null) this.firstModId = blueprintGrades[0].key; if (this.firstModId == null) this.firstModId = blueprintGrades[0].key;
this.lastModId = blueprintGrades[thisLen - 1].key; this.lastModId = blueprintGrades[thisLen - 1].key;
blueprints.push(<div key={blueprint.name} className={'select-group cap'}>{translate(blueprint.name)}</div>); blueprints.push(<div key={blueprint.name} className={'select-group cap'}>{translate(blueprint.name)}</div>);
blueprints.push(<ul key={blueprintName}>{blueprintGrades}</ul>); blueprints.push(<ul key={blueprintName}>{blueprintGrades}</ul>);
} }
} }
return blueprints; return blueprints;
} }
/** /**
* Key down - select module on Enter key, move to next/previous module on Tab/Shift-Tab, close on Esc * Key down - select module on Enter key, move to next/previous module on Tab/Shift-Tab, close on Esc
* @param {Function} event Select module callback * @param {SyntheticEvent} event Event
*/ *
_keyDown(event) { */
let className = null; _keyDown(event) {
let elemId = null; let className = null;
if (event.currentTarget.attributes['class']) className = event.currentTarget.attributes['class'].value; let elemId = null;
if (event.currentTarget.attributes['data-id']) elemId = event.currentTarget.attributes['data-id'].value; if (event.currentTarget.attributes['class']) className = event.currentTarget.attributes['class'].value;
if (event.currentTarget.attributes['data-id']) elemId = event.currentTarget.attributes['data-id'].value;
if (event.key == 'Enter' && className.indexOf('disabled') < 0 && className.indexOf('active') < 0) {
event.stopPropagation(); if (event.key == 'Enter' && className.indexOf('disabled') < 0 && className.indexOf('active') < 0) {
if (elemId != null) { event.stopPropagation();
this.modItems[elemId].click(); if (elemId != null) {
} else { this.modItems[elemId].click();
event.currentTarget.click(); } else {
} event.currentTarget.click();
return; }
} return;
if (event.key == 'Tab') { }
// Shift-Tab if (event.key == 'Tab') {
if(event.shiftKey) { // Shift-Tab
if (elemId == this.firstModId && elemId != null) { if(event.shiftKey) {
// Initial modification menu if (elemId == this.firstModId && elemId != null) {
event.preventDefault(); // Initial modification menu
this.modItems[this.lastModId].focus(); event.preventDefault();
return; this.modItems[this.lastModId].focus();
} else if (event.currentTarget.className.indexOf('button-inline-menu') >= 0 && event.currentTarget.previousElementSibling == null && this.lastNeId != null && this.modItems[this.lastNeId] != null) { return;
// shift-tab on first element in modifications menu. set focus to last number editor field if open } else if (event.currentTarget.className.indexOf('button-inline-menu') >= 0 && event.currentTarget.previousElementSibling == null && this.lastNeId != null && this.modItems[this.lastNeId] != null) {
event.preventDefault(); // shift-tab on first element in modifications menu. set focus to last number editor field if open
this.modItems[this.lastNeId].lastChild.focus(); event.preventDefault();
return; this.modItems[this.lastNeId].lastChild.focus();
} else if (event.currentTarget.className.indexOf('button-inline-menu') >= 0 && event.currentTarget.previousElementSibling == null) { return;
// shift-tab on button-inline-menu with no number editor } else if (event.currentTarget.className.indexOf('button-inline-menu') >= 0 && event.currentTarget.previousElementSibling == null) {
event.preventDefault(); // shift-tab on button-inline-menu with no number editor
event.currentTarget.parentElement.lastElementChild.focus(); event.preventDefault();
} event.currentTarget.parentElement.lastElementChild.focus();
} else { }
if (elemId == this.lastModId && elemId != null) { } else {
// Initial modification menu if (elemId == this.lastModId && elemId != null) {
event.preventDefault(); // Initial modification menu
this.modItems[this.firstModId].focus(); event.preventDefault();
return; this.modItems[this.firstModId].focus();
} else if (event.currentTarget.className.indexOf('button-inline-menu') >= 0 && event.currentTarget.nextSibling == null && event.currentTarget.nodeName != 'TD') { return;
// Experimental menu } else if (event.currentTarget.className.indexOf('button-inline-menu') >= 0 && event.currentTarget.nextSibling == null && event.currentTarget.nodeName != 'TD') {
event.preventDefault(); // Experimental menu
event.currentTarget.parentElement.firstElementChild.focus(); event.preventDefault();
return; event.currentTarget.parentElement.firstElementChild.focus();
} else if (event.currentTarget.className == 'cb' && event.currentTarget.parentElement.nextSibling == null) { return;
event.preventDefault(); } else if (event.currentTarget.className == 'cb' && event.currentTarget.parentElement.nextSibling == null) {
this.modItems[this.firstBPLabel].focus(); event.preventDefault();
} this.modItems[this.firstBPLabel].focus();
} }
} }
} }
}
/**
* Render the specials /**
* @param {Object} props React component properties * Render the specials
* @param {Object} context React component context * @param {Object} props React component properties
* @return {Object} list: Array of React Components * @param {Object} context React component context
*/ * @return {Object} list: Array of React Components
_renderSpecials(props, context) { */
const { m } = props; _renderSpecials(props, context) {
const { language, tooltip, termtip } = context; const { m } = props;
const translate = language.translate; const { language, tooltip, termtip } = context;
const specials = []; const translate = language.translate;
const specialsId = m.missile && Modifications.modules[m.grp]['specials_' + m.missile] ? 'specials_' + m.missile : 'specials'; const specials = [];
if (Modifications.modules[m.grp][specialsId] && Modifications.modules[m.grp][specialsId].length > 0) { const specialsId = m.missile && Modifications.modules[m.grp]['specials_' + m.missile] ? 'specials_' + m.missile : 'specials';
const close = this._specialSelected.bind(this, null); if (Modifications.modules[m.grp][specialsId] && Modifications.modules[m.grp][specialsId].length > 0) {
specials.push(<div tabIndex="0" style={{ cursor: 'pointer', fontWeight: 'bold' }} className={ 'button-inline-menu warning' } key={ 'none' } data-id={ 'none' } onClick={ close } onKeyDown={this._keyDown} ref={modItem => this.modItems['none'] = modItem}>{translate('PHRASE_NO_SPECIAL')}</div>); const close = this._specialSelected.bind(this, null);
for (const specialName of Modifications.modules[m.grp][specialsId]) { specials.push(<div tabIndex="0" style={{ cursor: 'pointer', fontWeight: 'bold' }} className={ 'button-inline-menu warning' } key={ 'none' } data-id={ 'none' } onClick={ close } onKeyDown={this._keyDown} ref={modItem => this.modItems['none'] = modItem}>{translate('PHRASE_NO_SPECIAL')}</div>);
if (Modifications.specials[specialName].name.search('Legacy') >= 0) { for (const specialName of Modifications.modules[m.grp][specialsId]) {
continue; if (Modifications.specials[specialName].name.search('Legacy') >= 0) {
} continue;
const classes = cn('button-inline-menu', { }
active: m.blueprint && m.blueprint.special && m.blueprint.special.edname == specialName const classes = cn('button-inline-menu', {
}); active: m.blueprint && m.blueprint.special && m.blueprint.special.edname == specialName
const close = this._specialSelected.bind(this, specialName); });
if (m.blueprint && m.blueprint.name) { const close = this._specialSelected.bind(this, specialName);
let tmp = {}; if (m.blueprint && m.blueprint.name) {
if (m.blueprint.special) { let tmp = {};
tmp = m.blueprint.special; if (m.blueprint.special) {
} else { tmp = m.blueprint.special;
tmp = undefined; } else {
} tmp = undefined;
m.blueprint.special = Modifications.specials[specialName]; }
let specialTt = specialToolTip(translate, m.blueprint.grades[m.blueprint.grade], m.grp, m, specialName); m.blueprint.special = Modifications.specials[specialName];
m.blueprint.special = tmp; let specialTt = specialToolTip(translate, m.blueprint.grades[m.blueprint.grade], m.grp, m, specialName);
specials.push(<div tabIndex="0" style={{ cursor: 'pointer' }} className={classes} key={ specialName } data-id={ specialName } onMouseOver={termtip.bind(null, specialTt)} onMouseOut={tooltip.bind(null, null)} onClick={ close } onKeyDown={this._keyDown} ref={modItem => this.modItems[specialName] = modItem}>{translate(Modifications.specials[specialName].name)}</div>); m.blueprint.special = tmp;
} else { specials.push(<div tabIndex="0" style={{ cursor: 'pointer' }} className={classes} key={ specialName } data-id={ specialName } onMouseOver={termtip.bind(null, specialTt)} onMouseOut={tooltip.bind(null, null)} onClick={ close } onKeyDown={this._keyDown} ref={modItem => this.modItems[specialName] = modItem}>{translate(Modifications.specials[specialName].name)}</div>);
specials.push(<div tabIndex="0" style={{ cursor: 'pointer' }} className={classes} key={ specialName } data-id={ specialName }onClick={ close } onKeyDown={this._keyDown} ref={modItem => this.modItems[specialName] = modItem}>{translate(Modifications.specials[specialName].name)}</div>); } else {
} specials.push(<div tabIndex="0" style={{ cursor: 'pointer' }} className={classes} key={ specialName } data-id={ specialName }onClick={ close } onKeyDown={this._keyDown} ref={modItem => this.modItems[specialName] = modItem}>{translate(Modifications.specials[specialName].name)}</div>);
} }
} }
console.log('_renderSpecials. specials: %O', specials); }
return specials; return specials;
} }
/** /**
* Render the modifications * Render the modifications
* @param {Object} props React Component properties * @param {Object} props React Component properties
* @return {Object} list: Array of React Components * @return {Object} list: Array of React Components
*/ */
_renderModifications(props) { _renderModifications(props) {
const { m, onChange, ship } = props; const { m, onChange, ship } = props;
const modifications = []; const modifications = [];
for (const modName of Modifications.modules[m.grp].modifications) { for (const modName of Modifications.modules[m.grp].modifications) {
if (!Modifications.modifications[modName].hidden) { if (!Modifications.modifications[modName].hidden) {
const key = modName + (m.getModValue(modName) / 100 || 0); const key = modName + (m.getModValue(modName) / 100 || 0);
this.lastNeId = modName; this.lastNeId = modName;
modifications.push(<Modification key={ key } ship={ ship } m={ m } name={ modName } value={ m.getModValue(modName) / 100 || 0 } onChange={ onChange } onKeyDown={ this._keyDown } modItems={ this.modItems } handleModChange = {this._handleModChange} />); modifications.push(<Modification key={ key } ship={ ship } m={ m } name={ modName } value={ m.getModValue(modName) / 100 || 0 } onChange={ onChange } onKeyDown={ this._keyDown } modItems={ this.modItems } handleModChange = {this._handleModChange} />);
} }
} }
console.log('_renderModifications. modItems: %O', this.modItems); return modifications;
return modifications; }
}
/**
/** * Toggle the blueprints menu
* Toggle the blueprints menu */
*/ _toggleBlueprintsMenu() {
_toggleBlueprintsMenu() { const blueprintMenuOpened = !this.state.blueprintMenuOpened;
const blueprintMenuOpened = !this.state.blueprintMenuOpened; this.setState({ blueprintMenuOpened });
this.setState({ blueprintMenuOpened }); }
}
/**
/** * Activated when a blueprint is selected
* Activated when a blueprint is selected * @param {int} fdname The Frontier name of the blueprint
* @param {int} fdname The Frontier name of the blueprint * @param {int} grade The grade of the selected blueprint
* @param {int} grade The grade of the selected blueprint */
*/ _blueprintSelected(fdname, grade) {
_blueprintSelected(fdname, grade) { this.context.tooltip(null);
this.context.tooltip(null); const { m, ship } = this.props;
const { m, ship } = this.props; const blueprint = getBlueprint(fdname, m);
const blueprint = getBlueprint(fdname, m); blueprint.grade = grade;
blueprint.grade = grade; ship.setModuleBlueprint(m, blueprint);
ship.setModuleBlueprint(m, blueprint); setPercent(ship, m, 100);
setPercent(ship, m, 100);
this.setState({ blueprintMenuOpened: false, specialMenuOpened: true });
this.setState({ blueprintMenuOpened: false, specialMenuOpened: true }); this.props.onChange();
this.props.onChange(); }
}
/**
/** * Toggle the specials menu
* Toggle the specials menu */
*/ _toggleSpecialsMenu() {
_toggleSpecialsMenu() { const specialMenuOpened = !this.state.specialMenuOpened;
const specialMenuOpened = !this.state.specialMenuOpened; this.setState({ specialMenuOpened });
this.setState({ specialMenuOpened }); }
}
/**
/** * Activated when a special is selected
* Activated when a special is selected * @param {int} special The name of the selected special
* @param {int} special The name of the selected special */
*/ _specialSelected(special) {
_specialSelected(special) { this.context.tooltip(null);
this.context.tooltip(null); const { m, ship } = this.props;
const { m, ship } = this.props;
if (special === null) {
if (special === null) { ship.clearModuleSpecial(m);
ship.clearModuleSpecial(m); } else {
} else { ship.setModuleSpecial(m, Modifications.specials[special]);
ship.setModuleSpecial(m, Modifications.specials[special]); }
}
this.setState({ specialMenuOpened: false });
this.setState({ specialMenuOpened: false }); this.props.onChange();
this.props.onChange(); }
}
/**
/** * Provide a '50%' roll within the information we have
* Provide a '50%' roll within the information we have */
*/ _rollFifty() {
_rollFifty() { const { m, ship } = this.props;
const { m, ship } = this.props; setPercent(ship, m, 50);
setPercent(ship, m, 50);
// this will change the values in the modifications. Set modDidChange to true to prevent focus change when component updates
// this will change the values in the modifications. Set modDidChange to true to prevent focus change when component updates this._handleModChange(true);
this._handleModChange(true);
this.props.onChange();
this.props.onChange(); }
}
/**
/** * Provide a random roll within the information we have
* Provide a random roll within the information we have */
*/ _rollRandom() {
_rollRandom() { const { m, ship } = this.props;
const { m, ship } = this.props; setRandom(ship, m);
setRandom(ship, m);
// this will change the values in the modifications. Set modDidChange to true to prevent focus change when component updates
// this will change the values in the modifications. Set modDidChange to true to prevent focus change when component updates this._handleModChange(true);
this._handleModChange(true);
this.props.onChange();
this.props.onChange(); }
}
/**
/** * Provide a 'best' roll within the information we have
* Provide a 'best' roll within the information we have */
*/ _rollBest() {
_rollBest() { const { m, ship } = this.props;
const { m, ship } = this.props; setPercent(ship, m, 100);
setPercent(ship, m, 100);
// this will change the values in the modifications. Set modDidChange to true to prevent focus change when component updates
// this will change the values in the modifications. Set modDidChange to true to prevent focus change when component updates this._handleModChange(true);
this._handleModChange(true);
this.props.onChange();
this.props.onChange(); }
}
/**
/** * Provide a 'worst' roll within the information we have
* Provide a 'worst' roll within the information we have */
*/ _rollWorst() {
_rollWorst() { const { m, ship } = this.props;
const { m, ship } = this.props; setPercent(ship, m, 0);
setPercent(ship, m, 0); // this will change the values in the modifications. Set modDidChange to true to prevent focus change when component updates
this._handleModChange(true);
// this will change the values in the modifications. Set modDidChange to true to prevent focus change when component updates this.props.onChange();
this._handleModChange(true); }
this.props.onChange(); /**
} * Reset modification information
*/
/** _reset() {
* Reset modification information const { m, ship } = this.props;
*/ ship.clearModifications(m);
_reset() { ship.clearModuleBlueprint(m);
const { m, ship } = this.props; this.props.onChange();
ship.clearModifications(m); }
ship.clearModuleBlueprint(m);
this.props.onChange(); /**
} * set mod did change boolean
* @param {boolean} b Boolean to determine if a change has been made to a module
/** */
* set mod did change boolean _handleModChange(b) {
* @param {Boolean} b Did the mod value change? this.modValDidChange = b;
*/ }
_handleModChange(b) {
this.modValDidChange = b; /**
} * Set focus on first element in modifications menu
/** * after it first mounts
* Set focus on first element in modifications menu */
* after it first mounts componentDidMount() {
*/ let firstEleCn = this.modItems['modMainDiv'].children.length > 0 ? this.modItems['modMainDiv'].children[0].className : null;
componentDidMount() { if (firstEleCn.indexOf('select-group cap') >= 0) {
let firstEleCn = this.modItems['modMainDiv'].children.length > 0 ? this.modItems['modMainDiv'].children[0].className : null; this.modItems['modMainDiv'].children[1].firstElementChild.focus();
if (firstEleCn.indexOf('select-group cap') >= 0) { } else {
this.modItems['modMainDiv'].children[1].firstElementChild.focus(); this.modItems['modMainDiv'].firstElementChild.focus();
} else { }
this.modItems['modMainDiv'].firstElementChild.focus(); }
}
} /**
/** * Set focus on first element in modifications menu
* Set focus on first element in modifications menu * if component updates, unless update is due to value change
* if component updates, unless update is due to value change * in a modification
* in a modification */
*/ componentDidUpdate() {
componentDidUpdate() { if (!this.modValDidChange) {
if (!this.modValDidChange) { if (this.modItems['modMainDiv'].children.length > 0) {
if (this.modItems['modMainDiv'].children.length > 0) { let firstEleCn = this.modItems['modMainDiv'].children[0].className;
let firstEleCn = this.modItems['modMainDiv'].children[0].className; if (firstEleCn.indexOf('button-inline-menu') >= 0) {
if (firstEleCn.indexOf('button-inline-menu') >= 0) { this.modItems['modMainDiv'].firstElementChild.focus();
this.modItems['modMainDiv'].firstElementChild.focus(); } else if (firstEleCn.indexOf('select-group cap') >= 0) {
} else if (firstEleCn.indexOf('select-group cap') >= 0) { this.modItems['modMainDiv'].children[1].firstElementChild.focus();
this.modItems['modMainDiv'].children[1].firstElementChild.focus(); }
} }
} } else {
} else { this._handleModChange(false);// Need to reset if component update due to value change
this._handleModChange(false);// Need to reset if component update due to value change }
} }
} /**
* set focus to the modification menu icon after mod menu is unmounted.
/** */
* Set focus to the modification menu icon componentWillUnmount() {
*/ if (this.props.modButton) {
componentWillUnmount() { this.props.modButton.focus();
if (this.props.modButton) { }
this.props.modButton.focus();// set focus to the modification menu icon after mod menu is unmounted. }
}
} /**
* Render the list
/** * @return {React.Component} List
* Render the list */
* @return {React.Component} List render() {
*/ const { language, tooltip, termtip } = this.context;
render() { const translate = language.translate;
const { language, tooltip, termtip } = this.context; const { m } = this.props;
const translate = language.translate; const { blueprintMenuOpened, specialMenuOpened } = this.state;
const { m } = this.props;
const { blueprintMenuOpened, specialMenuOpened } = this.state; const _toggleBlueprintsMenu = this._toggleBlueprintsMenu;
const _toggleSpecialsMenu = this._toggleSpecialsMenu;
const _toggleBlueprintsMenu = this._toggleBlueprintsMenu; const _rollFull = this._rollBest;
const _toggleSpecialsMenu = this._toggleSpecialsMenu; const _rollWorst = this._rollWorst;
const _rollFull = this._rollBest; const _rollFifty = this._rollFifty;
const _rollWorst = this._rollWorst; const _rollRandom = this._rollRandom;
const _rollFifty = this._rollFifty; const _reset = this._reset;
const _rollRandom = this._rollRandom;
const _reset = this._reset; let blueprintLabel;
let haveBlueprint = false;
let blueprintLabel; let blueprintTt;
let haveBlueprint = false; let blueprintCv;
let blueprintTt; // TODO: Fix this to actually find the correct blueprint.
let blueprintCv; if (!m.blueprint || !m.blueprint.name || !m.blueprint.fdname || !Modifications.modules[m.grp].blueprints || !Modifications.modules[m.grp].blueprints[m.blueprint.fdname]) {
// TODO: Fix this to actually find the correct blueprint. this.props.ship.clearModuleBlueprint(m);
if (!m.blueprint || !m.blueprint.name || !m.blueprint.fdname || !Modifications.modules[m.grp].blueprints || !Modifications.modules[m.grp].blueprints[m.blueprint.fdname]) { this.props.ship.clearModuleSpecial(m);
this.props.ship.clearModuleBlueprint(m); }
this.props.ship.clearModuleSpecial(m); if (m.blueprint && m.blueprint.name && Modifications.modules[m.grp].blueprints[m.blueprint.fdname].grades[m.blueprint.grade]) {
} blueprintLabel = translate(m.blueprint.name) + ' ' + translate('grade') + ' ' + m.blueprint.grade;
if (m.blueprint && m.blueprint.name && Modifications.modules[m.grp].blueprints[m.blueprint.fdname].grades[m.blueprint.grade]) { haveBlueprint = true;
blueprintLabel = translate(m.blueprint.name) + ' ' + translate('grade') + ' ' + m.blueprint.grade; blueprintTt = blueprintTooltip(translate, m.blueprint.grades[m.blueprint.grade], Modifications.modules[m.grp].blueprints[m.blueprint.fdname].grades[m.blueprint.grade].engineers, m.grp);
haveBlueprint = true; blueprintCv = getPercent(m);
blueprintTt = blueprintTooltip(translate, m.blueprint.grades[m.blueprint.grade], Modifications.modules[m.grp].blueprints[m.blueprint.fdname].grades[m.blueprint.grade].engineers, m.grp); }
blueprintCv = getPercent(m);
} let specialLabel;
let specialTt;
let specialLabel; if (m.blueprint && m.blueprint.special) {
let specialTt; specialLabel = m.blueprint.special.name;
if (m.blueprint && m.blueprint.special) { specialTt = specialToolTip(translate, m.blueprint.grades[m.blueprint.grade], m.grp, m, m.blueprint.special.edname);
specialLabel = m.blueprint.special.name; } else {
specialTt = specialToolTip(translate, m.blueprint.grades[m.blueprint.grade], m.grp, m, m.blueprint.special.edname); specialLabel = translate('PHRASE_SELECT_SPECIAL');
} else { }
specialLabel = translate('PHRASE_SELECT_SPECIAL');
} const specials = this._renderSpecials(this.props, this.context);
/**
const specials = this._renderSpecials(this.props, this.context); * pnellesen - 05/28/2018 - added additional checks for specials.length below to ensure menus
/** * display correctly in cases where there are no specials (ex: AFMUs.)
* pnellesen - 05/28/2018 - added additional checks for specials.length below to ensure menus */
* display correctly in cases where there are no specials (ex: AFMUs.) const showBlueprintsMenu = blueprintMenuOpened;
*/ const showSpecial = haveBlueprint && specials.length && !blueprintMenuOpened;
const showBlueprintsMenu = blueprintMenuOpened; const showSpecialsMenu = specialMenuOpened && specials.length;
const showSpecial = haveBlueprint && specials.length && !blueprintMenuOpened; const showRolls = haveBlueprint && !blueprintMenuOpened && (!specialMenuOpened || !specials.length);
const showSpecialsMenu = specialMenuOpened && specials.length; const showReset = !blueprintMenuOpened && (!specialMenuOpened || !specials.length) && haveBlueprint;
const showRolls = haveBlueprint && !blueprintMenuOpened && (!specialMenuOpened || !specials.length); const showMods = !blueprintMenuOpened && (!specialMenuOpened || !specials.length) && haveBlueprint;
const showReset = !blueprintMenuOpened && (!specialMenuOpened || !specials.length) && haveBlueprint; if (haveBlueprint) {
const showMods = !blueprintMenuOpened && (!specialMenuOpened || !specials.length) && haveBlueprint; this.firstBPLabel = blueprintLabel;
if (haveBlueprint) { } else {
this.firstBPLabel = blueprintLabel; this.firstBPLabel = 'selectBP';
} else { }
this.firstBPLabel = 'selectBP'; return (
} <div
return ( className={cn('select', this.props.className)}
<div onClick={(e) => e.stopPropagation() }
className={cn('select', this.props.className)} onContextMenu={stopCtxPropagation}
onClick={(e) => e.stopPropagation() } ref={modItem => this.modItems['modMainDiv'] = modItem}
onContextMenu={stopCtxPropagation} >
ref={modItem => this.modItems['modMainDiv'] = modItem} { showBlueprintsMenu | showSpecialsMenu ? '' : haveBlueprint ?
> <div tabIndex="0" className={ cn('section-menu button-inline-menu', { selected: blueprintMenuOpened })} style={{ cursor: 'pointer' }} onMouseOver={termtip.bind(null, blueprintTt)} onMouseOut={tooltip.bind(null, null)} onClick={_toggleBlueprintsMenu} onKeyDown={ this._keyDown } ref={modItems => this.modItems[this.firstBPLabel] = modItems}>{blueprintLabel}</div> :
{ showBlueprintsMenu | showSpecialsMenu ? '' : haveBlueprint ? <div tabIndex="0" className={ cn('section-menu button-inline-menu', { selected: blueprintMenuOpened })} style={{ cursor: 'pointer' }} onClick={_toggleBlueprintsMenu} onKeyDown={ this._keyDown } ref={modItems => this.modItems[this.firstBPLabel] = modItems}>{translate('PHRASE_SELECT_BLUEPRINT')}</div> }
<div tabIndex="0" className={ cn('section-menu button-inline-menu', { selected: blueprintMenuOpened })} style={{ cursor: 'pointer' }} onMouseOver={termtip.bind(null, blueprintTt)} onMouseOut={tooltip.bind(null, null)} onClick={_toggleBlueprintsMenu} onKeyDown={ this._keyDown } ref={modItems => this.modItems[this.firstBPLabel] = modItems}>{blueprintLabel}</div> : { showBlueprintsMenu ? this._renderBlueprints(this.props, this.context) : null }
<div tabIndex="0" className={ cn('section-menu button-inline-menu', { selected: blueprintMenuOpened })} style={{ cursor: 'pointer' }} onClick={_toggleBlueprintsMenu} onKeyDown={ this._keyDown } ref={modItems => this.modItems[this.firstBPLabel] = modItems}>{translate('PHRASE_SELECT_BLUEPRINT')}</div> } { showSpecial & !showSpecialsMenu ? <div tabIndex="0" className={ cn('section-menu button-inline-menu', { selected: specialMenuOpened })} style={{ cursor: 'pointer' }} onMouseOver={specialTt ? termtip.bind(null, specialTt) : null} onMouseOut={specialTt ? tooltip.bind(null, null) : null} onClick={_toggleSpecialsMenu} onKeyDown={ this._keyDown }>{specialLabel}</div> : null }
{ showBlueprintsMenu ? this._renderBlueprints(this.props, this.context) : null } { showSpecialsMenu ? specials : null }
{ showSpecial & !showSpecialsMenu ? <div tabIndex="0" className={ cn('section-menu button-inline-menu', { selected: specialMenuOpened })} style={{ cursor: 'pointer' }} onMouseOver={specialTt ? termtip.bind(null, specialTt) : null} onMouseOut={specialTt ? tooltip.bind(null, null) : null} onClick={_toggleSpecialsMenu} onKeyDown={ this._keyDown }>{specialLabel}</div> : null } { showReset ? <div tabIndex="0" className={'section-menu button-inline-menu warning'} style={{ cursor: 'pointer' }} onClick={_reset} onKeyDown={ this._keyDown } onMouseOver={termtip.bind(null, 'PHRASE_BLUEPRINT_RESET')} onMouseOut={tooltip.bind(null, null)}> { translate('reset') } </div> : null }
{ showSpecialsMenu ? specials : null } { showRolls ?
{ showReset ? <div tabIndex="0" className={'section-menu button-inline-menu warning'} style={{ cursor: 'pointer' }} onClick={_reset} onKeyDown={ this._keyDown } onMouseOver={termtip.bind(null, 'PHRASE_BLUEPRINT_RESET')} onMouseOut={tooltip.bind(null, null)}> { translate('reset') } </div> : null }
{ showRolls ? <table style={{ width: '100%', backgroundColor: 'transparent' }}>
<tbody>
<table style={{ width: '100%', backgroundColor: 'transparent' }}> { showRolls ?
<tbody> <tr>
{ showRolls ? <td tabIndex="0" className={ cn('section-menu button-inline-menu', { active: false}) }> { translate('roll') }: </td>
<tr> <td tabIndex="0" className={ cn('section-menu button-inline-menu', { active: blueprintCv === 0 }) } style={{ cursor: 'pointer' }} onClick={_rollWorst} onKeyDown={ this._keyDown } onMouseOver={termtip.bind(null, 'PHRASE_BLUEPRINT_WORST')} onMouseOut={tooltip.bind(null, null)}> { translate('0%') } </td>
<td tabIndex="0" className={ cn('section-menu button-inline-menu', { active: false })}> { translate('roll') }: </td> <td tabIndex="0" className={ cn('section-menu button-inline-menu', { active: blueprintCv === 50 })} style={{ cursor: 'pointer' }} onClick={_rollFifty} onKeyDown={ this._keyDown } onMouseOver={termtip.bind(null, 'PHRASE_BLUEPRINT_FIFTY')} onMouseOut={tooltip.bind(null, null)}> { translate('50%') } </td>
<td tabIndex="0" className={ cn('section-menu button-inline-menu', { active: blueprintCv === 0 })} style={{ cursor: 'pointer' }} onClick={_rollWorst} onKeyDown={ this._keyDown } onMouseOver={termtip.bind(null, 'PHRASE_BLUEPRINT_WORST')} onMouseOut={tooltip.bind(null, null)}> { translate('0%') } </td> <td tabIndex="0" className={ cn('section-menu button-inline-menu', { active: blueprintCv === 100 })} style={{ cursor: 'pointer' }} onClick={_rollFull} onKeyDown={ this._keyDown } onMouseOver={termtip.bind(null, 'PHRASE_BLUEPRINT_BEST')} onMouseOut={tooltip.bind(null, null)}> { translate('100%') } </td>
<td tabIndex="0" className={ cn('section-menu button-inline-menu', { active: blueprintCv === 50 })} style={{ cursor: 'pointer' }} onClick={_rollFifty} onKeyDown={ this._keyDown } onMouseOver={termtip.bind(null, 'PHRASE_BLUEPRINT_FIFTY')} onMouseOut={tooltip.bind(null, null)}> { translate('50%') } </td> <td tabIndex="0" className={ cn('section-menu button-inline-menu', { active: blueprintCv === null || blueprintCv % 50 != 0 })} style={{ cursor: 'pointer' }} onClick={_rollRandom} onKeyDown={ this._keyDown } onMouseOver={termtip.bind(null, 'PHRASE_BLUEPRINT_RANDOM')} onMouseOut={tooltip.bind(null, null)}> { translate('random') } </td>
<td tabIndex="0" className={ cn('section-menu button-inline-menu', { active: blueprintCv === 100 })} style={{ cursor: 'pointer' }} onClick={_rollFull} onKeyDown={ this._keyDown } onMouseOver={termtip.bind(null, 'PHRASE_BLUEPRINT_BEST')} onMouseOut={tooltip.bind(null, null)}> { translate('100%') } </td> </tr> : null }
<td tabIndex="0" className={ cn('section-menu button-inline-menu', { active: blueprintCv === null || blueprintCv % 50 != 0 })} style={{ cursor: 'pointer' }} onClick={_rollRandom} onKeyDown={ this._keyDown } onMouseOver={termtip.bind(null, 'PHRASE_BLUEPRINT_RANDOM')} onMouseOut={tooltip.bind(null, null)}> { translate('random') } </td> </tbody>
</tr> : null } </table> : null }
</tbody> { showMods ? <hr /> : null }
</table> : null } { showMods ?
{ showMods ? <hr /> : null } <span onMouseOver={termtip.bind(null, 'HELP_MODIFICATIONS_MENU')} onMouseOut={tooltip.bind(null, null)} >
{ showMods ? { this._renderModifications(this.props) }
<span onMouseOver={termtip.bind(null, 'HELP_MODIFICATIONS_MENU')} onMouseOut={tooltip.bind(null, null)} > </span> : null }
{ this._renderModifications(this.props) } </div>
</span> : null } );
</div> }
); }
}
}

View File

@@ -94,7 +94,6 @@ export default class Slider extends React.Component {
case 'Enter': case 'Enter':
event.preventDefault(); event.preventDefault();
this.sliderInputBox._setDisplay('block'); this.sliderInputBox._setDisplay('block');
// this.enterTimer = setTimeout(() => this.sliderInputBox.sliderVal.focus(), 10);
return; return;
default: default:
return; return;
@@ -103,17 +102,17 @@ export default class Slider extends React.Component {
/** /**
* Key down handler * Key down handler
* increment slider position by +/- 1 when right/left arrow key is pressed or held * increment slider position by +/- 1 when right/left arrow key is pressed or held
* @param {Event} event The key down event. * @param {Event} event Keyboard even
*/ */
_keydown(event) { _keydown(event) {
let newVal; let newVal = this.props.percent * this.props.max;
switch (event.key) { switch (event.key) {
case 'ArrowRight': case 'ArrowRight':
newVal = this.props.percent * this.props.max + 1; newVal += 1;
if (newVal <= this.props.max) this.props.onChange(newVal / this.props.max); if (newVal <= this.props.max) this.props.onChange(newVal / this.props.max);
return; return;
case 'ArrowLeft': case 'ArrowLeft':
newVal = this.props.percent * this.props.max - 1; newVal -= 1;
if (newVal >= 0) this.props.onChange(newVal / this.props.max); if (newVal >= 0) this.props.onChange(newVal / this.props.max);
return; return;
default: default:
@@ -124,15 +123,16 @@ export default class Slider extends React.Component {
/** /**
* Touch start handler * Touch start handler
* @param {Event} event DOM Event * @param {Event} event DOM Event
* *
*/ */
_touchstart(event) { _touchstart(event) {
this.touchStartTimer = setTimeout(() => this.sliderInputBox._setDisplay('block'), 1500); this.touchStartTimer = setTimeout(() => this.sliderInputBox._setDisplay('block'), 1500);
} }
/** /**
* Touch end handler * Touch end handler
* @param {Event} event DOM Event * @param {Event} event DOM Event
*
*/ */
_touchend(event) { _touchend(event) {
this.sliderInputBox.sliderVal.focus(); this.sliderInputBox.sliderVal.focus();
@@ -201,22 +201,18 @@ export default class Slider extends React.Component {
render() { render() {
let outerWidth = this.state.outerWidth; let outerWidth = this.state.outerWidth;
let { axis, axisUnit, min, max, scale } = this.props; let { axis, axisUnit, min, max, scale } = this.props;
let style = { let style = {
width: '100%', width: '100%',
height: axis ? '2.5em' : '1.5em', height: axis ? '2.5em' : '1.5em',
boxSizing: 'border-box' boxSizing: 'border-box'
}; };
if (!outerWidth) { if (!outerWidth) {
return <svg style={style} ref={node => this.node = node} />; return <svg style={style} ref={node => this.node = node} />;
} }
let margin = MARGIN_LR * scale; let margin = MARGIN_LR * scale;
let width = outerWidth - (margin * 2); let width = outerWidth - (margin * 2);
let pctPos = width * this.props.percent; let pctPos = width * this.props.percent;
return <div><svg
return <div><svg
onMouseUp={this._up} onMouseEnter={this._enter.bind(this)} onMouseMove={this._move} onKeyUp={this._keyup} onKeyDown={this._keydown} style={style} ref={node => this.node = node} tabIndex="0"> onMouseUp={this._up} onMouseEnter={this._enter.bind(this)} onMouseMove={this._move} onKeyUp={this._keyup} onKeyDown={this._keydown} style={style} ref={node => this.node = node} tabIndex="0">
<rect className='primary' style={{ opacity: 0.3 }} x={margin} y='0.25em' rx='0.3em' ry='0.3em' width={width} height='0.7em' /> <rect className='primary' style={{ opacity: 0.3 }} x={margin} y='0.25em' rx='0.3em' ry='0.3em' width={width} height='0.7em' />
<rect className='primary-disabled' x={margin} y='0.45em' rx='0.15em' ry='0.15em' width={pctPos} height='0.3em' /> <rect className='primary-disabled' x={margin} y='0.45em' rx='0.15em' ry='0.15em' width={pctPos} height='0.3em' />
@@ -234,11 +230,9 @@ export default class Slider extends React.Component {
axisUnit={this.props.axisUnit} axisUnit={this.props.axisUnit}
scale={this.props.scale} scale={this.props.scale}
max={this.props.max} max={this.props.max}
/> />
</div>; </div>;
} }
} }
/** /**
* New component to add keyboard support for sliders - works on all devices (desktop, iOS, Android) * New component to add keyboard support for sliders - works on all devices (desktop, iOS, Android)
@@ -251,133 +245,120 @@ class TextInputBox extends React.Component {
percent: PropTypes.number.isRequired,// value of slider percent: PropTypes.number.isRequired,// value of slider
scale: PropTypes.number scale: PropTypes.number
}; };
/** /**
* Constructor for TextInputBox * Determine if the user is still dragging
* @param {Object} props The props * @param {Object} props React Component properties
*/ */
constructor(props) { constructor(props) {
super(props); super(props);
this._handleFocus = this._handleFocus.bind(this); this._handleFocus = this._handleFocus.bind(this);
this._handleBlur = this._handleBlur.bind(this); this._handleBlur = this._handleBlur.bind(this);
this._handleChange = this._handleChange.bind(this); this._handleChange = this._handleChange.bind(this);
// this._keydown = this._keydown.bind(this); this._keyup = this._keyup.bind(this);
this._keyup = this._keyup.bind(this); this.state = this._getInitialState();
this.state = this._getInitialState();
this.percent = this.props.percent;
this.max = this.props.max;
this.setState({ inputValue: this.percent * this.max });
} }
/** /**
* Slider willrecieveprops * Update input value if slider changes will change props/state
* @param {Object} nextProps The next props * @param {Object} nextProps React Component properites
* @param {Object} nextState The next state * @param {Object} nextState React Component state values
*/ */
componentWillReceiveProps(nextProps, nextState) { componentWillReceiveProps(nextProps, nextState) {
let nextValue = nextProps.percent * nextProps.max; let nextValue = nextProps.percent * nextProps.max;
// See https://stackoverflow.com/questions/32414308/updating-state-on-props-change-in-react-form // See https://stackoverflow.com/questions/32414308/updating-state-on-props-change-in-react-form
if (nextValue !== this.state.inputValue && nextValue <= nextProps.max) { if (nextValue !== this.state.inputValue && nextValue <= nextProps.max) {
this.setState({ inputValue: nextValue }); this.setState({ inputValue: nextValue });
} }
} }
/**
/** * Update slider textbox visibility/values if changes are made to slider
* Slider Component did update * @param {Object} prevProps React Component properites
* @param {Object} prevProps The prev props * @param {Object} prevState React Component state values
* @param {Object} prevState The prev state
*/ */
componentDidUpdate(prevProps, prevState) { componentDidUpdate(prevProps, prevState) {
if (prevState.divStyle.display == 'none' && this.state.divStyle.display == 'block') { if (prevState.divStyle.display == 'none' && this.state.divStyle.display == 'block') {
this.enterTimer = setTimeout(() => this.sliderVal.focus(), 10); this.enterTimer = setTimeout(() => this.sliderVal.focus(), 10);
} }
if (prevProps.max !== this.props.max && this.state.inputValue > this.props.max) { if (prevProps.max !== this.props.max && this.state.inputValue > this.props.max) {
// they chose a different module // they chose a different module
this.setState({ inputValue: this.props.max }); this.setState({ inputValue: this.props.max });
} }
if (this.state.inputValue != prevState.inputValue && prevProps.max == this.props.max) { if (this.state.inputValue != prevState.inputValue && prevProps.max == this.props.max) {
this.props.onChange(this.state.inputValue / this.props.max); this.props.onChange(this.state.inputValue/this.props.max);
} }
} }
/** /**
* Get the initial state. * Set initial state for the textbox.
* @returns {{divStyle: {display: string}, inputStyle: {width: string}, labelStyle: {marginLeft: string}, maxLength: number, size: number, min: number, tabIndex: number, type: string, readOnly: boolean}} Initial state. * We may want to rethink this to
* @private * try and make it a stateless component
* @returns {object} React state object with initial values set
*/ */
_getInitialState() { _getInitialState() {
return { return {
divStyle: { display:'none' }, divStyle: {display:'none'},
inputStyle: { width:'4em' }, inputStyle: {width:'4em'},
labelStyle: { marginLeft: '.1em' }, labelStyle: {marginLeft: '.1em'},
maxLength:5, maxLength:5,
size:5, size:5,
min:0, min:0,
tabIndex:-1, tabIndex:-1,
type:'number', type:'number',
readOnly: true readOnly: true,
}; inputValue: this.props.percent * this.props.max
}
} }
/** /**
* Set display style *
* @param {string} val The display CSS code. * @param {string} val block or none
* @private
*/ */
_setDisplay(val) { _setDisplay(val) {
this.setState({ this.setState({
divStyle: { display:val } divStyle: {display:val}
}); });
} }
/** /**
* Focus handler * Update the input value
* @private * when textbox gets focus
*/ */
_handleFocus() { _handleFocus() {
this.setState({ this.setState({
inputValue:this._getValue() inputValue:this._getValue()
}); });
} }
/** /**
* Handles blurring * Update inputValue when textbox loses focus
*/ */
_handleBlur() { _handleBlur() {
this._setDisplay('none'); this._setDisplay('none');
if (this.state.inputValue !== '') { if (this.state.inputValue !== '') {
this.props.onChange(this.state.inputValue / this.props.max); this.props.onChange(this.state.inputValue/this.props.max);
} else { } else {
this.setState({ inputValue: this.props.percent * this.props.max }); this.setState({
inputValue: this.props.percent * this.props.max
});
} }
} }
/** /**
* Get inputValue * Get the value in the text box
* @returns {number|Number|*} inputValue
* @private
*/ */
_getValue() { _getValue() {
return this.state.inputValue; return this.state.inputValue;
} }
/** /**
* Handle changes * Update and set limits on input box
* @param {Event} event DOM Event * values depending on what user
* @private * has selected
*
* @param {SyntheticEvent} event
*/ */
_handleChange(event) { _handleChange(event) {
if (event.target.value < 0) { if (event.target.value < 0) {
this.setState({ inputValue: 0 }); this.setState({inputValue: 0});
} else if (event.target.value <= this.props.max) { } else if (event.target.value <= this.props.max) {
this.setState({ inputValue: event.target.value }); this.setState({inputValue: event.target.value});
} else { } else {
this.setState({ inputValue: this.props.max }); this.setState({inputValue: this.props.max});
} }
} }
/** /**
* Key up handler for input field. * Key up handler for input field.
* If user hits Enter key, blur/close the input field * If user hits Enter key, blur/close the input field
@@ -392,14 +373,9 @@ class TextInputBox extends React.Component {
return; return;
} }
} }
/**
* JSX Render handler
* @returns {*} Slider
*/
render() { render() {
let { axisUnit, onChange, percent, scale } = this.props; let { axisUnit, onChange, percent, scale } = this.props;
return <div style={this.state.divStyle}><input style={this.state.inputStyle} value={this._getValue()} min={this.state.min} max={this.props.max} onChange={this._handleChange} onKeyUp={this._keyup} tabIndex={this.state.tabIndex} maxLength={this.state.maxLength} size={this.state.size} onBlur={() => {this._handleBlur();}} onFocus={() => {this._handleFocus();}} type={this.state.type} ref={(ip) => this.sliderVal = ip}/><text className="primary upp" style={this.state.labelStyle}>{this.props.axisUnit}</text></div>; return <div style={this.state.divStyle}><input style={this.state.inputStyle} value={this._getValue()} min={this.state.min} max={this.props.max} onChange={this._handleChange} onKeyUp={this._keyup} tabIndex={this.state.tabIndex} maxLength={this.state.maxLength} size={this.state.size} onBlur={() => {this._handleBlur()}} onFocus={() => {this._handleFocus()}} type={this.state.type} ref={(ip) => this.sliderVal = ip}/><text className="primary upp" style={this.state.labelStyle}>{this.props.axisUnit}</text></div>;
} }
} }

View File

@@ -1,164 +1,161 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import TranslatedComponent from './TranslatedComponent'; import TranslatedComponent from './TranslatedComponent';
import cn from 'classnames'; import cn from 'classnames';
import AvailableModulesMenu from './AvailableModulesMenu'; import AvailableModulesMenu from './AvailableModulesMenu';
import ModificationsMenu from './ModificationsMenu'; import ModificationsMenu from './ModificationsMenu';
import { diffDetails } from '../utils/SlotFunctions'; import { diffDetails } from '../utils/SlotFunctions';
import { wrapCtxMenu } from '../utils/UtilityFunctions'; import { wrapCtxMenu } from '../utils/UtilityFunctions';
import { stopCtxPropagation } from '../utils/UtilityFunctions'; import { stopCtxPropagation } from '../utils/UtilityFunctions';
/** /**
* Abstract Slot * Abstract Slot
*/ */
export default class Slot extends TranslatedComponent { export default class Slot extends TranslatedComponent {
static propTypes = { static propTypes = {
availableModules: PropTypes.func.isRequired, availableModules: PropTypes.func.isRequired,
onSelect: PropTypes.func.isRequired, onSelect: PropTypes.func.isRequired,
onOpen: PropTypes.func.isRequired, onOpen: PropTypes.func.isRequired,
maxClass: PropTypes.number.isRequired, maxClass: PropTypes.number.isRequired,
selected: PropTypes.bool, selected: PropTypes.bool,
m: PropTypes.object, m: PropTypes.object,
enabled: PropTypes.bool.isRequired, enabled: PropTypes.bool.isRequired,
ship: PropTypes.object.isRequired, ship: PropTypes.object.isRequired,
eligible: PropTypes.object, eligible: PropTypes.object,
warning: PropTypes.func, warning: PropTypes.func,
drag: PropTypes.func, drag: PropTypes.func,
drop: PropTypes.func, drop: PropTypes.func,
dropClass: PropTypes.string dropClass: PropTypes.string
}; };
/** /**
* Constructor * Constructor
* @param {Object} props React Component properties * @param {Object} props React Component properties
*/ */
constructor(props) { constructor(props) {
super(props); super(props);
this._modificationsSelected = false; this._modificationsSelected = false;
this._contextMenu = wrapCtxMenu(this._contextMenu.bind(this)); this._contextMenu = wrapCtxMenu(this._contextMenu.bind(this));
this._getMaxClassLabel = this._getMaxClassLabel.bind(this); this._getMaxClassLabel = this._getMaxClassLabel.bind(this);
this._keyDown = this._keyDown.bind(this); this._keyDown = this._keyDown.bind(this);
this.slotDiv = null; this.slotDiv = null;
} }
// Must be implemented by subclasses: // Must be implemented by subclasses:
// _getSlotDetails() // _getSlotDetails()
/** /**
* Get the CSS class name for the slot. Can/should be overriden * Get the CSS class name for the slot. Can/should be overriden
* as necessary. * as necessary.
* @return {string} CSS Class name * @return {string} CSS Class name
*/ */
_getClassNames() { _getClassNames() {
return null; return null;
} }
/** /**
* Get the label for the slot size/class * Get the label for the slot size/class
* Should be overriden if necessary * Should be overriden if necessary
* @return {string} label * @return {string} label
*/ */
_getMaxClassLabel() { _getMaxClassLabel() {
return this.props.maxClass; return this.props.maxClass;
} }
/** /**
* Empty slot on right-click * Empty slot on right-click
* @param {SyntheticEvent} event Event * @param {SyntheticEvent} event Event
*/ */
_contextMenu(event) { _contextMenu(event) {
event.stopPropagation(); event.stopPropagation();
event.preventDefault(); event.preventDefault();
this.props.onSelect(null,null); this.props.onSelect(null,null);
} }
/** Key Down handler /** Key Down handler
* @param {SyntheticEvent} event Event * @param {SyntheticEvent} event Event
* ToDo: see if this can be moved up * ToDo: see if this can be moved up
* we do more or less the same thing * we do more or less the same thing
* in every section when Enter key is pressed * in every section when Enter key is pressed
* on a focusable item * on a focusable item
* *
*/ */
_keyDown(event) { _keyDown(event) {
if (event.key == 'Enter') { if (event.key == 'Enter') {
if(event.target.className == 'r') { if(event.target.className == 'r') {
console.log('Slot: Enter key pressed on mod icon'); this._toggleModifications();
this._toggleModifications(); }
} else { this.props.onOpen(event);
console.log('Slot: Enter key pressed on: %O', event.target); }
} }
this.props.onOpen(event); /**
} * Render the slot
} * @return {React.Component} The slot
/** */
* Render the slot render() {
* @return {React.Component} The slot let language = this.context.language;
*/ let translate = language.translate;
render() { let { ship, m, enabled, dropClass, dragOver, onOpen, onChange, selected, eligible, onSelect, warning, availableModules } = this.props;
let language = this.context.language; let slotDetails, modificationsMarker, menu;
let translate = language.translate;
let { ship, m, enabled, dropClass, dragOver, onOpen, onChange, selected, eligible, onSelect, warning, availableModules } = this.props; if (!selected) {
let slotDetails, modificationsMarker, menu; // If not selected then sure that modifications flag is unset
this._modificationsSelected = false;
if (!selected) { }
// If not selected then sure that modifications flag is unset
this._modificationsSelected = false; if (m) {
} slotDetails = this._getSlotDetails(m, enabled, translate, language.formats, language.units); // Must be implemented by sub classes
modificationsMarker = JSON.stringify(m);
if (m) { } else {
slotDetails = this._getSlotDetails(m, enabled, translate, language.formats, language.units); // Must be implemented by sub classes slotDetails = <div className={'empty'}>{translate(eligible ? 'emptyrestricted' : 'empty')}</div>;
modificationsMarker = JSON.stringify(m); modificationsMarker = '';
} else { }
slotDetails = <div className={'empty'}>{translate(eligible ? 'emptyrestricted' : 'empty')}</div>;
modificationsMarker = ''; if (selected) {
} if (this._modificationsSelected) {
menu = <ModificationsMenu
if (selected) { className={this._getClassNames()}
if (this._modificationsSelected) { onChange={onChange}
menu = <ModificationsMenu ship={ship}
className={this._getClassNames()} m={m}
onChange={onChange} marker={modificationsMarker}
ship={ship} modButton = {this.modButton}
m={m} />;
marker={modificationsMarker} } else {
modButton = {this.modButton} menu = <AvailableModulesMenu
/>; className={this._getClassNames()}
} else { modules={availableModules()}
menu = <AvailableModulesMenu shipMass={ship.hullMass}
className={this._getClassNames()} m={m}
modules={availableModules()} onSelect={onSelect}
shipMass={ship.hullMass} warning={warning}
m={m} diffDetails={diffDetails.bind(ship, this.context.language)}
onSelect={onSelect} slotDiv = {this.slotDiv}
warning={warning} />;
diffDetails={diffDetails.bind(ship, this.context.language)} }
slotDiv = {this.slotDiv} }
/>;
} // TODO: implement touch dragging
}
return (
// TODO: implement touch dragging <div className={cn('slot', dropClass, { selected })} onClick={onOpen} onKeyDown={this._keyDown} onContextMenu={this._contextMenu} onDragOver={dragOver} tabIndex="0" ref={slotDiv => this.slotDiv = slotDiv}>
<div className='details-container'>
return ( <div className='sz'>{this._getMaxClassLabel(translate)}</div>
<div className={cn('slot', dropClass, { selected })} onClick={onOpen} onKeyDown={this._keyDown} onContextMenu={this._contextMenu} onDragOver={dragOver} tabIndex="0" ref={slotDiv => this.slotDiv = slotDiv}> {slotDetails}
<div className='details-container'> </div>
<div className='sz'>{this._getMaxClassLabel(translate)}</div> {menu}
{slotDetails} </div>
</div> );
{menu} }
</div>
);
} /**
* Toggle the modifications flag when selecting the modifications icon
*/
/** _toggleModifications() {
* Toggle the modifications flag when selecting the modifications icon this._modificationsSelected = !this._modificationsSelected;
*/ }
_toggleModifications() { }
this._modificationsSelected = !this._modificationsSelected;
}
}

View File

@@ -18,7 +18,8 @@ export default class SlotSection extends TranslatedComponent {
onCargoChange: PropTypes.func.isRequired, onCargoChange: PropTypes.func.isRequired,
onFuelChange: PropTypes.func.isRequired, onFuelChange: PropTypes.func.isRequired,
code: PropTypes.string.isRequired, code: PropTypes.string.isRequired,
togglePwr: PropTypes.func togglePwr: PropTypes.func,
sectionMenuRefs: PropTypes.object
}; };
/** /**
@@ -32,7 +33,10 @@ export default class SlotSection extends TranslatedComponent {
super(props); super(props);
this.sectionId = sectionId; this.sectionId = sectionId;
this.sectionName = sectionName; this.sectionName = sectionName;
this.ssHeadRef = null;
this.sectionRefArr = this.props.sectionMenuRefs[this.sectionId] = [];
this.sectionRefArr['selectedRef'] = null;
this._getSlots = this._getSlots.bind(this); this._getSlots = this._getSlots.bind(this);
this._selectModule = this._selectModule.bind(this); this._selectModule = this._selectModule.bind(this);
this._getSectionMenu = this._getSectionMenu.bind(this); this._getSectionMenu = this._getSectionMenu.bind(this);
@@ -40,6 +44,8 @@ export default class SlotSection extends TranslatedComponent {
this._drop = this._drop.bind(this); this._drop = this._drop.bind(this);
this._dragOverNone = this._dragOverNone.bind(this); this._dragOverNone = this._dragOverNone.bind(this);
this._close = this._close.bind(this); this._close = this._close.bind(this);
this._keyDown = this._keyDown.bind(this);
this._handleSectionFocus = this._handleSectionFocus.bind(this);
this.state = {}; this.state = {};
} }
@@ -47,7 +53,59 @@ export default class SlotSection extends TranslatedComponent {
// _getSlots() // _getSlots()
// _getSectionMenu() // _getSectionMenu()
// _contextMenu() // _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 * Open a menu
* @param {string} menu Menu name * @param {string} menu Menu name
@@ -225,7 +283,7 @@ export default class SlotSection extends TranslatedComponent {
return ( return (
<div id={this.sectionId} className={'group'} onDragLeave={this._dragOverNone}> <div id={this.sectionId} className={'group'} onDragLeave={this._dragOverNone}>
<div className={cn('section-menu', { selected: sectionMenuOpened })} onClick={open} onContextMenu={ctx}> <div className={cn('section-menu', { selected: sectionMenuOpened })} onClick={open} onContextMenu={ctx}>
<h1>{translate(this.sectionName)} <Equalizer/></h1> <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 } {sectionMenuOpened ? this._getSectionMenu(translate, this.props.ship) : null }
</div> </div>
{this._getSlots()} {this._getSlots()}

View File

@@ -1,161 +1,159 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import cn from 'classnames'; import cn from 'classnames';
import Persist from '../stores/Persist'; import Persist from '../stores/Persist';
import TranslatedComponent from './TranslatedComponent'; import TranslatedComponent from './TranslatedComponent';
import { diffDetails } from '../utils/SlotFunctions'; import { diffDetails } from '../utils/SlotFunctions';
import AvailableModulesMenu from './AvailableModulesMenu'; import AvailableModulesMenu from './AvailableModulesMenu';
import ModificationsMenu from './ModificationsMenu'; import ModificationsMenu from './ModificationsMenu';
import * as ModuleUtils from '../shipyard/ModuleUtils'; import * as ModuleUtils from '../shipyard/ModuleUtils';
import { ListModifications, Modified } from './SvgIcons'; import { ListModifications, Modified } from './SvgIcons';
import { Modifications } from 'coriolis-data/dist'; import { Modifications } from 'coriolis-data/dist';
import { stopCtxPropagation } from '../utils/UtilityFunctions'; import { stopCtxPropagation } from '../utils/UtilityFunctions';
import { blueprintTooltip } from '../utils/BlueprintFunctions'; import { blueprintTooltip } from '../utils/BlueprintFunctions';
/** /**
* Standard Slot * Standard Slot
*/ */
export default class StandardSlot extends TranslatedComponent { export default class StandardSlot extends TranslatedComponent {
static propTypes = { static propTypes = {
slot: PropTypes.object, slot: PropTypes.object,
modules: PropTypes.array.isRequired, modules: PropTypes.array.isRequired,
onSelect: PropTypes.func.isRequired, onSelect: PropTypes.func.isRequired,
onOpen: PropTypes.func.isRequired, onOpen: PropTypes.func.isRequired,
onChange: PropTypes.func.isRequired, onChange: PropTypes.func.isRequired,
ship: PropTypes.object.isRequired, ship: PropTypes.object.isRequired,
selected: PropTypes.bool, selected: PropTypes.bool,
warning: PropTypes.func, warning: PropTypes.func,
}; };
/** /**
* Construct the slot * Construct the slot
* @param {object} props Object properties * @param {object} props Object properties
*/ */
constructor(props) { constructor(props) {
super(props); super(props);
this._modificationsSelected = false; this._modificationsSelected = false;
this._keyDown = this._keyDown.bind(this); this._keyDown = this._keyDown.bind(this);
this.modButton = null; this.modButton = null;
this.slotDiv = null; this.slotDiv = null;
} }
/**
/** * Handle Enter key
* Fired on key down * @param {SyntheticEvent} event KeyDown event
* @param {KeyboardEvent} event The keydown event */
* @private _keyDown(event) {
*/ if (event.key == 'Enter') {
_keyDown(event) { if(event.target.className == 'r') {
if (event.key == 'Enter') { this._toggleModifications();
if(event.target.className == 'r') { }
this._toggleModifications(); this.props.onOpen(event);
} }
this.props.onOpen(event); }
}
} /**
* Render the slot
/** * @return {React.Component} Slot component
* Render the slot */
* @return {React.Component} Slot component render() {
*/ let { termtip, tooltip } = this.context;
render() { let { translate, formats, units } = this.context.language;
let { termtip, tooltip } = this.context; let { modules, slot, selected, warning, onSelect, onChange, ship } = this.props;
let { translate, formats, units } = this.context.language; let m = slot.m;
let { modules, slot, selected, warning, onSelect, onChange, ship } = this.props; let classRating = m.class + m.rating;
let m = slot.m; let menu;
let classRating = m.class + m.rating; let validMods = m == null || !Modifications.modules[m.grp] ? [] : (Modifications.modules[m.grp].modifications || []);
let menu; if (m && m.name && m.name === 'Guardian Hybrid Power Plant') {
let validMods = m == null || !Modifications.modules[m.grp] ? [] : (Modifications.modules[m.grp].modifications || []); validMods = [];
if (m && m.name && m.name === 'Guardian Hybrid Power Plant') { }
validMods = []; let showModuleResistances = Persist.showModuleResistances();
} let mass = m.getMass() || m.cargo || m.fuel || 0;
let showModuleResistances = Persist.showModuleResistances();
let mass = m.getMass() || m.cargo || m.fuel || 0; // Modifications tooltip shows blueprint and grade, if available
let modTT = translate('modified');
// Modifications tooltip shows blueprint and grade, if available if (m && m.blueprint && m.blueprint.name) {
let modTT = translate('modified'); modTT = translate(m.blueprint.name) + ' ' + translate('grade') + ' ' + m.blueprint.grade;
if (m && m.blueprint && m.blueprint.name) { if (m.blueprint.special && m.blueprint.special.id >= 0) {
modTT = translate(m.blueprint.name) + ' ' + translate('grade') + ' ' + m.blueprint.grade; modTT += ', ' + translate(m.blueprint.special.name);
if (m.blueprint.special && m.blueprint.special.id >= 0) { }
modTT += ', ' + translate(m.blueprint.special.name); modTT = (
} <div>
modTT = ( <div>{modTT}</div>
<div> {blueprintTooltip(translate, m.blueprint.grades[m.blueprint.grade], null, m.grp, m)}
<div>{modTT}</div> </div>
{blueprintTooltip(translate, m.blueprint.grades[m.blueprint.grade], null, m.grp, m)} );
</div> }
);
} if (!selected) {
// If not selected then sure that modifications flag is unset
if (!selected) { this._modificationsSelected = false;
// If not selected then sure that modifications flag is unset }
this._modificationsSelected = false;
} const modificationsMarker = JSON.stringify(m);
const modificationsMarker = JSON.stringify(m); if (selected) {
if (this._modificationsSelected) {
if (selected) { menu = <ModificationsMenu
if (this._modificationsSelected) { className='standard'
menu = <ModificationsMenu onChange={onChange}
className='standard' ship={ship}
onChange={onChange} m={m}
ship={ship} marker={modificationsMarker}
m={m} modButton = {this.modButton}
marker={modificationsMarker} />;
modButton = {this.modButton} } else {
/>; menu = <AvailableModulesMenu
} else { className='standard'
menu = <AvailableModulesMenu modules={modules}
className='standard' shipMass={ModuleUtils.isShieldGenerator(m.grp) ? ship.hullMass : ship.unladenMass}
modules={modules} m={m}
shipMass={ModuleUtils.isShieldGenerator(m.grp) ? ship.hullMass : ship.unladenMass} onSelect={onSelect}
m={m} warning={warning}
onSelect={onSelect} diffDetails={diffDetails.bind(ship, this.context.language)}
warning={warning} slotDiv = {this.slotDiv}
diffDetails={diffDetails.bind(ship, this.context.language)} />;
slotDiv = {this.slotDiv} }
/>; }
}
} return (
<div className={cn('slot', { selected: this.props.selected })} onClick={this.props.onOpen} onKeyDown={this._keyDown} onContextMenu={stopCtxPropagation} tabIndex="0" ref={ slotDiv => this.slotDiv = slotDiv }>
return ( <div className={cn('details-container', { warning: warning && warning(slot.m), disabled: m.grp !== 'bh' && !slot.enabled })}>
<div className={cn('slot', { selected: this.props.selected })} onClick={this.props.onOpen} onKeyDown={this._keyDown} onContextMenu={stopCtxPropagation} tabIndex="0" ref={ slotDiv => this.slotDiv = slotDiv }> <div className={'sz'}>{m.grp == 'bh' ? m.name.charAt(0) : slot.maxClass}</div>
<div className={cn('details-container', { warning: warning && warning(slot.m), disabled: m.grp !== 'bh' && !slot.enabled })}> <div>
<div className={'sz'}>{m.grp == 'bh' ? m.name.charAt(0) : slot.maxClass}</div> <div className={'l'}>{classRating} {translate(m.name || m.grp)}{m.mods && Object.keys(m.mods).length > 0 ? <span className='r' onMouseOver={termtip.bind(null, modTT)} onMouseOut={tooltip.bind(null, null)}><Modified /></span> : null }</div>
<div> <div className={'r'}>{formats.round(mass)}{units.T}</div>
<div className={'l'}>{classRating} {translate(m.name || m.grp)}{m.mods && Object.keys(m.mods).length > 0 ? <span className='r' onMouseOver={termtip.bind(null, modTT)} onMouseOut={tooltip.bind(null, null)}><Modified /></span> : null }</div> <div/>
<div className={'r'}>{formats.round(mass)}{units.T}</div> <div className={'cb'}>
<div/> { m.getMinMass() ? <div className='l'>{translate('minimum mass')}: {formats.int(m.getMinMass())}{units.T}</div> : null }
<div className={'cb'}> { m.getOptMass() ? <div className='l'>{translate('optimal mass')}: {formats.int(m.getOptMass())}{units.T}</div> : null }
{ m.getMinMass() ? <div className='l'>{translate('minimum mass')}: {formats.int(m.getMinMass())}{units.T}</div> : null } { m.getMaxMass() ? <div className='l'>{translate('max mass')}: {formats.int(m.getMaxMass())}{units.T}</div> : null }
{ m.getOptMass() ? <div className='l'>{translate('optimal mass')}: {formats.int(m.getOptMass())}{units.T}</div> : null } { m.getOptMul() ? <div className='l'>{translate('optimal multiplier')}: {formats.rPct(m.getOptMul())}</div> : null }
{ m.getMaxMass() ? <div className='l'>{translate('max mass')}: {formats.int(m.getMaxMass())}{units.T}</div> : null } { m.getRange() ? <div className='l'>{translate('range', m.grp)}: {formats.f2(m.getRange())}{units.km}</div> : null }
{ m.getOptMul() ? <div className='l'>{translate('optimal multiplier')}: {formats.rPct(m.getOptMul())}</div> : null } { m.time ? <div className='l'>{translate('time')}: {formats.time(m.time)}</div> : null }
{ m.getRange() ? <div className='l'>{translate('range', m.grp)}: {formats.f2(m.getRange())}{units.km}</div> : null } { m.getThermalEfficiency() ? <div className='l'>{translate('efficiency')}: {formats.f2(m.getThermalEfficiency())}</div> : null }
{ m.time ? <div className='l'>{translate('time')}: {formats.time(m.time)}</div> : null } { m.getPowerGeneration() > 0 ? <div className='l'>{translate('pgen')}: {formats.f1(m.getPowerGeneration())}{units.MW}</div> : null }
{ m.getThermalEfficiency() ? <div className='l'>{translate('efficiency')}: {formats.f2(m.getThermalEfficiency())}</div> : null } { m.getMaxFuelPerJump() ? <div className='l'>{translate('max')} {translate('fuel')}: {formats.f1(m.getMaxFuelPerJump())}{units.T}</div> : null }
{ m.getPowerGeneration() > 0 ? <div className='l'>{translate('pgen')}: {formats.f1(m.getPowerGeneration())}{units.MW}</div> : null } { m.getWeaponsCapacity() ? <div className='l'>{translate('WEP')}: {formats.f1(m.getWeaponsCapacity())}{units.MJ} / {formats.f1(m.getWeaponsRechargeRate())}{units.MW}</div> : null }
{ m.getMaxFuelPerJump() ? <div className='l'>{translate('max')} {translate('fuel')}: {formats.f1(m.getMaxFuelPerJump())}{units.T}</div> : null } { m.getSystemsCapacity() ? <div className='l'>{translate('SYS')}: {formats.f1(m.getSystemsCapacity())}{units.MJ} / {formats.f1(m.getSystemsRechargeRate())}{units.MW}</div> : null }
{ m.getWeaponsCapacity() ? <div className='l'>{translate('WEP')}: {formats.f1(m.getWeaponsCapacity())}{units.MJ} / {formats.f1(m.getWeaponsRechargeRate())}{units.MW}</div> : null } { m.getEnginesCapacity() ? <div className='l'>{translate('ENG')}: {formats.f1(m.getEnginesCapacity())}{units.MJ} / {formats.f1(m.getEnginesRechargeRate())}{units.MW}</div> : null }
{ m.getSystemsCapacity() ? <div className='l'>{translate('SYS')}: {formats.f1(m.getSystemsCapacity())}{units.MJ} / {formats.f1(m.getSystemsRechargeRate())}{units.MW}</div> : null } { showModuleResistances && m.getExplosiveResistance() ? <div className='l'>{translate('explres')}: {formats.pct(m.getExplosiveResistance())}</div> : null }
{ m.getEnginesCapacity() ? <div className='l'>{translate('ENG')}: {formats.f1(m.getEnginesCapacity())}{units.MJ} / {formats.f1(m.getEnginesRechargeRate())}{units.MW}</div> : null } { showModuleResistances && m.getKineticResistance() ? <div className='l'>{translate('kinres')}: {formats.pct(m.getKineticResistance())}</div> : null }
{ showModuleResistances && m.getExplosiveResistance() ? <div className='l'>{translate('explres')}: {formats.pct(m.getExplosiveResistance())}</div> : null } { showModuleResistances && m.getThermalResistance() ? <div className='l'>{translate('thermres')}: {formats.pct(m.getThermalResistance())}</div> : null }
{ showModuleResistances && m.getKineticResistance() ? <div className='l'>{translate('kinres')}: {formats.pct(m.getKineticResistance())}</div> : null } { m.getIntegrity() ? <div className='l'>{translate('integrity')}: {formats.int(m.getIntegrity())}</div> : null }
{ showModuleResistances && m.getThermalResistance() ? <div className='l'>{translate('thermres')}: {formats.pct(m.getThermalResistance())}</div> : null } { validMods.length > 0 ? <div className='r' tabIndex="0" ref={ modButton => this.modButton = modButton }><button tabIndex="-1" onClick={this._toggleModifications.bind(this)} onContextMenu={stopCtxPropagation} onMouseOver={termtip.bind(null, 'modifications')} onMouseOut={tooltip.bind(null, null)}><ListModifications /></button></div> : null }
{ m.getIntegrity() ? <div className='l'>{translate('integrity')}: {formats.int(m.getIntegrity())}</div> : null } </div>
{ validMods.length > 0 ? <div className='r' tabIndex="0" ref={ modButton => this.modButton = modButton }><button tabIndex="-1" onClick={this._toggleModifications.bind(this)} onContextMenu={stopCtxPropagation} onMouseOver={termtip.bind(null, 'modifications')} onMouseOut={tooltip.bind(null, null)}><ListModifications /></button></div> : null } </div>
</div> </div>
</div> {menu}
</div> </div>
{menu} );
</div> }
);
} /**
* Toggle the modifications flag when selecting the modifications icon
/** */
* Toggle the modifications flag when selecting the modifications icon _toggleModifications() {
*/ this._modificationsSelected = !this._modificationsSelected;
_toggleModifications() { }
this._modificationsSelected = !this._modificationsSelected; }
}
}

View File

@@ -20,12 +20,23 @@ export default class StandardSlotSection extends SlotSection {
super(props, context, 'standard', 'core internal'); super(props, context, 'standard', 'core internal');
this._optimizeStandard = this._optimizeStandard.bind(this); this._optimizeStandard = this._optimizeStandard.bind(this);
this._selectBulkhead = this._selectBulkhead.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);
} }
/** /**
* Use the lightest/optimal available standard modules * Use the lightest/optimal available standard modules
*/ */
_optimizeStandard() { _optimizeStandard() {
this.selectedRefId = 'maxjump';
this.props.ship.useLightestStandard(); this.props.ship.useLightestStandard();
this.props.onChange(); this.props.onChange();
this.props.onCargoChange(this.props.ship.cargoCapacity); this.props.onCargoChange(this.props.ship.cargoCapacity);
@@ -39,6 +50,8 @@ export default class StandardSlotSection extends SlotSection {
* @param {integer} bulkheadIndex Bulkhead to use see Constants.BulkheadNames * @param {integer} bulkheadIndex Bulkhead to use see Constants.BulkheadNames
*/ */
_multiPurpose(shielded, bulkheadIndex) { _multiPurpose(shielded, bulkheadIndex) {
this.selectedRefId = 'multipurpose';
if (bulkheadIndex === 2) this.selectedRefId = 'combat';
ShipRoles.multiPurpose(this.props.ship, shielded, bulkheadIndex); ShipRoles.multiPurpose(this.props.ship, shielded, bulkheadIndex);
this.props.onChange(); this.props.onChange();
this.props.onCargoChange(this.props.ship.cargoCapacity); this.props.onCargoChange(this.props.ship.cargoCapacity);
@@ -51,6 +64,7 @@ export default class StandardSlotSection extends SlotSection {
* @param {Boolean} shielded True if shield generator should be included * @param {Boolean} shielded True if shield generator should be included
*/ */
_optimizeCargo(shielded) { _optimizeCargo(shielded) {
this.selectedRefId = 'trader';
ShipRoles.trader(this.props.ship, shielded); ShipRoles.trader(this.props.ship, shielded);
this.props.onChange(); this.props.onChange();
this.props.onCargoChange(this.props.ship.cargoCapacity); this.props.onCargoChange(this.props.ship.cargoCapacity);
@@ -63,6 +77,7 @@ export default class StandardSlotSection extends SlotSection {
* @param {Boolean} shielded True if shield generator should be included * @param {Boolean} shielded True if shield generator should be included
*/ */
_optimizeMiner(shielded) { _optimizeMiner(shielded) {
this.selectedRefId = 'miner';
ShipRoles.miner(this.props.ship, shielded); ShipRoles.miner(this.props.ship, shielded);
this.props.onChange(); this.props.onChange();
this.props.onCargoChange(this.props.ship.cargoCapacity); this.props.onCargoChange(this.props.ship.cargoCapacity);
@@ -75,6 +90,8 @@ export default class StandardSlotSection extends SlotSection {
* @param {Boolean} planetary True if Planetary Vehicle Hangar (PVH) should be included * @param {Boolean} planetary True if Planetary Vehicle Hangar (PVH) should be included
*/ */
_optimizeExplorer(planetary) { _optimizeExplorer(planetary) {
this.selectedRefId = 'explorer';
if (planetary) this.selectedRefId = 'planetary';
ShipRoles.explorer(this.props.ship, planetary); ShipRoles.explorer(this.props.ship, planetary);
this.props.onChange(); this.props.onChange();
this.props.onCargoChange(this.props.ship.cargoCapacity); this.props.onCargoChange(this.props.ship.cargoCapacity);
@@ -86,6 +103,7 @@ export default class StandardSlotSection extends SlotSection {
* Racer role * Racer role
*/ */
_optimizeRacer() { _optimizeRacer() {
this.selectedRefId = 'racer';
ShipRoles.racer(this.props.ship); ShipRoles.racer(this.props.ship);
this.props.onChange(); this.props.onChange();
this.props.onCargoChange(this.props.ship.cargoCapacity); this.props.onCargoChange(this.props.ship.cargoCapacity);
@@ -229,17 +247,17 @@ export default class StandardSlotSection extends SlotSection {
let planetaryDisabled = this.props.ship.internal.length < 4; let planetaryDisabled = this.props.ship.internal.length < 4;
return <div className='select' onClick={(e) => e.stopPropagation()} onContextMenu={stopCtxPropagation}> return <div className='select' onClick={(e) => e.stopPropagation()} onContextMenu={stopCtxPropagation}>
<ul> <ul>
<li className='lc' onClick={this._optimizeStandard}>{translate('Maximize Jump Range')}</li> <li className='lc' tabIndex="0" onClick={this._optimizeStandard} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['maxjump'] = smRef}>{translate('Maximize Jump Range')}</li>
</ul> </ul>
<div className='select-group cap'>{translate('roles')}</div> <div className='select-group cap'>{translate('roles')}</div>
<ul> <ul>
<li className='lc' onClick={this._multiPurpose.bind(this, false, 0)}>{translate('Multi-purpose')}</li> <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' onClick={this._multiPurpose.bind(this, true, 2)}>{translate('Combat')}</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' onClick={this._optimizeCargo.bind(this, true)}>{translate('Trader')}</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' onClick={this._optimizeExplorer.bind(this, false)}>{translate('Explorer')}</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 })} onClick={!planetaryDisabled && this._optimizeExplorer.bind(this, true)}>{translate('Planetary 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' onClick={this._optimizeMiner.bind(this, true)}>{translate('Miner')}</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' onClick={this._optimizeRacer.bind(this)}>{translate('Racer')}</li> <li className='lc' tabIndex="0" onClick={this._optimizeRacer.bind(this)} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['racer'] = smRef}>{translate('Racer')}</li>
</ul> </ul>
</div>; </div>;
} }

View File

@@ -17,6 +17,16 @@ export default class UtilitySlotSection extends SlotSection {
constructor(props, context) { constructor(props, context) {
super(props, context, 'utility', 'utility mounts'); super(props, context, 'utility', 'utility mounts');
this._empty = this._empty.bind(this); 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);
} }
/** /**
@@ -36,6 +46,9 @@ export default class UtilitySlotSection extends SlotSection {
* @param {Synthetic} event Event * @param {Synthetic} event Event
*/ */
_use(group, rating, name, 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.ship.useUtility(group, rating, name, event.getModifierState('Alt'));
this.props.onChange(); this.props.onChange();
this._close(); this._close();
@@ -94,28 +107,28 @@ export default class UtilitySlotSection extends SlotSection {
return <div className='select' onClick={(e) => e.stopPropagation()} onContextMenu={stopCtxPropagation}> return <div className='select' onClick={(e) => e.stopPropagation()} onContextMenu={stopCtxPropagation}>
<ul> <ul>
<li className='lc' onClick={this._empty}>{translate('empty all')}</li> <li className='lc' tabIndex='0' onClick={this._empty} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['emptyall'] = smRef}>{translate('empty all')}</li>
<li className='optional-hide' style={{ textAlign: 'center', marginTop: '1em' }}>{translate('PHRASE_ALT_ALL')}</li> <li className='optional-hide' style={{ textAlign: 'center', marginTop: '1em' }}>{translate('PHRASE_ALT_ALL')}</li>
</ul> </ul>
<div className='select-group cap'>{translate('sb')}</div> <div className='select-group cap'>{translate('sb')}</div>
<ul> <ul>
<li className='c' onClick={_use.bind(this, 'sb', 'A', null)}>A</li> <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' onClick={_use.bind(this, 'sb', 'B', null)}>B</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' onClick={_use.bind(this, 'sb', 'C', null)}>C</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' onClick={_use.bind(this, 'sb', 'D', null)}>D</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' onClick={_use.bind(this, 'sb', 'E', null)}>E</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>
</ul> </ul>
<div className='select-group cap'>{translate('hs')}</div> <div className='select-group cap'>{translate('hs')}</div>
<ul> <ul>
<li className='lc' onClick={_use.bind(this, 'hs', null, 'Heat Sink Launcher')}>{translate('Heat Sink Launcher')}</li> <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>
</ul> </ul>
<div className='select-group cap'>{translate('ch')}</div> <div className='select-group cap'>{translate('ch')}</div>
<ul> <ul>
<li className='lc' onClick={_use.bind(this, 'ch', null, 'Chaff Launcher')}>{translate('Chaff Launcher')}</li> <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>
</ul> </ul>
<div className='select-group cap'>{translate('po')}</div> <div className='select-group cap'>{translate('po')}</div>
<ul> <ul>
<li className='lc' onClick={_use.bind(this, 'po', null, 'Point Defence')}>{translate('Point Defence')}</li> <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>
</ul> </ul>
</div>; </div>;
} }

View File

@@ -58,6 +58,7 @@ export default class OutfittingPage extends Page {
this._fuelUpdated = this._fuelUpdated.bind(this); this._fuelUpdated = this._fuelUpdated.bind(this);
this._opponentUpdated = this._opponentUpdated.bind(this); this._opponentUpdated = this._opponentUpdated.bind(this);
this._engagementRangeUpdated = this._engagementRangeUpdated.bind(this); this._engagementRangeUpdated = this._engagementRangeUpdated.bind(this);
this._sectionMenuRefs = {};
} }
/** /**
@@ -555,7 +556,6 @@ export default class OutfittingPage extends Page {
const requirements = Ships[ship.id].requirements; const requirements = Ships[ship.id].requirements;
let requirementElements = []; let requirementElements = [];
/** /**
* Render the requirements for a ship / etc * Render the requirements for a ship / etc
* @param {string} className Class names * @param {string} className Class names
@@ -608,11 +608,11 @@ export default class OutfittingPage extends Page {
</div> </div>
{/* Main tables */} {/* Main tables */}
<ShipSummaryTable ship={ship} fuel={fuel} cargo={cargo} marker={shipSummaryMarker} pips={{ sys: this.state.sys, wep: this.state.wep, eng: this.state.eng }} /> <ShipSummaryTable ship={ship} fuel={fuel} cargo={cargo} marker={shipSummaryMarker} pips={{sys: this.state.sys, wep: this.state.wep, eng: this.state.eng}} />
<StandardSlotSection ship={ship} fuel={fuel} cargo={cargo} code={standardSlotMarker} onChange={shipUpdated} onCargoChange={this._cargoUpdated} onFuelChange={this._fuelUpdated} currentMenu={menu} /> <StandardSlotSection ship={ship} fuel={fuel} cargo={cargo} 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} /> <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} /> <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} /> <UtilitySlotSection ship={ship} code={hardpointsSlotMarker} onChange={shipUpdated} onCargoChange={this._cargoUpdated} onFuelChange={this._fuelUpdated} currentMenu={menu} sectionMenuRefs={this._sectionMenuRefs}/>
{/* Control of ship and opponent */} {/* Control of ship and opponent */}
<div className='group quarter'> <div className='group quarter'>