Compare commits

..

3 Commits

Author SHA1 Message Date
Unknown
e520208ec6 Some more attempts 2018-01-27 16:15:21 +00:00
Unknown
29020e64f2 Some work 2018-01-27 15:40:43 +00:00
Unknown
16fe3ba9f5 Axios 2018-01-27 14:51:56 +00:00
76 changed files with 14671 additions and 5983 deletions

View File

@@ -1,2 +0,0 @@
node_modules
npm-debug.log

View File

@@ -1,37 +0,0 @@
### STAGE 1: Build ###
FROM node:9.11.1-alpine as builder
ARG branch=develop
ENV BRANCH=$branch
WORKDIR /src/app
RUN mkdir -p /src/app/coriolis
RUN mkdir -p /src/app/coriolis-data
COPY ./coriolis/ /src/app/coriolis
COPY ./coriolis-data/ /src/app/coriolis-data
RUN apk update
RUN apk add git
RUN npm i -g npm
# Set up coriolis-data
WORKDIR /src/app/coriolis-data
RUN git fetch --all
RUN git reset --hard origin/$BRANCH
RUN npm install --no-package-lock
RUN npm start
WORKDIR /src/app/coriolis
RUN git fetch --all
RUN git reset --hard origin/$BRANCH
RUN npm install --no-package-lock
RUN npm run build
### STAGE 2: Production Environment ###
FROM nginx:1.13.12-alpine as web
COPY coriolis/.docker/nginx.conf /etc/nginx/nginx.conf
COPY --from=builder /src/app/coriolis/build /usr/share/nginx/html
WORKDIR /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-c", "/etc/nginx/nginx.conf", "-g", "daemon off;"]

View File

@@ -1,34 +0,0 @@
version: '2.2'
services:
coriolis_prod:
image: edcd/coriolis:master
restart: always
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
networks:
- web
labels:
- "traefik.docker.network=web"
- "traefik.enable=true"
- "traefik.basic.frontend.rule=Host:coriolis.io,coriolis.edcd.io"
- "traefik.basic.port=80"
- "traefik.basic.protocol=http"
coriolis_dev:
image: edcd/coriolis:develop
restart: always
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
networks:
- web
labels:
- "traefik.docker.network=web"
- "traefik.enable=true"
- "traefik.basic.frontend.rule=Host:beta.coriolis.io,beta.coriolis.edcd.io"
- "traefik.basic.port=80"
- "traefik.basic.protocol=http"
networks:
web:
external: true

View File

@@ -1,45 +0,0 @@
worker_processes 1;
user nobody nobody;
error_log /tmp/error.log;
pid /tmp/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
sendfile on;
client_body_temp_path /tmp/client_body;
fastcgi_temp_path /tmp/fastcgi_temp;
proxy_temp_path /tmp/proxy_temp;
scgi_temp_path /tmp/scgi_temp;
uwsgi_temp_path /tmp/uwsgi_temp;
access_log /tmp/access.log;
error_log /tmp/error.log;
keepalive_timeout 3000;
server {
listen 80;
listen [::]:80;
index index.html;
server_name localhost;
root /usr/share/nginx/html;
autoindex on;
location ~* \.(?:manifest|appcache|html?|xml|json|css|js|map|jpg|jpeg|gif|png|ico|svg|eot|ttf|woff|woff2)$ {
expires -1;
add_header Access-Control-Allow-Origin *;
add_header Access-Control-Allow-Credentials true;
add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS';
add_header Access-Control-Allow-Headers 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type';
access_log off;
}
location / {
try_files $uri $uri/ /index.html =404;
}
}
}

View File

@@ -1,21 +0,0 @@
root = true
[*]
# change these settings to your own preference
indent_style = space
indent_size = 2
# we recommend you to keep these unchanged
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.md]
trim_trailing_whitespace = false
[{package,bower}.json]
indent_style = space
indent_size = 2

View File

@@ -1,103 +0,0 @@
{
"source": "./src/app",
"includes": ["\\.js$", "\\.jsx$"],
"destination": "./docs",
"index": "./README.md",
"plugins": [
{
"name": "esdoc-standard-plugin",
"option": {
"lint": {
"enable": false
},
"coverage": {
"enable": false
},
"accessor": {
"access": [
"public",
"protected",
"private"
],
"autoPrivate": true
},
"undocumentIdentifier": {
"enable": true
},
"unexportedIdentifier": {
"enable": false
},
"typeInference": {
"enable": true
},
"brand": {
"logo": "./src/images/logo/192x192.png",
"title": "Coriolis",
"description": "Coriolis Shipyard for Elite Dangerous",
"repository": "https://github.com/EDCD/coriolis",
"site": "https://coriolis.edcd.io",
"author": "https://github.com/edcd",
"image": "./src/images/logo/192x192.png"
}
}
},
{
"name": "esdoc-ecmascript-proposal-plugin",
"option": {
"all": true
}
},
{
"name": "esdoc-react-plugin"
},
{
"name": "esdoc-standard-plugin",
"option": {
"lint": {
"enable": false
},
"coverage": {
"enable": false
},
"accessor": {
"access": [
"public",
"protected",
"private"
],
"autoPrivate": true
},
"undocumentIdentifier": {
"enable": true
},
"unexportedIdentifier": {
"enable": false
},
"typeInference": {
"enable": true
},
"brand": {
"logo": "./src/images/logo/192x192.png",
"title": "Coriolis",
"description": "Coriolis Shipyard for Elite Dangerous",
"repository": "https://github.com/EDCD/coriolis",
"site": "https://coriolis.edcd.io",
"author": "https://github.com/edcd",
"image": "./src/images/logo/192x192.png"
}
}
},
{
"name": "esdoc-jsx-plugin",
"option": {
"enable": true
}
},
{
"name": "esdoc-publish-html-plugin",
"option": {
"template": "./node_modules/esdoc-custom-theme/template"
}
}
]
}

View File

@@ -5,12 +5,11 @@
"jsx": true, "jsx": true,
"classes": true, "classes": true,
"modules": true "modules": true
} },
}, },
"env": { "env": {
"browser": true, "browser": true,
"node": true, "node": true
"es6": true
}, },
"plugins": [ "plugins": [
"react" "react"
@@ -34,6 +33,7 @@
"ClassDeclaration": true "ClassDeclaration": true
} }
}], }],
"no-console": 2,
"brace-style": [2, "1tbs", { "allowSingleLine": true }], "brace-style": [2, "1tbs", { "allowSingleLine": true }],
"comma-style": [2, "last"], "comma-style": [2, "last"],
"indent": [2, 2, { "SwitchCase": 1, "VariableDeclarator": 2 }], "indent": [2, 2, { "SwitchCase": 1, "VariableDeclarator": 2 }],

4
.gitignore vendored
View File

@@ -7,7 +7,3 @@ nginx.pid
/bin /bin
env env
*.swp *.swp
.project
.vscode/
docs/
package-lock.json

1
.npmrc
View File

@@ -1 +0,0 @@
package-lock=false

View File

@@ -8,8 +8,7 @@ cache:
directories: directories:
- node_modules - node_modules
before_install: before_script:
- git clone https://github.com/EDCD/coriolis-data.git ../coriolis-data
script: script:
- npm run lint - npm run lint

View File

@@ -23,7 +23,6 @@ Chat to us on [Discord](https://discord.gg/0uwCh6R62aPRjk9w)!
See the [Developer's Guide](https://github.com/EDCD/coriolis/wiki/Developing-for-Coriolis) in the wiki. See the [Developer's Guide](https://github.com/EDCD/coriolis/wiki/Developing-for-Coriolis) in the wiki.
Also see [the documentation site.](https://coriolis.willb.info/)
### Ship and Module Database ### Ship and Module Database

1
devServer.js Executable file → Normal file
View File

@@ -5,7 +5,6 @@ var config = require('./webpack.config.dev');
new WebpackDevServer(webpack(config), { new WebpackDevServer(webpack(config), {
publicPath: config.output.publicPath, publicPath: config.output.publicPath,
hot: true, hot: true,
disableHostCheck: true,
headers: { "Access-Control-Allow-Origin": "*" }, headers: { "Access-Control-Allow-Origin": "*" },
historyApiFallback: { historyApiFallback: {
rewrites: [ rewrites: [

12218
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,125 +1,114 @@
{ {
"name": "coriolis_shipyard", "name": "coriolis_shipyard",
"version": "2.9.18", "version": "2.5.1",
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://github.com/EDCD/coriolis" "url": "https://github.com/EDCD/coriolis"
}, },
"homepage": "https://coriolis.edcd.io", "homepage": "https://coriolis.edcd.io",
"bugs": "https://github.com/EDCD/coriolis/issues", "bugs": "https://github.com/EDCD/coriolis/issues",
"private": true, "private": true,
"engine": "node >= 4.8.1", "engine": "node >= 4.8.1",
"license": "MIT", "license": "MIT",
"scripts": { "scripts": {
"prepublish": "rollup -c && uglifyjs d3.js -c -m -o d3.min.js", "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", "extract-translations": "grep -hroE \"(translate\\('[^']+'\\))|(tip.bind\\(null, '[^']+')\" src/* | grep -oE \"'[^']+'\" | grep -oE \"[^']+\" | sort -u -f",
"clean": "rimraf build", "clean": "rimraf build",
"start": "node devServer.js", "start": "node devServer.js",
"lint": "eslint --ext .js,.jsx src", "lint": "eslint --ext .js,.jsx src",
"test": "jest", "test": "jest",
"prod-serve": "nginx -p $(pwd) -c nginx.conf", "prod-serve": "nginx -p $(pwd) -c nginx.conf",
"prod-stop": "kill -QUIT $(cat nginx.pid)", "prod-stop": "kill -QUIT $(cat nginx.pid)",
"build": "npm run clean && cross-env NODE_ENV=production webpack -p --config webpack.config.prod.js", "build": "npm run clean && NODE_ENV=production webpack -p --config webpack.config.prod.js",
"rsync": "rsync -ae \"ssh -i $CORIOLIS_PEM\" --delete build/ $CORIOLIS_USER@$CORIOLIS_HOST:~/wwws", "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" "deploy": "npm run lint && npm test && npm run build && npm run rsync"
}, },
"jest": { "jest": {
"transform": { "transform": {
".*": "<rootDir>/node_modules/babel-jest" ".*": "<rootDir>/node_modules/babel-jest"
}, },
"testRegex": "(/__tests__/test-.*|\\.(test|spec))\\.js$", "testRegex": "(/__tests__/test-.*|\\.(test|spec))\\.js$",
"moduleFileExtensions": [ "moduleFileExtensions": [
"js", "js",
"json", "json",
"jsx" "jsx"
], ],
"automock": true, "automock": true,
"bail": false, "bail": false,
"unmockedModulePathPatterns": [ "unmockedModulePathPatterns": [
"<rootDir>/node_modules/lodash", "<rootDir>/node_modules/lodash",
"<rootDir>/node_modules/react", "<rootDir>/node_modules/react",
"<rootDir>/node_modules/react-dom", "<rootDir>/node_modules/react-dom",
"<rootDir>/node_modules/react-transition-group", "<rootDir>/node_modules/react-transition-group",
"<rootDir>/node_modules/react-testutils-additions", "<rootDir>/node_modules/react-testutils-additions",
"<rootDir>/node_modules/fbjs", "<rootDir>/node_modules/fbjs",
"<rootDir>/node_modules/fbemitter", "<rootDir>/node_modules/fbemitter",
"<rootDir>/node_modules/classnames", "<rootDir>/node_modules/classnames",
"<rootDir>/node_modules/d3", "<rootDir>/node_modules/d3",
"<rootDir>/node_modules/lz-string", "<rootDir>/node_modules/lz-string",
"<rootDir>/node_modules/jsen", "<rootDir>/node_modules/jsen",
"coriolis-data", "coriolis-data",
"<rootDir>/src/app/shipyard", "<rootDir>/src/app/shipyard",
"<rootDir>/src/app/i18n", "<rootDir>/src/app/i18n",
"<rootDir>/src/app/utils", "<rootDir>/src/app/utils",
"<rootDir>/src/schemas", "<rootDir>/src/schemas",
"<rootDir>/__tests__" "<rootDir>/__tests__"
] ]
}, },
"devDependencies": { "devDependencies": {
"appcache-webpack-plugin": "^1.3.0", "appcache-webpack-plugin": "^1.3.0",
"babel-core": "*", "babel-core": "*",
"babel-eslint": "*", "babel-eslint": "*",
"babel-jest": "*", "babel-jest": "*",
"babel-loader": "*", "babel-loader": "*",
"babel-preset-env": "*", "babel-preset-env": "*",
"babel-preset-react": "*", "babel-preset-react": "*",
"babel-preset-stage-0": "*", "babel-preset-stage-0": "*",
"create-react-class": "^15.6.2", "create-react-class": "^15.6.2",
"cross-env": "^5.1.4", "css-loader": "^0.28.0",
"css-loader": "^0.28.0", "d3-selection": "1",
"d3-selection": "1", "eslint": "3.19.0",
"esdoc": "^1.1.0", "eslint-plugin-react": "^6.10.3",
"esdoc-custom-theme": "^1.4.2", "expose-loader": "^0.7.3",
"esdoc-ecmascript-proposal-plugin": "^1.0.0", "express": "^4.15.2",
"esdoc-jsx-plugin": "^1.0.0", "extract-text-webpack-plugin": "2.1.0",
"esdoc-publish-html-plugin": "^1.1.2", "file-loader": "^0.11.1",
"esdoc-react-plugin": "^1.0.1", "html-webpack-plugin": "^2.28.0",
"esdoc-standard-plugin": "^1.0.0", "jest-cli": "^21.2.1",
"eslint": "3.19.0", "jsen": "^0.6.4",
"eslint-plugin-react": "^6.10.3", "json-loader": "^0.5.4",
"expose-loader": "^0.7.3", "less": "^2.7.2",
"express": "^4.15.2", "less-loader": "^4.0.3",
"extract-text-webpack-plugin": "2.1.0", "react-addons-perf": "^15.4.2",
"file-loader": "^0.11.1", "react-measure": "^1.4.7",
"html-webpack-plugin": "^2.28.0", "react-testutils-additions": "^15.2.0",
"jest-cli": "^21.2.1", "react-transition-group": "^1.1.2",
"jsen": "^0.6.4", "rimraf": "^2.6.1",
"json-loader": "^0.5.4", "rollup": "0.41",
"less": "^2.7.2", "rollup-plugin-node-resolve": "3",
"less-loader": "^4.0.3", "style-loader": "^0.16.1",
"react-addons-perf": "^15.4.2", "url-loader": "^0.5.8",
"react-container-dimensions": "^1.4.1", "webpack": "^2.4.1",
"react-testutils-additions": "^15.2.0", "webpack-dev-server": "^2.4.4",
"react-transition-group": "^1.1.2", "uglify-js": "^2.4.11"
"rimraf": "^2.6.1", },
"rollup": "0.41", "dependencies": {
"rollup-plugin-node-resolve": "3", "axios": "^0.17.1",
"style-loader": "^0.16.1", "babel-polyfill": "*",
"uglify-js": "^2.4.11", "browserify-zlib-next": "^1.0.1",
"url-loader": "^0.5.8", "classnames": "^2.2.5",
"webpack": "^2.4.1", "coriolis-data": "../coriolis-data",
"webpack-bugsnag-plugins": "^1.1.1", "d3": "4.8.0",
"webpack-dev-server": "^2.4.4", "detect-browser": "^1.7.0",
"webpack-notifier": "^1.6.0", "fbemitter": "^2.1.1",
"workbox-webpack-plugin": "^3.4.1" "lodash": "^4.17.4",
}, "lz-string": "^1.4.4",
"dependencies": { "pako": "^1.0.6",
"babel-polyfill": "*", "prop-types": "^15.5.8",
"browserify-zlib-next": "^1.0.1", "react": "^15.5.4",
"classnames": "^2.2.5", "react-dom": "^15.5.4",
"coriolis-data": "../coriolis-data", "react-number-editor": "Athanasius/react-number-editor.git#miggy",
"d3": "4.8.0", "recharts": "^0.22.3",
"detect-browser": "^1.7.0", "superagent": "^3.5.2"
"fbemitter": "^2.1.1", }
"lodash": "^4.17.10", }
"lz-string": "^1.4.4",
"pako": "^1.0.6",
"prop-types": "^15.5.8",
"react": "^15.5.4",
"react-dom": "^15.5.4",
"react-ga": "^2.5.3",
"react-number-editor": "Athanasius/react-number-editor.git#miggy",
"recharts": "^0.22.3",
"superagent": "^3.5.2"
}
}

View File

@@ -12,7 +12,6 @@ import ModalHelp from './components/ModalHelp';
import ModalImport from './components/ModalImport'; import ModalImport from './components/ModalImport';
import ModalPermalink from './components/ModalPermalink'; import ModalPermalink from './components/ModalPermalink';
import * as CompanionApiUtils from './utils/CompanionApiUtils'; import * as CompanionApiUtils from './utils/CompanionApiUtils';
import * as JournalUtils from './utils/JournalUtils';
import AboutPage from './pages/AboutPage'; import AboutPage from './pages/AboutPage';
import NotFoundPage from './pages/NotFoundPage'; import NotFoundPage from './pages/NotFoundPage';
@@ -93,14 +92,7 @@ export default class Coriolis extends React.Component {
// Need to decode and gunzip the data, then build the ship // Need to decode and gunzip the data, then build the ship
const data = zlib.inflate(new Buffer(r.params.data, 'base64'), { to: 'string' }); const data = zlib.inflate(new Buffer(r.params.data, 'base64'), { to: 'string' });
const json = JSON.parse(data); const json = JSON.parse(data);
console.info('Ship import data: '); const ship = CompanionApiUtils.shipFromJson(json);
console.info(json);
let ship;
if (json && json.modules) {
ship = CompanionApiUtils.shipFromJson(json);
} else if (json && json.Modules) {
ship = JournalUtils.shipFromLoadoutJSON(json);
}
r.params.ship = ship.id; r.params.ship = ship.id;
r.params.code = ship.toString(); r.params.code = ship.toString();
this._setPage(OutfittingPage, r); this._setPage(OutfittingPage, r);
@@ -134,9 +126,9 @@ export default class Coriolis extends React.Component {
console && console.error && console.error(arguments); // eslint-disable-line no-console console && console.error && console.error(arguments); // eslint-disable-line no-console
if (errObj) { if (errObj) {
if (errObj instanceof Error) { if (errObj instanceof Error) {
bugsnagClient.notify(errObj); // eslint-disable-line Bugsnag.notifyException(errObj) // eslint-disable-line
} else if (errObj instanceof String) { } else if (errObj instanceof String) {
bugsnagClient.notify(msg, errObj); // eslint-disable-line Bugsnag.notify(msg, errObj) // eslint-disable-line
} }
} }
this.setState({ this.setState({
@@ -180,13 +172,13 @@ export default class Coriolis extends React.Component {
case 72: // 'h' case 72: // 'h'
if (e.ctrlKey || e.metaKey) { // CTRL/CMD + h if (e.ctrlKey || e.metaKey) { // CTRL/CMD + h
e.preventDefault(); e.preventDefault();
this._showModal(<ModalHelp/>); this._showModal(<ModalHelp />);
} }
break; break;
case 73: // 'i' case 73: // 'i'
if (e.ctrlKey || e.metaKey) { // CTRL/CMD + i if (e.ctrlKey || e.metaKey) { // CTRL/CMD + i
e.preventDefault(); e.preventDefault();
this._showModal(<ModalImport/>); this._showModal(<ModalImport />);
} }
break; break;
case 79: // 'o' case 79: // 'o'
@@ -208,7 +200,7 @@ export default class Coriolis extends React.Component {
* @param {React.Component} content Modal Content * @param {React.Component} content Modal Content
*/ */
_showModal(content) { _showModal(content) {
let modal = <div className='modal-bg' onClick={(e) => this._hideModal()}>{content}</div>; let modal = <div className='modal-bg' onClick={(e) => this._hideModal() }>{content}</div>;
this.setState({ modal }); this.setState({ modal });
} }
@@ -286,7 +278,7 @@ export default class Coriolis extends React.Component {
return this.emitter.addListener('windowResize', listener); return this.emitter.addListener('windowResize', listener);
} }
/** /**
* Add a listener to global commands such as save, * Add a listener to global commands such as save,
* @param {Function} listener Listener callback * @param {Function} listener Listener callback
* @return {Object} Subscription token * @return {Object} Subscription token
@@ -322,60 +314,14 @@ export default class Coriolis extends React.Component {
*/ */
componentWillMount() { componentWillMount() {
// Listen for appcache updated event, present refresh to update view // Listen for appcache updated event, present refresh to update view
// Check that service workers are registered if (window.applicationCache) {
if (navigator.storage && navigator.storage.persist) { window.applicationCache.addEventListener('updateready', () => {
window.addEventListener('load', () => { if (window.applicationCache.status == window.applicationCache.UPDATEREADY) {
navigator.storage.persist().then(granted => { this.setState({ appCacheUpdate: true }); // Browser downloaded a new app cache.
if (granted) }
console.log('Storage will not be cleared except by explicit user action');
else
console.log('Storage may be cleared by the UA under storage pressure.');
});
}); });
} }
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
// Your service-worker.js *must* be located at the top-level directory relative to your site.
// It won't be able to control pages unless it's located at the same level or higher than them.
// *Don't* register service worker file in, e.g., a scripts/ sub-directory!
// See https://github.com/slightlyoff/ServiceWorker/issues/468
const self = this;
navigator.serviceWorker.register('/service-worker.js').then(function(reg) {
// updatefound is fired if service-worker.js changes.
reg.onupdatefound = function() {
// The updatefound event implies that reg.installing is set; see
// https://slightlyoff.github.io/ServiceWorker/spec/service_worker/index.html#service-worker-container-updatefound-event
var installingWorker = reg.installing;
installingWorker.onstatechange = function() {
switch (installingWorker.state) {
case 'installed':
if (navigator.serviceWorker.controller) {
// At this point, the old content will have been purged and the fresh content will
// have been added to the cache.
// It's the perfect time to display a "New content is available; please refresh."
// message in the page's interface.
console.log('New or updated content is available.');
self.setState({ appCacheUpdate: true }); // Browser downloaded a new app cache.
} else {
// At this point, everything has been precached.
// It's the perfect time to display a "Content is cached for offline use." message.
console.log('Content is now available offline!');
self.setState({ appCacheUpdate: true }); // Browser downloaded a new app cache.
}
break;
case 'redundant':
console.error('The installing service worker became redundant.');
break;
}
};
};
}).catch(function(e) {
console.error('Error during service worker registration:', e);
});
});
}
window.onerror = this._onError.bind(this); window.onerror = this._onError.bind(this);
window.addEventListener('resize', () => this.emitter.emit('windowResize')); window.addEventListener('resize', () => this.emitter.emit('windowResize'));
document.getElementById('coriolis').addEventListener('scroll', () => this._tooltip()); document.getElementById('coriolis').addEventListener('scroll', () => this._tooltip());
@@ -393,22 +339,14 @@ export default class Coriolis extends React.Component {
render() { render() {
let currentMenu = this.state.currentMenu; let currentMenu = this.state.currentMenu;
return <div style={{ minHeight: '100%' }} onClick={this._closeMenu} return <div style={{ minHeight: '100%' }} onClick={this._closeMenu} className={ this.state.noTouch ? 'no-touch' : null }>
className={this.state.noTouch ? 'no-touch' : null}> <Header appCacheUpdate={this.state.appCacheUpdate} currentMenu={currentMenu} />
<Header appCacheUpdate={this.state.appCacheUpdate} currentMenu={currentMenu}/> { this.state.error ? this.state.error : this.state.page ? React.createElement(this.state.page, { currentMenu }) : <NotFoundPage/> }
{this.state.error ? this.state.error : this.state.page ? React.createElement(this.state.page, { currentMenu }) : { this.state.modal }
<NotFoundPage/>} { this.state.tooltip }
{this.state.modal}
{this.state.tooltip}
<footer> <footer>
<div className="right cap"> <div className="right cap">
<a href="https://github.com/EDCD/coriolis" target="_blank" <a href="https://github.com/EDCD/coriolis" target="_blank" title="Coriolis Github Project">{window.CORIOLIS_VERSION} - {window.CORIOLIS_DATE}</a>
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> </div>
</footer> </footer>
</div>; </div>;

View File

@@ -1,6 +1,5 @@
import Persist from './stores/Persist'; import Persist from './stores/Persist';
import ReactGA from 'react-ga';
ReactGA.initialize('UA-55840909-18');
let standalone = undefined; let standalone = undefined;
/** /**
@@ -258,16 +257,9 @@ Route.prototype.match = function(path, params) {
* @param {string} path Path to track * @param {string} path Path to track
*/ */
function gaTrack(path) { function gaTrack(path) {
const match = path.match(/\/outfit\/(.*)(\?code=.*)/); if (window.ga) {
if (match) { window.ga('send', 'pageview', path);
if (match[1]) {
ReactGA.ga('set', 'contentGroup1', match[1]);
}
if (match[2]) {
ReactGA.ga('set', 'contentGroup2', match[2]);
}
} }
ReactGA.pageview(path);
} }
/** /**

View File

@@ -44,13 +44,6 @@ const GRPCAT = {
'rg': 'projectiles', 'rg': 'projectiles',
'mr': 'ordnance', 'mr': 'ordnance',
'axmr': 'experimental', 'axmr': 'experimental',
'rcpl': 'experimental',
'dtl': 'experimental',
'tbsc': 'experimental',
'tbem': 'experimental',
'tbrfl': 'experimental',
'mahr': 'experimental',
'rsl': 'experimental',
'tp': 'ordnance', 'tp': 'ordnance',
'nl': 'ordnance', 'nl': 'ordnance',
'sc': 'scanners', 'sc': 'scanners',
@@ -63,16 +56,7 @@ const GRPCAT = {
'ch': 'defence', 'ch': 'defence',
'po': 'defence', 'po': 'defence',
'ec': 'defence', 'ec': 'defence',
'sfn': 'defence', 'sfn': 'defence'
// Guardian
'gpp': 'guardian',
'gpc': 'guardian',
'gsrp': 'guardian',
'ggc': 'guardian',
'gfsb': 'guardian',
'gmrp': 'guardian',
'gsc': 'guardian',
'ghrp': 'guardian'
}; };
// Order here is the order in which items will be shown in the modules menu // Order here is the order in which items will be shown in the modules menu
const CATEGORIES = { const CATEGORIES = {
@@ -98,10 +82,7 @@ const CATEGORIES = {
'defence': ['ch', 'po', 'ec'], 'defence': ['ch', 'po', 'ec'],
'scanners': ['sc', 'ss', 'cs', 'kw', 'ws'], // Overloaded with internal scanners 'scanners': ['sc', 'ss', 'cs', 'kw', 'ws'], // Overloaded with internal scanners
// Experimental // Experimental
'experimental': ['axmc', 'axmr', 'rfl', 'tbrfl', 'tbsc', 'tbem', 'xs', 'sfn', 'rcpl', 'dtl', 'rsl', 'mahr', ], 'experimental': ['axmc', 'axmr', 'rfl', 'xs', 'sfn']
// Guardian
'guardian': ['gpp', 'gpd', 'gpc', 'ggc', 'gsrp', 'gfsb', 'ghrp', 'gmrp', 'gsc']
}; };
/** /**
@@ -115,11 +96,7 @@ export default class AvailableModulesMenu extends TranslatedComponent {
diffDetails: PropTypes.func, diffDetails: PropTypes.func,
m: PropTypes.object, m: PropTypes.object,
shipMass: PropTypes.number, shipMass: PropTypes.number,
warning: PropTypes.func, warning: PropTypes.func
firstSlotId: PropTypes.string,
lastSlotId: PropTypes.string,
activeSlotId: PropTypes.string,
slotDiv: PropTypes.object
}; };
static defaultProps = { static defaultProps = {
@@ -135,7 +112,6 @@ export default class AvailableModulesMenu extends TranslatedComponent {
super(props); super(props);
this._hideDiff = this._hideDiff.bind(this); this._hideDiff = this._hideDiff.bind(this);
this.state = this._initState(props, context); this.state = this._initState(props, context);
this.slotItems = [];// Array to hold <li> refs.
} }
/** /**
@@ -146,9 +122,8 @@ export default class AvailableModulesMenu extends TranslatedComponent {
*/ */
_initState(props, context) { _initState(props, context) {
let translate = context.language.translate; let translate = context.language.translate;
let { m, warning, shipMass, onSelect, modules, firstSlotId, lastSlotId } = props; let { m, warning, shipMass, onSelect, modules } = props;
let list, currentGroup; let list, currentGroup;
let buildGroup = this._buildGroup.bind( let buildGroup = this._buildGroup.bind(
this, this,
translate, translate,
@@ -159,7 +134,7 @@ export default class AvailableModulesMenu extends TranslatedComponent {
this._hideDiff(event); this._hideDiff(event);
onSelect(m); onSelect(m);
} }
); );
if (modules instanceof Array) { if (modules instanceof Array) {
list = buildGroup(modules[0].grp, modules); list = buildGroup(modules[0].grp, modules);
@@ -167,10 +142,7 @@ export default class AvailableModulesMenu extends TranslatedComponent {
list = []; list = [];
// At present time slots with grouped options (Hardpoints and Internal) can be empty // At present time slots with grouped options (Hardpoints and Internal) can be empty
if (m) { if (m) {
let emptyId = 'empty'; list.push(<div className='empty-c upp' key='empty' onClick={onSelect.bind(null, null)} >{translate('empty')}</div>);
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 // Need to regroup the modules by our own categorisation
@@ -220,40 +192,37 @@ export default class AvailableModulesMenu extends TranslatedComponent {
} }
} }
} }
let trackingFocus = false;
return { list, currentGroup, trackingFocus }; return { list, currentGroup };
} }
/** /**
* Generate React Components for Module Group * Generate React Components for Module Group
* @param {Function} translate Translate function * @param {Function} translate Translate function
* @param {Object} mountedModule Mounted Module * @param {Objecy} mountedModule Mounted Module
* @param {Function} warningFunc Warning function * @param {Funciton} warningFunc Warning function
* @param {number} mass Mass * @param {number} mass Mass
* @param {function} onSelect Select/Mount callback * @param {function} onSelect Select/Mount callback
* @param {string} grp Group name * @param {string} grp Group name
* @param {Array} modules Available modules * @param {Array} modules Available modules
* @param {string} firstSlotId id of first slot item
* @param {string} lastSlotId id of last slot item
* @return {React.Component} Available Module Group contents * @return {React.Component} Available Module Group contents
*/ */
_buildGroup(translate, mountedModule, warningFunc, mass, onSelect, grp, modules, firstSlotId, lastSlotId) { _buildGroup(translate, mountedModule, warningFunc, mass, onSelect, grp, modules) {
let prevClass = null, prevRating = null, prevName; let prevClass = null, prevRating = null;
let elems = []; let elems = [];
const sortedModules = modules.sort(this._moduleOrder); 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 // 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 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])); const itemsPerClass = Math.max.apply(null, Object.keys(tmp).map(key => tmp[key]));
let itemsOnThisRow = 0; let itemsOnThisRow = 0;
for (let i = 0; i < sortedModules.length; i++) { for (let i = 0; i < sortedModules.length; i++) {
let m = sortedModules[i]; let m = sortedModules[i];
let mount = null; let mount = null;
let disabled = false; let disabled = false;
prevName = m.name;
if (ModuleUtils.isShieldGenerator(m.grp)) { if (ModuleUtils.isShieldGenerator(m.grp)) {
// Shield generators care about maximum hull mass // Shield generators care about maximum hull mass
disabled = mass > m.maxmass; disabled = mass > m.maxmass;
@@ -269,21 +238,9 @@ export default class AvailableModulesMenu extends TranslatedComponent {
}); });
let eventHandlers; let eventHandlers;
if (disabled) { if (disabled || active) {
eventHandlers = { eventHandlers = {};
onKeyDown: this._keyDown.bind(this, null),
onKeyUp: this._keyUp.bind(this, null)
};
} else { } 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 showDiff = this._showDiff.bind(this, mountedModule, m);
let select = onSelect.bind(null, m); let select = onSelect.bind(null, m);
@@ -292,9 +249,7 @@ export default class AvailableModulesMenu extends TranslatedComponent {
onTouchStart: this._touchStart.bind(this, showDiff), onTouchStart: this._touchStart.bind(this, showDiff),
onTouchEnd: this._touchEnd.bind(this, select), onTouchEnd: this._touchEnd.bind(this, select),
onMouseLeave: this._hideDiff, onMouseLeave: this._hideDiff,
onClick: select, onClick: select
onKeyDown: this._keyDown.bind(this, select),
onKeyUp: this._keyUp.bind(this, select)
}; };
} }
@@ -303,28 +258,24 @@ export default class AvailableModulesMenu extends TranslatedComponent {
case 'G': mount = <MountGimballed className={'lg'}/>; break; case 'G': mount = <MountGimballed className={'lg'}/>; break;
case 'T': mount = <MountTurret 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)) { 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} />); elems.push(<br key={'b' + m.grp + i} />);
itemsOnThisRow = 0; itemsOnThisRow = 0;
} }
let tbIdx = (classes.indexOf('disabled') < 0) ? 0 : undefined;
elems.push( elems.push(
<li key={m.id} data-id={m.id} className={classes} {...eventHandlers} tabIndex={tbIdx} ref={slotItem => this.slotItems[m.id] = slotItem}> <li key={m.id} className={classes} {...eventHandlers}>
{mount} {mount}
{(mount ? ' ' : '') + m.class + m.rating + (m.missile ? '/' + m.missile : '') + (m.name ? ' ' + translate(m.name) : '')} {(mount ? ' ' : '') + m.class + m.rating + (m.missile ? '/' + m.missile : '') + (m.name ? ' ' + translate(m.name) : '')}
</li> </li>
); );
itemsOnThisRow++; itemsOnThisRow++;
prevClass = m.class; prevClass = m.class;
prevRating = m.rating; prevRating = m.rating;
prevName = m.name;
} }
return <ul key={'modules' + grp}>{elems}</ul>;
return <ul key={'modules' + grp} >{elems}</ul>;
} }
/** /**
@@ -375,41 +326,6 @@ export default class AvailableModulesMenu extends TranslatedComponent {
this._hideDiff(); this._hideDiff();
} }
/**
* Key down - select module on Enter key, move to next/previous module on Tab/Shift-Tab, close on Esc
* @param {Function} select Select module callback
* @param {SyntheticEvent} event Event
*/
_keyDown(select, event) {
let className = event.currentTarget.attributes['class'].value;
if (event.key == 'Enter' && className.indexOf('disabled') < 0 && className.indexOf('active') < 0) {
select();
return;
}
let elemId = event.currentTarget.attributes['data-id'].value;
if (className.indexOf('disabled') < 0 && event.key == 'Tab') {
if (event.shiftKey && elemId == this.firstSlotId) {
event.preventDefault();
this.slotItems[this.lastSlotId].focus();
return;
}
if (!event.shiftKey && elemId == this.lastSlotId) {
event.preventDefault();
this.slotItems[this.firstSlotId].focus();
return;
}
}
}
/**
* Key Up
* @param {Function} select Select module callback
* @param {SytheticEvent} event Event
*/
_keyUp(select,event) {
// nothing here yet
}
/** /**
* Hide diff tooltip * Hide diff tooltip
* @param {SyntheticEvent} event Event * @param {SyntheticEvent} event Event
@@ -467,23 +383,6 @@ export default class AvailableModulesMenu extends TranslatedComponent {
if (this.groupElem) { // Scroll to currently selected group if (this.groupElem) { // Scroll to currently selected group
this.node.scrollTop = this.groupElem.offsetTop; 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();
}
}
/**
* Handle focus if the component updates
*
*/
componentWillUnmount() {
if(this.props.slotDiv) {
this.props.slotDiv.focus();
}
} }
/** /**

View File

@@ -76,7 +76,6 @@ export default class Defence extends TranslatedComponent {
shieldSourcesData.push({ value: Math.round(shield.generator), label: translate('generator') }); shieldSourcesData.push({ value: Math.round(shield.generator), label: translate('generator') });
shieldSourcesData.push({ value: Math.round(shield.boosters), label: translate('boosters') }); shieldSourcesData.push({ value: Math.round(shield.boosters), label: translate('boosters') });
shieldSourcesData.push({ value: Math.round(shield.cells), label: translate('cells') }); shieldSourcesData.push({ value: Math.round(shield.cells), label: translate('cells') });
shieldSourcesData.push({ value: Math.round(shield.addition), label: translate('shield addition') });
if (shield.generator > 0) { if (shield.generator > 0) {
shieldSourcesTt.push(<div key='generator'>{translate('generator') + ' ' + formats.int(shield.generator)}{units.MJ}</div>); shieldSourcesTt.push(<div key='generator'>{translate('generator') + ' ' + formats.int(shield.generator)}{units.MJ}</div>);
@@ -102,19 +101,19 @@ export default class Defence extends TranslatedComponent {
// Add effective shield from resistances // Add effective shield from resistances
const rawMj = shield.generator + shield.boosters + shield.cells; const rawMj = shield.generator + shield.boosters + shield.cells;
const explosiveMj = rawMj / (shield.explosive.base) - rawMj; const explosiveMj = rawMj / (shield.explosive.generator * shield.explosive.boosters) - rawMj;
if (explosiveMj != 0) effectiveShieldExplosiveTt.push(<div key='resistance'>{translate('resistance') + ' ' + formats.int(explosiveMj)}{units.MJ}</div>); if (explosiveMj != 0) effectiveShieldExplosiveTt.push(<div key='resistance'>{translate('resistance') + ' ' + formats.int(explosiveMj)}{units.MJ}</div>);
const kineticMj = rawMj / (shield.kinetic.base) - rawMj; const kineticMj = rawMj / (shield.kinetic.generator * shield.kinetic.boosters) - rawMj;
if (kineticMj != 0) effectiveShieldKineticTt.push(<div key='resistance'>{translate('resistance') + ' ' + formats.int(kineticMj)}{units.MJ}</div>); if (kineticMj != 0) effectiveShieldKineticTt.push(<div key='resistance'>{translate('resistance') + ' ' + formats.int(kineticMj)}{units.MJ}</div>);
const thermalMj = rawMj / (shield.thermal.base) - rawMj; const thermalMj = rawMj / (shield.thermal.generator * shield.thermal.boosters) - rawMj;
if (thermalMj != 0) effectiveShieldThermalTt.push(<div key='resistance'>{translate('resistance') + ' ' + formats.int(thermalMj)}{units.MJ}</div>); if (thermalMj != 0) effectiveShieldThermalTt.push(<div key='resistance'>{translate('resistance') + ' ' + formats.int(thermalMj)}{units.MJ}</div>);
// Add effective shield from power distributor SYS pips // Add effective shield from power distributor SYS pips
if (shield.absolute.sys != 1) { if (shield.absolute.sys != 1) {
effectiveShieldAbsoluteTt.push(<div key='power distributor'>{translate('power distributor') + ' ' + formats.int(rawMj / shield.absolute.total - rawMj)}{units.MJ}</div>); effectiveShieldAbsoluteTt.push(<div key='power distributor'>{translate('power distributor') + ' ' + formats.int(rawMj / shield.absolute.sys - rawMj)}{units.MJ}</div>);
effectiveShieldExplosiveTt.push(<div key='power distributor'>{translate('power distributor') + ' ' + formats.int(rawMj / shield.explosive.total - rawMj / shield.explosive.base)}{units.MJ}</div>); effectiveShieldExplosiveTt.push(<div key='power distributor'>{translate('power distributor') + ' ' + formats.int(rawMj / shield.explosive.sys - rawMj)}{units.MJ}</div>);
effectiveShieldKineticTt.push(<div key='power distributor'>{translate('power distributor') + ' ' + formats.int(rawMj / shield.kinetic.total - rawMj / shield.kinetic.base)}{units.MJ}</div>); effectiveShieldKineticTt.push(<div key='power distributor'>{translate('power distributor') + ' ' + formats.int(rawMj / shield.kinetic.sys - rawMj)}{units.MJ}</div>);
effectiveShieldThermalTt.push(<div key='power distributor'>{translate('power distributor') + ' ' + formats.int(rawMj / shield.thermal.total - rawMj / shield.thermal.base)}{units.MJ}</div>); effectiveShieldThermalTt.push(<div key='power distributor'>{translate('power distributor') + ' ' + formats.int(rawMj / shield.thermal.sys - rawMj)}{units.MJ}</div>);
} }
} }
@@ -160,21 +159,18 @@ export default class Defence extends TranslatedComponent {
const effectiveArmourExplosiveTt = []; const effectiveArmourExplosiveTt = [];
const effectiveArmourKineticTt = []; const effectiveArmourKineticTt = [];
const effectiveArmourThermalTt = []; const effectiveArmourThermalTt = [];
const effectiveArmourCausticTt = [];
if (armour.bulkheads > 0) { if (armour.bulkheads > 0) {
armourSourcesTt.push(<div key='bulkheads'>{translate('bulkheads') + ' ' + formats.int(armour.bulkheads)}</div>); armourSourcesTt.push(<div key='bulkheads'>{translate('bulkheads') + ' ' + formats.int(armour.bulkheads)}</div>);
effectiveArmourAbsoluteTt.push(<div key='bulkheads'>{translate('bulkheads') + ' ' + formats.int(armour.bulkheads)}</div>); effectiveArmourAbsoluteTt.push(<div key='bulkheads'>{translate('bulkheads') + ' ' + formats.int(armour.bulkheads)}</div>);
effectiveArmourExplosiveTt.push(<div key='bulkheads'>{translate('bulkheads') + ' ' + formats.int(armour.bulkheads)}</div>); effectiveArmourExplosiveTt.push(<div key='bulkheads'>{translate('bulkheads') + ' ' + formats.int(armour.bulkheads)}</div>);
effectiveArmourKineticTt.push(<div key='bulkheads'>{translate('bulkheads') + ' ' + formats.int(armour.bulkheads)}</div>); effectiveArmourKineticTt.push(<div key='bulkheads'>{translate('bulkheads') + ' ' + formats.int(armour.bulkheads)}</div>);
effectiveArmourThermalTt.push(<div key='bulkheads'>{translate('bulkheads') + ' ' + formats.int(armour.bulkheads)}</div>); effectiveArmourThermalTt.push(<div key='bulkheads'>{translate('bulkheads') + ' ' + formats.int(armour.bulkheads)}</div>);
effectiveArmourCausticTt.push(<div key='bulkheads'>{translate('bulkheads') + ' ' + formats.int(armour.bulkheads)}</div>);
if (armour.reinforcement > 0) { if (armour.reinforcement > 0) {
armourSourcesTt.push(<div key='reinforcement'>{translate('reinforcement') + ' ' + formats.int(armour.reinforcement)}</div>); armourSourcesTt.push(<div key='reinforcement'>{translate('reinforcement') + ' ' + formats.int(armour.reinforcement)}</div>);
effectiveArmourAbsoluteTt.push(<div key='reinforcement'>{translate('reinforcement') + ' ' + formats.int(armour.reinforcement)}</div>); effectiveArmourAbsoluteTt.push(<div key='reinforcement'>{translate('reinforcement') + ' ' + formats.int(armour.reinforcement)}</div>);
effectiveArmourExplosiveTt.push(<div key='reinforcement'>{translate('reinforcement') + ' ' + formats.int(armour.reinforcement)}</div>); effectiveArmourExplosiveTt.push(<div key='reinforcement'>{translate('reinforcement') + ' ' + formats.int(armour.reinforcement)}</div>);
effectiveArmourKineticTt.push(<div key='reinforcement'>{translate('reinforcement') + ' ' + formats.int(armour.reinforcement)}</div>); effectiveArmourKineticTt.push(<div key='reinforcement'>{translate('reinforcement') + ' ' + formats.int(armour.reinforcement)}</div>);
effectiveArmourThermalTt.push(<div key='reinforcement'>{translate('reinforcement') + ' ' + formats.int(armour.reinforcement)}</div>); effectiveArmourThermalTt.push(<div key='reinforcement'>{translate('reinforcement') + ' ' + formats.int(armour.reinforcement)}</div>);
effectiveArmourCausticTt.push(<div key='reinforcement'>{translate('reinforcement') + ' ' + formats.int(armour.reinforcement)}</div>);
} }
} }
@@ -187,22 +183,17 @@ export default class Defence extends TranslatedComponent {
const armourDamageTakenExplosiveTt = []; const armourDamageTakenExplosiveTt = [];
armourDamageTakenExplosiveTt.push(<div key='bulkheads'>{translate('bulkheads') + ' ' + formats.pct1(armour.explosive.bulkheads)}</div>); armourDamageTakenExplosiveTt.push(<div key='bulkheads'>{translate('bulkheads') + ' ' + formats.pct1(armour.explosive.bulkheads)}</div>);
armourDamageTakenExplosiveTt.push(<div key='reinforcement'>{translate('reinforcement') + ' ' + formats.pct1(armour.explosive.reinforcement)}</div>); armourDamageTakenExplosiveTt.push(<div key='reinforcement'>{translate('reinforcement') + ' ' + formats.pct1(armour.explosive.reinforcement)}</div>);
if (armour.explosive.total != 1) effectiveArmourExplosiveTt.push(<div key='resistance'>{translate('resistance') + ' ' + formats.int(rawArmour / armour.explosive.total - rawArmour)}</div>); if (armour.explosive.bulkheads * armour.explosive.reinforcement != 1) effectiveArmourExplosiveTt.push(<div key='resistance'>{translate('resistance') + ' ' + formats.int(rawArmour / (armour.explosive.bulkheads * armour.explosive.reinforcement) - rawArmour)}</div>);
const armourDamageTakenKineticTt = []; const armourDamageTakenKineticTt = [];
armourDamageTakenKineticTt.push(<div key='bulkheads'>{translate('bulkheads') + ' ' + formats.pct1(armour.kinetic.bulkheads)}</div>); armourDamageTakenKineticTt.push(<div key='bulkheads'>{translate('bulkheads') + ' ' + formats.pct1(armour.kinetic.bulkheads)}</div>);
armourDamageTakenKineticTt.push(<div key='reinforcement'>{translate('reinforcement') + ' ' + formats.pct1(armour.kinetic.reinforcement)}</div>); armourDamageTakenKineticTt.push(<div key='reinforcement'>{translate('reinforcement') + ' ' + formats.pct1(armour.kinetic.reinforcement)}</div>);
if (armour.kinetic.total != 1) effectiveArmourKineticTt.push(<div key='resistance'>{translate('resistance') + ' ' + formats.int(rawArmour / armour.kinetic.total - rawArmour)}</div>); if (armour.kinetic.bulkheads * armour.kinetic.reinforcement != 1) effectiveArmourKineticTt.push(<div key='resistance'>{translate('resistance') + ' ' + formats.int(rawArmour / (armour.kinetic.bulkheads * armour.kinetic.reinforcement) - rawArmour)}</div>);
const armourDamageTakenThermalTt = []; const armourDamageTakenThermalTt = [];
armourDamageTakenThermalTt.push(<div key='bulkheads'>{translate('bulkheads') + ' ' + formats.pct1(armour.thermal.bulkheads)}</div>); armourDamageTakenThermalTt.push(<div key='bulkheads'>{translate('bulkheads') + ' ' + formats.pct1(armour.thermal.bulkheads)}</div>);
armourDamageTakenThermalTt.push(<div key='reinforcement'>{translate('reinforcement') + ' ' + formats.pct1(armour.thermal.reinforcement)}</div>); armourDamageTakenThermalTt.push(<div key='reinforcement'>{translate('reinforcement') + ' ' + formats.pct1(armour.thermal.reinforcement)}</div>);
if (armour.thermal.total != 1) effectiveArmourThermalTt.push(<div key='resistance'>{translate('resistance') + ' ' + formats.int(rawArmour / armour.thermal.total - rawArmour)}</div>); if (armour.thermal.bulkheads * armour.thermal.reinforcement != 1) effectiveArmourThermalTt.push(<div key='resistance'>{translate('resistance') + ' ' + formats.int(rawArmour / (armour.thermal.bulkheads * armour.thermal.reinforcement) - rawArmour)}</div>);
const armourDamageTakenCausticTt = [];
armourDamageTakenCausticTt.push(<div key='bulkheads'>{translate('bulkheads') + ' ' + formats.pct1(armour.caustic.bulkheads)}</div>);
armourDamageTakenCausticTt.push(<div key='reinforcement'>{translate('reinforcement') + ' ' + formats.pct1(armour.caustic.reinforcement)}</div>);
if (armour.thermal.total != 1) effectiveArmourCausticTt.push(<div key='resistance'>{translate('resistance') + ' ' + formats.int(rawArmour / armour.caustic.total - rawArmour)}</div>);
const effectiveArmourData = []; const effectiveArmourData = [];
const effectiveAbsoluteArmour = armour.total / armour.absolute.total; const effectiveAbsoluteArmour = armour.total / armour.absolute.total;
@@ -213,15 +204,12 @@ export default class Defence extends TranslatedComponent {
effectiveArmourData.push({ value: Math.round(effectiveKineticArmour), label: translate('kinetic'), tooltip: effectiveArmourKineticTt }); effectiveArmourData.push({ value: Math.round(effectiveKineticArmour), label: translate('kinetic'), tooltip: effectiveArmourKineticTt });
const effectiveThermalArmour = armour.total / armour.thermal.total; const effectiveThermalArmour = armour.total / armour.thermal.total;
effectiveArmourData.push({ value: Math.round(effectiveThermalArmour), label: translate('thermal'), tooltip: effectiveArmourThermalTt }); effectiveArmourData.push({ value: Math.round(effectiveThermalArmour), label: translate('thermal'), tooltip: effectiveArmourThermalTt });
const effectiveCausticArmour = armour.total / armour.caustic.total;
effectiveArmourData.push({ value: Math.round(effectiveCausticArmour), label: translate('caustic'), tooltip: effectiveArmourCausticTt });
const armourDamageTakenData = []; const armourDamageTakenData = [];
armourDamageTakenData.push({ value: Math.round(armour.absolute.total * 100), label: translate('absolute'), tooltip: armourDamageTakenTt }); armourDamageTakenData.push({ value: Math.round(armour.absolute.total * 100), label: translate('absolute'), tooltip: armourDamageTakenTt });
armourDamageTakenData.push({ value: Math.round(armour.explosive.total * 100), label: translate('explosive'), tooltip: armourDamageTakenExplosiveTt }); armourDamageTakenData.push({ value: Math.round(armour.explosive.total * 100), label: translate('explosive'), tooltip: armourDamageTakenExplosiveTt });
armourDamageTakenData.push({ value: Math.round(armour.kinetic.total * 100), label: translate('kinetic'), tooltip: armourDamageTakenKineticTt }); armourDamageTakenData.push({ value: Math.round(armour.kinetic.total * 100), label: translate('kinetic'), tooltip: armourDamageTakenKineticTt });
armourDamageTakenData.push({ value: Math.round(armour.thermal.total * 100), label: translate('thermal'), tooltip: armourDamageTakenThermalTt }); armourDamageTakenData.push({ value: Math.round(armour.thermal.total * 100), label: translate('thermal'), tooltip: armourDamageTakenThermalTt });
armourDamageTakenData.push({ value: Math.round(armour.caustic.total * 100), label: translate('caustic'), tooltip: armourDamageTakenCausticTt });
return ( return (
<span id='defence'> <span id='defence'>

View File

@@ -57,7 +57,7 @@ export default class FSDProfile extends TranslatedComponent {
*/ */
_calcMaxRange(ship, fuel, mass) { _calcMaxRange(ship, fuel, mass) {
// Obtain the maximum range // Obtain the maximum range
return Calc.jumpRange(mass, ship.standard[2].m, Math.min(fuel, ship.standard[2].m.getMaxFuelPerJump()), ship); return Calc.jumpRange(mass, ship.standard[2].m, Math.min(fuel, ship.standard[2].m.getMaxFuelPerJump()));
} }
/** /**
@@ -77,7 +77,7 @@ export default class FSDProfile extends TranslatedComponent {
const maxMass = thrusters.getMaxMass(); const maxMass = thrusters.getMaxMass();
const mass = ship.unladenMass + fuel + cargo; const mass = ship.unladenMass + fuel + cargo;
const minRange = 0; const minRange = 0;
const maxRange = Calc.jumpRange(minMass + fsd.getMaxFuelPerJump(), fsd, fsd.getMaxFuelPerJump(), ship); const maxRange = Calc.jumpRange(minMass + fsd.getMaxFuelPerJump(), fsd, fsd.getMaxFuelPerJump());
// Add a mark at our current mass // Add a mark at our current mass
const mark = Math.min(mass, maxMass); const mark = Math.min(mass, maxMass);

View File

@@ -79,7 +79,7 @@ export default class HardpointSlot extends Slot {
<div className={'r'}>{formats.round(m.getMass())}{u.T}</div> <div className={'r'}>{formats.round(m.getMass())}{u.T}</div>
</div> </div>
<div className={'cb'}> <div className={'cb'}>
{ m.getDps() ? <div className={'l'} onMouseOver={termtip.bind(null, m.getClip() ? 'dpssdps' : 'dps')} onMouseOut={tooltip.bind(null, null)}>{translate('DPS')}: {formats.round1(m.getDps())} { m.getClip() ? <span>({formats.round1(m.getSDps()) })</span> : null }</div> : null } { m.getDps() ? <div className={'l'} onMouseOver={termtip.bind(null, m.getClip() ? 'dpssdps' : 'dps')} onMouseOut={tooltip.bind(null, null)}>{translate('DPS')}: {formats.round1(m.getDps())} { m.getClip() ? <span>({formats.round1((m.getClip() * m.getDps() / m.getRoF()) / ((m.getClip() / m.getRoF()) + m.getReload())) })</span> : null }</div> : null }
{ m.getEps() ? <div className={'l'} onMouseOver={termtip.bind(null, m.getClip() ? 'epsseps' : 'eps')} onMouseOut={tooltip.bind(null, null)}>{translate('EPS')}: {formats.round1(m.getEps())}{u.MW} { m.getClip() ? <span>({formats.round1((m.getClip() * m.getEps() / m.getRoF()) / ((m.getClip() / m.getRoF()) + m.getReload())) }{u.MW})</span> : null }</div> : null } { m.getEps() ? <div className={'l'} onMouseOver={termtip.bind(null, m.getClip() ? 'epsseps' : 'eps')} onMouseOut={tooltip.bind(null, null)}>{translate('EPS')}: {formats.round1(m.getEps())}{u.MW} { m.getClip() ? <span>({formats.round1((m.getClip() * m.getEps() / m.getRoF()) / ((m.getClip() / m.getRoF()) + m.getReload())) }{u.MW})</span> : null }</div> : null }
{ m.getHps() ? <div className={'l'} onMouseOver={termtip.bind(null, m.getClip() ? 'hpsshps' : 'hps')} onMouseOut={tooltip.bind(null, null)}>{translate('HPS')}: {formats.round1(m.getHps())} { m.getClip() ? <span>({formats.round1((m.getClip() * m.getHps() / m.getRoF()) / ((m.getClip() / m.getRoF()) + m.getReload())) })</span> : null }</div> : null } { m.getHps() ? <div className={'l'} onMouseOver={termtip.bind(null, m.getClip() ? 'hpsshps' : 'hps')} onMouseOut={tooltip.bind(null, null)}>{translate('HPS')}: {formats.round1(m.getHps())} { m.getClip() ? <span>({formats.round1((m.getClip() * m.getHps() / m.getRoF()) / ((m.getClip() / m.getRoF()) + m.getReload())) })</span> : null }</div> : null }
{ m.getDps() && m.getEps() ? <div className={'l'} onMouseOver={termtip.bind(null, 'dpe')} onMouseOut={tooltip.bind(null, null)}>{translate('DPE')}: {formats.f1(m.getDps() / m.getEps())}</div> : null } { m.getDps() && m.getEps() ? <div className={'l'} onMouseOver={termtip.bind(null, 'dpe')} onMouseOut={tooltip.bind(null, null)}>{translate('DPE')}: {formats.f1(m.getDps() / m.getEps())}</div> : null }
@@ -97,12 +97,11 @@ export default class HardpointSlot extends Slot {
{ showModuleResistances && m.getKineticResistance() ? <div className='l'>{translate('kinres')}: {formats.pct(m.getKineticResistance())}</div> : null } { 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 } { 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.getIntegrity() ? <div className='l'>{translate('integrity')}: {formats.int(m.getIntegrity())}</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 } { 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 }
</div> </div>
</div>; </div>;
} else { } else {
return <div className={'empty'}>{translate('empty')}</div>; return <div className={'empty'}>{translate('empty')}</div>;
} }
} }
} }

View File

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

View File

@@ -9,8 +9,6 @@ import { Cogs, CoriolisLogo, Hammer, Help, Rocket, StatsBars } from './SvgIcons'
import { Ships } from 'coriolis-data/dist'; import { Ships } from 'coriolis-data/dist';
import Persist from '../stores/Persist'; import Persist from '../stores/Persist';
import { toDetailedExport } from '../shipyard/Serializer'; import { toDetailedExport } from '../shipyard/Serializer';
import Ship from '../shipyard/Ship';
import ModalBatchOrbis from './ModalBatchOrbis';
import ModalDeleteAll from './ModalDeleteAll'; import ModalDeleteAll from './ModalDeleteAll';
import ModalExport from './ModalExport'; import ModalExport from './ModalExport';
import ModalHelp from './ModalHelp'; import ModalHelp from './ModalHelp';
@@ -237,43 +235,6 @@ export default class Header extends TranslatedComponent {
/>); />);
}; };
/**
* Uploads all ship-builds to orbis
* @param {e} e Event
*/
_uploadAllBuildsToOrbis(e) {
e.preventDefault();
const data = Persist.getBuilds();
let postObject = [];
for (const ship in data) {
for (const code in data[ship]) {
const shipModel = ship;
if (!shipModel) {
throw 'No such ship found: "' + ship + '"';
}
const shipTemplate = Ships[shipModel];
const shipPostObject = {};
let shipInstance = new Ship(shipModel, shipTemplate.properties, shipTemplate.slots);
shipInstance.buildWith(null);
shipInstance.buildFrom(data[ship][code]);
shipPostObject.coriolisId = shipInstance.id;
shipPostObject.coriolisShip = shipInstance;
shipPostObject.coriolisShip.url = window.location.origin + outfitURL(shipModel, data[ship][code], code);
shipPostObject.title = code || shipInstance.id;
shipPostObject.description = code || shipInstance.id;
shipPostObject.ShipName = shipInstance.id;
shipPostObject.Ship = shipInstance.id;
postObject.push(shipPostObject);
}
}
console.log(postObject);
this.context.showModal(<ModalBatchOrbis
ships={postObject}
/>);
}
/** /**
* Show export modal with detailed export * Show export modal with detailed export
* @param {SyntheticEvent} e Event * @param {SyntheticEvent} e Event
@@ -469,7 +430,6 @@ export default class Header extends TranslatedComponent {
{translate('builds')} & {translate('comparisons')} {translate('builds')} & {translate('comparisons')}
<li><Link href="#" className='block' onClick={this._showBackup.bind(this)}>{translate('backup')}</Link></li> <li><Link href="#" className='block' onClick={this._showBackup.bind(this)}>{translate('backup')}</Link></li>
<li><Link href="#" className='block' onClick={this._showDetailedExport.bind(this)}>{translate('detailed export')}</Link></li> <li><Link href="#" className='block' onClick={this._showDetailedExport.bind(this)}>{translate('detailed export')}</Link></li>
<li><Link href="#" className='block' onClick={this._uploadAllBuildsToOrbis.bind(this)}>{translate('upload all builds to orbis')}</Link></li>
<li><Link href="#" className='block' onClick={this._showImport.bind(this)}>{translate('import')}</Link></li> <li><Link href="#" className='block' onClick={this._showImport.bind(this)}>{translate('import')}</Link></li>
<li><Link href="#" className='block' onClick={this._showDeleteAll.bind(this)}>{translate('delete all')}</Link></li> <li><Link href="#" className='block' onClick={this._showDeleteAll.bind(this)}>{translate('delete all')}</Link></li>
</ul> </ul>
@@ -545,9 +505,6 @@ export default class Header extends TranslatedComponent {
return ( return (
<header> <header>
{this.props.appCacheUpdate && <div id="app-update" onClick={() => window.location.reload() }>{translate('PHRASE_UPDATE_RDY')}</div>} {this.props.appCacheUpdate && <div id="app-update" onClick={() => window.location.reload() }>{translate('PHRASE_UPDATE_RDY')}</div>}
{this.props.appCacheUpdate ? <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> <Link className='l' href='/' style={{ marginRight: '1em' }} title='Home'><CoriolisLogo className='icon xl' /></Link>
<div className='l menu'> <div className='l menu'>
@@ -571,16 +528,6 @@ export default class Header extends TranslatedComponent {
{openedMenu == 'comp' ? this._getComparisonsMenu() : null} {openedMenu == 'comp' ? this._getComparisonsMenu() : null}
</div> </div>
{window.location.origin.search('.edcd.io') >= 0 ?
<div className='l menu'>
<a href="https://youtu.be/4SvnLcefhtI" target="_blank">
<div className={cn('menu-header')}>
<Rocket className='warning'/><span className='menu-item-label'>{translate('please migrate to coriolis.io')}</span>
</div>
</a>
</div> : null
}
<div className='r menu'> <div className='r menu'>
<div className={cn('menu-header', { selected: openedMenu == 'settings' })} onClick={this._openSettings}> <div className={cn('menu-header', { selected: openedMenu == 'settings' })} onClick={this._openSettings}>
<Cogs className='xl warning'/><span className='menu-item-label'>{translate('settings')}</span> <Cogs className='xl warning'/><span className='menu-item-label'>{translate('settings')}</span>

View File

@@ -33,9 +33,6 @@ export default class InternalSlot extends Slot {
let modTT = translate('modified'); let modTT = translate('modified');
if (m && m.blueprint && m.blueprint.name) { if (m && m.blueprint && m.blueprint.name) {
modTT = translate(m.blueprint.name) + ' ' + translate('grade') + ' ' + m.blueprint.grade; modTT = translate(m.blueprint.name) + ' ' + translate('grade') + ' ' + m.blueprint.grade;
if (m.blueprint.special && m.blueprint.special.id >= 0) {
modTT += ', ' + translate(m.blueprint.special.name);
}
modTT = ( modTT = (
<div> <div>
<div>{modTT}</div> <div>{modTT}</div>
@@ -63,9 +60,6 @@ export default class InternalSlot extends Slot {
{ m.getSpinup() ? <div className={'l'}>{translate('spinup')}: {formats.f1(m.getSpinup())}{u.s}</div> : null } { m.getSpinup() ? <div className={'l'}>{translate('spinup')}: {formats.f1(m.getSpinup())}{u.s}</div> : null }
{ m.getDuration() ? <div className={'l'}>{translate('duration')}: {formats.f1(m.getDuration())}{u.s}</div> : null } { m.getDuration() ? <div className={'l'}>{translate('duration')}: {formats.f1(m.getDuration())}{u.s}</div> : null }
{ m.grp === 'scb' ? <div className={'l'}>{translate('cells')}: {formats.int(m.getAmmo() + 1)}</div> : null } { m.grp === 'scb' ? <div className={'l'}>{translate('cells')}: {formats.int(m.getAmmo() + 1)}</div> : null }
{ m.grp === 'gsrp' ? <div className={'l'}>{translate('shield addition')}: {formats.f1(m.getShieldAddition())}{u.MJ}</div> : null }
{ m.grp === 'gfsb' ? <div className={'l'}>{translate('jump addition')}: {formats.f1(m.getJumpBoost())}{u.LY}</div> : null }
{ m.grp === 'gs' ? <div className={'l'}>{translate('shield addition')}: {formats.f1(m.getShieldAddition())}{u.MJ}</div> : null }
{ m.getShieldReinforcement() ? <div className={'l'}>{translate('shieldreinforcement')}: {formats.f1(m.getDuration() * m.getShieldReinforcement())}{u.MJ}</div> : null } { m.getShieldReinforcement() ? <div className={'l'}>{translate('shieldreinforcement')}: {formats.f1(m.getDuration() * m.getShieldReinforcement())}{u.MJ}</div> : null }
{ m.getShieldReinforcement() ? <div className={'l'}>{translate('total')}: {formats.int((m.getAmmo() + 1) * (m.getDuration() * m.getShieldReinforcement()))}{u.MJ}</div> : null } { m.getShieldReinforcement() ? <div className={'l'}>{translate('total')}: {formats.int((m.getAmmo() + 1) * (m.getDuration() * m.getShieldReinforcement()))}{u.MJ}</div> : null }
{ m.repair ? <div className={'l'}>{translate('repair')}: {m.repair}</div> : null } { m.repair ? <div className={'l'}>{translate('repair')}: {m.repair}</div> : null }
@@ -73,7 +67,6 @@ export default class InternalSlot extends Slot {
{ m.getRange() ? <div className={'l'}>{translate('range')} {formats.f2(m.getRange())}{u.km}</div> : null } { 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.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.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.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 ? <div className={'l'}>{translate('range')}: {m.rangeLS}{u.Ls}</div> : null }
{ m.rangeLS === null ? <div className={'l'}>{u.Ls}</div> : null } { m.rangeLS === null ? <div className={'l'}>{u.Ls}</div> : null }
@@ -85,11 +78,10 @@ export default class InternalSlot extends Slot {
{ showModuleResistances && m.getExplosiveResistance() ? <div className='l'>{translate('explres')}: {formats.pct(m.getExplosiveResistance())}</div> : null } { showModuleResistances && m.getExplosiveResistance() ? <div className='l'>{translate('explres')}: {formats.pct(m.getExplosiveResistance())}</div> : null }
{ showModuleResistances && m.getKineticResistance() ? <div className='l'>{translate('kinres')}: {formats.pct(m.getKineticResistance())}</div> : null } { 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 } { showModuleResistances && m.getThermalResistance() ? <div className='l'>{translate('thermres')}: {formats.pct(m.getThermalResistance())}</div> : null }
{ showModuleResistances && m.getCausticResistance() ? <div className='l'>{translate('causres')}: {formats.pct(m.getCausticResistance())}</div> : null }
{ m.getHullReinforcement() ? <div className='l'>{translate('armour')}: {formats.int(m.getHullReinforcement() + ship.baseArmour * m.getModValue('hullboost') / 10000)}</div> : null } { 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.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.getIntegrity() ? <div className='l'>{translate('integrity')}: {formats.int(m.getIntegrity())}</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 } { 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 }
</div> </div>
</div>; </div>;
} else { } else {

View File

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

View File

@@ -61,7 +61,7 @@ export default class JumpRange extends TranslatedComponent {
const fuel = this.state.fuelLevel * ship.fuelCapacity; const fuel = this.state.fuelLevel * ship.fuelCapacity;
// Obtain the jump range // Obtain the jump range
return Calc.jumpRange(ship.unladenMass + fuel + cargo, fsd, fuel, ship); return Calc.jumpRange(ship.unladenMass + fuel + cargo, fsd, fuel);
} }
/** /**

View File

@@ -1,281 +1,283 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import ContainerDimensions from 'react-container-dimensions'; import Measure from 'react-measure';
import * as d3 from 'd3'; import * as d3 from 'd3';
import TranslatedComponent from './TranslatedComponent'; import TranslatedComponent from './TranslatedComponent';
const MARGIN = { top: 15, right: 20, bottom: 35, left: 60 }; const MARGIN = { top: 15, right: 20, bottom: 35, left: 60 };
/** /**
* Line Chart * Line Chart
*/ */
export default class LineChart extends TranslatedComponent { export default class LineChart extends TranslatedComponent {
static defaultProps = { static defaultProps = {
code: '', code: '',
xMin: 0, xMin: 0,
yMin: 0, yMin: 0,
points: 20, points: 20,
colors: ['#ff8c0d'], colors: ['#ff8c0d'],
aspect: 0.5 aspect: 0.5
}; };
static propTypes = { static propTypes = {
func: PropTypes.func.isRequired, func: PropTypes.func.isRequired,
xLabel: PropTypes.string.isRequired, xLabel: PropTypes.string.isRequired,
xMin: PropTypes.number, xMin: PropTypes.number,
xMax: PropTypes.number.isRequired, xMax: PropTypes.number.isRequired,
xUnit: PropTypes.string.isRequired, xUnit: PropTypes.string.isRequired,
xMark: PropTypes.number, xMark: PropTypes.number,
yLabel: PropTypes.string.isRequired, yLabel: PropTypes.string.isRequired,
yMin: PropTypes.number, yMin: PropTypes.number,
yMax: PropTypes.number.isRequired, yMax: PropTypes.number.isRequired,
yUnit: PropTypes.string, yUnit: PropTypes.string,
series: PropTypes.array, series: PropTypes.array,
colors: PropTypes.array, colors: PropTypes.array,
points: PropTypes.number, points: PropTypes.number,
aspect: PropTypes.number, aspect: PropTypes.number,
code: PropTypes.string, code: PropTypes.string,
}; };
/** /**
* Constructor * Constructor
* @param {Object} props React Component properties * @param {Object} props React Component properties
* @param {Object} context React Component context * @param {Object} context React Component context
*/ */
constructor(props, context) { constructor(props, context) {
super(props); super(props);
this._updateDimensions = this._updateDimensions.bind(this); this._updateDimensions = this._updateDimensions.bind(this);
this._updateSeries = this._updateSeries.bind(this); this._updateSeries = this._updateSeries.bind(this);
this._tooltip = this._tooltip.bind(this); this._tooltip = this._tooltip.bind(this);
this._showTip = this._showTip.bind(this); this._showTip = this._showTip.bind(this);
this._hideTip = this._hideTip.bind(this); this._hideTip = this._hideTip.bind(this);
this._moveTip = this._moveTip.bind(this); this._moveTip = this._moveTip.bind(this);
const series = props.series; const series = props.series;
let xScale = d3.scaleLinear(); let xScale = d3.scaleLinear();
let yScale = d3.scaleLinear(); let yScale = d3.scaleLinear();
let xAxisScale = d3.scaleLinear(); let xAxisScale = d3.scaleLinear();
this.xAxis = d3.axisBottom(xAxisScale).tickSizeOuter(0); this.xAxis = d3.axisBottom(xAxisScale).tickSizeOuter(0);
this.yAxis = d3.axisLeft(yScale).ticks(6).tickSizeOuter(0); this.yAxis = d3.axisLeft(yScale).ticks(6).tickSizeOuter(0);
this.state = { this.state = {
xScale, xScale,
xAxisScale, xAxisScale,
yScale, yScale,
tipHeight: 2 + (1.2 * (series ? series.length : 0.8)), tipHeight: 2 + (1.2 * (series ? series.length : 0.8)),
}; dimensions: {
} width: 100,
height: 100
/** }
* Update tooltip content };
* @param {number} xPos x coordinate }
* @param {number} width current container width
*/ /**
_tooltip(xPos, width) { * Update tooltip content
let { xLabel, yLabel, xUnit, yUnit, func, series } = this.props; * @param {number} xPos x coordinate
let { xScale, yScale } = this.state; */
let { formats, translate } = this.context.language; _tooltip(xPos) {
let x0 = xScale.invert(xPos), let { xLabel, yLabel, xUnit, yUnit, func, series } = this.props;
y0 = func(x0), let { xScale, yScale } = this.state;
tips = this.tipContainer, let { width } = this.state.dimensions;
yTotal = 0, let { formats, translate } = this.context.language;
flip = (xPos / width > 0.50), let x0 = xScale.invert(xPos),
tipWidth = 0, y0 = func(x0),
tipHeightPx = tips.selectAll('rect').node().getBoundingClientRect().height; tips = this.tipContainer,
yTotal = 0,
flip = (xPos / width > 0.50),
xPos = xScale(x0); // Clamp xPos tipWidth = 0,
tipHeightPx = tips.selectAll('rect').node().getBoundingClientRect().height;
tips.selectAll('text.text-tip.y').text(function(d, i) {
let yVal = series ? y0[series[i]] : y0;
yTotal += yVal; xPos = xScale(x0); // Clamp xPos
return (series ? translate(series[i]) : '') + ' ' + formats.f2(yVal);
}).append('tspan').attr('class', 'metric').text(yUnit ? ' ' + yUnit : ''); tips.selectAll('text.text-tip.y').text(function(d, i) {
let yVal = series ? y0[series[i]] : y0;
tips.selectAll('text').each(function() { yTotal += yVal;
if (this.getBBox().width > tipWidth) { return (series ? translate(series[i]) : '') + ' ' + formats.f2(yVal);
tipWidth = Math.ceil(this.getBBox().width); }).append('tspan').attr('class', 'metric').text(yUnit ? ' ' + yUnit : '');
}
}); tips.selectAll('text').each(function() {
if (this.getBBox().width > tipWidth) {
let tipY = Math.floor(yScale(yTotal / (series ? series.length : 1)) - (tipHeightPx / 2)); tipWidth = Math.ceil(this.getBBox().width);
}
tipWidth += 8; });
tips.attr('transform', 'translate(' + xPos + ',' + tipY + ')');
tips.selectAll('text.text-tip').attr('x', flip ? -12 : 12).style('text-anchor', flip ? 'end' : 'start'); let tipY = Math.floor(yScale(yTotal / (series ? series.length : 1)) - (tipHeightPx / 2));
tips.selectAll('text.text-tip.x').text(formats.f2(x0)).append('tspan').attr('class', 'metric').text(' ' + xUnit);
tips.selectAll('rect').attr('width', tipWidth + 4).attr('x', flip ? -tipWidth - 12 : 8).attr('y', 0).style('text-anchor', flip ? 'end' : 'start'); tipWidth += 8;
this.markersContainer.selectAll('circle').attr('cx', xPos).attr('cy', (d, i) => yScale(series ? y0[series[i]] : y0)); tips.attr('transform', 'translate(' + xPos + ',' + tipY + ')');
} tips.selectAll('text.text-tip').attr('x', flip ? -12 : 12).style('text-anchor', flip ? 'end' : 'start');
tips.selectAll('text.text-tip.x').text(formats.f2(x0)).append('tspan').attr('class', 'metric').text(' ' + xUnit);
/** tips.selectAll('rect').attr('width', tipWidth + 4).attr('x', flip ? -tipWidth - 12 : 8).attr('y', 0).style('text-anchor', flip ? 'end' : 'start');
* Update dimensions based on properties and scale this.markersContainer.selectAll('circle').attr('cx', xPos).attr('cy', (d, i) => yScale(series ? y0[series[i]] : y0));
* @param {Object} props React Component properties }
* @param {number} scale size ratio / scale
* @param {number} width current width of the container /**
* @returns {Object} calculated dimensions * Update dimensions based on properties and scale
*/ * @param {Object} props React Component properties
_updateDimensions(props, scale, width) { * @param {number} scale size ratio / scale
const { xMax, xMin, yMin, yMax } = props; * @returns {Object} calculated dimensions
const innerWidth = width - MARGIN.left - MARGIN.right; */
const outerHeight = Math.round(width * props.aspect); _updateDimensions(props, scale) {
const innerHeight = outerHeight - MARGIN.top - MARGIN.bottom; const { xMax, xMin, yMin, yMax } = props;
const { width, height } = this.state.dimensions;
this.state.xScale.range([0, innerWidth]).domain([xMin, xMax || 1]).clamp(true); const innerWidth = width - MARGIN.left - MARGIN.right;
this.state.xAxisScale.range([0, innerWidth]).domain([xMin, xMax]).clamp(true); const outerHeight = Math.round(width * props.aspect);
this.state.yScale.range([innerHeight, 0]).domain([yMin, yMax + (yMax - yMin) * 0.1]); // 10% higher than maximum value for tooltip visibility const innerHeight = outerHeight - MARGIN.top - MARGIN.bottom;
return { innerWidth, outerHeight, innerHeight };
} this.state.xScale.range([0, innerWidth]).domain([xMin, xMax || 1]).clamp(true);
this.state.xAxisScale.range([0, innerWidth]).domain([xMin, xMax]).clamp(true);
/** this.state.yScale.range([innerHeight, 0]).domain([yMin, yMax + (yMax - yMin) * 0.1]); // 10% higher than maximum value for tooltip visibility
* Show tooltip return { innerWidth, outerHeight, innerHeight };
* @param {SyntheticEvent} e Event }
*/
_showTip(e) { /**
e.preventDefault(); * Show tooltip
this.tipContainer.style('display', null); * @param {SyntheticEvent} e Event
this.markersContainer.style('display', null); */
this._moveTip(e); _showTip(e) {
} e.preventDefault();
this.tipContainer.style('display', null);
/** this.markersContainer.style('display', null);
* Move and update tooltip this._moveTip(e);
* @param {SyntheticEvent} e Event }
* @param {number} width current container width
*/ /**
_moveTip(e, width) { * Move and update tooltip
let clientX = e.touches ? e.touches[0].clientX : e.clientX; * @param {SyntheticEvent} e Event
this._tooltip(Math.round(clientX - e.currentTarget.getBoundingClientRect().left), width); */
} _moveTip(e) {
let clientX = e.touches ? e.touches[0].clientX : e.clientX;
/** this._tooltip(Math.round(clientX - e.currentTarget.getBoundingClientRect().left));
* Hide tooltip }
* @param {SyntheticEvent} e Event
*/ /**
_hideTip(e) { * Hide tooltip
e.preventDefault(); * @param {SyntheticEvent} e Event
this.tipContainer.style('display', 'none'); */
this.markersContainer.style('display', 'none'); _hideTip(e) {
} e.preventDefault();
this.tipContainer.style('display', 'none');
/** this.markersContainer.style('display', 'none');
* Update series generated from props }
* @param {Object} props React Component properties
* @param {Object} state React Component state /**
*/ * Update series generated from props
_updateSeries(props, state) { * @param {Object} props React Component properties
let { func, xMin, xMax, series, points } = props; * @param {Object} state React Component state
let delta = (xMax - xMin) / points; */
let seriesData = new Array(points); _updateSeries(props, state) {
let { func, xMin, xMax, series, points } = props;
if (delta) { let delta = (xMax - xMin) / points;
seriesData = new Array(points); let seriesData = new Array(points);
for (let i = 0, x = xMin; i < points; i++) {
seriesData[i] = [x, func(x)]; if (delta) {
x += delta; seriesData = new Array(points);
} for (let i = 0, x = xMin; i < points; i++) {
seriesData[points - 1] = [xMax, func(xMax)]; seriesData[i] = [x, func(x)];
} else { x += delta;
let yVal = func(xMin); }
seriesData = [[0, yVal], [1, yVal]]; seriesData[points - 1] = [xMax, func(xMax)];
} } else {
let yVal = func(xMin);
const markerElems = []; seriesData = [[0, yVal], [1, yVal]];
const detailElems = [<text key='lbl' className='text-tip x' y='1.25em'/>]; }
const seriesLines = [];
for (let i = 0, l = series ? series.length : 1; i < l; i++) { const markerElems = [];
const yAccessor = series ? function(d) { return state.yScale(d[1][this]); }.bind(series[i]) : (d) => state.yScale(d[1]); const detailElems = [<text key='lbl' className='text-tip x' y='1.25em'/>];
seriesLines.push(d3.line().x((d, i) => this.state.xScale(d[0])).y(yAccessor)); const seriesLines = [];
detailElems.push(<text key={i} className='text-tip y' strokeWidth={0} fill={props.colors[i]} y={1.25 * (i + 2) + 'em'}/>); for (let i = 0, l = series ? series.length : 1; i < l; i++) {
markerElems.push(<circle key={i} className='marker' r='4' />); const yAccessor = series ? function(d) { return state.yScale(d[1][this]); }.bind(series[i]) : (d) => state.yScale(d[1]);
} seriesLines.push(d3.line().x((d, i) => this.state.xScale(d[0])).y(yAccessor));
detailElems.push(<text key={i} className='text-tip y' strokeWidth={0} fill={props.colors[i]} y={1.25 * (i + 2) + 'em'}/>);
const tipHeight = 2 + (1.2 * (seriesLines ? seriesLines.length : 0.8)); markerElems.push(<circle key={i} className='marker' r='4' />);
}
this.setState({ markerElems, detailElems, seriesLines, seriesData, tipHeight });
} const tipHeight = 2 + (1.2 * (seriesLines ? seriesLines.length : 0.8));
/** this.setState({ markerElems, detailElems, seriesLines, seriesData, tipHeight });
* Update dimensions and series data based on props and context. }
*/
componentWillMount() { /**
this._updateSeries(this.props, this.state); * Update dimensions and series data based on props and context.
} */
componentWillMount() {
/** this._updateSeries(this.props, this.state);
* Update state based on property and context changes }
* @param {Object} nextProps Incoming/Next properties
* @param {Object} nextContext Incoming/Next conext /**
*/ * Update state based on property and context changes
componentWillReceiveProps(nextProps, nextContext) { * @param {Object} nextProps Incoming/Next properties
const props = this.props; * @param {Object} nextContext Incoming/Next conext
*/
if (props.code != nextProps.code) { componentWillReceiveProps(nextProps, nextContext) {
this._updateSeries(nextProps, this.state); const props = this.props;
}
} if (props.code != nextProps.code) {
this._updateSeries(nextProps, this.state);
/** }
* Render the chart }
* @return {React.Component} Chart SVG
*/ /**
render() { * Render the chart
return ( * @return {React.Component} Chart SVG
<ContainerDimensions> */
{ ({ width, height }) => { render() {
const { innerWidth, outerHeight, innerHeight } = this._updateDimensions(this.props, this.context.sizeRatio, width, height); const { innerWidth, outerHeight, innerHeight } = this._updateDimensions(this.props, this.context.sizeRatio);
const { xMin, xMax, xLabel, yLabel, xUnit, yUnit, xMark, colors } = this.props; const { width, height } = this.state.dimensions;
const { tipHeight, detailElems, markerElems, seriesData, seriesLines } = this.state; const { xMin, xMax, xLabel, yLabel, xUnit, yUnit, xMark, colors } = this.props;
const lines = seriesLines.map((line, i) => <path key={i} className='line' fill='none' stroke={colors[i]} strokeWidth='1' d={line(seriesData)} />).reverse(); const { tipHeight, detailElems, markerElems, seriesData, seriesLines } = this.state;
const line = this.line;
const markX = xMark ? innerWidth * (xMark - xMin) / (xMax - xMin) : 0; const lines = seriesLines.map((line, i) => <path key={i} className='line' fill='none' stroke={colors[i]} strokeWidth='1' d={line(seriesData)} />).reverse();
const xmark = xMark ? <path key={'mark'} className='line' fill='none' strokeDasharray='5,5' stroke={'#ff8c0d'} strokeWidth='1' d={'M ' + markX + ' ' + innerHeight + ' L ' + markX + ' 0'} /> : '';
return ( const markX = xMark ? innerWidth * (xMark - xMin) / (xMax - xMin) : 0;
<div width={width} height={height}> const xmark = xMark ? <path key={'mark'} className='line' fill='none' strokeDasharray='5,5' stroke={'#ff8c0d'} strokeWidth='1' d={'M ' + markX + ' ' + innerHeight + ' L ' + markX + ' 0'} /> : '';
<svg style={{ width: '100%', height: outerHeight }}>
<g transform={`translate(${MARGIN.left},${MARGIN.top})`}> return (
<g>{xmark}</g> <Measure width='100%' whitelist={['width', 'top']} onMeasure={ (dimensions) => { this.setState({ dimensions }); }}>
<g>{lines}</g> <div width={width} height={height}>
<g className='x axis' ref={(elem) => d3.select(elem).call(this.xAxis)} transform={`translate(0,${innerHeight})`}> <svg style={{ width: '100%', height: outerHeight }}>
<text className='cap' y='30' dy='.1em' x={innerWidth / 2} style={{ textAnchor: 'middle' }}> <g transform={`translate(${MARGIN.left},${MARGIN.top})`}>
<tspan>{xLabel}</tspan> <g>{xmark}</g>
<tspan className='metric'> ({xUnit})</tspan> <g>{lines}</g>
</text> <g className='x axis' ref={(elem) => d3.select(elem).call(this.xAxis)} transform={`translate(0,${innerHeight})`}>
</g> <text className='cap' y='30' dy='.1em' x={innerWidth / 2} style={{ textAnchor: 'middle' }}>
<g className='y axis' ref={(elem) => d3.select(elem).call(this.yAxis)}> <tspan>{xLabel}</tspan>
<text className='cap' transform='rotate(-90)' y='-50' dy='.1em' x={innerHeight / -2} style={{ textAnchor: 'middle' }}> <tspan className='metric'> ({xUnit})</tspan>
<tspan>{yLabel}</tspan> </text>
{ yUnit && <tspan className='metric'> ({yUnit})</tspan> } </g>
</text> <g className='y axis' ref={(elem) => d3.select(elem).call(this.yAxis)}>
</g> <text className='cap' transform='rotate(-90)' y='-50' dy='.1em' x={innerHeight / -2} style={{ textAnchor: 'middle' }}>
<g ref={(g) => this.tipContainer = d3.select(g)} style={{ display: 'none' }}> <tspan>{yLabel}</tspan>
<rect className='tooltip' height={tipHeight + 'em'}></rect> { yUnit && <tspan className='metric'> ({yUnit})</tspan> }
{detailElems} </text>
</g> </g>
<g ref={(g) => this.markersContainer = d3.select(g)} style={{ display: 'none' }}> <g ref={(g) => this.tipContainer = d3.select(g)} style={{ display: 'none' }}>
{markerElems} <rect className='tooltip' height={tipHeight + 'em'}></rect>
</g> {detailElems}
<rect </g>
fillOpacity='0' <g ref={(g) => this.markersContainer = d3.select(g)} style={{ display: 'none' }}>
height={innerHeight} {markerElems}
width={innerWidth + 1} </g>
onMouseEnter={this._showTip} <rect
onTouchStart={this._showTip} fillOpacity='0'
onMouseLeave={this._hideTip} height={innerHeight}
onTouchEnd={this._hideTip} width={innerWidth + 1}
onMouseMove={e => this._moveTip(e, width)} onMouseEnter={this._showTip}
onTouchMove={e => this._moveTip(e, width)} onTouchStart={this._showTip}
/> onMouseLeave={this._hideTip}
</g> onTouchEnd={this._hideTip}
</svg> onMouseMove={this._moveTip}
</div> onTouchMove={this._moveTip}
); />
}} </g>
</ContainerDimensions> </svg>
); </div>
} </Measure>
} );
}
}

View File

@@ -1,93 +0,0 @@
import React from 'react';
import PropTypes from 'prop-types';
import request from 'superagent';
import TranslatedComponent from './TranslatedComponent';
import { orbisUpload } from '../utils/ShortenUrl';
import Persist from '../stores/Persist';
/**
* Permalink modal
*/
export default class ModalBatchOrbis extends TranslatedComponent {
static propTypes = {
ships: PropTypes.any.isRequired
};
/**
* Constructor
* @param {Object} props React Component properties
*/
constructor(props) {
super(props);
this.state = {
orbisCreds: Persist.getOrbisCreds(),
resp: ''
};
}
/**
* Send ship to Orbis.zone
* @param {SyntheticEvent} e React Event
* @return {Promise} Promise sending post request to orbis
*/
sendToOrbis(e) {
let agent;
try {
agent = request.agent(); // apparently this crashes somehow
} catch (e) {
console.error(e);
}
if (!agent) {
agent = request;
}
const API_ORBIS = 'https://orbis.zone/api/builds/add/batch';
return new Promise((resolve, reject) => {
try {
agent
.post(API_ORBIS)
.withCredentials()
.redirects(0)
.set('Content-Type', 'application/json')
.send(this.props.ships)
.end((err, response) => {
console.log(response);
if (err) {
console.error(err);
this.setState({ resp: response.text });
reject('Bad Request');
} else {
this.setState({ resp: 'All builds uploaded. Check https://orbis.zone' });
resolve('All builds uploaded. Check https://orbis.zone');
}
});
} catch (e) {
console.log(e);
reject(e.message ? e.message : e);
}
});
}
/**
* Render the modal
* @return {React.Component} Modal Content
*/
render() {
let translate = this.context.language.translate;
this.sendToOrbis = this.sendToOrbis.bind(this);
return <div className='modal' onClick={ (e) => e.stopPropagation() }>
<h2>{translate('permalink')}</h2>
<br/>
<a className='button' href="https://orbis.zone/api/auth">Log in / signup to Orbis</a>
<br/><br/>
<h3 >{translate('success')}</h3>
<input value={this.state.resp} readOnly size={25} onFocus={ (e) => e.target.select() }/>
<br/><br/>
<p>Orbis.zone is currently in a trial period, and may be wiped at any time as development progresses. Some elements are also still placeholders.</p>
<button className={'l cb dismiss cap'} disabled={!!this.state.failed} onClick={this.sendToOrbis}>{translate('PHASE_UPLOAD_ORBIS')}</button>
<button className={'r dismiss cap'} onClick={this.context.hideModal}>{translate('close')}</button>
</div>;
}
}

View File

@@ -126,11 +126,7 @@ export default class ModalImport extends TranslatedComponent {
if (importData.builds && typeof importData.builds == 'object') { if (importData.builds && typeof importData.builds == 'object') {
for (let shipId in importData.builds) { for (let shipId in importData.builds) {
for (let buildName in importData.builds[shipId]) { for (let buildName in importData.builds[shipId]) {
try { validateBuild(shipId, importData.builds[shipId][buildName], buildName);
validateBuild(shipId, importData.builds[shipId][buildName], buildName);
} catch (err) {
delete importData.builds[shipId][buildName];
}
} }
} }
this.setState({ builds: importData.builds }); this.setState({ builds: importData.builds });

View File

@@ -1,117 +0,0 @@
import React from 'react';
import PropTypes from 'prop-types';
import TranslatedComponent from './TranslatedComponent';
import { orbisUpload } from '../utils/ShortenUrl';
import Persist from '../stores/Persist';
/**
* Permalink modal
*/
export default class ModalOrbis extends TranslatedComponent {
static propTypes = {
ship: PropTypes.any.isRequired
};
/**
* Constructor
* @param {Object} props React Component properties
*/
constructor(props) {
super(props);
this.state = {
orbisCreds: Persist.getOrbisCreds(),
orbisUrl: '...',
authenticatedStatus: 'Checking...'
};
}
/**
* Send ship to Orbis.zone
* @param {SyntheticEvent} e React Event
*/
sendToOrbis(e) {
const target = e.target;
target.disabled = true;
this.setState({ orbisUrl: 'Sending...' }, () => {
orbisUpload(this.props.ship, this.state.orbisCreds)
.then(orbisUrl => {
target.disabled = false;
this.setState({ orbisUrl });
})
.catch(err => {
target.disabled = false;
this.setState({ orbisUrl: 'Error - ' + err });
});
});
}
/**
* Get Orbis.zone auth status
* @returns {Object} auth status
*/
getOrbisAuthStatus() {
return fetch('https://orbis.zone/api/checkauth', {
credentials: 'include',
mode: 'cors'
})
.then(data => data.json())
.then(res => {
this.setState({ authenticatedStatus: res.status || res.error });
})
.catch(err => {
console.error(err);
this.setState({ authenticatedStatus: err.message });
});
}
/**
* Handler for changing cmdr name
* @param {SyntheticEvent} e React Event
*/
orbisPasswordHandler(e) {
let password = e.target.value;
this.setState({ orbisCreds: { email: this.state.orbisCreds.email, password } }, () => {
Persist.setOrbisCreds(this.state.orbisCreds);
});
}
/**
* Handler for changing cmdr name
* @param {SyntheticEvent} e React Event
*/
orbisUsername(e) {
let orbisUsername = e.target.value;
this.setState({ orbisCreds: { email: orbisUsername, password: this.state.orbisCreds.password } }, () => {
Persist.setOrbisCreds(this.state.orbisCreds);
});
}
/**
* Render the modal
* @return {React.Component} Modal Content
*/
render() {
let translate = this.context.language.translate;
this.orbisPasswordHandler = this.orbisPasswordHandler.bind(this);
this.orbisUsername = this.orbisUsername.bind(this);
this.sendToOrbis = this.sendToOrbis.bind(this);
this.getOrbisAuthStatus();
return <div className='modal' onClick={ (e) => e.stopPropagation() }>
<h2>{translate('upload to orbis')}</h2>
<br/>
<label>Orbis auth status: </label>
<input value={this.state.authenticatedStatus} readOnly size={25} onFocus={ (e) => e.target.select() }/>
<br/><br/>
<a className='button' href="https://orbis.zone/api/auth">Log in / signup to Orbis</a>
<br/><br/>
<h3 >{translate('Orbis link')}</h3>
<input value={this.state.orbisUrl} readOnly size={25} onFocus={ (e) => e.target.select() }/>
<br/><br/>
<p>Orbis.zone is currently in a trial period, and may be wiped at any time as development progresses. Some elements are also still placeholders.</p>
<button className={'l cb dismiss cap'} disabled={!!this.state.failed} onClick={this.sendToOrbis}>{translate('PHASE_UPLOAD_ORBIS')}</button>
<button className={'r dismiss cap'} onClick={this.context.hideModal}>{translate('close')}</button>
</div>;
}
}

View File

@@ -50,7 +50,6 @@ export default class ModalPermalink extends TranslatedComponent {
<h3 >{translate('shortened')}</h3> <h3 >{translate('shortened')}</h3>
<input value={this.state.shortenedUrl} readOnly size={25} onFocus={ (e) => e.target.select() }/> <input value={this.state.shortenedUrl} readOnly size={25} onFocus={ (e) => e.target.select() }/>
<br/><br/> <br/><br/>
<p>s.orbis.zone is the new URL shortener domain, old eddp.co urls are considered end of life and could go down at any moment. Sorry for any inconvenience.</p>
<button className={'r dismiss cap'} onClick={this.context.hideModal}>{translate('close')}</button> <button className={'r dismiss cap'} onClick={this.context.hideModal}>{translate('close')}</button>
</div>; </div>;
} }

View File

@@ -1,262 +0,0 @@
import React from 'react';
import PropTypes from 'prop-types';
import TranslatedComponent from './TranslatedComponent';
import request from 'superagent';
import Persist from '../stores/Persist';
/**
* Permalink modal
*/
export default class ModalShoppingList extends TranslatedComponent {
static propTypes = {
ship: PropTypes.object.isRequired
};
/**
* Constructor
* @param {Object} props React Component properties
*/
constructor(props) {
super(props);
this.state = {
matsList: '',
mats: {},
failed: false,
cmdrName: Persist.getCmdr().selected,
cmdrs: Persist.getCmdr().cmdrs,
matsPerGrade: Persist.getRolls(),
blueprints: []
};
}
/**
* React component did mount
*/
componentDidMount() {
this.renderMats();
if (this.checkBrowserIsCompatible()) {
this.getCommanders();
this.registerBPs();
}
}
/**
* Find all blueprints needed to make a build.
*/
registerBPs() {
const ship = this.props.ship;
let blueprints = [];
for (const module of ship.costList) {
if (module.type === 'SHIP') {
continue;
}
if (module.m && module.m.blueprint) {
if (!module.m.blueprint.grade || !module.m.blueprint.grades) {
continue;
}
if (module.m.blueprint.special) {
console.log(module.m.blueprint.special);
blueprints.push({ uuid: module.m.blueprint.special.uuid, number: 1 });
}
for (const g in module.m.blueprint.grades) {
if (!module.m.blueprint.grades.hasOwnProperty(g)) {
continue;
}
if (g > module.m.blueprint.grade) {
continue;
}
blueprints.push({ uuid: module.m.blueprint.grades[g].uuid, number: this.state.matsPerGrade[g] });
}
}
}
this.setState({ blueprints });
}
/**
* Check browser isn't firefox.
* @return {boolean} true if compatible, false if not.
*/
checkBrowserIsCompatible() {
// Firefox 1.0+
return typeof InstallTrigger === 'undefined';
}
/**
* Get a list of commanders from EDEngineer.
*/
getCommanders() {
request
.get('http://localhost:44405/commanders')
.end((err, res) => {
if (err) {
console.log(err);
return this.setState({ failed: true });
}
const cmdrs = JSON.parse(res.text);
if (!this.state.cmdrName) {
this.setState({ cmdrName: cmdrs[0] });
}
this.setState({ cmdrs }, () => {
Persist.setCmdr({ selected: this.state.cmdrName, cmdrs });
});
});
}
/**
* Send all blueprints to ED Engineer
* @param {Event} event React event
*/
sendToEDEng(event) {
event.preventDefault();
const target = event.target;
target.disabled = this.state.blueprints.length > 0;
if (this.state.blueprints.length === 0) {
target.innerText = 'No modded components.';
target.disabled = true;
setTimeout(() => {
target.innerText = 'Send to EDEngineer';
target.disabled = false;
}, 3000);
} else {
target.innerText = 'Sending...';
}
let countSent = 0;
let countTotal = this.state.blueprints.length;
for (const i of this.state.blueprints) {
request
.patch(`http://localhost:44405/${this.state.cmdrName}/shopping-list`)
.field('uuid', i.uuid)
.field('size', i.number)
.end(err => {
if (err) {
console.log(err);
if (err.message !== 'Bad Request') {
this.setState({ failed: true });
}
}
countSent++;
if (countSent === countTotal) {
target.disabled = false;
target.innerText = 'Send to EDEngineer';
}
});
}
}
/**
* Convert mats object to string
*/
renderMats() {
const ship = this.props.ship;
let mats = {};
for (const module of ship.costList) {
if (module.type === 'SHIP') {
continue;
}
if (module.m && module.m.blueprint) {
if (!module.m.blueprint.grade || !module.m.blueprint.grades) {
continue;
}
for (const g in module.m.blueprint.grades) {
if (!module.m.blueprint.grades.hasOwnProperty(g)) {
continue;
}
if (g > module.m.blueprint.grade) {
continue;
}
for (const i in module.m.blueprint.grades[g].components) {
if (!module.m.blueprint.grades[g].components.hasOwnProperty(i)) {
continue;
}
if (mats[i]) {
mats[i] += module.m.blueprint.grades[g].components[i] * this.state.matsPerGrade[g];
} else {
mats[i] = module.m.blueprint.grades[g].components[i] * this.state.matsPerGrade[g];
}
}
}
}
}
let matsString = '';
for (const i in mats) {
if (!mats.hasOwnProperty(i)) {
continue;
}
if (mats[i] === 0) {
delete mats[i];
continue;
}
matsString += `${i}: ${mats[i]}\n`;
}
this.setState({ matsList: matsString, mats });
}
/**
* Handler for changing roll amounts
* @param {SyntheticEvent} e React Event
*/
changeHandler(e) {
let grade = e.target.id;
let newState = this.state.matsPerGrade;
newState[grade] = parseInt(e.target.value);
this.setState({ matsPerGrade: newState });
Persist.setRolls(newState);
this.renderMats();
this.registerBPs();
}
/**
* Handler for changing cmdr name
* @param {SyntheticEvent} e React Event
*/
cmdrChangeHandler(e) {
let cmdrName = e.target.value;
this.setState({ cmdrName }, () => {
Persist.setCmdr({ selected: this.state.cmdrName, cmdrs: this.state.cmdrs });
});
}
/**
* Render the modal
* @return {React.Component} Modal Content
*/
render() {
let translate = this.context.language.translate;
this.changeHandler = this.changeHandler.bind(this);
const compatible = this.checkBrowserIsCompatible();
this.cmdrChangeHandler = this.cmdrChangeHandler.bind(this);
this.sendToEDEng = this.sendToEDEng.bind(this);
return <div className='modal' onClick={ (e) => e.stopPropagation() }>
<h2>{translate('PHRASE_SHOPPING_MATS')}</h2>
<label>Grade 1 rolls </label>
<input id={1} type={'number'} min={0} defaultValue={this.state.matsPerGrade[1]} onChange={this.changeHandler} />
<br/>
<label>Grade 2 rolls </label>
<input id={2} type={'number'} min={0} defaultValue={this.state.matsPerGrade[2]} onChange={this.changeHandler} />
<br/>
<label>Grade 3 rolls </label>
<input id={3} type={'number'} min={0} value={this.state.matsPerGrade[3]} onChange={this.changeHandler} />
<br/>
<label>Grade 4 rolls </label>
<input id={4} type={'number'} min={0} value={this.state.matsPerGrade[4]} onChange={this.changeHandler} />
<br/>
<label>Grade 5 rolls </label>
<input id={5} type={'number'} min={0} value={this.state.matsPerGrade[5]} onChange={this.changeHandler} />
<div>
<textarea className='cb json' readOnly value={this.state.matsList} />
</div>
<label hidden={!compatible} className={'l cap'}>CMDR Name </label>
<br/>
<select hidden={!compatible} className={'cmdr-select l cap'} onChange={this.cmdrChangeHandler} defaultValue={this.state.cmdrName}>
{this.state.cmdrs.map(e => <option key={e}>{e}</option>)}
</select>
<br/>
<p hidden={!this.state.failed} id={'failed'} className={'l'}>Failed to send to EDEngineer (Launch EDEngineer and make sure the API is started then refresh the page.)</p>
<p hidden={compatible} id={'browserbad'} className={'l'}>Sending to EDEngineer is not compatible with Firefox's security settings. Please try again with Chrome.</p>
<button className={'l cb dismiss cap'} disabled={!!this.state.failed || !compatible} onClick={this.sendToEDEng}>{translate('Send To EDEngineer')}</button>
<button className={'r dismiss cap'} onClick={this.context.hideModal}>{translate('close')}</button>
</div>;
}
}

View File

@@ -3,7 +3,6 @@ import PropTypes from 'prop-types';
import TranslatedComponent from './TranslatedComponent'; import TranslatedComponent from './TranslatedComponent';
import cn from 'classnames'; import cn from 'classnames';
import NumberEditor from 'react-number-editor'; import NumberEditor from 'react-number-editor';
import { isValueBeneficial } from '../utils/BlueprintFunctions';
/** /**
* Modification * Modification
@@ -15,10 +14,7 @@ export default class Modification extends TranslatedComponent {
m: PropTypes.object.isRequired, m: PropTypes.object.isRequired,
name: PropTypes.string.isRequired, name: PropTypes.string.isRequired,
value: PropTypes.number.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
}; };
/** /**
@@ -39,23 +35,31 @@ export default class Modification extends TranslatedComponent {
* in a value by hand * in a value by hand
*/ */
_updateValue(value) { _updateValue(value) {
let { m, name, ship } = this.props; const name = this.props.name;
value = Math.max(Math.min(value, 50000), -50000);
ship.setModification(m, name, value, true, true); let scaledValue = Math.round(Number(value) * 100);
// Limit to +1000% / -99.99%
if (scaledValue > 100000) {
scaledValue = 100000;
value = 1000;
}
if (scaledValue < -9999) {
scaledValue = -9999;
value = -99.99;
}
let m = this.props.m;
let ship = this.props.ship;
ship.setModification(m, name, scaledValue, true);
this.setState({ value }); this.setState({ value });
} }
/** /**
* Triggered when an update to slider value is finished i.e. when losing focus * 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() { _updateFinished() {
if (this.props.value != this.state.value) { this.props.onChange();
this.props.handleModChange(true);
this.props.onChange();
}
} }
/** /**
@@ -63,50 +67,28 @@ export default class Modification extends TranslatedComponent {
* @return {React.Component} modification * @return {React.Component} modification
*/ */
render() { render() {
let { translate, formats, units } = this.context.language; let translate = this.context.language.translate;
let { m, name } = this.props; let { m, name } = this.props;
let modValue = m.getChange(name);
if (name === 'damagedist') { if (name === 'damagedist') {
// We don't show damage distribution // We don't show damage distribution
return null; return null;
} }
let symbol;
if (name === 'jitter') {
symbol = '°';
} else if (name !== 'burst' && name != 'burstrof') {
symbol = '%';
}
if (symbol) {
symbol = ' (' + symbol + ')';
}
return ( return (
<div onBlur={this._updateFinished.bind(this)} key={name} <div onBlur={this._updateFinished.bind(this)} className={'cb'} key={name}>
className={cn('cb', 'modification-container')} <div className={'cb'}>{translate(name, m.grp)}{symbol}</div>
ref={ modItem => this.props.modItems[name] = modItem }> <NumberEditor className={'cb'} style={{ width: '90%', textAlign: 'center' }} step={0.01} stepModifier={1} decimals={2} value={this.state.value} onValueChange={this._updateValue.bind(this)} />
<span className={'cb'}>{translate(name, m.grp)}</span>
<span className={'header-adjuster'}></span>
<table style={{ width: '100%' }}>
<tbody>
<tr>
<td className={'input-container'}>
<span>
{this.props.editable ?
<NumberEditor className={'cb'} value={this.state.value}
decimals={2} style={{ textAlign: 'right' }} step={0.01}
stepModifier={1} onKeyDown={ this.props.onKeyDown }
onValueChange={this._updateValue.bind(this)} /> :
<input type="text" value={formats.f2(this.state.value)}
disabled className={'number-editor'}
style={{ textAlign: 'right', cursor: 'inherit' }}/>
}
<span className={'unit-container'}>
{units[m.getStoredUnitFor(name)]}
</span>
</span>
</td>
<td style={{ textAlign: 'center' }} className={
modValue ?
isValueBeneficial(name, modValue) ? 'secondary': 'warning':
''
}>
{formats.f2(modValue / 100) || 0}%
</td>
</tr>
</tbody>
</table>
</div> </div>
); );
} }

View File

@@ -6,18 +6,7 @@ import { isEmpty, stopCtxPropagation } from '../utils/UtilityFunctions';
import cn from 'classnames'; import cn from 'classnames';
import { Modifications } from 'coriolis-data/dist'; import { Modifications } from 'coriolis-data/dist';
import Modification from './Modification'; import Modification from './Modification';
import { import { getBlueprint, blueprintTooltip, setWorst, setBest, setExtreme, setRandom } from '../utils/BlueprintFunctions';
getBlueprint,
blueprintTooltip,
setPercent,
getPercent,
setRandom,
specialToolTip
} from '../utils/BlueprintFunctions';
const MODIFICATIONS_COMPARATOR = (mod1, mod2) => {
return mod1.props.name.localeCompare(mod2.props.name);
};
/** /**
* Modifications menu * Modifications menu
@@ -28,8 +17,7 @@ export default class ModificationsMenu extends TranslatedComponent {
ship: PropTypes.object.isRequired, ship: PropTypes.object.isRequired,
m: PropTypes.object.isRequired, m: PropTypes.object.isRequired,
marker: PropTypes.string.isRequired, marker: PropTypes.string.isRequired,
onChange: PropTypes.func.isRequired, onChange: PropTypes.func.isRequired
modButton:PropTypes.object
}; };
/** /**
@@ -42,24 +30,14 @@ export default class ModificationsMenu extends TranslatedComponent {
this._toggleBlueprintsMenu = this._toggleBlueprintsMenu.bind(this); this._toggleBlueprintsMenu = this._toggleBlueprintsMenu.bind(this);
this._toggleSpecialsMenu = this._toggleSpecialsMenu.bind(this); this._toggleSpecialsMenu = this._toggleSpecialsMenu.bind(this);
this._rollFifty = this._rollFifty.bind(this); this._rollWorst = this._rollWorst.bind(this);
this._rollRandom = this._rollRandom.bind(this); this._rollRandom = this._rollRandom.bind(this);
this._rollBest = this._rollBest.bind(this); this._rollBest = this._rollBest.bind(this);
this._rollWorst = this._rollWorst.bind(this); this._rollExtreme = this._rollExtreme.bind(this);
this._reset = this._reset.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.selectedModId = null;
this.selectedSpecialId = 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 = { this.state = {
blueprintMenuOpened: !(props.m.blueprint && props.m.blueprint.name), blueprintMenuOpened: false,
specialMenuOpened: false specialMenuOpened: false
}; };
} }
@@ -74,6 +52,7 @@ export default class ModificationsMenu extends TranslatedComponent {
const { m } = props; const { m } = props;
const { language, tooltip, termtip } = context; const { language, tooltip, termtip } = context;
const translate = language.translate; const translate = language.translate;
const blueprints = []; const blueprints = [];
for (const blueprintName in Modifications.modules[m.grp].blueprints) { for (const blueprintName in Modifications.modules[m.grp].blueprints) {
const blueprint = getBlueprint(blueprintName, m); const blueprint = getBlueprint(blueprintName, m);
@@ -82,18 +61,14 @@ export default class ModificationsMenu extends TranslatedComponent {
// Grade is a string in the JSON so make it a number // Grade is a string in the JSON so make it a number
grade = Number(grade); grade = Number(grade);
const classes = cn('c', { const classes = cn('c', {
active: m.blueprint && blueprint.id === m.blueprint.id && grade === m.blueprint.grade active: m.blueprint && blueprint.id === m.blueprint.id && grade === m.blueprint.grade
}); });
const close = this._blueprintSelected.bind(this, blueprintName, grade); const close = this._blueprintSelected.bind(this, blueprintName, grade);
const key = blueprintName + ':' + grade; const key = blueprintName + ':' + grade;
const tooltipContent = blueprintTooltip(translate, blueprint.grades[grade], Modifications.modules[m.grp].blueprints[blueprintName].grades[grade].engineers, m.grp); const tooltipContent = blueprintTooltip(translate, blueprint.grades[grade], Modifications.modules[m.grp].blueprints[blueprintName].grades[grade].engineers, m.grp);
if (classes.indexOf('active') >= 0) this.selectedModId = key; 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) { 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(<div key={blueprint.name} className={'select-group cap'}>{translate(blueprint.name)}</div>);
blueprints.push(<ul key={blueprintName}>{blueprintGrades}</ul>); blueprints.push(<ul key={blueprintName}>{blueprintGrades}</ul>);
} }
@@ -101,64 +76,6 @@ export default class ModificationsMenu extends TranslatedComponent {
return blueprints; return blueprints;
} }
/**
* Key down - select module on Enter key, move to next/previous module on Tab/Shift-Tab, close on Esc
* @param {SyntheticEvent} event Event
*
*/
_keyDown(event) {
let className = null;
let 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 * Render the specials
* @param {Object} props React component properties * @param {Object} props React component properties
@@ -169,34 +86,15 @@ export default class ModificationsMenu extends TranslatedComponent {
const { m } = props; const { m } = props;
const { language, tooltip, termtip } = context; const { language, tooltip, termtip } = context;
const translate = language.translate; const translate = language.translate;
const specials = []; const specials = [];
const specialsId = m.missile && Modifications.modules[m.grp]['specials_' + m.missile] ? 'specials_' + m.missile : '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) { if (Modifications.modules[m.grp][specialsId] && Modifications.modules[m.grp][specialsId].length > 0) {
const close = this._specialSelected.bind(this, null); const close = this._specialSelected.bind(this, null);
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>); specials.push(<div style={{ cursor: 'pointer' }} key={ 'none' } onClick={ close }>{translate('PHRASE_NO_SPECIAL')}</div>);
for (const specialName of Modifications.modules[m.grp][specialsId]) { 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
});
if (classes.indexOf('active') >= 0) this.selectedSpecialId = specialName;
const close = this._specialSelected.bind(this, specialName); const close = this._specialSelected.bind(this, specialName);
if (m.blueprint && m.blueprint.name) { specials.push(<div style={{ cursor: 'pointer' }} key={ specialName } onClick={ close }>{translate(Modifications.specials[specialName].name)}</div>);
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>);
}
} }
} }
return specials; return specials;
@@ -209,26 +107,14 @@ export default class ModificationsMenu extends TranslatedComponent {
*/ */
_renderModifications(props) { _renderModifications(props) {
const { m, onChange, ship } = props; const { m, onChange, ship } = props;
const modifiableModifications = [];
const modifications = []; const modifications = [];
for (const modName of Modifications.modules[m.grp].modifications) { for (const modName of Modifications.modules[m.grp].modifications) {
if (!Modifications.modifications[modName].hidden) { if (!Modifications.modifications[modName].hidden) {
const key = modName + (m.getModValue(modName) / 100 || 0); const key = modName + (m.getModValue(modName) / 100 || 0);
const editable = modName !== 'fallofffromrange' && modifications.push(<Modification key={ key } ship={ ship } m={ m } name={ modName } value={ m.getModValue(modName) / 100 || 0 } onChange={ onChange }/>);
m.blueprint.grades[m.blueprint.grade].features[modName];
this.lastNeId = modName;
(editable ? modifiableModifications : modifications).push(
<Modification key={ key } ship={ ship } m={ m }
value={m.getPretty(modName) || 0} modItems={this.modItems}
onChange={onChange} onKeyDown={this._keyDown} name={modName}
editable={editable} handleModChange = {this._handleModChange} />
);
} }
} }
return modifications;
modifiableModifications.sort(MODIFICATIONS_COMPARATOR);
modifications.sort(MODIFICATIONS_COMPARATOR);
return modifiableModifications.concat(modifications);
} }
/** /**
@@ -250,9 +136,8 @@ export default class ModificationsMenu extends TranslatedComponent {
const blueprint = getBlueprint(fdname, m); const blueprint = getBlueprint(fdname, m);
blueprint.grade = grade; blueprint.grade = grade;
ship.setModuleBlueprint(m, blueprint); ship.setModuleBlueprint(m, blueprint);
setPercent(ship, m, 100);
this.setState({ blueprintMenuOpened: false, specialMenuOpened: true }); this.setState({ blueprintMenuOpened: false });
this.props.onChange(); this.props.onChange();
} }
@@ -283,15 +168,11 @@ export default class ModificationsMenu extends TranslatedComponent {
} }
/** /**
* Provide a '50%' roll within the information we have * Provide a 'worst' roll within the information we have
*/ */
_rollFifty() { _rollWorst() {
const { m, ship } = this.props; const { m, ship } = this.props;
setPercent(ship, m, 50); setWorst(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(); this.props.onChange();
} }
@@ -301,10 +182,6 @@ export default class ModificationsMenu extends TranslatedComponent {
_rollRandom() { _rollRandom() {
const { m, ship } = this.props; const { m, ship } = this.props;
setRandom(ship, m); setRandom(ship, m);
// this will change the values in the modifications. Set modDidChange to true to prevent focus change when component updates
this._handleModChange(true);
this.props.onChange(); this.props.onChange();
} }
@@ -313,22 +190,16 @@ export default class ModificationsMenu extends TranslatedComponent {
*/ */
_rollBest() { _rollBest() {
const { m, ship } = this.props; const { m, ship } = this.props;
setPercent(ship, m, 100); setBest(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(); this.props.onChange();
} }
/** /**
* Provide a 'worst' roll within the information we have * Provide an 'extreme' roll within the information we have
*/ */
_rollWorst() { _rollExtreme() {
const { m, ship } = this.props; const { m, ship } = this.props;
setPercent(ship, m, 0); setExtreme(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(); this.props.onChange();
} }
@@ -339,67 +210,10 @@ export default class ModificationsMenu extends TranslatedComponent {
const { m, ship } = this.props; const { m, ship } = this.props;
ship.clearModifications(m); ship.clearModifications(m);
ship.clearModuleBlueprint(m); ship.clearModuleBlueprint(m);
this.selectedModId = null;
this.selectedSpecialId = null;
this.props.onChange(); this.props.onChange();
} }
/**
* set mod did change boolean
* @param {boolean} b Boolean to determine if a change has been made to a module
*/
_handleModChange(b) {
this.modValDidChange = b;
}
/**
* Set focus on first element in modifications menu
* after it first mounts
*/
componentDidMount() {
let firstEleCn = this.modItems['modMainDiv'].children.length > 0 ? this.modItems['modMainDiv'].children[0].className : null;
if (firstEleCn.indexOf('select-group cap') >= 0) {
this.modItems['modMainDiv'].children[1].firstElementChild.focus();
} else {
this.modItems['modMainDiv'].firstElementChild.focus();
}
}
/**
* Set focus on first element in modifications menu
* if component updates, unless update is due to value change
* in a modification
*/
componentDidUpdate() {
if (!this.modValDidChange) {
if (this.modItems['modMainDiv'].children.length > 0) {
if (this.modItems[this.selectedModId]) {
this.modItems[this.selectedModId].focus();
return;
} else if (this.modItems[this.selectedSpecialId]) {
this.modItems[this.selectedSpecialId].focus();
return;
}
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
}
}
/**
* set focus to the modification menu icon after mod menu is unmounted.
*/
componentWillUnmount() {
if (this.props.modButton) {
this.props.modButton.focus();
}
}
/** /**
* Render the list * Render the list
* @return {React.Component} List * @return {React.Component} List
@@ -412,82 +226,66 @@ export default class ModificationsMenu extends TranslatedComponent {
const _toggleBlueprintsMenu = this._toggleBlueprintsMenu; const _toggleBlueprintsMenu = this._toggleBlueprintsMenu;
const _toggleSpecialsMenu = this._toggleSpecialsMenu; const _toggleSpecialsMenu = this._toggleSpecialsMenu;
const _rollFull = this._rollBest; const _rollBest = this._rollBest;
const _rollExtreme = this._rollExtreme;
const _rollWorst = this._rollWorst; const _rollWorst = this._rollWorst;
const _rollFifty = this._rollFifty;
const _rollRandom = this._rollRandom; const _rollRandom = this._rollRandom;
const _reset = this._reset; const _reset = this._reset;
let blueprintLabel; let blueprintLabel;
let haveBlueprint = false; let haveBlueprint = false;
let blueprintTt; let blueprintTt;
let blueprintCv; if (m.blueprint && m.blueprint.name) {
// 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; blueprintLabel = translate(m.blueprint.name) + ' ' + translate('grade') + ' ' + m.blueprint.grade;
haveBlueprint = true; 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); 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 specialLabel;
let specialTt;
if (m.blueprint && m.blueprint.special) { if (m.blueprint && m.blueprint.special) {
specialLabel = m.blueprint.special.name; specialLabel = m.blueprint.special.name;
specialTt = specialToolTip(translate, m.blueprint.grades[m.blueprint.grade], m.grp, m, m.blueprint.special.edname);
} else { } else {
specialLabel = translate('PHRASE_SELECT_SPECIAL'); specialLabel = translate('PHRASE_SELECT_SPECIAL');
} }
const specials = this._renderSpecials(this.props, this.context); const specials = this._renderSpecials(this.props, this.context);
/**
* pnellesen - 05/28/2018 - added additional checks for specials.length below to ensure menus
* display correctly in cases where there are no specials (ex: AFMUs.)
*/
const showBlueprintsMenu = blueprintMenuOpened; const showBlueprintsMenu = blueprintMenuOpened;
const showSpecial = haveBlueprint && specials.length && !blueprintMenuOpened; const showSpecial = haveBlueprint && specials.length && !blueprintMenuOpened;
const showSpecialsMenu = specialMenuOpened && specials.length; const showSpecialsMenu = specialMenuOpened;
const showRolls = haveBlueprint && !blueprintMenuOpened && (!specialMenuOpened || !specials.length); const showRolls = haveBlueprint && !blueprintMenuOpened && !specialMenuOpened;
const showReset = !blueprintMenuOpened && (!specialMenuOpened || !specials.length) && haveBlueprint; const showReset = !blueprintMenuOpened && !specialMenuOpened;
const showMods = !blueprintMenuOpened && (!specialMenuOpened || !specials.length) && haveBlueprint; const showMods = !blueprintMenuOpened && !specialMenuOpened;
if (haveBlueprint) {
this.firstBPLabel = blueprintLabel;
} else {
this.firstBPLabel = 'selectBP';
}
return ( return (
<div <div
className={cn('select', this.props.className)} className={cn('select', this.props.className)}
onClick={(e) => e.stopPropagation() } onClick={(e) => e.stopPropagation() }
onContextMenu={stopCtxPropagation} onContextMenu={stopCtxPropagation}
ref={modItem => this.modItems['modMainDiv'] = modItem}
> >
{ showBlueprintsMenu | showSpecialsMenu ? '' : haveBlueprint ? { showBlueprintsMenu ? '' : haveBlueprint ?
<div tabIndex="0" className={ cn('section-menu button-inline-menu', { selected: blueprintMenuOpened })} style={{ cursor: 'pointer' }} onMouseOver={termtip.bind(null, blueprintTt)} onMouseOut={tooltip.bind(null, null)} onClick={_toggleBlueprintsMenu} onKeyDown={ this._keyDown } ref={modItems => this.modItems[this.firstBPLabel] = modItems}>{blueprintLabel}</div> : <div className={ cn('section-menu', { selected: blueprintMenuOpened })} style={{ cursor: 'pointer' }} onMouseOver={termtip.bind(null, blueprintTt)} onMouseOut={tooltip.bind(null, null)} onClick={_toggleBlueprintsMenu}>{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> } <div className={ cn('section-menu', { selected: blueprintMenuOpened })} style={{ cursor: 'pointer' }} onClick={_toggleBlueprintsMenu}>{translate('PHRASE_SELECT_BLUEPRINT')}</div> }
{ showBlueprintsMenu ? this._renderBlueprints(this.props, this.context) : null } { showBlueprintsMenu ? this._renderBlueprints(this.props, this.context) : 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 } { showSpecial ? <div className={ cn('section-menu', { selected: specialMenuOpened })} style={{ cursor: 'pointer' }} onClick={_toggleSpecialsMenu}>{specialLabel}</div> : null }
{ showSpecialsMenu ? specials : null } { showSpecialsMenu ? specials : null }
{ showReset ? <div tabIndex="0" className={'section-menu button-inline-menu warning'} style={{ cursor: 'pointer' }} onClick={_reset} onKeyDown={ this._keyDown } onMouseOver={termtip.bind(null, 'PHRASE_BLUEPRINT_RESET')} onMouseOut={tooltip.bind(null, null)}> { translate('reset') } </div> : null } { showRolls || showReset ?
{ showRolls ?
<table style={{ width: '100%', backgroundColor: 'transparent' }}> <table style={{ width: '100%', backgroundColor: 'transparent' }}>
<tbody> <tbody>
{ showRolls ? { showRolls ?
<tr> <tr>
<td tabIndex="0" className={ cn('section-menu button-inline-menu', { active: false }) }> { translate('roll') }: </td> <td> { 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 style={{ cursor: 'pointer' }} onClick={_rollWorst} onMouseOver={termtip.bind(null, 'PHRASE_BLUEPRINT_WORST')} onMouseOut={tooltip.bind(null, null)}> { translate('worst') } </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 style={{ cursor: 'pointer' }} onClick={_rollBest}onMouseOver={termtip.bind(null, 'PHRASE_BLUEPRINT_BEST')} onMouseOut={tooltip.bind(null, null)}> { translate('best') } </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 style={{ cursor: 'pointer' }} onClick={_rollExtreme}onMouseOver={termtip.bind(null, 'PHRASE_BLUEPRINT_EXTREME')} onMouseOut={tooltip.bind(null, null)}> { translate('extreme') } </td>
<td tabIndex="0" className={ cn('section-menu button-inline-menu', { active: blueprintCv === null || blueprintCv % 50 != 0 })} style={{ cursor: 'pointer' }} onClick={_rollRandom} onKeyDown={ this._keyDown } onMouseOver={termtip.bind(null, 'PHRASE_BLUEPRINT_RANDOM')} onMouseOut={tooltip.bind(null, null)}> { translate('random') } </td> <td 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>
</tr> : null } </tr> : null }
</tbody> </tbody>
</table> : null } </table> : null }
{ showMods ? <hr /> : null }
{ showMods ? { showMods ?
<span onMouseOver={termtip.bind(null, 'HELP_MODIFICATIONS_MENU')} onMouseOut={tooltip.bind(null, null)} > <span onMouseOver={termtip.bind(null, 'HELP_MODIFICATIONS_MENU')} onMouseOut={tooltip.bind(null, null)} >
{ this._renderModifications(this.props) } { this._renderModifications(this.props) }

View File

@@ -42,63 +42,6 @@ export function weaponComparator(translate, propComparator, desc) {
}; };
} }
/**
* Creates a tooltip that shows damage by type.
* @param {function} translate Translation function
* @param {Object} formats Object that holds format functions
* @param {Calc.SDps} sdpsObject Object that holds sdps split up by type
* @returns {Array} Tooltip
*/
function getSDpsTooltip(translate, formats, sdpsObject) {
return Object.keys(sdpsObject).filter(key => sdpsObject[key])
.map(key => {
return (
<div key={key}>
{translate(key) + ' ' + formats.f1(sdpsObject[key])}
</div>
);
});
}
/**
* Returns a data object used by {@link PieChart} that shows damage by type.
* @param {function} translate Translation function
* @param {Calc.SDps} sdpsObject Object that holds sdps split up by type
* @returns {Object} Data object
*/
function getSDpsData(translate, sdpsObject) {
return Object.keys(sdpsObject).map(key => {
return {
value: Math.round(sdpsObject[key]),
label: translate(key)
};
});
}
/**
* Adds all damage of `add` onto `addOn`.
* @param {Calc.SDps} addOn Object that holds sdps split up by type (will be mutated)
* @param {Calc.SDps} add Object that holds sdps split up by type
*/
function addSDps(addOn, add) {
Object.keys(addOn).map(k => addOn[k] += (add[k] ? add[k] : 0));
}
/**
* Calculates the overall sdps of an sdps object.
* @param {Calc.SDps} sdpsObject Object that holds sdps spluit up by type
*/
function sumSDps(sdpsObject) {
if (sdpsObject.total) {
return sdpsObject.total;
}
return Object.keys(sdpsObject).reduce(
(acc, k) => acc + (sdpsObject[k] ? sdpsObject[k] : 0),
0
);
}
/** /**
* Offence information * Offence information
* Offence information consists of four panels: * Offence information consists of four panels:
@@ -127,7 +70,7 @@ export default class Offence extends TranslatedComponent {
this._sort = this._sort.bind(this); this._sort = this._sort.bind(this);
const damage = Calc.offenceMetrics(props.ship, props.opponent, props.wep, props.opponentSys, props.engagementrange); const damage = Calc.offenceMetrics(props.ship, props.opponent, props.wep, props.opponentSys, props.engagementrange);
this.state = { this.state = {
predicate: 'n', predicate: 'n',
desc: true, desc: true,
damage damage
@@ -201,36 +144,51 @@ export default class Offence extends TranslatedComponent {
const timeToDrain = Calc.timeToDrainWep(ship, wep); const timeToDrain = Calc.timeToDrainWep(ship, wep);
let absoluteShieldsSDps = 0;
let explosiveShieldsSDps = 0;
let kineticShieldsSDps = 0;
let thermalShieldsSDps = 0;
let absoluteArmourSDps = 0;
let explosiveArmourSDps = 0;
let kineticArmourSDps = 0;
let thermalArmourSDps = 0;
let totalSEps = 0; let totalSEps = 0;
let totalSDpsObject = {'absolute': 0, 'explosive': 0, 'kinetic': 0, 'thermal': 0};
let shieldsSDpsObject = {'absolute': 0, 'explosive': 0, 'kinetic': 0, 'thermal': 0};
let armourSDpsObject = {'absolute': 0, 'explosive': 0, 'kinetic': 0, 'thermal': 0};
const rows = []; const rows = [];
for (let i = 0; i < damage.length; i++) { for (let i = 0; i < damage.length; i++) {
const weapon = damage[i]; const weapon = damage[i];
totalSEps += weapon.seps; totalSEps += weapon.seps;
addSDps(totalSDpsObject, weapon.sdps.base); absoluteShieldsSDps += weapon.sdps.shields.absolute;
addSDps(shieldsSDpsObject, weapon.sdps.shields); explosiveShieldsSDps += weapon.sdps.shields.explosive;
addSDps(armourSDpsObject, weapon.sdps.armour); kineticShieldsSDps += weapon.sdps.shields.kinetic;
thermalShieldsSDps += weapon.sdps.shields.thermal;
const baseSDpsTooltipDetails = getSDpsTooltip(translate, formats, weapon.sdps.base); absoluteArmourSDps += weapon.sdps.armour.absolute;
explosiveArmourSDps += weapon.sdps.armour.explosive;
kineticArmourSDps += weapon.sdps.armour.kinetic;
thermalArmourSDps += weapon.sdps.armour.thermal;
const effectivenessShieldsTooltipDetails = []; const effectivenessShieldsTooltipDetails = [];
effectivenessShieldsTooltipDetails.push(<div key='range'>{translate('range') + ' ' + formats.pct1(weapon.effectiveness.shields.range)}</div>); effectivenessShieldsTooltipDetails.push(<div key='range'>{translate('range') + ' ' + formats.pct1(weapon.effectiveness.shields.range)}</div>);
effectivenessShieldsTooltipDetails.push(<div key='resistance'>{translate('resistance') + ' ' + formats.pct1(weapon.effectiveness.shields.resistance)}</div>); effectivenessShieldsTooltipDetails.push(<div key='resistance'>{translate('resistance') + ' ' + formats.pct1(weapon.effectiveness.shields.resistance)}</div>);
effectivenessShieldsTooltipDetails.push(<div key='power distributor'>{translate('power distributor') + ' ' + formats.pct1(weapon.effectiveness.shields.sys)}</div>); effectivenessShieldsTooltipDetails.push(<div key='power distributor'>{translate('power distributor') + ' ' + formats.pct1(weapon.effectiveness.shields.sys)}</div>);
const effectiveShieldsSDpsTooltipDetails = getSDpsTooltip(translate, formats, weapon.sdps.armour); const effectiveShieldsSDpsTooltipDetails = [];
if (weapon.sdps.shields.absolute) effectiveShieldsSDpsTooltipDetails.push(<div key='absolute'>{translate('absolute') + ' ' + formats.f1(weapon.sdps.shields.absolute)}</div>);
if (weapon.sdps.shields.explosive) effectiveShieldsSDpsTooltipDetails.push(<div key='explosive'>{translate('explosive') + ' ' + formats.f1(weapon.sdps.shields.explosive)}</div>);
if (weapon.sdps.shields.kinetic) effectiveShieldsSDpsTooltipDetails.push(<div key='kinetic'>{translate('kinetic') + ' ' + formats.f1(weapon.sdps.shields.kinetic)}</div>);
if (weapon.sdps.shields.thermal) effectiveShieldsSDpsTooltipDetails.push(<div key='thermal'>{translate('thermal') + ' ' + formats.f1(weapon.sdps.shields.thermal)}</div>);
const effectivenessArmourTooltipDetails = []; const effectivenessArmourTooltipDetails = [];
effectivenessArmourTooltipDetails.push(<div key='range'>{translate('range') + ' ' + formats.pct1(weapon.effectiveness.armour.range)}</div>); effectivenessArmourTooltipDetails.push(<div key='range'>{translate('range') + ' ' + formats.pct1(weapon.effectiveness.armour.range)}</div>);
effectivenessArmourTooltipDetails.push(<div key='resistance'>{translate('resistance') + ' ' + formats.pct1(weapon.effectiveness.armour.resistance)}</div>); effectivenessArmourTooltipDetails.push(<div key='resistance'>{translate('resistance') + ' ' + formats.pct1(weapon.effectiveness.armour.resistance)}</div>);
effectivenessArmourTooltipDetails.push(<div key='hardness'>{translate('hardness') + ' ' + formats.pct1(weapon.effectiveness.armour.hardness)}</div>); effectivenessArmourTooltipDetails.push(<div key='hardness'>{translate('hardness') + ' ' + formats.pct1(weapon.effectiveness.armour.hardness)}</div>);
const effectiveArmourSDpsTooltipDetails = [];
const effectiveArmourSDpsTooltipDetails = getSDpsTooltip(translate, formats, weapon.sdps.armour); if (weapon.sdps.armour.absolute) effectiveArmourSDpsTooltipDetails.push(<div key='absolute'>{translate('absolute') + ' ' + formats.f1(weapon.sdps.armour.absolute)}</div>);
if (weapon.sdps.armour.explosive) effectiveArmourSDpsTooltipDetails.push(<div key='explosive'>{translate('explosive') + ' ' + formats.f1(weapon.sdps.armour.explosive)}</div>);
if (weapon.sdps.armour.kinetic) effectiveArmourSDpsTooltipDetails.push(<div key='kinetic'>{translate('kinetic') + ' ' + formats.f1(weapon.sdps.armour.kinetic)}</div>);
if (weapon.sdps.armour.thermal) effectiveArmourSDpsTooltipDetails.push(<div key='thermal'>{translate('thermal') + ' ' + formats.f1(weapon.sdps.armour.thermal)}</div>);
rows.push( rows.push(
<tr key={weapon.id}> <tr key={weapon.id}>
@@ -241,25 +199,27 @@ export default class Offence extends TranslatedComponent {
{weapon.classRating} {translate(weapon.name)} {weapon.classRating} {translate(weapon.name)}
{weapon.engineering ? ' (' + weapon.engineering + ')' : null } {weapon.engineering ? ' (' + weapon.engineering + ')' : null }
</td> </td>
<td className='ri'><span onMouseOver={termtip.bind(null, baseSDpsTooltipDetails)} onMouseOut={tooltip.bind(null, null)}>{formats.f1(weapon.sdps.base.total)}</span></td>
<td className='ri'><span onMouseOver={termtip.bind(null, effectiveShieldsSDpsTooltipDetails)} onMouseOut={tooltip.bind(null, null)}>{formats.f1(weapon.sdps.shields.total)}</span></td> <td className='ri'><span onMouseOver={termtip.bind(null, effectiveShieldsSDpsTooltipDetails)} onMouseOut={tooltip.bind(null, null)}>{formats.f1(weapon.sdps.shields.total)}</span></td>
<td className='ri'><span onMouseOver={termtip.bind(null, effectivenessShieldsTooltipDetails)} onMouseOut={tooltip.bind(null, null)}>{formats.pct1(weapon.effectiveness.shields.total)}</span></td> <td className='ri'><span onMouseOver={termtip.bind(null, effectivenessShieldsTooltipDetails)} onMouseOut={tooltip.bind(null, null)}>{formats.pct1(weapon.effectiveness.shields.total)}</span></td>
<td className='ri'><span onMouseOver={termtip.bind(null, effectiveArmourSDpsTooltipDetails)} onMouseOut={tooltip.bind(null, null)}>{formats.f1(weapon.sdps.armour.total)}</span></td> <td className='ri'><span onMouseOver={termtip.bind(null, effectiveArmourSDpsTooltipDetails)} onMouseOut={tooltip.bind(null, null)}>{formats.f1(weapon.sdps.armour.total)}</span></td>
<td className='ri'><span onMouseOver={termtip.bind(null, effectivenessArmourTooltipDetails)} onMouseOut={tooltip.bind(null, null)}>{formats.pct1(weapon.effectiveness.armour.total)}</span></td> <td className='ri'><span onMouseOver={termtip.bind(null, effectivenessArmourTooltipDetails)} onMouseOut={tooltip.bind(null, null)}>{formats.pct1(weapon.effectiveness.armour.total)}</span></td>
</tr>); </tr>);
} }
const totalSDps = sumSDps(totalSDpsObject); const totalShieldsSDps = absoluteShieldsSDps + explosiveShieldsSDps + kineticShieldsSDps + thermalShieldsSDps;
const totalSDpsTooltipDetails = getSDpsTooltip(translate, formats, totalSDpsObject); const totalArmourSDps = absoluteArmourSDps + explosiveArmourSDps + kineticArmourSDps + thermalArmourSDps;
const totalSDpsData = getSDpsData(translate, totalSDpsObject);
const totalShieldsSDps = sumSDps(shieldsSDpsObject); const shieldsSDpsData = [];
const totalShieldsSDpsTooltipDetails = getSDpsTooltip(translate, formats, shieldsSDpsObject); shieldsSDpsData.push({ value: Math.round(absoluteShieldsSDps), label: translate('absolute') });
const shieldsSDpsData = getSDpsData(translate, shieldsSDpsObject); shieldsSDpsData.push({ value: Math.round(explosiveShieldsSDps), label: translate('explosive') });
shieldsSDpsData.push({ value: Math.round(kineticShieldsSDps), label: translate('kinetic') });
shieldsSDpsData.push({ value: Math.round(thermalShieldsSDps), label: translate('thermal') });
const totalArmourSDps = sumSDps(armourSDpsObject); const armourSDpsData = [];
const totalArmourSDpsTooltipDetails = getSDpsTooltip(translate, formats, armourSDpsObject); armourSDpsData.push({ value: Math.round(absoluteArmourSDps), label: translate('absolute') });
const armourSDpsData = getSDpsData(translate, armourSDpsObject); armourSDpsData.push({ value: Math.round(explosiveArmourSDps), label: translate('explosive') });
armourSDpsData.push({ value: Math.round(kineticArmourSDps), label: translate('kinetic') });
armourSDpsData.push({ value: Math.round(thermalArmourSDps), label: translate('thermal') });
const timeToDepleteShields = Calc.timeToDeplete(opponentShields.total, totalShieldsSDps, totalSEps, pd.getWeaponsCapacity(), pd.getWeaponsRechargeRate() * (wep / 4)); const timeToDepleteShields = Calc.timeToDeplete(opponentShields.total, totalShieldsSDps, totalSEps, pd.getWeaponsCapacity(), pd.getWeaponsRechargeRate() * (wep / 4));
const timeToDepleteArmour = Calc.timeToDeplete(opponentArmour.total, totalArmourSDps, totalSEps, pd.getWeaponsCapacity(), pd.getWeaponsRechargeRate() * (wep / 4)); const timeToDepleteArmour = Calc.timeToDeplete(opponentArmour.total, totalArmourSDps, totalSEps, pd.getWeaponsCapacity(), pd.getWeaponsRechargeRate() * (wep / 4));
@@ -271,31 +231,19 @@ export default class Offence extends TranslatedComponent {
<thead> <thead>
<tr className='main'> <tr className='main'>
<th rowSpan='2' className='sortable' onClick={sortOrder.bind(this, 'n')}>{translate('weapon')}</th> <th rowSpan='2' className='sortable' onClick={sortOrder.bind(this, 'n')}>{translate('weapon')}</th>
<th colSpan='1'>{translate('overall')}</th>
<th colSpan='2'>{translate('opponent\'s shields')}</th> <th colSpan='2'>{translate('opponent\'s shields')}</th>
<th colSpan='2'>{translate('opponent\'s armour')}</th> <th colSpan='2'>{translate('opponent\'s armour')}</th>
</tr> </tr>
<tr> <tr>
<th className='lft sortable' onMouseOver={termtip.bind(null, 'TT_EFFECTIVE_SDPS_SHIELDS')} onMouseOut={tooltip.bind(null, null)} onClick={sortOrder.bind(this, 'esdpss')}>{'sdps'}</th>
<th className='lft sortable' onMouseOver={termtip.bind(null, 'TT_EFFECTIVE_SDPS_SHIELDS')} onMouseOut={tooltip.bind(null, null)} onClick={sortOrder.bind(this, 'esdpss')}>{'sdps'}</th> <th className='lft sortable' onMouseOver={termtip.bind(null, 'TT_EFFECTIVE_SDPS_SHIELDS')} onMouseOut={tooltip.bind(null, null)} onClick={sortOrder.bind(this, 'esdpss')}>{'sdps'}</th>
<th className='sortable' onMouseOver={termtip.bind(null, 'TT_EFFECTIVENESS_SHIELDS')} onMouseOut={tooltip.bind(null, null)}onClick={sortOrder.bind(this, 'es')}>{'eft'}</th> <th className='sortable' onMouseOver={termtip.bind(null, 'TT_EFFECTIVENESS_SHIELDS')} onMouseOut={tooltip.bind(null, null)}onClick={sortOrder.bind(this, 'es')}>{'eft'}</th>
<th className='lft sortable' onMouseOver={termtip.bind(null, 'TT_EFFECTIVE_SDPS_ARMOUR')} onMouseOut={tooltip.bind(null, null)}onClick={sortOrder.bind(this, 'esdpsh')}>{'sdps'}</th> <th className='lft sortable' onMouseOver={termtip.bind(null, 'TT_EFFECTIVE_SDPS_ARMOUR')} onMouseOut={tooltip.bind(null, null)}onClick={sortOrder.bind(this, 'esdpsh')}>{'sdps'}</th>
<th className='sortable' onMouseOver={termtip.bind(null, 'TT_EFFECTIVENESS_ARMOUR')} onMouseOut={tooltip.bind(null, null)}onClick={sortOrder.bind(this, 'eh')}>{'eft'}</th> <th className='sortable' onMouseOver={termtip.bind(null, 'TT_EFFECTIVENESS_ARMOUR')} onMouseOut={tooltip.bind(null, null)}onClick={sortOrder.bind(this, 'eh')}>{'eft'}</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{rows} {rows}
{rows.length > 0 && </tbody>
<tr>
<td></td>
<td className='ri'><span onMouseOver={termtip.bind(null, totalSDpsTooltipDetails)} onMouseOut={tooltip.bind(null, null)}>={formats.f1(totalSDps)}</span></td>
<td className='ri'><span onMouseOver={termtip.bind(null, totalShieldsSDpsTooltipDetails)} onMouseOut={tooltip.bind(null, null)}>={formats.f1(totalShieldsSDps)}</span></td>
<td></td>
<td className='ri'><span onMouseOver={termtip.bind(null, totalArmourSDpsTooltipDetails)} onMouseOut={tooltip.bind(null, null)}>={formats.f1(totalArmourSDps)}</span></td>
<td></td>
</tr>
}
</tbody>
</table> </table>
</div> </div>
<div className='group quarter'> <div className='group quarter'>
@@ -306,10 +254,6 @@ export default class Offence extends TranslatedComponent {
<h2 onMouseOver={termtip.bind(null, translate('TT_EFFECTIVE_SDPS_ARMOUR'))} onMouseOut={tooltip.bind(null, null)}>{translate('PHRASE_EFFECTIVE_SDPS_ARMOUR')}<br/>{formats.f1(totalArmourSDps)}</h2> <h2 onMouseOver={termtip.bind(null, translate('TT_EFFECTIVE_SDPS_ARMOUR'))} onMouseOut={tooltip.bind(null, null)}>{translate('PHRASE_EFFECTIVE_SDPS_ARMOUR')}<br/>{formats.f1(totalArmourSDps)}</h2>
<h2 onMouseOver={termtip.bind(null, translate('TT_TIME_TO_REMOVE_ARMOUR'))} onMouseOut={tooltip.bind(null, null)}>{translate('PHRASE_TIME_TO_REMOVE_ARMOUR')}<br/>{timeToDepleteArmour === Infinity ? translate('never') : formats.time(timeToDepleteArmour)}</h2> <h2 onMouseOver={termtip.bind(null, translate('TT_TIME_TO_REMOVE_ARMOUR'))} onMouseOut={tooltip.bind(null, null)}>{translate('PHRASE_TIME_TO_REMOVE_ARMOUR')}<br/>{timeToDepleteArmour === Infinity ? translate('never') : formats.time(timeToDepleteArmour)}</h2>
</div> </div>
<div className='group quarter'>
<h2 onMouseOver={termtip.bind(null, translate('PHRASE_OVERALL_DAMAGE'))} onMouseOut={tooltip.bind(null, null)}>{translate('overall damage')}</h2>
<PieChart data={totalSDpsData} />
</div>
<div className='group quarter'> <div className='group quarter'>
<h2 onMouseOver={termtip.bind(null, translate('PHRASE_SHIELD_DAMAGE'))} onMouseOut={tooltip.bind(null, null)}>{translate('shield damage sources')}</h2> <h2 onMouseOver={termtip.bind(null, translate('PHRASE_SHIELD_DAMAGE'))} onMouseOut={tooltip.bind(null, null)}>{translate('shield damage sources')}</h2>
<PieChart data={shieldsSDpsData} /> <PieChart data={shieldsSDpsData} />

View File

@@ -1,92 +1,97 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import ContainerDimensions from 'react-container-dimensions'; import Measure from 'react-measure';
import * as d3 from 'd3'; import * as d3 from 'd3';
const CORIOLIS_COLOURS = ['#FF8C0D', '#1FB0FF', '#71A052', '#D5D54D']; const CORIOLIS_COLOURS = ['#FF8C0D', '#1FB0FF', '#71A052', '#D5D54D'];
const LABEL_COLOUR = '#000000'; const LABEL_COLOUR = '#000000';
/** /**
* A pie chart * A pie chart
*/ */
export default class PieChart extends Component { export default class PieChart extends Component {
static propTypes = { static propTypes = {
data : PropTypes.array.isRequired data : PropTypes.array.isRequired
}; };
/** /**
* Constructor * Constructor
* @param {Object} props React Component properties * @param {Object} props React Component properties
* @param {Object} context React Component context * @param {Object} context React Component context
*/ */
constructor(props, context) { constructor(props, context) {
super(props); super(props);
this.pie = d3.pie().value((d) => d.value); this.pie = d3.pie().value((d) => d.value);
this.colors = CORIOLIS_COLOURS; this.colors = CORIOLIS_COLOURS;
this.arc = d3.arc(); this.arc = d3.arc();
this.arc.innerRadius(0); this.arc.innerRadius(0);
}
this.state = {
dimensions: {
/** width: 100,
* Generate a slice of the pie chart height: 100
* @param {Object} d the data for this slice }
* @param {number} i the index of this slice };
* @param {number} width the current width of the parent container }
* @returns {Object} the SVG for the slice
*/
sliceGenerator(d, i, width) { /**
if (!d || d.value == 0) { * Generate a slice of the pie chart
// Ignore 0 values * @param {Object} d the data for this slice
return null; * @param {number} i the index of this slice
} * @returns {Object} the SVG for the slice
*/
const { data } = this.props; sliceGenerator(d, i) {
if (!d || d.value == 0) {
// Push the labels further out from the centre of the slice // Ignore 0 values
let [labelX, labelY] = this.arc.centroid(d); return null;
const labelTranslate = `translate(${labelX * 1.5}, ${labelY * 1.5})`; }
// Put the keys in a line with equal spacing const { width, height } = this.state.dimensions;
const nonZeroItems = data.filter(d => d.value != 0).length; const { data } = this.props;
const thisItemIndex = data.slice(0, i + 1).filter(d => d.value != 0).length - 1;
const keyX = -width / 2 + (width / nonZeroItems) * (thisItemIndex + 0.5); // Push the labels further out from the centre of the slice
const keyTranslate = `translate(${keyX}, ${width * 0.45})`; let [labelX, labelY] = this.arc.centroid(d);
const labelTranslate = `translate(${labelX * 1.5}, ${labelY * 1.5})`;
return (
<g key={`group-${i}`}> // Put the keys in a line with equal spacing
<path key={`arc-${i}`} d={this.arc(d)} style={{ fill: this.colors[i] }} /> const nonZeroItems = data.filter(d => d.value != 0).length;
<text key={`label-${i}`} transform={labelTranslate} style={{ strokeWidth: '0px', fill: LABEL_COLOUR }} textAnchor='middle'>{d.value}</text> const thisItemIndex = data.slice(0, i + 1).filter(d => d.value != 0).length - 1;
<text key={`key-${i}`} transform={keyTranslate} style={{ strokeWidth:'0px', fill: this.colors[i] }} textAnchor='middle'>{d.data.label}</text> const keyX = -width / 2 + (width / nonZeroItems) * (thisItemIndex + 0.5);
</g> const keyTranslate = `translate(${keyX}, ${width * 0.45})`;
);
} return (
<g key={`group-${i}`}>
/** <path key={`arc-${i}`} d={this.arc(d)} style={{ fill: this.colors[i] }} />
* Render the component <text key={`label-${i}`} transform={labelTranslate} style={{ strokeWidth: '0px', fill: LABEL_COLOUR }} textAnchor='middle'>{d.value}</text>
* @returns {object} Markup <text key={`key-${i}`} transform={keyTranslate} style={{ strokeWidth:'0px', fill: this.colors[i] }} textAnchor='middle'>{d.data.label}</text>
*/ </g>
render() { );
return ( }
<ContainerDimensions>
{ ({ width }) => { /**
const pie = this.pie(this.props.data), * Render the component
translate = `translate(${width / 2}, ${width * 0.4})`; * @returns {object} Markup
*/
this.arc.outerRadius(width * 0.4); render() {
return ( const { width, height } = this.state.dimensions;
<div width={width} height={width}> const pie = this.pie(this.props.data),
<svg style={{ stroke: 'None' }} width={width} height={width * 0.9}> translate = `translate(${width / 2}, ${width * 0.4})`;
<g transform={translate}>
{pie.map((d, i) => this.sliceGenerator(d, i, width))} this.arc.outerRadius(width * 0.4);
</g>
</svg> return (
</div> <Measure width='100%' whitelist={['width', 'top']} onMeasure={ (dimensions) => { this.setState({ dimensions }); }}>
); <div width={width} height={width}>
}} <svg style={{ stroke: 'None' }} width={width} height={width * 0.9}>
</ContainerDimensions> <g transform={translate}>
); {pie.map((d, i) => this.sliceGenerator(d, i))}
} </g>
} </svg>
</div>
</Measure>
);
}
}

View File

@@ -15,34 +15,22 @@ export default class ShipSummaryTable extends TranslatedComponent {
cargo: PropTypes.number.isRequired, cargo: PropTypes.number.isRequired,
fuel: PropTypes.number.isRequired, fuel: PropTypes.number.isRequired,
marker: PropTypes.string.isRequired, marker: PropTypes.string.isRequired,
pips: PropTypes.object.isRequired
}; };
/**
* The ShipSummaryTable constructor
* @param {Object} props The props
*/
constructor(props) {
super(props);
this.didContextChange = this.didContextChange.bind(this);
this.state = {
shieldColour: 'blue'
};
}
/** /**
* Render the table * Render the table
* @return {React.Component} Summary table * @return {React.Component} Summary table
*/ */
render() { render() {
const { ship, cargo, fuel, pips } = this.props; const { ship, cargo, fuel } = this.props;
let { language, tooltip, termtip } = this.context; let { language, tooltip, termtip } = this.context;
let translate = language.translate; let translate = language.translate;
let u = language.units; let u = language.units;
let formats = language.formats; let formats = language.formats;
let { time, int, round, f1, f2 } = formats; let { time, int, round, f1, f2 } = formats;
let hide = tooltip.bind(null, null); let hide = tooltip.bind(null, null);
const shieldGenerator = ship.findInternalByGroup('sg') || ship.findInternalByGroup('psg');
const shieldGenerator = ship.findInternalByGroup('sg');
const sgClassNames = cn({ warning: shieldGenerator && !ship.shield, muted: !shieldGenerator }); const sgClassNames = cn({ warning: shieldGenerator && !ship.shield, muted: !shieldGenerator });
const sgTooltip = shieldGenerator ? 'TT_SUMMARY_SHIELDS' : 'TT_SUMMARY_SHIELDS_NONFUNCTIONAL'; const sgTooltip = shieldGenerator ? 'TT_SUMMARY_SHIELDS' : 'TT_SUMMARY_SHIELDS_NONFUNCTIONAL';
const timeToDrain = Calc.timeToDrainWep(ship, 4); const timeToDrain = Calc.timeToDrainWep(ship, 4);
@@ -50,163 +38,64 @@ export default class ShipSummaryTable extends TranslatedComponent {
const speedTooltip = canThrust ? 'TT_SUMMARY_SPEED' : 'TT_SUMMARY_SPEED_NONFUNCTIONAL'; const speedTooltip = canThrust ? 'TT_SUMMARY_SPEED' : 'TT_SUMMARY_SPEED_NONFUNCTIONAL';
const canBoost = ship.canBoost(cargo, ship.fuelCapacity); const canBoost = ship.canBoost(cargo, ship.fuelCapacity);
const boostTooltip = canBoost ? 'TT_SUMMARY_BOOST' : canThrust ? 'TT_SUMMARY_BOOST_NONFUNCTIONAL' : 'TT_SUMMARY_SPEED_NONFUNCTIONAL'; const boostTooltip = canBoost ? 'TT_SUMMARY_BOOST' : canThrust ? 'TT_SUMMARY_BOOST_NONFUNCTIONAL' : 'TT_SUMMARY_SPEED_NONFUNCTIONAL';
const sgMetrics = Calc.shieldMetrics(ship, pips.sys);
const shipBoost = canBoost ? Calc.calcBoost(ship) : 'No Boost';
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'> return <div id='summary'>
<div style={{display: "table", width: "100%"}}> <table id='summaryTable'>
<div style={{display: "table-row"}}> <thead>
<table className={'summaryTable'}> <tr className='main'>
<thead> <th rowSpan={2} className={ cn({ 'bg-warning-disabled': !canThrust }) }>{translate('speed')}</th>
<tr className='main'> <th rowSpan={2} className={ cn({ 'bg-warning-disabled': !canBoost }) }>{translate('boost')}</th>
<th rowSpan={2} className={ cn({ 'bg-warning-disabled': !canThrust }) }>{translate('speed')}</th> <th colSpan={5}>{translate('jump range')}</th>
<th rowSpan={2} className={ cn({ 'bg-warning-disabled': !canBoost }) }>{translate('boost')}</th> <th rowSpan={2}>{translate('shield')}</th>
<th colSpan={5}>{translate('jump range')}</th> <th rowSpan={2}>{translate('integrity')}</th>
<th rowSpan={2}>{translate('shield')}</th> <th rowSpan={2}>{translate('DPS')}</th>
<th rowSpan={2}>{translate('integrity')}</th> <th rowSpan={2}>{translate('EPS')}</th>
<th rowSpan={2}>{translate('DPS')}</th> <th rowSpan={2}>{translate('TTD')}</th>
<th rowSpan={2}>{translate('EPS')}</th> {/* <th onMouseEnter={termtip.bind(null, 'heat per second')} onMouseLeave={hide} rowSpan={2}>{translate('HPS')}</th> */}
<th rowSpan={2}>{translate('TTD')}</th> <th rowSpan={2}>{translate('cargo')}</th>
{/* <th onMouseEnter={termtip.bind(null, 'heat per second')} onMouseLeave={hide} rowSpan={2}>{translate('HPS')}</th> */} <th rowSpan={2}>{translate('fuel')}</th>
<th rowSpan={2}>{translate('cargo')}</th> <th colSpan={3}>{translate('mass')}</th>
<th rowSpan={2}>{translate('pax')}</th> <th onMouseEnter={termtip.bind(null, 'hull hardness', { cap: 0 })} onMouseLeave={hide} rowSpan={2}>{translate('hrd')}</th>
<th rowSpan={2}>{translate('fuel')}</th> <th rowSpan={2}>{translate('crew')}</th>
<th colSpan={3}>{translate('mass')}</th> <th onMouseEnter={termtip.bind(null, 'mass lock factor', { cap: 0 })} onMouseLeave={hide} rowSpan={2}>{translate('MLF')}</th>
<th onMouseEnter={termtip.bind(null, 'hull hardness', { cap: 0 })} onMouseLeave={hide} rowSpan={2}>{translate('hrd')}</th> </tr>
<th rowSpan={2}>{translate('crew')}</th> <tr>
<th onMouseEnter={termtip.bind(null, 'mass lock factor', { cap: 0 })} onMouseLeave={hide} rowSpan={2}>{translate('MLF')}</th> <th className='lft'>{translate('max')}</th>
<th onMouseEnter={termtip.bind(null, 'TT_SUMMARY_BOOST_TIME', { cap: 0 })} onMouseLeave={hide} rowSpan={2}>{translate('boost time')}</th> <th>{translate('unladen')}</th>
</tr> <th>{translate('laden')}</th>
<tr> <th>{translate('total unladen')}</th>
<th className='lft'>{translate('max')}</th> <th>{translate('total laden')}</th>
<th>{translate('unladen')}</th> <th className='lft'>{translate('hull')}</th>
<th>{translate('laden')}</th> <th>{translate('unladen')}</th>
<th>{translate('total unladen')}</th> <th>{translate('laden')}</th>
<th>{translate('total laden')}</th> </tr>
<th className='lft'>{translate('hull')}</th> </thead>
<th>{translate('unladen')}</th> <tbody>
<th>{translate('laden')}</th> <tr>
</tr> <td onMouseEnter={termtip.bind(null, speedTooltip, { cap: 0 })} onMouseLeave={hide}>{ canThrust ? <span>{int(ship.calcSpeed(4, ship.fuelCapacity, 0, false))}{u['m/s']}</span> : <span className='warning'>0 <Warning/></span> }</td>
</thead> <td onMouseEnter={termtip.bind(null, boostTooltip, { cap: 0 })} onMouseLeave={hide}>{ canBoost ? <span>{int(ship.calcSpeed(4, ship.fuelCapacity, 0, true))}{u['m/s']}</span> : <span className='warning'>0 <Warning/></span> }</td>
<tbody> <td><span onMouseEnter={termtip.bind(null, 'TT_SUMMARY_MAX_SINGLE_JUMP', { cap: 0 })} onMouseLeave={hide}>{f2(Calc.jumpRange(ship.unladenMass + ship.standard[2].m.getMaxFuelPerJump(), ship.standard[2].m, ship.standard[2].m.getMaxFuelPerJump()))}{u.LY}</span></td>
<tr> <td><span onMouseEnter={termtip.bind(null, 'TT_SUMMARY_UNLADEN_SINGLE_JUMP', { cap: 0 })} onMouseLeave={hide}>{f2(Calc.jumpRange(ship.unladenMass + ship.fuelCapacity, ship.standard[2].m, ship.fuelCapacity))}{u.LY}</span></td>
<td onMouseEnter={termtip.bind(null, speedTooltip, { cap: 0 })} onMouseLeave={hide}>{ canThrust ? <span>{int(ship.calcSpeed(4, ship.fuelCapacity, 0, false))}{u['m/s']}</span> : <span className='warning'>0 <Warning/></span> }</td> <td><span onMouseEnter={termtip.bind(null, 'TT_SUMMARY_LADEN_SINGLE_JUMP', { cap: 0 })} onMouseLeave={hide}>{f2(Calc.jumpRange(ship.unladenMass + ship.fuelCapacity + ship.cargoCapacity, ship.standard[2].m, ship.fuelCapacity))}{u.LY}</span></td>
<td onMouseEnter={termtip.bind(null, boostTooltip, { cap: 0 })} onMouseLeave={hide}>{ canBoost ? <span>{int(ship.calcSpeed(4, ship.fuelCapacity, 0, true))}{u['m/s']}</span> : <span className='warning'>0 <Warning/></span> }</td> <td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_UNLADEN_TOTAL_JUMP', { cap: 0 })} onMouseLeave={hide}>{f2(Calc.totalJumpRange(ship.unladenMass + ship.fuelCapacity, ship.standard[2].m, ship.fuelCapacity))}{u.LY}</td>
<td><span onMouseEnter={termtip.bind(null, 'TT_SUMMARY_MAX_SINGLE_JUMP', { cap: 0 })} onMouseLeave={hide}>{f2(Calc.jumpRange(ship.unladenMass + ship.standard[2].m.getMaxFuelPerJump(), ship.standard[2].m, ship.standard[2].m.getMaxFuelPerJump(), ship))}{u.LY}</span></td> <td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_LADEN_TOTAL_JUMP', { cap: 0 })} onMouseLeave={hide}>{f2(Calc.totalJumpRange(ship.unladenMass + ship.fuelCapacity + ship.cargoCapacity, ship.standard[2].m, ship.fuelCapacity))}{u.LY}</td>
<td><span onMouseEnter={termtip.bind(null, 'TT_SUMMARY_UNLADEN_SINGLE_JUMP', { cap: 0 })} onMouseLeave={hide}>{f2(Calc.jumpRange(ship.unladenMass + ship.fuelCapacity, ship.standard[2].m, ship.fuelCapacity, ship))}{u.LY}</span></td> <td className={sgClassNames} onMouseEnter={termtip.bind(null, sgTooltip, { cap: 0 })} onMouseLeave={hide}>{int(ship.shield)}{u.MJ}</td>
<td><span onMouseEnter={termtip.bind(null, 'TT_SUMMARY_LADEN_SINGLE_JUMP', { cap: 0 })} onMouseLeave={hide}>{f2(Calc.jumpRange(ship.unladenMass + ship.fuelCapacity + ship.cargoCapacity, ship.standard[2].m, ship.fuelCapacity, ship))}{u.LY}</span></td> <td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_INTEGRITY', { cap: 0 })} onMouseLeave={hide}>{int(ship.armour)}</td>
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_UNLADEN_TOTAL_JUMP', { cap: 0 })} onMouseLeave={hide}>{f2(Calc.totalJumpRange(ship.unladenMass + ship.fuelCapacity, ship.standard[2].m, ship.fuelCapacity, ship))}{u.LY}</td> <td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_DPS', { cap: 0 })} onMouseLeave={hide}>{f1(ship.totalDps)}</td>
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_LADEN_TOTAL_JUMP', { cap: 0 })} onMouseLeave={hide}>{f2(Calc.totalJumpRange(ship.unladenMass + ship.fuelCapacity + ship.cargoCapacity, ship.standard[2].m, ship.fuelCapacity, ship))}{u.LY}</td> <td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_EPS', { cap: 0 })} onMouseLeave={hide}>{f1(ship.totalEps)}</td>
<td className={sgClassNames} onMouseEnter={termtip.bind(null, sgTooltip, { cap: 0 })} onMouseLeave={hide}>{int(ship.shield)}{u.MJ}</td> <td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_TTD', { cap: 0 })} onMouseLeave={hide}>{timeToDrain === Infinity ? '∞' : time(timeToDrain)}</td>
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_INTEGRITY', { cap: 0 })} onMouseLeave={hide}>{int(ship.armour)}</td> {/* <td>{f1(ship.totalHps)}</td> */}
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_DPS', { cap: 0 })} onMouseLeave={hide}>{f1(ship.totalDps)}</td> <td>{round(ship.cargoCapacity)}{u.T}</td>
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_EPS', { cap: 0 })} onMouseLeave={hide}>{f1(ship.totalEps)}</td> <td>{round(ship.fuelCapacity)}{u.T}</td>
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_TTD', { cap: 0 })} onMouseLeave={hide}>{timeToDrain === Infinity ? '∞' : time(timeToDrain)}</td> <td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_HULL_MASS', { cap: 0 })} onMouseLeave={hide}>{ship.hullMass}{u.T}</td>
{/* <td>{f1(ship.totalHps)}</td> */} <td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_UNLADEN_MASS', { cap: 0 })} onMouseLeave={hide}>{int(ship.unladenMass)}{u.T}</td>
<td>{round(ship.cargoCapacity)}{u.T}</td> <td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_LADEN_MASS', { cap: 0 })} onMouseLeave={hide}>{int(ship.ladenMass)}{u.T}</td>
<td>{ship.passengerCapacity}</td> <td>{int(ship.hardness)}</td>
<td>{round(ship.fuelCapacity)}{u.T}</td> <td>{ship.crew}</td>
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_HULL_MASS', { cap: 0 })} onMouseLeave={hide}>{ship.hullMass}{u.T}</td> <td>{ship.masslock}</td>
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_UNLADEN_MASS', { cap: 0 })} onMouseLeave={hide}>{int(ship.unladenMass)}{u.T}</td> </tr>
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_LADEN_MASS', { cap: 0 })} onMouseLeave={hide}>{int(ship.ladenMass)}{u.T}</td> </tbody>
<td>{int(ship.hardness)}</td> </table>
<td>{ship.crew}</td>
<td>{ship.masslock}</td>
<td>{shipBoost !== 'No Boost' ? formats.time(shipBoost) : 'No Boost'}</td>
</tr>
</tbody>
</table>
<table className={'summaryTable'}>
<thead className={this.state.shieldColour}>
<tr>
<th rowSpan={2} onMouseEnter={termtip.bind(null, 'shield', { cap: 0 })} onMouseLeave={hide} className='lft'>{translate('shield')}</th>
<th colSpan={4} className='lft'>{translate('resistance')}</th>
<th colSpan={5} onMouseEnter={termtip.bind(null, 'TT_SUMMARY_SHIELDS_SCB', { cap: 0 })} onMouseLeave={hide} className='lft'>{`${translate('HP')}`}</th>
<th rowSpan={2} onMouseEnter={termtip.bind(null, 'PHRASE_SG_RECOVER', { cap: 0 })} onMouseLeave={hide} className='lft'>{translate('recovery')}</th>
<th rowSpan={2} onMouseEnter={termtip.bind(null, 'PHRASE_SG_RECHARGE', { cap: 0 })} onMouseLeave={hide} className='lft'>{translate('recharge')}</th>
</tr>
<tr>
<th>{`${translate('explosive')}`}</th>
<th>{`${translate('kinetic')}`}</th>
<th>{`${translate('thermal')}`}</th>
<th></th>
<th className={'bordered'}>{`${translate('absolute')}`}</th>
<th>{`${translate('explosive')}`}</th>
<th>{`${translate('kinetic')}`}</th>
<th>{`${translate('thermal')}`}</th>
<th></th>
</tr>
</thead>
<tbody>
<tr>
<td>{translate(shieldGenerator && shieldGenerator.m.grp || 'No Shield')}</td>
<td>{formats.pct1(ship.shieldExplRes)}</td>
<td>{formats.pct1(ship.shieldKinRes)}</td>
<td>{formats.pct1(ship.shieldThermRes)}</td>
<td></td>
<td>{int(ship && ship.shield > 0 ? ship.shield : 0)}{u.MJ}</td>
<td>{int(ship && ship.shield > 0 ? ship.shield * ((1 / (1 - (ship.shieldExplRes)))) : 0)}{u.MJ}</td>
<td>{int(ship && ship.shield > 0 ? ship.shield * ((1 / (1 - (ship.shieldKinRes)))) : 0)}{u.MJ}</td>
<td>{int(ship && ship.shield > 0 ? ship.shield * ((1 / (1 - (ship.shieldThermRes)))) : 0)}{u.MJ}</td>
<td></td>
<td>{sgMetrics && sgMetrics.recover === Math.Inf ? translate('Never') : formats.time(sgMetrics.recover)}</td>
<td>{sgMetrics && sgMetrics.recharge === Math.Inf ? translate('Never') : formats.time(sgMetrics.recharge)}</td>
</tr>
</tbody>
<thead>
<tr>
<th rowSpan={2} onMouseEnter={termtip.bind(null, 'armour', { cap: 0 })} onMouseLeave={hide} className='lft'>{translate('armour')}</th>
<th colSpan={4} className='lft'>{translate('resistance')}</th>
<th colSpan={5} onMouseEnter={termtip.bind(null, 'PHRASE_EFFECTIVE_ARMOUR', { cap: 0 })} onMouseLeave={hide} className='lft'>{`${translate('HP')}`}</th>
<th rowSpan={2} onMouseEnter={termtip.bind(null, 'TT_MODULE_ARMOUR', { cap: 0 })} onMouseLeave={hide} className='lft'>{translate('raw module armour')}</th>
<th rowSpan={2} onMouseEnter={termtip.bind(null, 'TT_MODULE_PROTECTION_INTERNAL', { cap: 0 })} onMouseLeave={hide} className='lft'>{translate('internal protection')}</th>
</tr>
<tr>
<th>{`${translate('explosive')}`}</th>
<th>{`${translate('kinetic')}`}</th>
<th>{`${translate('thermal')}`}</th>
<th>{`${translate('caustic')}`}</th>
<th className={'bordered'}>{`${translate('absolute')}`}</th>
<th>{`${translate('explosive')}`}</th>
<th>{`${translate('kinetic')}`}</th>
<th>{`${translate('thermal')}`}</th>
<th>{`${translate('caustic')}`}</th>
</tr>
</thead>
<tbody>
<tr>
<td>{translate(ship && ship.bulkheads && ship.bulkheads.m && ship.bulkheads.m.name || 'No Armour')}</td>
<td>{formats.pct1(ship.hullExplRes)}</td>
<td>{formats.pct1(ship.hullKinRes)}</td>
<td>{formats.pct1(ship.hullThermRes)}</td>
<td>{formats.pct1(ship.hullCausRes)}</td>
<td>{int(ship.armour)}</td>
<td>{int(ship.armour * ((1 / (1 - (ship.hullExplRes)))))}</td>
<td>{int(ship.armour * ((1 / (1 - (ship.hullKinRes)))))}</td>
<td>{int(ship.armour * ((1 / (1 - (ship.hullThermRes)))))}</td>
<td>{int(ship.armour * ((1 / (1 - (ship.hullCausRes)))))}</td>
<td>{int(armourMetrics.modulearmour)}</td>
<td>{int(armourMetrics.moduleprotection * 100) + '%'}</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>; </div>;
} }
} }

View File

@@ -1,386 +1,166 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
const MARGIN_LR = 8; // Left/ Right margin const MARGIN_LR = 8; // Left/ Right margin
/** /**
* Horizontal Slider * Horizontal Slider
*/ */
export default class Slider extends React.Component { export default class Slider extends React.Component {
static defaultProps = { static defaultProps = {
axis: false, axis: false,
min: 0, min: 0,
max: 1, max: 1,
scale: 1 // SVG render scale scale: 1 // SVG render scale
}; };
static propTypes = { static propTypes = {
axis: PropTypes.bool, axis: PropTypes.bool,
axisUnit: PropTypes.string,// units (T, M, etc.) axisUnit: PropTypes.string,
max: PropTypes.number, max: PropTypes.number,
min: PropTypes.number, min: PropTypes.number,
onChange: PropTypes.func.isRequired,// function which determins percent value onChange: PropTypes.func.isRequired,
onResize: PropTypes.func, onResize: PropTypes.func,
percent: PropTypes.number.isRequired,// value of slider percent: PropTypes.number.isRequired,
scale: PropTypes.number scale: PropTypes.number
}; };
/** /**
* Constructor * Constructor
* @param {Object} props React Component properties * @param {Object} props React Component properties
*/ */
constructor(props) { constructor(props) {
super(props); super(props);
this._down = this._down.bind(this); this._down = this._down.bind(this);
this._move = this._move.bind(this); this._move = this._move.bind(this);
this._up = this._up.bind(this); this._up = this._up.bind(this);
this._keyup = this._keyup.bind(this); this._updatePercent = this._updatePercent.bind(this);
this._keydown = this._keydown.bind(this); this._updateDimensions = this._updateDimensions.bind(this);
this._touchstart = this._touchstart.bind(this);
this._touchend = this._touchend.bind(this); this.state = { width: 0 };
}
this._updatePercent = this._updatePercent.bind(this);
this._updateDimensions = this._updateDimensions.bind(this); /**
* On Mouse/Touch down handler
this.state = { width: 0 }; * @param {SyntheticEvent} event Event
} */
_down(event) {
/** let rect = event.currentTarget.getBoundingClientRect();
* On Mouse/Touch down handler this.left = rect.left;
* @param {SyntheticEvent} event Event this.width = rect.width;
*/ this._move(event);
_down(event) { }
let rect = event.currentTarget.getBoundingClientRect();
this.left = rect.left; /**
this.width = rect.width; * Update the slider percentage on move
this._move(event); * @param {SyntheticEvent} event Event
this.touchStartTimer = setTimeout(() => this.sliderInputBox._setDisplay('block'), 1500); */
} _move(event) {
if(this.width !== null && this.left != null) {
/** let clientX = event.touches ? event.touches[0].clientX : event.clientX;
* Update the slider percentage on move event.preventDefault();
* @param {SyntheticEvent} event Event this._updatePercent(clientX - this.left, this.width);
*/ }
_move(event) { }
if(this.width !== null && this.left != null) {
let clientX = event.touches ? event.touches[0].clientX : event.clientX; /**
event.preventDefault(); * On Mouse/Touch up handler
this._updatePercent(clientX - this.left, this.width); * @param {Event} event DOM Event
} */
} _up(event) {
event.preventDefault();
/** this.left = null;
* On Mouse/Touch up handler this.width = null;
* @param {Event} event DOM Event }
*/
_up(event) { /**
this.sliderInputBox.sliderVal.focus(); * Determine if the user is still dragging
clearTimeout(this.touchStartTimer); * @param {SyntheticEvent} event Event
event.preventDefault(); */
this.left = null; _enter(event) {
this.width = null; if(event.buttons !== 1) {
} 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 * Update the slider percentage
* @param {Event} event Keyboard event * @param {number} pos Slider drag position
*/ * @param {number} width Slider width
_keyup(event) { * @param {Event} event DOM Event
switch (event.key) { */
case 'Enter': _updatePercent(pos, width) {
event.preventDefault(); this.props.onChange(Math.min(Math.max(pos / width, 0), 1));
this.sliderInputBox._setDisplay('block'); }
return;
default: /**
return; * Update dimenions from rendered DOM
} */
} _updateDimensions() {
/** this.setState({
* Key down handler outerWidth: this.node.getBoundingClientRect().width
* increment slider position by +/- 1 when right/left arrow key is pressed or held });
* @param {Event} event Keyboard even }
*/
_keydown(event) { /**
let newVal = this.props.percent * this.props.max; * Add listeners when about to mount
switch (event.key) { */
case 'ArrowRight': componentWillMount() {
newVal += 1; if (this.props.onResize) {
if (newVal <= this.props.max) this.props.onChange(newVal / this.props.max); this.resizeListener = this.props.onResize(this._updateDimensions);
return; }
case 'ArrowLeft': }
newVal -= 1;
if (newVal >= 0) this.props.onChange(newVal / this.props.max); /**
return; * Trigger DOM updates on mount
default: */
return; componentDidMount() {
} this._updateDimensions();
} }
/** /**
* Touch start handler * Remove listeners on unmount
* @param {Event} event DOM Event */
* componentWillUnmount() {
*/ if (this.resizeListener) {
_touchstart(event) { this.resizeListener.remove();
this.touchStartTimer = setTimeout(() => this.sliderInputBox._setDisplay('block'), 1500); }
} }
/** /**
* Touch end handler * Render the slider
* @param {Event} event DOM Event * @return {React.Component} The slider
* */
*/ render() {
_touchend(event) { let outerWidth = this.state.outerWidth;
this.sliderInputBox.sliderVal.focus(); let { axis, axisUnit, min, max, scale } = this.props;
clearTimeout(this.touchStartTimer);
} let style = {
width: '100%',
/** height: axis ? '2.5em' : '1.5em',
* Determine if the user is still dragging boxSizing: 'border-box'
* @param {SyntheticEvent} event Event };
*/
_enter(event) { if (!outerWidth) {
if(event.buttons !== 1) { return <svg style={style} ref={node => this.node = node} />;
this.left = null; }
this.width = null;
} let margin = MARGIN_LR * scale;
} let width = outerWidth - (margin * 2);
let pctPos = width * this.props.percent;
/**
* Update the slider percentage return <svg onMouseUp={this._up} onMouseEnter={this._enter.bind(this)} onMouseMove={this._move} onTouchEnd={this._up} style={style} ref={node => this.node = node}>
* @param {number} pos Slider drag position <rect className='primary' style={{ opacity: 0.3 }} x={margin} y='0.25em' rx='0.3em' ry='0.3em' width={width} height='0.7em' />
* @param {number} width Slider width <rect className='primary-disabled' x={margin} y='0.45em' rx='0.15em' ry='0.15em' width={pctPos} height='0.3em' />
* @param {Event} event DOM Event <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} />
_updatePercent(pos, width) { {axis && <g style={{ fontSize: '.7em' }}>
this.props.onChange(Math.min(Math.max(pos / width, 0), 1)); <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>}
* Update dimenions from rendered DOM </svg>;
*/ }
_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
};
/**
* Determine if the user is still dragging
* @param {Object} props React Component properties
*/
constructor(props) {
super(props);
this._handleFocus = this._handleFocus.bind(this);
this._handleBlur = this._handleBlur.bind(this);
this._handleChange = this._handleChange.bind(this);
this._keyup = this._keyup.bind(this);
this.state = this._getInitialState();
}
/**
* Update input value if slider changes will change props/state
* @param {Object} nextProps React Component properites
* @param {Object} nextState React Component state values
*/
componentWillReceiveProps(nextProps, nextState) {
let 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 });
}
}
/**
* Update slider textbox visibility/values if changes are made to slider
* @param {Object} prevProps React Component properites
* @param {Object} prevState React Component state values
*/
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);
}
}
/**
* Set initial state for the textbox.
* We may want to rethink this to
* try and make it a stateless component
* @returns {object} React state object with initial values set
*/
_getInitialState() {
return {
divStyle: { display:'none' },
inputStyle: { width:'4em' },
labelStyle: { marginLeft: '.1em' },
maxLength:5,
size:5,
min:0,
tabIndex:-1,
type:'number',
readOnly: true,
inputValue: this.props.percent * this.props.max
};
}
/**
*
* @param {string} val block or none
*/
_setDisplay(val) {
this.setState({
divStyle: { display:val }
});
}
/**
* Update the input value
* when textbox gets focus
*/
_handleFocus() {
this.setState({
inputValue:this._getValue()
});
}
/**
* Update inputValue when textbox loses focus
*/
_handleBlur() {
this._setDisplay('none');
if (this.state.inputValue !== '') {
this.props.onChange(this.state.inputValue / this.props.max);
} else {
this.setState({
inputValue: this.props.percent * this.props.max
});
}
}
/**
* Get the value in the text box
* @returns {number} inputValue Value of the input box
*/
_getValue() {
return this.state.inputValue;
}
/**
* Update and set limits on input box
* values depending on what user
* has selected
*
* @param {SyntheticEvent} event ReactJs onChange event
*/
_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;
}
}
/**
* Get the value in the text box
* @return {React.Component} Text Input component for Slider
*/
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>;
}
}

View File

@@ -40,8 +40,6 @@ export default class Slot extends TranslatedComponent {
this._contextMenu = wrapCtxMenu(this._contextMenu.bind(this)); this._contextMenu = wrapCtxMenu(this._contextMenu.bind(this));
this._getMaxClassLabel = this._getMaxClassLabel.bind(this); this._getMaxClassLabel = this._getMaxClassLabel.bind(this);
this._keyDown = this._keyDown.bind(this);
this.slotDiv = null;
} }
// Must be implemented by subclasses: // Must be implemented by subclasses:
@@ -75,22 +73,6 @@ export default class Slot extends TranslatedComponent {
this.props.onSelect(null,null); 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') {
this._toggleModifications();
}
this.props.onOpen(event);
}
}
/** /**
* Render the slot * Render the slot
* @return {React.Component} The slot * @return {React.Component} The slot
@@ -122,7 +104,6 @@ export default class Slot extends TranslatedComponent {
ship={ship} ship={ship}
m={m} m={m}
marker={modificationsMarker} marker={modificationsMarker}
modButton = {this.modButton}
/>; />;
} else { } else {
menu = <AvailableModulesMenu menu = <AvailableModulesMenu
@@ -133,7 +114,6 @@ export default class Slot extends TranslatedComponent {
onSelect={onSelect} onSelect={onSelect}
warning={warning} warning={warning}
diffDetails={diffDetails.bind(ship, this.context.language)} diffDetails={diffDetails.bind(ship, this.context.language)}
slotDiv = {this.slotDiv}
/>; />;
} }
} }
@@ -141,7 +121,7 @@ export default class Slot extends TranslatedComponent {
// TODO: implement touch dragging // TODO: implement touch dragging
return ( return (
<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={cn('slot', dropClass, { selected })} onClick={onOpen} onContextMenu={this._contextMenu} onDragOver={dragOver}>
<div className='details-container'> <div className='details-container'>
<div className='sz'>{this._getMaxClassLabel(translate)}</div> <div className='sz'>{this._getMaxClassLabel(translate)}</div>
{slotDetails} {slotDetails}
@@ -151,7 +131,6 @@ export default class Slot extends TranslatedComponent {
); );
} }
/** /**
* Toggle the modifications flag when selecting the modifications icon * Toggle the modifications flag when selecting the modifications icon
*/ */

View File

@@ -18,8 +18,7 @@ export default class SlotSection extends TranslatedComponent {
onCargoChange: PropTypes.func.isRequired, onCargoChange: PropTypes.func.isRequired,
onFuelChange: PropTypes.func.isRequired, onFuelChange: PropTypes.func.isRequired,
code: PropTypes.string.isRequired, code: PropTypes.string.isRequired,
togglePwr: PropTypes.func, togglePwr: PropTypes.func
sectionMenuRefs: PropTypes.object
}; };
/** /**
@@ -33,10 +32,7 @@ export default class SlotSection extends TranslatedComponent {
super(props); super(props);
this.sectionId = sectionId; this.sectionId = sectionId;
this.sectionName = sectionName; this.sectionName = sectionName;
this.ssHeadRef = null;
this.sectionRefArr = this.props.sectionMenuRefs[this.sectionId] = [];
this.sectionRefArr['selectedRef'] = null;
this._getSlots = this._getSlots.bind(this); this._getSlots = this._getSlots.bind(this);
this._selectModule = this._selectModule.bind(this); this._selectModule = this._selectModule.bind(this);
this._getSectionMenu = this._getSectionMenu.bind(this); this._getSectionMenu = this._getSectionMenu.bind(this);
@@ -44,8 +40,6 @@ export default class SlotSection extends TranslatedComponent {
this._drop = this._drop.bind(this); this._drop = this._drop.bind(this);
this._dragOverNone = this._dragOverNone.bind(this); this._dragOverNone = this._dragOverNone.bind(this);
this._close = this._close.bind(this); this._close = this._close.bind(this);
this._keyDown = this._keyDown.bind(this);
this._handleSectionFocus = this._handleSectionFocus.bind(this);
this.state = {}; this.state = {};
} }
@@ -53,59 +47,7 @@ export default class SlotSection extends TranslatedComponent {
// _getSlots() // _getSlots()
// _getSectionMenu() // _getSectionMenu()
// _contextMenu() // _contextMenu()
// componentDidUpdate(prevProps)
/**
* TODO: May either need to send the function to be triggered when Enter key is pressed, or else
* may need a separate keyDown handler for each subclass (StandardSlotSection, HardpointSlotSection, etc.)
* ex: _keyDown(_keyDownfn, event)
*
* @param {SyntheticEvent} event KeyDown event
*/
_keyDown(event) {
if (event.key == 'Enter') {
event.stopPropagation();
if (event.currentTarget.nodeName === 'H1') {
this._openMenu(this.sectionName, event);
} else {
event.currentTarget.click();
}
return;
}
if (event.key == 'Tab') {
if (event.shiftKey) {
if ((event.currentTarget === this.sectionRefArr[this.firstRefId]) && this.sectionRefArr[this.lastRefId]) {
event.preventDefault();
this.sectionRefArr[this.lastRefId].focus();
}
} else {
if ((event.currentTarget === this.sectionRefArr[this.lastRefId]) && this.sectionRefArr[this.firstRefId]) {
event.preventDefault();
this.sectionRefArr[this.firstRefId].focus();
}
}
}
}
/**
* Set focus on appropriate Slot Section Menu element
* @param {Object} focusPrevProps prevProps for componentDidUpdate() from ...SlotSection.jsx
* @param {String} firstRef id of the first ref in ...SlotSection.jsx
* @param {String} lastRef id of the last ref in ...SlotSection.jsx
*
*/
_handleSectionFocus(focusPrevProps, firstRef, lastRef) {
if (this.selectedRefId !== null && this.sectionRefArr[this.selectedRefId]) {
// set focus on the previously selected option for the currently open section menu
this.sectionRefArr[this.selectedRefId].focus();
} else if (this.sectionRefArr[firstRef] && this.sectionRefArr[firstRef] != null) {
// set focus on the first option in the currently open section menu if none have been selected previously
this.sectionRefArr[firstRef].focus();
} else if (this.props.currentMenu == null && focusPrevProps.currentMenu == this.sectionName && this.sectionRefArr['ssHeadRef']) {
// set focus on the section menu header when section menu is closed
this.sectionRefArr['ssHeadRef'].focus();
}
}
/** /**
* Open a menu * Open a menu
* @param {string} menu Menu name * @param {string} menu Menu name
@@ -228,18 +170,6 @@ export default class SlotSection extends TranslatedComponent {
targetSlot.priority = targetPriority; targetSlot.priority = targetPriority;
} }
this.props.onChange(); this.props.onChange();
this.props.ship
.updatePowerGenerated()
.updatePowerUsed()
.recalculateMass()
.updateJumpStats()
.recalculateShield()
.recalculateShieldCells()
.recalculateArmour()
.recalculateDps()
.recalculateEps()
.recalculateHps()
.updateMovement();
} }
} }
} }
@@ -295,7 +225,7 @@ export default class SlotSection extends TranslatedComponent {
return ( return (
<div id={this.sectionId} className={'group'} onDragLeave={this._dragOverNone}> <div id={this.sectionId} className={'group'} onDragLeave={this._dragOverNone}>
<div className={cn('section-menu', { selected: sectionMenuOpened })} onClick={open} onContextMenu={ctx}> <div className={cn('section-menu', { selected: sectionMenuOpened })} onClick={open} onContextMenu={ctx}>
<h1 tabIndex="0" onKeyDown={this._keyDown} ref={ssHead => this.sectionRefArr['ssHeadRef'] = ssHead}>{translate(this.sectionName)} <Equalizer/></h1> <h1>{translate(this.sectionName)} <Equalizer/></h1>
{sectionMenuOpened ? this._getSectionMenu(translate, this.props.ship) : null } {sectionMenuOpened ? this._getSectionMenu(translate, this.props.ship) : null }
</div> </div>
{this._getSlots()} {this._getSlots()}

View File

@@ -35,21 +35,6 @@ export default class StandardSlot extends TranslatedComponent {
constructor(props) { constructor(props) {
super(props); super(props);
this._modificationsSelected = false; this._modificationsSelected = false;
this._keyDown = this._keyDown.bind(this);
this.modButton = null;
this.slotDiv = null;
}
/**
* Handle Enter key
* @param {SyntheticEvent} event KeyDown event
*/
_keyDown(event) {
if (event.key == 'Enter') {
if(event.target.className == 'r') {
this._toggleModifications();
}
this.props.onOpen(event);
}
} }
/** /**
@@ -63,13 +48,7 @@ export default class StandardSlot extends TranslatedComponent {
let m = slot.m; let m = slot.m;
let classRating = m.class + m.rating; let classRating = m.class + m.rating;
let menu; let menu;
let validMods = m == null || !Modifications.modules[m.grp] ? [] : (Modifications.modules[m.grp].modifications || []); let validMods = m == null ? [] : (Modifications.modules[m.grp].modifications || []);
if (m && m.name && m.name === 'Guardian Hybrid Power Plant') {
validMods = [];
}
if (m && m.name && m.name === 'Guardian Power Distributor') {
validMods = [];
}
let showModuleResistances = Persist.showModuleResistances(); let showModuleResistances = Persist.showModuleResistances();
let mass = m.getMass() || m.cargo || m.fuel || 0; let mass = m.getMass() || m.cargo || m.fuel || 0;
@@ -77,9 +56,6 @@ export default class StandardSlot extends TranslatedComponent {
let modTT = translate('modified'); let modTT = translate('modified');
if (m && m.blueprint && m.blueprint.name) { if (m && m.blueprint && m.blueprint.name) {
modTT = translate(m.blueprint.name) + ' ' + translate('grade') + ' ' + m.blueprint.grade; modTT = translate(m.blueprint.name) + ' ' + translate('grade') + ' ' + m.blueprint.grade;
if (m.blueprint.special && m.blueprint.special.id >= 0) {
modTT += ', ' + translate(m.blueprint.special.name);
}
modTT = ( modTT = (
<div> <div>
<div>{modTT}</div> <div>{modTT}</div>
@@ -103,7 +79,6 @@ export default class StandardSlot extends TranslatedComponent {
ship={ship} ship={ship}
m={m} m={m}
marker={modificationsMarker} marker={modificationsMarker}
modButton = {this.modButton}
/>; />;
} else { } else {
menu = <AvailableModulesMenu menu = <AvailableModulesMenu
@@ -114,15 +89,14 @@ export default class StandardSlot extends TranslatedComponent {
onSelect={onSelect} onSelect={onSelect}
warning={warning} warning={warning}
diffDetails={diffDetails.bind(ship, this.context.language)} diffDetails={diffDetails.bind(ship, this.context.language)}
slotDiv = {this.slotDiv}
/>; />;
} }
} }
return ( return (
<div className={cn('slot', { selected: this.props.selected })} onClick={this.props.onOpen} onKeyDown={this._keyDown} onContextMenu={stopCtxPropagation} tabIndex="0" ref={ slotDiv => this.slotDiv = slotDiv }> <div className={cn('slot', { selected: this.props.selected })} onClick={this.props.onOpen} onContextMenu={stopCtxPropagation}>
<div className={cn('details-container', { warning: warning && warning(slot.m), disabled: m.grp !== 'bh' && !slot.enabled })}> <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 className={'sz'}>{slot.maxClass}</div>
<div> <div>
<div className={'l'}>{classRating} {translate(m.name || m.grp)}{m.mods && Object.keys(m.mods).length > 0 ? <span className='r' onMouseOver={termtip.bind(null, modTT)} onMouseOut={tooltip.bind(null, null)}><Modified /></span> : null }</div> <div className={'l'}>{classRating} {translate(m.name || m.grp)}{m.mods && Object.keys(m.mods).length > 0 ? <span className='r' onMouseOver={termtip.bind(null, modTT)} onMouseOut={tooltip.bind(null, null)}><Modified /></span> : null }</div>
<div className={'r'}>{formats.round(mass)}{units.T}</div> <div className={'r'}>{formats.round(mass)}{units.T}</div>
@@ -144,7 +118,7 @@ export default class StandardSlot extends TranslatedComponent {
{ showModuleResistances && m.getKineticResistance() ? <div className='l'>{translate('kinres')}: {formats.pct(m.getKineticResistance())}</div> : null } { 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 } { 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.getIntegrity() ? <div className='l'>{translate('integrity')}: {formats.int(m.getIntegrity())}</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 } { 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 }
</div> </div>
</div> </div>
</div> </div>

View File

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

View File

@@ -228,96 +228,6 @@ export class LinkIcon extends SvgIcon {
} }
} }
/**
* Link / Permalink / Chain
*/
export class OrbisIcon extends SvgIcon {
/**
* Generate the SVG
* @return {React.Component} SVG Contents
*/
svg() {
return (
<g transform="scale(.037296)">
<path d="m429 319c60.75 0 110 49.248 110 110 0 60.75-49.247 110-110 110s-110-49.247-110-110c0-60.749 49.248-110 110-110m0-20c-34.724 0-67.369 13.522-91.922 38.075s-38.075 57.198-38.075 91.922 13.522 67.369 38.075 91.922c24.553 24.554 57.198 38.075 91.922 38.075s67.369-13.521 91.922-38.075c24.554-24.553 38.075-57.198 38.075-91.922s-13.521-67.369-38.075-91.922c-24.553-24.553-57.198-38.075-91.922-38.075z" />
<path d="m429 235c107.14 0 194 86.855 194 194s-86.855 194-194 194-194-86.855-194-194 86.855-194 194-194m0-20c-28.881 0-56.908 5.661-83.304 16.825-25.485 10.779-48.368 26.207-68.016 45.853-19.646 19.647-35.074 42.53-45.853 68.015-11.163 26.396-16.824 54.423-16.824 83.304s5.661 56.908 16.825 83.304c10.779 25.484 26.207 48.368 45.853 68.016 19.647 19.646 42.53 35.073 68.015 45.854 26.396 11.164 54.423 16.825 83.304 16.825s56.908-5.661 83.304-16.825c25.484-10.779 48.368-26.206 68.016-45.854 19.646-19.646 35.073-42.53 45.854-68.016 11.164-26.396 16.825-54.423 16.825-83.304s-5.661-56.908-16.825-83.304c-10.779-25.485-26.206-48.368-45.854-68.015-19.646-19.646-42.53-35.074-68.016-45.853-26.396-11.164-54.423-16.825-83.304-16.825z" />
<path d="m429 63c202.14 0 366 163.86 366 366s-163.86 366-366 366-366-163.86-366-366 163.86-366 366-366m0-20c-52.101 0-102.65 10.208-150.25 30.342-45.966 19.442-87.244 47.271-122.69 82.714s-63.272 76.721-82.714 122.69c-20.134 47.601-30.342 98.153-30.342 150.25s10.208 102.65 30.342 150.25c19.442 45.967 47.271 87.244 82.714 122.69 35.443 35.442 76.721 63.271 122.69 82.715 47.601 20.133 98.153 30.342 150.25 30.342s102.65-10.209 150.25-30.342c45.967-19.442 87.244-47.271 122.69-82.715 35.441-35.442 63.271-76.721 82.714-122.69 20.133-47.601 30.342-98.153 30.342-150.25s-10.209-102.65-30.342-150.25c-19.442-45.966-47.271-87.244-82.714-122.69s-76.722-63.272-122.69-82.714c-47.601-20.134-98.153-30.342-150.25-30.342z"/>
<path d="m429 63c202.14 0 366 163.86 366 366s-163.86 366-366 366-366-163.86-366-366 163.86-366 366-366m0-20c-52.101 0-102.65 10.208-150.25 30.342-45.966 19.442-87.244 47.271-122.69 82.714s-63.272 76.721-82.714 122.69c-20.134 47.601-30.342 98.153-30.342 150.25s10.208 102.65 30.342 150.25c19.442 45.967 47.271 87.244 82.714 122.69 35.443 35.442 76.721 63.271 122.69 82.715 47.601 20.133 98.153 30.342 150.25 30.342s102.65-10.209 150.25-30.342c45.967-19.442 87.244-47.271 122.69-82.715 35.441-35.442 63.271-76.721 82.714-122.69 20.133-47.601 30.342-98.153 30.342-150.25s-10.209-102.65-30.342-150.25c-19.442-45.966-47.271-87.244-82.714-122.69s-76.722-63.272-122.69-82.714c-47.601-20.134-98.153-30.342-150.25-30.342z" />
<path d="m429 20c225.88 0 409 183.11 409 409s-183.11 409-409 409-409-183.11-409-409 183.11-409 409-409m0-20c-57.905 0-114.09 11.345-166.99 33.721-51.087 21.608-96.963 52.538-136.36 91.93s-70.321 85.269-91.93 136.36c-22.376 52.902-33.721 109.09-33.721 166.99s11.345 114.09 33.721 166.99c21.608 51.087 52.538 96.964 91.93 136.35 39.392 39.392 85.269 70.321 136.36 91.931 52.902 22.375 109.09 33.721 166.99 33.721s114.09-11.346 166.99-33.721c51.087-21.608 96.964-52.538 136.35-91.931 39.392-39.392 70.321-85.269 91.931-136.35 22.375-52.902 33.721-109.09 33.721-166.99s-11.346-114.09-33.721-166.99c-21.608-51.087-52.538-96.963-91.931-136.36-39.392-39.392-85.269-70.321-136.35-91.93-52.902-22.376-109.09-33.721-166.99-33.721z"/>
<path d="m155.34 679.12 173.25-190.21-15.626-13.721-170.9 190.4zm31.01 31.714 202.41-169.1-16.418-14.417-198.76 170.43z"/>
<path d="m702.66 178.87-173.25 190.21 15.625 13.721 170.9-190.4zm-31.01-31.714-202.41 169.1 16.418 14.417 198.76-170.43z" />
<rect transform="matrix(-.7071 -.7071 .7071 -.7071 429.34 1036.2)" x="387.09" y="420.77" width="84.379" height="16.859" />
</g>);
}
}
/**
* Material
*/
export class MatIcon extends SvgIcon {
/**
* Generate the SVG
* @return {React.Component} SVG Contents
*/
svg() {
return<g xmlns="http://www.w3.org/2000/svg">
<path fill="#FF7100" d="M 24.86,4.18
C 24.86,4.18 17.17,7.82 17.17,7.82
17.17,7.82 15.35,14.55 15.35,14.55
15.35,14.55 24.70,9.75 24.70,9.75
24.70,9.75 24.86,4.18 24.86,4.18 Z
M 32.21,17.45
C 32.21,17.45 26.41,11.18 26.41,11.18
26.41,11.18 19.51,11.51 19.51,11.51
19.51,11.51 26.92,19.01 26.92,19.01
26.92,19.01 32.21,17.45 32.21,17.45 Z
M 21.99,28.62
C 21.99,28.62 26.10,21.10 26.10,21.10
26.10,21.10 23.66,14.57 23.66,14.57
23.66,14.57 18.89,24.01 18.89,24.01
18.89,24.01 21.99,28.62 21.99,28.62 Z
M 8.33,22.24
C 8.33,22.24 16.67,23.87 16.67,23.87
16.67,23.87 22.06,19.51 22.06,19.51
22.06,19.51 11.70,17.84 11.70,17.84
11.70,17.84 8.33,22.24 8.33,22.24 Z
M 10.11,7.14
C 10.11,7.14 11.15,15.66 11.15,15.66
11.15,15.66 16.92,19.49 16.92,19.49
16.92,19.49 15.29,9.02 15.29,9.02
15.29,9.02 10.11,7.14 10.11,7.14 Z
M 27.69,2.67
C 27.69,2.67 35.89,16.00 35.89,16.00
35.89,16.00 27.69,29.33 27.69,29.33
27.69,29.33 11.31,29.33 11.31,29.33
11.31,29.33 3.11,16.00 3.11,16.00
3.11,16.00 11.31,2.67 11.31,2.67
11.31,2.67 27.67,2.67 27.67,2.67M 29.16,0.00
C 29.16,0.00 27.69,0.00 27.69,0.00
27.69,0.00 11.31,0.00 11.31,0.00
11.31,0.00 9.84,0.00 9.84,0.00
9.84,0.00 9.06,1.25 9.06,1.25
9.06,1.25 0.87,14.57 0.87,14.57
0.87,14.57 0.00,15.98 0.00,15.98
0.00,15.98 0.87,17.39 0.87,17.39
0.87,17.39 9.06,30.73 9.06,30.73
9.06,30.73 9.84,32.00 9.84,32.00
9.84,32.00 11.31,32.00 11.31,32.00
11.31,32.00 27.69,32.00 27.69,32.00
27.69,32.00 29.16,32.00 29.16,32.00
29.16,32.00 29.94,30.73 29.94,30.73
29.94,30.73 38.13,17.39 38.13,17.39
38.13,17.39 39.00,15.98 39.00,15.98
39.00,15.98 38.13,14.57 38.13,14.57
38.13,14.57 29.94,1.25 29.94,1.25
29.94,1.25 29.16,0.00 29.16,0.00
29.16,0.00 29.16,0.00 29.16,0.00 Z" />
</g>;
}
}
/** /**
* Shopping icon (dollar sign) * Shopping icon (dollar sign)
*/ */

View File

@@ -17,23 +17,12 @@ export default class UtilitySlotSection extends SlotSection {
constructor(props, context) { constructor(props, context) {
super(props, context, 'utility', 'utility mounts'); super(props, context, 'utility', 'utility mounts');
this._empty = this._empty.bind(this); this._empty = this._empty.bind(this);
this.selectedRefId = null;
this.firstRefId = 'emptyall';
this.lastRefId = 'po';
}
/**
* Handle focus if the component updates
* @param {Object} prevProps React Component properties
*/
componentDidUpdate(prevProps) {
this._handleSectionFocus(prevProps,this.firstRefId, this.lastRefId);
} }
/** /**
* Empty all utility slots and close the menu * Empty all utility slots and close the menu
*/ */
_empty() { _empty() {
this.selectedRefId = this.firstRefId;
this.props.ship.emptyUtility(); this.props.ship.emptyUtility();
this.props.onChange(); this.props.onChange();
this._close(); this._close();
@@ -47,9 +36,6 @@ export default class UtilitySlotSection extends SlotSection {
* @param {Synthetic} event Event * @param {Synthetic} event Event
*/ */
_use(group, rating, name, event) { _use(group, rating, name, event) {
this.selectedRefId = group;
if (rating !== null) this.selectedRefId += '-' + rating;
this.props.ship.useUtility(group, rating, name, event.getModifierState('Alt')); this.props.ship.useUtility(group, rating, name, event.getModifierState('Alt'));
this.props.onChange(); this.props.onChange();
this._close(); this._close();
@@ -108,28 +94,28 @@ export default class UtilitySlotSection extends SlotSection {
return <div className='select' onClick={(e) => e.stopPropagation()} onContextMenu={stopCtxPropagation}> return <div className='select' onClick={(e) => e.stopPropagation()} onContextMenu={stopCtxPropagation}>
<ul> <ul>
<li className='lc' tabIndex='0' onClick={this._empty} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['emptyall'] = smRef}>{translate('empty all')}</li> <li className='lc' onClick={this._empty}>{translate('empty all')}</li>
<li className='optional-hide' style={{ textAlign: 'center', marginTop: '1em' }}>{translate('PHRASE_ALT_ALL')}</li> <li className='optional-hide' style={{ textAlign: 'center', marginTop: '1em' }}>{translate('PHRASE_ALT_ALL')}</li>
</ul> </ul>
<div className='select-group cap'>{translate('sb')}</div> <div className='select-group cap'>{translate('sb')}</div>
<ul> <ul>
<li className='c' tabIndex='0' onClick={_use.bind(this, 'sb', 'A', null)} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['sb-A'] = smRef}>A</li> <li className='c' onClick={_use.bind(this, 'sb', 'A', null)}>A</li>
<li className='c' tabIndex='0' onClick={_use.bind(this, 'sb', 'B', null)} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['sb-B'] = smRef}>B</li> <li className='c' onClick={_use.bind(this, 'sb', 'B', null)}>B</li>
<li className='c' tabIndex='0' onClick={_use.bind(this, 'sb', 'C', null)} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['sb-C'] = smRef}>C</li> <li className='c' onClick={_use.bind(this, 'sb', 'C', null)}>C</li>
<li className='c' tabIndex='0' onClick={_use.bind(this, 'sb', 'D', null)} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['sb-D'] = smRef}>D</li> <li className='c' onClick={_use.bind(this, 'sb', 'D', null)}>D</li>
<li className='c' tabIndex='0' onClick={_use.bind(this, 'sb', 'E', null)} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['sb-E'] = smRef}>E</li> <li className='c' onClick={_use.bind(this, 'sb', 'E', null)}>E</li>
</ul> </ul>
<div className='select-group cap'>{translate('hs')}</div> <div className='select-group cap'>{translate('hs')}</div>
<ul> <ul>
<li className='lc' tabIndex='0' onClick={_use.bind(this, 'hs', null, 'Heat Sink Launcher')} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['hs'] = smRef}>{translate('Heat Sink Launcher')}</li> <li className='lc' onClick={_use.bind(this, 'hs', null, 'Heat Sink Launcher')}>{translate('Heat Sink Launcher')}</li>
</ul> </ul>
<div className='select-group cap'>{translate('ch')}</div> <div className='select-group cap'>{translate('ch')}</div>
<ul> <ul>
<li className='lc' tabIndex='0' onClick={_use.bind(this, 'ch', null, 'Chaff Launcher')} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['ch'] = smRef}>{translate('Chaff Launcher')}</li> <li className='lc' onClick={_use.bind(this, 'ch', null, 'Chaff Launcher')}>{translate('Chaff Launcher')}</li>
</ul> </ul>
<div className='select-group cap'>{translate('po')}</div> <div className='select-group cap'>{translate('po')}</div>
<ul> <ul>
<li className='lc' tabIndex='0' onClick={_use.bind(this, 'po', null, 'Point Defence')} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['po'] = smRef}>{translate('Point Defence')}</li> <li className='lc' onClick={_use.bind(this, 'po', null, 'Point Defence')}>{translate('Point Defence')}</li>
</ul> </ul>
</div>; </div>;
} }

View File

@@ -1,105 +1,111 @@
import TranslatedComponent from './TranslatedComponent'; import TranslatedComponent from './TranslatedComponent';
import React, { PropTypes } from 'react'; import React, { PropTypes } from 'react';
import ContainerDimensions from 'react-container-dimensions'; import Measure from 'react-measure';
import { BarChart, Bar, XAxis, YAxis } from 'recharts'; import { BarChart, Bar, XAxis, YAxis } from 'recharts';
const CORIOLIS_COLOURS = ['#FF8C0D', '#1FB0FF', '#71A052', '#D5D54D']; const CORIOLIS_COLOURS = ['#FF8C0D', '#1FB0FF', '#71A052', '#D5D54D'];
const LABEL_COLOUR = '#000000'; const LABEL_COLOUR = '#000000';
const AXIS_COLOUR = '#C06400'; const AXIS_COLOUR = '#C06400';
const ASPECT = 1; const ASPECT = 1;
const merge = function(one, two) { const merge = function(one, two) {
return Object.assign({}, one, two); return Object.assign({}, one, two);
}; };
/** /**
* A vertical bar chart * A vertical bar chart
*/ */
export default class VerticalBarChart extends TranslatedComponent { export default class VerticalBarChart extends TranslatedComponent {
static propTypes = { static propTypes = {
data : PropTypes.array.isRequired, data : PropTypes.array.isRequired,
yMax : PropTypes.number yMax : PropTypes.number
}; };
/** /**
* Constructor * Constructor
* @param {Object} props React Component properties * @param {Object} props React Component properties
* @param {Object} context React Component context * @param {Object} context React Component context
*/ */
constructor(props, context) { constructor(props, context) {
super(props); super(props);
this._termtip = this._termtip.bind(this); this._termtip = this._termtip.bind(this);
}
this.state = {
/** dimensions: {
* Render the bar chart width: 300,
* @returns {Object} the markup height: 300
*/ }
render() { };
const { tooltip, termtip } = this.context; }
// Calculate maximum for Y /**
let dataMax = Math.max(...this.props.data.map(d => d.value)); * Render the bar chart
if (dataMax == -Infinity) dataMax = 0; * @returns {Object} the markup
let yMax = this.props.yMax ? Math.round(this.props.yMax) : 0; */
const localMax = Math.max(dataMax, yMax); render() {
const { width, height } = this.state.dimensions;
return ( const { tooltip, termtip } = this.context;
<ContainerDimensions>
{ ({ width }) => ( // Calculate maximum for Y
<div width='100%'> let dataMax = Math.max(...this.props.data.map(d => d.value));
<BarChart width={width} height={width * ASPECT} data={this.props.data} margin={{ top: 5, right: 5, left: 5, bottom: 5 }}> if (dataMax == -Infinity) dataMax = 0;
<XAxis interval={0} fontSize='0.8em' stroke={AXIS_COLOUR} dataKey='label' /> let yMax = this.props.yMax ? Math.round(this.props.yMax) : 0;
<YAxis interval={'preserveStart'} tickCount={11} fontSize='0.8em' stroke={AXIS_COLOUR} type='number' domain={[0, localMax]}/> const localMax = Math.max(dataMax, yMax);
<Bar dataKey='value' label={<ValueLabel />} fill={CORIOLIS_COLOURS[0]} isAnimationActive={false} onMouseOver={this._termtip} onMouseOut={tooltip.bind(null, null)}/>
</BarChart> return (
</div> <Measure whitelist={['width', 'top']} onMeasure={ (dimensions) => this.setState({ dimensions }) }>
)} <div width='100%'>
</ContainerDimensions> <BarChart width={width} height={width * ASPECT} data={this.props.data} margin={{ top: 5, right: 5, left: 5, bottom: 5 }}>
); <XAxis interval={0} fontSize='0.8em' stroke={AXIS_COLOUR} dataKey='label' />
} <YAxis interval={'preserveStart'} tickCount={11} fontSize='0.8em' stroke={AXIS_COLOUR} type='number' domain={[0, localMax]}/>
<Bar dataKey='value' label={<ValueLabel />} fill={CORIOLIS_COLOURS[0]} isAnimationActive={false} onMouseOver={this._termtip} onMouseOut={tooltip.bind(null, null)}/>
/** </BarChart>
* Generate a term tip </div>
* @param {Object} d the data </Measure>
* @param {number} i the index );
* @param {Object} e the event }
* @returns {Object} termtip markup
*/ /**
_termtip(d, i, e) { * Generate a term tip
if (this.props.data[i].tooltip) { * @param {Object} d the data
return this.context.termtip(this.props.data[i].tooltip, e); * @param {number} i the index
} else { * @param {Object} e the event
return null; * @returns {Object} termtip markup
} */
} _termtip(d, i, e) {
} if (this.props.data[i].tooltip) {
return this.context.termtip(this.props.data[i].tooltip, e);
/** } else {
* A label that displays the value within the bar of the chart return null;
*/ }
class ValueLabel extends React.Component { }
static propTypes = { }
x: PropTypes.number,
y: PropTypes.number, /**
payload: PropTypes.object, * A label that displays the value within the bar of the chart
value: PropTypes.number */
}; class ValueLabel extends React.Component {
static propTypes = {
/** x: PropTypes.number,
* Render offence y: PropTypes.number,
* @return {React.Component} contents payload: PropTypes.object,
*/ value: PropTypes.number
render() { };
const { x, y, payload, value } = this.props;
/**
const em = value < 1000 ? '1em' : value < 1000 ? '0.8em' : '0.7em'; * Render offence
* @return {React.Component} contents
return ( */
<text x={x} y={y} fill="#000000" textAnchor="middle" dy={20} style={{ fontSize: em }}>{value}</text> render() {
); const { x, y, payload, value } = this.props;
}
}; const em = value < 1000 ? '1em' : value < 1000 ? '0.8em' : '0.7em';
return (
<text x={x} y={y} fill="#000000" textAnchor="middle" dy={20} style={{ fontSize: em }}>{value}</text>
);
}
};

View File

@@ -74,7 +74,7 @@ export default class WeaponDamageChart extends TranslatedComponent {
* Calculate the maximum range of a ship's weapons * Calculate the maximum range of a ship's weapons
* @param {Object} ship The ship * @param {Object} ship The ship
* @returns {int} The maximum range, in metres * @returns {int} The maximum range, in metres
*/ */
_calcMaxRange(ship) { _calcMaxRange(ship) {
let maxRange = 1000; // Minimum let maxRange = 1000; // Minimum
for (let i = 0; i < ship.hardpoints.length; i++) { for (let i = 0; i < ship.hardpoints.length; i++) {
@@ -184,7 +184,7 @@ export default class WeaponDamageChart extends TranslatedComponent {
const code = `${ship.toString()}:${opponent.toString()}`; const code = `${ship.toString()}:${opponent.toString()}`;
return ( return (
<div> <span>
<LineChart <LineChart
xMax={maxRange} xMax={maxRange}
yMax={this.state.maxDps} yMax={this.state.maxDps}
@@ -198,7 +198,7 @@ export default class WeaponDamageChart extends TranslatedComponent {
points={200} points={200}
code={code} code={code}
/> />
</div> </span>
); );
} }
} }

View File

@@ -6,7 +6,6 @@ import * as FR from './fr';
import * as IT from './it'; import * as IT from './it';
import * as RU from './ru'; import * as RU from './ru';
import * as PL from './pl'; import * as PL from './pl';
import * as PT from './pt';
import * as d3 from 'd3'; import * as d3 from 'd3';
let fallbackTerms = EN.terms; let fallbackTerms = EN.terms;
@@ -26,7 +25,6 @@ export function getLanguage(langCode) {
case 'it': lang = IT; break; case 'it': lang = IT; break;
case 'ru': lang = RU; break; case 'ru': lang = RU; break;
case 'pl': lang = PL; break; case 'pl': lang = PL; break;
case 'pt': lang = PT; break;
default: default:
lang = EN; lang = EN;
} }
@@ -60,20 +58,17 @@ export function getLanguage(langCode) {
}, },
translate, translate,
units: { units: {
ang: '°', // Angle
CR: <u>{translate('CR')}</u>, // Credits CR: <u>{translate('CR')}</u>, // Credits
kg: <u>{translate('kg')}</u>, // Kilograms kg: <u>{translate('kg')}</u>, // Kilograms
kgs: <u>{translate('kg/s')}</u>, // Kilograms per second kgs: <u>{translate('kg/s')}</u>, // Kilograms per second
km: <u>{translate('km')}</u>, // Kilometers km: <u>{translate('km')}</u>, // Kilometers
Ls: <u>{translate('Ls')}</u>, // Light Seconds Ls: <u>{translate('Ls')}</u>, // Light Seconds
LY: <u>{translate('LY')}</u>, // Light Years LY: <u>{translate('LY')}</u>, // Light Years
m: <u>{translate('m')}</u>, // Meters
MJ: <u>{translate('MJ')}</u>, // Mega Joules MJ: <u>{translate('MJ')}</u>, // Mega Joules
'm/s': <u>{translate('m/s')}</u>, // Meters per second 'm/s': <u>{translate('m/s')}</u>, // Meters per second
'°/s': <u>{translate('°/s')}</u>, // Degrees per second '°/s': <u>{translate('°/s')}</u>, // Degrees per second
MW: <u>{translate('MW')}</u>, // Mega Watts (same as Mega Joules per second) MW: <u>{translate('MW')}</u>, // Mega Watts (same as Mega Joules per second)
mps: <u>{translate('m/s')}</u>, // Metres per second mps: <u>{translate('m/s')}</u>, // Metres per second
pct: '%', // Percent
ps: <u>{translate('/s')}</u>, // per second ps: <u>{translate('/s')}</u>, // per second
pm: <u>{translate('/min')}</u>, // per minute pm: <u>{translate('/min')}</u>, // per minute
s: <u>{translate('secs')}</u>, // Seconds s: <u>{translate('secs')}</u>, // Seconds
@@ -93,6 +88,5 @@ export const Languages = {
es: 'Español', es: 'Español',
fr: 'Français', fr: 'Français',
ru: 'ру́сский', ru: 'ру́сский',
pl: 'polski', pl: 'polski'
pt: 'português'
}; };

File diff suppressed because one or more lines are too long

View File

@@ -1,16 +0,0 @@
export const formats = {
decimal: ',',
thousands: '.',
grouping: [3],
currency: ['', ' €'],
dateTime: '%A, %e de %B de %Y, %X',
date: '%d/%m/%Y',
time: '%H:%M:%S',
periods: ['AM', 'PM'],
days: ['domingo', 'lunes', 'martes', 'miércoles', 'jueves', 'viernes', 'sábado'],
shortDays: ['dom', 'lun', 'mar', 'mié', 'jue', 'vie', 'sáb'],
months: ['enero', 'febrero', 'marzo', 'abril', 'mayo', 'junio', 'julio', 'agosto', 'septiembre', 'octubre', 'noviembre', 'diciembre'],
shortMonths: ['ene', 'feb', 'mar', 'abr', 'may', 'jun', 'jul', 'ago', 'sep', 'oct', 'nov', 'dic']
};
export { default as terms } from './pt.json';

File diff suppressed because one or more lines are too long

View File

@@ -20,11 +20,11 @@
"PHRASE_BLUEPRINT_BEST": "Лучшие основные значения для чертежа", "PHRASE_BLUEPRINT_BEST": "Лучшие основные значения для чертежа",
"PHRASE_BLUEPRINT_EXTREME": "Лучшие положительные и худшие отрицательные основные значения для чертежа", "PHRASE_BLUEPRINT_EXTREME": "Лучшие положительные и худшие отрицательные основные значения для чертежа",
"PHRASE_BLUEPRINT_RESET": "Убрать все изменения и чертёж", "PHRASE_BLUEPRINT_RESET": "Убрать все изменения и чертёж",
"PHRASE_SELECT_SPECIAL": "Нажмите, чтобы выбрать экспериментальный эффект", "PHRASE_SELECT_SPECIAL": "Нажмите чтобы выбрать экспериментальный эффект",
"PHRASE_NO_SPECIAL": "Без экспериментального эффекта", "PHRASE_NO_SPECIAL": "Без экспериментального эффекта",
"PHRASE_SHOPPING_LIST": "Станции, что продают эту сборку", "PHRASE_SHOPPING_LIST": "Станции что продают эту сборку",
"PHRASE_REFIT_SHOPPING_LIST": "Станции, что продают необходимые модули", "PHRASE_REFIT_SHOPPING_LIST": "Станции что продают необходимые модули",
"PHRASE_TOTAL_EFFECTIVE_SHIELD": "Общий урон, что может быть нанесён в каждым типе, если используются все щитонакопители", "PHRASE_TOTAL_EFFECTIVE_SHIELD": "Общий урон что может быть нанесён в каждым типе, если используются все щитонакопители",
"PHRASE_TIME_TO_LOSE_SHIELDS": "Щиты продержатся", "PHRASE_TIME_TO_LOSE_SHIELDS": "Щиты продержатся",
"PHRASE_TIME_TO_RECOVER_SHIELDS": "Щиты восстановятся за", "PHRASE_TIME_TO_RECOVER_SHIELDS": "Щиты восстановятся за",
"PHRASE_TIME_TO_RECHARGE_SHIELDS": "Щиты будут заряжены за", "PHRASE_TIME_TO_RECHARGE_SHIELDS": "Щиты будут заряжены за",
@@ -43,12 +43,12 @@
"PHRASE_TIME_TO_REMOVE_ARMOUR": "Снимет броню за", "PHRASE_TIME_TO_REMOVE_ARMOUR": "Снимет броню за",
"TT_TIME_TO_REMOVE_ARMOUR": "Непрерывным огнём из всех орудий", "TT_TIME_TO_REMOVE_ARMOUR": "Непрерывным огнём из всех орудий",
"PHRASE_TIME_TO_DRAIN_WEP": "Опустошит ОРУЖ за", "PHRASE_TIME_TO_DRAIN_WEP": "Опустошит ОРУЖ за",
"TT_TIME_TO_DRAIN_WEP": "Время, за которое опустошится аккумулятор ОРУЖ при стрельбе из всех орудий", "TT_TIME_TO_DRAIN_WEP": "Время за которое опустошится аккумулятор ОРУЖ при стрельбе из всех орудий",
"TT_TIME_TO_LOSE_SHIELDS": "Против поддерживаемой стрельбы из всех орудий противника", "TT_TIME_TO_LOSE_SHIELDS": "Против поддерживаемой стрельбы из всех орудий противника",
"TT_TIME_TO_LOSE_ARMOUR": "Против поддерживаемой стрельбы из всех орудий противника", "TT_TIME_TO_LOSE_ARMOUR": "Против поддерживаемой стрельбы из всех орудий противника",
"TT_MODULE_ARMOUR": "Броня, защищающая модули от урона", "TT_MODULE_ARMOUR": "Броня защищаюшае модули от урона",
"TT_MODULE_PROTECTION_EXTERNAL": "Процент урона, перенаправленного от гнёзд на наборы для усиления модулей", "TT_MODULE_PROTECTION_EXTERNAL": "Процент урона перенаправленного от гнёзд на наборы для усиления модулей",
"TT_MODULE_PROTECTION_INTERNAL": "Процент урона, перенаправленного от модулей вне гнёзд на наборы для усиления модулей", "TT_MODULE_PROTECTION_INTERNAL": "Процент урона перенаправленного от модулей вне гнёзд на наборы для усиления модулей",
"TT_EFFECTIVE_SDPS_SHIELDS": "Реальный поддерживаемый ДПС пока аккумулятор ОРУЖ не пуст", "TT_EFFECTIVE_SDPS_SHIELDS": "Реальный поддерживаемый ДПС пока аккумулятор ОРУЖ не пуст",
"TT_EFFECTIVENESS_SHIELDS": "Эффективность в сравнении с попаданием по цели с 0-сопротивляемостью без пунктов в СИС на 0 метрах", "TT_EFFECTIVENESS_SHIELDS": "Эффективность в сравнении с попаданием по цели с 0-сопротивляемостью без пунктов в СИС на 0 метрах",
"TT_EFFECTIVE_SDPS_ARMOUR": "Реальный поддерживаемый ДПС пока аккумулятор ОРУЖ не пуст", "TT_EFFECTIVE_SDPS_ARMOUR": "Реальный поддерживаемый ДПС пока аккумулятор ОРУЖ не пуст",
@@ -56,7 +56,7 @@
"PHRASE_EFFECTIVE_SDPS_SHIELDS": "ПДПС против щитов", "PHRASE_EFFECTIVE_SDPS_SHIELDS": "ПДПС против щитов",
"PHRASE_EFFECTIVE_SDPS_ARMOUR": "ПДПС против брони", "PHRASE_EFFECTIVE_SDPS_ARMOUR": "ПДПС против брони",
"TT_SUMMARY_SPEED": "С полным топливным баком и 4 пунктами в ДВИ", "TT_SUMMARY_SPEED": "С полным топливным баком и 4 пунктами в ДВИ",
"TT_SUMMARY_SPEED_NONFUNCTIONAL": "Маневровые двигатели выключены или превышена максимальная масса с топливом и грузом", "TT_SUMMARY_SPEED_NONFUNCTIONAL": "маневровые двигатели выключены или превышена максимальная масса с топливом и грузом",
"TT_SUMMARY_BOOST": "С полным топливным баком и 4 пунктами в ДВИ", "TT_SUMMARY_BOOST": "С полным топливным баком и 4 пунктами в ДВИ",
"TT_SUMMARY_BOOST_NONFUNCTIONAL": "Распределитель питания не может обеспечить достаточно энергии для форсажа", "TT_SUMMARY_BOOST_NONFUNCTIONAL": "Распределитель питания не может обеспечить достаточно энергии для форсажа",
"TT_SUMMARY_SHIELDS": "Чистая сила щита, включая усилители", "TT_SUMMARY_SHIELDS": "Чистая сила щита, включая усилители",
@@ -68,16 +68,16 @@
"TT_SUMMARY_DPS": "Урон в секунду при стрельбе из всех орудий", "TT_SUMMARY_DPS": "Урон в секунду при стрельбе из всех орудий",
"TT_SUMMARY_EPS": "Расход аккумулятора ОРУЖ в секунду при стрельбе из всех орудий", "TT_SUMMARY_EPS": "Расход аккумулятора ОРУЖ в секунду при стрельбе из всех орудий",
"TT_SUMMARY_TTD": "Время расхода аккумулятора ОРУЖ при стрельбе из всех орудий и с 4 пунктами в ОРУЖ", "TT_SUMMARY_TTD": "Время расхода аккумулятора ОРУЖ при стрельбе из всех орудий и с 4 пунктами в ОРУЖ",
"TT_SUMMARY_MAX_SINGLE_JUMP": "Самый дальний возможный прыжок без груза и с топливом, достаточным только на сам прыжок", "TT_SUMMARY_MAX_SINGLE_JUMP": "Самый дальний возможный прыжок без груза и с топливом достаточным только на сам прыжок",
"TT_SUMMARY_UNLADEN_SINGLE_JUMP": "Самый дальний возможный прыжок без груза и с полным топливным баком", "TT_SUMMARY_UNLADEN_SINGLE_JUMP": "Самый дальний возможный прыжок без груза и с полным топливным баком",
"TT_SUMMARY_LADEN_SINGLE_JUMP": "Самый дальний возможный прыжок с полным грузовым отсеком и с полным топливным баком", "TT_SUMMARY_LADEN_SINGLE_JUMP": "Самый дальний возможный прыжок с полным грузовым отсеком и с полным топливным баком",
"TT_SUMMARY_UNLADEN_TOTAL_JUMP": "Самая дальняя общая дистанция без груза, с полным топливным баком и при прыжках на максимальное расстояние", "TT_SUMMARY_UNLADEN_TOTAL_JUMP": "Самая дальняя общая дистанция без груза, с полным топливным баком и при прыжках на максимальное расстояние",
"TT_SUMMARY_LADEN_TOTAL_JUMP": "Самая дальняя общая дистанция с полным грузовым отсеком, с полным топливным баком и при прыжках на максимальное расстояние", "TT_SUMMARY_LADEN_TOTAL_JUMP": "Самая дальняя общая дистанция с полным грузовым отсеком, с полным топливным баком и при прыжках на максимальное расстояние",
"HELP_MODIFICATIONS_MENU": "Нажмите на номер, чтобы ввести новое значение, или потяните вдоль полосы для малых изменений", "HELP_MODIFICATIONS_MENU": "Ткните на номер чтобы ввести новое значение, или потяните вдоль полосы для малых изменений",
"am": "Блок Автом. Полевого Ремонта", "am": "Блок Автом. Полевого Ремонта",
"bh": "Переборки", "bh": "Переборки",
"bl": "Пучковый лазер", "bl": "Пучковый Лазер",
"bsg": "Двухпоточный щитогенератор", "bsg": "Двухпоточный Щитогенератор",
"c": "Орудие", "c": "Орудие",
"cc": "Контроллер магнитного снаряда для сбора", "cc": "Контроллер магнитного снаряда для сбора",
"ch": "Разбрасыватель дипольных отражателей", "ch": "Разбрасыватель дипольных отражателей",
@@ -89,7 +89,7 @@
"fh": "Ангар для истребителя", "fh": "Ангар для истребителя",
"fi": "FSD-перехватчик", "fi": "FSD-перехватчик",
"fs": "Топливозаборник", "fs": "Топливозаборник",
"fsd": "Рамочно-сместительный двигатель", "fsd": "Рамочно Сместительный двигатель",
"ft": "Топливный бак", "ft": "Топливный бак",
"fx": "Контроллер магнитного снаряда для топлива", "fx": "Контроллер магнитного снаряда для топлива",
"hb": "Контроллер магнитного снаряда для взлома трюма", "hb": "Контроллер магнитного снаряда для взлома трюма",
@@ -110,7 +110,7 @@
"pcm": "Каюта пассажира первого класса", "pcm": "Каюта пассажира первого класса",
"pcq": "Каюта пассажира класса люкс", "pcq": "Каюта пассажира класса люкс",
"pd": "Распределитель питания", "pd": "Распределитель питания",
"pl": мпульсный лазер", "pl": "Ипмульсный лазер",
"po": "Точечная оборона", "po": "Точечная оборона",
"pp": "Силовая установка", "pp": "Силовая установка",
"psg": "Призматический щитогенератор", "psg": "Призматический щитогенератор",
@@ -122,7 +122,7 @@
"sc": "Сканер обнаружения", "sc": "Сканер обнаружения",
"scb": "Щитонакопитель", "scb": "Щитонакопитель",
"sg": "Щитогенератор", "sg": "Щитогенератор",
"ss": "Сканер поверхностей", "ss": "Сканер Поверхностей",
"t": "Маневровые двигатели", "t": "Маневровые двигатели",
"tp": "Торпедная стойка", "tp": "Торпедная стойка",
"ul": "Пульсирующие лазеры", "ul": "Пульсирующие лазеры",
@@ -130,7 +130,7 @@
"emptyrestricted": "пусто (ограниченно)", "emptyrestricted": "пусто (ограниченно)",
"damage dealt to": "Урон нанесён", "damage dealt to": "Урон нанесён",
"damage received from": "Урон получен от", "damage received from": "Урон получен от",
"against shields": "Против щитов", "against shields": "Против шитов",
"against hull": "Против корпуса", "against hull": "Против корпуса",
"total effective shield": "Общие эффективные щиты", "total effective shield": "Общие эффективные щиты",
"ammunition": "Припасы", "ammunition": "Припасы",
@@ -149,7 +149,7 @@
"eps": "Энергия в секунду", "eps": "Энергия в секунду",
"epsseps": "Энергия в секунду (поддерживаемая энергия в секунду)", "epsseps": "Энергия в секунду (поддерживаемая энергия в секунду)",
"hps": "Нагрев в секунду", "hps": "Нагрев в секунду",
"hpsshps": "Нагрев в секунду (поддерживаемый нагрев в секунду)", "hpsshps": "Heat per second (sustained heat per second)",
"damage by": "Урон", "damage by": "Урон",
"damage from": "Урон от", "damage from": "Урон от",
"shield cells": "Щитонакопители", "shield cells": "Щитонакопители",
@@ -183,7 +183,7 @@
"hullreinforcement": "Укрепление корпуса", "hullreinforcement": "Укрепление корпуса",
"integrity": "Целостность", "integrity": "Целостность",
"jitter": "Дрожание", "jitter": "Дрожание",
"kinres": "Сопротивление кинетическому урону", "kinres": "Сопротивление китетическому урону",
"maxfuel": "Макс. топлива на прыжок", "maxfuel": "Макс. топлива на прыжок",
"mass": "Масса", "mass": "Масса",
"optmass": "Оптимизированная масса", "optmass": "Оптимизированная масса",
@@ -381,4 +381,4 @@
"URL": "Ссылка", "URL": "Ссылка",
"WEP": "ОРУЖ", "WEP": "ОРУЖ",
"yes": "Да" "yes": "Да"
} }

View File

@@ -41,15 +41,6 @@ export default class AboutPage extends Page {
<h3>Chat</h3> <h3>Chat</h3>
<p>You can chat to us on our <a href='https://discord.gg/0uwCh6R62aPRjk9w' target='_blank'>EDCD Discord server</a>.</p> <p>You can chat to us on our <a href='https://discord.gg/0uwCh6R62aPRjk9w' target='_blank'>EDCD Discord server</a>.</p>
<h3>Supporting Coriolis</h3>
<p>Coriolis is an open source project, and I work on it in my free time. I have set up a patreon at <a href='https://www.patreon.com/coriolis_elite'>patreon.com/coriolis_elite</a>, which will be used to keep Coriolis up to date and the servers running.</p>
<form action="https://www.paypal.com/cgi-bin/webscr" method="post" target="_top">
<input type="hidden" name="cmd" value="_s-xclick"/>
<input type="hidden" name="hosted_button_id" value="SJBKT2SWEEU68" />
<input type="image" src="https://www.paypalobjects.com/en_AU/i/btn/btn_donate_SM.gif" border="0" name="submit" alt="PayPal The safer, easier way to pay online!" />
<img alt="" border="0" src="https://www.paypalobjects.com/en_AU/i/scr/pixel.gif" width="1" height="1" />
</form>
</div>; </div>;
} }
} }

View File

@@ -4,23 +4,14 @@ import { Ships } from 'coriolis-data/dist';
import cn from 'classnames'; import cn from 'classnames';
import Page from './Page'; import Page from './Page';
import Router from '../Router'; import Router from '../Router';
import Axios from 'axios';
import Persist from '../stores/Persist'; import Persist from '../stores/Persist';
import * as Utils from '../utils/UtilityFunctions'; import * as Utils from '../utils/UtilityFunctions';
import Ship from '../shipyard/Ship'; import Ship from '../shipyard/Ship';
import * as _ from 'lodash';
import { toDetailedBuild } from '../shipyard/Serializer'; import { toDetailedBuild } from '../shipyard/Serializer';
import { outfitURL } from '../utils/UrlGenerators'; import { outfitURL } from '../utils/UrlGenerators';
import { import { SHIP_FD_NAME_TO_CORIOLIS_NAME } from '../utils/CompanionApiUtils';
FloppyDisk, import { FloppyDisk, Bin, Switch, Download, Reload, LinkIcon, ShoppingIcon } from '../components/SvgIcons';
Bin,
Switch,
Download,
Reload,
LinkIcon,
ShoppingIcon,
MatIcon,
OrbisIcon
} from '../components/SvgIcons';
import LZString from 'lz-string'; import LZString from 'lz-string';
import ShipSummaryTable from '../components/ShipSummaryTable'; import ShipSummaryTable from '../components/ShipSummaryTable';
import StandardSlotSection from '../components/StandardSlotSection'; import StandardSlotSection from '../components/StandardSlotSection';
@@ -36,8 +27,6 @@ import EngagementRange from '../components/EngagementRange';
import OutfittingSubpages from '../components/OutfittingSubpages'; import OutfittingSubpages from '../components/OutfittingSubpages';
import ModalExport from '../components/ModalExport'; import ModalExport from '../components/ModalExport';
import ModalPermalink from '../components/ModalPermalink'; import ModalPermalink from '../components/ModalPermalink';
import ModalShoppingList from '../components/ModalShoppingList';
import ModalOrbis from '../components/ModalOrbis';
/** /**
* Document Title Generator * Document Title Generator
@@ -71,7 +60,6 @@ export default class OutfittingPage extends Page {
this._fuelUpdated = this._fuelUpdated.bind(this); this._fuelUpdated = this._fuelUpdated.bind(this);
this._opponentUpdated = this._opponentUpdated.bind(this); this._opponentUpdated = this._opponentUpdated.bind(this);
this._engagementRangeUpdated = this._engagementRangeUpdated.bind(this); this._engagementRangeUpdated = this._engagementRangeUpdated.bind(this);
this._sectionMenuRefs = {};
} }
/** /**
@@ -82,48 +70,98 @@ export default class OutfittingPage extends Page {
*/ */
_initState(props, context) { _initState(props, context) {
let params = context.route.params; let params = context.route.params;
let shipId = params.ship; let remoteID = params.remote;
let code = params.code;
let buildName = params.bn; if (remoteID) { // Remote instance, fetch data.
let data = Ships[shipId]; // Retrieve the basic ship properties, slots and defaults Axios.get('http://127.0.0.1:8081/api/ships/' + remoteID + '/fdev')
let savedCode = Persist.getBuild(shipId, buildName); .then((response) => {
if (!data) { let remote = response.data;
return { error: { message: 'Ship not found: ' + shipId } }; let buildName = params.bn;
}
let ship = new Ship(shipId, data.properties, data.slots); // Create a new Ship instance let shipId = SHIP_FD_NAME_TO_CORIOLIS_NAME[remote.Ship];
if (code) { let data = Ships[shipId];
ship.buildFrom(code); // Populate modules from serialized 'code' URL param
if (!data) {
return { error: { message: 'Ship not found: ' + shipId } };
}
let ship = new Ship(shipId, data.properties, data.slots);
ship.buildFromLoadout(remote.Modules);
this._getTitle = getTitle.bind(this, data.properties.name);
const { sys, eng, wep, boost, fuel, cargo, opponent, opponentBuild, opponentSys, opponentEng, opponentWep, engagementRange } = this._obtainControlFromCode(ship);
return {
error: null,
title: this._getTitle(buildName),
costTab: Persist.getCostTab() || 'costs',
buildName,
newBuildName: buildName,
shipId,
ship,
sys,
eng,
wep,
boost,
fuel,
cargo,
opponent,
opponentBuild,
opponentSys,
opponentEng,
opponentWep,
engagementRange
};
})
.catch((err) => {
this.state = { error: { message: 'Issue communicating with remote server'}};
})
} else { } else {
ship.buildWith(data.defaults); // Populate with default components let shipId = params.ship;
let data = Ships[shipId]; // Retrieve the basic ship properties, slots and defaults
let savedCode = Persist.getBuild(shipId, buildName);
let code = params.code;
let buildName = params.bn;
if (!data) {
return { error: { message: 'Ship not found: ' + shipId } };
}
let ship = new Ship(shipId, data.properties, data.slots); // Create a new Ship instance
if (code) {
ship.buildFrom(code); // Populate modules from serialized 'code' URL param
} else {
ship.buildWith(data.defaults); // Populate with default components
}
this._getTitle = getTitle.bind(this, data.properties.name);
// Obtain ship control from code
const { sys, eng, wep, boost, fuel, cargo, opponent, opponentBuild, opponentSys, opponentEng, opponentWep, engagementRange } = this._obtainControlFromCode(ship, code);
return {
error: null,
title: this._getTitle(buildName),
costTab: Persist.getCostTab() || 'costs',
buildName,
newBuildName: buildName,
shipId,
ship,
code,
savedCode,
sys,
eng,
wep,
boost,
fuel,
cargo,
opponent,
opponentBuild,
opponentSys,
opponentEng,
opponentWep,
engagementRange
};
} }
this._getTitle = getTitle.bind(this, data.properties.name);
// Obtain ship control from code
const { sys, eng, wep, boost, fuel, cargo, opponent, opponentBuild, opponentSys, opponentEng, opponentWep, engagementRange } = this._obtainControlFromCode(ship, code);
return {
error: null,
title: this._getTitle(buildName),
costTab: Persist.getCostTab() || 'costs',
buildName,
newBuildName: buildName,
shipId,
ship,
code,
savedCode,
sys,
eng,
wep,
boost,
fuel,
cargo,
opponent,
opponentBuild,
opponentSys,
opponentEng,
opponentWep,
engagementRange
};
} }
/** /**
@@ -505,23 +543,6 @@ export default class OutfittingPage extends Page {
this.context.showModal(<ModalPermalink url={window.location.href}/>); this.context.showModal(<ModalPermalink url={window.location.href}/>);
} }
/**
* Generate Orbis link
*/
_genOrbis() {
const data = {};
const ship = this.state.ship;
ship.coriolisId = ship.id;
data.coriolisShip = ship;
data.url = window.location.href;
data.title = this.state.buildName || ship.id;
data.description = this.state.buildName || ship.id;
data.ShipName = ship.id;
data.Ship = ship.id;
console.log(data);
this.context.showModal(<ModalOrbis ship={data}/>);
}
/** /**
* Open up a window for EDDB with a shopping list of our components * Open up a window for EDDB with a shopping list of our components
*/ */
@@ -536,13 +557,6 @@ export default class OutfittingPage extends Page {
window.open('https://eddb.io/station?s=' + shipId + '&m=' + modIds.join(',')); window.open('https://eddb.io/station?s=' + shipId + '&m=' + modIds.join(','));
} }
/**
* Generates the shopping list
*/
_genShoppingList() {
this.context.showModal(<ModalShoppingList ship={this.state.ship}/>);
}
/** /**
* Handle Key Down * Handle Key Down
* @param {Event} e Keyboard Event * @param {Event} e Keyboard Event
@@ -592,26 +606,17 @@ export default class OutfittingPage extends Page {
const shipSummaryMarker = `${ship.name}${_sStr}${_iStr}${_hStr}${_pStr}${_mStr}${ship.ladenMass}${cargo}${fuel}`; const shipSummaryMarker = `${ship.name}${_sStr}${_iStr}${_hStr}${_pStr}${_mStr}${ship.ladenMass}${cargo}${fuel}`;
const requirements = Ships[ship.id].requirements; const requirements = Ships[ship.id].requirements;
let requirementElements = []; var requirementElements = [];
/**
* Render the requirements for a ship / etc
* @param {string} className Class names
* @param {string} textKey The key for translating
* @param {String} tooltipTextKey Tooltip key
*/
function renderRequirement(className, textKey, tooltipTextKey) { function renderRequirement(className, textKey, tooltipTextKey) {
if (textKey.startsWith('empire') || textKey.startsWith('federation')) { requirementElements.push(<div key={textKey} className={className} onMouseEnter={termtip.bind(null, tooltipTextKey)} onMouseLeave={hide}>{translate(textKey)}</div>);
requirementElements.push(<div key={textKey} className={className} onMouseEnter={termtip.bind(null, tooltipTextKey)} onMouseLeave={hide}><a href={textKey.startsWith('empire') ? 'http://elite-dangerous.wikia.com/wiki/Empire/Ranks' : 'http://elite-dangerous.wikia.com/wiki/Federation/Ranks'} target="_blank" rel="noopener">{translate(textKey)}</a></div>);
} else {
requirementElements.push(<div key={textKey} className={className} onMouseEnter={termtip.bind(null, tooltipTextKey)} onMouseLeave={hide}>{translate(textKey)}</div>);
}
} }
if (requirements) { if (requirements) {
requirements.federationRank && renderRequirement('federation', 'federation rank ' + requirements.federationRank, 'federation rank required'); requirements.federationRank && renderRequirement('federation', 'federation rank ' + requirements.federationRank, 'federation rank required');
requirements.empireRank && renderRequirement('empire', 'empire rank ' + requirements.empireRank, 'empire rank required'); requirements.empireRank && renderRequirement('empire', 'empire rank ' + requirements.empireRank, 'empire rank required');
requirements.horizons && renderRequirement('horizons', 'horizons', 'horizons required'); requirements.horizons && renderRequirement('horizons', 'horizons', 'horizons required');
requirements.horizonsEarlyAdoption && renderRequirement('horizons', 'horizons early adoption', 'horizons early adoption required'); requirements.horizonsEarlyAdoption && renderRequirement('horizons', 'horizons early adoption', 'horizons early adoption required');
} }
return ( return (
@@ -645,21 +650,15 @@ export default class OutfittingPage extends Page {
<button onClick={this._genShortlink} onMouseOver={termtip.bind(null, 'shortlink')} onMouseOut={hide}> <button onClick={this._genShortlink} onMouseOver={termtip.bind(null, 'shortlink')} onMouseOut={hide}>
<LinkIcon className='lg' /> <LinkIcon className='lg' />
</button> </button>
<button onClick={this._genOrbis} onMouseOver={termtip.bind(null, 'PHASE_UPLOAD_ORBIS')} onMouseOut={hide}>
<OrbisIcon className='lg' />
</button>
<button onClick={this._genShoppingList} onMouseOver={termtip.bind(null, 'PHRASE_SHOPPING_MATS')} onMouseOut={hide}>
<MatIcon className='lg' />
</button>
</div> </div>
</div> </div>
{/* Main tables */} {/* Main tables */}
<ShipSummaryTable ship={ship} fuel={fuel} cargo={cargo} marker={shipSummaryMarker} pips={{ sys: this.state.sys, wep: this.state.wep, eng: this.state.eng }} /> <ShipSummaryTable ship={ship} fuel={fuel} cargo={cargo} marker={shipSummaryMarker} />
<StandardSlotSection ship={ship} fuel={fuel} cargo={cargo} code={standardSlotMarker} onChange={shipUpdated} onCargoChange={this._cargoUpdated} onFuelChange={this._fuelUpdated} currentMenu={menu} sectionMenuRefs={this._sectionMenuRefs}/> <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} sectionMenuRefs={this._sectionMenuRefs}/> <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} sectionMenuRefs={this._sectionMenuRefs}/> <HardpointSlotSection ship={ship} code={hardpointsSlotMarker} onChange={shipUpdated} onCargoChange={this._cargoUpdated} onFuelChange={this._fuelUpdated} currentMenu={menu} />
<UtilitySlotSection ship={ship} code={hardpointsSlotMarker} onChange={shipUpdated} onCargoChange={this._cargoUpdated} onFuelChange={this._fuelUpdated} currentMenu={menu} sectionMenuRefs={this._sectionMenuRefs}/> <UtilitySlotSection ship={ship} code={hardpointsSlotMarker} onChange={shipUpdated} onCargoChange={this._cargoUpdated} onFuelChange={this._fuelUpdated} currentMenu={menu} />
{/* Control of ship and opponent */} {/* Control of ship and opponent */}
<div className='group quarter'> <div className='group quarter'>

View File

@@ -42,7 +42,7 @@ function countInt(slot) {
passSlotType = 'pcq'; passSlotType = 'pcq';
passSlotRating = 'B'; passSlotRating = 'B';
} }
let passengerBay = passSlotType ? ModuleUtils.findMaxInternal(passSlotType, slot.maxClass, passSlotRating) : null; let passengerBay = passSlotType ? ModuleUtils.findInternal(passSlotType, slot.maxClass, passSlotRating) : null;
this.maxPassengers += passengerBay ? passengerBay.passengers : 0; this.maxPassengers += passengerBay ? passengerBay.passengers : 0;
} }
@@ -194,7 +194,6 @@ export default class ShipyardPage extends Page {
<td className='cn'>{s.standard[3]}</td> <td className='cn'>{s.standard[3]}</td>
<td className='cn'>{s.standard[4]}</td> <td className='cn'>{s.standard[4]}</td>
<td className='cn'>{s.standard[5]}</td> <td className='cn'>{s.standard[5]}</td>
<td className='cn'>{s.standard[6]}</td>
<td className={cn({ disabled: !s.hp[1] })}>{s.hp[1]}</td> <td className={cn({ disabled: !s.hp[1] })}>{s.hp[1]}</td>
<td className={cn({ disabled: !s.hp[2] })}>{s.hp[2]}</td> <td className={cn({ disabled: !s.hp[2] })}>{s.hp[2]}</td>
<td className={cn({ disabled: !s.hp[3] })}>{s.hp[3]}</td> <td className={cn({ disabled: !s.hp[3] })}>{s.hp[3]}</td>
@@ -274,11 +273,11 @@ export default class ShipyardPage extends Page {
for (let s of shipSummaries) { for (let s of shipSummaries) {
let shipSortValue = s[shipPredicate]; let shipSortValue = s[shipPredicate];
if(shipPredicateIndex != undefined) { if( shipPredicateIndex != undefined ) {
shipSortValue = shipSortValue[shipPredicateIndex]; shipSortValue = shipSortValue[shipPredicateIndex];
} }
if(shipSortValue != lastShipSortValue) { if( shipSortValue != lastShipSortValue ) {
backgroundHighlight = !backgroundHighlight; backgroundHighlight = !backgroundHighlight;
lastShipSortValue = shipSortValue; lastShipSortValue = shipSortValue;
} }
@@ -330,7 +329,7 @@ export default class ShipyardPage extends Page {
<th>&nbsp;</th> <th>&nbsp;</th>
<th colSpan={4}>{translate('base')}</th> <th colSpan={4}>{translate('base')}</th>
<th colSpan={5}>{translate('max')}</th> <th colSpan={5}>{translate('max')}</th>
<th className='lft' colSpan={7}></th> <th className='lft' colSpan={6}></th>
<th className='lft' colSpan={5}></th> <th className='lft' colSpan={5}></th>
<th className='lft' colSpan={8}></th> <th className='lft' colSpan={8}></th>
</tr> </tr>
@@ -346,9 +345,9 @@ export default class ShipyardPage extends Page {
<th className='sortable' onClick={sortShips('topBoost')}>{translate('boost')}</th> <th className='sortable' onClick={sortShips('topBoost')}>{translate('boost')}</th>
<th className='sortable' onClick={sortShips('maxJumpRange')}>{translate('jump')}</th> <th className='sortable' onClick={sortShips('maxJumpRange')}>{translate('jump')}</th>
<th className='sortable' onClick={sortShips('maxCargo')}>{translate('cargo')}</th> <th className='sortable' onClick={sortShips('maxCargo')}>{translate('cargo')}</th>
<th className='sortable' onClick={sortShips('maxPassengers')}>{translate('pax')}</th> <th className='sortable' onClick={sortShips('maxPassengers')}>{translate('passengers')}</th>
<th className='lft' colSpan={7}>{translate('core module classes')}</th> <th className='lft' colSpan={6}>{translate('core module classes')}</th>
<th colSpan={5} className='sortable lft' onClick={sortShips('hpCount')}>{translate('hardpoints')}</th> <th colSpan={5} className='sortable lft' onClick={sortShips('hpCount')}>{translate('hardpoints')}</th>
<th colSpan={8} className='sortable lft' onClick={sortShips('intCount')}>{translate('internal compartments')}</th> <th colSpan={8} className='sortable lft' onClick={sortShips('intCount')}>{translate('internal compartments')}</th>
</tr> </tr>
@@ -370,7 +369,7 @@ export default class ShipyardPage extends Page {
<th className='sortable' onMouseEnter={termtip.bind(null, 'life support')} onMouseLeave={hide} onClick={sortShips('standard', 3)}>{'ls'}</th> <th className='sortable' onMouseEnter={termtip.bind(null, 'life support')} onMouseLeave={hide} onClick={sortShips('standard', 3)}>{'ls'}</th>
<th className='sortable' onMouseEnter={termtip.bind(null, 'power distriubtor')} onMouseLeave={hide} onClick={sortShips('standard', 4)}>{'pd'}</th> <th className='sortable' onMouseEnter={termtip.bind(null, 'power distriubtor')} onMouseLeave={hide} onClick={sortShips('standard', 4)}>{'pd'}</th>
<th className='sortable' onMouseEnter={termtip.bind(null, 'sensors')} onMouseLeave={hide} onClick={sortShips('standard', 5)}>{'s'}</th> <th className='sortable' onMouseEnter={termtip.bind(null, 'sensors')} onMouseLeave={hide} onClick={sortShips('standard', 5)}>{'s'}</th>
<th className='sortable' onMouseEnter={termtip.bind(null, 'fuel tank')} onMouseLeave={hide} onClick={sortShips('standard', 6)}>{'ft'}</th>
<th className='sortable lft' onClick={sortShips('hp',1)}>{translate('S')}</th> <th className='sortable lft' onClick={sortShips('hp',1)}>{translate('S')}</th>
<th className='sortable' onClick={sortShips('hp', 2)}>{translate('M')}</th> <th className='sortable' onClick={sortShips('hp', 2)}>{translate('M')}</th>
<th className='sortable' onClick={sortShips('hp', 3)}>{translate('L')}</th> <th className='sortable' onClick={sortShips('hp', 3)}>{translate('L')}</th>

View File

@@ -7,20 +7,11 @@ import Module from './Module';
* @param {object} fsd The FDS object/component with maxfuel, fuelmul, fuelpower, optmass * @param {object} fsd The FDS object/component with maxfuel, fuelmul, fuelpower, optmass
* @param {number} fuel Optional - The fuel consumed during the jump * @param {number} fuel Optional - The fuel consumed during the jump
* @return {number} Distance in Light Years * @return {number} Distance in Light Years
* @param {object} ship Ship instance
*/ */
export function jumpRange(mass, fsd, fuel, ship) { export function jumpRange(mass, fsd, fuel) {
const fsdMaxFuelPerJump = fsd instanceof Module ? fsd.getMaxFuelPerJump() : fsd.maxfuel; const fsdMaxFuelPerJump = fsd instanceof Module ? fsd.getMaxFuelPerJump() : fsd.maxfuel;
const fsdOptimalMass = fsd instanceof Module ? fsd.getOptMass() : fsd.optmass; const fsdOptimalMass = fsd instanceof Module ? fsd.getOptMass() : fsd.optmass;
let jumpAddition = 0; return Math.pow(Math.min(fuel === undefined ? fsdMaxFuelPerJump : fuel, fsdMaxFuelPerJump) / fsd.fuelmul, 1 / fsd.fuelpower) * fsdOptimalMass / mass;
if (ship) {
for (const module of ship.internal) {
if (module && module.m && module.m.grp === 'gfsb') {
jumpAddition += module.m.getJumpBoost();
}
}
}
return (Math.pow(Math.min(fuel === undefined ? fsdMaxFuelPerJump : fuel, fsdMaxFuelPerJump) / fsd.fuelmul, 1 / fsd.fuelpower) * fsdOptimalMass / mass) + jumpAddition;
} }
/** /**
@@ -30,9 +21,8 @@ export function jumpRange(mass, fsd, fuel, ship) {
* @param {object} fsd The FDS object/component with maxfuel, fuelmul, fuelpower, optmass * @param {object} fsd The FDS object/component with maxfuel, fuelmul, fuelpower, optmass
* @param {number} fuel The total fuel available * @param {number} fuel The total fuel available
* @return {number} Distance in Light Years * @return {number} Distance in Light Years
* @param {object} ship Ship instance
*/ */
export function totalJumpRange(mass, fsd, fuel, ship) { export function totalJumpRange(mass, fsd, fuel) {
const fsdMaxFuelPerJump = fsd instanceof Module ? fsd.getMaxFuelPerJump() : fsd.maxfuel; const fsdMaxFuelPerJump = fsd instanceof Module ? fsd.getMaxFuelPerJump() : fsd.maxfuel;
const fsdOptimalMass = fsd instanceof Module ? fsd.getOptMass() : fsd.optmass; const fsdOptimalMass = fsd instanceof Module ? fsd.getOptMass() : fsd.optmass;
@@ -40,7 +30,7 @@ export function totalJumpRange(mass, fsd, fuel, ship) {
let totalRange = 0; let totalRange = 0;
while (fuelRemaining > 0) { while (fuelRemaining > 0) {
const fuelForThisJump = Math.min(fuelRemaining, fsdMaxFuelPerJump); const fuelForThisJump = Math.min(fuelRemaining, fsdMaxFuelPerJump);
totalRange += this.jumpRange(mass, fsd, fuelForThisJump, ship); totalRange += this.jumpRange(mass, fsd, fuelForThisJump);
// Mass is reduced // Mass is reduced
mass -= fuelForThisJump; mass -= fuelForThisJump;
fuelRemaining -= fuelForThisJump; fuelRemaining -= fuelForThisJump;
@@ -55,7 +45,7 @@ export function totalJumpRange(mass, fsd, fuel, ship) {
* @param {number} baseShield Base Shield strength MJ for ship * @param {number} baseShield Base Shield strength MJ for ship
* @param {object} sg The shield generator used * @param {object} sg The shield generator used
* @param {number} multiplier Shield multiplier for ship (1 + shield boosters if any) * @param {number} multiplier Shield multiplier for ship (1 + shield boosters if any)
* @return {number} Approximate shield strengh in MJ * @return {number} Approximate shield strengh in MJ
*/ */
export function shieldStrength(mass, baseShield, sg, multiplier) { export function shieldStrength(mass, baseShield, sg, multiplier) {
// sg might be a module or a template; handle either here // sg might be a module or a template; handle either here
@@ -65,12 +55,13 @@ export function shieldStrength(mass, baseShield, sg, multiplier) {
let minMul = sg instanceof Module ? sg.getMinMul() : sg.minmul; let minMul = sg instanceof Module ? sg.getMinMul() : sg.minmul;
let optMul = sg instanceof Module ? sg.getOptMul() : sg.optmul; let optMul = sg instanceof Module ? sg.getOptMul() : sg.optmul;
let maxMul = sg instanceof Module ? sg.getMaxMul() : sg.maxmul; let maxMul = sg instanceof Module ? sg.getMaxMul() : sg.maxmul;
let xnorm = Math.min(1, (maxMass - mass) / (maxMass - minMass)); 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 exponent = Math.log((optMul - minMul) / (maxMul - minMul)) / Math.log(Math.min(1, (maxMass - optMass) / (maxMass - minMass)));
let ynorm = Math.pow(xnorm, exponent); let ynorm = Math.pow(xnorm, exponent);
let mul = minMul + ynorm * (maxMul - minMul); let mul = minMul + ynorm * (maxMul - minMul);
return (baseShield * mul * multiplier); return baseShield * mul * multiplier;
} }
/** /**
@@ -96,16 +87,6 @@ export function speed(mass, baseSpeed, thrusters, engpip) {
return results; return results;
} }
/**
* Calculate pip multiplier for speed.
* @param {number} baseSpeed The base speed of ship in data
* @param {number} topSpeed The top speed of ship in data
* @return {number} The multiplier that pips affect speed.
*/
export function calcPipSpeed(baseSpeed, topSpeed) {
return (topSpeed - baseSpeed) / (4 * topSpeed);
}
/** /**
* Calculate pitch of a ship based on mass and thrusters * Calculate pitch of a ship based on mass and thrusters
* @param {number} mass the mass of the ship * @param {number} mass the mass of the ship
@@ -221,7 +202,7 @@ function calcValue(minMass, optMass, maxMass, minMul, optMul, maxMul, mass, base
* Calculate speed for a given setup * Calculate speed for a given setup
* @param {number} mass the mass of the ship * @param {number} mass the mass of the ship
* @param {number} baseSpeed the base speed of the ship * @param {number} baseSpeed the base speed of the ship
* @param {object} thrusters the thrusters of the ship * @param {ojbect} thrusters the thrusters of the ship
* @param {number} engpip the multiplier per pip to engines * @param {number} engpip the multiplier per pip to engines
* @param {number} eng the pips to engines * @param {number} eng the pips to engines
* @param {number} boostFactor the boost factor for ths ship * @param {number} boostFactor the boost factor for ths ship
@@ -249,7 +230,7 @@ export function calcSpeed(mass, baseSpeed, thrusters, engpip, eng, boostFactor,
* Calculate pitch for a given setup * Calculate pitch for a given setup
* @param {number} mass the mass of the ship * @param {number} mass the mass of the ship
* @param {number} basePitch the base pitch of the ship * @param {number} basePitch the base pitch of the ship
* @param {object} thrusters the thrusters of the ship * @param {ojbect} thrusters the thrusters of the ship
* @param {number} engpip the multiplier per pip to engines * @param {number} engpip the multiplier per pip to engines
* @param {number} eng the pips to engines * @param {number} eng the pips to engines
* @param {number} boostFactor the boost factor for ths ship * @param {number} boostFactor the boost factor for ths ship
@@ -340,76 +321,41 @@ export function shieldMetrics(ship, sys) {
const maxSysResistance = this.sysResistance(4); const maxSysResistance = this.sysResistance(4);
let shield = {}; let shield = {};
const dimReturnLine = (res) => 1 - (1 - res) * 0.7;
const shieldGeneratorSlot = ship.findInternalByGroup('sg'); const shieldGeneratorSlot = ship.findInternalByGroup('sg');
if (shieldGeneratorSlot && shieldGeneratorSlot.enabled && shieldGeneratorSlot.m) { if (shieldGeneratorSlot && shieldGeneratorSlot.enabled && shieldGeneratorSlot.m) {
const shieldGenerator = shieldGeneratorSlot.m; const shieldGenerator = shieldGeneratorSlot.m;
let res = {
kin: shieldGenerator.kinres,
therm: shieldGenerator.thermres,
expl: shieldGenerator.explres
};
// Boosters // Boosters
let boost = 1; let boost = 1;
let boosterExplDmg = 1; let boosterExplDmg = 1;
let boosterKinDmg = 1; let boosterKinDmg = 1;
let boosterThermDmg = 1; let boosterThermDmg = 1;
// const explDim = dimReturnLine(shieldGenerator.explres);
// const thermDim = dimReturnLine(shieldGenerator.thermres);
// const kinDim = dimReturnLine(shieldGenerator.kinres);
for (let slot of ship.hardpoints) { for (let slot of ship.hardpoints) {
if (slot.enabled && slot.m && slot.m.grp == 'sb') { if (slot.enabled && slot.m && slot.m.grp == 'sb') {
boost += slot.m.getShieldBoost(); boost += slot.m.getShieldBoost();
res.expl += slot.m.getExplosiveResistance();
res.kin += slot.m.getKineticResistance();
res.therm += slot.m.getThermalResistance();
boosterExplDmg = boosterExplDmg * (1 - slot.m.getExplosiveResistance()); boosterExplDmg = boosterExplDmg * (1 - slot.m.getExplosiveResistance());
boosterKinDmg = boosterKinDmg * (1 - slot.m.getKineticResistance()); boosterKinDmg = boosterKinDmg * (1 - slot.m.getKineticResistance());
boosterThermDmg = boosterThermDmg * (1 - slot.m.getThermalResistance()); boosterThermDmg = boosterThermDmg * (1 - slot.m.getThermalResistance());
} }
if (slot.m && slot.m.grp == 'gsrp') {
}
} }
// Calculate diminishing returns for boosters // Calculate diminishing returns for boosters
// Diminishing returns not currently in-game // Diminishing returns not currently in-game
// boost = Math.min(boost, (1 - Math.pow(Math.E, -0.7 * boost)) * 2.5); // boost = Math.min(boost, (1 - Math.pow(Math.E, -0.7 * boost)) * 2.5);
// Remove base shield generator strength // Remove base shield generator strength
boost -= 1; boost -= 1;
// Apply diminishing returns
boosterExplDmg = boosterExplDmg > 0.7 ? boosterExplDmg : 0.7 - (0.7 - boosterExplDmg) / 2;
boosterKinDmg = boosterKinDmg > 0.7 ? boosterKinDmg : 0.7 - (0.7 - boosterKinDmg) / 2;
boosterThermDmg = boosterThermDmg > 0.7 ? boosterThermDmg : 0.7 - (0.7 - boosterThermDmg) / 2;
// if (res.expl > explDim) { const generatorStrength = this.shieldStrength(ship.hullMass, ship.baseShieldStrength, shieldGenerator, 1);
// const overage = (res.expl - explDim) * 0.5;
// res.expl = explDim + overage;
// boosterExplDmg = explDim + overage;
// }
//
// if (res.therm > thermDim) {
// const overage = (res.therm - thermDim) * 0.5;
// res.therm = thermDim + overage;
// boosterThermDmg = thermDim + overage;
// }
//
// if (res.kin > kinDim) {
// const overage = (res.kin - kinDim) * 0.5;
// res.kin = kinDim + overage;
// boosterKinDmg = kinDim + overage;
// }
let shieldAddition = 0;
if (ship) {
for (const module of ship.internal) {
if (module && module.m && module.m.grp === 'gsrp') {
shieldAddition += module.m.getShieldAddition();
}
}
}
let generatorStrength = this.shieldStrength(ship.hullMass, ship.baseShieldStrength, shieldGenerator, 1);
const boostersStrength = generatorStrength * boost; 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 // Recover time is the time taken to go from 0 to 50%. It includes a 16-second wait before shields start to recover
const shieldToRecover = (generatorStrength + boostersStrength + shieldAddition) / 2; const shieldToRecover = (generatorStrength + boostersStrength) / 2;
const powerDistributor = ship.standard[4].m; const powerDistributor = ship.standard[4].m;
const sysRechargeRate = this.sysRechargeRate(powerDistributor, sys); const sysRechargeRate = this.sysRechargeRate(powerDistributor, sys);
@@ -428,7 +374,7 @@ export function shieldMetrics(ship, sys) {
const remainingShieldToRecover = shieldToRecover - capacitorLifetime * shieldGenerator.getBrokenRegenerationRate(); const remainingShieldToRecover = shieldToRecover - capacitorLifetime * shieldGenerator.getBrokenRegenerationRate();
if (sys === 0) { if (sys === 0) {
// No system pips so will never recover shields // No system pips so will never recover shields
recover = Math.Infinity; recover = Math.Inf;
} else { } else {
// Recover remaining shields at the rate of the power distributor's recharge // Recover remaining shields at the rate of the power distributor's recharge
recover += remainingShieldToRecover / (sysRechargeRate / 0.6); recover += remainingShieldToRecover / (sysRechargeRate / 0.6);
@@ -436,7 +382,7 @@ export function shieldMetrics(ship, sys) {
} }
// Recharge time is the time taken to go from 50% to 100% // Recharge time is the time taken to go from 50% to 100%
const shieldToRecharge = (generatorStrength + boostersStrength + shieldAddition) / 2; const shieldToRecharge = (generatorStrength + boostersStrength) / 2;
// Our initial regeneration comes from the SYS capacitor store, which is replenished as it goes // Our initial regeneration comes from the SYS capacitor store, which is replenished as it goes
// 0.6 is a magic number from FD: each 0.6 MW of energy from the power distributor recharges 1 MJ/s of regeneration // 0.6 is a magic number from FD: each 0.6 MW of energy from the power distributor recharges 1 MJ/s of regeneration
@@ -463,9 +409,8 @@ export function shieldMetrics(ship, sys) {
shield = { shield = {
generator: generatorStrength, generator: generatorStrength,
boosters: boostersStrength, boosters: boostersStrength,
addition: shieldAddition,
cells: ship.shieldCells, cells: ship.shieldCells,
total: generatorStrength + boostersStrength + ship.shieldCells + shieldAddition, total: generatorStrength + boostersStrength + ship.shieldCells,
recover, recover,
recharge, recharge,
}; };
@@ -480,74 +425,34 @@ export function shieldMetrics(ship, sys) {
max: 1 - maxSysResistance max: 1 - maxSysResistance
}; };
/**
* An object that stores a selection of difference damage multipliers that
* deal with a ship's shield strength.
* @typedef {Object} ShieldDamageMults
* @property {number} generator Base damage multiplier of the shield
* contributing it's base resistance.
* @property {number} boosters Damage multiplier contributed by all
* boosters, i.e. `rawMj / (generator * boosters)` equals shield strength
* with 0 pips to sys.
* @property {number} sys Damage multiplier contributed by pips to sys.
* @property {number} base Damage multiplier with 0 pips to sys; just
* boosters and shield generator. Equals `generator * boosters`.
* @property {number} total Damage multiplier with current pip settings.
* @property {number} max Damage multiplier with 4 pips to sys.
*/
let sgExplosiveDmg = 1 - shieldGenerator.getExplosiveResistance();
let sgSbExplosiveDmg = diminishDamageMult(sgExplosiveDmg * 0.7, (1 - shieldGenerator.getExplosiveResistance()) * boosterExplDmg);
/** @type {ShieldDamageMults} */
shield.explosive = { shield.explosive = {
generator: sgExplosiveDmg, generator: 1 - shieldGenerator.getExplosiveResistance(),
boosters: sgSbExplosiveDmg / sgExplosiveDmg, boosters: boosterExplDmg,
sys: (1 - sysResistance), sys: (1 - sysResistance),
base: sgSbExplosiveDmg, total: (1 - shieldGenerator.getExplosiveResistance()) * boosterExplDmg * (1 - sysResistance),
total: sgSbExplosiveDmg * (1 - sysResistance), max: (1 - shieldGenerator.getExplosiveResistance()) * boosterExplDmg * (1 - maxSysResistance)
max: sgSbExplosiveDmg * (1 - maxSysResistance),
}; };
let sgKineticDmg = 1 - shieldGenerator.getKineticResistance();
let sgSbKineticDmg = diminishDamageMult(sgKineticDmg * 0.7, (1 - shieldGenerator.getKineticResistance()) * boosterKinDmg);
/** @type {ShieldDamageMults} */
shield.kinetic = { shield.kinetic = {
generator: sgKineticDmg, generator: 1 - shieldGenerator.getKineticResistance(),
boosters: sgSbKineticDmg / sgKineticDmg, boosters: boosterKinDmg,
sys: (1 - sysResistance), sys: (1 - sysResistance),
base: sgSbKineticDmg, total: (1 - shieldGenerator.getKineticResistance()) * boosterKinDmg * (1 - sysResistance),
total: sgSbKineticDmg * (1 - sysResistance), max: (1 - shieldGenerator.getKineticResistance()) * boosterKinDmg * (1 - maxSysResistance)
max: sgSbKineticDmg * (1 - maxSysResistance),
}; };
let sgThermalDmg = 1 - shieldGenerator.getThermalResistance();
let sgSbThermalDmg = diminishDamageMult(sgThermalDmg * 0.7, (1 - shieldGenerator.getThermalResistance()) * boosterThermDmg);
/** @type {ShieldDamageMults} */
shield.thermal = { shield.thermal = {
generator: sgThermalDmg, generator: 1 - shieldGenerator.getThermalResistance(),
boosters: sgSbThermalDmg / sgThermalDmg, boosters: boosterThermDmg,
sys: (1 - sysResistance), sys: (1 - sysResistance),
base: sgSbThermalDmg, total: (1 - shieldGenerator.getThermalResistance()) * boosterThermDmg * (1 - sysResistance),
total: sgSbThermalDmg * (1 - sysResistance), max: (1 - shieldGenerator.getThermalResistance()) * boosterThermDmg * (1 - maxSysResistance)
max: sgSbThermalDmg * (1 - maxSysResistance),
}; };
} }
return shield; return shield;
} }
/**
* Calculate time from one boost to another
* @return {number} Boost frequency in seconds
* @param {Ship} ship Ship object
*/
export function calcBoost(ship) {
if (!ship.boostEnergy || !ship.standard[4] || !ship.standard[4].m) {
return undefined;
}
return ship.boostEnergy / ship.standard[4].m.getEnginesRechargeRate();
}
/** /**
* Calculate armour metrics * Calculate armour metrics
* @param {Object} ship The ship * @param {Object} ship The ship
@@ -560,58 +465,33 @@ export function armourMetrics(ship) {
let moduleArmour = 0; let moduleArmour = 0;
let moduleProtection = 1; let moduleProtection = 1;
const bulkheads = ship.bulkheads.m;
let hullExplDmg = 1; let hullExplDmg = 1;
let hullKinDmg = 1; let hullKinDmg = 1;
let hullThermDmg = 1; let hullThermDmg = 1;
let hullCausDmg = 1;
// const dimReturnLine = (res) => 1 - (1 - res) * 0.7;
// let res = {
// kin: 0,
// therm: 0,
// expl: 0
// };
// Armour from HRPs and module armour from MRPs // Armour from HRPs and module armour from MRPs
for (let slot of ship.internal) { for (let slot of ship.internal) {
if (slot.m && (slot.m.grp === 'hr' || slot.m.grp === 'ghrp' || slot.m.grp == 'mahr')) { if (slot.m && slot.m.grp == 'hr') {
armourReinforcement += slot.m.getHullReinforcement(); armourReinforcement += slot.m.getHullReinforcement();
// Hull boost for HRPs is applied against the ship's base armour // Hull boost for HRPs is applied against the ship's base armour
armourReinforcement += ship.baseArmour * slot.m.getModValue('hullboost') / 10000; armourReinforcement += ship.baseArmour * slot.m.getModValue('hullboost') / 10000;
// res.expl += slot.m.getExplosiveResistance();
// res.kin += slot.m.getKineticResistance();
// res.therm += slot.m.getThermalResistance();
hullExplDmg = hullExplDmg * (1 - slot.m.getExplosiveResistance()); hullExplDmg = hullExplDmg * (1 - slot.m.getExplosiveResistance());
hullKinDmg = hullKinDmg * (1 - slot.m.getKineticResistance()); hullKinDmg = hullKinDmg * (1 - slot.m.getKineticResistance());
hullThermDmg = hullThermDmg * (1 - slot.m.getThermalResistance()); hullThermDmg = hullThermDmg * (1 - slot.m.getThermalResistance());
hullCausDmg = hullCausDmg * (1 - slot.m.getCausticResistance());
} }
if (slot.m && (slot.m.grp == 'mrp' || slot.m.grp == 'gmrp')) { if (slot.m && slot.m.grp == 'mrp') {
moduleArmour += slot.m.getIntegrity(); moduleArmour += slot.m.getIntegrity();
moduleProtection = moduleProtection * (1 - slot.m.getProtection()); moduleProtection = moduleProtection * (1 - slot.m.getProtection());
} }
} }
moduleProtection = 1 - moduleProtection; moduleProtection = 1 - moduleProtection;
// const explDim = dimReturnLine(bulkheads.explres); // Apply diminishing returns
// const thermDim = dimReturnLine(bulkheads.thermres); hullExplDmg = hullExplDmg > 0.7 ? hullExplDmg : 0.7 - (0.7 - hullExplDmg) / 2;
// const kinDim = dimReturnLine(bulkheads.kinres); hullKinDmg = hullKinDmg > 0.7 ? hullKinDmg : 0.7 - (0.7 - hullKinDmg) / 2;
// if (res.expl > explDim) { hullThermDmg = hullThermDmg > 0.7 ? hullThermDmg : 0.7 - (0.7 - hullThermDmg) / 2;
// const overage = (res.expl - explDim) * 0.5;
// res.expl = explDim + overage;
// hullExplDmg = explDim + overage;
// }
//
// if (res.therm > thermDim) {
// const overage = (res.therm - thermDim) * 0.5;
// res.therm = thermDim + overage;
// hullThermDmg = thermDim + overage;
// }
//
// if (res.kin > kinDim) {
// const overage = (res.kin - kinDim) * 0.5;
// res.kin = kinDim + overage;
// hullKinDmg = kinDim + overage;
// }
const armour = { const armour = {
bulkheads: armourBulkheads, bulkheads: armourBulkheads,
@@ -629,41 +509,24 @@ export function armourMetrics(ship) {
total: 1 total: 1
}; };
let armourExplDmg = diminishDamageMult(0.7, 1 - ship.bulkheads.m.getExplosiveResistance());
let armourReinforcedExplDmg = diminishDamageMult(0.7, (1 - ship.bulkheads.m.getExplosiveResistance()) * hullExplDmg);
armour.explosive = { armour.explosive = {
bulkheads: armourExplDmg, bulkheads: 1 - ship.bulkheads.m.getExplosiveResistance(),
reinforcement: armourReinforcedExplDmg / armourExplDmg, reinforcement: hullExplDmg,
total: armourReinforcedExplDmg, total: (1 - ship.bulkheads.m.getExplosiveResistance()) * hullExplDmg
res: 1 - armourReinforcedExplDmg
}; };
let armourKinDmg = diminishDamageMult(0.7, 1 - ship.bulkheads.m.getKineticResistance());
let armourReinforcedKinDmg = diminishDamageMult(0.7, (1 - ship.bulkheads.m.getKineticResistance()) * hullKinDmg);
armour.kinetic = { armour.kinetic = {
bulkheads: armourKinDmg, bulkheads: 1 - ship.bulkheads.m.getKineticResistance(),
reinforcement: armourReinforcedKinDmg / armourKinDmg, reinforcement: hullKinDmg,
total: armourReinforcedKinDmg, total: (1 - ship.bulkheads.m.getKineticResistance()) * hullKinDmg
res: 1 - armourReinforcedKinDmg
}; };
let armourThermDmg = diminishDamageMult(0.7, 1 - ship.bulkheads.m.getThermalResistance());
let armourReinforcedThermDmg = diminishDamageMult(0.7, (1 - ship.bulkheads.m.getThermalResistance()) * hullThermDmg);
armour.thermal = { armour.thermal = {
bulkheads: armourThermDmg, bulkheads: 1 - ship.bulkheads.m.getThermalResistance(),
reinforcement: armourReinforcedThermDmg / armourThermDmg, reinforcement: hullThermDmg,
total: armourReinforcedThermDmg, total: (1 - ship.bulkheads.m.getThermalResistance()) * hullThermDmg
res: 1 - armourReinforcedThermDmg
}; };
let armourCausDmg = diminishDamageMult(0.7, 1 - ship.bulkheads.m.getCausticResistance());
let armourReinforcedCausDmg = diminishDamageMult(0.7, (1 - ship.bulkheads.m.getCausticResistance()) * hullCausDmg);
armour.caustic = {
bulkheads: armourCausDmg,
reinforcement: armourReinforcedCausDmg / armourCausDmg,
total: armourReinforcedCausDmg,
res: 1 - armourReinforcedCausDmg,
};
return armour; return armour;
} }
@@ -835,55 +698,20 @@ export function _sustainedDps(ship, opponent, opponentShields, opponentArmour, e
return { shieldsdps, armoursdps, eps }; return { shieldsdps, armoursdps, eps };
} }
/**
* Stores SDPS split up by type.
* @typedef {Object} SDps
* @property {number} absolute Damage of type absolute
* @property {number} explosive Damage of type explosive
* @property {number} kinetic Damage of type kinetic
* @property {number} thermal Damage of type thermal
* @property {number} [total] Sum of all damage types
*/
/**
* An object that holds information about SDPS for a given weapon and opponent.
* @typedef {Object} WeaponDamage
* @property {number} eps Energy per second
* @property {Object} damage An object that stores damage inflicted by
* the weapon.
* @property {Object} effectiveness An object that stores the effectiveness of
* the weapon against the opponent given.
*/
/**
* Stores overall SDPS and against a given opponent's shields and armour.
* @typedef {Object} WeaponDamage~damage
* @property {SDps} base Overall SDPS.
* @property {SDps} shields SDPS against the given opponent's shields.
* @property {SDps} armour SDPS against the given opponent's armour.
*/
/** /**
* Calculate the sustained DPS for a weapon at a given range * Calculate the sustained DPS for a weapon at a given range
* @param {Object} m The weapon * @param {Object} m The weapon
* @param {Object} opponent The opponent ship * @param {Object} opponent The opponent ship
* @param {Object} opponentShields The opponent's shield resistances * @param {Object} opponentShields The opponent's shield resistances
* @param {Object} opponentArmour The opponent's armour resistances * @param {Object} opponentArmour The opponent's armour resistances
* @param {int} engagementrange The range between the ship and opponent * @param {int} engagementrange The range between the ship and opponent
* @returns {WeaponDamage} Sustained DPS for shield and armour * @returns {Object} Sustained DPS for shield and armour
*/ */
export function _weaponSustainedDps(m, opponent, opponentShields, opponentArmour, engagementrange) { export function _weaponSustainedDps(m, opponent, opponentShields, opponentArmour, engagementrange) {
const opponentHasShields = opponentShields.generator ? true : false; const opponentHasShields = opponentShields.generator ? true : false;
const weapon = { const weapon = {
eps: 0, eps: 0,
damage: { damage: {
base: {
absolute: 0,
explosive: 0,
kinetic: 0,
thermal: 0,
total: 0,
},
shields: { shields: {
absolute: 0, absolute: 0,
explosive: 0, explosive: 0,
@@ -917,7 +745,7 @@ export function _weaponSustainedDps(m, opponent, opponentShields, opponentArmour
weapon.eps = m.getClip() ? (m.getClip() * m.getEps() / m.getRoF()) / ((m.getClip() / m.getRoF()) + m.getReload()) : m.getEps(); weapon.eps = m.getClip() ? (m.getClip() * m.getEps() / m.getRoF()) / ((m.getClip() / m.getRoF()) + m.getReload()) : m.getEps();
// Initial sustained DPS // Initial sustained DPS
let sDps = m.getSDps(); let sDps = m.getClip() ? (m.getClip() * m.getDps() / m.getRoF()) / ((m.getClip() / m.getRoF()) + m.getReload()) : m.getDps();
// Take fall-off in to account // Take fall-off in to account
const falloff = m.getFalloff(); const falloff = m.getFalloff();
@@ -928,12 +756,6 @@ export function _weaponSustainedDps(m, opponent, opponentShields, opponentArmour
sDps *= dropoff; sDps *= dropoff;
} }
weapon.damage.base.absolute = sDps * m.getDamageDist().A;
weapon.damage.base.explosive = sDps * m.getDamageDist().E;
weapon.damage.base.kinetic = sDps * m.getDamageDist().K;
weapon.damage.base.thermal = sDps * m.getDamageDist().T;
weapon.damage.base.total = sDps;
// Piercing/hardness modifier (for armour only) // Piercing/hardness modifier (for armour only)
const armourMultiple = m.getPiercing() >= opponent.hardness ? 1 : m.getPiercing() / opponent.hardness; const armourMultiple = m.getPiercing() >= opponent.hardness ? 1 : m.getPiercing() / opponent.hardness;
weapon.effectiveness.armour.hardness = armourMultiple; weapon.effectiveness.armour.hardness = armourMultiple;
@@ -1031,17 +853,3 @@ export function timeToDeplete(amount, dps, eps, capacity, recharge) {
} }
} }
} }
/**
* Applies diminishing returns to resistances.
* @param {number} diminishFrom The base resistance up to which no diminishing returns are applied.
* @param {number} damageMult Resistance as damage multiplier
* @returns {number} Actual damage multiplier
*/
export function diminishDamageMult(diminishFrom, damageMult) {
if (damageMult > diminishFrom) {
return damageMult;
} else {
return (diminishFrom / 2) + 0.5 * damageMult;
}
}

View File

@@ -9,16 +9,12 @@ export const StandardArray = [
'pd', // Power Distributor 'pd', // Power Distributor
's', // Sensors 's', // Sensors
'ft', // Fuel Tank '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 // Map to lookup group labels/names for component grp, used for JSON Serialization
export const ModuleGroupToName = { export const ModuleGroupToName = {
// Standard // Standard
pp: 'Power Plant', pp: 'Power Plant',
gpp: 'Guardian Hybrid Power Plant',
gpd: 'Guardian Power Distributor',
t: 'Thrusters', t: 'Thrusters',
fsd: 'Frame Shift Drive', fsd: 'Frame Shift Drive',
ls: 'Life Support', ls: 'Life Support',
@@ -52,11 +48,6 @@ export const ModuleGroupToName = {
pcq: 'Luxury Passenger Cabin', pcq: 'Luxury Passenger Cabin',
cc: 'Collector Limpet Controller', cc: 'Collector Limpet Controller',
ss: 'Surface Scanner', ss: 'Surface Scanner',
gsrp: 'Guardian Shield Reinforcement Packages',
gfsb: 'Guardian Frame Shift Drive Booster',
ghrp: 'Guardian Hull Reinforcement Package',
gmrp: 'Guardian Module Reinforcement Package',
mahr: 'Meta Alloy Hull Reinforcement Package',
// Hard Points // Hard Points
bl: 'Beam Laser', bl: 'Beam Laser',
@@ -84,16 +75,7 @@ export const ModuleGroupToName = {
sb: 'Shield Booster', sb: 'Shield Booster',
tp: 'Torpedo Pylon', tp: 'Torpedo Pylon',
sfn: 'Shutdown Field Neutraliser', sfn: 'Shutdown Field Neutraliser',
xs: 'Xeno Scanner', xs: 'Xeno Scanner'
rcpl: 'Recon Limpet Controller',
rsl: 'Research Limpet Controller',
dtl: 'Decontamination Limpet Controller',
gpc: 'Guardian Plasma Charger',
ggc: 'Guardian Gauss Cannon',
tbsc: 'Shock Cannon',
gsc: 'Guardian Shard Cannon',
tbem: 'Enzyme Missile Rack',
tbrfl: 'Remote Release Flechette Launcher',
}; };
let GrpNameToCodeMap = {}; let GrpNameToCodeMap = {};

File diff suppressed because it is too large Load Diff

View File

@@ -63,6 +63,7 @@ export function standard(type, id) {
if (!isNaN(type)) { if (!isNaN(type)) {
type = StandardArray[type]; type = StandardArray[type];
} }
let s = Modules.standard[type].find(e => e.id == id || (e.class == id.charAt(0) && e.rating == id.charAt(1))); let s = Modules.standard[type].find(e => e.id == id || (e.class == id.charAt(0) && e.rating == id.charAt(1)));
if (s) { if (s) {
s = new Module({ template: s }); s = new Module({ template: s });
@@ -195,29 +196,6 @@ export function findInternal(groupName, clss, rating, name) {
return null; return null;
} }
/**
* Finds an internal module based on Class, Rating, Group and/or name.
* At least one of Group name or unique module name must be provided.
* will start searching at specified class and proceed lower until a
* module is found or 0 is hit
* Uses findInternal internally
*
* @param {String} groupName [Optional] Full name or abbreviated name for module group
* @param {integer} clss module Class
* @param {String} rating module Rating
* @param {String} name [Optional] Long/unique name for module -e.g. 'Advanced Discover Scanner'
* @return {Object} The module if found, null if not found
*/
export function findMaxInternal(groupName, clss, rating, name) {
let foundModule = null;
let currentClss = clss;
while (currentClss > 0 && foundModule == null) {
foundModule = findInternal(groupName, currentClss, rating, name);
currentClss = currentClss - 1;
}
return foundModule;
}
/** /**
* Finds an internal Module ID based on Class, Rating, Group and/or name. * Finds an internal Module ID based on Class, Rating, Group and/or name.
* At least one ofGroup name or unique module name must be provided * At least one ofGroup name or unique module name must be provided

View File

@@ -7,10 +7,9 @@ import LZString from 'lz-string';
import * as _ from 'lodash'; import * as _ from 'lodash';
import isEqual from 'lodash/lang'; import isEqual from 'lodash/lang';
import { Ships, Modifications } from 'coriolis-data/dist'; import { Ships, Modifications } from 'coriolis-data/dist';
import { chain } from 'lodash';
const zlib = require('zlib'); const zlib = require('zlib');
const UNIQUE_MODULES = ['psg', 'sg', 'bsg', 'rf', 'fs', 'fh', 'gfsb']; const UNIQUE_MODULES = ['psg', 'sg', 'bsg', 'rf', 'fs', 'fh'];
// Constants for modifications struct // Constants for modifications struct
const SLOT_ID_DONE = -1; const SLOT_ID_DONE = -1;
@@ -152,7 +151,7 @@ export default class Ship {
* @return {Number} Jump range in Light Years * @return {Number} Jump range in Light Years
*/ */
calcLadenRange(massDelta, fuel, fsd) { calcLadenRange(massDelta, fuel, fsd) {
return Calc.jumpRange(this.ladenMass + (massDelta || 0), fsd || this.standard[2].m, fuel, this); return Calc.jumpRange(this.ladenMass + (massDelta || 0), fsd || this.standard[2].m, fuel);
} }
/** /**
@@ -165,7 +164,7 @@ export default class Ship {
calcUnladenRange(massDelta, fuel, fsd) { calcUnladenRange(massDelta, fuel, fsd) {
fsd = fsd || this.standard[2].m; fsd = fsd || this.standard[2].m;
let fsdMaxFuelPerJump = fsd instanceof Module ? fsd.getMaxFuelPerJump() : fsd.maxfuel; let fsdMaxFuelPerJump = fsd instanceof Module ? fsd.getMaxFuelPerJump() : fsd.maxfuel;
return Calc.jumpRange(this.unladenMass + (massDelta || 0) + Math.min(fsdMaxFuelPerJump, fuel || this.fuelCapacity), fsd || this.standard[2].m, fuel, this); return Calc.jumpRange(this.unladenMass + (massDelta || 0) + Math.min(fsdMaxFuelPerJump, fuel || this.fuelCapacity), fsd || this.standard[2].m, fuel);
} }
/** /**
@@ -240,6 +239,7 @@ export default class Ship {
} }
sg = sgSlot.m; sg = sgSlot.m;
} }
// TODO Not accurate if the ship has modified shield boosters // 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));
} }
@@ -437,15 +437,12 @@ export default class Ship {
m.blueprint = bp; m.blueprint = bp;
this.clearModifications(m); this.clearModifications(m);
// Set any hidden items for the blueprint now // Set any hidden items for the blueprint now
if (m.blueprint.grades[m.blueprint.grade] && m.blueprint.grades[m.blueprint.grade].features) { const features = m.blueprint.grades[m.blueprint.grade].features;
const features = m.blueprint.grades[m.blueprint.grade].features; for (const featureName in features) {
for (const featureName in features) { if (Modifications.modifications[featureName].hidden) {
if (Modifications.modifications[featureName].hidden) { this.setModification(m, featureName, bp.grades[bp.grade].features[featureName][0]);
this.setModification(m, featureName, bp.grades[bp.grade].features[featureName][0]);
}
} }
} }
this.updateModificationsString(); this.updateModificationsString();
} }
@@ -467,17 +464,7 @@ export default class Ship {
if (m.blueprint) { if (m.blueprint) {
m.blueprint.special = special; m.blueprint.special = special;
} }
this.updatePowerGenerated() this.recalculateDps().recalculateHps().recalculateEps();
.updatePowerUsed()
.recalculateMass()
.updateJumpStats()
.recalculateShield()
.recalculateShieldCells()
.recalculateArmour()
.recalculateDps()
.recalculateEps()
.recalculateHps()
.updateMovement();
} }
/** /**
@@ -485,7 +472,10 @@ export default class Ship {
* @param {Object} m The module for which to clear the blueprint * @param {Object} m The module for which to clear the blueprint
*/ */
clearModuleSpecial(m) { clearModuleSpecial(m) {
this.setModuleSpecial(m, null); if (m.blueprint) {
m.blueprint.special = null;
}
this.recalculateDps().recalculateHps().recalculateEps();
} }
/** /**
@@ -494,62 +484,69 @@ export default class Ship {
* @param {Object} name The name of the modification to change * @param {Object} name The name of the modification to change
* @param {Number} value The new value of the modification. The value of the modification is scaled to provide two decimal places of precision in an integer. For example 1.23% is stored as 123 * @param {Number} value The new value of the modification. The value of the modification is scaled to provide two decimal places of precision in an integer. For example 1.23% is stored as 123
* @param {bool} sentfromui True if this update was sent from the UI * @param {bool} sentfromui True if this update was sent from the UI
* @param {bool} isAbsolute True if value is an absolute value and not a
* modification value
*/ */
setModification(m, name, value, sentfromui, isAbsolute) { setModification(m, name, value, sentfromui) {
if (isNaN(value)) { if (isNaN(value)) {
// Value passed is invalid; reset it to 0 // Value passed is invalid; reset it to 0
value = 0; value = 0;
} }
if (isAbsolute) {
m.setPretty(name, value, sentfromui);
} else {
m.setModValue(name, value, sentfromui);
}
// Handle special cases // Handle special cases
if (name === 'pgen') { if (name === 'pgen') {
// Power generation // Power generation
m.setModValue(name, value, sentfromui);
this.updatePowerGenerated(); this.updatePowerGenerated();
} else if (name === 'power') { } else if (name === 'power') {
// Power usage // Power usage
m.setModValue(name, value, sentfromui);
this.updatePowerUsed(); this.updatePowerUsed();
} else if (name === 'mass') { } else if (name === 'mass') {
// Mass // Mass
m.setModValue(name, value, sentfromui);
this.recalculateMass(); this.recalculateMass();
this.updateMovement(); this.updateMovement();
this.updateJumpStats(); this.updateJumpStats();
} else if (name === 'maxfuel') { } else if (name === 'maxfuel') {
m.setModValue(name, value, sentfromui);
this.updateJumpStats(); this.updateJumpStats();
} else if (name === 'optmass') { } else if (name === 'optmass') {
m.setModValue(name, value, sentfromui);
// Could be for any of thrusters, FSD or shield // Could be for any of thrusters, FSD or shield
this.updateMovement(); this.updateMovement();
this.updateJumpStats(); this.updateJumpStats();
this.recalculateShield(); this.recalculateShield();
} else if (name === 'optmul') { } else if (name === 'optmul') {
m.setModValue(name, value, sentfromui);
// Could be for any of thrusters, FSD or shield // Could be for any of thrusters, FSD or shield
this.updateMovement(); this.updateMovement();
this.updateJumpStats(); this.updateJumpStats();
this.recalculateShield(); this.recalculateShield();
} else if (name === 'shieldboost') { } else if (name === 'shieldboost') {
m.setModValue(name, value, sentfromui);
this.recalculateShield(); this.recalculateShield();
} else if (name === 'hullboost' || name === 'hullreinforcement' || name === 'modulereinforcement') { } else if (name === 'hullboost' || name === 'hullreinforcement' || name === 'modulereinforcement') {
m.setModValue(name, value, sentfromui);
this.recalculateArmour(); this.recalculateArmour();
} else if (name === 'shieldreinforcement') { } else if (name === 'shieldreinforcement') {
m.setModValue(name, value, sentfromui);
this.recalculateShieldCells(); this.recalculateShieldCells();
} else if (name === 'burst' || name == 'burstrof' || name === 'clip' || name === 'damage' || name === 'distdraw' || name === 'jitter' || name === 'piercing' || name === 'range' || name === 'reload' || name === 'rof' || name === 'thermload') { } else if (name === 'burst' || name == 'burstrof' || name === 'clip' || name === 'damage' || name === 'distdraw' || name === 'jitter' || name === 'piercing' || name === 'range' || name === 'reload' || name === 'rof' || name === 'thermload') {
m.setModValue(name, value, sentfromui);
this.recalculateDps(); this.recalculateDps();
this.recalculateHps(); this.recalculateHps();
this.recalculateEps(); this.recalculateEps();
} else if (name === 'explres' || name === 'kinres' || name === 'thermres') { } else if (name === 'explres' || name === 'kinres' || name === 'thermres') {
m.setModValue(name, value, sentfromui);
// Could be for shields or armour // Could be for shields or armour
this.recalculateArmour(); this.recalculateArmour();
this.recalculateShield(); this.recalculateShield();
} else if (name === 'engcap') { } else if (name === 'engcap') {
m.setModValue(name, value, sentfromui);
// Might have resulted in a change in boostability // Might have resulted in a change in boostability
this.updateMovement(); this.updateMovement();
} else {
// Generic
m.setModValue(name, value, sentfromui);
} }
} }
@@ -572,7 +569,6 @@ export default class Ship {
// Reset Cumulative stats // Reset Cumulative stats
this.fuelCapacity = 0; this.fuelCapacity = 0;
this.cargoCapacity = 0; this.cargoCapacity = 0;
this.passengerCapacity = 0;
this.ladenMass = 0; this.ladenMass = 0;
this.armour = this.baseArmour; this.armour = this.baseArmour;
this.shield = this.baseShieldStrength; this.shield = this.baseShieldStrength;
@@ -620,7 +616,7 @@ export default class Ship {
standard[i].cat = 0; standard[i].cat = 0;
standard[i].priority = priorities && priorities[i + 1] ? priorities[i + 1] * 1 : 0; standard[i].priority = priorities && priorities[i + 1] ? priorities[i + 1] * 1 : 0;
standard[i].type = 'SYS'; standard[i].type = 'SYS';
standard[i].m = null; // Resetting 'old' module if there was one standard[i].m = null; // Resetting 'old' modul if there was one
standard[i].discountedCost = 0; standard[i].discountedCost = 0;
if (comps) { if (comps) {
let module = ModuleUtils.standard(i, comps.standard[i]); let module = ModuleUtils.standard(i, comps.standard[i]);
@@ -774,7 +770,7 @@ export default class Ship {
// Alter as required due to changes in the (build) code from one version to the next // Alter as required due to changes in the (build) code from one version to the next
this.upgradeInternals(internal, 1 + this.standard.length + this.hardpoints.length, priorities, enabled, modifications, blueprints, version); this.upgradeInternals(internal, 1 + this.standard.length + this.hardpoints.length, priorities, enabled, modifications, blueprints, version);
} }
return this.buildWith( return this.buildWith(
{ {
bulkheads: code.charAt(0) * 1, bulkheads: code.charAt(0) * 1,
@@ -789,6 +785,14 @@ export default class Ship {
); );
}; };
buildFromLoadout(Modules) {
let internal = this.internal,
standard = this.standard,
hps = this.hardpoints,
cl = standard.length,
i, l;
}
/** /**
* Empties all hardpoints and utility slots * Empties all hardpoints and utility slots
* @return {this} The current ship instance for chaining * @return {this} The current ship instance for chaining
@@ -932,14 +936,9 @@ export default class Ship {
let epsChanged = n && n.getEps() || old && old.getEps(); let epsChanged = n && n.getEps() || old && old.getEps();
let hpsChanged = n && n.getHps() || old && old.getHps(); let hpsChanged = n && n.getHps() || old && old.getHps();
let armourChange = (slot === this.bulkheads) || let armourChange = (slot === this.bulkheads) || (n && n.grp === 'hr') || (old && old.grp === 'hr') || (n && n.grp === 'mrp') || (old && old.grp === 'mrp');
(n && n.grp === 'hr') || (old && old.grp === 'hr') ||
(n && n.grp === 'ghrp') || (old && old.grp === 'ghrp') ||
(n && n.grp == 'mahr') || (old && old.grp == 'mahr') ||
(n && n.grp === 'mrp') || (old && old.grp === 'mrp') ||
(n && n.grp === 'gmrp') || (old && old.grp == 'gmrp');
let shieldChange = (n && n.grp === 'bsg') || (old && old.grp === 'bsg') || (n && n.grp === 'psg') || (old && old.grp === 'psg') || (n && n.grp === 'sg') || (old && old.grp === 'sg') || (n && n.grp === 'sb') || (old && old.grp === 'sb') || (old && old.grp === 'gsrp') || (n && n.grp === 'gsrp'); let shieldChange = (n && n.grp === 'bsg') || (old && old.grp === 'bsg') || (n && n.grp === 'psg') || (old && old.grp === 'psg') || (n && n.grp === 'sg') || (old && old.grp === 'sg') || (n && n.grp === 'sb') || (old && old.grp === 'sb');
let shieldCellsChange = (n && n.grp === 'scb') || (old && old.grp === 'scb'); let shieldCellsChange = (n && n.grp === 'scb') || (old && old.grp === 'scb');
@@ -996,6 +995,25 @@ export default class Ship {
return this; return this;
} }
/**
* Calculate diminishing returns value, where values below a given limit are returned
* as-is, and values between the lower and upper limit of the diminishing returns are
* given at half value.
* Commonly used for resistances.
* @param {Number} val The value
* @param {Number} drll The lower limit for diminishing returns
* @param {Number} drul The upper limit for diminishing returns
* @return {this} The ship instance (for chaining operations)
*/
diminishingReturns(val, drll, drul) {
if (val < drll) {
val = drll;
} else if (val < drul) {
val = drul - (drul - val) / 2;
}
return val;
}
/** /**
* Calculate damage per second and related items for weapons * Calculate damage per second and related items for weapons
* @return {this} The ship instance (for chaining operations) * @return {this} The ship instance (for chaining operations)
@@ -1178,45 +1196,42 @@ export default class Ship {
let unladenMass = this.hullMass; let unladenMass = this.hullMass;
let cargoCapacity = 0; let cargoCapacity = 0;
let fuelCapacity = 0; let fuelCapacity = 0;
let passengerCapacity = 0;
unladenMass += this.bulkheads.m.getMass(); unladenMass += this.bulkheads.m.getMass();
let slots = this.standard.concat(this.internal, this.hardpoints); for (let slotNum in this.standard) {
// TODO: create class for slot and also add slot.get const slot = this.standard[slotNum];
// handle unladen mass if (slot.m) {
unladenMass += chain(slots) unladenMass += slot.m.getMass();
.map(slot => slot.m ? slot.m.get('mass') : null) if (slot.m.grp === 'ft') {
.filter() fuelCapacity += slot.m.fuel;
.reduce((sum, mass) => sum + mass) }
.value(); }
}
// handle fuel capacity for (let slotNum in this.internal) {
fuelCapacity += chain(slots) const slot = this.internal[slotNum];
.map(slot => slot.m ? slot.m.get('fuel') : null) if (slot.m) {
.filter() unladenMass += slot.m.getMass();
.reduce((sum, fuel) => sum + fuel) if (slot.m.grp === 'ft') {
.value(); fuelCapacity += slot.m.fuel;
} else if (slot.m.grp === 'cr') {
cargoCapacity += slot.m.cargo;
}
}
}
// handle cargo capacity for (let slotNum in this.hardpoints) {
cargoCapacity += chain(slots) const slot = this.hardpoints[slotNum];
.map(slot => slot.m ? slot.m.get('cargo') : null) if (slot.m) {
.filter() unladenMass += slot.m.getMass();
.reduce((sum, cargo) => sum + cargo) }
.value(); }
// handle passenger capacity
passengerCapacity += chain(slots)
.map(slot => slot.m ? slot.m.get('passengers') : null)
.filter()
.reduce((sum, passengers) => sum + passengers)
.value();
// Update global stats // Update global stats
this.unladenMass = unladenMass; this.unladenMass = unladenMass;
this.cargoCapacity = cargoCapacity; this.cargoCapacity = cargoCapacity;
this.fuelCapacity = fuelCapacity; this.fuelCapacity = fuelCapacity;
this.passengerCapacity = passengerCapacity;
this.ladenMass = unladenMass + fuelCapacity + cargoCapacity; this.ladenMass = unladenMass + fuelCapacity + cargoCapacity;
return this; return this;
@@ -1251,7 +1266,7 @@ export default class Ship {
// Obtain shield metrics with 0 pips to sys (parts affected by SYS aren't used here) // Obtain shield metrics with 0 pips to sys (parts affected by SYS aren't used here)
const metrics = Calc.shieldMetrics(this, 0); const metrics = Calc.shieldMetrics(this, 0);
this.shield = metrics.generator ? metrics.generator + metrics.boosters + metrics.addition : 0; this.shield = metrics.generator ? metrics.generator + metrics.boosters : 0;
this.shieldExplRes = this.shield > 0 ? 1 - metrics.explosive.total : null; this.shieldExplRes = this.shield > 0 ? 1 - metrics.explosive.total : null;
this.shieldKinRes = this.shield > 0 ? 1 - metrics.kinetic.total : null; this.shieldKinRes = this.shield > 0 ? 1 - metrics.kinetic.total : null;
this.shieldThermRes = this.shield > 0 ? 1 - metrics.thermal.total : null; this.shieldThermRes = this.shield > 0 ? 1 - metrics.thermal.total : null;
@@ -1284,15 +1299,45 @@ export default class Ship {
*/ */
recalculateArmour() { recalculateArmour() {
// Armour from bulkheads // Armour from bulkheads
let metrics = Calc.armourMetrics(this); let bulkhead = this.bulkheads.m;
let armour = this.baseArmour + (this.baseArmour * bulkhead.getHullBoost());
let modulearmour = 0;
let moduleprotection = 1;
let hullExplRes = 1 - bulkhead.getExplosiveResistance();
const hullExplResDRStart = hullExplRes * 0.7;
const hullExplResDREnd = hullExplRes * 0;
let hullKinRes = 1 - bulkhead.getKineticResistance();
const hullKinResDRStart = hullKinRes * 0.7;
const hullKinResDREnd = hullKinRes * 0;
let hullThermRes = 1 - bulkhead.getThermalResistance();
const hullThermResDRStart = hullThermRes * 0.7;
const hullThermResDREnd = hullThermRes * 0;
// Armour from HRPs and module armour from MRPs
for (let slot of this.internal) {
if (slot.m && slot.m.grp == 'hr') {
armour += slot.m.getHullReinforcement();
// Hull boost for HRPs is applied against the ship's base armour
armour += this.baseArmour * slot.m.getModValue('hullboost') / 10000;
hullExplRes *= (1 - slot.m.getExplosiveResistance());
hullKinRes *= (1 - slot.m.getKineticResistance());
hullThermRes *= (1 - slot.m.getThermalResistance());
}
if (slot.m && slot.m.grp == 'mrp') {
modulearmour += slot.m.getIntegrity();
moduleprotection = moduleprotection * (1 - slot.m.getProtection());
}
}
moduleprotection = 1 - moduleprotection;
this.armour = armour;
this.modulearmour = modulearmour;
this.moduleprotection = moduleprotection;
this.hullExplRes = 1 - this.diminishingReturns(hullExplRes, hullExplResDREnd, hullExplResDRStart);
this.hullKinRes = 1 - this.diminishingReturns(hullKinRes, hullKinResDREnd, hullKinResDRStart);
this.hullThermRes = 1 - this.diminishingReturns(hullThermRes, hullThermResDREnd, hullThermResDRStart);
this.armour = metrics.total ? metrics.total : 0;
this.modulearmour = metrics.modulearmour;
this.moduleprotection = metrics.moduleprotection;
this.hullExplRes = 1 - metrics.explosive.total;
this.hullKinRes = 1 - metrics.kinetic.total;
this.hullThermRes = 1 - metrics.thermal.total;
this.hullCausRes = 1 - metrics.caustic.total;
return this; return this;
} }
@@ -1304,10 +1349,10 @@ export default class Ship {
let fsd = this.standard[2].m; // Frame Shift Drive; let fsd = this.standard[2].m; // Frame Shift Drive;
let { unladenMass, fuelCapacity } = this; let { unladenMass, fuelCapacity } = this;
this.unladenRange = this.calcUnladenRange(); // Includes fuel weight for jump this.unladenRange = this.calcUnladenRange(); // Includes fuel weight for jump
this.fullTankRange = Calc.jumpRange(unladenMass + fuelCapacity, fsd, this); // Full Tank this.fullTankRange = Calc.jumpRange(unladenMass + fuelCapacity, fsd); // Full Tank
this.ladenRange = this.calcLadenRange(); // Includes full tank and caro this.ladenRange = this.calcLadenRange(); // Includes full tank and caro
this.unladenFastestRange = Calc.totalJumpRange(unladenMass + this.fuelCapacity, fsd, fuelCapacity, this); this.unladenFastestRange = Calc.totalJumpRange(unladenMass + this.fuelCapacity, fsd, fuelCapacity);
this.ladenFastestRange = Calc.totalJumpRange(unladenMass + this.fuelCapacity + this.cargoCapacity, fsd, fuelCapacity, this); this.ladenFastestRange = Calc.totalJumpRange(unladenMass + this.fuelCapacity + this.cargoCapacity, fsd, fuelCapacity);
this.maxJumpCount = Math.ceil(fuelCapacity / fsd.getMaxFuelPerJump()); this.maxJumpCount = Math.ceil(fuelCapacity / fsd.getMaxFuelPerJump());
return this; return this;
} }

View File

@@ -9,9 +9,9 @@ import { canMount } from '../utils/SlotFunctions';
*/ */
export function multiPurpose(ship, shielded, bulkheadIndex) { export function multiPurpose(ship, shielded, bulkheadIndex) {
ship.useStandard('A') ship.useStandard('A')
.use(ship.standard[3], ModuleUtils.standard(3, ship.standard[3].maxClass + 'D')) // D Life Support .use(ship.standard[3], ModuleUtils.standard(3, ship.standard[3].maxClass + 'D')) // D Life Support
.use(ship.standard[5], ModuleUtils.standard(5, ship.standard[5].maxClass + 'D')) // D Sensors .use(ship.standard[5], ModuleUtils.standard(5, ship.standard[5].maxClass + 'D')) // D Sensors
.useBulkhead(bulkheadIndex); .useBulkhead(bulkheadIndex);
if (shielded) { if (shielded) {
ship.internal.some(function(slot) { ship.internal.some(function(slot) {
@@ -31,31 +31,24 @@ export function multiPurpose(ship, shielded, bulkheadIndex) {
* @param {Object} standardOpts [Optional] Standard module optional overrides * @param {Object} standardOpts [Optional] Standard module optional overrides
*/ */
export function trader(ship, shielded, standardOpts) { export function trader(ship, shielded, standardOpts) {
let usedSlots = []; let usedSlots = [],
let bstCount = 2; sg = ship.getAvailableModules().lightestShieldGenerator(ship.hullMass);
let sg = ship.getAvailableModules().lightestShieldGenerator(ship.hullMass);
ship.useStandard('A') // Shield generator if required
.use(ship.standard[3], ModuleUtils.standard(3, ship.standard[3].maxClass + 'D')) // D Life Support if (shielded) {
.use(ship.standard[1], ModuleUtils.standard(1, ship.standard[1].maxClass + 'D')) // D Life Support const shieldOrder = [1, 2, 3, 4, 5, 6, 7, 8];
.use(ship.standard[4], ModuleUtils.standard(4, ship.standard[4].maxClass + 'D')) // D Life Support const shieldInternals = ship.internal.filter(a => usedSlots.indexOf(a) == -1)
.use(ship.standard[5], ModuleUtils.standard(5, ship.standard[5].maxClass + 'D')); // D Sensors .filter(a => (!a.eligible) || a.eligible.sg)
.filter(a => a.maxClass >= sg.class)
const shieldOrder = [1, 2, 3, 4, 5, 6, 7, 8]; .sort((a,b) => shieldOrder.indexOf(a.maxClass) - shieldOrder.indexOf(b.maxClass));
const shieldInternals = ship.internal.filter(a => usedSlots.indexOf(a) == -1) for (let i = 0; i < shieldInternals.length; i++) {
.filter(a => (!a.eligible) || a.eligible.sg) if (canMount(ship, shieldInternals[i], 'sg')) {
.filter(a => a.maxClass >= sg.class) ship.use(shieldInternals[i], sg);
.sort((a, b) => shieldOrder.indexOf(a.maxClass) - shieldOrder.indexOf(b.maxClass)); usedSlots.push(shieldInternals[i]);
shieldInternals.some(function(slot) { break;
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) {
ship.use(slot, shield);
ship.setSlotEnabled(slot, true);
usedSlots.push(slot);
return true;
} }
} }
}); }
// Fill the empty internals with cargo racks // Fill the empty internals with cargo racks
for (let i = ship.internal.length; i--;) { for (let i = ship.internal.length; i--;) {
@@ -69,15 +62,8 @@ export function trader(ship, shielded, standardOpts) {
for (let s of ship.hardpoints) { for (let s of ship.hardpoints) {
ship.use(s, null); ship.use(s, null);
} }
for (let s of ship.hardpoints) {
if (s.maxClass == 0 && bstCount) { // Mount up to 2 boosters ship.useLightestStandard(standardOpts);
ship.use(s, ModuleUtils.hardpoints('04'));
bstCount--;
} else {
ship.use(s, null);
}
}
// ship.useLightestStandard(standardOpts);
} }
/** /**
@@ -103,8 +89,8 @@ export function explorer(ship, planetary) {
// Advanced Discovery Scanner - class 1 or higher // Advanced Discovery Scanner - class 1 or higher
const adsOrder = [1, 2, 3, 4, 5, 6, 7, 8]; const adsOrder = [1, 2, 3, 4, 5, 6, 7, 8];
const adsInternals = ship.internal.filter(a => usedSlots.indexOf(a) == -1) const adsInternals = ship.internal.filter(a => usedSlots.indexOf(a) == -1)
.filter(a => (!a.eligible) || a.eligible.sc) .filter(a => (!a.eligible) || a.eligible.sc)
.sort((a, b) => adsOrder.indexOf(a.maxClass) - adsOrder.indexOf(b.maxClass)); .sort((a,b) => adsOrder.indexOf(a.maxClass) - adsOrder.indexOf(b.maxClass));
for (let i = 0; i < adsInternals.length; i++) { for (let i = 0; i < adsInternals.length; i++) {
if (canMount(ship, adsInternals[i], 'sc')) { if (canMount(ship, adsInternals[i], 'sc')) {
ship.use(adsInternals[i], ModuleUtils.internal('2f')); ship.use(adsInternals[i], ModuleUtils.internal('2f'));
@@ -117,8 +103,8 @@ export function explorer(ship, planetary) {
// Planetary Vehicle Hangar - class 2 or higher // Planetary Vehicle Hangar - class 2 or higher
const pvhOrder = [2, 3, 4, 5, 6, 7, 8, 1]; const pvhOrder = [2, 3, 4, 5, 6, 7, 8, 1];
const pvhInternals = ship.internal.filter(a => usedSlots.indexOf(a) == -1) const pvhInternals = ship.internal.filter(a => usedSlots.indexOf(a) == -1)
.filter(a => (!a.eligible) || a.eligible.pv) .filter(a => (!a.eligible) || a.eligible.pv)
.sort((a, b) => pvhOrder.indexOf(a.maxClass) - pvhOrder.indexOf(b.maxClass)); .sort((a,b) => pvhOrder.indexOf(a.maxClass) - pvhOrder.indexOf(b.maxClass));
for (let i = 0; i < pvhInternals.length; i++) { for (let i = 0; i < pvhInternals.length; i++) {
if (canMount(ship, pvhInternals[i], 'pv')) { if (canMount(ship, pvhInternals[i], 'pv')) {
// Planetary Vehical Hangar only has even classes // Planetary Vehical Hangar only has even classes
@@ -134,9 +120,9 @@ export function explorer(ship, planetary) {
// Shield generator // Shield generator
const shieldOrder = [1, 2, 3, 4, 5, 6, 7, 8]; const shieldOrder = [1, 2, 3, 4, 5, 6, 7, 8];
const shieldInternals = ship.internal.filter(a => usedSlots.indexOf(a) == -1) const shieldInternals = ship.internal.filter(a => usedSlots.indexOf(a) == -1)
.filter(a => (!a.eligible) || a.eligible.sg) .filter(a => (!a.eligible) || a.eligible.sg)
.filter(a => a.maxClass >= sg.class) .filter(a => a.maxClass >= sg.class)
.sort((a, b) => shieldOrder.indexOf(a.maxClass) - shieldOrder.indexOf(b.maxClass)); .sort((a,b) => shieldOrder.indexOf(a.maxClass) - shieldOrder.indexOf(b.maxClass));
for (let i = 0; i < shieldInternals.length; i++) { for (let i = 0; i < shieldInternals.length; i++) {
if (canMount(ship, shieldInternals[i], 'sg')) { if (canMount(ship, shieldInternals[i], 'sg')) {
ship.use(shieldInternals[i], sg); ship.use(shieldInternals[i], sg);
@@ -149,8 +135,8 @@ export function explorer(ship, planetary) {
// Detailed Surface Scanner // Detailed Surface Scanner
const dssOrder = [1, 2, 3, 4, 5, 6, 7, 8]; const dssOrder = [1, 2, 3, 4, 5, 6, 7, 8];
const dssInternals = ship.internal.filter(a => usedSlots.indexOf(a) == -1) const dssInternals = ship.internal.filter(a => usedSlots.indexOf(a) == -1)
.filter(a => (!a.eligible) || a.eligible.sc) .filter(a => (!a.eligible) || a.eligible.sc)
.sort((a, b) => dssOrder.indexOf(a.maxClass) - dssOrder.indexOf(b.maxClass)); .sort((a,b) => dssOrder.indexOf(a.maxClass) - dssOrder.indexOf(b.maxClass));
for (let i = 0; i < dssInternals.length; i++) { for (let i = 0; i < dssInternals.length; i++) {
if (canMount(ship, dssInternals[i], 'sc')) { if (canMount(ship, dssInternals[i], 'sc')) {
ship.use(dssInternals[i], ModuleUtils.internal('2i')); ship.use(dssInternals[i], ModuleUtils.internal('2i'));
@@ -162,8 +148,8 @@ export function explorer(ship, planetary) {
// Fuel scoop - best possible // Fuel scoop - best possible
const fuelScoopOrder = [8, 7, 6, 5, 4, 3, 2, 1]; const fuelScoopOrder = [8, 7, 6, 5, 4, 3, 2, 1];
const fuelScoopInternals = ship.internal.filter(a => usedSlots.indexOf(a) == -1) const fuelScoopInternals = ship.internal.filter(a => usedSlots.indexOf(a) == -1)
.filter(a => (!a.eligible) || a.eligible.fs) .filter(a => (!a.eligible) || a.eligible.fs)
.sort((a, b) => fuelScoopOrder.indexOf(a.maxClass) - fuelScoopOrder.indexOf(b.maxClass)); .sort((a,b) => fuelScoopOrder.indexOf(a.maxClass) - fuelScoopOrder.indexOf(b.maxClass));
for (let i = 0; i < fuelScoopInternals.length; i++) { for (let i = 0; i < fuelScoopInternals.length; i++) {
if (canMount(ship, fuelScoopInternals[i], 'fs')) { if (canMount(ship, fuelScoopInternals[i], 'fs')) {
ship.use(fuelScoopInternals[i], ModuleUtils.findInternal('fs', fuelScoopInternals[i].maxClass, 'A')); ship.use(fuelScoopInternals[i], ModuleUtils.findInternal('fs', fuelScoopInternals[i].maxClass, 'A'));
@@ -176,8 +162,8 @@ export function explorer(ship, planetary) {
// AFMUs - fill as they are 0-weight // AFMUs - fill as they are 0-weight
const afmuOrder = [8, 7, 6, 5, 4, 3, 2, 1]; const afmuOrder = [8, 7, 6, 5, 4, 3, 2, 1];
const afmuInternals = ship.internal.filter(a => usedSlots.indexOf(a) == -1) const afmuInternals = ship.internal.filter(a => usedSlots.indexOf(a) == -1)
.filter(a => (!a.eligible) || a.eligible.pc) .filter(a => (!a.eligible) || a.eligible.pc)
.sort((a, b) => afmuOrder.indexOf(a.maxClass) - afmuOrder.indexOf(b.maxClass)); .sort((a,b) => afmuOrder.indexOf(a.maxClass) - afmuOrder.indexOf(b.maxClass));
for (let i = 0; i < afmuInternals.length; i++) { for (let i = 0; i < afmuInternals.length; i++) {
if (canMount(ship, afmuInternals[i], 'am')) { if (canMount(ship, afmuInternals[i], 'am')) {
ship.use(afmuInternals[i], ModuleUtils.findInternal('am', afmuInternals[i].maxClass, 'A')); ship.use(afmuInternals[i], ModuleUtils.findInternal('am', afmuInternals[i].maxClass, 'A'));
@@ -214,7 +200,6 @@ export function explorer(ship, planetary) {
* @param {Boolean} shielded True if shield generator should be included * @param {Boolean} shielded True if shield generator should be included
*/ */
export function miner(ship, shielded) { export function miner(ship, shielded) {
shielded = true;
let standardOpts = { ppRating: 'A' }, let standardOpts = { ppRating: 'A' },
miningLaserCount = 2, miningLaserCount = 2,
usedSlots = [], usedSlots = [],
@@ -226,8 +211,8 @@ export function miner(ship, shielded) {
// Largest possible refinery // Largest possible refinery
const refineryOrder = [4, 5, 6, 7, 8, 3, 2, 1]; const refineryOrder = [4, 5, 6, 7, 8, 3, 2, 1];
const refineryInternals = ship.internal.filter(a => usedSlots.indexOf(a) == -1) const refineryInternals = ship.internal.filter(a => usedSlots.indexOf(a) == -1)
.filter(a => (!a.eligible) || a.eligible.rf) .filter(a => (!a.eligible) || a.eligible.rf)
.sort((a, b) => refineryOrder.indexOf(a.maxClass) - refineryOrder.indexOf(b.maxClass)); .sort((a,b) => refineryOrder.indexOf(a.maxClass) - refineryOrder.indexOf(b.maxClass));
for (let i = 0; i < refineryInternals.length; i++) { for (let i = 0; i < refineryInternals.length; i++) {
if (canMount(ship, refineryInternals[i], 'rf')) { if (canMount(ship, refineryInternals[i], 'rf')) {
ship.use(refineryInternals[i], ModuleUtils.findInternal('rf', Math.min(refineryInternals[i].maxClass, 4), 'A')); ship.use(refineryInternals[i], ModuleUtils.findInternal('rf', Math.min(refineryInternals[i].maxClass, 4), 'A'));
@@ -239,8 +224,8 @@ export function miner(ship, shielded) {
// Prospector limpet controller - 3A if possible // Prospector limpet controller - 3A if possible
const prospectorOrder = [3, 4, 5, 6, 7, 8, 2, 1]; const prospectorOrder = [3, 4, 5, 6, 7, 8, 2, 1];
const prospectorInternals = ship.internal.filter(a => usedSlots.indexOf(a) == -1) const prospectorInternals = ship.internal.filter(a => usedSlots.indexOf(a) == -1)
.filter(a => (!a.eligible) || a.eligible.pc) .filter(a => (!a.eligible) || a.eligible.pc)
.sort((a, b) => prospectorOrder.indexOf(a.maxClass) - prospectorOrder.indexOf(b.maxClass)); .sort((a,b) => prospectorOrder.indexOf(a.maxClass) - prospectorOrder.indexOf(b.maxClass));
for (let i = 0; i < prospectorInternals.length; i++) { for (let i = 0; i < prospectorInternals.length; i++) {
if (canMount(ship, prospectorInternals[i], 'pc')) { if (canMount(ship, prospectorInternals[i], 'pc')) {
// Prospector only has odd classes // Prospector only has odd classes
@@ -255,9 +240,9 @@ export function miner(ship, shielded) {
if (shielded) { if (shielded) {
const shieldOrder = [1, 2, 3, 4, 5, 6, 7, 8]; const shieldOrder = [1, 2, 3, 4, 5, 6, 7, 8];
const shieldInternals = ship.internal.filter(a => usedSlots.indexOf(a) == -1) const shieldInternals = ship.internal.filter(a => usedSlots.indexOf(a) == -1)
.filter(a => (!a.eligible) || a.eligible.sg) .filter(a => (!a.eligible) || a.eligible.sg)
.filter(a => a.maxClass >= sg.class) .filter(a => a.maxClass >= sg.class)
.sort((a, b) => shieldOrder.indexOf(a.maxClass) - shieldOrder.indexOf(b.maxClass)); .sort((a,b) => shieldOrder.indexOf(a.maxClass) - shieldOrder.indexOf(b.maxClass));
for (let i = 0; i < shieldInternals.length; i++) { for (let i = 0; i < shieldInternals.length; i++) {
if (canMount(ship, shieldInternals[i], 'sg')) { if (canMount(ship, shieldInternals[i], 'sg')) {
ship.use(shieldInternals[i], sg); ship.use(shieldInternals[i], sg);
@@ -269,7 +254,7 @@ export function miner(ship, shielded) {
// Dual mining lasers of highest possible class; remove anything else // Dual mining lasers of highest possible class; remove anything else
const miningLaserOrder = [2, 3, 4, 1, 0]; const miningLaserOrder = [2, 3, 4, 1, 0];
const miningLaserHardpoints = ship.hardpoints.concat().sort(function(a, b) { const miningLaserHardpoints = ship.hardpoints.concat().sort(function(a,b) {
return miningLaserOrder.indexOf(a.maxClass) - miningLaserOrder.indexOf(b.maxClass); return miningLaserOrder.indexOf(a.maxClass) - miningLaserOrder.indexOf(b.maxClass);
}); });
for (let s of miningLaserHardpoints) { for (let s of miningLaserHardpoints) {
@@ -283,23 +268,23 @@ export function miner(ship, shielded) {
// Number of collector limpets required to be active is a function of the size of the ship and the power of the lasers // Number of collector limpets required to be active is a function of the size of the ship and the power of the lasers
const miningLaserDps = ship.hardpoints.filter(h => h.m != null) const miningLaserDps = ship.hardpoints.filter(h => h.m != null)
.reduce(function(a, b) { .reduce(function(a, b) {
return a + b.m.getDps(); return a + b.m.getDps();
}, 0); }, 0);
// Find out how many internal slots we have, and their potential cargo size // Find out how many internal slots we have, and their potential cargo size
const potentialCargo = ship.internal.filter(a => usedSlots.indexOf(a) == -1) const potentialCargo = ship.internal.filter(a => usedSlots.indexOf(a) == -1)
.filter(a => (!a.eligible) || a.eligible.cr) .filter(a => (!a.eligible) || a.eligible.cr)
.map(b => Math.pow(2, b.maxClass)); .map(b => Math.pow(2, b.maxClass));
// One collector for each 1.25 DPS, multiply by 1.25 for medium ships and 1.5 for large ships as they have further to travel // One collector for each 1.25 DPS, multiply by 1.25 for medium ships and 1.5 for large ships as they have further to travel
// 0 if we only have 1 cargo slot, otherwise minium of 1 and maximum of 6 (excluding size modifier) // 0 if we only have 1 cargo slot, otherwise minium of 1 and maximum of 6 (excluding size modifier)
const sizeModifier = ship.class == 2 ? 1.2 : ship.class == 3 ? 1.5 : 1; const sizeModifier = ship.class == 2 ? 1.2 : ship.class == 3 ? 1.5 : 1;
let collectorLimpetsRequired = potentialCargo.length == 1 ? 0 : Math.ceil(sizeModifier * Math.min(6, Math.floor(miningLaserDps / 1.25))); let collectorLimpetsRequired = potentialCargo.length == 1 ? 0 : Math.ceil(sizeModifier * Math.min(6, Math.floor(miningLaserDps / 1.25)));
if (collectorLimpetsRequired > 0) { if (collectorLimpetsRequired > 0) {
const collectorOrder = [1, 2, 3, 4, 5, 6, 7, 8]; const collectorOrder = [1, 2, 3, 4, 5, 6, 7, 8];
const collectorInternals = ship.internal.filter(a => usedSlots.indexOf(a) == -1) const collectorInternals = ship.internal.filter(a => usedSlots.indexOf(a) == -1)
.filter(a => (!a.eligible) || a.eligible.cc) .filter(a => (!a.eligible) || a.eligible.cc)
.sort((a, b) => collectorOrder.indexOf(a.maxClass) - collectorOrder.indexOf(b.maxClass)); .sort((a,b) => collectorOrder.indexOf(a.maxClass) - collectorOrder.indexOf(b.maxClass));
// Always keep at least 2 slots free for cargo racks (1 for shielded) // Always keep at least 2 slots free for cargo racks (1 for shielded)
for (let i = 0; i < collectorInternals.length - (shielded ? 1 : 2) && collectorLimpetsRequired > 0; i++) { for (let i = 0; i < collectorInternals.length - (shielded ? 1 : 2) && collectorLimpetsRequired > 0; i++) {
if (canMount(ship, collectorInternals[i], 'cc')) { if (canMount(ship, collectorInternals[i], 'cc')) {
@@ -314,9 +299,9 @@ export function miner(ship, shielded) {
// Power distributor to power the mining lasers indefinitely // Power distributor to power the mining lasers indefinitely
const wepRateRequired = ship.hardpoints.filter(h => h.m != null) const wepRateRequired = ship.hardpoints.filter(h => h.m != null)
.reduce(function(a, b) { .reduce(function(a, b) {
return a + b.m.getEps(); return a + b.m.getEps();
}, 0); }, 0);
standardOpts.pd = ship.getAvailableModules().matchingPowerDist({ weprate: wepRateRequired }).id; standardOpts.pd = ship.getAvailableModules().matchingPowerDist({ weprate: wepRateRequired }).id;
// Fill the empty internals with cargo racks // Fill the empty internals with cargo racks
@@ -346,9 +331,9 @@ export function racer(ship) {
// Shield generator // Shield generator
const shieldOrder = [1, 2, 3, 4, 5, 6, 7, 8]; const shieldOrder = [1, 2, 3, 4, 5, 6, 7, 8];
const shieldInternals = ship.internal.filter(a => usedSlots.indexOf(a) == -1) const shieldInternals = ship.internal.filter(a => usedSlots.indexOf(a) == -1)
.filter(a => (!a.eligible) || a.eligible.sg) .filter(a => (!a.eligible) || a.eligible.sg)
.filter(a => a.maxClass >= sg.class) .filter(a => a.maxClass >= sg.class)
.sort((a, b) => shieldOrder.indexOf(a.maxClass) - shieldOrder.indexOf(b.maxClass)); .sort((a,b) => shieldOrder.indexOf(a.maxClass) - shieldOrder.indexOf(b.maxClass));
for (let i = 0; i < shieldInternals.length; i++) { for (let i = 0; i < shieldInternals.length; i++) {
if (canMount(ship, shieldInternals[i], 'sg')) { if (canMount(ship, shieldInternals[i], 'sg')) {
ship.use(shieldInternals[i], sg); ship.use(shieldInternals[i], sg);
@@ -413,3 +398,4 @@ export function racer(ship) {
// ship.standard[5].m.blueprint.grade = 5; // ship.standard[5].m.blueprint.grade = 5;
// setBest(ship, ship.standard[5].m); // setBest(ship, ship.standard[5].m);
} }

View File

@@ -1,82 +0,0 @@
export const SI_PREFIXES = {
'Y': 1e+24, // Yotta
'Z': 1e+21, // Zetta
'E': 1e+18, // Peta
'P': 1e+15, // Peta
'T': 1e+12, // Tera
'G': 1e+9, // Giga
'M': 1e+6, // Mega
'k': 1e+3, // Kilo
'h': 1e+2, // Hekto
'da': 1e+1, // Deka
'': 1,
'd': 1e-1, // Dezi
'c': 1e-2, // Zenti
'm': 1e-3, // Milli
'μ': 1e-6, // mikro not supported due to charset
'n': 10e-9, // Nano
'p': 1e-12, // Nano
'f': 1e-15, // Femto
'a': 1e-18, // Atto
'z': 1e-21, // Zepto
'y': 1e-24 // Yokto
};
export const STATS_FORMATTING = {
'ammo': { 'format': 'int', },
'boot': { 'format': 'int', 'unit': 'secs' },
'brokenregen': { 'format': 'round1', 'unit': 'ps' },
'burst': { 'format': 'int' },
'burstrof': { 'format': 'round1', 'unit': 'ps' },
'causres': { 'format': 'pct' },
'clip': { 'format': 'int' },
'damage': { 'format': 'round' },
'dps': { 'format': 'round', 'units': 'ps', 'synthetic': 'getDps' },
'dpe': { 'format': 'round', 'units': 'ps', 'synthetic': 'getDpe' },
'distdraw': { 'format': 'round', 'unit': 'MW' },
'duration': { 'format': 'round1', 'unit': 's' },
'eff': { 'format': 'round2' },
'engcap': { 'format': 'round1', 'unit': 'MJ' },
'engrate': { 'format': 'round1', 'unit': 'MW' },
'eps': { 'format': 'round', 'units': 'ps', 'synthetic': 'getEps' },
'explres': { 'format': 'pct' },
'facinglimit': { 'format': 'round1', 'unit': 'ang' },
'falloff': { 'format': 'round', 'unit': 'km', 'storedUnit': 'm' },
'fallofffromrange': { 'format': 'round', 'unit': 'km', 'storedUnit': 'm', 'synthetic': 'getFalloff' },
'hps': { 'format': 'round', 'units': 'ps', 'synthetic': 'getHps' },
'hullboost': { 'format': 'pct1', 'change': 'additive' },
'hullreinforcement': { 'format': 'int' },
'integrity': { 'format': 'round1' },
'jitter': { 'format': 'round', 'unit': 'ang' },
'kinres': { 'format': 'pct' },
'mass': { 'format': 'round1', 'unit': 'T' },
'maxfuel': { 'format': 'round1', 'unit': 'T' },
'optmass': { 'format': 'int', 'unit': 'T' },
'optmul': { 'format': 'pct', 'change': 'additive' },
'pgen': { 'format': 'round1', 'unit': 'MW' },
'piercing': { 'format': 'int' },
'power': { 'format': 'round', 'unit': 'MW' },
'protection': { 'format': 'pct' },
'range': { 'format': 'f2', 'unit': 'km', 'storedUnit': 'm' },
'ranget': { 'format': 'f1', 'unit': 's' },
'regen': { 'format': 'round1', 'unit': 'ps' },
'reload': { 'format': 'int', 'unit': 's' },
'rof': { 'format': 'round1', 'unit': 'ps' },
'angle': { 'format': 'round1', 'unit': 'ang' },
'scanrate': { 'format': 'int' },
'scantime': { 'format': 'round1', 'unit': 's' },
'sdps': { 'format': 'round1', 'units': 'ps', 'synthetic': 'getSDps' },
'shield': { 'format': 'int', 'unit': 'MJ' },
'shieldaddition': { 'format': 'round1', 'unit': 'MJ' },
'shieldboost': { 'format': 'pct1', 'change': 'additive' },
'shieldreinforcement': { 'format': 'round1', 'unit': 'MJ' },
'shotspeed': { 'format': 'int', 'unit': 'm/s' },
'spinup': { 'format': 'round1', 'unit': 's' },
'syscap': { 'format': 'round1', 'unit': 'MJ' },
'sysrate': { 'format': 'round1', 'unit': 'MW' },
'thermload': { 'format': 'round1' },
'thermres': { 'format': 'pct' },
'wepcap': { 'format': 'round1', 'unit': 'MJ' },
'weprate': { 'format': 'round1', 'unit': 'MW' },
'jumpboost': { 'format': 'round1', 'unit': 'LY' }
};

View File

@@ -5,7 +5,6 @@ const LS_KEY_BUILDS = 'builds';
const LS_KEY_COMPARISONS = 'comparisons'; const LS_KEY_COMPARISONS = 'comparisons';
const LS_KEY_LANG = 'NG_TRANSLATE_LANG_KEY'; const LS_KEY_LANG = 'NG_TRANSLATE_LANG_KEY';
const LS_KEY_COST_TAB = 'costTab'; const LS_KEY_COST_TAB = 'costTab';
const LS_KEY_CMDR_NAME = 'cmdrName';
const LS_KEY_OUTFITTING_TAB = 'outfittingTab'; const LS_KEY_OUTFITTING_TAB = 'outfittingTab';
const LS_KEY_INSURANCE = 'insurance'; const LS_KEY_INSURANCE = 'insurance';
const LS_KEY_SHIP_DISCOUNT = 'shipDiscount'; const LS_KEY_SHIP_DISCOUNT = 'shipDiscount';
@@ -14,8 +13,6 @@ const LS_KEY_STATE = 'state';
const LS_KEY_SIZE_RATIO = 'sizeRatio'; const LS_KEY_SIZE_RATIO = 'sizeRatio';
const LS_KEY_TOOLTIPS = 'tooltips'; const LS_KEY_TOOLTIPS = 'tooltips';
const LS_KEY_MODULE_RESISTANCES = 'moduleResistances'; const LS_KEY_MODULE_RESISTANCES = 'moduleResistances';
const LS_KEY_ROLLS = 'matsPerGrade';
const LS_KEY_ORBIS = 'orbis';
let LS; let LS;
@@ -87,8 +84,6 @@ export class Persist extends EventEmitter {
} }
let moduleResistances = _get(LS_KEY_MODULE_RESISTANCES); let moduleResistances = _get(LS_KEY_MODULE_RESISTANCES);
let matsPerGrade = _get(LS_KEY_ROLLS);
let cmdrName = _get(LS_KEY_CMDR_NAME);
let tips = _get(LS_KEY_TOOLTIPS); let tips = _get(LS_KEY_TOOLTIPS);
let insurance = _getString(LS_KEY_INSURANCE); let insurance = _getString(LS_KEY_INSURANCE);
let shipDiscount = _get(LS_KEY_SHIP_DISCOUNT); let shipDiscount = _get(LS_KEY_SHIP_DISCOUNT);
@@ -96,7 +91,6 @@ export class Persist extends EventEmitter {
let buildJson = _get(LS_KEY_BUILDS); let buildJson = _get(LS_KEY_BUILDS);
let comparisonJson = _get(LS_KEY_COMPARISONS); let comparisonJson = _get(LS_KEY_COMPARISONS);
this.orbisCreds = _get(LS_KEY_ORBIS) || { email: '', password: '' };
this.onStorageChange = this.onStorageChange.bind(this); this.onStorageChange = this.onStorageChange.bind(this);
this.langCode = _getString(LS_KEY_LANG) || 'en'; this.langCode = _getString(LS_KEY_LANG) || 'en';
this.insurance = insurance && Insurance[insurance.toLowerCase()] !== undefined ? insurance : 'standard'; this.insurance = insurance && Insurance[insurance.toLowerCase()] !== undefined ? insurance : 'standard';
@@ -108,14 +102,6 @@ export class Persist extends EventEmitter {
this.outfittingTab = _getString(LS_KEY_OUTFITTING_TAB); this.outfittingTab = _getString(LS_KEY_OUTFITTING_TAB);
this.state = _get(LS_KEY_STATE); this.state = _get(LS_KEY_STATE);
this.sizeRatio = _get(LS_KEY_SIZE_RATIO) || 1; this.sizeRatio = _get(LS_KEY_SIZE_RATIO) || 1;
this.matsPerGrade = matsPerGrade || {
1: 2,
2: 2,
3: 4,
4: 4,
5: 10
};
this.cmdrName = cmdrName || { selected: '', cmdrs: [] };
this.tooltipsEnabled = tips === null ? true : tips; this.tooltipsEnabled = tips === null ? true : tips;
this.moduleResistancesEnabled = moduleResistances === null ? true : moduleResistances; this.moduleResistancesEnabled = moduleResistances === null ? true : moduleResistances;
@@ -166,14 +152,6 @@ export class Persist extends EventEmitter {
this.moduleResistancesEnabled = !!newValue && newValue.toLowerCase() == 'true'; this.moduleResistancesEnabled = !!newValue && newValue.toLowerCase() == 'true';
this.emit('moduleresistances', this.moduleResistancesEnabled); this.emit('moduleresistances', this.moduleResistancesEnabled);
break; break;
case LS_KEY_ROLLS:
this.matsPerGrade = JSON.parse(newValue);
this.emit('matsPerGrade', this.matsPerGrade);
break;
case LS_KEY_ORBIS:
this.orbisCreds = JSON.parse(newValue);
this.emit('orbis', this.orbisCreds);
break;
} }
} catch (e) { } catch (e) {
// On JSON.Parse Error - don't sync or do anything // On JSON.Parse Error - don't sync or do anything
@@ -199,24 +177,6 @@ export class Persist extends EventEmitter {
this.emit('language', langCode); this.emit('language', langCode);
} }
/**
* Get the current orbis.zone credentials
* @return {String} language code
*/
getOrbisCreds() {
return this.orbisCreds;
};
/**
* Update and save the orbis.zone credentials
* @param {Object} creds object with username and password properties.
*/
setOrbisCreds(creds) {
this.langCode = creds;
_put(LS_KEY_ORBIS, creds);
this.emit('orbis', creds);
}
/** /**
* Show tooltips setting * Show tooltips setting
* @param {boolean} show Optional - update setting * @param {boolean} show Optional - update setting
@@ -497,31 +457,6 @@ export class Persist extends EventEmitter {
return this.moduleDiscount; return this.moduleDiscount;
} }
/**
* Get the saved ship discount
* @param {Object} matsPerGrade # of rolls per grade
*/
setRolls(matsPerGrade) {
this.matsPerGrade = matsPerGrade;
_put(LS_KEY_ROLLS, this.matsPerGrade);
this.emit('matsPerGrade');
}
/**
* Get the saved Mats per grade
* @return {Object} # of rolls per grade
*/
getRolls() {
return this.matsPerGrade;
}
/**
* Get the saved Mats per grade
* @return {Object} # of rolls per grade
*/
getCmdr() {
return this.cmdrName;
}
/** /**
* Persist selected cost tab * Persist selected cost tab
* @param {number} tabName Cost tab name * @param {number} tabName Cost tab name
@@ -531,16 +466,6 @@ export class Persist extends EventEmitter {
_put(LS_KEY_COST_TAB, tabName); _put(LS_KEY_COST_TAB, tabName);
} }
/**
* Persist cmdr name
* @param {Object} cmdrName Commander name for EDEngineer
*/
setCmdr(cmdrName) {
this.cmdrName = cmdrName;
_put(LS_KEY_CMDR_NAME, cmdrName);
this.emit('cmdr');
}
/** /**
* Get the saved discount * Get the saved discount
* @return {number} val Discount value/amount * @return {number} val Discount value/amount

View File

@@ -1,417 +1,371 @@
import React from 'react'; import React from 'react';
import { Modifications } from 'coriolis-data/dist'; import { Modifications } from 'coriolis-data/dist';
/** /**
* Generate a tooltip with details of a blueprint's specials * Generate a tooltip with details of a blueprint's effects
* @param {Object} translate The translate object * @param {Object} translate The translate object
* @param {Object} blueprint The blueprint at the required grade * @param {Object} blueprint The blueprint at the required grade
* @param {string} grp The group of the module * @param {Array} engineers The engineers supplying this blueprint
* @param {Object} m The module to compare with * @param {string} grp The group of the module
* @param {string} specialName The name of the special * @param {Object} m The module to compare with
* @returns {Object} The react components * @returns {Object} The react components
*/ */
export function specialToolTip(translate, blueprint, grp, m, specialName) { export function blueprintTooltip(translate, blueprint, engineers, grp, m) {
const effects = []; const effects = [];
if (!blueprint || !blueprint.features) { for (const feature in blueprint.features) {
return undefined; const featureIsBeneficial = isBeneficial(feature, blueprint.features[feature]);
} const featureDef = Modifications.modifications[feature];
if (m) { if (!featureDef.hidden) {
// We also add in any benefits from specials that aren't covered above let symbol = '';
if (m.blueprint) { if (feature === 'jitter') {
for (const feature in Modifications.modifierActions[specialName]) { symbol = '°';
// if (!blueprint.features[feature] && !m.mods.feature) { } else if (featureDef.type === 'percentage') {
const featureDef = Modifications.modifications[feature]; symbol = '%';
if (featureDef && !featureDef.hidden) { }
let symbol = ''; let lowerBound = blueprint.features[feature][0];
if (feature === 'jitter') { let upperBound = blueprint.features[feature][1];
symbol = '°'; if (featureDef.type === 'percentage') {
} else if (featureDef.type === 'percentage') { lowerBound = Math.round(lowerBound * 1000) / 10;
symbol = '%'; upperBound = Math.round(upperBound * 1000) / 10;
} }
let current = m.getModValue(feature) - m.getModValue(feature, true); const lowerIsBeneficial = isValueBeneficial(feature, lowerBound);
if (featureDef.type === 'percentage') { const upperIsBeneficial = isValueBeneficial(feature, upperBound);
current = Math.round(current / 10) / 10; if (m) {
} else if (featureDef.type === 'numeric') { // We have a module - add in the current value
current /= 100; let current = m.getModValue(feature);
} if (featureDef.type === 'percentage' || featureDef.name === 'burst' || featureDef.name === 'burstrof') {
const currentIsBeneficial = isValueBeneficial(feature, current); current = Math.round(current / 10) / 10;
} else if (featureDef.type === 'numeric') {
effects.push( current /= 100;
<tr key={feature + '_specialTT'}> }
<td style={{ textAlign: 'left' }}>{translate(feature, grp)}</td> const currentIsBeneficial = isValueBeneficial(feature, current);
<td>&nbsp;</td> effects.push(
<td className={current === 0 ? '' : currentIsBeneficial ? 'secondary' : 'warning'} <tr key={feature}>
style={{ textAlign: 'right' }}>{current}{symbol}</td> <td style={{ textAlign: 'left' }}>{translate(feature, grp)}</td>
<td>&nbsp;</td> <td className={lowerBound === 0 ? '' : lowerIsBeneficial ? 'secondary' : 'warning'} style={{ textAlign: 'right' }}>{lowerBound}{symbol}</td>
</tr> <td className={current === 0 ? '' : currentIsBeneficial ? 'secondary' : 'warning'} style={{ textAlign: 'right' }}>{current}{symbol}</td>
); <td className={upperBound === 0 ? '' : upperIsBeneficial ? 'secondary' : 'warning'} style={{ textAlign: 'right' }}>{upperBound}{symbol}</td>
} </tr>
} );
} } else {
} // We do not have a module, no value
effects.push(
return ( <tr key={feature}>
<div> <td style={{ textAlign: 'left' }}>{translate(feature, grp)}</td>
<table width='100%'> <td className={lowerBound === 0 ? '' : lowerIsBeneficial ? 'secondary' : 'warning'} style={{ textAlign: 'right' }}>{lowerBound}{symbol}</td>
<tbody> <td className={upperBound === 0 ? '' : upperIsBeneficial ? 'secondary' : 'warning'} style={{ textAlign: 'right' }}>{upperBound}{symbol}</td>
{effects} </tr>
</tbody> );
</table> }
</div> }
); }
} if (m) {
// Because we have a module add in any benefits that aren't part of the primary blueprint
/** for (const feature in m.mods) {
* Generate a tooltip with details of a blueprint's effects if (!blueprint.features[feature]) {
* @param {Object} translate The translate object const featureDef = Modifications.modifications[feature];
* @param {Object} blueprint The blueprint at the required grade if (featureDef && !featureDef.hidden) {
* @param {Array} engineers The engineers supplying this blueprint let symbol = '';
* @param {string} grp The group of the module if (feature === 'jitter') {
* @param {Object} m The module to compare with symbol = '°';
* @returns {Object} The react components } else if (featureDef.type === 'percentage') {
*/ symbol = '%';
export function blueprintTooltip(translate, blueprint, engineers, grp, m) { }
const effects = []; let current = m.getModValue(feature);
if (!blueprint || !blueprint.features) { if (featureDef.type === 'percentage' || featureDef.name === 'burst' || featureDef.name === 'burstrof') {
return undefined; current = Math.round(current / 10) / 10;
} } else if (featureDef.type === 'numeric') {
for (const feature in blueprint.features) { current /= 100;
const featureIsBeneficial = isBeneficial(feature, blueprint.features[feature]); }
const featureDef = Modifications.modifications[feature]; const currentIsBeneficial = isValueBeneficial(feature, current);
if (!featureDef.hidden) { effects.push(
let symbol = ''; <tr key={feature}>
if (feature === 'jitter') { <td style={{ textAlign: 'left' }}>{translate(feature, grp)}</td>
symbol = '°'; <td>&nbsp;</td>
} else if (featureDef.type === 'percentage') { <td className={current === 0 ? '' : currentIsBeneficial ? 'secondary' : 'warning'} style={{ textAlign: 'right' }}>{current}{symbol}</td>
symbol = '%'; <td>&nbsp;</td>
} </tr>
let lowerBound = blueprint.features[feature][0]; );
let upperBound = blueprint.features[feature][1]; }
if (featureDef.type === 'percentage') { }
lowerBound = Math.round(lowerBound * 1000) / 10; }
upperBound = Math.round(upperBound * 1000) / 10;
} // We also add in any benefits from specials that aren't covered above
const lowerIsBeneficial = isValueBeneficial(feature, lowerBound); if (m.blueprint && m.blueprint.special) {
const upperIsBeneficial = isValueBeneficial(feature, upperBound); for (const feature in Modifications.modifierActions[m.blueprint.special.edname]) {
if (m) { if (!blueprint.features[feature] && !m.mods.feature) {
// We have a module - add in the current value const featureDef = Modifications.modifications[feature];
let current = m.getModValue(feature); if (featureDef && !featureDef.hidden) {
if (featureDef.type === 'percentage' || featureDef.name === 'burst' || featureDef.name === 'burstrof') { let symbol = '';
current = Math.round(current / 10) / 10; if (feature === 'jitter') {
} else if (featureDef.type === 'numeric') { symbol = '°';
current /= 100; } else if (featureDef.type === 'percentage') {
} symbol = '%';
const currentIsBeneficial = isValueBeneficial(feature, current); }
effects.push( let current = m.getModValue(feature);
<tr key={feature}> if (featureDef.type === 'percentage' || featureDef.name === 'burst' || featureDef.name === 'burstrof') {
<td style={{ textAlign: 'left' }}>{translate(feature, grp)}</td> current = Math.round(current / 10) / 10;
<td className={lowerBound === 0 ? '' : lowerIsBeneficial ? 'secondary' : 'warning'} style={{ textAlign: 'right' }}>{lowerBound}{symbol}</td> } else if (featureDef.type === 'numeric') {
<td className={current === 0 ? '' : currentIsBeneficial ? 'secondary' : 'warning'} style={{ textAlign: 'right' }}>{current}{symbol}</td> current /= 100;
<td className={upperBound === 0 ? '' : upperIsBeneficial ? 'secondary' : 'warning'} style={{ textAlign: 'right' }}>{upperBound}{symbol}</td> }
</tr> const currentIsBeneficial = isValueBeneficial(feature, current);
); effects.push(
} else { <tr key={feature}>
// We do not have a module, no value <td style={{ textAlign: 'left' }}>{translate(feature, grp)}</td>
effects.push( <td>&nbsp;</td>
<tr key={feature}> <td className={current === 0 ? '' : currentIsBeneficial ? 'secondary' : 'warning'} style={{ textAlign: 'right' }}>{current}{symbol}</td>
<td style={{ textAlign: 'left' }}>{translate(feature, grp)}</td> <td>&nbsp;</td>
<td className={lowerBound === 0 ? '' : lowerIsBeneficial ? 'secondary' : 'warning'} style={{ textAlign: 'right' }}>{lowerBound}{symbol}</td> </tr>
<td className={upperBound === 0 ? '' : upperIsBeneficial ? 'secondary' : 'warning'} style={{ textAlign: 'right' }}>{upperBound}{symbol}</td> );
</tr> }
); }
} }
} }
} }
if (m) {
// Because we have a module add in any benefits that aren't part of the primary blueprint let components;
for (const feature in m.mods) { if (!m) {
if (!blueprint.features[feature]) { components = [];
const featureDef = Modifications.modifications[feature]; for (const component in blueprint.components) {
if (featureDef && !featureDef.hidden) { components.push(
let symbol = ''; <tr key={component}>
if (feature === 'jitter') { <td style={{ textAlign: 'left' }}>{translate(component)}</td>
symbol = '°'; <td style={{ textAlign: 'right' }}>{blueprint.components[component]}</td>
} else if (featureDef.type === 'percentage') { </tr>
symbol = '%'; );
} }
let current = m.getModValue(feature); }
if (featureDef.type === 'percentage' || featureDef.name === 'burst' || featureDef.name === 'burstrof') {
current = Math.round(current / 10) / 10; let engineersList;
} else if (featureDef.type === 'numeric') { if (engineers) {
current /= 100; engineersList = [];
} for (const engineer of engineers) {
const currentIsBeneficial = isValueBeneficial(feature, current); engineersList.push(
effects.push( <tr key={engineer}>
<tr key={feature}> <td style={{ textAlign: 'left' }}>{engineer}</td>
<td style={{ textAlign: 'left' }}>{translate(feature, grp)}</td> </tr>
<td>&nbsp;</td> );
<td className={current === 0 ? '' : currentIsBeneficial ? 'secondary' : 'warning'} style={{ textAlign: 'right' }}>{current}{symbol}</td> }
<td>&nbsp;</td> }
</tr>
); return (
} <div>
} <table width='100%'>
} <thead>
<tr>
// We also add in any benefits from specials that aren't covered above <td>{translate('feature')}</td>
if (m.blueprint && m.blueprint.special) { <td>{translate('worst')}</td>
for (const feature in Modifications.modifierActions[m.blueprint.special.edname]) { {m ? <td>{translate('current')}</td> : null }
if (!blueprint.features[feature] && !m.mods.feature) { <td>{translate('best')}</td>
const featureDef = Modifications.modifications[feature]; </tr>
if (featureDef && !featureDef.hidden) { </thead>
let symbol = ''; <tbody>
if (feature === 'jitter') { {effects}
symbol = '°'; </tbody>
} else if (featureDef.type === 'percentage') { </table>
symbol = '%'; { components ? <table width='100%'>
} <thead>
let current = m.getModValue(feature); <tr>
if (featureDef.type === 'percentage' || featureDef.name === 'burst' || featureDef.name === 'burstrof') { <td>{translate('component')}</td>
current = Math.round(current / 10) / 10; <td>{translate('amount')}</td>
} else if (featureDef.type === 'numeric') { </tr>
current /= 100; </thead>
} <tbody>
const currentIsBeneficial = isValueBeneficial(feature, current); {components}
effects.push( </tbody>
<tr key={feature}> </table> : null }
<td style={{ textAlign: 'left' }}>{translate(feature, grp)}</td> { engineersList ? <table width='100%'>
<td>&nbsp;</td> <thead>
<td className={current === 0 ? '' : currentIsBeneficial ? 'secondary' : 'warning'} style={{ textAlign: 'right' }}>{current}{symbol}</td> <tr>
<td>&nbsp;</td> <td>{translate('engineers')}</td>
</tr> </tr>
); </thead>
} <tbody>
} {engineersList}
} </tbody>
} </table> : null }
} </div>
);
let components; }
if (!m) {
components = []; /**
for (const component in blueprint.components) { * Is this blueprint feature beneficial?
components.push( * @param {string} feature The name of the feature
<tr key={component}> * @param {array} values The value of the feature
<td style={{ textAlign: 'left' }}>{translate(component)}</td> * @returns {boolean} True if this feature is beneficial
<td style={{ textAlign: 'right' }}>{blueprint.components[component]}</td> */
</tr> export function isBeneficial(feature, values) {
); const fact = (values[0] < 0 || (values[0] === 0 && values[1] < 0));
} if (Modifications.modifications[feature].higherbetter) {
} return !fact;
} else {
let engineersList; return fact;
if (engineers) { }
engineersList = []; }
for (const engineer of engineers) {
engineersList.push( /**
<tr key={engineer}> * Is this feature value beneficial?
<td style={{ textAlign: 'left' }}>{engineer}</td> * @param {string} feature The name of the feature
</tr> * @param {number} value The value of the feature
); * @returns {boolean} True if this value is beneficial
} */
} export function isValueBeneficial(feature, value) {
if (Modifications.modifications[feature].higherbetter) {
return ( return value > 0;
<div> } else {
<table width='100%'> return value < 0;
<thead> }
<tr> }
<td>{translate('feature')}</td>
<td>{translate('worst')}</td> /**
{m ? <td>{translate('current')}</td> : null } * Get a blueprint with a given name and an optional module
<td>{translate('best')}</td> * @param {string} name The name of the blueprint
</tr> * @param {Object} module The module for which to obtain this blueprint
</thead> * @returns {Object} The matching blueprint
<tbody> */
{effects} export function getBlueprint(name, module) {
</tbody> // Start with a copy of the blueprint
</table> const blueprint = JSON.parse(JSON.stringify(Modifications.blueprints[name]));
{ components ? <table width='100%'> if (module) {
<thead> if (module.grp === 'bh' || module.grp === 'hr' || module.grp === 'sg' || module.grp === 'psg' || module.grp === 'bsg') {
<tr> // Bulkheads, hull reinforcements and shield generators need to have their resistances altered by the base values
<td>{translate('component')}</td> for (const grade in blueprint.grades) {
<td>{translate('amount')}</td> for (const feature in blueprint.grades[grade].features) {
</tr> if (feature === 'explres') {
</thead> blueprint.grades[grade].features[feature][0] *= (1 - module.explres);
<tbody> blueprint.grades[grade].features[feature][1] *= (1 - module.explres);
{components} }
</tbody> if (feature === 'kinres') {
</table> : null } blueprint.grades[grade].features[feature][0] *= (1 - module.kinres);
{ engineersList ? <table width='100%'> blueprint.grades[grade].features[feature][1] *= (1 - module.kinres);
<thead> }
<tr> if (feature === 'thermres') {
<td>{translate('engineers')}</td> blueprint.grades[grade].features[feature][0] *= (1 - module.thermres);
</tr> blueprint.grades[grade].features[feature][1] *= (1 - module.thermres);
</thead> }
<tbody> }
{engineersList} }
</tbody> }
</table> : null } if (module.grp === 'sb') {
</div> // Shield boosters are treated internally as straight modifiers, so rather than (for example)
); // being a 4% boost they are a 104% multiplier. We need to fix the values here so that they look
} // accurate as per the information in Elite
for (const grade in blueprint.grades) {
/** for (const feature in blueprint.grades[grade].features) {
* Is this blueprint feature beneficial? if (feature === 'shieldboost') {
* @param {string} feature The name of the feature blueprint.grades[grade].features[feature][0] = ((1 + blueprint.grades[grade].features[feature][0]) * (1 + module.shieldboost) - 1) / module.shieldboost - 1;
* @param {array} values The value of the feature blueprint.grades[grade].features[feature][1] = ((1 + blueprint.grades[grade].features[feature][1]) * (1 + module.shieldboost) - 1) / module.shieldboost - 1;
* @returns {boolean} True if this feature is beneficial }
*/ }
export function isBeneficial(feature, values) { }
const fact = (values[0] < 0 || (values[0] === 0 && values[1] < 0)); }
if (Modifications.modifications[feature].higherbetter) { }
return !fact; return blueprint;
} else { }
return fact;
} /**
} * Provide 'worst' primary modifications
* @param {Object} ship The ship for which to perform the modifications
/** * @param {Object} m The module for which to perform the modifications
* Is this feature value beneficial? */
* @param {string} feature The name of the feature export function setWorst(ship, m) {
* @param {number} value The value of the feature ship.clearModifications(m);
* @returns {boolean} True if this value is beneficial const features = m.blueprint.grades[m.blueprint.grade].features;
*/ for (const featureName in features) {
export function isValueBeneficial(feature, value) { const value = features[featureName][0];
if (Modifications.modifications[feature].higherbetter) { _setValue(ship, m, featureName, value);
return value > 0; }
} else { }
return value < 0;
} /**
} * Provide 'best' primary modifications
* @param {Object} ship The ship for which to perform the modifications
/** * @param {Object} m The module for which to perform the modifications
* Get a blueprint with a given name and an optional module */
* @param {string} name The name of the blueprint export function setBest(ship, m) {
* @param {Object} module The module for which to obtain this blueprint ship.clearModifications(m);
* @returns {Object} The matching blueprint const features = m.blueprint.grades[m.blueprint.grade].features;
*/ for (const featureName in features) {
export function getBlueprint(name, module) { const value = features[featureName][1];
// Start with a copy of the blueprint _setValue(ship, m, featureName, value);
const findMod = val => Object.keys(Modifications.blueprints).find(elem => elem.toString().toLowerCase().search(val.toString().toLowerCase().replace(/(OutfittingFieldType_|persecond)/igm, '')) >= 0); }
const found = Modifications.blueprints[findMod(name)]; }
if (!found || !found.fdname) {
return {}; /**
} * Provide 'extreme' primary modifications
const blueprint = JSON.parse(JSON.stringify(found)); * @param {Object} ship The ship for which to perform the modifications
return blueprint; * @param {Object} m The module for which to perform the modifications
} */
export function setExtreme(ship, m) {
/** ship.clearModifications(m);
* Provide 'percent' primary modifications const features = m.blueprint.grades[m.blueprint.grade].features;
* @param {Object} ship The ship for which to perform the modifications for (const featureName in features) {
* @param {Object} m The module for which to perform the modifications let value;
* @param {Number} percent The percent to set values to of full. if (Modifications.modifications[featureName].higherbetter) {
*/ // Higher is better, but is this making it better or worse?
export function setPercent(ship, m, percent) { if (features[featureName][0] < 0 || (features[featureName][0] === 0 && features[featureName][1] < 0)) {
ship.clearModifications(m); value = features[featureName][0];
// Pick given value as multiplier } else {
const mult = percent / 100; value = features[featureName][1];
const features = m.blueprint.grades[m.blueprint.grade].features; }
for (const featureName in features) { } else {
let value; // Higher is worse, but is this making it better or worse?
if (Modifications.modifications[featureName].higherbetter) { if (features[featureName][0] < 0 || (features[featureName][0] === 0 && features[featureName][1] < 0)) {
// Higher is better, but is this making it better or worse? value = features[featureName][1];
if (features[featureName][0] < 0 || (features[featureName][0] === 0 && features[featureName][1] < 0)) { } else {
value = features[featureName][1] + ((features[featureName][0] - features[featureName][1]) * mult); value = features[featureName][0];
} else { }
value = features[featureName][0] + ((features[featureName][1] - features[featureName][0]) * mult); }
}
} else { _setValue(ship, m, featureName, value);
// Higher is worse, but is this making it better or worse? }
if (features[featureName][0] < 0 || (features[featureName][0] === 0 && features[featureName][1] < 0)) { }
value = features[featureName][0] + ((features[featureName][1] - features[featureName][0]) * mult);
} else { /**
value = features[featureName][1] + ((features[featureName][0] - features[featureName][1]) * mult); * Provide 'random' primary modifications
} * @param {Object} ship The ship for which to perform the modifications
} * @param {Object} m The module for which to perform the modifications
*/
_setValue(ship, m, featureName, value); export function setRandom(ship, m) {
} ship.clearModifications(m);
} // Pick a single value for our randomness
const mult = Math.random();
/** const features = m.blueprint.grades[m.blueprint.grade].features;
* Provide 'random' primary modifications for (const featureName in features) {
* @param {Object} ship The ship for which to perform the modifications let value;
* @param {Object} m The module for which to perform the modifications if (Modifications.modifications[featureName].higherbetter) {
*/ // Higher is better, but is this making it better or worse?
export function setRandom(ship, m) { if (features[featureName][0] < 0 || (features[featureName][0] === 0 && features[featureName][1] < 0)) {
// Pick a single value for our randomness value = features[featureName][1] + ((features[featureName][0] - features[featureName][1]) * mult);
setPercent(ship, m, Math.random() * 100); } else {
} value = features[featureName][0] + ((features[featureName][1] - features[featureName][0]) * mult);
}
/** } else {
* Set a modification feature value // Higher is worse, but is this making it better or worse?
* @param {Object} ship The ship for which to perform the modifications if (features[featureName][0] < 0 || (features[featureName][0] === 0 && features[featureName][1] < 0)) {
* @param {Object} m The module for which to perform the modifications value = features[featureName][0] + ((features[featureName][1] - features[featureName][0]) * mult);
* @param {string} featureName The feature being set } else {
* @param {number} value The value being set for the feature value = features[featureName][1] + ((features[featureName][0] - features[featureName][1]) * mult);
*/ }
function _setValue(ship, m, featureName, value) { }
if (Modifications.modifications[featureName].type == 'percentage') {
ship.setModification(m, featureName, value * 10000); _setValue(ship, m, featureName, value);
} else if (Modifications.modifications[featureName].type == 'numeric') { }
ship.setModification(m, featureName, value * 100); }
} else {
ship.setModification(m, featureName, value); /**
} * Set a modification feature value
} * @param {Object} ship The ship for which to perform the modifications
* @param {Object} m The module for which to perform the modifications
/** * @param {string} featureName The feature being set
* Provide 'percent' primary query * @param {number} value The value being set for the feature
* @param {Object} m The module for which to perform the query */
* @returns {Number} percent The percentage indicator of current applied values. function _setValue(ship, m, featureName, value) {
*/ if (Modifications.modifications[featureName].type == 'percentage') {
export function getPercent(m) { ship.setModification(m, featureName, value * 10000);
let result = null; } else if (Modifications.modifications[featureName].type == 'numeric') {
const features = m.blueprint.grades[m.blueprint.grade].features; ship.setModification(m, featureName, value * 100);
for (const featureName in features) { } else {
if (features[featureName][0] === features[featureName][1]) { ship.setModification(m, featureName, value);
continue; }
} }
let value = _getValue(m, featureName);
let mult;
if (featureName == 'shieldboost') {
mult = ((1 + value) * (1 + m.shieldboost)) - 1 - m.shieldboost;
} else 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
* @returns {number} The value of the modification as a %
*/
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);
}
}

View File

@@ -29,7 +29,6 @@ export const SHIP_FD_NAME_TO_CORIOLIS_NAME = {
'FerDeLance': 'fer_de_lance', 'FerDeLance': 'fer_de_lance',
'Hauler': 'hauler', 'Hauler': 'hauler',
'Independant_Trader': 'keelback', 'Independant_Trader': 'keelback',
'Krait_MkII': 'krait_mkii',
'Orca': 'orca', 'Orca': 'orca',
'Python': 'python', 'Python': 'python',
'SideWinder': 'sidewinder', 'SideWinder': 'sidewinder',
@@ -38,15 +37,13 @@ export const SHIP_FD_NAME_TO_CORIOLIS_NAME = {
'Type9': 'type_9_heavy', 'Type9': 'type_9_heavy',
'Type9_Military': 'type_10_defender', 'Type9_Military': 'type_10_defender',
'TypeX': 'alliance_chieftain', 'TypeX': 'alliance_chieftain',
'TypeX_2': 'alliance_crusader',
'TypeX_3': 'alliance_challenger',
'Viper': 'viper', 'Viper': 'viper',
'Viper_MkIV': 'viper_mk_iv', 'Viper_MkIV': 'viper_mk_iv',
'Vulture': 'vulture' 'Vulture': 'vulture'
}; };
// Mapping from hardpoint class to name in companion API // Mapping from hardpoint class to name in companion API
export const HARDPOINT_NUM_TO_CLASS = { const HARDPOINT_NUM_TO_CLASS = {
0: 'Tiny', 0: 'Tiny',
1: 'Small', 1: 'Small',
2: 'Medium', 2: 'Medium',
@@ -54,7 +51,6 @@ export const HARDPOINT_NUM_TO_CLASS = {
4: 'Huge' 4: 'Huge'
}; };
/** /**
* Obtain a module given its ED ID * Obtain a module given its ED ID
* @param {Integer} edId the Elite ID of the module * @param {Integer} edId the Elite ID of the module
@@ -109,7 +105,7 @@ function _moduleFromEdId(edId) {
* @return {string} the Coriolis model of the ship * @return {string} the Coriolis model of the ship
*/ */
function _shipModelFromEDName(edName) { function _shipModelFromEDName(edName) {
return SHIP_FD_NAME_TO_CORIOLIS_NAME[Object.keys(SHIP_FD_NAME_TO_CORIOLIS_NAME).find(elem => elem.toLowerCase() === edName.toLowerCase())]; return SHIP_FD_NAME_TO_CORIOLIS_NAME[edName];
} }
/** /**
@@ -118,7 +114,7 @@ function _shipModelFromEDName(edName) {
* @return {string} the Coriolis model of the ship * @return {string} the Coriolis model of the ship
*/ */
export function shipModelFromJson(json) { export function shipModelFromJson(json) {
return _shipModelFromEDName(json.name || json.Ship); return _shipModelFromEDName(json.name);
} }
/** /**
@@ -148,18 +144,18 @@ export function shipFromJson(json) {
} }
let rootModule; let rootModule;
// Add the bulkheads // Add the bulkheads
const armourJson = json.modules.Armour.module; const armourJson = json.modules.Armour.module;
if (armourJson.name.toLowerCase().endsWith('_armour_grade1')) { if (armourJson.name.endsWith('_Armour_Grade1')) {
ship.useBulkhead(0, true); ship.useBulkhead(0, true);
} else if (armourJson.name.toLowerCase().endsWith('_armour_grade2')) { } else if (armourJson.name.endsWith('_Armour_Grade2')) {
ship.useBulkhead(1, true); ship.useBulkhead(1, true);
} else if (armourJson.name.toLowerCase().endsWith('_armour_grade3')) { } else if (armourJson.name.endsWith('_Armour_Grade3')) {
ship.useBulkhead(2, true); ship.useBulkhead(2, true);
} else if (armourJson.name.toLowerCase().endsWith('_armour_mirrored')) { } else if (armourJson.name.endsWith('_Armour_Mirrored')) {
ship.useBulkhead(3, true); ship.useBulkhead(3, true);
} else if (armourJson.name.toLowerCase().endsWith('_armour_reactive')) { } else if (armourJson.name.endsWith('_Armour_Reactive')) {
ship.useBulkhead(4, true); ship.useBulkhead(4, true);
} else { } else {
throw 'Unknown bulkheads "' + armourJson.name + '"'; throw 'Unknown bulkheads "' + armourJson.name + '"';
@@ -321,7 +317,7 @@ function _addModifications(module, modifiers, blueprint, grade, specialModificat
if (!modifiers) return; if (!modifiers) return;
let special; let special;
if (specialModifications) { if (specialModifications) {
special = Modifications.specials[Object.keys(specialModifications)[0]]; special = Modifications.specials[Object.keys(specialModifications)[0]]
} }
for (const i in modifiers) { for (const i in modifiers) {
// Some special modifications // Some special modifications
@@ -348,7 +344,7 @@ function _addModifications(module, modifiers, blueprint, grade, specialModificat
let value; let value;
if (i === 'OutfittingFieldType_DefenceModifierShieldMultiplier') { if (i === 'OutfittingFieldType_DefenceModifierShieldMultiplier') {
value = modifiers[i].value - 1; value = modifiers[i].value - 1;
} else if (i === 'OutfittingFieldType_DefenceModifierHealthMultiplier' && blueprint.startsWith('Armour_')) { } else if (i === 'OutfittingFieldType_DefenceModifierHealthMultiplier' && blueprint.startsWith('Armour_')) {
value = (modifiers[i].value - module.hullboost) / module.hullboost; value = (modifiers[i].value - module.hullboost) / module.hullboost;
} else if (i === 'OutfittingFieldType_DefenceModifierHealthMultiplier') { } else if (i === 'OutfittingFieldType_DefenceModifierHealthMultiplier') {
value = modifiers[i].value / module.hullboost; value = modifiers[i].value / module.hullboost;

View File

@@ -1,297 +0,0 @@
import Ship from '../shipyard/Ship';
import { HARDPOINT_NUM_TO_CLASS, shipModelFromJson } from './CompanionApiUtils';
import { Ships } from 'coriolis-data/dist';
import Module from '../shipyard/Module';
import { Modules } from 'coriolis-data/dist';
import { Modifications } from 'coriolis-data/dist';
import { getBlueprint } from './BlueprintFunctions';
/**
* Obtain a module given its FD Name
* @param {string} fdname the FD Name of the module
* @return {Module} the module
*/
function _moduleFromFdName(fdname) {
if (!fdname) return null;
fdname = fdname.toLowerCase();
// Check standard modules
for (const grp in Modules.standard) {
if (Modules.standard.hasOwnProperty(grp)) {
for (const i in Modules.standard[grp]) {
if (Modules.standard[grp][i].symbol && Modules.standard[grp][i].symbol.toLowerCase() === fdname) {
// Found it
return new Module({ template: Modules.standard[grp][i] });
}
}
}
}
// Check hardpoint modules
for (const grp in Modules.hardpoints) {
if (Modules.hardpoints.hasOwnProperty(grp)) {
for (const i in Modules.hardpoints[grp]) {
if (Modules.hardpoints[grp][i].symbol && Modules.hardpoints[grp][i].symbol.toLowerCase() === fdname) {
// Found it
return new Module({ template: Modules.hardpoints[grp][i] });
}
}
}
}
// Check internal modules
for (const grp in Modules.internal) {
if (Modules.internal.hasOwnProperty(grp)) {
for (const i in Modules.internal[grp]) {
if (Modules.internal[grp][i].symbol && Modules.internal[grp][i].symbol.toLowerCase() === fdname) {
// Found it
return new Module({ template: Modules.internal[grp][i] });
}
}
}
}
// Not found
return null;
}
/**
* Build a ship from the journal Loadout event JSON
* @param {object} json the Loadout event JSON
* @return {Ship} the built ship
*/
export function shipFromLoadoutJSON(json) {
// Start off building a basic ship
const shipModel = shipModelFromJson(json);
if (!shipModel) {
throw 'No such ship found: "' + json.Ship + '"';
}
const shipTemplate = Ships[shipModel];
let ship = new Ship(shipModel, shipTemplate.properties, shipTemplate.slots);
ship.buildWith(null);
// Initial Ship building, don't do engineering yet.
let modsToAdd = [];
for (const module of json.Modules) {
switch (module.Slot.toLowerCase()) {
// Cargo Hatch.
case 'cargohatch':
ship.cargoHatch.enabled = module.On;
ship.cargoHatch.priority = module.Priority;
break;
// Add the bulkheads
case 'armour':
if (module.Item.toLowerCase().endsWith('_armour_grade1')) {
ship.useBulkhead(0, true);
} else if (module.Item.toLowerCase().endsWith('_armour_grade2')) {
ship.useBulkhead(1, true);
} else if (module.Item.toLowerCase().endsWith('_armour_grade3')) {
ship.useBulkhead(2, true);
} else if (module.Item.toLowerCase().endsWith('_armour_mirrored')) {
ship.useBulkhead(3, true);
} else if (module.Item.toLowerCase().endsWith('_armour_reactive')) {
ship.useBulkhead(4, true);
} else {
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, module.Engineering.ExperimentalEffect);
break;
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, module.Engineering.ExperimentalEffect);
break;
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, module.Engineering.ExperimentalEffect);
break;
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, module.Engineering.ExperimentalEffect);
break;
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, module.Engineering.ExperimentalEffect);
break;
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, module.Engineering.ExperimentalEffect);
break;
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, module.Engineering.ExperimentalEffect);
break;
case 'fueltank':
const fueltank = _moduleFromFdName(module.Item);
ship.use(ship.standard[6], fueltank, true);
ship.standard[6].enabled = true;
ship.standard[6].priority = 0;
break;
default:
}
for (const module of json.Modules) {
if (module.Slot.toLowerCase().search(/hardpoint/) !== -1) {
// Add hardpoints
let hardpoint;
let hardpointClassNum = -1;
let hardpointSlotNum = -1;
let hardpointArrayNum = 0;
for (let i in shipTemplate.slots.hardpoints) {
if (shipTemplate.slots.hardpoints[i] === hardpointClassNum) {
// Another slot of the same class
hardpointSlotNum++;
} else {
// The first slot of a new class
hardpointClassNum = shipTemplate.slots.hardpoints[i];
hardpointSlotNum = 1;
}
// 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.toLowerCase() === hardpointName.toLowerCase());
if (!hardpointSlot) {
// This can happen with old imports that don't contain new hardpoints
} else if (!hardpointSlot) {
// No module
} else {
hardpoint = _moduleFromFdName(hardpointSlot.Item);
ship.use(ship.hardpoints[hardpointArrayNum], hardpoint, true);
ship.hardpoints[hardpointArrayNum].enabled = hardpointSlot.On;
ship.hardpoints[hardpointArrayNum].priority = hardpointSlot.Priority;
modsToAdd.push({ coriolisMod: hardpoint, json: hardpointSlot });
}
hardpointArrayNum++;
}
}
if (module.Slot.toLowerCase().search(/slot\d/) !== -1) {
let internalSlotNum = 1;
let militarySlotNum = 1;
for (let i in shipTemplate.slots.internal) {
if (!shipTemplate.slots.internal.hasOwnProperty(i)) {
continue;
}
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.toLowerCase() === internalName.toLowerCase());
militarySlotNum++;
} else {
// Slot numbers are not contiguous so handle skips.
while (internalSlot === null && internalSlotNum < 99) {
// 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' : '0') + internalSlotNum + '_Size' + slotsize;
if (json.Modules.find(elem => elem.Slot.toLowerCase() === internalName.toLowerCase())) {
internalSlot = json.Modules.find(elem => elem.Slot.toLowerCase() === internalName.toLowerCase());
break;
}
}
internalSlotNum++;
}
}
if (!internalSlot) {
// This can happen with old imports that don't contain new slots
} else {
const internalJson = internalSlot;
const internal = _moduleFromFdName(internalJson.Item);
ship.use(ship.internal[i], internal, true);
ship.internal[i].enabled = internalJson.On === true;
ship.internal[i].priority = internalJson.Priority;
modsToAdd.push({ coriolisMod: internal, json: internalSlot });
}
}
}
}
}
for (const i of modsToAdd) {
if (i.json.Engineering) {
_addModifications(i.coriolisMod, i.json.Engineering.Modifiers, i.json.Engineering.BlueprintName, i.json.Engineering.Level, i.json.Engineering.ExperimentalEffect);
}
}
// We don't have any information on it so guess it's priority 5 and disabled
if (!ship.cargoHatch) {
ship.cargoHatch.enabled = false;
ship.cargoHatch.priority = 4;
}
// Now update the ship's codes before returning it
return ship.updatePowerPrioritesString().updatePowerEnabledString().updateModificationsString();
}
/**
* Add the modifications for a module
* @param {Module} module the module
* @param {Object} modifiers the modifiers
* @param {Object} blueprint the blueprint of the modification
* @param {Object} grade the grade of the modification
* @param {Object} specialModifications special modification
*/
function _addModifications(module, modifiers, blueprint, grade, specialModifications) {
if (!modifiers) return;
let special;
if (specialModifications) {
special = Modifications.specials[specialModifications];
}
// Add the blueprint definition, grade and special
if (blueprint) {
module.blueprint = getBlueprint(blueprint, module);
if (grade) {
module.blueprint.grade = Number(grade);
}
if (special) {
module.blueprint.special = special;
}
}
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().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) {
}
let value = (modifiers[i].Value / modifiers[i].OriginalValue * 100 - 100) * 100;
if (value === Infinity) {
value = modifiers[i].Value * 100;
}
if (modifiers[i].Label.search('Resistance') >= 0) {
value = (modifiers[i].Value * 100) - (modifiers[i].OriginalValue * 100);
}
if (modifiers[i].Label.search('ShieldMultiplier') >= 0 || modifiers[i].Label.search('DefenceModifierHealthMultiplier') >= 0) {
value = ((100 + modifiers[i].Value) / (100 + modifiers[i].OriginalValue) * 100 - 100) * 100;
}
// Carry out the required changes
for (const action in modifierActions) {
if (isNaN(modifierActions[action])) {
module.setModValue(action, modifierActions[action]);
} else {
module.setModValue(action, value, true);
}
}
}
}

View File

@@ -1,23 +1,14 @@
import request from 'superagent'; import request from 'superagent';
let agent;
try {
agent = request.agent(); // apparently this crashes somehow
} catch (e) {
console.error(e);
}
if (!agent) {
agent = request;
}
/** /**
* Shorten a URL * Shorten a URL
* @param {string} url The URL to shorten * @param {string} url The URL to shorten
* @param {function} success Success callback * @param {function} success Success callback
* @param {function} error Failure/Error callback * @param {function} error Failure/Error callback
*/ */
export default function shorternUrl(url, success, error) { export default function shorternUrl(url, success, error) {
orbisShorten(url, success, error); shortenUrlEddp(url, success, error);
} }
const SHORTEN_API_GOOGLE = 'https://www.googleapis.com/urlshortener/v1/url?key='; const SHORTEN_API_GOOGLE = 'https://www.googleapis.com/urlshortener/v1/url?key=';
@@ -73,68 +64,3 @@ function shortenUrlEddp(url, success, error) {
error('Not Online'); error('Not Online');
} }
} }
const SHORTEN_API_ORBIS = 'https://s.orbis.zone/api.php';
/**
* Shorten a URL using Orbis's URL shortener API
* @param {string} url The URL to shorten
* @param {function} success Success callback
* @param {function} error Failure/Error callback
*/
function orbisShorten(url, success, error) {
if (window.navigator.onLine) {
try {
request.post(SHORTEN_API_ORBIS)
.field('action', 'shorturl')
.field('url', url)
.field('format', 'json')
.end(function(err, response) {
if (err) {
console.error(err);
error('Bad Request');
} else {
success(response.body.shorturl);
}
});
} catch (e) {
console.log(e);
error(e.message ? e.message : e);
}
} else {
error('Not Online');
}
}
const API_ORBIS = 'https://orbis.zone/api/builds/add';
/**
* Upload to Orbis
* @param {object} ship The URL to shorten
* @param {object} creds Orbis credentials
* @return {Promise<any>} Either a URL or error message.
*/
export function orbisUpload(ship, creds) {
return new Promise(async (resolve, reject) => {
if (window.navigator.onLine) {
try {
agent
.post(API_ORBIS)
.withCredentials()
.redirects(0)
.set('Content-Type', 'application/json')
.send(ship)
.end(function(err, response) {
if (err) {
reject('Bad Request');
} else {
resolve(response.body.link);
}
});
} catch (e) {
console.log(e);
reject(e.message ? e.message : e);
}
} else {
reject('Not Online');
}
});
}

View File

@@ -1,5 +1,5 @@
<!DOCTYPE html> <!DOCTYPE html>
<html manifest="/"> <html <%= htmlWebpackPlugin.options.appCache ? 'manifest=/' + htmlWebpackPlugin.options.appCache : '' %> >
<head> <head>
<title>Coriolis EDCD Edition</title> <title>Coriolis EDCD Edition</title>
<meta charset="UTF-8"> <meta charset="UTF-8">
@@ -26,8 +26,7 @@
<script> <script>
window.CORIOLIS_GAPI_KEY = '<%- htmlWebpackPlugin.options.gapiKey %>'; window.CORIOLIS_GAPI_KEY = '<%- htmlWebpackPlugin.options.gapiKey %>';
window.CORIOLIS_VERSION = '<%- htmlWebpackPlugin.options.version %>'; window.CORIOLIS_VERSION = '<%- htmlWebpackPlugin.options.version %>';
window.CORIOLIS_DATE = '<%- htmlWebpackPlugin.options.date.toISOString().slice(0, 10) %>'; window.CORIOLIS_DATE = '<%- new Date().toISOString().slice(0, 10) %>';
window.BUGSNAG_VERSION = '<%- htmlWebpackPlugin.options.version + '-' + htmlWebpackPlugin.options.date.toISOString() %>';
</script> </script>
<% if (htmlWebpackPlugin.options.uaTracking) { %> <% if (htmlWebpackPlugin.options.uaTracking) { %>
<script> <script>
@@ -37,9 +36,9 @@
</script> </script>
<script async src='https://www.google-analytics.com/analytics.js'></script> <script async src='https://www.google-analytics.com/analytics.js'></script>
<% } %> <% } %>
<!-- Piwik --> <!-- Piwik -->
<!-- <script type="text/javascript"> <script type="text/javascript">
var _paq = _paq || []; var _paq = _paq || [];
/* tracker methods like "setCustomDimension" should be called before "trackPageView" */ /* tracker methods like "setCustomDimension" should be called before "trackPageView" */
_paq.push(["setCookieDomain", "*.coriolis.edcd.io"]); _paq.push(["setCookieDomain", "*.coriolis.edcd.io"]);
@@ -52,16 +51,14 @@
var d=document, g=d.createElement('script'), s=d.getElementsByTagName('script')[0]; var d=document, g=d.createElement('script'), s=d.getElementsByTagName('script')[0];
g.type='text/javascript'; g.async=true; g.defer=true; g.src=u+'piwik.js'; s.parentNode.insertBefore(g,s); g.type='text/javascript'; g.async=true; g.defer=true; g.src=u+'piwik.js'; s.parentNode.insertBefore(g,s);
})(); })();
</script>--> </script>
<!-- End Piwik Code --> <!-- End Piwik Code -->
<!-- Bugsnag --> <!-- Bugsnag -->
<script src="//d2wy8f7a9ursnm.cloudfront.net/v4/bugsnag.min.js"></script> <script
<script src="//d2wy8f7a9ursnm.cloudfront.net/bugsnag-plugins/v1/bugsnag-react.min.js"></script> src="//d2wy8f7a9ursnm.cloudfront.net/bugsnag-3.min.js"
<script> data-apikey="2382691c622937f28f8fa82a1bfd797a"></script>
window.bugsnagClient = bugsnag('ba9fae819372850fb660755341fa6ef5', {appVersion: window.BUGSNAG_VERSION || undefined})
window.Bugsnag = window.bugsnagClient
</script>
</head> </head>
<body style="background-color:#000;"> <body style="background-color:#000;">
<section id="coriolis"></section> <section id="coriolis"></section>

View File

@@ -31,50 +31,6 @@ button {
} }
} }
.button-inline-menu {
white-space: nowrap;
line-height: 1.5em;
text-align: center;
margin: 0.5em 0;
padding-left: 5px;
border-top: 1px solid @primary-disabled;
border-bottom: 1px solid @primary-disabled;
overflow: hidden;
text-overflow: ellipsis;
background: @primary-bg;
&.warning {
border-color: @warning-disabled;
color: @warning-disabled;
stroke: @warning-disabled;
.no-touch &:hover {
border-color: @warning;
color: @warning;
stroke: @warning;
}
}
&.disabled, &.disabled:hover {
cursor: not-allowed;
border-color: @disabled;
color: @disabled;
stroke: @disabled;
}
&.active {
border-color: @secondary;
color: @secondary;
stroke: @secondary;
}
&:hover {
border-color: @primary;
color: @primary;
stroke: @primary;
}
}
.button-lbl { .button-lbl {
margin-left: 0.5em; margin-left: 0.5em;

View File

@@ -10,8 +10,6 @@
@secondary: #1FB0FF; // Light blue @secondary: #1FB0FF; // Light blue
@warning: #FF3B00; // Dark Orange @warning: #FF3B00; // Dark Orange
@disabled: #555; // Light grey @disabled: #555; // Light grey
@success: #71a052; // Green
@purple: #800080; // Purple
@primary-disabled: darken(@primary, @disabledDarken); @primary-disabled: darken(@primary, @disabledDarken);
@secondary-disabled: darken(@secondary, @disabledDarken); @secondary-disabled: darken(@secondary, @disabledDarken);
@warning-disabled: darken(@warning, @disabledDarken); @warning-disabled: darken(@warning, @disabledDarken);

View File

@@ -12,20 +12,6 @@
cursor: pointer; 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 { header {
background-color: @bg; background-color: @bg;
margin: 0; margin: 0;

View File

@@ -54,7 +54,6 @@ textarea {
width:100%; width:100%;
min-height: 10em; min-height: 10em;
resize: vertical; resize: vertical;
user-select: auto;
margin:2em 0; margin:2em 0;
} }
} }

View File

@@ -70,32 +70,10 @@
padding: 0.5em 0.2em; padding: 0.5em 0.2em;
font-size: 0.9em; font-size: 0.9em;
.summaryTable { #summaryTable {
.user-select-none(); .user-select-none();
width: 100%; width: 100%;
border-collapse: collapse; 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;
}
& thead th.bordered {
border-left: 1px solid @primary-bg;
}
} }
} }

View File

@@ -22,11 +22,6 @@ select {
} }
} }
.cmdr-select {
border: 1px solid @primary;
padding: 0.5em 0.5em;
}
.select { .select {
color: @primary-disabled; color: @primary-disabled;
position: absolute; position: absolute;

View File

@@ -37,48 +37,10 @@
text-overflow: ellipsis; text-overflow: ellipsis;
} }
.modification-container {
@input-container-width: 75%;
td {
width: 100% - @input-container-width;
text-align: center;
}
.input-container {
width: @input-container-width;
text-align: right;
}
input {
width: 80%;
}
.unit-container {
width: 30px;
padding: 3px;
text-align: left;
display: inline-block;
}
.header-adjuster {
width: 100% - @input-container-width;
display: inline-block;
}
}
.cb { .cb {
overflow: hidden; overflow: hidden;
} }
input.cb:focus {
border-color:#fff;
}
input:disabled {
border-color: #888;
color: #888;
}
.l { .l {
text-transform: capitalize; text-transform: capitalize;
margin-right: 0.8em; margin-right: 0.8em;

View File

@@ -1,46 +0,0 @@
console.log('Hello from sw.js');
if (workbox) {
workbox.skipWaiting();
workbox.clientsClaim();
console.log('Yay! Workbox is loaded 🎉');
workbox.routing.registerRoute(
new RegExp('https://fonts.(?:googleapis|gstatic).com/(.*)'),
workbox.strategies.cacheFirst({
cacheName: 'google-fonts',
plugins: [
new workbox.expiration.Plugin({
maxEntries: 30
}),
new workbox.cacheableResponse.Plugin({
statuses: [0, 200]
})
]
})
);
workbox.routing.registerRoute(
/\.(?:png|gif|jpg|jpeg|svg)$/,
workbox.strategies.cacheFirst({
cacheName: 'images',
plugins: [
new workbox.expiration.Plugin({
maxEntries: 60,
maxAgeSeconds: 30 * 24 * 60 * 60 // 30 Days
})
]
})
);
workbox.routing.registerRoute(
/\.(?:js|css)$/,
workbox.strategies.staleWhileRevalidate({
cacheName: 'static-resources'
})
);
try {
workbox.googleAnalytics.initialize();
} catch (e) {
console.log('Probably an ad-blocker');
}
} else {
console.log('Boo! Workbox didn\'t load 😬');
}

View File

@@ -1,30 +1,28 @@
const path = require('path'); var path = require('path');
const exec = require('child_process').exec; var exec = require('child_process').exec;
const webpack = require('webpack'); var webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin'); var pkgJson = require('./package');
const ExtractTextPlugin = require('extract-text-webpack-plugin'); var HtmlWebpackPlugin = require("html-webpack-plugin");
const WebpackNotifierPlugin = require('webpack-notifier'); var ExtractTextPlugin = require("extract-text-webpack-plugin");
const pkgJson = require('./package');
const buildDate = new Date();
function CopyDirPlugin(source, destination) { function CopyDirPlugin(source, destination) {
this.source = source; this.source = source;
this.destination = destination; this.destination = destination;
} }
CopyDirPlugin.prototype.apply = function(compiler) { CopyDirPlugin.prototype.apply = function(compiler) {
compiler.plugin('done', () => { compiler.plugin('done', function() {
console.log(compiler.outputPath, this.destination); console.log(compiler.outputPath, this.destination);
exec('cp -r ' + this.source + ' ' + path.join(compiler.outputPath, this.destination)); exec('cp -r ' + this.source + ' ' + path.join(compiler.outputPath, this.destination));
}); }.bind(this));
}; };
module.exports = { module.exports = {
devtool: 'source-map', devtool: 'eval',
devServer: { devServer: {
headers: { 'Access-Control-Allow-Origin': '*' } headers: { "Access-Control-Allow-Origin": "*" }
}, },
entry: { entry: {
app: ['webpack-dev-server/client?http://0.0.0.0:3300', 'webpack/hot/only-dev-server', path.join(__dirname, 'src/app/index.js')], app: [ 'webpack-dev-server/client?http://0.0.0.0:3300', 'webpack/hot/only-dev-server', path.join(__dirname, "src/app/index.js") ],
lib: ['d3', 'react', 'react-dom', 'classnames', 'fbemitter', 'lz-string'] lib: ['d3', 'react', 'react-dom', 'classnames', 'fbemitter', 'lz-string']
}, },
resolve: { resolve: {
@@ -39,30 +37,28 @@ module.exports = {
plugins: [ plugins: [
new CopyDirPlugin(path.join(__dirname, 'src/.htaccess'), ''), new CopyDirPlugin(path.join(__dirname, 'src/.htaccess'), ''),
new webpack.optimize.CommonsChunkPlugin({ new webpack.optimize.CommonsChunkPlugin({
name: 'lib', name: 'lib',
filename: 'lib.js' filename: 'lib.js'
}), }),
new HtmlWebpackPlugin({ new HtmlWebpackPlugin({
inject: false, inject: false,
template: path.join(__dirname, 'src/index.ejs'), template: path.join(__dirname, "src/index.ejs"),
version: pkgJson.version, version: pkgJson.version,
date: buildDate, gapiKey: process.env.CORIOLIS_GAPI_KEY || '',
gapiKey: process.env.CORIOLIS_GAPI_KEY || ''
}), }),
new ExtractTextPlugin({ new ExtractTextPlugin({
filename: 'app.css', filename: 'app.css',
disable: false, disable: false,
allChunks: true allChunks: true
}), }),
new WebpackNotifierPlugin({ alwaysNotify: true }),
new webpack.HotModuleReplacementPlugin(), new webpack.HotModuleReplacementPlugin(),
new webpack.NoEmitOnErrorsPlugin() new webpack.NoEmitOnErrorsPlugin()
], ],
module: { module: {
rules: [ rules: [
{ test: /\.css$/, loader: ExtractTextPlugin.extract({ fallback: 'style-loader', use: 'css-loader' }) }, { test: /\.css$/, loader: ExtractTextPlugin.extract({ fallback: 'style-loader', use: 'css-loader'}) },
{ test: /\.less$/, loader: ExtractTextPlugin.extract({ fallback: 'style-loader', use: 'css-loader!less-loader' }) }, { test: /\.less$/, loader: ExtractTextPlugin.extract({ fallback: 'style-loader', use: 'css-loader!less-loader'}) },
{ test: /\.(js|jsx)$/, loaders: ['babel-loader'], include: path.join(__dirname, 'src') }, { test: /\.(js|jsx)$/, loaders: [ 'babel-loader' ], include: path.join(__dirname, 'src') },
{ test: /\.woff(\?v=\d+\.\d+\.\d+)?$/, loader: 'url-loader?limit=10000&mimetype=application/font-woff' }, { test: /\.woff(\?v=\d+\.\d+\.\d+)?$/, loader: 'url-loader?limit=10000&mimetype=application/font-woff' },
{ test: /\.woff2(\?v=\d+\.\d+\.\d+)?$/, loader: 'url-loader?limit=10000&mimetype=application/font-woff' }, { test: /\.woff2(\?v=\d+\.\d+\.\d+)?$/, loader: 'url-loader?limit=10000&mimetype=application/font-woff' },
{ test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/, loader: 'url-loader?limit=10000&mimetype=application/octet-stream' }, { test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/, loader: 'url-loader?limit=10000&mimetype=application/octet-stream' },

View File

@@ -1,28 +1,24 @@
const path = require('path'); var path = require('path');
const exec = require('child_process').exec; var exec = require('child_process').exec;
const webpack = require('webpack'); var webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin'); var pkgJson = require('./package');
const ExtractTextPlugin = require('extract-text-webpack-plugin'); var HtmlWebpackPlugin = require("html-webpack-plugin");
const { InjectManifest } = require('workbox-webpack-plugin'); var ExtractTextPlugin = require("extract-text-webpack-plugin");
var AppCachePlugin = require('appcache-webpack-plugin');
const { BugsnagSourceMapUploaderPlugin } = require('webpack-bugsnag-plugins');
const pkgJson = require('./package');
const buildDate = new Date();
function CopyDirPlugin(source, destination) { function CopyDirPlugin(source, destination) {
this.source = source; this.source = source;
this.destination = destination; this.destination = destination;
} }
CopyDirPlugin.prototype.apply = function(compiler) { CopyDirPlugin.prototype.apply = function(compiler) {
compiler.plugin('done', () => { compiler.plugin('done', function() {
console.log(compiler.outputPath, this.destination); console.log(compiler.outputPath, this.destination);
exec('cp -r ' + this.source + ' ' + path.join(compiler.outputPath, this.destination)); exec('cp -r ' + this.source + ' ' + path.join(compiler.outputPath, this.destination));
}); }.bind(this));
}; };
module.exports = { module.exports = {
cache: true, cache: true,
devtool: 'source-map',
entry: { entry: {
app: ['babel-polyfill', path.resolve(__dirname, 'src/app/index')], app: ['babel-polyfill', path.resolve(__dirname, 'src/app/index')],
lib: ['d3', 'react', 'react-dom', 'classnames', 'fbemitter', 'lz-string'] lib: ['d3', 'react', 'react-dom', 'classnames', 'fbemitter', 'lz-string']
@@ -38,54 +34,49 @@ module.exports = {
}, },
plugins: [ plugins: [
new webpack.optimize.UglifyJsPlugin({ new webpack.optimize.UglifyJsPlugin({
'screw-ie8': true, 'screw-ie8': true
sourceMap: true
}), }),
// new webpack.optimize.CommonsChunkPlugin({ //new webpack.optimize.CommonsChunkPlugin({
// name: 'lib', // name: 'lib',
// filename: 'lib.[chunkhash:6].js' // filename: 'lib.[chunkhash:6].js'
// }), //}),
new HtmlWebpackPlugin({ new HtmlWebpackPlugin({
inject: false, inject: false,
appCache: 'coriolis.appcache', appCache: 'coriolis.appcache',
minify: { minify: {
collapseBooleanAttributes: true, collapseBooleanAttributes: true,
collapseWhitespace: true, collapseWhitespace: true,
removeAttributeQuotes: true, removeAttributeQuotes: true,
removeComments: true, removeComments: true,
removeEmptyAttributes: true, removeEmptyAttributes: true,
removeRedundantAttributes: true, removeRedundantAttributes: true,
removeScriptTypeAttributes: true, removeScriptTypeAttributes: true,
removeStyleLinkTypeAttributes: true removeStyleLinkTypeAttributes: true
}, },
template: path.join(__dirname, 'src/index.ejs'), template: path.join(__dirname, "src/index.ejs"),
uaTracking: process.env.CORIOLIS_UA_TRACKING || '', uaTracking: process.env.CORIOLIS_UA_TRACKING || '',
gapiKey: process.env.CORIOLIS_GAPI_KEY || '', gapiKey: process.env.CORIOLIS_GAPI_KEY || '',
date: buildDate, version: pkgJson.version
version: pkgJson.version
}), }),
new ExtractTextPlugin({ new ExtractTextPlugin({
filename: '[contenthash:6].css', filename: '[contenthash:6].css',
disable: false, disable: false,
allChunks: true allChunks: true
}),
new BugsnagSourceMapUploaderPlugin({
apiKey: 'ba9fae819372850fb660755341fa6ef5',
appVersion: `${pkgJson.version}-${buildDate.toISOString()}`
}), }),
new CopyDirPlugin(path.join(__dirname, 'src/schemas'), 'schemas'), new CopyDirPlugin(path.join(__dirname, 'src/schemas'), 'schemas'),
new CopyDirPlugin(path.join(__dirname, 'src/images/logo/*'), ''), new CopyDirPlugin(path.join(__dirname, 'src/images/logo/*'), ''),
new CopyDirPlugin(path.join(__dirname, 'src/.htaccess'), ''), new CopyDirPlugin(path.join(__dirname, 'src/.htaccess'), ''),
new InjectManifest({ new AppCachePlugin({
swSrc: './src/sw.js', network: ['*'],
importWorkboxFrom: 'cdn', settings: ['prefer-online'],
swDest: 'service-worker.js' exclude: ['index.html', /.*\.map$/],
}), output: 'coriolis.appcache'
})
], ],
module: { module: {
rules: [ rules: [
{ test: /\.css$/, loader: ExtractTextPlugin.extract({ fallback: 'style-loader', use: 'css-loader' }) }, { test: /\.css$/, loader: ExtractTextPlugin.extract({ fallback: 'style-loader', use: 'css-loader'}) },
{ test: /\.less$/, loader: ExtractTextPlugin.extract({ fallback: 'style-loader', use: 'css-loader!less-loader' }) }, { test: /\.less$/, loader: ExtractTextPlugin.extract({ fallback: 'style-loader',use: 'css-loader!less-loader'}) },
{ test: /\.(js|jsx)$/, loader: 'babel-loader?cacheDirectory=true', include: path.join(__dirname, 'src') }, { test: /\.(js|jsx)$/, loader: 'babel-loader?cacheDirectory=true', include: path.join(__dirname, 'src') },
{ test: /\.woff(\?v=\d+\.\d+\.\d+)?$/, loader: 'url-loader?limit=10000&mimetype=application/font-woff' }, { test: /\.woff(\?v=\d+\.\d+\.\d+)?$/, loader: 'url-loader?limit=10000&mimetype=application/font-woff' },
{ test: /\.woff2(\?v=\d+\.\d+\.\d+)?$/, loader: 'url-loader?limit=10000&mimetype=application/font-woff' }, { test: /\.woff2(\?v=\d+\.\d+\.\d+)?$/, loader: 'url-loader?limit=10000&mimetype=application/font-woff' },