2.0.1 Beta

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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