mirror of
https://github.com/EDCD/coriolis.git
synced 2025-12-09 14:45:35 +00:00
Compare commits
273 Commits
v2.9.18
...
2eed1bc85b
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2eed1bc85b | ||
|
|
3469af10b6 | ||
|
|
629ba35bc5 | ||
|
|
e453ff73b7 | ||
|
|
c73ce1c234 | ||
|
|
00d3a93b91 | ||
|
|
d4e612cb61 | ||
|
|
c07cfc6e70 | ||
|
|
c4c6d32a5d | ||
|
|
a65bb06754 | ||
|
|
23548e7c5c | ||
|
|
74e6f54e19 | ||
|
|
a46f8f97f6 | ||
|
|
44dbdb1703 | ||
|
|
187c5dae4a | ||
|
|
07d324a3fa | ||
|
|
5c63afd96c | ||
|
|
53c40ac9c4 | ||
|
|
a180cbfdd4 | ||
|
|
fc1524a943 | ||
|
|
c3747e4e5e | ||
|
|
ab153981c9 | ||
|
|
a970e052c1 | ||
|
|
df7e264a02 | ||
|
|
cdcda004f3 | ||
|
|
20e448fc0a | ||
|
|
436e626c42 | ||
|
|
3dd4675a0b | ||
|
|
d3766d9e17 | ||
|
|
d987c08ac8 | ||
|
|
f865ef6c6c | ||
|
|
f2b7daac82 | ||
|
|
832bc488b6 | ||
|
|
f513166d6c | ||
|
|
61fd0eb991 | ||
|
|
1e51e7d4a6 | ||
|
|
4943d36bb8 | ||
|
|
9271d1fa09 | ||
|
|
8a09d94dfa | ||
|
|
c44925dd62 | ||
|
|
d006bbcb0f | ||
|
|
14453f6b80 | ||
|
|
8c267150a9 | ||
|
|
ed60a78be0 | ||
|
|
82142b0cb1 | ||
|
|
d8949fedb2 | ||
|
|
cf72bd11a8 | ||
|
|
16ef7ea389 | ||
|
|
ff455e349e | ||
|
|
ba9e7f1a32 | ||
|
|
904498b20c | ||
|
|
409be7374c | ||
|
|
00c525e6ab | ||
|
|
a2f52c03a1 | ||
|
|
037df6b166 | ||
|
|
90ab5b4b0a | ||
|
|
7bbfa8c43f | ||
|
|
2fbcd158cc | ||
|
|
33c201800e | ||
|
|
9797a8d781 | ||
|
|
cee4c32551 | ||
|
|
081d8fb86a | ||
|
|
3dfd563d90 | ||
|
|
fd08cd219c | ||
|
|
6a15326d31 | ||
|
|
608ecc51b7 | ||
|
|
fcef26ebbb | ||
|
|
ba6d758ed5 | ||
|
|
43aa3e4e79 | ||
|
|
18f0e060a7 | ||
|
|
c7547e8baf | ||
|
|
ffff242abe | ||
|
|
b44c66b986 | ||
|
|
ae77ec6256 | ||
|
|
4f1e32b154 | ||
|
|
af37c2bfc5 | ||
|
|
5d4ab6f2ad | ||
|
|
0c9db53057 | ||
|
|
b689605ac2 | ||
|
|
baab91e371 | ||
|
|
70e69c7099 | ||
|
|
f4534fd3eb | ||
|
|
93594e1a65 | ||
|
|
b5e449ea54 | ||
|
|
0ff4b849aa | ||
|
|
b99e38043f | ||
|
|
28e3a59473 | ||
|
|
b20290fb60 | ||
|
|
2734beb6f8 | ||
|
|
345eec528c | ||
|
|
7a17e18a76 | ||
|
|
4697677457 | ||
|
|
7d8a5a1368 | ||
|
|
dd7402bd0e | ||
|
|
65592b0fc6 | ||
|
|
0ab66023a6 | ||
|
|
d6fad098ee | ||
|
|
1b5730d337 | ||
|
|
439b615b1b | ||
|
|
a8b30594dc | ||
|
|
9b6b1d328c | ||
|
|
ac2e2e4d69 | ||
|
|
3a5fb31860 | ||
|
|
c610eb8627 | ||
|
|
94980270c4 | ||
|
|
c685e002e3 | ||
|
|
1f665eed9e | ||
|
|
0c4fc1fd9a | ||
|
|
0fc033363e | ||
|
|
fb6e9538bc | ||
|
|
95b98fc4ed | ||
|
|
1840dafed0 | ||
|
|
1ad82b116c | ||
|
|
7bdd17504b | ||
|
|
2d820bb5d5 | ||
|
|
2e14512ed8 | ||
|
|
48ed583c6d | ||
|
|
dd444a17f3 | ||
|
|
2ea63c711e | ||
|
|
6d6d31db25 | ||
|
|
e9273dcb9b | ||
|
|
2bdc4562c6 | ||
|
|
9e8a5323e9 | ||
|
|
8e001063b3 | ||
|
|
dc88fab4c5 | ||
|
|
dfca917e50 | ||
|
|
ef7dfd6ca1 | ||
|
|
435c1b6d45 | ||
|
|
c5c9abe588 | ||
|
|
363735d36b | ||
|
|
2741e7701b | ||
|
|
3be442ea60 | ||
|
|
34cbeca201 | ||
|
|
b37e73ead6 | ||
|
|
ee775521d6 | ||
|
|
5f84aaef1b | ||
|
|
99ac58d999 | ||
|
|
f128a1e87d | ||
|
|
8c0768b451 | ||
|
|
319307136c | ||
|
|
a498452943 | ||
|
|
4b854b8305 | ||
|
|
b400db8216 | ||
|
|
fb811faf5e | ||
|
|
deeb525433 | ||
|
|
1cb88115f6 | ||
|
|
a181791500 | ||
|
|
94eec120da | ||
|
|
48092d4395 | ||
|
|
2457c30b94 | ||
|
|
593f069806 | ||
|
|
a073692632 | ||
|
|
7752d5c9db | ||
|
|
544e5acaef | ||
|
|
9ab35bbaf9 | ||
|
|
98782da200 | ||
|
|
2936364934 | ||
|
|
01e1609a9f | ||
|
|
f85a03a9ae | ||
|
|
2703c2aa23 | ||
|
|
954921c231 | ||
|
|
8bed35a8ba | ||
|
|
9f4ae60577 | ||
|
|
ee3c50e27d | ||
|
|
03020743b3 | ||
|
|
001fed67b7 | ||
|
|
3894915740 | ||
|
|
68fd13e8dc | ||
|
|
fdf16cd959 | ||
|
|
d916c67fe0 | ||
|
|
d8a8e224f4 | ||
|
|
e1c115747c | ||
|
|
e9b6d71606 | ||
|
|
e03e249d2f | ||
|
|
0cfb0b6878 | ||
|
|
600df162aa | ||
|
|
94141aa3c5 | ||
|
|
aca90d7077 | ||
|
|
a66fa8e83f | ||
|
|
194db07057 | ||
|
|
307886d4ae | ||
|
|
bbba048129 | ||
|
|
222173b388 | ||
|
|
ec0d05e081 | ||
|
|
b3be0bd639 | ||
|
|
529d80682c | ||
|
|
934de01803 | ||
|
|
3367580d78 | ||
|
|
fbf59219d0 | ||
|
|
77401a3b3f | ||
|
|
7c587c29aa | ||
|
|
2295dccd82 | ||
|
|
cc4ad6d132 | ||
|
|
8a2d27290a | ||
|
|
eda61a8e06 | ||
|
|
3987c4e681 | ||
|
|
71b90eb6f4 | ||
|
|
4e891f382c | ||
|
|
3d3f9e44b5 | ||
|
|
dd7a133caa | ||
|
|
ae247c4812 | ||
|
|
6c932f96a6 | ||
|
|
0ea25692d3 | ||
|
|
e129e1da39 | ||
|
|
8acd32b0fc | ||
|
|
8e5dd9fb8d | ||
|
|
97ce2828e0 | ||
|
|
f8f99a5aaa | ||
|
|
70cfa58896 | ||
|
|
6e79ced51e | ||
|
|
56571f9c1f | ||
|
|
0b10cac85c | ||
|
|
34c04a6354 | ||
|
|
4e337c4ca1 | ||
|
|
5048b7e094 | ||
|
|
27fbc1ad66 | ||
|
|
4ab376d9ed | ||
|
|
dfffc3a268 | ||
|
|
b59fa15e00 | ||
|
|
12bca4c44e | ||
|
|
2858ef3e93 | ||
|
|
7d99471f89 | ||
|
|
a2ab708ac9 | ||
|
|
a34a9c355f | ||
|
|
3215b3942d | ||
|
|
557c0afd9b | ||
|
|
d52365a204 | ||
|
|
14b2a14e58 | ||
|
|
7f24904f77 | ||
|
|
da07790594 | ||
|
|
5008c7cd74 | ||
|
|
a778b1b6e1 | ||
|
|
bd9771f9ba | ||
|
|
600c244f9b | ||
|
|
a599b1a076 | ||
|
|
3e0a5e22b1 | ||
|
|
3a6ac818c2 | ||
|
|
6f077d4c41 | ||
|
|
9c767c928c | ||
|
|
515f4ad3da | ||
|
|
4fcf074595 | ||
|
|
e5f8153a34 | ||
|
|
571854a11c | ||
|
|
1f22f249a1 | ||
|
|
718ac0a514 | ||
|
|
8f089cb1ee | ||
|
|
d19a7276dd | ||
|
|
10fffe67fc | ||
|
|
f0bf8e8ce2 | ||
|
|
598cf8d677 | ||
|
|
90f03de3fe | ||
|
|
e0766f4424 | ||
|
|
28a90768e4 | ||
|
|
f3d917ccbe | ||
|
|
7e5d52385d | ||
|
|
4368015dc0 | ||
|
|
1201da1811 | ||
|
|
d195b568b0 | ||
|
|
c9866c146b | ||
|
|
5d52809d0d | ||
|
|
8f0cca4fd9 | ||
|
|
e46bb425fe | ||
|
|
06dc110025 | ||
|
|
e9c34c636a | ||
|
|
59d38cbd33 | ||
|
|
51f5188efc | ||
|
|
be8934da80 | ||
|
|
18d78b3089 | ||
|
|
b1ff4e84f7 | ||
|
|
bed2ede701 | ||
|
|
124bd62d2c | ||
|
|
975846f4ab | ||
|
|
3f73f9be10 |
33
.babelrc
33
.babelrc
@@ -1,3 +1,34 @@
|
|||||||
{
|
{
|
||||||
"presets": ["env", "react", "stage-0"]
|
"presets": [
|
||||||
|
["@babel/preset-env", {"modules": "commonjs"}],
|
||||||
|
"@babel/preset-react"
|
||||||
|
],
|
||||||
|
"plugins": [
|
||||||
|
"@babel/plugin-syntax-dynamic-import",
|
||||||
|
"@babel/plugin-syntax-import-meta",
|
||||||
|
["@babel/plugin-proposal-class-properties", { "loose": true }],
|
||||||
|
"@babel/plugin-proposal-json-strings",
|
||||||
|
[
|
||||||
|
"@babel/plugin-proposal-decorators",
|
||||||
|
{
|
||||||
|
"legacy": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"@babel/plugin-proposal-function-sent",
|
||||||
|
"@babel/plugin-proposal-export-namespace-from",
|
||||||
|
"@babel/plugin-proposal-numeric-separator",
|
||||||
|
"@babel/plugin-proposal-throw-expressions",
|
||||||
|
"@babel/plugin-proposal-export-default-from",
|
||||||
|
"@babel/plugin-proposal-logical-assignment-operators",
|
||||||
|
"@babel/plugin-proposal-optional-chaining",
|
||||||
|
[
|
||||||
|
"@babel/plugin-proposal-pipeline-operator",
|
||||||
|
{
|
||||||
|
"proposal": "minimal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"@babel/plugin-proposal-nullish-coalescing-operator",
|
||||||
|
"@babel/plugin-proposal-do-expressions",
|
||||||
|
"@babel/plugin-proposal-function-bind"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,2 +0,0 @@
|
|||||||
node_modules
|
|
||||||
npm-debug.log
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
77
.dockerignore
Normal file
77
.dockerignore
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
node_modules
|
||||||
|
npm-debug.log
|
||||||
|
### Node template
|
||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
|
||||||
|
# Runtime data
|
||||||
|
pids
|
||||||
|
*.pid
|
||||||
|
*.seed
|
||||||
|
*.pid.lock
|
||||||
|
|
||||||
|
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||||
|
lib-cov
|
||||||
|
|
||||||
|
# Coverage directory used by tools like istanbul
|
||||||
|
coverage
|
||||||
|
|
||||||
|
# nyc test coverage
|
||||||
|
.nyc_output
|
||||||
|
|
||||||
|
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
|
||||||
|
.grunt
|
||||||
|
|
||||||
|
# Bower dependency directory (https://bower.io/)
|
||||||
|
bower_components
|
||||||
|
|
||||||
|
# node-waf configuration
|
||||||
|
.lock-wscript
|
||||||
|
|
||||||
|
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||||
|
build/Release
|
||||||
|
|
||||||
|
# Dependency directories
|
||||||
|
node_modules/
|
||||||
|
jspm_packages/
|
||||||
|
|
||||||
|
# TypeScript v1 declaration files
|
||||||
|
typings/
|
||||||
|
|
||||||
|
# Optional npm cache directory
|
||||||
|
.npm
|
||||||
|
|
||||||
|
# Optional eslint cache
|
||||||
|
.eslintcache
|
||||||
|
|
||||||
|
# Optional REPL history
|
||||||
|
.node_repl_history
|
||||||
|
|
||||||
|
# Output of 'npm pack'
|
||||||
|
*.tgz
|
||||||
|
|
||||||
|
# Yarn Integrity file
|
||||||
|
.yarn-integrity
|
||||||
|
|
||||||
|
# dotenv environment variables file
|
||||||
|
.env
|
||||||
|
|
||||||
|
# parcel-bundler cache (https://parceljs.org/)
|
||||||
|
.cache
|
||||||
|
|
||||||
|
# next.js build output
|
||||||
|
.next
|
||||||
|
|
||||||
|
# nuxt.js build output
|
||||||
|
.nuxt
|
||||||
|
|
||||||
|
# vuepress build output
|
||||||
|
.vuepress/dist
|
||||||
|
|
||||||
|
# Serverless directories
|
||||||
|
.serverless
|
||||||
|
|
||||||
@@ -35,7 +35,7 @@
|
|||||||
"title": "Coriolis",
|
"title": "Coriolis",
|
||||||
"description": "Coriolis Shipyard for Elite Dangerous",
|
"description": "Coriolis Shipyard for Elite Dangerous",
|
||||||
"repository": "https://github.com/EDCD/coriolis",
|
"repository": "https://github.com/EDCD/coriolis",
|
||||||
"site": "https://coriolis.edcd.io",
|
"site": "https://coriolis.io",
|
||||||
"author": "https://github.com/edcd",
|
"author": "https://github.com/edcd",
|
||||||
"image": "./src/images/logo/192x192.png"
|
"image": "./src/images/logo/192x192.png"
|
||||||
}
|
}
|
||||||
@@ -81,7 +81,7 @@
|
|||||||
"title": "Coriolis",
|
"title": "Coriolis",
|
||||||
"description": "Coriolis Shipyard for Elite Dangerous",
|
"description": "Coriolis Shipyard for Elite Dangerous",
|
||||||
"repository": "https://github.com/EDCD/coriolis",
|
"repository": "https://github.com/EDCD/coriolis",
|
||||||
"site": "https://coriolis.edcd.io",
|
"site": "https://coriolis.io",
|
||||||
"author": "https://github.com/edcd",
|
"author": "https://github.com/edcd",
|
||||||
"image": "./src/images/logo/192x192.png"
|
"image": "./src/images/logo/192x192.png"
|
||||||
}
|
}
|
||||||
@@ -100,4 +100,4 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -10,4 +10,3 @@ env
|
|||||||
.project
|
.project
|
||||||
.vscode/
|
.vscode/
|
||||||
docs/
|
docs/
|
||||||
package-lock.json
|
|
||||||
|
|||||||
13
.gitlab-ci.yml
Normal file
13
.gitlab-ci.yml
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
image: docker:stable
|
||||||
|
services:
|
||||||
|
- docker:dind
|
||||||
|
|
||||||
|
stages:
|
||||||
|
- Build image
|
||||||
|
|
||||||
|
docker build:
|
||||||
|
stage: Build image
|
||||||
|
script:
|
||||||
|
- img build --build-arg branch=$CI_COMMIT_REF_NAME -t edcd/coriolis:$CI_COMMIT_REF_NAME .
|
||||||
|
- echo "$REGISTRY_PASSWORD" | img login --username "$REGISTRY_USER" --password-stdin
|
||||||
|
- img push edcd/coriolis:$CI_COMMIT_REF_NAME
|
||||||
@@ -6,31 +6,29 @@ WORKDIR /src/app
|
|||||||
RUN mkdir -p /src/app/coriolis
|
RUN mkdir -p /src/app/coriolis
|
||||||
RUN mkdir -p /src/app/coriolis-data
|
RUN mkdir -p /src/app/coriolis-data
|
||||||
|
|
||||||
COPY ./coriolis/ /src/app/coriolis
|
RUN apk add --update git
|
||||||
COPY ./coriolis-data/ /src/app/coriolis-data
|
|
||||||
|
|
||||||
RUN apk update
|
COPY . /src/app/coriolis
|
||||||
RUN apk add git
|
|
||||||
|
|
||||||
RUN npm i -g npm
|
RUN npm i -g npm
|
||||||
|
|
||||||
# Set up coriolis-data
|
# Set up coriolis-data
|
||||||
WORKDIR /src/app/coriolis-data
|
WORKDIR /src/app/coriolis-data
|
||||||
RUN git fetch --all
|
RUN git clone https://github.com/EDCD/coriolis-data.git .
|
||||||
RUN git reset --hard origin/$BRANCH
|
RUN git checkout ${BRANCH}
|
||||||
RUN npm install --no-package-lock
|
RUN npm install --no-package-lock
|
||||||
RUN npm start
|
RUN npm start
|
||||||
|
|
||||||
|
# Set up coriolis
|
||||||
WORKDIR /src/app/coriolis
|
WORKDIR /src/app/coriolis
|
||||||
RUN git fetch --all
|
RUN git checkout ${BRANCH}
|
||||||
RUN git reset --hard origin/$BRANCH
|
|
||||||
RUN npm install --no-package-lock
|
RUN npm install --no-package-lock
|
||||||
RUN npm run build
|
RUN npm run build
|
||||||
|
|
||||||
|
|
||||||
### STAGE 2: Production Environment ###
|
### STAGE 2: Production Environment ###
|
||||||
FROM nginx:1.13.12-alpine as web
|
FROM fholzer/nginx-brotli as web
|
||||||
COPY coriolis/.docker/nginx.conf /etc/nginx/nginx.conf
|
COPY nginx.conf /etc/nginx/nginx.conf
|
||||||
COPY --from=builder /src/app/coriolis/build /usr/share/nginx/html
|
COPY --from=builder /src/app/coriolis/build /usr/share/nginx/html
|
||||||
WORKDIR /usr/share/nginx/html
|
WORKDIR /usr/share/nginx/html
|
||||||
EXPOSE 80
|
EXPOSE 80
|
||||||
24
LICENSE.md
Normal file
24
LICENSE.md
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
All Data and [associated JSON](https://github.com/EDCD/coriolis-data) files are intellectual property and copyright of Frontier Developments plc ('Frontier', 'Frontier Developments') and are subject to their
|
||||||
|
[terms and conditions](https://www.frontierstore.net/terms-and-conditions/).
|
||||||
|
|
||||||
|
The code (Javascript, CSS, HTML, and SVG files only) specificially for Coriolis.io is released under the MIT License.
|
||||||
|
|
||||||
|
Copyright (c) 2015 Coriolis.io, Colin McLeod
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software (Javascript, CSS, HTML, and SVG files only), and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
||||||
@@ -29,6 +29,8 @@ Also see [the documentation site.](https://coriolis.willb.info/)
|
|||||||
|
|
||||||
See the [Data wiki](https://github.com/cmmcleod/coriolis-data/wiki) for details on structure, etc.
|
See the [Data wiki](https://github.com/cmmcleod/coriolis-data/wiki) for details on structure, etc.
|
||||||
|
|
||||||
|
You can find hosted and compiled versions of these data-jsons under https://coriolis.io/data/ and https://beta.coriolis.io/data/.
|
||||||
|
You might want to load these as depedency instead of reyling on the npm-dependency.
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
|
|||||||
11
d3-funcs.js
vendored
11
d3-funcs.js
vendored
@@ -1,11 +0,0 @@
|
|||||||
export {
|
|
||||||
axisBottom,
|
|
||||||
axisLeft,
|
|
||||||
axisTop,
|
|
||||||
formatLocale,
|
|
||||||
line,
|
|
||||||
scaleBand,
|
|
||||||
scaleLinear,
|
|
||||||
scaleOrdinal,
|
|
||||||
select
|
|
||||||
} from 'd3';
|
|
||||||
@@ -3,6 +3,10 @@ version: '2.2'
|
|||||||
services:
|
services:
|
||||||
coriolis_prod:
|
coriolis_prod:
|
||||||
image: edcd/coriolis:master
|
image: edcd/coriolis:master
|
||||||
|
build:
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
args:
|
||||||
|
branch: master
|
||||||
restart: always
|
restart: always
|
||||||
volumes:
|
volumes:
|
||||||
- ./nginx.conf:/etc/nginx/nginx.conf
|
- ./nginx.conf:/etc/nginx/nginx.conf
|
||||||
@@ -17,6 +21,10 @@ services:
|
|||||||
|
|
||||||
coriolis_dev:
|
coriolis_dev:
|
||||||
image: edcd/coriolis:develop
|
image: edcd/coriolis:develop
|
||||||
|
build:
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
args:
|
||||||
|
branch: develop
|
||||||
restart: always
|
restart: always
|
||||||
volumes:
|
volumes:
|
||||||
- ./nginx.conf:/etc/nginx/nginx.conf
|
- ./nginx.conf:/etc/nginx/nginx.conf
|
||||||
@@ -29,6 +37,20 @@ services:
|
|||||||
- "traefik.basic.port=80"
|
- "traefik.basic.port=80"
|
||||||
- "traefik.basic.protocol=http"
|
- "traefik.basic.protocol=http"
|
||||||
|
|
||||||
|
coriolis_dw2:
|
||||||
|
image: edcd/coriolis:dw2
|
||||||
|
restart: always
|
||||||
|
volumes:
|
||||||
|
- ./nginx.conf:/etc/nginx/nginx.conf
|
||||||
|
networks:
|
||||||
|
- web
|
||||||
|
labels:
|
||||||
|
- "traefik.docker.network=web"
|
||||||
|
- "traefik.enable=true"
|
||||||
|
- "traefik.basic.frontend.rule=Host:dw2.coriolis.io"
|
||||||
|
- "traefik.basic.port=80"
|
||||||
|
- "traefik.basic.protocol=http"
|
||||||
|
|
||||||
networks:
|
networks:
|
||||||
web:
|
web:
|
||||||
external: true
|
external: true
|
||||||
127
nginx.conf
127
nginx.conf
@@ -1,59 +1,96 @@
|
|||||||
worker_processes 2;
|
worker_processes 1;
|
||||||
error_log ./nginx.error.log;
|
user nobody nobody;
|
||||||
worker_rlimit_nofile 8192;
|
error_log /tmp/error.log;
|
||||||
pid nginx.pid;
|
pid /tmp/nginx.pid;
|
||||||
|
|
||||||
events {
|
events {
|
||||||
worker_connections 1024;
|
|
||||||
multi_accept on;
|
worker_connections 1024;
|
||||||
}
|
}
|
||||||
|
|
||||||
http {
|
http {
|
||||||
|
|
||||||
access_log off;
|
include /etc/nginx/mime.types;
|
||||||
charset UTF-8;
|
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;
|
||||||
|
|
||||||
types {
|
# https://nginx.org/en/docs/http/ngx_http_gzip_module.html
|
||||||
text/html html htm shtml;
|
# Enable gzip compression.
|
||||||
text/css css;
|
# Default: off
|
||||||
text/xml xml rss;
|
gzip off;
|
||||||
image/gif gif;
|
|
||||||
image/jpeg jpeg jpg;
|
|
||||||
application/x-javascript js;
|
|
||||||
text/plain txt;
|
|
||||||
image/png png;
|
|
||||||
image/svg+xml svg;
|
|
||||||
image/x-icon ico;
|
|
||||||
application/pdf pdf;
|
|
||||||
text/cache-manifest appcache;
|
|
||||||
}
|
|
||||||
|
|
||||||
gzip on;
|
# Compression level (1-9).
|
||||||
gzip_vary on;
|
# 5 is a perfect compromise between size and CPU usage, offering about
|
||||||
gzip_proxied any;
|
# 75% reduction for most ASCII files (almost identical to level 9).
|
||||||
gzip_comp_level 6;
|
# Default: 1
|
||||||
gzip_buffers 16 8k;
|
gzip_comp_level 5;
|
||||||
gzip_http_version 1.1;
|
|
||||||
gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript;
|
|
||||||
|
|
||||||
server {
|
# Don't compress anything that's already small and unlikely to shrink much
|
||||||
listen 3301;
|
# if at all (the default is 20 bytes, which is bad as that usually leads to
|
||||||
server_name localhost;
|
# larger files after gzipping).
|
||||||
root ./build/;
|
# Default: 20
|
||||||
index index.html;
|
gzip_min_length 256;
|
||||||
|
|
||||||
location ~* \.(?:manifest|appcache|html?|xml|json|css|js|map|jpg|jpeg|gif|png|ico|svg|eot|ttf|woff|woff2)$ {
|
# Compress data even for clients that are connecting to us via proxies,
|
||||||
expires -1;
|
# identified by the "Via" header (required for CloudFront).
|
||||||
add_header Access-Control-Allow-Origin *;
|
# Default: off
|
||||||
|
gzip_proxied any;
|
||||||
|
|
||||||
|
# Tell proxies to cache both the gzipped and regular version of a resource
|
||||||
|
# whenever the client's Accept-Encoding capabilities header varies;
|
||||||
|
# Avoids the issue where a non-gzip capable client (which is extremely rare
|
||||||
|
# today) would display gibberish if their proxy gave them the gzipped version.
|
||||||
|
# Default: off
|
||||||
|
gzip_vary on;
|
||||||
|
|
||||||
|
# Compress all output labeled with one of the following MIME-types.
|
||||||
|
# text/html is always compressed by gzip module.
|
||||||
|
# Default: text/html
|
||||||
|
gzip_types *;
|
||||||
|
brotli on;
|
||||||
|
# brotli_static on;
|
||||||
|
brotli_types *;
|
||||||
|
# This should be turned on if you are going to have pre-compressed copies (.gz) of
|
||||||
|
# static files available. If not it should be left off as it will cause extra I/O
|
||||||
|
# for the check. It is best if you enable this in a location{} block for
|
||||||
|
# a specific directory, or on an individual server{} level.
|
||||||
|
# gzip_static on;
|
||||||
|
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)$ {
|
||||||
|
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 /service-worker.js {
|
||||||
|
expires -1;
|
||||||
|
add_header Access-Control-Allow-Origin *;
|
||||||
add_header Access-Control-Allow-Credentials true;
|
add_header Access-Control-Allow-Credentials true;
|
||||||
add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS';
|
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';
|
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;
|
access_log off;
|
||||||
}
|
}
|
||||||
|
location / {
|
||||||
location / {
|
try_files $uri $uri/ /index.html =404;
|
||||||
try_files $uri $uri/ /index.html =404;
|
}
|
||||||
}
|
location /iframe.html {
|
||||||
}
|
try_files $uri $uri/ /iframe.html =404;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
25347
package-lock.json
generated
Normal file
25347
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
273
package.json
273
package.json
@@ -1,125 +1,148 @@
|
|||||||
{
|
{
|
||||||
"name": "coriolis_shipyard",
|
"name": "coriolis_shipyard",
|
||||||
"version": "2.9.18",
|
"version": "3.0.0",
|
||||||
"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.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",
|
"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 && cross-env NODE_ENV=production webpack -p --config webpack.config.prod.js",
|
"rsync": "rsync -ae \"ssh -i $CORIOLIS_PEM\" --delete build/ $CORIOLIS_USER@$CORIOLIS_HOST:~/wwws",
|
||||||
"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": {
|
"@babel/core": "^7.0.0",
|
||||||
"appcache-webpack-plugin": "^1.3.0",
|
"@babel/plugin-proposal-class-properties": "^7.0.0",
|
||||||
"babel-core": "*",
|
"@babel/plugin-proposal-decorators": "^7.0.0",
|
||||||
"babel-eslint": "*",
|
"@babel/plugin-proposal-do-expressions": "^7.0.0",
|
||||||
"babel-jest": "*",
|
"@babel/plugin-proposal-export-default-from": "^7.0.0",
|
||||||
"babel-loader": "*",
|
"@babel/plugin-proposal-export-namespace-from": "^7.0.0",
|
||||||
"babel-preset-env": "*",
|
"@babel/plugin-proposal-function-bind": "^7.0.0",
|
||||||
"babel-preset-react": "*",
|
"@babel/plugin-proposal-function-sent": "^7.0.0",
|
||||||
"babel-preset-stage-0": "*",
|
"@babel/plugin-proposal-json-strings": "^7.0.0",
|
||||||
"create-react-class": "^15.6.2",
|
"@babel/plugin-proposal-logical-assignment-operators": "^7.0.0",
|
||||||
"cross-env": "^5.1.4",
|
"@babel/plugin-proposal-nullish-coalescing-operator": "^7.0.0",
|
||||||
"css-loader": "^0.28.0",
|
"@babel/plugin-proposal-numeric-separator": "^7.0.0",
|
||||||
"d3-selection": "1",
|
"@babel/plugin-proposal-optional-chaining": "^7.0.0",
|
||||||
"esdoc": "^1.1.0",
|
"@babel/plugin-proposal-pipeline-operator": "^7.0.0",
|
||||||
"esdoc-custom-theme": "^1.4.2",
|
"@babel/plugin-proposal-throw-expressions": "^7.0.0",
|
||||||
"esdoc-ecmascript-proposal-plugin": "^1.0.0",
|
"@babel/plugin-syntax-dynamic-import": "^7.0.0",
|
||||||
"esdoc-jsx-plugin": "^1.0.0",
|
"@babel/plugin-syntax-import-meta": "^7.0.0",
|
||||||
"esdoc-publish-html-plugin": "^1.1.2",
|
"@babel/preset-env": "^7.0.0",
|
||||||
"esdoc-react-plugin": "^1.0.1",
|
"@babel/preset-react": "^7.0.0",
|
||||||
"esdoc-standard-plugin": "^1.0.0",
|
"appcache-webpack-plugin": "^1.4.0",
|
||||||
"eslint": "3.19.0",
|
"babel-core": "^7.0.0-bridge.0",
|
||||||
"eslint-plugin-react": "^6.10.3",
|
"babel-eslint": "^10.0.1",
|
||||||
"expose-loader": "^0.7.3",
|
"babel-jest": "^23.6.0",
|
||||||
"express": "^4.15.2",
|
"babel-loader": "^8.0.0",
|
||||||
"extract-text-webpack-plugin": "2.1.0",
|
"copy-webpack-plugin": "^4.5.2",
|
||||||
"file-loader": "^0.11.1",
|
"create-react-class": "^15.6.3",
|
||||||
"html-webpack-plugin": "^2.28.0",
|
"cross-env": "^5.2.0",
|
||||||
"jest-cli": "^21.2.1",
|
"css-loader": "^1.0.0",
|
||||||
"jsen": "^0.6.4",
|
"d3-selection": "^1.3.2",
|
||||||
"json-loader": "^0.5.4",
|
"esdoc": "^1.1.0",
|
||||||
"less": "^2.7.2",
|
"esdoc-custom-theme": "^1.4.2",
|
||||||
"less-loader": "^4.0.3",
|
"esdoc-ecmascript-proposal-plugin": "^1.0.0",
|
||||||
"react-addons-perf": "^15.4.2",
|
"esdoc-jsx-plugin": "^1.0.0",
|
||||||
"react-container-dimensions": "^1.4.1",
|
"esdoc-publish-html-plugin": "^1.1.2",
|
||||||
"react-testutils-additions": "^15.2.0",
|
"esdoc-react-plugin": "^1.0.1",
|
||||||
"react-transition-group": "^1.1.2",
|
"esdoc-standard-plugin": "^1.0.0",
|
||||||
"rimraf": "^2.6.1",
|
"eslint": "^5.6.0",
|
||||||
"rollup": "0.41",
|
"eslint-plugin-react": "^7.11.1",
|
||||||
"rollup-plugin-node-resolve": "3",
|
"expose-loader": "^0.7.5",
|
||||||
"style-loader": "^0.16.1",
|
"express": "^4.16.3",
|
||||||
"uglify-js": "^2.4.11",
|
"extract-text-webpack-plugin": "^4.0.0-beta.0",
|
||||||
"url-loader": "^0.5.8",
|
"file-loader": "^2.0.0",
|
||||||
"webpack": "^2.4.1",
|
"html-webpack-plugin": "^3.0.7",
|
||||||
"webpack-bugsnag-plugins": "^1.1.1",
|
"jest-cli": "^23.6.0",
|
||||||
"webpack-dev-server": "^2.4.4",
|
"jsen": "^0.6.4",
|
||||||
"webpack-notifier": "^1.6.0",
|
"json-loader": "^0.5.4",
|
||||||
"workbox-webpack-plugin": "^3.4.1"
|
"less": "^3.8.1",
|
||||||
},
|
"less-loader": "^4.1.0",
|
||||||
"dependencies": {
|
"react-addons-perf": "^15.4.2",
|
||||||
"babel-polyfill": "*",
|
"react-container-dimensions": "^1.4.1",
|
||||||
"browserify-zlib-next": "^1.0.1",
|
"react-testutils-additions": "^16.0.0",
|
||||||
"classnames": "^2.2.5",
|
"react-transition-group": "^2.5.0",
|
||||||
"coriolis-data": "../coriolis-data",
|
"rimraf": "^2.6.1",
|
||||||
"d3": "4.8.0",
|
"rollup": "^0.66.2",
|
||||||
"detect-browser": "^1.7.0",
|
"rollup-plugin-node-resolve": "^3.4.0",
|
||||||
"fbemitter": "^2.1.1",
|
"style-loader": "^0.23.0",
|
||||||
"lodash": "^4.17.10",
|
"uglify-js": "^3.4.9",
|
||||||
"lz-string": "^1.4.4",
|
"url-loader": "^1.1.1",
|
||||||
"pako": "^1.0.6",
|
"webpack": "^4.20.2",
|
||||||
"prop-types": "^15.5.8",
|
"webpack-bugsnag-plugins": "^1.2.2",
|
||||||
"react": "^15.5.4",
|
"webpack-cli": "^3.1.1",
|
||||||
"react-dom": "^15.5.4",
|
"webpack-dev-server": "^3.1.9",
|
||||||
"react-ga": "^2.5.3",
|
"webpack-notifier": "^1.6.0",
|
||||||
"react-number-editor": "Athanasius/react-number-editor.git#miggy",
|
"workbox-webpack-plugin": "^3.6.1"
|
||||||
"recharts": "^0.22.3",
|
},
|
||||||
"superagent": "^3.5.2"
|
"sideEffects": false,
|
||||||
}
|
"dependencies": {
|
||||||
}
|
"@babel/polyfill": "^7.0.0",
|
||||||
|
"auto-bind": "^2.1.1",
|
||||||
|
"browserify-zlib-next": "^1.0.1",
|
||||||
|
"classnames": "^2.2.6",
|
||||||
|
"coriolis-data": "../coriolis-data",
|
||||||
|
"d3": "^5.7.0",
|
||||||
|
"detect-browser": "^3.0.1",
|
||||||
|
"ed-forge": "github:EDCD/ed-forge",
|
||||||
|
"fbemitter": "^2.1.1",
|
||||||
|
"lodash": "^4.17.11",
|
||||||
|
"lz-string": "^1.4.4",
|
||||||
|
"pako": "^1.0.6",
|
||||||
|
"prop-types": "^15.6.2",
|
||||||
|
"react": "^15.5.4",
|
||||||
|
"react-dom": "^15.5.4",
|
||||||
|
"react-extras": "^0.7.1",
|
||||||
|
"react-fuzzy": "^0.5.2",
|
||||||
|
"react-ga": "^2.5.3",
|
||||||
|
"react-number-editor": "^4.0.3",
|
||||||
|
"recharts": "^1.2.0",
|
||||||
|
"register-service-worker": "^1.5.2",
|
||||||
|
"superagent": "^3.8.3"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,19 +1,18 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import Router from './Router';
|
import Router from './Router';
|
||||||
|
import { register } from 'register-service-worker';
|
||||||
import { EventEmitter } from 'fbemitter';
|
import { EventEmitter } from 'fbemitter';
|
||||||
import { getLanguage } from './i18n/Language';
|
import { getLanguage } from './i18n/Language';
|
||||||
import Persist from './stores/Persist';
|
import Persist from './stores/Persist';
|
||||||
|
import { Ship } from 'ed-forge';
|
||||||
|
|
||||||
|
import Announcement from './components/Announcement';
|
||||||
import Header from './components/Header';
|
import Header from './components/Header';
|
||||||
import Tooltip from './components/Tooltip';
|
import Tooltip from './components/Tooltip';
|
||||||
import ModalExport from './components/ModalExport';
|
|
||||||
import ModalHelp from './components/ModalHelp';
|
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 JournalUtils from './utils/JournalUtils';
|
|
||||||
|
|
||||||
import AboutPage from './pages/AboutPage';
|
import AboutPage from './pages/AboutPage';
|
||||||
import NotFoundPage from './pages/NotFoundPage';
|
import NotFoundPage from './pages/NotFoundPage';
|
||||||
import OutfittingPage from './pages/OutfittingPage';
|
import OutfittingPage from './pages/OutfittingPage';
|
||||||
@@ -21,13 +20,12 @@ import ComparisonPage from './pages/ComparisonPage';
|
|||||||
import ShipyardPage from './pages/ShipyardPage';
|
import ShipyardPage from './pages/ShipyardPage';
|
||||||
import ErrorDetails from './pages/ErrorDetails';
|
import ErrorDetails from './pages/ErrorDetails';
|
||||||
|
|
||||||
const zlib = require('pako');
|
const request = require('superagent');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Coriolis App
|
* Coriolis App
|
||||||
*/
|
*/
|
||||||
export default class Coriolis extends React.Component {
|
export default class Coriolis extends React.Component {
|
||||||
|
|
||||||
static childContextTypes = {
|
static childContextTypes = {
|
||||||
closeMenu: PropTypes.func.isRequired,
|
closeMenu: PropTypes.func.isRequired,
|
||||||
hideModal: PropTypes.func.isRequired,
|
hideModal: PropTypes.func.isRequired,
|
||||||
@@ -60,17 +58,17 @@ export default class Coriolis extends React.Component {
|
|||||||
this._onLanguageChange = this._onLanguageChange.bind(this);
|
this._onLanguageChange = this._onLanguageChange.bind(this);
|
||||||
this._onSizeRatioChange = this._onSizeRatioChange.bind(this);
|
this._onSizeRatioChange = this._onSizeRatioChange.bind(this);
|
||||||
this._keyDown = this._keyDown.bind(this);
|
this._keyDown = this._keyDown.bind(this);
|
||||||
this._importBuild = this._importBuild.bind(this);
|
|
||||||
|
|
||||||
this.emitter = new EventEmitter();
|
this.emitter = new EventEmitter();
|
||||||
this.state = {
|
this.state = {
|
||||||
noTouch: !('ontouchstart' in window || navigator.msMaxTouchPoints || navigator.maxTouchPoints),
|
noTouch: !('ontouchstart' in window || navigator.msMaxTouchPoints || navigator.maxTouchPoints),
|
||||||
page: null,
|
page: null,
|
||||||
|
announcements: [],
|
||||||
language: getLanguage(Persist.getLangCode()),
|
language: getLanguage(Persist.getLangCode()),
|
||||||
route: {},
|
route: {},
|
||||||
sizeRatio: Persist.getSizeRatio()
|
sizeRatio: Persist.getSizeRatio()
|
||||||
};
|
};
|
||||||
|
this._getAnnouncements();
|
||||||
Router('', (r) => this._setPage(ShipyardPage, r));
|
Router('', (r) => this._setPage(ShipyardPage, r));
|
||||||
Router('/import?', (r) => this._importBuild(r));
|
Router('/import?', (r) => this._importBuild(r));
|
||||||
Router('/import/:data', (r) => this._importBuild(r));
|
Router('/import/:data', (r) => this._importBuild(r));
|
||||||
@@ -91,24 +89,25 @@ export default class Coriolis extends React.Component {
|
|||||||
_importBuild(r) {
|
_importBuild(r) {
|
||||||
try {
|
try {
|
||||||
// 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' });
|
let ship = new Ship(r.params.data);
|
||||||
const json = JSON.parse(data);
|
r.params.ship = ship.getShipType();
|
||||||
console.info('Ship import data: ');
|
r.params.code = ship.compress();
|
||||||
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.code = ship.toString();
|
|
||||||
this._setPage(OutfittingPage, r);
|
this._setPage(OutfittingPage, r);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this._onError('Failed to import ship', r.path, 0, 0, err);
|
this._onError('Failed to import ship', r.path, 0, 0, err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async _getAnnouncements() {
|
||||||
|
try {
|
||||||
|
const announces = await request.get('https://orbis.zone/api/announcement')
|
||||||
|
.query({ showInCoriolis: true });
|
||||||
|
this.setState({ announcements: announces.body });
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates / Sets the page and route context
|
* Updates / Sets the page and route context
|
||||||
* @param {[type]} page The page to be shown
|
* @param {[type]} page The page to be shown
|
||||||
@@ -334,47 +333,37 @@ export default class Coriolis extends React.Component {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
if ('serviceWorker' in navigator) {
|
if ('serviceWorker' in navigator) {
|
||||||
window.addEventListener('load', () => {
|
// Your service-worker.js *must* be located at the top-level directory relative to your site.
|
||||||
// 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.
|
||||||
// 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!
|
||||||
// *Don't* register service worker file in, e.g., a scripts/ sub-directory!
|
// See https://github.com/slightlyoff/ServiceWorker/issues/468
|
||||||
// See https://github.com/slightlyoff/ServiceWorker/issues/468
|
const self = this;
|
||||||
const self = this;
|
if (process.env.NODE_ENV === 'production') {
|
||||||
navigator.serviceWorker.register('/service-worker.js').then(function(reg) {
|
register('/service-worker.js', {
|
||||||
// updatefound is fired if service-worker.js changes.
|
ready(registration) {
|
||||||
reg.onupdatefound = function() {
|
console.log('Service worker is active.');
|
||||||
// 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
|
registered(registration) {
|
||||||
var installingWorker = reg.installing;
|
console.log('Service worker has been registered.');
|
||||||
|
},
|
||||||
installingWorker.onstatechange = function() {
|
cached(registration) {
|
||||||
switch (installingWorker.state) {
|
console.log('Content has been cached for offline use.');
|
||||||
case 'installed':
|
},
|
||||||
if (navigator.serviceWorker.controller) {
|
updatefound(registration) {
|
||||||
// At this point, the old content will have been purged and the fresh content will
|
console.log('New content is downloading.');
|
||||||
// have been added to the cache.
|
},
|
||||||
// It's the perfect time to display a "New content is available; please refresh."
|
updated(registration) {
|
||||||
// message in the page's interface.
|
self.setState({ appCacheUpdate: true });
|
||||||
console.log('New or updated content is available.');
|
console.log('New content is available; please refresh.');
|
||||||
self.setState({ appCacheUpdate: true }); // Browser downloaded a new app cache.
|
},
|
||||||
} else {
|
offline() {
|
||||||
// At this point, everything has been precached.
|
console.log('No internet connection found. App is running in offline mode.');
|
||||||
// It's the perfect time to display a "Content is cached for offline use." message.
|
},
|
||||||
console.log('Content is now available offline!');
|
error(error) {
|
||||||
self.setState({ appCacheUpdate: true }); // Browser downloaded a new app cache.
|
console.error('Error during service worker registration:', error);
|
||||||
}
|
}
|
||||||
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'));
|
||||||
@@ -395,19 +384,23 @@ export default class Coriolis extends React.Component {
|
|||||||
|
|
||||||
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 announcements={this.state.announcements} appCacheUpdate={this.state.appCacheUpdate}
|
||||||
|
currentMenu={currentMenu}/>
|
||||||
|
<div className="announcement-container">{this.state.announcements.map(a => <Announcement
|
||||||
|
text={a.message}/>)}</div>
|
||||||
{this.state.error ? this.state.error : this.state.page ? React.createElement(this.state.page, { currentMenu }) :
|
{this.state.error ? this.state.error : this.state.page ? React.createElement(this.state.page, { currentMenu }) :
|
||||||
<NotFoundPage/>}
|
<NotFoundPage/>}
|
||||||
{this.state.modal}
|
{this.state.modal}
|
||||||
{this.state.tooltip}
|
{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" rel="noopener noreferrer"
|
||||||
title="Coriolis Github Project">{window.CORIOLIS_VERSION} - {window.CORIOLIS_DATE}</a>
|
title="Coriolis Github Project">{window.CORIOLIS_VERSION} - {window.CORIOLIS_DATE}</a>
|
||||||
<br/>
|
<br/>
|
||||||
<a
|
<a
|
||||||
href={'https://github.com/EDCD/coriolis/compare/edcd:develop@{' + window.CORIOLIS_DATE + '}...edcd:develop'}
|
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
|
target="_blank" rel="noopener noreferrer" title={'Coriolis Commits since' + window.CORIOLIS_DATE}>Commits
|
||||||
|
since last release
|
||||||
({window.CORIOLIS_DATE})</a>
|
({window.CORIOLIS_DATE})</a>
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
|
|||||||
@@ -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,8 @@ 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=.*)/);
|
const _paq = window._paq || [];
|
||||||
if (match) {
|
_paq.push(['trackPageView']);
|
||||||
if (match[1]) {
|
|
||||||
ReactGA.ga('set', 'contentGroup1', match[1]);
|
|
||||||
}
|
|
||||||
if (match[2]) {
|
|
||||||
ReactGA.ga('set', 'contentGroup2', match[2]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ReactGA.pageview(path);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ function isActive(href) {
|
|||||||
* Active Link - Highlighted when URL matches window location
|
* Active Link - Highlighted when URL matches window location
|
||||||
*/
|
*/
|
||||||
export default class ActiveLink extends Link {
|
export default class ActiveLink extends Link {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Renders the component
|
* Renders the component
|
||||||
* @return {React.Component} The active link
|
* @return {React.Component} The active link
|
||||||
@@ -29,5 +28,4 @@ export default class ActiveLink extends Link {
|
|||||||
|
|
||||||
return <a {...this.props} className={className} onClick={this.handler}>{this.props.children}</a>;
|
return <a {...this.props} className={className} onClick={this.handler}>{this.props.children}</a>;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
|
||||||
|
|||||||
29
src/app/components/Announcement.jsx
Normal file
29
src/app/components/Announcement.jsx
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { autoBind } from 'react-extras';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Announcement component
|
||||||
|
*/
|
||||||
|
export default class Announcement extends React.Component {
|
||||||
|
static propTypes = {
|
||||||
|
text: PropTypes.string
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
* @param {Object} props React Component properties
|
||||||
|
*/
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
autoBind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renders the announcement
|
||||||
|
* @return {React.Component} A href element
|
||||||
|
*/
|
||||||
|
render() {
|
||||||
|
return <div className="announcement" >{this.props.text}</div>;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,131 +1,35 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import * as ModuleUtils from '../shipyard/ModuleUtils';
|
|
||||||
import TranslatedComponent from './TranslatedComponent';
|
import TranslatedComponent from './TranslatedComponent';
|
||||||
import { stopCtxPropagation } from '../utils/UtilityFunctions';
|
import { stopCtxPropagation } from '../utils/UtilityFunctions';
|
||||||
import cn from 'classnames';
|
import cn from 'classnames';
|
||||||
import { MountFixed, MountGimballed, MountTurret } from './SvgIcons';
|
import { MountFixed, MountGimballed, MountTurret } from './SvgIcons';
|
||||||
|
import FuzzySearch from 'react-fuzzy';
|
||||||
|
import { getModuleInfo } from 'ed-forge/lib/data/items';
|
||||||
|
import { groupBy, mapValues, sortBy } from 'lodash';
|
||||||
|
import autoBind from 'auto-bind';
|
||||||
|
|
||||||
const PRESS_THRESHOLD = 500; // mouse/touch down threshold
|
const PRESS_THRESHOLD = 500; // mouse/touch down threshold
|
||||||
|
|
||||||
/*
|
const MOUNT_MAP = {
|
||||||
* Categorisation of module groups
|
fixed: <MountFixed className={'lg'} />,
|
||||||
*/
|
gimbal: <MountGimballed className={'lg'} />,
|
||||||
const GRPCAT = {
|
turret: <MountTurret className={'lg'} />,
|
||||||
'sg': 'shields',
|
|
||||||
'bsg': 'shields',
|
|
||||||
'psg': 'shields',
|
|
||||||
'scb': 'shields',
|
|
||||||
'cc': 'limpet controllers',
|
|
||||||
'fx': 'limpet controllers',
|
|
||||||
'hb': 'limpet controllers',
|
|
||||||
'pc': 'limpet controllers',
|
|
||||||
'rpl': 'limpet controllers',
|
|
||||||
'pce': 'passenger cabins',
|
|
||||||
'pci': 'passenger cabins',
|
|
||||||
'pcm': 'passenger cabins',
|
|
||||||
'pcq': 'passenger cabins',
|
|
||||||
'fh': 'hangars',
|
|
||||||
'pv': 'hangars',
|
|
||||||
'fs': 'fuel',
|
|
||||||
'ft': 'fuel',
|
|
||||||
'hr': 'structural reinforcement',
|
|
||||||
'mrp': 'structural reinforcement',
|
|
||||||
'bl': 'lasers',
|
|
||||||
'pl': 'lasers',
|
|
||||||
'ul': 'lasers',
|
|
||||||
'ml': 'lasers',
|
|
||||||
'c': 'projectiles',
|
|
||||||
'mc': 'projectiles',
|
|
||||||
'axmc': 'experimental',
|
|
||||||
'fc': 'projectiles',
|
|
||||||
'rfl': 'experimental',
|
|
||||||
'pa': 'projectiles',
|
|
||||||
'rg': 'projectiles',
|
|
||||||
'mr': 'ordnance',
|
|
||||||
'axmr': 'experimental',
|
|
||||||
'rcpl': 'experimental',
|
|
||||||
'dtl': 'experimental',
|
|
||||||
'tbsc': 'experimental',
|
|
||||||
'tbem': 'experimental',
|
|
||||||
'tbrfl': 'experimental',
|
|
||||||
'mahr': 'experimental',
|
|
||||||
'rsl': 'experimental',
|
|
||||||
'tp': 'ordnance',
|
|
||||||
'nl': 'ordnance',
|
|
||||||
'sc': 'scanners',
|
|
||||||
'ss': 'scanners',
|
|
||||||
// Utilities
|
|
||||||
'cs': 'scanners',
|
|
||||||
'kw': 'scanners',
|
|
||||||
'ws': 'scanners',
|
|
||||||
'xs': 'scanners',
|
|
||||||
'ch': 'defence',
|
|
||||||
'po': 'defence',
|
|
||||||
'ec': 'defence',
|
|
||||||
'sfn': 'defence',
|
|
||||||
// Guardian
|
|
||||||
'gpp': 'guardian',
|
|
||||||
'gpc': 'guardian',
|
|
||||||
'gsrp': 'guardian',
|
|
||||||
'ggc': 'guardian',
|
|
||||||
'gfsb': 'guardian',
|
|
||||||
'gmrp': 'guardian',
|
|
||||||
'gsc': 'guardian',
|
|
||||||
'ghrp': 'guardian'
|
|
||||||
};
|
|
||||||
// Order here is the order in which items will be shown in the modules menu
|
|
||||||
const CATEGORIES = {
|
|
||||||
// Internals
|
|
||||||
'am': ['am'],
|
|
||||||
'cr': ['cr'],
|
|
||||||
'fi': ['fi'],
|
|
||||||
'fuel': ['ft', 'fs'],
|
|
||||||
'hangars': ['fh', 'pv'],
|
|
||||||
'limpet controllers': ['cc', 'fx', 'hb', 'pc', 'rpl'],
|
|
||||||
'passenger cabins': ['pce', 'pci', 'pcm', 'pcq'],
|
|
||||||
'rf': ['rf'],
|
|
||||||
'shields': ['sg', 'bsg', 'psg', 'scb'],
|
|
||||||
'structural reinforcement': ['hr', 'mrp'],
|
|
||||||
'dc': ['dc'],
|
|
||||||
// Hardpoints
|
|
||||||
'lasers': ['pl', 'ul', 'bl', 'ml'],
|
|
||||||
'projectiles': ['mc', 'c', 'fc', 'pa', 'rg'],
|
|
||||||
'ordnance': ['mr', 'tp', 'nl'],
|
|
||||||
// Utilities
|
|
||||||
'sb': ['sb'],
|
|
||||||
'hs': ['hs'],
|
|
||||||
'defence': ['ch', 'po', 'ec'],
|
|
||||||
'scanners': ['sc', 'ss', 'cs', 'kw', 'ws'], // Overloaded with internal scanners
|
|
||||||
// Experimental
|
|
||||||
'experimental': ['axmc', 'axmr', 'rfl', 'tbrfl', 'tbsc', 'tbem', 'xs', 'sfn', 'rcpl', 'dtl', 'rsl', 'mahr', ],
|
|
||||||
|
|
||||||
// Guardian
|
|
||||||
'guardian': ['gpp', 'gpd', 'gpc', 'ggc', 'gsrp', 'gfsb', 'ghrp', 'gmrp', 'gsc']
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Available modules menu
|
* Available modules menu
|
||||||
*/
|
*/
|
||||||
export default class AvailableModulesMenu extends TranslatedComponent {
|
export default class AvailableModulesMenu extends TranslatedComponent {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
modules: PropTypes.oneOfType([PropTypes.object, PropTypes.array]).isRequired,
|
|
||||||
onSelect: PropTypes.func.isRequired,
|
onSelect: PropTypes.func.isRequired,
|
||||||
diffDetails: PropTypes.func,
|
diffDetails: PropTypes.func,
|
||||||
|
hideSearch: PropTypes.bool,
|
||||||
m: PropTypes.object,
|
m: PropTypes.object,
|
||||||
shipMass: PropTypes.number,
|
|
||||||
warning: PropTypes.func,
|
warning: PropTypes.func,
|
||||||
firstSlotId: PropTypes.string,
|
|
||||||
lastSlotId: PropTypes.string,
|
|
||||||
activeSlotId: PropTypes.string,
|
|
||||||
slotDiv: PropTypes.object
|
slotDiv: PropTypes.object
|
||||||
};
|
};
|
||||||
|
|
||||||
static defaultProps = {
|
|
||||||
shipMass: 0
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor
|
* Constructor
|
||||||
* @param {Object} props React Component properties
|
* @param {Object} props React Component properties
|
||||||
@@ -133,9 +37,8 @@ export default class AvailableModulesMenu extends TranslatedComponent {
|
|||||||
*/
|
*/
|
||||||
constructor(props, context) {
|
constructor(props, context) {
|
||||||
super(props);
|
super(props);
|
||||||
this._hideDiff = this._hideDiff.bind(this);
|
autoBind(this);
|
||||||
this.state = this._initState(props, context);
|
this.state = this._initState(props, context);
|
||||||
this.slotItems = [];// Array to hold <li> refs.
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -145,202 +48,199 @@ export default class AvailableModulesMenu extends TranslatedComponent {
|
|||||||
* @return {Object} list: Array of React Components, currentGroup Component if any
|
* @return {Object} list: Array of React Components, currentGroup Component if any
|
||||||
*/
|
*/
|
||||||
_initState(props, context) {
|
_initState(props, context) {
|
||||||
let translate = context.language.translate;
|
const { translate } = context.language;
|
||||||
let { m, warning, shipMass, onSelect, modules, firstSlotId, lastSlotId } = props;
|
const { m } = props;
|
||||||
let list, currentGroup;
|
const list = [], fuzzy = [];
|
||||||
|
let currentGroup;
|
||||||
|
|
||||||
let buildGroup = this._buildGroup.bind(
|
const modules = m.getApplicableItems().map(getModuleInfo);
|
||||||
this,
|
const groups = mapValues(
|
||||||
translate,
|
groupBy(modules, (info) => info.meta.group),
|
||||||
m,
|
(infos) => groupBy(infos, (info) => info.meta.type),
|
||||||
warning,
|
);
|
||||||
shipMass - (m && m.mass ? m.mass : 0),
|
// Build categories sorted by translated category name
|
||||||
(m, event) => {
|
const groupKeys = sortBy(Object.keys(groups), translate);
|
||||||
this._hideDiff(event);
|
for (const group of groupKeys) {
|
||||||
onSelect(m);
|
const groupName = translate(group);
|
||||||
}
|
if (groupKeys.length > 1) {
|
||||||
);
|
list.push(<div key={`group-${group}`} className="select-category upp">{groupName}</div>);
|
||||||
|
|
||||||
if (modules instanceof Array) {
|
|
||||||
list = buildGroup(modules[0].grp, modules);
|
|
||||||
} else {
|
|
||||||
list = [];
|
|
||||||
// At present time slots with grouped options (Hardpoints and Internal) can be empty
|
|
||||||
if (m) {
|
|
||||||
let emptyId = 'empty';
|
|
||||||
if(this.firstSlotId == null) this.firstSlotId = emptyId;
|
|
||||||
let keyDown = this._keyDown.bind(this, onSelect);
|
|
||||||
list.push(<div className='empty-c upp' key={emptyId} data-id={emptyId} onClick={onSelect.bind(null, null)} onKeyDown={keyDown} tabIndex="0" ref={slotItem => this.slotItems[emptyId] = slotItem} >{translate('empty')}</div>);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Need to regroup the modules by our own categorisation
|
const categories = groups[group];
|
||||||
let catmodules = {};
|
const categoryKeys = sortBy(Object.keys(categories), translate);
|
||||||
// Pre-create to preserve ordering
|
for (const category of categoryKeys) {
|
||||||
for (let cat in CATEGORIES) {
|
const categoryName = translate(category);
|
||||||
catmodules[cat] = [];
|
const infos = categories[category];
|
||||||
}
|
if (categoryKeys.length > 1) {
|
||||||
for (let g in modules) {
|
list.push(<div key={`category-${category}`} className="select-group cap">{categoryName}</div>);
|
||||||
const moduleCategory = GRPCAT[g] || g;
|
|
||||||
const existing = catmodules[moduleCategory] || [];
|
|
||||||
catmodules[moduleCategory] = existing.concat(modules[g]);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let category in catmodules) {
|
|
||||||
let categoryHeader = false;
|
|
||||||
// Order through CATEGORIES if present
|
|
||||||
const categories = CATEGORIES[category] || [category];
|
|
||||||
if (categories && categories.length) {
|
|
||||||
for (let n in categories) {
|
|
||||||
const grp = categories[n];
|
|
||||||
// We now have the group and the category. We might not have any modules, though...
|
|
||||||
if (modules[grp]) {
|
|
||||||
// Decide if we need a category header as well as a group header
|
|
||||||
if (categories.length === 1) {
|
|
||||||
// Show category header instead of group header
|
|
||||||
if (m && grp == m.grp) {
|
|
||||||
list.push(<div ref={(elem) => this.groupElem = elem} key={category} className={'select-category upp'}>{translate(category)}</div>);
|
|
||||||
} else {
|
|
||||||
list.push(<div key={category} className={'select-category upp'}>{translate(category)}</div>);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Show category header as well as group header
|
|
||||||
if (!categoryHeader) {
|
|
||||||
list.push(<div key={category} className={'select-category upp'}>{translate(category)}</div>);
|
|
||||||
categoryHeader = true;
|
|
||||||
}
|
|
||||||
if (m && grp == m.grp) {
|
|
||||||
list.push(<div ref={(elem) => this.groupElem = elem} key={grp} className={'select-group cap'}>{translate(grp)}</div>);
|
|
||||||
} else {
|
|
||||||
list.push(<div key={grp} className={'select-group cap'}>{translate(grp)}</div>);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
list.push(buildGroup(grp, modules[grp]));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
list.push(
|
||||||
|
this._buildGroup(
|
||||||
|
m,
|
||||||
|
category,
|
||||||
|
infos,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
fuzzy.push(
|
||||||
|
...infos.map((info) => {
|
||||||
|
const { meta } = info;
|
||||||
|
const mount = meta.mount ? ' ' + translate(meta.mount) : '';
|
||||||
|
return {
|
||||||
|
grp: groupName,
|
||||||
|
cat: categoryName,
|
||||||
|
m: info.proto.Item,
|
||||||
|
name: `${meta.class}${meta.rating}${mount} ${categoryName}`,
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let trackingFocus = false;
|
return { list, currentGroup, fuzzy, trackingFocus: false };
|
||||||
return { list, currentGroup, trackingFocus };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate React Components for Module Group
|
* Generate React Components for Module Group
|
||||||
* @param {Function} translate Translate function
|
|
||||||
* @param {Object} mountedModule Mounted Module
|
* @param {Object} mountedModule Mounted Module
|
||||||
* @param {Function} warningFunc Warning function
|
* @param {String} category Category key
|
||||||
* @param {number} mass Mass
|
|
||||||
* @param {function} onSelect Select/Mount callback
|
|
||||||
* @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(mountedModule, category, modules) {
|
||||||
let prevClass = null, prevRating = null, prevName;
|
const { warning } = this.props;
|
||||||
let elems = [];
|
const ship = mountedModule.getShip();
|
||||||
|
const classMapping = groupBy(modules, (info) => info.meta.class);
|
||||||
|
|
||||||
const sortedModules = modules.sort(this._moduleOrder);
|
const itemsPerClass = Math.max(
|
||||||
|
...Object.values(classMapping).map((l) => l.length),
|
||||||
|
);
|
||||||
|
const itemsPerRow = itemsPerClass <= 2 ? 6 : itemsPerClass;
|
||||||
|
// Nested array of <li> elements; will be flattened before being rendered.
|
||||||
|
// Each sub-array represents one row in the final view.
|
||||||
|
const elems = [[]];
|
||||||
|
|
||||||
|
// Reverse sort for descending order of module class
|
||||||
|
for (const clazz of Object.keys(classMapping).sort().reverse()) {
|
||||||
|
for (let info of sortBy(
|
||||||
|
classMapping[clazz],
|
||||||
|
(info) => info.meta.mount || info.meta.rating,
|
||||||
|
)) {
|
||||||
|
const { meta } = info;
|
||||||
|
const { Item } = info.proto;
|
||||||
|
|
||||||
// Calculate the number of items per class. Used so we don't have long lists with only a few items in each row
|
// Can only be true if shieldgenmaximalmass is defined, i.e. this
|
||||||
const tmp = sortedModules.map((v, i) => v['class']).reduce((count, cls) => { count[cls] = ++count[cls] || 1; return count; }, {});
|
// module must be a shield generator
|
||||||
const itemsPerClass = Math.max.apply(null, Object.keys(tmp).map(key => tmp[key]));
|
let disabled = info.props.shieldgenmaximalmass < ship.readProp('hullmass');
|
||||||
|
if (meta.experimental && !mountedModule.readMeta('experimental')) {
|
||||||
|
disabled =
|
||||||
|
4 <=
|
||||||
|
ship.getHardpoints().filter((m) => m.readMeta('experimental'))
|
||||||
|
.length;
|
||||||
|
}
|
||||||
|
|
||||||
let itemsOnThisRow = 0;
|
// Default event handlers for objects that are disabled
|
||||||
for (let i = 0; i < sortedModules.length; i++) {
|
let eventHandlers = {};
|
||||||
let m = sortedModules[i];
|
if (!disabled) {
|
||||||
let mount = null;
|
const showDiff = this._showDiff.bind(this, mountedModule, info);
|
||||||
let disabled = false;
|
const select = (event) => {
|
||||||
prevName = m.name;
|
this._hideDiff(event);
|
||||||
if (ModuleUtils.isShieldGenerator(m.grp)) {
|
this.props.onSelect(Item);
|
||||||
// Shield generators care about maximum hull mass
|
};
|
||||||
disabled = mass > m.maxmass;
|
|
||||||
} else if (m.maxmass) {
|
eventHandlers = {
|
||||||
// Thrusters care about total mass
|
onMouseEnter: this._over.bind(this, showDiff),
|
||||||
disabled = mass + m.mass > m.maxmass;
|
onTouchStart: this._touchStart.bind(this, showDiff),
|
||||||
|
onTouchEnd: this._touchEnd.bind(this, select),
|
||||||
|
onMouseLeave: this._hideDiff,
|
||||||
|
onClick: select,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const mountSymbol = MOUNT_MAP[meta.mount];
|
||||||
|
const li = (
|
||||||
|
<li key={Item} data-id={Item}
|
||||||
|
ref={Item === mountedModule.getItem() ? (ref) => { this.activeSlotRef = ref; } : undefined}
|
||||||
|
className={cn(meta.type === 'armour' ? 'lc' : 'c', {
|
||||||
|
warning: !disabled && warning && warning(info),
|
||||||
|
active: mountedModule.getItem() === Item,
|
||||||
|
disabled,
|
||||||
|
hardpoint: mountSymbol,
|
||||||
|
})}
|
||||||
|
{...eventHandlers}
|
||||||
|
>{mountSymbol}{meta.type === 'armour' ? Item : `${meta.class}${meta.rating}`}</li>
|
||||||
|
);
|
||||||
|
|
||||||
|
const tail = elems.pop();
|
||||||
|
let newTail = [tail];
|
||||||
|
if (tail.length < itemsPerRow) {
|
||||||
|
// If the row has not grown too long, the new <li> element can be
|
||||||
|
// added to the row itself
|
||||||
|
tail.push(li);
|
||||||
|
} else {
|
||||||
|
// Otherwise, the last row gets a line break element added and this
|
||||||
|
// item is put into a new row
|
||||||
|
tail.push(<br key={elems.length}/>);
|
||||||
|
newTail.push([li]);
|
||||||
|
}
|
||||||
|
elems.push(...newTail);
|
||||||
}
|
}
|
||||||
let active = mountedModule && mountedModule.id === m.id;
|
|
||||||
let classes = cn(m.name ? 'lc' : 'c', {
|
|
||||||
warning: !disabled && warningFunc && warningFunc(m),
|
|
||||||
active,
|
|
||||||
disabled
|
|
||||||
});
|
|
||||||
let eventHandlers;
|
|
||||||
|
|
||||||
if (disabled) {
|
|
||||||
eventHandlers = {
|
|
||||||
onKeyDown: this._keyDown.bind(this, null),
|
|
||||||
onKeyUp: this._keyUp.bind(this, null)
|
|
||||||
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
/**
|
|
||||||
* Get the ids of the first and last <li> elements in the <ul> that are focusable (i.e. are not active or disabled)
|
|
||||||
* Will be used to keep focus inside the <ul> on Tab and Shift-Tab while it is visible
|
|
||||||
*/
|
|
||||||
if (this.firstSlotId == null) this.firstSlotId = sortedModules[i].id;
|
|
||||||
if (active) this.activeSlotId = sortedModules[i].id;
|
|
||||||
this.lastSlotId = sortedModules[i].id;
|
|
||||||
|
|
||||||
let showDiff = this._showDiff.bind(this, mountedModule, m);
|
|
||||||
let select = onSelect.bind(null, m);
|
|
||||||
|
|
||||||
eventHandlers = {
|
|
||||||
onMouseEnter: this._over.bind(this, showDiff),
|
|
||||||
onTouchStart: this._touchStart.bind(this, showDiff),
|
|
||||||
onTouchEnd: this._touchEnd.bind(this, select),
|
|
||||||
onMouseLeave: this._hideDiff,
|
|
||||||
onClick: select,
|
|
||||||
onKeyDown: this._keyDown.bind(this, select),
|
|
||||||
onKeyUp: this._keyUp.bind(this, select)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
switch(m.mount) {
|
|
||||||
case 'F': mount = <MountFixed className={'lg'} />; break;
|
|
||||||
case 'G': mount = <MountGimballed className={'lg'}/>; break;
|
|
||||||
case 'T': mount = <MountTurret className={'lg'}/>; break;
|
|
||||||
}
|
|
||||||
if (m.name && m.name === prevName) {
|
|
||||||
// elems.push(<br key={'b' + m.grp + i} />);
|
|
||||||
itemsOnThisRow = 0;
|
|
||||||
}
|
|
||||||
if (itemsOnThisRow == 6 || i > 0 && sortedModules.length > 3 && itemsPerClass > 2 && m.class != prevClass && (m.rating != prevRating || m.mount)) {
|
|
||||||
elems.push(<br key={'b' + m.grp + i} />);
|
|
||||||
itemsOnThisRow = 0;
|
|
||||||
}
|
|
||||||
let tbIdx = (classes.indexOf('disabled') < 0) ? 0 : undefined;
|
|
||||||
elems.push(
|
|
||||||
<li key={m.id} data-id={m.id} className={classes} {...eventHandlers} tabIndex={tbIdx} ref={slotItem => this.slotItems[m.id] = slotItem}>
|
|
||||||
{mount}
|
|
||||||
{(mount ? ' ' : '') + m.class + m.rating + (m.missile ? '/' + m.missile : '') + (m.name ? ' ' + translate(m.name) : '')}
|
|
||||||
</li>
|
|
||||||
);
|
|
||||||
|
|
||||||
itemsOnThisRow++;
|
|
||||||
prevClass = m.class;
|
|
||||||
prevRating = m.rating;
|
|
||||||
prevName = m.name;
|
|
||||||
}
|
}
|
||||||
return <ul key={'modules' + grp}>{elems}</ul>;
|
|
||||||
|
return <ul key={'ul' + category}>{[].concat(...elems)}</ul>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate tooltip content for the difference between the
|
* Generate tooltip content for the difference between the
|
||||||
* mounted module and the hovered modules
|
* mounted module and the hovered modules
|
||||||
* @param {Object} mm The module mounet currently
|
* @param {Object} mountedModule The module mounted currently
|
||||||
* @param {Object} m The hovered module
|
* @param {Object} hoveringModule The hovered module
|
||||||
* @param {DOMRect} rect DOMRect for target element
|
* @param {DOMRect} rect DOMRect for target element
|
||||||
*/
|
*/
|
||||||
_showDiff(mm, m, rect) {
|
_showDiff(mountedModule, hoveringModule, rect) {
|
||||||
if (this.props.diffDetails) {
|
if (this.props.diffDetails) {
|
||||||
this.touchTimeout = null;
|
this.touchTimeout = null;
|
||||||
this.context.tooltip(this.props.diffDetails(m, mm), rect);
|
// TODO:
|
||||||
|
// this.context.tooltip(
|
||||||
|
// this.props.diffDetails(hoveringModule, mountedModule),
|
||||||
|
// rect,
|
||||||
|
// );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate tooltip content for the difference between the
|
||||||
|
* mounted module and the hovered modules
|
||||||
|
* @returns {React.Component} Search component if available
|
||||||
|
*/
|
||||||
|
_showSearch() {
|
||||||
|
if (this.props.hideSearch) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<FuzzySearch
|
||||||
|
list={this.state.fuzzy}
|
||||||
|
keys={['grp', 'name']}
|
||||||
|
tokenize={true}
|
||||||
|
className={'input'}
|
||||||
|
width={'100%'}
|
||||||
|
style={{ padding: 0 }}
|
||||||
|
onSelect={e => this.props.onSelect.bind(null, e.m)()}
|
||||||
|
resultsTemplate={(props, state, styles, clickHandler) => {
|
||||||
|
return state.results.map((val, i) => {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={i}
|
||||||
|
className={'lc'}
|
||||||
|
onClick={() => clickHandler(i)}
|
||||||
|
>
|
||||||
|
{val.name}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mouse over diff handler
|
* Mouse over diff handler
|
||||||
* @param {Function} showDiff diff tooltip callback
|
* @param {Function} showDiff diff tooltip callback
|
||||||
@@ -375,41 +275,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
|
||||||
@@ -420,68 +285,21 @@ export default class AvailableModulesMenu extends TranslatedComponent {
|
|||||||
this.context.tooltip();
|
this.context.tooltip();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Order two modules suitably for display in module selection
|
|
||||||
* @param {Object} a the first module
|
|
||||||
* @param {Object} b the second module
|
|
||||||
* @return {int} -1 if the first module should go first, 1 if the second module should go first
|
|
||||||
*/
|
|
||||||
_moduleOrder(a, b) {
|
|
||||||
// Named modules go last
|
|
||||||
if (!a.name && b.name) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
if (a.name && !b.name) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
// Class ordered from highest (8) to lowest (1)
|
|
||||||
if (a.class < b.class) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
if (a.class > b.class) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
// Mount type, if applicable
|
|
||||||
if (a.mount && b.mount && a.mount !== b.mount) {
|
|
||||||
if (a.mount === 'F' || (a.mount === 'G' && b.mount === 'T')) {
|
|
||||||
return -1;
|
|
||||||
} else {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Rating ordered from highest (A) to lowest (E)
|
|
||||||
if (a.rating < b.rating) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
if (a.rating > b.rating) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
// Do not attempt to order by name at this point, as that mucks up the order of armour
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Scroll to mounted (if it exists) module group on mount
|
* Scroll to mounted (if it exists) module group on mount
|
||||||
*/
|
*/
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
if (this.groupElem) { // Scroll to currently selected group
|
if (this.activeSlotRef) {
|
||||||
this.node.scrollTop = this.groupElem.offsetTop;
|
this.activeSlotRef.focus();
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Set focus on active or first slot element, if applicable.
|
|
||||||
*/
|
|
||||||
if (this.slotItems[this.activeSlotId]) {
|
|
||||||
this.slotItems[this.activeSlotId].focus();
|
|
||||||
} else if (this.slotItems[this.firstSlotId]) {
|
|
||||||
this.slotItems[this.firstSlotId].focus();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle focus if the component updates
|
* Handle focus if the component updates
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
if(this.props.slotDiv) {
|
if (this.props.slotDiv) {
|
||||||
this.props.slotDiv.focus();
|
this.props.slotDiv.focus();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -502,14 +320,14 @@ export default class AvailableModulesMenu extends TranslatedComponent {
|
|||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div ref={node => this.node = node}
|
<div ref={node => this.node = node}
|
||||||
className={cn('select', this.props.className)}
|
className={cn('select', this.props.className)}
|
||||||
onScroll={this._hideDiff}
|
onScroll={this._hideDiff}
|
||||||
onClick={(e) => e.stopPropagation() }
|
onClick={(e) => e.stopPropagation()}
|
||||||
onContextMenu={stopCtxPropagation}
|
onContextMenu={stopCtxPropagation}
|
||||||
>
|
>
|
||||||
|
{this._showSearch()}
|
||||||
{this.state.list}
|
{this.state.list}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,7 +38,6 @@ function insertLinebreaks(d) {
|
|||||||
* Bar Chart
|
* Bar Chart
|
||||||
*/
|
*/
|
||||||
export default class BarChart extends TranslatedComponent {
|
export default class BarChart extends TranslatedComponent {
|
||||||
|
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
colors: ['#7b6888', '#6b486b', '#3182bd', '#a05d56', '#d0743c'],
|
colors: ['#7b6888', '#6b486b', '#3182bd', '#a05d56', '#d0743c'],
|
||||||
labels: null,
|
labels: null,
|
||||||
|
|||||||
@@ -1,13 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import TranslatedComponent from './TranslatedComponent';
|
import TranslatedComponent from './TranslatedComponent';
|
||||||
import { Ships } from 'coriolis-data/dist';
|
import autoBind from 'auto-bind';
|
||||||
import { nameComparator } from '../utils/SlotFunctions';
|
|
||||||
import { Pip } from './SvgIcons';
|
|
||||||
import LineChart from '../components/LineChart';
|
|
||||||
import Slider from '../components/Slider';
|
|
||||||
import * as ModuleUtils from '../shipyard/ModuleUtils';
|
|
||||||
import Module from '../shipyard/Module';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Boost displays a boost button that toggles bosot
|
* Boost displays a boost button that toggles bosot
|
||||||
@@ -15,8 +9,6 @@ import Module from '../shipyard/Module';
|
|||||||
*/
|
*/
|
||||||
export default class Boost extends TranslatedComponent {
|
export default class Boost extends TranslatedComponent {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
marker: PropTypes.string.isRequired,
|
|
||||||
ship: PropTypes.object.isRequired,
|
|
||||||
boost: PropTypes.bool.isRequired,
|
boost: PropTypes.bool.isRequired,
|
||||||
onChange: PropTypes.func.isRequired
|
onChange: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
@@ -26,12 +18,9 @@ export default class Boost extends TranslatedComponent {
|
|||||||
* @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) {
|
||||||
super(props);
|
super(props);
|
||||||
const { ship, boost } = props;
|
autoBind(this);
|
||||||
|
|
||||||
this._keyDown = this._keyDown.bind(this);
|
|
||||||
this._toggleBoost = this._toggleBoost.bind(this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -77,13 +66,12 @@ export default class Boost extends TranslatedComponent {
|
|||||||
* @return {React.Component} contents
|
* @return {React.Component} contents
|
||||||
*/
|
*/
|
||||||
render() {
|
render() {
|
||||||
const { formats, translate, units } = this.context.language;
|
const { translate } = this.context.language;
|
||||||
const { ship, boost } = this.props;
|
|
||||||
|
|
||||||
// TODO disable if ship cannot boost
|
|
||||||
return (
|
return (
|
||||||
<span id='boost'>
|
<span id='boost'>
|
||||||
<button id='boost' className={boost ? 'selected' : null} onClick={this._toggleBoost}>{translate('boost')}</button>
|
<button id='boost' className={this.props.boost ? 'selected' : null} onClick={this._toggleBoost}>
|
||||||
|
{translate('boost')}
|
||||||
|
</button>
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import TranslatedComponent from './TranslatedComponent';
|
import TranslatedComponent from './TranslatedComponent';
|
||||||
import { Ships } from 'coriolis-data/dist';
|
|
||||||
import Slider from '../components/Slider';
|
import Slider from '../components/Slider';
|
||||||
|
import autoBind from 'auto-bind';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Cargo slider
|
* Cargo slider
|
||||||
@@ -22,8 +22,7 @@ export default class Cargo extends TranslatedComponent {
|
|||||||
*/
|
*/
|
||||||
constructor(props, context) {
|
constructor(props, context) {
|
||||||
super(props);
|
super(props);
|
||||||
|
autoBind(this);
|
||||||
this._cargoChange = this._cargoChange.bind(this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import { outfitURL } from '../utils/UrlGenerators';
|
|||||||
* Comparison Table
|
* Comparison Table
|
||||||
*/
|
*/
|
||||||
export default class ComparisonTable extends TranslatedComponent {
|
export default class ComparisonTable extends TranslatedComponent {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
facets: PropTypes.array.isRequired,
|
facets: PropTypes.array.isRequired,
|
||||||
builds: PropTypes.array.isRequired,
|
builds: PropTypes.array.isRequired,
|
||||||
|
|||||||
@@ -1,23 +1,23 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import cn from 'classnames';
|
import cn from 'classnames';
|
||||||
import { Ships } from 'coriolis-data/dist';
|
|
||||||
import Persist from '../stores/Persist';
|
import Persist from '../stores/Persist';
|
||||||
import Ship from '../shipyard/Ship';
|
import { Factory, Ship } from 'ed-forge';
|
||||||
import { Insurance } from '../shipyard/Constants';
|
import { Insurance } from '../shipyard/Constants';
|
||||||
import { slotName, slotComparator } from '../utils/SlotFunctions';
|
|
||||||
import TranslatedComponent from './TranslatedComponent';
|
import TranslatedComponent from './TranslatedComponent';
|
||||||
import { ShoppingIcon } from '../components/SvgIcons';
|
import { ShoppingIcon } from './SvgIcons';
|
||||||
|
import autoBind from 'auto-bind';
|
||||||
|
import { assign, differenceBy, sortBy, reverse } from 'lodash';
|
||||||
|
import { FUEL_CAPACITY } from 'ed-forge/lib/ship-stats';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Cost Section
|
* Cost Section
|
||||||
*/
|
*/
|
||||||
export default class CostSection extends TranslatedComponent {
|
export default class CostSection extends TranslatedComponent {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
ship: PropTypes.object.isRequired,
|
ship: PropTypes.object.isRequired,
|
||||||
code: PropTypes.string.isRequired,
|
code: PropTypes.string.isRequired,
|
||||||
buildName: PropTypes.string
|
buildName: PropTypes.string,
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -26,71 +26,34 @@ export default class CostSection extends TranslatedComponent {
|
|||||||
*/
|
*/
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this._costsTab = this._costsTab.bind(this);
|
autoBind(this);
|
||||||
this._sortCost = this._sortCost.bind(this);
|
|
||||||
this._sortAmmo = this._sortAmmo.bind(this);
|
|
||||||
this._sortRetrofit = this._sortRetrofit.bind(this);
|
|
||||||
this._buildRetrofitShip = this._buildRetrofitShip.bind(this);
|
|
||||||
this._onBaseRetrofitChange = this._onBaseRetrofitChange.bind(this);
|
|
||||||
this._defaultRetrofitName = this._defaultRetrofitName.bind(this);
|
|
||||||
this._eddbShoppingList = this._eddbShoppingList.bind(this);
|
|
||||||
|
|
||||||
let data = Ships[props.ship.id]; // Retrieve the basic ship properties, slots and defaults
|
|
||||||
let retrofitName = this._defaultRetrofitName(props.ship.id, props.buildName);
|
|
||||||
let retrofitShip = this._buildRetrofitShip(props.ship.id, retrofitName);
|
|
||||||
let shipDiscount = Persist.getShipDiscount();
|
|
||||||
let moduleDiscount = Persist.getModuleDiscount();
|
|
||||||
|
|
||||||
this.props.ship.applyDiscounts(shipDiscount, moduleDiscount);
|
|
||||||
retrofitShip.applyDiscounts(shipDiscount, moduleDiscount);
|
|
||||||
|
|
||||||
|
const { ship, buildName } = props;
|
||||||
|
this.shipType = ship.getShipType();
|
||||||
this.state = {
|
this.state = {
|
||||||
retrofitShip,
|
retrofitName: Persist.hasBuild(ship.getShipType(), buildName) ? buildName : null,
|
||||||
retrofitName,
|
shipDiscount: Persist.getShipDiscount(),
|
||||||
shipDiscount,
|
moduleDiscount: Persist.getModuleDiscount(),
|
||||||
moduleDiscount,
|
|
||||||
insurance: Insurance[Persist.getInsurance()],
|
insurance: Insurance[Persist.getInsurance()],
|
||||||
tab: Persist.getCostTab(),
|
tab: Persist.getCostTab(),
|
||||||
buildOptions: Persist.getBuildsNamesFor(props.ship.id),
|
buildOptions: Persist.getBuildsNamesFor(ship.getShipType()),
|
||||||
ammoPredicate: 'cr',
|
predicate: 'cr',
|
||||||
ammoDesc: true,
|
desc: true,
|
||||||
costPredicate: 'cr',
|
excluded: {},
|
||||||
costDesc: true,
|
|
||||||
retroPredicate: 'cr',
|
|
||||||
retroDesc: true
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a ship instance to base/reference retrofit changes from
|
* Create a ship instance to base/reference retrofit changes from
|
||||||
* @param {string} shipId Ship Id
|
|
||||||
* @param {string} name Build name
|
|
||||||
* @param {Ship} retrofitShip Existing retrofit ship
|
|
||||||
* @return {Ship} Retrofit ship
|
* @return {Ship} Retrofit ship
|
||||||
*/
|
*/
|
||||||
_buildRetrofitShip(shipId, name, retrofitShip) {
|
_buildRetrofitShip() {
|
||||||
let data = Ships[shipId]; // Retrieve the basic ship properties, slots and defaults
|
const { retrofitName } = this.state;
|
||||||
|
if (Persist.hasBuild(this.shipType, retrofitName)) {
|
||||||
if (!retrofitShip) { // Don't create a new instance unless needed
|
return new Ship(Persist.getBuild(this.shipType, retrofitName));
|
||||||
retrofitShip = new Ship(shipId, data.properties, data.slots); // Create a new Ship for retrofit comparison
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Persist.hasBuild(shipId, name)) {
|
|
||||||
retrofitShip.buildFrom(Persist.getBuild(shipId, name)); // Populate modules from existing build
|
|
||||||
} else {
|
} else {
|
||||||
retrofitShip.buildWith(data.defaults); // Populate with default components
|
return Factory.newShip(this.shipType);
|
||||||
}
|
}
|
||||||
return retrofitShip;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the default retrofit build name if it exists
|
|
||||||
* @param {string} shipId Ship Id
|
|
||||||
* @param {string} name Build name
|
|
||||||
* @return {string} Build name or null
|
|
||||||
*/
|
|
||||||
_defaultRetrofitName(shipId, name) {
|
|
||||||
return Persist.hasBuild(shipId, name) ? name : null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -108,9 +71,6 @@ export default class CostSection extends TranslatedComponent {
|
|||||||
_onDiscountChanged() {
|
_onDiscountChanged() {
|
||||||
let shipDiscount = Persist.getShipDiscount();
|
let shipDiscount = Persist.getShipDiscount();
|
||||||
let moduleDiscount = Persist.getModuleDiscount();
|
let moduleDiscount = Persist.getModuleDiscount();
|
||||||
this.props.ship.applyDiscounts(shipDiscount, moduleDiscount);
|
|
||||||
this.state.retrofitShip.applyDiscounts(shipDiscount, moduleDiscount);
|
|
||||||
this._updateRetrofit(this.props.ship, this.state.retrofitShip);
|
|
||||||
this.setState({ shipDiscount, moduleDiscount });
|
this.setState({ shipDiscount, moduleDiscount });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -127,156 +87,33 @@ export default class CostSection extends TranslatedComponent {
|
|||||||
* @param {SyntheticEvent} event Build name to base the retrofit ship on
|
* @param {SyntheticEvent} event Build name to base the retrofit ship on
|
||||||
*/
|
*/
|
||||||
_onBaseRetrofitChange(event) {
|
_onBaseRetrofitChange(event) {
|
||||||
let retrofitName = event.target.value;
|
this.setState({ retrofitName: event.target.value });
|
||||||
let ship = this.props.ship;
|
|
||||||
|
|
||||||
if (retrofitName) {
|
|
||||||
this.state.retrofitShip.buildFrom(Persist.getBuild(ship.id, retrofitName));
|
|
||||||
} else {
|
|
||||||
this.state.retrofitShip.buildWith(Ships[ship.id].defaults); // Retrofit ship becomes stock build
|
|
||||||
}
|
|
||||||
this._updateRetrofit(ship, this.state.retrofitShip);
|
|
||||||
this.setState({ retrofitName });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* On builds changed check to see if the retrofit ship needs
|
* Toggle item cost inclusion
|
||||||
* to be updated
|
* @param {String} key Key of the row to toggle
|
||||||
*/
|
*/
|
||||||
_onBuildsChanged() {
|
_toggleExcluded(key) {
|
||||||
let update = false;
|
let { excluded } = this.state;
|
||||||
let ship = this.props.ship;
|
excluded = assign({}, excluded);
|
||||||
let { retrofitName, retrofitShip } = this.state;
|
const slotExcluded = excluded[key];
|
||||||
|
excluded[key] = (slotExcluded === undefined ? true : !slotExcluded);
|
||||||
if(!Persist.hasBuild(ship.id, retrofitName)) {
|
this.setState({ excluded });
|
||||||
retrofitShip.buildWith(Ships[ship.id].defaults); // Retrofit ship becomes stock build
|
|
||||||
this.setState({ retrofitName: null });
|
|
||||||
update = true;
|
|
||||||
} else if (Persist.getBuild(ship.id, retrofitName) != retrofitShip.toString()) {
|
|
||||||
retrofitShip.buildFrom(Persist.getBuild(ship.id, retrofitName)); // Repopulate modules from saved build
|
|
||||||
update = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (update) { // Update retrofit comparison
|
|
||||||
this._updateRetrofit(ship, retrofitShip);
|
|
||||||
}
|
|
||||||
// Update list of retrofit base build options
|
|
||||||
this.setState({ buildOptions: Persist.getBuildsNamesFor(ship.id) });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Toggle item cost inclusion in overall total
|
* Set list sort predicate
|
||||||
* @param {Object} item Cost item
|
* @param {string} newPredicate sort predicate
|
||||||
*/
|
*/
|
||||||
_toggleCost(item) {
|
_sortBy(newPredicate) {
|
||||||
this.props.ship.setCostIncluded(item, !item.incCost);
|
let { predicate, desc } = this.state;
|
||||||
this.forceUpdate();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
if (newPredicate == predicate) {
|
||||||
* Toggle item cost inclusion in retrofit total
|
desc = !desc;
|
||||||
* @param {Object} item Cost item
|
|
||||||
*/
|
|
||||||
_toggleRetrofitCost(item) {
|
|
||||||
let retrofitTotal = this.state.retrofitTotal;
|
|
||||||
item.retroItem.incCost = !item.retroItem.incCost;
|
|
||||||
retrofitTotal += item.netCost * (item.retroItem.incCost ? 1 : -1);
|
|
||||||
this.setState({ retrofitTotal });
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set cost list sort predicate
|
|
||||||
* @param {string} predicate sort predicate
|
|
||||||
*/
|
|
||||||
_sortCostBy(predicate) {
|
|
||||||
let { costPredicate, costDesc } = this.state;
|
|
||||||
|
|
||||||
if (costPredicate == predicate) {
|
|
||||||
costDesc = !costDesc;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setState({ costPredicate: predicate, costDesc });
|
this.setState({ predicate: newPredicate, desc });
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sort cost list
|
|
||||||
* @param {Ship} ship Ship instance
|
|
||||||
* @param {string} predicate Sort predicate
|
|
||||||
* @param {Boolean} desc Sort descending
|
|
||||||
*/
|
|
||||||
_sortCost(ship, predicate, desc) {
|
|
||||||
let costList = ship.costList;
|
|
||||||
let translate = this.context.language.translate;
|
|
||||||
|
|
||||||
if (predicate == 'm') {
|
|
||||||
costList.sort(slotComparator(translate, null, desc));
|
|
||||||
} else {
|
|
||||||
costList.sort(slotComparator(translate, (a, b) => (a.m.cost || 0) - (b.m.cost || 0), desc));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set ammo list sort predicate
|
|
||||||
* @param {string} predicate sort predicate
|
|
||||||
*/
|
|
||||||
_sortAmmoBy(predicate) {
|
|
||||||
let { ammoPredicate, ammoDesc } = this.state;
|
|
||||||
|
|
||||||
if (ammoPredicate == predicate) {
|
|
||||||
ammoDesc = !ammoDesc;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setState({ ammoPredicate: predicate, ammoDesc });
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sort ammo cost list
|
|
||||||
* @param {Array} ammoCosts Ammo cost list
|
|
||||||
* @param {string} predicate Sort predicate
|
|
||||||
* @param {Boolean} desc Sort descending
|
|
||||||
*/
|
|
||||||
_sortAmmo(ammoCosts, predicate, desc) {
|
|
||||||
let translate = this.context.language.translate;
|
|
||||||
|
|
||||||
if (predicate == 'm') {
|
|
||||||
ammoCosts.sort(slotComparator(translate, null, desc));
|
|
||||||
} else {
|
|
||||||
ammoCosts.sort(slotComparator(translate, (a, b) => a[predicate] - b[predicate], desc));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set retrofit list sort predicate
|
|
||||||
* @param {string} predicate sort predicate
|
|
||||||
*/
|
|
||||||
_sortRetrofitBy(predicate) {
|
|
||||||
let { retroPredicate, retroDesc } = this.state;
|
|
||||||
|
|
||||||
if (retroPredicate == predicate) {
|
|
||||||
retroDesc = !retroDesc;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setState({ retroPredicate: predicate, retroDesc });
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sort retrofit cost list
|
|
||||||
* @param {Array} retrofitCosts Retrofit cost list
|
|
||||||
* @param {string} predicate Sort predicate
|
|
||||||
* @param {Boolean} desc Sort descending
|
|
||||||
*/
|
|
||||||
_sortRetrofit(retrofitCosts, predicate, desc) {
|
|
||||||
let translate = this.context.language.translate;
|
|
||||||
|
|
||||||
if (predicate == 'cr') {
|
|
||||||
retrofitCosts.sort((a, b) => a.netCost - b.netCost);
|
|
||||||
} else {
|
|
||||||
retrofitCosts.sort((a , b) => (a[predicate] ? translate(a[predicate]).toLowerCase() : '').localeCompare(b[predicate] ? translate(b[predicate]).toLowerCase() : ''));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!desc) {
|
|
||||||
retrofitCosts.reverse();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -285,18 +122,34 @@ export default class CostSection extends TranslatedComponent {
|
|||||||
*/
|
*/
|
||||||
_costsTab() {
|
_costsTab() {
|
||||||
let { ship } = this.props;
|
let { ship } = this.props;
|
||||||
let { shipDiscount, moduleDiscount, insurance } = this.state;
|
let {
|
||||||
|
excluded, shipDiscount, moduleDiscount, insurance, desc, predicate
|
||||||
|
} = this.state;
|
||||||
let { translate, formats, units } = this.context.language;
|
let { translate, formats, units } = this.context.language;
|
||||||
let rows = [];
|
let rows = [];
|
||||||
|
|
||||||
for (let i = 0, l = ship.costList.length; i < l; i++) {
|
let modules = sortBy(
|
||||||
let item = ship.costList[i];
|
ship.getModules(),
|
||||||
if (item.m && item.m.cost) {
|
(predicate === 'm' ? (m) => m.getItem() : (m) => m.readMeta('cost'))
|
||||||
let toggle = this._toggleCost.bind(this, item);
|
);
|
||||||
rows.push(<tr key={i} className={cn('highlight', { disabled: !item.incCost })}>
|
if (desc) {
|
||||||
<td className='ptr' style={{ width: '1em' }} onClick={toggle}>{item.m.class + item.m.rating}</td>
|
reverse(modules);
|
||||||
<td className='le ptr shorten cap' onClick={toggle}>{slotName(translate, item)}</td>
|
}
|
||||||
<td className='ri ptr' onClick={toggle}>{formats.int(item.discountedCost)}{units.CR}</td>
|
|
||||||
|
let totalCost = 0;
|
||||||
|
for (let module of modules) {
|
||||||
|
const cost = module.readMeta('cost');
|
||||||
|
const slot = module.getSlot();
|
||||||
|
if (cost) {
|
||||||
|
let toggle = this._toggleExcluded.bind(this, slot);
|
||||||
|
const disabled = excluded[slot];
|
||||||
|
if (!disabled) {
|
||||||
|
totalCost += cost;
|
||||||
|
}
|
||||||
|
rows.push(<tr key={slot} className={cn('highlight', { disabled })}>
|
||||||
|
<td className='ptr' style={{ width: '1em' }} onClick={toggle}>{module.getClassRating()}</td>
|
||||||
|
<td className='le ptr shorten cap' onClick={toggle}>{translate(module.readMeta('type'))}</td>
|
||||||
|
<td className='ri ptr' onClick={toggle}>{formats.int(cost * (1 - moduleDiscount))}{units.CR}</td>
|
||||||
</tr>);
|
</tr>);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -305,23 +158,23 @@ export default class CostSection extends TranslatedComponent {
|
|||||||
<table style={{ width: '100%', borderCollapse: 'collapse' }}>
|
<table style={{ width: '100%', borderCollapse: 'collapse' }}>
|
||||||
<thead>
|
<thead>
|
||||||
<tr className='main'>
|
<tr className='main'>
|
||||||
<th colSpan='2' className='sortable le' onClick={this._sortCostBy.bind(this,'m')}>
|
<th colSpan='2' className='sortable le' onClick={() => this._sortBy('m')}>
|
||||||
{translate('module')}
|
{translate('module')}
|
||||||
{shipDiscount ? <u className='cap optional-hide' style={{ marginLeft: '0.5em' }}>{`[${translate('ship')} -${formats.pct(shipDiscount)}]`}</u> : null}
|
{shipDiscount ? <u className='cap optional-hide' style={{ marginLeft: '0.5em' }}>{`[${translate('ship')} -${formats.pct(shipDiscount)}]`}</u> : null}
|
||||||
{moduleDiscount ? <u className='cap optional-hide' style={{ marginLeft: '0.5em' }}>{`[${translate('modules')} -${formats.pct(moduleDiscount)}]`}</u> : null}
|
{moduleDiscount ? <u className='cap optional-hide' style={{ marginLeft: '0.5em' }}>{`[${translate('modules')} -${formats.pct(moduleDiscount)}]`}</u> : null}
|
||||||
</th>
|
</th>
|
||||||
<th className='sortable le' onClick={this._sortCostBy.bind(this, 'cr')} >{translate('credits')}</th>
|
<th className='sortable le' onClick={() => this._sortBy('cr')} >{translate('credits')}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{rows}
|
{rows}
|
||||||
<tr className='ri'>
|
<tr className='ri'>
|
||||||
<td colSpan='2' className='lbl' >{translate('total')}</td>
|
<td colSpan='2' className='lbl' >{translate('total')}</td>
|
||||||
<td className='val'>{formats.int(ship.totalCost)}{units.CR}</td>
|
<td className='val'>{formats.int(totalCost)}{units.CR}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr className='ri'>
|
<tr className='ri'>
|
||||||
<td colSpan='2' className='lbl'>{translate('insurance')}</td>
|
<td colSpan='2' className='lbl'>{translate('insurance')}</td>
|
||||||
<td className='val'>{formats.int(ship.totalCost * insurance)}{units.CR}</td>
|
<td className='val'>{formats.int(totalCost * insurance)}{units.CR}</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
@@ -332,14 +185,63 @@ export default class CostSection extends TranslatedComponent {
|
|||||||
* Open up a window for EDDB with a shopping list of our retrofit components
|
* Open up a window for EDDB with a shopping list of our retrofit components
|
||||||
*/
|
*/
|
||||||
_eddbShoppingList() {
|
_eddbShoppingList() {
|
||||||
const { retrofitCosts } = this.state;
|
const {} = this.state;
|
||||||
const { ship } = this.props;
|
const { ship } = this.props;
|
||||||
|
|
||||||
// Provide unique list of non-PP module EDDB IDs to buy
|
// Provide unique list of non-PP module EDDB IDs to buy
|
||||||
const modIds = retrofitCosts.filter(item => item.retroItem.incCost && item.buyId && !item.buyPp).map(item => item.buyId).filter((v, i, a) => a.indexOf(v) === i);
|
// const modIds = retrofitCosts.filter(item => item.retroItem.incCost && item.buyId && !item.buyPp).map(item => item.buyId).filter((v, i, a) => a.indexOf(v) === i);
|
||||||
|
|
||||||
// Open up the relevant URL
|
// Open up the relevant URL
|
||||||
window.open('https://eddb.io/station?m=' + modIds.join(','));
|
// TODO:
|
||||||
|
// window.open('https://eddb.io/station?m=' + modIds.join(','));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
_retrofitInfo() {
|
||||||
|
const { ship } = this.props;
|
||||||
|
const { desc, moduleDiscount, predicate, retrofitName, excluded } = this.state;
|
||||||
|
const retrofitShip = this._buildRetrofitShip();
|
||||||
|
|
||||||
|
const currentModules = ship.getModules();
|
||||||
|
const oldModules = retrofitShip.getModules();
|
||||||
|
const buyModules = differenceBy(currentModules, oldModules, (m) => m.getItem());
|
||||||
|
const sellModules = differenceBy(oldModules, currentModules, (m) => m.getItem());
|
||||||
|
|
||||||
|
let modules = [];
|
||||||
|
let totalCost = 0;
|
||||||
|
const addModule = (m, costFactor) => {
|
||||||
|
const key = `${m.getItem()}@${m.getSlot()}`;
|
||||||
|
const cost = costFactor * m.readMeta('cost') * (1 - moduleDiscount);
|
||||||
|
modules.push({
|
||||||
|
key, cost,
|
||||||
|
rating: m.getClassRating(),
|
||||||
|
item: m.readMeta('type'),
|
||||||
|
});
|
||||||
|
if (!excluded[key]) {
|
||||||
|
totalCost += cost;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
for (let m of buyModules) {
|
||||||
|
addModule(m, 1);
|
||||||
|
}
|
||||||
|
for (let m of sellModules) {
|
||||||
|
addModule(m, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
let _sortF = undefined;
|
||||||
|
switch (predicate) {
|
||||||
|
case 'cr': _sortF = (o) => o.cost; break;
|
||||||
|
case 'm':
|
||||||
|
default: _sortF = (o) => o.item; break;
|
||||||
|
};
|
||||||
|
|
||||||
|
modules = sortBy(modules, _sortF);
|
||||||
|
if (desc) {
|
||||||
|
reverse(modules);
|
||||||
|
}
|
||||||
|
return [totalCost, modules];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -347,59 +249,52 @@ export default class CostSection extends TranslatedComponent {
|
|||||||
* @return {React.Component} Tab contents
|
* @return {React.Component} Tab contents
|
||||||
*/
|
*/
|
||||||
_retrofitTab() {
|
_retrofitTab() {
|
||||||
let { retrofitTotal, retrofitCosts, moduleDiscount, retrofitName } = this.state;
|
let { buildOptions, excluded, moduleDiscount, retrofitName } = this.state;
|
||||||
const { termtip, tooltip } = this.context;
|
const { termtip, tooltip } = this.context;
|
||||||
let { translate, formats, units } = this.context.language;
|
let { translate, formats, units } = this.context.language;
|
||||||
let int = formats.int;
|
let int = formats.int;
|
||||||
let rows = [], options = [<option key='stock' value=''>{translate('Stock')}</option>];
|
|
||||||
|
|
||||||
for (let opt of this.state.buildOptions) {
|
|
||||||
options.push(<option key={opt} value={opt}>{opt}</option>);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (retrofitCosts.length) {
|
|
||||||
for (let i = 0, l = retrofitCosts.length; i < l; i++) {
|
|
||||||
let item = retrofitCosts[i];
|
|
||||||
rows.push(<tr key={i} className={cn('highlight', { disabled: !item.retroItem.incCost })} onClick={this._toggleRetrofitCost.bind(this, item)}>
|
|
||||||
<td className='ptr' style={{ width: '1em' }}>{item.sellClassRating}</td>
|
|
||||||
<td className='le ptr shorten cap'>{translate(item.sellName)}</td>
|
|
||||||
<td className='ptr' style={{ width: '1em' }}>{item.buyClassRating}</td>
|
|
||||||
<td className='le ptr shorten cap'>{translate(item.buyName)}</td>
|
|
||||||
<td colSpan='2' className={cn('ri ptr', item.retroItem.incCost ? item.netCost > 0 ? 'warning' : 'secondary-disabled' : 'disabled')}>{int(item.netCost)}{units.CR}</td>
|
|
||||||
</tr>);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
rows = <tr><td colSpan='7' style={{ padding: '3em 0' }}>{translate('PHRASE_NO_RETROCH')}</td></tr>;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
const [retrofitTotal, retrofitInfo] = this._retrofitInfo();
|
||||||
return <div>
|
return <div>
|
||||||
<div className='scroll-x'>
|
<div className='scroll-x'>
|
||||||
<table style={{ width: '100%' }}>
|
<table style={{ width: '100%' }}>
|
||||||
<thead>
|
<thead>
|
||||||
<tr className='main'>
|
<tr className='main'>
|
||||||
<th colSpan='2' className='sortable le' onClick={this._sortRetrofitBy.bind(this, 'sellName')}>{translate('sell')}</th>
|
<th colSpan='2' className='sortable le' onClick={() => this._sortBy('m')}>{translate('module')}</th>
|
||||||
<th colSpan='2' className='sortable le' onClick={this._sortRetrofitBy.bind(this, 'buyName')}>{translate('buy')}</th>
|
<th colSpan='2' className='sortable le' onClick={() => this._sortBy('cr')}>
|
||||||
<th colSpan='2' className='sortable le' onClick={this._sortRetrofitBy.bind(this, 'cr')}>
|
|
||||||
{translate('net cost')}
|
{translate('net cost')}
|
||||||
{moduleDiscount ? <u className='cap optional-hide' style={{ marginLeft: '0.5em' }}>{`[${translate('modules')} -${formats.pct(moduleDiscount)}]`}</u> : null}
|
{moduleDiscount ? <u className='cap optional-hide' style={{ marginLeft: '0.5em' }}>{`[${translate('modules')} -${formats.pct(moduleDiscount)}]`}</u> : null}
|
||||||
</th>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{rows}
|
{retrofitInfo.length ?
|
||||||
|
retrofitInfo.map((info) => (
|
||||||
|
<tr key={info.key} className={cn('highlight', { disabled: excluded[info.key] })}
|
||||||
|
onClick={() => this._toggleExcluded(info.key)}>
|
||||||
|
<td className='ptr' style={{ width: '1em' }}>{info.rating}</td>
|
||||||
|
<td className='le ptr shorten cap'>{translate(info.item)}</td>
|
||||||
|
<td colSpan="2" className={cn('ri ptr', excluded[info.key] ? 'disabled' : (info.cost < 0 ? 'secondary-disabled' : 'warning'))}>
|
||||||
|
{int(info.cost)}{units.CR}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
)) : (
|
||||||
|
<tr><td colSpan='7' style={{ padding: '3em 0' }}>{translate('PHRASE_NO_RETROCH')}</td></tr>
|
||||||
|
)}
|
||||||
<tr className='ri'>
|
<tr className='ri'>
|
||||||
<td className='lbl' ><button onClick={this._eddbShoppingList} onMouseOver={termtip.bind(null, 'PHRASE_REFIT_SHOPPING_LIST')} onMouseOut={tooltip.bind(null, null)}><ShoppingIcon className='lg' style={{ fill: 'black' }}/></button></td>
|
<td className='lbl' ><button onClick={this._eddbShoppingList} onMouseOver={termtip.bind(null, 'PHRASE_REFIT_SHOPPING_LIST')} onMouseOut={tooltip.bind(null, null)}><ShoppingIcon className='lg' style={{ fill: 'black' }}/></button></td>
|
||||||
<td colSpan='3' className='lbl' >{translate('cost')}</td>
|
<td className='lbl' >{translate('cost')}</td>
|
||||||
<td colSpan='2' className={cn('val', retrofitTotal > 0 ? 'warning' : 'secondary-disabled')} style={{ borderBottom:'none' }}>
|
<td colSpan='2' className={cn('val', retrofitTotal > 0 ? 'warning' : 'secondary-disabled')} style={{ borderBottom:'none' }}>
|
||||||
{int(retrofitTotal)}{units.CR}
|
{int(retrofitTotal)}{units.CR}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr className='ri'>
|
<tr className='ri'>
|
||||||
<td colSpan='4' className='lbl cap' >{translate('retrofit from')}</td>
|
<td colSpan='2' className='lbl cap' >{translate('retrofit from')}</td>
|
||||||
<td className='val cen' style={{ borderRight: 'none', width: '1em' }}><u className='primary-disabled'>▾</u></td>
|
<td className='val cen' style={{ borderRight: 'none', width: '1em' }}><u className='primary-disabled'>▾</u></td>
|
||||||
<td className='val' style={{ borderLeft:'none', padding: 0 }}>
|
<td className='val' style={{ borderLeft:'none', padding: 0 }}>
|
||||||
<select style={{ width: '100%', padding: 0 }} value={retrofitName || translate('Stock')} onChange={this._onBaseRetrofitChange}>
|
<select style={{ width: '100%', padding: 0 }} value={retrofitName || translate('Stock')} onChange={this._onBaseRetrofitChange}>
|
||||||
{options}
|
<option key='stock' value=''>{translate('Stock')}</option>
|
||||||
|
{buildOptions.map((opt) => <option key={opt} value={opt}>{opt}</option>)}
|
||||||
</select>
|
</select>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -409,63 +304,50 @@ export default class CostSection extends TranslatedComponent {
|
|||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update retrofit costs
|
*
|
||||||
* @param {Ship} ship Ship instance
|
* @param {*} modules
|
||||||
* @param {Ship} retrofitShip Retrofit Ship instance
|
|
||||||
*/
|
*/
|
||||||
_updateRetrofit(ship, retrofitShip) {
|
_ammoInfo() {
|
||||||
let retrofitCosts = [];
|
const { ship } = this.props;
|
||||||
let retrofitTotal = 0, i, l, item;
|
const { desc, predicate } = this.state;
|
||||||
|
|
||||||
if (ship.bulkheads.m.index != retrofitShip.bulkheads.m.index) {
|
let info = [{
|
||||||
item = {
|
key: 'fuel',
|
||||||
buyClassRating: ship.bulkheads.m.class + ship.bulkheads.m.rating,
|
item: 'Fuel',
|
||||||
buyId: ship.bulkheads.m.eddbID,
|
qty: ship.get(FUEL_CAPACITY),
|
||||||
buyPp: ship.bulkheads.m.pp,
|
unitCost: 50,
|
||||||
buyName: ship.bulkheads.m.name,
|
cost: 50 * ship.get(FUEL_CAPACITY),
|
||||||
sellClassRating: retrofitShip.bulkheads.m.class + retrofitShip.bulkheads.m.rating,
|
}];
|
||||||
sellName: retrofitShip.bulkheads.m.name,
|
for (let m of ship.getModules()) {
|
||||||
netCost: ship.bulkheads.discountedCost - retrofitShip.bulkheads.discountedCost,
|
const rebuilds = m.get('bays') * m.get('rebuildsperbay');
|
||||||
retroItem: retrofitShip.bulkheads
|
const ammo = (m.get('ammomaximum') + m.get('ammoclipsize')) || rebuilds;
|
||||||
};
|
if (ammo) {
|
||||||
retrofitCosts.push(item);
|
const unitCost = m.readMeta('ammocost');
|
||||||
if (retrofitShip.bulkheads.incCost) {
|
info.push({
|
||||||
retrofitTotal += item.netCost;
|
key: `restock_${m.getSlot()}`,
|
||||||
|
rating: m.getClassRating(),
|
||||||
|
item: m.readMeta('type'),
|
||||||
|
qty: ammo,
|
||||||
|
unitCost, cost: unitCost * ammo,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (let g in { standard: 1, internal: 1, hardpoints: 1 }) {
|
let _sortF = undefined;
|
||||||
let retroSlotGroup = retrofitShip[g];
|
switch (predicate) {
|
||||||
let slotGroup = ship[g];
|
case 'cr': _sortF = (o) => o.cost; break;
|
||||||
for (i = 0, l = slotGroup.length; i < l; i++) {
|
case 'qty': _sortF = (o) => o.qty; break;
|
||||||
const modId = slotGroup[i].m ? slotGroup[i].m.eddbID : null;
|
case 'cost': _sortF = (o) => o.unitCost; break;
|
||||||
const retroModId = retroSlotGroup[i].m ? retroSlotGroup[i].m.eddbID : null;
|
case 'm':
|
||||||
if (modId != retroModId) {
|
default: _sortF = (o) => o.item;
|
||||||
item = { netCost: 0, retroItem: retroSlotGroup[i] };
|
}
|
||||||
if (slotGroup[i].m) {
|
info = sortBy(info, _sortF);
|
||||||
item.buyId = slotGroup[i].m.eddbID,
|
if (desc) {
|
||||||
item.buyPp = slotGroup[i].m.pp,
|
reverse(info);
|
||||||
item.buyName = slotGroup[i].m.name || slotGroup[i].m.grp;
|
|
||||||
item.buyClassRating = slotGroup[i].m.class + slotGroup[i].m.rating;
|
|
||||||
item.netCost = slotGroup[i].discountedCost;
|
|
||||||
}
|
|
||||||
if (retroSlotGroup[i].m) {
|
|
||||||
item.sellName = retroSlotGroup[i].m.name || retroSlotGroup[i].m.grp;
|
|
||||||
item.sellClassRating = retroSlotGroup[i].m.class + retroSlotGroup[i].m.rating;
|
|
||||||
item.netCost -= retroSlotGroup[i].discountedCost;
|
|
||||||
}
|
|
||||||
retrofitCosts.push(item);
|
|
||||||
if (retroSlotGroup[i].incCost) {
|
|
||||||
retrofitTotal += item.netCost;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setState({ retrofitCosts, retrofitTotal });
|
return info;
|
||||||
this._sortRetrofit(retrofitCosts, this.state.retroPredicate, this.state.retroDesc);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -473,20 +355,24 @@ export default class CostSection extends TranslatedComponent {
|
|||||||
* @return {React.Component} Tab contents
|
* @return {React.Component} Tab contents
|
||||||
*/
|
*/
|
||||||
_ammoTab() {
|
_ammoTab() {
|
||||||
let { ammoTotal, ammoCosts } = this.state;
|
const { excluded } = this.state;
|
||||||
let { translate, formats, units } = this.context.language;
|
const { translate, formats, units } = this.context.language;
|
||||||
let int = formats.int;
|
const int = formats.int;
|
||||||
let rows = [];
|
const rows = [];
|
||||||
|
|
||||||
for (let i = 0, l = ammoCosts.length; i < l; i++) {
|
const ammoInfo = this._ammoInfo();
|
||||||
let item = ammoCosts[i];
|
let total = 0;
|
||||||
rows.push(<tr key={i} className='highlight'>
|
for (let i of ammoInfo) {
|
||||||
<td style={{ width: '1em' }}>{item.m.class + item.m.rating}</td>
|
const disabled = excluded[i.key];
|
||||||
<td className='le shorten cap'>{slotName(translate, item)}</td>
|
rows.push(<tr key={i.key} onClick={() => this._toggleExcluded(i.key)}
|
||||||
<td className='ri'>{int(item.max)}</td>
|
className={cn('highlight', { disabled })}>
|
||||||
<td className='ri'>{int(item.cost)}{units.CR}</td>
|
<td style={{ width: '1em' }}>{i.rating}</td>
|
||||||
<td className='ri'>{int(item.total)}{units.CR}</td>
|
<td className='le shorten cap'>{translate(i.item)}</td>
|
||||||
|
<td className='ri'>{int(i.qty)}</td>
|
||||||
|
<td className='ri'>{int(i.unitCost)}{units.CR}</td>
|
||||||
|
<td className='ri'>{int(i.cost)}{units.CR}</td>
|
||||||
</tr>);
|
</tr>);
|
||||||
|
total += disabled ? 0 : i.cost;
|
||||||
}
|
}
|
||||||
|
|
||||||
return <div>
|
return <div>
|
||||||
@@ -494,17 +380,17 @@ export default class CostSection extends TranslatedComponent {
|
|||||||
<table style={{ width: '100%' }}>
|
<table style={{ width: '100%' }}>
|
||||||
<thead>
|
<thead>
|
||||||
<tr className='main'>
|
<tr className='main'>
|
||||||
<th colSpan='2' className='sortable le' onClick={this._sortAmmoBy.bind(this, 'm')} >{translate('module')}</th>
|
<th colSpan='2' className='sortable le' onClick={() => this._sortBy('m')}>{translate('module')}</th>
|
||||||
<th colSpan='1' className='sortable le' onClick={this._sortAmmoBy.bind(this, 'max')} >{translate('qty')}</th>
|
<th colSpan='1' className='sortable le' onClick={() => this._sortBy('qty')}>{translate('qty')}</th>
|
||||||
<th colSpan='1' className='sortable le' onClick={this._sortAmmoBy.bind(this, 'cost')} >{translate('unit cost')}</th>
|
<th colSpan='1' className='sortable le' onClick={() => this._sortBy('cost')}>{translate('unit cost')}</th>
|
||||||
<th className='sortable le' onClick={this._sortAmmoBy.bind(this, 'total')}>{translate('subtotal')}</th>
|
<th className='sortable le' onClick={() => this._sortBy('cr')}>{translate('subtotal')}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{rows}
|
{rows}
|
||||||
<tr className='ri'>
|
<tr className='ri'>
|
||||||
<td colSpan='4' className='lbl' >{translate('total')}</td>
|
<td colSpan='4' className='lbl' >{translate('total')}</td>
|
||||||
<td className='val'>{int(ammoTotal)}{units.CR}</td>
|
<td className='val'>{int(total)}{units.CR}</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
@@ -512,103 +398,6 @@ export default class CostSection extends TranslatedComponent {
|
|||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Recalculate all ammo costs
|
|
||||||
* @param {Ship} ship Ship instance
|
|
||||||
*/
|
|
||||||
_updateAmmoCosts(ship) {
|
|
||||||
let ammoCosts = [], ammoTotal = 0, item, q, limpets = 0, srvs = 0, scoop = false;
|
|
||||||
|
|
||||||
for (let g in { standard: 1, internal: 1, hardpoints: 1 }) {
|
|
||||||
let slotGroup = ship[g];
|
|
||||||
for (let i = 0, l = slotGroup.length; i < l; i++) {
|
|
||||||
if (slotGroup[i].m) {
|
|
||||||
// Special cases needed for SCB, AFMU, and limpet controllers since they don't use standard ammo/clip
|
|
||||||
q = 0;
|
|
||||||
switch (slotGroup[i].m.grp) {
|
|
||||||
case 'fs': // Skip fuel calculation if scoop present
|
|
||||||
scoop = true;
|
|
||||||
break;
|
|
||||||
case 'scb':
|
|
||||||
q = slotGroup[i].m.getAmmo() + 1;
|
|
||||||
break;
|
|
||||||
case 'am':
|
|
||||||
q = slotGroup[i].m.getAmmo();
|
|
||||||
break;
|
|
||||||
case 'pv':
|
|
||||||
srvs += slotGroup[i].m.getBays();
|
|
||||||
break;
|
|
||||||
case 'fx': case 'hb': case 'cc': case 'pc':
|
|
||||||
limpets = ship.cargoCapacity;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
q = slotGroup[i].m.getClip() + slotGroup[i].m.getAmmo();
|
|
||||||
}
|
|
||||||
// Calculate ammo costs only if a cost is specified
|
|
||||||
if (slotGroup[i].m.ammocost > 0) {
|
|
||||||
item = {
|
|
||||||
m: slotGroup[i].m,
|
|
||||||
max: q,
|
|
||||||
cost: slotGroup[i].m.ammocost,
|
|
||||||
total: q * slotGroup[i].m.ammocost
|
|
||||||
};
|
|
||||||
ammoCosts.push(item);
|
|
||||||
ammoTotal += item.total;
|
|
||||||
}
|
|
||||||
// Add fighters
|
|
||||||
if (slotGroup[i].m.grp === 'fh') {
|
|
||||||
item = {
|
|
||||||
m: slotGroup[i].m,
|
|
||||||
max: slotGroup[i].m.getRebuildsPerBay() * slotGroup[i].m.getBays(),
|
|
||||||
cost: slotGroup[i].m.fightercost,
|
|
||||||
total: slotGroup[i].m.getRebuildsPerBay() * slotGroup[i].m.getBays() * slotGroup[i].m.fightercost
|
|
||||||
};
|
|
||||||
ammoCosts.push(item);
|
|
||||||
ammoTotal += item.total;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Limpets if controllers exist and cargo space available
|
|
||||||
if (limpets > 0) {
|
|
||||||
item = {
|
|
||||||
m: { name: 'limpets', class: '', rating: '' },
|
|
||||||
max: ship.cargoCapacity,
|
|
||||||
cost: 101,
|
|
||||||
total: ship.cargoCapacity * 101
|
|
||||||
};
|
|
||||||
ammoCosts.push(item);
|
|
||||||
ammoTotal += item.total;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (srvs > 0) {
|
|
||||||
item = {
|
|
||||||
m: { name: 'SRVs', class: '', rating: '' },
|
|
||||||
max: srvs,
|
|
||||||
cost: 1030,
|
|
||||||
total: srvs * 1030
|
|
||||||
};
|
|
||||||
ammoCosts.push(item);
|
|
||||||
ammoTotal += item.total;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Calculate refuel costs if no scoop present
|
|
||||||
if (!scoop) {
|
|
||||||
item = {
|
|
||||||
m: { name: 'fuel', class: '', rating: '' },
|
|
||||||
max: ship.fuelCapacity,
|
|
||||||
cost: 50,
|
|
||||||
total: ship.fuelCapacity * 50
|
|
||||||
};
|
|
||||||
ammoCosts.push(item);
|
|
||||||
ammoTotal += item.total;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setState({ ammoTotal, ammoCosts });
|
|
||||||
this._sortAmmo(ammoCosts, this.state.ammoPredicate, this.state.ammoDesc);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add listeners on mount and update costs
|
* Add listeners on mount and update costs
|
||||||
*/
|
*/
|
||||||
@@ -616,64 +405,7 @@ export default class CostSection extends TranslatedComponent {
|
|||||||
this.listeners = [
|
this.listeners = [
|
||||||
Persist.addListener('discounts', this._onDiscountChanged.bind(this)),
|
Persist.addListener('discounts', this._onDiscountChanged.bind(this)),
|
||||||
Persist.addListener('insurance', this._onInsuranceChanged.bind(this)),
|
Persist.addListener('insurance', this._onInsuranceChanged.bind(this)),
|
||||||
Persist.addListener('builds', this._onBuildsChanged.bind(this)),
|
|
||||||
];
|
];
|
||||||
this._updateAmmoCosts(this.props.ship);
|
|
||||||
this._updateRetrofit(this.props.ship, this.state.retrofitShip);
|
|
||||||
this._sortCost(this.props.ship);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update state based on property and context changes
|
|
||||||
* @param {Object} nextProps Incoming/Next properties
|
|
||||||
* @param {Object} nextContext Incoming/Next context
|
|
||||||
*/
|
|
||||||
componentWillReceiveProps(nextProps, nextContext) {
|
|
||||||
let retrofitShip = this.state.retrofitShip;
|
|
||||||
|
|
||||||
if (nextProps.ship != this.props.ship) { // Ship has changed
|
|
||||||
let nextId = nextProps.ship.id;
|
|
||||||
let retrofitName = this._defaultRetrofitName(nextId, nextProps.buildName);
|
|
||||||
retrofitShip = this._buildRetrofitShip(nextId, retrofitName, nextId == this.props.ship.id ? retrofitShip : null);
|
|
||||||
this.setState({
|
|
||||||
retrofitShip,
|
|
||||||
retrofitName,
|
|
||||||
buildOptions: Persist.getBuildsNamesFor(nextId)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (nextProps.ship != this.props.ship || nextProps.code != this.props.code) {
|
|
||||||
nextProps.ship.applyDiscounts(Persist.getShipDiscount(), Persist.getModuleDiscount());
|
|
||||||
this._updateAmmoCosts(nextProps.ship);
|
|
||||||
this._updateRetrofit(nextProps.ship, retrofitShip);
|
|
||||||
this._sortCost(nextProps.ship);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sort lists before render
|
|
||||||
* @param {Object} nextProps Incoming/Next properties
|
|
||||||
* @param {Object} nextState Incoming/Next state
|
|
||||||
*/
|
|
||||||
componentWillUpdate(nextProps, nextState) {
|
|
||||||
let state = this.state;
|
|
||||||
|
|
||||||
switch (nextState.tab) {
|
|
||||||
case 'ammo':
|
|
||||||
if (state.ammoPredicate != nextState.ammoPredicate || state.ammoDesc != nextState.ammoDesc) {
|
|
||||||
this._sortAmmo(nextState.ammoCosts, nextState.ammoPredicate, nextState.ammoDesc);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'retrofit':
|
|
||||||
if (state.retroPredicate != nextState.retroPredicate || state.retroDesc != nextState.retroDesc) {
|
|
||||||
this._sortRetrofit(nextState.retrofitCosts, nextState.retroPredicate, nextState.retroDesc);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
if (state.costPredicate != nextState.costPredicate || state.costDesc != nextState.costDesc) {
|
|
||||||
this._sortCost(nextProps.ship, nextState.costPredicate, nextState.costDesc);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import TranslatedComponent from './TranslatedComponent';
|
|||||||
import * as Calc from '../shipyard/Calculations';
|
import * as Calc from '../shipyard/Calculations';
|
||||||
import PieChart from './PieChart';
|
import PieChart from './PieChart';
|
||||||
import VerticalBarChart from './VerticalBarChart';
|
import VerticalBarChart from './VerticalBarChart';
|
||||||
|
import autoBind from 'auto-bind';
|
||||||
|
import { ARMOUR_METRICS, MODULE_PROTECTION_METRICS, SHIELD_METRICS } from 'ed-forge/lib/ship-stats';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Defence information
|
* Defence information
|
||||||
@@ -15,12 +17,10 @@ import VerticalBarChart from './VerticalBarChart';
|
|||||||
*/
|
*/
|
||||||
export default class Defence extends TranslatedComponent {
|
export default class Defence extends TranslatedComponent {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
marker: PropTypes.string.isRequired,
|
code: PropTypes.string.isRequired,
|
||||||
ship: PropTypes.object.isRequired,
|
ship: PropTypes.object.isRequired,
|
||||||
opponent: PropTypes.object.isRequired,
|
opponent: PropTypes.object.isRequired,
|
||||||
engagementrange: PropTypes.number.isRequired,
|
engagementRange: PropTypes.number.isRequired,
|
||||||
sys: PropTypes.number.isRequired,
|
|
||||||
opponentWep: PropTypes.number.isRequired
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -29,22 +29,7 @@ export default class Defence extends TranslatedComponent {
|
|||||||
*/
|
*/
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
autoBind(this);
|
||||||
const { shield, armour, shielddamage, armourdamage } = Calc.defenceMetrics(props.ship, props.opponent, props.sys, props.opponentWep, props.engagementrange);
|
|
||||||
this.state = { shield, armour, shielddamage, armourdamage };
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update the state if our properties change
|
|
||||||
* @param {Object} nextProps Incoming/Next properties
|
|
||||||
* @return {boolean} Returns true if the component should be rerendered
|
|
||||||
*/
|
|
||||||
componentWillReceiveProps(nextProps) {
|
|
||||||
if (this.props.marker != nextProps.marker || this.props.sys != nextProps.sys) {
|
|
||||||
const { shield, armour, shielddamage, armourdamage } = Calc.defenceMetrics(nextProps.ship, nextProps.opponent, nextProps.sys, nextProps.opponentWep, nextProps.engagementrange);
|
|
||||||
this.setState({ shield, armour, shielddamage, armourdamage });
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -52,209 +37,126 @@ export default class Defence extends TranslatedComponent {
|
|||||||
* @return {React.Component} contents
|
* @return {React.Component} contents
|
||||||
*/
|
*/
|
||||||
render() {
|
render() {
|
||||||
const { ship, sys, opponentWep } = this.props;
|
const { ship } = this.props;
|
||||||
const { language, tooltip, termtip } = this.context;
|
const { language, tooltip, termtip } = this.context;
|
||||||
const { formats, translate, units } = language;
|
const { formats, translate, units } = language;
|
||||||
const { shield, armour, shielddamage, armourdamage } = this.state;
|
|
||||||
|
|
||||||
const pd = ship.standard[4].m;
|
const shields = ship.get(SHIELD_METRICS);
|
||||||
|
|
||||||
const shieldSourcesData = [];
|
// Data for pie chart (absolute MJ)
|
||||||
const effectiveShieldData = [];
|
const shieldSourcesData = [
|
||||||
const shieldDamageTakenData = [];
|
'byBoosters', 'byGenerator', 'byReinforcements', 'bySCBs',
|
||||||
const shieldSourcesTt = [];
|
].map((key) => { return { label: key, value: Math.round(shields[key]) }; });
|
||||||
const shieldDamageTakenAbsoluteTt = [];
|
|
||||||
const shieldDamageTakenExplosiveTt = [];
|
|
||||||
const shieldDamageTakenKineticTt = [];
|
|
||||||
const shieldDamageTakenThermalTt = [];
|
|
||||||
const effectiveShieldAbsoluteTt = [];
|
|
||||||
const effectiveShieldExplosiveTt = [];
|
|
||||||
const effectiveShieldKineticTt = [];
|
|
||||||
const effectiveShieldThermalTt = [];
|
|
||||||
let maxEffectiveShield = 0;
|
|
||||||
if (shield.total) {
|
|
||||||
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.cells), label: translate('cells') });
|
|
||||||
shieldSourcesData.push({ value: Math.round(shield.addition), label: translate('shield addition') });
|
|
||||||
|
|
||||||
if (shield.generator > 0) {
|
// Data for tooltip
|
||||||
shieldSourcesTt.push(<div key='generator'>{translate('generator') + ' ' + formats.int(shield.generator)}{units.MJ}</div>);
|
const shieldSourcesTt = shieldSourcesData.map((o) => {
|
||||||
effectiveShieldAbsoluteTt.push(<div key='generator'>{translate('generator') + ' ' + formats.int(shield.generator)}{units.MJ}</div>);
|
let { label, value } = o;
|
||||||
effectiveShieldExplosiveTt.push(<div key='generator'>{translate('generator') + ' ' + formats.int(shield.generator)}{units.MJ}</div>);
|
return <div key={label}>
|
||||||
effectiveShieldKineticTt.push(<div key='generator'>{translate('generator') + ' ' + formats.int(shield.generator)}{units.MJ}</div>);
|
{translate(label)} {formats.int(value)}{units.MJ}
|
||||||
effectiveShieldThermalTt.push(<div key='generator'>{translate('generator') + ' ' + formats.int(shield.generator)}{units.MJ}</div>);
|
</div>;
|
||||||
if (shield.boosters > 0) {
|
});
|
||||||
shieldSourcesTt.push(<div key='boosters'>{translate('boosters') + ' ' + formats.int(shield.boosters)}{units.MJ}</div>);
|
|
||||||
effectiveShieldAbsoluteTt.push(<div key='boosters'>{translate('boosters') + ' ' + formats.int(shield.boosters)}{units.MJ}</div>);
|
|
||||||
effectiveShieldExplosiveTt.push(<div key='boosters'>{translate('boosters') + ' ' + formats.int(shield.boosters)}{units.MJ}</div>);
|
|
||||||
effectiveShieldKineticTt.push(<div key='boosters'>{translate('boosters') + ' ' + formats.int(shield.boosters)}{units.MJ}</div>);
|
|
||||||
effectiveShieldThermalTt.push(<div key='boosters'>{translate('boosters') + ' ' + formats.int(shield.boosters)}{units.MJ}</div>);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (shield.cells > 0) {
|
// Shield resistances
|
||||||
shieldSourcesTt.push(<div key='cells'>{translate('cells') + ' ' + formats.int(shield.cells)}{units.MJ}</div>);
|
const shieldDamageTakenData = [
|
||||||
effectiveShieldAbsoluteTt.push(<div key='cells'>{translate('cells') + ' ' + formats.int(shield.cells)}{units.MJ}</div>);
|
'absolute', 'explosive', 'kinetic', 'thermal',
|
||||||
effectiveShieldExplosiveTt.push(<div key='cells'>{translate('cells') + ' ' + formats.int(shield.cells)}{units.MJ}</div>);
|
].map((label) => {
|
||||||
effectiveShieldKineticTt.push(<div key='cells'>{translate('cells') + ' ' + formats.int(shield.cells)}{units.MJ}</div>);
|
const dmgMult = shields[label];
|
||||||
effectiveShieldThermalTt.push(<div key='cells'>{translate('cells') + ' ' + formats.int(shield.cells)}{units.MJ}</div>);
|
const tooltip = ['byBoosters', 'byGenerator', 'bySys'].map(
|
||||||
}
|
(label) => <div key={label}>
|
||||||
|
{translate(label)} {formats.pct1(dmgMult[label])}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
return { label, value: Math.round(100 * dmgMult.withSys), tooltip };
|
||||||
|
});
|
||||||
|
|
||||||
// Add effective shield from resistances
|
// Effective MJ
|
||||||
const rawMj = shield.generator + shield.boosters + shield.cells;
|
const effectiveShieldData = [
|
||||||
const explosiveMj = rawMj / (shield.explosive.base) - rawMj;
|
'absolute', 'explosive', 'kinetic', 'thermal'
|
||||||
if (explosiveMj != 0) effectiveShieldExplosiveTt.push(<div key='resistance'>{translate('resistance') + ' ' + formats.int(explosiveMj)}{units.MJ}</div>);
|
].map((label) => {
|
||||||
const kineticMj = rawMj / (shield.kinetic.base) - rawMj;
|
const dmgMult = shields[label];
|
||||||
if (kineticMj != 0) effectiveShieldKineticTt.push(<div key='resistance'>{translate('resistance') + ' ' + formats.int(kineticMj)}{units.MJ}</div>);
|
const raw = shields.withSCBs;
|
||||||
const thermalMj = rawMj / (shield.thermal.base) - rawMj;
|
const tooltip = ['byBoosters', 'byGenerator', 'bySys'].map(
|
||||||
if (thermalMj != 0) effectiveShieldThermalTt.push(<div key='resistance'>{translate('resistance') + ' ' + formats.int(thermalMj)}{units.MJ}</div>);
|
(label) => <div key={label}>
|
||||||
|
{translate(label)} {formats.int(raw * dmgMult[label])}{units.MJ}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
return { label, value: Math.round(dmgMult.withSys * raw), tooltip };
|
||||||
|
});
|
||||||
|
const maxEffectiveShield = Math.max(...effectiveShieldData.map((o) => o.value));
|
||||||
|
|
||||||
// Add effective shield from power distributor SYS pips
|
const armour = ship.get(ARMOUR_METRICS);
|
||||||
if (shield.absolute.sys != 1) {
|
const moduleProtection = ship.get(MODULE_PROTECTION_METRICS);
|
||||||
effectiveShieldAbsoluteTt.push(<div key='power distributor'>{translate('power distributor') + ' ' + formats.int(rawMj / shield.absolute.total - 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>);
|
|
||||||
effectiveShieldKineticTt.push(<div key='power distributor'>{translate('power distributor') + ' ' + formats.int(rawMj / shield.kinetic.total - rawMj / shield.kinetic.base)}{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>);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
shieldDamageTakenAbsoluteTt.push(<div key='generator'>{translate('generator') + ' ' + formats.pct1(shield.absolute.generator)}</div>);
|
// Data for pie chart (absolute HP)
|
||||||
shieldDamageTakenAbsoluteTt.push(<div key='boosters'>{translate('boosters') + ' ' + formats.pct1(shield.absolute.boosters)}</div>);
|
const armourSourcesData = ['base', 'byAlloys', 'byHRPs',].map(
|
||||||
shieldDamageTakenAbsoluteTt.push(<div key='power distributor'>{translate('power distributor') + ' ' + formats.pct1(shield.absolute.sys)}</div>);
|
(key) => { return { label: key, value: Math.round(armour[key]) }; }
|
||||||
|
);
|
||||||
|
|
||||||
shieldDamageTakenExplosiveTt.push(<div key='generator'>{translate('generator') + ' ' + formats.pct1(shield.explosive.generator)}</div>);
|
// Data for tooltip
|
||||||
shieldDamageTakenExplosiveTt.push(<div key='boosters'>{translate('boosters') + ' ' + formats.pct1(shield.explosive.boosters)}</div>);
|
const armourSourcesTt = armourSourcesData.map((o) => {
|
||||||
shieldDamageTakenExplosiveTt.push(<div key='power distributor'>{translate('power distributor') + ' ' + formats.pct1(shield.explosive.sys)}</div>);
|
let { label, value } = o;
|
||||||
|
return <div key={label}>{translate(label)} {formats.int(value)}</div>;
|
||||||
|
});
|
||||||
|
|
||||||
shieldDamageTakenKineticTt.push(<div key='generator'>{translate('generator') + ' ' + formats.pct1(shield.kinetic.generator)}</div>);
|
// Armour resistances
|
||||||
shieldDamageTakenKineticTt.push(<div key='boosters'>{translate('boosters') + ' ' + formats.pct1(shield.kinetic.boosters)}</div>);
|
const armourDamageTakenData = [
|
||||||
shieldDamageTakenKineticTt.push(<div key='power distributor'>{translate('power distributor') + ' ' + formats.pct1(shield.kinetic.sys)}</div>);
|
'absolute', 'explosive', 'kinetic', 'thermal', 'caustic',
|
||||||
|
].map((label) => {
|
||||||
|
const dmgMult = armour[label];
|
||||||
|
const tooltip = ['byAlloys', 'byHRPs'].map(
|
||||||
|
(label) => <div key={label}>
|
||||||
|
{translate(label)} {formats.pct1(dmgMult[label])}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
return { label, value: Math.round(100 * dmgMult.damageMultiplier), tooltip };
|
||||||
|
});
|
||||||
|
|
||||||
shieldDamageTakenThermalTt.push(<div key='generator'>{translate('generator') + ' ' + formats.pct1(shield.thermal.generator)}</div>);
|
// Effective HP
|
||||||
shieldDamageTakenThermalTt.push(<div key='boosters'>{translate('boosters') + ' ' + formats.pct1(shield.thermal.boosters)}</div>);
|
const effectiveArmourData = [
|
||||||
shieldDamageTakenThermalTt.push(<div key='power distributor'>{translate('power distributor') + ' ' + formats.pct1(shield.thermal.sys)}</div>);
|
'absolute', 'explosive', 'kinetic', 'thermal'
|
||||||
|
].map((label) => {
|
||||||
const effectiveAbsoluteShield = shield.total / shield.absolute.total;
|
const dmgMult = armour[label];
|
||||||
effectiveShieldData.push({ value: Math.round(effectiveAbsoluteShield), label: translate('absolute'), tooltip: effectiveShieldAbsoluteTt });
|
const raw = armour.armour;
|
||||||
const effectiveExplosiveShield = shield.total / shield.explosive.total;
|
const tooltip = ['byBoosters', 'byGenerator', 'bySys'].map(
|
||||||
effectiveShieldData.push({ value: Math.round(effectiveExplosiveShield), label: translate('explosive'), tooltip: effectiveShieldExplosiveTt });
|
(label) => <div key={label}>
|
||||||
const effectiveKineticShield = shield.total / shield.kinetic.total;
|
{translate(label)} {formats.int(raw * dmgMult[label])}
|
||||||
effectiveShieldData.push({ value: Math.round(effectiveKineticShield), label: translate('kinetic'), tooltip: effectiveShieldKineticTt });
|
</div>
|
||||||
const effectiveThermalShield = shield.total / shield.thermal.total;
|
);
|
||||||
effectiveShieldData.push({ value: Math.round(effectiveThermalShield), label: translate('thermal'), tooltip: effectiveShieldThermalTt });
|
return { label, value: Math.round(dmgMult.damageMultiplier * raw), tooltip };
|
||||||
|
});
|
||||||
shieldDamageTakenData.push({ value: Math.round(shield.absolute.total * 100), label: translate('absolute'), tooltip: shieldDamageTakenAbsoluteTt });
|
|
||||||
shieldDamageTakenData.push({ value: Math.round(shield.explosive.total * 100), label: translate('explosive'), tooltip: shieldDamageTakenExplosiveTt });
|
|
||||||
shieldDamageTakenData.push({ value: Math.round(shield.kinetic.total * 100), label: translate('kinetic'), tooltip: shieldDamageTakenKineticTt });
|
|
||||||
shieldDamageTakenData.push({ value: Math.round(shield.thermal.total * 100), label: translate('thermal'), tooltip: shieldDamageTakenThermalTt });
|
|
||||||
|
|
||||||
maxEffectiveShield = Math.max(shield.total / shield.absolute.max, shield.total / shield.explosive.max, shield.total / shield.kinetic.max, shield.total / shield.thermal.max);
|
|
||||||
}
|
|
||||||
|
|
||||||
const armourSourcesData = [];
|
|
||||||
armourSourcesData.push({ value: Math.round(armour.bulkheads), label: translate('bulkheads') });
|
|
||||||
armourSourcesData.push({ value: Math.round(armour.reinforcement), label: translate('reinforcement') });
|
|
||||||
|
|
||||||
const armourSourcesTt = [];
|
|
||||||
const effectiveArmourAbsoluteTt = [];
|
|
||||||
const effectiveArmourExplosiveTt = [];
|
|
||||||
const effectiveArmourKineticTt = [];
|
|
||||||
const effectiveArmourThermalTt = [];
|
|
||||||
const effectiveArmourCausticTt = [];
|
|
||||||
if (armour.bulkheads > 0) {
|
|
||||||
armourSourcesTt.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>);
|
|
||||||
effectiveArmourKineticTt.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) {
|
|
||||||
armourSourcesTt.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>);
|
|
||||||
effectiveArmourKineticTt.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>);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const rawArmour = armour.bulkheads + armour.reinforcement;
|
|
||||||
|
|
||||||
const armourDamageTakenTt = [];
|
|
||||||
armourDamageTakenTt.push(<div key='bulkheads'>{translate('bulkheads') + ' ' + formats.pct1(armour.absolute.bulkheads)}</div>);
|
|
||||||
armourDamageTakenTt.push(<div key='reinforcement'>{translate('reinforcement') + ' ' + formats.pct1(armour.absolute.reinforcement)}</div>);
|
|
||||||
|
|
||||||
const armourDamageTakenExplosiveTt = [];
|
|
||||||
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>);
|
|
||||||
if (armour.explosive.total != 1) effectiveArmourExplosiveTt.push(<div key='resistance'>{translate('resistance') + ' ' + formats.int(rawArmour / armour.explosive.total - rawArmour)}</div>);
|
|
||||||
|
|
||||||
const armourDamageTakenKineticTt = [];
|
|
||||||
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>);
|
|
||||||
if (armour.kinetic.total != 1) effectiveArmourKineticTt.push(<div key='resistance'>{translate('resistance') + ' ' + formats.int(rawArmour / armour.kinetic.total - rawArmour)}</div>);
|
|
||||||
|
|
||||||
const armourDamageTakenThermalTt = [];
|
|
||||||
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>);
|
|
||||||
if (armour.thermal.total != 1) effectiveArmourThermalTt.push(<div key='resistance'>{translate('resistance') + ' ' + formats.int(rawArmour / armour.thermal.total - 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 effectiveAbsoluteArmour = armour.total / armour.absolute.total;
|
|
||||||
effectiveArmourData.push({ value: Math.round(effectiveAbsoluteArmour), label: translate('absolute'), tooltip: effectiveArmourAbsoluteTt });
|
|
||||||
const effectiveExplosiveArmour = armour.total / armour.explosive.total;
|
|
||||||
effectiveArmourData.push({ value: Math.round(effectiveExplosiveArmour), label: translate('explosive'), tooltip: effectiveArmourExplosiveTt });
|
|
||||||
const effectiveKineticArmour = armour.total / armour.kinetic.total;
|
|
||||||
effectiveArmourData.push({ value: Math.round(effectiveKineticArmour), label: translate('kinetic'), tooltip: effectiveArmourKineticTt });
|
|
||||||
const effectiveThermalArmour = armour.total / armour.thermal.total;
|
|
||||||
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 = [];
|
|
||||||
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.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.caustic.total * 100), label: translate('caustic'), tooltip: armourDamageTakenCausticTt });
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<span id='defence'>
|
<span id='defence'>
|
||||||
{shield.total ? <span>
|
{shields.withSCBs ? <span>
|
||||||
<div className='group quarter'>
|
<div className='group quarter'>
|
||||||
<h2>{translate('shield metrics')}</h2>
|
<h2>{translate('shield metrics')}</h2>
|
||||||
<br/>
|
<br/>
|
||||||
<h2 onMouseOver={termtip.bind(null, <div>{shieldSourcesTt}</div>)} onMouseOut={tooltip.bind(null, null)} className='summary'>{translate('raw shield strength')}<br/>{formats.int(shield.total)}{units.MJ}</h2>
|
<h2 onMouseOver={termtip.bind(null, <div>{shieldSourcesTt}</div>)} onMouseOut={tooltip.bind(null, null)} className='summary'>{translate('raw shield strength')}<br/>{formats.int(shields.withSCBs)}{units.MJ}</h2>
|
||||||
<h2 onMouseOver={termtip.bind(null, translate('TT_TIME_TO_LOSE_SHIELDS'))} onMouseOut={tooltip.bind(null, null)}>{translate('PHRASE_TIME_TO_LOSE_SHIELDS')}<br/>{shielddamage.totalsdps == 0 ? translate('ever') : formats.time(Calc.timeToDeplete(shield.total, shielddamage.totalsdps, shielddamage.totalseps, pd.getWeaponsCapacity(), pd.getWeaponsRechargeRate() * opponentWep / 4))}</h2>
|
<h2 onMouseOver={termtip.bind(null, translate('TT_TIME_TO_LOSE_SHIELDS'))} onMouseOut={tooltip.bind(null, null)}>{translate('PHRASE_TIME_TO_LOSE_SHIELDS')}<br/>TODO</h2>
|
||||||
<h2 onMouseOver={termtip.bind(null, translate('PHRASE_SG_RECOVER'))} onMouseOut={tooltip.bind(null, null)}>{translate('PHRASE_TIME_TO_RECOVER_SHIELDS')}<br/>{shield.recover === Math.Inf ? translate('never') : formats.time(shield.recover)}</h2>
|
<h2 onMouseOver={termtip.bind(null, translate('PHRASE_SG_RECOVER'))} onMouseOut={tooltip.bind(null, null)}>{translate('PHRASE_TIME_TO_RECOVER_SHIELDS')}<br/>{shields.recover ? formats.time(shields.recover) : translate('never')}</h2>
|
||||||
<h2 onMouseOver={termtip.bind(null, translate('PHRASE_SG_RECHARGE'))} onMouseOut={tooltip.bind(null, null)}>{translate('PHRASE_TIME_TO_RECHARGE_SHIELDS')}<br/>{shield.recharge === Math.Inf ? translate('never') : formats.time(shield.recharge)}</h2>
|
<h2 onMouseOver={termtip.bind(null, translate('PHRASE_SG_RECHARGE'))} onMouseOut={tooltip.bind(null, null)}>{translate('PHRASE_TIME_TO_RECHARGE_SHIELDS')}<br/>{shields.recharge ? formats.time(shields.recharge) : translate('never')}</h2>
|
||||||
</div>
|
</div>
|
||||||
<div className='group quarter'>
|
<div className='group quarter'>
|
||||||
<h2 onMouseOver={termtip.bind(null, translate('PHRASE_SHIELD_SOURCES'))} onMouseOut={tooltip.bind(null, null)}>{translate('shield sources')}</h2>
|
<h2 onMouseOver={termtip.bind(null, translate('PHRASE_SHIELD_SOURCES'))} onMouseOut={tooltip.bind(null, null)}>{translate('shield sources')}</h2>
|
||||||
<PieChart data={shieldSourcesData} />
|
<PieChart data={shieldSourcesData} />
|
||||||
</div>
|
</div>
|
||||||
<div className='group quarter'>
|
<div className='group quarter'>
|
||||||
<h2 onMouseOver={termtip.bind(null, translate('PHRASE_DAMAGE_TAKEN'))} onMouseOut={tooltip.bind(null, null)}>{translate('damage taken')}(%)</h2>
|
<h2 onMouseOver={termtip.bind(null, translate('PHRASE_DAMAGE_TAKEN'))} onMouseOut={tooltip.bind(null, null)}>{translate('damage taken')}(%)</h2>
|
||||||
<VerticalBarChart data={shieldDamageTakenData} yMax={140} />
|
<VerticalBarChart data={shieldDamageTakenData} yMax={140} />
|
||||||
</div>
|
</div>
|
||||||
<div className='group quarter'>
|
<div className='group quarter'>
|
||||||
<h2 onMouseOver={termtip.bind(null, translate('PHRASE_EFFECTIVE_SHIELD'))} onMouseOut={tooltip.bind(null, null)}>{translate('effective shield')}(MJ)</h2>
|
<h2 onMouseOver={termtip.bind(null, translate('PHRASE_EFFECTIVE_SHIELD'))} onMouseOut={tooltip.bind(null, null)}>{translate('effective shield')}(MJ)</h2>
|
||||||
<VerticalBarChart data={effectiveShieldData} yMax={maxEffectiveShield}/>
|
<VerticalBarChart data={effectiveShieldData} yMax={maxEffectiveShield}/>
|
||||||
</div>
|
</div>
|
||||||
</span> : null }
|
</span> : null }
|
||||||
|
|
||||||
<div className='group quarter'>
|
<div className='group quarter'>
|
||||||
<h2>{translate('armour metrics')}</h2>
|
<h2>{translate('armour metrics')}</h2>
|
||||||
<h2 onMouseOver={termtip.bind(null, <div>{armourSourcesTt}</div>)} onMouseOut={tooltip.bind(null, null)} className='summary'>{translate('raw armour strength')}<br/>{formats.int(armour.total)}</h2>
|
<h2 onMouseOver={termtip.bind(null, <div>{armourSourcesTt}</div>)} onMouseOut={tooltip.bind(null, null)} className='summary'>{translate('raw armour strength')}<br/>{formats.int(armour.armour)}</h2>
|
||||||
<h2 onMouseOver={termtip.bind(null, translate('TT_TIME_TO_LOSE_ARMOUR'))} onMouseOut={tooltip.bind(null, null)}>{translate('PHRASE_TIME_TO_LOSE_ARMOUR')}<br/>{armourdamage.totalsdps == 0 ? translate('ever') : formats.time(Calc.timeToDeplete(armour.total, armourdamage.totalsdps, armourdamage.totalseps, pd.getWeaponsCapacity(), pd.getWeaponsRechargeRate() * opponentWep / 4))}</h2>
|
<h2 onMouseOver={termtip.bind(null, translate('TT_TIME_TO_LOSE_ARMOUR'))} onMouseOut={tooltip.bind(null, null)}>{translate('PHRASE_TIME_TO_LOSE_ARMOUR')}<br/>TODO</h2>
|
||||||
<h2 onMouseOver={termtip.bind(null, translate('TT_MODULE_ARMOUR'))} onMouseOut={tooltip.bind(null, null)}>{translate('raw module armour')}<br/>{formats.int(armour.modulearmour)}</h2>
|
<h2 onMouseOver={termtip.bind(null, translate('TT_MODULE_ARMOUR'))} onMouseOut={tooltip.bind(null, null)}>{translate('raw module armour')}<br/>{formats.int(moduleProtection.moduleArmour)}</h2>
|
||||||
<h2 onMouseOver={termtip.bind(null, translate('TT_MODULE_PROTECTION_EXTERNAL'))} onMouseOut={tooltip.bind(null, null)}>{translate('PHRASE_MODULE_PROTECTION_EXTERNAL')}<br/>{formats.pct1(armour.moduleprotection / 2)}</h2>
|
<h2 onMouseOver={termtip.bind(null, translate('TT_MODULE_PROTECTION_EXTERNAL'))} onMouseOut={tooltip.bind(null, null)}>{translate('PHRASE_MODULE_PROTECTION_EXTERNAL')}<br/>{formats.pct1((1 - moduleProtection.moduleProtection) / 2)}</h2>
|
||||||
<h2 onMouseOver={termtip.bind(null, translate('TT_MODULE_PROTECTION_INTERNAL'))} onMouseOut={tooltip.bind(null, null)}>{translate('PHRASE_MODULE_PROTECTION_INTERNAL')}<br/>{formats.pct1(armour.moduleprotection)}</h2>
|
<h2 onMouseOver={termtip.bind(null, translate('TT_MODULE_PROTECTION_INTERNAL'))} onMouseOut={tooltip.bind(null, null)}>{translate('PHRASE_MODULE_PROTECTION_INTERNAL')}<br/>{formats.pct1(1 - moduleProtection.moduleProtection)}</h2>
|
||||||
<br/>
|
<br/>
|
||||||
</div>
|
</div>
|
||||||
<div className='group quarter'>
|
<div className='group quarter'>
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import TranslatedComponent from './TranslatedComponent';
|
import TranslatedComponent from './TranslatedComponent';
|
||||||
import { Ships } from 'coriolis-data/dist';
|
|
||||||
import Slider from '../components/Slider';
|
import Slider from '../components/Slider';
|
||||||
|
import { moduleReduce } from 'ed-forge/lib/helper';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Engagement range slider
|
* Engagement range slider
|
||||||
@@ -22,35 +22,18 @@ export default class EngagementRange extends TranslatedComponent {
|
|||||||
*/
|
*/
|
||||||
constructor(props, context) {
|
constructor(props, context) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
const { ship } = props;
|
|
||||||
|
|
||||||
const maxRange = Math.round(this._calcMaxRange(ship));
|
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
maxRange
|
maxRange: moduleReduce(
|
||||||
|
this.props.ship.getHardpoints(),
|
||||||
|
'maximumrange',
|
||||||
|
true,
|
||||||
|
// Don't use plain `Math.max` because callback will be passed four args
|
||||||
|
(a, v) => Math.max(a, v),
|
||||||
|
1000,
|
||||||
|
),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Calculate the maximum range of a ship's weapons
|
|
||||||
* @param {Object} ship The ship
|
|
||||||
* @returns {int} The maximum range, in metres
|
|
||||||
*/
|
|
||||||
_calcMaxRange(ship) {
|
|
||||||
let maxRange = 1000;
|
|
||||||
for (let i = 0; i < ship.hardpoints.length; i++) {
|
|
||||||
if (ship.hardpoints[i].maxClass > 0 && ship.hardpoints[i].m && ship.hardpoints[i].enabled) {
|
|
||||||
const thisRange = ship.hardpoints[i].m.getRange();
|
|
||||||
if (thisRange > maxRange) {
|
|
||||||
maxRange = thisRange;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return maxRange;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update range
|
* Update range
|
||||||
* @param {number} rangeLevel percentage level from 0 to 1
|
* @param {number} rangeLevel percentage level from 0 to 1
|
||||||
@@ -62,7 +45,9 @@ export default class EngagementRange extends TranslatedComponent {
|
|||||||
const range = Math.round(rangeLevel * maxRange);
|
const range = Math.round(rangeLevel * maxRange);
|
||||||
|
|
||||||
if (range !== this.props.engagementRange) {
|
if (range !== this.props.engagementRange) {
|
||||||
this.props.onChange(range);
|
const { onChange, ship } = this.props;
|
||||||
|
ship.setEngagementRange(range);
|
||||||
|
onChange(range);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -71,8 +56,8 @@ export default class EngagementRange extends TranslatedComponent {
|
|||||||
* @return {React.Component} contents
|
* @return {React.Component} contents
|
||||||
*/
|
*/
|
||||||
render() {
|
render() {
|
||||||
const { language, onWindowResize, sizeRatio, tooltip, termtip } = this.context;
|
const { language, onWindowResize, sizeRatio } = this.context;
|
||||||
const { formats, translate, units } = language;
|
const { formats, translate } = language;
|
||||||
const { engagementRange } = this.props;
|
const { engagementRange } = this.props;
|
||||||
const { maxRange } = this.state;
|
const { maxRange } = this.state;
|
||||||
|
|
||||||
|
|||||||
@@ -1,104 +1,59 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import TranslatedComponent from './TranslatedComponent';
|
import TranslatedComponent from './TranslatedComponent';
|
||||||
import { Ships } from 'coriolis-data/dist';
|
|
||||||
import { nameComparator } from '../utils/SlotFunctions';
|
|
||||||
import LineChart from '../components/LineChart';
|
import LineChart from '../components/LineChart';
|
||||||
import Slider from '../components/Slider';
|
import { getBoostMultiplier, getSpeedMultipliers } from 'ed-forge/lib/stats/SpeedProfile';
|
||||||
import * as ModuleUtils from '../shipyard/ModuleUtils';
|
import { ShipProps } from 'ed-forge';
|
||||||
import Module from '../shipyard/Module';
|
const { LADEN_MASS } = ShipProps;
|
||||||
import * as Calc from '../shipyard/Calculations';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Engine profile for a given ship
|
* Engine profile for a given ship
|
||||||
*/
|
*/
|
||||||
export default class EngineProfile extends TranslatedComponent {
|
export default class EngineProfile extends TranslatedComponent {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
|
code: PropTypes.string.isRequired,
|
||||||
ship: PropTypes.object.isRequired,
|
ship: PropTypes.object.isRequired,
|
||||||
cargo: PropTypes.number.isRequired,
|
cargo: PropTypes.number.isRequired,
|
||||||
fuel: PropTypes.number.isRequired,
|
fuel: PropTypes.number.isRequired,
|
||||||
eng: PropTypes.number.isRequired,
|
pips: PropTypes.number.isRequired,
|
||||||
boost: PropTypes.bool.isRequired,
|
boost: PropTypes.bool.isRequired,
|
||||||
marker: PropTypes.string.isRequired
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructor
|
|
||||||
* @param {Object} props React Component properties
|
|
||||||
* @param {Object} context React Component context
|
|
||||||
*/
|
|
||||||
constructor(props, context) {
|
|
||||||
super(props);
|
|
||||||
|
|
||||||
const ship = this.props.ship;
|
|
||||||
|
|
||||||
this.state = {
|
|
||||||
calcMaxSpeedFunc: this.calcMaxSpeed.bind(this, ship, this.props.eng, this.props.boost)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update the state if our ship changes
|
|
||||||
* @param {Object} nextProps Incoming/Next properties
|
|
||||||
* @param {Object} nextContext Incoming/Next conext
|
|
||||||
* @return {boolean} Returns true if the component should be rerendered
|
|
||||||
*/
|
|
||||||
componentWillReceiveProps(nextProps, nextContext) {
|
|
||||||
if (nextProps.marker != this.props.marker) {
|
|
||||||
this.setState({ calcMaxSpeedFunc: this.calcMaxSpeed.bind(this, nextProps.ship, nextProps.eng, nextProps.boost) });
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Calculate the top speed for this ship given thrusters, mass and pips to ENG
|
|
||||||
* @param {Object} ship The ship
|
|
||||||
* @param {Object} eng The number of pips to ENG
|
|
||||||
* @param {Object} boost If boost is enabled
|
|
||||||
* @param {Object} mass The mass at which to calculate the top speed
|
|
||||||
* @return {number} The maximum speed
|
|
||||||
*/
|
|
||||||
calcMaxSpeed(ship, eng, boost, mass) {
|
|
||||||
// Obtain the top speed
|
|
||||||
return Calc.calcSpeed(mass, ship.speed, ship.standard[1].m, ship.pipSpeed, eng, ship.boost / ship.speed, boost);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Render engine profile
|
* Render engine profile
|
||||||
* @return {React.Component} contents
|
* @return {React.Component} contents
|
||||||
*/
|
*/
|
||||||
render() {
|
render() {
|
||||||
const { language, onWindowResize, sizeRatio, tooltip, termtip } = this.context;
|
const { language } = this.context;
|
||||||
const { formats, translate, units } = language;
|
const { translate } = language;
|
||||||
const { ship, cargo, eng, fuel, boost } = this.props;
|
const { code, ship, pips, boost } = this.props;
|
||||||
|
|
||||||
// Calculate bounds for our line chart
|
// Calculate bounds for our line chart
|
||||||
const thrusters = ship.standard[1].m;
|
const minMass = ship.readProp('hullmass');
|
||||||
const minMass = ship.calcLowestPossibleMass({ th: thrusters });
|
const maxMass = ship.getThrusters().get('enginemaximalmass');
|
||||||
const maxMass = thrusters.getMaxMass();
|
const baseSpeed = ship.readProp('speed');
|
||||||
const mass = ship.unladenMass + fuel + cargo;
|
const baseBoost = getBoostMultiplier(ship);
|
||||||
const minSpeed = Calc.calcSpeed(maxMass, ship.speed, thrusters, ship.pipSpeed, 0, ship.boost / ship.speed, false);
|
const cb = (eng, boost, mass) => {
|
||||||
const maxSpeed = Calc.calcSpeed(minMass, ship.speed, thrusters, ship.pipSpeed, 4, ship.boost / ship.speed, true);
|
const mult = getSpeedMultipliers(ship, mass)[(boost ? 4 : eng) / 0.5];
|
||||||
// Add a mark at our current mass
|
return baseSpeed * (boost ? baseBoost : 1) * mult;
|
||||||
const mark = Math.min(mass, maxMass);
|
};
|
||||||
|
|
||||||
const code = `${ship.toString()}:${cargo}:${fuel}:${eng}:${boost}`;
|
|
||||||
|
|
||||||
// This graph can have a precipitous fall-off so we use lots of points to make it look a little smoother
|
// This graph can have a precipitous fall-off so we use lots of points to make it look a little smoother
|
||||||
return (
|
return (
|
||||||
<LineChart
|
<LineChart
|
||||||
xMin={minMass}
|
xMin={minMass}
|
||||||
xMax={maxMass}
|
xMax={maxMass}
|
||||||
yMin={minSpeed}
|
yMin={cb(0, false, maxMass)}
|
||||||
yMax={maxSpeed}
|
yMax={cb(4, true, minMass)}
|
||||||
xMark={mark}
|
// Add a mark at our current mass
|
||||||
|
xMark={Math.min(ship.get(LADEN_MASS), maxMass)}
|
||||||
xLabel={translate('mass')}
|
xLabel={translate('mass')}
|
||||||
xUnit={translate('T')}
|
xUnit={translate('T')}
|
||||||
yLabel={translate('maximum speed')}
|
yLabel={translate('maximum speed')}
|
||||||
yUnit={translate('m/s')}
|
yUnit={translate('m/s')}
|
||||||
func={this.state.calcMaxSpeedFunc}
|
func={cb.bind(this, pips.Eng.base + pips.Eng.mc, boost)}
|
||||||
points={1000}
|
points={1000}
|
||||||
code={code}
|
// Encode boost in code to re-render on state change
|
||||||
|
code={`${pips.Eng.base + pips.Eng.mc}:${Number(boost)}:${code}`}
|
||||||
aspect={0.7}
|
aspect={0.7}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,53 +1,23 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import TranslatedComponent from './TranslatedComponent';
|
import TranslatedComponent from './TranslatedComponent';
|
||||||
import { Ships } from 'coriolis-data/dist';
|
|
||||||
import { nameComparator } from '../utils/SlotFunctions';
|
|
||||||
import LineChart from '../components/LineChart';
|
import LineChart from '../components/LineChart';
|
||||||
import Slider from '../components/Slider';
|
|
||||||
import * as ModuleUtils from '../shipyard/ModuleUtils';
|
|
||||||
import Module from '../shipyard/Module';
|
|
||||||
import * as Calc from '../shipyard/Calculations';
|
import * as Calc from '../shipyard/Calculations';
|
||||||
|
import { calculateJumpRange } from 'ed-forge/lib/stats/JumpRangeProfile';
|
||||||
|
import { ShipProps } from 'ed-forge';
|
||||||
|
const { LADEN_MASS } = ShipProps;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* FSD profile for a given ship
|
* FSD profile for a given ship
|
||||||
*/
|
*/
|
||||||
export default class FSDProfile extends TranslatedComponent {
|
export default class FSDProfile extends TranslatedComponent {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
|
code: PropTypes.string.isRequired,
|
||||||
ship: PropTypes.object.isRequired,
|
ship: PropTypes.object.isRequired,
|
||||||
cargo: PropTypes.number.isRequired,
|
cargo: PropTypes.number.isRequired,
|
||||||
fuel: PropTypes.number.isRequired,
|
fuel: PropTypes.number.isRequired,
|
||||||
marker: PropTypes.string.isRequired
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructor
|
|
||||||
* @param {Object} props React Component properties
|
|
||||||
* @param {Object} context React Component context
|
|
||||||
*/
|
|
||||||
constructor(props, context) {
|
|
||||||
super(props);
|
|
||||||
|
|
||||||
const ship = this.props.ship;
|
|
||||||
|
|
||||||
this.state = {
|
|
||||||
calcMaxRangeFunc: this._calcMaxRange.bind(this, ship, this.props.fuel)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update the state if our ship changes
|
|
||||||
* @param {Object} nextProps Incoming/Next properties
|
|
||||||
* @param {Object} nextContext Incoming/Next conext
|
|
||||||
* @return {boolean} Returns true if the component should be rerendered
|
|
||||||
*/
|
|
||||||
componentWillReceiveProps(nextProps, nextContext) {
|
|
||||||
if (nextProps.marker != this.props.marker) {
|
|
||||||
this.setState({ calcMaxRangeFunc: this._calcMaxRange.bind(this, nextProps.ship, nextProps.fuel) });
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calculate the maximum range for this ship across its applicable mass
|
* Calculate the maximum range for this ship across its applicable mass
|
||||||
* @param {Object} ship The ship
|
* @param {Object} ship The ship
|
||||||
@@ -65,36 +35,27 @@ export default class FSDProfile extends TranslatedComponent {
|
|||||||
* @return {React.Component} contents
|
* @return {React.Component} contents
|
||||||
*/
|
*/
|
||||||
render() {
|
render() {
|
||||||
const { language, onWindowResize, sizeRatio, tooltip, termtip } = this.context;
|
const { language } = this.context;
|
||||||
const { formats, translate, units } = language;
|
const { translate } = language;
|
||||||
const { ship, cargo, fuel } = this.props;
|
const { code, ship } = this.props;
|
||||||
|
|
||||||
|
|
||||||
// Calculate bounds for our line chart - use thruster info for X
|
|
||||||
const thrusters = ship.standard[1].m;
|
|
||||||
const fsd = ship.standard[2].m;
|
|
||||||
const minMass = ship.calcLowestPossibleMass({ th: thrusters });
|
|
||||||
const maxMass = thrusters.getMaxMass();
|
|
||||||
const mass = ship.unladenMass + fuel + cargo;
|
|
||||||
const minRange = 0;
|
|
||||||
const maxRange = Calc.jumpRange(minMass + fsd.getMaxFuelPerJump(), fsd, fsd.getMaxFuelPerJump(), ship);
|
|
||||||
// Add a mark at our current mass
|
|
||||||
const mark = Math.min(mass, maxMass);
|
|
||||||
|
|
||||||
const code = ship.name + ship.toString() + '.' + fuel;
|
|
||||||
|
|
||||||
|
const minMass = ship.readProp('hullmass');
|
||||||
|
const maxMass = ship.getThrusters().get('enginemaximalmass');
|
||||||
|
const mass = ship.get(LADEN_MASS);
|
||||||
|
const cb = (mass) => calculateJumpRange(ship, mass, Infinity, true);
|
||||||
return (
|
return (
|
||||||
<LineChart
|
<LineChart
|
||||||
xMin={minMass}
|
xMin={minMass}
|
||||||
xMax={maxMass}
|
xMax={maxMass}
|
||||||
yMin={minRange}
|
yMin={0}
|
||||||
yMax={maxRange}
|
yMax={cb(minMass)}
|
||||||
xMark={mark}
|
// Add a mark at our current mass
|
||||||
|
xMark={Math.min(mass, maxMass)}
|
||||||
xLabel={translate('mass')}
|
xLabel={translate('mass')}
|
||||||
xUnit={translate('T')}
|
xUnit={translate('T')}
|
||||||
yLabel={translate('maximum range')}
|
yLabel={translate('maximum range')}
|
||||||
yUnit={translate('LY')}
|
yUnit={translate('LY')}
|
||||||
func={this.state.calcMaxRangeFunc}
|
func={cb}
|
||||||
points={200}
|
points={200}
|
||||||
code={code}
|
code={code}
|
||||||
aspect={0.7}
|
aspect={0.7}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import TranslatedComponent from './TranslatedComponent';
|
import TranslatedComponent from './TranslatedComponent';
|
||||||
import { Ships } from 'coriolis-data/dist';
|
|
||||||
import Slider from '../components/Slider';
|
import Slider from '../components/Slider';
|
||||||
|
import autoBind from 'auto-bind';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fuel slider
|
* Fuel slider
|
||||||
@@ -22,8 +22,7 @@ export default class Fuel extends TranslatedComponent {
|
|||||||
*/
|
*/
|
||||||
constructor(props, context) {
|
constructor(props, context) {
|
||||||
super(props);
|
super(props);
|
||||||
|
autoBind(this);
|
||||||
this._fuelChange = this._fuelChange.bind(this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,108 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import cn from 'classnames';
|
|
||||||
import Slot from './Slot';
|
|
||||||
import Persist from '../stores/Persist';
|
|
||||||
import { DamageAbsolute, DamageKinetic, DamageThermal, DamageExplosive, MountFixed, MountGimballed, MountTurret, ListModifications, Modified } from './SvgIcons';
|
|
||||||
import { Modifications } from 'coriolis-data/dist';
|
|
||||||
import { stopCtxPropagation } from '../utils/UtilityFunctions';
|
|
||||||
import { blueprintTooltip } from '../utils/BlueprintFunctions';
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Hardpoint / Utility Slot
|
|
||||||
*/
|
|
||||||
export default class HardpointSlot extends Slot {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the CSS class name for the slot.
|
|
||||||
* @return {string} CSS Class name
|
|
||||||
*/
|
|
||||||
_getClassNames() {
|
|
||||||
return this.props.maxClass > 0 ? 'hardpoint' : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the label for the slot
|
|
||||||
* @param {Function} translate Translate function
|
|
||||||
* @return {string} Label
|
|
||||||
*/
|
|
||||||
_getMaxClassLabel(translate) {
|
|
||||||
return translate(['U','S','M','L','H'][this.props.maxClass]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generate the slot contents
|
|
||||||
* @param {Object} m Mounted Module
|
|
||||||
* @param {Boolean} enabled Slot enabled
|
|
||||||
* @param {Function} translate Translate function
|
|
||||||
* @param {Object} formats Localized Formats map
|
|
||||||
* @param {Object} u Localized Units Map
|
|
||||||
* @return {React.Component} Slot contents
|
|
||||||
*/
|
|
||||||
_getSlotDetails(m, enabled, translate, formats, u) {
|
|
||||||
if (m) {
|
|
||||||
let classRating = `${m.class}${m.rating}${m.missile ? '/' + m.missile : ''}`;
|
|
||||||
let { drag, drop } = this.props;
|
|
||||||
let { termtip, tooltip } = this.context;
|
|
||||||
let validMods = Modifications.modules[m.grp].modifications || [];
|
|
||||||
let showModuleResistances = Persist.showModuleResistances();
|
|
||||||
|
|
||||||
// Modifications tooltip shows blueprint and grade, if available
|
|
||||||
let modTT = translate('modified');
|
|
||||||
if (m && m.blueprint && m.blueprint.name) {
|
|
||||||
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 = (
|
|
||||||
<div>
|
|
||||||
<div>{modTT}</div>
|
|
||||||
{blueprintTooltip(translate, m.blueprint.grades[m.blueprint.grade], null, m.grp, m)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const className = cn('details', enabled ? '' : 'disabled');
|
|
||||||
return <div className={className} draggable='true' onDragStart={drag} onDragEnd={drop}>
|
|
||||||
<div className={'cb'}>
|
|
||||||
<div className={'l'}>
|
|
||||||
{m.mount && m.mount == 'F' ? <span onMouseOver={termtip.bind(null, 'fixed')} onMouseOut={tooltip.bind(null, null)}><MountFixed /></span> : ''}
|
|
||||||
{m.mount && m.mount == 'G' ? <span onMouseOver={termtip.bind(null, 'gimballed')} onMouseOut={tooltip.bind(null, null)}><MountGimballed /></span> : ''}
|
|
||||||
{m.mount && m.mount == 'T' ? <span onMouseOver={termtip.bind(null, 'turreted')} onMouseOut={tooltip.bind(null, null)}><MountTurret /></span> : ''}
|
|
||||||
{m.getDamageDist() && m.getDamageDist().K ? <span onMouseOver={termtip.bind(null, 'kinetic')} onMouseOut={tooltip.bind(null, null)}><DamageKinetic /></span> : ''}
|
|
||||||
{m.getDamageDist() && m.getDamageDist().T ? <span onMouseOver={termtip.bind(null, 'thermal')} onMouseOut={tooltip.bind(null, null)}><DamageThermal /></span> : ''}
|
|
||||||
{m.getDamageDist() && m.getDamageDist().E ? <span onMouseOver={termtip.bind(null, 'explosive')} onMouseOut={tooltip.bind(null, null)}><DamageExplosive /></span> : ''}
|
|
||||||
{m.getDamageDist() && m.getDamageDist().A ? <span onMouseOver={termtip.bind(null, 'absolute')} onMouseOut={tooltip.bind(null, null)}><DamageAbsolute /></span> : ''}
|
|
||||||
{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(m.getMass())}{u.T}</div>
|
|
||||||
</div>
|
|
||||||
<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.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.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.getRoF() ? <div className={'l'} onMouseOver={termtip.bind(null, 'rof')} onMouseOut={tooltip.bind(null, null)}>{translate('ROF')}: {formats.f1(m.getRoF())}{u.ps}</div> : null }
|
|
||||||
{ m.getRange() ? <div className={'l'}>{translate('range', m.grp)} {formats.f1(m.getRange() / 1000)}{u.km}</div> : null }
|
|
||||||
{ m.getScanTime() ? <div className={'l'}>{translate('scantime')} {formats.f1(m.getScanTime())}{u.s}</div> : null }
|
|
||||||
{ m.getFalloff() ? <div className={'l'}>{translate('falloff')} {formats.round(m.getFalloff() / 1000)}{u.km}</div> : null }
|
|
||||||
{ m.getShieldBoost() ? <div className={'l'}>+{formats.pct1(m.getShieldBoost())}</div> : null }
|
|
||||||
{ m.getAmmo() ? <div className={'l'}>{translate('ammunition')}: {formats.int(m.getClip())}/{formats.int(m.getAmmo())}</div> : null }
|
|
||||||
{ m.getReload() ? <div className={'l'}>{translate('reload')}: {formats.round(m.getReload())}{u.s}</div> : null }
|
|
||||||
{ m.getShotSpeed() ? <div className={'l'}>{translate('shotspeed')}: {formats.int(m.getShotSpeed())}{u.mps}</div> : null }
|
|
||||||
{ m.getPiercing() ? <div className={'l'}>{translate('piercing')}: {formats.int(m.getPiercing())}</div> : null }
|
|
||||||
{ m.getJitter() ? <div className={'l'}>{translate('jitter')}: {formats.f2(m.getJitter())}°</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.getThermalResistance() ? <div className='l'>{translate('thermres')}: {formats.pct(m.getThermalResistance())}</div> : null }
|
|
||||||
{ m.getIntegrity() ? <div className='l'>{translate('integrity')}: {formats.int(m.getIntegrity())}</div> : null }
|
|
||||||
{ m && validMods.length > 0 ? <div className='r' tabIndex="0" ref={ modButton => this.modButton = modButton }><button tabIndex="-1" onClick={this._toggleModifications.bind(this)} onContextMenu={stopCtxPropagation} onMouseOver={termtip.bind(null, 'modifications')} onMouseOut={tooltip.bind(null, null)}><ListModifications /></button></div> : null }
|
|
||||||
</div>
|
|
||||||
</div>;
|
|
||||||
} else {
|
|
||||||
return <div className={'empty'}>{translate('empty')}</div>;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,43 +1,29 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import SlotSection from './SlotSection';
|
import SlotSection from './SlotSection';
|
||||||
import HardpointSlot from './HardpointSlot';
|
import Slot from './Slot';
|
||||||
import cn from 'classnames';
|
|
||||||
import { MountFixed, MountGimballed, MountTurret } from '../components/SvgIcons';
|
import { MountFixed, MountGimballed, MountTurret } from '../components/SvgIcons';
|
||||||
import { stopCtxPropagation } from '../utils/UtilityFunctions';
|
import { stopCtxPropagation } from '../utils/UtilityFunctions';
|
||||||
|
import autoBind from 'auto-bind';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hardpoint slot section
|
* Hardpoint slot section
|
||||||
*/
|
*/
|
||||||
export default class HardpointSlotSection extends SlotSection {
|
export default class HardpointSlotSection extends SlotSection {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor
|
* Constructor
|
||||||
* @param {Object} props React Component properties
|
* @param {Object} props React Component properties
|
||||||
* @param {Object} context React Component context
|
|
||||||
*/
|
*/
|
||||||
constructor(props, context) {
|
constructor(props) {
|
||||||
super(props, context, 'hardpoints', 'hardpoints');
|
super(props, 'hardpoints');
|
||||||
this._empty = this._empty.bind(this);
|
autoBind(this);
|
||||||
this.selectedRefId = null;
|
|
||||||
this.firstRefId = 'emptyall';
|
|
||||||
this.lastRefId = 'nl-F';
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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';
|
// TODO:
|
||||||
this.props.ship.emptyWeapons();
|
// this.props.ship.emptyWeapons();
|
||||||
this.props.onChange();
|
|
||||||
this._close();
|
this._close();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -48,9 +34,8 @@ 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;
|
// TODO:
|
||||||
this.props.ship.useWeapon(group, mount, null, event.getModifierState('Alt'));
|
// this.props.ship.useWeapon(group, mount, null, event.getModifierState('Alt'));
|
||||||
this.props.onChange();
|
|
||||||
this._close();
|
this._close();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -66,32 +51,24 @@ export default class HardpointSlotSection extends SlotSection {
|
|||||||
* @return {Array} Array of Slots
|
* @return {Array} Array of Slots
|
||||||
*/
|
*/
|
||||||
_getSlots() {
|
_getSlots() {
|
||||||
let { ship, currentMenu } = this.props;
|
let { ship, currentMenu, propsToShow, onPropToggle } = this.props;
|
||||||
let { originSlot, targetSlot } = this.state;
|
let { originSlot, targetSlot } = this.state;
|
||||||
let slots = [];
|
let slots = [];
|
||||||
let hardpoints = ship.hardpoints;
|
|
||||||
let availableModules = ship.getAvailableModules();
|
|
||||||
|
|
||||||
for (let i = 0, l = hardpoints.length; i < l; i++) {
|
for (let h of ship.getHardpoints(undefined, true)) {
|
||||||
let h = hardpoints[i];
|
slots.push(<Slot
|
||||||
if (h.maxClass) {
|
key={h.object.Slot}
|
||||||
slots.push(<HardpointSlot
|
maxClass={h.getSize()}
|
||||||
key={i}
|
currentMenu={currentMenu}
|
||||||
maxClass={h.maxClass}
|
drag={this._drag.bind(this, h)}
|
||||||
availableModules={() => availableModules.getHps(h.maxClass)}
|
dragOver={this._dragOverSlot.bind(this, h)}
|
||||||
onOpen={this._openMenu.bind(this, h)}
|
drop={this._drop}
|
||||||
onSelect={this._selectModule.bind(this, h)}
|
dropClass={this._dropClass(h, originSlot, targetSlot)}
|
||||||
onChange={this.props.onChange}
|
m={h}
|
||||||
selected={currentMenu == h}
|
enabled={h.enabled ? true : false}
|
||||||
drag={this._drag.bind(this, h)}
|
propsToShow={propsToShow}
|
||||||
dragOver={this._dragOverSlot.bind(this, h)}
|
onPropToggle={onPropToggle}
|
||||||
drop={this._drop}
|
/>);
|
||||||
dropClass={this._dropClass(h, originSlot, targetSlot)}
|
|
||||||
ship={ship}
|
|
||||||
m={h.m}
|
|
||||||
enabled={h.enabled ? true : false}
|
|
||||||
/>);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return slots;
|
return slots;
|
||||||
@@ -102,59 +79,68 @@ export default class HardpointSlotSection extends SlotSection {
|
|||||||
* @param {Function} translate Translate function
|
* @param {Function} translate Translate function
|
||||||
* @return {React.Component} Section menu
|
* @return {React.Component} Section menu
|
||||||
*/
|
*/
|
||||||
_getSectionMenu(translate) {
|
_getSectionMenu() {
|
||||||
|
const { translate } = this.context.language;
|
||||||
let _fill = this._fill;
|
let _fill = this._fill;
|
||||||
|
|
||||||
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' tabIndex="0" onClick={this._empty} ref={smRef => this.sectionRefArr['emptyall'] = smRef}>{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" tabIndex="0" 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" tabIndex="0" 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" tabIndex="0" 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" tabIndex="0" 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" tabIndex="0" 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" tabIndex="0" 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" tabIndex="0" 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" tabIndex="0" 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" tabIndex="0" 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" tabIndex="0" 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" tabIndex="0" 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" tabIndex="0" 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" tabIndex="0" 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" tabIndex="0" 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" tabIndex="0" 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" tabIndex="0" 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" tabIndex="0" 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" tabIndex="0" 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' tabIndex="0" onClick={_fill.bind(this, 'pa', 'F')}>{translate('pa')}</li>
|
||||||
|
</ul>
|
||||||
|
<div className='select-group cap'>{translate('rg')}</div>
|
||||||
|
<ul>
|
||||||
|
<li className='lc' tabIndex="0" onClick={_fill.bind(this, 'rg', 'F')}>{translate('rg')}</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' tabIndex="0" onClick={_fill.bind(this, 'nl', 'F')}>{translate('nl')}</li>
|
||||||
|
</ul>
|
||||||
|
<div className='select-group cap'>{translate('rfl')}</div>
|
||||||
|
<ul>
|
||||||
|
<li className="c" tabIndex="0" onClick={_fill.bind(this, 'rfl', 'F')}><MountFixed className='lg'/></li>
|
||||||
|
<li className="c" tabIndex="0" onClick={_fill.bind(this, 'rfl', 'T')}><MountTurret className='lg'/></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import ModalExport from './ModalExport';
|
|||||||
import ModalHelp from './ModalHelp';
|
import ModalHelp from './ModalHelp';
|
||||||
import ModalImport from './ModalImport';
|
import ModalImport from './ModalImport';
|
||||||
import Slider from './Slider';
|
import Slider from './Slider';
|
||||||
|
import Announcement from './Announcement';
|
||||||
import { outfitURL } from '../utils/UrlGenerators';
|
import { outfitURL } from '../utils/UrlGenerators';
|
||||||
|
|
||||||
const SIZE_MIN = 0.65;
|
const SIZE_MIN = 0.65;
|
||||||
@@ -55,12 +56,11 @@ function selectAll(e) {
|
|||||||
* Coriolis App Header section / menus
|
* Coriolis App Header section / menus
|
||||||
*/
|
*/
|
||||||
export default class Header extends TranslatedComponent {
|
export default class Header extends TranslatedComponent {
|
||||||
|
/**
|
||||||
/**
|
* 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.shipOrder = Object.keys(Ships).sort();
|
this.shipOrder = Object.keys(Ships).sort();
|
||||||
@@ -76,8 +76,11 @@ export default class Header extends TranslatedComponent {
|
|||||||
this._openShips = this._openMenu.bind(this, 's');
|
this._openShips = this._openMenu.bind(this, 's');
|
||||||
this._openBuilds = this._openMenu.bind(this, 'b');
|
this._openBuilds = this._openMenu.bind(this, 'b');
|
||||||
this._openComp = this._openMenu.bind(this, 'comp');
|
this._openComp = this._openMenu.bind(this, 'comp');
|
||||||
|
this._openAnnounce = this._openMenu.bind(this, 'announce');
|
||||||
|
this._getAnnouncementsMenu = this._getAnnouncementsMenu.bind(this);
|
||||||
this._openSettings = this._openMenu.bind(this, 'settings');
|
this._openSettings = this._openMenu.bind(this, 'settings');
|
||||||
this._showHelp = this._showHelp.bind(this);
|
this._showHelp = this._showHelp.bind(this);
|
||||||
|
this.update = this.update.bind(this);
|
||||||
this.languageOptions = [];
|
this.languageOptions = [];
|
||||||
this.insuranceOptions = [];
|
this.insuranceOptions = [];
|
||||||
this.state = {
|
this.state = {
|
||||||
@@ -345,7 +348,7 @@ export default class Header extends TranslatedComponent {
|
|||||||
_getShipsMenu() {
|
_getShipsMenu() {
|
||||||
let shipList = [];
|
let shipList = [];
|
||||||
|
|
||||||
for (let s in Ships) {
|
for (let s of this.shipOrder) {
|
||||||
shipList.push(<ActiveLink key={s} href={outfitURL(s)} className='block'>{Ships[s].properties.name}</ActiveLink>);
|
shipList.push(<ActiveLink key={s} href={outfitURL(s)} className='block'>{Ships[s].properties.name}</ActiveLink>);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -411,6 +414,29 @@ export default class Header extends TranslatedComponent {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate the announcement menu
|
||||||
|
* @return {React.Component} Menu
|
||||||
|
*/
|
||||||
|
_getAnnouncementsMenu() {
|
||||||
|
let announcements;
|
||||||
|
let translate = this.context.language.translate;
|
||||||
|
|
||||||
|
if (this.props.announcements) {
|
||||||
|
announcements = [];
|
||||||
|
for (let announce of this.props.announcements) {
|
||||||
|
announcements.push(<Announcement text={announce.message} />);
|
||||||
|
announcements.push(<hr/>);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div className='menu-list' onClick={ (e) => e.stopPropagation() } style={{ whiteSpace: 'nowrap' }}>
|
||||||
|
{announcements}
|
||||||
|
<hr />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate the settings menu
|
* Generate the settings menu
|
||||||
* @return {React.Component} Menu
|
* @return {React.Component} Menu
|
||||||
@@ -482,7 +508,7 @@ export default class Header extends TranslatedComponent {
|
|||||||
<td style={{ width: 20 }}><span style={{ fontSize: 30 }}>A</span></td>
|
<td style={{ width: 20 }}><span style={{ fontSize: 30 }}>A</span></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td colSpan='3' style={{ textAlign: 'center', cursor: 'pointer' }} className='primary-disabled cap' onClick={this._resetTextSize.bind(this)}>{translate('reset')}</td>
|
<td colSpan='3' style={{ textAlign: 'center', cursor: 'pointer' }} className='primary-disabled cap' onClick={this._resetTextSize.bind(this)}>{translate('reset')}</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
@@ -534,6 +560,15 @@ export default class Header extends TranslatedComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async update() {
|
||||||
|
const reg = await navigator.serviceWorker.getRegistration();
|
||||||
|
if (!reg || !reg.waiting) {
|
||||||
|
return window.location.reload();
|
||||||
|
}
|
||||||
|
reg.waiting.postMessage('skipWaiting');
|
||||||
|
window.location.reload();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Render the header
|
* Render the header
|
||||||
* @return {React.Component} Header
|
* @return {React.Component} Header
|
||||||
@@ -544,7 +579,7 @@ export default class Header extends TranslatedComponent {
|
|||||||
let hasBuilds = Persist.hasBuilds();
|
let hasBuilds = Persist.hasBuilds();
|
||||||
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={this.update}>{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">
|
{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'}
|
{'View Release Changes'}
|
||||||
</a> : null}
|
</a> : null}
|
||||||
@@ -571,6 +606,13 @@ export default class Header extends TranslatedComponent {
|
|||||||
{openedMenu == 'comp' ? this._getComparisonsMenu() : null}
|
{openedMenu == 'comp' ? this._getComparisonsMenu() : null}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className='l menu'>
|
||||||
|
<div className={cn('menu-header', { selected: openedMenu == 'announce', disabled: this.props.announcements.length === 0 })} onClick={this.props.announcements.length !== 0 && this._openAnnounce}>
|
||||||
|
<span className='menu-item-label'>{translate('announcements')}</span>
|
||||||
|
</div>
|
||||||
|
{openedMenu == 'announce' ? this._getAnnouncementsMenu() : null}
|
||||||
|
</div>
|
||||||
|
|
||||||
{window.location.origin.search('.edcd.io') >= 0 ?
|
{window.location.origin.search('.edcd.io') >= 0 ?
|
||||||
<div className='l menu'>
|
<div className='l menu'>
|
||||||
<a href="https://youtu.be/4SvnLcefhtI" target="_blank">
|
<a href="https://youtu.be/4SvnLcefhtI" target="_blank">
|
||||||
@@ -596,5 +638,4 @@ export default class Header extends TranslatedComponent {
|
|||||||
</header>
|
</header>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,99 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import cn from 'classnames';
|
|
||||||
import Slot from './Slot';
|
|
||||||
import Persist from '../stores/Persist';
|
|
||||||
import { ListModifications, Modified } from './SvgIcons';
|
|
||||||
import { Modifications } from 'coriolis-data/dist';
|
|
||||||
import { stopCtxPropagation } from '../utils/UtilityFunctions';
|
|
||||||
import { blueprintTooltip } from '../utils/BlueprintFunctions';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Internal Slot
|
|
||||||
*/
|
|
||||||
export default class InternalSlot extends Slot {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generate the slot contents
|
|
||||||
* @param {Object} m Mounted Module
|
|
||||||
* @param {Boolean} enabled Slot enabled
|
|
||||||
* @param {Function} translate Translate function
|
|
||||||
* @param {Object} formats Localized Formats map
|
|
||||||
* @param {Object} u Localized Units Map
|
|
||||||
* @return {React.Component} Slot contents
|
|
||||||
*/
|
|
||||||
_getSlotDetails(m, enabled, translate, formats, u) {
|
|
||||||
if (m) {
|
|
||||||
let classRating = m.class + m.rating;
|
|
||||||
let { drag, drop, ship } = this.props;
|
|
||||||
let { termtip, tooltip } = this.context;
|
|
||||||
let validMods = (Modifications.modules[m.grp] ? Modifications.modules[m.grp].modifications : []);
|
|
||||||
let showModuleResistances = Persist.showModuleResistances();
|
|
||||||
|
|
||||||
// Modifications tooltip shows blueprint and grade, if available
|
|
||||||
let modTT = translate('modified');
|
|
||||||
if (m && m.blueprint && m.blueprint.name) {
|
|
||||||
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 = (
|
|
||||||
<div>
|
|
||||||
<div>{modTT}</div>
|
|
||||||
{blueprintTooltip(translate, m.blueprint.grades[m.blueprint.grade], null, m.grp, m)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let mass = m.getMass() || m.cargo || m.fuel || 0;
|
|
||||||
|
|
||||||
const className = cn('details', enabled ? '' : 'disabled');
|
|
||||||
return <div className={className} draggable='true' onDragStart={drag} onDragEnd={drop}>
|
|
||||||
<div className={'cb'}>
|
|
||||||
<div className={'l'}>{classRating} {translate(m.name || m.grp)}{m.mods && Object.keys(m.mods).length > 0 ? <span onMouseOver={termtip.bind(null, modTT)} onMouseOut={tooltip.bind(null, null)}><Modified /></span> : ''}</div>
|
|
||||||
<div className={'r'}>{formats.round(mass)}{u.T}</div>
|
|
||||||
</div>
|
|
||||||
<div className={'cb'}>
|
|
||||||
{ m.getOptMass() ? <div className={'l'}>{translate('optmass', 'sg')}: {formats.int(m.getOptMass())}{u.T}</div> : null }
|
|
||||||
{ m.getMaxMass() ? <div className={'l'}>{translate('maxmass', 'sg')}: {formats.int(m.getMaxMass())}{u.T}</div> : null }
|
|
||||||
{ m.bins ? <div className={'l'}>{m.bins} <u>{translate('bins')}</u></div> : null }
|
|
||||||
{ m.bays ? <div className={'l'}>{translate('bays')}: {m.bays}</div> : null }
|
|
||||||
{ m.rebuildsperbay ? <div className={'l'}>{translate('rebuildsperbay')}: {m.rebuildsperbay}</div> : null }
|
|
||||||
{ m.rate ? <div className={'l'}>{translate('rate')}: {m.rate}{u.kgs} {translate('refuel time')}: {formats.time(this.props.fuel * 1000 / m.rate)}</div> : null }
|
|
||||||
{ m.getAmmo() && m.grp !== 'scb' ? <div className={'l'}>{translate('ammunition')}: {formats.gen(m.getAmmo())}</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.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('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.getFacingLimit() ? <div className={'l'}>{translate('facinglimit')} {formats.f1(m.getFacingLimit())}°</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.getTime() ? <div className={'l'}>{translate('time')}: {formats.time(m.getTime())}</div> : null }
|
|
||||||
{ m.getHackTime() ? <div className={'l'}>{translate('hacktime')}: {formats.time(m.getHackTime())}</div> : null }
|
|
||||||
{ m.maximum ? <div className={'l'}>{translate('max')}: {(m.maximum)}</div> : null }
|
|
||||||
{ m.rangeLS ? <div className={'l'}>{translate('range')}: {m.rangeLS}{u.Ls}</div> : null }
|
|
||||||
{ m.rangeLS === null ? <div className={'l'}>∞{u.Ls}</div> : null }
|
|
||||||
{ m.rangeRating ? <div className={'l'}>{translate('range')}: {m.rangeRating}</div> : null }
|
|
||||||
{ m.maximum ? <div className={'l'}>{translate('max')}: {(m.maximum)}</div> : null }
|
|
||||||
{ m.passengers ? <div className={'l'}>{translate('passengers')}: {m.passengers}</div> : null }
|
|
||||||
{ m.getRegenerationRate() ? <div className='l'>{translate('regen')}: {formats.round1(m.getRegenerationRate())}{u.ps}</div> : null }
|
|
||||||
{ m.getBrokenRegenerationRate() ? <div className='l'>{translate('brokenregen')}: {formats.round1(m.getBrokenRegenerationRate())}{u.ps}</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.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.getProtection() ? <div className='l'>{translate('protection')}: {formats.rPct(m.getProtection())}</div> : null }
|
|
||||||
{ m.getIntegrity() ? <div className='l'>{translate('integrity')}: {formats.int(m.getIntegrity())}</div> : null }
|
|
||||||
{ m && validMods.length > 0 ? <div className='r' tabIndex="0" ref={ modButton => this.modButton = modButton }><button tabIndex="-1" onClick={this._toggleModifications.bind(this)} onContextMenu={stopCtxPropagation} onMouseOver={termtip.bind(null, 'modifications')} onMouseOut={tooltip.bind(null, null)}><ListModifications /></button></div> : null }
|
|
||||||
</div>
|
|
||||||
</div>;
|
|
||||||
} else {
|
|
||||||
return <div className={'empty'}>{translate('empty')}</div>;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,53 +1,30 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import cn from 'classnames';
|
|
||||||
import SlotSection from './SlotSection';
|
import SlotSection from './SlotSection';
|
||||||
import InternalSlot from './InternalSlot';
|
import Slot from './Slot';
|
||||||
import * as ModuleUtils from '../shipyard/ModuleUtils';
|
import * as ModuleUtils from '../shipyard/ModuleUtils';
|
||||||
import { stopCtxPropagation } from '../utils/UtilityFunctions';
|
import { stopCtxPropagation } from '../utils/UtilityFunctions';
|
||||||
import { canMount } from '../utils/SlotFunctions';
|
import { canMount } from '../utils/SlotFunctions';
|
||||||
|
import autoBind from 'auto-bind';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Internal slot section
|
* Internal slot section
|
||||||
*/
|
*/
|
||||||
export default class InternalSlotSection extends SlotSection {
|
export default class InternalSlotSection extends SlotSection {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor
|
* Constructor
|
||||||
* @param {Object} props React Component properties
|
* @param {Object} props React Component properties
|
||||||
* @param {Object} context React Component context
|
|
||||||
*/
|
*/
|
||||||
constructor(props, context) {
|
constructor(props) {
|
||||||
super(props, context, 'internal', 'optional internal');
|
super(props, 'optional internal');
|
||||||
this._empty = this._empty.bind(this);
|
autoBind(this);
|
||||||
this._fillWithCargo = this._fillWithCargo.bind(this);
|
|
||||||
this._fillWithCells = this._fillWithCells.bind(this);
|
|
||||||
this._fillWithArmor = this._fillWithArmor.bind(this);
|
|
||||||
this._fillWithModuleReinforcementPackages = this._fillWithModuleReinforcementPackages.bind(this);
|
|
||||||
this._fillWithFuelTanks = this._fillWithFuelTanks.bind(this);
|
|
||||||
this._fillWithLuxuryCabins = this._fillWithLuxuryCabins.bind(this);
|
|
||||||
this._fillWithFirstClassCabins = this._fillWithFirstClassCabins.bind(this);
|
|
||||||
this._fillWithBusinessClassCabins = this._fillWithBusinessClassCabins.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';
|
// TODO:
|
||||||
this.props.ship.emptyInternal();
|
// this.props.ship.emptyInternal();
|
||||||
this.props.onChange();
|
|
||||||
this._close();
|
this._close();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,7 +33,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) => {
|
||||||
@@ -64,7 +40,6 @@ export default class InternalSlotSection extends SlotSection {
|
|||||||
ship.use(slot, ModuleUtils.findInternal('cr', slot.maxClass, 'E'));
|
ship.use(slot, ModuleUtils.findInternal('cr', slot.maxClass, 'E'));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
this.props.onChange();
|
|
||||||
this._close();
|
this._close();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -73,7 +48,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) => {
|
||||||
@@ -81,7 +55,6 @@ export default class InternalSlotSection extends SlotSection {
|
|||||||
ship.use(slot, ModuleUtils.findInternal('ft', slot.maxClass, 'C'));
|
ship.use(slot, ModuleUtils.findInternal('ft', slot.maxClass, 'C'));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
this.props.onChange();
|
|
||||||
this._close();
|
this._close();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,7 +63,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) => {
|
||||||
@@ -98,7 +70,6 @@ export default class InternalSlotSection extends SlotSection {
|
|||||||
ship.use(slot, ModuleUtils.findInternal('pcq', Math.min(slot.maxClass, 6), 'B')); // Passenger cabins top out at 6
|
ship.use(slot, ModuleUtils.findInternal('pcq', Math.min(slot.maxClass, 6), 'B')); // Passenger cabins top out at 6
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
this.props.onChange();
|
|
||||||
this._close();
|
this._close();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -107,7 +78,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) => {
|
||||||
@@ -115,7 +85,6 @@ export default class InternalSlotSection extends SlotSection {
|
|||||||
ship.use(slot, ModuleUtils.findInternal('pcm', Math.min(slot.maxClass, 6), 'C')); // Passenger cabins top out at 6
|
ship.use(slot, ModuleUtils.findInternal('pcm', Math.min(slot.maxClass, 6), 'C')); // Passenger cabins top out at 6
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
this.props.onChange();
|
|
||||||
this._close();
|
this._close();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -124,7 +93,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) => {
|
||||||
@@ -132,7 +100,6 @@ export default class InternalSlotSection extends SlotSection {
|
|||||||
ship.use(slot, ModuleUtils.findInternal('pci', Math.min(slot.maxClass, 6), 'D')); // Passenger cabins top out at 6
|
ship.use(slot, ModuleUtils.findInternal('pci', Math.min(slot.maxClass, 6), 'D')); // Passenger cabins top out at 6
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
this.props.onChange();
|
|
||||||
this._close();
|
this._close();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -141,7 +108,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) => {
|
||||||
@@ -149,7 +115,6 @@ export default class InternalSlotSection extends SlotSection {
|
|||||||
ship.use(slot, ModuleUtils.findInternal('pce', Math.min(slot.maxClass, 6), 'E')); // Passenger cabins top out at 6
|
ship.use(slot, ModuleUtils.findInternal('pce', Math.min(slot.maxClass, 6), 'E')); // Passenger cabins top out at 6
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
this.props.onChange();
|
|
||||||
this._close();
|
this._close();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -158,7 +123,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
|
||||||
@@ -169,7 +133,6 @@ export default class InternalSlotSection extends SlotSection {
|
|||||||
chargeCap += slot.m.recharge;
|
chargeCap += slot.m.recharge;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
this.props.onChange();
|
|
||||||
this._close();
|
this._close();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -178,7 +141,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) => {
|
||||||
@@ -186,7 +148,6 @@ export default class InternalSlotSection extends SlotSection {
|
|||||||
ship.use(slot, ModuleUtils.findInternal('hr', Math.min(slot.maxClass, 5), 'D')); // Hull reinforcements top out at 5D
|
ship.use(slot, ModuleUtils.findInternal('hr', Math.min(slot.maxClass, 5), 'D')); // Hull reinforcements top out at 5D
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
this.props.onChange();
|
|
||||||
this._close();
|
this._close();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -195,7 +156,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) => {
|
||||||
@@ -203,7 +163,6 @@ export default class InternalSlotSection extends SlotSection {
|
|||||||
ship.use(slot, ModuleUtils.findInternal('mrp', Math.min(slot.maxClass, 5), 'D')); // Module reinforcements top out at 5D
|
ship.use(slot, ModuleUtils.findInternal('mrp', Math.min(slot.maxClass, 5), 'D')); // Module reinforcements top out at 5D
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
this.props.onChange();
|
|
||||||
this._close();
|
this._close();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -220,31 +179,20 @@ export default class InternalSlotSection extends SlotSection {
|
|||||||
*/
|
*/
|
||||||
_getSlots() {
|
_getSlots() {
|
||||||
let slots = [];
|
let slots = [];
|
||||||
let { currentMenu, ship } = this.props;
|
let { currentMenu, ship, propsToShow, onPropToggle } = this.props;
|
||||||
let { originSlot, targetSlot } = this.state;
|
let { originSlot, targetSlot } = this.state;
|
||||||
let { internal, fuelCapacity } = ship;
|
|
||||||
let availableModules = ship.getAvailableModules();
|
|
||||||
|
|
||||||
for (let i = 0, l = internal.length; i < l; i++) {
|
for (const m of ship.getInternals(undefined, true)) {
|
||||||
let s = internal[i];
|
slots.push(<Slot
|
||||||
|
key={m.object.Slot}
|
||||||
slots.push(<InternalSlot
|
currentMenu={currentMenu}
|
||||||
key={i}
|
m={m}
|
||||||
maxClass={s.maxClass}
|
drag={this._drag.bind(this, m)}
|
||||||
availableModules={() => availableModules.getInts(ship, s.maxClass, s.eligible)}
|
dragOver={this._dragOverSlot.bind(this, m)}
|
||||||
onOpen={this._openMenu.bind(this,s)}
|
|
||||||
onChange={this.props.onChange}
|
|
||||||
onSelect={this._selectModule.bind(this, s)}
|
|
||||||
selected={currentMenu == s}
|
|
||||||
eligible={s.eligible}
|
|
||||||
m={s.m}
|
|
||||||
drag={this._drag.bind(this, s)}
|
|
||||||
dragOver={this._dragOverSlot.bind(this, s)}
|
|
||||||
drop={this._drop}
|
drop={this._drop}
|
||||||
dropClass={this._dropClass(s, originSlot, targetSlot)}
|
dropClass={this._dropClass(m, originSlot, targetSlot)}
|
||||||
fuel={fuelCapacity}
|
propsToShow={propsToShow}
|
||||||
ship={ship}
|
onPropToggle={onPropToggle}
|
||||||
enabled={s.enabled ? true : false}
|
|
||||||
/>);
|
/>);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -257,22 +205,23 @@ export default class InternalSlotSection extends SlotSection {
|
|||||||
* @param {Function} ship The ship
|
* @param {Function} ship The ship
|
||||||
* @return {React.Component} Section menu
|
* @return {React.Component} Section menu
|
||||||
*/
|
*/
|
||||||
_getSectionMenu(translate, ship) {
|
_getSectionMenu() {
|
||||||
|
const { ship } = this.props;
|
||||||
|
const { translate } = this.context.language;
|
||||||
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' tabIndex='0' 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' tabIndex='0' 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' tabIndex='0' 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' tabIndex='0' 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' tabIndex='0' 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' tabIndex='0' 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' tabIndex='0' 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' tabIndex='0' 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' tabIndex='0' onClick={this._fillWithFirstClassCabins} onKeyDown={ship.luxuryCabins ? '' : this._keyDown}>{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' tabIndex='0' 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>;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,8 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import TranslatedComponent from './TranslatedComponent';
|
import TranslatedComponent from './TranslatedComponent';
|
||||||
import { Ships } from 'coriolis-data/dist';
|
|
||||||
import { nameComparator } from '../utils/SlotFunctions';
|
|
||||||
import LineChart from '../components/LineChart';
|
import LineChart from '../components/LineChart';
|
||||||
import Slider from '../components/Slider';
|
import Slider from '../components/Slider';
|
||||||
import * as ModuleUtils from '../shipyard/ModuleUtils';
|
|
||||||
import Module from '../shipyard/Module';
|
|
||||||
import * as Calc from '../shipyard/Calculations';
|
import * as Calc from '../shipyard/Calculations';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
|
|||||||
import ContainerDimensions from 'react-container-dimensions';
|
import ContainerDimensions from 'react-container-dimensions';
|
||||||
import * as d3 from 'd3';
|
import * as d3 from 'd3';
|
||||||
import TranslatedComponent from './TranslatedComponent';
|
import TranslatedComponent from './TranslatedComponent';
|
||||||
|
import autoBind from 'auto-bind';
|
||||||
|
|
||||||
const MARGIN = { top: 15, right: 20, bottom: 35, left: 60 };
|
const MARGIN = { top: 15, right: 20, bottom: 35, left: 60 };
|
||||||
|
|
||||||
@@ -10,7 +11,6 @@ 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,
|
||||||
@@ -45,13 +45,7 @@ export default class LineChart extends TranslatedComponent {
|
|||||||
*/
|
*/
|
||||||
constructor(props, context) {
|
constructor(props, context) {
|
||||||
super(props);
|
super(props);
|
||||||
|
autoBind(this);
|
||||||
this._updateDimensions = this._updateDimensions.bind(this);
|
|
||||||
this._updateSeries = this._updateSeries.bind(this);
|
|
||||||
this._tooltip = this._tooltip.bind(this);
|
|
||||||
this._showTip = this._showTip.bind(this);
|
|
||||||
this._hideTip = this._hideTip.bind(this);
|
|
||||||
this._moveTip = this._moveTip.bind(this);
|
|
||||||
|
|
||||||
const series = props.series;
|
const series = props.series;
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import { shallowEqual } from '../utils/UtilityFunctions';
|
|||||||
* Link wrapper component
|
* Link wrapper component
|
||||||
*/
|
*/
|
||||||
export default class Link extends React.Component {
|
export default class Link extends React.Component {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
children: PropTypes.any,
|
children: PropTypes.any,
|
||||||
href: PropTypes.string.isRequired,
|
href: PropTypes.string.isRequired,
|
||||||
@@ -56,5 +55,4 @@ export default class Link extends React.Component {
|
|||||||
render() {
|
render() {
|
||||||
return <a {...this.props} onClick={this.handler}>{this.props.children}</a>;
|
return <a {...this.props} onClick={this.handler}>{this.props.children}</a>;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import Persist from '../stores/Persist';
|
|||||||
* Permalink modal
|
* Permalink modal
|
||||||
*/
|
*/
|
||||||
export default class ModalBatchOrbis extends TranslatedComponent {
|
export default class ModalBatchOrbis extends TranslatedComponent {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
ships: PropTypes.any.isRequired
|
ships: PropTypes.any.isRequired
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ function buildComparator(a, b) {
|
|||||||
* Compare builds modal
|
* Compare builds modal
|
||||||
*/
|
*/
|
||||||
export default class ModalCompare extends TranslatedComponent {
|
export default class ModalCompare extends TranslatedComponent {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
onSelect: PropTypes.func.isRequired,
|
onSelect: PropTypes.func.isRequired,
|
||||||
builds: PropTypes.array
|
builds: PropTypes.array
|
||||||
@@ -105,8 +104,8 @@ export default class ModalCompare extends TranslatedComponent {
|
|||||||
|
|
||||||
let selectedBuilds = usedBuilds.map((build, i) =>
|
let selectedBuilds = usedBuilds.map((build, i) =>
|
||||||
<tr key={i} onClick={this._removeBuild.bind(this, i)}>
|
<tr key={i} onClick={this._removeBuild.bind(this, i)}>
|
||||||
<td className='tl'>{build.name}</td><
|
<td className='tl'>{build.name}</td>
|
||||||
td className='tl'>{build.buildName}</td>
|
<td className='tl'>{build.buildName}</td>
|
||||||
</tr>
|
</tr>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import Persist from '../stores/Persist';
|
|||||||
* Delete All saved data modal
|
* Delete All saved data modal
|
||||||
*/
|
*/
|
||||||
export default class ModalDeleteAll extends TranslatedComponent {
|
export default class ModalDeleteAll extends TranslatedComponent {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Delete everything and hide the modal
|
* Delete everything and hide the modal
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import TranslatedComponent from './TranslatedComponent';
|
|||||||
* Export Modal
|
* Export Modal
|
||||||
*/
|
*/
|
||||||
export default class ModalExport extends TranslatedComponent {
|
export default class ModalExport extends TranslatedComponent {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
title: PropTypes.string,
|
title: PropTypes.string,
|
||||||
generator: PropTypes.func,
|
generator: PropTypes.func,
|
||||||
|
|||||||
@@ -7,19 +7,10 @@ import TranslatedComponent from './TranslatedComponent';
|
|||||||
* Help Modal
|
* Help Modal
|
||||||
*/
|
*/
|
||||||
export default class ModalHelp extends TranslatedComponent {
|
export default class ModalHelp extends TranslatedComponent {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
title: PropTypes.string
|
title: PropTypes.string
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructor
|
|
||||||
* @param {Object} props React Component properties
|
|
||||||
*/
|
|
||||||
constructor(props) {
|
|
||||||
super(props);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Render the modal
|
* Render the modal
|
||||||
* @return {React.Component} Modal Content
|
* @return {React.Component} Modal Content
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ import { Download } from './SvgIcons';
|
|||||||
import { outfitURL } from '../utils/UrlGenerators';
|
import { outfitURL } from '../utils/UrlGenerators';
|
||||||
import * as CompanionApiUtils from '../utils/CompanionApiUtils';
|
import * as CompanionApiUtils from '../utils/CompanionApiUtils';
|
||||||
|
|
||||||
|
const zlib = require('pako');
|
||||||
|
|
||||||
const textBuildRegex = new RegExp('^\\[([\\w \\-]+)\\]\n');
|
const textBuildRegex = new RegExp('^\\[([\\w \\-]+)\\]\n');
|
||||||
const lineRegex = new RegExp('^([\\dA-Z]{1,2}): (\\d)([A-I])[/]?([FGT])?([SD])? ([\\w\\- ]+)');
|
const lineRegex = new RegExp('^([\\dA-Z]{1,2}): (\\d)([A-I])[/]?([FGT])?([SD])? ([\\w\\- ]+)');
|
||||||
const mountMap = { 'H': 4, 'L': 3, 'M': 2, 'S': 1, 'U': 0 };
|
const mountMap = { 'H': 4, 'L': 3, 'M': 2, 'S': 1, 'U': 0 };
|
||||||
@@ -83,8 +85,6 @@ function detailedJsonToBuild(detailedBuild) {
|
|||||||
* Import Modal
|
* Import Modal
|
||||||
*/
|
*/
|
||||||
export default class ModalImport extends TranslatedComponent {
|
export default class ModalImport extends TranslatedComponent {
|
||||||
|
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
builds: PropTypes.object, // Optional: Import object
|
builds: PropTypes.object, // Optional: Import object
|
||||||
};
|
};
|
||||||
@@ -99,6 +99,7 @@ export default class ModalImport extends TranslatedComponent {
|
|||||||
this.state = {
|
this.state = {
|
||||||
builds: props.builds,
|
builds: props.builds,
|
||||||
canEdit: !props.builds,
|
canEdit: !props.builds,
|
||||||
|
loadoutEvent: null,
|
||||||
comparisons: null,
|
comparisons: null,
|
||||||
shipDiscount: null,
|
shipDiscount: null,
|
||||||
moduleDiscount: null,
|
moduleDiscount: null,
|
||||||
@@ -111,12 +112,28 @@ export default class ModalImport extends TranslatedComponent {
|
|||||||
this._process = this._process.bind(this);
|
this._process = this._process.bind(this);
|
||||||
this._import = this._import.bind(this);
|
this._import = this._import.bind(this);
|
||||||
this._importBackup = this._importBackup.bind(this);
|
this._importBackup = this._importBackup.bind(this);
|
||||||
|
this._importLoadout = this._importLoadout.bind(this);
|
||||||
this._importDetailedArray = this._importDetailedArray.bind(this);
|
this._importDetailedArray = this._importDetailedArray.bind(this);
|
||||||
this._importTextBuild = this._importTextBuild.bind(this);
|
this._importTextBuild = this._importTextBuild.bind(this);
|
||||||
this._importCompanionApiBuild = this._importCompanionApiBuild.bind(this);
|
this._importCompanionApiBuild = this._importCompanionApiBuild.bind(this);
|
||||||
this._validateImport = this._validateImport.bind(this);
|
this._validateImport = this._validateImport.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Import a Loadout event from Elite: Dangerous journal files
|
||||||
|
* @param {Object} data Loadout event
|
||||||
|
* @throws {string} If import fails
|
||||||
|
*/
|
||||||
|
_importLoadout(data) {
|
||||||
|
if (data && data.Ship && data.Modules) {
|
||||||
|
const deflated = zlib.deflate(JSON.stringify(data), { to: 'string' });
|
||||||
|
let compressed = btoa(deflated);
|
||||||
|
this.setState({ loadoutEvent: compressed });
|
||||||
|
} else {
|
||||||
|
throw 'Loadout event must contain Ship and Modules';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Import a Coriolis backup
|
* Import a Coriolis backup
|
||||||
* @param {Object} importData Backup Data
|
* @param {Object} importData Backup Data
|
||||||
@@ -159,7 +176,7 @@ export default class ModalImport extends TranslatedComponent {
|
|||||||
}
|
}
|
||||||
// Check for module discount
|
// Check for module discount
|
||||||
if (!isNaN(importData.moduleDiscount)) {
|
if (!isNaN(importData.moduleDiscount)) {
|
||||||
this.setState({ shipDiscount: importData.moduleDiscount * 1 });
|
this.setState({ moduleDiscount: importData.moduleDiscount * 1 });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof importData.insurance == 'string') {
|
if (typeof importData.insurance == 'string') {
|
||||||
@@ -345,12 +362,14 @@ export default class ModalImport extends TranslatedComponent {
|
|||||||
} else if (importData.ship && typeof importData.name !== undefined) { // Using JSON from a single ship build export
|
} else if (importData.ship && typeof importData.name !== undefined) { // Using JSON from a single ship build export
|
||||||
this._importDetailedArray([importData]); // Convert to array with singleobject
|
this._importDetailedArray([importData]); // Convert to array with singleobject
|
||||||
this.setState({ singleBuild: true });
|
this.setState({ singleBuild: true });
|
||||||
|
} else if (importData.Modules != null && importData.Modules[0] != null) {
|
||||||
|
this._importLoadout(importData);
|
||||||
} else { // Using Backup JSON
|
} else { // Using Backup JSON
|
||||||
this._importBackup(importData);
|
this._importBackup(importData);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// console.log(e.stack);
|
console.log(e);
|
||||||
this.setState({ errorMsg: (typeof e == 'string') ? e : 'Cannot Parse the data!' });
|
this.setState({ errorMsg: (typeof e == 'string') ? e : 'Cannot Parse the data!' });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -364,6 +383,10 @@ export default class ModalImport extends TranslatedComponent {
|
|||||||
_process() {
|
_process() {
|
||||||
let builds = null, comparisons = null;
|
let builds = null, comparisons = null;
|
||||||
|
|
||||||
|
if (this.state.loadoutEvent) {
|
||||||
|
return Router.go(`/import?data=${this.state.loadoutEvent}`);
|
||||||
|
}
|
||||||
|
|
||||||
// If only importing a single build go straight to the outfitting page
|
// If only importing a single build go straight to the outfitting page
|
||||||
if (this.state.singleBuild) {
|
if (this.state.singleBuild) {
|
||||||
builds = this.state.builds;
|
builds = this.state.builds;
|
||||||
@@ -480,7 +503,7 @@ export default class ModalImport extends TranslatedComponent {
|
|||||||
if (!state.processed) {
|
if (!state.processed) {
|
||||||
importStage = (
|
importStage = (
|
||||||
<div>
|
<div>
|
||||||
<textarea className='cb json' ref={node => this.importField = node} onChange={this._validateImport} defaultValue={this.state.importString} placeholder={translate('PHRASE_IMPORT')} />
|
<textarea spellCheck={false} className='cb json' ref={node => this.importField = node} onChange={this._validateImport} defaultValue={this.state.importString} placeholder={translate('PHRASE_IMPORT')} />
|
||||||
<button id='proceed' className='l cap' onClick={this._process} disabled={!state.importValid} >{translate('proceed')}</button>
|
<button id='proceed' className='l cap' onClick={this._process} disabled={!state.importValid} >{translate('proceed')}</button>
|
||||||
<div className='l warning' style={{ marginLeft:'3em' }}>{state.errorMsg}</div>
|
<div className='l warning' style={{ marginLeft:'3em' }}>{state.errorMsg}</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -517,7 +540,7 @@ export default class ModalImport extends TranslatedComponent {
|
|||||||
{comparisonRows}
|
{comparisonRows}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(this.state.canEdit) {
|
if(this.state.canEdit) {
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import Persist from '../stores/Persist';
|
|||||||
* Permalink modal
|
* Permalink modal
|
||||||
*/
|
*/
|
||||||
export default class ModalOrbis extends TranslatedComponent {
|
export default class ModalOrbis extends TranslatedComponent {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
ship: PropTypes.any.isRequired
|
ship: PropTypes.any.isRequired
|
||||||
};
|
};
|
||||||
@@ -23,8 +22,10 @@ export default class ModalOrbis extends TranslatedComponent {
|
|||||||
this.state = {
|
this.state = {
|
||||||
orbisCreds: Persist.getOrbisCreds(),
|
orbisCreds: Persist.getOrbisCreds(),
|
||||||
orbisUrl: '...',
|
orbisUrl: '...',
|
||||||
|
ship: this.props.ship,
|
||||||
authenticatedStatus: 'Checking...'
|
authenticatedStatus: 'Checking...'
|
||||||
};
|
};
|
||||||
|
this.orbisCategory = this.orbisCategory.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -56,14 +57,14 @@ export default class ModalOrbis extends TranslatedComponent {
|
|||||||
credentials: 'include',
|
credentials: 'include',
|
||||||
mode: 'cors'
|
mode: 'cors'
|
||||||
})
|
})
|
||||||
.then(data => data.json())
|
.then(data => data.json())
|
||||||
.then(res => {
|
.then(res => {
|
||||||
this.setState({ authenticatedStatus: res.status || res.error });
|
this.setState({ authenticatedStatus: res.status || res.error });
|
||||||
})
|
})
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
this.setState({ authenticatedStatus: err.message });
|
this.setState({ authenticatedStatus: err.message });
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -88,6 +89,17 @@ export default class ModalOrbis extends TranslatedComponent {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handler for changing category
|
||||||
|
* @param {SyntheticEvent} e React Event
|
||||||
|
*/
|
||||||
|
orbisCategory(e) {
|
||||||
|
let ship = this.state.ship;
|
||||||
|
let cat = e.target.value;
|
||||||
|
ship.category = cat;
|
||||||
|
this.setState({ ship });
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Render the modal
|
* Render the modal
|
||||||
* @return {React.Component} Modal Content
|
* @return {React.Component} Modal Content
|
||||||
@@ -106,6 +118,17 @@ export default class ModalOrbis extends TranslatedComponent {
|
|||||||
<br/><br/>
|
<br/><br/>
|
||||||
<a className='button' href="https://orbis.zone/api/auth">Log in / signup to Orbis</a>
|
<a className='button' href="https://orbis.zone/api/auth">Log in / signup to Orbis</a>
|
||||||
<br/><br/>
|
<br/><br/>
|
||||||
|
<h3>Category</h3>
|
||||||
|
<select onChange={this.orbisCategory}>
|
||||||
|
<option value="">No Category</option>
|
||||||
|
<option>Combat</option>
|
||||||
|
<option>Mining</option>
|
||||||
|
<option>Trading</option>
|
||||||
|
<option>Exploration</option>
|
||||||
|
<option>Passenger Liner</option>
|
||||||
|
<option>PvP</option>
|
||||||
|
</select>
|
||||||
|
<br/><br/>
|
||||||
<h3 >{translate('Orbis link')}</h3>
|
<h3 >{translate('Orbis link')}</h3>
|
||||||
<input value={this.state.orbisUrl} readOnly size={25} onFocus={ (e) => e.target.select() }/>
|
<input value={this.state.orbisUrl} readOnly size={25} onFocus={ (e) => e.target.select() }/>
|
||||||
<br/><br/>
|
<br/><br/>
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import ShortenUrl from '../utils/ShortenUrl';
|
|||||||
* Permalink modal
|
* Permalink modal
|
||||||
*/
|
*/
|
||||||
export default class ModalPermalink extends TranslatedComponent {
|
export default class ModalPermalink extends TranslatedComponent {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
url: PropTypes.string.isRequired
|
url: PropTypes.string.isRequired
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import Persist from '../stores/Persist';
|
|||||||
* Permalink modal
|
* Permalink modal
|
||||||
*/
|
*/
|
||||||
export default class ModalShoppingList extends TranslatedComponent {
|
export default class ModalShoppingList extends TranslatedComponent {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
ship: PropTypes.object.isRequired
|
ship: PropTypes.object.isRequired
|
||||||
};
|
};
|
||||||
@@ -109,17 +108,18 @@ export default class ModalShoppingList extends TranslatedComponent {
|
|||||||
*/
|
*/
|
||||||
sendToEDEng(event) {
|
sendToEDEng(event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
let translate = this.context.language.translate;
|
||||||
const target = event.target;
|
const target = event.target;
|
||||||
target.disabled = this.state.blueprints.length > 0;
|
target.disabled = this.state.blueprints.length > 0;
|
||||||
if (this.state.blueprints.length === 0) {
|
if (this.state.blueprints.length === 0) {
|
||||||
target.innerText = 'No modded components.';
|
target.innerText = translate('No modded components.');
|
||||||
target.disabled = true;
|
target.disabled = true;
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
target.innerText = 'Send to EDEngineer';
|
target.innerText = translate('Send to EDEngineer');
|
||||||
target.disabled = false;
|
target.disabled = false;
|
||||||
}, 3000);
|
}, 3000);
|
||||||
} else {
|
} else {
|
||||||
target.innerText = 'Sending...';
|
target.innerText = translate('Sending...');
|
||||||
}
|
}
|
||||||
let countSent = 0;
|
let countSent = 0;
|
||||||
let countTotal = this.state.blueprints.length;
|
let countTotal = this.state.blueprints.length;
|
||||||
@@ -139,7 +139,7 @@ export default class ModalShoppingList extends TranslatedComponent {
|
|||||||
countSent++;
|
countSent++;
|
||||||
if (countSent === countTotal) {
|
if (countSent === countTotal) {
|
||||||
target.disabled = false;
|
target.disabled = false;
|
||||||
target.innerText = 'Send to EDEngineer';
|
target.innerText = translate('Send to EDEngineer');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -230,32 +230,32 @@ export default class ModalShoppingList extends TranslatedComponent {
|
|||||||
this.sendToEDEng = this.sendToEDEng.bind(this);
|
this.sendToEDEng = this.sendToEDEng.bind(this);
|
||||||
return <div className='modal' onClick={ (e) => e.stopPropagation() }>
|
return <div className='modal' onClick={ (e) => e.stopPropagation() }>
|
||||||
<h2>{translate('PHRASE_SHOPPING_MATS')}</h2>
|
<h2>{translate('PHRASE_SHOPPING_MATS')}</h2>
|
||||||
<label>Grade 1 rolls </label>
|
<label>{translate('Grade 1 rolls ')}</label>
|
||||||
<input id={1} type={'number'} min={0} defaultValue={this.state.matsPerGrade[1]} onChange={this.changeHandler} />
|
<input id={1} type={'number'} min={0} defaultValue={this.state.matsPerGrade[1]} onChange={this.changeHandler} />
|
||||||
<br/>
|
<br/>
|
||||||
<label>Grade 2 rolls </label>
|
<label>{translate('Grade 2 rolls ')}</label>
|
||||||
<input id={2} type={'number'} min={0} defaultValue={this.state.matsPerGrade[2]} onChange={this.changeHandler} />
|
<input id={2} type={'number'} min={0} defaultValue={this.state.matsPerGrade[2]} onChange={this.changeHandler} />
|
||||||
<br/>
|
<br/>
|
||||||
<label>Grade 3 rolls </label>
|
<label>{translate('Grade 3 rolls ')}</label>
|
||||||
<input id={3} type={'number'} min={0} value={this.state.matsPerGrade[3]} onChange={this.changeHandler} />
|
<input id={3} type={'number'} min={0} value={this.state.matsPerGrade[3]} onChange={this.changeHandler} />
|
||||||
<br/>
|
<br/>
|
||||||
<label>Grade 4 rolls </label>
|
<label>{translate('Grade 4 rolls ')}</label>
|
||||||
<input id={4} type={'number'} min={0} value={this.state.matsPerGrade[4]} onChange={this.changeHandler} />
|
<input id={4} type={'number'} min={0} value={this.state.matsPerGrade[4]} onChange={this.changeHandler} />
|
||||||
<br/>
|
<br/>
|
||||||
<label>Grade 5 rolls </label>
|
<label>{translate('Grade 5 rolls ')}</label>
|
||||||
<input id={5} type={'number'} min={0} value={this.state.matsPerGrade[5]} onChange={this.changeHandler} />
|
<input id={5} type={'number'} min={0} value={this.state.matsPerGrade[5]} onChange={this.changeHandler} />
|
||||||
<div>
|
<div>
|
||||||
<textarea className='cb json' readOnly value={this.state.matsList} />
|
<textarea className='cb json' readOnly value={this.state.matsList} />
|
||||||
</div>
|
</div>
|
||||||
<label hidden={!compatible} className={'l cap'}>CMDR Name </label>
|
<label hidden={!compatible} className={'l cap'}>{translate('CMDR Name')}</label>
|
||||||
<br/>
|
<br/>
|
||||||
<select hidden={!compatible} className={'cmdr-select l cap'} onChange={this.cmdrChangeHandler} defaultValue={this.state.cmdrName}>
|
<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>)}
|
{this.state.cmdrs.map(e => <option key={e}>{e}</option>)}
|
||||||
</select>
|
</select>
|
||||||
<br/>
|
<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={!this.state.failed} id={'failed'} className={'l'}>{translate('PHRASE_FAIL_EDENGINEER')}</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>
|
<p hidden={compatible} id={'browserbad'} className={'l'}>{translate('PHRASE_FIREFOX_EDENGINEER')}</p>
|
||||||
<button className={'l cb dismiss cap'} disabled={!!this.state.failed || !compatible} onClick={this.sendToEDEng}>{translate('Send To EDEngineer')}</button>
|
<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>
|
<button className={'r dismiss cap'} onClick={this.context.hideModal}>{translate('close')}</button>
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,111 +3,106 @@ 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';
|
import { Module } from 'ed-forge';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Modification
|
* Modification
|
||||||
*/
|
*/
|
||||||
export default class Modification extends TranslatedComponent {
|
export default class Modification extends TranslatedComponent {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
ship: PropTypes.object.isRequired,
|
highlight: PropTypes.bool,
|
||||||
m: PropTypes.object.isRequired,
|
m: PropTypes.instanceOf(Module).isRequired,
|
||||||
name: PropTypes.string.isRequired,
|
property: PropTypes.string.isRequired,
|
||||||
value: PropTypes.number.isRequired,
|
onSet: PropTypes.func.isRequired,
|
||||||
onChange: PropTypes.func.isRequired,
|
showProp: PropTypes.object,
|
||||||
onKeyDown: PropTypes.func.isRequired,
|
onPropToggle: PropTypes.func.isRequired,
|
||||||
modItems: PropTypes.array.isRequired,
|
|
||||||
handleModChange: PropTypes.func.isRequired
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor
|
* Constructor
|
||||||
* @param {Object} props React Component properties
|
* @param {Object} props React Component properties
|
||||||
* @param {Object} context React Component context
|
|
||||||
*/
|
*/
|
||||||
constructor(props, context) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {};
|
const { m, property, showProp } = props;
|
||||||
this.state.value = props.value;
|
const { beneficial, unit, value } = m.getFormatted(property, true);
|
||||||
|
this.state = { beneficial, unit, value, showProp };
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update modification given a value.
|
* Notify listeners that a new value has been entered and commited.
|
||||||
* @param {Number} value The value to set. This comes in as a string and must be stored in state as a string,
|
|
||||||
* because it needs to allow illegal 'numbers' ('-', '1.', etc) when the user is typing
|
|
||||||
* in a value by hand
|
|
||||||
*/
|
|
||||||
_updateValue(value) {
|
|
||||||
let { m, name, ship } = this.props;
|
|
||||||
value = Math.max(Math.min(value, 50000), -50000);
|
|
||||||
ship.setModification(m, name, value, true, true);
|
|
||||||
this.setState({ value });
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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) {
|
const { onSet, m, property } = this.props;
|
||||||
this.props.handleModChange(true);
|
const { inputValue } = this.state;
|
||||||
this.props.onChange();
|
const numValue = Number(inputValue);
|
||||||
|
if (!isNaN(numValue) && this.state.value !== numValue) {
|
||||||
|
onSet(property, numValue);
|
||||||
|
const { beneficial, unit, value } = m.getFormatted(property, true);
|
||||||
|
this.setState({ beneficial, unit, value });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_toggleProperty() {
|
||||||
|
const { onPropToggle, property } = this.props;
|
||||||
|
const showProp = !this.state.showProp;
|
||||||
|
// TODO: defer until menu closed
|
||||||
|
onPropToggle(property, showProp);
|
||||||
|
this.setState({ showProp });
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Render the modification
|
* Render the modification
|
||||||
* @return {React.Component} modification
|
* @return {React.Component} modification
|
||||||
*/
|
*/
|
||||||
render() {
|
render() {
|
||||||
let { translate, formats, units } = this.context.language;
|
const { formats } = this.context.language;
|
||||||
let { m, name } = this.props;
|
const { highlight, m, property } = this.props;
|
||||||
let modValue = m.getChange(name);
|
const { beneficial, unit, value, inputValue, showProp } = this.state;
|
||||||
|
|
||||||
if (name === 'damagedist') {
|
// Some features only apply to specific modules; these features will be
|
||||||
// We don't show damage distribution
|
// undefined on items that do not belong to the same class. Filter these
|
||||||
|
// features here
|
||||||
|
if (value === undefined) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { value: modifierValue, unit: modifierUnit } = m.getModifierFormatted(property);
|
||||||
return (
|
return (
|
||||||
<div onBlur={this._updateFinished.bind(this)} key={name}
|
<tr>
|
||||||
className={cn('cb', 'modification-container')}
|
<td>
|
||||||
ref={ modItem => this.props.modItems[name] = modItem }>
|
<span>
|
||||||
<span className={'cb'}>{translate(name, m.grp)}</span>
|
<input type="checkbox" checked={showProp} onClick={() => this._toggleProperty()}/>
|
||||||
<span className={'header-adjuster'}></span>
|
</span>
|
||||||
<table style={{ width: '100%' }}>
|
</td>
|
||||||
<tbody>
|
<td className="input-container">
|
||||||
<tr>
|
<span>
|
||||||
<td className={'input-container'}>
|
<NumberEditor value={inputValue || value} stepModifier={1}
|
||||||
<span>
|
decimals={2} step={0.01} style={{ textAlign: 'right', width: '100%' }}
|
||||||
{this.props.editable ?
|
className={cn('cb', { 'greyed-out': !highlight })}
|
||||||
<NumberEditor className={'cb'} value={this.state.value}
|
onKeyDown={(event) => {
|
||||||
decimals={2} style={{ textAlign: 'right' }} step={0.01}
|
if (event.key == 'Enter') {
|
||||||
stepModifier={1} onKeyDown={ this.props.onKeyDown }
|
this._updateFinished();
|
||||||
onValueChange={this._updateValue.bind(this)} /> :
|
event.stopPropagation();
|
||||||
<input type="text" value={formats.f2(this.state.value)}
|
}
|
||||||
disabled className={'number-editor'}
|
}}
|
||||||
style={{ textAlign: 'right', cursor: 'inherit' }}/>
|
onValueChange={(inputValue) => {
|
||||||
}
|
if (inputValue.length <= 15) {
|
||||||
<span className={'unit-container'}>
|
this.setState({ inputValue });
|
||||||
{units[m.getStoredUnitFor(name)]}
|
}
|
||||||
</span>
|
}} />
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
<td style={{ textAlign: 'center' }} className={
|
<td style={{ textAlign: 'left' }}>
|
||||||
modValue ?
|
<span className="unit-container">{unit}</span>
|
||||||
isValueBeneficial(name, modValue) ? 'secondary': 'warning':
|
</td>
|
||||||
''
|
<td style={{ textAlign: 'center' }}
|
||||||
}>
|
className={cn({
|
||||||
{formats.f2(modValue / 100) || 0}%
|
secondary: beneficial,
|
||||||
</td>
|
warning: beneficial === false,
|
||||||
</tr>
|
})}
|
||||||
</tbody>
|
>{formats.f2(modifierValue)}{modifierUnit || ''}</td>
|
||||||
</table>
|
</tr>
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,35 +1,27 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import * as _ from 'lodash';
|
import { chain, flatMap, keys } from 'lodash';
|
||||||
import TranslatedComponent from './TranslatedComponent';
|
import TranslatedComponent from './TranslatedComponent';
|
||||||
import { isEmpty, stopCtxPropagation } from '../utils/UtilityFunctions';
|
import { stopCtxPropagation } from '../utils/UtilityFunctions';
|
||||||
import cn from 'classnames';
|
import cn from 'classnames';
|
||||||
import { Modifications } from 'coriolis-data/dist';
|
|
||||||
import Modification from './Modification';
|
import Modification from './Modification';
|
||||||
import {
|
import {
|
||||||
getBlueprint,
|
|
||||||
blueprintTooltip,
|
blueprintTooltip,
|
||||||
setPercent,
|
|
||||||
getPercent,
|
|
||||||
setRandom,
|
|
||||||
specialToolTip
|
specialToolTip
|
||||||
} from '../utils/BlueprintFunctions';
|
} from '../utils/BlueprintFunctions';
|
||||||
|
import { getBlueprintInfo, getExperimentalInfo } from 'ed-forge/lib/data/blueprints';
|
||||||
const MODIFICATIONS_COMPARATOR = (mod1, mod2) => {
|
import { getModuleInfo } from 'ed-forge/lib/data/items';
|
||||||
return mod1.props.name.localeCompare(mod2.props.name);
|
import { SHOW } from '../shipyard/StatsMapping';
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Modifications menu
|
* Modifications menu
|
||||||
*/
|
*/
|
||||||
export default class ModificationsMenu extends TranslatedComponent {
|
export default class ModificationsMenu extends TranslatedComponent {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
ship: PropTypes.object.isRequired,
|
className: PropTypes.string,
|
||||||
m: PropTypes.object.isRequired,
|
m: PropTypes.object.isRequired,
|
||||||
marker: PropTypes.string.isRequired,
|
propsToShow: PropTypes.object.isRequired,
|
||||||
onChange: PropTypes.func.isRequired,
|
onPropToggle: PropTypes.func.isRequired,
|
||||||
modButton:PropTypes.object
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -42,327 +34,177 @@ 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.selectedModRef = null;
|
||||||
this._rollRandom = this._rollRandom.bind(this);
|
this.selectedSpecialRef = null;
|
||||||
this._rollBest = this._rollBest.bind(this);
|
|
||||||
this._rollWorst = this._rollWorst.bind(this);
|
|
||||||
this._reset = this._reset.bind(this);
|
|
||||||
this._keyDown = this._keyDown.bind(this);
|
|
||||||
this.modItems = [];// Array to hold various element refs (<li>, <div>, <ul>, etc.)
|
|
||||||
this.firstModId = null;
|
|
||||||
this.firstBPLabel = null;// First item in mod menu
|
|
||||||
this.lastModId = null;
|
|
||||||
this.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);
|
|
||||||
|
|
||||||
|
const { m } = props;
|
||||||
this.state = {
|
this.state = {
|
||||||
blueprintMenuOpened: !(props.m.blueprint && props.m.blueprint.name),
|
blueprintProgress: m.getBlueprintProgress(),
|
||||||
|
blueprintMenuOpened: !m.getBlueprint(),
|
||||||
specialMenuOpened: false
|
specialMenuOpened: false
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Render the blueprints
|
* Render the blueprints
|
||||||
* @param {Object} props React component properties
|
|
||||||
* @param {Object} context React component context
|
|
||||||
* @return {Object} list: Array of React Components
|
* @return {Object} list: Array of React Components
|
||||||
*/
|
*/
|
||||||
_renderBlueprints(props, context) {
|
_renderBlueprints() {
|
||||||
const { m } = props;
|
const { m } = this.props;
|
||||||
const { language, tooltip, termtip } = context;
|
const { language, tooltip, termtip } = this.context;
|
||||||
const translate = language.translate;
|
const { translate } = language;
|
||||||
const blueprints = [];
|
|
||||||
for (const blueprintName in Modifications.modules[m.grp].blueprints) {
|
const blueprints = m.getApplicableBlueprints().map(blueprint => {
|
||||||
const blueprint = getBlueprint(blueprintName, m);
|
const info = getBlueprintInfo(blueprint);
|
||||||
let blueprintGrades = [];
|
let blueprintGrades = keys(info.features).map(grade => {
|
||||||
for (let grade in Modifications.modules[m.grp].blueprints[blueprintName].grades) {
|
|
||||||
// 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 active = m.getBlueprint() === blueprint && m.getBlueprintGrade() === grade;
|
||||||
active: m.blueprint && blueprint.id === m.blueprint.id && grade === m.blueprint.grade
|
const key = blueprint + ':' + grade;
|
||||||
});
|
return <li key={key} data-id={key} className={cn('c', { active })}
|
||||||
const close = this._blueprintSelected.bind(this, blueprintName, grade);
|
style={{ width: '2em' }}
|
||||||
const key = blueprintName + ':' + grade;
|
onMouseOver={termtip.bind(null, blueprintTooltip(language, m, blueprint, grade))}
|
||||||
const tooltipContent = blueprintTooltip(translate, blueprint.grades[grade], Modifications.modules[m.grp].blueprints[blueprintName].grades[grade].engineers, m.grp);
|
onMouseOut={tooltip.bind(null, null)}
|
||||||
if (classes.indexOf('active') >= 0) this.selectedModId = key;
|
onClick={() => {
|
||||||
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>);
|
m.setBlueprint(blueprint, grade, 1);
|
||||||
}
|
this.setState({
|
||||||
if (blueprintGrades) {
|
blueprintMenuOpened: false,
|
||||||
const thisLen = blueprintGrades.length;
|
specialMenuOpened: true,
|
||||||
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>);
|
ref={active ? (ref) => { this.selectedModRef = ref; } : undefined}
|
||||||
blueprints.push(<ul key={blueprintName}>{blueprintGrades}</ul>);
|
>{grade}</li>;
|
||||||
}
|
});
|
||||||
}
|
|
||||||
return blueprints;
|
return [
|
||||||
|
<div key={'div' + blueprint} className={'select-group cap'}>
|
||||||
|
{translate(blueprint)}
|
||||||
|
</div>,
|
||||||
|
<ul key={'ul' + blueprint}>{blueprintGrades}</ul>
|
||||||
|
];
|
||||||
|
});
|
||||||
|
|
||||||
|
return flatMap(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
|
||||||
* @param {Object} context React component context
|
* @param {Object} context React component context
|
||||||
* @return {Object} list: Array of React Components
|
* @return {Object} list: Array of React Components
|
||||||
*/
|
*/
|
||||||
_renderSpecials(props, context) {
|
_renderSpecials() {
|
||||||
const { m } = props;
|
const { m } = this.props;
|
||||||
const { language, tooltip, termtip } = context;
|
const { language, tooltip, termtip } = this.context;
|
||||||
const translate = language.translate;
|
const translate = language.translate;
|
||||||
const specials = [];
|
|
||||||
const specialsId = m.missile && Modifications.modules[m.grp]['specials_' + m.missile] ? 'specials_' + m.missile : 'specials';
|
const applied = m.getExperimental();
|
||||||
if (Modifications.modules[m.grp][specialsId] && Modifications.modules[m.grp][specialsId].length > 0) {
|
const experimentals = [];
|
||||||
const close = this._specialSelected.bind(this, null);
|
for (const experimental of m.getApplicableExperimentals()) {
|
||||||
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>);
|
const active = experimental === applied;
|
||||||
for (const specialName of Modifications.modules[m.grp][specialsId]) {
|
let specialTt = specialToolTip(language, m, experimental);
|
||||||
if (Modifications.specials[specialName].name.search('Legacy') >= 0) {
|
experimentals.push(
|
||||||
continue;
|
<div key={experimental} data-id={experimental}
|
||||||
}
|
style={{ cursor: 'pointer' }}
|
||||||
const classes = cn('button-inline-menu', {
|
className={cn('button-inline-menu', { active })}
|
||||||
active: m.blueprint && m.blueprint.special && m.blueprint.special.edname == specialName
|
onClick={this._specialSelected(experimental)}
|
||||||
});
|
ref={active ? (ref) => { this.selectedSpecialRef = ref; } : undefined}
|
||||||
if (classes.indexOf('active') >= 0) this.selectedSpecialId = specialName;
|
onMouseOver={termtip.bind(null, specialTt)}
|
||||||
const close = this._specialSelected.bind(this, specialName);
|
onMouseOut={tooltip.bind(null, null)}
|
||||||
if (m.blueprint && m.blueprint.name) {
|
>{translate(experimental)}</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;
|
|
||||||
|
if (experimentals.length) {
|
||||||
|
experimentals.unshift(
|
||||||
|
<div style={{ cursor: 'pointer', fontWeight: 'bold' }}
|
||||||
|
className="button-inline-menu warning" key="none" data-id="none"
|
||||||
|
// Setting the special effect to undefined clears it
|
||||||
|
onClick={this._specialSelected(undefined)}
|
||||||
|
ref={!applied ? (ref) => { this.selectedSpecialRef = ref; } : undefined}
|
||||||
|
>{translate('PHRASE_NO_SPECIAL')}</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return experimentals;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a modification component
|
||||||
|
*/
|
||||||
|
_mkModification(property, highlight) {
|
||||||
|
const { translate } = this.context.language;
|
||||||
|
const { m, propsToShow, onPropToggle } = this.props;
|
||||||
|
|
||||||
|
let onSet = m.set.bind(m);
|
||||||
|
// Show resistance instead of effectiveness
|
||||||
|
const mapped = SHOW[property];
|
||||||
|
if (mapped) {
|
||||||
|
property = mapped.as;
|
||||||
|
onSet = mapped.setter.bind(undefined, m);
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
<tr key={`th-${property}`}>
|
||||||
|
<th colSpan="4">
|
||||||
|
<span className="cb">{translate(property)}</span>
|
||||||
|
</th>
|
||||||
|
</tr>,
|
||||||
|
<Modification key={property} m={m} property={property}
|
||||||
|
onSet={onSet} highlight={highlight} showProp={propsToShow[property]}
|
||||||
|
onPropToggle={onPropToggle} />
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Render the modifications
|
* Render the modifications
|
||||||
* @param {Object} props React Component properties
|
* @return {Array} Array of React Components
|
||||||
* @return {Object} list: Array of React Components
|
|
||||||
*/
|
*/
|
||||||
_renderModifications(props) {
|
_renderModifications() {
|
||||||
const { m, onChange, ship } = props;
|
const { m } = this.props;
|
||||||
const modifiableModifications = [];
|
|
||||||
const modifications = [];
|
|
||||||
for (const modName of Modifications.modules[m.grp].modifications) {
|
|
||||||
if (!Modifications.modifications[modName].hidden) {
|
|
||||||
const key = modName + (m.getModValue(modName) / 100 || 0);
|
|
||||||
const editable = modName !== 'fallofffromrange' &&
|
|
||||||
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} />
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
modifiableModifications.sort(MODIFICATIONS_COMPARATOR);
|
const blueprintFeatures = getBlueprintInfo(m.getBlueprint()).features[
|
||||||
modifications.sort(MODIFICATIONS_COMPARATOR);
|
m.getBlueprintGrade()
|
||||||
return modifiableModifications.concat(modifications);
|
];
|
||||||
|
const blueprintModifications = chain(keys(blueprintFeatures))
|
||||||
|
.map((feature) => this._mkModification(feature, true))
|
||||||
|
.filter(([_, mod]) => Boolean(mod))
|
||||||
|
.flatMap()
|
||||||
|
.value();
|
||||||
|
const moduleModifications = chain(keys(getModuleInfo(m.getItem()).props))
|
||||||
|
.filter((prop) => !blueprintFeatures[prop])
|
||||||
|
.map((prop) => this._mkModification(prop, false))
|
||||||
|
.flatMap()
|
||||||
|
.value();
|
||||||
|
|
||||||
|
return blueprintModifications.concat(moduleModifications);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Toggle the blueprints menu
|
* Toggle the blueprints menu
|
||||||
*/
|
*/
|
||||||
_toggleBlueprintsMenu() {
|
_toggleBlueprintsMenu() {
|
||||||
const blueprintMenuOpened = !this.state.blueprintMenuOpened;
|
this.setState({ blueprintMenuOpened: !this.state.blueprintMenuOpened });
|
||||||
this.setState({ blueprintMenuOpened });
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Activated when a blueprint is selected
|
|
||||||
* @param {int} fdname The Frontier name of the blueprint
|
|
||||||
* @param {int} grade The grade of the selected blueprint
|
|
||||||
*/
|
|
||||||
_blueprintSelected(fdname, grade) {
|
|
||||||
this.context.tooltip(null);
|
|
||||||
const { m, ship } = this.props;
|
|
||||||
const blueprint = getBlueprint(fdname, m);
|
|
||||||
blueprint.grade = grade;
|
|
||||||
ship.setModuleBlueprint(m, blueprint);
|
|
||||||
setPercent(ship, m, 100);
|
|
||||||
|
|
||||||
this.setState({ blueprintMenuOpened: false, specialMenuOpened: true });
|
|
||||||
this.props.onChange();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Toggle the specials menu
|
* Toggle the specials menu
|
||||||
*/
|
*/
|
||||||
_toggleSpecialsMenu() {
|
_toggleSpecialsMenu() {
|
||||||
const specialMenuOpened = !this.state.specialMenuOpened;
|
this.setState({ specialMenuOpened: !this.state.specialMenuOpened });
|
||||||
this.setState({ specialMenuOpened });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Activated when a special is selected
|
* Creates a callback for when a special effect is being selected
|
||||||
* @param {int} special The name of the selected special
|
* @param {string} special The name of the selected special
|
||||||
|
* @returns {function} Callback
|
||||||
*/
|
*/
|
||||||
_specialSelected(special) {
|
_specialSelected(special) {
|
||||||
this.context.tooltip(null);
|
return () => {
|
||||||
const { m, ship } = this.props;
|
const { m } = this.props;
|
||||||
|
m.setExperimental(special);
|
||||||
if (special === null) {
|
this.setState({ specialMenuOpened: false });
|
||||||
ship.clearModuleSpecial(m);
|
};
|
||||||
} else {
|
|
||||||
ship.setModuleSpecial(m, Modifications.specials[special]);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setState({ specialMenuOpened: false });
|
|
||||||
this.props.onChange();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Provide a '50%' roll within the information we have
|
|
||||||
*/
|
|
||||||
_rollFifty() {
|
|
||||||
const { m, ship } = this.props;
|
|
||||||
setPercent(ship, m, 50);
|
|
||||||
|
|
||||||
// this will change the values in the modifications. Set modDidChange to true to prevent focus change when component updates
|
|
||||||
this._handleModChange(true);
|
|
||||||
|
|
||||||
this.props.onChange();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Provide a random roll within the information we have
|
|
||||||
*/
|
|
||||||
_rollRandom() {
|
|
||||||
const { m, ship } = this.props;
|
|
||||||
setRandom(ship, m);
|
|
||||||
|
|
||||||
// this will change the values in the modifications. Set modDidChange to true to prevent focus change when component updates
|
|
||||||
this._handleModChange(true);
|
|
||||||
|
|
||||||
this.props.onChange();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Provide a 'best' roll within the information we have
|
|
||||||
*/
|
|
||||||
_rollBest() {
|
|
||||||
const { m, ship } = this.props;
|
|
||||||
setPercent(ship, m, 100);
|
|
||||||
|
|
||||||
// this will change the values in the modifications. Set modDidChange to true to prevent focus change when component updates
|
|
||||||
this._handleModChange(true);
|
|
||||||
|
|
||||||
this.props.onChange();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Provide a 'worst' roll within the information we have
|
|
||||||
*/
|
|
||||||
_rollWorst() {
|
|
||||||
const { m, ship } = this.props;
|
|
||||||
setPercent(ship, m, 0);
|
|
||||||
// this will change the values in the modifications. Set modDidChange to true to prevent focus change when component updates
|
|
||||||
this._handleModChange(true);
|
|
||||||
this.props.onChange();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reset modification information
|
|
||||||
*/
|
|
||||||
_reset() {
|
|
||||||
const { m, ship } = this.props;
|
|
||||||
ship.clearModifications(m);
|
|
||||||
ship.clearModuleBlueprint(m);
|
|
||||||
this.selectedModId = null;
|
|
||||||
this.selectedSpecialId = null;
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -371,32 +213,12 @@ export default class ModificationsMenu extends TranslatedComponent {
|
|||||||
* in a modification
|
* in a modification
|
||||||
*/
|
*/
|
||||||
componentDidUpdate() {
|
componentDidUpdate() {
|
||||||
if (!this.modValDidChange) {
|
if (this.selectedModRef) {
|
||||||
if (this.modItems['modMainDiv'].children.length > 0) {
|
this.selectedModRef.focus();
|
||||||
if (this.modItems[this.selectedModId]) {
|
return;
|
||||||
this.modItems[this.selectedModId].focus();
|
} else if (this.selectedSpecialRef) {
|
||||||
return;
|
this.selectedSpecialRef.focus();
|
||||||
} else if (this.modItems[this.selectedSpecialId]) {
|
return;
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -408,90 +230,155 @@ export default class ModificationsMenu extends TranslatedComponent {
|
|||||||
const { language, tooltip, termtip } = this.context;
|
const { language, tooltip, termtip } = this.context;
|
||||||
const translate = language.translate;
|
const translate = language.translate;
|
||||||
const { m } = this.props;
|
const { m } = this.props;
|
||||||
const { blueprintMenuOpened, specialMenuOpened } = this.state;
|
const {
|
||||||
|
blueprintProgress, blueprintMenuOpened, specialMenuOpened,
|
||||||
|
} = this.state;
|
||||||
|
|
||||||
const _toggleBlueprintsMenu = this._toggleBlueprintsMenu;
|
const appliedBlueprint = m.getBlueprint();
|
||||||
const _toggleSpecialsMenu = this._toggleSpecialsMenu;
|
const appliedGrade = m.getBlueprintGrade();
|
||||||
const _rollFull = this._rollBest;
|
const appliedExperimental = m.getExperimental();
|
||||||
const _rollWorst = this._rollWorst;
|
|
||||||
const _rollFifty = this._rollFifty;
|
|
||||||
const _rollRandom = this._rollRandom;
|
|
||||||
const _reset = this._reset;
|
|
||||||
|
|
||||||
let blueprintLabel;
|
let renderComponents = [];
|
||||||
let haveBlueprint = false;
|
switch (true) {
|
||||||
let blueprintTt;
|
case !appliedBlueprint || blueprintMenuOpened:
|
||||||
let blueprintCv;
|
renderComponents = this._renderBlueprints();
|
||||||
// TODO: Fix this to actually find the correct blueprint.
|
break;
|
||||||
if (!m.blueprint || !m.blueprint.name || !m.blueprint.fdname || !Modifications.modules[m.grp].blueprints || !Modifications.modules[m.grp].blueprints[m.blueprint.fdname]) {
|
case specialMenuOpened:
|
||||||
this.props.ship.clearModuleBlueprint(m);
|
renderComponents = this._renderSpecials();
|
||||||
this.props.ship.clearModuleSpecial(m);
|
break;
|
||||||
}
|
default:
|
||||||
if (m.blueprint && m.blueprint.name && Modifications.modules[m.grp].blueprints[m.blueprint.fdname].grades[m.blueprint.grade]) {
|
// Since the first case didn't apply, there is a blueprint applied so
|
||||||
blueprintLabel = translate(m.blueprint.name) + ' ' + translate('grade') + ' ' + m.blueprint.grade;
|
// we render the modifications
|
||||||
haveBlueprint = true;
|
let blueprintTt = blueprintTooltip(language, m, appliedBlueprint, appliedGrade);
|
||||||
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;
|
renderComponents.push(
|
||||||
let specialTt;
|
<div style={{ cursor: 'pointer' }} key="blueprintsMenu"
|
||||||
if (m.blueprint && m.blueprint.special) {
|
className="section-menu button-inline-menu"
|
||||||
specialLabel = m.blueprint.special.name;
|
onMouseOver={termtip.bind(null, blueprintTt)}
|
||||||
specialTt = specialToolTip(translate, m.blueprint.grades[m.blueprint.grade], m.grp, m, m.blueprint.special.edname);
|
onMouseOut={tooltip.bind(null, null)}
|
||||||
} else {
|
onClick={this._toggleBlueprintsMenu}
|
||||||
specialLabel = translate('PHRASE_SELECT_SPECIAL');
|
>
|
||||||
}
|
{translate(appliedBlueprint)} {translate('grade')} {appliedGrade}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
const specials = this._renderSpecials(this.props, this.context);
|
if (m.getApplicableExperimentals().length) {
|
||||||
/**
|
let specialLabel = translate('PHRASE_SELECT_SPECIAL');
|
||||||
* pnellesen - 05/28/2018 - added additional checks for specials.length below to ensure menus
|
let specialTt;
|
||||||
* display correctly in cases where there are no specials (ex: AFMUs.)
|
if (appliedExperimental) {
|
||||||
*/
|
specialLabel = appliedExperimental;
|
||||||
const showBlueprintsMenu = blueprintMenuOpened;
|
specialTt = specialToolTip(language, m, appliedExperimental);
|
||||||
const showSpecial = haveBlueprint && specials.length && !blueprintMenuOpened;
|
}
|
||||||
const showSpecialsMenu = specialMenuOpened && specials.length;
|
renderComponents.push(
|
||||||
const showRolls = haveBlueprint && !blueprintMenuOpened && (!specialMenuOpened || !specials.length);
|
<div className="section-menu button-inline-menu"
|
||||||
const showReset = !blueprintMenuOpened && (!specialMenuOpened || !specials.length) && haveBlueprint;
|
style={{ cursor: 'pointer' }}
|
||||||
const showMods = !blueprintMenuOpened && (!specialMenuOpened || !specials.length) && haveBlueprint;
|
onMouseOver={specialTt ? termtip.bind(null, specialTt) : null}
|
||||||
if (haveBlueprint) {
|
onMouseOut={specialTt ? tooltip.bind(null, null) : null}
|
||||||
this.firstBPLabel = blueprintLabel;
|
onClick={this._toggleSpecialsMenu}
|
||||||
} else {
|
>{specialLabel}</div>
|
||||||
this.firstBPLabel = 'selectBP';
|
);
|
||||||
}
|
}
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className={cn('select', this.props.className)}
|
|
||||||
onClick={(e) => e.stopPropagation() }
|
|
||||||
onContextMenu={stopCtxPropagation}
|
|
||||||
ref={modItem => this.modItems['modMainDiv'] = modItem}
|
|
||||||
>
|
|
||||||
{ showBlueprintsMenu | showSpecialsMenu ? '' : haveBlueprint ?
|
|
||||||
<div tabIndex="0" className={ cn('section-menu button-inline-menu', { selected: blueprintMenuOpened })} style={{ cursor: 'pointer' }} onMouseOver={termtip.bind(null, blueprintTt)} onMouseOut={tooltip.bind(null, null)} onClick={_toggleBlueprintsMenu} onKeyDown={ this._keyDown } ref={modItems => this.modItems[this.firstBPLabel] = modItems}>{blueprintLabel}</div> :
|
|
||||||
<div tabIndex="0" className={ cn('section-menu button-inline-menu', { selected: blueprintMenuOpened })} style={{ cursor: 'pointer' }} onClick={_toggleBlueprintsMenu} onKeyDown={ this._keyDown } ref={modItems => this.modItems[this.firstBPLabel] = modItems}>{translate('PHRASE_SELECT_BLUEPRINT')}</div> }
|
|
||||||
{ showBlueprintsMenu ? this._renderBlueprints(this.props, this.context) : null }
|
|
||||||
{ showSpecial & !showSpecialsMenu ? <div tabIndex="0" className={ cn('section-menu button-inline-menu', { selected: specialMenuOpened })} style={{ cursor: 'pointer' }} onMouseOver={specialTt ? termtip.bind(null, specialTt) : null} onMouseOut={specialTt ? tooltip.bind(null, null) : null} onClick={_toggleSpecialsMenu} onKeyDown={ this._keyDown }>{specialLabel}</div> : null }
|
|
||||||
{ showSpecialsMenu ? specials : null }
|
|
||||||
{ showReset ? <div tabIndex="0" className={'section-menu button-inline-menu warning'} style={{ cursor: 'pointer' }} onClick={_reset} onKeyDown={ this._keyDown } onMouseOver={termtip.bind(null, 'PHRASE_BLUEPRINT_RESET')} onMouseOut={tooltip.bind(null, null)}> { translate('reset') } </div> : null }
|
|
||||||
{ showRolls ?
|
|
||||||
|
|
||||||
<table style={{ width: '100%', backgroundColor: 'transparent' }}>
|
renderComponents.push(
|
||||||
|
<div
|
||||||
|
className="section-menu button-inline-menu warning"
|
||||||
|
style={{ cursor: 'pointer' }}
|
||||||
|
onClick={() => {
|
||||||
|
m.resetEngineering();
|
||||||
|
this.selectedModRef = null;
|
||||||
|
this.selectedSpecialRef = null;
|
||||||
|
tooltip(null);
|
||||||
|
this.setState({
|
||||||
|
blueprintMenuOpened: true,
|
||||||
|
blueprintProgress: undefined,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
onMouseOver={termtip.bind(null, 'PHRASE_BLUEPRINT_RESET')}
|
||||||
|
onMouseOut={tooltip.bind(null, null)}
|
||||||
|
>{translate('reset')}</div>,
|
||||||
|
<table style={{ width: '100%', backgroundColor: 'transparent' }}>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td
|
||||||
|
className={cn(
|
||||||
|
'section-menu button-inline-menu',
|
||||||
|
{ active: false },
|
||||||
|
)}
|
||||||
|
>{translate('mroll')}:</td>
|
||||||
|
<td
|
||||||
|
className={cn(
|
||||||
|
'section-menu button-inline-menu',
|
||||||
|
{ active: blueprintProgress === 0 },
|
||||||
|
)} style={{ cursor: 'pointer' }}
|
||||||
|
onClick={() => {
|
||||||
|
m.setBlueprintProgress(0);
|
||||||
|
this.setState({ blueprintProgress: 0 });
|
||||||
|
}}
|
||||||
|
onMouseOver={termtip.bind(null, 'PHRASE_BLUEPRINT_WORST')}
|
||||||
|
onMouseOut={tooltip.bind(null, null)}
|
||||||
|
>{translate('0%')}</td>
|
||||||
|
<td
|
||||||
|
className={cn(
|
||||||
|
'section-menu button-inline-menu',
|
||||||
|
{ active: blueprintProgress === 0.5 },
|
||||||
|
)} style={{ cursor: 'pointer' }}
|
||||||
|
onClick={() => {
|
||||||
|
m.setBlueprintProgress(0.5);
|
||||||
|
this.setState({ blueprintProgress: 0.5 });
|
||||||
|
}}
|
||||||
|
onMouseOver={termtip.bind(null, 'PHRASE_BLUEPRINT_FIFTY')}
|
||||||
|
onMouseOut={tooltip.bind(null, null)}
|
||||||
|
>{translate('50%')}</td>
|
||||||
|
<td
|
||||||
|
className={cn(
|
||||||
|
'section-menu button-inline-menu',
|
||||||
|
{ active: blueprintProgress === 1 },
|
||||||
|
)}
|
||||||
|
style={{ cursor: 'pointer' }}
|
||||||
|
onClick={() => {
|
||||||
|
m.setBlueprintProgress(1);
|
||||||
|
this.setState({ blueprintProgress: 1 });
|
||||||
|
}}
|
||||||
|
onMouseOver={termtip.bind(null, 'PHRASE_BLUEPRINT_BEST')}
|
||||||
|
onMouseOut={tooltip.bind(null, null)}
|
||||||
|
>{translate('100%')}</td>
|
||||||
|
<td
|
||||||
|
className={cn(
|
||||||
|
'section-menu button-inline-menu',
|
||||||
|
{ active: blueprintProgress % 0.5 !== 0 },
|
||||||
|
)}
|
||||||
|
style={{ cursor: 'pointer' }}
|
||||||
|
onClick={() => {
|
||||||
|
const blueprintProgress = Math.random();
|
||||||
|
m.setBlueprintProgress(blueprintProgress);
|
||||||
|
this.setState({ blueprintProgress });
|
||||||
|
}}
|
||||||
|
onMouseOver={termtip.bind(null, 'PHRASE_BLUEPRINT_RANDOM')}
|
||||||
|
onMouseOut={tooltip.bind(null, null)}
|
||||||
|
>{translate('random')}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>,
|
||||||
|
<hr />,
|
||||||
|
<span
|
||||||
|
onMouseOver={termtip.bind(null, 'HELP_MODIFICATIONS_MENU')}
|
||||||
|
onMouseOut={tooltip.bind(null, null)}
|
||||||
|
>
|
||||||
|
<table style={{ width: '100%' }}>
|
||||||
<tbody>
|
<tbody>
|
||||||
{ showRolls ?
|
{this._renderModifications()}
|
||||||
<tr>
|
|
||||||
<td tabIndex="0" className={ cn('section-menu button-inline-menu', { active: false }) }> { translate('roll') }: </td>
|
|
||||||
<td tabIndex="0" className={ cn('section-menu button-inline-menu', { active: blueprintCv === 0 }) } style={{ cursor: 'pointer' }} onClick={_rollWorst} onKeyDown={ this._keyDown } onMouseOver={termtip.bind(null, 'PHRASE_BLUEPRINT_WORST')} onMouseOut={tooltip.bind(null, null)}> { translate('0%') } </td>
|
|
||||||
<td tabIndex="0" className={ cn('section-menu button-inline-menu', { active: blueprintCv === 50 })} style={{ cursor: 'pointer' }} onClick={_rollFifty} onKeyDown={ this._keyDown } onMouseOver={termtip.bind(null, 'PHRASE_BLUEPRINT_FIFTY')} onMouseOut={tooltip.bind(null, null)}> { translate('50%') } </td>
|
|
||||||
<td tabIndex="0" className={ cn('section-menu button-inline-menu', { active: blueprintCv === 100 })} style={{ cursor: 'pointer' }} onClick={_rollFull} onKeyDown={ this._keyDown } onMouseOver={termtip.bind(null, 'PHRASE_BLUEPRINT_BEST')} onMouseOut={tooltip.bind(null, null)}> { translate('100%') } </td>
|
|
||||||
<td tabIndex="0" className={ cn('section-menu button-inline-menu', { active: blueprintCv === null || blueprintCv % 50 != 0 })} style={{ cursor: 'pointer' }} onClick={_rollRandom} onKeyDown={ this._keyDown } onMouseOver={termtip.bind(null, 'PHRASE_BLUEPRINT_RANDOM')} onMouseOut={tooltip.bind(null, null)}> { translate('random') } </td>
|
|
||||||
</tr> : null }
|
|
||||||
</tbody>
|
</tbody>
|
||||||
</table> : null }
|
</table>
|
||||||
{ showMods ? <hr /> : null }
|
</span>
|
||||||
{ showMods ?
|
);
|
||||||
<span onMouseOver={termtip.bind(null, 'HELP_MODIFICATIONS_MENU')} onMouseOut={tooltip.bind(null, null)} >
|
}
|
||||||
{ this._renderModifications(this.props) }
|
|
||||||
</span> : null }
|
return (
|
||||||
|
<div className={cn('select', this.props.className)}
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
onContextMenu={stopCtxPropagation}
|
||||||
|
>
|
||||||
|
{renderComponents}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,47 +1,39 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import TranslatedComponent from './TranslatedComponent';
|
import TranslatedComponent from './TranslatedComponent';
|
||||||
|
import { ShipProps } from 'ed-forge';
|
||||||
|
const { SPEED, BOOST_SPEED, ROLL, BOOST_ROLL, YAW, BOOST_YAW, PITCH, BOOST_PITCH } = ShipProps;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Movement
|
* Movement
|
||||||
*/
|
*/
|
||||||
export default class Movement extends TranslatedComponent {
|
export default class Movement extends TranslatedComponent {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
marker: PropTypes.string.isRequired,
|
code: PropTypes.string.isRequired,
|
||||||
ship: PropTypes.object.isRequired,
|
ship: PropTypes.object.isRequired,
|
||||||
boost: PropTypes.bool.isRequired,
|
boost: PropTypes.bool.isRequired,
|
||||||
eng: PropTypes.number.isRequired,
|
pips: PropTypes.object.isRequired,
|
||||||
fuel: PropTypes.number.isRequired,
|
|
||||||
cargo: PropTypes.number.isRequired
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructor
|
|
||||||
* @param {Object} props React Component properties
|
|
||||||
*/
|
|
||||||
constructor(props) {
|
|
||||||
super(props);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Render movement
|
* Render movement
|
||||||
* @return {React.Component} contents
|
* @return {React.Component} contents
|
||||||
*/
|
*/
|
||||||
render() {
|
render() {
|
||||||
const { ship, boost, eng, cargo, fuel } = this.props;
|
const { ship, boost } = this.props;
|
||||||
const { language } = this.context;
|
const { language } = this.context;
|
||||||
const { formats, translate, units } = language;
|
const { formats } = language;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<span id='movement'>
|
<span id='movement'>
|
||||||
<svg viewBox='0 0 600 600' fillRule="evenodd" clipRule="evenodd">
|
<svg viewBox='0 0 600 600' fillRule="evenodd" clipRule="evenodd">
|
||||||
// Axes
|
{/* Axes */}
|
||||||
<path d="M150 250v300" strokeWidth='1'/>
|
<path d="M150 250v300" strokeWidth='1'/>
|
||||||
<path d="M150 250l236 236" strokeWidth='1'/>
|
<path d="M150 250l236 236" strokeWidth='1'/>
|
||||||
<path d="M150 250l350 -200" strokeWidth='1'/>
|
<path d="M150 250l350 -200" strokeWidth='1'/>
|
||||||
// End Arrow
|
{/* End Arrow */}
|
||||||
<path d="M508 43.3L487 67l-10-17.3 31-6.4z"/>
|
<path d="M508 43.3L487 67l-10-17.3 31-6.4z"/>
|
||||||
// Axes arcs and arrows
|
{/* Axes arcs and arrows */}
|
||||||
<path d="M71.7 251.7C64.2 259.2 60 269.4 60 280c0 22 18 40 40 40s40-18 40-40c0-10.6-4.2-20.8-11.7-28.3 7.5 7.5 11.7 17.7 11.7 28.3 0 22-18 40-40 40s-40-18-40-40c0-10.6 4.2-20.8 11.7-28.3z" strokeWidth='4' transform="matrix(.6 0 0 .3 87.5 376.3)"/>
|
<path d="M71.7 251.7C64.2 259.2 60 269.4 60 280c0 22 18 40 40 40s40-18 40-40c0-10.6-4.2-20.8-11.7-28.3 7.5 7.5 11.7 17.7 11.7 28.3 0 22-18 40-40 40s-40-18-40-40c0-10.6 4.2-20.8 11.7-28.3z" strokeWidth='4' transform="matrix(.6 0 0 .3 87.5 376.3)"/>
|
||||||
<path d="M142.8 453l-13.2 8.7-2.6-9.7 15.8 1z"/>
|
<path d="M142.8 453l-13.2 8.7-2.6-9.7 15.8 1z"/>
|
||||||
<path d="M144.7 451.6l.5 1.6-16.2 10.6h-.4l-3.5-13 .7-.4 19.3 1.2zm-14.2 7.7l7.7-5-9.2-.7 1.5 5.7zm25.7-6.3l15.8-1-2.6 9.7-13.2-8.8z"/>
|
<path d="M144.7 451.6l.5 1.6-16.2 10.6h-.4l-3.5-13 .7-.4 19.3 1.2zm-14.2 7.7l7.7-5-9.2-.7 1.5 5.7zm25.7-6.3l15.8-1-2.6 9.7-13.2-8.8z"/>
|
||||||
@@ -57,14 +49,10 @@ export default class Movement extends TranslatedComponent {
|
|||||||
<path d="M359.5 422.4l-1.2 19.3-1.6.4-10.7-16 .2-.2 13-3.4.3.4zm-9 5l5.2 7.8.6-9.3-5.7 1.2zm-10.5 24l-13.2 8.6-2.6-9.7 15.8 1z"/>
|
<path d="M359.5 422.4l-1.2 19.3-1.6.4-10.7-16 .2-.2 13-3.4.3.4zm-9 5l5.2 7.8.6-9.3-5.7 1.2zm-10.5 24l-13.2 8.6-2.6-9.7 15.8 1z"/>
|
||||||
<path d="M342 450l.4 1.5-16.2 10.7-.4-.2-3.5-13 .3-.3L342 450zm-14.3 7.6l7.7-5-9.2-.6 1.5 5.6z"/>
|
<path d="M342 450l.4 1.5-16.2 10.7-.4-.2-3.5-13 .3-.3L342 450zm-14.3 7.6l7.7-5-9.2-.6 1.5 5.6z"/>
|
||||||
|
|
||||||
// Speed
|
<text x="470" y="30" strokeWidth='0'>{formats.int(ship.get(boost ? BOOST_SPEED : SPEED)) + 'm/s'}</text>
|
||||||
<text x="470" y="30" strokeWidth='0'>{ship.canThrust(cargo, fuel) ? formats.int(ship.calcSpeed(eng, fuel, cargo, boost)) + 'm/s' : '-'}</text>
|
<text x="355" y="410" strokeWidth='0'>{formats.int(ship.get(boost ? BOOST_PITCH : PITCH)) + '°/s'}</text>
|
||||||
// Pitch
|
<text x="450" y="110" strokeWidth='0'>{formats.int(ship.get(boost ? BOOST_ROLL : ROLL)) + '°/s'}</text>
|
||||||
<text x="355" y="410" strokeWidth='0'>{ship.canThrust(cargo, fuel) ? formats.int(ship.calcPitch(eng, fuel, cargo, boost)) + '°/s' : '-'}</text>
|
<text x="160" y="430" strokeWidth='0'>{formats.int(ship.get(boost ? BOOST_YAW : YAW)) + '°/s'}</text>
|
||||||
// Roll
|
|
||||||
<text x="450" y="110" strokeWidth='0'>{ship.canThrust(cargo, fuel) ? formats.int(ship.calcRoll(eng, fuel, cargo, boost)) + '°/s' : '-'}</text>
|
|
||||||
// Yaw
|
|
||||||
<text x="160" y="430" strokeWidth='0'>{ship.canThrust(cargo, fuel) ? formats.int(ship.calcYaw(eng, fuel, cargo, boost)) + '°/s' : '-'}</text>
|
|
||||||
</svg>
|
</svg>
|
||||||
</span>);
|
</span>);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,102 +3,36 @@ import PropTypes from 'prop-types';
|
|||||||
import TranslatedComponent from './TranslatedComponent';
|
import TranslatedComponent from './TranslatedComponent';
|
||||||
import * as Calc from '../shipyard/Calculations';
|
import * as Calc from '../shipyard/Calculations';
|
||||||
import PieChart from './PieChart';
|
import PieChart from './PieChart';
|
||||||
import { nameComparator } from '../utils/SlotFunctions';
|
|
||||||
import { MountFixed, MountGimballed, MountTurret } from './SvgIcons';
|
import { MountFixed, MountGimballed, MountTurret } from './SvgIcons';
|
||||||
import VerticalBarChart from './VerticalBarChart';
|
import { Ship } from 'ed-forge';
|
||||||
|
import autoBind from 'auto-bind';
|
||||||
|
import { DAMAGE_METRICS } from 'ed-forge/lib/ship-stats';
|
||||||
|
import { clone, mapValues, mergeWith, reverse, sortBy, sum, toPairs, values } from 'lodash';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generates an internationalization friendly weapon comparator that will
|
* Turns an object into a tooltip.
|
||||||
* sort by specified property (if provided) then by name/group, class, rating
|
* @param {function} translate Translate function
|
||||||
* @param {function} translate Translation function
|
* @param {object} o Map to make the tooltip from
|
||||||
* @param {function} propComparator Optional property comparator
|
* @returns {React.Component} Tooltip
|
||||||
* @param {boolean} desc Use descending order
|
|
||||||
* @return {function} Comparator function for names
|
|
||||||
*/
|
*/
|
||||||
export function weaponComparator(translate, propComparator, desc) {
|
function objToTooltip(translate, o) {
|
||||||
return (a, b) => {
|
return toPairs(o)
|
||||||
if (!desc) { // Flip A and B if ascending order
|
.filter(([k, v]) => Boolean(v))
|
||||||
let t = a;
|
.map(([k, v]) => <div key={k}>{`${translate(k)}: ${v}`}</div>);
|
||||||
a = b;
|
|
||||||
b = t;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If a property comparator is provided use it first
|
|
||||||
let diff = propComparator ? propComparator(a, b) : nameComparator(translate, a, b);
|
|
||||||
|
|
||||||
if (diff) {
|
|
||||||
return diff;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Property matches so sort by name / group, then class, rating
|
|
||||||
if (a.name === b.name && a.grp === b.grp) {
|
|
||||||
if(a.class == b.class) {
|
|
||||||
return a.rating > b.rating ? 1 : -1;
|
|
||||||
}
|
|
||||||
return a.class - b.class;
|
|
||||||
}
|
|
||||||
|
|
||||||
return nameComparator(translate, a, b);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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.
|
* Returns a data object used by {@link PieChart} that shows damage by type.
|
||||||
* @param {function} translate Translation function
|
* @param {function} translate Translation function
|
||||||
* @param {Calc.SDps} sdpsObject Object that holds sdps split up by type
|
* @param {Calc.SDps} o Object that holds sdps split up by type
|
||||||
* @returns {Object} Data object
|
* @returns {Object} Data object
|
||||||
*/
|
*/
|
||||||
function getSDpsData(translate, sdpsObject) {
|
function objToPie(translate, o) {
|
||||||
return Object.keys(sdpsObject).map(key => {
|
return toPairs(o).map(([k, value]) => {
|
||||||
return {
|
return { label: translate(k), value };
|
||||||
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:
|
||||||
@@ -109,12 +43,10 @@ function sumSDps(sdpsObject) {
|
|||||||
*/
|
*/
|
||||||
export default class Offence extends TranslatedComponent {
|
export default class Offence extends TranslatedComponent {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
marker: PropTypes.string.isRequired,
|
code: PropTypes.string.isRequired,
|
||||||
ship: PropTypes.object.isRequired,
|
ship: PropTypes.instanceOf(Ship).isRequired,
|
||||||
opponent: PropTypes.object.isRequired,
|
opponent: PropTypes.instanceOf(Ship).isRequired,
|
||||||
engagementrange: PropTypes.number.isRequired,
|
engagementRange: PropTypes.number.isRequired,
|
||||||
wep: PropTypes.number.isRequired,
|
|
||||||
opponentSys: PropTypes.number.isRequired
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -123,199 +55,363 @@ export default class Offence extends TranslatedComponent {
|
|||||||
*/
|
*/
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
autoBind(this);
|
||||||
|
|
||||||
this._sort = this._sort.bind(this);
|
|
||||||
|
|
||||||
const damage = Calc.offenceMetrics(props.ship, props.opponent, props.wep, props.opponentSys, props.engagementrange);
|
|
||||||
this.state = {
|
this.state = {
|
||||||
predicate: 'n',
|
predicate: 'classRating',
|
||||||
desc: true,
|
desc: true,
|
||||||
damage
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Update the state if our properties change
|
|
||||||
* @param {Object} nextProps Incoming/Next properties
|
|
||||||
* @return {boolean} Returns true if the component should be rerendered
|
|
||||||
*/
|
|
||||||
componentWillReceiveProps(nextProps) {
|
|
||||||
if (this.props.marker != nextProps.marker || this.props.eng != nextProps.eng) {
|
|
||||||
const damage = Calc.offenceMetrics(nextProps.ship, nextProps.opponent, nextProps.wep, nextProps.opponentSys, nextProps.engagementrange);
|
|
||||||
this.setState({ damage });
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the sort order and sort
|
* Set the sort order and sort
|
||||||
* @param {string} predicate Sort predicate
|
* @param {string} predicate Sort predicate
|
||||||
*/
|
*/
|
||||||
_sortOrder(predicate) {
|
_sortOrder(predicate) {
|
||||||
let desc = this.state.desc;
|
let desc = predicate == this.state.predicate ? !this.state.desc : true;
|
||||||
|
|
||||||
if (predicate == this.state.predicate) {
|
|
||||||
desc = !desc;
|
|
||||||
} else {
|
|
||||||
desc = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
this._sort(predicate, desc);
|
|
||||||
this.setState({ predicate, desc });
|
this.setState({ predicate, desc });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Sorts the weapon list
|
|
||||||
* @param {string} predicate Sort predicate
|
|
||||||
* @param {Boolean} desc Sort order descending
|
|
||||||
*/
|
|
||||||
_sort(predicate, desc) {
|
|
||||||
let comp = weaponComparator.bind(null, this.context.language.translate);
|
|
||||||
|
|
||||||
switch (predicate) {
|
|
||||||
case 'n': comp = comp(null, desc); break;
|
|
||||||
case 'esdpss': comp = comp((a, b) => a.sdps.shields.total - b.sdps.shields.total, desc); break;
|
|
||||||
case 'es': comp = comp((a, b) => a.effectiveness.shields.total - b.effectiveness.shields.total, desc); break;
|
|
||||||
case 'esdpsh': comp = comp((a, b) => a.sdps.armour.total - b.sdps.armour.total, desc); break;
|
|
||||||
case 'eh': comp = comp((a, b) => a.effectiveness.armour.total - b.effectiveness.armour.total, desc); break;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.state.damage.sort(comp);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Render offence
|
* Render offence
|
||||||
* @return {React.Component} contents
|
* @return {React.Component} contents
|
||||||
*/
|
*/
|
||||||
render() {
|
render() {
|
||||||
const { ship, opponent, wep, engagementrange } = this.props;
|
const { ship } = this.props;
|
||||||
const { language, tooltip, termtip } = this.context;
|
const { language, tooltip, termtip } = this.context;
|
||||||
const { formats, translate, units } = language;
|
const { formats, translate, units } = language;
|
||||||
const { damage } = this.state;
|
|
||||||
const sortOrder = this._sortOrder;
|
const sortOrder = this._sortOrder;
|
||||||
|
|
||||||
const pd = ship.standard[4].m;
|
const {
|
||||||
|
drained, sustained, rangeMultiplier, hardnessMultiplier, timeToDrain
|
||||||
|
} = ship.getMetrics(DAMAGE_METRICS);
|
||||||
|
const portions = {
|
||||||
|
Absolute: sustained.types.abs,
|
||||||
|
Explosive: sustained.types.expl,
|
||||||
|
Kinetic: sustained.types.kin,
|
||||||
|
Thermic: sustained.types.therm,
|
||||||
|
};
|
||||||
|
|
||||||
const opponentShields = Calc.shieldMetrics(opponent, 4);
|
const oppShield = ship.getOpponent().getShield();
|
||||||
const opponentArmour = Calc.armourMetrics(opponent);
|
const shieldMults = {
|
||||||
|
Absolute: 1,
|
||||||
|
Explosive: oppShield.explosive.damageMultiplier,
|
||||||
|
Kinetic: oppShield.kinetic.damageMultiplier,
|
||||||
|
Thermic: oppShield.thermal.damageMultiplier,
|
||||||
|
};
|
||||||
|
|
||||||
const timeToDrain = Calc.timeToDrainWep(ship, wep);
|
const oppArmour = ship.getOpponent().getArmour();
|
||||||
|
const armourMults = {
|
||||||
|
Absolute: 1,
|
||||||
|
Explosive: oppArmour.explosive.damageMultiplier,
|
||||||
|
Kinetic: oppArmour.kinetic.damageMultiplier,
|
||||||
|
Thermic: oppArmour.thermal.damageMultiplier,
|
||||||
|
};
|
||||||
|
|
||||||
|
const weapons = sortBy(ship.getHardpoints(), (m) => m.get('distributordraw'));
|
||||||
|
let rows = weapons.map((weapon) => {
|
||||||
|
const sdps = weapon.get('sustaineddamagepersecond');
|
||||||
|
const byRange = weapon.getRangeEffectiveness();
|
||||||
|
const weaponPortions = {
|
||||||
|
Absolute: weapon.get('absolutedamageportion'),
|
||||||
|
Explosive: weapon.get('explosivedamageportion'),
|
||||||
|
Kinetic: weapon.get('kineticdamageportion'),
|
||||||
|
Thermic: weapon.get('thermicdamageportion'),
|
||||||
|
};
|
||||||
|
const baseSdpsTooltip = objToTooltip(
|
||||||
|
translate,
|
||||||
|
mapValues(weaponPortions, (p) => formats.f1(sdps * p)),
|
||||||
|
);
|
||||||
|
|
||||||
let totalSEps = 0;
|
const bySys = oppShield.absolute.bySys;
|
||||||
let totalSDpsObject = {'absolute': 0, 'explosive': 0, 'kinetic': 0, 'thermal': 0};
|
const shieldResEfts = mergeWith(
|
||||||
let shieldsSDpsObject = {'absolute': 0, 'explosive': 0, 'kinetic': 0, 'thermal': 0};
|
clone(weaponPortions),
|
||||||
let armourSDpsObject = {'absolute': 0, 'explosive': 0, 'kinetic': 0, 'thermal': 0};
|
shieldMults,
|
||||||
|
(objV, srcV) => objV * srcV
|
||||||
|
);
|
||||||
|
const byShieldRes = sum(values(shieldResEfts));
|
||||||
|
const shieldsSdpsTooltip = objToTooltip(
|
||||||
|
translate,
|
||||||
|
mapValues(
|
||||||
|
shieldResEfts,
|
||||||
|
(mult) => formats.f1(byRange * mult * bySys * sdps),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
const shieldsEftTooltip = objToTooltip(
|
||||||
|
translate,
|
||||||
|
{
|
||||||
|
range: formats.pct1(byRange),
|
||||||
|
resistance: formats.pct1(byShieldRes),
|
||||||
|
'power distributor': formats.pct1(bySys),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
const shieldEft = byRange * byShieldRes * bySys;
|
||||||
|
|
||||||
const rows = [];
|
const byHardness = weapon.getArmourEffectiveness();
|
||||||
for (let i = 0; i < damage.length; i++) {
|
const armourResEfts = mergeWith(
|
||||||
const weapon = damage[i];
|
clone(weaponPortions),
|
||||||
|
armourMults,
|
||||||
|
(objV, srcV) => objV * srcV,
|
||||||
|
);
|
||||||
|
const byArmourRes = sum(values(armourResEfts));
|
||||||
|
const armourSdpsTooltip = objToTooltip(
|
||||||
|
translate,
|
||||||
|
mapValues(
|
||||||
|
armourResEfts,
|
||||||
|
(mult) => formats.f1(byRange * mult * byHardness * sdps)
|
||||||
|
),
|
||||||
|
);
|
||||||
|
const armourEftTooltip = objToTooltip(
|
||||||
|
translate,
|
||||||
|
{
|
||||||
|
range: formats.pct1(byRange),
|
||||||
|
resistance: formats.pct1(byArmourRes),
|
||||||
|
hardness: formats.pct1(byHardness),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
const armourEft = byRange * byArmourRes * byHardness;
|
||||||
|
|
||||||
totalSEps += weapon.seps;
|
const bp = weapon.getBlueprint();
|
||||||
addSDps(totalSDpsObject, weapon.sdps.base);
|
const grade = weapon.getBlueprintGrade();
|
||||||
addSDps(shieldsSDpsObject, weapon.sdps.shields);
|
const exp = weapon.getExperimental();
|
||||||
addSDps(armourSDpsObject, weapon.sdps.armour);
|
let bpTitle = `${translate(bp)} ${translate('grade')} ${grade}`;
|
||||||
|
if (exp) {
|
||||||
|
bpTitle += `, ${translate(exp)}`;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
slot: weapon.getSlot(),
|
||||||
|
mount: weapon.mount,
|
||||||
|
classRating: weapon.getClassRating(),
|
||||||
|
type: weapon.readMeta('type'),
|
||||||
|
bpTitle: bp ? ` (${bpTitle})` : null,
|
||||||
|
sdps,
|
||||||
|
baseSdpsTooltip,
|
||||||
|
shieldSdps: sdps * shieldEft,
|
||||||
|
shieldEft,
|
||||||
|
shieldsSdpsTooltip,
|
||||||
|
shieldsEftTooltip,
|
||||||
|
armourSdps: sdps * armourEft,
|
||||||
|
armourEft,
|
||||||
|
armourSdpsTooltip,
|
||||||
|
armourEftTooltip,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
const baseSDpsTooltipDetails = getSDpsTooltip(translate, formats, weapon.sdps.base);
|
const { predicate, desc } = this.state;
|
||||||
|
rows = sortBy(rows, (row) => row[predicate]);
|
||||||
const effectivenessShieldsTooltipDetails = [];
|
if (desc) {
|
||||||
effectivenessShieldsTooltipDetails.push(<div key='range'>{translate('range') + ' ' + formats.pct1(weapon.effectiveness.shields.range)}</div>);
|
reverse(rows);
|
||||||
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>);
|
|
||||||
|
|
||||||
const effectiveShieldsSDpsTooltipDetails = getSDpsTooltip(translate, formats, weapon.sdps.armour);
|
|
||||||
|
|
||||||
const effectivenessArmourTooltipDetails = [];
|
|
||||||
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='hardness'>{translate('hardness') + ' ' + formats.pct1(weapon.effectiveness.armour.hardness)}</div>);
|
|
||||||
|
|
||||||
const effectiveArmourSDpsTooltipDetails = getSDpsTooltip(translate, formats, weapon.sdps.armour);
|
|
||||||
|
|
||||||
rows.push(
|
|
||||||
<tr key={weapon.id}>
|
|
||||||
<td className='ri'>
|
|
||||||
{weapon.mount == 'F' ? <span onMouseOver={termtip.bind(null, 'fixed')} onMouseOut={tooltip.bind(null, null)}><MountFixed className='icon'/></span> : null}
|
|
||||||
{weapon.mount == 'G' ? <span onMouseOver={termtip.bind(null, 'gimballed')} onMouseOut={tooltip.bind(null, null)}><MountGimballed /></span> : null}
|
|
||||||
{weapon.mount == 'T' ? <span onMouseOver={termtip.bind(null, 'turreted')} onMouseOut={tooltip.bind(null, null)}><MountTurret /></span> : null}
|
|
||||||
{weapon.classRating} {translate(weapon.name)}
|
|
||||||
{weapon.engineering ? ' (' + weapon.engineering + ')' : null }
|
|
||||||
</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, 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, effectivenessArmourTooltipDetails)} onMouseOut={tooltip.bind(null, null)}>{formats.pct1(weapon.effectiveness.armour.total)}</span></td>
|
|
||||||
</tr>);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const totalSDps = sumSDps(totalSDpsObject);
|
const sdpsTooltip = objToTooltip(
|
||||||
const totalSDpsTooltipDetails = getSDpsTooltip(translate, formats, totalSDpsObject);
|
translate,
|
||||||
const totalSDpsData = getSDpsData(translate, totalSDpsObject);
|
mapValues(portions, (p) => formats.f1(sustained.dps * p)),
|
||||||
|
);
|
||||||
|
const sdpsPie = objToPie(
|
||||||
|
translate,
|
||||||
|
mapValues(portions, (p) => Math.round(sustained.dps * p)),
|
||||||
|
);
|
||||||
|
|
||||||
const totalShieldsSDps = sumSDps(shieldsSDpsObject);
|
const shieldSdpsSrcs = mergeWith(
|
||||||
const totalShieldsSDpsTooltipDetails = getSDpsTooltip(translate, formats, shieldsSDpsObject);
|
clone(portions),
|
||||||
const shieldsSDpsData = getSDpsData(translate, shieldsSDpsObject);
|
shieldMults,
|
||||||
|
(objV, srcV) => sustained.dps * oppShield.absolute.bySys *
|
||||||
|
rangeMultiplier * objV * srcV,
|
||||||
|
);
|
||||||
|
const shieldsSdps = sum(values(shieldSdpsSrcs));
|
||||||
|
const shieldsSdpsTooltip = objToTooltip(
|
||||||
|
translate,
|
||||||
|
mapValues(shieldSdpsSrcs, (v) => formats.f1(v)),
|
||||||
|
);
|
||||||
|
const shieldsSdpsPie = objToPie(
|
||||||
|
translate,
|
||||||
|
mapValues(shieldSdpsSrcs, (v) => Math.round(v)),
|
||||||
|
);
|
||||||
|
|
||||||
const totalArmourSDps = sumSDps(armourSDpsObject);
|
const armourSdpsSrcs = mergeWith(
|
||||||
const totalArmourSDpsTooltipDetails = getSDpsTooltip(translate, formats, armourSDpsObject);
|
clone(portions),
|
||||||
const armourSDpsData = getSDpsData(translate, armourSDpsObject);
|
armourMults,
|
||||||
|
(objV, srcV) => sustained.dps * hardnessMultiplier * rangeMultiplier *
|
||||||
|
objV * srcV,
|
||||||
|
);
|
||||||
|
const armourSdps = sum(values(armourSdpsSrcs));
|
||||||
|
const totalArmourSDpsTooltipDetails = objToTooltip(
|
||||||
|
translate,
|
||||||
|
mapValues(armourSdpsSrcs, (v) => formats.f1(v)),
|
||||||
|
);
|
||||||
|
const armourSDpsData = objToPie(
|
||||||
|
translate,
|
||||||
|
mapValues(armourSdpsSrcs, (v) => Math.round(v)),
|
||||||
|
);
|
||||||
|
|
||||||
const timeToDepleteShields = Calc.timeToDeplete(opponentShields.total, totalShieldsSDps, totalSEps, pd.getWeaponsCapacity(), pd.getWeaponsRechargeRate() * (wep / 4));
|
const drainedPortions = {
|
||||||
const timeToDepleteArmour = Calc.timeToDeplete(opponentArmour.total, totalArmourSDps, totalSEps, pd.getWeaponsCapacity(), pd.getWeaponsRechargeRate() * (wep / 4));
|
Absolute: drained.types.abs,
|
||||||
|
Explosive: drained.types.expl,
|
||||||
|
Kinetic: drained.types.kin,
|
||||||
|
Thermic: drained.types.therm,
|
||||||
|
};
|
||||||
|
|
||||||
|
// How much damage do we deal, before the capacitor is empty?
|
||||||
|
const armourLeft = oppArmour.armour - (timeToDrain * armourSdps);
|
||||||
|
// If we can't kill the enemy on one capacitor, factor in drained damage
|
||||||
|
let timeToDepleteArmour;
|
||||||
|
if (armourLeft > 0) {
|
||||||
|
const effectiveDrainedDps = sum(values(mergeWith(
|
||||||
|
clone(drainedPortions),
|
||||||
|
armourMults,
|
||||||
|
(objV, srcV) => objV * srcV,
|
||||||
|
))) * drained.dps * rangeMultiplier *
|
||||||
|
hardnessMultiplier;
|
||||||
|
timeToDepleteArmour = effectiveDrainedDps === 0 ? Infinity :
|
||||||
|
timeToDrain + (armourLeft / effectiveDrainedDps);
|
||||||
|
} else {
|
||||||
|
timeToDepleteArmour = oppArmour.armour / armourSdps;
|
||||||
|
}
|
||||||
|
|
||||||
|
// How much damage do we deal, before the capacitor is empty?
|
||||||
|
const shieldsLeft = oppShield.withSCBs - (timeToDrain * shieldsSdps);
|
||||||
|
// If we can't kill the enemy on one capacitor, factor in drained damage
|
||||||
|
let timeToDepleteShields;
|
||||||
|
if (shieldsLeft > 0) {
|
||||||
|
const effectiveDrainedDps = sum(values(mergeWith(
|
||||||
|
clone(drainedPortions),
|
||||||
|
shieldMults,
|
||||||
|
(objV, srcV) => objV * srcV,
|
||||||
|
))) * drained.dps * rangeMultiplier;
|
||||||
|
timeToDepleteShields = effectiveDrainedDps === 0 ? Infinity :
|
||||||
|
timeToDrain + (shieldsLeft / effectiveDrainedDps);
|
||||||
|
} else {
|
||||||
|
timeToDepleteShields = oppShield.withSCBs / shieldsSdps;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<span id='offence'>
|
<span id='offence'>
|
||||||
<div className='group full'>
|
<div className='group full'>
|
||||||
<table>
|
<table>
|
||||||
<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, 'classRating')}>{translate('weapon')}</th>
|
||||||
<th colSpan='1'>{translate('overall')}</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')}
|
||||||
<th className='lft sortable' onMouseOver={termtip.bind(null, 'TT_EFFECTIVE_SDPS_SHIELDS')} onMouseOut={tooltip.bind(null, null)} onClick={sortOrder.bind(this, 'esdpss')}>{'sdps'}</th>
|
onMouseOut={tooltip.bind(null, null)} onClick={sortOrder.bind(this, 'sdps')}>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='lft sortable' onMouseOver={termtip.bind(null, 'TT_EFFECTIVE_SDPS_SHIELDS')}
|
||||||
<th className='lft sortable' onMouseOver={termtip.bind(null, 'TT_EFFECTIVE_SDPS_ARMOUR')} onMouseOut={tooltip.bind(null, null)}onClick={sortOrder.bind(this, 'esdpsh')}>{'sdps'}</th>
|
onMouseOut={tooltip.bind(null, null)} onClick={sortOrder.bind(this, 'shieldSdps')}>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_SHIELDS')}
|
||||||
</tr>
|
onMouseOut={tooltip.bind(null, null)}onClick={sortOrder.bind(this, 'shieldEft')}>eft</th>
|
||||||
</thead>
|
<th className='lft sortable' onMouseOver={termtip.bind(null, 'TT_EFFECTIVE_SDPS_ARMOUR')}
|
||||||
|
onMouseOut={tooltip.bind(null, null)}onClick={sortOrder.bind(this, 'armourSdps')}>sdps</th>
|
||||||
|
<th className='sortable' onMouseOver={termtip.bind(null, 'TT_EFFECTIVENESS_ARMOUR')}
|
||||||
|
onMouseOut={tooltip.bind(null, null)} onClick={sortOrder.bind(this, 'armourEft')}>eft</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{rows}
|
{rows.map((row) => (
|
||||||
|
<tr key={row.slot}>
|
||||||
|
<td className='ri'>
|
||||||
|
{row.mount == 'F' ? <span onMouseOver={termtip.bind(null, 'fixed')} onMouseOut={tooltip.bind(null, null)}><MountFixed className='icon'/></span> : null}
|
||||||
|
{row.mount == 'G' ? <span onMouseOver={termtip.bind(null, 'gimballed')} onMouseOut={tooltip.bind(null, null)}><MountGimballed /></span> : null}
|
||||||
|
{row.mount == 'T' ? <span onMouseOver={termtip.bind(null, 'turreted')} onMouseOut={tooltip.bind(null, null)}><MountTurret /></span> : null}
|
||||||
|
{row.classRating} {translate(row.type)}
|
||||||
|
{row.bpTitle}
|
||||||
|
</td>
|
||||||
|
<td className='ri'>
|
||||||
|
<span onMouseOver={termtip.bind(null, row.baseSdpsTooltip)}
|
||||||
|
onMouseOut={tooltip.bind(null, null)}
|
||||||
|
>{formats.f1(row.sdps)}</span></td>
|
||||||
|
<td className='ri'>
|
||||||
|
<span onMouseOver={termtip.bind(null, row.shieldsSdpsTooltip)}
|
||||||
|
onMouseOut={tooltip.bind(null, null)}
|
||||||
|
>{formats.f1(row.shieldSdps)}</span></td>
|
||||||
|
<td className='ri'>
|
||||||
|
<span onMouseOver={termtip.bind(null, row.shieldsEftTooltip)}
|
||||||
|
onMouseOut={tooltip.bind(null, null)}
|
||||||
|
>{formats.pct1(row.shieldEft)}</span></td>
|
||||||
|
<td className='ri'>
|
||||||
|
<span onMouseOver={termtip.bind(null, row.armourSdpsTooltip)}
|
||||||
|
onMouseOut={tooltip.bind(null, null)}
|
||||||
|
>{formats.f1(row.armourSdps)}</span></td>
|
||||||
|
<td className='ri'>
|
||||||
|
<span onMouseOver={termtip.bind(null, row.armourEftTooltip)}
|
||||||
|
onMouseOut={tooltip.bind(null, null)}
|
||||||
|
>{formats.pct1(row.armourEft)}</span></td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
{rows.length > 0 &&
|
{rows.length > 0 &&
|
||||||
<tr>
|
<tr>
|
||||||
<td></td>
|
<td></td>
|
||||||
<td className='ri'><span onMouseOver={termtip.bind(null, totalSDpsTooltipDetails)} onMouseOut={tooltip.bind(null, null)}>={formats.f1(totalSDps)}</span></td>
|
<td className='ri'>
|
||||||
<td className='ri'><span onMouseOver={termtip.bind(null, totalShieldsSDpsTooltipDetails)} onMouseOut={tooltip.bind(null, null)}>={formats.f1(totalShieldsSDps)}</span></td>
|
<span onMouseOver={termtip.bind(null, sdpsTooltip)} onMouseOut={tooltip.bind(null, null)}>
|
||||||
|
={formats.f1(sustained.dps)}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td className='ri'>
|
||||||
|
<span onMouseOver={termtip.bind(null, shieldsSdpsTooltip)} onMouseOut={tooltip.bind(null, null)}>
|
||||||
|
={formats.f1(shieldsSdps)}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
<td></td>
|
<td></td>
|
||||||
<td className='ri'><span onMouseOver={termtip.bind(null, totalArmourSDpsTooltipDetails)} onMouseOut={tooltip.bind(null, null)}>={formats.f1(totalArmourSDps)}</span></td>
|
<td className='ri'>
|
||||||
|
<span onMouseOver={termtip.bind(null, totalArmourSDpsTooltipDetails)} onMouseOut={tooltip.bind(null, null)}>
|
||||||
|
={formats.f1(armourSdps)}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
<td></td>
|
<td></td>
|
||||||
</tr>
|
</tr>
|
||||||
}
|
}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
<div className='group quarter'>
|
<div className='group quarter'>
|
||||||
<h2>{translate('offence metrics')}</h2>
|
<h2>{translate('offence metrics')}</h2>
|
||||||
<h2 onMouseOver={termtip.bind(null, translate('TT_TIME_TO_DRAIN_WEP'))} onMouseOut={tooltip.bind(null, null)}>{translate('PHRASE_TIME_TO_DRAIN_WEP')}<br/>{timeToDrain === Infinity ? translate('never') : formats.time(timeToDrain)}</h2>
|
<h2 onMouseOver={termtip.bind(null, translate('TT_TIME_TO_DRAIN_WEP'))}
|
||||||
<h2 onMouseOver={termtip.bind(null, translate('TT_EFFECTIVE_SDPS_SHIELDS'))} onMouseOut={tooltip.bind(null, null)}>{translate('PHRASE_EFFECTIVE_SDPS_SHIELDS')}<br/>{formats.f1(totalShieldsSDps)}</h2>
|
onMouseOut={tooltip.bind(null, null)}>
|
||||||
<h2 onMouseOver={termtip.bind(null, translate('TT_TIME_TO_REMOVE_SHIELDS'))} onMouseOut={tooltip.bind(null, null)}>{translate('PHRASE_TIME_TO_REMOVE_SHIELDS')}<br/>{timeToDepleteShields === Infinity ? translate('never') : formats.time(timeToDepleteShields)}</h2>
|
{translate('PHRASE_TIME_TO_DRAIN_WEP')}<br/>
|
||||||
<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>
|
{timeToDrain === Infinity ? translate('never') : formats.time(timeToDrain)}
|
||||||
<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>
|
||||||
|
<h2 onMouseOver={termtip.bind(null, translate('TT_EFFECTIVE_SDPS_SHIELDS'))}
|
||||||
|
onMouseOut={tooltip.bind(null, null)}>
|
||||||
|
{translate('PHRASE_EFFECTIVE_SDPS_SHIELDS')}<br/>
|
||||||
|
{formats.f1(shieldsSdps)}
|
||||||
|
</h2>
|
||||||
|
<h2 onMouseOver={termtip.bind(null, translate('TT_TIME_TO_REMOVE_SHIELDS'))}
|
||||||
|
onMouseOut={tooltip.bind(null, null)}>
|
||||||
|
{translate('PHRASE_TIME_TO_REMOVE_SHIELDS')}<br/>
|
||||||
|
{timeToDepleteShields === Infinity ? translate('never') : formats.time(timeToDepleteShields)}
|
||||||
|
</h2>
|
||||||
|
<h2 onMouseOver={termtip.bind(null, translate('TT_EFFECTIVE_SDPS_ARMOUR'))}
|
||||||
|
onMouseOut={tooltip.bind(null, null)}>
|
||||||
|
{translate('PHRASE_EFFECTIVE_SDPS_ARMOUR')}<br/>
|
||||||
|
{formats.f1(armourSdps)}
|
||||||
|
</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'>
|
<div className='group quarter'>
|
||||||
<h2 onMouseOver={termtip.bind(null, translate('PHRASE_OVERALL_DAMAGE'))} onMouseOut={tooltip.bind(null, null)}>{translate('overall damage')}</h2>
|
<h2 onMouseOver={termtip.bind(null, translate('PHRASE_OVERALL_DAMAGE'))}
|
||||||
<PieChart data={totalSDpsData} />
|
onMouseOut={tooltip.bind(null, null)}>
|
||||||
|
{translate('overall damage')}
|
||||||
|
</h2>
|
||||||
|
<PieChart data={sdpsPie} />
|
||||||
</div>
|
</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'))}
|
||||||
<PieChart data={shieldsSDpsData} />
|
onMouseOut={tooltip.bind(null, null)}>
|
||||||
|
{translate('shield damage sources')}
|
||||||
|
</h2>
|
||||||
|
<PieChart data={shieldsSdpsPie} />
|
||||||
</div>
|
</div>
|
||||||
<div className='group quarter'>
|
<div className='group quarter'>
|
||||||
<h2 onMouseOver={termtip.bind(null, translate('PHRASE_ARMOUR_DAMAGE'))} onMouseOut={tooltip.bind(null, null)}>{translate('armour damage sources')}</h2>
|
<h2 onMouseOver={termtip.bind(null, translate('PHRASE_ARMOUR_DAMAGE'))}
|
||||||
|
onMouseOut={tooltip.bind(null, null)}>
|
||||||
|
{translate('armour damage sources')}
|
||||||
|
</h2>
|
||||||
<PieChart data={armourSDpsData} />
|
<PieChart data={armourSDpsData} />
|
||||||
</div>
|
</div>
|
||||||
</span>);
|
</span>);
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import cn from 'classnames';
|
import cn from 'classnames';
|
||||||
import { Ships } from 'coriolis-data/dist';
|
|
||||||
import Ship from '../shipyard/Ship';
|
|
||||||
import Persist from '../stores/Persist';
|
import Persist from '../stores/Persist';
|
||||||
import TranslatedComponent from './TranslatedComponent';
|
import TranslatedComponent from './TranslatedComponent';
|
||||||
import PowerManagement from './PowerManagement';
|
import PowerManagement from './PowerManagement';
|
||||||
@@ -13,29 +11,24 @@ import Movement from './Movement';
|
|||||||
import Offence from './Offence';
|
import Offence from './Offence';
|
||||||
import Defence from './Defence';
|
import Defence from './Defence';
|
||||||
import WeaponDamageChart from './WeaponDamageChart';
|
import WeaponDamageChart from './WeaponDamageChart';
|
||||||
|
import Pips from '../components/Pips';
|
||||||
|
import Boost from '../components/Boost';
|
||||||
|
import Fuel from '../components/Fuel';
|
||||||
|
import Cargo from '../components/Cargo';
|
||||||
|
import ShipPicker from '../components/ShipPicker';
|
||||||
|
import EngagementRange from '../components/EngagementRange';
|
||||||
|
import autoBind from 'auto-bind';
|
||||||
|
import { ShipProps } from 'ed-forge';
|
||||||
|
const { CARGO_CAPACITY, FUEL_CAPACITY } = ShipProps;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Outfitting subpages
|
* Outfitting subpages
|
||||||
*/
|
*/
|
||||||
export default class OutfittingSubpages extends TranslatedComponent {
|
export default class OutfittingSubpages extends TranslatedComponent {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
ship: PropTypes.object.isRequired,
|
ship: PropTypes.object.isRequired,
|
||||||
code: PropTypes.string.isRequired,
|
code: PropTypes.string.isRequired,
|
||||||
onChange: PropTypes.func.isRequired,
|
|
||||||
buildName: PropTypes.string,
|
buildName: PropTypes.string,
|
||||||
sys: PropTypes.number.isRequired,
|
|
||||||
eng: PropTypes.number.isRequired,
|
|
||||||
wep: PropTypes.number.isRequired,
|
|
||||||
cargo: PropTypes.number.isRequired,
|
|
||||||
fuel: PropTypes.number.isRequired,
|
|
||||||
boost: PropTypes.bool.isRequired,
|
|
||||||
engagementRange: PropTypes.number.isRequired,
|
|
||||||
opponent: PropTypes.object.isRequired,
|
|
||||||
opponentBuild: PropTypes.string,
|
|
||||||
opponentSys: PropTypes.number.isRequired,
|
|
||||||
opponentEng: PropTypes.number.isRequired,
|
|
||||||
opponentWep: PropTypes.number.isRequired,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -44,13 +37,17 @@ export default class OutfittingSubpages extends TranslatedComponent {
|
|||||||
*/
|
*/
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this._powerTab = this._powerTab.bind(this);
|
autoBind(this);
|
||||||
this._profilesTab = this._profilesTab.bind(this);
|
|
||||||
this._offenceTab = this._offenceTab.bind(this);
|
|
||||||
this._defenceTab = this._defenceTab.bind(this);
|
|
||||||
|
|
||||||
|
this.props.ship.setOpponent(this.props.ship);
|
||||||
this.state = {
|
this.state = {
|
||||||
|
boost: false,
|
||||||
|
cargo: props.ship.get(CARGO_CAPACITY),
|
||||||
|
fuel: props.ship.get(FUEL_CAPACITY),
|
||||||
|
pips: props.ship.getDistributorSettingsObject(),
|
||||||
tab: Persist.getOutfittingTab() || 'power',
|
tab: Persist.getOutfittingTab() || 'power',
|
||||||
|
engagementRange: 1000,
|
||||||
|
opponent: this.props.ship,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -59,128 +56,114 @@ export default class OutfittingSubpages extends TranslatedComponent {
|
|||||||
* @param {string} tab Tab name
|
* @param {string} tab Tab name
|
||||||
*/
|
*/
|
||||||
_showTab(tab) {
|
_showTab(tab) {
|
||||||
|
Persist.setOutfittingTab(tab);
|
||||||
this.setState({ tab });
|
this.setState({ tab });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Render the power tab
|
|
||||||
* @return {React.Component} Tab contents
|
|
||||||
*/
|
|
||||||
_powerTab() {
|
|
||||||
let { ship, buildName, code, onChange } = this.props;
|
|
||||||
Persist.setOutfittingTab('power');
|
|
||||||
|
|
||||||
const powerMarker = `${ship.toString()}`;
|
|
||||||
const costMarker = `${ship.toString().split('.')[0]}`;
|
|
||||||
|
|
||||||
return <div>
|
|
||||||
<PowerManagement ship={ship} code={powerMarker} onChange={onChange} />
|
|
||||||
<CostSection ship={ship} buildName={buildName} code={costMarker} />
|
|
||||||
</div>;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Render the profiles tab
|
|
||||||
* @return {React.Component} Tab contents
|
|
||||||
*/
|
|
||||||
_profilesTab() {
|
|
||||||
const { ship, opponent, cargo, fuel, eng, boost, engagementRange, opponentSys } = this.props;
|
|
||||||
const { translate } = this.context.language;
|
|
||||||
let realBoost = boost && ship.canBoost(cargo, fuel);
|
|
||||||
Persist.setOutfittingTab('profiles');
|
|
||||||
|
|
||||||
const engineProfileMarker = `${ship.toString()}:${cargo}:${fuel}:${eng}:${realBoost}`;
|
|
||||||
const fsdProfileMarker = `${ship.toString()}:${cargo}:${fuel}`;
|
|
||||||
const movementMarker = `${ship.topSpeed}:${ship.pitch}:${ship.roll}:${ship.yaw}:${ship.canBoost(cargo, fuel)}`;
|
|
||||||
const damageMarker = `${ship.toString()}:${opponent.toString()}:${engagementRange}:${opponentSys}`;
|
|
||||||
|
|
||||||
return <div>
|
|
||||||
<div className='group third'>
|
|
||||||
<h1>{translate('engine profile')}</h1>
|
|
||||||
<EngineProfile ship={ship} marker={engineProfileMarker} fuel={fuel} cargo={cargo} eng={eng} boost={realBoost} />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='group third'>
|
|
||||||
<h1>{translate('fsd profile')}</h1>
|
|
||||||
<FSDProfile ship={ship} marker={fsdProfileMarker} fuel={fuel} cargo={cargo} />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='group third'>
|
|
||||||
<h1>{translate('movement profile')}</h1>
|
|
||||||
<Movement marker={movementMarker} ship={ship} boost={boost} eng={eng} cargo={cargo} fuel={fuel} />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='group half'>
|
|
||||||
<h1>{translate('damage to opponent\'s shields')}</h1>
|
|
||||||
<WeaponDamageChart marker={damageMarker} ship={ship} opponent={opponent} opponentSys={opponentSys} hull={false} engagementRange={engagementRange} />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='group half'>
|
|
||||||
<h1>{translate('damage to opponent\'s hull')}</h1>
|
|
||||||
<WeaponDamageChart marker={damageMarker} ship={ship} opponent={opponent} opponentSys={opponentSys} hull={true} engagementRange={engagementRange} />
|
|
||||||
</div>
|
|
||||||
</div>;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Render the offence tab
|
|
||||||
* @return {React.Component} Tab contents
|
|
||||||
*/
|
|
||||||
_offenceTab() {
|
|
||||||
const { ship, sys, eng, wep, cargo, fuel, boost, engagementRange, opponent, opponentBuild, opponentSys } = this.props;
|
|
||||||
Persist.setOutfittingTab('offence');
|
|
||||||
|
|
||||||
const marker = `${ship.toString()}${opponent.toString()}${opponentBuild}${engagementRange}${opponentSys}`;
|
|
||||||
|
|
||||||
return <div>
|
|
||||||
<Offence marker={marker} ship={ship} opponent={opponent} wep={wep} opponentSys={opponentSys} engagementrange={engagementRange}/>
|
|
||||||
</div>;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Render the defence tab
|
|
||||||
* @return {React.Component} Tab contents
|
|
||||||
*/
|
|
||||||
_defenceTab() {
|
|
||||||
const { ship, sys, eng, wep, cargo, fuel, boost, engagementRange, opponent, opponentBuild, opponentWep } = this.props;
|
|
||||||
Persist.setOutfittingTab('defence');
|
|
||||||
|
|
||||||
const marker = `${ship.toString()}${opponent.toString()}{opponentBuild}${engagementRange}${opponentWep}`;
|
|
||||||
|
|
||||||
return <div>
|
|
||||||
<Defence marker={marker} ship={ship} opponent={opponent} sys={sys} opponentWep={opponentWep} engagementrange={engagementRange}/>
|
|
||||||
</div>;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Render the section
|
* Render the section
|
||||||
* @return {React.Component} Contents
|
* @return {React.Component} Contents
|
||||||
*/
|
*/
|
||||||
render() {
|
render() {
|
||||||
const tab = this.state.tab;
|
const { buildName, code, ship } = this.props;
|
||||||
const translate = this.context.language.translate;
|
const { boost, cargo, fuel, pips, tab, engagementRange, opponent } = this.state;
|
||||||
let tabSection;
|
const { translate } = this.context.language;
|
||||||
|
|
||||||
switch (tab) {
|
|
||||||
case 'power': tabSection = this._powerTab(); break;
|
|
||||||
case 'profiles': tabSection = this._profilesTab(); break;
|
|
||||||
case 'offence': tabSection = this._offenceTab(); break;
|
|
||||||
case 'defence': tabSection = this._defenceTab(); break;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
const cargoCapacity = ship.get(CARGO_CAPACITY);
|
||||||
|
const showCargoSlider = cargoCapacity > 0;
|
||||||
return (
|
return (
|
||||||
<div className='group full' style={{ minHeight: '1000px' }}>
|
<div>
|
||||||
<table className='tabs'>
|
{/* Control of ship and opponent */}
|
||||||
<thead>
|
<div className="group quarter">
|
||||||
<tr>
|
<h2 style={{ verticalAlign: 'middle', textAlign: 'center' }}>
|
||||||
<th style={{ width:'25%' }} className={cn({ active: tab == 'power' })} onClick={this._showTab.bind(this, 'power')} >{translate('power and costs')}</th>
|
{translate('ship control')}
|
||||||
<th style={{ width:'25%' }} className={cn({ active: tab == 'profiles' })} onClick={this._showTab.bind(this, 'profiles')} >{translate('profiles')}</th>
|
</h2>
|
||||||
<th style={{ width:'25%' }} className={cn({ active: tab == 'offence' })} onClick={this._showTab.bind(this, 'offence')} >{translate('offence')}</th>
|
<Boost boost={boost} onChange={(boost) => this.setState({ boost })} />
|
||||||
<th style={{ width:'25%' }} className={cn({ active: tab == 'defence' })} onClick={this._showTab.bind(this, 'defence')} >{translate('defence')}</th>
|
</div>
|
||||||
</tr>
|
<div className="group quarter">
|
||||||
</thead>
|
<h2 style={{ verticalAlign: 'middle', textAlign: 'center' }}>
|
||||||
</table>
|
{translate('opponent')}
|
||||||
{tabSection}
|
</h2>
|
||||||
|
<ShipPicker ship={ship} onChange={(opponent) => this.setState({ opponent })} />
|
||||||
|
</div>
|
||||||
|
<div className={cn('group', { quarter: showCargoSlider, half: !showCargoSlider })}>
|
||||||
|
<Fuel fuelCapacity={ship.get(FUEL_CAPACITY)} fuel={fuel}
|
||||||
|
onChange={(fuel) => this.setState({ fuel })} />
|
||||||
|
</div>
|
||||||
|
{showCargoSlider ?
|
||||||
|
<div className="group quarter">
|
||||||
|
<Cargo cargoCapacity={cargoCapacity} cargo={cargo}
|
||||||
|
onChange={(cargo) => this.setState({ cargo })} />
|
||||||
|
</div> : null}
|
||||||
|
<div className="group half">
|
||||||
|
<Pips ship={ship} pips={pips} onChange={(pips) => this.setState({ pips })} />
|
||||||
|
</div>
|
||||||
|
<div className="group half">
|
||||||
|
<EngagementRange ship={ship} engagementRange={engagementRange}
|
||||||
|
onChange={(engagementRange) => this.setState({ engagementRange })} />
|
||||||
|
</div>
|
||||||
|
<div className='group full' style={{ minHeight: '1000px' }}>
|
||||||
|
<table className='tabs'>
|
||||||
|
{/* Select tab section */}
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th style={{ width:'25%' }} className={cn({ active: tab == 'power' })}
|
||||||
|
onClick={this._showTab.bind(this, 'power')}>
|
||||||
|
{translate('power and costs')}
|
||||||
|
</th>
|
||||||
|
<th style={{ width:'25%' }} className={cn({ active: tab == 'profiles' })}
|
||||||
|
onClick={this._showTab.bind(this, 'profiles')}>
|
||||||
|
{translate('profiles')}</th>
|
||||||
|
<th style={{ width:'25%' }} className={cn({ active: tab == 'offence' })}
|
||||||
|
onClick={this._showTab.bind(this, 'offence')}>
|
||||||
|
{translate('offence')}
|
||||||
|
</th>
|
||||||
|
<th style={{ width:'25%' }} className={cn({ active: tab == 'defence' })}
|
||||||
|
onClick={this._showTab.bind(this, 'defence')}>
|
||||||
|
{translate('tab_defence')}
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
</table>
|
||||||
|
{/* Show selected tab */}
|
||||||
|
{tab == 'power' ?
|
||||||
|
<div>
|
||||||
|
<PowerManagement ship={ship} code={code} />
|
||||||
|
<CostSection ship={ship} buildName={buildName} code={code} />
|
||||||
|
</div> : null}
|
||||||
|
{tab == 'profiles' ?
|
||||||
|
<div>
|
||||||
|
<div className='group third'>
|
||||||
|
<h1>{translate('engine profile')}</h1>
|
||||||
|
<EngineProfile code={code} ship={ship} fuel={fuel} cargo={cargo} pips={pips} boost={boost} />
|
||||||
|
</div>
|
||||||
|
<div className='group third'>
|
||||||
|
<h1>{translate('fsd profile')}</h1>
|
||||||
|
<FSDProfile code={code} ship={ship} fuel={fuel} cargo={cargo} />
|
||||||
|
</div>
|
||||||
|
<div className='group third'>
|
||||||
|
<h1>{translate('movement profile')}</h1>
|
||||||
|
<Movement code={code} ship={ship} boost={boost} pips={pips} />
|
||||||
|
</div>
|
||||||
|
<div className='group third'>
|
||||||
|
<h1>{translate('damage to opponent\'s shields')}</h1>
|
||||||
|
<WeaponDamageChart code={code} ship={ship} opponentDefence={opponent.getShield()} engagementRange={engagementRange} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='group third'>
|
||||||
|
<h1>{translate('damage to opponent\'s hull')}</h1>
|
||||||
|
<WeaponDamageChart code={code} ship={ship} opponentDefence={opponent.getArmour()} engagementRange={engagementRange} />
|
||||||
|
</div>
|
||||||
|
</div> : null}
|
||||||
|
{tab == 'offence' ?
|
||||||
|
<div>
|
||||||
|
<Offence code={code} ship={ship} opponent={opponent} engagementRange={engagementRange} />
|
||||||
|
</div> : null}
|
||||||
|
{tab == 'defence' ?
|
||||||
|
<div>
|
||||||
|
<Defence code={code} ship={ship} opponent={opponent} engagementRange={engagementRange} />
|
||||||
|
</div> : null}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ 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
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,13 +1,9 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import TranslatedComponent from './TranslatedComponent';
|
import TranslatedComponent from './TranslatedComponent';
|
||||||
import { Ships } from 'coriolis-data/dist';
|
|
||||||
import { nameComparator } from '../utils/SlotFunctions';
|
|
||||||
import { Pip } from './SvgIcons';
|
import { Pip } from './SvgIcons';
|
||||||
import LineChart from '../components/LineChart';
|
import { autoBind } from 'react-extras';
|
||||||
import Slider from '../components/Slider';
|
import { Ship } from 'ed-forge';
|
||||||
import * as ModuleUtils from '../shipyard/ModuleUtils';
|
|
||||||
import Module from '../shipyard/Module';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Pips displays SYS/ENG/WEP pips and allows users to change them with key presses by clicking on the relevant area.
|
* Pips displays SYS/ENG/WEP pips and allows users to change them with key presses by clicking on the relevant area.
|
||||||
@@ -15,10 +11,9 @@ import Module from '../shipyard/Module';
|
|||||||
*/
|
*/
|
||||||
export default class Pips extends TranslatedComponent {
|
export default class Pips extends TranslatedComponent {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
sys: PropTypes.number.isRequired,
|
ship: PropTypes.instanceOf(Ship).isRequired,
|
||||||
eng: PropTypes.number.isRequired,
|
pips: PropTypes.object.isRequired,
|
||||||
wep: PropTypes.number.isRequired,
|
onChange: PropTypes.func.isRequired,
|
||||||
onChange: PropTypes.func.isRequired
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -28,9 +23,13 @@ export default class Pips extends TranslatedComponent {
|
|||||||
*/
|
*/
|
||||||
constructor(props, context) {
|
constructor(props, context) {
|
||||||
super(props);
|
super(props);
|
||||||
const { sys, eng, wep } = props;
|
autoBind(this);
|
||||||
|
|
||||||
this._keyDown = this._keyDown.bind(this);
|
const { ship } = props;
|
||||||
|
this._incSys = this._change(ship.incSys);
|
||||||
|
this._incEng = this._change(ship.incEng);
|
||||||
|
this._incWep = this._change(ship.incWep);
|
||||||
|
this._reset = this._change(ship.pipsReset);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -75,181 +74,43 @@ export default class Pips extends TranslatedComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle a click
|
* Creates a function that handles pip assignment and call `onChance`.
|
||||||
* @param {string} which Which item was clicked
|
* @param {String} cb Callback that handles the actual pip assignment
|
||||||
|
* @param {Boolean} isMc True when increase is by multi crew
|
||||||
|
* @returns {Function} Function that handles pip assigment
|
||||||
*/
|
*/
|
||||||
onClick(which) {
|
_change(cb, isMc) {
|
||||||
if (which == 'SYS') {
|
return () => {
|
||||||
this._incSys();
|
cb(isMc);
|
||||||
} else if (which == 'ENG') {
|
this.props.onChange(this.props.ship.getDistributorSettingsObject());
|
||||||
this._incEng();
|
};
|
||||||
} else if (which == 'WEP') {
|
|
||||||
this._incWep();
|
|
||||||
} else if (which == 'RST') {
|
|
||||||
this._reset();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reset the capacitor
|
|
||||||
*/
|
|
||||||
_reset() {
|
|
||||||
let { sys, eng, wep } = this.props;
|
|
||||||
if (sys != 2 || eng != 2 || wep != 2) {
|
|
||||||
sys = eng = wep = 2;
|
|
||||||
this.props.onChange(sys, eng, wep);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Increment the SYS capacitor
|
|
||||||
*/
|
|
||||||
_incSys() {
|
|
||||||
let { sys, eng, wep } = this.props;
|
|
||||||
|
|
||||||
const required = Math.min(1, 4 - sys);
|
|
||||||
if (required > 0) {
|
|
||||||
if (required == 0.5) {
|
|
||||||
// Take from whichever is larger
|
|
||||||
if (eng > wep) {
|
|
||||||
eng -= 0.5;
|
|
||||||
sys += 0.5;
|
|
||||||
} else {
|
|
||||||
wep -= 0.5;
|
|
||||||
sys += 0.5;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Required is 1 - take from both if possible
|
|
||||||
if (eng == 0) {
|
|
||||||
wep -= 1;
|
|
||||||
sys += 1;
|
|
||||||
} else if (wep == 0) {
|
|
||||||
eng -= 1;
|
|
||||||
sys += 1;
|
|
||||||
} else {
|
|
||||||
eng -= 0.5;
|
|
||||||
wep -= 0.5;
|
|
||||||
sys += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.props.onChange(sys, eng, wep);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Increment the ENG capacitor
|
|
||||||
*/
|
|
||||||
_incEng() {
|
|
||||||
let { sys, eng, wep } = this.props;
|
|
||||||
|
|
||||||
const required = Math.min(1, 4 - eng);
|
|
||||||
if (required > 0) {
|
|
||||||
if (required == 0.5) {
|
|
||||||
// Take from whichever is larger
|
|
||||||
if (sys > wep) {
|
|
||||||
sys -= 0.5;
|
|
||||||
eng += 0.5;
|
|
||||||
} else {
|
|
||||||
wep -= 0.5;
|
|
||||||
eng += 0.5;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Required is 1 - take from both if possible
|
|
||||||
if (sys == 0) {
|
|
||||||
wep -= 1;
|
|
||||||
eng += 1;
|
|
||||||
} else if (wep == 0) {
|
|
||||||
sys -= 1;
|
|
||||||
eng += 1;
|
|
||||||
} else {
|
|
||||||
sys -= 0.5;
|
|
||||||
wep -= 0.5;
|
|
||||||
eng += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.props.onChange(sys, eng, wep);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Increment the WEP capacitor
|
|
||||||
*/
|
|
||||||
_incWep() {
|
|
||||||
let { sys, eng, wep } = this.props;
|
|
||||||
|
|
||||||
const required = Math.min(1, 4 - wep);
|
|
||||||
if (required > 0) {
|
|
||||||
if (required == 0.5) {
|
|
||||||
// Take from whichever is larger
|
|
||||||
if (sys > eng) {
|
|
||||||
sys -= 0.5;
|
|
||||||
wep += 0.5;
|
|
||||||
} else {
|
|
||||||
eng -= 0.5;
|
|
||||||
wep += 0.5;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Required is 1 - take from both if possible
|
|
||||||
if (sys == 0) {
|
|
||||||
eng -= 1;
|
|
||||||
wep += 1;
|
|
||||||
} else if (eng == 0) {
|
|
||||||
sys -= 1;
|
|
||||||
wep += 1;
|
|
||||||
} else {
|
|
||||||
sys -= 0.5;
|
|
||||||
eng -= 0.5;
|
|
||||||
wep += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.props.onChange(sys, eng, wep);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set up the rendering for pips
|
* Set up the rendering for pips
|
||||||
* @param {int} sys the SYS pips
|
|
||||||
* @param {int} eng the ENG pips
|
|
||||||
* @param {int} wep the WEP pips
|
|
||||||
* @returns {Object} Object containing the rendering for the pips
|
* @returns {Object} Object containing the rendering for the pips
|
||||||
*/
|
*/
|
||||||
_renderPips(sys, eng, wep) {
|
_renderPips() {
|
||||||
const pipsSvg = {};
|
const pipsSvg = {
|
||||||
|
Sys: [],
|
||||||
|
Eng: [],
|
||||||
|
Wep: [],
|
||||||
|
};
|
||||||
|
|
||||||
// SYS
|
for (let k in this.props.pips) {
|
||||||
pipsSvg['SYS'] = [];
|
let { base, mc } = this.props.pips[k];
|
||||||
for (let i = 0; i < Math.floor(sys); i++) {
|
for (let i = 0; i < Math.floor(base); i++) {
|
||||||
pipsSvg['SYS'].push(<Pip className='full' key={i} />);
|
pipsSvg[k].push(<Pip key={i} className='full' />);
|
||||||
}
|
}
|
||||||
if (sys > Math.floor(sys)) {
|
if (base > Math.floor(base)) {
|
||||||
pipsSvg['SYS'].push(<Pip className='half' key={'half'} />);
|
pipsSvg[k].push(<Pip className='half' key={'half'} />);
|
||||||
}
|
}
|
||||||
for (let i = Math.floor(sys + 0.5); i < 4; i++) {
|
for (let i = 0; i < mc; i++) {
|
||||||
pipsSvg['SYS'].push(<Pip className='empty' key={i} />);
|
pipsSvg[k].push(<Pip key={base + i} className='mc' />);
|
||||||
}
|
}
|
||||||
|
for (let i = Math.ceil(base + mc); i < 4; i++) {
|
||||||
// ENG
|
pipsSvg[k].push(<Pip className='empty' key={i} />);
|
||||||
pipsSvg['ENG'] = [];
|
}
|
||||||
for (let i = 0; i < Math.floor(eng); i++) {
|
|
||||||
pipsSvg['ENG'].push(<Pip className='full' key={i} />);
|
|
||||||
}
|
|
||||||
if (eng > Math.floor(eng)) {
|
|
||||||
pipsSvg['ENG'].push(<Pip className='half' key={'half'} />);
|
|
||||||
}
|
|
||||||
for (let i = Math.floor(eng + 0.5); i < 4; i++) {
|
|
||||||
pipsSvg['ENG'].push(<Pip className='empty' key={i} />);
|
|
||||||
}
|
|
||||||
|
|
||||||
// WEP
|
|
||||||
pipsSvg['WEP'] = [];
|
|
||||||
for (let i = 0; i < Math.floor(wep); i++) {
|
|
||||||
pipsSvg['WEP'].push(<Pip className='full' key={i} />);
|
|
||||||
}
|
|
||||||
if (wep > Math.floor(wep)) {
|
|
||||||
pipsSvg['WEP'].push(<Pip className='half' key={'half'} />);
|
|
||||||
}
|
|
||||||
for (let i = Math.floor(wep + 0.5); i < 4; i++) {
|
|
||||||
pipsSvg['WEP'].push(<Pip className='empty' key={i} />);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return pipsSvg;
|
return pipsSvg;
|
||||||
@@ -260,15 +121,10 @@ export default class Pips extends TranslatedComponent {
|
|||||||
* @return {React.Component} contents
|
* @return {React.Component} contents
|
||||||
*/
|
*/
|
||||||
render() {
|
render() {
|
||||||
const { formats, translate, units } = this.context.language;
|
const { ship } = this.props;
|
||||||
const { sys, eng, wep } = this.props;
|
const { translate } = this.context.language;
|
||||||
|
|
||||||
const onSysClicked = this.onClick.bind(this, 'SYS');
|
const pipsSvg = this._renderPips();
|
||||||
const onEngClicked = this.onClick.bind(this, 'ENG');
|
|
||||||
const onWepClicked = this.onClick.bind(this, 'WEP');
|
|
||||||
const onRstClicked = this.onClick.bind(this, 'RST');
|
|
||||||
|
|
||||||
const pipsSvg = this._renderPips(sys, eng, wep);
|
|
||||||
return (
|
return (
|
||||||
<span id='pips'>
|
<span id='pips'>
|
||||||
<table>
|
<table>
|
||||||
@@ -276,20 +132,40 @@ export default class Pips extends TranslatedComponent {
|
|||||||
<tr>
|
<tr>
|
||||||
<td> </td>
|
<td> </td>
|
||||||
<td> </td>
|
<td> </td>
|
||||||
<td className='clickable' onClick={onEngClicked}>{pipsSvg['ENG']}</td>
|
<td className='clickable' onClick={this._incEng}>{pipsSvg.Eng}</td>
|
||||||
<td> </td>
|
<td> </td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td> </td>
|
<td> </td>
|
||||||
<td className='clickable' onClick={onSysClicked}>{pipsSvg['SYS']}</td>
|
<td className='clickable' onClick={this._incSys}>{pipsSvg.Sys}</td>
|
||||||
<td className='clickable' onClick={onEngClicked}>{translate('ENG')}</td>
|
<td className='clickable' onClick={this._incEng}>
|
||||||
<td className='clickable' onClick={onWepClicked}>{pipsSvg['WEP']}</td>
|
{translate('ENG')}
|
||||||
|
</td>
|
||||||
|
<td className='clickable' onClick={this._incWep}>{pipsSvg.Wep}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td> </td>
|
<td> </td>
|
||||||
<td className='clickable' onClick={onSysClicked}>{translate('SYS')}</td>
|
<td className='clickable' onClick={this._incSys}>
|
||||||
<td className='clickable' onClick={onRstClicked}>{translate('RST')}</td>
|
{translate('SYS')}
|
||||||
<td className='clickable' onClick={onWepClicked}>{translate('WEP')}</td>
|
</td>
|
||||||
|
<td className='clickable' onClick={this._reset}>
|
||||||
|
{translate('RST')}
|
||||||
|
</td>
|
||||||
|
<td className='clickable' onClick={this._incWep}>
|
||||||
|
{translate('WEP')}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td> </td>
|
||||||
|
<td className='clickable' onClick={this._change(ship.incSys, true)}>
|
||||||
|
<Pip className='mc' />
|
||||||
|
</td>
|
||||||
|
<td className='clickable' onClick={this._change(ship.incEng, true)}>
|
||||||
|
<Pip className='mc' />
|
||||||
|
</td>
|
||||||
|
<td className='clickable' onClick={this._change(ship.incWep, true)}>
|
||||||
|
<Pip className='mc' />
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|||||||
@@ -4,27 +4,25 @@ import * as d3 from 'd3';
|
|||||||
import cn from 'classnames';
|
import cn from 'classnames';
|
||||||
import TranslatedComponent from './TranslatedComponent';
|
import TranslatedComponent from './TranslatedComponent';
|
||||||
import { wrapCtxMenu } from '../utils/UtilityFunctions';
|
import { wrapCtxMenu } from '../utils/UtilityFunctions';
|
||||||
|
import { Ship } from 'ed-forge';
|
||||||
|
import { POWER_METRICS } from 'ed-forge/lib/ship-stats';
|
||||||
|
import autoBind from 'auto-bind';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Round to avoid floating point precision errors
|
* Get the band-class.
|
||||||
* @param {Boolean} selected Band selected
|
* @param {Boolean} selected Band selected
|
||||||
* @param {number} sum Band power sum
|
* @param {Number} relDraw Relative amount of power drawn by this band and
|
||||||
* @param {number} avail Total available power
|
* all prior
|
||||||
* @return {string} CSS Class name
|
* @return {string} CSS Class name
|
||||||
*/
|
*/
|
||||||
function getClass(selected, sum, avail) {
|
function getClass(selected, relDraw) {
|
||||||
return selected ? 'secondary' : ((Math.round(sum * 100) / 100) >= avail) ? 'warning' : 'primary';
|
if (selected) {
|
||||||
}
|
return 'secondary';
|
||||||
|
} else if (relDraw >= 1) {
|
||||||
/**
|
return 'warning';
|
||||||
* Get the # label for a Priority band
|
} else {
|
||||||
* @param {number} val Priority Band Watt value
|
return 'primary';
|
||||||
* @param {number} index Priority Band index
|
}
|
||||||
* @param {Function} wattScale Watt Scale function
|
|
||||||
* @return {number} label / text
|
|
||||||
*/
|
|
||||||
function bandText(val, index, wattScale) {
|
|
||||||
return (val > 0 && wattScale(val) > 13) ? index + 1 : null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -32,12 +30,10 @@ function bandText(val, index, wattScale) {
|
|||||||
* Renders the SVG to simulate in-game power bands
|
* Renders the SVG to simulate in-game power bands
|
||||||
*/
|
*/
|
||||||
export default class PowerBands extends TranslatedComponent {
|
export default class PowerBands extends TranslatedComponent {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
bands: PropTypes.array.isRequired,
|
ship: PropTypes.instanceOf(Ship).isRequired,
|
||||||
available: PropTypes.number.isRequired,
|
code: PropTypes.string.isRequired,
|
||||||
width: PropTypes.number.isRequired,
|
width: PropTypes.number.isRequired,
|
||||||
code: PropTypes.string,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -47,20 +43,16 @@ export default class PowerBands extends TranslatedComponent {
|
|||||||
*/
|
*/
|
||||||
constructor(props, context) {
|
constructor(props, context) {
|
||||||
super(props);
|
super(props);
|
||||||
|
autoBind(this);
|
||||||
this.wattScale = d3.scaleLinear();
|
this.wattScale = d3.scaleLinear();
|
||||||
this.pctScale = d3.scaleLinear().domain([0, 1]);
|
this.pctScale = d3.scaleLinear().domain([0, 1]);
|
||||||
this.wattAxis = d3.axisTop(this.wattScale).tickSizeOuter(0).tickFormat(context.language.formats.r2);
|
this.wattAxis = d3.axisTop(this.wattScale).tickSizeOuter(0).tickFormat(context.language.formats.r2);
|
||||||
this.pctAxis = d3.axisBottom(this.pctScale).tickSizeOuter(0).tickFormat(context.language.formats.rPct);
|
this.pctAxis = d3.axisBottom(this.pctScale).tickSizeOuter(0).tickFormat(context.language.formats.rPct);
|
||||||
|
|
||||||
this._updateDimensions = this._updateDimensions.bind(this);
|
|
||||||
this._updateScales = this._updateScales.bind(this);
|
|
||||||
this._selectNone = this._selectNone.bind(this);
|
|
||||||
this._hidetip = () => this.context.tooltip();
|
this._hidetip = () => this.context.tooltip();
|
||||||
|
|
||||||
let maxBand = props.bands[props.bands.length - 1];
|
this.profile = props.ship.getMetrics(POWER_METRICS);
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
maxPwr: Math.max(props.available, maxBand.retractedSum, maxBand.deployedSum),
|
|
||||||
ret: {},
|
ret: {},
|
||||||
dep: {}
|
dep: {}
|
||||||
};
|
};
|
||||||
@@ -84,8 +76,6 @@ export default class PowerBands extends TranslatedComponent {
|
|||||||
let mRight = Math.round(140 * scale);
|
let mRight = Math.round(140 * scale);
|
||||||
let innerWidth = props.width - mLeft - mRight;
|
let innerWidth = props.width - mLeft - mRight;
|
||||||
|
|
||||||
this._updateScales(innerWidth, this.state.maxPwr, props.available);
|
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
barHeight,
|
barHeight,
|
||||||
innerHeight,
|
innerHeight,
|
||||||
@@ -141,41 +131,67 @@ export default class PowerBands extends TranslatedComponent {
|
|||||||
this.setState({ dep: Object.assign({}, dep) });
|
this.setState({ dep: Object.assign({}, dep) });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Update scale
|
|
||||||
* @param {number} innerWidth SVG innerwidth
|
|
||||||
* @param {number} maxPwr Maximum power level MJ (deployed or available)
|
|
||||||
* @param {number} available Available power MJ
|
|
||||||
*/
|
|
||||||
_updateScales(innerWidth, maxPwr, available) {
|
|
||||||
this.wattScale.range([0, innerWidth]).domain([0, maxPwr]).clamp(true);
|
|
||||||
this.pctScale.range([0, innerWidth]).domain([0, maxPwr / available]).clamp(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update state based on property and context changes
|
* Update state based on property and context changes
|
||||||
* @param {Object} nextProps Incoming/Next properties
|
* @param {Object} nextProps Incoming/Next properties
|
||||||
* @param {Object} nextContext Incoming/Next context
|
* @param {Object} nextContext Incoming/Next context
|
||||||
*/
|
*/
|
||||||
componentWillReceiveProps(nextProps, nextContext) {
|
componentWillReceiveProps(nextProps, nextContext) {
|
||||||
let { innerWidth, maxPwr } = this.state;
|
|
||||||
let { language, sizeRatio } = this.context;
|
let { language, sizeRatio } = this.context;
|
||||||
let maxBand = nextProps.bands[nextProps.bands.length - 1];
|
|
||||||
let nextMaxPwr = Math.max(nextProps.available, maxBand.retractedSum, maxBand.deployedSum);
|
|
||||||
|
|
||||||
if (language !== nextContext.language) {
|
if (language !== nextContext.language) {
|
||||||
this.wattAxis.tickFormat(nextContext.language.formats.r2);
|
this.wattAxis.tickFormat(nextContext.language.formats.r2);
|
||||||
this.pctAxis.tickFormat(nextContext.language.formats.rPct);
|
this.pctAxis.tickFormat(nextContext.language.formats.rPct);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (maxPwr != nextMaxPwr) { // Update Axes if max power has changed
|
if (nextProps.width != this.props.width || sizeRatio != nextContext.sizeRatio) {
|
||||||
this._updateScales(innerWidth, nextMaxPwr, nextProps.available);
|
|
||||||
this.setState({ maxPwr: nextMaxPwr });
|
|
||||||
} else if (nextProps.width != this.props.width || sizeRatio != nextContext.sizeRatio) {
|
|
||||||
this._updateDimensions(nextProps, nextContext.sizeRatio);
|
this._updateDimensions(nextProps, nextContext.sizeRatio);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assemble bands for relative consumption array.
|
||||||
|
* @param {Number[]} consumed Array of relative-consumption numbers
|
||||||
|
* @param {object} selected Object mapping selected bands to 1
|
||||||
|
* @param {Number} yOffset Offset in y-direction of the bar
|
||||||
|
* @param {Function} onClick onClick callback
|
||||||
|
* @returns {React.Component} Bands
|
||||||
|
*/
|
||||||
|
_consumedToBands(consumed, selected, yOffset, onClick) {
|
||||||
|
const { state, wattScale } = this;
|
||||||
|
const bands = [];
|
||||||
|
let consumesPrev = 0;
|
||||||
|
for (let i = 0; i < consumed.length; i++) {
|
||||||
|
consumesPrev = consumed[i - 1] || consumesPrev;
|
||||||
|
const consumes = consumed[i];
|
||||||
|
|
||||||
|
if (!consumes) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
bands.push(<rect
|
||||||
|
key={'b' + i}
|
||||||
|
width={Math.ceil(Math.max(wattScale(consumes - consumesPrev), 0))}
|
||||||
|
height={state.barHeight}
|
||||||
|
x={wattScale(consumesPrev)}
|
||||||
|
y={yOffset + 1}
|
||||||
|
onClick={onClick.bind(this, i)}
|
||||||
|
className={getClass(selected[i], consumes)}
|
||||||
|
/>);
|
||||||
|
|
||||||
|
bands.push(<text
|
||||||
|
key={'t' + i}
|
||||||
|
dy='0.5em'
|
||||||
|
textAnchor='middle'
|
||||||
|
height={state.barHeight}
|
||||||
|
x={wattScale(consumesPrev) + (wattScale(consumes - consumesPrev) / 2)}
|
||||||
|
y={yOffset + (state.barHeight / 2)}
|
||||||
|
onClick={onClick.bind(this, i)}
|
||||||
|
className='primary-bg'>{i + 1}</text>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return bands;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Render the power bands
|
* Render the power bands
|
||||||
* @return {React.Component} Power bands
|
* @return {React.Component} Power bands
|
||||||
@@ -185,78 +201,27 @@ export default class PowerBands extends TranslatedComponent {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
let { wattScale, pctScale, context, props, state } = this;
|
let { pctScale, context, props, state } = this;
|
||||||
let { translate, formats } = context.language;
|
let { translate, formats } = context.language;
|
||||||
let { f2, pct1 } = formats; // wattFmt, pctFmt
|
let { f2, pct1 } = formats; // wattFmt, pctFmt
|
||||||
let { available, bands } = props;
|
let { ship } = props;
|
||||||
let { innerWidth, ret, dep } = state;
|
let { innerWidth, ret, dep, barHeight } = state;
|
||||||
let pwrWarningClass = cn('threshold', { exceeded: bands[0].retractedSum > available * 0.4 });
|
|
||||||
let deployed = [];
|
let {
|
||||||
let retracted = [];
|
consumed, generated, relativeConsumed, relativeConsumedRetracted
|
||||||
|
} = ship.getMetrics(POWER_METRICS);
|
||||||
|
let maxPwr = Math.max(consumed, generated);
|
||||||
|
let retSum = relativeConsumedRetracted[relativeConsumedRetracted.length - 1];
|
||||||
|
let depSum = relativeConsumed[relativeConsumed.length - 1];
|
||||||
|
|
||||||
|
this.wattScale.range([0, innerWidth]).domain([0, 1]).clamp(true);
|
||||||
|
this.pctScale.range([0, innerWidth]).domain([0, maxPwr / generated]).clamp(true);
|
||||||
|
|
||||||
|
let pwrWarningClass = cn('threshold', { exceeded: retSum > generated * 0.4 });
|
||||||
|
let retracted = this._consumedToBands(relativeConsumedRetracted, ret, 0, this._selectRet);
|
||||||
|
let deployed = this._consumedToBands(relativeConsumed, dep, barHeight, this._selectDep);
|
||||||
let retSelected = Object.keys(ret).length > 0;
|
let retSelected = Object.keys(ret).length > 0;
|
||||||
let depSelected = Object.keys(dep).length > 0;
|
let depSelected = Object.keys(dep).length > 0;
|
||||||
let retSum = 0;
|
|
||||||
let depSum = 0;
|
|
||||||
|
|
||||||
for (let i = 0; i < bands.length; i++) {
|
|
||||||
let b = bands[i];
|
|
||||||
retSum += (!retSelected || ret[i]) ? b.retracted : 0;
|
|
||||||
depSum += (!depSelected || dep[i]) ? b.deployed + b.retracted : 0;
|
|
||||||
|
|
||||||
if (b.retracted > 0) {
|
|
||||||
let retLbl = bandText(b.retracted, i, wattScale);
|
|
||||||
|
|
||||||
retracted.push(<rect
|
|
||||||
key={'rB' + i}
|
|
||||||
width={Math.ceil(Math.max(wattScale(b.retracted), 0))}
|
|
||||||
height={state.barHeight}
|
|
||||||
x={Math.floor(Math.max(wattScale(b.retractedSum) - wattScale(b.retracted), 0))}
|
|
||||||
y={1}
|
|
||||||
onClick={this._selectRet.bind(this, i)}
|
|
||||||
className={getClass(ret[i], b.retractedSum, available)}
|
|
||||||
/>);
|
|
||||||
|
|
||||||
if (retLbl) {
|
|
||||||
retracted.push(<text
|
|
||||||
key={'rT' + i}
|
|
||||||
dy='0.5em'
|
|
||||||
textAnchor='middle'
|
|
||||||
height={state.barHeight}
|
|
||||||
x={wattScale(b.retractedSum) - (wattScale(b.retracted) / 2)}
|
|
||||||
y={state.retY}
|
|
||||||
onClick={this._selectRet.bind(this, i)}
|
|
||||||
className='primary-bg'>{retLbl}</text>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (b.retracted > 0 || b.deployed > 0) {
|
|
||||||
let depLbl = bandText(b.deployed + b.retracted, i, wattScale);
|
|
||||||
|
|
||||||
deployed.push(<rect
|
|
||||||
key={'dB' + i}
|
|
||||||
width={Math.ceil(Math.max(wattScale(b.deployed + b.retracted), 0))}
|
|
||||||
height={state.barHeight}
|
|
||||||
x={Math.floor(Math.max(wattScale(b.deployedSum) - wattScale(b.retracted) - wattScale(b.deployed), 0))}
|
|
||||||
y={state.barHeight + 1}
|
|
||||||
onClick={this._selectDep.bind(this, i)}
|
|
||||||
className={getClass(dep[i], b.deployedSum, available)}
|
|
||||||
/>);
|
|
||||||
|
|
||||||
if (depLbl) {
|
|
||||||
deployed.push(<text
|
|
||||||
key={'dT' + i}
|
|
||||||
dy='0.5em'
|
|
||||||
textAnchor='middle'
|
|
||||||
height={state.barHeight}
|
|
||||||
x={wattScale(b.deployedSum) - ((wattScale(b.retracted) + wattScale(b.deployed)) / 2)}
|
|
||||||
y={state.depY}
|
|
||||||
onClick={this._selectDep.bind(this, i)}
|
|
||||||
className='primary-bg'>{depLbl}</text>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<svg style={{ marginTop: '1em', width: '100%', height: state.height }} onContextMenu={wrapCtxMenu(this._selectNone)}>
|
<svg style={{ marginTop: '1em', width: '100%', height: state.height }} onContextMenu={wrapCtxMenu(this._selectNone)}>
|
||||||
@@ -272,8 +237,8 @@ export default class PowerBands extends TranslatedComponent {
|
|||||||
<line x1={pctScale(0.4)} x2={pctScale(0.4)} y1='0' y2={state.innerHeight} className={pwrWarningClass} />
|
<line x1={pctScale(0.4)} x2={pctScale(0.4)} y1='0' y2={state.innerHeight} className={pwrWarningClass} />
|
||||||
<text dy='0.5em' x='-3' y={state.retY} className='primary upp' textAnchor='end' onMouseOver={this.context.termtip.bind(null, 'retracted')} onMouseLeave={this._hidetip}>{translate('ret')}</text>
|
<text dy='0.5em' x='-3' y={state.retY} className='primary upp' textAnchor='end' onMouseOver={this.context.termtip.bind(null, 'retracted')} onMouseLeave={this._hidetip}>{translate('ret')}</text>
|
||||||
<text dy='0.5em' x='-3' y={state.depY} className='primary upp' textAnchor='end' onMouseOver={this.context.termtip.bind(null, 'deployed', { orientation: 's', cap: 1 })} onMouseLeave={this._hidetip}>{translate('dep')}</text>
|
<text dy='0.5em' x='-3' y={state.depY} className='primary upp' textAnchor='end' onMouseOver={this.context.termtip.bind(null, 'deployed', { orientation: 's', cap: 1 })} onMouseLeave={this._hidetip}>{translate('dep')}</text>
|
||||||
<text dy='0.5em' x={innerWidth + 5} y={state.retY} className={getClass(retSelected, retSum, available)}>{f2(Math.max(0, retSum))} ({pct1(Math.max(0, retSum / available))})</text>
|
<text dy='0.5em' x={innerWidth + 5} y={state.retY} className={getClass(retSelected, retSum, generated)}>{f2(Math.max(0, retSum * generated))} ({pct1(Math.max(0, retSum))})</text>
|
||||||
<text dy='0.5em' x={innerWidth + 5} y={state.depY} className={getClass(depSelected, depSum, available)}>{f2(Math.max(0, depSum))} ({pct1(Math.max(0, depSum / available))})</text>
|
<text dy='0.5em' x={innerWidth + 5} y={state.depY} className={getClass(depSelected, depSum, generated)}>{f2(Math.max(0, depSum * generated))} ({pct1(Math.max(0, depSum))})</text>
|
||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -3,24 +3,49 @@ import PropTypes from 'prop-types';
|
|||||||
import cn from 'classnames';
|
import cn from 'classnames';
|
||||||
import TranslatedComponent from './TranslatedComponent';
|
import TranslatedComponent from './TranslatedComponent';
|
||||||
import PowerBands from './PowerBands';
|
import PowerBands from './PowerBands';
|
||||||
import { slotName, slotComparator } from '../utils/SlotFunctions';
|
|
||||||
import { Power, NoPower } from './SvgIcons';
|
import { Power, NoPower } from './SvgIcons';
|
||||||
|
import autoBind from 'auto-bind';
|
||||||
|
import { Ship, Module } from 'ed-forge';
|
||||||
|
|
||||||
const POWER = [
|
/**
|
||||||
null,
|
* Makes a comparison based on the order `false < undefined < true` (fut) and
|
||||||
null,
|
* maps it to `[-1, 0, 1]`.
|
||||||
<NoPower className='icon warning' />,
|
* @param {boolean} a Bool or undefined
|
||||||
<Power className='secondary-disabled' />
|
* @param {boolean} b Bool or undefined
|
||||||
];
|
* @returns {number} Comparison
|
||||||
|
*/
|
||||||
|
function futComp(a, b) {
|
||||||
|
switch (a) {
|
||||||
|
case false: return (b === false ? 0 : 1);
|
||||||
|
// The next else-expression maps false to -1 and true to 1
|
||||||
|
case undefined: return (b === undefined ? 0 : 2 * Number(b) - 1);
|
||||||
|
case true: return (b === true ? 0 : -1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the enabled-icon.
|
||||||
|
* @param {boolean} enabled Is the module enabled?
|
||||||
|
* @returns {React.Component} Enabled icon.
|
||||||
|
*/
|
||||||
|
function getPowerIcon(enabled) {
|
||||||
|
if (enabled === undefined) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (enabled) {
|
||||||
|
return <Power className='secondary-disabled' />;
|
||||||
|
} else {
|
||||||
|
return <NoPower className='icon warning' />;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Power Management Section
|
* Power Management Section
|
||||||
*/
|
*/
|
||||||
export default class PowerManagement extends TranslatedComponent {
|
export default class PowerManagement extends TranslatedComponent {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
ship: PropTypes.object.isRequired,
|
ship: PropTypes.instanceOf(Ship).isRequired,
|
||||||
code: PropTypes.string.isRequired,
|
code: PropTypes.string.isRequired,
|
||||||
onChange: PropTypes.func.isRequired
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -29,19 +54,17 @@ export default class PowerManagement extends TranslatedComponent {
|
|||||||
*/
|
*/
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this._renderPowerRows = this._renderPowerRows.bind(this);
|
autoBind(this);
|
||||||
this._updateWidth = this._updateWidth.bind(this);
|
|
||||||
this._sort = this._sort.bind(this);
|
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
predicate: 'pwr',
|
predicate: 'pwr',
|
||||||
desc: false,
|
desc: true,
|
||||||
width: 0
|
width: 0
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the sort order and sort
|
* Set the sort order
|
||||||
* @param {string} predicate Sort predicate
|
* @param {string} predicate Sort predicate
|
||||||
*/
|
*/
|
||||||
_sortOrder(predicate) {
|
_sortOrder(predicate) {
|
||||||
@@ -53,50 +76,51 @@ export default class PowerManagement extends TranslatedComponent {
|
|||||||
desc = true;
|
desc = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
this._sort(this.props.ship, predicate, desc);
|
|
||||||
this.setState({ predicate, desc });
|
this.setState({ predicate, desc });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sorts the power list
|
* Sorts the power list
|
||||||
* @param {Ship} ship Ship instance
|
* @param {Module[]} modules Modules to sort
|
||||||
* @param {string} predicate Sort predicate
|
* @returns {Module[]} Sorted modules
|
||||||
* @param {Boolean} desc Sort order descending
|
|
||||||
*/
|
*/
|
||||||
_sort(ship, predicate, desc) {
|
_sortAndFilter(modules) {
|
||||||
let powerList = ship.powerList;
|
modules = modules.filter((m) => m.get('powerdraw') >= 0);
|
||||||
let comp = slotComparator.bind(null, this.context.language.translate);
|
let { translate } = this.context.language;
|
||||||
|
const { predicate, desc } = this.state;
|
||||||
|
let comp;
|
||||||
switch (predicate) {
|
switch (predicate) {
|
||||||
case 'n': comp = comp(null, desc); break;
|
case 'n': comp = (a, b) => translate(a.readMeta('type')).localeCompare(
|
||||||
case 't': comp = comp((a, b) => a.type.localeCompare(b.type), desc); break;
|
translate(b.readMeta('type'))
|
||||||
case 'pri': comp = comp((a, b) => a.priority - b.priority, desc); break;
|
); break;
|
||||||
case 'pwr': comp = comp((a, b) => a.m.getPowerUsage() - b.m.getPowerUsage(), desc); break;
|
// case 't': comp = comp((a, b) => a.type.localeCompare(b.type), desc); break;
|
||||||
case 'r': comp = comp((a, b) => ship.getSlotStatus(a) - ship.getSlotStatus(b), desc); break;
|
case 'pri': comp = (a, b) => a.getPowerPriority() - b.getPowerPriority(); break;
|
||||||
case 'd': comp = comp((a, b) => ship.getSlotStatus(a, true) - ship.getSlotStatus(b, true), desc); break;
|
case 'pwr': comp = (a, b) => a.get('powerdraw') - b.get('powerdraw'); break;
|
||||||
|
case 'r': comp = (a, b) => futComp(a.isPowered().retracted, b.isPowered().retracted); break;
|
||||||
|
case 'd': comp = (a, b) => futComp(a.isPowered().deployed, b.isPowered().deployed); break;
|
||||||
}
|
}
|
||||||
|
modules.sort(comp);
|
||||||
powerList.sort(comp);
|
if (desc) {
|
||||||
|
modules.reverse();
|
||||||
|
}
|
||||||
|
return modules;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update slot priority
|
* Creates a callback that changes the power priority for the given module
|
||||||
* @param {Object} slot Slot model
|
* based on the given delta.
|
||||||
* @param {number} inc increment / decrement
|
* @param {Module} m Module to set the priority for
|
||||||
|
* @param {Number} delta Delta to set
|
||||||
|
* @returns {Function} Callback
|
||||||
*/
|
*/
|
||||||
_priority(slot, inc) {
|
_prioCb(m, delta) {
|
||||||
if (this.props.ship.setSlotPriority(slot, slot.priority + inc)) {
|
return () => {
|
||||||
this.props.onChange();
|
const prio = m.getPowerPriority();
|
||||||
}
|
const newPrio = Math.max(0, prio + delta);
|
||||||
}
|
if (0 <= newPrio) {
|
||||||
|
m.setPowerPriority(newPrio);
|
||||||
/**
|
}
|
||||||
* Toggle slot active/inactive
|
};
|
||||||
* @param {Object} slot Slot model
|
|
||||||
*/
|
|
||||||
_toggleEnabled(slot) {
|
|
||||||
this.props.ship.setSlotEnabled(slot, !slot.enabled);
|
|
||||||
this.props.onChange();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -110,36 +134,35 @@ export default class PowerManagement extends TranslatedComponent {
|
|||||||
_renderPowerRows(ship, translate, pwr, pct) {
|
_renderPowerRows(ship, translate, pwr, pct) {
|
||||||
let powerRows = [];
|
let powerRows = [];
|
||||||
|
|
||||||
for (let i = 0, l = ship.powerList.length; i < l; i++) {
|
let modules = this._sortAndFilter(ship.getModules());
|
||||||
let slot = ship.powerList[i];
|
for (let m of modules) {
|
||||||
|
let retractedElem = null, deployedElem = null;
|
||||||
if (slot.m && slot.m.getPowerUsage() > 0) {
|
const flipEnabled = () => m.setEnabled();
|
||||||
let m = slot.m;
|
if (m.isEnabled()) {
|
||||||
let toggleEnabled = this._toggleEnabled.bind(this, slot);
|
let powered = m.isPowered();
|
||||||
let retractedElem = null, deployedElem = null;
|
retractedElem = <td className='ptr upp' onClick={flipEnabled}>{getPowerIcon(powered.retracted)}</td>;
|
||||||
|
deployedElem = <td className='ptr upp' onClick={flipEnabled}>{getPowerIcon(powered.deployed)}</td>;
|
||||||
if (slot.enabled) {
|
} else {
|
||||||
retractedElem = <td className='ptr upp' onClick={toggleEnabled}>{POWER[ship.getSlotStatus(slot, false)]}</td>;
|
retractedElem = <td className='ptr disabled upp' colSpan='2' onClick={flipEnabled}>{translate('disabled')}</td>;
|
||||||
deployedElem = <td className='ptr upp' onClick={toggleEnabled}>{POWER[ship.getSlotStatus(slot, true)]}</td>;
|
|
||||||
} else {
|
|
||||||
retractedElem = <td className='ptr disabled upp' colSpan='2' onClick={toggleEnabled}>{translate('disabled')}</td>;
|
|
||||||
}
|
|
||||||
|
|
||||||
powerRows.push(<tr key={i} className={cn('highlight', { disabled: !slot.enabled })}>
|
|
||||||
<td className='ptr' style={{ width: '1em' }} onClick={toggleEnabled}>{m.class + m.rating}</td>
|
|
||||||
<td className='ptr le shorten cap' onClick={toggleEnabled}>{slotName(translate, slot)}</td>
|
|
||||||
<td className='ptr' onClick={toggleEnabled}><u>{translate(slot.type)}</u></td>
|
|
||||||
<td>
|
|
||||||
<span className='flip ptr btn' onClick={this._priority.bind(this, slot, -1)}>►</span>
|
|
||||||
{' ' + (slot.priority + 1) + ' '}
|
|
||||||
<span className='ptr btn' onClick={this._priority.bind(this, slot, 1)}>►</span>
|
|
||||||
</td>
|
|
||||||
<td className='ri ptr' style={{ width: '3.25em' }} onClick={toggleEnabled}>{pwr(m.getPowerUsage())}</td>
|
|
||||||
<td className='ri ptr' style={{ width: '3em' }} onClick={toggleEnabled}><u>{pct(m.getPowerUsage() / ship.powerAvailable)}</u></td>
|
|
||||||
{retractedElem}
|
|
||||||
{deployedElem}
|
|
||||||
</tr>);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const slot = m.getSlot();
|
||||||
|
powerRows.push(<tr key={slot} className={cn('highlight', { disabled: !m.isEnabled() })}>
|
||||||
|
<td className='ptr' style={{ width: '1em' }} onClick={flipEnabled}>{String(m.getClass()) + m.getRating()}</td>
|
||||||
|
<td className='ptr le shorten cap' onClick={flipEnabled}>{translate(m.readMeta('type'))}</td>
|
||||||
|
{/* <td className='ptr' onClick={flipEnabled}><u>{translate(slot.type)}</u></td> */}
|
||||||
|
<td>
|
||||||
|
<span className='flip ptr btn' onClick={this._prioCb(m, -1)}>►</span>
|
||||||
|
{' ' + (m.getPowerPriority() + 1) + ' '}
|
||||||
|
<span className='ptr btn' onClick={this._prioCb(m, 1)}>►</span>
|
||||||
|
</td>
|
||||||
|
<td className='ri ptr' style={{ width: '3.25em' }} onClick={flipEnabled}>{pwr(m.get('powerdraw'))}</td>
|
||||||
|
<td className='ri ptr' style={{ width: '3em' }} onClick={flipEnabled}>
|
||||||
|
<u>{pct(m.get('powerdraw') / ship.getPowerPlant().get('powercapacity'))}</u>
|
||||||
|
</td>
|
||||||
|
{retractedElem}
|
||||||
|
{deployedElem}
|
||||||
|
</tr>);
|
||||||
}
|
}
|
||||||
return powerRows;
|
return powerRows;
|
||||||
}
|
}
|
||||||
@@ -155,7 +178,6 @@ export default class PowerManagement extends TranslatedComponent {
|
|||||||
* Add listeners when about to mount and sort power list
|
* Add listeners when about to mount and sort power list
|
||||||
*/
|
*/
|
||||||
componentWillMount() {
|
componentWillMount() {
|
||||||
this._sort(this.props.ship, this.state.predicate, this.state.desc);
|
|
||||||
this.resizeListener = this.context.onWindowResize(this._updateWidth);
|
this.resizeListener = this.context.onWindowResize(this._updateWidth);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -166,17 +188,6 @@ export default class PowerManagement extends TranslatedComponent {
|
|||||||
this._updateWidth();
|
this._updateWidth();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Sort power list if the ship instance has changed
|
|
||||||
* @param {Object} nextProps Incoming/Next properties
|
|
||||||
* @param {Object} nextState Incoming/Next state
|
|
||||||
*/
|
|
||||||
componentWillUpdate(nextProps, nextState) {
|
|
||||||
if (this.props.ship != nextProps.ship) {
|
|
||||||
this._sort(nextProps.ship, nextState.predicate, nextState.desc);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove listeners on unmount
|
* Remove listeners on unmount
|
||||||
*/
|
*/
|
||||||
@@ -191,39 +202,38 @@ export default class PowerManagement extends TranslatedComponent {
|
|||||||
render() {
|
render() {
|
||||||
let { ship, code } = this.props;
|
let { ship, code } = this.props;
|
||||||
let { translate, formats } = this.context.language;
|
let { translate, formats } = this.context.language;
|
||||||
let pwr = formats.f2;
|
let pp = ship.getPowerPlant();
|
||||||
let pp = ship.standard[0].m;
|
|
||||||
let sortOrder = this._sortOrder;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div ref={node => this.node = node} className='group half' id='componentPriority'>
|
<div ref={node => this.node = node} className='group half' id='componentPriority'>
|
||||||
<table style={{ width: '100%' }}>
|
<table style={{ width: '100%' }}>
|
||||||
<thead>
|
<thead>
|
||||||
<tr className='main'>
|
<tr className='main'>
|
||||||
<th colSpan='2' className='sortable le' onClick={sortOrder.bind(this, 'n')} >{translate('module')}</th>
|
<th colSpan='2' className='sortable le' onClick={() => this._sortOrder('n')} >{translate('module')}</th>
|
||||||
<th style={{ width: '3em' }} className='sortable' onClick={sortOrder.bind(this, 't')} >{translate('type')}</th>
|
{/* <th style={{ width: '3em' }} className='sortable' onClick={() => this._sortOrder('t')} >{translate('type')}</th> */}
|
||||||
<th style={{ width: '4em' }} className='sortable' onClick={sortOrder.bind(this, 'pri')} >{translate('pri')}</th>
|
<th style={{ width: '4em' }} className='sortable' onClick={() => this._sortOrder('pri')} >{translate('pri')}</th>
|
||||||
<th colSpan='2' className='sortable' onClick={sortOrder.bind(this, 'pwr')} >{translate('PWR')}</th>
|
<th colSpan='2' className='sortable' onClick={() => this._sortOrder('pwr')} >{translate('PWR')}</th>
|
||||||
<th style={{ width: '3em' }} className='sortable' onClick={sortOrder.bind(this, 'r')} >{translate('ret')}</th>
|
<th style={{ width: '3em' }} className='sortable' onClick={() => this._sortOrder('r')} >{translate('ret')}</th>
|
||||||
<th style={{ width: '3em' }} className='sortable' onClick={sortOrder.bind(this, 'd')} >{translate('dep')}</th>
|
<th style={{ width: '3em' }} className='sortable' onClick={() => this._sortOrder('d')} >{translate('dep')}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<td>{pp.class + pp.rating}</td>
|
<td>{String(pp.getClass()) + pp.getRating()}</td>
|
||||||
<td className='le shorten cap' >{translate('pp')}</td>
|
<td className='le shorten cap' >{translate('pp')}</td>
|
||||||
<td><u >{translate('SYS')}</u></td>
|
|
||||||
<td>1</td>
|
<td>1</td>
|
||||||
<td className='ri'>{pwr(pp.getPowerGeneration())}</td>
|
<td className='ri'>{formats.f2(pp.get('powercapacity'))}</td>
|
||||||
<td className='ri'><u>100%</u></td>
|
<td className='ri'><u>100%</u></td>
|
||||||
<td></td>
|
<td></td>
|
||||||
<td></td>
|
<td></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr><td style={{ lineHeight:0 }} colSpan='8'><hr style={{ margin: '0 0 3px', background: '#ff8c0d', border: 0, height: 1 }} /></td></tr>
|
<tr><td style={{ lineHeight:0 }} colSpan='8'>
|
||||||
{this._renderPowerRows(ship, translate, pwr, formats.pct1)}
|
<hr style={{ margin: '0 0 3px', background: '#ff8c0d', border: 0, height: 1 }} />
|
||||||
|
</td></tr>
|
||||||
|
{this._renderPowerRows(ship, translate, formats.f2, formats.pct1)}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
<PowerBands width={this.state.width} code={code} available={pp.getPowerGeneration()} bands={ship.priorityBands} />
|
<PowerBands width={this.state.width} ship={ship} code={code} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import TranslatedComponent from './TranslatedComponent';
|
import TranslatedComponent from './TranslatedComponent';
|
||||||
import Ship from '../shipyard/Ship';
|
|
||||||
import { Ships } from 'coriolis-data/dist';
|
|
||||||
import { Rocket } from './SvgIcons';
|
import { Rocket } from './SvgIcons';
|
||||||
import Persist from '../stores/Persist';
|
import Persist from '../stores/Persist';
|
||||||
import cn from 'classnames';
|
import cn from 'classnames';
|
||||||
|
import { Factory, Ship } from 'ed-forge';
|
||||||
|
import autoBind from 'auto-bind';
|
||||||
|
import { isEqual } from 'lodash';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ship picker
|
* Ship picker
|
||||||
@@ -14,40 +15,49 @@ import cn from 'classnames';
|
|||||||
export default class ShipPicker extends TranslatedComponent {
|
export default class ShipPicker extends TranslatedComponent {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
onChange: PropTypes.func.isRequired,
|
onChange: PropTypes.func.isRequired,
|
||||||
ship: PropTypes.string.isRequired,
|
ship: PropTypes.instanceOf(Ship).isRequired,
|
||||||
build: PropTypes.string
|
|
||||||
};
|
};
|
||||||
|
|
||||||
static defaultProps = {
|
|
||||||
ship: 'eagle'
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* constructor
|
* constructor
|
||||||
* @param {object} props Properties react
|
* @param {object} props Properties react
|
||||||
* @param {object} context react context
|
* @param {object} context react context
|
||||||
*/
|
*/
|
||||||
constructor(props, context) { // eslint-disable-line
|
constructor(props, context) {
|
||||||
super(props);
|
super(props);
|
||||||
|
autoBind(this);
|
||||||
this.shipOrder = Object.keys(Ships).sort();
|
this.state = {
|
||||||
this._toggleMenu = this._toggleMenu.bind(this);
|
menuOpen: false,
|
||||||
this._closeMenu = this._closeMenu.bind(this);
|
opponent: {
|
||||||
|
self: true,
|
||||||
this.state = { menuOpen: false };
|
type: props.ship.getShipType(),
|
||||||
|
stock: false,
|
||||||
|
id: undefined,
|
||||||
|
},
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update ship
|
* Update ship
|
||||||
* @param {object} ship the ship
|
* @param {boolean} self True to compare with ship itself
|
||||||
* @param {string} build the build, if present
|
* @param {object} type The ship type
|
||||||
|
* @param {boolean} stock True to compare with a stock version of given type
|
||||||
|
* @param {string} id The build's stored ID
|
||||||
*/
|
*/
|
||||||
_shipChange(ship, build) {
|
_shipChange(self, type, stock = false, id = null) {
|
||||||
this._closeMenu();
|
const opponent = { self, type, stock, id };
|
||||||
|
if (isEqual(opponent, this.state.opponent)) {
|
||||||
// Ensure that the ship has changed
|
this.setState({ menuOpen: false });
|
||||||
if (ship !== this.props.ship || build !== this.props.build) {
|
} else {
|
||||||
this.props.onChange(ship, build);
|
const { onChange } = this.props;
|
||||||
|
if (self) {
|
||||||
|
onChange(this.props.ship);
|
||||||
|
} else if (stock) {
|
||||||
|
onChange(Factory.newShip(type));
|
||||||
|
} else {
|
||||||
|
onChange(new Ship(Persist.getBuild(type, id)));
|
||||||
|
}
|
||||||
|
this.setState({ menuOpen: false, opponent });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,26 +66,41 @@ export default class ShipPicker extends TranslatedComponent {
|
|||||||
* @returns {object} the picker menu
|
* @returns {object} the picker menu
|
||||||
*/
|
*/
|
||||||
_renderPickerMenu() {
|
_renderPickerMenu() {
|
||||||
const { ship, build } = this.props;
|
const { menuOpen } = this.state;
|
||||||
const _shipChange = this._shipChange;
|
if (!menuOpen) {
|
||||||
const builds = Persist.getBuilds();
|
return null;
|
||||||
const buildList = [];
|
|
||||||
for (let shipId of this.shipOrder) {
|
|
||||||
const shipBuilds = [];
|
|
||||||
// Add stock build
|
|
||||||
const stockSelected = (ship == shipId && !build);
|
|
||||||
shipBuilds.push(<li key={shipId} className={ cn({ 'selected': stockSelected })} onClick={_shipChange.bind(this, shipId, null)}>Stock</li>);
|
|
||||||
if (builds[shipId]) {
|
|
||||||
let buildNameOrder = Object.keys(builds[shipId]).sort();
|
|
||||||
for (let buildName of buildNameOrder) {
|
|
||||||
const buildSelected = ship === shipId && build === buildName;
|
|
||||||
shipBuilds.push(<li key={shipId + '-' + buildName} className={ cn({ 'selected': buildSelected })} onClick={_shipChange.bind(this, shipId, buildName)}>{buildName}</li>);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
buildList.push(<ul key={shipId} className='block'>{Ships[shipId].properties.name}{shipBuilds}</ul>);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return buildList;
|
const { translate } = this.context.language;
|
||||||
|
const { self, type, stock, id } = this.state.opponent;
|
||||||
|
return <div className='menu-list' onClick={(e) => e.stopPropagation()}>
|
||||||
|
<div className='quad'>
|
||||||
|
{Factory.getAllShipTypes().sort().map((shipType) =>
|
||||||
|
<ul key={shipType} className='block'>
|
||||||
|
{translate(shipType)}
|
||||||
|
{/* Add stock build */}
|
||||||
|
<li key={shipType}
|
||||||
|
onClick={this._shipChange.bind(this, false, shipType, true)}
|
||||||
|
className={cn({ selected: stock && type === shipType })}>
|
||||||
|
{translate('stock')}
|
||||||
|
</li>
|
||||||
|
{Persist.getBuildsNamesFor(shipType).sort().map((storedId) =>
|
||||||
|
<li key={`${shipType}-${storedId}`}
|
||||||
|
onClick={this._shipChange.bind(this, false, shipType, false, storedId)}
|
||||||
|
className={ cn({ selected: type === shipType && id === storedId })}>
|
||||||
|
{storedId}
|
||||||
|
</li>)}
|
||||||
|
{/* Add ship itself */}
|
||||||
|
{(this.props.ship.getShipType() === shipType ?
|
||||||
|
<li key='self'
|
||||||
|
onClick={this._shipChange.bind(this, true, shipType)}
|
||||||
|
className={cn({ selected: self })}>
|
||||||
|
{translate('THIS_SHIP')}
|
||||||
|
</li> :
|
||||||
|
null)}
|
||||||
|
</ul>)}
|
||||||
|
</div>
|
||||||
|
</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -86,40 +111,35 @@ export default class ShipPicker extends TranslatedComponent {
|
|||||||
this.setState({ menuOpen: !menuOpen });
|
this.setState({ menuOpen: !menuOpen });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Close the menu
|
|
||||||
*/
|
|
||||||
_closeMenu() {
|
|
||||||
const { menuOpen } = this.state;
|
|
||||||
if (menuOpen) {
|
|
||||||
this._toggleMenu();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Render picker
|
* Render picker
|
||||||
* @return {React.Component} contents
|
* @return {React.Component} contents
|
||||||
*/
|
*/
|
||||||
render() {
|
render() {
|
||||||
const { language, onWindowResize, sizeRatio, tooltip, termtip } = this.context;
|
const { translate } = this.context.language;
|
||||||
const { formats, translate, units } = language;
|
const { ship } = this.props;
|
||||||
const { ship, build } = this.props;
|
|
||||||
const { menuOpen } = this.state;
|
const { menuOpen } = this.state;
|
||||||
|
const { self, type, stock, id } = this.state.opponent;
|
||||||
|
|
||||||
|
let label;
|
||||||
|
if (self) {
|
||||||
|
label = translate('THIS_SHIP');
|
||||||
|
} else if (stock) {
|
||||||
|
label = translate('stock');
|
||||||
|
} else {
|
||||||
|
label = id;
|
||||||
|
}
|
||||||
|
|
||||||
const shipString = ship + ': ' + (build ? build : translate('stock'));
|
|
||||||
return (
|
return (
|
||||||
<div className='shippicker' onClick={ (e) => e.stopPropagation() }>
|
<div className='shippicker' onClick={ (e) => e.stopPropagation() }>
|
||||||
<div className='menu'>
|
<div className='menu'>
|
||||||
<div className={cn('menu-header', { selected: menuOpen })} onClick={this._toggleMenu}>
|
<div className={cn('menu-header', { selected: menuOpen })} onClick={this._toggleMenu}>
|
||||||
<span><Rocket className='warning' /></span>
|
<span><Rocket className='warning' /></span>
|
||||||
<span className='menu-item-label'>{shipString}</span>
|
<span className='menu-item-label'>
|
||||||
|
{`${translate(type)}: ${label}`}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
{ menuOpen ?
|
{this._renderPickerMenu()}
|
||||||
<div className='menu-list' onClick={ (e) => e.stopPropagation() }>
|
|
||||||
<div className='quad'>
|
|
||||||
{this._renderPickerMenu()}
|
|
||||||
</div>
|
|
||||||
</div> : null }
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,21 +1,24 @@
|
|||||||
|
import autoBind from 'auto-bind';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import TranslatedComponent from './TranslatedComponent';
|
import TranslatedComponent from './TranslatedComponent';
|
||||||
import cn from 'classnames';
|
import cn from 'classnames';
|
||||||
import { Warning } from './SvgIcons';
|
import { Warning } from './SvgIcons';
|
||||||
import * as Calc from '../shipyard/Calculations';
|
|
||||||
|
import { ShipProps } from 'ed-forge';
|
||||||
|
const {
|
||||||
|
SPEED, BOOST_SPEED, DAMAGE_METRICS, JUMP_METRICS, SHIELD_METRICS,
|
||||||
|
ARMOUR_METRICS, CARGO_CAPACITY, FUEL_CAPACITY, UNLADEN_MASS, MAXIMUM_MASS,
|
||||||
|
MODULE_PROTECTION_METRICS, PASSENGER_CAPACITY
|
||||||
|
} = ShipProps;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ship Summary Table / Stats
|
* Ship Summary Table / Stats
|
||||||
*/
|
*/
|
||||||
export default class ShipSummaryTable extends TranslatedComponent {
|
export default class ShipSummaryTable extends TranslatedComponent {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
ship: PropTypes.object.isRequired,
|
ship: PropTypes.object.isRequired,
|
||||||
cargo: PropTypes.number.isRequired,
|
code: PropTypes.string.isRequired,
|
||||||
fuel: PropTypes.number.isRequired,
|
|
||||||
marker: PropTypes.string.isRequired,
|
|
||||||
pips: PropTypes.object.isRequired
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -24,7 +27,7 @@ export default class ShipSummaryTable extends TranslatedComponent {
|
|||||||
*/
|
*/
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.didContextChange = this.didContextChange.bind(this);
|
autoBind(this);
|
||||||
this.state = {
|
this.state = {
|
||||||
shieldColour: 'blue'
|
shieldColour: 'blue'
|
||||||
};
|
};
|
||||||
@@ -35,42 +38,54 @@ export default class ShipSummaryTable extends TranslatedComponent {
|
|||||||
* @return {React.Component} Summary table
|
* @return {React.Component} Summary table
|
||||||
*/
|
*/
|
||||||
render() {
|
render() {
|
||||||
const { ship, cargo, fuel, pips } = this.props;
|
const { ship } = 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, f1, f2 } = formats;
|
||||||
let hide = tooltip.bind(null, null);
|
let hide = tooltip.bind(null, null);
|
||||||
const shieldGenerator = ship.findInternalByGroup('sg') || ship.findInternalByGroup('psg');
|
|
||||||
const sgClassNames = cn({ warning: shieldGenerator && !ship.shield, muted: !shieldGenerator });
|
const speed = ship.get(SPEED);
|
||||||
const sgTooltip = shieldGenerator ? 'TT_SUMMARY_SHIELDS' : 'TT_SUMMARY_SHIELDS_NONFUNCTIONAL';
|
const shipBoost = ship.get(BOOST_SPEED);
|
||||||
const timeToDrain = Calc.timeToDrainWep(ship, 4);
|
const canThrust = 0 < speed;
|
||||||
const canThrust = ship.canThrust(cargo, ship.fuelCapacity);
|
const canBoost = canThrust && !isNaN(shipBoost);
|
||||||
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 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 sgMetrics = ship.get(SHIELD_METRICS);
|
||||||
const armourMetrics = Calc.armourMetrics(ship);
|
const armourMetrics = ship.get(ARMOUR_METRICS);
|
||||||
|
const damageMetrics = ship.get(DAMAGE_METRICS);
|
||||||
|
const moduleProtectionMetrics = ship.get(MODULE_PROTECTION_METRICS);
|
||||||
|
const timeToDrain = damageMetrics.timeToDrain[8];
|
||||||
|
|
||||||
|
const shieldGenerator = ship.getShieldGenerator();
|
||||||
|
const sgClassNames = cn({
|
||||||
|
warning: shieldGenerator && !shieldGenerator.isEnabled(),
|
||||||
|
muted: !shieldGenerator,
|
||||||
|
});
|
||||||
|
const sgTooltip = shieldGenerator ? 'TT_SUMMARY_SHIELDS' : 'TT_SUMMARY_SHIELDS_NONFUNCTIONAL';
|
||||||
|
const sgType = shieldGenerator ? shieldGenerator.readMeta('type') : undefined;
|
||||||
let shieldColour = 'blue';
|
let shieldColour = 'blue';
|
||||||
if (shieldGenerator && shieldGenerator.m.grp === 'psg') {
|
switch (sgType) {
|
||||||
shieldColour = 'green';
|
case 'biweaveshieldgen': shieldColour = 'purple'; break;
|
||||||
} else if (shieldGenerator && shieldGenerator.m.grp === 'bsg') {
|
case 'prismaticshieldgen': shieldColour = 'green'; break;
|
||||||
shieldColour = 'purple';
|
|
||||||
}
|
}
|
||||||
this.state = {
|
this.state = { shieldColour };
|
||||||
shieldColour
|
|
||||||
};
|
const jumpRangeMetrics = ship.getMetrics(JUMP_METRICS);
|
||||||
|
// TODO:
|
||||||
|
const canJump = true;
|
||||||
|
|
||||||
return <div id='summary'>
|
return <div id='summary'>
|
||||||
<div style={{display: "table", width: "100%"}}>
|
<div style={{ display: 'table', width: '100%' }}>
|
||||||
<div style={{display: "table-row"}}>
|
<div style={{ display: 'table-row' }}>
|
||||||
<table className={'summaryTable'}>
|
<table className={'summaryTable'}>
|
||||||
<thead>
|
<thead>
|
||||||
<tr className='main'>
|
<tr className='main'>
|
||||||
<th rowSpan={2} className={ cn({ 'bg-warning-disabled': !canThrust }) }>{translate('speed')}</th>
|
<th rowSpan={2} className={ cn({ 'bg-warning-disabled': speed == 0 }) }>{translate('speed')}</th>
|
||||||
<th rowSpan={2} className={ cn({ 'bg-warning-disabled': !canBoost }) }>{translate('boost')}</th>
|
<th rowSpan={2} className={ cn({ 'bg-warning-disabled': !canBoost }) }>{translate('boost')}</th>
|
||||||
<th colSpan={5}>{translate('jump range')}</th>
|
<th colSpan={5} className={ cn({ 'bg-warning-disabled': jumpRangeMetrics.jumpRange == 0 }) }>{translate('jump range')}</th>
|
||||||
<th rowSpan={2}>{translate('shield')}</th>
|
<th rowSpan={2}>{translate('shield')}</th>
|
||||||
<th rowSpan={2}>{translate('integrity')}</th>
|
<th rowSpan={2}>{translate('integrity')}</th>
|
||||||
<th rowSpan={2}>{translate('DPS')}</th>
|
<th rowSpan={2}>{translate('DPS')}</th>
|
||||||
@@ -78,16 +93,17 @@ export default class ShipSummaryTable extends TranslatedComponent {
|
|||||||
<th rowSpan={2}>{translate('TTD')}</th>
|
<th rowSpan={2}>{translate('TTD')}</th>
|
||||||
{/* <th onMouseEnter={termtip.bind(null, 'heat per second')} onMouseLeave={hide} rowSpan={2}>{translate('HPS')}</th> */}
|
{/* <th onMouseEnter={termtip.bind(null, 'heat per second')} onMouseLeave={hide} rowSpan={2}>{translate('HPS')}</th> */}
|
||||||
<th rowSpan={2}>{translate('cargo')}</th>
|
<th rowSpan={2}>{translate('cargo')}</th>
|
||||||
<th rowSpan={2}>{translate('pax')}</th>
|
<th rowSpan={2} onMouseEnter={termtip.bind(null, 'passenger capacity', { cap: 0 })} onMouseLeave={hide}>{translate('pax')}</th>
|
||||||
<th rowSpan={2}>{translate('fuel')}</th>
|
<th rowSpan={2}>{translate('fuel')}</th>
|
||||||
<th colSpan={3}>{translate('mass')}</th>
|
<th colSpan={3}>{translate('mass')}</th>
|
||||||
<th onMouseEnter={termtip.bind(null, 'hull hardness', { cap: 0 })} onMouseLeave={hide} rowSpan={2}>{translate('hrd')}</th>
|
<th onMouseEnter={termtip.bind(null, 'hull hardness', { cap: 0 })} onMouseLeave={hide} rowSpan={2}>{translate('hrd')}</th>
|
||||||
<th rowSpan={2}>{translate('crew')}</th>
|
<th rowSpan={2}>{translate('crew')}</th>
|
||||||
<th onMouseEnter={termtip.bind(null, 'mass lock factor', { cap: 0 })} onMouseLeave={hide} rowSpan={2}>{translate('MLF')}</th>
|
<th onMouseEnter={termtip.bind(null, 'mass lock factor', { cap: 0 })} onMouseLeave={hide} rowSpan={2}>{translate('MLF')}</th>
|
||||||
<th onMouseEnter={termtip.bind(null, 'TT_SUMMARY_BOOST_TIME', { cap: 0 })} onMouseLeave={hide} rowSpan={2}>{translate('boost time')}</th>
|
<th onMouseEnter={termtip.bind(null, 'TT_SUMMARY_BOOST_INTERVAL', { cap: 0 })} onMouseLeave={hide} rowSpan={2}>{translate('boost interval')}</th>
|
||||||
|
<th rowSpan={2}>{translate('resting heat (Beta)')}</th>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th className='lft'>{translate('max')}</th>
|
<th className="lft">{translate('max')}</th>
|
||||||
<th>{translate('unladen')}</th>
|
<th>{translate('unladen')}</th>
|
||||||
<th>{translate('laden')}</th>
|
<th>{translate('laden')}</th>
|
||||||
<th>{translate('total unladen')}</th>
|
<th>{translate('total unladen')}</th>
|
||||||
@@ -99,29 +115,87 @@ export default class ShipSummaryTable extends TranslatedComponent {
|
|||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<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>
|
<td onMouseEnter={termtip.bind(null, speedTooltip, { cap: 0 })}
|
||||||
<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>
|
onMouseLeave={hide}
|
||||||
<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>
|
>{canThrust ?
|
||||||
<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>
|
<span>{int(speed)}{u['m/s']}</span> :
|
||||||
<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>
|
<span className='warning'>0<Warning/></span>
|
||||||
<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>
|
||||||
<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, boostTooltip, { cap: 0 })}
|
||||||
<td className={sgClassNames} onMouseEnter={termtip.bind(null, sgTooltip, { cap: 0 })} onMouseLeave={hide}>{int(ship.shield)}{u.MJ}</td>
|
onMouseLeave={hide}
|
||||||
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_INTEGRITY', { cap: 0 })} onMouseLeave={hide}>{int(ship.armour)}</td>
|
>{canBoost ?
|
||||||
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_DPS', { cap: 0 })} onMouseLeave={hide}>{f1(ship.totalDps)}</td>
|
<span>{int(shipBoost)}{u['m/s']}</span> :
|
||||||
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_EPS', { cap: 0 })} onMouseLeave={hide}>{f1(ship.totalEps)}</td>
|
<span className='warning'>0<Warning/></span>
|
||||||
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_TTD', { cap: 0 })} onMouseLeave={hide}>{timeToDrain === Infinity ? '∞' : time(timeToDrain)}</td>
|
}</td>
|
||||||
|
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_MAX_SINGLE_JUMP', { cap: 0 })}
|
||||||
|
onMouseLeave={hide}
|
||||||
|
>{canJump ?
|
||||||
|
// TODO:
|
||||||
|
<span>{NaN}{u.LY}</span> :
|
||||||
|
<span className='warning'>0<Warning/></span>
|
||||||
|
}</td>
|
||||||
|
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_UNLADEN_SINGLE_JUMP', { cap: 0 })}
|
||||||
|
onMouseLeave={hide}
|
||||||
|
>{canJump ?
|
||||||
|
// TODO:
|
||||||
|
<span>{NaN}{u.LY}</span> :
|
||||||
|
<span className='warning'>0<Warning/></span>
|
||||||
|
}</td>
|
||||||
|
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_LADEN_SINGLE_JUMP', { cap: 0 })}
|
||||||
|
onMouseLeave={hide}
|
||||||
|
>{canJump ?
|
||||||
|
<span>{f2(jumpRangeMetrics.jumpRange)}{u.LY}</span> :
|
||||||
|
<span className='warning'>0<Warning/></span>
|
||||||
|
}</td>
|
||||||
|
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_UNLADEN_TOTAL_JUMP', { cap: 0 })}
|
||||||
|
onMouseLeave={hide}
|
||||||
|
>{canJump ?
|
||||||
|
// TODO:
|
||||||
|
<span>{NaN}{u.LY}</span> :
|
||||||
|
<span className='warning'>0 <Warning/></span>
|
||||||
|
}</td>
|
||||||
|
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_LADEN_TOTAL_JUMP', { cap: 0 })}
|
||||||
|
onMouseLeave={hide}
|
||||||
|
>{canJump ?
|
||||||
|
<span>{f2(jumpRangeMetrics.totalRange)}{u.LY}</span> :
|
||||||
|
<span className='warning'>0<Warning/></span>
|
||||||
|
}</td>
|
||||||
|
<td className={sgClassNames}
|
||||||
|
onMouseEnter={termtip.bind(null, sgTooltip, { cap: 0 })}
|
||||||
|
onMouseLeave={hide}
|
||||||
|
>{int(sgMetrics.shieldStrength)}{u.MJ}</td>
|
||||||
|
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_INTEGRITY', { cap: 0 })}
|
||||||
|
onMouseLeave={hide}
|
||||||
|
>{int(armourMetrics.armour)}</td>
|
||||||
|
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_DPS', { cap: 0 })}
|
||||||
|
onMouseLeave={hide}
|
||||||
|
>{f1(damageMetrics.dps)}</td>
|
||||||
|
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_EPS', { cap: 0 })}
|
||||||
|
onMouseLeave={hide}
|
||||||
|
>{f1(damageMetrics.eps)}</td>
|
||||||
|
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_TTD', { cap: 0 })}
|
||||||
|
onMouseLeave={hide}
|
||||||
|
>{timeToDrain === Infinity ? '∞' : time(timeToDrain)}</td>
|
||||||
{/* <td>{f1(ship.totalHps)}</td> */}
|
{/* <td>{f1(ship.totalHps)}</td> */}
|
||||||
<td>{round(ship.cargoCapacity)}{u.T}</td>
|
<td>{ship.get(CARGO_CAPACITY)}{u.T}</td>
|
||||||
<td>{ship.passengerCapacity}</td>
|
<td>{ship.get(PASSENGER_CAPACITY)}</td>
|
||||||
<td>{round(ship.fuelCapacity)}{u.T}</td>
|
<td>{ship.get(FUEL_CAPACITY)}{u.T}</td>
|
||||||
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_HULL_MASS', { cap: 0 })} onMouseLeave={hide}>{ship.hullMass}{u.T}</td>
|
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_HULL_MASS', { cap: 0 })}
|
||||||
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_UNLADEN_MASS', { cap: 0 })} onMouseLeave={hide}>{int(ship.unladenMass)}{u.T}</td>
|
onMouseLeave={hide}
|
||||||
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_LADEN_MASS', { cap: 0 })} onMouseLeave={hide}>{int(ship.ladenMass)}{u.T}</td>
|
>{ship.readProp('hullmass')}{u.T}</td>
|
||||||
<td>{int(ship.hardness)}</td>
|
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_UNLADEN_MASS', { cap: 0 })}
|
||||||
<td>{ship.crew}</td>
|
onMouseLeave={hide}
|
||||||
<td>{ship.masslock}</td>
|
>{int(ship.get(UNLADEN_MASS))}{u.T}</td>
|
||||||
<td>{shipBoost !== 'No Boost' ? formats.time(shipBoost) : 'No Boost'}</td>
|
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_LADEN_MASS', { cap: 0 })}
|
||||||
|
onMouseLeave={hide}
|
||||||
|
>{int(ship.get(MAXIMUM_MASS))}{u.T}</td>
|
||||||
|
<td>{int(ship.readProp('hardness'))}</td>
|
||||||
|
<td>{ship.readMeta('crew')}</td>
|
||||||
|
<td>{ship.readProp('masslock')}</td>
|
||||||
|
{/* TODO: boost intervall */}
|
||||||
|
<td>{NaN}</td>
|
||||||
|
{/* TODO: resting heat */}
|
||||||
|
<td>{NaN}</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
@@ -149,21 +223,21 @@ export default class ShipSummaryTable extends TranslatedComponent {
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<td>{translate(shieldGenerator && shieldGenerator.m.grp || 'No Shield')}</td>
|
<td>{translate(sgType || 'No Shield')}</td>
|
||||||
<td>{formats.pct1(ship.shieldExplRes)}</td>
|
<td>{formats.pct1(1 - sgMetrics.explosive.damageMultiplier)}</td>
|
||||||
<td>{formats.pct1(ship.shieldKinRes)}</td>
|
<td>{formats.pct1(1 - sgMetrics.kinetic.damageMultiplier)}</td>
|
||||||
<td>{formats.pct1(ship.shieldThermRes)}</td>
|
<td>{formats.pct1(1 - sgMetrics.thermal.damageMultiplier)}</td>
|
||||||
<td></td>
|
<td></td>
|
||||||
|
|
||||||
<td>{int(ship && ship.shield > 0 ? ship.shield : 0)}{u.MJ}</td>
|
<td>{int(sgMetrics.shieldStrength || 0)}{u.MJ}</td>
|
||||||
<td>{int(ship && ship.shield > 0 ? ship.shield * ((1 / (1 - (ship.shieldExplRes)))) : 0)}{u.MJ}</td>
|
<td>{int(sgMetrics.shieldStrength / sgMetrics.explosive.damageMultiplier || 0)}{u.MJ}</td>
|
||||||
<td>{int(ship && ship.shield > 0 ? ship.shield * ((1 / (1 - (ship.shieldKinRes)))) : 0)}{u.MJ}</td>
|
<td>{int(sgMetrics.shieldStrength / sgMetrics.kinetic.damageMultiplier || 0)}{u.MJ}</td>
|
||||||
<td>{int(ship && ship.shield > 0 ? ship.shield * ((1 / (1 - (ship.shieldThermRes)))) : 0)}{u.MJ}</td>
|
<td>{int(sgMetrics.shieldStrength / sgMetrics.thermal.damageMultiplier || 0)}{u.MJ}</td>
|
||||||
<td></td>
|
<td></td>
|
||||||
<td>{sgMetrics && sgMetrics.recover === Math.Inf ? translate('Never') : formats.time(sgMetrics.recover)}</td>
|
<td>{formats.time(sgMetrics.recover) || translate('Never')}</td>
|
||||||
<td>{sgMetrics && sgMetrics.recharge === Math.Inf ? translate('Never') : formats.time(sgMetrics.recharge)}</td>
|
<td>{formats.time(sgMetrics.recharge) || translate('Never')}</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
@@ -175,7 +249,7 @@ export default class ShipSummaryTable extends TranslatedComponent {
|
|||||||
<th rowSpan={2} onMouseEnter={termtip.bind(null, 'TT_MODULE_PROTECTION_INTERNAL', { cap: 0 })} onMouseLeave={hide} className='lft'>{translate('internal protection')}</th>
|
<th rowSpan={2} onMouseEnter={termtip.bind(null, 'TT_MODULE_PROTECTION_INTERNAL', { cap: 0 })} onMouseLeave={hide} className='lft'>{translate('internal protection')}</th>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>{`${translate('explosive')}`}</th>
|
<th>{`${translate('explosive')}`}</th>
|
||||||
<th>{`${translate('kinetic')}`}</th>
|
<th>{`${translate('kinetic')}`}</th>
|
||||||
<th>{`${translate('thermal')}`}</th>
|
<th>{`${translate('thermal')}`}</th>
|
||||||
<th>{`${translate('caustic')}`}</th>
|
<th>{`${translate('caustic')}`}</th>
|
||||||
@@ -189,19 +263,18 @@ export default class ShipSummaryTable extends TranslatedComponent {
|
|||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<td>{translate(ship && ship.bulkheads && ship.bulkheads.m && ship.bulkheads.m.name || 'No Armour')}</td>
|
<td>{translate(ship.getAlloys().readMeta('type') || 'No Armour')}</td>
|
||||||
<td>{formats.pct1(ship.hullExplRes)}</td>
|
<td>{formats.pct1(1 - armourMetrics.explosive.damageMultiplier)}</td>
|
||||||
<td>{formats.pct1(ship.hullKinRes)}</td>
|
<td>{formats.pct1(1 - armourMetrics.kinetic.damageMultiplier)}</td>
|
||||||
<td>{formats.pct1(ship.hullThermRes)}</td>
|
<td>{formats.pct1(1 - armourMetrics.thermal.damageMultiplier)}</td>
|
||||||
<td>{formats.pct1(ship.hullCausRes)}</td>
|
<td>{formats.pct1(1 - armourMetrics.caustic.damageMultiplier)}</td>
|
||||||
<td>{int(ship.armour)}</td>
|
<td>{int(armourMetrics.armour)}</td>
|
||||||
<td>{int(ship.armour * ((1 / (1 - (ship.hullExplRes)))))}</td>
|
<td>{int(armourMetrics.armour / armourMetrics.explosive.damageMultiplier)}</td>
|
||||||
<td>{int(ship.armour * ((1 / (1 - (ship.hullKinRes)))))}</td>
|
<td>{int(armourMetrics.armour / armourMetrics.kinetic.damageMultiplier)}</td>
|
||||||
<td>{int(ship.armour * ((1 / (1 - (ship.hullThermRes)))))}</td>
|
<td>{int(armourMetrics.armour / armourMetrics.thermal.damageMultiplier)}</td>
|
||||||
<td>{int(ship.armour * ((1 / (1 - (ship.hullCausRes)))))}</td>
|
<td>{int(armourMetrics.armour / armourMetrics.caustic.damageMultiplier)}</td>
|
||||||
<td>{int(armourMetrics.modulearmour)}</td>
|
<td>{int(moduleProtectionMetrics.moduleArmour)}</td>
|
||||||
<td>{int(armourMetrics.moduleprotection * 100) + '%'}</td>
|
<td>{formats.pct1(1 - moduleProtectionMetrics.moduleProtection)}</td>
|
||||||
|
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
import autoBind from 'auto-bind';
|
||||||
|
|
||||||
const MARGIN_LR = 8; // Left/ Right margin
|
const MARGIN_LR = 8; // Left/ Right margin
|
||||||
|
|
||||||
@@ -7,7 +8,6 @@ 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,
|
||||||
@@ -32,16 +32,7 @@ export default class Slider extends React.Component {
|
|||||||
*/
|
*/
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this._down = this._down.bind(this);
|
autoBind(this);
|
||||||
this._move = this._move.bind(this);
|
|
||||||
this._up = this._up.bind(this);
|
|
||||||
this._keyup = this._keyup.bind(this);
|
|
||||||
this._keydown = this._keydown.bind(this);
|
|
||||||
this._touchstart = this._touchstart.bind(this);
|
|
||||||
this._touchend = this._touchend.bind(this);
|
|
||||||
|
|
||||||
this._updatePercent = this._updatePercent.bind(this);
|
|
||||||
this._updateDimensions = this._updateDimensions.bind(this);
|
|
||||||
|
|
||||||
this.state = { width: 0 };
|
this.state = { width: 0 };
|
||||||
}
|
}
|
||||||
@@ -55,7 +46,6 @@ export default class Slider extends React.Component {
|
|||||||
this.left = rect.left;
|
this.left = rect.left;
|
||||||
this.width = rect.width;
|
this.width = rect.width;
|
||||||
this._move(event);
|
this._move(event);
|
||||||
this.touchStartTimer = setTimeout(() => this.sliderInputBox._setDisplay('block'), 1500);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -75,70 +65,11 @@ export default class Slider extends React.Component {
|
|||||||
* @param {Event} event DOM Event
|
* @param {Event} event DOM Event
|
||||||
*/
|
*/
|
||||||
_up(event) {
|
_up(event) {
|
||||||
this.sliderInputBox.sliderVal.focus();
|
|
||||||
clearTimeout(this.touchStartTimer);
|
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
this.left = null;
|
this.left = null;
|
||||||
this.width = null;
|
this.width = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Key up handler for keyboard.
|
|
||||||
* display the number field then set focus to it
|
|
||||||
* when "Enter" key is pressed
|
|
||||||
* @param {Event} event Keyboard event
|
|
||||||
*/
|
|
||||||
_keyup(event) {
|
|
||||||
switch (event.key) {
|
|
||||||
case 'Enter':
|
|
||||||
event.preventDefault();
|
|
||||||
this.sliderInputBox._setDisplay('block');
|
|
||||||
return;
|
|
||||||
default:
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Key down handler
|
|
||||||
* 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;
|
|
||||||
switch (event.key) {
|
|
||||||
case 'ArrowRight':
|
|
||||||
newVal += 1;
|
|
||||||
if (newVal <= this.props.max) this.props.onChange(newVal / this.props.max);
|
|
||||||
return;
|
|
||||||
case 'ArrowLeft':
|
|
||||||
newVal -= 1;
|
|
||||||
if (newVal >= 0) this.props.onChange(newVal / this.props.max);
|
|
||||||
return;
|
|
||||||
default:
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Touch start handler
|
|
||||||
* @param {Event} event DOM Event
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
_touchstart(event) {
|
|
||||||
this.touchStartTimer = setTimeout(() => this.sliderInputBox._setDisplay('block'), 1500);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Touch end handler
|
|
||||||
* @param {Event} event DOM Event
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
_touchend(event) {
|
|
||||||
this.sliderInputBox.sliderVal.focus();
|
|
||||||
clearTimeout(this.touchStartTimer);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determine if the user is still dragging
|
* Determine if the user is still dragging
|
||||||
* @param {SyntheticEvent} event Event
|
* @param {SyntheticEvent} event Event
|
||||||
@@ -212,8 +143,8 @@ export default class Slider extends React.Component {
|
|||||||
let margin = MARGIN_LR * scale;
|
let margin = MARGIN_LR * scale;
|
||||||
let width = outerWidth - (margin * 2);
|
let width = outerWidth - (margin * 2);
|
||||||
let pctPos = width * this.props.percent;
|
let pctPos = width * this.props.percent;
|
||||||
return <div><svg
|
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">
|
onMouseUp={this._up} onMouseEnter={this._enter.bind(this)} onMouseMove={this._move} 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' 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' />
|
<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} />
|
<circle className='primary' r={margin} cy='0.6em' cx={pctPos + margin} />
|
||||||
@@ -224,163 +155,6 @@ export default class Slider extends React.Component {
|
|||||||
<text className='primary-disabled' y='3em' x='100%' style={{ textAnchor: 'end' }}>{max + axisUnit}</text>
|
<text className='primary-disabled' y='3em' x='100%' style={{ textAnchor: 'end' }}>{max + axisUnit}</text>
|
||||||
</g>}
|
</g>}
|
||||||
</svg>
|
</svg>
|
||||||
<TextInputBox ref={(tb) => this.sliderInputBox = tb}
|
</div>;
|
||||||
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>;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|||||||
@@ -2,31 +2,38 @@ import React from 'react';
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import TranslatedComponent from './TranslatedComponent';
|
import TranslatedComponent from './TranslatedComponent';
|
||||||
import cn from 'classnames';
|
import cn from 'classnames';
|
||||||
|
import { ListModifications, Modified } from './SvgIcons';
|
||||||
import AvailableModulesMenu from './AvailableModulesMenu';
|
import AvailableModulesMenu from './AvailableModulesMenu';
|
||||||
import ModificationsMenu from './ModificationsMenu';
|
import ModificationsMenu from './ModificationsMenu';
|
||||||
import { diffDetails } from '../utils/SlotFunctions';
|
import { diffDetails } from '../utils/SlotFunctions';
|
||||||
import { wrapCtxMenu } from '../utils/UtilityFunctions';
|
import { stopCtxPropagation, wrapCtxMenu } from '../utils/UtilityFunctions';
|
||||||
import { stopCtxPropagation } from '../utils/UtilityFunctions';
|
import { blueprintTooltip } from '../utils/BlueprintFunctions';
|
||||||
|
import { Module } from 'ed-forge';
|
||||||
|
import { TYPES } from 'ed-forge/lib/data/slots';
|
||||||
|
import autoBind from 'auto-bind';
|
||||||
|
import { toPairs } from 'lodash';
|
||||||
|
|
||||||
|
const HARDPOINT_SLOT_LABELS = {
|
||||||
|
1: 'S',
|
||||||
|
2: 'M',
|
||||||
|
3: 'L',
|
||||||
|
4: 'H',
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Abstract Slot
|
* Abstract Slot
|
||||||
*/
|
*/
|
||||||
export default class Slot extends TranslatedComponent {
|
export default class Slot extends TranslatedComponent {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
availableModules: PropTypes.func.isRequired,
|
currentMenu: PropTypes.any,
|
||||||
onSelect: PropTypes.func.isRequired,
|
hideSearch: PropTypes.bool,
|
||||||
onOpen: PropTypes.func.isRequired,
|
m: PropTypes.instanceOf(Module),
|
||||||
maxClass: PropTypes.number.isRequired,
|
|
||||||
selected: PropTypes.bool,
|
|
||||||
m: PropTypes.object,
|
|
||||||
enabled: PropTypes.bool.isRequired,
|
|
||||||
ship: PropTypes.object.isRequired,
|
|
||||||
eligible: PropTypes.object,
|
|
||||||
warning: PropTypes.func,
|
warning: PropTypes.func,
|
||||||
drag: PropTypes.func,
|
drag: PropTypes.func,
|
||||||
drop: PropTypes.func,
|
drop: PropTypes.func,
|
||||||
dropClass: PropTypes.string
|
dropClass: PropTypes.string,
|
||||||
|
propsToShow: PropTypes.object.isRequired,
|
||||||
|
onPropToggle: PropTypes.func.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -35,25 +42,122 @@ export default class Slot extends TranslatedComponent {
|
|||||||
*/
|
*/
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
autoBind(this);
|
||||||
this._modificationsSelected = false;
|
this.state = { menuIndex: 0 };
|
||||||
|
|
||||||
this._contextMenu = wrapCtxMenu(this._contextMenu.bind(this));
|
|
||||||
this._getMaxClassLabel = this._getMaxClassLabel.bind(this);
|
|
||||||
this._keyDown = this._keyDown.bind(this);
|
|
||||||
this.slotDiv = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Must be implemented by subclasses:
|
/**
|
||||||
// _getSlotDetails()
|
* Opens a menu while setting state.
|
||||||
|
* @param {Object} newMenuIndex New menu index
|
||||||
|
* @param {Event} event Event object
|
||||||
|
*/
|
||||||
|
_openMenu(newMenuIndex, event) {
|
||||||
|
const slotName = this.props.m.getSlot();
|
||||||
|
if (
|
||||||
|
this.props.currentMenu === slotName &&
|
||||||
|
newMenuIndex === this.state.menuIndex
|
||||||
|
) {
|
||||||
|
this.context.closeMenu();
|
||||||
|
} {
|
||||||
|
this.setState({ menuIndex: newMenuIndex });
|
||||||
|
this.context.openMenu(slotName);
|
||||||
|
}
|
||||||
|
// If we don't stop event propagation, the underlying divs also might
|
||||||
|
// get clicked which would open up other menus
|
||||||
|
event.stopPropagation();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the CSS class name for the slot. Can/should be overriden
|
* Generate the slot contents
|
||||||
* as necessary.
|
* @return {React.Component} Slot contents
|
||||||
* @return {string} CSS Class name
|
|
||||||
*/
|
*/
|
||||||
_getClassNames() {
|
_getSlotDetails() {
|
||||||
return null;
|
const { m, propsToShow } = this.props;
|
||||||
|
let { termtip, tooltip, language } = this.context;
|
||||||
|
const { translate, units, formats } = language;
|
||||||
|
|
||||||
|
if (m.isEmpty()) {
|
||||||
|
return <div className="empty">
|
||||||
|
{translate(
|
||||||
|
m.isOnSlot(TYPES.MILITARY) ? 'emptyrestricted' : 'empty'
|
||||||
|
)}
|
||||||
|
</div>;
|
||||||
|
} else {
|
||||||
|
let classRating = m.getClassRating();
|
||||||
|
let { drag, drop } = this.props;
|
||||||
|
|
||||||
|
// Modifications tooltip shows blueprint and grade, if available
|
||||||
|
let modTT = translate('modified');
|
||||||
|
const blueprint = m.getBlueprint();
|
||||||
|
const experimental = m.getExperimental();
|
||||||
|
const grade = m.getBlueprintGrade();
|
||||||
|
if (blueprint) {
|
||||||
|
modTT = `${translate(blueprint)} ${translate('grade')}: ${grade}`;
|
||||||
|
if (experimental) {
|
||||||
|
modTT += `, ${translate(experimental)}`;
|
||||||
|
}
|
||||||
|
modTT = (
|
||||||
|
<div>
|
||||||
|
<div>{modTT}</div>
|
||||||
|
{blueprintTooltip(language, m)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mass = m.get('mass') || m.get('cargo') || m.get('fuel') || 0;
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={cn('details', { disabled: !m.isEnabled() })}
|
||||||
|
draggable="true"
|
||||||
|
onDragStart={drag}
|
||||||
|
onDragEnd={drop}
|
||||||
|
>
|
||||||
|
<div className={'cb'}>
|
||||||
|
<div className={'l'}>
|
||||||
|
{classRating} {translate(m.readMeta('type'))}
|
||||||
|
{blueprint && (
|
||||||
|
<span
|
||||||
|
onMouseOver={termtip.bind(null, modTT)}
|
||||||
|
onMouseOut={tooltip.bind(null, null)}
|
||||||
|
>
|
||||||
|
<Modified />
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{propsToShow.mass ?
|
||||||
|
<div className={'r'}>
|
||||||
|
{formats.round(mass)}
|
||||||
|
{units.T}
|
||||||
|
</div> : null}
|
||||||
|
</div>
|
||||||
|
<div className={'cb'}>
|
||||||
|
{toPairs(propsToShow).sort().map(([prop, show]) => {
|
||||||
|
const { unit, value } = m.getFormatted(prop, true);
|
||||||
|
// Don't show mass again; it's already shown on the top right
|
||||||
|
// corner of a slot
|
||||||
|
if (!show || isNaN(value) || prop == 'mass') {
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
return (<div className='l'>
|
||||||
|
{translate(prop)}: {formats.round(value)}{unit}
|
||||||
|
</div>);
|
||||||
|
}
|
||||||
|
})}
|
||||||
|
{(m.getApplicableBlueprints() || []).length > 0 ? (
|
||||||
|
<div className="r">
|
||||||
|
<button onClick={this._openMenu.bind(this, 1)}
|
||||||
|
onContextMenu={stopCtxPropagation}
|
||||||
|
onMouseOver={termtip.bind(null, translate('modifications'))}
|
||||||
|
onMouseOut={tooltip.bind(null, null)}
|
||||||
|
>
|
||||||
|
<ListModifications />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -62,7 +166,19 @@ export default class Slot extends TranslatedComponent {
|
|||||||
* @return {string} label
|
* @return {string} label
|
||||||
*/
|
*/
|
||||||
_getMaxClassLabel() {
|
_getMaxClassLabel() {
|
||||||
return this.props.maxClass;
|
const { m } = this.props;
|
||||||
|
let size = m.getSize();
|
||||||
|
switch (true) {
|
||||||
|
case m.getSlot() === 'armour':
|
||||||
|
return '';
|
||||||
|
case size === 0:
|
||||||
|
// This can also happen for armour but that case was handled above
|
||||||
|
return 'U';
|
||||||
|
case m.isOnSlot(TYPES.HARDPOINT):
|
||||||
|
return HARDPOINT_SLOT_LABELS[size];
|
||||||
|
default:
|
||||||
|
return size;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -72,25 +188,15 @@ export default class Slot extends TranslatedComponent {
|
|||||||
_contextMenu(event) {
|
_contextMenu(event) {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
this.props.onSelect(null,null);
|
const { m } = this.props;
|
||||||
}
|
m.reset();
|
||||||
|
if (this.props.currentMenu === m.getSlot()) {
|
||||||
/** Key Down handler
|
this.context.closeMenu();
|
||||||
* @param {SyntheticEvent} event Event
|
} else {
|
||||||
* ToDo: see if this can be moved up
|
this.forceUpdate();
|
||||||
* 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
|
||||||
@@ -98,64 +204,42 @@ export default class Slot extends TranslatedComponent {
|
|||||||
render() {
|
render() {
|
||||||
let language = this.context.language;
|
let language = this.context.language;
|
||||||
let translate = language.translate;
|
let translate = language.translate;
|
||||||
let { ship, m, enabled, dropClass, dragOver, onOpen, onChange, selected, eligible, onSelect, warning, availableModules } = this.props;
|
let {
|
||||||
let slotDetails, modificationsMarker, menu;
|
currentMenu, m, dropClass, dragOver, warning, hideSearch, propsToShow,
|
||||||
|
onPropToggle,
|
||||||
if (!selected) {
|
} = this.props;
|
||||||
// If not selected then sure that modifications flag is unset
|
const { menuIndex } = this.state;
|
||||||
this._modificationsSelected = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (m) {
|
|
||||||
slotDetails = this._getSlotDetails(m, enabled, translate, language.formats, language.units); // Must be implemented by sub classes
|
|
||||||
modificationsMarker = JSON.stringify(m);
|
|
||||||
} else {
|
|
||||||
slotDetails = <div className={'empty'}>{translate(eligible ? 'emptyrestricted' : 'empty')}</div>;
|
|
||||||
modificationsMarker = '';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (selected) {
|
|
||||||
if (this._modificationsSelected) {
|
|
||||||
menu = <ModificationsMenu
|
|
||||||
className={this._getClassNames()}
|
|
||||||
onChange={onChange}
|
|
||||||
ship={ship}
|
|
||||||
m={m}
|
|
||||||
marker={modificationsMarker}
|
|
||||||
modButton = {this.modButton}
|
|
||||||
/>;
|
|
||||||
} else {
|
|
||||||
menu = <AvailableModulesMenu
|
|
||||||
className={this._getClassNames()}
|
|
||||||
modules={availableModules()}
|
|
||||||
shipMass={ship.hullMass}
|
|
||||||
m={m}
|
|
||||||
onSelect={onSelect}
|
|
||||||
warning={warning}
|
|
||||||
diffDetails={diffDetails.bind(ship, this.context.language)}
|
|
||||||
slotDiv = {this.slotDiv}
|
|
||||||
/>;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: implement touch dragging
|
// TODO: implement touch dragging
|
||||||
|
const selected = currentMenu === m.getSlot();
|
||||||
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
|
||||||
<div className='details-container'>
|
className={cn('slot', dropClass, { selected })}
|
||||||
<div className='sz'>{this._getMaxClassLabel(translate)}</div>
|
onContextMenu={this._contextMenu}
|
||||||
{slotDetails}
|
onDragOver={dragOver} tabIndex="0"
|
||||||
</div>
|
onClick={this._openMenu.bind(this, 0)}
|
||||||
{menu}
|
>
|
||||||
|
<div className={cn(
|
||||||
|
'details-container',
|
||||||
|
{ warning: warning && warning(m) },
|
||||||
|
)}>
|
||||||
|
<div className="sz">{this._getMaxClassLabel(translate)}</div>
|
||||||
|
{this._getSlotDetails()}
|
||||||
|
</div>
|
||||||
|
{selected && menuIndex === 0 &&
|
||||||
|
<AvailableModulesMenu
|
||||||
|
m={m} hideSearch={hideSearch}
|
||||||
|
onSelect={(item) => {
|
||||||
|
m.setItem(item);
|
||||||
|
this.context.closeMenu();
|
||||||
|
}}
|
||||||
|
warning={warning}
|
||||||
|
// diffDetails={diffDetails.bind(ship, this.context.language)}
|
||||||
|
/>}
|
||||||
|
{selected && menuIndex === 1 &&
|
||||||
|
<ModificationsMenu m={m} propsToShow={propsToShow}
|
||||||
|
onPropToggle={onPropToggle} />}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Toggle the modifications flag when selecting the modifications icon
|
|
||||||
*/
|
|
||||||
_toggleModifications() {
|
|
||||||
this._modificationsSelected = !this._modificationsSelected;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,47 +5,33 @@ import { wrapCtxMenu } from '../utils/UtilityFunctions';
|
|||||||
import { canMount } from '../utils/SlotFunctions';
|
import { canMount } from '../utils/SlotFunctions';
|
||||||
import { Equalizer } from '../components/SvgIcons';
|
import { Equalizer } from '../components/SvgIcons';
|
||||||
import cn from 'classnames';
|
import cn from 'classnames';
|
||||||
|
import { Ship } from 'ed-forge';
|
||||||
|
import autoBind from 'auto-bind';
|
||||||
const browser = require('detect-browser');
|
const browser = require('detect-browser');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Abstract Slot Section
|
* Abstract Slot Section
|
||||||
*/
|
*/
|
||||||
export default class SlotSection extends TranslatedComponent {
|
export default class SlotSection extends TranslatedComponent {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
ship: PropTypes.object.isRequired,
|
ship: PropTypes.instanceOf(Ship),
|
||||||
onChange: PropTypes.func.isRequired,
|
|
||||||
onCargoChange: PropTypes.func.isRequired,
|
|
||||||
onFuelChange: PropTypes.func.isRequired,
|
|
||||||
code: PropTypes.string.isRequired,
|
code: PropTypes.string.isRequired,
|
||||||
togglePwr: PropTypes.func,
|
togglePwr: PropTypes.func,
|
||||||
sectionMenuRefs: PropTypes.object
|
propsToShow: PropTypes.object.isRequired,
|
||||||
|
onPropToggle: PropTypes.func.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor
|
* Constructor
|
||||||
* @param {Object} props React Component properties
|
* @param {Object} props React Component properties
|
||||||
* @param {Object} context React Component context
|
|
||||||
* @param {string} sectionId Section DOM Id
|
|
||||||
* @param {string} sectionName Section name
|
* @param {string} sectionName Section name
|
||||||
*/
|
*/
|
||||||
constructor(props, context, sectionId, sectionName) {
|
constructor(props, sectionName) {
|
||||||
super(props);
|
super(props);
|
||||||
this.sectionId = sectionId;
|
autoBind(this);
|
||||||
this.sectionName = sectionName;
|
|
||||||
this.ssHeadRef = null;
|
this.sectionName = sectionName;
|
||||||
|
|
||||||
this.sectionRefArr = this.props.sectionMenuRefs[this.sectionId] = [];
|
|
||||||
this.sectionRefArr['selectedRef'] = null;
|
|
||||||
this._getSlots = this._getSlots.bind(this);
|
|
||||||
this._selectModule = this._selectModule.bind(this);
|
|
||||||
this._getSectionMenu = this._getSectionMenu.bind(this);
|
|
||||||
this._contextMenu = this._contextMenu.bind(this);
|
|
||||||
this._drop = this._drop.bind(this);
|
|
||||||
this._dragOverNone = this._dragOverNone.bind(this);
|
|
||||||
this._close = this._close.bind(this);
|
|
||||||
this._keyDown = this._keyDown.bind(this);
|
|
||||||
this._handleSectionFocus = this._handleSectionFocus.bind(this);
|
|
||||||
this.state = {};
|
this.state = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,82 +41,6 @@ export default class SlotSection extends TranslatedComponent {
|
|||||||
// _contextMenu()
|
// _contextMenu()
|
||||||
// componentDidUpdate(prevProps)
|
// 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
|
|
||||||
* @param {string} menu Menu name
|
|
||||||
* @param {SyntheticEvent} event Event
|
|
||||||
*/
|
|
||||||
_openMenu(menu, event) {
|
|
||||||
event.preventDefault();
|
|
||||||
event.stopPropagation();
|
|
||||||
if (this.props.currentMenu === menu) {
|
|
||||||
menu = null;
|
|
||||||
}
|
|
||||||
this.context.openMenu(menu);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Mount/Use the specified module in the slot
|
|
||||||
* @param {Object} slot Slot
|
|
||||||
* @param {Object} m Selected module
|
|
||||||
*/
|
|
||||||
_selectModule(slot, m) {
|
|
||||||
this.props.ship.use(slot, m, false);
|
|
||||||
this.props.onChange();
|
|
||||||
this._close();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Slot Drag Handler
|
* Slot Drag Handler
|
||||||
* @param {object} originSlot Origin slot model
|
* @param {object} originSlot Origin slot model
|
||||||
@@ -195,10 +105,18 @@ export default class SlotSection extends TranslatedComponent {
|
|||||||
if (targetSlot && canMount(this.props.ship, targetSlot, m.grp, m.class)) {
|
if (targetSlot && canMount(this.props.ship, targetSlot, m.grp, m.class)) {
|
||||||
const mCopy = m.clone();
|
const mCopy = m.clone();
|
||||||
this.props.ship.use(targetSlot, mCopy, false);
|
this.props.ship.use(targetSlot, mCopy, false);
|
||||||
|
let experimentalNum = this.props.ship.hardpoints
|
||||||
|
.filter(s => s.m && s.m.experimental).length;
|
||||||
|
// Remove the module on the last slot if we now exceed the number of
|
||||||
|
// experimentals allowed
|
||||||
|
if (m.experimental && 4 < experimentalNum) {
|
||||||
|
this.props.ship.updateStats(originSlot, null, originSlot.m);
|
||||||
|
originSlot.m = null; // Empty the slot
|
||||||
|
originSlot.discountedCost = 0;
|
||||||
|
}
|
||||||
// Copy power info
|
// Copy power info
|
||||||
targetSlot.enabled = originSlot.enabled;
|
targetSlot.enabled = originSlot.enabled;
|
||||||
targetSlot.priority = originSlot.priority;
|
targetSlot.priority = originSlot.priority;
|
||||||
this.props.onChange();
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Store power info
|
// Store power info
|
||||||
@@ -227,7 +145,6 @@ export default class SlotSection extends TranslatedComponent {
|
|||||||
targetSlot.enabled = targetEnabled;
|
targetSlot.enabled = targetEnabled;
|
||||||
targetSlot.priority = targetPriority;
|
targetSlot.priority = targetPriority;
|
||||||
}
|
}
|
||||||
this.props.onChange();
|
|
||||||
this.props.ship
|
this.props.ship
|
||||||
.updatePowerGenerated()
|
.updatePowerGenerated()
|
||||||
.updatePowerUsed()
|
.updatePowerUsed()
|
||||||
@@ -273,6 +190,17 @@ export default class SlotSection extends TranslatedComponent {
|
|||||||
return 'ineligible'; // Cannot be dropped / invalid drop slot
|
return 'ineligible'; // Cannot be dropped / invalid drop slot
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_open(newMenu, event) {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
const { currentMenu } = this.props;
|
||||||
|
if (currentMenu === newMenu) {
|
||||||
|
this.context.closeMenu();
|
||||||
|
} else {
|
||||||
|
this.context.openMenu(newMenu);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Close current menu
|
* Close current menu
|
||||||
*/
|
*/
|
||||||
@@ -289,14 +217,13 @@ export default class SlotSection extends TranslatedComponent {
|
|||||||
render() {
|
render() {
|
||||||
let translate = this.context.language.translate;
|
let translate = this.context.language.translate;
|
||||||
let sectionMenuOpened = this.props.currentMenu === this.sectionName;
|
let sectionMenuOpened = this.props.currentMenu === this.sectionName;
|
||||||
let open = this._openMenu.bind(this, this.sectionName);
|
|
||||||
let ctx = wrapCtxMenu(this._contextMenu);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div id={this.sectionId} className={'group'} onDragLeave={this._dragOverNone}>
|
<div className="group" onDragLeave={this._dragOverNone}>
|
||||||
<div className={cn('section-menu', { selected: sectionMenuOpened })} onClick={open} onContextMenu={ctx}>
|
<div className={cn('section-menu', { selected: sectionMenuOpened })}
|
||||||
<h1 tabIndex="0" onKeyDown={this._keyDown} ref={ssHead => this.sectionRefArr['ssHeadRef'] = ssHead}>{translate(this.sectionName)} <Equalizer/></h1>
|
onContextMenu={wrapCtxMenu(this._contextMenu)} onClick={this._open.bind(this, this.sectionName)}>
|
||||||
{sectionMenuOpened ? this._getSectionMenu(translate, this.props.ship) : null }
|
<h1 tabIndex="0">{translate(this.sectionName)}<Equalizer/></h1>
|
||||||
|
{sectionMenuOpened && this._getSectionMenu()}
|
||||||
</div>
|
</div>
|
||||||
{this._getSlots()}
|
{this._getSlots()}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,162 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import cn from 'classnames';
|
|
||||||
import Persist from '../stores/Persist';
|
|
||||||
import TranslatedComponent from './TranslatedComponent';
|
|
||||||
import { diffDetails } from '../utils/SlotFunctions';
|
|
||||||
import AvailableModulesMenu from './AvailableModulesMenu';
|
|
||||||
import ModificationsMenu from './ModificationsMenu';
|
|
||||||
import * as ModuleUtils from '../shipyard/ModuleUtils';
|
|
||||||
import { ListModifications, Modified } from './SvgIcons';
|
|
||||||
import { Modifications } from 'coriolis-data/dist';
|
|
||||||
import { stopCtxPropagation } from '../utils/UtilityFunctions';
|
|
||||||
import { blueprintTooltip } from '../utils/BlueprintFunctions';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Standard Slot
|
|
||||||
*/
|
|
||||||
export default class StandardSlot extends TranslatedComponent {
|
|
||||||
|
|
||||||
static propTypes = {
|
|
||||||
slot: PropTypes.object,
|
|
||||||
modules: PropTypes.array.isRequired,
|
|
||||||
onSelect: PropTypes.func.isRequired,
|
|
||||||
onOpen: PropTypes.func.isRequired,
|
|
||||||
onChange: PropTypes.func.isRequired,
|
|
||||||
ship: PropTypes.object.isRequired,
|
|
||||||
selected: PropTypes.bool,
|
|
||||||
warning: PropTypes.func,
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Construct the slot
|
|
||||||
* @param {object} props Object properties
|
|
||||||
*/
|
|
||||||
constructor(props) {
|
|
||||||
super(props);
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Render the slot
|
|
||||||
* @return {React.Component} Slot component
|
|
||||||
*/
|
|
||||||
render() {
|
|
||||||
let { termtip, tooltip } = this.context;
|
|
||||||
let { translate, formats, units } = this.context.language;
|
|
||||||
let { modules, slot, selected, warning, onSelect, onChange, ship } = this.props;
|
|
||||||
let m = slot.m;
|
|
||||||
let classRating = m.class + m.rating;
|
|
||||||
let menu;
|
|
||||||
let validMods = m == null || !Modifications.modules[m.grp] ? [] : (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 mass = m.getMass() || m.cargo || m.fuel || 0;
|
|
||||||
|
|
||||||
// Modifications tooltip shows blueprint and grade, if available
|
|
||||||
let modTT = translate('modified');
|
|
||||||
if (m && m.blueprint && m.blueprint.name) {
|
|
||||||
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 = (
|
|
||||||
<div>
|
|
||||||
<div>{modTT}</div>
|
|
||||||
{blueprintTooltip(translate, m.blueprint.grades[m.blueprint.grade], null, m.grp, m)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!selected) {
|
|
||||||
// If not selected then sure that modifications flag is unset
|
|
||||||
this._modificationsSelected = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const modificationsMarker = JSON.stringify(m);
|
|
||||||
|
|
||||||
if (selected) {
|
|
||||||
if (this._modificationsSelected) {
|
|
||||||
menu = <ModificationsMenu
|
|
||||||
className='standard'
|
|
||||||
onChange={onChange}
|
|
||||||
ship={ship}
|
|
||||||
m={m}
|
|
||||||
marker={modificationsMarker}
|
|
||||||
modButton = {this.modButton}
|
|
||||||
/>;
|
|
||||||
} else {
|
|
||||||
menu = <AvailableModulesMenu
|
|
||||||
className='standard'
|
|
||||||
modules={modules}
|
|
||||||
shipMass={ModuleUtils.isShieldGenerator(m.grp) ? ship.hullMass : ship.unladenMass}
|
|
||||||
m={m}
|
|
||||||
onSelect={onSelect}
|
|
||||||
warning={warning}
|
|
||||||
diffDetails={diffDetails.bind(ship, this.context.language)}
|
|
||||||
slotDiv = {this.slotDiv}
|
|
||||||
/>;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={cn('slot', { selected: this.props.selected })} onClick={this.props.onOpen} onKeyDown={this._keyDown} onContextMenu={stopCtxPropagation} tabIndex="0" ref={ slotDiv => this.slotDiv = slotDiv }>
|
|
||||||
<div className={cn('details-container', { warning: warning && warning(slot.m), disabled: m.grp !== 'bh' && !slot.enabled })}>
|
|
||||||
<div className={'sz'}>{m.grp == 'bh' ? m.name.charAt(0) : slot.maxClass}</div>
|
|
||||||
<div>
|
|
||||||
<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/>
|
|
||||||
<div className={'cb'}>
|
|
||||||
{ m.getMinMass() ? <div className='l'>{translate('minimum mass')}: {formats.int(m.getMinMass())}{units.T}</div> : null }
|
|
||||||
{ m.getOptMass() ? <div className='l'>{translate('optimal mass')}: {formats.int(m.getOptMass())}{units.T}</div> : null }
|
|
||||||
{ m.getMaxMass() ? <div className='l'>{translate('max mass')}: {formats.int(m.getMaxMass())}{units.T}</div> : null }
|
|
||||||
{ m.getOptMul() ? <div className='l'>{translate('optimal multiplier')}: {formats.rPct(m.getOptMul())}</div> : null }
|
|
||||||
{ m.getRange() ? <div className='l'>{translate('range', m.grp)}: {formats.f2(m.getRange())}{units.km}</div> : null }
|
|
||||||
{ m.time ? <div className='l'>{translate('time')}: {formats.time(m.time)}</div> : null }
|
|
||||||
{ m.getThermalEfficiency() ? <div className='l'>{translate('efficiency')}: {formats.f2(m.getThermalEfficiency())}</div> : null }
|
|
||||||
{ m.getPowerGeneration() > 0 ? <div className='l'>{translate('pgen')}: {formats.f1(m.getPowerGeneration())}{units.MW}</div> : null }
|
|
||||||
{ m.getMaxFuelPerJump() ? <div className='l'>{translate('max')} {translate('fuel')}: {formats.f1(m.getMaxFuelPerJump())}{units.T}</div> : null }
|
|
||||||
{ m.getWeaponsCapacity() ? <div className='l'>{translate('WEP')}: {formats.f1(m.getWeaponsCapacity())}{units.MJ} / {formats.f1(m.getWeaponsRechargeRate())}{units.MW}</div> : null }
|
|
||||||
{ m.getSystemsCapacity() ? <div className='l'>{translate('SYS')}: {formats.f1(m.getSystemsCapacity())}{units.MJ} / {formats.f1(m.getSystemsRechargeRate())}{units.MW}</div> : null }
|
|
||||||
{ m.getEnginesCapacity() ? <div className='l'>{translate('ENG')}: {formats.f1(m.getEnginesCapacity())}{units.MJ} / {formats.f1(m.getEnginesRechargeRate())}{units.MW}</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.getThermalResistance() ? <div className='l'>{translate('thermres')}: {formats.pct(m.getThermalResistance())}</div> : null }
|
|
||||||
{ m.getIntegrity() ? <div className='l'>{translate('integrity')}: {formats.int(m.getIntegrity())}</div> : null }
|
|
||||||
{ validMods.length > 0 ? <div className='r' tabIndex="0" ref={ modButton => this.modButton = modButton }><button tabIndex="-1" onClick={this._toggleModifications.bind(this)} onContextMenu={stopCtxPropagation} onMouseOver={termtip.bind(null, 'modifications')} onMouseOut={tooltip.bind(null, null)}><ListModifications /></button></div> : null }
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{menu}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Toggle the modifications flag when selecting the modifications icon
|
|
||||||
*/
|
|
||||||
_toggleModifications() {
|
|
||||||
this._modificationsSelected = !this._modificationsSelected;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,46 +1,33 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import cn from 'classnames';
|
import cn from 'classnames';
|
||||||
import SlotSection from './SlotSection';
|
import SlotSection from './SlotSection';
|
||||||
import StandardSlot from './StandardSlot';
|
import Slot from './Slot';
|
||||||
import Module from '../shipyard/Module';
|
import Module from '../shipyard/Module';
|
||||||
import * as ShipRoles from '../shipyard/ShipRoles';
|
import * as ShipRoles from '../shipyard/ShipRoles';
|
||||||
import { stopCtxPropagation } from '../utils/UtilityFunctions';
|
import autoBind from 'auto-bind';
|
||||||
|
import { stopCtxPropagation, moduleGet } from '../utils/UtilityFunctions';
|
||||||
|
import { ShipProps } from 'ed-forge';
|
||||||
|
const { CONSUMED_RETR, LADEN_MASS } = ShipProps;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Standard Slot section
|
* Standard Slot section
|
||||||
*/
|
*/
|
||||||
export default class StandardSlotSection extends SlotSection {
|
export default class StandardSlotSection extends SlotSection {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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) {
|
||||||
super(props, context, 'standard', 'core internal');
|
super(props, 'core internal');
|
||||||
this._optimizeStandard = this._optimizeStandard.bind(this);
|
autoBind(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.onCargoChange(this.props.ship.cargoCapacity);
|
|
||||||
this.props.onFuelChange(this.props.ship.fuelCapacity);
|
|
||||||
this._close();
|
this._close();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,12 +37,7 @@ 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.onCargoChange(this.props.ship.cargoCapacity);
|
|
||||||
this.props.onFuelChange(this.props.ship.fuelCapacity);
|
|
||||||
this._close();
|
this._close();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,11 +46,7 @@ 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.onCargoChange(this.props.ship.cargoCapacity);
|
|
||||||
this.props.onFuelChange(this.props.ship.fuelCapacity);
|
|
||||||
this._close();
|
this._close();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,11 +55,7 @@ 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.onCargoChange(this.props.ship.cargoCapacity);
|
|
||||||
this.props.onFuelChange(this.props.ship.fuelCapacity);
|
|
||||||
this._close();
|
this._close();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,12 +64,7 @@ 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.onCargoChange(this.props.ship.cargoCapacity);
|
|
||||||
this.props.onFuelChange(this.props.ship.fuelCapacity);
|
|
||||||
this._close();
|
this._close();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -103,22 +72,7 @@ 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.onCargoChange(this.props.ship.cargoCapacity);
|
|
||||||
this.props.onFuelChange(this.props.ship.fuelCapacity);
|
|
||||||
this._close();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Use the specified bulkhead
|
|
||||||
* @param {Object} bulkhead Bulkhead module details
|
|
||||||
*/
|
|
||||||
_selectBulkhead(bulkhead) {
|
|
||||||
this.props.ship.useBulkhead(bulkhead.index);
|
|
||||||
this.context.tooltip();
|
|
||||||
this.props.onChange();
|
|
||||||
this._close();
|
this._close();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -129,113 +83,48 @@ export default class StandardSlotSection extends SlotSection {
|
|||||||
this._optimizeStandard();
|
this._optimizeStandard();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new slot for a given module.
|
||||||
|
* @param {Module} m Module to create the slot for
|
||||||
|
* @param {function} warning Warning callback
|
||||||
|
* @return {React.Component} Slot component
|
||||||
|
*/
|
||||||
|
_mkSlot(m, warning) {
|
||||||
|
const { currentMenu, propsToShow, onPropToggle } = this.props;
|
||||||
|
return <Slot key={m.getSlot()} m={m} warning={warning} hideSearch={true}
|
||||||
|
currentMenu={currentMenu} propsToShow={propsToShow} onPropToggle={onPropToggle}
|
||||||
|
/>;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate the slot React Components
|
* Generate the slot React Components
|
||||||
* @return {Array} Array of Slots
|
* @return {Array} Array of Slots
|
||||||
*/
|
*/
|
||||||
_getSlots() {
|
_getSlots() {
|
||||||
let { ship, currentMenu, cargo, fuel } = this.props;
|
const { ship } = this.props;
|
||||||
let slots = new Array(8);
|
const fsd = ship.getFSD();
|
||||||
let open = this._openMenu;
|
return [
|
||||||
let select = this._selectModule;
|
this._mkSlot(ship.getAlloys()),
|
||||||
let st = ship.standard;
|
this._mkSlot(
|
||||||
let avail = ship.getAvailableModules().standard;
|
ship.getPowerPlant(),
|
||||||
let bh = ship.bulkheads;
|
(m) => moduleGet(m, 'powercapacity') < ship.get(CONSUMED_RETR),
|
||||||
|
),
|
||||||
slots[0] = <StandardSlot
|
this._mkSlot(
|
||||||
key='bh'
|
ship.getThrusters(),
|
||||||
slot={bh}
|
(m) => moduleGet(m, 'enginemaximalmass') < ship.get(LADEN_MASS),
|
||||||
modules={ship.getAvailableModules().bulkheads}
|
),
|
||||||
onOpen={open.bind(this, bh)}
|
this._mkSlot(fsd),
|
||||||
onSelect={this._selectBulkhead}
|
this._mkSlot(
|
||||||
selected={currentMenu == bh}
|
ship.getPowerDistributor(),
|
||||||
onChange={this.props.onChange}
|
(m) => moduleGet(m, 'enginescapacity') <= ship.readProp('boostenergy'),
|
||||||
ship={ship}
|
),
|
||||||
/>;
|
this._mkSlot(ship.getLifeSupport()),
|
||||||
|
this._mkSlot(ship.getSensors()),
|
||||||
slots[1] = <StandardSlot
|
this._mkSlot(
|
||||||
key='pp'
|
ship.getCoreFuelTank(),
|
||||||
slot={st[0]}
|
(m) => moduleGet(m, 'fuel') < fsd.get('maxfuel')
|
||||||
modules={avail[0]}
|
),
|
||||||
onOpen={open.bind(this, st[0])}
|
];
|
||||||
onSelect={select.bind(this, st[0])}
|
|
||||||
selected={currentMenu == st[0]}
|
|
||||||
onChange={this.props.onChange}
|
|
||||||
ship={ship}
|
|
||||||
warning={m => m instanceof Module ? m.getPowerGeneration() < ship.powerRetracted : m.pgen < ship.powerRetracted}
|
|
||||||
/>;
|
|
||||||
|
|
||||||
slots[2] = <StandardSlot
|
|
||||||
key='th'
|
|
||||||
slot={st[1]}
|
|
||||||
modules={avail[1]}
|
|
||||||
onOpen={open.bind(this, st[1])}
|
|
||||||
onSelect={select.bind(this, st[1])}
|
|
||||||
selected={currentMenu == st[1]}
|
|
||||||
onChange={this.props.onChange}
|
|
||||||
ship={ship}
|
|
||||||
warning={m => m instanceof Module ? m.getMaxMass() < (ship.unladenMass + cargo + fuel - st[1].m.mass + m.mass) : m.maxmass < (ship.unladenMass + cargo + fuel - st[1].m.mass + m.mass)}
|
|
||||||
/>;
|
|
||||||
|
|
||||||
|
|
||||||
slots[3] = <StandardSlot
|
|
||||||
key='fsd'
|
|
||||||
slot={st[2]}
|
|
||||||
modules={avail[2]}
|
|
||||||
onOpen={open.bind(this, st[2])}
|
|
||||||
onSelect={select.bind(this, st[2])}
|
|
||||||
onChange={this.props.onChange}
|
|
||||||
ship={ship}
|
|
||||||
selected={currentMenu == st[2]}
|
|
||||||
/>;
|
|
||||||
|
|
||||||
slots[4] = <StandardSlot
|
|
||||||
key='ls'
|
|
||||||
slot={st[3]}
|
|
||||||
modules={avail[3]}
|
|
||||||
onOpen={open.bind(this, st[3])}
|
|
||||||
onSelect={select.bind(this, st[3])}
|
|
||||||
onChange={this.props.onChange}
|
|
||||||
ship={ship}
|
|
||||||
selected={currentMenu == st[3]}
|
|
||||||
/>;
|
|
||||||
|
|
||||||
slots[5] = <StandardSlot
|
|
||||||
key='pd'
|
|
||||||
slot={st[4]}
|
|
||||||
modules={avail[4]}
|
|
||||||
onOpen={open.bind(this, st[4])}
|
|
||||||
onSelect={select.bind(this, st[4])}
|
|
||||||
selected={currentMenu == st[4]}
|
|
||||||
onChange={this.props.onChange}
|
|
||||||
ship={ship}
|
|
||||||
warning={m => m instanceof Module ? m.getEnginesCapacity() <= ship.boostEnergy : m.engcap <= ship.boostEnergy}
|
|
||||||
/>;
|
|
||||||
|
|
||||||
slots[6] = <StandardSlot
|
|
||||||
key='ss'
|
|
||||||
slot={st[5]}
|
|
||||||
modules={avail[5]}
|
|
||||||
onOpen={open.bind(this, st[5])}
|
|
||||||
onSelect={select.bind(this, st[5])}
|
|
||||||
selected={currentMenu == st[5]}
|
|
||||||
onChange={this.props.onChange}
|
|
||||||
ship={ship}
|
|
||||||
/>;
|
|
||||||
|
|
||||||
slots[7] = <StandardSlot
|
|
||||||
key='ft'
|
|
||||||
slot={st[6]}
|
|
||||||
modules={avail[6]}
|
|
||||||
onOpen={open.bind(this, st[6])}
|
|
||||||
onSelect={select.bind(this, st[6])}
|
|
||||||
selected={currentMenu == st[6]}
|
|
||||||
onChange={this.props.onChange}
|
|
||||||
ship={ship}
|
|
||||||
warning= {m => m.fuel < st[2].m.maxfuel} // Show warning when fuel tank is smaller than FSD Max Fuel
|
|
||||||
/>;
|
|
||||||
|
|
||||||
return slots;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -243,23 +132,22 @@ export default class StandardSlotSection extends SlotSection {
|
|||||||
* @param {Function} translate Translate function
|
* @param {Function} translate Translate function
|
||||||
* @return {React.Component} Section menu
|
* @return {React.Component} Section menu
|
||||||
*/
|
*/
|
||||||
_getSectionMenu(translate) {
|
_getSectionMenu() {
|
||||||
let planetaryDisabled = this.props.ship.internal.length < 4;
|
const { translate } = this.context.language;
|
||||||
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' tabIndex="0" 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' tabIndex="0" 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' tabIndex="0" 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' tabIndex="0" onClick={this._optimizeCargo.bind(this, true)}>{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' tabIndex="0" onClick={this._optimizeExplorer.bind(this, false)}>{translate('Explorer')}</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' tabIndex="0" onClick={this._optimizeExplorer.bind(this, true)}>{translate('Planetary 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='lc' tabIndex="0" onClick={this._optimizeMiner.bind(this, true)}>{translate('Miner')}</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' tabIndex="0" onClick={this._optimizeRacer.bind(this)}>{translate('Racer')}</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -247,7 +247,7 @@ export class OrbisIcon extends SvgIcon {
|
|||||||
<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="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" />
|
<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" />
|
<rect transform="matrix(-.7071 -.7071 .7071 -.7071 429.34 1036.2)" x="387.09" y="420.77" width="84.379" height="16.859" />
|
||||||
</g>);
|
</g>);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -741,9 +741,9 @@ export class Modified extends SvgIcon {
|
|||||||
*/
|
*/
|
||||||
svg() {
|
svg() {
|
||||||
return <g>
|
return <g>
|
||||||
<path d="M100,5L18,52.5L18,147.5L100,195L182,147.5L182,52.5L100,5Z"/>
|
<path d="M100,5L18,52.5L18,147.5L100,195L182,147.5L182,52.5L100,5Z"/>
|
||||||
<path d="M100,70L74,85L74,115L100,130L126,115L126,85L100,70Z"/>
|
<path d="M100,70L74,85L74,115L100,130L126,115L126,85L100,70Z"/>
|
||||||
</g>;
|
</g>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import TranslatedComponent from './TranslatedComponent';
|
|||||||
* Document Root Tooltip
|
* Document Root Tooltip
|
||||||
*/
|
*/
|
||||||
export default class Tooltip extends TranslatedComponent {
|
export default class Tooltip extends TranslatedComponent {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
rect: PropTypes.object.isRequired,
|
rect: PropTypes.object.isRequired,
|
||||||
options: PropTypes.object
|
options: PropTypes.object
|
||||||
@@ -127,5 +126,4 @@ export default class Tooltip extends TranslatedComponent {
|
|||||||
</div>
|
</div>
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import { shallowEqual } from '../utils/UtilityFunctions';
|
|||||||
* Abstract Translated Component
|
* Abstract Translated Component
|
||||||
*/
|
*/
|
||||||
export default class TranslatedComponent extends React.Component {
|
export default class TranslatedComponent extends React.Component {
|
||||||
|
|
||||||
static contextTypes = {
|
static contextTypes = {
|
||||||
language: PropTypes.object.isRequired,
|
language: PropTypes.object.isRequired,
|
||||||
sizeRatio: PropTypes.number.isRequired,
|
sizeRatio: PropTypes.number.isRequired,
|
||||||
|
|||||||
@@ -1,39 +1,26 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import cn from 'classnames';
|
|
||||||
import SlotSection from './SlotSection';
|
import SlotSection from './SlotSection';
|
||||||
import HardpointSlot from './HardpointSlot';
|
import Slot from './Slot';
|
||||||
import { stopCtxPropagation } from '../utils/UtilityFunctions';
|
import { stopCtxPropagation } from '../utils/UtilityFunctions';
|
||||||
|
import autoBind from 'auto-bind';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Utility Slot Section
|
* Utility Slot Section
|
||||||
*/
|
*/
|
||||||
export default class UtilitySlotSection extends SlotSection {
|
export default class UtilitySlotSection extends SlotSection {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor
|
* Constructor
|
||||||
* @param {Object} props React Component properties
|
* @param {Object} props React Component properties
|
||||||
* @param {Object} context React Component context
|
|
||||||
*/
|
*/
|
||||||
constructor(props, context) {
|
constructor(props) {
|
||||||
super(props, context, 'utility', 'utility mounts');
|
super(props, 'utility mounts');
|
||||||
this._empty = this._empty.bind(this);
|
autoBind(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 +34,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();
|
||||||
@@ -68,31 +52,24 @@ export default class UtilitySlotSection extends SlotSection {
|
|||||||
*/
|
*/
|
||||||
_getSlots() {
|
_getSlots() {
|
||||||
let slots = [];
|
let slots = [];
|
||||||
let { ship, currentMenu } = this.props;
|
let { ship, currentMenu, propsToShow, onPropToggle } = this.props;
|
||||||
let hardpoints = ship.hardpoints;
|
|
||||||
let { originSlot, targetSlot } = this.state;
|
let { originSlot, targetSlot } = this.state;
|
||||||
let availableModules = ship.getAvailableModules();
|
|
||||||
|
|
||||||
for (let i = 0, l = hardpoints.length; i < l; i++) {
|
for (let h of ship.getUtilities(undefined, true)) {
|
||||||
let h = hardpoints[i];
|
slots.push(<Slot
|
||||||
if (h.maxClass === 0) {
|
key={h.object.Slot}
|
||||||
slots.push(<HardpointSlot
|
maxClass={h.getSize()}
|
||||||
key={i}
|
onChange={this.props.onChange}
|
||||||
maxClass={h.maxClass}
|
currentMenu={currentMenu}
|
||||||
availableModules={() => availableModules.getHps(h.maxClass)}
|
drag={this._drag.bind(this, h)}
|
||||||
onOpen={this._openMenu.bind(this,h)}
|
dragOver={this._dragOverSlot.bind(this, h)}
|
||||||
onSelect={this._selectModule.bind(this, h)}
|
drop={this._drop}
|
||||||
onChange={this.props.onChange}
|
dropClass={this._dropClass(h, originSlot, targetSlot)}
|
||||||
selected={currentMenu == h}
|
m={h}
|
||||||
drag={this._drag.bind(this, h)}
|
enabled={h.enabled ? true : false}
|
||||||
dragOver={this._dragOverSlot.bind(this, h)}
|
propsToShow={propsToShow}
|
||||||
drop={this._drop}
|
onPropToggle={onPropToggle}
|
||||||
dropClass={this._dropClass(h, originSlot, targetSlot)}
|
/>);
|
||||||
ship={ship}
|
|
||||||
m={h.m}
|
|
||||||
enabled={h.enabled ? true : false}
|
|
||||||
/>);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return slots;
|
return slots;
|
||||||
@@ -103,35 +80,35 @@ export default class UtilitySlotSection extends SlotSection {
|
|||||||
* @param {Function} translate Translate function
|
* @param {Function} translate Translate function
|
||||||
* @return {React.Component} Section menu
|
* @return {React.Component} Section menu
|
||||||
*/
|
*/
|
||||||
_getSectionMenu(translate) {
|
_getSectionMenu() {
|
||||||
|
const { translate } = this.context.language;
|
||||||
let _use = this._use;
|
let _use = this._use;
|
||||||
|
|
||||||
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' tabIndex='0' 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' tabIndex='0' 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' tabIndex='0' 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' tabIndex='0' 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' tabIndex='0' 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' tabIndex='0' 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' tabIndex='0' 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' tabIndex='0' 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' tabIndex='0' onClick={_use.bind(this, 'po', null, 'Point Defence')}>{translate('Point Defence')}</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
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 ContainerDimensions from 'react-container-dimensions';
|
||||||
import { BarChart, Bar, XAxis, YAxis } from 'recharts';
|
import { BarChart, Bar, XAxis, YAxis, LabelList } from 'recharts';
|
||||||
|
|
||||||
const CORIOLIS_COLOURS = ['#FF8C0D', '#1FB0FF', '#71A052', '#D5D54D'];
|
const CORIOLIS_COLOURS = ['#FF8C0D', '#1FB0FF', '#71A052', '#D5D54D'];
|
||||||
const LABEL_COLOUR = '#000000';
|
const LABEL_COLOUR = '#000000';
|
||||||
@@ -17,7 +17,6 @@ const merge = function(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
|
||||||
@@ -54,7 +53,9 @@ export default class VerticalBarChart extends TranslatedComponent {
|
|||||||
<BarChart width={width} height={width * ASPECT} data={this.props.data} margin={{ top: 5, right: 5, left: 5, bottom: 5 }}>
|
<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' />
|
<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]}/>
|
<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)}/>
|
<Bar dataKey='value' fill={CORIOLIS_COLOURS[0]} isAnimationActive={false} onMouseOver={this._termtip} onMouseOut={tooltip.bind(null, null)}>
|
||||||
|
<LabelList dataKey='value' position='insideTop'/>
|
||||||
|
</Bar>
|
||||||
</BarChart>
|
</BarChart>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -77,29 +78,3 @@ export default class VerticalBarChart extends TranslatedComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* A label that displays the value within the bar of the chart
|
|
||||||
*/
|
|
||||||
class ValueLabel extends React.Component {
|
|
||||||
static propTypes = {
|
|
||||||
x: PropTypes.number,
|
|
||||||
y: PropTypes.number,
|
|
||||||
payload: PropTypes.object,
|
|
||||||
value: PropTypes.number
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Render offence
|
|
||||||
* @return {React.Component} contents
|
|
||||||
*/
|
|
||||||
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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -1,200 +1,101 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import TranslatedComponent from './TranslatedComponent';
|
import TranslatedComponent from './TranslatedComponent';
|
||||||
import { Ships } from 'coriolis-data/dist';
|
|
||||||
import { nameComparator } from '../utils/SlotFunctions';
|
|
||||||
import { CollapseSection, ExpandSection, MountFixed, MountGimballed, MountTurret } from './SvgIcons';
|
|
||||||
import LineChart from '../components/LineChart';
|
import LineChart from '../components/LineChart';
|
||||||
import Slider from '../components/Slider';
|
|
||||||
import * as Calc from '../shipyard/Calculations';
|
import * as Calc from '../shipyard/Calculations';
|
||||||
import Module from '../shipyard/Module';
|
import { moduleReduce } from 'ed-forge/lib/helper';
|
||||||
|
import { chain, keys, mapValues, values } from 'lodash';
|
||||||
|
|
||||||
const DAMAGE_DEALT_COLORS = ['#FFFFFF', '#FF0000', '#00FF00', '#7777FF', '#FFFF00', '#FF00FF', '#00FFFF', '#777777'];
|
const DAMAGE_DEALT_COLORS = ['#FFFFFF', '#FF0000', '#00FF00', '#7777FF', '#FFFF00', '#FF00FF', '#00FFFF', '#777777'];
|
||||||
|
const PORTION_MAPPINGS = {
|
||||||
|
'absolute': 'absolutedamageportion',
|
||||||
|
'explosive': 'explosivedamageportion',
|
||||||
|
'kinetic': 'kineticdamageportion',
|
||||||
|
'thermal': 'thermicdamageportion',
|
||||||
|
};
|
||||||
|
const MULTS = keys(PORTION_MAPPINGS);
|
||||||
|
|
||||||
|
// TODO: help with this in ed-forge
|
||||||
|
/**
|
||||||
|
* .
|
||||||
|
* @param {Object} opponentDefence .
|
||||||
|
* @returns {Object} .
|
||||||
|
*/
|
||||||
|
function defenceToMults(opponentDefence) {
|
||||||
|
return chain(opponentDefence)
|
||||||
|
.pick(MULTS)
|
||||||
|
.mapKeys((v, k) => PORTION_MAPPINGS[k])
|
||||||
|
.mapValues((resistanceProfile) => resistanceProfile.damageMultiplier)
|
||||||
|
.value();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Weapon damage chart
|
* Weapon damage chart
|
||||||
*/
|
*/
|
||||||
export default class WeaponDamageChart extends TranslatedComponent {
|
export default class WeaponDamageChart extends TranslatedComponent {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
|
code: PropTypes.string.isRequired,
|
||||||
ship: PropTypes.object.isRequired,
|
ship: PropTypes.object.isRequired,
|
||||||
opponent: PropTypes.object.isRequired,
|
opponentDefence: PropTypes.object.isRequired,
|
||||||
hull: PropTypes.bool.isRequired,
|
|
||||||
engagementRange: PropTypes.number.isRequired,
|
engagementRange: PropTypes.number.isRequired,
|
||||||
opponentSys: PropTypes.number.isRequired,
|
|
||||||
marker: PropTypes.string.isRequired
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructor
|
|
||||||
* @param {Object} props React Component properties
|
|
||||||
* @param {Object} context React Component context
|
|
||||||
*/
|
|
||||||
constructor(props, context) {
|
|
||||||
super(props);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the initial weapons state
|
|
||||||
*/
|
|
||||||
componentWillMount() {
|
|
||||||
const weaponNames = this._weaponNames(this.props.ship, this.context);
|
|
||||||
const opponentShields = Calc.shieldMetrics(this.props.opponent, this.props.opponentSys);
|
|
||||||
const opponentArmour = Calc.armourMetrics(this.props.opponent);
|
|
||||||
const maxRange = this._calcMaxRange(this.props.ship);
|
|
||||||
const maxDps = this._calcMaxSDps(this.props.ship, this.props.opponent, opponentShields, opponentArmour);
|
|
||||||
|
|
||||||
this.setState({ maxRange, maxDps, weaponNames, opponentShields, opponentArmour, calcSDpsFunc: this._calcSDps.bind(this, this.props.ship, weaponNames, this.props.opponent, opponentShields, opponentArmour, this.props.hull) });
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the updated weapons state if our ship changes
|
|
||||||
* @param {Object} nextProps Incoming/Next properties
|
|
||||||
* @param {Object} nextContext Incoming/Next conext
|
|
||||||
* @return {boolean} Returns true if the component should be rerendered
|
|
||||||
*/
|
|
||||||
componentWillReceiveProps(nextProps, nextContext) {
|
|
||||||
if (nextProps.marker != this.props.marker) {
|
|
||||||
const weaponNames = this._weaponNames(nextProps.ship, nextContext);
|
|
||||||
const opponentShields = Calc.shieldMetrics(nextProps.opponent, nextProps.opponentSys);
|
|
||||||
const opponentArmour = Calc.armourMetrics(nextProps.opponent);
|
|
||||||
const maxRange = this._calcMaxRange(nextProps.ship);
|
|
||||||
const maxDps = this._calcMaxSDps(nextProps.ship, nextProps.opponent, opponentShields, opponentArmour);
|
|
||||||
this.setState({ weaponNames,
|
|
||||||
opponentShields,
|
|
||||||
opponentArmour,
|
|
||||||
maxRange,
|
|
||||||
maxDps,
|
|
||||||
calcSDpsFunc: this._calcSDps.bind(this, nextProps.ship, weaponNames, nextProps.opponent, opponentShields, opponentArmour, nextProps.hull)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Calculate the maximum range of a ship's weapons
|
|
||||||
* @param {Object} ship The ship
|
|
||||||
* @returns {int} The maximum range, in metres
|
|
||||||
*/
|
|
||||||
_calcMaxRange(ship) {
|
|
||||||
let maxRange = 1000; // Minimum
|
|
||||||
for (let i = 0; i < ship.hardpoints.length; i++) {
|
|
||||||
if (ship.hardpoints[i].maxClass > 0 && ship.hardpoints[i].m && ship.hardpoints[i].enabled) {
|
|
||||||
const thisRange = ship.hardpoints[i].m.getRange();
|
|
||||||
if (thisRange > maxRange) {
|
|
||||||
maxRange = thisRange;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return maxRange;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Calculate the maximum sustained single-weapon DPS for this ship
|
|
||||||
* @param {Object} ship The ship
|
|
||||||
* @param {Object} opponent The opponent ship
|
|
||||||
* @param {Object} opponentShields The opponent's shields
|
|
||||||
* @param {Object} opponentArmour The opponent's armour
|
|
||||||
* @return {number} The maximum sustained single-weapon DPS
|
|
||||||
*/
|
|
||||||
_calcMaxSDps(ship, opponent, opponentShields, opponentArmour) {
|
|
||||||
// Additional information to allow effectiveness calculations
|
|
||||||
let maxSDps = 0;
|
|
||||||
for (let i = 0; i < ship.hardpoints.length; i++) {
|
|
||||||
if (ship.hardpoints[i].maxClass > 0 && ship.hardpoints[i].m && ship.hardpoints[i].enabled) {
|
|
||||||
const m = ship.hardpoints[i].m;
|
|
||||||
|
|
||||||
const sustainedDps = Calc._weaponSustainedDps(m, opponent, opponentShields, opponentArmour, 0);
|
|
||||||
const thisSDps = sustainedDps.damage.armour.total > sustainedDps.damage.shields.total ? sustainedDps.damage.armour.total : sustainedDps.damage.shields.total;
|
|
||||||
if (thisSDps > maxSDps) {
|
|
||||||
maxSDps = thisSDps;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return maxSDps;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Obtain the weapon names for this ship
|
|
||||||
* @param {Object} ship The ship
|
|
||||||
* @param {Object} context The context
|
|
||||||
* @return {array} The weapon names
|
|
||||||
*/
|
|
||||||
_weaponNames(ship, context) {
|
|
||||||
const translate = context.language.translate;
|
|
||||||
let names = [];
|
|
||||||
let num = 1;
|
|
||||||
for (let i = 0; i < ship.hardpoints.length; i++) {
|
|
||||||
if (ship.hardpoints[i].maxClass > 0 && ship.hardpoints[i].m && ship.hardpoints[i].enabled) {
|
|
||||||
const m = ship.hardpoints[i].m;
|
|
||||||
let name = '' + num++ + ': ' + m.class + m.rating + (m.missile ? '/' + m.missile : '') + ' ' + translate(m.name || m.grp);
|
|
||||||
let engineering;
|
|
||||||
if (m.blueprint && m.blueprint.name) {
|
|
||||||
engineering = translate(m.blueprint.name) + ' ' + translate('grade') + ' ' + m.blueprint.grade;
|
|
||||||
if (m.blueprint.special && m.blueprint.special.id) {
|
|
||||||
engineering += ', ' + translate(m.blueprint.special.name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (engineering) {
|
|
||||||
name = name + ' (' + engineering + ')';
|
|
||||||
}
|
|
||||||
names.push(name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return names;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Calculate the per-weapon sustained DPS for this ship against another ship at a given range
|
|
||||||
* @param {Object} ship The ship
|
|
||||||
* @param {Object} weaponNames The names of the weapons for which to calculate DPS
|
|
||||||
* @param {Object} opponent The target
|
|
||||||
* @param {Object} opponentShields The opponent's shields
|
|
||||||
* @param {Object} opponentArmour The opponent's armour
|
|
||||||
* @param {bool} hull true if to calculate against hull, false if to calculate against shields
|
|
||||||
* @param {Object} engagementRange The engagement range
|
|
||||||
* @return {array} The array of weapon DPS
|
|
||||||
*/
|
|
||||||
_calcSDps(ship, weaponNames, opponent, opponentShields, opponentArmour, hull, engagementRange) {
|
|
||||||
let results = {};
|
|
||||||
let weaponNum = 0;
|
|
||||||
for (let i = 0; i < ship.hardpoints.length; i++) {
|
|
||||||
if (ship.hardpoints[i].maxClass > 0 && ship.hardpoints[i].m && ship.hardpoints[i].enabled) {
|
|
||||||
const m = ship.hardpoints[i].m;
|
|
||||||
const sustainedDps = Calc._weaponSustainedDps(m, opponent, opponentShields, opponentArmour, engagementRange);
|
|
||||||
results[weaponNames[weaponNum++]] = hull ? sustainedDps.damage.armour.total : sustainedDps.damage.shields.total;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return results;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Render damage dealt
|
* Render damage dealt
|
||||||
* @return {React.Component} contents
|
* @return {React.Component} contents
|
||||||
*/
|
*/
|
||||||
render() {
|
render() {
|
||||||
const { language, onWindowResize, sizeRatio, tooltip, termtip } = this.context;
|
const { language } = this.context;
|
||||||
const { formats, translate, units } = language;
|
const { translate } = language;
|
||||||
const { maxRange } = this.state;
|
const { code, ship, opponentDefence, engagementRange } = this.props;
|
||||||
const { ship, opponent, engagementRange } = this.props;
|
|
||||||
|
|
||||||
const sortOrder = this._sortOrder;
|
const hardpoints = ship.getHardpoints();
|
||||||
const onCollapseExpand = this._onCollapseExpand;
|
const hardpointsMap = chain(hardpoints)
|
||||||
|
.map((m) => [m.getSlot(), m])
|
||||||
const code = `${ship.toString()}:${opponent.toString()}`;
|
.fromPairs()
|
||||||
|
.value();
|
||||||
|
const mults = defenceToMults(opponentDefence);
|
||||||
|
const cb = (range) => {
|
||||||
|
return mapValues(
|
||||||
|
hardpointsMap,
|
||||||
|
(m) => {
|
||||||
|
const sdps = m.get('sustaineddamagepersecond', true);
|
||||||
|
const resistanceMul = chain(mults)
|
||||||
|
.toPairs()
|
||||||
|
.map((pair) => {
|
||||||
|
const [k, mul] = pair;
|
||||||
|
return m.get(k, true) * mul;
|
||||||
|
})
|
||||||
|
.sum()
|
||||||
|
.value();
|
||||||
|
|
||||||
|
const falloff = m.get('damagefalloffrange', true);
|
||||||
|
const rangeMul = Math.min(1, Math.max(0,
|
||||||
|
1 - (range - falloff) / (m.get('maximumrange', true) - falloff)
|
||||||
|
));
|
||||||
|
return sdps * resistanceMul * rangeMul;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<LineChart
|
<LineChart
|
||||||
xMax={maxRange}
|
xMin={0}
|
||||||
yMax={this.state.maxDps}
|
xMax={moduleReduce(
|
||||||
|
hardpoints, 'maximumrange', true, (a, v) => Math.max(a, v), 1000,
|
||||||
|
)}
|
||||||
|
yMin={0}
|
||||||
|
// Factor in highest damage multiplier to get a safe upper bound
|
||||||
|
yMax={Math.max(1, ...values(mults)) * moduleReduce(
|
||||||
|
hardpoints, 'sustaineddamagepersecond', true, (a, v) => Math.max(a, v), 0,
|
||||||
|
)}
|
||||||
xLabel={translate('range')}
|
xLabel={translate('range')}
|
||||||
xUnit={translate('m')}
|
xUnit={translate('m')}
|
||||||
yLabel={translate('sdps')}
|
yLabel={translate('sustaineddamagepersecond')}
|
||||||
series={this.state.weaponNames}
|
series={hardpoints.map((m) => m.getSlot())}
|
||||||
xMark={this.props.engagementRange}
|
xMark={engagementRange}
|
||||||
colors={DAMAGE_DEALT_COLORS}
|
colors={DAMAGE_DEALT_COLORS}
|
||||||
func={this.state.calcSDpsFunc}
|
func={cb}
|
||||||
points={200}
|
points={200}
|
||||||
code={code}
|
code={code}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ 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 PT from './pt';
|
||||||
|
import * as CN from './cn';
|
||||||
import * as d3 from 'd3';
|
import * as d3 from 'd3';
|
||||||
|
|
||||||
let fallbackTerms = EN.terms;
|
let fallbackTerms = EN.terms;
|
||||||
@@ -27,6 +28,7 @@ export function getLanguage(langCode) {
|
|||||||
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;
|
case 'pt': lang = PT; break;
|
||||||
|
case 'cn': lang = CN; break;
|
||||||
default:
|
default:
|
||||||
lang = EN;
|
lang = EN;
|
||||||
}
|
}
|
||||||
@@ -94,5 +96,6 @@ export const Languages = {
|
|||||||
fr: 'Français',
|
fr: 'Français',
|
||||||
ru: 'ру́сский',
|
ru: 'ру́сский',
|
||||||
pl: 'polski',
|
pl: 'polski',
|
||||||
pt: 'português'
|
pt: 'português',
|
||||||
|
cn: '中文'
|
||||||
};
|
};
|
||||||
|
|||||||
16
src/app/i18n/cn.js
Normal file
16
src/app/i18n/cn.js
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
export const formats = {
|
||||||
|
decimal: '.',
|
||||||
|
thousands: ',',
|
||||||
|
grouping: [3],
|
||||||
|
currency: ['¥', ''],
|
||||||
|
dateTime: '%a %b %e %X %Y',
|
||||||
|
date: '%Y年%m月%d日',
|
||||||
|
time: '%H:%M:%S',
|
||||||
|
periods: ['AM', 'PM'],
|
||||||
|
days: ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六'],
|
||||||
|
shortDays: ['周日', '周一', '周二', '周三', '周四', '周五', '周六'],
|
||||||
|
months: ['一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月'],
|
||||||
|
shortMonths: ['一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月']
|
||||||
|
};
|
||||||
|
|
||||||
|
export { default as terms } from './cn.json';
|
||||||
405
src/app/i18n/cn.json
Normal file
405
src/app/i18n/cn.json
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -59,7 +59,7 @@
|
|||||||
"empty all": "vide tout",
|
"empty all": "vide tout",
|
||||||
"Enter Name": "Entrer nom",
|
"Enter Name": "Entrer nom",
|
||||||
"Explorer": "explorateur",
|
"Explorer": "explorateur",
|
||||||
"fastest range": "gamme la plus rapide",
|
"farthest range": "gamme la plus rapide",
|
||||||
"fuel": "carburant",
|
"fuel": "carburant",
|
||||||
"fuel level": "niveau de carburant",
|
"fuel level": "niveau de carburant",
|
||||||
"full tank": "Réservoir plein",
|
"full tank": "Réservoir plein",
|
||||||
|
|||||||
@@ -299,7 +299,7 @@
|
|||||||
"edit data": "Редактирование",
|
"edit data": "Редактирование",
|
||||||
"empty all": "пусто все",
|
"empty all": "пусто все",
|
||||||
"Enter Name": "Введите имя",
|
"Enter Name": "Введите имя",
|
||||||
"fastest range": "быстрый диапазон",
|
"farthest range": "быстрый диапазон",
|
||||||
"fuel level": "уровень топлива",
|
"fuel level": "уровень топлива",
|
||||||
"full tank": "Полный бак",
|
"full tank": "Полный бак",
|
||||||
"internal compartments": "внутренние отсеки",
|
"internal compartments": "внутренние отсеки",
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import 'babel-polyfill';
|
import '@babel/polyfill';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { render } from 'react-dom';
|
import { render } from 'react-dom';
|
||||||
import '../less/app.less';
|
import '../less/app.less';
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import { CoriolisLogo, GitHub } from '../components/SvgIcons';
|
|||||||
* About Page
|
* About Page
|
||||||
*/
|
*/
|
||||||
export default class AboutPage extends Page {
|
export default class AboutPage extends Page {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor
|
* Constructor
|
||||||
* @param {Object} props React Component properties
|
* @param {Object} props React Component properties
|
||||||
@@ -23,33 +22,112 @@ export default class AboutPage extends Page {
|
|||||||
* @return {React.Component} The page contents
|
* @return {React.Component} The page contents
|
||||||
*/
|
*/
|
||||||
renderPage() {
|
renderPage() {
|
||||||
return <div className={'page'} style={{ textAlign: 'left', maxWidth: 800, margin: '0 auto' }}>
|
return (
|
||||||
<h1><CoriolisLogo style={{ marginRight: '0.4em' }} className='xl'/><span className='warning'>Coriolis EDCD Edition</span></h1>
|
<div
|
||||||
|
className={'page'}
|
||||||
|
style={{ textAlign: 'left', maxWidth: 800, margin: '0 auto' }}
|
||||||
|
>
|
||||||
|
<h1>
|
||||||
|
<CoriolisLogo style={{ marginRight: '0.4em' }} className="xl" />
|
||||||
|
<span className="warning">Coriolis EDCD Edition</span>
|
||||||
|
</h1>
|
||||||
|
|
||||||
<p>This is a clone of the Coriolis project, whose original author is currently unable to maintain it. This clone is maintained by the <a href="http://edcd.github.io/">EDCD community</a>.</p>
|
<p>
|
||||||
<p>To recover your builds, go to <a href='https://coriolis.io/' target='_blank'>https://coriolis.io/</a>, backup your builds (Settings / Backup), copy the text, return here and import (Settings / Import).</p>
|
This is a clone of the Coriolis project, whose original author is
|
||||||
<p>The Coriolis project was inspired by <a href='http://www.edshipyard.com/' target='_blank'>E:D Shipyard</a> and, of course, <a href='http://www.elitedangerous.com' target='_blank'>Elite Dangerous</a>. The ultimate goal of Coriolis is to provide rich features to support in-game play and planning while engaging the E:D community to support its development.</p>
|
currently unable to maintain it. This clone is maintained by the{' '}
|
||||||
<p>Coriolis was created using assets and imagery from Elite: Dangerous, with the permission of Frontier Developments plc, for non-commercial purposes. It is not endorsed by nor reflects the views or opinions of Frontier Developments. A number of assets were sourced from <a href='http://edassets.org' target='_blank'>ED Assets</a></p>
|
<a href="http://edcd.github.io/">EDCD community</a>.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
To recover your builds, go to{' '}
|
||||||
|
<a href="https://coriolis.io/" target="_blank">
|
||||||
|
https://coriolis.io/
|
||||||
|
</a>
|
||||||
|
, backup your builds (Settings / Backup), copy the text, return here
|
||||||
|
and import (Settings / Import).
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
The Coriolis project was inspired by{' '}
|
||||||
|
<a href="http://www.edshipyard.com/" target="_blank">
|
||||||
|
E:D Shipyard
|
||||||
|
</a>{' '}
|
||||||
|
and, of course,{' '}
|
||||||
|
<a href="http://www.elitedangerous.com" target="_blank">
|
||||||
|
Elite Dangerous
|
||||||
|
</a>
|
||||||
|
. The ultimate goal of Coriolis is to provide rich features to support
|
||||||
|
in-game play and planning while engaging the E:D community to support
|
||||||
|
its development.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Coriolis was created using assets and imagery from Elite: Dangerous,
|
||||||
|
with the permission of Frontier Developments plc, for non-commercial
|
||||||
|
purposes. It is not endorsed by nor reflects the views or opinions of
|
||||||
|
Frontier Developments. A number of assets were sourced from{' '}
|
||||||
|
<a href="http://edassets.org" target="_blank">
|
||||||
|
ED Assets
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
<a style={{ display: 'block', textDecoration: 'none' }} href='https://github.com/EDCD/coriolis' target='_blank' title='Coriolis Github Project'>
|
<a
|
||||||
<GitHub style={{ margin: '0.4em' }} className='l fg xl'/>
|
style={{ display: 'block', textDecoration: 'none' }}
|
||||||
<h2 style={{ margin: 0, textDecoration: 'none' }}>Github</h2>
|
href="https://github.com/EDCD/coriolis"
|
||||||
github.com/EDCD/coriolis
|
target="_blank"
|
||||||
</a>
|
title="Coriolis Github Project"
|
||||||
|
>
|
||||||
|
<GitHub style={{ margin: '0.4em' }} className="l fg xl" />
|
||||||
|
<h2 style={{ margin: 0, textDecoration: 'none' }}>Github</h2>
|
||||||
|
github.com/EDCD/coriolis
|
||||||
|
</a>
|
||||||
|
|
||||||
<p>Coriolis is an open source project. Checkout the list of upcoming features and to-do list on github. Any and all contributions and feedback are welcome. If you encounter any bugs please report them and provide as much detail as possible.</p>
|
<p>
|
||||||
|
Coriolis is an open source project. Checkout the list of upcoming
|
||||||
|
features and to-do list on github. Any and all contributions and
|
||||||
|
feedback are welcome. If you encounter any bugs please report them and
|
||||||
|
provide as much detail as possible.
|
||||||
|
</p>
|
||||||
|
|
||||||
<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>
|
<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>
|
<p>
|
||||||
<form action="https://www.paypal.com/cgi-bin/webscr" method="post" target="_top">
|
Coriolis is an open source project, and I work on it in my free time.
|
||||||
<input type="hidden" name="cmd" value="_s-xclick"/>
|
I have set up a patreon at{' '}
|
||||||
<input type="hidden" name="hosted_button_id" value="SJBKT2SWEEU68" />
|
<a href="https://www.patreon.com/coriolis_elite">
|
||||||
<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!" />
|
patreon.com/coriolis_elite
|
||||||
<img alt="" border="0" src="https://www.paypalobjects.com/en_AU/i/scr/pixel.gif" width="1" height="1" />
|
</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>
|
</form>
|
||||||
</div>;
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,14 @@ import ModalCompare from '../components/ModalCompare';
|
|||||||
import ModalExport from '../components/ModalExport';
|
import ModalExport from '../components/ModalExport';
|
||||||
import ModalPermalink from '../components/ModalPermalink';
|
import ModalPermalink from '../components/ModalPermalink';
|
||||||
import ModalImport from '../components/ModalImport';
|
import ModalImport from '../components/ModalImport';
|
||||||
import { FloppyDisk, Bin, Download, Embed, Rocket, LinkIcon } from '../components/SvgIcons';
|
import {
|
||||||
|
FloppyDisk,
|
||||||
|
Bin,
|
||||||
|
Download,
|
||||||
|
Embed,
|
||||||
|
Rocket,
|
||||||
|
LinkIcon
|
||||||
|
} from '../components/SvgIcons';
|
||||||
import ShortenUrl from '../utils/ShortenUrl';
|
import ShortenUrl from '../utils/ShortenUrl';
|
||||||
import { comparisonBBCode } from '../utils/BBCode';
|
import { comparisonBBCode } from '../utils/BBCode';
|
||||||
const browser = require('detect-browser');
|
const browser = require('detect-browser');
|
||||||
@@ -42,7 +49,6 @@ function sortBy(predicate) {
|
|||||||
* Comparison Page
|
* Comparison Page
|
||||||
*/
|
*/
|
||||||
export default class ComparisonPage extends Page {
|
export default class ComparisonPage extends Page {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor
|
* Constructor
|
||||||
* @param {Object} props React Component properties
|
* @param {Object} props React Component properties
|
||||||
@@ -81,7 +87,13 @@ export default class ComparisonPage extends Page {
|
|||||||
for (let shipId in allBuilds) {
|
for (let shipId in allBuilds) {
|
||||||
for (let buildName in allBuilds[shipId]) {
|
for (let buildName in allBuilds[shipId]) {
|
||||||
if (buildName && allBuilds[shipId][buildName]) {
|
if (buildName && allBuilds[shipId][buildName]) {
|
||||||
builds.push(this._createBuild(shipId, buildName, allBuilds[shipId][buildName]));
|
builds.push(
|
||||||
|
this._createBuild(
|
||||||
|
shipId,
|
||||||
|
buildName,
|
||||||
|
allBuilds[shipId][buildName]
|
||||||
|
)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -89,7 +101,9 @@ export default class ComparisonPage extends Page {
|
|||||||
let comparisonData = Persist.getComparison(name);
|
let comparisonData = Persist.getComparison(name);
|
||||||
if (comparisonData) {
|
if (comparisonData) {
|
||||||
defaultFacets = comparisonData.facets;
|
defaultFacets = comparisonData.facets;
|
||||||
comparisonData.builds.forEach((b) => builds.push(this._createBuild(b.shipId, b.buildName)));
|
comparisonData.builds.forEach(b =>
|
||||||
|
builds.push(this._createBuild(b.shipId, b.buildName))
|
||||||
|
);
|
||||||
saved = true;
|
saved = true;
|
||||||
newName = name;
|
newName = name;
|
||||||
}
|
}
|
||||||
@@ -101,7 +115,7 @@ export default class ComparisonPage extends Page {
|
|||||||
newName = name = comparisonData.n;
|
newName = name = comparisonData.n;
|
||||||
predicate = comparisonData.p;
|
predicate = comparisonData.p;
|
||||||
desc = comparisonData.d;
|
desc = comparisonData.d;
|
||||||
comparisonData.b.forEach((build) => {
|
comparisonData.b.forEach(build => {
|
||||||
builds.push(this._createBuild(build.s, build.n, build.c));
|
builds.push(this._createBuild(build.s, build.n, build.c));
|
||||||
if (!importObj[build.s]) {
|
if (!importObj[build.s]) {
|
||||||
importObj[build.s] = {};
|
importObj[build.s] = {};
|
||||||
@@ -118,9 +132,9 @@ export default class ComparisonPage extends Page {
|
|||||||
let selectedFacets = new Array(selectedLength);
|
let selectedFacets = new Array(selectedLength);
|
||||||
|
|
||||||
for (let i = 0; i < ShipFacets.length; i++) {
|
for (let i = 0; i < ShipFacets.length; i++) {
|
||||||
let facet = Object.assign({ }, ShipFacets[i]);
|
let facet = Object.assign({}, ShipFacets[i]);
|
||||||
let defaultIndex = defaultFacets.indexOf(facet.i);
|
let defaultIndex = defaultFacets.indexOf(facet.i);
|
||||||
if(defaultIndex == -1) {
|
if (defaultIndex == -1) {
|
||||||
facets.push(facet);
|
facets.push(facet);
|
||||||
} else {
|
} else {
|
||||||
facet.active = true;
|
facet.active = true;
|
||||||
@@ -155,17 +169,18 @@ export default class ComparisonPage extends Page {
|
|||||||
_createBuild(id, name, code) {
|
_createBuild(id, name, code) {
|
||||||
code = code ? code : Persist.getBuild(id, name); // Retrieve build code if not passed
|
code = code ? code : Persist.getBuild(id, name); // Retrieve build code if not passed
|
||||||
|
|
||||||
if (!code) { // No build found
|
if (!code) {
|
||||||
|
// No build found
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let data = Ships[id]; // Get ship properties
|
let data = Ships[id]; // Get ship properties
|
||||||
let b = new Ship(id, data.properties, data.slots); // Create a new Ship instance
|
let b = new Ship(id, data.properties, data.slots); // Create a new Ship instance
|
||||||
b.buildFrom(code); // Populate components from code
|
b.buildFrom(code); // Populate components from code
|
||||||
b.buildName = name;
|
b.buildName = name;
|
||||||
b.applyDiscounts(Persist.getShipDiscount(), Persist.getModuleDiscount());
|
b.applyDiscounts(Persist.getShipDiscount(), Persist.getModuleDiscount());
|
||||||
return b;
|
return b;
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update state with the specified sort predicates
|
* Update state with the specified sort predicates
|
||||||
@@ -184,13 +199,18 @@ export default class ComparisonPage extends Page {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.setState({ predicate, desc });
|
this.setState({ predicate, desc });
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Show selected builds modal
|
* Show selected builds modal
|
||||||
*/
|
*/
|
||||||
_selectBuilds() {
|
_selectBuilds() {
|
||||||
this.context.showModal(<ModalCompare onSelect={this._buildsSelected} builds={this.state.builds} />);
|
this.context.showModal(
|
||||||
|
<ModalCompare
|
||||||
|
onSelect={this._buildsSelected}
|
||||||
|
builds={this.state.builds}
|
||||||
|
/>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -224,7 +244,7 @@ export default class ComparisonPage extends Page {
|
|||||||
_facetDrag(e) {
|
_facetDrag(e) {
|
||||||
this.nodeAfter = false;
|
this.nodeAfter = false;
|
||||||
this.dragged = e.currentTarget;
|
this.dragged = e.currentTarget;
|
||||||
let placeholder = this.placeholder = document.createElement('li');
|
let placeholder = (this.placeholder = document.createElement('li'));
|
||||||
placeholder.style.width = Math.round(this.dragged.offsetWidth) + 'px';
|
placeholder.style.width = Math.round(this.dragged.offsetWidth) + 'px';
|
||||||
placeholder.className = 'facet-placeholder';
|
placeholder.className = 'facet-placeholder';
|
||||||
if (!browser || (browser.name !== 'edge' && browser.name !== 'ie')) {
|
if (!browser || (browser.name !== 'edge' && browser.name !== 'ie')) {
|
||||||
@@ -262,7 +282,7 @@ export default class ComparisonPage extends Page {
|
|||||||
_facetDragOver(e) {
|
_facetDragOver(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
if(e.target.className == 'facet-placeholder') {
|
if (e.target.className == 'facet-placeholder') {
|
||||||
return;
|
return;
|
||||||
} else if (e.target != e.currentTarget) {
|
} else if (e.target != e.currentTarget) {
|
||||||
this.over = e.target;
|
this.over = e.target;
|
||||||
@@ -272,7 +292,7 @@ export default class ComparisonPage extends Page {
|
|||||||
let parent = e.target.parentNode;
|
let parent = e.target.parentNode;
|
||||||
|
|
||||||
if (parent == e.currentTarget) {
|
if (parent == e.currentTarget) {
|
||||||
if(relX > width && this.dragged != e.target) {
|
if (relX > width && this.dragged != e.target) {
|
||||||
this.nodeAfter = true;
|
this.nodeAfter = true;
|
||||||
parent.insertBefore(this.placeholder, e.target.nextElementSibling);
|
parent.insertBefore(this.placeholder, e.target.nextElementSibling);
|
||||||
} else {
|
} else {
|
||||||
@@ -321,7 +341,7 @@ export default class ComparisonPage extends Page {
|
|||||||
let { newName, builds, facets } = this.state;
|
let { newName, builds, facets } = this.state;
|
||||||
let selectedFacets = [];
|
let selectedFacets = [];
|
||||||
|
|
||||||
facets.forEach((f) => {
|
facets.forEach(f => {
|
||||||
if (f.active) {
|
if (f.active) {
|
||||||
selectedFacets.unshift(f.i);
|
selectedFacets.unshift(f.i);
|
||||||
}
|
}
|
||||||
@@ -348,14 +368,20 @@ export default class ComparisonPage extends Page {
|
|||||||
|
|
||||||
let code = fromComparison(name, builds, selectedFacets, predicate, desc);
|
let code = fromComparison(name, builds, selectedFacets, predicate, desc);
|
||||||
let loc = window.location;
|
let loc = window.location;
|
||||||
return loc.protocol + '//' + loc.host + '/comparison?code=' + encodeURIComponent(code);
|
return (
|
||||||
|
loc.protocol +
|
||||||
|
'//' +
|
||||||
|
loc.host +
|
||||||
|
'/comparison?code=' +
|
||||||
|
encodeURIComponent(code)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generates the long permalink URL
|
* Generates the long permalink URL
|
||||||
*/
|
*/
|
||||||
_genPermalink() {
|
_genPermalink() {
|
||||||
this.context.showModal(<ModalPermalink url={this._buildUrl()}/>);
|
this.context.showModal(<ModalPermalink url={this._buildUrl()} />);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -365,18 +391,25 @@ export default class ComparisonPage extends Page {
|
|||||||
let { translate, formats } = this.context.language;
|
let { translate, formats } = this.context.language;
|
||||||
let { facets, builds } = this.state;
|
let { facets, builds } = this.state;
|
||||||
|
|
||||||
let generator = (callback) => {
|
let generator = callback => {
|
||||||
let url = this._buildUrl();
|
let url = this._buildUrl();
|
||||||
ShortenUrl(url,
|
ShortenUrl(
|
||||||
(shortenedUrl) => callback(comparisonBBCode(translate, formats, facets, builds, shortenedUrl)),
|
url,
|
||||||
(error) => callback(comparisonBBCode(translate, formats, facets, builds, url))
|
shortenedUrl =>
|
||||||
|
callback(
|
||||||
|
comparisonBBCode(translate, formats, facets, builds, shortenedUrl)
|
||||||
|
),
|
||||||
|
error =>
|
||||||
|
callback(comparisonBBCode(translate, formats, facets, builds, url))
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
this.context.showModal(<ModalExport
|
this.context.showModal(
|
||||||
title={translate('forum') + ' BBCode'}
|
<ModalExport
|
||||||
generator={generator}
|
title={translate('forum') + ' BBCode'}
|
||||||
/>);
|
generator={generator}
|
||||||
|
/>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -409,7 +442,8 @@ export default class ComparisonPage extends Page {
|
|||||||
* @param {Object} nextContext Incoming/Next conext
|
* @param {Object} nextContext Incoming/Next conext
|
||||||
*/
|
*/
|
||||||
componentWillReceiveProps(nextProps, nextContext) {
|
componentWillReceiveProps(nextProps, nextContext) {
|
||||||
if (this.context.route !== nextContext.route) { // Only reinit state if the route has changed
|
if (this.context.route !== nextContext.route) {
|
||||||
|
// Only reinit state if the route has changed
|
||||||
this.setState(this._initState(nextContext));
|
this.setState(this._initState(nextContext));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -419,7 +453,10 @@ export default class ComparisonPage extends Page {
|
|||||||
*/
|
*/
|
||||||
componentWillMount() {
|
componentWillMount() {
|
||||||
this.resizeListener = this.context.onWindowResize(this._updateDimensions);
|
this.resizeListener = this.context.onWindowResize(this._updateDimensions);
|
||||||
this.persistListener = Persist.addListener('discounts', this._updateDiscounts);
|
this.persistListener = Persist.addListener(
|
||||||
|
'discounts',
|
||||||
|
this._updateDiscounts
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -444,65 +481,132 @@ export default class ComparisonPage extends Page {
|
|||||||
renderPage() {
|
renderPage() {
|
||||||
let translate = this.context.language.translate;
|
let translate = this.context.language.translate;
|
||||||
let compareHeader;
|
let compareHeader;
|
||||||
let { newName, name, saved, builds, facets, predicate, desc, chartWidth } = this.state;
|
let {
|
||||||
|
newName,
|
||||||
|
name,
|
||||||
|
saved,
|
||||||
|
builds,
|
||||||
|
facets,
|
||||||
|
predicate,
|
||||||
|
desc,
|
||||||
|
chartWidth
|
||||||
|
} = this.state;
|
||||||
|
|
||||||
if (this.state.compareMode) {
|
if (this.state.compareMode) {
|
||||||
compareHeader = <tr>
|
compareHeader = (
|
||||||
<td className='head'>{translate('comparison')}</td>
|
<tr>
|
||||||
<td>
|
<td className="head">{translate('comparison')}</td>
|
||||||
<input value={newName} onChange={this._onNameChange} placeholder={translate('Enter Name')} maxLength='50' />
|
<td>
|
||||||
<button onClick={this._save} disabled={!newName || newName == 'all' || saved}>
|
<input
|
||||||
<FloppyDisk className='lg'/><span className='button-lbl'>{translate('save')}</span>
|
value={newName}
|
||||||
</button>
|
onChange={this._onNameChange}
|
||||||
<button onClick={this._delete} disabled={name == 'all' || !saved}><Bin className='lg warning'/></button>
|
placeholder={translate('Enter Name')}
|
||||||
<button onClick={this._selectBuilds}>
|
maxLength="50"
|
||||||
<Rocket className='lg'/><span className='button-lbl'>{translate('builds')}</span>
|
/>
|
||||||
</button>
|
<button
|
||||||
<button className='r' onClick={this._genPermalink} disabled={builds.length == 0}>
|
onClick={this._save}
|
||||||
<LinkIcon className='lg'/><span className='button-lbl'>{translate('permalink')}</span>
|
disabled={!newName || newName == 'all' || saved}
|
||||||
</button>
|
>
|
||||||
<button className='r' onClick={this._genBBcode} disabled={builds.length == 0}>
|
<FloppyDisk className="lg" />
|
||||||
<Embed className='lg'/><span className='button-lbl'>{translate('forum')}</span>
|
<span className="button-lbl">{translate('save')}</span>
|
||||||
</button>
|
</button>
|
||||||
</td>
|
<button onClick={this._delete} disabled={name == 'all' || !saved}>
|
||||||
</tr>;
|
<Bin className="lg warning" />
|
||||||
|
</button>
|
||||||
|
<button onClick={this._selectBuilds}>
|
||||||
|
<Rocket className="lg" />
|
||||||
|
<span className="button-lbl">{translate('builds')}</span>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className="r"
|
||||||
|
onClick={this._genPermalink}
|
||||||
|
disabled={builds.length == 0}
|
||||||
|
>
|
||||||
|
<LinkIcon className="lg" />
|
||||||
|
<span className="button-lbl">{translate('permalink')}</span>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className="r"
|
||||||
|
onClick={this._genBBcode}
|
||||||
|
disabled={builds.length == 0}
|
||||||
|
>
|
||||||
|
<Embed className="lg" />
|
||||||
|
<span className="button-lbl">{translate('forum')}</span>
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
compareHeader = <tr>
|
compareHeader = (
|
||||||
<td className='head'>{translate('comparison')}</td>
|
<tr>
|
||||||
<td>
|
<td className="head">{translate('comparison')}</td>
|
||||||
<h3>{name}</h3>
|
<td>
|
||||||
<button className='r' onClick={this._import}><Download className='lg'/>{translate('import')}</button>
|
<h3>{name}</h3>
|
||||||
</td>
|
<button className="r" onClick={this._import}>
|
||||||
</tr>;
|
<Download className="lg" />
|
||||||
|
{translate('import')}
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={'page'} style={{ fontSize: this.context.sizeRatio + 'em' }}>
|
<div
|
||||||
<table id='comparison'>
|
className={'page'}
|
||||||
|
style={{ fontSize: this.context.sizeRatio + 'em' }}
|
||||||
|
>
|
||||||
|
<table id="comparison">
|
||||||
<tbody>
|
<tbody>
|
||||||
{compareHeader}
|
{compareHeader}
|
||||||
<tr key='facets'>
|
<tr key="facets">
|
||||||
<td className='head'>{translate('compare')}</td>
|
<td className="head">{translate('compare')}</td>
|
||||||
<td>
|
<td>
|
||||||
<ul id='facet-container' onDragOver={this._facetDragOver}>
|
<ul id="facet-container" onDragOver={this._facetDragOver}>
|
||||||
{facets.map((f, i) =>
|
{facets.map((f, i) => (
|
||||||
<li key={f.title} data-i={i} draggable='true' onDragStart={this._facetDrag} onDragEnd={this._facetDrop} className={cn('facet', { active: f.active })} onClick={this._toggleFacet.bind(this, f)}>
|
<li
|
||||||
|
key={f.title}
|
||||||
|
data-i={i}
|
||||||
|
draggable="true"
|
||||||
|
onDragStart={this._facetDrag}
|
||||||
|
onDragEnd={this._facetDrop}
|
||||||
|
className={cn('facet', { active: f.active })}
|
||||||
|
onClick={this._toggleFacet.bind(this, f)}
|
||||||
|
>
|
||||||
{'↔ ' + translate(f.title)}
|
{'↔ ' + translate(f.title)}
|
||||||
</li>
|
</li>
|
||||||
)}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
<ComparisonTable builds={builds} facets={facets} onSort={this._sortShips} predicate={predicate} desc={desc} />
|
<ComparisonTable
|
||||||
|
builds={builds}
|
||||||
|
facets={facets}
|
||||||
|
onSort={this._sortShips}
|
||||||
|
predicate={predicate}
|
||||||
|
desc={desc}
|
||||||
|
/>
|
||||||
|
|
||||||
{!builds.length ?
|
{!builds.length ? (
|
||||||
<div className='chart' ref={node => this.chartRef = node}>{translate('PHRASE_NO_BUILDS')}</div> :
|
<div className="chart" ref={node => (this.chartRef = node)}>
|
||||||
facets.filter((f) => f.active).map((f, i) =>
|
{translate('PHRASE_NO_BUILDS')}
|
||||||
<div key={f.title} className='chart' ref={ i == 0 ? node => this.chartRef = node : null}>
|
</div>
|
||||||
<h3 className='ptr' onClick={this._sortShips.bind(this, f.props[0])}>{translate(f.title)}</h3>
|
) : (
|
||||||
|
facets.filter(f => f.active).map((f, i) => (
|
||||||
|
<div
|
||||||
|
key={f.title}
|
||||||
|
className="chart"
|
||||||
|
ref={i == 0 ? node => (this.chartRef = node) : null}
|
||||||
|
>
|
||||||
|
<h3
|
||||||
|
className="ptr"
|
||||||
|
onClick={this._sortShips.bind(this, f.props[0])}
|
||||||
|
>
|
||||||
|
{translate(f.title)}
|
||||||
|
</h3>
|
||||||
<BarChart
|
<BarChart
|
||||||
width={chartWidth}
|
width={chartWidth}
|
||||||
data={builds}
|
data={builds}
|
||||||
@@ -515,8 +619,8 @@ export default class ComparisonPage extends Page {
|
|||||||
desc={desc}
|
desc={desc}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
))
|
||||||
)}
|
)}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import PropTypes from 'prop-types';
|
|||||||
* Unexpected Error page / block
|
* Unexpected Error page / block
|
||||||
*/
|
*/
|
||||||
export default class ErrorDetails extends React.Component {
|
export default class ErrorDetails extends React.Component {
|
||||||
|
|
||||||
static contextTypes = {
|
static contextTypes = {
|
||||||
route: PropTypes.object.isRequired,
|
route: PropTypes.object.isRequired,
|
||||||
language: PropTypes.object.isRequired
|
language: PropTypes.object.isRequired
|
||||||
@@ -46,7 +45,7 @@ export default class ErrorDetails extends React.Component {
|
|||||||
<h1>Jameson, we have a problem..</h1>
|
<h1>Jameson, we have a problem..</h1>
|
||||||
<h1><small>{error.message}</small></h1>
|
<h1><small>{error.message}</small></h1>
|
||||||
<br/>
|
<br/>
|
||||||
{importerror ? <div>If you are attempting to import a ship from EDDI or EDMC and are seeing a 'Z_BUF_ERROR' it means that the URL has not been provided correctly. This is a common problem when using Microsoft Internet Explorer or Microsoft Edge, and you should use another browser instead.</div> : null }
|
{importerror ? <div>If you are attempting to import a ship from EDDI or EDMC and are seeing a 'Z_BUF_ERROR' it means that the URL has not been provided correctly. This is a common problem when using Microsoft Internet Explorer or Microsoft Edge, and you should use another browser instead.</div> : null }
|
||||||
<br/>
|
<br/>
|
||||||
<div>Please note that this site uses Google Analytics to track performance and usage. If you are blocking cookies, for example using Ghostery, please disable blocking for this site and try again.</div>
|
<div>Please note that this site uses Google Analytics to track performance and usage. If you are blocking cookies, for example using Ghostery, please disable blocking for this site and try again.</div>
|
||||||
<br/>
|
<br/>
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import Page from './Page';
|
|||||||
* 404 Page
|
* 404 Page
|
||||||
*/
|
*/
|
||||||
export default class NotFoundPage extends Page {
|
export default class NotFoundPage extends Page {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor
|
* Constructor
|
||||||
* @param {Object} props React Component properties
|
* @param {Object} props React Component properties
|
||||||
@@ -22,6 +21,10 @@ export default class NotFoundPage extends Page {
|
|||||||
* @return {React.Component} The page contents
|
* @return {React.Component} The page contents
|
||||||
*/
|
*/
|
||||||
renderPage() {
|
renderPage() {
|
||||||
return <div className='page' style={{ marginTop: 30 }}>Page <small>{this.context.route.path}</small> Not Found</div>;
|
return (
|
||||||
|
<div className="page" style={{ marginTop: 30 }}>
|
||||||
|
Page <small>{this.context.route.path}</small> Not Found
|
||||||
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,6 @@ import { shallowEqual } from '../utils/UtilityFunctions';
|
|||||||
* Abstract/Base Page
|
* Abstract/Base Page
|
||||||
*/
|
*/
|
||||||
export default class Page extends React.Component {
|
export default class Page extends React.Component {
|
||||||
|
|
||||||
static contextTypes = {
|
static contextTypes = {
|
||||||
closeMenu: PropTypes.func.isRequired,
|
closeMenu: PropTypes.func.isRequired,
|
||||||
hideModal: PropTypes.func.isRequired,
|
hideModal: PropTypes.func.isRequired,
|
||||||
@@ -51,22 +50,16 @@ export default class Page extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Pages are 'pure' components that only render when props, state, or context changes.
|
* Update the window title upon mount
|
||||||
* This method performs a shallow comparison to determine change.
|
|
||||||
*
|
|
||||||
* @param {Object} np Next/Incoming properties
|
|
||||||
* @param {Object} ns Next/Incoming state
|
|
||||||
* @param {Object} nc Next/Incoming context
|
|
||||||
* @return {Boolean} True if props, state, or context has changed
|
|
||||||
*/
|
*/
|
||||||
shouldComponentUpdate(np, ns, nc) {
|
componentWillMount() {
|
||||||
return !shallowEqual(this.props, np) || !shallowEqual(this.state, ns) || !shallowEqual(this.context, nc);
|
document.title = this.state.title || 'Coriolis';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update the window title upon mount
|
* Update the window title upon mount
|
||||||
*/
|
*/
|
||||||
componentWillMount() {
|
componentDidMount() {
|
||||||
document.title = this.state.title || 'Coriolis';
|
document.title = this.state.title || 'Coriolis';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,5 +83,4 @@ export default class Page extends React.Component {
|
|||||||
}
|
}
|
||||||
return this.renderPage();
|
return this.renderPage();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,94 +1,81 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import Page from './Page';
|
import Page from './Page';
|
||||||
import { Ships } from 'coriolis-data/dist';
|
|
||||||
import cn from 'classnames';
|
import cn from 'classnames';
|
||||||
import Ship from '../shipyard/Ship';
|
import { Factory } from 'ed-forge';
|
||||||
import * as ModuleUtils from '../shipyard/ModuleUtils';
|
import { JUMP_METRICS } from 'ed-forge/lib/ship-stats';
|
||||||
import { SizeMap } from '../shipyard/Constants';
|
import { SizeMap } from '../shipyard/Constants';
|
||||||
import Link from '../components/Link';
|
import Link from '../components/Link';
|
||||||
|
|
||||||
/**
|
|
||||||
* Counts the hardpoints by class/size
|
|
||||||
* @param {Object} slot Hardpoint Slot model
|
|
||||||
*/
|
|
||||||
function countHp(slot) {
|
|
||||||
this.hp[slot.maxClass]++;
|
|
||||||
this.hpCount++;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Counts the internal slots and aggregated properties
|
|
||||||
* @param {Object} slot Internal Slots
|
|
||||||
*/
|
|
||||||
function countInt(slot) {
|
|
||||||
let crEligible = !slot.eligible || slot.eligible.cr;
|
|
||||||
this.int[slot.maxClass - 1]++; // Subtract 1 since there is no Class 0 Internal compartment
|
|
||||||
this.intCount++;
|
|
||||||
this.maxCargo += crEligible ? ModuleUtils.findInternal('cr', slot.maxClass, 'E').cargo : 0;
|
|
||||||
|
|
||||||
// if no eligiblity, then assume pce
|
|
||||||
let passSlotType = null;
|
|
||||||
let passSlotRating = null;
|
|
||||||
if (!slot.eligible || slot.eligible.pce) {
|
|
||||||
passSlotType = 'pce';
|
|
||||||
passSlotRating = 'E';
|
|
||||||
} else if (slot.eligible.pci) {
|
|
||||||
passSlotType = 'pci';
|
|
||||||
passSlotRating = 'D';
|
|
||||||
} else if (slot.eligible.pcm) {
|
|
||||||
passSlotType = 'pcm';
|
|
||||||
passSlotRating = 'C';
|
|
||||||
} else if (slot.eligible.pcq) {
|
|
||||||
passSlotType = 'pcq';
|
|
||||||
passSlotRating = 'B';
|
|
||||||
}
|
|
||||||
let passengerBay = passSlotType ? ModuleUtils.findMaxInternal(passSlotType, slot.maxClass, passSlotRating) : null;
|
|
||||||
this.maxPassengers += passengerBay ? passengerBay.passengers : 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate Ship summary and aggregated properties
|
* Generate Ship summary and aggregated properties
|
||||||
* @param {String} shipId Ship Id
|
* @param {String} shipId Ship Id
|
||||||
* @param {Object} shipData Ship Default Data
|
|
||||||
* @return {Object} Ship summary and aggregated properties
|
* @return {Object} Ship summary and aggregated properties
|
||||||
*/
|
*/
|
||||||
function shipSummary(shipId, shipData) {
|
function shipSummary(shipId) {
|
||||||
|
// Build Ship
|
||||||
|
let ship = Factory.newShip(shipId);
|
||||||
|
|
||||||
|
let coreSizes = ship.readMeta('coreSizes');
|
||||||
|
let { jumpRange, totalRange } = ship.getMetrics(JUMP_METRICS);
|
||||||
let summary = {
|
let summary = {
|
||||||
|
agility: ship.readProp('pitch') + ship.readProp('yaw') + ship.readProp('roll'),
|
||||||
|
baseArmour: ship.readProp('basearmour'),
|
||||||
|
baseShieldStrength: ship.readProp('baseshieldstrength'),
|
||||||
|
boost: ship.readProp('boost'),
|
||||||
|
class: ship.readMeta('class'),
|
||||||
|
crew: ship.readMeta('crew'),
|
||||||
id: shipId,
|
id: shipId,
|
||||||
|
hardness: ship.readProp('hardness'),
|
||||||
hpCount: 0,
|
hpCount: 0,
|
||||||
|
hullMass: ship.readProp('hullmass'),
|
||||||
intCount: 0,
|
intCount: 0,
|
||||||
maxCargo: 0,
|
masslock: ship.readProp('masslock'),
|
||||||
maxPassengers: 0,
|
|
||||||
hp: [0, 0, 0, 0, 0], // Utility, Small, Medium, Large, Huge
|
hp: [0, 0, 0, 0, 0], // Utility, Small, Medium, Large, Huge
|
||||||
int: [0, 0, 0, 0, 0, 0, 0, 0], // Sizes 1 - 8
|
int: [0, 0, 0, 0, 0, 0, 0, 0], // Sizes 1 - 8
|
||||||
standard: shipData.slots.standard,
|
jumpRange,
|
||||||
agility: shipData.properties.pitch + shipData.properties.yaw + shipData.properties.roll
|
pitch: ship.readProp('pitch'),
|
||||||
|
retailCost: ship.readMeta('retailCost'),
|
||||||
|
roll: ship.readProp('roll'),
|
||||||
|
speed: ship.readProp('speed'),
|
||||||
|
standard: [
|
||||||
|
'powerplant',
|
||||||
|
'mainengines',
|
||||||
|
'frameshiftdrive',
|
||||||
|
'lifesupport',
|
||||||
|
'powerdistributor',
|
||||||
|
'radar',
|
||||||
|
'fueltank'
|
||||||
|
].map(k => coreSizes[k]),
|
||||||
|
totalRange,
|
||||||
|
yaw: ship.readProp('yaw'),
|
||||||
};
|
};
|
||||||
Object.assign(summary, shipData.properties);
|
|
||||||
let ship = new Ship(shipId, shipData.properties, shipData.slots);
|
|
||||||
|
|
||||||
// Build Ship
|
// Count Hardpoints by class
|
||||||
ship.buildWith(shipData.defaults); // Populate with stock/default components
|
ship.getHardpoints(undefined, true).forEach(hardpoint => {
|
||||||
ship.hardpoints.forEach(countHp.bind(summary)); // Count Hardpoints by class
|
summary.hp[hardpoint.getSize()]++;
|
||||||
ship.internal.forEach(countInt.bind(summary)); // Count Internal Compartments by class
|
summary.hpCount++;
|
||||||
summary.retailCost = ship.totalCost; // Record Stock/Default/retail cost
|
});
|
||||||
ship.optimizeMass({ pd: '1D' }); // Optimize Mass with 1D PD for maximum possible jump range
|
// Count Internal Compartments by class
|
||||||
summary.maxJumpRange = ship.unladenRange; // Record Jump Range
|
let maxCargo = 0, maxPassengers = 0;
|
||||||
|
ship.getInternals(undefined, true).forEach(internal => {
|
||||||
|
const size = String(internal.getSize());
|
||||||
|
summary.int[size]++;
|
||||||
|
summary.intCount++;
|
||||||
|
|
||||||
// Best thrusters
|
// Try cargo racks
|
||||||
let th;
|
try {
|
||||||
if (ship.standard[1].maxClass === 3) {
|
internal.setItem('cargorack', size);
|
||||||
th = 'tz';
|
maxCargo += internal.get('cargo');
|
||||||
} else if (ship.standard[1].maxClass === 2) {
|
} catch {}
|
||||||
th = 'u0';
|
// Try economy cabins
|
||||||
} else {
|
try {
|
||||||
th = ship.standard[1].maxClass + 'A';
|
internal.setItem('passengercabins', size < '6' ? size : '6', '1');
|
||||||
}
|
maxPassengers += internal.get('cabincapacity');
|
||||||
|
} catch {}
|
||||||
|
});
|
||||||
|
|
||||||
ship.optimizeMass({ th, fsd: '2D', ft: '1C' }); // Optmize mass with Max Thrusters
|
summary.maxCargo = maxCargo;
|
||||||
summary.topSpeed = ship.topSpeed;
|
summary.maxPassengers = maxPassengers;
|
||||||
summary.topBoost = ship.topBoost;
|
|
||||||
summary.baseArmour = ship.armour;
|
|
||||||
|
|
||||||
return summary;
|
return summary;
|
||||||
}
|
}
|
||||||
@@ -97,7 +84,6 @@ function shipSummary(shipId, shipData) {
|
|||||||
* The Shipyard summary page
|
* The Shipyard summary page
|
||||||
*/
|
*/
|
||||||
export default class ShipyardPage extends Page {
|
export default class ShipyardPage extends Page {
|
||||||
|
|
||||||
static cachedShipSummaries = null;
|
static cachedShipSummaries = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -110,21 +96,23 @@ export default class ShipyardPage extends Page {
|
|||||||
|
|
||||||
if (!ShipyardPage.cachedShipSummaries) {
|
if (!ShipyardPage.cachedShipSummaries) {
|
||||||
ShipyardPage.cachedShipSummaries = [];
|
ShipyardPage.cachedShipSummaries = [];
|
||||||
for (let s in Ships) {
|
for (let s of Factory.getAllShipTypes()) {
|
||||||
ShipyardPage.cachedShipSummaries.push(shipSummary(s, Ships[s]));
|
ShipyardPage.cachedShipSummaries.push(shipSummary(s));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
title: 'Coriolis EDCD Edition - Shipyard',
|
title: 'Coriolis EDCD Edition - Shipyard',
|
||||||
shipPredicate: 'name',
|
shipPredicate: 'id',
|
||||||
shipDesc: true,
|
shipDesc: true,
|
||||||
shipSummaries: ShipyardPage.cachedShipSummaries
|
shipSummaries: ShipyardPage.cachedShipSummaries,
|
||||||
|
compare: {},
|
||||||
|
groupCompared: false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Higlight the current ship in the table
|
* Higlight the current ship in the table on mouse over
|
||||||
* @param {String} shipId Ship Id
|
* @param {String} shipId Ship Id
|
||||||
* @param {SyntheticEvent} event Event
|
* @param {SyntheticEvent} event Event
|
||||||
*/
|
*/
|
||||||
@@ -133,6 +121,24 @@ export default class ShipyardPage extends Page {
|
|||||||
this.setState({ shipId });
|
this.setState({ shipId });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Toggle compare highlighting for ships in the table
|
||||||
|
* @param {String} shipId Ship Id
|
||||||
|
*/
|
||||||
|
_toggleCompare(shipId) {
|
||||||
|
let compare = this.state.compare;
|
||||||
|
compare[shipId] = !compare[shipId];
|
||||||
|
this.setState({ compare });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Toggle grouping of compared ships in the table
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_toggleGroupCompared() {
|
||||||
|
this.setState({ groupCompared: !this.state.groupCompared });
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update state with the specified sort predicates
|
* Update state with the specified sort predicates
|
||||||
* @param {String} shipPredicate Sort predicate - property name
|
* @param {String} shipPredicate Sort predicate - property name
|
||||||
@@ -145,12 +151,15 @@ export default class ShipyardPage extends Page {
|
|||||||
shipPredicateIndex = undefined;
|
shipPredicateIndex = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.state.shipPredicate == shipPredicate && this.state.shipPredicateIndex == shipPredicateIndex) {
|
if (
|
||||||
|
this.state.shipPredicate == shipPredicate &&
|
||||||
|
this.state.shipPredicateIndex == shipPredicateIndex
|
||||||
|
) {
|
||||||
shipDesc = !shipDesc;
|
shipDesc = !shipDesc;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setState({ shipPredicate, shipDesc, shipPredicateIndex });
|
this.setState({ shipPredicate, shipDesc, shipPredicateIndex });
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate the table row summary for the ship
|
* Generate the table row summary for the ship
|
||||||
@@ -159,56 +168,62 @@ export default class ShipyardPage extends Page {
|
|||||||
* @param {Object} u Localized unit map
|
* @param {Object} u Localized unit map
|
||||||
* @param {Function} fInt Localized integer formatter
|
* @param {Function} fInt Localized integer formatter
|
||||||
* @param {Function} fRound Localized round formatter
|
* @param {Function} fRound Localized round formatter
|
||||||
* @param {Boolean} highlight Should this row be highlighted
|
|
||||||
* @return {React.Component} Table Row
|
* @return {React.Component} Table Row
|
||||||
*/
|
*/
|
||||||
_shipRowElement(s, translate, u, fInt, fRound, highlight) {
|
_shipRowElement(s, translate, u, fInt, fRound) {
|
||||||
let noTouch = this.context.noTouch;
|
let noTouch = this.context.noTouch;
|
||||||
|
|
||||||
return <tr
|
return (
|
||||||
|
<tr
|
||||||
key={s.id}
|
key={s.id}
|
||||||
style={{ height: '1.5em' }}
|
style={{ height: '1.5em' }}
|
||||||
className={cn({ highlighted: noTouch && this.state.shipId === s.id, alt: highlight })}
|
className={cn({
|
||||||
|
highlighted: noTouch && this.state.shipId === s.id,
|
||||||
|
comparehighlight: this.state.compare[s.id],
|
||||||
|
})}
|
||||||
onMouseEnter={noTouch && this._highlightShip.bind(this, s.id)}
|
onMouseEnter={noTouch && this._highlightShip.bind(this, s.id)}
|
||||||
|
onClick={() => this._toggleCompare(s.id)}
|
||||||
>
|
>
|
||||||
<td className='ri'>{s.manufacturer}</td>
|
<td className="ri">{fInt(s.retailCost)}</td>
|
||||||
<td className='ri'>{fInt(s.retailCost)}</td>
|
<td className="ri cap">{translate(SizeMap[s.class])}</td>
|
||||||
<td className='ri cap'>{translate(SizeMap[s.class])}</td>
|
<td className="ri">{fInt(s.crew)}</td>
|
||||||
<td className='ri'>{fInt(s.crew)}</td>
|
<td className="ri">{s.masslock}</td>
|
||||||
<td className='ri'>{s.masslock}</td>
|
<td className="ri">{fInt(s.hullMass)}</td>
|
||||||
<td className='ri'>{fInt(s.agility)}</td>
|
<td className="ri">{fInt(s.agility)}</td>
|
||||||
<td className='ri'>{fInt(s.hardness)}</td>
|
<td className="ri">{fInt(s.speed)}</td>
|
||||||
<td className='ri'>{fInt(s.hullMass)}</td>
|
<td className="ri">{fInt(s.boost)}</td>
|
||||||
<td className='ri'>{fInt(s.speed)}</td>
|
<td className="ri">{fInt(s.pitch)}</td>
|
||||||
<td className='ri'>{fInt(s.boost)}</td>
|
<td className="ri">{fInt(s.yaw)}</td>
|
||||||
<td className='ri'>{fInt(s.baseArmour)}</td>
|
<td className="ri">{fInt(s.roll)}</td>
|
||||||
<td className='ri'>{fInt(s.baseShieldStrength)}</td>
|
<td className="ri">{fRound(s.jumpRange)}</td>
|
||||||
<td className='ri'>{fInt(s.topSpeed)}</td>
|
<td className="ri">{fRound(s.totalRange)}</td>
|
||||||
<td className='ri'>{fInt(s.topBoost)}</td>
|
<td className="ri">{fInt(s.hardness)}</td>
|
||||||
<td className='ri'>{fRound(s.maxJumpRange)}</td>
|
<td className="ri">{fInt(s.baseArmour)}</td>
|
||||||
<td className='ri'>{fInt(s.maxCargo)}</td>
|
<td className="ri">{fInt(s.baseShieldStrength)}</td>
|
||||||
<td className='ri'>{fInt(s.maxPassengers)}</td>
|
<td className="ri">{fInt(s.maxCargo)}</td>
|
||||||
<td className='cn'>{s.standard[0]}</td>
|
<td className="ri">{fInt(s.maxPassengers)}</td>
|
||||||
<td className='cn'>{s.standard[1]}</td>
|
<td className="cn">{s.standard[0]}</td>
|
||||||
<td className='cn'>{s.standard[2]}</td>
|
<td className="cn">{s.standard[1]}</td>
|
||||||
<td className='cn'>{s.standard[3]}</td>
|
<td className="cn">{s.standard[2]}</td>
|
||||||
<td className='cn'>{s.standard[4]}</td>
|
<td className="cn">{s.standard[3]}</td>
|
||||||
<td className='cn'>{s.standard[5]}</td>
|
<td className="cn">{s.standard[4]}</td>
|
||||||
<td className='cn'>{s.standard[6]}</td>
|
<td className="cn">{s.standard[5]}</td>
|
||||||
<td className={cn({ disabled: !s.hp[1] })}>{s.hp[1]}</td>
|
<td className="cn">{s.standard[6]}</td>
|
||||||
<td className={cn({ disabled: !s.hp[2] })}>{s.hp[2]}</td>
|
<td className={cn({ disabled: !s.hp[1] })}>{s.hp[1]}</td>
|
||||||
<td className={cn({ disabled: !s.hp[3] })}>{s.hp[3]}</td>
|
<td className={cn({ disabled: !s.hp[2] })}>{s.hp[2]}</td>
|
||||||
<td className={cn({ disabled: !s.hp[4] })}>{s.hp[4]}</td>
|
<td className={cn({ disabled: !s.hp[3] })}>{s.hp[3]}</td>
|
||||||
<td className={cn({ disabled: !s.hp[0] })}>{s.hp[0]}</td>
|
<td className={cn({ disabled: !s.hp[4] })}>{s.hp[4]}</td>
|
||||||
<td className={cn({ disabled: !s.int[0] })}>{s.int[0]}</td>
|
<td className={cn({ disabled: !s.hp[0] })}>{s.hp[0]}</td>
|
||||||
<td className={cn({ disabled: !s.int[1] })}>{s.int[1]}</td>
|
<td className={cn({ disabled: !s.int[0] })}>{s.int[0]}</td>
|
||||||
<td className={cn({ disabled: !s.int[2] })}>{s.int[2]}</td>
|
<td className={cn({ disabled: !s.int[1] })}>{s.int[1]}</td>
|
||||||
<td className={cn({ disabled: !s.int[3] })}>{s.int[3]}</td>
|
<td className={cn({ disabled: !s.int[2] })}>{s.int[2]}</td>
|
||||||
<td className={cn({ disabled: !s.int[4] })}>{s.int[4]}</td>
|
<td className={cn({ disabled: !s.int[3] })}>{s.int[3]}</td>
|
||||||
<td className={cn({ disabled: !s.int[5] })}>{s.int[5]}</td>
|
<td className={cn({ disabled: !s.int[4] })}>{s.int[4]}</td>
|
||||||
<td className={cn({ disabled: !s.int[6] })}>{s.int[6]}</td>
|
<td className={cn({ disabled: !s.int[5] })}>{s.int[5]}</td>
|
||||||
<td className={cn({ disabled: !s.int[7] })}>{s.int[7]}</td>
|
<td className={cn({ disabled: !s.int[6] })}>{s.int[6]}</td>
|
||||||
</tr>;
|
<td className={cn({ disabled: !s.int[7] })}>{s.int[7]}</td>
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -220,9 +235,9 @@ export default class ShipyardPage extends Page {
|
|||||||
let { translate, formats, units } = language;
|
let { translate, formats, units } = language;
|
||||||
let hide = this.context.tooltip.bind(null, null);
|
let hide = this.context.tooltip.bind(null, null);
|
||||||
let fInt = formats.int;
|
let fInt = formats.int;
|
||||||
let fRound = formats.round;
|
let { shipSummaries, shipPredicate, shipPredicateIndex, compare, groupCompared } = this.state;
|
||||||
let { shipSummaries, shipPredicate, shipPredicateIndex } = this.state;
|
let sortShips = (predicate, index) =>
|
||||||
let sortShips = (predicate, index) => this._sortShips.bind(this, predicate, index);
|
this._sortShips.bind(this, predicate, index);
|
||||||
|
|
||||||
let filters = {
|
let filters = {
|
||||||
// 'class': { 1: 1, 2: 1}
|
// 'class': { 1: 1, 2: 1}
|
||||||
@@ -239,7 +254,8 @@ export default class ShipyardPage extends Page {
|
|||||||
|
|
||||||
// Sort shipsOverview
|
// Sort shipsOverview
|
||||||
shipSummaries.sort((a, b) => {
|
shipSummaries.sort((a, b) => {
|
||||||
let valA = a[shipPredicate], valB = b[shipPredicate];
|
let valA = a[shipPredicate],
|
||||||
|
valB = b[shipPredicate];
|
||||||
|
|
||||||
if (shipPredicateIndex != undefined) {
|
if (shipPredicateIndex != undefined) {
|
||||||
valA = valA[shipPredicateIndex];
|
valA = valA[shipPredicateIndex];
|
||||||
@@ -252,8 +268,17 @@ export default class ShipyardPage extends Page {
|
|||||||
valB = val;
|
valB = val;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(valA == valB) {
|
if (groupCompared) {
|
||||||
if (a.name > b.name) {
|
if (compare[a.id] && !compare[b.id]) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (!compare[a.id] && compare[b.id]) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (valA == valB) {
|
||||||
|
if (a.id > b.id) {
|
||||||
return 1;
|
return 1;
|
||||||
} else {
|
} else {
|
||||||
return -1;
|
return -1;
|
||||||
@@ -269,128 +294,276 @@ export default class ShipyardPage extends Page {
|
|||||||
let shipRows = new Array(shipSummaries.length);
|
let shipRows = new Array(shipSummaries.length);
|
||||||
let detailRows = new Array(shipSummaries.length);
|
let detailRows = new Array(shipSummaries.length);
|
||||||
|
|
||||||
let lastShipSortValue = null;
|
|
||||||
let backgroundHighlight = false;
|
|
||||||
|
|
||||||
for (let s of shipSummaries) {
|
for (let s of shipSummaries) {
|
||||||
let shipSortValue = s[shipPredicate];
|
detailRows[i] = this._shipRowElement(
|
||||||
if(shipPredicateIndex != undefined) {
|
s,
|
||||||
shipSortValue = shipSortValue[shipPredicateIndex];
|
translate,
|
||||||
}
|
units,
|
||||||
|
fInt,
|
||||||
if(shipSortValue != lastShipSortValue) {
|
formats.f1,
|
||||||
backgroundHighlight = !backgroundHighlight;
|
);
|
||||||
lastShipSortValue = shipSortValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
detailRows[i] = this._shipRowElement(s, translate, units, fInt, formats.f1, backgroundHighlight);
|
|
||||||
shipRows[i] = (
|
shipRows[i] = (
|
||||||
<tr
|
<tr
|
||||||
key={i}
|
key={i}
|
||||||
style={{ height: '1.5em' }}
|
style={{ height: '1.5em' }}
|
||||||
className={cn({ highlighted: noTouch && this.state.shipId === s.id, alt: backgroundHighlight })}
|
className={cn({
|
||||||
|
highlighted: noTouch && this.state.shipId === s.id,
|
||||||
|
comparehighlight: this.state.compare[s.id],
|
||||||
|
})}
|
||||||
onMouseEnter={noTouch && this._highlightShip.bind(this, s.id)}
|
onMouseEnter={noTouch && this._highlightShip.bind(this, s.id)}
|
||||||
|
onClick={() => this._toggleCompare(s.id)}
|
||||||
>
|
>
|
||||||
<td className='le'><Link href={'/outfit/' + s.id}>{s.name}</Link></td>
|
<td className="le">
|
||||||
|
<Link href={'/outfit/' + s.id}>{s.id} {s.beta === true ? '(Beta)' : null}</Link>
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
);
|
);
|
||||||
i++;
|
i++;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='page' style={{ fontSize: sizeRatio + 'em' }}>
|
<div className="page" style={{ fontSize: sizeRatio + 'em' }}>
|
||||||
<div style={{ whiteSpace: 'nowrap', margin: '0 auto', fontSize: '0.8em', position: 'relative', display: 'inline-block', maxWidth: '100%' }}>
|
<div className="content-wrapper">
|
||||||
<table style={{ width: '12em', position: 'absolute', zIndex: 1 }}>
|
<div className="shipyard-table-wrapper">
|
||||||
<thead>
|
<table style={{ width: '12em', position: 'absolute', zIndex: 1 }} className="shipyard-table">
|
||||||
<tr>
|
<thead>
|
||||||
<th className='le rgt'> </th>
|
<tr>
|
||||||
</tr>
|
<th className="le rgt"> </th>
|
||||||
<tr className='main'>
|
</tr>
|
||||||
<th className='sortable le rgt' onClick={sortShips('name')}>{translate('ship')}</th>
|
<tr className="main">
|
||||||
</tr>
|
<th className="sortable le rgt" onClick={sortShips('id')}>
|
||||||
<tr>
|
{translate('ship')}
|
||||||
<th className='le rgt invisible'>{units['m/s']}</th>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
<tr>
|
||||||
<tbody onMouseLeave={this._highlightShip.bind(this, null)}>
|
<th className="le rgt invisible">{units['m/s']}</th>
|
||||||
{shipRows}
|
</tr>
|
||||||
</tbody>
|
</thead>
|
||||||
</table>
|
<tbody onMouseLeave={this._highlightShip.bind(this, null)}>
|
||||||
<div style={{ overflowX: 'scroll', maxWidth: '100%' }}>
|
{shipRows}
|
||||||
<table style={{ marginLeft: 'calc(12em - 1px)', zIndex: 0 }}>
|
</tbody>
|
||||||
<thead>
|
</table>
|
||||||
<tr className='main'>
|
<div style={{ overflowX: 'auto', maxWidth: '100%' }}>
|
||||||
<th rowSpan={3} className='sortable' onClick={sortShips('manufacturer')}>{translate('manufacturer')}</th>
|
<table style={{ marginLeft: 'calc(12em - 1px)', zIndex: 0 }} className="shipyard-table">
|
||||||
<th> </th>
|
<thead>
|
||||||
<th rowSpan={3} className='sortable' onClick={sortShips('class')}>{translate('size')}</th>
|
<tr className="main">
|
||||||
<th rowSpan={3} className='sortable' onClick={sortShips('crew')}>{translate('crew')}</th>
|
{/* First all headers that spread out over three rows */}
|
||||||
<th rowSpan={3} className='sortable' onMouseEnter={termtip.bind(null, 'mass lock factor')} onMouseLeave={hide} onClick={sortShips('masslock')} >{translate('MLF')}</th>
|
{/* cost placeholder */}
|
||||||
<th rowSpan={3} className='sortable' onClick={sortShips('agility')}>{translate('agility')}</th>
|
<th> </th>
|
||||||
<th rowSpan={3} className='sortable' onMouseEnter={termtip.bind(null, 'hardness')} onMouseLeave={hide} onClick={sortShips('hardness')}>{translate('hrd')}</th>
|
<th rowSpan={3} className="sortable" onClick={sortShips('class')}>
|
||||||
<th> </th>
|
{translate('size')}
|
||||||
<th colSpan={4}>{translate('base')}</th>
|
</th>
|
||||||
<th colSpan={5}>{translate('max')}</th>
|
<th rowSpan={3} className="sortable" onClick={sortShips('crew')}>
|
||||||
<th className='lft' colSpan={7}></th>
|
{translate('crew')}
|
||||||
<th className='lft' colSpan={5}></th>
|
</th>
|
||||||
<th className='lft' colSpan={8}></th>
|
<th rowSpan={3} className="sortable"
|
||||||
</tr>
|
onMouseEnter={termtip.bind(null, 'mass lock factor')}
|
||||||
<tr>
|
onMouseLeave={hide} onClick={sortShips('masslock')}>
|
||||||
<th className='sortable lft' onClick={sortShips('retailCost')}>{translate('cost')}</th>
|
{translate('MLF')}
|
||||||
<th className='sortable lft' onClick={sortShips('hullMass')}>{translate('hull')}</th>
|
</th>
|
||||||
<th className='sortable lft' onClick={sortShips('speed')}>{translate('speed')}</th>
|
{/* hull mass placeholder */}
|
||||||
<th className='sortable' onClick={sortShips('boost')}>{translate('boost')}</th>
|
<th> </th>
|
||||||
<th className='sortable' onClick={sortShips('baseArmour')}>{translate('armour')}</th>
|
<th colSpan={6}>{translate('agility')}</th>
|
||||||
<th className='sortable' onClick={sortShips('baseShieldStrength')}>{translate('shields')}</th>
|
<th colSpan={2}>{translate('travel')}</th>
|
||||||
|
<th colSpan={3}>{translate('defence')}</th>
|
||||||
|
{/* cargo placeholder */}
|
||||||
|
<th> </th>
|
||||||
|
{/* pax placeholder */}
|
||||||
|
<th> </th>
|
||||||
|
<th className="lft" colSpan={7} />
|
||||||
|
<th className="lft" colSpan={5} />
|
||||||
|
<th className="lft" colSpan={8} />
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
{/* Now all headers in a second-row */}
|
||||||
|
<th className="sortable lft" onClick={sortShips('retailCost')}>
|
||||||
|
{translate('cost')}
|
||||||
|
</th>
|
||||||
|
<th className="sortable lft" onClick={sortShips('hullMass')}>
|
||||||
|
{translate('hull')}
|
||||||
|
</th>
|
||||||
|
<th className="sortable lft" onClick={sortShips('agility')}>
|
||||||
|
{translate('rating')}
|
||||||
|
</th>
|
||||||
|
<th className="sortable" onClick={sortShips('speed')}>
|
||||||
|
{translate('speed')}
|
||||||
|
</th>
|
||||||
|
<th className="sortable" onClick={sortShips('boost')}>
|
||||||
|
{translate('boost')}
|
||||||
|
</th>
|
||||||
|
<th className="sortable" onClick={sortShips('pitch')}>
|
||||||
|
{translate('pitch')}
|
||||||
|
</th>
|
||||||
|
<th className="sortable" onClick={sortShips('yaw')}>
|
||||||
|
{translate('yaw')}
|
||||||
|
</th>
|
||||||
|
<th className="sortable" onClick={sortShips('roll')}>
|
||||||
|
{translate('roll')}
|
||||||
|
</th>
|
||||||
|
<th className="sortable lft" onClick={sortShips('jumpRange')}>
|
||||||
|
{translate('jump')}
|
||||||
|
</th>
|
||||||
|
<th className="sortable" onClick={sortShips('totalRange')}>
|
||||||
|
{translate('range')}
|
||||||
|
</th>
|
||||||
|
<th className="sortable lft" onMouseEnter={termtip.bind(null, 'hardness')}
|
||||||
|
onMouseLeave={hide} onClick={sortShips('hardness')}>
|
||||||
|
{translate('hrd')}
|
||||||
|
</th>
|
||||||
|
<th className="sortable" onClick={sortShips('baseArmour')}>
|
||||||
|
{translate('armour')}
|
||||||
|
</th>
|
||||||
|
<th className="sortable" onClick={sortShips('baseShieldStrength')}>
|
||||||
|
{translate('shields')}
|
||||||
|
</th>
|
||||||
|
|
||||||
<th className='sortable lft' onClick={sortShips('topSpeed')}>{translate('speed')}</th>
|
<th className="sortable lft" onClick={sortShips('maxCargo')}>
|
||||||
<th className='sortable' onClick={sortShips('topBoost')}>{translate('boost')}</th>
|
{translate('cargo')}
|
||||||
<th className='sortable' onClick={sortShips('maxJumpRange')}>{translate('jump')}</th>
|
</th>
|
||||||
<th className='sortable' onClick={sortShips('maxCargo')}>{translate('cargo')}</th>
|
<th className="sortable lft" onClick={sortShips('maxPassengers')}
|
||||||
<th className='sortable' onClick={sortShips('maxPassengers')}>{translate('pax')}</th>
|
onMouseEnter={termtip.bind(null, 'passenger capacity')} onMouseLeave={hide}>
|
||||||
|
{translate('pax')}
|
||||||
|
</th>
|
||||||
|
<th className="lft" colSpan={7}>
|
||||||
|
{translate('core module classes')}
|
||||||
|
</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>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
{/* Third row headers, i.e., units */}
|
||||||
|
<th
|
||||||
|
className="sortable lft"
|
||||||
|
onClick={sortShips('retailCost')}
|
||||||
|
>
|
||||||
|
{units.CR}
|
||||||
|
</th>
|
||||||
|
<th className="sortable lft" onClick={sortShips('hullMass')}>
|
||||||
|
{units.T}
|
||||||
|
</th>
|
||||||
|
{/* agility rating placeholder */}
|
||||||
|
<th className="lft"> </th>
|
||||||
|
<th className="sortable" onClick={sortShips('speed')}>
|
||||||
|
{units['m/s']}
|
||||||
|
</th>
|
||||||
|
<th className="sortable" onClick={sortShips('boost')}>
|
||||||
|
{units['m/s']}
|
||||||
|
</th>
|
||||||
|
<th className="sortable" onClick={sortShips('pitch')}>
|
||||||
|
{units['°/s']}
|
||||||
|
</th>
|
||||||
|
<th className="sortable" onClick={sortShips('yaw')}>
|
||||||
|
{units['°/s']}
|
||||||
|
</th>
|
||||||
|
<th className="sortable" onClick={sortShips('roll')}>
|
||||||
|
{units['°/s']}
|
||||||
|
</th>
|
||||||
|
<th className="sortable lft" onClick={sortShips('jumpRange')}>
|
||||||
|
{units.LY}
|
||||||
|
</th>
|
||||||
|
<th className="sortable" onClick={sortShips('totalRange')}>
|
||||||
|
{units.LY}
|
||||||
|
</th>
|
||||||
|
<th className="lft"> </th>
|
||||||
|
{/* armour placeholder */}
|
||||||
|
<th> </th>
|
||||||
|
<th
|
||||||
|
className="sortable"
|
||||||
|
onClick={sortShips('baseShieldStrength')}
|
||||||
|
>
|
||||||
|
{units.MJ}
|
||||||
|
</th>
|
||||||
|
<th className="sortable lft" onClick={sortShips('maxCargo')}>
|
||||||
|
{units.T}
|
||||||
|
</th>
|
||||||
|
{/* pax placeholder */}
|
||||||
|
<th className="lft"> </th>
|
||||||
|
<th className="sortable lft" onMouseEnter={termtip.bind(null, 'power plant')}
|
||||||
|
onMouseLeave={hide} onClick={sortShips('standard', 0)}>
|
||||||
|
{'pp'}
|
||||||
|
</th>
|
||||||
|
<th className="sortable" onMouseEnter={termtip.bind(null, 'thrusters')}
|
||||||
|
onMouseLeave={hide} onClick={sortShips('standard', 1)}>
|
||||||
|
{'th'}
|
||||||
|
</th>
|
||||||
|
<th className="sortable" onMouseEnter={termtip.bind(null, 'frame shift drive')}
|
||||||
|
onMouseLeave={hide} onClick={sortShips('standard', 2)}>
|
||||||
|
{'fsd'}
|
||||||
|
</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, '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" onClick={sortShips('hp', 2)}>
|
||||||
|
{translate('M')}
|
||||||
|
</th>
|
||||||
|
<th className="sortable" onClick={sortShips('hp', 3)}>
|
||||||
|
{translate('L')}
|
||||||
|
</th>
|
||||||
|
<th className="sortable" onClick={sortShips('hp', 4)}>
|
||||||
|
{translate('H')}
|
||||||
|
</th>
|
||||||
|
<th className="sortable" onClick={sortShips('hp', 0)}>
|
||||||
|
{translate('U')}
|
||||||
|
</th>
|
||||||
|
|
||||||
<th className='lft' colSpan={7}>{translate('core module classes')}</th>
|
<th className="sortable lft" onClick={sortShips('int', 0)}>
|
||||||
<th colSpan={5} className='sortable lft' onClick={sortShips('hpCount')}>{translate('hardpoints')}</th>
|
1
|
||||||
<th colSpan={8} className='sortable lft' onClick={sortShips('intCount')}>{translate('internal compartments')}</th>
|
</th>
|
||||||
</tr>
|
<th className="sortable" onClick={sortShips('int', 1)}>
|
||||||
<tr>
|
2
|
||||||
<th className='sortable lft' onClick={sortShips('retailCost')}>{units.CR}</th>
|
</th>
|
||||||
<th className='sortable lft' onClick={sortShips('hullMass')}>{units.T}</th>
|
<th className="sortable" onClick={sortShips('int', 2)}>
|
||||||
<th className='sortable lft' onClick={sortShips('speed')}>{units['m/s']}</th>
|
3
|
||||||
<th className='sortable' onClick={sortShips('boost')}>{units['m/s']}</th>
|
</th>
|
||||||
<th> </th>
|
<th className="sortable" onClick={sortShips('int', 3)}>
|
||||||
<th className='sortable' onClick={sortShips('baseShieldStrength')}>{units.MJ}</th>
|
4
|
||||||
<th className='sortable lft' onClick={sortShips('topSpeed')}>{units['m/s']}</th>
|
</th>
|
||||||
<th className='sortable' onClick={sortShips('topBoost')}>{units['m/s']}</th>
|
<th className="sortable" onClick={sortShips('int', 4)}>
|
||||||
<th className='sortable' onClick={sortShips('maxJumpRange')}>{units.LY}</th>
|
5
|
||||||
<th className='sortable' onClick={sortShips('maxCargo')}>{units.T}</th>
|
</th>
|
||||||
<th> </th>
|
<th className="sortable" onClick={sortShips('int', 5)}>
|
||||||
<th className='sortable lft' onMouseEnter={termtip.bind(null, 'power plant')} onMouseLeave={hide} onClick={sortShips('standard', 0)}>{'pp'}</th>
|
6
|
||||||
<th className='sortable' onMouseEnter={termtip.bind(null, 'thrusters')} onMouseLeave={hide} onClick={sortShips('standard', 1)}>{'th'}</th>
|
</th>
|
||||||
<th className='sortable' onMouseEnter={termtip.bind(null, 'frame shift drive')} onMouseLeave={hide} onClick={sortShips('standard', 2)}>{'fsd'}</th>
|
<th className="sortable" onClick={sortShips('int', 6)}>
|
||||||
<th className='sortable' onMouseEnter={termtip.bind(null, 'life support')} onMouseLeave={hide} onClick={sortShips('standard', 3)}>{'ls'}</th>
|
7
|
||||||
<th className='sortable' onMouseEnter={termtip.bind(null, 'power distriubtor')} onMouseLeave={hide} onClick={sortShips('standard', 4)}>{'pd'}</th>
|
</th>
|
||||||
<th className='sortable' onMouseEnter={termtip.bind(null, 'sensors')} onMouseLeave={hide} onClick={sortShips('standard', 5)}>{'s'}</th>
|
<th className="sortable" onClick={sortShips('int', 7)}>
|
||||||
<th className='sortable' onMouseEnter={termtip.bind(null, 'fuel tank')} onMouseLeave={hide} onClick={sortShips('standard', 6)}>{'ft'}</th>
|
8
|
||||||
<th className='sortable lft' onClick={sortShips('hp',1)}>{translate('S')}</th>
|
</th>
|
||||||
<th className='sortable' onClick={sortShips('hp', 2)}>{translate('M')}</th>
|
</tr>
|
||||||
<th className='sortable' onClick={sortShips('hp', 3)}>{translate('L')}</th>
|
</thead>
|
||||||
<th className='sortable' onClick={sortShips('hp', 4)}>{translate('H')}</th>
|
<tbody onMouseLeave={this._highlightShip.bind(this, null)}>
|
||||||
<th className='sortable' onClick={sortShips('hp', 0)}>{translate('U')}</th>
|
{detailRows}
|
||||||
|
</tbody>
|
||||||
<th className='sortable lft' onClick={sortShips('int', 0)} >1</th>
|
</table>
|
||||||
<th className='sortable' onClick={sortShips('int', 1)} >2</th>
|
</div>
|
||||||
<th className='sortable' onClick={sortShips('int', 2)} >3</th>
|
</div>
|
||||||
<th className='sortable' onClick={sortShips('int', 3)} >4</th>
|
<div className="table-tools" >
|
||||||
<th className='sortable' onClick={sortShips('int', 4)} >5</th>
|
<label><input type="checkbox" checked={this.state.groupCompared} onClick={() => this._toggleGroupCompared()}/>Group highlighted ships</label>
|
||||||
<th className='sortable' onClick={sortShips('int', 5)} >6</th>
|
|
||||||
<th className='sortable' onClick={sortShips('int', 6)} >7</th>
|
|
||||||
<th className='sortable' onClick={sortShips('int', 7)} >8</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody onMouseLeave={this._highlightShip.bind(this, null)}>
|
|
||||||
{detailRows}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ export function jumpRange(mass, fsd, fuel, ship) {
|
|||||||
let jumpAddition = 0;
|
let jumpAddition = 0;
|
||||||
if (ship) {
|
if (ship) {
|
||||||
for (const module of ship.internal) {
|
for (const module of ship.internal) {
|
||||||
if (module && module.m && module.m.grp === 'gfsb') {
|
if (module && module.m && module.m.grp === 'gfsb' && ship.getSlotStatus(module) == 3) {
|
||||||
jumpAddition += module.m.getJumpBoost();
|
jumpAddition += module.m.getJumpBoost();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -400,7 +400,7 @@ export function shieldMetrics(ship, sys) {
|
|||||||
let shieldAddition = 0;
|
let shieldAddition = 0;
|
||||||
if (ship) {
|
if (ship) {
|
||||||
for (const module of ship.internal) {
|
for (const module of ship.internal) {
|
||||||
if (module && module.m && module.m.grp === 'gsrp') {
|
if (module && module.m && module.m.grp === 'gsrp' && module.enabled) {
|
||||||
shieldAddition += module.m.getShieldAddition();
|
shieldAddition += module.m.getShieldAddition();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -465,6 +465,7 @@ export function shieldMetrics(ship, sys) {
|
|||||||
boosters: boostersStrength,
|
boosters: boostersStrength,
|
||||||
addition: shieldAddition,
|
addition: shieldAddition,
|
||||||
cells: ship.shieldCells,
|
cells: ship.shieldCells,
|
||||||
|
summary: generatorStrength + boostersStrength + shieldAddition,
|
||||||
total: generatorStrength + boostersStrength + ship.shieldCells + shieldAddition,
|
total: generatorStrength + boostersStrength + ship.shieldCells + shieldAddition,
|
||||||
recover,
|
recover,
|
||||||
recharge,
|
recharge,
|
||||||
@@ -573,7 +574,7 @@ export function armourMetrics(ship) {
|
|||||||
// };
|
// };
|
||||||
// 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.enabled && (slot.m.grp === 'hr' || slot.m.grp === 'ghrp' || slot.m.grp == 'mahr')) {
|
||||||
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;
|
||||||
@@ -585,7 +586,7 @@ export function armourMetrics(ship) {
|
|||||||
hullThermDmg = hullThermDmg * (1 - slot.m.getThermalResistance());
|
hullThermDmg = hullThermDmg * (1 - slot.m.getThermalResistance());
|
||||||
hullCausDmg = hullCausDmg * (1 - slot.m.getCausticResistance());
|
hullCausDmg = hullCausDmg * (1 - slot.m.getCausticResistance());
|
||||||
}
|
}
|
||||||
if (slot.m && (slot.m.grp == 'mrp' || slot.m.grp == 'gmrp')) {
|
if (slot.m && slot.enabled && (slot.m.grp == 'mrp' || slot.m.grp == 'gmrp')) {
|
||||||
moduleArmour += slot.m.getIntegrity();
|
moduleArmour += slot.m.getIntegrity();
|
||||||
moduleProtection = moduleProtection * (1 - slot.m.getProtection());
|
moduleProtection = moduleProtection * (1 - slot.m.getProtection());
|
||||||
}
|
}
|
||||||
@@ -1014,7 +1015,10 @@ export function timeToDrainWep(ship, wep) {
|
|||||||
*/
|
*/
|
||||||
export function timeToDeplete(amount, dps, eps, capacity, recharge) {
|
export function timeToDeplete(amount, dps, eps, capacity, recharge) {
|
||||||
const drainPerSecond = eps - recharge;
|
const drainPerSecond = eps - recharge;
|
||||||
if (drainPerSecond <= 0) {
|
// If there is nothing to remove, we're don instantly
|
||||||
|
if (!amount) {
|
||||||
|
return 0;
|
||||||
|
} if (drainPerSecond <= 0) {
|
||||||
// Simple result
|
// Simple result
|
||||||
return amount / dps;
|
return amount / dps;
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -57,6 +57,7 @@ export const ModuleGroupToName = {
|
|||||||
ghrp: 'Guardian Hull Reinforcement Package',
|
ghrp: 'Guardian Hull Reinforcement Package',
|
||||||
gmrp: 'Guardian Module Reinforcement Package',
|
gmrp: 'Guardian Module Reinforcement Package',
|
||||||
mahr: 'Meta Alloy Hull Reinforcement Package',
|
mahr: 'Meta Alloy Hull Reinforcement Package',
|
||||||
|
sua: 'Supercruise Assist',
|
||||||
|
|
||||||
// Hard Points
|
// Hard Points
|
||||||
bl: 'Beam Laser',
|
bl: 'Beam Laser',
|
||||||
@@ -94,6 +95,10 @@ export const ModuleGroupToName = {
|
|||||||
gsc: 'Guardian Shard Cannon',
|
gsc: 'Guardian Shard Cannon',
|
||||||
tbem: 'Enzyme Missile Rack',
|
tbem: 'Enzyme Missile Rack',
|
||||||
tbrfl: 'Remote Release Flechette Launcher',
|
tbrfl: 'Remote Release Flechette Launcher',
|
||||||
|
pwa: 'Pulse Wave Analyser',
|
||||||
|
abl: 'Abrasion Blaster',
|
||||||
|
scl: 'Seismic Charge Launcher',
|
||||||
|
sdm: 'Sub-Surface Displacement Missile',
|
||||||
};
|
};
|
||||||
|
|
||||||
let GrpNameToCodeMap = {};
|
let GrpNameToCodeMap = {};
|
||||||
@@ -202,7 +207,7 @@ export const ShipFacets = [
|
|||||||
i: 9
|
i: 9
|
||||||
},
|
},
|
||||||
{ // 10
|
{ // 10
|
||||||
title: 'fastest range',
|
title: 'farthest range',
|
||||||
props: ['unladenFastestRange', 'ladenFastestRange'],
|
props: ['unladenFastestRange', 'ladenFastestRange'],
|
||||||
lbls: ['unladen', 'laden'],
|
lbls: ['unladen', 'laden'],
|
||||||
unit: 'LY',
|
unit: 'LY',
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
* Modification - a modification and its value
|
* Modification - a modification and its value
|
||||||
*/
|
*/
|
||||||
export default class Modification {
|
export default class Modification {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {String} id Unique modification ID
|
* @param {String} id Unique modification ID
|
||||||
* @param {Number} value Value of the modification
|
* @param {Number} value Value of the modification
|
||||||
@@ -11,5 +10,4 @@ export default class Modification {
|
|||||||
this.id = id;
|
this.id = id;
|
||||||
this.value = value;
|
this.value = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import { STATS_FORMATTING, SI_PREFIXES } from './StatsFormatting';
|
|||||||
* Module - active module in a ship's buildout
|
* Module - active module in a ship's buildout
|
||||||
*/
|
*/
|
||||||
export default class Module {
|
export default class Module {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Construct a new module
|
* Construct a new module
|
||||||
* @param {Object} params Module parameters. Either grp/id or template
|
* @param {Object} params Module parameters. Either grp/id or template
|
||||||
@@ -42,6 +41,7 @@ export default class Module {
|
|||||||
* @return {object} The value of the modification. If it is a numeric value then it is returned as an integer value scaled so that 1.23% == 123
|
* @return {object} The value of the modification. If it is a numeric value then it is returned as an integer value scaled so that 1.23% == 123
|
||||||
*/
|
*/
|
||||||
getModValue(name, raw) {
|
getModValue(name, raw) {
|
||||||
|
let baseVal = this[name];
|
||||||
let result = this.mods && this.mods[name] ? this.mods[name] : null;
|
let result = this.mods && this.mods[name] ? this.mods[name] : null;
|
||||||
|
|
||||||
if ((!raw) && this.blueprint && this.blueprint.special) {
|
if ((!raw) && this.blueprint && this.blueprint.special) {
|
||||||
@@ -52,39 +52,18 @@ export default class Module {
|
|||||||
const modification = Modifications.modifications[name];
|
const modification = Modifications.modifications[name];
|
||||||
const multiplier = modification.type === 'percentage' ? 10000 : 100;
|
const multiplier = modification.type === 'percentage' ? 10000 : 100;
|
||||||
if (name === 'explres' || name === 'kinres' || name === 'thermres' || name === 'causres') {
|
if (name === 'explres' || name === 'kinres' || name === 'thermres' || name === 'causres') {
|
||||||
// Resistance modifications in itself are additive, however their
|
// Apply resistance modding mechanisms to special effects subsequently
|
||||||
// special effects are multiplicative. They affect the overall result
|
result = result + modifierActions[name] * (1 - (this[name] + result / multiplier)) * 100;
|
||||||
// by (special effect resistance) * (damage mult after modification),
|
|
||||||
// i. e. we need to apply the special effect as a multiplier to the
|
|
||||||
// overall result and then calculate the difference.
|
|
||||||
let baseMult = this[name] ? 1 - this[name] : 1;
|
|
||||||
result = (baseMult - (baseMult - result / multiplier) * (1 - modifierActions[name] / 100)) * multiplier;
|
|
||||||
} else if (modification.method === 'additive') {
|
} else if (modification.method === 'additive') {
|
||||||
result = result + modifierActions[name] * 100;
|
result = result + modifierActions[name] * 100;
|
||||||
} else if (modification.method === 'overwrite') {
|
} else if (modification.method === 'overwrite') {
|
||||||
result = modifierActions[name];
|
result = modifierActions[name];
|
||||||
} else {
|
} else {
|
||||||
// rate of fire is special, as it's really burst interval. Handle that here
|
result = (((1 + result / multiplier) * (1 + modifierActions[name])) - 1) * multiplier;
|
||||||
let mod = null;
|
|
||||||
if (name === 'rof') {
|
|
||||||
mod = 1 / (1 + modifierActions[name]) - 1;
|
|
||||||
} else {
|
|
||||||
mod = modifierActions[name];
|
|
||||||
}
|
|
||||||
result = (((1 + result / multiplier) * (1 + mod)) - 1) * multiplier;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Resistance modding for hull reinforcement packages has additional
|
|
||||||
// diminishing returns implemented. The mod value gets lowered by
|
|
||||||
// the amount of base resistance the hrp has.
|
|
||||||
if (!isNaN(result) && this.grp === 'hr' &&
|
|
||||||
(name === 'kinres' || name === 'thermres' || name === 'explres')) {
|
|
||||||
let baseRes = this[name];
|
|
||||||
result = result * (1 - baseRes);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sanitise the resultant value to 4dp equivalent
|
// Sanitise the resultant value to 4dp equivalent
|
||||||
return isNaN(result) ? result : Math.round(result);
|
return isNaN(result) ? result : Math.round(result);
|
||||||
}
|
}
|
||||||
@@ -109,24 +88,17 @@ export default class Module {
|
|||||||
// This special effect modifies the value being set, so we need to revert it prior to storing the value
|
// This special effect modifies the value being set, so we need to revert it prior to storing the value
|
||||||
const modification = Modifications.modifications[name];
|
const modification = Modifications.modifications[name];
|
||||||
if (name === 'explres' || name === 'kinres' || name === 'thermres' || name === 'causres') {
|
if (name === 'explres' || name === 'kinres' || name === 'thermres' || name === 'causres') {
|
||||||
// Resistance modifications in itself are additive but their
|
let res = (this[name] ? this[name] : 0) + value / 10000;
|
||||||
// experimentals are applied multiplicatively therefor we must handle
|
let experimental = modifierActions[name] / 100;
|
||||||
// them differently here (cf. documentation in getModValue).
|
value = (experimental - res) / (experimental - 1) - this[name];
|
||||||
let baseMult = (this[name] ? 1 - this[name] : 1);
|
value *= 10000;
|
||||||
value = ((baseMult - value / 10000) / (1 - modifierActions[name] / 100) - baseMult) * -10000;
|
// value = ((baseMult - value / 10000) / (1 - modifierActions[name] / 100) - baseMult) * -10000;
|
||||||
} else if (modification.method === 'additive') {
|
} else if (modification.method === 'additive') {
|
||||||
value = value - modifierActions[name];
|
value = value - modifierActions[name];
|
||||||
} else if (modification.method === 'overwrite') {
|
} else if (modification.method === 'overwrite') {
|
||||||
value = null;
|
value = null;
|
||||||
} else {
|
} else {
|
||||||
// rate of fire is special, as it's really burst interval. Handle that here
|
value = ((value / 10000 + 1) / (1 + modifierActions[name]) - 1) * 10000;
|
||||||
let mod = null;
|
|
||||||
if (name === 'rof') {
|
|
||||||
mod = 1 / (1 + modifierActions[name]) - 1;
|
|
||||||
} else {
|
|
||||||
mod = modifierActions[name];
|
|
||||||
}
|
|
||||||
value = ((value / 10000 + 1) / (1 + mod) - 1) * 10000;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -145,6 +117,13 @@ export default class Module {
|
|||||||
* @return {Number} The value queried
|
* @return {Number} The value queried
|
||||||
*/
|
*/
|
||||||
get(name, modified = true) {
|
get(name, modified = true) {
|
||||||
|
if (name == 'rof' && isNaN(this[name])) {
|
||||||
|
let fireint = this['fireint'];
|
||||||
|
if (!isNaN(fireint)) {
|
||||||
|
this['rof'] = 1 / fireint;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let val;
|
let val;
|
||||||
if (modified) {
|
if (modified) {
|
||||||
val = this._getModifiedValue(name);
|
val = this._getModifiedValue(name);
|
||||||
@@ -178,21 +157,23 @@ export default class Module {
|
|||||||
baseValue = 0;
|
baseValue = 0;
|
||||||
}
|
}
|
||||||
modValue = value - baseValue;
|
modValue = value - baseValue;
|
||||||
if (this.grp === 'hr' &&
|
|
||||||
(name === 'kinres' || name === 'thermres' || name === 'explres')) {
|
|
||||||
modValue = modValue / (1 - baseValue);
|
|
||||||
}
|
|
||||||
} else if (name === 'shieldboost' || name === 'hullboost') {
|
} else if (name === 'shieldboost' || name === 'hullboost') {
|
||||||
modValue = (1 + value) / (1 + baseValue) - 1;
|
modValue = (1 + value) / (1 + baseValue) - 1;
|
||||||
|
} else if (name === 'rof') {
|
||||||
|
let burst = this.get('burst', true) || 1;
|
||||||
|
let burstInt = 1 / (this.get('burstrof', true) / 1);
|
||||||
|
|
||||||
|
let interval = burst / value;
|
||||||
|
let newFireint = (interval - (burst - 1) * burstInt);
|
||||||
|
modValue = newFireint / this['fireint'] - 1;
|
||||||
} else { // multiplicative
|
} else { // multiplicative
|
||||||
modValue = value / baseValue - 1;
|
modValue = baseValue == 0 ? 0 : value / baseValue - 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (modification.type === 'percentage') {
|
if (modification.type === 'percentage') {
|
||||||
modValue = modValue * 10000;
|
modValue = modValue * 10000;
|
||||||
} else if (modification.type === 'numeric' && name !== 'burst' &&
|
} else if (modification.type === 'numeric') {
|
||||||
name !== 'burstrof') {
|
modValue = modValue * 100;
|
||||||
modValue = modValue * 100;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setModValue(name, modValue, valueIsWithSpecial);
|
this.setModValue(name, modValue, valueIsWithSpecial);
|
||||||
@@ -209,7 +190,14 @@ export default class Module {
|
|||||||
*/
|
*/
|
||||||
getPretty(name, modified = true, places = 2) {
|
getPretty(name, modified = true, places = 2) {
|
||||||
const formattingOptions = STATS_FORMATTING[name];
|
const formattingOptions = STATS_FORMATTING[name];
|
||||||
let val = this.get(name, modified) || 0;
|
let val;
|
||||||
|
if (formattingOptions && formattingOptions.synthetic) {
|
||||||
|
val = (this[formattingOptions.synthetic]).call(this, modified);
|
||||||
|
} else {
|
||||||
|
val = this.get(name, modified);
|
||||||
|
}
|
||||||
|
val = val || 0;
|
||||||
|
|
||||||
if (formattingOptions && formattingOptions.format.startsWith('pct')) {
|
if (formattingOptions && formattingOptions.format.startsWith('pct')) {
|
||||||
return 100 * val;
|
return 100 * val;
|
||||||
}
|
}
|
||||||
@@ -242,38 +230,43 @@ export default class Module {
|
|||||||
const modification = Modifications.modifications[name];
|
const modification = Modifications.modifications[name];
|
||||||
let result = this[name];
|
let result = this[name];
|
||||||
|
|
||||||
if (modification) {
|
if (modification) {
|
||||||
// We store percentages as decimals, so to get them back we need to divide by 10000. Otherwise
|
// We store percentages as decimals, so to get them back we need to divide by 10000. Otherwise
|
||||||
// we divide by 100. Both ways we end up with a value with two decimal places
|
// we divide by 100. Both ways we end up with a value with two decimal places
|
||||||
let modValue;
|
let modValue;
|
||||||
if (modification.type === 'percentage') {
|
if (modification.type === 'percentage') {
|
||||||
modValue = this.getModValue(name) / 10000;
|
modValue = this.getModValue(name) / 10000;
|
||||||
} else if (modification.type === 'numeric') {
|
} else if (modification.type === 'numeric') {
|
||||||
modValue = this.getModValue(name) / 100;
|
modValue = this.getModValue(name) / 100;
|
||||||
} else {
|
} else {
|
||||||
modValue = this.getModValue(name);
|
modValue = this.getModValue(name);
|
||||||
|
}
|
||||||
|
if (modValue) {
|
||||||
|
if (!result && modification.method === 'additive') {
|
||||||
|
// If the modification is additive and no value is given by default we
|
||||||
|
// start at zero
|
||||||
|
result = 0;
|
||||||
}
|
}
|
||||||
if (modValue) {
|
|
||||||
if (!result && modification.method === 'additive') {
|
|
||||||
// If the modification is additive and no value is given by default we
|
|
||||||
// start at zero
|
|
||||||
result = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result !== undefined) {
|
if (result !== undefined) {
|
||||||
if (modification.method === 'additive') {
|
if (modification.method === 'additive') {
|
||||||
result = result + modValue;
|
result = result + modValue;
|
||||||
} else if (modification.method === 'overwrite') {
|
} else if (modification.method === 'overwrite') {
|
||||||
result = modValue;
|
result = modValue;
|
||||||
} else if (name === 'shieldboost' || name === 'hullboost') {
|
} else if (name === 'shieldboost' || name === 'hullboost') {
|
||||||
result = (1 + result) * (1 + modValue) - 1;
|
result = (1 + result) * (1 + modValue) - 1;
|
||||||
} else {
|
} else {
|
||||||
result = result * (1 + modValue);
|
// Rate of fire modifiers are special as they actually are modifiers
|
||||||
|
// for fire interval. Translate them accordingly here:
|
||||||
|
if (name == 'rof') {
|
||||||
|
modValue = 1 / (1 + modValue) - 1;
|
||||||
}
|
}
|
||||||
} else if (name === 'burst' || name === 'burstrof') {
|
result = result * (1 + modValue);
|
||||||
// Burst and burst rate of fire are special, as it can not exist but
|
}
|
||||||
// have a modification
|
} else if (name === 'burstrof' || name === 'burst') {
|
||||||
result = modValue / 100;
|
// Burst and burst rate of fire are special, as it can not exist but
|
||||||
|
// have a modification
|
||||||
|
result = modValue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -295,11 +288,7 @@ export default class Module {
|
|||||||
formatModifiedValue(name, language, unit, val) {
|
formatModifiedValue(name, language, unit, val) {
|
||||||
const formattingOptions = STATS_FORMATTING[name];
|
const formattingOptions = STATS_FORMATTING[name];
|
||||||
if (val === undefined) {
|
if (val === undefined) {
|
||||||
if (formattingOptions && formattingOptions.synthetic) {
|
val = this.getPretty(name, true);
|
||||||
val = (this[formattingOptions.synthetic]).call(this, true);
|
|
||||||
} else {
|
|
||||||
val = this._getModifiedValue(name);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val = val || 0;
|
val = val || 0;
|
||||||
@@ -369,14 +358,18 @@ export default class Module {
|
|||||||
|
|
||||||
if (formattingOptions && formattingOptions.change) {
|
if (formattingOptions && formattingOptions.change) {
|
||||||
let changeFormatting = formattingOptions.change;
|
let changeFormatting = formattingOptions.change;
|
||||||
let baseVal = this[name];
|
let baseVal = this[name] || 0;
|
||||||
let absVal = this._getModifiedValue(name);
|
let absVal = this._getModifiedValue(name);
|
||||||
if (changeFormatting === 'additive') {
|
if (changeFormatting === 'additive') {
|
||||||
val = absVal - baseVal;
|
val = absVal - baseVal;
|
||||||
} else if (changeFormatting === 'multiplicative') {
|
} else if (changeFormatting === 'multiplicative') {
|
||||||
val = absVal / baseVal - 1;
|
val = absVal / baseVal - 1;
|
||||||
}
|
}
|
||||||
val *= 10000;
|
if (Modifications.modifications[name].method === 'overwrite') {
|
||||||
|
val *= 100;
|
||||||
|
} else {
|
||||||
|
val *= 10000;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return val;
|
return val;
|
||||||
}
|
}
|
||||||
@@ -590,20 +583,9 @@ export default class Module {
|
|||||||
* @return {Number} the falloff of this module
|
* @return {Number} the falloff of this module
|
||||||
*/
|
*/
|
||||||
getFalloff(modified = true) {
|
getFalloff(modified = true) {
|
||||||
if (!modified) {
|
|
||||||
const range = this.getRange(false);
|
|
||||||
const falloff = this.get('falloff', false);
|
|
||||||
return (falloff > range ? range : falloff);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Falloff from range is mapped to range
|
// Falloff from range is mapped to range
|
||||||
if (this.mods && this.mods['fallofffromrange']) {
|
if (modified && this.mods && this.mods['fallofffromrange']) {
|
||||||
return this.getRange();
|
return this.getRange();
|
||||||
// Need to find out if we have a focused modification, in which case our
|
|
||||||
// falloff is scaled to range
|
|
||||||
} else if (this.blueprint && this.blueprint.name === 'Focused') {
|
|
||||||
const rangeMod = this.getModValue('range') / 10000;
|
|
||||||
return this.falloff * (1 + rangeMod);
|
|
||||||
// Standard falloff calculation
|
// Standard falloff calculation
|
||||||
} else {
|
} else {
|
||||||
const range = this.getRange();
|
const range = this.getRange();
|
||||||
@@ -657,15 +639,6 @@ export default class Module {
|
|||||||
return this.get('protection', modified);
|
return this.get('protection', modified);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the delay for this module
|
|
||||||
* @param {Boolean} [modified=true] Whether to take modifications into account
|
|
||||||
* @return {Number} the delay of this module
|
|
||||||
*/
|
|
||||||
getDelay(modified = true) {
|
|
||||||
return this.get('delay', modified);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the duration for this module
|
* Get the duration for this module
|
||||||
* @param {Boolean} [modified=true] Whether to take modifications into account
|
* @param {Boolean} [modified=true] Whether to take modifications into account
|
||||||
@@ -721,8 +694,7 @@ export default class Module {
|
|||||||
let result = 0;
|
let result = 0;
|
||||||
if (this['maxmass']) {
|
if (this['maxmass']) {
|
||||||
result = this['maxmass'];
|
result = this['maxmass'];
|
||||||
// max mass is only modified for non-shield boosters
|
if (result && modified) {
|
||||||
if (result && modified && this.grp !== 'sg') {
|
|
||||||
let mult = this.getModValue('optmass') / 10000;
|
let mult = this.getModValue('optmass') / 10000;
|
||||||
if (mult) { result = result * (1 + mult); }
|
if (mult) { result = result * (1 + mult); }
|
||||||
}
|
}
|
||||||
@@ -811,24 +783,6 @@ export default class Module {
|
|||||||
return this.get('distdraw', modified);
|
return this.get('distdraw', modified);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the thermal load for this module
|
|
||||||
* @param {Boolean} [modified=true] Whether to take modifications into account
|
|
||||||
* @return {Number} the thermal load of this module
|
|
||||||
*/
|
|
||||||
getThermalLoad(modified = true) {
|
|
||||||
return this.get('thermload', modified);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the rounds per shot for this module
|
|
||||||
* @param {Boolean} [modified=true] Whether to take modifications into account
|
|
||||||
* @return {Number} the rounds per shot of this module
|
|
||||||
*/
|
|
||||||
getRoundsPerShot(modified = true) {
|
|
||||||
return this.get('roundspershot', modified);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the DPS for this module
|
* Get the DPS for this module
|
||||||
* @param {Boolean} [modified=true] Whether to take modifications into account
|
* @param {Boolean} [modified=true] Whether to take modifications into account
|
||||||
@@ -853,26 +807,40 @@ export default class Module {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the SDPS for this module
|
* Return the factor that gets applied when calculating certain "sustained"
|
||||||
|
* values, e.g. `SDPS = this.getSustainedFactor() * DPS`.
|
||||||
* @param {Boolean} [modified=true] Whether to take modifications into account
|
* @param {Boolean} [modified=true] Whether to take modifications into account
|
||||||
* @return {Number} The SDPS of this module
|
|
||||||
*/
|
*/
|
||||||
getSDps(modified = true) {
|
getSustainedFactor(modified = true) {
|
||||||
let dps = this.getDps(modified);
|
let clipSize = this.getClip(modified);
|
||||||
if (this.getClip(modified)) {
|
if (clipSize) {
|
||||||
let clipSize = this.getClip(modified);
|
|
||||||
// If auto-loader is applied, effective clip size will be nearly doubled
|
// If auto-loader is applied, effective clip size will be nearly doubled
|
||||||
// as you get one reload for every two shots fired.
|
// as you get one reload for every two shots fired.
|
||||||
if (this.blueprint && this.blueprint.special && this.blueprint.special.edname === 'special_auto_loader' && modified) {
|
if (this.blueprint && this.blueprint.special && this.blueprint.special.edname === 'special_auto_loader' && modified) {
|
||||||
clipSize += clipSize - 1;
|
clipSize += clipSize - 1;
|
||||||
}
|
}
|
||||||
let timeToDeplete = clipSize / this.getRoF(modified);
|
|
||||||
return dps * timeToDeplete / (timeToDeplete + this.getReload(modified));
|
let burstSize = this.get('burst', modified) || 1;
|
||||||
|
let rof = this.getRoF(modified);
|
||||||
|
// rof averages burstfire + pause until next interval but for sustained
|
||||||
|
// rof we need to take another burst without pause into account
|
||||||
|
let burstOverhead = (burstSize - 1) / (this.get('burstrof', modified) || 1);
|
||||||
|
let srof = clipSize / ((clipSize - burstSize) / rof + burstOverhead + this.getReload(modified));
|
||||||
|
return srof / rof;
|
||||||
} else {
|
} else {
|
||||||
return dps;
|
return 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the SDPS for this module
|
||||||
|
* @param {Boolean} [modified=true] Whether to take modifications into account
|
||||||
|
* @return {Number} The SDPS of this module
|
||||||
|
*/
|
||||||
|
getSDps(modified = true) {
|
||||||
|
return this.getDps(modified) * this.getSustainedFactor(modified);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the EPS for this module
|
* Get the EPS for this module
|
||||||
* @param {Boolean} [modified=true] Whether to take modifications into account
|
* @param {Boolean} [modified=true] Whether to take modifications into account
|
||||||
@@ -894,7 +862,7 @@ export default class Module {
|
|||||||
*/
|
*/
|
||||||
getHps(modified = true) {
|
getHps(modified = true) {
|
||||||
// HPS is a synthetic value
|
// HPS is a synthetic value
|
||||||
let heat = this.getThermalLoad(modified);
|
let heat = this.get('thermload', modified);
|
||||||
// We don't use rpshot here as dist draw is per combined shot
|
// We don't use rpshot here as dist draw is per combined shot
|
||||||
let rof = this.getRoF(modified) || 1;
|
let rof = this.getRoF(modified) || 1;
|
||||||
|
|
||||||
@@ -931,24 +899,6 @@ export default class Module {
|
|||||||
return this.get('reload', modified);
|
return this.get('reload', modified);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the burst size for this module
|
|
||||||
* @param {Boolean} [modified=true] Whether to take modifications into account
|
|
||||||
* @return {Number} the burst size of this module
|
|
||||||
*/
|
|
||||||
getBurst(modified = true) {
|
|
||||||
return this.get('burst', modified);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the burst rate of fire for this module
|
|
||||||
* @param {Boolean} [modified=true] Whether to take modifications into account
|
|
||||||
* @return {Number} the burst rate of fire of this module
|
|
||||||
*/
|
|
||||||
getBurstRoF(modified = true) {
|
|
||||||
return this.get('burstrof', modified);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the rate of fire for this module.
|
* Get the rate of fire for this module.
|
||||||
* The rate of fire is a combination value, and needs to take in to account
|
* The rate of fire is a combination value, and needs to take in to account
|
||||||
@@ -959,8 +909,8 @@ export default class Module {
|
|||||||
* @return {Number} the rate of fire for this module
|
* @return {Number} the rate of fire for this module
|
||||||
*/
|
*/
|
||||||
getRoF(modified = true) {
|
getRoF(modified = true) {
|
||||||
const burst = this.getBurst(modified) || 1;
|
const burst = this.get('burst', modified) || 1;
|
||||||
const burstRoF = this.getBurstRoF(modified) || 1;
|
const burstRoF = this.get('burstrof', modified) || 1;
|
||||||
const intRoF = this.get('rof', modified);
|
const intRoF = this.get('rof', modified);
|
||||||
|
|
||||||
return burst / (((burst - 1) / burstRoF) + 1 / intRoF);
|
return burst / (((burst - 1) / burstRoF) + 1 / intRoF);
|
||||||
@@ -1092,4 +1042,30 @@ export default class Module {
|
|||||||
return this.get('hacktime', modified);
|
return this.get('hacktime', modified);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the scan range for this module
|
||||||
|
* @param {Boolean} [modified=true] Whether to take modifications into account
|
||||||
|
* @return {string} the time for this module
|
||||||
|
*/
|
||||||
|
getScanRange(modified = true) {
|
||||||
|
return this.get('scanrange', modified);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the scan angle for this module
|
||||||
|
* @param {Boolean} [modified=true] Whether to take modifications into account
|
||||||
|
* @return {string} the time for this module
|
||||||
|
*/
|
||||||
|
getScanAngle(modified = true) {
|
||||||
|
return this.get('scanangle', modified);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the max angle for this module
|
||||||
|
* @param {Boolean} [modified=true] Whether to take modifications into account
|
||||||
|
* @return {string} the time for this module
|
||||||
|
*/
|
||||||
|
getMaxAngle(modified = true) {
|
||||||
|
return this.get('maxangle', modified);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ function filter(arr, maxClass, minClass, mass) {
|
|||||||
* The available module set for a specific ship
|
* The available module set for a specific ship
|
||||||
*/
|
*/
|
||||||
export default class ModuleSet {
|
export default class ModuleSet {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Instantiate the module set
|
* Instantiate the module set
|
||||||
* @param {Object} modules All Modules
|
* @param {Object} modules All Modules
|
||||||
|
|||||||
@@ -63,7 +63,10 @@ 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);
|
||||||
|
if (!s) {
|
||||||
|
s = Modules.standard[type].find(e => (e.class == id.charAt(0) && e.rating == id.charAt(1)));
|
||||||
|
}
|
||||||
if (s) {
|
if (s) {
|
||||||
s = new Module({ template: s });
|
s = new Module({ template: s });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -85,12 +85,12 @@ export function toDetailedBuild(buildName, ship) {
|
|||||||
code = ship.toString();
|
code = ship.toString();
|
||||||
|
|
||||||
let data = {
|
let data = {
|
||||||
$schema: 'https://coriolis.edcd.io/schemas/ship-loadout/4.json#',
|
$schema: 'https://coriolis.io/schemas/ship-loadout/4.json#',
|
||||||
name: buildName,
|
name: buildName,
|
||||||
ship: ship.name,
|
ship: ship.name,
|
||||||
references: [{
|
references: [{
|
||||||
name: 'Coriolis.io',
|
name: 'Coriolis.io',
|
||||||
url: 'https://coriolis.edcd.io' + outfitURL(ship.id, code, buildName),
|
url: 'https://coriolis.io' + outfitURL(ship.id, code, buildName),
|
||||||
code,
|
code,
|
||||||
shipId: ship.id
|
shipId: ship.id
|
||||||
}],
|
}],
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import { Ships, Modifications } from 'coriolis-data/dist';
|
|||||||
import { chain } from 'lodash';
|
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', 'gfsb', 'dc'];
|
||||||
|
|
||||||
// Constants for modifications struct
|
// Constants for modifications struct
|
||||||
const SLOT_ID_DONE = -1;
|
const SLOT_ID_DONE = -1;
|
||||||
@@ -71,7 +71,6 @@ function reduceToIDs(idArray, slot, slotIndex) {
|
|||||||
* Ship Model - Encapsulates and models in-game ship behavior
|
* Ship Model - Encapsulates and models in-game ship behavior
|
||||||
*/
|
*/
|
||||||
export default class Ship {
|
export default class Ship {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {String} id Unique ship Id / Key
|
* @param {String} id Unique ship Id / Key
|
||||||
* @param {Object} properties Basic ship properties such as name, manufacturer, mass, etc
|
* @param {Object} properties Basic ship properties such as name, manufacturer, mass, etc
|
||||||
@@ -416,16 +415,16 @@ export default class Ship {
|
|||||||
clearModifications(m) {
|
clearModifications(m) {
|
||||||
m.mods = {};
|
m.mods = {};
|
||||||
this.updatePowerGenerated()
|
this.updatePowerGenerated()
|
||||||
.updatePowerUsed()
|
.updatePowerUsed()
|
||||||
.recalculateMass()
|
.recalculateMass()
|
||||||
.updateJumpStats()
|
.updateJumpStats()
|
||||||
.recalculateShield()
|
.recalculateShield()
|
||||||
.recalculateShieldCells()
|
.recalculateShieldCells()
|
||||||
.recalculateArmour()
|
.recalculateArmour()
|
||||||
.recalculateDps()
|
.recalculateDps()
|
||||||
.recalculateEps()
|
.recalculateEps()
|
||||||
.recalculateHps()
|
.recalculateHps()
|
||||||
.updateMovement();
|
.updateMovement();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -506,6 +505,11 @@ export default class Ship {
|
|||||||
if (isAbsolute) {
|
if (isAbsolute) {
|
||||||
m.setPretty(name, value, sentfromui);
|
m.setPretty(name, value, sentfromui);
|
||||||
} else {
|
} else {
|
||||||
|
// Resistance modifiers scale with the base value
|
||||||
|
if (name == 'kinres' || name == 'thermres' || name == 'causres' || name == 'explres') {
|
||||||
|
let baseValue = m.get(name, false);
|
||||||
|
value = (1 - baseValue) * value;
|
||||||
|
}
|
||||||
m.setModValue(name, value, sentfromui);
|
m.setModValue(name, value, sentfromui);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -696,16 +700,16 @@ export default class Ship {
|
|||||||
// Update aggragated stats
|
// Update aggragated stats
|
||||||
if (comps) {
|
if (comps) {
|
||||||
this.updatePowerGenerated()
|
this.updatePowerGenerated()
|
||||||
.updatePowerUsed()
|
.updatePowerUsed()
|
||||||
.recalculateMass()
|
.recalculateMass()
|
||||||
.updateJumpStats()
|
.updateJumpStats()
|
||||||
.recalculateShield()
|
.recalculateShield()
|
||||||
.recalculateShieldCells()
|
.recalculateShieldCells()
|
||||||
.recalculateArmour()
|
.recalculateArmour()
|
||||||
.recalculateDps()
|
.recalculateDps()
|
||||||
.recalculateEps()
|
.recalculateEps()
|
||||||
.recalculateHps()
|
.recalculateHps()
|
||||||
.updateMovement();
|
.updateMovement();
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.updatePowerPrioritesString().updatePowerEnabledString().updateModificationsString();
|
return this.updatePowerPrioritesString().updatePowerEnabledString().updateModificationsString();
|
||||||
@@ -1187,28 +1191,28 @@ export default class Ship {
|
|||||||
// handle unladen mass
|
// handle unladen mass
|
||||||
unladenMass += chain(slots)
|
unladenMass += chain(slots)
|
||||||
.map(slot => slot.m ? slot.m.get('mass') : null)
|
.map(slot => slot.m ? slot.m.get('mass') : null)
|
||||||
.filter()
|
.map(mass => mass || 0)
|
||||||
.reduce((sum, mass) => sum + mass)
|
.reduce((sum, mass) => sum + mass)
|
||||||
.value();
|
.value();
|
||||||
|
|
||||||
// handle fuel capacity
|
// handle fuel capacity
|
||||||
fuelCapacity += chain(slots)
|
fuelCapacity += chain(slots)
|
||||||
.map(slot => slot.m ? slot.m.get('fuel') : null)
|
.map(slot => slot.m ? slot.m.get('fuel') : null)
|
||||||
.filter()
|
.map(fuel => fuel || 0)
|
||||||
.reduce((sum, fuel) => sum + fuel)
|
.reduce((sum, fuel) => sum + fuel)
|
||||||
.value();
|
.value();
|
||||||
|
|
||||||
// handle cargo capacity
|
// handle cargo capacity
|
||||||
cargoCapacity += chain(slots)
|
cargoCapacity += chain(slots)
|
||||||
.map(slot => slot.m ? slot.m.get('cargo') : null)
|
.map(slot => slot.m ? slot.m.get('cargo') : null)
|
||||||
.filter()
|
.map(cargo => cargo || 0)
|
||||||
.reduce((sum, cargo) => sum + cargo)
|
.reduce((sum, cargo) => sum + cargo)
|
||||||
.value();
|
.value();
|
||||||
|
|
||||||
// handle passenger capacity
|
// handle passenger capacity
|
||||||
passengerCapacity += chain(slots)
|
passengerCapacity += chain(slots)
|
||||||
.map(slot => slot.m ? slot.m.get('passengers') : null)
|
.map(slot => slot.m ? slot.m.get('passengers') : null)
|
||||||
.filter()
|
.map(passengers => passengers || 0)
|
||||||
.reduce((sum, passengers) => sum + passengers)
|
.reduce((sum, passengers) => sum + passengers)
|
||||||
.value();
|
.value();
|
||||||
|
|
||||||
@@ -1304,7 +1308,7 @@ 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, fuelCapacity, this); // 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);
|
||||||
this.ladenFastestRange = Calc.totalJumpRange(unladenMass + this.fuelCapacity + this.cargoCapacity, fsd, fuelCapacity, this);
|
this.ladenFastestRange = Calc.totalJumpRange(unladenMass + this.fuelCapacity + this.cargoCapacity, fsd, fuelCapacity, this);
|
||||||
@@ -1502,7 +1506,7 @@ export default class Ship {
|
|||||||
} else {
|
} else {
|
||||||
buffer.writeInt32LE(slotMod.value, curpos);
|
buffer.writeInt32LE(slotMod.value, curpos);
|
||||||
}
|
}
|
||||||
// const modification = _.find(Modifications.modifications, function(o) { return o.id === slotMod.id; });
|
const modification = _.find(Modifications.modifications, function(o) { return o.id === slotMod.id; });
|
||||||
// console.log('ENCODE Slot ' + i + ': ' + modification.name + ' = ' + slotMod.value);
|
// console.log('ENCODE Slot ' + i + ': ' + modification.name + ' = ' + slotMod.value);
|
||||||
curpos += 4;
|
curpos += 4;
|
||||||
}
|
}
|
||||||
@@ -1515,6 +1519,7 @@ export default class Ship {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.serialized.modifications = zlib.gzipSync(buffer).toString('base64');
|
this.serialized.modifications = zlib.gzipSync(buffer).toString('base64');
|
||||||
|
// console.log(this.serialized.modifications)
|
||||||
} else {
|
} else {
|
||||||
this.serialized.modifications = null;
|
this.serialized.modifications = null;
|
||||||
}
|
}
|
||||||
@@ -1683,11 +1688,11 @@ export default class Ship {
|
|||||||
updated;
|
updated;
|
||||||
|
|
||||||
this.useBulkhead(0)
|
this.useBulkhead(0)
|
||||||
.use(standard[2], fsd) // FSD
|
.use(standard[2], fsd) // FSD
|
||||||
.use(standard[3], ls) // Life Support
|
.use(standard[3], ls) // Life Support
|
||||||
.use(standard[5], s) // Sensors
|
.use(standard[5], s) // Sensors
|
||||||
.use(standard[4], pd) // Power Distributor
|
.use(standard[4], pd) // Power Distributor
|
||||||
.use(standard[6], ft); // Fuel Tank
|
.use(standard[6], ft); // Fuel Tank
|
||||||
|
|
||||||
// Turn off nearly everything
|
// Turn off nearly everything
|
||||||
if (m.fsdDisabled) this.setSlotEnabled(this.standard[2], false);
|
if (m.fsdDisabled) this.setSlotEnabled(this.standard[2], false);
|
||||||
|
|||||||
@@ -26,8 +26,8 @@ export const STATS_FORMATTING = {
|
|||||||
'ammo': { 'format': 'int', },
|
'ammo': { 'format': 'int', },
|
||||||
'boot': { 'format': 'int', 'unit': 'secs' },
|
'boot': { 'format': 'int', 'unit': 'secs' },
|
||||||
'brokenregen': { 'format': 'round1', 'unit': 'ps' },
|
'brokenregen': { 'format': 'round1', 'unit': 'ps' },
|
||||||
'burst': { 'format': 'int' },
|
'burst': { 'format': 'int', 'change': 'additive' },
|
||||||
'burstrof': { 'format': 'round1', 'unit': 'ps' },
|
'burstrof': { 'format': 'round1', 'unit': 'ps', 'change': 'additive' },
|
||||||
'causres': { 'format': 'pct' },
|
'causres': { 'format': 'pct' },
|
||||||
'clip': { 'format': 'int' },
|
'clip': { 'format': 'int' },
|
||||||
'damage': { 'format': 'round' },
|
'damage': { 'format': 'round' },
|
||||||
@@ -61,7 +61,7 @@ export const STATS_FORMATTING = {
|
|||||||
'ranget': { 'format': 'f1', 'unit': 's' },
|
'ranget': { 'format': 'f1', 'unit': 's' },
|
||||||
'regen': { 'format': 'round1', 'unit': 'ps' },
|
'regen': { 'format': 'round1', 'unit': 'ps' },
|
||||||
'reload': { 'format': 'int', 'unit': 's' },
|
'reload': { 'format': 'int', 'unit': 's' },
|
||||||
'rof': { 'format': 'round1', 'unit': 'ps' },
|
'rof': { 'format': 'round1', 'unit': 'ps', 'synthetic': 'getRoF', 'higherbetter': true },
|
||||||
'angle': { 'format': 'round1', 'unit': 'ang' },
|
'angle': { 'format': 'round1', 'unit': 'ang' },
|
||||||
'scanrate': { 'format': 'int' },
|
'scanrate': { 'format': 'int' },
|
||||||
'scantime': { 'format': 'round1', 'unit': 's' },
|
'scantime': { 'format': 'round1', 'unit': 's' },
|
||||||
@@ -78,5 +78,6 @@ export const STATS_FORMATTING = {
|
|||||||
'thermres': { 'format': 'pct' },
|
'thermres': { 'format': 'pct' },
|
||||||
'wepcap': { 'format': 'round1', 'unit': 'MJ' },
|
'wepcap': { 'format': 'round1', 'unit': 'MJ' },
|
||||||
'weprate': { 'format': 'round1', 'unit': 'MW' },
|
'weprate': { 'format': 'round1', 'unit': 'MW' },
|
||||||
'jumpboost': { 'format': 'round1', 'unit': 'LY' }
|
'jumpboost': { 'format': 'round1', 'unit': 'LY' },
|
||||||
|
'proberadius': { 'format': 'pct1', 'unit': 'pct' },
|
||||||
};
|
};
|
||||||
|
|||||||
33
src/app/shipyard/StatsMapping.js
Normal file
33
src/app/shipyard/StatsMapping.js
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import { Module } from 'ed-forge';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets a resistance value of a module as
|
||||||
|
* @param {Module} module Module to set the property
|
||||||
|
* @param {string} prop Property name; must end with 'resistance'
|
||||||
|
* @param {number} val Resistance value to set
|
||||||
|
*/
|
||||||
|
function setterResToEff(module, prop, val) {
|
||||||
|
module.set(
|
||||||
|
prop.replace('resistance', 'effectiveness'),
|
||||||
|
1 - val / 100,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SHOW = {
|
||||||
|
causticeffectiveness: {
|
||||||
|
as: 'causticresistance',
|
||||||
|
setter: setterResToEff,
|
||||||
|
},
|
||||||
|
explosiveeffectiveness: {
|
||||||
|
as: 'explosiveresistance',
|
||||||
|
setter: setterResToEff,
|
||||||
|
},
|
||||||
|
kineticeffectiveness: {
|
||||||
|
as: 'kineticresistance',
|
||||||
|
setter: setterResToEff,
|
||||||
|
},
|
||||||
|
thermiceffectiveness: {
|
||||||
|
as: 'thermicresistance',
|
||||||
|
setter: setterResToEff,
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -70,7 +70,6 @@ function _delete(key) {
|
|||||||
* export is an instance (see end of this file).
|
* export is an instance (see end of this file).
|
||||||
*/
|
*/
|
||||||
export class Persist extends EventEmitter {
|
export class Persist extends EventEmitter {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create an instance
|
* Create an instance
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1,60 +1,43 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Modifications } from 'coriolis-data/dist';
|
import { Modifications } from 'coriolis-data/dist';
|
||||||
|
import { Module } from 'ed-forge';
|
||||||
|
import { getBlueprintInfo, getExperimentalInfo } from 'ed-forge/lib/data/blueprints';
|
||||||
|
import { entries, keys, uniq } from 'lodash';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate a tooltip with details of a blueprint's specials
|
* Generate a tooltip with details of a blueprint's specials
|
||||||
* @param {Object} translate The translate object
|
* @param {Object} language The translate object
|
||||||
* @param {Object} blueprint The blueprint at the required grade
|
* @param {Module} m The module to compare with
|
||||||
* @param {string} grp The group of the module
|
|
||||||
* @param {Object} m The module to compare with
|
|
||||||
* @param {string} specialName The name of the special
|
* @param {string} specialName The name of the special
|
||||||
* @returns {Object} The react components
|
* @returns {Object} The react components
|
||||||
*/
|
*/
|
||||||
export function specialToolTip(translate, blueprint, grp, m, specialName) {
|
export function specialToolTip(language, m, specialName) {
|
||||||
const effects = [];
|
const { formats, translate } = language;
|
||||||
if (!blueprint || !blueprint.features) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
if (m) {
|
|
||||||
// We also add in any benefits from specials that aren't covered above
|
|
||||||
if (m.blueprint) {
|
|
||||||
for (const feature in Modifications.modifierActions[specialName]) {
|
|
||||||
// if (!blueprint.features[feature] && !m.mods.feature) {
|
|
||||||
const featureDef = Modifications.modifications[feature];
|
|
||||||
if (featureDef && !featureDef.hidden) {
|
|
||||||
let symbol = '';
|
|
||||||
if (feature === 'jitter') {
|
|
||||||
symbol = '°';
|
|
||||||
} else if (featureDef.type === 'percentage') {
|
|
||||||
symbol = '%';
|
|
||||||
}
|
|
||||||
let current = m.getModValue(feature) - m.getModValue(feature, true);
|
|
||||||
if (featureDef.type === 'percentage') {
|
|
||||||
current = Math.round(current / 10) / 10;
|
|
||||||
} else if (featureDef.type === 'numeric') {
|
|
||||||
current /= 100;
|
|
||||||
}
|
|
||||||
const currentIsBeneficial = isValueBeneficial(feature, current);
|
|
||||||
|
|
||||||
effects.push(
|
|
||||||
<tr key={feature + '_specialTT'}>
|
|
||||||
<td style={{ textAlign: 'left' }}>{translate(feature, grp)}</td>
|
|
||||||
<td> </td>
|
|
||||||
<td className={current === 0 ? '' : currentIsBeneficial ? 'secondary' : 'warning'}
|
|
||||||
style={{ textAlign: 'right' }}>{current}{symbol}</td>
|
|
||||||
<td> </td>
|
|
||||||
</tr>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<table width='100%'>
|
<table width='100%'>
|
||||||
<tbody>
|
<tbody>
|
||||||
{effects}
|
{entries(getExperimentalInfo(specialName).features).map(
|
||||||
|
([prop, feats]) => {
|
||||||
|
const { max, only } = feats;
|
||||||
|
if (only && !m.getItem().match(only)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { value, unit, beneficial } = m.getModifierFormatted(prop);
|
||||||
|
// If the product of value and min/max is positive, both values
|
||||||
|
// point into the same direction, i.e. positive/negative.
|
||||||
|
const specialBeneficial = (value * max) > 0 === beneficial;
|
||||||
|
|
||||||
|
return <tr key={prop + '_specialTT'}>
|
||||||
|
<td style={{ textAlign: 'left' }}>{translate(prop)}</td>
|
||||||
|
<td> </td>
|
||||||
|
<td className={specialBeneficial ? 'secondary' : 'warning'}
|
||||||
|
style={{ textAlign: 'right' }}>{formats.round(max * 100)}{unit}</td>
|
||||||
|
<td> </td>
|
||||||
|
</tr>;
|
||||||
|
}
|
||||||
|
)}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
@@ -62,224 +45,70 @@ export function specialToolTip(translate, blueprint, grp, m, specialName) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate a tooltip with details of a blueprint's effects
|
* Generate a tooltip with details and preview of a blueprint's effects
|
||||||
* @param {Object} translate The translate object
|
* @param {Object} language The language object
|
||||||
* @param {Object} blueprint The blueprint at the required grade
|
* @param {Module} m The module to compare with
|
||||||
* @param {Array} engineers The engineers supplying this blueprint
|
* @param {string} previewBP Blueprint to preview
|
||||||
* @param {string} grp The group of the module
|
* @param {number} previewGrade Grade to preview
|
||||||
* @param {Object} m The module to compare with
|
* @returns {Object} The react components
|
||||||
* @returns {Object} The react components
|
|
||||||
*/
|
*/
|
||||||
export function blueprintTooltip(translate, blueprint, engineers, grp, m) {
|
export function blueprintTooltip(language, m, previewBP, previewGrade) {
|
||||||
const effects = [];
|
const { translate, formats } = language;
|
||||||
if (!blueprint || !blueprint.features) {
|
const blueprint = previewBP || m.getBlueprint();
|
||||||
return undefined;
|
const grade = previewGrade || m.getBlueprintGrade();
|
||||||
}
|
if (!blueprint) {
|
||||||
for (const feature in blueprint.features) {
|
return null;
|
||||||
const featureIsBeneficial = isBeneficial(feature, blueprint.features[feature]);
|
|
||||||
const featureDef = Modifications.modifications[feature];
|
|
||||||
if (!featureDef.hidden) {
|
|
||||||
let symbol = '';
|
|
||||||
if (feature === 'jitter') {
|
|
||||||
symbol = '°';
|
|
||||||
} else if (featureDef.type === 'percentage') {
|
|
||||||
symbol = '%';
|
|
||||||
}
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
const lowerIsBeneficial = isValueBeneficial(feature, lowerBound);
|
|
||||||
const upperIsBeneficial = isValueBeneficial(feature, upperBound);
|
|
||||||
if (m) {
|
|
||||||
// We have a module - add in the current value
|
|
||||||
let current = m.getModValue(feature);
|
|
||||||
if (featureDef.type === 'percentage' || featureDef.name === 'burst' || featureDef.name === 'burstrof') {
|
|
||||||
current = Math.round(current / 10) / 10;
|
|
||||||
} else if (featureDef.type === 'numeric') {
|
|
||||||
current /= 100;
|
|
||||||
}
|
|
||||||
const currentIsBeneficial = isValueBeneficial(feature, current);
|
|
||||||
effects.push(
|
|
||||||
<tr key={feature}>
|
|
||||||
<td style={{ textAlign: 'left' }}>{translate(feature, grp)}</td>
|
|
||||||
<td className={lowerBound === 0 ? '' : lowerIsBeneficial ? 'secondary' : 'warning'} style={{ textAlign: 'right' }}>{lowerBound}{symbol}</td>
|
|
||||||
<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(
|
|
||||||
<tr key={feature}>
|
|
||||||
<td style={{ textAlign: 'left' }}>{translate(feature, grp)}</td>
|
|
||||||
<td className={lowerBound === 0 ? '' : lowerIsBeneficial ? 'secondary' : 'warning'} style={{ textAlign: 'right' }}>{lowerBound}{symbol}</td>
|
|
||||||
<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
|
|
||||||
for (const feature in m.mods) {
|
|
||||||
if (!blueprint.features[feature]) {
|
|
||||||
const featureDef = Modifications.modifications[feature];
|
|
||||||
if (featureDef && !featureDef.hidden) {
|
|
||||||
let symbol = '';
|
|
||||||
if (feature === 'jitter') {
|
|
||||||
symbol = '°';
|
|
||||||
} else if (featureDef.type === 'percentage') {
|
|
||||||
symbol = '%';
|
|
||||||
}
|
|
||||||
let current = m.getModValue(feature);
|
|
||||||
if (featureDef.type === 'percentage' || featureDef.name === 'burst' || featureDef.name === 'burstrof') {
|
|
||||||
current = Math.round(current / 10) / 10;
|
|
||||||
} else if (featureDef.type === 'numeric') {
|
|
||||||
current /= 100;
|
|
||||||
}
|
|
||||||
const currentIsBeneficial = isValueBeneficial(feature, current);
|
|
||||||
effects.push(
|
|
||||||
<tr key={feature}>
|
|
||||||
<td style={{ textAlign: 'left' }}>{translate(feature, grp)}</td>
|
|
||||||
<td> </td>
|
|
||||||
<td className={current === 0 ? '' : currentIsBeneficial ? 'secondary' : 'warning'} style={{ textAlign: 'right' }}>{current}{symbol}</td>
|
|
||||||
<td> </td>
|
|
||||||
</tr>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// We also add in any benefits from specials that aren't covered above
|
|
||||||
if (m.blueprint && m.blueprint.special) {
|
|
||||||
for (const feature in Modifications.modifierActions[m.blueprint.special.edname]) {
|
|
||||||
if (!blueprint.features[feature] && !m.mods.feature) {
|
|
||||||
const featureDef = Modifications.modifications[feature];
|
|
||||||
if (featureDef && !featureDef.hidden) {
|
|
||||||
let symbol = '';
|
|
||||||
if (feature === 'jitter') {
|
|
||||||
symbol = '°';
|
|
||||||
} else if (featureDef.type === 'percentage') {
|
|
||||||
symbol = '%';
|
|
||||||
}
|
|
||||||
let current = m.getModValue(feature);
|
|
||||||
if (featureDef.type === 'percentage' || featureDef.name === 'burst' || featureDef.name === 'burstrof') {
|
|
||||||
current = Math.round(current / 10) / 10;
|
|
||||||
} else if (featureDef.type === 'numeric') {
|
|
||||||
current /= 100;
|
|
||||||
}
|
|
||||||
const currentIsBeneficial = isValueBeneficial(feature, current);
|
|
||||||
effects.push(
|
|
||||||
<tr key={feature}>
|
|
||||||
<td style={{ textAlign: 'left' }}>{translate(feature, grp)}</td>
|
|
||||||
<td> </td>
|
|
||||||
<td className={current === 0 ? '' : currentIsBeneficial ? 'secondary' : 'warning'} style={{ textAlign: 'right' }}>{current}{symbol}</td>
|
|
||||||
<td> </td>
|
|
||||||
</tr>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let components;
|
const bpFeatures = getBlueprintInfo(blueprint).features[grade];
|
||||||
if (!m) {
|
const features = uniq(m.getModifiedProperties().concat(keys(bpFeatures)));
|
||||||
components = [];
|
|
||||||
for (const component in blueprint.components) {
|
|
||||||
components.push(
|
|
||||||
<tr key={component}>
|
|
||||||
<td style={{ textAlign: 'left' }}>{translate(component)}</td>
|
|
||||||
<td style={{ textAlign: 'right' }}>{blueprint.components[component]}</td>
|
|
||||||
</tr>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let engineersList;
|
|
||||||
if (engineers) {
|
|
||||||
engineersList = [];
|
|
||||||
for (const engineer of engineers) {
|
|
||||||
engineersList.push(
|
|
||||||
<tr key={engineer}>
|
|
||||||
<td style={{ textAlign: 'left' }}>{engineer}</td>
|
|
||||||
</tr>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<table width='100%'>
|
<table width='100%'>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<td>{translate('feature')}</td>
|
<td>{translate('feature')}</td>
|
||||||
<td>{translate('worst')}</td>
|
<td>{translate('worst')}</td>
|
||||||
{m ? <td>{translate('current')}</td> : null }
|
<td>{translate('current')}</td>
|
||||||
<td>{translate('best')}</td>
|
<td>{translate('best')}</td>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{effects}
|
{features.map((prop) => {
|
||||||
|
const { min, max, only } = bpFeatures[prop] || {};
|
||||||
|
// Skip this property if it doesn't apply to this module
|
||||||
|
if (only && !m.getItem().match(only)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const { value, unit, beneficial } = m.getModifierFormatted(prop);
|
||||||
|
if (!bpFeatures[prop] && !value) {
|
||||||
|
// Can happen for exported synthetics
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
// If the product of value and min/max is positive, both values
|
||||||
|
// point into the same direction, i.e. positive/negative.
|
||||||
|
const minBeneficial = (value * min) > 0 === beneficial;
|
||||||
|
const maxBeneficial = (value * max) > 0 === beneficial;
|
||||||
|
return (<tr key={prop}>
|
||||||
|
<td style={{ textAlign: 'left' }}>{translate(prop)}</td>
|
||||||
|
<td className={!min ? '' : minBeneficial ? 'secondary' : 'warning'} style={{ textAlign: 'right' }}>
|
||||||
|
{!isNaN(min) && formats.round(min * 100)}{!isNaN(min) && unit}
|
||||||
|
</td>
|
||||||
|
<td className={!value ? '' : beneficial ? 'secondary' : 'warning'} style={{ textAlign: 'right' }}>
|
||||||
|
{formats.round(value || 0)}{unit}
|
||||||
|
</td>
|
||||||
|
<td className={!max ? '' : maxBeneficial ? 'secondary' : 'warning'} style={{ textAlign: 'right' }}>
|
||||||
|
{!isNaN(max) && formats.round(max * 100)}{!isNaN(max) && unit}
|
||||||
|
</td>
|
||||||
|
</tr>);
|
||||||
|
})}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
{ components ? <table width='100%'>
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<td>{translate('component')}</td>
|
|
||||||
<td>{translate('amount')}</td>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{components}
|
|
||||||
</tbody>
|
|
||||||
</table> : null }
|
|
||||||
{ engineersList ? <table width='100%'>
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<td>{translate('engineers')}</td>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{engineersList}
|
|
||||||
</tbody>
|
|
||||||
</table> : null }
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Is this blueprint feature beneficial?
|
|
||||||
* @param {string} feature The name of the feature
|
|
||||||
* @param {array} values The value of the feature
|
|
||||||
* @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;
|
|
||||||
} else {
|
|
||||||
return fact;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Is this feature value beneficial?
|
|
||||||
* @param {string} feature The name of the feature
|
|
||||||
* @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 value > 0;
|
|
||||||
} else {
|
|
||||||
return value < 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a blueprint with a given name and an optional module
|
* Get a blueprint with a given name and an optional module
|
||||||
* @param {string} name The name of the blueprint
|
* @param {string} name The name of the blueprint
|
||||||
@@ -296,122 +125,3 @@ export function getBlueprint(name, module) {
|
|||||||
const blueprint = JSON.parse(JSON.stringify(found));
|
const blueprint = JSON.parse(JSON.stringify(found));
|
||||||
return blueprint;
|
return blueprint;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Provide 'percent' primary modifications
|
|
||||||
* @param {Object} ship The ship for which to perform the modifications
|
|
||||||
* @param {Object} m The module for which to perform the modifications
|
|
||||||
* @param {Number} percent The percent to set values to of full.
|
|
||||||
*/
|
|
||||||
export function setPercent(ship, m, percent) {
|
|
||||||
ship.clearModifications(m);
|
|
||||||
// Pick given value as multiplier
|
|
||||||
const mult = percent / 100;
|
|
||||||
const features = m.blueprint.grades[m.blueprint.grade].features;
|
|
||||||
for (const featureName in features) {
|
|
||||||
let value;
|
|
||||||
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)) {
|
|
||||||
value = features[featureName][1] + ((features[featureName][0] - features[featureName][1]) * mult);
|
|
||||||
} else {
|
|
||||||
value = features[featureName][0] + ((features[featureName][1] - features[featureName][0]) * mult);
|
|
||||||
}
|
|
||||||
} 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)) {
|
|
||||||
value = features[featureName][0] + ((features[featureName][1] - features[featureName][0]) * mult);
|
|
||||||
} else {
|
|
||||||
value = features[featureName][1] + ((features[featureName][0] - features[featureName][1]) * mult);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_setValue(ship, m, featureName, value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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
|
|
||||||
*/
|
|
||||||
export function setRandom(ship, m) {
|
|
||||||
// Pick a single value for our randomness
|
|
||||||
setPercent(ship, m, Math.random() * 100);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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
|
|
||||||
* @param {number} value The value being set for the feature
|
|
||||||
*/
|
|
||||||
function _setValue(ship, m, featureName, value) {
|
|
||||||
if (Modifications.modifications[featureName].type == 'percentage') {
|
|
||||||
ship.setModification(m, featureName, value * 10000);
|
|
||||||
} else if (Modifications.modifications[featureName].type == 'numeric') {
|
|
||||||
ship.setModification(m, featureName, value * 100);
|
|
||||||
} else {
|
|
||||||
ship.setModification(m, featureName, value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Provide 'percent' primary query
|
|
||||||
* @param {Object} m The module for which to perform the query
|
|
||||||
* @returns {Number} percent The percentage indicator of current applied values.
|
|
||||||
*/
|
|
||||||
export function getPercent(m) {
|
|
||||||
let result = null;
|
|
||||||
const features = m.blueprint.grades[m.blueprint.grade].features;
|
|
||||||
for (const featureName in features) {
|
|
||||||
if (features[featureName][0] === features[featureName][1]) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
let value = _getValue(m, featureName);
|
|
||||||
let mult;
|
|
||||||
if (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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -30,6 +30,8 @@ export const SHIP_FD_NAME_TO_CORIOLIS_NAME = {
|
|||||||
'Hauler': 'hauler',
|
'Hauler': 'hauler',
|
||||||
'Independant_Trader': 'keelback',
|
'Independant_Trader': 'keelback',
|
||||||
'Krait_MkII': 'krait_mkii',
|
'Krait_MkII': 'krait_mkii',
|
||||||
|
'Mamba': 'mamba',
|
||||||
|
'Krait_Light': 'krait_phantom',
|
||||||
'Orca': 'orca',
|
'Orca': 'orca',
|
||||||
'Python': 'python',
|
'Python': 'python',
|
||||||
'SideWinder': 'sidewinder',
|
'SideWinder': 'sidewinder',
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user