mirror of
https://github.com/EDCD/coriolis.git
synced 2025-12-10 15:15:34 +00:00
Rewrite AvailableModulesMenu.jsx
This commit is contained in:
committed by
Felix Linker
parent
9797a8d781
commit
33c201800e
@@ -1,120 +1,19 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import * as ModuleUtils from '../shipyard/ModuleUtils';
|
|
||||||
import TranslatedComponent from './TranslatedComponent';
|
import TranslatedComponent from './TranslatedComponent';
|
||||||
import { stopCtxPropagation } from '../utils/UtilityFunctions';
|
import { stopCtxPropagation } from '../utils/UtilityFunctions';
|
||||||
import cn from 'classnames';
|
import cn from 'classnames';
|
||||||
import { MountFixed, MountGimballed, MountTurret } from './SvgIcons';
|
import { MountFixed, MountGimballed, MountTurret } from './SvgIcons';
|
||||||
import FuzzySearch from 'react-fuzzy';
|
import FuzzySearch from 'react-fuzzy';
|
||||||
|
import { getModuleInfo } from 'ed-forge/lib/data/items';
|
||||||
|
import { groupBy, sortBy } from 'lodash';
|
||||||
|
|
||||||
const PRESS_THRESHOLD = 500; // mouse/touch down threshold
|
const PRESS_THRESHOLD = 500; // mouse/touch down threshold
|
||||||
|
|
||||||
/*
|
const MOUNT_MAP = {
|
||||||
* Categorisation of module groups
|
fixed: <MountFixed className={'lg'} />,
|
||||||
*/
|
gimbal: <MountGimballed className={'lg'} />,
|
||||||
const GRPCAT = {
|
turret: <MountTurret className={'lg'} />,
|
||||||
'sg': 'shields',
|
|
||||||
'bsg': 'shields',
|
|
||||||
'psg': 'shields',
|
|
||||||
'scb': 'shields',
|
|
||||||
'cc': 'limpet controllers',
|
|
||||||
'fx': 'limpet controllers',
|
|
||||||
'hb': 'limpet controllers',
|
|
||||||
'pc': 'limpet controllers',
|
|
||||||
'rpl': 'limpet controllers',
|
|
||||||
'pce': 'passenger cabins',
|
|
||||||
'pci': 'passenger cabins',
|
|
||||||
'pcm': 'passenger cabins',
|
|
||||||
'pcq': 'passenger cabins',
|
|
||||||
'fh': 'hangars',
|
|
||||||
'pv': 'hangars',
|
|
||||||
'fs': 'fuel',
|
|
||||||
'ft': 'fuel',
|
|
||||||
'hr': 'structural reinforcement',
|
|
||||||
'mrp': 'structural reinforcement',
|
|
||||||
'bl': 'lasers',
|
|
||||||
'pl': 'lasers',
|
|
||||||
'ul': 'lasers',
|
|
||||||
'ml': 'lasers',
|
|
||||||
'c': 'projectiles',
|
|
||||||
'mc': 'projectiles',
|
|
||||||
'axmc': 'experimental',
|
|
||||||
'fc': 'projectiles',
|
|
||||||
'rfl': 'experimental',
|
|
||||||
'pa': 'projectiles',
|
|
||||||
'rg': 'projectiles',
|
|
||||||
'mr': 'ordnance',
|
|
||||||
'axmr': 'experimental',
|
|
||||||
'rcpl': 'experimental',
|
|
||||||
'dtl': 'experimental',
|
|
||||||
'tbsc': 'experimental',
|
|
||||||
'tbem': 'experimental',
|
|
||||||
'tbrfl': 'experimental',
|
|
||||||
'mahr': 'experimental',
|
|
||||||
'rsl': 'experimental',
|
|
||||||
'tp': 'ordnance',
|
|
||||||
'nl': 'ordnance',
|
|
||||||
'sc': 'scanners',
|
|
||||||
'ss': 'scanners',
|
|
||||||
// Utilities
|
|
||||||
'cs': 'scanners',
|
|
||||||
'kw': 'scanners',
|
|
||||||
'ws': 'scanners',
|
|
||||||
'xs': 'scanners',
|
|
||||||
'ch': 'defence',
|
|
||||||
'po': 'defence',
|
|
||||||
'ec': 'defence',
|
|
||||||
'sfn': 'defence',
|
|
||||||
// Guardian
|
|
||||||
'gpp': 'guardian',
|
|
||||||
'gpc': 'guardian',
|
|
||||||
'gsrp': 'guardian',
|
|
||||||
'ggc': 'guardian',
|
|
||||||
'gfsb': 'guardian',
|
|
||||||
'gmrp': 'guardian',
|
|
||||||
'gsc': 'guardian',
|
|
||||||
'ghrp': 'guardian',
|
|
||||||
|
|
||||||
// Mining
|
|
||||||
'scl': 'mining',
|
|
||||||
'pwa': 'mining',
|
|
||||||
'sdm': 'mining',
|
|
||||||
|
|
||||||
// Assists
|
|
||||||
'dc': 'flight assists',
|
|
||||||
'sua': 'flight assists',
|
|
||||||
};
|
|
||||||
// Order here is the order in which items will be shown in the modules menu
|
|
||||||
const CATEGORIES = {
|
|
||||||
// Internals
|
|
||||||
'am': ['am'],
|
|
||||||
'cr': ['cr'],
|
|
||||||
'fi': ['fi'],
|
|
||||||
'fuel': ['ft', 'fs'],
|
|
||||||
'hangars': ['fh', 'pv'],
|
|
||||||
'limpet controllers': ['cc', 'fx', 'hb', 'pc', 'rpl'],
|
|
||||||
'passenger cabins': ['pce', 'pci', 'pcm', 'pcq'],
|
|
||||||
'rf': ['rf'],
|
|
||||||
'shields': ['sg', 'bsg', 'psg', 'scb'],
|
|
||||||
'structural reinforcement': ['hr', 'mrp'],
|
|
||||||
'flight assists': ['dc', 'sua'],
|
|
||||||
|
|
||||||
// Hardpoints
|
|
||||||
'lasers': ['pl', 'ul', 'bl'],
|
|
||||||
'projectiles': ['mc', 'c', 'fc', 'pa', 'rg'],
|
|
||||||
'ordnance': ['mr', 'tp', 'nl'],
|
|
||||||
// Utilities
|
|
||||||
'sb': ['sb'],
|
|
||||||
'hs': ['hs'],
|
|
||||||
'defence': ['ch', 'po', 'ec'],
|
|
||||||
'scanners': ['sc', 'ss', 'cs', 'kw', 'ws'], // Overloaded with internal scanners
|
|
||||||
// Experimental
|
|
||||||
'experimental': ['axmc', 'axmr', 'rfl', 'tbrfl', 'tbsc', 'tbem', 'xs', 'sfn', 'rcpl', 'dtl', 'rsl', 'mahr',],
|
|
||||||
|
|
||||||
// Guardian
|
|
||||||
'guardian': ['gpp', 'gpd', 'gpc', 'ggc', 'gsrp', 'gfsb', 'ghrp', 'gmrp', 'gsc'],
|
|
||||||
|
|
||||||
'mining': ['ml', 'scl', 'pwa', 'sdm', 'abl'],
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -127,9 +26,6 @@ export default class AvailableModulesMenu extends TranslatedComponent {
|
|||||||
m: PropTypes.object,
|
m: PropTypes.object,
|
||||||
ship: PropTypes.object.isRequired,
|
ship: PropTypes.object.isRequired,
|
||||||
warning: PropTypes.func,
|
warning: PropTypes.func,
|
||||||
firstSlotId: PropTypes.string,
|
|
||||||
lastSlotId: PropTypes.string,
|
|
||||||
activeSlotId: PropTypes.string,
|
|
||||||
slotDiv: PropTypes.object
|
slotDiv: PropTypes.object
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -143,7 +39,6 @@ export default class AvailableModulesMenu extends TranslatedComponent {
|
|||||||
this._hideDiff = this._hideDiff.bind(this);
|
this._hideDiff = this._hideDiff.bind(this);
|
||||||
this._showSearch = this._showSearch.bind(this);
|
this._showSearch = this._showSearch.bind(this);
|
||||||
this.state = this._initState(props, context);
|
this.state = this._initState(props, context);
|
||||||
this.slotItems = [];// Array to hold <li> refs.
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -153,165 +48,92 @@ export default class AvailableModulesMenu extends TranslatedComponent {
|
|||||||
* @return {Object} list: Array of React Components, currentGroup Component if any
|
* @return {Object} list: Array of React Components, currentGroup Component if any
|
||||||
*/
|
*/
|
||||||
_initState(props, context) {
|
_initState(props, context) {
|
||||||
let translate = context.language.translate;
|
const { translate } = context.language;
|
||||||
let { m, warning, onSelect, ship } = props;
|
const { m, warning, onSelect, ship } = props;
|
||||||
let list, currentGroup;
|
const list = [], fuzzy = [];
|
||||||
|
let currentGroup;
|
||||||
|
|
||||||
let buildGroup = this._buildGroup.bind(
|
const modules = m.getApplicableItems().map(getModuleInfo);
|
||||||
this,
|
const categories = groupBy(modules, (info) => info.meta.type);
|
||||||
|
// Build categories sorted by translated category name
|
||||||
|
const sortedKeys = sortBy(Object.keys(categories), translate);
|
||||||
|
for (const category of sortedKeys) {
|
||||||
|
const catName = translate(category);
|
||||||
|
const infos = categories[category];
|
||||||
|
list.push(
|
||||||
|
<div key={'div-' + category} className="select-group cap">{catName}</div>,
|
||||||
|
this._buildGroup(
|
||||||
ship,
|
ship,
|
||||||
translate,
|
|
||||||
m,
|
m,
|
||||||
warning,
|
warning,
|
||||||
(m, event) => {
|
(m, event) => {
|
||||||
this._hideDiff(event);
|
this._hideDiff(event);
|
||||||
onSelect(m);
|
onSelect(m);
|
||||||
}
|
},
|
||||||
|
category,
|
||||||
|
infos,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
fuzzy.push(
|
||||||
|
...infos.map((info) => {
|
||||||
|
const { meta } = info;
|
||||||
|
const mount = meta.mount ? ' ' + translate(meta.mount) : '';
|
||||||
|
return {
|
||||||
|
grp: category,
|
||||||
|
m: info.proto.Item,
|
||||||
|
name: `${meta.class}${meta.rating}${mount} ${catName}`,
|
||||||
|
};
|
||||||
|
}),
|
||||||
);
|
);
|
||||||
let fuzzy = [];
|
|
||||||
let modules = m.getApplicableItems();
|
|
||||||
if (modules instanceof Array) {
|
|
||||||
list = buildGroup(modules[0].grp, modules);
|
|
||||||
} else {
|
|
||||||
list = [];
|
|
||||||
// At present time slots with grouped options (Hardpoints and Internal) can be empty
|
|
||||||
if (m) {
|
|
||||||
let emptyId = 'empty';
|
|
||||||
if (this.firstSlotId == null) this.firstSlotId = emptyId;
|
|
||||||
let keyDown = this._keyDown.bind(this, onSelect);
|
|
||||||
list.push(<div className='empty-c upp' key={emptyId} data-id={emptyId} onClick={onSelect.bind(null, null)}
|
|
||||||
onKeyDown={keyDown} tabIndex="0"
|
|
||||||
ref={slotItem => this.slotItems[emptyId] = slotItem}>{translate('empty')}</div>);
|
|
||||||
}
|
}
|
||||||
|
return { list, currentGroup, fuzzy, trackingFocus: false };
|
||||||
// Need to regroup the modules by our own categorisation
|
|
||||||
let catmodules = {};
|
|
||||||
// Pre-create to preserve ordering
|
|
||||||
for (let cat in CATEGORIES) {
|
|
||||||
catmodules[cat] = [];
|
|
||||||
}
|
|
||||||
for (let g in modules) {
|
|
||||||
const moduleCategory = GRPCAT[g] || g;
|
|
||||||
const existing = catmodules[moduleCategory] || [];
|
|
||||||
catmodules[moduleCategory] = existing.concat(modules[g]);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let category in catmodules) {
|
|
||||||
let categoryHeader = false;
|
|
||||||
// Order through CATEGORIES if present
|
|
||||||
const categories = CATEGORIES[category] || [category];
|
|
||||||
if (categories && categories.length) {
|
|
||||||
for (let n in categories) {
|
|
||||||
const grp = categories[n];
|
|
||||||
// We now have the group and the category. We might not have any modules, though...
|
|
||||||
if (modules[grp]) {
|
|
||||||
// Decide if we need a category header as well as a group header
|
|
||||||
if (categories.length === 1) {
|
|
||||||
// Show category header instead of group header
|
|
||||||
if (m && grp == m.grp) {
|
|
||||||
list.push(<div ref={(elem) => this.groupElem = elem} key={category}
|
|
||||||
className={'select-category upp'}>{translate(category)}</div>);
|
|
||||||
} else {
|
|
||||||
list.push(<div key={category} className={'select-category upp'}>{translate(category)}</div>);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Show category header as well as group header
|
|
||||||
if (!categoryHeader) {
|
|
||||||
list.push(<div key={category} className={'select-category upp'}>{translate(category)}</div>);
|
|
||||||
categoryHeader = true;
|
|
||||||
}
|
|
||||||
if (m && grp == m.grp) {
|
|
||||||
list.push(<div ref={(elem) => this.groupElem = elem} key={grp}
|
|
||||||
className={'select-group cap'}>{translate(grp)}</div>);
|
|
||||||
} else {
|
|
||||||
list.push(<div key={grp} className={'select-group cap'}>{translate(grp)}</div>);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
list.push(buildGroup(grp, modules[grp]));
|
|
||||||
for (const i of modules[grp]) {
|
|
||||||
let mount = '';
|
|
||||||
if (i.mount === 'F') {
|
|
||||||
mount = 'Fixed';
|
|
||||||
} else if (i.mount === 'G') {
|
|
||||||
mount = 'Gimballed';
|
|
||||||
} else if (i.mount === 'T') {
|
|
||||||
mount = 'Turreted';
|
|
||||||
}
|
|
||||||
const fuzz = { grp, m: i, name: `${i.class}${i.rating}${mount ? ' ' + mount : ''} ${translate(grp)}` };
|
|
||||||
fuzzy.push(fuzz);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let trackingFocus = false;
|
|
||||||
return { list, currentGroup, fuzzy, trackingFocus };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate React Components for Module Group
|
* Generate React Components for Module Group
|
||||||
* @param {Ship} ship Ship the selection is for
|
* @param {Ship} ship Ship the selection is for
|
||||||
* @param {Function} translate Translate function
|
|
||||||
* @param {Object} mountedModule Mounted Module
|
* @param {Object} mountedModule Mounted Module
|
||||||
* @param {Function} warningFunc Warning function
|
* @param {Function} warningFunc Warning function
|
||||||
* @param {function} onSelect Select/Mount callback
|
* @param {function} onSelect Select/Mount callback
|
||||||
* @param {string} grp Group name
|
* @param {String} category Category key
|
||||||
* @param {Array} modules Available modules
|
* @param {Array} modules Available modules
|
||||||
* @return {React.Component} Available Module Group contents
|
* @return {React.Component} Available Module Group contents
|
||||||
*/
|
*/
|
||||||
_buildGroup(ship, translate, mountedModule, warningFunc, onSelect, grp, modules) {
|
_buildGroup(ship, mountedModule, warningFunc, onSelect, category, modules) {
|
||||||
let prevClass = null, prevRating = null, prevName;
|
const classMapping = groupBy(modules, (info) => info.meta.class);
|
||||||
let elems = [];
|
|
||||||
|
|
||||||
const sortedModules = modules.sort(this._moduleOrder);
|
const itemsPerClass = Math.max(
|
||||||
|
...Object.values(classMapping).map((l) => l.length),
|
||||||
|
);
|
||||||
|
const itemsPerRow = itemsPerClass <= 2 ? 6 : itemsPerClass;
|
||||||
|
// Nested array of <li> elements; will be flattened before being rendered.
|
||||||
|
// Each sub-array represents one row in the final view.
|
||||||
|
const elems = [[]];
|
||||||
|
|
||||||
// Calculate the number of items per class. Used so we don't have long lists with only a few items in each row
|
// Reverse sort for descending order of module class
|
||||||
const tmp = sortedModules.map((v, i) => v['class']).reduce((count, cls) => {
|
for (const clazz of Object.keys(classMapping).sort().reverse()) {
|
||||||
count[cls] = ++count[cls] || 1;
|
for (let info of sortBy(
|
||||||
return count;
|
classMapping[clazz],
|
||||||
}, {});
|
(info) => info.meta.mount || info.meta.rating,
|
||||||
const itemsPerClass = Math.max.apply(null, Object.keys(tmp).map(key => tmp[key]));
|
)) {
|
||||||
|
const { meta } = info;
|
||||||
|
const { Item } = info.proto;
|
||||||
|
|
||||||
let itemsOnThisRow = 0;
|
// Can only be true if shieldgenmaximalmass is defined, i.e. this
|
||||||
for (let i = 0; i < sortedModules.length; i++) {
|
// module must be a shield generator
|
||||||
let m = sortedModules[i];
|
let disabled = info.props.shieldgenmaximalmass < ship.getBaseProperty('hullmass');
|
||||||
let mount = null;
|
if (meta.experimental && !mountedModule.readMeta('experimental')) {
|
||||||
let disabled = false;
|
disabled =
|
||||||
prevName = m.name;
|
4 <=
|
||||||
if (ModuleUtils.isShieldGenerator(m.grp)) {
|
ship.getHardpoints().filter((m) => m.readMeta('experimental'))
|
||||||
// Shield generators care about maximum hull mass
|
.length;
|
||||||
disabled = ship.hullMass > m.maxmass;
|
|
||||||
// If the mounted module is experimental as well, we can replace it so
|
|
||||||
// the maximum does not apply
|
|
||||||
} else if (m.experimental && (!mountedModule || !mountedModule.experimental)) {
|
|
||||||
disabled = 4 <= ship.hardpoints.filter(o => o.m && o.m.experimental).length;
|
|
||||||
}
|
}
|
||||||
let active = mountedModule && mountedModule.id === m.id;
|
|
||||||
let classes = cn(m.name ? 'lc' : 'c', {
|
|
||||||
warning: !disabled && warningFunc && warningFunc(m),
|
|
||||||
active,
|
|
||||||
disabled
|
|
||||||
});
|
|
||||||
let eventHandlers;
|
|
||||||
|
|
||||||
if (disabled) {
|
// Default event handlers for objects that are disabled
|
||||||
eventHandlers = {
|
let eventHandlers = {};
|
||||||
onKeyDown: this._keyDown.bind(this, null),
|
if (!disabled) {
|
||||||
onKeyUp: this._keyUp.bind(this, null)
|
const showDiff = this._showDiff.bind(this, mountedModule, info);
|
||||||
|
const select = onSelect.bind(null, info);
|
||||||
};
|
|
||||||
} else {
|
|
||||||
/**
|
|
||||||
* Get the ids of the first and last <li> elements in the <ul> that are focusable (i.e. are not active or disabled)
|
|
||||||
* Will be used to keep focus inside the <ul> on Tab and Shift-Tab while it is visible
|
|
||||||
*/
|
|
||||||
if (this.firstSlotId == null) this.firstSlotId = sortedModules[i].id;
|
|
||||||
if (active) this.activeSlotId = sortedModules[i].id;
|
|
||||||
this.lastSlotId = sortedModules[i].id;
|
|
||||||
|
|
||||||
let showDiff = this._showDiff.bind(this, mountedModule, m);
|
|
||||||
let select = onSelect.bind(null, m);
|
|
||||||
|
|
||||||
eventHandlers = {
|
eventHandlers = {
|
||||||
onMouseEnter: this._over.bind(this, showDiff),
|
onMouseEnter: this._over.bind(this, showDiff),
|
||||||
@@ -319,64 +141,61 @@ export default class AvailableModulesMenu extends TranslatedComponent {
|
|||||||
onTouchEnd: this._touchEnd.bind(this, select),
|
onTouchEnd: this._touchEnd.bind(this, select),
|
||||||
onMouseLeave: this._hideDiff,
|
onMouseLeave: this._hideDiff,
|
||||||
onClick: select,
|
onClick: select,
|
||||||
onKeyDown: this._keyDown.bind(this, select),
|
|
||||||
onKeyUp: this._keyUp.bind(this, select)
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (m.mount) {
|
const li = (
|
||||||
case 'F':
|
<li key={Item} data-id={Item}
|
||||||
mount = <MountFixed className={'lg'}/>;
|
ref={Item === mountedModule.getItem() ? (ref) => { this.activeSlotRef = ref; } : undefined}
|
||||||
break;
|
className={cn('c', {
|
||||||
case 'G':
|
warning: !disabled && warningFunc && warningFunc(info),
|
||||||
mount = <MountGimballed className={'lg'}/>;
|
active: mountedModule.getItem() === Item,
|
||||||
break;
|
disabled,
|
||||||
case 'T':
|
})}
|
||||||
mount = <MountTurret className={'lg'}/>;
|
{...eventHandlers}
|
||||||
break;
|
>{MOUNT_MAP[meta.mount]}{meta.class}{meta.rating}</li>
|
||||||
}
|
|
||||||
if (m.name && m.name === prevName) {
|
|
||||||
// elems.push(<br key={'b' + m.grp + i} />);
|
|
||||||
itemsOnThisRow = 0;
|
|
||||||
}
|
|
||||||
if (itemsOnThisRow == 6 || i > 0 && sortedModules.length > 3 && itemsPerClass > 2 && m.class != prevClass && (m.rating != prevRating || m.mount)) {
|
|
||||||
elems.push(<br key={'b' + m.grp + i}/>);
|
|
||||||
itemsOnThisRow = 0;
|
|
||||||
}
|
|
||||||
let tbIdx = (classes.indexOf('disabled') < 0) ? 0 : undefined;
|
|
||||||
elems.push(
|
|
||||||
<li key={m.id} data-id={m.id} className={classes} {...eventHandlers} tabIndex={tbIdx}
|
|
||||||
ref={slotItem => this.slotItems[m.id] = slotItem}>
|
|
||||||
{mount}
|
|
||||||
{(mount ? ' ' : '') + m.class + m.rating + (m.missile ? '/' + m.missile : '') + (m.name ? ' ' + translate(m.name) : '')}
|
|
||||||
</li>
|
|
||||||
);
|
);
|
||||||
|
|
||||||
itemsOnThisRow++;
|
const tail = elems.pop();
|
||||||
prevClass = m.class;
|
let newTail = [tail];
|
||||||
prevRating = m.rating;
|
if (tail.length < itemsPerRow) {
|
||||||
prevName = m.name;
|
// If the row has not grown too long, the new <li> element can be
|
||||||
|
// added to the row itself
|
||||||
|
tail.push(li);
|
||||||
|
} else {
|
||||||
|
// Otherwise, the last row gets a line break element added and this
|
||||||
|
// item is put into a new row
|
||||||
|
tail.push(<br key={elems.length}/>);
|
||||||
|
newTail.push([li]);
|
||||||
}
|
}
|
||||||
return <ul key={'modules' + grp}>{elems}</ul>;
|
elems.push(...newTail);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return <ul key={'ul' + category}>{[].concat(...elems)}</ul>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate tooltip content for the difference between the
|
* Generate tooltip content for the difference between the
|
||||||
* mounted module and the hovered modules
|
* mounted module and the hovered modules
|
||||||
* @param {Object} mm The module mounet currently
|
* @param {Object} mountedModule The module mounted currently
|
||||||
* @param {Object} m The hovered module
|
* @param {Object} hoveringModule The hovered module
|
||||||
* @param {DOMRect} rect DOMRect for target element
|
* @param {DOMRect} rect DOMRect for target element
|
||||||
*/
|
*/
|
||||||
_showDiff(mm, m, rect) {
|
_showDiff(mountedModule, hoveringModule, rect) {
|
||||||
if (this.props.diffDetails) {
|
if (this.props.diffDetails) {
|
||||||
this.touchTimeout = null;
|
this.touchTimeout = null;
|
||||||
this.context.tooltip(this.props.diffDetails(m, mm), rect);
|
this.context.tooltip(
|
||||||
|
this.props.diffDetails(hoveringModule, mountedModule),
|
||||||
|
rect,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate tooltip content for the difference between the
|
* Generate tooltip content for the difference between the
|
||||||
* mounted module and the hovered modules
|
* mounted module and the hovered modules
|
||||||
|
* @returns {React.Component} Search component if available
|
||||||
*/
|
*/
|
||||||
_showSearch() {
|
_showSearch() {
|
||||||
if (this.props.modules instanceof Array) {
|
if (this.props.modules instanceof Array) {
|
||||||
@@ -442,41 +261,6 @@ export default class AvailableModulesMenu extends TranslatedComponent {
|
|||||||
this._hideDiff();
|
this._hideDiff();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Key down - select module on Enter key, move to next/previous module on Tab/Shift-Tab, close on Esc
|
|
||||||
* @param {Function} select Select module callback
|
|
||||||
* @param {SyntheticEvent} event Event
|
|
||||||
*/
|
|
||||||
_keyDown(select, event) {
|
|
||||||
let className = event.currentTarget.attributes['class'].value;
|
|
||||||
if (event.key == 'Enter' && className.indexOf('disabled') < 0 && className.indexOf('active') < 0) {
|
|
||||||
select();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let elemId = event.currentTarget.attributes['data-id'].value;
|
|
||||||
if (className.indexOf('disabled') < 0 && event.key == 'Tab') {
|
|
||||||
if (event.shiftKey && elemId == this.firstSlotId) {
|
|
||||||
event.preventDefault();
|
|
||||||
this.slotItems[this.lastSlotId].focus();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!event.shiftKey && elemId == this.lastSlotId) {
|
|
||||||
event.preventDefault();
|
|
||||||
this.slotItems[this.firstSlotId].focus();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Key Up
|
|
||||||
* @param {Function} select Select module callback
|
|
||||||
* @param {SytheticEvent} event Event
|
|
||||||
*/
|
|
||||||
_keyUp(select, event) {
|
|
||||||
// nothing here yet
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hide diff tooltip
|
* Hide diff tooltip
|
||||||
* @param {SyntheticEvent} event Event
|
* @param {SyntheticEvent} event Event
|
||||||
@@ -487,60 +271,12 @@ export default class AvailableModulesMenu extends TranslatedComponent {
|
|||||||
this.context.tooltip();
|
this.context.tooltip();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Order two modules suitably for display in module selection
|
|
||||||
* @param {Object} a the first module
|
|
||||||
* @param {Object} b the second module
|
|
||||||
* @return {int} -1 if the first module should go first, 1 if the second module should go first
|
|
||||||
*/
|
|
||||||
_moduleOrder(a, b) {
|
|
||||||
// Named modules go last
|
|
||||||
if (!a.name && b.name) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
if (a.name && !b.name) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
// Class ordered from highest (8) to lowest (1)
|
|
||||||
if (a.class < b.class) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
if (a.class > b.class) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
// Mount type, if applicable
|
|
||||||
if (a.mount && b.mount && a.mount !== b.mount) {
|
|
||||||
if (a.mount === 'F' || (a.mount === 'G' && b.mount === 'T')) {
|
|
||||||
return -1;
|
|
||||||
} else {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Rating ordered from highest (A) to lowest (E)
|
|
||||||
if (a.rating < b.rating) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
if (a.rating > b.rating) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
// Do not attempt to order by name at this point, as that mucks up the order of armour
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Scroll to mounted (if it exists) module group on mount
|
* Scroll to mounted (if it exists) module group on mount
|
||||||
*/
|
*/
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
if (this.groupElem) { // Scroll to currently selected group
|
if (this.activeSlotRef) {
|
||||||
this.node.scrollTop = this.groupElem.offsetTop;
|
this.activeSlotRef.focus();
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Set focus on active or first slot element, if applicable.
|
|
||||||
*/
|
|
||||||
if (this.slotItems[this.activeSlotId]) {
|
|
||||||
this.slotItems[this.activeSlotId].focus();
|
|
||||||
} else if (this.slotItems[this.firstSlotId]) {
|
|
||||||
this.slotItems[this.firstSlotId].focus();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user