mirror of
https://github.com/EDCD/coriolis.git
synced 2025-12-08 22:33:24 +00:00
244 lines
7.0 KiB
JavaScript
244 lines
7.0 KiB
JavaScript
import React from 'react';
|
|
import PropTypes from 'prop-types';
|
|
import TranslatedComponent from './TranslatedComponent';
|
|
import cn from 'classnames';
|
|
import { ListModifications, Modified } from './SvgIcons';
|
|
import AvailableModulesMenu from './AvailableModulesMenu';
|
|
import ModificationsMenu from './ModificationsMenu';
|
|
import { stopCtxPropagation, wrapCtxMenu } from '../utils/UtilityFunctions';
|
|
import { blueprintTooltip } from '../utils/BlueprintFunctions';
|
|
import { Module } from 'ed-forge';
|
|
import { TYPES } from 'ed-forge/lib/src/data/slots';
|
|
import autoBind from 'auto-bind';
|
|
import { toPairs } from 'lodash';
|
|
|
|
const HARDPOINT_SLOT_LABELS = {
|
|
1: 'S',
|
|
2: 'M',
|
|
3: 'L',
|
|
4: 'H',
|
|
};
|
|
|
|
/**
|
|
* Abstract Slot
|
|
*/
|
|
export default class Slot extends TranslatedComponent {
|
|
static propTypes = {
|
|
currentMenu: PropTypes.any,
|
|
hideSearch: PropTypes.bool,
|
|
m: PropTypes.instanceOf(Module),
|
|
warning: PropTypes.func,
|
|
drag: PropTypes.func,
|
|
drop: PropTypes.func,
|
|
dropClass: PropTypes.string,
|
|
propsToShow: PropTypes.object.isRequired,
|
|
onPropToggle: PropTypes.func.isRequired,
|
|
};
|
|
|
|
/**
|
|
* Constructor
|
|
* @param {Object} props React Component properties
|
|
*/
|
|
constructor(props) {
|
|
super(props);
|
|
autoBind(this);
|
|
this.state = { menuIndex: 0 };
|
|
}
|
|
|
|
/**
|
|
* Opens a menu while setting state.
|
|
* @param {Object} newMenuIndex New menu index
|
|
* @param {Event} event Event object
|
|
*/
|
|
_openMenu(newMenuIndex, event) {
|
|
const slotName = this.props.m.getSlot();
|
|
if (
|
|
this.props.currentMenu === slotName &&
|
|
newMenuIndex === this.state.menuIndex
|
|
) {
|
|
this.context.closeMenu();
|
|
} {
|
|
this.setState({ menuIndex: newMenuIndex });
|
|
this.context.openMenu(slotName);
|
|
}
|
|
// If we don't stop event propagation, the underlying divs also might
|
|
// get clicked which would open up other menus
|
|
event.stopPropagation();
|
|
}
|
|
|
|
/**
|
|
* Generate the slot contents
|
|
* @return {React.Component} Slot contents
|
|
*/
|
|
_getSlotDetails() {
|
|
const { m, propsToShow } = this.props;
|
|
let { termtip, tooltip, language } = this.context;
|
|
const { translate, units, formats } = language;
|
|
|
|
if (m.isEmpty()) {
|
|
return <div className="empty">
|
|
{translate(
|
|
m.isOnSlot(TYPES.MILITARY) ? 'emptyrestricted' : 'empty'
|
|
)}
|
|
</div>;
|
|
} else {
|
|
let classRating = m.getClassRating();
|
|
let { drag, drop } = this.props;
|
|
|
|
// Modifications tooltip shows blueprint and grade, if available
|
|
let modTT = translate('modified');
|
|
const blueprint = m.getBlueprint();
|
|
const experimental = m.getExperimental();
|
|
const grade = m.getBlueprintGrade();
|
|
if (blueprint) {
|
|
modTT = `${translate(blueprint)} ${translate('grade')}: ${grade}`;
|
|
if (experimental) {
|
|
modTT += `, ${translate(experimental)}`;
|
|
}
|
|
modTT = (
|
|
<div>
|
|
<div>{modTT}</div>
|
|
{blueprintTooltip(language, m)}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
let mass = m.get('mass') || m.get('cargo') || m.get('fuel') || 0;
|
|
return (
|
|
<div
|
|
className={cn('details', { disabled: !m.isEnabled() })}
|
|
draggable="true"
|
|
onDragStart={drag}
|
|
onDragEnd={drop}
|
|
>
|
|
<div className={'cb'}>
|
|
<div className={'l'}>
|
|
{classRating} {translate(m.readMeta('type'))}
|
|
{blueprint && (
|
|
<span
|
|
onMouseOver={termtip.bind(null, modTT)}
|
|
onMouseOut={tooltip.bind(null, null)}
|
|
>
|
|
<Modified />
|
|
</span>
|
|
)}
|
|
</div>
|
|
{propsToShow.mass ?
|
|
<div className={'r'}>
|
|
{formats.round(mass)}
|
|
{units.T}
|
|
</div> : null}
|
|
</div>
|
|
<div className={'cb'}>
|
|
{toPairs(propsToShow).sort().map(([prop, show]) => {
|
|
const { unit, value } = m.getFormatted(prop, true);
|
|
// Don't show mass again; it's already shown on the top right
|
|
// corner of a slot
|
|
if (!show || isNaN(value) || prop == 'mass') {
|
|
return null;
|
|
} else {
|
|
return (<div className='l'>
|
|
{translate(prop)}: {formats.round(value)}{unit}
|
|
</div>);
|
|
}
|
|
})}
|
|
{(m.getApplicableBlueprints() || []).length > 0 ? (
|
|
<div className="r">
|
|
<button onClick={this._openMenu.bind(this, 1)}
|
|
onContextMenu={stopCtxPropagation}
|
|
onMouseOver={termtip.bind(null, translate('modifications'))}
|
|
onMouseOut={tooltip.bind(null, null)}
|
|
>
|
|
<ListModifications />
|
|
</button>
|
|
</div>
|
|
) : null}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get the label for the slot size/class
|
|
* Should be overriden if necessary
|
|
* @return {string} label
|
|
*/
|
|
_getMaxClassLabel() {
|
|
const { m } = this.props;
|
|
let size = m.getSizeNum();
|
|
switch (true) {
|
|
case m.getSlot() === 'armour':
|
|
return '';
|
|
case size === 0:
|
|
// This can also happen for armour but that case was handled above
|
|
return 'U';
|
|
case m.isOnSlot(TYPES.HARDPOINT):
|
|
return HARDPOINT_SLOT_LABELS[size];
|
|
default:
|
|
return size;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Empty slot on right-click
|
|
* @param {SyntheticEvent} event Event
|
|
*/
|
|
_contextMenu(event) {
|
|
event.stopPropagation();
|
|
event.preventDefault();
|
|
const { m } = this.props;
|
|
m.reset();
|
|
if (this.props.currentMenu === m.getSlot()) {
|
|
this.context.closeMenu();
|
|
} else {
|
|
this.forceUpdate();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Render the slot
|
|
* @return {React.Component} The slot
|
|
*/
|
|
render() {
|
|
let language = this.context.language;
|
|
let translate = language.translate;
|
|
let {
|
|
currentMenu, m, dropClass, dragOver, warning, hideSearch, propsToShow,
|
|
onPropToggle,
|
|
} = this.props;
|
|
const { menuIndex } = this.state;
|
|
|
|
// TODO: implement touch dragging
|
|
const selected = currentMenu === m.getSlot();
|
|
return (
|
|
<div
|
|
className={cn('slot', dropClass, { selected })}
|
|
onContextMenu={this._contextMenu}
|
|
onDragOver={dragOver} tabIndex="0"
|
|
onClick={this._openMenu.bind(this, 0)}
|
|
>
|
|
<div className={cn(
|
|
'details-container',
|
|
{ warning: warning && warning(m) },
|
|
)}>
|
|
<div className="sz">{this._getMaxClassLabel(translate)}</div>
|
|
{this._getSlotDetails()}
|
|
</div>
|
|
{selected && menuIndex === 0 &&
|
|
<AvailableModulesMenu
|
|
m={m} hideSearch={hideSearch}
|
|
onSelect={(item) => {
|
|
m.setItem(item);
|
|
this.context.closeMenu();
|
|
}}
|
|
warning={warning}
|
|
/>}
|
|
{selected && menuIndex === 1 &&
|
|
<ModificationsMenu m={m} propsToShow={propsToShow}
|
|
onPropToggle={onPropToggle} />}
|
|
</div>
|
|
);
|
|
}
|
|
}
|