mirror of
https://github.com/EDCD/coriolis.git
synced 2025-12-08 22:33:24 +00:00
Compare commits
52 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
08c5d2256a | ||
|
|
ed6ee4341f | ||
|
|
157c1148fb | ||
|
|
507ea9e09e | ||
|
|
af68cba7be | ||
|
|
224fbe0e8f | ||
|
|
07c936897c | ||
|
|
3786fb907c | ||
|
|
752d9f0c68 | ||
|
|
baf59aafcb | ||
|
|
8787303d2a | ||
|
|
118c80af27 | ||
|
|
d103939e45 | ||
|
|
2a6ade3cab | ||
|
|
c8c42689f9 | ||
|
|
da2f472f4d | ||
|
|
3ba878237b | ||
|
|
7577fb53a2 | ||
|
|
f2509f89ee | ||
|
|
9dd1f78330 | ||
|
|
ebb6c2c420 | ||
|
|
9d23dc1763 | ||
|
|
5fa3f8703e | ||
|
|
797885faea | ||
|
|
68e7e9f5b7 | ||
|
|
d1d165ad51 | ||
|
|
0ebb247666 | ||
|
|
aa73bc2809 | ||
|
|
c3fcdb918f | ||
|
|
5e3722bcfd | ||
|
|
6a4fca2eb1 | ||
|
|
6922cfd047 | ||
|
|
d93fc1d2d0 | ||
|
|
48290b2e75 | ||
|
|
b62abef618 | ||
|
|
e0c0778d82 | ||
|
|
b4a82ae7c2 | ||
|
|
fc73102b30 | ||
|
|
b14e7473f3 | ||
|
|
f4cc9fc722 | ||
|
|
8d19ef7783 | ||
|
|
32be186ec5 | ||
|
|
abb0c7f90d | ||
|
|
71ddbdfe75 | ||
|
|
38463ad9a6 | ||
|
|
70375f94c8 | ||
|
|
2d4336116a | ||
|
|
f52880765e | ||
|
|
dbfe68decb | ||
|
|
659f337de9 | ||
|
|
1d36d41da1 | ||
|
|
0f90efaa54 |
@@ -5,7 +5,7 @@ root = true
|
||||
|
||||
# change these settings to your own preference
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
indent_size = 2
|
||||
|
||||
# we recommend you to keep these unchanged
|
||||
end_of_line = lf
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -7,3 +7,4 @@ nginx.pid
|
||||
/bin
|
||||
env
|
||||
*.swp
|
||||
.project
|
||||
|
||||
25705
package-lock.json
generated
25705
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
232
package.json
232
package.json
@@ -1,116 +1,116 @@
|
||||
{
|
||||
"name": "coriolis_shipyard",
|
||||
"version": "2.9.7",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/EDCD/coriolis"
|
||||
},
|
||||
"homepage": "https://coriolis.edcd.io",
|
||||
"bugs": "https://github.com/EDCD/coriolis/issues",
|
||||
"private": true,
|
||||
"engine": "node >= 4.8.1",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"prepublish": "rollup -c && uglifyjs d3.js -c -m -o d3.min.js",
|
||||
"extract-translations": "grep -hroE \"(translate\\('[^']+'\\))|(tip.bind\\(null, '[^']+')\" src/* | grep -oE \"'[^']+'\" | grep -oE \"[^']+\" | sort -u -f",
|
||||
"clean": "rimraf build",
|
||||
"start": "node devServer.js",
|
||||
"lint": "eslint --ext .js,.jsx src",
|
||||
"test": "jest",
|
||||
"prod-serve": "nginx -p $(pwd) -c nginx.conf",
|
||||
"prod-stop": "kill -QUIT $(cat nginx.pid)",
|
||||
"build": "npm run clean && cross-env NODE_ENV=production webpack -p --config webpack.config.prod.js",
|
||||
"rsync": "rsync -ae \"ssh -i $CORIOLIS_PEM\" --delete build/ $CORIOLIS_USER@$CORIOLIS_HOST:~/wwws",
|
||||
"deploy": "npm run lint && npm test && npm run build && npm run rsync"
|
||||
},
|
||||
"jest": {
|
||||
"transform": {
|
||||
".*": "<rootDir>/node_modules/babel-jest"
|
||||
},
|
||||
"testRegex": "(/__tests__/test-.*|\\.(test|spec))\\.js$",
|
||||
"moduleFileExtensions": [
|
||||
"js",
|
||||
"json",
|
||||
"jsx"
|
||||
],
|
||||
"automock": true,
|
||||
"bail": false,
|
||||
"unmockedModulePathPatterns": [
|
||||
"<rootDir>/node_modules/lodash",
|
||||
"<rootDir>/node_modules/react",
|
||||
"<rootDir>/node_modules/react-dom",
|
||||
"<rootDir>/node_modules/react-transition-group",
|
||||
"<rootDir>/node_modules/react-testutils-additions",
|
||||
"<rootDir>/node_modules/fbjs",
|
||||
"<rootDir>/node_modules/fbemitter",
|
||||
"<rootDir>/node_modules/classnames",
|
||||
"<rootDir>/node_modules/d3",
|
||||
"<rootDir>/node_modules/lz-string",
|
||||
"<rootDir>/node_modules/jsen",
|
||||
"coriolis-data",
|
||||
"<rootDir>/src/app/shipyard",
|
||||
"<rootDir>/src/app/i18n",
|
||||
"<rootDir>/src/app/utils",
|
||||
"<rootDir>/src/schemas",
|
||||
"<rootDir>/__tests__"
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"appcache-webpack-plugin": "^1.3.0",
|
||||
"babel-core": "*",
|
||||
"babel-eslint": "*",
|
||||
"babel-jest": "*",
|
||||
"babel-loader": "*",
|
||||
"babel-preset-env": "*",
|
||||
"babel-preset-react": "*",
|
||||
"babel-preset-stage-0": "*",
|
||||
"create-react-class": "^15.6.2",
|
||||
"css-loader": "^0.28.0",
|
||||
"cross-env": "^5.1.4",
|
||||
"d3-selection": "1",
|
||||
"eslint": "3.19.0",
|
||||
"eslint-plugin-react": "^6.10.3",
|
||||
"expose-loader": "^0.7.3",
|
||||
"express": "^4.15.2",
|
||||
"extract-text-webpack-plugin": "2.1.0",
|
||||
"file-loader": "^0.11.1",
|
||||
"html-webpack-plugin": "^2.28.0",
|
||||
"jest-cli": "^21.2.1",
|
||||
"jsen": "^0.6.4",
|
||||
"json-loader": "^0.5.4",
|
||||
"less": "^2.7.2",
|
||||
"less-loader": "^4.0.3",
|
||||
"react-addons-perf": "^15.4.2",
|
||||
"react-measure": "^1.4.7",
|
||||
"react-testutils-additions": "^15.2.0",
|
||||
"react-transition-group": "^1.1.2",
|
||||
"rimraf": "^2.6.1",
|
||||
"rollup": "0.41",
|
||||
"rollup-plugin-node-resolve": "3",
|
||||
"style-loader": "^0.16.1",
|
||||
"uglify-js": "^2.4.11",
|
||||
"url-loader": "^0.5.8",
|
||||
"webpack": "^2.4.1",
|
||||
"webpack-dev-server": "^2.4.4",
|
||||
"webpack-notifier": "^1.6.0",
|
||||
"webpack-bugsnag-plugins": "^1.1.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"babel-polyfill": "*",
|
||||
"browserify-zlib-next": "^1.0.1",
|
||||
"classnames": "^2.2.5",
|
||||
"coriolis-data": "../coriolis-data",
|
||||
"d3": "4.8.0",
|
||||
"detect-browser": "^1.7.0",
|
||||
"fbemitter": "^2.1.1",
|
||||
"lodash": "^4.17.4",
|
||||
"lz-string": "^1.4.4",
|
||||
"pako": "^1.0.6",
|
||||
"prop-types": "^15.5.8",
|
||||
"react": "^15.5.4",
|
||||
"react-dom": "^15.5.4",
|
||||
"react-number-editor": "Athanasius/react-number-editor.git#miggy",
|
||||
"recharts": "^0.22.3",
|
||||
"superagent": "^3.5.2"
|
||||
}
|
||||
}
|
||||
{
|
||||
"name": "coriolis_shipyard",
|
||||
"version": "2.9.15",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/EDCD/coriolis"
|
||||
},
|
||||
"homepage": "https://coriolis.edcd.io",
|
||||
"bugs": "https://github.com/EDCD/coriolis/issues",
|
||||
"private": true,
|
||||
"engine": "node >= 4.8.1",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"prepublish": "rollup -c && uglifyjs d3.js -c -m -o d3.min.js",
|
||||
"extract-translations": "grep -hroE \"(translate\\('[^']+'\\))|(tip.bind\\(null, '[^']+')\" src/* | grep -oE \"'[^']+'\" | grep -oE \"[^']+\" | sort -u -f",
|
||||
"clean": "rimraf build",
|
||||
"start": "node devServer.js",
|
||||
"lint": "eslint --ext .js,.jsx src",
|
||||
"test": "jest",
|
||||
"prod-serve": "nginx -p $(pwd) -c nginx.conf",
|
||||
"prod-stop": "kill -QUIT $(cat nginx.pid)",
|
||||
"build": "npm run clean && cross-env NODE_ENV=production webpack -p --config webpack.config.prod.js",
|
||||
"rsync": "rsync -ae \"ssh -i $CORIOLIS_PEM\" --delete build/ $CORIOLIS_USER@$CORIOLIS_HOST:~/wwws",
|
||||
"deploy": "npm run lint && npm test && npm run build && npm run rsync"
|
||||
},
|
||||
"jest": {
|
||||
"transform": {
|
||||
".*": "<rootDir>/node_modules/babel-jest"
|
||||
},
|
||||
"testRegex": "(/__tests__/test-.*|\\.(test|spec))\\.js$",
|
||||
"moduleFileExtensions": [
|
||||
"js",
|
||||
"json",
|
||||
"jsx"
|
||||
],
|
||||
"automock": true,
|
||||
"bail": false,
|
||||
"unmockedModulePathPatterns": [
|
||||
"<rootDir>/node_modules/lodash",
|
||||
"<rootDir>/node_modules/react",
|
||||
"<rootDir>/node_modules/react-dom",
|
||||
"<rootDir>/node_modules/react-transition-group",
|
||||
"<rootDir>/node_modules/react-testutils-additions",
|
||||
"<rootDir>/node_modules/fbjs",
|
||||
"<rootDir>/node_modules/fbemitter",
|
||||
"<rootDir>/node_modules/classnames",
|
||||
"<rootDir>/node_modules/d3",
|
||||
"<rootDir>/node_modules/lz-string",
|
||||
"<rootDir>/node_modules/jsen",
|
||||
"coriolis-data",
|
||||
"<rootDir>/src/app/shipyard",
|
||||
"<rootDir>/src/app/i18n",
|
||||
"<rootDir>/src/app/utils",
|
||||
"<rootDir>/src/schemas",
|
||||
"<rootDir>/__tests__"
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"appcache-webpack-plugin": "^1.3.0",
|
||||
"babel-core": "*",
|
||||
"babel-eslint": "*",
|
||||
"babel-jest": "*",
|
||||
"babel-loader": "*",
|
||||
"babel-preset-env": "*",
|
||||
"babel-preset-react": "*",
|
||||
"babel-preset-stage-0": "*",
|
||||
"create-react-class": "^15.6.2",
|
||||
"css-loader": "^0.28.0",
|
||||
"cross-env": "^5.1.4",
|
||||
"d3-selection": "1",
|
||||
"eslint": "3.19.0",
|
||||
"eslint-plugin-react": "^6.10.3",
|
||||
"expose-loader": "^0.7.3",
|
||||
"express": "^4.15.2",
|
||||
"extract-text-webpack-plugin": "2.1.0",
|
||||
"file-loader": "^0.11.1",
|
||||
"html-webpack-plugin": "^2.28.0",
|
||||
"jest-cli": "^21.2.1",
|
||||
"jsen": "^0.6.4",
|
||||
"json-loader": "^0.5.4",
|
||||
"less": "^2.7.2",
|
||||
"less-loader": "^4.0.3",
|
||||
"react-addons-perf": "^15.4.2",
|
||||
"react-measure": "^1.4.7",
|
||||
"react-testutils-additions": "^15.2.0",
|
||||
"react-transition-group": "^1.1.2",
|
||||
"rimraf": "^2.6.1",
|
||||
"rollup": "0.41",
|
||||
"rollup-plugin-node-resolve": "3",
|
||||
"style-loader": "^0.16.1",
|
||||
"uglify-js": "^2.4.11",
|
||||
"url-loader": "^0.5.8",
|
||||
"webpack": "^2.4.1",
|
||||
"webpack-dev-server": "^2.4.4",
|
||||
"webpack-notifier": "^1.6.0",
|
||||
"webpack-bugsnag-plugins": "^1.1.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"babel-polyfill": "*",
|
||||
"browserify-zlib-next": "^1.0.1",
|
||||
"classnames": "^2.2.5",
|
||||
"coriolis-data": "../coriolis-data",
|
||||
"d3": "4.8.0",
|
||||
"detect-browser": "^1.7.0",
|
||||
"fbemitter": "^2.1.1",
|
||||
"lodash": "^4.17.4",
|
||||
"lz-string": "^1.4.4",
|
||||
"pako": "^1.0.6",
|
||||
"prop-types": "^15.5.8",
|
||||
"react": "^15.5.4",
|
||||
"react-dom": "^15.5.4",
|
||||
"react-number-editor": "Athanasius/react-number-editor.git#miggy",
|
||||
"recharts": "^0.22.3",
|
||||
"superagent": "^3.5.2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -355,6 +355,8 @@ export default class Coriolis extends React.Component {
|
||||
<footer>
|
||||
<div className="right cap">
|
||||
<a href="https://github.com/EDCD/coriolis" target="_blank" title="Coriolis Github Project">{window.CORIOLIS_VERSION} - {window.CORIOLIS_DATE}</a>
|
||||
<br/>
|
||||
<a href={"https://github.com/EDCD/coriolis/compare/edcd:develop@{" + window.CORIOLIS_DATE + "}...edcd:develop"} target="_blank" title={"Coriolis Commits since" + window.CORIOLIS_DATE}>Commits since last release ({window.CORIOLIS_DATE})</a>
|
||||
</div>
|
||||
</footer>
|
||||
</div>;
|
||||
|
||||
@@ -44,6 +44,7 @@ const GRPCAT = {
|
||||
'rg': 'projectiles',
|
||||
'mr': 'ordnance',
|
||||
'axmr': 'experimental',
|
||||
'rcpl': 'experimental',
|
||||
'tp': 'ordnance',
|
||||
'nl': 'ordnance',
|
||||
'sc': 'scanners',
|
||||
@@ -56,7 +57,11 @@ const GRPCAT = {
|
||||
'ch': 'defence',
|
||||
'po': 'defence',
|
||||
'ec': 'defence',
|
||||
'sfn': 'defence'
|
||||
'sfn': 'defence',
|
||||
// Standard
|
||||
'gpp': 'guardian',
|
||||
'gpc': 'guardian',
|
||||
'ggc': 'guardian'
|
||||
};
|
||||
// Order here is the order in which items will be shown in the modules menu
|
||||
const CATEGORIES = {
|
||||
@@ -82,7 +87,10 @@ const CATEGORIES = {
|
||||
'defence': ['ch', 'po', 'ec'],
|
||||
'scanners': ['sc', 'ss', 'cs', 'kw', 'ws'], // Overloaded with internal scanners
|
||||
// Experimental
|
||||
'experimental': ['axmc', 'axmr', 'rfl', 'xs', 'sfn']
|
||||
'experimental': ['axmc', 'axmr', 'rfl', 'xs', 'sfn', 'rcpl'],
|
||||
|
||||
// Guardian
|
||||
'guardian': ['gpp', 'gpc', 'ggc']
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -96,7 +104,11 @@ export default class AvailableModulesMenu extends TranslatedComponent {
|
||||
diffDetails: PropTypes.func,
|
||||
m: PropTypes.object,
|
||||
shipMass: PropTypes.number,
|
||||
warning: PropTypes.func
|
||||
warning: PropTypes.func,
|
||||
firstSlotId: PropTypes.string,
|
||||
lastSlotId: PropTypes.string,
|
||||
activeSlotId: PropTypes.string,
|
||||
slotDiv: PropTypes.object
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
@@ -112,6 +124,7 @@ export default class AvailableModulesMenu extends TranslatedComponent {
|
||||
super(props);
|
||||
this._hideDiff = this._hideDiff.bind(this);
|
||||
this.state = this._initState(props, context);
|
||||
this.slotItems = [];// Array to hold <li> refs.
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -122,8 +135,9 @@ export default class AvailableModulesMenu extends TranslatedComponent {
|
||||
*/
|
||||
_initState(props, context) {
|
||||
let translate = context.language.translate;
|
||||
let { m, warning, shipMass, onSelect, modules } = props;
|
||||
let { m, warning, shipMass, onSelect, modules, firstSlotId, lastSlotId } = props;
|
||||
let list, currentGroup;
|
||||
|
||||
let buildGroup = this._buildGroup.bind(
|
||||
this,
|
||||
translate,
|
||||
@@ -134,15 +148,18 @@ export default class AvailableModulesMenu extends TranslatedComponent {
|
||||
this._hideDiff(event);
|
||||
onSelect(m);
|
||||
}
|
||||
);
|
||||
|
||||
);
|
||||
|
||||
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) {
|
||||
list.push(<div className='empty-c upp' key='empty' onClick={onSelect.bind(null, null)} >{translate('empty')}</div>);
|
||||
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>);
|
||||
}
|
||||
|
||||
// Need to regroup the modules by our own categorisation
|
||||
@@ -192,37 +209,38 @@ export default class AvailableModulesMenu extends TranslatedComponent {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return { list, currentGroup };
|
||||
let trackingFocus = false;
|
||||
return { list, currentGroup, trackingFocus};
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate React Components for Module Group
|
||||
* @param {Function} translate Translate function
|
||||
* @param {Objecy} mountedModule Mounted Module
|
||||
* @param {Funciton} warningFunc Warning function
|
||||
* @param {Object} mountedModule Mounted Module
|
||||
* @param {Function} warningFunc Warning function
|
||||
* @param {number} mass Mass
|
||||
* @param {function} onSelect Select/Mount callback
|
||||
* @param {string} grp Group name
|
||||
* @param {Array} modules Available modules
|
||||
* @return {React.Component} Available Module Group contents
|
||||
*/
|
||||
_buildGroup(translate, mountedModule, warningFunc, mass, onSelect, grp, modules) {
|
||||
let prevClass = null, prevRating = null;
|
||||
_buildGroup(translate, mountedModule, warningFunc, mass, onSelect, grp, modules, firstSlotId, lastSlotId) {
|
||||
let prevClass = null, prevRating = null, prevName;
|
||||
let elems = [];
|
||||
|
||||
|
||||
const sortedModules = modules.sort(this._moduleOrder);
|
||||
|
||||
|
||||
|
||||
// Calculate the number of items per class. Used so we don't have long lists with only a few items in each row
|
||||
const tmp = sortedModules.map((v, i) => v['class']).reduce((count, cls) => { count[cls] = ++count[cls] || 1; return count; }, {});
|
||||
const itemsPerClass = Math.max.apply(null, Object.keys(tmp).map(key => tmp[key]));
|
||||
|
||||
let itemsOnThisRow = 0;
|
||||
|
||||
for (let i = 0; i < sortedModules.length; i++) {
|
||||
let m = sortedModules[i];
|
||||
let mount = null;
|
||||
let disabled = false;
|
||||
prevName = m.name
|
||||
if (ModuleUtils.isShieldGenerator(m.grp)) {
|
||||
// Shield generators care about maximum hull mass
|
||||
disabled = mass > m.maxmass;
|
||||
@@ -238,9 +256,21 @@ export default class AvailableModulesMenu extends TranslatedComponent {
|
||||
});
|
||||
let eventHandlers;
|
||||
|
||||
if (disabled || active) {
|
||||
eventHandlers = {};
|
||||
if (disabled) {
|
||||
eventHandlers = {
|
||||
onKeyDown: this._keyDown.bind(this, null),
|
||||
onKeyUp: this._keyUp.bind(this, null)
|
||||
|
||||
};
|
||||
} 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);
|
||||
|
||||
@@ -249,7 +279,9 @@ export default class AvailableModulesMenu extends TranslatedComponent {
|
||||
onTouchStart: this._touchStart.bind(this, showDiff),
|
||||
onTouchEnd: this._touchEnd.bind(this, select),
|
||||
onMouseLeave: this._hideDiff,
|
||||
onClick: select
|
||||
onClick: select,
|
||||
onKeyDown: this._keyDown.bind(this, select),
|
||||
onKeyUp: this._keyUp.bind(this, select)
|
||||
};
|
||||
}
|
||||
|
||||
@@ -258,24 +290,28 @@ export default class AvailableModulesMenu extends TranslatedComponent {
|
||||
case 'G': mount = <MountGimballed className={'lg'}/>; break;
|
||||
case 'T': mount = <MountTurret className={'lg'}/>; break;
|
||||
}
|
||||
|
||||
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} className={classes} {...eventHandlers}>
|
||||
<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++;
|
||||
prevClass = m.class;
|
||||
prevRating = m.rating;
|
||||
prevName = m.name;
|
||||
}
|
||||
|
||||
return <ul key={'modules' + grp} >{elems}</ul>;
|
||||
return <ul key={'modules' + grp}>{elems}</ul>;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -326,6 +362,41 @@ export default class AvailableModulesMenu extends TranslatedComponent {
|
||||
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) {
|
||||
var className = event.currentTarget.attributes['class'].value;
|
||||
if (event.key == 'Enter' && className.indexOf('disabled') < 0 && className.indexOf('active') < 0) {
|
||||
select();
|
||||
return
|
||||
}
|
||||
var 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
|
||||
*
|
||||
*/
|
||||
_keyUp(select,event) {
|
||||
//nothing here yet
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide diff tooltip
|
||||
* @param {SyntheticEvent} event Event
|
||||
@@ -383,6 +454,23 @@ export default class AvailableModulesMenu extends TranslatedComponent {
|
||||
if (this.groupElem) { // Scroll to currently selected group
|
||||
this.node.scrollTop = this.groupElem.offsetTop;
|
||||
}
|
||||
/**
|
||||
* 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();
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
/**
|
||||
* Set focus to slot element ref (if we have one) after modules component unmounts
|
||||
*/
|
||||
if(this.props.slotDiv) {
|
||||
this.props.slotDiv.focus();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -399,6 +487,7 @@ export default class AvailableModulesMenu extends TranslatedComponent {
|
||||
* @return {React.Component} List
|
||||
*/
|
||||
render() {
|
||||
console.log("Tracking focus? " + this.state.trackingFocus);
|
||||
return (
|
||||
<div ref={node => this.node = node}
|
||||
className={cn('select', this.props.className)}
|
||||
|
||||
@@ -97,11 +97,12 @@ export default class HardpointSlot extends Slot {
|
||||
{ showModuleResistances && m.getKineticResistance() ? <div className='l'>{translate('kinres')}: {formats.pct(m.getKineticResistance())}</div> : null }
|
||||
{ showModuleResistances && m.getThermalResistance() ? <div className='l'>{translate('thermres')}: {formats.pct(m.getThermalResistance())}</div> : null }
|
||||
{ m.getIntegrity() ? <div className='l'>{translate('integrity')}: {formats.int(m.getIntegrity())}</div> : null }
|
||||
{ m && validMods.length > 0 ? <div className='r' ><button onClick={this._toggleModifications.bind(this)} onContextMenu={stopCtxPropagation} onMouseOver={termtip.bind(null, 'modifications')} onMouseOut={tooltip.bind(null, null)}><ListModifications /></button></div> : null }
|
||||
{ m && validMods.length > 0 ? <div className='r' tabIndex="0" ref={ modButton => this.modButton = modButton }><button tabIndex="-1" onClick={this._toggleModifications.bind(this)} onContextMenu={stopCtxPropagation} onMouseOver={termtip.bind(null, 'modifications')} onMouseOut={tooltip.bind(null, null)}><ListModifications /></button></div> : null }
|
||||
</div>
|
||||
</div>;
|
||||
} else {
|
||||
return <div className={'empty'}>{translate('empty')}</div>;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -505,6 +505,9 @@ export default class Header extends TranslatedComponent {
|
||||
return (
|
||||
<header>
|
||||
{this.props.appCacheUpdate && <div id="app-update" onClick={() => window.location.reload() }>{translate('PHRASE_UPDATE_RDY')}</div>}
|
||||
{this.props.appCache ? <a className={"view-changes"} href={"https://github.com/EDCD/coriolis/compare/edcd:develop@{" + window.CORIOLIS_DATE + "}...edcd:develop"} target="_blank">
|
||||
{"View Release Changes"}
|
||||
</a> : null}
|
||||
<Link className='l' href='/' style={{ marginRight: '1em' }} title='Home'><CoriolisLogo className='icon xl' /></Link>
|
||||
|
||||
<div className='l menu'>
|
||||
|
||||
@@ -21,6 +21,8 @@ export default class InternalSlot extends Slot {
|
||||
* @param {Object} u Localized Units Map
|
||||
* @return {React.Component} Slot contents
|
||||
*/
|
||||
|
||||
|
||||
_getSlotDetails(m, enabled, translate, formats, u) {
|
||||
if (m) {
|
||||
let classRating = m.class + m.rating;
|
||||
@@ -70,6 +72,7 @@ export default class InternalSlot extends Slot {
|
||||
{ m.getRange() ? <div className={'l'}>{translate('range')} {formats.f2(m.getRange())}{u.km}</div> : null }
|
||||
{ m.getRangeT() ? <div className={'l'}>{translate('ranget')} {formats.f1(m.getRangeT())}{u.s}</div> : null }
|
||||
{ m.getTime() ? <div className={'l'}>{translate('time')}: {formats.time(m.getTime())}</div> : null }
|
||||
{ m.getHackTime() ? <div className={'l'}>{translate('hacktime')}: {formats.time(m.getHackTime())}</div> : null }
|
||||
{ m.maximum ? <div className={'l'}>{translate('max')}: {(m.maximum)}</div> : null }
|
||||
{ m.rangeLS ? <div className={'l'}>{translate('range')}: {m.rangeLS}{u.Ls}</div> : null }
|
||||
{ m.rangeLS === null ? <div className={'l'}>∞{u.Ls}</div> : null }
|
||||
@@ -84,7 +87,7 @@ export default class InternalSlot extends Slot {
|
||||
{ m.getHullReinforcement() ? <div className='l'>{translate('armour')}: {formats.int(m.getHullReinforcement() + ship.baseArmour * m.getModValue('hullboost') / 10000)}</div> : null }
|
||||
{ m.getProtection() ? <div className='l'>{translate('protection')}: {formats.rPct(m.getProtection())}</div> : null }
|
||||
{ m.getIntegrity() ? <div className='l'>{translate('integrity')}: {formats.int(m.getIntegrity())}</div> : null }
|
||||
{ m && validMods.length > 0 ? <div className='r' ><button onClick={this._toggleModifications.bind(this)} onContextMenu={stopCtxPropagation} onMouseOver={termtip.bind(null, 'modifications')} onMouseOut={tooltip.bind(null, null)}><ListModifications /></button></div> : null }
|
||||
{ m && validMods.length > 0 ? <div className='r' tabIndex="0" ref={ modButton => this.modButton = modButton }><button tabIndex="-1" onClick={this._toggleModifications.bind(this)} onContextMenu={stopCtxPropagation} onMouseOver={termtip.bind(null, 'modifications')} onMouseOut={tooltip.bind(null, null)}><ListModifications /></button></div> : null }
|
||||
</div>
|
||||
</div>;
|
||||
} else {
|
||||
|
||||
@@ -14,7 +14,10 @@ export default class Modification extends TranslatedComponent {
|
||||
m: PropTypes.object.isRequired,
|
||||
name: PropTypes.string.isRequired,
|
||||
value: PropTypes.number.isRequired,
|
||||
onChange: PropTypes.func.isRequired
|
||||
onChange: PropTypes.func.isRequired,
|
||||
onKeyDown: PropTypes.func.isRequired,
|
||||
modItems: PropTypes.array.isRequired,
|
||||
handleModChange: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -36,7 +39,6 @@ export default class Modification extends TranslatedComponent {
|
||||
*/
|
||||
_updateValue(value) {
|
||||
const name = this.props.name;
|
||||
|
||||
let scaledValue = Math.round(Number(value) * 100);
|
||||
// Limit to +1000% / -99.99%
|
||||
if (scaledValue > 100000) {
|
||||
@@ -57,9 +59,15 @@ export default class Modification extends TranslatedComponent {
|
||||
|
||||
/**
|
||||
* Triggered when an update to slider value is finished i.e. when losing focus
|
||||
*
|
||||
* pnellesen (24/05/2018): added value check below - this should prevent experimental effects from being recalculated
|
||||
* with each onBlur event, even when no change has actually been made to the field.
|
||||
*/
|
||||
_updateFinished() {
|
||||
this.props.onChange();
|
||||
if (this.props.value != this.state.value) {
|
||||
this.props.handleModChange(true);
|
||||
this.props.onChange();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -86,9 +94,9 @@ export default class Modification extends TranslatedComponent {
|
||||
}
|
||||
|
||||
return (
|
||||
<div onBlur={this._updateFinished.bind(this)} className={'cb'} key={name}>
|
||||
<div onBlur={this._updateFinished.bind(this)} className={'cb'} key={name} ref={ modItem => this.props.modItems[name] = modItem }>
|
||||
<div className={'cb'}>{translate(name, m.grp)}{symbol}</div>
|
||||
<NumberEditor className={'cb'} style={{ width: '90%', textAlign: 'center' }} step={0.01} stepModifier={1} decimals={2} value={this.state.value} onValueChange={this._updateValue.bind(this)} />
|
||||
<NumberEditor className={'cb'} style={{ width: '90%', textAlign: 'center' }} step={0.01} stepModifier={1} decimals={2} value={this.state.value} onValueChange={this._updateValue.bind(this)} onKeyDown={ this.props.onKeyDown } />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -6,7 +6,14 @@ import { isEmpty, stopCtxPropagation } from '../utils/UtilityFunctions';
|
||||
import cn from 'classnames';
|
||||
import { Modifications } from 'coriolis-data/dist';
|
||||
import Modification from './Modification';
|
||||
import { getBlueprint, blueprintTooltip, setPercent, setRandom } from '../utils/BlueprintFunctions';
|
||||
import {
|
||||
getBlueprint,
|
||||
blueprintTooltip,
|
||||
setPercent,
|
||||
getPercent,
|
||||
setRandom,
|
||||
specialToolTip
|
||||
} from '../utils/BlueprintFunctions'
|
||||
|
||||
/**
|
||||
* Modifications menu
|
||||
@@ -17,7 +24,8 @@ export default class ModificationsMenu extends TranslatedComponent {
|
||||
ship: PropTypes.object.isRequired,
|
||||
m: PropTypes.object.isRequired,
|
||||
marker: PropTypes.string.isRequired,
|
||||
onChange: PropTypes.func.isRequired
|
||||
onChange: PropTypes.func.isRequired,
|
||||
modButton:PropTypes.object
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -36,6 +44,16 @@ export default class ModificationsMenu extends TranslatedComponent {
|
||||
this._rollWorst = this._rollWorst.bind(this);
|
||||
this._reset = this._reset.bind(this);
|
||||
|
||||
this._keyDown = this._keyDown.bind(this);
|
||||
|
||||
this.modItems = [];// Array to hold various element refs (<li>, <div>, <ul>, etc.)
|
||||
this.firstModId = null;
|
||||
this.firstBPLabel = null;// First item in mod menu
|
||||
this.lastModId = null;
|
||||
this.lastNeId = null;//Last number editor id. Used to set focus to last number editor when shift-tab pressed on first element in mod menu.
|
||||
this.modValDidChange = false; //used to determine if component update was caused by change in modification value.
|
||||
this._handleModChange = this._handleModChange.bind(this);
|
||||
|
||||
this.state = {
|
||||
blueprintMenuOpened: !(props.m.blueprint && props.m.blueprint.name),
|
||||
specialMenuOpened: false
|
||||
@@ -52,7 +70,6 @@ export default class ModificationsMenu extends TranslatedComponent {
|
||||
const { m } = props;
|
||||
const { language, tooltip, termtip } = context;
|
||||
const translate = language.translate;
|
||||
|
||||
const blueprints = [];
|
||||
for (const blueprintName in Modifications.modules[m.grp].blueprints) {
|
||||
const blueprint = getBlueprint(blueprintName, m);
|
||||
@@ -66,9 +83,14 @@ export default class ModificationsMenu extends TranslatedComponent {
|
||||
const close = this._blueprintSelected.bind(this, blueprintName, grade);
|
||||
const key = blueprintName + ':' + grade;
|
||||
const tooltipContent = blueprintTooltip(translate, blueprint.grades[grade], Modifications.modules[m.grp].blueprints[blueprintName].grades[grade].engineers, m.grp);
|
||||
blueprintGrades.unshift(<li key={key} className={classes} style={{ width: '2em' }} onMouseOver={termtip.bind(null, tooltipContent)} onMouseOut={tooltip.bind(null, null)} onClick={close}>{grade}</li>);
|
||||
|
||||
|
||||
blueprintGrades.unshift(<li key={key} tabIndex="0" data-id={key} className={classes} style={{ width: '2em' }} onMouseOver={termtip.bind(null, tooltipContent)} onMouseOut={tooltip.bind(null, null)} onClick={close} onKeyDown={this._keyDown} ref={modItem => this.modItems[key] = modItem}>{grade}</li>);
|
||||
}
|
||||
if (blueprintGrades) {
|
||||
const thisLen = blueprintGrades.length;
|
||||
if (this.firstModId == null) this.firstModId = blueprintGrades[0].key;
|
||||
this.lastModId = blueprintGrades[thisLen-1].key;
|
||||
blueprints.push(<div key={blueprint.name} className={'select-group cap'}>{translate(blueprint.name)}</div>);
|
||||
blueprints.push(<ul key={blueprintName}>{blueprintGrades}</ul>);
|
||||
}
|
||||
@@ -76,6 +98,68 @@ export default class ModificationsMenu extends TranslatedComponent {
|
||||
return blueprints;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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(event) {
|
||||
var className = null;
|
||||
var elemId = null;
|
||||
if (event.currentTarget.attributes['class']) className = event.currentTarget.attributes['class'].value;
|
||||
if (event.currentTarget.attributes['data-id']) elemId = event.currentTarget.attributes['data-id'].value;
|
||||
|
||||
if (event.key == 'Enter' && className.indexOf('disabled') < 0 && className.indexOf('active') < 0) {
|
||||
event.stopPropagation();
|
||||
if (elemId != null) {
|
||||
this.modItems[elemId].click();
|
||||
} else {
|
||||
|
||||
event.currentTarget.click();
|
||||
}
|
||||
return
|
||||
}
|
||||
if (event.key == 'Tab') {
|
||||
//Shift-Tab
|
||||
if(event.shiftKey) {
|
||||
if (elemId == this.firstModId && elemId != null) {
|
||||
// Initial modification menu
|
||||
event.preventDefault();
|
||||
this.modItems[this.lastModId].focus();
|
||||
return;
|
||||
} else if (event.currentTarget.className.indexOf("button-inline-menu") >= 0 && event.currentTarget.previousElementSibling == null && this.lastNeId != null && this.modItems[this.lastNeId] != null) {
|
||||
// shift-tab on first element in modifications menu. set focus to last number editor field if open
|
||||
event.preventDefault();
|
||||
this.modItems[this.lastNeId].lastChild.focus();
|
||||
return;
|
||||
} else if (event.currentTarget.className.indexOf("button-inline-menu") >= 0 && event.currentTarget.previousElementSibling == null) {
|
||||
// shift-tab on button-inline-menu with no number editor
|
||||
event.preventDefault();
|
||||
event.currentTarget.parentElement.lastElementChild.focus();
|
||||
}
|
||||
} else {
|
||||
if (elemId == this.lastModId && elemId != null) {
|
||||
// Initial modification menu
|
||||
event.preventDefault();
|
||||
this.modItems[this.firstModId].focus();
|
||||
return;
|
||||
} else if (event.currentTarget.className.indexOf("button-inline-menu") >= 0 && event.currentTarget.nextSibling == null && event.currentTarget.nodeName != "TD") {
|
||||
// Experimental menu
|
||||
event.preventDefault();
|
||||
event.currentTarget.parentElement.firstElementChild.focus();
|
||||
return;
|
||||
} else if (event.currentTarget.className == 'cb' && event.currentTarget.parentElement.nextSibling == null) {
|
||||
event.preventDefault();
|
||||
this.modItems[this.firstBPLabel].focus();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Render the specials
|
||||
* @param {Object} props React component properties
|
||||
@@ -83,23 +167,40 @@ export default class ModificationsMenu extends TranslatedComponent {
|
||||
* @return {Object} list: Array of React Components
|
||||
*/
|
||||
_renderSpecials(props, context) {
|
||||
|
||||
const { m } = props;
|
||||
const { language, tooltip, termtip } = context;
|
||||
const translate = language.translate;
|
||||
|
||||
const specials = [];
|
||||
const specialsId = m.missile && Modifications.modules[m.grp]['specials_' + m.missile] ? 'specials_' + m.missile : 'specials';
|
||||
if (Modifications.modules[m.grp][specialsId] && Modifications.modules[m.grp][specialsId].length > 0) {
|
||||
const close = this._specialSelected.bind(this, null);
|
||||
specials.push(<div style={{ cursor: 'pointer', fontWeight: 'bold' }} className={ 'button-inline-menu warning' } key={ 'none' } onClick={ close }>{translate('PHRASE_NO_SPECIAL')}</div>);
|
||||
specials.push(<div tabIndex="0" style={{ cursor: 'pointer', fontWeight: 'bold' }} className={ 'button-inline-menu warning' } key={ 'none' } data-id={ 'none' } onClick={ close } onKeyDown={this._keyDown} ref={modItem => this.modItems['none'] = modItem}>{translate('PHRASE_NO_SPECIAL')}</div>);
|
||||
for (const specialName of Modifications.modules[m.grp][specialsId]) {
|
||||
if (Modifications.specials[specialName].name.search('Legacy') >= 0) {
|
||||
continue;
|
||||
}
|
||||
const classes = cn('button-inline-menu', {
|
||||
active: m.blueprint && m.blueprint.special && m.blueprint.special.edname == specialName
|
||||
});
|
||||
const close = this._specialSelected.bind(this, specialName);
|
||||
specials.push(<div style={{ cursor: 'pointer' }} className={classes} key={ specialName } onClick={ close }>{translate(Modifications.specials[specialName].name)}</div>);
|
||||
if (m.blueprint && m.blueprint.name) {
|
||||
let tmp = {};
|
||||
if (m.blueprint.special) {
|
||||
tmp = m.blueprint.special;
|
||||
} else {
|
||||
tmp = undefined;
|
||||
}
|
||||
m.blueprint.special = Modifications.specials[specialName];
|
||||
let specialTt = specialToolTip(translate, m.blueprint.grades[m.blueprint.grade], m.grp, m, specialName);
|
||||
m.blueprint.special = tmp;
|
||||
specials.push(<div tabIndex="0" style={{ cursor: 'pointer' }} className={classes} key={ specialName } data-id={ specialName } onMouseOver={termtip.bind(null, specialTt)} onMouseOut={tooltip.bind(null, null)} onClick={ close } onKeyDown={this._keyDown} ref={modItem => this.modItems[specialName] = modItem}>{translate(Modifications.specials[specialName].name)}</div>);
|
||||
} else {
|
||||
specials.push(<div tabIndex="0" style={{ cursor: 'pointer' }} className={classes} key={ specialName } data-id={ specialName }onClick={ close } onKeyDown={this._keyDown} ref={modItem => this.modItems[specialName] = modItem}>{translate(Modifications.specials[specialName].name)}</div>);
|
||||
}
|
||||
}
|
||||
}
|
||||
console.log("_renderSpecials. specials: %O", specials);
|
||||
return specials;
|
||||
}
|
||||
|
||||
@@ -114,9 +215,11 @@ export default class ModificationsMenu extends TranslatedComponent {
|
||||
for (const modName of Modifications.modules[m.grp].modifications) {
|
||||
if (!Modifications.modifications[modName].hidden) {
|
||||
const key = modName + (m.getModValue(modName) / 100 || 0);
|
||||
modifications.push(<Modification key={ key } ship={ ship } m={ m } name={ modName } value={ m.getModValue(modName) / 100 || 0 } onChange={ onChange }/>);
|
||||
this.lastNeId = modName;
|
||||
modifications.push(<Modification key={ key } ship={ ship } m={ m } name={ modName } value={ m.getModValue(modName) / 100 || 0 } onChange={ onChange } onKeyDown={ this._keyDown } modItems={ this.modItems } handleModChange = {this._handleModChange} />);
|
||||
}
|
||||
}
|
||||
console.log("_renderModifications. modItems: %O", this.modItems);
|
||||
return modifications;
|
||||
}
|
||||
|
||||
@@ -177,6 +280,10 @@ export default class ModificationsMenu extends TranslatedComponent {
|
||||
_rollFifty() {
|
||||
const { m, ship } = this.props;
|
||||
setPercent(ship, m, 50);
|
||||
|
||||
// this will change the values in the modifications. Set modDidChange to true to prevent focus change when component updates
|
||||
this._handleModChange(true);
|
||||
|
||||
this.props.onChange();
|
||||
}
|
||||
|
||||
@@ -186,6 +293,10 @@ export default class ModificationsMenu extends TranslatedComponent {
|
||||
_rollRandom() {
|
||||
const { m, ship } = this.props;
|
||||
setRandom(ship, m);
|
||||
|
||||
// this will change the values in the modifications. Set modDidChange to true to prevent focus change when component updates
|
||||
this._handleModChange(true);
|
||||
|
||||
this.props.onChange();
|
||||
}
|
||||
|
||||
@@ -195,6 +306,10 @@ export default class ModificationsMenu extends TranslatedComponent {
|
||||
_rollBest() {
|
||||
const { m, ship } = this.props;
|
||||
setPercent(ship, m, 100);
|
||||
|
||||
// this will change the values in the modifications. Set modDidChange to true to prevent focus change when component updates
|
||||
this._handleModChange(true);
|
||||
|
||||
this.props.onChange();
|
||||
}
|
||||
|
||||
@@ -204,7 +319,13 @@ export default class ModificationsMenu extends TranslatedComponent {
|
||||
_rollWorst() {
|
||||
const { m, ship } = this.props;
|
||||
setPercent(ship, m, 0);
|
||||
|
||||
// this will change the values in the modifications. Set modDidChange to true to prevent focus change when component updates
|
||||
this._handleModChange(true);
|
||||
|
||||
this.props.onChange();
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -214,10 +335,56 @@ export default class ModificationsMenu extends TranslatedComponent {
|
||||
const { m, ship } = this.props;
|
||||
ship.clearModifications(m);
|
||||
ship.clearModuleBlueprint(m);
|
||||
|
||||
this.props.onChange();
|
||||
}
|
||||
|
||||
/**
|
||||
* set mod did change boolean
|
||||
*/
|
||||
_handleModChange(b) {
|
||||
this.modValDidChange = b;
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
/**
|
||||
* Set focus on first element in modifications menu
|
||||
* after it first mounts
|
||||
*/
|
||||
let firstEleCn = this.modItems['modMainDiv'].children.length > 0 ? this.modItems['modMainDiv'].children[0].className : null;
|
||||
if (firstEleCn.indexOf('select-group cap') >= 0) {
|
||||
this.modItems['modMainDiv'].children[1].firstElementChild.focus();
|
||||
} else {
|
||||
this.modItems['modMainDiv'].firstElementChild.focus();
|
||||
}
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
/**
|
||||
* Set focus on first element in modifications menu
|
||||
* if component updates, unless update is due to value change
|
||||
* in a modification
|
||||
*/
|
||||
if (!this.modValDidChange) {
|
||||
if (this.modItems['modMainDiv'].children.length > 0) {
|
||||
let firstEleCn = this.modItems['modMainDiv'].children[0].className;
|
||||
if (firstEleCn.indexOf('button-inline-menu') >= 0) {
|
||||
this.modItems['modMainDiv'].firstElementChild.focus();
|
||||
} else if (firstEleCn.indexOf('select-group cap') >= 0) {
|
||||
this.modItems['modMainDiv'].children[1].firstElementChild.focus();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this._handleModChange(false);//Need to reset if component update due to value change
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
if (this.props.modButton) {
|
||||
this.props.modButton.focus();// set focus to the modification menu icon after mod menu is unmounted.
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the list
|
||||
* @return {React.Component} List
|
||||
@@ -239,57 +406,73 @@ export default class ModificationsMenu extends TranslatedComponent {
|
||||
let blueprintLabel;
|
||||
let haveBlueprint = false;
|
||||
let blueprintTt;
|
||||
if (m.blueprint && m.blueprint.name) {
|
||||
let blueprintCv;
|
||||
//TODO: Fix this to actually find the correct blueprint.
|
||||
if (!m.blueprint || !m.blueprint.name || !m.blueprint.fdname || !Modifications.modules[m.grp].blueprints || !Modifications.modules[m.grp].blueprints[m.blueprint.fdname]) {
|
||||
this.props.ship.clearModuleBlueprint(m);
|
||||
this.props.ship.clearModuleSpecial(m);
|
||||
}
|
||||
if (m.blueprint && m.blueprint.name && Modifications.modules[m.grp].blueprints[m.blueprint.fdname].grades[m.blueprint.grade]) {
|
||||
blueprintLabel = translate(m.blueprint.name) + ' ' + translate('grade') + ' ' + m.blueprint.grade;
|
||||
haveBlueprint = true;
|
||||
blueprintTt = blueprintTooltip(translate, m.blueprint.grades[m.blueprint.grade], Modifications.modules[m.grp].blueprints[m.blueprint.fdname].grades[m.blueprint.grade].engineers, m.grp);
|
||||
blueprintCv = getPercent(m);
|
||||
}
|
||||
|
||||
let specialLabel;
|
||||
let specialTt;
|
||||
if (m.blueprint && m.blueprint.special) {
|
||||
specialLabel = m.blueprint.special.name;
|
||||
specialTt = specialToolTip(translate, m.blueprint.grades[m.blueprint.grade], m.grp, m, m.blueprint.special.edname);
|
||||
} else {
|
||||
specialLabel = translate('PHRASE_SELECT_SPECIAL');
|
||||
}
|
||||
|
||||
const specials = this._renderSpecials(this.props, this.context);
|
||||
|
||||
/**
|
||||
* pnellesen - 05/28/2018 - added additional checks for specials.length below to ensure menus
|
||||
* display correctly in cases where there are no specials (ex: AFMUs.)
|
||||
*/
|
||||
const showBlueprintsMenu = blueprintMenuOpened;
|
||||
const showSpecial = haveBlueprint && specials.length && !blueprintMenuOpened;
|
||||
const showSpecialsMenu = specialMenuOpened;
|
||||
const showRolls = haveBlueprint && !blueprintMenuOpened && !specialMenuOpened;
|
||||
const showReset = !blueprintMenuOpened && !specialMenuOpened;
|
||||
const showMods = !blueprintMenuOpened && !specialMenuOpened;
|
||||
|
||||
const showSpecialsMenu = specialMenuOpened && specials.length;
|
||||
const showRolls = haveBlueprint && !blueprintMenuOpened && (!specialMenuOpened || !specials.length);
|
||||
const showReset = !blueprintMenuOpened && (!specialMenuOpened || !specials.length) && haveBlueprint;
|
||||
const showMods = !blueprintMenuOpened && (!specialMenuOpened || !specials.length) && haveBlueprint;
|
||||
if (haveBlueprint) {
|
||||
this.firstBPLabel = blueprintLabel
|
||||
} else {
|
||||
this.firstBPLabel = 'selectBP';
|
||||
}
|
||||
return (
|
||||
<div
|
||||
className={cn('select', this.props.className)}
|
||||
onClick={(e) => e.stopPropagation() }
|
||||
onContextMenu={stopCtxPropagation}
|
||||
ref={modItem => this.modItems['modMainDiv'] = modItem}
|
||||
>
|
||||
{ showBlueprintsMenu | showSpecialsMenu ? '' : haveBlueprint ?
|
||||
<div className={ cn('section-menu button-inline-menu', { selected: blueprintMenuOpened })} style={{ cursor: 'pointer' }} onMouseOver={termtip.bind(null, blueprintTt)} onMouseOut={tooltip.bind(null, null)} onClick={_toggleBlueprintsMenu}>{blueprintLabel}</div> :
|
||||
<div className={ cn('section-menu button-inline-menu', { selected: blueprintMenuOpened })} style={{ cursor: 'pointer' }} onClick={_toggleBlueprintsMenu}>{translate('PHRASE_SELECT_BLUEPRINT')}</div> }
|
||||
<div tabIndex="0" className={ cn('section-menu button-inline-menu', { selected: blueprintMenuOpened })} style={{ cursor: 'pointer' }} onMouseOver={termtip.bind(null, blueprintTt)} onMouseOut={tooltip.bind(null, null)} onClick={_toggleBlueprintsMenu} onKeyDown={ this._keyDown } ref={modItems => this.modItems[this.firstBPLabel] = modItems}>{blueprintLabel}</div> :
|
||||
<div tabIndex="0" className={ cn('section-menu button-inline-menu', { selected: blueprintMenuOpened })} style={{ cursor: 'pointer' }} onClick={_toggleBlueprintsMenu} onKeyDown={ this._keyDown } ref={modItems => this.modItems[this.firstBPLabel] = modItems}>{translate('PHRASE_SELECT_BLUEPRINT')}</div> }
|
||||
{ showBlueprintsMenu ? this._renderBlueprints(this.props, this.context) : null }
|
||||
{ showSpecial & !showSpecialsMenu ? <div className={ cn('section-menu button-inline-menu', { selected: specialMenuOpened })} style={{ cursor: 'pointer' }} onClick={_toggleSpecialsMenu}>{specialLabel}</div> : null }
|
||||
{ showSpecial & !showSpecialsMenu ? <div tabIndex="0" className={ cn('section-menu button-inline-menu', { selected: specialMenuOpened })} style={{ cursor: 'pointer' }} onMouseOver={specialTt ? termtip.bind(null, specialTt) : null} onMouseOut={specialTt ? tooltip.bind(null, null) : null} onClick={_toggleSpecialsMenu} onKeyDown={ this._keyDown }>{specialLabel}</div> : null }
|
||||
{ showSpecialsMenu ? specials : null }
|
||||
{ showRolls || showReset ?
|
||||
{ showReset ? <div tabIndex="0" className={'section-menu button-inline-menu warning'} style={{ cursor: 'pointer' }} onClick={_reset} onKeyDown={ this._keyDown } onMouseOver={termtip.bind(null, 'PHRASE_BLUEPRINT_RESET')} onMouseOut={tooltip.bind(null, null)}> { translate('reset') } </div> : null }
|
||||
{ showRolls ?
|
||||
|
||||
<table style={{ width: '100%', backgroundColor: 'transparent' }}>
|
||||
<tbody>
|
||||
{ showRolls ?
|
||||
<tr>
|
||||
<td> { translate('roll') }: </td>
|
||||
<td style={{ cursor: 'pointer' }} onClick={_rollWorst} onMouseOver={termtip.bind(null, 'PHRASE_BLUEPRINT_WORST')} onMouseOut={tooltip.bind(null, null)}> { translate('0%') } </td>
|
||||
<td style={{ cursor: 'pointer' }} onClick={_rollFifty} onMouseOver={termtip.bind(null, 'PHRASE_BLUEPRINT_FIFTY')} onMouseOut={tooltip.bind(null, null)}> { translate('50%') } </td>
|
||||
<td style={{ cursor: 'pointer' }} onClick={_rollFull} onMouseOver={termtip.bind(null, 'PHRASE_BLUEPRINT_BEST')} onMouseOut={tooltip.bind(null, null)}> { translate('100%') } </td>
|
||||
<td style={{ cursor: 'pointer' }} onClick={_rollRandom} onMouseOver={termtip.bind(null, 'PHRASE_BLUEPRINT_RANDOM')} onMouseOut={tooltip.bind(null, null)}> { translate('random') } </td>
|
||||
</tr> : null }
|
||||
{ showReset ?
|
||||
<tr>
|
||||
<td colSpan={'5'} style={{ cursor: 'pointer' }} onClick={_reset}onMouseOver={termtip.bind(null, 'PHRASE_BLUEPRINT_RESET')} onMouseOut={tooltip.bind(null, null)}> { translate('reset') } </td>
|
||||
<td tabIndex="0" className={ cn('section-menu button-inline-menu', {active: false})}> { translate('roll') }: </td>
|
||||
<td tabIndex="0" className={ cn('section-menu button-inline-menu', { active: blueprintCv === 0 })} style={{ cursor: 'pointer' }} onClick={_rollWorst} onKeyDown={ this._keyDown } onMouseOver={termtip.bind(null, 'PHRASE_BLUEPRINT_WORST')} onMouseOut={tooltip.bind(null, null)}> { translate('0%') } </td>
|
||||
<td tabIndex="0" className={ cn('section-menu button-inline-menu', { active: blueprintCv === 50 })} style={{ cursor: 'pointer' }} onClick={_rollFifty} onKeyDown={ this._keyDown } onMouseOver={termtip.bind(null, 'PHRASE_BLUEPRINT_FIFTY')} onMouseOut={tooltip.bind(null, null)}> { translate('50%') } </td>
|
||||
<td tabIndex="0" className={ cn('section-menu button-inline-menu', { active: blueprintCv === 100 })} style={{ cursor: 'pointer' }} onClick={_rollFull} onKeyDown={ this._keyDown } onMouseOver={termtip.bind(null, 'PHRASE_BLUEPRINT_BEST')} onMouseOut={tooltip.bind(null, null)}> { translate('100%') } </td>
|
||||
<td tabIndex="0" className={ cn('section-menu button-inline-menu', { active: blueprintCv === null || blueprintCv % 50 != 0 })} style={{ cursor: 'pointer' }} onClick={_rollRandom} onKeyDown={ this._keyDown } onMouseOver={termtip.bind(null, 'PHRASE_BLUEPRINT_RANDOM')} onMouseOut={tooltip.bind(null, null)}> { translate('random') } </td>
|
||||
</tr> : null }
|
||||
</tbody>
|
||||
</table> : null }
|
||||
{ showMods ? <hr /> : null }
|
||||
{ showMods ?
|
||||
<span onMouseOver={termtip.bind(null, 'HELP_MODIFICATIONS_MENU')} onMouseOut={tooltip.bind(null, null)} >
|
||||
{ this._renderModifications(this.props) }
|
||||
|
||||
@@ -15,22 +15,30 @@ export default class ShipSummaryTable extends TranslatedComponent {
|
||||
cargo: PropTypes.number.isRequired,
|
||||
fuel: PropTypes.number.isRequired,
|
||||
marker: PropTypes.string.isRequired,
|
||||
pips: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.didContextChange = this.didContextChange.bind(this);
|
||||
this.state = {
|
||||
shieldColour: 'blue'
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the table
|
||||
* @return {React.Component} Summary table
|
||||
*/
|
||||
render() {
|
||||
const { ship, cargo, fuel } = this.props;
|
||||
const { ship, cargo, fuel, pips } = this.props;
|
||||
let { language, tooltip, termtip } = this.context;
|
||||
let translate = language.translate;
|
||||
let u = language.units;
|
||||
let formats = language.formats;
|
||||
let { time, int, round, f1, f2 } = formats;
|
||||
let hide = tooltip.bind(null, null);
|
||||
|
||||
const shieldGenerator = ship.findInternalByGroup('sg');
|
||||
const shieldGenerator = ship.findInternalByGroup('sg') || ship.findInternalByGroup('psg');
|
||||
const sgClassNames = cn({ warning: shieldGenerator && !ship.shield, muted: !shieldGenerator });
|
||||
const sgTooltip = shieldGenerator ? 'TT_SUMMARY_SHIELDS' : 'TT_SUMMARY_SHIELDS_NONFUNCTIONAL';
|
||||
const timeToDrain = Calc.timeToDrainWep(ship, 4);
|
||||
@@ -38,9 +46,19 @@ export default class ShipSummaryTable extends TranslatedComponent {
|
||||
const speedTooltip = canThrust ? 'TT_SUMMARY_SPEED' : 'TT_SUMMARY_SPEED_NONFUNCTIONAL';
|
||||
const canBoost = ship.canBoost(cargo, ship.fuelCapacity);
|
||||
const boostTooltip = canBoost ? 'TT_SUMMARY_BOOST' : canThrust ? 'TT_SUMMARY_BOOST_NONFUNCTIONAL' : 'TT_SUMMARY_SPEED_NONFUNCTIONAL';
|
||||
|
||||
const sgMetrics = Calc.shieldMetrics(ship, pips.sys || 2);
|
||||
const armourMetrics = Calc.armourMetrics(ship);
|
||||
let shieldColour = 'blue';
|
||||
if (shieldGenerator && shieldGenerator.m.grp === 'psg') {
|
||||
shieldColour = 'green';
|
||||
} else if (shieldGenerator && shieldGenerator.m.grp === 'bsg') {
|
||||
shieldColour = 'purple';
|
||||
}
|
||||
this.state = {
|
||||
shieldColour
|
||||
}
|
||||
return <div id='summary'>
|
||||
<table id='summaryTable'>
|
||||
<table className={'summaryTable'}>
|
||||
<thead>
|
||||
<tr className='main'>
|
||||
<th rowSpan={2} className={ cn({ 'bg-warning-disabled': !canThrust }) }>{translate('speed')}</th>
|
||||
@@ -98,6 +116,70 @@ export default class ShipSummaryTable extends TranslatedComponent {
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<table className={'summaryTable'}>
|
||||
<thead className={this.state.shieldColour}>
|
||||
<tr>
|
||||
<th onMouseEnter={termtip.bind(null, 'shield', { cap: 0 })} onMouseLeave={hide} className='lft'>{translate('shield')}</th>
|
||||
<th onMouseEnter={termtip.bind(null, 'explres', { cap: 0 })} onMouseLeave={hide} className='lft'>{translate('explres')}</th>
|
||||
<th onMouseEnter={termtip.bind(null, 'kinres', { cap: 0 })} onMouseLeave={hide} className='lft'>{translate('kinres')}</th>
|
||||
<th onMouseEnter={termtip.bind(null, 'thermres', { cap: 0 })} onMouseLeave={hide} className='lft'>{translate('thermres')}</th>
|
||||
|
||||
<th onMouseEnter={termtip.bind(null, 'TT_SUMMARY_SHIELDS_SCB', { cap: 0 })} onMouseLeave={hide} className='lft'>{`${translate('absolute')} ${translate('HP')}`}</th>
|
||||
<th onMouseEnter={termtip.bind(null, 'TT_SUMMARY_SHIELDS_SCB', { cap: 0 })} onMouseLeave={hide} className='lft'>{`${translate('explosive')} ${translate('HP')}`}</th>
|
||||
<th onMouseEnter={termtip.bind(null, 'TT_SUMMARY_SHIELDS_SCB', { cap: 0 })} onMouseLeave={hide} className='lft'>{`${translate('kinetic')} ${translate('HP')}`}</th>
|
||||
<th onMouseEnter={termtip.bind(null, 'TT_SUMMARY_SHIELDS_SCB', { cap: 0 })} onMouseLeave={hide} className='lft'>{`${translate('thermal')} ${translate('HP')}`}</th>
|
||||
<th onMouseEnter={termtip.bind(null, 'PHRASE_SG_RECOVER', { cap: 0 })} onMouseLeave={hide} className='lft'>{translate('recovery')}</th>
|
||||
<th onMouseEnter={termtip.bind(null, 'PHRASE_SG_RECHARGE', { cap: 0 })} onMouseLeave={hide} className='lft'>{translate('recharge')}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>{translate(shieldGenerator && shieldGenerator.m.grp || 'No Shield')}</td>
|
||||
<td>{int(ship.shieldExplRes * 100) + '%'}</td>
|
||||
<td>{int(ship.shieldKinRes * 100) + '%'}</td>
|
||||
<td>{int(ship.shieldThermRes * 100) + '%'}</td>
|
||||
<td>{int(sgMetrics && sgMetrics.generator ? sgMetrics.total / sgMetrics.absolute.total : 0)}</td>
|
||||
<td>{int(sgMetrics && sgMetrics.generator ? sgMetrics.total / sgMetrics.explosive.total : 0)}</td>
|
||||
<td>{int(sgMetrics && sgMetrics.generator ? sgMetrics.total / sgMetrics.kinetic.total : 0 )}</td>
|
||||
<td>{int(sgMetrics && sgMetrics.generator ? sgMetrics.total / sgMetrics.thermal.total : 0 )}</td>
|
||||
<td>{sgMetrics && sgMetrics.recover ? formats.time(sgMetrics.recover) : 0}</td>
|
||||
<td>{sgMetrics && sgMetrics.recharge ? formats.time(sgMetrics.recharge) : 0}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<thead>
|
||||
<tr>
|
||||
<th onMouseEnter={termtip.bind(null, 'armour', { cap: 0 })} onMouseLeave={hide} className='lft'>{translate('armour')}</th>
|
||||
<th onMouseEnter={termtip.bind(null, 'explres', { cap: 0 })} onMouseLeave={hide} className='lft'>{translate('explres')}</th>
|
||||
<th onMouseEnter={termtip.bind(null, 'kinres', { cap: 0 })} onMouseLeave={hide} className='lft'>{translate('kinres')}</th>
|
||||
<th onMouseEnter={termtip.bind(null, 'thermres', { cap: 0 })} onMouseLeave={hide} className='lft'>{translate('thermres')}</th>
|
||||
|
||||
<th onMouseEnter={termtip.bind(null, 'PHRASE_EFFECTIVE_ARMOUR', { cap: 0 })} onMouseLeave={hide} className='lft'>{`${translate('absolute')} ${translate('HP')}`}</th>
|
||||
<th onMouseEnter={termtip.bind(null, 'PHRASE_EFFECTIVE_ARMOUR', { cap: 0 })} onMouseLeave={hide} className='lft'>{`${translate('explosive')} ${translate('HP')}`}</th>
|
||||
<th onMouseEnter={termtip.bind(null, 'PHRASE_EFFECTIVE_ARMOUR', { cap: 0 })} onMouseLeave={hide} className='lft'>{`${translate('kinetic')} ${translate('HP')}`}</th>
|
||||
<th onMouseEnter={termtip.bind(null, 'PHRASE_EFFECTIVE_ARMOUR', { cap: 0 })} onMouseLeave={hide} className='lft'>{`${translate('thermal')} ${translate('HP')}`}</th>
|
||||
<th onMouseEnter={termtip.bind(null, 'TT_MODULE_ARMOUR', { cap: 0 })} onMouseLeave={hide} className='lft'>{translate('raw module armour')}</th>
|
||||
<th onMouseEnter={termtip.bind(null, 'armour', { cap: 0 })} onMouseLeave={hide} className='lft'>{translate('internal protection')}</th>
|
||||
|
||||
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>{translate(ship && ship.bulkheads && ship.bulkheads.m && ship.bulkheads.m.name || 'No Armour')}</td>
|
||||
<td>{int(ship.hullExplRes * 100) + '%'}</td>
|
||||
<td>{int(ship.hullKinRes * 100) + '%'}</td>
|
||||
<td>{int(ship.hullThermRes * 100) + '%'}</td>
|
||||
<td>{int(armourMetrics.total / armourMetrics.absolute.total)}</td>
|
||||
<td>{int(armourMetrics.total / armourMetrics.explosive.total)}</td>
|
||||
<td>{int(armourMetrics.total / armourMetrics.kinetic.total)}</td>
|
||||
<td>{int(armourMetrics.total / armourMetrics.thermal.total)}</td>
|
||||
<td>{int(armourMetrics.modulearmour)}</td>
|
||||
<td>{int(armourMetrics.moduleprotection * 100) + '%'}</td>
|
||||
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,166 +1,361 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
const MARGIN_LR = 8; // Left/ Right margin
|
||||
|
||||
/**
|
||||
* Horizontal Slider
|
||||
*/
|
||||
export default class Slider extends React.Component {
|
||||
|
||||
static defaultProps = {
|
||||
axis: false,
|
||||
min: 0,
|
||||
max: 1,
|
||||
scale: 1 // SVG render scale
|
||||
};
|
||||
|
||||
static propTypes = {
|
||||
axis: PropTypes.bool,
|
||||
axisUnit: PropTypes.string,
|
||||
max: PropTypes.number,
|
||||
min: PropTypes.number,
|
||||
onChange: PropTypes.func.isRequired,
|
||||
onResize: PropTypes.func,
|
||||
percent: PropTypes.number.isRequired,
|
||||
scale: PropTypes.number
|
||||
};
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param {Object} props React Component properties
|
||||
*/
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this._down = this._down.bind(this);
|
||||
this._move = this._move.bind(this);
|
||||
this._up = this._up.bind(this);
|
||||
this._updatePercent = this._updatePercent.bind(this);
|
||||
this._updateDimensions = this._updateDimensions.bind(this);
|
||||
|
||||
this.state = { width: 0 };
|
||||
}
|
||||
|
||||
/**
|
||||
* On Mouse/Touch down handler
|
||||
* @param {SyntheticEvent} event Event
|
||||
*/
|
||||
_down(event) {
|
||||
let rect = event.currentTarget.getBoundingClientRect();
|
||||
this.left = rect.left;
|
||||
this.width = rect.width;
|
||||
this._move(event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the slider percentage on move
|
||||
* @param {SyntheticEvent} event Event
|
||||
*/
|
||||
_move(event) {
|
||||
if(this.width !== null && this.left != null) {
|
||||
let clientX = event.touches ? event.touches[0].clientX : event.clientX;
|
||||
event.preventDefault();
|
||||
this._updatePercent(clientX - this.left, this.width);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* On Mouse/Touch up handler
|
||||
* @param {Event} event DOM Event
|
||||
*/
|
||||
_up(event) {
|
||||
event.preventDefault();
|
||||
this.left = null;
|
||||
this.width = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the user is still dragging
|
||||
* @param {SyntheticEvent} event Event
|
||||
*/
|
||||
_enter(event) {
|
||||
if(event.buttons !== 1) {
|
||||
this.left = null;
|
||||
this.width = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the slider percentage
|
||||
* @param {number} pos Slider drag position
|
||||
* @param {number} width Slider width
|
||||
* @param {Event} event DOM Event
|
||||
*/
|
||||
_updatePercent(pos, width) {
|
||||
this.props.onChange(Math.min(Math.max(pos / width, 0), 1));
|
||||
}
|
||||
|
||||
/**
|
||||
* Update dimenions from rendered DOM
|
||||
*/
|
||||
_updateDimensions() {
|
||||
this.setState({
|
||||
outerWidth: this.node.getBoundingClientRect().width
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Add listeners when about to mount
|
||||
*/
|
||||
componentWillMount() {
|
||||
if (this.props.onResize) {
|
||||
this.resizeListener = this.props.onResize(this._updateDimensions);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger DOM updates on mount
|
||||
*/
|
||||
componentDidMount() {
|
||||
this._updateDimensions();
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove listeners on unmount
|
||||
*/
|
||||
componentWillUnmount() {
|
||||
if (this.resizeListener) {
|
||||
this.resizeListener.remove();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the slider
|
||||
* @return {React.Component} The slider
|
||||
*/
|
||||
render() {
|
||||
let outerWidth = this.state.outerWidth;
|
||||
let { axis, axisUnit, min, max, scale } = this.props;
|
||||
|
||||
let style = {
|
||||
width: '100%',
|
||||
height: axis ? '2.5em' : '1.5em',
|
||||
boxSizing: 'border-box'
|
||||
};
|
||||
|
||||
if (!outerWidth) {
|
||||
return <svg style={style} ref={node => this.node = node} />;
|
||||
}
|
||||
|
||||
let margin = MARGIN_LR * scale;
|
||||
let width = outerWidth - (margin * 2);
|
||||
let pctPos = width * this.props.percent;
|
||||
|
||||
return <svg onMouseUp={this._up} onMouseEnter={this._enter.bind(this)} onMouseMove={this._move} onTouchEnd={this._up} style={style} ref={node => this.node = node}>
|
||||
<rect className='primary' style={{ opacity: 0.3 }} x={margin} y='0.25em' rx='0.3em' ry='0.3em' width={width} height='0.7em' />
|
||||
<rect className='primary-disabled' x={margin} y='0.45em' rx='0.15em' ry='0.15em' width={pctPos} height='0.3em' />
|
||||
<circle className='primary' r={margin} cy='0.6em' cx={pctPos + margin} />
|
||||
<rect x={margin} width={width} height='100%' fillOpacity='0' style={{ cursor: 'col-resize' }} onMouseDown={this._down} onTouchMove={this._move} onTouchStart={this._down} />
|
||||
{axis && <g style={{ fontSize: '.7em' }}>
|
||||
<text className='primary-disabled' y='3em' x={margin} style={{ textAnchor: 'middle' }}>{min + axisUnit}</text>
|
||||
<text className='primary-disabled' y='3em' x='50%' style={{ textAnchor: 'middle' }}>{(min + max / 2) + axisUnit}</text>
|
||||
<text className='primary-disabled' y='3em' x='100%' style={{ textAnchor: 'end' }}>{max + axisUnit}</text>
|
||||
</g>}
|
||||
</svg>;
|
||||
}
|
||||
}
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
const MARGIN_LR = 8; // Left/ Right margin
|
||||
|
||||
/**
|
||||
* Horizontal Slider
|
||||
*/
|
||||
export default class Slider extends React.Component {
|
||||
|
||||
static defaultProps = {
|
||||
axis: false,
|
||||
min: 0,
|
||||
max: 1,
|
||||
scale: 1 // SVG render scale
|
||||
};
|
||||
|
||||
static propTypes = {
|
||||
axis: PropTypes.bool,
|
||||
axisUnit: PropTypes.string,//units (T, M, etc.)
|
||||
max: PropTypes.number,
|
||||
min: PropTypes.number,
|
||||
onChange: PropTypes.func.isRequired,// function which determins percent value
|
||||
onResize: PropTypes.func,
|
||||
percent: PropTypes.number.isRequired,//value of slider
|
||||
scale: PropTypes.number
|
||||
};
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param {Object} props React Component properties
|
||||
*/
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this._down = this._down.bind(this);
|
||||
this._move = this._move.bind(this);
|
||||
this._up = this._up.bind(this);
|
||||
this._keyup = this._keyup.bind(this);
|
||||
this._keydown = this._keydown.bind(this);
|
||||
this._touchstart = this._touchstart.bind(this);
|
||||
this._touchend = this._touchend.bind(this);
|
||||
|
||||
this._updatePercent = this._updatePercent.bind(this);
|
||||
this._updateDimensions = this._updateDimensions.bind(this);
|
||||
|
||||
this.state = { width: 0 };
|
||||
}
|
||||
|
||||
/**
|
||||
* On Mouse/Touch down handler
|
||||
* @param {SyntheticEvent} event Event
|
||||
*/
|
||||
_down(event) {
|
||||
|
||||
let rect = event.currentTarget.getBoundingClientRect();
|
||||
this.left = rect.left;
|
||||
this.width = rect.width;
|
||||
this._move(event);
|
||||
this.touchStartTimer = setTimeout(() => this.sliderInputBox._setDisplay('block'), 1500);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the slider percentage on move
|
||||
* @param {SyntheticEvent} event Event
|
||||
*/
|
||||
_move(event) {
|
||||
if(this.width !== null && this.left != null) {
|
||||
let clientX = event.touches ? event.touches[0].clientX : event.clientX;
|
||||
event.preventDefault();
|
||||
this._updatePercent(clientX - this.left, this.width);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* On Mouse/Touch up handler
|
||||
* @param {Event} event DOM Event
|
||||
*/
|
||||
_up(event) {
|
||||
this.sliderInputBox.sliderVal.focus();
|
||||
clearTimeout(this.touchStartTimer);
|
||||
event.preventDefault();
|
||||
this.left = null;
|
||||
this.width = null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Key up handler for keyboard.
|
||||
* display the number field then set focus to it
|
||||
* when "Enter" key is pressed
|
||||
* @param {Event} event Keyboard event
|
||||
*/
|
||||
_keyup(event) {
|
||||
switch (event.key) {
|
||||
case 'Enter':
|
||||
event.preventDefault();
|
||||
this.sliderInputBox._setDisplay('block');
|
||||
//this.enterTimer = setTimeout(() => this.sliderInputBox.sliderVal.focus(), 10);
|
||||
return;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
/**
|
||||
* Key down handler
|
||||
* increment slider position by +/- 1 when right/left arrow key is pressed or held
|
||||
* @param {Event} event
|
||||
*/
|
||||
_keydown(event) {
|
||||
|
||||
switch (event.key) {
|
||||
case 'ArrowRight':
|
||||
var newVal = this.props.percent*this.props.max + 1;
|
||||
if (newVal <= this.props.max) this.props.onChange(newVal/this.props.max);
|
||||
return;
|
||||
case 'ArrowLeft':
|
||||
var newVal = this.props.percent*this.props.max - 1;
|
||||
if (newVal >= 0) this.props.onChange(newVal/this.props.max);
|
||||
return;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Touch start handler
|
||||
* @param {Event} event DOM Event
|
||||
*
|
||||
*/
|
||||
_touchstart(event) {
|
||||
this.touchStartTimer = setTimeout(() => this.sliderInputBox._setDisplay('block'), 1500);
|
||||
}
|
||||
|
||||
_touchend(event) {
|
||||
this.sliderInputBox.sliderVal.focus();
|
||||
clearTimeout(this.touchStartTimer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the user is still dragging
|
||||
* @param {SyntheticEvent} event Event
|
||||
*/
|
||||
_enter(event) {
|
||||
if(event.buttons !== 1) {
|
||||
this.left = null;
|
||||
this.width = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the slider percentage
|
||||
* @param {number} pos Slider drag position
|
||||
* @param {number} width Slider width
|
||||
* @param {Event} event DOM Event
|
||||
*/
|
||||
_updatePercent(pos, width) {
|
||||
this.props.onChange(Math.min(Math.max(pos / width, 0), 1));
|
||||
}
|
||||
|
||||
/**
|
||||
* Update dimenions from rendered DOM
|
||||
*/
|
||||
_updateDimensions() {
|
||||
this.setState({
|
||||
outerWidth: this.node.getBoundingClientRect().width
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Add listeners when about to mount
|
||||
*/
|
||||
componentWillMount() {
|
||||
if (this.props.onResize) {
|
||||
this.resizeListener = this.props.onResize(this._updateDimensions);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger DOM updates on mount
|
||||
*/
|
||||
componentDidMount() {
|
||||
this._updateDimensions();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove listeners on unmount
|
||||
*/
|
||||
componentWillUnmount() {
|
||||
if (this.resizeListener) {
|
||||
this.resizeListener.remove();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the slider
|
||||
* @return {React.Component} The slider
|
||||
*/
|
||||
render() {
|
||||
let outerWidth = this.state.outerWidth;
|
||||
let { axis, axisUnit, min, max, scale } = this.props;
|
||||
|
||||
let style = {
|
||||
width: '100%',
|
||||
height: axis ? '2.5em' : '1.5em',
|
||||
boxSizing: 'border-box'
|
||||
};
|
||||
|
||||
if (!outerWidth) {
|
||||
return <svg style={style} ref={node => this.node = node} />;
|
||||
}
|
||||
|
||||
let margin = MARGIN_LR * scale;
|
||||
let width = outerWidth - (margin * 2);
|
||||
let pctPos = width * this.props.percent;
|
||||
|
||||
return <div><svg
|
||||
onMouseUp={this._up} onMouseEnter={this._enter.bind(this)} onMouseMove={this._move} onKeyUp={this._keyup} onKeyDown={this._keydown} style={style} ref={node => this.node = node} tabIndex="0">
|
||||
<rect className='primary' style={{ opacity: 0.3 }} x={margin} y='0.25em' rx='0.3em' ry='0.3em' width={width} height='0.7em' />
|
||||
<rect className='primary-disabled' x={margin} y='0.45em' rx='0.15em' ry='0.15em' width={pctPos} height='0.3em' />
|
||||
<circle className='primary' r={margin} cy='0.6em' cx={pctPos + margin} />
|
||||
<rect x={margin} width={width} height='100%' fillOpacity='0' style={{ cursor: 'col-resize' }} onMouseDown={this._down} onTouchMove={this._move} onTouchStart={this._down} onTouchEnd={this._touchend} />
|
||||
{axis && <g style={{ fontSize: '.7em' }}>
|
||||
<text className='primary-disabled' y='3em' x={margin} style={{ textAnchor: 'middle' }}>{min + axisUnit}</text>
|
||||
<text className='primary-disabled' y='3em' x='50%' style={{ textAnchor: 'middle' }}>{(min + max / 2) + axisUnit}</text>
|
||||
<text className='primary-disabled' y='3em' x='100%' style={{ textAnchor: 'end' }}>{max + axisUnit}</text>
|
||||
</g>}
|
||||
</svg>
|
||||
<TextInputBox ref={(tb) => this.sliderInputBox = tb}
|
||||
onChange={this.props.onChange}
|
||||
percent={this.props.percent}
|
||||
axisUnit={this.props.axisUnit}
|
||||
scale={this.props.scale}
|
||||
max={this.props.max}
|
||||
|
||||
/>
|
||||
</div>;
|
||||
}
|
||||
|
||||
}
|
||||
/**
|
||||
* New component to add keyboard support for sliders - works on all devices (desktop, iOS, Android)
|
||||
**/
|
||||
class TextInputBox extends React.Component {
|
||||
static propTypes = {
|
||||
axisUnit: PropTypes.string,//units (T, M, etc.)
|
||||
max: PropTypes.number,
|
||||
onChange: PropTypes.func.isRequired,// function which determins percent value
|
||||
percent: PropTypes.number.isRequired,//value of slider
|
||||
scale: PropTypes.number
|
||||
};
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this._handleFocus = this._handleFocus.bind(this);
|
||||
this._handleBlur = this._handleBlur.bind(this);
|
||||
this._handleChange = this._handleChange.bind(this);
|
||||
//this._keydown = this._keydown.bind(this);
|
||||
this._keyup = this._keyup.bind(this);
|
||||
this.state = this._getInitialState();
|
||||
this.percent = this.props.percent;
|
||||
this.max = this.props.max;
|
||||
this.state.inputValue = this.percent * this.max;
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps, nextState) {
|
||||
var nextValue = nextProps.percent * nextProps.max;
|
||||
// See https://stackoverflow.com/questions/32414308/updating-state-on-props-change-in-react-form
|
||||
if (nextValue !== this.state.inputValue && nextValue <= nextProps.max) {
|
||||
this.setState({ inputValue: nextValue });
|
||||
}
|
||||
}
|
||||
componentDidUpdate(prevProps, prevState) {
|
||||
|
||||
if (prevState.divStyle.display == 'none' && this.state.divStyle.display == 'block') {
|
||||
this.enterTimer = setTimeout(() => this.sliderVal.focus(), 10);
|
||||
}
|
||||
|
||||
if (prevProps.max !== this.props.max && this.state.inputValue > this.props.max) {
|
||||
// they chose a different module
|
||||
this.setState({ inputValue: this.props.max });
|
||||
}
|
||||
|
||||
if (this.state.inputValue != prevState.inputValue && prevProps.max == this.props.max) {
|
||||
this.props.onChange(this.state.inputValue/this.props.max);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
_getInitialState() {
|
||||
return {
|
||||
divStyle: {display:'none'},
|
||||
inputStyle: {width:'4em'},
|
||||
labelStyle: {marginLeft: '.1em'},
|
||||
maxLength:5,
|
||||
size:5,
|
||||
min:0,
|
||||
tabIndex:-1,
|
||||
type:'number',
|
||||
readOnly: true
|
||||
}
|
||||
}
|
||||
|
||||
_setDisplay(val) {
|
||||
this.setState({
|
||||
divStyle: {display:val}
|
||||
});
|
||||
}
|
||||
|
||||
_handleFocus() {
|
||||
this.setState({
|
||||
inputValue:this._getValue()
|
||||
});
|
||||
}
|
||||
|
||||
_handleBlur() {
|
||||
this._setDisplay('none');
|
||||
if (this.state.inputValue !== '') {
|
||||
this.props.onChange(this.state.inputValue/this.props.max);
|
||||
} else {
|
||||
this.state.inputValue = this.props.percent * this.props.max;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
_getValue() {
|
||||
return this.state.inputValue;
|
||||
}
|
||||
|
||||
_handleChange(event) {
|
||||
if (event.target.value < 0) {
|
||||
this.setState({inputValue: 0});
|
||||
} else if (event.target.value <= this.props.max) {
|
||||
this.setState({inputValue: event.target.value});
|
||||
} else {
|
||||
this.setState({inputValue: this.props.max});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Key up handler for input field.
|
||||
* If user hits Enter key, blur/close the input field
|
||||
* @param {Event} event Keyboard event
|
||||
*/
|
||||
_keyup(event) {
|
||||
switch (event.key) {
|
||||
case 'Enter':
|
||||
this.sliderVal.blur();
|
||||
return;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
render() {
|
||||
let { axisUnit, onChange, percent, scale } = this.props;
|
||||
return <div style={this.state.divStyle}><input style={this.state.inputStyle} value={this._getValue()} min={this.state.min} max={this.props.max} onChange={this._handleChange} onKeyUp={this._keyup} tabIndex={this.state.tabIndex} maxLength={this.state.maxLength} size={this.state.size} onBlur={() => {this._handleBlur()}} onFocus={() => {this._handleFocus()}} type={this.state.type} ref={(ip) => this.sliderVal = ip}/><text className="primary upp" style={this.state.labelStyle}>{this.props.axisUnit}</text></div>;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -40,6 +40,8 @@ export default class Slot extends TranslatedComponent {
|
||||
|
||||
this._contextMenu = wrapCtxMenu(this._contextMenu.bind(this));
|
||||
this._getMaxClassLabel = this._getMaxClassLabel.bind(this);
|
||||
this._keyDown = this._keyDown.bind(this);
|
||||
this.slotDiv = null;
|
||||
}
|
||||
|
||||
// Must be implemented by subclasses:
|
||||
@@ -73,6 +75,25 @@ export default class Slot extends TranslatedComponent {
|
||||
this.props.onSelect(null,null);
|
||||
}
|
||||
|
||||
/** Key Down handler
|
||||
* @param {SyntheticEvent} event Event
|
||||
* ToDo: see if this can be moved up
|
||||
* we do more or less the same thing
|
||||
* in every section when Enter key is pressed
|
||||
* on a focusable item
|
||||
*
|
||||
*/
|
||||
_keyDown(event) {
|
||||
if (event.key == 'Enter') {
|
||||
if(event.target.className == 'r') {
|
||||
console.log("Slot: Enter key pressed on mod icon");
|
||||
this._toggleModifications();
|
||||
} else {
|
||||
console.log("Slot: Enter key pressed on: %O", event.target);
|
||||
}
|
||||
this.props.onOpen(event);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Render the slot
|
||||
* @return {React.Component} The slot
|
||||
@@ -104,6 +125,7 @@ export default class Slot extends TranslatedComponent {
|
||||
ship={ship}
|
||||
m={m}
|
||||
marker={modificationsMarker}
|
||||
modButton = {this.modButton}
|
||||
/>;
|
||||
} else {
|
||||
menu = <AvailableModulesMenu
|
||||
@@ -114,6 +136,7 @@ export default class Slot extends TranslatedComponent {
|
||||
onSelect={onSelect}
|
||||
warning={warning}
|
||||
diffDetails={diffDetails.bind(ship, this.context.language)}
|
||||
slotDiv = {this.slotDiv}
|
||||
/>;
|
||||
}
|
||||
}
|
||||
@@ -121,7 +144,7 @@ export default class Slot extends TranslatedComponent {
|
||||
// TODO: implement touch dragging
|
||||
|
||||
return (
|
||||
<div className={cn('slot', dropClass, { selected })} onClick={onOpen} onContextMenu={this._contextMenu} onDragOver={dragOver}>
|
||||
<div className={cn('slot', dropClass, { selected })} onClick={onOpen} onKeyDown={this._keyDown} onContextMenu={this._contextMenu} onDragOver={dragOver} tabIndex="0" ref={slotDiv => this.slotDiv = slotDiv}>
|
||||
<div className='details-container'>
|
||||
<div className='sz'>{this._getMaxClassLabel(translate)}</div>
|
||||
{slotDetails}
|
||||
@@ -131,6 +154,7 @@ export default class Slot extends TranslatedComponent {
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Toggle the modifications flag when selecting the modifications icon
|
||||
*/
|
||||
|
||||
@@ -35,6 +35,18 @@ export default class StandardSlot extends TranslatedComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this._modificationsSelected = false;
|
||||
this._keyDown = this._keyDown.bind(this);
|
||||
this.modButton = null;
|
||||
this.slotDiv = null;
|
||||
}
|
||||
|
||||
_keyDown(event) {
|
||||
if (event.key == 'Enter') {
|
||||
if(event.target.className == 'r') {
|
||||
this._toggleModifications();
|
||||
}
|
||||
this.props.onOpen(event);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -48,7 +60,7 @@ export default class StandardSlot extends TranslatedComponent {
|
||||
let m = slot.m;
|
||||
let classRating = m.class + m.rating;
|
||||
let menu;
|
||||
let validMods = m == null ? [] : (Modifications.modules[m.grp].modifications || []);
|
||||
let validMods = m == null || !Modifications.modules[m.grp] ? [] : (Modifications.modules[m.grp].modifications || []);
|
||||
let showModuleResistances = Persist.showModuleResistances();
|
||||
let mass = m.getMass() || m.cargo || m.fuel || 0;
|
||||
|
||||
@@ -82,6 +94,7 @@ export default class StandardSlot extends TranslatedComponent {
|
||||
ship={ship}
|
||||
m={m}
|
||||
marker={modificationsMarker}
|
||||
modButton = {this.modButton}
|
||||
/>;
|
||||
} else {
|
||||
menu = <AvailableModulesMenu
|
||||
@@ -92,12 +105,13 @@ export default class StandardSlot extends TranslatedComponent {
|
||||
onSelect={onSelect}
|
||||
warning={warning}
|
||||
diffDetails={diffDetails.bind(ship, this.context.language)}
|
||||
slotDiv = {this.slotDiv}
|
||||
/>;
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={cn('slot', { selected: this.props.selected })} onClick={this.props.onOpen} onContextMenu={stopCtxPropagation}>
|
||||
<div className={cn('slot', { selected: this.props.selected })} onClick={this.props.onOpen} onKeyDown={this._keyDown} onContextMenu={stopCtxPropagation} tabIndex="0" ref={ slotDiv => this.slotDiv = slotDiv }>
|
||||
<div className={cn('details-container', { warning: warning && warning(slot.m), disabled: m.grp !== 'bh' && !slot.enabled })}>
|
||||
<div className={'sz'}>{m.grp == 'bh' ? m.name.charAt(0) : slot.maxClass}</div>
|
||||
<div>
|
||||
@@ -121,7 +135,7 @@ export default class StandardSlot extends TranslatedComponent {
|
||||
{ showModuleResistances && m.getKineticResistance() ? <div className='l'>{translate('kinres')}: {formats.pct(m.getKineticResistance())}</div> : null }
|
||||
{ showModuleResistances && m.getThermalResistance() ? <div className='l'>{translate('thermres')}: {formats.pct(m.getThermalResistance())}</div> : null }
|
||||
{ m.getIntegrity() ? <div className='l'>{translate('integrity')}: {formats.int(m.getIntegrity())}</div> : null }
|
||||
{ validMods.length > 0 ? <div className='r' ><button onClick={this._toggleModifications.bind(this)} onContextMenu={stopCtxPropagation} onMouseOver={termtip.bind(null, 'modifications')} onMouseOut={tooltip.bind(null, null)}><ListModifications /></button></div> : null }
|
||||
{ validMods.length > 0 ? <div className='r' tabIndex="0" ref={ modButton => this.modButton = modButton }><button tabIndex="-1" onClick={this._toggleModifications.bind(this)} onContextMenu={stopCtxPropagation} onMouseOver={termtip.bind(null, 'modifications')} onMouseOut={tooltip.bind(null, null)}><ListModifications /></button></div> : null }
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -134,6 +148,7 @@ export default class StandardSlot extends TranslatedComponent {
|
||||
* Toggle the modifications flag when selecting the modifications icon
|
||||
*/
|
||||
_toggleModifications() {
|
||||
|
||||
this._modificationsSelected = !this._modificationsSelected;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,6 +62,7 @@
|
||||
"TT_SUMMARY_BOOST": "With full fuel tank and 4 pips to ENG",
|
||||
"TT_SUMMARY_BOOST_NONFUNCTIONAL": "Power distributor not able to supply enough power to boost",
|
||||
"TT_SUMMARY_SHIELDS": "Raw shield strength, including boosters",
|
||||
"TT_SUMMARY_SHIELDS_SCB": "Raw shield strength, including boosters and SCBs",
|
||||
"TT_SUMMARY_SHIELDS_NONFUNCTIONAL": "No shield generator or shield generator powered off",
|
||||
"TT_SUMMARY_INTEGRITY": "Ship integrity, including bulkheads and hull reinforcement packages",
|
||||
"TT_SUMMARY_HULL_MASS": "Mass of the hull prior to any modules being installed",
|
||||
@@ -117,6 +118,10 @@
|
||||
"pl": "Pulse Laser",
|
||||
"po": "Point Defence",
|
||||
"pp": "Power Plant",
|
||||
"gpp": "Guardian Hybrid Power Plant",
|
||||
"gpd": "Guardian Hybrid Power Distributor",
|
||||
"gpc": "Guardian Plasma Charger",
|
||||
"ggc": "Guardian Gauss Cannon",
|
||||
"psg": "Prismatic Shield Generator",
|
||||
"pv": "Planetary Vehicle Hangar",
|
||||
"rf": "Refinery",
|
||||
@@ -134,6 +139,7 @@
|
||||
"ul": "Burst Laser",
|
||||
"ws": "Frame Shift Wake Scanner",
|
||||
"rpl": "Repair Limpet Controller",
|
||||
"rcpl": "Recon Limpet Controller",
|
||||
"xs": "Xeno Scanner",
|
||||
"emptyrestricted": "empty (restricted)",
|
||||
"damage dealt to": "Damage dealt to",
|
||||
@@ -175,6 +181,7 @@
|
||||
"total": "Total",
|
||||
"ammo": "Ammunition maximum",
|
||||
"boot": "Boot time",
|
||||
"hacktime": "Hack time",
|
||||
"brokenregen": "Broken regeneration rate",
|
||||
"burst": "Burst",
|
||||
"burstrof": "Burst rate of fire",
|
||||
@@ -209,6 +216,7 @@
|
||||
"scanrate": "Scan rate",
|
||||
"scantime": "Scan time",
|
||||
"shield": "Shield",
|
||||
"armour": "Armour",
|
||||
"shieldboost": "Shield boost",
|
||||
"shieldreinforcement": "Shield reinforcement",
|
||||
"shotspeed": "Shot speed",
|
||||
|
||||
@@ -602,7 +602,7 @@ export default class OutfittingPage extends Page {
|
||||
</div>
|
||||
|
||||
{/* Main tables */}
|
||||
<ShipSummaryTable ship={ship} fuel={fuel} cargo={cargo} marker={shipSummaryMarker} />
|
||||
<ShipSummaryTable ship={ship} fuel={fuel} cargo={cargo} marker={shipSummaryMarker} pips={{sys: this.state.sys, wep: this.state.wep, eng: this.state.eng}} />
|
||||
<StandardSlotSection ship={ship} fuel={fuel} cargo={cargo} code={standardSlotMarker} onChange={shipUpdated} onCargoChange={this._cargoUpdated} onFuelChange={this._fuelUpdated} currentMenu={menu} />
|
||||
<InternalSlotSection ship={ship} code={internalSlotMarker} onChange={shipUpdated} onCargoChange={this._cargoUpdated} onFuelChange={this._fuelUpdated} currentMenu={menu} />
|
||||
<HardpointSlotSection ship={ship} code={hardpointsSlotMarker} onChange={shipUpdated} onCargoChange={this._cargoUpdated} onFuelChange={this._fuelUpdated} currentMenu={menu} />
|
||||
|
||||
@@ -47,7 +47,7 @@ export function totalJumpRange(mass, fsd, fuel) {
|
||||
* @param {number} multiplier Shield multiplier for ship (1 + shield boosters if any)
|
||||
* @return {number} Approximate shield strengh in MJ
|
||||
*/
|
||||
export function shieldStrength(mass, baseShield, sg, multiplier) {
|
||||
export function shieldStrength(mass, baseShield, sg, multiplier, ship) {
|
||||
// sg might be a module or a template; handle either here
|
||||
let minMass = sg instanceof Module ? sg.getMinMass() : sg.minmass;
|
||||
let optMass = sg instanceof Module ? sg.getOptMass() : sg.optmass;
|
||||
@@ -55,7 +55,17 @@ export function shieldStrength(mass, baseShield, sg, multiplier) {
|
||||
let minMul = sg instanceof Module ? sg.getMinMul() : sg.minmul;
|
||||
let optMul = sg instanceof Module ? sg.getOptMul() : sg.optmul;
|
||||
let maxMul = sg instanceof Module ? sg.getMaxMul() : sg.maxmul;
|
||||
|
||||
if (ship) {
|
||||
for (const i of ship.hardpoints) {
|
||||
if (!i.maxClass) {
|
||||
if (i.grp === 'sb' || (i.m && i.m.grp === 'sb')) {
|
||||
if (!isNaN(i.m.getModValue('optmul'))) {
|
||||
optMul += i.m.getModValue('optmul') / 10000;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let xnorm = Math.min(1, (maxMass - mass) / (maxMass - minMass));
|
||||
let exponent = Math.log((optMul - minMul) / (maxMul - minMul)) / Math.log(Math.min(1, (maxMass - optMass) / (maxMass - minMass)));
|
||||
let ynorm = Math.pow(xnorm, exponent);
|
||||
@@ -351,7 +361,7 @@ export function shieldMetrics(ship, sys) {
|
||||
boosterKinDmg = boosterKinDmg > 0.7 ? boosterKinDmg : 0.7 - (0.7 - boosterKinDmg) / 2;
|
||||
boosterThermDmg = boosterThermDmg > 0.7 ? boosterThermDmg : 0.7 - (0.7 - boosterThermDmg) / 2;
|
||||
|
||||
const generatorStrength = this.shieldStrength(ship.hullMass, ship.baseShieldStrength, shieldGenerator, 1);
|
||||
const generatorStrength = this.shieldStrength(ship.hullMass, ship.baseShieldStrength, shieldGenerator, 1, ship);
|
||||
const boostersStrength = generatorStrength * boost;
|
||||
|
||||
// Recover time is the time taken to go from 0 to 50%. It includes a 16-second wait before shields start to recover
|
||||
|
||||
@@ -9,12 +9,16 @@ export const StandardArray = [
|
||||
'pd', // Power Distributor
|
||||
's', // Sensors
|
||||
'ft', // Fuel Tank
|
||||
'gpp', // Guardian Hybrid Power Plant
|
||||
'gpd' // Guardian Hybrid Power Distributor
|
||||
];
|
||||
|
||||
// Map to lookup group labels/names for component grp, used for JSON Serialization
|
||||
export const ModuleGroupToName = {
|
||||
// Standard
|
||||
pp: 'Power Plant',
|
||||
gpp: 'Guardian Hybrid Power Plant',
|
||||
gpd: 'Guardian Hybrid Power Distributor',
|
||||
t: 'Thrusters',
|
||||
fsd: 'Frame Shift Drive',
|
||||
ls: 'Life Support',
|
||||
@@ -75,7 +79,10 @@ export const ModuleGroupToName = {
|
||||
sb: 'Shield Booster',
|
||||
tp: 'Torpedo Pylon',
|
||||
sfn: 'Shutdown Field Neutraliser',
|
||||
xs: 'Xeno Scanner'
|
||||
xs: 'Xeno Scanner',
|
||||
rcpl: 'Recon Limpet Controller',
|
||||
gpc: 'Guardian Plasma Charger',
|
||||
ggc: 'Guardian Gauss Cannon',
|
||||
};
|
||||
|
||||
let GrpNameToCodeMap = {};
|
||||
|
||||
@@ -722,4 +722,13 @@ export default class Module {
|
||||
getTime() {
|
||||
return this._getModifiedValue('time');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the hack time for this module, taking in to account modifications
|
||||
* @return {string} the time for this module
|
||||
*/
|
||||
getHackTime() {
|
||||
return this._getModifiedValue('hacktime');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -241,7 +241,7 @@ export default class Ship {
|
||||
}
|
||||
|
||||
// TODO Not accurate if the ship has modified shield boosters
|
||||
return Calc.shieldStrength(this.hullMass, this.baseShieldStrength, sg, 1 + (multiplierDelta || 0));
|
||||
return Calc.shieldStrength(this.hullMass, this.baseShieldStrength, sg, 1 + (multiplierDelta || 0), this);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -437,12 +437,15 @@ export default class Ship {
|
||||
m.blueprint = bp;
|
||||
this.clearModifications(m);
|
||||
// Set any hidden items for the blueprint now
|
||||
const features = m.blueprint.grades[m.blueprint.grade].features;
|
||||
for (const featureName in features) {
|
||||
if (Modifications.modifications[featureName].hidden) {
|
||||
this.setModification(m, featureName, bp.grades[bp.grade].features[featureName][0]);
|
||||
if (m.blueprint.grades[m.blueprint.grade] && m.blueprint.grades[m.blueprint.grade].features) {
|
||||
const features = m.blueprint.grades[m.blueprint.grade].features;
|
||||
for (const featureName in features) {
|
||||
if (Modifications.modifications[featureName].hidden) {
|
||||
this.setModification(m, featureName, bp.grades[bp.grade].features[featureName][0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.updateModificationsString();
|
||||
}
|
||||
|
||||
@@ -464,7 +467,17 @@ export default class Ship {
|
||||
if (m.blueprint) {
|
||||
m.blueprint.special = special;
|
||||
}
|
||||
this.recalculateDps().recalculateHps().recalculateEps();
|
||||
this.updatePowerGenerated()
|
||||
.updatePowerUsed()
|
||||
.recalculateMass()
|
||||
.updateJumpStats()
|
||||
.recalculateShield()
|
||||
.recalculateShieldCells()
|
||||
.recalculateArmour()
|
||||
.recalculateDps()
|
||||
.recalculateEps()
|
||||
.recalculateHps()
|
||||
.updateMovement();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -49,7 +49,6 @@ export function trader (ship, shielded, standardOpts) {
|
||||
if (canMount(ship, slot, 'sg')) { // Assuming largest slot can hold an eligible shield
|
||||
const shield = ModuleUtils.findInternal('sg', slot.maxClass, 'A')
|
||||
if (shield && shield.maxmass > ship.hullMass) {
|
||||
console.log(shield)
|
||||
ship.use(slot, shield)
|
||||
ship.setSlotEnabled(slot, true)
|
||||
usedSlots.push(slot)
|
||||
@@ -414,4 +413,3 @@ export function racer (ship) {
|
||||
// ship.standard[5].m.blueprint.grade = 5;
|
||||
// setBest(ship, ship.standard[5].m);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,67 @@
|
||||
import React from 'react';
|
||||
import { Modifications } from 'coriolis-data/dist';
|
||||
|
||||
/**
|
||||
* Generate a tooltip with details of a blueprint's specials
|
||||
* @param {Object} translate The translate object
|
||||
* @param {Object} blueprint The blueprint at the required grade
|
||||
* @param {string} grp The group of the module
|
||||
* @param {Object} m The module to compare with
|
||||
* @param specialName
|
||||
* @returns {Object} The react components
|
||||
*/
|
||||
export function specialToolTip(translate, blueprint, grp, m, specialName) {
|
||||
const effects = [];
|
||||
if (!blueprint || !blueprint.features) {
|
||||
return undefined;
|
||||
}
|
||||
if (m) {
|
||||
// We also add in any benefits from specials that aren't covered above
|
||||
if (m.blueprint) {
|
||||
for (const feature in Modifications.modifierActions[specialName]) {
|
||||
// if (!blueprint.features[feature] && !m.mods.feature) {
|
||||
const featureDef = Modifications.modifications[feature];
|
||||
if (featureDef && !featureDef.hidden) {
|
||||
let symbol = '';
|
||||
if (feature === 'jitter') {
|
||||
symbol = '°';
|
||||
} else if (featureDef.type === 'percentage') {
|
||||
symbol = '%';
|
||||
}
|
||||
let current = m.getModValue(feature) - m.getModValue(feature, true);
|
||||
if (featureDef.type === 'percentage') {
|
||||
current = Math.round(current / 10) / 10;
|
||||
} else if (featureDef.type === 'numeric') {
|
||||
current /= 100;
|
||||
}
|
||||
const currentIsBeneficial = isValueBeneficial(feature, current);
|
||||
|
||||
effects.push(
|
||||
<tr key={feature + '_specialTT'}>
|
||||
<td style={{textAlign: 'left'}}>{translate(feature, grp)}</td>
|
||||
<td> </td>
|
||||
<td className={current === 0 ? '' : currentIsBeneficial ? 'secondary' : 'warning'}
|
||||
style={{textAlign: 'right'}}>{current}{symbol}</td>
|
||||
<td> </td>
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<table width='100%'>
|
||||
<tbody>
|
||||
{effects}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a tooltip with details of a blueprint's effects
|
||||
* @param {Object} translate The translate object
|
||||
@@ -311,3 +372,60 @@ function _setValue(ship, m, featureName, value) {
|
||||
ship.setModification(m, featureName, value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide 'percent' primary query
|
||||
* @param {Object} m The module for which to perform the query
|
||||
* @returns {Number} percent The percentage indicator of current applied values.
|
||||
*/
|
||||
export function getPercent(m) {
|
||||
let result = null;
|
||||
const features = m.blueprint.grades[m.blueprint.grade].features;
|
||||
for (const featureName in features) {
|
||||
|
||||
if (features[featureName][0] === features[featureName][1]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let value = _getValue(m, featureName);
|
||||
let mult;
|
||||
if (Modifications.modifications[featureName].higherbetter) {
|
||||
// Higher is better, but is this making it better or worse?
|
||||
if (features[featureName][0] < 0 || (features[featureName][0] === 0 && features[featureName][1] < 0)) {
|
||||
mult = Math.round((value - features[featureName][1]) / (features[featureName][0] - features[featureName][1]) * 100);
|
||||
} else {
|
||||
mult = Math.round((value - features[featureName][0]) / (features[featureName][1] - features[featureName][0]) * 100);
|
||||
}
|
||||
} else {
|
||||
// Higher is worse, but is this making it better or worse?
|
||||
if (features[featureName][0] < 0 || (features[featureName][0] === 0 && features[featureName][1] < 0)) {
|
||||
mult = Math.round((value - features[featureName][0]) / (features[featureName][1] - features[featureName][0]) * 100);
|
||||
} else {
|
||||
mult = Math.round((value - features[featureName][1]) / (features[featureName][0] - features[featureName][1]) * 100);
|
||||
}
|
||||
}
|
||||
|
||||
if (result && result != mult) {
|
||||
return null;
|
||||
} else if (result != mult) {
|
||||
result = mult;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Query a feature value
|
||||
* @param {Object} m The module for which to perform the query
|
||||
* @param {string} featureName The feature being queried
|
||||
*/
|
||||
function _getValue(m, featureName) {
|
||||
if (Modifications.modifications[featureName].type == 'percentage') {
|
||||
return m.getModValue(featureName, true) / 10000;
|
||||
} else if (Modifications.modifications[featureName].type == 'numeric') {
|
||||
return m.getModValue(featureName, true) / 100;
|
||||
} else {
|
||||
return m.getModValue(featureName, true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,14 +73,14 @@ export function shipFromLoadoutJSON (json) {
|
||||
let opts = [];
|
||||
|
||||
for (const module of json.Modules) {
|
||||
switch (module.Slot) {
|
||||
switch (module.Slot.toLowerCase()) {
|
||||
// Cargo Hatch.
|
||||
case 'CargoHatch':
|
||||
case 'cargohatch':
|
||||
ship.cargoHatch.enabled = module.On
|
||||
ship.cargoHatch.priority = module.Priority
|
||||
break
|
||||
// Add the bulkheads
|
||||
case 'Armour':
|
||||
case 'armour':
|
||||
if (module.Item.toLowerCase().endsWith('_armour_grade1')) {
|
||||
ship.useBulkhead(0, true)
|
||||
} else if (module.Item.toLowerCase().endsWith('_armour_grade2')) {
|
||||
@@ -95,51 +95,51 @@ export function shipFromLoadoutJSON (json) {
|
||||
throw 'Unknown bulkheads "' + module.Item + '"'
|
||||
}
|
||||
ship.bulkheads.enabled = true
|
||||
if (module.Engineering) _addModifications(ship.bulkheads.m, module.Engineering.Modifiers, module.Engineering.BlueprintName, module.Engineering.Level)
|
||||
if (module.Engineering) _addModifications(ship.bulkheads.m, module.Engineering.Modifiers, module.Engineering.BlueprintName, module.Engineering.Level, module.Engineering.ExperimentalEffect)
|
||||
break
|
||||
case 'PowerPlant':
|
||||
case 'powerplant':
|
||||
const powerplant = _moduleFromFdName(module.Item)
|
||||
ship.use(ship.standard[0], powerplant, true)
|
||||
ship.standard[0].enabled = module.On
|
||||
ship.standard[0].priority = module.Priority
|
||||
if (module.Engineering) _addModifications(powerplant, module.Engineering.Modifiers, module.Engineering.BlueprintName, module.Engineering.Level)
|
||||
if (module.Engineering) _addModifications(powerplant, module.Engineering.Modifiers, module.Engineering.BlueprintName, module.Engineering.Level, module.Engineering.ExperimentalEffect)
|
||||
break
|
||||
case 'MainEngines':
|
||||
case 'mainengines':
|
||||
const thrusters = _moduleFromFdName(module.Item)
|
||||
ship.use(ship.standard[1], thrusters, true)
|
||||
ship.standard[1].enabled = module.On
|
||||
ship.standard[1].priority = module.Priority
|
||||
if (module.Engineering) _addModifications(thrusters, module.Engineering.Modifiers, module.Engineering.BlueprintName, module.Engineering.Level)
|
||||
if (module.Engineering) _addModifications(thrusters, module.Engineering.Modifiers, module.Engineering.BlueprintName, module.Engineering.Level, module.Engineering.ExperimentalEffect)
|
||||
break
|
||||
case 'FrameShiftDrive':
|
||||
case 'frameshiftdrive':
|
||||
const frameshiftdrive = _moduleFromFdName(module.Item)
|
||||
ship.use(ship.standard[2], frameshiftdrive, true)
|
||||
ship.standard[2].enabled = module.On
|
||||
ship.standard[2].priority = module.Priority
|
||||
if (module.Engineering) _addModifications(frameshiftdrive, module.Engineering.Modifiers, module.Engineering.BlueprintName, module.Engineering.Level)
|
||||
if (module.Engineering) _addModifications(frameshiftdrive, module.Engineering.Modifiers, module.Engineering.BlueprintName, module.Engineering.Level, module.Engineering.ExperimentalEffect)
|
||||
break
|
||||
case 'LifeSupport':
|
||||
case 'lifesupport':
|
||||
const lifesupport = _moduleFromFdName(module.Item)
|
||||
ship.use(ship.standard[3], lifesupport, true)
|
||||
ship.standard[3].enabled = module.On === true
|
||||
ship.standard[3].priority = module.Priority
|
||||
if (module.Engineering) _addModifications(lifesupport, module.Engineering.Modifiers, module.Engineering.BlueprintName, module.Engineering.Level)
|
||||
if (module.Engineering) _addModifications(lifesupport, module.Engineering.Modifiers, module.Engineering.BlueprintName, module.Engineering.Level, module.Engineering.ExperimentalEffect)
|
||||
break
|
||||
case 'PowerDistributor':
|
||||
case 'powerdistributor':
|
||||
const powerdistributor = _moduleFromFdName(module.Item)
|
||||
ship.use(ship.standard[4], powerdistributor, true)
|
||||
ship.standard[4].enabled = module.On
|
||||
ship.standard[4].priority = module.Priority
|
||||
if (module.Engineering) _addModifications(powerdistributor, module.Engineering.Modifiers, module.Engineering.BlueprintName, module.Engineering.Level)
|
||||
if (module.Engineering) _addModifications(powerdistributor, module.Engineering.Modifiers, module.Engineering.BlueprintName, module.Engineering.Level, module.Engineering.ExperimentalEffect)
|
||||
break
|
||||
case 'Radar':
|
||||
case 'radar':
|
||||
const sensors = _moduleFromFdName(module.Item)
|
||||
ship.use(ship.standard[5], sensors, true)
|
||||
ship.standard[5].enabled = module.On
|
||||
ship.standard[5].priority = module.Priority
|
||||
if (module.Engineering) _addModifications(sensors, module.Engineering.Modifiers, module.Engineering.BlueprintName, module.Engineering.Level)
|
||||
if (module.Engineering) _addModifications(sensors, module.Engineering.Modifiers, module.Engineering.BlueprintName, module.Engineering.Level, module.Engineering.ExperimentalEffect)
|
||||
break
|
||||
case 'FuelTank':
|
||||
case 'fueltank':
|
||||
const fueltank = _moduleFromFdName(module.Item)
|
||||
ship.use(ship.standard[6], fueltank, true)
|
||||
ship.standard[6].enabled = true
|
||||
@@ -148,7 +148,7 @@ export function shipFromLoadoutJSON (json) {
|
||||
default:
|
||||
}
|
||||
for (const module of json.Modules) {
|
||||
if (module.Slot.search(/Hardpoint/) !== -1) {
|
||||
if (module.Slot.toLowerCase().search(/hardpoint/) !== -1) {
|
||||
// Add hardpoints
|
||||
let hardpoint;
|
||||
let hardpointClassNum = -1
|
||||
@@ -166,7 +166,7 @@ export function shipFromLoadoutJSON (json) {
|
||||
|
||||
// Now that we know what we're looking for, find it
|
||||
const hardpointName = HARDPOINT_NUM_TO_CLASS[hardpointClassNum] + 'Hardpoint' + hardpointSlotNum
|
||||
const hardpointSlot = json.Modules.find(elem => elem.Slot === hardpointName)
|
||||
const hardpointSlot = json.Modules.find(elem => elem.Slot.toLowerCase() === hardpointName.toLowerCase())
|
||||
if (!hardpointSlot) {
|
||||
// This can happen with old imports that don't contain new hardpoints
|
||||
} else if (!hardpointSlot) {
|
||||
@@ -181,17 +181,17 @@ export function shipFromLoadoutJSON (json) {
|
||||
hardpointArrayNum++
|
||||
}
|
||||
}
|
||||
if (module.Slot.search(/Slot\d/) !== -1) {
|
||||
if (module.Slot.toLowerCase().search(/slot\d/) !== -1) {
|
||||
let internalSlotNum = 1
|
||||
let militarySlotNum = 1
|
||||
for (let i in shipTemplate.slots.internal) {
|
||||
const isMilitary = isNaN(shipTemplate.slots.internal[i]) ? shipTemplate.slots.internal[i].name = 'Military' : false
|
||||
const isMilitary = isNaN(shipTemplate.slots.internal[i]) ? shipTemplate.slots.internal[i].name = 'military' : false
|
||||
|
||||
// The internal slot might be a standard or a military slot. Military slots have a different naming system
|
||||
let internalSlot = null
|
||||
if (isMilitary) {
|
||||
const internalName = 'Military0' + militarySlotNum
|
||||
internalSlot = json.Modules.find(elem => elem.Slot === internalName)
|
||||
internalSlot = json.Modules.find(elem => elem.Slot.toLowerCase() === internalName.toLowerCase())
|
||||
militarySlotNum++
|
||||
} else {
|
||||
// Slot numbers are not contiguous so handle skips.
|
||||
@@ -199,8 +199,8 @@ export function shipFromLoadoutJSON (json) {
|
||||
// Slot sizes have no relationship to the actual size, either, so check all possibilities
|
||||
for (let slotsize = 0; slotsize < 9; slotsize++) {
|
||||
const internalName = 'Slot' + (internalSlotNum <= 9 ? '0' : '') + internalSlotNum + '_Size' + slotsize
|
||||
if (json.Modules.find(elem => elem.Slot === internalName)) {
|
||||
internalSlot = json.Modules.find(elem => elem.Slot === internalName);
|
||||
if (json.Modules.find(elem => elem.Slot.toLowerCase() === internalName.toLowerCase())) {
|
||||
internalSlot = json.Modules.find(elem => elem.Slot.toLowerCase() === internalName.toLowerCase());
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -256,7 +256,7 @@ function _addModifications (module, modifiers, blueprint, grade, specialModifica
|
||||
for (const i in modifiers) {
|
||||
// Some special modifications
|
||||
// Look up the modifiers to find what we need to do
|
||||
const findMod = val => Object.keys(Modifications.modifierActions).find(elem => elem.toString().toLowerCase().search(val.toString().toLowerCase().replace(/(OutfittingFieldType_|persecond)/igm, '')) >= 0)
|
||||
const findMod = val => Object.keys(Modifications.modifierActions).find(elem => elem.toString().toLowerCase().replace(/(outfittingfieldtype_|persecond)/igm, '') === val.toString().toLowerCase().replace(/(outfittingfieldtype_|persecond)/igm, ''))
|
||||
const modifierActions = Modifications.modifierActions[findMod(modifiers[i].Label)]
|
||||
//TODO: Figure out how to scale this value.
|
||||
if (!!modifiers[i].LessIsGood) {
|
||||
|
||||
@@ -10,6 +10,8 @@
|
||||
@secondary: #1FB0FF; // Light blue
|
||||
@warning: #FF3B00; // Dark Orange
|
||||
@disabled: #555; // Light grey
|
||||
@success: #71a052; // Green
|
||||
@purple: #800080; // Purple
|
||||
@primary-disabled: darken(@primary, @disabledDarken);
|
||||
@secondary-disabled: darken(@secondary, @disabledDarken);
|
||||
@warning-disabled: darken(@warning, @disabledDarken);
|
||||
|
||||
@@ -12,6 +12,20 @@
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.view-changes {
|
||||
position: fixed;
|
||||
top: 3em;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 3em;
|
||||
z-index: 3;
|
||||
line-height: 3em;
|
||||
text-align: center;
|
||||
background-color: @bg;
|
||||
color: @warning;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
header {
|
||||
background-color: @bg;
|
||||
margin: 0;
|
||||
|
||||
@@ -70,10 +70,29 @@
|
||||
padding: 0.5em 0.2em;
|
||||
font-size: 0.9em;
|
||||
|
||||
#summaryTable {
|
||||
.summaryTable {
|
||||
.user-select-none();
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
|
||||
& > thead.blue {
|
||||
background-color: @secondary;
|
||||
border-left: 1px solid @primary-bg;
|
||||
color: @primary-bg;
|
||||
}
|
||||
|
||||
& > thead.green {
|
||||
background-color: @success;
|
||||
border-left: 1px solid @primary-bg;
|
||||
color: @primary-bg;
|
||||
}
|
||||
|
||||
& > thead.purple {
|
||||
background-color: @purple;
|
||||
border-left: 1px solid @primary-bg;
|
||||
color: @primary-bg;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -41,6 +41,10 @@
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
input.cb:focus {
|
||||
border-color:#fff;
|
||||
}
|
||||
|
||||
.l {
|
||||
text-transform: capitalize;
|
||||
margin-right: 0.8em;
|
||||
|
||||
Reference in New Issue
Block a user